chainlesschain 0.163.7 → 0.164.0
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 +5 -1
- 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/assets/{AIOps-axqQfwv8.js → AIOps-D2MEn7fI.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DV-Ro0By.js → ActionButton-lCW_4Gf0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bt72WEVu.js → Analytics-Bs7i-nsS.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BtepG55E.js → AppLayout-CK7B6S_j.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-B7KaJ7S_.js → Artifacts-C8ILjpyo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-FNFaUg7A.js → Audit-Bb9cNaWc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DFb6J2wQ.js → BackgroundAgents-sC9ooXIf.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CC7YBhyj.js → Backup-D-DrsplT.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-RkgTNVtj.js → BaseInput-uCNZpwSK.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-hHEmGZy-.js → Chat-CsDK1uE9.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CAP9YhHv.js → ChatBubbleRenderer-OqSDt50w.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-z5wXOQP1.js → Checkbox-JhTXM1-K.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DVaDJnxy.js → Codegen-BbzZLnyD.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CJmaRDR5.js → Col-D4VW4m4j.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DF29_2bI.js → Community-BXrYawxV.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DtkQkBzn.js → Compact-DCihGy1u.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-xoHDxp7I.js → Compliance-DlyZ-lk8.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BUm-R7ht.js → Cowork-DvGZrhbO.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Cgl14ZkB.js → Cron-DUncZi8h.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BNcbmoqh.js → Crosschain-C_hLuUcm.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Bwja0Zo9.js → DID-b6YSUqxr.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Bk5XjEdJ.js → Dashboard-uvM15Cfb.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Ci36Oitq.js → Dropdown-D1-Nd4qY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer--U04ZN4q.js → EmailListRenderer-8ApvL6Zc.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-B2gNPR2s.js → FamilyGuardDashboard-LYJt8811.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C4KghYn7.js → Federation-BbI3HGpv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-QL7x_Q_0.js → FormItemContext-DSoEKuFu.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-F3Ryltwm.js → GenericCardRenderer-B0S9OWd5.js} +1 -1
- package/src/assets/web-panel/assets/{Git-2HassK5k.js → Git-DIs9Zhf3.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DoBIp7oe.js → Governance-jzZXrxcl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference--W01QhZ-.js → Inference-JUPjf2ZI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DmvwqGpT.js → KnowledgeGraph-DxnfJvlP.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BBJ4yAel.js → Logs-iXiz8BEE.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BLCZMzhJ.js → MarkdownRenderer-GmB6DOr6.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-D5lNngSI.js → Marketplace-Y3Xch312.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CkEVOMKH.js → McpTools-D7kGGbO7.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-B1Ow1xVE.js → Memory-Do_hVxzh.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-SgYbUfb6.js → MobileBridge-CtrR0Tas.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-DcTXSSma.js → Mtc-qo5QCTGd.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Dt3WG4oj.js → MtcAudit-cq5mnjYj.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-B9B38UZx.js → Multisig-Bq0MAQHw.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-B0S4GGvG.js → NLProgramming-DdWE6Vi2.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DZ5U58Ng.js → Notes-BxMBFcIp.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-PjL5ZW34.js → NotificationSettings-RTrAcbUe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DrAX2ESt.js → OrderTableRenderer-DJbpGqzT.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Cv1GYs-c.js → Organization-SQzIPdqC.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Bbkp_PL3.js → Overflow-02yECh22.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BD8QUgoZ.js → P2P-BNg4pNCM.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BO-2PeaB.js → PdhVaultBrowser-BxPJZsD3.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D_OPVa02.js → Permissions-CLnDnPAz.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-mh-pEmaB.js → PersonalDataHub-C5m0AjGf.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-6JC2EDdb.js → Pipeline-VwOf5Dhv.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CUshzUAG.js → Privacy-CC0W2hZd.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Cs2Aalri.js → ProjectInit-Dz893Bob.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-1ak49tcj.js → ProjectSettings-CxQkvl0k.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CXXxvafH.js → Providers-DeB9IkxK.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-PTcDsF58.js → QrScannerModal-OCeoaswn.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CDwucrJw.js → QuickAsk-DenotaAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJlLVRXR.js → Recommend-Cies_jjI.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DlN-ETsO.js → RemoteSession-Dxzl5n2T.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DI23LR6m.js → Reputation-CW0mvxgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Cl7UQMrM.js → Row-SEd32qZU.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CexHNfh8.js → RssFeed-BSwnJ3Lm.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BneBWvRN.js → Search-xe2m8bkC.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DX7Phgrp.js → Security-Mq2F9Nro.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BWXO8Mte.js → Services-BKGw2V3P.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DCyOjKPJ.js → Skeleton-DW95-Rxf.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CXqERsTB.js → Skills-CHgChCv_.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-bhbrbz8E.js → Sla-tKh6k3R7.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-3IPU0HBZ.js → SpeechSettings-D6UL3dA5.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DBNTQLbM.js → SyncSettings-D0Fw8fVL.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-quP0bkN-.js → Tasks-20x1GB3D.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BouWmgho.js → Templates-DJ-lts-a.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-qv0D9s7z.js → Tenant-BFdGpbs7.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BxvbE8BB.js → Terminal-Xpx61vHL.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D-IeLnJ2.js → TimelineRenderer-C6UIvyZQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CFMKKyVG.js → Tokens-Dellq08f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-L8as3UuJ.js → Trigger-VKw8d0J-.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-MxmA4VZG.js → Trust-DNftdKT5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C0VvJYRi.js → UkeySign-DX9VAKJI.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Bf5QZj8N.js → VideoEditing-B2h3jO8q.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BIJ9riRz.js → Wallet-CjGkof7f.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-B8p9Mhkw.js → WebAuthn-2Zfcu55P.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BVsuaPen.js → WorkflowEditor-B9eZ8UxZ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BfkjoKpV.js → chat-CQNfD-YO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DeAlsWK8.js → colors-D4usyFT2.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cs97bz5j.js → compact-item-s4TpjvjX.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BqbG60LH.js → createContext-BRCSvYxj.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-i_RY0b1X.js → hasIn-AdkUbkV-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOVkf1K3.js → index--00U6vXZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-3tEpTc.js → index-B2V-eKfK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2B4VPyP.js → index-B4JJCfQ2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkBi5TmC.js → index-BIoG5yYf.js} +1 -1
- package/src/assets/web-panel/assets/{index-79V_zv1s.js → index-BMcimU-2.js} +1 -1
- package/src/assets/web-panel/assets/{index-U4f5izHz.js → index-BPhkFZ1I.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiSkx4UB.js → index-BQ3DXBtD.js} +3 -3
- package/src/assets/web-panel/assets/{index-dIHr4B_u.js → index-Bad4VNvz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIG8fQ2_.js → index-BgiXR_-_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqBgBpgp.js → index-BtobShro.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZxO_-E8a.js → index-Bw2MEnUk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUUrv141.js → index-C1mJtE6q.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bm7lLR4c.js → index-C484EJiS.js} +1 -1
- package/src/assets/web-panel/assets/{index-COdGA-8n.js → index-C5KAQFPQ.js} +1 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +1 -0
- package/src/assets/web-panel/assets/{index-CED2PL_y.js → index-C7Y8Aluc.js} +1 -1
- package/src/assets/web-panel/assets/{index-YXgLng0K.js → index-CCsM_Ybx.js} +1 -1
- package/src/assets/web-panel/assets/{index-0-agYlWL.js → index-CJCFe2Qf.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5UkyA3c.js → index-CJVEOa4i.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1CbOH1i.js → index-CJdQrZth.js} +1 -1
- package/src/assets/web-panel/assets/{index-s1opAL-M.js → index-CKC1t3Ic.js} +1 -1
- package/src/assets/web-panel/assets/{index-yObj1O4n.js → index-CQhAVU5t.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgsqdnRV.js → index-CYdoQsa1.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dk7-v4hP.js → index-Cduu2Hhr.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-JVTrpr.js → index-CkwiSLhU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeWoNHau.js → index-Cvyt-zhm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CE2VtTlp.js → index-D2GjI2oe.js} +1 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +1 -0
- package/src/assets/web-panel/assets/{index-DmbfhJUP.js → index-DARsAyUp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BESLFt4q.js → index-DVKfQ0Ux.js} +1 -1
- package/src/assets/web-panel/assets/{index-PT_5sBMA.js → index-DZT36ZYq.js} +1 -1
- package/src/assets/web-panel/assets/{index-gyFpvL7l.js → index-D_XBHopw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYUVuwKt.js → index-DdCB70SS.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcUXAB2s.js → index-DzRjHxEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dk2qouAz.js → index-MMN9V6JF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9evtvg2.js → index-RfMr79MW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BuLQobzW.js → index-_IRVsV9X.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf3WCLmy.js → index-nXyk8GZ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CG3PRyAL.js → index-s_buFMEG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C9h9yKL3.js → initDefaultProps-Blmpk7HL.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BvmLsPdu.js → motion-DPBJMv1n.js} +1 -1
- package/src/assets/web-panel/assets/{move-CoL4ekU1.js → move-ButdzCM7.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C4pMy6xV.js → mtc-parser-cyRC_QCe.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DPuJfNxx.js → omit-1epSV7hz.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D0KEeaK2.js → pickAttrs-DuPY8xJs.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BpkyINxt.js → placementArrow-6XZqarhe.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DQI_wOq8.js → responsiveObserve-B40pXBWM.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DYfdDiRg.js → slide-jbZ9Wu4n.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B5dtvb0D.js → statusUtils-BKQQcVy4.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CF310mSj.js → styleChecker-NiKfWjUT.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D1Qp4BLp.js → useFlexGapSupport-bQUCvPeb.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C7_iyvJK.js → useFs-CUGji7ph.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DZUt_fIm.js → usePersonalDataHub-4veyN2N2.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D3dsi0lZ.js → vnode-1-nLug-Z.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-WdbOlvdQ.js → zoom-dhCeRDMo.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +6 -6
- package/src/commands/automation.js +103 -1
- package/src/commands/cowork.js +174 -19
- 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/commands/team-merge-review.js +937 -0
- package/src/commands/team.js +2 -0
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/action-protocol.js +35 -10
- package/src/gateways/ws/remote-session-protocol.js +24 -1
- package/src/gateways/ws/ws-agent-handler.js +12 -0
- package/src/gateways/ws/ws-server.js +48 -12
- package/src/harness/jsonl-session-store.js +341 -24
- package/src/harness/mcp-client.js +8 -1
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +210 -2
- package/src/harness/remote-session-registry.js +159 -6
- package/src/harness/session-list-index.js +233 -14
- package/src/lib/agent-authority.js +6 -0
- package/src/lib/agent-schedule-store.js +139 -28
- package/src/lib/agent-team/team-merge-review-store.js +765 -0
- package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
- package/src/lib/agent-team/team-merge-review.js +1847 -0
- package/src/lib/automation-center-incidents.js +314 -0
- package/src/lib/automation-center-runtime.js +448 -0
- package/src/lib/automation-center.js +182 -38
- package/src/lib/automation-engine.js +41 -0
- package/src/lib/automation-execution-authority.js +241 -12
- package/src/lib/automation-execution-incident.js +737 -0
- package/src/lib/background-agent-keeper-client.js +248 -0
- package/src/lib/background-agent-keeper-protocol.js +147 -0
- package/src/lib/background-agent-supervisor.js +520 -40
- package/src/lib/background-interaction-resolver.js +12 -0
- package/src/lib/background-turn-bootstrap-protocol.js +126 -0
- package/src/lib/cowork-cron.js +85 -14
- package/src/lib/cowork-workflow.js +336 -16
- package/src/lib/dynamic-workflow-facade.js +648 -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 +123 -17
- package/src/lib/remote-membership-authority.js +632 -0
- package/src/lib/remote-membership-coordinator.js +2409 -0
- package/src/lib/remote-membership-host-store.js +1750 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +17 -7
- package/src/lib/scheduler-kernel/automation-adapter.js +105 -11
- package/src/lib/scheduler-kernel/automation-event-adapter.js +87 -11
- package/src/lib/scheduler-kernel/contract.js +91 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +12 -1
- package/src/lib/scheduler-kernel/loop-adapter.js +7 -1
- package/src/lib/scheduler-kernel/routine-adapter.js +7 -1
- package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
- package/src/lib/scheduler-kernel/runtime.js +176 -3
- package/src/lib/scheduler-kernel/store.js +1253 -45
- package/src/lib/scoped-permission-store.js +369 -0
- package/src/lib/session-anti-rollback-anchor.js +262 -5
- package/src/lib/sub-agent-context.js +4 -0
- package/src/lib/workflow-definition-contract.js +149 -0
- package/src/repl/agent-repl.js +41 -7
- 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/runtime/agent-core.js +93 -3
- package/src/runtime/headless-runner.js +30 -25
- package/src/runtime/headless-stream.js +32 -25
- package/src/runtime/mcp-config.js +81 -10
- package/src/workers/background-agent-keeper.js +498 -0
- package/src/workers/background-agent-worker.js +358 -13
- package/src/workers/background-turn-bootstrap.js +143 -0
- package/src/assets/web-panel/assets/MobileProjects-BWdOvQAj.js +0 -1
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +0 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +0 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +0 -1
|
@@ -12,6 +12,15 @@ import {
|
|
|
12
12
|
|
|
13
13
|
const PROTOCOL = "chainlesschain.remote-session.e2ee.v1";
|
|
14
14
|
const PAIRING_SCHEME = "chainlesschain://remote-session/pair#";
|
|
15
|
+
const RELAY_POSSESSION_SCHEMA =
|
|
16
|
+
"chainlesschain.remote-session.relay-possession-capability/v1";
|
|
17
|
+
const relayPossessionCapabilities = new WeakMap();
|
|
18
|
+
const RELAY_AUTHORITY_SCOPES = new Set([
|
|
19
|
+
"observe",
|
|
20
|
+
"prompt",
|
|
21
|
+
"approve",
|
|
22
|
+
"interrupt",
|
|
23
|
+
]);
|
|
15
24
|
|
|
16
25
|
function b64(value) {
|
|
17
26
|
return Buffer.from(value).toString("base64url");
|
|
@@ -34,6 +43,91 @@ function importPublicKey(value) {
|
|
|
34
43
|
return createPublicKey({ key: der, type: "spki", format: "der" });
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
function canonicalPublicKey(value) {
|
|
47
|
+
const key = importPublicKey(value);
|
|
48
|
+
if (key.asymmetricKeyType !== "x25519") {
|
|
49
|
+
throw new Error("Remote Session peer key must be X25519");
|
|
50
|
+
}
|
|
51
|
+
return exportPublicKey(key);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function digest(domain, value) {
|
|
55
|
+
return createHash("sha256")
|
|
56
|
+
.update(domain, "utf8")
|
|
57
|
+
.update("\0", "utf8")
|
|
58
|
+
.update(value)
|
|
59
|
+
.digest("hex");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function remoteSessionPairingTokenDigest(pairingToken) {
|
|
63
|
+
if (typeof pairingToken !== "string" || pairingToken.length === 0) {
|
|
64
|
+
throw new Error("Remote Session pairing token is required");
|
|
65
|
+
}
|
|
66
|
+
return digest("chainlesschain.remote-session.pairing-token.v1", pairingToken);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function relayEnvelopeTranscriptHash(envelope) {
|
|
70
|
+
const normalized = {
|
|
71
|
+
v: envelope.v,
|
|
72
|
+
sessionId: envelope.sessionId,
|
|
73
|
+
senderId: envelope.senderId,
|
|
74
|
+
sequence: envelope.sequence,
|
|
75
|
+
nonce: envelope.nonce,
|
|
76
|
+
ciphertext: envelope.ciphertext,
|
|
77
|
+
tag: envelope.tag,
|
|
78
|
+
};
|
|
79
|
+
return digest(
|
|
80
|
+
"chainlesschain.remote-session.relay-envelope.v1",
|
|
81
|
+
JSON.stringify(normalized),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function consumeRemoteRelayPossessionCapability(
|
|
86
|
+
capability,
|
|
87
|
+
{
|
|
88
|
+
sessionId,
|
|
89
|
+
mobilePeerId,
|
|
90
|
+
mobilePublicKey,
|
|
91
|
+
pairingTokenDigest,
|
|
92
|
+
coordinatorId,
|
|
93
|
+
serverInstanceId,
|
|
94
|
+
authorityVersion,
|
|
95
|
+
} = {},
|
|
96
|
+
) {
|
|
97
|
+
const proof = relayPossessionCapabilities.get(capability);
|
|
98
|
+
// Consume before checking the caller's expectation. A confused-deputy or
|
|
99
|
+
// stale binding attempt cannot probe and then reuse the capability.
|
|
100
|
+
relayPossessionCapabilities.delete(capability);
|
|
101
|
+
const expectedPublicKey = canonicalPublicKey(mobilePublicKey);
|
|
102
|
+
if (
|
|
103
|
+
!proof ||
|
|
104
|
+
proof.sessionId !== sessionId ||
|
|
105
|
+
proof.mobilePeerId !== mobilePeerId ||
|
|
106
|
+
proof.mobilePublicKey !== expectedPublicKey ||
|
|
107
|
+
proof.pairingTokenDigest !== pairingTokenDigest ||
|
|
108
|
+
proof.coordinatorId !== coordinatorId ||
|
|
109
|
+
proof.serverInstanceId !== serverInstanceId ||
|
|
110
|
+
proof.authorityVersion !== authorityVersion
|
|
111
|
+
) {
|
|
112
|
+
throw new Error("Remote relay possession capability is invalid or stale");
|
|
113
|
+
}
|
|
114
|
+
return Object.freeze({
|
|
115
|
+
...proof,
|
|
116
|
+
authorizedScopes: Object.freeze([...proof.authorizedScopes]),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeRelayAuthorityScopes(scopes) {
|
|
121
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
122
|
+
throw new Error("Durable Remote relay scopes are required");
|
|
123
|
+
}
|
|
124
|
+
const normalized = [...new Set(scopes.map(String))].sort();
|
|
125
|
+
if (normalized.some((scope) => !RELAY_AUTHORITY_SCOPES.has(scope))) {
|
|
126
|
+
throw new Error("Durable Remote relay scopes are invalid");
|
|
127
|
+
}
|
|
128
|
+
return Object.freeze(normalized);
|
|
129
|
+
}
|
|
130
|
+
|
|
37
131
|
function aad(sessionId, senderId, sequence) {
|
|
38
132
|
return Buffer.from(
|
|
39
133
|
`${PROTOCOL}\n${sessionId}\n${senderId}\n${sequence}`,
|
|
@@ -121,7 +215,13 @@ export function parseRemotePairingUri(uri, now = Date.now()) {
|
|
|
121
215
|
}
|
|
122
216
|
|
|
123
217
|
export class RemoteSessionCryptoContext {
|
|
124
|
-
constructor({
|
|
218
|
+
constructor({
|
|
219
|
+
sessionId,
|
|
220
|
+
localPeerId,
|
|
221
|
+
privateKey,
|
|
222
|
+
publicKey,
|
|
223
|
+
now = () => Date.now(),
|
|
224
|
+
} = {}) {
|
|
125
225
|
if (!sessionId || !localPeerId)
|
|
126
226
|
throw new Error("sessionId and localPeerId are required");
|
|
127
227
|
let keyPair;
|
|
@@ -134,23 +234,88 @@ export class RemoteSessionCryptoContext {
|
|
|
134
234
|
format: "der",
|
|
135
235
|
});
|
|
136
236
|
this.publicKey = publicKey || keyPair.publicKey;
|
|
237
|
+
this._now = now;
|
|
137
238
|
this.keys = new Map();
|
|
239
|
+
this.pairingMaterial = new Map();
|
|
240
|
+
this.pendingRelayCapabilities = new Map();
|
|
241
|
+
this.outstandingRelayCapabilities = new Map();
|
|
138
242
|
this.sendSequence = 0;
|
|
139
243
|
this.receivedSequences = new Map();
|
|
140
244
|
}
|
|
141
245
|
|
|
142
246
|
pair(peerId, peerPublicKey, pairingToken) {
|
|
247
|
+
return this._pair(peerId, peerPublicKey, pairingToken, null);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pairForDurableMembership(
|
|
251
|
+
peerId,
|
|
252
|
+
peerPublicKey,
|
|
253
|
+
pairingToken,
|
|
254
|
+
{
|
|
255
|
+
authorizedScopes,
|
|
256
|
+
expiresAtMs,
|
|
257
|
+
coordinatorId,
|
|
258
|
+
serverInstanceId,
|
|
259
|
+
authorityVersion,
|
|
260
|
+
} = {},
|
|
261
|
+
) {
|
|
262
|
+
const normalizedExpiry = Number(expiresAtMs);
|
|
263
|
+
if (
|
|
264
|
+
!Number.isSafeInteger(normalizedExpiry) ||
|
|
265
|
+
normalizedExpiry <= this._now()
|
|
266
|
+
) {
|
|
267
|
+
throw new Error("Durable Remote relay pairing authority is expired");
|
|
268
|
+
}
|
|
269
|
+
if (
|
|
270
|
+
typeof coordinatorId !== "string" ||
|
|
271
|
+
coordinatorId.length === 0 ||
|
|
272
|
+
typeof serverInstanceId !== "string" ||
|
|
273
|
+
serverInstanceId.length === 0 ||
|
|
274
|
+
typeof authorityVersion !== "string" ||
|
|
275
|
+
authorityVersion.length === 0
|
|
276
|
+
) {
|
|
277
|
+
throw new Error("Durable Remote relay authority domain is required");
|
|
278
|
+
}
|
|
279
|
+
return this._pair(peerId, peerPublicKey, pairingToken, {
|
|
280
|
+
authorizedScopes: normalizeRelayAuthorityScopes(authorizedScopes),
|
|
281
|
+
expiresAtMs: normalizedExpiry,
|
|
282
|
+
coordinatorId,
|
|
283
|
+
serverInstanceId,
|
|
284
|
+
authorityVersion,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
_pair(peerId, peerPublicKey, pairingToken, durableAuthority) {
|
|
143
289
|
if (!peerId || !peerPublicKey || !pairingToken)
|
|
144
290
|
throw new Error("Incomplete peer key material");
|
|
291
|
+
// Preserve the legacy re-pair/rotated-token behavior. Re-pairing replaces
|
|
292
|
+
// the transport key, but first invalidates any not-yet-consumed authority
|
|
293
|
+
// capability minted under the old key/token tuple.
|
|
294
|
+
const outstandingCapability = this.outstandingRelayCapabilities.get(peerId);
|
|
295
|
+
if (outstandingCapability) {
|
|
296
|
+
relayPossessionCapabilities.delete(outstandingCapability);
|
|
297
|
+
}
|
|
298
|
+
this.pendingRelayCapabilities.delete(peerId);
|
|
299
|
+
this.outstandingRelayCapabilities.delete(peerId);
|
|
300
|
+
const normalizedPublicKey = canonicalPublicKey(peerPublicKey);
|
|
145
301
|
this.keys.set(
|
|
146
302
|
peerId,
|
|
147
303
|
deriveKey(
|
|
148
304
|
this.privateKey,
|
|
149
|
-
importPublicKey(
|
|
305
|
+
importPublicKey(normalizedPublicKey),
|
|
150
306
|
this.sessionId,
|
|
151
307
|
pairingToken,
|
|
152
308
|
),
|
|
153
309
|
);
|
|
310
|
+
this.pairingMaterial.set(peerId, {
|
|
311
|
+
mobilePublicKey: normalizedPublicKey,
|
|
312
|
+
pairingTokenDigest: remoteSessionPairingTokenDigest(pairingToken),
|
|
313
|
+
authorizedScopes: durableAuthority?.authorizedScopes || null,
|
|
314
|
+
expiresAtMs: durableAuthority?.expiresAtMs || null,
|
|
315
|
+
coordinatorId: durableAuthority?.coordinatorId || null,
|
|
316
|
+
serverInstanceId: durableAuthority?.serverInstanceId || null,
|
|
317
|
+
authorityVersion: durableAuthority?.authorityVersion || null,
|
|
318
|
+
});
|
|
154
319
|
}
|
|
155
320
|
|
|
156
321
|
encrypt(peerId, message) {
|
|
@@ -207,6 +372,38 @@ export class RemoteSessionCryptoContext {
|
|
|
207
372
|
]);
|
|
208
373
|
const message = JSON.parse(plaintext.toString("utf8"));
|
|
209
374
|
this.receivedSequences.set(envelope.senderId, envelope.sequence);
|
|
375
|
+
const pairing = this.pairingMaterial.get(envelope.senderId);
|
|
376
|
+
if (
|
|
377
|
+
pairing &&
|
|
378
|
+
pairing.authorizedScopes !== null &&
|
|
379
|
+
pairing.coordinatorId !== null &&
|
|
380
|
+
pairing.serverInstanceId !== null &&
|
|
381
|
+
pairing.authorityVersion !== null &&
|
|
382
|
+
pairing.expiresAtMs > this._now() &&
|
|
383
|
+
message?.type === "pair.join" &&
|
|
384
|
+
typeof message.token === "string" &&
|
|
385
|
+
remoteSessionPairingTokenDigest(message.token) ===
|
|
386
|
+
pairing.pairingTokenDigest
|
|
387
|
+
) {
|
|
388
|
+
const proof = Object.freeze({
|
|
389
|
+
schema: RELAY_POSSESSION_SCHEMA,
|
|
390
|
+
sessionId: this.sessionId,
|
|
391
|
+
mobilePeerId: envelope.senderId,
|
|
392
|
+
mobilePublicKey: pairing.mobilePublicKey,
|
|
393
|
+
pairingTokenDigest: pairing.pairingTokenDigest,
|
|
394
|
+
coordinatorId: pairing.coordinatorId,
|
|
395
|
+
serverInstanceId: pairing.serverInstanceId,
|
|
396
|
+
authorityVersion: pairing.authorityVersion,
|
|
397
|
+
authorizedScopes: pairing.authorizedScopes,
|
|
398
|
+
pairingExpiresAtMs: pairing.expiresAtMs,
|
|
399
|
+
envelopeSequence: envelope.sequence,
|
|
400
|
+
envelopeTranscriptHash: relayEnvelopeTranscriptHash(envelope),
|
|
401
|
+
});
|
|
402
|
+
relayPossessionCapabilities.set(proof, { ...proof });
|
|
403
|
+
this.pendingRelayCapabilities.set(envelope.senderId, proof);
|
|
404
|
+
this.outstandingRelayCapabilities.set(envelope.senderId, proof);
|
|
405
|
+
this.pairingMaterial.delete(envelope.senderId);
|
|
406
|
+
}
|
|
210
407
|
return message;
|
|
211
408
|
} catch (error) {
|
|
212
409
|
throw new Error(
|
|
@@ -214,4 +411,15 @@ export class RemoteSessionCryptoContext {
|
|
|
214
411
|
);
|
|
215
412
|
}
|
|
216
413
|
}
|
|
414
|
+
|
|
415
|
+
takeRelayPossessionCapability(peerId) {
|
|
416
|
+
const capability = this.pendingRelayCapabilities.get(peerId);
|
|
417
|
+
this.pendingRelayCapabilities.delete(peerId);
|
|
418
|
+
if (!capability) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
"No authenticated Remote relay pairing transcript is available",
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return capability;
|
|
424
|
+
}
|
|
217
425
|
}
|
|
@@ -167,6 +167,12 @@ function publicSession(session) {
|
|
|
167
167
|
createdAt: session.createdAt,
|
|
168
168
|
expiresAt: session.expiresAt,
|
|
169
169
|
memberCount: session.members.size,
|
|
170
|
+
...(session.membershipAuthority
|
|
171
|
+
? {
|
|
172
|
+
membershipAuthority: session.membershipAuthority,
|
|
173
|
+
sessionEpoch: session.sessionEpoch,
|
|
174
|
+
}
|
|
175
|
+
: {}),
|
|
170
176
|
};
|
|
171
177
|
}
|
|
172
178
|
|
|
@@ -177,21 +183,54 @@ export class RemoteSessionRegistry {
|
|
|
177
183
|
this.tokenTtlMs = options.tokenTtlMs || DEFAULT_TOKEN_TTL_MS;
|
|
178
184
|
this.sessionTtlMs = options.sessionTtlMs || DEFAULT_SESSION_TTL_MS;
|
|
179
185
|
this.policy = options.policy || new RemoteSessionPolicy();
|
|
186
|
+
// A factory keeps ordinary/in-memory Remote Session tests and deployments
|
|
187
|
+
// from touching the machine security store. Client-hosted approval bridges
|
|
188
|
+
// explicitly require this authority and trigger its lazy construction.
|
|
189
|
+
this._membershipAuthority = options.membershipAuthority ?? null;
|
|
180
190
|
this.sessions = new Map();
|
|
181
191
|
this.tokens = new Map();
|
|
182
192
|
}
|
|
183
193
|
|
|
184
|
-
|
|
194
|
+
_requireMembershipAuthority() {
|
|
195
|
+
if (typeof this._membershipAuthority === "function") {
|
|
196
|
+
this._membershipAuthority = this._membershipAuthority();
|
|
197
|
+
}
|
|
198
|
+
if (!this._membershipAuthority) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
"Durable Remote membership authority is required for client-hosted approvals",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return this._membershipAuthority;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
create({
|
|
207
|
+
hostClientId,
|
|
208
|
+
agentSessionId,
|
|
209
|
+
name,
|
|
210
|
+
scopes,
|
|
211
|
+
durableMembership = false,
|
|
212
|
+
} = {}) {
|
|
185
213
|
if (!hostClientId) throw new Error("hostClientId is required");
|
|
186
214
|
if (!agentSessionId) throw new Error("agentSessionId is required");
|
|
187
215
|
const now = this.now();
|
|
216
|
+
const expiresAt = now + this.policy.capSessionTtl(this.sessionTtlMs);
|
|
217
|
+
const sessionId = randomUUID();
|
|
218
|
+
const durable = durableMembership
|
|
219
|
+
? this._requireMembershipAuthority().createSession({
|
|
220
|
+
sessionId,
|
|
221
|
+
agentSessionId,
|
|
222
|
+
hostPrincipalId: hostClientId,
|
|
223
|
+
scopes: REMOTE_SESSION_SCOPES,
|
|
224
|
+
expiresAt,
|
|
225
|
+
})
|
|
226
|
+
: null;
|
|
188
227
|
const session = {
|
|
189
|
-
sessionId
|
|
228
|
+
sessionId,
|
|
190
229
|
agentSessionId,
|
|
191
230
|
name: name || agentSessionId,
|
|
192
231
|
hostClientId,
|
|
193
232
|
createdAt: now,
|
|
194
|
-
expiresAt
|
|
233
|
+
expiresAt,
|
|
195
234
|
members: new Map([
|
|
196
235
|
[
|
|
197
236
|
hostClientId,
|
|
@@ -199,9 +238,16 @@ export class RemoteSessionRegistry {
|
|
|
199
238
|
clientId: hostClientId,
|
|
200
239
|
scopes: REMOTE_SESSION_SCOPES,
|
|
201
240
|
joinedAt: now,
|
|
241
|
+
...(durable ? { membershipEpoch: durable.membershipEpoch } : {}),
|
|
202
242
|
},
|
|
203
243
|
],
|
|
204
244
|
]),
|
|
245
|
+
...(durable
|
|
246
|
+
? {
|
|
247
|
+
membershipAuthority: durable.authorityVersion,
|
|
248
|
+
sessionEpoch: durable.sessionEpoch,
|
|
249
|
+
}
|
|
250
|
+
: {}),
|
|
205
251
|
};
|
|
206
252
|
this.sessions.set(session.sessionId, session);
|
|
207
253
|
return {
|
|
@@ -251,6 +297,9 @@ export class RemoteSessionRegistry {
|
|
|
251
297
|
if (!tokensMatch(token, pairing.digest)) {
|
|
252
298
|
throw new Error("Invalid pairing token");
|
|
253
299
|
}
|
|
300
|
+
if (clientId === session.hostClientId) {
|
|
301
|
+
throw new Error("Cannot replace the host membership through pairing");
|
|
302
|
+
}
|
|
254
303
|
// Org policy is enforced only after the token proves the caller is invited,
|
|
255
304
|
// so a device-limit / relay-disabled message never leaks to unauthenticated
|
|
256
305
|
// probes. Existing (non-host) devices count against the limit.
|
|
@@ -258,8 +307,17 @@ export class RemoteSessionRegistry {
|
|
|
258
307
|
(member) => member.clientId !== session.hostClientId,
|
|
259
308
|
).length;
|
|
260
309
|
this.policy.enforceJoin({ deviceCount, via });
|
|
310
|
+
const durable = session.membershipAuthority
|
|
311
|
+
? this._requireMembershipAuthority().joinMember({
|
|
312
|
+
sessionId,
|
|
313
|
+
principalId: clientId,
|
|
314
|
+
scopes: pairing.scopes,
|
|
315
|
+
expectedSessionEpoch: session.sessionEpoch,
|
|
316
|
+
})
|
|
317
|
+
: null;
|
|
261
318
|
// Pairing credentials are deliberately one-time. The host must explicitly
|
|
262
|
-
// issue another token for each additional device.
|
|
319
|
+
// issue another token for each additional device. For durable sessions the
|
|
320
|
+
// authority commit happens first, so a persistence failure stays retryable.
|
|
263
321
|
this.tokens.delete(sessionId);
|
|
264
322
|
const member = {
|
|
265
323
|
clientId,
|
|
@@ -267,6 +325,7 @@ export class RemoteSessionRegistry {
|
|
|
267
325
|
joinedAt: this.now(),
|
|
268
326
|
pushToken: pushToken || null,
|
|
269
327
|
pushProvider: pushToken ? pushProvider || null : null,
|
|
328
|
+
...(durable ? { membershipEpoch: durable.membershipEpoch } : {}),
|
|
270
329
|
};
|
|
271
330
|
session.members.set(clientId, member);
|
|
272
331
|
return { session: publicSession(session), member: { ...member } };
|
|
@@ -311,13 +370,41 @@ export class RemoteSessionRegistry {
|
|
|
311
370
|
|
|
312
371
|
authorize(sessionId, clientId, scope) {
|
|
313
372
|
const session = this.requireSession(sessionId);
|
|
373
|
+
if (session.membershipQuarantined) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
"Remote membership session is quarantined after an unknown durable transition",
|
|
376
|
+
);
|
|
377
|
+
}
|
|
314
378
|
const member = session.members.get(clientId);
|
|
315
379
|
if (!member)
|
|
316
380
|
throw new Error("Client is not paired with this remote session");
|
|
381
|
+
if (member.membershipQuarantined) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
"Remote membership is quarantined after an unknown durable transition",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
317
386
|
if (!member.scopes.includes(scope)) {
|
|
318
387
|
throw new Error(`Remote session scope required: ${scope}`);
|
|
319
388
|
}
|
|
320
|
-
|
|
389
|
+
let membership = null;
|
|
390
|
+
if (session.membershipAuthority) {
|
|
391
|
+
const verdict = this._requireMembershipAuthority().readMembership(
|
|
392
|
+
{
|
|
393
|
+
sessionId,
|
|
394
|
+
principalId: clientId,
|
|
395
|
+
sessionEpoch: session.sessionEpoch,
|
|
396
|
+
membershipEpoch: member.membershipEpoch,
|
|
397
|
+
},
|
|
398
|
+
scope,
|
|
399
|
+
);
|
|
400
|
+
if (!verdict.ok) {
|
|
401
|
+
throw new Error(
|
|
402
|
+
`Remote membership authority denied: ${verdict.reason}`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
membership = verdict.binding;
|
|
406
|
+
}
|
|
407
|
+
return { session, member, membership };
|
|
321
408
|
}
|
|
322
409
|
|
|
323
410
|
members(sessionId) {
|
|
@@ -364,6 +451,21 @@ export class RemoteSessionRegistry {
|
|
|
364
451
|
const member = session.members.get(clientId);
|
|
365
452
|
if (!member)
|
|
366
453
|
throw new Error("Device is not paired with this remote session");
|
|
454
|
+
if (session.membershipAuthority) {
|
|
455
|
+
try {
|
|
456
|
+
this._requireMembershipAuthority().revokeMember({
|
|
457
|
+
sessionId,
|
|
458
|
+
principalId: clientId,
|
|
459
|
+
expectedSessionEpoch: session.sessionEpoch,
|
|
460
|
+
expectedMembershipEpoch: member.membershipEpoch,
|
|
461
|
+
});
|
|
462
|
+
} catch (error) {
|
|
463
|
+
// The durable outcome may be unknown. Keep the record for diagnostics,
|
|
464
|
+
// but locally quarantine it so no later frame can authorize.
|
|
465
|
+
member.membershipQuarantined = true;
|
|
466
|
+
throw error;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
367
469
|
session.members.delete(clientId);
|
|
368
470
|
return { session: publicSession(session), member: { ...member } };
|
|
369
471
|
}
|
|
@@ -385,12 +487,39 @@ export class RemoteSessionRegistry {
|
|
|
385
487
|
removeClient(clientId) {
|
|
386
488
|
const affected = [];
|
|
387
489
|
for (const [sessionId, session] of this.sessions) {
|
|
388
|
-
|
|
490
|
+
const member = session.members.get(clientId);
|
|
491
|
+
if (!member) continue;
|
|
389
492
|
if (session.hostClientId === clientId) {
|
|
493
|
+
if (session.membershipAuthority) {
|
|
494
|
+
try {
|
|
495
|
+
this._requireMembershipAuthority().closeSession({
|
|
496
|
+
sessionId,
|
|
497
|
+
hostPrincipalId: clientId,
|
|
498
|
+
expectedSessionEpoch: session.sessionEpoch,
|
|
499
|
+
});
|
|
500
|
+
} catch (error) {
|
|
501
|
+
session.membershipQuarantined = true;
|
|
502
|
+
throw error;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
390
505
|
this.sessions.delete(sessionId);
|
|
391
506
|
this.tokens.delete(sessionId);
|
|
392
507
|
affected.push({ sessionId, closed: true });
|
|
393
508
|
} else {
|
|
509
|
+
if (session.membershipAuthority) {
|
|
510
|
+
try {
|
|
511
|
+
this._requireMembershipAuthority().revokeMember({
|
|
512
|
+
sessionId,
|
|
513
|
+
principalId: clientId,
|
|
514
|
+
expectedSessionEpoch: session.sessionEpoch,
|
|
515
|
+
expectedMembershipEpoch: member.membershipEpoch,
|
|
516
|
+
});
|
|
517
|
+
} catch (error) {
|
|
518
|
+
member.membershipQuarantined = true;
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
session.members.delete(clientId);
|
|
394
523
|
affected.push({ sessionId, closed: false });
|
|
395
524
|
}
|
|
396
525
|
}
|
|
@@ -402,6 +531,18 @@ export class RemoteSessionRegistry {
|
|
|
402
531
|
if (session.hostClientId !== clientId) {
|
|
403
532
|
throw new Error("Only the host can close a Remote Session");
|
|
404
533
|
}
|
|
534
|
+
if (session.membershipAuthority) {
|
|
535
|
+
try {
|
|
536
|
+
this._requireMembershipAuthority().closeSession({
|
|
537
|
+
sessionId,
|
|
538
|
+
hostPrincipalId: clientId,
|
|
539
|
+
expectedSessionEpoch: session.sessionEpoch,
|
|
540
|
+
});
|
|
541
|
+
} catch (error) {
|
|
542
|
+
session.membershipQuarantined = true;
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
405
546
|
this.sessions.delete(sessionId);
|
|
406
547
|
this.tokens.delete(sessionId);
|
|
407
548
|
return publicSession(session);
|
|
@@ -411,6 +552,18 @@ export class RemoteSessionRegistry {
|
|
|
411
552
|
const session = this.sessions.get(sessionId);
|
|
412
553
|
if (!session) throw new Error("Remote session not found");
|
|
413
554
|
if (session.expiresAt <= this.now()) {
|
|
555
|
+
if (session.membershipAuthority) {
|
|
556
|
+
try {
|
|
557
|
+
this._requireMembershipAuthority().closeSession({
|
|
558
|
+
sessionId,
|
|
559
|
+
hostPrincipalId: session.hostClientId,
|
|
560
|
+
expectedSessionEpoch: session.sessionEpoch,
|
|
561
|
+
});
|
|
562
|
+
} catch (error) {
|
|
563
|
+
session.membershipQuarantined = true;
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
414
567
|
this.sessions.delete(sessionId);
|
|
415
568
|
this.tokens.delete(sessionId);
|
|
416
569
|
throw new Error("Remote session expired");
|