chainlesschain 0.164.0 → 0.165.2

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 (228) hide show
  1. package/README.md +248 -2
  2. package/package.json +7 -4
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
  40. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
  75. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  76. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
  104. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  109. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
  134. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  135. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/cowork.js +559 -62
  154. package/src/commands/plugin.js +819 -64
  155. package/src/commands/session-location.js +318 -7
  156. package/src/constants.js +2 -2
  157. package/src/data/changelog.json +28 -2
  158. package/src/gateways/ws/action-protocol.js +293 -19
  159. package/src/gateways/ws/message-dispatcher.js +30 -0
  160. package/src/gateways/ws/remote-session-protocol.js +542 -35
  161. package/src/gateways/ws/ws-approval-gate.js +5 -0
  162. package/src/gateways/ws/ws-server.js +462 -38
  163. package/src/harness/jsonl-session-store.js +638 -2
  164. package/src/harness/prompt-compressor.js +4 -0
  165. package/src/harness/remote-session-audit.js +1 -0
  166. package/src/harness/remote-session-crypto.js +750 -50
  167. package/src/harness/remote-session-registry.js +795 -27
  168. package/src/lib/auto-mode-config.js +98 -3
  169. package/src/lib/background-agent-keeper-client.js +29 -0
  170. package/src/lib/background-agent-keeper-protocol.js +158 -10
  171. package/src/lib/background-agent-supervisor.js +870 -48
  172. package/src/lib/background-session-transport.js +23 -6
  173. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  174. package/src/lib/cowork-task-runner.js +70 -0
  175. package/src/lib/cowork-workflow.js +887 -62
  176. package/src/lib/delivery-production-adapter.js +38 -2
  177. package/src/lib/doctor-checkup.js +34 -10
  178. package/src/lib/dynamic-workflow-draft.js +535 -0
  179. package/src/lib/dynamic-workflow-facade.js +1105 -17
  180. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  181. package/src/lib/execution-location-contract.js +111 -7
  182. package/src/lib/execution-location-runtime.js +3 -1
  183. package/src/lib/execution-location-target.js +910 -0
  184. package/src/lib/llm-providers.js +1 -0
  185. package/src/lib/mcp-call-ledger-store.js +35 -0
  186. package/src/lib/mcp-call-ledger.js +30 -0
  187. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  188. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  189. package/src/lib/plugin-runtime/install.js +4956 -657
  190. package/src/lib/plugin-runtime/manifest.js +3 -1
  191. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  192. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  193. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  194. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  195. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  196. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  197. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  198. package/src/lib/plugin-runtime/policy.js +21 -6
  199. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  200. package/src/lib/plugin-runtime/remote-source.js +323 -27
  201. package/src/lib/plugin-runtime/scopes.js +317 -41
  202. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  203. package/src/lib/public-ide-capability-manifest.js +1 -1
  204. package/src/lib/remote-approval-bridge.js +745 -108
  205. package/src/lib/remote-control.js +2 -0
  206. package/src/lib/remote-membership-coordinator.js +612 -46
  207. package/src/lib/remote-membership-host-store.js +59 -10
  208. package/src/lib/runtime-provenance-ledger.js +171 -46
  209. package/src/lib/shell-approval.js +169 -6
  210. package/src/lib/sub-agent-context.js +268 -2
  211. package/src/lib/task-model-selector.js +1 -1
  212. package/src/lib/with-file-lock.js +167 -42
  213. package/src/repl/agent-repl.js +70 -29
  214. package/src/repl/remote-approval.js +29 -7
  215. package/src/runtime/agent-core.js +1695 -194
  216. package/src/runtime/headless-runner.js +22 -5
  217. package/src/runtime/headless-stream.js +19 -3
  218. package/src/workers/background-agent-keeper.js +279 -58
  219. package/src/workers/background-agent-worker.js +7 -0
  220. package/src/workers/background-turn-bootstrap.js +29 -2
  221. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
  222. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  223. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
  224. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  225. package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  227. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  228. package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
@@ -31,6 +31,27 @@ const SUMMARY_DIRECT_THRESHOLD = 500; // chars — below this, use result as-is
31
31
  const SUMMARY_SECTION_PATTERN =
32
32
  /^##\s*(Summary|Result|Output|Conclusion|Answer)/im;
33
33
  const TRUNCATE_LENGTH = 500;
