aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-10-15 06:49:35 +0100
committerIgor Soarez <igor@soarez.org>2026-10-15 06:49:35 +0100
commitcc86d6114291305fcea1ed8b0904c8be0e633767 (patch)
treef8647ed99c461e5a20560a5e927c6285a62e8e94
parent50c251e082b8122bfd1a6e80427d0dc19fa4f246 (diff)
pi-chrome 0.15.57: stand in for pi-tui, collect the primer from prompt sections
-rw-r--r--mcp/pi-tui.mjs3
-rwxr-xr-xmcp/server.mjs23
2 files changed, 21 insertions, 5 deletions
diff --git a/mcp/pi-tui.mjs b/mcp/pi-tui.mjs
new file mode 100644
index 0000000..836d686
--- /dev/null
+++ b/mcp/pi-tui.mjs
@@ -0,0 +1,3 @@
+// Stands in for @earendil-works/pi-tui, which pi-chrome imports for its transcript renderers. Those
+// draw pi's terminal UI and never run here, so the classes only have to exist.
+export class Text {}
diff --git a/mcp/server.mjs b/mcp/server.mjs
index 9c3bea4..c6e70f1 100755
--- a/mcp/server.mjs
+++ b/mcp/server.mjs
@@ -211,6 +211,8 @@ const pi = {
active = new Set(names);
},
sendMessage() {},
+ // pi-chrome logs authorization changes to the session history; there is none here.
+ appendEntry() {},
exec: async () => ({ code: 1, stdout: "", stderr: "not available in the MCP server" }),
};
@@ -223,10 +225,14 @@ async function emit(event, payload) {
// --- load pi-chrome ---------------------------------------------------------------------------
// pi-chrome imports typebox at runtime, and its own directory may have no node_modules: point it
-// at the copy pinned here, the version pi ships. jiti rather than Node's type stripping, because
-// pi-chrome uses constructor parameter properties, which strip-only mode rejects.
+// at the copy pinned here, the version pi ships, and pi-tui at a stand-in. jiti rather than Node's
+// type stripping, because pi-chrome uses constructor parameter properties, which strip-only mode
+// rejects.
const jiti = createJiti(import.meta.url, {
- alias: { typebox: fileURLToPath(import.meta.resolve("typebox")) },
+ alias: {
+ typebox: fileURLToPath(import.meta.resolve("typebox")),
+ "@earendil-works/pi-tui": fileURLToPath(new URL("pi-tui.mjs", import.meta.url)),
+ },
});
const extension = await jiti.import(EXTENSION, { default: true });
extension(pi);
@@ -245,12 +251,19 @@ if (active.size === 0) {
// A fork's primer may name another browser's tools, which differ by client: browser_* in pi,
// mcp__<server>__* elsewhere. pi-chrome reads PI_CHROME_OTHER_BROWSER_TOOLS as it builds the
// primer, synchronously inside the handler, so the variable only has to hold for that call.
+const UPSTREAM_SECTION = "chrome-profile-bridge";
async function instructionsWith(otherTools) {
const saved = process.env.PI_CHROME_OTHER_BROWSER_TOOLS;
if (otherTools) process.env.PI_CHROME_OTHER_BROWSER_TOOLS = otherTools;
- const pending = emit("before_agent_start", { systemPrompt: "" });
+ // pi-chrome adds its primer as named prompt sections, one per handler (upstream's and a fork's),
+ // or, written for an older pi, returns a whole systemPrompt instead. Take both; upstream's goes
+ // first.
+ const sections = {};
+ const pending = emit("before_agent_start", { systemPrompt: "", systemPromptOptions: { sections } });
process.env.PI_CHROME_OTHER_BROWSER_TOOLS = saved;
- const primer = (await pending)?.systemPrompt ?? "";
+ const returned = (await pending)?.systemPrompt ?? "";
+ const names = Object.keys(sections).sort((a, b) => (b === UPSTREAM_SECTION) - (a === UPSTREAM_SECTION));
+ const primer = [...names.map((name) => `<${name}>\n${sections[name]}\n</${name}>`), returned].join("\n");
return [ESSENTIALS, primer.split("\n").filter((line) => !/\/chrome\b/.test(line)).join("\n")].join("\n");
}
const OTHER_BROWSER_TOOLS = process.env.PI_CHROME_OTHER_BROWSER_TOOLS?.trim();