@gajae-code/coding-agent 0.17.2 → 0.17.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (390) hide show
  1. package/CHANGELOG.md +267 -0
  2. package/dist/types/cli/daemon-cli.d.ts +3 -1
  3. package/dist/types/cli/github-release.d.ts +17 -0
  4. package/dist/types/cli/public-command-entry.d.ts +56 -0
  5. package/dist/types/cli/public-command-errors.d.ts +126 -0
  6. package/dist/types/cli/public-command-evidence.d.ts +99 -0
  7. package/dist/types/cli/public-command-help.d.ts +65 -0
  8. package/dist/types/cli/public-command-registry.d.ts +69 -0
  9. package/dist/types/cli/session-picker.d.ts +1 -1
  10. package/dist/types/cli/setup-cli.d.ts +1 -0
  11. package/dist/types/cli/skills-cli.d.ts +5 -0
  12. package/dist/types/cli.d.ts +3 -0
  13. package/dist/types/commands/daemon.d.ts +4 -50
  14. package/dist/types/commands/sdk.d.ts +20 -37
  15. package/dist/types/commands/setup.d.ts +3 -0
  16. package/dist/types/commands/skills.d.ts +9 -0
  17. package/dist/types/config/autorouting-tier-map.d.ts +1049 -3
  18. package/dist/types/config/file-lock.d.ts +13 -3
  19. package/dist/types/config/model-profile-activation.d.ts +45 -1
  20. package/dist/types/config/model-registry.d.ts +14 -3
  21. package/dist/types/config/model-resolver.d.ts +1 -1
  22. package/dist/types/config/settings-schema.d.ts +11 -0
  23. package/dist/types/coordinator-mcp/question-state.d.ts +6 -1
  24. package/dist/types/daemon/operator-contract.d.ts +5 -1
  25. package/dist/types/eval/js/worker-protocol.d.ts +2 -0
  26. package/dist/types/exec/bash-executor.d.ts +2 -0
  27. package/dist/types/extensibility/extensions/types.d.ts +12 -0
  28. package/dist/types/extensibility/gjc-plugins/registry.d.ts +4 -0
  29. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +11 -2
  30. package/dist/types/extensibility/runtime-skill-discovery.d.ts +18 -0
  31. package/dist/types/extensibility/shared-events.d.ts +3 -1
  32. package/dist/types/gjc-runtime/session-state-lock.d.ts +61 -0
  33. package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
  34. package/dist/types/harness-control-plane/control-endpoint.d.ts +4 -1
  35. package/dist/types/harness-control-plane/finalize.d.ts +14 -3
  36. package/dist/types/internal-urls/embedded-protocol.d.ts +9 -0
  37. package/dist/types/internal-urls/index.d.ts +1 -0
  38. package/dist/types/internal-urls/parse.d.ts +9 -1
  39. package/dist/types/main.d.ts +23 -1
  40. package/dist/types/modes/acp/acp-agent.d.ts +55 -1
  41. package/dist/types/modes/acp/startup-provenance.d.ts +16 -0
  42. package/dist/types/modes/components/custom-provider-wizard.d.ts +19 -1
  43. package/dist/types/modes/components/session-selector.d.ts +5 -2
  44. package/dist/types/modes/components/user-message-selector.d.ts +5 -2
  45. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  46. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  47. package/dist/types/modes/interactive-mode.d.ts +1 -1
  48. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +11 -1
  49. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +14 -1
  50. package/dist/types/modes/types.d.ts +2 -2
  51. package/dist/types/runtime/process-lifecycle.d.ts +2 -0
  52. package/dist/types/runtime-mcp/loader.d.ts +2 -0
  53. package/dist/types/runtime-mcp/manager.d.ts +69 -6
  54. package/dist/types/runtime-mcp/pool.d.ts +17 -0
  55. package/dist/types/runtime-mcp/redaction.d.ts +20 -0
  56. package/dist/types/runtime-mcp/startup-policy.d.ts +6 -0
  57. package/dist/types/runtime-mcp/tool-bridge.d.ts +9 -0
  58. package/dist/types/runtime-mcp/transports/http.d.ts +11 -0
  59. package/dist/types/sdk/broker/broker.d.ts +17 -4
  60. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  61. package/dist/types/sdk/broker/identity.d.ts +5 -1
  62. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +23 -3
  63. package/dist/types/sdk/broker/lifecycle.d.ts +15 -8
  64. package/dist/types/sdk/broker/managed-task-dag.d.ts +1335 -0
  65. package/dist/types/sdk/broker/managed-task-verification.d.ts +37 -0
  66. package/dist/types/sdk/broker/runtime.d.ts +1 -1
  67. package/dist/types/sdk/broker/session-index.d.ts +22 -3
  68. package/dist/types/sdk/broker/spawn-authority.d.ts +12 -0
  69. package/dist/types/sdk/broker/startup-failure.d.ts +4 -0
  70. package/dist/types/sdk/broker/stderr-drainer.d.ts +13 -0
  71. package/dist/types/sdk/bus/index.d.ts +19 -0
  72. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  73. package/dist/types/sdk/bus/telegram-daemon.d.ts +5 -0
  74. package/dist/types/sdk/cli/master-cli.d.ts +6 -10
  75. package/dist/types/sdk/cli/rows.d.ts +19 -1
  76. package/dist/types/sdk/cli/session-cli.d.ts +22 -5
  77. package/dist/types/sdk/client/client.d.ts +2 -0
  78. package/dist/types/sdk/guides/cli.d.ts +3 -4
  79. package/dist/types/sdk/guides/verify.d.ts +2 -0
  80. package/dist/types/sdk/host/host.d.ts +9 -0
  81. package/dist/types/sdk/host/session-runtime.d.ts +31 -0
  82. package/dist/types/sdk/host/types.d.ts +10 -0
  83. package/dist/types/sdk/index.d.ts +1 -0
  84. package/dist/types/sdk/prompt-deadline-flush.d.ts +85 -0
  85. package/dist/types/sdk/prompt-deadline-manager.d.ts +37 -0
  86. package/dist/types/sdk/prompt-tool-boundary.d.ts +73 -0
  87. package/dist/types/sdk/router/session-router.d.ts +2 -0
  88. package/dist/types/sdk/session.d.ts +3 -3
  89. package/dist/types/sdk/startup-capability.d.ts +2 -0
  90. package/dist/types/sdk/transport/relay.d.ts +1 -0
  91. package/dist/types/sdk/transport/serve-cli.d.ts +48 -4
  92. package/dist/types/sdk/transport/stdio.d.ts +5 -1
  93. package/dist/types/session/agent-session.d.ts +175 -23
  94. package/dist/types/session/fallback-chain-controller.d.ts +6 -6
  95. package/dist/types/session/fold-coordinator.d.ts +17 -3
  96. package/dist/types/session/internal/managed-session-storage.d.ts +7 -1
  97. package/dist/types/session/session-manager.d.ts +64 -1
  98. package/dist/types/session/session-work-lease.d.ts +14 -0
  99. package/dist/types/setup/model-onboarding-guidance.d.ts +1 -1
  100. package/dist/types/setup/provider-onboarding.d.ts +81 -1
  101. package/dist/types/task/types.d.ts +2 -0
  102. package/dist/types/tools/bash-interactive.d.ts +1 -0
  103. package/dist/types/tools/bash.d.ts +37 -11
  104. package/dist/types/tools/fetch.d.ts +10 -1
  105. package/dist/types/tools/job.d.ts +1 -1
  106. package/dist/types/tools/steer-fold.d.ts +31 -0
  107. package/dist/types/tools/subagent.d.ts +8 -1
  108. package/dist/types/utils/git.d.ts +28 -2
  109. package/dist/types/utils/mupdf-embedded.d.ts +2 -0
  110. package/dist/types/utils/mupdf.d.ts +4 -0
  111. package/dist/types/utils/session-title-generation.d.ts +6 -0
  112. package/dist/types/utils/title-generator.d.ts +11 -2
  113. package/dist/types/web/scrapers/types.d.ts +3 -0
  114. package/package.json +18 -10
  115. package/scripts/build-binary.ts +9 -3
  116. package/scripts/compile-args.ts +0 -9
  117. package/scripts/embed-mupdf.ts +52 -0
  118. package/scripts/generate-sdk-operation-inventory.ts +31 -1
  119. package/scripts/vendor-markit.ts +196 -0
  120. package/src/async/job-manager.ts +22 -1
  121. package/src/cli/daemon-cli.ts +53 -18
  122. package/src/cli/github-release.ts +87 -5
  123. package/src/cli/mcp-cli.ts +38 -4
  124. package/src/cli/public-command-entry.ts +450 -0
  125. package/src/cli/public-command-errors.ts +581 -0
  126. package/src/cli/public-command-evidence.ts +717 -0
  127. package/src/cli/public-command-help.ts +360 -0
  128. package/src/cli/public-command-registry.ts +541 -0
  129. package/src/cli/session-picker.ts +2 -1
  130. package/src/cli/setup-cli.ts +10 -1
  131. package/src/cli/skills-cli.ts +46 -2
  132. package/src/cli/stats-cli.ts +11 -1
  133. package/src/cli.ts +61 -1
  134. package/src/commands/daemon.ts +65 -69
  135. package/src/commands/sdk.ts +175 -117
  136. package/src/commands/setup.ts +2 -0
  137. package/src/commands/skills.ts +11 -0
  138. package/src/config/autorouting-tier-map.ts +543 -3
  139. package/src/config/file-lock.ts +206 -65
  140. package/src/config/model-discovery-manager.ts +4 -4
  141. package/src/config/model-profile-activation.ts +417 -66
  142. package/src/config/model-registry.ts +254 -36
  143. package/src/config/model-resolver.ts +6 -1
  144. package/src/config/settings-schema.ts +26 -2
  145. package/src/coordinator-mcp/policy.ts +10 -5
  146. package/src/coordinator-mcp/question-gate-codec.ts +17 -0
  147. package/src/coordinator-mcp/question-state.ts +179 -9
  148. package/src/coordinator-mcp/server.ts +521 -87
  149. package/src/coordinator-mcp/session-reaper.ts +6 -4
  150. package/src/crash/record-loader.ts +6 -2
  151. package/src/daemon/operator-contract.ts +26 -12
  152. package/src/defaults/gjc/skills/ultragoal/SKILL.md +1 -1
  153. package/src/eval/js/context-manager.ts +8 -8
  154. package/src/eval/js/worker-core.ts +9 -4
  155. package/src/eval/js/worker-protocol.ts +2 -0
  156. package/src/exec/bash-executor.ts +3 -0
  157. package/src/extensibility/extensions/runner.ts +52 -4
  158. package/src/extensibility/extensions/types.ts +10 -0
  159. package/src/extensibility/gjc-plugins/registry.ts +196 -37
  160. package/src/extensibility/gjc-plugins/runtime-adapters.ts +46 -8
  161. package/src/extensibility/runtime-skill-discovery.ts +61 -3
  162. package/src/extensibility/shared-events.ts +3 -1
  163. package/src/gjc-runtime/session-state-lock.ts +165 -24
  164. package/src/gjc-runtime/state-writer.ts +317 -6
  165. package/src/gjc-runtime/ultragoal-succession.ts +31 -3
  166. package/src/harness-control-plane/control-endpoint.ts +24 -6
  167. package/src/harness-control-plane/finalize.ts +312 -25
  168. package/src/harness-control-plane/owner.ts +323 -89
  169. package/src/internal-urls/docs-index.generated.ts +23 -20
  170. package/src/internal-urls/embedded-protocol.ts +83 -0
  171. package/src/internal-urls/index.ts +1 -0
  172. package/src/internal-urls/parse.ts +21 -1
  173. package/src/internal-urls/router.ts +4 -2
  174. package/src/main.ts +192 -74
  175. package/src/modes/acp/acp-agent.ts +973 -56
  176. package/src/modes/acp/startup-provenance.ts +59 -0
  177. package/src/modes/components/custom-provider-wizard.ts +308 -7
  178. package/src/modes/components/gjc-bundle-settings.ts +21 -1
  179. package/src/modes/components/oauth-selector.ts +9 -0
  180. package/src/modes/components/session-selector.ts +47 -4
  181. package/src/modes/components/tool-status-header.ts +72 -6
  182. package/src/modes/components/user-message-selector.ts +57 -15
  183. package/src/modes/controllers/command-controller.ts +58 -32
  184. package/src/modes/controllers/extension-ui-controller.ts +33 -7
  185. package/src/modes/controllers/input-controller.ts +4 -0
  186. package/src/modes/controllers/runtime-mcp-command-controller.ts +1 -1
  187. package/src/modes/controllers/selector-controller.ts +161 -15
  188. package/src/modes/interactive-mode.ts +21 -7
  189. package/src/modes/runtime-init.ts +1 -0
  190. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +124 -7
  191. package/src/modes/shared/agent-wire/workflow-gate-types.ts +16 -2
  192. package/src/modes/types.ts +2 -2
  193. package/src/prompts/agent-fragments/browser-aside.md +1 -0
  194. package/src/prompts/system/title-system.md +1 -1
  195. package/src/prompts/tools/bash.md +1 -1
  196. package/src/prompts/tools/job.md +1 -1
  197. package/src/prompts/tools/read.md +2 -0
  198. package/src/runtime/network-prewarm-service.test.ts +36 -1
  199. package/src/runtime/network-prewarm-service.ts +28 -3
  200. package/src/runtime/process-lifecycle.ts +22 -11
  201. package/src/runtime-mcp/config.ts +16 -1
  202. package/src/runtime-mcp/loader.ts +6 -3
  203. package/src/runtime-mcp/manager.ts +845 -109
  204. package/src/runtime-mcp/pool.test.ts +31 -0
  205. package/src/runtime-mcp/pool.ts +304 -28
  206. package/src/runtime-mcp/redaction.ts +42 -0
  207. package/src/runtime-mcp/startup-policy.ts +7 -0
  208. package/src/runtime-mcp/tool-bridge.ts +71 -0
  209. package/src/runtime-mcp/transports/http.ts +108 -25
  210. package/src/sdk/acp/adapter.ts +27 -8
  211. package/src/sdk/broker/broker.ts +1431 -198
  212. package/src/sdk/broker/ensure.ts +21 -3
  213. package/src/sdk/broker/identity.ts +22 -1
  214. package/src/sdk/broker/lifecycle-ledger.ts +95 -31
  215. package/src/sdk/broker/lifecycle.ts +690 -141
  216. package/src/sdk/broker/managed-task-dag.ts +1927 -0
  217. package/src/sdk/broker/managed-task-verification.ts +642 -0
  218. package/src/sdk/broker/runtime.ts +1 -1
  219. package/src/sdk/broker/session-index.ts +97 -4
  220. package/src/sdk/broker/spawn-authority.ts +175 -14
  221. package/src/sdk/broker/startup-failure.ts +80 -1
  222. package/src/sdk/broker/stderr-drainer.ts +59 -0
  223. package/src/sdk/broker/transport.ts +1 -0
  224. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  225. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  226. package/src/sdk/bus/index.ts +707 -81
  227. package/src/sdk/bus/telegram-daemon-cli.ts +31 -2
  228. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  229. package/src/sdk/bus/telegram-daemon.ts +10 -3
  230. package/src/sdk/cli/master-cli.ts +47 -36
  231. package/src/sdk/cli/rows.ts +30 -7
  232. package/src/sdk/cli/session-cli.ts +705 -184
  233. package/src/sdk/client/client.ts +18 -0
  234. package/src/sdk/guides/catalog.ts +2 -2
  235. package/src/sdk/guides/cli.ts +20 -62
  236. package/src/sdk/guides/verify.ts +14 -0
  237. package/src/sdk/host/host.ts +49 -5
  238. package/src/sdk/host/session-runtime.test.ts +184 -2
  239. package/src/sdk/host/session-runtime.ts +322 -101
  240. package/src/sdk/host/types.ts +12 -1
  241. package/src/sdk/host/websocket-transport.lifecycle.test.ts +84 -0
  242. package/src/sdk/host/websocket-transport.ts +40 -6
  243. package/src/sdk/index.ts +11 -0
  244. package/src/sdk/lifecycle/service.ts +2 -0
  245. package/src/sdk/prompt-deadline-flush.ts +356 -0
  246. package/src/sdk/prompt-deadline-manager.ts +133 -1
  247. package/src/sdk/prompt-tool-boundary.ts +121 -0
  248. package/src/sdk/protocol/operation-inventory.generated.json +106 -0
  249. package/src/sdk/router/session-router.ts +9 -1
  250. package/src/sdk/session.ts +763 -116
  251. package/src/sdk/startup-capability.ts +1 -1
  252. package/src/sdk/transport/relay.ts +71 -6
  253. package/src/sdk/transport/serve-cli.ts +397 -42
  254. package/src/sdk/transport/socket.ts +12 -11
  255. package/src/sdk/transport/stdio.ts +7 -3
  256. package/src/session/agent-session.ts +2472 -407
  257. package/src/session/fallback-chain-controller.ts +6 -7
  258. package/src/session/fold-coordinator.ts +24 -5
  259. package/src/session/internal/managed-session-scope.ts +68 -18
  260. package/src/session/internal/managed-session-storage.ts +334 -12
  261. package/src/session/session-manager.ts +382 -44
  262. package/src/session/session-work-lease.ts +34 -0
  263. package/src/setup/model-onboarding-guidance.ts +1 -1
  264. package/src/setup/provider-onboarding.ts +397 -24
  265. package/src/setup/provider-presets.json +2 -1
  266. package/src/slash-commands/builtin-registry.ts +259 -15
  267. package/src/task/executor.ts +46 -17
  268. package/src/task/index.ts +14 -2
  269. package/src/task/types.ts +2 -0
  270. package/src/tools/bash-interactive.ts +2 -0
  271. package/src/tools/bash.ts +118 -78
  272. package/src/tools/fetch.ts +140 -25
  273. package/src/tools/job.ts +151 -25
  274. package/src/tools/output-meta.ts +15 -2
  275. package/src/tools/path-utils.ts +27 -3
  276. package/src/tools/read.ts +3 -3
  277. package/src/tools/steer-fold.ts +107 -0
  278. package/src/tools/subagent-render.ts +24 -1
  279. package/src/tools/subagent.ts +27 -1
  280. package/src/tools/tool-catalog.generated.ts +29 -2
  281. package/src/tools/tool-errors.ts +4 -0
  282. package/src/utils/git.ts +53 -3
  283. package/src/utils/markit.ts +55 -41
  284. package/src/utils/mupdf-assets.d.ts +4 -0
  285. package/src/utils/mupdf-embedded.ts +4 -0
  286. package/src/utils/mupdf.ts +136 -0
  287. package/src/utils/session-title-generation.ts +29 -0
  288. package/src/utils/title-generator.ts +46 -2
  289. package/src/web/scrapers/types.ts +24 -3
  290. package/vendor/markit-ai/LICENSE +21 -0
  291. package/vendor/markit-ai/MANIFEST.json +412 -0
  292. package/vendor/markit-ai/dist/commands/config.d.ts +4 -0
  293. package/vendor/markit-ai/dist/commands/config.js +139 -0
  294. package/vendor/markit-ai/dist/commands/convert.d.ts +6 -0
  295. package/vendor/markit-ai/dist/commands/convert.js +113 -0
  296. package/vendor/markit-ai/dist/commands/formats.d.ts +2 -0
  297. package/vendor/markit-ai/dist/commands/formats.js +92 -0
  298. package/vendor/markit-ai/dist/commands/init.d.ts +2 -0
  299. package/vendor/markit-ai/dist/commands/init.js +29 -0
  300. package/vendor/markit-ai/dist/commands/onboard.d.ts +2 -0
  301. package/vendor/markit-ai/dist/commands/onboard.js +61 -0
  302. package/vendor/markit-ai/dist/commands/plugin.d.ts +4 -0
  303. package/vendor/markit-ai/dist/commands/plugin.js +58 -0
  304. package/vendor/markit-ai/dist/config.d.ts +26 -0
  305. package/vendor/markit-ai/dist/config.js +42 -0
  306. package/vendor/markit-ai/dist/converters/audio.d.ts +7 -0
  307. package/vendor/markit-ai/dist/converters/audio.js +104 -0
  308. package/vendor/markit-ai/dist/converters/csv.d.ts +7 -0
  309. package/vendor/markit-ai/dist/converters/csv.js +83 -0
  310. package/vendor/markit-ai/dist/converters/docx.d.ts +6 -0
  311. package/vendor/markit-ai/dist/converters/docx.js +57 -0
  312. package/vendor/markit-ai/dist/converters/epub.d.ts +8 -0
  313. package/vendor/markit-ai/dist/converters/epub.js +128 -0
  314. package/vendor/markit-ai/dist/converters/github.d.ts +18 -0
  315. package/vendor/markit-ai/dist/converters/github.js +148 -0
  316. package/vendor/markit-ai/dist/converters/html.d.ts +6 -0
  317. package/vendor/markit-ai/dist/converters/html.js +30 -0
  318. package/vendor/markit-ai/dist/converters/image.d.ts +6 -0
  319. package/vendor/markit-ai/dist/converters/image.js +124 -0
  320. package/vendor/markit-ai/dist/converters/ipynb.d.ts +6 -0
  321. package/vendor/markit-ai/dist/converters/ipynb.js +75 -0
  322. package/vendor/markit-ai/dist/converters/iwork.d.ts +20 -0
  323. package/vendor/markit-ai/dist/converters/iwork.js +391 -0
  324. package/vendor/markit-ai/dist/converters/json.d.ts +6 -0
  325. package/vendor/markit-ai/dist/converters/json.js +21 -0
  326. package/vendor/markit-ai/dist/converters/pdf/columns.d.ts +35 -0
  327. package/vendor/markit-ai/dist/converters/pdf/columns.js +93 -0
  328. package/vendor/markit-ai/dist/converters/pdf/extract.d.ts +19 -0
  329. package/vendor/markit-ai/dist/converters/pdf/extract.js +514 -0
  330. package/vendor/markit-ai/dist/converters/pdf/grid.d.ts +25 -0
  331. package/vendor/markit-ai/dist/converters/pdf/grid.js +795 -0
  332. package/vendor/markit-ai/dist/converters/pdf/headers.d.ts +24 -0
  333. package/vendor/markit-ai/dist/converters/pdf/headers.js +108 -0
  334. package/vendor/markit-ai/dist/converters/pdf/index.d.ts +19 -0
  335. package/vendor/markit-ai/dist/converters/pdf/index.js +136 -0
  336. package/vendor/markit-ai/dist/converters/pdf/mupdf-loader.js +9 -0
  337. package/vendor/markit-ai/dist/converters/pdf/render.d.ts +24 -0
  338. package/vendor/markit-ai/dist/converters/pdf/render.js +513 -0
  339. package/vendor/markit-ai/dist/converters/pdf/types.d.ts +75 -0
  340. package/vendor/markit-ai/dist/converters/pdf/types.js +1 -0
  341. package/vendor/markit-ai/dist/converters/plain-text.d.ts +6 -0
  342. package/vendor/markit-ai/dist/converters/plain-text.js +87 -0
  343. package/vendor/markit-ai/dist/converters/pptx.d.ts +8 -0
  344. package/vendor/markit-ai/dist/converters/pptx.js +289 -0
  345. package/vendor/markit-ai/dist/converters/rss.d.ts +11 -0
  346. package/vendor/markit-ai/dist/converters/rss.js +138 -0
  347. package/vendor/markit-ai/dist/converters/wikipedia.d.ts +6 -0
  348. package/vendor/markit-ai/dist/converters/wikipedia.js +33 -0
  349. package/vendor/markit-ai/dist/converters/xlsx.d.ts +8 -0
  350. package/vendor/markit-ai/dist/converters/xlsx.js +147 -0
  351. package/vendor/markit-ai/dist/converters/xml.d.ts +6 -0
  352. package/vendor/markit-ai/dist/converters/xml.js +18 -0
  353. package/vendor/markit-ai/dist/converters/yaml.d.ts +6 -0
  354. package/vendor/markit-ai/dist/converters/yaml.js +17 -0
  355. package/vendor/markit-ai/dist/converters/zip.d.ts +8 -0
  356. package/vendor/markit-ai/dist/converters/zip.js +57 -0
  357. package/vendor/markit-ai/dist/index.d.ts +30 -0
  358. package/vendor/markit-ai/dist/index.js +26 -0
  359. package/vendor/markit-ai/dist/main.d.ts +2 -0
  360. package/vendor/markit-ai/dist/main.js +191 -0
  361. package/vendor/markit-ai/dist/markit.d.ts +45 -0
  362. package/vendor/markit-ai/dist/markit.js +267 -0
  363. package/vendor/markit-ai/dist/plugins/api.d.ts +7 -0
  364. package/vendor/markit-ai/dist/plugins/api.js +42 -0
  365. package/vendor/markit-ai/dist/plugins/index.d.ts +4 -0
  366. package/vendor/markit-ai/dist/plugins/index.js +3 -0
  367. package/vendor/markit-ai/dist/plugins/installer.d.ts +25 -0
  368. package/vendor/markit-ai/dist/plugins/installer.js +176 -0
  369. package/vendor/markit-ai/dist/plugins/loader.d.ts +6 -0
  370. package/vendor/markit-ai/dist/plugins/loader.js +61 -0
  371. package/vendor/markit-ai/dist/plugins/types.d.ts +25 -0
  372. package/vendor/markit-ai/dist/plugins/types.js +1 -0
  373. package/vendor/markit-ai/dist/providers/anthropic.d.ts +2 -0
  374. package/vendor/markit-ai/dist/providers/anthropic.js +47 -0
  375. package/vendor/markit-ai/dist/providers/index.d.ts +21 -0
  376. package/vendor/markit-ai/dist/providers/index.js +56 -0
  377. package/vendor/markit-ai/dist/providers/openai.d.ts +2 -0
  378. package/vendor/markit-ai/dist/providers/openai.js +76 -0
  379. package/vendor/markit-ai/dist/providers/types.d.ts +26 -0
  380. package/vendor/markit-ai/dist/providers/types.js +1 -0
  381. package/vendor/markit-ai/dist/types.d.ts +36 -0
  382. package/vendor/markit-ai/dist/types.js +1 -0
  383. package/vendor/markit-ai/dist/utils/exit-codes.d.ts +4 -0
  384. package/vendor/markit-ai/dist/utils/exit-codes.js +4 -0
  385. package/vendor/markit-ai/dist/utils/output.d.ts +22 -0
  386. package/vendor/markit-ai/dist/utils/output.js +31 -0
  387. package/vendor/markit-ai/dist/utils/turndown.d.ts +8 -0
  388. package/vendor/markit-ai/dist/utils/turndown.js +70 -0
  389. package/vendor/markit-ai/package.json +74 -0
  390. package/vendor/markit-ai.patch +49 -0
