chainlesschain 0.165.2 → 0.165.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -29
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
- package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
- package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
- package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
- package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
- package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
- package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
- package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
- package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
- package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
- package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
- package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
- package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
- package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
- package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
- package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
- package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +105 -35
- package/src/commands/artifacts.js +363 -33
- package/src/commands/cowork.js +202 -36
- package/src/commands/session-budget.js +155 -0
- package/src/commands/session-location.js +948 -0
- package/src/commands/session.js +72 -46
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/artifact-protocol.js +77 -13
- package/src/gateways/ws/session-protocol.js +391 -24
- package/src/gateways/ws/ws-agent-handler.js +191 -14
- package/src/gateways/ws/ws-server.js +14 -3
- package/src/gateways/ws/ws-session-gateway.js +101 -5
- package/src/harness/jsonl-session-store.js +1210 -0
- package/src/lib/agent-team/team-runner.js +43 -13
- package/src/lib/artifact-access-ledger.js +461 -0
- package/src/lib/artifact-cleanup-ledger.js +861 -0
- package/src/lib/artifact-deletion-ledger.js +677 -0
- package/src/lib/artifact-store.js +256 -51
- package/src/lib/background-agent-keeper-protocol.js +75 -4
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +149 -5
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/command-lifecycle-report.js +8 -4
- package/src/lib/cowork-adapter.js +9 -5
- package/src/lib/cowork-task-runner.js +20 -0
- package/src/lib/cowork-workflow.js +178 -20
- package/src/lib/direct-model-usage.js +62 -22
- package/src/lib/dynamic-workflow-runtime.js +3184 -111
- package/src/lib/execution-location-result-apply.js +339 -0
- package/src/lib/execution-location-result-artifact.js +271 -0
- package/src/lib/execution-location-result-review.js +253 -0
- package/src/lib/execution-location-result-store.js +331 -0
- package/src/lib/execution-location-result.js +617 -0
- package/src/lib/execution-location-target.js +236 -1
- package/src/lib/managed-tool-checkpoint.js +47 -4
- package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
- package/src/lib/session-budget-production-root.js +376 -0
- package/src/lib/session-budget-runtime.js +109 -18
- package/src/lib/session-budget-usage.js +63 -0
- package/src/lib/session-host-snapshot.js +19 -2
- package/src/lib/session-resource-budget.js +766 -105
- package/src/lib/sub-agent-context.js +69 -22
- package/src/lib/web-ui-server.js +12 -1
- package/src/lib/with-file-lock.js +175 -26
- package/src/repl/agent-repl.js +237 -16
- package/src/runtime/agent-core.js +123 -0
- package/src/runtime/headless-runner.js +135 -46
- package/src/runtime/headless-stream.js +133 -25
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/workers/background-agent-keeper.js +112 -19
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
|
@@ -23,6 +23,12 @@ import {
|
|
|
23
23
|
import { isGitRepo } from "./git-integration.js";
|
|
24
24
|
import { markRuntimeLedgerPersistenceError } from "./runtime-usage-ledger.js";
|
|
25
25
|
import { IterationBudget } from "./iteration-budget.js";
|
|
26
|
+
import {
|
|
27
|
+
beginSessionBudgetUsage,
|
|
28
|
+
markSessionBudgetUsageUnknown,
|
|
29
|
+
recordSessionBudgetUsage,
|
|
30
|
+
rejectSessionBudgetUsageUnknown,
|
|
31
|
+
} from "./session-budget-usage.js";
|
|
26
32
|
|
|
27
33
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
28
34
|
|
|
@@ -162,6 +168,8 @@ export class SubAgentContext {
|
|
|
162
168
|
* work cannot begin without a durable parent boundary.
|
|
163
169
|
* @param {function} [options.onUsageSettlement] - Synchronous observer for
|
|
164
170
|
* real child token-usage/model-usage-unknown settlements.
|
|
171
|
+
* @param {function} [options.onProviderReceipt] - Synchronous observer for
|
|
172
|
+
* a provider-returned receipt before the matching usage settlement.
|
|
165
173
|
* @param {function} [options.onToolCallBoundary] - Synchronous observer for
|
|
166
174
|
* real child tool-executing boundaries.
|
|
167
175
|
* @param {function} [options.onToolCallSettlement] - Synchronous observer for
|
|
@@ -284,6 +292,10 @@ export class SubAgentContext {
|
|
|
284
292
|
typeof options.onUsageSettlement === "function"
|
|
285
293
|
? options.onUsageSettlement
|
|
286
294
|
: null;
|
|
295
|
+
this._onProviderReceipt =
|
|
296
|
+
typeof options.onProviderReceipt === "function"
|
|
297
|
+
? options.onProviderReceipt
|
|
298
|
+
: null;
|
|
287
299
|
this._onToolCallBoundary =
|
|
288
300
|
typeof options.onToolCallBoundary === "function"
|
|
289
301
|
? options.onToolCallBoundary
|
|
@@ -690,6 +702,7 @@ export class SubAgentContext {
|
|
|
690
702
|
const pendingToolCalls = new Map();
|
|
691
703
|
let observeUsageBoundary = null;
|
|
692
704
|
let observeUsageSettlement = null;
|
|
705
|
+
let observeProviderReceipt = null;
|
|
693
706
|
let observeToolBoundary = null;
|
|
694
707
|
let observeToolSettlement = null;
|
|
695
708
|
const providerReceiptForSettlement = (event) => {
|
|
@@ -739,6 +752,17 @@ export class SubAgentContext {
|
|
|
739
752
|
requireSyncUsageObserver(this._onUsageSettlement, event, "settlement");
|
|
740
753
|
pendingUsageCalls.delete(callId);
|
|
741
754
|
};
|
|
755
|
+
observeProviderReceipt = (event) => {
|
|
756
|
+
const callId = requireUsageCallId(event, "receipt");
|
|
757
|
+
if (!pendingUsageCalls.has(callId)) {
|
|
758
|
+
throw runtimeUsageObserverFailure(
|
|
759
|
+
null,
|
|
760
|
+
"CC_USAGE_RECEIPT_BOUNDARY_MISSING",
|
|
761
|
+
`Child provider receipt has no matching boundary: ${callId}`,
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
requireSyncUsageObserver(this._onProviderReceipt, event, "receipt");
|
|
765
|
+
};
|
|
742
766
|
|
|
743
767
|
// agentLoop yields model-usage-started before provider work. Its consumer
|
|
744
768
|
// runs synchronously before requesting the next event, so a durable host
|
|
@@ -747,6 +771,9 @@ export class SubAgentContext {
|
|
|
747
771
|
// directly, before its provider query, rather than yielding a start event.
|
|
748
772
|
options.onUsageBoundary = observeUsageBoundary;
|
|
749
773
|
options.onUsageSettlement = observeUsageSettlement;
|
|
774
|
+
if (this._onProviderReceipt) {
|
|
775
|
+
options.onProviderReceipt = observeProviderReceipt;
|
|
776
|
+
}
|
|
750
777
|
options.strictUsageTelemetry = true;
|
|
751
778
|
|
|
752
779
|
observeToolBoundary = (event) => {
|
|
@@ -869,21 +896,28 @@ export class SubAgentContext {
|
|
|
869
896
|
}
|
|
870
897
|
}
|
|
871
898
|
if (event.type === "provider-request-receipt") {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
)
|
|
899
|
+
const receipt = Object.freeze({
|
|
900
|
+
protocol: event.protocol,
|
|
901
|
+
provider: event.provider,
|
|
902
|
+
workflowEffectId: event.workflowEffectId,
|
|
903
|
+
callId: event.callId,
|
|
904
|
+
callSequence: event.callSequence,
|
|
905
|
+
source: event.source,
|
|
906
|
+
clientRequestId: event.clientRequestId,
|
|
907
|
+
requestId: event.requestId || null,
|
|
908
|
+
responseId: event.responseId || null,
|
|
909
|
+
requestIdentitySemantics: event.requestIdentitySemantics,
|
|
910
|
+
independentlyReadable: event.independentlyReadable,
|
|
911
|
+
});
|
|
912
|
+
this._providerRequestReceipts.push(receipt);
|
|
913
|
+
if (strictUsageTelemetry && this._onProviderReceipt) {
|
|
914
|
+
observeProviderReceipt({
|
|
915
|
+
type: "provider-request-receipt",
|
|
916
|
+
...receipt,
|
|
917
|
+
workflowRequestSource: receipt.source,
|
|
918
|
+
providerReceipt: { ...receipt },
|
|
919
|
+
});
|
|
920
|
+
}
|
|
887
921
|
}
|
|
888
922
|
|
|
889
923
|
if (event.type === "token-usage") {
|
|
@@ -914,13 +948,11 @@ export class SubAgentContext {
|
|
|
914
948
|
// call into a permanently open `started` row.
|
|
915
949
|
// Attributed usage was already charged by the nested child sharing
|
|
916
950
|
// this same authority, so only direct usage is folded here.
|
|
917
|
-
|
|
918
|
-
this.sessionBudget
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
});
|
|
923
|
-
}
|
|
951
|
+
recordSessionBudgetUsage(
|
|
952
|
+
this.sessionBudget,
|
|
953
|
+
event,
|
|
954
|
+
"sub-agent usage settlement",
|
|
955
|
+
);
|
|
924
956
|
if (this._onUsage) {
|
|
925
957
|
// Forward real usage to the spawner. A nested child's event already
|
|
926
958
|
// carries its own attribution frame — preserve it (deepest wins).
|
|
@@ -954,6 +986,11 @@ export class SubAgentContext {
|
|
|
954
986
|
observeUsageBoundary(event);
|
|
955
987
|
}
|
|
956
988
|
}
|
|
989
|
+
beginSessionBudgetUsage(
|
|
990
|
+
this.sessionBudget,
|
|
991
|
+
event,
|
|
992
|
+
"sub-agent provider call",
|
|
993
|
+
);
|
|
957
994
|
}
|
|
958
995
|
|
|
959
996
|
if (event.type === "tool-executing" && strictUsageTelemetry) {
|
|
@@ -990,6 +1027,10 @@ export class SubAgentContext {
|
|
|
990
1027
|
observeUsageSettlement(forwardedUnknown);
|
|
991
1028
|
forwardedUnknown.ledgerPersisted = true;
|
|
992
1029
|
}
|
|
1030
|
+
const budgetUsageUnknown = markSessionBudgetUsageUnknown(
|
|
1031
|
+
this.sessionBudget,
|
|
1032
|
+
event,
|
|
1033
|
+
);
|
|
993
1034
|
if (this._onUsage) {
|
|
994
1035
|
if (strictUsageTelemetry) {
|
|
995
1036
|
requireSyncUsageObserver(
|
|
@@ -1005,6 +1046,12 @@ export class SubAgentContext {
|
|
|
1005
1046
|
}
|
|
1006
1047
|
}
|
|
1007
1048
|
}
|
|
1049
|
+
if (budgetUsageUnknown) {
|
|
1050
|
+
rejectSessionBudgetUsageUnknown(
|
|
1051
|
+
event,
|
|
1052
|
+
"sub-agent provider call",
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1008
1055
|
}
|
|
1009
1056
|
|
|
1010
1057
|
if (event.type === "tool-executing") {
|
package/src/lib/web-ui-server.js
CHANGED
|
@@ -23,6 +23,7 @@ import crypto from "crypto";
|
|
|
23
23
|
import { fileURLToPath } from "url";
|
|
24
24
|
import { getInlineSource as getEnvelopeInlineSource } from "./web-ui-envelope.js";
|
|
25
25
|
import { CLISkillLoader } from "./skill-loader.js";
|
|
26
|
+
import { authorizeArtifactContentAccess } from "./artifact-access-ledger.js";
|
|
26
27
|
|
|
27
28
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
29
|
|
|
@@ -1333,6 +1334,15 @@ export function handleApiRequest(req, res, ctx = {}) {
|
|
|
1333
1334
|
res.end(JSON.stringify({ error: "Stored copy missing" }));
|
|
1334
1335
|
return;
|
|
1335
1336
|
}
|
|
1337
|
+
const accessIdHeader = req.headers["x-chainlesschain-access-id"];
|
|
1338
|
+
const authorization = authorizeArtifactContentAccess(store, {
|
|
1339
|
+
artifactId: entry.id,
|
|
1340
|
+
client: "web",
|
|
1341
|
+
action: "download",
|
|
1342
|
+
...(typeof accessIdHeader === "string"
|
|
1343
|
+
? { accessId: accessIdHeader }
|
|
1344
|
+
: {}),
|
|
1345
|
+
});
|
|
1336
1346
|
const downloadName =
|
|
1337
1347
|
entry.title && path.extname(entry.title)
|
|
1338
1348
|
? entry.title
|
|
@@ -1343,8 +1353,9 @@ export function handleApiRequest(req, res, ctx = {}) {
|
|
|
1343
1353
|
"Content-Disposition": encodeDispositionFilename(downloadName),
|
|
1344
1354
|
"Cache-Control": "no-store",
|
|
1345
1355
|
"X-Content-Type-Options": "nosniff",
|
|
1356
|
+
"X-ChainlessChain-Access-Digest": authorization.access.eventDigest,
|
|
1346
1357
|
});
|
|
1347
|
-
const stream = fs.createReadStream(storedPath);
|
|
1358
|
+
const stream = fs.createReadStream(authorization.storedPath);
|
|
1348
1359
|
stream.on("error", () => res.destroy());
|
|
1349
1360
|
stream.pipe(res);
|
|
1350
1361
|
} catch (err) {
|
|
@@ -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,6 +94,17 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
88
94
|
const incumbent = ownerRead.owner;
|
|
89
95
|
acquisitionError = ownerRead.error;
|
|
90
96
|
if (incumbent) {
|
|
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
|
+
};
|
|
91
108
|
// The owner writes this exact-token marker only after its critical
|
|
92
109
|
// section has finished. A Windows sharing violation can then prevent
|
|
93
110
|
// the final recursive removal while the owner process remains alive,
|
|
@@ -102,11 +119,19 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
102
119
|
owner,
|
|
103
120
|
isOwnerAlive,
|
|
104
121
|
);
|
|
122
|
+
lastOwnerObservation.releasePublished = publishedRelease.published;
|
|
105
123
|
if (publishedRelease.completed) continue;
|
|
106
|
-
if (!
|
|
107
|
-
|
|
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
|
+
)
|
|
108
134
|
continue;
|
|
109
|
-
}
|
|
110
135
|
}
|
|
111
136
|
} else if (!failIfUnavailable) {
|
|
112
137
|
// Compatibility for legacy best-effort locks that predate owner.json.
|
|
@@ -139,12 +164,21 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
139
164
|
}
|
|
140
165
|
|
|
141
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
|
+
: "";
|
|
142
173
|
const error = new Error(
|
|
143
|
-
`Could not acquire state lock: ${targetPath}`,
|
|
174
|
+
`Could not acquire state lock: ${targetPath}${ownerDetails}`,
|
|
144
175
|
acquisitionError ? { cause: acquisitionError } : undefined,
|
|
145
176
|
);
|
|
146
177
|
error.code = "STATE_LOCK_UNAVAILABLE";
|
|
147
178
|
error.attempts = retryAttempt + 1;
|
|
179
|
+
error.lockOwner = lastOwnerObservation
|
|
180
|
+
? Object.freeze({ ...lastOwnerObservation })
|
|
181
|
+
: null;
|
|
148
182
|
throw error;
|
|
149
183
|
}
|
|
150
184
|
|
|
@@ -152,7 +186,32 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
152
186
|
let bodyThrew = false;
|
|
153
187
|
let result;
|
|
154
188
|
try {
|
|
155
|
-
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
|
+
});
|
|
156
215
|
} catch (error) {
|
|
157
216
|
bodyThrew = true;
|
|
158
217
|
bodyError = error;
|
|
@@ -161,7 +220,13 @@ export function withFileLock(targetPath, fn, opts = {}) {
|
|
|
161
220
|
let releaseError = null;
|
|
162
221
|
if (held) {
|
|
163
222
|
try {
|
|
164
|
-
released = releaseOwnedDirectory(
|
|
223
|
+
released = releaseOwnedDirectory(
|
|
224
|
+
_fs,
|
|
225
|
+
lockDir,
|
|
226
|
+
owner,
|
|
227
|
+
isOwnerAlive,
|
|
228
|
+
releasePublished,
|
|
229
|
+
);
|
|
165
230
|
} catch (error) {
|
|
166
231
|
releaseError = error;
|
|
167
232
|
}
|
|
@@ -251,6 +316,15 @@ function directoryExists(_fs, target) {
|
|
|
251
316
|
}
|
|
252
317
|
}
|
|
253
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
|
+
|
|
254
328
|
function isProcessAlive(pid) {
|
|
255
329
|
try {
|
|
256
330
|
process.kill(pid, 0);
|
|
@@ -361,21 +435,65 @@ function writeOwnerMarker(_fs, markerPath, owner) {
|
|
|
361
435
|
});
|
|
362
436
|
}
|
|
363
437
|
|
|
364
|
-
function reclaimOwnedDirectory(_fs, lockDir,
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
+
}
|
|
371
464
|
}
|
|
465
|
+
|
|
372
466
|
const current = readOwner(_fs, path.join(lockDir, "owner.json"));
|
|
373
|
-
if (!sameOwner(current,
|
|
374
|
-
removeOwnMarker(_fs,
|
|
467
|
+
if (!sameOwner(current, incumbent) || ownerAlive(incumbent)) {
|
|
468
|
+
removeOwnMarker(_fs, claimPath, claimant);
|
|
375
469
|
return false;
|
|
376
470
|
}
|
|
377
|
-
|
|
378
|
-
|
|
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
|
+
}
|
|
379
497
|
}
|
|
380
498
|
|
|
381
499
|
function reclaimLegacyDirectory(_fs, lockDir, stat) {
|
|
@@ -401,9 +519,23 @@ function reclaimLegacyDirectory(_fs, lockDir, stat) {
|
|
|
401
519
|
|
|
402
520
|
function completePublishedRelease(_fs, lockDir, owner, claimant, ownerAlive) {
|
|
403
521
|
const markerPath = path.join(lockDir, `.release-${owner.token}`);
|
|
404
|
-
|
|
522
|
+
const marker = readOwner(_fs, markerPath);
|
|
523
|
+
if (!sameOwner(marker, owner)) {
|
|
405
524
|
return { published: false, completed: false };
|
|
406
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
|
+
}
|
|
407
539
|
const claimPath = path.join(lockDir, `.release-claim-${owner.token}`);
|
|
408
540
|
for (;;) {
|
|
409
541
|
const existingClaim = readOwner(_fs, claimPath);
|
|
@@ -461,9 +593,20 @@ function completePublishedRelease(_fs, lockDir, owner, claimant, ownerAlive) {
|
|
|
461
593
|
}
|
|
462
594
|
}
|
|
463
595
|
|
|
464
|
-
function releaseOwnedDirectory(
|
|
596
|
+
function releaseOwnedDirectory(
|
|
597
|
+
_fs,
|
|
598
|
+
lockDir,
|
|
599
|
+
owner,
|
|
600
|
+
ownerAlive,
|
|
601
|
+
releaseWasPublished = false,
|
|
602
|
+
) {
|
|
465
603
|
if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
|
|
466
|
-
|
|
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;
|
|
467
610
|
}
|
|
468
611
|
// Atomically move the exact owned directory out of the acquisition path,
|
|
469
612
|
// then remove that uniquely-tokened directory. A replacement owner may
|
|
@@ -482,7 +625,7 @@ function releaseOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
|
|
|
482
625
|
}
|
|
483
626
|
return true;
|
|
484
627
|
} catch (error) {
|
|
485
|
-
if (error?.code === "ENOENT") return
|
|
628
|
+
if (error?.code === "ENOENT") return releaseWasPublished;
|
|
486
629
|
// Windows sharing transients fall back to the marker-based release.
|
|
487
630
|
}
|
|
488
631
|
}
|
|
@@ -490,7 +633,13 @@ function releaseOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
|
|
|
490
633
|
try {
|
|
491
634
|
writeOwnerMarker(_fs, markerPath, owner);
|
|
492
635
|
} catch (error) {
|
|
493
|
-
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
|
+
}
|
|
494
643
|
throw error;
|
|
495
644
|
}
|
|
496
645
|
if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
|