@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,331 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { type Line } from '../types/grid.ts';
3
+ import { ModalFactory } from './modal-factory.ts';
4
+ import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
5
+ import type { AgentMessageBus } from '@pellux/goodvibes-sdk/platform/agents';
6
+ import type { WrfcController } from '@pellux/goodvibes-sdk/platform/agents';
7
+ import { formatDuration } from './modal-utils.ts';
8
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
9
+ import { getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
10
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
11
+
12
+ // ─── Constants ────────────────────────────────────────────────────────────────
13
+
14
+ const TOKENS_PER_TOOL_CALL = 400;
15
+ const MAX_LOG_ENTRIES = 10;
16
+ const AGENT_ID_DISPLAY_LENGTH = 16;
17
+
18
+ export interface AgentDetailModalDeps {
19
+ readonly agentManager: Pick<AgentManager, 'getStatus'>;
20
+ readonly agentMessageBus: Pick<AgentMessageBus, 'getMessages'>;
21
+ readonly sessionLogPathResolver: (agentId: string) => string;
22
+ /** Optional — when supplied, constraint data from the agent's WRFC chain is shown (SDK 0.23.0). */
23
+ readonly wrfcController?: Pick<WrfcController, 'getChain'>;
24
+ }
25
+
26
+ // ─── AgentDetailModal ─────────────────────────────────────────────────────────
27
+
28
+ /**
29
+ * AgentDetailModal — deep-view modal for a single running/completed agent.
30
+ *
31
+ * Displays task description, template, model, status, duration, tool-call
32
+ * count, estimated token usage, recent messages from AgentMessageBus, and
33
+ * the agent's current progress note.
34
+ */
35
+ export class AgentDetailModal {
36
+ public active = false;
37
+ public agentId: string | null = null;
38
+
39
+ /** Cached JSONL log entries, loaded on open(). */
40
+ public logEntries: Record<string, unknown>[] = [];
41
+ public logTotal = 0;
42
+
43
+ private refreshTimer: ReturnType<typeof setInterval> | null = null;
44
+ private onRefresh: (() => void) | null = null;
45
+
46
+ constructor(readonly deps: AgentDetailModalDeps) {}
47
+
48
+ /** Set a callback to trigger re-render when log data updates. */
49
+ setOnRefresh(fn: () => void): void {
50
+ this.onRefresh = fn;
51
+ }
52
+
53
+ open(agentId: string): void {
54
+ this.agentId = agentId;
55
+ this.active = true;
56
+ this.logEntries = [];
57
+ this.logTotal = 0;
58
+ this.loadLog().catch((err) => { logger.debug('agent detail log load failed', { err }); });
59
+ // Auto-refresh log every 500ms while modal is open
60
+ if (this.refreshTimer) clearInterval(this.refreshTimer);
61
+ this.refreshTimer = setInterval(() => {
62
+ this.loadLog().then(() => this.onRefresh?.()).catch((err) => { logger.debug('agent detail log refresh tick failed', { err }); });
63
+ }, 500);
64
+ }
65
+
66
+ close(): void {
67
+ this.active = false;
68
+ this.agentId = null;
69
+ this.logEntries = [];
70
+ this.logTotal = 0;
71
+ if (this.refreshTimer) {
72
+ clearInterval(this.refreshTimer);
73
+ this.refreshTimer = null;
74
+ }
75
+ }
76
+
77
+ async loadLog(): Promise<void> {
78
+ if (!this.agentId) { this.logEntries = []; this.logTotal = 0; return; }
79
+ try {
80
+ const sessionFile = this.deps.sessionLogPathResolver(this.agentId);
81
+ const logContent = await readFile(sessionFile, 'utf-8');
82
+ const logLines = logContent.trim().split('\n');
83
+ this.logTotal = logLines.length;
84
+ const parsed = logLines.slice(-MAX_LOG_ENTRIES).map(line => {
85
+ try { return JSON.parse(line) as Record<string, unknown>; } catch { return null; }
86
+ });
87
+ const failedCount = parsed.filter(e => e === null).length;
88
+ if (failedCount > 0) {
89
+ logger.debug('AgentDetailModal: skipped malformed JSONL lines', { count: failedCount });
90
+ }
91
+ this.logEntries = parsed.filter((e): e is Record<string, unknown> => e !== null);
92
+ } catch (err) {
93
+ const code = (err as NodeJS.ErrnoException).code;
94
+ if (code !== 'ENOENT') {
95
+ logger.debug('AgentDetailModal: failed to load session log', { error: summarizeError(err) });
96
+ }
97
+ this.logEntries = [];
98
+ this.logTotal = 0;
99
+ }
100
+ }
101
+ }
102
+
103
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
104
+
105
+ /** Rough token estimate: toolCallCount * avg tokens per tool exchange. */
106
+ function estimateTokens(toolCallCount: number): number {
107
+ return toolCallCount * TOKENS_PER_TOOL_CALL;
108
+ }
109
+
110
+ // ─── renderAgentDetailModal ───────────────────────────────────────────────────
111
+
112
+ /**
113
+ * Render the agent detail modal as Line[] for overlay in the viewport.
114
+ *
115
+ * Shows a deep view of the selected agent: task, template, model, status,
116
+ * duration, tool call count, token estimate, recent bus messages, and
117
+ * progress text.
118
+ *
119
+ * @param modal AgentDetailModal state
120
+ * @param width Terminal width
121
+ */
122
+ export function renderAgentDetailModal(
123
+ modal: AgentDetailModal,
124
+ width: number,
125
+ viewportHeight = 24,
126
+ ): Line[] {
127
+ if (!modal.agentId) return [];
128
+ const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
129
+ margin: 2,
130
+ maxWidth: width - 4,
131
+ chromeRows: 6,
132
+ minContentRows: 10,
133
+ maxContentRows: 22,
134
+ });
135
+ const targetContentRows = Math.max(18, Math.min(22, getStableOverlayContentRows(metrics.contentRows, 12) + 8));
136
+
137
+ const rec = modal.deps.agentManager.getStatus(modal.agentId);
138
+ if (!rec) {
139
+ return ModalFactory.createModal({
140
+ title: 'Agent Detail',
141
+ width: metrics.boxWidth,
142
+ margin: metrics.margin,
143
+ targetContentRows,
144
+ sections: [
145
+ { type: 'text', content: '(agent not found)' },
146
+ ],
147
+ hints: ['[Esc] Close'],
148
+ }, width);
149
+ }
150
+
151
+ const now = Date.now();
152
+ const elapsedMs = (rec.completedAt ?? now) - rec.startedAt;
153
+ const tokenEst = estimateTokens(rec.toolCallCount);
154
+
155
+ // ── Build sections ────────────────────────────────────────────────────────
156
+
157
+ const sections: import('./modal-factory.ts').ModalSection[] = [];
158
+
159
+ // Task — show first line only, capped at 120 chars
160
+ const taskFirstLine = rec.task.split('\n')[0].replace(/^(WRFC\s+(Fix|Review)\s+Request\s*)/i, '').trim();
161
+ const taskDisplay = taskFirstLine.length > 120 ? taskFirstLine.slice(0, 117) + '\u2026' : taskFirstLine;
162
+ sections.push({
163
+ type: 'text',
164
+ content: `Task: ${taskDisplay}`,
165
+ style: { bold: true },
166
+ });
167
+ sections.push({ type: 'separator' });
168
+
169
+ // Metadata grid
170
+ const modelStr = rec.model ? `${rec.provider ?? ''}/${rec.model}` : (rec.provider ?? '(default)');
171
+ sections.push({ type: 'text', content: `Template : ${rec.template}` });
172
+ sections.push({ type: 'text', content: `Model : ${modelStr}` });
173
+ sections.push({ type: 'text', content: `Status : ${rec.status}` });
174
+ sections.push({ type: 'text', content: `Duration : ${formatDuration(elapsedMs)}` });
175
+ sections.push({ type: 'separator' });
176
+
177
+ // Metrics
178
+ sections.push({ type: 'text', content: `Tool calls : ${rec.toolCallCount}` });
179
+ sections.push({ type: 'text', content: `Est tokens : ~${tokenEst.toLocaleString()}` });
180
+
181
+ // SDK 0.23.0: systemPromptAddendum indicator — confirms WRFC constraint addendum was injected
182
+ if (rec.systemPromptAddendum) {
183
+ sections.push({
184
+ type: 'text',
185
+ content: 'Addendum : yes (WRFC constraint layer injected)',
186
+ style: { fg: '#aaffee' },
187
+ });
188
+ }
189
+
190
+ // SDK 0.23.0: constraint data from WRFC chain (engineer constraints + reviewer findings)
191
+ if (rec.wrfcId && modal.deps.wrfcController) {
192
+ try {
193
+ const chain = modal.deps.wrfcController.getChain(rec.wrfcId);
194
+ if (chain && chain.constraints.length > 0) {
195
+ sections.push({ type: 'separator' });
196
+ sections.push({
197
+ type: 'text',
198
+ content: `Constraints (${chain.constraints.length}):`,
199
+ style: { dim: true },
200
+ });
201
+ for (const c of chain.constraints) {
202
+ const text = c.text.length > 80 ? c.text.slice(0, 77) + '…' : c.text;
203
+ sections.push({
204
+ type: 'text',
205
+ content: ` [${c.id}] ${text}`,
206
+ style: { fg: '246' },
207
+ });
208
+ }
209
+ // Reviewer constraint findings (if review has completed)
210
+ const findings = chain.reviewerReport?.constraintFindings;
211
+ if (findings && findings.length > 0) {
212
+ const unsatisfied = findings.filter((f) => !f.satisfied);
213
+ sections.push({
214
+ type: 'text',
215
+ content: `Findings : ${findings.length} checked, ${unsatisfied.length} unsatisfied`,
216
+ style: { fg: unsatisfied.length > 0 ? '#ff6666' : '#44ff88' },
217
+ });
218
+ }
219
+ }
220
+ } catch {
221
+ // wrfcController.getChain throws when chain not found — normal during teardown
222
+ }
223
+ }
224
+
225
+ // Progress
226
+ if (rec.progress) {
227
+ sections.push({ type: 'separator' });
228
+ sections.push({
229
+ type: 'text',
230
+ content: `Progress: ${rec.progress}`,
231
+ style: { fg: '#00ffcc' },
232
+ });
233
+ }
234
+
235
+ // Error
236
+ if (rec.error) {
237
+ sections.push({ type: 'separator' });
238
+ sections.push({
239
+ type: 'text',
240
+ content: `Error: ${rec.error}`,
241
+ style: { fg: '#ff6666' },
242
+ });
243
+ }
244
+
245
+ // Recent messages from AgentMessageBus
246
+ const recentMessages = modal.deps.agentMessageBus
247
+ .getMessages(modal.agentId)
248
+ .slice(-4); // last 4 messages
249
+
250
+ if (recentMessages.length > 0) {
251
+ sections.push({ type: 'separator' });
252
+ sections.push({
253
+ type: 'text',
254
+ content: `Recent messages (${recentMessages.length}):`,
255
+ style: { dim: true },
256
+ });
257
+ for (const msg of recentMessages) {
258
+ const fromLabel = msg.from === '*' ? 'broadcast' : msg.from.slice(0, 12);
259
+ const preview = msg.content.length > 50
260
+ ? msg.content.slice(0, 47) + '\u2026'
261
+ : msg.content;
262
+ sections.push({
263
+ type: 'text',
264
+ content: ` [${fromLabel}] ${preview}`,
265
+ style: { fg: '246' },
266
+ });
267
+ }
268
+ }
269
+
270
+ // Execution history from cached JSONL session log (loaded on open())
271
+ if (modal.logEntries.length > 0) {
272
+ sections.push({ type: 'separator' });
273
+ sections.push({
274
+ type: 'text',
275
+ content: `Execution Log (${modal.logTotal} events, showing last ${modal.logEntries.length}):`,
276
+ style: { dim: true },
277
+ });
278
+ for (const entry of modal.logEntries) {
279
+ const entryType = String(entry.type ?? 'unknown');
280
+ const rawTs = entry.timestamp;
281
+ const ts = typeof rawTs === 'string' && rawTs.length >= 19
282
+ ? rawTs.slice(11, 19)
283
+ : '';
284
+ let detail = '';
285
+ if (entryType === 'tool_execution') detail = ` ${entry.toolName}`;
286
+ if (entryType === 'llm_response') detail = ` (${entry.toolCallCount} tools, ${entry.contentLength} chars)`;
287
+ if (entryType === 'session_end') detail = ` [${entry.status}]`;
288
+ sections.push({
289
+ type: 'text',
290
+ content: ` ${ts} ${entryType}${detail}`,
291
+ style: { fg: '246' },
292
+ });
293
+ }
294
+ }
295
+
296
+ // Streaming content — show live output when agent is actively streaming
297
+ const STREAMING_MAX_CHARS = 500;
298
+ if (rec.status === 'running' && rec.streamingContent) {
299
+ const content = rec.streamingContent;
300
+ const truncated = content.length > STREAMING_MAX_CHARS;
301
+ const display = truncated ? content.slice(-STREAMING_MAX_CHARS) : content;
302
+ sections.push({ type: 'separator' });
303
+ sections.push({
304
+ type: 'text',
305
+ content: truncated
306
+ ? `Streaming (last ${STREAMING_MAX_CHARS} of ${content.length} chars \u2191 scroll for more):`
307
+ : 'Streaming:',
308
+ style: { fg: '#00ffcc', dim: true },
309
+ });
310
+ // Split into display lines, capped at width for readability
311
+ const maxLineWidth = Math.max(width - 10, 40);
312
+ const streamLines = display.split('\n');
313
+ for (const line of streamLines) {
314
+ const trimmed = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + '\u2026' : line;
315
+ sections.push({
316
+ type: 'text',
317
+ content: ` ${trimmed}`,
318
+ style: { fg: '#aaffee' },
319
+ });
320
+ }
321
+ }
322
+
323
+ return ModalFactory.createModal({
324
+ title: `Agent: ${rec.id.slice(0, AGENT_ID_DISPLAY_LENGTH)}`,
325
+ width: metrics.boxWidth,
326
+ margin: metrics.margin,
327
+ targetContentRows,
328
+ sections,
329
+ hints: ['[Esc] Close'],
330
+ }, width);
331
+ }
@@ -0,0 +1,238 @@
1
+ import type {
2
+ AgentWorkspace,
3
+ AgentWorkspaceAction,
4
+ AgentWorkspaceActionResult,
5
+ AgentWorkspaceCategory,
6
+ AgentWorkspaceRuntimeSnapshot,
7
+ } from '../input/agent-workspace.ts';
8
+ import type { Line } from '../types/grid.ts';
9
+ import { wrapText } from '../utils/terminal-width.ts';
10
+ import { GLYPHS } from './ui-primitives.ts';
11
+ import {
12
+ getFullscreenWorkspaceMetrics,
13
+ padDisplay,
14
+ renderFullscreenWorkspace,
15
+ stableWindow,
16
+ WORKSPACE_PALETTE as PALETTE,
17
+ type WorkspaceRow,
18
+ } from './fullscreen-workspace.ts';
19
+
20
+ function safetyColor(action: AgentWorkspaceAction): string {
21
+ if (action.safety === 'safe') return PALETTE.good;
22
+ if (action.safety === 'read-only') return PALETTE.info;
23
+ if (action.safety === 'delegates') return PALETTE.warn;
24
+ return PALETTE.bad;
25
+ }
26
+
27
+ function actionResultColor(result: AgentWorkspaceActionResult): string {
28
+ if (result.kind === 'blocked' || result.kind === 'error') return PALETTE.bad;
29
+ if (result.kind === 'dispatched') return PALETTE.info;
30
+ if (result.kind === 'refreshed') return PALETTE.good;
31
+ return PALETTE.muted;
32
+ }
33
+
34
+ function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[] {
35
+ const rows: WorkspaceRow[] = [];
36
+ let selectedRenderedIndex = 0;
37
+ let lastGroup = '';
38
+
39
+ workspace.categories.forEach((category, index) => {
40
+ if (category.group !== lastGroup) {
41
+ rows.push({ text: category.group, kind: 'group', bold: true });
42
+ lastGroup = category.group;
43
+ }
44
+ const selected = index === workspace.selectedCategoryIndex;
45
+ if (selected) selectedRenderedIndex = rows.length;
46
+ const marker = selected ? GLYPHS.navigation.selected : ' ';
47
+ rows.push({
48
+ text: ` ${marker} ${category.label}`,
49
+ selected: selected && workspace.focusPane === 'categories',
50
+ kind: 'item',
51
+ fg: selected ? PALETTE.text : PALETTE.muted,
52
+ bold: selected,
53
+ });
54
+ });
55
+
56
+ const visible = Math.max(1, height);
57
+ const window = stableWindow(rows.length, selectedRenderedIndex, visible);
58
+ const visibleRows = rows.slice(window.start, window.end);
59
+ if (window.start > 0 && visibleRows.length > 0) {
60
+ visibleRows[0] = { text: `${GLYPHS.navigation.moreAbove} ${window.start} more row(s) above`, kind: 'more', fg: PALETTE.dim, dim: true };
61
+ }
62
+ if (window.end < rows.length && visibleRows.length > 0) {
63
+ visibleRows[visibleRows.length - 1] = { text: `${GLYPHS.navigation.moreBelow} ${rows.length - window.end} more row(s) below`, kind: 'more', fg: PALETTE.dim, dim: true };
64
+ }
65
+ while (visibleRows.length < height) visibleRows.push({ text: '', kind: 'empty' });
66
+ return visibleRows.slice(0, height);
67
+ }
68
+
69
+ function actionCommand(action: AgentWorkspaceAction): string {
70
+ return action.command ?? '(guidance)';
71
+ }
72
+
73
+ type ContextLine = { readonly text: string; readonly fg?: string; readonly bold?: boolean; readonly dim?: boolean };
74
+
75
+ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
76
+ if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
77
+ const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
78
+ if (category.id === 'home') {
79
+ base.push(
80
+ { text: `Chat route: ${snapshot.provider} / ${snapshot.modelDisplayName}`, fg: PALETTE.info },
81
+ { text: `Session: ${snapshot.sessionId}`, fg: PALETTE.muted },
82
+ { text: `Policy: ${snapshot.executionPolicy}; WRFC ${snapshot.wrfcPolicy}`, fg: PALETTE.good },
83
+ );
84
+ } else if (category.id === 'setup') {
85
+ base.push(
86
+ { text: `External daemon: ${snapshot.daemonBaseUrl}`, fg: PALETTE.info },
87
+ { text: `Daemon ownership: ${snapshot.daemonOwnership}; Agent never starts or restarts it`, fg: PALETTE.good },
88
+ { text: `Workspace: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
89
+ { text: `Home: ${snapshot.homeDirectory}`, fg: PALETTE.muted },
90
+ );
91
+ } else if (category.id === 'knowledge') {
92
+ base.push(
93
+ { text: `Route family: ${snapshot.knowledgeRoute}/{status,ask,search}`, fg: PALETTE.info },
94
+ { text: `Isolation: ${snapshot.knowledgeIsolation}; no default Knowledge/Wiki or HomeGraph fallback`, fg: PALETTE.good },
95
+ { text: 'Agent-owned content appears here only after explicit Agent knowledge ingestion.', fg: PALETTE.muted },
96
+ );
97
+ } else if (category.id === 'memory') {
98
+ base.push(
99
+ { text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
100
+ { text: 'Durable memory, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
101
+ { text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
102
+ );
103
+ } else if (category.id === 'work') {
104
+ base.push(
105
+ { text: 'Work plan and approvals are read or explicitly confirmed through public operator routes.', fg: PALETTE.info },
106
+ { text: 'This workspace does not approve, deny, cancel, or mutate requests by selection alone.', fg: PALETTE.good },
107
+ );
108
+ } else if (category.id === 'automation') {
109
+ base.push(
110
+ { text: 'Automation and schedules default to read-only observability.', fg: PALETTE.info },
111
+ { text: 'Run/pause/resume/cancel/retry require exact explicit commands and confirmation.', fg: PALETTE.warn },
112
+ );
113
+ } else if (category.id === 'delegate') {
114
+ base.push(
115
+ { text: 'Build/fix/review work is handed to GoodVibes TUI/shared-session contracts.', fg: PALETTE.info },
116
+ { text: `WRFC policy: ${snapshot.wrfcPolicy}`, fg: PALETTE.warn },
117
+ { text: 'Agent does not spawn local Engineer/Reviewer/Tester roots.', fg: PALETTE.good },
118
+ );
119
+ }
120
+ if (snapshot.warnings.length > 0) {
121
+ base.push({ text: '', dim: true }, { text: 'Warnings', fg: PALETTE.warn, bold: true });
122
+ for (const warning of snapshot.warnings) base.push({ text: warning, fg: PALETTE.warn });
123
+ }
124
+ return base;
125
+ }
126
+
127
+ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction | null, width: number): WorkspaceRow[] {
128
+ const lines: ContextLine[] = [
129
+ { text: category.label, fg: PALETTE.title, bold: true },
130
+ { text: category.summary, fg: PALETTE.subtitle },
131
+ { text: '' },
132
+ { text: category.detail, fg: PALETTE.text },
133
+ { text: '' },
134
+ ...snapshotLines(category, workspace.runtimeSnapshot),
135
+ ];
136
+
137
+ if (action) {
138
+ lines.push(
139
+ { text: '' },
140
+ { text: `Selected: ${action.label}`, fg: PALETTE.title, bold: true },
141
+ { text: action.detail, fg: PALETTE.text },
142
+ { text: `Command: ${actionCommand(action)}`, fg: action.kind === 'command' ? PALETTE.info : PALETTE.muted },
143
+ { text: `Safety: ${action.safety}`, fg: safetyColor(action) },
144
+ );
145
+ }
146
+
147
+ if (workspace.lastActionResult) {
148
+ lines.push(
149
+ { text: '' },
150
+ { text: 'Action Result', fg: PALETTE.title, bold: true },
151
+ { text: workspace.lastActionResult.title, fg: actionResultColor(workspace.lastActionResult), bold: true },
152
+ { text: workspace.lastActionResult.detail, fg: PALETTE.text },
153
+ );
154
+ if (workspace.lastActionResult.command) {
155
+ lines.push({ text: `Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
156
+ }
157
+ }
158
+
159
+ return lines.flatMap((entry): WorkspaceRow[] => {
160
+ if (entry.text.length === 0) return [{ text: '', kind: 'empty', dim: true }];
161
+ return wrapText(entry.text, Math.max(1, width)).map((text, index): WorkspaceRow => ({
162
+ text,
163
+ fg: entry.fg,
164
+ bold: entry.bold && index === 0,
165
+ dim: entry.dim,
166
+ }));
167
+ });
168
+ }
169
+
170
+ function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
171
+ const rows: WorkspaceRow[] = [];
172
+ const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
173
+ const safetyWidth = 10;
174
+ const commandWidth = Math.max(10, width - labelWidth - safetyWidth - 9);
175
+ rows.push({
176
+ text: ` ${padDisplay('Action', labelWidth)} ${padDisplay('Safety', safetyWidth)} ${padDisplay('Command', commandWidth)}`,
177
+ fg: PALETTE.muted,
178
+ bold: true,
179
+ });
180
+
181
+ const actions = workspace.actions;
182
+ const visible = Math.max(1, height - 2);
183
+ const window = stableWindow(actions.length, workspace.selectedActionIndex, visible);
184
+ if (window.start > 0) rows.push({ text: `${GLYPHS.navigation.moreAbove} ${window.start} more action(s) above`, kind: 'more', fg: PALETTE.dim, dim: true });
185
+
186
+ for (let index = window.start; index < window.end; index += 1) {
187
+ const action = actions[index]!;
188
+ const selected = index === workspace.selectedActionIndex;
189
+ const marker = selected ? GLYPHS.navigation.selected : ' ';
190
+ rows.push({
191
+ text: `${marker} ${padDisplay(action.label, labelWidth)} ${padDisplay(action.safety, safetyWidth)} ${padDisplay(actionCommand(action), commandWidth)}`,
192
+ selected: selected && workspace.focusPane === 'actions',
193
+ fg: safetyColor(action),
194
+ bold: selected,
195
+ });
196
+ }
197
+
198
+ if (window.end < actions.length) rows.push({ text: `${GLYPHS.navigation.moreBelow} ${actions.length - window.end} more action(s) below`, kind: 'more', fg: PALETTE.dim, dim: true });
199
+ rows.push({ text: '' });
200
+ rows.push({ text: `Status: ${workspace.status}`, fg: PALETTE.muted });
201
+ if (workspace.lastActionResult) {
202
+ rows.push({ text: '' });
203
+ rows.push({ text: `Action Result: ${workspace.lastActionResult.title}`, fg: actionResultColor(workspace.lastActionResult), bold: true });
204
+ for (const line of wrapText(workspace.lastActionResult.detail, Math.max(1, width - 2))) {
205
+ rows.push({ text: ` ${line}`, fg: PALETTE.text });
206
+ }
207
+ if (workspace.lastActionResult.command) {
208
+ rows.push({ text: ` Command: ${workspace.lastActionResult.command}`, fg: PALETTE.muted });
209
+ }
210
+ }
211
+
212
+ while (rows.length < height) rows.push({ text: '', kind: 'empty' });
213
+ return rows.slice(0, height);
214
+ }
215
+
216
+ function footerText(workspace: AgentWorkspace): string {
217
+ const focus = workspace.focusPane === 'categories' ? 'categories' : 'actions';
218
+ return `Agent workspace · focus ${focus} · Up/Down navigate · Left/Right pane · Enter open/action · R refresh · Esc close`;
219
+ }
220
+
221
+ export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, height: number): Line[] {
222
+ const metrics = getFullscreenWorkspaceMetrics({ width, height, leftWidth: width < 90 ? undefined : 30, contextRatio: 0.48, minContextRows: 8 });
223
+ const category = workspace.selectedCategory;
224
+ const action = workspace.selectedAction;
225
+
226
+ return renderFullscreenWorkspace({
227
+ width,
228
+ height,
229
+ title: 'GoodVibes Agent / Operator Workspace',
230
+ stateLabel: workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
231
+ leftHeader: 'Operator Areas',
232
+ mainHeader: `${category.label} · ${category.actions.length} action(s)`,
233
+ leftRows: buildLeftRows(workspace, metrics.bodyRows),
234
+ contextRows: buildContextRows(workspace, category, action, metrics.contextWidth),
235
+ controlRows: buildActionRows(workspace, metrics.contextWidth, metrics.controlRows),
236
+ footer: footerText(workspace),
237
+ });
238
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ANSI sanitizer for untrusted content entering the renderer.
3
+ *
4
+ * The TUI grid renders content character-by-character via writeStyledText,
5
+ * which already drops zero-width characters (including ESC \x1b) by checking
6
+ * display width. However, that is incidental protection — not a contract.
7
+ * This module provides explicit, intentional sanitization.
8
+ *
9
+ * Strategy:
10
+ * - STRIP all non-SGR escape sequences (cursor moves, OSC, BEL, alt-screen,
11
+ * DECSET/private mode, and any other CSI/ESC sequences).
12
+ * - PRESERVE SGR color/style codes (\x1b[<params>m) — used legitimately by
13
+ * the TUI's own colorized output paths.
14
+ * - STRIP bare BEL (\x07) characters.
15
+ *
16
+ * Safe SGR pattern: \x1b[ followed by digits/semicolons, ending in 'm'.
17
+ * Everything else that starts with \x1b is dangerous and stripped.
18
+ */
19
+
20
+ // Matches safe SGR sequences: ESC [ <digits/semicolons> m
21
+ const SGR_PATTERN = /\x1b\[([0-9;]*)m/g;
22
+
23
+ // Matches ALL CSI sequences: ESC [ ... <final byte 0x40-0x7E>
24
+ const CSI_SEQUENCE = /\x1b\[[\x20-\x3f]*[\x40-\x7e]/g;
25
+
26
+ // Matches OSC sequences: ESC ] ... (ESC \ or BEL)
27
+ const OSC_SEQUENCE = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g;
28
+
29
+ // Matches other ESC sequences (ESC + single character that is not '[' or ']')
30
+ const ESC_OTHER = /\x1b[^\[\]]/g;
31
+
32
+ // Matches standalone BEL
33
+ const BEL = /\x07/g;
34
+
35
+ /**
36
+ * Strip dangerous ANSI escape sequences from untrusted content.
37
+ *
38
+ * Preserves SGR color codes (\x1b[<n>m). Removes:
39
+ * - Cursor movement CSI sequences (\x1b[<n>A/B/C/D, \x1b[H, etc.)
40
+ * - OSC sequences (\x1b]...\x07 or \x1b]...\x1b\\)
41
+ * - Alt-screen and DECSET private mode (\x1b[?...h/l)
42
+ * - Any other CSI or ESC sequences
43
+ * - Bare BEL (\x07)
44
+ *
45
+ * @param input - Raw string that may contain ANSI escape sequences
46
+ * @returns Sanitized string safe for grid rendering
47
+ */
48
+ export function stripDangerousAnsi(input: string): string {
49
+ // Step 1: Extract and preserve SGR sequences by replacing them with placeholders,
50
+ // then strip all other escape sequences, then restore SGR sequences.
51
+ // This approach avoids complex negative lookahead regexes.
52
+
53
+ // Collect SGR sequences and replace with unique markers
54
+ const sgrTokens: string[] = [];
55
+ const withPlaceholders = input.replace(SGR_PATTERN, (match) => {
56
+ const idx = sgrTokens.length;
57
+ sgrTokens.push(match);
58
+ return `\x00SGR${idx}\x00`;
59
+ });
60
+
61
+ // Strip all remaining dangerous sequences
62
+ let sanitized = withPlaceholders
63
+ .replace(CSI_SEQUENCE, '') // removes cursor moves, alt-screen, DECSET, etc.
64
+ .replace(OSC_SEQUENCE, '') // removes OSC
65
+ .replace(ESC_OTHER, '') // removes remaining ESC+char sequences
66
+ .replace(/\x1b/g, '') // removes any leftover bare ESC
67
+ .replace(BEL, ''); // removes BEL
68
+
69
+ // Restore SGR sequences from placeholders
70
+ sanitized = sanitized.replace(/\x00SGR(\d+)\x00/g, (_match, idxStr) => {
71
+ const idx = parseInt(idxStr, 10);
72
+ return sgrTokens[idx] ?? '';
73
+ });
74
+
75
+ return sanitized;
76
+ }