@@ -3,6 +3,7 @@ import type { Dirent } from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
+ import { redactCrashSecrets, safeErrorDescription } from "@gajae-code/utils";
6
7
  import { bindPluginMcpToPublicNetwork } from "../../runtime-mcp/plugin-network-boundary";
7
8
  import type { MCPStdioPreparedLaunch, MCPStdioSpawnLaunch } from "../../runtime-mcp/types";
8
9
  import { loadCustomTools } from "../custom-tools/loader";
@@ -20,7 +21,13 @@ import { isV2Tool } from "./migration";
20
21
  import { gjcPluginInstallRoot, resolveWithinRoot } from "./paths";
21
22
  import { loadEffectiveGjcPluginRegistry, registryPathForScope, registryRootForScope } from "./registry";
22
23
  import { type SessionQuarantine, type SessionValidationResult, validateSessionBundles } from "./session-validation";
23
- import type { GjcPluginRegistryEntry, GjcPluginScope, JsonSchema202012, NormalizedToolSurfaceV2 } from "./types";
24
+ import type {
25
+ GjcBundleIdentity,
26
+ GjcPluginRegistryEntry,
27
+ GjcPluginScope,
28
+ JsonSchema202012,
29
+ NormalizedToolSurfaceV2,
30
+ } from "./types";
24
31
 
