@gajae-code/coding-agent 0.11.1 → 0.11.3

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 (413) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/dist/types/cli/config-cli.d.ts +11 -1
  3. package/dist/types/cli/launch-disposition.d.ts +23 -0
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +141 -11
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  54. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  55. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  56. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  57. package/dist/types/goals/runtime.d.ts +1 -0
  58. package/dist/types/goals/state.d.ts +8 -0
  59. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  60. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  61. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  62. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  63. package/dist/types/hindsight/state.d.ts +11 -4
  64. package/dist/types/lsp/lspmux.d.ts +5 -3
  65. package/dist/types/lsp/path-trust.d.ts +1 -0
  66. package/dist/types/main.d.ts +4 -2
  67. package/dist/types/memories/storage.d.ts +1 -0
  68. package/dist/types/modes/action-registry.d.ts +29 -0
  69. package/dist/types/modes/components/chrome.d.ts +14 -0
  70. package/dist/types/modes/components/command-palette.d.ts +27 -0
  71. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  72. package/dist/types/modes/components/index.d.ts +2 -1
  73. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  74. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  75. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  76. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  77. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  78. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  79. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  80. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  81. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  82. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  83. package/dist/types/modes/components/status-line.d.ts +1 -94
  84. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  85. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  86. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  87. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  88. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  89. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  90. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  91. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  92. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  93. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  94. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  95. package/dist/types/modes/interactive-mode.d.ts +19 -17
  96. package/dist/types/modes/jobs-observer.d.ts +2 -0
  97. package/dist/types/modes/prompt-action-autocomplete.d.ts +4 -0
  98. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  99. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +20 -2
  100. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  101. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  102. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  103. package/dist/types/modes/types.d.ts +20 -13
  104. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  105. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  106. package/dist/types/sdk/broker/broker.d.ts +78 -12
  107. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  108. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  109. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  110. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  111. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  112. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  113. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  114. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  115. package/dist/types/sdk/bus/config.d.ts +11 -0
  116. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  117. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  118. package/dist/types/sdk/bus/index.d.ts +29 -0
  119. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  120. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  121. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  122. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  123. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  124. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  125. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  126. package/dist/types/sdk/bus/telegram-daemon.d.ts +46 -3
  127. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  128. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  129. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  130. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  131. package/dist/types/sdk/host/host.d.ts +2 -0
  132. package/dist/types/sdk/index.d.ts +1 -0
  133. package/dist/types/sdk/session-directory.d.ts +56 -0
  134. package/dist/types/sdk/session.d.ts +2 -0
  135. package/dist/types/session/agent-session.d.ts +82 -47
  136. package/dist/types/session/agent-storage.d.ts +7 -0
  137. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  138. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  139. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  140. package/dist/types/session/messages.d.ts +16 -0
  141. package/dist/types/session/session-manager-internal.d.ts +14 -0
  142. package/dist/types/session/session-manager.d.ts +82 -12
  143. package/dist/types/session/session-storage.d.ts +43 -3
  144. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  145. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  146. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  147. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  148. package/dist/types/system-prompt.d.ts +2 -11
  149. package/dist/types/task/executor.d.ts +3 -1
  150. package/dist/types/thinking.d.ts +7 -0
  151. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  152. package/dist/types/tools/ask.d.ts +64 -2
  153. package/dist/types/tools/bash.d.ts +3 -0
  154. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  155. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  156. package/dist/types/tools/cron.d.ts +2 -0
  157. package/dist/types/tools/fetch.d.ts +1 -0
  158. package/dist/types/tools/index.d.ts +30 -15
  159. package/dist/types/tools/output-meta.d.ts +1 -0
  160. package/dist/types/tools/read.d.ts +5 -0
  161. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  162. package/dist/types/tools/subagent.d.ts +1 -0
  163. package/dist/types/utils/event-bus.d.ts +1 -1
  164. package/dist/types/utils/file-mentions.d.ts +3 -3
  165. package/dist/types/web/search/index.d.ts +3 -1
  166. package/package.json +21 -9
  167. package/scripts/build-sdk-package-smoke.ts +34 -1
  168. package/scripts/generate-hotkeys-docs.ts +74 -0
  169. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  170. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  171. package/src/async/job-manager.ts +49 -6
  172. package/src/cli/config-cli.ts +75 -2
  173. package/src/cli/launch-disposition.ts +42 -0
  174. package/src/cli/notify-cli.ts +92 -26
  175. package/src/cli/session-picker.ts +20 -3
  176. package/src/cli/setup-cli.ts +1 -1
  177. package/src/cli.ts +1 -1
  178. package/src/commands/config.ts +1 -1
  179. package/src/commands/harness.ts +34 -106
  180. package/src/commands/launch.ts +6 -4
  181. package/src/commands/mcp-serve.ts +85 -8
  182. package/src/commands/mcp.ts +2 -2
  183. package/src/commands/notify.ts +8 -4
  184. package/src/commands/sdk.ts +61 -40
  185. package/src/commands/ultragoal.ts +4 -1
  186. package/src/config/atomic-yaml-patch.ts +80 -13
  187. package/src/config/file-lock.ts +105 -43
  188. package/src/config/keybindings.ts +64 -20
  189. package/src/config/model-auth.ts +9 -0
  190. package/src/config/model-bindings-applier.ts +139 -0
  191. package/src/config/model-discovery-manager.ts +224 -0
  192. package/src/config/model-equivalence.ts +66 -0
  193. package/src/config/model-registry.ts +169 -302
  194. package/src/config/model-resolver.ts +361 -369
  195. package/src/config/models-config-schema.ts +5 -4
  196. package/src/config/settings-schema.ts +214 -12
  197. package/src/config/settings.ts +458 -482
  198. package/src/coordinator-mcp/policy.ts +76 -11
  199. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  200. package/src/coordinator-mcp/question-state.ts +778 -0
  201. package/src/coordinator-mcp/server.ts +1702 -238
  202. package/src/debug/index.ts +1 -1
  203. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  204. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  205. package/src/discovery/helpers.ts +1 -50
  206. package/src/edit/index.ts +64 -0
  207. package/src/eval/py/env.ts +26 -0
  208. package/src/eval/py/kernel.ts +5 -3
  209. package/src/extensibility/extensions/loader.ts +9 -1
  210. package/src/extensibility/extensions/runner.ts +12 -3
  211. package/src/extensibility/extensions/types.ts +39 -1
  212. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  213. package/src/extensibility/hooks/runner.ts +2 -2
  214. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  215. package/src/extensibility/shared-events.ts +2 -0
  216. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  217. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  218. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  219. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  220. package/src/gjc-runtime/launch-tmux.ts +2 -5
  221. package/src/gjc-runtime/launch-worktree.ts +5 -5
  222. package/src/gjc-runtime/linux-proc.ts +71 -0
  223. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  224. package/src/gjc-runtime/session-resolution.ts +18 -3
  225. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  226. package/src/gjc-runtime/state-migrations.ts +1 -1
  227. package/src/gjc-runtime/state-runtime.ts +327 -249
  228. package/src/gjc-runtime/state-writer.ts +36 -40
  229. package/src/gjc-runtime/team-launch.ts +319 -0
  230. package/src/gjc-runtime/team-notify.ts +326 -0
  231. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  232. package/src/gjc-runtime/team-store.ts +828 -0
  233. package/src/gjc-runtime/team-workers.ts +962 -0
  234. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  235. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  236. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  237. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  238. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  239. package/src/gjc-runtime/ultragoal-guard.ts +29 -5
  240. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  241. package/src/gjc-runtime/ultragoal-runtime.ts +84 -1311
  242. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  243. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  244. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  245. package/src/goals/continuation-timeout-guard.ts +79 -0
  246. package/src/goals/runtime.ts +7 -5
  247. package/src/goals/state.ts +18 -0
  248. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  249. package/src/harness-control-plane/phase-rollup.ts +4 -7
  250. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  251. package/src/harness-control-plane/receipt-spool.ts +51 -7
  252. package/src/harness-control-plane/session-lease.ts +12 -130
  253. package/src/hindsight/backend.ts +26 -27
  254. package/src/hindsight/state.ts +101 -32
  255. package/src/hooks/skill-state.ts +17 -11
  256. package/src/internal-urls/docs-index.generated.ts +21 -18
  257. package/src/lsp/client.ts +1 -1
  258. package/src/lsp/config.ts +145 -26
  259. package/src/lsp/index.ts +1 -1
  260. package/src/lsp/lspmux.ts +39 -10
  261. package/src/lsp/path-trust.ts +81 -0
  262. package/src/main.ts +127 -30
  263. package/src/memories/index.ts +1 -0
  264. package/src/memories/storage.ts +6 -2
  265. package/src/modes/action-registry.ts +165 -0
  266. package/src/modes/components/chrome.ts +40 -0
  267. package/src/modes/components/command-palette.ts +168 -0
  268. package/src/modes/components/custom-editor.ts +1 -1
  269. package/src/modes/components/custom-provider-wizard.ts +51 -14
  270. package/src/modes/components/index.ts +2 -1
  271. package/src/modes/components/pet-selector.ts +11 -17
  272. package/src/modes/components/plan-preview-overlay.ts +295 -0
  273. package/src/modes/components/queue-mode-selector.ts +10 -27
  274. package/src/modes/components/queue-pane.ts +79 -0
  275. package/src/modes/components/read-tool-group.ts +13 -0
  276. package/src/modes/components/session-observer-overlay.ts +207 -772
  277. package/src/modes/components/sessions-dashboard.ts +181 -0
  278. package/src/modes/components/settings-selector.ts +1 -1
  279. package/src/modes/components/show-images-selector.ts +10 -24
  280. package/src/modes/components/status-line/types.ts +1 -1
  281. package/src/modes/components/status-line.ts +1 -969
  282. package/src/modes/components/tasks-pane.ts +73 -0
  283. package/src/modes/components/theme-selector.ts +11 -31
  284. package/src/modes/components/tool-execution.ts +22 -0
  285. package/src/modes/components/tool-status-header.ts +1056 -0
  286. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  287. package/src/modes/controllers/btw-controller.ts +1 -0
  288. package/src/modes/controllers/event-controller.ts +3 -2
  289. package/src/modes/controllers/extension-ui-controller.ts +24 -1
  290. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  291. package/src/modes/controllers/input-controller.ts +764 -229
  292. package/src/modes/controllers/mode-gate.ts +24 -0
  293. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  294. package/src/modes/controllers/selector-controller.ts +225 -10
  295. package/src/modes/controllers/stt-controller.ts +125 -0
  296. package/src/modes/interactive-mode.ts +289 -1150
  297. package/src/modes/jobs-observer.ts +3 -0
  298. package/src/modes/prompt-action-autocomplete.ts +7 -0
  299. package/src/modes/runtime-init.ts +8 -0
  300. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  301. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +71 -16
  302. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  303. package/src/modes/tasks-aggregator.ts +199 -0
  304. package/src/modes/transcript-item-registry.ts +229 -0
  305. package/src/modes/types.ts +45 -18
  306. package/src/modes/utils/ui-helpers.ts +17 -9
  307. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  308. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  309. package/src/prompts/agents/architect.md +2 -8
  310. package/src/prompts/agents/critic.md +2 -8
  311. package/src/prompts/agents/executor.md +3 -0
  312. package/src/prompts/agents/planner.md +4 -12
  313. package/src/prompts/system/custom-system-prompt.md +1 -23
  314. package/src/prompts/system/plan-mode-approved.md +8 -0
  315. package/src/prompts/system/system-prompt.md +17 -96
  316. package/src/prompts/tools/bash.md +4 -4
  317. package/src/prompts/tools/browser.md +15 -52
  318. package/src/prompts/tools/find.md +2 -2
  319. package/src/prompts/tools/hashline.md +13 -55
  320. package/src/prompts/tools/read.md +3 -6
  321. package/src/prompts/tools/search-tool-bm25.md +0 -2
  322. package/src/prompts/tools/search.md +2 -4
  323. package/src/prompts/tools/skill-discovery.md +1 -2
  324. package/src/prompts/tools/task.md +1 -1
  325. package/src/runtime-mcp/manager.ts +1 -1
  326. package/src/sdk/broker/broker.ts +206 -78
  327. package/src/sdk/broker/discovery.ts +1 -1
  328. package/src/sdk/broker/ensure.ts +3 -1
  329. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  330. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  331. package/src/sdk/broker/lifecycle.ts +1399 -142
  332. package/src/sdk/broker/process-incarnation.ts +5 -13
  333. package/src/sdk/broker/session-index.ts +145 -56
  334. package/src/sdk/broker/state-version.ts +21 -0
  335. package/src/sdk/broker/transport.ts +1 -1
  336. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  337. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  338. package/src/sdk/bus/chat-effect-journal.ts +10 -2
  339. package/src/sdk/bus/config-commands.ts +5 -4
  340. package/src/sdk/bus/config.ts +98 -0
  341. package/src/sdk/bus/conversation-store.ts +5 -29
  342. package/src/sdk/bus/daemon-paths.ts +2 -0
  343. package/src/sdk/bus/html-format.ts +20 -3
  344. package/src/sdk/bus/index.ts +782 -94
  345. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  346. package/src/sdk/bus/natives-augment.d.ts +26 -1
  347. package/src/sdk/bus/notification-service.ts +168 -20
  348. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  349. package/src/sdk/bus/recent-activity.ts +188 -74
  350. package/src/sdk/bus/rich-document.ts +258 -0
  351. package/src/sdk/bus/rich-render.ts +62 -70
  352. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  353. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  354. package/src/sdk/bus/telegram-daemon.ts +1389 -237
  355. package/src/sdk/bus/threaded-inbound.ts +8 -3
  356. package/src/sdk/bus/threaded-render.ts +57 -1
  357. package/src/sdk/bus/topic-registry.ts +120 -13
  358. package/src/sdk/host/control/dispatch.ts +14 -2
  359. package/src/sdk/host/control/operations.ts +6 -1
  360. package/src/sdk/host/host.ts +44 -2
  361. package/src/sdk/host/query/handlers.ts +20 -22
  362. package/src/sdk/host/query/revision-store.ts +80 -57
  363. package/src/sdk/index.ts +1 -0
  364. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  365. package/src/sdk/session-directory.ts +141 -0
  366. package/src/sdk/session.ts +462 -480
  367. package/src/session/agent-session.ts +1700 -583
  368. package/src/session/agent-storage.ts +18 -0
  369. package/src/session/cache-economics.ts +25 -8
  370. package/src/session/fallback-chain-controller.ts +53 -3
  371. package/src/session/internal/managed-session-scope.ts +2226 -0
  372. package/src/session/internal/managed-session-storage.ts +453 -0
  373. package/src/session/messages.ts +91 -2
  374. package/src/session/session-manager-internal.ts +33 -0
  375. package/src/session/session-manager.ts +1112 -333
  376. package/src/session/session-storage.ts +444 -39
  377. package/src/session/volatile-context-pruning.ts +78 -0
  378. package/src/setup/credential-auto-import.ts +2 -1
  379. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  380. package/src/setup/provider-onboarding.ts +36 -61
  381. package/src/setup/provider-presets.json +5 -0
  382. package/src/skill-state/active-state.ts +3 -13
  383. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  384. package/src/skill-state/workflow-state-contract.ts +6 -41
  385. package/src/slash-commands/builtin-registry.ts +46 -11
  386. package/src/system-prompt.ts +37 -44
  387. package/src/task/agents.ts +19 -1
  388. package/src/task/executor.ts +90 -6
  389. package/src/task/index.ts +36 -21
  390. package/src/thinking.ts +21 -6
  391. package/src/tool-discovery/tool-index.ts +2 -134
  392. package/src/tools/ask.ts +157 -21
  393. package/src/tools/ast-edit.ts +1 -1
  394. package/src/tools/bash.ts +38 -0
  395. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  396. package/src/tools/browser/tab-supervisor.ts +282 -36
  397. package/src/tools/computer.ts +10 -3
  398. package/src/tools/cron.ts +30 -16
  399. package/src/tools/eval.ts +1 -2
  400. package/src/tools/fetch.ts +11 -4
  401. package/src/tools/index.ts +76 -24
  402. package/src/tools/output-meta.ts +19 -7
  403. package/src/tools/read.ts +219 -64
  404. package/src/tools/resource-gc.ts +2 -2
  405. package/src/tools/search-tool-bm25.ts +11 -74
  406. package/src/tools/subagent.ts +43 -2
  407. package/src/utils/event-bus.ts +9 -4
  408. package/src/utils/file-mentions.ts +4 -4
  409. package/src/web/search/index.ts +3 -3
  410. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  411. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  412. package/src/discovery/plugin-dir-roots.ts +0 -28
  413. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -0,0 +1,168 @@
