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.
- package/package.json +7 -4
- package/scripts/macos-mcp-launcher-build.mjs +317 -0
- package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
- package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
- package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
- package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
- package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
- package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
- package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/cowork.js +418 -41
- package/src/commands/permissions.js +240 -16
- package/src/commands/plugin.js +1293 -56
- package/src/commands/session-location.js +276 -0
- package/src/commands/session.js +2 -0
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +316 -17
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +565 -35
- package/src/gateways/ws/ws-agent-handler.js +12 -0
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +510 -50
- package/src/harness/jsonl-session-store.js +341 -24
- package/src/harness/mcp-client.js +8 -1
- package/src/harness/remote-session-audit.js +2 -0
- package/src/harness/remote-session-crypto.js +929 -21
- package/src/harness/remote-session-registry.js +945 -24
- package/src/harness/session-list-index.js +233 -14
- package/src/lib/agent-authority.js +6 -0
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +248 -0
- package/src/lib/background-agent-keeper-protocol.js +291 -0
- package/src/lib/background-agent-supervisor.js +1357 -85
- package/src/lib/background-interaction-resolver.js +12 -0
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +190 -0
- package/src/lib/cowork-workflow.js +1188 -76
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/dynamic-workflow-facade.js +1736 -0
- package/src/lib/execution-location-contract.js +551 -0
- package/src/lib/execution-location-runtime.js +130 -0
- package/src/lib/ide-context.js +94 -6
- package/src/lib/mcp-stdio-package-materialization.js +166 -14
- package/src/lib/packer/pkg-config-generator.js +2 -0
- package/src/lib/permission-authority.js +77 -0
- package/src/lib/permission-side-effect-center.js +495 -0
- package/src/lib/plugin-runtime/install.js +514 -13
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
- package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
- package/src/lib/plugin-security.js +60 -3
- package/src/lib/process-execution-broker/index.js +1095 -57
- package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
- package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
- package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
- package/src/lib/public-ide-capability-manifest.js +3 -0
- package/src/lib/remote-approval-bridge.js +777 -34
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-authority.js +632 -0
- package/src/lib/remote-membership-coordinator.js +2973 -0
- package/src/lib/remote-membership-host-store.js +1799 -0
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/scoped-permission-store.js +369 -0
- package/src/lib/session-anti-rollback-anchor.js +262 -5
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +4 -0
- package/src/lib/with-file-lock.js +73 -38
- package/src/lib/workflow-definition-contract.js +149 -0
- package/src/repl/agent-repl.js +111 -36
- package/src/repl/chat-repl.js +2 -0
- package/src/repl/clipboard-image.js +735 -16
- package/src/repl/ide-status.js +1 -1
- package/src/repl/prompt-interactions.js +59 -6
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1182 -155
- package/src/runtime/headless-runner.js +52 -30
- package/src/runtime/headless-stream.js +51 -28
- package/src/workers/background-agent-keeper.js +689 -0
- package/src/workers/background-agent-worker.js +365 -13
- package/src/workers/background-turn-bootstrap.js +170 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
- package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
- package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
- package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
- 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 (
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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", {
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
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.
|
|
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 (
|
|
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
|
|
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
|
-
|
|
703
|
-
crypto.encrypt(
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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) {
|