@pellux/goodvibes-tui 1.1.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +20 -11
  3. package/docs/foundation-artifacts/operator-contract.json +1 -1
  4. package/package.json +2 -2
  5. package/src/core/alert-gating.ts +67 -0
  6. package/src/core/approval-alert.ts +72 -0
  7. package/src/core/budget-breach-notifier.ts +106 -0
  8. package/src/core/conversation-rendering.ts +19 -0
  9. package/src/core/conversation.ts +18 -0
  10. package/src/core/focus-tracker.ts +41 -0
  11. package/src/core/long-task-notifier.ts +33 -6
  12. package/src/core/system-message-router.ts +37 -51
  13. package/src/core/turn-cancellation.ts +20 -0
  14. package/src/core/turn-event-wiring.ts +64 -3
  15. package/src/export/cost-utils.ts +36 -0
  16. package/src/input/command-registry.ts +38 -1
  17. package/src/input/commands/checkpoint-runtime.ts +280 -0
  18. package/src/input/commands/codebase-runtime.ts +192 -0
  19. package/src/input/commands/eval.ts +1 -1
  20. package/src/input/commands/health-runtime.ts +1 -1
  21. package/src/input/commands/image-runtime.ts +112 -0
  22. package/src/input/commands/intelligence-runtime.ts +11 -1
  23. package/src/input/commands/local-auth-runtime.ts +4 -1
  24. package/src/input/commands/local-runtime.ts +9 -3
  25. package/src/input/commands/marketplace-runtime.ts +2 -2
  26. package/src/input/commands/memory.ts +6 -1
  27. package/src/input/commands/operator-panel-runtime.ts +40 -24
  28. package/src/input/commands/planning-runtime.ts +26 -3
  29. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  30. package/src/input/commands/plugin-runtime.ts +2 -2
  31. package/src/input/commands/policy-dispatch.ts +21 -0
  32. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  33. package/src/input/commands/qrcode-runtime.ts +3 -3
  34. package/src/input/commands/recall-review.ts +35 -0
  35. package/src/input/commands/remote-runtime.ts +3 -3
  36. package/src/input/commands/runtime-services.ts +54 -13
  37. package/src/input/commands/services-runtime.ts +1 -1
  38. package/src/input/commands/session-workflow.ts +16 -1
  39. package/src/input/commands/settings-sync-runtime.ts +1 -1
  40. package/src/input/commands/shell-core.ts +15 -7
  41. package/src/input/commands/skills-runtime.ts +2 -8
  42. package/src/input/commands/subscription-runtime.ts +2 -2
  43. package/src/input/commands/test-runtime.ts +277 -0
  44. package/src/input/commands/websearch-runtime.ts +101 -0
  45. package/src/input/commands/work-plan-runtime.ts +36 -10
  46. package/src/input/commands/workstream-runtime.ts +338 -0
  47. package/src/input/commands.ts +12 -0
  48. package/src/input/config-modal-types.ts +161 -0
  49. package/src/input/config-modal.ts +377 -0
  50. package/src/input/feed-context-factory.ts +7 -8
  51. package/src/input/handler-command-route.ts +27 -17
  52. package/src/input/handler-feed-routes.ts +61 -23
  53. package/src/input/handler-feed.ts +47 -23
  54. package/src/input/handler-interactions.ts +1 -3
  55. package/src/input/handler-modal-routes.ts +65 -0
  56. package/src/input/handler-modal-stack.ts +3 -5
  57. package/src/input/handler-modal-token-routes.ts +16 -49
  58. package/src/input/handler-picker-routes.ts +11 -94
  59. package/src/input/handler-shortcuts.ts +24 -14
  60. package/src/input/handler-types.ts +2 -6
  61. package/src/input/handler-ui-state.ts +10 -22
  62. package/src/input/handler.ts +6 -28
  63. package/src/input/keybindings.ts +22 -8
  64. package/src/input/model-picker-types.ts +24 -6
  65. package/src/input/model-picker.ts +58 -0
  66. package/src/input/panel-integration-actions.ts +9 -170
  67. package/src/input/settings-modal-data.ts +95 -0
  68. package/src/input/settings-modal-mutations.ts +6 -4
  69. package/src/main.ts +24 -27
  70. package/src/panels/agent-inspector-shared.ts +2 -1
  71. package/src/panels/base-panel.ts +22 -1
  72. package/src/panels/builtin/agent.ts +21 -107
  73. package/src/panels/builtin/development.ts +15 -61
  74. package/src/panels/builtin/knowledge.ts +8 -32
  75. package/src/panels/builtin/operations.ts +84 -428
  76. package/src/panels/builtin/session.ts +21 -112
  77. package/src/panels/builtin/shared.ts +9 -43
  78. package/src/panels/builtin-modals.ts +218 -0
  79. package/src/panels/builtin-panels.ts +5 -0
  80. package/src/panels/cost-tracker-panel.ts +36 -10
  81. package/src/panels/diff-panel.ts +12 -43
  82. package/src/panels/eval-registry.ts +60 -0
  83. package/src/panels/fleet-panel.ts +800 -0
  84. package/src/panels/fleet-read-model.ts +477 -0
  85. package/src/panels/fleet-steer.ts +92 -0
  86. package/src/panels/fleet-stop.ts +125 -0
  87. package/src/panels/fleet-tabs.ts +230 -0
  88. package/src/panels/fleet-transcript.ts +356 -0
  89. package/src/panels/index.ts +7 -31
  90. package/src/panels/modals/hooks-modal.ts +187 -0
  91. package/src/panels/modals/keybindings-modal.ts +151 -0
  92. package/src/panels/modals/knowledge-modal.ts +158 -0
  93. package/src/panels/modals/local-auth-modal.ts +132 -0
  94. package/src/panels/modals/marketplace-modal.ts +218 -0
  95. package/src/panels/modals/memory-modal.ts +180 -0
  96. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  97. package/src/panels/modals/modal-theme.ts +36 -0
  98. package/src/panels/modals/pairing-modal.ts +144 -0
  99. package/src/panels/modals/planning-modal.ts +282 -0
  100. package/src/panels/modals/plugins-modal.ts +174 -0
  101. package/src/panels/modals/policy-modal.ts +256 -0
  102. package/src/panels/modals/provider-health-modal.ts +136 -0
  103. package/src/panels/modals/remote-modal.ts +115 -0
  104. package/src/panels/modals/sandbox-modal.ts +150 -0
  105. package/src/panels/modals/security-modal.ts +217 -0
  106. package/src/panels/modals/services-modal.ts +179 -0
  107. package/src/panels/modals/settings-sync-modal.ts +142 -0
  108. package/src/panels/modals/skills-modal.ts +189 -0
  109. package/src/panels/modals/subscription-modal.ts +172 -0
  110. package/src/panels/modals/work-plan-modal.ts +149 -0
  111. package/src/panels/panel-confirm-overlay.ts +72 -0
  112. package/src/panels/panel-manager.ts +108 -5
  113. package/src/panels/project-planning-answer-actions.ts +4 -2
  114. package/src/panels/skills-panel.ts +7 -51
  115. package/src/panels/types.ts +13 -0
  116. package/src/permissions/hunk-selection.ts +179 -0
  117. package/src/permissions/prompt.ts +60 -4
  118. package/src/renderer/compaction-history-modal.ts +19 -3
  119. package/src/renderer/compaction-preview.ts +13 -2
  120. package/src/renderer/compaction-quality.ts +100 -0
  121. package/src/renderer/config-modal.ts +81 -0
  122. package/src/renderer/conversation-overlays.ts +10 -17
  123. package/src/renderer/fleet-tab-strip.ts +56 -0
  124. package/src/renderer/footer-tips.ts +10 -2
  125. package/src/renderer/git-status.ts +40 -0
  126. package/src/renderer/help-overlay.ts +2 -2
  127. package/src/renderer/model-workspace.ts +44 -1
  128. package/src/renderer/panel-workspace-bar.ts +8 -2
  129. package/src/renderer/turn-injection.ts +114 -0
  130. package/src/runtime/bootstrap-command-context.ts +21 -1
  131. package/src/runtime/bootstrap-command-parts.ts +34 -7
  132. package/src/runtime/bootstrap-core.ts +14 -4
  133. package/src/runtime/bootstrap-shell.ts +29 -16
  134. package/src/runtime/bootstrap.ts +11 -17
  135. package/src/runtime/code-index-services.ts +112 -0
  136. package/src/runtime/orchestrator-core-services.ts +49 -0
  137. package/src/runtime/process-lifecycle.ts +3 -1
  138. package/src/runtime/services.ts +91 -43
  139. package/src/runtime/ui-services.ts +8 -0
  140. package/src/runtime/workstream-services.ts +195 -0
  141. package/src/shell/blocking-input.ts +22 -1
  142. package/src/shell/ui-openers.ts +129 -17
  143. package/src/utils/format-duration.ts +8 -18
  144. package/src/utils/splash-lines.ts +1 -1
  145. package/src/version.ts +1 -1
  146. package/src/panels/agent-inspector-panel.ts +0 -786
  147. package/src/panels/approval-panel.ts +0 -252
  148. package/src/panels/automation-control-panel.ts +0 -479
  149. package/src/panels/cockpit-panel.ts +0 -481
  150. package/src/panels/cockpit-read-model.ts +0 -238
  151. package/src/panels/communication-panel.ts +0 -256
  152. package/src/panels/control-plane-panel.ts +0 -470
  153. package/src/panels/debug-panel.ts +0 -615
  154. package/src/panels/docs-panel.ts +0 -384
  155. package/src/panels/eval-panel.ts +0 -627
  156. package/src/panels/file-explorer-panel.ts +0 -673
  157. package/src/panels/file-preview-panel.ts +0 -517
  158. package/src/panels/hooks-panel.ts +0 -401
  159. package/src/panels/incident-review-panel.ts +0 -406
  160. package/src/panels/intelligence-panel.ts +0 -383
  161. package/src/panels/knowledge-graph-panel.ts +0 -515
  162. package/src/panels/marketplace-panel.ts +0 -399
  163. package/src/panels/memory-panel.ts +0 -558
  164. package/src/panels/ops-control-panel.ts +0 -329
  165. package/src/panels/ops-strategy-panel.ts +0 -321
  166. package/src/panels/orchestration-panel.ts +0 -465
  167. package/src/panels/panel-list-panel.ts +0 -566
  168. package/src/panels/plan-dashboard-panel.ts +0 -707
  169. package/src/panels/policy-panel.ts +0 -517
  170. package/src/panels/project-planning-panel.ts +0 -731
  171. package/src/panels/provider-health-panel.ts +0 -678
  172. package/src/panels/provider-health-tracker.ts +0 -310
  173. package/src/panels/provider-health-views.ts +0 -567
  174. package/src/panels/qr-panel.ts +0 -280
  175. package/src/panels/remote-panel.ts +0 -534
  176. package/src/panels/routes-panel.ts +0 -241
  177. package/src/panels/sandbox-panel.ts +0 -456
  178. package/src/panels/security-panel.ts +0 -447
  179. package/src/panels/services-panel.ts +0 -329
  180. package/src/panels/session-browser-panel.ts +0 -496
  181. package/src/panels/settings-sync-panel.ts +0 -398
  182. package/src/panels/subscription-panel.ts +0 -342
  183. package/src/panels/symbol-outline-panel.ts +0 -619
  184. package/src/panels/system-messages-panel.ts +0 -364
  185. package/src/panels/tasks-panel.ts +0 -608
  186. package/src/panels/thinking-panel.ts +0 -333
  187. package/src/panels/tool-inspector-panel.ts +0 -537
  188. package/src/panels/work-plan-panel.ts +0 -540
  189. package/src/panels/worktree-panel.ts +0 -360
  190. package/src/panels/wrfc-panel.ts +0 -790
  191. package/src/renderer/agent-detail-modal.ts +0 -466
  192. package/src/renderer/live-tail-modal.ts +0 -156
  193. package/src/renderer/process-modal.ts +0 -671
  194. package/src/renderer/qr-renderer.ts +0 -120
