@pellux/goodvibes-agent 0.1.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 (398) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +48 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +74 -0
  6. package/bin/goodvibes-agent.ts +2 -0
  7. package/docs/README.md +23 -0
  8. package/docs/deployment-and-services.md +57 -0
  9. package/docs/getting-started.md +53 -0
  10. package/docs/release-and-publishing.md +46 -0
  11. package/package.json +134 -0
  12. package/scripts/check-bun.sh +20 -0
  13. package/src/audio/player.ts +156 -0
  14. package/src/audio/spoken-turn-controller.ts +203 -0
  15. package/src/audio/spoken-turn-model-routing.ts +117 -0
  16. package/src/audio/spoken-turn-wiring.ts +44 -0
  17. package/src/audio/text-chunker.ts +110 -0
  18. package/src/cli/bundle-command.ts +227 -0
  19. package/src/cli/completion.ts +90 -0
  20. package/src/cli/config-overrides.ts +159 -0
  21. package/src/cli/endpoints.ts +63 -0
  22. package/src/cli/entrypoint.ts +172 -0
  23. package/src/cli/help.ts +299 -0
  24. package/src/cli/index.ts +11 -0
  25. package/src/cli/management-commands.ts +426 -0
  26. package/src/cli/management.ts +744 -0
  27. package/src/cli/network-posture.ts +46 -0
  28. package/src/cli/package-verification.ts +123 -0
  29. package/src/cli/parser.ts +369 -0
  30. package/src/cli/provider-auth-routes.ts +22 -0
  31. package/src/cli/provider-classification.ts +107 -0
  32. package/src/cli/redaction.ts +105 -0
  33. package/src/cli/service-command.ts +26 -0
  34. package/src/cli/service-posture.ts +482 -0
  35. package/src/cli/status.ts +383 -0
  36. package/src/cli/surface-command.ts +247 -0
  37. package/src/cli/tui-startup.ts +32 -0
  38. package/src/cli/types.ts +69 -0
  39. package/src/cli-flags.ts +21 -0
  40. package/src/config/goodvibes-home-audit.ts +465 -0
  41. package/src/config/index.ts +57 -0
  42. package/src/config/provider-model.ts +23 -0
  43. package/src/config/secret-config.ts +119 -0
  44. package/src/config/secrets.ts +71 -0
  45. package/src/config/surface.ts +1 -0
  46. package/src/core/composer-state.ts +61 -0
  47. package/src/core/conversation-rendering.ts +359 -0
  48. package/src/core/conversation.ts +551 -0
  49. package/src/core/history.ts +45 -0
  50. package/src/core/orchestrator.ts +7 -0
  51. package/src/core/system-message-router.ts +171 -0
  52. package/src/daemon/cli.ts +55 -0
  53. package/src/daemon/safe-serve.ts +61 -0
  54. package/src/input/agent-workspace.ts +428 -0
  55. package/src/input/autocomplete.ts +96 -0
  56. package/src/input/bookmark-modal.ts +115 -0
  57. package/src/input/command-args-hint.ts +36 -0
  58. package/src/input/command-registry.ts +329 -0
  59. package/src/input/commands/agent-externalized-tui.ts +73 -0
  60. package/src/input/commands/agent-workspace-runtime.ts +17 -0
  61. package/src/input/commands/branch-runtime.ts +72 -0
  62. package/src/input/commands/cloudflare-runtime.ts +370 -0
  63. package/src/input/commands/config.ts +18 -0
  64. package/src/input/commands/control-room-runtime.ts +255 -0
  65. package/src/input/commands/conversation-runtime.ts +207 -0
  66. package/src/input/commands/discovery-runtime.ts +52 -0
  67. package/src/input/commands/eval.ts +204 -0
  68. package/src/input/commands/experience-runtime.ts +278 -0
  69. package/src/input/commands/guidance-runtime.ts +106 -0
  70. package/src/input/commands/health-runtime.ts +434 -0
  71. package/src/input/commands/hooks-runtime.ts +148 -0
  72. package/src/input/commands/incident-runtime.ts +95 -0
  73. package/src/input/commands/integration-runtime.ts +394 -0
  74. package/src/input/commands/intelligence-runtime.ts +223 -0
  75. package/src/input/commands/knowledge.ts +531 -0
  76. package/src/input/commands/local-auth-runtime.ts +105 -0
  77. package/src/input/commands/local-provider-runtime.ts +170 -0
  78. package/src/input/commands/local-runtime.ts +392 -0
  79. package/src/input/commands/local-setup-review.ts +199 -0
  80. package/src/input/commands/local-setup-transfer.ts +135 -0
  81. package/src/input/commands/local-setup.ts +282 -0
  82. package/src/input/commands/managed-runtime.ts +209 -0
  83. package/src/input/commands/marketplace-runtime.ts +290 -0
  84. package/src/input/commands/mcp-runtime.ts +432 -0
  85. package/src/input/commands/memory-product-runtime.ts +111 -0
  86. package/src/input/commands/memory.ts +151 -0
  87. package/src/input/commands/notify-runtime.ts +83 -0
  88. package/src/input/commands/onboarding-runtime.ts +14 -0
  89. package/src/input/commands/operator-panel-runtime.ts +146 -0
  90. package/src/input/commands/operator-runtime.ts +392 -0
  91. package/src/input/commands/planning-runtime.ts +205 -0
  92. package/src/input/commands/platform-access-runtime.ts +422 -0
  93. package/src/input/commands/platform-services-runtime.ts +246 -0
  94. package/src/input/commands/policy-dispatch.ts +339 -0
  95. package/src/input/commands/policy.ts +17 -0
  96. package/src/input/commands/product-runtime.ts +351 -0
  97. package/src/input/commands/profile-sync-runtime.ts +99 -0
  98. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  99. package/src/input/commands/provider.ts +363 -0
  100. package/src/input/commands/qrcode-runtime.ts +20 -0
  101. package/src/input/commands/quit-shared.ts +162 -0
  102. package/src/input/commands/recall-bundle.ts +132 -0
  103. package/src/input/commands/recall-capture.ts +152 -0
  104. package/src/input/commands/recall-query.ts +229 -0
  105. package/src/input/commands/recall-review.ts +98 -0
  106. package/src/input/commands/recall-shared.ts +22 -0
  107. package/src/input/commands/remote-runtime-pool.ts +106 -0
  108. package/src/input/commands/remote-runtime-setup.ts +199 -0
  109. package/src/input/commands/remote-runtime.ts +431 -0
  110. package/src/input/commands/replay-runtime.ts +18 -0
  111. package/src/input/commands/runtime-services.ts +291 -0
  112. package/src/input/commands/schedule-runtime.ts +91 -0
  113. package/src/input/commands/services-runtime.ts +209 -0
  114. package/src/input/commands/session-content.ts +408 -0
  115. package/src/input/commands/session-workflow.ts +464 -0
  116. package/src/input/commands/session.ts +375 -0
  117. package/src/input/commands/settings-sync-runtime.ts +174 -0
  118. package/src/input/commands/share-runtime.ts +119 -0
  119. package/src/input/commands/shell-core.ts +307 -0
  120. package/src/input/commands/skills-runtime.ts +221 -0
  121. package/src/input/commands/subscription-runtime.ts +434 -0
  122. package/src/input/commands/tasks-runtime.ts +230 -0
  123. package/src/input/commands/teamwork-runtime.ts +339 -0
  124. package/src/input/commands/teleport-runtime.ts +57 -0
  125. package/src/input/commands/tts-runtime.ts +29 -0
  126. package/src/input/commands/work-plan-runtime.ts +169 -0
  127. package/src/input/commands.ts +131 -0
  128. package/src/input/feed-context-factory.ts +254 -0
  129. package/src/input/file-picker.ts +192 -0
  130. package/src/input/handler-command-route.ts +180 -0
  131. package/src/input/handler-content-actions.ts +497 -0
  132. package/src/input/handler-feed-routes.ts +648 -0
  133. package/src/input/handler-feed.ts +452 -0
  134. package/src/input/handler-interactions.ts +281 -0
  135. package/src/input/handler-modal-routes.ts +418 -0
  136. package/src/input/handler-modal-stack.ts +263 -0
  137. package/src/input/handler-modal-token-routes.ts +329 -0
  138. package/src/input/handler-onboarding-cloudflare.ts +391 -0
  139. package/src/input/handler-onboarding.ts +620 -0
  140. package/src/input/handler-picker-routes.ts +472 -0
  141. package/src/input/handler-prompt-buffer.ts +320 -0
  142. package/src/input/handler-shortcuts.ts +213 -0
  143. package/src/input/handler-ui-state.ts +372 -0
  144. package/src/input/handler.ts +729 -0
  145. package/src/input/input-history.ts +297 -0
  146. package/src/input/keybindings.ts +292 -0
  147. package/src/input/mcp-workspace.ts +554 -0
  148. package/src/input/model-picker-provider-filter.ts +28 -0
  149. package/src/input/model-picker-types.ts +137 -0
  150. package/src/input/model-picker.ts +797 -0
  151. package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
  152. package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
  153. package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
  154. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
  155. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
  156. package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
  157. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
  158. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
  159. package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
  160. package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
  161. package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
  162. package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
  163. package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
  164. package/src/input/onboarding/onboarding-wizard.ts +711 -0
  165. package/src/input/panel-integration-actions.ts +78 -0
  166. package/src/input/profile-picker-modal.ts +222 -0
  167. package/src/input/search.ts +100 -0
  168. package/src/input/selection-modal.ts +163 -0
  169. package/src/input/selection.ts +135 -0
  170. package/src/input/session-picker-modal.ts +136 -0
  171. package/src/input/settings-modal-behavior.ts +37 -0
  172. package/src/input/settings-modal-secrets.ts +41 -0
  173. package/src/input/settings-modal-subscriptions.ts +95 -0
  174. package/src/input/settings-modal-types.ts +91 -0
  175. package/src/input/settings-modal.ts +793 -0
  176. package/src/input/submission-intent.ts +17 -0
  177. package/src/input/submission-router.ts +59 -0
  178. package/src/input/tts-settings-actions.ts +100 -0
  179. package/src/main.ts +792 -0
  180. package/src/mcp/runtime-reload.ts +81 -0
  181. package/src/panels/agent-inspector-panel.ts +521 -0
  182. package/src/panels/agent-inspector-shared.ts +94 -0
  183. package/src/panels/agent-logs-panel.ts +559 -0
  184. package/src/panels/agent-logs-shared.ts +129 -0
  185. package/src/panels/approval-panel.ts +150 -0
  186. package/src/panels/automation-control-panel.ts +212 -0
  187. package/src/panels/base-panel.ts +254 -0
  188. package/src/panels/builtin/agent.ts +117 -0
  189. package/src/panels/builtin/development.ts +31 -0
  190. package/src/panels/builtin/knowledge.ts +26 -0
  191. package/src/panels/builtin/operations.ts +349 -0
  192. package/src/panels/builtin/session.ts +129 -0
  193. package/src/panels/builtin/shared.ts +274 -0
  194. package/src/panels/builtin-panels.ts +23 -0
  195. package/src/panels/cockpit-panel.ts +183 -0
  196. package/src/panels/communication-panel.ts +153 -0
  197. package/src/panels/confirm-state.ts +61 -0
  198. package/src/panels/context-visualizer-panel.ts +204 -0
  199. package/src/panels/control-plane-panel.ts +211 -0
  200. package/src/panels/cost-tracker-panel.ts +444 -0
  201. package/src/panels/debug-panel.ts +432 -0
  202. package/src/panels/diff-panel.ts +520 -0
  203. package/src/panels/docs-panel.ts +283 -0
  204. package/src/panels/eval-panel.ts +399 -0
  205. package/src/panels/file-explorer-panel.ts +584 -0
  206. package/src/panels/file-preview-panel.ts +434 -0
  207. package/src/panels/forensics-panel.ts +364 -0
  208. package/src/panels/git-panel.ts +638 -0
  209. package/src/panels/hooks-panel.ts +239 -0
  210. package/src/panels/incident-review-panel.ts +197 -0
  211. package/src/panels/index.ts +46 -0
  212. package/src/panels/intelligence-panel.ts +176 -0
  213. package/src/panels/knowledge-panel.ts +345 -0
  214. package/src/panels/local-auth-panel.ts +130 -0
  215. package/src/panels/marketplace-panel.ts +212 -0
  216. package/src/panels/memory-panel.ts +225 -0
  217. package/src/panels/ops-control-panel.ts +150 -0
  218. package/src/panels/ops-strategy-panel.ts +235 -0
  219. package/src/panels/orchestration-panel.ts +273 -0
  220. package/src/panels/panel-list-panel.ts +509 -0
  221. package/src/panels/panel-manager.ts +570 -0
  222. package/src/panels/panel-picker.ts +106 -0
  223. package/src/panels/plan-dashboard-panel.ts +274 -0
  224. package/src/panels/plugins-panel.ts +178 -0
  225. package/src/panels/policy-panel.ts +308 -0
  226. package/src/panels/polish.ts +717 -0
  227. package/src/panels/project-planning-panel.ts +711 -0
  228. package/src/panels/provider-account-snapshot.ts +259 -0
  229. package/src/panels/provider-accounts-panel.ts +218 -0
  230. package/src/panels/provider-health-domains.ts +215 -0
  231. package/src/panels/provider-health-panel.ts +727 -0
  232. package/src/panels/provider-health-tracker.ts +115 -0
  233. package/src/panels/provider-stats-panel.ts +366 -0
  234. package/src/panels/qr-panel.ts +182 -0
  235. package/src/panels/remote-panel.ts +449 -0
  236. package/src/panels/routes-panel.ts +178 -0
  237. package/src/panels/sandbox-panel.ts +283 -0
  238. package/src/panels/schedule-panel.ts +329 -0
  239. package/src/panels/scrollable-list-panel.ts +491 -0
  240. package/src/panels/search-focus.ts +32 -0
  241. package/src/panels/security-panel.ts +295 -0
  242. package/src/panels/services-panel.ts +231 -0
  243. package/src/panels/session-browser-panel.ts +400 -0
  244. package/src/panels/session-maintenance.ts +125 -0
  245. package/src/panels/settings-sync-panel.ts +120 -0
  246. package/src/panels/skills-panel.ts +431 -0
  247. package/src/panels/subscription-panel.ts +263 -0
  248. package/src/panels/symbol-outline-panel.ts +486 -0
  249. package/src/panels/system-messages-panel.ts +230 -0
  250. package/src/panels/tasks-panel.ts +399 -0
  251. package/src/panels/thinking-panel.ts +304 -0
  252. package/src/panels/token-budget-panel.ts +475 -0
  253. package/src/panels/tool-inspector-panel.ts +429 -0
  254. package/src/panels/types.ts +54 -0
  255. package/src/panels/watchers-panel.ts +193 -0
  256. package/src/panels/work-plan-panel.ts +175 -0
  257. package/src/panels/worktree-panel.ts +182 -0
  258. package/src/panels/wrfc-panel.ts +609 -0
  259. package/src/permissions/prompt.ts +165 -0
  260. package/src/planning/project-planning-coordinator.ts +543 -0
  261. package/src/plugins/loader.ts +15 -0
  262. package/src/renderer/agent-detail-modal.ts +331 -0
  263. package/src/renderer/agent-workspace.ts +238 -0
  264. package/src/renderer/ansi-sanitize.ts +76 -0
  265. package/src/renderer/autocomplete-overlay.ts +154 -0
  266. package/src/renderer/block-actions.ts +76 -0
  267. package/src/renderer/bookmark-modal.ts +101 -0
  268. package/src/renderer/bottom-bar.ts +58 -0
  269. package/src/renderer/buffer.ts +113 -0
  270. package/src/renderer/code-block.ts +373 -0
  271. package/src/renderer/compositor.ts +283 -0
  272. package/src/renderer/context-inspector.ts +219 -0
  273. package/src/renderer/conversation-layout.ts +67 -0
  274. package/src/renderer/conversation-overlays.ts +140 -0
  275. package/src/renderer/conversation-surface.ts +260 -0
  276. package/src/renderer/diff-view.ts +132 -0
  277. package/src/renderer/diff.ts +130 -0
  278. package/src/renderer/file-picker-overlay.ts +101 -0
  279. package/src/renderer/file-tree.ts +153 -0
  280. package/src/renderer/fullscreen-primitives.ts +130 -0
  281. package/src/renderer/fullscreen-workspace.ts +199 -0
  282. package/src/renderer/git-status.ts +89 -0
  283. package/src/renderer/help-overlay.ts +267 -0
  284. package/src/renderer/history-search-overlay.ts +73 -0
  285. package/src/renderer/layout-engine.ts +97 -0
  286. package/src/renderer/layout.ts +32 -0
  287. package/src/renderer/live-tail-modal.ts +156 -0
  288. package/src/renderer/markdown.ts +635 -0
  289. package/src/renderer/mcp-workspace.ts +237 -0
  290. package/src/renderer/modal-factory.ts +467 -0
  291. package/src/renderer/modal-utils.ts +24 -0
  292. package/src/renderer/model-picker-overlay.ts +473 -0
  293. package/src/renderer/model-workspace.ts +488 -0
  294. package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
  295. package/src/renderer/overlay-box.ts +146 -0
  296. package/src/renderer/overlay-viewport.ts +104 -0
  297. package/src/renderer/panel-composite.ts +158 -0
  298. package/src/renderer/panel-picker-overlay.ts +202 -0
  299. package/src/renderer/panel-tab-bar.ts +69 -0
  300. package/src/renderer/panel-workspace-bar.ts +42 -0
  301. package/src/renderer/process-indicator.ts +96 -0
  302. package/src/renderer/process-modal.ts +656 -0
  303. package/src/renderer/process-summary.ts +67 -0
  304. package/src/renderer/profile-picker-modal.ts +129 -0
  305. package/src/renderer/progress.ts +98 -0
  306. package/src/renderer/qr-renderer.ts +120 -0
  307. package/src/renderer/search-overlay.ts +54 -0
  308. package/src/renderer/selection-modal-overlay.ts +214 -0
  309. package/src/renderer/semantic-diff.ts +369 -0
  310. package/src/renderer/session-picker-modal.ts +127 -0
  311. package/src/renderer/settings-modal-helpers.ts +193 -0
  312. package/src/renderer/settings-modal.ts +537 -0
  313. package/src/renderer/shell-surface.ts +88 -0
  314. package/src/renderer/status-glyphs.ts +21 -0
  315. package/src/renderer/status-token.ts +67 -0
  316. package/src/renderer/surface-layout.ts +101 -0
  317. package/src/renderer/syntax-highlighter.ts +542 -0
  318. package/src/renderer/system-message.ts +83 -0
  319. package/src/renderer/tab-strip.ts +108 -0
  320. package/src/renderer/text-layout.ts +31 -0
  321. package/src/renderer/thinking.ts +17 -0
  322. package/src/renderer/tool-call.ts +234 -0
  323. package/src/renderer/ui-factory.ts +524 -0
  324. package/src/renderer/ui-primitives.ts +96 -0
  325. package/src/runtime/bootstrap-command-context.ts +278 -0
  326. package/src/runtime/bootstrap-command-parts.ts +386 -0
  327. package/src/runtime/bootstrap-core.ts +540 -0
  328. package/src/runtime/bootstrap-hook-bridge.ts +112 -0
  329. package/src/runtime/bootstrap-shell.ts +283 -0
  330. package/src/runtime/bootstrap.ts +575 -0
  331. package/src/runtime/cloudflare-control-plane.ts +349 -0
  332. package/src/runtime/context.ts +142 -0
  333. package/src/runtime/diagnostics/panels/index.ts +24 -0
  334. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  335. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  336. package/src/runtime/diagnostics/panels/policy.ts +177 -0
  337. package/src/runtime/index.ts +662 -0
  338. package/src/runtime/onboarding/apply.ts +642 -0
  339. package/src/runtime/onboarding/derivation.ts +534 -0
  340. package/src/runtime/onboarding/index.ts +7 -0
  341. package/src/runtime/onboarding/markers.ts +148 -0
  342. package/src/runtime/onboarding/snapshot.ts +406 -0
  343. package/src/runtime/onboarding/state.ts +141 -0
  344. package/src/runtime/onboarding/types.ts +404 -0
  345. package/src/runtime/onboarding/verify.ts +171 -0
  346. package/src/runtime/operator-token-cleanup.ts +27 -0
  347. package/src/runtime/perf/panel-contracts.ts +32 -0
  348. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  349. package/src/runtime/sandbox-public-gaps.ts +358 -0
  350. package/src/runtime/services.ts +670 -0
  351. package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
  352. package/src/runtime/store/domains/index.ts +222 -0
  353. package/src/runtime/store/domains/panels.ts +117 -0
  354. package/src/runtime/store/domains/ui-perf.ts +103 -0
  355. package/src/runtime/store/index.ts +305 -0
  356. package/src/runtime/store/selectors/index.ts +359 -0
  357. package/src/runtime/store/state.ts +145 -0
  358. package/src/runtime/surface-feature-flags.ts +65 -0
  359. package/src/runtime/terminal-output-guard.ts +228 -0
  360. package/src/runtime/ui/index.ts +39 -0
  361. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  362. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  363. package/src/runtime/ui/model-picker/index.ts +59 -0
  364. package/src/runtime/ui/model-picker/types.ts +149 -0
  365. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  366. package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
  367. package/src/runtime/ui/provider-health/index.ts +46 -0
  368. package/src/runtime/ui/provider-health/types.ts +146 -0
  369. package/src/runtime/ui-events.ts +1 -0
  370. package/src/runtime/ui-read-model-helpers.ts +1 -0
  371. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
  372. package/src/runtime/ui-read-models-observability-options.ts +1 -0
  373. package/src/runtime/ui-read-models-observability-remote.ts +1 -0
  374. package/src/runtime/ui-read-models-observability-security.ts +1 -0
  375. package/src/runtime/ui-read-models-observability-system.ts +1 -0
  376. package/src/runtime/ui-read-models-observability.ts +1 -0
  377. package/src/runtime/ui-read-models.ts +61 -0
  378. package/src/runtime/ui-service-queries.ts +1 -0
  379. package/src/runtime/ui-services.ts +190 -0
  380. package/src/scripts/process-messages.ts +42 -0
  381. package/src/shell/blocking-input.ts +98 -0
  382. package/src/shell/service-settings-sync.ts +273 -0
  383. package/src/shell/ui-openers.ts +352 -0
  384. package/src/tools/index.ts +1 -0
  385. package/src/tools/wrfc-agent-guard.ts +49 -0
  386. package/src/types/grid.ts +48 -0
  387. package/src/types/sql-js.d.ts +15 -0
  388. package/src/utils/clipboard.ts +22 -0
  389. package/src/utils/splash-lines.ts +46 -0
  390. package/src/utils/terminal-width.ts +185 -0
  391. package/src/verification/live-verifier.ts +430 -0
  392. package/src/verification/verification-ledger.ts +242 -0
  393. package/src/version.ts +17 -0
  394. package/src/widget/index.ts +2 -0
  395. package/src/widget/types.ts +9 -0
  396. package/src/widget/widget.ts +8 -0
  397. package/src/work-plans/work-plan-store.ts +374 -0
  398. package/tsconfig.json +18 -0
