@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,310 +0,0 @@
1
- // ProviderStatus is the shared SDK type — imported from the runtime barrel
2
- // to eliminate the duplicate local definition that diverged from the SDK shape.
3
- import type {
4
- ProviderHealthDomainState,
5
- ProviderHealthRecord,
6
- ProviderStatus,
7
- } from '@/runtime/index.ts';
8
- import { calcSessionCost, isModelPriced } from '../export/cost-utils.ts';
9
-
10
- export type { ProviderStatus };
11
-
12
- /** Token/cost usage delta carried by one LLM_RESPONSE_RECEIVED event. */
13
- export interface ProviderUsageDelta {
14
- readonly model?: string;
15
- readonly inputTokens?: number;
16
- readonly outputTokens?: number;
17
- readonly cacheReadTokens?: number;
18
- readonly cacheWriteTokens?: number;
19
- }
20
-
21
- export interface ProviderHealth {
22
- name: string;
23
- status: ProviderStatus;
24
- lastLatencyMs?: number;
25
- lastErrorMessage?: string;
26
- lastSuccessAt?: number;
27
- lastErrorAt?: number;
28
- rateLimitExpiresAt: number;
29
- /** Most recent model id seen for this provider (from LLM responses). */
30
- lastModelId?: string;
31
- /** Session request counter (successes + errors). */
32
- requests: number;
33
- /** Session error counter. */
34
- errors: number;
35
- /** Session token counters (absorbed from the retired provider-stats panel). */
36
- inputTokens: number;
37
- outputTokens: number;
38
- cacheReadTokens: number;
39
- cacheWriteTokens: number;
40
- totalTokens: number;
41
- /** Session USD cost accumulated per call via calcSessionCost. */
42
- totalCostUsd: number;
43
- /** True when at least one call's model resolved to no real price (cost-utils isModelPriced === false) — totalCostUsd may understate the true cost. */
44
- hasUnpricedModel: boolean;
45
- /** Ring buffer of recent request latencies in ms (most-recent last). */
46
- latencies: number[];
47
- }
48
-
49
- /** Per-provider identity/config metadata used to build SDK domain state. */
50
- export interface ProviderHealthMeta {
51
- readonly providerId: string;
52
- readonly displayName?: string;
53
- readonly isActive: boolean;
54
- readonly isConfigured: boolean;
55
- }
56
-
57
- const LATENCY_RING_SIZE = 20;
58
-
59
- function avg(values: readonly number[]): number {
60
- if (values.length === 0) return 0;
61
- return values.reduce((sum, value) => sum + value, 0) / values.length;
62
- }
63
-
64
- /**
65
- * Tracks provider request posture from shell-facing turn and provider events.
66
- * The panel owns event subscriptions and feeds those events into this tracker.
67
- *
68
- * WO-112: the tracker also accumulates the session metrics the retired
69
- * provider-stats panel used to duplicate (latency ring, request/error/token
70
- * counters, per-call cost) and can project its records into the SDK
71
- * ProviderHealthDomainState shape so the orphaned ProviderHealthDataProvider
72
- * (60-point timelines, success/error rates, cache metrics) is the single
73
- * derivation engine for the provider console.
74
- */
75
- export class ProviderHealthTracker {
76
- private records = new Map<string, ProviderHealth>();
77
- private streamStartMs: number | null = null;
78
- private turnStartMs: number | null = null;
79
- private revision = 0;
80
-
81
- private static readonly DEFAULT_COOLDOWN_MS = 60_000;
82
-
83
- onTurnStart(): void {
84
- this.turnStartMs = Date.now();
85
- }
86
-
87
- onStreamStart(): void {
88
- this.streamStartMs = Date.now();
89
- }
90
-
91
- onLlmResponse(providerName: string, usage?: ProviderUsageDelta): void {
92
- const now = Date.now();
93
- const latencyMs =
94
- this.streamStartMs !== null
95
- ? now - this.streamStartMs
96
- : this.turnStartMs !== null
97
- ? now - this.turnStartMs
98
- : undefined;
99
- this.streamStartMs = null;
100
-
101
- this.recordSuccess(providerName, latencyMs, usage);
102
- }
103
-
104
- /**
105
- * Record a turn error against a concrete provider. The panel resolves the
106
- * active provider (in-flight request, model domain, config, last response)
107
- * before calling this — the tracker never invents an 'unknown' row.
108
- */
109
- onTurnError(error: string, providerName: string): void {
110
- this.streamStartMs = null;
111
- this.turnStartMs = null;
112
- const isRateLimit = this.isRateLimitMessage(error);
113
-
114
- this.recordError(providerName, error, isRateLimit);
115
- }
116
-
117
- onProvidersChanged(providerIds: readonly string[]): void {
118
- try {
119
- for (const providerId of providerIds) {
120
- if (!this.records.has(providerId)) {
121
- this.ensureRecord(providerId);
122
- }
123
- }
124
- } catch {
125
- // Ignore provider catalog churn while the shell is refreshing.
126
- }
127
- }
128
-
129
- getAll(): ProviderHealth[] {
130
- return [...this.records.values()];
131
- }
132
-
133
- get(name: string): ProviderHealth | undefined {
134
- return this.records.get(name);
135
- }
136
-
137
- /**
138
- * Project tracked records into the SDK ProviderHealthDomainState shape so
139
- * ProviderHealthDataProvider can derive timelines, rates, and sort order.
140
- * Providers present in `meta` but never seen by the tracker are emitted as
141
- * zero-stat 'unknown' records; tracked providers missing from `meta` are
142
- * kept so attribution/table rows never silently disappear.
143
- */
144
- buildHealthDomainState(meta: readonly ProviderHealthMeta[]): ProviderHealthDomainState {
145
- const providers = new Map<string, ProviderHealthRecord>();
146
- const metaById = new Map(meta.map((entry) => [entry.providerId, entry] as const));
147
- const ids = new Set<string>([...metaById.keys(), ...this.records.keys()]);
148
-
149
- let degradedCount = 0;
150
- let unavailableCount = 0;
151
- let healthySeen = 0;
152
-
153
- for (const id of ids) {
154
- const record = this.records.get(id);
155
- const entry = metaById.get(id);
156
- const latencies = record?.latencies ?? [];
157
- const requests = record?.requests ?? 0;
158
- const errors = record?.errors ?? 0;
159
- const status: ProviderStatus = record?.status ?? 'unknown';
160
-
161
- if (status === 'degraded' || status === 'rate_limited' || status === 'auth_error') degradedCount++;
162
- if (status === 'unavailable') unavailableCount++;
163
- if (status === 'healthy') healthySeen++;
164
-
165
- const cacheRead = record?.cacheReadTokens ?? 0;
166
- const cacheWrite = record?.cacheWriteTokens ?? 0;
167
- const promptTokens = record?.inputTokens ?? 0;
168
-
169
- providers.set(id, {
170
- providerId: id,
171
- displayName: entry?.displayName ?? id,
172
- status,
173
- isActive: entry?.isActive ?? false,
174
- isConfigured: entry?.isConfigured ?? true,
175
- stats: {
176
- totalCalls: requests,
177
- successCalls: Math.max(0, requests - errors),
178
- errorCalls: errors,
179
- avgLatencyMs: Math.round(avg(latencies)),
180
- minLatencyMs: latencies.length > 0 ? Math.min(...latencies) : 0,
181
- maxLatencyMs: latencies.length > 0 ? Math.max(...latencies) : 0,
182
- lastSuccessAt: record?.lastSuccessAt,
183
- lastErrorAt: record?.lastErrorAt,
184
- lastErrorMessage: record?.lastErrorMessage,
185
- },
186
- ...(cacheRead + cacheWrite > 0
187
- ? {
188
- cacheMetrics: {
189
- cacheReadTokens: cacheRead,
190
- cacheWriteTokens: cacheWrite,
191
- hitRate: promptTokens + cacheRead > 0 ? cacheRead / (promptTokens + cacheRead) : 0,
192
- },
193
- }
194
- : {}),
195
- lastCheckedAt: Date.now(),
196
- ...(record && record.rateLimitExpiresAt > Date.now()
197
- ? { rateLimitResetAt: record.rateLimitExpiresAt }
198
- : {}),
199
- });
200
- }
201
-
202
- const compositeStatus = unavailableCount > 0
203
- ? 'critical'
204
- : degradedCount > 0
205
- ? 'degraded'
206
- : healthySeen > 0 && healthySeen === ids.size
207
- ? 'healthy'
208
- : ids.size === 0
209
- ? 'unknown'
210
- : healthySeen > 0
211
- ? 'healthy'
212
- : 'unknown';
213
-
214
- this.revision += 1;
215
- return {
216
- revision: this.revision,
217
- lastUpdatedAt: Date.now(),
218
- source: 'provider-health-panel',
219
- providers,
220
- compositeStatus,
221
- degradedCount,
222
- unavailableCount,
223
- warnings: [],
224
- };
225
- }
226
-
227
- private ensureRecord(name: string): ProviderHealth {
228
- let record = this.records.get(name);
229
- if (!record) {
230
- record = {
231
- name,
232
- status: 'unknown',
233
- rateLimitExpiresAt: 0,
234
- requests: 0,
235
- errors: 0,
236
- inputTokens: 0,
237
- outputTokens: 0,
238
- cacheReadTokens: 0,
239
- cacheWriteTokens: 0,
240
- totalTokens: 0,
241
- totalCostUsd: 0,
242
- hasUnpricedModel: false,
243
- latencies: [],
244
- };
245
- this.records.set(name, record);
246
- }
247
- return record;
248
- }
249
-
250
- private applyUsage(record: ProviderHealth, usage: ProviderUsageDelta | undefined): void {
251
- if (!usage) return;
252
- const input = usage.inputTokens ?? 0;
253
- const output = usage.outputTokens ?? 0;
254
- const cacheRead = usage.cacheReadTokens ?? 0;
255
- const cacheWrite = usage.cacheWriteTokens ?? 0;
256
- record.inputTokens += input;
257
- record.outputTokens += output;
258
- record.cacheReadTokens += cacheRead;
259
- record.cacheWriteTokens += cacheWrite;
260
- record.totalTokens += input + output + cacheRead + cacheWrite;
261
- if (usage.model) record.lastModelId = usage.model;
262
- const model = usage.model ?? record.lastModelId;
263
- if (model) {
264
- record.totalCostUsd += calcSessionCost(input, output, cacheRead, cacheWrite, model);
265
- if (!isModelPriced(model)) record.hasUnpricedModel = true;
266
- }
267
- }
268
-
269
- private recordSuccess(name: string, latencyMs: number | undefined, usage?: ProviderUsageDelta): void {
270
- const record = this.ensureRecord(name);
271
- record.status = 'healthy';
272
- record.lastSuccessAt = Date.now();
273
- record.lastErrorMessage = undefined;
274
- record.requests += 1;
275
- this.applyUsage(record, usage);
276
- if (latencyMs !== undefined) {
277
- record.lastLatencyMs = latencyMs;
278
- if (latencyMs > 0) {
279
- record.latencies.push(latencyMs);
280
- if (record.latencies.length > LATENCY_RING_SIZE) record.latencies.shift();
281
- }
282
- }
283
- if (record.rateLimitExpiresAt > 0 && record.rateLimitExpiresAt <= Date.now()) {
284
- record.rateLimitExpiresAt = 0;
285
- }
286
- }
287
-
288
- private recordError(name: string, message: string, isRateLimit: boolean): void {
289
- const record = this.ensureRecord(name);
290
- record.lastErrorAt = Date.now();
291
- record.lastErrorMessage = message.slice(0, 120);
292
- record.requests += 1;
293
- record.errors += 1;
294
- if (isRateLimit) {
295
- record.status = 'rate_limited';
296
- record.rateLimitExpiresAt = Date.now() + ProviderHealthTracker.DEFAULT_COOLDOWN_MS;
297
- return;
298
- }
299
- record.status = 'degraded';
300
- }
301
-
302
- private isRateLimitMessage(message: string): boolean {
303
- const lower = message.toLowerCase();
304
- return (
305
- lower.includes('429')
306
- || lower.includes('402')
307
- || /rate.limit|too many requests|quota exceeded|throttl|depleted|credits/.test(lower)
308
- );
309
- }
310
- }