package/CHANGELOG.md CHANGED
@@ -4,29 +4,93 @@ All notable changes to GoodVibes TUI.
4
4
 
5
5
  ---
6
6
 
7
+ ## [1.7.0] — 2026-07-04
8
+
9
+ The evolution release: six batched development cycles (internal 1.2.0–1.6.0, which were never published) land together as one public version. The through-line is a product that reports the truth about itself — reversible edits you can actually undo, a live fleet you can watch and steer, orchestration that runs the plan it showed you, memory that demonstrably enters the turns it claims to, and a panel surface cut down to the consoles that earn their place. Ships against @pellux/goodvibes-sdk 0.38.0.
10
+
11
+ ### Reversibility and checkpoints
12
+ - Workspace checkpoints with `/checkpoints`, `/checkpoint`, and `/rewind`: snapshot the working tree, list snapshots newest-first, and preview-then-restore files from any checkpoint (files only — conversation history is left untouched). Backed by the SDK's `WorkspaceCheckpointManager` and wired through TUI services, with guards so an unguarded checkpoint call can no longer surface as a silent unhandled rejection.
13
+ - Per-hunk accept/reject for multi-edit approval prompts: when one approval covers several hunks you accept or reject each individually, and the decision is honored end-to-end instead of being all-or-nothing.
14
+ - `/test` runs the project test script and shows real pass/fail results, parsing both the SDK runner output and plain `bun test` output (per-test counts and the names of failing tests); the end state persists in the transcript instead of evaporating.
15
+ - Manual `/compact` now hands the compaction subagent the real original task and the full agent roster, so the handoff summary is accurate.
16
+
17
+ ### Fleet observability, session tabs, and steering
18
+ - A live Fleet panel over the real SDK process/agent registry: a process tree with per-node live states, honest cost, id-anchored selection that survives list churn, and capability-gated interrupt/kill actions. It is the fast-access surface below the prompt and the home of `F2`, `Ctrl+O`, and the footer `[Enter]`.
19
+ - Unfocused-user alerts that actually reach you when GoodVibes is in a background terminal: budget breach, agent/chain failure, awaiting-approval, and long-task notices, gated on real terminal-focus tracking (CSI 1004) with an honest "unknown" fallback.
20
+ - Live transcript tabs over the conversation snapshot bridge: attach to a running agent, browse its transcript read-only (with reserved-notice honesty), detach without killing it, and see interrupted vs. killed as distinct states with a frozen elapsed clock.
21
+ - A steering composer with a `queued` / `consumed` / `dropped` badge: send an instruction to a running agent and see honestly whether it was picked up, with TTL and ordering guards, and the consumed signal deferred until the steered turn actually succeeds.
22
+
23
+ ### Workstream orchestration
24
+ - `/workstream` authoring and oversight for multi-phase agent work: phases are insertable structure (float-ordinal ordering), work items flow to free capacity, and each item commits inside its own worktree through the scoped-commit machinery. The authoring view renders the real plan, so the proposal and the launched run can never disagree.
25
+ - Resume-prefix replay with crash-artifact reconciliation: an interrupted workstream re-queues its in-phase items on resume instead of double-running or dropping them.
26
+ - Budgets that refuse rather than kill, with a recovery path, and cooperative cancellation that reaches real child processes.
27
+ - Fleet-tree nesting for workstreams, plus pause/resume support.
28
+
29
+ ### Passive memory and the code index
30
+ - Durable memory demonstrably enters both agent turns and the main-session turn: per-turn retrieval is budgeted (800 tokens by default, floor 95), flag-gated, and inspectable — `/recall injections` shows exactly which records would be injected for a task and why, defaulting to the main-session ring.
31
+ - A repo source-tree code index with honest bounds and labeling: `/codebase build`, `/codebase status`, and `/codebase search`.
32
+ - Compaction quality scoring and a corrected capacity-percentage in the compaction path.
33
+ - The model picker gained an embeddings target, so the memory index's embedding provider is selectable alongside chat models.
34
+
35
+ ### The panel purge and config modals
36
+ - The panel surface was cut from roughly 50 panels to 6 surviving consoles — the Fleet hub plus git, diff, cost, tokens, and local-auth — with 19 configuration and review surfaces re-homed onto a single liveness-enforcing config-modal host. Heavy operator state that used to sprawl across dozens of panels now opens as a focused modal.
37
+ - Every retired panel id resolves honestly: old ids become aliases, modal redirects, or friendly "this moved" notices, so `/panel open <id>` and saved layouts never silently point at nothing.
38
+ - Live panels stay subscribed while open and now repaint while the main thread is idle, so agent, tool, and thinking updates no longer look frozen.
39
+
40
+ ### Keymap and focus honesty
41
+ - The keymap advertises what it actually does: `Ctrl+PageUp` / `Ctrl+PageDown` move between panel tabs (`Ctrl+[` was removed — it is byte `0x1B` and collided with Escape, so it never fired reliably; `Ctrl+]` remains a next-tab chord), `Ctrl+O` and `F2` both open and focus the Fleet panel (the retired Ops Control and process modals fold into it), and `Ctrl+C` honestly advertises the double-press to quit.
42
+ - Focus routing was fixed at the root: multi-character paste is detected per token instead of by a per-feed character-sum heuristic, and keyboard focus transfers only on an explicit request — so typed text no longer leaks into single-letter panel hotkeys.
43
+
44
+ ### Control-plane truth
45
+ - The control plane reports real lifecycle: a `stopping…` state while a process winds down, pause↔resume (including `/schedule` jobs shown in the fleet tree), zombie reaping, and a dirty-residue commit guard so an interrupted scoped commit cannot leave stray changes behind.
46
+
47
+ ### New commands
48
+ - `/search <query> [--limit <n>]` — search the web and render ranked results with source labels.
49
+ - `/imagine <prompt>` — generate an image from a prompt (distinct from `/image`, which attaches an existing image file; the two names collided and the generator was renamed).
50
+ - `/workstream` — author and oversee multi-phase agent workstreams.
51
+ - `/checkpoints`, `/checkpoint`, `/rewind` — workspace checkpoint list / create / restore.
52
+ - `/codebase` — build, inspect, and search the repo source-tree code index.
53
+ - `/test` — run the project test script and show pass/fail.
54
+ - `/recall injections` — inspect the memory records that would be injected for a task.
55
+
56
+ ### Notable fixes
57
+ - Clearing a feature-flag override back to default now removes it from disk instead of persisting a redundant entry.
58
+ - The `paused` process state renders with its own glyph and tone in the fleet tree.
59
+ - Planning: dismissing a planning action no longer corrupts the project goal; the `sessions` front door opens the session picker; `/panel open` reports redirects and deleted ids honestly.
60
+ - `Escape` cancel-first precedence and live git-init header refresh, so a directory becomes a recognized repo without a restart.
61
+
62
+ ### Internal
63
+ - SDK dependency 0.36.0 → 0.38.0 (through 0.37.0 / 0.37.1 / 0.37.2); foundation artifacts regenerated against the released SDK.
64
+ - A release-blocking gate requires the SDK pin, the lockfile resolution, and the installed package to all agree, plus a source-import sweep that fails on any non-npm SDK import — so the local-SDK development overlay can never ship.
65
+ - Test suite at 639 files green; coverage, performance, architecture, and eval-regression gates enforced on every cycle. Each wave was verified with the full gate block, a three-lens adversarial review, and a live tmux replay of its pillar scenarios.
66
+
7
67
  ## [1.1.0] — 2026-07-03
