@gajae-code/coding-agent 0.17.1 → 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 (375) hide show
  1. package/CHANGELOG.md +266 -0
  2. package/dist/types/async/job-manager.d.ts +7 -6
  3. package/dist/types/cli/daemon-cli.d.ts +3 -1
  4. package/dist/types/cli/github-release.d.ts +17 -0
  5. package/dist/types/cli/public-command-entry.d.ts +56 -0
  6. package/dist/types/cli/public-command-errors.d.ts +126 -0
  7. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  8. package/dist/types/cli/public-command-help.d.ts +65 -0
  9. package/dist/types/cli/public-command-registry.d.ts +69 -0
  10. package/dist/types/cli/session-picker.d.ts +1 -1
  11. package/dist/types/cli/setup-cli.d.ts +1 -0
  12. package/dist/types/cli/skills-cli.d.ts +5 -0
  13. package/dist/types/cli.d.ts +3 -0
  14. package/dist/types/commands/daemon.d.ts +4 -50
  15. package/dist/types/commands/sdk.d.ts +20 -37
  16. package/dist/types/commands/setup.d.ts +3 -0
  17. package/dist/types/commands/skills.d.ts +9 -0
  18. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  19. package/dist/types/config/file-lock.d.ts +10 -1
  20. package/dist/types/config/model-profile-activation.d.ts +63 -4
  21. package/dist/types/config/model-registry.d.ts +20 -2
  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/coordinator-mcp/server.d.ts +3 -0
  25. package/dist/types/daemon/operator-contract.d.ts +5 -1
  26. package/dist/types/eval/js/worker-protocol.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/runtime-skill-discovery.d.ts +18 -0
  30. package/dist/types/extensibility/shared-events.d.ts +3 -1
  31. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  32. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  33. package/dist/types/internal-urls/index.d.ts +1 -0
  34. package/dist/types/internal-urls/parse.d.ts +9 -1
  35. package/dist/types/main.d.ts +22 -0
  36. package/dist/types/modes/acp/acp-agent.d.ts +94 -1
  37. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  38. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  39. package/dist/types/modes/components/session-selector.d.ts +5 -2
  40. package/dist/types/modes/components/thinking-selector.d.ts +2 -1
  41. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  42. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  43. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  44. package/dist/types/modes/interactive-mode.d.ts +1 -1
  45. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  46. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  47. package/dist/types/modes/types.d.ts +2 -2
  48. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  49. package/dist/types/runtime-mcp/manager.d.ts +56 -0
  50. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  51. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  52. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  53. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  54. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  55. package/dist/types/sdk/acp/adapter.d.ts +27 -0
  56. package/dist/types/sdk/acp/final-text.d.ts +8 -0
  57. package/dist/types/sdk/broker/broker.d.ts +13 -2
  58. package/dist/types/sdk/broker/ensure.d.ts +46 -1
  59. package/dist/types/sdk/broker/identity.d.ts +3 -1
  60. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  61. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  62. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  63. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  64. package/dist/types/sdk/broker/spawn-authority.d.ts +4 -0
  65. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  66. package/dist/types/sdk/bus/index.d.ts +19 -0
  67. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  68. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  69. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  70. package/dist/types/sdk/cli/rows.d.ts +19 -1
  71. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  72. package/dist/types/sdk/client/client.d.ts +2 -0
  73. package/dist/types/sdk/guides/cli.d.ts +3 -4
  74. package/dist/types/sdk/guides/verify.d.ts +2 -0
  75. package/dist/types/sdk/host/host.d.ts +9 -0
  76. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  77. package/dist/types/sdk/host/types.d.ts +10 -0
  78. package/dist/types/sdk/index.d.ts +1 -0
  79. package/dist/types/sdk/prompt-deadline-flush.d.ts +83 -0
  80. package/dist/types/sdk/prompt-deadline-manager.d.ts +38 -1
  81. package/dist/types/sdk/prompt-failure.d.ts +30 -1
  82. package/dist/types/sdk/prompt-status.d.ts +8 -0
  83. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  84. package/dist/types/sdk/router/replay-retention-gap-cache.d.ts +13 -0
  85. package/dist/types/sdk/router/session-router.d.ts +2 -0
  86. package/dist/types/sdk/startup-capability.d.ts +2 -0
  87. package/dist/types/sdk/transport/relay.d.ts +1 -0
  88. package/dist/types/sdk/transport/serve-cli.d.ts +73 -2
  89. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  90. package/dist/types/session/agent-session.d.ts +168 -21
  91. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  92. package/dist/types/session/fold-coordinator.d.ts +17 -3
  93. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  94. package/dist/types/session/session-manager.d.ts +64 -1
  95. package/dist/types/session/session-work-lease.d.ts +14 -0
  96. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  97. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  98. package/dist/types/thinking.d.ts +3 -1
  99. package/dist/types/tools/bash.d.ts +37 -11
  100. package/dist/types/tools/job.d.ts +1 -1
  101. package/dist/types/tools/steer-fold.d.ts +31 -0
  102. package/dist/types/utils/git.d.ts +28 -2
  103. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  104. package/dist/types/utils/mupdf-wasm-embedded.d.ts +2 -0
  105. package/dist/types/utils/mupdf.d.ts +4 -0
  106. package/dist/types/utils/session-title-generation.d.ts +6 -0
  107. package/dist/types/utils/title-generator.d.ts +11 -2
  108. package/dist/types/web/scrapers/types.d.ts +3 -0
  109. package/package.json +18 -10
  110. package/scripts/build-binary.ts +9 -3
  111. package/scripts/compile-args.ts +0 -9
  112. package/scripts/embed-mupdf.ts +52 -0
  113. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  114. package/scripts/vendor-markit.ts +196 -0
  115. package/src/async/job-manager.ts +60 -9
  116. package/src/cli/daemon-cli.ts +53 -18
  117. package/src/cli/github-release.ts +87 -5
  118. package/src/cli/mcp-cli.ts +38 -4
  119. package/src/cli/public-command-entry.ts +450 -0
  120. package/src/cli/public-command-errors.ts +581 -0
  121. package/src/cli/public-command-evidence.ts +717 -0
  122. package/src/cli/public-command-help.ts +360 -0
  123. package/src/cli/public-command-registry.ts +535 -0
  124. package/src/cli/session-picker.ts +2 -1
  125. package/src/cli/setup-cli.ts +9 -1
  126. package/src/cli/skills-cli.ts +46 -2
  127. package/src/cli.ts +61 -1
  128. package/src/commands/daemon.ts +65 -69
  129. package/src/commands/sdk.ts +232 -100
  130. package/src/commands/setup.ts +2 -0
  131. package/src/commands/skills.ts +11 -0
  132. package/src/config/autorouting-tier-map.ts +16 -0
  133. package/src/config/file-lock.ts +199 -68
  134. package/src/config/model-profile-activation.ts +406 -53
  135. package/src/config/model-registry.ts +204 -25
  136. package/src/config/settings-schema.ts +26 -2
  137. package/src/coordinator-mcp/policy.ts +10 -5
  138. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  139. package/src/coordinator-mcp/question-state.ts +175 -8
  140. package/src/coordinator-mcp/server.ts +528 -87
  141. package/src/coordinator-mcp/session-reaper.ts +6 -4
  142. package/src/crash/record-loader.ts +6 -2
  143. package/src/daemon/operator-contract.ts +26 -12
  144. package/src/debug/report-bundle.ts +114 -24
  145. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  146. package/src/eval/js/context-manager.ts +8 -8
  147. package/src/eval/js/worker-core.ts +9 -4
  148. package/src/eval/js/worker-protocol.ts +2 -0
  149. package/src/extensibility/extensions/runner.ts +52 -4
  150. package/src/extensibility/extensions/types.ts +10 -0
  151. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  152. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  153. package/src/extensibility/shared-events.ts +3 -1
  154. package/src/gjc-runtime/managed-owner-admission.ts +67 -50
  155. package/src/gjc-runtime/session-state-lock.ts +165 -24
  156. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  157. package/src/internal-urls/docs-index.generated.ts +21 -19
  158. package/src/internal-urls/embedded-protocol.ts +83 -0
  159. package/src/internal-urls/index.ts +1 -0
  160. package/src/internal-urls/parse.ts +21 -1
  161. package/src/internal-urls/router.ts +4 -2
  162. package/src/main.ts +123 -55
  163. package/src/modes/acp/acp-agent.ts +593 -47
  164. package/src/modes/acp/acp-event-mapper.ts +9 -0
  165. package/src/modes/acp/startup-provenance.ts +59 -0
  166. package/src/modes/components/custom-provider-wizard.ts +308 -7
  167. package/src/modes/components/model-selector.ts +31 -15
  168. package/src/modes/components/oauth-selector.ts +9 -0
  169. package/src/modes/components/session-selector.ts +47 -4
  170. package/src/modes/components/status-line/segments.ts +6 -1
  171. package/src/modes/components/thinking-selector.ts +7 -3
  172. package/src/modes/components/user-message-selector.ts +57 -15
  173. package/src/modes/controllers/command-controller.ts +58 -32
  174. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  175. package/src/modes/controllers/input-controller.ts +4 -0
  176. package/src/modes/controllers/selector-controller.ts +177 -18
  177. package/src/modes/interactive-mode.ts +21 -7
  178. package/src/modes/runtime-init.ts +1 -0
  179. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  180. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  181. package/src/modes/types.ts +2 -2
  182. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  183. package/src/prompts/system/title-system.md +1 -1
  184. package/src/prompts/tools/bash.md +1 -1
  185. package/src/prompts/tools/job.md +1 -1
  186. package/src/prompts/tools/read.md +2 -0
  187. package/src/runtime/network-prewarm-service.test.ts +36 -1
  188. package/src/runtime/network-prewarm-service.ts +28 -3
  189. package/src/runtime/process-lifecycle.ts +22 -11
  190. package/src/runtime-mcp/config.ts +16 -1
  191. package/src/runtime-mcp/manager.ts +689 -55
  192. package/src/runtime-mcp/pool.test.ts +31 -0
  193. package/src/runtime-mcp/pool.ts +304 -28
  194. package/src/runtime-mcp/redaction.ts +42 -0
  195. package/src/runtime-mcp/startup-policy.ts +7 -0
  196. package/src/runtime-mcp/tool-bridge.ts +71 -0
  197. package/src/runtime-mcp/transports/http.ts +108 -25
  198. package/src/sdk/acp/adapter.ts +90 -9
  199. package/src/sdk/acp/final-text.ts +19 -2
  200. package/src/sdk/broker/broker.ts +378 -52
  201. package/src/sdk/broker/ensure.ts +97 -19
  202. package/src/sdk/broker/identity.ts +9 -1
  203. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  204. package/src/sdk/broker/lifecycle.ts +690 -141
  205. package/src/sdk/broker/runtime.ts +1 -1
  206. package/src/sdk/broker/session-index.ts +97 -4
  207. package/src/sdk/broker/spawn-authority.ts +35 -7
  208. package/src/sdk/broker/stderr-drainer.ts +59 -0
  209. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  210. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  211. package/src/sdk/bus/index.ts +721 -84
  212. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  213. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  214. package/src/sdk/bus/telegram-daemon.ts +10 -3
  215. package/src/sdk/cli/master-cli.ts +47 -36
  216. package/src/sdk/cli/rows.ts +30 -7
  217. package/src/sdk/cli/session-cli.ts +687 -184
  218. package/src/sdk/client/client.ts +18 -0
  219. package/src/sdk/guides/catalog.ts +2 -2
  220. package/src/sdk/guides/cli.ts +20 -62
  221. package/src/sdk/guides/verify.ts +14 -0
  222. package/src/sdk/host/host.ts +49 -5
  223. package/src/sdk/host/session-runtime.test.ts +636 -9
  224. package/src/sdk/host/session-runtime.ts +499 -118
  225. package/src/sdk/host/types.ts +12 -1
  226. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  227. package/src/sdk/host/websocket-transport.ts +40 -6
  228. package/src/sdk/index.ts +11 -0
  229. package/src/sdk/prompt-deadline-flush.ts +334 -0
  230. package/src/sdk/prompt-deadline-manager.ts +136 -4
  231. package/src/sdk/prompt-failure.ts +43 -1
  232. package/src/sdk/prompt-status.ts +8 -0
  233. package/src/sdk/prompt-tool-boundary.ts +121 -0
  234. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  235. package/src/sdk/router/replay-retention-gap-cache.ts +27 -0
  236. package/src/sdk/router/session-router.ts +31 -4
  237. package/src/sdk/session.ts +194 -16
  238. package/src/sdk/startup-capability.ts +1 -1
  239. package/src/sdk/transport/relay.ts +71 -6
  240. package/src/sdk/transport/serve-cli.ts +494 -49
  241. package/src/sdk/transport/socket.ts +12 -11
  242. package/src/sdk/transport/stdio.ts +7 -3
  243. package/src/session/agent-session.ts +2437 -400
  244. package/src/session/fallback-chain-controller.ts +6 -7
  245. package/src/session/fold-coordinator.ts +24 -5
  246. package/src/session/internal/managed-session-scope.ts +58 -18
  247. package/src/session/internal/managed-session-storage.ts +304 -11
  248. package/src/session/session-manager.ts +382 -44
  249. package/src/session/session-work-lease.ts +34 -0
  250. package/src/session-import/codex.ts +12 -3
  251. package/src/setup/model-onboarding-guidance.ts +1 -1
  252. package/src/setup/provider-onboarding.ts +397 -24
  253. package/src/slash-commands/builtin-registry.ts +258 -14
  254. package/src/task/index.ts +14 -2
  255. package/src/thinking.ts +21 -2
  256. package/src/tools/bash.ts +98 -78
  257. package/src/tools/fetch.ts +119 -20
  258. package/src/tools/job.ts +151 -25
  259. package/src/tools/output-meta.ts +15 -2
  260. package/src/tools/path-utils.ts +27 -3
  261. package/src/tools/read.ts +3 -3
  262. package/src/tools/steer-fold.ts +107 -0
  263. package/src/tools/tool-catalog.generated.ts +29 -2
  264. package/src/tools/tool-errors.ts +4 -0
  265. package/src/utils/git.ts +53 -3
  266. package/src/utils/markit.ts +55 -41
  267. package/src/utils/mupdf-assets.d.ts +4 -0
  268. package/src/utils/mupdf-embedded.ts +4 -0
  269. package/src/utils/mupdf-wasm-embedded.ts +3 -0
  270. package/src/utils/mupdf-wasm.ts +24 -2
  271. package/src/utils/mupdf.ts +129 -0
  272. package/src/utils/session-title-generation.ts +29 -0
  273. package/src/utils/title-generator.ts +46 -2
  274. package/src/web/scrapers/types.ts +24 -3
  275. package/vendor/markit-ai/LICENSE +21 -0
  276. package/vendor/markit-ai/MANIFEST.json +412 -0
  277. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  278. package/vendor/markit-ai/dist/commands/config.js +139 -0
  279. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  280. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  281. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  282. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  283. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  284. package/vendor/markit-ai/dist/commands/init.js +29 -0
  285. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  286. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  287. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  288. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  289. package/vendor/markit-ai/dist/config.d.ts +26 -0
  290. package/vendor/markit-ai/dist/config.js +42 -0
  291. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  292. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  293. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  294. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  295. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  296. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  297. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  298. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  299. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  300. package/vendor/markit-ai/dist/converters/github.js +148 -0
  301. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  302. package/vendor/markit-ai/dist/converters/html.js +30 -0
  303. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  304. package/vendor/markit-ai/dist/converters/image.js +124 -0
  305. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  306. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  307. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  308. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  309. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  310. package/vendor/markit-ai/dist/converters/json.js +21 -0
  311. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  312. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  313. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  314. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  315. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  316. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  317. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  318. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  319. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  320. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  321. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  322. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  323. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  324. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  325. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  326. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  327. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  328. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  329. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  330. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  331. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  332. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  333. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  334. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  335. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  336. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  337. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  338. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  339. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  340. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  341. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  342. package/vendor/markit-ai/dist/index.d.ts +30 -0
  343. package/vendor/markit-ai/dist/index.js +26 -0
  344. package/vendor/markit-ai/dist/main.d.ts +2 -0
  345. package/vendor/markit-ai/dist/main.js +191 -0
  346. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  347. package/vendor/markit-ai/dist/markit.js +267 -0
  348. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  349. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  350. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  351. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  352. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  353. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  354. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  355. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  356. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  357. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  358. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  359. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  360. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  361. package/vendor/markit-ai/dist/providers/index.js +56 -0
  362. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  363. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  364. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  365. package/vendor/markit-ai/dist/providers/types.js +1 -0
  366. package/vendor/markit-ai/dist/types.d.ts +36 -0
  367. package/vendor/markit-ai/dist/types.js +1 -0
  368. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  369. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  370. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  371. package/vendor/markit-ai/dist/utils/output.js +31 -0
  372. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  373. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  374. package/vendor/markit-ai/package.json +74 -0
  375. package/vendor/markit-ai.patch +49 -0
