chainlesschain 0.162.148 → 0.162.149
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-BJif14yR.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-C9pBYocA.js → ActionButton-CXek6gJY.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BWAkeXxK.js → Analytics-BgQhdAJi.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-CivFGH6B.js → AppLayout-DTYl5NtR.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Cm8hRpZm.js → Audit-BzATQAeR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DXdFMsE8.js → Backup-BzdPEQhL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uAwSTeql.js → BaseInput-BM0KVh8E.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-KU8eeJJE.js → Chat-D7fHXHkz.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-Wm34RR-b.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-C6AMDkjd.js → Checkbox-CAiSQ9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-t2moDxcO.js → Codegen-Df40CrEe.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DCcsRRhN.js → Col-DIT2fNFU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DtB-8SAC.js → Community-CSTIbW2k.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CZm8THYA.js → Compact-DtqXZZUi.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-LiQgC7g1.js → Compliance-CUtzw6o7.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CA8SAxht.js → Cowork-CezmlnmU.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CtRaF1wD.js → Cron-P4BITarg.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-6-br6Hub.js → Crosschain-BRRiYwvq.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Do2EccrL.js → DID-L5ijB9i4.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-wrXcbzGe.js → Dashboard-CFxro4ob.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-p3FsT245.js → Dropdown-BmAFCQ71.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D95A2L8q.js → EmailListRenderer-DJBCEuon.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-cLuJW2zo.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Brgq_cbN.js → Federation-DsVCpHMF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DWLCkNgh.js → FormItemContext-DP3bP5th.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBqUfQd6.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-8F090w4I.js → Git-I3g_bAw9.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CCogEbxb.js → Governance-jHS5-ioS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfLD7v7C.js → Inference-CPL7sfx9.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DDuHJewd.js → KnowledgeGraph-Jf236h4C.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CE8KSEVC.js → Logs-Cm2tcSKg.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DmwpZmhT.js → Marketplace-CZK82rhi.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Bf7AazU8.js → McpTools-CZCeji7a.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-D0QU_00S.js → Memory-3l2KVS9k.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-yXS9xdhx.js → MobileBridge-VYdpoLh6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BuDUoGUP.js → MobileProjects-CiB9cmm1.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BYyHy28p.js → Mtc-wCFZbBuL.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CK0aqpiZ.js → MtcAudit-1a3THIyG.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-mNimKYeb.js → Multisig-DMzLB2hG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-AioAJ0YA.js → NLProgramming-D9wZbf6l.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CVVNCLHE.js → Notes-hrFe2ZM-.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C4ABj-TK.js → NotificationSettings-CHGX5Jwm.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
- package/src/assets/web-panel/assets/{Organization-uozl_gWK.js → Organization-By7FkhtY.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BCZOM2hK.js → Overflow--khGSzJn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CArcaiaj.js → P2P-CAG9dH35.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Sgq2Srr8.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BYrQrXnH.js → Permissions-D35ec6JA.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BzHStAbz.js → PersonalDataHub-CxBF7hW_.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-k7KqxX1M.js → Pipeline-s6EtOO1s.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-IumTUWqx.js → Privacy-BbJYmWmO.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BRyImYTf.js → ProjectInit-VGQq1jMT.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BFIqTBFk.js → ProjectSettings-B1ew3Teh.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CGcNIs_g.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CyyFnUPs.js → Providers-mrO47FIK.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C82cRx-X.js → QrScannerModal-IJF2mHyw.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DGxDF521.js → QuickAsk-CU0wN_Z0.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cns-Am1y.js → Recommend-DhN37R6G.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-R7OqAIlg.js → RemoteSession-D_xOX_hu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D7mgPIYV.js → Reputation-D_Ssv7uH.js} +1 -1
- package/src/assets/web-panel/assets/{Row-B3lEURyd.js → Row-Kx5dKxX7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-9_UVrpBt.js → RssFeed-BlGwqZkY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-ClZeO80q.js → Search-hLFQzxpb.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BNNJczEp.js → Security-DMlvsVt4.js} +3 -3
- package/src/assets/web-panel/assets/{Services-C5SjrWUj.js → Services-Bd0Qsj2m.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Ci_obQ-g.js → Skeleton-DFclq9X3.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DdebN15P.js → Skills-BNtln2qY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-OinZP2vi.js → Sla-Dch5H19G.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CxzbNF51.js → SpeechSettings-2UfQcU1g.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D06N_66b.js → SyncSettings-CD9YQr4i.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BdEdW0AZ.js → Tasks-BNu-7MzI.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-F1ctKmPa.js → Templates-D1n9CaIR.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CVz1Urot.js → Tenant-DenL3iBW.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-Doitzjmf.js → TimelineRenderer-D87IfukO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BpyVRpVA.js → Tokens-BkiZc8E3.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-nWhWYTbU.js → Trigger-BNRSytGN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DK_G-wLx.js → Trust-Bwbd4X2m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B5yBUojO.js → UkeySign-B8O2bs0o.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C5D51qAe.js → VideoEditing-C2fL8zmH.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CLbL9K7v.js → Wallet-BORYDdE1.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DZUelI3V.js → WebAuthn-C0V5S2FM.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-8RPxt4KW.js → WorkflowEditor-2dR0fcHL.js} +1 -1
- package/src/assets/web-panel/assets/{chat-4N4tOA3d.js → chat-BmuAFAn8.js} +1 -1
- package/src/assets/web-panel/assets/{colors-zbbGVrua.js → colors-CN3smMcK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-D7iMkbnE.js → compact-item-DCnxb4kW.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CBzPJv-w.js → createContext-vlR43pHn.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-bHdrQSqZ.js → hasIn-D04tHYFd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbE5D0WL.js → index-0DnaZGkC.js} +1 -1
- package/src/assets/web-panel/assets/{index-308gCGh7.js → index-430S68MN.js} +1 -1
- package/src/assets/web-panel/assets/{index-kmh1TxRa.js → index-B78xL3s2.js} +1 -1
- package/src/assets/web-panel/assets/{index-QFObYeo1.js → index-BTPEZTk1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZb8F8YG.js → index-BXRg8GFQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE9j5YgT.js → index-BasvsWDM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiG1IZao.js → index-BeGDEXFs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqmx24kh.js → index-BqhASEL4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCr0geV9.js → index-BudvKQfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-S74FpAIn.js → index-C-6NO5il.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyoNgbXl.js → index-C5Sxb1sE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtbbdLnf.js → index-CP9m9Ggr.js} +3 -3
- package/src/assets/web-panel/assets/{index-CkxK86vf.js → index-CTOCnIQ7.js} +1 -1
- package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
- package/src/assets/web-panel/assets/{index-BTIjjXry.js → index-ClhAHDK3.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7b1hGry.js → index-CnXebZLL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPxkoKgA.js → index-CngCC8hC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci009ijp.js → index-Cr-A0FYJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdmMoYN1.js → index-CsAsCPJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBNr77E1.js → index-D28DeAAB.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1s0Bz9O.js → index-D5LZVZ0L.js} +1 -1
- package/src/assets/web-panel/assets/{index-oDKNgCsP.js → index-D65_XseV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QNAG4JtR.js → index-D8vwp4qp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQfu1U78.js → index-DDwLgQY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnBOpqv3.js → index-DIpY0qM5.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ6NcfWz.js → index-DTWg-18U.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4XKpj6c.js → index-DTtRscUa.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1YDh7Wr.js → index-DYAtmqiv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvRMA9uT.js → index-D_n8_vfI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahryTX0.js → index-DbADHwhr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJ4ZGyW0.js → index-DbVsQBOH.js} +1 -1
- package/src/assets/web-panel/assets/index-Dwzo-dtJ.js +1 -0
- package/src/assets/web-panel/assets/{index-xYCm6W2h.js → index-HhNjnCfe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS4_Mwk6.js → index-ToUh2b1-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DISWAYce.js → index-TslMTwNy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmqlJed-.js → index-_FIkN3jd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqdPyWm5.js → index-hpvvtAZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsKehmmS.js → index-jzR7Ujuw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUyjUkY7.js → index-ljuBiQW9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BD55yNBt.js → initDefaultProps-C3wUb4je.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B5Bbe77U.js → motion-D-jYFUNA.js} +1 -1
- package/src/assets/web-panel/assets/{move-CTvIqHEH.js → move-tqb8nwJ2.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BkNyPQKB.js → mtc-parser-DSOjMJMz.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BaXJXgHA.js → omit-CJ7VimIW.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bw6-YTxH.js → pickAttrs-qXiG1iT3.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BLdbkLqJ.js → placementArrow-CkGBcy1Z.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-IVYkzntU.js → responsiveObserve-D3WvTlLO.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DFMFwwtY.js → slide-BEE2ftge.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CURYRtZ1.js → statusUtils-CsfBpxiG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Cfz63s1r.js → styleChecker-xPrIiJPI.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-COJL0KE3.js → useFlexGapSupport-C5Yd-SCR.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-YLLojQQf.js → useFs-D_MDS8HI.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BF_21qUC.js → usePersonalDataHub-BqJh-usA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-9ZpkA7bb.js → vnode-chm_p-gz.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DhtAfhl8.js → zoom-3-YDNz0Z.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/agent.js +44 -5
- package/src/commands/codeintel.js +252 -0
- package/src/commands/eval.js +248 -0
- package/src/commands/plugin.js +354 -0
- package/src/commands/team.js +456 -0
- package/src/gateways/ws/message-dispatcher.js +45 -2
- package/src/gateways/ws/remote-session-protocol.js +37 -0
- package/src/gateways/ws/ws-server.js +1 -1
- package/src/harness/prompt-compressor.js +27 -1
- package/src/harness/remote-command-ledger.js +158 -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/index.js +6 -0
- package/src/lib/agent-core.js +2 -0
- 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 +266 -0
- package/src/lib/agent-team/team-worktree.js +204 -0
- package/src/lib/agents.js +18 -0
- package/src/lib/async-hook-supervisor.cjs +305 -0
- package/src/lib/eval/runner.js +163 -0
- package/src/lib/eval/tasks.js +456 -0
- package/src/lib/eval/trend.js +185 -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 +258 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +264 -0
- package/src/lib/lsp/__tests__/lsp-server-registry.test.js +128 -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 +318 -0
- package/src/lib/lsp/lsp-manager.js +323 -0
- package/src/lib/lsp/lsp-server-registry.js +196 -0
- package/src/lib/plugin-monitor-supervisor.js +238 -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 +388 -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 +144 -0
- package/src/lib/plugin-runtime/reload.js +79 -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/sandbox-egress-proxy.js +162 -0
- package/src/lib/sandbox-network-policy.js +134 -0
- package/src/lib/sandbox-v2.js +10 -4
- package/src/lib/settings-hook-events.cjs +78 -6
- package/src/lib/settings-hooks.cjs +29 -3
- package/src/lib/settings-loader.cjs +35 -1
- package/src/lib/skill-loader.js +18 -0
- package/src/lib/telemetry/span-recorder.js +237 -0
- package/src/repl/agent-repl.js +400 -4
- package/src/runtime/agent-core.js +546 -52
- 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 +353 -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/OrderTableRenderer-DIp8Xcbd.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dpid9CDg.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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamRunner (Phase 4 — Agent Team) — drives a TaskLeaseRegistry with N
|
|
3
|
+
* concurrent teammates, each looping: pick a claimable task → acquire its
|
|
4
|
+
* EXCLUSIVE lease → run it → complete (or fail → retry/cancel). The registry's
|
|
5
|
+
* lease + dependency DAG guarantees the two things a plain worker pool can't:
|
|
6
|
+
* - a task is run by AT MOST ONE teammate at a time (exclusive lease), so no
|
|
7
|
+
* duplicate work even with M teammates racing for the same task;
|
|
8
|
+
* - a task only starts once its dependencies are COMPLETED (DAG gating), and a
|
|
9
|
+
* task whose dependency was CANCELLED never runs (stays blocked).
|
|
10
|
+
*
|
|
11
|
+
* `runTask` is injected so the whole orchestration is unit-testable offline; in
|
|
12
|
+
* production it's wired to a real agent turn (SubAgentContext.run in a
|
|
13
|
+
* per-teammate worktree). Events are emitted for a machine-readable stream /
|
|
14
|
+
* status panel. A total-time / total-task budget bounds a runaway graph.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export class TeamRunner {
|
|
18
|
+
/**
|
|
19
|
+
* @param {TaskLeaseRegistry} registry
|
|
20
|
+
* @param {object} opts
|
|
21
|
+
* runTask async ({key, task, holder, renew}) => any (throw = task failed)
|
|
22
|
+
* teammates number of concurrent workers (default 2)
|
|
23
|
+
* ttlMs lease TTL per acquisition
|
|
24
|
+
* onEvent (evt) => void {type, ...}
|
|
25
|
+
* maxTasks safety cap on total task executions (default 1000)
|
|
26
|
+
* now () => ms (for deadline math; defaults to registry clock)
|
|
27
|
+
*/
|
|
28
|
+
constructor(registry, opts = {}) {
|
|
29
|
+
this.registry = registry;
|
|
30
|
+
this.runTask = opts.runTask;
|
|
31
|
+
this.teammates = opts.teammates > 0 ? Math.floor(opts.teammates) : 2;
|
|
32
|
+
this.ttlMs = opts.ttlMs;
|
|
33
|
+
this.onEvent = typeof opts.onEvent === "function" ? opts.onEvent : () => {};
|
|
34
|
+
this.maxTasks = opts.maxTasks > 0 ? opts.maxTasks : 1000;
|
|
35
|
+
this._now = opts.now || registry._now || (() => Date.now());
|
|
36
|
+
// Team-wide token/USD/time/task budget (null → unbounded). Consulted before
|
|
37
|
+
// every claim; folded after every settled task.
|
|
38
|
+
this.budget = opts.budget || null;
|
|
39
|
+
// Directed/broadcast messaging between teammates (null → disabled).
|
|
40
|
+
this.mailbox = opts.mailbox || null;
|
|
41
|
+
this._executions = 0;
|
|
42
|
+
this._inFlight = 0;
|
|
43
|
+
this._maxInFlight = 0;
|
|
44
|
+
this._members = new Map(); // holder → lifecycle record
|
|
45
|
+
this._budgetStopped = false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
_emit(type, extra) {
|
|
49
|
+
try {
|
|
50
|
+
this.onEvent({ type, ts: this._now(), ...extra });
|
|
51
|
+
} catch {
|
|
52
|
+
/* event sink is best-effort */
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Track a teammate's lifecycle state (idle / running / failed / shutdown) and
|
|
58
|
+
* emit a `teammate:state` event on every transition. `lost` is set on resume
|
|
59
|
+
* by the caller for a teammate whose lease was reclaimed after a crash.
|
|
60
|
+
*/
|
|
61
|
+
_setState(holder, state, extra = {}) {
|
|
62
|
+
let m = this._members.get(holder);
|
|
63
|
+
if (!m) {
|
|
64
|
+
m = { holder, state: null, completed: 0, failed: 0, lastError: null };
|
|
65
|
+
this._members.set(holder, m);
|
|
66
|
+
}
|
|
67
|
+
if (state === "completed-task") {
|
|
68
|
+
m.completed += 1;
|
|
69
|
+
return m;
|
|
70
|
+
}
|
|
71
|
+
if (state === "failed-task") {
|
|
72
|
+
m.failed += 1;
|
|
73
|
+
m.lastError = extra.error || null;
|
|
74
|
+
return m;
|
|
75
|
+
}
|
|
76
|
+
if (m.state === state) return m; // no transition
|
|
77
|
+
m.state = state;
|
|
78
|
+
this._emit("teammate:state", { holder, state, ...extra });
|
|
79
|
+
return m;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Snapshot of every teammate's lifecycle (for a status panel / resume). */
|
|
83
|
+
members() {
|
|
84
|
+
return Array.from(this._members.values()).map((m) => ({ ...m }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Restore prior-run member records (e.g. before a --resume run). */
|
|
88
|
+
seedMembers(records = []) {
|
|
89
|
+
for (const r of records) {
|
|
90
|
+
if (r && r.holder) this._members.set(r.holder, { ...r });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Run the graph to completion (all tasks terminal) or until no teammate can
|
|
96
|
+
* make progress (everything remaining is blocked/leased). Returns a summary.
|
|
97
|
+
*/
|
|
98
|
+
async run() {
|
|
99
|
+
if (typeof this.runTask !== "function") {
|
|
100
|
+
throw new Error("TeamRunner: opts.runTask is required");
|
|
101
|
+
}
|
|
102
|
+
this._emit("run:start", {
|
|
103
|
+
teammates: this.teammates,
|
|
104
|
+
tasks: this.registry.list().length,
|
|
105
|
+
});
|
|
106
|
+
if (this.budget) this.budget.start();
|
|
107
|
+
const workers = [];
|
|
108
|
+
for (let i = 0; i < this.teammates; i++) {
|
|
109
|
+
const holder = `teammate-${i + 1}`;
|
|
110
|
+
this._setState(holder, "idle");
|
|
111
|
+
workers.push(this._worker(holder));
|
|
112
|
+
}
|
|
113
|
+
await Promise.all(workers);
|
|
114
|
+
const stats = this.registry.stats();
|
|
115
|
+
const summary = {
|
|
116
|
+
done: this.registry.allDone(),
|
|
117
|
+
executions: this._executions,
|
|
118
|
+
maxConcurrent: this._maxInFlight,
|
|
119
|
+
budgetStopped: this._budgetStopped,
|
|
120
|
+
budgetReason: this.budget ? this.budget.reason() : null,
|
|
121
|
+
members: this.members(),
|
|
122
|
+
messages: this.mailbox ? this.mailbox.size() : 0,
|
|
123
|
+
stats,
|
|
124
|
+
};
|
|
125
|
+
this._emit("run:end", summary);
|
|
126
|
+
return summary;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async _worker(holder) {
|
|
130
|
+
// Each teammate keeps taking claimable work until none is left FOR IT. A
|
|
131
|
+
// task blocked only by a peer's in-flight lease will free up (complete) or
|
|
132
|
+
// its dep will finish — so a worker that finds nothing claimable yields and
|
|
133
|
+
// re-checks while any work is still in flight.
|
|
134
|
+
for (;;) {
|
|
135
|
+
if (this._executions >= this.maxTasks) {
|
|
136
|
+
this._emit("run:budget-exhausted", { maxTasks: this.maxTasks });
|
|
137
|
+
this._setState(holder, "shutdown", { reason: "max-tasks" });
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// Team budget (token/USD/time/task) — stop CLAIMING once any cap is hit so
|
|
141
|
+
// the team overshoots by at most the tasks already in flight.
|
|
142
|
+
if (this.budget && this.budget.shouldStop()) {
|
|
143
|
+
const reason = this.budget.reason();
|
|
144
|
+
if (!this._budgetStopped) {
|
|
145
|
+
this._budgetStopped = true;
|
|
146
|
+
this._emit("run:budget-exhausted", { reason });
|
|
147
|
+
}
|
|
148
|
+
this._setState(holder, "shutdown", { reason });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const key = this._nextFor(holder);
|
|
152
|
+
if (!key) {
|
|
153
|
+
// Nothing to claim right now. If peers are still working, wait and
|
|
154
|
+
// retry (their completions may unblock a dependent). Otherwise we're done.
|
|
155
|
+
if (this._inFlight > 0) {
|
|
156
|
+
this._setState(holder, "idle");
|
|
157
|
+
await this._tick();
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
this._setState(holder, "shutdown", { reason: "no-more-work" });
|
|
161
|
+
return; // no claimable + nothing in flight → this worker is finished
|
|
162
|
+
}
|
|
163
|
+
const acq = this.registry.acquire(key, { holder, ttlMs: this.ttlMs });
|
|
164
|
+
if (!acq.ok) {
|
|
165
|
+
// Lost the race to a peer (or it just got blocked) — try another.
|
|
166
|
+
this._setState(holder, "idle");
|
|
167
|
+
await this._tick();
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
await this._execute(holder, key, acq.lease);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Pick a claimable task key for this holder (highest priority first). */
|
|
175
|
+
_nextFor() {
|
|
176
|
+
const claimable = this.registry.claimable();
|
|
177
|
+
if (claimable.length === 0) return null;
|
|
178
|
+
// Stable: prefer higher priority, else insertion order (claimable order).
|
|
179
|
+
const rank = { high: 0, normal: 1, low: 2 };
|
|
180
|
+
let best = null;
|
|
181
|
+
let bestScore = Infinity;
|
|
182
|
+
for (const key of claimable) {
|
|
183
|
+
const t = this.registry.getTask(key);
|
|
184
|
+
const score = rank[t?.priority] ?? 1;
|
|
185
|
+
if (score < bestScore) {
|
|
186
|
+
bestScore = score;
|
|
187
|
+
best = key;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return best;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async _execute(holder, key, lease) {
|
|
194
|
+
const task = this.registry.getTask(key);
|
|
195
|
+
this._executions++;
|
|
196
|
+
this._inFlight++;
|
|
197
|
+
this._maxInFlight = Math.max(this._maxInFlight, this._inFlight);
|
|
198
|
+
this._setState(holder, "running", { key });
|
|
199
|
+
this._emit("task:claimed", { key, holder, attempts: task.attempts });
|
|
200
|
+
const renew = () => this.registry.renew(key, { holder, ttlMs: this.ttlMs });
|
|
201
|
+
// A teammate-scoped messaging handle: post to a peer / broadcast, and read
|
|
202
|
+
// its own inbox (direct messages + unseen broadcasts).
|
|
203
|
+
const inbox = this.mailbox ? this.mailbox.drain(holder) : [];
|
|
204
|
+
const sendMessage = (to, body, subject = null) =>
|
|
205
|
+
this.mailbox
|
|
206
|
+
? this.mailbox.send({ from: holder, to, subject, body })
|
|
207
|
+
: null;
|
|
208
|
+
const startedAt = this._now();
|
|
209
|
+
try {
|
|
210
|
+
const result = await this.runTask({
|
|
211
|
+
key,
|
|
212
|
+
task,
|
|
213
|
+
holder,
|
|
214
|
+
renew,
|
|
215
|
+
inbox,
|
|
216
|
+
sendMessage,
|
|
217
|
+
mailbox: this.mailbox,
|
|
218
|
+
budget: this.budget,
|
|
219
|
+
});
|
|
220
|
+
this.registry.complete(key, { holder, result });
|
|
221
|
+
// Fold usage/cost into the team budget. `--exec` shell tasks carry no
|
|
222
|
+
// usage → only the task-count / wall-clock dimensions move.
|
|
223
|
+
if (this.budget) {
|
|
224
|
+
this.budget.record(
|
|
225
|
+
{
|
|
226
|
+
usage: result?.usage || null,
|
|
227
|
+
provider: result?.provider,
|
|
228
|
+
model: result?.model,
|
|
229
|
+
},
|
|
230
|
+
this._now(),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
this._setState(holder, "completed-task");
|
|
234
|
+
this._emit("task:completed", {
|
|
235
|
+
key,
|
|
236
|
+
holder,
|
|
237
|
+
ms: this._now() - startedAt,
|
|
238
|
+
});
|
|
239
|
+
} catch (err) {
|
|
240
|
+
// A failed task still consumed a task-count slot (and any wall-clock) — fold
|
|
241
|
+
// it so a doomed retry loop can't dodge the budget.
|
|
242
|
+
if (this.budget) this.budget.record({ usage: null }, this._now());
|
|
243
|
+
const outcome = this.registry.fail(key, {
|
|
244
|
+
holder,
|
|
245
|
+
error: err?.message || String(err),
|
|
246
|
+
});
|
|
247
|
+
this._setState(holder, "failed-task", {
|
|
248
|
+
error: err?.message || String(err),
|
|
249
|
+
});
|
|
250
|
+
this._emit("task:failed", {
|
|
251
|
+
key,
|
|
252
|
+
holder,
|
|
253
|
+
error: err?.message || String(err),
|
|
254
|
+
retry: outcome?.retry === true,
|
|
255
|
+
attempts: outcome?.attempts,
|
|
256
|
+
});
|
|
257
|
+
} finally {
|
|
258
|
+
this._inFlight--;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Yield to the event loop so peer completions land before re-checking. */
|
|
263
|
+
_tick() {
|
|
264
|
+
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeamWorktreeCoordinator (Phase 4) — give each teammate its OWN git worktree so
|
|
3
|
+
* parallel task execution never fights over the working tree, then integrate the
|
|
4
|
+
* results by SEQUENTIALLY previewing + merging each branch back to base. Two
|
|
5
|
+
* tasks that touch different files both merge clean; two that touch the same
|
|
6
|
+
* file surface a conflict on the second merge (the first having moved base) —
|
|
7
|
+
* the Phase 4 acceptance "并行 Worktree 修改可预览冲突并安全合并".
|
|
8
|
+
*
|
|
9
|
+
* Unlike `isolateTask` (which removes the worktree as soon as its fn returns),
|
|
10
|
+
* the coordinator KEEPS every worktree until integration is done — merge preview
|
|
11
|
+
* needs the branch's worktree present — then cleans them all up.
|
|
12
|
+
*
|
|
13
|
+
* The git surface (`worktree-isolator`) is injected via `_deps` so the
|
|
14
|
+
* coordinator's orchestration is unit-testable without a real repo; a real-git
|
|
15
|
+
* integration test exercises the actual worktree/commit/merge path.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
createWorktree as _createWorktree,
|
|
20
|
+
removeWorktree as _removeWorktree,
|
|
21
|
+
previewWorktreeMerge as _previewWorktreeMerge,
|
|
22
|
+
mergeWorktree as _mergeWorktree,
|
|
23
|
+
} from "../../harness/worktree-isolator.js";
|
|
24
|
+
import { isGitRepo as _isGitRepo } from "../../lib/git-integration.js";
|
|
25
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
26
|
+
|
|
27
|
+
function defaultRunShell(command, cwd) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const child = spawn(command, { cwd, shell: true, env: process.env });
|
|
30
|
+
let err = "";
|
|
31
|
+
child.stderr?.on("data", (d) => (err += d.toString("utf8")));
|
|
32
|
+
child.on("error", (e) => reject(new Error(e.message)));
|
|
33
|
+
child.on("close", (code) =>
|
|
34
|
+
code === 0
|
|
35
|
+
? resolve({ code })
|
|
36
|
+
: reject(new Error(err.trim() || `command exited ${code}`)),
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Stage + commit everything in a worktree. Returns true if a commit was made. */
|
|
42
|
+
function defaultCommit(worktreePath, message) {
|
|
43
|
+
execFileSync("git", ["add", "-A"], { cwd: worktreePath, stdio: "ignore" });
|
|
44
|
+
try {
|
|
45
|
+
execFileSync(
|
|
46
|
+
"git",
|
|
47
|
+
[
|
|
48
|
+
"-c",
|
|
49
|
+
"user.email=team@chainlesschain.local",
|
|
50
|
+
"-c",
|
|
51
|
+
"user.name=cc team",
|
|
52
|
+
"commit",
|
|
53
|
+
"-m",
|
|
54
|
+
message,
|
|
55
|
+
],
|
|
56
|
+
{ cwd: worktreePath, stdio: "ignore" },
|
|
57
|
+
);
|
|
58
|
+
return true;
|
|
59
|
+
} catch {
|
|
60
|
+
return false; // nothing to commit (no changes)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const _deps = {
|
|
65
|
+
createWorktree: _createWorktree,
|
|
66
|
+
removeWorktree: _removeWorktree,
|
|
67
|
+
previewWorktreeMerge: _previewWorktreeMerge,
|
|
68
|
+
mergeWorktree: _mergeWorktree,
|
|
69
|
+
isGitRepo: _isGitRepo,
|
|
70
|
+
runShell: defaultRunShell,
|
|
71
|
+
commit: defaultCommit,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export class TeamWorktreeCoordinator {
|
|
75
|
+
constructor(repoDir) {
|
|
76
|
+
this.repoDir = repoDir;
|
|
77
|
+
this._created = new Map(); // key → { branch, path, committed }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
isGitRepo() {
|
|
81
|
+
return _deps.isGitRepo(this.repoDir);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
branchFor(key) {
|
|
85
|
+
return `team/${key}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A TeamRunner `runTask` that runs a task inside a fresh per-task worktree and
|
|
90
|
+
* commits the result. By default it runs the task's shell `command`; pass
|
|
91
|
+
* `runInWorktree({ key, task, holder, cwd })` to instead drive an agent turn
|
|
92
|
+
* (its `prompt`) with cwd set to the worktree, so `--agent --worktree` gets the
|
|
93
|
+
* same parallel isolation as `--exec --worktree`. Throws (→ task failure) on a
|
|
94
|
+
* non-zero command / agent exit so retry/cancel still work.
|
|
95
|
+
*/
|
|
96
|
+
makeRunTask({ runInWorktree = null } = {}) {
|
|
97
|
+
return async ({ key, task, holder }) => {
|
|
98
|
+
const branch = this.branchFor(key);
|
|
99
|
+
const { path: worktreePath } = _deps.createWorktree(this.repoDir, branch);
|
|
100
|
+
this._created.set(key, { branch, path: worktreePath, committed: false });
|
|
101
|
+
if (typeof runInWorktree === "function") {
|
|
102
|
+
await runInWorktree({ key, task, holder, cwd: worktreePath });
|
|
103
|
+
} else {
|
|
104
|
+
const command = task.metadata?.command || task?.command;
|
|
105
|
+
if (!command) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`task "${key}" has no \`command\` to run in a worktree`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
await _deps.runShell(command, worktreePath);
|
|
111
|
+
}
|
|
112
|
+
const committed = _deps.commit(worktreePath, `team task ${key}`);
|
|
113
|
+
this._created.get(key).committed = committed;
|
|
114
|
+
return { branch, committed };
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sequentially preview (and optionally merge) each committed branch back to
|
|
120
|
+
* base. Merging is in-order, so a later branch that conflicts with an
|
|
121
|
+
* already-merged one is reported as a conflict rather than silently clobbering.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} [opts] { merge:boolean } actually merge clean branches
|
|
124
|
+
* @returns {Array<{key,branch,committed,clean,merged,conflicts,error?}>}
|
|
125
|
+
*/
|
|
126
|
+
integrate({ merge = false } = {}) {
|
|
127
|
+
const results = [];
|
|
128
|
+
for (const [key, info] of this._created) {
|
|
129
|
+
if (!info.committed) {
|
|
130
|
+
results.push({
|
|
131
|
+
key,
|
|
132
|
+
branch: info.branch,
|
|
133
|
+
committed: false,
|
|
134
|
+
clean: true,
|
|
135
|
+
merged: false,
|
|
136
|
+
conflicts: [],
|
|
137
|
+
note: "no changes to merge",
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
let preview;
|
|
142
|
+
try {
|
|
143
|
+
preview = _deps.previewWorktreeMerge(this.repoDir, info.branch);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
results.push({
|
|
146
|
+
key,
|
|
147
|
+
branch: info.branch,
|
|
148
|
+
committed: true,
|
|
149
|
+
clean: false,
|
|
150
|
+
merged: false,
|
|
151
|
+
conflicts: [],
|
|
152
|
+
error: err.message,
|
|
153
|
+
});
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const clean = preview?.success === true;
|
|
157
|
+
const conflicts = preview?.conflicts || [];
|
|
158
|
+
let merged = false;
|
|
159
|
+
if (clean && merge) {
|
|
160
|
+
try {
|
|
161
|
+
_deps.mergeWorktree(this.repoDir, info.branch, {
|
|
162
|
+
message: `Merge team task ${key}`,
|
|
163
|
+
});
|
|
164
|
+
merged = true;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
results.push({
|
|
167
|
+
key,
|
|
168
|
+
branch: info.branch,
|
|
169
|
+
committed: true,
|
|
170
|
+
clean: false,
|
|
171
|
+
merged: false,
|
|
172
|
+
conflicts,
|
|
173
|
+
error: `merge failed: ${err.message}`,
|
|
174
|
+
});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
results.push({
|
|
179
|
+
key,
|
|
180
|
+
branch: info.branch,
|
|
181
|
+
committed: true,
|
|
182
|
+
clean,
|
|
183
|
+
merged,
|
|
184
|
+
conflicts,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Remove every worktree created for this run (branches left intact). */
|
|
191
|
+
cleanupAll({ deleteBranch = false } = {}) {
|
|
192
|
+
for (const [, info] of this._created) {
|
|
193
|
+
try {
|
|
194
|
+
_deps.removeWorktree(this.repoDir, info.path, { deleteBranch });
|
|
195
|
+
} catch {
|
|
196
|
+
/* best-effort */
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
branches() {
|
|
202
|
+
return Array.from(this._created.values()).map((i) => i.branch);
|
|
203
|
+
}
|
|
204
|
+
}
|
package/src/lib/agents.js
CHANGED
|
@@ -22,6 +22,7 @@ import pathDefault from "node:path";
|
|
|
22
22
|
import { homedir } from "node:os";
|
|
23
23
|
import yaml from "js-yaml";
|
|
24
24
|
import { projectRootBase } from "./project-root.cjs";
|
|
25
|
+
import { discoverPluginAgentLayers } from "./plugin-runtime/agents.js";
|
|
25
26
|
|
|
26
27
|
const _deps = { fs: fsDefault, path: pathDefault };
|
|
27
28
|
|
|
@@ -76,6 +77,23 @@ export function agentDirs(cwd = process.cwd(), opts = {}) {
|
|
|
76
77
|
dirs.push({ dir: path.join(root, ".claude", "agents"), scope: "project" });
|
|
77
78
|
}
|
|
78
79
|
dirs.push({ dir: path.join(home, ".claude", "agents"), scope: "personal" });
|
|
80
|
+
// Installed-plugin agents (Phase 3.3h) — LOWEST precedence: appended last so
|
|
81
|
+
// that after discoverAgents' reverse() they are scanned FIRST and a user's own
|
|
82
|
+
// project/personal agent of the same name shadows them. Declarative (system
|
|
83
|
+
// prompt only), so not trust-gated. Best-effort: a discovery failure just
|
|
84
|
+
// contributes no plugin dirs.
|
|
85
|
+
if (opts.includePlugins !== false) {
|
|
86
|
+
try {
|
|
87
|
+
for (const layer of discoverPluginAgentLayers({
|
|
88
|
+
cwd,
|
|
89
|
+
scopes: opts.scopes,
|
|
90
|
+
})) {
|
|
91
|
+
dirs.push({ dir: layer.dir, scope: "plugin" });
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
/* best-effort — plugin discovery never breaks core agent loading */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
79
97
|
return dirs;
|
|
80
98
|
}
|
|
81
99
|
|