@@ -0,0 +1,727 @@
1
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
2
+ import { BasePanel } from './base-panel.ts';
3
+ import { createEmptyLine, createStyledCell, type Line } from '../types/grid.ts';
4
+ import type { ProviderAuthRouteDescriptor } from '@pellux/goodvibes-sdk/platform/providers';
5
+ import type { ProviderEvent, TurnEvent } 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 { ProviderHealthTracker, type ProviderStatus } from './provider-health-tracker.ts';
11
+ import {
12
+ buildProviderHealthDomainSummaries,
13
+ type HealthDomainSummary,
14
+ } from './provider-health-domains.ts';
15
+ import type {
16
+ UiContinuitySnapshot,
17
+ UiIntelligenceSnapshot,
18
+ UiLocalAuthSnapshot,
19
+ UiProvidersSnapshot,
20
+ UiReadModel,
21
+ UiRemoteSnapshot,
22
+ UiSecuritySnapshot,
23
+ UiSessionSnapshot,
24
+ UiSettingsSnapshot,
25
+ UiWorktreeSnapshot,
26
+ } from '../runtime/ui-read-models.ts';
27
+ import { evaluateSessionMaintenance } from '@/runtime/index.ts';
28
+ import {
29
+ buildBodyText,
30
+ buildDetailBlock,
31
+ buildEmptyState,
32
+ buildGuidanceLine,
33
+ buildKeyValueLine,
34
+ buildPanelListRow,
35
+ buildPanelLine,
36
+ buildPanelWorkspace,
37
+ buildSummaryBlock,
38
+ DEFAULT_PANEL_PALETTE,
39
+ resolvePrimaryScrollableSection,
40
+ type PanelWorkspaceSection,
41
+ } from './polish.ts';
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Types
45
+ // ---------------------------------------------------------------------------
46
+
47
+ export interface ProviderHealthPanelDeps {
48
+ readonly configManager: Pick<ConfigManager, 'get'>;
49
+ readonly turnEvents: UiEventFeed<TurnEvent>;
50
+ readonly providerEvents: UiEventFeed<ProviderEvent>;
51
+ readonly providers: UiReadModel<UiProvidersSnapshot>;
52
+ readonly session: UiReadModel<UiSessionSnapshot>;
53
+ readonly security: UiReadModel<UiSecuritySnapshot>;
54
+ readonly localAuth: UiReadModel<UiLocalAuthSnapshot>;
55
+ readonly settings: UiReadModel<UiSettingsSnapshot>;
56
+ readonly remote: UiReadModel<UiRemoteSnapshot>;
57
+ readonly intelligence: UiReadModel<UiIntelligenceSnapshot>;
58
+ readonly continuity: UiReadModel<UiContinuitySnapshot>;
59
+ readonly worktrees: UiReadModel<UiWorktreeSnapshot>;
60
+ }
61
+
62
+ // Colors
63
+ // ---------------------------------------------------------------------------
64
+
65
+ const C = {
66
+ title: '#00ffff',
67
+ online: '#5fd700',
68
+ rateLimit: '#ffaf00',
69
+ error: '#ff5f5f',
70
+ unknown: '244',
71
+ label: '244',
72
+ value: '252',
73
+ dim: '240',
74
+ provName: '#e2e8f0',
75
+ errMsg: '#ff5f5f',
76
+ latGood: '#5fd700',
77
+ latWarn: '#ffaf00',
78
+ latBad: '#ff5f5f',
79
+ separator: '#374151',
80
+ } as const;
81
+
82
+ const LATENCY_WARN_MS = 2_000;
83
+ const LATENCY_BAD_MS = 5_000;
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Helpers
87
+ // ---------------------------------------------------------------------------
88
+
89
+ function statusDot(status: ProviderStatus): { char: string; color: string } {
90
+ switch (status) {
91
+ case 'online': return { char: '●', color: C.online };
92
+ case 'rate-limited': return { char: '◐', color: C.rateLimit };
93
+ case 'error': return { char: '✕', color: C.error };
94
+ default: return { char: '○', color: C.unknown };
95
+ }
96
+ }
97
+
98
+ function statusLabel(status: ProviderStatus): string {
99
+ switch (status) {
100
+ case 'online': return 'online';
101
+ case 'rate-limited': return 'rate-limited';
102
+ case 'error': return 'error';
103
+ default: return 'unknown';
104
+ }
105
+ }
106
+
107
+ function latencyColor(ms: number): string {
108
+ if (ms >= LATENCY_BAD_MS) return C.latBad;
109
+ if (ms >= LATENCY_WARN_MS) return C.latWarn;
110
+ return C.latGood;
111
+ }
112
+
113
+ function fmtMs(ms: number): string {
114
+ if (ms <= 0) return 'n/a';
115
+ if (ms >= 10_000) return `${(ms / 1000).toFixed(1)}s`;
116
+ if (ms >= 1_000) return `${(ms / 1000).toFixed(2)}s`;
117
+ return `${Math.round(ms)}ms`;
118
+ }
119
+
120
+ function fmtAgo(ts: number | undefined): string {
121
+ if (!ts) return 'n/a';
122
+ const sec = Math.floor((Date.now() - ts) / 1000);
123
+ if (sec < 60) return `${sec}s ago`;
124
+ if (sec < 3600) return `${Math.floor(sec / 60)}m ago`;
125
+ return `${Math.floor(sec / 3600)}h ago`;
126
+ }
127
+
128
+ function fmtCooldown(expiresAt: number): string {
129
+ const remaining = Math.ceil((expiresAt - Date.now()) / 1000);
130
+ if (remaining <= 0) return 'expiring';
131
+ return `${remaining}s cooldown`;
132
+ }
133
+
134
+ type ProviderPanelAuthRoute = ProviderAuthRouteDescriptor['route'] | 'unconfigured';
135
+ type ProviderPanelAuthFreshness = NonNullable<ProviderAuthRouteDescriptor['freshness']> | 'unconfigured';
136
+
137
+ interface ProviderRuntimeRecord {
138
+ readonly providerId: string;
139
+ readonly active: boolean;
140
+ readonly modelCount: number;
141
+ readonly activeRoute: ProviderPanelAuthRoute;
142
+ readonly preferredRoute: ProviderPanelAuthRoute;
143
+ readonly activeRouteReason: string;
144
+ readonly authFreshness: ProviderPanelAuthFreshness;
145
+ readonly fallbackRisk?: string;
146
+ readonly issues: readonly string[];
147
+ readonly recommendedActions: readonly string[];
148
+ }
149
+
150
+ function domainColor(level: HealthDomainSummary['level']): string {
151
+ switch (level) {
152
+ case 'good':
153
+ return C.online;
154
+ case 'warn':
155
+ return C.rateLimit;
156
+ case 'bad':
157
+ return C.error;
158
+ default:
159
+ return C.value;
160
+ }
161
+ }
162
+
163
+ const AUTH_ROUTE_PRIORITY: readonly ProviderPanelAuthRoute[] = [
164
+ 'subscription-oauth',
165
+ 'service-oauth',
166
+ 'secret-ref',
167
+ 'api-key',
168
+ 'anonymous',
169
+ 'none',
170
+ 'unconfigured',
171
+ ] as const;
172
+
173
+ function routePriority(route: ProviderPanelAuthRoute): number {
174
+ const priority = AUTH_ROUTE_PRIORITY.indexOf(route);
175
+ return priority >= 0 ? priority : AUTH_ROUTE_PRIORITY.length;
176
+ }
177
+
178
+ function routeColor(route: ProviderPanelAuthRoute): string {
179
+ switch (route) {
180
+ case 'subscription-oauth':
181
+ return C.title;
182
+ case 'service-oauth':
183
+ return C.online;
184
+ case 'api-key':
185
+ return C.rateLimit;
186
+ case 'secret-ref':
187
+ return C.value;
188
+ case 'anonymous':
189
+ case 'none':
190
+ return C.dim;
191
+ default:
192
+ return C.value;
193
+ }
194
+ }
195
+
196
+ function freshnessColor(freshness: ProviderPanelAuthFreshness): string {
197
+ switch (freshness) {
198
+ case 'expired':
199
+ return C.error;
200
+ case 'expiring':
201
+ case 'pending':
202
+ return C.rateLimit;
203
+ case 'healthy':
204
+ return C.online;
205
+ default:
206
+ return C.dim;
207
+ }
208
+ }
209
+
210
+ function buildSyntheticAuthRoutes(
211
+ auth: {
212
+ readonly mode: 'api-key' | 'oauth' | 'anonymous' | 'none';
213
+ readonly configured: boolean;
214
+ readonly detail?: string;
215
+ readonly envVars?: readonly string[];
216
+ } | undefined,
217
+ ): readonly ProviderAuthRouteDescriptor[] {
218
+ if (!auth) return [];
219
+ switch (auth.mode) {
220
+ case 'none':
221
+ return [{
222
+ route: 'none',
223
+ label: 'No auth required',
224
+ configured: true,
225
+ usable: true,
226
+ freshness: 'healthy',
227
+ detail: auth.detail ?? 'Provider does not require interactive credentials.',
228
+ }];
229
+ case 'anonymous':
230
+ return [{
231
+ route: 'anonymous',
232
+ label: 'Anonymous / local access',
233
+ configured: auth.configured,
234
+ usable: auth.configured,
235
+ freshness: auth.configured ? 'healthy' : 'unconfigured',
236
+ detail: auth.detail ?? 'Provider can be used without stored credentials.',
237
+ }];
238
+ case 'api-key':
239
+ return [{
240
+ route: 'api-key',
241
+ label: 'Ambient API key',
242
+ configured: auth.configured,
243
+ usable: auth.configured,
244
+ freshness: auth.configured ? 'healthy' : 'unconfigured',
245
+ detail: auth.detail ?? 'Provider expects a configured API key.',
246
+ ...(auth.envVars?.length ? { envVars: auth.envVars } : {}),
247
+ ...(auth.envVars?.length
248
+ ? { repairHints: [`Set ${auth.envVars.join(' or ')} in the environment or secrets store.`] }
249
+ : {}),
250
+ }];
251
+ case 'oauth':
252
+ return [{
253
+ route: 'service-oauth',
254
+ label: 'OAuth session',
255
+ configured: auth.configured,
256
+ usable: auth.configured,
257
+ freshness: auth.configured ? 'healthy' : 'unconfigured',
258
+ detail: auth.detail ?? 'Provider expects an OAuth-backed credential.',
259
+ repairHints: ['Refresh or repair the provider OAuth session before relying on it.'],
260
+ }];
261
+ default:
262
+ return [];
263
+ }
264
+ }
265
+
266
+ function getUsableRoute(route: ProviderAuthRouteDescriptor): boolean {
267
+ return route.usable ?? route.configured;
268
+ }
269
+
270
+ function pickRoute(
271
+ routes: readonly ProviderAuthRouteDescriptor[],
272
+ ): ProviderAuthRouteDescriptor | null {
273
+ if (routes.length === 0) return null;
274
+ return [...routes].sort((left, right) => routePriority(left.route) - routePriority(right.route))[0] ?? null;
275
+ }
276
+
277
+ function buildProviderRuntimeRecord(
278
+ snapshot: {
279
+ readonly providerId: string;
280
+ readonly active: boolean;
281
+ readonly modelCount: number;
282
+ readonly runtime: {
283
+ readonly auth?: {
284
+ readonly mode: 'api-key' | 'oauth' | 'anonymous' | 'none';
285
+ readonly configured: boolean;
286
+ readonly detail?: string;
287
+ readonly envVars?: readonly string[];
288
+ readonly routes?: readonly ProviderAuthRouteDescriptor[];
289
+ };
290
+ };
291
+ },
292
+ ): ProviderRuntimeRecord {
293
+ const auth = snapshot.runtime.auth;
294
+ const routes = auth?.routes?.length ? auth.routes : buildSyntheticAuthRoutes(auth);
295
+ const configuredRoutes = routes.filter((route) => route.configured);
296
+ const usableRoutes = routes.filter(getUsableRoute);
297
+ const preferredRoute = pickRoute(configuredRoutes.length > 0 ? configuredRoutes : routes);
298
+ const activeRoute = pickRoute(usableRoutes.length > 0 ? usableRoutes : (preferredRoute ? [preferredRoute] : []));
299
+ const activeRouteId = activeRoute?.route ?? 'unconfigured';
300
+ const preferredRouteId = preferredRoute?.route ?? activeRouteId;
301
+ const authFreshness = activeRoute?.freshness ?? (activeRouteId === 'none' ? 'healthy' : 'unconfigured');
302
+ const fallbackRisk = usableRoutes.length > 1
303
+ ? 'Multiple auth routes are simultaneously usable; verify route priority before switching providers.'
304
+ : undefined;
305
+
306
+ const issueSet = new Set<string>();
307
+ const actionSet = new Set<string>();
308
+
309
+ if (activeRouteId === 'unconfigured' && auth?.mode !== 'none') {
310
+ issueSet.add('Provider has no usable auth route configured.');
311
+ }
312
+
313
+ for (const route of routes) {
314
+ if (route.freshness === 'expired') {
315
+ issueSet.add(route.detail ?? `${route.label} is expired.`);
316
+ } else if (route.freshness === 'pending') {
317
+ issueSet.add(route.detail ?? `${route.label} is pending completion.`);
318
+ } else if (route.configured && !getUsableRoute(route)) {
319
+ issueSet.add(route.detail ?? `${route.label} is configured but not currently usable.`);
320
+ }
321
+ for (const hint of route.repairHints ?? []) {
322
+ if (hint.trim().length > 0) actionSet.add(hint);
323
+ }
324
+ }
325
+
326
+ if (fallbackRisk) issueSet.add(fallbackRisk);
327
+ if (issueSet.size > 0 && actionSet.size === 0 && activeRouteId !== 'none') {
328
+ actionSet.add(`Review ${snapshot.providerId} provider credentials and routing metadata.`);
329
+ }
330
+
331
+ return {
332
+ providerId: snapshot.providerId,
333
+ active: snapshot.active,
334
+ modelCount: snapshot.modelCount,
335
+ activeRoute: activeRouteId,
336
+ preferredRoute: preferredRouteId,
337
+ activeRouteReason: activeRoute?.detail
338
+ ?? auth?.detail
339
+ ?? (activeRouteId === 'none'
340
+ ? 'Provider does not require interactive credentials.'
341
+ : 'No usable auth route is configured for this provider.'),
342
+ authFreshness,
343
+ ...(fallbackRisk ? { fallbackRisk } : {}),
344
+ issues: [...issueSet],
345
+ recommendedActions: [...actionSet],
346
+ };
347
+ }
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // ProviderHealthPanel
351
+ // ---------------------------------------------------------------------------
352
+
353
+ /**
354
+ * Real-time provider health / status dashboard.
355
+ *
356
+ * Displays for each known provider:
357
+ * - Status indicator (online / rate-limited / error / unknown)
358
+ * - Last response latency
359
+ * - Last seen timestamp
360
+ * - Last error message (if any)
361
+ * - Active cooldown timer for rate-limited providers
362
+ */
363
+ export class ProviderHealthPanel extends BasePanel {
364
+ private _unsubs: Array<() => void> = [];
365
+ private _refreshTimerId: ReturnType<typeof setInterval> | null = null;
366
+ private _selectedIndex = 0;
367
+ private _scrollOffset = 0;
368
+ private _accountRecords = new Map<string, ProviderRuntimeRecord>();
369
+ private _accountRefreshAt = 0;
370
+ private _accountLoading = false;
371
+ private readonly providerHealthTracker = new ProviderHealthTracker();
372
+
373
+ constructor(
374
+ private readonly providerRuntime: ProviderRuntimeInspectionQuery,
375
+ private readonly deps: ProviderHealthPanelDeps,
376
+ private readonly requestRender: () => void = () => {},
377
+ ) {
378
+ super('provider-health', 'Health', 'N', 'monitoring');
379
+ this._subscribe();
380
+ void this._refreshAccountPosture(true);
381
+ this._ensureRefreshTimer();
382
+ }
383
+
384
+ // -------------------------------------------------------------------------
385
+ // Event subscription
386
+ // -------------------------------------------------------------------------
387
+
388
+ private _subscribe(): void {
389
+ this._unsubs.push(
390
+ this.deps.turnEvents.on('TURN_SUBMITTED', () => {
391
+ this.providerHealthTracker.onTurnStart();
392
+ }),
393
+ );
394
+
395
+ this._unsubs.push(
396
+ this.deps.turnEvents.on('STREAM_START', () => {
397
+ this.providerHealthTracker.onStreamStart();
398
+ }),
399
+ );
400
+
401
+ this._unsubs.push(
402
+ this.deps.turnEvents.on('LLM_RESPONSE_RECEIVED', (payload) => {
403
+ this.providerHealthTracker.onLlmResponse(payload.provider);
404
+ this._markDirtyAndRender();
405
+ }),
406
+ );
407
+
408
+ this._unsubs.push(
409
+ this.deps.turnEvents.on('TURN_ERROR', (payload) => {
410
+ this.providerHealthTracker.onTurnError(payload.error);
411
+ this._markDirtyAndRender();
412
+ }),
413
+ );
414
+
415
+ this._unsubs.push(
416
+ this.deps.providerEvents.on('PROVIDERS_CHANGED', () => {
417
+ this.providerHealthTracker.onProvidersChanged([
418
+ ...new Set([
419
+ ...this.deps.providers.getSnapshot().providerIds,
420
+ ...this.providerRuntime.listProviderIds(),
421
+ ]),
422
+ ]);
423
+ void this._refreshAccountPosture(true);
424
+ this._markDirtyAndRender();
425
+ }),
426
+ );
427
+
428
+ for (const readModel of [
429
+ this.deps.providers,
430
+ this.deps.session,
431
+ this.deps.security,
432
+ this.deps.localAuth,
433
+ this.deps.settings,
434
+ this.deps.remote,
435
+ this.deps.intelligence,
436
+ this.deps.continuity,
437
+ this.deps.worktrees,
438
+ ] as const) {
439
+ this._unsubs.push(readModel.subscribe(() => this._markDirtyAndRender()));
440
+ }
441
+ }
442
+
443
+ private _markDirtyAndRender(): void {
444
+ this.markDirty();
445
+ this.requestRender();
446
+ }
447
+
448
+ // -------------------------------------------------------------------------
449
+ // Lifecycle
450
+ // -------------------------------------------------------------------------
451
+
452
+ override onActivate(): void {
453
+ super.onActivate();
454
+ this.markDirty();
455
+ void this._refreshAccountPosture(true);
456
+ this._ensureRefreshTimer();
457
+ }
458
+
459
+ override onDeactivate(): void {
460
+ super.onDeactivate();
461
+ }
462
+
463
+ override onDestroy(): void {
464
+ super.onDestroy();
465
+ this._refreshTimerId = null;
466
+ for (const unsub of this._unsubs) unsub();
467
+ this._unsubs = [];
468
+ }
469
+
470
+ private _ensureRefreshTimer(): void {
471
+ if (this._refreshTimerId !== null) return;
472
+ this._refreshTimerId = this.registerTimer(setInterval(() => {
473
+ if (Date.now() - this._accountRefreshAt > 30_000) {
474
+ void this._refreshAccountPosture();
475
+ }
476
+ this.markDirty();
477
+ this.requestRender();
478
+ }, 1_000));
479
+ }
480
+
481
+ handleInput(key: string): boolean {
482
+ const knownSet = new Set([
483
+ ...this.deps.providers.getSnapshot().providerIds,
484
+ ...this.providerRuntime.listProviderIds(),
485
+ ...this._accountRecords.keys(),
486
+ ]);
487
+ for (const h of this.providerHealthTracker.getAll()) knownSet.add(h.name);
488
+ const providers = [...knownSet].sort();
489
+ if (providers.length === 0) return false;
490
+ if (key === 'j' || key === 'down' || key === '\x1b[B') {
491
+ this._selectedIndex = Math.min(providers.length - 1, this._selectedIndex + 1);
492
+ this.markDirty();
493
+ return true;
494
+ }
495
+ if (key === 'k' || key === 'up' || key === '\x1b[A') {
496
+ this._selectedIndex = Math.max(0, this._selectedIndex - 1);
497
+ this.markDirty();
498
+ return true;
499
+ }
500
+ return false;
501
+ }
502
+
503
+ private async _refreshAccountPosture(force = false): Promise<void> {
504
+ if (this._accountLoading) return;
505
+ if (!force && Date.now() - this._accountRefreshAt < 15_000) return;
506
+ this._accountLoading = true;
507
+ try {
508
+ const snapshots = await this.providerRuntime.inspectAll();
509
+ this._accountRecords = new Map(
510
+ snapshots
511
+ .map((snapshot) => buildProviderRuntimeRecord(snapshot))
512
+ .map((record) => [record.providerId, record] as const),
513
+ );
514
+ this._accountRefreshAt = Date.now();
515
+ this.markDirty();
516
+ this.requestRender();
517
+ } finally {
518
+ this._accountLoading = false;
519
+ }
520
+ }
521
+
522
+ // -------------------------------------------------------------------------
523
+ // Rendering
524
+ // -------------------------------------------------------------------------
525
+
526
+ override render(width: number, height: number): Line[] {
527
+ const intro = 'Cross-domain health workspace for providers, auth, settings, remote, MCP, continuity, worktrees, and maintenance posture.';
528
+
529
+ const knownSet = new Set([
530
+ ...this.deps.providers.getSnapshot().providerIds,
531
+ ...this.providerRuntime.listProviderIds(),
532
+ ...this._accountRecords.keys(),
533
+ ]);
534
+ for (const h of this.providerHealthTracker.getAll()) knownSet.add(h.name);
535
+ const providers = [...knownSet].sort();
536
+ this._selectedIndex = Math.min(this._selectedIndex, Math.max(0, providers.length - 1));
537
+
538
+ if (providers.length === 0) {
539
+ return buildPanelWorkspace(width, height, {
540
+ title: 'Health',
541
+ intro,
542
+ sections: [{
543
+ lines: buildEmptyState(
544
+ width,
545
+ ' No providers registered.',
546
+ 'Provider health appears here once model providers are available and the runtime begins making requests.',
547
+ [
548
+ { command: '/provider', summary: 'review current provider and model selection' },
549
+ { command: '/subscription', summary: 'review provider login and subscription state' },
550
+ ],
551
+ { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' },
552
+ ),
553
+ }],
554
+ palette: { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' },
555
+ });
556
+ }
557
+
558
+ let online = 0;
559
+ let rateLimited = 0;
560
+ let errored = 0;
561
+ let accountIssues = 0;
562
+ let expiringAuth = 0;
563
+ for (const name of providers) {
564
+ const status = this.providerHealthTracker.get(name)?.status ?? 'unknown';
565
+ if (status === 'online') online++;
566
+ else if (status === 'rate-limited') rateLimited++;
567
+ else if (status === 'error') errored++;
568
+ const account = this._accountRecords.get(name);
569
+ if (account) {
570
+ accountIssues += account.issues.length;
571
+ if (account.authFreshness === 'expiring' || account.authFreshness === 'expired' || account.authFreshness === 'pending') {
572
+ expiringAuth++;
573
+ }
574
+ }
575
+ }
576
+
577
+ const postureLines = [
578
+ buildKeyValueLine(width, [
579
+ { label: 'providers', value: String(providers.length), valueColor: C.value },
580
+ { label: 'online', value: String(online), valueColor: C.online },
581
+ { label: 'rate-limited', value: String(rateLimited), valueColor: C.rateLimit },
582
+ { label: 'error', value: String(errored), valueColor: C.error },
583
+ { label: 'auth alerts', value: String(expiringAuth), valueColor: expiringAuth > 0 ? C.rateLimit : C.dim },
584
+ { label: 'account issues', value: String(accountIssues), valueColor: accountIssues > 0 ? C.error : C.dim },
585
+ ], { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }),
586
+ buildGuidanceLine(width, '/provider', 'review provider selection and routing if health posture degrades', { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }),
587
+ buildGuidanceLine(width, '/accounts', 'inspect auth routes, fallback posture, and billing-path safety', { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }),
588
+ ];
589
+
590
+ const domainLines: Line[] = [];
591
+ for (const domain of buildProviderHealthDomainSummaries({
592
+ configManager: this.deps.configManager,
593
+ auth: this.deps.localAuth.getSnapshot(),
594
+ settings: this.deps.settings.getSnapshot(),
595
+ remote: this.deps.remote.getSnapshot(),
596
+ security: this.deps.security.getSnapshot(),
597
+ intelligence: this.deps.intelligence.getSnapshot(),
598
+ continuity: this.deps.continuity.getSnapshot(),
599
+ worktrees: this.deps.worktrees.getSnapshot(),
600
+ session: this.deps.session.getSnapshot(),
601
+ })) {
602
+ domainLines.push(buildPanelLine(width, [
603
+ [' ', C.label],
604
+ [domain.name.padEnd(14), C.provName],
605
+ [domain.summary.slice(0, Math.max(0, width - 36)).padEnd(Math.max(0, width - 36)), domainColor(domain.level)],
606
+ [' ', C.label],
607
+ [domain.next.slice(0, 20), C.dim],
608
+ ]));
609
+ for (const detail of domain.details.slice(0, 2)) {
610
+ domainLines.push(...buildBodyText(width, ` ${detail}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.dim));
611
+ }
612
+ if (domain.nextSteps.length > 1) {
613
+ domainLines.push(...buildBodyText(width, ` next: ${domain.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.title));
614
+ }
615
+ }
616
+
617
+ const selectedName = providers[this._selectedIndex];
618
+ const selectedHealth = selectedName ? this.providerHealthTracker.get(selectedName) : undefined;
619
+ const selectedAccount = selectedName ? this._accountRecords.get(selectedName) : undefined;
620
+ const selectedLines: Line[] = [];
621
+ const maintenanceLines: Line[] = [];
622
+ const session = this.deps.session.getSnapshot();
623
+ const maintenance = evaluateSessionMaintenance({
624
+ configManager: this.deps.configManager,
625
+ currentTokens: session.estimatedContextTokens,
626
+ contextWindow: session.contextWindow,
627
+ messageCount: session.messageCount,
628
+ session: session.session,
629
+ });
630
+ maintenanceLines.push(buildKeyValueLine(width, [
631
+ { label: 'level', value: maintenance.level, valueColor: maintenance.level === 'needs-repair' ? C.error : maintenance.level === 'suggest-compact' || maintenance.level === 'watch' ? C.rateLimit : C.online },
632
+ { label: 'guidance', value: maintenance.guidanceMode, valueColor: C.value },
633
+ { label: 'usage', value: `${maintenance.usagePct}%`, valueColor: maintenance.usagePct >= 80 ? C.error : maintenance.usagePct >= 70 ? C.rateLimit : C.value },
634
+ { label: 'remaining', value: maintenance.remainingTokens.toLocaleString(), valueColor: C.value },
635
+ ], { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }));
636
+ for (const reason of maintenance.reasons.slice(0, 3)) {
637
+ maintenanceLines.push(...buildBodyText(width, reason, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.dim));
638
+ }
639
+ if (maintenance.nextSteps.length > 0) {
640
+ maintenanceLines.push(...buildBodyText(width, `Next: ${maintenance.nextSteps.join(' | ')}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.title));
641
+ }
642
+ if (selectedName) {
643
+ const status = selectedHealth?.status ?? 'unknown';
644
+ selectedLines.push(buildKeyValueLine(width, [
645
+ { label: 'provider', value: selectedName, valueColor: C.provName },
646
+ { label: 'status', value: statusLabel(status), valueColor: statusDot(status).color },
647
+ { label: 'last ok', value: fmtAgo(selectedHealth?.lastSuccessAt), valueColor: C.value },
648
+ ], { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }));
649
+ if (selectedHealth?.rateLimitExpiresAt && selectedHealth.rateLimitExpiresAt > Date.now()) {
650
+ selectedLines.push(...buildBodyText(width, `Cooldown: ${fmtCooldown(selectedHealth.rateLimitExpiresAt)}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.rateLimit));
651
+ }
652
+ if (selectedHealth?.lastErrorMessage) {
653
+ selectedLines.push(...buildBodyText(width, `Last error: ${selectedHealth.lastErrorMessage}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.errMsg));
654
+ }
655
+ if (selectedAccount) {
656
+ selectedLines.push(buildKeyValueLine(width, [
657
+ { label: 'route', value: selectedAccount.activeRoute, valueColor: routeColor(selectedAccount.activeRoute) },
658
+ { label: 'preferred', value: selectedAccount.preferredRoute, valueColor: C.dim },
659
+ { label: 'freshness', value: selectedAccount.authFreshness, valueColor: freshnessColor(selectedAccount.authFreshness) },
660
+ ], { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }));
661
+ selectedLines.push(buildKeyValueLine(width, [
662
+ { label: 'models', value: String(selectedAccount.modelCount), valueColor: C.value },
663
+ { label: 'active', value: selectedAccount.active ? 'yes' : 'no', valueColor: selectedAccount.active ? C.online : C.dim },
664
+ ], { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }));
665
+ selectedLines.push(...buildBodyText(width, `Auth route: ${selectedAccount.activeRouteReason}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.dim));
666
+ if (selectedAccount.fallbackRisk) {
667
+ selectedLines.push(...buildBodyText(width, `Fallback: ${selectedAccount.fallbackRisk}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.rateLimit));
668
+ }
669
+ if (selectedAccount.issues.length > 0) {
670
+ selectedLines.push(...buildBodyText(width, `Issue: ${selectedAccount.issues[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.errMsg));
671
+ }
672
+ if (selectedAccount.recommendedActions.length > 0) {
673
+ selectedLines.push(...buildBodyText(width, `Next: ${selectedAccount.recommendedActions[0]!}`, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, C.title));
674
+ }
675
+ }
676
+ }
677
+
678
+ const postureSection: PanelWorkspaceSection = { lines: buildSummaryBlock(width, 'Health posture', postureLines, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }) };
679
+ const domainsSection: PanelWorkspaceSection = { title: 'Repair Domains', lines: domainLines };
680
+ const maintenanceSections = maintenanceLines.length > 0 ? [{ title: 'Session Maintenance', lines: maintenanceLines } satisfies PanelWorkspaceSection] : [];
681
+ const selectedSections = selectedLines.length > 0 ? [{ lines: buildDetailBlock(width, 'Selected provider', selectedLines, { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }) } satisfies PanelWorkspaceSection] : [];
682
+ const resolvedProvidersSection = resolvePrimaryScrollableSection(width, height, {
683
+ intro,
684
+ footerLines: [buildPanelLine(width, [[' j/k or Up/Down move live cooldowns refresh while active', C.dim]])],
685
+ palette: { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' },
686
+ beforeSections: [postureSection, domainsSection, ...maintenanceSections],
687
+ section: {
688
+ title: 'Providers',
689
+ scrollableLines: providers.map((name, absolute) => {
690
+ const health = this.providerHealthTracker.get(name);
691
+ const status = health?.status ?? 'unknown';
692
+ const latency = health?.lastLatencyMs !== undefined ? fmtMs(health.lastLatencyMs) : 'n/a';
693
+ const latencyFg = health?.lastLatencyMs !== undefined ? latencyColor(health.lastLatencyMs) : C.dim;
694
+ return buildPanelListRow(width, [
695
+ { text: name.padEnd(16), fg: C.provName },
696
+ { text: statusLabel(status).padEnd(14), fg: statusDot(status).color },
697
+ { text: ' lat ', fg: C.label },
698
+ { text: latency.padEnd(8), fg: latencyFg },
699
+ { text: ' ok ', fg: C.label },
700
+ { text: fmtAgo(health?.lastSuccessAt).padEnd(10), fg: C.value },
701
+ ], { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' }, { selected: absolute === this._selectedIndex, selectedBg: '#111827' });
702
+ }),
703
+ selectedIndex: this._selectedIndex,
704
+ scrollOffset: this._scrollOffset,
705
+ guardRows: 1,
706
+ minRows: 4,
707
+ appendWindowSummary: { dimColor: C.dim },
708
+ },
709
+ afterSections: selectedSections,
710
+ });
711
+ this._scrollOffset = resolvedProvidersSection.scrollOffset;
712
+ const sections: PanelWorkspaceSection[] = [
713
+ postureSection,
714
+ domainsSection,
715
+ ...maintenanceSections,
716
+ resolvedProvidersSection.section,
717
+ ...selectedSections,
718
+ ];
719
+ return buildPanelWorkspace(width, height, {
720
+ title: 'Health',
721
+ intro,
722
+ sections,
723
+ footerLines: [buildPanelLine(width, [[' j/k or Up/Down move live cooldowns refresh while active', C.dim]])],
724
+ palette: { ...DEFAULT_PANEL_PALETTE, header: C.title, headerBg: '#0f172a' },
725
+ });
726
+ }
727
+ }