@@ -25,5 +25,16 @@ export interface HostEndpointAdapters {
25
25
 
26
26
  export interface BrokerIndexWriter {
27
27
  register(input: { sessionId: string; stateRoot: string; endpointGeneration: number }): void | Promise<void>;
28
- unregister?(input: { sessionId: string; stateRoot: string; endpointGeneration: number }): void | Promise<void>;
28
+ heartbeat?(input: {
29
+ sessionId: string;
30
+ stateRoot: string;
31
+ endpointGeneration: number;
32
+ activity: { state: "active" | "idle"; at: number };
33
+ }): void | Promise<void>;
34
+ unregister?(input: {
35
+ sessionId: string;
36
+ stateRoot: string;
37
+ endpointGeneration: number;
38
+ reason?: "detached_idle";
39
+ }): void | Promise<void>;
29
40
  }
@@ -151,6 +151,90 @@ describe("SDK WebSocket transport lifecycle", () => {
151
151
  await Promise.all(roots.map(root => fs.rm(root, { recursive: true, force: true })));
152
152
  }
153
153
  });
154
+ test("live capability-gated events exclude clients without the negotiated capability", async () => {
155
+ const stateRoot = await tempStateRoot();
156
+ const transport = await createSdkWebSocketTransport({
157
+ sessionId: "live-capability-gate",
158
+ stateRoot,
159
+ token: "token",
160
+ });
161
+ const runtime = new SessionSdkSessionRuntime({ transport });
162
+ const capableFrames: Record<string, unknown>[] = [];
163
+ const legacyFrames: Record<string, unknown>[] = [];
164
+ let capable: SdkClient | undefined;
165
+ let legacy: SdkClient | undefined;
166
+ try {
167
+ const endpoint = await runtime.start();
168
+ capable = await SdkClient.connect(endpoint.url, "token", {
169
+ capabilities: ["tool_activity_v2"],
170
+ reconnectAttempts: 0,
171
+ });
172
+ legacy = await SdkClient.connect(endpoint.url, "token", {
173
+ capabilities: [],
174
+ reconnectAttempts: 0,
175
+ });
176
+ capable.onFrame(frame => capableFrames.push(frame));
177
+ legacy.onFrame(frame => legacyFrames.push(frame));
178
+ await Bun.sleep(20);
179
+ runtime.emitEvent({ kind: "tool_activity", payload: { phase: "started" } });
180
+ runtime.emitEvent({ kind: "activity", payload: { state: "idle" } });
181
+ await Bun.sleep(20);
182
+ expect(capableFrames).toEqual(
183
+ expect.arrayContaining([expect.objectContaining({ type: "event", kind: "tool_activity" })]),
184
+ );
185
+ expect(legacyFrames.some(frame => frame.type === "event" && frame.kind === "tool_activity")).toBe(false);
186
+ expect(legacyFrames).toEqual(
187
+ expect.arrayContaining([expect.objectContaining({ type: "event", kind: "activity" })]),
188
+ );
189
+ } finally {
190
+ await capable?.close();
191
+ await legacy?.close();
192
+ await runtime.stop().catch(() => undefined);
193
+ await fs.rm(stateRoot, { recursive: true, force: true });
194
+ }
195
+ });
196
+ test("event replay capability claims cannot authorize gated replay", async () => {
197
+ const stateRoot = await tempStateRoot();
198
+ const transport = await createSdkWebSocketTransport({
199
+ sessionId: "replay-capability-claim",
200
+ stateRoot,
201
+ token: "token",
202
+ });
203
+ const runtime = new SessionSdkSessionRuntime({ transport });
204
+ let socket: WebSocket | undefined;
205
+ try {
206
+ const endpoint = await runtime.start();
207
+ runtime.emitEvent({ kind: "tool_activity", payload: { phase: "started" } });
208
+ socket = new WebSocket(`${endpoint.url}?token=token`);
209
+ const replay = new Promise<Record<string, unknown>>((resolve, reject) => {
210
+ socket!.addEventListener("message", event => {
211
+ const frame = JSON.parse(String((event as MessageEvent).data)) as Record<string, unknown>;
212
+ if (frame.type === "event_replay_result") resolve(frame);
213
+ });
214
+ socket!.addEventListener("error", () => reject(new Error("SDK WebSocket error")), { once: true });
215
+ });
216
+ await new Promise<void>((resolve, reject) => {
217
+ socket!.addEventListener("open", () => resolve(), { once: true });
218
+ socket!.addEventListener("error", () => reject(new Error("SDK WebSocket error")), { once: true });
219
+ });
220
+ socket.send(
221
+ JSON.stringify({
222
+ type: "event_replay",
223
+ id: "forged-capability-claim",
224
+ sinceGeneration: 1,
225
+ sinceSeq: 0,
226
+ capabilities: ["tool_activity_v2"],
227
+ }),
228
+ );
229
+ const frame = await replay;
230
+ const events = (frame.events as Array<Record<string, unknown>> | undefined) ?? [];
231
+ expect(events.some(event => event.kind === "tool_activity")).toBe(false);
232
+ } finally {
233
+ socket?.close();
234
+ await runtime.stop().catch(() => undefined);
235
+ await fs.rm(stateRoot, { recursive: true, force: true });
236
+ }
237
+ });
154
238
 
