Skip to content

fix(pi): stop Calm claiming built-in tool names that refuse Pi's launch - #2

Merged
Bloh09 merged 4 commits into
mainfrom
fm/fm-pi-ext-tool-collision
Aug 7, 2026
Merged

fix(pi): stop Calm claiming built-in tool names that refuse Pi's launch#2
Bloh09 merged 4 commits into
mainfrom
fm/fm-pi-ext-tool-collision

Conversation

@Bloh09

@Bloh09 Bloh09 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Intent

Fix the defect that makes Pi refuse to start inside a firstmate worktree when the operator has their own Pi extension registering the same built-in tools.

DELIVERY TARGET: this work ships to the captain's own fork, https://github.com/Bloh09/firstmate.git, which is now origin. It must never be pushed to, or opened as a PR against, kunchenguid/firstmate - the captain does not own that repository and has a standing rule against opening or merging PRs into repositories they do not own. A previous run of this exact work failed only at the push step with a 403 because origin was still the upstream repo; origin has since been repointed to the fork and the gate re-initialized. Nothing else about the change has altered.

This is a re-run of an already-validated change, preserved at head 915af5d. The prior run passed intent, rebase, review, test, document and lint, and its review and document fix commits are already part of this branch (60ef61b, 915af5d). Only push failed, so expect this to move quickly.

Reproduction the captain hit: spawning a Pi crewmate into a firstmate worktree printed 'Failed to load extension "~/.pi/agent/extensions/zz-read-only-mode.ts": Tool "read" conflicts with /.pi/extensions/fm-calm.ts' (and grep/find/ls), then dropped straight back to a shell with no agent running.

Diagnosis established with evidence, not assumption. The brief's leading hypothesis - that loadCalmPreference() cannot resolve config/calm from a crewmate worktree and falls back to on - was FALSIFIED: it catches and returns false, so an unresolvable preference means Calm off. The real cause is that Pi's ResourceLoader.detectExtensionConflicts makes one pass over the fully loaded extension set and records an ERROR diagnostic for every later claim of an already-registered tool name, and main.js exits status 1 on any error diagnostic before the TUI opens. Both extensions stay loaded and precedence still follows load order, but the launch is refused. Firstmate's project-local extension resolves before the operator's global one, so the operator's extension took the diagnostic and the session died. The captain's observation predates PR kunchenguid#1724 (verified by reproducing the exact output at 71f0b3f^); that PR narrowed the exposure to Calm-on sessions but left them aborting the launch outright, which was the live defect.

Requirements the captain set: (1) Calm off must claim nothing; (2) firstmate must never prevent Pi from starting, even with Calm genuinely on; (3) the same bug class must not recur silently in fm-primary-pi-watch.ts or fm-primary-turnend-guard.ts. Constraints: do not weaken Calm's presentation to dodge the collision; do not touch ~/.pi or any operator-owned configuration; do not change how any other harness launches.

Deliberate design decision, explicitly authorized by the captain's brief. The brief's preferred shape - have the load-time path do the same contested-name check as activateBuiltInsIfNeeded() - is provably unreachable: pi.getAllTools() throws before extension binding, Pi exposes no pre-bind list of the extensions it is about to load, and deferring registration to session_start loses restored tool rows because Pi builds them from a pre-session_start registry snapshot. Registration cannot be made both safe and complete. So Calm now registers NO ToolDefinition at all, and .pi/extensions/lib/fm-calm-tool-row-layout.ts collapses the seven built-in rows at render time by patching ToolExecutionComponent.render, reaching the exact zero-height path the registered wrappers used. This deletes the collision class rather than working around it. The large deletion in fm-calm.ts (wrapBuiltIn, wrappedBuiltIns, builtInsRegistered, contestedBuiltIns, activateBuiltInsIfNeeded, reportBuiltInLosses, realpathOrSelf) is intended: every one of those existed only to manage a collision that can no longer occur. The adapter's Pi import is deliberately type-only, with the runtime class reached through the namespace import, so the typeof probe stays the only failure path and fm-calm.ts can catch it and skip just this adapter - a static value import would fail at ESM link time and defeat that contract.

Intended behavior changes, all improvements, none accidental: rows restored before a first /calm activation now collapse too, lifting the previously documented non-retroactive bound; Calm's coverage now applies even when another extension owns the name, while that extension keeps its own execution; and Pi again builds its built-ins from the live session cwd and from the operator's read auto-resize-images and bash command-prefix/shell-path settings, which Calm's wrappers had silently discarded. Coverage is otherwise deliberately unchanged - the same seven names (bash, read, edit, write, grep, find, ls), disclosed built-in images still visible, third-party custom tool rows still visible, byte-identical stock rendering while Calm is off.

On requirement 3: fm-primary-turnend-guard.ts registers no tool, and fm-primary-pi-watch.ts registers only the namespaced fm_watch_arm_pi; a test pins that every tracked firstmate Pi extension stays inside the fm_/fm- namespace.

