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.
Files changed (212) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-Bq1t-B4V.js → AIOps-DwpCCp64.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-BHb3KMc3.js → ActionButton-C9pBYocA.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-DtJrE99O.js → Analytics-BWAkeXxK.js} +2 -2
  7. package/src/assets/web-panel/assets/{AppLayout-HXt309zh.js → AppLayout-CivFGH6B.js} +3 -3
  8. package/src/assets/web-panel/assets/{Audit-B-0384hX.js → Audit-Cm8hRpZm.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BXNwUJ7y.js → Backup-DXdFMsE8.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CYdSBe24.js → BaseInput-uAwSTeql.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-C5hSt-bf.js → Chat-KU8eeJJE.js} +5 -5
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-DCNfbdlx.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-DZSpGJs8.js → Checkbox-C6AMDkjd.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BKfMmpq3.js → Codegen-t2moDxcO.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DgsxR9Gb.js → Col-DCcsRRhN.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B3GhKOp_.js → Community-DtB-8SAC.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Dtk1xHSY.js → Compact-CZm8THYA.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DmkkUDze.js → Compliance-LiQgC7g1.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CB_Tby_i.js → Cowork-CA8SAxht.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-BLQFn3UU.js → Cron-CtRaF1wD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-LCVSqtiL.js → Crosschain-6-br6Hub.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Cv3xor-m.js → DID-Do2EccrL.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DHAEYeUk.js → Dashboard-wrXcbzGe.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-FAp99VHO.js → Dropdown-p3FsT245.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-Cl4enKIT.js → EmailListRenderer-D95A2L8q.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CgbuK_Rv.js → FamilyGuardDashboard-cLuJW2zo.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-C2I5-34o.js → Federation-Brgq_cbN.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-4RNlhf3D.js → FormItemContext-DWLCkNgh.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-DmaKYzyo.js → GenericCardRenderer-BBqUfQd6.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-BMrCOEGV.js → Git-8F090w4I.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BLwFR0Vd.js → Governance-CCogEbxb.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-Cdlx3Y4t.js → Inference-CfLD7v7C.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-CiwGb2h3.js → KnowledgeGraph-DDuHJewd.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-C6A4r33C.js → Logs-CE8KSEVC.js} +2 -2
  35. package/src/assets/web-panel/assets/{Marketplace-DdybH2YZ.js → Marketplace-DmwpZmhT.js} +1 -1
  36. package/src/assets/web-panel/assets/{McpTools-pgwwEzwg.js → McpTools-Bf7AazU8.js} +4 -4
  37. package/src/assets/web-panel/assets/{Memory-BQ31MLPN.js → Memory-D0QU_00S.js} +2 -2
  38. package/src/assets/web-panel/assets/MobileBridge-D3Arxfeg.css +1 -0
  39. package/src/assets/web-panel/assets/MobileBridge-yXS9xdhx.js +1 -0
  40. package/src/assets/web-panel/assets/{MobileProjects-CmnCTggB.js → MobileProjects-BuDUoGUP.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-3K6RfaUB.js → Mtc-BYyHy28p.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-BN9hXvXq.js → MtcAudit-CK0aqpiZ.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-CRxIT2RV.js → Multisig-mNimKYeb.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CdPvh_dA.js → NLProgramming-AioAJ0YA.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-DzQSZ0ps.js → Notes-CVVNCLHE.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DkqRVvfb.js → NotificationSettings-C4ABj-TK.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-ClTA64ji.js → OrderTableRenderer-DIp8Xcbd.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-B-Pndhhn.js → Organization-uozl_gWK.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Dt56UM9b.js → Overflow-BCZOM2hK.js} +1 -1
  50. package/src/assets/web-panel/assets/{OverrideContext-C2r4vyBb.js → OverrideContext-BlgL9440.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-JDGQmED0.js → P2P-CArcaiaj.js} +2 -2
  52. package/src/assets/web-panel/assets/PdhVaultBrowser-Sgq2Srr8.js +7 -0
  53. package/src/assets/web-panel/assets/{Permissions-Dnqe1sny.js → Permissions-BYrQrXnH.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-mTkYbIR-.js → PersonalDataHub-BzHStAbz.js} +2 -2
  55. package/src/assets/web-panel/assets/{Pipeline-BG6J9UZO.js → Pipeline-k7KqxX1M.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BdEydH6w.js → Privacy-IumTUWqx.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-QPxCoUzs.js → ProjectInit-BRyImYTf.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-hU_phubM.js → ProjectSettings-BFIqTBFk.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BBzAEGk4.js → Projects-Dpid9CDg.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-DJ_bIDRG.js → Providers-CyyFnUPs.js} +1 -1
  61. package/src/assets/web-panel/assets/QrScannerModal-C82cRx-X.js +1 -0
  62. package/src/assets/web-panel/assets/{MobileBridge-zJpPuf4E.css → QrScannerModal-NwAHNfiJ.css} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-BNmX7Jgh.js → QuickAsk-DGxDF521.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Bo1I5nAG.js → Recommend-Cns-Am1y.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-R7OqAIlg.js +4 -0
  66. package/src/assets/web-panel/assets/{Reputation-Bc9mfsh7.js → Reputation-D7mgPIYV.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-D12ZZx5m.js → Row-B3lEURyd.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-CXSlmgHl.js → RssFeed-9_UVrpBt.js} +2 -2
  69. package/src/assets/web-panel/assets/{Search-bNXObahl.js → Search-ClZeO80q.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Bt7yBkJc.js → Security-BNNJczEp.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-ECQDydWI.js → Services-C5SjrWUj.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-BcgzLbTM.js → Skeleton-Ci_obQ-g.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-py_xMG8b.js → Skills-DdebN15P.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-CzNvjwaW.js → Sla-OinZP2vi.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-S3zMMqbB.js → SpeechSettings-CxzbNF51.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-BpDfhFv6.js → SyncSettings-D06N_66b.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-CUn36tpM.js → Tasks-BdEdW0AZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-yzCxc3lI.js → Templates-F1ctKmPa.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BTMrYsHv.js → Tenant-CVz1Urot.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-BvAK_Zel.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-BwJ4nL9S.js → TimelineRenderer-Doitzjmf.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-B1OwTaT_.js → Tokens-BpyVRpVA.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-BDxi9fib.js → Trigger-nWhWYTbU.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-B_5hXqzI.js → Trust-DK_G-wLx.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-BpIMO611.js → UkeySign-B5yBUojO.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-Ds-9lxOW.js → VideoEditing-C5D51qAe.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-CNfFp8if.js → Wallet-CLbL9K7v.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-DbCj1-qn.js → WebAuthn-DZUelI3V.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-DjEeb957.js → WorkflowEditor-8RPxt4KW.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-BX-27gI4.js → chat-4N4tOA3d.js} +1 -1
  91. package/src/assets/web-panel/assets/{collapseMotion-mxzzcDsg.js → collapseMotion-DVfhbsdP.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-CfM5FmdA.js → colors-zbbGVrua.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-Cb0Fm64g.js → compact-item-D7iMkbnE.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-D99MZ4gz.js → createContext-CBzPJv-w.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-C2Z5LRgq.js +1 -0
  96. package/src/assets/web-panel/assets/{echarts-jxgIOFUZ.js → echarts-CL0F2SnX.js} +1 -1
  97. package/src/assets/web-panel/assets/{hasIn-CW7wPkfb.js → hasIn-bHdrQSqZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{icons-DP3uiYxy.js → icons-BsDmGpcT.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BcCSEGzu.js → index-308gCGh7.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CXrNkD5j.js → index-B1s0Bz9O.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CxG5S21O.js → index-B7b1hGry.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BoCp5SY7.js → index-BBNr77E1.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Dt-h8idq.js → index-BCr0geV9.js} +1 -1
  104. package/src/assets/web-panel/assets/index-BGp6Yr-Y.js +1 -0
  105. package/src/assets/web-panel/assets/{index-Bf7G5mYl.js → index-BJ4ZGyW0.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-rIKv98lY.js → index-BS4_Mwk6.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-ClSExghA.js → index-BTIjjXry.js} +1 -1
  108. package/src/assets/web-panel/assets/index-BXiw9dQf.js +1 -0
  109. package/src/assets/web-panel/assets/{index-CzwoBX8m.js → index-BZb8F8YG.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CUENEaPU.js → index-BnBOpqv3.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DZjN-U7A.js → index-Bqmx24kh.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-ab4T5cPr.js → index-BsKehmmS.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DDGBasj6.js → index-BtbbdLnf.js} +4 -4
  114. package/src/assets/web-panel/assets/{index-CvFAgMxx.js → index-CPxkoKgA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BqOy4TS5.js → index-CQ6NcfWz.js} +1 -1
  116. package/src/assets/web-panel/assets/{index--Y8IzVi2.js → index-CQfu1U78.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DcRbrdqR.js → index-CahryTX0.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-ChGWrUQv.js → index-CdmMoYN1.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BTlOIaQj.js → index-Ci009ijp.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CNhZanpM.js → index-CiG1IZao.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-kRlIAKNw.js → index-CkxK86vf.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DAd31-6W.js → index-CqdPyWm5.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-kt0S8nXp.js → index-CvRMA9uT.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CxFhtfoo.js → index-D1YDh7Wr.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-7cahuPKG.js → index-D4XKpj6c.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-Cavk4P2D.js → index-DE9j5YgT.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-DUxnWbi6.js → index-DISWAYce.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CPmtR1Wr.js → index-DUyjUkY7.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DFRrP3LL.js → index-DbE5D0WL.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DAIk483F.js → index-DmqlJed-.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-yBiqcb9Z.js → index-DyoNgbXl.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DwhDhfZ8.js → index-QFObYeo1.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-B8tI4Hg_.js → index-QNAG4JtR.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BPXhpO02.js → index-S74FpAIn.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CaPluhcX.js → index-kmh1TxRa.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-3TDpYBKK.js → index-oDKNgCsP.js} +1 -1
  137. package/src/assets/web-panel/assets/{index-D8U1HFcm.js → index-xYCm6W2h.js} +1 -1
  138. package/src/assets/web-panel/assets/{initDefaultProps-mzQV01we.js → initDefaultProps-BD55yNBt.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-DgWAtKps.js → motion-B5Bbe77U.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-Dm9NMjIw.js → move-CTvIqHEH.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-BpuQIoKH.js → mtc-parser-BkNyPQKB.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-C507FdFH.js → omit-BaXJXgHA.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-BL5eykDR.js → pickAttrs-Bw6-YTxH.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-uSE1U6yg.js → placementArrow-BLdbkLqJ.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-BVhSz_U1.js → responsiveObserve-IVYkzntU.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-CWCp5XZc.js → slide-DFMFwwtY.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-B_rS9tlX.js → statusUtils-CURYRtZ1.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-DRo4TNq4.js → styleChecker-Cfz63s1r.js} +1 -1
  149. package/src/assets/web-panel/assets/useFlexGapSupport-COJL0KE3.js +1 -0
  150. package/src/assets/web-panel/assets/{useFs-DhsLsCr_.js → useFs-YLLojQQf.js} +1 -1
  151. package/src/assets/web-panel/assets/{useMergedState-Cl2q1fes.js → useMergedState-Dc9sO2d7.js} +1 -1
  152. package/src/assets/web-panel/assets/{usePersonalDataHub-BXI-FLwS.js → usePersonalDataHub-BF_21qUC.js} +1 -1
  153. package/src/assets/web-panel/assets/{useRefs-DD9q4mOT.js → useRefs-CAeFRzPf.js} +1 -1
  154. package/src/assets/web-panel/assets/{useState-BK4hy8Ma.js → useState-LwExUYDO.js} +1 -1
  155. package/src/assets/web-panel/assets/{vendor-BvqAck49.js → vendor-BMxUs6UX.js} +16 -16
  156. package/src/assets/web-panel/assets/{vnode-CYE0CVkI.js → vnode-9ZpkA7bb.js} +1 -1
  157. package/src/assets/web-panel/assets/{zoom-B2-MtXVF.js → zoom-DhtAfhl8.js} +1 -1
  158. package/src/assets/web-panel/index.html +3 -3
  159. package/src/auth/npm-auth.js +65 -0
  160. package/src/commands/agent.js +92 -0
  161. package/src/commands/agents.js +68 -0
  162. package/src/commands/permissions.js +66 -10
  163. package/src/commands/plugin.js +50 -3
  164. package/src/commands/serve.js +10 -0
  165. package/src/gateways/remote-session-relay.js +160 -0
  166. package/src/gateways/ws/message-dispatcher.js +32 -0
  167. package/src/gateways/ws/remote-session-protocol.js +429 -0
  168. package/src/gateways/ws/ws-server.js +299 -0
  169. package/src/harness/golden-transcript.js +65 -0
  170. package/src/harness/mcp-client.js +6 -2
  171. package/src/harness/remote-session-audit-sink.js +132 -0
  172. package/src/harness/remote-session-audit.js +110 -0
  173. package/src/harness/remote-session-crypto.js +217 -0
  174. package/src/harness/remote-session-push-fcm.js +254 -0
  175. package/src/harness/remote-session-push.js +104 -0
  176. package/src/harness/remote-session-registry.js +420 -0
  177. package/src/lib/agent-sandbox.js +72 -0
  178. package/src/lib/background-agent-supervisor.js +197 -0
  179. package/src/lib/code-review.js +72 -0
  180. package/src/lib/did-manager.js +6 -4
  181. package/src/lib/mcp-oauth.js +23 -6
  182. package/src/lib/plugin-security.js +128 -0
  183. package/src/lib/pr-create.js +108 -0
  184. package/src/lib/publish-workspace.js +107 -0
  185. package/src/lib/resume-session.js +111 -0
  186. package/src/lib/session-manager.js +7 -3
  187. package/src/lib/settings-hooks.cjs +68 -25
  188. package/src/lib/settings-loader.cjs +115 -2
  189. package/src/lib/task-list.js +53 -0
  190. package/src/repl/agent-repl.js +83 -2
  191. package/src/repl/agents-status.js +8 -2
  192. package/src/repl/btw-command.js +56 -0
  193. package/src/repl/chat-repl.js +52 -1
  194. package/src/repl/hooks-status.js +1 -2
  195. package/src/repl/slash-command-registry.js +197 -0
  196. package/src/runtime/agent-core.js +137 -1
  197. package/src/runtime/agent-runtime.js +8 -13
  198. package/src/runtime/coding-agent-contract-shared.cjs +37 -0
  199. package/src/runtime/coding-agent-policy.cjs +14 -0
  200. package/src/runtime/headless-runner.js +26 -6
  201. package/src/runtime/headless-stream.js +25 -6
  202. package/src/runtime/mcp-config.js +46 -2
  203. package/src/runtime/policies/agent-policy.js +3 -0
  204. package/src/workers/background-agent-worker.js +60 -0
  205. package/src/assets/web-panel/assets/ChatBubbleRenderer-BJQ6SG49.js +0 -1
  206. package/src/assets/web-panel/assets/MobileBridge-CnRXuSP5.js +0 -1
  207. package/src/assets/web-panel/assets/PdhVaultBrowser-C56Oi811.js +0 -7
  208. package/src/assets/web-panel/assets/Terminal-BjZry1Jj.js +0 -3
  209. package/src/assets/web-panel/assets/devWarning-CDh1y3xt.js +0 -1
  210. package/src/assets/web-panel/assets/index-ACeVMo7x.js +0 -1
  211. package/src/assets/web-panel/assets/index-gYKDzWA9.js +0 -1
  212. 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
+ }