@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.
- package/CHANGELOG.md +85 -21
- package/README.md +20 -11
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +36 -0
- package/src/input/command-registry.ts +38 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-feed-routes.ts +61 -23
- package/src/input/handler-feed.ts +47 -23
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +6 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +24 -27
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +13 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/turn-injection.ts +114 -0
- package/src/runtime/bootstrap-command-context.ts +21 -1
- package/src/runtime/bootstrap-command-parts.ts +34 -7
- package/src/runtime/bootstrap-core.ts +14 -4
- package/src/runtime/bootstrap-shell.ts +29 -16
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- 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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
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
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](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
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
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
|
|
165
|
-
-
|
|
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
|
|
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
|
-
| `
|
|
1392
|
-
| `Ctrl+
|
|
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
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
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
|
+
}
|
package/src/core/conversation.ts
CHANGED
|
@@ -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
|
+
}
|