Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/quiet-pandas-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sideshow": patch
---

Allow Pi-compatible agents to hide the persistent Sideshow TUI status with `SIDESHOW_TUI_STATUS=0` while retaining native tools and trace synchronization.
4 changes: 4 additions & 0 deletions docs/connecting-agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pi install npm:sideshow
pi -e npm:sideshow
```

The extension shows its current server or session in Pi's TUI status area by
default. Set `SIDESHOW_TUI_STATUS=0` before launching Pi to hide that status
without disabling the native tools or automatic trace synchronization.

## MCP

Tools: `publish_post`, `update_post`, `list_posts`, `get_post`,
Expand Down
1 change: 1 addition & 0 deletions extensions/sideshow.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function sideshowExtension(pi: Record<string, unknown>): void;
26 changes: 19 additions & 7 deletions extensions/sideshow.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ function agentName() {
return process.env.SIDESHOW_AGENT || "pi";
}

function showTuiStatus() {
return process.env.SIDESHOW_TUI_STATUS !== "0";
}

function authHeaders(extra = {}) {
return {
...(process.env.SIDESHOW_TOKEN
Expand Down Expand Up @@ -379,12 +383,16 @@ export default function sideshowExtension(pi) {

pi.on("session_start", (_event, ctx) => {
state.sessionId = reconstructSession(ctx);
ctx.ui.setStatus(
"sideshow",
state.sessionId
? `sideshow ${state.sessionId}`
: `sideshow ${baseUrl().replace(/^https?:\/\//, "")}`,
);
if (showTuiStatus()) {
ctx.ui.setStatus(
"sideshow",
state.sessionId
? `sideshow ${state.sessionId}`
: `sideshow ${baseUrl().replace(/^https?:\/\//, "")}`,
);
} else {
ctx.ui.setStatus("sideshow", undefined);
}
});

pi.on("turn_end", async (_event, ctx) => {
Expand All @@ -403,7 +411,11 @@ export default function sideshowExtension(pi) {
const command = args.trim();
if (command === "reset") {
state.sessionId = process.env.SIDESHOW_SESSION || undefined;
ctx.ui.setStatus("sideshow", `sideshow ${baseUrl().replace(/^https?:\/\//, "")}`);
if (showTuiStatus()) {
ctx.ui.setStatus("sideshow", `sideshow ${baseUrl().replace(/^https?:\/\//, "")}`);
} else {
ctx.ui.setStatus("sideshow", undefined);
}
ctx.ui.notify("Reset remembered sideshow session", "info");
return;
}
Expand Down
75 changes: 75 additions & 0 deletions test/piExtension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import assert from "node:assert/strict";
import { afterEach, describe, it } from "node:test";

import sideshowExtension from "../extensions/sideshow.js";

const originalTuiStatus = process.env.SIDESHOW_TUI_STATUS;

type Status = [key: string, text: string | undefined];
interface Context {
sessionManager: { getBranch: () => never[] };
ui: {
setStatus: (key: string, text: string | undefined) => number;
notify: () => void;
};
}
type Handler = (event: object, context: Context) => void;
type Command = { handler: (args: string, context: Context) => Promise<void> };

afterEach(() => {
if (originalTuiStatus === undefined) delete process.env.SIDESHOW_TUI_STATUS;
else process.env.SIDESHOW_TUI_STATUS = originalTuiStatus;
});

function loadExtension() {
const handlers = new Map<string, Handler>();
const commands = new Map<string, Command>();
const pi = {
on(event: string, handler: Handler) {
handlers.set(event, handler);
},
registerCommand(name: string, command: Command) {
commands.set(name, command);
},
registerTool() {},
};
sideshowExtension(pi);
return { handlers, commands };
}

function context(statuses: Status[]) {
return {
sessionManager: { getBranch: () => [] },
ui: {
setStatus: (key: string, text: string | undefined) => statuses.push([key, text]),
notify() {},
},
};
}

describe("Pi extension TUI status", () => {
it("shows status by default", () => {
delete process.env.SIDESHOW_TUI_STATUS;
const { handlers } = loadExtension();
const statuses: Status[] = [];

handlers.get("session_start")!({}, context(statuses));

assert.deepEqual(statuses, [["sideshow", "sideshow localhost:8228"]]);
});

it("clears status when SIDESHOW_TUI_STATUS=0", async () => {
process.env.SIDESHOW_TUI_STATUS = "0";
const { handlers, commands } = loadExtension();
const statuses: Status[] = [];
const ctx = context(statuses);

handlers.get("session_start")!({}, ctx);
await commands.get("sideshow")!.handler("reset", ctx);

assert.deepEqual(statuses, [
["sideshow", undefined],
["sideshow", undefined],
]);
});
});