chainlesschain 0.162.189 → 0.162.193
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 +61 -11
- package/bin/chainlesschain.js +7 -52
- package/completions/_cc +209 -0
- package/completions/cc.bash +18 -0
- package/completions/cc.fish +13 -0
- package/completions/cc.ps1 +226 -0
- package/package.json +10 -2
- package/src/assets/web-panel/assets/{AIOps-DbZft8w0.js → AIOps-DBLkaTje.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DoOZpwfN.js → ActionButton-BAy-Qebo.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-B0J0KLTW.js → Analytics-Dfb1Mlau.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D9eIR0qq.js → AppLayout-BbFkN8m0.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BnZXEVeo.js → Artifacts-kM4-cEEH.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-X1ce4Bjn.js → Audit-BSQfYWf3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BvWR-y5v.js → BackgroundAgents-CWDbI_fG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BublSGEd.js → Backup-BxLPbq_Q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CR_77Hso.js → BaseInput-DuIbdrp6.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DixW52tq.js → Chat-NEmjGahU.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDHDpBBA.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CCv1C87K.js → Checkbox-CaoeG3Ns.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B03Cc3dd.js → Codegen-BFf_g3xY.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CA06-6pU.js → Col-CNOd8mdQ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Bj881J52.js → Community-BhpDqz-i.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-UZLTPoaL.js → Compact-CQ42T7Z6.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-kGwzRb_0.js → Compliance-DI5PcLMN.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-KundoYM8.js → Cowork-wYYZe0wR.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BYZyoqDe.js → Cron-Dlq03DD_.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BhrrppBX.js → Crosschain-BzqS8PP3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CdC6EguZ.js → DID-It55Yem7.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CcQx3J4y.js → Dashboard-9HkH6oxn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BpijtEzQ.js → Dropdown-00e2YWRB.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DfZjeZTe.js → EmailListRenderer-By-ugRDL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BFYSf69C.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Okdi3pJm.js → Federation-BME5sbZY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-mH-HebKW.js → FormItemContext-B4TEZCr-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-xivV69x7.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
- package/src/assets/web-panel/assets/{Git-1rh_YBwg.js → Git-BrS6WVSr.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-eAXkNxdY.js → Governance-BMdCWnKF.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CS6dMVTN.js → Inference-Bcp3vzcA.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Bq_TnkvD.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-KchHtiHt.js → Logs-D-W_6x09.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-TabqHW9B.js → MarkdownRenderer-DElGPxCZ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CsmGcX5X.js → Marketplace-Dzn2guvI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BaPFnnyz.js → McpTools-uPict-KL.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-2m-sUUf7.js → Memory-BCw43kOb.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-D5nVZBRu.js → MobileBridge-DmV8Wv7F.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-C-HaCmSM.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-3GDvamGa.js → Mtc-C6jxJ34Y.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-D2zGuZCH.js → MtcAudit-Za8ExwhQ.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-BBVQ4t6t.js → Multisig-KKDbuEgb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BIOCyOGp.js → NLProgramming-Dj97zbCk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DVwBGpzc.js → Notes-DBXvMraU.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BDnHfIfX.js → NotificationSettings-DOxITLYl.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
- package/src/assets/web-panel/assets/{Organization-D3SRKaa7.js → Organization-D1i73mjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C2B8ahyn.js → Overflow-kW6Q3rvg.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-2iwQzG65.js → P2P-C-v_8p2v.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CoPiWqMN.js → PdhVaultBrowser-tUbbY64W.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-yV5XNXo5.js → Permissions-DkHxEZ58.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-uCDJhMUF.js → PersonalDataHub-DWuMPGrK.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-hN7ucMOA.js → Pipeline-30IYXWWm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CgU0ptN4.js → Privacy-DZNQ4RrH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CfYPiT6j.js → ProjectInit-De2MCElf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-B0A2C4nw.js → ProjectSettings-5uHk9wsC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BE5C0ajn.js → Projects-XjDfrFLb.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CstQPf8W.js → Providers-Bgwj6gcE.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Co3z_ynk.js → QrScannerModal-gKql9umv.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DMTS6pQ6.js → QuickAsk-BWvmwkh7.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-ETw4odUa.js → Recommend-Uok4hZPd.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-DLJzTk9a.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-BTVqQgKq.js → Reputation-ZVwIPMSO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CBKKwwtO.js → Row-CN04HxWR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BHUQ438I.js → RssFeed-BdSrEG4g.js} +3 -3
- package/src/assets/web-panel/assets/{Search-B4NqeUsF.js → Search-5PwVd0IR.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BRjoYG5_.js → Security-DdBo4zjN.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CxQzu3p3.js → Services-D3letU7M.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BKzwwmWO.js → Skeleton--Jb83f1v.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DrsFcgth.js → Skills-Bg-CulHX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D21tmXCH.js → Sla-7QOa7S-X.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CjyjLfp8.js → SpeechSettings-DQwppjRw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Cu45-tnx.js → SyncSettings-dNPQGL-s.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-oMcILYOj.js → Tasks-Ggs-Z48q.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bh-Zcr5R.js → Templates-78TYHgRO.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-68ns63aI.js → Tenant-xHwwv9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BLJkFykE.js → Terminal-BJXSJfm4.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-xcrJLq-O.js → TimelineRenderer-fLbjRhJw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C6PpFJtf.js → Tokens-oMSBSBmB.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BaKA7tdM.js → Trigger-CfnY_wX_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BzhA57Jc.js → Trust-AdyTgYKO.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-4_U3JkIR.js → UkeySign-C5K_81dS.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-162AANxX.js → VideoEditing-vcGVDqki.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B1viuql_.js → Wallet-hhOc3hlY.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-ZVWazhCy.js → WebAuthn-CVloC11s.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-tzdyUk-1.js → WorkflowEditor-CYeE32R5.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DmrHNuwq.js → chat-CNROR5bN.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DAJ_M-PK.js → colors-Cjx-cnwU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-uIzmYwLs.js → compact-item-DOIfelvD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D_ZmuLwb.js → createContext-DfA7UZ8u.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CDt_shTL.js → hasIn-BMSCzcfM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaelyMgF.js → index-5-HXgOX5.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4n1lGTB.js → index-AWg-0M1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-saf3VNeP.js → index-B4iYzpkL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bx4d6c1k.js → index-B5Udeb_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdUMcwRB.js → index-B7gf77CJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-clSX4PEc.js → index-BEMwBBHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBFmcaCQ.js → index-BM1y_9hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByzGvpR9.js → index-BUonmKSE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C16fYKFD.js → index-BbQ2eQ8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzXqvMuP.js → index-BemJ2u_E.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9OE8B3d.js → index-BolaOQxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-OzTxFIJP.js → index-BxOk9vkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqDSF2RW.js → index-By61ojUx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CG0lY4h3.js → index-C2K9SNVX.js} +1 -1
- package/src/assets/web-panel/assets/{index-QsgfSZrV.js → index-C3L5gdiD.js} +1 -1
- package/src/assets/web-panel/assets/{index-GcKOHD_c.js → index-C7xn0Nub.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRa4kJ_S.js → index-CAnvhwg6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeUpKjvi.js → index-CEKMcOyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvak6Oe2.js → index-CNC5onm4.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbD_WW_6.js → index-CVL27kXC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHvlm0In.js → index-CVm1_i3t.js} +1 -1
- package/src/assets/web-panel/assets/{index-rS3gl_68.js → index-CdYBye8B.js} +1 -1
- package/src/assets/web-panel/assets/{index-FyplHuNt.js → index-ClNMrqEY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfSSy9uy.js → index-Cs6l7uRu.js} +1 -1
- package/src/assets/web-panel/assets/{index-rXdwUeE3.js → index-CshQbilz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeGBoLQA.js → index-Cyweh0Hy.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5WcdJH2.js → index-DBpWXFed.js} +3 -3
- package/src/assets/web-panel/assets/{index-CcZ9xJI9.js → index-DHtpHZAQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKyd472S.js → index-DLmn0BoK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAvSmJXW.js → index-DPAWBrMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-ifxmu8SY.js → index-DjEsxO5h.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDF6EQBs.js → index-FBc_CJBd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbxjfstq.js → index-Icdoqmzt.js} +1 -1
- package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
- package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
- package/src/assets/web-panel/assets/{index-BTCXf1t-.js → index-dCd8cSUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvFhhe6r.js → index-m2vG9gG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwfvPe-j.js → index-mTpHoEzr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Z8P4BexE.js → index-ti_lnTWl.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-pmfaD-aK.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CaQ2CpYF.js → motion-s3iVK41k.js} +1 -1
- package/src/assets/web-panel/assets/{move-CkKTqNbI.js → move-B6B-E5Jz.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Dd4xsgRI.js → mtc-parser-BiYgTidA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BFuA5YEs.js → omit-pU1RdUDA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DDfxOAJc.js → pickAttrs-Cdoq_SeM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Be595JCp.js → placementArrow-DIuvAn2K.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-7byljUM3.js → responsiveObserve-8w16YR2D.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CTWfOTaY.js → slide-VZ01cZe3.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CNZOuIxM.js → statusUtils-lh8YMN8Z.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DwOcBV78.js → styleChecker-CmDyreec.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B6MNwAO9.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CzyQjH9r.js → useFs-Thnu-Iaj.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Dxh3KiVA.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DVfZiS5N.js → vnode-Cg6lFY_X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BYme7fbY.js → zoom-DVfsR1YO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/cli-help.js +181 -0
- package/src/command-help-index.json +181 -0
- package/src/command-manifest.json +1107 -175
- package/src/command-surface-policy.js +197 -0
- package/src/commands/agent.js +76 -33
- package/src/commands/artifacts.js +400 -2
- package/src/commands/background-session.js +41 -3
- package/src/commands/checkpoint-restore-recovery.js +1476 -0
- package/src/commands/checkpoint.js +947 -22
- package/src/commands/compact.js +101 -25
- package/src/commands/config.js +506 -20
- package/src/commands/doctor.js +4 -1
- package/src/commands/hub.js +1 -1
- package/src/commands/mcp.js +688 -24
- package/src/commands/pack.js +16 -2
- package/src/commands/plugin.js +20 -12
- package/src/commands/session-mcp-recovery.js +218 -0
- package/src/commands/session.js +470 -110
- package/src/commands/setup.js +36 -10
- package/src/commands/skill.js +181 -110
- package/src/commands/status.js +21 -5
- package/src/commands/update.js +68 -6
- package/src/constants.js +11 -0
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/action-protocol.js +10 -0
- package/src/gateways/ws/session-protocol.js +555 -27
- package/src/gateways/ws/ws-agent-handler.js +541 -14
- package/src/gateways/ws/ws-session-gateway.js +129 -22
- package/src/gateways/ws/ws-session-state.js +431 -0
- package/src/harness/background-task-manager.js +233 -36
- package/src/harness/jsonl-session-store.js +2551 -167
- package/src/harness/mcp-client.js +802 -61
- package/src/harness/prompt-compressor.js +227 -27
- package/src/harness/session-index.js +89 -38
- package/src/harness/session-list-index.js +254 -0
- package/src/harness/structured-handoff.js +410 -0
- package/src/lazy-dispatch.js +747 -84
- package/src/lib/__tests__/logger.test.js +50 -2
- package/src/lib/advisor-runtime.js +1047 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/agent-sandbox.js +120 -26
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/agent-team/team-runner.js +230 -8
- package/src/lib/artifact-store.js +131 -8
- package/src/lib/background-agent-supervisor.js +185 -13
- package/src/lib/background-interaction-journal.js +37 -9
- package/src/lib/background-launch-profile.js +1058 -0
- package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
- package/src/lib/checkpoint-restore-orchestrator.js +987 -0
- package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
- package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
- package/src/lib/checkpoint-restore-recovery.js +884 -0
- package/src/lib/checkpoint-restore-retention.js +126 -0
- package/src/lib/checkpoint-restore-saga.js +5079 -0
- package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
- package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
- package/src/lib/checkpoint-store.js +1688 -97
- package/src/lib/checkpoint-timeline-authority.js +522 -0
- package/src/lib/checkpoint-timeline.js +642 -0
- package/src/lib/config-keys.js +18 -119
- package/src/lib/config-manager.js +446 -27
- package/src/lib/config-redaction.js +57 -0
- package/src/lib/config-schema.js +853 -0
- package/src/lib/cowork-mcp-tools.js +5 -0
- package/src/lib/cowork-task-runner.js +458 -63
- package/src/lib/delivery-coordinator.js +1234 -0
- package/src/lib/delivery-evidence.js +450 -0
- package/src/lib/doctor-checkup.js +199 -14
- package/src/lib/executable-path.js +34 -0
- package/src/lib/fatal-handler.js +16 -0
- package/src/lib/file-checkpoint.js +4909 -186
- package/src/lib/file-lines.js +125 -0
- package/src/lib/headless-config-command.js +16 -5
- package/src/lib/hook-runner.cjs +139 -38
- package/src/lib/hooks-v2-workspace-context.js +10 -0
- package/src/lib/impacted-gate-selector.js +424 -0
- package/src/lib/interaction-adapter.js +62 -0
- package/src/lib/jsonl-session-store.js +1 -35
- package/src/lib/logger.js +66 -14
- package/src/lib/mcp-call-ledger-store.js +1069 -0
- package/src/lib/mcp-call-ledger.js +747 -0
- package/src/lib/mcp-client.js +5 -0
- package/src/lib/mcp-conflict-scheduler.js +468 -0
- package/src/lib/mcp-effect-contract.js +102 -0
- package/src/lib/mcp-headers-helper-trust.js +145 -0
- package/src/lib/mcp-headers-helper.js +616 -0
- package/src/lib/mcp-host-recovery-runtime.js +131 -0
- package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
- package/src/lib/mcp-recovery-adjudication.js +443 -0
- package/src/lib/output-context.js +86 -0
- package/src/lib/packer/file-identity.js +170 -0
- package/src/lib/packer/index.js +3 -0
- package/src/lib/packer/native-update-state.js +277 -0
- package/src/lib/packer/pack-update-applier.js +1983 -136
- package/src/lib/packer/pack-update-checker.js +34 -0
- package/src/lib/packer/pack-update-downloader.js +875 -86
- package/src/lib/packer/pack-update-signature.js +86 -0
- package/src/lib/packer/pkg-config-generator.js +6 -0
- package/src/lib/paths.js +65 -10
- package/src/lib/plan-mode.js +831 -14
- package/src/lib/plan-persistence.js +540 -0
- package/src/lib/plugin-runtime/hooks.js +227 -69
- package/src/lib/plugin-runtime/plugin-options.js +189 -44
- package/src/lib/pr-automation-policy.js +146 -8
- package/src/lib/process-execution-broker/credential-agent.js +7 -0
- package/src/lib/process-execution-broker/index.js +53 -8
- package/src/lib/process-execution-broker/workspace-transaction.js +976 -73
- package/src/lib/project-instructions.js +165 -29
- package/src/lib/project-mcp-trust.js +67 -0
- package/src/lib/public-ide-capability-manifest.js +272 -0
- package/src/lib/public-ide-capability-validation.js +351 -0
- package/src/lib/secret-store.js +267 -43
- package/src/lib/secure-fs.js +1089 -0
- package/src/lib/security-audit.js +58 -0
- package/src/lib/service-manager.js +34 -13
- package/src/lib/session-budget-runtime.js +1452 -0
- package/src/lib/session-host-snapshot.js +388 -0
- package/src/lib/session-message-provenance.js +429 -0
- package/src/lib/session-projection.js +804 -0
- package/src/lib/session-resource-budget.js +1495 -0
- package/src/lib/settings-hooks.cjs +138 -22
- package/src/lib/settings-loader.cjs +25 -2
- package/src/lib/side-effect-ledger-store.js +45 -23
- package/src/lib/skill-execution-identity.js +217 -0
- package/src/lib/skill-loader.js +299 -18
- package/src/lib/sub-agent-context.js +236 -28
- package/src/lib/sub-agent-registry.js +5 -0
- package/src/lib/subagent-contract.js +94 -0
- package/src/lib/todo-manager.js +185 -18
- package/src/lib/todo-persistence.js +417 -0
- package/src/lib/turn-binding-store.js +41 -23
- package/src/lib/with-file-lock.js +25 -1
- package/src/lib/ws-chat-handler.js +21 -0
- package/src/program-base.js +18 -0
- package/src/repl/advisor-command.js +103 -0
- package/src/repl/agent-repl.js +2156 -145
- package/src/repl/btw-command.js +93 -10
- package/src/repl/clipboard-image.js +119 -0
- package/src/repl/config-summary.js +64 -108
- package/src/repl/prompt-editor.js +233 -0
- package/src/repl/prompt-interactions.js +344 -0
- package/src/repl/prompt-stash.js +354 -0
- package/src/repl/prompt-suggestions.js +312 -0
- package/src/repl/repl-keybindings.js +227 -0
- package/src/repl/session-recap.js +217 -0
- package/src/repl/slash-command-registry.js +22 -0
- package/src/repl/terminal-layout.js +88 -0
- package/src/runtime/agent-core.js +1264 -325
- package/src/runtime/coding-agent-contract-shared.cjs +13 -1
- package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
- package/src/runtime/diagnostics.js +32 -13
- package/src/runtime/headless-runner.js +298 -118
- package/src/runtime/headless-stream.js +353 -95
- package/src/runtime/mcp-config.js +148 -14
- package/src/runtime/status-diagnostics-lite.js +137 -0
- package/src/assets/web-panel/assets/MobileProjects-Bo8MLZvT.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-C7Ek20ag.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +0 -5
- package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +0 -1
- package/src/assets/web-panel/assets/index-D-Swwqld.js +0 -1
- package/src/assets/web-panel/assets/index-FMZAMKy8.js +0 -1
|
@@ -18,6 +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 { isProxy } from "node:util/types";
|
|
21
22
|
import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
|
|
22
23
|
import sharedShellPolicy from "./coding-agent-shell-policy.cjs";
|
|
23
24
|
import sharedPermissionRules from "../lib/permission-rules.cjs";
|
|
@@ -59,7 +60,6 @@ import {
|
|
|
59
60
|
} from "../lib/idempotency.js";
|
|
60
61
|
import { buildSearchCommand } from "../lib/search-command.js";
|
|
61
62
|
import { discoverCommands } from "../lib/slash-commands.js";
|
|
62
|
-
import { createSkillProcessBroker } from "../lib/skill-process-broker.js";
|
|
63
63
|
import {
|
|
64
64
|
isRetryableStreamError,
|
|
65
65
|
STREAM_RETRY_BASE_MS,
|
|
@@ -70,10 +70,6 @@ import {
|
|
|
70
70
|
replaceByHash,
|
|
71
71
|
snippetAround,
|
|
72
72
|
} from "../lib/hashline.js";
|
|
73
|
-
import {
|
|
74
|
-
mountSkillMcpServers,
|
|
75
|
-
unmountSkillMcpServers,
|
|
76
|
-
} from "../lib/skill-mcp.js";
|
|
77
73
|
import {
|
|
78
74
|
hasImageContent,
|
|
79
75
|
toOllamaMessages,
|
|
@@ -99,6 +95,24 @@ import {
|
|
|
99
95
|
beginManagedToolCheckpoint,
|
|
100
96
|
settleManagedToolCheckpoint,
|
|
101
97
|
} from "../lib/managed-tool-checkpoint.js";
|
|
98
|
+
import {
|
|
99
|
+
createMcpCallLedger,
|
|
100
|
+
McpEffect,
|
|
101
|
+
snapshotMcpJsonRpcInput,
|
|
102
|
+
} from "../lib/mcp-call-ledger.js";
|
|
103
|
+
import {
|
|
104
|
+
MCP_OUTCOME_UNKNOWN_CODE,
|
|
105
|
+
markMcpLedgerOutcomeUnknown,
|
|
106
|
+
} from "../lib/mcp-ledger-recovery-admission.js";
|
|
107
|
+
import {
|
|
108
|
+
createHostOwnedMcpEffectContract,
|
|
109
|
+
createMcpConflictScheduler,
|
|
110
|
+
} from "../lib/mcp-conflict-scheduler.js";
|
|
111
|
+
import {
|
|
112
|
+
buildExtractiveHandoff,
|
|
113
|
+
formatStructuredHandoff,
|
|
114
|
+
} from "../harness/structured-handoff.js";
|
|
115
|
+
import { projectCanonicalResumeMessages } from "../lib/session-message-provenance.js";
|
|
102
116
|
|
|
103
117
|
export { formatProviderHttpError };
|
|
104
118
|
|
|
@@ -114,13 +128,31 @@ export function getActiveMcpServers() {
|
|
|
114
128
|
return new Set(_activeMcpServers);
|
|
115
129
|
}
|
|
116
130
|
|
|
131
|
+
// Direct executeTool callers do not pass agentLoop's run-scoped scheduler.
|
|
132
|
+
// Share a bounded scheduler per MCP client so two concurrent calls cannot each
|
|
133
|
+
// create a private lock and accidentally bypass unknown/write serialization.
|
|
134
|
+
const _directMcpSchedulers = new WeakMap();
|
|
135
|
+
function directMcpConflictScheduler(mcpClient) {
|
|
136
|
+
if (
|
|
137
|
+
(typeof mcpClient !== "object" || mcpClient === null) &&
|
|
138
|
+
typeof mcpClient !== "function"
|
|
139
|
+
) {
|
|
140
|
+
return createMcpConflictScheduler({ maxActive: 1 });
|
|
141
|
+
}
|
|
142
|
+
let scheduler = _directMcpSchedulers.get(mcpClient);
|
|
143
|
+
if (!scheduler) {
|
|
144
|
+
scheduler = createMcpConflictScheduler();
|
|
145
|
+
_directMcpSchedulers.set(mcpClient, scheduler);
|
|
146
|
+
}
|
|
147
|
+
return scheduler;
|
|
148
|
+
}
|
|
149
|
+
|
|
117
150
|
const { isDangerousGitCommand, isReadOnlyGitCommand, normalizeGitCommand } =
|
|
118
151
|
sharedCodingAgentPolicy;
|
|
119
152
|
const { evaluateShellCommandPolicy } = sharedShellPolicy;
|
|
120
153
|
const { evaluatePermissionRules } = sharedPermissionRules;
|
|
121
154
|
const { collectHooks, umbrellaFor } = sharedSettingsHooks;
|
|
122
|
-
const { runHooks: runCommandHooks
|
|
123
|
-
sharedHookRunner;
|
|
155
|
+
const { runHooks: runCommandHooks } = sharedHookRunner;
|
|
124
156
|
const {
|
|
125
157
|
runObserveHooks,
|
|
126
158
|
aggregateContext,
|
|
@@ -635,19 +667,6 @@ function _resolveShellTimeout(raw) {
|
|
|
635
667
|
return Math.min(Math.floor(n), MAX_SHELL_TIMEOUT_MS);
|
|
636
668
|
}
|
|
637
669
|
|
|
638
|
-
/**
|
|
639
|
-
* Opt-in: run ALL matching decision hooks and take the STRICTEST outcome
|
|
640
|
-
* (block > ask > allow > continue) instead of the default in-order first-wins
|
|
641
|
-
* short-circuit — so an earlier hook's `ask` can no longer mask a later hook's
|
|
642
|
-
* `block`. Default off = byte-identical short-circuit behavior. Flipping the
|
|
643
|
-
* default is a product decision (all matching hooks then run every time, which
|
|
644
|
-
* changes hook-execution volume + side effects).
|
|
645
|
-
*/
|
|
646
|
-
function _hookStrictMergeEnabled() {
|
|
647
|
-
const v = process.env.CC_HOOK_STRICT_MERGE;
|
|
648
|
-
return v === "1" || v === "true";
|
|
649
|
-
}
|
|
650
|
-
|
|
651
670
|
/**
|
|
652
671
|
* Run settings.json `PreToolUse` hooks (decision-capable). DB hooks are handled
|
|
653
672
|
* separately + stay observe-only. A `block` decision stops the tool; an `ask`
|
|
@@ -677,18 +696,18 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
|
|
|
677
696
|
parentId: context.hookParentId || null,
|
|
678
697
|
},
|
|
679
698
|
);
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
const outcome =
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
699
|
+
// Run every matching authority-bearing hook in configured order, then merge
|
|
700
|
+
// the outcomes with block > ask > allow > continue. Sequential execution
|
|
701
|
+
// preserves the documented hook side-effect order while preventing an early
|
|
702
|
+
// ask/allow from masking a later block. Environment variables cannot weaken
|
|
703
|
+
// this permission boundary.
|
|
704
|
+
const outcome = runCommandHooks(matched, payload, {
|
|
705
|
+
cwd,
|
|
706
|
+
event: "PreToolUse",
|
|
707
|
+
mergeStrict: true,
|
|
708
|
+
failClosed: true,
|
|
709
|
+
broker,
|
|
710
|
+
});
|
|
692
711
|
if (outcome.decision === "block") {
|
|
693
712
|
return { blocked: true, reason: outcome.reason, hook: outcome.hook };
|
|
694
713
|
}
|
|
@@ -742,6 +761,10 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
|
|
|
742
761
|
* @returns {{decision:("allow"|"deny"|null), reason?:string, hook?:string}}
|
|
743
762
|
*/
|
|
744
763
|
function runSettingsPermissionRequestHooks(name, args, context, cwd, reason) {
|
|
764
|
+
// Command hooks are arbitrary processes. Until a host-enforced read-only
|
|
765
|
+
// hook sandbox exists, no command hook may execute inside the planning-only
|
|
766
|
+
// capability fence (even when the underlying tool is read-only).
|
|
767
|
+
if (context.planReadOnlyFenceActive === true) return { decision: null };
|
|
745
768
|
const matched = collectHooks(
|
|
746
769
|
context.settingsHooks,
|
|
747
770
|
"PermissionRequest",
|
|
@@ -768,7 +791,8 @@ function runSettingsPermissionRequestHooks(name, args, context, cwd, reason) {
|
|
|
768
791
|
const outcome = runCommandHooks(matched, payload, {
|
|
769
792
|
cwd,
|
|
770
793
|
event: "PermissionRequest",
|
|
771
|
-
mergeStrict:
|
|
794
|
+
mergeStrict: true,
|
|
795
|
+
failClosed: true,
|
|
772
796
|
broker,
|
|
773
797
|
});
|
|
774
798
|
// Precedence: deny > ask > allow > defer. The runner normalizes deny/block →
|
|
@@ -895,17 +919,21 @@ export function getAgentToolDefinitions({
|
|
|
895
919
|
names = null,
|
|
896
920
|
disabledTools = [],
|
|
897
921
|
extraTools = [],
|
|
922
|
+
exactToolNames = false,
|
|
898
923
|
} = {}) {
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
);
|
|
924
|
+
// `names` historically selects a built-in tool preset while independently
|
|
925
|
+
// contributed host/MCP tools remain visible. Preserve that top-level behavior
|
|
926
|
+
// for a non-empty preset, but let authority-bearing child contexts opt into an
|
|
927
|
+
// exact all-tool ceiling. An explicit empty array is always deny-all.
|
|
928
|
+
const allowedNames = Array.isArray(names) ? new Set(names) : null;
|
|
904
929
|
const extraToolNames = new Set(
|
|
905
930
|
(Array.isArray(extraTools) ? extraTools : [])
|
|
906
931
|
.map((tool) => tool?.function?.name)
|
|
907
932
|
.filter(Boolean),
|
|
908
933
|
);
|
|
934
|
+
const disabledNames = new Set(
|
|
935
|
+
Array.isArray(disabledTools) ? disabledTools : [],
|
|
936
|
+
);
|
|
909
937
|
const allTools = mergeToolDefinitions(
|
|
910
938
|
AGENT_TOOLS,
|
|
911
939
|
Array.isArray(extraTools) ? extraTools : [],
|
|
@@ -914,7 +942,11 @@ export function getAgentToolDefinitions({
|
|
|
914
942
|
return allTools.filter((tool) => {
|
|
915
943
|
const name = tool?.function?.name;
|
|
916
944
|
if (!name) return false;
|
|
917
|
-
|
|
945
|
+
const unlistedExtraAllowed =
|
|
946
|
+
allowedNames?.size > 0 &&
|
|
947
|
+
exactToolNames !== true &&
|
|
948
|
+
extraToolNames.has(name);
|
|
949
|
+
if (allowedNames && !allowedNames.has(name) && !unlistedExtraAllowed) {
|
|
918
950
|
return false;
|
|
919
951
|
}
|
|
920
952
|
if (disabledNames.has(name)) return false;
|
|
@@ -1598,7 +1630,8 @@ async function tryIdeDiffApprovalForEdit(
|
|
|
1598
1630
|
) {
|
|
1599
1631
|
if (!IDE_DIFF_EDIT_TOOLS.has(name)) return null;
|
|
1600
1632
|
if (typeof context.permissionConfirm !== "function") return null; // interactive only
|
|
1601
|
-
|
|
1633
|
+
const hostMcpClient = context.mcpHostClient || context.mcpClient;
|
|
1634
|
+
if (!hostMcpClient || !context.externalToolExecutors) return null;
|
|
1602
1635
|
try {
|
|
1603
1636
|
const {
|
|
1604
1637
|
ideDiffApprovalEnabled,
|
|
@@ -1608,7 +1641,7 @@ async function tryIdeDiffApprovalForEdit(
|
|
|
1608
1641
|
summarizeUserAmendments,
|
|
1609
1642
|
} = await import("../lib/ide-context.js");
|
|
1610
1643
|
const mcpLike = {
|
|
1611
|
-
mcpClient:
|
|
1644
|
+
mcpClient: hostMcpClient,
|
|
1612
1645
|
externalToolExecutors: context.externalToolExecutors,
|
|
1613
1646
|
};
|
|
1614
1647
|
if (!ideDiffApprovalEnabled() || !hasIdeOpenDiff(mcpLike)) return null;
|
|
@@ -1722,6 +1755,19 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1722
1755
|
const skillLoader = context.skillLoader || _defaultSkillLoader;
|
|
1723
1756
|
const cwd = context.cwd || process.cwd();
|
|
1724
1757
|
const planManager = context.planManager || getPlanModeManager();
|
|
1758
|
+
// The provider receives a filtered tool schema, but an untrusted/buggy model
|
|
1759
|
+
// can still emit an arbitrary tool_call. Enforce the exact same immutable
|
|
1760
|
+
// capability set again at execution time; null/absent preserves direct-call
|
|
1761
|
+
// compatibility, while [] is an explicit deny-all ceiling.
|
|
1762
|
+
if (
|
|
1763
|
+
Array.isArray(context.effectiveAllowedToolNames) &&
|
|
1764
|
+
!context.effectiveAllowedToolNames.includes(name)
|
|
1765
|
+
) {
|
|
1766
|
+
return {
|
|
1767
|
+
error: `[Tool Capability] Tool "${name}" is outside this run's effective tool set.`,
|
|
1768
|
+
policy: { decision: "blocked", via: "effective-tool-set" },
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1725
1771
|
const localToolDescriptor =
|
|
1726
1772
|
context.externalToolDescriptors &&
|
|
1727
1773
|
typeof context.externalToolDescriptors === "object"
|
|
@@ -1789,11 +1835,60 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1789
1835
|
const workspacePathDenial = guardAgentFileToolPaths(name, args, context, cwd);
|
|
1790
1836
|
if (workspacePathDenial) return workspacePathDenial;
|
|
1791
1837
|
|
|
1838
|
+
// A malformed settings source may have hidden a deny/ask hook. Runtime
|
|
1839
|
+
// loaders attach these parse failures as host-owned metadata; executing with
|
|
1840
|
+
// a partial authority set would be strictly wider than the configured path.
|
|
1841
|
+
const hookAuthorityErrors = context.settingsHooks?._authorityErrors;
|
|
1842
|
+
if (Array.isArray(hookAuthorityErrors) && hookAuthorityErrors.length > 0) {
|
|
1843
|
+
return {
|
|
1844
|
+
error:
|
|
1845
|
+
"[Hook Authority] Tool execution blocked because one or more hook policy sources could not be parsed.",
|
|
1846
|
+
policy: { decision: "blocked", via: "hook-authority-load" },
|
|
1847
|
+
incidents: hookAuthorityErrors.map((entry) => ({
|
|
1848
|
+
sourceFile: entry?.sourceFile || entry?.file || null,
|
|
1849
|
+
code: entry?.code || "CC_HOOK_AUTHORITY_INVALID",
|
|
1850
|
+
})),
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1792
1854
|
const toolContext = createToolContext({
|
|
1793
1855
|
toolName: runtimeDescriptor?.name || name,
|
|
1794
1856
|
cwd,
|
|
1795
1857
|
metadata: { descriptor: runtimeDescriptor },
|
|
1796
1858
|
});
|
|
1859
|
+
const observeHookIncidents = [];
|
|
1860
|
+
const recordObserveHookIncident = (event, source, error) => {
|
|
1861
|
+
const incident = Object.freeze({
|
|
1862
|
+
code: "CC_HOOK_OBSERVER_DEGRADED",
|
|
1863
|
+
event,
|
|
1864
|
+
source,
|
|
1865
|
+
degraded: true,
|
|
1866
|
+
reason: String(error?.message || error || "hook observer failed")
|
|
1867
|
+
.replace(/\s+/g, " ")
|
|
1868
|
+
.slice(0, 240),
|
|
1869
|
+
});
|
|
1870
|
+
observeHookIncidents.push(incident);
|
|
1871
|
+
try {
|
|
1872
|
+
emitHooksV2Event("HookFailure", {
|
|
1873
|
+
schema_version: 1,
|
|
1874
|
+
session_id: context.sessionId || null,
|
|
1875
|
+
turn_id: context.turnId || null,
|
|
1876
|
+
tool_use_id: context.toolCallId || null,
|
|
1877
|
+
tool_name: name,
|
|
1878
|
+
hook_event_name: event,
|
|
1879
|
+
hook_source: source,
|
|
1880
|
+
incident_code: incident.code,
|
|
1881
|
+
});
|
|
1882
|
+
} catch {
|
|
1883
|
+
// Incident delivery must never turn an observe-only failure into a new
|
|
1884
|
+
// authority decision or hide the original tool outcome.
|
|
1885
|
+
}
|
|
1886
|
+
try {
|
|
1887
|
+
context.onHookIncident?.(incident);
|
|
1888
|
+
} catch {
|
|
1889
|
+
// Optional observer only.
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1797
1892
|
|
|
1798
1893
|
// Persona toolsDisabled guard
|
|
1799
1894
|
const persona = _loadProjectPersona(cwd);
|
|
@@ -1815,10 +1910,10 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1815
1910
|
// 3. settings `ask` → confirm (headless w/o confirmer falls closed).
|
|
1816
1911
|
// Reached only after BOTH denies clear, so a denied
|
|
1817
1912
|
// tool never wastes a confirmation round-trip.
|
|
1818
|
-
// 4. settings `allow` → pre-authorize
|
|
1819
|
-
// plan-mode
|
|
1820
|
-
//
|
|
1821
|
-
//
|
|
1913
|
+
// 4. settings `allow` → pre-authorize interactive gates OUTSIDE the
|
|
1914
|
+
// plan-mode hard ceiling. It can never add a tool to
|
|
1915
|
+
// the planning/approved capability set. The hard
|
|
1916
|
+
// shell-policy denylist also still applies.
|
|
1822
1917
|
// No matching rule + no host policy → every existing layer runs unchanged
|
|
1823
1918
|
// (default behaviour is byte-for-byte).
|
|
1824
1919
|
const settingsVerdict = context.permissionRules
|
|
@@ -1849,18 +1944,10 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1849
1944
|
toolPolicies && typeof toolPolicies === "object"
|
|
1850
1945
|
? toolPolicies[name]
|
|
1851
1946
|
: null;
|
|
1852
|
-
const isExternalHostTool =
|
|
1853
|
-
hostToolPolicy && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
1854
|
-
const isExternalLocalTool =
|
|
1855
|
-
localToolDescriptor && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
1856
1947
|
const hostPolicyAllowsReadOnlyGit =
|
|
1857
1948
|
name === "git" &&
|
|
1858
1949
|
hostToolPolicy?.planModeBehavior === "readonly-conditional" &&
|
|
1859
1950
|
isReadOnlyGitCommand(args.command);
|
|
1860
|
-
const localReadOnlyAllowedInPlanMode =
|
|
1861
|
-
isExternalLocalTool &&
|
|
1862
|
-
planManager.isActive() &&
|
|
1863
|
-
localToolDescriptor?.isReadOnly === true;
|
|
1864
1951
|
|
|
1865
1952
|
// 2. host deny (a settings `allow` does not relax this)
|
|
1866
1953
|
if (
|
|
@@ -1879,6 +1966,51 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1879
1966
|
};
|
|
1880
1967
|
}
|
|
1881
1968
|
|
|
1969
|
+
// Plan mode is a capability ceiling, not another prompt/approval source.
|
|
1970
|
+
// Evaluate it before settings `ask`, sensitive-file confirmation, and every
|
|
1971
|
+
// other interactive gate so no `allow` or confirmed `ask` can widen the
|
|
1972
|
+
// planning tool set. Read-only git remains a built-in conditional capability.
|
|
1973
|
+
// External tools remain blocked until a host-owned, per-tool effect authority
|
|
1974
|
+
// exists; server annotations/descriptors cannot self-authorize Plan access.
|
|
1975
|
+
const executionLockActive = planManager.executionLock != null;
|
|
1976
|
+
const planReadOnlyFenceActive =
|
|
1977
|
+
planManager.isActive() && !executionLockActive;
|
|
1978
|
+
const externalToolBlockedDuringPlanning =
|
|
1979
|
+
planReadOnlyFenceActive && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
1980
|
+
context = { ...context, planReadOnlyFenceActive };
|
|
1981
|
+
if (
|
|
1982
|
+
planManager.isActive() &&
|
|
1983
|
+
!(name === "git" && isReadOnlyGitCommand(args.command)) &&
|
|
1984
|
+
(externalToolBlockedDuringPlanning || !planManager.isToolAllowed(name))
|
|
1985
|
+
) {
|
|
1986
|
+
if (!executionLockActive) {
|
|
1987
|
+
planManager.addPlanItem({
|
|
1988
|
+
title: `${name}: ${formatToolArgs(name, args)}`,
|
|
1989
|
+
tool: name,
|
|
1990
|
+
params: args,
|
|
1991
|
+
estimatedImpact:
|
|
1992
|
+
name === "run_shell" ||
|
|
1993
|
+
name === "run_code" ||
|
|
1994
|
+
name === "git" ||
|
|
1995
|
+
localToolDescriptor?.riskLevel === "high"
|
|
1996
|
+
? "high"
|
|
1997
|
+
: GUARDED_FILE_MUTATION_TOOLS.has(name) ||
|
|
1998
|
+
localToolDescriptor?.riskLevel === "medium"
|
|
1999
|
+
? "medium"
|
|
2000
|
+
: "low",
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
return {
|
|
2004
|
+
error: executionLockActive
|
|
2005
|
+
? `[Plan Execution Lock] Tool "${name}" was not in the approved tool set. Request a plan revision before using it.`
|
|
2006
|
+
: `[Plan Mode] Tool "${name}" is blocked during planning. It has been added to the plan. Use /plan approve to execute.`,
|
|
2007
|
+
policy: {
|
|
2008
|
+
decision: "blocked",
|
|
2009
|
+
via: executionLockActive ? "plan-execution-lock" : "plan-mode",
|
|
2010
|
+
},
|
|
2011
|
+
};
|
|
2012
|
+
}
|
|
2013
|
+
|
|
1882
2014
|
// 3 + 4. settings ask / allow (only reached when neither layer denied)
|
|
1883
2015
|
let ruleAllowed = false;
|
|
1884
2016
|
if (settingsVerdict.decision === "ask") {
|
|
@@ -2062,43 +2194,17 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2062
2194
|
}
|
|
2063
2195
|
}
|
|
2064
2196
|
|
|
2065
|
-
//
|
|
2066
|
-
//
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
hostToolPolicy?.allowed === true
|
|
2077
|
-
) &&
|
|
2078
|
-
!(!executionLockActive && localReadOnlyAllowedInPlanMode)
|
|
2079
|
-
) {
|
|
2080
|
-
if (!executionLockActive) {
|
|
2081
|
-
planManager.addPlanItem({
|
|
2082
|
-
title: `${name}: ${formatToolArgs(name, args)}`,
|
|
2083
|
-
tool: name,
|
|
2084
|
-
params: args,
|
|
2085
|
-
estimatedImpact:
|
|
2086
|
-
name === "run_shell" ||
|
|
2087
|
-
name === "run_code" ||
|
|
2088
|
-
name === "git" ||
|
|
2089
|
-
localToolDescriptor?.riskLevel === "high"
|
|
2090
|
-
? "high"
|
|
2091
|
-
: GUARDED_FILE_MUTATION_TOOLS.has(name) ||
|
|
2092
|
-
localToolDescriptor?.riskLevel === "medium"
|
|
2093
|
-
? "medium"
|
|
2094
|
-
: "low",
|
|
2095
|
-
});
|
|
2096
|
-
}
|
|
2097
|
-
return {
|
|
2098
|
-
error: executionLockActive
|
|
2099
|
-
? `[Plan Execution Lock] Tool "${name}" was not in the approved tool set. Request a plan revision before using it.`
|
|
2100
|
-
: `[Plan Mode] Tool "${name}" is blocked during planning. It has been added to the plan. Use /plan approve to execute.`,
|
|
2101
|
-
};
|
|
2197
|
+
// Subtree rules are an authority input, so the first file mutation in a
|
|
2198
|
+
// subtree must observe them before any PreToolUse hook or filesystem effect.
|
|
2199
|
+
// Discovery+read commits the complete source/target batch atomically; this
|
|
2200
|
+
// call is intentionally deferred once and succeeds only on an explicit retry.
|
|
2201
|
+
if (GUARDED_FILE_MUTATION_TOOLS.has(name)) {
|
|
2202
|
+
const instructionPreflight = await _preflightMutationSubtreeInstructions(
|
|
2203
|
+
fileMutationPaths(name, args),
|
|
2204
|
+
cwd,
|
|
2205
|
+
context.subtreeInstructionScope || context.sessionId || "__legacy__",
|
|
2206
|
+
);
|
|
2207
|
+
if (instructionPreflight) return instructionPreflight;
|
|
2102
2208
|
}
|
|
2103
2209
|
|
|
2104
2210
|
// PreToolUse hooks. DB hooks (cc hook add) stay observe-only — a failure
|
|
@@ -2106,77 +2212,79 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2106
2212
|
// capable: a `block` (exit 2 / {decision:block}) stops the tool here, an
|
|
2107
2213
|
// `ask` routes to the confirmer. Runs after permission resolution so a
|
|
2108
2214
|
// settings deny / host deny short-circuits before any hook process spawns.
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
session_id: context.sessionId || null,
|
|
2114
|
-
turn_id: context.turnId || null,
|
|
2115
|
-
tool_use_id: context.toolCallId || null,
|
|
2116
|
-
tool_name: name,
|
|
2117
|
-
input_keys:
|
|
2118
|
-
args && typeof args === "object" ? Object.keys(args).sort() : [],
|
|
2119
|
-
cwd,
|
|
2120
|
-
},
|
|
2121
|
-
{ failClosed: true },
|
|
2122
|
-
);
|
|
2123
|
-
if (hooksV2Pre.blocked || hooksV2Pre.decision === "block") {
|
|
2124
|
-
return {
|
|
2125
|
-
error: `[Hook v2] PreToolUse blocked "${name}".`,
|
|
2126
|
-
policy: { decision: "block", via: "hooks-v2" },
|
|
2127
|
-
};
|
|
2128
|
-
}
|
|
2129
|
-
if (hooksV2Pre.requiresApproval || hooksV2Pre.decision === "ask") {
|
|
2130
|
-
const confirm = context.permissionConfirm || context.shellConfirm || null;
|
|
2131
|
-
const approved =
|
|
2132
|
-
typeof confirm === "function"
|
|
2133
|
-
? await confirm({
|
|
2134
|
-
tool: name,
|
|
2135
|
-
args,
|
|
2136
|
-
reason: "Hooks v2 PreToolUse requested confirmation",
|
|
2137
|
-
source: "hooks-v2",
|
|
2138
|
-
})
|
|
2139
|
-
: false;
|
|
2140
|
-
if (!approved) {
|
|
2141
|
-
emitHooksV2Event("PermissionDenied", {
|
|
2215
|
+
if (!planReadOnlyFenceActive) {
|
|
2216
|
+
const hooksV2Pre = await executeHooksV2Event(
|
|
2217
|
+
"PreToolUse",
|
|
2218
|
+
{
|
|
2142
2219
|
schema_version: 1,
|
|
2143
2220
|
session_id: context.sessionId || null,
|
|
2144
2221
|
turn_id: context.turnId || null,
|
|
2145
2222
|
tool_use_id: context.toolCallId || null,
|
|
2146
2223
|
tool_name: name,
|
|
2147
|
-
|
|
2148
|
-
|
|
2224
|
+
input_keys:
|
|
2225
|
+
args && typeof args === "object" ? Object.keys(args).sort() : [],
|
|
2226
|
+
cwd,
|
|
2227
|
+
},
|
|
2228
|
+
{ failClosed: true },
|
|
2229
|
+
);
|
|
2230
|
+
if (hooksV2Pre.blocked || hooksV2Pre.decision === "block") {
|
|
2149
2231
|
return {
|
|
2150
|
-
error: `[Hook v2] PreToolUse
|
|
2151
|
-
policy: { decision: "
|
|
2232
|
+
error: `[Hook v2] PreToolUse blocked "${name}".`,
|
|
2233
|
+
policy: { decision: "block", via: "hooks-v2" },
|
|
2152
2234
|
};
|
|
2153
2235
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2236
|
+
if (hooksV2Pre.requiresApproval || hooksV2Pre.decision === "ask") {
|
|
2237
|
+
const confirm = context.permissionConfirm || context.shellConfirm || null;
|
|
2238
|
+
const approved =
|
|
2239
|
+
typeof confirm === "function"
|
|
2240
|
+
? await confirm({
|
|
2241
|
+
tool: name,
|
|
2242
|
+
args,
|
|
2243
|
+
reason: "Hooks v2 PreToolUse requested confirmation",
|
|
2244
|
+
source: "hooks-v2",
|
|
2245
|
+
})
|
|
2246
|
+
: false;
|
|
2247
|
+
if (!approved) {
|
|
2248
|
+
emitHooksV2Event("PermissionDenied", {
|
|
2249
|
+
schema_version: 1,
|
|
2250
|
+
session_id: context.sessionId || null,
|
|
2251
|
+
turn_id: context.turnId || null,
|
|
2252
|
+
tool_use_id: context.toolCallId || null,
|
|
2253
|
+
tool_name: name,
|
|
2254
|
+
source: "hooks-v2",
|
|
2255
|
+
});
|
|
2256
|
+
return {
|
|
2257
|
+
error: `[Hook v2] PreToolUse confirmation denied for "${name}".`,
|
|
2258
|
+
policy: { decision: "deny", via: "hooks-v2" },
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2166
2261
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2262
|
+
if (hookDb) {
|
|
2263
|
+
try {
|
|
2264
|
+
await executeHooks(hookDb, HookEvents.PreToolUse, {
|
|
2265
|
+
tool: name,
|
|
2266
|
+
args,
|
|
2267
|
+
timestamp: new Date().toISOString(),
|
|
2268
|
+
descriptor: runtimeDescriptor,
|
|
2269
|
+
context: toolContext,
|
|
2270
|
+
});
|
|
2271
|
+
} catch (error) {
|
|
2272
|
+
recordObserveHookIncident("PreToolUse", "database", error);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
if (context.settingsHooks) {
|
|
2276
|
+
const pre = await runSettingsPreToolUseHooks(name, args, context, cwd);
|
|
2277
|
+
// A hook `ask` resolved by the IDE diff review: accepted → the IDE
|
|
2278
|
+
// already wrote the file, return the synthetic result and skip the tool;
|
|
2279
|
+
// rejected → the ide-diff deny shape (via:"ide-diff", not via:"hook").
|
|
2280
|
+
if (pre.ideApplied) return pre.ideApplied;
|
|
2281
|
+
if (pre.blocked) {
|
|
2282
|
+
if (pre.ideResult) return pre.ideResult;
|
|
2283
|
+
return {
|
|
2284
|
+
error: `[Hook] PreToolUse blocked "${name}"${pre.reason ? ": " + pre.reason : ""}`,
|
|
2285
|
+
policy: { decision: "block", via: "hook", hook: pre.hook || null },
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2180
2288
|
}
|
|
2181
2289
|
}
|
|
2182
2290
|
|
|
@@ -2201,10 +2309,18 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2201
2309
|
// 用量归因: shared per-run sink for child-loop (sub-agent / isolated
|
|
2202
2310
|
// skill) token usage, drained by agentLoop as attributed events.
|
|
2203
2311
|
subAgentUsageSink: context.subAgentUsageSink || null,
|
|
2312
|
+
planManager,
|
|
2313
|
+
permissionRules: context.permissionRules || null,
|
|
2314
|
+
effectiveAllowedToolNames: context.effectiveAllowedToolNames ?? null,
|
|
2204
2315
|
hostManagedToolPolicy: context.hostManagedToolPolicy || null,
|
|
2205
2316
|
externalToolDescriptors: context.externalToolDescriptors || null,
|
|
2206
2317
|
externalToolExecutors: context.externalToolExecutors || null,
|
|
2207
2318
|
mcpClient: context.mcpClient || null,
|
|
2319
|
+
mcpHostClient: context.mcpHostClient || context.mcpClient || null,
|
|
2320
|
+
mcpCallLedger: context.mcpCallLedger || null,
|
|
2321
|
+
mcpConflictScheduler: context.mcpConflictScheduler || null,
|
|
2322
|
+
subtreeInstructionScope:
|
|
2323
|
+
context.subtreeInstructionScope || context.sessionId || "__legacy__",
|
|
2208
2324
|
shellPolicyOverrides: context.shellPolicyOverrides || null,
|
|
2209
2325
|
approvalGate: context.approvalGate || null,
|
|
2210
2326
|
shellConfirm: context.shellConfirm || null,
|
|
@@ -2214,6 +2330,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2214
2330
|
settingsVerdict,
|
|
2215
2331
|
subAgentDepth: context.subAgentDepth || 0,
|
|
2216
2332
|
subAgentBudget: context.subAgentBudget || null,
|
|
2333
|
+
sessionBudget: context.sessionBudget || null,
|
|
2217
2334
|
// Effective contract of THIS loop (parent ceiling for a nested spawn) +
|
|
2218
2335
|
// the MCP tool definitions this loop exposes (inheritable by a spawn).
|
|
2219
2336
|
subAgentContract: context.subAgentContract || null,
|
|
@@ -2230,15 +2347,15 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2230
2347
|
managedCheckpoint: context.managedCheckpoint === true,
|
|
2231
2348
|
});
|
|
2232
2349
|
} catch (err) {
|
|
2233
|
-
if (hookDb) {
|
|
2350
|
+
if (hookDb && !planReadOnlyFenceActive) {
|
|
2234
2351
|
try {
|
|
2235
2352
|
await executeHooks(hookDb, HookEvents.ToolError, {
|
|
2236
2353
|
tool: name,
|
|
2237
2354
|
args,
|
|
2238
2355
|
error: err.message,
|
|
2239
2356
|
});
|
|
2240
|
-
} catch (
|
|
2241
|
-
|
|
2357
|
+
} catch (error) {
|
|
2358
|
+
recordObserveHookIncident("ToolError", "database", error);
|
|
2242
2359
|
}
|
|
2243
2360
|
}
|
|
2244
2361
|
throw err;
|
|
@@ -2261,7 +2378,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2261
2378
|
}
|
|
2262
2379
|
|
|
2263
2380
|
// PostToolUse hook
|
|
2264
|
-
if (hookDb) {
|
|
2381
|
+
if (hookDb && !planReadOnlyFenceActive) {
|
|
2265
2382
|
try {
|
|
2266
2383
|
await executeHooks(hookDb, HookEvents.PostToolUse, {
|
|
2267
2384
|
tool: name,
|
|
@@ -2273,13 +2390,18 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2273
2390
|
descriptor: runtimeDescriptor,
|
|
2274
2391
|
context: toolContext,
|
|
2275
2392
|
});
|
|
2276
|
-
} catch (
|
|
2277
|
-
|
|
2393
|
+
} catch (error) {
|
|
2394
|
+
recordObserveHookIncident("PostToolUse", "database", error);
|
|
2278
2395
|
}
|
|
2279
2396
|
}
|
|
2280
2397
|
// settings.json PostToolUse hooks: can't un-run the tool, but a `block`
|
|
2281
2398
|
// reason is attached as `hookFeedback` to be surfaced back to the model.
|
|
2282
|
-
if (
|
|
2399
|
+
if (
|
|
2400
|
+
!planReadOnlyFenceActive &&
|
|
2401
|
+
context.settingsHooks &&
|
|
2402
|
+
toolResult &&
|
|
2403
|
+
typeof toolResult === "object"
|
|
2404
|
+
) {
|
|
2283
2405
|
try {
|
|
2284
2406
|
const matched = collectHooks(context.settingsHooks, "PostToolUse", name);
|
|
2285
2407
|
if (matched && matched.length > 0) {
|
|
@@ -2314,6 +2436,20 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2314
2436
|
event: "PostToolUse",
|
|
2315
2437
|
broker,
|
|
2316
2438
|
});
|
|
2439
|
+
for (const hookResult of outcome.results || []) {
|
|
2440
|
+
if (
|
|
2441
|
+
hookResult?.nonBlockingError === true ||
|
|
2442
|
+
hookResult?.breakerOpen === true ||
|
|
2443
|
+
hookResult?.malformedDecision === true ||
|
|
2444
|
+
hookResult?.unsupportedDecision === true
|
|
2445
|
+
) {
|
|
2446
|
+
recordObserveHookIncident(
|
|
2447
|
+
"PostToolUse",
|
|
2448
|
+
"settings-command",
|
|
2449
|
+
hookResult.reason || hookResult.error || "hook command failed",
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2317
2453
|
if (outcome.decision === "block" && outcome.reason) {
|
|
2318
2454
|
toolResult.hookFeedback = outcome.reason;
|
|
2319
2455
|
}
|
|
@@ -2324,8 +2460,8 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2324
2460
|
context.hookSupervisor.dispatch(asyncHooks, payload, { cwd, broker });
|
|
2325
2461
|
}
|
|
2326
2462
|
}
|
|
2327
|
-
} catch (
|
|
2328
|
-
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
recordObserveHookIncident("PostToolUse", "settings-command", error);
|
|
2329
2465
|
}
|
|
2330
2466
|
}
|
|
2331
2467
|
|
|
@@ -2366,8 +2502,8 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2366
2502
|
? `${toolResult.hookFeedback}\n${outcome.reason}`
|
|
2367
2503
|
: outcome.reason;
|
|
2368
2504
|
}
|
|
2369
|
-
} catch (
|
|
2370
|
-
|
|
2505
|
+
} catch (error) {
|
|
2506
|
+
recordObserveHookIncident("SubagentStop", "settings-command", error);
|
|
2371
2507
|
}
|
|
2372
2508
|
}
|
|
2373
2509
|
|
|
@@ -2385,7 +2521,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2385
2521
|
typeof toolResult === "object" &&
|
|
2386
2522
|
!toolResult.error &&
|
|
2387
2523
|
args?.path &&
|
|
2388
|
-
context.mcpClient &&
|
|
2524
|
+
(context.mcpHostClient || context.mcpClient) &&
|
|
2389
2525
|
context.externalToolExecutors
|
|
2390
2526
|
) {
|
|
2391
2527
|
try {
|
|
@@ -2393,7 +2529,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2393
2529
|
await import("../lib/ide-context.js");
|
|
2394
2530
|
const diags = await collectIdeDiagnostics(
|
|
2395
2531
|
{
|
|
2396
|
-
mcpClient: context.mcpClient,
|
|
2532
|
+
mcpClient: context.mcpHostClient || context.mcpClient,
|
|
2397
2533
|
externalToolExecutors: context.externalToolExecutors,
|
|
2398
2534
|
},
|
|
2399
2535
|
path.resolve(cwd, args.path),
|
|
@@ -2405,6 +2541,14 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2405
2541
|
}
|
|
2406
2542
|
}
|
|
2407
2543
|
|
|
2544
|
+
if (
|
|
2545
|
+
observeHookIncidents.length > 0 &&
|
|
2546
|
+
toolResult &&
|
|
2547
|
+
typeof toolResult === "object"
|
|
2548
|
+
) {
|
|
2549
|
+
toolResult.hookIncidents = observeHookIncidents;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2408
2552
|
return toolResult;
|
|
2409
2553
|
}
|
|
2410
2554
|
|
|
@@ -2898,15 +3042,20 @@ export async function _withPostEditDiagnostics(
|
|
|
2898
3042
|
// NOT loaded up front — they cost tokens for subtrees a run may never touch.
|
|
2899
3043
|
// When a tool first ACCESSES a path inside such a subtree, we inject that
|
|
2900
3044
|
// subtree's directory instructions into the tool result (the SAME channel as
|
|
2901
|
-
// newDiagnostics), exactly once per subtree per
|
|
2902
|
-
// SubtreeInstructionLoader per root remembers what it already injected so a
|
|
3045
|
+
// newDiagnostics), exactly once per subtree per agent session. A stateful
|
|
3046
|
+
// SubtreeInstructionLoader per root/session remembers what it already injected so a
|
|
2903
3047
|
// second access to the same subtree is a no-op. Disable with
|
|
2904
3048
|
// CC_SUBTREE_INSTRUCTIONS=0. Common case (no cc.md below cwd) → zero cost.
|
|
2905
|
-
const _subtreeLoaderPool = new Map(); // root ->
|
|
3049
|
+
const _subtreeLoaderPool = new Map(); // root -> Map<session scope, loader>
|
|
2906
3050
|
|
|
2907
|
-
async function _getSubtreeLoader(cwd) {
|
|
3051
|
+
async function _getSubtreeLoader(cwd, sessionScope = "__legacy__") {
|
|
2908
3052
|
const root = path.resolve(cwd || process.cwd());
|
|
2909
|
-
let
|
|
3053
|
+
let scopedLoaders = _subtreeLoaderPool.get(root);
|
|
3054
|
+
if (!scopedLoaders) {
|
|
3055
|
+
scopedLoaders = new Map();
|
|
3056
|
+
_subtreeLoaderPool.set(root, scopedLoaders);
|
|
3057
|
+
}
|
|
3058
|
+
let loader = scopedLoaders.get(sessionScope);
|
|
2910
3059
|
if (!loader) {
|
|
2911
3060
|
const { SubtreeInstructionLoader } =
|
|
2912
3061
|
await import("../lib/project-instructions.js");
|
|
@@ -2925,7 +3074,7 @@ async function _getSubtreeLoader(cwd) {
|
|
|
2925
3074
|
baseDir: root,
|
|
2926
3075
|
instructionExcludes,
|
|
2927
3076
|
});
|
|
2928
|
-
|
|
3077
|
+
scopedLoaders.set(sessionScope, loader);
|
|
2929
3078
|
}
|
|
2930
3079
|
return loader;
|
|
2931
3080
|
}
|
|
@@ -2940,15 +3089,28 @@ export function _resetSubtreeInstructionLoaders() {
|
|
|
2940
3089
|
* with their (capped) content read for inline injection — or null when the
|
|
2941
3090
|
* subtree has no NEW cc.md/CLAUDE.md/AGENTS.md (the common case → zero cost).
|
|
2942
3091
|
*/
|
|
2943
|
-
async function
|
|
3092
|
+
async function _prepareSubtreeInstructions(
|
|
3093
|
+
accessedPaths,
|
|
3094
|
+
cwd,
|
|
3095
|
+
sessionScope = "__legacy__",
|
|
3096
|
+
) {
|
|
2944
3097
|
if (process.env.CC_SUBTREE_INSTRUCTIONS === "0") return null;
|
|
2945
3098
|
try {
|
|
2946
|
-
const loader = await _getSubtreeLoader(cwd);
|
|
2947
|
-
const
|
|
3099
|
+
const loader = await _getSubtreeLoader(cwd, sessionScope);
|
|
3100
|
+
const discovered = new Map();
|
|
3101
|
+
for (const accessedPath of Array.isArray(accessedPaths)
|
|
3102
|
+
? accessedPaths
|
|
3103
|
+
: [accessedPaths]) {
|
|
3104
|
+
for (const candidate of loader.discover(accessedPath)) {
|
|
3105
|
+
discovered.set(candidate.identity, candidate);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
const fresh = [...discovered.values()];
|
|
2948
3109
|
if (!fresh || !fresh.length) return null;
|
|
2949
3110
|
const { DEFAULT_MAX_FILE_BYTES } =
|
|
2950
3111
|
await import("../lib/project-instructions.js");
|
|
2951
3112
|
const out = [];
|
|
3113
|
+
const errors = [];
|
|
2952
3114
|
for (const f of fresh) {
|
|
2953
3115
|
try {
|
|
2954
3116
|
const buf = fs.readFileSync(f.path);
|
|
@@ -2962,23 +3124,84 @@ async function _subtreeInstructionsFor(accessedPath, cwd) {
|
|
|
2962
3124
|
content,
|
|
2963
3125
|
...(truncated ? { truncated: true } : {}),
|
|
2964
3126
|
});
|
|
2965
|
-
} catch {
|
|
2966
|
-
|
|
3127
|
+
} catch (err) {
|
|
3128
|
+
errors.push({ path: f.path, message: err.message });
|
|
2967
3129
|
}
|
|
2968
3130
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3131
|
+
// Commit only after every candidate in the source/target batch has been
|
|
3132
|
+
// read. Any failure leaves the entire batch discoverable for a retry.
|
|
3133
|
+
if (!errors.length) loader.commit(fresh);
|
|
3134
|
+
return out.length || errors.length ? { instructions: out, errors } : null;
|
|
3135
|
+
} catch (err) {
|
|
3136
|
+
// Paths outside cwd are handled by the normal path/sandbox authority and
|
|
3137
|
+
// have no cwd-subtree rules. Other failures remain visible to mutation
|
|
3138
|
+
// preflight so they cannot silently widen authority.
|
|
3139
|
+
if (err?.code === "ERR_SUBTREE_INSTRUCTION_BOUNDARY") return null;
|
|
3140
|
+
return {
|
|
3141
|
+
instructions: [],
|
|
3142
|
+
errors: [{ path: null, message: err?.message || String(err) }],
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
async function _subtreeInstructionsFor(accessedPath, cwd, sessionScope) {
|
|
3148
|
+
const prepared = await _prepareSubtreeInstructions(
|
|
3149
|
+
accessedPath,
|
|
3150
|
+
cwd,
|
|
3151
|
+
sessionScope,
|
|
3152
|
+
);
|
|
3153
|
+
return prepared?.instructions?.length ? prepared.instructions : null;
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
async function _preflightMutationSubtreeInstructions(
|
|
3157
|
+
accessedPaths,
|
|
3158
|
+
cwd,
|
|
3159
|
+
sessionScope,
|
|
3160
|
+
) {
|
|
3161
|
+
const prepared = await _prepareSubtreeInstructions(
|
|
3162
|
+
accessedPaths,
|
|
3163
|
+
cwd,
|
|
3164
|
+
sessionScope,
|
|
3165
|
+
);
|
|
3166
|
+
if (!prepared) return null;
|
|
3167
|
+
if (prepared.errors?.length) {
|
|
3168
|
+
return {
|
|
3169
|
+
error:
|
|
3170
|
+
"[Subtree Instructions] Mutation blocked because applicable instructions could not be loaded. No mutation was performed.",
|
|
3171
|
+
policy: { decision: "blocked", via: "subtree-instructions" },
|
|
3172
|
+
instructionLoadErrors: prepared.errors,
|
|
3173
|
+
mutationPerformed: false,
|
|
3174
|
+
...(prepared.instructions?.length
|
|
3175
|
+
? { subtreeInstructions: prepared.instructions }
|
|
3176
|
+
: {}),
|
|
3177
|
+
};
|
|
2972
3178
|
}
|
|
3179
|
+
if (!prepared.instructions?.length) return null;
|
|
3180
|
+
return {
|
|
3181
|
+
error:
|
|
3182
|
+
"[Subtree Instructions] Mutation deferred before its first effect. Review these authoritative subtree rules, then retry the tool call.",
|
|
3183
|
+
policy: { decision: "deferred", via: "subtree-instructions" },
|
|
3184
|
+
subtreeInstructions: prepared.instructions,
|
|
3185
|
+
mutationPerformed: false,
|
|
3186
|
+
};
|
|
2973
3187
|
}
|
|
2974
3188
|
|
|
2975
3189
|
/**
|
|
2976
3190
|
* Attach freshly-discovered subtree instructions to a SUCCESSFUL tool result
|
|
2977
3191
|
* (no-op on an error result or when the subtree has nothing new).
|
|
2978
3192
|
*/
|
|
2979
|
-
async function _withSubtreeInstructions(
|
|
3193
|
+
async function _withSubtreeInstructions(
|
|
3194
|
+
result,
|
|
3195
|
+
accessedPath,
|
|
3196
|
+
cwd,
|
|
3197
|
+
sessionScope,
|
|
3198
|
+
) {
|
|
2980
3199
|
if (result && result.error) return result;
|
|
2981
|
-
const subtreeInstructions = await _subtreeInstructionsFor(
|
|
3200
|
+
const subtreeInstructions = await _subtreeInstructionsFor(
|
|
3201
|
+
accessedPath,
|
|
3202
|
+
cwd,
|
|
3203
|
+
sessionScope,
|
|
3204
|
+
);
|
|
2982
3205
|
return subtreeInstructions ? { ...result, subtreeInstructions } : result;
|
|
2983
3206
|
}
|
|
2984
3207
|
|
|
@@ -3035,6 +3258,168 @@ async function promoteBrowserScreenshot(
|
|
|
3035
3258
|
}
|
|
3036
3259
|
}
|
|
3037
3260
|
|
|
3261
|
+
const MCP_LEDGER_EFFECTS = new Set(["read", "unknown", "write", "destructive"]);
|
|
3262
|
+
|
|
3263
|
+
/**
|
|
3264
|
+
* Reduce descriptor + managed-host metadata to the host-owned contract written
|
|
3265
|
+
* to the MCP ledger. A server's read-only declaration is never promoted to a
|
|
3266
|
+
* read authorization. Server declarations may only make the classification
|
|
3267
|
+
* stricter (write/destructive) when host authority is absent.
|
|
3268
|
+
*/
|
|
3269
|
+
function mcpLedgerEffectContract(descriptor, hostPolicy) {
|
|
3270
|
+
const descriptorContract = descriptor?.effectContract || {};
|
|
3271
|
+
const hostContract = hostPolicy?.effectContract || {};
|
|
3272
|
+
const authorizedCandidate =
|
|
3273
|
+
hostContract.authorizedEffect || hostPolicy?.authorizedEffect;
|
|
3274
|
+
const authorizedEffect = MCP_LEDGER_EFFECTS.has(authorizedCandidate)
|
|
3275
|
+
? authorizedCandidate
|
|
3276
|
+
: null;
|
|
3277
|
+
const declaredEffect = MCP_LEDGER_EFFECTS.has(
|
|
3278
|
+
descriptorContract.declaredEffect,
|
|
3279
|
+
)
|
|
3280
|
+
? descriptorContract.declaredEffect
|
|
3281
|
+
: "unknown";
|
|
3282
|
+
const effect =
|
|
3283
|
+
authorizedEffect ||
|
|
3284
|
+
(declaredEffect === "destructive" || declaredEffect === "write"
|
|
3285
|
+
? declaredEffect
|
|
3286
|
+
: "unknown");
|
|
3287
|
+
const annotations = descriptorContract.annotations || {};
|
|
3288
|
+
|
|
3289
|
+
return {
|
|
3290
|
+
effect,
|
|
3291
|
+
destructive:
|
|
3292
|
+
effect === "destructive" || annotations.destructiveHint === true,
|
|
3293
|
+
sideEffecting: effect === "write" || effect === "destructive",
|
|
3294
|
+
idempotent:
|
|
3295
|
+
typeof annotations.idempotentHint === "boolean"
|
|
3296
|
+
? annotations.idempotentHint
|
|
3297
|
+
: null,
|
|
3298
|
+
openWorld:
|
|
3299
|
+
typeof annotations.openWorldHint === "boolean"
|
|
3300
|
+
? annotations.openWorldHint
|
|
3301
|
+
: null,
|
|
3302
|
+
trusted:
|
|
3303
|
+
authorizedEffect != null &&
|
|
3304
|
+
(hostContract.trusted === true || hostPolicy?.sourceTrusted === true),
|
|
3305
|
+
source:
|
|
3306
|
+
hostContract.provenance ||
|
|
3307
|
+
descriptorContract.provenance ||
|
|
3308
|
+
descriptor?.source ||
|
|
3309
|
+
"mcp",
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
function mcpLedgerScopes(args) {
|
|
3314
|
+
const resourceScopes = [];
|
|
3315
|
+
const networkScopes = [];
|
|
3316
|
+
const visit = (value, key = "", depth = 0) => {
|
|
3317
|
+
if (depth > 3 || value == null) return;
|
|
3318
|
+
if (Array.isArray(value)) {
|
|
3319
|
+
value.slice(0, 32).forEach((entry) => visit(entry, key, depth + 1));
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
if (typeof value === "object") {
|
|
3323
|
+
Object.entries(value)
|
|
3324
|
+
.slice(0, 64)
|
|
3325
|
+
.forEach(([childKey, childValue]) =>
|
|
3326
|
+
visit(childValue, childKey, depth + 1),
|
|
3327
|
+
);
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
3330
|
+
if (typeof value !== "string") return;
|
|
3331
|
+
|
|
3332
|
+
const normalizedKey = String(key).toLowerCase();
|
|
3333
|
+
const isNetworkScope = /url|uri|endpoint|origin|host/.test(normalizedKey);
|
|
3334
|
+
if (isNetworkScope) {
|
|
3335
|
+
networkScopes.push(value);
|
|
3336
|
+
}
|
|
3337
|
+
// URL/URI values can carry credentials, query secrets and private paths.
|
|
3338
|
+
// The network ledger normalizes them to origin only; never duplicate the
|
|
3339
|
+
// raw value into resourceScopes, whose identifiers are intentionally not
|
|
3340
|
+
// URL parsers. The input digest still binds the exact request at rest.
|
|
3341
|
+
if (
|
|
3342
|
+
!isNetworkScope &&
|
|
3343
|
+
/path|file|resource|repo|project|workspace/.test(normalizedKey)
|
|
3344
|
+
) {
|
|
3345
|
+
resourceScopes.push(`${normalizedKey}:${value}`);
|
|
3346
|
+
}
|
|
3347
|
+
};
|
|
3348
|
+
visit(args || {});
|
|
3349
|
+
return { resourceScopes, networkScopes };
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
const MCP_TRANSPORT_OUTCOME_UNKNOWN_CODE = "CC_MCP_TRANSPORT_OUTCOME_UNKNOWN";
|
|
3353
|
+
const MCP_PROTOCOL_RESULT_INVALID_CODE = "CC_MCP_PROTOCOL_RESULT_INVALID";
|
|
3354
|
+
const MCP_RESULT_PROJECTION_FAILED_CODE = "CC_MCP_RESULT_PROJECTION_FAILED";
|
|
3355
|
+
|
|
3356
|
+
function safeMcpProperty(value, property) {
|
|
3357
|
+
try {
|
|
3358
|
+
return value?.[property];
|
|
3359
|
+
} catch {
|
|
3360
|
+
return undefined;
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3364
|
+
function safeMcpErrorMessage(error) {
|
|
3365
|
+
const message = safeMcpProperty(error, "message");
|
|
3366
|
+
if (typeof message === "string" && message) return message;
|
|
3367
|
+
try {
|
|
3368
|
+
return String(error || "unknown error");
|
|
3369
|
+
} catch {
|
|
3370
|
+
return "unknown error";
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
function safeMcpErrorCode(error, fallback) {
|
|
3375
|
+
const code = safeMcpProperty(error, "code");
|
|
3376
|
+
return typeof code === "string" && code ? code : fallback;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
function invalidMcpProtocolResult(cause) {
|
|
3380
|
+
const error = new TypeError("MCP result could not be inspected safely", {
|
|
3381
|
+
cause,
|
|
3382
|
+
});
|
|
3383
|
+
error.code = MCP_PROTOCOL_RESULT_INVALID_CODE;
|
|
3384
|
+
return error;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
function mcpTransportOutcomeIsUnsafe(effectContract) {
|
|
3388
|
+
return !(
|
|
3389
|
+
effectContract?.effect === McpEffect.READ &&
|
|
3390
|
+
effectContract?.trusted === true
|
|
3391
|
+
);
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
function mcpOutcomeUnknownPayload(
|
|
3395
|
+
ledger,
|
|
3396
|
+
ticket,
|
|
3397
|
+
{ phase, reasonCode = MCP_OUTCOME_UNKNOWN_CODE } = {},
|
|
3398
|
+
) {
|
|
3399
|
+
const stableReasonCode =
|
|
3400
|
+
typeof reasonCode === "string" && reasonCode
|
|
3401
|
+
? reasonCode
|
|
3402
|
+
: MCP_OUTCOME_UNKNOWN_CODE;
|
|
3403
|
+
try {
|
|
3404
|
+
markMcpLedgerOutcomeUnknown(ledger, stableReasonCode);
|
|
3405
|
+
} catch {
|
|
3406
|
+
// The public result must remain deterministic even for a hostile ledger.
|
|
3407
|
+
}
|
|
3408
|
+
return {
|
|
3409
|
+
error:
|
|
3410
|
+
"MCP tool may have completed, but its outcome is unknown; do not retry automatically until durable recovery is adjudicated.",
|
|
3411
|
+
code: MCP_OUTCOME_UNKNOWN_CODE,
|
|
3412
|
+
status: "outcome_unknown",
|
|
3413
|
+
outcomeUnknown: true,
|
|
3414
|
+
retryable: false,
|
|
3415
|
+
mcpLedgerId: safeMcpProperty(ticket, "ledgerId") || null,
|
|
3416
|
+
mcpLedgerIncident: {
|
|
3417
|
+
phase: phase || "settled",
|
|
3418
|
+
code: stableReasonCode,
|
|
3419
|
+
},
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3038
3423
|
async function executeToolInner(
|
|
3039
3424
|
name,
|
|
3040
3425
|
args,
|
|
@@ -3047,11 +3432,17 @@ async function executeToolInner(
|
|
|
3047
3432
|
sessionId,
|
|
3048
3433
|
turnId,
|
|
3049
3434
|
toolCallId,
|
|
3435
|
+
planManager = null,
|
|
3436
|
+
permissionRules = null,
|
|
3437
|
+
effectiveAllowedToolNames = null,
|
|
3050
3438
|
hostManagedToolPolicy,
|
|
3051
3439
|
externalToolDescriptors,
|
|
3052
3440
|
externalToolExecutors,
|
|
3053
3441
|
extraToolDefinitions = null,
|
|
3054
3442
|
mcpClient,
|
|
3443
|
+
mcpCallLedger = null,
|
|
3444
|
+
mcpConflictScheduler = null,
|
|
3445
|
+
subtreeInstructionScope = "__legacy__",
|
|
3055
3446
|
memoryDb = null,
|
|
3056
3447
|
permanentMemory = null,
|
|
3057
3448
|
subAgentContract = null,
|
|
@@ -3066,6 +3457,7 @@ async function executeToolInner(
|
|
|
3066
3457
|
settingsVerdict = null,
|
|
3067
3458
|
subAgentDepth = 0,
|
|
3068
3459
|
subAgentBudget = null,
|
|
3460
|
+
sessionBudget = null,
|
|
3069
3461
|
interactiveApproval = false,
|
|
3070
3462
|
settingsHooks = null,
|
|
3071
3463
|
signal = null,
|
|
@@ -3195,6 +3587,7 @@ async function executeToolInner(
|
|
|
3195
3587
|
},
|
|
3196
3588
|
filePath,
|
|
3197
3589
|
cwd,
|
|
3590
|
+
subtreeInstructionScope,
|
|
3198
3591
|
),
|
|
3199
3592
|
);
|
|
3200
3593
|
}
|
|
@@ -3207,6 +3600,7 @@ async function executeToolInner(
|
|
|
3207
3600
|
},
|
|
3208
3601
|
filePath,
|
|
3209
3602
|
cwd,
|
|
3603
|
+
subtreeInstructionScope,
|
|
3210
3604
|
),
|
|
3211
3605
|
);
|
|
3212
3606
|
}
|
|
@@ -3237,6 +3631,7 @@ async function executeToolInner(
|
|
|
3237
3631
|
),
|
|
3238
3632
|
filePath,
|
|
3239
3633
|
cwd,
|
|
3634
|
+
subtreeInstructionScope,
|
|
3240
3635
|
),
|
|
3241
3636
|
);
|
|
3242
3637
|
}
|
|
@@ -3318,6 +3713,7 @@ async function executeToolInner(
|
|
|
3318
3713
|
),
|
|
3319
3714
|
targetPath,
|
|
3320
3715
|
cwd,
|
|
3716
|
+
subtreeInstructionScope,
|
|
3321
3717
|
),
|
|
3322
3718
|
);
|
|
3323
3719
|
}
|
|
@@ -3418,6 +3814,7 @@ async function executeToolInner(
|
|
|
3418
3814
|
),
|
|
3419
3815
|
filePath,
|
|
3420
3816
|
cwd,
|
|
3817
|
+
subtreeInstructionScope,
|
|
3421
3818
|
),
|
|
3422
3819
|
);
|
|
3423
3820
|
}
|
|
@@ -3483,6 +3880,7 @@ async function executeToolInner(
|
|
|
3483
3880
|
),
|
|
3484
3881
|
filePath,
|
|
3485
3882
|
cwd,
|
|
3883
|
+
subtreeInstructionScope,
|
|
3486
3884
|
),
|
|
3487
3885
|
);
|
|
3488
3886
|
}
|
|
@@ -4444,13 +4842,28 @@ async function executeToolInner(
|
|
|
4444
4842
|
llmOptions,
|
|
4445
4843
|
subAgentDepth,
|
|
4446
4844
|
subAgentBudget,
|
|
4845
|
+
sessionBudget,
|
|
4447
4846
|
subAgentContract,
|
|
4448
4847
|
settingsHooks,
|
|
4848
|
+
// Immutable parent execution authority. The child may only tighten
|
|
4849
|
+
// these boundaries; it never reconstructs a fresh default policy.
|
|
4850
|
+
planManager,
|
|
4851
|
+
permissionRules,
|
|
4852
|
+
effectiveAllowedToolNames,
|
|
4853
|
+
hostManagedToolPolicy,
|
|
4854
|
+
sandbox,
|
|
4855
|
+
additionalDirectories,
|
|
4856
|
+
approvalGate,
|
|
4857
|
+
shellPolicyOverrides,
|
|
4858
|
+
classifyAllShell,
|
|
4859
|
+
unattendedActionPolicy,
|
|
4449
4860
|
// Parent trace for the child's hook envelopes (parent_id).
|
|
4450
4861
|
hookTraceId,
|
|
4451
4862
|
// Parent MCP plumbing — a spawn can inherit these into the child,
|
|
4452
4863
|
// filtered by the resolved contract's mcpServers allow-list.
|
|
4453
4864
|
mcpClient,
|
|
4865
|
+
mcpCallLedger,
|
|
4866
|
+
mcpConflictScheduler,
|
|
4454
4867
|
externalToolDescriptors,
|
|
4455
4868
|
externalToolExecutors,
|
|
4456
4869
|
extraToolDefinitions,
|
|
@@ -4522,14 +4935,31 @@ async function executeToolInner(
|
|
|
4522
4935
|
case "todo_write": {
|
|
4523
4936
|
try {
|
|
4524
4937
|
const { writeTodos } = await import("../lib/todo-manager.js");
|
|
4525
|
-
const result = writeTodos(sessionId, args.todos
|
|
4938
|
+
const result = writeTodos(sessionId, args.todos, {
|
|
4939
|
+
...(Number.isSafeInteger(args.expected_revision)
|
|
4940
|
+
? { expectedRevision: args.expected_revision }
|
|
4941
|
+
: {}),
|
|
4942
|
+
});
|
|
4526
4943
|
if (!result.success) {
|
|
4527
|
-
return attachDescriptor({
|
|
4944
|
+
return attachDescriptor({
|
|
4945
|
+
error: result.error,
|
|
4946
|
+
...(result.code ? { code: result.code } : {}),
|
|
4947
|
+
...(Number.isSafeInteger(result.expectedRevision)
|
|
4948
|
+
? { expectedRevision: result.expectedRevision }
|
|
4949
|
+
: {}),
|
|
4950
|
+
...(Number.isSafeInteger(result.actualRevision)
|
|
4951
|
+
? { actualRevision: result.actualRevision }
|
|
4952
|
+
: {}),
|
|
4953
|
+
...(result.recoveryStrategy
|
|
4954
|
+
? { recoveryStrategy: result.recoveryStrategy }
|
|
4955
|
+
: {}),
|
|
4956
|
+
});
|
|
4528
4957
|
}
|
|
4529
4958
|
return attachDescriptor({
|
|
4530
4959
|
success: true,
|
|
4531
4960
|
count: result.count,
|
|
4532
4961
|
summary: result.summary,
|
|
4962
|
+
revision: result.revision,
|
|
4533
4963
|
});
|
|
4534
4964
|
} catch (err) {
|
|
4535
4965
|
return attachDescriptor({ error: `todo_write failed: ${err.message}` });
|
|
@@ -5218,6 +5648,7 @@ async function executeToolInner(
|
|
|
5218
5648
|
},
|
|
5219
5649
|
dirPath,
|
|
5220
5650
|
cwd,
|
|
5651
|
+
subtreeInstructionScope,
|
|
5221
5652
|
),
|
|
5222
5653
|
);
|
|
5223
5654
|
}
|
|
@@ -5243,7 +5674,14 @@ async function executeToolInner(
|
|
|
5243
5674
|
});
|
|
5244
5675
|
}
|
|
5245
5676
|
try {
|
|
5246
|
-
if (typeof skillLoader.
|
|
5677
|
+
if (typeof skillLoader.materializeSkillForExecution === "function") {
|
|
5678
|
+
match = await skillLoader.materializeSkillForExecution(match, {
|
|
5679
|
+
sessionId,
|
|
5680
|
+
turnId,
|
|
5681
|
+
loadedBecause: "run_skill",
|
|
5682
|
+
bodyIncluded: false,
|
|
5683
|
+
});
|
|
5684
|
+
} else if (typeof skillLoader.materializeSkill === "function") {
|
|
5247
5685
|
match = skillLoader.materializeSkill(match, {
|
|
5248
5686
|
sessionId,
|
|
5249
5687
|
turnId,
|
|
@@ -5254,12 +5692,26 @@ async function executeToolInner(
|
|
|
5254
5692
|
} catch (error) {
|
|
5255
5693
|
return attachDescriptor({
|
|
5256
5694
|
error: `Skill "${args.skill_name}" body could not be loaded: ${error.message}`,
|
|
5695
|
+
...(error?.code ? { code: error.code } : {}),
|
|
5696
|
+
policy: {
|
|
5697
|
+
decision: "blocked",
|
|
5698
|
+
via: "skill-execution-boundary",
|
|
5699
|
+
},
|
|
5257
5700
|
});
|
|
5258
5701
|
}
|
|
5259
5702
|
|
|
5260
5703
|
// Check if skill requests isolation (via SKILL.md frontmatter)
|
|
5261
5704
|
const skillIsolation = match.isolation === true;
|
|
5262
5705
|
if (skillIsolation) {
|
|
5706
|
+
const isolatedSkillTools = [
|
|
5707
|
+
"read_file",
|
|
5708
|
+
"search_files",
|
|
5709
|
+
"list_dir",
|
|
5710
|
+
].filter(
|
|
5711
|
+
(toolName) =>
|
|
5712
|
+
!Array.isArray(effectiveAllowedToolNames) ||
|
|
5713
|
+
effectiveAllowedToolNames.includes(toolName),
|
|
5714
|
+
);
|
|
5263
5715
|
// 用量归因: an isolated skill runs as a child loop whose real token
|
|
5264
5716
|
// usage would otherwise be invisible — forward it into the parent
|
|
5265
5717
|
// run's sink tagged origin:"skill" so `cc session usage --by skill`
|
|
@@ -5271,11 +5723,36 @@ async function executeToolInner(
|
|
|
5271
5723
|
// Run skill through isolated sub-agent context
|
|
5272
5724
|
const subCtx = SubAgentContext.create({
|
|
5273
5725
|
role: `skill-${args.skill_name}`,
|
|
5274
|
-
task:
|
|
5275
|
-
|
|
5726
|
+
task:
|
|
5727
|
+
`Execute the "${args.skill_name}" skill using only the approved tools.\n\n` +
|
|
5728
|
+
`Authoritative skill instructions:\n${String(match.body || "").substring(0, 48000)}\n\n` +
|
|
5729
|
+
`User input:\n${String(args.input || "").substring(0, 8000)}`,
|
|
5730
|
+
allowedTools: isolatedSkillTools,
|
|
5276
5731
|
hookParentTraceId: hookTraceId || null,
|
|
5277
5732
|
toolAdmission,
|
|
5278
5733
|
cwd,
|
|
5734
|
+
llmOptions: llmOptions || null,
|
|
5735
|
+
...(sessionBudget ? { sessionBudget } : {}),
|
|
5736
|
+
...(permissionRules ? { permissionRules } : {}),
|
|
5737
|
+
...(hostManagedToolPolicy
|
|
5738
|
+
? {
|
|
5739
|
+
hostManagedToolPolicy: {
|
|
5740
|
+
...hostManagedToolPolicy,
|
|
5741
|
+
toolDefinitions: [],
|
|
5742
|
+
},
|
|
5743
|
+
}
|
|
5744
|
+
: {}),
|
|
5745
|
+
...(planManager ? { planManager } : {}),
|
|
5746
|
+
...(sandbox ? { sandbox } : {}),
|
|
5747
|
+
...(Array.isArray(additionalDirectories)
|
|
5748
|
+
? { additionalDirectories: [...additionalDirectories] }
|
|
5749
|
+
: {}),
|
|
5750
|
+
...(approvalGate ? { approvalGate } : {}),
|
|
5751
|
+
...(shellPolicyOverrides ? { shellPolicyOverrides } : {}),
|
|
5752
|
+
...(classifyAllShell ? { classifyAllShell: true } : {}),
|
|
5753
|
+
...(unattendedActionPolicy ? { unattendedActionPolicy } : {}),
|
|
5754
|
+
...(mcpCallLedger ? { mcpCallLedger } : {}),
|
|
5755
|
+
...(mcpConflictScheduler ? { mcpConflictScheduler } : {}),
|
|
5279
5756
|
onUsage: skillUsageSink
|
|
5280
5757
|
? (u) => {
|
|
5281
5758
|
try {
|
|
@@ -5286,6 +5763,7 @@ async function executeToolInner(
|
|
|
5286
5763
|
provider: u?.provider ?? null,
|
|
5287
5764
|
model: u?.model ?? null,
|
|
5288
5765
|
usage: u?.usage || null,
|
|
5766
|
+
...(u?.source ? { source: u.source } : {}),
|
|
5289
5767
|
attribution: {
|
|
5290
5768
|
origin: "skill",
|
|
5291
5769
|
skill: args.skill_name,
|
|
@@ -5304,6 +5782,25 @@ async function executeToolInner(
|
|
|
5304
5782
|
skillSubRef = subCtx;
|
|
5305
5783
|
try {
|
|
5306
5784
|
const result = await subCtx.run(args.input);
|
|
5785
|
+
const resolvedFailure =
|
|
5786
|
+
subCtx.status === "failed" ||
|
|
5787
|
+
result?.success === false ||
|
|
5788
|
+
Boolean(result?.error);
|
|
5789
|
+
if (resolvedFailure) {
|
|
5790
|
+
const failureDetail =
|
|
5791
|
+
result?.error?.message ||
|
|
5792
|
+
result?.error ||
|
|
5793
|
+
result?.summary ||
|
|
5794
|
+
`child status ${subCtx.status || "failed"}`;
|
|
5795
|
+
return attachDescriptor({
|
|
5796
|
+
success: false,
|
|
5797
|
+
isolated: true,
|
|
5798
|
+
skill: args.skill_name,
|
|
5799
|
+
code: result?.code || "CC_SKILL_ISOLATED_EXECUTION_FAILED",
|
|
5800
|
+
error: `Isolated skill execution failed: ${failureDetail}`,
|
|
5801
|
+
...(result?.summary ? { summary: result.summary } : {}),
|
|
5802
|
+
});
|
|
5803
|
+
}
|
|
5307
5804
|
return attachDescriptor({
|
|
5308
5805
|
success: true,
|
|
5309
5806
|
isolated: true,
|
|
@@ -5313,79 +5810,24 @@ async function executeToolInner(
|
|
|
5313
5810
|
});
|
|
5314
5811
|
} catch (err) {
|
|
5315
5812
|
return attachDescriptor({
|
|
5813
|
+
success: false,
|
|
5814
|
+
isolated: true,
|
|
5815
|
+
skill: args.skill_name,
|
|
5816
|
+
code: err?.code || "CC_SKILL_ISOLATED_EXECUTION_FAILED",
|
|
5316
5817
|
error: `Isolated skill execution failed: ${err.message}`,
|
|
5317
5818
|
});
|
|
5318
5819
|
}
|
|
5319
5820
|
}
|
|
5320
5821
|
|
|
5321
|
-
//
|
|
5322
|
-
//
|
|
5323
|
-
//
|
|
5324
|
-
//
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
const mountResult = await mountSkillMcpServers(mcpClient, match, {
|
|
5331
|
-
onWarn: (msg) => {
|
|
5332
|
-
// Non-fatal — logged as warning, skipped servers captured
|
|
5333
|
-
// in mountResult.skipped.
|
|
5334
|
-
|
|
5335
|
-
console.warn(msg);
|
|
5336
|
-
},
|
|
5337
|
-
});
|
|
5338
|
-
mountedMcpServers = mountResult.mounted;
|
|
5339
|
-
for (const s of mountedMcpServers) {
|
|
5340
|
-
_activeMcpServers.add(typeof s === "string" ? s : s.name);
|
|
5341
|
-
}
|
|
5342
|
-
} catch (err) {
|
|
5343
|
-
return attachDescriptor({
|
|
5344
|
-
error: `Skill MCP mount failed: ${err.message}`,
|
|
5345
|
-
});
|
|
5346
|
-
}
|
|
5347
|
-
}
|
|
5348
|
-
|
|
5349
|
-
try {
|
|
5350
|
-
const handlerPath = path.join(match.skillDir, "handler.js");
|
|
5351
|
-
const imported = await import(
|
|
5352
|
-
`file://${handlerPath.replace(/\\/g, "/")}`
|
|
5353
|
-
);
|
|
5354
|
-
const handler = imported.default || imported;
|
|
5355
|
-
if (handler.init) await handler.init(match);
|
|
5356
|
-
const task = {
|
|
5357
|
-
params: { input: args.input },
|
|
5358
|
-
input: args.input,
|
|
5359
|
-
action: args.input,
|
|
5360
|
-
};
|
|
5361
|
-
const taskContext = {
|
|
5362
|
-
projectRoot: cwd,
|
|
5363
|
-
workspacePath: cwd,
|
|
5364
|
-
processBroker: createSkillProcessBroker(match),
|
|
5365
|
-
// Expose the MCP client + mounted servers so the skill handler
|
|
5366
|
-
// can call MCP tools directly without going through the agent
|
|
5367
|
-
// loop. Handlers that don't need MCP can ignore these.
|
|
5368
|
-
mcpClient: mcpClient || null,
|
|
5369
|
-
mountedMcpServers,
|
|
5370
|
-
};
|
|
5371
|
-
const result = await handler.execute(task, taskContext, match);
|
|
5372
|
-
return attachDescriptor(result);
|
|
5373
|
-
} catch (err) {
|
|
5374
|
-
return attachDescriptor({
|
|
5375
|
-
error: `Skill execution failed: ${err.message}`,
|
|
5376
|
-
});
|
|
5377
|
-
} finally {
|
|
5378
|
-
if (mountedMcpServers.length > 0 && mcpClient) {
|
|
5379
|
-
try {
|
|
5380
|
-
await unmountSkillMcpServers(mcpClient, mountedMcpServers);
|
|
5381
|
-
} catch (_err) {
|
|
5382
|
-
// Non-critical — mount/unmount errors don't fail the skill
|
|
5383
|
-
}
|
|
5384
|
-
for (const s of mountedMcpServers) {
|
|
5385
|
-
_activeMcpServers.delete(typeof s === "string" ? s : s.name);
|
|
5386
|
-
}
|
|
5387
|
-
}
|
|
5388
|
-
}
|
|
5822
|
+
// Defense in depth: materializeSkill currently rejects this path before
|
|
5823
|
+
// reaching here. Keep the runtime fence explicit so a custom/legacy
|
|
5824
|
+
// loader can never re-enable arbitrary handler.js imports in the CLI
|
|
5825
|
+
// process or hand a Skill the raw MCP client/process broker.
|
|
5826
|
+
return attachDescriptor({
|
|
5827
|
+
error: `Skill "${args.skill_name}" cannot execute handler.js directly. Add isolation: true and use the controlled agent-tool path.`,
|
|
5828
|
+
code: "CC_SKILL_DIRECT_HANDLER_BLOCKED",
|
|
5829
|
+
policy: { decision: "blocked", via: "skill-execution-boundary" },
|
|
5830
|
+
});
|
|
5389
5831
|
}
|
|
5390
5832
|
|
|
5391
5833
|
case "list_skills": {
|
|
@@ -5501,21 +5943,268 @@ async function executeToolInner(
|
|
|
5501
5943
|
return attachDescriptor(deferredGate);
|
|
5502
5944
|
}
|
|
5503
5945
|
|
|
5946
|
+
let mcpWireInput;
|
|
5947
|
+
try {
|
|
5948
|
+
mcpWireInput = snapshotMcpJsonRpcInput(args || {});
|
|
5949
|
+
} catch (error) {
|
|
5950
|
+
return attachDescriptor({
|
|
5951
|
+
error:
|
|
5952
|
+
"MCP tool blocked because its input is not strict immutable JSON data",
|
|
5953
|
+
policy: {
|
|
5954
|
+
decision: "blocked",
|
|
5955
|
+
via: "mcp-wire-input",
|
|
5956
|
+
code: safeMcpErrorCode(error, "CC_MCP_WIRE_INPUT_INVALID"),
|
|
5957
|
+
},
|
|
5958
|
+
});
|
|
5959
|
+
}
|
|
5960
|
+
|
|
5961
|
+
const schedulerScopes = mcpLedgerScopes(mcpWireInput);
|
|
5962
|
+
const ledgerEffectContract = mcpLedgerEffectContract(
|
|
5963
|
+
localToolDescriptor,
|
|
5964
|
+
hostToolPolicy,
|
|
5965
|
+
);
|
|
5966
|
+
const schedulerEffectContract = ledgerEffectContract.trusted
|
|
5967
|
+
? createHostOwnedMcpEffectContract(ledgerEffectContract)
|
|
5968
|
+
: ledgerEffectContract;
|
|
5969
|
+
const scheduler =
|
|
5970
|
+
mcpConflictScheduler || directMcpConflictScheduler(mcpClient);
|
|
5971
|
+
let schedulerLease;
|
|
5504
5972
|
try {
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5973
|
+
schedulerLease = await scheduler.acquire(
|
|
5974
|
+
{
|
|
5975
|
+
effectContract: schedulerEffectContract,
|
|
5976
|
+
...schedulerScopes,
|
|
5977
|
+
},
|
|
5978
|
+
{ signal },
|
|
5509
5979
|
);
|
|
5510
|
-
if (result && typeof result === "object") {
|
|
5511
|
-
return attachDescriptor(result);
|
|
5512
|
-
}
|
|
5513
|
-
return attachDescriptor({ result });
|
|
5514
5980
|
} catch (err) {
|
|
5515
5981
|
return attachDescriptor({
|
|
5516
|
-
error: `MCP tool
|
|
5982
|
+
error: `MCP tool blocked by the effect conflict scheduler: ${err.message}`,
|
|
5983
|
+
policy: {
|
|
5984
|
+
decision: "blocked",
|
|
5985
|
+
via: "mcp-conflict-scheduler",
|
|
5986
|
+
code: err?.code || null,
|
|
5987
|
+
},
|
|
5517
5988
|
});
|
|
5518
5989
|
}
|
|
5990
|
+
|
|
5991
|
+
try {
|
|
5992
|
+
const ledger = mcpCallLedger || createMcpCallLedger();
|
|
5993
|
+
let ledgerTicket;
|
|
5994
|
+
try {
|
|
5995
|
+
ledgerTicket = await ledger.begin({
|
|
5996
|
+
sessionId,
|
|
5997
|
+
turnId,
|
|
5998
|
+
toolName: localToolExecutor.toolName,
|
|
5999
|
+
serverName: localToolExecutor.serverName,
|
|
6000
|
+
input: mcpWireInput,
|
|
6001
|
+
effectContract: ledgerEffectContract,
|
|
6002
|
+
...schedulerScopes,
|
|
6003
|
+
});
|
|
6004
|
+
} catch (err) {
|
|
6005
|
+
return attachDescriptor({
|
|
6006
|
+
error: `MCP tool blocked because its call ledger prewrite failed: ${err.message}`,
|
|
6007
|
+
policy: {
|
|
6008
|
+
decision: "blocked",
|
|
6009
|
+
via: "mcp-ledger-prewrite",
|
|
6010
|
+
code: err?.code || null,
|
|
6011
|
+
ledgerId: err?.ledgerId || null,
|
|
6012
|
+
effect: err?.effect || null,
|
|
6013
|
+
blockMode: err?.blockMode || null,
|
|
6014
|
+
},
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
|
|
6018
|
+
const transportFailure = async (callError) => {
|
|
6019
|
+
if (mcpTransportOutcomeIsUnsafe(ledgerEffectContract)) {
|
|
6020
|
+
return attachDescriptor(
|
|
6021
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6022
|
+
phase: "call",
|
|
6023
|
+
reasonCode: MCP_TRANSPORT_OUTCOME_UNKNOWN_CODE,
|
|
6024
|
+
}),
|
|
6025
|
+
);
|
|
6026
|
+
}
|
|
6027
|
+
try {
|
|
6028
|
+
await ledgerTicket.settle({
|
|
6029
|
+
status: "failed",
|
|
6030
|
+
error: callError,
|
|
6031
|
+
});
|
|
6032
|
+
} catch (ledgerError) {
|
|
6033
|
+
return attachDescriptor(
|
|
6034
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6035
|
+
phase: "settled",
|
|
6036
|
+
reasonCode: safeMcpErrorCode(
|
|
6037
|
+
ledgerError,
|
|
6038
|
+
"CC_MCP_LEDGER_SETTLE_FAILED",
|
|
6039
|
+
),
|
|
6040
|
+
}),
|
|
6041
|
+
);
|
|
6042
|
+
}
|
|
6043
|
+
return attachDescriptor({
|
|
6044
|
+
error: `MCP tool execution failed: ${safeMcpErrorMessage(callError)}`,
|
|
6045
|
+
mcpLedgerId: safeMcpProperty(ledgerTicket, "ledgerId") || null,
|
|
6046
|
+
});
|
|
6047
|
+
};
|
|
6048
|
+
|
|
6049
|
+
let pendingResult;
|
|
6050
|
+
try {
|
|
6051
|
+
pendingResult = Reflect.apply(mcpClient.callTool, mcpClient, [
|
|
6052
|
+
localToolExecutor.serverName,
|
|
6053
|
+
localToolExecutor.toolName,
|
|
6054
|
+
mcpWireInput,
|
|
6055
|
+
]);
|
|
6056
|
+
} catch (callError) {
|
|
6057
|
+
return await transportFailure(callError);
|
|
6058
|
+
}
|
|
6059
|
+
|
|
6060
|
+
let result;
|
|
6061
|
+
if (
|
|
6062
|
+
pendingResult !== null &&
|
|
6063
|
+
(typeof pendingResult === "object" ||
|
|
6064
|
+
typeof pendingResult === "function")
|
|
6065
|
+
) {
|
|
6066
|
+
if (isProxy(pendingResult)) {
|
|
6067
|
+
return attachDescriptor(
|
|
6068
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6069
|
+
phase: "result",
|
|
6070
|
+
reasonCode: MCP_PROTOCOL_RESULT_INVALID_CODE,
|
|
6071
|
+
}),
|
|
6072
|
+
);
|
|
6073
|
+
}
|
|
6074
|
+
|
|
6075
|
+
let nativePromise;
|
|
6076
|
+
try {
|
|
6077
|
+
nativePromise = pendingResult instanceof Promise;
|
|
6078
|
+
} catch (inspectionError) {
|
|
6079
|
+
return attachDescriptor(
|
|
6080
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6081
|
+
phase: "result",
|
|
6082
|
+
reasonCode: safeMcpErrorCode(
|
|
6083
|
+
invalidMcpProtocolResult(inspectionError),
|
|
6084
|
+
MCP_PROTOCOL_RESULT_INVALID_CODE,
|
|
6085
|
+
),
|
|
6086
|
+
}),
|
|
6087
|
+
);
|
|
6088
|
+
}
|
|
6089
|
+
|
|
6090
|
+
if (nativePromise) {
|
|
6091
|
+
try {
|
|
6092
|
+
result = await pendingResult;
|
|
6093
|
+
} catch (callError) {
|
|
6094
|
+
return await transportFailure(callError);
|
|
6095
|
+
}
|
|
6096
|
+
} else {
|
|
6097
|
+
let then;
|
|
6098
|
+
try {
|
|
6099
|
+
then = Reflect.get(pendingResult, "then", pendingResult);
|
|
6100
|
+
} catch (inspectionError) {
|
|
6101
|
+
return attachDescriptor(
|
|
6102
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6103
|
+
phase: "result",
|
|
6104
|
+
reasonCode: safeMcpErrorCode(
|
|
6105
|
+
invalidMcpProtocolResult(inspectionError),
|
|
6106
|
+
MCP_PROTOCOL_RESULT_INVALID_CODE,
|
|
6107
|
+
),
|
|
6108
|
+
}),
|
|
6109
|
+
);
|
|
6110
|
+
}
|
|
6111
|
+
if (typeof then === "function") {
|
|
6112
|
+
try {
|
|
6113
|
+
result = await new Promise((resolve, reject) => {
|
|
6114
|
+
try {
|
|
6115
|
+
Reflect.apply(then, pendingResult, [resolve, reject]);
|
|
6116
|
+
} catch (thenError) {
|
|
6117
|
+
reject(thenError);
|
|
6118
|
+
}
|
|
6119
|
+
});
|
|
6120
|
+
} catch (thenError) {
|
|
6121
|
+
return attachDescriptor(
|
|
6122
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6123
|
+
phase: "result",
|
|
6124
|
+
reasonCode: safeMcpErrorCode(
|
|
6125
|
+
invalidMcpProtocolResult(thenError),
|
|
6126
|
+
MCP_PROTOCOL_RESULT_INVALID_CODE,
|
|
6127
|
+
),
|
|
6128
|
+
}),
|
|
6129
|
+
);
|
|
6130
|
+
}
|
|
6131
|
+
} else {
|
|
6132
|
+
result = pendingResult;
|
|
6133
|
+
}
|
|
6134
|
+
}
|
|
6135
|
+
} else {
|
|
6136
|
+
result = pendingResult;
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
let protocolError;
|
|
6140
|
+
try {
|
|
6141
|
+
if (
|
|
6142
|
+
result !== null &&
|
|
6143
|
+
(typeof result === "object" || typeof result === "function") &&
|
|
6144
|
+
isProxy(result)
|
|
6145
|
+
) {
|
|
6146
|
+
throw invalidMcpProtocolResult();
|
|
6147
|
+
}
|
|
6148
|
+
protocolError = result?.isError === true;
|
|
6149
|
+
} catch (inspectionError) {
|
|
6150
|
+
return attachDescriptor(
|
|
6151
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6152
|
+
phase: "result",
|
|
6153
|
+
reasonCode: safeMcpErrorCode(
|
|
6154
|
+
invalidMcpProtocolResult(inspectionError),
|
|
6155
|
+
MCP_PROTOCOL_RESULT_INVALID_CODE,
|
|
6156
|
+
),
|
|
6157
|
+
}),
|
|
6158
|
+
);
|
|
6159
|
+
}
|
|
6160
|
+
|
|
6161
|
+
try {
|
|
6162
|
+
await ledgerTicket.settle(
|
|
6163
|
+
protocolError
|
|
6164
|
+
? {
|
|
6165
|
+
status: "failed",
|
|
6166
|
+
output: result,
|
|
6167
|
+
error: new Error("MCP server returned isError=true"),
|
|
6168
|
+
}
|
|
6169
|
+
: { status: "completed", output: result },
|
|
6170
|
+
);
|
|
6171
|
+
} catch (ledgerError) {
|
|
6172
|
+
return attachDescriptor(
|
|
6173
|
+
mcpOutcomeUnknownPayload(ledger, ledgerTicket, {
|
|
6174
|
+
phase: "settled",
|
|
6175
|
+
reasonCode: safeMcpErrorCode(
|
|
6176
|
+
ledgerError,
|
|
6177
|
+
"CC_MCP_LEDGER_SETTLE_FAILED",
|
|
6178
|
+
),
|
|
6179
|
+
}),
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
|
|
6183
|
+
const mcpLedgerId = safeMcpProperty(ledgerTicket, "ledgerId") || null;
|
|
6184
|
+
try {
|
|
6185
|
+
if (result && typeof result === "object") {
|
|
6186
|
+
return attachDescriptor({ ...result, mcpLedgerId });
|
|
6187
|
+
}
|
|
6188
|
+
return attachDescriptor({ result, mcpLedgerId });
|
|
6189
|
+
} catch (projectionError) {
|
|
6190
|
+
return attachDescriptor({
|
|
6191
|
+
error:
|
|
6192
|
+
"MCP tool completed, but its result could not be projected safely; do not retry automatically.",
|
|
6193
|
+
code: MCP_RESULT_PROJECTION_FAILED_CODE,
|
|
6194
|
+
retryable: false,
|
|
6195
|
+
mcpLedgerId,
|
|
6196
|
+
mcpLedgerIncident: {
|
|
6197
|
+
phase: "result",
|
|
6198
|
+
code: safeMcpErrorCode(
|
|
6199
|
+
projectionError,
|
|
6200
|
+
MCP_RESULT_PROJECTION_FAILED_CODE,
|
|
6201
|
+
),
|
|
6202
|
+
},
|
|
6203
|
+
});
|
|
6204
|
+
}
|
|
6205
|
+
} finally {
|
|
6206
|
+
schedulerLease.release();
|
|
6207
|
+
}
|
|
5519
6208
|
}
|
|
5520
6209
|
|
|
5521
6210
|
if (
|
|
@@ -6190,11 +6879,41 @@ function* _drainSubAgentUsage(sink) {
|
|
|
6190
6879
|
provider: u?.provider ?? null,
|
|
6191
6880
|
model: u?.model ?? null,
|
|
6192
6881
|
usage: u?.usage || {},
|
|
6882
|
+
...(u?.source ? { source: u.source } : {}),
|
|
6193
6883
|
attribution: u?.attribution || null,
|
|
6194
6884
|
};
|
|
6195
6885
|
}
|
|
6196
6886
|
}
|
|
6197
6887
|
|
|
6888
|
+
/**
|
|
6889
|
+
* Build the bounded, deterministic parent-to-child handoff used when callers
|
|
6890
|
+
* do not supply explicit context. Keeping the same nine-field schema as
|
|
6891
|
+
* semantic compaction prevents decisions, tests, and unresolved side effects
|
|
6892
|
+
* from disappearing merely because work crosses a sub-agent boundary.
|
|
6893
|
+
*/
|
|
6894
|
+
export function buildSubAgentHandoffContext(messages) {
|
|
6895
|
+
if (!Array.isArray(messages)) return null;
|
|
6896
|
+
const canonicalMessages = projectCanonicalResumeMessages(messages);
|
|
6897
|
+
const hasContent = canonicalMessages.some((message) => {
|
|
6898
|
+
if (!message || !["user", "assistant", "tool"].includes(message.role)) {
|
|
6899
|
+
return false;
|
|
6900
|
+
}
|
|
6901
|
+
if (typeof message.content === "string") {
|
|
6902
|
+
return message.content.trim().length > 0;
|
|
6903
|
+
}
|
|
6904
|
+
return message.content != null;
|
|
6905
|
+
});
|
|
6906
|
+
if (!hasContent) return null;
|
|
6907
|
+
|
|
6908
|
+
const handoff = buildExtractiveHandoff(canonicalMessages, {
|
|
6909
|
+
maxContentChars: 6000,
|
|
6910
|
+
maxItemsPerField: 6,
|
|
6911
|
+
maxItemChars: 500,
|
|
6912
|
+
maxFallbackSourceChars: 16000,
|
|
6913
|
+
});
|
|
6914
|
+
return `[Structured parent handoff v1]\n${formatStructuredHandoff(handoff)}`;
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6198
6917
|
async function _executeSpawnSubAgent(args, ctx) {
|
|
6199
6918
|
// Nesting cap: refuse before any context/registry work.
|
|
6200
6919
|
const currentDepth = ctx.subAgentDepth || 0;
|
|
@@ -6218,8 +6937,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6218
6937
|
hardChildrenCap: ctx.subAgentBudget?.max ?? MAX_SUB_AGENTS_PER_RUN,
|
|
6219
6938
|
});
|
|
6220
6939
|
if (!recur.ok) return { error: `spawn_sub_agent: ${recur.reason}` };
|
|
6221
|
-
} catch {
|
|
6222
|
-
|
|
6940
|
+
} catch (err) {
|
|
6941
|
+
return {
|
|
6942
|
+
error: `spawn_sub_agent: parent contract enforcement failed closed (${err.message}).`,
|
|
6943
|
+
};
|
|
6223
6944
|
}
|
|
6224
6945
|
}
|
|
6225
6946
|
// Breadth cap: a shared counter (one object for the whole tree) bounds the
|
|
@@ -6242,9 +6963,20 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6242
6963
|
role,
|
|
6243
6964
|
task,
|
|
6244
6965
|
context: inheritedContext,
|
|
6966
|
+
contextMode,
|
|
6245
6967
|
tools: explicitTools,
|
|
6246
6968
|
profile: profileName,
|
|
6247
6969
|
} = args;
|
|
6970
|
+
// Compatibility bridge for callers that used the original overloaded
|
|
6971
|
+
// `context: fresh|fork` authority spelling. An explicit `contextMode` always
|
|
6972
|
+
// wins and makes `context` unambiguously prompt text.
|
|
6973
|
+
const legacyContextMode =
|
|
6974
|
+
contextMode == null &&
|
|
6975
|
+
(inheritedContext === "fresh" || inheritedContext === "fork")
|
|
6976
|
+
? inheritedContext
|
|
6977
|
+
: null;
|
|
6978
|
+
if (legacyContextMode) inheritedContext = null;
|
|
6979
|
+
const requestedContextMode = contextMode ?? legacyContextMode;
|
|
6248
6980
|
// Extended sub-agent contract (gap 2026-07-11 P1): per-spawn deny-list,
|
|
6249
6981
|
// iteration cap and worktree isolation — spawn args win over the agent
|
|
6250
6982
|
// file's frontmatter defaults.
|
|
@@ -6311,7 +7043,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6311
7043
|
// effort, context inheritance); this child's contract becomes the ceiling for
|
|
6312
7044
|
// ITS own nested spawns (threaded via SubAgentContext.subAgentContract).
|
|
6313
7045
|
let effectiveContract = null;
|
|
6314
|
-
let
|
|
7046
|
+
let explicitContextMode = null;
|
|
6315
7047
|
let skillAllowlist = null;
|
|
6316
7048
|
// MCP-server + hook allow-lists for child INHERITANCE. Default `[]` = inherit
|
|
6317
7049
|
// NONE, which equals today's behavior (a spawned child gets zero MCP tools /
|
|
@@ -6326,10 +7058,23 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6326
7058
|
// = byte-identical.
|
|
6327
7059
|
let permModeDriven = false;
|
|
6328
7060
|
try {
|
|
6329
|
-
const {
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
7061
|
+
const {
|
|
7062
|
+
resolveSubagentContract,
|
|
7063
|
+
normalizeSubagentContract,
|
|
7064
|
+
assertValidSubagentContract,
|
|
7065
|
+
} = await import("../lib/subagent-contract.js");
|
|
7066
|
+
// `context` predates the authority contract and remains arbitrary prompt
|
|
7067
|
+
// text except for the narrow legacy spelling handled above. Map the
|
|
7068
|
+
// separate contextMode field onto the contract's canonical `context` key.
|
|
7069
|
+
const authorityArgs = { ...args };
|
|
7070
|
+
delete authorityArgs.context;
|
|
7071
|
+
delete authorityArgs.contextMode;
|
|
7072
|
+
if (requestedContextMode != null) {
|
|
7073
|
+
authorityArgs.context = requestedContextMode;
|
|
7074
|
+
}
|
|
7075
|
+
assertValidSubagentContract(authorityArgs);
|
|
7076
|
+
const spawnContract = normalizeSubagentContract(authorityArgs);
|
|
7077
|
+
explicitContextMode = spawnContract.context ?? mdContract?.context ?? null;
|
|
6333
7078
|
effectiveContract = resolveSubagentContract({
|
|
6334
7079
|
parent: ctx.subAgentContract || {},
|
|
6335
7080
|
definition: mdContract,
|
|
@@ -6349,7 +7094,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6349
7094
|
const skillsDriven =
|
|
6350
7095
|
spawnContract.skills != null ||
|
|
6351
7096
|
mdContract?.skills != null ||
|
|
6352
|
-
|
|
7097
|
+
explicitContextMode != null;
|
|
6353
7098
|
skillAllowlist = skillsDriven ? (effectiveContract.skills ?? null) : null;
|
|
6354
7099
|
// MCP/hooks work the OTHER way from skills: their pre-inheritance default is
|
|
6355
7100
|
// "none", so the silent-`fresh`→[] resolution IS the safe current behavior —
|
|
@@ -6357,12 +7102,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6357
7102
|
// into inheriting the corresponding parent capabilities.
|
|
6358
7103
|
mcpAllow = effectiveContract.mcpServers ?? null;
|
|
6359
7104
|
hookAllow = effectiveContract.hooks ?? null;
|
|
6360
|
-
} catch {
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
hookAllow = [];
|
|
6365
|
-
permModeDriven = false; // no gate when resolution fails
|
|
7105
|
+
} catch (err) {
|
|
7106
|
+
return {
|
|
7107
|
+
error: `spawn_sub_agent: authority contract resolution failed closed (${err.message}).`,
|
|
7108
|
+
};
|
|
6366
7109
|
}
|
|
6367
7110
|
|
|
6368
7111
|
// Filter the parent loop's live MCP plumbing + settings hooks down to what the
|
|
@@ -6383,9 +7126,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6383
7126
|
mcpAllow,
|
|
6384
7127
|
);
|
|
6385
7128
|
inheritedHooks = filterInheritedHooks(ctx.settingsHooks || null, hookAllow);
|
|
6386
|
-
} catch {
|
|
6387
|
-
|
|
6388
|
-
|
|
7129
|
+
} catch (err) {
|
|
7130
|
+
return {
|
|
7131
|
+
error: `spawn_sub_agent: capability inheritance failed closed (${err.message}).`,
|
|
7132
|
+
};
|
|
6389
7133
|
}
|
|
6390
7134
|
|
|
6391
7135
|
// Memory INHERITANCE (contract `memory` boolean, tighten-only across depth):
|
|
@@ -6412,8 +7156,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6412
7156
|
available: isGitRepo(ctx.cwd),
|
|
6413
7157
|
});
|
|
6414
7158
|
if (!iso.ok) return { error: `spawn_sub_agent: ${iso.reason}` };
|
|
6415
|
-
} catch {
|
|
6416
|
-
|
|
7159
|
+
} catch (err) {
|
|
7160
|
+
return {
|
|
7161
|
+
error: `spawn_sub_agent: isolation enforcement failed closed (${err.message}).`,
|
|
7162
|
+
};
|
|
6417
7163
|
}
|
|
6418
7164
|
}
|
|
6419
7165
|
|
|
@@ -6430,8 +7176,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6430
7176
|
error: `Unknown sub-agent profile: "${profileName}". Valid: explorer|executor|design`,
|
|
6431
7177
|
};
|
|
6432
7178
|
}
|
|
6433
|
-
} catch (
|
|
6434
|
-
|
|
7179
|
+
} catch (err) {
|
|
7180
|
+
return {
|
|
7181
|
+
error: `spawn_sub_agent: requested profile resolution failed closed (${err.message}).`,
|
|
7182
|
+
};
|
|
6435
7183
|
}
|
|
6436
7184
|
}
|
|
6437
7185
|
|
|
@@ -6453,6 +7201,16 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6453
7201
|
allowedTools = base.filter((t) => !deny.has(t));
|
|
6454
7202
|
}
|
|
6455
7203
|
|
|
7204
|
+
// A child can never regain a tool omitted from the parent's effective schema.
|
|
7205
|
+
// `[]` remains deny-all all the way into the child's execution-time fence.
|
|
7206
|
+
if (Array.isArray(ctx.effectiveAllowedToolNames)) {
|
|
7207
|
+
const parentCeiling = new Set(ctx.effectiveAllowedToolNames);
|
|
7208
|
+
const requested = Array.isArray(allowedTools)
|
|
7209
|
+
? allowedTools
|
|
7210
|
+
: [...parentCeiling];
|
|
7211
|
+
allowedTools = requested.filter((tool) => parentCeiling.has(tool));
|
|
7212
|
+
}
|
|
7213
|
+
|
|
6456
7214
|
// permissionMode enforcement into the child gate. Reuses the runner's own
|
|
6457
7215
|
// resolvers (single-sourced, no drift); best-effort so it never breaks a spawn.
|
|
6458
7216
|
// • `plan` → clamp to the read-only tool set (same rule as resolveEnabledTools),
|
|
@@ -6501,27 +7259,23 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6501
7259
|
defaultPolicy: perm.sessionPolicy,
|
|
6502
7260
|
});
|
|
6503
7261
|
}
|
|
6504
|
-
} catch {
|
|
6505
|
-
|
|
7262
|
+
} catch (err) {
|
|
7263
|
+
return {
|
|
7264
|
+
error: `spawn_sub_agent: permission-mode enforcement failed closed (${err.message}).`,
|
|
7265
|
+
};
|
|
6506
7266
|
}
|
|
6507
7267
|
}
|
|
6508
7268
|
|
|
6509
|
-
//
|
|
6510
|
-
// An explicit `
|
|
6511
|
-
// child starts clean); `fork` / unset keep
|
|
7269
|
+
// Build a structured parent handoff if the caller did not provide explicit
|
|
7270
|
+
// An explicit `contextMode: fresh` contract suppresses this inheritance (the
|
|
7271
|
+
// child starts clean); `fork` / unset keep deterministic auto-inheritance.
|
|
6512
7272
|
let resolvedContext = inheritedContext || null;
|
|
6513
7273
|
if (
|
|
6514
7274
|
!resolvedContext &&
|
|
6515
|
-
|
|
7275
|
+
explicitContextMode !== "fresh" &&
|
|
6516
7276
|
Array.isArray(ctx.parentMessages)
|
|
6517
7277
|
) {
|
|
6518
|
-
|
|
6519
|
-
.filter((m) => m.role === "assistant" && typeof m.content === "string")
|
|
6520
|
-
.slice(-3)
|
|
6521
|
-
.map((m) => m.content.substring(0, 200));
|
|
6522
|
-
if (recentMsgs.length > 0) {
|
|
6523
|
-
resolvedContext = recentMsgs.join("\n---\n");
|
|
6524
|
-
}
|
|
7278
|
+
resolvedContext = buildSubAgentHandoffContext(ctx.parentMessages);
|
|
6525
7279
|
}
|
|
6526
7280
|
|
|
6527
7281
|
// Link child to parent session so registry-scoped queries and
|
|
@@ -6557,8 +7311,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6557
7311
|
? `${resolvedContext}\n---\n${injected}`
|
|
6558
7312
|
: injected;
|
|
6559
7313
|
}
|
|
6560
|
-
} catch (
|
|
6561
|
-
|
|
7314
|
+
} catch (err) {
|
|
7315
|
+
return {
|
|
7316
|
+
error: `spawn_sub_agent: SubagentStart authority hook failed closed (${err.message}).`,
|
|
7317
|
+
};
|
|
6562
7318
|
}
|
|
6563
7319
|
}
|
|
6564
7320
|
|
|
@@ -6592,6 +7348,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6592
7348
|
provider: u?.provider ?? null,
|
|
6593
7349
|
model: u?.model ?? null,
|
|
6594
7350
|
usage: u?.usage || null,
|
|
7351
|
+
...(u?.source ? { source: u.source } : {}),
|
|
6595
7352
|
attribution: {
|
|
6596
7353
|
origin: "subagent",
|
|
6597
7354
|
subagentId: subCtxRef?.id || null,
|
|
@@ -6626,11 +7383,23 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6626
7383
|
if (sparse) wtOpts.sparsePaths = sparse;
|
|
6627
7384
|
if (symlink != null) wtOpts.symlinkDirectories = symlink;
|
|
6628
7385
|
if (Object.keys(wtOpts).length) subWorktreeOptions = wtOpts;
|
|
6629
|
-
} catch {
|
|
6630
|
-
|
|
7386
|
+
} catch (err) {
|
|
7387
|
+
return {
|
|
7388
|
+
error: `spawn_sub_agent: worktree scope resolution failed closed (${err.message}).`,
|
|
7389
|
+
};
|
|
6631
7390
|
}
|
|
6632
7391
|
}
|
|
6633
7392
|
|
|
7393
|
+
// Preserve host deny/policy metadata, but do not implicitly inherit hosted
|
|
7394
|
+
// external definitions. MCP/host capabilities enter the child only through
|
|
7395
|
+
// the explicit contract-filtered plumbing above.
|
|
7396
|
+
const childHostManagedToolPolicy = ctx.hostManagedToolPolicy
|
|
7397
|
+
? { ...ctx.hostManagedToolPolicy, toolDefinitions: [] }
|
|
7398
|
+
: null;
|
|
7399
|
+
// A parent gate is already an authority ceiling. Prefer it over a newly
|
|
7400
|
+
// derived child gate so an explicit child mode can never relax the parent.
|
|
7401
|
+
const effectiveChildApprovalGate = ctx.approvalGate || childApprovalGate;
|
|
7402
|
+
|
|
6634
7403
|
const subCtx = SubAgentContext.create({
|
|
6635
7404
|
role,
|
|
6636
7405
|
task,
|
|
@@ -6639,7 +7408,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6639
7408
|
// and THIS run's id as parent_id on every settings-hook payload it fires.
|
|
6640
7409
|
hookParentTraceId: ctx.hookTraceId || null,
|
|
6641
7410
|
inheritedContext: resolvedContext,
|
|
6642
|
-
allowedTools: allowedTools
|
|
7411
|
+
allowedTools: allowedTools ?? null,
|
|
6643
7412
|
cwd: ctx.cwd,
|
|
6644
7413
|
profile: profile || null,
|
|
6645
7414
|
llmOptions: subLlmOptions,
|
|
@@ -6647,6 +7416,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6647
7416
|
// Same shared counter object so the child's own spawns draw from the run's
|
|
6648
7417
|
// single total-sub-agent pool (breadth cap spans the whole tree).
|
|
6649
7418
|
subAgentBudget: ctx.subAgentBudget || null,
|
|
7419
|
+
...(ctx.sessionBudget ? { sessionBudget: ctx.sessionBudget } : {}),
|
|
6650
7420
|
onUsage,
|
|
6651
7421
|
// Extended contract (gap 2026-07-11): per-agent iteration cap + opt-in
|
|
6652
7422
|
// worktree isolation. undefined keeps the profile/flag defaults intact.
|
|
@@ -6674,6 +7444,10 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6674
7444
|
externalToolDescriptors: inheritedMcp.externalToolDescriptors,
|
|
6675
7445
|
externalToolExecutors: inheritedMcp.externalToolExecutors,
|
|
6676
7446
|
mcpClient: inheritedMcp.mcpClient,
|
|
7447
|
+
...(ctx.mcpCallLedger ? { mcpCallLedger: ctx.mcpCallLedger } : {}),
|
|
7448
|
+
...(ctx.mcpConflictScheduler
|
|
7449
|
+
? { mcpConflictScheduler: ctx.mcpConflictScheduler }
|
|
7450
|
+
: {}),
|
|
6677
7451
|
}
|
|
6678
7452
|
: {}),
|
|
6679
7453
|
...(inheritedHooks ? { settingsHooks: inheritedHooks } : {}),
|
|
@@ -6696,7 +7470,27 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6696
7470
|
// permissionMode ApprovalGate (2026-07-13): a dedicated confirmer-less gate
|
|
6697
7471
|
// seeded with the mode's tier gates the child's run_shell / browser_act;
|
|
6698
7472
|
// absent (ungated) for a plain default spawn = byte-identical.
|
|
6699
|
-
...(
|
|
7473
|
+
...(effectiveChildApprovalGate
|
|
7474
|
+
? { approvalGate: effectiveChildApprovalGate }
|
|
7475
|
+
: {}),
|
|
7476
|
+
// Parent execution authority is inherited as a tighten-only bundle and is
|
|
7477
|
+
// re-enforced by the child's executeTool path on every provider tool call.
|
|
7478
|
+
...(ctx.permissionRules ? { permissionRules: ctx.permissionRules } : {}),
|
|
7479
|
+
...(childHostManagedToolPolicy
|
|
7480
|
+
? { hostManagedToolPolicy: childHostManagedToolPolicy }
|
|
7481
|
+
: {}),
|
|
7482
|
+
...(ctx.planManager ? { planManager: ctx.planManager } : {}),
|
|
7483
|
+
...(ctx.sandbox ? { sandbox: ctx.sandbox } : {}),
|
|
7484
|
+
...(Array.isArray(ctx.additionalDirectories)
|
|
7485
|
+
? { additionalDirectories: [...ctx.additionalDirectories] }
|
|
7486
|
+
: {}),
|
|
7487
|
+
...(ctx.shellPolicyOverrides
|
|
7488
|
+
? { shellPolicyOverrides: ctx.shellPolicyOverrides }
|
|
7489
|
+
: {}),
|
|
7490
|
+
...(ctx.classifyAllShell ? { classifyAllShell: true } : {}),
|
|
7491
|
+
...(ctx.unattendedActionPolicy
|
|
7492
|
+
? { unattendedActionPolicy: ctx.unattendedActionPolicy }
|
|
7493
|
+
: {}),
|
|
6700
7494
|
...(ctx.toolAdmission ? { toolAdmission: ctx.toolAdmission } : {}),
|
|
6701
7495
|
});
|
|
6702
7496
|
subCtxRef = subCtx;
|
|
@@ -6777,7 +7571,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6777
7571
|
emit("sub-agent.started", {
|
|
6778
7572
|
task: subCtx.task,
|
|
6779
7573
|
background: true,
|
|
6780
|
-
allowedTools: allowedTools
|
|
7574
|
+
allowedTools: allowedTools ?? null,
|
|
6781
7575
|
maxIterations: subCtx.maxIterations,
|
|
6782
7576
|
createdAt: subCtx.createdAt,
|
|
6783
7577
|
});
|
|
@@ -6854,7 +7648,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6854
7648
|
|
|
6855
7649
|
emit("sub-agent.started", {
|
|
6856
7650
|
task: subCtx.task,
|
|
6857
|
-
allowedTools: allowedTools
|
|
7651
|
+
allowedTools: allowedTools ?? null,
|
|
6858
7652
|
maxIterations: subCtx.maxIterations,
|
|
6859
7653
|
createdAt: subCtx.createdAt,
|
|
6860
7654
|
});
|
|
@@ -6870,6 +7664,24 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6870
7664
|
}
|
|
6871
7665
|
}
|
|
6872
7666
|
|
|
7667
|
+
if (result?.budgetReason) {
|
|
7668
|
+
emit("sub-agent.failed", {
|
|
7669
|
+
status: subCtx.status,
|
|
7670
|
+
error: result.summary,
|
|
7671
|
+
budgetReason: result.budgetReason,
|
|
7672
|
+
completedAt: subCtx.completedAt,
|
|
7673
|
+
});
|
|
7674
|
+
return {
|
|
7675
|
+
error: result.summary,
|
|
7676
|
+
code: "ERR_SESSION_RESOURCE_BUDGET",
|
|
7677
|
+
budgetReason: result.budgetReason,
|
|
7678
|
+
subAgentId: subCtx.id,
|
|
7679
|
+
role: subCtx.role,
|
|
7680
|
+
parentSessionId,
|
|
7681
|
+
childBinding: subCtx.recoveryBinding(result),
|
|
7682
|
+
};
|
|
7683
|
+
}
|
|
7684
|
+
|
|
6873
7685
|
emit("sub-agent.completed", {
|
|
6874
7686
|
status: subCtx.status,
|
|
6875
7687
|
summary: result.summary,
|
|
@@ -6924,6 +7736,26 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
6924
7736
|
|
|
6925
7737
|
// ─── LLM chat with tools ─────────────────────────────────────────────────
|
|
6926
7738
|
|
|
7739
|
+
function getEffectiveToolDefinitions(options = {}) {
|
|
7740
|
+
const persona = _loadProjectPersona(options.cwd);
|
|
7741
|
+
// Merge every deny source before both schema projection and execution-time
|
|
7742
|
+
// enforcement. Keeping this in one helper prevents those two fences from
|
|
7743
|
+
// drifting apart.
|
|
7744
|
+
const mergedDisabledTools = [
|
|
7745
|
+
...(Array.isArray(persona?.toolsDisabled) ? persona.toolsDisabled : []),
|
|
7746
|
+
...(Array.isArray(options.disabledTools) ? options.disabledTools : []),
|
|
7747
|
+
];
|
|
7748
|
+
return getAgentToolDefinitions({
|
|
7749
|
+
names: options.enabledToolNames,
|
|
7750
|
+
disabledTools: mergedDisabledTools,
|
|
7751
|
+
exactToolNames: options.exactToolNames === true,
|
|
7752
|
+
extraTools: [
|
|
7753
|
+
...(options.hostManagedToolPolicy?.toolDefinitions || []),
|
|
7754
|
+
...(options.extraToolDefinitions || []),
|
|
7755
|
+
],
|
|
7756
|
+
});
|
|
7757
|
+
}
|
|
7758
|
+
|
|
6927
7759
|
/**
|
|
6928
7760
|
* Send a chat completion request with tool definitions.
|
|
6929
7761
|
* Supports 8 providers: ollama, anthropic, openai, deepseek, dashscope, gemini, mistral, volcengine
|
|
@@ -6942,22 +7774,7 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
6942
7774
|
signal,
|
|
6943
7775
|
} = options;
|
|
6944
7776
|
|
|
6945
|
-
const
|
|
6946
|
-
// Merge the project-persona deny-list with any caller-supplied deny-list
|
|
6947
|
-
// (e.g. headless `--disallowed-tools`). Without this merge the caller's
|
|
6948
|
-
// deny-list is silently dropped and the tool stays callable.
|
|
6949
|
-
const mergedDisabledTools = [
|
|
6950
|
-
...(Array.isArray(persona?.toolsDisabled) ? persona.toolsDisabled : []),
|
|
6951
|
-
...(Array.isArray(options.disabledTools) ? options.disabledTools : []),
|
|
6952
|
-
];
|
|
6953
|
-
const tools = getAgentToolDefinitions({
|
|
6954
|
-
names: options.enabledToolNames,
|
|
6955
|
-
disabledTools: mergedDisabledTools,
|
|
6956
|
-
extraTools: [
|
|
6957
|
-
...(options.hostManagedToolPolicy?.toolDefinitions || []),
|
|
6958
|
-
...(options.extraToolDefinitions || []),
|
|
6959
|
-
],
|
|
6960
|
-
});
|
|
7777
|
+
const tools = getEffectiveToolDefinitions(options);
|
|
6961
7778
|
|
|
6962
7779
|
const lastUserMsg = [...rawMessages].reverse().find((m) => m.role === "user");
|
|
6963
7780
|
const messages = ce
|
|
@@ -6986,7 +7803,14 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
6986
7803
|
() =>
|
|
6987
7804
|
_chatOllamaStreaming(
|
|
6988
7805
|
apiUrl,
|
|
6989
|
-
{
|
|
7806
|
+
{
|
|
7807
|
+
model,
|
|
7808
|
+
messages: ollamaMessages,
|
|
7809
|
+
tools,
|
|
7810
|
+
...(options.maxOutputTokens
|
|
7811
|
+
? { options: { num_predict: options.maxOutputTokens } }
|
|
7812
|
+
: {}),
|
|
7813
|
+
},
|
|
6990
7814
|
options.onToken,
|
|
6991
7815
|
signal,
|
|
6992
7816
|
options.onStall,
|
|
@@ -7013,6 +7837,9 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
7013
7837
|
messages: ollamaMessages,
|
|
7014
7838
|
tools,
|
|
7015
7839
|
stream: false,
|
|
7840
|
+
...(options.maxOutputTokens
|
|
7841
|
+
? { options: { num_predict: options.maxOutputTokens } }
|
|
7842
|
+
: {}),
|
|
7016
7843
|
}),
|
|
7017
7844
|
});
|
|
7018
7845
|
if (!response.ok) {
|
|
@@ -7071,7 +7898,9 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
7071
7898
|
);
|
|
7072
7899
|
const body = {
|
|
7073
7900
|
model: effModel,
|
|
7074
|
-
max_tokens:
|
|
7901
|
+
max_tokens: options.maxOutputTokens
|
|
7902
|
+
? Math.min(anthropicMaxTokens || 8192, options.maxOutputTokens)
|
|
7903
|
+
: anthropicMaxTokens || 8192,
|
|
7075
7904
|
// Convert cc's internal OpenAI-shaped history (role:"tool" results,
|
|
7076
7905
|
// assistant tool_calls[]) into Anthropic content blocks. Without this,
|
|
7077
7906
|
// multi-turn tool use 400s on turn 2 (Anthropic rejects role:"tool" and
|
|
@@ -7230,6 +8059,9 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
7230
8059
|
tools,
|
|
7231
8060
|
stream: true,
|
|
7232
8061
|
stream_options: { include_usage: true },
|
|
8062
|
+
...(options.maxOutputTokens
|
|
8063
|
+
? { max_tokens: options.maxOutputTokens }
|
|
8064
|
+
: {}),
|
|
7233
8065
|
},
|
|
7234
8066
|
key,
|
|
7235
8067
|
options.onToken,
|
|
@@ -7262,6 +8094,9 @@ export async function chatWithTools(rawMessages, options) {
|
|
|
7262
8094
|
model: model || defaultModels[provider] || "gpt-4o-mini",
|
|
7263
8095
|
messages,
|
|
7264
8096
|
tools,
|
|
8097
|
+
...(options.maxOutputTokens
|
|
8098
|
+
? { max_tokens: options.maxOutputTokens }
|
|
8099
|
+
: {}),
|
|
7265
8100
|
}),
|
|
7266
8101
|
});
|
|
7267
8102
|
|
|
@@ -8267,9 +9102,45 @@ async function _getAutoCompactor(options) {
|
|
|
8267
9102
|
if (feature("PROMPT_COMPRESSOR")) {
|
|
8268
9103
|
const { PromptCompressor } =
|
|
8269
9104
|
await import("../harness/prompt-compressor.js");
|
|
9105
|
+
const llmQuery =
|
|
9106
|
+
typeof options.compactionLlmQuery === "function"
|
|
9107
|
+
? options.compactionLlmQuery
|
|
9108
|
+
: options.chatFn
|
|
9109
|
+
? null
|
|
9110
|
+
: async (prompt) => {
|
|
9111
|
+
const maxOutputTokens = Math.min(
|
|
9112
|
+
4096,
|
|
9113
|
+
Math.max(
|
|
9114
|
+
256,
|
|
9115
|
+
Number(options.compactionMaxOutputTokens) || 2048,
|
|
9116
|
+
),
|
|
9117
|
+
);
|
|
9118
|
+
const response = await chatWithTools(
|
|
9119
|
+
[{ role: "user", content: prompt }],
|
|
9120
|
+
{
|
|
9121
|
+
...options,
|
|
9122
|
+
contextEngine: null,
|
|
9123
|
+
enabledToolNames: [],
|
|
9124
|
+
extraToolDefinitions: [],
|
|
9125
|
+
hostManagedToolPolicy: null,
|
|
9126
|
+
onToken: undefined,
|
|
9127
|
+
onStall: undefined,
|
|
9128
|
+
onStreamRetry: undefined,
|
|
9129
|
+
maxOutputTokens,
|
|
9130
|
+
},
|
|
9131
|
+
);
|
|
9132
|
+
return {
|
|
9133
|
+
summary: response?.message?.content || "",
|
|
9134
|
+
usage: response?.usage || null,
|
|
9135
|
+
provider: options.provider || null,
|
|
9136
|
+
model: options.model || null,
|
|
9137
|
+
};
|
|
9138
|
+
};
|
|
8270
9139
|
compressor = new PromptCompressor({
|
|
8271
9140
|
model: options.model,
|
|
8272
9141
|
provider: options.provider,
|
|
9142
|
+
llmQuery,
|
|
9143
|
+
summaryInputMaxChars: options.compactionInputMaxChars,
|
|
8273
9144
|
});
|
|
8274
9145
|
}
|
|
8275
9146
|
} catch {
|
|
@@ -8283,6 +9154,21 @@ async function _getAutoCompactor(options) {
|
|
|
8283
9154
|
return compressor;
|
|
8284
9155
|
}
|
|
8285
9156
|
|
|
9157
|
+
function _compactionTokenUsage(stats) {
|
|
9158
|
+
const summaryUsage = stats?.summaryUsage;
|
|
9159
|
+
if (!summaryUsage || typeof summaryUsage !== "object") return null;
|
|
9160
|
+
const tokenCount = (value) => {
|
|
9161
|
+
const number = Number(value);
|
|
9162
|
+
return Number.isFinite(number) && number > 0 ? Math.trunc(number) : 0;
|
|
9163
|
+
};
|
|
9164
|
+
return {
|
|
9165
|
+
input_tokens: tokenCount(summaryUsage.inputTokens),
|
|
9166
|
+
output_tokens: tokenCount(summaryUsage.outputTokens),
|
|
9167
|
+
cache_read_input_tokens: tokenCount(summaryUsage.cacheReadTokens),
|
|
9168
|
+
cache_creation_input_tokens: tokenCount(summaryUsage.cacheCreationTokens),
|
|
9169
|
+
};
|
|
9170
|
+
}
|
|
9171
|
+
|
|
8286
9172
|
/**
|
|
8287
9173
|
* Run `fn` inside an OpenTelemetry span when `options.recorder` is attached,
|
|
8288
9174
|
* else run it bare (zero overhead on the un-instrumented path). `onResult`
|
|
@@ -8390,6 +9276,15 @@ export async function* agentLoop(messages, options) {
|
|
|
8390
9276
|
options.runId ||
|
|
8391
9277
|
`run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8392
9278
|
|
|
9279
|
+
const effectiveAllowedToolNames = Object.freeze(
|
|
9280
|
+
getEffectiveToolDefinitions(options).map((tool) => tool.function.name),
|
|
9281
|
+
);
|
|
9282
|
+
const mcpCallLedger =
|
|
9283
|
+
options.mcpCallLedger ||
|
|
9284
|
+
createMcpCallLedger({ sink: options.mcpLedgerSink || null });
|
|
9285
|
+
const mcpConflictScheduler =
|
|
9286
|
+
options.mcpConflictScheduler || createMcpConflictScheduler();
|
|
9287
|
+
|
|
8393
9288
|
const toolContext = {
|
|
8394
9289
|
hookDb: options.hookDb || null,
|
|
8395
9290
|
skillLoader: options.skillLoader || _defaultSkillLoader,
|
|
@@ -8404,6 +9299,7 @@ export async function* agentLoop(messages, options) {
|
|
|
8404
9299
|
// unrestricted; [] = none; a list restricts run_skill/list_skills to those
|
|
8405
9300
|
// ids/dirNames. Set by the spawn path from the resolved subagent contract.
|
|
8406
9301
|
skillAllowlist: options.skillAllowlist ?? null,
|
|
9302
|
+
effectiveAllowedToolNames,
|
|
8407
9303
|
cwd: options.cwd || process.cwd(),
|
|
8408
9304
|
planManager: options.planManager || null,
|
|
8409
9305
|
sessionId: options.sessionId || null,
|
|
@@ -8425,6 +9321,14 @@ export async function* agentLoop(messages, options) {
|
|
|
8425
9321
|
// contract's mcpServers allow-list). Otherwise consumed only at agentLoop.
|
|
8426
9322
|
extraToolDefinitions: options.extraToolDefinitions || null,
|
|
8427
9323
|
mcpClient: options.mcpClient || null,
|
|
9324
|
+
mcpHostClient: options.mcpHostClient || options.mcpClient || null,
|
|
9325
|
+
mcpCallLedger,
|
|
9326
|
+
mcpConflictScheduler,
|
|
9327
|
+
// A loop-local identity prevents one session's lazy instruction commits
|
|
9328
|
+
// from suppressing first-access delivery in another session at the same
|
|
9329
|
+
// cwd. Hosts may inject a stable identity across resumed turns.
|
|
9330
|
+
subtreeInstructionScope:
|
|
9331
|
+
options.subtreeInstructionScope || options.sessionId || Symbol(runId),
|
|
8428
9332
|
// Parent memory source — a spawn can inherit the parent's hierarchical
|
|
8429
9333
|
// memory DB into the child ONLY when the resolved contract grants memory
|
|
8430
9334
|
// (context:fork from a memory-bearing parent, or explicit memory:true).
|
|
@@ -8482,6 +9386,10 @@ export async function* agentLoop(messages, options) {
|
|
|
8482
9386
|
spawned: 0,
|
|
8483
9387
|
max: MAX_SUB_AGENTS_PER_RUN,
|
|
8484
9388
|
},
|
|
9389
|
+
// Optional local adapter for a host-created SessionResourceBudget. The
|
|
9390
|
+
// CLI root does not create one yet; when supplied, every nested child sees
|
|
9391
|
+
// the same object and cannot reset concurrency/spawn/depth totals.
|
|
9392
|
+
sessionBudget: options.sessionBudget || null,
|
|
8485
9393
|
// This loop's EFFECTIVE subagent contract (set when this loop IS a spawned
|
|
8486
9394
|
// sub-agent). Threaded so a nested spawn_sub_agent sees it as the parent
|
|
8487
9395
|
// ceiling (tighten-only). null at the top level (no ceiling).
|
|
@@ -8584,7 +9492,7 @@ export async function* agentLoop(messages, options) {
|
|
|
8584
9492
|
// line) when provided; otherwise fall back to a clear stderr notice.
|
|
8585
9493
|
const message =
|
|
8586
9494
|
reason === "env-key"
|
|
8587
|
-
? `"${from}" 鉴权失败,已临时切换到不同厂商 "${to}"(请检查 ${from} 的 API key:cc config set llm.apiKey
|
|
9495
|
+
? `"${from}" 鉴权失败,已临时切换到不同厂商 "${to}"(请检查 ${from} 的 API key:cc config set-secret llm.apiKey)。`
|
|
8588
9496
|
: reason === "model-mismatch"
|
|
8589
9497
|
? `模型 "${fromModel}" 不属于 ${from},已改用其默认模型 "${toModel}"(用 cc config set llm.model 设置正确的 ${from} 模型)。`
|
|
8590
9498
|
: `provider 配置与 baseUrl 不一致,已按 baseUrl 切换到 "${to}"。`;
|
|
@@ -8765,6 +9673,16 @@ export async function* agentLoop(messages, options) {
|
|
|
8765
9673
|
preserveToolPairs: true,
|
|
8766
9674
|
...pinOpts,
|
|
8767
9675
|
});
|
|
9676
|
+
const compactionUsage = _compactionTokenUsage(stats);
|
|
9677
|
+
if (stats.degraded === true) {
|
|
9678
|
+
yield {
|
|
9679
|
+
type: "compaction-degraded",
|
|
9680
|
+
runId,
|
|
9681
|
+
reason: stats.degradedReason || "semantic-summary-degraded",
|
|
9682
|
+
summaryMode: stats.summaryMode || "extractive-fallback",
|
|
9683
|
+
stats,
|
|
9684
|
+
};
|
|
9685
|
+
}
|
|
8768
9686
|
if (stats.saved > 0 && compacted.length < messages.length) {
|
|
8769
9687
|
messages.splice(0, messages.length, ...compacted);
|
|
8770
9688
|
// Persist the compaction so a later --resume rebuilds from the
|
|
@@ -8806,10 +9724,31 @@ export async function* agentLoop(messages, options) {
|
|
|
8806
9724
|
});
|
|
8807
9725
|
yield { type: "compaction", stats, runId };
|
|
8808
9726
|
}
|
|
9727
|
+
if (compactionUsage) {
|
|
9728
|
+
// PromptCompressor calls the provider directly, outside the main
|
|
9729
|
+
// chat request. Apply/persist the resulting compaction and surface
|
|
9730
|
+
// any degraded fallback before yielding billable usage: a
|
|
9731
|
+
// headless consumer may stop the generator as soon as its cost cap
|
|
9732
|
+
// is exceeded. Usage still arrives exactly once and before the
|
|
9733
|
+
// next model call, without entering the normal response path.
|
|
9734
|
+
yield {
|
|
9735
|
+
type: "token-usage",
|
|
9736
|
+
provider: stats.summaryProvider || options.provider || null,
|
|
9737
|
+
model: stats.summaryModel || options.model || null,
|
|
9738
|
+
usage: compactionUsage,
|
|
9739
|
+
source: "semantic-compaction",
|
|
9740
|
+
runId,
|
|
9741
|
+
};
|
|
9742
|
+
}
|
|
8809
9743
|
}
|
|
8810
9744
|
} catch (_e) {
|
|
8811
9745
|
if (isAbortError(_e) || signal?.aborted) throw _e;
|
|
8812
|
-
|
|
9746
|
+
yield {
|
|
9747
|
+
type: "compaction-degraded",
|
|
9748
|
+
runId,
|
|
9749
|
+
reason: `compaction_failed:${_e?.message || String(_e)}`,
|
|
9750
|
+
summaryMode: "none",
|
|
9751
|
+
};
|
|
8813
9752
|
}
|
|
8814
9753
|
}
|
|
8815
9754
|
|