@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
@@ -27,6 +27,7 @@ export interface FileLockOptions {
27
27
  retryDelayMs?: number;
28
28
  signal?: AbortSignal;
29
29
  onAcquired?: () => void;
30
+ onContended?: () => void;
30
31
  /** Stable host identity required to safely reclaim locks on a shared volume. */
31
32
  ownerHostId?: string;
32
33
  /** Previous local host identities accepted only when deciding stale-owner reclamation. */
@@ -84,13 +85,22 @@ export function isFileLockAcquireTimeout(error: unknown): error is FileLockAcqui
84
85
  }
85
86
 
86
87
  const DEFAULT_OPTIONS: Required<
87
- Omit<FileLockOptions, "ownerHostId" | "previousOwnerHostIds" | "signal" | "onAcquired">
88
+ Omit<FileLockOptions, "ownerHostId" | "previousOwnerHostIds" | "signal" | "onAcquired" | "onContended">
88
89
  > = {
89
90
  staleMs: 10_000,
90
91
  retries: 50,
91
92
  retryDelayMs: 100,
92
93
  };
93
94
 
95
+ /**
96
+ * Windows can transiently deny a no-replace publication while another handle still
97
+ * has the staged or destination path open without delete sharing. The native result
98
+ * is explicitly pre-mutation in this case, so retrying the same source name cannot
99
+ * publish twice or clean up a committed namespace change.
100
+ */
101
+ const PUBLICATION_SHARING_RETRY_ATTEMPTS = 3;
102
+ const PUBLICATION_SHARING_RETRY_DELAY_MS = 10;
103
+
94
104
  /** Release retries cover transient handle denial and a competing exact-removal quarantine cleanup. */
95
105
  export const FILE_LOCK_RELEASE_RETRY_ATTEMPTS = 20;
96
106
  export const FILE_LOCK_RELEASE_RETRY_DELAY_MS = 25;
@@ -231,6 +241,7 @@ type LockInfoFileState = {
231
241
  size: bigint;
232
242
  mtimeNs: bigint;
233
243
  ctimeNs: bigint;
244
+ birthtimeNs: bigint;
234
245
  nlink: bigint;
235
246
  };
236
247
 