1
+ import {
2
+ type Component,
3
+ Container,
4
+ fuzzyFilter,
5
+ Input,
6
+ matchesKey,
7
+ padding,
8
+ truncateToWidth,
9
+ visibleWidth,
10
+ } from "@gajae-code/tui";
11
+ import { sanitizeStatusText } from "../shared";
12
+ import { theme } from "../theme/theme";
13
+ import { DynamicBorder } from "./dynamic-border";
14
+
15
+ export interface CommandPaletteAction {
16
+ id: string;
17
+ label: string;
18
+ handler: () => void | Promise<void>;
19
+ }
20
+
21
+ export interface CommandPaletteEntry {
22
+ id: string;
23
+ label: string;
24
+ description?: string;
25
+ category?: string;
26
+ keybinding?: string;
27
+ bindingHint?: string;
28
+ searchText?: string;
29
+ handler?: () => void | Promise<void>;
30
+ disabled?: boolean;
31
+ }
32
+
33
+ function sanitizePaletteText(text: string): string {
34
+ return sanitizeStatusText(text.toWellFormed());
35
+ }
36
+
37
+ function isSafePaletteEntryId(id: string): boolean {
38
+ return id.length > 0 && !/[\s\u0000-\u001f\u007f-\u009f]/u.test(id);
39
+ }
40
+
41
+ class CommandPaletteList implements Component {
42
+ #entries: CommandPaletteEntry[];
43
+ #filteredEntries: CommandPaletteEntry[];
44
+ #selectedIndex = 0;
45
+ readonly #searchInput = new Input();
46
+ onSelect?: (entry: CommandPaletteEntry) => void;
47
+ onCancel?: () => void;
48
+
49
+ constructor(entries: CommandPaletteEntry[]) {
50
+ this.#entries = entries.map(entry => ({
51
+ ...entry,
52
+ label: sanitizePaletteText(entry.label),
53
+ description: sanitizePaletteText(entry.description ?? entry.category ?? ""),
54
+ category: entry.category === undefined ? undefined : sanitizePaletteText(entry.category),
55
+ keybinding:
56
+ entry.keybinding === undefined && entry.bindingHint === undefined
57
+ ? undefined
58
+ : sanitizePaletteText(entry.keybinding ?? entry.bindingHint ?? ""),
59
+ bindingHint: entry.bindingHint === undefined ? undefined : sanitizePaletteText(entry.bindingHint),
60
+ searchText: entry.searchText === undefined ? undefined : sanitizePaletteText(entry.searchText),
61
+ }));
62
+ this.#filteredEntries = this.#entries;
63
+ }
64
+
65
+ #filter(query: string): void {
66
+ this.#filteredEntries = fuzzyFilter(this.#entries, query, entry =>
67
+ [entry.label, entry.description, entry.keybinding ?? "", entry.searchText ?? ""].join(" "),
68
+ );
69
+ this.#selectedIndex = Math.max(0, Math.min(this.#selectedIndex, this.#filteredEntries.length - 1));
70
+ }
71
+
72
+ invalidate(): void {}
73
+
74
+ getEntries(): readonly CommandPaletteEntry[] {
75
+ return this.#filteredEntries;
76
+ }
77
+
78
+ render(width: number): string[] {
79
+ const lines = [theme.fg("muted", " Search commands"), ...this.#searchInput.render(width), ""];
80
+ if (this.#filteredEntries.length === 0) {
81
+ lines.push(theme.fg("muted", " No matching commands"));
82
+ return lines;
83
+ }
84
+
85
+ const maxVisible = 8;
86
+ const start = Math.max(
87
+ 0,
88
+ Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), this.#filteredEntries.length - maxVisible),
89
+ );
90
+ const end = Math.min(start + maxVisible, this.#filteredEntries.length);
91
+ for (let index = start; index < end; index += 1) {
92
+ const entry = this.#filteredEntries[index];
93
+ if (!entry) continue;
94
+ const selected = index === this.#selectedIndex;
95
+ const cursor = `${theme.nav.cursor} `;
96
+ const prefix = selected ? theme.fg("accent", cursor) : padding(visibleWidth(cursor));
97
+ const keybinding = entry.keybinding ? theme.fg("muted", entry.keybinding) : "";
98
+ const availableLabelWidth = Math.max(
99
+ 1,
100
+ width - visibleWidth(prefix) - visibleWidth(keybinding) - (keybinding ? 1 : 0),
101
+ );
102
+ const label = truncateToWidth(entry.label, availableLabelWidth);
103
+ const labelPadding = padding(Math.max(0, availableLabelWidth - visibleWidth(label)));
104
+ const renderedLabel = selected ? theme.bold(label) : label;
105
+ lines.push(
106
+ entry.disabled
107
+ ? theme.fg("muted", prefix + renderedLabel + labelPadding + (keybinding ? ` ${keybinding}` : ""))
108
+ : prefix + renderedLabel + labelPadding + (keybinding ? ` ${keybinding}` : ""),
109
+ );
110
+ lines.push(theme.fg("dim", truncateToWidth(` ${entry.description ?? ""}`, width)));
111
+ }
112
+ if (start > 0 || end < this.#filteredEntries.length) {
113
+ lines.push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filteredEntries.length})`));
114
+ }
115
+ lines.push("", theme.fg("muted", " [Enter to run, Esc to close]"));
116
+ return lines;
117
+ }
118
+
119
+ handleInput(data: string): void {
120
+ if (matchesKey(data, "escape")) {
121
+ this.onCancel?.();
122
+ return;
123
+ }
124
+ if (matchesKey(data, "up")) {
125
+ this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
126
+ return;
127
+ }
128
+ if (matchesKey(data, "down")) {
129
+ this.#selectedIndex = Math.min(this.#filteredEntries.length - 1, this.#selectedIndex + 1);
130
+ return;
131
+ }
132
+ if (matchesKey(data, "enter") || matchesKey(data, "return") || data === "\n") {
133
+ const entry = this.#filteredEntries[this.#selectedIndex];
134
+ if (entry && !entry.disabled && isSafePaletteEntryId(entry.id)) this.onSelect?.(entry);
135
+ return;
136
+ }
137
+ this.#searchInput.handleInput(data);
138
+ this.#filter(this.#searchInput.getValue());
139
+ }
140
+ }
141
+
142
+ export class CommandPaletteComponent extends Container {
143
+ #list: CommandPaletteList;
144
+
145
+ constructor(entries: CommandPaletteEntry[], onSelect: (entry: CommandPaletteEntry) => void, onCancel: () => void) {
146
+ super();
147
+ this.addChild(new DynamicBorder());
148
+ this.#list = new CommandPaletteList(entries);
149
+ this.#list.onSelect = onSelect;
150
+ this.#list.onCancel = onCancel;
151
+ this.addChild(this.#list);
152
+ this.addChild(new DynamicBorder());
153
+ }
154
+
155
+ override render(width: number): string[] {
156
+ return super.render(width).map(line => truncateToWidth(line, width));
157
+ }
158
+ handleInput(data: string): void {
159
+ this.#list.handleInput(data);
160
+ }
161
+
162
+ getEntries(): readonly CommandPaletteEntry[] {
163
+ return this.#list.getEntries();
164
+ }
165
+ }
166
+
167
+ /** Backward-compatible name retained for extensions and existing call sites. */
168
+ export class CommandPalette extends CommandPaletteComponent {}
@@ -402,7 +402,7 @@ export class CustomEditor extends Editor {
402
402
  }
403
403
  }
404
404
 
405
- // Pass to parent for normal handling
405
+ // Pass to parent for normal handling.
406
406
  super.handleInput(data);
407
407
  }
408
408
  }
@@ -1,4 +1,4 @@
1
- import { Container, Input, matchesKey, Spacer, Text, TruncatedText } from "@gajae-code/tui";
1
+ import { Container, Input, matchesKey, SecretInput, Spacer, Text, TruncatedText } from "@gajae-code/tui";
2
2
  import type { ProviderCompatibility, ProviderSetupInput } from "../../setup/provider-onboarding";
3
3
  import { theme } from "../theme/theme";
4
4
  import { matchesAppInterrupt } from "../utils/keybinding-matchers";
@@ -29,7 +29,7 @@ export type CustomProviderWizardSubmit = ProviderSetupInput;
29
29
 
30
30
  export class CustomProviderWizardComponent extends Container {
31
31
  #contentContainer: Container;
32
- #input: Input | null = null;
32
+ #input: Input | SecretInput | null = null;
33
33
  #step: WizardStep = "compatibility";
34
34
  #selectedIndex = 0;
35
35
  #lastSubmitError: string | null = null;
@@ -82,6 +82,7 @@ export class CustomProviderWizardComponent extends Container {
82
82
  handleInput(keyData: string): void {
83
83
  if (matchesAppInterrupt(keyData)) {
84
84
  if (this.#step === "compatibility") {
85
+ this.#clearLiteralCredential();
85
86
  this.#onCancel();
86
87
  return;
87
88
  }
@@ -90,6 +91,10 @@ export class CustomProviderWizardComponent extends Container {
90
91
  }
91
92
 
92
93
  if (this.#input) {
94
+ if (this.#input instanceof SecretInput) {
95
+ this.#input.handleInput(keyData);
96
+ return;
97
+ }
93
98
  if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
94
99
  this.#saveInputAndProceed();
95
100
  return;
@@ -138,16 +143,16 @@ export class CustomProviderWizardComponent extends Container {
138
143
  this.#renderCredentialSourceStep();
139
144
  break;
140
145
  case "credential":
141
- this.#renderInputStep(
142
- "Step 5: Credential",
143
- this.#state.credentialSource === "env"
144
- ? "Enter the API key environment variable name:"
145
- : "Paste the API key:",
146
- this.#state.credential,
147
- this.#state.credentialSource === "env"
148
- ? "e.g. OPENAI_API_KEY"
149
- : "The key will be stored in models.yml and redacted in output.",
150
- );
146
+ if (this.#state.credentialSource === "env") {
147
+ this.#renderInputStep(
148
+ "Step 5: Credential",
149
+ "Enter the API key environment variable name:",
150
+ this.#state.credential,
151
+ "e.g. OPENAI_API_KEY",
152
+ );
153
+ } else {
154
+ this.#renderSecretInputStep();
155
+ }
151
156
  break;
152
157
  case "models":
153
158
  this.#renderInputStep(
@@ -198,6 +203,27 @@ export class CustomProviderWizardComponent extends Container {
198
203
  this.#addHelp("[Enter to continue, Esc to go back]");
199
204
  }
200
205
 
206
+ #renderSecretInputStep(): void {
207
+ this.#contentContainer.addChild(new Text(theme.fg("accent", "Step 5: Credential")));
208
+ this.#contentContainer.addChild(new Spacer(1));
209
+ this.#contentContainer.addChild(new Text("Paste the API key:", 0, 0));
210
+ this.#contentContainer.addChild(new Spacer(1));
211
+ const input = new SecretInput();
212
+ input.onSubmit = secret => {
213
+ const credential = secret.consume().trim();
214
+ if (!credential) return;
215
+ this.#state.credential = credential;
216
+ this.#step = "models";
217
+ this.#renderStep();
218
+ this.#onRender();
219
+ };
220
+ this.#input = input;
221
+ this.#contentContainer.addChild(input);
222
+ this.#contentContainer.addChild(new Spacer(1));
223
+ this.#addHelp("The key will be stored securely and redacted in output.");
224
+ this.#addHelp("[Enter to continue, Esc to go back]");
225
+ }
226
+
201
227
  #renderConfirmStep(force: boolean): void {
202
228
  this.#contentContainer.addChild(
203
229
  new Text(theme.fg("accent", force ? "Provider exists — replace it?" : "Confirm custom provider")),
@@ -235,7 +261,8 @@ export class CustomProviderWizardComponent extends Container {
235
261
  }
236
262
 
237
263
  #saveInputAndProceed(): void {
238
- const value = this.#input?.getValue().trim() ?? "";
264
+ if (!(this.#input instanceof Input)) return;
265
+ const value = this.#input.getValue().trim();
239
266
  if (!value) return;
240
267
  if (this.#step === "provider-id") {
241
268
  this.#state.providerId = value;
@@ -277,7 +304,7 @@ export class CustomProviderWizardComponent extends Container {
277
304
  }
278
305
 
279
306
  #buildInput(force: boolean): CustomProviderWizardSubmit {
280
- return {
307
+ const input = {
281
308
  compatibility: this.#state.compatibility,
282
309
  providerId: this.#state.providerId,
283
310
  baseUrl: this.#state.baseUrl,
@@ -289,6 +316,16 @@ export class CustomProviderWizardComponent extends Container {
289
316
  .filter(Boolean),
290
317
  force,
291
318
  };
319
+
320
+ return input;
321
+ }
322
+
323
+ complete(): void {
324
+ this.#clearLiteralCredential();
325
+ }
326
+
327
+ #clearLiteralCredential(): void {
328
+ if (this.#state.credentialSource === "literal") this.#state.credential = "";
292
329
  }
293
330
 
294
331
  #moveSelection(delta: number): void {
@@ -3,6 +3,7 @@ export * from "./assistant-message";
3
3
  export * from "./bash-execution";
4
4
  export * from "./bordered-loader";
5
5
  export * from "./branch-summary-message";
6
+ export * from "./chrome";
6
7
  export * from "./compaction-summary-message";
7
8
  export * from "./countdown-timer";
8
9
  export * from "./custom-editor";
@@ -23,11 +24,11 @@ export * from "./read-tool-group";
23
24
  export * from "./session-selector";
24
25
  export * from "./settings-selector";
25
26
  export * from "./show-images-selector";
26
- export * from "./status-line";
27
27
  export * from "./theme-selector";
28
28
  export * from "./thinking-selector";
29
29
  export * from "./todo-reminder";
30
30
  export * from "./tool-execution";
31
+ export * from "./tool-status-header";
31
32
  export * from "./tree-selector";
32
33
  export * from "./ttsr-notification";
33
34
  export * from "./user-message";
@@ -1,6 +1,5 @@
1
- import { Container, PET_SKIN_IDS, PET_SKINS, SelectList } from "@gajae-code/tui";
2
- import { getSelectListTheme } from "../theme/theme";
3
- import { DynamicBorder } from "./dynamic-border";
1
+ import { Container, PET_SKIN_IDS, PET_SKINS, type SelectList } from "@gajae-code/tui";
2
+ import { FramedSelect } from "./chrome";
4
3
  import type { PetMode } from "./gajae-pet-widget";
5
4
  import { createPetSelectItems } from "./pet-capability";
6
5
 
@@ -31,20 +30,15 @@ export class PetSelectorComponent extends Container {
31
30
 
32
31
  const items = createPetSelectItems(PET_OPTIONS, current, available);
33
32
 
34
- this.addChild(new DynamicBorder());
35
-
36
- this.#selectList = new SelectList(items, 10, getSelectListTheme());
37
- const currentIndex = PET_OPTIONS.findIndex(option => option.value === current);
38
- if (currentIndex !== -1) {
39
- this.#selectList.setSelectedIndex(currentIndex);
40
- }
41
-
42
- this.#selectList.onSelect = item => onSelect(item.value as PetMode);
43
- this.#selectList.onCancel = () => onCancel();
44
- this.#selectList.onSelectionChange = item => onPreview(item.value as PetMode);
45
-
46
- this.addChild(this.#selectList);
47
- this.addChild(new DynamicBorder());
33
+ const framed = FramedSelect(undefined, items, {
34
+ maxVisible: 10,
35
+ selectedValue: current,
36
+ onSelect: item => onSelect(item.value as PetMode),
37
+ onCancel,
38
+ onSelectionChange: item => onPreview(item.value as PetMode),
39
+ });
40
+ this.#selectList = framed.selectList;
41
+ this.addChild(framed.container);
48
42
  }
49
43
 
50
44
  getSelectList(): SelectList {
@@ -0,0 +1,295 @@
1
+ import { createHash } from "node:crypto";
2
+ import { Container, Input, type KeyId, type MouseEvent, matchesKey, truncateToWidth } from "@gajae-code/tui";
3
+
4
+ import { theme } from "../theme/theme";
5
+ import { DynamicBorder } from "./dynamic-border";
6
+
7
+ export const PLAN_REVIEW_ACTIONS = [
8
+ "Approve and execute",
9
+ "Approve and compact context",
10
+ "Approve and keep context",
11
+ "Refine plan",
12
+ ] as const;
13
+ export type PlanReviewAction = (typeof PLAN_REVIEW_ACTIONS)[number];
14
+ export type PlanPreviewFocus = "preview" | "sourceSelect" | "commentInput" | "actionBar";
15
+ export interface PlanPreviewOptions {
16
+ externalEditorKey?: string;
17
+ externalEditorKeys?: readonly KeyId[];
18
+ onExternalEditor?: () => Promise<string | null>;
19
+ }
20
+
21
+ export interface PlanComment {
22
+ id: string;
23
+ startLine: number;
24
+ endLine: number;
25
+ text: string;
26
+ snapshotHash: string;
27
+ createdAt: number;
28
+ }
29
+ export interface PlanPreviewResult {
30
+ action?: PlanReviewAction;
31
+ comments: PlanComment[];
32
+ notes: string;
33
+ snapshotHash: string;
34
+ }
35
+
36
+ export function planSnapshotHash(content: string): string {
37
+ return createHash("sha256").update(content).digest("hex");
38
+ }
39
+ export function serializePlanReviewComments(
40
+ content: string,
41
+ hash: string,
42
+ comments: readonly PlanComment[],
43
+ notes = "",
44
+ ): string {
45
+ const lines = content.split("\n").map(line => line.replace(/\r$/, ""));
46
+ const valid = comments.filter(
47
+ comment =>
48
+ comment.snapshotHash === hash &&
49
+ comment.text.trim() &&
50
+ Number.isInteger(comment.startLine) &&
51
+ Number.isInteger(comment.endLine) &&
52
+ comment.startLine >= 1 &&
53
+ comment.endLine >= comment.startLine &&
54
+ comment.startLine <= lines.length,
55
+ );
56
+ if (!valid.length && !notes.trim()) return "";
57
+ const block = [`Plan review comments (snapshot ${hash.slice(0, 8)}):`];
58
+ for (const comment of valid) {
59
+ const end = Math.min(lines.length, Math.max(comment.startLine, comment.endLine));
60
+ block.push(`- L${comment.startLine}${end === comment.startLine ? "" : `-L${end}`}: ${comment.text.trim()}`);
61
+ for (let line = comment.startLine; line <= Math.min(end, comment.startLine + 5); line++)
62
+ block.push(`> ${lines[line - 1]}`);
63
+ }
64
+ if (notes.trim()) block.push(notes.trim());
65
+ return block.join("\n");
66
+ }
67
+
68
+ /** Modal pre-decision review. Its result is consumed by InteractiveMode, which owns audit and dispatch semantics. */
69
+ export class PlanPreviewOverlay extends Container {
70
+ #focus: PlanPreviewFocus = "preview";
71
+ #scroll = 0;
72
+ #sourceLine = 1;
73
+ #actionIndex = 0;
74
+ #comments: PlanComment[] = [];
75
+ #notes = "";
76
+ #input = new Input();
77
+ #inputKind: "comment" | "notes" = "comment";
78
+ #renderedSourceLines: number[] = [];
79
+ #renderedBody: string[] = [];
80
+ #renderedBodyWidth = 0;
81
+ content: string | null;
82
+
83
+ snapshotHash: string;
84
+ lines: string[];
85
+ constructor(
86
+ content: string | null,
87
+ private readonly done: (result: PlanPreviewResult) => void,
88
+ private readonly requestRender: () => void,
89
+ private readonly options: PlanPreviewOptions = {},
90
+ ) {
91
+ super();
92
+ this.content = content;
93
+ this.snapshotHash = planSnapshotHash(content ?? "");
94
+ this.lines = (content ?? "").split("\n");
95
+ this.#input.onSubmit = text => this.#saveInput(text);
96
+ }
97
+
98
+ get focusState(): PlanPreviewFocus {
99
+ return this.#focus;
100
+ }
101
+ get comments(): readonly PlanComment[] {
102
+ return this.#comments;
103
+ }
104
+ get pageOffset(): number {
105
+ return this.#scroll;
106
+ }
107
+ get sourceLine(): number {
108
+ return this.#sourceLine;
109
+ }
110
+ override render(width: number): string[] {
111
+ const height = Math.max(5, (process.stdout.rows || 40) - 8);
112
+ const bodyWidth = Math.max(1, width - 2);
113
+ const body = this.#renderSourceRows(bodyWidth);
114
+ this.#scroll = Math.max(0, Math.min(this.#scroll, Math.max(0, body.length - height)));
115
+ const visible = body.slice(this.#scroll, this.#scroll + height);
116
+ const actions = PLAN_REVIEW_ACTIONS.map(
117
+ (action, index) => `${this.#focus === "actionBar" && index === this.#actionIndex ? "▶ " : " "}${action}`,
118
+ );
119
+ const actionBar = width < 40 ? actions : [actions.join(" ")];
120
+ const footer =
121
+ this.#focus === "commentInput"
122
+ ? `${this.#inputKind === "notes" ? "Notes" : `Comment for source line ${this.#sourceLine}`} (Enter save, Esc cancel): ${this.#input.getValue()}`
123
+ : this.#focus === "sourceSelect"
124
+ ? `Source line ${this.#sourceLine}/${this.lines.length || 1} j/k:select c:comment Esc:done`
125
+ : `${this.#scroll + 1}-${Math.min(this.#scroll + height, body.length)}/${body.length || 1} s:source line n:notes ${this.options.externalEditorKey ? `${this.options.externalEditorKey.toLowerCase()}:edit ` : ""}Tab:actions PgUp/PgDn:page`;
126
+
127
+ const rendered = [
128
+ new DynamicBorder().render(width)[0] ?? "",
129
+ ` ${theme.bold(theme.fg("accent", "Plan review"))}`,
130
+ ...visible.map(line => ` ${line}`),
131
+ "",
132
+ ...actionBar.map(action => ` ${action}`),
133
+ ` ${footer}`,
134
+ new DynamicBorder().render(width)[0] ?? "",
135
+ ];
136
+ return rendered.map(line =>
137
+ !this.content && width < 40 && PLAN_REVIEW_ACTIONS.some(action => line.includes(action))
138
+ ? line
139
+ : truncateToWidth(line, width),
140
+ );
141
+ }
142
+ handleMouse(event: MouseEvent): void {
143
+ if (event.kind !== "click" || this.#focus === "commentInput") return;
144
+ const renderedRow = this.#scroll + (event.localY ?? event.y) - 2;
145
+ const sourceLine = this.#renderedSourceLines[renderedRow];
146
+ if (sourceLine === undefined) return;
147
+ this.#sourceLine = sourceLine;
148
+ this.#focus = "sourceSelect";
149
+ this.requestRender();
150
+ }
151
+
152
+ handleInput(key: string): void {
153
+ if (this.#focus === "commentInput") {
154
+ if (matchesKey(key, "escape")) {
155
+ this.#focus = "preview";
156
+ this.#input.setValue("");
157
+ this.requestRender();
158
+ return;
159
+ }
160
+ this.#input.handleInput(key);
161
+ this.requestRender();
162
+ return;
163
+ }
164
+ if (matchesKey(key, "escape")) {
165
+ this.done({ comments: this.#comments, notes: this.#notes, snapshotHash: this.snapshotHash });
166
+ return;
167
+ }
168
+ if (this.#matchesExternalEditor(key)) {
169
+ void this.#openExternalEditor();
170
+ return;
171
+ }
172
+
173
+ if (matchesKey(key, "tab")) {
174
+ this.#focus = this.#focus === "actionBar" ? "preview" : "actionBar";
175
+ this.requestRender();
176
+ return;
177
+ }
178
+ if (this.#focus === "actionBar") {
179
+ if (matchesKey(key, "left") || matchesKey(key, "up")) this.#actionIndex = Math.max(0, this.#actionIndex - 1);
180
+ else if (matchesKey(key, "right") || matchesKey(key, "down"))
181
+ this.#actionIndex = Math.min(PLAN_REVIEW_ACTIONS.length - 1, this.#actionIndex + 1);
182
+ else if (matchesKey(key, "enter") || key === "\r")
183
+ this.done({
184
+ action: PLAN_REVIEW_ACTIONS[this.#actionIndex],
185
+ comments: this.#comments,
186
+
187
+ notes: this.#notes,
188
+ snapshotHash: this.snapshotHash,
189
+ });
190
+ this.requestRender();
191
+ return;
192
+ }
193
+ if (this.#focus === "sourceSelect") {
194
+ if (key === "j" || matchesKey(key, "down"))
195
+ this.#sourceLine = Math.min(this.lines.length || 1, this.#sourceLine + 1);
196
+ else if (key === "k" || matchesKey(key, "up")) this.#sourceLine = Math.max(1, this.#sourceLine - 1);
197
+ else if (key === "c") {
198
+ this.#inputKind = "comment";
199
+ this.#focus = "commentInput";
200
+ } else if (matchesKey(key, "enter")) this.#focus = "preview";
201
+ this.requestRender();
202
+ return;
203
+ }
204
+ if (key === "s") {
205
+ this.#focus = "sourceSelect";
206
+ this.requestRender();
207
+ return;
208
+ }
209
+ if (key === "c") {
210
+ this.#inputKind = "comment";
211
+ this.#focus = "commentInput";
212
+ this.requestRender();
213
+ return;
214
+ }
215
+ if (key === "n") {
216
+ this.#inputKind = "notes";
217
+ this.#focus = "commentInput";
218
+ this.requestRender();
219
+ return;
220
+ }
221
+ if (matchesKey(key, "pageDown")) this.#scroll += 15;
222
+ if (matchesKey(key, "pageUp")) this.#scroll -= 15;
223
+ this.requestRender();
224
+ }
225
+ #saveInput(text: string): void {
226
+ if (text.trim()) {
227
+ if (this.#inputKind === "notes") this.#notes = text;
228
+ else
229
+ this.#comments.push({
230
+ id: crypto.randomUUID(),
231
+ startLine: this.#sourceLine,
232
+ endLine: this.#sourceLine,
233
+ text,
234
+ snapshotHash: this.snapshotHash,
235
+ createdAt: Date.now(),
236
+ });
237
+ }
238
+ this.#input.setValue("");
239
+ this.#focus = "preview";
240
+ this.requestRender();
241
+ }
242
+ // Render numbered source rather than Markdown so every displayed row has an exact source-line owner.
243
+ #renderSourceRows(width: number): string[] {
244
+ if (!this.content) {
245
+ this.#renderedSourceLines = [];
246
+ this.#renderedBody = [theme.fg("warning", "Plan file is empty or missing.")];
247
+ this.#renderedBodyWidth = width;
248
+ return this.#renderedBody;
249
+ }
250
+ if (this.#renderedBodyWidth === width) return this.#renderedBody;
251
+
252
+ const lineNumberWidth = String(this.lines.length).length;
253
+ const prefixWidth = lineNumberWidth + 3;
254
+ const sourceWidth = Math.max(1, width - prefixWidth);
255
+ const body: string[] = [];
256
+ const sourceLines: number[] = [];
257
+ for (let index = 0; index < this.lines.length; index++) {
258
+ const prefix = `${String(index + 1).padStart(lineNumberWidth)} │ `;
259
+ const wrapped = Bun.wrapAnsi(this.lines[index]!.replace(/\r$/, ""), sourceWidth, {
260
+ hard: true,
261
+ trim: false,
262
+ });
263
+ const rows = wrapped ? wrapped.split("\n") : [""];
264
+ for (const row of rows) {
265
+ body.push(`${prefix}${row}`);
266
+ sourceLines.push(index + 1);
267
+ }
268
+ }
269
+ this.#renderedSourceLines = sourceLines;
270
+ this.#renderedBody = body;
271
+ this.#renderedBodyWidth = width;
272
+ return body;
273
+ }
274
+ #matchesExternalEditor(key: string): boolean {
275
+ return Boolean(
276
+ this.options.onExternalEditor && this.options.externalEditorKeys?.some(binding => matchesKey(key, binding)),
277
+ );
278
+ }
279
+ async #openExternalEditor(): Promise<void> {
280
+ const updatedContent = await this.options.onExternalEditor?.();
281
+ if (updatedContent === null || updatedContent === undefined || updatedContent === this.content) return;
282
+ this.content = updatedContent;
283
+ this.snapshotHash = planSnapshotHash(updatedContent);
284
+ this.lines = updatedContent.split("\n");
285
+ this.#renderedSourceLines = [];
286
+ this.#renderedBody = [];
287
+ this.#renderedBodyWidth = 0;
288
+ this.#comments = [];
289
+ this.#notes = "";
290
+ this.#scroll = 0;
291
+ this.#sourceLine = 1;
292
+ this.#focus = "preview";
293
+ this.requestRender();
294
+ }
295
+ }