chainlesschain 0.165.1 → 0.165.3

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 (206) hide show
  1. package/README.md +248 -2
  2. package/package.json +4 -1
  3. package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-BiHNUlbU.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-D-UFjDRQ.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-DazVsDgW.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-DXh9UfNJ.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-CZ6tsceY.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-RyxnBr_h.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BWS51Q6X.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-BRtPcUul.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-CGd4vZYY.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-h8ijVwCg.js} +4 -4
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-D7tcdDS9.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-NyF6dPl8.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-Ci4OOxr5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-CwTkc9BM.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-DW_tM82m.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-DEvHRV4S.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BbjBEEfD.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-10IXBaBa.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-BZkbpd5I.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-CFQ8L_6v.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-BDF6AkXe.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-CoOdWMi4.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-DEsGmu8Y.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-BS89SWlK.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-C1FpCHiI.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-BzvpVmLA.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-BeC259PQ.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-azgG3bmV.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-BmSSowc5.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-CTBCSyQZ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-DA5xyk4a.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-D1gPHNig.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-BPmLf1AR.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-SVAgNnkt.js → MarkdownRenderer-SVLOg49H.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-DtUFcR0E.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-DS_kqBv-.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-Fvh7XL7V.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-C7itAP45.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-DGbP36-3.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B57MiNLK.js} +6 -6
  43. package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-BXfCUcM7.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-yaJ-ZcF6.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-CoaUCKrO.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-BKLM_65R.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-DoDtK9uZ.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-BJ28TGr-.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-MuGsdoIb.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-BLUN9J4R.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-DWuoBUPd.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-CUi37YPj.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-BYQg2pcr.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-CAxoV2xu.js} +6 -6
  55. package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-B33wGvIO.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-A8yXTORP.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-D49I2I1O.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-Ci73jJDw.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-CF-yD6Cn.js +1 -0
  60. package/src/assets/web-panel/assets/Providers-D-etEhg5.js +1 -0
  61. package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-Brza0ykJ.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-DFydhjxL.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-DLF8_NhA.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-dyuStW3x.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-5F3tA6yw.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-Ca2A57S5.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-B0bxBkq_.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-CFNvHalZ.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-DSoH3Hav.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-BHpfEmBQ.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-Dsh-QNmn.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-BhT1NkSU.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-DY9snPeJ.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-B5pLcXUw.js → SpeechSettings-CWaX0_Cx.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-B690y_TC.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-2N-w3zWd.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-BjwQOQYC.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-CUqHErxY.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-C13rXvNy.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-CpbWcya_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BXHLGXYC.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-1e5i65o3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-FLGqNHs5.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-BbaR_LJB.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-NOp4RIov.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-B7DW7bG4.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-BptSUWix.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CAJnDOXS.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-I8LgG1-Z.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-CWe7SOVD.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-Cx3u3CA3.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-CgD5ArwY.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DLv_872g.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-BfQWiXN8.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-3iKP9OTJ.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-87gMxqhN.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-B1cX4Rxu.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-B6yChzt1.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BREnPKGo.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-BUAR7RY-.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-Bi24S7Dw.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-BiI-xPRH.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-BuEQSorC.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-Bx7BNIRH.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-C-rIKccl.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-C0nDKhWv.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-C0ztiqtB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-C1UjHdgZ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-CAUnl4Jv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-CMu16vPC.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-CNYFQxAW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-CQDKYbnv.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-CWZ3REg6.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-CcX4FSPf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-Cd79NQAx.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-CdRkCwm4.js} +1 -1
  117. package/src/assets/web-panel/assets/index-CdiGCibe.js +1 -0
  118. package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-Cy_cCJlr.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-DAVy98u7.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-DCJEY2T0.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-DFb-XIKy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-DGMYTN5W.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_0z-gyz.js +1 -0
  124. package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-Dcdm_Z2c.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-DeizoQVZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DlIyt1qF.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-DmgtY58H.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-DmrdUwzU.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-Doc0Ftpd.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-DsQcURkA.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-DslTuqro.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-V-kIHzAV.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-e8BM1yY1.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-DiBszVdG.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-BGIFFozv.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-D5W7mp7B.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-JFIlxJ7F.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-CQ3o6LIU.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-R8MIlXo3.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-sW7ylSky.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cnl39Vbz.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-sn8tFd14.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-IKKY_QxZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-BNpV4Nqs.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-BWyT_eoU.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-D09p6_U9.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-CzW3TkJZ.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-3s3WoAKB.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-Cjy_673s.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/cowork.js +280 -5
  153. package/src/commands/plugin.js +819 -64
  154. package/src/commands/session-location.js +318 -7
  155. package/src/constants.js +2 -2
  156. package/src/data/changelog.json +27 -1
  157. package/src/gateways/ws/action-protocol.js +1 -1
  158. package/src/harness/jsonl-session-store.js +638 -2
  159. package/src/harness/prompt-compressor.js +4 -0
  160. package/src/lib/background-agent-keeper-client.js +29 -0
  161. package/src/lib/background-agent-keeper-protocol.js +78 -3
  162. package/src/lib/background-agent-launch-barrier.js +74 -0
  163. package/src/lib/background-agent-supervisor.js +208 -34
  164. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  165. package/src/lib/cowork-task-runner.js +70 -0
  166. package/src/lib/cowork-workflow.js +42 -9
  167. package/src/lib/doctor-checkup.js +34 -10
  168. package/src/lib/dynamic-workflow-draft.js +535 -0
  169. package/src/lib/dynamic-workflow-facade.js +11 -11
  170. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  171. package/src/lib/execution-location-contract.js +111 -7
  172. package/src/lib/execution-location-runtime.js +3 -1
  173. package/src/lib/execution-location-target.js +910 -0
  174. package/src/lib/llm-providers.js +1 -0
  175. package/src/lib/mcp-call-ledger-store.js +35 -0
  176. package/src/lib/mcp-call-ledger.js +30 -0
  177. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  178. package/src/lib/plugin-runtime/install.js +4956 -657
  179. package/src/lib/plugin-runtime/manifest.js +3 -1
  180. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  181. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  182. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  183. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  184. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  185. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  186. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  187. package/src/lib/plugin-runtime/policy.js +21 -6
  188. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  189. package/src/lib/plugin-runtime/remote-source.js +323 -27
  190. package/src/lib/plugin-runtime/scopes.js +317 -41
  191. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  192. package/src/lib/public-ide-capability-manifest.js +1 -1
  193. package/src/lib/remote-membership-coordinator.js +8 -6
  194. package/src/lib/sub-agent-context.js +268 -2
  195. package/src/lib/task-model-selector.js +1 -1
  196. package/src/lib/with-file-lock.js +266 -27
  197. package/src/runtime/agent-core.js +595 -41
  198. package/src/workers/background-agent-keeper.js +147 -24
  199. package/src/workers/background-agent-worker.js +383 -134
  200. package/src/workers/background-turn-bootstrap.js +13 -8
  201. package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +0 -1
  202. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
  203. package/src/assets/web-panel/assets/Providers-CUnHbNf-.js +0 -1
  204. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
  205. package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
  206. package/src/assets/web-panel/assets/index-xYybvEic.js +0 -1
