chainlesschain 0.163.8 → 0.165.1

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 (240) hide show
  1. package/package.json +7 -4
  2. package/scripts/macos-mcp-launcher-build.mjs +317 -0
  3. package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
  4. package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
  5. package/src/assets/web-panel/.build-hash +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
  10. package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
  42. package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
  47. package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
  67. package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
  68. package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
  91. package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
  106. package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
  107. package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
  132. package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
  136. package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
  137. package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/commands/cowork.js +418 -41
  156. package/src/commands/permissions.js +240 -16
  157. package/src/commands/plugin.js +1293 -56
  158. package/src/commands/session-location.js +276 -0
  159. package/src/commands/session.js +2 -0
  160. package/src/data/changelog.json +28 -2
  161. package/src/gateways/ws/action-protocol.js +316 -17
  162. package/src/gateways/ws/message-dispatcher.js +30 -0
  163. package/src/gateways/ws/remote-session-protocol.js +565 -35
  164. package/src/gateways/ws/ws-agent-handler.js +12 -0
  165. package/src/gateways/ws/ws-approval-gate.js +5 -0
  166. package/src/gateways/ws/ws-server.js +510 -50
  167. package/src/harness/jsonl-session-store.js +341 -24
  168. package/src/harness/mcp-client.js +8 -1
  169. package/src/harness/remote-session-audit.js +2 -0
  170. package/src/harness/remote-session-crypto.js +929 -21
  171. package/src/harness/remote-session-registry.js +945 -24
  172. package/src/harness/session-list-index.js +233 -14
  173. package/src/lib/agent-authority.js +6 -0
  174. package/src/lib/auto-mode-config.js +98 -3
  175. package/src/lib/background-agent-keeper-client.js +248 -0
  176. package/src/lib/background-agent-keeper-protocol.js +291 -0
  177. package/src/lib/background-agent-supervisor.js +1357 -85
  178. package/src/lib/background-interaction-resolver.js +12 -0
  179. package/src/lib/background-session-transport.js +23 -6
  180. package/src/lib/background-turn-bootstrap-protocol.js +190 -0
  181. package/src/lib/cowork-workflow.js +1188 -76
  182. package/src/lib/delivery-production-adapter.js +38 -2
  183. package/src/lib/dynamic-workflow-facade.js +1736 -0
  184. package/src/lib/execution-location-contract.js +551 -0
  185. package/src/lib/execution-location-runtime.js +130 -0
  186. package/src/lib/ide-context.js +94 -6
  187. package/src/lib/mcp-stdio-package-materialization.js +166 -14
  188. package/src/lib/packer/pkg-config-generator.js +2 -0
  189. package/src/lib/permission-authority.js +77 -0
  190. package/src/lib/permission-side-effect-center.js +495 -0
  191. package/src/lib/plugin-runtime/install.js +514 -13
  192. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
  193. package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
  194. package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
  195. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
  196. package/src/lib/plugin-security.js +60 -3
  197. package/src/lib/process-execution-broker/index.js +1095 -57
  198. package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
  199. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
  200. package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
  201. package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
  202. package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
  203. package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
  204. package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
  205. package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
  206. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  207. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  208. package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
  209. package/src/lib/public-ide-capability-manifest.js +3 -0
  210. package/src/lib/remote-approval-bridge.js +777 -34
  211. package/src/lib/remote-control.js +2 -0
  212. package/src/lib/remote-membership-authority.js +632 -0
  213. package/src/lib/remote-membership-coordinator.js +2973 -0
  214. package/src/lib/remote-membership-host-store.js +1799 -0
  215. package/src/lib/runtime-provenance-ledger.js +171 -46
  216. package/src/lib/scoped-permission-store.js +369 -0
  217. package/src/lib/session-anti-rollback-anchor.js +262 -5
  218. package/src/lib/shell-approval.js +169 -6
  219. package/src/lib/sub-agent-context.js +4 -0
  220. package/src/lib/with-file-lock.js +73 -38
  221. package/src/lib/workflow-definition-contract.js +149 -0
  222. package/src/repl/agent-repl.js +111 -36
  223. package/src/repl/chat-repl.js +2 -0
  224. package/src/repl/clipboard-image.js +735 -16
  225. package/src/repl/ide-status.js +1 -1
  226. package/src/repl/prompt-interactions.js +59 -6
  227. package/src/repl/remote-approval.js +29 -7
  228. package/src/runtime/agent-core.js +1182 -155
  229. package/src/runtime/headless-runner.js +52 -30
  230. package/src/runtime/headless-stream.js +51 -28
  231. package/src/workers/background-agent-keeper.js +689 -0
  232. package/src/workers/background-agent-worker.js +365 -13
  233. package/src/workers/background-turn-bootstrap.js +170 -0
  234. package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
  235. package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
  236. package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
  237. package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
  238. package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
  239. package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
  240. package/src/assets/web-panel/assets/index-uWiUs7S_.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)
