@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
@@ -67,6 +67,7 @@ import { assertSafePathComponent } from "../gjc-runtime/session-layout";
67
67
  import { writeTextAtomic } from "../gjc-runtime/state-writer";
68
68
  import type { ManagedLegacyLocalMigrationSource } from "../internal-urls/local-protocol";
69
69
  import * as git from "../utils/git";
70
+ import { invalidateSessionTitleGeneration } from "../utils/session-title-generation";
70
71
  import { ArtifactManager } from "./artifacts";
71
72
  import {
72
73
  type BlobPutResult,
@@ -2162,8 +2163,47 @@ export const RESUME_TRANSCRIPT_MAX_BYTES = MANAGED_ARTIFACT_MAX_FILE_BYTES;
2162
2163
  export const BOUNDED_RESUME_TRANSCRIPT_MAX_BYTES = 2 * 1024 * 1024 * 1024 + 1024 * 1024;
2163
2164
  const EAGER_RESUME_TRANSCRIPT_MAX_BYTES = MANAGED_ARTIFACT_MAX_FILE_BYTES;
2164
2165
 
2166
+ /**
2167
+ * Recovery actions offered when a live transcript is at or past the managed
2168
+ * per-file cap.
2169
+ *
2170
+ * Constrained three ways, and every command named here satisfies all three:
2171
+ *
2172
+ * 1. **It must exist.** `gjc export <session-file>` was advised for both limits
2173
+ * and is not a subcommand, so following it literally started a fresh agent
2174
+ * that read "export" as a prompt while the session stayed unwritable. (The
2175
+ * root `--export` flag renders HTML and exits; it yields no resumable
2176
+ * session.)
2177
+ * 2. **It must be reachable on every surface that renders the message.** These
2178
+ * strings reach ACP/text consumers through `AgentSession`, and the ACP
2179
+ * registry is filtered to definitions carrying `handle`
2180
+ * (`slash-commands/acp-builtins.ts`), so a `handleTui`-only command such as
2181
+ * `/new` is a different dead end rather than a fix.
2182
+ * 3. **It must not drop the retained near-limit entries.** A failed append is
2183
+ * kept in memory with a full rewrite armed, and a rejected managed rewrite
2184
+ * likewise retains its resident entries; both messages promise the work
2185
+ * persists on the next successful write. `/compact` and `/clear` keep the
2186
+ * manager — and therefore that pending debt — alive; a session switch closes
2187
+ * the writer without paying it.
2188
+ *
2189
+ * Every near-limit error class MUST interpolate this constant rather than
2190
+ * restate the advice. `SessionNearLimitRewriteError` restated it and thereby
2191
+ * reintroduced `gjc export <session-file>` after #5621 removed it (#5691).
2192
+ *
2193
+ * `session-recovery-guidance.test.ts` pins all three constraints against the
2194
+ * CLI, builtin, and ACP registries, for every near-limit error class.
2195
+ */
2196
+ export const SESSION_LIMIT_RECOVERY_ACTIONS =
2197
+ "compact the session (`/compact`), or clear its context (`/clear`) if compaction cannot reclaim enough";
2198
+
2199
+ /**
2200
+ * Oversized-resume guidance. This surface is reached *before* a session is
2201
+ * opened, so it deliberately does not reuse {@link SESSION_LIMIT_RECOVERY_ACTIONS}:
2202
+ * an in-session command there would act on whichever session the user resumes
2203
+ * next, never on the transcript that was just rejected.
2204
+ */
2165
2205
  export const SESSION_OVERSIZED_RECOVERY_MESSAGE =
2166
- "The selected session transcript is too large to resume safely. Use `gjc export <session-file>` to export its content into a new session, or remove/archive it after confirming its content is no longer needed.";
2206
+ "The selected session transcript is too large to resume safely. Resume a different session, or remove/archive this transcript after confirming its content is no longer needed.";
2167
2207
 
2168
2208
  export class SessionAppendPersistenceError extends Error {
2169
2209
  readonly phase: SessionAppendPersistenceFailurePhase;
@@ -2210,8 +2250,8 @@ export class SessionNearLimitAppendError extends Error {
2210
2250
  [
2211
2251
  `near_limit_append: entry (${details.entryBytes} B) plus live transcript (${details.liveBytes} B) exceeds the managed per-file limit (${details.capBytes} B).`,
2212
2252
  details.entryRetained
2213
- ? "The appended entry is retained in memory; its effect (including any committed source edit) is recorded and will persist on the next successful write. Compact the session (`/compact`) or export to a fresh session (`gjc export <session-file>`) before continuing."
2214
- : "The appended entry was rolled back from memory; re-issue it after compacting the session (`/compact`) or exporting to a fresh session (`gjc export <session-file>`).",
2253
+ ? `The appended entry is retained in memory; its effect (including any committed source edit) is recorded and will persist on the next successful write. To continue, ${SESSION_LIMIT_RECOVERY_ACTIONS}.`
2254
+ : `The appended entry was rolled back from memory; ${SESSION_LIMIT_RECOVERY_ACTIONS}, then re-issue it.`,
2215
2255
  ].join(" "),
2216
2256
  );
2217
2257
  this.name = "SessionNearLimitAppendError";
@@ -2222,6 +2262,54 @@ export class SessionNearLimitAppendError extends Error {
2222
2262
  }
2223
2263
  }
2224
2264
 
2265
+ /**
2266
+ * Typed near-limit rewrite outcome (follow-up to #4566).
2267
+ *
2268
+ * A managed atomic rewrite (`#writeEntriesAtomicallySync`) whose serialized
2269
+ * transcript crosses the per-file cap cannot be published: the managed store
2270
+ * rejects the bytes with `content_too_large`. Unlike the append path there is
2271
+ * no appended entry here — the whole live transcript is the payload — so this
2272
+ * error carries the rejected rewrite size and the cap instead of append
2273
+ * fields, and every rewrite/flush/close caller can point at compaction or
2274
+ * export as the continuation path.
2275
+ */
2276
+ export class SessionNearLimitRewriteError extends Error {
2277
+ readonly code = "near_limit_rewrite" as const;
2278
+ /** Serialized transcript size (bytes) rejected by the managed store. */
2279
+ readonly transcriptBytes: number;
2280
+ /** Managed per-file cap in force when the rewrite was rejected. */
2281
+ readonly capBytes: number;
2282
+
2283
+ constructor(details: { transcriptBytes: number; capBytes: number }) {
2284
+ super(
2285
+ [
2286
+ `near_limit_rewrite: live transcript (${details.transcriptBytes} B) exceeds the managed per-file limit (${details.capBytes} B).`,
2287
+ // Must reuse SESSION_LIMIT_RECOVERY_ACTIONS rather than restate the advice:
2288
+ // this error class reintroduced `gjc export <session-file>` (#5691) after
2289
+ // #5621 removed it, because it hardcoded its own copy of the guidance.
2290
+ `The rewrite was rejected and the resident entries are retained in memory; the transcript persists again once you ${SESSION_LIMIT_RECOVERY_ACTIONS}.`,
2291
+ ].join(" "),
2292
+ );
2293
+ this.name = "SessionNearLimitRewriteError";
2294
+ this.transcriptBytes = details.transcriptBytes;
2295
+ this.capBytes = details.capBytes;
2296
+ }
2297
+ }
2298
+
2299
+ /**
2300
+ * Near-limit rejection from the managed transcript store.
2301
+ *
2302
+ * The store raises a bare `content_too_large` for any payload above the per-file
2303
+ * cap, and `#writeEntriesAtomicallySync` converts that same condition on the
2304
+ * rewrite lane into `SessionNearLimitRewriteError`. Both shapes mean the live
2305
+ * transcript crossed the cap and share one recovery path in `_persist`.
2306
+ */
2307
+ function isManagedNearLimitFailure(error: unknown): boolean {
2308
+ return (
2309
+ (error instanceof Error && error.message === "content_too_large") || error instanceof SessionNearLimitRewriteError
2310
+ );
2311
+ }
2312
+
2225
2313
  export class SessionManagedStorageError extends Error {
2226
2314
  readonly code = "managed_storage_unsupported";
2227
2315
 
@@ -2418,6 +2506,8 @@ export interface SessionInfo {
2418
2506
  firstMessage: string;
2419
2507
  allMessagesText: string;
2420
2508
  }
2509
+
2510
+ const pickerDiskIdentities = new WeakMap<SessionInfo, SessionStorageStat>();
2421
2511
  // =============================================================================
2422
2512
  // Strict ACP authorization inventory (fail-closed, never partial authority)
2423
2513
  // =============================================================================
@@ -3619,6 +3709,38 @@ function sameResumeStat(left: SessionStorageStat, right: SessionStorageStat): bo
3619
3709
  );
3620
3710
  }
