chainlesschain 0.162.148 → 0.162.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DwpCCp64.js → AIOps-CBLvrjeG.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-DYv2GtE0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-CL6PZ-Ww.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-xuyHt4uA.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-DOvSVycq.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-DMhfTswr.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-CQxDdm63.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-BEeNhK2a.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-8nul8eTq.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CL04O-ER.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-C50o-n7x.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-CH-bDkzs.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-DqAIMvSe.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-C43fo_my.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-BERnMrdP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-Baw3w1gp.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-BEqAHF1-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-CvnmKwK4.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-iMPxgVdt.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard--4SJX3YR.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BgLV6xgz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-Bvb8oUD8.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-DpXPjJB5.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-BbFfJ1Xi.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DeGq1B-q.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-CNW7s9zM.js} +1 -1
- package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-L1PjW-lT.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-PK-X58to.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-BePWEH-d.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-LF_sBvdL.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-BKkfk9hy.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-BNMqUDla.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-w4WPiyz2.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-DUKMVGNi.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-BWhiEyTJ.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-Ocf5JEkX.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-D2B6MMhU.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-DdYjCq1O.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-Cb1hfuUZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-CTeImGp7.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-BjYNc7eQ.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-Dn_NtRXQ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DIp8Xcbd.js → OrderTableRenderer-DVYYIizh.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-Be2Kmr5j.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow-Cu_-qRlR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-DD2HoGzE.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-BRI7DVth.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-C9Srcs2M.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CUqcMgo1.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-HBrQGLXt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-D-B0vjwh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-eTeLIu9e.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-DrRqCsC6.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Dpid9CDg.js → Projects-1G9DNOhI.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-RkTZzpxP.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-BSCUgsgC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CHgyM3Bz.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-0lry4OZq.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-Cn45UroZ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-Q-Zjb707.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-BdM70oda.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-DmOAKKap.js} +2 -2
- package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-Dwavbm07.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DukXFCnk.js} +3 -3
- package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-BIlRnITu.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-YYVQdhhQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-B8_iYHz2.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-BdVrhT31.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-CD3ggRx7.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-Bp02VZFH.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-DEVmCEsr.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-B6czWc4N.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-BCJLv17r.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-CD132d2Y.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-ZB-CvkZl.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BnSY0S-s.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-VZw9Oy2w.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-pZq7Hjd2.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-DnQaqk4q.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-BEUfmvJV.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-CoZKMXJ2.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-CBOGVx1I.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-huSKn7TT.js} +1 -1
- package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-A_3w6FBO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-D1Bem9Oy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-CJYJj0oO.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-BkbFiKT4.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-WDr3_M_N.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-CKvUafVY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-AjrSPnvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-B8i0lViZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-B9Svn0zc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-BHdHSX06.js} +1 -1
- package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BHdailyo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-BJwlEnYo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-BOoAQjJz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-BSgWxAOG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-BdutMsne.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-BiDWxzbn.js} +1 -1
- package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-By5a0T_W.js} +1 -1
- package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-C-WIY-FQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-C37f6iey.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-C9bWmTcO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-CBaosWRO.js} +1 -1
- package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-CBlBVJ_m.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-CD62EEGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-CUj-l7GM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-CVS8Ymuq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-ChkXUj3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-Cj0OZ-37.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-Co3OFL0t.js} +1 -1
- package/src/assets/web-panel/assets/{index-308gCGh7.js → index-Crp8CbRg.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-D22zNXiS.js} +1 -1
- package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-DTs_D1OL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-DfgCdFrN.js} +3 -3
- package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-DjYrecNb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-DpW9cf3e.js} +1 -1
- package/src/assets/web-panel/assets/index-Dwix3p4g.js +1 -0
- package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-F--HuPG1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-Fuk6t_3K.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-JEAGMii9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-PF-mpv8K.js} +1 -1
- package/src/assets/web-panel/assets/index-SHaoZ0CA.js +1 -0
- package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-b-sbVdjQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-pyvMVX8r.js} +1 -1
- package/src/assets/web-panel/assets/{index-DISWAYce.js → index-qNTtOVi_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-tgO8iza6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahryTX0.js → index-ttDQVhZy.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-DtrnfsZH.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D4zqSaX3.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-CGFGOUQj.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DH2HvkJl.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-jJ2at5HX.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-CRyMT1Fv.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-DZ_CX0Pt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-wruHk-h5.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BGgx8q67.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-Do5M7gKm.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-CRa-GZpX.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-BxPcq6pj.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-v5O2ZcJm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub--z2FU0Px.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-1emidVKd.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-CcJaRyHs.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/assets/web-panel/remote-session-sw.js +43 -0
- package/src/command-manifest.json +22 -1
- package/src/commands/a2a.js +19 -4
- package/src/commands/activitypub.js +20 -3
- package/src/commands/agent.js +71 -10
- package/src/commands/audit.js +40 -24
- package/src/commands/codeintel.js +320 -0
- package/src/commands/collab.js +15 -2
- package/src/commands/compliance.js +5 -0
- package/src/commands/config.js +4 -1
- package/src/commands/dao.js +86 -14
- package/src/commands/dev.js +2 -0
- package/src/commands/did.js +7 -1
- package/src/commands/dlp.js +23 -1
- package/src/commands/economy.js +29 -3
- package/src/commands/eval.js +255 -0
- package/src/commands/evomap.js +26 -0
- package/src/commands/governance.js +17 -3
- package/src/commands/hardening.js +18 -0
- package/src/commands/incentive.js +5 -0
- package/src/commands/init.js +46 -2
- package/src/commands/kg.js +4 -0
- package/src/commands/loop.js +6 -1
- package/src/commands/lowcode.js +15 -2
- package/src/commands/marketplace.js +40 -6
- package/src/commands/matrix.js +20 -1
- package/src/commands/memory.js +4 -1
- package/src/commands/mtc.js +7 -1
- package/src/commands/nostr.js +31 -4
- package/src/commands/note.js +7 -4
- package/src/commands/plugin.js +474 -0
- package/src/commands/pqc.js +5 -0
- package/src/commands/reputation.js +10 -1
- package/src/commands/scim.js +6 -0
- package/src/commands/session.js +32 -10
- package/src/commands/siem.js +11 -0
- package/src/commands/sla.js +18 -3
- package/src/commands/social.js +38 -5
- package/src/commands/sso.js +10 -2
- package/src/commands/stress.js +23 -4
- package/src/commands/team.js +463 -0
- package/src/commands/tech.js +2 -0
- package/src/commands/tenant.js +10 -1
- package/src/commands/terraform.js +6 -0
- package/src/commands/update.js +1 -1
- package/src/commands/zkp.js +20 -0
- package/src/gateways/ws/message-dispatcher.js +48 -2
- package/src/gateways/ws/remote-session-protocol.js +140 -42
- package/src/gateways/ws/ws-server.js +1 -1
- package/src/harness/jsonl-session-store.js +12 -5
- package/src/harness/plugin-manager.js +16 -4
- package/src/harness/prompt-compressor.js +27 -1
- package/src/harness/remote-command-ledger.js +189 -0
- package/src/harness/remote-session-push-apns.js +246 -0
- package/src/harness/remote-session-push-errors.js +15 -0
- package/src/harness/remote-session-push-fcm.js +9 -25
- package/src/harness/remote-session-push-huawei.js +174 -0
- package/src/harness/remote-session-push-oppo.js +171 -0
- package/src/harness/remote-session-push-senders.js +42 -0
- package/src/harness/remote-session-push-vivo.js +179 -0
- package/src/harness/remote-session-push-web.js +299 -0
- package/src/harness/remote-session-push-xiaomi.js +99 -0
- package/src/harness/worktree-isolator.js +16 -6
- package/src/index.js +6 -0
- package/src/lib/__tests__/logger.test.js +5 -2
- package/src/lib/a2a-protocol.js +25 -1
- package/src/lib/activitypub-bridge.js +61 -0
- package/src/lib/agent-core.js +4 -0
- package/src/lib/agent-economy.js +262 -29
- package/src/lib/agent-network.js +7 -1
- package/src/lib/agent-sandbox.js +161 -5
- package/src/lib/agent-team/task-lease.js +434 -0
- package/src/lib/agent-team/team-budget.js +165 -0
- package/src/lib/agent-team/team-mailbox.js +106 -0
- package/src/lib/agent-team/team-runner.js +282 -0
- package/src/lib/agent-team/team-worktree.js +236 -0
- package/src/lib/agents.js +18 -0
- package/src/lib/async-hook-supervisor.cjs +391 -0
- package/src/lib/audit-logger.js +7 -6
- package/src/lib/autonomous-developer.js +60 -0
- package/src/lib/chat-core.js +17 -4
- package/src/lib/collaboration-governance.js +56 -0
- package/src/lib/community-governance.js +68 -0
- package/src/lib/compliance-manager.js +63 -0
- package/src/lib/cross-chain.js +5 -0
- package/src/lib/dao-governance.js +151 -2
- package/src/lib/did-manager.js +23 -10
- package/src/lib/dlp-engine.js +70 -0
- package/src/lib/eval/runner.js +251 -0
- package/src/lib/eval/tasks.js +626 -0
- package/src/lib/eval/trend.js +200 -0
- package/src/lib/evolution-system.js +92 -0
- package/src/lib/evomap-federation.js +55 -0
- package/src/lib/evomap-governance.js +94 -0
- package/src/lib/fatal-handler.js +17 -1
- package/src/lib/hardening-manager.js +73 -0
- package/src/lib/hierarchical-memory.js +14 -8
- package/src/lib/knowledge-exporter.js +8 -2
- package/src/lib/knowledge-graph.js +79 -0
- package/src/lib/llm-providers.js +23 -14
- package/src/lib/logger.js +4 -1
- package/src/lib/lsp/__tests__/benchmark.test.js +88 -0
- package/src/lib/lsp/__tests__/code-intelligence.integration.test.js +129 -0
- package/src/lib/lsp/__tests__/code-intelligence.test.js +228 -0
- package/src/lib/lsp/__tests__/jsonrpc-stream.test.js +104 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +269 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +333 -0
- package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -0
- package/src/lib/lsp/benchmark.js +257 -0
- package/src/lib/lsp/code-intelligence.js +356 -0
- package/src/lib/lsp/jsonrpc-stream.js +139 -0
- package/src/lib/lsp/lsp-client.js +339 -0
- package/src/lib/lsp/lsp-manager.js +375 -0
- package/src/lib/lsp/lsp-server-registry.js +196 -0
- package/src/lib/matrix-bridge.js +84 -0
- package/src/lib/memory-manager.js +5 -3
- package/src/lib/nostr-bridge.js +53 -0
- package/src/lib/permission-engine.js +22 -4
- package/src/lib/plugin-monitor-supervisor.js +238 -0
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +256 -0
- package/src/lib/plugin-runtime/agents.js +51 -0
- package/src/lib/plugin-runtime/bin.js +100 -0
- package/src/lib/plugin-runtime/hooks.js +100 -0
- package/src/lib/plugin-runtime/install.js +396 -0
- package/src/lib/plugin-runtime/lsp.js +75 -0
- package/src/lib/plugin-runtime/manifest.js +458 -0
- package/src/lib/plugin-runtime/mcp.js +89 -0
- package/src/lib/plugin-runtime/monitors.js +100 -0
- package/src/lib/plugin-runtime/policy.js +152 -0
- package/src/lib/plugin-runtime/reload.js +79 -0
- package/src/lib/plugin-runtime/remote-source.js +240 -0
- package/src/lib/plugin-runtime/scopes.js +197 -0
- package/src/lib/plugin-runtime/settings.js +119 -0
- package/src/lib/plugin-runtime/signature.js +107 -0
- package/src/lib/plugin-runtime/skills.js +43 -0
- package/src/lib/plugin-runtime/trust.js +140 -0
- package/src/lib/pqc-manager.js +64 -0
- package/src/lib/reputation-optimizer.js +101 -0
- package/src/lib/sandbox-egress-proxy.js +262 -0
- package/src/lib/sandbox-fs-policy.js +112 -0
- package/src/lib/sandbox-network-policy.js +151 -0
- package/src/lib/sandbox-v2.js +24 -18
- package/src/lib/scim-manager.js +36 -0
- package/src/lib/session-manager.js +5 -2
- package/src/lib/settings-hook-events.cjs +78 -6
- package/src/lib/settings-hooks.cjs +30 -3
- package/src/lib/settings-loader.cjs +35 -1
- package/src/lib/siem-exporter.js +45 -0
- package/src/lib/skill-loader.js +38 -2
- package/src/lib/skill-marketplace.js +83 -0
- package/src/lib/sla-manager.js +88 -0
- package/src/lib/social-manager.js +74 -0
- package/src/lib/stress-tester.js +65 -0
- package/src/lib/tech-learning-engine.js +75 -0
- package/src/lib/telemetry/span-recorder.js +237 -0
- package/src/lib/tenant-saas.js +107 -0
- package/src/lib/terraform-manager.js +67 -0
- package/src/lib/token-incentive.js +180 -29
- package/src/lib/wallet-manager.js +81 -35
- package/src/lib/zkp-engine.js +87 -0
- package/src/repl/agent-repl.js +417 -6
- package/src/runtime/__tests__/auto-pin.test.js +104 -0
- package/src/runtime/agent-core.js +771 -104
- package/src/runtime/auto-pin.js +117 -0
- package/src/runtime/coding-agent-contract-shared.cjs +82 -8
- package/src/runtime/coding-agent-policy.cjs +32 -7
- package/src/runtime/fallback-model.js +134 -7
- package/src/runtime/headless-runner.js +388 -108
- package/src/runtime/mcp-config.js +46 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +0 -1
- package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +0 -3
- package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +0 -1
- package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +0 -1
- package/src/assets/web-panel/assets/index-BXiw9dQf.js +0 -1
|
@@ -83,7 +83,8 @@ const { evaluateShellCommandPolicy } = sharedShellPolicy;
|
|
|
83
83
|
const { evaluatePermissionRules } = sharedPermissionRules;
|
|
84
84
|
const { collectHooks, umbrellaFor } = sharedSettingsHooks;
|
|
85
85
|
const { runHooks: runCommandHooks } = sharedHookRunner;
|
|
86
|
-
const { runObserveHooks } =
|
|
86
|
+
const { runObserveHooks, aggregateContext, partitionAsyncHooks } =
|
|
87
|
+
sharedHookEvents;
|
|
87
88
|
|
|
88
89
|
// ─── Background shell tasks ────────────────────────────────────────────────
|
|
89
90
|
//
|
|
@@ -220,6 +221,77 @@ export function killAllBackgroundShellTasks() {
|
|
|
220
221
|
return killed;
|
|
221
222
|
}
|
|
222
223
|
|
|
224
|
+
// SYNCHRONOUS whole-tree kill for use inside a process 'exit' / signal handler,
|
|
225
|
+
// where only synchronous work runs to completion (the async `spawn`/`taskkill`
|
|
226
|
+
// in _killTask would be cut off the instant the process terminates, leaving the
|
|
227
|
+
// grandchild orphaned). POSIX signals the process group with SIGKILL; Windows
|
|
228
|
+
// uses spawnSync taskkill /T so the shell's children die too.
|
|
229
|
+
function _killTaskSync(task) {
|
|
230
|
+
const child = task?.child;
|
|
231
|
+
if (!child || child.killed || task?.status !== "running") return false;
|
|
232
|
+
try {
|
|
233
|
+
if (process.platform === "win32") {
|
|
234
|
+
if (child.pid) {
|
|
235
|
+
spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
236
|
+
windowsHide: true,
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
child.kill();
|
|
240
|
+
}
|
|
241
|
+
} else if (child.pid) {
|
|
242
|
+
// Negative pid → whole process group (requires the detached spawn above).
|
|
243
|
+
try {
|
|
244
|
+
process.kill(-child.pid, "SIGKILL");
|
|
245
|
+
} catch (_err) {
|
|
246
|
+
child.kill("SIGKILL");
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
child.kill("SIGKILL");
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
} catch (_err) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Synchronously kill every still-running background shell task. Safe to call
|
|
259
|
+
* from a process 'exit' or signal handler (uses spawnSync/process.kill, not the
|
|
260
|
+
* async spawn path which a terminating process would cut off). This is the
|
|
261
|
+
* orphan-reclaim net for the paths the normal `finally` reaper can't cover: a
|
|
262
|
+
* Ctrl-C / SIGTERM unwinds no `finally`, so without it a backgrounded dev server
|
|
263
|
+
* outlives a killed `cc agent -p`.
|
|
264
|
+
* @returns {number} count of tasks signalled
|
|
265
|
+
*/
|
|
266
|
+
export function killAllBackgroundShellTasksSync() {
|
|
267
|
+
let killed = 0;
|
|
268
|
+
for (const task of _backgroundShellTasks.values()) {
|
|
269
|
+
if (_killTaskSync(task)) {
|
|
270
|
+
killed += 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return killed;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Install a one-time process 'exit' net that synchronously reaps background
|
|
277
|
+
// shell tasks. `finally` blocks (headless/REPL) already reap on normal
|
|
278
|
+
// completion, but an explicit process.exit() elsewhere (serve shutdown, the
|
|
279
|
+
// headless signal handler that converts Ctrl-C → exit) would otherwise leave a
|
|
280
|
+
// backgrounded command orphaned. Installed lazily on the first background task,
|
|
281
|
+
// so a process that never backgrounds anything pays nothing.
|
|
282
|
+
let _bgExitReaperHooked = false;
|
|
283
|
+
function _ensureBgExitReaper() {
|
|
284
|
+
if (_bgExitReaperHooked) return;
|
|
285
|
+
_bgExitReaperHooked = true;
|
|
286
|
+
process.once("exit", () => {
|
|
287
|
+
try {
|
|
288
|
+
killAllBackgroundShellTasksSync();
|
|
289
|
+
} catch {
|
|
290
|
+
/* best-effort teardown on exit */
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
223
295
|
// Idle-reap tuning (Claude-Code 2.1.193 "automatic memory-pressure reaping for
|
|
224
296
|
// idle background shell commands"). A running task is a reap candidate when it
|
|
225
297
|
// has produced no output for BG_IDLE_REAP_MS AND the system is under memory
|
|
@@ -323,16 +395,12 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
|
|
|
323
395
|
ideResult: ide.result,
|
|
324
396
|
};
|
|
325
397
|
}
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
rule: `hook:${outcome.hook}`,
|
|
333
|
-
reason: outcome.reason || "a PreToolUse hook requests confirmation",
|
|
334
|
-
})
|
|
335
|
-
: false;
|
|
398
|
+
const ok = await requestInteractivePermission(name, args, context, cwd, {
|
|
399
|
+
tool: name,
|
|
400
|
+
args,
|
|
401
|
+
rule: `hook:${outcome.hook}`,
|
|
402
|
+
reason: outcome.reason || "a PreToolUse hook requests confirmation",
|
|
403
|
+
});
|
|
336
404
|
return ok
|
|
337
405
|
? { blocked: false }
|
|
338
406
|
: {
|
|
@@ -344,6 +412,87 @@ async function runSettingsPreToolUseHooks(name, args, context, cwd) {
|
|
|
344
412
|
return { blocked: false };
|
|
345
413
|
}
|
|
346
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Run settings.json `PermissionRequest` hooks (Claude-Code parity). Fires at the
|
|
417
|
+
* exact moment a tool call would prompt the user for approval — BEFORE the
|
|
418
|
+
* prompt — so a policy hook can auto-approve (`allow`/`approve`), auto-reject
|
|
419
|
+
* (`deny`/`block`), or defer (`ask` / no decision) to the normal prompt.
|
|
420
|
+
* Returns `{ decision: "allow" | "deny" | null }`. No matching hook (or no
|
|
421
|
+
* `settingsHooks`) → `{ decision: null }` and the caller prompts exactly as
|
|
422
|
+
* before (default behaviour is byte-for-byte unchanged).
|
|
423
|
+
* @returns {{decision:("allow"|"deny"|null), reason?:string, hook?:string}}
|
|
424
|
+
*/
|
|
425
|
+
function runSettingsPermissionRequestHooks(name, args, context, cwd, reason) {
|
|
426
|
+
const matched = collectHooks(
|
|
427
|
+
context.settingsHooks,
|
|
428
|
+
"PermissionRequest",
|
|
429
|
+
name,
|
|
430
|
+
);
|
|
431
|
+
if (!matched || matched.length === 0) return { decision: null };
|
|
432
|
+
const payload = {
|
|
433
|
+
hook_event_name: "PermissionRequest",
|
|
434
|
+
tool_name: umbrellaFor(name),
|
|
435
|
+
raw_tool_name: name,
|
|
436
|
+
tool_input: args,
|
|
437
|
+
permission_reason: reason || null,
|
|
438
|
+
cwd,
|
|
439
|
+
session_id: context.sessionId || null,
|
|
440
|
+
};
|
|
441
|
+
const outcome = runCommandHooks(matched, payload, {
|
|
442
|
+
cwd,
|
|
443
|
+
event: "PermissionRequest",
|
|
444
|
+
});
|
|
445
|
+
// Precedence: deny > ask > allow > defer. The runner normalizes deny/block →
|
|
446
|
+
// "block" and short-circuits block/ask, but it COLLAPSES an "allow" into the
|
|
447
|
+
// aggregate "continue" (it only short-circuits block/ask) — so an explicit
|
|
448
|
+
// auto-approve must be recovered from the per-hook `results`. A deny or ask
|
|
449
|
+
// by any hook still wins over an allow (safety: never let an allow override a
|
|
450
|
+
// gate that another hook wanted to block or prompt for).
|
|
451
|
+
if (outcome.decision === "block") {
|
|
452
|
+
return { decision: "deny", reason: outcome.reason, hook: outcome.hook };
|
|
453
|
+
}
|
|
454
|
+
if (outcome.decision === "ask") {
|
|
455
|
+
return { decision: null }; // a hook asked → defer to the confirmer
|
|
456
|
+
}
|
|
457
|
+
const allowHook =
|
|
458
|
+
outcome.decision === "allow"
|
|
459
|
+
? { command: outcome.hook }
|
|
460
|
+
: (outcome.results || []).find((r) => r && r.decision === "allow");
|
|
461
|
+
if (allowHook) {
|
|
462
|
+
return { decision: "allow", hook: allowHook.command || null };
|
|
463
|
+
}
|
|
464
|
+
return { decision: null }; // no actionable decision → defer to the confirmer
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Resolve an interactive permission gate. Gives `PermissionRequest` hooks first
|
|
469
|
+
* say (auto-allow → true without prompting, auto-deny → false without
|
|
470
|
+
* prompting), then falls back to the injected confirmer with the identical
|
|
471
|
+
* arguments the call site would have passed. When no `PermissionRequest` hook
|
|
472
|
+
* matches this is byte-for-byte the previous `confirm(confirmArgs)` call, so
|
|
473
|
+
* every existing permission gate keeps its exact behaviour absent a hook.
|
|
474
|
+
* @returns {Promise<boolean>}
|
|
475
|
+
*/
|
|
476
|
+
async function requestInteractivePermission(
|
|
477
|
+
name,
|
|
478
|
+
args,
|
|
479
|
+
context,
|
|
480
|
+
cwd,
|
|
481
|
+
confirmArgs,
|
|
482
|
+
) {
|
|
483
|
+
const verdict = runSettingsPermissionRequestHooks(
|
|
484
|
+
name,
|
|
485
|
+
args,
|
|
486
|
+
context,
|
|
487
|
+
cwd,
|
|
488
|
+
confirmArgs?.reason,
|
|
489
|
+
);
|
|
490
|
+
if (verdict.decision === "allow") return true;
|
|
491
|
+
if (verdict.decision === "deny") return false;
|
|
492
|
+
const confirm = context.permissionConfirm || context.shellConfirm || null;
|
|
493
|
+
return typeof confirm === "function" ? await confirm(confirmArgs) : false;
|
|
494
|
+
}
|
|
495
|
+
|
|
347
496
|
// ─── Tool definitions ────────────────────────────────────────────────────
|
|
348
497
|
|
|
349
498
|
export const AGENT_TOOLS = getCodingAgentFunctionToolDefinitions();
|
|
@@ -514,6 +663,8 @@ Key behaviors:
|
|
|
514
663
|
- For long-running commands (builds, full test suites, dev servers) set run_shell { run_in_background: true } to get a task_id back immediately, then poll output and completion with check_shell { task_id }. Kill a backgrounded server with check_shell { task_id, kill: true } when finished
|
|
515
664
|
- When asked about git status, diff, log, or other repository operations, use the git tool instead of run_shell
|
|
516
665
|
- When asked about files or code, use read_file and search_files to find information
|
|
666
|
+
- Before renaming or changing a symbol, use code_intelligence (action: references/definition) to find every real usage instead of guessing with text search. It degrades to "unavailable" when no language server is installed — fall back to search_files then.
|
|
667
|
+
- After an edit, if the tool result includes a "newDiagnostics" array, you just introduced (or exposed) those errors/warnings — read them and fix before moving on. You can also run code_intelligence (action: diagnostics) on any file to check it on demand.
|
|
517
668
|
- You have multi-layer skills (built-in, marketplace, global, project-level) — use list_skills to discover them and run_skill to execute them
|
|
518
669
|
- Always explain what you're doing and show results
|
|
519
670
|
- Be concise but thorough
|
|
@@ -1079,16 +1230,12 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1079
1230
|
source: "settings rule",
|
|
1080
1231
|
});
|
|
1081
1232
|
if (ide) return ide.result;
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
rule: settingsVerdict.rule,
|
|
1089
|
-
reason: `settings rule ${settingsVerdict.rule} requires confirmation`,
|
|
1090
|
-
})
|
|
1091
|
-
: false;
|
|
1233
|
+
const ok = await requestInteractivePermission(name, args, context, cwd, {
|
|
1234
|
+
tool: name,
|
|
1235
|
+
args,
|
|
1236
|
+
rule: settingsVerdict.rule,
|
|
1237
|
+
reason: `settings rule ${settingsVerdict.rule} requires confirmation`,
|
|
1238
|
+
});
|
|
1092
1239
|
if (!ok) {
|
|
1093
1240
|
return {
|
|
1094
1241
|
error: `[Permission] Tool "${name}" requires confirmation (settings rule: ${settingsVerdict.rule}) but this run is non-interactive — denied. Do not retry; tell the user this action needs their approval.`,
|
|
@@ -1120,16 +1267,12 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1120
1267
|
await import("../lib/sensitive-file-guard.js");
|
|
1121
1268
|
const sensReason = sensitiveFileReason(args.path);
|
|
1122
1269
|
if (sensReason) {
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
rule: null,
|
|
1130
|
-
reason: `sensitive file: ${sensReason}`,
|
|
1131
|
-
})
|
|
1132
|
-
: false;
|
|
1270
|
+
const ok = await requestInteractivePermission(name, args, context, cwd, {
|
|
1271
|
+
tool: name,
|
|
1272
|
+
args,
|
|
1273
|
+
rule: null,
|
|
1274
|
+
reason: `sensitive file: ${sensReason}`,
|
|
1275
|
+
});
|
|
1133
1276
|
if (!ok) {
|
|
1134
1277
|
return {
|
|
1135
1278
|
error: `[Sensitive File] Writing "${args.path}" requires confirmation (${sensReason}) — denied. Add a settings allow rule to pre-authorize.`,
|
|
@@ -1152,16 +1295,12 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1152
1295
|
!planManager.isActive() &&
|
|
1153
1296
|
isDangerousGitCommand(args?.command)
|
|
1154
1297
|
) {
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
rule: null,
|
|
1162
|
-
reason: `destructive git command: git ${normalizeGitCommand(args.command)}`,
|
|
1163
|
-
})
|
|
1164
|
-
: false;
|
|
1298
|
+
const ok = await requestInteractivePermission(name, args, context, cwd, {
|
|
1299
|
+
tool: name,
|
|
1300
|
+
args,
|
|
1301
|
+
rule: null,
|
|
1302
|
+
reason: `destructive git command: git ${normalizeGitCommand(args.command)}`,
|
|
1303
|
+
});
|
|
1165
1304
|
if (!ok) {
|
|
1166
1305
|
return {
|
|
1167
1306
|
error: `[Destructive Git] "git ${normalizeGitCommand(args.command)}" discards work irrecoverably and requires confirmation — denied. Add a settings allow rule to pre-authorize.`,
|
|
@@ -1196,17 +1335,18 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1196
1335
|
credReason = hit ? hit.reason : null;
|
|
1197
1336
|
}
|
|
1198
1337
|
if (credReason) {
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1338
|
+
const ok = await requestInteractivePermission(
|
|
1339
|
+
name,
|
|
1340
|
+
args,
|
|
1341
|
+
context,
|
|
1342
|
+
cwd,
|
|
1343
|
+
{
|
|
1344
|
+
tool: name,
|
|
1345
|
+
args,
|
|
1346
|
+
rule: null,
|
|
1347
|
+
reason: `credential access: ${credReason}`,
|
|
1348
|
+
},
|
|
1349
|
+
);
|
|
1210
1350
|
if (!ok) {
|
|
1211
1351
|
const what =
|
|
1212
1352
|
name === "read_file" ? `Reading "${args.path}"` : "This command";
|
|
@@ -1302,6 +1442,7 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1302
1442
|
subAgentDepth: context.subAgentDepth || 0,
|
|
1303
1443
|
subAgentBudget: context.subAgentBudget || null,
|
|
1304
1444
|
interactiveApproval: context.interactiveApproval || false,
|
|
1445
|
+
settingsHooks: context.settingsHooks || null,
|
|
1305
1446
|
});
|
|
1306
1447
|
} catch (err) {
|
|
1307
1448
|
if (hookDb) {
|
|
@@ -1354,24 +1495,36 @@ export async function executeTool(name, args, context = {}) {
|
|
|
1354
1495
|
try {
|
|
1355
1496
|
const matched = collectHooks(context.settingsHooks, "PostToolUse", name);
|
|
1356
1497
|
if (matched && matched.length > 0) {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1498
|
+
// `async:true` PostToolUse hooks must NOT block the tool loop — split
|
|
1499
|
+
// them off the synchronous decision path. Without this an async hook
|
|
1500
|
+
// ran synchronously here, defeating fire-and-forget (a background lint /
|
|
1501
|
+
// test after each edit would stall the turn).
|
|
1502
|
+
const { sync, async: asyncHooks } = partitionAsyncHooks(matched);
|
|
1503
|
+
const payload = {
|
|
1504
|
+
hook_event_name: "PostToolUse",
|
|
1505
|
+
tool_name: umbrellaFor(name),
|
|
1506
|
+
raw_tool_name: name,
|
|
1507
|
+
tool_input: args,
|
|
1508
|
+
tool_response:
|
|
1509
|
+
typeof toolResult === "object"
|
|
1510
|
+
? JSON.stringify(toolResult).substring(0, 2000)
|
|
1511
|
+
: String(toolResult).substring(0, 2000),
|
|
1512
|
+
cwd,
|
|
1513
|
+
session_id: context.sessionId || null,
|
|
1514
|
+
};
|
|
1515
|
+
if (sync.length > 0) {
|
|
1516
|
+
const outcome = runCommandHooks(sync, payload, {
|
|
1368
1517
|
cwd,
|
|
1369
|
-
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1518
|
+
event: "PostToolUse",
|
|
1519
|
+
});
|
|
1520
|
+
if (outcome.decision === "block" && outcome.reason) {
|
|
1521
|
+
toolResult.hookFeedback = outcome.reason;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
// Fire-and-forget the async hooks onto the REPL-owned supervisor when
|
|
1525
|
+
// one is wired; results/rewakes drain into the next turn's context.
|
|
1526
|
+
if (asyncHooks.length > 0 && context.hookSupervisor) {
|
|
1527
|
+
context.hookSupervisor.dispatch(asyncHooks, payload, { cwd });
|
|
1375
1528
|
}
|
|
1376
1529
|
}
|
|
1377
1530
|
} catch (_err) {
|
|
@@ -1482,6 +1635,58 @@ export function writeFileVerified(filePath, content, fsImpl = fs) {
|
|
|
1482
1635
|
return { size: actual };
|
|
1483
1636
|
}
|
|
1484
1637
|
|
|
1638
|
+
// ── Edit-concurrency (read-freshness) guard ──────────────────────────────
|
|
1639
|
+
//
|
|
1640
|
+
// Per-session map: resolved file path → the mtimeMs the agent last OBSERVED for
|
|
1641
|
+
// it (via read_file, or the write/edit it just made). If a file's on-disk mtime
|
|
1642
|
+
// is NEWER than that when the agent tries to edit it, an external process wrote
|
|
1643
|
+
// the file between the agent's read and its edit — so the edit is refused and
|
|
1644
|
+
// the agent is told to re-read, instead of clobbering the concurrent change on
|
|
1645
|
+
// top of a stale understanding. A file the agent never observed is NOT blocked
|
|
1646
|
+
// (first-touch / create stays frictionless). Disable with CC_EDIT_FRESHNESS=0.
|
|
1647
|
+
const _fileObservedMtimes = new Map();
|
|
1648
|
+
|
|
1649
|
+
/** Record the current mtime of a file the agent just read or wrote. */
|
|
1650
|
+
function _recordFileObservation(filePath, fsImpl = fs) {
|
|
1651
|
+
try {
|
|
1652
|
+
_fileObservedMtimes.set(filePath, fsImpl.statSync(filePath).mtimeMs);
|
|
1653
|
+
} catch {
|
|
1654
|
+
/* best-effort — a stat failure just means no freshness baseline */
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* Return an error string when `filePath` changed on disk since the agent last
|
|
1660
|
+
* observed it (external concurrent edit), else null (fresh, un-observed, or
|
|
1661
|
+
* disabled).
|
|
1662
|
+
*/
|
|
1663
|
+
function _checkFileFreshness(filePath, fsImpl = fs) {
|
|
1664
|
+
if (process.env.CC_EDIT_FRESHNESS === "0") return null;
|
|
1665
|
+
const known = _fileObservedMtimes.get(filePath);
|
|
1666
|
+
if (known === undefined) return null; // never observed → don't block
|
|
1667
|
+
let current;
|
|
1668
|
+
try {
|
|
1669
|
+
current = fsImpl.statSync(filePath).mtimeMs;
|
|
1670
|
+
} catch {
|
|
1671
|
+
return null;
|
|
1672
|
+
}
|
|
1673
|
+
if (current > known) {
|
|
1674
|
+
return (
|
|
1675
|
+
`File changed on disk since you last read it: ${filePath}. Another ` +
|
|
1676
|
+
`process modified it after your last read — re-read the file before ` +
|
|
1677
|
+
`editing so your change is based on its current content (this guard ` +
|
|
1678
|
+
`prevents silently clobbering a concurrent edit; set CC_EDIT_FRESHNESS=0 ` +
|
|
1679
|
+
`to disable).`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/** Clear the read-freshness map (new session / tests). */
|
|
1686
|
+
export function _resetFileFreshness() {
|
|
1687
|
+
_fileObservedMtimes.clear();
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1485
1690
|
// Short, stable-ish id for a freshly inserted notebook cell (nbformat 4.5+).
|
|
1486
1691
|
function _newNotebookCellId() {
|
|
1487
1692
|
return Math.random().toString(36).slice(2, 10);
|
|
@@ -1693,6 +1898,139 @@ export function _ingestSearchOutput(output, ctx) {
|
|
|
1693
1898
|
/**
|
|
1694
1899
|
* Inner tool execution — no hooks, no plan-mode checks.
|
|
1695
1900
|
*/
|
|
1901
|
+
// ─── Shared code-intelligence (LSP) pool ──────────────────────────────────
|
|
1902
|
+
//
|
|
1903
|
+
// A language server is expensive to start (spawns a process + indexes the
|
|
1904
|
+
// project), so the `code_intelligence` tool reuses ONE warm CodeIntelligence
|
|
1905
|
+
// per project root across tool calls within a run. To avoid the resource-leak
|
|
1906
|
+
// trap (orphaned server processes / dangling timers), each root auto-disposes
|
|
1907
|
+
// after an idle window, and a process-exit hook is registered once as a
|
|
1908
|
+
// backstop. `coldStart:true` makes the first query per file wait for the
|
|
1909
|
+
// project to load; warmed queries return immediately.
|
|
1910
|
+
const _codeIntelPool = new Map(); // root -> { ci, idleTimer }
|
|
1911
|
+
const CODE_INTEL_IDLE_MS = 60_000;
|
|
1912
|
+
let _codeIntelExitHooked = false;
|
|
1913
|
+
|
|
1914
|
+
export async function _getSharedCodeIntel(cwd) {
|
|
1915
|
+
const root = path.resolve(cwd || process.cwd());
|
|
1916
|
+
let entry = _codeIntelPool.get(root);
|
|
1917
|
+
if (!entry) {
|
|
1918
|
+
const { CodeIntelligence } =
|
|
1919
|
+
await import("../lib/lsp/code-intelligence.js");
|
|
1920
|
+
// Re-check AFTER the await: two concurrent callers (the parallel read-only
|
|
1921
|
+
// batch runs several `code_intelligence` calls at once) both observed an
|
|
1922
|
+
// empty pool before this `await import`, so without this guard both would
|
|
1923
|
+
// construct a CodeIntelligence — each spawning its own language server — and
|
|
1924
|
+
// the second `set` would orphan the first (leaked server process) while its
|
|
1925
|
+
// stale idle-timer, still bound to `root`, later evicts the wrong (warm,
|
|
1926
|
+
// in-use) entry. This is exactly the orphaned-server/dangling-timer trap the
|
|
1927
|
+
// pool exists to prevent. Construction below has NO further await, so once
|
|
1928
|
+
// one continuation wins the re-check the other reuses its entry — the window
|
|
1929
|
+
// is fully closed.
|
|
1930
|
+
entry = _codeIntelPool.get(root);
|
|
1931
|
+
if (!entry) {
|
|
1932
|
+
entry = {
|
|
1933
|
+
ci: new CodeIntelligence({ projectRoot: root, coldStart: true }),
|
|
1934
|
+
idleTimer: null,
|
|
1935
|
+
};
|
|
1936
|
+
_codeIntelPool.set(root, entry);
|
|
1937
|
+
if (!_codeIntelExitHooked) {
|
|
1938
|
+
_codeIntelExitHooked = true;
|
|
1939
|
+
process.once("exit", () => {
|
|
1940
|
+
for (const e of _codeIntelPool.values()) {
|
|
1941
|
+
try {
|
|
1942
|
+
e.ci.dispose();
|
|
1943
|
+
} catch {
|
|
1944
|
+
/* best-effort teardown on exit */
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
if (entry.idleTimer) clearTimeout(entry.idleTimer);
|
|
1952
|
+
entry.idleTimer = setTimeout(() => {
|
|
1953
|
+
_codeIntelPool.delete(root);
|
|
1954
|
+
entry.ci.dispose().catch(() => {});
|
|
1955
|
+
}, CODE_INTEL_IDLE_MS);
|
|
1956
|
+
if (typeof entry.idleTimer.unref === "function") entry.idleTimer.unref();
|
|
1957
|
+
return entry.ci;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
/** Dispose every pooled language server now (used by tests + explicit shutdown). */
|
|
1961
|
+
export async function disposeSharedCodeIntel() {
|
|
1962
|
+
const entries = [..._codeIntelPool.values()];
|
|
1963
|
+
_codeIntelPool.clear();
|
|
1964
|
+
for (const e of entries) {
|
|
1965
|
+
if (e.idleTimer) clearTimeout(e.idleTimer);
|
|
1966
|
+
}
|
|
1967
|
+
await Promise.all(entries.map((e) => e.ci.dispose().catch(() => {})));
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Hard wall-clock cap so post-edit diagnostics never stall the agent: a cold
|
|
1971
|
+
// language server can take seconds to load a project on the FIRST edit; after
|
|
1972
|
+
// that the server is warm and answers in well under a second.
|
|
1973
|
+
const EDIT_DIAGNOSTICS_WALL_MS = 6000;
|
|
1974
|
+
|
|
1975
|
+
/**
|
|
1976
|
+
* Best-effort "did this edit introduce a new error?" check. Run after a
|
|
1977
|
+
* successful workspace edit so the model sees fresh type/syntax errors in the
|
|
1978
|
+
* SAME turn instead of discovering them on the next build (the plan's
|
|
1979
|
+
* "编辑后执行增量诊断并将相关错误回喂 Agent"). Returns a compact array of
|
|
1980
|
+
* error/warning diagnostics, or null.
|
|
1981
|
+
*
|
|
1982
|
+
* Zero cost when no language server is installed for the file (probes first,
|
|
1983
|
+
* never cold-starts a server that isn't there) and fully bounded in time.
|
|
1984
|
+
* Disable entirely with CC_EDIT_DIAGNOSTICS=0.
|
|
1985
|
+
*/
|
|
1986
|
+
async function _postEditDiagnostics(filePath, cwd) {
|
|
1987
|
+
if (process.env.CC_EDIT_DIAGNOSTICS === "0") return null;
|
|
1988
|
+
let hasServer = false;
|
|
1989
|
+
try {
|
|
1990
|
+
const { languageIdForFile, resolveServer } =
|
|
1991
|
+
await import("../lib/lsp/lsp-server-registry.js");
|
|
1992
|
+
const languageId = languageIdForFile(filePath);
|
|
1993
|
+
if (!languageId) return null; // unsupported file type — no LSP, no cost
|
|
1994
|
+
hasServer = Boolean(
|
|
1995
|
+
resolveServer(languageId, path.resolve(cwd || process.cwd())),
|
|
1996
|
+
);
|
|
1997
|
+
} catch {
|
|
1998
|
+
return null;
|
|
1999
|
+
}
|
|
2000
|
+
if (!hasServer) return null;
|
|
2001
|
+
|
|
2002
|
+
const query = (async () => {
|
|
2003
|
+
try {
|
|
2004
|
+
const ci = await _getSharedCodeIntel(cwd);
|
|
2005
|
+
const res = await ci.refreshFile(filePath, { timeoutMs: 3000 });
|
|
2006
|
+
if (!res || res.available === false) return null;
|
|
2007
|
+
const diags = (res.diagnostics || []).filter(
|
|
2008
|
+
(d) => d.severity === "error" || d.severity === "warning",
|
|
2009
|
+
);
|
|
2010
|
+
return diags.length ? diags.slice(0, 20) : null;
|
|
2011
|
+
} catch {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
})();
|
|
2015
|
+
|
|
2016
|
+
let capTimer;
|
|
2017
|
+
const cap = new Promise((resolve) => {
|
|
2018
|
+
capTimer = setTimeout(() => resolve(null), EDIT_DIAGNOSTICS_WALL_MS);
|
|
2019
|
+
if (typeof capTimer.unref === "function") capTimer.unref();
|
|
2020
|
+
});
|
|
2021
|
+
try {
|
|
2022
|
+
return await Promise.race([query, cap]);
|
|
2023
|
+
} finally {
|
|
2024
|
+
clearTimeout(capTimer);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/** Merge post-edit diagnostics into a successful edit result (no-op if none). */
|
|
2029
|
+
async function _withPostEditDiagnostics(result, filePath, cwd) {
|
|
2030
|
+
const newDiagnostics = await _postEditDiagnostics(filePath, cwd);
|
|
2031
|
+
return newDiagnostics ? { ...result, newDiagnostics } : result;
|
|
2032
|
+
}
|
|
2033
|
+
|
|
1696
2034
|
async function executeToolInner(
|
|
1697
2035
|
name,
|
|
1698
2036
|
args,
|
|
@@ -1717,6 +2055,7 @@ async function executeToolInner(
|
|
|
1717
2055
|
subAgentDepth = 0,
|
|
1718
2056
|
subAgentBudget = null,
|
|
1719
2057
|
interactiveApproval = false,
|
|
2058
|
+
settingsHooks = null,
|
|
1720
2059
|
},
|
|
1721
2060
|
) {
|
|
1722
2061
|
const localToolDescriptor =
|
|
@@ -1769,6 +2108,9 @@ async function executeToolInner(
|
|
|
1769
2108
|
});
|
|
1770
2109
|
}
|
|
1771
2110
|
const content = fs.readFileSync(filePath, "utf8");
|
|
2111
|
+
// Record the mtime so a later edit can detect an external change that
|
|
2112
|
+
// happened between this read and the edit (read-freshness guard).
|
|
2113
|
+
_recordFileObservation(filePath);
|
|
1772
2114
|
// Jupyter notebooks: render a compact cell listing (index/id/type/source,
|
|
1773
2115
|
// outputs summarized) so the model can find cells for notebook_edit
|
|
1774
2116
|
// without ingesting raw JSON / base64 output blobs. `raw:true` returns the
|
|
@@ -1832,16 +2174,25 @@ async function executeToolInner(
|
|
|
1832
2174
|
case "write_file": {
|
|
1833
2175
|
const filePath = path.resolve(cwd, args.path);
|
|
1834
2176
|
const dir = path.dirname(filePath);
|
|
2177
|
+
// Overwriting an existing file: refuse if it changed on disk since the
|
|
2178
|
+
// agent last observed it (external concurrent edit).
|
|
2179
|
+
if (fs.existsSync(filePath)) {
|
|
2180
|
+
const stale = _checkFileFreshness(filePath);
|
|
2181
|
+
if (stale) return attachDescriptor({ error: stale });
|
|
2182
|
+
}
|
|
1835
2183
|
if (!fs.existsSync(dir)) {
|
|
1836
2184
|
fs.mkdirSync(dir, { recursive: true });
|
|
1837
2185
|
}
|
|
1838
2186
|
const wrote = writeFileVerified(filePath, args.content);
|
|
1839
2187
|
if (wrote.error) return attachDescriptor({ error: wrote.error });
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2188
|
+
_recordFileObservation(filePath);
|
|
2189
|
+
return attachDescriptor(
|
|
2190
|
+
await _withPostEditDiagnostics(
|
|
2191
|
+
{ success: true, path: filePath, size: wrote.size },
|
|
2192
|
+
filePath,
|
|
2193
|
+
cwd,
|
|
2194
|
+
),
|
|
2195
|
+
);
|
|
1845
2196
|
}
|
|
1846
2197
|
|
|
1847
2198
|
case "notebook_edit": {
|
|
@@ -1876,6 +2227,8 @@ async function executeToolInner(
|
|
|
1876
2227
|
if (typeof args.new_string !== "string") {
|
|
1877
2228
|
return attachDescriptor({ error: "new_string must be a string" });
|
|
1878
2229
|
}
|
|
2230
|
+
const staleEdit = _checkFileFreshness(filePath);
|
|
2231
|
+
if (staleEdit) return attachDescriptor({ error: staleEdit });
|
|
1879
2232
|
const content = fs.readFileSync(filePath, "utf8");
|
|
1880
2233
|
const replaceAll = args.replace_all === true;
|
|
1881
2234
|
const { count, newContent } = applyLiteralEdit(
|
|
@@ -1897,12 +2250,19 @@ async function executeToolInner(
|
|
|
1897
2250
|
}
|
|
1898
2251
|
const wrote = writeFileVerified(filePath, newContent);
|
|
1899
2252
|
if (wrote.error) return attachDescriptor({ error: wrote.error });
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2253
|
+
_recordFileObservation(filePath);
|
|
2254
|
+
return attachDescriptor(
|
|
2255
|
+
await _withPostEditDiagnostics(
|
|
2256
|
+
{
|
|
2257
|
+
success: true,
|
|
2258
|
+
path: filePath,
|
|
2259
|
+
size: wrote.size,
|
|
2260
|
+
replaced: replaceAll ? count : 1,
|
|
2261
|
+
},
|
|
2262
|
+
filePath,
|
|
2263
|
+
cwd,
|
|
2264
|
+
),
|
|
2265
|
+
);
|
|
1906
2266
|
}
|
|
1907
2267
|
|
|
1908
2268
|
case "edit_file_hashed": {
|
|
@@ -1922,6 +2282,8 @@ async function executeToolInner(
|
|
|
1922
2282
|
if (typeof args.new_line !== "string") {
|
|
1923
2283
|
return attachDescriptor({ error: "new_line must be a string" });
|
|
1924
2284
|
}
|
|
2285
|
+
const staleHashed = _checkFileFreshness(filePath);
|
|
2286
|
+
if (staleHashed) return attachDescriptor({ error: staleHashed });
|
|
1925
2287
|
const original = fs.readFileSync(filePath, "utf8");
|
|
1926
2288
|
const result = replaceByHash(original, {
|
|
1927
2289
|
anchorHash: args.anchor_hash,
|
|
@@ -1946,13 +2308,20 @@ async function executeToolInner(
|
|
|
1946
2308
|
}
|
|
1947
2309
|
const wrote = writeFileVerified(filePath, result.content);
|
|
1948
2310
|
if (wrote.error) return attachDescriptor({ error: wrote.error });
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
2311
|
+
_recordFileObservation(filePath);
|
|
2312
|
+
return attachDescriptor(
|
|
2313
|
+
await _withPostEditDiagnostics(
|
|
2314
|
+
{
|
|
2315
|
+
success: true,
|
|
2316
|
+
path: filePath,
|
|
2317
|
+
size: wrote.size,
|
|
2318
|
+
lineNumber: result.lineNumber,
|
|
2319
|
+
previousContent: result.previousContent,
|
|
2320
|
+
},
|
|
2321
|
+
filePath,
|
|
2322
|
+
cwd,
|
|
2323
|
+
),
|
|
2324
|
+
);
|
|
1956
2325
|
}
|
|
1957
2326
|
|
|
1958
2327
|
case "run_shell": {
|
|
@@ -2108,6 +2477,9 @@ async function executeToolInner(
|
|
|
2108
2477
|
task.endedAt = new Date().toISOString();
|
|
2109
2478
|
}
|
|
2110
2479
|
_backgroundShellTasks.set(id, task);
|
|
2480
|
+
// Arm the process-exit net so a Ctrl-C / hard exit can't orphan this
|
|
2481
|
+
// task (the normal `finally` reaper is bypassed by signals).
|
|
2482
|
+
_ensureBgExitReaper();
|
|
2111
2483
|
return attachDescriptor(
|
|
2112
2484
|
{
|
|
2113
2485
|
background: true,
|
|
@@ -2125,20 +2497,65 @@ async function executeToolInner(
|
|
|
2125
2497
|
if (sandbox) {
|
|
2126
2498
|
const { executeSandboxedShell, sandboxSummary } =
|
|
2127
2499
|
await import("../lib/agent-sandbox.js");
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2500
|
+
// Domain-restricted networking is ENFORCED by routing the sandboxed
|
|
2501
|
+
// process's egress through a local filtering proxy (see
|
|
2502
|
+
// sandbox-egress-proxy.js). Start it only when the policy actually
|
|
2503
|
+
// restricts domains and network is on; tear it down after the command.
|
|
2504
|
+
const sboxPolicy = sandbox.policy || {};
|
|
2505
|
+
const needsEgress =
|
|
2506
|
+
sandbox.network === true &&
|
|
2507
|
+
(sboxPolicy.allowedDomains?.length || 0) +
|
|
2508
|
+
(sboxPolicy.deniedDomains?.length || 0) >
|
|
2509
|
+
0;
|
|
2510
|
+
let egressProxy = null;
|
|
2511
|
+
let proxyHandle = null;
|
|
2512
|
+
if (needsEgress) {
|
|
2513
|
+
try {
|
|
2514
|
+
const { createEgressProxy } =
|
|
2515
|
+
await import("../lib/sandbox-egress-proxy.js");
|
|
2516
|
+
proxyHandle = createEgressProxy(
|
|
2517
|
+
{
|
|
2518
|
+
allowedDomains: sboxPolicy.allowedDomains || [],
|
|
2519
|
+
deniedDomains: sboxPolicy.deniedDomains || [],
|
|
2520
|
+
allowPrivate: sboxPolicy.allowPrivate === true,
|
|
2521
|
+
},
|
|
2522
|
+
{ bindHost: "0.0.0.0" }, // reachable from the container/netns
|
|
2523
|
+
);
|
|
2524
|
+
const listened = await proxyHandle.listen();
|
|
2525
|
+
egressProxy = { port: listened.port };
|
|
2526
|
+
} catch {
|
|
2527
|
+
// If the proxy can't start, leave egressProxy null so the sandbox
|
|
2528
|
+
// fails closed (refuses) rather than running without enforcement.
|
|
2529
|
+
proxyHandle = null;
|
|
2530
|
+
egressProxy = null;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
let result;
|
|
2534
|
+
try {
|
|
2535
|
+
result = executeSandboxedShell(args.command, sandbox, {
|
|
2536
|
+
cwd: args.cwd || cwd,
|
|
2537
|
+
timeout: _resolveShellTimeout(args.timeout),
|
|
2538
|
+
maxBuffer: 1024 * 1024,
|
|
2539
|
+
egressProxy,
|
|
2540
|
+
env: {
|
|
2541
|
+
CLAUDECODE: "1",
|
|
2542
|
+
...(sessionId
|
|
2543
|
+
? {
|
|
2544
|
+
CC_SESSION_ID: String(sessionId),
|
|
2545
|
+
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
2546
|
+
}
|
|
2547
|
+
: {}),
|
|
2548
|
+
},
|
|
2549
|
+
});
|
|
2550
|
+
} finally {
|
|
2551
|
+
if (proxyHandle) {
|
|
2552
|
+
try {
|
|
2553
|
+
await proxyHandle.close();
|
|
2554
|
+
} catch {
|
|
2555
|
+
/* best-effort teardown */
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2142
2559
|
const common = {
|
|
2143
2560
|
sandbox: sandboxSummary(sandbox),
|
|
2144
2561
|
shellCommandPolicy: shellPolicy,
|
|
@@ -2199,6 +2616,14 @@ async function executeToolInner(
|
|
|
2199
2616
|
return attachDescriptor(
|
|
2200
2617
|
{
|
|
2201
2618
|
error: err.message.substring(0, 2000),
|
|
2619
|
+
// Surface stdout too: a failing command (test runner / linter /
|
|
2620
|
+
// build) usually prints WHAT failed to stdout and only the summary
|
|
2621
|
+
// to stderr, so dropping it on non-zero exit blinds the agent to the
|
|
2622
|
+
// actual failure. Only attach it when there IS output (a timeout with
|
|
2623
|
+
// no output keeps the field absent), mirroring the success path.
|
|
2624
|
+
...(err.stdout
|
|
2625
|
+
? { stdout: String(err.stdout).substring(0, 30000) }
|
|
2626
|
+
: {}),
|
|
2202
2627
|
stderr: (err.stderr || "").substring(0, 2000),
|
|
2203
2628
|
exitCode: err.status,
|
|
2204
2629
|
shellCommandPolicy: shellPolicy,
|
|
@@ -2351,6 +2776,7 @@ async function executeToolInner(
|
|
|
2351
2776
|
llmOptions,
|
|
2352
2777
|
subAgentDepth,
|
|
2353
2778
|
subAgentBudget,
|
|
2779
|
+
settingsHooks,
|
|
2354
2780
|
}),
|
|
2355
2781
|
);
|
|
2356
2782
|
}
|
|
@@ -2615,6 +3041,95 @@ async function executeToolInner(
|
|
|
2615
3041
|
return attachDescriptor(isContent ? { matches: hits } : { files: hits });
|
|
2616
3042
|
}
|
|
2617
3043
|
|
|
3044
|
+
case "code_intelligence": {
|
|
3045
|
+
const action = String(args.action || "").trim();
|
|
3046
|
+
const positionActions = new Set([
|
|
3047
|
+
"definition",
|
|
3048
|
+
"references",
|
|
3049
|
+
"hover",
|
|
3050
|
+
"rename_preview",
|
|
3051
|
+
]);
|
|
3052
|
+
// Validate up front so the model gets a precise correction instead of a
|
|
3053
|
+
// cryptic crash deep in the LSP layer.
|
|
3054
|
+
if (action !== "workspace_symbols" && !args.file) {
|
|
3055
|
+
return attachDescriptor({
|
|
3056
|
+
error: `code_intelligence action "${action}" requires "file".`,
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
if (
|
|
3060
|
+
positionActions.has(action) &&
|
|
3061
|
+
(args.line == null || args.col == null)
|
|
3062
|
+
) {
|
|
3063
|
+
return attachDescriptor({
|
|
3064
|
+
error: `code_intelligence action "${action}" requires 1-based "line" and "col".`,
|
|
3065
|
+
});
|
|
3066
|
+
}
|
|
3067
|
+
if (action === "workspace_symbols" && !args.query) {
|
|
3068
|
+
return attachDescriptor({
|
|
3069
|
+
error: `code_intelligence action "workspace_symbols" requires "query".`,
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
if (action === "rename_preview" && !args.new_name) {
|
|
3073
|
+
return attachDescriptor({
|
|
3074
|
+
error: `code_intelligence action "rename_preview" requires "new_name".`,
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
const ci = await _getSharedCodeIntel(cwd);
|
|
3078
|
+
const file = args.file ? path.resolve(cwd, args.file) : null;
|
|
3079
|
+
let res;
|
|
3080
|
+
try {
|
|
3081
|
+
switch (action) {
|
|
3082
|
+
case "definition":
|
|
3083
|
+
res = await ci.definition(file, args.line, args.col);
|
|
3084
|
+
break;
|
|
3085
|
+
case "references":
|
|
3086
|
+
res = await ci.references(file, args.line, args.col);
|
|
3087
|
+
break;
|
|
3088
|
+
case "hover":
|
|
3089
|
+
res = await ci.hover(file, args.line, args.col);
|
|
3090
|
+
break;
|
|
3091
|
+
case "document_symbols":
|
|
3092
|
+
res = await ci.documentSymbols(file);
|
|
3093
|
+
break;
|
|
3094
|
+
case "workspace_symbols":
|
|
3095
|
+
res = await ci.workspaceSymbols(String(args.query));
|
|
3096
|
+
break;
|
|
3097
|
+
case "diagnostics":
|
|
3098
|
+
res = await ci.diagnostics(file);
|
|
3099
|
+
break;
|
|
3100
|
+
case "rename_preview":
|
|
3101
|
+
res = await ci.renamePreview(
|
|
3102
|
+
file,
|
|
3103
|
+
args.line,
|
|
3104
|
+
args.col,
|
|
3105
|
+
args.new_name,
|
|
3106
|
+
);
|
|
3107
|
+
break;
|
|
3108
|
+
default:
|
|
3109
|
+
return attachDescriptor({
|
|
3110
|
+
error:
|
|
3111
|
+
`Unknown code_intelligence action "${action}". Valid: definition, ` +
|
|
3112
|
+
`references, hover, document_symbols, workspace_symbols, ` +
|
|
3113
|
+
`diagnostics, rename_preview.`,
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
} catch (err) {
|
|
3117
|
+
return attachDescriptor({
|
|
3118
|
+
error: `code_intelligence failed: ${err.message}`,
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
// No language server installed for this file — tell the agent to fall back
|
|
3122
|
+
// rather than looping on an empty result.
|
|
3123
|
+
if (res && res.available === false) {
|
|
3124
|
+
return attachDescriptor({
|
|
3125
|
+
unavailable: true,
|
|
3126
|
+
reason: res.reason,
|
|
3127
|
+
hint: "No language server available — use search_files / read_file instead.",
|
|
3128
|
+
});
|
|
3129
|
+
}
|
|
3130
|
+
return attachDescriptor(res);
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2618
3133
|
case "list_dir": {
|
|
2619
3134
|
const dirPath = args.path ? path.resolve(cwd, args.path) : cwd;
|
|
2620
3135
|
if (!fs.existsSync(dirPath)) {
|
|
@@ -3296,6 +3811,39 @@ async function _executeSpawnSubAgent(args, ctx) {
|
|
|
3296
3811
|
const parentSessionId = ctx.sessionId || null;
|
|
3297
3812
|
const interaction = ctx.interaction || null;
|
|
3298
3813
|
|
|
3814
|
+
// settings.json SubagentStart hooks (Claude-Code parity): fire BEFORE the
|
|
3815
|
+
// sub-agent runs, so a policy hook can VETO the spawn (`block`) or INJECT
|
|
3816
|
+
// extra context that gets prepended to the child's inherited context. This is
|
|
3817
|
+
// the mirror of the existing SubagentStop fire (which runs after the summary
|
|
3818
|
+
// returns). Best-effort — a hook error never blocks the spawn.
|
|
3819
|
+
if (ctx.settingsHooks) {
|
|
3820
|
+
try {
|
|
3821
|
+
const startOutcome = runObserveHooks(
|
|
3822
|
+
ctx.settingsHooks,
|
|
3823
|
+
"SubagentStart",
|
|
3824
|
+
{
|
|
3825
|
+
session_id: parentSessionId,
|
|
3826
|
+
role: role || args.agent || null,
|
|
3827
|
+
subagent_task: String(task || "").substring(0, 2000),
|
|
3828
|
+
},
|
|
3829
|
+
{ cwd: ctx.cwd },
|
|
3830
|
+
);
|
|
3831
|
+
if (startOutcome.decision === "block" && startOutcome.reason) {
|
|
3832
|
+
return {
|
|
3833
|
+
error: `spawn_sub_agent blocked by SubagentStart hook: ${startOutcome.reason}`,
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
const injected = aggregateContext(startOutcome.results);
|
|
3837
|
+
if (injected) {
|
|
3838
|
+
resolvedContext = resolvedContext
|
|
3839
|
+
? `${resolvedContext}\n---\n${injected}`
|
|
3840
|
+
: injected;
|
|
3841
|
+
}
|
|
3842
|
+
} catch (_err) {
|
|
3843
|
+
// SubagentStart hooks are best-effort — never break the spawn.
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3299
3847
|
// Inherit the parent's provider / base-url / key; a named subagent's `model:`
|
|
3300
3848
|
// frontmatter (mdModel) overrides just the model, else keep the parent's.
|
|
3301
3849
|
const parentLlm = ctx.llmOptions || {};
|
|
@@ -4629,6 +5177,7 @@ export function _normalizeAnthropicResponse(data) {
|
|
|
4629
5177
|
const _CHECKPOINT_READ_ONLY = new Set([
|
|
4630
5178
|
"read_file",
|
|
4631
5179
|
"search_files",
|
|
5180
|
+
"code_intelligence",
|
|
4632
5181
|
"list_dir",
|
|
4633
5182
|
"list_skills",
|
|
4634
5183
|
"search_sessions",
|
|
@@ -4724,6 +5273,54 @@ async function _getAutoCompactor(options) {
|
|
|
4724
5273
|
return compressor;
|
|
4725
5274
|
}
|
|
4726
5275
|
|
|
5276
|
+
/**
|
|
5277
|
+
* Run `fn` inside an OpenTelemetry span when `options.recorder` is attached,
|
|
5278
|
+
* else run it bare (zero overhead on the un-instrumented path). `onResult`
|
|
5279
|
+
* gets (span, result) to stamp result-derived attributes (token usage, tool
|
|
5280
|
+
* status). An exception is recorded on the span (category `errCategory`) and
|
|
5281
|
+
* re-thrown so the loop's own error handling is unchanged. Kept dependency-free
|
|
5282
|
+
* — the recorder is the OTel-shaped TelemetryRecorder passed in by the caller
|
|
5283
|
+
* (eval / a future `--otlp` agent flag); the real agent loop now EMITS
|
|
5284
|
+
* model/tool/retry spans, not just eval.
|
|
5285
|
+
*/
|
|
5286
|
+
async function _withSpan(recorder, name, attrs, fn, onResult, errCategory) {
|
|
5287
|
+
if (!recorder || typeof recorder.startSpan !== "function") return fn();
|
|
5288
|
+
const span = recorder.startSpan(name, attrs);
|
|
5289
|
+
try {
|
|
5290
|
+
const r = await fn();
|
|
5291
|
+
if (onResult) {
|
|
5292
|
+
try {
|
|
5293
|
+
onResult(span, r);
|
|
5294
|
+
} catch {
|
|
5295
|
+
/* attribute stamping is best-effort */
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
span.end();
|
|
5299
|
+
return r;
|
|
5300
|
+
} catch (err) {
|
|
5301
|
+
try {
|
|
5302
|
+
span.recordException(err, errCategory || "error");
|
|
5303
|
+
span.end();
|
|
5304
|
+
} catch {
|
|
5305
|
+
/* span teardown is best-effort */
|
|
5306
|
+
}
|
|
5307
|
+
throw err;
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
|
|
5311
|
+
/** Normalize the varied provider usage shapes into input/output/cache tokens. */
|
|
5312
|
+
function _usageTokens(usage) {
|
|
5313
|
+
if (!usage || typeof usage !== "object") return null;
|
|
5314
|
+
const input = usage.prompt_tokens ?? usage.input_tokens ?? null;
|
|
5315
|
+
const output = usage.completion_tokens ?? usage.output_tokens ?? null;
|
|
5316
|
+
const cacheRead =
|
|
5317
|
+
usage.cache_read_input_tokens ??
|
|
5318
|
+
usage.prompt_tokens_details?.cached_tokens ??
|
|
5319
|
+
null;
|
|
5320
|
+
const cacheWrite = usage.cache_creation_input_tokens ?? null;
|
|
5321
|
+
return { input, output, cacheRead, cacheWrite };
|
|
5322
|
+
}
|
|
5323
|
+
|
|
4727
5324
|
export async function* agentLoop(messages, options) {
|
|
4728
5325
|
// Shared iteration budget — replaces hardcoded MAX_ITERATIONS.
|
|
4729
5326
|
// When options.iterationBudget is provided (e.g. from parent agent),
|
|
@@ -4732,6 +5329,9 @@ export async function* agentLoop(messages, options) {
|
|
|
4732
5329
|
await import("../lib/iteration-budget.js");
|
|
4733
5330
|
const budget = options.iterationBudget || new IterationBudget();
|
|
4734
5331
|
const signal = options.signal || null;
|
|
5332
|
+
// Optional OpenTelemetry recorder (TelemetryRecorder). When present, the loop
|
|
5333
|
+
// emits model/tool/retry spans + a per-run counter; when absent, zero cost.
|
|
5334
|
+
const recorder = options.recorder || null;
|
|
4735
5335
|
const toolContext = {
|
|
4736
5336
|
hookDb: options.hookDb || null,
|
|
4737
5337
|
skillLoader: options.skillLoader || _defaultSkillLoader,
|
|
@@ -4768,6 +5368,10 @@ export async function* agentLoop(messages, options) {
|
|
|
4768
5368
|
permissionRules: options.permissionRules || null,
|
|
4769
5369
|
permissionConfirm: options.permissionConfirm || null,
|
|
4770
5370
|
settingsHooks: options.settingsHooks || null,
|
|
5371
|
+
// Async-hook supervisor (REPL-owned): lets PostToolUse `async:true` hooks
|
|
5372
|
+
// run fire-and-forget instead of blocking the tool loop. Optional — when
|
|
5373
|
+
// absent, async PostToolUse hooks are simply skipped (never run sync).
|
|
5374
|
+
hookSupervisor: options.hookSupervisor || null,
|
|
4771
5375
|
autoCheckpoint: options.autoCheckpoint || false,
|
|
4772
5376
|
checkpointSession:
|
|
4773
5377
|
options.checkpointSession || options.sessionId || "agent",
|
|
@@ -4849,6 +5453,9 @@ export async function* agentLoop(messages, options) {
|
|
|
4849
5453
|
await import("../lib/runnable-provider.js");
|
|
4850
5454
|
llmCall = makeRunnableProviderFallback(llmCall, {
|
|
4851
5455
|
onFallback: ({ from, to, reason, fromModel, toModel }) => {
|
|
5456
|
+
// Telemetry: a provider/model fallback is a reliability signal — count
|
|
5457
|
+
// it (keyed by reason) so a run's OTLP export shows retry/fallback rate.
|
|
5458
|
+
if (recorder) recorder.counter("agent.model.fallback", 1, { reason });
|
|
4852
5459
|
// Switching VENDORS (or relabelling via baseUrl) must NEVER be silent —
|
|
4853
5460
|
// a user who configured volcengine deserves to know it ran on another
|
|
4854
5461
|
// provider/model. Build a human message and hand it to the driver's
|
|
@@ -4994,10 +5601,22 @@ export async function* agentLoop(messages, options) {
|
|
|
4994
5601
|
reason: preCompactReason,
|
|
4995
5602
|
};
|
|
4996
5603
|
}
|
|
5604
|
+
// Auto-pin (OPT-IN): when enabled, the original task (first user turn)
|
|
5605
|
+
// is pinned so compaction can't drop it. Off by default → no predicate
|
|
5606
|
+
// is passed and compaction is byte-identical to before.
|
|
5607
|
+
let pinOpts = {};
|
|
5608
|
+
if (options.autoPin) {
|
|
5609
|
+
const { buildAutoPinPredicate } = await import("./auto-pin.js");
|
|
5610
|
+
const isPinned = buildAutoPinPredicate(messages, options.autoPin);
|
|
5611
|
+
if (isPinned) pinOpts = { isPinned };
|
|
5612
|
+
}
|
|
4997
5613
|
const { messages: compacted, stats } =
|
|
4998
5614
|
!needFull || preCompactBlocked
|
|
4999
5615
|
? { messages, stats: { saved: 0 } }
|
|
5000
|
-
: await compactor.compress(messages, {
|
|
5616
|
+
: await compactor.compress(messages, {
|
|
5617
|
+
preserveToolPairs: true,
|
|
5618
|
+
...pinOpts,
|
|
5619
|
+
});
|
|
5001
5620
|
if (stats.saved > 0 && compacted.length < messages.length) {
|
|
5002
5621
|
messages.splice(0, messages.length, ...compacted);
|
|
5003
5622
|
// Persist the compaction so a later --resume rebuilds from the
|
|
@@ -5064,7 +5683,38 @@ export async function* agentLoop(messages, options) {
|
|
|
5064
5683
|
}
|
|
5065
5684
|
}
|
|
5066
5685
|
|
|
5067
|
-
const result = await
|
|
5686
|
+
const result = await _withSpan(
|
|
5687
|
+
recorder,
|
|
5688
|
+
"agent.model",
|
|
5689
|
+
{
|
|
5690
|
+
"gen_ai.system": options.provider || "ollama",
|
|
5691
|
+
"gen_ai.request.model": options.model || "unknown",
|
|
5692
|
+
"agent.iteration": budget.consumed,
|
|
5693
|
+
},
|
|
5694
|
+
() => llmCall(callMessages, options),
|
|
5695
|
+
(span, r) => {
|
|
5696
|
+
const t = _usageTokens(r?.usage);
|
|
5697
|
+
if (t) {
|
|
5698
|
+
if (t.input != null)
|
|
5699
|
+
span.setAttribute("gen_ai.usage.input_tokens", t.input);
|
|
5700
|
+
if (t.output != null)
|
|
5701
|
+
span.setAttribute("gen_ai.usage.output_tokens", t.output);
|
|
5702
|
+
// Cache read/write tokens — the prompt-caching hit rate the plan's
|
|
5703
|
+
// reliability telemetry cares about.
|
|
5704
|
+
if (t.cacheRead != null)
|
|
5705
|
+
span.setAttribute("gen_ai.usage.cache_read_tokens", t.cacheRead);
|
|
5706
|
+
if (t.cacheWrite != null)
|
|
5707
|
+
span.setAttribute("gen_ai.usage.cache_write_tokens", t.cacheWrite);
|
|
5708
|
+
}
|
|
5709
|
+
span.setAttribute(
|
|
5710
|
+
"agent.has_tool_calls",
|
|
5711
|
+
Array.isArray(r?.message?.tool_calls) &&
|
|
5712
|
+
r.message.tool_calls.length > 0,
|
|
5713
|
+
);
|
|
5714
|
+
},
|
|
5715
|
+
"model_error",
|
|
5716
|
+
);
|
|
5717
|
+
if (recorder) recorder.counter("agent.model.calls", 1);
|
|
5068
5718
|
throwIfAborted(signal);
|
|
5069
5719
|
const msg = result?.message;
|
|
5070
5720
|
|
|
@@ -5303,11 +5953,24 @@ export async function* agentLoop(messages, options) {
|
|
|
5303
5953
|
let toolResult;
|
|
5304
5954
|
let toolError = null;
|
|
5305
5955
|
try {
|
|
5306
|
-
toolResult = await
|
|
5956
|
+
toolResult = await _withSpan(
|
|
5957
|
+
recorder,
|
|
5958
|
+
"agent.tool",
|
|
5959
|
+
{ "tool.name": toolName },
|
|
5960
|
+
() => executeTool(toolName, toolArgs, toolContext),
|
|
5961
|
+
(span, r) => {
|
|
5962
|
+
span.setAttribute(
|
|
5963
|
+
"tool.is_error",
|
|
5964
|
+
!!(r && typeof r === "object" && r.error),
|
|
5965
|
+
);
|
|
5966
|
+
},
|
|
5967
|
+
"tool_error",
|
|
5968
|
+
);
|
|
5307
5969
|
} catch (err) {
|
|
5308
5970
|
toolResult = { error: err.message };
|
|
5309
5971
|
toolError = err.message;
|
|
5310
5972
|
}
|
|
5973
|
+
if (recorder) recorder.counter("agent.tool.calls", 1);
|
|
5311
5974
|
|
|
5312
5975
|
throwIfAborted(signal);
|
|
5313
5976
|
|
|
@@ -5371,6 +6034,10 @@ export function formatToolArgs(name, args) {
|
|
|
5371
6034
|
return args.command;
|
|
5372
6035
|
case "search_files":
|
|
5373
6036
|
return args.pattern;
|
|
6037
|
+
case "code_intelligence":
|
|
6038
|
+
return args.action === "workspace_symbols"
|
|
6039
|
+
? `${args.action} ${args.query || ""}`.trim()
|
|
6040
|
+
: `${args.action} ${args.file || ""}${args.line != null ? `:${args.line}:${args.col}` : ""}`.trim();
|
|
5374
6041
|
case "list_dir":
|
|
5375
6042
|
return args.path || ".";
|
|
5376
6043
|
case "run_skill":
|