@pellux/goodvibes-tui 0.26.0 → 0.28.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 +40 -0
- package/README.md +12 -7
- package/docs/foundation-artifacts/operator-contract.json +4 -1
- package/package.json +2 -2
- package/src/cli/bundle-command.ts +2 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation-rendering.ts +2 -2
- package/src/core/conversation.ts +53 -8
- package/src/core/session-recovery.ts +26 -18
- package/src/core/system-message-router.ts +42 -26
- package/src/core/turn-event-wiring.ts +13 -16
- package/src/daemon/handlers/calendar/caldav-client.ts +2 -1
- package/src/daemon/handlers/inbox/index.ts +2 -1
- package/src/daemon/handlers/inbox/poller.ts +2 -1
- package/src/daemon/handlers/inbox/providers/discord.ts +2 -1
- package/src/daemon/handlers/inbox/providers/email.ts +2 -1
- package/src/daemon/handlers/inbox/providers/route-util.ts +2 -1
- package/src/daemon/handlers/inbox/providers/slack.ts +2 -1
- package/src/daemon/handlers/triage/integration.ts +2 -1
- package/src/daemon/handlers/triage/pipeline.ts +2 -1
- package/src/daemon/handlers/triage/tagger/discord.ts +2 -1
- package/src/daemon/handlers/triage/tagger/imap.ts +4 -3
- package/src/export/gist-uploader.ts +3 -1
- package/src/input/command-registry.ts +1 -0
- package/src/input/commands/cloudflare-runtime.ts +2 -1
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +2 -4
- package/src/input/commands/health-runtime.ts +13 -7
- package/src/input/commands/knowledge.ts +2 -1
- package/src/input/commands/runtime-services.ts +40 -10
- package/src/input/handler-command-route.ts +1 -1
- package/src/input/handler-feed-routes.ts +61 -4
- package/src/input/handler-feed.ts +13 -0
- package/src/input/handler-interactions.ts +2 -1
- package/src/input/handler-modal-routes.ts +2 -1
- package/src/input/handler-onboarding-cloudflare.ts +2 -1
- package/src/input/handler-onboarding.ts +8 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-ui-state.ts +5 -1
- package/src/input/keybindings.ts +5 -0
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +10 -0
- package/src/input/tts-settings-actions.ts +2 -1
- package/src/main.ts +52 -60
- package/src/panels/agent-inspector-panel.ts +90 -15
- package/src/panels/agent-inspector-shared.ts +3 -3
- package/src/panels/agent-logs-panel.ts +149 -72
- package/src/panels/approval-panel.ts +146 -95
- package/src/panels/automation-control-panel.ts +24 -1
- package/src/panels/base-panel.ts +28 -3
- package/src/panels/builtin/agent.ts +3 -1
- package/src/panels/builtin/development.ts +2 -1
- package/src/panels/builtin/session.ts +1 -1
- package/src/panels/cockpit-panel.ts +24 -5
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +108 -43
- package/src/panels/context-visualizer-panel.ts +49 -15
- package/src/panels/control-plane-panel.ts +27 -1
- package/src/panels/cost-tracker-panel.ts +87 -65
- package/src/panels/debug-panel.ts +61 -37
- package/src/panels/diff-panel.ts +59 -30
- package/src/panels/docs-panel.ts +30 -24
- package/src/panels/eval-panel.ts +130 -81
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +42 -42
- package/src/panels/file-preview-panel.ts +11 -3
- package/src/panels/forensics-panel.ts +27 -13
- package/src/panels/git-panel.ts +65 -84
- package/src/panels/hooks-panel.ts +36 -2
- package/src/panels/incident-review-panel.ts +31 -10
- package/src/panels/intelligence-panel.ts +152 -71
- package/src/panels/knowledge-graph-panel.ts +89 -27
- package/src/panels/local-auth-panel.ts +17 -11
- package/src/panels/marketplace-panel.ts +33 -13
- package/src/panels/memory-panel.ts +7 -5
- package/src/panels/ops-control-panel.ts +69 -7
- package/src/panels/ops-strategy-panel.ts +61 -43
- package/src/panels/orchestration-panel.ts +34 -4
- package/src/panels/panel-list-panel.ts +33 -8
- package/src/panels/panel-manager.ts +23 -4
- package/src/panels/plan-dashboard-panel.ts +183 -66
- package/src/panels/plugins-panel.ts +48 -10
- package/src/panels/policy-panel.ts +60 -23
- package/src/panels/polish-core.ts +157 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +44 -145
- package/src/panels/project-planning-panel.ts +27 -4
- package/src/panels/provider-accounts-panel.ts +55 -18
- package/src/panels/provider-health-panel.ts +118 -87
- package/src/panels/provider-stats-panel.ts +47 -22
- package/src/panels/qr-panel.ts +23 -11
- package/src/panels/remote-panel.ts +12 -5
- package/src/panels/routes-panel.ts +27 -5
- package/src/panels/sandbox-panel.ts +62 -27
- package/src/panels/schedule-panel.ts +44 -24
- package/src/panels/scrollable-list-panel.ts +124 -10
- package/src/panels/security-panel.ts +72 -20
- package/src/panels/services-panel.ts +68 -22
- package/src/panels/session-browser-panel.ts +42 -26
- package/src/panels/session-maintenance.ts +2 -2
- package/src/panels/settings-sync-panel.ts +30 -15
- package/src/panels/skills-panel.ts +2 -1
- package/src/panels/subscription-panel.ts +21 -3
- package/src/panels/symbol-outline-panel.ts +40 -47
- package/src/panels/system-messages-panel.ts +60 -27
- package/src/panels/tasks-panel.ts +36 -14
- package/src/panels/thinking-panel.ts +32 -36
- package/src/panels/token-budget-panel.ts +80 -53
- package/src/panels/tool-inspector-panel.ts +37 -39
- package/src/panels/types.ts +25 -0
- package/src/panels/watchers-panel.ts +25 -5
- package/src/panels/work-plan-panel.ts +127 -35
- package/src/panels/worktree-panel.ts +65 -20
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +53 -147
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/modal-utils.ts +0 -10
- package/src/renderer/model-picker-overlay.ts +9 -4
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-modal.ts +2 -2
- package/src/renderer/progress.ts +3 -2
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/ui-factory.ts +4 -6
- package/src/runtime/bootstrap-command-context.ts +3 -0
- package/src/runtime/bootstrap-command-parts.ts +3 -1
- package/src/runtime/bootstrap-core.ts +31 -31
- package/src/runtime/bootstrap-shell.ts +1 -0
- package/src/runtime/onboarding/apply.ts +4 -3
- package/src/runtime/onboarding/snapshot.ts +4 -3
- package/src/runtime/process-lifecycle.ts +195 -0
- package/src/runtime/services.ts +4 -1
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/runtime/wrfc-persistence.ts +20 -5
- package/src/shell/ui-openers.ts +3 -2
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/verification/live-verifier.ts +4 -3
- package/src/version.ts +1 -1
- package/src/core/context-auto-compact.ts +0 -110
- package/src/panels/panel-picker.ts +0 -106
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.28.0] — 2026-06-30
|
|
8
|
+
|
|
9
|
+
Panel navigation & UI/UX overhaul, plus the accumulated post-0.27.0 UX fixes.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Unified panel navigation**: a single consolidated tab bar with a focus-accent border; jump directly to any panel with `Alt+1`–`Alt+9`, click tabs, and toggle the active pane with `Ctrl+G`.
|
|
13
|
+
- **In-panel filtering**: press `/` to filter the list in panels that support it.
|
|
14
|
+
- **Mouse-wheel scrolling** on every panel (via `BasePanel.handleScroll`).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Width-aware formatting** across panels: shared toolkit primitives (`fitDisplay`, table/tree/badge/meter helpers) replace hand-rolled `.slice()`/`.padEnd()`, so rows stay aligned across emoji, CJK, and other wide characters.
|
|
18
|
+
- **Scroll-back**: you can scroll up during and after a turn; the view only auto-follows the tail when parked at the bottom.
|
|
19
|
+
- **Auto-compaction is now owned entirely by the SDK** (`@pellux/goodvibes-sdk` 0.35.0 `handlePostTurnContextMaintenance`). The redundant TUI-side trigger was removed to prevent double-compaction; manual `/compact` now builds a richer handoff context (running agents, WRFC chains, active plan, session lineage).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Timer/async panels repaint while the main thread is idle instead of looking frozen (repaints gated on panel-active state).
|
|
23
|
+
- Input/keybinding papercuts: reverse-search accept, Home/End, and the command palette.
|
|
24
|
+
- Crash/signal safety net: the terminal is always restored on uncaught exceptions and termination signals.
|
|
25
|
+
- Consolidated number/duration/context-usage formatting and routed the context window through one corrected `getContextWindowForModel` source across every surface.
|
|
26
|
+
|
|
27
|
+
### Internal
|
|
28
|
+
- Split `polish.ts` into leaf `polish-core.ts` + `polish-tables.ts` and extracted `wrfc-panel-format.ts` to satisfy the 800-line architecture gate; no public API changes (all symbols re-exported).
|
|
29
|
+
|
|
30
|
+
## [0.27.0] — 2026-06-30
|
|
31
|
+
|
|
32
|
+
Full deep-review audit of the TUI (33 findings fixed, each reviewed to a score of 10) and adoption of `@pellux/goodvibes-sdk` 0.35.0.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
- **Context window**: the footer meter, footer-height, context-inspector overlay, `/health`, `/guidance`, and the context visualizer now use the resolved `getContextWindowForModel` so every surface agrees; the pre-catalog fallback uses the SDK's family-aware `inferFallbackContextWindow` instead of a hardcoded 128k/32k.
|
|
36
|
+
- **Compaction**: removed the TUI's redundant/racy post-turn auto-compact — the SDK Orchestrator's post-turn maintenance is now the sole, correctly-resolved path (the dead `context-auto-compact` module was deleted).
|
|
37
|
+
- **Sub-agent panels**: the Agent Inspector, Agent Logs, Token Budget, and Context Visualizer panels now repaint live (`requestRender` + live window / `lastInputTokens` wired at their construction sites); Agent Logs repaints on `streamingContent` growth, not just JSONL file-size changes — fixing the frozen-panel and stale-inspector symptoms.
|
|
38
|
+
- **Scroll / lifecycle**: scroll-up respects `scrollLocked` (no overwrite during a turn) and agrees with the renderer (no dead/snap-back when idle); the terminal is restored on `uncaughtException`/SIGTERM/SIGHUP/fatal startup; `exitApp` awaits shutdown before exit.
|
|
39
|
+
- **WRFC**: panel resume covers `reviewing`/`fixing`/`awaiting_gates`; `rehydrate` re-imports interrupted chains; the panel subscribes to the new `WORKFLOW_SCORE_REGRESSION` event.
|
|
40
|
+
- **Core**: journal replay preserves the session title/titleSource; transcript event-navigation indexing fixed; the streaming buffer survives a mid-stream terminal resize; journal seq-collision fixed; high-priority system messages now reach conversation delivery.
|
|
41
|
+
- **Sub-agent firehose**: resolved at the source by adopting SDK 0.35.0 (agent progress no longer dumps raw streamed output into the one-line status slot).
|
|
42
|
+
- **DRY**: standardized 53 error stringifications on the SDK `summarizeError`; consolidated three duration formatters onto `formatElapsed`; migrated 12 panel palettes to `extendPalette(DEFAULT_PANEL_PALETTE, …)` so themes propagate.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Adopt `@pellux/goodvibes-sdk` **0.35.0** — its own 55-finding audit remediation: tool-loop circuit-breaker fix, window-scaled compaction safety buffer, `configured_cap` context-window resolution, MCP orphan-restart fix, transport-middleware retry + SSE auth fixes, and the new `inferFallbackContextWindow` export.
|
|
46
|
+
|
|
7
47
|
## [0.26.0] — 2026-06-21
|
|
8
48
|
|
|
9
49
|
### Changes
|
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
|
|
|
@@ -125,7 +125,7 @@ The interface is rendered directly to the alternate screen buffer with raw ANSI
|
|
|
125
125
|
|
|
126
126
|
The runtime is organized around typed store domains, typed runtime events, a shared control plane for permissions and orchestration, and product surfaces for reviewing and repairing state. Agents run in-process with isolated histories, scoped tools, and optional worktrees. Operational state such as provider routing, local auth, daemon/gateway posture, channels, search, artifacts, structured knowledge, multimodal analysis, remote sessions, settings control-plane state, and task execution is routed into dedicated panels and APIs.
|
|
127
127
|
|
|
128
|
-
The TUI now consumes the extracted `@pellux/goodvibes-sdk` platform layer for shared contracts, daemon route surfaces, transports, remote runtime contracts, and other reusable runtime code. The repo keeps the terminal UI, host wiring, and product-specific composition while future surfaces consume the same SDK-backed runtime foundation.
|
|
128
|
+
The TUI now consumes the extracted `@pellux/goodvibes-sdk` (`0.34.0`) platform layer for shared contracts, daemon route surfaces, transports, remote runtime contracts, and other reusable runtime code. The repo keeps the terminal UI, host wiring, and product-specific composition while future surfaces consume the same SDK-backed runtime foundation.
|
|
129
129
|
|
|
130
130
|
---
|
|
131
131
|
|
|
@@ -145,6 +145,7 @@ The TUI now consumes the extracted `@pellux/goodvibes-sdk` platform layer for sh
|
|
|
145
145
|
- Width-aware overlays, stable bottom docking above the prompt, half-height message surfaces, and structured footer layers
|
|
146
146
|
- Shared panel workspace layout budgeting with renderer-owned visible-row budgets
|
|
147
147
|
- Copy/selection logic that strips decorative gutters and visual scaffolding from clipboard output
|
|
148
|
+
- Line-count indicator rendered right-aligned inside the prompt border on multi-line input (footer height stays stable regardless of prompt line count)
|
|
148
149
|
|
|
149
150
|
### Conversation And Transcript Workflow
|
|
150
151
|
- Markdown rendering, syntax highlighting, inline diffs, collapsible blocks, bookmarks, block copy, and block save
|
|
@@ -540,6 +541,7 @@ Related storage paths:
|
|
|
540
541
|
| `behavior.autoCompactThreshold` | `80` | Context % before auto-compact triggers |
|
|
541
542
|
| `behavior.saveHistory` | `true` | Persist conversation history |
|
|
542
543
|
| `behavior.returnContextMode` | `off` | Session return-context mode: `off`, `local`, `assisted` |
|
|
544
|
+
| `behavior.notifyAfterSeconds` | `60` | Send desktop and webhook notifications for long-running turns after this many seconds (`0` disables); metadata-only — no conversation text is included |
|
|
543
545
|
| `behavior.guidanceMode` | `minimal` | Operational guidance mode: `off`, `minimal`, `guided` |
|
|
544
546
|
| `storage.secretPolicy` | `preferred_secure` | Secret storage policy: prefer secure backing store, fall back when allowed |
|
|
545
547
|
| `permissions.mode` | `prompt` | Permission mode: `prompt`, `allow-all`, `custom` |
|
|
@@ -803,7 +805,7 @@ Key commands:
|
|
|
803
805
|
- `/update`
|
|
804
806
|
- `/trust`
|
|
805
807
|
- `/bridge`
|
|
806
|
-
- `/profile-sync`
|
|
808
|
+
- `/profile-sync` (alias: `/profilesync`)
|
|
807
809
|
|
|
808
810
|
The setup surface is also broader than a single readiness screen:
|
|
809
811
|
|
|
@@ -874,7 +876,7 @@ Key commands:
|
|
|
874
876
|
|
|
875
877
|
- `/services inspect|test|resolve|auth|auth-review|doctor|export|import`
|
|
876
878
|
- `/profiles`
|
|
877
|
-
- `/profile-sync`
|
|
879
|
+
- `/profile-sync` (alias: `/profilesync`)
|
|
878
880
|
- `/setup transfer export|inspect|import`
|
|
879
881
|
|
|
880
882
|
Service entries can use existing `tokenKey` fields, a SecretRef in the key field, or explicit `tokenRef` / `passwordRef` / `webhookUrlRef` / `signingSecretRef` / `publicKeyRef` / `appTokenRef` fields:
|
|
@@ -1270,6 +1272,8 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
|
|
|
1270
1272
|
| `/accounts [action]` | — | Review provider-account routes, auth posture, and repair actions |
|
|
1271
1273
|
| `/auth [action]` | — | Review auth posture and manage local service auth users/sessions |
|
|
1272
1274
|
| `/memory [action]` | — | Session memory management: `list`, `add <text>`, `remove <id>` |
|
|
1275
|
+
| `/keep <text>` | — | Pin text to session memory; survives context compaction |
|
|
1276
|
+
| `/memory-sync [action]` | `/memsync` | Durable memory export/import and bundle exchange: `export <path> [scope]`, `import <path>` |
|
|
1273
1277
|
| `/recall [action]` | `/rc` | Durable knowledge and memory substrate: capture, review, explain, export, import, and handoff |
|
|
1274
1278
|
| `/knowledge [action]` | `/know`, `/kb` | Structured knowledge graph: ingest URLs/bookmarks, inspect issues, build packets, and run consolidation jobs |
|
|
1275
1279
|
| `/context` | `/ctx` | Inspect context window usage (token breakdown per message) |
|
|
@@ -1281,7 +1285,7 @@ Those pieces cover conversation-noise routing, panel-health/performance budgets,
|
|
|
1281
1285
|
| `/git [action]` | `/g` | Git commands: status, log, diff. Opens git panel if no action given |
|
|
1282
1286
|
| `/scan` | — | Scan for local LLM servers |
|
|
1283
1287
|
| `/plan [goal]` | — | Inspect or seed TUI-owned project planning state; `panel`, `approve`, `list`, and `show <id>` are supported |
|
|
1284
|
-
| `/work-plan [action]` | `/wp`, `/todo` | Open or update the persistent workspace work-plan checklist |
|
|
1288
|
+
| `/work-plan [action]` | `/wp`, `/todo`, `/workplan` | Open or update the persistent workspace work-plan checklist |
|
|
1285
1289
|
| `/panel [action]` | `/panels` | Panel management: open, close, list, toggle, move, focus, split, width, height |
|
|
1286
1290
|
| `/plugin [action]` | — | Manage plugins (enable/disable/reload/list) |
|
|
1287
1291
|
| `/marketplace [action]` | — | Browse curated plugin, skill, hook-pack, and policy-pack surfaces |
|
|
@@ -1363,6 +1367,7 @@ All shortcuts are customizable via `~/.goodvibes/tui/keybindings.json`. Use `/ke
|
|
|
1363
1367
|
| `Mouse wheel` | Scroll |
|
|
1364
1368
|
| `Click drag` | Select text |
|
|
1365
1369
|
| `Middle click` | Paste |
|
|
1370
|
+
| `n` / `N` | Next / previous match in locked search mode; wraps with a `(wrap)` marker |
|
|
1366
1371
|
| `Escape` | Exit current mode (search, command, modal) |
|
|
1367
1372
|
|
|
1368
1373
|
### Blocks & Content
|
|
@@ -1646,7 +1651,7 @@ src/
|
|
|
1646
1651
|
- **Agent Client Protocol** — subagents communicate via @agentclientprotocol/sdk over stdio ndJsonStream
|
|
1647
1652
|
- **Backend-first external surface** — the daemon/control plane exposes typed HTTP/gateway methods for knowledge, artifacts, media, search, channels, and remote peers so future clients do not have to reimplement runtime logic
|
|
1648
1653
|
- **Plugin system** — manifest.json + sandboxed API surface for commands, providers, tools, gateway methods, channels, embeddings, voice, media, and search
|
|
1649
|
-
- **Crash recovery** — periodic JSONL snapshots with recovery prompt on next startup
|
|
1654
|
+
- **Crash recovery** — periodic JSONL snapshots with recovery prompt on next startup; an fsync-per-record append-only transcript journal between snapshots is replayed at every resume seam (command resume, Ctrl+R crash recovery, panel resume) for SIGKILL-proof durability
|
|
1650
1655
|
|
|
1651
1656
|
---
|
|
1652
1657
|
|
|
@@ -1664,7 +1669,7 @@ bun run dev
|
|
|
1664
1669
|
bun test
|
|
1665
1670
|
```
|
|
1666
1671
|
|
|
1667
|
-
8,
|
|
1672
|
+
8,978 tests across contract, security, release gate, runtime, renderer, panel, integration, and UX anti-regression suites. Performance budget gate runs as part of CI — the build fails if any of the 5 perf budgets (store update latency, event dispatch latency, tool execution overhead, compaction duration, startup time) are exceeded.
|
|
1668
1673
|
|
|
1669
1674
|
### Build standalone binary
|
|
1670
1675
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"product": {
|
|
4
4
|
"id": "goodvibes",
|
|
5
5
|
"surface": "operator",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.35.0"
|
|
7
7
|
},
|
|
8
8
|
"auth": {
|
|
9
9
|
"modes": [
|
|
@@ -19468,6 +19468,7 @@
|
|
|
19468
19468
|
],
|
|
19469
19469
|
"additionalProperties": false
|
|
19470
19470
|
},
|
|
19471
|
+
"dangerous": true,
|
|
19471
19472
|
"invokable": true
|
|
19472
19473
|
},
|
|
19473
19474
|
{
|
|
@@ -20641,6 +20642,7 @@
|
|
|
20641
20642
|
],
|
|
20642
20643
|
"additionalProperties": false
|
|
20643
20644
|
},
|
|
20645
|
+
"dangerous": true,
|
|
20644
20646
|
"invokable": true
|
|
20645
20647
|
},
|
|
20646
20648
|
{
|
|
@@ -25987,6 +25989,7 @@
|
|
|
25987
25989
|
],
|
|
25988
25990
|
"additionalProperties": false
|
|
25989
25991
|
},
|
|
25992
|
+
"dangerous": true,
|
|
25990
25993
|
"invokable": true
|
|
25991
25994
|
},
|
|
25992
25995
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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.35.0",
|
|
103
103
|
"bash-language-server": "^5.6.0",
|
|
104
104
|
"fuse.js": "^7.1.0",
|
|
105
105
|
"graphql": "^16.13.2",
|
|
@@ -15,6 +15,7 @@ import { getPackageVersion } from './help.ts';
|
|
|
15
15
|
import { classifyProviderSetup } from './provider-classification.ts';
|
|
16
16
|
import { buildCliServicePosture } from './service-posture.ts';
|
|
17
17
|
import { REDACTED_VALUE, collectSensitiveConfigValues, isRedactedValue, redactConfig, redactSerializedSecrets } from './redaction.ts';
|
|
18
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
18
19
|
|
|
19
20
|
interface BundleInspectSummary {
|
|
20
21
|
readonly type: string;
|
|
@@ -43,7 +44,7 @@ function readJsonFile(path: string): { readonly ok: true; readonly value: Record
|
|
|
43
44
|
try {
|
|
44
45
|
return { ok: true, value: JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown> };
|
|
45
46
|
} catch (error) {
|
|
46
|
-
return { ok: false, error:
|
|
47
|
+
return { ok: false, error: summarizeError(error) };
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -10,6 +10,7 @@ import { resolveRuntimeEndpointBinding } from './endpoints.ts';
|
|
|
10
10
|
import type { RuntimeEndpointBinding, RuntimeEndpointId } from './endpoints.ts';
|
|
11
11
|
import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
|
|
12
12
|
import { redactText } from './redaction.ts';
|
|
13
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
13
14
|
|
|
14
15
|
export interface CliServiceRuntime {
|
|
15
16
|
readonly configManager: ConfigManager;
|
|
@@ -326,7 +327,7 @@ function readLogPosture(path: string | undefined, tailBytes: number): CliService
|
|
|
326
327
|
exists: true,
|
|
327
328
|
size: 0,
|
|
328
329
|
modifiedAt: null,
|
|
329
|
-
readError:
|
|
330
|
+
readError: summarizeError(error),
|
|
330
331
|
};
|
|
331
332
|
}
|
|
332
333
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared context-window usage computation.
|
|
3
|
+
*
|
|
4
|
+
* Centralises the tokens/contextWindow ratio so all panels, auto-compact,
|
|
5
|
+
* and session-maintenance read from one formula rather than six diverging
|
|
6
|
+
* hand-rolled copies.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: compaction-preview.ts intentionally displays usage > 100%
|
|
9
|
+
* ("exceeds window") — callers that need the unclamped value must use
|
|
10
|
+
* rawRatio, not clampedRatio / pct.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface ContextUsage {
|
|
14
|
+
/**
|
|
15
|
+
* Raw (unclamped) ratio: max(0, tokens) / contextWindow.
|
|
16
|
+
* May exceed 1.0 when token count surpasses the context window.
|
|
17
|
+
* 0 when contextWindow <= 0.
|
|
18
|
+
*/
|
|
19
|
+
rawRatio: number;
|
|
20
|
+
/**
|
|
21
|
+
* Ratio clamped to [0, 1]. Use for progress-bar fill and color bands.
|
|
22
|
+
*/
|
|
23
|
+
clampedRatio: number;
|
|
24
|
+
/**
|
|
25
|
+
* Integer percentage clamped to [0, 100].
|
|
26
|
+
* Equivalent to Math.min(100, Math.round(rawRatio * 100)).
|
|
27
|
+
* Use for numeric displays and threshold comparisons.
|
|
28
|
+
*/
|
|
29
|
+
pct: number;
|
|
30
|
+
/**
|
|
31
|
+
* Remaining tokens: max(0, contextWindow - tokens).
|
|
32
|
+
* 0 when contextWindow <= 0.
|
|
33
|
+
*/
|
|
34
|
+
remaining: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Compute context-window usage metrics from raw token counts.
|
|
39
|
+
*
|
|
40
|
+
* @param tokens Current input-token count (negative values are treated as 0).
|
|
41
|
+
* @param contextWindow Model context window size (0 or negative → all fields return 0).
|
|
42
|
+
*/
|
|
43
|
+
export function computeContextUsage(tokens: number, contextWindow: number): ContextUsage {
|
|
44
|
+
if (contextWindow <= 0) {
|
|
45
|
+
return { rawRatio: 0, clampedRatio: 0, pct: 0, remaining: 0 };
|
|
46
|
+
}
|
|
47
|
+
const safeTokens = Math.max(0, tokens);
|
|
48
|
+
const rawRatio = safeTokens / contextWindow;
|
|
49
|
+
const clampedRatio = Math.min(1, rawRatio);
|
|
50
|
+
const pct = Math.min(100, Math.round(rawRatio * 100));
|
|
51
|
+
const remaining = Math.max(0, contextWindow - tokens);
|
|
52
|
+
return { rawRatio, clampedRatio, pct, remaining };
|
|
53
|
+
}
|
|
@@ -320,10 +320,10 @@ export function appendConversationMessages(
|
|
|
320
320
|
|
|
321
321
|
for (let msgIdx = 0; msgIdx < messages.length; msgIdx++) {
|
|
322
322
|
const message = messages[msgIdx];
|
|
323
|
-
messageLineRegistry[msgIdx] = context.history.getLineCount();
|
|
324
323
|
// absoluteIdx aligns the slice-relative loop counter with the absolute
|
|
325
|
-
// message index used as the key in messageKindRegistry.
|
|
324
|
+
// message index used as the key in messageKindRegistry and messageLineRegistry.
|
|
326
325
|
const absoluteIdx = msgIndexOffset + msgIdx;
|
|
326
|
+
messageLineRegistry[absoluteIdx] = context.history.getLineCount();
|
|
327
327
|
if (message.role === 'user') {
|
|
328
328
|
renderConversationUserMessage(context, message, width);
|
|
329
329
|
} else if (message.role === 'assistant') {
|
package/src/core/conversation.ts
CHANGED
|
@@ -64,7 +64,15 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
64
64
|
private collapseState: Map<string, boolean> = new Map();
|
|
65
65
|
/** Block registry: track rendered blocks for copy/apply. */
|
|
66
66
|
protected blockRegistry: BlockMeta[] = [];
|
|
67
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Absolute message index -> first rendered line index in the history buffer.
|
|
69
|
+
* Keyed by the message's ABSOLUTE position in the full (unsliced) snapshot —
|
|
70
|
+
* appendConversationMessages writes `messageLineRegistry[absoluteIdx]`, not the
|
|
71
|
+
* slice-relative loop counter — so transcript-event navigation
|
|
72
|
+
* (next/prevTranscriptEventLine), whose event.messageIndex is absolute, resolves
|
|
73
|
+
* correctly even after clearDisplay() renders only a tail slice. Holes (indices
|
|
74
|
+
* not currently rendered) read back as undefined and are filtered out by consumers.
|
|
75
|
+
*/
|
|
68
76
|
private messageLineRegistry: number[] = [];
|
|
69
77
|
/**
|
|
70
78
|
* Registry of rendered line indices for system messages whose kind is
|
|
@@ -91,11 +99,18 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
91
99
|
* Reset to 0 on startStreamingBlock and finalizeStreamingBlock.
|
|
92
100
|
*/
|
|
93
101
|
private _lastStreamRenderMs = 0;
|
|
102
|
+
/**
|
|
103
|
+
* Terminal width captured when the current streaming block was anchored.
|
|
104
|
+
* If the width changes mid-stream, updateStreamingBlock forces a rebuild so
|
|
105
|
+
* streamingStartLine is re-anchored to the new buffer (see rebuildHistory).
|
|
106
|
+
* -1 when no stream is active.
|
|
107
|
+
*/
|
|
108
|
+
private _streamWidth = -1;
|
|
94
109
|
/**
|
|
95
110
|
* Message index at the time of the last clearDisplay() call.
|
|
96
111
|
* rebuildHistory() renders only messages at or after this index, so the
|
|
97
112
|
* display stays blank for messages added before the clear while LLM history
|
|
98
|
-
* is fully preserved.
|
|
113
|
+
* is fully preserved. Persists until resetAll(). rebuildHistory() never resets this value.
|
|
99
114
|
*/
|
|
100
115
|
private _displayFromMessageIndex = 0;
|
|
101
116
|
|
|
@@ -229,6 +244,7 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
229
244
|
// Reset the render throttle so the first delta always renders immediately.
|
|
230
245
|
this.flushHistory();
|
|
231
246
|
this.streamingStartLine = this.history.getLineCount();
|
|
247
|
+
this._streamWidth = this._getWidth();
|
|
232
248
|
this._lastStreamRenderMs = 0;
|
|
233
249
|
}
|
|
234
250
|
|
|
@@ -243,6 +259,13 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
243
259
|
// Throttle renderMarkdown to the 16ms render-coalescer cadence to avoid
|
|
244
260
|
// O(n) parse overhead on every delta token during streaming.
|
|
245
261
|
if (this.streamingStartLine >= 0) {
|
|
262
|
+
// If the terminal was resized mid-stream, force the pending width-change
|
|
263
|
+
// rebuild now. rebuildHistory() re-anchors streamingStartLine to the new
|
|
264
|
+
// buffer (see its streaming branch), so the incremental truncate below
|
|
265
|
+
// targets the correct offset instead of a stale one.
|
|
266
|
+
if (this._getWidth() !== this._streamWidth) {
|
|
267
|
+
this.flushHistory();
|
|
268
|
+
}
|
|
246
269
|
const now = Date.now();
|
|
247
270
|
if (now - this._lastStreamRenderMs >= 16) {
|
|
248
271
|
this._lastStreamRenderMs = now;
|
|
@@ -261,6 +284,7 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
261
284
|
public override finalizeStreamingBlock(): void {
|
|
262
285
|
super.finalizeStreamingBlock();
|
|
263
286
|
this.streamingStartLine = -1;
|
|
287
|
+
this._streamWidth = -1;
|
|
264
288
|
this._lastStreamRenderMs = 0;
|
|
265
289
|
this.markDirty();
|
|
266
290
|
}
|
|
@@ -367,12 +391,21 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
367
391
|
this.dirty = false;
|
|
368
392
|
|
|
369
393
|
const snapshot = this.getMessageSnapshot();
|
|
394
|
+
// During streaming, the in-progress placeholder (always the last message) is
|
|
395
|
+
// rendered here as EMPTY; the incremental streaming path (updateStreamingBlock)
|
|
396
|
+
// owns its content. This keeps streamingStartLine valid across width-change
|
|
397
|
+
// rebuilds — otherwise the placeholder would be re-rendered with its content at
|
|
398
|
+
// the new width while streamingStartLine still pointed into the old buffer.
|
|
399
|
+
const lastMsg = snapshot[snapshot.length - 1];
|
|
400
|
+
const isStreaming = this.streamingStartLine >= 0 && lastMsg?.role === 'assistant';
|
|
401
|
+
const renderSnapshot = isStreaming
|
|
402
|
+
? [...snapshot.slice(0, -1), { ...lastMsg, content: '' } as Message]
|
|
403
|
+
: snapshot;
|
|
404
|
+
|
|
370
405
|
// When _displayFromMessageIndex > 0, clearDisplay() was called. Only render
|
|
371
406
|
// messages added after the clear — the pre-clear history stays off-screen.
|
|
372
|
-
// On a full rebuild (e.g. width change), reset the display-start to 0 so the
|
|
373
|
-
// user can scroll back to the full history if needed.
|
|
374
407
|
const displayStart = this._displayFromMessageIndex;
|
|
375
|
-
const visibleSnapshot = displayStart > 0 ?
|
|
408
|
+
const visibleSnapshot = displayStart > 0 ? renderSnapshot.slice(displayStart) : renderSnapshot;
|
|
376
409
|
|
|
377
410
|
// Tool messages ARE rendered (as collapsed blocks); this filter is only
|
|
378
411
|
// for determining whether to show the splash screen (tool-only messages
|
|
@@ -388,6 +421,18 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
388
421
|
|
|
389
422
|
this.appendMessages(visibleSnapshot, width, displayStart);
|
|
390
423
|
this.appendedUpTo = snapshot.length;
|
|
424
|
+
|
|
425
|
+
if (isStreaming) {
|
|
426
|
+
// Re-anchor the streaming block to the freshly rebuilt buffer and re-render
|
|
427
|
+
// the in-progress content at the current width, mirroring startStreamingBlock.
|
|
428
|
+
this.streamingStartLine = this.history.getLineCount();
|
|
429
|
+
this._streamWidth = width;
|
|
430
|
+
this._lastStreamRenderMs = 0;
|
|
431
|
+
const streamingContent = lastMsg?.content;
|
|
432
|
+
if (typeof streamingContent === 'string' && streamingContent.length > 0) {
|
|
433
|
+
this.history.addLines(renderMarkdown(streamingContent, width, { isStreaming: true }));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
391
436
|
}
|
|
392
437
|
|
|
393
438
|
/**
|
|
@@ -615,9 +660,9 @@ export class ConversationManager extends SdkConversationManager {
|
|
|
615
660
|
// messageKindRegistry is NOT cleared here. The underlying messages array is
|
|
616
661
|
// preserved by clearDisplay(); kind entries for pre-clear messages are harmless
|
|
617
662
|
// because those messages are hidden by _displayFromMessageIndex and never rendered.
|
|
618
|
-
// Clearing the registry would cause kind loss for pre-clear messages
|
|
619
|
-
//
|
|
620
|
-
//
|
|
663
|
+
// Clearing the registry would cause kind loss for pre-clear messages; if
|
|
664
|
+
// resetAll() later restores displayStart to 0, those messages re-render
|
|
665
|
+
// and their kind entries must still be present to suppress navigation.
|
|
621
666
|
// Advance _displayFromMessageIndex to exclude all current messages from display.
|
|
622
667
|
// rebuildHistory() will only render messages added AFTER this point.
|
|
623
668
|
this._displayFromMessageIndex = this.getMessageSnapshot().length;
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
* journal silently and return.
|
|
27
27
|
* 3. If records are found, apply the final record's messages — each journal
|
|
28
28
|
* record carries the full conversation snapshot at that moment, so the
|
|
29
|
-
*
|
|
29
|
+
* record with the newest timestamp is the authoritative post-crash state
|
|
30
|
+
* (resilient to seq collisions across re-inits onto a stale journal file).
|
|
30
31
|
* 4. Rebuild the conversation history and call the snapshot writer so the
|
|
31
32
|
* gap is durably closed before the user sees the restored conversation.
|
|
32
33
|
* 5. Rotate the journal (it is no longer needed as a gap-filler).
|
|
@@ -64,8 +65,6 @@ export interface ReplayIntoConversationResult {
|
|
|
64
65
|
readonly replayed: number;
|
|
65
66
|
/** True if the journal tail was corrupt (quarantined). */
|
|
66
67
|
readonly hadCorruptTail: boolean;
|
|
67
|
-
/** True if the journal had an unrecognised schema version (quarantined). */
|
|
68
|
-
readonly hadVersionMismatch: boolean;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
@@ -85,30 +84,39 @@ export function replayJournalIntoConversation(
|
|
|
85
84
|
try {
|
|
86
85
|
const { records, hadCorruptTail } = replayJournal(journalPath, snapshotTimestamp);
|
|
87
86
|
|
|
88
|
-
// Detect version mismatch: replayJournal quarantines and returns
|
|
89
|
-
// hadCorruptTail=true + 0 records when the header version is wrong.
|
|
90
|
-
// We distinguish it from a genuine corrupt tail by checking whether the
|
|
91
|
-
// journal file still exists (quarantine renames it away in both cases,
|
|
92
|
-
// so we cannot inspect the header at this point). We surface both cases
|
|
93
|
-
// through hadCorruptTail to the caller; hadVersionMismatch is derived
|
|
94
|
-
// from it to give the caller a distinct notice option.
|
|
95
|
-
const hadVersionMismatch = hadCorruptTail && records.length === 0;
|
|
96
|
-
|
|
97
87
|
const journal = openTranscriptJournal(journalPath, sessionId);
|
|
98
88
|
|
|
99
89
|
if (records.length === 0) {
|
|
100
90
|
// Nothing to replay — rotate the (now-stale) journal silently.
|
|
101
91
|
journal.rotate();
|
|
102
|
-
return { replayed: 0, hadCorruptTail
|
|
92
|
+
return { replayed: 0, hadCorruptTail };
|
|
103
93
|
}
|
|
104
94
|
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
95
|
+
// Select the authoritative record by newest wall-clock timestamp (tie-broken
|
|
96
|
+
// by highest seq). Each record carries the full conversation snapshot at its
|
|
97
|
+
// moment, so the newest ts is by definition the most recent state. This is
|
|
98
|
+
// resilient to seq collisions that arise when a fresh process appends to a
|
|
99
|
+
// pre-existing, non-rotated journal: its records restart at seq 0 after the
|
|
100
|
+
// surviving old records, so "last by seq" could otherwise pick a stale one.
|
|
101
|
+
const lastRecord = records.reduce((newest, candidate) =>
|
|
102
|
+
candidate.ts > newest.ts ||
|
|
103
|
+
(candidate.ts === newest.ts && candidate.seq > newest.seq)
|
|
104
|
+
? candidate
|
|
105
|
+
: newest,
|
|
106
|
+
);
|
|
108
107
|
const replayedMessages = lastRecord.messages as ConversationMessageSnapshot[];
|
|
109
108
|
|
|
109
|
+
// Preserve the session identity (title/titleSource/branches/currentBranch)
|
|
110
|
+
// that the resume seam already hydrated onto the live conversation. Journal
|
|
111
|
+
// records carry only messages, so a bare fromJSON would blank the title and
|
|
112
|
+
// reset titleSource to the system default — and the next TURN_COMPLETED
|
|
113
|
+
// snapshot would then persist the empty title, making the loss permanent.
|
|
114
|
+
const preserved = conversation.toJSON();
|
|
110
115
|
conversation.fromJSON({
|
|
116
|
+
...preserved,
|
|
111
117
|
messages: replayedMessages as never[],
|
|
118
|
+
title: conversation.title,
|
|
119
|
+
titleSource: conversation.getTitleSource(),
|
|
112
120
|
});
|
|
113
121
|
conversation.rebuildHistory();
|
|
114
122
|
|
|
@@ -123,10 +131,10 @@ export function replayJournalIntoConversation(
|
|
|
123
131
|
// Rotate the journal — it is no longer needed as a gap-filler.
|
|
124
132
|
journal.rotate();
|
|
125
133
|
|
|
126
|
-
return { replayed: records.length, hadCorruptTail
|
|
134
|
+
return { replayed: records.length, hadCorruptTail };
|
|
127
135
|
} catch {
|
|
128
136
|
// Absolute last-resort guard — recovery must never crash a resume.
|
|
129
|
-
return { replayed: 0, hadCorruptTail: false
|
|
137
|
+
return { replayed: 0, hadCorruptTail: false };
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SystemMessageRouter — routes system messages to the appropriate surfaces
|
|
3
|
-
* based on
|
|
3
|
+
* based on their kind and configured routing target.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* Delivery is resolved from the message KIND ('system' | 'wrfc' |
|
|
6
|
+
* 'operational'), which maps to a configurable routing target
|
|
7
|
+
* (ui.systemMessages / ui.wrfcMessages / ui.operationalMessages, each
|
|
8
|
+
* 'panel' | 'conversation' | 'both'). resolveSystemMessageDelivery() turns that
|
|
9
|
+
* target — plus whether a panel is attached — into a { toPanel, toConversation }
|
|
10
|
+
* decision. The priority ('high' | 'low') only sets the panel emphasis; it does
|
|
11
|
+
* not by itself decide conversation delivery.
|
|
12
|
+
*
|
|
13
|
+
* Critical override: errors, provider failovers, and compaction/context notices
|
|
14
|
+
* (see FORCE_CONVERSATION_PREFIXES) ALWAYS also reach the main conversation,
|
|
15
|
+
* regardless of target, so the user never has to open the SystemMessagesPanel
|
|
16
|
+
* to discover them.
|
|
13
17
|
*
|
|
14
18
|
* Usage:
|
|
15
19
|
* ```ts
|
|
@@ -18,9 +22,9 @@
|
|
|
18
22
|
* router.routeSystemMessage('[Session] Saved session abc123', 'high');
|
|
19
23
|
* ```
|
|
20
24
|
*
|
|
21
|
-
*
|
|
22
|
-
* conversation.addSystemMessage()
|
|
23
|
-
* automatically
|
|
25
|
+
* routeAuto() can be used as a drop-in replacement for
|
|
26
|
+
* conversation.addSystemMessage(): it classifies the message kind and priority
|
|
27
|
+
* automatically before routing.
|
|
24
28
|
*
|
|
25
29
|
* @remarks
|
|
26
30
|
* This router handles system messages (operational status, errors). It is
|
|
@@ -28,8 +32,6 @@
|
|
|
28
32
|
* notifications with policy-based routing.
|
|
29
33
|
*/
|
|
30
34
|
|
|
31
|
-
import { getConfigSnapshot } from '../config/index.ts';
|
|
32
|
-
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
|
|
33
35
|
import type { ConversationManager } from './conversation';
|
|
34
36
|
import type { SystemMessagesPanel, SystemMessagePriority } from '../panels/system-messages-panel.ts';
|
|
35
37
|
import {
|
|
@@ -46,14 +48,23 @@ export type {
|
|
|
46
48
|
SystemMessageTarget,
|
|
47
49
|
} from '@/runtime/index.ts';
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Message categories that are operationally critical enough that the user must
|
|
53
|
+
* see them inline in the main conversation, regardless of the configured
|
|
54
|
+
* routing target (ui.systemMessages defaults to panel-only). Errors, provider
|
|
55
|
+
* failovers, and compaction/context notices fall here: a user should never have
|
|
56
|
+
* to open the SystemMessagesPanel to discover that a turn errored, a provider
|
|
57
|
+
* failed over, or the context was compacted.
|
|
58
|
+
*
|
|
59
|
+
* Detection is by the stable message prefix tag, mirroring how the SDK
|
|
60
|
+
* classifiers key off message content. This deliberately does NOT force every
|
|
61
|
+
* high-priority message inline (model/provider/session confirmations still
|
|
62
|
+
* honour the configured target), so the routing-target config stays meaningful.
|
|
63
|
+
*/
|
|
64
|
+
const FORCE_CONVERSATION_PREFIXES = ['[Error]', '[Failover]', '[Compaction]', '[Context]'] as const;
|
|
65
|
+
|
|
66
|
+
function mustReachConversation(message: string): boolean {
|
|
67
|
+
return FORCE_CONVERSATION_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
// ---------------------------------------------------------------------------
|
|
@@ -74,10 +85,11 @@ export class SystemMessageRouter {
|
|
|
74
85
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
75
86
|
|
|
76
87
|
/**
|
|
77
|
-
* Route a system message
|
|
88
|
+
* Route a system message.
|
|
78
89
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
90
|
+
* Delivery is resolved from the kind and its configured target; priority only
|
|
91
|
+
* sets panel emphasis. Critical notices (errors/failover/compaction, see
|
|
92
|
+
* FORCE_CONVERSATION_PREFIXES) are additionally forced into the conversation.
|
|
81
93
|
*
|
|
82
94
|
* @param message - Message text.
|
|
83
95
|
* @param priority - 'high' | 'low'.
|
|
@@ -94,7 +106,11 @@ export class SystemMessageRouter {
|
|
|
94
106
|
if (delivery.toPanel) {
|
|
95
107
|
this.panel?.push(message, priority);
|
|
96
108
|
}
|
|
97
|
-
|
|
109
|
+
// Critical notices (errors, failover, compaction) must surface inline even
|
|
110
|
+
// when the configured target is panel-only — otherwise a user without the
|
|
111
|
+
// SystemMessagesPanel open would never see them.
|
|
112
|
+
const toConversation = delivery.toConversation || mustReachConversation(message);
|
|
113
|
+
if (toConversation) {
|
|
98
114
|
// addTypedSystemMessage threads the kind into the conversation so the
|
|
99
115
|
// renderer can use kind-based navigability instead of substring matching.
|
|
100
116
|
this.conversation.addTypedSystemMessage(message, kind);
|