chainlesschain 0.162.147 → 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 +2 -2
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-DywJ7xTv.js → AIOps-BJif14yR.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BTlqcY-q.js → ActionButton-CXek6gJY.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-FdG1Mvwy.js → Analytics-BgQhdAJi.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CVsnbvN1.js → AppLayout-DTYl5NtR.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-ITdpJ7vR.js → Audit-BzATQAeR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BfXqnXo1.js → Backup-BzdPEQhL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C09ip3_E.js → BaseInput-BM0KVh8E.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-qdkhB42l.js → Chat-D7fHXHkz.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-D2KfETZD.js → ChatBubbleRenderer-Wm34RR-b.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-j6WZCuff.js → Checkbox-CAiSQ9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CGZ-K3uK.js → Codegen-Df40CrEe.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CIlPOAu6.js → Col-DIT2fNFU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-sY-i-hic.js → Community-CSTIbW2k.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DcoEfyL8.js → Compact-DtqXZZUi.js} +1 -1
- package/src/assets/web-panel/assets/Compliance-CUtzw6o7.js +1 -0
- package/src/assets/web-panel/assets/{Cowork-DLY_fkLv.js → Cowork-CezmlnmU.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-D049pZBC.js → Cron-P4BITarg.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DCXdE8M9.js → Crosschain-BRRiYwvq.js} +1 -1
- package/src/assets/web-panel/assets/{DID-JCvPZtjW.js → DID-L5ijB9i4.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cg2xu3iE.js → Dashboard-CFxro4ob.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DjJLjbLh.js → Dropdown-BmAFCQ71.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DugNcSLR.js → EmailListRenderer-DJBCEuon.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-J6Nb-E30.js → FamilyGuardDashboard-z4oLq6eT.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CQ3Ttpbl.js → Federation-DsVCpHMF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cz0NKag4.js → FormItemContext-DP3bP5th.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NYcMP6iz.js → GenericCardRenderer-BAXP2Gc6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B4hYN18N.js → Git-I3g_bAw9.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DOQPCcC0.js → Governance-jHS5-ioS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-HtLF746W.js → Inference-CPL7sfx9.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BXrk8rMm.js → KnowledgeGraph-Jf236h4C.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DS0JnD3-.js → Logs-Cm2tcSKg.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-Cqnjihrz.js → Marketplace-CZK82rhi.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cx3Psk-U.js → McpTools-CZCeji7a.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BjyysPtj.js → Memory-3l2KVS9k.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
- package/src/assets/web-panel/assets/MobileBridge-VYdpoLh6.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-Xwf-fdOQ.js → MobileProjects-CiB9cmm1.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Djkql5m5.js → Mtc-wCFZbBuL.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-D2BAZlu0.js → MtcAudit-1a3THIyG.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-ByqP1ZzH.js → Multisig-DMzLB2hG.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C_kn0nE2.js → NLProgramming-D9wZbf6l.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-7l7eXHPq.js → Notes-hrFe2ZM-.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BrCDoitw.js → NotificationSettings-CHGX5Jwm.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CRIFE2Tj.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DdoNAxsM.js → Organization-By7FkhtY.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Bw7R6dE3.js → Overflow--khGSzJn.js} +1 -1
- package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DJNtBugA.js → P2P-CAG9dH35.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dwhd5_ue.js → PdhVaultBrowser-B3SQZmK9.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-BYoSfhPR.js → Permissions-D35ec6JA.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-aYCYJbnH.js → PersonalDataHub-CxBF7hW_.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-DJp6CprF.js → Pipeline-s6EtOO1s.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Cq9HZweR.js → Privacy-BbJYmWmO.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CmF0HsSt.js → ProjectInit-VGQq1jMT.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-P8V5Aea2.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-D589v0q2.js → Providers-mrO47FIK.js} +1 -1
- package/src/assets/web-panel/assets/QrScannerModal-IJF2mHyw.js +1 -0
- package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-lvRV2osB.js → QuickAsk-CU0wN_Z0.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-sb84MTpY.js → Recommend-DhN37R6G.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-D_xOX_hu.js +4 -0
- package/src/assets/web-panel/assets/{Reputation-d9Y3vy-W.js → Reputation-D_Ssv7uH.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DMdhLUTp.js → Row-Kx5dKxX7.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-OL-SMDkz.js → RssFeed-BlGwqZkY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-BjStLKq1.js → Search-hLFQzxpb.js} +1 -1
- package/src/assets/web-panel/assets/{Security-MeI411qr.js → Security-DMlvsVt4.js} +2 -2
- package/src/assets/web-panel/assets/{Services-UajosuhT.js → Services-Bd0Qsj2m.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CGpG-QJ1.js → Skeleton-DFclq9X3.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C6P2RPY-.js → Skills-BNtln2qY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-B_fPprgw.js → Sla-Dch5H19G.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CqxLjSlQ.js → SpeechSettings-2UfQcU1g.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DixSfFOQ.js → SyncSettings-CD9YQr4i.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-PavzPBxc.js → Tasks-BNu-7MzI.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BYdGRnfX.js → Templates-D1n9CaIR.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BQLgnarD.js → Tenant-DenL3iBW.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-ZzU0Io1Y.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-Bv7uZRM7.js → TimelineRenderer-D87IfukO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DxhgszRJ.js → Tokens-BkiZc8E3.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-nQYHayuc.js → Trigger-BNRSytGN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CvqZDuZ4.js → Trust-Bwbd4X2m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DaR8GV2I.js → UkeySign-B8O2bs0o.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C6Mpsokw.js → VideoEditing-C2fL8zmH.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B57VRrWc.js → Wallet-BORYDdE1.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BbtEEtpq.js → WebAuthn-C0V5S2FM.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-C5LwvKmH.js → WorkflowEditor-2dR0fcHL.js} +1 -1
- package/src/assets/web-panel/assets/{chat-MzuPR5jh.js → chat-BmuAFAn8.js} +1 -1
- package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
- package/src/assets/web-panel/assets/{colors-HyW8mi_y.js → colors-CN3smMcK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DvUSzWAp.js → compact-item-DCnxb4kW.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-4MppZl7X.js → createContext-vlR43pHn.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DxwmsL4j.js +1 -0
- package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-D9q3CJ-u.js → hasIn-D04tHYFd.js} +1 -1
- package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9I5MuJ0.js → index-0DnaZGkC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Caqtu6Et.js → index-430S68MN.js} +1 -1
- package/src/assets/web-panel/assets/{index-eOQO6KnV.js → index-B78xL3s2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ci6adKh8.js → index-BTPEZTk1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CifKUNtV.js → index-BXRg8GFQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7TDMMfu.js → index-BasvsWDM.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNYqw0MO.js → index-BeGDEXFs.js} +1 -1
- package/src/assets/web-panel/assets/{index-m0b6Fj9q.js → index-BqhASEL4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BF2JsbiX.js → index-BudvKQfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFqvWeGc.js → index-C-6NO5il.js} +1 -1
- package/src/assets/web-panel/assets/{index-SDblbKj6.js → index-C5Sxb1sE.js} +1 -1
- package/src/assets/web-panel/assets/{index-UUMCA2Sq.js → index-CP9m9Ggr.js} +4 -4
- package/src/assets/web-panel/assets/{index--Bm6OqmU.js → index-CTOCnIQ7.js} +1 -1
- package/src/assets/web-panel/assets/index-Cg2ldRfU.js +1 -0
- package/src/assets/web-panel/assets/{index-Dv7ffI2g.js → index-ClhAHDK3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcunsBIx.js → index-CnXebZLL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvAqCAo9.js → index-CngCC8hC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcMkjb92.js → index-Cr-A0FYJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFGfC5RS.js → index-CsAsCPJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDZ6fP5Z.js → index-D28DeAAB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgIfXOD9.js → index-D5LZVZ0L.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmZ6-suL.js → index-D65_XseV.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs-RKRUM.js → index-D8vwp4qp.js} +1 -1
- package/src/assets/web-panel/assets/{index-B994UQfE.js → index-DDwLgQY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Btz8pU68.js → index-DIpY0qM5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5E4DhVc.js → index-DTWg-18U.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2jrnaq3.js → index-DTtRscUa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmCo2Xdp.js → index-DYAtmqiv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Zvtru2oE.js → index-D_n8_vfI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLAZs1Vd.js → index-DbADHwhr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CucRo4Vz.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-C0DiL97c.js → index-HhNjnCfe.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bl-E4W4q.js → index-ToUh2b1-.js} +1 -1
- package/src/assets/web-panel/assets/{index-ryFLxB1p.js → index-TslMTwNy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGQtFso0.js → index-_FIkN3jd.js} +1 -1
- package/src/assets/web-panel/assets/{index-zLwYpvX0.js → index-hpvvtAZ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dvg5xYuP.js → index-jzR7Ujuw.js} +1 -1
- package/src/assets/web-panel/assets/{index-xvUVuFnA.js → index-ljuBiQW9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CC--PMsC.js → initDefaultProps-C3wUb4je.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DdrBjRF1.js → motion-D-jYFUNA.js} +1 -1
- package/src/assets/web-panel/assets/{move-D712Gwl2.js → move-tqb8nwJ2.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-39y-keKO.js → mtc-parser-DSOjMJMz.js} +1 -1
- package/src/assets/web-panel/assets/{omit-6Y51gDB9.js → omit-CJ7VimIW.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Cy6EHbq9.js → pickAttrs-qXiG1iT3.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BYxdAm0p.js → placementArrow-CkGBcy1Z.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C9R6q_cr.js → responsiveObserve-D3WvTlLO.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CXUJlilB.js → slide-BEE2ftge.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DOtrOeql.js → statusUtils-CsfBpxiG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-D5r39o92.js → styleChecker-xPrIiJPI.js} +1 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-C5Yd-SCR.js +1 -0
- package/src/assets/web-panel/assets/{useFs-BkrT-Zbc.js → useFs-D_MDS8HI.js} +1 -1
- package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BwnnnZjU.js → usePersonalDataHub-BqJh-usA.js} +1 -1
- package/src/assets/web-panel/assets/{useRefs-DD9q4mOT.js → useRefs-CAeFRzPf.js} +1 -1
- package/src/assets/web-panel/assets/{useState-BK4hy8Ma.js → useState-LwExUYDO.js} +1 -1
- package/src/assets/web-panel/assets/{vendor-BvqAck49.js → vendor-BMxUs6UX.js} +16 -16
- package/src/assets/web-panel/assets/{vnode-SU-N4pum.js → vnode-chm_p-gz.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DHScfpTP.js → zoom-3-YDNz0Z.js} +1 -1
- package/src/assets/web-panel/index.html +3 -3
- 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/serve.js +10 -0
- package/src/commands/team.js +456 -0
- package/src/gateways/remote-session-relay.js +160 -0
- package/src/gateways/ws/message-dispatcher.js +77 -2
- package/src/gateways/ws/remote-session-protocol.js +466 -0
- package/src/gateways/ws/ws-server.js +299 -0
- package/src/harness/golden-transcript.js +65 -0
- package/src/harness/mcp-client.js +6 -2
- package/src/harness/prompt-compressor.js +27 -1
- package/src/harness/remote-command-ledger.js +158 -0
- package/src/harness/remote-session-audit-sink.js +132 -0
- package/src/harness/remote-session-audit.js +110 -0
- package/src/harness/remote-session-crypto.js +217 -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 +238 -0
- 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/remote-session-push.js +104 -0
- package/src/harness/remote-session-registry.js +420 -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/did-manager.js +6 -4
- 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/mcp-oauth.js +23 -6
- 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/session-manager.js +7 -3
- 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/agent-runtime.js +8 -13
- 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/runtime/policies/agent-policy.js +2 -0
- package/src/assets/web-panel/assets/Compliance-T8EtTecW.js +0 -1
- package/src/assets/web-panel/assets/MobileBridge-CYaITZ7w.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CmtqIdwr.js +0 -1
- package/src/assets/web-panel/assets/Projects-DPd5-lNS.js +0 -1
- package/src/assets/web-panel/assets/Terminal-BzOHpq-B.js +0 -3
- package/src/assets/web-panel/assets/devWarning-6YJJMjSH.js +0 -1
- package/src/assets/web-panel/assets/index-D_oeGLr6.js +0 -1
- package/src/assets/web-panel/assets/index-Qa57YG19.js +0 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-DfAD5U4Z.js +0 -1
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "crypto";
|
|
2
|
+
|
|
3
|
+
export const REMOTE_SESSION_PROTOCOL_VERSION = "1.0";
|
|
4
|
+
export const REMOTE_SESSION_SCOPES = Object.freeze([
|
|
5
|
+
"observe",
|
|
6
|
+
"prompt",
|
|
7
|
+
"approve",
|
|
8
|
+
"interrupt",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TOKEN_TTL_MS = 5 * 60 * 1000;
|
|
12
|
+
const DEFAULT_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
13
|
+
|
|
14
|
+
function hashToken(token) {
|
|
15
|
+
return createHash("sha256").update(String(token), "utf8").digest();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function tokensMatch(token, digest) {
|
|
19
|
+
const candidate = hashToken(token);
|
|
20
|
+
return (
|
|
21
|
+
candidate.length === digest.length && timingSafeEqual(candidate, digest)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeScopes(scopes) {
|
|
26
|
+
const requested = scopes || REMOTE_SESSION_SCOPES;
|
|
27
|
+
if (!Array.isArray(requested) || requested.length === 0) {
|
|
28
|
+
throw new TypeError("Remote session scopes must be a non-empty array");
|
|
29
|
+
}
|
|
30
|
+
const unique = [...new Set(requested)];
|
|
31
|
+
for (const scope of unique) {
|
|
32
|
+
if (!REMOTE_SESSION_SCOPES.includes(scope)) {
|
|
33
|
+
throw new Error(`Unsupported remote session scope: ${scope}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return unique;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Org-level constraints an administrator can impose on Remote Sessions. Lives
|
|
41
|
+
* next to the registry (its single enforcement point) so both the direct WS
|
|
42
|
+
* join and the relay pairing path go through the same checks. Permissive by
|
|
43
|
+
* default — an unconfigured policy is a no-op.
|
|
44
|
+
*/
|
|
45
|
+
export class RemoteSessionPolicy {
|
|
46
|
+
constructor({
|
|
47
|
+
allowedScopes = null,
|
|
48
|
+
maxDevices = null,
|
|
49
|
+
maxSessionTtlMs = null,
|
|
50
|
+
maxTokenTtlMs = null,
|
|
51
|
+
allowRelayPairing = true,
|
|
52
|
+
} = {}) {
|
|
53
|
+
this.allowedScopes = allowedScopes ? [...new Set(allowedScopes)] : null;
|
|
54
|
+
if (this.allowedScopes) {
|
|
55
|
+
if (this.allowedScopes.length === 0) {
|
|
56
|
+
throw new Error("allowedScopes cannot be empty");
|
|
57
|
+
}
|
|
58
|
+
for (const scope of this.allowedScopes) {
|
|
59
|
+
if (!REMOTE_SESSION_SCOPES.includes(scope)) {
|
|
60
|
+
throw new Error(`Unknown scope in org policy: ${scope}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.maxDevices =
|
|
65
|
+
maxDevices == null ? null : Math.max(0, Math.floor(maxDevices));
|
|
66
|
+
this.maxSessionTtlMs =
|
|
67
|
+
maxSessionTtlMs == null ? null : Math.max(1, Math.floor(maxSessionTtlMs));
|
|
68
|
+
this.maxTokenTtlMs =
|
|
69
|
+
maxTokenTtlMs == null ? null : Math.max(1, Math.floor(maxTokenTtlMs));
|
|
70
|
+
this.allowRelayPairing = allowRelayPairing !== false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Narrow requested scopes to the org-allowed set. Throws only when the request
|
|
75
|
+
* and the allow-list are wholly disjoint (i.e. nothing could be granted).
|
|
76
|
+
*/
|
|
77
|
+
applyScopes(requestedScopes) {
|
|
78
|
+
if (!this.allowedScopes) {
|
|
79
|
+
return { scopes: requestedScopes || null, narrowed: false };
|
|
80
|
+
}
|
|
81
|
+
const requested =
|
|
82
|
+
requestedScopes && requestedScopes.length
|
|
83
|
+
? [...new Set(requestedScopes)]
|
|
84
|
+
: [...REMOTE_SESSION_SCOPES];
|
|
85
|
+
const granted = requested.filter((scope) =>
|
|
86
|
+
this.allowedScopes.includes(scope),
|
|
87
|
+
);
|
|
88
|
+
if (granted.length === 0) {
|
|
89
|
+
throw new Error("Remote session scopes are not permitted by org policy");
|
|
90
|
+
}
|
|
91
|
+
return { scopes: granted, narrowed: granted.length !== requested.length };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
capSessionTtl(ttlMs) {
|
|
95
|
+
return this.maxSessionTtlMs == null
|
|
96
|
+
? ttlMs
|
|
97
|
+
: Math.min(ttlMs, this.maxSessionTtlMs);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
capTokenTtl(ttlMs) {
|
|
101
|
+
return this.maxTokenTtlMs == null
|
|
102
|
+
? ttlMs
|
|
103
|
+
: Math.min(ttlMs, this.maxTokenTtlMs);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
enforceJoin({ deviceCount, via } = {}) {
|
|
107
|
+
if (via === "relay" && !this.allowRelayPairing) {
|
|
108
|
+
throw new Error("Relay pairing is disabled by org policy");
|
|
109
|
+
}
|
|
110
|
+
if (this.maxDevices != null && deviceCount >= this.maxDevices) {
|
|
111
|
+
throw new Error("Org policy device limit reached");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
describe() {
|
|
116
|
+
return {
|
|
117
|
+
allowedScopes: this.allowedScopes,
|
|
118
|
+
maxDevices: this.maxDevices,
|
|
119
|
+
maxSessionTtlMs: this.maxSessionTtlMs,
|
|
120
|
+
maxTokenTtlMs: this.maxTokenTtlMs,
|
|
121
|
+
allowRelayPairing: this.allowRelayPairing,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
static fromEnv(env = {}) {
|
|
126
|
+
const options = {};
|
|
127
|
+
const scopes = env.CHAINLESSCHAIN_REMOTE_SESSION_ALLOWED_SCOPES;
|
|
128
|
+
if (scopes) {
|
|
129
|
+
options.allowedScopes = scopes
|
|
130
|
+
.split(",")
|
|
131
|
+
.map((scope) => scope.trim())
|
|
132
|
+
.filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
const num = (value) => {
|
|
135
|
+
const parsed = Number(value);
|
|
136
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
137
|
+
};
|
|
138
|
+
if (env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_DEVICES != null) {
|
|
139
|
+
options.maxDevices = num(env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_DEVICES);
|
|
140
|
+
}
|
|
141
|
+
if (env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_SESSION_TTL_MS != null) {
|
|
142
|
+
options.maxSessionTtlMs = num(
|
|
143
|
+
env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_SESSION_TTL_MS,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_TOKEN_TTL_MS != null) {
|
|
147
|
+
options.maxTokenTtlMs = num(
|
|
148
|
+
env.CHAINLESSCHAIN_REMOTE_SESSION_MAX_TOKEN_TTL_MS,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (env.CHAINLESSCHAIN_REMOTE_SESSION_ALLOW_RELAY != null) {
|
|
152
|
+
options.allowRelayPairing = !/^(0|false|no|off)$/i.test(
|
|
153
|
+
String(env.CHAINLESSCHAIN_REMOTE_SESSION_ALLOW_RELAY).trim(),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return new RemoteSessionPolicy(options);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function publicSession(session) {
|
|
161
|
+
return {
|
|
162
|
+
protocolVersion: REMOTE_SESSION_PROTOCOL_VERSION,
|
|
163
|
+
sessionId: session.sessionId,
|
|
164
|
+
agentSessionId: session.agentSessionId,
|
|
165
|
+
name: session.name,
|
|
166
|
+
hostClientId: session.hostClientId,
|
|
167
|
+
createdAt: session.createdAt,
|
|
168
|
+
expiresAt: session.expiresAt,
|
|
169
|
+
memberCount: session.members.size,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** In-memory authorization state for one local CLI process. */
|
|
174
|
+
export class RemoteSessionRegistry {
|
|
175
|
+
constructor(options = {}) {
|
|
176
|
+
this.now = options.now || Date.now;
|
|
177
|
+
this.tokenTtlMs = options.tokenTtlMs || DEFAULT_TOKEN_TTL_MS;
|
|
178
|
+
this.sessionTtlMs = options.sessionTtlMs || DEFAULT_SESSION_TTL_MS;
|
|
179
|
+
this.policy = options.policy || new RemoteSessionPolicy();
|
|
180
|
+
this.sessions = new Map();
|
|
181
|
+
this.tokens = new Map();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
create({ hostClientId, agentSessionId, name, scopes } = {}) {
|
|
185
|
+
if (!hostClientId) throw new Error("hostClientId is required");
|
|
186
|
+
if (!agentSessionId) throw new Error("agentSessionId is required");
|
|
187
|
+
const now = this.now();
|
|
188
|
+
const session = {
|
|
189
|
+
sessionId: randomUUID(),
|
|
190
|
+
agentSessionId,
|
|
191
|
+
name: name || agentSessionId,
|
|
192
|
+
hostClientId,
|
|
193
|
+
createdAt: now,
|
|
194
|
+
expiresAt: now + this.policy.capSessionTtl(this.sessionTtlMs),
|
|
195
|
+
members: new Map([
|
|
196
|
+
[
|
|
197
|
+
hostClientId,
|
|
198
|
+
{
|
|
199
|
+
clientId: hostClientId,
|
|
200
|
+
scopes: REMOTE_SESSION_SCOPES,
|
|
201
|
+
joinedAt: now,
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
]),
|
|
205
|
+
};
|
|
206
|
+
this.sessions.set(session.sessionId, session);
|
|
207
|
+
return {
|
|
208
|
+
session: publicSession(session),
|
|
209
|
+
pairing: this.issuePairingToken(session.sessionId, { scopes }),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
issuePairingToken(sessionId, { scopes } = {}) {
|
|
214
|
+
const session = this.requireSession(sessionId);
|
|
215
|
+
const token = randomBytes(32).toString("base64url");
|
|
216
|
+
const now = this.now();
|
|
217
|
+
const { scopes: applied, narrowed } = this.policy.applyScopes(scopes);
|
|
218
|
+
const grantedScopes = normalizeScopes(applied);
|
|
219
|
+
this.tokens.set(sessionId, {
|
|
220
|
+
digest: hashToken(token),
|
|
221
|
+
scopes: grantedScopes,
|
|
222
|
+
createdAt: now,
|
|
223
|
+
expiresAt: Math.min(
|
|
224
|
+
now + this.policy.capTokenTtl(this.tokenTtlMs),
|
|
225
|
+
session.expiresAt,
|
|
226
|
+
),
|
|
227
|
+
});
|
|
228
|
+
return {
|
|
229
|
+
token,
|
|
230
|
+
expiresAt: this.tokens.get(sessionId).expiresAt,
|
|
231
|
+
scopes: grantedScopes,
|
|
232
|
+
policyNarrowed: narrowed,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
join({
|
|
237
|
+
sessionId,
|
|
238
|
+
clientId,
|
|
239
|
+
token,
|
|
240
|
+
via = "direct",
|
|
241
|
+
pushToken = null,
|
|
242
|
+
pushProvider = null,
|
|
243
|
+
} = {}) {
|
|
244
|
+
if (!clientId) throw new Error("clientId is required");
|
|
245
|
+
const session = this.requireSession(sessionId);
|
|
246
|
+
const pairing = this.tokens.get(sessionId);
|
|
247
|
+
if (!pairing || pairing.expiresAt <= this.now()) {
|
|
248
|
+
this.tokens.delete(sessionId);
|
|
249
|
+
throw new Error("Pairing token is missing or expired");
|
|
250
|
+
}
|
|
251
|
+
if (!tokensMatch(token, pairing.digest)) {
|
|
252
|
+
throw new Error("Invalid pairing token");
|
|
253
|
+
}
|
|
254
|
+
// Org policy is enforced only after the token proves the caller is invited,
|
|
255
|
+
// so a device-limit / relay-disabled message never leaks to unauthenticated
|
|
256
|
+
// probes. Existing (non-host) devices count against the limit.
|
|
257
|
+
const deviceCount = [...session.members.values()].filter(
|
|
258
|
+
(member) => member.clientId !== session.hostClientId,
|
|
259
|
+
).length;
|
|
260
|
+
this.policy.enforceJoin({ deviceCount, via });
|
|
261
|
+
// Pairing credentials are deliberately one-time. The host must explicitly
|
|
262
|
+
// issue another token for each additional device.
|
|
263
|
+
this.tokens.delete(sessionId);
|
|
264
|
+
const member = {
|
|
265
|
+
clientId,
|
|
266
|
+
scopes: pairing.scopes,
|
|
267
|
+
joinedAt: this.now(),
|
|
268
|
+
pushToken: pushToken || null,
|
|
269
|
+
pushProvider: pushToken ? pushProvider || null : null,
|
|
270
|
+
};
|
|
271
|
+
session.members.set(clientId, member);
|
|
272
|
+
return { session: publicSession(session), member: { ...member } };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* A device registers (or refreshes / clears) its own vendor push token after
|
|
277
|
+
* pairing — e.g. once FCM assigns one. Only an existing member may set its own
|
|
278
|
+
* token. A null token clears push for that device.
|
|
279
|
+
*/
|
|
280
|
+
registerPush(sessionId, clientId, { token = null, provider = null } = {}) {
|
|
281
|
+
const session = this.requireSession(sessionId);
|
|
282
|
+
const member = session.members.get(clientId);
|
|
283
|
+
if (!member) {
|
|
284
|
+
throw new Error("Device is not paired with this remote session");
|
|
285
|
+
}
|
|
286
|
+
member.pushToken = token || null;
|
|
287
|
+
member.pushProvider = token ? provider || null : null;
|
|
288
|
+
return {
|
|
289
|
+
clientId,
|
|
290
|
+
hasPush: Boolean(member.pushToken),
|
|
291
|
+
provider: member.pushProvider,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Non-host members that carry a push token, for wake-up dispatch. */
|
|
296
|
+
pushTargets(sessionId, { excludeClientId } = {}) {
|
|
297
|
+
const session = this.requireSession(sessionId);
|
|
298
|
+
const targets = [];
|
|
299
|
+
for (const member of session.members.values()) {
|
|
300
|
+
if (member.clientId === session.hostClientId) continue;
|
|
301
|
+
if (member.clientId === excludeClientId) continue;
|
|
302
|
+
if (!member.pushToken) continue;
|
|
303
|
+
targets.push({
|
|
304
|
+
clientId: member.clientId,
|
|
305
|
+
pushToken: member.pushToken,
|
|
306
|
+
pushProvider: member.pushProvider,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return targets;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
authorize(sessionId, clientId, scope) {
|
|
313
|
+
const session = this.requireSession(sessionId);
|
|
314
|
+
const member = session.members.get(clientId);
|
|
315
|
+
if (!member)
|
|
316
|
+
throw new Error("Client is not paired with this remote session");
|
|
317
|
+
if (!member.scopes.includes(scope)) {
|
|
318
|
+
throw new Error(`Remote session scope required: ${scope}`);
|
|
319
|
+
}
|
|
320
|
+
return { session, member };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
members(sessionId) {
|
|
324
|
+
const session = this.requireSession(sessionId);
|
|
325
|
+
return [...session.members.values()].map((member) => ({ ...member }));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Host-only view of every paired device on a session. The host flag lets the
|
|
330
|
+
* UI keep the host row un-revocable and label it distinctly.
|
|
331
|
+
*/
|
|
332
|
+
listDevices(sessionId, hostClientId) {
|
|
333
|
+
const session = this.requireSession(sessionId);
|
|
334
|
+
if (hostClientId && session.hostClientId !== hostClientId) {
|
|
335
|
+
throw new Error("Only the host can list paired devices");
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
session: publicSession(session),
|
|
339
|
+
devices: [...session.members.values()].map((member) => ({
|
|
340
|
+
clientId: member.clientId,
|
|
341
|
+
scopes: [...member.scopes],
|
|
342
|
+
joinedAt: member.joinedAt,
|
|
343
|
+
isHost: member.clientId === session.hostClientId,
|
|
344
|
+
hasPush: Boolean(member.pushToken),
|
|
345
|
+
pushProvider: member.pushProvider || null,
|
|
346
|
+
})),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Host-initiated revocation of a single paired device. The host cannot revoke
|
|
352
|
+
* itself (use close() to end the whole session). Returns the removed member so
|
|
353
|
+
* callers can push a revocation notice to that device.
|
|
354
|
+
*/
|
|
355
|
+
revokeMember(sessionId, hostClientId, clientId) {
|
|
356
|
+
if (!clientId) throw new Error("clientId is required");
|
|
357
|
+
const session = this.requireSession(sessionId);
|
|
358
|
+
if (session.hostClientId !== hostClientId) {
|
|
359
|
+
throw new Error("Only the host can revoke devices");
|
|
360
|
+
}
|
|
361
|
+
if (clientId === session.hostClientId) {
|
|
362
|
+
throw new Error("Cannot revoke the host device");
|
|
363
|
+
}
|
|
364
|
+
const member = session.members.get(clientId);
|
|
365
|
+
if (!member)
|
|
366
|
+
throw new Error("Device is not paired with this remote session");
|
|
367
|
+
session.members.delete(clientId);
|
|
368
|
+
return { session: publicSession(session), member: { ...member } };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
findHosted(agentSessionId, hostClientId) {
|
|
372
|
+
const matches = [];
|
|
373
|
+
for (const session of this.sessions.values()) {
|
|
374
|
+
if (
|
|
375
|
+
session.agentSessionId === agentSessionId &&
|
|
376
|
+
session.hostClientId === hostClientId &&
|
|
377
|
+
session.expiresAt > this.now()
|
|
378
|
+
) {
|
|
379
|
+
matches.push(session);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return matches;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
removeClient(clientId) {
|
|
386
|
+
const affected = [];
|
|
387
|
+
for (const [sessionId, session] of this.sessions) {
|
|
388
|
+
if (!session.members.delete(clientId)) continue;
|
|
389
|
+
if (session.hostClientId === clientId) {
|
|
390
|
+
this.sessions.delete(sessionId);
|
|
391
|
+
this.tokens.delete(sessionId);
|
|
392
|
+
affected.push({ sessionId, closed: true });
|
|
393
|
+
} else {
|
|
394
|
+
affected.push({ sessionId, closed: false });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return affected;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
close(sessionId, clientId) {
|
|
401
|
+
const session = this.requireSession(sessionId);
|
|
402
|
+
if (session.hostClientId !== clientId) {
|
|
403
|
+
throw new Error("Only the host can close a Remote Session");
|
|
404
|
+
}
|
|
405
|
+
this.sessions.delete(sessionId);
|
|
406
|
+
this.tokens.delete(sessionId);
|
|
407
|
+
return publicSession(session);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
requireSession(sessionId) {
|
|
411
|
+
const session = this.sessions.get(sessionId);
|
|
412
|
+
if (!session) throw new Error("Remote session not found");
|
|
413
|
+
if (session.expiresAt <= this.now()) {
|
|
414
|
+
this.sessions.delete(sessionId);
|
|
415
|
+
this.tokens.delete(sessionId);
|
|
416
|
+
throw new Error("Remote session expired");
|
|
417
|
+
}
|
|
418
|
+
return session;
|
|
419
|
+
}
|
|
420
|
+
}
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,8 @@ import { registerAskCommand } from "./commands/ask.js";
|
|
|
17
17
|
import { registerLlmCommand } from "./commands/llm.js";
|
|
18
18
|
import { registerHubCommand } from "./commands/hub.js";
|
|
19
19
|
import { registerAndroidCommand } from "./commands/android.js";
|
|
20
|
+
import { registerEvalCommand } from "./commands/eval.js";
|
|
21
|
+
import { registerTeamCommand } from "./commands/team.js";
|
|
20
22
|
import {
|
|
21
23
|
registerAgentCommand,
|
|
22
24
|
registerSubAgentV2Command,
|
|
@@ -58,6 +60,7 @@ import { registerRCacheCommand } from "./commands/rcache.js";
|
|
|
58
60
|
import { registerSessionCommand } from "./commands/session.js";
|
|
59
61
|
import { registerCostCommand } from "./commands/cost.js";
|
|
60
62
|
import { registerContextCommand } from "./commands/context.js";
|
|
63
|
+
import { registerCodeIntelCommand } from "./commands/codeintel.js";
|
|
61
64
|
import { registerCheckpointCommand } from "./commands/checkpoint.js";
|
|
62
65
|
import { registerGoalCommand } from "./commands/goal.js";
|
|
63
66
|
import { registerCommandCommand } from "./commands/command.js";
|
|
@@ -428,6 +431,8 @@ export function createProgram(opts = {}) {
|
|
|
428
431
|
registerHubCommand(program);
|
|
429
432
|
registerAndroidCommand(program);
|
|
430
433
|
registerAgentCommand(program);
|
|
434
|
+
registerEvalCommand(program);
|
|
435
|
+
registerTeamCommand(program);
|
|
431
436
|
registerSubAgentV2Command(program);
|
|
432
437
|
registerExecBackendV2Command(program);
|
|
433
438
|
registerTodoV2Command(program);
|
|
@@ -468,6 +473,7 @@ export function createProgram(opts = {}) {
|
|
|
468
473
|
registerSessionCommand(program);
|
|
469
474
|
registerCostCommand(program);
|
|
470
475
|
registerContextCommand(program);
|
|
476
|
+
registerCodeIntelCommand(program);
|
|
471
477
|
registerCheckpointCommand(program);
|
|
472
478
|
registerGoalCommand(program);
|
|
473
479
|
registerCommandCommand(program);
|
package/src/lib/agent-core.js
CHANGED
package/src/lib/agent-sandbox.js
CHANGED
|
@@ -1,29 +1,107 @@
|
|
|
1
1
|
/** OS-isolated shell execution for the coding agent. */
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { proxyEnv } from "./sandbox-egress-proxy.js";
|
|
4
5
|
|
|
5
6
|
export const DEFAULT_SANDBOX_IMAGE = "node:22-bookworm-slim";
|
|
6
7
|
export const _deps = { spawnSync };
|
|
7
8
|
|
|
9
|
+
function stringList(value) {
|
|
10
|
+
if (!Array.isArray(value)) return [];
|
|
11
|
+
return [
|
|
12
|
+
...new Set(
|
|
13
|
+
value.map((entry) => String(entry || "").trim()).filter(Boolean),
|
|
14
|
+
),
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolvePolicyPaths(entries, cwd) {
|
|
19
|
+
return stringList(entries).map((entry) => {
|
|
20
|
+
if (entry.startsWith("~/")) {
|
|
21
|
+
return path.resolve(
|
|
22
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
23
|
+
entry.slice(2),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return path.resolve(cwd, entry);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeSandboxPolicy(settings = {}, cwd = process.cwd()) {
|
|
31
|
+
const filesystem = settings.filesystem || {};
|
|
32
|
+
const network = settings.network || {};
|
|
33
|
+
return {
|
|
34
|
+
allowRead: resolvePolicyPaths(filesystem.allowRead, cwd),
|
|
35
|
+
denyRead: resolvePolicyPaths(filesystem.denyRead, cwd),
|
|
36
|
+
allowWrite: resolvePolicyPaths(filesystem.allowWrite, cwd),
|
|
37
|
+
denyWrite: resolvePolicyPaths(filesystem.denyWrite, cwd),
|
|
38
|
+
allowedDomains: stringList(network.allowedDomains),
|
|
39
|
+
deniedDomains: stringList(network.deniedDomains),
|
|
40
|
+
excludedCommands: stringList(settings.excludedCommands),
|
|
41
|
+
allowUnsandboxedCommands: settings.allowUnsandboxedCommands !== false,
|
|
42
|
+
failIfUnavailable: settings.failIfUnavailable === true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
8
46
|
export function normalizeAgentSandbox(value, options = {}) {
|
|
9
|
-
|
|
47
|
+
const settings = options.settings || {};
|
|
48
|
+
if (!value && settings.enabled !== true) return null;
|
|
49
|
+
if (value === false || settings.enabled === false) return null;
|
|
10
50
|
const image =
|
|
11
51
|
typeof value === "string" && value.trim() && value !== "true"
|
|
12
52
|
? value.trim()
|
|
13
53
|
: DEFAULT_SANDBOX_IMAGE;
|
|
14
54
|
return {
|
|
15
|
-
engine: "docker",
|
|
16
|
-
image,
|
|
55
|
+
engine: settings.engine || "docker",
|
|
56
|
+
image: settings.image || image,
|
|
17
57
|
cwd: path.resolve(options.cwd || process.cwd()),
|
|
18
|
-
network: options.network === true,
|
|
58
|
+
network: options.network === true || settings.network === true,
|
|
59
|
+
policy: normalizeSandboxPolicy(settings, options.cwd || process.cwd()),
|
|
19
60
|
};
|
|
20
61
|
}
|
|
21
62
|
|
|
22
63
|
export function executeSandboxedShell(command, sandbox, options = {}) {
|
|
23
|
-
if (!sandbox || sandbox.engine
|
|
64
|
+
if (!sandbox || !["docker", "bubblewrap"].includes(sandbox.engine)) {
|
|
24
65
|
throw new Error("A supported agent sandbox configuration is required");
|
|
25
66
|
}
|
|
26
67
|
const hostCwd = path.resolve(options.cwd || sandbox.cwd);
|
|
68
|
+
const policy = sandbox.policy || normalizeSandboxPolicy({}, hostCwd);
|
|
69
|
+
// An egress proxy (see sandbox-egress-proxy.js) ENFORCES the domain allow/deny
|
|
70
|
+
// policy: the caller starts it and passes `{ env }` here, and the sandboxed
|
|
71
|
+
// process routes its HTTP(S) traffic through it. Without a proxy, a
|
|
72
|
+
// domain-restricted network request has no enforcement point, so we refuse
|
|
73
|
+
// rather than grant unrestricted access.
|
|
74
|
+
const egress = options.egressProxy || null;
|
|
75
|
+
if (
|
|
76
|
+
(policy.allowedDomains.length || policy.deniedDomains.length) &&
|
|
77
|
+
sandbox.network &&
|
|
78
|
+
!egress
|
|
79
|
+
) {
|
|
80
|
+
return {
|
|
81
|
+
stdout: "",
|
|
82
|
+
stderr:
|
|
83
|
+
"Domain-restricted sandbox networking requires a configured sandbox proxy; refusing unrestricted network access",
|
|
84
|
+
exitCode: 1,
|
|
85
|
+
failedToStart: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (sandbox.engine === "bubblewrap") {
|
|
89
|
+
return executeBubblewrapShell(command, sandbox, options, hostCwd, policy);
|
|
90
|
+
}
|
|
91
|
+
if (
|
|
92
|
+
policy.allowRead.length ||
|
|
93
|
+
policy.denyRead.length ||
|
|
94
|
+
policy.allowWrite.length ||
|
|
95
|
+
policy.denyWrite.length
|
|
96
|
+
) {
|
|
97
|
+
return {
|
|
98
|
+
stdout: "",
|
|
99
|
+
stderr:
|
|
100
|
+
"The Docker sandbox backend cannot enforce fine-grained filesystem policy; use engine=bubblewrap",
|
|
101
|
+
exitCode: 1,
|
|
102
|
+
failedToStart: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
27
105
|
const args = ["run", "--rm", "--init"];
|
|
28
106
|
if (!sandbox.network) args.push("--network", "none");
|
|
29
107
|
args.push("--mount", `type=bind,source=${hostCwd},target=/workspace`);
|
|
@@ -31,6 +109,17 @@ export function executeSandboxedShell(command, sandbox, options = {}) {
|
|
|
31
109
|
if (process.platform !== "win32" && process.getuid && process.getgid) {
|
|
32
110
|
args.push("--user", `${process.getuid()}:${process.getgid()}`);
|
|
33
111
|
}
|
|
112
|
+
// Route egress through the host proxy so the domain policy is enforced. The
|
|
113
|
+
// container reaches the host proxy via host.docker.internal (mapped to the
|
|
114
|
+
// gateway on Linux via host-gateway); the caller builds `egress.env` with that
|
|
115
|
+
// hostname (proxyEnv(port, "host.docker.internal")).
|
|
116
|
+
if (egress && egress.port && sandbox.network) {
|
|
117
|
+
args.push("--add-host", "host.docker.internal:host-gateway");
|
|
118
|
+
const penv = proxyEnv(egress.port, "host.docker.internal");
|
|
119
|
+
for (const [k, v] of Object.entries(penv)) {
|
|
120
|
+
args.push("--env", `${k}=${v}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
34
123
|
const env = options.env || {};
|
|
35
124
|
for (const key of ["CLAUDECODE", "CC_SESSION_ID", "CLAUDE_CODE_SESSION_ID"]) {
|
|
36
125
|
if (env[key] != null) args.push("--env", `${key}=${env[key]}`);
|
|
@@ -61,6 +150,65 @@ export function executeSandboxedShell(command, sandbox, options = {}) {
|
|
|
61
150
|
};
|
|
62
151
|
}
|
|
63
152
|
|
|
153
|
+
function executeBubblewrapShell(command, sandbox, options, hostCwd, policy) {
|
|
154
|
+
const args = [
|
|
155
|
+
"--die-with-parent",
|
|
156
|
+
"--new-session",
|
|
157
|
+
"--unshare-all",
|
|
158
|
+
"--ro-bind",
|
|
159
|
+
"/",
|
|
160
|
+
"/",
|
|
161
|
+
"--bind",
|
|
162
|
+
hostCwd,
|
|
163
|
+
hostCwd,
|
|
164
|
+
"--chdir",
|
|
165
|
+
hostCwd,
|
|
166
|
+
"--proc",
|
|
167
|
+
"/proc",
|
|
168
|
+
"--dev",
|
|
169
|
+
"/dev",
|
|
170
|
+
"--tmpfs",
|
|
171
|
+
"/tmp",
|
|
172
|
+
];
|
|
173
|
+
if (sandbox.network) args.push("--share-net");
|
|
174
|
+
for (const target of policy.allowWrite) args.push("--bind", target, target);
|
|
175
|
+
for (const target of policy.denyWrite) args.push("--ro-bind", target, target);
|
|
176
|
+
for (const target of policy.denyRead) args.push("--tmpfs", target);
|
|
177
|
+
args.push("--", "sh", "-lc", String(command || ""));
|
|
178
|
+
// bwrap `--share-net` shares the host network namespace, so the egress proxy
|
|
179
|
+
// is reachable at 127.0.0.1 directly — merge its env into the child's.
|
|
180
|
+
const egress = options.egressProxy || null;
|
|
181
|
+
const bwrapEnv =
|
|
182
|
+
egress && egress.port && sandbox.network
|
|
183
|
+
? { ...(options.env || {}), ...proxyEnv(egress.port, "127.0.0.1") }
|
|
184
|
+
: options.env;
|
|
185
|
+
const result = _deps.spawnSync("bwrap", args, {
|
|
186
|
+
cwd: hostCwd,
|
|
187
|
+
env: bwrapEnv,
|
|
188
|
+
encoding: "utf8",
|
|
189
|
+
timeout: options.timeout,
|
|
190
|
+
maxBuffer: options.maxBuffer,
|
|
191
|
+
windowsHide: true,
|
|
192
|
+
});
|
|
193
|
+
if (result.error) {
|
|
194
|
+
return {
|
|
195
|
+
stdout: result.stdout || "",
|
|
196
|
+
stderr:
|
|
197
|
+
result.error.code === "ENOENT"
|
|
198
|
+
? "bubblewrap is not installed"
|
|
199
|
+
: result.error.message,
|
|
200
|
+
exitCode: typeof result.status === "number" ? result.status : 1,
|
|
201
|
+
failedToStart: true,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
stdout: result.stdout || "",
|
|
206
|
+
stderr: result.stderr || "",
|
|
207
|
+
exitCode: typeof result.status === "number" ? result.status : 1,
|
|
208
|
+
signal: result.signal || null,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
64
212
|
export function sandboxSummary(sandbox) {
|
|
65
213
|
if (!sandbox) return null;
|
|
66
214
|
return {
|
|
@@ -68,5 +216,13 @@ export function sandboxSummary(sandbox) {
|
|
|
68
216
|
image: sandbox.image,
|
|
69
217
|
network: sandbox.network ? "enabled" : "disabled",
|
|
70
218
|
workspace: "read-write",
|
|
219
|
+
policy: {
|
|
220
|
+
additionalReadPaths: sandbox.policy?.allowRead?.length || 0,
|
|
221
|
+
additionalWritePaths: sandbox.policy?.allowWrite?.length || 0,
|
|
222
|
+
networkRestricted:
|
|
223
|
+
Boolean(sandbox.policy?.allowedDomains?.length) ||
|
|
224
|
+
Boolean(sandbox.policy?.deniedDomains?.length),
|
|
225
|
+
failIfUnavailable: sandbox.policy?.failIfUnavailable === true,
|
|
226
|
+
},
|
|
71
227
|
};
|
|
72
228
|
}
|