chainlesschain 0.165.1 → 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.
- package/README.md +248 -2
- package/package.json +4 -1
- package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-lS1R0Cza.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-BmlgBPsj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-CNKczv1S.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-BuZGJARQ.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-Dm73Sl-O.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-BGMW7A5e.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BQ6M54JR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-DBzGP0bR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-D1mLCoOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-BUfcN7p8.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-Du0Yg5Pi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-B2RXziHW.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-DWhekOrR.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-HQhrkon4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-izHNb4uz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BDwLy41-.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-DFJhLa7K.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-DYY9RG7-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-rr7tJTtv.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-D2sfHRLi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-q_eGdhha.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-Cufrh2Bz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-B-kdv10f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-CAwRTLZH.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-vx3daNDF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-Q6SwauyD.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-BJ1q4IQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-CSWjROgD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-DVF8Mnns.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-BQP7LOA4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-BNlJCmNc.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-DLLg5WQ4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-CR2skNAF.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Tyd3o2A0.js → MobileProjects-BxVavObl.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B17pgqMI.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-4UmV5cIL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-DoMEXEcU.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-h9DMXFX4.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-Cihn-xX8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-BE6ZGa3R.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-CnwojoSI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-B2vpwmjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-C4z28R0-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-BVlFkwhW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-C28xs4lp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-Dgl_rBjM.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-3QUIhrKI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-8Gh33OZ5.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-BNmg4tZi.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-vgMfeCf9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CUnHbNf-.js → Providers-CXoOa8eX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-BhfTF0oS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-C746HEAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-BSej8Eo0.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-f3gmnlG3.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-CyvybpkN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-DwV2PeYn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-11GrzjZy.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-BjKXkrAY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-eWiFb9Yk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-CdCsMAtt.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-4Q2UTKQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-v4rtYrRj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-CRhsFvd_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-D16zonl7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-Xxozz0i_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-CXJpPtCT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-XRV0L1ZF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-CYuO1i_w.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-BXqitWsu.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BvPrDHIy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-DhCrSTf5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-DmsuZD6K.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-D_yrts5C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-B01ihPrm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-DpUjKa-N.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-CL07yON6.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CLMflnRr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-DtUqe8h_.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-DPKrgVfy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-BTt2GtOm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-D_VeUc1J.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-1kndiylj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-2eyAXE3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-4bna2_SR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-8nz_fmlj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-B9KvcS7R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-BAXfRKfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BHpBMWCh.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-BaZAbw9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-BepLZwgB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-Bhudc_Ez.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-BlDNVgBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-By5n8up9.js} +1 -1
- package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-CHG-KcS4.js} +1 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
- package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-CX81qSM4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-CauoBQkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-CcO04eDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-CtF7EN49.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-CzjkKUmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-D95OFht7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DFIZ_1Sf.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-DG2MZQ3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-DJ8vXGOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-Dbbymxpj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-DdkplL9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-DfGl8CqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-DfKBRdSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-DsJCDUJG.js} +1 -1
- package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-Dwqmtk65.js} +1 -1
- package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-DxdTdBix.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-FzdwcSy2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-GsoUQ2M7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-brCInrzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-dVc-CXY4.js} +1 -1
- package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-fk2Rjacw.js} +3 -3
- package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-l1BETrDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-m31Ka8hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-ocf06EVQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-sDoMHTNA.js} +1 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-CvyYJd7z.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-DYZkhUdz.js} +1 -1
- package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-slnJRVT1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-DVc0CKmy.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-BCXAtHCq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-DBsemz_K.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-BRtkJ-nv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cebq7EZn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-DT7qvId2.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-pQiITLkG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-c-19BVKQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-CxGiMe5n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-D9btVID5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-CUYFsFEN.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-CXmn_35j.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 +14 -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 +4 -0
- package/src/lib/background-agent-supervisor.js +59 -29
- 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 +95 -5
- package/src/runtime/agent-core.js +595 -41
- package/src/workers/background-agent-keeper.js +40 -10
- package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +0 -1
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-B5pLcXUw.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
|
@@ -161,7 +161,7 @@ const DESKTOP_SHORTCUT_KEYS = [
|
|
|
161
161
|
export const PUBLIC_IDE_CAPABILITY_MANIFEST = freeze({
|
|
162
162
|
schema: "chainlesschain.public-ide-capabilities/v1",
|
|
163
163
|
schemaVersion: 1,
|
|
164
|
-
minimumCliVersion: "0.162.
|
|
164
|
+
minimumCliVersion: "0.162.190",
|
|
165
165
|
protocol: buildCompatFixture(),
|
|
166
166
|
bridge: {
|
|
167
167
|
schemaVersion: 1,
|
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
* with a pinned Ed25519 key; WebSocket delivery is transport only and never an
|
|
9
9
|
* authority by itself.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Production integration: the Remote Session registry and WebSocket protocol
|
|
12
|
+
* use this coordinator, the client-hosted approval bridge durably adopts and
|
|
13
|
+
* ACKs its signed leases, and Agent shell dispatch consumes the exact one-shot
|
|
14
|
+
* lease online immediately before the side effect. Side-effect paths without
|
|
15
|
+
* that consume boundary reject Remote approval instead of falling back to a
|
|
16
|
+
* cached decision. This provides cross-host revoke fencing under one online,
|
|
17
|
+
* strongly consistent coordinator; it does not claim quorum availability or
|
|
18
|
+
* undo an external effect after a successful consume has linearized dispatch.
|
|
17
19
|
*/
|
|
18
20
|
|
|
19
21
|
import fs from "node:fs";
|
|
@@ -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)
|
|
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)
|
|
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: "
|
|
27
|
+
volcengine: "deepseek-v4-flash-260425",
|
|
28
28
|
openai: "gpt-4o-mini",
|
|
29
29
|
anthropic: "claude-sonnet-4-6",
|
|
30
30
|
deepseek: "deepseek-chat",
|
|
@@ -88,7 +88,22 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
88
88
|
const incumbent = ownerRead.owner;
|
|
89
89
|
acquisitionError = ownerRead.error;
|
|
90
90
|
if (incumbent) {
|
|
91
|
-
|
|
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)) {
|
|
92
107
|
if (reclaimOwnedDirectory(_fs, lockDir, incumbent, isOwnerAlive)) {
|
|
93
108
|
continue;
|
|
94
109
|
}
|
|
@@ -146,7 +161,7 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
146
161
|
let releaseError = null;
|
|
147
162
|
if (held) {
|
|
148
163
|
try {
|
|
149
|
-
released = releaseOwnedDirectory(_fs, lockDir, owner);
|
|
164
|
+
released = releaseOwnedDirectory(_fs, lockDir, owner, isOwnerAlive);
|
|
150
165
|
} catch (error) {
|
|
151
166
|
releaseError = error;
|
|
152
167
|
}
|
|
@@ -160,8 +175,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
160
175
|
throw error;
|
|
161
176
|
}
|
|
162
177
|
if (held && released && yieldAfterReleaseMs > 0) {
|
|
163
|
-
// The lock is
|
|
164
|
-
//
|
|
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.
|
|
165
181
|
_sleep(yieldAfterReleaseMs);
|
|
166
182
|
}
|
|
167
183
|
return result;
|
|
@@ -383,7 +399,69 @@ function reclaimLegacyDirectory(_fs, lockDir, stat) {
|
|
|
383
399
|
return true;
|
|
384
400
|
}
|
|
385
401
|
|
|
386
|
-
function
|
|
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) {
|
|
387
465
|
if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
|
|
388
466
|
return false;
|
|
389
467
|
}
|
|
@@ -419,6 +497,18 @@ function releaseOwnedDirectory(_fs, lockDir, owner) {
|
|
|
419
497
|
removeOwnMarker(_fs, markerPath, owner);
|
|
420
498
|
return false;
|
|
421
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
|
+
}
|
|
422
512
|
_fs.rmSync(lockDir, { recursive: true, force: true });
|
|
423
513
|
return true;
|
|
424
514
|
}
|