3621
3711
 
3712
+ function pickerDiskIdentityMatchesStorageStat(expected: SessionStorageStat, observed: SessionStorageStat): boolean {
3713
+ return (
3714
+ expected.isFile &&
3715
+ observed.isFile &&
3716
+ expected.dev === observed.dev &&
3717
+ expected.ino === observed.ino &&
3718
+ expected.nlink === observed.nlink &&
3719
+ expected.size === observed.size &&
3720
+ expected.mtimeNs === observed.mtimeNs &&
3721
+ expected.ctimeNs === observed.ctimeNs
3722
+ );
3723
+ }
3724
+
3725
+ function pickerDiskIdentityMatchesManagedSnapshot(
3726
+ expected: SessionStorageStat,
3727
+ observed: Pick<SessionStorageStat, "dev" | "ino" | "nlink" | "size" | "mtimeNs" | "ctimeNs">,
3728
+ ): boolean {
3729
+ return (
3730
+ expected.isFile &&
3731
+ expected.dev === observed.dev &&
3732
+ expected.ino === observed.ino &&
3733
+ expected.nlink === observed.nlink &&
3734
+ expected.size === observed.size &&
3735
+ expected.mtimeNs === observed.mtimeNs &&
3736
+ expected.ctimeNs === observed.ctimeNs
3737
+ );
3738
+ }
3739
+
3740
+ function pickerDiskIdentityMatchesDescriptor(expected: SessionStorageStat, observed: DescriptorSnapshot): boolean {
3741
+ return pickerDiskIdentityMatchesManagedSnapshot(expected, observed);
3742
+ }
3743
+
3622
3744
  function resumeIdentityMatchesDescriptor(identity: ResumeSessionIdentity, descriptor: DescriptorSnapshot): boolean {
3623
3745
  return (
3624
3746
  identity.dev === descriptor.dev &&
@@ -6948,7 +7070,7 @@ async function collectSessionFromFile(
6948
7070
  firstMessage ||= extractFirstUserMessageFromPrefix(content) ?? "";
6949
7071
  const messageCount = Math.max(parsedMessageCount, countMessageMarkers(content));
6950
7072
  const stats = storage.statSync(file);
6951
- return {
7073
+ const session: SessionInfo = {
6952
7074
  path: file,
6953
7075
  id: header.id,
6954
7076
  cwd: header.cwd ?? "",
@@ -6963,6 +7085,8 @@ async function collectSessionFromFile(
6963
7085
  firstMessage: firstMessage || "(no messages)",
6964
7086
  allMessagesText: allMessages.length > 0 ? allMessages.join(" ") : firstMessage,
6965
7087
  };
7088
+ pickerDiskIdentities.set(session, stats);
7089
+ return session;
6966
7090
  } catch {
6967
7091
  return undefined;
6968
7092
  }
@@ -7081,6 +7205,7 @@ export const SessionManagerTestHooks: {
7081
7205
  afterForkTranscriptPublished?: () => void | Promise<void>;
7082
7206
  beforeEphemeralArtifactManagerInstall?: (dir: string) => void | Promise<void>;
7083
7207
  beforePersistPatchFence?: (attempt: number) => void;
7208
+ beforeLivePickerStarFence?: () => void;
7084
7209
  beforeStrictMissingCheck?: (filePath: string, storage: SessionStorage) => void;
7085
7210
  beforeManagedResumeAcceptance?: (filePath: string, storage: SessionStorage) => void;
7086
7211
  beforeManagedResumeReturn?: (filePath: string, storage: SessionStorage) => void;
@@ -15615,18 +15740,36 @@ export class SessionManager {
15615
15740
  const bytes = Buffer.from(`${entries.map(entry => JSON.stringify(entry)).join("\n")}\n`, "utf8");
15616
15741
  const store = this.#managedTranscriptStore(sessionFile);
15617
15742
  const relativePath = path.basename(sessionFile);
15618
- if (this.#managedPersistExpectedIdentity) {
15619
- try {
15620
- store.replaceExpectedIdentitySync(relativePath, bytes, this.#managedPersistExpectedIdentity);
15621
- } catch (err) {
15622
- // A confirmed missing predecessor can be recreated from the complete
15623
- // resident transcript. Any present-but-different identity still fails
15624
- // closed so a concurrent successor is never overwritten.
15625
- if (!isEnoent(err)) throw err;
15626
- this.#managedPersistExpectedIdentity = undefined;
15627
- store.replaceSync(relativePath, bytes);
15743
+ try {
15744
+ if (this.#managedPersistExpectedIdentity) {
15745
+ try {
15746
+ store.replaceExpectedIdentitySync(relativePath, bytes, this.#managedPersistExpectedIdentity);
15747
+ } catch (err) {
15748
+ // A confirmed missing predecessor can be recreated from the complete
15749
+ // resident transcript. Any present-but-different identity still fails
15750
+ // closed so a concurrent successor is never overwritten.
15751
+ if (!isEnoent(err)) throw err;
15752
+ this.#managedPersistExpectedIdentity = undefined;
15753
+ store.replaceSync(relativePath, bytes);
15754
+ }
15755
+ } else store.replaceSync(relativePath, bytes);
15756
+ } catch (err) {
15757
+ // A live transcript that no longer fits the managed per-file cap is
15758
+ // a typed near-limit outcome, not an unclassified abort: surface a
15759
+ // rewrite-scoped error instead of leaking a raw `content_too_large`
15760
+ // rejection to callers with no entry context. Resident entries stay
15761
+ // in memory, so the transcript persists again once the user runs one
15762
+ // of the actions in SESSION_LIMIT_RECOVERY_ACTIONS. Do not name the
15763
+ // commands here — a second copy of the advice is how `gjc export`
15764
+ // came back (#5732).
15765
+ if (err instanceof Error && err.message === "content_too_large") {
15766
+ throw new SessionNearLimitRewriteError({
15767
+ transcriptBytes: bytes.byteLength,
15768
+ capBytes: MANAGED_ARTIFACT_MAX_FILE_BYTES,
15769
+ });
15628
15770
  }
15629
- } else store.replaceSync(relativePath, bytes);
15771
+ throw err;
15772
+ }
15630
15773
  const descriptor = store.descriptorExpected(relativePath);
15631
15774
  if (!descriptor) throw new Error("managed_replace_identity_unavailable");
15632
15775
  this.#managedPersistExpectedIdentity = this.#captureManagedPersistIdentity(sessionFile);
@@ -17316,6 +17459,190 @@ export class SessionManager {
17316
17459
  }
17317
17460
  }
17318
17461
 
17462
+ async setSessionStarredForPicker(session: SessionInfo, starred: boolean): Promise<void> {
17463
+ if (this.#sessionFile && canonicalizeTrustedPath(session.path) === canonicalizeTrustedPath(this.#sessionFile)) {
17464
+ await this.#setLivePickerStar(session, starred);
17465
+ return;
17466
+ }
17467
+ await SessionManager.setSessionStarredForPicker(
17468
+ session,
17469
+ starred,
17470
+ this.destination.kind === "explicit" ? this.destination.directory : undefined,
17471
+ );
17472
+ }
17473
+
17474
+ #assertPickerSessionIsCurrent(session: SessionInfo): void {
17475
+ if (
17476
+ !this.#sessionFile ||
17477
+ canonicalizeTrustedPath(session.path) !== canonicalizeTrustedPath(this.#sessionFile) ||
17478
+ session.id !== this.#sessionId
17479
+ )
17480
+ throw new Error("Selected session identity changed. Reopen the session picker.");
17481
+ if (session.cwd !== this.cwd) throw new Error("Selected session workspace changed. Reopen the session picker.");
17482
+ const expectedDiskIdentity = pickerDiskIdentities.get(session);
17483
+ if (expectedDiskIdentity) {
17484
+ let observed: SessionStorageStat;
17485
+ try {
17486
+ observed = this.#storage.statSync(this.#sessionFile);
17487
+ } catch {
17488
+ throw new Error("Selected session identity changed. Reopen the session picker.");
17489
+ }
17490
+ if (!pickerDiskIdentityMatchesStorageStat(expectedDiskIdentity, observed))
17491
+ throw new Error("Selected session identity changed. Reopen the session picker.");
17492
+ }
17493
+ }
17494
+
17495
+ async #setLivePickerStar(session: SessionInfo, starred: boolean): Promise<void> {
17496
+ this.#assertRecoveryHydrationWritable();
17497
+ this.#assertPickerSessionIsCurrent(session);
17498
+ const operation = this.#persistChain.then(() => {
17499
+ if (this.#persistError) throw this.#persistError;
17500
+ SessionManagerTestHooks.beforeLivePickerStarFence?.();
17501
+ this.#withSessionPersistenceFenceSync(() => {
17502
+ this.#assertRecoveryHydrationWritable();
17503
+ this.#assertPickerSessionIsCurrent(session);
17504
+ const sessionFile = this.#sessionFile!;
17505
+ const directory = path.dirname(sessionFile);
17506
+ const relativePath = path.basename(sessionFile);
17507
+ const authority = new ManagedSessionDescendantStore(managedDirectoryRoot(directory), directory);
17508
+ try {
17509
+ const descriptor = authority.descriptorExpected(relativePath);
17510
+ if (!descriptor) throw new Error("Selected session no longer exists.");
17511
+ if (descriptor.size > EAGER_RESUME_TRANSCRIPT_MAX_BYTES)
17512
+ throw new Error("Session is too large to star from the picker. Use /star or /unstar in the session.");
17513
+ const snapshot = authority.readExpected(relativePath);
17514
+ if (!snapshot) throw new Error("Selected session no longer exists.");
17515
+ const expectedDiskIdentity = pickerDiskIdentities.get(session);
17516
+ if (
17517
+ expectedDiskIdentity &&
17518
+ !pickerDiskIdentityMatchesManagedSnapshot(expectedDiskIdentity, snapshot.identity)
17519
+ )
17520
+ throw new Error("Selected session identity changed. Reopen the session picker.");
17521
+ const content = snapshot.bytes.toString("utf8");
17522
+ const newline = content.indexOf("\n");
17523
+ const rawHeader: unknown = JSON.parse(newline === -1 ? content : content.slice(0, newline));
17524
+ if (!isRecord(rawHeader) || rawHeader.type !== "session" || rawHeader.id !== session.id)
17525
+ throw new Error("Selected session identity changed. Reopen the session picker.");
17526
+ const entries = parseSessionEntries(content);
17527
+ const diskHeader = entries[0];
17528
+ if (diskHeader?.type !== "session" || diskHeader.cwd !== session.cwd)
17529
+ throw new Error("Selected session workspace changed. Reopen the session picker.");
17530
+ if ((diskHeader.version ?? 1) < 4)
17531
+ throw new Error("Session format requires an upgrade. Resume it before changing its star.");
17532
+ if ((diskHeader.starredPatchVersion === 1 && diskHeader.starred === true) === starred) return;
17533
+
17534
+ const patch = `${JSON.stringify({ type: "header_patch", patch: { starred } })}\n`;
17535
+ const appendOnly = rawHeader.starredPatchVersion === 1 && Number(rawHeader.version) >= 4;
17536
+ let bytes: Uint8Array;
17537
+ if (appendOnly) bytes = Buffer.from(`${content.endsWith("\n") ? "" : "\n"}${patch}`);
17538
+ else {
17539
+ rawHeader.starredPatchVersion = 1;
17540
+ delete rawHeader.starred;
17541
+ const body = newline === -1 ? "" : content.slice(newline + 1);
17542
+ bytes = Buffer.from(
17543
+ `${JSON.stringify(rawHeader)}\n${body}${body && !body.endsWith("\n") ? "\n" : ""}${patch}`,
17544
+ );
17545
+ }
17546
+
17547
+ this.#closePersistWriterInternalSync();
17548
+ if (appendOnly) authority.appendExpectedIdentitySync(relativePath, bytes, snapshot.identity);
17549
+ else authority.replaceExpectedIdentitySync(relativePath, bytes, snapshot.identity);
17550
+ const committedDescriptor = authority.descriptorExpected(relativePath);
17551
+ if (!committedDescriptor) throw new Error("Selected session no longer exists after star mutation.");
17552
+ const committedIdentity = this.#storage.statSync(sessionFile);
17553
+ if (!pickerDiskIdentityMatchesDescriptor(committedIdentity, committedDescriptor))
17554
+ throw new Error("Selected session identity changed after star mutation.");
17555
+ pickerDiskIdentities.set(session, committedIdentity);
17556
+ const liveHeader = this.#fileEntries.find(entry => entry.type === "session") as
17557
+ | SessionHeader
17558
+ | undefined;
17559
+ if (!liveHeader) throw new Error("Selected session identity changed. Reopen the session picker.");
17560
+ liveHeader.starredPatchVersion = 1;
17561
+ applyHeaderPatch(liveHeader, { starred });
17562
+ this.#headerExportRevision++;
17563
+ if (this.destination.kind === "managed") {
17564
+ this.#adoptManagedPersistIdentity(sessionFile);
17565
+ this.#publishCommitMarkerFromCurrentTranscriptSync();
17566
+ }
17567
+ } finally {
17568
+ authority.close();
17569
+ }
17570
+ });
17571
+ });
17572
+ this.#persistChain = operation.catch(() => {});
17573
+ await operation;
17574
+ }
17575
+
17576
+ /** Update only a picker candidate's metadata, without opening a runtime or publishing a resume breadcrumb. */
17577
+ static async setSessionStarredForPicker(
17578
+ session: SessionInfo,
17579
+ starred: boolean,
17580
+ explicitSessionDir?: string,
17581
+ ): Promise<void> {
17582
+ if (isStagedSessionPath(session.path)) throw new Error("Staged session paths are not resumable");
17583
+ const directory = await fs.promises.realpath(path.dirname(session.path));
17584
+ const sessionPath = path.join(directory, path.basename(session.path));
17585
+ if (explicitSessionDir) {
17586
+ const root = await fs.promises.realpath(explicitSessionDir);
17587
+ if (!pathIsWithin(root, sessionPath)) throw new Error("Session is outside the configured session directory.");
17588
+ } else if (
17589
+ !isProjectSessionTranscriptPath(path.join(canonicalizeTrustedPath(session.cwd), ".gjc"), sessionPath)
17590
+ ) {
17591
+ const sessionsRoot = path.dirname(directory);
17592
+ const resolved = resolveManagedScope({ cwd: session.cwd, agentDir: path.dirname(sessionsRoot), sessionsRoot });
17593
+ if (resolved.kind === "error") throw new Error(`Could not resolve managed session scope: ${resolved.message}`);
17594
+ const listing = listManagedCandidates(resolved.scope);
17595
+ if (
17596
+ listing.kind !== "complete" ||
17597
+ !listing.owned.some(candidate => path.resolve(candidate.path) === sessionPath)
17598
+ )
17599
+ throw new Error("Session is not an authorized managed candidate.");
17600
+ }
17601
+ const authority = new ManagedSessionDescendantStore(managedDirectoryRoot(directory), directory);
17602
+ try {
17603
+ const relativePath = path.basename(session.path);
17604
+ const descriptor = authority.descriptorExpected(relativePath);
17605
+ if (!descriptor) throw new Error("Selected session no longer exists.");
17606
+ if (descriptor.size > EAGER_RESUME_TRANSCRIPT_MAX_BYTES)
17607
+ throw new Error("Session is too large to star from the picker. Use /star or /unstar in the session.");
17608
+ const snapshot = authority.readExpected(relativePath);
17609
+ if (!snapshot) throw new Error("Selected session no longer exists.");
17610
+ const content = snapshot.bytes.toString("utf8");
17611
+ const newline = content.indexOf("\n");
17612
+ const rawHeader: unknown = JSON.parse(newline === -1 ? content : content.slice(0, newline));
17613
+ if (!isRecord(rawHeader) || rawHeader.type !== "session" || rawHeader.id !== session.id)
17614
+ throw new Error("Selected session identity changed. Reopen the session picker.");
17615
+ const entries = parseSessionEntries(content);
17616
+ const header = entries[0];
17617
+ if (header?.type !== "session" || header.cwd !== session.cwd)
17618
+ throw new Error("Selected session workspace changed. Reopen the session picker.");
17619
+ if ((header.version ?? 1) < 4)
17620
+ throw new Error("Session format requires an upgrade. Resume it before changing its star.");
17621
+ if ((header.starredPatchVersion === 1 && header.starred === true) === starred) return;
17622
+
17623
+ const patch = `${JSON.stringify({ type: "header_patch", patch: { starred } })}\n`;
17624
+ const appendOnly = rawHeader.starredPatchVersion === 1 && Number(rawHeader.version) >= 4;
17625
+ let bytes: Uint8Array;
17626
+ if (appendOnly) {
17627
+ bytes = Buffer.from(`${content.endsWith("\n") ? "" : "\n"}${patch}`);
17628
+ } else {
17629
+ rawHeader.starredPatchVersion = 1;
17630
+ delete rawHeader.starred;
17631
+ const body = newline === -1 ? "" : content.slice(newline + 1);
17632
+ bytes = Buffer.from(
17633
+ `${JSON.stringify(rawHeader)}\n${body}${body && !body.endsWith("\n") ? "\n" : ""}${patch}`,
17634
+ );
17635
+ }
17636
+ if (appendOnly) authority.appendExpectedIdentitySync(relativePath, bytes, snapshot.identity);
17637
+ else authority.replaceExpectedIdentitySync(relativePath, bytes, snapshot.identity);
17638
+ const committedDescriptor = authority.descriptorExpected(relativePath);
17639
+ if (!committedDescriptor) throw new Error("Selected session no longer exists after star mutation.");
17640
+ pickerDiskIdentities.set(session, descriptorSnapshotAsStorageStat(committedDescriptor));
17641
+ } finally {
17642
+ authority.close();
17643
+ }
17644
+ }
17645
+
17319
17646
  async #ensureStarPatchCapability(header: SessionHeader): Promise<void> {
17320
17647
  if (header.starredPatchVersion === 1) return;
17321
17648
  const previousStarred = header.starred;
@@ -17363,6 +17690,7 @@ export class SessionManager {
17363
17690
  const sanitized = SessionManager.#sanitizeName(name);
17364
17691
  if (!sanitized) return false;
17365
17692
 
17693
+ invalidateSessionTitleGeneration(this);
17366
17694
  this.#sessionName = sanitized;
17367
17695
  this.#titleSource = source;
17368
17696
  await this.#appendHeaderPatch({ title: sanitized, titleSource: source });
@@ -17558,21 +17886,24 @@ export class SessionManager {
17558
17886
  if (publishResumeBreadcrumb && persisted) writeTerminalBreadcrumb(this.cwd, this.#sessionFile);
17559
17887
  if (persisted) this.#readOnlyResume = false;
17560
17888
  } catch (err) {
17561
- // content_too_large on the managed append hot path means the append-only
17562
- // transcript file has reached the per-file storage limit (64 MiB). The
17563
- // on-disk file is append-only and grew past the limit even though the
17564
- // in-memory entry list may be much smaller (compaction evicts old
17565
- // content but the append-only file never shrinks until a full rewrite).
17566
- // Fall back to a full rewrite (replaceSync) which writes only the live
17567
- // in-memory entries, shrinking the file below the limit. The entry has
17568
- // already been added to #fileEntries by #appendEntryWithinPersistenceFence.
17569
- if (err instanceof Error && err.message === "content_too_large") {
17570
- // Typed near-limit contract (#4566): recover by rewriting only the
17571
- // live in-memory entries, then verify the recovered file actually
17572
- // holds the just-appended entry. When even the rewrite cannot fit
17573
- // the entry (live content alone is at the cap), surface the typed
17574
- // near-limit outcome instead of silently succeeding without the
17575
- // receipt for an effect that already committed (e.g. a source edit).
17889
+ // Near-limit failure on the managed append hot path. Two shapes reach here,
17890
+ // both meaning the managed transcript can no longer grow at its per-file cap:
17891
+ // - the appended bytes alone exceed the cap (`content_too_large` from the
17892
+ // store's append, #4566); or
17893
+ // - the ENOENT / missing-predecessor recovery rewrite inside
17894
+ // #appendManagedRecordsSync raised SessionNearLimitRewriteError because the
17895
+ // resident transcript itself no longer fits.
17896
+ // Recovery is the same for both: rewrite only the live in-memory entries (the
17897
+ // append-only file grew past the limit even though the in-memory entry list
17898
+ // may be much smaller, since compaction evicts old content but the file never
17899
+ // shrinks until a full rewrite) and verify the just-appended entry is actually
17900
+ // durable. The entry has already been added to #fileEntries by
17901
+ // #appendEntryWithinPersistenceFence.
17902
+ if (isManagedNearLimitFailure(err)) {
17903
+ // Typed near-limit contract (#4566): when even the rewrite cannot fit the
17904
+ // entry (live content alone is at the cap), surface the typed near-limit
17905
+ // outcome instead of silently succeeding without the receipt for an effect
17906
+ // that already committed (e.g. a source edit).
17576
17907
  const entryBytes = (() => {
17577
17908
  try {
17578
17909
  const materialized = materializeResidentEntryForPersistenceSync(
@@ -17588,7 +17919,23 @@ export class SessionManager {
17588
17919
  return 0;
17589
17920
  }
17590
17921
  })();
17591
- const liveBytesBefore = this.getTranscriptFileBytes();
17922
+ const nearLimit = (liveBytes: number) =>
17923
+ new SessionNearLimitAppendError({
17924
+ entryBytes,
17925
+ liveBytes,
17926
+ capBytes: MANAGED_ARTIFACT_MAX_FILE_BYTES,
17927
+ entryRetained: this.#byId.has(entry.id),
17928
+ });
17929
+ // A rewrite-scoped overflow reports the rejected live-transcript size and
17930
+ // proves the rewrite already ran and failed, so it is not repeated: the
17931
+ // on-disk size is authoritative only for a rejected append, where the
17932
+ // recovery rewrite has not run yet.
17933
+ const liveBytesBefore =
17934
+ err instanceof SessionNearLimitRewriteError ? err.transcriptBytes : this.getTranscriptFileBytes();
17935
+ if (err instanceof SessionNearLimitRewriteError) {
17936
+ this.#needsFullRewriteOnNextPersist = true;
17937
+ throw nearLimit(liveBytesBefore);
17938
+ }
17592
17939
  try {
17593
17940
  this.#rewriteFileSync();
17594
17941
  } catch (rewriteError) {
@@ -17596,14 +17943,9 @@ export class SessionManager {
17596
17943
  // the resident list (its effect, including any committed source
17597
17944
  // edit, is not lost), but the receipt is not durable yet: report
17598
17945
  // the typed near-limit outcome instead of an unclassified abort.
17599
- if (rewriteError instanceof Error && rewriteError.message === "content_too_large") {
17946
+ if (rewriteError instanceof SessionNearLimitRewriteError) {
17600
17947
  this.#needsFullRewriteOnNextPersist = true;
17601
- throw new SessionNearLimitAppendError({
17602
- entryBytes,
17603
- liveBytes: liveBytesBefore,
17604
- capBytes: MANAGED_ARTIFACT_MAX_FILE_BYTES,
17605
- entryRetained: this.#byId.has(entry.id),
17606
- });
17948
+ throw nearLimit(liveBytesBefore);
17607
17949
  }
17608
17950
  throw rewriteError;
17609
17951
  }
@@ -17616,12 +17958,7 @@ export class SessionManager {
17616
17958
  const entryRetained = liveBytesAfter <= MANAGED_ARTIFACT_MAX_FILE_BYTES && this.#byId.has(entry.id);
17617
17959
  if (!entryRetained) {
17618
17960
  this.#needsFullRewriteOnNextPersist = true;
17619
- throw new SessionNearLimitAppendError({
17620
- entryBytes,
17621
- liveBytes: liveBytesAfter || liveBytesBefore,
17622
- capBytes: MANAGED_ARTIFACT_MAX_FILE_BYTES,
17623
- entryRetained: this.#byId.has(entry.id),
17624
- });
17961
+ throw nearLimit(liveBytesAfter || liveBytesBefore);
17625
17962
  }
17626
17963
  return;
17627
17964
  }
@@ -17887,6 +18224,7 @@ export class SessionManager {
17887
18224
  | PythonExecutionMessage
17888
18225
  | FileMentionMessage,
17889
18226
  ): string {
18227
+ if (message.role === "user") invalidateSessionTitleGeneration(this);
17890
18228
  const entry: SessionMessageEntry = {
17891
18229
  type: "message",
17892
18230
  id: this.#generateEntryId(),
@@ -0,0 +1,34 @@
1
+ /**
2
+ * One authoritative, reference-counted lease for work that still needs a
3
+ * session host. Each admitted work state owns one handle and releases that
4
+ * handle at its terminal boundary; the host only observes whether any handle
5
+ * remains held.
6
+ */
7
+ export interface SessionWorkLeaseHandle {
8
+ release(): void;
9
+ }
10
+
11
+ export interface SessionWorkLease {
12
+ acquire(): SessionWorkLeaseHandle;
13
+ isHeld(): boolean;
14
+ }
15
+
16
+ export function createSessionWorkLease(): SessionWorkLease {
17
+ let holders = 0;
18
+ return {
19
+ acquire(): SessionWorkLeaseHandle {
20
+ holders += 1;
21
+ let released = false;
22
+ return {
23
+ release(): void {
24
+ if (released) return;
25
+ released = true;
26
+ holders = Math.max(0, holders - 1);
27
+ },
28
+ };
29
+ },
30
+ isHeld(): boolean {
31
+ return holders > 0;
32
+ },
33
+ };
34
+ }
@@ -1,7 +1,7 @@
1
1
  import { formatProviderCredentialHint } from "@gajae-code/ai/stream";
2
2
 
3
3
  export const MODEL_ONBOARDING_API_PROVIDER_COMMAND =
4
- "/provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <model>";
4
+ "/provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> (--model <model> | --discover)";
5
5
  export const MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND = "/provider add --preset <id>";
6
6
 
7
7
  export const MODEL_ONBOARDING_SETUP_COMMAND = "gjc setup provider";