@gajae-code/coding-agent 0.17.2 → 0.17.4

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 (349) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/dist/types/cli/daemon-cli.d.ts +3 -1
  3. package/dist/types/cli/github-release.d.ts +17 -0
  4. package/dist/types/cli/public-command-entry.d.ts +56 -0
  5. package/dist/types/cli/public-command-errors.d.ts +126 -0
  6. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  7. package/dist/types/cli/public-command-help.d.ts +65 -0
  8. package/dist/types/cli/public-command-registry.d.ts +69 -0
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -0
  11. package/dist/types/cli/skills-cli.d.ts +5 -0
  12. package/dist/types/cli.d.ts +3 -0
  13. package/dist/types/commands/daemon.d.ts +4 -50
  14. package/dist/types/commands/sdk.d.ts +20 -37
  15. package/dist/types/commands/setup.d.ts +3 -0
  16. package/dist/types/commands/skills.d.ts +9 -0
  17. package/dist/types/config/autorouting-tier-map.d.ts +18 -0
  18. package/dist/types/config/file-lock.d.ts +9 -1
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +11 -2
  21. package/dist/types/config/settings-schema.d.ts +11 -0
  22. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  23. package/dist/types/daemon/operator-contract.d.ts +5 -1
  24. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  25. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  26. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  27. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  28. package/dist/types/extensibility/shared-events.d.ts +3 -1
  29. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  30. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  31. package/dist/types/internal-urls/index.d.ts +1 -0
  32. package/dist/types/internal-urls/parse.d.ts +9 -1
  33. package/dist/types/main.d.ts +22 -0
  34. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  35. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  36. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  37. package/dist/types/modes/components/session-selector.d.ts +5 -2
  38. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  39. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  40. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  41. package/dist/types/modes/interactive-mode.d.ts +1 -1
  42. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  43. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  44. package/dist/types/modes/types.d.ts +2 -2
  45. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  46. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  47. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  48. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  49. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  50. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  51. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  52. package/dist/types/sdk/broker/broker.d.ts +13 -2
  53. package/dist/types/sdk/broker/identity.d.ts +3 -1
  54. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  55. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  56. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  57. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  58. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  59. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  60. package/dist/types/sdk/bus/index.d.ts +19 -0
  61. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  62. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  63. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  64. package/dist/types/sdk/cli/rows.d.ts +19 -1
  65. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  66. package/dist/types/sdk/client/client.d.ts +2 -0
  67. package/dist/types/sdk/guides/cli.d.ts +3 -4
  68. package/dist/types/sdk/guides/verify.d.ts +2 -0
  69. package/dist/types/sdk/host/host.d.ts +9 -0
  70. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  71. package/dist/types/sdk/host/types.d.ts +10 -0
  72. package/dist/types/sdk/index.d.ts +1 -0
  73. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  74. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  75. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  76. package/dist/types/sdk/router/session-router.d.ts +2 -0
  77. package/dist/types/sdk/startup-capability.d.ts +2 -0
  78. package/dist/types/sdk/transport/relay.d.ts +1 -0
  79. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  80. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  81. package/dist/types/session/agent-session.d.ts +168 -21
  82. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  83. package/dist/types/session/fold-coordinator.d.ts +17 -3
  84. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  85. package/dist/types/session/session-manager.d.ts +64 -1
  86. package/dist/types/session/session-work-lease.d.ts +14 -0
  87. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  88. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  89. package/dist/types/tools/bash.d.ts +37 -11
  90. package/dist/types/tools/job.d.ts +1 -1
  91. package/dist/types/tools/steer-fold.d.ts +31 -0
  92. package/dist/types/utils/git.d.ts +28 -2
  93. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  94. package/dist/types/utils/mupdf.d.ts +4 -0
  95. package/dist/types/utils/session-title-generation.d.ts +6 -0
  96. package/dist/types/utils/title-generator.d.ts +11 -2
  97. package/dist/types/web/scrapers/types.d.ts +3 -0
  98. package/package.json +18 -10
  99. package/scripts/build-binary.ts +9 -3
  100. package/scripts/compile-args.ts +0 -9
  101. package/scripts/embed-mupdf.ts +52 -0
  102. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  103. package/scripts/vendor-markit.ts +196 -0
  104. package/src/async/job-manager.ts +22 -1
  105. package/src/cli/daemon-cli.ts +53 -18
  106. package/src/cli/github-release.ts +87 -5
  107. package/src/cli/mcp-cli.ts +38 -4
  108. package/src/cli/public-command-entry.ts +450 -0
  109. package/src/cli/public-command-errors.ts +581 -0
  110. package/src/cli/public-command-evidence.ts +717 -0
  111. package/src/cli/public-command-help.ts +360 -0
  112. package/src/cli/public-command-registry.ts +535 -0
  113. package/src/cli/session-picker.ts +2 -1
  114. package/src/cli/setup-cli.ts +9 -1
  115. package/src/cli/skills-cli.ts +46 -2
  116. package/src/cli.ts +61 -1
  117. package/src/commands/daemon.ts +65 -69
  118. package/src/commands/sdk.ts +175 -117
  119. package/src/commands/setup.ts +2 -0
  120. package/src/commands/skills.ts +11 -0
  121. package/src/config/autorouting-tier-map.ts +14 -0
  122. package/src/config/file-lock.ts +147 -51
  123. package/src/config/model-profile-activation.ts +334 -43
  124. package/src/config/model-registry.ts +183 -25
  125. package/src/config/settings-schema.ts +26 -2
  126. package/src/coordinator-mcp/policy.ts +10 -5
  127. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  128. package/src/coordinator-mcp/question-state.ts +175 -8
  129. package/src/coordinator-mcp/server.ts +507 -81
  130. package/src/coordinator-mcp/session-reaper.ts +6 -4
  131. package/src/crash/record-loader.ts +6 -2
  132. package/src/daemon/operator-contract.ts +26 -12
  133. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  134. package/src/eval/js/context-manager.ts +8 -8
  135. package/src/eval/js/worker-core.ts +9 -4
  136. package/src/eval/js/worker-protocol.ts +2 -0
  137. package/src/extensibility/extensions/runner.ts +52 -4
  138. package/src/extensibility/extensions/types.ts +10 -0
  139. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  140. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  141. package/src/extensibility/shared-events.ts +3 -1
  142. package/src/gjc-runtime/session-state-lock.ts +165 -24
  143. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  144. package/src/internal-urls/docs-index.generated.ts +20 -19
  145. package/src/internal-urls/embedded-protocol.ts +83 -0
  146. package/src/internal-urls/index.ts +1 -0
  147. package/src/internal-urls/parse.ts +21 -1
  148. package/src/internal-urls/router.ts +4 -2
  149. package/src/main.ts +123 -55
  150. package/src/modes/acp/acp-agent.ts +408 -35
  151. package/src/modes/acp/startup-provenance.ts +59 -0
  152. package/src/modes/components/custom-provider-wizard.ts +308 -7
  153. package/src/modes/components/oauth-selector.ts +9 -0
  154. package/src/modes/components/session-selector.ts +47 -4
  155. package/src/modes/components/user-message-selector.ts +57 -15
  156. package/src/modes/controllers/command-controller.ts +58 -32
  157. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  158. package/src/modes/controllers/input-controller.ts +4 -0
  159. package/src/modes/controllers/selector-controller.ts +161 -15
  160. package/src/modes/interactive-mode.ts +21 -7
  161. package/src/modes/runtime-init.ts +1 -0
  162. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  163. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  164. package/src/modes/types.ts +2 -2
  165. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  166. package/src/prompts/system/title-system.md +1 -1
  167. package/src/prompts/tools/bash.md +1 -1
  168. package/src/prompts/tools/job.md +1 -1
  169. package/src/prompts/tools/read.md +2 -0
  170. package/src/runtime/network-prewarm-service.test.ts +36 -1
  171. package/src/runtime/network-prewarm-service.ts +28 -3
  172. package/src/runtime/process-lifecycle.ts +22 -11
  173. package/src/runtime-mcp/config.ts +16 -1
  174. package/src/runtime-mcp/manager.ts +689 -55
  175. package/src/runtime-mcp/pool.test.ts +31 -0
  176. package/src/runtime-mcp/pool.ts +304 -28
  177. package/src/runtime-mcp/redaction.ts +42 -0
  178. package/src/runtime-mcp/startup-policy.ts +7 -0
  179. package/src/runtime-mcp/tool-bridge.ts +71 -0
  180. package/src/runtime-mcp/transports/http.ts +108 -25
  181. package/src/sdk/acp/adapter.ts +27 -8
  182. package/src/sdk/broker/broker.ts +378 -52
  183. package/src/sdk/broker/identity.ts +9 -1
  184. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  185. package/src/sdk/broker/lifecycle.ts +690 -141
  186. package/src/sdk/broker/runtime.ts +1 -1
  187. package/src/sdk/broker/session-index.ts +97 -4
  188. package/src/sdk/broker/spawn-authority.ts +35 -7
  189. package/src/sdk/broker/stderr-drainer.ts +59 -0
  190. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  191. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  192. package/src/sdk/bus/index.ts +690 -80
  193. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  194. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  195. package/src/sdk/bus/telegram-daemon.ts +10 -3
  196. package/src/sdk/cli/master-cli.ts +47 -36
  197. package/src/sdk/cli/rows.ts +30 -7
  198. package/src/sdk/cli/session-cli.ts +687 -184
  199. package/src/sdk/client/client.ts +18 -0
  200. package/src/sdk/guides/catalog.ts +2 -2
  201. package/src/sdk/guides/cli.ts +20 -62
  202. package/src/sdk/guides/verify.ts +14 -0
  203. package/src/sdk/host/host.ts +49 -5
  204. package/src/sdk/host/session-runtime.test.ts +138 -2
  205. package/src/sdk/host/session-runtime.ts +311 -97
  206. package/src/sdk/host/types.ts +12 -1
  207. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  208. package/src/sdk/host/websocket-transport.ts +40 -6
  209. package/src/sdk/index.ts +11 -0
  210. package/src/sdk/prompt-deadline-flush.ts +334 -0
  211. package/src/sdk/prompt-deadline-manager.ts +133 -1
  212. package/src/sdk/prompt-tool-boundary.ts +121 -0
  213. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  214. package/src/sdk/router/session-router.ts +9 -1
  215. package/src/sdk/session.ts +182 -7
  216. package/src/sdk/startup-capability.ts +1 -1
  217. package/src/sdk/transport/relay.ts +71 -6
  218. package/src/sdk/transport/serve-cli.ts +397 -42
  219. package/src/sdk/transport/socket.ts +12 -11
  220. package/src/sdk/transport/stdio.ts +7 -3
  221. package/src/session/agent-session.ts +2437 -400
  222. package/src/session/fallback-chain-controller.ts +6 -7
  223. package/src/session/fold-coordinator.ts +24 -5
  224. package/src/session/internal/managed-session-scope.ts +58 -18
  225. package/src/session/internal/managed-session-storage.ts +304 -11
  226. package/src/session/session-manager.ts +382 -44
  227. package/src/session/session-work-lease.ts +34 -0
  228. package/src/setup/model-onboarding-guidance.ts +1 -1
  229. package/src/setup/provider-onboarding.ts +397 -24
  230. package/src/slash-commands/builtin-registry.ts +258 -14
  231. package/src/task/index.ts +14 -2
  232. package/src/tools/bash.ts +98 -78
  233. package/src/tools/fetch.ts +119 -20
  234. package/src/tools/job.ts +151 -25
  235. package/src/tools/output-meta.ts +15 -2
  236. package/src/tools/path-utils.ts +27 -3
  237. package/src/tools/read.ts +3 -3
  238. package/src/tools/steer-fold.ts +107 -0
  239. package/src/tools/tool-catalog.generated.ts +29 -2
  240. package/src/tools/tool-errors.ts +4 -0
  241. package/src/utils/git.ts +53 -3
  242. package/src/utils/markit.ts +55 -41
  243. package/src/utils/mupdf-assets.d.ts +4 -0
  244. package/src/utils/mupdf-embedded.ts +4 -0
  245. package/src/utils/mupdf.ts +129 -0
  246. package/src/utils/session-title-generation.ts +29 -0
  247. package/src/utils/title-generator.ts +46 -2
  248. package/src/web/scrapers/types.ts +24 -3
  249. package/vendor/markit-ai/LICENSE +21 -0
  250. package/vendor/markit-ai/MANIFEST.json +412 -0
  251. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  252. package/vendor/markit-ai/dist/commands/config.js +139 -0
  253. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  254. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  255. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  256. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  257. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  258. package/vendor/markit-ai/dist/commands/init.js +29 -0
  259. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  260. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  261. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  262. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  263. package/vendor/markit-ai/dist/config.d.ts +26 -0
  264. package/vendor/markit-ai/dist/config.js +42 -0
  265. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  266. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  267. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  268. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  269. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  270. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  271. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  272. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  273. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  274. package/vendor/markit-ai/dist/converters/github.js +148 -0
  275. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  276. package/vendor/markit-ai/dist/converters/html.js +30 -0
  277. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  278. package/vendor/markit-ai/dist/converters/image.js +124 -0
  279. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  280. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  281. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  282. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  283. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  284. package/vendor/markit-ai/dist/converters/json.js +21 -0
  285. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  286. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  287. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  288. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  289. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  290. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  291. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  292. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  293. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  294. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  295. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  296. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  297. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  298. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  299. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  300. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  301. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  302. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  303. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  304. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  305. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  306. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  307. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  308. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  309. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  310. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  312. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  313. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  314. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  315. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  316. package/vendor/markit-ai/dist/index.d.ts +30 -0
  317. package/vendor/markit-ai/dist/index.js +26 -0
  318. package/vendor/markit-ai/dist/main.d.ts +2 -0
  319. package/vendor/markit-ai/dist/main.js +191 -0
  320. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  321. package/vendor/markit-ai/dist/markit.js +267 -0
  322. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  323. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  324. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  325. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  326. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  327. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  328. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  329. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  330. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  331. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  332. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  333. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  334. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  335. package/vendor/markit-ai/dist/providers/index.js +56 -0
  336. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  337. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  338. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  339. package/vendor/markit-ai/dist/providers/types.js +1 -0
  340. package/vendor/markit-ai/dist/types.d.ts +36 -0
  341. package/vendor/markit-ai/dist/types.js +1 -0
  342. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  343. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  344. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  345. package/vendor/markit-ai/dist/utils/output.js +31 -0
  346. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  347. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  348. package/vendor/markit-ai/package.json +74 -0
  349. package/vendor/markit-ai.patch +49 -0
