From c8b12b6384c8c0ee18d35ac0a6e083ba1157336c Mon Sep 17 00:00:00 2001 From: DietrichGebert Date: Wed, 24 Jun 2026 01:10:04 +0200 Subject: [PATCH] fix(pi-extension): guard status bar render when ui has no theme (#279) syncStatus guarded setStatus but used theme.fg unguarded, so a Pi host exposing setStatus without a theme threw TypeError on session_start (and agent_start/agent_end/setMode). Require both before rendering. Add tests for the render path (previously untested) and the theme-absent degradation. Follow-up to #275 / #84. --- pi-extension/index.js | 2 +- pi-extension/test/extension.test.js | 30 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pi-extension/index.js b/pi-extension/index.js index 0eb9375..5f17627 100644 --- a/pi-extension/index.js +++ b/pi-extension/index.js @@ -63,7 +63,7 @@ export default function ponytailExtension(pi) { function syncStatus(ctx) { if (ctx) lastCtx = ctx; const c = ctx || lastCtx; - if (!c?.ui?.setStatus) return; + if (!c?.ui?.setStatus || !c.ui.theme?.fg) return; const theme = c.ui.theme; if (currentMode === "off") { c.ui.setStatus("ponytail", ""); diff --git a/pi-extension/test/extension.test.js b/pi-extension/test/extension.test.js index 969b467..b5f3918 100644 --- a/pi-extension/test/extension.test.js +++ b/pi-extension/test/extension.test.js @@ -135,3 +135,33 @@ test("a request mentioning normal mode stays active", async () => withTempConfig const result = await events.get("before_agent_start")({ systemPrompt: "BASE" }, ctx); assert.match(result.systemPrompt, /PONYTAIL MODE ACTIVE/); })); + +test("status bar renders the mode and flips active on agent_start", async () => withTempConfig(async () => { + const { events } = createPiHarness(); + const statusWrites = []; + const ctx = createCommandContext({ + sessionManager: { getEntries: () => [{ type: "custom", customType: "ponytail-mode", data: { mode: "ultra" } }] }, + ui: { notify() {}, setStatus: (key, text) => statusWrites.push({ key, text }), theme: { fg: (_color, text) => text } }, + }); + + await events.get("session_start")({ reason: "resume" }, ctx); + await events.get("agent_start")({}, ctx); + + assert.equal(statusWrites.at(-2).key, "ponytail"); + assert.match(statusWrites.at(-2).text, /○.*ULTRA/); + assert.match(statusWrites.at(-1).text, /●.*ULTRA/); +})); + +test("status bar stays silent when ui lacks a theme", async () => withTempConfig(async () => { + const { events } = createPiHarness(); + const calls = []; + const ctx = createCommandContext({ + sessionManager: { getEntries: () => [{ type: "custom", customType: "ponytail-mode", data: { mode: "ultra" } }] }, + ui: { notify() {}, setStatus: (_key, text) => calls.push(text) }, // setStatus present, theme absent + }); + + await events.get("session_start")({ reason: "resume" }, ctx); + await events.get("agent_start")({}, ctx); + + assert.deepEqual(calls, []); +}));