@@ -14,14 +14,18 @@
14
14
  * reclaimed merely because the directory is old, and corrupt ownership fails
15
15
  * closed. The default filesystem publishes a fully populated, uniquely staged
16
16
  * directory in one rename so a process killed during acquisition cannot expose
17
- * an ownerless lock. Confirmed-dead owners are reclaimed with an exact-token
18
- * marker inside the lock directory so a delayed contender cannot delete a
19
- * replacement lock. Strict callers may use bounded jittered retries and an
17
+ * an ownerless lock. Confirmed-dead owners are reclaimed with an exact-token,
18
+ * process-owned claim inside the lock directory so a delayed contender cannot
19
+ * delete a replacement lock and another contender can take over if the first
20
+ * reclaimer is killed. Strict callers may use bounded jittered retries and an
20
21
  * after-release yield to avoid Windows sharing transients and fixed-interval
21
22
  * lock convoys.
22
23
  *
23
24
  * @param {string} targetPath the file being guarded (lock is `${targetPath}.lock`)
24
- * @param {(ctx:{locked:boolean})=>T} fn critical section; receives whether the lock was held
25
+ * @param {(ctx:{locked:boolean,publishReleaseAfterPathRemoved:(pendingPath:string)=>boolean})=>T} fn
26
+ * critical section; `publishReleaseAfterPathRemoved` lets a transaction
27
+ * publish an exact, contender-completable handoff before its final atomic
28
+ * rename removes a uniquely named staging path
25
29
  * @returns {T} whatever `fn` returns