155
239
  test("start waits for a pending stop before publishing a probeable replacement endpoint", async () => {
156
240
  const stateRoot = await tempStateRoot();
@@ -2,6 +2,12 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { exactUnlink, type NativeExactFileIdentity } from "@gajae-code/natives";
5
+ import {
6
+ canDeliverSdkEvent,
7
+ SESSION_HOST_OBSERVER_CAPABILITY,
8
+ TOOL_ACTIVITY_CAPABILITY,
9
+ TURN_STREAM_CAPABILITY,
10
+ } from "./host";
5
11
  import type { SessionSdkTransport } from "./session-runtime";
6
12
  import type { SdkFrame } from "./types";
7
13
 
@@ -67,6 +73,8 @@ export async function createSdkWebSocketTransport(
67
73
  let capabilitiesHandler: ((connectionId: string, capabilities: readonly string[]) => void) | undefined;
68
74
  let server: SdkServer | undefined;
69
75
  const sockets = new Map<string, Socket>();
76
+ const negotiatedCapabilities = new Map<string, ReadonlySet<string>>();
77
+ const helloReceived = new Set<string>();
70
78
  const endpointFile = path.join(input.stateRoot, "sdk", `${input.sessionId}.json`);
71
79
  let endpointIdentity: NativeExactFileIdentity | undefined;
72
80
  let started = false;
@@ -95,6 +103,8 @@ export async function createSdkWebSocketTransport(
95
103
  }
96
104
  }
97
105
  sockets.clear();
106
+ negotiatedCapabilities.clear();
107
+ helloReceived.clear();
98
108
  if (current) await stopServer(current);
99
109
  };
100
110
 
@@ -187,9 +197,21 @@ export async function createSdkWebSocketTransport(
187
197
  websocket: {
188
198
  open(socket) {
189
199
  sockets.set(socket.data.connectionId, socket);
190
- socket.send(JSON.stringify({ type: "hello", connectionId: socket.data.connectionId }));
200
+ socket.send(
201
+ JSON.stringify({
202
+ type: "hello",
203
+ protocolVersion: 3,
204
+ connectionId: socket.data.connectionId,
205
+ capabilities: [
206
+ TOOL_ACTIVITY_CAPABILITY,
207
+ TURN_STREAM_CAPABILITY,
208
+ SESSION_HOST_OBSERVER_CAPABILITY,
209
+ ],
210
+ }),
211
+ );
191
212
  },
192
213
  message(socket, message) {
214
+ if (sockets.get(socket.data.connectionId) !== socket) return;
193
215
  const raw = String(message);
194
216
  try {
195
217
  const frame = JSON.parse(raw) as SdkFrame;
@@ -204,11 +226,18 @@ export async function createSdkWebSocketTransport(
204
226
  );
205
227
  return;
206
228
  }
207
- if (frame.type === "event_replay" && Array.isArray(frame.capabilities)) {
208
- capabilitiesHandler?.(
209
- socket.data.connectionId,
210
- frame.capabilities.filter((value): value is string => typeof value === "string"),
211
- );
229
+ if (
230
+ frame.type === "hello" &&
231
+ frame.protocolVersion === 3 &&
232
+ (frame.capabilities === undefined || Array.isArray(frame.capabilities)) &&
233
+ !helloReceived.has(socket.data.connectionId)
234
+ ) {
235
+ const capabilities = Array.isArray(frame.capabilities)
236
+ ? frame.capabilities.filter((value): value is string => typeof value === "string")
237
+ : [];
238
+ helloReceived.add(socket.data.connectionId);
239
+ negotiatedCapabilities.set(socket.data.connectionId, new Set(capabilities));
240
+ capabilitiesHandler?.(socket.data.connectionId, capabilities);
212
241
  }
213
242
  frameHandler?.(socket.data.connectionId, frame);
214
243
  } catch {
@@ -217,7 +246,10 @@ export async function createSdkWebSocketTransport(
217
246
  },
218
247
  close(socket) {
219
248
  const { connectionId } = socket.data;
249
+ if (sockets.get(connectionId) !== socket) return;
220
250
  sockets.delete(connectionId);
251
+ negotiatedCapabilities.delete(connectionId);
252
+ helloReceived.delete(connectionId);
221
253
  connectionCloseHandler?.(connectionId);
222
254
  },
223
255
  },
@@ -336,6 +368,8 @@ export async function createSdkWebSocketTransport(
336
368
  broadcastFrame(frame) {
337
369
  const json = JSON.stringify(frame);
338
370
  for (const socket of sockets.values()) {
371
+ const capabilities = negotiatedCapabilities.get(socket.data.connectionId);
372
+ if (!canDeliverSdkEvent(String(frame.kind), capabilities)) continue;
339
373
  try {
340
374
  socket.send(json);
341
375
  } catch {
package/src/sdk/index.ts CHANGED
@@ -9,6 +9,17 @@ export {
9
9
  type UnknownModelProfileDetails,
10
10
  UnknownModelProfileError,
11
11
  } from "../config/model-profile-contract";
12
+ export type {
13
+ QueuedInputAdmission,
14
+ QueuedInputDelivery,
15
+ QueuedInputExecution,
16
+ QueuedInputQueuePolicy,
17
+ QueuedInputRemovalReason,
18
+ QueuedInputSubmission,
19
+ QueuedInputTerminal,
20
+ SendUserMessageOptions,
21
+ TrackedSendUserMessageOptions,
22
+ } from "../session/agent-session";
12
23
  export * as bus from "./bus";
13
24
  export * as host from "./host";
14
25
  export * as lifecycle from "./lifecycle";
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Worktree durability for a prompt retired by its deadline (#5583).
3
+ *
4
+ * A raised `sdk.promptDeadlineMs` makes the crash rarer, never impossible: the
5
+ * hard `sdk.promptMaxRuntimeMs` cap still fires on a long run, and the session
6
+ * is then torn down with whatever the agent already edited still uncommitted.
7
+ * Field reports lost ~25 minutes of finished work across 37 dirty files that
8
+ * way. Persist that work as a WIP commit in the agent's own worktree before
9
+ * teardown so the next run resumes from a commit instead of re-reasoning.
10
+ *
11
+ * Strictly best effort AND strictly bounded. Every failure — including an abort —
12
+ * is logged and swallowed: the prompt still fails with `prompt_deadline_exceeded`
13
+ * and teardown still happens, because the deadline outcome must never depend on
14
+ * git. Every git call that accepts a signal gets one, so a blocking lock or a
15
+ * hanging plumbing command is killed rather than waited on.
16
+ *
17
+ * ── Isolated index, plumbing commit, compare-and-swap ref move (#5623 round 4)
18
+ *
19
+ * The autosave never opens the user's real `.git/index` for writing and never
20
+ * runs a repository hook:
21
+ *
22
+ * 1. everything is staged into a throwaway index inside the worktree's own git
23
+ * dir (`GIT_INDEX_FILE`), seeded from the HEAD captured before staging;
24
+ * 2. the commit is built with plumbing — `write-tree` then `commit-tree` — so
25
+ * `pre-commit`/`commit-msg` cannot execute during teardown;
26
+ * 3. the ref moves with a compare-and-swap against that captured HEAD, so the
27
+ * one mutation that can outlive teardown is conditional: an abandoned flush
28
+ * that wakes up late finds HEAD moved and does nothing. `withRepoLock`
29
+ * awaits its predecessor BEFORE honouring the signal, so this really does
30
+ * happen — the abort alone cannot prevent it;
31
+ * 4. the real index is adopted only after that swap succeeds, so a successful
32
+ * autosave leaves `git status` clean instead of a phantom reverse diff.
33
+ *
34
+ * Every failing and aborted path therefore just unlinks the temp index. There is
35
+ * no rollback left to get wrong, and index-only state the user set — intent-to-add,
36
+ * skip-worktree/assume-unchanged, sparse-index, index extensions — survives byte
37
+ * for byte because the file was never written.
38
+ *
39
+ * ── Ownership
40
+ *
41
+ * Staging the whole dirty worktree (`git add -A`, untracked files included) is
42
+ * the point: the work being saved is whatever the agent touched, and narrowing
43
+ * it would strand exactly the files this exists for. What makes that safe is
44
+ * WHERE it runs. `sdk.flushWorktreeOnDeadline` still defaults to on, but the
45
+ * implicit default applies only in a LINKED worktree (`gitDir !== commonDir`) —
46
+ * what agent/paseo sessions run in, and the #5583 field report. In the user's
47
+ * primary checkout an autosave needs an explicit opt-in.
48
+ */
49
+
50
+ import * as fsp from "node:fs/promises";
51
+ import * as path from "node:path";
52
+ import { logger } from "@gajae-code/utils";
53
+ import * as git from "../utils/git";
54
+ import { DEADLINE_FLUSH_TIMEOUT_MS } from "./prompt-deadline-manager";
55
+
56
+ export interface PromptDeadlineFlushResult {
57
+ /** Branch the WIP commit landed on, or `undefined` on a detached HEAD. */
58
+ branch?: string;
59
+ /** Abbreviated SHA of the WIP commit. */
60
+ commit: string;
61
+ /** Worktree root the commit was made in. */
62
+ worktreeRoot: string;
63
+ }
64
+
65
+ export interface PromptDeadlineFlushOptions {
66
+ /**
67
+ * Whether the caller resolved `sdk.flushWorktreeOnDeadline` to `true` from a
68
+ * value the user actually wrote, rather than from the schema default. Only an
69
+ * explicit opt-in may autosave outside a linked worktree.
70
+ */
71
+ readonly explicitOptIn?: boolean;
72
+ /**
73
+ * Whether the deadline attempt still owns the prompt. The callback is checked
74
+ * immediately before the ref move and again before the real index is adopted,
75
+ * so progress that renews the lease can abandon the prepared WIP safely.
76
+ */
77
+ readonly isCurrent?: () => boolean;
78
+ readonly signal?: AbortSignal;
79
+ }
80
+
81
+ function wipCommitMessage(branch: string | undefined): string {
82
+ return `wip(${branch ?? "detached"}): autosave on prompt deadline\n`;
83
+ }
84
+
85
+ /** Porcelain v1 XY codes for a path with unresolved merge stages. */
86
+ const UNMERGED_STATUS_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
87
+
88
+ const AUTOSAVE_PATHS = [":(top)**", ":(top,exclude).gjc/agents/**", ":(top,exclude).gjc/skills/**"] as const;
89
+
90
+ type IndexSnapshot = Uint8Array | undefined;
91
+
92
+ async function snapshotIndex(indexPath: string): Promise<IndexSnapshot> {
93
+ try {
94
+ return await fsp.readFile(indexPath);
95
+ } catch (error) {
96
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
97
+ throw error;
98
+ }
99
+ }
100
+
101
+ function sameBytes(left: IndexSnapshot, right: IndexSnapshot): boolean {
102
+ if (left === undefined || right === undefined) return left === right;
103
+ if (left.byteLength !== right.byteLength) return false;
104
+ for (let index = 0; index < left.byteLength; index += 1) if (left[index] !== right[index]) return false;
105
+ return true;
106
+ }
107
+
108
+ function sameHeadIdentity(
109
+ current: git.GitHeadState | null,
110
+ captured: git.GitHeadState,
111
+ expectedCommit: string | null,
112
+ ): boolean {
113
+ if (!current) return false;
114
+ if (
115
+ path.resolve(current.repoRoot) !== path.resolve(captured.repoRoot) ||
116
+ path.resolve(current.gitDir) !== path.resolve(captured.gitDir) ||
117
+ path.resolve(current.commonDir) !== path.resolve(captured.commonDir) ||
118
+ current.kind !== captured.kind ||
119
+ current.commit !== expectedCommit
120
+ )
121
+ return false;
122
+ if (current.kind === "ref" && captured.kind === "ref")
123
+ return current.ref === captured.ref && current.headContent === captured.headContent;
124
+ return true;
125
+ }
126
+
127
+ /**
128
+ * Whether the repository is mid-conflict. Staging into an isolated index seeded
129
+ * from HEAD would happily commit the conflict markers and then adopt a resolved
130
+ * index over the user's unresolved one, so a conflicted repo is left alone.
131
+ */
132
+ function hasUnmergedPaths(statusText: string): boolean {
133
+ return statusText.split("\n").some(line => UNMERGED_STATUS_CODES.has(line.slice(0, 2)));
134
+ }
135
+
136
+ /**
137
+ * A session owns its checkout when it runs in a linked worktree: those are
138
+ * created per agent/task and have their own git dir pointing back at the shared
139
+ * common dir. A primary checkout is the user's, and `git add -A` there would
140
+ * commit whatever they happen to have open alongside the agent.
141
+ */
142
+ function isLinkedWorktree(repository: git.GitRepository): boolean {
143
+ return path.resolve(repository.gitDir) !== path.resolve(repository.commonDir);
144
+ }
145
+
146
+ /**
147
+ * Commit any uncommitted work in the worktree owning `cwd`.
148
+ *
149
+ * Returns `undefined` — without moving any ref — when `cwd` is not inside a git
150
+ * worktree, when the tree is already clean, when the session does not own the
151
+ * checkout, or when git fails or is aborted. Only the session's own worktree is
152
+ * touched; nothing is ever pushed.
153
+ *
154
+ * The abort signal is composed with an internal `DEADLINE_FLUSH_TIMEOUT_MS`
155
+ * bound, so a caller that passes nothing still cannot hang here.
156
+ */
157
+ export async function flushWorktreeOnPromptDeadline(
158
+ cwd: string,
159
+ options?: AbortSignal | PromptDeadlineFlushOptions,
160
+ ): Promise<PromptDeadlineFlushResult | undefined> {
161
+ const {
162
+ explicitOptIn = false,
163
+ isCurrent,
164
+ signal,
165
+ } = options instanceof AbortSignal ? { signal: options } : (options ?? {});
166
+ const timeout = AbortSignal.timeout(DEADLINE_FLUSH_TIMEOUT_MS);
167
+ const bound = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
168
+ try {
169
+ // One resolution for the root, the git dir (where the temp index lives) and
170
+ // the common dir (the ownership signal). A repository we cannot resolve is
171
+ // one we cannot place a temp index in, so it is left alone.
172
+ const repository = await git.repo.resolve(cwd);
173
+ if (!repository) return undefined;
174
+ const worktreeRoot = repository.repoRoot;
175
+ if (!explicitOptIn && !isLinkedWorktree(repository)) {
176
+ logger.debug(
177
+ `sdk: prompt deadline worktree autosave skipped; ${worktreeRoot} is a primary checkout the session does ` +
178
+ `not own. Set sdk.flushWorktreeOnDeadline to true to autosave here anyway.`,
179
+ );
180
+ return undefined;
181
+ }
182
+ const statusText = await git.status(worktreeRoot, { porcelainV1: true, signal: bound });
183
+ const summary = git.status.parse(statusText);
184
+ if (summary.staged + summary.unstaged + summary.untracked === 0) return undefined;
185
+ if (hasUnmergedPaths(statusText)) {
186
+ logger.warn(
187
+ `sdk: prompt deadline worktree autosave skipped; ${worktreeRoot} has an unmerged index and autosaving ` +
188
+ `would commit conflict markers over it.`,
189
+ );
190
+ return undefined;
191
+ }
192
+ // `head.resolve` reads .git files directly and takes no signal.
193
+ const headState = await git.head.resolve(worktreeRoot);
194
+ if (!headState) return undefined;
195
+ const branch = headState?.kind === "ref" ? (headState.branchName ?? undefined) : undefined;
196
+ // Captured HERE, before the repo lock rather than inside it, because this is
197
+ // the value the adoption below is conditional on: an autosave may only land
198
+ // on the HEAD the deadline saw. `withRepoLock` awaits its predecessor before
199
+ // honouring the signal, so the wait between this line and the swap is
200
+ // genuinely unbounded, and anything that moved HEAD in between wins.
201
+ const headSha = await git.head.sha(worktreeRoot, bound);
202
+ const indexPath = path.join(repository.gitDir, "index");
203
+ const indexSnapshot = await snapshotIndex(indexPath);
204
+ // The ref adoption targets. On a detached HEAD that is HEAD itself;
205
+ // otherwise the branch ref, so the swap cannot be confused by a checkout.
206
+ const refName = headState?.kind === "ref" ? headState.ref : "HEAD";
207
+ // Serialize against other in-process git writers on this repo; the lock is
208
+ // keyed by primary repo root, so sibling worktrees share one queue.
209
+ const commit = await git.withRepoLock(
210
+ worktreeRoot,
211
+ () =>
212
+ autosave(
213
+ repository,
214
+ { headState, headSha, indexPath, indexSnapshot, message: wipCommitMessage(branch), refName },
215
+ bound,
216
+ isCurrent,
217
+ ),
218
+ bound,
219
+ );
220
+ if (!commit) return undefined;
221
+ logger.warn(
222
+ `sdk: prompt deadline exceeded with a dirty worktree; autosaved the uncommitted work as ${commit}` +
223
+ `${branch ? ` on ${branch}` : ""} in ${worktreeRoot}`,
224
+ );
225
+ return { commit, worktreeRoot, ...(branch === undefined ? {} : { branch }) };
226
+ } catch (error) {
227
+ logger.warn(
228
+ `sdk: prompt deadline worktree autosave failed; uncommitted work was left in place: ${String(error)}`,
229
+ );
230
+ return undefined;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Build and adopt the WIP commit. Runs under the repo write lock; returns the
236
+ * abbreviated SHA, or `undefined` when the swap was refused because HEAD moved.
237
+ */
238
+ async function autosave(
239
+ repository: git.GitRepository,
240
+ target: {
241
+ headSha: string | null;
242
+ headState: git.GitHeadState;
243
+ indexPath: string;
244
+ indexSnapshot: IndexSnapshot;
245
+ message: string;
246
+ refName: string;
247
+ },
248
+ bound: AbortSignal,
249
+ isCurrent?: () => boolean,
250
+ ): Promise<string | undefined> {
251
+ const worktreeRoot = repository.repoRoot;
252
+ const { headSha, headState, indexPath, indexSnapshot, message, refName } = target;
253
+ // Inside the git dir, never the worktree (where it would show up as untracked
254
+ // and be staged by our own `add -A`) and never /tmp (a different filesystem
255
+ // breaks git's rename-into-place).
256
+ const indexFile = path.join(repository.gitDir, `gjc-deadline-index-${crypto.randomUUID()}`);
257
+ const env = { GIT_INDEX_FILE: indexFile };
258
+ try {
259
+ // Checkout and staging/index mutations are outside the in-process repo lock.
260
+ // Revalidate both the worktree identity and the real index before copying the
261
+ // live tree into the isolated index; otherwise a concurrent checkout can put
262
+ // the new branch's files on the captured branch.
263
+ if (
264
+ isCurrent?.() === false ||
265
+ !sameHeadIdentity(await git.head.resolve(worktreeRoot), headState, headSha) ||
266
+ !sameBytes(await snapshotIndex(indexPath), indexSnapshot)
267
+ )
268
+ return undefined;
269
+ // A missing index file is an empty index, which is already correct for an
270
+ // unborn HEAD; otherwise seed from the commit the swap is conditional on.
271
+ if (headSha) await git.readTree(worktreeRoot, headSha, { env, signal: bound });
272
+ // `read-tree` above only touched the scratch index, but it still yielded to
273
+ // checkout/index writers. Recheck immediately before staging live files.
274
+ if (
275
+ isCurrent?.() === false ||
276
+ !sameHeadIdentity(await git.head.resolve(worktreeRoot), headState, headSha) ||
277
+ !sameBytes(await snapshotIndex(indexPath), indexSnapshot)
278
+ )
279
+ return undefined;
280
+ await git.stage.files(worktreeRoot, AUTOSAVE_PATHS, { env, signal: bound });
281
+ const tree = await git.writeTree(worktreeRoot, { env, signal: bound });
282
+ // `git status` counts paths git will not necessarily commit (a dirty
283
+ // submodule at the same SHA, for one). Without this an autosave could land
284
+ // an empty WIP commit, which `git commit` used to refuse for us.
285
+ if (headSha && tree === (await git.ref.resolve(worktreeRoot, `${headSha}^{tree}`, bound))) return undefined;
286
+ const created = await git.commitTree(worktreeRoot, tree, message, {
287
+ parents: headSha ? [headSha] : [],
288
+ signal: bound,
289
+ });
290
+ // A progress renewal or checkout can happen while the plumbing objects are
291
+ // being prepared. Check again immediately before the irreversible ref CAS.
292
+ if (
293
+ isCurrent?.() === false ||
294
+ !sameHeadIdentity(await git.head.resolve(worktreeRoot), headState, headSha) ||
295
+ !sameBytes(await snapshotIndex(indexPath), indexSnapshot)
296
+ )
297
+ return undefined;
298
+ try {
299
+ await git.ref.update(worktreeRoot, refName, created, headSha ?? "", { reason: message, signal: bound });
300
+ } catch (error) {
301
+ // The expected-old-value check failed (or the ref is locked): someone
302
+ // moved HEAD while this flush was queued or abandoned. Their commit wins;
303
+ // we never retry and never force.
304
+ logger.warn(
305
+ `sdk: prompt deadline worktree autosave abandoned; ${refName} in ${worktreeRoot} moved after the ` +
306
+ `autosave was prepared, so nothing was adopted: ${String(error)}`,
307
+ );
308
+ return undefined;
309
+ }
310
+ // Only now is it safe to touch the real index: it matches the commit that
311
+ // is genuinely HEAD, so `git status` reads clean. This is index-only — the
312
+ // user's files are not rewritten.
313
+ if (
314
+ isCurrent?.() === false ||
315
+ !sameHeadIdentity(await git.head.resolve(worktreeRoot), headState, created) ||
316
+ !sameBytes(await snapshotIndex(indexPath), indexSnapshot)
317
+ )
318
+ return created.slice(0, 7);
319
+ try {
320
+ await git.readTree(worktreeRoot, created, { signal: bound });
321
+ } catch (error) {
322
+ logger.warn(
323
+ `sdk: prompt deadline worktree autosave committed ${created} but could not adopt it into the index in ` +
324
+ `${worktreeRoot}; recover with \`git reset\`: ${String(error)}`,
325
+ );
326
+ }
327
+ return created.slice(0, 7);
328
+ } finally {
329
+ // The real index was never written, so unlinking the scratch one is the
330
+ // whole cleanup. `.lock` is git's in-progress sibling, stranded only if a
331
+ // plumbing command was killed mid-write.
332
+ await Promise.all([indexFile, `${indexFile}.lock`].map(file => fsp.rm(file, { force: true }).catch(() => {})));
333
+ }
334
+ }