Verification and its constraint: the captain's Pi account has NO usage quota, so no Pi model turn was run and none may be. Acceptance is that Pi OPENS correctly. Verified by launching the real Pi TUI with cwd inside this firstmate worktree and the captain's real ~/.pi/agent/extensions in place (including zz-read-only-mode.ts), with Calm off and again with Calm on: both start with zero extension-load errors and stay up, where both previously aborted. Nothing under ~/.pi was modified. Automated coverage added in the repo's existing colocated test style: test_no_tool_name_claims, test_tool_row_collapse_without_name_claims, and test_launch_beside_foreign_builtin_extension, the last of which drives the real pi executable through its own conflict-diagnostic path with no model turn and carries a positive control so it cannot pass vacuously. All three fail against the pre-fix extension.

This is firstmate's own shared tracked material, so .agents/skills/firstmate-coding-guidelines/SKILL.md applies: docs/calm.md and docs/calm-mode-feasibility.md were patched in place as the one owner of the user-facing contract and the Pi-source evidence respectively.

One incidental repair is included because it blocked verifying this change: tests/fm-calm-pi-extension.test.sh had a fixture stub predating Pi 0.84.0's getMarkdownTransformers, already failing test_rendering_and_session_lifecycle on the default branch.

KNOWN PRE-EXISTING FAILURES, deliberately NOT fixed here and confirmed by the previous run to reproduce identically on base commit 70aeba8 without this change, in areas this change does not touch: test_operational_followup_turn_e2e ('Pi follow-up loaded_on case rendered a duplicate captain answer') and test_interactive_terminal_e2e ('/export did not complete while calm mode was on'). The captain's explicit instruction is to report those rather than absorb them, so do not expand scope to them.

What Changed

  • .pi/extensions/fm-calm.ts no longer registers any ToolDefinition. The seven built-in wrappers and the collision machinery they needed (wrapBuiltIn, wrappedBuiltIns, builtInsRegistered, contestedBuiltIns, activateBuiltInsIfNeeded, reportBuiltInLosses, realpathOrSelf) are deleted, so Pi's detectExtensionConflicts can no longer record a fatal Tool "<name>" conflicts with ... diagnostic against an operator extension that claims bash, read, edit, write, grep, find, or ls. Coverage moves to a new .pi/extensions/lib/fm-calm-tool-row-layout.ts, which patches ToolExecutionComponent.render to collapse those seven rows at render time, reuses Pi's own per-row image state so disclosed images stay visible, and is installed through the existing installCalmPresentationAdapter probe so a missing seam degrades this adapter alone. Its Pi import is type-only with the runtime class read off the namespace import, keeping the typeof probe the only failure path.
  • tests/fm-calm-pi-extension.test.sh replaces test_builtin_gate_load_time and test_calm_activation_collision_and_regression_bound with test_no_tool_name_claims, test_tool_row_collapse_without_name_claims, and test_launch_beside_foreign_builtin_extension, the last driving the real pi executable beside a foreign extension claiming all seven names, with a positive control. The same file's fixture stub was updated for Pi 0.84.0's getMarkdownTransformers, which was already failing test_rendering_and_session_lifecycle. tests/fm-pi-primary-types.test.sh and tests/fm-pi-primary-live-e2e.test.sh now copy the new adapter into their fixtures.
  • Documentation follows the code: docs/calm.md states that Calm registers no tool, collapses rows regardless of which extension owns the name, and drops the previous non-retroactive first-toggle bound; docs/calm-mode-feasibility.md records the ResourceLoader.detectExtensionConflicts / main.js exit evidence, why no in-extension check can avoid it, and extends the verified Pi range to 0.84.0; .agents/skills/harness-adapters/SKILL.md pins the fm_ namespace rule for tracked Firstmate Pi extensions.
  • The branch delta against this base also carries 72 upstream commits already merged on the default branch (remote secondmates, process-event sources, Muse adapter, Herdr backend work, and their tests and docs); only the four commits above are new to this branch.

Risk Assessment

✅ Low: The change deletes a whole failure class rather than patching a symptom - Calm now registers no ToolDefinition, so Pi's fatal tool-name conflict diagnostic is unreachable from Firstmate - and the replacement render-time collapse reproduces Pi's own zero-height path exactly, degrades independently behind a probe, and is pinned by a real-pi-binary launch regression test carrying a positive control; the only remaining exposure is the same undocumented-Pi-internal dependency the two pre-existing sibling adapters already carry, and both findings are cosmetic.

Testing