34
+ const PROVIDER_CLIENT_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/;
35
+ const PROVIDER_CALL_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/;
36
+ const PROVIDER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
37
+ const WORKFLOW_EFFECT_ID_RE = /^sha256:[a-f0-9]{64}$/;
38
+ const WORKFLOW_CHILD_EFFECT_PROTOCOL = "cc-workflow-child-effect/v1";
39
+ const WORKFLOW_TOOL_CALL_ID_RE = /^[\x21-\x7e]{1,512}$/;
40
+ const WORKFLOW_TOOL_NAME_RE = /^[A-Za-z0-9._:-]{1,256}$/;
41
+
42
+ function ownDataValue(value, property) {
43
+ if (!value || (typeof value !== "object" && typeof value !== "function")) {
44
+ return undefined;
45
+ }
46
+ try {
47
+ const descriptor = Object.getOwnPropertyDescriptor(value, property);
48
+ return descriptor && Object.hasOwn(descriptor, "value")
49
+ ? descriptor.value
50
+ : undefined;
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }
34
55
 
35
56
  function runtimeUsageObserverFailure(error, code, message) {
36
57
  const failure =
@@ -132,6 +153,8 @@ export class SubAgentContext {
132
153
  * @param {boolean} [options.memoryEnabled=true] - When false, the child's
133
154
  * context engine suppresses hierarchical-memory recall (contract memory:false)
134
155
  * @param {object} [options.llmOptions] - LLM provider/model/key options
156
+ * @param {string} [options.workflowEffectId] - Durable workflow effect bound
157
+ * to provider request identities for this child run
135
158
  * @param {string} [options.cwd] - Working directory
136
159
  * @param {boolean} [options.useWorktree] - Force worktree isolation (overrides flag)
137
160
  * @param {function} [options.onUsageBoundary] - Synchronous observer for
@@ -207,6 +230,11 @@ export class SubAgentContext {
207
230
  this._runId = null;
208
231
  this._checkpointIds = [];
209
232
  this._toolUseIds = [];
233
+ this._workflowEffectId = options.workflowEffectId ?? null;
234
+ this._providerRequestAttempts = [];
235
+ this._providerRequestReceipts = [];
236
+ this._nestedEffectAttempts = [];
237
+ this._nestedEffectSettlements = [];
210
238
 
211
239
  // ── Isolated state ──────────────────────────────────────────────
212
240
  // Independent message history — never shared with parent
@@ -547,7 +575,9 @@ export class SubAgentContext {
547
575
  }
548
576
  return result;
549
577
  } catch (err) {
550
- if (err?.runtimeLedgerPersistence === true) throw err;
578
+ if (err?.runtimeLedgerPersistence === true || this._workflowEffectId) {
579
+ throw err;
580
+ }
551
581
  // If worktree creation fails (e.g. not a git repo), fall back to direct
552
582
  this.status = "failed";
553
583
  this.completedAt = new Date().toISOString();
@@ -615,6 +645,17 @@ export class SubAgentContext {
615
645
  }
616
646
  options.toolAdmission = this.toolAdmission;
617
647
  options.skillAllowlist = this.skillAllowlist;
648
+ // A caller cannot replace the durable outer effect through loopOptions.
649
+ // agentLoop validates the canonical sha256 form before any provider call.
650
+ if (this._workflowEffectId) {
651
+ options.workflowEffectId = this._workflowEffectId;
652
+ options.onProviderRequestBoundary = (event) => {
653
+ this._recordProviderRequestAttempt(event);
654
+ };
655
+ } else {
656
+ delete options.workflowEffectId;
657
+ delete options.onProviderRequestBoundary;
658
+ }
618
659
  const authority = this._parentAuthority;
619
660
  if (authority.permissionRules) {
620
661
  options.permissionRules = authority.permissionRules;
@@ -651,6 +692,22 @@ export class SubAgentContext {
651
692
  let observeUsageSettlement = null;
652
693
  let observeToolBoundary = null;
653
694
  let observeToolSettlement = null;
695
+ const providerReceiptForSettlement = (event) => {
696
+ for (
697
+ let index = this._providerRequestReceipts.length - 1;
698
+ index >= 0;
699
+ index -= 1
700
+ ) {
701
+ const receipt = this._providerRequestReceipts[index];
702
+ if (
703
+ receipt.callId === event?.callId &&
704
+ (!event?.provider || receipt.provider === event.provider)
705
+ ) {
706
+ return { ...receipt };
707
+ }
708
+ }
709
+ return null;
710
+ };
654
711
  if (strictUsageTelemetry) {
655
712
  // Presence is checked without invoking either observer. In particular,
656
713
  // do not manufacture a null "validation event" that a durable writer
@@ -805,11 +862,29 @@ export class SubAgentContext {
805
862
  }
806
863
  }
807
864
  if (event.type === "tool-executing" && event.tool_use_id) {
865
+ this._recordNestedEffectAttempt(event);
808
866
  const toolUseId = String(event.tool_use_id);
809
867
  if (!this._toolUseIds.includes(toolUseId)) {
810
868
  this._toolUseIds.push(toolUseId);
811
869
  }
812
870
  }
871
+ if (event.type === "provider-request-receipt") {
872
+ this._providerRequestReceipts.push(
873
+ Object.freeze({
874
+ protocol: event.protocol,
875
+ provider: event.provider,
876
+ workflowEffectId: event.workflowEffectId,
877
+ callId: event.callId,
878
+ callSequence: event.callSequence,
879
+ source: event.source,
880
+ clientRequestId: event.clientRequestId,
881
+ requestId: event.requestId || null,
882
+ responseId: event.responseId || null,
883
+ requestIdentitySemantics: event.requestIdentitySemantics,
884
+ independentlyReadable: event.independentlyReadable,
885
+ }),
886
+ );
887
+ }
813
888
 
814
889
  if (event.type === "token-usage") {
815
890
  const forwardedUsage = {
@@ -819,6 +894,7 @@ export class SubAgentContext {
819
894
  model: event.model || null,
820
895
  usage: event.usage || null,
821
896
  ...(event.source ? { source: event.source } : {}),
897
+ providerReceipt: providerReceiptForSettlement(event),
822
898
  attribution: event.attribution || null,
823
899
  ...(event.boundaryNotified === true ||
824
900
  event.ledgerPersisted === true
@@ -865,6 +941,9 @@ export class SubAgentContext {
865
941
  }
866
942
 
867
943
  if (event.type === "model-usage-started") {
944
+ if (event.providerRequestId) {
945
+ this._recordProviderRequestAttempt(event);
946
+ }
868
947
  if (strictUsageTelemetry) {
869
948
  if (
870
949
  event.boundaryNotified === true ||
@@ -896,6 +975,7 @@ export class SubAgentContext {
896
975
  ? "semantic-compaction"
897
976
  : "model"),
898
977
  code: event.code || "provider_transport_outcome_unknown",
978
+ providerReceipt: providerReceiptForSettlement(event),
899
979
  attribution: event.attribution || null,
900
980
  ...(event.boundaryNotified === true ||
901
981
  event.ledgerPersisted === true
@@ -932,9 +1012,26 @@ export class SubAgentContext {
932
1012
  }
933
1013
 
934
1014
  if (event.type === "tool-result") {
1015
+ const nestedEffectSettlement =
1016
+ this._recordNestedEffectSettlement(event);
935
1017
  if (strictUsageTelemetry && event.tool_use_id) {
936
1018
  observeToolSettlement(event);
937
1019
  }
1020
+ // A consumer may stop the async generator after this yielded result
1021
+ // because of cancellation or a local token budget. Fence the unknown
1022
+ // here, after the strict settlement observer closes its boundary, so
1023
+ // generator.return() cannot suppress the outer reconciliation signal.
1024
+ if (nestedEffectSettlement?.outcomeUnknown === true) {
1025
+ const error = new Error(
1026
+ `Workflow-bound nested tool ${nestedEffectSettlement.tool} has an unknown outcome and requires reconciliation`,
1027
+ );
1028
+ error.code = "CC_WORKFLOW_NESTED_TOOL_OUTCOME_UNKNOWN";
1029
+ error.workflowEffectOutcomeUnknown = true;
1030
+ error.workflowEffectId = nestedEffectSettlement.workflowEffectId;
1031
+ error.workflowChildEffectId =
1032
+ nestedEffectSettlement.childEffectId;
1033
+ throw error;
1034
+ }
938
1035
  // Store large tool results as artifacts
939
1036
  const resultStr = JSON.stringify(event.result);
940
1037
  // Estimate token count from tool result (~4 chars per token)
@@ -1004,8 +1101,22 @@ export class SubAgentContext {
1004
1101
  "Strict child execution ended with an unsettled provider or tool call",
1005
1102
  );
1006
1103
  }
1104
+ if (
1105
+ this._workflowEffectId &&
1106
+ this._nestedEffectAttempts.length !==
1107
+ this._nestedEffectSettlements.length
1108
+ ) {
1109
+ const error = new Error(
1110
+ "Workflow-bound child execution ended with an unsettled nested effect",
1111
+ );
1112
+ error.code = "CC_WORKFLOW_CHILD_EFFECT_SETTLEMENT_INCOMPLETE";
1113
+ error.workflowEffectOutcomeUnknown = true;
1114
+ throw error;
1115
+ }
1007
1116
  } catch (err) {
1008
- if (err?.runtimeLedgerPersistence === true) throw err;
1117
+ if (err?.runtimeLedgerPersistence === true || this._workflowEffectId) {
1118
+ throw err;
1119
+ }
1009
1120
  if (this.isAborted()) {
1010
1121
  this.forceComplete(this._cancelReason || "cancelled", {
1011
1122
  partialContent: lastContent,
@@ -1159,11 +1270,166 @@ export class SubAgentContext {
1159
1270
  parentTraceId: this._hookParentTraceId || null,
1160
1271
  checkpointIds: [...this._checkpointIds],
1161
1272
  toolUseIds: [...this._toolUseIds],
1273
+ ...(this._workflowEffectId
1274
+ ? {
1275
+ providerRequestAttempts: this.providerRequestAttempts(),
1276
+ providerRequestReceipts: this.providerRequestReceipts(),
1277
+ nestedEffectAttempts: this.nestedEffectAttempts(),
1278
+ nestedEffectSettlements: this.nestedEffectSettlements(),
1279
+ }
1280
+ : {}),
1162
1281
  worktreeId: worktree?.branch || this._worktreeBranch || null,
1163
1282
  worktreePath: worktree?.path || this._worktreePath || null,
1164
1283
  };
1165
1284
  }
1166
1285
 
1286
+ /**
1287
+ * Effect-bound provider calls admitted before transport dispatch.
1288
+ */
1289
+ providerRequestAttempts() {
1290
+ return this._providerRequestAttempts.map((attempt) => ({ ...attempt }));
1291
+ }
1292
+
1293
+ /**
1294
+ * Provider-returned request identifiers observed during this child run.
1295
+ * Values are copied so callers cannot mutate recovery evidence in place.
1296
+ */
1297
+ providerRequestReceipts() {
1298
+ return this._providerRequestReceipts.map((receipt) => ({ ...receipt }));
1299
+ }
1300
+
1301
+ nestedEffectAttempts() {
1302
+ return this._nestedEffectAttempts.map((attempt) => ({ ...attempt }));
1303
+ }
1304
+
1305
+ nestedEffectSettlements() {
1306
+ return this._nestedEffectSettlements.map((settlement) => ({
1307
+ ...settlement,
1308
+ }));
1309
+ }
1310
+
1311
+ _recordNestedEffectAttempt(event) {
1312
+ if (!this._workflowEffectId) return;
1313
+ const attempt = {
1314
+ protocol: event?.workflowEffectProtocol,
1315
+ workflowEffectId: event?.workflowEffectId,
1316
+ childEffectId: event?.workflowChildEffectId,
1317
+ childSequence: event?.workflowChildSequence,
1318
+ kind: "tool",
1319
+ tool: event?.tool,
1320
+ toolUseId: event?.tool_use_id,
1321
+ identitySemantics: "runtime-derived",
1322
+ };
1323
+ if (
1324
+ attempt.protocol !== WORKFLOW_CHILD_EFFECT_PROTOCOL ||
1325
+ attempt.workflowEffectId !== this._workflowEffectId ||
1326
+ !WORKFLOW_EFFECT_ID_RE.test(attempt.childEffectId || "") ||
1327
+ !Number.isSafeInteger(attempt.childSequence) ||
1328
+ attempt.childSequence < 1 ||
1329
+ !WORKFLOW_TOOL_NAME_RE.test(attempt.tool || "") ||
1330
+ !WORKFLOW_TOOL_CALL_ID_RE.test(attempt.toolUseId || "") ||
1331
+ this._nestedEffectAttempts.some(
1332
+ (existing) =>
1333
+ existing.childEffectId === attempt.childEffectId ||
1334
+ existing.childSequence === attempt.childSequence,
1335
+ )
1336
+ ) {
1337
+ const error = new TypeError(
1338
+ "workflow nested-effect boundary is malformed or duplicated",
1339
+ );
1340
+ error.code = "CC_WORKFLOW_CHILD_EFFECT_BOUNDARY_INVALID";
1341
+ throw error;
1342
+ }
1343
+ this._nestedEffectAttempts.push(Object.freeze(attempt));
1344
+ }
1345
+
1346
+ _recordNestedEffectSettlement(event) {
1347
+ if (!this._workflowEffectId || !event?.workflowChildEffectId) return null;
1348
+ const attempt = this._nestedEffectAttempts.find(
1349
+ (candidate) =>
1350
+ candidate.childEffectId === event.workflowChildEffectId,
1351
+ );
1352
+ const outcomeUnknown = ownDataValue(event.result, "outcomeUnknown") === true;
1353
+ const resultError = ownDataValue(event.result, "error");
1354
+ const mcpLedgerId = ownDataValue(event.result, "mcpLedgerId") ?? null;
1355
+ const settlement = {
1356
+ protocol: event?.workflowEffectProtocol,
1357
+ workflowEffectId: event?.workflowEffectId,
1358
+ childEffectId: event?.workflowChildEffectId,
1359
+ childSequence: event?.workflowChildSequence,
1360
+ kind: "tool",
1361
+ tool: event?.tool,
1362
+ toolUseId: event?.tool_use_id,
1363
+ status: outcomeUnknown
1364
+ ? "outcome_unknown"
1365
+ : event?.error || resultError
1366
+ ? "failed"
1367
+ : "completed",
1368
+ outcomeUnknown,
1369
+ mcpLedgerId,
1370
+ mcpLedgerPrewritePersisted:
1371
+ ownDataValue(event.result, "mcpLedgerPrewritePersisted") === true,
1372
+ mcpLedgerSettlementPersisted:
1373
+ ownDataValue(event.result, "mcpLedgerSettlementPersisted") === true,
1374
+ };
1375
+ if (
1376
+ !attempt ||
1377
+ settlement.protocol !== WORKFLOW_CHILD_EFFECT_PROTOCOL ||
1378
+ settlement.workflowEffectId !== this._workflowEffectId ||
1379
+ settlement.childSequence !== attempt.childSequence ||
1380
+ settlement.tool !== attempt.tool ||
1381
+ settlement.toolUseId !== attempt.toolUseId ||
1382
+ (mcpLedgerId !== null &&
1383
+ !PROVIDER_CALL_ID_RE.test(String(mcpLedgerId))) ||
1384
+ this._nestedEffectSettlements.some(
1385
+ (existing) => existing.childEffectId === settlement.childEffectId,
1386
+ )
1387
+ ) {
1388
+ const error = new TypeError(
1389
+ "workflow nested-effect settlement is malformed or duplicated",
1390
+ );
1391
+ error.code = "CC_WORKFLOW_CHILD_EFFECT_SETTLEMENT_INVALID";
1392
+ error.workflowEffectOutcomeUnknown = true;
1393
+ throw error;
1394
+ }
1395
+ this._nestedEffectSettlements.push(Object.freeze(settlement));
1396
+ return settlement;
1397
+ }
1398
+
1399
+ _recordProviderRequestAttempt(event) {
1400
+ if (!this._workflowEffectId) return;
1401
+ const attempt = {
1402
+ protocol: "cc-provider-request-attempt/v1",
1403
+ provider: event?.provider,
1404
+ workflowEffectId: event?.workflowEffectId,
1405
+ callId: event?.callId,
1406
+ callSequence: event?.callSequence,
1407
+ source: event?.source,
1408
+ clientRequestId: event?.providerRequestId,
1409
+ requestIdentitySemantics: event?.requestIdentitySemantics,
1410
+ };
1411
+ if (
1412
+ !PROVIDER_NAME_RE.test(attempt.provider || "") ||
1413
+ attempt.workflowEffectId !== this._workflowEffectId ||
1414
+ !PROVIDER_CALL_ID_RE.test(attempt.callId || "") ||
1415
+ !Number.isSafeInteger(attempt.callSequence) ||
1416
+ attempt.callSequence < 1 ||
1417
+ !["model", "semantic-compaction"].includes(attempt.source) ||
1418
+ !PROVIDER_CLIENT_REQUEST_ID_RE.test(attempt.clientRequestId || "") ||
1419
+ attempt.requestIdentitySemantics !== "trace-only" ||
1420
+ this._providerRequestAttempts.some(
1421
+ (existing) => existing.callId === attempt.callId,
1422
+ )
1423
+ ) {
1424
+ const error = new TypeError(
1425
+ "workflow provider request boundary is malformed or duplicated",
1426
+ );
1427
+ error.code = "CC_WORKFLOW_PROVIDER_REQUEST_BOUNDARY_INVALID";
1428
+ throw error;
1429
+ }
1430
+ this._providerRequestAttempts.push(Object.freeze(attempt));
1431
+ }
1432
+
1167
1433
  /**
1168
1434
  * True when this sub-agent has been asked to stop — either via an external
1169
1435
  * abort signal or via this context's own `abort()` (precise single-agent
@@ -24,7 +24,7 @@ export const TaskType = {
24
24
  */
25
25
  const TASK_MODEL_MAP = {
26
26
  [TaskType.CHAT]: {
27
- volcengine: "doubao-seed-2-1-pro-260628",
27
+ volcengine: "deepseek-v4-flash-260425",
28
28
  openai: "gpt-4o-mini",
29
29
  anthropic: "claude-sonnet-4-6",
30
30
  deepseek: "deepseek-chat",
@@ -12,10 +12,13 @@
12
12
  * `timeoutMs`, it proceeds unlocked. Callers that set `failIfUnavailable` get a
13
13
  * strict lock: every owner has a PID + unguessable token, a live owner is never
14
14
  * reclaimed merely because the directory is old, and corrupt ownership fails
15
- * closed. Confirmed-dead owners are reclaimed with an exact-token marker inside
16
- * the lock directory so a delayed contender cannot delete a replacement lock.
17
- * Strict callers may use bounded jittered retries and an after-release yield to
18
- * avoid Windows sharing transients and fixed-interval lock convoys.
15
+ * closed. The default filesystem publishes a fully populated, uniquely staged
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
20
+ * after-release yield to avoid Windows sharing transients and fixed-interval
21
+ * lock convoys.
19
22
  *
20
23
  * @param {string} targetPath the file being guarded (lock is `${targetPath}.lock`)
21
24
  * @param {(ctx:{locked:boolean})=>T} fn critical section; receives whether the lock was held
@@ -37,6 +40,10 @@ export function withFileLock(targetPath, fn, opts = {}) {
37
40
  _ownerToken = () => randomUUID(),
38
41
  failIfUnavailable = false,
39
42
  } = opts;
43
+ const isOwnerAlive =
44
+ typeof opts._isOwnerAlive === "function"
45
+ ? opts._isOwnerAlive
46
+ : (candidate) => _isProcessAlive(candidate.pid);
40
47
 
41
48
  const lockDir = `${targetPath}.lock`;
42
49
  const ownerPath = path.join(lockDir, "owner.json");
@@ -52,36 +59,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
52
59
 
53
60
  for (;;) {
54
61
  try {
55
- _fs.mkdirSync(lockDir);
56
- try {
57
- _fs.writeFileSync(ownerPath, JSON.stringify(owner), {
58
- encoding: "utf8",
59
- mode: 0o600,
60
- flag: "wx",
61
- });
62
- held = true;
63
- break;
64
- } catch (error) {
65
- _fs.rmSync(lockDir, { recursive: true, force: true });
66
- acquisitionError = error;
67
- if (
68
- failIfUnavailable &&
69
- isTransientLockError(error) &&
70
- waitForRetry({
71
- _now,
72
- _sleep,
73
- _random,
74
- deadline,
75
- retryAttempt: retryAttempt++,
76
- retryMs,
77
- maxRetryMs,
78
- retryJitterMs,
79
- })
80
- ) {
81
- continue;
82
- }
83
- break;
84
- }
62
+ acquireOwnedDirectory(_fs, lockDir, owner);
63
+ held = true;
64
+ break;
85
65
  } catch (error) {
86
66
  if (!error || error.code !== "EEXIST") {
87
67
  acquisitionError = error;
@@ -108,8 +88,23 @@ export function withFileLock(targetPath, fn, opts = {}) {
108
88
  const incumbent = ownerRead.owner;
109
89
  acquisitionError = ownerRead.error;
110
90
  if (incumbent) {
111
- if (!_isProcessAlive(incumbent.pid)) {
112
- if (reclaimOwnedDirectory(_fs, lockDir, incumbent, _isProcessAlive)) {
91
+ // The owner writes this exact-token marker only after its critical
92
+ // section has finished. A Windows sharing violation can then prevent
93
+ // the final recursive removal while the owner process remains alive,
94
+ // which would otherwise strand every later strict caller behind a
95
+ // lock that no longer protects any work. Complete only the release
96
+ // explicitly published by the still-current owner; a missing,
97
+ // corrupt or mismatched marker remains fail-closed.
98
+ const publishedRelease = completePublishedRelease(
99
+ _fs,
100
+ lockDir,
101
+ incumbent,
102
+ owner,
103
+ isOwnerAlive,
104
+ );
105
+ if (publishedRelease.completed) continue;
106
+ if (!publishedRelease.published && !isOwnerAlive(incumbent)) {
107
+ if (reclaimOwnedDirectory(_fs, lockDir, incumbent, isOwnerAlive)) {
113
108
  continue;
114
109
  }
115
110
  }
@@ -166,7 +161,7 @@ export function withFileLock(targetPath, fn, opts = {}) {
166
161
  let releaseError = null;
167
162
  if (held) {
168
163
  try {
169
- released = releaseOwnedDirectory(_fs, lockDir, owner);
164
+ released = releaseOwnedDirectory(_fs, lockDir, owner, isOwnerAlive);
170
165
  } catch (error) {
171
166
  releaseError = error;
172
167
  }
@@ -180,8 +175,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
180
175
  throw error;
181
176
  }
182
177
  if (held && released && yieldAfterReleaseMs > 0) {
183
- // The lock is already gone. This lets an existing waiter run before a
184
- // process performing many tiny transactions can immediately reacquire it.
178
+ // The lock is gone or its exact release marker has transferred cleanup to
179
+ // contenders. Let an existing waiter run before a process performing many
180
+ // tiny transactions can immediately reacquire it.
185
181
  _sleep(yieldAfterReleaseMs);
186
182
  }
187
183
  return result;
@@ -200,6 +196,61 @@ const defaultFs = {
200
196
  rmSync: (p, o) => fs.rmSync(p, o),
201
197
  };
202
198
 
199
+ function acquireOwnedDirectory(_fs, lockDir, owner) {
200
+ const serializedOwner = JSON.stringify(owner);
201
+ // Injected legacy filesystems used by a few consumers may not implement
202
+ // rename. Keep their old acquire path, while the real Node filesystem always
203
+ // uses the crash-safe staged publication below.
204
+ if (typeof _fs.renameSync !== "function") {
205
+ _fs.mkdirSync(lockDir);
206
+ try {
207
+ _fs.writeFileSync(path.join(lockDir, "owner.json"), serializedOwner, {
208
+ encoding: "utf8",
209
+ mode: 0o600,
210
+ flag: "wx",
211
+ });
212
+ return;
213
+ } catch (error) {
214
+ _fs.rmSync(lockDir, { recursive: true, force: true });
215
+ throw error;
216
+ }
217
+ }
218
+
219
+ const candidateDir = `${lockDir}.acquire-${owner.token}`;
220
+ try {
221
+ _fs.mkdirSync(candidateDir);
222
+ _fs.writeFileSync(path.join(candidateDir, "owner.json"), serializedOwner, {
223
+ encoding: "utf8",
224
+ mode: 0o600,
225
+ flag: "wx",
226
+ });
227
+ _fs.renameSync(candidateDir, lockDir);
228
+ } catch (error) {
229
+ try {
230
+ _fs.rmSync(candidateDir, { recursive: true, force: true });
231
+ } catch {
232
+ /* uniquely-tokened acquisition debris cannot block the lock path */
233
+ }
234
+ if (directoryExists(_fs, lockDir)) {
235
+ const contention = new Error("State lock already exists", {
236
+ cause: error,
237
+ });
238
+ contention.code = "EEXIST";
239
+ throw contention;
240
+ }
241
+ throw error;
242
+ }
243
+ }
244
+
245
+ function directoryExists(_fs, target) {
246
+ try {
247
+ _fs.statSync(target);
248
+ return true;
249
+ } catch {
250
+ return false;
251
+ }
252
+ }
253
+
203
254
  function isProcessAlive(pid) {
204
255
  try {
205
256
  process.kill(pid, 0);
@@ -310,7 +361,7 @@ function writeOwnerMarker(_fs, markerPath, owner) {
310
361
  });
311
362
  }
312
363
 
313
- function reclaimOwnedDirectory(_fs, lockDir, owner, processAlive) {
364
+ function reclaimOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
314
365
  const markerPath = path.join(lockDir, `.reclaim-${owner.token}`);
315
366
  try {
316
367
  writeOwnerMarker(_fs, markerPath, owner);
@@ -319,7 +370,7 @@ function reclaimOwnedDirectory(_fs, lockDir, owner, processAlive) {
319
370
  throw error;
320
371
  }
321
372
  const current = readOwner(_fs, path.join(lockDir, "owner.json"));
322
- if (!sameOwner(current, owner) || processAlive(owner.pid)) {
373
+ if (!sameOwner(current, owner) || ownerAlive(owner)) {
323
374
  removeOwnMarker(_fs, markerPath, owner);
324
375
  return false;
325
376
  }
@@ -348,7 +399,69 @@ function reclaimLegacyDirectory(_fs, lockDir, stat) {
348
399
  return true;
349
400
  }
350
401
 
351
- function releaseOwnedDirectory(_fs, lockDir, owner) {
402
+ function completePublishedRelease(_fs, lockDir, owner, claimant, ownerAlive) {
403
+ const markerPath = path.join(lockDir, `.release-${owner.token}`);
404
+ if (!sameOwner(readOwner(_fs, markerPath), owner)) {
405
+ return { published: false, completed: false };
406
+ }
407
+ const claimPath = path.join(lockDir, `.release-claim-${owner.token}`);
408
+ for (;;) {
409
+ const existingClaim = readOwner(_fs, claimPath);
410
+ if (existingClaim) {
411
+ if (sameOwner(existingClaim, claimant)) break;
412
+ if (!ownerAlive(existingClaim)) {
413
+ if (removeOwnMarker(_fs, claimPath, existingClaim)) continue;
414
+ }
415
+ return { published: true, completed: false };
416
+ }
417
+ try {
418
+ writeOwnerMarker(_fs, claimPath, claimant);
419
+ break;
420
+ } catch (error) {
421
+ if (error?.code === "ENOENT") {
422
+ return { published: true, completed: !directoryExists(_fs, lockDir) };
423
+ }
424
+ if (error?.code === "EEXIST" || isTransientLockError(error)) {
425
+ return { published: true, completed: false };
426
+ }
427
+ throw error;
428
+ }
429
+ }
430
+ if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
431
+ removeOwnMarker(_fs, claimPath, claimant);
432
+ return { published: true, completed: false };
433
+ }
434
+ const cleanupDir = `${lockDir}.released-${owner.token}-${claimant.token}`;
435
+ try {
436
+ // Detach the exact released directory from the acquisition path before
437
+ // cleaning it. Recursive removal may span several Windows filesystem
438
+ // operations; deleting `lockDir` in place lets a new atomic acquirer
439
+ // install a replacement between those operations and risks deleting that
440
+ // replacement. The claimant token makes delayed cleanup private to this
441
+ // handoff, just like the owner's normal rename-first release path.
442
+ _fs.renameSync(lockDir, cleanupDir);
443
+ try {
444
+ _fs.rmSync(cleanupDir, { recursive: true, force: true });
445
+ } catch {
446
+ // The acquisition path is already free. A uniquely named released
447
+ // directory is harmless bounded debris if Windows still holds a handle.
448
+ }
449
+ return { published: true, completed: true };
450
+ } catch (error) {
451
+ removeOwnMarker(_fs, claimPath, claimant);
452
+ if (error?.code === "ENOENT") {
453
+ return { published: true, completed: !directoryExists(_fs, lockDir) };
454
+ }
455
+ // A Windows sharing transient leaves the exact release marker in place so
456
+ // this or another contender can retry within its normal deadline.
457
+ if (isTransientLockError(error)) {
458
+ return { published: true, completed: false };
459
+ }
460
+ throw error;
461
+ }
462
+ }
463
+
464
+ function releaseOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
352
465
  if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
353
466
  return false;
354
467
  }
@@ -384,6 +497,18 @@ function releaseOwnedDirectory(_fs, lockDir, owner) {
384
497
  removeOwnMarker(_fs, markerPath, owner);
385
498
  return false;
386
499
  }
500
+ if (typeof _fs.renameSync === "function") {
501
+ // After publishing the marker, use the same exclusive claim protocol as a
502
+ // later contender. The releasing process must not perform an unclaimed,
503
+ // delayed delete: another process could complete the release and install a
504
+ // replacement lock before that delete resumes.
505
+ // Publishing the exact release marker is the logical handoff point. A
506
+ // contender may win the cleanup claim before this owner does; that is a
507
+ // successful release, not lost ownership, because every acquirer must
508
+ // complete or wait behind the published marker before entering its body.
509
+ return completePublishedRelease(_fs, lockDir, owner, owner, ownerAlive)
510
+ .published;
511
+ }
387
512
  _fs.rmSync(lockDir, { recursive: true, force: true });
388
513
  return true;
389
514
  }