@@ -245,6 +256,10 @@ type LockInfoPathState = {
245
256
 
246
257
  function lockInfoFileState(stats: BigIntStats): LockInfoFileState | null {
247
258
  if (stats.isSymbolicLink() || !stats.isFile()) return null;
259
+ // Some supported filesystems report creation time as missing or epoch zero. Keep
260
+ // the stable dev/inode identity and content evidence usable instead of wedging
261
+ // acquire/release/GC on a metadata field the filesystem cannot provide.
262
+ const birthtimeNs = typeof stats.birthtimeNs === "bigint" && stats.birthtimeNs > 0n ? stats.birthtimeNs : 0n;
248
263
  return {
249
264
  dev: stats.dev,
250
265
  ino: stats.ino,
@@ -252,18 +267,21 @@ function lockInfoFileState(stats: BigIntStats): LockInfoFileState | null {
252
267
  size: stats.size,
253
268
  mtimeNs: stats.mtimeNs,
254
269
  ctimeNs: stats.ctimeNs,
270
+ birthtimeNs,
255
271
  nlink: stats.nlink,
256
272
  };
257
273
  }
258
274
 
259
275
  function sameLockInfoFileState(left: LockInfoFileState, right: LockInfoFileState): boolean {
276
+ const birthtimeMatches =
277
+ left.birthtimeNs === 0n || right.birthtimeNs === 0n || left.birthtimeNs === right.birthtimeNs;
260
278
  return (
261
279
  left.dev === right.dev &&
262
280
  left.ino === right.ino &&
263
281
  left.mode === right.mode &&
264
282
  left.size === right.size &&
265
283
  left.mtimeNs === right.mtimeNs &&
266
- left.ctimeNs === right.ctimeNs &&
284
+ birthtimeMatches &&
267
285
  left.nlink === right.nlink
268
286
  );
269
287
  }
@@ -312,6 +330,7 @@ function fileLockDirIdentityFromPathState(state: LockInfoPathState, bytes: strin
312
330
  infoSize: String(state.file.size),
313
331
  infoMtimeNs: String(state.file.mtimeNs),
314
332
  infoCtimeNs: String(state.file.ctimeNs),
333
+ infoBirthtimeNs: String(state.file.birthtimeNs),
315
334
  infoSha256: crypto.createHash("sha256").update(bytes).digest("hex"),
316
335
  };
317
336
  }
@@ -679,6 +698,8 @@ function sameFileLockTreeAfterPublication(
679
698
  staged: NativeDirectoryTreeSnapshot,
680
699
  published: NativeDirectoryTreeSnapshot,
681
700
  ): boolean {
701
+ // ctime is intentionally omitted: native snapshots expose no birthtime, and a
702
+ // metadata-only change must not reject an otherwise identical published tree.
682
703
  return (
683
704
  staged.rootDev === published.rootDev &&
684
705
  staged.rootIno === published.rootIno &&
@@ -694,7 +715,6 @@ function sameFileLockTreeAfterPublication(
694
715
  entry.nlink === current.nlink &&
695
716
  entry.size === current.size &&
696
717
  entry.mtimeNs === current.mtimeNs &&
697
- (entry.relativePath === "" || entry.ctimeNs === current.ctimeNs) &&
698
718
  entry.sha256 === current.sha256
699
719
  );
700
720
  })
@@ -878,6 +898,7 @@ function isValidNativeNoReplaceResult(value: unknown): value is NativeNoReplaceR
878
898
  "interrupted",
879
899
  "identity_violation",
880
900
  "durability_not_provable",
901
+ "sharing_violation",
881
902
  "unknown",
882
903
  ] as const
883
904
  ).includes(value.reason as never) ||
@@ -953,25 +974,18 @@ function isValidNativeNoReplaceResult(value: unknown): value is NativeNoReplaceR
953
974
  function isSuccessfulNativePublication(value: unknown, operation: "primary" | "directory"): boolean {
954
975
  if (!isValidNativeNoReplaceResult(value) || !value.ok) return false;
955
976
  if (operation === "directory") return value.primitive === "mkdirat_renameat_noreplace";
956
- switch (process.platform) {
957
- case "linux":
958
- return value.primitive === "renameat2_noreplace";
959
- case "darwin":
960
- return value.primitive === "renameatx_np_excl";
961
- case "win32":
962
- return value.primitive === "windows_rename_noreplace";
963
- default:
964
- return false;
965
- }
977
+ // Native code records the primitive it actually invoked in the receipt. That
978
+ // primitive is the producer-platform evidence; process.platform is only ambient
979
+ // capability metadata and may be overridden by a coordinator discovery test.
980
+ return (
981
+ value.primitive === "renameat2_noreplace" ||
982
+ value.primitive === "renameatx_np_excl" ||
983
+ value.primitive === "windows_rename_noreplace"
984
+ );
966
985
  }
967
986
 
968
987
  function isCommittedDirectoryVerificationFailure(value: unknown): value is NativeNoReplaceResult {
969
- return (
970
- process.platform === "linux" &&
971
- isValidNativeNoReplaceResult(value) &&
972
- !value.ok &&
973
- value.mutationState === "committed"
974
- );
988
+ return isValidNativeNoReplaceResult(value) && !value.ok && value.mutationState === "committed";
975
989
  }
976
990
 
