chainlesschain 0.162.162 → 0.162.163
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BQ0kgfA1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-Dr1MDANe.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-CZ5vsaXW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-BIBga881.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-vpXHRU3u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-B8exuoub.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-923evRRN.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-sr_sswlQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-CnW-4XKT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-FZCsTZIL.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DFrxB59H.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-_b6WEel8.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-CkbnnZ-K.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-BxM_82zW.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-DYinP45R.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DyXp3sSP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BEr2DhW7.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-B-Vwo1hN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-DbLxKddd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BsQ_LFw9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-lVlXQHu5.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-Beo_foO7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DszHIXED.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-CuJLa2eb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-Cxd1lxhc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-WqPKetx6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CWYtY3ke.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-BnX1iiNV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-DqLMNntm.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-BqYwEYou.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-_vGfmhNl.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-BeJDOkDj.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-Da0EEZDO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-DSuzcCCy.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-BE9YgdA0.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-fC2BpO5S.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-D24Xxqw3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-Bs3oGvS8.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-BFjmAA-3.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-BX_9Bhv0.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-DBcdsVEP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-DDyaF9vC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-BB78L4he.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-INbnWv4j.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-BST-VmTq.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CE7_tS2k.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-hK5L8dOB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-BvkkUoDn.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-u7_EUtkm.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-5Fmt_8Nw.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-B9eanarR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-C0dX5N2f.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-CKTpG-PZ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-Px_w_XQM.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-Di0fnYkM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-vesnWjXt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-exbY6LkT.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-B3O3x70t.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-uRA0DhNz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-CViqdH6K.js} +4 -4
- package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CkXS97WN.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DxW5WYnE.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-CLxmGFse.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Cr5fziXq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-H0JSb3cp.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-DMYWMae7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-CbYyDdF4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-Cpc8JjiK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-CRh77Jda.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal-4D8tNBO5.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D41jzzbG.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-C_jWEy6b.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-Dk1ZUhsy.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-CLVy4l7m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-C-FeqFDj.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-CqVZzyMm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-DX9BW9pu.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-7hQDpWw_.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-6XqUAO0j.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-5R_vkaJG.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-huUPZdC-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-CfJlgisE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-jO8heofr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-DY8Vxqx7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-A48PtXw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-B300RPnT.js} +3 -3
- package/src/assets/web-panel/assets/{index-BbepULSc.js → index-B74OXFxC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-BAON7TzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-BCqxyvqp.js} +1 -1
- package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BHE8OIls.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-BQS-uPOK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-BRXOP09X.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-BYLBPpt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-BfCx4pFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-BliaT02i.js} +1 -1
- package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BmqGerpC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-BsBtRWbA.js} +1 -1
- package/src/assets/web-panel/assets/{index-C229ABfy.js → index-BtEXSoPL.js} +1 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
- package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-Bxax9vQ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-C2iVSFpy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-CA6sO5VD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CA90TZxG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-CLhBh-qE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-CRElRhyS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-Cbe9G0V4.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-ChvHubX3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-Cis_Ip7H.js} +1 -1
- package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-Cjxbu68P.js} +1 -1
- package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-CzKmteNS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-D0pet5Uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D1VLuww0.js} +1 -1
- package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-DXBcvUnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-Db1cLWir.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-DoI2sZm7.js} +1 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
- package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-KRHroryI.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-d1xkbtNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-dKx57io7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-j3O2EEJe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-p85YaaKw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-v3pTaEQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-vXs3B8GJ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-MCB7-raZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-UWDF9swV.js} +1 -1
- package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CsTmgXZU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-fQy6KGLg.js} +1 -1
- package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-woPnEjd1.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-Bs2dZ1gf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-C7EVgPkg.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BH2kYCTN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BFBP2V_e.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-xdDxDYse.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-aAWgPPQf.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-BrOih40N.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-B8lutNSE.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-CffNre7V.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +254 -23
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +239 -1
- package/src/commands/review.js +347 -2
- package/src/data/changelog.json +9 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +5 -2
- package/src/lib/agent-schedule-store.js +289 -10
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/doctor-checkup.js +52 -0
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +46 -12
- package/src/lib/json-schema-validate.js +143 -3
- package/src/lib/plugin-runtime/capability-consent.js +175 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +52 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/settings-hook-events.cjs +13 -0
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +296 -0
- package/src/lib/sub-agent-context.js +74 -5
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/repl/agent-repl.js +136 -0
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +376 -10
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/headless-runner.js +267 -0
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-HwJR-C6B.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
- package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
- package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
|
@@ -1471,6 +1471,8 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1471
1471
|
try {
|
|
1472
1472
|
toolResult = await executeToolInner(name, args, {
|
|
1473
1473
|
skillLoader,
|
|
1474
|
+
// Subagent skill capability INTERSECT — forwarded to run_skill/list_skills.
|
|
1475
|
+
skillAllowlist: context.skillAllowlist ?? null,
|
|
1474
1476
|
cwd,
|
|
1475
1477
|
parentMessages: context.parentMessages,
|
|
1476
1478
|
interaction: context.interaction,
|
|
@@ -1496,6 +1498,13 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1496
1498
|
settingsVerdict,
|
|
1497
1499
|
subAgentDepth: context.subAgentDepth || 0,
|
|
1498
1500
|
subAgentBudget: context.subAgentBudget || null,
|
|
1501
|
+
// Effective contract of THIS loop (parent ceiling for a nested spawn) +
|
|
1502
|
+
// the MCP tool definitions this loop exposes (inheritable by a spawn).
|
|
1503
|
+
subAgentContract: context.subAgentContract || null,
|
|
1504
|
+
extraToolDefinitions: context.extraToolDefinitions || null,
|
|
1505
|
+
// Parent memory source — forwarded so a memory-granted spawn inherits it.
|
|
1506
|
+
memoryDb: context.memoryDb || null,
|
|
1507
|
+
permanentMemory: context.permanentMemory || null,
|
|
1499
1508
|
interactiveApproval: context.interactiveApproval || false,
|
|
1500
1509
|
settingsHooks: context.settingsHooks || null,
|
|
1501
1510
|
signal: context.signal || null,
|
|
@@ -2096,6 +2105,7 @@ async function executeToolInner(
|
|
|
2096
2105
|
args,
|
|
2097
2106
|
{
|
|
2098
2107
|
skillLoader,
|
|
2108
|
+
skillAllowlist = null,
|
|
2099
2109
|
cwd,
|
|
2100
2110
|
parentMessages,
|
|
2101
2111
|
interaction,
|
|
@@ -2103,7 +2113,11 @@ async function executeToolInner(
|
|
|
2103
2113
|
hostManagedToolPolicy,
|
|
2104
2114
|
externalToolDescriptors,
|
|
2105
2115
|
externalToolExecutors,
|
|
2116
|
+
extraToolDefinitions = null,
|
|
2106
2117
|
mcpClient,
|
|
2118
|
+
memoryDb = null,
|
|
2119
|
+
permanentMemory = null,
|
|
2120
|
+
subAgentContract = null,
|
|
2107
2121
|
llmOptions,
|
|
2108
2122
|
shellPolicyOverrides,
|
|
2109
2123
|
classifyAllShell = false,
|
|
@@ -2128,6 +2142,13 @@ async function executeToolInner(
|
|
|
2128
2142
|
: null;
|
|
2129
2143
|
const runtimeDescriptor =
|
|
2130
2144
|
getRuntimeToolDescriptor(name) || localToolDescriptor;
|
|
2145
|
+
// Subagent skill capability INTERSECT: null = unrestricted; a list (possibly
|
|
2146
|
+
// empty) restricts which skills run_skill/list_skills expose in this loop.
|
|
2147
|
+
const _skillAllowlist = Array.isArray(skillAllowlist) ? skillAllowlist : null;
|
|
2148
|
+
const skillAllowed = (s) =>
|
|
2149
|
+
!_skillAllowlist ||
|
|
2150
|
+
_skillAllowlist.includes(s.id) ||
|
|
2151
|
+
_skillAllowlist.includes(s.dirName);
|
|
2131
2152
|
const hostToolPolicies =
|
|
2132
2153
|
hostManagedToolPolicy?.tools || hostManagedToolPolicy?.toolPolicies || null;
|
|
2133
2154
|
const hostToolPolicy =
|
|
@@ -2903,8 +2924,20 @@ async function executeToolInner(
|
|
|
2903
2924
|
// the run_shell gate). A settings `allow` rule (ruleAllowed) pre-
|
|
2904
2925
|
// authorizes; headless leaves interactiveApproval false so its existing
|
|
2905
2926
|
// per-permission-mode behavior is unchanged.
|
|
2927
|
+
//
|
|
2928
|
+
// A spawned sub-agent gets a dedicated CONFIRMER-LESS ApprovalGate for a
|
|
2929
|
+
// strict/trusted mode (2026-07-13). It has no human to prompt, so gate
|
|
2930
|
+
// run_code under it too — decide() auto-denies CONFIRM via no-confirmer,
|
|
2931
|
+
// enforcing the tier on arbitrary code exactly like run_shell/browser_act
|
|
2932
|
+
// (which already gate whenever an approvalGate is present). This is the
|
|
2933
|
+
// child's gate specifically: the main headless + REPL gates ALWAYS carry a
|
|
2934
|
+
// confirmer, so `!hasConfirmer()` leaves them byte-identical.
|
|
2935
|
+
const policyGateNoConfirmer =
|
|
2936
|
+
approvalGate &&
|
|
2937
|
+
typeof approvalGate.hasConfirmer === "function" &&
|
|
2938
|
+
!approvalGate.hasConfirmer();
|
|
2906
2939
|
if (
|
|
2907
|
-
interactiveApproval &&
|
|
2940
|
+
(interactiveApproval || policyGateNoConfirmer) &&
|
|
2908
2941
|
approvalGate &&
|
|
2909
2942
|
!ruleAllowed &&
|
|
2910
2943
|
typeof approvalGate.decide === "function"
|
|
@@ -2947,7 +2980,17 @@ async function executeToolInner(
|
|
|
2947
2980
|
llmOptions,
|
|
2948
2981
|
subAgentDepth,
|
|
2949
2982
|
subAgentBudget,
|
|
2983
|
+
subAgentContract,
|
|
2950
2984
|
settingsHooks,
|
|
2985
|
+
// Parent MCP plumbing — a spawn can inherit these into the child,
|
|
2986
|
+
// filtered by the resolved contract's mcpServers allow-list.
|
|
2987
|
+
mcpClient,
|
|
2988
|
+
externalToolDescriptors,
|
|
2989
|
+
externalToolExecutors,
|
|
2990
|
+
extraToolDefinitions,
|
|
2991
|
+
// Parent memory — inherited into the child only when contract grants it.
|
|
2992
|
+
memoryDb,
|
|
2993
|
+
permanentMemory,
|
|
2951
2994
|
signal,
|
|
2952
2995
|
backgroundSubAgents,
|
|
2953
2996
|
subAgentUsageSink,
|
|
@@ -3158,6 +3201,26 @@ async function executeToolInner(
|
|
|
3158
3201
|
const { parseDuration } = await import("../lib/loop.js");
|
|
3159
3202
|
const store = new AgentScheduleStore();
|
|
3160
3203
|
const action = String(args.action || "").toLowerCase();
|
|
3204
|
+
// Optional lifecycle knobs shared by all create actions: `expires` is a
|
|
3205
|
+
// duration after which an un-fired entry retires (never re-fires); `jitter`
|
|
3206
|
+
// is a per-entry spread so tasks sharing a cron minute fan out instead of
|
|
3207
|
+
// firing as a thundering herd.
|
|
3208
|
+
const expiresInMs = args.expires ? parseDuration(args.expires) : null;
|
|
3209
|
+
const jitterMs = args.jitter ? parseDuration(args.jitter) : 0;
|
|
3210
|
+
// Optional per-task execution policy for wakeup/cron: the scheduled run
|
|
3211
|
+
// can carry its OWN permission mode, worktree isolation and turn budget
|
|
3212
|
+
// instead of inheriting whatever ambient env `cc agenda run` runs in.
|
|
3213
|
+
// The store validates/drops each field; monitors ignore it.
|
|
3214
|
+
const runPolicy = {
|
|
3215
|
+
permissionMode: args.permission_mode || null,
|
|
3216
|
+
worktree: args.worktree === true,
|
|
3217
|
+
maxTurns: args.max_turns ?? null,
|
|
3218
|
+
goalCondition: args.goal_condition || null,
|
|
3219
|
+
maxOuterTurns: args.max_outer_turns ?? null,
|
|
3220
|
+
goalMaxTokens: args.goal_max_tokens ?? null,
|
|
3221
|
+
goalMaxCost: args.goal_max_cost ?? null,
|
|
3222
|
+
goalMaxTime: args.goal_max_time ?? null,
|
|
3223
|
+
};
|
|
3161
3224
|
if (action === "wakeup") {
|
|
3162
3225
|
if (!args.prompt) {
|
|
3163
3226
|
return attachDescriptor({
|
|
@@ -3169,6 +3232,9 @@ async function executeToolInner(
|
|
|
3169
3232
|
prompt: args.prompt,
|
|
3170
3233
|
delayMs,
|
|
3171
3234
|
label: args.label || null,
|
|
3235
|
+
expiresInMs,
|
|
3236
|
+
jitterMs,
|
|
3237
|
+
...runPolicy,
|
|
3172
3238
|
});
|
|
3173
3239
|
return attachDescriptor({
|
|
3174
3240
|
scheduled: entry,
|
|
@@ -3185,6 +3251,9 @@ async function executeToolInner(
|
|
|
3185
3251
|
prompt: args.prompt,
|
|
3186
3252
|
cron: args.cron,
|
|
3187
3253
|
label: args.label || null,
|
|
3254
|
+
expiresInMs,
|
|
3255
|
+
jitterMs,
|
|
3256
|
+
...runPolicy,
|
|
3188
3257
|
});
|
|
3189
3258
|
return attachDescriptor({ scheduled: entry });
|
|
3190
3259
|
}
|
|
@@ -3204,6 +3273,8 @@ async function executeToolInner(
|
|
|
3204
3273
|
notify: args.notify_title ? { title: args.notify_title } : null,
|
|
3205
3274
|
maxChecks: args.max_checks ?? null,
|
|
3206
3275
|
label: args.label || null,
|
|
3276
|
+
expiresInMs,
|
|
3277
|
+
jitterMs,
|
|
3207
3278
|
});
|
|
3208
3279
|
return attachDescriptor({ scheduled: entry });
|
|
3209
3280
|
}
|
|
@@ -3549,11 +3620,12 @@ async function executeToolInner(
|
|
|
3549
3620
|
}
|
|
3550
3621
|
|
|
3551
3622
|
case "run_skill": {
|
|
3552
|
-
const allSkills = skillLoader.getResolvedSkills();
|
|
3623
|
+
const allSkills = skillLoader.getResolvedSkills().filter(skillAllowed);
|
|
3553
3624
|
if (allSkills.length === 0) {
|
|
3554
3625
|
return attachDescriptor({
|
|
3555
|
-
error:
|
|
3556
|
-
"No skills
|
|
3626
|
+
error: _skillAllowlist
|
|
3627
|
+
? "No skills are available to this sub-agent (restricted by its contract's skill allow-list)."
|
|
3628
|
+
: "No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
|
|
3557
3629
|
});
|
|
3558
3630
|
}
|
|
3559
3631
|
const match = allSkills.find(
|
|
@@ -3694,9 +3766,13 @@ async function executeToolInner(
|
|
|
3694
3766
|
}
|
|
3695
3767
|
|
|
3696
3768
|
case "list_skills": {
|
|
3697
|
-
let skills = skillLoader.getResolvedSkills();
|
|
3769
|
+
let skills = skillLoader.getResolvedSkills().filter(skillAllowed);
|
|
3698
3770
|
if (skills.length === 0) {
|
|
3699
|
-
return attachDescriptor({
|
|
3771
|
+
return attachDescriptor({
|
|
3772
|
+
error: _skillAllowlist
|
|
3773
|
+
? "No skills are available to this sub-agent (restricted by its contract's skill allow-list)."
|
|
3774
|
+
: "No skills found.",
|
|
3775
|
+
});
|
|
3700
3776
|
}
|
|
3701
3777
|
if (args.category) {
|
|
3702
3778
|
skills = skills.filter(
|
|
@@ -4282,6 +4358,25 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4282
4358
|
error: `spawn_sub_agent: max nesting depth (${MAX_SUB_AGENT_DEPTH}) reached — complete the task directly instead of delegating further.`,
|
|
4283
4359
|
};
|
|
4284
4360
|
}
|
|
4361
|
+
// Contract-aware recursion tightening: a parent's EFFECTIVE contract can only
|
|
4362
|
+
// LOWER the absolute hard caps (fail-closed, tighten-only). Evaluated before
|
|
4363
|
+
// the breadth counter increments so spawnedCount is this-spawn-exclusive.
|
|
4364
|
+
if (ctx.subAgentContract) {
|
|
4365
|
+
try {
|
|
4366
|
+
const { enforceRecursionLimits } =
|
|
4367
|
+
await import("../lib/subagent-contract.js");
|
|
4368
|
+
const recur = enforceRecursionLimits({
|
|
4369
|
+
depth: currentDepth,
|
|
4370
|
+
spawnedCount: ctx.subAgentBudget?.spawned || 0,
|
|
4371
|
+
contract: ctx.subAgentContract,
|
|
4372
|
+
hardDepthCap: MAX_SUB_AGENT_DEPTH,
|
|
4373
|
+
hardChildrenCap: ctx.subAgentBudget?.max ?? MAX_SUB_AGENTS_PER_RUN,
|
|
4374
|
+
});
|
|
4375
|
+
if (!recur.ok) return { error: `spawn_sub_agent: ${recur.reason}` };
|
|
4376
|
+
} catch {
|
|
4377
|
+
/* contract module unavailable — hard caps above still apply */
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4285
4380
|
// Breadth cap: a shared counter (one object for the whole tree) bounds the
|
|
4286
4381
|
// TOTAL sub-agents a run may spawn, so a wide fan-out can't blow up even
|
|
4287
4382
|
// within the depth limit. Refuse + count BEFORE any work so the increment
|
|
@@ -4322,6 +4417,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4322
4417
|
// Explicit role/tools still win over the agent file's values.
|
|
4323
4418
|
let mdProfile = null;
|
|
4324
4419
|
let mdModel = null;
|
|
4420
|
+
let mdContract = null; // agent-file's normalized subagent contract (definition)
|
|
4325
4421
|
if (args.agent) {
|
|
4326
4422
|
try {
|
|
4327
4423
|
const { getAgent } = await import("../lib/agents.js");
|
|
@@ -4341,6 +4437,7 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4341
4437
|
subIsolation = "worktree";
|
|
4342
4438
|
}
|
|
4343
4439
|
if (md.model) mdModel = md.model;
|
|
4440
|
+
if (md.contract) mdContract = md.contract;
|
|
4344
4441
|
if (md.systemPrompt) {
|
|
4345
4442
|
mdProfile = { name: md.name, systemPrompt: md.systemPrompt };
|
|
4346
4443
|
}
|
|
@@ -4357,6 +4454,118 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4357
4454
|
};
|
|
4358
4455
|
}
|
|
4359
4456
|
|
|
4457
|
+
// Resolve the child's EFFECTIVE subagent contract: spawnArgs > agent-file
|
|
4458
|
+
// definition (mdContract) > parent ceiling (ctx.subAgentContract). The safe,
|
|
4459
|
+
// tighten-only fields are consumed below (isolation fail-closed, budget,
|
|
4460
|
+
// effort, context inheritance); this child's contract becomes the ceiling for
|
|
4461
|
+
// ITS own nested spawns (threaded via SubAgentContext.subAgentContract).
|
|
4462
|
+
let effectiveContract = null;
|
|
4463
|
+
let explicitContext = null;
|
|
4464
|
+
let skillAllowlist = null;
|
|
4465
|
+
// MCP-server + hook allow-lists for child INHERITANCE. Default `[]` = inherit
|
|
4466
|
+
// NONE, which equals today's behavior (a spawned child gets zero MCP tools /
|
|
4467
|
+
// zero Pre-PostToolUse hooks). `context: fork` resolves these to `null` (all);
|
|
4468
|
+
// an explicit list subsets them. See filterInherited* below.
|
|
4469
|
+
let mcpAllow = [];
|
|
4470
|
+
let hookAllow = [];
|
|
4471
|
+
// Was the child's permission mode EXPLICITLY driven — a `permissionMode` in the
|
|
4472
|
+
// spawn args / agent file, OR a NON-"default" parent ceiling from the run
|
|
4473
|
+
// (`--permission-mode manual|acceptEdits|…`)? Only then do we attach an
|
|
4474
|
+
// ApprovalGate to the child (below), so a plain default spawn stays ungated
|
|
4475
|
+
// = byte-identical.
|
|
4476
|
+
let permModeDriven = false;
|
|
4477
|
+
try {
|
|
4478
|
+
const { resolveSubagentContract, normalizeSubagentContract } =
|
|
4479
|
+
await import("../lib/subagent-contract.js");
|
|
4480
|
+
const spawnContract = normalizeSubagentContract(args);
|
|
4481
|
+
explicitContext = spawnContract.context ?? mdContract?.context ?? null;
|
|
4482
|
+
effectiveContract = resolveSubagentContract({
|
|
4483
|
+
parent: ctx.subAgentContract || {},
|
|
4484
|
+
definition: mdContract,
|
|
4485
|
+
spawnArgs: spawnContract,
|
|
4486
|
+
});
|
|
4487
|
+
permModeDriven =
|
|
4488
|
+
spawnContract.permissionMode != null ||
|
|
4489
|
+
mdContract?.permissionMode != null ||
|
|
4490
|
+
(ctx.subAgentContract?.permissionMode != null &&
|
|
4491
|
+
ctx.subAgentContract.permissionMode !== "default");
|
|
4492
|
+
// Skill capability INTERSECT: restrict the child's skills ONLY when it was
|
|
4493
|
+
// explicitly driven — an explicit `skills` list (spawn args or agent file)
|
|
4494
|
+
// OR an explicit `context` mode. A fully-defaulted spawn stays unrestricted
|
|
4495
|
+
// (null) so the silent-`fresh`→[] default can never strip ALL skills from a
|
|
4496
|
+
// plain sub-agent. `effectiveContract.skills` already encodes the intersect
|
|
4497
|
+
// against the parent ceiling (tighten-only across depth).
|
|
4498
|
+
const skillsDriven =
|
|
4499
|
+
spawnContract.skills != null ||
|
|
4500
|
+
mdContract?.skills != null ||
|
|
4501
|
+
explicitContext != null;
|
|
4502
|
+
skillAllowlist = skillsDriven ? (effectiveContract.skills ?? null) : null;
|
|
4503
|
+
// MCP/hooks work the OTHER way from skills: their pre-inheritance default is
|
|
4504
|
+
// "none", so the silent-`fresh`→[] resolution IS the safe current behavior —
|
|
4505
|
+
// no explicit-driven guard needed. A list (or `null` on fork) opts the child
|
|
4506
|
+
// into inheriting the corresponding parent capabilities.
|
|
4507
|
+
mcpAllow = effectiveContract.mcpServers ?? null;
|
|
4508
|
+
hookAllow = effectiveContract.hooks ?? null;
|
|
4509
|
+
} catch {
|
|
4510
|
+
effectiveContract = null; // contract resolution is best-effort
|
|
4511
|
+
skillAllowlist = null;
|
|
4512
|
+
mcpAllow = []; // inherit no MCP / hooks when resolution fails
|
|
4513
|
+
hookAllow = [];
|
|
4514
|
+
permModeDriven = false; // no gate when resolution fails
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
// Filter the parent loop's live MCP plumbing + settings hooks down to what the
|
|
4518
|
+
// child may inherit. Default `[]` → null → the spawn passes nothing, so a plain
|
|
4519
|
+
// sub-agent is byte-identical to before.
|
|
4520
|
+
let inheritedMcp = null;
|
|
4521
|
+
let inheritedHooks = null;
|
|
4522
|
+
try {
|
|
4523
|
+
const { filterInheritedMcp, filterInheritedHooks } =
|
|
4524
|
+
await import("../lib/subagent-inheritance.js");
|
|
4525
|
+
inheritedMcp = filterInheritedMcp(
|
|
4526
|
+
{
|
|
4527
|
+
extraToolDefinitions: ctx.extraToolDefinitions,
|
|
4528
|
+
externalToolDescriptors: ctx.externalToolDescriptors,
|
|
4529
|
+
externalToolExecutors: ctx.externalToolExecutors,
|
|
4530
|
+
mcpClient: ctx.mcpClient,
|
|
4531
|
+
},
|
|
4532
|
+
mcpAllow,
|
|
4533
|
+
);
|
|
4534
|
+
inheritedHooks = filterInheritedHooks(ctx.settingsHooks || null, hookAllow);
|
|
4535
|
+
} catch {
|
|
4536
|
+
inheritedMcp = null; // inheritance is best-effort; never break the spawn
|
|
4537
|
+
inheritedHooks = null;
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
// Memory INHERITANCE (contract `memory` boolean, tighten-only across depth):
|
|
4541
|
+
// grant the child the parent's hierarchical-memory DB ONLY when the resolved
|
|
4542
|
+
// contract says memory:true (explicit, or context:fork from a memory-bearing
|
|
4543
|
+
// parent). Default (silent-`fresh`→memory:false) → no db + memoryEnabled:false
|
|
4544
|
+
// → no recall = today's behavior. `effectiveContract.memory` already encodes
|
|
4545
|
+
// the intersect (a parent that denied memory can never re-grant downstream).
|
|
4546
|
+
const memoryGranted = effectiveContract?.memory === true;
|
|
4547
|
+
const inheritedMemory =
|
|
4548
|
+
memoryGranted && ctx.memoryDb
|
|
4549
|
+
? { db: ctx.memoryDb, permanentMemory: ctx.permanentMemory || null }
|
|
4550
|
+
: null;
|
|
4551
|
+
|
|
4552
|
+
// Worktree isolation must FAIL CLOSED: if requested but the cwd is not a git
|
|
4553
|
+
// repo, refuse the spawn instead of silently running in the parent checkout.
|
|
4554
|
+
if (subIsolation === "worktree") {
|
|
4555
|
+
try {
|
|
4556
|
+
const { resolveIsolationFailClosed } =
|
|
4557
|
+
await import("../lib/subagent-contract.js");
|
|
4558
|
+
const { isGitRepo } = await import("../lib/git-integration.js");
|
|
4559
|
+
const iso = resolveIsolationFailClosed({
|
|
4560
|
+
requested: "worktree",
|
|
4561
|
+
available: isGitRepo(ctx.cwd),
|
|
4562
|
+
});
|
|
4563
|
+
if (!iso.ok) return { error: `spawn_sub_agent: ${iso.reason}` };
|
|
4564
|
+
} catch {
|
|
4565
|
+
/* helper unavailable — sub-agent-context.js still fails closed at run() */
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4360
4569
|
// Phase 3: resolve declarative profile if requested. Explicit tools/context
|
|
4361
4570
|
// override profile defaults; missing fields fall back to the profile.
|
|
4362
4571
|
let profile = null;
|
|
@@ -4393,9 +4602,68 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4393
4602
|
allowedTools = base.filter((t) => !deny.has(t));
|
|
4394
4603
|
}
|
|
4395
4604
|
|
|
4396
|
-
//
|
|
4605
|
+
// permissionMode enforcement into the child gate. Reuses the runner's own
|
|
4606
|
+
// resolvers (single-sourced, no drift); best-effort so it never breaks a spawn.
|
|
4607
|
+
// • `plan` → clamp to the read-only tool set (same rule as resolveEnabledTools),
|
|
4608
|
+
// so a plan sub-agent physically cannot mutate anything.
|
|
4609
|
+
// • confirmer threading → hand the child the mode's NON-interactive confirmer
|
|
4610
|
+
// as `permissionConfirm` (governs the ask / sensitive-file / destructive-git
|
|
4611
|
+
// gates) ONLY when it is the autopilot (bypassPermissions) ALLOW confirmer.
|
|
4612
|
+
// Every other mode's headless child already denies implicitly (no confirmer),
|
|
4613
|
+
// so we leave it unset to stay byte-identical — crucially preserving the
|
|
4614
|
+
// parallel-read fast-path + IDE-diff branch, both of which key off whether a
|
|
4615
|
+
// permissionConfirm is present.
|
|
4616
|
+
// • ApprovalGate sessionPolicy → attach the child its OWN ApprovalGate seeded
|
|
4617
|
+
// with the mode's tier (perm.sessionPolicy) and NO confirmer, so run_shell /
|
|
4618
|
+
// browser_act are gated per tier headlessly: strict denies MED/HIGH, trusted
|
|
4619
|
+
// denies only HIGH, autopilot allows all (CONFIRM→no-confirmer→DENY). A fresh
|
|
4620
|
+
// per-child gate (never the shared singleton) means zero interference with
|
|
4621
|
+
// the parent's global confirmer. Attached ONLY when the mode was explicitly
|
|
4622
|
+
// driven (permModeDriven) AND the tier actually gates (non-autopilot) — a
|
|
4623
|
+
// plain default spawn stays ungated = byte-identical; autopilot's gate would
|
|
4624
|
+
// be a pure no-op so it is skipped.
|
|
4625
|
+
// `tightenPermissionMode` already stops a child EXCEEDING the parent's mode; a
|
|
4626
|
+
// fully-defaulted spawn (→ "default") touches neither tools nor confirmer nor gate.
|
|
4627
|
+
let childPermissionConfirm = null;
|
|
4628
|
+
let childApprovalGate = null;
|
|
4629
|
+
if (effectiveContract?.permissionMode) {
|
|
4630
|
+
try {
|
|
4631
|
+
const { resolvePermissionMode, resolveEnabledTools } =
|
|
4632
|
+
await import("./headless-runner.js");
|
|
4633
|
+
const perm = resolvePermissionMode(effectiveContract.permissionMode);
|
|
4634
|
+
if (perm.readOnly) {
|
|
4635
|
+
allowedTools = resolveEnabledTools({ allowedTools, readOnly: true });
|
|
4636
|
+
}
|
|
4637
|
+
// autopilot ⟺ bypassPermissions — the only mode whose headless confirmer
|
|
4638
|
+
// ALLOWS. Threading a deny confirmer would needlessly disable the child's
|
|
4639
|
+
// parallel-read fast-path, so restrict to the allow case.
|
|
4640
|
+
if (
|
|
4641
|
+
perm.sessionPolicy === "autopilot" &&
|
|
4642
|
+
typeof perm.confirmer === "function"
|
|
4643
|
+
) {
|
|
4644
|
+
childPermissionConfirm = perm.confirmer;
|
|
4645
|
+
}
|
|
4646
|
+
// Dedicated, confirmer-less child ApprovalGate for the strict/trusted tiers.
|
|
4647
|
+
if (permModeDriven && perm.sessionPolicy !== "autopilot") {
|
|
4648
|
+
const { ApprovalGate } = await import("@chainlesschain/session-core");
|
|
4649
|
+
childApprovalGate = new ApprovalGate({
|
|
4650
|
+
defaultPolicy: perm.sessionPolicy,
|
|
4651
|
+
});
|
|
4652
|
+
}
|
|
4653
|
+
} catch {
|
|
4654
|
+
// Enforcement is best-effort — never break the spawn.
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
|
|
4658
|
+
// Auto-condense parent context if caller didn't provide explicit context.
|
|
4659
|
+
// An explicit `context: fresh` contract suppresses this inheritance (the
|
|
4660
|
+
// child starts clean); `fork` / unset keep the existing auto-condense.
|
|
4397
4661
|
let resolvedContext = inheritedContext || null;
|
|
4398
|
-
if (
|
|
4662
|
+
if (
|
|
4663
|
+
!resolvedContext &&
|
|
4664
|
+
explicitContext !== "fresh" &&
|
|
4665
|
+
Array.isArray(ctx.parentMessages)
|
|
4666
|
+
) {
|
|
4399
4667
|
const recentMsgs = ctx.parentMessages
|
|
4400
4668
|
.filter((m) => m.role === "assistant" && typeof m.content === "string")
|
|
4401
4669
|
.slice(-3)
|
|
@@ -4449,6 +4717,9 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4449
4717
|
const subLlmOptions = {
|
|
4450
4718
|
...parentLlm,
|
|
4451
4719
|
model: mdModel || parentLlm.model || undefined,
|
|
4720
|
+
// Contract `effort` is a compute hint (reasoning level), not authority —
|
|
4721
|
+
// forwarded to the child loop; harmless if the provider ignores it.
|
|
4722
|
+
...(effectiveContract?.effort ? { effort: effectiveContract.effort } : {}),
|
|
4452
4723
|
};
|
|
4453
4724
|
|
|
4454
4725
|
// 用量归因: forward the child's real token usage into the parent run's sink
|
|
@@ -4503,6 +4774,48 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
4503
4774
|
// worktree isolation. undefined keeps the profile/flag defaults intact.
|
|
4504
4775
|
...(subMaxTurns ? { maxIterations: subMaxTurns } : {}),
|
|
4505
4776
|
...(subIsolation === "worktree" ? { useWorktree: true } : {}),
|
|
4777
|
+
// Resolved contract (gap 2026-07-12): cap the child's token budget and hand
|
|
4778
|
+
// it its EFFECTIVE contract so its OWN spawns inherit this ceiling.
|
|
4779
|
+
...(effectiveContract?.budget?.tokens
|
|
4780
|
+
? { tokenBudget: effectiveContract.budget.tokens }
|
|
4781
|
+
: {}),
|
|
4782
|
+
...(effectiveContract ? { subAgentContract: effectiveContract } : {}),
|
|
4783
|
+
// Skill capability INTERSECT (2026-07-12): a non-null allow-list (possibly
|
|
4784
|
+
// empty) restricts run_skill/list_skills in the child loop.
|
|
4785
|
+
...(skillAllowlist != null ? { skillAllowlist } : {}),
|
|
4786
|
+
// MCP + hook capability INHERITANCE (2026-07-12): only non-null when the
|
|
4787
|
+
// contract opted the child into inheriting (context:fork or an explicit
|
|
4788
|
+
// list). filterInherited* already subset these to the allowed servers /
|
|
4789
|
+
// matchers. Absent → child inherits neither (today's default).
|
|
4790
|
+
...(inheritedMcp
|
|
4791
|
+
? {
|
|
4792
|
+
extraToolDefinitions: inheritedMcp.extraToolDefinitions,
|
|
4793
|
+
externalToolDescriptors: inheritedMcp.externalToolDescriptors,
|
|
4794
|
+
externalToolExecutors: inheritedMcp.externalToolExecutors,
|
|
4795
|
+
mcpClient: inheritedMcp.mcpClient,
|
|
4796
|
+
}
|
|
4797
|
+
: {}),
|
|
4798
|
+
...(inheritedHooks ? { settingsHooks: inheritedHooks } : {}),
|
|
4799
|
+
// Memory INHERITANCE (2026-07-12): grant the child the parent's memory DB
|
|
4800
|
+
// (namespaced by the child's task id) only when the contract allows; else
|
|
4801
|
+
// memoryEnabled:false hard-suppresses recall even if a db leaks through.
|
|
4802
|
+
...(inheritedMemory
|
|
4803
|
+
? {
|
|
4804
|
+
db: inheritedMemory.db,
|
|
4805
|
+
permanentMemory: inheritedMemory.permanentMemory,
|
|
4806
|
+
memoryEnabled: true,
|
|
4807
|
+
}
|
|
4808
|
+
: { memoryEnabled: false }),
|
|
4809
|
+
// permissionMode confirmer (2026-07-12): only set for the autopilot (bypass)
|
|
4810
|
+
// ALLOW confirmer; absent for every other mode so the child stays
|
|
4811
|
+
// implicitly-deny + byte-identical (parallel-read fast-path preserved).
|
|
4812
|
+
...(childPermissionConfirm
|
|
4813
|
+
? { permissionConfirm: childPermissionConfirm }
|
|
4814
|
+
: {}),
|
|
4815
|
+
// permissionMode ApprovalGate (2026-07-13): a dedicated confirmer-less gate
|
|
4816
|
+
// seeded with the mode's tier gates the child's run_shell / browser_act;
|
|
4817
|
+
// absent (ungated) for a plain default spawn = byte-identical.
|
|
4818
|
+
...(childApprovalGate ? { approvalGate: childApprovalGate } : {}),
|
|
4506
4819
|
});
|
|
4507
4820
|
subCtxRef = subCtx;
|
|
4508
4821
|
|
|
@@ -6042,6 +6355,30 @@ function _usageTokens(usage) {
|
|
|
6042
6355
|
return { input, output, cacheRead, cacheWrite };
|
|
6043
6356
|
}
|
|
6044
6357
|
|
|
6358
|
+
/**
|
|
6359
|
+
* Best-effort text of the first user prompt in a message list, for the
|
|
6360
|
+
* `--otlp-content` span attribute. Handles both string content and the
|
|
6361
|
+
* multimodal `[{type:"text",text}, …]` shape; returns "" when none is found.
|
|
6362
|
+
* Not exported (no shim-parity impact) — only the telemetry seam uses it.
|
|
6363
|
+
*/
|
|
6364
|
+
function extractInitialPromptText(messages) {
|
|
6365
|
+
if (!Array.isArray(messages)) return "";
|
|
6366
|
+
for (const msg of messages) {
|
|
6367
|
+
if (!msg || msg.role !== "user") continue;
|
|
6368
|
+
const content = msg.content;
|
|
6369
|
+
if (typeof content === "string") return content;
|
|
6370
|
+
if (Array.isArray(content)) {
|
|
6371
|
+
const text = content
|
|
6372
|
+
.filter((part) => part && part.type === "text" && part.text)
|
|
6373
|
+
.map((part) => part.text)
|
|
6374
|
+
.join("\n")
|
|
6375
|
+
.trim();
|
|
6376
|
+
if (text) return text;
|
|
6377
|
+
}
|
|
6378
|
+
}
|
|
6379
|
+
return "";
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6045
6382
|
export async function* agentLoop(messages, options) {
|
|
6046
6383
|
// Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
|
|
6047
6384
|
// When options.iterationBudget is provided (e.g. from parent agent),
|
|
@@ -6056,13 +6393,28 @@ export async function* agentLoop(messages, options) {
|
|
|
6056
6393
|
const toolContext = {
|
|
6057
6394
|
hookDb: options.hookDb || null,
|
|
6058
6395
|
skillLoader: options.skillLoader || _defaultSkillLoader,
|
|
6396
|
+
// Contract-driven skill allow-list (subagent capability INTERSECT). null =
|
|
6397
|
+
// unrestricted; [] = none; a list restricts run_skill/list_skills to those
|
|
6398
|
+
// ids/dirNames. Set by the spawn path from the resolved subagent contract.
|
|
6399
|
+
skillAllowlist: options.skillAllowlist ?? null,
|
|
6059
6400
|
cwd: options.cwd || process.cwd(),
|
|
6060
6401
|
planManager: options.planManager || null,
|
|
6061
6402
|
sessionId: options.sessionId || null,
|
|
6062
6403
|
hostManagedToolPolicy: options.hostManagedToolPolicy || null,
|
|
6063
6404
|
externalToolDescriptors: options.externalToolDescriptors || null,
|
|
6064
6405
|
externalToolExecutors: options.externalToolExecutors || null,
|
|
6406
|
+
// MCP tool DEFINITIONS the LLM sees (mcp__server__tool). Threaded here so a
|
|
6407
|
+
// spawn can inherit the parent's MCP tools into the child (filtered by the
|
|
6408
|
+
// contract's mcpServers allow-list). Otherwise consumed only at agentLoop.
|
|
6409
|
+
extraToolDefinitions: options.extraToolDefinitions || null,
|
|
6065
6410
|
mcpClient: options.mcpClient || null,
|
|
6411
|
+
// Parent memory source — a spawn can inherit the parent's hierarchical
|
|
6412
|
+
// memory DB into the child ONLY when the resolved contract grants memory
|
|
6413
|
+
// (context:fork from a memory-bearing parent, or explicit memory:true).
|
|
6414
|
+
// Read off this loop's context engine (REPL sets one with db+permanentMemory).
|
|
6415
|
+
memoryDb: options.contextEngine?.db ?? options.db ?? null,
|
|
6416
|
+
permanentMemory:
|
|
6417
|
+
options.contextEngine?.permanentMemory ?? options.permanentMemory ?? null,
|
|
6066
6418
|
// Parent LLM config — forwarded to spawn_sub_agent so a delegated subagent
|
|
6067
6419
|
// inherits the provider/key and can override just the model (cc agents `model:`).
|
|
6068
6420
|
llmOptions: {
|
|
@@ -6106,6 +6458,10 @@ export async function* agentLoop(messages, options) {
|
|
|
6106
6458
|
spawned: 0,
|
|
6107
6459
|
max: MAX_SUB_AGENTS_PER_RUN,
|
|
6108
6460
|
},
|
|
6461
|
+
// This loop's EFFECTIVE subagent contract (set when this loop IS a spawned
|
|
6462
|
+
// sub-agent). Threaded so a nested spawn_sub_agent sees it as the parent
|
|
6463
|
+
// ceiling (tighten-only). null at the top level (no ceiling).
|
|
6464
|
+
subAgentContract: options.subAgentContract || null,
|
|
6109
6465
|
// Abort signal — forwarded to background sub-agents so cancelling the
|
|
6110
6466
|
// parent run also cancels children still running detached.
|
|
6111
6467
|
signal,
|
|
@@ -6238,13 +6594,23 @@ export async function* agentLoop(messages, options) {
|
|
|
6238
6594
|
// Normalize the unified id set (P2 observability): every id is charset-
|
|
6239
6595
|
// sanitized + length-capped, and only these allow-listed keys are stamped,
|
|
6240
6596
|
// so span cardinality stays bounded and no content leaks in as an id.
|
|
6241
|
-
const
|
|
6597
|
+
const telemetryCtx = {
|
|
6242
6598
|
"workflow.run_id": runId,
|
|
6243
6599
|
"workflow.name": options.workflowName || undefined,
|
|
6244
6600
|
"session.id": options.sessionId || undefined,
|
|
6245
6601
|
"agent.id": options.agentId || undefined,
|
|
6246
6602
|
"parent_agent.id": options.parentAgentId || undefined,
|
|
6247
|
-
}
|
|
6603
|
+
};
|
|
6604
|
+
// --otlp-content opt-in (P2): only when explicitly enabled do we feed the
|
|
6605
|
+
// initial prompt through the redactor with includeContent — so a debugger
|
|
6606
|
+
// can see which prompt a span belongs to. Default OFF omits the field
|
|
6607
|
+
// entirely (not just redacts it), keeping default OTLP output byte-identical.
|
|
6608
|
+
const includeContent = options.otlpIncludeContent === true;
|
|
6609
|
+
if (includeContent) {
|
|
6610
|
+
const promptText = extractInitialPromptText(messages);
|
|
6611
|
+
if (promptText) telemetryCtx.prompt = promptText;
|
|
6612
|
+
}
|
|
6613
|
+
const idAttrs = buildTelemetryAttributes(telemetryCtx, { includeContent });
|
|
6248
6614
|
for (const [k, v] of Object.entries(idAttrs)) {
|
|
6249
6615
|
recorder.setDefaultAttribute(k, v);
|
|
6250
6616
|
}
|
|
@@ -614,7 +614,7 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
|
|
|
614
614
|
type: "string",
|
|
615
615
|
enum: ["worktree"],
|
|
616
616
|
description:
|
|
617
|
-
|
|
617
|
+
'Optional isolation mode: "worktree" runs the sub-agent in a fresh git worktree so its file edits land on an isolated branch (diff/merge summary comes back with the result).',
|
|
618
618
|
},
|
|
619
619
|
},
|
|
620
620
|
required: ["task"],
|
|
@@ -959,6 +959,65 @@ const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
|
|
|
959
959
|
type: "string",
|
|
960
960
|
description: "Optional human label for list/cancel",
|
|
961
961
|
},
|
|
962
|
+
expires: {
|
|
963
|
+
type: "string",
|
|
964
|
+
description:
|
|
965
|
+
"For wakeup/cron/monitor: retire the entry after this long if it hasn't fired (e.g. 2h, 7d); it then never fires",
|
|
966
|
+
},
|
|
967
|
+
jitter: {
|
|
968
|
+
type: "string",
|
|
969
|
+
description:
|
|
970
|
+
"For wakeup/cron/monitor: a random-but-stable spread (e.g. 30s) so entries sharing a schedule fan out instead of firing all at once",
|
|
971
|
+
},
|
|
972
|
+
permission_mode: {
|
|
973
|
+
type: "string",
|
|
974
|
+
enum: [
|
|
975
|
+
"plan",
|
|
976
|
+
"manual",
|
|
977
|
+
"default",
|
|
978
|
+
"acceptEdits",
|
|
979
|
+
"auto",
|
|
980
|
+
"dontAsk",
|
|
981
|
+
"bypassPermissions",
|
|
982
|
+
],
|
|
983
|
+
description:
|
|
984
|
+
"For wakeup/cron: run the fired `cc agent` with this permission mode instead of the ambient one (e.g. `plan` for a read-only report, `manual` to gate every action)",
|
|
985
|
+
},
|
|
986
|
+
worktree: {
|
|
987
|
+
type: "boolean",
|
|
988
|
+
description:
|
|
989
|
+
"For wakeup/cron: run the fired `cc agent` in a fresh git worktree so its changes stay isolated",
|
|
990
|
+
},
|
|
991
|
+
max_turns: {
|
|
992
|
+
type: "number",
|
|
993
|
+
description:
|
|
994
|
+
"For wakeup/cron: cap the fired agent's loop iterations (a per-task budget)",
|
|
995
|
+
},
|
|
996
|
+
goal_condition: {
|
|
997
|
+
type: "string",
|
|
998
|
+
description:
|
|
999
|
+
"For wakeup/cron: run the fired agent to a completion condition, re-driving outer turns until met. Prefix exit-zero:/file-exists:/contains:/regex: (deterministic) or model:<desc> (model-judged). Invalid specs are dropped.",
|
|
1000
|
+
},
|
|
1001
|
+
max_outer_turns: {
|
|
1002
|
+
type: "number",
|
|
1003
|
+
description:
|
|
1004
|
+
"For wakeup/cron with goal_condition: max outer re-drive turns (default 10, hard cap 100)",
|
|
1005
|
+
},
|
|
1006
|
+
goal_max_tokens: {
|
|
1007
|
+
type: "number",
|
|
1008
|
+
description:
|
|
1009
|
+
"For wakeup/cron with goal_condition: token budget across outer turns",
|
|
1010
|
+
},
|
|
1011
|
+
goal_max_cost: {
|
|
1012
|
+
type: "number",
|
|
1013
|
+
description:
|
|
1014
|
+
"For wakeup/cron with goal_condition: cost budget (USD) across outer turns",
|
|
1015
|
+
},
|
|
1016
|
+
goal_max_time: {
|
|
1017
|
+
type: "number",
|
|
1018
|
+
description:
|
|
1019
|
+
"For wakeup/cron with goal_condition: wall-clock budget (ms) across outer turns",
|
|
1020
|
+
},
|
|
962
1021
|
id: {
|
|
963
1022
|
type: "string",
|
|
964
1023
|
description: "For cancel: the schedule entry id to remove",
|