chainlesschain 0.162.162 → 0.162.164
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 +3 -1
- package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
- package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +320 -32
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +456 -1
- package/src/commands/review.js +363 -2
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +10 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +55 -2
- package/src/gateways/ws/ws-agent-handler.js +50 -0
- package/src/harness/jsonl-session-store.js +60 -0
- package/src/harness/worktree-isolator.js +140 -5
- package/src/lib/agent-schedule-store.js +318 -10
- package/src/lib/agent-team/team-worktree.js +37 -2
- package/src/lib/agent-tool-admission.js +125 -0
- package/src/lib/app-preview.js +360 -0
- package/src/lib/async-hook-supervisor.cjs +54 -0
- package/src/lib/capability-manifest.js +319 -0
- package/src/lib/capability-negotiation.js +23 -12
- 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/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +275 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -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/jsonl-session-store.js +1 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
- package/src/lib/lsp/lsp-manager.js +56 -0
- package/src/lib/monitor-event.js +290 -0
- package/src/lib/operation-fingerprint.js +217 -0
- package/src/lib/plugin-runtime/bin.js +37 -0
- package/src/lib/plugin-runtime/capabilities.js +19 -0
- package/src/lib/plugin-runtime/capability-consent.js +203 -0
- package/src/lib/plugin-runtime/hooks.js +37 -0
- package/src/lib/plugin-runtime/lsp.js +38 -0
- package/src/lib/plugin-runtime/mcp.js +40 -0
- package/src/lib/plugin-runtime/monitors.js +37 -0
- package/src/lib/plugin-runtime/plugin-options.js +151 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/pr-automation-policy.js +224 -0
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +153 -0
- package/src/lib/review-comment-anchor.js +241 -0
- package/src/lib/review-pipeline.js +46 -7
- package/src/lib/runtime-checkup.js +221 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- 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 +401 -0
- package/src/lib/skill-loader.js +32 -0
- package/src/lib/skill-path-scope.js +63 -0
- package/src/lib/sub-agent-context.js +110 -9
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/unattended-action-policy.js +182 -0
- package/src/lib/worktree-cleanup-safety.js +138 -0
- package/src/lib/worktree-isolator.js +8 -0
- package/src/lib/worktree-sparse.js +131 -0
- package/src/repl/agent-repl.js +280 -30
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +565 -21
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +393 -1
- package/src/runtime/headless-stream.js +60 -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
|
@@ -45,8 +45,10 @@ export class SubAgentContext {
|
|
|
45
45
|
* @param {number} [options.maxIterations] - Iteration limit (fallback if no budget)
|
|
46
46
|
* @param {import('./iteration-budget.js').IterationBudget} [options.iterationBudget] - Shared iteration budget (takes priority over maxIterations)
|
|
47
47
|
* @param {number} [options.tokenBudget] - Optional token budget
|
|
48
|
-
* @param {object} [options.db] - Database instance
|
|
48
|
+
* @param {object} [options.db] - Database instance (memory recall source)
|
|
49
49
|
* @param {object} [options.permanentMemory] - Permanent memory instance
|
|
50
|
+
* @param {boolean} [options.memoryEnabled=true] - When false, the child's
|
|
51
|
+
* context engine suppresses hierarchical-memory recall (contract memory:false)
|
|
50
52
|
* @param {object} [options.llmOptions] - LLM provider/model/key options
|
|
51
53
|
* @param {string} [options.cwd] - Working directory
|
|
52
54
|
* @param {boolean} [options.useWorktree] - Force worktree isolation (overrides flag)
|
|
@@ -77,6 +79,14 @@ export class SubAgentContext {
|
|
|
77
79
|
// Shared run-wide TOTAL-sub-agent counter (one object across the whole tree)
|
|
78
80
|
// so this sub-agent's own spawns draw from the same breadth pool.
|
|
79
81
|
this.subAgentBudget = options.subAgentBudget || null;
|
|
82
|
+
// This context's EFFECTIVE subagent contract — the ceiling handed to its OWN
|
|
83
|
+
// nested spawns (threaded into the loop options so a nested spawn_sub_agent
|
|
84
|
+
// reads it as ctx.subAgentContract).
|
|
85
|
+
this.subAgentContract = options.subAgentContract || null;
|
|
86
|
+
// Skill capability INTERSECT: null = unrestricted; a list (possibly empty)
|
|
87
|
+
// restricts run_skill/list_skills in this context's loop to those skills.
|
|
88
|
+
this.skillAllowlist =
|
|
89
|
+
options.skillAllowlist != null ? options.skillAllowlist : null;
|
|
80
90
|
this.cwd = options.cwd || process.cwd();
|
|
81
91
|
this.status = "active";
|
|
82
92
|
this.result = null;
|
|
@@ -93,10 +103,17 @@ export class SubAgentContext {
|
|
|
93
103
|
// Independent message history — never shared with parent
|
|
94
104
|
this.messages = [];
|
|
95
105
|
|
|
96
|
-
// Independent context engine — does not inherit parent's compaction/errors
|
|
106
|
+
// Independent context engine — does not inherit parent's compaction/errors.
|
|
107
|
+
// Memory recall is gated by the subagent contract: the spawn passes a `db`
|
|
108
|
+
// and memoryEnabled:true only when the resolved contract grants memory
|
|
109
|
+
// (context:fork from a memory-bearing parent, or explicit memory:true).
|
|
110
|
+
// Default (silent-`fresh`→memory:false) → no db + memoryEnabled:false → no
|
|
111
|
+
// recall, which is today's byte-identical behavior for a plain sub-agent.
|
|
112
|
+
this._memoryEnabled = options.memoryEnabled !== false;
|
|
97
113
|
this.contextEngine = new CLIContextEngineering({
|
|
98
114
|
db: options.db || null,
|
|
99
115
|
permanentMemory: options.permanentMemory || null,
|
|
116
|
+
memoryEnabled: this._memoryEnabled,
|
|
100
117
|
scope: {
|
|
101
118
|
taskId: this.id,
|
|
102
119
|
role: this.role,
|
|
@@ -123,8 +140,13 @@ export class SubAgentContext {
|
|
|
123
140
|
this._onUsage =
|
|
124
141
|
typeof options.onUsage === "function" ? options.onUsage : null;
|
|
125
142
|
|
|
126
|
-
// Optional abort signal for cancellation
|
|
143
|
+
// Optional EXTERNAL abort signal for cancellation (e.g. a parent-provided
|
|
144
|
+
// AbortSignal). In addition, every context owns an INTERNAL controller so a
|
|
145
|
+
// single sub-agent can be cancelled precisely by id via `abort()` (the
|
|
146
|
+
// registry's `cancel(id)`), independent of any external signal.
|
|
127
147
|
this._signal = options.signal || null;
|
|
148
|
+
this._abortController = new AbortController();
|
|
149
|
+
this._cancelReason = null;
|
|
128
150
|
|
|
129
151
|
// Optional MCP / external tool plumbing. These are forwarded into the
|
|
130
152
|
// agentLoop options so MCP-backed tools (e.g. from a cowork template's
|
|
@@ -145,6 +167,22 @@ export class SubAgentContext {
|
|
|
145
167
|
: {};
|
|
146
168
|
this._mcpClient = options.mcpClient || null;
|
|
147
169
|
|
|
170
|
+
// Inherited settings hooks (Pre/PostToolUse) for this child loop. null =
|
|
171
|
+
// no hooks (the spawn default). The spawn path passes the parent's hooks
|
|
172
|
+
// filtered by the contract's `hooks` allow-list; forwarded into agentLoop.
|
|
173
|
+
this._settingsHooks = options.settingsHooks || null;
|
|
174
|
+
|
|
175
|
+
// permissionMode confirmer for this child loop. null = no confirmer (the
|
|
176
|
+
// spawn default → the child denies ask/sensitive-file/git gates). The spawn
|
|
177
|
+
// sets this only for the autopilot (bypassPermissions) ALLOW confirmer.
|
|
178
|
+
this._permissionConfirm = options.permissionConfirm || null;
|
|
179
|
+
|
|
180
|
+
// permissionMode ApprovalGate for this child loop. null = ungated (the spawn
|
|
181
|
+
// default). The spawn sets a dedicated confirmer-less gate (seeded with the
|
|
182
|
+
// mode's tier) for the strict/trusted tiers so run_shell/browser_act are
|
|
183
|
+
// gated headlessly (CONFIRM→no-confirmer→DENY).
|
|
184
|
+
this._approvalGate = options.approvalGate || null;
|
|
185
|
+
|
|
148
186
|
// Build isolated system prompt
|
|
149
187
|
const basePrompt = buildSystemPrompt(this.cwd);
|
|
150
188
|
const profilePrompt = this._profile?.systemPrompt
|
|
@@ -176,9 +214,25 @@ export class SubAgentContext {
|
|
|
176
214
|
);
|
|
177
215
|
}
|
|
178
216
|
|
|
179
|
-
// If worktree isolation is enabled, wrap execution in isolated worktree
|
|
180
|
-
if (this._useWorktree
|
|
181
|
-
|
|
217
|
+
// If worktree isolation is enabled, wrap execution in isolated worktree.
|
|
218
|
+
if (this._useWorktree) {
|
|
219
|
+
if (isGitRepo(this._repoDir)) {
|
|
220
|
+
return this._runInWorktree(userPrompt, loopOptions);
|
|
221
|
+
}
|
|
222
|
+
// FAIL CLOSED: isolation was explicitly requested but this is not a git
|
|
223
|
+
// repo. Never silently fall back to the parent checkout — refuse.
|
|
224
|
+
this.status = "failed";
|
|
225
|
+
this.completedAt = new Date().toISOString();
|
|
226
|
+
this.result = {
|
|
227
|
+
summary:
|
|
228
|
+
"Worktree isolation was requested but the working directory is not a git repository — refusing to run in the parent checkout.",
|
|
229
|
+
artifacts: [],
|
|
230
|
+
tokenCount: this._tokenCount,
|
|
231
|
+
toolsUsed: [...new Set(this._toolsUsed)],
|
|
232
|
+
iterationCount: this._iterationCount,
|
|
233
|
+
isolationError: true,
|
|
234
|
+
};
|
|
235
|
+
return this.result;
|
|
182
236
|
}
|
|
183
237
|
|
|
184
238
|
return this._runCore(userPrompt, loopOptions);
|
|
@@ -270,6 +324,10 @@ export class SubAgentContext {
|
|
|
270
324
|
// Shared total-sub-agent counter so a nested spawn_sub_agent draws from
|
|
271
325
|
// (and is bounded by) the run's single breadth pool.
|
|
272
326
|
subAgentBudget: this.subAgentBudget,
|
|
327
|
+
// This context's effective contract = the ceiling for its nested spawns.
|
|
328
|
+
subAgentContract: this.subAgentContract,
|
|
329
|
+
// Skill allow-list restricting run_skill/list_skills in this loop.
|
|
330
|
+
skillAllowlist: this.skillAllowlist,
|
|
273
331
|
...loopOptions,
|
|
274
332
|
};
|
|
275
333
|
if (this.iterationBudget) {
|
|
@@ -298,6 +356,22 @@ export class SubAgentContext {
|
|
|
298
356
|
if (this._mcpClient) {
|
|
299
357
|
options.mcpClient = this._mcpClient;
|
|
300
358
|
}
|
|
359
|
+
// Inherited Pre/PostToolUse hooks (spawn passes the parent's, filtered by
|
|
360
|
+
// the contract's `hooks` allow-list). Only set when non-null so a plain
|
|
361
|
+
// sub-agent keeps its no-hooks default.
|
|
362
|
+
if (this._settingsHooks) {
|
|
363
|
+
options.settingsHooks = this._settingsHooks;
|
|
364
|
+
}
|
|
365
|
+
// permissionMode confirmer (spawn sets it only for bypassPermissions). Only
|
|
366
|
+
// forward when present so a plain sub-agent keeps its implicit-deny default.
|
|
367
|
+
if (this._permissionConfirm) {
|
|
368
|
+
options.permissionConfirm = this._permissionConfirm;
|
|
369
|
+
}
|
|
370
|
+
// permissionMode ApprovalGate (spawn sets it for strict/trusted tiers). Only
|
|
371
|
+
// forward when present so a plain sub-agent stays ungated (byte-identical).
|
|
372
|
+
if (this._approvalGate) {
|
|
373
|
+
options.approvalGate = this._approvalGate;
|
|
374
|
+
}
|
|
301
375
|
|
|
302
376
|
try {
|
|
303
377
|
// Use a separate messages array for the agent loop
|
|
@@ -361,9 +435,9 @@ export class SubAgentContext {
|
|
|
361
435
|
}
|
|
362
436
|
}
|
|
363
437
|
|
|
364
|
-
// Check abort signal
|
|
365
|
-
if (this.
|
|
366
|
-
this.forceComplete("cancelled", {
|
|
438
|
+
// Check abort signal (external OR this context's own `abort()`)
|
|
439
|
+
if (this.isAborted()) {
|
|
440
|
+
this.forceComplete(this._cancelReason || "cancelled", {
|
|
367
441
|
partialContent: lastContent,
|
|
368
442
|
artifacts,
|
|
369
443
|
});
|
|
@@ -512,6 +586,33 @@ export class SubAgentContext {
|
|
|
512
586
|
}
|
|
513
587
|
}
|
|
514
588
|
|
|
589
|
+
/**
|
|
590
|
+
* True when this sub-agent has been asked to stop — either via an external
|
|
591
|
+
* abort signal or via this context's own `abort()` (precise single-agent
|
|
592
|
+
* cancel). The agent loop checks this each iteration and winds down
|
|
593
|
+
* cooperatively (force-completes with the partial work it has).
|
|
594
|
+
*/
|
|
595
|
+
isAborted() {
|
|
596
|
+
return Boolean(
|
|
597
|
+
this._signal?.aborted || this._abortController.signal.aborted,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Precisely cancel THIS sub-agent: trip its internal abort signal so the loop
|
|
603
|
+
* stops at its next iteration and hands back partial work. Idempotent. The
|
|
604
|
+
* `reason` surfaces in the force-completed summary. Returns true if the abort
|
|
605
|
+
* was newly requested, false if it was already aborted / not active.
|
|
606
|
+
*/
|
|
607
|
+
abort(reason = "cancelled") {
|
|
608
|
+
if (this.status !== "active" || this._abortController.signal.aborted) {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
this._cancelReason = reason;
|
|
612
|
+
this._abortController.abort();
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
|
|
515
616
|
/**
|
|
516
617
|
* Get a serializable snapshot of this context (for debugging/logging).
|
|
517
618
|
*/
|
|
@@ -188,6 +188,25 @@ export class SubAgentRegistry {
|
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* Precisely cancel a SINGLE active sub-agent by id (the per-agent sibling of
|
|
193
|
+
* `forceCompleteAll`). Trips the context's abort signal so its loop stops at
|
|
194
|
+
* the next iteration and hands back partial work; the normal completion path
|
|
195
|
+
* then moves it to history. Cooperative — a loop blocked on a long call winds
|
|
196
|
+
* down at its next check.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} id - Sub-agent id to cancel
|
|
199
|
+
* @param {string} [reason="cancelled"] - Surfaced in the force-completed summary
|
|
200
|
+
* @returns {{ found: boolean, cancelled: boolean }}
|
|
201
|
+
*/
|
|
202
|
+
cancel(id, reason = "cancelled") {
|
|
203
|
+
const subCtx = this._active.get(id);
|
|
204
|
+
if (!subCtx) return { found: false, cancelled: false };
|
|
205
|
+
const cancelled =
|
|
206
|
+
typeof subCtx.abort === "function" ? subCtx.abort(reason) : false;
|
|
207
|
+
return { found: true, cancelled };
|
|
208
|
+
}
|
|
209
|
+
|
|
191
210
|
/**
|
|
192
211
|
* Clean up stale entries older than maxAgeMs.
|
|
193
212
|
* @param {number} [maxAgeMs=600000] - Max age in ms (default: 10 minutes)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subagent capability INHERITANCE + INTERSECT (P1 §"补齐 Subagent 契约").
|
|
3
|
+
*
|
|
4
|
+
* A spawned sub-agent inherits the parent loop's MCP tools and Pre/PostToolUse
|
|
5
|
+
* hooks ONLY through these filters, keyed off the resolved contract's
|
|
6
|
+
* `mcpServers` / `hooks` allow-lists. The tri-state matches
|
|
7
|
+
* `resolveSubagentContract` (see subagent-contract.js):
|
|
8
|
+
*
|
|
9
|
+
* allow === null → inherit ALL (context:fork with an unrestricted parent)
|
|
10
|
+
* allow === [] → inherit NONE (the silent-`fresh` default = today's
|
|
11
|
+
* behavior: a spawned child gets no MCP tools / no hooks)
|
|
12
|
+
* allow === [...] → inherit only the named servers / hook matchers
|
|
13
|
+
*
|
|
14
|
+
* Because the default is `[]` = none, wiring these is byte-identical for a
|
|
15
|
+
* plain sub-agent and only grants capabilities when the contract explicitly
|
|
16
|
+
* opts in (fork context or an explicit list). Pure — no I/O.
|
|
17
|
+
*
|
|
18
|
+
* @module subagent-inheritance
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Extract the server segment from an `mcp__<server>__<tool>` wire name. */
|
|
22
|
+
export function mcpServerOf(name) {
|
|
23
|
+
const s = String(name || "");
|
|
24
|
+
if (!s.startsWith("mcp__")) return null;
|
|
25
|
+
const rest = s.slice(5);
|
|
26
|
+
const idx = rest.indexOf("__");
|
|
27
|
+
return idx === -1 ? rest || null : rest.slice(0, idx) || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Filter the parent's MCP plumbing to the servers the child may use.
|
|
32
|
+
*
|
|
33
|
+
* @param {object} parent { extraToolDefinitions[], externalToolDescriptors{},
|
|
34
|
+
* externalToolExecutors{}, mcpClient }
|
|
35
|
+
* @param {string[]|null} allow null=all, []=none, list=subset of server names
|
|
36
|
+
* @returns {object|null} filtered plumbing, or null when nothing is inherited
|
|
37
|
+
*/
|
|
38
|
+
export function filterInheritedMcp(parent, allow) {
|
|
39
|
+
if (Array.isArray(allow) && allow.length === 0) return null; // none
|
|
40
|
+
const allowSet = Array.isArray(allow) ? new Set(allow) : null; // null = all
|
|
41
|
+
const serverAllowed = (server) =>
|
|
42
|
+
!allowSet || (server != null && allowSet.has(server));
|
|
43
|
+
|
|
44
|
+
const defs = Array.isArray(parent?.extraToolDefinitions)
|
|
45
|
+
? parent.extraToolDefinitions
|
|
46
|
+
: [];
|
|
47
|
+
const descs =
|
|
48
|
+
parent?.externalToolDescriptors &&
|
|
49
|
+
typeof parent.externalToolDescriptors === "object"
|
|
50
|
+
? parent.externalToolDescriptors
|
|
51
|
+
: {};
|
|
52
|
+
const execs =
|
|
53
|
+
parent?.externalToolExecutors &&
|
|
54
|
+
typeof parent.externalToolExecutors === "object"
|
|
55
|
+
? parent.externalToolExecutors
|
|
56
|
+
: {};
|
|
57
|
+
|
|
58
|
+
const outDefs = defs.filter((d) => {
|
|
59
|
+
const name = d?.function?.name;
|
|
60
|
+
return name && serverAllowed(mcpServerOf(name));
|
|
61
|
+
});
|
|
62
|
+
const outDescs = {};
|
|
63
|
+
for (const [name, desc] of Object.entries(descs)) {
|
|
64
|
+
const server = desc?.serverName || mcpServerOf(name);
|
|
65
|
+
if (serverAllowed(server)) outDescs[name] = desc;
|
|
66
|
+
}
|
|
67
|
+
const outExecs = {};
|
|
68
|
+
for (const [name, exec] of Object.entries(execs)) {
|
|
69
|
+
const server = exec?.serverName || mcpServerOf(name);
|
|
70
|
+
if (serverAllowed(server)) outExecs[name] = exec;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
outDefs.length === 0 &&
|
|
75
|
+
Object.keys(outDescs).length === 0 &&
|
|
76
|
+
Object.keys(outExecs).length === 0
|
|
77
|
+
) {
|
|
78
|
+
return null; // nothing survived the filter — inherit no MCP
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
extraToolDefinitions: outDefs,
|
|
82
|
+
externalToolDescriptors: outDescs,
|
|
83
|
+
externalToolExecutors: outExecs,
|
|
84
|
+
mcpClient: parent?.mcpClient || null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Filter the parent's settings hooks to the matchers the child may fire. Hooks
|
|
90
|
+
* have no name; a group's `matcher` (normalized: null/""→"*") is its identity.
|
|
91
|
+
*
|
|
92
|
+
* @param {object|null} settingsHooks { [event]: [{matcher, hooks:[...]}] }
|
|
93
|
+
* @param {string[]|null} allow null=all, []=none, list=subset of matchers
|
|
94
|
+
* @returns {object|null} filtered hooks, or null when nothing is inherited
|
|
95
|
+
*/
|
|
96
|
+
export function filterInheritedHooks(settingsHooks, allow) {
|
|
97
|
+
if (Array.isArray(allow) && allow.length === 0) return null; // none
|
|
98
|
+
if (!settingsHooks || typeof settingsHooks !== "object") return null;
|
|
99
|
+
if (!Array.isArray(allow)) return settingsHooks; // null = inherit all as-is
|
|
100
|
+
|
|
101
|
+
const allowSet = new Set(allow);
|
|
102
|
+
const matcherKey = (g) =>
|
|
103
|
+
g?.matcher == null || g.matcher === "" ? "*" : String(g.matcher);
|
|
104
|
+
|
|
105
|
+
const out = {};
|
|
106
|
+
for (const [event, groups] of Object.entries(settingsHooks)) {
|
|
107
|
+
if (!Array.isArray(groups)) continue;
|
|
108
|
+
const kept = groups.filter((g) => allowSet.has(matcherKey(g)));
|
|
109
|
+
if (kept.length > 0) out[event] = kept;
|
|
110
|
+
}
|
|
111
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
112
|
+
}
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `summary()` renders them. The clock is injected so tests are deterministic.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { redactSecrets } from "../secret-scan.js";
|
|
16
|
+
|
|
15
17
|
let _seq = 0;
|
|
16
18
|
function spanId() {
|
|
17
19
|
_seq = (_seq + 1) % Number.MAX_SAFE_INTEGER;
|
|
@@ -187,8 +189,19 @@ export class TelemetryRecorder {
|
|
|
187
189
|
};
|
|
188
190
|
}
|
|
189
191
|
|
|
190
|
-
/**
|
|
191
|
-
|
|
192
|
+
/**
|
|
193
|
+
* OTLP/JSON-shaped export (resourceSpans) — ingestible by an OTel collector.
|
|
194
|
+
*
|
|
195
|
+
* §8.1 export gate: a span attribute or an exception message can carry a
|
|
196
|
+
* provider token / JWT / connection string (a tool `command` attribute, an
|
|
197
|
+
* error string, a redirect URL). A trace is shipped to a collector that may be
|
|
198
|
+
* external, so string values are secret-redacted by default (recall-first —
|
|
199
|
+
* an export must fail toward "no leak"). Only true secret substrings are
|
|
200
|
+
* replaced, so a span with no secret is byte-identical. `{ redact: false }`
|
|
201
|
+
* keeps raw values for a fully-trusted local collector.
|
|
202
|
+
*/
|
|
203
|
+
toOtlp({ redact = true } = {}) {
|
|
204
|
+
const scrub = redact ? (v) => redactSecrets(String(v)) : (v) => String(v);
|
|
192
205
|
return {
|
|
193
206
|
resourceSpans: [
|
|
194
207
|
{
|
|
@@ -219,14 +232,14 @@ export class TelemetryRecorder {
|
|
|
219
232
|
? { doubleValue: v }
|
|
220
233
|
: typeof v === "boolean"
|
|
221
234
|
? { boolValue: v }
|
|
222
|
-
: { stringValue:
|
|
235
|
+
: { stringValue: scrub(v) },
|
|
223
236
|
})),
|
|
224
237
|
events: s.events.map((e) => ({
|
|
225
238
|
name: e.name,
|
|
226
239
|
timeUnixNano: String(Math.round(e.time * 1e6)),
|
|
227
240
|
attributes: Object.entries(e.attributes).map(([k, v]) => ({
|
|
228
241
|
key: k,
|
|
229
|
-
value: { stringValue:
|
|
242
|
+
value: { stringValue: scrub(v) },
|
|
230
243
|
})),
|
|
231
244
|
})),
|
|
232
245
|
})),
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-context policy core — the pure-logic half of P1-2 (交互终端) of
|
|
3
|
+
* CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-07-13.md.
|
|
4
|
+
*
|
|
5
|
+
* P1-2 asks for an embedded interactive PTY inside an Agent Session: continuous
|
|
6
|
+
* ANSI output, scrollback trimming, search/copy, user takeover, "把选中输出附加
|
|
7
|
+
* 到下一轮", background commands surfacing PID / port / health, and — critically —
|
|
8
|
+
* "Agent 不得把 Terminal 中的敏感输出默认加入模型上下文" (terminal output must NOT
|
|
9
|
+
* enter the model context by default).
|
|
10
|
+
*
|
|
11
|
+
* This module is that policy + text layer, NOT a PTY: it never spawns a shell,
|
|
12
|
+
* allocates a pseudo-terminal, or touches fs / clock / RNG / process. A caller
|
|
13
|
+
* (the real PTY host) feeds captured bytes and a selection intent and reads back
|
|
14
|
+
* what — if anything — is safe to hand the model.
|
|
15
|
+
*
|
|
16
|
+
* Two invariants give it teeth, mirroring the other gap cores:
|
|
17
|
+
* 1. Terminal output fails CLOSED out of the model context: `selectTerminal
|
|
18
|
+
* ContextForModel` returns null unless the user EXPLICITLY opted this turn's
|
|
19
|
+
* output in. Ambient scrollback never leaks into a prompt.
|
|
20
|
+
* 2. Whatever DOES get opted in is ANSI-stripped and secret-redacted (via
|
|
21
|
+
* [[secret-scan.js]]) and scrollback-capped, so a printed token or a control
|
|
22
|
+
* sequence can't ride into the context.
|
|
23
|
+
*
|
|
24
|
+
* PURE: no fs / clock / RNG / process / PTY.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { redactSecrets } from "./secret-scan.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Match ANSI/VT control sequences: CSI (ESC [ … final byte), OSC (ESC ] … BEL or
|
|
31
|
+
* ST) and the standalone two/three-byte escapes. Kept explicit rather than a
|
|
32
|
+
* dependency so the strip is auditable (ESC = U+001B, CSI = U+009B, BEL = U+0007).
|
|
33
|
+
* A fresh RegExp is compiled per call to avoid the /g lastIndex
|
|
34
|
+
* hazard across invocations.
|
|
35
|
+
*/
|
|
36
|
+
function ansiRegex() {
|
|
37
|
+
return new RegExp(
|
|
38
|
+
[
|
|
39
|
+
"[\\u001B\\u009B][[\\]()#;?]*" +
|
|
40
|
+
"(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)" +
|
|
41
|
+
"|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))",
|
|
42
|
+
].join(""),
|
|
43
|
+
"g",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Remove ANSI/VT escape sequences from terminal output, leaving human-readable
|
|
49
|
+
* text (for search, copy, and — importantly — before the text ever reaches a
|
|
50
|
+
* model, so escape codes can't smuggle intent or bloat the context). Non-string
|
|
51
|
+
* input → "".
|
|
52
|
+
*/
|
|
53
|
+
export function stripAnsi(text) {
|
|
54
|
+
if (typeof text !== "string") return "";
|
|
55
|
+
return text.replace(ansiRegex(), "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Default scrollback cap — how many trailing lines survive truncation. */
|
|
59
|
+
export const DEFAULT_SCROLLBACK_LINES = 400;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Cap a captured buffer to its last `maxLines` lines (the live tail is what
|
|
63
|
+
* matters for a running process). Marks the elision so the reader — and any
|
|
64
|
+
* model — knows output was dropped rather than seeing a silently-truncated head.
|
|
65
|
+
*
|
|
66
|
+
* @returns {{text:string, truncated:boolean, droppedLines:number, totalLines:number}}
|
|
67
|
+
*/
|
|
68
|
+
export function truncateScrollback(text, maxLines = DEFAULT_SCROLLBACK_LINES) {
|
|
69
|
+
const src = typeof text === "string" ? text : "";
|
|
70
|
+
const cap =
|
|
71
|
+
Number.isInteger(maxLines) && maxLines > 0
|
|
72
|
+
? maxLines
|
|
73
|
+
: DEFAULT_SCROLLBACK_LINES;
|
|
74
|
+
const lines = src.split("\n");
|
|
75
|
+
const total = lines.length;
|
|
76
|
+
if (total <= cap) {
|
|
77
|
+
return { text: src, truncated: false, droppedLines: 0, totalLines: total };
|
|
78
|
+
}
|
|
79
|
+
const kept = lines.slice(total - cap);
|
|
80
|
+
const dropped = total - cap;
|
|
81
|
+
return {
|
|
82
|
+
text: `…(${dropped} earlier lines elided)\n${kept.join("\n")}`,
|
|
83
|
+
truncated: true,
|
|
84
|
+
droppedLines: dropped,
|
|
85
|
+
totalLines: total,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Decide what terminal output may be attached to the next model turn — the
|
|
91
|
+
* fail-closed core of "Agent 不得把 Terminal 中的敏感输出默认加入模型上下文".
|
|
92
|
+
*
|
|
93
|
+
* Returns `null` (attach NOTHING) unless the user explicitly opted in this turn
|
|
94
|
+
* (`optIn === true`). When opted in, a supplied `selection` (the user's
|
|
95
|
+
* highlighted region) is preferred over the full `output` — "把选中输出附加到下
|
|
96
|
+
* 一轮". Whatever is chosen is ANSI-stripped, secret-redacted and scrollback-
|
|
97
|
+
* capped before it becomes context.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} args
|
|
100
|
+
* @param {string} [args.output] full captured terminal buffer
|
|
101
|
+
* @param {string} [args.selection] user-highlighted region (preferred)
|
|
102
|
+
* @param {boolean} [args.optIn] explicit "include this in context" intent
|
|
103
|
+
* @param {number} [args.maxLines] scrollback cap for the attached text
|
|
104
|
+
* @returns {null | {source:"selection"|"output", text:string, truncated:boolean, redacted:boolean}}
|
|
105
|
+
*/
|
|
106
|
+
export function selectTerminalContextForModel(args = {}) {
|
|
107
|
+
const a = args && typeof args === "object" ? args : {};
|
|
108
|
+
if (a.optIn !== true) {
|
|
109
|
+
return null; // fail-closed: no ambient terminal output in the prompt
|
|
110
|
+
}
|
|
111
|
+
const hasSelection =
|
|
112
|
+
typeof a.selection === "string" && a.selection.trim() !== "";
|
|
113
|
+
const source = hasSelection ? "selection" : "output";
|
|
114
|
+
const raw = hasSelection ? a.selection : a.output;
|
|
115
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
116
|
+
return null; // opted in but nothing to attach
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const clean = stripAnsi(raw);
|
|
120
|
+
const redactedText = redactSecrets(clean);
|
|
121
|
+
const capped = truncateScrollback(redactedText, a.maxLines);
|
|
122
|
+
return {
|
|
123
|
+
source,
|
|
124
|
+
text: capped.text,
|
|
125
|
+
truncated: capped.truncated,
|
|
126
|
+
redacted: redactedText !== clean,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Canonical health states for a backgrounded terminal command. */
|
|
131
|
+
export const HEALTH_STATUS = Object.freeze({
|
|
132
|
+
STARTING: "starting",
|
|
133
|
+
RUNNING: "running",
|
|
134
|
+
HEALTHY: "healthy",
|
|
135
|
+
UNHEALTHY: "unhealthy",
|
|
136
|
+
EXITED: "exited",
|
|
137
|
+
UNKNOWN: "unknown",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const HEALTH_ALIASES = new Map([
|
|
141
|
+
["starting", HEALTH_STATUS.STARTING],
|
|
142
|
+
["running", HEALTH_STATUS.RUNNING],
|
|
143
|
+
["up", HEALTH_STATUS.RUNNING],
|
|
144
|
+
["healthy", HEALTH_STATUS.HEALTHY],
|
|
145
|
+
["ok", HEALTH_STATUS.HEALTHY],
|
|
146
|
+
["unhealthy", HEALTH_STATUS.UNHEALTHY],
|
|
147
|
+
["degraded", HEALTH_STATUS.UNHEALTHY],
|
|
148
|
+
["exited", HEALTH_STATUS.EXITED],
|
|
149
|
+
["stopped", HEALTH_STATUS.EXITED],
|
|
150
|
+
["dead", HEALTH_STATUS.EXITED],
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
/** Normalize a health label; anything unrecognized → UNKNOWN (fail-closed). */
|
|
154
|
+
export function normalizeHealthStatus(value) {
|
|
155
|
+
if (typeof value !== "string") return HEALTH_STATUS.UNKNOWN;
|
|
156
|
+
return (
|
|
157
|
+
HEALTH_ALIASES.get(value.trim().toLowerCase()) || HEALTH_STATUS.UNKNOWN
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function normalizePorts(ports) {
|
|
162
|
+
const list = Array.isArray(ports) ? ports : [];
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const p of list) {
|
|
165
|
+
const n = Number(p);
|
|
166
|
+
if (Number.isInteger(n) && n > 0 && n <= 65535 && !out.includes(n)) {
|
|
167
|
+
out.push(n);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Build a safe descriptor for a backgrounded terminal command — the "后台命令显示
|
|
175
|
+
* PID、端口、健康状态并可安全停止" surface. The command string is secret-redacted
|
|
176
|
+
* (a `FOO=token cmd` invocation must not leak the token into a status pill), PID
|
|
177
|
+
* is coerced to a positive integer or null, ports are validated, and the health
|
|
178
|
+
* status is normalized fail-closed. `stoppable` is true only when we hold a real
|
|
179
|
+
* PID to signal.
|
|
180
|
+
*
|
|
181
|
+
* @param {object} cmd {command?, pid?, ports?, status?}
|
|
182
|
+
* @returns {{command:string|null, pid:number|null, ports:number[], status:string, stoppable:boolean}}
|
|
183
|
+
*/
|
|
184
|
+
export function describeBackgroundCommand(cmd = {}) {
|
|
185
|
+
const c = cmd && typeof cmd === "object" ? cmd : {};
|
|
186
|
+
const pidNum = Number(c.pid);
|
|
187
|
+
const pid = Number.isInteger(pidNum) && pidNum > 0 ? pidNum : null;
|
|
188
|
+
return {
|
|
189
|
+
command: typeof c.command === "string" ? redactSecrets(c.command) : null,
|
|
190
|
+
pid,
|
|
191
|
+
ports: normalizePorts(c.ports),
|
|
192
|
+
status: normalizeHealthStatus(c.status),
|
|
193
|
+
stoppable: pid !== null,
|
|
194
|
+
};
|
|
195
|
+
}
|