chainlesschain 0.164.0 → 0.165.2

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 (228) hide show
  1. package/README.md +248 -2
  2. package/package.json +7 -4
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
  40. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
  75. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  76. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
  104. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  109. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
  134. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  135. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/cowork.js +559 -62
  154. package/src/commands/plugin.js +819 -64
  155. package/src/commands/session-location.js +318 -7
  156. package/src/constants.js +2 -2
  157. package/src/data/changelog.json +28 -2
  158. package/src/gateways/ws/action-protocol.js +293 -19
  159. package/src/gateways/ws/message-dispatcher.js +30 -0
  160. package/src/gateways/ws/remote-session-protocol.js +542 -35
  161. package/src/gateways/ws/ws-approval-gate.js +5 -0
  162. package/src/gateways/ws/ws-server.js +462 -38
  163. package/src/harness/jsonl-session-store.js +638 -2
  164. package/src/harness/prompt-compressor.js +4 -0
  165. package/src/harness/remote-session-audit.js +1 -0
  166. package/src/harness/remote-session-crypto.js +750 -50
  167. package/src/harness/remote-session-registry.js +795 -27
  168. package/src/lib/auto-mode-config.js +98 -3
  169. package/src/lib/background-agent-keeper-client.js +29 -0
  170. package/src/lib/background-agent-keeper-protocol.js +158 -10
  171. package/src/lib/background-agent-supervisor.js +870 -48
  172. package/src/lib/background-session-transport.js +23 -6
  173. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  174. package/src/lib/cowork-task-runner.js +70 -0
  175. package/src/lib/cowork-workflow.js +887 -62
  176. package/src/lib/delivery-production-adapter.js +38 -2
  177. package/src/lib/doctor-checkup.js +34 -10
  178. package/src/lib/dynamic-workflow-draft.js +535 -0
  179. package/src/lib/dynamic-workflow-facade.js +1105 -17
  180. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  181. package/src/lib/execution-location-contract.js +111 -7
  182. package/src/lib/execution-location-runtime.js +3 -1
  183. package/src/lib/execution-location-target.js +910 -0
  184. package/src/lib/llm-providers.js +1 -0
  185. package/src/lib/mcp-call-ledger-store.js +35 -0
  186. package/src/lib/mcp-call-ledger.js +30 -0
  187. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  188. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  189. package/src/lib/plugin-runtime/install.js +4956 -657
  190. package/src/lib/plugin-runtime/manifest.js +3 -1
  191. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  192. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  193. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  194. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  195. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  196. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  197. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  198. package/src/lib/plugin-runtime/policy.js +21 -6
  199. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  200. package/src/lib/plugin-runtime/remote-source.js +323 -27
  201. package/src/lib/plugin-runtime/scopes.js +317 -41
  202. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  203. package/src/lib/public-ide-capability-manifest.js +1 -1
  204. package/src/lib/remote-approval-bridge.js +745 -108
  205. package/src/lib/remote-control.js +2 -0
  206. package/src/lib/remote-membership-coordinator.js +612 -46
  207. package/src/lib/remote-membership-host-store.js +59 -10
  208. package/src/lib/runtime-provenance-ledger.js +171 -46
  209. package/src/lib/shell-approval.js +169 -6
  210. package/src/lib/sub-agent-context.js +268 -2
  211. package/src/lib/task-model-selector.js +1 -1
  212. package/src/lib/with-file-lock.js +167 -42
  213. package/src/repl/agent-repl.js +70 -29
  214. package/src/repl/remote-approval.js +29 -7
  215. package/src/runtime/agent-core.js +1695 -194
  216. package/src/runtime/headless-runner.js +22 -5
  217. package/src/runtime/headless-stream.js +19 -3
  218. package/src/workers/background-agent-keeper.js +279 -58
  219. package/src/workers/background-agent-worker.js +7 -0
  220. package/src/workers/background-turn-bootstrap.js +29 -2
  221. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
  222. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  223. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
  224. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  225. package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  227. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  228. package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
@@ -16,6 +16,7 @@ import {
16
16
  describeAuthorityChain,
17
17
  } from "../../lib/agent-authority.js";
18
18
  import { hasCompleteInteractionBinding } from "../../lib/interaction-binding.js";
19
+ import { REMOTE_MEMBERSHIP_NOT_ACTIVE_CODE } from "../../lib/remote-membership-coordinator.js";
19
20
 
