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