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
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable, same-machine authority for client-hosted Remote Session members.
|
|
3
|
+
*
|
|
4
|
+
* A WebSocket connection id is not, by itself, durable proof that a remote
|
|
5
|
+
* approval still comes from a paired principal. This store gives every
|
|
6
|
+
* session and membership activation a monotonically increasing epoch. The WS
|
|
7
|
+
* server persists create/join/revoke/close transitions before changing its
|
|
8
|
+
* in-memory registry, then forwards the exact epochs it authorized. The local
|
|
9
|
+
* approval bridge re-checks those epochs under the same cross-process lock and
|
|
10
|
+
* performs the approval CAS inside that lock.
|
|
11
|
+
*
|
|
12
|
+
* `principalId` is the current server process's connection/relay identity; the
|
|
13
|
+
* registry does not reconstruct live sockets or sessions from this file after
|
|
14
|
+
* a server restart. This is deliberately a same-OS-user,
|
|
15
|
+
* same-machine/file-authority
|
|
16
|
+
* boundary. It does not claim consensus, quorum revocation, fencing across
|
|
17
|
+
* independent hosts, detection of an offline store rollback after every
|
|
18
|
+
* observing process exits, or cancellation of a side effect dispatched after
|
|
19
|
+
* an approval CAS has already won.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { getMachineSecurityAnchorDir } from "./paths.js";
|
|
26
|
+
import {
|
|
27
|
+
mutateSecurityStore,
|
|
28
|
+
readSecurityStore,
|
|
29
|
+
} from "./durable-security-store.js";
|
|
30
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
31
|
+
|
|
32
|
+
export const REMOTE_MEMBERSHIP_AUTHORITY_VERSION =
|
|
33
|
+
"durable-monotonic-membership-epoch-v1";
|
|
34
|
+
export const REMOTE_MEMBERSHIP_AUTHORITY_UNAVAILABLE_CODE =
|
|
35
|
+
"CC_REMOTE_MEMBERSHIP_AUTHORITY_UNAVAILABLE";
|
|
36
|
+
export const REMOTE_MEMBERSHIP_AUTHORITY_ROLLBACK_CODE =
|
|
37
|
+
"CC_REMOTE_MEMBERSHIP_AUTHORITY_ROLLBACK";
|
|
38
|
+
|
|
39
|
+
const STORE_VERSION = 1;
|
|
40
|
+
const LABEL = "Remote membership authority";
|
|
41
|
+
const VALID_SCOPES = new Set(["observe", "prompt", "approve", "interrupt"]);
|
|
42
|
+
const VALID_EVENT_TYPES = new Set([
|
|
43
|
+
"session.created",
|
|
44
|
+
"member.joined",
|
|
45
|
+
"member.revoked",
|
|
46
|
+
"session.closed",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
function requiredId(value, label) {
|
|
50
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 512) {
|
|
51
|
+
throw new TypeError(`${label} must be a non-empty bounded string`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseEpoch(value, label) {
|
|
57
|
+
if (typeof value !== "string" || !/^[1-9]\d*$/.test(value)) {
|
|
58
|
+
throw new TypeError(`${label} must be a canonical positive integer`);
|
|
59
|
+
}
|
|
60
|
+
return BigInt(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseGeneration(value, label) {
|
|
64
|
+
if (typeof value !== "string" || !/^(?:0|[1-9]\d*)$/.test(value)) {
|
|
65
|
+
throw new TypeError(`${label} must be a canonical non-negative integer`);
|
|
66
|
+
}
|
|
67
|
+
return BigInt(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeScopes(scopes) {
|
|
71
|
+
if (!Array.isArray(scopes) || scopes.length === 0) {
|
|
72
|
+
throw new TypeError("membership scopes must be a non-empty array");
|
|
73
|
+
}
|
|
74
|
+
const normalized = [...new Set(scopes.map((scope) => String(scope)))];
|
|
75
|
+
for (const scope of normalized) {
|
|
76
|
+
if (!VALID_SCOPES.has(scope)) {
|
|
77
|
+
throw new TypeError(`unsupported membership scope: ${scope}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function authorityError(code, message, cause, filePath) {
|
|
84
|
+
const error = new Error(message, cause ? { cause } : undefined);
|
|
85
|
+
error.name = "RemoteMembershipAuthorityError";
|
|
86
|
+
error.code = code;
|
|
87
|
+
error.filePath = filePath;
|
|
88
|
+
return error;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unavailableError(cause, filePath) {
|
|
92
|
+
if (
|
|
93
|
+
cause?.code === REMOTE_MEMBERSHIP_AUTHORITY_UNAVAILABLE_CODE ||
|
|
94
|
+
cause?.code === REMOTE_MEMBERSHIP_AUTHORITY_ROLLBACK_CODE
|
|
95
|
+
) {
|
|
96
|
+
return cause;
|
|
97
|
+
}
|
|
98
|
+
return authorityError(
|
|
99
|
+
REMOTE_MEMBERSHIP_AUTHORITY_UNAVAILABLE_CODE,
|
|
100
|
+
"Remote membership authority is unavailable; remote approval is denied",
|
|
101
|
+
cause,
|
|
102
|
+
filePath,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function rollbackError(observed, current, filePath) {
|
|
107
|
+
const error = authorityError(
|
|
108
|
+
REMOTE_MEMBERSHIP_AUTHORITY_ROLLBACK_CODE,
|
|
109
|
+
`Remote membership authority rolled back from ${current} to ${observed}; remote approval is denied`,
|
|
110
|
+
null,
|
|
111
|
+
filePath,
|
|
112
|
+
);
|
|
113
|
+
error.observedGeneration = String(observed);
|
|
114
|
+
error.currentGeneration = String(current);
|
|
115
|
+
return error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function cloneMember(member) {
|
|
119
|
+
return {
|
|
120
|
+
principalId: member.principalId,
|
|
121
|
+
membershipEpoch: member.membershipEpoch,
|
|
122
|
+
status: member.status,
|
|
123
|
+
scopes: [...member.scopes],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cloneSession(session) {
|
|
128
|
+
return {
|
|
129
|
+
sessionId: session.sessionId,
|
|
130
|
+
agentSessionId: session.agentSessionId,
|
|
131
|
+
hostPrincipalId: session.hostPrincipalId,
|
|
132
|
+
sessionEpoch: session.sessionEpoch,
|
|
133
|
+
expiresAt: session.expiresAt,
|
|
134
|
+
status: session.status,
|
|
135
|
+
members: new Map(
|
|
136
|
+
[...session.members].map(([principalId, member]) => [
|
|
137
|
+
principalId,
|
|
138
|
+
cloneMember(member),
|
|
139
|
+
]),
|
|
140
|
+
),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function replayStore(store, { missing = false } = {}) {
|
|
145
|
+
if (missing && Object.keys(store).length === 0) {
|
|
146
|
+
return { generation: 0n, events: [], sessions: new Map() };
|
|
147
|
+
}
|
|
148
|
+
if (store.version !== STORE_VERSION) {
|
|
149
|
+
throw new TypeError(
|
|
150
|
+
`unsupported Remote membership authority version: ${String(store.version)}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const generation = parseGeneration(store.generation, "generation");
|
|
154
|
+
if (!Array.isArray(store.events)) {
|
|
155
|
+
throw new TypeError("events must be an array");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const sessions = new Map();
|
|
159
|
+
let previousGeneration = 0n;
|
|
160
|
+
for (const [index, event] of store.events.entries()) {
|
|
161
|
+
const prefix = `events[${index}]`;
|
|
162
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) {
|
|
163
|
+
throw new TypeError(`${prefix} must be an object`);
|
|
164
|
+
}
|
|
165
|
+
const eventGeneration = parseEpoch(
|
|
166
|
+
event.generation,
|
|
167
|
+
`${prefix}.generation`,
|
|
168
|
+
);
|
|
169
|
+
const eventPrevious = parseGeneration(
|
|
170
|
+
event.previousGeneration,
|
|
171
|
+
`${prefix}.previousGeneration`,
|
|
172
|
+
);
|
|
173
|
+
if (
|
|
174
|
+
eventPrevious !== previousGeneration ||
|
|
175
|
+
eventGeneration !== previousGeneration + 1n
|
|
176
|
+
) {
|
|
177
|
+
throw new TypeError(`${prefix} breaks the generation chain`);
|
|
178
|
+
}
|
|
179
|
+
if (!VALID_EVENT_TYPES.has(event.type)) {
|
|
180
|
+
throw new TypeError(`${prefix}.type is unsupported`);
|
|
181
|
+
}
|
|
182
|
+
requiredId(event.eventId, `${prefix}.eventId`);
|
|
183
|
+
const sessionId = requiredId(event.sessionId, `${prefix}.sessionId`);
|
|
184
|
+
if (!Number.isSafeInteger(event.occurredAtMs) || event.occurredAtMs < 0) {
|
|
185
|
+
throw new TypeError(`${prefix}.occurredAtMs must be a safe timestamp`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (event.type === "session.created") {
|
|
189
|
+
if (sessions.has(sessionId)) {
|
|
190
|
+
throw new TypeError(`${prefix} recreates an existing session`);
|
|
191
|
+
}
|
|
192
|
+
const agentSessionId = requiredId(
|
|
193
|
+
event.agentSessionId,
|
|
194
|
+
`${prefix}.agentSessionId`,
|
|
195
|
+
);
|
|
196
|
+
const hostPrincipalId = requiredId(
|
|
197
|
+
event.principalId,
|
|
198
|
+
`${prefix}.principalId`,
|
|
199
|
+
);
|
|
200
|
+
const scopes = normalizeScopes(event.scopes);
|
|
201
|
+
if (
|
|
202
|
+
!Number.isSafeInteger(event.expiresAt) ||
|
|
203
|
+
event.expiresAt <= event.occurredAtMs
|
|
204
|
+
) {
|
|
205
|
+
throw new TypeError(`${prefix}.expiresAt must be after creation`);
|
|
206
|
+
}
|
|
207
|
+
const epoch = String(eventGeneration);
|
|
208
|
+
sessions.set(sessionId, {
|
|
209
|
+
sessionId,
|
|
210
|
+
agentSessionId,
|
|
211
|
+
hostPrincipalId,
|
|
212
|
+
sessionEpoch: epoch,
|
|
213
|
+
expiresAt: event.expiresAt,
|
|
214
|
+
status: "active",
|
|
215
|
+
members: new Map([
|
|
216
|
+
[
|
|
217
|
+
hostPrincipalId,
|
|
218
|
+
{
|
|
219
|
+
principalId: hostPrincipalId,
|
|
220
|
+
membershipEpoch: epoch,
|
|
221
|
+
status: "active",
|
|
222
|
+
scopes,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
]),
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
const session = sessions.get(sessionId);
|
|
229
|
+
if (!session || session.status !== "active") {
|
|
230
|
+
throw new TypeError(`${prefix} targets an inactive session`);
|
|
231
|
+
}
|
|
232
|
+
if (event.expectedSessionEpoch !== session.sessionEpoch) {
|
|
233
|
+
throw new TypeError(`${prefix} has a stale session epoch`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (event.type === "member.joined") {
|
|
237
|
+
const principalId = requiredId(
|
|
238
|
+
event.principalId,
|
|
239
|
+
`${prefix}.principalId`,
|
|
240
|
+
);
|
|
241
|
+
const prior = session.members.get(principalId) || null;
|
|
242
|
+
const expectedPrior = prior ? prior.membershipEpoch : null;
|
|
243
|
+
if ((event.previousMembershipEpoch ?? null) !== expectedPrior) {
|
|
244
|
+
throw new TypeError(`${prefix} has a stale prior membership epoch`);
|
|
245
|
+
}
|
|
246
|
+
session.members.set(principalId, {
|
|
247
|
+
principalId,
|
|
248
|
+
membershipEpoch: String(eventGeneration),
|
|
249
|
+
status: "active",
|
|
250
|
+
scopes: normalizeScopes(event.scopes),
|
|
251
|
+
});
|
|
252
|
+
} else if (event.type === "member.revoked") {
|
|
253
|
+
const principalId = requiredId(
|
|
254
|
+
event.principalId,
|
|
255
|
+
`${prefix}.principalId`,
|
|
256
|
+
);
|
|
257
|
+
const member = session.members.get(principalId);
|
|
258
|
+
if (!member || member.status !== "active") {
|
|
259
|
+
throw new TypeError(`${prefix} targets an inactive member`);
|
|
260
|
+
}
|
|
261
|
+
if (event.expectedMembershipEpoch !== member.membershipEpoch) {
|
|
262
|
+
throw new TypeError(`${prefix} has a stale membership epoch`);
|
|
263
|
+
}
|
|
264
|
+
member.status = "revoked";
|
|
265
|
+
member.membershipEpoch = String(eventGeneration);
|
|
266
|
+
} else {
|
|
267
|
+
if (event.principalId !== session.hostPrincipalId) {
|
|
268
|
+
throw new TypeError(`${prefix} was not closed by its host`);
|
|
269
|
+
}
|
|
270
|
+
session.status = "closed";
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
previousGeneration = eventGeneration;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (previousGeneration !== generation) {
|
|
277
|
+
throw new TypeError("generation does not match the durable event chain");
|
|
278
|
+
}
|
|
279
|
+
return { generation, events: [...store.events], sessions };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function activeVerdict(snapshot, binding, requiredScope, now = null) {
|
|
283
|
+
let sessionId;
|
|
284
|
+
let principalId;
|
|
285
|
+
try {
|
|
286
|
+
sessionId = requiredId(binding?.sessionId, "sessionId");
|
|
287
|
+
principalId = requiredId(binding?.principalId, "principalId");
|
|
288
|
+
} catch {
|
|
289
|
+
return { ok: false, reason: "membership-binding-required" };
|
|
290
|
+
}
|
|
291
|
+
let sessionEpoch;
|
|
292
|
+
let membershipEpoch;
|
|
293
|
+
try {
|
|
294
|
+
sessionEpoch = String(parseEpoch(binding?.sessionEpoch, "sessionEpoch"));
|
|
295
|
+
membershipEpoch = String(
|
|
296
|
+
parseEpoch(binding?.membershipEpoch, "membershipEpoch"),
|
|
297
|
+
);
|
|
298
|
+
} catch {
|
|
299
|
+
return { ok: false, reason: "membership-epoch-required" };
|
|
300
|
+
}
|
|
301
|
+
const session = snapshot.sessions.get(sessionId);
|
|
302
|
+
if (!session) return { ok: false, reason: "membership-session-unknown" };
|
|
303
|
+
if (session.status !== "active") {
|
|
304
|
+
return { ok: false, reason: "membership-session-closed" };
|
|
305
|
+
}
|
|
306
|
+
if (Number.isSafeInteger(now) && session.expiresAt <= now) {
|
|
307
|
+
return { ok: false, reason: "membership-session-expired" };
|
|
308
|
+
}
|
|
309
|
+
if (session.sessionEpoch !== sessionEpoch) {
|
|
310
|
+
return { ok: false, reason: "stale-session-epoch" };
|
|
311
|
+
}
|
|
312
|
+
const member = session.members.get(principalId);
|
|
313
|
+
if (!member) return { ok: false, reason: "membership-unknown" };
|
|
314
|
+
if (member.status !== "active") {
|
|
315
|
+
return { ok: false, reason: "membership-revoked" };
|
|
316
|
+
}
|
|
317
|
+
if (member.membershipEpoch !== membershipEpoch) {
|
|
318
|
+
return { ok: false, reason: "stale-membership-epoch" };
|
|
319
|
+
}
|
|
320
|
+
if (requiredScope && !member.scopes.includes(requiredScope)) {
|
|
321
|
+
return { ok: false, reason: "membership-scope-denied" };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
ok: true,
|
|
325
|
+
reason: null,
|
|
326
|
+
binding: Object.freeze({
|
|
327
|
+
authorityVersion: REMOTE_MEMBERSHIP_AUTHORITY_VERSION,
|
|
328
|
+
sessionId,
|
|
329
|
+
principalId,
|
|
330
|
+
sessionEpoch,
|
|
331
|
+
membershipEpoch,
|
|
332
|
+
scopes: Object.freeze([...member.scopes]),
|
|
333
|
+
}),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function getRemoteMembershipAuthorityPath() {
|
|
338
|
+
return path.join(
|
|
339
|
+
getMachineSecurityAnchorDir(),
|
|
340
|
+
"remote-membership-authority-v1.json",
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export class DurableRemoteMembershipAuthority {
|
|
345
|
+
constructor({
|
|
346
|
+
filePath = getRemoteMembershipAuthorityPath(),
|
|
347
|
+
now = () => Date.now(),
|
|
348
|
+
createEventId = randomUUID,
|
|
349
|
+
lockOptions,
|
|
350
|
+
lock = withFileLock,
|
|
351
|
+
} = {}) {
|
|
352
|
+
if (!path.isAbsolute(filePath)) {
|
|
353
|
+
throw new TypeError("Remote membership authority path must be absolute");
|
|
354
|
+
}
|
|
355
|
+
this.filePath = path.resolve(filePath);
|
|
356
|
+
this._now = now;
|
|
357
|
+
this._createEventId = createEventId;
|
|
358
|
+
this._lockOptions = lockOptions;
|
|
359
|
+
this._lock = lock;
|
|
360
|
+
this._observedGeneration = 0n;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
_observe(generation) {
|
|
364
|
+
if (generation < this._observedGeneration) {
|
|
365
|
+
throw rollbackError(generation, this._observedGeneration, this.filePath);
|
|
366
|
+
}
|
|
367
|
+
this._observedGeneration = generation;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
_readUnlocked() {
|
|
371
|
+
const existed = fs.existsSync(this.filePath);
|
|
372
|
+
const snapshot = replayStore(readSecurityStore(this.filePath, LABEL), {
|
|
373
|
+
missing: !existed,
|
|
374
|
+
});
|
|
375
|
+
this._observe(snapshot.generation);
|
|
376
|
+
return snapshot;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
_mutate(makeEvent) {
|
|
380
|
+
try {
|
|
381
|
+
const result = mutateSecurityStore(
|
|
382
|
+
this.filePath,
|
|
383
|
+
LABEL,
|
|
384
|
+
(draft) => {
|
|
385
|
+
const snapshot = replayStore(draft, {
|
|
386
|
+
missing: !fs.existsSync(this.filePath),
|
|
387
|
+
});
|
|
388
|
+
this._observe(snapshot.generation);
|
|
389
|
+
const nextGeneration = snapshot.generation + 1n;
|
|
390
|
+
const event = {
|
|
391
|
+
eventId: requiredId(this._createEventId(), "eventId"),
|
|
392
|
+
generation: String(nextGeneration),
|
|
393
|
+
previousGeneration: String(snapshot.generation),
|
|
394
|
+
occurredAtMs: this._now(),
|
|
395
|
+
...makeEvent(snapshot, String(nextGeneration)),
|
|
396
|
+
};
|
|
397
|
+
draft.version = STORE_VERSION;
|
|
398
|
+
draft.generation = String(nextGeneration);
|
|
399
|
+
draft.events = [...snapshot.events, event];
|
|
400
|
+
const verified = replayStore(draft);
|
|
401
|
+
const session = verified.sessions.get(event.sessionId);
|
|
402
|
+
const member = event.principalId
|
|
403
|
+
? session?.members.get(event.principalId)
|
|
404
|
+
: null;
|
|
405
|
+
return {
|
|
406
|
+
generation: nextGeneration,
|
|
407
|
+
session: session ? cloneSession(session) : null,
|
|
408
|
+
member: member ? cloneMember(member) : null,
|
|
409
|
+
};
|
|
410
|
+
},
|
|
411
|
+
{ ...this._lockOptions, lock: this._lock },
|
|
412
|
+
);
|
|
413
|
+
this._observe(result.generation);
|
|
414
|
+
return result;
|
|
415
|
+
} catch (cause) {
|
|
416
|
+
throw unavailableError(cause, this.filePath);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
createSession({
|
|
421
|
+
sessionId,
|
|
422
|
+
agentSessionId,
|
|
423
|
+
hostPrincipalId,
|
|
424
|
+
scopes,
|
|
425
|
+
expiresAt,
|
|
426
|
+
} = {}) {
|
|
427
|
+
const normalizedSessionId = requiredId(sessionId, "sessionId");
|
|
428
|
+
const normalizedAgentSessionId = requiredId(
|
|
429
|
+
agentSessionId,
|
|
430
|
+
"agentSessionId",
|
|
431
|
+
);
|
|
432
|
+
const normalizedHost = requiredId(hostPrincipalId, "hostPrincipalId");
|
|
433
|
+
const normalizedScopes = normalizeScopes(scopes);
|
|
434
|
+
if (!Number.isSafeInteger(expiresAt) || expiresAt <= this._now()) {
|
|
435
|
+
throw new TypeError("expiresAt must be a future safe timestamp");
|
|
436
|
+
}
|
|
437
|
+
const result = this._mutate((snapshot) => {
|
|
438
|
+
if (snapshot.sessions.has(normalizedSessionId)) {
|
|
439
|
+
throw new Error("Remote membership session already exists");
|
|
440
|
+
}
|
|
441
|
+
return {
|
|
442
|
+
type: "session.created",
|
|
443
|
+
sessionId: normalizedSessionId,
|
|
444
|
+
agentSessionId: normalizedAgentSessionId,
|
|
445
|
+
principalId: normalizedHost,
|
|
446
|
+
scopes: normalizedScopes,
|
|
447
|
+
expiresAt,
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
return Object.freeze({
|
|
451
|
+
authorityVersion: REMOTE_MEMBERSHIP_AUTHORITY_VERSION,
|
|
452
|
+
sessionEpoch: result.session.sessionEpoch,
|
|
453
|
+
membershipEpoch: result.member.membershipEpoch,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
joinMember({ sessionId, principalId, scopes, expectedSessionEpoch } = {}) {
|
|
458
|
+
const normalizedSessionId = requiredId(sessionId, "sessionId");
|
|
459
|
+
const normalizedPrincipal = requiredId(principalId, "principalId");
|
|
460
|
+
const normalizedScopes = normalizeScopes(scopes);
|
|
461
|
+
const normalizedSessionEpoch = String(
|
|
462
|
+
parseEpoch(expectedSessionEpoch, "expectedSessionEpoch"),
|
|
463
|
+
);
|
|
464
|
+
const result = this._mutate((snapshot) => {
|
|
465
|
+
const session = snapshot.sessions.get(normalizedSessionId);
|
|
466
|
+
if (!session || session.status !== "active") {
|
|
467
|
+
throw new Error("Remote membership session is not active");
|
|
468
|
+
}
|
|
469
|
+
if (session.sessionEpoch !== normalizedSessionEpoch) {
|
|
470
|
+
throw new Error("Remote membership session epoch is stale");
|
|
471
|
+
}
|
|
472
|
+
if (session.hostPrincipalId === normalizedPrincipal) {
|
|
473
|
+
throw new Error("Cannot replace the host membership through join");
|
|
474
|
+
}
|
|
475
|
+
const previous = session.members.get(normalizedPrincipal) || null;
|
|
476
|
+
return {
|
|
477
|
+
type: "member.joined",
|
|
478
|
+
sessionId: normalizedSessionId,
|
|
479
|
+
expectedSessionEpoch: normalizedSessionEpoch,
|
|
480
|
+
principalId: normalizedPrincipal,
|
|
481
|
+
previousMembershipEpoch: previous?.membershipEpoch || null,
|
|
482
|
+
scopes: normalizedScopes,
|
|
483
|
+
};
|
|
484
|
+
});
|
|
485
|
+
return Object.freeze({
|
|
486
|
+
authorityVersion: REMOTE_MEMBERSHIP_AUTHORITY_VERSION,
|
|
487
|
+
sessionEpoch: result.session.sessionEpoch,
|
|
488
|
+
membershipEpoch: result.member.membershipEpoch,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
revokeMember({
|
|
493
|
+
sessionId,
|
|
494
|
+
principalId,
|
|
495
|
+
expectedSessionEpoch,
|
|
496
|
+
expectedMembershipEpoch,
|
|
497
|
+
} = {}) {
|
|
498
|
+
const normalizedSessionId = requiredId(sessionId, "sessionId");
|
|
499
|
+
const normalizedPrincipal = requiredId(principalId, "principalId");
|
|
500
|
+
const normalizedSessionEpoch = String(
|
|
501
|
+
parseEpoch(expectedSessionEpoch, "expectedSessionEpoch"),
|
|
502
|
+
);
|
|
503
|
+
const normalizedMembershipEpoch = String(
|
|
504
|
+
parseEpoch(expectedMembershipEpoch, "expectedMembershipEpoch"),
|
|
505
|
+
);
|
|
506
|
+
const result = this._mutate((snapshot) => {
|
|
507
|
+
const verdict = activeVerdict(
|
|
508
|
+
snapshot,
|
|
509
|
+
{
|
|
510
|
+
sessionId: normalizedSessionId,
|
|
511
|
+
principalId: normalizedPrincipal,
|
|
512
|
+
sessionEpoch: normalizedSessionEpoch,
|
|
513
|
+
membershipEpoch: normalizedMembershipEpoch,
|
|
514
|
+
},
|
|
515
|
+
null,
|
|
516
|
+
this._now(),
|
|
517
|
+
);
|
|
518
|
+
if (!verdict.ok) {
|
|
519
|
+
throw new Error(`Remote membership revoke rejected: ${verdict.reason}`);
|
|
520
|
+
}
|
|
521
|
+
const session = snapshot.sessions.get(normalizedSessionId);
|
|
522
|
+
if (session.hostPrincipalId === normalizedPrincipal) {
|
|
523
|
+
throw new Error("Cannot revoke the host membership");
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
type: "member.revoked",
|
|
527
|
+
sessionId: normalizedSessionId,
|
|
528
|
+
expectedSessionEpoch: normalizedSessionEpoch,
|
|
529
|
+
principalId: normalizedPrincipal,
|
|
530
|
+
expectedMembershipEpoch: normalizedMembershipEpoch,
|
|
531
|
+
};
|
|
532
|
+
});
|
|
533
|
+
return Object.freeze({
|
|
534
|
+
authorityVersion: REMOTE_MEMBERSHIP_AUTHORITY_VERSION,
|
|
535
|
+
revokedEpoch: String(result.generation),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
closeSession({ sessionId, hostPrincipalId, expectedSessionEpoch } = {}) {
|
|
540
|
+
const normalizedSessionId = requiredId(sessionId, "sessionId");
|
|
541
|
+
const normalizedHost = requiredId(hostPrincipalId, "hostPrincipalId");
|
|
542
|
+
const normalizedSessionEpoch = String(
|
|
543
|
+
parseEpoch(expectedSessionEpoch, "expectedSessionEpoch"),
|
|
544
|
+
);
|
|
545
|
+
const result = this._mutate((snapshot) => {
|
|
546
|
+
const session = snapshot.sessions.get(normalizedSessionId);
|
|
547
|
+
if (!session || session.status !== "active") {
|
|
548
|
+
throw new Error("Remote membership session is not active");
|
|
549
|
+
}
|
|
550
|
+
if (
|
|
551
|
+
session.hostPrincipalId !== normalizedHost ||
|
|
552
|
+
session.sessionEpoch !== normalizedSessionEpoch
|
|
553
|
+
) {
|
|
554
|
+
throw new Error("Remote membership session close CAS is stale");
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
type: "session.closed",
|
|
558
|
+
sessionId: normalizedSessionId,
|
|
559
|
+
expectedSessionEpoch: normalizedSessionEpoch,
|
|
560
|
+
principalId: normalizedHost,
|
|
561
|
+
};
|
|
562
|
+
});
|
|
563
|
+
return Object.freeze({
|
|
564
|
+
authorityVersion: REMOTE_MEMBERSHIP_AUTHORITY_VERSION,
|
|
565
|
+
closedEpoch: String(result.generation),
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Hold the membership lock while `task` performs the approval-store CAS.
|
|
571
|
+
* A revoke either linearizes first (task is never called) or waits until the
|
|
572
|
+
* already-authorized approval CAS has completed. This does not retroactively
|
|
573
|
+
* cancel a tool side effect dispatched after that CAS.
|
|
574
|
+
*/
|
|
575
|
+
withActiveMembership(binding, requiredScope, task) {
|
|
576
|
+
if (typeof task !== "function") throw new TypeError("task is required");
|
|
577
|
+
if (task.constructor?.name === "AsyncFunction") {
|
|
578
|
+
throw new TypeError(
|
|
579
|
+
"membership-fenced task must be synchronous so the lock covers its CAS",
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
fs.mkdirSync(path.dirname(this.filePath), {
|
|
584
|
+
recursive: true,
|
|
585
|
+
mode: 0o700,
|
|
586
|
+
});
|
|
587
|
+
return this._lock(
|
|
588
|
+
this.filePath,
|
|
589
|
+
() => {
|
|
590
|
+
const snapshot = this._readUnlocked();
|
|
591
|
+
const verdict = activeVerdict(
|
|
592
|
+
snapshot,
|
|
593
|
+
binding,
|
|
594
|
+
requiredScope,
|
|
595
|
+
this._now(),
|
|
596
|
+
);
|
|
597
|
+
if (!verdict.ok) return verdict;
|
|
598
|
+
const value = task(verdict.binding);
|
|
599
|
+
if (value && typeof value.then === "function") {
|
|
600
|
+
throw new TypeError(
|
|
601
|
+
"membership-fenced task returned a Promise before the lock could cover its CAS",
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
...verdict,
|
|
606
|
+
value,
|
|
607
|
+
};
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
timeoutMs: 2000,
|
|
611
|
+
staleMs: 30000,
|
|
612
|
+
failIfUnavailable: true,
|
|
613
|
+
...this._lockOptions,
|
|
614
|
+
},
|
|
615
|
+
);
|
|
616
|
+
} catch (cause) {
|
|
617
|
+
if (
|
|
618
|
+
cause instanceof TypeError &&
|
|
619
|
+
cause.message.startsWith("membership-fenced task")
|
|
620
|
+
) {
|
|
621
|
+
throw cause;
|
|
622
|
+
}
|
|
623
|
+
throw unavailableError(cause, this.filePath);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
readMembership(binding, requiredScope = null) {
|
|
628
|
+
return this.withActiveMembership(binding, requiredScope, (current) =>
|
|
629
|
+
Object.freeze({ ...current, scopes: Object.freeze([...current.scopes]) }),
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
}
|