@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,400 @@
1
+ // ---------------------------------------------------------------------------
2
+ // SessionBrowserPanel — browse, search, and resume old sessions.
3
+ // ---------------------------------------------------------------------------
4
+
5
+ import type { Line } from '../types/grid.ts';
6
+ import { BasePanel } from './base-panel.ts';
7
+ import type { SessionInfo } from '@pellux/goodvibes-sdk/platform/sessions';
8
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
9
+ import type { SessionBrowserQuery } from '../runtime/ui-service-queries.ts';
10
+ import {
11
+ buildEmptyState,
12
+ buildPanelLine,
13
+ buildSearchInputLine,
14
+ buildStyledPanelLine,
15
+ buildPanelWorkspace,
16
+ resolveScrollablePanelSection,
17
+ DEFAULT_PANEL_PALETTE,
18
+ type PanelWorkspaceSection,
19
+ } from './polish.ts';
20
+ import { truncateDisplay } from '../utils/terminal-width.ts';
21
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
22
+ import {
23
+ getPanelSearchFocusTransition,
24
+ isPanelSearchBackspace,
25
+ isPanelSearchCancel,
26
+ isPanelSearchCommit,
27
+ isPanelSearchPrintable,
28
+ } from './search-focus.ts';
29
+ import { type ConfirmState, handleConfirmInput } from './confirm-state.ts';
30
+
31
+ const C = {
32
+ headerBg: '#1a1a2e',
33
+ headerFg: '#ffffff',
34
+ statusBar: '#222233',
35
+ statusFg: '#aaaaaa',
36
+ selected: '#00ffff',
37
+ selectedBg: '#1a2a3a',
38
+ normal: '#ccccdd',
39
+ dim: '#555566',
40
+ label: '#8888bb',
41
+ value: '#ccccdd',
42
+ dateFg: '#6699aa',
43
+ modelFg: '#99aacc',
44
+ countFg: '#88bbcc',
45
+ warnFg: '#ffcc44',
46
+ errorFg: '#ff6666',
47
+ separator: '#333355',
48
+ } as const;
49
+
50
+ function shortDate(ts: number): string {
51
+ const d = new Date(ts);
52
+ const Y = d.getFullYear();
53
+ const M = String(d.getMonth() + 1).padStart(2, '0');
54
+ const D = String(d.getDate()).padStart(2, '0');
55
+ const h = String(d.getHours()).padStart(2, '0');
56
+ const m = String(d.getMinutes()).padStart(2, '0');
57
+ return `${Y}-${M}-${D} ${h}:${m}`;
58
+ }
59
+
60
+ function formatReturnContextLines(returnContext: SessionInfo['returnContext']): string[] {
61
+ if (!returnContext) return [];
62
+ const lines: string[] = [];
63
+ if (returnContext.activityLabel) lines.push(`activity: ${returnContext.activityLabel}`);
64
+ if (returnContext.statusLabel) lines.push(`status: ${returnContext.statusLabel}`);
65
+ if (returnContext.activeTasks || returnContext.blockedTasks || returnContext.pendingApprovals) {
66
+ lines.push(`tasks: active=${returnContext.activeTasks ?? 0} blocked=${returnContext.blockedTasks ?? 0} approvals=${returnContext.pendingApprovals ?? 0}`);
67
+ }
68
+ if (returnContext.remoteRunners?.length) {
69
+ lines.push(`remote runners: ${returnContext.remoteRunners.join(', ')}`);
70
+ }
71
+ if (returnContext.worktreePaths?.length) {
72
+ lines.push(`worktrees: ${returnContext.worktreePaths.join(', ')}`);
73
+ }
74
+ if (returnContext.openPanels?.length) {
75
+ lines.push(`open panels: ${returnContext.openPanels.join(', ')}`);
76
+ }
77
+ return lines;
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Confirmation state for deletion
82
+ // ---------------------------------------------------------------------------
83
+ // ConfirmState<string> — subject holds the session name to delete
84
+
85
+ export class SessionBrowserPanel extends BasePanel {
86
+ private sessions: SessionInfo[] = [];
87
+ private filtered: SessionInfo[] = [];
88
+ private searchQuery = '';
89
+ private searching = false; // true when user is actively typing a search
90
+ private cursorIndex = 0;
91
+ private scrollOffset = 0;
92
+ private confirm: ConfirmState<string> | null = null;
93
+ private deleteError = '';
94
+ private loadError = '';
95
+ private refreshTimerId: ReturnType<typeof setInterval> | null = null;
96
+
97
+ constructor(
98
+ private readonly sessionManager: SessionBrowserQuery,
99
+ private resumeSession?: (sessionId: string) => void,
100
+ ) {
101
+ super('sessions', 'Sessions', 'H', 'session');
102
+ }
103
+
104
+ override onActivate(): void {
105
+ super.onActivate();
106
+ this._load();
107
+ this.refreshTimerId = this.registerTimer(setInterval(() => { this._load(); }, 5000));
108
+ }
109
+
110
+ override onDeactivate(): void {
111
+ if (this.refreshTimerId !== null) { this.clearTimer(this.refreshTimerId); this.refreshTimerId = null; }
112
+ this.searching = false;
113
+ this.confirm = null;
114
+ super.onDeactivate();
115
+ }
116
+
117
+ handleInput(key: string): boolean {
118
+ // Confirmation dialog — use shared handleConfirmInput for y/n/Esc UX
119
+ const confirmResult = handleConfirmInput(this.confirm, key);
120
+ if (confirmResult === 'confirmed') {
121
+ this._deleteConfirmed();
122
+ return true;
123
+ }
124
+ if (confirmResult === 'cancelled') {
125
+ this.confirm = null;
126
+ this.markDirty();
127
+ return true;
128
+ }
129
+ if (confirmResult === 'absorbed') return true;
130
+
131
+ // Search mode
132
+ if (this.searching) {
133
+ const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.cursorIndex, itemCount: this.filtered.length });
134
+ if (transition === 'focus-list') {
135
+ this.searching = false;
136
+ this.cursorIndex = 0;
137
+ this.markDirty();
138
+ return true;
139
+ }
140
+ if (isPanelSearchCancel(key) || isPanelSearchCommit(key)) {
141
+ this.searching = false;
142
+ this.markDirty();
143
+ return true;
144
+ }
145
+ if (isPanelSearchBackspace(key)) {
146
+ this.searchQuery = this.searchQuery.slice(0, -1);
147
+ this._filter();
148
+ return true;
149
+ }
150
+ if (isPanelSearchPrintable(key)) {
151
+ this.searchQuery += key;
152
+ this._filter();
153
+ return true;
154
+ }
155
+ return false;
156
+ }
157
+
158
+ const transition = getPanelSearchFocusTransition(key, { selectedIndex: this.cursorIndex, itemCount: this.filtered.length });
159
+ if (transition === 'focus-search') {
160
+ this._startSearch();
161
+ return true;
162
+ }
163
+
164
+ switch (key) {
165
+ case 'up': this._move(-1); return true;
166
+ case 'down': this._move(1); return true;
167
+ case 'pageup': this._move(-10); return true;
168
+ case 'pagedown': this._move(10); return true;
169
+ case 'return': this._resume(); return true;
170
+ case 'd': this._promptDelete(); return true;
171
+ case 'r': this._load(); return true;
172
+ default: return false;
173
+ }
174
+ }
175
+
176
+ render(width: number, height: number): Line[] {
177
+ if (height <= 0 || width <= 0) return [];
178
+ const intro = 'Browse, search, resume, and prune saved conversations.';
179
+
180
+ const count = this.filtered.length;
181
+ const total = this.sessions.length;
182
+ const searchLine = this.searching
183
+ ? ` Search: ${this.searchQuery}_`
184
+ : this.loadError
185
+ ? ` Error: ${this.loadError}`
186
+ : this.deleteError
187
+ ? ` Error: ${this.deleteError}`
188
+ : this.searchQuery
189
+ ? ` Filter: ${this.searchQuery} (/ or up at top to edit)`
190
+ : ` / or up at top to search Enter: resume d: delete r: refresh`;
191
+ const statusFg = this.loadError || this.deleteError ? DEFAULT_PANEL_PALETTE.bad : this.searching ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim;
192
+ const footerLines = [
193
+ buildSearchInputLine(width, '', searchLine.trimStart(), DEFAULT_PANEL_PALETTE, { active: this.searching, valueColor: statusFg }),
194
+ ];
195
+
196
+ if (this.confirm) {
197
+ return buildPanelWorkspace(width, height, {
198
+ title: ` Sessions [${count}/${total}]`,
199
+ intro,
200
+ sections: [
201
+ {
202
+ title: 'Confirmation',
203
+ lines: [
204
+ buildPanelLine(width, [[` Delete "${this.confirm.subject}"?`, DEFAULT_PANEL_PALETTE.warn]]),
205
+ buildPanelLine(width, [[' y', DEFAULT_PANEL_PALETTE.info], [' confirm delete', DEFAULT_PANEL_PALETTE.dim], [' n / Esc', DEFAULT_PANEL_PALETTE.info], [' cancel', DEFAULT_PANEL_PALETTE.dim]]),
206
+ ],
207
+ },
208
+ ],
209
+ footerLines,
210
+ palette: DEFAULT_PANEL_PALETTE,
211
+ });
212
+ }
213
+
214
+ if (this.filtered.length === 0) {
215
+ const emptyTitle = this.searchQuery ? ` No sessions match "${this.searchQuery}"` : ' No sessions found';
216
+ const emptyBody = this.searchQuery
217
+ ? 'Clear or change the current filter to surface saved conversations again.'
218
+ : 'Conversations are saved automatically. Once you have saved sessions, they appear here for review and resume.';
219
+ return buildPanelWorkspace(width, height, {
220
+ title: ` Sessions [${count}/${total}]`,
221
+ intro,
222
+ sections: [
223
+ {
224
+ lines: buildEmptyState(width, emptyTitle, emptyBody, [], DEFAULT_PANEL_PALETTE),
225
+ },
226
+ ],
227
+ footerLines,
228
+ palette: DEFAULT_PANEL_PALETTE,
229
+ });
230
+ }
231
+
232
+ this.cursorIndex = Math.max(0, Math.min(this.cursorIndex, this.filtered.length - 1));
233
+ const summary: PanelWorkspaceSection = {
234
+ title: 'Summary',
235
+ lines: [
236
+ buildPanelLine(width, [
237
+ [' Sessions ', DEFAULT_PANEL_PALETTE.label],
238
+ [String(total), DEFAULT_PANEL_PALETTE.value],
239
+ [' Visible ', DEFAULT_PANEL_PALETTE.label],
240
+ [String(count), DEFAULT_PANEL_PALETTE.info],
241
+ [' Search ', DEFAULT_PANEL_PALETTE.label],
242
+ [this.searchQuery || 'none', this.searchQuery ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
243
+ ]),
244
+ ],
245
+ };
246
+
247
+ const selected = this.filtered[this.cursorIndex];
248
+ const selectedSection: PanelWorkspaceSection = selected
249
+ ? {
250
+ title: 'Selected',
251
+ lines: [
252
+ buildPanelLine(width, [[' Title ', DEFAULT_PANEL_PALETTE.label], [selected.title || selected.name || '(untitled)', DEFAULT_PANEL_PALETTE.value]]),
253
+ buildPanelLine(width, [[' Model ', DEFAULT_PANEL_PALETTE.label], [selected.model || 'unknown', DEFAULT_PANEL_PALETTE.info], [' Title ', DEFAULT_PANEL_PALETTE.label], [selected.titleSource === 'user' ? 'user-set' : 'system', selected.titleSource === 'user' ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.dim]]),
254
+ buildPanelLine(width, [[' Date ', DEFAULT_PANEL_PALETTE.label], [shortDate(selected.timestamp), DEFAULT_PANEL_PALETTE.value], [' Messages ', DEFAULT_PANEL_PALETTE.label], [String(selected.messageCount), DEFAULT_PANEL_PALETTE.value]]),
255
+ buildPanelLine(width, [
256
+ [' Tasks ', DEFAULT_PANEL_PALETTE.label],
257
+ [String(selected.returnContext?.activeTasks ?? 0), (selected.returnContext?.activeTasks ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
258
+ [' Blocked ', DEFAULT_PANEL_PALETTE.label],
259
+ [String(selected.returnContext?.blockedTasks ?? 0), (selected.returnContext?.blockedTasks ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
260
+ [' Approvals ', DEFAULT_PANEL_PALETTE.label],
261
+ [String(selected.returnContext?.pendingApprovals ?? 0), (selected.returnContext?.pendingApprovals ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
262
+ ]),
263
+ buildPanelLine(width, [
264
+ [' Remote ', DEFAULT_PANEL_PALETTE.label],
265
+ [String(selected.returnContext?.remoteRunners?.length ?? 0), (selected.returnContext?.remoteRunners?.length ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
266
+ [' Worktrees ', DEFAULT_PANEL_PALETTE.label],
267
+ [String(selected.returnContext?.worktreePaths?.length ?? 0), (selected.returnContext?.worktreePaths?.length ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.info : DEFAULT_PANEL_PALETTE.dim],
268
+ [' Panels ', DEFAULT_PANEL_PALETTE.label],
269
+ [String(selected.returnContext?.openPanels?.length ?? 0), (selected.returnContext?.openPanels?.length ?? 0) > 0 ? DEFAULT_PANEL_PALETTE.good : DEFAULT_PANEL_PALETTE.dim],
270
+ ]),
271
+ ...formatReturnContextLines(selected.returnContext).map((line) =>
272
+ buildPanelLine(width, [[' ', DEFAULT_PANEL_PALETTE.dim], [truncateDisplay(line, Math.max(0, width - 2)), DEFAULT_PANEL_PALETTE.dim]])
273
+ ),
274
+ buildPanelLine(width, [[' Next ', DEFAULT_PANEL_PALETTE.label], [selected.returnContext?.remoteRunners?.length ? `/remote recover ${selected.returnContext.remoteRunners[0]}` : '/session resume', DEFAULT_PANEL_PALETTE.dim]]),
275
+ ],
276
+ }
277
+ : { title: 'Selected', lines: [] };
278
+
279
+ const sessionsSection = resolveScrollablePanelSection(width, height, {
280
+ intro,
281
+ footerLines,
282
+ palette: DEFAULT_PANEL_PALETTE,
283
+ beforeSections: [summary],
284
+ section: {
285
+ title: 'Sessions',
286
+ scrollableLines: this.filtered.map((sess, index) =>
287
+ this._renderSession(width, sess, index === this.cursorIndex),
288
+ ),
289
+ selectedIndex: this.cursorIndex,
290
+ scrollOffset: this.scrollOffset,
291
+ minRows: 6,
292
+ },
293
+ afterSections: [selectedSection],
294
+ });
295
+ this.scrollOffset = sessionsSection.scrollOffset;
296
+
297
+ return buildPanelWorkspace(width, height, {
298
+ title: ` Sessions [${count}/${total}]`,
299
+ intro,
300
+ sections: [
301
+ summary,
302
+ sessionsSection.section,
303
+ selectedSection,
304
+ ],
305
+ footerLines,
306
+ palette: DEFAULT_PANEL_PALETTE,
307
+ });
308
+ }
309
+
310
+ private _renderSession(width: number, sess: SessionInfo, isCursor: boolean): Line {
311
+ const bg = isCursor ? C.selectedBg : '';
312
+ const date = shortDate(sess.timestamp);
313
+ const cnt = String(sess.messageCount).padStart(3) + 'm ';
314
+ const model = (sess.model || 'unknown').slice(0, 18).padEnd(18) + ' ';
315
+ const prefixLength = 1 + 16 + 1 + 4 + 19;
316
+ const title = truncateDisplay(sess.title || sess.name || '(untitled)', Math.max(0, width - prefixLength));
317
+ return buildStyledPanelLine(width, [
318
+ { text: isCursor ? '▸' : ' ', fg: C.selected, bg, bold: isCursor },
319
+ { text: date, fg: C.dateFg, bg },
320
+ { text: ' ', fg: C.normal, bg },
321
+ { text: cnt, fg: C.countFg, bg },
322
+ { text: model, fg: C.modelFg, bg },
323
+ { text: title, fg: isCursor ? C.selected : C.normal, bg, bold: isCursor },
324
+ ]);
325
+ }
326
+
327
+ private _load(): void {
328
+ try {
329
+ this.sessions = this.sessionManager.list();
330
+ this._filter();
331
+ this.loadError = '';
332
+ this.markDirty();
333
+ } catch (e) {
334
+ logger.debug('SessionBrowserPanel._load failed', { error: summarizeError(e) });
335
+ this.loadError = 'Failed to load sessions';
336
+ this.markDirty();
337
+ }
338
+ }
339
+
340
+ private _filter(): void {
341
+ if (!this.searchQuery.trim()) {
342
+ this.filtered = [...this.sessions];
343
+ } else {
344
+ const q = this.searchQuery.toLowerCase();
345
+ try {
346
+ const results = this.sessionManager.search(q);
347
+ const names = new Set(results.map(r => r.session.name));
348
+ this.filtered = this.sessions.filter(s => names.has(s.name));
349
+ } catch (e) {
350
+ logger.debug('SessionBrowserPanel._filter search failed, falling back', { error: summarizeError(e) });
351
+ this.filtered = this.sessions.filter(s =>
352
+ (s.title || '').toLowerCase().includes(q) ||
353
+ (s.model || '').toLowerCase().includes(q) ||
354
+ s.name.toLowerCase().includes(q)
355
+ );
356
+ }
357
+ }
358
+ this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.filtered.length - 1));
359
+ this.markDirty();
360
+ }
361
+
362
+ private _startSearch(): void {
363
+ this.searching = true;
364
+ this.markDirty();
365
+ }
366
+
367
+ private _move(delta: number): void {
368
+ if (this.filtered.length === 0) return;
369
+ this.cursorIndex = Math.max(0, Math.min(this.filtered.length - 1, this.cursorIndex + delta));
370
+ this.markDirty();
371
+ }
372
+
373
+ private _resume(): void {
374
+ const sess = this.filtered[this.cursorIndex];
375
+ if (!sess) return;
376
+ this.resumeSession?.(sess.name);
377
+ }
378
+
379
+ private _promptDelete(): void {
380
+ const sess = this.filtered[this.cursorIndex];
381
+ if (!sess) return;
382
+ this.confirm = { subject: sess.name, label: sess.name };
383
+ this.markDirty();
384
+ }
385
+
386
+ private _deleteConfirmed(): void {
387
+ if (!this.confirm) return;
388
+ const name = this.confirm.subject;
389
+ this.confirm = null;
390
+ try {
391
+ this.sessionManager.delete(name);
392
+ this.deleteError = '';
393
+ this._load();
394
+ } catch (e) {
395
+ logger.debug('SessionBrowserPanel._deleteConfirmed failed', { error: summarizeError(e) });
396
+ this.deleteError = `Delete failed: ${name}`;
397
+ }
398
+ this.markDirty();
399
+ }
400
+ }
@@ -0,0 +1,125 @@
1
+ export type PanelGuidanceMode = 'off' | 'minimal' | 'guided';
2
+ export type PanelSessionMaintenanceLevel = 'stable' | 'watch' | 'suggest-compact' | 'compacting' | 'needs-repair' | 'unknown';
3
+
4
+ export interface PanelSessionMaintenanceLineageEntry {
5
+ readonly branchReason?: string;
6
+ }
7
+
8
+ export interface PanelSessionMaintenanceSession {
9
+ readonly lineage?: readonly PanelSessionMaintenanceLineageEntry[];
10
+ readonly lastCompactedAt?: number;
11
+ readonly compactionMessageCount?: number;
12
+ }
13
+
14
+ export interface PanelSessionMaintenanceInput {
15
+ readonly currentTokens: number;
16
+ readonly contextWindow: number;
17
+ readonly messageCount?: number;
18
+ readonly sessionMemoryCount?: number;
19
+ readonly session?: PanelSessionMaintenanceSession | null;
20
+ }
21
+
22
+ export interface PanelSessionMaintenanceStatus {
23
+ readonly level: PanelSessionMaintenanceLevel;
24
+ readonly summary: string;
25
+ readonly reasons: readonly string[];
26
+ readonly nextSteps: readonly string[];
27
+ readonly guidanceMode: PanelGuidanceMode;
28
+ readonly usagePct: number;
29
+ readonly remainingTokens: number;
30
+ readonly thresholdPct: number;
31
+ readonly autoCompactEnabled: boolean;
32
+ readonly sessionMemoryCount: number;
33
+ readonly compactionCount: number;
34
+ readonly lastCompactedAt?: number;
35
+ readonly compactRecommended: boolean;
36
+ }
37
+
38
+ export function evaluateSessionMaintenance(input: PanelSessionMaintenanceInput): PanelSessionMaintenanceStatus {
39
+ const guidanceMode: PanelGuidanceMode = 'minimal';
40
+ const thresholdPct = 80;
41
+ const autoCompactEnabled = false;
42
+ const usagePct = input.contextWindow > 0 ? Math.min(100, Math.round((Math.max(0, input.currentTokens) / input.contextWindow) * 100)) : 0;
43
+ const remainingTokens = Math.max(0, input.contextWindow - input.currentTokens);
44
+ const sessionMemoryCount = Math.max(0, input.sessionMemoryCount ?? 0);
45
+ const compactionCount = Math.max(0, input.session?.lineage?.filter((entry) => entry.branchReason === 'compaction').length ?? 0);
46
+ const lastCompactedAt = input.session?.lastCompactedAt;
47
+ const messageCount = Math.max(0, input.messageCount ?? 0);
48
+ const staleByMessageGrowth = compactionCount > 0
49
+ ? messageCount - (input.session?.compactionMessageCount ?? 0) >= 12
50
+ : messageCount >= 24;
51
+ if (input.contextWindow <= 0) {
52
+ return {
53
+ level: 'unknown',
54
+ summary: 'Context window unavailable.',
55
+ reasons: ['Current model does not expose a known context limit yet.'],
56
+ nextSteps: ['/provider', '/context'],
57
+ guidanceMode,
58
+ usagePct,
59
+ remainingTokens,
60
+ thresholdPct,
61
+ autoCompactEnabled,
62
+ sessionMemoryCount,
63
+ compactionCount,
64
+ lastCompactedAt,
65
+ compactRecommended: false,
66
+ };
67
+ }
68
+
69
+ const reasons: string[] = [];
70
+ const nextSteps: string[] = [];
71
+ let summary = 'Session maintenance is stable.';
72
+ let compactRecommended = false;
73
+ let level: PanelSessionMaintenanceLevel = 'stable';
74
+
75
+ if (usagePct >= 90) {
76
+ level = 'needs-repair';
77
+ summary = `Compact now to recover context headroom (${usagePct}% used).`;
78
+ reasons.push(`Context pressure is high at ${usagePct}% usage.`);
79
+ nextSteps.push('/compact', '/panel tokens');
80
+ compactRecommended = true;
81
+ } else if (usagePct >= thresholdPct || remainingTokens <= 15_000) {
82
+ level = 'suggest-compact';
83
+ summary = `Watch context growth (${usagePct}% used).`;
84
+ reasons.push(`Context pressure is climbing at ${usagePct}% usage.`);
85
+ nextSteps.push('/panel tokens');
86
+ compactRecommended = true;
87
+ } else if (usagePct >= 70 || staleByMessageGrowth) {
88
+ level = 'watch';
89
+ summary = staleByMessageGrowth
90
+ ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
91
+ : `Watch context growth (${usagePct}% used, threshold ${thresholdPct}%).`;
92
+ reasons.push(staleByMessageGrowth
93
+ ? `Conversation has grown ${messageCount.toLocaleString()} messages since the last maintenance checkpoint.`
94
+ : `Context usage is climbing toward the ${thresholdPct}% maintenance threshold.`);
95
+ nextSteps.push('/panel tokens');
96
+ } else {
97
+ reasons.push('Context pressure is currently within the stable operating band.');
98
+ }
99
+
100
+ if (sessionMemoryCount > 0) {
101
+ reasons.push(`${sessionMemoryCount} pinned session memor${sessionMemoryCount === 1 ? 'y is' : 'ies are'} preserved during compaction.`);
102
+ }
103
+ if (compactionCount > 0) {
104
+ reasons.push(`Last compaction ran ${lastCompactedAt ? new Date(lastCompactedAt).toISOString() : 'recently'}.`);
105
+ }
106
+ if (!autoCompactEnabled) {
107
+ reasons.push('Auto-compact is currently disabled; use /compact when you need to recover headroom.');
108
+ }
109
+
110
+ return {
111
+ level,
112
+ summary,
113
+ reasons,
114
+ nextSteps,
115
+ guidanceMode,
116
+ usagePct,
117
+ remainingTokens,
118
+ thresholdPct,
119
+ autoCompactEnabled,
120
+ sessionMemoryCount,
121
+ compactionCount,
122
+ lastCompactedAt,
123
+ compactRecommended,
124
+ };
125
+ }
@@ -0,0 +1,120 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
+ import {
4
+ buildDetailBlock,
5
+ buildGuidanceLine,
6
+ buildPanelListRow,
7
+ buildPanelLine,
8
+ buildStatusPill,
9
+ buildSummaryBlock,
10
+ DEFAULT_PANEL_PALETTE,
11
+ type PanelPalette,
12
+ } from './polish.ts';
13
+ import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
14
+ import type { ConfigManager } from '../config/index.ts';
15
+
16
+ const C = {
17
+ ...DEFAULT_PANEL_PALETTE,
18
+ dim: '#475569',
19
+ info: '#38bdf8',
20
+ ok: '#22c55e',
21
+ warn: '#eab308',
22
+ error: '#ef4444',
23
+ } as const;
24
+
25
+ type ResolvedEntry = ReturnType<typeof getSettingsControlPlaneSnapshot>['resolvedEntries'][number];
26
+
27
+ export class SettingsSyncPanel extends ScrollableListPanel<ResolvedEntry> {
28
+ public constructor(private readonly configManager: ConfigManager) {
29
+ super('settings-sync', 'Settings Sync', 'S', 'monitoring');
30
+ this.showSelectionGutter = true; // I5: non-color selection affordance
31
+ }
32
+
33
+ protected override getPalette(): PanelPalette {
34
+ return C;
35
+ }
36
+
37
+ protected getItems(): readonly ResolvedEntry[] {
38
+ return getSettingsControlPlaneSnapshot(this.configManager).resolvedEntries;
39
+ }
40
+
41
+ protected renderItem(entry: ResolvedEntry, _index: number, selected: boolean, width: number): Line {
42
+ return buildPanelListRow(width, [
43
+ { text: entry.key.padEnd(32), fg: C.value },
44
+ { text: ` ${entry.effectiveSource}`.padEnd(11), fg: entry.effectiveSource === 'managed' ? C.warn : entry.effectiveSource === 'synced' ? C.ok : entry.effectiveSource === 'local' ? C.info : C.dim },
45
+ { text: `${String(entry.effectiveValue)}`.slice(0, Math.max(0, width - 47)), fg: entry.locked ? C.warn : C.dim },
46
+ ], C, { selected });
47
+ }
48
+
49
+ protected override getEmptyStateMessage(): string {
50
+ return ' No resolved settings entries.';
51
+ }
52
+
53
+ public render(width: number, height: number): Line[] {
54
+ const snapshot = getSettingsControlPlaneSnapshot(this.configManager);
55
+
56
+ const postureLines: Line[] = [
57
+ buildPanelLine(width, [[' resolved keys ', C.label], [String(snapshot.resolvedEntries.length), C.value], [' conflicts ', C.label], ...buildStatusPill(snapshot.conflicts.length > 0 ? 'bad' : 'good', String(snapshot.conflicts.length)), [' failures ', C.label], ...buildStatusPill(snapshot.recentFailures.length > 0 ? 'warn' : 'good', String(snapshot.recentFailures.length))]),
58
+ buildPanelLine(width, [[' managed locks ', C.label], [String(snapshot.managedLockCount), snapshot.managedLockCount > 0 ? C.warn : C.dim], [' staged bundle ', C.label], [snapshot.stagedManagedBundle ? snapshot.stagedManagedBundle.profileName : 'none', snapshot.stagedManagedBundle ? C.info : C.dim]]),
59
+ buildGuidanceLine(width, '/settingssync conflicts', 'review conflicting synced values before they silently shape effective configuration', C),
60
+ buildGuidanceLine(width, '/managed review', 'inspect staged managed changes, risk posture, and rollback records', C),
61
+ ];
62
+
63
+ const headerLines: Line[] = [
64
+ ...buildSummaryBlock(width, 'Settings posture', postureLines, C),
65
+ buildPanelLine(width, [[' local typed config ', C.label], [String(snapshot.liveKeyCount), C.value], [' saved profiles ', C.label], [String(snapshot.profileCount), C.info], [' managed locks ', C.label], [String(snapshot.managedLockCount), snapshot.managedLockCount > 0 ? C.warn : C.dim]]),
66
+ buildPanelLine(width, [[' effective local ', C.label], [String(snapshot.resolvedCounts.local), C.info], [' synced ', C.label], [String(snapshot.resolvedCounts.synced), snapshot.resolvedCounts.synced > 0 ? C.ok : C.dim], [' managed ', C.label], [String(snapshot.resolvedCounts.managed), snapshot.resolvedCounts.managed > 0 ? C.warn : C.dim]]),
67
+ buildPanelLine(width, [[' last sync ', C.label], [snapshot.lastSync ? `${snapshot.lastSync.surface}/${snapshot.lastSync.direction}` : 'none', snapshot.lastSync ? C.ok : C.dim], [' when ', C.label], [snapshot.lastSync ? new Date(snapshot.lastSync.timestamp).toLocaleString() : 'n/a', C.dim]]),
68
+ // Staged Bundle
69
+ ...(snapshot.stagedManagedBundle
70
+ ? [
71
+ buildPanelLine(width, [[' profile ', C.label], [snapshot.stagedManagedBundle.profileName, C.value], [' risk ', C.label], [snapshot.stagedManagedBundle.risk, snapshot.stagedManagedBundle.risk === 'high' ? C.error : snapshot.stagedManagedBundle.risk === 'medium' ? C.warn : C.ok], [' changes ', C.label], [String(snapshot.stagedManagedBundle.changeCount), C.info]]),
72
+ buildPanelLine(width, [[' path ', C.label], [snapshot.stagedManagedBundle.path.slice(0, Math.max(0, width - 9)), C.dim]]),
73
+ ]
74
+ : [buildPanelLine(width, [[' No staged managed settings bundle.', C.dim]])]),
75
+ // Recent Events
76
+ ...(snapshot.recentEvents.length > 0
77
+ ? snapshot.recentEvents.map((event) => buildPanelLine(width, [[` ${event.surface}/${event.direction}`.padEnd(18), C.info], [` ${event.detail}`.slice(0, Math.max(0, width - 20)), C.dim]]))
78
+ : [buildPanelLine(width, [[' No sync or managed-setting events recorded yet.', C.dim]])]),
79
+ // Managed Locks
80
+ ...(snapshot.managedLocks.length > 0
81
+ ? snapshot.managedLocks.slice(0, 10).map((lock) => buildPanelLine(width, [[` ${lock.key}`.padEnd(30), C.value], [` source=${lock.source}`.padEnd(24), C.info], [` ${lock.reason}`.slice(0, Math.max(0, width - 56)), C.dim]]))
82
+ : [buildPanelLine(width, [[' No managed locks are currently active.', C.dim]])]),
83
+ // Failures
84
+ ...(snapshot.recentFailures.length > 0
85
+ ? snapshot.recentFailures.map((failure) => buildPanelLine(width, [[` ${failure.surface}`.padEnd(10), C.error], [` ${failure.message}`.slice(0, Math.max(0, width - 12)), C.dim]]))
86
+ : [buildPanelLine(width, [[' No recent sync or managed-setting failures.', C.dim]])]),
87
+ // Conflicts
88
+ ...(snapshot.conflicts.length > 0
89
+ ? snapshot.conflicts.map((conflict) => buildPanelLine(width, [[` ${conflict.key}`.padEnd(30), C.value], [` ${conflict.source}`.padEnd(10), C.warn], [` resolve: /settingssync resolve ${conflict.key} local|synced`.slice(0, Math.max(0, width - 42)), C.dim]]))
90
+ : [buildPanelLine(width, [[' No settings conflicts detected.', C.dim]])]),
91
+ // Rollback History
92
+ ...(snapshot.rollbackHistory.length > 0
93
+ ? snapshot.rollbackHistory.map((entry) => buildPanelLine(width, [[` ${entry.token}`.padEnd(18), C.info], [` ${entry.profileName}`.padEnd(18), C.value], [` restored=${String(entry.restoredKeys.length).padEnd(4)}`, C.warn], [` ${new Date(entry.appliedAt).toLocaleString()}`.slice(0, Math.max(0, width - 46)), C.dim]]))
94
+ : [buildPanelLine(width, [[' No managed rollback records yet.', C.dim]])]),
95
+ ];
96
+
97
+ this.clampSelection();
98
+ const selectedEntry = snapshot.resolvedEntries[this.selectedIndex];
99
+ const footerLines: Line[] = [
100
+ ...(selectedEntry
101
+ ? buildDetailBlock(width, 'Selected setting', [
102
+ buildPanelLine(width, [[' key ', C.label], [selectedEntry.key, C.value], [' category ', C.label], [selectedEntry.category, C.info]]),
103
+ buildPanelLine(width, [[' effective ', C.label], [selectedEntry.effectiveSource, selectedEntry.effectiveSource === 'managed' ? C.warn : selectedEntry.effectiveSource === 'synced' ? C.ok : selectedEntry.effectiveSource === 'local' ? C.info : C.dim], [' locked ', C.label], [selectedEntry.locked ? 'yes' : 'no', selectedEntry.locked ? C.warn : C.dim], [' conflict ', C.label], [selectedEntry.conflict ? 'yes' : 'no', selectedEntry.conflict ? C.error : C.good]]),
104
+ buildPanelLine(width, [[' source ', C.label], [(selectedEntry.sourceLabel ?? 'local/default').slice(0, Math.max(0, width - 10)), C.dim]]),
105
+ buildPanelLine(width, [[' overrides ', C.label], [(selectedEntry.overriddenSources.length > 0 ? selectedEntry.overriddenSources.join(', ') : 'none').slice(0, Math.max(0, width - 13)), C.dim]]),
106
+ buildPanelLine(width, [[' local ', C.label], [String(selectedEntry.localValue).slice(0, Math.max(0, width - 9)), C.dim]]),
107
+ buildPanelLine(width, [[' synced ', C.label], [String(selectedEntry.syncedValue ?? '(unset)').slice(0, Math.max(0, width - 10)), C.ok]]),
108
+ buildPanelLine(width, [[' managed ', C.label], [String(selectedEntry.managedValue ?? '(unset)').slice(0, Math.max(0, width - 11)), C.warn]]),
109
+ ], C)
110
+ : []),
111
+ buildPanelLine(width, [[' ↑/↓ browse /settingssync show <key> /settingssync resolve <key> <local|synced> /managed apply-staged [key...] ', C.dim]]),
112
+ ];
113
+
114
+ return this.renderList(width, height, {
115
+ title: 'Settings Sync',
116
+ header: headerLines,
117
+ footer: footerLines,
118
+ });
119
+ }
120
+ }