feat(pi): make Calm mode conversation-only in the Pi TUI - #1278
Closed
morris2spears wants to merge 13 commits into
Closed
feat(pi): make Calm mode conversation-only in the Pi TUI#1278morris2spears wants to merge 13 commits into
morris2spears wants to merge 13 commits into
Conversation
…e-inbox bridge (#1) * feat: add Telegram mode - two-way captain phone chat through the phone-inbox bridge Inert until a home opts in via the gitignored config/telegram-mode flag. Bootstrap then arms a byte-validated tg-watch.check.sh identity shim and a 30s cadence config, exactly the X-mode contract; the watcher dispatches the trusted bin/fm-tg-poll.sh, which surfaces pending phone-inbox note ids (never bodies, never text-derived filename slugs) as tg-message wakes with durable offer markers, pruning and a 30-minute re-offer. The fmtg-respond skill owns claim/classify/act/reply through the phone-inbox durable bridge, with fm-tg-link.sh and fm-tg-followup.sh carrying milestone and final follow-ups under a local cap and window. Reversible lifecycle actions only: destructive, irreversible, and security-sensitive actions still require the captain in a trusted session. No firstmate-side code reads the bot token. * no-mistakes(review): fix telegram poll dedupe dir, cadence block, doc drift * no-mistakes(document): document telegram mode in stale doc owners * no-mistakes(review): clear tg follow-up link on counter write failure * no-mistakes(document): document tg follow-up link-clear fallback and opt-out behavior * test: use the sanctioned /dev/null source directive in the tg suite The lint meta-contract pins the exact set of interop tests allowed a '# shellcheck source=bin/' directive; test files that source production libs use 'source=/dev/null', the same idiom as the X security and turn-end guard suites.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
The developer wanted Firstmate's Pi conversation view to be conversation-only by default: Calm mode should hide ordinary tool call/result rows and their shells, internal thinking, the Pi working/status row, and all Firstmate operational injected input (watcher, turn-end guard, away-supervisor, session-start), while preserving real typed captain prompts and Firstmate's captain-facing replies exactly. They required extending the existing single Calm owner in .pi/extensions/fm-calm.ts rather than adding a second presentation mechanism, persisting Calm as this home's default and applying it immediately on reload with correct redraw of prior transcript rows, keeping /calm as a reversible toggle, and adding no "Calm enabled" banner or other replacement UI clutter. Constraints: no brittle terminal-output scraping or global hide-everything patch, stay within supported Pi 0.82.x APIs scoped to trusted interactive TUI mode, degrade safely if an upstream API disappears, and keep the change presentation-only so message delivery, model context, and supervision are unchanged. They also asked for focused tests on visibility rules, persistence, reload/redraw, and preservation of real captain/assistant conversation, plus full test and type/lint runs, with docs limited to what the existing classification requires. During no-mistakes validation the developer resolved the open question under decision key calm-tool-errors: show actionable tool errors only (explicit tool-turn abort/provider errors needing a response) while keeping routine tool activity hidden, applying that exact fix to every affected review-gate finding via the gate's own response command without --yes, then continuing validation.
What Changed
.pi/extensions/fm-calm.tswith two new layout modules (fm-calm-tool-layout.ts,fm-calm-nonconversation-layout.ts) so Calm now hides routine tool call/result rows and their shells, internal thinking, the Pi working/status row, and injected Firstmate operational input (watcher, turn-end guard, away-supervisor, session-start), including their leading spacers, while real captain prompts and Firstmate replies render unchanged. Actionable tool errors (aborted/provider failures on an interrupted assistant turn) still surface, clamped to terminal width, and are shown once per assistant turn across adapter reloads./calmremains a reversible toggle with no added banner or replacement UI, and missing upstream Pi APIs degrade to a no-op rather than failing.tests/fm-calm-pi-extension.test.shto 11 focused cases (visibility rules, persistence, reload/redraw, non-conversation rows, operational follow-ups, adapter degradation, real-Pi tmux E2E), fixed a stale composer-readiness probe intests/fm-pi-primary-live-e2e.test.shthat had been short-circuiting the live suite, refresheddocs/calm.mdplus the feasibility notes, and dropped a leaked.pi-live-e2e.*scratch gitlink from the tree.Risk Assessment
Testing
Ran the focused Calm suite (
tests/fm-calm-pi-extension.test.sh, 11 cases green, including its real-Pi tmux E2E coverage of visibility rules, persistence, reload/redraw, and conversation preservation);tests/fm-pi-primary-types.test.shskips here becausetscis not installed. Since passing tests alone would not show the captain-facing surface, I additionally drove a live Pi 0.82.1 TUI in tmux over a transcript containing every row class the intent names and captured the actual rendered pane in four states, producing a side-by-side screenshot and rendered HTML: Calm on shows only captain prompts and Firstmate replies while thinking, tool rows, routine tool failures, all injected operational input, user bash, branch and compaction rows and the Working row are hidden with no replacement banner;/calmrestores every one of them and a second/calmre-hides them with prior rows redrawn; quit-and-relaunch keeps Calm on and redraws conversation-only. A captain message that merely quotes the operational marker stays visible, confirming genuine typed prompts are untouched. I did not re-run the opt-in credentialed live E2E (round 1 already ran it green after its probe fix, and it costs real provider credits); I did fix the artifact that run left behind - its scratch lab directory had been committed as a gitlink - by removing it and gitignoring the path. Worktree is otherwise clean./var/folders/z5/m852gsm10ts0cd8hmbk2jjcw0000gn/T/no-mistakes-evidence/01KYR5N3KGY3V99JX0YB1QB74Q/calm-mode-panes.png)Evidence: Rendered HTML of the same four terminal panes (color-preserving)
Evidence: Raw tmux pane capture - Calm ON (conversation only)
Evidence: Raw tmux pane capture - Calm OFF (everything restored)
Evidence: Raw tmux pane capture - Calm re-enabled by a second /calm
Evidence: Raw tmux pane capture - after /quit and relaunch (Calm persisted on)
Evidence: Row-visibility matrix measured from the live panes
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
tests/fm-calm-pi-extension.test.sh:1792- The e2e helpers no longer kill the prior tmux session (or wait for Pi to exit) before creating a new one with the same name; they onlysleep 0.2after sending/quit. Since the file runs underset -uwithout-e, a slow Pi exit makestmux new-session -s fm-calm-e2efail non-fatally and every followingcapture-pane/send-keystargets the OLD, dying session. Inrun_followup_case(9 sequential invocations) the stale pane still containsfollowup-e2e.ts, so the readiness loop breaks immediately and the/followup-e2e <label>keys are typed into the exiting session, yielding a confusing timeout failure - or, for the generic asserts, a pass against the previous case's transcript. Same pattern at lines 1918, 2074, 2398 and 2673. Additionally, the Calm-persistence restart at line 2671 replacedwait_for_text "PI_EXIT=0"withsleep 0.2, dropping the clean-exit assertion the previous code made. Fix: before eachnew-session, either restoretmux kill-session -t "$TMUX_SESSION" 2>/dev/null || trueor polltmux has-sessionuntil the prior session is gone.docs/calm.md:9- The rewritten operator doc states what Calm removes but no longer discloses what still renders.!bashrows (BashExecutionComponent), skill-invocation rows, compaction/branch summaries, cache-miss notices and unrelated custom entries have no adapter and remain fully visible - the previous version of this file said so explicitly, and docs/calm-mode-feasibility.md still documents it in its taxonomy table's last row. Re-adding one sentence keeps the operator-facing doc from implying a stricter conversation-only guarantee than the implementation makes.tests/fm-calm-pi-extension.test.sh:38-mkdir -p "$TMP_ROOT"andFM_TEST_CLEANUP_DIRS+=("$TMP_ROOT")are redundant:fm_test_tmprootalready creates the directory viamktemp -dand registers it for EXIT cleanup (tests/lib.sh:69-77), so this double-registers the same path. Only thecpof fm-operational-input.sh into $TMP_ROOT is actually needed here.🔧 Fix: hide remaining non-conversation Calm rows; fix tmux e2e race
1 info still open:
.pi/extensions/lib/fm-calm-nonconversation-layout.ts:233- The leading-spacer wrapper capturesrowHidesat mount time (const rowHides = calmHidesRow(row)on line 231), so the wrapper closes over the closure belonging to whichever module instance was live when the row was mounted. Pi re-evaluates the extension modules on every reload while the surviving prototype wrappers keep the registry object - which is exactly why the tool-error turn scope is registry-held - but a spacer wrapper created before a reload keeps calling the OLD module'scalmPresentationHides, and only the NEW module'ssetCalmPresentationis updated by fm-calm.ts. Concrete path: Calm on, a compaction summary (or branch summary / skill block) already rendered, extensions reload (session_start reason "reload"), then/calmoff - the summary row reappears (its prototype patch reads the registry-updatedpatch.hides) but its leading blank line stays suppressed; the reverse order leaves a stray blank line where a hidden row used to be, which is the gaplessness this feature asserts elsewhere. Impact is one stray/missing blank line, not lost content. Fix: resolve the policy lazily inside the wrapper, e.g.calmConditionalRow(spacer, () => calmHidesRow(row)?.() ?? false), sincecalmHidesRowreads the shared symbol-keyed map and is always current.🔧 **Test** - 1 issue found → auto-fixed ✅
tests/fm-pi-primary-live-e2e.test.sh:298- tests/fm-pi-primary-live-e2e.test.sh failed immediately with "Pi did not reach its ready composer": its readiness probe waited for "(openai-codex)" in the composer footer, which Pi 0.82.x no longer prints (the footer shows "gpt-5.6-sol • low"). The probe predates this branch, so the branch's new Calm working-row suppression assertion on the credentialed provider path had never actually executed. Fixed by pinning the probe to the model id; the suite then passed end-to-end.bash tests/fm-calm-pi-extension.test.sh- all 11 focused Calm cases (visibility rules, persistence, reload/redraw, adapter degradation, tmux TUI E2E) passFM_PI_LIVE_E2E=1 bash tests/fm-pi-primary-live-e2e.test.sh- failed on a stale composer-readiness probe, fixed the probe, re-ran:ok - Pi 0.82.1 live E2E covered native Calm working suppression, Ahoy first/later messages, legacy transcripts, near misses, and watcher continuitybash tests/fm-documentation-audiences.test.sh- doc classification for the changed docs/calm.md, docs/configuration.md, README.mdbash tests/fm-pi-primary-types.test.sh- reportsskip: tsc not foundin this environment (typecheck is the type/lint phase's concern, not run here)Manual TUI evidence A: drove realpi0.82.1 in tmux over a replayed session containing a captain prompt, thinking, bash tool call/result, all five encoded operational inputs, a user bash row, a compaction summary and Firstmate replies; captured the viewport with Calm on (home default) and again after/calm, and read back the persistedconfig/calmvalue (on->off)Manual TUI evidence B: drove realpiwith a deterministic faux provider to compare a routine failing tool turn (stays hidden) against an escape-interrupted tool turn (surfacesOperation aborted) with Calm on🔧 Fix: fix stale Pi composer readiness probe in live e2e
✅ Re-checked - no issues remain.
bash tests/fm-calm-pi-extension.test.sh- all 11 focused cases pass, including the real-Pi tmux E2E cases for rendering, persistence, reload/redraw, non-conversation rows, operational follow-ups, and adapter degradationbash tests/fm-pi-primary-types.test.sh- skips on this box (notscinstalled); typecheck is owned by the lint phaseManual end-user demo: launched a realpi0.82.1 in tmux (PI_OFFLINE=1, project-scopedfm-calm.ts,FM_HOMEwithconfig/calm=on) resuming a session transcript containing a captain prompt, internal thinking, routine tool calls/results, a routine tool failure, session-start/watcher/turn-end-guard/away-supervisor injected input, a user bash row, branch and compaction summaries, a captain message that quotes the operational marker, and Firstmate repliesCaptured the rendered pane withtmux capture-pane -e -p -S -300at four points: Calm on by default, after/calm(off), after a second/calm(re-enabled), and after/quitplus relaunchRendered the ANSI captures to HTML and screenshotted with headless Chrome for reviewer-visible visual evidenceVerified$FM_HOME/config/calmflipped tooffthen back toonacross the two/calmtoggles and stayedonacross relaunchgit rm --cached .pi-live-e2e.19827/projectplus a.pi-live-e2e.*gitignore entry to remove the leaked live-e2e scratch gitlink committed by the previous round✅ **Document** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.