8
68
 
9
- ### Changes
10
- - e3d20840 chore(artifacts): regenerate foundation artifacts against SDK 0.36.0
11
- - 2dab222b integrate wave0: trust repairs (v1.1.0 scope)
12
- - d3116e0a chore(deps): bump @pellux/goodvibes-sdk to 0.36.0 (Wave-0 trust repairs)
13
- - 08caa3a3 test(tools): pin the SDK 0.36 tail-preserving truncation contract
14
- - e1e124b7 chore: trim stall-gate comment to keep main.ts under the 800-line cap
15
- - 57a9c5bd fix(stream): suppress stall indicator false-positive during tool execution
16
- - 96225275 fix(test-runner): fence git discovery at the per-file temp root
17
- - efbfc8de chore(wave0): trim main.ts/git-panel.ts back to the 800-line cap after wave merges
18
- - e7d166e4 integrate WO-315 (W0.10 cost-truth unpriced markers) into wave0
19
- - 83d77e50 integrate WO-314 (W0.9 per-agent stats + footer usage hydration) into wave0
20
- - 5801ced8 integrate WO-313 (W0.7 stream stall honesty, TUI half) into wave0
21
- - 5abfed54 integrate WO-312 (W0.4/5/6/11/12 diff+slash+ctrlc cluster) into wave0
22
- - 40fdee43 integrate WO-311 (W0.8 panel focus/burst/indicator) into wave0
23
- - 8e61b248 fix(wo312): Wave 0 TUI trust-repair cluster (W0.4/W0.5/W0.6/W0.11/W0.12)
24
- - 210b4b80 fix(W0.9): honest per-agent usage display + footer token hydration after resume
25
- - b791dbd2 fix(cost): catalog-backed pricing with honest unpriced state (WO-315/W0.10)
26
- - 1af7105e fix(input,renderer): panel entry points reachable + burst guard + focus indicator (W0.8)
27
- - 000250a1 fix(streaming): honest stall indicator + mid-stream watchdog re-arm (W0.7)
28
- - 74141d53 feat(dev): local SDK overlay for rapid iteration, with release-blocking gates
29
- - 69c282a1 docs: reconcile documentation with the shipped 0.29.0 and 1.0.0 releases
69
+ Wave 0 of the post-1.0 evolution effort: trust repairs. Every change closes a defect found by live dogfooding v1.0.0 where the product reported something other than the truth. Ships against @pellux/goodvibes-sdk 0.36.0 (same effort, SDK side).
70
+
71
+ ### Fixed
72
+ - Failing commands are visible again: the SDK masked every command-level failure as "Error: Unknown error", so a red test suite was literally invisible to the model — it would honestly conclude "no failure observed" while your tests were failing. Exit code, stdout, and stderr now reach the model byte-faithfully, and long-output truncation keeps the tail (where test runners print failures) instead of the head. Verified live: the model reads a sabotaged test's actual assertion diff and fixes the right side.
73
+ - Agent auto-commits are scoped: a WRFC chain used to `git add --all`, sweeping your unrelated dirty files into its commit with a truncated message. Commits now include only chain-touched files, with full accurate messages, under a new policy config (`off | scoped | all`, default scoped). Verified live with bait files.
74
+ - Agent chains can no longer die silently: transport failures surface a reasoned failure state in the transcript and status strip, with one bounded automatic retry.
75
+ - Panels work during turns: Ctrl+P / F2 / the status-strip Enter were dead exactly while agents ran — the Inspector was unreachable whenever there was something to inspect. Verified live mid-chain.
76
+ - Panels no longer eat typed text: multi-character input routes to the composer instead of being silently consumed as single-letter panel hotkeys, and a visible focus indicator shows where keys go.
77
+ - Streaming honesty: a stalled provider stream now shows a real stall/reconnect indicator (with attempt counts via the SDK's new STREAM_RETRY event) instead of cycling whimsical labels over dead air — and the indicator correctly stays quiet while a tool is executing.
78
+ - Slash routing is deterministic: `/command` in an empty composer is always a command, never sent to the model as chat (previously state-dependent after overlay use, burning tokens on junk turns).
79
+ - Per-agent stats are real: the Inspector showed Tools 0 / Tokens 0 / Cost $0.00 for agents that wrote hundreds of lines; agents (including WRFC owners, which aggregate their whole chain) now report true token/tool counts, and footer counters survive session resume.
80
+ - Cost is honest: models missing from the old hardcoded ~16-model price table silently showed $0.00 everywhere; pricing now resolves through the SDK catalog with an explicit "unpriced" marker when unknown.
81
+ - Indicator truth: the footer no longer shows `hitl:balanced` alongside DANGER MODE; the effective permission posture wins.
82
+ - Renderer truth: subprocess stderr can no longer leak raw into the transcript and garble cells (`/diff` in a non-git dir now prints one honest sentence, not a fake diff); git repos are re-detected live after `git init` without a restart; cancelled-turn echo no longer mangles its first character.
83
+
84
+ ### Added
85
+ - Auto-resume offer at startup when a previous session exists.
86
+ - `/sessions` groups subagent sessions instead of burying your real session under them.
87
+ - `.goodvibes/` transient state is auto-gitignored in project directories.
88
+ - Wider, persistent Ctrl+C exit-confirmation notice.
89
+
90
+ ### Internal
91
+ - SDK dependency 0.35.0 → 0.36.0; foundation artifacts regenerated (new event contract entries).
92
+ - Local-SDK overlay dev tooling (`scripts/sdk-dev.ts link|status|restore`) with release-blocking gates in release.ts and publish-check so a local SDK can never ship.
93
+ - Wave verified three ways: full gate block, a 3-lens adversarial code review, and a live tmux scenario replay of the five previously-failing dogfood scenarios (all PASS).
30
94
 
31
95
  ## [1.0.0] — 2026-07-03
32
96
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml/badge.svg)](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
5
+ [![Version](https://img.shields.io/badge/version-1.7.0-blue.svg)](https://github.com/mgd34msu/goodvibes-tui)
6
6
 
7
7
  A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
8
8
 
@@ -157,12 +157,12 @@ The TUI now consumes the extracted `@pellux/goodvibes-sdk` (`0.34.0`) platform l
157
157
 
158
158
  ### Panels, Control Rooms, And Workspaces
159
159
  - Split-pane panel system with panel picker, layout control, and keyboard-first focus behavior
160
- - Dedicated control rooms for provider accounts, provider health, local auth, settings sync, remote, MCP, marketplace, orchestration, tasks, intelligence, worktrees, approvals, forensics, security, policy, cockpit, system messages, and more
161
- - Summary-first heavy panels with posture, issues, next actions, and detail regions
162
- - Routed system-message workspace for startup discovery and operational notices
160
+ - Six persistent consoles the Fleet hub plus git, diff, cost, tokens, and local-auth with heavier operator surfaces (providers, remote, MCP, marketplace, orchestration, tasks, intelligence, worktrees, approvals, policy, security, settings sync, and more) re-homed as configuration/review modals on a single liveness-enforcing config-modal host
161
+ - Retired panel ids resolve honestly through aliases, modal redirects, and "this moved" notices, so `/panel open <id>` and saved layouts never point at nothing
162
+ - Summary-first heavy surfaces with posture, issues, next actions, and detail regions
163
163
  - Cross-panel actions between Explorer, Preview, and Symbols so file browsing can open previews and jump to symbol locations directly from panel focus
164
- - Live panels stay subscribed while open, so agent, tool, and thinking updates continue while the panel is visible
165
- - Dedicated `Agents` panel provides a view-only live peek into running agent sessions while the background-process strip remains the fast-access surface below the prompt
164
+ - Live panels stay subscribed while open and repaint while the main thread is idle, so agent, tool, and thinking updates never look frozen
165
+ - The Fleet panel is the live control room for running agents, workstreams, and scheduled jobs attach/detach (detach never kills), steer, pause/resume, and capability-gated interrupt/kill reached by `F2`, `Ctrl+O`, or the footer `[Enter]`
166
166
 
167
167
  ### Modal And Selection UX
168
168
  - Modal stack navigation that unwinds correctly back to the slash-command menu and prior nested modals
@@ -587,7 +587,7 @@ Changing `service.autostart` or `service.enabled` from `/config` reconciles the
587
587
 
588
588
  GoodVibes is built around routing runtime state to the right surface.
589
589
 
590
- The current product ships dedicated workspaces for:
590
+ The Fleet panel is the one persistent operator control room; most other operator surfaces now open as configuration and review modals on a shared, liveness-enforcing config-modal host rather than as standing panels. The product surfaces state for:
591
591
 
592
592
  - provider accounts and provider health
593
593
  - local auth and local service posture
@@ -1265,6 +1265,9 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
1265
1265
  | `/session [action]` | `/sess` | Full session management: list, rename, resume, fork, save, info, export, search, delete |
1266
1266
  | `/undo [file]` | `/u` | Remove last turn, or `/undo file` to revert last file write/edit |
1267
1267
  | `/redo [file]` | — | Restore last undone turn, or `/redo file` to re-apply last reverted file |
1268
+ | `/checkpoints` | — | List workspace checkpoints, newest first |
1269
+ | `/checkpoint [label]` | — | Create a manual workspace checkpoint (forensic retention) |
1270
+ | `/rewind [id]` | — | Preview and restore a workspace checkpoint (files only — conversation history is unchanged) |
1268
1271
  | `/retry [text]` | `/r` | Re-send the last user message |
1269
1272
  | `/template` | `/tmpl` | Manage prompt templates: save, use, list, edit, delete |
1270
1273
  | `/tools` | `/t` | List available tools |
@@ -1275,8 +1278,9 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
1275
1278
  | `/memory [action]` | — | Session memory management: `list`, `add <text>`, `remove <id>` |
1276
1279
  | `/keep <text>` | — | Pin text to session memory; survives context compaction |
1277
1280
  | `/memory-sync [action]` | `/memsync` | Durable memory export/import and bundle exchange: `export <path> [scope]`, `import <path>` |
1278
- | `/recall [action]` | `/rc` | Durable knowledge and memory substrate: capture, review, explain, export, import, and handoff |
1281
+ | `/recall [action]` | `/rc` | Durable knowledge and memory substrate: capture, review, explain, inspect `injections`, export, import, and handoff |
1279
1282
  | `/knowledge [action]` | `/know`, `/kb` | Structured knowledge graph: ingest URLs/bookmarks, inspect issues, build packets, and run consolidation jobs |
1283
+ | `/codebase [action]` | — | Repo source-tree code index: `build`, `status`, and `search` |
1280
1284
  | `/context` | `/ctx` | Inspect context window usage (token breakdown per message) |
1281
1285
  | `/next-error` | `/ne` | Jump to the next error message in the conversation |
1282
1286
  | `/prev-error` | `/pe` | Jump to the previous error message in the conversation |
@@ -1287,6 +1291,7 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
1287
1291
  | `/scan` | — | Scan for local LLM servers |
1288
1292
  | `/plan [goal]` | — | Inspect or seed TUI-owned project planning state; `panel`, `approve`, `list`, and `show <id>` are supported |
1289
1293
  | `/work-plan [action]` | `/wp`, `/todo`, `/workplan` | Open or update the persistent workspace work-plan checklist |
1294
+ | `/workstream [action]` | — | Author and oversee multi-phase agent workstreams (orchestration engine) |
1290
1295
  | `/panel [action]` | `/panels` | Panel management: open, close, list, toggle, move, focus, split, width, height |
1291
1296
  | `/plugin [action]` | — | Manage plugins (enable/disable/reload/list) |
1292
1297
  | `/marketplace [action]` | — | Browse curated plugin, skill, hook-pack, and policy-pack surfaces |
@@ -1297,6 +1302,7 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
1297
1302
  | `/wrfc` | — | Show WRFC chain status, constraint satisfaction counts, and per-constraint `[SAT]`/`[UNS]`/`[UNV]` breakdown |
1298
1303
  | `/health [action]` | — | Unified runtime health review and repair entry point |
1299
1304
  | `/guidance [action]` | — | Contextual operational guidance without cluttering the conversation |
1305
+ | `/test [pattern]` | — | Run the project test script and show pass/fail results |
1300
1306
  | `/remote [action]` | — | Distributed peer, node-host contract, work-queue, and artifact control room |
1301
1307
  | `/sandbox [action]` | — | Isolation presets, doctor/probe, sessions, and QEMU setup flows |
1302
1308
  | `/setup [action]` | — | First-run readiness, services, sandbox, transfer bundles, and deep links |
@@ -1310,6 +1316,8 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
1310
1316
  | `/keybindings` | `/kb` | List current keyboard bindings and their config file path |
1311
1317
  | `/schedule [action]` | `/sched` | Manage scheduled agent tasks (cron): add, list, remove, enable, disable, run |
1312
1318
  | `/image <path>` | `/img` | Attach an image file to the next message |
1319
+ | `/imagine <prompt>` | — | Generate an image from a prompt |
1320
+ | `/search <query>` | — | Search the web and render ranked results with source labels (`--limit <n>`) |
1313
1321
  | `/paste` | `/clip` | Pull supported text or image data directly from the system clipboard into the prompt |
1314
1322
  | `/refresh-models` | — | Refresh model catalog, benchmarks, and token limits |
1315
1323
  | `/notify [action]` | `/ntf` | Manage webhook notifications (ntfy.sh): add, remove, list, clear, test |
@@ -1388,12 +1396,13 @@ All shortcuts are customizable via `~/.goodvibes/tui/keybindings.json`. Use `/ke
1388
1396
  | Key | Action |
1389
1397
  |-----|--------|
1390
1398
  | `Ctrl+P` | Toggle panel sidebar |
1391
- | `Ctrl+]` | Next panel tab |
1392
- | `Ctrl+[` | Previous panel tab |
1399
+ | `F2` | Open and focus the Fleet panel |
1400
+ | `Ctrl+]` / `Ctrl+PageDown` | Next panel tab |
1401
+ | `Ctrl+PageUp` | Previous panel tab |
1393
1402
  | `Alt+1`…`Alt+9` | Jump to panel tab 1–9 |
1394
1403
  | `Ctrl+X` | Close the focused panel |
1395
1404
  | `Ctrl+Shift+X` | Close all panels |
1396
- | `Ctrl+O` | Open the Ops Control panel |
1405
+ | `Ctrl+O` | Open and focus the Fleet panel (formerly Ops Control) |
1397
1406
  | `Ctrl+G` | Toggle focus between split panes |
1398
1407
 
1399
1408
  ### System
@@ -3,7 +3,7 @@
3
3
  "product": {
4
4
  "id": "goodvibes",
5
5
  "surface": "operator",
6
- "version": "0.36.0"
6
+ "version": "0.38.0"
7
7
  },
8
8
  "auth": {
9
9
  "modes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-tui",
3
- "version": "1.1.0",
3
+ "version": "1.7.0",
4
4
  "description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
5
5
  "type": "module",
6
6
  "main": "src/main.ts",
@@ -99,7 +99,7 @@
99
99
  "@anthropic-ai/vertex-sdk": "^0.16.0",
100
100
  "@ast-grep/napi": "^0.42.0",
101
101
  "@aws/bedrock-token-generator": "^1.1.0",
102
- "@pellux/goodvibes-sdk": "0.36.0",
102
+ "@pellux/goodvibes-sdk": "0.38.0",
103
103
  "bash-language-server": "^5.6.0",
104
104
  "fuse.js": "^7.1.0",
105
105
  "graphql": "^16.13.2",
@@ -0,0 +1,67 @@
1
+ /**
2
+ * alert-gating — shared config + focus gating logic for the unfocused-user
3
+ * alert classes (budget-breach, long-task, agent/chain failure,
4
+ * awaiting-approval). Each alert class has its own on/off config key
5
+ * (default true); a single master key, `behavior.notifyOnlyWhenUnfocused`
6
+ * (default true), decides whether any of them additionally require the
7
+ * terminal to be unfocused-or-unknown before firing.
8
+ *
9
+ * Extracted so every alert-class module (long-task-notifier.ts,
10
+ * budget-breach-notifier.ts, approval-alert.ts, and the agent/chain-failure
11
+ * wiring in turn-event-wiring.ts) shares one implementation of "should this
12
+ * fire right now" instead of re-deriving the same two config reads and the
13
+ * same focus check.
14
+ */
15
+ import type { FocusTracker } from './focus-tracker.ts';
16
+
17
+ /** Minimal config-read surface every gating check needs. */
18
+ export type ConfigGet = (key: string) => unknown;
19
+
20
+ /** Read a TUI-local synthetic boolean setting, defaulting when absent/invalid. */
21
+ export function readBooleanConfig(configGet: ConfigGet, key: string, defaultValue: boolean): boolean {
22
+ const raw = configGet(key);
23
+ if (typeof raw === 'boolean') return raw;
24
+ if (raw === undefined || raw === null) return defaultValue;
25
+ // Tolerate string/number forms the same way the rest of the config layer does
26
+ // (settings-modal writes real booleans, but env/file edits may not).
27
+ if (raw === 'true' || raw === 1) return true;
28
+ if (raw === 'false' || raw === 0) return false;
29
+ return defaultValue;
30
+ }
31
+
32
+ /** `behavior.notifyOnlyWhenUnfocused` — master gate, default on. */
33
+ export function readNotifyOnlyWhenUnfocused(configGet: ConfigGet): boolean {
34
+ return readBooleanConfig(configGet, 'behavior.notifyOnlyWhenUnfocused', true);
35
+ }
36
+
37
+ /**
38
+ * Decide whether a given alert class should fire right now.
39
+ *
40
+ * `classConfigKey` is the per-class on/off switch (e.g.
41
+ * 'behavior.notifyOnBudgetBreach'); default true when absent. When that key
42
+ * is off, the class never fires regardless of focus. When it's on, the
43
+ * result additionally depends on the master `notifyOnlyWhenUnfocused` gate:
44
+ * if that master gate is off, the class fires unconditionally (today's
45
+ * pre-focus-tracking behavior); if on (the default), it only fires when the
46
+ * terminal is unfocused or focus state was never observed (see
47
+ * FocusTracker.shouldAlertWhenUnfocused).
48
+ */
49
+ export function shouldFireAlert(
50
+ focusTracker: Pick<FocusTracker, 'shouldAlertWhenUnfocused'>,
51
+ configGet: ConfigGet,
52
+ classConfigKey: string,
53
+ ): boolean {
54
+ if (!readBooleanConfig(configGet, classConfigKey, true)) return false;
55
+ if (!readNotifyOnlyWhenUnfocused(configGet)) return true;
56
+ return focusTracker.shouldAlertWhenUnfocused();
57
+ }
58
+
59
+ /**
60
+ * notifyCompletion (SDK platform/utils) only rings the bell above 5s of
61
+ * "duration" and only pops a desktop notification above 30s — a heuristic
62
+ * tuned for long-running-turn notifications. The alert classes in this
63
+ * directory are point-in-time events with no natural duration, so they pass
64
+ * this constant as the `durationMs` argument to force both the bell and the
65
+ * desktop popup unconditionally.
66
+ */
67
+ export const FORCE_NOTIFY_DURATION_MS = 30_001;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * approval-alert — fires an unfocused-user alert the moment a tool call
3
+ * becomes a real, user-blocking permission prompt.
4
+ *
5
+ * Anchor: `PERMISSION_REQUESTED` (SDK events/permissions.ts) is the wrong
6
+ * signal — it fires for every policy-evaluation step, including ones
7
+ * auto-resolved by allow-lists/yolo-mode in milliseconds. The only place a
8
+ * request becomes an actual user-facing prompt is where main.ts assigns
9
+ * `permissionPromptRef.requestPermission`. There is no `permissions` feed on
10
+ * UiRuntimeEvents to subscribe to instead, so this wraps the handler at that
11
+ * assignment site.
12
+ *
13
+ * wrapRequestPermissionWithAlert fires the alert synchronously (before the
14
+ * wrapped promise settles) so it reflects "a prompt just appeared", not
15
+ * "a prompt was just resolved".
16
+ *
17
+ * PRIVACY: message content is tool name + permission category only — no
18
+ * args, no file contents, no command strings (mirrors long-task-notifier's
19
+ * privacy guarantee).
20
+ */
21
+ import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
22
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
23
+ import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
24
+ import type { PermissionRequestHandler, PermissionPromptRequest } from '@pellux/goodvibes-sdk/platform/permissions';
25
+ import type { FocusTracker } from './focus-tracker.ts';
26
+ import { shouldFireAlert, FORCE_NOTIFY_DURATION_MS, type ConfigGet } from './alert-gating.ts';
27
+
28
+ export interface ApprovalAlertDeps {
29
+ readonly focusTracker: Pick<FocusTracker, 'shouldAlertWhenUnfocused'>;
30
+ readonly configGet: ConfigGet;
31
+ readonly webhookNotifier?: WebhookNotifier | null;
32
+ }
33
+
34
+ /**
35
+ * Wrap a PermissionRequestHandler so every call also fires an unfocused-user
36
+ * alert (subject to the behavior.notifyOnApprovalPending / notifyOnlyWhenUnfocused
37
+ * gates). The wrapped handler's behavior (what it resolves to) is completely
38
+ * unchanged — this only adds a side effect at call time.
39
+ */
40
+ export function wrapRequestPermissionWithAlert(
41
+ original: PermissionRequestHandler,
42
+ deps: ApprovalAlertDeps,
43
+ ): PermissionRequestHandler {
44
+ return (request: PermissionPromptRequest) => {
45
+ fireApprovalAlert(request, deps);
46
+ return original(request);
47
+ };
48
+ }
49
+
50
+ function fireApprovalAlert(request: PermissionPromptRequest, deps: ApprovalAlertDeps): void {
51
+ if (!shouldFireAlert(deps.focusTracker, deps.configGet, 'behavior.notifyOnApprovalPending')) return;
52
+
53
+ const title = 'GoodVibes — approval needed';
54
+ // PRIVACY: tool name + category only. Never request.args.
55
+ const message = `${request.tool} (${request.category}) is waiting for approval`;
56
+
57
+ try {
58
+ notifyCompletion(title, message, FORCE_NOTIFY_DURATION_MS);
59
+ } catch (err) {
60
+ logger.debug('approval-alert: desktop notify error', { error: String(err) });
61
+ }
62
+
63
+ const webhookNotifier = deps.webhookNotifier;
64
+ if (webhookNotifier) {
65
+ const urls = webhookNotifier.getUrls();
66
+ if (urls.length > 0) {
67
+ webhookNotifier.send(message).catch((err: unknown) => {
68
+ logger.debug('approval-alert: webhook send error', { error: String(err) });
69
+ });
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * budget-breach-notifier — fires an unfocused-user alert the moment session
3
+ * cost crosses the configured budget threshold (behavior.budgetAlertUsd).
4
+ *
5
+ * One-shot semantics: fires exactly once on the false→true edge (crossing
6
+ * into breach), never again while still over budget on every subsequent
7
+ * turn. The "already notified" latch resets when the session drops back
8
+ * under budget (so a later re-breach can fire again) or when the threshold
9
+ * itself changes (raising, lowering, or clearing the budget re-arms it
10
+ * against the new value).
11
+ *
12
+ * Delivery mirrors long-task-notifier.ts: desktop notification
13
+ * (notifyCompletion) + outbound webhook (WebhookNotifier), both gated by
14
+ * the shared focus/config gating in alert-gating.ts. Skips evaluation
15
+ * entirely when the session model is unpriced — a $0 placeholder cost must
16
+ * never be reported as a real breach (mirrors CostTrackerPanel's "unpriced"
17
+ * display convention).
18
+ *
19
+ * PRIVACY: message text is built from cost/threshold numbers and the
20
+ * session id prefix only — no conversation content.
21
+ */
22
+ import { notifyCompletion } from '@pellux/goodvibes-sdk/platform/utils';
23
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
24
+ import type { WebhookNotifier } from '@pellux/goodvibes-sdk/platform/integrations';
25
+ import { calcSessionCost, computeBudgetBreach, isModelPriced } from '../export/cost-utils.ts';
26
+ import type { FocusTracker } from './focus-tracker.ts';
27
+ import { shouldFireAlert, FORCE_NOTIFY_DURATION_MS, type ConfigGet } from './alert-gating.ts';
28
+
29
+ export interface BudgetBreachUsageSnapshot {
30
+ readonly input: number;
31
+ readonly output: number;
32
+ readonly cacheRead: number;
33
+ readonly cacheWrite: number;
34
+ }
35
+
36
+ export interface BudgetBreachNotifierDeps {
37
+ readonly focusTracker: Pick<FocusTracker, 'shouldAlertWhenUnfocused'>;
38
+ readonly configGet: ConfigGet;
39
+ readonly webhookNotifier?: WebhookNotifier | null;
40
+ readonly sessionId: string;
41
+ }
42
+
43
+ /** Stateful edge-trigger checker — construct once per session, call on every TURN_COMPLETED. */
44
+ export interface BudgetBreachNotifier {
45
+ /**
46
+ * Evaluate current usage against the configured threshold and fire an
47
+ * alert on the false→true breach edge. Returns true when an alert was
48
+ * fired this call (for tests / observability), false otherwise.
49
+ */
50
+ check(usage: BudgetBreachUsageSnapshot, sessionModel: string, budgetThresholdUsd: number): boolean;
51
+ }
52
+
53
+ export function createBudgetBreachNotifier(deps: BudgetBreachNotifierDeps): BudgetBreachNotifier {
54
+ let lastThreshold: number | null = null;
55
+ let notified = false;
56
+
57
+ return {
58
+ check(usage, sessionModel, budgetThresholdUsd) {
59
+ if (budgetThresholdUsd !== lastThreshold) {
60
+ // Threshold raised, lowered, or cleared since the last check — re-arm
61
+ // the latch so a breach against the new threshold can fire again.
62
+ lastThreshold = budgetThresholdUsd;
63
+ notified = false;
64
+ }
65
+
66
+ if (budgetThresholdUsd <= 0) return false; // disabled
67
+ if (!isModelPriced(sessionModel)) return false; // cost would be a placeholder, not real
68
+
69
+ const sessionCost = calcSessionCost(usage.input, usage.output, usage.cacheRead, usage.cacheWrite, sessionModel);
70
+ const breached = computeBudgetBreach(sessionCost, budgetThresholdUsd);
71
+
72
+ if (!breached) {
73
+ notified = false; // dropped back under budget — can re-fire on the next crossing
74
+ return false;
75
+ }
76
+ if (notified) return false; // already alerted for this crossing
77
+
78
+ notified = true;
79
+ fireBudgetBreachAlert(deps, sessionCost, budgetThresholdUsd);
80
+ return true;
81
+ },
82
+ };
83
+ }
84
+
85
+ function fireBudgetBreachAlert(deps: BudgetBreachNotifierDeps, sessionCost: number, budgetThresholdUsd: number): void {
86
+ if (!shouldFireAlert(deps.focusTracker, deps.configGet, 'behavior.notifyOnBudgetBreach')) return;
87
+
88
+ const title = 'GoodVibes — budget breach';
89
+ const message = `session cost $${sessionCost.toFixed(2)} exceeded budget $${budgetThresholdUsd.toFixed(2)} · session ${deps.sessionId.slice(0, 8)}`;
90
+
91
+ try {
92
+ notifyCompletion(title, message, FORCE_NOTIFY_DURATION_MS);
93
+ } catch (err) {
94
+ logger.debug('budget-breach-notifier: desktop notify error', { error: String(err) });
95
+ }
96
+
97
+ const webhookNotifier = deps.webhookNotifier;
98
+ if (webhookNotifier) {
99
+ const urls = webhookNotifier.getUrls();
100
+ if (urls.length > 0) {
101
+ webhookNotifier.send(message).catch((err: unknown) => {
102
+ logger.debug('budget-breach-notifier: webhook send error', { error: String(err) });
103
+ });
104
+ }
105
+ }
106
+ }
@@ -3,6 +3,7 @@ import { renderMarkdownTracked } from '../renderer/markdown.ts';
3
3
  import { renderDiffView } from '../renderer/diff-view.ts';
4
4
  import { DARK_THEME } from '../renderer/theme.ts';
5
5
  import { renderToolCallBlock } from '../renderer/tool-call.ts';
6
+ import type { ToolCall } from '@pellux/goodvibes-sdk/platform/types';
6
7
  import { renderThinkingBlock } from '../renderer/thinking.ts';
7
8
  import { renderSystemMessage } from '../renderer/system-message.ts';
8
9
  import { createEmptyLine, type Line, type Cell } from '../types/grid.ts';
@@ -402,3 +403,21 @@ export function logConversationText(
402
403
  const lines = text.split('\n').map((line) => UIFactory.stringToLine(indent + line, width, style));
403
404
  context.history.addLines(lines);
404
405
  }
406
+
407
+ /**
408
+ * logConversationToolResult - Append a single tool-call-style result line to the
409
+ * display history only, reusing renderToolCallBlock so a display-only render (e.g.
410
+ * a slash command's subprocess result) is visually indistinguishable from the
411
+ * model's own tool-call results. Display-only: never touches message history.
412
+ */
413
+ export function logConversationToolResult(
414
+ context: Pick<ConversationRenderContext, 'history'>,
415
+ width: number,
416
+ toolCall: ToolCall,
417
+ status: 'done' | 'error',
418
+ resultSummary: string,
419
+ durationMs: number,
420
+ errorMsg?: string,
421
+ ): void {
422
+ context.history.addLines(renderToolCallBlock(toolCall, status, resultSummary, width, durationMs, errorMsg));
423
+ }
@@ -18,6 +18,7 @@ import {
18
18
  addConversationSplashScreen,
19
19
  conversationTextToLines,
20
20
  logConversationText,
21
+ logConversationToolResult,
21
22
  renderConversationAssistantMessage,
22
23
  renderConversationSystemMessage,
23
24
  renderConversationToolMessage,
@@ -727,6 +728,23 @@ export class ConversationManager extends SdkConversationManager {
727
728
  logConversationText(this.renderingContext(), this._getWidth(), text, style, indent);
728
729
  }
729
730
 
731
+ /**
732
+ * logToolResultBlock - Append a single tool-call-styled result line to the display
733
+ * history only (never touches the LLM message list), so a slash command's subprocess
734
+ * result (e.g. /test) renders visually identical to a real tool-call result without
735
+ * leaking into model context.
736
+ */
737
+ public logToolResultBlock(
738
+ toolCall: ToolCall,
739
+ status: 'done' | 'error',
740
+ resultSummary: string,
741
+ durationMs: number,
742
+ errorMsg?: string,
743
+ ): void {
744
+ logConversationToolResult(this.renderingContext(), this._getWidth(), toolCall, status, resultSummary, durationMs, errorMsg);
745
+ this.markDirty();
746
+ }
747
+
730
748
  /**
731
749
  * clearDisplay - Clear the visual history buffer without touching the LLM context messages.
732
750
  * The next render will show a blank conversation area. Subsequent message additions
@@ -0,0 +1,41 @@
1
+ /**
2
+ * focus-tracker — tracks whether the terminal window currently has OS-level
3
+ * input focus, using the terminal focus-reporting protocol (DECSET ?1004h,
4
+ * enabled at terminal setup in main.ts). The SDK's InputTokenizer already
5
+ * parses the resulting `\x1b[I` (focus-in) / `\x1b[O` (focus-out) sequences
6
+ * into `{ type: 'focus', action }` tokens (platform/core/tokenizer.ts); this
7
+ * module is the single place those tokens land (consumed in
8
+ * src/input/handler-feed.ts's feedInputTokens()).
9
+ *
10
+ * Honest degradation: a terminal that does not implement focus reporting
11
+ * (or a multiplexer that swallows the sequences) never sends focus tokens.
12
+ * In that case `isFocused()` stays `null` forever — "unknown", never a
13
+ * guessed `true`/`false`. Callers that gate a user-facing behavior on focus
14
+ * (e.g. the unfocused-alert notifiers in this directory) must treat `null`
15
+ * the same as "not focused" per the fallback rule: alerts fire when the
16
+ * terminal is definitely unfocused OR when focus state was never observed.
17
+ */
18
+ export class FocusTracker {
19
+ private focused: boolean | null = null;
20
+
21
+ /** True/false once at least one focus token has arrived; null if none ever has. */
22
+ isFocused(): boolean | null {
23
+ return this.focused;
24
+ }
25
+
26
+ /** Called from the input pipeline on every focus token. */
27
+ setFocused(value: boolean): void {
28
+ this.focused = value;
29
+ }
30
+
31
+ /**
32
+ * The alert-gating rule shared by every unfocused-alert notifier: fire
33
+ * when the terminal is known to be unfocused, or when focus was never
34
+ * observed (honest fallback — never silently suppress on an unknown
35
+ * terminal). Only `isFocused() === true` (a real, observed focus-in with
36
+ * no observed focus-out since) suppresses an alert.
37
+ */
38
+ shouldAlertWhenUnfocused(): boolean {
39
+ return this.focused !== true;
40
+ }
41
+ }