I validated the fix at the level the captain actually hit it: the real Pi 0.84.0 TUI launching inside a Firstmate project beside the operator's own global zz-read-only-mode.ts. With the pre-fix extension restored from base 70aeba8, Pi printed the exact reported errors ('Tool "read" conflicts with .../fm-calm.ts', plus grep, find and ls) and dropped back to the shell with exit 1; with this change Pi opens and stays up with Calm off, with Calm already on at launch, and after /calm is toggled live, with both extensions listed in Pi's own [Extensions] banner and zero load errors. A before/after screenshot of those four real terminal states is the primary artifact. On top of that, the three new automated tests pass on HEAD and all three fail against the pre-fix extension, so the regression is genuinely pinned; the real-pi launch test also carries a positive control that I saw hold. No Pi model turn was run and nothing under ~/.pi was modified, per the captain's quota and configuration constraints. The two E2E tests the intent flags as pre-existing failures do still fail, but they fail identically on base 70aeba8, so they are not this change's doing; the touched type test gate-skipped locally because tsc is not installed, leaving that contract to CI. Transient fixtures and temp test drivers were removed and the worktree is clean.

  • Evidence: Pi TUI before/after: launch beside an operator extension claiming the same built-in tool names (local file: /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/pi-launch-evidence.png)
Evidence: Same evidence as a rendered HTML page (ANSI-faithful terminal captures)
<!doctype html><meta charset="utf-8"><title>Pi launch beside an operator extension that claims the same built-in tool names</title>
<style>
body{background:#12141a;color:#c5c8c6;font-family:ui-sans-serif,system-ui,sans-serif;margin:0;padding:28px}
h1{font-size:19px;margin:0 0 6px;color:#eaeaea}
p.sub{margin:0 0 26px;color:#8a8f98;font-size:13px;max-width:1100px;line-height:1.5}
section{margin:0 0 26px}
h2{font-size:13px;margin:0 0 8px;letter-spacing:.02em;font-weight:600}
h2.bad{color:#e05252} h2.good{color:#7fbf5f}
pre{background:#1d1f21;border:1px solid #2c3038;border-left:3px solid #444;border-radius:6px;padding:14px 16px;margin:0;
 font-family:"DejaVu Sans Mono","Menlo",monospace;font-size:12px;line-height:1.32;white-space:pre;overflow-x:auto;color:#c5c8c6}
h2.bad + pre{border-left-color:#e05252} h2.good + pre{border-left-color:#7fbf5f}
</style>
<h1>Pi launch beside an operator extension that claims the same built-in tool names</h1>
<p class="sub">Real <code>pi</code> 0.84.0 TUI, launched with cwd inside a Firstmate project whose <code>.pi/extensions</code> carries <code>fm-calm.ts</code>, beside the operator's own global <code>zz-read-only-mode.ts</code> (registers <code>read</code>, <code>grep</code>, <code>find</code>, <code>ls</code>). No model turn was run.</p>
<section><h2 class="bad">BEFORE - pre-fix fm-calm.ts, Calm on: Pi refuses to start and drops straight back to the shell (exit 1, no TUI)</h2><pre><span style="color:#cc6666">Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":</span>
<span style="color:#cc6666">Tool "read" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts</span>
<span style="color:#cc6666">Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":</span>
<span style="color:#cc6666">Tool "grep" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts</span>
<span style="color:#cc6666">Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":</span>
<span style="color:#cc6666">Tool "find" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts</span>
<span style="color:#cc6666">Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":</span>
<span style="color:#cc6666">Tool "ls" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts</span>
<span style="color:#f0c674">Hint: Start without extensions using "pi -ne".</span>
PI_EXIT=1
</pre></section>
<section><h2 class="good">AFTER - this change, Calm off: Pi opens, both fm-calm.ts and zz-read-only-mode.ts loaded, zero extension-load errors</h2><pre>

<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ███████████████████████████╗  </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ╚══██████╔════════██████╔══╝  </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ████████████╗  ████████████╗  </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ╚═══════════╝  ╚═══════════╝  </span>

<span style="font-weight:700"></span><span style="color:rgb(138,190,183);font-weight:700">pi</span><span style="color:rgb(102,102,102)"> v0.84.0</span>

<span style="color:rgb(240,198,116)">[Skills]</span>
<span style="color:rgb(102,102,102)">  ponytail, ponytail-audit, ponytail-debt, ponytail-gain, ponytail-help, ponytail-review</span>

<span style="color:rgb(240,198,116)">[Extensions]</span>
<span style="color:rgb(102,102,102)">  algal/pi-openai-server-compaction:src, ask-user-question.ts, auto-compact-75.ts, codex-usage-bar.ts, context.ts,</span>
<span style="color:rgb(102,102,102)">custom-header.ts, DietrichGebert/ponytail:pi-extension, filechanges, fm-calm.ts, fm-primary-pi-watch.ts,</span>
<span style="color:rgb(102,102,102)">fm-primary-turnend-guard.ts, herdr-agent-state.ts, md-link.ts, memory.ts, pi-gpt-fast-mode, pi-herd.js, pi-web-access@0.14.0,</span>
<span style="color:rgb(102,102,102)">usage-bar, zz-read-only-mode.ts</span>


 <span style="color:rgb(102,102,102)">Ponytail loaded: full</span>

 <span style="color:rgb(255,255,0)">Warning: tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart</span>
 <span style="color:rgb(255,255,0)">tmux.</span>

<span style="color:rgb(255,255,0)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
 <span style="font-weight:700"></span><span style="color:rgb(255,255,0);font-weight:700">Update Available</span>
 <span style="color:rgb(128,128,128)">New version 0.84.1 is available. Run </span><span style="color:rgb(138,190,183)">pi update</span>
 <span style="color:rgb(128,128,128)">Changelog: </span><span style="color:rgb(138,190,183)">https://pi.dev/changelog</span>
<span style="color:rgb(255,255,0)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>

<span style="color:rgb(178,148,187)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
<span style="color:#1d1f21;background:rgb(178,148,187)"></span><span style="color:#1d1f21;background:#c5c8c6"> </span>
<span style="color:rgb(178,148,187)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
<span style="color:rgb(102,102,102)">~/project</span>
<

... [3961 bytes truncated] ...

pan style="font-weight:700"></span><span style="color:rgb(255,255,0);font-weight:700">Update Available</span>
 <span style="color:rgb(128,128,128)">New version 0.84.1 is available. Run </span><span style="color:rgb(138,190,183)">pi update</span>
 <span style="color:rgb(128,128,128)">Changelog: </span><span style="color:rgb(138,190,183)">https://pi.dev/changelog</span>
<span style="color:rgb(255,255,0)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>

<span style="color:rgb(178,148,187)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
<span style="color:#1d1f21;background:rgb(178,148,187)"></span><span style="color:#1d1f21;background:#c5c8c6"> </span>
<span style="color:rgb(178,148,187)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
<span style="color:rgb(102,102,102)">~/project</span>
<span style="color:rgb(102,102,102)">$0.000 (sub) 0.0%/500k (auto)                                                                                (xai) grok-4.5 • high</span>
<span style="color:rgb(138,190,183)">Grok</span> <span style="color:rgb(255,255,0)">usage check failed: {"code":"personal-team-blocked:spending-limit","error":"You have run out of credits or need a Grok sub</span><span style="color:rgb(102,102,102)">...</span>
</pre></section>
<section><h2 class="good">AFTER - /calm typed in the live session: Calm activates ("Tool output: collapsed") and the session stays up</h2><pre>

<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ███████████████████████████╗  </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ╚══██████╔════════██████╔══╝  </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">      ██████║        ██████║     </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ████████████╗  ████████████╗  </span>
<span style="font-weight:700"></span><span style="color:rgb(181,189,104);font-weight:700">   ╚═══════════╝  ╚═══════════╝  </span>

<span style="font-weight:700"></span><span style="color:rgb(138,190,183);font-weight:700">pi</span><span style="color:rgb(102,102,102)"> v0.84.0</span>

<span style="color:rgb(240,198,116)">[Skills]</span>
<span style="color:rgb(102,102,102)">  ponytail, ponytail-audit, ponytail-debt, ponytail-gain, ponytail-help, ponytail-review</span>

<span style="color:rgb(240,198,116)">[Extensions]</span>
<span style="color:rgb(102,102,102)">  algal/pi-openai-server-compaction:src, ask-user-question.ts, auto-compact-75.ts, codex-usage-bar.ts, context.ts,</span>
<span style="color:rgb(102,102,102)">custom-header.ts, DietrichGebert/ponytail:pi-extension, filechanges, fm-calm.ts, fm-primary-pi-watch.ts,</span>
<span style="color:rgb(102,102,102)">fm-primary-turnend-guard.ts, herdr-agent-state.ts, md-link.ts, memory.ts, pi-gpt-fast-mode, pi-herd.js, pi-web-access@0.14.0,</span>
<span style="color:rgb(102,102,102)">usage-bar, zz-read-only-mode.ts</span>


 <span style="color:rgb(102,102,102)">Ponytail loaded: full</span>

 <span style="color:rgb(255,255,0)">Warning: tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart</span>
 <span style="color:rgb(255,255,0)">tmux.</span>

<span style="color:rgb(255,255,0)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
 <span style="font-weight:700"></span><span style="color:rgb(255,255,0);font-weight:700">Update Available</span>
 <span style="color:rgb(128,128,128)">New version 0.84.1 is available. Run </span><span style="color:rgb(138,190,183)">pi update</span>
 <span style="color:rgb(128,128,128)">Changelog: </span><span style="color:rgb(138,190,183)">https://pi.dev/changelog</span>
<span style="color:rgb(255,255,0)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>

 <span style="color:rgb(102,102,102)">Tool output: collapsed</span>

<span style="color:rgb(178,148,187)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
<span style="color:#1d1f21;background:rgb(178,148,187)"></span><span style="color:#1d1f21;background:#c5c8c6"> </span>
<span style="color:rgb(178,148,187)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</span>
<span style="color:rgb(102,102,102)">~/project</span>
<span style="color:rgb(102,102,102)">$0.000 (sub) 0.0%/500k (auto)                                                                                (xai) grok-4.5 • high</span>
<span style="color:rgb(138,190,183)">Grok</span> <span style="color:rgb(255,255,0)">usage check failed: {"code":"personal-team-blocked:spending-limit","error":"You have run out of credits or need a Grok sub</span><span style="color:rgb(102,102,102)">...</span>
</pre></section>
Evidence: BEFORE - pre-fix fm-calm.ts, Calm on: real Pi refuses to start

Error: Failed to load extension ".../agent/extensions/zz-read-only-mode.ts": Tool "read" conflicts with .../project/.pi/extensions/fm-calm.ts Error: Failed to load extension ".../agent/extensions/zz-read-only-mode.ts": Tool "grep" conflicts with .../project/.pi/extensions/fm-calm.ts Error: Failed to load extension ".../agent/extensions/zz-read-only-mode.ts": Tool "find" conflicts with .../project/.pi/extensions/fm-calm.ts Error: Failed to load extension ".../agent/extensions/zz-read-only-mode.ts": Tool "ls" conflicts with .../project/.pi/extensions/fm-calm.ts Hint: Start without extensions using "pi -ne". PI_EXIT=1

Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":
Tool "read" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":
Tool "grep" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":
Tool "find" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/agent/extensions/zz-read-only-mode.ts":
Tool "ls" conflicts with /tmp/no-mistakes-evidence/01KZER916F3Y3RTBF9VR455RXC/tui/project/.pi/extensions/fm-calm.ts
Hint: Start without extensions using "pi -ne".
PI_EXIT=1
Evidence: AFTER - this change, Calm off: Pi TUI opens with both extensions loaded

pi v0.84.0 [Extensions] algal/pi-openai-server-compaction:src, ask-user-question.ts, auto-compact-75.ts, codex-usage-bar.ts, context.ts, custom-header.ts, DietrichGebert/ponytail:pi-extension, filechanges, fm-calm.ts, fm-primary-pi-watch.ts, fm-primary-turnend-guard.ts, herdr-agent-state.ts, md-link.ts, memory.ts, pi-gpt-fast-mode, pi-herd.js, pi-web-access@0.14.0, usage-bar, zz-read-only-mode.ts ~/project $0.000 (sub) 0.0%/500k (auto) (xai) grok-4.5 - high Grok usage check failed: {"code":"personal-team-blocked:spending-limit", ...} <- no model turn was run



   ███████████████████████████╗
   ╚══██████╔════════██████╔══╝
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
   ████████████╗  ████████████╗
   ╚═══════════╝  ╚═══════════╝

pi v0.84.0

[Skills]
  ponytail, ponytail-audit, ponytail-debt, ponytail-gain, ponytail-help, ponytail-review

[Extensions]
  algal/pi-openai-server-compaction:src, ask-user-question.ts, auto-compact-75.ts, codex-usage-bar.ts, context.ts,
custom-header.ts, DietrichGebert/ponytail:pi-extension, filechanges, fm-calm.ts, fm-primary-pi-watch.ts,
fm-primary-turnend-guard.ts, herdr-agent-state.ts, md-link.ts, memory.ts, pi-gpt-fast-mode, pi-herd.js, pi-web-access@0.14.0,
usage-bar, zz-read-only-mode.ts


 Ponytail loaded: full

 Warning: tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart
 tmux.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Update Available
 New version 0.84.1 is available. Run pi update
 Changelog: https://pi.dev/changelog
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
~/project
$0.000 (sub) 0.0%/500k (auto)                                                                                (xai) grok-4.5 • high
Grok usage check failed: {"code":"personal-team-blocked:spending-limit","error":"You have run out of credits or need a Grok sub...
Evidence: AFTER - /calm activated live in the running session

Tool output: collapsed (persisted preference after the toggle: config/calm = on; session remained up)



   ███████████████████████████╗
   ╚══██████╔════════██████╔══╝
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
      ██████║        ██████║
   ████████████╗  ████████████╗
   ╚═══════════╝  ╚═══════════╝

pi v0.84.0

[Skills]
  ponytail, ponytail-audit, ponytail-debt, ponytail-gain, ponytail-help, ponytail-review

[Extensions]
  algal/pi-openai-server-compaction:src, ask-user-question.ts, auto-compact-75.ts, codex-usage-bar.ts, context.ts,
custom-header.ts, DietrichGebert/ponytail:pi-extension, filechanges, fm-calm.ts, fm-primary-pi-watch.ts,
fm-primary-turnend-guard.ts, herdr-agent-state.ts, md-link.ts, memory.ts, pi-gpt-fast-mode, pi-herd.js, pi-web-access@0.14.0,
usage-bar, zz-read-only-mode.ts


 Ponytail loaded: full

 Warning: tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf and restart
 tmux.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 Update Available
 New version 0.84.1 is available. Run pi update
 Changelog: https://pi.dev/changelog
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

 Tool output: collapsed

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
~/project
$0.000 (sub) 0.0%/500k (auto)                                                                                (xai) grok-4.5 • high
Grok usage check failed: {"code":"personal-team-blocked:spending-limit","error":"You have run out of credits or need a Grok sub...
Evidence: Targeted test run on HEAD 915af5d

ok - Calm claims no tool name in either preference state, and no tracked Firstmate Pi extension claims a tool name or CLI flag outside its own namespace ok - Calm collapses its seven built-in tool rows at render time without claiming a name, covers a row a foreign extension owns while leaving that tool's execution intact, keeps disclosed images and third-party rows visible, and restores byte-identical stock rendering when it turns off ok - the real Pi executable starts with no extension-load error or tool-name conflict, with Calm off and with Calm on, beside an extension that overrides all seven built-in tool names ok - Pi calm centralizes transcript visibility, preserves execution/export data, keeps Pi's stock working row visible while no run is active, and persists its choice across session starts

ok - Pi calm compatibility evidence never rejects a Pi version for being newer than 0.82.0, and still fails closed on a missing or malformed version
ok - a missing collapsed-thinking presentation API degrades only that Calm adapter with a clear skip reason, while the rest of Calm still registers
ok - missing Pi presentation class exports reach the independent adapter degradation path
ok - Calm claims no tool name in either preference state, and no tracked Firstmate Pi extension claims a tool name or CLI flag outside its own namespace
ok - Calm collapses its seven built-in tool rows at render time without claiming a name, covers a row a foreign extension owns while leaving that tool's execution intact, keeps disclosed images and third-party rows visible, and restores byte-identical stock rendering when it turns off
ok - the real Pi executable starts with no extension-load error or tool-name conflict, with Calm off and with Calm on, beside an extension that overrides all seven built-in tool names
ok - Pi calm centralizes transcript visibility, preserves execution/export data, keeps Pi's stock working row visible while no run is active, and persists its choice across session starts
Evidence: Same three tests run against the pre-fix extension from base 70aeba8 - all fail

not ok - Pi tracked-extension tool-name claim contract failed: Error: Calm registered ["read","bash","edit","write","grep","find","ls"] while config/calm was on not ok - Pi refused to start with Calm on beside an extension overriding the built-in tool names: Error: Failed to load extension ".../zz-foreign-builtins.ts": Tool "read" conflicts with .../fm-calm.ts (and bash, edit, write, grep, find, ls) not ok - Pi calm tool-row collapse contract failed: Error: Calm registered a tool; it must claim no tool name at all at activateBuiltInsIfNeeded (.../fm-calm.ts:347)

cp: cannot stat '/home/brandon/.no-mistakes/worktrees/9bb7d18f9e5b/01KZER916F3Y3RTBF9VR455RXC/.pi/extensions/lib/fm-calm-tool-row-layout.ts': No such file or directory
not ok - Pi tracked-extension tool-name claim contract failed: file:///tmp/fm-calm-pi-extension.wmrV7S/no-tool-name-claims/project/[eval1]:54
  throw new Error(`Calm registered ${JSON.stringify(calmOn.tools)} while config/calm was on`);
        ^

Error: Calm registered ["read","bash","edit","write","grep","find","ls"] while config/calm was on
    at file:///tmp/fm-calm-pi-extension.wmrV7S/no-tool-name-claims/project/[eval1]:54:9
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)

Node.js v22.23.1
=== PRE-FIX: real pi launch beside a foreign built-in extension ===
cp: cannot stat '/home/brandon/.no-mistakes/worktrees/9bb7d18f9e5b/01KZER916F3Y3RTBF9VR455RXC/.pi/extensions/lib/fm-calm-tool-row-layout.ts': No such file or directory
not ok - Pi refused to start with Calm on beside an extension overriding the built-in tool names: Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "read" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "bash" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "edit" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "write" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "grep" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "find" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Error: Failed to load extension "/tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/agent/extensions/zz-foreign-builtins.ts": Tool "ls" conflicts with /tmp/fm-calm-pi-extension.mpwbnA/foreign-launch/project/.pi/extensions/fm-calm.ts
Hint: Start without extensions using "pi -ne".
exit=1

=== PRE-FIX: tool-row collapse without name claims ===
cp: cannot stat '/home/brandon/.no-mistakes/worktrees/9bb7d18f9e5b/01KZER916F3Y3RTBF9VR455RXC/.pi/extensions/lib/fm-calm-tool-row-layout.ts': No such file or directory
not ok - Pi calm tool-row collapse contract failed: Firstmate Calm: built-in ownership check unavailable, claiming every built-in unconditionally. Calm consulted the tool registry; its presentation must not depend on tool ownership
file:///tmp/fm-calm-pi-extension.sGg6Ef/tool-row-collapse/project/[eval1]:27
    throw new Error("Calm registered a tool; it must claim no tool name at all");
          ^

Error: Calm registered a tool; it must claim no tool name at all
    at Object.registerTool (file:///tmp/fm-calm-pi-extension.sGg6Ef/tool-row-collapse/project/[eval1]:27:11)
    at activateBuiltInsIfNeeded (file:///tmp/fm-calm-pi-extension.sGg6Ef/tool-row-collapse/project/.pi/extensions/fm-calm.ts?toolrow=1786128985897:347:46)
    at Object.handler (file:///tmp/fm-calm-pi-extension.sGg6Ef/tool-row-collapse/project/.pi/extensions/fm-calm.ts?toolrow=1786128985897:450:19)
    at file:///tmp/fm-calm-pi-extension.sGg6Ef/tool-row-collapse/project/[eval1]:154:19
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)

Node.js v22.23.1
exit=1
- Outcome: ⚠️ 3 issues (2 warnings, 1 info) across 1 run (12m1s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⚠️ **Rebase** - 1 warning

Push main to origin, or rebase your branch onto origin/main, before gating.

⚠️ **Review** - 2 infos
  • ℹ️ .pi/extensions/fm-calm.ts:44 - calmPresentationHides is left in the import list but is no longer referenced anywhere in fm-calm.ts - it was only used by the deleted wrapBuiltIn renderCall/renderResult bodies. Nothing in the pipeline catches it: tests/fm-pi-primary-types.test.sh's tsconfig sets strict but not noUnusedLocals, and bin/fm-lint.sh is shellcheck-only over bin/*.sh and tests/*.sh, so no TS linter runs over .pi/extensions. Drop the import line.
  • ℹ️ .pi/extensions/lib/fm-calm-tool-row-layout.ts:3 - The file header states that installCalmToolRowLayout() "probes that exact method and those exact fields and throws if any is missing", but only ToolExecutionComponent and .prototype.render are probed at install time (lines 66-72). imageComponents/imageSpacers are per-instance fields and cannot be probed then; they are checked at render time (line 82) and degrade via the imageShapeUnavailable latch plus a console diagnostic, never a throw. Since this file's whole contract is how it fails, the header should describe the two-stage behavior rather than a single throw path.
⚠️ **Test** - 3 issues (2 warnings, 1 info)
  • ⚠️ tests/fm-calm-pi-extension.test.sh - Pre-existing failure, NOT caused by this change: test_operational_followup_turn_e2e in tests/fm-calm-pi-extension.test.sh fails with "Pi follow-up loaded_on case rendered a duplicate captain answer". I confirmed it reproduces identically against base commit 70aeba8 with the pre-fix .pi/extensions in place, so this change neither introduces nor worsens it. Reported per the captain's explicit instruction not to absorb it into this change's scope.
  • ⚠️ tests/fm-calm-pi-extension.test.sh - Pre-existing failure, NOT caused by this change: test_interactive_terminal_e2e in tests/fm-calm-pi-extension.test.sh fails with "/export did not complete while calm mode was on". I confirmed it reproduces identically against base commit 70aeba8 with the pre-fix .pi/extensions in place. Reported per the captain's explicit instruction not to absorb it into this change's scope.
  • ℹ️ tests/fm-pi-primary-types.test.sh - tests/fm-pi-primary-types.test.sh gate-skipped locally ("skip: tsc not found for Pi extension typecheck"), so the type-only Pi import contract added in 60ef61b for .pi/extensions/lib/fm-calm-tool-row-layout.ts is not typechecked in this environment. Remote CI owns that check. No action needed locally.
  • bash tests/fm-calm-pi-extension.test.sh scoped to test_no_tool_name_claims, test_tool_row_collapse_without_name_claims, test_launch_beside_foreign_builtin_extension, test_rendering_and_session_lifecycle, test_pi_compat_no_upper_bound, test_pi_compat_degraded_adapter, test_pi_compat_missing_adapter_exports - all pass on HEAD 915af5d
  • Regression proof: git checkout 70aeba8 -- .pi/extensions then re-ran test_no_tool_name_claims, test_tool_row_collapse_without_name_claims, test_launch_beside_foreign_builtin_extension - all three fail pre-fix; worktree restored clean
  • Manual E2E: launched the real pi 0.84.0 TUI under tmux with cwd in a project carrying the worktree's .pi/extensions, and PI_CODING_AGENT_DIR holding a copy of the captain's real global extensions including zz-read-only-mode.ts - pre-fix + Calm on reproduces the four conflict errors and exits 1 with no TUI
  • Manual E2E: same launch with this change, config/calm=off - Pi TUI opens, [Extensions] banner lists both fm-calm.ts and zz-read-only-mode.ts, zero extension-load errors
  • Manual E2E: same launch with this change, config/calm=on at startup - Pi TUI opens with the same extension set and zero errors
  • Manual E2E: typed /calm in the live session - Calm activates ("Tool output: collapsed"), session stays up, preference persists to config/calm
  • Confirmed no Pi model turn ran (status bar shows the account's personal-team-blocked:spending-limit state) and no file under ~/.pi was written (mtime sweep)
  • bash tests/fm-calm-pi-extension.test.sh scoped to test_operational_followup_turn_e2e on HEAD and on base 70aeba8 - fails identically in both
  • bash tests/fm-calm-pi-extension.test.sh scoped to test_interactive_terminal_e2e on HEAD and on base 70aeba8 - fails identically in both
  • bash tests/fm-pi-primary-types.test.sh - gate-skipped (tsc not installed)
⚠️ **Document** - 1 info
  • ℹ️ docs/calm-mode-feasibility.md:401 - Judgment call, not fixed: docs/calm-mode-feasibility.md ends with a series of dated verification records (2026-07-23, 2026-07-26, two 2026-07-30), but this change added no dated record for the Pi 0.84.0 collision-free launch verification described in the intent. I did not author one because I do not have the captured launch transcript from that verification run, and the repo's maintainer-verification rule requires exact commands and exact output rather than reconstructed prose. The current guarantee is instead carried by reproducible automated coverage already named in the doc (test_no_tool_name_claims, test_tool_row_collapse_without_name_claims, test_launch_beside_foreign_builtin_extension) plus the 0.84.0 version statement in the Compatibility evidence section, which is arguably stronger than a one-time dated record. If the captain wants the dated record for parity with the earlier sections, it needs the real output from that run pasted in.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Pi refused to start inside a Firstmate worktree whenever the operator's own
Pi extension overrode a built-in tool name Calm also claimed, dropping the
crewmate straight back to a shell with no agent running.

Root cause, reproduced end to end against the operator's real global
extensions and Pi 0.84.0: Calm registered wrapped ToolDefinitions for
read/bash/edit/write/grep/find/ls, and Pi's ResourceLoader.detectExtensionConflicts
runs one pass over the fully loaded extension set and records an error
diagnostic for every later claim of an already-registered name. main.js exits
the process with status 1 on any error diagnostic before the TUI opens. Both
extensions stay loaded and precedence still follows load order, but the launch
itself is refused. Firstmate's project-local extension is resolved first, so
the operator's extension took the diagnostic and the whole session died.

The reported observation predates the gate added in kunchenguid#1724, which is why it
appeared with the preference off; that gate reduced the exposure to Calm-on
sessions but left them refusing the launch outright.

No in-extension check can avoid the conflict at registration time:
getAllTools() throws before extension binding, Pi exposes no pre-bind list of
the extensions it is about to load, and deferring to session_start loses
restored tool rows, which Pi builds from a pre-session_start registry
snapshot. Registration cannot be made both safe and complete, so Calm no
longer registers a tool at all.

lib/fm-calm-tool-row-layout.ts collapses the seven rows at render time through
ToolExecutionComponent.render instead, reaching the same zero-height path the
registered wrappers used. That removes the collision class entirely, applies
to restored rows with no load-order constraint, lifts the previously
documented bound against rows drawn before a first /calm activation, and
restores two behaviors the wrappers dropped: Pi builds its own built-ins from
the live session cwd and from the operator's read auto-resize-images and bash
command-prefix and shell-path settings.

Coverage is unchanged: the same seven names, disclosed images and third-party
tool rows still visible, byte-identical stock rendering while Calm is off.
The watcher and turn-end-guard extensions were checked too; only
fm_watch_arm_pi is claimed, and a test now pins that every tracked extension
stays inside the Firstmate namespace.

Verified by launching the real Pi TUI in a worktree with the operator's own
~/.pi/agent/extensions present, with Calm off and with Calm on: both start
clean, and both previously aborted. No operator-owned configuration was
touched.

tests/fm-calm-pi-extension.test.sh gains test_no_tool_name_claims,
test_tool_row_collapse_without_name_claims, and
test_launch_beside_foreign_builtin_extension, which drives the real pi
executable through its own conflict-diagnostic path with no model turn and
carries a positive control so it cannot pass vacuously. All three fail against
the pre-fix extension.

Also repairs a stale fixture stub that predated Pi 0.84.0's
getMarkdownTransformers, which was failing test_rendering_and_session_lifecycle
on the default branch.
@Bloh09
Bloh09 merged commit 877c72d into main Aug 7, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants