@gajae-code/coding-agent 0.17.2 → 0.17.5

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 (390) hide show
  1. package/CHANGELOG.md +267 -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 +1049 -3
  18. package/dist/types/config/file-lock.d.ts +13 -3
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +14 -3
  21. package/dist/types/config/model-resolver.d.ts +1 -1
  22. package/dist/types/config/settings-schema.d.ts +11 -0
  23. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  24. package/dist/types/daemon/operator-contract.d.ts +5 -1
  25. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  26. package/dist/types/exec/bash-executor.d.ts +2 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  28. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  29. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -1
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  33. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  34. package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
  35. package/dist/types/harness-control-plane/finalize.d.ts +14 -3
  36. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  37. package/dist/types/internal-urls/index.d.ts +1 -0
  38. package/dist/types/internal-urls/parse.d.ts +9 -1
  39. package/dist/types/main.d.ts +23 -1
  40. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  41. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  42. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  43. package/dist/types/modes/components/session-selector.d.ts +5 -2
  44. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  45. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  46. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  47. package/dist/types/modes/interactive-mode.d.ts +1 -1
  48. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  49. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  50. package/dist/types/modes/types.d.ts +2 -2
  51. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  52. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  53. package/dist/types/runtime-mcp/manager.d.ts +69 -6
  54. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  55. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  56. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  57. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  58. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  59. package/dist/types/sdk/broker/broker.d.ts +17 -4
  60. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  61. package/dist/types/sdk/broker/identity.d.ts +5 -1
  62. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  63. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  64. package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
  65. package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
  66. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  67. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  68. package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
  69. package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
  70. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  71. package/dist/types/sdk/bus/index.d.ts +19 -0
  72. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  73. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  74. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  75. package/dist/types/sdk/cli/rows.d.ts +19 -1
  76. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  77. package/dist/types/sdk/client/client.d.ts +2 -0
  78. package/dist/types/sdk/guides/cli.d.ts +3 -4
  79. package/dist/types/sdk/guides/verify.d.ts +2 -0
  80. package/dist/types/sdk/host/host.d.ts +9 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  82. package/dist/types/sdk/host/types.d.ts +10 -0
  83. package/dist/types/sdk/index.d.ts +1 -0
  84. package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  86. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  87. package/dist/types/sdk/router/session-router.d.ts +2 -0
  88. package/dist/types/sdk/session.d.ts +3 -3
  89. package/dist/types/sdk/startup-capability.d.ts +2 -0
  90. package/dist/types/sdk/transport/relay.d.ts +1 -0
  91. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  92. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  93. package/dist/types/session/agent-session.d.ts +175 -23
  94. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  95. package/dist/types/session/fold-coordinator.d.ts +17 -3
  96. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  97. package/dist/types/session/session-manager.d.ts +64 -1
  98. package/dist/types/session/session-work-lease.d.ts +14 -0
  99. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  100. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  101. package/dist/types/task/types.d.ts +2 -0
  102. package/dist/types/tools/bash-interactive.d.ts +1 -0
  103. package/dist/types/tools/bash.d.ts +37 -11
  104. package/dist/types/tools/fetch.d.ts +10 -1
  105. package/dist/types/tools/job.d.ts +1 -1
  106. package/dist/types/tools/steer-fold.d.ts +31 -0
  107. package/dist/types/tools/subagent.d.ts +8 -1
  108. package/dist/types/utils/git.d.ts +28 -2
  109. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  110. package/dist/types/utils/mupdf.d.ts +4 -0
  111. package/dist/types/utils/session-title-generation.d.ts +6 -0
  112. package/dist/types/utils/title-generator.d.ts +11 -2
  113. package/dist/types/web/scrapers/types.d.ts +3 -0
  114. package/package.json +18 -10
  115. package/scripts/build-binary.ts +9 -3
  116. package/scripts/compile-args.ts +0 -9
  117. package/scripts/embed-mupdf.ts +52 -0
  118. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  119. package/scripts/vendor-markit.ts +196 -0
  120. package/src/async/job-manager.ts +22 -1
  121. package/src/cli/daemon-cli.ts +53 -18
  122. package/src/cli/github-release.ts +87 -5
  123. package/src/cli/mcp-cli.ts +38 -4
  124. package/src/cli/public-command-entry.ts +450 -0
  125. package/src/cli/public-command-errors.ts +581 -0
  126. package/src/cli/public-command-evidence.ts +717 -0
  127. package/src/cli/public-command-help.ts +360 -0
  128. package/src/cli/public-command-registry.ts +541 -0
  129. package/src/cli/session-picker.ts +2 -1
  130. package/src/cli/setup-cli.ts +10 -1
  131. package/src/cli/skills-cli.ts +46 -2
  132. package/src/cli/stats-cli.ts +11 -1
  133. package/src/cli.ts +61 -1
  134. package/src/commands/daemon.ts +65 -69
  135. package/src/commands/sdk.ts +175 -117
  136. package/src/commands/setup.ts +2 -0
  137. package/src/commands/skills.ts +11 -0
  138. package/src/config/autorouting-tier-map.ts +543 -3
  139. package/src/config/file-lock.ts +206 -65
  140. package/src/config/model-discovery-manager.ts +4 -4
  141. package/src/config/model-profile-activation.ts +417 -66
  142. package/src/config/model-registry.ts +254 -36
  143. package/src/config/model-resolver.ts +6 -1
  144. package/src/config/settings-schema.ts +26 -2
  145. package/src/coordinator-mcp/policy.ts +10 -5
  146. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  147. package/src/coordinator-mcp/question-state.ts +179 -9
  148. package/src/coordinator-mcp/server.ts +521 -87
  149. package/src/coordinator-mcp/session-reaper.ts +6 -4
  150. package/src/crash/record-loader.ts +6 -2
  151. package/src/daemon/operator-contract.ts +26 -12
  152. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  153. package/src/eval/js/context-manager.ts +8 -8
  154. package/src/eval/js/worker-core.ts +9 -4
  155. package/src/eval/js/worker-protocol.ts +2 -0
  156. package/src/exec/bash-executor.ts +3 -0
  157. package/src/extensibility/extensions/runner.ts +52 -4
  158. package/src/extensibility/extensions/types.ts +10 -0
  159. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
  161. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  162. package/src/extensibility/shared-events.ts +3 -1
  163. package/src/gjc-runtime/session-state-lock.ts +165 -24
  164. package/src/gjc-runtime/state-writer.ts +317 -6
  165. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  166. package/src/harness-control-plane/control-endpoint.ts +24 -6
  167. package/src/harness-control-plane/finalize.ts +312 -25
  168. package/src/harness-control-plane/owner.ts +323 -89
  169. package/src/internal-urls/docs-index.generated.ts +23 -20
  170. package/src/internal-urls/embedded-protocol.ts +83 -0
  171. package/src/internal-urls/index.ts +1 -0
  172. package/src/internal-urls/parse.ts +21 -1
  173. package/src/internal-urls/router.ts +4 -2
  174. package/src/main.ts +192 -74
  175. package/src/modes/acp/acp-agent.ts +973 -56
  176. package/src/modes/acp/startup-provenance.ts +59 -0
  177. package/src/modes/components/custom-provider-wizard.ts +308 -7
  178. package/src/modes/components/gjc-bundle-settings.ts +21 -1
  179. package/src/modes/components/oauth-selector.ts +9 -0
  180. package/src/modes/components/session-selector.ts +47 -4
  181. package/src/modes/components/tool-status-header.ts +72 -6
  182. package/src/modes/components/user-message-selector.ts +57 -15
  183. package/src/modes/controllers/command-controller.ts +58 -32
  184. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  185. package/src/modes/controllers/input-controller.ts +4 -0
  186. package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
  187. package/src/modes/controllers/selector-controller.ts +161 -15
  188. package/src/modes/interactive-mode.ts +21 -7
  189. package/src/modes/runtime-init.ts +1 -0
  190. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  191. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  192. package/src/modes/types.ts +2 -2
  193. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  194. package/src/prompts/system/title-system.md +1 -1
  195. package/src/prompts/tools/bash.md +1 -1
  196. package/src/prompts/tools/job.md +1 -1
  197. package/src/prompts/tools/read.md +2 -0
  198. package/src/runtime/network-prewarm-service.test.ts +36 -1
  199. package/src/runtime/network-prewarm-service.ts +28 -3
  200. package/src/runtime/process-lifecycle.ts +22 -11
  201. package/src/runtime-mcp/config.ts +16 -1
  202. package/src/runtime-mcp/loader.ts +6 -3
  203. package/src/runtime-mcp/manager.ts +845 -109
  204. package/src/runtime-mcp/pool.test.ts +31 -0
  205. package/src/runtime-mcp/pool.ts +304 -28
  206. package/src/runtime-mcp/redaction.ts +42 -0
  207. package/src/runtime-mcp/startup-policy.ts +7 -0
  208. package/src/runtime-mcp/tool-bridge.ts +71 -0
  209. package/src/runtime-mcp/transports/http.ts +108 -25
  210. package/src/sdk/acp/adapter.ts +27 -8
  211. package/src/sdk/broker/broker.ts +1431 -198
  212. package/src/sdk/broker/ensure.ts +21 -3
  213. package/src/sdk/broker/identity.ts +22 -1
  214. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  215. package/src/sdk/broker/lifecycle.ts +690 -141
  216. package/src/sdk/broker/managed-task-dag.ts +1927 -0
  217. package/src/sdk/broker/managed-task-verification.ts +642 -0
  218. package/src/sdk/broker/runtime.ts +1 -1
  219. package/src/sdk/broker/session-index.ts +97 -4
  220. package/src/sdk/broker/spawn-authority.ts +175 -14
  221. package/src/sdk/broker/startup-failure.ts +80 -1
  222. package/src/sdk/broker/stderr-drainer.ts +59 -0
  223. package/src/sdk/broker/transport.ts +1 -0
  224. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  225. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  226. package/src/sdk/bus/index.ts +707 -81
  227. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  228. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  229. package/src/sdk/bus/telegram-daemon.ts +10 -3
  230. package/src/sdk/cli/master-cli.ts +47 -36
  231. package/src/sdk/cli/rows.ts +30 -7
  232. package/src/sdk/cli/session-cli.ts +705 -184
  233. package/src/sdk/client/client.ts +18 -0
  234. package/src/sdk/guides/catalog.ts +2 -2
  235. package/src/sdk/guides/cli.ts +20 -62
  236. package/src/sdk/guides/verify.ts +14 -0
  237. package/src/sdk/host/host.ts +49 -5
  238. package/src/sdk/host/session-runtime.test.ts +184 -2
  239. package/src/sdk/host/session-runtime.ts +322 -101
  240. package/src/sdk/host/types.ts +12 -1
  241. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  242. package/src/sdk/host/websocket-transport.ts +40 -6
  243. package/src/sdk/index.ts +11 -0
  244. package/src/sdk/lifecycle/service.ts +2 -0
  245. package/src/sdk/prompt-deadline-flush.ts +356 -0
  246. package/src/sdk/prompt-deadline-manager.ts +133 -1
  247. package/src/sdk/prompt-tool-boundary.ts +121 -0
  248. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  249. package/src/sdk/router/session-router.ts +9 -1
  250. package/src/sdk/session.ts +763 -116
  251. package/src/sdk/startup-capability.ts +1 -1
  252. package/src/sdk/transport/relay.ts +71 -6
  253. package/src/sdk/transport/serve-cli.ts +397 -42
  254. package/src/sdk/transport/socket.ts +12 -11
  255. package/src/sdk/transport/stdio.ts +7 -3
  256. package/src/session/agent-session.ts +2472 -407
  257. package/src/session/fallback-chain-controller.ts +6 -7
  258. package/src/session/fold-coordinator.ts +24 -5
  259. package/src/session/internal/managed-session-scope.ts +68 -18
  260. package/src/session/internal/managed-session-storage.ts +334 -12
  261. package/src/session/session-manager.ts +382 -44
  262. package/src/session/session-work-lease.ts +34 -0
  263. package/src/setup/model-onboarding-guidance.ts +1 -1
  264. package/src/setup/provider-onboarding.ts +397 -24
  265. package/src/setup/provider-presets.json +2 -1
  266. package/src/slash-commands/builtin-registry.ts +259 -15
  267. package/src/task/executor.ts +46 -17
  268. package/src/task/index.ts +14 -2
  269. package/src/task/types.ts +2 -0
  270. package/src/tools/bash-interactive.ts +2 -0
  271. package/src/tools/bash.ts +118 -78
  272. package/src/tools/fetch.ts +140 -25
  273. package/src/tools/job.ts +151 -25
  274. package/src/tools/output-meta.ts +15 -2
  275. package/src/tools/path-utils.ts +27 -3
  276. package/src/tools/read.ts +3 -3
  277. package/src/tools/steer-fold.ts +107 -0
  278. package/src/tools/subagent-render.ts +24 -1
  279. package/src/tools/subagent.ts +27 -1
  280. package/src/tools/tool-catalog.generated.ts +29 -2
  281. package/src/tools/tool-errors.ts +4 -0
  282. package/src/utils/git.ts +53 -3
  283. package/src/utils/markit.ts +55 -41
  284. package/src/utils/mupdf-assets.d.ts +4 -0
  285. package/src/utils/mupdf-embedded.ts +4 -0
  286. package/src/utils/mupdf.ts +136 -0
  287. package/src/utils/session-title-generation.ts +29 -0
  288. package/src/utils/title-generator.ts +46 -2
  289. package/src/web/scrapers/types.ts +24 -3
  290. package/vendor/markit-ai/LICENSE +21 -0
  291. package/vendor/markit-ai/MANIFEST.json +412 -0
  292. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  293. package/vendor/markit-ai/dist/commands/config.js +139 -0
  294. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  295. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  296. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  297. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  298. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  299. package/vendor/markit-ai/dist/commands/init.js +29 -0
  300. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  301. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  302. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  303. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  304. package/vendor/markit-ai/dist/config.d.ts +26 -0
  305. package/vendor/markit-ai/dist/config.js +42 -0
  306. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  307. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  308. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  309. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  310. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  312. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  313. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  314. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  315. package/vendor/markit-ai/dist/converters/github.js +148 -0
  316. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  317. package/vendor/markit-ai/dist/converters/html.js +30 -0
  318. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  319. package/vendor/markit-ai/dist/converters/image.js +124 -0
  320. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  321. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  322. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  323. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  324. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  325. package/vendor/markit-ai/dist/converters/json.js +21 -0
  326. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  327. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  328. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  329. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  330. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  331. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  332. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  333. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  334. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  335. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  336. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  337. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  338. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  339. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  340. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  341. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  342. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  343. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  344. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  345. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  346. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  347. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  348. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  349. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  350. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  351. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  352. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  353. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  354. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  355. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  356. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  357. package/vendor/markit-ai/dist/index.d.ts +30 -0
  358. package/vendor/markit-ai/dist/index.js +26 -0
  359. package/vendor/markit-ai/dist/main.d.ts +2 -0
  360. package/vendor/markit-ai/dist/main.js +191 -0
  361. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  362. package/vendor/markit-ai/dist/markit.js +267 -0
  363. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  364. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  365. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  366. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  367. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  368. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  369. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  370. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  371. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  372. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  373. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  374. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  375. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  376. package/vendor/markit-ai/dist/providers/index.js +56 -0
  377. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  378. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  379. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  380. package/vendor/markit-ai/dist/providers/types.js +1 -0
  381. package/vendor/markit-ai/dist/types.d.ts +36 -0
  382. package/vendor/markit-ai/dist/types.js +1 -0
  383. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  384. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  385. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  386. package/vendor/markit-ai/dist/utils/output.js +31 -0
  387. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  388. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  389. package/vendor/markit-ai/package.json +74 -0
  390. package/vendor/markit-ai.patch +49 -0
@@ -39,6 +39,7 @@ export type SessionInspector = (sessionPath: string) => Promise<ResumeTailInspec
39
39
 
40
40
  type SelectorState =
41
41
  | { kind: "browsing" }
42
+ | { kind: "starring"; token: number }
42
43
  | { kind: "checking"; session: SessionInfo; token: number }
43
44
  | { kind: "confirming"; session: SessionInfo; token: number; identity: ResumeSessionIdentity }
44
45
  | { kind: "settled" };
@@ -52,12 +53,13 @@ class SessionList implements Component {
52
53
  onCancel?: () => void;
53
54
  onExit: () => void = () => {};
54
55
  onDeleteRequest?: (session: SessionInfo) => void;
56
+ onToggleStarRequest?: (session: SessionInfo) => void;
55
57
 
56
58
  constructor(
57
59
  private readonly allSessions: SessionInfo[],
58
60
  private readonly showCwd = false,
59
61
  ) {
60
- this.#filteredSessions = allSessions;
62
+ this.#filteredSessions = prioritizeStarredSessions(allSessions);
61
63
  this.#searchInput.onSubmit = () => {
62
64
  const selected = this.#filteredSessions[this.#selectedIndex];
63
65
  if (selected) this.onSelect?.(selected);
@@ -100,6 +102,17 @@ class SessionList implements Component {
100
102
  this.#filterSessions(this.#searchInput.getValue());
101
103
  }
102
104
 
105
+ setSessionStarred(sessionPath: string, starred: boolean): void {
106
+ const session = this.allSessions.find(session => session.path === sessionPath);
107
+ if (!session) return;
108
+ session.starred = starred;
109
+ this.#filterSessions(this.#searchInput.getValue());
110
+ this.#selectedIndex = Math.max(
111
+ 0,
112
+ this.#filteredSessions.findIndex(session => session.path === sessionPath),
113
+ );
114
+ }
115
+
103
116
  invalidate(): void {}
104
117
 
105
118
  render(width: number): string[] {
@@ -155,6 +168,8 @@ class SessionList implements Component {
155
168
  }
156
169
  if (start > 0 || end < this.#filteredSessions.length)
157
170
  lines.push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#filteredSessions.length})`));
171
+ if (this.onToggleStarRequest)
172
+ lines.push(theme.fg("muted", truncateToWidth(" [Ctrl+S to star/unstar selected session]", width)));
158
173
  lines.push(
159
174
  "",
160
175
  theme.fg("muted", " [Del to delete selected transcript/artifacts, Enter to select, Esc to cancel]"),
@@ -177,7 +192,10 @@ class SessionList implements Component {
177
192
  this.#selectedIndex += delta;
178
193
  this.#clampSelectedIndex();
179
194
  };
180
- if (matchesKey(keyData, "delete")) {
195
+ if (matchesKey(keyData, "ctrl+s")) {
196
+ const selected = this.#filteredSessions[this.#selectedIndex];
197
+ if (selected) this.onToggleStarRequest?.(selected);
198
+ } else if (matchesKey(keyData, "delete")) {
181
199
  const selected = this.#filteredSessions[this.#selectedIndex];
182
200
  if (selected) this.onDeleteRequest?.(selected);
183
201
  } else if (matchesKey(keyData, "up")) move(-1);
@@ -194,7 +212,7 @@ class SessionList implements Component {
194
212
  }
195
213
  }
196
214
 
197
- /** A one-shot resume consent selector. It never opens or mutates sessions. */
215
+ /** A one-shot resume consent selector with host-owned deletion and star persistence. */
198
216
  export class SessionSelectorComponent extends Container {
199
217
  #sessionList: SessionList;
200
218
  #confirmationDialog: HookSelectorComponent | null = null;
@@ -211,6 +229,7 @@ export class SessionSelectorComponent extends Container {
211
229
  private readonly onDelete?: (session: SessionInfo) => Promise<boolean>,
212
230
  private readonly inspector?: SessionInspector,
213
231
  private readonly onSelection?: (selection: SessionSelectionResult) => void,
232
+ private readonly onSetStarred?: (session: SessionInfo, starred: boolean) => Promise<void>,
214
233
  ) {
215
234
  super();
216
235
  this.addChild(new Spacer(1));
@@ -219,7 +238,7 @@ export class SessionSelectorComponent extends Container {
219
238
  this.addChild(new DynamicBorder());
220
239
  this.addChild(new Spacer(1));
221
240
  this.addChild(this.#messageContainer);
222
- this.#sessionList = new SessionList(prioritizeStarredSessions(sessions));
241
+ this.#sessionList = new SessionList([...sessions]);
223
242
  this.#sessionList.onSelect = session => {
224
243
  if (this.inspector) void this.#inspect(session);
225
244
  else this.#dispatchSelect(session.path);
@@ -227,6 +246,7 @@ export class SessionSelectorComponent extends Container {
227
246
  this.#sessionList.onCancel = () => this.#cancel();
228
247
  this.#sessionList.onExit = () => this.#exit();
229
248
  this.#sessionList.onDeleteRequest = session => this.#showDeleteConfirmation(session);
249
+ if (this.onSetStarred) this.#sessionList.onToggleStarRequest = session => void this.#toggleStar(session);
230
250
  this.addChild(this.#sessionList);
231
251
  this.addChild(new Spacer(1));
232
252
  this.addChild(new DynamicBorder());
@@ -294,6 +314,29 @@ export class SessionSelectorComponent extends Container {
294
314
  this.#sessionList.setInputFrozen(true);
295
315
  this.onExit();
296
316
  }
317
+ async #toggleStar(session: SessionInfo): Promise<void> {
318
+ if (!this.onSetStarred || this.#state.kind !== "browsing" || this.#confirmationDialog) return;
319
+ const token = ++this.#nextToken;
320
+ const starred = !session.starred;
321
+ this.#state = { kind: "starring", token };
322
+ this.#sessionList.setInputFrozen(true);
323
+ this.#clearError();
324
+ this.#requestRender();
325
+ try {
326
+ await this.onSetStarred(session, starred);
327
+ if (this.#state.kind !== "starring" || this.#state.token !== token) return;
328
+ this.#sessionList.setSessionStarred(session.path, starred);
329
+ } catch (error) {
330
+ if (this.#state.kind !== "starring" || this.#state.token !== token) return;
331
+ this.#showError(error instanceof Error ? error.message : String(error));
332
+ } finally {
333
+ if (this.#state.kind === "starring" && this.#state.token === token) {
334
+ this.#state = { kind: "browsing" };
335
+ this.#sessionList.setInputFrozen(false);
336
+ this.#requestRender();
337
+ }
338
+ }
339
+ }
297
340
  async #inspect(session: SessionInfo): Promise<void> {
298
341
  const inspector = this.inspector;
299
342
  if (!inspector || this.#state.kind !== "browsing") return;
@@ -41,6 +41,33 @@ import { getSeparator } from "./status-line/separators";
41
41
  import { calculateTokensPerSecond } from "./status-line/token-rate";
42
42
  import type { SeparatorDef } from "./status-line/types";
43
43
 
44
+ function usageReportAccountId(report: unknown): string | undefined {
45
+ if (!report || typeof report !== "object") return undefined;
46
+ const record = report as {
47
+ metadata?: unknown;
48
+ limits?: unknown;
49
+ };
50
+ const metadata = record.metadata;
51
+ const rawMetadataAccountId =
52
+ metadata && typeof metadata === "object" ? (metadata as { accountId?: unknown }).accountId : undefined;
53
+ const metadataAccountId =
54
+ typeof rawMetadataAccountId === "string" && rawMetadataAccountId.trim() ? rawMetadataAccountId.trim() : undefined;
55
+ if (!Array.isArray(record.limits)) return metadataAccountId;
56
+
57
+ const scopeAccountIds = new Set<string>();
58
+ for (const limit of record.limits) {
59
+ if (!limit || typeof limit !== "object") continue;
60
+ const scope = (limit as { scope?: unknown }).scope;
61
+ if (!scope || typeof scope !== "object") continue;
62
+ const accountId = (scope as { accountId?: unknown }).accountId;
63
+ if (typeof accountId === "string" && accountId.trim()) scopeAccountIds.add(accountId.trim());
64
+ }
65
+ if (scopeAccountIds.size > 1) return undefined;
66
+ const [scopeAccountId] = scopeAccountIds;
67
+ if (metadataAccountId && scopeAccountId && metadataAccountId !== scopeAccountId) return undefined;
68
+ return metadataAccountId ?? scopeAccountId;
69
+ }
70
+
44
71
  export interface StatusLineSegmentOptions {
45
72
  model?: { showThinkingLevel?: boolean; showContextPercent?: boolean };
46
73
  path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
@@ -218,6 +245,7 @@ export class StatusLineComponent implements Component {
218
245
  #cachedUsage: SegmentContext["usage"] = null;
219
246
  #cachedUsageReports: unknown = null;
220
247
  #cachedUsageProvider: string | undefined;
248
+ #cachedUsageAccountId: string | undefined;
221
249
  #usageFetchedAt = 0;
222
250
  #usageInFlight = false;
223
251
 
@@ -702,7 +730,12 @@ export class StatusLineComponent implements Component {
702
730
  .then(reports => {
703
731
  this.#cachedUsageReports = reports;
704
732
  this.#cachedUsageProvider = this.#activeUsageProvider();
705
- this.#cachedUsage = this.#normalizeUsageReports(reports);
733
+ this.#cachedUsageAccountId = this.#activeCodexAccountId(this.#cachedUsageProvider);
734
+ this.#cachedUsage = this.#normalizeUsageReports(
735
+ reports,
736
+ this.#cachedUsageProvider,
737
+ this.#cachedUsageAccountId,
738
+ );
706
739
  this.#usageFetchedAt = Date.now();
707
740
  if (this.#onBranchChange) {
708
741
  this.#onBranchChange();
@@ -720,10 +753,25 @@ export class StatusLineComponent implements Component {
720
753
  });
721
754
  }
722
755
 
723
- #normalizeUsageReports(reports: unknown): SegmentContext["usage"] {
724
- const activeProvider = this.#activeUsageProvider();
725
-
756
+ #normalizeUsageReports(
757
+ reports: unknown,
758
+ activeProvider = this.#activeUsageProvider(),
759
+ activeCodexAccountId = this.#activeCodexAccountId(activeProvider),
760
+ ): SegmentContext["usage"] {
726
761
  if (!activeProvider || !Array.isArray(reports)) return null;
762
+ const activeCodexReportCount =
763
+ activeProvider === "openai-codex" && activeCodexAccountId
764
+ ? reports.filter(report => {
765
+ if (!report || typeof report !== "object") return false;
766
+ const provider = (report as { provider?: unknown }).provider;
767
+ return (
768
+ typeof provider === "string" &&
769
+ resolveOAuthStorageProvider(provider) === activeProvider &&
770
+ usageReportAccountId(report) === activeCodexAccountId
771
+ );
772
+ }).length
773
+ : 0;
774
+ const ambiguousActiveCodexReports = activeCodexReportCount > 1;
727
775
  const windows: NonNullable<SegmentContext["usage"]>["windows"] = [];
728
776
  const seen = new Set<string>();
729
777
  const now = Date.now();
@@ -759,6 +807,14 @@ export class StatusLineComponent implements Component {
759
807
  const provider = (report as { provider?: unknown }).provider;
760
808
  const providerId = typeof provider === "string" ? resolveOAuthStorageProvider(provider) : undefined;
761
809
  if (providerId !== activeProvider) continue;
810
+ // Codex usage fetches cover every OAuth account; fail closed unless this session's account matches uniquely.
811
+ if (
812
+ providerId === "openai-codex" &&
813
+ (!activeCodexAccountId ||
814
+ ambiguousActiveCodexReports ||
815
+ usageReportAccountId(report) !== activeCodexAccountId)
816
+ )
817
+ continue;
762
818
  const limits = (report as { limits?: unknown }).limits;
763
819
  if (!Array.isArray(limits)) continue;
764
820
  for (const limit of limits) {
@@ -808,11 +864,21 @@ export class StatusLineComponent implements Component {
808
864
  return typeof provider === "string" && provider.length > 0 ? resolveOAuthStorageProvider(provider) : undefined;
809
865
  }
810
866
 
867
+ #activeCodexAccountId(activeProvider: string | undefined): string | undefined {
868
+ if (activeProvider !== "openai-codex") return undefined;
869
+ const modelRegistry = this.session.modelRegistry;
870
+ return modelRegistry.authStorage.getOAuthAccountId(activeProvider, this.session.credentialSessionId, {
871
+ owner: modelRegistry.getAuthStorageOwner(),
872
+ });
873
+ }
874
+
811
875
  #syncCachedUsageForActiveProvider(): void {
812
876
  const activeProvider = this.#activeUsageProvider();
813
- if (this.#cachedUsageProvider === activeProvider) return;
877
+ const activeCodexAccountId = this.#activeCodexAccountId(activeProvider);
878
+ if (this.#cachedUsageProvider === activeProvider && this.#cachedUsageAccountId === activeCodexAccountId) return;
814
879
  this.#cachedUsageProvider = activeProvider;
815
- this.#cachedUsage = this.#normalizeUsageReports(this.#cachedUsageReports);
880
+ this.#cachedUsageAccountId = activeCodexAccountId;
881
+ this.#cachedUsage = this.#normalizeUsageReports(this.#cachedUsageReports, activeProvider, activeCodexAccountId);
816
882
  }
817
883
 
818
884
  #buildSegmentContext(
@@ -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
 
@@ -1606,7 +1606,7 @@ export class MCPCommandController {
1606
1606
  if (!this.ctx.mcpManager) {
1607
1607
  return;
1608
1608
  }
1609
- if (this.ctx.mcpManager.isConnectionSetSealed()) {
1609
+ if (this.ctx.mcpManager.isConnectionSetMutationBlocked()) {
1610
1610
  throw new Error("This session's plugin-bundle MCP connections are fixed for its lifetime.");
1611
1611
  }
1612
1612