@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
package/src/main.ts CHANGED
@@ -23,6 +23,7 @@ import chalk from "chalk";
23
23
  import type { Args } from "./cli/args";
24
24
  import { processFileArguments } from "./cli/file-processor";
25
25
  import { buildInitialMessage } from "./cli/initial-message";
26
+ import { resolveLaunchDisposition } from "./cli/launch-disposition";
26
27
  import { runListModelsCommand } from "./cli/list-models";
27
28
  import { selectSession } from "./cli/session-picker";
28
29
  import { findConfigFile } from "./config";
@@ -57,6 +58,7 @@ import type { AgentSession } from "./session/agent-session";
57
58
  import {
58
59
  type ResumeSessionIdentity,
59
60
  resolveResumableSession,
61
+ type SessionDirectoryMigrationPolicy,
60
62
  type SessionInfo,
61
63
  SessionManager,
62
64
  type StrictSessionOpenResult,
@@ -281,7 +283,7 @@ export function resolveAcpStartupOptions(
281
283
  }
282
284
 
283
285
  async function readPipedInput(): Promise<string | undefined> {
284
- if (process.stdin.isTTY !== false) return undefined;
286
+ if (process.stdin.isTTY === true) return undefined;
285
287
  try {
286
288
  const text = await Bun.stdin.text();
287
289
  if (text.trim().length === 0) return undefined;
@@ -291,6 +293,30 @@ async function readPipedInput(): Promise<string | undefined> {
291
293
  }
292
294
  }
293
295
 
296
+ async function readPipedInputIfReady(): Promise<string | undefined> {
297
+ if (process.stdin.isTTY === true) return undefined;
298
+
299
+ const ready = Promise.withResolvers<void>();
300
+ const onReadable = () => ready.resolve();
301
+ const onEnd = () => ready.resolve();
302
+ const onError = () => ready.resolve();
303
+ process.stdin.once("readable", onReadable);
304
+ process.stdin.once("end", onEnd);
305
+ process.stdin.once("error", onError);
306
+ await Promise.race([ready.promise, new Promise<void>(resolve => setImmediate(resolve))]);
307
+ process.stdin.removeListener("readable", onReadable);
308
+ process.stdin.removeListener("end", onEnd);
309
+ process.stdin.removeListener("error", onError);
310
+
311
+ const chunks: Buffer[] = [];
312
+ for (let chunk = process.stdin.read(); chunk !== null; chunk = process.stdin.read()) {
313
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
314
+ }
315
+ if (chunks.length === 0) return undefined;
316
+ const text = Buffer.concat(chunks).toString("utf8");
317
+ return text.trim() ? text : undefined;
318
+ }
319
+
294
320
  export interface InteractiveModeNotify {
295
321
  kind: "warn" | "error" | "info";
296
322
  message: string;
@@ -510,6 +536,7 @@ type SelectResumeSession = (sessions: SessionInfo[]) => Promise<SessionSelection
510
536
  type OpenExistingSessionStrict = (
511
537
  identity: ResumeSessionIdentity,
512
538
  sessionDir?: string,
539
+ migrationPolicy?: SessionDirectoryMigrationPolicy,
513
540
  ) => Promise<StrictSessionOpenResult>;
514
541
 
515
542
  export const BARE_RESUME_CONFLICT_ERROR =
@@ -694,6 +721,7 @@ export async function createSessionManager(
694
721
  cwd: string,
695
722
  activeSettings: Settings = settings,
696
723
  ): Promise<SessionManager | undefined> {
724
+ const migrationPolicy = activeSettings.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
697
725
  if (parsed.resume === true) {
698
726
  return undefined;
699
727
  }
@@ -703,13 +731,13 @@ export async function createSessionManager(
703
731
  }
704
732
  const forkSource = parsed.fork;
705
733
  if (forkSource.includes("/") || forkSource.includes("\\") || forkSource.endsWith(".jsonl")) {
706
- return await SessionManager.forkFrom(forkSource, cwd, parsed.sessionDir);
734
+ return await SessionManager.forkFrom(forkSource, cwd, parsed.sessionDir, undefined, migrationPolicy);
707
735
  }
708
736
  const match = await resolveResumableSession(forkSource, cwd, parsed.sessionDir);
709
737
  if (!match) {
710
738
  throw new Error(`Session "${forkSource}" not found.`);
711
739
  }
712
- return await SessionManager.forkFrom(match.session.path, cwd, parsed.sessionDir);
740
+ return await SessionManager.forkFrom(match.session.path, cwd, parsed.sessionDir, undefined, migrationPolicy);
713
741
  }
714
742
 
715
743
  if (parsed.noSession) {
@@ -718,7 +746,7 @@ export async function createSessionManager(
718
746
  if (typeof parsed.resume === "string") {
719
747
  const sessionArg = parsed.resume;
720
748
  if (sessionArg.includes("/") || sessionArg.includes("\\") || sessionArg.endsWith(".jsonl")) {
721
- return await SessionManager.open(sessionArg, parsed.sessionDir);
749
+ return await SessionManager.open(sessionArg, parsed.sessionDir, undefined, migrationPolicy);
722
750
  }
723
751
  const match = await resolveResumableSession(sessionArg, cwd, parsed.sessionDir);
724
752
  if (!match) {
@@ -732,13 +760,19 @@ export async function createSessionManager(
732
760
  if (!shouldFork) {
733
761
  throw new Error(`Session "${sessionArg}" is in another project (${match.session.cwd}).`);
734
762
  }
735
- return await SessionManager.forkFrom(match.session.path, cwd, parsed.sessionDir);
763
+ return await SessionManager.forkFrom(
764
+ match.session.path,
765
+ cwd,
766
+ parsed.sessionDir,
767
+ undefined,
768
+ migrationPolicy,
769
+ );
736
770
  }
737
771
  }
738
- return await SessionManager.open(match.session.path, parsed.sessionDir);
772
+ return await SessionManager.open(match.session.path, parsed.sessionDir, undefined, migrationPolicy);
739
773
  }
740
774
  if (parsed.continue) {
741
- return await SessionManager.continueRecent(cwd, parsed.sessionDir);
775
+ return await SessionManager.continueRecent(cwd, parsed.sessionDir, undefined, migrationPolicy);
742
776
  }
743
777
  // --resume without value is handled separately (needs picker UI)
744
778
  // If --session-dir provided without --continue/--resume, create new session there
@@ -761,7 +795,7 @@ export async function createSessionManager(
761
795
  // buildSessionOptions restores the session's model/thinking instead of
762
796
  // overriding them with CLI defaults.
763
797
  if (activeSettings.get("autoResume")) {
764
- const manager = await SessionManager.continueRecent(cwd, parsed.sessionDir);
798
+ const manager = await SessionManager.continueRecent(cwd, parsed.sessionDir, undefined, migrationPolicy);
765
799
  if (manager.getEntries().length > 0) {
766
800
  parsed.continue = true;
767
801
  }
@@ -1005,6 +1039,7 @@ export interface RunRootCommandDependencies {
1005
1039
  startupUpdate?: { check: () => Promise<string | undefined> };
1006
1040
  initTheme?: typeof initTheme;
1007
1041
  readPipedInput?: typeof readPipedInput;
1042
+ stdinIsTTY?: boolean;
1008
1043
  runStartupCredentialAutoImportIfNeeded?: typeof runStartupCredentialAutoImportIfNeeded;
1009
1044
  getChangelogForDisplay?: typeof getChangelogForDisplay;
1010
1045
  createInteractiveMode?: CreateInteractiveMode;
@@ -1014,6 +1049,7 @@ export interface RunRootCommandDependencies {
1014
1049
  selectResumeSession?: SelectResumeSession;
1015
1050
  openExistingSessionStrict?: OpenExistingSessionStrict;
1016
1051
  initializeSettings?: typeof Settings.init;
1052
+ loadSettingsForScope?: typeof Settings.loadForScope;
1017
1053
  }
1018
1054
 
1019
1055
  export async function runRootCommand(
@@ -1054,15 +1090,25 @@ export async function runRootCommand(
1054
1090
  process.stdout.write(`${chalk.dim("No sessions found")}\n`);
1055
1091
  return;
1056
1092
  }
1057
- const selection = await (deps.selectResumeSession ?? selectSession)(sessions);
1093
+ const selection = deps.selectResumeSession
1094
+ ? await deps.selectResumeSession(sessions)
1095
+ : await selectSession(sessions, parsedArgs.sessionDir);
1058
1096
  if (selection.kind === "cancelled") {
1059
1097
  return;
1060
1098
  }
1099
+ const resumeMigrationPolicy =
1100
+ (await (deps.loadSettingsForScope ?? Settings.loadForScope)({ cwd: resumeCwd })).get(
1101
+ "session.directoryMigration",
1102
+ ) === "disabled"
1103
+ ? "disabled"
1104
+ : "copy-retain";
1061
1105
  let opened: StrictSessionOpenResult;
1062
1106
  try {
1063
1107
  opened = await (deps.openExistingSessionStrict ?? SessionManager.openExistingStrict)(
1064
1108
  selection.identity,
1065
1109
  parsedArgs.sessionDir,
1110
+ undefined,
1111
+ resumeMigrationPolicy,
1066
1112
  );
1067
1113
  } catch {
1068
1114
  process.stderr.write(`${BARE_RESUME_OPEN_ERROR}\n`);
@@ -1144,8 +1190,16 @@ export async function runRootCommand(
1144
1190
  if (parsedArgs.noTitle || parsedArgs.mode === "acp") {
1145
1191
  Bun.env.PI_NO_TITLE = "1";
1146
1192
  }
1193
+ const hasPreparedInput = parsedArgs.messages.length > 0 || parsedArgs.fileArgs.length > 0;
1147
1194
  const { pipedInput, fileText, fileImages } = await logger.time("prepareInitialMessage", async () => {
1148
- const pipedInput = await (deps.readPipedInput ?? readPipedInput)();
1195
+ const pipedInput =
1196
+ parsedArgs.mode === "acp"
1197
+ ? undefined
1198
+ : deps.readPipedInput
1199
+ ? await deps.readPipedInput()
1200
+ : hasPreparedInput
1201
+ ? await readPipedInputIfReady()
1202
+ : await readPipedInput();
1149
1203
  if (parsedArgs.fileArgs.length === 0) {
1150
1204
  return { pipedInput, fileText: undefined, fileImages: undefined };
1151
1205
  }
@@ -1160,14 +1214,25 @@ export async function runRootCommand(
1160
1214
  fileImages,
1161
1215
  stdinContent: pipedInput,
1162
1216
  });
1163
- const autoPrint = pipedInput !== undefined && !parsedArgs.print && parsedArgs.mode === undefined;
1217
+ const disposition = resolveLaunchDisposition({
1218
+ stdinIsTTY: deps.stdinIsTTY ?? process.stdin.isTTY,
1219
+ pipedInput,
1220
+ hasPreparedInput,
1221
+ print: Boolean(parsedArgs.print),
1222
+ mode: parsedArgs.mode,
1223
+ });
1224
+ if (disposition.nonInteractiveError) {
1225
+ process.stderr.write(`${chalk.red(disposition.nonInteractiveError)}\n`);
1226
+ process.exit(1);
1227
+ }
1228
+ const autoPrint = disposition.autoPrint;
1164
1229
  const startupUpdateRoute = classifyStartupUpdateRoute(parsedArgs, autoPrint);
1165
1230
  const startupUpdate = new StartupUpdateOrchestrator(
1166
1231
  startupUpdateRoute,
1167
1232
  () => settingsInstance.get("startup.checkUpdate"),
1168
1233
  deps.startupUpdate?.check ?? (() => checkForNewVersion(VERSION)),
1169
1234
  );
1170
- const isInteractive = startupUpdateRoute === "interactive";
1235
+ const isInteractive = disposition.isInteractive;
1171
1236
  const mode = parsedArgs.mode || "text";
1172
1237
 
1173
1238
  // Initialize discovery system with settings for provider persistence
@@ -1299,6 +1364,7 @@ export async function runRootCommand(
1299
1364
  sessionOptions.modelRegistry = modelRegistry;
1300
1365
  sessionOptions.hasUI = isInteractive;
1301
1366
  sessionOptions.notificationHostModeSupported = isInteractive;
1367
+ sessionOptions.sdkHostModeSupported = isInteractive;
1302
1368
  sessionOptions.settings = settingsInstance;
1303
1369
  const hasRootStartupProfile = Boolean(settingsInstance.get("modelProfile.default") || parsedArgs.mpreset);
1304
1370
 
@@ -1331,6 +1397,30 @@ export async function runRootCommand(
1331
1397
  applyCliRuntimeApiKeyOverride(authStorage, parsedArgs.apiKey, sessionOptions.model);
1332
1398
  }
1333
1399
 
1400
+ if (
1401
+ !deps.suppressProcessExit &&
1402
+ deps.createAgentSession === undefined &&
1403
+ parsedArgs.noSession === true &&
1404
+ !parsedArgs.continue &&
1405
+ parsedArgs.resume === undefined &&
1406
+ parsedArgs.fork === undefined &&
1407
+ !isInteractive &&
1408
+ mode !== "acp" &&
1409
+ !hasRootStartupProfile &&
1410
+ !sessionOptions.model &&
1411
+ !sessionOptions.modelPattern &&
1412
+ modelRegistry.getAvailable().length === 0
1413
+ ) {
1414
+ process.stderr.write(`${chalk.red(`No models available. ${formatModelOnboardingGuidance()}`)}\n`);
1415
+ process.stderr.write(
1416
+ `${chalk.yellow(`\nAdvanced manual config remains available at ${ModelsConfigFile.path()}`)}\n`,
1417
+ );
1418
+ process.exitCode = 1;
1419
+ authStorage.close();
1420
+ stopThemeWatcher();
1421
+ await postmortem.cleanup();
1422
+ return;
1423
+ }
1334
1424
  const createAgentSessionImpl = deps.createAgentSession ?? createAgentSession;
1335
1425
  const createSession: CreateSessionForMain = async (options, context): Promise<CreateAgentSessionResult> => {
1336
1426
  const result = await logger.time("createAgentSession", createAgentSessionImpl, options);
@@ -1345,10 +1435,16 @@ export async function runRootCommand(
1345
1435
  };
1346
1436
 
1347
1437
  if (mode === "acp") {
1348
- await (deps.runAcpMode ?? (await import("./modes/acp")).runAcpMode)({
1349
- agentDir: settingsInstance.getAgentDir(),
1350
- ...(acpStartupOptions ? { startupOptions: acpStartupOptions } : {}),
1351
- });
1438
+ try {
1439
+ await (deps.runAcpMode ?? (await import("./modes/acp")).runAcpMode)({
1440
+ agentDir: settingsInstance.getAgentDir(),
1441
+ ...(acpStartupOptions ? { startupOptions: acpStartupOptions } : {}),
1442
+ });
1443
+ } finally {
1444
+ authStorage.close();
1445
+ stopThemeWatcher();
1446
+ if (!deps.suppressProcessExit) await postmortem.cleanup();
1447
+ }
1352
1448
  } else {
1353
1449
  const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager, eventBus } = await createSession(
1354
1450
  sessionOptions,
@@ -1483,20 +1579,21 @@ export async function runRootCommand(
1483
1579
  }
1484
1580
  } else {
1485
1581
  const runPrint = deps.runPrintMode ?? (await import("./modes/print-mode")).runPrintMode;
1486
- await runPrint(session, {
1487
- mode,
1488
- messages: parsedArgs.messages,
1489
- initialMessage,
1490
- initialImages,
1491
- suppressProcessExit: deps.suppressProcessExit,
1492
- });
1493
- if ($env.PI_TIMING) {
1494
- logger.printTimings();
1495
- }
1496
- stopThemeWatcher();
1497
- if (!deps.suppressProcessExit) {
1498
- const exitCode = typeof process.exitCode === "number" ? process.exitCode : 0;
1499
- await postmortem.quit(exitCode);
1582
+ try {
1583
+ await runPrint(session, {
1584
+ mode,
1585
+ messages: parsedArgs.messages,
1586
+ initialMessage,
1587
+ initialImages,
1588
+ suppressProcessExit: deps.suppressProcessExit,
1589
+ });
1590
+ if ($env.PI_TIMING) {
1591
+ logger.printTimings();
1592
+ }
1593
+ } finally {
1594
+ stopThemeWatcher();
1595
+ authStorage.close();
1596
+ if (!deps.suppressProcessExit) await postmortem.cleanup();
1500
1597
  }
1501
1598
  }
1502
1599
  }
@@ -249,6 +249,7 @@ async function runPhase1(options: {
249
249
 
250
250
  const claims = claimStage1Jobs(db, {
251
251
  nowSec,
252
+ cwd: session.sessionManager.getCwd(),
252
253
  threadScanLimit: config.threadScanLimit,
253
254
  maxRolloutsPerStartup: config.maxRolloutsPerStartup,
254
255
  maxRolloutAgeDays: config.maxRolloutAgeDays,
@@ -137,6 +137,7 @@ export function claimStage1Jobs(
137
137
  db: Database,
138
138
  params: {
139
139
  nowSec: number;
140
+ cwd: string;
140
141
  threadScanLimit: number;
141
142
  maxRolloutsPerStartup: number;
142
143
  maxRolloutAgeDays: number;
@@ -149,6 +150,7 @@ export function claimStage1Jobs(
149
150
  ): Stage1Claim[] {
150
151
  const {
151
152
  nowSec,
153
+ cwd,
152
154
  threadScanLimit,
153
155
  maxRolloutsPerStartup,
154
156
  maxRolloutAgeDays,
@@ -168,8 +170,10 @@ export function claimStage1Jobs(
168
170
  let runningCount = runningCountRow?.count ?? 0;
169
171
  if (runningCount >= runningConcurrencyCap) return [];
170
172
  const candidateRows = db
171
- .prepare("SELECT id, updated_at, rollout_path, cwd, source_kind FROM threads ORDER BY updated_at DESC LIMIT ?")
172
- .all(threadScanLimit) as Array<{
173
+ .prepare(
174
+ "SELECT id, updated_at, rollout_path, cwd, source_kind FROM threads WHERE cwd = ? ORDER BY updated_at DESC LIMIT ?",
175
+ )
176
+ .all(cwd, threadScanLimit) as Array<{
173
177
  id: string;
174
178
  updated_at: number;
175
179
  rollout_path: string;
@@ -0,0 +1,165 @@
1
+ import type { AppKeybinding } from "../config/keybindings";
2
+
3
+ export type FocusDomain = "composer" | "selector" | "overlay" | "global";
4
+
5
+ export interface ActionMetadata {
6
+ id: AppKeybinding;
7
+ title: string;
8
+ category: string;
9
+ bindingId?: AppKeybinding;
10
+ domains: readonly FocusDomain[];
11
+ exclusiveGroup?: string | false;
12
+ }
13
+
14
+ const action = (
15
+ id: AppKeybinding,
16
+ title: string,
17
+ category: string,
18
+ domains: FocusDomain[],
19
+ exclusiveGroup?: string | false,
20
+ ): ActionMetadata => ({
21
+ id,
22
+ title,
23
+ category,
24
+ bindingId: id,
25
+ domains,
26
+ exclusiveGroup,
27
+ });
28
+
29
+ export const APP_ACTION_METADATA: readonly ActionMetadata[] = [
30
+ action("app.interrupt", "Interrupt", "Session", ["global"], false),
31
+ action("app.clear", "Clear", "Session", ["global"]),
32
+ action("app.exit", "Exit", "Session", ["global"]),
33
+ action("app.suspend", "Suspend", "Session", ["global"]),
34
+ action("app.thinking.cycle", "Cycle thinking level", "Thinking", ["composer"]),
35
+ action("app.thinking.toggle", "Toggle thinking", "Thinking", ["composer"]),
36
+ action("app.commandPalette.open", "Open command palette", "Navigation", ["composer"]),
37
+ action("app.model.cycleForward", "Next model", "Model", ["composer"]),
38
+ action("app.model.cycleBackward", "Previous model", "Model", ["composer"]),
39
+ action("app.model.select", "Select model", "Model", ["composer"]),
40
+ action("app.model.selectTemporary", "Select temporary model", "Model", ["composer"]),
41
+ action("app.tools.expand", "Toggle tool expansion", "Tools", ["composer"]),
42
+ action("app.tool.backgroundFold", "Fold foreground tool", "Tools", ["composer"]),
43
+ action("app.editor.external", "Open external editor", "Editor", ["composer"]),
44
+ action("app.message.followUp", "Send follow-up", "Messages", ["composer"]),
45
+ action("app.message.queue", "Queue message", "Messages", ["composer"]),
46
+ action("app.message.dequeue", "Edit queued message", "Messages", ["composer"]),
47
+ action("app.clipboard.pasteImage", "Paste image", "Clipboard", ["composer"]),
48
+ action("app.clipboard.copyLine", "Copy line", "Clipboard", ["composer"]),
49
+ action("app.clipboard.copyPrompt", "Copy prompt", "Clipboard", ["composer"]),
50
+ action("app.session.new", "New session", "Session", ["composer"]),
51
+ action("app.session.tree", "Session tree", "Session", ["composer"]),
52
+ action("app.session.fork", "Fork session", "Session", ["composer"]),
53
+ action("app.session.resume", "Resume session", "Session", ["composer"]),
54
+ action("app.session.observe", "Observe sessions", "Session", ["composer"]),
55
+ action("app.session.dashboard", "Show sessions dashboard", "Session", ["composer"]),
56
+ action("app.jobs.open", "Open jobs", "Jobs", ["composer"]),
57
+ action("app.session.togglePath", "Toggle session path", "Session", ["selector"]),
58
+ action("app.session.toggleSort", "Toggle session sort", "Session", ["selector"]),
59
+ action("app.session.rename", "Rename session", "Session", ["selector"]),
60
+ action("app.session.delete", "Delete session", "Session", ["selector"]),
61
+ action("app.session.deleteNoninvasive", "Delete selected session", "Session", ["selector"]),
62
+ action("app.tree.foldOrUp", "Fold tree item", "Session", ["selector"]),
63
+ action("app.tree.unfoldOrDown", "Unfold tree item", "Session", ["selector"]),
64
+ action("app.plan.toggle", "Toggle plan mode", "Mode", ["composer"]),
65
+ action("app.history.search", "Search history", "Navigation", ["composer"]),
66
+ action("app.stt.toggle", "Toggle speech-to-text", "Speech", ["composer"]),
67
+ action("app.irc.sidebar.toggle", "Toggle IRC sidebar", "IRC", ["composer"]),
68
+ action("app.transcript.browse", "Browse transcript", "Transcript", ["composer"]),
69
+ action("app.transcript.prevTurn", "Previous transcript turn", "Transcript", ["composer"]),
70
+ action("app.transcript.nextTurn", "Next transcript turn", "Transcript", ["composer"]),
71
+ action("app.mode.cycle", "Cycle mode", "Mode", ["composer"]),
72
+ action("app.tasks.toggle", "Toggle tasks pane", "Jobs", ["composer"]),
73
+ action("app.queue.togglePane", "Toggle queue pane", "Messages", ["composer"]),
74
+ action("app.message.sendNow", "Send message now", "Messages", ["composer"]),
75
+ ];
76
+
77
+ export interface ActionDefinition<Context> extends ActionMetadata {
78
+ availability(context: Context): boolean;
79
+ execute(context: Context): void | Promise<void>;
80
+ }
81
+
82
+ export interface ActionRegistryOptions<Context> {
83
+ context: Context;
84
+ showError(error: string): void;
85
+ }
86
+
87
+ export class ActionRegistry<Context> {
88
+ readonly #actions = new Map<AppKeybinding, ActionDefinition<Context>>();
89
+ readonly #busy = new Set<AppKeybinding>();
90
+ readonly #busyGroups = new Set<string>();
91
+ readonly #availability = new Map<AppKeybinding, boolean>();
92
+ #availabilityResetQueued = false;
93
+
94
+ constructor(private readonly options: ActionRegistryOptions<Context>) {}
95
+
96
+ register(action: ActionDefinition<Context>): void {
97
+ if (this.#actions.has(action.id)) throw new Error(`Action already registered: ${action.id}`);
98
+ this.#actions.set(action.id, action);
99
+ }
100
+
101
+ get(id: AppKeybinding): ActionDefinition<Context> | undefined {
102
+ return this.#actions.get(id);
103
+ }
104
+ all(): readonly ActionDefinition<Context>[] {
105
+ return [...this.#actions.values()];
106
+ }
107
+
108
+ #reportError(id: AppKeybinding, phase: "availability" | "execution", error: unknown): void {
109
+ const cause = error instanceof Error ? error.message : String(error);
110
+ try {
111
+ this.options.showError(`Action ${id} ${phase} failed: ${cause}`);
112
+ } catch {
113
+ // Error reporting must not escape an action dispatch.
114
+ }
115
+ }
116
+
117
+ #evaluateAvailability(id: AppKeybinding, action: ActionDefinition<Context>): boolean {
118
+ const cached = this.#availability.get(id);
119
+ if (cached !== undefined) return cached;
120
+ let available: boolean;
121
+ try {
122
+ available = Boolean(action.availability(this.options.context));
123
+ } catch (error) {
124
+ this.#reportError(id, "availability", error);
125
+ available = false;
126
+ }
127
+ this.#availability.set(id, available);
128
+ if (!this.#availabilityResetQueued) {
129
+ this.#availabilityResetQueued = true;
130
+ queueMicrotask(() => {
131
+ this.#availability.clear();
132
+ this.#availabilityResetQueued = false;
133
+ });
134
+ }
135
+ return available;
136
+ }
137
+
138
+ #exclusiveGroup(action: ActionDefinition<Context>): string | undefined {
139
+ return action.exclusiveGroup === false ? undefined : (action.exclusiveGroup ?? "default");
140
+ }
141
+
142
+ isAvailable(id: AppKeybinding): boolean {
143
+ const action = this.#actions.get(id);
144
+ return action ? this.#evaluateAvailability(id, action) : false;
145
+ }
146
+
147
+ async execute(id: AppKeybinding): Promise<boolean> {
148
+ const current = this.#actions.get(id);
149
+ if (!current || this.#busy.has(id) || !this.#evaluateAvailability(id, current)) return false;
150
+ const group = this.#exclusiveGroup(current);
151
+ if (group && this.#busyGroups.has(group)) return false;
152
+ this.#busy.add(id);
153
+ if (group) this.#busyGroups.add(group);
154
+ try {
155
+ await current.execute(this.options.context);
156
+ return true;
157
+ } catch (error) {
158
+ this.#reportError(id, "execution", error);
159
+ return false;
160
+ } finally {
161
+ this.#busy.delete(id);
162
+ if (group) this.#busyGroups.delete(group);
163
+ }
164
+ }
165
+ }
@@ -0,0 +1,40 @@
1
+ import { Container, type SelectItem, SelectList, Text } from "@gajae-code/tui";
2
+ import { getSelectListTheme, theme } from "../../modes/theme/theme";
3
+ import { DynamicBorder } from "./dynamic-border";
4
+
5
+ export interface FramedSelectOptions {
6
+ maxVisible?: number;
7
+ selectedValue?: string;
8
+ onSelect?: (item: SelectItem) => void;
9
+ onCancel?: () => void;
10
+ onSelectionChange?: (item: SelectItem) => void;
11
+ }
12
+
13
+ export interface FramedSelect {
14
+ container: Container;
15
+ selectList: SelectList;
16
+ }
17
+
18
+ /** Build the shared border/list selector chrome used by compact mode selectors. */
19
+ export function FramedSelect(
20
+ title: string | undefined,
21
+ items: ReadonlyArray<SelectItem>,
22
+ opts: FramedSelectOptions = {},
23
+ ): FramedSelect {
24
+ const container = new Container();
25
+ container.addChild(new DynamicBorder());
26
+ if (title) {
27
+ container.addChild(new Text(theme.bold(theme.fg("accent", title)), 0, 0));
28
+ }
29
+
30
+ const selectList = new SelectList(items, opts.maxVisible ?? Math.min(items.length, 10), getSelectListTheme());
31
+ const selectedIndex =
32
+ opts.selectedValue === undefined ? -1 : items.findIndex(item => item.value === opts.selectedValue);
33
+ if (selectedIndex !== -1) selectList.setSelectedIndex(selectedIndex);
34
+ selectList.onSelect = item => opts.onSelect?.(item);
35
+ selectList.onCancel = () => opts.onCancel?.();
36
+ if (opts.onSelectionChange) selectList.onSelectionChange = opts.onSelectionChange;
37
+ container.addChild(selectList);
38
+ container.addChild(new DynamicBorder());
39
+ return { container, selectList };
40
+ }