20
21
  const CLIENT_EVENT_SCOPES = Object.freeze({
21
22
  prompt: "prompt",
@@ -32,6 +33,74 @@ function audit(server, entry) {
32
33
  server.remoteSessionAudit?.record(entry);
33
34
  }
34
35
 
36
+ function approvalRequestKey(remoteSessionId, requestId) {
37
+ return JSON.stringify([String(remoteSessionId), String(requestId)]);
38
+ }
39
+
40
+ function rememberRemoteApprovalRequest(server, session, member, event) {
41
+ const requestId = event?.requestId;
42
+ if (
43
+ typeof requestId !== "string" ||
44
+ !requestId ||
45
+ typeof event.fingerprint !== "string" ||
46
+ !event.fingerprint ||
47
+ typeof event.binding !== "string" ||
48
+ !event.binding ||
49
+ !(
50
+ (Number.isSafeInteger(event.revision) && event.revision > 0) ||
51
+ (typeof event.revision === "string" && /^[1-9]\d*$/.test(event.revision))
52
+ ) ||
53
+ !Number.isSafeInteger(event.notAfter) ||
54
+ event.notAfter <= Date.now()
55
+ ) {
56
+ throw new Error("Complete durable approval request binding is required");
57
+ }
58
+ if (!server._remoteApprovalRequests) {
59
+ server._remoteApprovalRequests = new Map();
60
+ }
61
+ const key = approvalRequestKey(session.sessionId, requestId);
62
+ const prior = server._remoteApprovalRequests.get(key);
63
+ const record = Object.freeze({
64
+ remoteSessionId: session.sessionId,
65
+ agentSessionId: session.agentSessionId,
66
+ hostPrincipalId: member.principalId,
67
+ hostMembershipEpoch: member.membershipEpoch,
68
+ sessionEpoch: session.sessionEpoch,
69
+ requestId,
70
+ fingerprint: event.fingerprint,
71
+ binding: event.binding,
72
+ revision: event.revision,
73
+ expiresAt: event.notAfter,
74
+ });
75
+ if (prior && JSON.stringify(prior) !== JSON.stringify(record)) {
76
+ throw new Error("Remote approval request binding changed");
77
+ }
78
+ server._remoteApprovalRequests.set(key, record);
79
+ return record;
80
+ }
81
+
82
+ function requireRemoteApprovalRequest(server, session, event) {
83
+ const requestId = event?.requestId || event?.approvalId;
84
+ const record = server._remoteApprovalRequests?.get(
85
+ approvalRequestKey(session.sessionId, requestId),
86
+ );
87
+ if (
88
+ !record ||
89
+ record.agentSessionId !== session.agentSessionId ||
90
+ record.sessionEpoch !== session.sessionEpoch ||
91
+ record.requestId !== requestId ||
92
+ record.fingerprint !== event.fingerprint ||
93
+ record.binding !== event.binding ||
94
+ record.revision !== event.revision ||
95
+ record.expiresAt <= Date.now()
96
+ ) {
97
+ throw new Error(
98
+ "Remote approval response does not match a live host request",
99
+ );
100
+ }
101
+ return record;
102
+ }
103
+
35
104
  /**
36
105
  * Apply a remote-client control event (prompt / approval.resolve /
37
106
  * question.answer / interrupt)
@@ -133,6 +202,7 @@ export function handleRemoteSessionCreate(server, clientId, ws, message) {
133
202
  // principal/session epoch before it exposes pairing credentials. Older
134
203
  // or ordinary server-hosted sessions remain on their in-memory path.
135
204
  durableMembership: message.requireDurableMembershipAuthority === true,
205
+ hostCredentialPublicKeySpki: message.hostCredentialPublicKeySpki,
136
206
  });
137
207
  audit(server, {
138
208
  sessionId: result.session.sessionId,
@@ -155,14 +225,153 @@ export function handleRemoteSessionCreate(server, clientId, ws, message) {
155
225
  }
156
226
  }
157
227
 
228
+ export function handleRemoteSessionJoinChallenge(
229
+ server,
230
+ clientId,
231
+ ws,
232
+ message,
233
+ ) {
234
+ try {
235
+ const client = server.clients.get(clientId);
236
+ if (!client?.membershipConnectionNonce) {
237
+ throw new Error("Remote membership connection nonce is unavailable");
238
+ }
239
+ const challenge = server.remoteSessions.issueMemberJoinChallenge({
240
+ sessionId: message.remoteSessionId,
241
+ transportClientId: clientId,
242
+ token: message.token,
243
+ credentialPublicKey: message.credentialPublicKey,
244
+ connectionNonce: client.membershipConnectionNonce,
245
+ capabilities: message.capabilities,
246
+ });
247
+ reply(server, ws, message.id, "remote-session-join-challenge", {
248
+ challenge,
249
+ });
250
+ } catch (error) {
251
+ reply(server, ws, message.id, "error", {
252
+ code: "REMOTE_SESSION_JOIN_CHALLENGE_ERROR",
253
+ message: error.message,
254
+ });
255
+ }
256
+ }
257
+
258
+ export function handleRemoteSessionResumeChallenge(
259
+ server,
260
+ clientId,
261
+ ws,
262
+ message,
263
+ ) {
264
+ try {
265
+ const client = server.clients.get(clientId);
266
+ if (!client?.membershipConnectionNonce) {
267
+ throw new Error("Remote membership connection nonce is unavailable");
268
+ }
269
+ const challenge = server.remoteSessions.issueSessionResumeChallenge({
270
+ sessionId: message.remoteSessionId,
271
+ principalId: message.principalId,
272
+ transportClientId: clientId,
273
+ connectionNonce: client.membershipConnectionNonce,
274
+ });
275
+ reply(server, ws, message.id, "remote-session-resume-challenge", {
276
+ challenge,
277
+ });
278
+ } catch (error) {
279
+ reply(server, ws, message.id, "error", {
280
+ code:
281
+ error?.code === REMOTE_MEMBERSHIP_NOT_ACTIVE_CODE
282
+ ? "REMOTE_SESSION_MEMBERSHIP_NOT_ACTIVE"
283
+ : "REMOTE_SESSION_RESUME_CHALLENGE_ERROR",
284
+ message: error.message,
285
+ });
286
+ }
287
+ }
288
+
289
+ export function handleRemoteSessionResume(server, clientId, ws, message) {
290
+ try {
291
+ const client = server.clients.get(clientId);
292
+ if (!client?.membershipConnectionNonce) {
293
+ throw new Error("Remote membership connection nonce is unavailable");
294
+ }
295
+ const result = server.remoteSessions.completeSessionResume({
296
+ challengeId: message.challengeId,
297
+ transportClientId: clientId,
298
+ connectionNonce: client.membershipConnectionNonce,
299
+ signature: message.signature,
300
+ });
301
+ client.membershipConnectionNonce = result.nextConnectionNonce;
302
+ reply(server, ws, message.id, "remote-session-resumed", result);
303
+ } catch (error) {
304
+ reply(server, ws, message.id, "error", {
305
+ code: "REMOTE_SESSION_RESUME_ERROR",
306
+ message: error.message,
307
+ });
308
+ }
309
+ }
310
+
311
+ export function handleRemoteSessionReenableChallenge(
312
+ server,
313
+ clientId,
314
+ ws,
315
+ message,
316
+ ) {
317
+ try {
318
+ const client = server.clients.get(clientId);
319
+ if (!client?.membershipConnectionNonce) {
320
+ throw new Error("Remote membership connection nonce is unavailable");
321
+ }
322
+ const challenge = server.remoteSessions.issueSessionReenableChallenge({
323
+ sessionId: message.remoteSessionId,
324
+ principalId: message.principalId,
325
+ transportClientId: clientId,
326
+ connectionNonce: client.membershipConnectionNonce,
327
+ newHostCredentialPublicKeySpki:
328
+ message.newHostCredentialPublicKeySpki || null,
329
+ });
330
+ reply(server, ws, message.id, "remote-session-reenable-challenge", {
331
+ challenge,
332
+ });
333
+ } catch (error) {
334
+ reply(server, ws, message.id, "error", {
335
+ code: "REMOTE_SESSION_REENABLE_CHALLENGE_ERROR",
336
+ message: error.message,
337
+ });
338
+ }
339
+ }
340
+
341
+ export function handleRemoteSessionReenable(server, clientId, ws, message) {
342
+ try {
343
+ const client = server.clients.get(clientId);
344
+ if (!client?.membershipConnectionNonce) {
345
+ throw new Error("Remote membership connection nonce is unavailable");
346
+ }
347
+ const result = server.remoteSessions.completeSessionReenable({
348
+ challengeId: message.challengeId,
349
+ transportClientId: clientId,
350
+ connectionNonce: client.membershipConnectionNonce,
351
+ signature: message.signature,
352
+ });
353
+ client.membershipConnectionNonce = result.nextConnectionNonce;
354
+ reply(server, ws, message.id, "remote-session-reenabled", result);
355
+ } catch (error) {
356
+ reply(server, ws, message.id, "error", {
357
+ code: "REMOTE_SESSION_REENABLE_ERROR",
358
+ message: error.message,
359
+ });
360
+ }
361
+ }
362
+
158
363
  export function handleRemoteSessionPairingToken(server, clientId, ws, message) {
159
364
  try {
160
- const { session } = server.remoteSessions.authorize(
365
+ const { session, member } = server.remoteSessions.authorize(
161
366
  message.remoteSessionId,
162
367
  clientId,
163
368
  "observe",
164
369
  );
165
- if (session.hostClientId !== clientId) {
370
+ if (
371
+ session.hostPrincipalId
372
+ ? member.principalId !== session.hostPrincipalId
373
+ : session.hostClientId !== clientId
374
+ ) {
166
375
  throw new Error("Only the host can issue pairing tokens");
167
376
  }
168
377
  const pairing = server.remoteSessions.issuePairingToken(
@@ -194,15 +403,31 @@ export function handleRemoteSessionPairingToken(server, clientId, ws, message) {
194
403
 
195
404
  export function handleRemoteSessionJoin(server, clientId, ws, message) {
196
405
  try {
197
- const result = server.remoteSessions.join({
198
- sessionId: message.remoteSessionId,
199
- clientId,
200
- token: message.token,
201
- pushToken: message.pushToken,
202
- pushProvider: message.pushProvider,
203
- });
406
+ const client = server.clients.get(clientId);
407
+ const result = message.challengeId
408
+ ? server.remoteSessions.completeMemberJoin({
409
+ challengeId: message.challengeId,
410
+ transportClientId: clientId,
411
+ connectionNonce: client?.membershipConnectionNonce,
412
+ signature: message.signature,
413
+ pushToken: message.pushToken,
414
+ pushProvider: message.pushProvider,
415
+ })
416
+ : server.remoteSessions.join({
417
+ sessionId: message.remoteSessionId,
418
+ clientId,
419
+ token: message.token,
420
+ pushToken: message.pushToken,
421
+ pushProvider: message.pushProvider,
422
+ });
423
+ if (message.challengeId && client) {
424
+ client.membershipConnectionNonce = result.nextConnectionNonce;
425
+ }
204
426
  audit(server, {
205
- sessionId: message.remoteSessionId,
427
+ // A challengeId is the authority for a durable join. Its signed result
428
+ // can legitimately identify a different session than an untrusted outer
429
+ // field, so successful attribution must use only the committed result.
430
+ sessionId: result.session.sessionId,
206
431
  actor: clientId,
207
432
  action: "device.joined",
208
433
  detail: {
@@ -220,6 +445,160 @@ export function handleRemoteSessionJoin(server, clientId, ws, message) {
220
445
  }
221
446
  }
222
447
 
448
+ export function handleRemoteSessionLeaseAck(server, clientId, ws, message) {
449
+ try {
450
+ const { session, member, membership } = server.remoteSessions.authorize(
451
+ message.remoteSessionId,
452
+ clientId,
453
+ "observe",
454
+ );
455
+ if (
456
+ !membership ||
457
+ member.principalId !== session.hostPrincipalId ||
458
+ membership.principalId !== session.hostPrincipalId
459
+ ) {
460
+ throw new Error("Only the durable host may ACK an approval lease");
461
+ }
462
+ const result = server
463
+ ._requireRemoteMembershipCoordinator()
464
+ .ackApprovalLease({
465
+ sessionId: session.sessionId,
466
+ leaseId: message.leaseId,
467
+ hostPrincipalId: membership.principalId,
468
+ expectedHostMembershipEpoch: membership.membershipEpoch,
469
+ expectedCreatedGeneration: message.expectedCreatedGeneration,
470
+ hostReceiptDigest: message.hostReceiptDigest,
471
+ });
472
+ reply(server, ws, message.id, "remote-session-lease-acked", result);
473
+ } catch (error) {
474
+ reply(server, ws, message.id, "error", {
475
+ code: "REMOTE_SESSION_LEASE_ACK_ERROR",
476
+ message: error.message,
477
+ });
478
+ }
479
+ }
480
+
481
+ export function handleRemoteSessionLeaseConsume(server, clientId, ws, message) {
482
+ try {
483
+ const { session, member, membership } = server.remoteSessions.authorize(
484
+ message.remoteSessionId,
485
+ clientId,
486
+ "observe",
487
+ );
488
+ if (
489
+ !membership ||
490
+ member.principalId !== session.hostPrincipalId ||
491
+ membership.principalId !== session.hostPrincipalId
492
+ ) {
493
+ throw new Error("Only the durable host may consume an approval lease");
494
+ }
495
+ const result = server
496
+ ._requireRemoteMembershipCoordinator()
497
+ .consumeApprovalLease({
498
+ sessionId: session.sessionId,
499
+ leaseId: message.leaseId,
500
+ hostPrincipalId: membership.principalId,
501
+ expectedHostMembershipEpoch: membership.membershipEpoch,
502
+ expectedAckedGeneration: message.expectedAckedGeneration,
503
+ expectedMembershipEpoch: message.expectedMembershipEpoch,
504
+ requestId: message.requestId,
505
+ fingerprint: message.fingerprint,
506
+ binding: message.binding,
507
+ });
508
+ reply(server, ws, message.id, "remote-session-lease-consumed", result);
509
+ } catch (error) {
510
+ reply(server, ws, message.id, "error", {
511
+ code: "REMOTE_SESSION_LEASE_CONSUME_ERROR",
512
+ message: error.message,
513
+ });
514
+ }
515
+ }
516
+
517
+ export function handleRemoteSessionLeaseCancel(server, clientId, ws, message) {
518
+ try {
519
+ const { session, member, membership } = server.remoteSessions.authorize(
520
+ message.remoteSessionId,
521
+ clientId,
522
+ "observe",
523
+ );
524
+ if (
525
+ !membership ||
526
+ member.principalId !== session.hostPrincipalId ||
527
+ membership.principalId !== session.hostPrincipalId
528
+ ) {
529
+ throw new Error("Only the durable host may cancel an approval lease");
530
+ }
531
+ const result = server
532
+ ._requireRemoteMembershipCoordinator()
533
+ .cancelApprovalLease({
534
+ sessionId: session.sessionId,
535
+ leaseId: message.leaseId,
536
+ hostPrincipalId: membership.principalId,
537
+ expectedHostMembershipEpoch: membership.membershipEpoch,
538
+ reason: message.reason || "host-cancelled",
539
+ });
540
+ reply(server, ws, message.id, "remote-session-lease-cancelled", result);
541
+ } catch (error) {
542
+ reply(server, ws, message.id, "error", {
543
+ code: "REMOTE_SESSION_LEASE_CANCEL_ERROR",
544
+ message: error.message,
545
+ });
546
+ }
547
+ }
548
+
549
+ export function handleRemoteSessionMembershipSnapshot(
550
+ server,
551
+ clientId,
552
+ ws,
553
+ message,
554
+ ) {
555
+ try {
556
+ const { session, member, membership } = server.remoteSessions.authorize(
557
+ message.remoteSessionId,
558
+ clientId,
559
+ "observe",
560
+ );
561
+ if (
562
+ !membership ||
563
+ member.principalId !== session.hostPrincipalId ||
564
+ membership.principalId !== session.hostPrincipalId
565
+ ) {
566
+ throw new Error("Only the durable host may read a membership snapshot");
567
+ }
568
+ const snapshot = server
569
+ ._requireRemoteMembershipCoordinator()
570
+ .snapshotSession(session.sessionId);
571
+ reply(server, ws, message.id, "remote-session-membership-snapshot", {
572
+ ...snapshot,
573
+ });
574
+ } catch (error) {
575
+ reply(server, ws, message.id, "error", {
576
+ code: "REMOTE_SESSION_MEMBERSHIP_SNAPSHOT_ERROR",
577
+ message: error.message,
578
+ });
579
+ }
580
+ }
581
+
582
+ export function handleRemoteSessionTerminal(server, _clientId, ws, message) {
583
+ try {
584
+ const terminal = server.remoteSessions.terminalSnapshot(
585
+ message.remoteSessionId,
586
+ );
587
+ if (
588
+ message.principalId &&
589
+ terminal.session.hostPrincipalId !== message.principalId
590
+ ) {
591
+ throw new Error("Remote membership terminal principal changed");
592
+ }
593
+ reply(server, ws, message.id, "remote-session-terminal", terminal);
594
+ } catch (error) {
595
+ reply(server, ws, message.id, "error", {
596
+ code: "REMOTE_SESSION_TERMINAL_ERROR",
597
+ message: error.message,
598
+ });
599
+ }
600
+ }
601
+
223
602
  export function handleRemoteSessionPushRegister(server, clientId, ws, message) {
224
603
  try {
225
604
  // A device may only register its OWN token (clientId is the authenticated
@@ -278,12 +657,16 @@ export function handleRemoteSessionPolicy(server, clientId, ws, message) {
278
657
  export function handleRemoteSessionAudit(server, clientId, ws, message) {
279
658
  try {
280
659
  // Host-only: authorize proves membership, the host check proves ownership.
281
- const { session } = server.remoteSessions.authorize(
660
+ const { session, member } = server.remoteSessions.authorize(
282
661
  message.remoteSessionId,
283
662
  clientId,
284
663
  "observe",
285
664
  );
286
- if (session.hostClientId !== clientId) {
665
+ if (
666
+ session.hostPrincipalId
667
+ ? member.principalId !== session.hostPrincipalId
668
+ : session.hostClientId !== clientId
669
+ ) {
287
670
  throw new Error("Only the host can read the audit log");
288
671
  }
289
672
  const auditLog = server.remoteSessionAudit;
@@ -346,7 +729,7 @@ function notifyRevokedDevice(
346
729
  export function handleRemoteSessionRevoke(server, clientId, ws, message) {
347
730
  try {
348
731
  const targetClientId = message.clientId || message.deviceId;
349
- const { session, member } = server.remoteSessions.revokeMember(
732
+ const { session, member, statement } = server.remoteSessions.revokeMember(
350
733
  message.remoteSessionId,
351
734
  clientId,
352
735
  targetClientId,
@@ -355,7 +738,7 @@ export function handleRemoteSessionRevoke(server, clientId, ws, message) {
355
738
  server,
356
739
  message.remoteSessionId,
357
740
  session.agentSessionId,
358
- member.clientId,
741
+ member.transportClientId || member.clientId,
359
742
  );
360
743
  audit(server, {
361
744
  sessionId: message.remoteSessionId,
@@ -366,6 +749,7 @@ export function handleRemoteSessionRevoke(server, clientId, ws, message) {
366
749
  reply(server, ws, message.id, "remote-session-revoked", {
367
750
  session,
368
751
  revoked: member.clientId,
752
+ ...(statement ? { statement } : {}),
369
753
  devices: server.remoteSessions.listDevices(
370
754
  message.remoteSessionId,
371
755
  clientId,
@@ -381,10 +765,11 @@ export function handleRemoteSessionRevoke(server, clientId, ws, message) {
381
765
 
382
766
  export function handleRemoteSessionClose(server, clientId, ws, message) {
383
767
  try {
384
- const session = server.remoteSessions.close(
768
+ const closed = server.remoteSessions.close(
385
769
  message.remoteSessionId,
386
770
  clientId,
387
771
  );
772
+ const session = closed?.session || closed;
388
773
  server.remoteSessionCrypto.delete(message.remoteSessionId);
389
774
  server.remoteSessionPairingSecrets.delete(message.remoteSessionId);
390
775
  audit(server, {
@@ -393,7 +778,12 @@ export function handleRemoteSessionClose(server, clientId, ws, message) {
393
778
  action: "session.closed",
394
779
  detail: { reason: "host-closed" },
395
780
  });
396
- reply(server, ws, message.id, "remote-session-closed", { session });
781
+ reply(server, ws, message.id, "remote-session-closed", {
782
+ session,
783
+ ...(closed?.statement ? { statement: closed.statement } : {}),
784
+ ...(closed?.terminal ? { terminal: true } : {}),
785
+ ...(closed?.alreadyClosed ? { alreadyClosed: true } : {}),
786
+ });
397
787
  } catch (error) {
398
788
  reply(server, ws, message.id, "error", {
399
789
  code: "REMOTE_SESSION_CLOSE_ERROR",
@@ -450,18 +840,41 @@ export async function handleRemoteSessionPublish(
450
840
  return;
451
841
  }
452
842
  const requiredScope = CLIENT_EVENT_SCOPES[message.event.type];
453
- const { session, membership } = server.remoteSessions.authorize(
843
+ const { session, member, membership } = server.remoteSessions.authorize(
454
844
  message.remoteSessionId,
455
845
  clientId,
456
846
  requiredScope || "observe",
457
847
  );
458
848
  // Runtime/output events are host-only. Remote clients may publish only the
459
849
  // explicitly scoped control event types above.
460
- if (!requiredScope && session.hostClientId !== clientId) {
850
+ const actorIsHost = session.hostPrincipalId
851
+ ? member.principalId === session.hostPrincipalId
852
+ : session.hostClientId === clientId;
853
+ if (!requiredScope && !actorIsHost) {
461
854
  throw new Error("Only the host can publish runtime events");
462
855
  }
463
856
 
464
- if (requiredScope && session.hostClientId !== clientId) {
857
+ if (
858
+ !requiredScope &&
859
+ session.hostPrincipalId &&
860
+ message.event.type === "permission.request"
861
+ ) {
862
+ if (!membership) {
863
+ throw new Error(
864
+ "Durable coordinator membership is required for a remote approval request",
865
+ );
866
+ }
867
+ rememberRemoteApprovalRequest(server, session, member, message.event);
868
+ } else if (!requiredScope && message.event.type === "permission.resolved") {
869
+ server._remoteApprovalRequests?.delete(
870
+ approvalRequestKey(
871
+ session.sessionId,
872
+ message.event.requestId || message.event.approvalId,
873
+ ),
874
+ );
875
+ }
876
+
877
+ if (requiredScope && !actorIsHost) {
465
878
  const controlMessage = {
466
879
  id: message.id,
467
880
  sessionId: session.agentSessionId,
@@ -490,7 +903,7 @@ export async function handleRemoteSessionPublish(
490
903
  origin: ORIGIN.REMOTE,
491
904
  authenticated: true,
492
905
  scopes: ["prompt"],
493
- principalId: clientId,
906
+ principalId: member.principalId || clientId,
494
907
  sessionId: message.remoteSessionId,
495
908
  });
496
909
  }
@@ -510,7 +923,8 @@ export async function handleRemoteSessionPublish(
510
923
  origin: ORIGIN.REMOTE,
511
924
  authenticated: true,
512
925
  scopes: ["approve"],
513
- principalId: clientId,
926
+ principalId:
927
+ membership?.principalId || member.principalId || clientId,
514
928
  sessionId: message.remoteSessionId,
515
929
  ...(membership
516
930
  ? {
@@ -523,6 +937,9 @@ export async function handleRemoteSessionPublish(
523
937
  const approved = message.event.answer ?? message.event.approved;
524
938
  if (approved === true || approved === "true" || approved === "yes") {
525
939
  assertCanApprove(approvalEnvelope);
940
+ if (session.hostPrincipalId) {
941
+ requireRemoteApprovalRequest(server, session, message.event);
942
+ }
526
943
  }
527
944
  }
528
945
  // CLIENT-HOSTED sessions (第四阶段 #2): when the agent session does not
@@ -554,6 +971,17 @@ export async function handleRemoteSessionPublish(
554
971
  "Durable Remote membership authority is required before forwarding an approval",
555
972
  );
556
973
  }
974
+ } else if (
975
+ session.hostPrincipalId &&
976
+ message.event.type === "approval.resolve" &&
977
+ (message.event.answer === true ||
978
+ message.event.answer === "true" ||
979
+ message.event.answer === "yes" ||
980
+ message.event.approved === true)
981
+ ) {
982
+ throw new Error(
983
+ "Server-hosted remote approval is disabled until its dispatch path consumes a durable lease",
984
+ );
557
985
  }
558
986
  // Idempotent forward: a control event with a stable commandId runs its
559
987
  // side effect (audit + dispatch into the host agent) AT MOST ONCE, so a
@@ -561,6 +989,41 @@ export async function handleRemoteSessionPublish(
561
989
  // `replayed` ack instead of a second agent turn. No commandId → forwarded
562
990
  // directly, byte-identical to before.
563
991
  await applyControlIdempotent(server, clientId, ws, message, async () => {
992
+ let approvalLease = null;
993
+ if (
994
+ session.hostPrincipalId &&
995
+ message.event.type === "approval.resolve" &&
996
+ !serverHosted
997
+ ) {
998
+ const rawDecision = message.event.answer ?? message.event.approved;
999
+ const approved =
1000
+ rawDecision === true ||
1001
+ rawDecision === "true" ||
1002
+ rawDecision === "yes";
1003
+ const request = requireRemoteApprovalRequest(
1004
+ server,
1005
+ session,
1006
+ message.event,
1007
+ );
1008
+ if (approved) {
1009
+ approvalLease = server
1010
+ ._requireRemoteMembershipCoordinator()
1011
+ .createApprovalLease({
1012
+ sessionId: session.sessionId,
1013
+ sessionEpoch: membership.sessionEpoch,
1014
+ principalId: membership.principalId,
1015
+ membershipEpoch: membership.membershipEpoch,
1016
+ hostPrincipalId: request.hostPrincipalId,
1017
+ requestId: request.requestId,
1018
+ fingerprint: request.fingerprint,
1019
+ binding: request.binding,
1020
+ expiresAt: request.expiresAt,
1021
+ });
1022
+ }
1023
+ server._remoteApprovalRequests.delete(
1024
+ approvalRequestKey(session.sessionId, request.requestId),
1025
+ );
1026
+ }
564
1027
  if (!serverHosted) {
565
1028
  // Audit the control action with the same taxonomy as server-hosted
566
1029
  // dispatch, then hand the event to the host process.
@@ -574,7 +1037,7 @@ export async function handleRemoteSessionPublish(
574
1037
  : "control.interrupt";
575
1038
  audit(server, {
576
1039
  sessionId: message.remoteSessionId,
577
- actor: clientId,
1040
+ actor: membership?.principalId || member.principalId || clientId,
578
1041
  action: auditAction,
579
1042
  detail:
580
1043
  message.event.type === "prompt"
@@ -605,11 +1068,31 @@ export async function handleRemoteSessionPublish(
605
1068
  // Hand the event to the host process. `message.event` carries any
606
1069
  // echoed approval or interaction binding, so the host gate can
607
1070
  // reject a stale / tampered verdict.
608
- server._send(hostTarget.ws, {
1071
+ server.remoteSessions.authorize(
1072
+ message.remoteSessionId,
1073
+ clientId,
1074
+ requiredScope || "observe",
1075
+ );
1076
+ const currentSession = server.remoteSessions.requireSession(
1077
+ message.remoteSessionId,
1078
+ );
1079
+ const currentHostTransport = currentSession.hostClientId;
1080
+ server.remoteSessions.authorize(
1081
+ message.remoteSessionId,
1082
+ currentHostTransport,
1083
+ "observe",
1084
+ );
1085
+ const currentHostTarget = server.clients.get(currentHostTransport);
1086
+ if (!currentHostTarget || currentHostTarget.ws !== hostTarget.ws) {
1087
+ throw new Error(
1088
+ "Remote session host transport changed before delivery",
1089
+ );
1090
+ }
1091
+ server._send(currentHostTarget.ws, {
609
1092
  type: "remote-session-control",
610
1093
  remoteSessionId: message.remoteSessionId,
611
1094
  agentSessionId: session.agentSessionId,
612
- from: clientId,
1095
+ from: membership?.principalId || member.principalId || clientId,
613
1096
  ...(membership
614
1097
  ? {
615
1098
  membershipAuthority: membership.authorityVersion,
@@ -618,6 +1101,9 @@ export async function handleRemoteSessionPublish(
618
1101
  }
619
1102
  : {}),
620
1103
  event: message.event,
1104
+ ...(approvalLease
1105
+ ? { approvalLeaseStatement: approvalLease.statement }
1106
+ : {}),
621
1107
  });
622
1108
  reply(server, ws, message.id, "remote-session-published", {
623
1109
  delivered: 1,
@@ -698,7 +1184,11 @@ export async function handleRemoteSessionPublish(
698
1184
  for (const member of server.remoteSessions.members(
699
1185
  message.remoteSessionId,
700
1186
  )) {
701
- if (member.clientId === clientId) continue;
1187
+ if (
1188
+ member.transportClientId === clientId ||
1189
+ (!member.transportClientId && member.clientId === clientId)
1190
+ )
1191
+ continue;
702
1192
  // Wake a backgrounded device for approval/permission requests — parity
703
1193
  // with the server-hosted mirror path (_mirrorRemoteSessionEvent). A
704
1194
  // client-hosted session publishing `permission.request` must reach a
@@ -710,7 +1200,8 @@ export async function handleRemoteSessionPublish(
710
1200
  ) {
711
1201
  server._dispatchApprovalPush(session, member, message.event);
712
1202
  }
713
- const target = server.clients.get(member.clientId);
1203
+ const transportClientId = member.transportClientId || member.clientId;
1204
+ const target = server.clients.get(transportClientId);
714
1205
  if (!target) {
715
1206
  // Relay-paired mobiles have no local socket — deliver over the E2EE
716
1207
  // relay like the mirror path does; without this, host-published
@@ -721,9 +1212,14 @@ export async function handleRemoteSessionPublish(
721
1212
  );
722
1213
  if (crypto) {
723
1214
  try {
1215
+ server.remoteSessions.authorize(
1216
+ message.remoteSessionId,
1217
+ transportClientId,
1218
+ "observe",
1219
+ );
724
1220
  server.remoteSessionRelay.sendEncrypted(
725
- member.clientId,
726
- crypto.encrypt(member.clientId, {
1221
+ transportClientId,
1222
+ crypto.encrypt(transportClientId, {
727
1223
  type: "remote-session-event",
728
1224
  remoteSessionId: message.remoteSessionId,
729
1225
  agentSessionId: session.agentSessionId,
@@ -738,13 +1234,24 @@ export async function handleRemoteSessionPublish(
738
1234
  }
739
1235
  continue;
740
1236
  }
741
- server._send(target.ws, {
742
- type: "remote-session-event",
743
- remoteSessionId: message.remoteSessionId,
744
- agentSessionId: session.agentSessionId,
745
- event: message.event,
746
- });
747
- delivered += 1;
1237
+ try {
1238
+ server.remoteSessions.authorize(
1239
+ message.remoteSessionId,
1240
+ transportClientId,
1241
+ "observe",
1242
+ );
1243
+ const currentTarget = server.clients.get(transportClientId);
1244
+ if (!currentTarget || currentTarget.ws !== target.ws) continue;
1245
+ server._send(currentTarget.ws, {
1246
+ type: "remote-session-event",
1247
+ remoteSessionId: message.remoteSessionId,
1248
+ agentSessionId: session.agentSessionId,
1249
+ event: message.event,
1250
+ });
1251
+ delivered += 1;
1252
+ } catch {
1253
+ // Revoked/closed after enumeration: do not deliver to the old socket.
1254
+ }
748
1255
  }
749
1256
  reply(server, ws, message.id, "remote-session-published", { delivered });
750
1257
  } catch (error) {