977
991
  /**
@@ -995,6 +1009,35 @@ function isPreMutationUnsupportedRenameResult(value: unknown): value is NativeNo
995
1009
  return true;
996
1010
  }
997
1011
 
1012
+ function isPreMutationSharingViolation(value: unknown): value is NativeNoReplaceResult {
1013
+ return (
1014
+ isValidNativeNoReplaceResult(value) &&
1015
+ !value.ok &&
1016
+ value.code === "sharing_violation" &&
1017
+ value.mutationState === "not_committed" &&
1018
+ value.durabilityState === "not_attempted" &&
1019
+ value.reason === "sharing_violation" &&
1020
+ value.phase === "rename"
1021
+ );
1022
+ }
1023
+
1024
+ async function publishNoReplaceWithSharingRetry(
1025
+ publish: (sourcePath: string, destinationPath: string) => Promise<NativeNoReplaceResult>,
1026
+ sourcePath: string,
1027
+ destinationPath: string,
1028
+ ): Promise<NativeNoReplaceResult> {
1029
+ let result = await publish(sourcePath, destinationPath);
1030
+ for (
1031
+ let attempt = 1;
1032
+ attempt < PUBLICATION_SHARING_RETRY_ATTEMPTS && isPreMutationSharingViolation(result);
1033
+ attempt++
1034
+ ) {
1035
+ await Bun.sleep(PUBLICATION_SHARING_RETRY_DELAY_MS * attempt);
1036
+ result = await publish(sourcePath, destinationPath);
1037
+ }
1038
+ return result;
1039
+ }
1040
+
998
1041
  async function localLockKey(lockPath: string): Promise<string> {
999
1042
  try {
1000
1043
  return normalizeLockKey(await fs.realpath(lockPath));
@@ -1033,7 +1076,14 @@ type LockStaleSnapshot =
1033
1076
  | { stale: false }
1034
1077
  | { stale: true; owner: FileLockOwnerToken; identity: GenericFileLockDirIdentity };
1035
1078
 
1036
- /** Identity evidence carried by the generic stale verdict into a later removal. */
1079
+ /**
1080
+ * Identity evidence carried by the generic stale verdict into a later removal.
1081
+ *
1082
+ * `ctimeNs` is a mutable metadata-change time on every supported filesystem, so
1083
+ * it is retained for diagnostics/legacy consumers but is not part of the lock
1084
+ * identity predicate. `infoBirthtimeNs` is the creation time that stays bound
1085
+ * to the file object.
1086
+ */
1037
1087
  export interface GenericFileLockDirIdentity {
1038
1088
  rootDev: string;
1039
1089
  rootIno: string;
@@ -1043,23 +1093,34 @@ export interface GenericFileLockDirIdentity {
1043
1093
  infoSize: string;
1044
1094
  infoMtimeNs: string;
1045
1095
  infoCtimeNs: string;
1096
+ infoBirthtimeNs: string;
1046
1097
  infoSha256: string;
1047
1098
  }
1048
1099
 
1049
- function sameGenericFileLockDirIdentity(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
1100
+ function sameStableFileLockIdentity(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
1050
1101
  return (
1051
1102
  left.rootDev === right.rootDev &&
1052
1103
  left.rootIno === right.rootIno &&
1053
1104
  left.infoDev === right.infoDev &&
1054
1105
  left.infoIno === right.infoIno &&
1106
+ left.infoBirthtimeNs === right.infoBirthtimeNs
1107
+ );
1108
+ }
1109
+
1110
+ /** Content/topology evidence kept separate from the stable file identity. */
1111
+ function sameFileLockContentEvidence(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
1112
+ return (
1055
1113
  left.infoNlink === right.infoNlink &&
1056
1114
  left.infoSize === right.infoSize &&
1057
1115
  left.infoMtimeNs === right.infoMtimeNs &&
1058
- left.infoCtimeNs === right.infoCtimeNs &&
1059
1116
  left.infoSha256 === right.infoSha256
1060
1117
  );
1061
1118
  }
1062
1119
 
1120
+ function sameGenericFileLockDirIdentity(left: GenericFileLockDirIdentity, right: GenericFileLockDirIdentity): boolean {
1121
+ return sameStableFileLockIdentity(left, right) && sameFileLockContentEvidence(left, right);
1122
+ }
1123
+
1063
1124
  let nativeExactRemovalUsable: boolean | undefined;
1064
1125
  let nativeExactRemovalProbePromise: Promise<boolean> | undefined;
1065
1126
 
@@ -1170,26 +1231,54 @@ async function removeVerifiedOwnedLockDirWithoutNative(
1170
1231
  return "cleanup_failed";
1171
1232
  }
1172
1233
  const current = await captureFileLockDirIdentity(lockDir);
1173
- if (!current || !sameGenericFileLockDirIdentity(current, expected)) return "owner_changed";
1234
+ if (!current || !sameStableFileLockIdentity(current, expected)) return "owner_changed";
1174
1235
  const captured = nativeFileLockBindings().snapshotDirectoryTree(lockDir);
1175
1236
  if (!captured.ok || !captured.snapshot) return "owner_changed";
1176
1237
  const infoEntry = captured.snapshot.entries.find(entry => entry.relativePath === "info");
1177
1238
  if (
1178
- captured.snapshot.rootDev !== expected.rootDev ||
1179
- captured.snapshot.rootIno !== expected.rootIno ||
1180
1239
  !infoEntry ||
1181
- infoEntry.dev !== expected.infoDev ||
1182
- infoEntry.ino !== expected.infoIno ||
1183
- infoEntry.nlink !== expected.infoNlink ||
1184
- infoEntry.size !== expected.infoSize ||
1185
- infoEntry.mtimeNs !== expected.infoMtimeNs ||
1186
- infoEntry.ctimeNs !== expected.infoCtimeNs ||
1187
- infoEntry.sha256 !== expected.infoSha256
1240
+ !nativeFileLockInfoMatchesStableIdentity(
1241
+ captured.snapshot.rootDev,
1242
+ captured.snapshot.rootIno,
1243
+ infoEntry,
1244
+ expected,
1245
+ ) ||
1246
+ !nativeFileLockInfoMatchesContentEvidence(infoEntry, expected)
1188
1247
  )
1189
1248
  return "owner_changed";
1190
1249
  return await removeVerifiedLockDirWithoutNative(lockDir, captured.snapshot, owner);
1191
1250
  }
1192
1251
 
1252
+ function nativeFileLockInfoMatchesStableIdentity(
1253
+ rootDev: string,
1254
+ rootIno: string,
1255
+ infoEntry: NativeDirectoryTreeSnapshot["entries"][number],
1256
+ expected: GenericFileLockDirIdentity,
1257
+ ): boolean {
1258
+ return (
1259
+ rootDev === expected.rootDev &&
1260
+ rootIno === expected.rootIno &&
1261
+ infoEntry.dev === expected.infoDev &&
1262
+ infoEntry.ino === expected.infoIno
1263
+ );
1264
+ }
1265
+
1266
+ function nativeFileLockInfoMatchesContentEvidence(
1267
+ infoEntry: NativeDirectoryTreeSnapshot["entries"][number],
1268
+ expected: GenericFileLockDirIdentity,
1269
+ ): boolean {
1270
+ // Native snapshots expose a ctime-shaped field, but ctime is a mutable
1271
+ // metadata-change timestamp on every supported filesystem. The stable file
1272
+ // identity is checked separately; requiring ctime here would reject a
1273
+ // legitimate lock after chmod/ACL/indexer activity.
1274
+ return (
1275
+ infoEntry.nlink === expected.infoNlink &&
1276
+ infoEntry.size === expected.infoSize &&
1277
+ infoEntry.mtimeNs === expected.infoMtimeNs &&
1278
+ infoEntry.sha256 === expected.infoSha256
1279
+ );
1280
+ }
1281
+
1193
1282
  export type GenericFileLockDirStaleVerdict = { stale: false } | { stale: true; identity: GenericFileLockDirIdentity };
1194
1283
 
1195
1284
  /**
@@ -1253,17 +1342,17 @@ export async function removeFileLockDirForGc(
1253
1342
  if (!infoEntry?.sha256) return "owner_changed";
1254
1343
  const judgedDigest = crypto.createHash("sha256").update(onDiskBytes).digest("hex");
1255
1344
  if (infoEntry.sha256 !== judgedDigest) return "owner_changed";
1345
+ const currentIdentity = await captureFileLockDirIdentity(nativeCapturePath);
1346
+ if (!currentIdentity || !sameStableFileLockIdentity(currentIdentity, expectedIdentity)) return "owner_changed";
1256
1347
  if (
1257
1348
  !captured.snapshot.rootDev ||
1258
- captured.snapshot.rootDev !== expectedIdentity.rootDev ||
1259
- captured.snapshot.rootIno !== expectedIdentity.rootIno ||
1260
- infoEntry.dev !== expectedIdentity.infoDev ||
1261
- infoEntry.ino !== expectedIdentity.infoIno ||
1262
- infoEntry.nlink !== expectedIdentity.infoNlink ||
1263
- infoEntry.size !== expectedIdentity.infoSize ||
1264
- infoEntry.mtimeNs !== expectedIdentity.infoMtimeNs ||
1265
- infoEntry.ctimeNs !== expectedIdentity.infoCtimeNs ||
1266
- infoEntry.sha256 !== expectedIdentity.infoSha256
1349
+ !nativeFileLockInfoMatchesStableIdentity(
1350
+ captured.snapshot.rootDev,
1351
+ captured.snapshot.rootIno,
1352
+ infoEntry,
1353
+ expectedIdentity,
1354
+ ) ||
1355
+ !nativeFileLockInfoMatchesContentEvidence(infoEntry, expectedIdentity)
1267
1356
  )
1268
1357
  return "owner_changed";
1269
1358
  let removed: NativeExactUnlinkResult;
@@ -1363,6 +1452,28 @@ function ownerLiveness(pid: number): OwnerLiveness {
1363
1452
  }
1364
1453
  }
1365
1454
 
1455
+ /**
1456
+ * Does this owner record belong to a machine other than the acquirer's?
1457
+ *
1458
+ * A host-qualified record is foreign unless the acquirer can prove it is its own:
1459
+ * either the identity matches, or it matches an identity this installation used
1460
+ * before. An acquirer carrying no host identity can prove nothing, so every
1461
+ * host-qualified record stays foreign to it. Foreign records fail closed — their
1462
+ * PID values and clocks are not meaningful here, so they are neither reclaimable
1463
+ * nor locally probeable for liveness.
1464
+ *
1465
+ * Shared by the reclamation verdict and the exhaustion diagnostic so the two can
1466
+ * never disagree about whose pid a record names.
1467
+ */
1468
+ function lockRecordIsForeignHost(
1469
+ info: LockInfo,
1470
+ ownerHostId: string | undefined,
1471
+ previousOwnerHostIds: readonly string[],
1472
+ ): boolean {
1473
+ if (ownerHostId === undefined) return info.owner_host_id !== undefined;
1474
+ return info.owner_host_id !== ownerHostId && !previousOwnerHostIds.includes(info.owner_host_id ?? "");
1475
+ }
1476
+
1366
1477
  async function staleLockSnapshot(
1367
1478
  lockPath: string,
1368
1479
  _staleMs: number,
@@ -1402,13 +1513,7 @@ async function staleLockSnapshot(
1402
1513
  // A host-qualified lock may only be reclaimed after proving that its owner is
1403
1514
  // local. Foreign and malformed host-qualified records fail closed: PID values
1404
1515
  // and clocks are not meaningful across hosts.
1405
- if (
1406
- ownerHostId !== undefined &&
1407
- info.owner_host_id !== ownerHostId &&
1408
- !previousOwnerHostIds.includes(info.owner_host_id ?? "")
1409
- )
1410
- return { stale: false };
1411
- if (ownerHostId === undefined && info.owner_host_id !== undefined) return { stale: false };
1516
+ if (lockRecordIsForeignHost(info, ownerHostId, previousOwnerHostIds)) return { stale: false };
1412
1517
  if (ownerIncarnationChanged(info, startTimeCache)) {
1413
1518
  if (!judgedIdentity) return { stale: false };
1414
1519
  let currentIdentity: GenericFileLockDirIdentity | null;
@@ -1619,12 +1724,20 @@ async function tryAcquireLock(
1619
1724
  renameNoReplacePathAsync,
1620
1725
  renameDirectoryNoReplacePathAsync,
1621
1726
  };
1622
- const published = await publication.renameNoReplacePathAsync(canonicalPendingPath, destinationPath);
1727
+ const published = await publishNoReplaceWithSharingRetry(
1728
+ publication.renameNoReplacePathAsync,
1729
+ canonicalPendingPath,
1730
+ destinationPath,
1731
+ );
1623
1732
  let publishedSuccessfully = isSuccessfulNativePublication(published, "primary");
1624
1733
  if (published.ok && !publishedSuccessfully)
1625
1734
  throw new Error("Failed to publish file lock: invalid primary success receipt.");
1626
1735
  if (!published.ok && isPreMutationUnsupportedRenameResult(published)) {
1627
- const fallback = await publication.renameDirectoryNoReplacePathAsync(canonicalPendingPath, destinationPath);
1736
+ const fallback = await publishNoReplaceWithSharingRetry(
1737
+ publication.renameDirectoryNoReplacePathAsync,
1738
+ canonicalPendingPath,
1739
+ destinationPath,
1740
+ );
1628
1741
  const fallbackSuccessfully = isSuccessfulNativePublication(fallback, "directory");
1629
1742
  if (fallback.ok && !fallbackSuccessfully)
1630
1743
  throw new Error("Failed to publish file lock: invalid directory success receipt.");
@@ -1783,16 +1896,16 @@ async function quarantineReleasedLock(
1783
1896
  if (!captured.ok || !captured.snapshot) return false;
1784
1897
  const infoEntry = captured.snapshot.entries.find(entry => entry.relativePath === "info");
1785
1898
  if (!infoEntry?.sha256) return false;
1899
+ const currentIdentity = await captureFileLockDirIdentity(nativeCapturePath);
1900
+ if (!currentIdentity || !sameStableFileLockIdentity(currentIdentity, expectedIdentity)) return false;
1786
1901
  if (
1787
- captured.snapshot.rootDev !== expectedIdentity.rootDev ||
1788
- captured.snapshot.rootIno !== expectedIdentity.rootIno ||
1789
- infoEntry.dev !== expectedIdentity.infoDev ||
1790
- infoEntry.ino !== expectedIdentity.infoIno ||
1791
- infoEntry.nlink !== expectedIdentity.infoNlink ||
1792
- infoEntry.size !== expectedIdentity.infoSize ||
1793
- infoEntry.mtimeNs !== expectedIdentity.infoMtimeNs ||
1794
- infoEntry.ctimeNs !== expectedIdentity.infoCtimeNs ||
1795
- infoEntry.sha256 !== expectedIdentity.infoSha256
1902
+ !nativeFileLockInfoMatchesStableIdentity(
1903
+ captured.snapshot.rootDev,
1904
+ captured.snapshot.rootIno,
1905
+ infoEntry,
1906
+ expectedIdentity,
1907
+ ) ||
1908
+ !nativeFileLockInfoMatchesContentEvidence(infoEntry, expectedIdentity)
1796
1909
  )
1797
1910
  return false;
1798
1911
  // Bind the owner generation to the snapshot before exact removal. A successor
@@ -1962,14 +2075,22 @@ async function lockHolderDescription(
1962
2075
  info = bytes === null ? null : parseLockInfoBytes(bytes);
1963
2076
  }
1964
2077
  if (info) {
1965
- // A lock record carrying a foreign owner_host_id belongs to another
2078
+ // A lock record carrying a FOREIGN owner_host_id belongs to another
1966
2079
  // machine (shared-volume topic registry): its pid is meaningful only
1967
2080
  // on that host, so probing the same numeric pid here could mislabel a
1968
2081
  // coincident local process as the holder. Report the owner host with
1969
- // unknown liveness instead.
1970
- if (info.owner_host_id !== undefined) {
2082
+ // unknown liveness instead. The same predicate that decides whether
2083
+ // the record is reclaimable decides whether its pid is probeable, so a
2084
+ // holder this acquirer *may* reclaim is never described as opaque.
2085
+ if (lockRecordIsForeignHost(info, ownerHostId, previousOwnerHostIds)) {
2086
+ // An unqualified record carries no host provenance at all. A host-aware
2087
+ // acquirer still fails closed on it — its pid is never probed and its
2088
+ // lock is never reclaimed — but the diagnostic must say the provenance
2089
+ // is missing instead of naming a host it does not have.
2090
+ const provenance =
2091
+ info.owner_host_id === undefined ? "on an unrecorded host" : `on host ${info.owner_host_id}`;
1971
2092
  return (
1972
- `held by pid ${info.pid} on host ${info.owner_host_id} (liveness unknown from this host)` +
2093
+ `held by pid ${info.pid} ${provenance} (liveness unknown from this host)` +
1973
2094
  ` since ${new Date(info.timestamp).toISOString()}`
1974
2095
  );
1975
2096
  }
@@ -1977,13 +2098,18 @@ async function lockHolderDescription(
1977
2098
  // record carries a start_time, the start-time identity match) so a dead
1978
2099
  // holder whose pid was already reused is not mislabeled "(live)".
1979
2100
  const alive = ownerIsAlive(info);
2101
+ const liveness = alive
2102
+ ? "live"
2103
+ : ownerLiveness(info.pid) === "dead"
2104
+ ? "dead but not reaped"
2105
+ : "liveness unknown";
1980
2106
  return (
1981
2107
  `held by pid ${info.pid}` +
1982
- (alive
1983
- ? " (live)"
1984
- : ownerLiveness(info.pid) === "dead"
1985
- ? " (dead but not reaped)"
1986
- : " (liveness unknown)") +
2108
+ // Keep the host on a host-qualified local record: on a shared volume the
2109
+ // same pathname is contended from several hosts, so naming the one whose
2110
+ // pid space this verdict came from is what makes it actionable.
2111
+ (info.owner_host_id === undefined ? "" : " on this host") +
2112
+ ` (${liveness})` +
1987
2113
  ` since ${new Date(info.timestamp).toISOString()}`
1988
2114
  );
1989
2115
  }
@@ -2022,6 +2148,7 @@ export async function acquireFileLock(filePath: string, options: FileLockOptions
2022
2148
  }
2023
2149
  const ownerToken = crypto.randomUUID();
2024
2150
  const contentionStartTimes = new Map<string, string | null>();
2151
+ let contentionObserved = false;
2025
2152
  let staleRemovalFailure: RecordedStaleRemovalFailure | undefined;
2026
2153
  for (let attempt = 0; attempt < opts.retries; attempt++) {
2027
2154
  if (opts.signal?.aborted) throw opts.signal.reason ?? new Error("File lock acquisition aborted");
@@ -2052,6 +2179,10 @@ export async function acquireFileLock(filePath: string, options: FileLockOptions
2052
2179
  localLockStates.set(localKey, { owner: result, status: "held" });
2053
2180
  return () => releaseLock(lockPath, result, localKey);
2054
2181
  }
2182
+ if (!contentionObserved) {
2183
+ contentionObserved = true;
2184
+ opts.onContended?.();
2185
+ }
2055
2186
  const pendingKey = await pendingLocalReleaseKey(lockPath, localKey);
2056
2187
  const localState = localLockStates.get(pendingKey ?? localKey);
2057
2188
  if (pendingKey !== undefined && localState?.status !== "held" && localState?.owner.owner_token !== undefined) {