@@ -14,9 +14,10 @@ interface UserMessageItem {
14
14
  */
15
15
  class UserMessageList implements Component {
16
16
  #selectedIndex: number = 0;
17
+ #selectionInFlight = false;
17
18
  onSelect?: (entryId: string) => void;
18
19
  onCancel?: () => void;
19
- #maxVisible: number = 10; // Max messages visible
20
+ #availableRows = Number.POSITIVE_INFINITY;
20
21
 
21
22
  constructor(private readonly messages: UserMessageItem[]) {
22
23
  // Store messages in chronological order (oldest to newest)
@@ -24,6 +25,10 @@ class UserMessageList implements Component {
24
25
  this.#selectedIndex = Math.max(0, this.messages.length - 1);
25
26
  }
26
27
 
28
+ setAvailableRows(rows: number): void {
29
+ this.#availableRows = Math.max(3, rows);
30
+ }
31
+
27
32
  invalidate(): void {
28
33
  // No cached state to invalidate currently
29
34
  }
@@ -36,12 +41,15 @@ class UserMessageList implements Component {
36
41
  return lines;
37
42
  }
38
43
 
39
- // Calculate visible range with scrolling
44
+ // Each entry uses two content rows plus a separator. Leave one row for
45
+ // the scroll position whenever the full history does not fit.
46
+ const needsScrollIndicator = this.messages.length * 3 > this.#availableRows;
47
+ const maxVisible = Math.max(1, Math.floor((this.#availableRows - (needsScrollIndicator ? 1 : 0)) / 3));
40
48
  const startIndex = Math.max(
41
49
  0,
42
- Math.min(this.#selectedIndex - Math.floor(this.#maxVisible / 2), this.messages.length - this.#maxVisible),
50
+ Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), this.messages.length - maxVisible),
43
51
  );
44
- const endIndex = Math.min(startIndex + this.#maxVisible, this.messages.length);
52
+ const endIndex = Math.min(startIndex + maxVisible, this.messages.length);
45
53
 
46
54
  // Render visible messages (2 lines per message + blank line)
47
55
  for (let i = startIndex; i < endIndex; i++) {
@@ -77,6 +85,7 @@ class UserMessageList implements Component {
77
85
  }
78
86
 
79
87
  handleInput(keyData: string): void {
88
+ if (this.#selectionInFlight) return;
80
89
  // Up arrow - go to previous (older) message, wrap to bottom when at top
81
90
  if (matchesKey(keyData, "up")) {
82
91
  this.#selectedIndex = this.#selectedIndex === 0 ? this.messages.length - 1 : this.#selectedIndex - 1;
@@ -89,6 +98,7 @@ class UserMessageList implements Component {
89
98
  else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
90
99
  const selected = this.messages[this.#selectedIndex];
91
100
  if (selected && this.onSelect) {
101
+ this.#selectionInFlight = true;
92
102
  this.onSelect(selected.id);
93
103
  }
94
104
  }
@@ -102,21 +112,43 @@ class UserMessageList implements Component {
102
112
  }
103
113
 
104
114
  /**
105
- * Component that renders a user message selector for branching
115
+ * Component that renders a prompt selector for creating a new session
106
116
  */
107
117
  export class UserMessageSelectorComponent extends Container {
108
118
  #messageList: UserMessageList;
109
-
110
- constructor(messages: UserMessageItem[], onSelect: (entryId: string) => void, onCancel: () => void) {
119
+ #header: Container;
120
+ #footer: Container;
121
+
122
+ constructor(
123
+ messages: UserMessageItem[],
124
+ onSelect: (entryId: string) => void,
125
+ onCancel: () => void,
126
+ private readonly getViewportRows: () => number,
127
+ ) {
111
128
  super();
112
129
 
113
130
  // Add header
114
- this.addChild(new Spacer(1));
115
- this.addChild(new Text(theme.bold("Branch from Message"), 1, 0));
116
- this.addChild(new Text(theme.fg("muted", "Select a message to create a new branch from that point"), 1, 0));
117
- this.addChild(new Spacer(1));
118
- this.addChild(new DynamicBorder());
119
- this.addChild(new Spacer(1));
131
+ this.#header = new Container();
132
+ this.#header.addChild(new Spacer(1));
133
+ this.#header.addChild(new Text(theme.bold("Fork from Prompt"), 1, 0));
134
+ this.#header.addChild(
135
+ new Text(
136
+ theme.fg(
137
+ "muted",
138
+ "Creates a new session with history before the prompt; shared files stay in the same cwd.",
139
+ ),
140
+ 1,
141
+ 0,
142
+ ),
143
+ );
144
+ this.#header.addChild(
145
+ new Text(theme.fg("muted", "The selected prompt is restored for editing, not submitted."), 1, 0),
146
+ );
147
+ this.#header.addChild(new Text(theme.fg("muted", "↑/↓ move · Enter select · Esc cancel"), 1, 0));
148
+ this.#header.addChild(new Spacer(1));
149
+ this.#header.addChild(new DynamicBorder());
150
+ this.#header.addChild(new Spacer(1));
151
+ this.addChild(this.#header);
120
152
 
121
153
  // Create message list
122
154
  this.#messageList = new UserMessageList(messages);
@@ -126,8 +158,10 @@ export class UserMessageSelectorComponent extends Container {
126
158
  this.addChild(this.#messageList);
127
159
 
128
160
  // Add bottom border
129
- this.addChild(new Spacer(1));
130
- this.addChild(new DynamicBorder());
161
+ this.#footer = new Container();
162
+ this.#footer.addChild(new Spacer(1));
163
+ this.#footer.addChild(new DynamicBorder());
164
+ this.addChild(this.#footer);
131
165
 
132
166
  // Auto-cancel if no messages
133
167
  if (messages.length === 0) {
@@ -135,6 +169,14 @@ export class UserMessageSelectorComponent extends Container {
135
169
  }
136
170
  }
137
171
 
172
+ render(width: number): string[] {
173
+ const chromeRows = this.#header.render(width).length + this.#footer.render(width).length;
174
+ // Reserve the pinned status/composer rows outside the overlay. Recompute
175
+ // against current terminal rows and wrapped header height on every render.
176
+ this.#messageList.setAvailableRows(this.getViewportRows() - chromeRows - 2);
177
+ return super.render(width);
178
+ }
179
+
138
180
  getMessageList(): UserMessageList {
139
181
  return this.#messageList;
140
182
  }
@@ -38,7 +38,16 @@ import { replaceTabs } from "../../tools/render-utils";
38
38
  import { getDisplayChangelogEntries } from "../../utils/changelog";
39
39
  import { copyToClipboard } from "../../utils/clipboard";
40
40
  import { openPath } from "../../utils/open";
41
- import { setSessionTerminalTitle } from "../../utils/title-generator";
41
+ import {
42
+ beginSessionTitleGeneration,
43
+ invalidateSessionTitleGeneration,
44
+ isSessionTitleGenerationCurrent,
45
+ } from "../../utils/session-title-generation";
46
+ import {
47
+ buildConversationTitleInput,
48
+ generateSessionTitle,
49
+ setSessionTerminalTitle,
50
+ } from "../../utils/title-generator";
42
51
  import { addChatChild, prepareTranscriptRebuild, syncPendingExecutionComponents } from "../utils/ui-helpers";
43
52
 
44
53
  type HindsightModule = typeof import("../../hindsight");
@@ -84,6 +93,7 @@ export function buildHelpMarkdown(keybindings: Pick<KeybindingsManager, "getAcce
84
93
  "| Resume another session | `/resume` |",
85
94
  "| Show session details | `/session info` |",
86
95
  "| Delete current session transcript/artifacts | `/session delete` |",
96
+ "| Fork from an earlier prompt into a new session | `/fork` |",
87
97
  `| Select a model | \`/model\` or ${selectModelKey} |`,
88
98
  `| Queue a message for the next turn | ${queueMessageKey} |`,
89
99
  `| Select or edit a queued message | ${dequeueMessageKey} |`,
@@ -1109,31 +1119,7 @@ export class CommandController {
1109
1119
  }
1110
1120
 
1111
1121
  async handleForkCommand(): Promise<void> {
1112
- if (this.ctx.session.isStreaming) {
1113
- this.ctx.showWarning("Wait for the current response to finish or abort it before forking.");
1114
- return;
1115
- }
1116
-
1117
- const success = await this.ctx.session.fork();
1118
- if (this.ctx.isStopped?.()) return;
1119
- if (!success) {
1120
- this.ctx.showError("Fork failed (session not persisted or cancelled)");
1121
- return;
1122
- }
1123
- clearInteractiveActivityLoaders(this.ctx);
1124
- stopInteractiveActivityIndicator(this.ctx, { foregroundSettled: true });
1125
- this.ctx.resetIrcSidebarSession();
1126
-
1127
- this.ctx.statusLine.invalidate();
1128
- this.ctx.updateEditorTopBorder();
1129
-
1130
- const sessionFile = this.ctx.session.sessionFile;
1131
- const shortPath = sessionFile ? sessionFile.split("/").pop() : "new session";
1132
- this.ctx.chatContainer.addChild(new Spacer(1));
1133
- this.ctx.chatContainer.addChild(
1134
- new Text(`${theme.fg("accent", `${theme.status.success} Session forked to ${shortPath}`)}`, 1, 1),
1135
- );
1136
- this.ctx.ui.requestRender();
1122
+ this.ctx.showUserMessageSelector();
1137
1123
  }
1138
1124
 
1139
1125
  async handleMoveCommand(targetPath: string): Promise<void> {
@@ -1186,23 +1172,63 @@ export class CommandController {
1186
1172
  }
1187
1173
  }
1188
1174
 
1189
- async handleRenameCommand(title: string): Promise<void> {
1175
+ async handleRenameCommand(title?: string): Promise<void> {
1190
1176
  try {
1177
+ if (!title?.trim()) {
1178
+ const generation = beginSessionTitleGeneration(this.ctx.sessionManager);
1179
+ const input = buildConversationTitleInput(this.ctx.session.messages);
1180
+ if (!input) {
1181
+ this.ctx.showError("Nothing to summarize yet — pass a title: /rename <title>");
1182
+ return;
1183
+ }
1184
+
1185
+ const generated = await generateSessionTitle(
1186
+ input,
1187
+ this.ctx.session.modelRegistry,
1188
+ this.ctx.settings,
1189
+ this.ctx.session.credentialSessionId,
1190
+ this.ctx.session.model,
1191
+ provider => this.ctx.session.agent.metadataForProvider(provider),
1192
+ );
1193
+ if (
1194
+ !isSessionTitleGenerationCurrent(this.ctx.sessionManager, generation) ||
1195
+ buildConversationTitleInput(this.ctx.session.messages) !== input
1196
+ )
1197
+ return;
1198
+ if (!generated) {
1199
+ this.ctx.showError("Could not generate a session title — pass one: /rename <title>");
1200
+ return;
1201
+ }
1202
+
1203
+ const stored = await this.ctx.sessionManager.setSessionName(generated, "user");
1204
+ if (!stored) {
1205
+ this.ctx.showError("Session name cannot be empty.");
1206
+ return;
1207
+ }
1208
+ this.#finishRename();
1209
+ return;
1210
+ }
1211
+
1212
+ invalidateSessionTitleGeneration(this.ctx.sessionManager);
1191
1213
  const stored = await this.ctx.sessionManager.setSessionName(title, "user");
1192
1214
  if (!stored) {
1193
1215
  this.ctx.showError("Session name cannot be empty.");
1194
1216
  return;
1195
1217
  }
1196
- const name = this.ctx.sessionManager.getSessionName()!;
1197
- setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd());
1198
- this.ctx.statusLine.invalidate();
1199
- this.ctx.updateEditorBorderColor();
1200
- this.ctx.showStatus(`Session renamed to "${name}".`);
1218
+ this.#finishRename();
1201
1219
  } catch (err) {
1202
1220
  this.ctx.showError(`Rename failed: ${err instanceof Error ? err.message : String(err)}`);
1203
1221
  }
1204
1222
  }
1205
1223
 
1224
+ #finishRename(): void {
1225
+ const name = this.ctx.sessionManager.getSessionName()!;
1226
+ setSessionTerminalTitle(name, this.ctx.sessionManager.getCwd());
1227
+ this.ctx.statusLine.invalidate();
1228
+ this.ctx.updateEditorBorderColor();
1229
+ this.ctx.showStatus(`Session renamed to "${name}".`);
1230
+ }
1231
+
1206
1232
  async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
1207
1233
  const isDeferred = this.ctx.session.isStreaming;
1208
1234
  const component = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);
@@ -146,31 +146,55 @@ export class ExtensionUiController {
146
146
  const widgetsBelow = [...this.#hookWidgetsBelow.entries()];
147
147
  const activeHookCustomComponent = this.#activeHookCustomComponent;
148
148
  const activeHookCustomOverlay = this.#activeHookCustomOverlay;
149
+ const activeHookCustomCancel = this.#activeHookCustomCancel;
149
150
  return () => {
151
+ const errors: unknown[] = [];
152
+ const attempt = (cleanup: () => void): void => {
153
+ try {
154
+ cleanup();
155
+ } catch (error) {
156
+ errors.push(error);
157
+ }
158
+ };
150
159
  for (const unsubscribe of terminalInputUnsubscribers) {
151
- unsubscribe();
152
- this.#extensionTerminalInputUnsubscribers.delete(unsubscribe);
160
+ if (!this.#extensionTerminalInputUnsubscribers.delete(unsubscribe)) continue;
161
+ attempt(unsubscribe);
153
162
  }
154
163
  let widgetsChanged = false;
155
164
  for (const [key, widget] of widgetsAbove) {
156
165
  if (this.#hookWidgetsAbove.get(key) !== widget) continue;
157
166
  this.#hookWidgetsAbove.delete(key);
158
- widget.dispose?.();
167
+ attempt(() => widget.dispose?.());
159
168
  widgetsChanged = true;
160
169
  }
161
170
  for (const [key, widget] of widgetsBelow) {
162
171
  if (this.#hookWidgetsBelow.get(key) !== widget) continue;
163
172
  this.#hookWidgetsBelow.delete(key);
164
- widget.dispose?.();
173
+ attempt(() => widget.dispose?.());
165
174
  widgetsChanged = true;
166
175
  }
167
176
  if (
168
177
  this.#activeHookCustomComponent === activeHookCustomComponent &&
169
- this.#activeHookCustomOverlay === activeHookCustomOverlay
178
+ this.#activeHookCustomOverlay === activeHookCustomOverlay &&
179
+ this.#activeHookCustomCancel === activeHookCustomCancel
170
180
  ) {
171
- this.#clearActiveHookCustom();
181
+ this.#activeHookCustomComponent = undefined;
182
+ this.#activeHookCustomOverlay = undefined;
183
+ this.#activeHookCustomCancel = undefined;
184
+ attempt(() => activeHookCustomCancel?.());
185
+ attempt(() => activeHookCustomComponent?.dispose?.());
186
+ attempt(() => activeHookCustomOverlay?.hide());
187
+ }
188
+ if (widgetsChanged) {
189
+ attempt(() =>
190
+ this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerAbove, this.#hookWidgetsAbove, true),
191
+ );
192
+ attempt(() =>
193
+ this.#renderHookWidgetContainer(this.ctx.hookWidgetContainerBelow, this.#hookWidgetsBelow, false),
194
+ );
195
+ attempt(() => this.ctx.ui.requestRender());
172
196
  }
173
- if (widgetsChanged) this.#rebuildHookWidgets();
197
+ if (errors.length > 0) throw new AggregateError(errors, "Previous session extension UI cleanup failed");
174
198
  };
175
199
  }
176
200
 
@@ -551,6 +575,7 @@ export class ExtensionUiController {
551
575
  getCredentialSessionId: () => this.ctx.session.credentialSessionId,
552
576
  isIdle: () => !this.ctx.session.isStreaming,
553
577
  getActivePromptHandle: () => this.ctx.session.activePromptHandle,
578
+ getSessionWorkLease: () => this.ctx.session.sessionWorkLease,
554
579
  abort: () => this.ctx.session.abort(),
555
580
  abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
556
581
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
@@ -781,6 +806,7 @@ export class ExtensionUiController {
781
806
  getCredentialSessionId: () => this.ctx.session.credentialSessionId,
782
807
  isIdle: () => !this.ctx.session.isStreaming,
783
808
  getActivePromptHandle: () => this.ctx.session.activePromptHandle,
809
+ getSessionWorkLease: () => this.ctx.session.sessionWorkLease,
784
810
  abort: () => this.ctx.session.abort(),
785
811
  abortPromptAndWait: (handle, options) => this.ctx.session.abortPromptAndWait(handle, options),
786
812
  hasPendingMessages: () => this.ctx.session.queuedMessageCount > 0,
@@ -33,6 +33,7 @@ import {
33
33
  locatePastedImageReferenceAroundCursor,
34
34
  parsePastedImagePaths,
35
35
  } from "../../utils/pasted-image-path";
36
+ import { invalidateSessionTitleGeneration } from "../../utils/session-title-generation";
36
37
  import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
37
38
  import { ActionRegistry, APP_ACTION_METADATA } from "../action-registry";
38
39
  import { CommandPalette, type CommandPaletteAction, type CommandPaletteEntry } from "../components/command-palette";
@@ -1146,6 +1147,7 @@ export class InputController {
1146
1147
 
1147
1148
  // Queue input during compaction
1148
1149
  if (this.ctx.session.isCompacting) {
1150
+ invalidateSessionTitleGeneration(this.ctx.sessionManager);
1149
1151
  if ((inputImages?.length ?? 0) > 0) {
1150
1152
  this.ctx.showStatus("Compaction in progress. Retry after it completes to send images.");
1151
1153
  return;
@@ -1159,6 +1161,7 @@ export class InputController {
1159
1161
  // prompt to run after the active turn completes (in submission order).
1160
1162
  // This handles extension commands (execute immediately), prompt template expansion, and queueing
1161
1163
  if (this.ctx.session.isStreaming) {
1164
+ invalidateSessionTitleGeneration(this.ctx.sessionManager);
1162
1165
  if (this.#canModifyComposer(composer)) {
1163
1166
  this.ctx.editor.addToHistory(text);
1164
1167
  this.ctx.editor.setText("");
@@ -1183,6 +1186,7 @@ export class InputController {
1183
1186
  }
1184
1187
 
1185
1188
  // Normal message submission
1189
+ invalidateSessionTitleGeneration(this.ctx.sessionManager);
1186
1190
  // First, move any pending bash components to chat
1187
1191
  this.ctx.flushPendingBashComponents();
1188
1192
 
@@ -155,7 +155,12 @@ import {
155
155
  MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND,
156
156
  MODEL_ONBOARDING_SETUP_COMMAND,
157
157
  } from "../../setup/model-onboarding-guidance";
158
- import { addApiCompatibleProvider, formatProviderSetupResult } from "../../setup/provider-onboarding";
158
+ import {
159
+ addApiCompatibleProvider,
160
+ formatProviderSetupResult,
161
+ probeOpenAIModelsList,
162
+ reloadAndRefreshDiscoveryCatalog,
163
+ } from "../../setup/provider-onboarding";
159
164
  import { getThinkingLevelMetadata } from "../../thinking";
160
165
  import {
161
166
  isConfigurableSearchProviderId,
@@ -1821,15 +1826,48 @@ export class SelectorController {
1821
1826
  this.showSelector(done => {
1822
1827
  let wizard: CustomProviderWizardComponent;
1823
1828
  const submit = async (input: CustomProviderWizardSubmit): Promise<void> => {
1829
+ // Component-owned generation: revision or cancel after submit
1830
+ // start invalidates this attempt. Both completion branches
1831
+ // check it so a stale refresh cannot report success or
1832
+ // rerender over inputs the user has since revised.
1833
+ const generation = wizard.currentSubmitGeneration();
1834
+ const isCurrentSubmission = (): boolean => wizard.isSubmitCurrent(generation);
1824
1835
  try {
1825
- const result = await addApiCompatibleProvider(input);
1826
- await this.ctx.session.modelRegistry.refresh("offline", this.ctx.session.credentialSessionId);
1836
+ const result = await addApiCompatibleProvider({
1837
+ ...input,
1838
+ authStorage: this.ctx.session.modelRegistry.authStorage,
1839
+ });
1840
+ let recoveryHint: string | null = null;
1841
+ if (result.discoveryEnabled && !result.preset) {
1842
+ // A discovery-only add has no static models and no cache yet,
1843
+ // so an offline reload alone leaves an empty catalog. Refresh
1844
+ // only the new provider online; a hint is surfaced instead
1845
+ // of unconditional success when it stays unavailable.
1846
+ recoveryHint = await reloadAndRefreshDiscoveryCatalog(
1847
+ this.ctx.session.modelRegistry,
1848
+ result.providerId,
1849
+ this.ctx.session.credentialSessionId,
1850
+ );
1851
+ } else {
1852
+ await this.ctx.session.modelRegistry.refresh("offline", this.ctx.session.credentialSessionId);
1853
+ }
1827
1854
  await this.ctx.notifyConfigChanged?.();
1828
- this.ctx.showStatus(formatProviderSetupResult(result));
1855
+ // A late settle after Esc-cancel or a mid-submit revision
1856
+ // must not drive completion UI: done() restores the
1857
+ // composer and could close a selector the user opened
1858
+ // after dismissal, and a stale refresh must not report
1859
+ // success over revised inputs.
1860
+ if (!isCurrentSubmission()) return;
1861
+ this.ctx.showStatus(
1862
+ recoveryHint
1863
+ ? `${formatProviderSetupResult(result)}\n${recoveryHint}`
1864
+ : formatProviderSetupResult(result),
1865
+ );
1829
1866
  wizard.complete();
1830
1867
  done();
1831
1868
  this.ctx.ui.requestRender();
1832
1869
  } catch (err) {
1870
+ if (!isCurrentSubmission()) return;
1833
1871
  const message = err instanceof Error ? err.message : String(err);
1834
1872
  wizard.setSubmitError(`Provider setup failed: ${message}`);
1835
1873
  }
@@ -1843,6 +1881,9 @@ export class SelectorController {
1843
1881
  this.ctx.ui.requestRender();
1844
1882
  },
1845
1883
  () => this.ctx.ui.requestRender(),
1884
+ {
1885
+ discoverModels: async request => probeOpenAIModelsList(request),
1886
+ },
1846
1887
  );
1847
1888
  return { component: wizard, focus: wizard };
1848
1889
  });
@@ -3070,36 +3111,138 @@ export class SelectorController {
3070
3111
  });
3071
3112
  }
3072
3113
 
3073
- showUserMessageSelector(): void {
3074
- const userMessages = this.ctx.session.getUserMessagesForBranching();
3114
+ #forkBusyReason(): string | undefined {
3115
+ if (this.ctx.session.isStreaming) {
3116
+ return "Wait for the current response to finish or abort it before forking.";
3117
+ }
3118
+ if (this.ctx.session.isCompacting) {
3119
+ return "Wait for compaction to finish or abort it before forking.";
3120
+ }
3121
+ if (this.ctx.hasPendingSubmission()) {
3122
+ return "Wait for the pending message to be submitted or cancel it before forking.";
3123
+ }
3124
+ if (this.ctx.bashComponent || this.ctx.pythonComponent) {
3125
+ return "Wait for the foreground command to finish or cancel it before forking.";
3126
+ }
3127
+ return undefined;
3128
+ }
3075
3129
 
3130
+ showUserMessageSelector(onCommitted?: () => void): void {
3131
+ const openedSessionId = this.ctx.sessionManager.getSessionId();
3132
+ const openedSessionFile = this.ctx.sessionManager.getSessionFile();
3133
+ if (!openedSessionFile) {
3134
+ this.ctx.showError("Fork requires a persisted session");
3135
+ return;
3136
+ }
3137
+ const busyReason = this.#forkBusyReason();
3138
+ if (busyReason) {
3139
+ this.ctx.showWarning(busyReason);
3140
+ return;
3141
+ }
3142
+
3143
+ const userMessages = this.ctx.session.getUserMessagesForBranching();
3076
3144
  if (userMessages.length === 0) {
3077
3145
  this.ctx.showStatus("No messages to branch from");
3078
3146
  return;
3079
3147
  }
3080
3148
 
3149
+ this.ctx.goalModeController.cancelContinuation();
3150
+ // Close the side-chat composer before the picker takes ownership, including
3151
+ // paths that cancel, fail, or stop while branch() is pending.
3152
+ if (this.ctx.hasActiveBtw()) this.ctx.handleBtwEscape();
3153
+
3081
3154
  this.showSelector(done => {
3155
+ let selectionInFlight = false;
3156
+ const closeWithoutBranch = (message: string, error = false): void => {
3157
+ done();
3158
+ if (error) {
3159
+ this.ctx.showError(message);
3160
+ } else {
3161
+ this.ctx.showStatus(message);
3162
+ }
3163
+ this.ctx.goalModeController.scheduleContinuation();
3164
+ this.ctx.ui.requestRender();
3165
+ };
3082
3166
  const selector = new UserMessageSelectorComponent(
3083
3167
  userMessages.map(m => ({ id: m.entryId, text: m.text })),
3084
3168
  async entryId => {
3085
- const result = await this.ctx.session.branch(entryId);
3086
- if (result.cancelled) {
3087
- // Hook cancelled the branch
3088
- done();
3089
- this.ctx.ui.requestRender();
3169
+ if (selectionInFlight) return;
3170
+ selectionInFlight = true;
3171
+
3172
+ if (
3173
+ this.ctx.sessionManager.getSessionId() !== openedSessionId ||
3174
+ this.ctx.sessionManager.getSessionFile() !== openedSessionFile
3175
+ ) {
3176
+ closeWithoutBranch("Fork cancelled because the active session changed", true);
3177
+ return;
3178
+ }
3179
+ const busyReason = this.#forkBusyReason();
3180
+ if (busyReason) {
3181
+ closeWithoutBranch(busyReason);
3090
3182
  return;
3091
3183
  }
3092
- this.ctx.resetIrcSidebarSession();
3093
3184
 
3094
- this.ctx.rebuildInitialMessages("replace-identity");
3095
- this.ctx.editor.setText(result.selectedText);
3185
+ let selectedText = userMessages.find(message => message.entryId === entryId)!.text;
3186
+ let branchFailure: unknown;
3187
+ try {
3188
+ const result = await this.ctx.session.branch(entryId);
3189
+ if (this.ctx.isStopped?.()) return;
3190
+ if (result.cancelled) {
3191
+ closeWithoutBranch("Fork cancelled");
3192
+ return;
3193
+ }
3194
+ selectedText = result.selectedText;
3195
+ } catch (error) {
3196
+ if (this.ctx.isStopped?.()) return;
3197
+ const committed =
3198
+ this.ctx.sessionManager.getSessionId() !== openedSessionId ||
3199
+ this.ctx.sessionManager.getSessionFile() !== openedSessionFile;
3200
+ if (!committed) {
3201
+ closeWithoutBranch(`Fork failed: ${error instanceof Error ? error.message : String(error)}`, true);
3202
+ return;
3203
+ }
3204
+ // The successor is already durable. Never describe this as a
3205
+ // cancelled transition or roll it back to the parent's UI.
3206
+ branchFailure = error;
3207
+ }
3208
+
3209
+ try {
3210
+ onCommitted?.();
3211
+ } catch (error) {
3212
+ branchFailure ??= error;
3213
+ }
3214
+ try {
3215
+ this.ctx.resetIrcSidebarSession();
3216
+ this.ctx.resetObserverRegistry();
3217
+ this.#refreshSessionTerminalTitle();
3218
+ this.ctx.statusLine.invalidate();
3219
+ this.ctx.statusLine.setSessionStartTime(Date.now());
3220
+ this.ctx.updateEditorTopBorder();
3221
+ this.ctx.updateEditorBorderColor();
3222
+ this.ctx.rebuildInitialMessages("replace-identity");
3223
+ await this.ctx.reloadTodos();
3224
+ } catch (error) {
3225
+ branchFailure ??= error;
3226
+ }
3227
+ if (this.ctx.isStopped?.()) return;
3228
+ this.ctx.editor.setText(selectedText);
3096
3229
  done();
3097
- this.ctx.showStatus("Branched to new session");
3230
+ if (branchFailure !== undefined) {
3231
+ this.ctx.showError(
3232
+ `Fork created, but session setup failed: ${branchFailure instanceof Error ? branchFailure.message : String(branchFailure)}`,
3233
+ );
3234
+ } else {
3235
+ this.ctx.showStatus("Forked to a new session; edit the selected prompt to continue");
3236
+ }
3237
+ // Do not schedule goal continuation over the selected prompt draft.
3238
+ this.ctx.ui.requestRender();
3098
3239
  },
3099
3240
  () => {
3100
3241
  done();
3242
+ this.ctx.goalModeController.scheduleContinuation();
3101
3243
  this.ctx.ui.requestRender();
3102
3244
  },
3245
+ () => this.ctx.ui.terminal.rows,
3103
3246
  );
3104
3247
  return { component: selector, focus: selector.getMessageList() };
3105
3248
  });
@@ -3274,6 +3417,9 @@ export class SelectorController {
3274
3417
  });
3275
3418
  }
3276
3419
  },
3420
+ undefined,
3421
+ undefined,
3422
+ (session, starred) => this.ctx.sessionManager.setSessionStarredForPicker(session, starred),
3277
3423
  );
3278
3424
  selector.setOnRequestRender(() => this.ctx.ui.requestRender());
3279
3425
  return { component: selector, focus: selector };
@@ -2265,10 +2265,24 @@ export class InteractiveMode implements InteractiveModeContext {
2265
2265
  }
2266
2266
 
2267
2267
  #prepareSessionSwitch(cleanupPreviousSessionUi?: () => void): void {
2268
- this.#btwController.dispose();
2269
- if (cleanupPreviousSessionUi) cleanupPreviousSessionUi();
2270
- else this.#extensionUiController.clearExtensionTerminalInputListeners();
2271
- this.#planModeController.clearReview();
2268
+ const errors: unknown[] = [];
2269
+ try {
2270
+ this.#btwController.dispose();
2271
+ } catch (error) {
2272
+ errors.push(error);
2273
+ }
2274
+ try {
2275
+ if (cleanupPreviousSessionUi) cleanupPreviousSessionUi();
2276
+ else this.#extensionUiController.clearExtensionTerminalInputListeners();
2277
+ } catch (error) {
2278
+ errors.push(error);
2279
+ }
2280
+ try {
2281
+ this.#planModeController.clearReview();
2282
+ } catch (error) {
2283
+ errors.push(error);
2284
+ }
2285
+ if (errors.length > 0) throw new AggregateError(errors, "Previous session UI cleanup failed");
2272
2286
  }
2273
2287
 
2274
2288
  async handleClearCommand(): Promise<boolean> {
@@ -2291,7 +2305,6 @@ export class InteractiveMode implements InteractiveModeContext {
2291
2305
  }
2292
2306
 
2293
2307
  handleForkCommand(): Promise<void> {
2294
- this.#btwController.dispose();
2295
2308
  return this.#commandController.handleForkCommand();
2296
2309
  }
2297
2310
 
@@ -2299,7 +2312,7 @@ export class InteractiveMode implements InteractiveModeContext {
2299
2312
  return this.#commandController.handleMoveCommand(targetPath);
2300
2313
  }
2301
2314
 
2302
- handleRenameCommand(title: string): Promise<void> {
2315
+ handleRenameCommand(title?: string): Promise<void> {
2303
2316
  return this.#commandController.handleRenameCommand(title);
2304
2317
  }
2305
2318
 
@@ -2564,7 +2577,8 @@ export class InteractiveMode implements InteractiveModeContext {
2564
2577
  }
2565
2578
 
2566
2579
  showUserMessageSelector(): void {
2567
- this.#selectorController.showUserMessageSelector();
2580
+ const cleanupPreviousSessionUi = this.#extensionUiController.captureSessionUiCleanup();
2581
+ this.#selectorController.showUserMessageSelector(() => this.#prepareSessionSwitch(cleanupPreviousSessionUi));
2568
2582
  }
2569
2583
 
2570
2584
  showTreeSelector(): void {
@@ -123,6 +123,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
123
123
  getCredentialSessionId: () => session.credentialSessionId,
124
124
  isIdle: () => !session.isStreaming,
125
125
  getActivePromptHandle: () => session.activePromptHandle,
126
+ getSessionWorkLease: () => session.sessionWorkLease,
126
127
  abort: () => session.abort(),
127
128
  abortPromptAndWait: (handle, abortOptions) => session.abortPromptAndWait(handle, abortOptions),
128
129
  hasPendingMessages: () => session.queuedMessageCount > 0,