26
30
  */
27
31
  export function withFileLock(targetPath, fn, opts = {}) {
@@ -54,7 +58,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
54
58
  };
55
59
  let held = false;
56
60
  let acquisitionError = null;
61
+ let lastOwnerObservation = null;
57
62
  let retryAttempt = 0;
63
+ let releasePublished = false;
58
64
  const deadline = _now() + timeoutMs;
59
65
 
60
66
  for (;;) {
@@ -88,10 +94,44 @@ export function withFileLock(targetPath, fn, opts = {}) {
88
94
  const incumbent = ownerRead.owner;
89
95
  acquisitionError = ownerRead.error;
90
96
  if (incumbent) {
91
- if (!isOwnerAlive(incumbent)) {
92
- if (reclaimOwnedDirectory(_fs, lockDir, incumbent, isOwnerAlive)) {
97
+ let incumbentAlive = true;
98
+ try {
99
+ incumbentAlive = isOwnerAlive(incumbent);
100
+ } catch {
101
+ // An unavailable identity probe must retain the lock.
102
+ }
103
+ lastOwnerObservation = {
104
+ pid: incumbent.pid,
105
+ startedAt: incumbent.startedAt,
106
+ alive: incumbentAlive,
107
+ };
108
+ // The owner writes this exact-token marker only after its critical
109
+ // section has finished. A Windows sharing violation can then prevent
110
+ // the final recursive removal while the owner process remains alive,
111
+ // which would otherwise strand every later strict caller behind a
112
+ // lock that no longer protects any work. Complete only the release
113
+ // explicitly published by the still-current owner; a missing,
114
+ // corrupt or mismatched marker remains fail-closed.
115
+ const publishedRelease = completePublishedRelease(
116
+ _fs,
117
+ lockDir,
118
+ incumbent,
119
+ owner,
120
+ isOwnerAlive,
121
+ );
122
+ lastOwnerObservation.releasePublished = publishedRelease.published;
123
+ if (publishedRelease.completed) continue;
124
+ if (!incumbentAlive) {
125
+ // A published handoff can still retain its unique staging path when
126
+ // the owner is killed between marker publication and the atomic
127
+ // replacement. No process can commit that staging file after the
128
+ // exact owner is proven dead, so the lock directory is reclaimable
129
+ // whether the transaction committed or not. Leave the private
130
+ // staging file untouched; it cannot affect the guarded target.
131
+ if (
132
+ reclaimOwnedDirectory(_fs, lockDir, incumbent, owner, isOwnerAlive)
133
+ )
93
134
  continue;
94
- }
95
135
  }
96
136
  } else if (!failIfUnavailable) {
97
137
  // Compatibility for legacy best-effort locks that predate owner.json.
@@ -124,12 +164,21 @@ export function withFileLock(targetPath, fn, opts = {}) {
124
164
  }
125
165
 
126
166
  if (!held && failIfUnavailable) {
167
+ const ownerDetails = lastOwnerObservation
168
+ ? `; owner pid=${lastOwnerObservation.pid}, ageMs=${Math.max(
169
+ 0,
170
+ _now() - lastOwnerObservation.startedAt,
171
+ )}, alive=${lastOwnerObservation.alive}, releasePublished=${Boolean(lastOwnerObservation.releasePublished)}`
172
+ : "";
127
173
  const error = new Error(
128
- `Could not acquire state lock: ${targetPath}`,
174
+ `Could not acquire state lock: ${targetPath}${ownerDetails}`,
129
175
  acquisitionError ? { cause: acquisitionError } : undefined,
130
176
  );
131
177
  error.code = "STATE_LOCK_UNAVAILABLE";
132
178
  error.attempts = retryAttempt + 1;
179
+ error.lockOwner = lastOwnerObservation
180
+ ? Object.freeze({ ...lastOwnerObservation })
181
+ : null;
133
182
  throw error;
134
183
  }
135
184
 
@@ -137,7 +186,32 @@ export function withFileLock(targetPath, fn, opts = {}) {
137
186
  let bodyThrew = false;
138
187
  let result;
139
188
  try {
140
- result = fn({ locked: held });
189
+ result = fn({
190
+ locked: held,
191
+ publishReleaseAfterPathRemoved(pendingPath) {
192
+ if (!held || typeof pendingPath !== "string" || !pendingPath) {
193
+ return false;
194
+ }
195
+ const current = readOwner(_fs, ownerPath);
196
+ if (!sameOwner(current, owner)) return false;
197
+ const markerPath = path.join(lockDir, `.release-${owner.token}`);
198
+ const marker = { ...owner, releaseAfterPathRemoved: pendingPath };
199
+ try {
200
+ writeOwnerMarker(_fs, markerPath, marker);
201
+ } catch (error) {
202
+ if (error?.code !== "EEXIST") throw error;
203
+ const existing = readOwner(_fs, markerPath);
204
+ if (
205
+ !sameOwner(existing, owner) ||
206
+ existing?.releaseAfterPathRemoved !== pendingPath
207
+ ) {
208
+ return false;
209
+ }
210
+ }
211
+ releasePublished = true;
212
+ return true;
213
+ },
214
+ });
141
215
  } catch (error) {
142
216
  bodyThrew = true;
143
217
  bodyError = error;
@@ -146,7 +220,13 @@ export function withFileLock(targetPath, fn, opts = {}) {
146
220
  let releaseError = null;
147
221
  if (held) {
148
222
  try {
149
- released = releaseOwnedDirectory(_fs, lockDir, owner);
223
+ released = releaseOwnedDirectory(
224
+ _fs,
225
+ lockDir,
226
+ owner,
227
+ isOwnerAlive,
228
+ releasePublished,
229
+ );
150
230
  } catch (error) {
151
231
  releaseError = error;
152
232
  }
@@ -160,8 +240,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
160
240
  throw error;
161
241
  }
162
242
  if (held && released && yieldAfterReleaseMs > 0) {
163
- // The lock is already gone. This lets an existing waiter run before a
164
- // process performing many tiny transactions can immediately reacquire it.
243
+ // The lock is gone or its exact release marker has transferred cleanup to
244
+ // contenders. Let an existing waiter run before a process performing many
245
+ // tiny transactions can immediately reacquire it.
165
246
  _sleep(yieldAfterReleaseMs);
166
247
  }
167
248
  return result;
@@ -235,6 +316,15 @@ function directoryExists(_fs, target) {
235
316
  }
236
317
  }
237
318
 
319
+ function pathStatus(_fs, target) {
320
+ try {
321
+ _fs.statSync(target);
322
+ return "present";
323
+ } catch (error) {
324
+ return error?.code === "ENOENT" ? "absent" : "unknown";
325
+ }
326
+ }
327
+
238
328
  function isProcessAlive(pid) {
239
329
  try {
240
330
  process.kill(pid, 0);
@@ -345,21 +435,65 @@ function writeOwnerMarker(_fs, markerPath, owner) {
345
435
  });
346
436
  }
347
437
 
348
- function reclaimOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
349
- const markerPath = path.join(lockDir, `.reclaim-${owner.token}`);
350
- try {
351
- writeOwnerMarker(_fs, markerPath, owner);
352
- } catch (error) {
353
- if (error?.code === "ENOENT" || error?.code === "EEXIST") return false;
354
- throw error;
438
+ function reclaimOwnedDirectory(_fs, lockDir, incumbent, claimant, ownerAlive) {
439
+ const claimPath = path.join(lockDir, `.reclaim-${incumbent.token}`);
440
+ for (;;) {
441
+ const existingClaim = readOwner(_fs, claimPath);
442
+ if (existingClaim) {
443
+ if (sameOwner(existingClaim, claimant)) break;
444
+ if (!ownerAlive(existingClaim)) {
445
+ if (removeOwnMarker(_fs, claimPath, existingClaim)) continue;
446
+ }
447
+ return false;
448
+ }
449
+ try {
450
+ // The claim belongs to the contender, not the dead incumbent. If this
451
+ // contender is killed before detaching the directory, another process
452
+ // can prove that exact claimant dead and safely take over.
453
+ writeOwnerMarker(_fs, claimPath, claimant);
454
+ break;
455
+ } catch (error) {
456
+ if (error?.code === "ENOENT") {
457
+ return !directoryExists(_fs, lockDir);
458
+ }
459
+ if (error?.code === "EEXIST" || isTransientLockError(error)) {
460
+ return false;
461
+ }
462
+ throw error;
463
+ }
355
464
  }
465
+
356
466
  const current = readOwner(_fs, path.join(lockDir, "owner.json"));
357
- if (!sameOwner(current, owner) || ownerAlive(owner)) {
358
- removeOwnMarker(_fs, markerPath, owner);
467
+ if (!sameOwner(current, incumbent) || ownerAlive(incumbent)) {
468
+ removeOwnMarker(_fs, claimPath, claimant);
359
469
  return false;
360
470
  }
361
- _fs.rmSync(lockDir, { recursive: true, force: true });
362
- return true;
471
+
472
+ if (typeof _fs.renameSync !== "function") {
473
+ _fs.rmSync(lockDir, { recursive: true, force: true });
474
+ return true;
475
+ }
476
+
477
+ const cleanupDir = `${lockDir}.reclaimed-${incumbent.token}-${claimant.token}`;
478
+ try {
479
+ // Free the acquisition path atomically before recursive cleanup. A killed
480
+ // reclaimer can therefore leave only uniquely named debris, never a
481
+ // claimant marker that permanently fences the shared lock path.
482
+ _fs.renameSync(lockDir, cleanupDir);
483
+ try {
484
+ _fs.rmSync(cleanupDir, { recursive: true, force: true });
485
+ } catch {
486
+ /* private reclaim debris is harmless after the lock path is detached */
487
+ }
488
+ return true;
489
+ } catch (error) {
490
+ removeOwnMarker(_fs, claimPath, claimant);
491
+ if (error?.code === "ENOENT") {
492
+ return !directoryExists(_fs, lockDir);
493
+ }
494
+ if (isTransientLockError(error)) return false;
495
+ throw error;
496
+ }
363
497
  }
364
498
 
365
499
  function reclaimLegacyDirectory(_fs, lockDir, stat) {
@@ -383,9 +517,96 @@ function reclaimLegacyDirectory(_fs, lockDir, stat) {
383
517
  return true;
384
518
  }
385
519
 
386
- function releaseOwnedDirectory(_fs, lockDir, owner) {
520
+ function completePublishedRelease(_fs, lockDir, owner, claimant, ownerAlive) {
521
+ const markerPath = path.join(lockDir, `.release-${owner.token}`);
522
+ const marker = readOwner(_fs, markerPath);
523
+ if (!sameOwner(marker, owner)) {
524
+ return { published: false, completed: false };
525
+ }
526
+ // A state transaction can stage its exact replacement, publish this marker,
527
+ // and only then atomically rename the staging file over the guarded target.
528
+ // Contenders may complete the release only after that unique staging path is
529
+ // gone. This closes the otherwise unbounded scheduler-preemption window
530
+ // between a committed rename and the owner's first release syscall without
531
+ // ever exposing an in-progress write to a competing critical section.
532
+ if (
533
+ typeof marker.releaseAfterPathRemoved === "string" &&
534
+ marker.releaseAfterPathRemoved &&
535
+ pathStatus(_fs, marker.releaseAfterPathRemoved) !== "absent"
536
+ ) {
537
+ return { published: true, completed: false };
538
+ }
539
+ const claimPath = path.join(lockDir, `.release-claim-${owner.token}`);
540
+ for (;;) {
541
+ const existingClaim = readOwner(_fs, claimPath);
542
+ if (existingClaim) {
543
+ if (sameOwner(existingClaim, claimant)) break;
544
+ if (!ownerAlive(existingClaim)) {
545
+ if (removeOwnMarker(_fs, claimPath, existingClaim)) continue;
546
+ }
547
+ return { published: true, completed: false };
548
+ }
549
+ try {
550
+ writeOwnerMarker(_fs, claimPath, claimant);
551
+ break;
552
+ } catch (error) {
553
+ if (error?.code === "ENOENT") {
554
+ return { published: true, completed: !directoryExists(_fs, lockDir) };
555
+ }
556
+ if (error?.code === "EEXIST" || isTransientLockError(error)) {
557
+ return { published: true, completed: false };
558
+ }
559
+ throw error;
560
+ }
561
+ }
387
562
  if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
388
- return false;
563
+ removeOwnMarker(_fs, claimPath, claimant);
564
+ return { published: true, completed: false };
565
+ }
566
+ const cleanupDir = `${lockDir}.released-${owner.token}-${claimant.token}`;
567
+ try {
568
+ // Detach the exact released directory from the acquisition path before
569
+ // cleaning it. Recursive removal may span several Windows filesystem
570
+ // operations; deleting `lockDir` in place lets a new atomic acquirer
571
+ // install a replacement between those operations and risks deleting that
572
+ // replacement. The claimant token makes delayed cleanup private to this
573
+ // handoff, just like the owner's normal rename-first release path.
574
+ _fs.renameSync(lockDir, cleanupDir);
575
+ try {
576
+ _fs.rmSync(cleanupDir, { recursive: true, force: true });
577
+ } catch {
578
+ // The acquisition path is already free. A uniquely named released
579
+ // directory is harmless bounded debris if Windows still holds a handle.
580
+ }
581
+ return { published: true, completed: true };
582
+ } catch (error) {
583
+ removeOwnMarker(_fs, claimPath, claimant);
584
+ if (error?.code === "ENOENT") {
585
+ return { published: true, completed: !directoryExists(_fs, lockDir) };
586
+ }
587
+ // A Windows sharing transient leaves the exact release marker in place so
588
+ // this or another contender can retry within its normal deadline.
589
+ if (isTransientLockError(error)) {
590
+ return { published: true, completed: false };
591
+ }
592
+ throw error;
593
+ }
594
+ }
595
+
596
+ function releaseOwnedDirectory(
597
+ _fs,
598
+ lockDir,
599
+ owner,
600
+ ownerAlive,
601
+ releaseWasPublished = false,
602
+ ) {
603
+ if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
604
+ // Once this exact owner published a guarded handoff, a contender is allowed
605
+ // to detach the old directory as soon as the final staging path disappears.
606
+ // A missing or replacement lock therefore proves successful release, not
607
+ // ownership loss. Without that publication the strict legacy verdict stays
608
+ // fail-closed.
609
+ return releaseWasPublished;
389
610
  }
390
611
  // Atomically move the exact owned directory out of the acquisition path,
391
612
  // then remove that uniquely-tokened directory. A replacement owner may
@@ -404,7 +625,7 @@ function releaseOwnedDirectory(_fs, lockDir, owner) {
404
625
  }
405
626
  return true;
406
627
  } catch (error) {
407
- if (error?.code === "ENOENT") return false;
628
+ if (error?.code === "ENOENT") return releaseWasPublished;
408
629
  // Windows sharing transients fall back to the marker-based release.
409
630
  }
410
631
  }
@@ -412,13 +633,31 @@ function releaseOwnedDirectory(_fs, lockDir, owner) {
412
633
  try {
413
634
  writeOwnerMarker(_fs, markerPath, owner);
414
635
  } catch (error) {
415
- if (error?.code === "ENOENT" || error?.code === "EEXIST") return false;
636
+ if (error?.code === "ENOENT") return releaseWasPublished;
637
+ if (error?.code === "EEXIST") {
638
+ const existing = readOwner(_fs, markerPath);
639
+ if (!sameOwner(existing, owner)) return false;
640
+ return completePublishedRelease(_fs, lockDir, owner, owner, ownerAlive)
641
+ .published;
642
+ }
416
643
  throw error;
417
644
  }
418
645
  if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
419
646
  removeOwnMarker(_fs, markerPath, owner);
420
647
  return false;
421
648
  }
649
+ if (typeof _fs.renameSync === "function") {
650
+ // After publishing the marker, use the same exclusive claim protocol as a
651
+ // later contender. The releasing process must not perform an unclaimed,
652
+ // delayed delete: another process could complete the release and install a
653
+ // replacement lock before that delete resumes.
654
+ // Publishing the exact release marker is the logical handoff point. A
655
+ // contender may win the cleanup claim before this owner does; that is a
656
+ // successful release, not lost ownership, because every acquirer must
657
+ // complete or wait behind the published marker before entering its body.
658
+ return completePublishedRelease(_fs, lockDir, owner, owner, ownerAlive)
659
+ .published;
660
+ }
422
661
  _fs.rmSync(lockDir, { recursive: true, force: true });
423
662
  return true;
424
663
  }