@@ -129,6 +198,11 @@ export function handleRemoteSessionCreate(server, clientId, ws, message) {
129
198
  agentSessionId: message.sessionId,
130
199
  name: message.name,
131
200
  scopes: message.scopes,
201
+ // A local/headless approval bridge asks the server to persist a
202
+ // principal/session epoch before it exposes pairing credentials. Older
203
+ // or ordinary server-hosted sessions remain on their in-memory path.
204
+ durableMembership: message.requireDurableMembershipAuthority === true,
205
+ hostCredentialPublicKeySpki: message.hostCredentialPublicKeySpki,
132
206
  });
133
207
  audit(server, {
134
208
  sessionId: result.session.sessionId,
@@ -151,14 +225,153 @@ export function handleRemoteSessionCreate(server, clientId, ws, message) {
151
225
  }
152
226
  }
153
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
+
154
363
  export function handleRemoteSessionPairingToken(server, clientId, ws, message) {
155
364
  try {
156
- const { session } = server.remoteSessions.authorize(
365
+ const { session, member } = server.remoteSessions.authorize(
157
366
  message.remoteSessionId,
158
367
  clientId,
159
368
  "observe",
160
369
  );
161
- if (session.hostClientId !== clientId) {
370
+ if (
371
+ session.hostPrincipalId
372
+ ? member.principalId !== session.hostPrincipalId
373
+ : session.hostClientId !== clientId
374
+ ) {
162
375
  throw new Error("Only the host can issue pairing tokens");
163
376
  }
164
377
  const pairing = server.remoteSessions.issuePairingToken(
@@ -190,15 +403,31 @@ export function handleRemoteSessionPairingToken(server, clientId, ws, message) {
190
403
 
191
404
  export function handleRemoteSessionJoin(server, clientId, ws, message) {
192
405
  try {
193
- const result = server.remoteSessions.join({
194
- sessionId: message.remoteSessionId,
195
- clientId,
196
- token: message.token,
197
- pushToken: message.pushToken,
198
- pushProvider: message.pushProvider,
199
- });
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
+ }
200
426
  audit(server, {
201
- 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,
202
431
  actor: clientId,
203
432
  action: "device.joined",
204
433
  detail: {
@@ -216,6 +445,160 @@ export function handleRemoteSessionJoin(server, clientId, ws, message) {
216
445
  }
217
446
  }
218
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
+
219
602
  export function handleRemoteSessionPushRegister(server, clientId, ws, message) {
220
603
  try {
221
604
  // A device may only register its OWN token (clientId is the authenticated
@@ -274,12 +657,16 @@ export function handleRemoteSessionPolicy(server, clientId, ws, message) {
274
657
  export function handleRemoteSessionAudit(server, clientId, ws, message) {
275
658
  try {
276
659
  // Host-only: authorize proves membership, the host check proves ownership.
277
- const { session } = server.remoteSessions.authorize(
660
+ const { session, member } = server.remoteSessions.authorize(
278
661
  message.remoteSessionId,
279
662
  clientId,
280
663
  "observe",
281
664
  );
282
- if (session.hostClientId !== clientId) {
665
+ if (
666
+ session.hostPrincipalId
667
+ ? member.principalId !== session.hostPrincipalId
668
+ : session.hostClientId !== clientId
669
+ ) {
283
670
  throw new Error("Only the host can read the audit log");
284
671
  }
285
672
  const auditLog = server.remoteSessionAudit;
@@ -342,7 +729,7 @@ function notifyRevokedDevice(
342
729
  export function handleRemoteSessionRevoke(server, clientId, ws, message) {
343
730
  try {
344
731
  const targetClientId = message.clientId || message.deviceId;
345
- const { session, member } = server.remoteSessions.revokeMember(
732
+ const { session, member, statement } = server.remoteSessions.revokeMember(
346
733
  message.remoteSessionId,
347
734
  clientId,
348
735
  targetClientId,
@@ -351,7 +738,7 @@ export function handleRemoteSessionRevoke(server, clientId, ws, message) {
351
738
  server,
352
739
  message.remoteSessionId,
353
740
  session.agentSessionId,
354
- member.clientId,
741
+ member.transportClientId || member.clientId,
355
742
  );
356
743
  audit(server, {
357
744
  sessionId: message.remoteSessionId,
@@ -362,6 +749,7 @@ export function handleRemoteSessionRevoke(server, clientId, ws, message) {
362
749
  reply(server, ws, message.id, "remote-session-revoked", {
363
750
  session,
364
751
  revoked: member.clientId,
752
+ ...(statement ? { statement } : {}),
365
753
  devices: server.remoteSessions.listDevices(
366
754
  message.remoteSessionId,
367
755
  clientId,
@@ -377,10 +765,11 @@ export function handleRemoteSessionRevoke(server, clientId, ws, message) {
377
765
 
378
766
  export function handleRemoteSessionClose(server, clientId, ws, message) {
379
767
  try {
380
- const session = server.remoteSessions.close(
768
+ const closed = server.remoteSessions.close(
381
769
  message.remoteSessionId,
382
770
  clientId,
383
771
  );
772
+ const session = closed?.session || closed;
384
773
  server.remoteSessionCrypto.delete(message.remoteSessionId);
385
774
  server.remoteSessionPairingSecrets.delete(message.remoteSessionId);
386
775
  audit(server, {
@@ -389,7 +778,12 @@ export function handleRemoteSessionClose(server, clientId, ws, message) {
389
778
  action: "session.closed",
390
779
  detail: { reason: "host-closed" },
391
780
  });
392
- 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
+ });
393
787
  } catch (error) {
394
788
  reply(server, ws, message.id, "error", {
395
789
  code: "REMOTE_SESSION_CLOSE_ERROR",
@@ -446,18 +840,41 @@ export async function handleRemoteSessionPublish(
446
840
  return;
447
841
  }
448
842
  const requiredScope = CLIENT_EVENT_SCOPES[message.event.type];
449
- const { session } = server.remoteSessions.authorize(
843
+ const { session, member, membership } = server.remoteSessions.authorize(
450
844
  message.remoteSessionId,
451
845
  clientId,
452
846
  requiredScope || "observe",
453
847
  );
454
848
  // Runtime/output events are host-only. Remote clients may publish only the
455
849
  // explicitly scoped control event types above.
456
- if (!requiredScope && session.hostClientId !== clientId) {
850
+ const actorIsHost = session.hostPrincipalId
851
+ ? member.principalId === session.hostPrincipalId
852
+ : session.hostClientId === clientId;
853
+ if (!requiredScope && !actorIsHost) {
457
854
  throw new Error("Only the host can publish runtime events");
458
855
  }
459
856
 
460
- 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) {
461
878
  const controlMessage = {
462
879
  id: message.id,
463
880
  sessionId: session.agentSessionId,
@@ -486,7 +903,7 @@ export async function handleRemoteSessionPublish(
486
903
  origin: ORIGIN.REMOTE,
487
904
  authenticated: true,
488
905
  scopes: ["prompt"],
489
- principalId: clientId,
906
+ principalId: member.principalId || clientId,
490
907
  sessionId: message.remoteSessionId,
491
908
  });
492
909
  }
@@ -506,12 +923,23 @@ export async function handleRemoteSessionPublish(
506
923
  origin: ORIGIN.REMOTE,
507
924
  authenticated: true,
508
925
  scopes: ["approve"],
509
- principalId: clientId,
926
+ principalId:
927
+ membership?.principalId || member.principalId || clientId,
510
928
  sessionId: message.remoteSessionId,
929
+ ...(membership
930
+ ? {
931
+ remoteSessionId: membership.sessionId,
932
+ sessionEpoch: membership.sessionEpoch,
933
+ membershipEpoch: membership.membershipEpoch,
934
+ }
935
+ : {}),
511
936
  };
512
937
  const approved = message.event.answer ?? message.event.approved;
513
938
  if (approved === true || approved === "true" || approved === "yes") {
514
939
  assertCanApprove(approvalEnvelope);
940
+ if (session.hostPrincipalId) {
941
+ requireRemoteApprovalRequest(server, session, message.event);
942
+ }
515
943
  }
516
944
  }
517
945
  // CLIENT-HOSTED sessions (第四阶段 #2): when the agent session does not
@@ -538,6 +966,22 @@ export async function handleRemoteSessionPublish(
538
966
  "Remote session host is not reachable for this control event",
539
967
  );
540
968
  }
969
+ if (message.event.type === "approval.resolve" && !membership) {
970
+ throw new Error(
971
+ "Durable Remote membership authority is required before forwarding an approval",
972
+ );
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
+ );
541
985
  }
542
986
  // Idempotent forward: a control event with a stable commandId runs its
543
987
  // side effect (audit + dispatch into the host agent) AT MOST ONCE, so a
@@ -545,6 +989,41 @@ export async function handleRemoteSessionPublish(
545
989
  // `replayed` ack instead of a second agent turn. No commandId → forwarded
546
990
  // directly, byte-identical to before.
547
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
+ }
548
1027
  if (!serverHosted) {
549
1028
  // Audit the control action with the same taxonomy as server-hosted
550
1029
  // dispatch, then hand the event to the host process.
@@ -558,7 +1037,7 @@ export async function handleRemoteSessionPublish(
558
1037
  : "control.interrupt";
559
1038
  audit(server, {
560
1039
  sessionId: message.remoteSessionId,
561
- actor: clientId,
1040
+ actor: membership?.principalId || member.principalId || clientId,
562
1041
  action: auditAction,
563
1042
  detail:
564
1043
  message.event.type === "prompt"
@@ -589,12 +1068,42 @@ export async function handleRemoteSessionPublish(
589
1068
  // Hand the event to the host process. `message.event` carries any
590
1069
  // echoed approval or interaction binding, so the host gate can
591
1070
  // reject a stale / tampered verdict.
592
- 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, {
593
1092
  type: "remote-session-control",
594
1093
  remoteSessionId: message.remoteSessionId,
595
1094
  agentSessionId: session.agentSessionId,
596
- from: clientId,
1095
+ from: membership?.principalId || member.principalId || clientId,
1096
+ ...(membership
1097
+ ? {
1098
+ membershipAuthority: membership.authorityVersion,
1099
+ sessionEpoch: membership.sessionEpoch,
1100
+ membershipEpoch: membership.membershipEpoch,
1101
+ }
1102
+ : {}),
597
1103
  event: message.event,
1104
+ ...(approvalLease
1105
+ ? { approvalLeaseStatement: approvalLease.statement }
1106
+ : {}),
598
1107
  });
599
1108
  reply(server, ws, message.id, "remote-session-published", {
600
1109
  delivered: 1,
@@ -675,7 +1184,11 @@ export async function handleRemoteSessionPublish(
675
1184
  for (const member of server.remoteSessions.members(
676
1185
  message.remoteSessionId,
677
1186
  )) {
678
- if (member.clientId === clientId) continue;
1187
+ if (
1188
+ member.transportClientId === clientId ||
1189
+ (!member.transportClientId && member.clientId === clientId)
1190
+ )
1191
+ continue;
679
1192
  // Wake a backgrounded device for approval/permission requests — parity
680
1193
  // with the server-hosted mirror path (_mirrorRemoteSessionEvent). A
681
1194
  // client-hosted session publishing `permission.request` must reach a
@@ -687,7 +1200,8 @@ export async function handleRemoteSessionPublish(
687
1200
  ) {
688
1201
  server._dispatchApprovalPush(session, member, message.event);
689
1202
  }
690
- const target = server.clients.get(member.clientId);
1203
+ const transportClientId = member.transportClientId || member.clientId;
1204
+ const target = server.clients.get(transportClientId);
691
1205
  if (!target) {
692
1206
  // Relay-paired mobiles have no local socket — deliver over the E2EE
693
1207
  // relay like the mirror path does; without this, host-published
@@ -698,9 +1212,14 @@ export async function handleRemoteSessionPublish(
698
1212
  );
699
1213
  if (crypto) {
700
1214
  try {
1215
+ server.remoteSessions.authorize(
1216
+ message.remoteSessionId,
1217
+ transportClientId,
1218
+ "observe",
1219
+ );
701
1220
  server.remoteSessionRelay.sendEncrypted(
702
- member.clientId,
703
- crypto.encrypt(member.clientId, {
1221
+ transportClientId,
1222
+ crypto.encrypt(transportClientId, {
704
1223
  type: "remote-session-event",
705
1224
  remoteSessionId: message.remoteSessionId,
706
1225
  agentSessionId: session.agentSessionId,
@@ -715,13 +1234,24 @@ export async function handleRemoteSessionPublish(
715
1234
  }
716
1235
  continue;
717
1236
  }
718
- server._send(target.ws, {
719
- type: "remote-session-event",
720
- remoteSessionId: message.remoteSessionId,
721
- agentSessionId: session.agentSessionId,
722
- event: message.event,
723
- });
724
- 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
+ }
725
1255
  }
726
1256
  reply(server, ws, message.id, "remote-session-published", { delivered });
727
1257
  } catch (error) {