@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,119 @@
1
+ import { isSecretRefInput } from '@pellux/goodvibes-sdk/platform/config';
2
+ import type { ConfigKey } from './index.ts';
3
+ import type { SecretScope, SecretStorageMedium } from './secrets.ts';
4
+
5
+ export const SECRET_CONFIG_KEYS = new Set<ConfigKey>([
6
+ 'surfaces.slack.signingSecret',
7
+ 'surfaces.slack.botToken',
8
+ 'surfaces.slack.appToken',
9
+ 'surfaces.discord.botToken',
10
+ 'surfaces.ntfy.token',
11
+ 'surfaces.webhook.secret',
12
+ 'surfaces.homeassistant.accessToken',
13
+ 'surfaces.homeassistant.webhookSecret',
14
+ 'surfaces.telegram.botToken',
15
+ 'surfaces.telegram.webhookSecret',
16
+ 'surfaces.googleChat.verificationToken',
17
+ 'surfaces.signal.token',
18
+ 'surfaces.whatsapp.accessToken',
19
+ 'surfaces.whatsapp.verifyToken',
20
+ 'surfaces.whatsapp.signingSecret',
21
+ 'surfaces.imessage.token',
22
+ 'surfaces.msteams.appPassword',
23
+ 'surfaces.bluebubbles.password',
24
+ 'surfaces.mattermost.botToken',
25
+ 'surfaces.matrix.accessToken',
26
+ ]);
27
+
28
+ export interface SecretBackedConfigUpdate {
29
+ readonly configValue: string;
30
+ readonly secretKey?: string;
31
+ readonly secretValue?: string;
32
+ readonly clearSecretKey?: string;
33
+ }
34
+
35
+ export interface SecretBackedConfigManager {
36
+ readonly get: (key: ConfigKey) => unknown;
37
+ readonly setDynamic: (key: ConfigKey, value: unknown) => void;
38
+ }
39
+
40
+ export interface SecretBackedSecretStore {
41
+ readonly set: (key: string, value: string, options?: { readonly scope?: SecretScope; readonly medium?: SecretStorageMedium }) => Promise<void>;
42
+ readonly delete?: (key: string, options?: { readonly scope?: SecretScope; readonly medium?: SecretStorageMedium }) => Promise<void>;
43
+ }
44
+
45
+ export function isSecretConfigKey(key: string): key is ConfigKey {
46
+ return SECRET_CONFIG_KEYS.has(key as ConfigKey);
47
+ }
48
+
49
+ export function normalizeSecretKeyPart(value: string): string {
50
+ return value
51
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
52
+ .replace(/[^a-zA-Z0-9]+/g, '_')
53
+ .replace(/^_+|_+$/g, '')
54
+ .toUpperCase();
55
+ }
56
+
57
+ export function buildGoodVibesSecretKey(configKey: string): string {
58
+ return `GOODVIBES_${configKey.split('.').map(normalizeSecretKeyPart).filter(Boolean).join('_')}`;
59
+ }
60
+
61
+ export function buildGoodVibesSecretRef(secretKey: string): string {
62
+ return `goodvibes://secrets/goodvibes/${encodeURIComponent(secretKey)}`;
63
+ }
64
+
65
+ export function isSecretReferenceValue(value: string): boolean {
66
+ const normalized = value.trim();
67
+ return normalized.startsWith('goodvibes://secrets/') && isSecretRefInput(normalized);
68
+ }
69
+
70
+ export function isMalformedGoodVibesSecretReferenceValue(value: string): boolean {
71
+ const normalized = value.trim();
72
+ return normalized.startsWith('goodvibes://') && !isSecretReferenceValue(normalized);
73
+ }
74
+
75
+ export function getSecretWriteMedium(policy: unknown): SecretStorageMedium {
76
+ if (policy === 'plaintext_allowed') return 'plaintext';
77
+ return 'secure';
78
+ }
79
+
80
+ export function buildSecretBackedConfigUpdate(configKey: ConfigKey, rawValue: string): SecretBackedConfigUpdate {
81
+ const value = rawValue.trim();
82
+ const secretKey = buildGoodVibesSecretKey(configKey);
83
+ if (value.length === 0) {
84
+ return {
85
+ configValue: '',
86
+ clearSecretKey: secretKey,
87
+ };
88
+ }
89
+ if (isSecretReferenceValue(value)) {
90
+ return { configValue: value };
91
+ }
92
+ return {
93
+ configValue: buildGoodVibesSecretRef(secretKey),
94
+ secretKey,
95
+ secretValue: rawValue,
96
+ };
97
+ }
98
+
99
+ export async function persistSecretBackedConfigValue(
100
+ configManager: SecretBackedConfigManager,
101
+ secretsManager: SecretBackedSecretStore | null | undefined,
102
+ configKey: ConfigKey,
103
+ rawValue: string,
104
+ options: { readonly scope?: SecretScope } = {},
105
+ ): Promise<string> {
106
+ const update = buildSecretBackedConfigUpdate(configKey, rawValue);
107
+ const scope = options.scope ?? 'user';
108
+ if (update.secretKey && update.secretValue !== undefined && secretsManager) {
109
+ await secretsManager.set(update.secretKey, update.secretValue, {
110
+ scope,
111
+ medium: getSecretWriteMedium(configManager.get('storage.secretPolicy')),
112
+ });
113
+ }
114
+ if (update.clearSecretKey && secretsManager?.delete) {
115
+ await secretsManager.delete(update.clearSecretKey, { scope });
116
+ }
117
+ configManager.setDynamic(configKey, update.configValue);
118
+ return update.configValue;
119
+ }
@@ -0,0 +1,71 @@
1
+ export type {
2
+ SecretDeleteOptions,
3
+ SecretRecord,
4
+ SecretScope,
5
+ SecretSource,
6
+ SecretStorageMedium,
7
+ SecretStorageMode,
8
+ SecretStorageReview,
9
+ SecretWriteOptions,
10
+ } from '@pellux/goodvibes-sdk/platform/config';
11
+
12
+ import {
13
+ SecretsManager as SdkSecretsManager,
14
+ type SecretsManagerOptions as SdkSecretsManagerOptions,
15
+ } from '@pellux/goodvibes-sdk/platform/config';
16
+ import { isSecretRefInput } from '@pellux/goodvibes-sdk/platform/config';
17
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from './surface.ts';
18
+
19
+ export type SecretsManagerOptions = Omit<SdkSecretsManagerOptions, 'surfaceRoot'>;
20
+
21
+ const RAW_SECRET_LITERAL_PREFIX = '__GOODVIBES_LITERAL_V1__';
22
+
23
+ function isGoodVibesSecretRefInput(value: string): boolean {
24
+ const normalized = value.trim();
25
+ return normalized.startsWith('goodvibes://secrets/') && isSecretRefInput(normalized);
26
+ }
27
+
28
+ function shouldStoreAsLiteral(value: string): boolean {
29
+ return value.startsWith(RAW_SECRET_LITERAL_PREFIX)
30
+ || (isSecretRefInput(value) && !isGoodVibesSecretRefInput(value));
31
+ }
32
+
33
+ function encodeLiteralSecret(value: string): string {
34
+ return `${RAW_SECRET_LITERAL_PREFIX}${Buffer.from(JSON.stringify({ value }), 'utf-8').toString('base64url')}`;
35
+ }
36
+
37
+ function decodeLiteralSecret(value: string): string | null {
38
+ if (!value.startsWith(RAW_SECRET_LITERAL_PREFIX)) return null;
39
+ try {
40
+ const decoded = Buffer.from(value.slice(RAW_SECRET_LITERAL_PREFIX.length), 'base64url').toString('utf-8');
41
+ const parsed = JSON.parse(decoded) as unknown;
42
+ if (!parsed || typeof parsed !== 'object' || typeof (parsed as { value?: unknown }).value !== 'string') return null;
43
+ return (parsed as { value: string }).value;
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export class SecretsManager extends SdkSecretsManager {
50
+ constructor(options: SecretsManagerOptions) {
51
+ super({
52
+ ...options,
53
+ surfaceRoot: GOODVIBES_AGENT_SURFACE_ROOT,
54
+ });
55
+ }
56
+
57
+ override async get(key: string): Promise<string | null> {
58
+ const envValue = process.env[key];
59
+ if (envValue !== undefined && shouldStoreAsLiteral(envValue)) {
60
+ return decodeLiteralSecret(envValue) ?? envValue;
61
+ }
62
+
63
+ const value = await super.get(key);
64
+ if (value === null) return null;
65
+ return decodeLiteralSecret(value) ?? value;
66
+ }
67
+
68
+ override async set(key: string, value: string, options?: Parameters<SdkSecretsManager['set']>[2]): Promise<void> {
69
+ await super.set(key, shouldStoreAsLiteral(value) ? encodeLiteralSecret(value) : value, options);
70
+ }
71
+ }
@@ -0,0 +1 @@
1
+ export const GOODVIBES_AGENT_SURFACE_ROOT = 'agent';
@@ -0,0 +1,61 @@
1
+ import type { SubmissionIntent } from '../input/submission-intent.ts';
2
+ import { routeSubmissionIntent, type SubmissionRouterInput } from '../input/submission-router.ts';
3
+ import type { TurnState } from '@/runtime/index.ts';
4
+
5
+ export interface ComposerState {
6
+ readonly intent: SubmissionIntent;
7
+ readonly modeLabel: string;
8
+ readonly statusLabel: string;
9
+ readonly pendingRisk: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote';
10
+ readonly flags: readonly string[];
11
+ }
12
+
13
+ export interface ComposerStateInput extends SubmissionRouterInput {
14
+ readonly pendingApproval?: boolean;
15
+ readonly turnState?: TurnState;
16
+ }
17
+
18
+ export function deriveComposerState(input: ComposerStateInput): ComposerState {
19
+ const intent = routeSubmissionIntent(input);
20
+ const flags: string[] = [];
21
+ let pendingRisk: ComposerState['pendingRisk'] = 'none';
22
+
23
+ if (input.pendingApproval) {
24
+ flags.push('approval');
25
+ pendingRisk = 'approval-wait';
26
+ }
27
+ if (intent.kind === 'shell') {
28
+ flags.push('shell');
29
+ if (pendingRisk === 'none') pendingRisk = 'shell';
30
+ } else if (intent.kind === 'slash-command' || intent.kind === 'plan') {
31
+ if (pendingRisk === 'none') pendingRisk = 'command';
32
+ } else if (intent.kind === 'delegation') {
33
+ flags.push('delegation');
34
+ if (pendingRisk === 'none') pendingRisk = 'remote';
35
+ } else if (intent.kind === 'orchestration') {
36
+ flags.push('orchestration');
37
+ if (pendingRisk === 'none') pendingRisk = 'remote';
38
+ }
39
+ if (input.hasAttachments) flags.push('attachments');
40
+
41
+ const statusLabel = (() => {
42
+ switch (input.turnState) {
43
+ case 'preflight': return 'preflight';
44
+ case 'streaming': return 'streaming';
45
+ case 'tool_dispatch': return 'tools';
46
+ case 'post_hooks': return 'post-hooks';
47
+ case 'failed': return 'failed';
48
+ case 'cancelled': return 'cancelled';
49
+ case 'completed': return 'completed';
50
+ default: return 'idle';
51
+ }
52
+ })();
53
+
54
+ return {
55
+ intent,
56
+ modeLabel: intent.label,
57
+ statusLabel,
58
+ pendingRisk,
59
+ flags,
60
+ };
61
+ }
@@ -0,0 +1,359 @@
1
+ import { UIFactory } from '../renderer/ui-factory.ts';
2
+ import { renderMarkdownTracked } from '../renderer/markdown.ts';
3
+ import { renderToolCallBlock } from '../renderer/tool-call.ts';
4
+ import { renderThinkingBlock } from '../renderer/thinking.ts';
5
+ import { renderSystemMessage } from '../renderer/system-message.ts';
6
+ import { createEmptyLine, type Line, type Cell } from '../types/grid.ts';
7
+ import { getSplashLines, type SplashOptions } from '../utils/splash-lines.ts';
8
+ import { interpolateColor, getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
9
+ import { LAYOUT } from '../renderer/layout.ts';
10
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
11
+ import { renderConversationCollapsedFragment, renderConversationEventLine } from '../renderer/conversation-surface.ts';
12
+ import { GLYPHS } from '../renderer/ui-primitives.ts';
13
+ import type { BlockMeta, ConversationMessageSnapshot } from './conversation';
14
+ import { parseDiffForApply } from '@pellux/goodvibes-sdk/platform/core';
15
+ import { extractUserDisplayText } from '@pellux/goodvibes-sdk/platform/core';
16
+
17
+ type Message = ConversationMessageSnapshot;
18
+
19
+ function summarizeCallId(callId: string, maxLength = 24): string {
20
+ return callId.length <= maxLength ? callId : `${callId.slice(0, maxLength - 1)}…`;
21
+ }
22
+
23
+ interface ConversationRenderContext {
24
+ readonly history: {
25
+ addLine: (line: Line) => void;
26
+ addLines: (lines: Line[]) => void;
27
+ getLineCount: () => number;
28
+ };
29
+ readonly blockRegistry: BlockMeta[];
30
+ readonly collapseState: Map<string, boolean>;
31
+ readonly errorLineRegistry: number[];
32
+ readonly configManager: ConfigManager | null;
33
+ readonly splashOptions: SplashOptions;
34
+ }
35
+
36
+ export function renderConversationUserMessage(
37
+ context: ConversationRenderContext,
38
+ message: Extract<Message, { role: 'user' }>,
39
+ width: number,
40
+ ): void {
41
+ const displayText = extractUserDisplayText(message.content);
42
+ if (message.cancelled) {
43
+ context.history.addLines(UIFactory.createMessageBar(width, displayText, '#3a1a1a', '196', ' x ', true));
44
+ return;
45
+ }
46
+ context.history.addLines(UIFactory.createMessageBar(width, displayText));
47
+ }
48
+
49
+ export function renderConversationAssistantMessage(
50
+ context: ConversationRenderContext,
51
+ message: Extract<Message, { role: 'assistant' }>,
52
+ width: number,
53
+ lineNumberMode: 'all' | 'code' | 'off',
54
+ collapseThreshold: number,
55
+ msgIdx: number,
56
+ ): void {
57
+ const assistantHeaderDetails = [];
58
+ if (message.model) {
59
+ assistantHeaderDetails.push({ text: ` ${message.model}${message.provider ? ` (${message.provider})` : ''} `, fg: '#94a3b8', dim: true });
60
+ }
61
+ if (message.toolCalls && message.toolCalls.length > 0) {
62
+ assistantHeaderDetails.push({ text: ` ${GLYPHS.status.pending} tools:${message.toolCalls.length} `, fg: '#38bdf8' });
63
+ }
64
+ if (message.reasoningContent || message.reasoningSummary) {
65
+ assistantHeaderDetails.push({ text: ` ${GLYPHS.status.active} reasoning `, fg: '#a855f7', dim: true });
66
+ }
67
+ if (assistantHeaderDetails.length > 0) {
68
+ context.history.addLine(renderConversationEventLine(width, {
69
+ marker: GLYPHS.status.active,
70
+ markerFg: '#22d3ee',
71
+ label: 'assistant',
72
+ labelFg: '#22d3ee',
73
+ detailFg: '244',
74
+ }, assistantHeaderDetails));
75
+ }
76
+
77
+ const showThinking = context.configManager?.get('display.showThinking') ?? false;
78
+ const showReasoningSummary = context.configManager?.get('display.showReasoningSummary') ?? false;
79
+ if (showThinking && message.reasoningContent) {
80
+ const thinkingStartLine = context.history.getLineCount();
81
+ const thinkingBlockIdx = context.blockRegistry.length;
82
+ const thinkingCollapseKey = `msg_${msgIdx}_thinking`;
83
+ const thinkingLines = renderThinkingBlock(message.reasoningContent, width);
84
+ context.history.addLines(thinkingLines);
85
+ context.history.addLine(createEmptyLine(width));
86
+ const thinkingRenderedLines = context.history.getLineCount() - thinkingStartLine;
87
+ context.blockRegistry.push({
88
+ blockIndex: thinkingBlockIdx,
89
+ collapseKey: thinkingCollapseKey,
90
+ type: 'thinking',
91
+ startLine: thinkingStartLine,
92
+ lineCount: thinkingRenderedLines,
93
+ rawContent: message.reasoningContent,
94
+ });
95
+ }
96
+ if (showReasoningSummary && message.reasoningSummary) {
97
+ const summaryLines = renderThinkingBlock(message.reasoningSummary, width);
98
+ context.history.addLines(summaryLines);
99
+ context.history.addLine(createEmptyLine(width));
100
+ }
101
+
102
+ if (message.content) {
103
+ const showAllLineNumbers = lineNumberMode === 'all';
104
+ const showCodeBlockLineNumbers = lineNumberMode === 'all' ? false : lineNumberMode === 'code';
105
+ // First pass: measure totalLines for gutter sizing (only when line-numbers='all').
106
+ // When line numbers are off, skip the measurement pass entirely.
107
+ //
108
+ // NOTE: The 'all' mode intentionally calls renderMarkdownTracked twice:
109
+ // 1. Measure pass: render at full `width` to get the total line count, which
110
+ // determines `numWidth` (digit count) and thus `gutterW` (gutter column width).
111
+ // 2. Render pass: render at `width - gutterW` with the gutter factored in.
112
+ //
113
+ // Single-pass is not pursued here. It would require either a pessimistic
114
+ // `numWidth=6` (fits 999,999 lines, but wastes 3-4 gutter columns on typical
115
+ // messages) or rendering the numbered output into a scratch buffer and trimming.
116
+ // Neither is clearly better than the current two-pass measurement approach.
117
+ // The 4α commit message claim that this "eliminates double-parse when line
118
+ // numbers are enabled" was inaccurate: 4α eliminated the legacy
119
+ // `renderMarkdown()` duplicate used for code-block line-number mode ('code').
120
+ // The 'all' mode double-call is a deliberate design choice and remains unchanged.
121
+ const measureWidth = showAllLineNumbers ? width : 0;
122
+ const totalLines = showAllLineNumbers
123
+ ? renderMarkdownTracked(message.content, measureWidth, { codeBlockLineNumbers: false }).lines.length
124
+ : 0;
125
+ const numWidth = Math.max(3, String(totalLines).length);
126
+ const gutterW = numWidth + 3;
127
+ const contentWidth = showAllLineNumbers ? width - gutterW : width;
128
+ const renderWidth = showAllLineNumbers ? contentWidth : width;
129
+
130
+ const { lines: tracked, codeBlocks } = renderMarkdownTracked(message.content, renderWidth, {
131
+ codeBlockLineNumbers: showCodeBlockLineNumbers,
132
+ });
133
+
134
+ const msgBaseLineOffset = context.history.getLineCount();
135
+ for (const cb of codeBlocks) {
136
+ const blockStartLine = msgBaseLineOffset + cb.startOffset;
137
+ const blockIdx = context.blockRegistry.length;
138
+ const collapseKey = `code_${msgIdx}_${blockIdx}`;
139
+ const isAutoCollapsed = cb.rawContent.split('\n').length > collapseThreshold;
140
+ if (isAutoCollapsed && !context.collapseState.has(collapseKey)) {
141
+ context.collapseState.set(collapseKey, true);
142
+ }
143
+ context.blockRegistry.push({
144
+ blockIndex: blockIdx,
145
+ collapseKey,
146
+ type: 'code',
147
+ startLine: blockStartLine,
148
+ lineCount: cb.lineCount,
149
+ rawContent: cb.rawContent,
150
+ });
151
+ }
152
+
153
+ if (showAllLineNumbers) {
154
+ const numbered = tracked.map((line, i) => {
155
+ const label = String(i + 1).padStart(numWidth) + ' │ ';
156
+ const gutterCells = UIFactory.stringToLine(label, gutterW, { fg: '238', dim: true });
157
+ const fullLine = createEmptyLine(width);
158
+ for (let ci = 0; ci < gutterW && ci < gutterCells.length; ci++) {
159
+ fullLine[ci] = gutterCells[ci];
160
+ }
161
+ for (let ci = 0; ci < line.length && gutterW + ci < width; ci++) {
162
+ fullLine[gutterW + ci] = line[ci];
163
+ }
164
+ return fullLine;
165
+ });
166
+ context.history.addLines(numbered);
167
+ } else {
168
+ context.history.addLines(tracked);
169
+ }
170
+ }
171
+
172
+ if (message.toolCalls && message.toolCalls.length > 0) {
173
+ for (const tc of message.toolCalls) {
174
+ context.history.addLines(renderToolCallBlock(tc, 'done', undefined, width));
175
+ }
176
+ }
177
+ }
178
+
179
+ export function renderConversationSystemMessage(
180
+ context: ConversationRenderContext,
181
+ message: Extract<Message, { role: 'system' }>,
182
+ width: number,
183
+ ): void {
184
+ const sysStartLine = context.history.getLineCount();
185
+ const sysLines = renderSystemMessage(message.content, width);
186
+ context.history.addLines(sysLines);
187
+ if (/error/i.test(message.content)) {
188
+ context.errorLineRegistry.push(sysStartLine);
189
+ }
190
+ }
191
+
192
+ export function renderConversationToolMessage(
193
+ context: ConversationRenderContext,
194
+ message: Extract<Message, { role: 'tool' }>,
195
+ width: number,
196
+ msgIdx: number,
197
+ ): void {
198
+ const collapseKey = `msg_${msgIdx}`;
199
+ const blockIdx = context.blockRegistry.length;
200
+ const startLine = context.history.getLineCount();
201
+ const contentLines = message.content.split('\n');
202
+ const lineCount = contentLines.length;
203
+ const hasDiffHeader = contentLines.some((l) => l.startsWith('--- ')) && contentLines.some((l) => l.startsWith('+++ '));
204
+ const hasHunk = contentLines.some((l) => l.startsWith('@@ '));
205
+ const isDiff = hasDiffHeader && hasHunk;
206
+ const blockType: 'diff' | 'tool' = isDiff ? 'diff' : 'tool';
207
+
208
+ const isShort = message.content.length <= 200;
209
+ const isCollapsed = isShort
210
+ ? false
211
+ : context.collapseState.has(collapseKey)
212
+ ? context.collapseState.get(collapseKey)!
213
+ : true;
214
+
215
+ if (!context.collapseState.has(collapseKey)) {
216
+ context.collapseState.set(collapseKey, isShort ? false : true);
217
+ }
218
+
219
+ context.history.addLine(renderConversationEventLine(width, {
220
+ marker: blockType === 'diff' ? GLYPHS.status.dualPane : GLYPHS.status.active,
221
+ markerFg: blockType === 'diff' ? '#f59e0b' : '#38bdf8',
222
+ label: blockType === 'diff' ? 'diff' : 'tool result',
223
+ labelFg: blockType === 'diff' ? '#f59e0b' : '#38bdf8',
224
+ detailFg: '244',
225
+ }, [
226
+ ...(message.toolName
227
+ ? [{ text: ` ${message.toolName} `, fg: '#e2e8f0' as const }]
228
+ : [{ text: ` ${summarizeCallId(message.callId || 'standalone')} `, fg: '244' as const, dim: true }]),
229
+ { text: ` ${isCollapsed ? GLYPHS.navigation.collapsed : GLYPHS.navigation.expanded} ${lineCount} line${lineCount === 1 ? '' : 's'} `, fg: '244', dim: true },
230
+ ]));
231
+
232
+ if (isCollapsed) {
233
+ const collapseSuffixReserve = 30;
234
+ const preview = contentLines[0].slice(0, width - LAYOUT.LEFT_MARGIN - LAYOUT.RIGHT_MARGIN - collapseSuffixReserve);
235
+ const hiddenCount = lineCount - 1;
236
+ const collapsedText = hiddenCount > 0
237
+ ? `${preview}... [${GLYPHS.navigation.collapsed} ${hiddenCount} hidden]`
238
+ : preview;
239
+ const rendered = renderConversationCollapsedFragment(collapsedText, width, {
240
+ prefix: blockType === 'diff' ? ` ${GLYPHS.status.dualPane} ` : ` ${GLYPHS.navigation.collapsed} `,
241
+ prefixFg: blockType === 'diff' ? '#f59e0b' : '#38bdf8',
242
+ text: '244',
243
+ bodyBg: '#1a1a1a',
244
+ dim: true,
245
+ });
246
+ context.history.addLines(rendered);
247
+ } else {
248
+ let contentToRender = message.content;
249
+ const trimmed = contentToRender.trimStart();
250
+ if ((trimmed.startsWith('{') || trimmed.startsWith('[')) && contentToRender.length < 100_000) {
251
+ try {
252
+ const parsed = JSON.parse(contentToRender);
253
+ contentToRender = `\`\`\`json\n${JSON.stringify(parsed, null, 2)}\n\`\`\``;
254
+ } catch {
255
+ // Leave invalid JSON as-is.
256
+ }
257
+ }
258
+ context.history.addLines(renderMarkdownTracked(contentToRender, width).lines);
259
+ }
260
+
261
+ const renderedLineCount = context.history.getLineCount() - startLine;
262
+ let meta: BlockMeta = {
263
+ blockIndex: blockIdx,
264
+ collapseKey,
265
+ type: blockType,
266
+ startLine,
267
+ lineCount: renderedLineCount,
268
+ rawContent: message.content,
269
+ };
270
+
271
+ if (isDiff) {
272
+ meta = { ...meta, ...parseDiffForApply(message.content) };
273
+ }
274
+
275
+ context.blockRegistry.push(meta);
276
+ }
277
+
278
+ export function appendConversationMessages(
279
+ context: ConversationRenderContext,
280
+ messages: Message[],
281
+ width: number,
282
+ messageLineRegistry: number[],
283
+ ): void {
284
+ const lineNumberMode = context.configManager?.get('display.lineNumbers') ?? 'off';
285
+ const collapseThreshold = context.configManager?.get('display.collapseThreshold') ?? 30;
286
+
287
+ for (let msgIdx = 0; msgIdx < messages.length; msgIdx++) {
288
+ const message = messages[msgIdx];
289
+ messageLineRegistry[msgIdx] = context.history.getLineCount();
290
+ if (message.role === 'user') {
291
+ renderConversationUserMessage(context, message, width);
292
+ } else if (message.role === 'assistant') {
293
+ renderConversationAssistantMessage(context, message, width, lineNumberMode, collapseThreshold, msgIdx);
294
+ } else if (message.role === 'system') {
295
+ renderConversationSystemMessage(context, message, width);
296
+ } else if (message.role === 'tool') {
297
+ renderConversationToolMessage(context, message, width, msgIdx);
298
+ }
299
+ context.history.addLine(createEmptyLine(width));
300
+ }
301
+ }
302
+
303
+ export function addConversationSplashScreen(
304
+ context: ConversationRenderContext,
305
+ width: number,
306
+ ): void {
307
+ const splashStrings = getSplashLines(width, context.splashOptions);
308
+ const cyan = '#00ffff';
309
+ const purple = '#d000ff';
310
+ const grey = '244';
311
+
312
+ splashStrings.forEach((str, y) => {
313
+ const line = UIFactory.stringToLine(str, width);
314
+ const isVersion = y === splashStrings.length - 1;
315
+ const startX = Math.floor((width - getDisplayWidth(str)) / 2);
316
+ const endX = startX + getDisplayWidth(str);
317
+
318
+ for (let x = 0; x < width; x++) {
319
+ const cell = line[x];
320
+ if (cell.char === ' ' && (x < startX || x >= endX)) continue;
321
+ if (isVersion) {
322
+ cell.fg = grey;
323
+ cell.dim = true;
324
+ } else {
325
+ const factor = (x - startX) / (endX - startX || 1);
326
+ cell.fg = interpolateColor(cyan, purple, Math.max(0, Math.min(1, factor)));
327
+ cell.bold = true;
328
+ }
329
+ }
330
+ context.history.addLine(line);
331
+ });
332
+ for (let i = 0; i < 5; i++) {
333
+ context.history.addLine(createEmptyLine(width));
334
+ }
335
+ }
336
+
337
+ export function conversationTextToLines(
338
+ text: string,
339
+ width: number,
340
+ style: Partial<Cell> = {},
341
+ ): Line[] {
342
+ const contentWidth = LAYOUT.contentWidth(width);
343
+ const wrapped = wrapText(text, contentWidth);
344
+ return wrapped.map((line, index) => {
345
+ const prefix = index === 0 ? '>' + ' '.repeat(LAYOUT.LEFT_MARGIN - 1) : ' '.repeat(LAYOUT.LEFT_MARGIN);
346
+ return UIFactory.stringToLine(prefix + line, width, style);
347
+ });
348
+ }
349
+
350
+ export function logConversationText(
351
+ context: Pick<ConversationRenderContext, 'history'>,
352
+ width: number,
353
+ text: string,
354
+ style: Partial<Cell> = {},
355
+ indent = ' '.repeat(LAYOUT.LEFT_MARGIN),
356
+ ): void {
357
+ const lines = text.split('\n').map((line) => UIFactory.stringToLine(indent + line, width, style));
358
+ context.history.addLines(lines);
359
+ }