25
32
  export interface AlwaysOnPluginTools {
26
33
  tools: CustomTool[];
@@ -32,6 +39,23 @@ export interface GjcPluginToolDeclaration extends NormalizedToolSurfaceV2 {
32
39
  scope: GjcPluginScope;
33
40
  }
34
41
 
42
+ export interface GjcPluginMcpServerProvenance {
43
+ identity: GjcBundleIdentity;
44
+ surfaceId: string;
45
+ }
46
+
47
+ // Plugin MCP manifests reject per-server timeout controls; generated transports
48
+ // share one bounded window so startup evidence reaches a terminal result.
49
+ const PLUGIN_MCP_STARTUP_TIMEOUT_MS = 5_000;
50
+ const MAX_PLUGIN_MCP_DIAGNOSTIC_LENGTH = 1_024;
51
+
52
+ /** Bound and scrub plugin-controlled MCP causes before they enter logs or runtime findings. */
53
+ export function safePluginMcpDiagnostic(value: unknown): string {
54
+ const message = safeErrorDescription(value).replace(/[\u0000-\u001f\u007f-\u009f]/gu, "");
55
+ const normalized = message.replace(/\b(Bearer|Basic|Token)(?=[A-Za-z0-9._~+/=-]{8,})/giu, "$1 ");
56
+ return redactCrashSecrets(normalized).slice(0, MAX_PLUGIN_MCP_DIAGNOSTIC_LENGTH);
57
+ }
58
+
35
59
  function isWithin(root: string, target: string): boolean {
36
60
  const rel = path.relative(root, target);
37
61
  return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
@@ -557,7 +581,9 @@ async function prepareVerifiedStdioLaunch(input: {
557
581
  throw new Error("Authenticated plugin MCP stdio launch capsules are available only on Linux");
558
582
  }
559
583
  if (input.launcher === "bun") {
560
- throw new Error("Authenticated plugin MCP Bun launch capsules are unavailable");
584
+ throw new Error(
585
+ "Authenticated plugin MCP Bun launch capsules are unavailable: Bun's clearable runtime plugin hooks cannot enforce the authenticated module-loading boundary required to restrict plugin imports to verified capsule bytes",
586
+ );
561
587
  }
562
588
  const registerCleanup = input.registerCleanup;
563
589
  if (!registerCleanup) {
@@ -1045,18 +1071,22 @@ export async function renderSkillAdvertisement(input: {
1045
1071
  * Convert active plugin-bundle MCP surfaces into runtime MCPServerConfig entries,
1046
1072
  * applying install + runtime MCP policy (URL scheme/private-range deny, DNS
1047
1073
  * re-resolution for http/sse, stdio root-confinement) before connection. Servers
1048
- * failing policy are quarantined and excluded. Returns {} when none.
1074
+ * failing policy are quarantined and excluded. The provenance map contains
1075
+ * only servers present in `configs`, so connection errors can be attributed to
1076
+ * the authenticated bundle and stable surface that supplied each server.
1049
1077
  */
1050
1078
  export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
1051
1079
  configs: Record<string, any>;
1052
1080
  quarantine: SessionQuarantine[];
1081
+ serverProvenance: ReadonlyMap<string, GjcPluginMcpServerProvenance>;
1053
1082
  }> {
1054
1083
  const { effective, active, quarantine } = await loadValidatedPluginRegistry(input.cwd, true);
1055
- if (effective.length === 0) return { configs: {}, quarantine: [] };
1084
+ if (effective.length === 0) return { configs: {}, quarantine: [], serverProvenance: new Map() };
1056
1085
 
1057
1086
  // A manifest-controlled MCP name such as "constructor" or "toString" must
1058
1087
  // remain an ordinary own key rather than interacting with Object.prototype.
1059
1088
  const configs: Record<string, any> = Object.create(null) as Record<string, any>;
1089
+ const serverProvenance = new Map<string, GjcPluginMcpServerProvenance>();
1060
1090
  for (const entry of active) {
1061
1091
  const disabled = new Set(entry.disabledSurfaceIds);
1062
1092
  let compiledMcps: Map<string, (typeof entry.surfaces.mcps)[number]> | undefined;
@@ -1119,7 +1149,7 @@ export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
1119
1149
  command,
1120
1150
  args: initialArgs,
1121
1151
  cwd: invocation.cwd,
1122
- timeout: 5_000,
1152
+ timeout: PLUGIN_MCP_STARTUP_TIMEOUT_MS,
1123
1153
  // Third-party plugin MCP processes must not inherit host secrets;
1124
1154
  // only a minimal OS allowlist (PATH/HOME/temp/locale) is provided.
1125
1155
  noInheritEnv: true,
@@ -1207,18 +1237,26 @@ export async function buildPluginMcpConfigs(input: { cwd: string }): Promise<{
1207
1237
  // resolution path expands ${env:...}/shell templates, which would let
1208
1238
  // a third-party bundle exfiltrate host secrets. Plugin-bundle MCP
1209
1239
  // servers connect without bundle-declared headers.
1210
- configs[m.name] = bindPluginMcpToPublicNetwork({ type: cfg.transport, url: url.toString() });
1240
+ configs[m.name] = bindPluginMcpToPublicNetwork({
1241
+ type: cfg.transport,
1242
+ url: url.toString(),
1243
+ timeout: PLUGIN_MCP_STARTUP_TIMEOUT_MS,
1244
+ });
1211
1245
  }
1246
+ serverProvenance.set(m.name, {
1247
+ identity: bundleIdentity(entry.scope, entry.name),
1248
+ surfaceId: `mcp:${m.name}`,
1249
+ });
1212
1250
  } catch (error) {
1213
1251
  quarantine.push({
1214
1252
  identity: bundleIdentity(entry.scope, entry.name),
1215
1253
  plugin: entry.name,
1216
1254
  surfaceId: m.extensionId,
1217
1255
  code: "security_policy",
1218
- message: error instanceof Error ? error.message : String(error),
1256
+ message: safePluginMcpDiagnostic(error),
1219
1257
  });
1220
1258
  }
1221
1259
  }
1222
1260
  }
1223
- return { configs, quarantine };
1261
+ return { configs, quarantine, serverProvenance };
1224
1262
  }
@@ -54,6 +54,16 @@ export interface RuntimeSkillDiscoveryResult {
54
54
  candidates: RuntimeSkillDiscoveryCandidate[];
55
55
  /** Deduped skill metadata the query filter ran against, including bundled entries; candidates.length <= scanned. */
56
56
  scanned: number;
57
+ /**
58
+ * Skills left after the query filter, before the page slice: the denominator
59
+ * a page is a page *of*. `scanned` counts everything the filter ran against,
60
+ * so it cannot serve this role.
61
+ */
62
+ matching: number;
63
+ /** Normalized zero-based offset the returned page starts at. */
64
+ offset: number;
65
+ /** Offset of the next page, present only while one exists; omitted on the final page. */
66
+ nextOffset?: number;
57
67
  diagnostics: RuntimeSkillDiscoveryDiagnostics;
58
68
  }
59
69
 
@@ -65,6 +75,8 @@ export interface DiscoverRuntimeSkillsOptions {
65
75
  profileAuthority?: "default" | "custom";
66
76
  query?: string;
67
77
  limit?: number;
78
+ /** Zero-based index into the matching set the returned page starts at; defaults to 0. */
79
+ offset?: number;
68
80
  source?: RuntimeSkillDiscoveryScope | "all";
69
81
  policy?: SkillsSettings;
70
82
  }
@@ -74,11 +86,26 @@ function getRuntimeHome(): string {
74
86
  }
75
87
 
76
88
  const DEFAULT_LIMIT = 20;
77
- const MAX_LIMIT = 50;
89
+ /**
90
+ * Upper bound every caller's `limit` clamps to. Exported so `gjc skills discover`
91
+ * can default a human to the widest page the library will serve instead of the
92
+ * model-context-sized `DEFAULT_LIMIT`.
93
+ */
94
+ export const SKILL_DISCOVERY_MAX_LIMIT = 50;
78
95
  const MAX_DIAGNOSTICS = 10;
79
96
  function normalizeLimit(limit: number | undefined): number {
80
97
  if (limit === undefined || !Number.isFinite(limit)) return DEFAULT_LIMIT;
81
- return Math.max(1, Math.min(MAX_LIMIT, Math.trunc(limit)));
98
+ return Math.max(1, Math.min(SKILL_DISCOVERY_MAX_LIMIT, Math.trunc(limit)));
99
+ }
100
+
101
+ /**
102
+ * Offsets are clamped from below only. An offset past the end is a legitimate
103
+ * empty final page (the catalog shrank between pages), not an error, so it is
104
+ * reported rather than silently snapped back onto the last populated page.
105
+ */
106
+ function normalizeOffset(offset: number | undefined): number {
107
+ if (offset === undefined || !Number.isFinite(offset) || offset < 0) return 0;
108
+ return Math.trunc(offset);
82
109
  }
83
110
 
84
111
  interface ProjectScanDir {
@@ -558,9 +585,40 @@ export async function discoverRuntimeSkills(
558
585
  seenNames,
559
586
  diagnostics,
560
587
  );
588
+ const limit = normalizeLimit(options.limit);
589
+ const offset = normalizeOffset(options.offset);
590
+ const matching = candidates.length;
591
+ const page = candidates.slice(offset, offset + limit);
592
+ // Continuation is omitted on the final page, so "is there more" is the
593
+ // presence of the field rather than a value the caller has to compare.
594
+ const nextOffset = offset + page.length < matching ? offset + page.length : undefined;
595
+ // Appended directly instead of through pushDiagnostic: these are the only
596
+ // messages that explain why a skill the caller can see on disk is absent
597
+ // from the result, and the reported case (several stale
598
+ // skills.customDirectories entries, one "does not exist" message each)
599
+ // fills the MAX_DIAGNOSTICS budget before the slice is ever reached. A
600
+ // dropped paging notice makes the truncation invisible again.
601
+ if (page.length === 0) {
602
+ if (offset > 0 && matching > 0) {
603
+ diagnostics.push(
604
+ `no skills at offset ${offset}; ${matching} matching skill${matching === 1 ? "" : "s"} total, pass --offset 0 to start over`,
605
+ );
606
+ }
607
+ } else if (nextOffset !== undefined) {
608
+ diagnostics.push(
609
+ `showing ${offset + 1}-${offset + page.length} of ${matching} matching skills; pass --offset ${nextOffset} for the next page, or narrow the query`,
610
+ );
611
+ } else if (offset > 0) {
612
+ // Final page of a multi-page walk: no continuation to offer, but the
613
+ // caller still needs to know this page is not the whole matching set.
614
+ diagnostics.push(`showing ${offset + 1}-${offset + page.length} of ${matching} matching skills`);
615
+ }
561
616
  return {
562
- candidates: candidates.slice(0, normalizeLimit(options.limit)),
617
+ candidates: page,
563
618
  scanned,
619
+ matching,
620
+ offset,
621
+ ...(nextOffset === undefined ? {} : { nextOffset }),
564
622
  diagnostics: { messages: diagnostics },
565
623
  };
566
624
  }
@@ -93,9 +93,11 @@ export interface SessionCompactEvent {
93
93
  fromExtension: boolean;
94
94
  }
95
95
 
96
- /** Fired on process exit (SIGINT/SIGTERM) */
96
+ /** Fired during session teardown, including process exit and SDK host self-reap. */
97
97
  export interface SessionShutdownEvent {
98
98
  type: "session_shutdown";
99
+ /** Set only when the SDK host reaped itself after detached-idle grace. */
100
+ reason?: "detached_idle";
99
101
  }
100
102
 
101
103
  /** Preparation data for tree navigation (used by session_before_tree event) */
@@ -14,7 +14,7 @@ import {
14
14
  readFileLockObservationForGc,
15
15
  } from "../config/file-lock";
16
16
  import { loadInstallationHostId, loadLegacyInstallationHostId } from "../config/machine-identity";
17
- import { readLinuxProcStartTimeSync } from "./linux-proc";
17
+ import { type LinuxProcPidProbeResult, probeLinuxProcPidSync, readLinuxProcStartTimeSync } from "./linux-proc";
18
18
 
19
19
  /** SHA-256 of the empty payload; the constant identity of every verified-empty receipt. */
20
20
  const EMPTY_FILE_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
@@ -122,6 +122,10 @@ function lockRetryExhausted(budget: LockRetryBudget): boolean {
122
122
  return lockRetryRemainingMs(budget) <= 0;
123
123
  }
124
124
 
125
+ function lockRetryTotalWaitExhausted(budget: LockRetryBudget): boolean {
126
+ return Math.max(0, performance.now() - budget.firstAttemptAt) >= LOCK_ACQUIRE_MAX_WAIT_MS;
127
+ }
128
+
125
129
  async function waitForLockRetry(budget: LockRetryBudget): Promise<boolean> {
126
130
  budget.attempts++;
127
131
  const remainingMs = lockRetryRemainingMs(budget);
@@ -501,6 +505,13 @@ export const SessionStateLockTestHooks: {
501
505
  * configuration.
502
506
  */
503
507
  probeProcessSignal?: (pid: number) => void;
508
+ /**
509
+ * @internal How an owner pid's `/proc` entry is read. Whether a killed pid is still an
510
+ * unreaped zombie depends on when its parent happens to reap it, so it cannot be
511
+ * arranged deterministically from a test. Production code never sets it, and it is
512
+ * never serialized or exposed as runtime configuration.
513
+ */
514
+ probeLinuxProcPid?: (pid: number) => LinuxProcPidProbeResult;
504
515
  /**
505
516
  * @internal Runs inside a NEW owner record's write, after the exclusive create has
506
517
  * taken the pathname and before the record's bytes land. Throwing from it fails that
@@ -560,6 +571,33 @@ export type SessionStateLockUnavailableReason =
560
571
  | "transition_claim_timeout"
561
572
  | "unsafe_lock_path_type";
562
573
 
574
+ /**
575
+ * Why a stale-transition reclaim refused, as a bounded classifier.
576
+ *
577
+ * `transition_claim_timeout` collapsed several structurally different refusals into
578
+ * one message, so an operator could not tell a foreign-host tombstone from a
579
+ * live owner from a directory an external process was stamping — the gap #5606
580
+ * asked for. Every member is a fixed enum: never a path, token, host id, pid or
581
+ * raw errno, because this rides on an error message users paste into reports.
582
+ */
583
+ export type TransitionReclaimRefusal =
584
+ /** The claim vanished, or its owner sidecar was absent/unparseable/invalid. */
585
+ | "claim_or_owner_unreadable"
586
+ /** A released tombstone carrying no `owner_host_id` at all. */
587
+ | "released_owner_unprovenanced"
588
+ /** A released tombstone belonging to a different installation. */
589
+ | "released_owner_foreign_host"
590
+ /** A released tombstone still inside `RELEASED_TRANSITION_GRACE_MS`. */
591
+ | "released_owner_within_grace"
592
+ /** The owner is alive, or its liveness could not be disproven. */
593
+ | "owner_live_or_unverifiable"
594
+ /** The claim changed between inspection and capture — an external writer. */
595
+ | "claim_generation_changed"
596
+ /** The claim was not empty when captured, so it is not ours to remove. */
597
+ | "claim_not_empty"
598
+ /** The claim's canonical path or directory snapshot could not be taken. */
599
+ | "claim_capture_failed";
600
+
563
601
  interface SessionStateLockUnavailableDetails {
564
602
  lockPath: string;
565
603
  reason: SessionStateLockUnavailableReason;
@@ -750,6 +788,28 @@ function lockUnavailable(
750
788
  });
751
789
  }
752
790
 
791
+ /**
792
+ * `transition_claim_timeout` with the reclaim refusal that kept repeating.
793
+ *
794
+ * The bare classifier could not distinguish several structurally different
795
+ * refusals, so an operator had no way to tell which action would help (#5606).
796
+ * The suffix is a fixed enum member and nothing else — this string reaches user
797
+ * reports, so it must never carry a path, token, host id, pid or errno.
798
+ */
799
+ function transitionClaimTimeout(
800
+ transitionDir: string,
801
+ budget: LockRetryBudget,
802
+ cause: unknown,
803
+ refusal: TransitionReclaimRefusal | undefined,
804
+ ): SessionStateLockUnavailableError {
805
+ const error = lockUnavailable(transitionDir, "transition_claim_timeout", budget, cause);
806
+ if (refusal === undefined) return error;
807
+ return Object.assign(error, {
808
+ reclaimRefusal: refusal,
809
+ message: `${error.message.replace(/\.$/, "")} (reclaim refused: ${refusal}).`,
810
+ });
811
+ }
812
+
753
813
  function lockDiagnostic(
754
814
  error: unknown,
755
815
  lockPath: string,
@@ -941,7 +1001,17 @@ function probeOwnerProcess(pid: number): OwnerProcessLiveness {
941
1001
  try {
942
1002
  const probe = SessionStateLockTestHooks.probeProcessSignal;
943
1003
  if (probe) probe(pid);
944
- else process.kill(pid, 0);
1004
+ else {
1005
+ // Linux keeps a killed child as a zombie until its parent reaps it. `kill(pid, 0)`
1006
+ // still succeeds for that zombie even though it can no longer hold the lock, so
1007
+ // inspect `/proc` first and treat the terminal `Z` state as proof of death.
1008
+ if (process.platform === "linux") {
1009
+ const proc = (SessionStateLockTestHooks.probeLinuxProcPid ?? probeLinuxProcPidSync)(pid);
1010
+ if (proc.kind === "absent") return "dead";
1011
+ if (proc.kind === "live" && proc.state === "Z") return "dead";
1012
+ }
1013
+ process.kill(pid, 0);
1014
+ }
945
1015
  return "alive";
946
1016
  } catch (error) {
947
1017
  const code = (error as NodeJS.ErrnoException).code;
@@ -1846,12 +1916,27 @@ async function releaseTransitionClaim(
1846
1916
  }
1847
1917
  }
1848
1918
 
1849
- async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName: string): Promise<boolean> {
1919
+ /**
1920
+ * Outcome of one stale-transition reclaim attempt. `reclaimed` drives the
1921
+ * caller's retry accounting exactly as the previous boolean did; `refusal`
1922
+ * exists only so the eventual `transition_claim_timeout` can name which
1923
+ * precondition said no (#5606).
1924
+ */
1925
+ interface TransitionReclaimOutcome {
1926
+ reclaimed: boolean;
1927
+ reclaimReason?: "released_handoff" | "dead_owner";
1928
+ refusal?: TransitionReclaimRefusal;
1929
+ }
1930
+
1931
+ async function reclaimStaleTransitionClaim(
1932
+ transitionDir: string,
1933
+ quarantineName: string,
1934
+ ): Promise<TransitionReclaimOutcome> {
1850
1935
  let stat: fsSync.BigIntStats;
1851
1936
  try {
1852
1937
  stat = await fs.lstat(transitionDir, { bigint: true });
1853
1938
  } catch {
1854
- return false;
1939
+ return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
1855
1940
  }
1856
1941
  // Regular-file claims belong to the superseded protocol. They retain the old
1857
1942
  // exact-identity stale path; released PID-1 tombstones deliberately require
@@ -1865,41 +1950,46 @@ async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName
1865
1950
  },
1866
1951
  quarantineName,
1867
1952
  );
1868
- return reclaimed === "owner_removed";
1953
+ return {
1954
+ reclaimed: reclaimed === "owner_removed",
1955
+ ...(reclaimed === "owner_removed" ? { reclaimReason: "dead_owner" as const } : {}),
1956
+ };
1869
1957
  }
1870
1958
  if (!stat.isDirectory()) throw new SessionStateLockUnavailableError();
1871
1959
  const ownerSnapshot = await captureRegularLockOwner(`${transitionDir}.owner`);
1872
- if (!ownerSnapshot) return false;
1960
+ if (!ownerSnapshot) return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
1873
1961
  let owner: unknown;
1874
1962
  try {
1875
1963
  owner = JSON.parse(ownerSnapshot.bytes);
1876
1964
  } catch {
1877
- return false;
1965
+ return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
1878
1966
  }
1879
- if (!validLockOwner(owner)) return false;
1967
+ if (!validLockOwner(owner)) return { reclaimed: false, refusal: "claim_or_owner_unreadable" };
1880
1968
  await SessionStateLockTestHooks.afterTransitionStaleInspection?.(transitionDir);
1881
1969
  if (owner.released === true) {
1882
1970
  // A released record is safe only when it is qualified by this installation.
1883
1971
  // Never let a shared-volume tombstone, an absent identity, or a legacy foreign
1884
1972
  // identity authorize removal of a claim this process cannot own.
1885
- if (owner.owner_host_id === undefined) return false;
1973
+ if (owner.owner_host_id === undefined) return { reclaimed: false, refusal: "released_owner_unprovenanced" };
1886
1974
  const currentHost = await currentOwnerHostId();
1887
1975
  const legacyHost = await currentLegacyOwnerHostId();
1888
- if (owner.owner_host_id !== currentHost && owner.owner_host_id !== legacyHost) return false;
1889
- if (Date.now() - Number(ownerSnapshot.mtimeNs / 1_000_000n) < RELEASED_TRANSITION_GRACE_MS) return false;
1976
+ if (owner.owner_host_id !== currentHost && owner.owner_host_id !== legacyHost)
1977
+ return { reclaimed: false, refusal: "released_owner_foreign_host" };
1978
+ if (Date.now() - Number(ownerSnapshot.mtimeNs / 1_000_000n) < RELEASED_TRANSITION_GRACE_MS)
1979
+ return { reclaimed: false, refusal: "released_owner_within_grace" };
1890
1980
  } else if (await lockOwnerIsAlive(owner)) {
1891
1981
  // Only a host-qualified owner whose pid is PROVEN dead (ESRCH, or a live pid
1892
1982
  // with a provably different incarnation) authorizes reclaim. The generation
1893
1983
  // + exact-tree checks below then bind the removal to the very directory
1894
1984
  // inspected here, on every platform; a claim stranded by a force-quit
1895
1985
  // (`postmortem.quit`) would otherwise wall the session directory forever.
1896
- return false;
1986
+ return { reclaimed: false, refusal: "owner_live_or_unverifiable" };
1897
1987
  }
1898
1988
  const generation = transitionGenerationFromStat(stat);
1899
1989
  const nativePath = await canonicalOwnedTransitionPath(transitionDir).catch(() => null);
1900
- if (!nativePath) return false;
1990
+ if (!nativePath) return { reclaimed: false, refusal: "claim_capture_failed" };
1901
1991
  const captured = nativeSessionStateLock().snapshotDirectoryTree(nativePath);
1902
- if (!captured.ok || !captured.snapshot) return false;
1992
+ if (!captured.ok || !captured.snapshot) return { reclaimed: false, refusal: "claim_capture_failed" };
1903
1993
  const root = captured.snapshot.entries.find(entry => entry.relativePath === "");
1904
1994
  if (
1905
1995
  root?.kind !== "directory" ||
@@ -1909,12 +1999,15 @@ async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName
1909
1999
  root.mtimeNs !== String(generation.mtimeNs) ||
1910
2000
  root.ctimeNs !== String(generation.ctimeNs)
1911
2001
  )
1912
- return false;
1913
- if (captured.snapshot.entries.length !== 1) return false;
2002
+ return { reclaimed: false, refusal: "claim_generation_changed" };
2003
+ if (captured.snapshot.entries.length !== 1) return { reclaimed: false, refusal: "claim_not_empty" };
1914
2004
  const removed = nativeSessionStateLock().exactRemoveDirectoryTree(nativePath, captured.snapshot);
1915
2005
  if (removed.ok || removed.code === "not_found") {
1916
2006
  exactUnlinkOwnerRecord(`${transitionDir}.owner`, ownerSnapshot, quarantineName);
1917
- return removed.ok;
2007
+ return {
2008
+ reclaimed: removed.ok,
2009
+ ...(removed.ok ? { reclaimReason: owner.released === true ? "released_handoff" : "dead_owner" } : {}),
2010
+ };
1918
2011
  }
1919
2012
  if (
1920
2013
  removed.code === "cleanup_pending" &&
@@ -1931,7 +2024,7 @@ async function reclaimStaleTransitionClaim(transitionDir: string, quarantineName
1931
2024
  // successor populated the detached path.
1932
2025
  await removeTransitionDir(removed.detachedPath);
1933
2026
  exactUnlinkOwnerRecord(`${transitionDir}.owner`, ownerSnapshot, quarantineName);
1934
- return true;
2027
+ return { reclaimed: true, reclaimReason: owner.released === true ? "released_handoff" : "dead_owner" };
1935
2028
  }
1936
2029
  throw new SessionStateLockUnavailableError(
1937
2030
  new Error(`Stale transition claim could not be reclaimed (${removed.code ?? "unknown"}).`),
@@ -2310,6 +2403,7 @@ async function runLockPathTransition<T>(
2310
2403
  throw new SessionStateLockUnavailableError(new Error("Safe transition ownership is unsupported."));
2311
2404
  const transitionDir = `${lockFile}${LOCK_TRANSITION_RESOURCE_SUFFIX}`;
2312
2405
  const ownerFile = `${transitionDir}.owner`;
2406
+ let lastReclaimRefusal: TransitionReclaimRefusal | undefined;
2313
2407
 
2314
2408
  for (;;) {
2315
2409
  if (ownerAccessStrategy() === "unsupported" && fsSync.existsSync(transitionDir))
@@ -2332,18 +2426,37 @@ async function runLockPathTransition<T>(
2332
2426
  // Claims are held only for pathname bookkeeping, so a changed claim identity is
2333
2427
  // proof that contenders are cycling through rather than that one is wedged.
2334
2428
  const observedClaim = await observedLockHolderIdentity(transitionDir);
2335
- const reclaimed = await reclaimStaleTransitionClaim(transitionDir, quarantineName);
2336
- creditLockHandoffProgress(budget, observedClaim, reclaimed);
2429
+ const outcome = await reclaimStaleTransitionClaim(transitionDir, quarantineName);
2430
+ creditLockHandoffProgress(budget, observedClaim, outcome.reclaimed);
2431
+ // Remember the LAST refusal: on a wedged claim every retry refuses for the
2432
+ // same reason, and that reason is the actionable one. Carrying it onto the
2433
+ // timeout is the whole point — `transition_claim_timeout` alone cannot tell
2434
+ // a foreign-host tombstone from a live owner from an external writer
2435
+ // restamping the claim directory (#5606).
2436
+ if (outcome.refusal !== undefined) lastReclaimRefusal = outcome.refusal;
2337
2437
  // One safe reclaim may make progress, but a try-acquire never follows a
2338
2438
  // succession of contenders. A later maintenance pass can take the freed claim.
2339
2439
  if (budget.nonBlocking) throw SESSION_STATE_LOCK_BUSY;
2340
- if (reclaimed) {
2341
- if (lockRetryExhausted(budget))
2342
- throw lockUnavailable(transitionDir, "transition_claim_timeout", budget, error);
2440
+ if (outcome.reclaimed) {
2441
+ if (lockRetryExhausted(budget)) {
2442
+ // Only a released tombstone is the previous holder's explicit handoff.
2443
+ // Ordinary dead-owner reclaims remain bounded by the original deadline,
2444
+ // even though both reclaim paths unlink their owner sidecar.
2445
+ const ownerStillPresent = await fs.lstat(`${transitionDir}.owner`).then(
2446
+ () => true,
2447
+ error => (error as NodeJS.ErrnoException).code !== "ENOENT",
2448
+ );
2449
+ if (
2450
+ outcome.reclaimReason !== "released_handoff" ||
2451
+ ownerStillPresent ||
2452
+ lockRetryTotalWaitExhausted(budget)
2453
+ )
2454
+ throw transitionClaimTimeout(transitionDir, budget, error, lastReclaimRefusal);
2455
+ }
2343
2456
  continue;
2344
2457
  }
2345
2458
  if (!(await waitForLockRetry(budget)))
2346
- throw lockUnavailable(transitionDir, "transition_claim_timeout", budget, error);
2459
+ throw transitionClaimTimeout(transitionDir, budget, error, lastReclaimRefusal);
2347
2460
  continue;
2348
2461
  }
2349
2462
  const pendingSetup: PendingTransitionRelease = {
@@ -2753,6 +2866,34 @@ async function observedLockHolderIdentity(lockPath: string): Promise<string | un
2753
2866
  return `d:${stat.dev}:${stat.ino}:${stat.ctimeNs}`;
2754
2867
  }
2755
2868
 
2869
+ /**
2870
+ * Classify one stale-transition reclaim attempt.
2871
+ *
2872
+ * @internal exported as the seam the refusal classifier is tested through. Driving
2873
+ * a real `transition_claim_timeout` would mean waiting out `LOCK_ACQUIRE_MAX_WAIT_MS`
2874
+ * (60s) per case, and `tryWithSessionStateFileLock` throws BUSY before the timeout
2875
+ * is ever built, so the per-branch reasons have no other reachable surface (#5606).
2876
+ */
2877
+ export async function classifyTransitionReclaimForTests(
2878
+ transitionDir: string,
2879
+ quarantineName: string,
2880
+ ): Promise<TransitionReclaimOutcome> {
2881
+ return reclaimStaleTransitionClaim(transitionDir, quarantineName);
2882
+ }
2883
+
2884
+ /**
2885
+ * Build the timeout error exactly as the transition loop does.
2886
+ *
2887
+ * @internal exported so the message contract — a bounded enum suffix and nothing
2888
+ * else — is pinned without a 60s wait (#5606).
2889
+ */
2890
+ export function transitionClaimTimeoutForTests(
2891
+ transitionDir: string,
2892
+ refusal: TransitionReclaimRefusal | undefined,
2893
+ ): SessionStateLockUnavailableError {
2894
+ return transitionClaimTimeout(transitionDir, lockRetryBudget(), new Error("EEXIST"), refusal);
2895
+ }
2896
+
2756
2897
  /**
2757
2898
  * Decide what actually occupies the lock path, without following it.
2758
2899
  *