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
|
@@ -27,6 +27,19 @@ import {
|
|
|
27
27
|
createRuntimeEvent,
|
|
28
28
|
} from "../../runtime/runtime-events.js";
|
|
29
29
|
import { createWsMessageDispatcher } from "./message-dispatcher.js";
|
|
30
|
+
import {
|
|
31
|
+
RemoteSessionRegistry,
|
|
32
|
+
RemoteSessionPolicy,
|
|
33
|
+
} from "../../harness/remote-session-registry.js";
|
|
34
|
+
import { RemoteSessionAuditLog } from "../../harness/remote-session-audit.js";
|
|
35
|
+
import { RemoteSessionAuditFileSink } from "../../harness/remote-session-audit-sink.js";
|
|
36
|
+
import {
|
|
37
|
+
RemoteSessionPushDispatcher,
|
|
38
|
+
isApprovalRequestEvent,
|
|
39
|
+
} from "../../harness/remote-session-push.js";
|
|
40
|
+
import { createRemoteSessionPushSender } from "../../harness/remote-session-push-senders.js";
|
|
41
|
+
import { RemoteSessionRelay } from "../remote-session-relay.js";
|
|
42
|
+
import { handleRemoteSessionPublish } from "./remote-session-protocol.js";
|
|
30
43
|
import { handleTaskDetail, handleTaskHistory } from "./task-protocol.js";
|
|
31
44
|
import {
|
|
32
45
|
handleSessionCreate,
|
|
@@ -226,6 +239,63 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
226
239
|
|
|
227
240
|
/** Session handlers: sessionId → WSAgentHandler | WSChatHandler */
|
|
228
241
|
this.sessionHandlers = new Map();
|
|
242
|
+
/** Org-policy constraints for Remote Sessions (scopes, device cap, TTL). */
|
|
243
|
+
this.remoteSessionPolicy =
|
|
244
|
+
options.remoteSessionPolicy || RemoteSessionPolicy.fromEnv(process.env);
|
|
245
|
+
/** Local authorization state for multi-device Remote Sessions. */
|
|
246
|
+
this.remoteSessions =
|
|
247
|
+
options.remoteSessionRegistry ||
|
|
248
|
+
new RemoteSessionRegistry({ policy: this.remoteSessionPolicy });
|
|
249
|
+
/**
|
|
250
|
+
* Optional durable sink (JSONL) for the audit trail. Enabled only when a
|
|
251
|
+
* sink is injected or CHAINLESSCHAIN_REMOTE_SESSION_AUDIT_FILE is set; the
|
|
252
|
+
* in-memory ring stays the primary store and query surface.
|
|
253
|
+
*/
|
|
254
|
+
this.remoteSessionAuditSink =
|
|
255
|
+
options.remoteSessionAuditSink ||
|
|
256
|
+
RemoteSessionAuditFileSink.fromEnv(process.env);
|
|
257
|
+
/** Bounded in-memory audit trail for Remote Session lifecycle + control. */
|
|
258
|
+
this.remoteSessionAudit =
|
|
259
|
+
options.remoteSessionAudit ||
|
|
260
|
+
new RemoteSessionAuditLog(
|
|
261
|
+
this.remoteSessionAuditSink
|
|
262
|
+
? {
|
|
263
|
+
sink: this.remoteSessionAuditSink.handler,
|
|
264
|
+
// Hydrate so audit queries survive a restart; cap to the ring size.
|
|
265
|
+
initialEntries: this.remoteSessionAuditSink.readAll({
|
|
266
|
+
limit: 1000,
|
|
267
|
+
}),
|
|
268
|
+
}
|
|
269
|
+
: {},
|
|
270
|
+
);
|
|
271
|
+
/**
|
|
272
|
+
* Vendor-push dispatcher — wakes backgrounded paired devices for approval
|
|
273
|
+
* requests. Credential-free by default (a no-op until a `sender` is
|
|
274
|
+
* injected); FCM/APNs delivery is host-supplied.
|
|
275
|
+
*/
|
|
276
|
+
this.remoteSessionPush =
|
|
277
|
+
options.remoteSessionPush ||
|
|
278
|
+
RemoteSessionPushDispatcher.fromEnv(process.env, {
|
|
279
|
+
// Prefer an explicitly injected sender (tests / custom transport); else
|
|
280
|
+
// build a real FCM sender from env when service-account creds are
|
|
281
|
+
// configured. Stays a no-op (null) when neither is present.
|
|
282
|
+
sender:
|
|
283
|
+
options.remoteSessionPushSender ||
|
|
284
|
+
createRemoteSessionPushSender(process.env),
|
|
285
|
+
});
|
|
286
|
+
this.remoteSessionRelayUrl = options.remoteSessionRelayUrl || null;
|
|
287
|
+
this.remoteSessionPeerId = options.remoteSessionPeerId || null;
|
|
288
|
+
this.remoteSessionCrypto = new Map();
|
|
289
|
+
this.remoteSessionPairingSecrets = new Map();
|
|
290
|
+
this.remoteSessionRelay =
|
|
291
|
+
options.remoteSessionRelay ||
|
|
292
|
+
(this.remoteSessionRelayUrl && this.remoteSessionPeerId
|
|
293
|
+
? new RemoteSessionRelay({
|
|
294
|
+
relayUrl: this.remoteSessionRelayUrl,
|
|
295
|
+
peerId: this.remoteSessionPeerId,
|
|
296
|
+
})
|
|
297
|
+
: null);
|
|
298
|
+
this._attachRemoteSessionRelay();
|
|
229
299
|
this._dispatcher = createWsMessageDispatcher(this);
|
|
230
300
|
|
|
231
301
|
this._heartbeatTimer = null;
|
|
@@ -251,6 +321,12 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
251
321
|
this.port = addr.port;
|
|
252
322
|
}
|
|
253
323
|
this._startHeartbeat();
|
|
324
|
+
this.remoteSessionRelay?.connect().catch((error) => {
|
|
325
|
+
this.emit("client-error", {
|
|
326
|
+
clientId: "remote-session-relay",
|
|
327
|
+
error: error.message,
|
|
328
|
+
});
|
|
329
|
+
});
|
|
254
330
|
this.emit("listening", { port: this.port, host: this.host });
|
|
255
331
|
resolve();
|
|
256
332
|
});
|
|
@@ -285,6 +361,9 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
285
361
|
}
|
|
286
362
|
}
|
|
287
363
|
this.sessionHandlers.clear();
|
|
364
|
+
this.remoteSessionCrypto.clear();
|
|
365
|
+
this.remoteSessionPairingSecrets.clear();
|
|
366
|
+
this.remoteSessionRelay?.close();
|
|
288
367
|
|
|
289
368
|
// Kill all running child processes
|
|
290
369
|
for (const [id, child] of this.processes) {
|
|
@@ -391,6 +470,18 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
391
470
|
client.authTimer = null;
|
|
392
471
|
}
|
|
393
472
|
this.clients.delete(clientId);
|
|
473
|
+
for (const affected of this.remoteSessions.removeClient(clientId)) {
|
|
474
|
+
if (affected.closed)
|
|
475
|
+
this.remoteSessionCrypto.delete(affected.sessionId);
|
|
476
|
+
if (affected.closed)
|
|
477
|
+
this.remoteSessionPairingSecrets.delete(affected.sessionId);
|
|
478
|
+
this.remoteSessionAudit?.record({
|
|
479
|
+
sessionId: affected.sessionId,
|
|
480
|
+
actor: clientId,
|
|
481
|
+
action: affected.closed ? "session.closed" : "device.disconnected",
|
|
482
|
+
detail: { reason: "disconnect" },
|
|
483
|
+
});
|
|
484
|
+
}
|
|
394
485
|
this.emit("disconnection", { clientId });
|
|
395
486
|
});
|
|
396
487
|
|
|
@@ -975,12 +1066,220 @@ export class ChainlessChainWSServer extends EventEmitter {
|
|
|
975
1066
|
if (ws.readyState === ws.OPEN) {
|
|
976
1067
|
try {
|
|
977
1068
|
ws.send(JSON.stringify(data));
|
|
1069
|
+
this._mirrorRemoteSessionEvent(ws, data);
|
|
978
1070
|
} catch (_err) {
|
|
979
1071
|
// Connection may have just closed
|
|
980
1072
|
}
|
|
981
1073
|
}
|
|
982
1074
|
}
|
|
983
1075
|
|
|
1076
|
+
/** Mirror canonical Agent Session output to paired Remote Session clients. */
|
|
1077
|
+
_mirrorRemoteSessionEvent(hostWs, data) {
|
|
1078
|
+
if (
|
|
1079
|
+
!data ||
|
|
1080
|
+
typeof data !== "object" ||
|
|
1081
|
+
!data.sessionId ||
|
|
1082
|
+
String(data.type || "").startsWith("remote-session-")
|
|
1083
|
+
) {
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
let hostClientId = null;
|
|
1087
|
+
for (const [clientId, client] of this.clients) {
|
|
1088
|
+
if (client.ws === hostWs) {
|
|
1089
|
+
hostClientId = clientId;
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (!hostClientId) return;
|
|
1094
|
+
|
|
1095
|
+
for (const remoteSession of this.remoteSessions.findHosted(
|
|
1096
|
+
data.sessionId,
|
|
1097
|
+
hostClientId,
|
|
1098
|
+
)) {
|
|
1099
|
+
for (const member of remoteSession.members.values()) {
|
|
1100
|
+
if (
|
|
1101
|
+
member.clientId === hostClientId ||
|
|
1102
|
+
!member.scopes.includes("observe")
|
|
1103
|
+
) {
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
// Wake a backgrounded device via vendor push for approval requests.
|
|
1107
|
+
// Independent of the WS mirror below — a relay-paired mobile app may
|
|
1108
|
+
// report a live socket while its process is suspended and will never
|
|
1109
|
+
// read the mirrored event until the user taps the push.
|
|
1110
|
+
if (member.pushToken && isApprovalRequestEvent(data.type)) {
|
|
1111
|
+
this._dispatchApprovalPush(remoteSession, member, data);
|
|
1112
|
+
}
|
|
1113
|
+
const target = this.clients.get(member.clientId);
|
|
1114
|
+
if (!target && this.remoteSessionRelay) {
|
|
1115
|
+
const crypto = this.remoteSessionCrypto.get(remoteSession.sessionId);
|
|
1116
|
+
if (crypto) {
|
|
1117
|
+
this.remoteSessionRelay.sendEncrypted(
|
|
1118
|
+
member.clientId,
|
|
1119
|
+
crypto.encrypt(member.clientId, data),
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
if (!target) continue;
|
|
1125
|
+
this._send(target.ws, {
|
|
1126
|
+
type: "remote-session-event",
|
|
1127
|
+
remoteSessionId: remoteSession.sessionId,
|
|
1128
|
+
agentSessionId: remoteSession.agentSessionId,
|
|
1129
|
+
event: data,
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Fire-and-forget a vendor push to one member for an approval request, then
|
|
1137
|
+
* record the outcome in the audit trail. Never throws (the dispatcher is
|
|
1138
|
+
* best-effort); the WS mirror + in-app notification remain primary.
|
|
1139
|
+
*/
|
|
1140
|
+
_dispatchApprovalPush(remoteSession, member, data) {
|
|
1141
|
+
const dispatcher = this.remoteSessionPush;
|
|
1142
|
+
if (!dispatcher || !dispatcher.enabled) return;
|
|
1143
|
+
const requestId =
|
|
1144
|
+
data.requestId || data.approvalId || data.id || `approval:${data.type}`;
|
|
1145
|
+
dispatcher
|
|
1146
|
+
.dispatch({
|
|
1147
|
+
token: member.pushToken,
|
|
1148
|
+
provider: member.pushProvider,
|
|
1149
|
+
sessionId: remoteSession.sessionId,
|
|
1150
|
+
clientId: member.clientId,
|
|
1151
|
+
dedupeKey: requestId,
|
|
1152
|
+
notification: {
|
|
1153
|
+
title: "Approval requested",
|
|
1154
|
+
body: "A coding session needs your approval",
|
|
1155
|
+
},
|
|
1156
|
+
})
|
|
1157
|
+
.then((outcome) => {
|
|
1158
|
+
// A vendor that reports the token as retired means the app was
|
|
1159
|
+
// uninstalled / the token rotated — prune it so it is never retried.
|
|
1160
|
+
if (outcome.code === "PUSH_TOKEN_UNREGISTERED") {
|
|
1161
|
+
try {
|
|
1162
|
+
this.remoteSessions.registerPush(
|
|
1163
|
+
remoteSession.sessionId,
|
|
1164
|
+
member.clientId,
|
|
1165
|
+
{ token: null },
|
|
1166
|
+
);
|
|
1167
|
+
} catch {
|
|
1168
|
+
// Member may have been revoked meanwhile — nothing to prune.
|
|
1169
|
+
}
|
|
1170
|
+
this.remoteSessionAudit?.record({
|
|
1171
|
+
sessionId: remoteSession.sessionId,
|
|
1172
|
+
actor: member.clientId,
|
|
1173
|
+
action: "push.unregistered",
|
|
1174
|
+
detail: { provider: member.pushProvider || null },
|
|
1175
|
+
});
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
this.remoteSessionAudit?.record({
|
|
1179
|
+
sessionId: remoteSession.sessionId,
|
|
1180
|
+
actor: member.clientId,
|
|
1181
|
+
action:
|
|
1182
|
+
outcome.status === "sent"
|
|
1183
|
+
? "push.sent"
|
|
1184
|
+
: outcome.status === "failed"
|
|
1185
|
+
? "push.failed"
|
|
1186
|
+
: "push.skipped",
|
|
1187
|
+
detail: {
|
|
1188
|
+
provider: member.pushProvider || null,
|
|
1189
|
+
reason: outcome.reason || null,
|
|
1190
|
+
error: outcome.error || null,
|
|
1191
|
+
},
|
|
1192
|
+
});
|
|
1193
|
+
})
|
|
1194
|
+
.catch(() => {
|
|
1195
|
+
// Dispatcher never rejects, but guard anyway — a failed courtesy push
|
|
1196
|
+
// must not surface as an unhandled rejection.
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
_attachRemoteSessionRelay() {
|
|
1201
|
+
if (!this.remoteSessionRelay) return;
|
|
1202
|
+
this.remoteSessionRelay.on("relay-message", (message) => {
|
|
1203
|
+
this._handleRemoteRelayMessage(message).catch((error) => {
|
|
1204
|
+
this.emit("client-error", {
|
|
1205
|
+
clientId: "remote-session-relay",
|
|
1206
|
+
error: error.message,
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
this.remoteSessionRelay.on("encrypted-message", ({ from, envelope }) => {
|
|
1211
|
+
this._handleRemoteEncryptedControl(from, envelope).catch((error) => {
|
|
1212
|
+
this.emit("client-error", { clientId: from, error: error.message });
|
|
1213
|
+
});
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async _handleRemoteRelayMessage(message) {
|
|
1218
|
+
if (
|
|
1219
|
+
message?.type !== "message" ||
|
|
1220
|
+
message.payload?.type !== "remote-session.pair"
|
|
1221
|
+
)
|
|
1222
|
+
return;
|
|
1223
|
+
const payload = message.payload;
|
|
1224
|
+
const sessionId = payload.envelope?.sessionId;
|
|
1225
|
+
const crypto = this.remoteSessionCrypto.get(sessionId);
|
|
1226
|
+
const token = this.remoteSessionPairingSecrets.get(sessionId);
|
|
1227
|
+
if (!crypto || !token)
|
|
1228
|
+
throw new Error("Remote Session pairing is unavailable or expired");
|
|
1229
|
+
crypto.pair(payload.mobilePeerId, payload.mobilePublicKey, token);
|
|
1230
|
+
const join = crypto.decrypt(payload.envelope);
|
|
1231
|
+
if (join.type !== "pair.join" || join.token !== token) {
|
|
1232
|
+
throw new Error("Invalid encrypted Remote Session pairing request");
|
|
1233
|
+
}
|
|
1234
|
+
const relayMember = this.remoteSessions.join({
|
|
1235
|
+
sessionId,
|
|
1236
|
+
clientId: payload.mobilePeerId,
|
|
1237
|
+
token: join.token,
|
|
1238
|
+
via: "relay",
|
|
1239
|
+
pushToken: join.pushToken,
|
|
1240
|
+
pushProvider: join.pushProvider,
|
|
1241
|
+
});
|
|
1242
|
+
this.remoteSessionAudit?.record({
|
|
1243
|
+
sessionId,
|
|
1244
|
+
actor: payload.mobilePeerId,
|
|
1245
|
+
action: "device.joined",
|
|
1246
|
+
detail: {
|
|
1247
|
+
via: "relay",
|
|
1248
|
+
hasPush: relayMember?.member?.pushToken ? true : false,
|
|
1249
|
+
},
|
|
1250
|
+
});
|
|
1251
|
+
this.remoteSessionPairingSecrets.delete(sessionId);
|
|
1252
|
+
this.remoteSessionRelay.sendEncrypted(
|
|
1253
|
+
payload.mobilePeerId,
|
|
1254
|
+
crypto.encrypt(payload.mobilePeerId, {
|
|
1255
|
+
type: "pair.accepted",
|
|
1256
|
+
remoteSessionId: sessionId,
|
|
1257
|
+
}),
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
async _handleRemoteEncryptedControl(from, envelope) {
|
|
1262
|
+
const crypto = this.remoteSessionCrypto.get(envelope?.sessionId);
|
|
1263
|
+
if (!crypto) throw new Error("Remote Session crypto context not found");
|
|
1264
|
+
const event = crypto.decrypt(envelope);
|
|
1265
|
+
const virtualWs = {
|
|
1266
|
+
OPEN: 1,
|
|
1267
|
+
readyState: 1,
|
|
1268
|
+
send: (raw) => {
|
|
1269
|
+
const response = JSON.parse(raw);
|
|
1270
|
+
this.remoteSessionRelay.sendEncrypted(
|
|
1271
|
+
from,
|
|
1272
|
+
crypto.encrypt(from, response),
|
|
1273
|
+
);
|
|
1274
|
+
},
|
|
1275
|
+
};
|
|
1276
|
+
await handleRemoteSessionPublish(this, from, virtualWs, {
|
|
1277
|
+
id: `remote-${Date.now()}`,
|
|
1278
|
+
remoteSessionId: envelope.sessionId,
|
|
1279
|
+
event,
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
984
1283
|
/** @private — broadcast a message to all connected, authenticated clients */
|
|
985
1284
|
_broadcast(data) {
|
|
986
1285
|
for (const [, client] of this.clients) {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize runtime events before comparing them with a golden transcript.
|
|
3
|
+
*
|
|
4
|
+
* Runtime envelopes intentionally contain volatile identity and timing fields.
|
|
5
|
+
* Golden files should describe observable behavior, not a particular run, so
|
|
6
|
+
* those fields are removed recursively and platform-specific workspace paths
|
|
7
|
+
* are replaced with a stable token.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const VOLATILE_KEYS = new Set([
|
|
11
|
+
"eventId",
|
|
12
|
+
"id",
|
|
13
|
+
"requestId",
|
|
14
|
+
"sessionId",
|
|
15
|
+
"sequence",
|
|
16
|
+
"timestamp",
|
|
17
|
+
"createdAt",
|
|
18
|
+
"updatedAt",
|
|
19
|
+
"duration",
|
|
20
|
+
"durationMs",
|
|
21
|
+
"toolTelemetryRecord",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function normalizeString(value, workspace) {
|
|
25
|
+
if (!workspace) return value;
|
|
26
|
+
const variants = new Set([
|
|
27
|
+
workspace,
|
|
28
|
+
workspace.replaceAll("\\", "/"),
|
|
29
|
+
workspace.replaceAll("/", "\\"),
|
|
30
|
+
]);
|
|
31
|
+
let normalized = value;
|
|
32
|
+
for (const variant of variants) {
|
|
33
|
+
normalized = normalized.split(variant).join("<WORKSPACE>");
|
|
34
|
+
}
|
|
35
|
+
return normalized.replaceAll("<WORKSPACE>\\", "<WORKSPACE>/");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeValue(value, workspace) {
|
|
39
|
+
if (typeof value === "string") return normalizeString(value, workspace);
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
return value.map((item) => normalizeValue(item, workspace));
|
|
42
|
+
}
|
|
43
|
+
if (!value || typeof value !== "object") return value;
|
|
44
|
+
|
|
45
|
+
return Object.fromEntries(
|
|
46
|
+
Object.entries(value)
|
|
47
|
+
.filter(([key]) => !VOLATILE_KEYS.has(key))
|
|
48
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
49
|
+
.map(([key, item]) => [key, normalizeValue(item, workspace)]),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {Array<object>} events
|
|
55
|
+
* @param {{workspace?: string, ignoredTypes?: string[]}} [options]
|
|
56
|
+
*/
|
|
57
|
+
export function normalizeGoldenTranscript(events, options = {}) {
|
|
58
|
+
if (!Array.isArray(events)) {
|
|
59
|
+
throw new TypeError("Golden transcript input must be an array");
|
|
60
|
+
}
|
|
61
|
+
const ignored = new Set(options.ignoredTypes || ["run-started", "run-ended"]);
|
|
62
|
+
return events
|
|
63
|
+
.filter((event) => !ignored.has(event?.type))
|
|
64
|
+
.map((event) => normalizeValue(event, options.workspace));
|
|
65
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { spawn } from "child_process";
|
|
12
12
|
import { EventEmitter } from "events";
|
|
13
|
+
import { safeJsonParse } from "../lib/safe-json.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Injectable dependencies — overridable from tests.
|
|
@@ -1156,8 +1157,11 @@ export class MCPServerConfig {
|
|
|
1156
1157
|
return {
|
|
1157
1158
|
name: row.name,
|
|
1158
1159
|
command: row.command || null,
|
|
1159
|
-
|
|
1160
|
-
|
|
1160
|
+
// safeJsonParse, not bare JSON.parse: list()/getAutoConnect() map every
|
|
1161
|
+
// row through here, so one corrupt cell must not take down the whole
|
|
1162
|
+
// MCP server list (`|| "[]"` only guards NULL, not a corrupt non-empty string).
|
|
1163
|
+
args: safeJsonParse(row.args, []),
|
|
1164
|
+
env: safeJsonParse(row.env, {}),
|
|
1161
1165
|
autoConnect: row.auto_connect === 1,
|
|
1162
1166
|
url: row.url || null,
|
|
1163
1167
|
transport:
|
|
@@ -251,8 +251,25 @@ export class PromptCompressor {
|
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// Pinned facts — a DETERMINISTIC fact-retention guarantee. A message marked
|
|
255
|
+
// `pinned:true` (or matched by an `options.isPinned` predicate) MUST survive
|
|
256
|
+
// compaction verbatim regardless of any strategy or the non-deterministic LLM
|
|
257
|
+
// summary. Every other retention here is positional (system / recent-N / last
|
|
258
|
+
// user) or summary-dependent, so an important fact that scrolls out of the
|
|
259
|
+
// recent window would otherwise survive only if the summary happens to keep
|
|
260
|
+
// it. We pull pins out BEFORE any strategy runs (so nothing can drop them),
|
|
261
|
+
// then re-insert them as a sticky block right after the system messages.
|
|
262
|
+
const isPinned =
|
|
263
|
+
typeof options.isPinned === "function"
|
|
264
|
+
? options.isPinned
|
|
265
|
+
: (m) => m && (m.pinned === true || m._pin === true);
|
|
266
|
+
const pinned = messages.filter((m) => isPinned(m));
|
|
267
|
+
const working = pinned.length
|
|
268
|
+
? messages.filter((m) => !isPinned(m))
|
|
269
|
+
: messages;
|
|
270
|
+
|
|
254
271
|
const originalTokens = estimateMessagesTokens(messages);
|
|
255
|
-
let result = [...
|
|
272
|
+
let result = [...working];
|
|
256
273
|
const applied = [];
|
|
257
274
|
|
|
258
275
|
if (feature("CONTEXT_SNIP")) {
|
|
@@ -288,6 +305,15 @@ export class PromptCompressor {
|
|
|
288
305
|
}
|
|
289
306
|
}
|
|
290
307
|
|
|
308
|
+
// Re-insert pinned facts verbatim (order preserved) right after the leading
|
|
309
|
+
// system messages, so they end up as a sticky facts block at the top.
|
|
310
|
+
if (pinned.length) {
|
|
311
|
+
const sys = result.filter((m) => m.role === "system");
|
|
312
|
+
const nonSys = result.filter((m) => m.role !== "system");
|
|
313
|
+
result = [...sys, ...pinned, ...nonSys];
|
|
314
|
+
applied.push("pinned");
|
|
315
|
+
}
|
|
316
|
+
|
|
291
317
|
// Tool-pair repair (opt-in) — callers compacting tool-laden histories
|
|
292
318
|
// (e.g. the headless agent loop) pass this so truncation/snip never leaves
|
|
293
319
|
// an orphaned tool result or unanswered tool_call for a strict API.
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RemoteCommandLedger (Phase 5, Remote Control) — application-level idempotency
|
|
3
|
+
* + total ordering + device revocation for commands arriving from remote
|
|
4
|
+
* endpoints (terminal / web / mobile).
|
|
5
|
+
*
|
|
6
|
+
* The transport layer (remote-session-crypto) already rejects a replayed or
|
|
7
|
+
* out-of-order ENVELOPE within one connection — but a reconnect starts a fresh
|
|
8
|
+
* crypto session with a reset sequence baseline, so a command the client
|
|
9
|
+
* re-sends after a dropped ACK would sail through and EXECUTE A SECOND TIME. That
|
|
10
|
+
* is exactly the Phase 5 acceptance this guards: "断网恢复后不会重复执行工具调用".
|
|
11
|
+
*
|
|
12
|
+
* Guarantees:
|
|
13
|
+
* - Idempotency: a command carries a stable `commandId`; the side effect runs
|
|
14
|
+
* AT MOST ONCE. A re-delivered command returns its cached result flagged
|
|
15
|
+
* `replayed` — it never re-executes.
|
|
16
|
+
* - Total order: every applied command gets a monotonic `applyIndex`, so all
|
|
17
|
+
* three endpoints agree on one order; `appliedSince(cursor)` lets a
|
|
18
|
+
* reconnecting endpoint catch up deterministically.
|
|
19
|
+
* - Revocation: a revoked device's commands are rejected (can't read/control).
|
|
20
|
+
*
|
|
21
|
+
* Pure + injectable clock → fully unit-testable without any transport/hardware.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export class RemoteCommandLedger {
|
|
25
|
+
constructor({ now = () => Date.now(), revokedDevices = [] } = {}) {
|
|
26
|
+
this._now = typeof now === "function" ? now : () => now;
|
|
27
|
+
this._applied = new Map(); // commandId → { applyIndex, result, deviceId, ts }
|
|
28
|
+
this._order = []; // applyIndex → commandId (total order)
|
|
29
|
+
this._revoked = new Set(revokedDevices);
|
|
30
|
+
this._perDeviceSeq = new Map(); // deviceId → highest accepted seq
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
isRevoked(deviceId) {
|
|
34
|
+
return this._revoked.has(deviceId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
revokeDevice(deviceId) {
|
|
38
|
+
this._revoked.add(deviceId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Apply a command idempotently. `execute` (async, side-effecting) runs AT MOST
|
|
43
|
+
* ONCE per commandId; a re-delivery returns the cached result without calling
|
|
44
|
+
* it again.
|
|
45
|
+
*
|
|
46
|
+
* @param {{commandId:string, deviceId:string, seq?:number, payload?:any}} command
|
|
47
|
+
* @param {(command)=>Promise<any>|any} execute
|
|
48
|
+
* @returns {Promise<{status:"applied"|"replayed"|"rejected", result?, reason?, applyIndex?}>}
|
|
49
|
+
*/
|
|
50
|
+
async apply(command, execute) {
|
|
51
|
+
if (
|
|
52
|
+
!command ||
|
|
53
|
+
typeof command.commandId !== "string" ||
|
|
54
|
+
!command.commandId
|
|
55
|
+
) {
|
|
56
|
+
return { status: "rejected", reason: "missing commandId" };
|
|
57
|
+
}
|
|
58
|
+
const { commandId, deviceId } = command;
|
|
59
|
+
|
|
60
|
+
// A revoked device can neither read nor control the session.
|
|
61
|
+
if (deviceId != null && this._revoked.has(deviceId)) {
|
|
62
|
+
return { status: "rejected", reason: "device revoked" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Idempotent replay: already applied → return the cached result, DO NOT
|
|
66
|
+
// re-run the side effect. This is the reconnect-safety guarantee.
|
|
67
|
+
const prior = this._applied.get(commandId);
|
|
68
|
+
if (prior) {
|
|
69
|
+
return {
|
|
70
|
+
status: "replayed",
|
|
71
|
+
result: prior.result,
|
|
72
|
+
applyIndex: prior.applyIndex,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Optional per-device monotonic sequence check: a STALE seq (below what we've
|
|
77
|
+
// already accepted from this device) with a NEW commandId is a protocol
|
|
78
|
+
// violation (a genuine reconnect resends the SAME commandId, caught above).
|
|
79
|
+
if (
|
|
80
|
+
deviceId != null &&
|
|
81
|
+
Number.isFinite(command.seq) &&
|
|
82
|
+
this._perDeviceSeq.has(deviceId) &&
|
|
83
|
+
command.seq <= this._perDeviceSeq.get(deviceId)
|
|
84
|
+
) {
|
|
85
|
+
return { status: "rejected", reason: "stale sequence" };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let result;
|
|
89
|
+
try {
|
|
90
|
+
result = await execute(command);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// A failed execution is NOT recorded as applied — the command may be
|
|
93
|
+
// retried (with the same commandId) and will run again. Surfacing the
|
|
94
|
+
// error lets the caller decide; idempotency only protects SUCCESSES.
|
|
95
|
+
return { status: "rejected", reason: `execute failed: ${err.message}` };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const applyIndex = this._order.length;
|
|
99
|
+
this._applied.set(commandId, {
|
|
100
|
+
applyIndex,
|
|
101
|
+
result,
|
|
102
|
+
deviceId,
|
|
103
|
+
ts: this._now(),
|
|
104
|
+
});
|
|
105
|
+
this._order.push(commandId);
|
|
106
|
+
if (deviceId != null && Number.isFinite(command.seq)) {
|
|
107
|
+
this._perDeviceSeq.set(deviceId, command.seq);
|
|
108
|
+
}
|
|
109
|
+
return { status: "applied", result, applyIndex };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The ordered tail of applied commands after `cursor` (an applyIndex), so a
|
|
114
|
+
* reconnecting endpoint can replay missed state in the one agreed order.
|
|
115
|
+
* @returns {Array<{applyIndex, commandId, result, deviceId}>}
|
|
116
|
+
*/
|
|
117
|
+
appliedSince(cursor = -1) {
|
|
118
|
+
const out = [];
|
|
119
|
+
for (let i = cursor + 1; i < this._order.length; i++) {
|
|
120
|
+
const commandId = this._order[i];
|
|
121
|
+
const rec = this._applied.get(commandId);
|
|
122
|
+
out.push({
|
|
123
|
+
applyIndex: i,
|
|
124
|
+
commandId,
|
|
125
|
+
result: rec.result,
|
|
126
|
+
deviceId: rec.deviceId,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Highest assigned applyIndex (−1 when nothing applied). */
|
|
133
|
+
cursor() {
|
|
134
|
+
return this._order.length - 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
appliedCount() {
|
|
138
|
+
return this._order.length;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
snapshot() {
|
|
142
|
+
return {
|
|
143
|
+
order: [...this._order],
|
|
144
|
+
applied: Array.from(this._applied.entries()),
|
|
145
|
+
revoked: Array.from(this._revoked),
|
|
146
|
+
perDeviceSeq: Array.from(this._perDeviceSeq.entries()),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static restore(snap, { now = () => Date.now() } = {}) {
|
|
151
|
+
const l = new RemoteCommandLedger({ now });
|
|
152
|
+
l._order = [...(snap?.order || [])];
|
|
153
|
+
l._applied = new Map(snap?.applied || []);
|
|
154
|
+
l._revoked = new Set(snap?.revoked || []);
|
|
155
|
+
l._perDeviceSeq = new Map(snap?.perDeviceSeq || []);
|
|
156
|
+
return l;
|
|
157
|
+
}
|
|
158
|
+
}
|