chainlesschain 0.164.0 → 0.165.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +248 -2
- package/package.json +7 -4
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
- package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BRCSvYxj.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-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
- package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
- package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvyt-zhm.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-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
- package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
- package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
- package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-dhCeRDMo.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 +559 -62
- 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 +28 -2
- package/src/gateways/ws/action-protocol.js +293 -19
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +542 -35
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +462 -38
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +750 -50
- package/src/harness/remote-session-registry.js +795 -27
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +158 -10
- package/src/lib/background-agent-supervisor.js +870 -48
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +64 -0
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +887 -62
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +1105 -17
- 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/mcp-stdio-package-materialization.js +1 -1
- 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-approval-bridge.js +745 -108
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-coordinator.js +612 -46
- package/src/lib/remote-membership-host-store.js +59 -10
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/shell-approval.js +169 -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 +167 -42
- package/src/repl/agent-repl.js +70 -29
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1695 -194
- package/src/runtime/headless-runner.js +22 -5
- package/src/runtime/headless-stream.js +19 -3
- package/src/workers/background-agent-keeper.js +279 -58
- package/src/workers/background-agent-worker.js +7 -0
- package/src/workers/background-turn-bootstrap.js +29 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
- package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
- package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
|
@@ -18,7 +18,7 @@ import fs from "fs";
|
|
|
18
18
|
import path from "path";
|
|
19
19
|
import broker from "../lib/process-execution-broker/index.js";
|
|
20
20
|
import os from "os";
|
|
21
|
-
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
22
22
|
import { isProxy } from "node:util/types";
|
|
23
23
|
import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
|
|
24
24
|
import sharedShellPolicy from "./coding-agent-shell-policy.cjs";
|
|
@@ -28,6 +28,12 @@ import sharedHookRunner from "../lib/hook-runner.js";
|
|
|
28
28
|
import sharedHookEvents from "../lib/settings-hook-events.js";
|
|
29
29
|
import { mergeProviderOptions } from "../lib/provider-options.js";
|
|
30
30
|
import { applyCredentialProxy } from "../lib/credential-proxy.js";
|
|
31
|
+
import {
|
|
32
|
+
commitShellApprovalSideEffects,
|
|
33
|
+
createShellExecutionDescriptor,
|
|
34
|
+
evaluateShellCommandWithApproval,
|
|
35
|
+
snapshotShellExecutionArgs,
|
|
36
|
+
} from "../lib/shell-approval.js";
|
|
31
37
|
import { describeBackgroundCommand } from "../lib/terminal-context.js";
|
|
32
38
|
import { buildTelemetryAttributes } from "../lib/telemetry-ids.js";
|
|
33
39
|
import {
|
|
@@ -230,6 +236,36 @@ function collectWorkspacePluginBinSandboxPolicy(
|
|
|
230
236
|
);
|
|
231
237
|
}
|
|
232
238
|
|
|
239
|
+
function projectPluginBinInvocationAuthority(invocation) {
|
|
240
|
+
if (!invocation) return null;
|
|
241
|
+
const identity = invocation.executableIdentity || {};
|
|
242
|
+
return {
|
|
243
|
+
command: invocation.command || null,
|
|
244
|
+
runtime: invocation.runtime || null,
|
|
245
|
+
shell: invocation.shell === true,
|
|
246
|
+
pluginId: invocation.pluginId || null,
|
|
247
|
+
pluginVersion: invocation.pluginVersion || null,
|
|
248
|
+
pluginSource: invocation.pluginSource || null,
|
|
249
|
+
scope: invocation.scope || null,
|
|
250
|
+
binName: invocation.binName || null,
|
|
251
|
+
binPath: invocation.binPath || null,
|
|
252
|
+
pluginRoot: invocation.pluginRoot || null,
|
|
253
|
+
args: Array.isArray(invocation.args) ? [...invocation.args] : [],
|
|
254
|
+
requiredBoundaries: [
|
|
255
|
+
...(invocation.sandboxPolicy?.requiredBoundaries || []),
|
|
256
|
+
].sort(),
|
|
257
|
+
executableIdentity: {
|
|
258
|
+
realPath: identity.realPath || null,
|
|
259
|
+
sha256: identity.sha256 || null,
|
|
260
|
+
bytes: identity.bytes ?? null,
|
|
261
|
+
dev: identity.dev ?? null,
|
|
262
|
+
ino: identity.ino ?? null,
|
|
263
|
+
mtimeMs: identity.mtimeMs ?? null,
|
|
264
|
+
mode: identity.mode ?? null,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
233
269
|
function rethrowRunCodeSandboxFailure(error) {
|
|
234
270
|
if (
|
|
235
271
|
error?.sandboxFailClosed === true ||
|
|
@@ -2223,6 +2259,308 @@ function attachDiffReviewAudit(result, audit) {
|
|
|
2223
2259
|
return result;
|
|
2224
2260
|
}
|
|
2225
2261
|
|
|
2262
|
+
const RUN_CODE_ARGUMENT_KEYS = new Set([
|
|
2263
|
+
"language",
|
|
2264
|
+
"code",
|
|
2265
|
+
"timeout",
|
|
2266
|
+
"persist",
|
|
2267
|
+
]);
|
|
2268
|
+
|
|
2269
|
+
function snapshotRunCodeExecutionArgs(args) {
|
|
2270
|
+
if (
|
|
2271
|
+
!args ||
|
|
2272
|
+
typeof args !== "object" ||
|
|
2273
|
+
Array.isArray(args) ||
|
|
2274
|
+
isProxy(args)
|
|
2275
|
+
) {
|
|
2276
|
+
throw new TypeError("run_code arguments must be a plain data object");
|
|
2277
|
+
}
|
|
2278
|
+
const descriptors = Object.getOwnPropertyDescriptors(args);
|
|
2279
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
2280
|
+
if (typeof key !== "string" || !RUN_CODE_ARGUMENT_KEYS.has(key)) {
|
|
2281
|
+
throw new TypeError(`run_code contains unsupported field ${String(key)}`);
|
|
2282
|
+
}
|
|
2283
|
+
if (!Object.hasOwn(descriptors[key], "value")) {
|
|
2284
|
+
throw new TypeError(`run_code field ${key} must be a data property`);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
const language = descriptors.language?.value;
|
|
2288
|
+
const code = descriptors.code?.value;
|
|
2289
|
+
if (typeof language !== "string" || !language) {
|
|
2290
|
+
throw new TypeError("run_code language must be a non-empty string");
|
|
2291
|
+
}
|
|
2292
|
+
if (typeof code !== "string") {
|
|
2293
|
+
throw new TypeError("run_code code must be a string");
|
|
2294
|
+
}
|
|
2295
|
+
const snapshot = { language, code };
|
|
2296
|
+
if (descriptors.timeout) {
|
|
2297
|
+
const timeout = descriptors.timeout.value;
|
|
2298
|
+
if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
|
|
2299
|
+
throw new TypeError("run_code timeout must be a finite number");
|
|
2300
|
+
}
|
|
2301
|
+
snapshot.timeout = timeout;
|
|
2302
|
+
}
|
|
2303
|
+
if (descriptors.persist) {
|
|
2304
|
+
const persist = descriptors.persist.value;
|
|
2305
|
+
if (typeof persist !== "boolean") {
|
|
2306
|
+
throw new TypeError("run_code persist must be a boolean");
|
|
2307
|
+
}
|
|
2308
|
+
snapshot.persist = persist;
|
|
2309
|
+
}
|
|
2310
|
+
return Object.freeze(snapshot);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
function runCodeApprovalDescriptor(args, cwd) {
|
|
2314
|
+
return Object.freeze({
|
|
2315
|
+
language: args.language,
|
|
2316
|
+
codeSha256: createHash("sha256").update(args.code, "utf8").digest("hex"),
|
|
2317
|
+
codeBytes: Buffer.byteLength(args.code, "utf8"),
|
|
2318
|
+
timeout: Math.min(Math.max(args.timeout || 60, 1), 300),
|
|
2319
|
+
persist: args.persist === true,
|
|
2320
|
+
cwd,
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
function canonicalPolicyAuthorityData(value, state = null, depth = 0) {
|
|
2325
|
+
const budget = state || { nodes: 0, seen: new WeakSet() };
|
|
2326
|
+
budget.nodes += 1;
|
|
2327
|
+
if (budget.nodes > 4096 || depth > 12) {
|
|
2328
|
+
throw new TypeError("policy authority exceeds canonical bounds");
|
|
2329
|
+
}
|
|
2330
|
+
if (value === null) return "null";
|
|
2331
|
+
if (value === undefined) return '"$undefined"';
|
|
2332
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
2333
|
+
return JSON.stringify(value);
|
|
2334
|
+
}
|
|
2335
|
+
if (typeof value === "number") {
|
|
2336
|
+
if (!Number.isFinite(value)) {
|
|
2337
|
+
throw new TypeError("policy authority contains a non-finite number");
|
|
2338
|
+
}
|
|
2339
|
+
return JSON.stringify(value);
|
|
2340
|
+
}
|
|
2341
|
+
if (typeof value !== "object" || isProxy(value)) {
|
|
2342
|
+
throw new TypeError("policy authority must contain data objects only");
|
|
2343
|
+
}
|
|
2344
|
+
if (budget.seen.has(value)) {
|
|
2345
|
+
throw new TypeError("policy authority must not contain cycles");
|
|
2346
|
+
}
|
|
2347
|
+
budget.seen.add(value);
|
|
2348
|
+
try {
|
|
2349
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
2350
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
2351
|
+
if (keys.some((key) => typeof key !== "string") || keys.length > 512) {
|
|
2352
|
+
throw new TypeError("policy authority contains invalid keys");
|
|
2353
|
+
}
|
|
2354
|
+
if (Array.isArray(value)) {
|
|
2355
|
+
const length = descriptors.length?.value;
|
|
2356
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > 512) {
|
|
2357
|
+
throw new TypeError("policy authority array exceeds bounds");
|
|
2358
|
+
}
|
|
2359
|
+
const entries = [];
|
|
2360
|
+
for (let index = 0; index < length; index += 1) {
|
|
2361
|
+
const descriptor = descriptors[String(index)];
|
|
2362
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value")) {
|
|
2363
|
+
throw new TypeError("policy authority arrays must be dense data");
|
|
2364
|
+
}
|
|
2365
|
+
entries.push(
|
|
2366
|
+
canonicalPolicyAuthorityData(descriptor.value, budget, depth + 1),
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
const allowedKeys = new Set([
|
|
2370
|
+
"length",
|
|
2371
|
+
...Array.from({ length }, (_, index) => String(index)),
|
|
2372
|
+
]);
|
|
2373
|
+
if (keys.some((key) => !allowedKeys.has(key))) {
|
|
2374
|
+
throw new TypeError("policy authority arrays contain extra fields");
|
|
2375
|
+
}
|
|
2376
|
+
return `[${entries.join(",")}]`;
|
|
2377
|
+
}
|
|
2378
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2379
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
2380
|
+
throw new TypeError("policy authority must contain plain objects");
|
|
2381
|
+
}
|
|
2382
|
+
return `{${keys
|
|
2383
|
+
.sort()
|
|
2384
|
+
.map((key) => {
|
|
2385
|
+
const descriptor = descriptors[key];
|
|
2386
|
+
if (!Object.hasOwn(descriptor, "value")) {
|
|
2387
|
+
throw new TypeError("policy authority accessors are not allowed");
|
|
2388
|
+
}
|
|
2389
|
+
return `${JSON.stringify(key)}:${canonicalPolicyAuthorityData(
|
|
2390
|
+
descriptor.value,
|
|
2391
|
+
budget,
|
|
2392
|
+
depth + 1,
|
|
2393
|
+
)}`;
|
|
2394
|
+
})
|
|
2395
|
+
.join(",")}}`;
|
|
2396
|
+
} finally {
|
|
2397
|
+
budget.seen.delete(value);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
function digestPolicyAuthority(projection) {
|
|
2402
|
+
return createHash("sha256")
|
|
2403
|
+
.update("chainlesschain.shell-policy-authority.v1\0", "utf8")
|
|
2404
|
+
.update(canonicalPolicyAuthorityData(projection), "utf8")
|
|
2405
|
+
.digest("hex");
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
function validatePermissionRuleset(rules) {
|
|
2409
|
+
if (!rules) return null;
|
|
2410
|
+
if (typeof rules !== "object" || Array.isArray(rules)) {
|
|
2411
|
+
throw new TypeError(
|
|
2412
|
+
"permission authority provider returned an invalid ruleset",
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
const normalized = {};
|
|
2416
|
+
for (const key of ["allow", "ask", "deny"]) {
|
|
2417
|
+
const value = rules[key];
|
|
2418
|
+
if (value !== undefined && !Array.isArray(value)) {
|
|
2419
|
+
throw new TypeError(
|
|
2420
|
+
"permission authority provider returned an invalid ruleset",
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
normalized[key] = value ? [...value] : [];
|
|
2424
|
+
}
|
|
2425
|
+
return normalized;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
function projectPermissionAuthority({ authority, rules, tool, args, cwd }) {
|
|
2429
|
+
const verdict = rules
|
|
2430
|
+
? evaluatePermissionRules({ tool, args, cwd, rules })
|
|
2431
|
+
: { decision: null, rule: null };
|
|
2432
|
+
const source = verdict.rule
|
|
2433
|
+
? authority?.sources?.[`${verdict.decision}:${verdict.rule}`] || null
|
|
2434
|
+
: null;
|
|
2435
|
+
const scopedRule = source?.startsWith("scoped:")
|
|
2436
|
+
? authority?.scoped?.rules?.find(
|
|
2437
|
+
(record) =>
|
|
2438
|
+
record.status === "active" && `scoped:${record.id}` === source,
|
|
2439
|
+
) || null
|
|
2440
|
+
: null;
|
|
2441
|
+
return {
|
|
2442
|
+
rules: rules || null,
|
|
2443
|
+
sources: authority?.sources || null,
|
|
2444
|
+
scoped: authority?.scoped || null,
|
|
2445
|
+
version: authority?.version || authority?.policyVersion || null,
|
|
2446
|
+
revision: authority?.revision ?? null,
|
|
2447
|
+
headHash: authority?.headHash || null,
|
|
2448
|
+
verdict: {
|
|
2449
|
+
decision: verdict.decision || null,
|
|
2450
|
+
rule: verdict.rule || null,
|
|
2451
|
+
source,
|
|
2452
|
+
scopedRule: scopedRule
|
|
2453
|
+
? {
|
|
2454
|
+
id: scopedRule.id || null,
|
|
2455
|
+
revision: scopedRule.revision ?? null,
|
|
2456
|
+
expiresAt: scopedRule.expiresAt ?? null,
|
|
2457
|
+
status: scopedRule.status || null,
|
|
2458
|
+
}
|
|
2459
|
+
: null,
|
|
2460
|
+
},
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
function selectedHostToolPolicy(hostAuthority, tool) {
|
|
2465
|
+
const policies = hostAuthority?.tools || hostAuthority?.toolPolicies || null;
|
|
2466
|
+
return policies && typeof policies === "object"
|
|
2467
|
+
? policies[tool] || null
|
|
2468
|
+
: null;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
function snapshotPlanToolAuthority(planManager, tool, args) {
|
|
2472
|
+
const active = planManager?.isActive?.() === true;
|
|
2473
|
+
return {
|
|
2474
|
+
active,
|
|
2475
|
+
executionLockActive: planManager?.executionLock != null,
|
|
2476
|
+
toolAllowed:
|
|
2477
|
+
!active || (tool === "git" && isReadOnlyGitCommand(args?.command))
|
|
2478
|
+
? true
|
|
2479
|
+
: planManager?.isToolAllowed?.(tool) === true,
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
function snapshotEffectiveToolSetAuthority(effectiveAllowedToolNames, tool) {
|
|
2484
|
+
const enforced = Array.isArray(effectiveAllowedToolNames);
|
|
2485
|
+
return {
|
|
2486
|
+
enforced,
|
|
2487
|
+
allowed: !enforced || effectiveAllowedToolNames.includes(tool),
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
function evaluateSelectedToolAdmissionAuthority({
|
|
2492
|
+
admission,
|
|
2493
|
+
tool,
|
|
2494
|
+
runtimeDescriptor,
|
|
2495
|
+
localToolDescriptor,
|
|
2496
|
+
hostManagedToolPolicy,
|
|
2497
|
+
}) {
|
|
2498
|
+
if (admission?.enforce !== true) {
|
|
2499
|
+
return {
|
|
2500
|
+
override: {},
|
|
2501
|
+
decision: null,
|
|
2502
|
+
projection: { enforce: false },
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
const override =
|
|
2506
|
+
admission.tools && typeof admission.tools === "object"
|
|
2507
|
+
? admission.tools[tool] || {}
|
|
2508
|
+
: {};
|
|
2509
|
+
const tier =
|
|
2510
|
+
override.tier ||
|
|
2511
|
+
runtimeDescriptor?.tier ||
|
|
2512
|
+
getCodingAgentToolPolicy(tool)?.tier ||
|
|
2513
|
+
(localToolDescriptor ? "extension" : "mvp");
|
|
2514
|
+
const inputs = {
|
|
2515
|
+
capabilityGranted:
|
|
2516
|
+
override.capabilityGranted ?? admission.capabilityGranted,
|
|
2517
|
+
policyAllowed:
|
|
2518
|
+
override.policyAllowed ??
|
|
2519
|
+
(hostManagedToolPolicy
|
|
2520
|
+
? (hostManagedToolPolicy?.tools ||
|
|
2521
|
+
hostManagedToolPolicy?.toolPolicies ||
|
|
2522
|
+
{})[tool]?.allowed !== false
|
|
2523
|
+
: admission.policyAllowed),
|
|
2524
|
+
permissionGranted:
|
|
2525
|
+
override.permissionGranted ?? admission.permissionGranted,
|
|
2526
|
+
budgetOk: override.budgetOk ?? admission.budgetOk,
|
|
2527
|
+
uiSupported: override.uiSupported ?? admission.uiSupported,
|
|
2528
|
+
};
|
|
2529
|
+
const decision = admitTool({ tool, tier, ...inputs });
|
|
2530
|
+
return {
|
|
2531
|
+
override,
|
|
2532
|
+
decision,
|
|
2533
|
+
projection: {
|
|
2534
|
+
enforce: true,
|
|
2535
|
+
tier: decision.tier,
|
|
2536
|
+
capabilityGranted: inputs.capabilityGranted === true,
|
|
2537
|
+
policyAllowed: inputs.policyAllowed === true,
|
|
2538
|
+
permissionGranted: inputs.permissionGranted === true,
|
|
2539
|
+
budgetOk: inputs.budgetOk === true,
|
|
2540
|
+
uiSupported: inputs.uiSupported === true,
|
|
2541
|
+
admitted: decision.admitted === true,
|
|
2542
|
+
unmet: [...decision.unmet],
|
|
2543
|
+
},
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function snapshotApprovalPolicyAuthority(approvalGate, sessionId) {
|
|
2548
|
+
if (!approvalGate) return null;
|
|
2549
|
+
const snapshot = approvalGate.getAuthorizationPolicySnapshot?.(sessionId);
|
|
2550
|
+
if (snapshot && typeof snapshot.then === "function") {
|
|
2551
|
+
throw new TypeError("approval policy snapshot must be synchronous");
|
|
2552
|
+
}
|
|
2553
|
+
if (snapshot) return snapshot;
|
|
2554
|
+
return {
|
|
2555
|
+
schema: "chainlesschain.approval-policy-authority/v1",
|
|
2556
|
+
kind: "legacy-gate",
|
|
2557
|
+
sessionId: sessionId ? String(sessionId) : null,
|
|
2558
|
+
policy: approvalGate.getSessionPolicy?.(sessionId) || null,
|
|
2559
|
+
hasAuthorizationConsumer:
|
|
2560
|
+
approvalGate.hasAuthorizationConsumer?.() === true,
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2226
2564
|
/**
|
|
2227
2565
|
* Execute a single tool call with plan-mode filtering and hook pipeline.
|
|
2228
2566
|
*
|
|
@@ -2235,9 +2573,99 @@ function attachDiffReviewAudit(result, audit) {
|
|
|
2235
2573
|
* @returns {Promise<object>} tool result
|
|
2236
2574
|
*/
|
|
2237
2575
|
export async function executeTool(name, args, context = {}) {
|
|
2576
|
+
const liveExecutionContext = context;
|
|
2577
|
+
// run_shell can cross several asynchronous permission/hook boundaries before
|
|
2578
|
+
// reaching executeToolInner. Capture its declared tuple and working directory
|
|
2579
|
+
// before any of them so an in-process caller cannot swap the approved command
|
|
2580
|
+
// or reinterpret a relative cwd after process.chdir().
|
|
2581
|
+
let shellAuthorityCwd = null;
|
|
2582
|
+
let shellAuthorityWorkspaceCwd = null;
|
|
2583
|
+
let runCodeAuthorityCwd = null;
|
|
2584
|
+
if (name === "run_shell") {
|
|
2585
|
+
try {
|
|
2586
|
+
const entryProcessCwd = process.cwd();
|
|
2587
|
+
const declared = snapshotShellExecutionArgs(args);
|
|
2588
|
+
const contextCwd =
|
|
2589
|
+
context.cwd === undefined || context.cwd === null
|
|
2590
|
+
? entryProcessCwd
|
|
2591
|
+
: context.cwd;
|
|
2592
|
+
if (typeof contextCwd !== "string" || !contextCwd) {
|
|
2593
|
+
throw new TypeError("run_shell context cwd must be a non-empty string");
|
|
2594
|
+
}
|
|
2595
|
+
if (
|
|
2596
|
+
declared.cwd !== undefined &&
|
|
2597
|
+
(typeof declared.cwd !== "string" || !declared.cwd)
|
|
2598
|
+
) {
|
|
2599
|
+
throw new TypeError("run_shell cwd must be a non-empty string");
|
|
2600
|
+
}
|
|
2601
|
+
shellAuthorityWorkspaceCwd = fs.realpathSync.native(
|
|
2602
|
+
path.resolve(entryProcessCwd, contextCwd),
|
|
2603
|
+
);
|
|
2604
|
+
if (!fs.statSync(shellAuthorityWorkspaceCwd).isDirectory()) {
|
|
2605
|
+
throw new Error("run_shell context cwd is not a directory");
|
|
2606
|
+
}
|
|
2607
|
+
const requestedCwd = path.resolve(
|
|
2608
|
+
shellAuthorityWorkspaceCwd,
|
|
2609
|
+
declared.cwd || ".",
|
|
2610
|
+
);
|
|
2611
|
+
shellAuthorityCwd = fs.realpathSync.native(requestedCwd);
|
|
2612
|
+
if (!fs.statSync(shellAuthorityCwd).isDirectory()) {
|
|
2613
|
+
throw new Error("run_shell cwd is not a directory");
|
|
2614
|
+
}
|
|
2615
|
+
args = snapshotShellExecutionArgs({
|
|
2616
|
+
...declared,
|
|
2617
|
+
cwd: shellAuthorityCwd,
|
|
2618
|
+
});
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
return {
|
|
2621
|
+
error: `[Shell Dispatch] ${error?.message || "invalid execution arguments"}`,
|
|
2622
|
+
policy: {
|
|
2623
|
+
decision: "blocked",
|
|
2624
|
+
via: "shell-execution-snapshot",
|
|
2625
|
+
code: "CC_SHELL_EXECUTION_SNAPSHOT_INVALID",
|
|
2626
|
+
},
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if (name === "run_code") {
|
|
2631
|
+
try {
|
|
2632
|
+
const entryProcessCwd = process.cwd();
|
|
2633
|
+
args = snapshotRunCodeExecutionArgs(args);
|
|
2634
|
+
const contextCwd =
|
|
2635
|
+
context.cwd === undefined || context.cwd === null
|
|
2636
|
+
? entryProcessCwd
|
|
2637
|
+
: context.cwd;
|
|
2638
|
+
if (typeof contextCwd !== "string" || !contextCwd) {
|
|
2639
|
+
throw new TypeError("run_code context cwd must be a non-empty string");
|
|
2640
|
+
}
|
|
2641
|
+
runCodeAuthorityCwd = fs.realpathSync.native(
|
|
2642
|
+
path.resolve(entryProcessCwd, contextCwd),
|
|
2643
|
+
);
|
|
2644
|
+
if (!fs.statSync(runCodeAuthorityCwd).isDirectory()) {
|
|
2645
|
+
throw new Error("run_code cwd is not a directory");
|
|
2646
|
+
}
|
|
2647
|
+
} catch (error) {
|
|
2648
|
+
return {
|
|
2649
|
+
error: `[Run Code Dispatch] ${error?.message || "invalid execution arguments"}`,
|
|
2650
|
+
policy: {
|
|
2651
|
+
decision: "blocked",
|
|
2652
|
+
via: "run-code-execution-snapshot",
|
|
2653
|
+
code: "CC_RUN_CODE_EXECUTION_SNAPSHOT_INVALID",
|
|
2654
|
+
},
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2238
2658
|
const hookDb = context.hookDb || null;
|
|
2239
2659
|
const skillLoader = context.skillLoader || _defaultSkillLoader;
|
|
2240
|
-
|
|
2660
|
+
// Keep the authority-bearing workspace root distinct from the command's
|
|
2661
|
+
// requested cwd. A command may run in a nested directory, but Plugin policy
|
|
2662
|
+
// discovery and strong-sandbox issuance must remain anchored to the host
|
|
2663
|
+
// workspace that admitted the tool call.
|
|
2664
|
+
const cwd =
|
|
2665
|
+
shellAuthorityWorkspaceCwd ||
|
|
2666
|
+
runCodeAuthorityCwd ||
|
|
2667
|
+
context.cwd ||
|
|
2668
|
+
process.cwd();
|
|
2241
2669
|
const planManager = context.planManager || getPlanModeManager();
|
|
2242
2670
|
// The provider receives a filtered schema, but an untrusted/buggy model can
|
|
2243
2671
|
// still emit an arbitrary tool_call. Reuse the same pure preflight that the
|
|
@@ -2247,6 +2675,27 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2247
2675
|
cwd,
|
|
2248
2676
|
});
|
|
2249
2677
|
if (authorityDenial) return authorityDenial;
|
|
2678
|
+
let admittedEffectiveToolSetDigest = null;
|
|
2679
|
+
if (name === "run_shell") {
|
|
2680
|
+
try {
|
|
2681
|
+
admittedEffectiveToolSetDigest = digestPolicyAuthority(
|
|
2682
|
+
snapshotEffectiveToolSetAuthority(
|
|
2683
|
+
context.effectiveAllowedToolNames,
|
|
2684
|
+
name,
|
|
2685
|
+
),
|
|
2686
|
+
);
|
|
2687
|
+
} catch (error) {
|
|
2688
|
+
return {
|
|
2689
|
+
error:
|
|
2690
|
+
"[Shell Dispatch] The effective tool capability ceiling could not be bound. No command side effect was started.",
|
|
2691
|
+
policy: {
|
|
2692
|
+
decision: "blocked",
|
|
2693
|
+
via: "shell-policy-authority",
|
|
2694
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
2695
|
+
},
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2250
2699
|
const localToolDescriptor =
|
|
2251
2700
|
context.externalToolDescriptors &&
|
|
2252
2701
|
typeof context.externalToolDescriptors === "object"
|
|
@@ -2259,41 +2708,41 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2259
2708
|
: null;
|
|
2260
2709
|
const runtimeDescriptor =
|
|
2261
2710
|
getRuntimeToolDescriptor(name) || localToolDescriptor;
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
: {};
|
|
2268
|
-
const tier =
|
|
2269
|
-
override.tier ||
|
|
2270
|
-
runtimeDescriptor?.tier ||
|
|
2271
|
-
getCodingAgentToolPolicy(name)?.tier ||
|
|
2272
|
-
(localToolDescriptor ? "extension" : "mvp");
|
|
2273
|
-
const decision = admitTool({
|
|
2711
|
+
let admittedToolAdmissionDigest = null;
|
|
2712
|
+
let toolAdmissionEvaluation;
|
|
2713
|
+
try {
|
|
2714
|
+
toolAdmissionEvaluation = evaluateSelectedToolAdmissionAuthority({
|
|
2715
|
+
admission: context.toolAdmission,
|
|
2274
2716
|
tool: name,
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
policyAllowed:
|
|
2279
|
-
override.policyAllowed ??
|
|
2280
|
-
(context.hostManagedToolPolicy
|
|
2281
|
-
? (context.hostManagedToolPolicy?.tools ||
|
|
2282
|
-
context.hostManagedToolPolicy?.toolPolicies ||
|
|
2283
|
-
{})[name]?.allowed !== false
|
|
2284
|
-
: admission.policyAllowed),
|
|
2285
|
-
permissionGranted:
|
|
2286
|
-
override.permissionGranted ?? admission.permissionGranted,
|
|
2287
|
-
budgetOk: override.budgetOk ?? admission.budgetOk,
|
|
2288
|
-
uiSupported: override.uiSupported ?? admission.uiSupported,
|
|
2717
|
+
runtimeDescriptor,
|
|
2718
|
+
localToolDescriptor,
|
|
2719
|
+
hostManagedToolPolicy: context.hostManagedToolPolicy,
|
|
2289
2720
|
});
|
|
2721
|
+
if (name === "run_shell") {
|
|
2722
|
+
admittedToolAdmissionDigest = digestPolicyAuthority(
|
|
2723
|
+
toolAdmissionEvaluation.projection,
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
} catch (error) {
|
|
2727
|
+
return {
|
|
2728
|
+
error:
|
|
2729
|
+
"[Tool Admission] The selected tool authority could not be bound. No tool side effect was started.",
|
|
2730
|
+
policy: {
|
|
2731
|
+
decision: "blocked",
|
|
2732
|
+
via: "tool-admission",
|
|
2733
|
+
code: error?.code || "CC_TOOL_ADMISSION_AUTHORITY_UNAVAILABLE",
|
|
2734
|
+
},
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
if (toolAdmissionEvaluation.decision) {
|
|
2738
|
+
const { override, decision } = toolAdmissionEvaluation;
|
|
2290
2739
|
const attribution = buildToolAttribution({
|
|
2291
2740
|
tool: name,
|
|
2292
2741
|
source:
|
|
2293
2742
|
override.source ||
|
|
2294
2743
|
runtimeDescriptor?.source ||
|
|
2295
2744
|
localToolDescriptor?.source ||
|
|
2296
|
-
|
|
2745
|
+
context.toolAdmission?.source ||
|
|
2297
2746
|
null,
|
|
2298
2747
|
version: override.version || runtimeDescriptor?.version || null,
|
|
2299
2748
|
scope: override.scope || localToolDescriptor?.scope || null,
|
|
@@ -2468,6 +2917,30 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2468
2917
|
: null,
|
|
2469
2918
|
scopedRule: matchedScopedRule || null,
|
|
2470
2919
|
};
|
|
2920
|
+
let admittedPermissionPolicyDigest = null;
|
|
2921
|
+
if (name === "run_shell") {
|
|
2922
|
+
try {
|
|
2923
|
+
admittedPermissionPolicyDigest = digestPolicyAuthority(
|
|
2924
|
+
projectPermissionAuthority({
|
|
2925
|
+
authority: permissionAuthority,
|
|
2926
|
+
rules: validatePermissionRuleset(effectivePermissionRules),
|
|
2927
|
+
tool: name,
|
|
2928
|
+
args,
|
|
2929
|
+
cwd,
|
|
2930
|
+
}),
|
|
2931
|
+
);
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
return {
|
|
2934
|
+
error:
|
|
2935
|
+
"[Shell Dispatch] The evaluated permission authority could not be bound. No command side effect was started.",
|
|
2936
|
+
policy: {
|
|
2937
|
+
decision: "blocked",
|
|
2938
|
+
via: "shell-policy-authority",
|
|
2939
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
2940
|
+
},
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2471
2944
|
|
|
2472
2945
|
// 1. settings deny
|
|
2473
2946
|
if (settingsVerdict.decision === "deny") {
|
|
@@ -2497,6 +2970,22 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2497
2970
|
toolPolicies && typeof toolPolicies === "object"
|
|
2498
2971
|
? toolPolicies[name]
|
|
2499
2972
|
: null;
|
|
2973
|
+
let admittedHostPolicyDigest = null;
|
|
2974
|
+
if (name === "run_shell") {
|
|
2975
|
+
try {
|
|
2976
|
+
admittedHostPolicyDigest = digestPolicyAuthority(hostToolPolicy);
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
return {
|
|
2979
|
+
error:
|
|
2980
|
+
"[Shell Dispatch] The evaluated host policy could not be bound. No command side effect was started.",
|
|
2981
|
+
policy: {
|
|
2982
|
+
decision: "blocked",
|
|
2983
|
+
via: "shell-policy-authority",
|
|
2984
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
2985
|
+
},
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2500
2989
|
const hostPolicyAllowsReadOnlyGit =
|
|
2501
2990
|
name === "git" &&
|
|
2502
2991
|
hostToolPolicy?.planModeBehavior === "readonly-conditional" &&
|
|
@@ -2530,6 +3019,24 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2530
3019
|
planManager.isActive() && !executionLockActive;
|
|
2531
3020
|
const externalToolBlockedDuringPlanning =
|
|
2532
3021
|
planReadOnlyFenceActive && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
3022
|
+
let admittedPlanPolicyDigest = null;
|
|
3023
|
+
if (name === "run_shell") {
|
|
3024
|
+
try {
|
|
3025
|
+
admittedPlanPolicyDigest = digestPolicyAuthority(
|
|
3026
|
+
snapshotPlanToolAuthority(planManager, name, args),
|
|
3027
|
+
);
|
|
3028
|
+
} catch (error) {
|
|
3029
|
+
return {
|
|
3030
|
+
error:
|
|
3031
|
+
"[Shell Dispatch] The evaluated plan authority could not be bound. No command side effect was started.",
|
|
3032
|
+
policy: {
|
|
3033
|
+
decision: "blocked",
|
|
3034
|
+
via: "shell-policy-authority",
|
|
3035
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
3036
|
+
},
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
2533
3040
|
context = { ...context, planReadOnlyFenceActive };
|
|
2534
3041
|
if (
|
|
2535
3042
|
planManager.isActive() &&
|
|
@@ -2828,6 +3335,160 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2828
3335
|
if (instructionPreflight) return instructionPreflight;
|
|
2829
3336
|
}
|
|
2830
3337
|
|
|
3338
|
+
let shellDispatchPolicyAuthority = null;
|
|
3339
|
+
if (name === "run_shell") {
|
|
3340
|
+
try {
|
|
3341
|
+
const permissionRulesProvider = context.permissionRulesProvider || null;
|
|
3342
|
+
const approvalGate = context.approvalGate || null;
|
|
3343
|
+
const initialProjection = {
|
|
3344
|
+
permission: projectPermissionAuthority({
|
|
3345
|
+
authority: permissionAuthority,
|
|
3346
|
+
rules: validatePermissionRuleset(effectivePermissionRules),
|
|
3347
|
+
tool: name,
|
|
3348
|
+
args,
|
|
3349
|
+
cwd,
|
|
3350
|
+
}),
|
|
3351
|
+
host: selectedHostToolPolicy(
|
|
3352
|
+
context.hostManagedToolPolicy || null,
|
|
3353
|
+
name,
|
|
3354
|
+
),
|
|
3355
|
+
plan: snapshotPlanToolAuthority(planManager, name, args),
|
|
3356
|
+
effectiveToolSet: snapshotEffectiveToolSetAuthority(
|
|
3357
|
+
context.effectiveAllowedToolNames,
|
|
3358
|
+
name,
|
|
3359
|
+
),
|
|
3360
|
+
toolAdmission: evaluateSelectedToolAdmissionAuthority({
|
|
3361
|
+
admission: context.toolAdmission,
|
|
3362
|
+
tool: name,
|
|
3363
|
+
runtimeDescriptor,
|
|
3364
|
+
localToolDescriptor,
|
|
3365
|
+
hostManagedToolPolicy: context.hostManagedToolPolicy,
|
|
3366
|
+
}).projection,
|
|
3367
|
+
approval: snapshotApprovalPolicyAuthority(
|
|
3368
|
+
approvalGate,
|
|
3369
|
+
context.sessionId || null,
|
|
3370
|
+
),
|
|
3371
|
+
unattended: context.unattendedActionPolicy || null,
|
|
3372
|
+
shellPolicyOverrides: context.shellPolicyOverrides || null,
|
|
3373
|
+
classifyAllShell: context.classifyAllShell === true,
|
|
3374
|
+
};
|
|
3375
|
+
if (
|
|
3376
|
+
digestPolicyAuthority(initialProjection.permission) !==
|
|
3377
|
+
admittedPermissionPolicyDigest ||
|
|
3378
|
+
digestPolicyAuthority(initialProjection.host) !==
|
|
3379
|
+
admittedHostPolicyDigest ||
|
|
3380
|
+
digestPolicyAuthority(initialProjection.plan) !==
|
|
3381
|
+
admittedPlanPolicyDigest ||
|
|
3382
|
+
digestPolicyAuthority(initialProjection.effectiveToolSet) !==
|
|
3383
|
+
admittedEffectiveToolSetDigest ||
|
|
3384
|
+
digestPolicyAuthority(initialProjection.toolAdmission) !==
|
|
3385
|
+
admittedToolAdmissionDigest
|
|
3386
|
+
) {
|
|
3387
|
+
const error = new Error(
|
|
3388
|
+
"Shell policy authority changed while an earlier confirmation was pending",
|
|
3389
|
+
);
|
|
3390
|
+
error.code = "CC_SHELL_POLICY_AUTHORITY_CHANGED";
|
|
3391
|
+
throw error;
|
|
3392
|
+
}
|
|
3393
|
+
const initialDigest = digestPolicyAuthority(initialProjection);
|
|
3394
|
+
shellDispatchPolicyAuthority = Object.freeze({
|
|
3395
|
+
policyVersion: `cc-shell-policy-authority/v1:${initialDigest}`,
|
|
3396
|
+
async revalidate() {
|
|
3397
|
+
try {
|
|
3398
|
+
if (
|
|
3399
|
+
(liveExecutionContext.permissionRulesProvider || null) !==
|
|
3400
|
+
permissionRulesProvider ||
|
|
3401
|
+
(liveExecutionContext.approvalGate || null) !== approvalGate ||
|
|
3402
|
+
(liveExecutionContext.planManager || getPlanModeManager()) !==
|
|
3403
|
+
planManager
|
|
3404
|
+
) {
|
|
3405
|
+
const error = new Error("policy authority provider changed");
|
|
3406
|
+
error.code = "CC_SHELL_POLICY_AUTHORITY_CHANGED";
|
|
3407
|
+
throw error;
|
|
3408
|
+
}
|
|
3409
|
+
let currentPermissionAuthority = null;
|
|
3410
|
+
let currentPermissionRules =
|
|
3411
|
+
liveExecutionContext.permissionRules || null;
|
|
3412
|
+
if (permissionRulesProvider) {
|
|
3413
|
+
currentPermissionAuthority = await permissionRulesProvider({
|
|
3414
|
+
cwd,
|
|
3415
|
+
tool: name,
|
|
3416
|
+
args,
|
|
3417
|
+
});
|
|
3418
|
+
currentPermissionRules =
|
|
3419
|
+
currentPermissionAuthority?.rules ||
|
|
3420
|
+
currentPermissionAuthority ||
|
|
3421
|
+
null;
|
|
3422
|
+
}
|
|
3423
|
+
const currentProjection = {
|
|
3424
|
+
permission: projectPermissionAuthority({
|
|
3425
|
+
authority: currentPermissionAuthority,
|
|
3426
|
+
rules: validatePermissionRuleset(currentPermissionRules),
|
|
3427
|
+
tool: name,
|
|
3428
|
+
args,
|
|
3429
|
+
cwd,
|
|
3430
|
+
}),
|
|
3431
|
+
host: selectedHostToolPolicy(
|
|
3432
|
+
liveExecutionContext.hostManagedToolPolicy || null,
|
|
3433
|
+
name,
|
|
3434
|
+
),
|
|
3435
|
+
plan: snapshotPlanToolAuthority(planManager, name, args),
|
|
3436
|
+
effectiveToolSet: snapshotEffectiveToolSetAuthority(
|
|
3437
|
+
liveExecutionContext.effectiveAllowedToolNames,
|
|
3438
|
+
name,
|
|
3439
|
+
),
|
|
3440
|
+
toolAdmission: evaluateSelectedToolAdmissionAuthority({
|
|
3441
|
+
admission: liveExecutionContext.toolAdmission,
|
|
3442
|
+
tool: name,
|
|
3443
|
+
runtimeDescriptor,
|
|
3444
|
+
localToolDescriptor,
|
|
3445
|
+
hostManagedToolPolicy:
|
|
3446
|
+
liveExecutionContext.hostManagedToolPolicy,
|
|
3447
|
+
}).projection,
|
|
3448
|
+
approval: snapshotApprovalPolicyAuthority(
|
|
3449
|
+
approvalGate,
|
|
3450
|
+
context.sessionId || null,
|
|
3451
|
+
),
|
|
3452
|
+
unattended: liveExecutionContext.unattendedActionPolicy || null,
|
|
3453
|
+
shellPolicyOverrides:
|
|
3454
|
+
liveExecutionContext.shellPolicyOverrides || null,
|
|
3455
|
+
classifyAllShell: liveExecutionContext.classifyAllShell === true,
|
|
3456
|
+
};
|
|
3457
|
+
if (digestPolicyAuthority(currentProjection) !== initialDigest) {
|
|
3458
|
+
const error = new Error(
|
|
3459
|
+
"Shell permission authority changed after initial admission",
|
|
3460
|
+
);
|
|
3461
|
+
error.code = "CC_SHELL_POLICY_AUTHORITY_CHANGED";
|
|
3462
|
+
throw error;
|
|
3463
|
+
}
|
|
3464
|
+
return true;
|
|
3465
|
+
} catch (error) {
|
|
3466
|
+
if (error?.code === "CC_SHELL_POLICY_AUTHORITY_CHANGED") {
|
|
3467
|
+
throw error;
|
|
3468
|
+
}
|
|
3469
|
+
const wrapped = new Error(
|
|
3470
|
+
"Current shell permission authority could not be revalidated",
|
|
3471
|
+
{ cause: error },
|
|
3472
|
+
);
|
|
3473
|
+
wrapped.code =
|
|
3474
|
+
error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE";
|
|
3475
|
+
throw wrapped;
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
});
|
|
3479
|
+
} catch (error) {
|
|
3480
|
+
return {
|
|
3481
|
+
error:
|
|
3482
|
+
"[Shell Dispatch] The current permission authority could not be bound. No command side effect was started.",
|
|
3483
|
+
policy: {
|
|
3484
|
+
decision: "blocked",
|
|
3485
|
+
via: "shell-policy-authority",
|
|
3486
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
3487
|
+
},
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
|
|
2831
3492
|
// PreToolUse hooks. DB hooks (cc hook add) stay observe-only — a failure
|
|
2832
3493
|
// never blocks. settings.json hooks (context.settingsHooks) are decision-
|
|
2833
3494
|
// capable: a `block` (exit 2 / {decision:block}) stops the tool here, an
|
|
@@ -2922,6 +3583,10 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2922
3583
|
sessionId: context.sessionId || null,
|
|
2923
3584
|
turnId: context.turnId || null,
|
|
2924
3585
|
toolCallId: context.toolCallId || null,
|
|
3586
|
+
workflowEffectId: context.workflowEffectId || null,
|
|
3587
|
+
workflowChildEffectId: context.workflowChildEffectId || null,
|
|
3588
|
+
workflowChildSequence: context.workflowChildSequence || null,
|
|
3589
|
+
workflowEffectProtocol: context.workflowEffectProtocol || null,
|
|
2925
3590
|
// Parent LLM config — documented at toolContext as forwarded to
|
|
2926
3591
|
// spawn_sub_agent for provider/key inheritance; without this line it
|
|
2927
3592
|
// never actually reached executeToolInner (sub-agents silently fell
|
|
@@ -2951,12 +3616,14 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2951
3616
|
subtreeInstructionScope:
|
|
2952
3617
|
context.subtreeInstructionScope || context.sessionId || "__legacy__",
|
|
2953
3618
|
shellPolicyOverrides: context.shellPolicyOverrides || null,
|
|
3619
|
+
classifyAllShell: context.classifyAllShell === true,
|
|
2954
3620
|
approvalGate: context.approvalGate || null,
|
|
2955
3621
|
shellConfirm: context.shellConfirm || null,
|
|
2956
3622
|
additionalDirectories: context.additionalDirectories || null,
|
|
2957
3623
|
sandbox: context.sandbox || null,
|
|
2958
3624
|
ruleAllowed,
|
|
2959
3625
|
settingsVerdict,
|
|
3626
|
+
shellDispatchPolicyAuthority,
|
|
2960
3627
|
subAgentDepth: context.subAgentDepth || 0,
|
|
2961
3628
|
subAgentBudget: context.subAgentBudget || null,
|
|
2962
3629
|
sessionBudget: context.sessionBudget || null,
|
|
@@ -4306,6 +4973,10 @@ async function executeToolInner(
|
|
|
4306
4973
|
sessionId,
|
|
4307
4974
|
turnId,
|
|
4308
4975
|
toolCallId,
|
|
4976
|
+
workflowEffectId = null,
|
|
4977
|
+
workflowChildEffectId = null,
|
|
4978
|
+
workflowChildSequence = null,
|
|
4979
|
+
workflowEffectProtocol = null,
|
|
4309
4980
|
planManager = null,
|
|
4310
4981
|
permissionRules = null,
|
|
4311
4982
|
effectiveAllowedToolNames = null,
|
|
@@ -4331,6 +5002,7 @@ async function executeToolInner(
|
|
|
4331
5002
|
sandbox,
|
|
4332
5003
|
ruleAllowed = false,
|
|
4333
5004
|
settingsVerdict = null,
|
|
5005
|
+
shellDispatchPolicyAuthority = null,
|
|
4334
5006
|
subAgentDepth = 0,
|
|
4335
5007
|
subAgentBudget = null,
|
|
4336
5008
|
sessionBudget = null,
|
|
@@ -4812,6 +5484,9 @@ async function executeToolInner(
|
|
|
4812
5484
|
}
|
|
4813
5485
|
|
|
4814
5486
|
case "run_shell": {
|
|
5487
|
+
// Bound the declared run_shell data once. Approval and dispatch never
|
|
5488
|
+
// observe later mutation of the caller-owned tool arguments.
|
|
5489
|
+
args = Object.isFrozen(args) ? args : snapshotShellExecutionArgs(args);
|
|
4815
5490
|
if (unattendedActionPolicy?.unattended === true) {
|
|
4816
5491
|
const unattendedVerdict = evaluateUnattendedShellAction(args.command, {
|
|
4817
5492
|
...unattendedActionPolicy,
|
|
@@ -4829,12 +5504,12 @@ async function executeToolInner(
|
|
|
4829
5504
|
}
|
|
4830
5505
|
}
|
|
4831
5506
|
|
|
4832
|
-
const shellPolicyOpts = {
|
|
5507
|
+
const shellPolicyOpts = Object.freeze({
|
|
4833
5508
|
...(shellPolicyOverrides
|
|
4834
|
-
? { overrideRuleIds: shellPolicyOverrides }
|
|
5509
|
+
? { overrideRuleIds: Object.freeze([...shellPolicyOverrides]) }
|
|
4835
5510
|
: {}),
|
|
4836
5511
|
...(classifyAllShell ? { classifyAllShell: true } : {}),
|
|
4837
|
-
};
|
|
5512
|
+
});
|
|
4838
5513
|
// Layer-by-layer explanation chain for a blocked command: which layers
|
|
4839
5514
|
// were consulted and what each said (settings rules → shell policy →
|
|
4840
5515
|
// approval gate). Attached to denial results so `/permissions denials`
|
|
@@ -4870,60 +5545,80 @@ async function executeToolInner(
|
|
|
4870
5545
|
return chain;
|
|
4871
5546
|
};
|
|
4872
5547
|
const override = getRuntimeToolDescriptorByCommand(args.command);
|
|
4873
|
-
let shellPolicy
|
|
5548
|
+
let shellPolicy = evaluateShellCommandPolicy(
|
|
5549
|
+
args.command,
|
|
5550
|
+
shellPolicyOpts,
|
|
5551
|
+
);
|
|
4874
5552
|
let approvalOutcome = null;
|
|
5553
|
+
let approvalAuthorization = null;
|
|
5554
|
+
let approvalAuthorizationContext = null;
|
|
5555
|
+
let approvalGateResult = null;
|
|
4875
5556
|
// A settings `allow` rule (ruleAllowed) pre-authorizes: skip the
|
|
4876
5557
|
// ApprovalGate tier confirm, but still run the hard shell-policy denylist
|
|
4877
5558
|
// below so an allow rule can never re-enable a blocked unsafe command.
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
5559
|
+
const requestShellApproval = async (operationArgs) => {
|
|
5560
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
5561
|
+
if (approvalGate && !ruleAllowed) {
|
|
5562
|
+
const approvalPolicyVersion = `${shellDispatchPolicyAuthority?.policyVersion || "cc-shell-policy-authority/v1:unobserved"}:shell:${shellPolicy.decision}:${shellPolicy.ruleId || "none"}`;
|
|
5563
|
+
const gated = await evaluateShellCommandWithApproval({
|
|
5564
|
+
command: args.command,
|
|
5565
|
+
args: operationArgs,
|
|
5566
|
+
sessionId,
|
|
5567
|
+
approvalGate,
|
|
5568
|
+
shellPolicyOptions: shellPolicyOpts,
|
|
5569
|
+
workspace: args.cwd || cwd,
|
|
5570
|
+
policyEnv: shellParentEnvironment,
|
|
5571
|
+
policyVersion: approvalPolicyVersion,
|
|
5572
|
+
deferSideEffects: true,
|
|
5573
|
+
});
|
|
5574
|
+
approvalGateResult = gated;
|
|
5575
|
+
shellPolicy = gated.shellPolicy;
|
|
5576
|
+
approvalOutcome = {
|
|
5577
|
+
decision: gated.decision,
|
|
5578
|
+
via: gated.via,
|
|
5579
|
+
riskLevel: gated.riskLevel,
|
|
5580
|
+
policy: gated.policy,
|
|
5581
|
+
};
|
|
5582
|
+
approvalAuthorization = gated.authorization;
|
|
5583
|
+
approvalAuthorizationContext = gated.authorizationContext;
|
|
5584
|
+
if (!gated.allowed) {
|
|
5585
|
+
// Make a policy denial ACTIONABLE for the model (Claude-Code 2.1.193
|
|
5586
|
+
// "denial reasons to transcripts"): tell it this won't change on
|
|
5587
|
+
// retry and to involve the user, so it stops re-issuing the same
|
|
5588
|
+
// blocked command (which otherwise just burns turns + tokens).
|
|
5589
|
+
const tierLabel =
|
|
5590
|
+
typeof gated.policy === "string" ? `"${gated.policy}" ` : "";
|
|
5591
|
+
return attachDescriptor(
|
|
5592
|
+
{
|
|
5593
|
+
error:
|
|
5594
|
+
gated.via === "shell-policy"
|
|
5595
|
+
? `[Shell Policy] ${gated.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`
|
|
5596
|
+
: `[ApprovalGate] command denied by the ${tierLabel}approval policy (via ${gated.via}). Retrying the same command will not help — it needs user approval. Tell the user (they can run it themselves, approve it, or relax the policy) and continue with other work.`,
|
|
5597
|
+
shellCommandPolicy: shellPolicy,
|
|
5598
|
+
approval: approvalOutcome,
|
|
5599
|
+
permissionChain: buildPermissionChain(gated),
|
|
5600
|
+
},
|
|
5601
|
+
override || runtimeDescriptor,
|
|
5602
|
+
);
|
|
5603
|
+
}
|
|
5604
|
+
} else {
|
|
5605
|
+
shellPolicy = evaluateShellCommandPolicy(
|
|
5606
|
+
args.command,
|
|
5607
|
+
shellPolicyOpts,
|
|
4924
5608
|
);
|
|
5609
|
+
if (!shellPolicy.allowed) {
|
|
5610
|
+
return attachDescriptor(
|
|
5611
|
+
{
|
|
5612
|
+
error: `[Shell Policy] ${shellPolicy.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`,
|
|
5613
|
+
shellCommandPolicy: shellPolicy,
|
|
5614
|
+
permissionChain: buildPermissionChain(null),
|
|
5615
|
+
},
|
|
5616
|
+
override || runtimeDescriptor,
|
|
5617
|
+
);
|
|
5618
|
+
}
|
|
4925
5619
|
}
|
|
4926
|
-
|
|
5620
|
+
return null;
|
|
5621
|
+
};
|
|
4927
5622
|
|
|
4928
5623
|
// P1 #8 Windows/PowerShell first-class: per-call args.shell or the
|
|
4929
5624
|
// configured settings `shell.windowsDefault` may route this command
|
|
@@ -4932,7 +5627,7 @@ async function executeToolInner(
|
|
|
4932
5627
|
// the historical default shell byte-identical (useDefaultShell=true).
|
|
4933
5628
|
const { resolveShellInvocation } =
|
|
4934
5629
|
await import("../lib/shell-selector.cjs");
|
|
4935
|
-
|
|
5630
|
+
let shellInv = resolveShellInvocation({
|
|
4936
5631
|
command: args.command,
|
|
4937
5632
|
requested: args.shell,
|
|
4938
5633
|
cwd: args.cwd || cwd,
|
|
@@ -4949,12 +5644,16 @@ async function executeToolInner(
|
|
|
4949
5644
|
// Resolver failures for a matching strict alias are terminal — falling
|
|
4950
5645
|
// back to a shell would bypass target identity and manifest requirements.
|
|
4951
5646
|
let pluginBinInvocation = null;
|
|
4952
|
-
let
|
|
5647
|
+
let pluginBinRuntime = null;
|
|
4953
5648
|
let pluginBinSandboxPolicy = null;
|
|
4954
5649
|
let pluginBinSandboxExecutionContract = null;
|
|
5650
|
+
const backgroundPlatform =
|
|
5651
|
+
typeof _backgroundProcessDeps.platform === "function"
|
|
5652
|
+
? _backgroundProcessDeps.platform()
|
|
5653
|
+
: process.platform;
|
|
4955
5654
|
try {
|
|
4956
5655
|
const pluginBin = await import("../lib/plugin-runtime/bin.js");
|
|
4957
|
-
|
|
5656
|
+
pluginBinRuntime = pluginBin;
|
|
4958
5657
|
pluginBinSandboxPolicy = collectWorkspacePluginBinSandboxPolicy(
|
|
4959
5658
|
pluginBin,
|
|
4960
5659
|
cwd,
|
|
@@ -4978,8 +5677,8 @@ async function executeToolInner(
|
|
|
4978
5677
|
{ cwd },
|
|
4979
5678
|
);
|
|
4980
5679
|
if (
|
|
4981
|
-
|
|
4982
|
-
(
|
|
5680
|
+
backgroundPlatform === "linux" ||
|
|
5681
|
+
(backgroundPlatform === "win32" &&
|
|
4983
5682
|
pluginBinInvocation.runtime === "node")
|
|
4984
5683
|
) {
|
|
4985
5684
|
try {
|
|
@@ -5024,7 +5723,7 @@ async function executeToolInner(
|
|
|
5024
5723
|
const processOrigin = pluginBinInvocation
|
|
5025
5724
|
? "plugin:bin"
|
|
5026
5725
|
: "tool:run_shell";
|
|
5027
|
-
|
|
5726
|
+
let processProvenance = {
|
|
5028
5727
|
...(pluginBinInvocation
|
|
5029
5728
|
? {
|
|
5030
5729
|
pluginId: pluginBinInvocation.pluginId,
|
|
@@ -5056,23 +5755,264 @@ async function executeToolInner(
|
|
|
5056
5755
|
direct_argv: true,
|
|
5057
5756
|
},
|
|
5058
5757
|
}
|
|
5059
|
-
: {};
|
|
5758
|
+
: {};
|
|
5759
|
+
|
|
5760
|
+
// Snapshot process-derived execution inputs before confirmation. The
|
|
5761
|
+
// descriptor carries only an opaque, secret-free environment reference;
|
|
5762
|
+
// the actual child environment remains in this invocation-local closure.
|
|
5763
|
+
const shellParentEnvironment = Object.freeze({ ...process.env });
|
|
5764
|
+
const agentIdentityEnvironment = Object.freeze({
|
|
5765
|
+
CLAUDECODE: "1",
|
|
5766
|
+
...(sessionId
|
|
5767
|
+
? {
|
|
5768
|
+
CC_SESSION_ID: String(sessionId),
|
|
5769
|
+
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5770
|
+
}
|
|
5771
|
+
: {}),
|
|
5772
|
+
});
|
|
5773
|
+
const credentialProxyResult = applyCredentialProxy(
|
|
5774
|
+
{
|
|
5775
|
+
...shellParentEnvironment,
|
|
5776
|
+
...agentIdentityEnvironment,
|
|
5777
|
+
},
|
|
5778
|
+
{ env: shellParentEnvironment },
|
|
5779
|
+
);
|
|
5780
|
+
const shellChildEnvironment = Object.freeze({
|
|
5781
|
+
...credentialProxyResult.env,
|
|
5782
|
+
});
|
|
5783
|
+
const sandboxChildEnvironment = agentIdentityEnvironment;
|
|
5784
|
+
const shellExecutionDescriptor = createShellExecutionDescriptor({
|
|
5785
|
+
args,
|
|
5786
|
+
workspace: args.cwd || cwd,
|
|
5787
|
+
shellInvocation: shellInv,
|
|
5788
|
+
pluginInvocation: pluginBinInvocation,
|
|
5789
|
+
pluginSandboxPolicy: pluginBinSandboxPolicy,
|
|
5790
|
+
pluginSandboxExecutionContract: pluginBinSandboxExecutionContract,
|
|
5791
|
+
sandbox,
|
|
5792
|
+
environmentRef: {
|
|
5793
|
+
id: `run-shell-env:${randomUUID()}`,
|
|
5794
|
+
inheritance: sandbox ? "sandbox-minimal" : "host-snapshot",
|
|
5795
|
+
credentialProxy: credentialProxyResult.enabled === true,
|
|
5796
|
+
maskedNames: Object.freeze([...credentialProxyResult.masked]),
|
|
5797
|
+
},
|
|
5798
|
+
});
|
|
5799
|
+
// From here onward the descriptor itself is the tool-argument snapshot.
|
|
5800
|
+
args = shellExecutionDescriptor;
|
|
5801
|
+
shellInv = shellExecutionDescriptor.execution.shell;
|
|
5802
|
+
const shellSandbox = shellExecutionDescriptor.execution.sandbox;
|
|
5803
|
+
const executionPlugin = shellExecutionDescriptor.execution.plugin;
|
|
5804
|
+
const approvedPluginProjection = executionPlugin
|
|
5805
|
+
? canonicalPolicyAuthorityData(executionPlugin)
|
|
5806
|
+
: null;
|
|
5807
|
+
const approvedWorkspacePluginPolicy = canonicalPolicyAuthorityData(
|
|
5808
|
+
pluginBinSandboxPolicy,
|
|
5809
|
+
);
|
|
5810
|
+
const approvedPluginInvocationAuthority = pluginBinInvocation
|
|
5811
|
+
? canonicalPolicyAuthorityData(
|
|
5812
|
+
projectPluginBinInvocationAuthority(pluginBinInvocation),
|
|
5813
|
+
)
|
|
5814
|
+
: null;
|
|
5815
|
+
const refreshPluginBinExecution = () => {
|
|
5816
|
+
if (!executionPlugin) return;
|
|
5817
|
+
if (!pluginBinRuntime) {
|
|
5818
|
+
const error = new Error(
|
|
5819
|
+
"Plugin bin runtime is unavailable during launch revalidation",
|
|
5820
|
+
);
|
|
5821
|
+
error.code = "ERR_PLUGIN_BIN_DISCOVERY_FAILED";
|
|
5822
|
+
error.pluginBinFailClosed = true;
|
|
5823
|
+
throw error;
|
|
5824
|
+
}
|
|
5825
|
+
const freshInvocation = pluginBinRuntime.resolvePluginBinInvocation(
|
|
5826
|
+
shellExecutionDescriptor.command,
|
|
5827
|
+
{
|
|
5828
|
+
cwd,
|
|
5829
|
+
commandCwd: shellExecutionDescriptor.cwd || cwd,
|
|
5830
|
+
failClosed: true,
|
|
5831
|
+
},
|
|
5832
|
+
);
|
|
5833
|
+
if (!freshInvocation) {
|
|
5834
|
+
const error = new Error(
|
|
5835
|
+
"Approved plugin bin alias is no longer available",
|
|
5836
|
+
);
|
|
5837
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5838
|
+
error.pluginBinFailClosed = true;
|
|
5839
|
+
throw error;
|
|
5840
|
+
}
|
|
5841
|
+
if (
|
|
5842
|
+
canonicalPolicyAuthorityData(
|
|
5843
|
+
projectPluginBinInvocationAuthority(freshInvocation),
|
|
5844
|
+
) !== approvedPluginInvocationAuthority
|
|
5845
|
+
) {
|
|
5846
|
+
const error = new Error(
|
|
5847
|
+
"Plugin bin manifest, trust, alias, or sandbox authority changed after approval",
|
|
5848
|
+
);
|
|
5849
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5850
|
+
error.pluginBinFailClosed = true;
|
|
5851
|
+
throw error;
|
|
5852
|
+
}
|
|
5853
|
+
let freshPolicy = collectWorkspacePluginBinSandboxPolicy(
|
|
5854
|
+
pluginBinRuntime,
|
|
5855
|
+
cwd,
|
|
5856
|
+
shellExecutionDescriptor.cwd || cwd,
|
|
5857
|
+
);
|
|
5858
|
+
if (freshInvocation.sandboxPolicy) {
|
|
5859
|
+
freshPolicy = pluginBinRuntime.pinPluginBinSandboxPolicy(
|
|
5860
|
+
{
|
|
5861
|
+
requiredBoundaries: [
|
|
5862
|
+
...(freshPolicy?.requiredBoundaries || []),
|
|
5863
|
+
...(freshInvocation.sandboxPolicy.requiredBoundaries || []),
|
|
5864
|
+
],
|
|
5865
|
+
},
|
|
5866
|
+
{ cwd },
|
|
5867
|
+
);
|
|
5868
|
+
}
|
|
5869
|
+
let freshContract = null;
|
|
5870
|
+
if (
|
|
5871
|
+
freshInvocation.sandboxPolicy &&
|
|
5872
|
+
(backgroundPlatform === "linux" ||
|
|
5873
|
+
(backgroundPlatform === "win32" &&
|
|
5874
|
+
freshInvocation.runtime === "node"))
|
|
5875
|
+
) {
|
|
5876
|
+
freshContract = pluginBinRuntime.createPluginSandboxExecutionContract(
|
|
5877
|
+
freshInvocation,
|
|
5878
|
+
{ sync: shellExecutionDescriptor.run_in_background !== true },
|
|
5879
|
+
);
|
|
5880
|
+
}
|
|
5881
|
+
const freshProjection = createShellExecutionDescriptor({
|
|
5882
|
+
args: shellExecutionDescriptor,
|
|
5883
|
+
workspace: shellExecutionDescriptor.execution.workspace,
|
|
5884
|
+
shellInvocation: shellExecutionDescriptor.execution.shell,
|
|
5885
|
+
pluginInvocation: freshInvocation,
|
|
5886
|
+
pluginSandboxPolicy: freshPolicy,
|
|
5887
|
+
pluginSandboxExecutionContract: freshContract,
|
|
5888
|
+
sandbox: shellExecutionDescriptor.execution.sandbox,
|
|
5889
|
+
environmentRef: shellExecutionDescriptor.execution.environment,
|
|
5890
|
+
}).execution.plugin;
|
|
5891
|
+
if (
|
|
5892
|
+
canonicalPolicyAuthorityData(freshProjection) !==
|
|
5893
|
+
approvedPluginProjection
|
|
5894
|
+
) {
|
|
5895
|
+
const error = new Error(
|
|
5896
|
+
"Plugin bin manifest, trust, alias, or sandbox authority changed after approval",
|
|
5897
|
+
);
|
|
5898
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5899
|
+
error.pluginBinFailClosed = true;
|
|
5900
|
+
throw error;
|
|
5901
|
+
}
|
|
5902
|
+
pluginBinInvocation = freshInvocation;
|
|
5903
|
+
pluginBinSandboxPolicy = freshPolicy;
|
|
5904
|
+
pluginBinSandboxExecutionContract = freshContract;
|
|
5905
|
+
processProvenance = {
|
|
5906
|
+
pluginId: freshInvocation.pluginId,
|
|
5907
|
+
pluginVersion: freshInvocation.pluginVersion,
|
|
5908
|
+
pluginSource: freshInvocation.pluginSource,
|
|
5909
|
+
pluginExecutableIdentity: freshInvocation.executableIdentity,
|
|
5910
|
+
...(freshPolicy ? { sandboxPolicy: freshPolicy } : {}),
|
|
5911
|
+
...(freshContract ? { sandboxExecutionContract: freshContract } : {}),
|
|
5912
|
+
};
|
|
5913
|
+
};
|
|
5914
|
+
const refreshPluginExecutionAuthority = () => {
|
|
5915
|
+
if (executionPlugin) {
|
|
5916
|
+
refreshPluginBinExecution();
|
|
5917
|
+
return;
|
|
5918
|
+
}
|
|
5919
|
+
if (!pluginBinRuntime) return;
|
|
5920
|
+
const freshPolicy = collectWorkspacePluginBinSandboxPolicy(
|
|
5921
|
+
pluginBinRuntime,
|
|
5922
|
+
cwd,
|
|
5923
|
+
shellExecutionDescriptor.cwd || cwd,
|
|
5924
|
+
);
|
|
5925
|
+
if (
|
|
5926
|
+
canonicalPolicyAuthorityData(freshPolicy) !==
|
|
5927
|
+
approvedWorkspacePluginPolicy
|
|
5928
|
+
) {
|
|
5929
|
+
const error = new Error(
|
|
5930
|
+
"Workspace plugin sandbox authority changed after approval",
|
|
5931
|
+
);
|
|
5932
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5933
|
+
error.pluginBinFailClosed = true;
|
|
5934
|
+
throw error;
|
|
5935
|
+
}
|
|
5936
|
+
pluginBinSandboxPolicy = freshPolicy;
|
|
5937
|
+
processProvenance = freshPolicy ? { sandboxPolicy: freshPolicy } : {};
|
|
5938
|
+
};
|
|
5939
|
+
|
|
5940
|
+
const approvalDenial = await requestShellApproval(
|
|
5941
|
+
shellExecutionDescriptor,
|
|
5942
|
+
);
|
|
5943
|
+
if (approvalDenial) return approvalDenial;
|
|
5944
|
+
|
|
5945
|
+
let shellDispatchAdmitted = false;
|
|
5946
|
+
const admitShellDispatch = async () => {
|
|
5947
|
+
if (shellDispatchAdmitted) {
|
|
5948
|
+
throw new Error("Shell dispatch authorization was already consumed");
|
|
5949
|
+
}
|
|
5950
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
5951
|
+
refreshPluginExecutionAuthority();
|
|
5952
|
+
const revalidated = evaluateShellCommandPolicy(
|
|
5953
|
+
shellExecutionDescriptor.command,
|
|
5954
|
+
shellPolicyOpts,
|
|
5955
|
+
);
|
|
5956
|
+
if (
|
|
5957
|
+
!revalidated.allowed ||
|
|
5958
|
+
revalidated.decision !== shellPolicy.decision ||
|
|
5959
|
+
(revalidated.ruleId || null) !== (shellPolicy.ruleId || null)
|
|
5960
|
+
) {
|
|
5961
|
+
const error = new Error(
|
|
5962
|
+
"Shell policy changed after approval; command dispatch was denied",
|
|
5963
|
+
);
|
|
5964
|
+
error.code = "ERR_SHELL_POLICY_REVALIDATION_FAILED";
|
|
5965
|
+
throw error;
|
|
5966
|
+
}
|
|
5967
|
+
if (approvalAuthorization) {
|
|
5968
|
+
if (typeof approvalGate?.consumeAuthorization !== "function") {
|
|
5969
|
+
throw new Error(
|
|
5970
|
+
"Remote shell approval cannot be consumed by this ApprovalGate",
|
|
5971
|
+
);
|
|
5972
|
+
}
|
|
5973
|
+
await approvalGate.consumeAuthorization(
|
|
5974
|
+
approvalAuthorization,
|
|
5975
|
+
approvalAuthorizationContext,
|
|
5976
|
+
);
|
|
5977
|
+
approvalAuthorization = null;
|
|
5978
|
+
}
|
|
5979
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
5980
|
+
refreshPluginExecutionAuthority();
|
|
5981
|
+
const finalPolicy = evaluateShellCommandPolicy(
|
|
5982
|
+
shellExecutionDescriptor.command,
|
|
5983
|
+
shellPolicyOpts,
|
|
5984
|
+
);
|
|
5985
|
+
if (
|
|
5986
|
+
!finalPolicy.allowed ||
|
|
5987
|
+
finalPolicy.decision !== shellPolicy.decision ||
|
|
5988
|
+
(finalPolicy.ruleId || null) !== (shellPolicy.ruleId || null)
|
|
5989
|
+
) {
|
|
5990
|
+
const error = new Error(
|
|
5991
|
+
"Shell policy changed while authorization was consumed; command dispatch was denied",
|
|
5992
|
+
);
|
|
5993
|
+
error.code = "ERR_SHELL_POLICY_REVALIDATION_FAILED";
|
|
5994
|
+
throw error;
|
|
5995
|
+
}
|
|
5996
|
+
// Auditing is intentionally committed only after durable authorization
|
|
5997
|
+
// consumption and immediately before the first external side effect.
|
|
5998
|
+
commitShellApprovalSideEffects(approvalGateResult);
|
|
5999
|
+
if (pluginBinInvocation) {
|
|
6000
|
+
pluginBinResult.plugin_bin.launch_identity_reattested = true;
|
|
6001
|
+
}
|
|
6002
|
+
shellDispatchAdmitted = true;
|
|
6003
|
+
};
|
|
5060
6004
|
|
|
5061
6005
|
// Background: spawn, register, return a task_id immediately. The agent
|
|
5062
6006
|
// polls output + completion via check_shell. No timeout — that's the whole
|
|
5063
6007
|
// point of backgrounding (builds, test suites, dev servers).
|
|
5064
6008
|
if (args.run_in_background === true) {
|
|
5065
|
-
if (
|
|
6009
|
+
if (shellSandbox) {
|
|
5066
6010
|
return attachDescriptor({
|
|
5067
6011
|
error:
|
|
5068
6012
|
"[Sandbox] Background shell tasks are not supported in the ephemeral sandbox. Run in the foreground or explicitly disable --sandbox.",
|
|
5069
6013
|
policy: { decision: "deny", via: "sandbox" },
|
|
5070
6014
|
});
|
|
5071
6015
|
}
|
|
5072
|
-
const backgroundPlatform =
|
|
5073
|
-
typeof _backgroundProcessDeps.platform === "function"
|
|
5074
|
-
? _backgroundProcessDeps.platform()
|
|
5075
|
-
: process.platform;
|
|
5076
6016
|
const pinnedBackgroundBoundaries = [
|
|
5077
6017
|
...(pluginBinSandboxPolicy?.requiredBoundaries || []),
|
|
5078
6018
|
];
|
|
@@ -5124,16 +6064,7 @@ async function executeToolInner(
|
|
|
5124
6064
|
cwd: workingDirectory,
|
|
5125
6065
|
shell: false,
|
|
5126
6066
|
windowsHide: true,
|
|
5127
|
-
env:
|
|
5128
|
-
...process.env,
|
|
5129
|
-
CLAUDECODE: "1",
|
|
5130
|
-
...(sessionId
|
|
5131
|
-
? {
|
|
5132
|
-
CC_SESSION_ID: String(sessionId),
|
|
5133
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5134
|
-
}
|
|
5135
|
-
: {}),
|
|
5136
|
-
}).env,
|
|
6067
|
+
env: shellChildEnvironment,
|
|
5137
6068
|
detached: false,
|
|
5138
6069
|
origin: "tool:run_shell",
|
|
5139
6070
|
policy: "allow",
|
|
@@ -5188,6 +6119,9 @@ async function executeToolInner(
|
|
|
5188
6119
|
},
|
|
5189
6120
|
};
|
|
5190
6121
|
}
|
|
6122
|
+
// Admission precedes task cleanup, sequence allocation, listener
|
|
6123
|
+
// registration, proxy startup and process creation.
|
|
6124
|
+
await admitShellDispatch();
|
|
5191
6125
|
// Free memory from idle background tasks before adding another, so a
|
|
5192
6126
|
// long agent run can't accumulate forgotten dev servers (no-op unless
|
|
5193
6127
|
// the machine is actually under memory pressure).
|
|
@@ -5199,7 +6133,7 @@ async function executeToolInner(
|
|
|
5199
6133
|
cwd:
|
|
5200
6134
|
linuxGenericBackgroundLaunch?.options.cwd ||
|
|
5201
6135
|
(linuxDirectPluginStrongBackground
|
|
5202
|
-
?
|
|
6136
|
+
? executionPlugin.sandboxExecution.workingDirectory
|
|
5203
6137
|
: args.cwd || cwd),
|
|
5204
6138
|
status: "running",
|
|
5205
6139
|
exitCode: null,
|
|
@@ -5230,16 +6164,7 @@ async function executeToolInner(
|
|
|
5230
6164
|
// Credential proxy (opt-in, CC_CREDENTIAL_PROXY): keeps the agent's
|
|
5231
6165
|
// real long-lived secrets out of the spawned command's env — a
|
|
5232
6166
|
// no-op (same object) when disabled. See credential-proxy.js.
|
|
5233
|
-
env:
|
|
5234
|
-
...process.env,
|
|
5235
|
-
CLAUDECODE: "1",
|
|
5236
|
-
...(sessionId
|
|
5237
|
-
? {
|
|
5238
|
-
CC_SESSION_ID: String(sessionId),
|
|
5239
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5240
|
-
}
|
|
5241
|
-
: {}),
|
|
5242
|
-
}).env,
|
|
6167
|
+
env: shellChildEnvironment,
|
|
5243
6168
|
// POSIX: own process group so check_shell{kill}/teardown can signal
|
|
5244
6169
|
// the whole tree (shell + its grandchild command). No-op on Windows
|
|
5245
6170
|
// where the tree is killed via taskkill /T instead.
|
|
@@ -5266,18 +6191,16 @@ async function executeToolInner(
|
|
|
5266
6191
|
linuxGenericBackgroundLaunch.options,
|
|
5267
6192
|
);
|
|
5268
6193
|
} else if (pluginBinInvocation) {
|
|
5269
|
-
reattestPluginBinInvocation(pluginBinInvocation);
|
|
5270
|
-
pluginBinResult.plugin_bin.launch_identity_reattested = true;
|
|
5271
6194
|
child = broker.spawn(
|
|
5272
|
-
|
|
6195
|
+
executionPlugin.sandboxExecution?.kind ===
|
|
5273
6196
|
"strict-plugin-node-bin"
|
|
5274
|
-
?
|
|
5275
|
-
:
|
|
5276
|
-
|
|
6197
|
+
? executionPlugin.sandboxExecution.runtimePath
|
|
6198
|
+
: executionPlugin.command,
|
|
6199
|
+
executionPlugin.argv,
|
|
5277
6200
|
{
|
|
5278
6201
|
...brokerOpts,
|
|
5279
6202
|
cwd:
|
|
5280
|
-
|
|
6203
|
+
executionPlugin.sandboxExecution?.workingDirectory ||
|
|
5281
6204
|
brokerOpts.cwd,
|
|
5282
6205
|
shell: false,
|
|
5283
6206
|
},
|
|
@@ -5373,7 +6296,7 @@ async function executeToolInner(
|
|
|
5373
6296
|
);
|
|
5374
6297
|
}
|
|
5375
6298
|
|
|
5376
|
-
if (
|
|
6299
|
+
if (shellSandbox) {
|
|
5377
6300
|
if (pluginBinSandboxPolicy) {
|
|
5378
6301
|
return attachDescriptor(
|
|
5379
6302
|
{
|
|
@@ -5391,15 +6314,16 @@ async function executeToolInner(
|
|
|
5391
6314
|
override || runtimeDescriptor,
|
|
5392
6315
|
);
|
|
5393
6316
|
}
|
|
6317
|
+
await admitShellDispatch();
|
|
5394
6318
|
const { executeSandboxedShell, sandboxSummary } =
|
|
5395
6319
|
await import("../lib/agent-sandbox.js");
|
|
5396
6320
|
// Domain-restricted networking is ENFORCED by routing the sandboxed
|
|
5397
6321
|
// process's egress through a local filtering proxy (see
|
|
5398
6322
|
// sandbox-egress-proxy.js). Start it only when the policy actually
|
|
5399
6323
|
// restricts domains and network is on; tear it down after the command.
|
|
5400
|
-
const sboxPolicy =
|
|
6324
|
+
const sboxPolicy = shellSandbox.policy || {};
|
|
5401
6325
|
const needsEgress =
|
|
5402
|
-
|
|
6326
|
+
shellSandbox.network === true &&
|
|
5403
6327
|
(sboxPolicy.allowedDomains?.length || 0) +
|
|
5404
6328
|
(sboxPolicy.deniedDomains?.length || 0) >
|
|
5405
6329
|
0;
|
|
@@ -5428,20 +6352,25 @@ async function executeToolInner(
|
|
|
5428
6352
|
}
|
|
5429
6353
|
let result;
|
|
5430
6354
|
try {
|
|
5431
|
-
|
|
6355
|
+
// Proxy startup and dynamic imports are asynchronous. Re-read the
|
|
6356
|
+
// live authority once more after they settle and immediately before
|
|
6357
|
+
// the synchronous sandbox dispatch.
|
|
6358
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
6359
|
+
refreshPluginExecutionAuthority();
|
|
6360
|
+
if (pluginBinSandboxPolicy) {
|
|
6361
|
+
const error = new Error(
|
|
6362
|
+
"A plugin sandbox authority appeared while the legacy shell sandbox was starting",
|
|
6363
|
+
);
|
|
6364
|
+
error.code = "ERR_PLUGIN_BIN_SANDBOX_ROUTE_CONFLICT";
|
|
6365
|
+
error.pluginBinFailClosed = true;
|
|
6366
|
+
throw error;
|
|
6367
|
+
}
|
|
6368
|
+
result = executeSandboxedShell(args.command, shellSandbox, {
|
|
5432
6369
|
cwd: args.cwd || cwd,
|
|
5433
6370
|
timeout: _resolveShellTimeout(args.timeout),
|
|
5434
6371
|
maxBuffer: 1024 * 1024,
|
|
5435
6372
|
egressProxy,
|
|
5436
|
-
env:
|
|
5437
|
-
CLAUDECODE: "1",
|
|
5438
|
-
...(sessionId
|
|
5439
|
-
? {
|
|
5440
|
-
CC_SESSION_ID: String(sessionId),
|
|
5441
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5442
|
-
}
|
|
5443
|
-
: {}),
|
|
5444
|
-
},
|
|
6373
|
+
env: sandboxChildEnvironment,
|
|
5445
6374
|
});
|
|
5446
6375
|
} finally {
|
|
5447
6376
|
if (proxyHandle) {
|
|
@@ -5453,7 +6382,7 @@ async function executeToolInner(
|
|
|
5453
6382
|
}
|
|
5454
6383
|
}
|
|
5455
6384
|
const common = {
|
|
5456
|
-
sandbox: sandboxSummary(
|
|
6385
|
+
sandbox: sandboxSummary(shellSandbox),
|
|
5457
6386
|
shellCommandPolicy: shellPolicy,
|
|
5458
6387
|
approval: approvalOutcome,
|
|
5459
6388
|
policyTrace: ["shell-policy", "approval", "sandbox"],
|
|
@@ -5492,18 +6421,10 @@ async function executeToolInner(
|
|
|
5492
6421
|
// Credential proxy (opt-in, CC_CREDENTIAL_PROXY): keeps the agent's
|
|
5493
6422
|
// real long-lived secrets out of the shell's env — a no-op (same
|
|
5494
6423
|
// object) when disabled. See credential-proxy.js.
|
|
5495
|
-
env:
|
|
5496
|
-
...process.env,
|
|
5497
|
-
CLAUDECODE: "1",
|
|
5498
|
-
...(sessionId
|
|
5499
|
-
? {
|
|
5500
|
-
CC_SESSION_ID: String(sessionId),
|
|
5501
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5502
|
-
}
|
|
5503
|
-
: {}),
|
|
5504
|
-
}).env,
|
|
6424
|
+
env: shellChildEnvironment,
|
|
5505
6425
|
};
|
|
5506
6426
|
let output;
|
|
6427
|
+
await admitShellDispatch();
|
|
5507
6428
|
const brokerExecOpts = {
|
|
5508
6429
|
...fgExecOpts,
|
|
5509
6430
|
origin: processOrigin,
|
|
@@ -5512,17 +6433,15 @@ async function executeToolInner(
|
|
|
5512
6433
|
...processProvenance,
|
|
5513
6434
|
};
|
|
5514
6435
|
if (pluginBinInvocation) {
|
|
5515
|
-
reattestPluginBinInvocation(pluginBinInvocation);
|
|
5516
|
-
pluginBinResult.plugin_bin.launch_identity_reattested = true;
|
|
5517
6436
|
const res = broker.spawnSync(
|
|
5518
|
-
|
|
5519
|
-
?
|
|
5520
|
-
:
|
|
5521
|
-
|
|
6437
|
+
executionPlugin.sandboxExecution?.kind === "strict-plugin-node-bin"
|
|
6438
|
+
? executionPlugin.sandboxExecution.runtimePath
|
|
6439
|
+
: executionPlugin.command,
|
|
6440
|
+
executionPlugin.argv,
|
|
5522
6441
|
{
|
|
5523
6442
|
...brokerExecOpts,
|
|
5524
6443
|
cwd:
|
|
5525
|
-
|
|
6444
|
+
executionPlugin.sandboxExecution?.workingDirectory ||
|
|
5526
6445
|
brokerExecOpts.cwd,
|
|
5527
6446
|
shell: false,
|
|
5528
6447
|
windowsHide: true,
|
|
@@ -5601,6 +6520,15 @@ async function executeToolInner(
|
|
|
5601
6520
|
: {}),
|
|
5602
6521
|
stderr: (err.stderr || "").substring(0, 2000),
|
|
5603
6522
|
exitCode: err.status,
|
|
6523
|
+
...(typeof err.code === "string" && err.code
|
|
6524
|
+
? {
|
|
6525
|
+
policy: {
|
|
6526
|
+
decision: "deny",
|
|
6527
|
+
via: "dispatch-revalidation",
|
|
6528
|
+
code: err.code,
|
|
6529
|
+
},
|
|
6530
|
+
}
|
|
6531
|
+
: {}),
|
|
5604
6532
|
...shellMeta,
|
|
5605
6533
|
...pluginBinResult,
|
|
5606
6534
|
shellCommandPolicy: shellPolicy,
|
|
@@ -5724,8 +6652,18 @@ async function executeToolInner(
|
|
|
5724
6652
|
approvalGate &&
|
|
5725
6653
|
typeof approvalGate.hasConfirmer === "function" &&
|
|
5726
6654
|
!approvalGate.hasConfirmer();
|
|
6655
|
+
// A durable remote-approval consumer is an explicit fail-closed mode.
|
|
6656
|
+
// run_code does not yet carry an exact lease tuple, so it must still
|
|
6657
|
+
// consult the remote confirmer in headless sessions; that confirmer
|
|
6658
|
+
// returns lease-unavailable and no code is persisted or launched.
|
|
6659
|
+
const durableAuthorizationGate =
|
|
6660
|
+
approvalGate &&
|
|
6661
|
+
typeof approvalGate.hasAuthorizationConsumer === "function" &&
|
|
6662
|
+
approvalGate.hasAuthorizationConsumer() === true;
|
|
5727
6663
|
if (
|
|
5728
|
-
(interactiveApproval ||
|
|
6664
|
+
(interactiveApproval ||
|
|
6665
|
+
policyGateNoConfirmer ||
|
|
6666
|
+
durableAuthorizationGate) &&
|
|
5729
6667
|
approvalGate &&
|
|
5730
6668
|
!ruleAllowed &&
|
|
5731
6669
|
typeof approvalGate.decide === "function"
|
|
@@ -5736,7 +6674,7 @@ async function executeToolInner(
|
|
|
5736
6674
|
sessionId,
|
|
5737
6675
|
riskLevel: APPROVAL_RISK.HIGH,
|
|
5738
6676
|
tool: "run_code",
|
|
5739
|
-
args:
|
|
6677
|
+
args: runCodeApprovalDescriptor(args, cwd),
|
|
5740
6678
|
});
|
|
5741
6679
|
if (gate.decision !== APPROVAL_DECISION.ALLOW) {
|
|
5742
6680
|
// Actionable denial (matches run_shell, Claude-Code 2.1.193): tell the
|
|
@@ -5753,6 +6691,23 @@ async function executeToolInner(
|
|
|
5753
6691
|
},
|
|
5754
6692
|
});
|
|
5755
6693
|
}
|
|
6694
|
+
if (gate.authorization) {
|
|
6695
|
+
return attachDescriptor({
|
|
6696
|
+
error:
|
|
6697
|
+
"[ApprovalGate] run_code received a durable authorization, but this tool does not yet implement an exact one-shot consume tuple. No code was launched.",
|
|
6698
|
+
approval: {
|
|
6699
|
+
decision: APPROVAL_DECISION.DENY,
|
|
6700
|
+
via: "authorization-consume-unsupported",
|
|
6701
|
+
riskLevel: "high",
|
|
6702
|
+
policy: gate.policy,
|
|
6703
|
+
},
|
|
6704
|
+
policy: {
|
|
6705
|
+
decision: "deny",
|
|
6706
|
+
via: "authorization-consume-unsupported",
|
|
6707
|
+
code: "CC_RUN_CODE_AUTHORIZATION_CONSUME_UNSUPPORTED",
|
|
6708
|
+
},
|
|
6709
|
+
});
|
|
6710
|
+
}
|
|
5756
6711
|
}
|
|
5757
6712
|
return attachDescriptor(await _executeRunCode(args, cwd));
|
|
5758
6713
|
}
|
|
@@ -5766,6 +6721,10 @@ async function executeToolInner(
|
|
|
5766
6721
|
interaction,
|
|
5767
6722
|
sessionId,
|
|
5768
6723
|
llmOptions,
|
|
6724
|
+
workflowEffectId,
|
|
6725
|
+
workflowChildEffectId,
|
|
6726
|
+
workflowChildSequence,
|
|
6727
|
+
workflowEffectProtocol,
|
|
5769
6728
|
subAgentDepth,
|
|
5770
6729
|
subAgentBudget,
|
|
5771
6730
|
sessionBudget,
|
|
@@ -6699,6 +7658,9 @@ async function executeToolInner(
|
|
|
6699
7658
|
? event
|
|
6700
7659
|
: {
|
|
6701
7660
|
...event,
|
|
7661
|
+
...(["model", "semantic-compaction"].includes(event?.source)
|
|
7662
|
+
? { workflowRequestSource: event.source }
|
|
7663
|
+
: {}),
|
|
6702
7664
|
source:
|
|
6703
7665
|
event?.source === "semantic-compaction"
|
|
6704
7666
|
? "semantic-compaction"
|
|
@@ -6774,6 +7736,9 @@ async function executeToolInner(
|
|
|
6774
7736
|
toolAdmission,
|
|
6775
7737
|
cwd,
|
|
6776
7738
|
llmOptions: skillLlmOptions,
|
|
7739
|
+
...(strictUsageTelemetry === true && workflowChildEffectId
|
|
7740
|
+
? { workflowEffectId: workflowChildEffectId }
|
|
7741
|
+
: {}),
|
|
6777
7742
|
...(sessionBudget ? { sessionBudget } : {}),
|
|
6778
7743
|
...(permissionRules ? { permissionRules } : {}),
|
|
6779
7744
|
...(hostManagedToolPolicy
|
|
@@ -6864,7 +7829,12 @@ async function executeToolInner(
|
|
|
6864
7829
|
toolsUsed: result.toolsUsed,
|
|
6865
7830
|
});
|
|
6866
7831
|
} catch (err) {
|
|
6867
|
-
if (
|
|
7832
|
+
if (
|
|
7833
|
+
err?.runtimeLedgerPersistence === true ||
|
|
7834
|
+
err?.workflowEffectOutcomeUnknown === true
|
|
7835
|
+
) {
|
|
7836
|
+
throw err;
|
|
7837
|
+
}
|
|
6868
7838
|
if (
|
|
6869
7839
|
signal?.aborted ||
|
|
6870
7840
|
skillExecutionSignal?.aborted ||
|
|
@@ -7088,6 +8058,14 @@ async function executeToolInner(
|
|
|
7088
8058
|
ledgerTicket = await ledger.begin({
|
|
7089
8059
|
sessionId,
|
|
7090
8060
|
turnId,
|
|
8061
|
+
...(workflowEffectId
|
|
8062
|
+
? {
|
|
8063
|
+
workflowEffectId,
|
|
8064
|
+
workflowChildEffectId,
|
|
8065
|
+
workflowChildSequence,
|
|
8066
|
+
workflowEffectProtocol,
|
|
8067
|
+
}
|
|
8068
|
+
: {}),
|
|
7091
8069
|
toolName: localToolExecutor.toolName,
|
|
7092
8070
|
serverName: localToolExecutor.serverName,
|
|
7093
8071
|
input: mcpWireInput,
|
|
@@ -7161,12 +8139,20 @@ async function executeToolInner(
|
|
|
7161
8139
|
localToolExecutor.toolName,
|
|
7162
8140
|
mcpWireInput,
|
|
7163
8141
|
];
|
|
7164
|
-
if (signal || mcpDispatchAdmission) {
|
|
8142
|
+
if (signal || mcpDispatchAdmission || workflowEffectId) {
|
|
7165
8143
|
callArguments.push({
|
|
7166
8144
|
...(signal ? { signal } : {}),
|
|
7167
8145
|
...(mcpDispatchAdmission
|
|
7168
8146
|
? { dispatchAdmission: mcpDispatchAdmission }
|
|
7169
8147
|
: {}),
|
|
8148
|
+
...(workflowEffectId
|
|
8149
|
+
? {
|
|
8150
|
+
workflowEffectId,
|
|
8151
|
+
workflowChildEffectId,
|
|
8152
|
+
workflowChildSequence,
|
|
8153
|
+
workflowEffectProtocol,
|
|
8154
|
+
}
|
|
8155
|
+
: {}),
|
|
7170
8156
|
});
|
|
7171
8157
|
}
|
|
7172
8158
|
pendingResult = Reflect.apply(
|
|
@@ -7295,8 +8281,9 @@ async function executeToolInner(
|
|
|
7295
8281
|
);
|
|
7296
8282
|
}
|
|
7297
8283
|
|
|
8284
|
+
let settledLedgerRecord;
|
|
7298
8285
|
try {
|
|
7299
|
-
await ledgerTicket.settle(
|
|
8286
|
+
settledLedgerRecord = await ledgerTicket.settle(
|
|
7300
8287
|
protocolError
|
|
7301
8288
|
? {
|
|
7302
8289
|
status: "failed",
|
|
@@ -7318,11 +8305,26 @@ async function executeToolInner(
|
|
|
7318
8305
|
}
|
|
7319
8306
|
|
|
7320
8307
|
const mcpLedgerId = safeMcpProperty(ledgerTicket, "ledgerId") || null;
|
|
8308
|
+
const mcpLedgerPrewritePersisted =
|
|
8309
|
+
safeMcpProperty(ledgerTicket, "prewritePersisted") === true;
|
|
8310
|
+
const mcpLedgerSettlementPersisted =
|
|
8311
|
+
safeMcpProperty(settledLedgerRecord, "settlementPersistence") ===
|
|
8312
|
+
"persisted";
|
|
7321
8313
|
try {
|
|
7322
8314
|
if (result && typeof result === "object") {
|
|
7323
|
-
return attachDescriptor({
|
|
8315
|
+
return attachDescriptor({
|
|
8316
|
+
...result,
|
|
8317
|
+
mcpLedgerId,
|
|
8318
|
+
mcpLedgerPrewritePersisted,
|
|
8319
|
+
mcpLedgerSettlementPersisted,
|
|
8320
|
+
});
|
|
7324
8321
|
}
|
|
7325
|
-
return attachDescriptor({
|
|
8322
|
+
return attachDescriptor({
|
|
8323
|
+
result,
|
|
8324
|
+
mcpLedgerId,
|
|
8325
|
+
mcpLedgerPrewritePersisted,
|
|
8326
|
+
mcpLedgerSettlementPersisted,
|
|
8327
|
+
});
|
|
7326
8328
|
} catch (projectionError) {
|
|
7327
8329
|
return attachDescriptor({
|
|
7328
8330
|
error:
|
|
@@ -7349,7 +8351,18 @@ async function executeToolInner(
|
|
|
7349
8351
|
interaction &&
|
|
7350
8352
|
typeof interaction.requestHostTool === "function"
|
|
7351
8353
|
) {
|
|
7352
|
-
const hostedResult =
|
|
8354
|
+
const hostedResult = workflowEffectId
|
|
8355
|
+
? await interaction.requestHostTool(
|
|
8356
|
+
name,
|
|
8357
|
+
args,
|
|
8358
|
+
Object.freeze({
|
|
8359
|
+
workflowEffectId,
|
|
8360
|
+
workflowChildEffectId,
|
|
8361
|
+
workflowChildSequence,
|
|
8362
|
+
workflowEffectProtocol,
|
|
8363
|
+
}),
|
|
8364
|
+
)
|
|
8365
|
+
: await interaction.requestHostTool(name, args);
|
|
7353
8366
|
if (hostedResult?.success === false) {
|
|
7354
8367
|
return attachDescriptor({
|
|
7355
8368
|
error:
|
|
@@ -8012,7 +9025,12 @@ function _backgroundSubAgentResultText(entry) {
|
|
|
8012
9025
|
|
|
8013
9026
|
function _throwBackgroundSubAgentUsageFailure(entry) {
|
|
8014
9027
|
const error = entry?.outcome?.fatalError;
|
|
8015
|
-
if (
|
|
9028
|
+
if (
|
|
9029
|
+
error?.runtimeLedgerPersistence === true ||
|
|
9030
|
+
error?.workflowEffectOutcomeUnknown === true
|
|
9031
|
+
) {
|
|
9032
|
+
throw error;
|
|
9033
|
+
}
|
|
8016
9034
|
}
|
|
8017
9035
|
|
|
8018
9036
|
function _throwSettledBackgroundUsageFailure(map) {
|
|
@@ -8567,6 +9585,9 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
8567
9585
|
? u
|
|
8568
9586
|
: {
|
|
8569
9587
|
...u,
|
|
9588
|
+
...(["model", "semantic-compaction"].includes(u?.source)
|
|
9589
|
+
? { workflowRequestSource: u.source }
|
|
9590
|
+
: {}),
|
|
8570
9591
|
source:
|
|
8571
9592
|
u?.source === "semantic-compaction"
|
|
8572
9593
|
? "semantic-compaction"
|
|
@@ -8716,6 +9737,9 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
8716
9737
|
cwd: ctx.cwd,
|
|
8717
9738
|
profile: profile || null,
|
|
8718
9739
|
llmOptions: subLlmOptions,
|
|
9740
|
+
...(ctx.strictUsageTelemetry === true && ctx.workflowChildEffectId
|
|
9741
|
+
? { workflowEffectId: ctx.workflowChildEffectId }
|
|
9742
|
+
: {}),
|
|
8719
9743
|
depth: currentDepth + 1, // nested spawns see their own level
|
|
8720
9744
|
// Same shared counter object so the child's own spawns draw from the run's
|
|
8721
9745
|
// single total-sub-agent pool (breadth cap spans the whole tree).
|
|
@@ -8917,7 +9941,8 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
8917
9941
|
return {
|
|
8918
9942
|
result: subCtx.result,
|
|
8919
9943
|
error: err.message,
|
|
8920
|
-
...(err?.runtimeLedgerPersistence === true
|
|
9944
|
+
...(err?.runtimeLedgerPersistence === true ||
|
|
9945
|
+
err?.workflowEffectOutcomeUnknown === true
|
|
8921
9946
|
? { fatalError: err }
|
|
8922
9947
|
: {}),
|
|
8923
9948
|
};
|
|
@@ -9050,7 +10075,12 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
9050
10075
|
completedAt: subCtx.completedAt,
|
|
9051
10076
|
});
|
|
9052
10077
|
|
|
9053
|
-
if (
|
|
10078
|
+
if (
|
|
10079
|
+
err?.runtimeLedgerPersistence === true ||
|
|
10080
|
+
err?.workflowEffectOutcomeUnknown === true
|
|
10081
|
+
) {
|
|
10082
|
+
throw err;
|
|
10083
|
+
}
|
|
9054
10084
|
|
|
9055
10085
|
return {
|
|
9056
10086
|
error: `Sub-agent failed: ${err.message}`,
|
|
@@ -9130,6 +10160,72 @@ function _hasCompleteProviderUsage(usage) {
|
|
|
9130
10160
|
);
|
|
9131
10161
|
}
|
|
9132
10162
|
|
|
10163
|
+
const PROVIDER_REQUEST_ID_RE = /^[\x21-\x7e]{1,512}$/;
|
|
10164
|
+
const PROVIDER_RECEIPT_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/;
|
|
10165
|
+
|
|
10166
|
+
function _normalizeProviderRequestId(value) {
|
|
10167
|
+
if (value == null) return null;
|
|
10168
|
+
if (typeof value !== "string" || !PROVIDER_REQUEST_ID_RE.test(value)) {
|
|
10169
|
+
throw new TypeError(
|
|
10170
|
+
"providerRequestId must be 1-512 printable ASCII characters",
|
|
10171
|
+
);
|
|
10172
|
+
}
|
|
10173
|
+
return value;
|
|
10174
|
+
}
|
|
10175
|
+
|
|
10176
|
+
function _physicalProviderRequestId(logicalRequestId, attempt) {
|
|
10177
|
+
if (!logicalRequestId) return null;
|
|
10178
|
+
const retry = Number(attempt);
|
|
10179
|
+
if (!Number.isSafeInteger(retry) || retry <= 0) return logicalRequestId;
|
|
10180
|
+
const suffix = `-r${retry}`;
|
|
10181
|
+
return `${logicalRequestId.slice(0, 512 - suffix.length)}${suffix}`;
|
|
10182
|
+
}
|
|
10183
|
+
|
|
10184
|
+
function _providerReceiptId(value) {
|
|
10185
|
+
return typeof value === "string" && PROVIDER_RECEIPT_ID_RE.test(value)
|
|
10186
|
+
? value
|
|
10187
|
+
: null;
|
|
10188
|
+
}
|
|
10189
|
+
|
|
10190
|
+
function _isOfficialOpenAIEndpoint(provider, baseUrl) {
|
|
10191
|
+
if (provider !== "openai") return false;
|
|
10192
|
+
try {
|
|
10193
|
+
const parsed = new URL(String(baseUrl));
|
|
10194
|
+
return (
|
|
10195
|
+
parsed.protocol === "https:" &&
|
|
10196
|
+
parsed.hostname.toLowerCase() === "api.openai.com" &&
|
|
10197
|
+
!parsed.port &&
|
|
10198
|
+
!parsed.username &&
|
|
10199
|
+
!parsed.password
|
|
10200
|
+
);
|
|
10201
|
+
} catch {
|
|
10202
|
+
return false;
|
|
10203
|
+
}
|
|
10204
|
+
}
|
|
10205
|
+
|
|
10206
|
+
/**
|
|
10207
|
+
* Capture only provider-returned identifiers. X-Client-Request-Id is a
|
|
10208
|
+
* trace/correlation header, not an idempotency guarantee, so a locally sent
|
|
10209
|
+
* client id alone is deliberately not called a receipt.
|
|
10210
|
+
*/
|
|
10211
|
+
function _openAIProviderRequestReceipt(response, data, clientRequestId) {
|
|
10212
|
+
if (!clientRequestId) return null;
|
|
10213
|
+
const requestId = _providerReceiptId(
|
|
10214
|
+
response?.headers?.get?.("x-request-id"),
|
|
10215
|
+
);
|
|
10216
|
+
const responseId = _providerReceiptId(data?.id);
|
|
10217
|
+
if (!requestId && !responseId) return null;
|
|
10218
|
+
return Object.freeze({
|
|
10219
|
+
protocol: "cc-provider-request-receipt/v1",
|
|
10220
|
+
provider: "openai",
|
|
10221
|
+
clientRequestId,
|
|
10222
|
+
requestId,
|
|
10223
|
+
responseId,
|
|
10224
|
+
requestIdentitySemantics: "trace-only",
|
|
10225
|
+
independentlyReadable: false,
|
|
10226
|
+
});
|
|
10227
|
+
}
|
|
10228
|
+
|
|
9133
10229
|
/**
|
|
9134
10230
|
* Send a chat completion request with tool definitions.
|
|
9135
10231
|
* Supports 8 providers: ollama, anthropic, openai, deepseek, dashscope, gemini, mistral, volcengine
|
|
@@ -9148,6 +10244,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9148
10244
|
signal,
|
|
9149
10245
|
} = options;
|
|
9150
10246
|
|
|
10247
|
+
const providerRequestId = _normalizeProviderRequestId(
|
|
10248
|
+
options.providerRequestId,
|
|
10249
|
+
);
|
|
10250
|
+
|
|
9151
10251
|
const tools = getEffectiveToolDefinitions(options);
|
|
9152
10252
|
|
|
9153
10253
|
const lastUserMsg = [...rawMessages].reverse().find((m) => m.role === "user");
|
|
@@ -9193,6 +10293,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9193
10293
|
),
|
|
9194
10294
|
{
|
|
9195
10295
|
signal,
|
|
10296
|
+
retries: options.workflowEffectId ? 0 : undefined,
|
|
9196
10297
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
9197
10298
|
...(typeof options.onStreamRetry === "function"
|
|
9198
10299
|
? {
|
|
@@ -9342,6 +10443,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9342
10443
|
),
|
|
9343
10444
|
{
|
|
9344
10445
|
signal,
|
|
10446
|
+
retries: options.workflowEffectId ? 0 : undefined,
|
|
9345
10447
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
9346
10448
|
...(typeof options.onStreamRetry === "function"
|
|
9347
10449
|
? {
|
|
@@ -9434,6 +10536,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9434
10536
|
`Unsupported provider: ${provider}. Supported: ollama, anthropic, openai, deepseek, dashscope, mistral, gemini, volcengine`,
|
|
9435
10537
|
);
|
|
9436
10538
|
}
|
|
10539
|
+
const supportsOpenAIRequestIdentity = _isOfficialOpenAIEndpoint(
|
|
10540
|
+
provider,
|
|
10541
|
+
url,
|
|
10542
|
+
);
|
|
9437
10543
|
|
|
9438
10544
|
const envKey = providerApiKeyEnvs[provider] || "OPENAI_API_KEY";
|
|
9439
10545
|
const key = apiKey || process.env[envKey];
|
|
@@ -9445,7 +10551,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9445
10551
|
dashscope: "qwen-turbo",
|
|
9446
10552
|
mistral: "mistral-large-latest",
|
|
9447
10553
|
gemini: "gemini-2.0-flash",
|
|
9448
|
-
volcengine: "
|
|
10554
|
+
volcengine: "deepseek-v4-flash-260425",
|
|
9449
10555
|
};
|
|
9450
10556
|
|
|
9451
10557
|
// Real token streaming (--include-partial-messages) for every OpenAI-compatible
|
|
@@ -9454,7 +10560,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9454
10560
|
// delta-fragmented tool_calls into the standard {message, usage} shape.
|
|
9455
10561
|
if (typeof options.onToken === "function") {
|
|
9456
10562
|
return await _retryStreamingChat(
|
|
9457
|
-
() =>
|
|
10563
|
+
(attempt = 0) =>
|
|
9458
10564
|
_chatOpenAIStreaming(
|
|
9459
10565
|
`${url}/chat/completions`,
|
|
9460
10566
|
{
|
|
@@ -9474,9 +10580,13 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9474
10580
|
options.onStall,
|
|
9475
10581
|
options.streamStallMs,
|
|
9476
10582
|
options.streamStallTimeoutMs,
|
|
10583
|
+
supportsOpenAIRequestIdentity
|
|
10584
|
+
? _physicalProviderRequestId(providerRequestId, attempt)
|
|
10585
|
+
: null,
|
|
9477
10586
|
),
|
|
9478
10587
|
{
|
|
9479
10588
|
signal,
|
|
10589
|
+
retries: options.workflowEffectId ? 0 : undefined,
|
|
9480
10590
|
strictRetryObserver: options.strictUsageTelemetry === true,
|
|
9481
10591
|
...(typeof options.onStreamRetry === "function"
|
|
9482
10592
|
? {
|
|
@@ -9497,6 +10607,9 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9497
10607
|
headers: {
|
|
9498
10608
|
"Content-Type": "application/json",
|
|
9499
10609
|
Authorization: `Bearer ${key}`,
|
|
10610
|
+
...(supportsOpenAIRequestIdentity && providerRequestId
|
|
10611
|
+
? { "X-Client-Request-Id": providerRequestId }
|
|
10612
|
+
: {}),
|
|
9500
10613
|
},
|
|
9501
10614
|
signal,
|
|
9502
10615
|
body: JSON.stringify({
|
|
@@ -9519,6 +10632,10 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
9519
10632
|
}
|
|
9520
10633
|
const choice = data.choices[0];
|
|
9521
10634
|
const out = { message: choice.message };
|
|
10635
|
+
const providerReceipt = supportsOpenAIRequestIdentity
|
|
10636
|
+
? _openAIProviderRequestReceipt(response, data, providerRequestId)
|
|
10637
|
+
: null;
|
|
10638
|
+
if (providerReceipt) out.providerReceipt = providerReceipt;
|
|
9522
10639
|
if (_hasCompleteProviderUsage(data.usage)) {
|
|
9523
10640
|
// OpenAI/DeepSeek/volcengine report cached prompt tokens AS PART of
|
|
9524
10641
|
// prompt_tokens — split them out so cost prices the cached prefix at the
|
|
@@ -9761,7 +10878,7 @@ export async function _retryStreamingChat(streamFn, opts = {}) {
|
|
|
9761
10878
|
for (;;) {
|
|
9762
10879
|
const attemptStartedAt = now();
|
|
9763
10880
|
try {
|
|
9764
|
-
return await streamFn();
|
|
10881
|
+
return await streamFn(attempt);
|
|
9765
10882
|
} catch (err) {
|
|
9766
10883
|
if (attempt >= retries || !_isRetryableStreamError(err, signal))
|
|
9767
10884
|
throw err;
|
|
@@ -10241,6 +11358,7 @@ function _openaiCachedTokens(usage) {
|
|
|
10241
11358
|
|
|
10242
11359
|
function _openaiInitState() {
|
|
10243
11360
|
return {
|
|
11361
|
+
responseId: null,
|
|
10244
11362
|
text: "",
|
|
10245
11363
|
tools: [],
|
|
10246
11364
|
inputTokens: null,
|
|
@@ -10261,6 +11379,7 @@ function _openaiReduceLine(state, raw, onToken) {
|
|
|
10261
11379
|
} catch {
|
|
10262
11380
|
return state;
|
|
10263
11381
|
}
|
|
11382
|
+
if (!state.responseId) state.responseId = _providerReceiptId(obj.id);
|
|
10264
11383
|
const delta = obj.choices?.[0]?.delta;
|
|
10265
11384
|
if (delta?.content) {
|
|
10266
11385
|
state.text += delta.content;
|
|
@@ -10347,12 +11466,16 @@ async function _chatOpenAIStreaming(
|
|
|
10347
11466
|
onStall,
|
|
10348
11467
|
stallMs,
|
|
10349
11468
|
stallTimeoutMs,
|
|
11469
|
+
providerRequestId = null,
|
|
10350
11470
|
) {
|
|
10351
11471
|
const response = await fetch(apiUrl, {
|
|
10352
11472
|
method: "POST",
|
|
10353
11473
|
headers: {
|
|
10354
11474
|
"Content-Type": "application/json",
|
|
10355
11475
|
Authorization: `Bearer ${apiKey}`,
|
|
11476
|
+
...(providerRequestId
|
|
11477
|
+
? { "X-Client-Request-Id": providerRequestId }
|
|
11478
|
+
: {}),
|
|
10356
11479
|
},
|
|
10357
11480
|
signal,
|
|
10358
11481
|
body: JSON.stringify(body),
|
|
@@ -10386,9 +11509,23 @@ async function _chatOpenAIStreaming(
|
|
|
10386
11509
|
} catch (err) {
|
|
10387
11510
|
if (_streamErrorDisposition(err, signal, state.text) === "rethrow")
|
|
10388
11511
|
throw err;
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
11512
|
+
const out = _finalizeTruncatedStream(_openaiFinalize, state);
|
|
11513
|
+
const providerReceipt = _openAIProviderRequestReceipt(
|
|
11514
|
+
response,
|
|
11515
|
+
{ id: state.responseId },
|
|
11516
|
+
providerRequestId,
|
|
11517
|
+
);
|
|
11518
|
+
if (providerReceipt) out.providerReceipt = providerReceipt;
|
|
11519
|
+
return out;
|
|
11520
|
+
}
|
|
11521
|
+
const out = _openaiFinalize(state);
|
|
11522
|
+
const providerReceipt = _openAIProviderRequestReceipt(
|
|
11523
|
+
response,
|
|
11524
|
+
{ id: state.responseId },
|
|
11525
|
+
providerRequestId,
|
|
11526
|
+
);
|
|
11527
|
+
if (providerReceipt) out.providerReceipt = providerReceipt;
|
|
11528
|
+
return out;
|
|
10392
11529
|
}
|
|
10393
11530
|
|
|
10394
11531
|
/**
|
|
@@ -10685,6 +11822,7 @@ function _managedCheckpointUncoveredWriterReason(toolContext) {
|
|
|
10685
11822
|
* { type: "tool-executing", tool, args }
|
|
10686
11823
|
* { type: "tool-result", tool, result, error }
|
|
10687
11824
|
* { type: "model-usage-started", callId, provider, model, source }
|
|
11825
|
+
* { type: "provider-request-receipt", source, workflowEffectId, clientRequestId, requestId, responseId }
|
|
10688
11826
|
* { type: "token-usage", callId, provider, model, usage, source? }
|
|
10689
11827
|
* { type: "model-usage-unknown", callId, provider, model, source, code }
|
|
10690
11828
|
* { type: "response-complete", content }
|
|
@@ -10708,6 +11846,154 @@ function _newModelUsageCallId(source = "model") {
|
|
|
10708
11846
|
return `${prefix}-${randomUUID()}`;
|
|
10709
11847
|
}
|
|
10710
11848
|
|
|
11849
|
+
const WORKFLOW_EFFECT_ID_RE = /^sha256:[a-f0-9]{64}$/;
|
|
11850
|
+
const WORKFLOW_CHILD_EFFECT_PROTOCOL = "cc-workflow-child-effect/v1";
|
|
11851
|
+
const WORKFLOW_TOOL_CALL_ID_RE = /^[\x21-\x7e]{1,512}$/;
|
|
11852
|
+
|
|
11853
|
+
function _normalizeWorkflowEffectId(value) {
|
|
11854
|
+
if (value == null) return null;
|
|
11855
|
+
if (typeof value !== "string" || !WORKFLOW_EFFECT_ID_RE.test(value)) {
|
|
11856
|
+
throw new TypeError("workflowEffectId must be a canonical sha256 identity");
|
|
11857
|
+
}
|
|
11858
|
+
return value;
|
|
11859
|
+
}
|
|
11860
|
+
|
|
11861
|
+
function _workflowProviderRequestId(workflowEffectId, source, sequence) {
|
|
11862
|
+
if (!workflowEffectId) return null;
|
|
11863
|
+
return `ccwf_${createHash("sha256")
|
|
11864
|
+
.update(
|
|
11865
|
+
`${workflowEffectId}\0${String(source || "model")}\0${String(sequence)}`,
|
|
11866
|
+
"utf8",
|
|
11867
|
+
)
|
|
11868
|
+
.digest("hex")}`;
|
|
11869
|
+
}
|
|
11870
|
+
|
|
11871
|
+
function _workflowToolEffectBinding(
|
|
11872
|
+
workflowEffectId,
|
|
11873
|
+
sequence,
|
|
11874
|
+
toolCallId,
|
|
11875
|
+
toolName,
|
|
11876
|
+
) {
|
|
11877
|
+
if (!workflowEffectId) return null;
|
|
11878
|
+
if (
|
|
11879
|
+
!Number.isSafeInteger(sequence) ||
|
|
11880
|
+
sequence < 1 ||
|
|
11881
|
+
typeof toolCallId !== "string" ||
|
|
11882
|
+
!WORKFLOW_TOOL_CALL_ID_RE.test(toolCallId) ||
|
|
11883
|
+
typeof toolName !== "string" ||
|
|
11884
|
+
!toolName
|
|
11885
|
+
) {
|
|
11886
|
+
const error = new TypeError(
|
|
11887
|
+
"workflow-bound tool call identity is malformed",
|
|
11888
|
+
);
|
|
11889
|
+
error.code = "CC_WORKFLOW_CHILD_EFFECT_IDENTITY_INVALID";
|
|
11890
|
+
throw error;
|
|
11891
|
+
}
|
|
11892
|
+
const childEffectId = `sha256:${createHash("sha256")
|
|
11893
|
+
.update(
|
|
11894
|
+
`${workflowEffectId}\0tool\0${String(sequence)}\0${toolCallId}\0${toolName}`,
|
|
11895
|
+
"utf8",
|
|
11896
|
+
)
|
|
11897
|
+
.digest("hex")}`;
|
|
11898
|
+
return Object.freeze({
|
|
11899
|
+
workflowEffectProtocol: WORKFLOW_CHILD_EFFECT_PROTOCOL,
|
|
11900
|
+
workflowEffectId,
|
|
11901
|
+
workflowChildEffectId: childEffectId,
|
|
11902
|
+
workflowChildSequence: sequence,
|
|
11903
|
+
});
|
|
11904
|
+
}
|
|
11905
|
+
|
|
11906
|
+
function _workflowNestedToolOutcomeUnknown(binding, toolName, result) {
|
|
11907
|
+
if (!binding || safeMcpProperty(result, "outcomeUnknown") !== true) {
|
|
11908
|
+
return null;
|
|
11909
|
+
}
|
|
11910
|
+
const error = new Error(
|
|
11911
|
+
`Workflow-bound nested tool ${toolName} has an unknown outcome and requires reconciliation`,
|
|
11912
|
+
);
|
|
11913
|
+
error.code = "CC_WORKFLOW_NESTED_TOOL_OUTCOME_UNKNOWN";
|
|
11914
|
+
error.workflowEffectOutcomeUnknown = true;
|
|
11915
|
+
error.workflowEffectId = binding.workflowEffectId;
|
|
11916
|
+
error.workflowChildEffectId = binding.workflowChildEffectId;
|
|
11917
|
+
return error;
|
|
11918
|
+
}
|
|
11919
|
+
|
|
11920
|
+
function _workflowCompactionOutcomeUnknown(
|
|
11921
|
+
cause,
|
|
11922
|
+
code = "CC_WORKFLOW_COMPACTION_PROVIDER_OUTCOME_UNKNOWN",
|
|
11923
|
+
message = "Workflow-bound semantic compaction provider outcome is unknown",
|
|
11924
|
+
) {
|
|
11925
|
+
const error = new Error(message, cause ? { cause } : undefined);
|
|
11926
|
+
error.code = code;
|
|
11927
|
+
error.workflowEffectOutcomeUnknown = true;
|
|
11928
|
+
error.compactionFailureReported = false;
|
|
11929
|
+
return error;
|
|
11930
|
+
}
|
|
11931
|
+
|
|
11932
|
+
function _markWorkflowCompactionFailureReported(error) {
|
|
11933
|
+
if (error?.workflowEffectOutcomeUnknown === true) {
|
|
11934
|
+
error.compactionFailureReported = true;
|
|
11935
|
+
}
|
|
11936
|
+
return error;
|
|
11937
|
+
}
|
|
11938
|
+
|
|
11939
|
+
function _snapshotProviderRequestReceipt(
|
|
11940
|
+
value,
|
|
11941
|
+
expectedClientRequestId,
|
|
11942
|
+
expectedProvider,
|
|
11943
|
+
) {
|
|
11944
|
+
if (value == null) return null;
|
|
11945
|
+
if (isProxy(value)) {
|
|
11946
|
+
throw new TypeError("provider request receipt must not be a Proxy");
|
|
11947
|
+
}
|
|
11948
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
11949
|
+
const expectedFields = [
|
|
11950
|
+
"clientRequestId",
|
|
11951
|
+
"independentlyReadable",
|
|
11952
|
+
"protocol",
|
|
11953
|
+
"provider",
|
|
11954
|
+
"requestId",
|
|
11955
|
+
"requestIdentitySemantics",
|
|
11956
|
+
"responseId",
|
|
11957
|
+
];
|
|
11958
|
+
const fields = Object.keys(descriptors).sort();
|
|
11959
|
+
const hasOnlyDataFields =
|
|
11960
|
+
fields.length === expectedFields.length &&
|
|
11961
|
+
fields.every(
|
|
11962
|
+
(field, index) =>
|
|
11963
|
+
field === expectedFields[index] &&
|
|
11964
|
+
Object.hasOwn(descriptors[field], "value") &&
|
|
11965
|
+
descriptors[field].enumerable === true,
|
|
11966
|
+
);
|
|
11967
|
+
if (
|
|
11968
|
+
!value ||
|
|
11969
|
+
typeof value !== "object" ||
|
|
11970
|
+
Array.isArray(value) ||
|
|
11971
|
+
!hasOnlyDataFields ||
|
|
11972
|
+
descriptors.protocol.value !== "cc-provider-request-receipt/v1" ||
|
|
11973
|
+
!/^[a-z0-9][a-z0-9._-]{0,63}$/.test(descriptors.provider.value || "") ||
|
|
11974
|
+
descriptors.provider.value !== expectedProvider ||
|
|
11975
|
+
descriptors.clientRequestId.value !== expectedClientRequestId ||
|
|
11976
|
+
descriptors.requestIdentitySemantics.value !== "trace-only" ||
|
|
11977
|
+
descriptors.independentlyReadable.value !== false
|
|
11978
|
+
) {
|
|
11979
|
+
throw new TypeError("provider request receipt is malformed");
|
|
11980
|
+
}
|
|
11981
|
+
const requestId = _providerReceiptId(descriptors.requestId.value);
|
|
11982
|
+
const responseId = _providerReceiptId(descriptors.responseId.value);
|
|
11983
|
+
if (!requestId && !responseId) {
|
|
11984
|
+
throw new TypeError("provider request receipt has no provider identifier");
|
|
11985
|
+
}
|
|
11986
|
+
return Object.freeze({
|
|
11987
|
+
protocol: "cc-provider-request-receipt/v1",
|
|
11988
|
+
provider: descriptors.provider.value,
|
|
11989
|
+
clientRequestId: expectedClientRequestId,
|
|
11990
|
+
requestId,
|
|
11991
|
+
responseId,
|
|
11992
|
+
requestIdentitySemantics: "trace-only",
|
|
11993
|
+
independentlyReadable: false,
|
|
11994
|
+
});
|
|
11995
|
+
}
|
|
11996
|
+
|
|
10711
11997
|
function _runtimeUsageBoundaryFailure(error, code, fallbackMessage) {
|
|
10712
11998
|
const failure =
|
|
10713
11999
|
error && (typeof error === "object" || typeof error === "function")
|
|
@@ -10877,7 +12163,7 @@ function _assertStrictUsageObserver(observer, phase) {
|
|
|
10877
12163
|
function _autoCompactionUsageState(options) {
|
|
10878
12164
|
let state = _autoCompactionUsageStates.get(options);
|
|
10879
12165
|
if (!state) {
|
|
10880
|
-
state = { calls: [] };
|
|
12166
|
+
state = { calls: [], callSequence: null };
|
|
10881
12167
|
_autoCompactionUsageStates.set(options, state);
|
|
10882
12168
|
}
|
|
10883
12169
|
return state;
|
|
@@ -10901,27 +12187,59 @@ function _instrumentAutoCompactorUsage(compactor, options) {
|
|
|
10901
12187
|
const original = installed?.original || compactor.llmQuery;
|
|
10902
12188
|
const state = _autoCompactionUsageState(options);
|
|
10903
12189
|
const wrapper = async function instrumentedCompactionQuery(...args) {
|
|
12190
|
+
const workflowEffectId = _normalizeWorkflowEffectId(
|
|
12191
|
+
options.workflowEffectId,
|
|
12192
|
+
);
|
|
12193
|
+
const callSequence =
|
|
12194
|
+
Number.isSafeInteger(state.callSequence) && state.callSequence > 0
|
|
12195
|
+
? state.callSequence
|
|
12196
|
+
: null;
|
|
12197
|
+
if (workflowEffectId && callSequence == null) {
|
|
12198
|
+
throw _runtimeUsageBoundaryFailure(
|
|
12199
|
+
null,
|
|
12200
|
+
"CC_COMPACTION_REQUEST_SEQUENCE_REQUIRED",
|
|
12201
|
+
"Workflow-bound semantic compaction requires a stable call sequence",
|
|
12202
|
+
);
|
|
12203
|
+
}
|
|
12204
|
+
const providerRequestId = _workflowProviderRequestId(
|
|
12205
|
+
workflowEffectId,
|
|
12206
|
+
"semantic-compaction",
|
|
12207
|
+
callSequence,
|
|
12208
|
+
);
|
|
10904
12209
|
const call = {
|
|
10905
12210
|
callId: _newModelUsageCallId("semantic-compaction"),
|
|
10906
|
-
provider: options.provider ||
|
|
10907
|
-
model: options.model ||
|
|
12211
|
+
provider: options.provider || "ollama",
|
|
12212
|
+
model: options.model || "unknown",
|
|
10908
12213
|
source: "semantic-compaction",
|
|
12214
|
+
workflowEffectId,
|
|
12215
|
+
callSequence,
|
|
12216
|
+
providerRequestId,
|
|
12217
|
+
providerReceipt: null,
|
|
10909
12218
|
observerError: null,
|
|
10910
12219
|
observerFailed: false,
|
|
10911
12220
|
boundaryNotified: false,
|
|
10912
12221
|
};
|
|
10913
12222
|
state.calls.push(call);
|
|
12223
|
+
const boundaryEvent = {
|
|
12224
|
+
type: "model-usage-started",
|
|
12225
|
+
callId: call.callId,
|
|
12226
|
+
provider: call.provider,
|
|
12227
|
+
model: call.model,
|
|
12228
|
+
source: call.source,
|
|
12229
|
+
...(providerRequestId
|
|
12230
|
+
? {
|
|
12231
|
+
workflowEffectId,
|
|
12232
|
+
callSequence,
|
|
12233
|
+
providerRequestId,
|
|
12234
|
+
requestIdentitySemantics: "trace-only",
|
|
12235
|
+
}
|
|
12236
|
+
: {}),
|
|
12237
|
+
};
|
|
10914
12238
|
try {
|
|
10915
12239
|
if (typeof options.onUsageBoundary === "function") {
|
|
10916
12240
|
// Synchronous and before `original`: a host can durably append the
|
|
10917
12241
|
// start row, and any append failure prevents provider spend.
|
|
10918
|
-
const observation = options.onUsageBoundary(
|
|
10919
|
-
type: "model-usage-started",
|
|
10920
|
-
callId: call.callId,
|
|
10921
|
-
provider: call.provider,
|
|
10922
|
-
model: call.model,
|
|
10923
|
-
source: call.source,
|
|
10924
|
-
});
|
|
12242
|
+
const observation = options.onUsageBoundary(boundaryEvent);
|
|
10925
12243
|
if (observation && typeof observation.then === "function") {
|
|
10926
12244
|
void Promise.resolve(observation).catch(() => {});
|
|
10927
12245
|
const error = new Error(
|
|
@@ -10932,13 +12250,54 @@ function _instrumentAutoCompactorUsage(compactor, options) {
|
|
|
10932
12250
|
}
|
|
10933
12251
|
call.boundaryNotified = true;
|
|
10934
12252
|
}
|
|
12253
|
+
if (
|
|
12254
|
+
providerRequestId &&
|
|
12255
|
+
typeof options.onProviderRequestBoundary === "function"
|
|
12256
|
+
) {
|
|
12257
|
+
const observation = options.onProviderRequestBoundary(boundaryEvent);
|
|
12258
|
+
if (observation && typeof observation.then === "function") {
|
|
12259
|
+
void Promise.resolve(observation).catch(() => {});
|
|
12260
|
+
throw _runtimeUsageBoundaryFailure(
|
|
12261
|
+
null,
|
|
12262
|
+
"CC_PROVIDER_REQUEST_BOUNDARY_ASYNC",
|
|
12263
|
+
"Provider request boundary observer must be synchronous",
|
|
12264
|
+
);
|
|
12265
|
+
}
|
|
12266
|
+
}
|
|
10935
12267
|
} catch (error) {
|
|
10936
12268
|
call.observerError = error;
|
|
10937
12269
|
call.observerFailed = true;
|
|
10938
12270
|
throw error;
|
|
10939
12271
|
}
|
|
10940
12272
|
|
|
10941
|
-
|
|
12273
|
+
const requestBinding = providerRequestId
|
|
12274
|
+
? Object.freeze({
|
|
12275
|
+
workflowEffectId,
|
|
12276
|
+
callSequence,
|
|
12277
|
+
source: "semantic-compaction",
|
|
12278
|
+
providerRequestId,
|
|
12279
|
+
requestIdentitySemantics: "trace-only",
|
|
12280
|
+
})
|
|
12281
|
+
: null;
|
|
12282
|
+
try {
|
|
12283
|
+
const result = await original.apply(
|
|
12284
|
+
this,
|
|
12285
|
+
requestBinding ? [...args, requestBinding] : args,
|
|
12286
|
+
);
|
|
12287
|
+
call.providerReceipt = providerRequestId
|
|
12288
|
+
? _snapshotProviderRequestReceipt(
|
|
12289
|
+
result?.providerReceipt,
|
|
12290
|
+
providerRequestId,
|
|
12291
|
+
call.provider,
|
|
12292
|
+
)
|
|
12293
|
+
: null;
|
|
12294
|
+
return result;
|
|
12295
|
+
} catch (error) {
|
|
12296
|
+
if (workflowEffectId) {
|
|
12297
|
+
throw _workflowCompactionOutcomeUnknown(error);
|
|
12298
|
+
}
|
|
12299
|
+
throw error;
|
|
12300
|
+
}
|
|
10942
12301
|
};
|
|
10943
12302
|
|
|
10944
12303
|
try {
|
|
@@ -10992,7 +12351,7 @@ async function _getAutoCompactor(options) {
|
|
|
10992
12351
|
? options.compactionLlmQuery
|
|
10993
12352
|
: options.chatFn
|
|
10994
12353
|
? null
|
|
10995
|
-
: async (prompt) => {
|
|
12354
|
+
: async (prompt, requestBinding = null) => {
|
|
10996
12355
|
const maxOutputTokens = Math.min(
|
|
10997
12356
|
4096,
|
|
10998
12357
|
Math.max(
|
|
@@ -11011,14 +12370,17 @@ async function _getAutoCompactor(options) {
|
|
|
11011
12370
|
onToken: undefined,
|
|
11012
12371
|
onStall: undefined,
|
|
11013
12372
|
onStreamRetry: undefined,
|
|
12373
|
+
providerRequestId:
|
|
12374
|
+
requestBinding?.providerRequestId || undefined,
|
|
11014
12375
|
maxOutputTokens,
|
|
11015
12376
|
},
|
|
11016
12377
|
);
|
|
11017
12378
|
return {
|
|
11018
12379
|
summary: response?.message?.content || "",
|
|
11019
12380
|
usage: response?.usage || null,
|
|
11020
|
-
provider: options.provider ||
|
|
11021
|
-
model: options.model ||
|
|
12381
|
+
provider: options.provider || "ollama",
|
|
12382
|
+
model: options.model || "unknown",
|
|
12383
|
+
providerReceipt: response?.providerReceipt || null,
|
|
11022
12384
|
};
|
|
11023
12385
|
};
|
|
11024
12386
|
compressor = new PromptCompressor({
|
|
@@ -11258,6 +12620,7 @@ export async function* agentLoop(messages, options) {
|
|
|
11258
12620
|
await import("../lib/iteration-budget.js");
|
|
11259
12621
|
const budget = options.iterationBudget || new IterationBudget();
|
|
11260
12622
|
const signal = options.signal || null;
|
|
12623
|
+
const workflowEffectId = _normalizeWorkflowEffectId(options.workflowEffectId);
|
|
11261
12624
|
// Optional OpenTelemetry recorder (TelemetryRecorder). When present, the loop
|
|
11262
12625
|
// emits model/tool/retry spans + a per-run counter; when absent, zero cost.
|
|
11263
12626
|
const recorder = options.recorder || null;
|
|
@@ -11268,6 +12631,7 @@ export async function* agentLoop(messages, options) {
|
|
|
11268
12631
|
options.runId ||
|
|
11269
12632
|
`run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11270
12633
|
let automaticCompactionSettlementBlocked = false;
|
|
12634
|
+
let workflowChildEffectSequence = 0;
|
|
11271
12635
|
|
|
11272
12636
|
const fileMutationScope = normalizeExactFileMutationScope(
|
|
11273
12637
|
options.fileMutationScope,
|
|
@@ -11367,6 +12731,7 @@ export async function* agentLoop(messages, options) {
|
|
|
11367
12731
|
mcpDispatchAdmission: hermeticExecution
|
|
11368
12732
|
? null
|
|
11369
12733
|
: options.mcpDispatchAdmission || null,
|
|
12734
|
+
workflowEffectId,
|
|
11370
12735
|
// A loop-local identity prevents one session's lazy instruction commits
|
|
11371
12736
|
// from suppressing first-access delivery in another session at the same
|
|
11372
12737
|
// cwd. Hosts may inject a stable identity across resumed turns.
|
|
@@ -11560,10 +12925,13 @@ export async function* agentLoop(messages, options) {
|
|
|
11560
12925
|
// attempt inside one logical `llmCall`, where this generator cannot durably
|
|
11561
12926
|
// bracket each transport attempt. Strict call-ledger sessions therefore
|
|
11562
12927
|
// disable that recovery path and surface the configured provider's failure
|
|
11563
|
-
// as unknown
|
|
12928
|
+
// as unknown. A workflow-bound request does the same: a hidden fallback
|
|
12929
|
+
// would be a second physical provider attempt under one durable effect.
|
|
12930
|
+
// Legacy sessions keep the existing self-healing behavior.
|
|
11564
12931
|
if (
|
|
11565
12932
|
options.runnableProviderFallback !== false &&
|
|
11566
|
-
options.strictUsageTelemetry !== true
|
|
12933
|
+
options.strictUsageTelemetry !== true &&
|
|
12934
|
+
workflowEffectId == null
|
|
11567
12935
|
) {
|
|
11568
12936
|
const { makeRunnableProviderFallback } =
|
|
11569
12937
|
await import("../lib/runnable-provider.js");
|
|
@@ -11707,6 +13075,7 @@ export async function* agentLoop(messages, options) {
|
|
|
11707
13075
|
if (compactor && compactor.shouldAutoCompact(messages)) {
|
|
11708
13076
|
compactionUsageState = _autoCompactionUsageState(options);
|
|
11709
13077
|
compactionUsageState.calls = [];
|
|
13078
|
+
compactionUsageState.callSequence = budget.consumed;
|
|
11710
13079
|
// This is the canonical pre-provider authority snapshot. A local
|
|
11711
13080
|
// micro-compact may change the working input, but the durable CAS must
|
|
11712
13081
|
// still compare against the transcript state that existed before any
|
|
@@ -11803,6 +13172,16 @@ export async function* agentLoop(messages, options) {
|
|
|
11803
13172
|
}
|
|
11804
13173
|
compactionUsageCall = compactionUsageState.calls.at(-1) || null;
|
|
11805
13174
|
const compactionUsage = _compactionTokenUsage(stats);
|
|
13175
|
+
if (compactionUsageCall?.providerReceipt) {
|
|
13176
|
+
yield {
|
|
13177
|
+
type: "provider-request-receipt",
|
|
13178
|
+
callId: compactionUsageCall.callId,
|
|
13179
|
+
workflowEffectId,
|
|
13180
|
+
callSequence: compactionUsageCall.callSequence,
|
|
13181
|
+
source: "semantic-compaction",
|
|
13182
|
+
...compactionUsageCall.providerReceipt,
|
|
13183
|
+
};
|
|
13184
|
+
}
|
|
11806
13185
|
if (stats.degraded === true) {
|
|
11807
13186
|
const usageOutcomeUnknown = stats.summaryUsageUnknown === true;
|
|
11808
13187
|
const projectedStats = usageOutcomeUnknown
|
|
@@ -11869,6 +13248,15 @@ export async function* agentLoop(messages, options) {
|
|
|
11869
13248
|
backgroundSubAgents,
|
|
11870
13249
|
backgroundUsageFailureState,
|
|
11871
13250
|
);
|
|
13251
|
+
if (workflowEffectId) {
|
|
13252
|
+
throw _markWorkflowCompactionFailureReported(
|
|
13253
|
+
_workflowCompactionOutcomeUnknown(
|
|
13254
|
+
null,
|
|
13255
|
+
"CC_WORKFLOW_COMPACTION_USAGE_UNKNOWN",
|
|
13256
|
+
"Workflow-bound semantic compaction usage is unknown",
|
|
13257
|
+
),
|
|
13258
|
+
);
|
|
13259
|
+
}
|
|
11872
13260
|
return;
|
|
11873
13261
|
}
|
|
11874
13262
|
if (
|
|
@@ -11922,6 +13310,15 @@ export async function* agentLoop(messages, options) {
|
|
|
11922
13310
|
backgroundSubAgents,
|
|
11923
13311
|
backgroundUsageFailureState,
|
|
11924
13312
|
);
|
|
13313
|
+
if (workflowEffectId) {
|
|
13314
|
+
throw _markWorkflowCompactionFailureReported(
|
|
13315
|
+
_workflowCompactionOutcomeUnknown(
|
|
13316
|
+
error,
|
|
13317
|
+
"CC_WORKFLOW_COMPACTION_SETTLEMENT_UNKNOWN",
|
|
13318
|
+
"Workflow-bound semantic compaction settlement is unknown",
|
|
13319
|
+
),
|
|
13320
|
+
);
|
|
13321
|
+
}
|
|
11925
13322
|
return;
|
|
11926
13323
|
}
|
|
11927
13324
|
if (!hermeticExecution) {
|
|
@@ -11969,6 +13366,12 @@ export async function* agentLoop(messages, options) {
|
|
|
11969
13366
|
} catch (_e) {
|
|
11970
13367
|
if (isAbortError(_e) || signal?.aborted) throw _e;
|
|
11971
13368
|
if (_e?.runtimeLedgerPersistence === true) throw _e;
|
|
13369
|
+
if (
|
|
13370
|
+
_e?.workflowEffectOutcomeUnknown === true &&
|
|
13371
|
+
_e?.compactionFailureReported === true
|
|
13372
|
+
) {
|
|
13373
|
+
throw _e;
|
|
13374
|
+
}
|
|
11972
13375
|
if (compactionObserverFailure === _e) throw _e;
|
|
11973
13376
|
const observerFailure = compactionUsageState?.calls?.find(
|
|
11974
13377
|
(call) => call.observerFailed && call.observerError === _e,
|
|
@@ -11995,6 +13398,13 @@ export async function* agentLoop(messages, options) {
|
|
|
11995
13398
|
backgroundSubAgents,
|
|
11996
13399
|
backgroundUsageFailureState,
|
|
11997
13400
|
);
|
|
13401
|
+
if (workflowEffectId) {
|
|
13402
|
+
throw _markWorkflowCompactionFailureReported(
|
|
13403
|
+
_e?.workflowEffectOutcomeUnknown === true
|
|
13404
|
+
? _e
|
|
13405
|
+
: _workflowCompactionOutcomeUnknown(_e),
|
|
13406
|
+
);
|
|
13407
|
+
}
|
|
11998
13408
|
return;
|
|
11999
13409
|
}
|
|
12000
13410
|
yield {
|
|
@@ -12101,12 +13511,28 @@ export async function* agentLoop(messages, options) {
|
|
|
12101
13511
|
throwIfAborted(signal);
|
|
12102
13512
|
_throwBackgroundUsageFailureState(backgroundUsageFailureState);
|
|
12103
13513
|
_throwSettledBackgroundUsageFailure(backgroundSubAgents);
|
|
13514
|
+
const providerRequestId = _workflowProviderRequestId(
|
|
13515
|
+
workflowEffectId,
|
|
13516
|
+
"model",
|
|
13517
|
+
budget.consumed,
|
|
13518
|
+
);
|
|
13519
|
+
const providerCallOptions = providerRequestId
|
|
13520
|
+
? { ...effectiveToolOptions, providerRequestId }
|
|
13521
|
+
: effectiveToolOptions;
|
|
12104
13522
|
const modelUsageCall = {
|
|
12105
13523
|
type: "model-usage-started",
|
|
12106
13524
|
callId: _newModelUsageCallId("model"),
|
|
12107
13525
|
provider: options.provider || "ollama",
|
|
12108
13526
|
model: options.model || "unknown",
|
|
12109
13527
|
source: "model",
|
|
13528
|
+
...(providerRequestId
|
|
13529
|
+
? {
|
|
13530
|
+
workflowEffectId,
|
|
13531
|
+
callSequence: budget.consumed,
|
|
13532
|
+
providerRequestId,
|
|
13533
|
+
requestIdentitySemantics: "trace-only",
|
|
13534
|
+
}
|
|
13535
|
+
: {}),
|
|
12110
13536
|
};
|
|
12111
13537
|
// The consumer receives this boundary before `.next()` resumes into the
|
|
12112
13538
|
// provider call, so a durable append failure can prevent spend.
|
|
@@ -12132,6 +13558,7 @@ export async function* agentLoop(messages, options) {
|
|
|
12132
13558
|
}
|
|
12133
13559
|
|
|
12134
13560
|
let result;
|
|
13561
|
+
let providerReceipt = null;
|
|
12135
13562
|
try {
|
|
12136
13563
|
result = await _withSpan(
|
|
12137
13564
|
recorder,
|
|
@@ -12142,7 +13569,7 @@ export async function* agentLoop(messages, options) {
|
|
|
12142
13569
|
"agent.iteration": budget.consumed,
|
|
12143
13570
|
...(modelIdAttrs || {}),
|
|
12144
13571
|
},
|
|
12145
|
-
() => llmCall(callMessages,
|
|
13572
|
+
() => llmCall(callMessages, providerCallOptions),
|
|
12146
13573
|
(span, r) => {
|
|
12147
13574
|
const t = _usageTokens(r?.usage);
|
|
12148
13575
|
if (t) {
|
|
@@ -12183,6 +13610,13 @@ export async function* agentLoop(messages, options) {
|
|
|
12183
13610
|
},
|
|
12184
13611
|
"model_error",
|
|
12185
13612
|
);
|
|
13613
|
+
providerReceipt = providerRequestId
|
|
13614
|
+
? _snapshotProviderRequestReceipt(
|
|
13615
|
+
result?.providerReceipt,
|
|
13616
|
+
providerRequestId,
|
|
13617
|
+
modelUsageCall.provider,
|
|
13618
|
+
)
|
|
13619
|
+
: null;
|
|
12186
13620
|
} catch (error) {
|
|
12187
13621
|
// Never retain provider error text in the usage ledger. Pause on a fixed
|
|
12188
13622
|
// unknown settlement, then rethrow only after the consumer resumes.
|
|
@@ -12203,6 +13637,20 @@ export async function* agentLoop(messages, options) {
|
|
|
12203
13637
|
if (recorder) recorder.counter("agent.model.calls", 1);
|
|
12204
13638
|
const msg = result?.message;
|
|
12205
13639
|
|
|
13640
|
+
if (providerReceipt) {
|
|
13641
|
+
// Yield the provider-returned identifiers before billable usage. A host
|
|
13642
|
+
// that stops after a cost boundary still retains the exact request/effect
|
|
13643
|
+
// binding. This is trace evidence only, never an exactly-once assertion.
|
|
13644
|
+
yield {
|
|
13645
|
+
type: "provider-request-receipt",
|
|
13646
|
+
callId: modelUsageCall.callId,
|
|
13647
|
+
workflowEffectId,
|
|
13648
|
+
callSequence: budget.consumed,
|
|
13649
|
+
source: modelUsageCall.source,
|
|
13650
|
+
...providerReceipt,
|
|
13651
|
+
};
|
|
13652
|
+
}
|
|
13653
|
+
|
|
12206
13654
|
// Close the already-persisted call boundary before honoring an abort that
|
|
12207
13655
|
// raced with a successful provider response. Once the transport returned,
|
|
12208
13656
|
// dropping this settlement would strand a durable `started` row forever.
|
|
@@ -12428,7 +13876,13 @@ export async function* agentLoop(messages, options) {
|
|
|
12428
13876
|
} catch {
|
|
12429
13877
|
toolArgs = {};
|
|
12430
13878
|
}
|
|
12431
|
-
|
|
13879
|
+
const workflowBinding = _workflowToolEffectBinding(
|
|
13880
|
+
workflowEffectId,
|
|
13881
|
+
workflowEffectId ? (workflowChildEffectSequence += 1) : 0,
|
|
13882
|
+
call.id,
|
|
13883
|
+
call.function.name,
|
|
13884
|
+
);
|
|
13885
|
+
return { call, toolArgs, workflowBinding };
|
|
12432
13886
|
});
|
|
12433
13887
|
// Yield every start boundary before dispatch. Async-generator consumers
|
|
12434
13888
|
// persist each boundary before asking for the next item, so by the time
|
|
@@ -12437,20 +13891,21 @@ export async function* agentLoop(messages, options) {
|
|
|
12437
13891
|
try {
|
|
12438
13892
|
throwIfAborted(signal);
|
|
12439
13893
|
_throwBackgroundUsageFailureState(backgroundUsageFailureState);
|
|
12440
|
-
for (const { call, toolArgs } of prepared) {
|
|
13894
|
+
for (const { call, toolArgs, workflowBinding } of prepared) {
|
|
12441
13895
|
yield {
|
|
12442
13896
|
type: "tool-executing",
|
|
12443
13897
|
tool: call.function.name,
|
|
12444
13898
|
args: toolArgs,
|
|
12445
13899
|
tool_use_id: call.id,
|
|
12446
13900
|
turn_id: `${runId}:t${budget.consumed}`,
|
|
13901
|
+
...(workflowBinding || {}),
|
|
12447
13902
|
};
|
|
12448
|
-
startedPrepared.push({ call, toolArgs });
|
|
13903
|
+
startedPrepared.push({ call, toolArgs, workflowBinding });
|
|
12449
13904
|
throwIfAborted(signal);
|
|
12450
13905
|
}
|
|
12451
13906
|
_throwBackgroundUsageFailureState(backgroundUsageFailureState);
|
|
12452
13907
|
} catch (admissionFailure) {
|
|
12453
|
-
for (const { call } of startedPrepared) {
|
|
13908
|
+
for (const { call, workflowBinding } of startedPrepared) {
|
|
12454
13909
|
yield {
|
|
12455
13910
|
type: "tool-result",
|
|
12456
13911
|
tool: call.function.name,
|
|
@@ -12458,6 +13913,7 @@ export async function* agentLoop(messages, options) {
|
|
|
12458
13913
|
error: "tool execution cancelled before dispatch",
|
|
12459
13914
|
tool_use_id: call.id,
|
|
12460
13915
|
turn_id: `${runId}:t${budget.consumed}`,
|
|
13916
|
+
...(workflowBinding || {}),
|
|
12461
13917
|
};
|
|
12462
13918
|
}
|
|
12463
13919
|
throw admissionFailure;
|
|
@@ -12465,18 +13921,30 @@ export async function* agentLoop(messages, options) {
|
|
|
12465
13921
|
// Only after every start has been consumed do we kick every read off.
|
|
12466
13922
|
// Settle each promise into {result,error} so an abort before its ordered
|
|
12467
13923
|
// result is consumed can never surface an unhandled rejection.
|
|
12468
|
-
const inflight = prepared.map(({ call, toolArgs }) => {
|
|
13924
|
+
const inflight = prepared.map(({ call, toolArgs, workflowBinding }) => {
|
|
12469
13925
|
const promise = executeTool(call.function.name, toolArgs, {
|
|
12470
13926
|
...toolContext,
|
|
12471
13927
|
toolCallId: call.id,
|
|
12472
13928
|
turnId: `${runId}:t${budget.consumed}`,
|
|
13929
|
+
...(workflowBinding || {}),
|
|
12473
13930
|
}).then(
|
|
12474
13931
|
(result) => ({ result, error: null }),
|
|
12475
|
-
(err) => ({
|
|
13932
|
+
(err) => ({
|
|
13933
|
+
result: {
|
|
13934
|
+
error: err.message,
|
|
13935
|
+
...(workflowBinding
|
|
13936
|
+
? {
|
|
13937
|
+
code: "CC_WORKFLOW_NESTED_TOOL_EXECUTION_THROWN",
|
|
13938
|
+
outcomeUnknown: true,
|
|
13939
|
+
}
|
|
13940
|
+
: {}),
|
|
13941
|
+
},
|
|
13942
|
+
error: err.message,
|
|
13943
|
+
}),
|
|
12476
13944
|
);
|
|
12477
|
-
return { call, toolArgs, promise };
|
|
13945
|
+
return { call, toolArgs, workflowBinding, promise };
|
|
12478
13946
|
});
|
|
12479
|
-
for (const { call, toolArgs, promise } of inflight) {
|
|
13947
|
+
for (const { call, toolArgs, workflowBinding, promise } of inflight) {
|
|
12480
13948
|
throwIfAborted(signal);
|
|
12481
13949
|
_throwBackgroundUsageFailureState(backgroundUsageFailureState);
|
|
12482
13950
|
const { result: toolResult, error: toolError } = await promise;
|
|
@@ -12516,9 +13984,16 @@ export async function* agentLoop(messages, options) {
|
|
|
12516
13984
|
error: toolError,
|
|
12517
13985
|
tool_use_id: call.id,
|
|
12518
13986
|
turn_id: `${runId}:t${budget.consumed}`,
|
|
13987
|
+
...(workflowBinding || {}),
|
|
12519
13988
|
permission_decision_id: decision?.id || null,
|
|
12520
13989
|
permission_decision: decision,
|
|
12521
13990
|
};
|
|
13991
|
+
const nestedOutcomeUnknown = _workflowNestedToolOutcomeUnknown(
|
|
13992
|
+
workflowBinding,
|
|
13993
|
+
call.function.name,
|
|
13994
|
+
toolResult,
|
|
13995
|
+
);
|
|
13996
|
+
if (nestedOutcomeUnknown) throw nestedOutcomeUnknown;
|
|
12522
13997
|
messages.push({
|
|
12523
13998
|
role: "tool",
|
|
12524
13999
|
content: toolContent,
|
|
@@ -12663,6 +14138,7 @@ export async function* agentLoop(messages, options) {
|
|
|
12663
14138
|
|
|
12664
14139
|
let managedCheckpointHandle = null;
|
|
12665
14140
|
let managedCheckpointPreparationError = null;
|
|
14141
|
+
let workflowBinding = null;
|
|
12666
14142
|
try {
|
|
12667
14143
|
managedCheckpointHandle = beginManagedToolCheckpoint({
|
|
12668
14144
|
enabled: toolContext.managedCheckpoint === true,
|
|
@@ -12716,12 +14192,19 @@ export async function* agentLoop(messages, options) {
|
|
|
12716
14192
|
// event. Consumers use that event to bind hooks, telemetry and turns to
|
|
12717
14193
|
// an actual invocation.
|
|
12718
14194
|
if (!managedCheckpointPreparationError) {
|
|
14195
|
+
workflowBinding = _workflowToolEffectBinding(
|
|
14196
|
+
workflowEffectId,
|
|
14197
|
+
workflowEffectId ? (workflowChildEffectSequence += 1) : 0,
|
|
14198
|
+
call.id,
|
|
14199
|
+
toolName,
|
|
14200
|
+
);
|
|
12719
14201
|
yield {
|
|
12720
14202
|
type: "tool-executing",
|
|
12721
14203
|
tool: toolName,
|
|
12722
14204
|
args: toolArgs,
|
|
12723
14205
|
tool_use_id: call.id,
|
|
12724
14206
|
turn_id: `${runId}:t${budget.consumed}`,
|
|
14207
|
+
...(workflowBinding || {}),
|
|
12725
14208
|
};
|
|
12726
14209
|
// The event yield is a host persistence boundary. A detached child's
|
|
12727
14210
|
// fatal usage write can settle while the consumer handles it; fence
|
|
@@ -12739,6 +14222,7 @@ export async function* agentLoop(messages, options) {
|
|
|
12739
14222
|
error: "authoritative background usage persistence failure",
|
|
12740
14223
|
tool_use_id: call.id,
|
|
12741
14224
|
turn_id: `${runId}:t${budget.consumed}`,
|
|
14225
|
+
...(workflowBinding || {}),
|
|
12742
14226
|
};
|
|
12743
14227
|
throw backgroundFailure;
|
|
12744
14228
|
}
|
|
@@ -12788,6 +14272,7 @@ export async function* agentLoop(messages, options) {
|
|
|
12788
14272
|
...toolContext,
|
|
12789
14273
|
toolCallId: call.id,
|
|
12790
14274
|
turnId: `${runId}:t${budget.consumed}`,
|
|
14275
|
+
...(workflowBinding || {}),
|
|
12791
14276
|
}),
|
|
12792
14277
|
(span, r) => {
|
|
12793
14278
|
span.setAttribute(
|
|
@@ -12827,7 +14312,15 @@ export async function* agentLoop(messages, options) {
|
|
|
12827
14312
|
);
|
|
12828
14313
|
} catch (err) {
|
|
12829
14314
|
if (err?.runtimeLedgerPersistence === true) throw err;
|
|
12830
|
-
toolResult = {
|
|
14315
|
+
toolResult = {
|
|
14316
|
+
error: err.message,
|
|
14317
|
+
...(workflowBinding
|
|
14318
|
+
? {
|
|
14319
|
+
code: "CC_WORKFLOW_NESTED_TOOL_EXECUTION_THROWN",
|
|
14320
|
+
outcomeUnknown: true,
|
|
14321
|
+
}
|
|
14322
|
+
: {}),
|
|
14323
|
+
};
|
|
12831
14324
|
toolError = err.message;
|
|
12832
14325
|
}
|
|
12833
14326
|
}
|
|
@@ -12931,10 +14424,18 @@ export async function* agentLoop(messages, options) {
|
|
|
12931
14424
|
error: toolError,
|
|
12932
14425
|
tool_use_id: call.id,
|
|
12933
14426
|
turn_id: `${runId}:t${budget.consumed}`,
|
|
14427
|
+
...(workflowBinding || {}),
|
|
12934
14428
|
permission_decision_id: decision?.id || null,
|
|
12935
14429
|
permission_decision: decision,
|
|
12936
14430
|
};
|
|
12937
14431
|
|
|
14432
|
+
const nestedOutcomeUnknown = _workflowNestedToolOutcomeUnknown(
|
|
14433
|
+
workflowBinding,
|
|
14434
|
+
toolName,
|
|
14435
|
+
toolResult,
|
|
14436
|
+
);
|
|
14437
|
+
if (nestedOutcomeUnknown) throw nestedOutcomeUnknown;
|
|
14438
|
+
|
|
12938
14439
|
messages.push({
|
|
12939
14440
|
role: "tool",
|
|
12940
14441
|
content: toolContent,
|