chainlesschain 0.162.143 → 0.162.148
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 +3 -3
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-Bq1t-B4V.js → AIOps-DwpCCp64.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BHb3KMc3.js → ActionButton-C9pBYocA.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DtJrE99O.js → Analytics-BWAkeXxK.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-HXt309zh.js → AppLayout-CivFGH6B.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-B-0384hX.js → Audit-Cm8hRpZm.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BXNwUJ7y.js → Backup-DXdFMsE8.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CYdSBe24.js → BaseInput-uAwSTeql.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C5hSt-bf.js → Chat-KU8eeJJE.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DZSpGJs8.js → Checkbox-C6AMDkjd.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BKfMmpq3.js → Codegen-t2moDxcO.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DgsxR9Gb.js → Col-DCcsRRhN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B3GhKOp_.js → Community-DtB-8SAC.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dtk1xHSY.js → Compact-CZm8THYA.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DmkkUDze.js → Compliance-LiQgC7g1.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CB_Tby_i.js → Cowork-CA8SAxht.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BLQFn3UU.js → Cron-CtRaF1wD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-LCVSqtiL.js → Crosschain-6-br6Hub.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cv3xor-m.js → DID-Do2EccrL.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DHAEYeUk.js → Dashboard-wrXcbzGe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-FAp99VHO.js → Dropdown-p3FsT245.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Cl4enKIT.js → EmailListRenderer-D95A2L8q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CgbuK_Rv.js → FamilyGuardDashboard-cLuJW2zo.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C2I5-34o.js → Federation-Brgq_cbN.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-4RNlhf3D.js → FormItemContext-DWLCkNgh.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DmaKYzyo.js → GenericCardRenderer-BBqUfQd6.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BMrCOEGV.js → Git-8F090w4I.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BLwFR0Vd.js → Governance-CCogEbxb.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Cdlx3Y4t.js → Inference-CfLD7v7C.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CiwGb2h3.js → KnowledgeGraph-DDuHJewd.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-C6A4r33C.js → Logs-CE8KSEVC.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-DdybH2YZ.js → Marketplace-DmwpZmhT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-pgwwEzwg.js → McpTools-Bf7AazU8.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-BQ31MLPN.js → Memory-D0QU_00S.js} +2 -2
- package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
- package/src/assets/web-panel/assets/MobileBridge-yXS9xdhx.js +1 -0
- package/src/assets/web-panel/assets/{MobileProjects-CmnCTggB.js → MobileProjects-BuDUoGUP.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-3K6RfaUB.js → Mtc-BYyHy28p.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BN9hXvXq.js → MtcAudit-CK0aqpiZ.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-CRxIT2RV.js → Multisig-mNimKYeb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CdPvh_dA.js → NLProgramming-AioAJ0YA.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DzQSZ0ps.js → Notes-CVVNCLHE.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DkqRVvfb.js → NotificationSettings-C4ABj-TK.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-ClTA64ji.js → OrderTableRenderer-DIp8Xcbd.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B-Pndhhn.js → Organization-uozl_gWK.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Dt56UM9b.js → Overflow-BCZOM2hK.js} +1 -1
- package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-JDGQmED0.js → P2P-CArcaiaj.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-Sgq2Srr8.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-Dnqe1sny.js → Permissions-BYrQrXnH.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-mTkYbIR-.js → PersonalDataHub-BzHStAbz.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-BG6J9UZO.js → Pipeline-k7KqxX1M.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BdEydH6w.js → Privacy-IumTUWqx.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-QPxCoUzs.js → ProjectInit-BRyImYTf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-hU_phubM.js → ProjectSettings-BFIqTBFk.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BBzAEGk4.js → Projects-Dpid9CDg.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DJ_bIDRG.js → Providers-CyyFnUPs.js} +1 -1
- package/src/assets/web-panel/assets/QrScannerModal-C82cRx-X.js +1 -0
- package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BNmX7Jgh.js → QuickAsk-DGxDF521.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Bo1I5nAG.js → Recommend-Cns-Am1y.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-R7OqAIlg.js +4 -0
- package/src/assets/web-panel/assets/{Reputation-Bc9mfsh7.js → Reputation-D7mgPIYV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D12ZZx5m.js → Row-B3lEURyd.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CXSlmgHl.js → RssFeed-9_UVrpBt.js} +2 -2
- package/src/assets/web-panel/assets/{Search-bNXObahl.js → Search-ClZeO80q.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bt7yBkJc.js → Security-BNNJczEp.js} +3 -3
- package/src/assets/web-panel/assets/{Services-ECQDydWI.js → Services-C5SjrWUj.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BcgzLbTM.js → Skeleton-Ci_obQ-g.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-py_xMG8b.js → Skills-DdebN15P.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CzNvjwaW.js → Sla-OinZP2vi.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-S3zMMqbB.js → SpeechSettings-CxzbNF51.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BpDfhFv6.js → SyncSettings-D06N_66b.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CUn36tpM.js → Tasks-BdEdW0AZ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-yzCxc3lI.js → Templates-F1ctKmPa.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BTMrYsHv.js → Tenant-CVz1Urot.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-BwJ4nL9S.js → TimelineRenderer-Doitzjmf.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-B1OwTaT_.js → Tokens-BpyVRpVA.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BDxi9fib.js → Trigger-nWhWYTbU.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B_5hXqzI.js → Trust-DK_G-wLx.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BpIMO611.js → UkeySign-B5yBUojO.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Ds-9lxOW.js → VideoEditing-C5D51qAe.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CNfFp8if.js → Wallet-CLbL9K7v.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DbCj1-qn.js → WebAuthn-DZUelI3V.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DjEeb957.js → WorkflowEditor-8RPxt4KW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BX-27gI4.js → chat-4N4tOA3d.js} +1 -1
- package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CfM5FmdA.js → colors-zbbGVrua.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cb0Fm64g.js → compact-item-D7iMkbnE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D99MZ4gz.js → createContext-CBzPJv-w.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +1 -0
- package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
- package/src/assets/web-panel/assets/{hasIn-CW7wPkfb.js → hasIn-bHdrQSqZ.js} +1 -1
- package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcCSEGzu.js → index-308gCGh7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXrNkD5j.js → index-B1s0Bz9O.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxG5S21O.js → index-B7b1hGry.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoCp5SY7.js → index-BBNr77E1.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt-h8idq.js → index-BCr0geV9.js} +1 -1
- package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +1 -0
- package/src/assets/web-panel/assets/{index-Bf7G5mYl.js → index-BJ4ZGyW0.js} +1 -1
- package/src/assets/web-panel/assets/{index-rIKv98lY.js → index-BS4_Mwk6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClSExghA.js → index-BTIjjXry.js} +1 -1
- package/src/assets/web-panel/assets/index-BXiw9dQf.js +1 -0
- package/src/assets/web-panel/assets/{index-CzwoBX8m.js → index-BZb8F8YG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUENEaPU.js → index-BnBOpqv3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZjN-U7A.js → index-Bqmx24kh.js} +1 -1
- package/src/assets/web-panel/assets/{index-ab4T5cPr.js → index-BsKehmmS.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDGBasj6.js → index-BtbbdLnf.js} +4 -4
- package/src/assets/web-panel/assets/{index-CvFAgMxx.js → index-CPxkoKgA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqOy4TS5.js → index-CQ6NcfWz.js} +1 -1
- package/src/assets/web-panel/assets/{index--Y8IzVi2.js → index-CQfu1U78.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcRbrdqR.js → index-CahryTX0.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChGWrUQv.js → index-CdmMoYN1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTlOIaQj.js → index-Ci009ijp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNhZanpM.js → index-CiG1IZao.js} +1 -1
- package/src/assets/web-panel/assets/{index-kRlIAKNw.js → index-CkxK86vf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAd31-6W.js → index-CqdPyWm5.js} +1 -1
- package/src/assets/web-panel/assets/{index-kt0S8nXp.js → index-CvRMA9uT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxFhtfoo.js → index-D1YDh7Wr.js} +1 -1
- package/src/assets/web-panel/assets/{index-7cahuPKG.js → index-D4XKpj6c.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cavk4P2D.js → index-DE9j5YgT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUxnWbi6.js → index-DISWAYce.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPmtR1Wr.js → index-DUyjUkY7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFRrP3LL.js → index-DbE5D0WL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAIk483F.js → index-DmqlJed-.js} +1 -1
- package/src/assets/web-panel/assets/{index-yBiqcb9Z.js → index-DyoNgbXl.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwhDhfZ8.js → index-QFObYeo1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8tI4Hg_.js → index-QNAG4JtR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPXhpO02.js → index-S74FpAIn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaPluhcX.js → index-kmh1TxRa.js} +1 -1
- package/src/assets/web-panel/assets/{index-3TDpYBKK.js → index-oDKNgCsP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8U1HFcm.js → index-xYCm6W2h.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-mzQV01we.js → initDefaultProps-BD55yNBt.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DgWAtKps.js → motion-B5Bbe77U.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dm9NMjIw.js → move-CTvIqHEH.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BpuQIoKH.js → mtc-parser-BkNyPQKB.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C507FdFH.js → omit-BaXJXgHA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BL5eykDR.js → pickAttrs-Bw6-YTxH.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-uSE1U6yg.js → placementArrow-BLdbkLqJ.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BVhSz_U1.js → responsiveObserve-IVYkzntU.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CWCp5XZc.js → slide-DFMFwwtY.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B_rS9tlX.js → statusUtils-CURYRtZ1.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DRo4TNq4.js → styleChecker-Cfz63s1r.js} +1 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-COJL0KE3.js +1 -0
- package/src/assets/web-panel/assets/{useFs-DhsLsCr_.js → useFs-YLLojQQf.js} +1 -1
- package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BXI-FLwS.js → usePersonalDataHub-BF_21qUC.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-CYE0CVkI.js → vnode-9ZpkA7bb.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B2-MtXVF.js → zoom-DhtAfhl8.js} +1 -1
- package/src/assets/web-panel/index.html +3 -3
- package/src/auth/npm-auth.js +65 -0
- package/src/commands/agent.js +92 -0
- package/src/commands/agents.js +68 -0
- package/src/commands/permissions.js +66 -10
- package/src/commands/plugin.js +50 -3
- package/src/commands/serve.js +10 -0
- package/src/gateways/remote-session-relay.js +160 -0
- package/src/gateways/ws/message-dispatcher.js +32 -0
- package/src/gateways/ws/remote-session-protocol.js +429 -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/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-fcm.js +254 -0
- package/src/harness/remote-session-push.js +104 -0
- package/src/harness/remote-session-registry.js +420 -0
- package/src/lib/agent-sandbox.js +72 -0
- package/src/lib/background-agent-supervisor.js +197 -0
- package/src/lib/code-review.js +72 -0
- package/src/lib/did-manager.js +6 -4
- package/src/lib/mcp-oauth.js +23 -6
- package/src/lib/plugin-security.js +128 -0
- package/src/lib/pr-create.js +108 -0
- package/src/lib/publish-workspace.js +107 -0
- package/src/lib/resume-session.js +111 -0
- package/src/lib/session-manager.js +7 -3
- package/src/lib/settings-hooks.cjs +68 -25
- package/src/lib/settings-loader.cjs +115 -2
- package/src/lib/task-list.js +53 -0
- package/src/repl/agent-repl.js +83 -2
- package/src/repl/agents-status.js +8 -2
- package/src/repl/btw-command.js +56 -0
- package/src/repl/chat-repl.js +52 -1
- package/src/repl/hooks-status.js +1 -2
- package/src/repl/slash-command-registry.js +197 -0
- package/src/runtime/agent-core.js +137 -1
- package/src/runtime/agent-runtime.js +8 -13
- package/src/runtime/coding-agent-contract-shared.cjs +37 -0
- package/src/runtime/coding-agent-policy.cjs +14 -0
- package/src/runtime/headless-runner.js +26 -6
- package/src/runtime/headless-stream.js +25 -6
- package/src/runtime/mcp-config.js +46 -2
- package/src/runtime/policies/agent-policy.js +3 -0
- package/src/workers/background-agent-worker.js +60 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BJQ6SG49.js +0 -1
- package/src/assets/web-panel/assets/MobileBridge-CnRXuSP5.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-C56Oi811.js +0 -7
- package/src/assets/web-panel/assets/Terminal-BjZry1Jj.js +0 -3
- package/src/assets/web-panel/assets/devWarning-CDh1y3xt.js +0 -1
- package/src/assets/web-panel/assets/index-ACeVMo7x.js +0 -1
- package/src/assets/web-panel/assets/index-gYKDzWA9.js +0 -1
- package/src/assets/web-panel/assets/useFlexGapSupport-QsPzwpvi.js +0 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { EventEmitter } from "events";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
|
|
4
|
+
export class RemoteSessionRelay extends EventEmitter {
|
|
5
|
+
constructor({
|
|
6
|
+
relayUrl,
|
|
7
|
+
peerId,
|
|
8
|
+
deviceType = "remote-session",
|
|
9
|
+
websocketFactory,
|
|
10
|
+
reconnectBaseMs = 500,
|
|
11
|
+
reconnectMaxMs = 30_000,
|
|
12
|
+
maxReconnectAttempts = Infinity,
|
|
13
|
+
queueLimit = 100,
|
|
14
|
+
setTimer = setTimeout,
|
|
15
|
+
clearTimer = clearTimeout,
|
|
16
|
+
} = {}) {
|
|
17
|
+
super();
|
|
18
|
+
if (!relayUrl || !peerId)
|
|
19
|
+
throw new Error("relayUrl and peerId are required");
|
|
20
|
+
this.relayUrl = relayUrl;
|
|
21
|
+
this.peerId = peerId;
|
|
22
|
+
this.deviceType = deviceType;
|
|
23
|
+
this.websocketFactory = websocketFactory || ((url) => new WebSocket(url));
|
|
24
|
+
this.ws = null;
|
|
25
|
+
this.reconnectBaseMs = reconnectBaseMs;
|
|
26
|
+
this.reconnectMaxMs = reconnectMaxMs;
|
|
27
|
+
this.maxReconnectAttempts = maxReconnectAttempts;
|
|
28
|
+
this.queueLimit = queueLimit;
|
|
29
|
+
this.setTimer = setTimer;
|
|
30
|
+
this.clearTimer = clearTimer;
|
|
31
|
+
this.reconnectAttempts = 0;
|
|
32
|
+
this.reconnectTimer = null;
|
|
33
|
+
this.outbox = [];
|
|
34
|
+
this.closedExplicitly = false;
|
|
35
|
+
this.connectPromise = null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
connect() {
|
|
39
|
+
if (this.ws && this.ws.readyState === this.ws.OPEN)
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
if (this.connectPromise) return this.connectPromise;
|
|
42
|
+
this.closedExplicitly = false;
|
|
43
|
+
this.connectPromise = new Promise((resolve, reject) => {
|
|
44
|
+
const ws = this.websocketFactory(this.relayUrl);
|
|
45
|
+
this.ws = ws;
|
|
46
|
+
const onOpen = () => {
|
|
47
|
+
this._send({
|
|
48
|
+
type: "register",
|
|
49
|
+
peerId: this.peerId,
|
|
50
|
+
deviceType: this.deviceType,
|
|
51
|
+
deviceInfo: { protocol: "remote-session.e2ee.v1" },
|
|
52
|
+
});
|
|
53
|
+
this.reconnectAttempts = 0;
|
|
54
|
+
this.connectPromise = null;
|
|
55
|
+
this._flushOutbox();
|
|
56
|
+
this.emit("connect");
|
|
57
|
+
resolve();
|
|
58
|
+
};
|
|
59
|
+
ws.once("open", onOpen);
|
|
60
|
+
ws.once("error", (error) => {
|
|
61
|
+
this.connectPromise = null;
|
|
62
|
+
reject(error);
|
|
63
|
+
});
|
|
64
|
+
ws.on("message", (raw) => this._handleMessage(raw));
|
|
65
|
+
ws.on("close", () => {
|
|
66
|
+
if (this.ws === ws) this.ws = null;
|
|
67
|
+
this.connectPromise = null;
|
|
68
|
+
this.emit("disconnect");
|
|
69
|
+
this._scheduleReconnect();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return this.connectPromise;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
sendEncrypted(targetPeerId, envelope) {
|
|
76
|
+
const message = {
|
|
77
|
+
type: "message",
|
|
78
|
+
to: targetPeerId,
|
|
79
|
+
payload: { type: "remote-session.encrypted", envelope },
|
|
80
|
+
};
|
|
81
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
82
|
+
if (this.outbox.length >= this.queueLimit) this.outbox.shift();
|
|
83
|
+
this.outbox.push(message);
|
|
84
|
+
this.emit("queued", { size: this.outbox.length });
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
this._send(message);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
close() {
|
|
92
|
+
this.closedExplicitly = true;
|
|
93
|
+
if (this.reconnectTimer) {
|
|
94
|
+
this.clearTimer(this.reconnectTimer);
|
|
95
|
+
this.reconnectTimer = null;
|
|
96
|
+
}
|
|
97
|
+
this.ws?.close();
|
|
98
|
+
this.ws = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_scheduleReconnect() {
|
|
102
|
+
if (
|
|
103
|
+
this.closedExplicitly ||
|
|
104
|
+
this.reconnectTimer ||
|
|
105
|
+
this.reconnectAttempts >= this.maxReconnectAttempts
|
|
106
|
+
) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const delay = Math.min(
|
|
110
|
+
this.reconnectBaseMs * 2 ** this.reconnectAttempts,
|
|
111
|
+
this.reconnectMaxMs,
|
|
112
|
+
);
|
|
113
|
+
this.reconnectAttempts += 1;
|
|
114
|
+
this.emit("reconnecting", { attempt: this.reconnectAttempts, delay });
|
|
115
|
+
this.reconnectTimer = this.setTimer(() => {
|
|
116
|
+
this.reconnectTimer = null;
|
|
117
|
+
this.connect().catch((error) => {
|
|
118
|
+
this.emit("reconnect-error", error);
|
|
119
|
+
this._scheduleReconnect();
|
|
120
|
+
});
|
|
121
|
+
}, delay);
|
|
122
|
+
if (typeof this.reconnectTimer?.unref === "function")
|
|
123
|
+
this.reconnectTimer.unref();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_flushOutbox() {
|
|
127
|
+
const pending = this.outbox.splice(0);
|
|
128
|
+
for (const message of pending) this._send(message);
|
|
129
|
+
if (pending.length) this.emit("flushed", { count: pending.length });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_send(message) {
|
|
133
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
134
|
+
throw new Error("Remote Session relay is not connected");
|
|
135
|
+
}
|
|
136
|
+
this.ws.send(JSON.stringify(message));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_handleMessage(raw) {
|
|
140
|
+
let message;
|
|
141
|
+
try {
|
|
142
|
+
message = JSON.parse(raw.toString("utf8"));
|
|
143
|
+
} catch {
|
|
144
|
+
this.emit("protocol-error", new Error("Invalid signaling relay JSON"));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (message.type === "offline-message") message = message.originalMessage;
|
|
148
|
+
if (
|
|
149
|
+
message?.type === "message" &&
|
|
150
|
+
message.payload?.type === "remote-session.encrypted"
|
|
151
|
+
) {
|
|
152
|
+
this.emit("encrypted-message", {
|
|
153
|
+
from: message.from,
|
|
154
|
+
envelope: message.payload.envelope,
|
|
155
|
+
});
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
this.emit("relay-message", message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -7,6 +7,18 @@ import {
|
|
|
7
7
|
PERSONAL_DATA_HUB_HANDLERS,
|
|
8
8
|
PERSONAL_DATA_HUB_STREAMING_HANDLERS,
|
|
9
9
|
} from "./personal-data-hub-protocol.js";
|
|
10
|
+
import {
|
|
11
|
+
handleRemoteSessionAudit,
|
|
12
|
+
handleRemoteSessionCreate,
|
|
13
|
+
handleRemoteSessionClose,
|
|
14
|
+
handleRemoteSessionDevices,
|
|
15
|
+
handleRemoteSessionJoin,
|
|
16
|
+
handleRemoteSessionPairingToken,
|
|
17
|
+
handleRemoteSessionPolicy,
|
|
18
|
+
handleRemoteSessionPublish,
|
|
19
|
+
handleRemoteSessionPushRegister,
|
|
20
|
+
handleRemoteSessionRevoke,
|
|
21
|
+
} from "./remote-session-protocol.js";
|
|
10
22
|
|
|
11
23
|
export function createWsMessageDispatcher(server) {
|
|
12
24
|
return {
|
|
@@ -54,6 +66,26 @@ export function createWsMessageDispatcher(server) {
|
|
|
54
66
|
server._handleSessionInterrupt(id, ws, message),
|
|
55
67
|
"slash-command": () => server._handleSlashCommand(id, ws, message),
|
|
56
68
|
"session-answer": () => server._handleSessionAnswer(id, ws, message),
|
|
69
|
+
"remote-session-create": () =>
|
|
70
|
+
handleRemoteSessionCreate(server, clientId, ws, message),
|
|
71
|
+
"remote-session-close": () =>
|
|
72
|
+
handleRemoteSessionClose(server, clientId, ws, message),
|
|
73
|
+
"remote-session-pairing-token": () =>
|
|
74
|
+
handleRemoteSessionPairingToken(server, clientId, ws, message),
|
|
75
|
+
"remote-session-join": () =>
|
|
76
|
+
handleRemoteSessionJoin(server, clientId, ws, message),
|
|
77
|
+
"remote-session-devices": () =>
|
|
78
|
+
handleRemoteSessionDevices(server, clientId, ws, message),
|
|
79
|
+
"remote-session-audit": () =>
|
|
80
|
+
handleRemoteSessionAudit(server, clientId, ws, message),
|
|
81
|
+
"remote-session-policy": () =>
|
|
82
|
+
handleRemoteSessionPolicy(server, clientId, ws, message),
|
|
83
|
+
"remote-session-push-register": () =>
|
|
84
|
+
handleRemoteSessionPushRegister(server, clientId, ws, message),
|
|
85
|
+
"remote-session-revoke": () =>
|
|
86
|
+
handleRemoteSessionRevoke(server, clientId, ws, message),
|
|
87
|
+
"remote-session-publish": () =>
|
|
88
|
+
handleRemoteSessionPublish(server, clientId, ws, message),
|
|
57
89
|
"host-tool-result": () => server._handleHostToolResult(id, ws, message),
|
|
58
90
|
orchestrate: () => server._handleOrchestrate(id, ws, message),
|
|
59
91
|
"cowork-task": () => server._handleCoworkTask(id, ws, message),
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleSessionAnswer,
|
|
3
|
+
handleSessionInterrupt,
|
|
4
|
+
handleSessionMessage,
|
|
5
|
+
} from "./session-protocol.js";
|
|
6
|
+
import {
|
|
7
|
+
RemoteSessionCryptoContext,
|
|
8
|
+
createRemotePairingUri,
|
|
9
|
+
} from "../../harness/remote-session-crypto.js";
|
|
10
|
+
|
|
11
|
+
const CLIENT_EVENT_SCOPES = Object.freeze({
|
|
12
|
+
prompt: "prompt",
|
|
13
|
+
"approval.resolve": "approve",
|
|
14
|
+
interrupt: "interrupt",
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function reply(server, ws, id, type, payload = {}) {
|
|
18
|
+
server._send(ws, { id, type, ...payload });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function audit(server, entry) {
|
|
22
|
+
server.remoteSessionAudit?.record(entry);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function attachPairingUri(server, result) {
|
|
26
|
+
const relayUrl = server.remoteSessionRelayUrl;
|
|
27
|
+
const hostPeerId = server.remoteSessionPeerId;
|
|
28
|
+
if (!relayUrl || !hostPeerId) return result;
|
|
29
|
+
server.remoteSessionPairingSecrets.set(
|
|
30
|
+
result.session.sessionId,
|
|
31
|
+
result.pairing.token,
|
|
32
|
+
);
|
|
33
|
+
let crypto = server.remoteSessionCrypto.get(result.session.sessionId);
|
|
34
|
+
if (!crypto) {
|
|
35
|
+
crypto = new RemoteSessionCryptoContext({
|
|
36
|
+
sessionId: result.session.sessionId,
|
|
37
|
+
localPeerId: hostPeerId,
|
|
38
|
+
});
|
|
39
|
+
server.remoteSessionCrypto.set(result.session.sessionId, crypto);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...result,
|
|
43
|
+
pairing: {
|
|
44
|
+
...result.pairing,
|
|
45
|
+
hostPublicKey: crypto.publicKey,
|
|
46
|
+
uri: createRemotePairingUri({
|
|
47
|
+
relayUrl,
|
|
48
|
+
remoteSessionId: result.session.sessionId,
|
|
49
|
+
hostPeerId,
|
|
50
|
+
hostPublicKey: crypto.publicKey,
|
|
51
|
+
pairingToken: result.pairing.token,
|
|
52
|
+
expiresAt: result.pairing.expiresAt,
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function handleRemoteSessionCreate(server, clientId, ws, message) {
|
|
59
|
+
try {
|
|
60
|
+
const result = server.remoteSessions.create({
|
|
61
|
+
hostClientId: clientId,
|
|
62
|
+
agentSessionId: message.sessionId,
|
|
63
|
+
name: message.name,
|
|
64
|
+
scopes: message.scopes,
|
|
65
|
+
});
|
|
66
|
+
audit(server, {
|
|
67
|
+
sessionId: result.session.sessionId,
|
|
68
|
+
actor: clientId,
|
|
69
|
+
action: "session.created",
|
|
70
|
+
detail: { agentSessionId: message.sessionId, name: message.name || null },
|
|
71
|
+
});
|
|
72
|
+
reply(
|
|
73
|
+
server,
|
|
74
|
+
ws,
|
|
75
|
+
message.id,
|
|
76
|
+
"remote-session-created",
|
|
77
|
+
attachPairingUri(server, result),
|
|
78
|
+
);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
reply(server, ws, message.id, "error", {
|
|
81
|
+
code: "REMOTE_SESSION_CREATE_ERROR",
|
|
82
|
+
message: error.message,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function handleRemoteSessionPairingToken(server, clientId, ws, message) {
|
|
88
|
+
try {
|
|
89
|
+
const { session } = server.remoteSessions.authorize(
|
|
90
|
+
message.remoteSessionId,
|
|
91
|
+
clientId,
|
|
92
|
+
"observe",
|
|
93
|
+
);
|
|
94
|
+
if (session.hostClientId !== clientId) {
|
|
95
|
+
throw new Error("Only the host can issue pairing tokens");
|
|
96
|
+
}
|
|
97
|
+
const pairing = server.remoteSessions.issuePairingToken(
|
|
98
|
+
message.remoteSessionId,
|
|
99
|
+
{ scopes: message.scopes },
|
|
100
|
+
);
|
|
101
|
+
const result = attachPairingUri(server, {
|
|
102
|
+
session: {
|
|
103
|
+
sessionId: message.remoteSessionId,
|
|
104
|
+
},
|
|
105
|
+
pairing,
|
|
106
|
+
});
|
|
107
|
+
audit(server, {
|
|
108
|
+
sessionId: message.remoteSessionId,
|
|
109
|
+
actor: clientId,
|
|
110
|
+
action: "pairing-token.issued",
|
|
111
|
+
detail: { scopes: message.scopes || null, expiresAt: pairing.expiresAt },
|
|
112
|
+
});
|
|
113
|
+
reply(server, ws, message.id, "remote-session-pairing-token", {
|
|
114
|
+
pairing: result.pairing,
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
reply(server, ws, message.id, "error", {
|
|
118
|
+
code: "REMOTE_SESSION_PAIRING_ERROR",
|
|
119
|
+
message: error.message,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function handleRemoteSessionJoin(server, clientId, ws, message) {
|
|
125
|
+
try {
|
|
126
|
+
const result = server.remoteSessions.join({
|
|
127
|
+
sessionId: message.remoteSessionId,
|
|
128
|
+
clientId,
|
|
129
|
+
token: message.token,
|
|
130
|
+
pushToken: message.pushToken,
|
|
131
|
+
pushProvider: message.pushProvider,
|
|
132
|
+
});
|
|
133
|
+
audit(server, {
|
|
134
|
+
sessionId: message.remoteSessionId,
|
|
135
|
+
actor: clientId,
|
|
136
|
+
action: "device.joined",
|
|
137
|
+
detail: {
|
|
138
|
+
scopes: result.member.scopes,
|
|
139
|
+
via: "direct",
|
|
140
|
+
hasPush: result.member.pushToken ? true : false,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
reply(server, ws, message.id, "remote-session-joined", result);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
reply(server, ws, message.id, "error", {
|
|
146
|
+
code: "REMOTE_SESSION_JOIN_ERROR",
|
|
147
|
+
message: error.message,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function handleRemoteSessionPushRegister(server, clientId, ws, message) {
|
|
153
|
+
try {
|
|
154
|
+
// A device may only register its OWN token (clientId is the authenticated
|
|
155
|
+
// caller). Storing no push value clears it.
|
|
156
|
+
const result = server.remoteSessions.registerPush(
|
|
157
|
+
message.remoteSessionId,
|
|
158
|
+
clientId,
|
|
159
|
+
{ token: message.pushToken, provider: message.pushProvider },
|
|
160
|
+
);
|
|
161
|
+
audit(server, {
|
|
162
|
+
sessionId: message.remoteSessionId,
|
|
163
|
+
actor: clientId,
|
|
164
|
+
action: "push.registered",
|
|
165
|
+
detail: { hasPush: result.hasPush, provider: result.provider },
|
|
166
|
+
});
|
|
167
|
+
reply(server, ws, message.id, "remote-session-push-registered", result);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
reply(server, ws, message.id, "error", {
|
|
170
|
+
code: "REMOTE_SESSION_PUSH_REGISTER_ERROR",
|
|
171
|
+
message: error.message,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function handleRemoteSessionDevices(server, clientId, ws, message) {
|
|
177
|
+
try {
|
|
178
|
+
const result = server.remoteSessions.listDevices(
|
|
179
|
+
message.remoteSessionId,
|
|
180
|
+
clientId,
|
|
181
|
+
);
|
|
182
|
+
reply(server, ws, message.id, "remote-session-devices", result);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
reply(server, ws, message.id, "error", {
|
|
185
|
+
code: "REMOTE_SESSION_DEVICES_ERROR",
|
|
186
|
+
message: error.message,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function handleRemoteSessionPolicy(server, clientId, ws, message) {
|
|
192
|
+
try {
|
|
193
|
+
// The active org policy is not sensitive — any authenticated client may
|
|
194
|
+
// read it (e.g. to pre-check allowed scopes before requesting a session).
|
|
195
|
+
const policy = server.remoteSessions?.policy;
|
|
196
|
+
reply(server, ws, message.id, "remote-session-policy", {
|
|
197
|
+
policy: policy ? policy.describe() : null,
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
reply(server, ws, message.id, "error", {
|
|
201
|
+
code: "REMOTE_SESSION_POLICY_ERROR",
|
|
202
|
+
message: error.message,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function handleRemoteSessionAudit(server, clientId, ws, message) {
|
|
208
|
+
try {
|
|
209
|
+
// Host-only: authorize proves membership, the host check proves ownership.
|
|
210
|
+
const { session } = server.remoteSessions.authorize(
|
|
211
|
+
message.remoteSessionId,
|
|
212
|
+
clientId,
|
|
213
|
+
"observe",
|
|
214
|
+
);
|
|
215
|
+
if (session.hostClientId !== clientId) {
|
|
216
|
+
throw new Error("Only the host can read the audit log");
|
|
217
|
+
}
|
|
218
|
+
const auditLog = server.remoteSessionAudit;
|
|
219
|
+
const entries = auditLog
|
|
220
|
+
? auditLog.list({
|
|
221
|
+
sessionId: message.remoteSessionId,
|
|
222
|
+
limit: message.limit || 200,
|
|
223
|
+
})
|
|
224
|
+
: [];
|
|
225
|
+
const stats = auditLog
|
|
226
|
+
? auditLog.stats(message.remoteSessionId)
|
|
227
|
+
: { total: 0, byAction: {} };
|
|
228
|
+
reply(server, ws, message.id, "remote-session-audit", {
|
|
229
|
+
remoteSessionId: message.remoteSessionId,
|
|
230
|
+
entries,
|
|
231
|
+
stats,
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
reply(server, ws, message.id, "error", {
|
|
235
|
+
code: "REMOTE_SESSION_AUDIT_ERROR",
|
|
236
|
+
message: error.message,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Tell a revoked device it is no longer paired. Locally connected clients get a
|
|
243
|
+
* plaintext `remote-session-revoked` push; relay-paired mobile devices get an
|
|
244
|
+
* encrypted `session.revoked` control event so they stop auto-reconnecting.
|
|
245
|
+
*/
|
|
246
|
+
function notifyRevokedDevice(
|
|
247
|
+
server,
|
|
248
|
+
remoteSessionId,
|
|
249
|
+
agentSessionId,
|
|
250
|
+
clientId,
|
|
251
|
+
) {
|
|
252
|
+
const target = server.clients.get(clientId);
|
|
253
|
+
if (target) {
|
|
254
|
+
server._send(target.ws, {
|
|
255
|
+
type: "remote-session-revoked",
|
|
256
|
+
remoteSessionId,
|
|
257
|
+
agentSessionId,
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (!server.remoteSessionRelay) return;
|
|
262
|
+
const crypto = server.remoteSessionCrypto.get(remoteSessionId);
|
|
263
|
+
if (!crypto) return;
|
|
264
|
+
try {
|
|
265
|
+
server.remoteSessionRelay.sendEncrypted(
|
|
266
|
+
clientId,
|
|
267
|
+
crypto.encrypt(clientId, { type: "session.revoked", remoteSessionId }),
|
|
268
|
+
);
|
|
269
|
+
} catch {
|
|
270
|
+
// Peer key may already be gone; the registry removal is what actually
|
|
271
|
+
// enforces revocation, so a failed courtesy notice is non-fatal.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function handleRemoteSessionRevoke(server, clientId, ws, message) {
|
|
276
|
+
try {
|
|
277
|
+
const targetClientId = message.clientId || message.deviceId;
|
|
278
|
+
const { session, member } = server.remoteSessions.revokeMember(
|
|
279
|
+
message.remoteSessionId,
|
|
280
|
+
clientId,
|
|
281
|
+
targetClientId,
|
|
282
|
+
);
|
|
283
|
+
notifyRevokedDevice(
|
|
284
|
+
server,
|
|
285
|
+
message.remoteSessionId,
|
|
286
|
+
session.agentSessionId,
|
|
287
|
+
member.clientId,
|
|
288
|
+
);
|
|
289
|
+
audit(server, {
|
|
290
|
+
sessionId: message.remoteSessionId,
|
|
291
|
+
actor: clientId,
|
|
292
|
+
action: "device.revoked",
|
|
293
|
+
detail: { revoked: member.clientId },
|
|
294
|
+
});
|
|
295
|
+
reply(server, ws, message.id, "remote-session-revoked", {
|
|
296
|
+
session,
|
|
297
|
+
revoked: member.clientId,
|
|
298
|
+
devices: server.remoteSessions.listDevices(
|
|
299
|
+
message.remoteSessionId,
|
|
300
|
+
clientId,
|
|
301
|
+
).devices,
|
|
302
|
+
});
|
|
303
|
+
} catch (error) {
|
|
304
|
+
reply(server, ws, message.id, "error", {
|
|
305
|
+
code: "REMOTE_SESSION_REVOKE_ERROR",
|
|
306
|
+
message: error.message,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function handleRemoteSessionClose(server, clientId, ws, message) {
|
|
312
|
+
try {
|
|
313
|
+
const session = server.remoteSessions.close(
|
|
314
|
+
message.remoteSessionId,
|
|
315
|
+
clientId,
|
|
316
|
+
);
|
|
317
|
+
server.remoteSessionCrypto.delete(message.remoteSessionId);
|
|
318
|
+
server.remoteSessionPairingSecrets.delete(message.remoteSessionId);
|
|
319
|
+
audit(server, {
|
|
320
|
+
sessionId: message.remoteSessionId,
|
|
321
|
+
actor: clientId,
|
|
322
|
+
action: "session.closed",
|
|
323
|
+
detail: { reason: "host-closed" },
|
|
324
|
+
});
|
|
325
|
+
reply(server, ws, message.id, "remote-session-closed", { session });
|
|
326
|
+
} catch (error) {
|
|
327
|
+
reply(server, ws, message.id, "error", {
|
|
328
|
+
code: "REMOTE_SESSION_CLOSE_ERROR",
|
|
329
|
+
message: error.message,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export async function handleRemoteSessionPublish(
|
|
335
|
+
server,
|
|
336
|
+
clientId,
|
|
337
|
+
ws,
|
|
338
|
+
message,
|
|
339
|
+
) {
|
|
340
|
+
try {
|
|
341
|
+
if (!message.event || typeof message.event.type !== "string") {
|
|
342
|
+
throw new Error("event.type is required");
|
|
343
|
+
}
|
|
344
|
+
const requiredScope = CLIENT_EVENT_SCOPES[message.event.type];
|
|
345
|
+
const { session } = server.remoteSessions.authorize(
|
|
346
|
+
message.remoteSessionId,
|
|
347
|
+
clientId,
|
|
348
|
+
requiredScope || "observe",
|
|
349
|
+
);
|
|
350
|
+
// Runtime/output events are host-only. Remote clients may publish only the
|
|
351
|
+
// three explicitly scoped control event types above.
|
|
352
|
+
if (!requiredScope && session.hostClientId !== clientId) {
|
|
353
|
+
throw new Error("Only the host can publish runtime events");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (requiredScope && session.hostClientId !== clientId) {
|
|
357
|
+
const controlMessage = {
|
|
358
|
+
id: message.id,
|
|
359
|
+
sessionId: session.agentSessionId,
|
|
360
|
+
};
|
|
361
|
+
if (message.event.type === "prompt") {
|
|
362
|
+
if (
|
|
363
|
+
typeof message.event.content !== "string" ||
|
|
364
|
+
!message.event.content.trim()
|
|
365
|
+
) {
|
|
366
|
+
throw new Error("prompt content is required");
|
|
367
|
+
}
|
|
368
|
+
// Record the shape, not the content — the audit trail must stay useful
|
|
369
|
+
// without hoarding potentially sensitive session prompts.
|
|
370
|
+
audit(server, {
|
|
371
|
+
sessionId: message.remoteSessionId,
|
|
372
|
+
actor: clientId,
|
|
373
|
+
action: "control.prompt",
|
|
374
|
+
detail: { chars: message.event.content.length },
|
|
375
|
+
});
|
|
376
|
+
handleSessionMessage(server, message.id, ws, {
|
|
377
|
+
...controlMessage,
|
|
378
|
+
content: message.event.content,
|
|
379
|
+
});
|
|
380
|
+
} else if (message.event.type === "approval.resolve") {
|
|
381
|
+
audit(server, {
|
|
382
|
+
sessionId: message.remoteSessionId,
|
|
383
|
+
actor: clientId,
|
|
384
|
+
action: "control.approval",
|
|
385
|
+
detail: {
|
|
386
|
+
requestId: message.event.requestId || message.event.approvalId,
|
|
387
|
+
approved: message.event.answer ?? message.event.approved,
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
await handleSessionAnswer(server, message.id, ws, {
|
|
391
|
+
...controlMessage,
|
|
392
|
+
requestId: message.event.requestId || message.event.approvalId,
|
|
393
|
+
answer: message.event.answer ?? message.event.approved,
|
|
394
|
+
});
|
|
395
|
+
} else if (message.event.type === "interrupt") {
|
|
396
|
+
audit(server, {
|
|
397
|
+
sessionId: message.remoteSessionId,
|
|
398
|
+
actor: clientId,
|
|
399
|
+
action: "control.interrupt",
|
|
400
|
+
detail: null,
|
|
401
|
+
});
|
|
402
|
+
await handleSessionInterrupt(server, message.id, ws, controlMessage);
|
|
403
|
+
}
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
let delivered = 0;
|
|
408
|
+
for (const member of server.remoteSessions.members(
|
|
409
|
+
message.remoteSessionId,
|
|
410
|
+
)) {
|
|
411
|
+
if (member.clientId === clientId) continue;
|
|
412
|
+
const target = server.clients.get(member.clientId);
|
|
413
|
+
if (!target) continue;
|
|
414
|
+
server._send(target.ws, {
|
|
415
|
+
type: "remote-session-event",
|
|
416
|
+
remoteSessionId: message.remoteSessionId,
|
|
417
|
+
agentSessionId: session.agentSessionId,
|
|
418
|
+
event: message.event,
|
|
419
|
+
});
|
|
420
|
+
delivered += 1;
|
|
421
|
+
}
|
|
422
|
+
reply(server, ws, message.id, "remote-session-published", { delivered });
|
|
423
|
+
} catch (error) {
|
|
424
|
+
reply(server, ws, message.id, "error", {
|
|
425
|
+
code: "REMOTE_SESSION_PUBLISH_ERROR",
|
|
426
|
+
message: error.message,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|