@pellux/goodvibes-tui 1.1.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,678 +0,0 @@
1
- import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
2
- import { BasePanel } from './base-panel.ts';
3
- import type { Line } from '../types/grid.ts';
4
- import type { ModelDomainState, ProviderEvent, TurnEvent } from '@/runtime/index.ts';
5
- import { createInitialModelState, evaluateSessionMaintenance } from '@/runtime/index.ts';
6
- import type { UiEventFeed } from '../runtime/ui-events.ts';
7
- import {
8
- type ProviderRuntimeInspectionQuery,
9
- } from '../runtime/ui-service-queries.ts';
10
- import {
11
- ProviderHealthTracker,
12
- type ProviderHealthMeta,
13
- } from './provider-health-tracker.ts';
14
- import {
15
- buildAccountPosture,
16
- type ProviderAccountPosture,
17
- } from './provider-health-routes.ts';
18
- import {
19
- buildProviderHealthDomainSummaries,
20
- type HealthDomainSummary,
21
- } from './provider-health-domains.ts';
22
- import {
23
- ProviderHealthDataProvider,
24
- type FallbackChainData,
25
- type ProviderHealthEntry,
26
- } from '../runtime/ui/provider-health/index.ts';
27
- import type { PanelIntegrationContext } from './types.ts';
28
- import type {
29
- UiContinuitySnapshot,
30
- UiIntelligenceSnapshot,
31
- UiLocalAuthSnapshot,
32
- UiProvidersSnapshot,
33
- UiReadModel,
34
- UiRemoteSnapshot,
35
- UiSecuritySnapshot,
36
- UiSessionSnapshot,
37
- UiSettingsSnapshot,
38
- UiWorktreeSnapshot,
39
- } from '../runtime/ui-read-models.ts';
40
- import {
41
- buildEmptyState,
42
- buildKeyboardHints,
43
- buildPanelWorkspace,
44
- buildSummaryBlock,
45
- DEFAULT_PANEL_PALETTE,
46
- extendPalette,
47
- resolvePrimaryScrollableSection,
48
- type PanelWorkspaceSection,
49
- } from './polish.ts';
50
- import {
51
- buildChainLines,
52
- buildDomainLines,
53
- buildMaintenanceLines,
54
- buildPostureLines,
55
- buildProviderColumnHeader,
56
- buildProviderRow,
57
- buildRouteColumnHeader,
58
- buildRouteViewLines,
59
- buildSelectedDetailSection,
60
- } from './provider-health-views.ts';
61
-
62
- // ---------------------------------------------------------------------------
63
- // Types
64
- // ---------------------------------------------------------------------------
65
-
66
- export interface ProviderHealthPanelDeps {
67
- readonly configManager: Pick<ConfigManager, 'get'>;
68
- readonly turnEvents: UiEventFeed<TurnEvent>;
69
- readonly providerEvents: UiEventFeed<ProviderEvent>;
70
- readonly providers: UiReadModel<UiProvidersSnapshot>;
71
- readonly session: UiReadModel<UiSessionSnapshot>;
72
- readonly security: UiReadModel<UiSecuritySnapshot>;
73
- readonly localAuth: UiReadModel<UiLocalAuthSnapshot>;
74
- readonly settings: UiReadModel<UiSettingsSnapshot>;
75
- readonly remote: UiReadModel<UiRemoteSnapshot>;
76
- readonly intelligence: UiReadModel<UiIntelligenceSnapshot>;
77
- readonly continuity: UiReadModel<UiContinuitySnapshot>;
78
- readonly worktrees: UiReadModel<UiWorktreeSnapshot>;
79
- /**
80
- * Model-domain access wired from the runtime store at the composition root
81
- * (selectModel over RuntimeStore). Drives fallback-chain rendering and
82
- * TURN_ERROR attribution to the active provider.
83
- */
84
- readonly modelState?: {
85
- readonly get: () => ModelDomainState;
86
- readonly subscribe: (listener: () => void) => () => void;
87
- };
88
- }
89
-
90
- type ConsoleView = 'providers' | 'routes' | 'domains';
91
-
92
- const CONSOLE_VIEWS: readonly ConsoleView[] = ['providers', 'routes', 'domains'] as const;
93
-
94
- // Base chrome plus console accents (domain accents only — hex ratchet).
95
- const C = extendPalette(DEFAULT_PANEL_PALETTE, {
96
- title: '#00ffff',
97
- unknown: '244',
98
- rowSelectBg: '#111827',
99
- });
100
-
101
- const INTRO = 'Provider console: status, latency timelines, error attribution, auth routes, and fallback posture.';
102
-
103
- // ---------------------------------------------------------------------------
104
- // ProviderHealthPanel — the merged provider console (WO-112)
105
- // ---------------------------------------------------------------------------
106
-
107
- /**
108
- * Single provider console. Absorbs the retired providers (stats) and accounts
109
- * panels:
110
- * - Status, latency avg/p95, error attribution, sparkline timelines, and
111
- * per-provider token/cost totals in one table (ProviderHealthDataProvider).
112
- * - Fallback chain with current node and fallover count.
113
- * - Auth routes with per-route freshness, issues, and repair hints
114
- * (ProviderRuntimeInspectionQuery.inspectAll() is the single route source).
115
- * - Repair Domains + Session Maintenance as a secondary 't' view.
116
- * - Enter dispatches '/accounts repair <provider>' through the panel
117
- * integration context; r forces a posture refresh.
118
- */
119
- export class ProviderHealthPanel extends BasePanel {
120
- private _unsubs: Array<() => void> = [];
121
- private _tickTimerId: ReturnType<typeof setInterval> | null = null;
122
- private _selectedIndex = 0;
123
- private _scrollOffset = 0;
124
- private _view: ConsoleView = 'providers';
125
- private _accountRecords = new Map<string, ProviderAccountPosture>();
126
- private _accountRefreshAt = 0;
127
- private _accountLoading = false;
128
- private _inflightProvider: string | null = null;
129
- private _lastResponseProvider: string | null = null;
130
- private _unattributedError: string | null = null;
131
- private _modelState: ModelDomainState;
132
- private readonly _dataProvider: ProviderHealthDataProvider;
133
- private readonly providerHealthTracker = new ProviderHealthTracker();
134
-
135
- constructor(
136
- private readonly providerRuntime: ProviderRuntimeInspectionQuery,
137
- private readonly deps: ProviderHealthPanelDeps,
138
- private readonly requestRender: () => void = () => {},
139
- ) {
140
- super('provider-health', 'Health', 'N', 'providers');
141
- this._modelState = deps.modelState?.get() ?? this._syntheticModelState();
142
- this._dataProvider = new ProviderHealthDataProvider(this._buildHealthState(), this._modelState);
143
- this._subscribe();
144
- void this._refreshAccountPosture(true);
145
- // Background 30s posture refresh (auth routes go stale otherwise); cleared on destroy.
146
- this.registerTimer(setInterval(() => { void this._refreshAccountPosture(); }, 30_000));
147
- }
148
-
149
- // -------------------------------------------------------------------------
150
- // Event subscription
151
- // -------------------------------------------------------------------------
152
-
153
- private _subscribe(): void {
154
- this._unsubs.push(
155
- this.deps.turnEvents.on('TURN_SUBMITTED', () => {
156
- this.providerHealthTracker.onTurnStart();
157
- }),
158
- );
159
-
160
- this._unsubs.push(
161
- this.deps.turnEvents.on('STREAM_START', () => {
162
- this.providerHealthTracker.onStreamStart();
163
- }),
164
- );
165
-
166
- // The provider named on the in-flight LLM request is the authoritative
167
- // attribution target when the turn subsequently errors.
168
- this._unsubs.push(
169
- this.deps.turnEvents.on('LLM_REQUEST_STARTED', (payload) => {
170
- this._inflightProvider = payload.provider;
171
- }),
172
- );
173
-
174
- this._unsubs.push(
175
- this.deps.turnEvents.on('LLM_RESPONSE_RECEIVED', (payload) => {
176
- this.providerHealthTracker.onLlmResponse(payload.provider, {
177
- model: payload.model,
178
- inputTokens: payload.inputTokens,
179
- outputTokens: payload.outputTokens,
180
- ...(payload.cacheReadTokens !== undefined ? { cacheReadTokens: payload.cacheReadTokens } : {}),
181
- ...(payload.cacheWriteTokens !== undefined ? { cacheWriteTokens: payload.cacheWriteTokens } : {}),
182
- });
183
- this._lastResponseProvider = payload.provider;
184
- this._pushHealthState();
185
- this._markDirtyAndRender();
186
- }),
187
- );
188
-
189
- this._unsubs.push(
190
- this.deps.turnEvents.on('TURN_ERROR', (payload) => {
191
- const providerId = this._resolveActiveProviderId();
192
- if (providerId) {
193
- this.providerHealthTracker.onTurnError(payload.error, providerId);
194
- this._unattributedError = null;
195
- } else {
196
- // No provider is resolvable (nothing registered yet) — surface the
197
- // error in the posture block instead of inventing a phantom row.
198
- this._unattributedError = payload.error.slice(0, 120);
199
- }
200
- this._inflightProvider = null;
201
- this._pushHealthState();
202
- this._markDirtyAndRender();
203
- }),
204
- );
205
-
206
- this._unsubs.push(
207
- this.deps.turnEvents.on('TURN_COMPLETED', () => {
208
- this._inflightProvider = null;
209
- }),
210
- );
211
-
212
- this._unsubs.push(
213
- this.deps.turnEvents.on('TURN_CANCEL', () => {
214
- this._inflightProvider = null;
215
- }),
216
- );
217
-
218
- this._unsubs.push(
219
- this.deps.providerEvents.on('PROVIDERS_CHANGED', () => {
220
- this.providerHealthTracker.onProvidersChanged(this._knownProviders());
221
- void this._refreshAccountPosture(true);
222
- this._pushHealthState();
223
- this._markDirtyAndRender();
224
- }),
225
- );
226
-
227
- if (this.deps.modelState) {
228
- const modelState = this.deps.modelState;
229
- this._unsubs.push(modelState.subscribe(() => {
230
- const next = modelState.get();
231
- if (next !== this._modelState) {
232
- this._modelState = next;
233
- this._dataProvider.updateModelState(next);
234
- this._markDirtyAndRender();
235
- }
236
- }));
237
- }
238
-
239
- for (const readModel of [
240
- this.deps.providers,
241
- this.deps.session,
242
- this.deps.security,
243
- this.deps.localAuth,
244
- this.deps.settings,
245
- this.deps.remote,
246
- this.deps.intelligence,
247
- this.deps.continuity,
248
- this.deps.worktrees,
249
- ] as const) {
250
- this._unsubs.push(readModel.subscribe(() => this._markDirtyAndRender()));
251
- }
252
- }
253
-
254
- private _markDirtyAndRender(): void {
255
- this.markDirty();
256
- this.requestRender();
257
- }
258
-
259
- // -------------------------------------------------------------------------
260
- // Lifecycle
261
- // -------------------------------------------------------------------------
262
-
263
- override onActivate(): void {
264
- super.onActivate();
265
- this.markDirty();
266
- void this._refreshAccountPosture(true);
267
- this._startTickTimer();
268
- }
269
-
270
- override onDeactivate(): void {
271
- super.onDeactivate();
272
- // Stop the per-second display tick while hidden (TokenBudgetPanel pattern);
273
- // the 30s posture refresh keeps running so data stays warm for preload.
274
- this._stopTickTimer();
275
- }
276
-
277
- override onDestroy(): void {
278
- super.onDestroy();
279
- this._tickTimerId = null;
280
- this._dataProvider.dispose();
281
- for (const unsub of this._unsubs) unsub();
282
- this._unsubs = [];
283
- }
284
-
285
- private _startTickTimer(): void {
286
- if (this._tickTimerId !== null) return;
287
- this._tickTimerId = this.registerTimer(setInterval(() => {
288
- this.markDirty();
289
- this.requestRender();
290
- }, 1_000));
291
- }
292
-
293
- private _stopTickTimer(): void {
294
- if (this._tickTimerId !== null) {
295
- this.clearTimer(this._tickTimerId);
296
- this._tickTimerId = null;
297
- }
298
- }
299
-
300
- // -------------------------------------------------------------------------
301
- // Input
302
- // -------------------------------------------------------------------------
303
-
304
- handleInput(key: string): boolean {
305
- if (key === 't' || key === 'tab') {
306
- const next = CONSOLE_VIEWS[(CONSOLE_VIEWS.indexOf(this._view) + 1) % CONSOLE_VIEWS.length];
307
- this._view = next ?? 'providers';
308
- this._scrollOffset = 0;
309
- this.markDirty();
310
- return true;
311
- }
312
- if (key === 'r') {
313
- void this._refreshAccountPosture(true);
314
- this.markDirty();
315
- return true;
316
- }
317
- const providers = this._knownProviders();
318
- if (providers.length === 0) return false;
319
- if (key === 'j' || key === 'down' || key === '\x1b[B') {
320
- this._selectedIndex = Math.min(providers.length - 1, this._selectedIndex + 1);
321
- this.markDirty();
322
- return true;
323
- }
324
- if (key === 'k' || key === 'up' || key === '\x1b[A') {
325
- this._selectedIndex = Math.max(0, this._selectedIndex - 1);
326
- this.markDirty();
327
- return true;
328
- }
329
- return false;
330
- }
331
-
332
- /** Enter dispatches the real repair command for the selected provider. */
333
- handlePanelIntegrationAction(key: string, ctx: PanelIntegrationContext): boolean {
334
- if (key !== 'enter' && key !== 'return') return false;
335
- if (!ctx.executeCommand) return false;
336
- const provider = this._selectedProvider();
337
- if (!provider) return false;
338
- void ctx.executeCommand('accounts', ['repair', provider]).catch(() => {
339
- // Command output surfaces in conversation; dispatch failures are non-fatal here.
340
- });
341
- return true;
342
- }
343
-
344
- // -------------------------------------------------------------------------
345
- // Data plumbing
346
- // -------------------------------------------------------------------------
347
-
348
- private _knownProviders(): string[] {
349
- const known = new Set<string>([
350
- ...this.deps.providers.getSnapshot().providerIds,
351
- ...this.providerRuntime.listProviderIds(),
352
- ...this._accountRecords.keys(),
353
- ]);
354
- for (const health of this.providerHealthTracker.getAll()) known.add(health.name);
355
- return [...known].sort();
356
- }
357
-
358
- private _selectedProvider(): string | undefined {
359
- const providers = this._knownProviders();
360
- return providers[Math.min(this._selectedIndex, Math.max(0, providers.length - 1))];
361
- }
362
-
363
- /**
364
- * Resolve the provider that should own a TURN_ERROR: the in-flight LLM
365
- * request's provider, then the store model domain, then the configured
366
- * provider.model, then the last responding provider, then the sole known
367
- * provider. Never returns 'unknown'.
368
- */
369
- private _resolveActiveProviderId(): string | undefined {
370
- if (this._inflightProvider) return this._inflightProvider;
371
- if (this.deps.modelState) {
372
- const storeProvider = this.deps.modelState.get().activeProviderId;
373
- if (storeProvider && storeProvider !== 'unknown') return storeProvider;
374
- }
375
- const raw = this.deps.configManager.get('provider.model');
376
- if (typeof raw === 'string' && raw.includes(':')) {
377
- const providerId = raw.split(':')[0];
378
- if (providerId) return providerId;
379
- }
380
- if (this._lastResponseProvider) return this._lastResponseProvider;
381
- const known = this._knownProviders();
382
- if (known.length === 1) return known[0];
383
- return undefined;
384
- }
385
-
386
- private _syntheticModelState(): ModelDomainState {
387
- const base = createInitialModelState();
388
- const raw = this.deps.configManager.get('provider.model');
389
- if (typeof raw !== 'string' || !raw.includes(':')) return base;
390
- const [providerId = '', ...rest] = raw.split(':');
391
- const modelId = rest.join(':');
392
- if (!providerId || !modelId) return base;
393
- return {
394
- ...base,
395
- activeProviderId: providerId,
396
- activeModelId: modelId,
397
- displayName: raw,
398
- registryKey: raw,
399
- source: 'provider-health-panel',
400
- lastUpdatedAt: Date.now(),
401
- };
402
- }
403
-
404
- private _buildHealthState() {
405
- const activeId = this._resolveActiveProviderId();
406
- const meta: ProviderHealthMeta[] = this._knownProviders().map((providerId) => {
407
- const account = this._accountRecords.get(providerId);
408
- return {
409
- providerId,
410
- isActive: providerId === activeId,
411
- isConfigured: account ? account.activeRoute !== 'unconfigured' : true,
412
- };
413
- });
414
- return this.providerHealthTracker.buildHealthDomainState(meta);
415
- }
416
-
417
- private _pushHealthState(): void {
418
- this._dataProvider.updateHealthState(this._buildHealthState());
419
- }
420
-
421
- private async _refreshAccountPosture(force = false): Promise<void> {
422
- if (this._accountLoading) return;
423
- if (!force && Date.now() - this._accountRefreshAt < 15_000) return;
424
- this._accountLoading = true;
425
- try {
426
- const snapshots = await this.providerRuntime.inspectAll();
427
- this._accountRecords = new Map(
428
- snapshots
429
- .map((snapshot) => buildAccountPosture(snapshot))
430
- .map((record) => [record.providerId, record] as const),
431
- );
432
- this._accountRefreshAt = Date.now();
433
- this._pushHealthState();
434
- this.markDirty();
435
- this.requestRender();
436
- } finally {
437
- this._accountLoading = false;
438
- }
439
- }
440
-
441
- private _collectDomainSummaries(): HealthDomainSummary[] {
442
- return [...buildProviderHealthDomainSummaries({
443
- configManager: this.deps.configManager,
444
- auth: this.deps.localAuth.getSnapshot(),
445
- settings: this.deps.settings.getSnapshot(),
446
- remote: this.deps.remote.getSnapshot(),
447
- security: this.deps.security.getSnapshot(),
448
- intelligence: this.deps.intelligence.getSnapshot(),
449
- continuity: this.deps.continuity.getSnapshot(),
450
- worktrees: this.deps.worktrees.getSnapshot(),
451
- session: this.deps.session.getSnapshot(),
452
- })];
453
- }
454
-
455
- private _evaluateMaintenance() {
456
- const session = this.deps.session.getSnapshot();
457
- return evaluateSessionMaintenance({
458
- configManager: this.deps.configManager,
459
- currentTokens: session.estimatedContextTokens,
460
- contextWindow: session.contextWindow,
461
- messageCount: session.messageCount,
462
- session: session.session,
463
- });
464
- }
465
-
466
- // -------------------------------------------------------------------------
467
- // Rendering
468
- // -------------------------------------------------------------------------
469
-
470
- override render(width: number, height: number): Line[] {
471
- const palette = { ...DEFAULT_PANEL_PALETTE, header: C.title };
472
-
473
- const providers = this._knownProviders();
474
- this._selectedIndex = Math.min(this._selectedIndex, Math.max(0, providers.length - 1));
475
-
476
- if (providers.length === 0) {
477
- return buildPanelWorkspace(width, height, {
478
- title: 'Health',
479
- intro: INTRO,
480
- sections: [{
481
- lines: buildEmptyState(
482
- width,
483
- ' No providers registered.',
484
- 'Provider health appears here once model providers are available and the runtime begins making requests.',
485
- [
486
- { command: '/provider', summary: 'review current provider and model selection' },
487
- { command: '/subscription', summary: 'review provider login and subscription state' },
488
- ],
489
- palette,
490
- ),
491
- }],
492
- palette,
493
- });
494
- }
495
-
496
- const snapshot = this._dataProvider.getSnapshot();
497
- const entriesById = new Map(snapshot.entries.map((entry) => [entry.providerId, entry] as const));
498
- const selectedName = providers[this._selectedIndex];
499
-
500
- const footerHint = buildKeyboardHints(width, [
501
- { keys: 'j/k', label: 'select provider' },
502
- { keys: 'enter', label: 'repair auth routes' },
503
- { keys: 'r', label: 'refresh posture' },
504
- { keys: 't', label: `view: ${this._view}` },
505
- { keys: '/provider', label: 'switch model' },
506
- ], palette);
507
-
508
- const collapsedDomains = this._view !== 'domains'
509
- ? {
510
- attention: this._collectDomainSummaries().filter((summary) => summary.level === 'warn' || summary.level === 'bad').length,
511
- maintenanceLevel: this._evaluateMaintenance().level,
512
- }
513
- : undefined;
514
-
515
- const postureSection: PanelWorkspaceSection = {
516
- lines: buildSummaryBlock(width, 'Provider console posture', buildPostureLines(width, C, palette, {
517
- providers,
518
- entriesById,
519
- accounts: this._accountRecords,
520
- trackerRecords: this.providerHealthTracker.getAll(),
521
- compositeStatus: snapshot.compositeStatus,
522
- falloverCount: snapshot.fallbackChain.falloverCount,
523
- activeProvider: this._resolveActiveProviderId(),
524
- ...(collapsedDomains ? { collapsedDomains } : {}),
525
- unattributedError: this._unattributedError,
526
- }), palette),
527
- };
528
-
529
- if (this._view === 'domains') {
530
- return this._renderDomainsView(width, height, postureSection, footerHint, palette);
531
- }
532
- if (this._view === 'routes') {
533
- return this._renderRoutesView(width, height, postureSection, footerHint, palette, selectedName);
534
- }
535
- return this._renderProvidersView(width, height, postureSection, footerHint, palette, providers, entriesById, snapshot.fallbackChain, selectedName);
536
- }
537
-
538
- private _renderProvidersView(
539
- width: number,
540
- height: number,
541
- postureSection: PanelWorkspaceSection,
542
- footerHint: Line,
543
- palette: typeof DEFAULT_PANEL_PALETTE,
544
- providers: readonly string[],
545
- entriesById: ReadonlyMap<string, ProviderHealthEntry>,
546
- chain: FallbackChainData,
547
- selectedName: string | undefined,
548
- ): Line[] {
549
- const chainLines = buildChainLines(width, C, palette, chain);
550
- const chainSections: PanelWorkspaceSection[] = chainLines.length > 0
551
- ? [{ title: 'Fallback Chain', lines: chainLines }]
552
- : [];
553
-
554
- const rows = providers.map((name, absolute) => buildProviderRow(width, C, {
555
- name,
556
- entry: entriesById.get(name),
557
- health: this.providerHealthTracker.get(name),
558
- account: this._accountRecords.get(name),
559
- selected: absolute === this._selectedIndex,
560
- }));
561
-
562
- const selectedSections: PanelWorkspaceSection[] = selectedName
563
- ? [buildSelectedDetailSection(width, C, palette, {
564
- selectedName,
565
- entry: entriesById.get(selectedName),
566
- health: this.providerHealthTracker.get(selectedName),
567
- account: this._accountRecords.get(selectedName),
568
- })]
569
- : [];
570
-
571
- const resolved = resolvePrimaryScrollableSection(width, height, {
572
- intro: INTRO,
573
- footerLines: [footerHint],
574
- palette,
575
- beforeSections: [postureSection, ...chainSections],
576
- section: {
577
- title: 'Providers',
578
- fixedLines: [buildProviderColumnHeader(width, C)],
579
- scrollableLines: rows,
580
- selectedIndex: this._selectedIndex,
581
- scrollOffset: this._scrollOffset,
582
- guardRows: 1,
583
- minRows: 4,
584
- appendWindowSummary: { dimColor: C.dim },
585
- },
586
- afterSections: selectedSections,
587
- });
588
- this._scrollOffset = resolved.scrollOffset;
589
-
590
- return buildPanelWorkspace(width, height, {
591
- title: 'Health',
592
- intro: INTRO,
593
- sections: [postureSection, ...chainSections, resolved.section, ...selectedSections],
594
- footerLines: [footerHint],
595
- palette,
596
- });
597
- }
598
-
599
- private _renderRoutesView(
600
- width: number,
601
- height: number,
602
- postureSection: PanelWorkspaceSection,
603
- footerHint: Line,
604
- palette: typeof DEFAULT_PANEL_PALETTE,
605
- selectedName: string | undefined,
606
- ): Line[] {
607
- const account = selectedName ? this._accountRecords.get(selectedName) : undefined;
608
- const routeLines = buildRouteViewLines(width, C, palette, account);
609
-
610
- const resolved = resolvePrimaryScrollableSection(width, height, {
611
- intro: INTRO,
612
- footerLines: [footerHint],
613
- palette,
614
- beforeSections: [postureSection],
615
- section: {
616
- title: `Auth Routes — ${selectedName ?? 'n/a'}`,
617
- fixedLines: [buildRouteColumnHeader(width, C)],
618
- scrollableLines: routeLines,
619
- selectedIndex: 0,
620
- scrollOffset: this._scrollOffset,
621
- guardRows: 1,
622
- minRows: 4,
623
- appendWindowSummary: { dimColor: C.dim },
624
- },
625
- afterSections: [],
626
- });
627
- this._scrollOffset = resolved.scrollOffset;
628
-
629
- return buildPanelWorkspace(width, height, {
630
- title: 'Health',
631
- intro: INTRO,
632
- sections: [postureSection, resolved.section],
633
- footerLines: [footerHint],
634
- palette,
635
- });
636
- }
637
-
638
- private _renderDomainsView(
639
- width: number,
640
- height: number,
641
- postureSection: PanelWorkspaceSection,
642
- footerHint: Line,
643
- palette: typeof DEFAULT_PANEL_PALETTE,
644
- ): Line[] {
645
- const domainLines = buildDomainLines(width, C, palette, this._collectDomainSummaries());
646
- const maintenanceSection: PanelWorkspaceSection = {
647
- title: 'Session Maintenance',
648
- lines: buildMaintenanceLines(width, C, palette, this._evaluateMaintenance()),
649
- };
650
-
651
- const resolved = resolvePrimaryScrollableSection(width, height, {
652
- intro: INTRO,
653
- footerLines: [footerHint],
654
- palette,
655
- beforeSections: [postureSection],
656
- section: {
657
- title: 'Repair Domains',
658
- fixedLines: [],
659
- scrollableLines: domainLines,
660
- selectedIndex: 0,
661
- scrollOffset: this._scrollOffset,
662
- guardRows: 1,
663
- minRows: 4,
664
- appendWindowSummary: { dimColor: C.dim },
665
- },
666
- afterSections: [maintenanceSection],
667
- });
668
- this._scrollOffset = resolved.scrollOffset;
669
-
670
- return buildPanelWorkspace(width, height, {
671
- title: 'Health',
672
- intro: INTRO,
673
- sections: [postureSection, resolved.section, maintenanceSection],
674
- footerLines: [footerHint],
675
- palette,
676
- });
677
- }
678
- }