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.
- package/README.md +248 -2
- package/package.json +4 -1
- package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-BiHNUlbU.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-D-UFjDRQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-DazVsDgW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-DXh9UfNJ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-CZ6tsceY.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-RyxnBr_h.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BWS51Q6X.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-BRtPcUul.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-CGd4vZYY.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-h8ijVwCg.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-D7tcdDS9.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-NyF6dPl8.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-Ci4OOxr5.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-CwTkc9BM.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-DW_tM82m.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-DEvHRV4S.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BbjBEEfD.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-10IXBaBa.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-BZkbpd5I.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-CFQ8L_6v.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-BDF6AkXe.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-CoOdWMi4.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-DEsGmu8Y.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-BS89SWlK.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-C1FpCHiI.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-BzvpVmLA.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-BeC259PQ.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-azgG3bmV.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-BmSSowc5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-CTBCSyQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-DA5xyk4a.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-D1gPHNig.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-BPmLf1AR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-SVAgNnkt.js → MarkdownRenderer-SVLOg49H.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-DtUFcR0E.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-DS_kqBv-.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-Fvh7XL7V.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-C7itAP45.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-DGbP36-3.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B57MiNLK.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-BXfCUcM7.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-yaJ-ZcF6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-CoaUCKrO.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-BKLM_65R.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-DoDtK9uZ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-BJ28TGr-.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-MuGsdoIb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-BLUN9J4R.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-DWuoBUPd.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-CUi37YPj.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-BYQg2pcr.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-CAxoV2xu.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-B33wGvIO.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-A8yXTORP.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-D49I2I1O.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-Ci73jJDw.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CF-yD6Cn.js +1 -0
- package/src/assets/web-panel/assets/Providers-D-etEhg5.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-Brza0ykJ.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-DFydhjxL.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-DLF8_NhA.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-dyuStW3x.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-5F3tA6yw.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-Ca2A57S5.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-B0bxBkq_.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-CFNvHalZ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-DSoH3Hav.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-BHpfEmBQ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-Dsh-QNmn.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-BhT1NkSU.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-DY9snPeJ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-B5pLcXUw.js → SpeechSettings-CWaX0_Cx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-B690y_TC.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-2N-w3zWd.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-BjwQOQYC.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-CUqHErxY.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-C13rXvNy.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-CpbWcya_.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BXHLGXYC.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-1e5i65o3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-FLGqNHs5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-BbaR_LJB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-NOp4RIov.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-B7DW7bG4.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-BptSUWix.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CAJnDOXS.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-I8LgG1-Z.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-CWe7SOVD.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-Cx3u3CA3.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-CgD5ArwY.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DLv_872g.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-BfQWiXN8.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-3iKP9OTJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-87gMxqhN.js} +1 -1
- package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-B1cX4Rxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-B6yChzt1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BREnPKGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-BUAR7RY-.js} +1 -1
- package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-Bi24S7Dw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-BiI-xPRH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-BuEQSorC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-Bx7BNIRH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-C-rIKccl.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-C0nDKhWv.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-C0ztiqtB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-C1UjHdgZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-CAUnl4Jv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-CMu16vPC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-CNYFQxAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-CQDKYbnv.js} +1 -1
- package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-CWZ3REg6.js} +3 -3
- package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-CcX4FSPf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-Cd79NQAx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-CdRkCwm4.js} +1 -1
- package/src/assets/web-panel/assets/index-CdiGCibe.js +1 -0
- package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-Cy_cCJlr.js} +1 -1
- package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-DAVy98u7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-DCJEY2T0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-DFb-XIKy.js} +1 -1
- package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-DGMYTN5W.js} +1 -1
- package/src/assets/web-panel/assets/index-D_0z-gyz.js +1 -0
- package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-Dcdm_Z2c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-DeizoQVZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DlIyt1qF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-DmgtY58H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-DmrdUwzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-Doc0Ftpd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-DsQcURkA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-DslTuqro.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-V-kIHzAV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-e8BM1yY1.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-DiBszVdG.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-BGIFFozv.js} +1 -1
- package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-D5W7mp7B.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-JFIlxJ7F.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-CQ3o6LIU.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-R8MIlXo3.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-sW7ylSky.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cnl39Vbz.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-sn8tFd14.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-IKKY_QxZ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-BNpV4Nqs.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-BWyT_eoU.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-D09p6_U9.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-CzW3TkJZ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-3s3WoAKB.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-Cjy_673s.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/cowork.js +280 -5
- package/src/commands/plugin.js +819 -64
- package/src/commands/session-location.js +318 -7
- package/src/constants.js +2 -2
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/action-protocol.js +1 -1
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +78 -3
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +208 -34
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +42 -9
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +11 -11
- package/src/lib/dynamic-workflow-runtime.js +2974 -0
- package/src/lib/execution-location-contract.js +111 -7
- package/src/lib/execution-location-runtime.js +3 -1
- package/src/lib/execution-location-target.js +910 -0
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/mcp-call-ledger-store.js +35 -0
- package/src/lib/mcp-call-ledger.js +30 -0
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
- package/src/lib/plugin-runtime/install.js +4956 -657
- package/src/lib/plugin-runtime/manifest.js +3 -1
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
- package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
- package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
- package/src/lib/plugin-runtime/marketplace-network.js +317 -0
- package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
- package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
- package/src/lib/plugin-runtime/policy.js +21 -6
- package/src/lib/plugin-runtime/publisher-trust.js +332 -0
- package/src/lib/plugin-runtime/remote-source.js +323 -27
- package/src/lib/plugin-runtime/scopes.js +317 -41
- package/src/lib/plugin-runtime/transaction-journal.js +806 -0
- package/src/lib/public-ide-capability-manifest.js +1 -1
- package/src/lib/remote-membership-coordinator.js +8 -6
- package/src/lib/sub-agent-context.js +268 -2
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/with-file-lock.js +266 -27
- package/src/runtime/agent-core.js +595 -41
- package/src/workers/background-agent-keeper.js +147 -24
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +0 -1
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
- package/src/assets/web-panel/assets/Providers-CUnHbNf-.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
- 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
|
-
*
|
|
19
|
-
* replacement lock
|
|
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
|
|
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
|
-
|
|
92
|
-
|
|
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({
|
|
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(
|
|
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
|
|
164
|
-
//
|
|
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,
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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,
|
|
358
|
-
removeOwnMarker(_fs,
|
|
467
|
+
if (!sameOwner(current, incumbent) || ownerAlive(incumbent)) {
|
|
468
|
+
removeOwnMarker(_fs, claimPath, claimant);
|
|
359
469
|
return false;
|
|
360
470
|
}
|
|
361
|
-
|
|
362
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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"
|
|
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
|
}
|