@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,219 @@
1
+ import { type Line } from '../types/grid.ts';
2
+ import { ModalFactory } from './modal-factory.ts';
3
+ import type { ConversationManager } from '../core/conversation';
4
+ import { getOverlayContentBudget, getOverlaySurfaceMetrics, getStableOverlayContentRows } from './overlay-viewport.ts';
5
+
6
+ // ─── ContextInspectorModal ────────────────────────────────────────────────────
7
+
8
+ /**
9
+ * ContextInspectorModal — state for the context inspector overlay.
10
+ */
11
+ export class ContextInspectorModal {
12
+ public active = false;
13
+
14
+ open(): void {
15
+ this.active = true;
16
+ }
17
+
18
+ close(): void {
19
+ this.active = false;
20
+ }
21
+ }
22
+
23
+ // ─── renderContextInspector ───────────────────────────────────────────────────
24
+
25
+ /** Rough token estimate: 4 chars ≈ 1 token. */
26
+ function estimateTokens(text: string): number {
27
+ return Math.ceil(text.length / 4);
28
+ }
29
+
30
+ /** Format a number with thousands separators. */
31
+ function fmtN(n: number): string {
32
+ return n.toLocaleString();
33
+ }
34
+
35
+ /** Format a percentage as XX.X%. */
36
+ function fmtPct(ratio: number): string {
37
+ return `${(ratio * 100).toFixed(1)}%`;
38
+ }
39
+
40
+ /**
41
+ * Render the context inspector as Line[] for overlay in the viewport.
42
+ *
43
+ * Lists each message with role, estimated token count, and percentage of total
44
+ * context. Highlights large consumers (>10%). Shows total vs context window
45
+ * capacity and suggests compaction targets.
46
+ *
47
+ * @param conversation The conversation manager to inspect.
48
+ * @param width Terminal width.
49
+ * @param _height Terminal height (reserved for future scrolling).
50
+ * @param contextWindow Optional context window size for capacity display.
51
+ */
52
+ export function renderContextInspector(
53
+ conversation: ConversationManager,
54
+ width: number,
55
+ viewportHeight = 24,
56
+ contextWindow = 0,
57
+ ): Line[] {
58
+ const messages = conversation.getMessagesForLLM();
59
+ const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
60
+ margin: 1,
61
+ maxWidth: 78,
62
+ chromeRows: 7,
63
+ minContentRows: 6,
64
+ maxContentRows: 10,
65
+ });
66
+ const targetContentRows = getStableOverlayContentRows(metrics.contentRows, 8);
67
+
68
+ if (messages.length === 0) {
69
+ return ModalFactory.createModal({
70
+ title: 'Context Inspector',
71
+ width: metrics.boxWidth,
72
+ margin: metrics.margin,
73
+ targetContentRows,
74
+ sections: [
75
+ { type: 'text', content: 'No messages in conversation yet.' },
76
+ ],
77
+ hints: ['[Esc] Close'],
78
+ }, width);
79
+ }
80
+
81
+ // ── Token accounting ──────────────────────────────────────────────────────
82
+
83
+ type MsgEntry = {
84
+ role: string;
85
+ tokens: number;
86
+ label: string;
87
+ };
88
+
89
+ const entries: MsgEntry[] = [];
90
+ let totalTokens = 0;
91
+ let largeCount = 0;
92
+
93
+ for (const msg of messages) {
94
+ const role = msg.role;
95
+ let text = '';
96
+ if (typeof msg.content === 'string') {
97
+ text = msg.content;
98
+ } else if (Array.isArray(msg.content)) {
99
+ // ContentPart[]
100
+ text = (msg.content as Array<{ type: string; text?: string }>)
101
+ .filter((p) => p.type === 'text')
102
+ .map((p) => p.text ?? '')
103
+ .join('');
104
+ }
105
+ // Include tool call text for assistant messages
106
+ if (role === 'assistant' && (msg as { toolCalls?: Array<{ name: string; arguments: unknown }> }).toolCalls) {
107
+ const tcs = (msg as { toolCalls?: Array<{ name: string; arguments: unknown }> }).toolCalls!;
108
+ for (const tc of tcs) {
109
+ text += tc.name + JSON.stringify(tc.arguments);
110
+ }
111
+ }
112
+ const tokens = estimateTokens(text);
113
+ totalTokens += tokens;
114
+
115
+ let label: string;
116
+ if (role === 'user') {
117
+ label = `user: ${text.slice(0, 40).replace(/\n/g, ' ')}${text.length > 40 ? '...' : ''}`;
118
+ } else if (role === 'assistant') {
119
+ label = `assistant: ${text.slice(0, 36).replace(/\n/g, ' ')}${text.length > 36 ? '...' : ''}`;
120
+ } else if (role === 'tool') {
121
+ const toolMsg = msg as { callId?: string };
122
+ label = `tool-result (${(toolMsg.callId ?? '').slice(0, 12)})`;
123
+ } else {
124
+ label = role;
125
+ }
126
+
127
+ entries.push({ role, tokens, label });
128
+ }
129
+
130
+ // ── Identify large consumers (>10%) ───────────────────────────────────────
131
+
132
+ const largeThreshold = totalTokens * 0.10;
133
+ for (const e of entries) {
134
+ if (e.tokens > largeThreshold) largeCount++;
135
+ }
136
+
137
+ // ── Build sections ────────────────────────────────────────────────────────
138
+
139
+ const sections: import('./modal-factory.ts').ModalSection[] = [];
140
+
141
+ // Summary header
142
+ const capacityStr = contextWindow > 0
143
+ ? ` | Capacity: ${fmtN(totalTokens)} / ${fmtN(contextWindow)} (${fmtPct(totalTokens / contextWindow)})`
144
+ : '';
145
+ sections.push({
146
+ type: 'text',
147
+ content: `Total: ~${fmtN(totalTokens)} tokens (${messages.length} messages)${capacityStr}`,
148
+ style: { bold: true },
149
+ });
150
+
151
+ if (contextWindow > 0 && totalTokens / contextWindow >= 0.80) {
152
+ sections.push({
153
+ type: 'text',
154
+ content: 'WARNING: context is 80%+ full. Run /compact to free space.',
155
+ style: { fg: '#ff9900', bold: true },
156
+ });
157
+ }
158
+
159
+ sections.push({ type: 'separator' });
160
+
161
+ // Per-message list (up to 20 rows to keep modal manageable)
162
+ const maxVisibleRows = getOverlayContentBudget(viewportHeight, {
163
+ chromeRows: 7,
164
+ minContentRows: 6,
165
+ maxContentRows: 10,
166
+ });
167
+ const display = entries.slice(-maxVisibleRows);
168
+ const startOffset = entries.length - display.length;
169
+ if (startOffset > 0) {
170
+ sections.push({
171
+ type: 'text',
172
+ content: `(${startOffset} older messages not shown)`,
173
+ style: { dim: true },
174
+ });
175
+ }
176
+
177
+ for (let i = 0; i < display.length; i++) {
178
+ const e = display[i];
179
+ const pct = totalTokens > 0 ? e.tokens / totalTokens : 0;
180
+ const pctStr = fmtPct(pct).padStart(6);
181
+ const tokStr = `~${fmtN(e.tokens)}`.padStart(8);
182
+ const isLarge = e.tokens > largeThreshold;
183
+ const marker = isLarge ? '* ' : ' ';
184
+ const line = `${marker}${pctStr} ${tokStr} ${e.label}`;
185
+ sections.push({
186
+ type: 'text',
187
+ content: line,
188
+ style: isLarge ? { fg: '#ffcc00', bold: true } : {},
189
+ });
190
+ }
191
+
192
+ // Compaction suggestions
193
+ const largeMsgs = entries.filter((e) => e.tokens > largeThreshold);
194
+ if (largeMsgs.length > 0) {
195
+ sections.push({ type: 'separator' });
196
+ const largePct = fmtPct(
197
+ largeMsgs.reduce((s, e) => s + e.tokens, 0) / totalTokens,
198
+ );
199
+ sections.push({
200
+ type: 'text',
201
+ content: `Compaction hint: ${largeMsgs.length} message${largeMsgs.length > 1 ? 's' : ''} use ${largePct} of context.`,
202
+ style: { fg: '#00ffcc' },
203
+ });
204
+ sections.push({
205
+ type: 'text',
206
+ content: 'Run /compact to summarise and reduce context size.',
207
+ style: { dim: true },
208
+ });
209
+ }
210
+
211
+ return ModalFactory.createModal({
212
+ title: 'Context Inspector',
213
+ width: metrics.boxWidth,
214
+ margin: metrics.margin,
215
+ targetContentRows,
216
+ sections,
217
+ hints: ['[*] >10% of context', '[Esc] Close'],
218
+ }, width);
219
+ }
@@ -0,0 +1,67 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
+ import type { ConversationManager } from '../core/conversation';
4
+
5
+ export interface ConversationViewportRequest {
6
+ readonly conversation: ConversationManager;
7
+ readonly width: number;
8
+ readonly viewportHeight: number;
9
+ readonly scrollTop: number;
10
+ readonly scrollLocked: boolean;
11
+ readonly overlayRows?: number;
12
+ }
13
+
14
+ export interface ConversationViewportResult {
15
+ readonly effectiveHeight: number;
16
+ readonly maxScroll: number;
17
+ readonly nextScrollTop: number;
18
+ readonly viewport: Line[];
19
+ }
20
+
21
+ export function buildConversationViewport(
22
+ request: ConversationViewportRequest,
23
+ ): ConversationViewportResult {
24
+ const overlayRows = request.overlayRows ?? 0;
25
+ const effectiveHeight = Math.max(0, request.viewportHeight - overlayRows);
26
+ const lineCount = request.conversation.history.getLineCount();
27
+ const maxScroll = Math.max(0, lineCount - effectiveHeight);
28
+ const nextScrollTop = request.scrollLocked
29
+ ? maxScroll
30
+ : Math.max(0, Math.min(request.scrollTop, maxScroll));
31
+ const viewport = request.conversation.history.getSnapshot(nextScrollTop, effectiveHeight, request.width);
32
+
33
+ return {
34
+ effectiveHeight,
35
+ maxScroll,
36
+ nextScrollTop,
37
+ viewport,
38
+ };
39
+ }
40
+
41
+ export function overlayViewportBottom(
42
+ viewport: readonly Line[],
43
+ overlay: readonly Line[],
44
+ width: number,
45
+ viewportHeight: number,
46
+ bottomInset: number = 0,
47
+ ): Line[] {
48
+ if (overlay.length === 0) return [...viewport];
49
+ const next = [...viewport];
50
+ const targetStart = Math.max(0, viewportHeight - bottomInset - overlay.length);
51
+ next.length = Math.min(next.length, targetStart);
52
+ while (next.length < targetStart) next.push(createEmptyLine(width));
53
+ next.push(...overlay);
54
+ return next;
55
+ }
56
+
57
+ export function replaceViewportWithOverlay(
58
+ overlay: readonly Line[],
59
+ width: number,
60
+ viewportHeight: number,
61
+ ): Line[] {
62
+ const next: Line[] = [];
63
+ const pad = Math.max(0, viewportHeight - overlay.length);
64
+ for (let i = 0; i < pad; i++) next.push(createEmptyLine(width));
65
+ next.push(...overlay);
66
+ return next;
67
+ }
@@ -0,0 +1,140 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import type { ConversationManager } from '../core/conversation';
3
+ import type { CommandRegistry } from '../input/command-registry.ts';
4
+ import type { InputHandler } from '../input/handler.ts';
5
+ import type { KeybindingsManager } from '../input/keybindings.ts';
6
+ import { renderFilePickerOverlay } from './file-picker-overlay.ts';
7
+ import { renderModelWorkspace } from './model-workspace.ts';
8
+ import { renderSelectionModalOverlay } from './selection-modal-overlay.ts';
9
+ import { renderSearchOverlay } from './search-overlay.ts';
10
+ import { renderHistorySearchOverlay } from './history-search-overlay.ts';
11
+ import { renderProcessModal } from './process-modal.ts';
12
+ import { renderAgentDetailModal } from './agent-detail-modal.ts';
13
+ import { renderLiveTailModal } from './live-tail-modal.ts';
14
+ import { renderContextInspector } from './context-inspector.ts';
15
+ import { renderSettingsModal } from './settings-modal.ts';
16
+ import { renderMcpWorkspace } from './mcp-workspace.ts';
17
+ import { renderAgentWorkspace } from './agent-workspace.ts';
18
+ import { renderSessionPickerModal } from './session-picker-modal.ts';
19
+ import { renderProfilePickerModal } from './profile-picker-modal.ts';
20
+ import { renderBookmarkModal } from './bookmark-modal.ts';
21
+ import { renderHelpOverlay, renderShortcutsOverlay } from './help-overlay.ts';
22
+ import { renderAutocompleteOverlay } from './autocomplete-overlay.ts';
23
+ import { renderOnboardingWizard } from './onboarding/onboarding-wizard.ts';
24
+ import { overlayViewportBottom, replaceViewportWithOverlay } from './conversation-layout.ts';
25
+
26
+ export interface ConversationOverlayContext {
27
+ readonly input: InputHandler;
28
+ readonly conversation: ConversationManager;
29
+ readonly commandRegistry: CommandRegistry;
30
+ readonly keybindingsManager: KeybindingsManager;
31
+ readonly conversationWidth: number;
32
+ readonly viewportHeight: number;
33
+ readonly contextWindow?: number;
34
+ }
35
+
36
+ export function applyConversationOverlays(
37
+ viewport: Line[],
38
+ context: ConversationOverlayContext,
39
+ ): Line[] {
40
+ const { input, conversation, commandRegistry, keybindingsManager, conversationWidth, viewportHeight, contextWindow } = context;
41
+ let next = viewport;
42
+ const bottomDockInset = 1 + (input.searchManager.active || input.historySearch.active ? 1 : 0);
43
+
44
+ if (input.onboardingWizard.active) {
45
+ const lines = renderOnboardingWizard(input.onboardingWizard, conversationWidth, viewportHeight);
46
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
47
+ }
48
+
49
+ if (input.filePicker.active) {
50
+ const lines = renderFilePickerOverlay(input.filePicker, conversationWidth, viewportHeight);
51
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
52
+ }
53
+
54
+ if (input.modelPicker.active) {
55
+ const lines = renderModelWorkspace(input.modelPicker, conversationWidth, viewportHeight);
56
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
57
+ }
58
+
59
+ if (input.selectionModal.active) {
60
+ const lines = renderSelectionModalOverlay(input.selectionModal, conversationWidth, viewportHeight);
61
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
62
+ }
63
+
64
+ if (input.searchManager.active) {
65
+ next.push(...renderSearchOverlay(input.searchManager, conversationWidth));
66
+ }
67
+
68
+ if (input.historySearch.active) {
69
+ next.push(...renderHistorySearchOverlay(input.historySearch, conversationWidth));
70
+ }
71
+
72
+ if (input.processModal.active) {
73
+ const lines = renderProcessModal(input.processModal, conversationWidth, viewportHeight);
74
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
75
+ }
76
+
77
+ if (input.agentDetailModal.active) {
78
+ const lines = renderAgentDetailModal(input.agentDetailModal, conversationWidth);
79
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
80
+ }
81
+
82
+ if (input.liveTailModal.active) {
83
+ const lines = renderLiveTailModal(input.liveTailModal, conversationWidth, viewportHeight);
84
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
85
+ }
86
+
87
+ if (input.contextInspectorModal.active) {
88
+ const lines = renderContextInspector(conversation, conversationWidth, viewportHeight, contextWindow);
89
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
90
+ }
91
+
92
+ if (input.settingsModal.active) {
93
+ const lines = renderSettingsModal(input.settingsModal, conversationWidth, viewportHeight);
94
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
95
+ }
96
+
97
+ if (input.mcpWorkspace.active) {
98
+ const lines = renderMcpWorkspace(input.mcpWorkspace, conversationWidth, viewportHeight);
99
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
100
+ }
101
+
102
+ if (input.agentWorkspace.active) {
103
+ const lines = renderAgentWorkspace(input.agentWorkspace, conversationWidth, viewportHeight);
104
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
105
+ }
106
+
107
+ if (input.sessionPickerModal.active) {
108
+ const lines = renderSessionPickerModal(input.sessionPickerModal, conversationWidth, viewportHeight);
109
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
110
+ }
111
+
112
+ if (input.profilePickerModal.active) {
113
+ const lines = renderProfilePickerModal(input.profilePickerModal, conversationWidth, viewportHeight);
114
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
115
+ }
116
+
117
+ if (input.bookmarkModal.active) {
118
+ const lines = renderBookmarkModal(input.bookmarkModal, conversationWidth, viewportHeight);
119
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
120
+ }
121
+
122
+ if (input.helpOverlayActive) {
123
+ const lines = renderHelpOverlay(conversationWidth, keybindingsManager, commandRegistry.getAll(), input.helpScrollOffset, viewportHeight);
124
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
125
+ }
126
+
127
+ if (input.shortcutsOverlayActive) {
128
+ const lines = renderShortcutsOverlay(conversationWidth, keybindingsManager, input.shortcutsScrollOffset, viewportHeight);
129
+ next = replaceViewportWithOverlay(lines, conversationWidth, viewportHeight);
130
+ }
131
+
132
+ if (input.commandMode && input.autocomplete?.isActive) {
133
+ const lines = renderAutocompleteOverlay(input.autocomplete, conversationWidth, viewportHeight);
134
+ if (lines.length > 0) {
135
+ next = overlayViewportBottom(next, lines, conversationWidth, viewportHeight, bottomDockInset);
136
+ }
137
+ }
138
+
139
+ return next;
140
+ }
@@ -0,0 +1,260 @@
1
+ import { type Line, createEmptyLine, createStyledCell } from '../types/grid.ts';
2
+ import { getDisplayWidth, truncateDisplay, wrapText } from '../utils/terminal-width.ts';
3
+ import { LAYOUT } from './layout.ts';
4
+ import { GLYPHS } from './ui-primitives.ts';
5
+
6
+ export interface ConversationSurfacePalette {
7
+ readonly accent: string;
8
+ readonly text: string;
9
+ readonly dim?: boolean;
10
+ readonly bodyBg?: string;
11
+ readonly italic?: boolean;
12
+ }
13
+
14
+ export interface ConversationFragmentPalette {
15
+ readonly prefix: string;
16
+ readonly prefixFg: string;
17
+ readonly text: string;
18
+ readonly bodyBg: string;
19
+ readonly dim?: boolean;
20
+ readonly italic?: boolean;
21
+ readonly strikethrough?: boolean;
22
+ }
23
+
24
+ export interface ConversationStatusSegment {
25
+ readonly text: string;
26
+ readonly fg: string;
27
+ readonly bold?: boolean;
28
+ readonly dim?: boolean;
29
+ readonly italic?: boolean;
30
+ }
31
+
32
+ export interface ConversationEventTone {
33
+ readonly marker: string;
34
+ readonly markerFg: string;
35
+ readonly label: string;
36
+ readonly labelFg: string;
37
+ readonly detailFg?: string;
38
+ }
39
+
40
+ function writeText(
41
+ line: Line,
42
+ startCol: number,
43
+ endColExclusive: number,
44
+ text: string,
45
+ fg: string,
46
+ options: { readonly bg?: string; readonly bold?: boolean; readonly dim?: boolean; readonly italic?: boolean; readonly strikethrough?: boolean } = {},
47
+ ): void {
48
+ let col = startCol;
49
+ for (const ch of text) {
50
+ const w = getDisplayWidth(ch);
51
+ if (w <= 0) continue;
52
+ if (col + w > endColExclusive) break;
53
+ line[col] = createStyledCell(ch, {
54
+ fg,
55
+ bg: options.bg ?? '',
56
+ bold: options.bold ?? false,
57
+ dim: options.dim ?? false,
58
+ italic: options.italic ?? false,
59
+ strikethrough: options.strikethrough ?? false,
60
+ });
61
+ if (w > 1 && col + 1 < endColExclusive) {
62
+ line[col + 1] = createStyledCell('', {
63
+ fg,
64
+ bg: options.bg ?? '',
65
+ bold: options.bold ?? false,
66
+ dim: options.dim ?? false,
67
+ italic: options.italic ?? false,
68
+ strikethrough: options.strikethrough ?? false,
69
+ });
70
+ }
71
+ col += w;
72
+ }
73
+ }
74
+
75
+ export function renderConversationNotice(
76
+ content: string,
77
+ width: number,
78
+ palette: ConversationSurfacePalette,
79
+ marker = '▌',
80
+ ): Line[] {
81
+ const borderCol = LAYOUT.LEFT_MARGIN - 1;
82
+ const textStartCol = LAYOUT.LEFT_MARGIN + 1;
83
+ const textWidth = Math.max(1, width - textStartCol - LAYOUT.RIGHT_MARGIN);
84
+ const wrapped = wrapText(content, textWidth);
85
+ const lines: Line[] = [];
86
+
87
+ for (const text of wrapped) {
88
+ const line = createEmptyLine(width);
89
+ line[borderCol] = createStyledCell(marker, { fg: palette.accent, bg: palette.bodyBg ?? '' });
90
+ writeText(line, textStartCol, width - LAYOUT.RIGHT_MARGIN, text, palette.text, {
91
+ bg: palette.bodyBg,
92
+ dim: palette.dim ?? false,
93
+ italic: palette.italic ?? false,
94
+ });
95
+ lines.push(line);
96
+ }
97
+
98
+ return lines;
99
+ }
100
+
101
+ export function renderConversationFragment(
102
+ content: string,
103
+ width: number,
104
+ palette: ConversationFragmentPalette,
105
+ ): Line[] {
106
+ const margin = LAYOUT.USER_BOX_MARGIN;
107
+ const prefixWidth = getDisplayWidth(palette.prefix);
108
+ const maxContentWidth = Math.max(1, width - (margin * 2) - prefixWidth - 2);
109
+ const wrapped = wrapText(content, maxContentWidth);
110
+ const contentWidth = wrapped.length > 0 ? Math.max(...wrapped.map((line) => getDisplayWidth(line))) : 0;
111
+ const fragmentWidth = Math.max(prefixWidth + 2, prefixWidth + contentWidth + 2);
112
+ const startCol = margin;
113
+ const lines: Line[] = [];
114
+
115
+ const createFilledLine = (): Line => {
116
+ const line = createEmptyLine(width);
117
+ for (let x = 0; x < fragmentWidth && startCol + x < width; x++) {
118
+ line[startCol + x] = createStyledCell(' ', {
119
+ fg: palette.text,
120
+ bg: palette.bodyBg,
121
+ dim: palette.dim ?? false,
122
+ italic: palette.italic ?? false,
123
+ strikethrough: palette.strikethrough ?? false,
124
+ });
125
+ }
126
+ return line;
127
+ };
128
+
129
+ const topLine = createEmptyLine(width);
130
+ const bottomLine = createEmptyLine(width);
131
+ for (let x = 0; x < fragmentWidth && startCol + x < width; x++) {
132
+ topLine[startCol + x] = createStyledCell(GLYPHS.surface.top, {
133
+ fg: palette.bodyBg,
134
+ bg: '',
135
+ dim: palette.dim ?? false,
136
+ italic: palette.italic ?? false,
137
+ });
138
+ bottomLine[startCol + x] = createStyledCell(GLYPHS.surface.bottom, {
139
+ fg: palette.bodyBg,
140
+ bg: '',
141
+ dim: palette.dim ?? false,
142
+ italic: palette.italic ?? false,
143
+ });
144
+ }
145
+ lines.push(topLine);
146
+ for (let index = 0; index < wrapped.length; index++) {
147
+ const line = createFilledLine();
148
+ const prefix = index === 0 ? palette.prefix : ' '.repeat(prefixWidth);
149
+ writeText(line, startCol, startCol + prefixWidth, prefix, palette.prefixFg, {
150
+ bg: palette.bodyBg,
151
+ dim: palette.dim ?? false,
152
+ italic: palette.italic ?? false,
153
+ });
154
+ writeText(line, startCol + prefixWidth, startCol + fragmentWidth - 1, wrapped[index] ?? '', palette.text, {
155
+ bg: palette.bodyBg,
156
+ dim: palette.dim ?? false,
157
+ italic: palette.italic ?? false,
158
+ strikethrough: palette.strikethrough ?? false,
159
+ });
160
+ lines.push(line);
161
+ }
162
+ lines.push(bottomLine);
163
+ return lines;
164
+ }
165
+
166
+ export function renderConversationCollapsedFragment(
167
+ content: string,
168
+ width: number,
169
+ options: {
170
+ readonly prefix?: string;
171
+ readonly prefixFg?: string;
172
+ readonly text?: string;
173
+ readonly bodyBg?: string;
174
+ readonly dim?: boolean;
175
+ readonly italic?: boolean;
176
+ } = {},
177
+ ): Line[] {
178
+ return renderConversationFragment(content, width, {
179
+ prefix: options.prefix ?? ` ${GLYPHS.navigation.selected} `,
180
+ prefixFg: options.prefixFg ?? '#38bdf8',
181
+ text: options.text ?? '244',
182
+ bodyBg: options.bodyBg ?? '#1a1a1a',
183
+ dim: options.dim ?? true,
184
+ italic: options.italic ?? false,
185
+ });
186
+ }
187
+
188
+ export function renderConversationKeyValueRow(
189
+ width: number,
190
+ left: string,
191
+ right: string,
192
+ palette: {
193
+ readonly leftFg: string;
194
+ readonly rightFg: string;
195
+ readonly dimFg?: string;
196
+ readonly bg?: string;
197
+ },
198
+ ): Line {
199
+ const line = createEmptyLine(width);
200
+ const leftText = truncateDisplay(left, Math.max(1, width - LAYOUT.RIGHT_MARGIN - 8));
201
+ const rightWidth = getDisplayWidth(right);
202
+ const rightStart = Math.max(LAYOUT.LEFT_MARGIN + 1, width - LAYOUT.RIGHT_MARGIN - rightWidth);
203
+ writeText(line, LAYOUT.LEFT_MARGIN, rightStart - 1, leftText, palette.leftFg, { bg: palette.bg });
204
+ writeText(line, rightStart, width - LAYOUT.RIGHT_MARGIN, right, palette.rightFg, { bg: palette.bg, dim: palette.dimFg === palette.rightFg });
205
+ return line;
206
+ }
207
+
208
+ export function renderConversationStatusLine(
209
+ width: number,
210
+ segments: readonly ConversationStatusSegment[],
211
+ options: {
212
+ readonly marker?: string;
213
+ readonly markerFg?: string;
214
+ readonly markerBg?: string;
215
+ readonly bodyBg?: string;
216
+ } = {},
217
+ ): Line {
218
+ const line = createEmptyLine(width);
219
+ const markerCol = LAYOUT.LEFT_MARGIN - 1;
220
+ const startCol = LAYOUT.LEFT_MARGIN + 1;
221
+ const endCol = Math.max(startCol, width - LAYOUT.RIGHT_MARGIN);
222
+ line[markerCol] = createStyledCell(options.marker ?? '▌', {
223
+ fg: options.markerFg ?? '#64748b',
224
+ bg: options.markerBg ?? options.bodyBg ?? '',
225
+ bold: true,
226
+ });
227
+ let col = startCol;
228
+ for (const segment of segments) {
229
+ if (col >= endCol) break;
230
+ writeText(line, col, endCol, segment.text, segment.fg, {
231
+ bg: options.bodyBg,
232
+ bold: segment.bold ?? false,
233
+ dim: segment.dim ?? false,
234
+ italic: segment.italic ?? false,
235
+ });
236
+ col += getDisplayWidth(segment.text);
237
+ }
238
+ return line;
239
+ }
240
+
241
+ export function renderConversationEventLine(
242
+ width: number,
243
+ tone: ConversationEventTone,
244
+ details: readonly ConversationStatusSegment[] = [],
245
+ ): Line {
246
+ return renderConversationStatusLine(
247
+ width,
248
+ [
249
+ { text: ` ${tone.label} `, fg: tone.labelFg, bold: true },
250
+ ...details.map((segment) => ({
251
+ ...segment,
252
+ fg: segment.fg || tone.detailFg || tone.labelFg,
253
+ })),
254
+ ],
255
+ {
256
+ marker: tone.marker,
257
+ markerFg: tone.markerFg,
258
+ },
259
+ );
260
+ }