chainlesschain 0.163.8 → 0.165.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -4
- package/scripts/macos-mcp-launcher-build.mjs +317 -0
- package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
- package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
- package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
- package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
- package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
- package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
- package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/cowork.js +418 -41
- package/src/commands/permissions.js +240 -16
- package/src/commands/plugin.js +1293 -56
- package/src/commands/session-location.js +276 -0
- package/src/commands/session.js +2 -0
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +316 -17
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +565 -35
- package/src/gateways/ws/ws-agent-handler.js +12 -0
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +510 -50
- package/src/harness/jsonl-session-store.js +341 -24
- package/src/harness/mcp-client.js +8 -1
- package/src/harness/remote-session-audit.js +2 -0
- package/src/harness/remote-session-crypto.js +929 -21
- package/src/harness/remote-session-registry.js +945 -24
- package/src/harness/session-list-index.js +233 -14
- package/src/lib/agent-authority.js +6 -0
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +248 -0
- package/src/lib/background-agent-keeper-protocol.js +291 -0
- package/src/lib/background-agent-supervisor.js +1357 -85
- package/src/lib/background-interaction-resolver.js +12 -0
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +190 -0
- package/src/lib/cowork-workflow.js +1188 -76
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/dynamic-workflow-facade.js +1736 -0
- package/src/lib/execution-location-contract.js +551 -0
- package/src/lib/execution-location-runtime.js +130 -0
- package/src/lib/ide-context.js +94 -6
- package/src/lib/mcp-stdio-package-materialization.js +166 -14
- package/src/lib/packer/pkg-config-generator.js +2 -0
- package/src/lib/permission-authority.js +77 -0
- package/src/lib/permission-side-effect-center.js +495 -0
- package/src/lib/plugin-runtime/install.js +514 -13
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
- package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
- package/src/lib/plugin-security.js +60 -3
- package/src/lib/process-execution-broker/index.js +1095 -57
- package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
- package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
- package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
- package/src/lib/public-ide-capability-manifest.js +3 -0
- package/src/lib/remote-approval-bridge.js +777 -34
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-authority.js +632 -0
- package/src/lib/remote-membership-coordinator.js +2973 -0
- package/src/lib/remote-membership-host-store.js +1799 -0
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/scoped-permission-store.js +369 -0
- package/src/lib/session-anti-rollback-anchor.js +262 -5
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +4 -0
- package/src/lib/with-file-lock.js +73 -38
- package/src/lib/workflow-definition-contract.js +149 -0
- package/src/repl/agent-repl.js +111 -36
- package/src/repl/chat-repl.js +2 -0
- package/src/repl/clipboard-image.js +735 -16
- package/src/repl/ide-status.js +1 -1
- package/src/repl/prompt-interactions.js +59 -6
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1182 -155
- package/src/runtime/headless-runner.js +52 -30
- package/src/runtime/headless-stream.js +51 -28
- package/src/workers/background-agent-keeper.js +689 -0
- package/src/workers/background-agent-worker.js +365 -13
- package/src/workers/background-turn-bootstrap.js +170 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
- package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
- package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
- package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
- package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
|
@@ -9,14 +9,23 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
appendFileSync,
|
|
12
|
+
closeSync,
|
|
13
|
+
constants as fsConstants,
|
|
12
14
|
existsSync,
|
|
15
|
+
fstatSync,
|
|
16
|
+
fsyncSync,
|
|
17
|
+
lstatSync,
|
|
18
|
+
openSync,
|
|
13
19
|
readFileSync,
|
|
20
|
+
renameSync,
|
|
14
21
|
rmSync,
|
|
15
22
|
writeFileSync,
|
|
16
23
|
} from "node:fs";
|
|
17
|
-
import {
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
25
|
+
import { basename, dirname, join } from "node:path";
|
|
18
26
|
import { iterateFileLinesReverseSync } from "../lib/file-lines.js";
|
|
19
27
|
import { ensurePrivateFile } from "../lib/secure-fs.js";
|
|
28
|
+
import { isAffectedWindowsZeroDeviceStatRuntime } from "../lib/secure-file-identity.js";
|
|
20
29
|
|
|
21
30
|
export const SESSION_INDEX_SCHEMA = 2;
|
|
22
31
|
export const SESSION_INDEX_FILE = ".sessions-index-v2.ndjson";
|
|
@@ -31,6 +40,14 @@ export const SESSION_GENERATION_AUTHORITY_SCHEMA =
|
|
|
31
40
|
// Keeping the injection immediately around the production sidecar/journal
|
|
32
41
|
// boundary makes that gate deterministic without replacing either write.
|
|
33
42
|
export const _sessionScaleFaultHooks = Object.seal({
|
|
43
|
+
afterMetaTempFsync: null,
|
|
44
|
+
afterMetaRename: null,
|
|
45
|
+
afterMetaDirectoryFsync: null,
|
|
46
|
+
afterTombstoneTempFsync: null,
|
|
47
|
+
afterTombstoneRename: null,
|
|
48
|
+
afterTombstoneDirectoryFsync: null,
|
|
49
|
+
afterTombstoneRemoval: null,
|
|
50
|
+
afterTombstoneRemovalDirectoryFsync: null,
|
|
34
51
|
afterMetaSnapshot: null,
|
|
35
52
|
});
|
|
36
53
|
|
|
@@ -40,6 +57,157 @@ function runSessionScaleFaultHook(name, payload) {
|
|
|
40
57
|
if (typeof hook === "function") hook(payload);
|
|
41
58
|
}
|
|
42
59
|
|
|
60
|
+
function annotateSnapshotFailure(
|
|
61
|
+
cause,
|
|
62
|
+
{ targetPath, renamed, directorySynced },
|
|
63
|
+
) {
|
|
64
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
65
|
+
if (!error.commitState) {
|
|
66
|
+
// Before rename the public snapshot is unchanged. POSIX needs the parent
|
|
67
|
+
// directory fsync to make the rename durable; Windows exposes no usable
|
|
68
|
+
// directory fsync through Node, so a flushed atomic rename is its complete
|
|
69
|
+
// host boundary. A hook after that boundary models response loss.
|
|
70
|
+
error.commitState = !renamed
|
|
71
|
+
? "not-committed"
|
|
72
|
+
: directorySynced
|
|
73
|
+
? "committed"
|
|
74
|
+
: "unknown";
|
|
75
|
+
}
|
|
76
|
+
if (!error.persistenceTarget) error.persistenceTarget = targetPath;
|
|
77
|
+
return error;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function syncParentDirectory(targetPath) {
|
|
81
|
+
if (process.platform === "win32") return false;
|
|
82
|
+
let descriptor = null;
|
|
83
|
+
try {
|
|
84
|
+
const flags =
|
|
85
|
+
fsConstants.O_RDONLY |
|
|
86
|
+
Number(fsConstants.O_DIRECTORY ?? 0) |
|
|
87
|
+
Number(fsConstants.O_NOFOLLOW ?? 0);
|
|
88
|
+
descriptor = openSync(dirname(targetPath), flags);
|
|
89
|
+
if (!fstatSync(descriptor).isDirectory()) {
|
|
90
|
+
throw new Error("Session snapshot parent is not a directory");
|
|
91
|
+
}
|
|
92
|
+
fsyncSync(descriptor);
|
|
93
|
+
return true;
|
|
94
|
+
} finally {
|
|
95
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function assertSnapshotPathMatchesDescriptor(
|
|
100
|
+
filePath,
|
|
101
|
+
descriptorStats,
|
|
102
|
+
serialized,
|
|
103
|
+
) {
|
|
104
|
+
const published = lstatSync(filePath, { bigint: true });
|
|
105
|
+
const usefulIdentity =
|
|
106
|
+
descriptorStats.dev !== 0n ||
|
|
107
|
+
descriptorStats.ino !== 0n ||
|
|
108
|
+
published.dev !== 0n ||
|
|
109
|
+
published.ino !== 0n;
|
|
110
|
+
const deviceMatches =
|
|
111
|
+
String(descriptorStats.dev) === String(published.dev) ||
|
|
112
|
+
(isAffectedWindowsZeroDeviceStatRuntime() &&
|
|
113
|
+
String(descriptorStats.dev) !== "0" &&
|
|
114
|
+
String(published.dev) === "0");
|
|
115
|
+
const identityMatches =
|
|
116
|
+
deviceMatches &&
|
|
117
|
+
String(descriptorStats.ino) === String(published.ino) &&
|
|
118
|
+
String(descriptorStats.mode) === String(published.mode);
|
|
119
|
+
const expectedBytes = BigInt(Buffer.byteLength(serialized, "utf8"));
|
|
120
|
+
if (
|
|
121
|
+
published.isSymbolicLink() ||
|
|
122
|
+
!published.isFile() ||
|
|
123
|
+
(usefulIdentity && !identityMatches) ||
|
|
124
|
+
descriptorStats.size !== expectedBytes ||
|
|
125
|
+
published.size !== expectedBytes ||
|
|
126
|
+
readFileSync(filePath, "utf8") !== serialized
|
|
127
|
+
) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"Session snapshot path no longer identifies the flushed payload",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Publish one authority snapshot without ever exposing a partial JSON file.
|
|
136
|
+
* The temporary file lives beside the destination so rename remains atomic.
|
|
137
|
+
* Activity ordering is intentionally outside this helper: that journal is a
|
|
138
|
+
* rebuildable cache and must not be confused with the durable snapshot.
|
|
139
|
+
*/
|
|
140
|
+
function writeSnapshotDurably(
|
|
141
|
+
filePath,
|
|
142
|
+
serialized,
|
|
143
|
+
{ kind, sessionId, status, afterTempFsync, afterRename, afterDirectoryFsync },
|
|
144
|
+
) {
|
|
145
|
+
const temporaryPath = join(
|
|
146
|
+
dirname(filePath),
|
|
147
|
+
`.${basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
|
|
148
|
+
);
|
|
149
|
+
const payload = Object.freeze({
|
|
150
|
+
kind,
|
|
151
|
+
sessionId,
|
|
152
|
+
status,
|
|
153
|
+
targetPath: filePath,
|
|
154
|
+
temporaryPath,
|
|
155
|
+
});
|
|
156
|
+
let descriptor = null;
|
|
157
|
+
let descriptorStats = null;
|
|
158
|
+
let renamed = false;
|
|
159
|
+
let directorySynced = process.platform === "win32";
|
|
160
|
+
try {
|
|
161
|
+
descriptor = openSync(temporaryPath, "wx", 0o600);
|
|
162
|
+
writeFileSync(descriptor, serialized, "utf8");
|
|
163
|
+
ensurePrivateFile(temporaryPath);
|
|
164
|
+
fsyncSync(descriptor);
|
|
165
|
+
descriptorStats = fstatSync(descriptor, { bigint: true });
|
|
166
|
+
runSessionScaleFaultHook(afterTempFsync, payload);
|
|
167
|
+
assertSnapshotPathMatchesDescriptor(
|
|
168
|
+
temporaryPath,
|
|
169
|
+
descriptorStats,
|
|
170
|
+
serialized,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Both files are in the same directory; rename atomically replaces an old
|
|
174
|
+
// snapshot or publishes a first generation without a partial target.
|
|
175
|
+
// The temp already has the final owner-only permissions before publication.
|
|
176
|
+
renameSync(temporaryPath, filePath);
|
|
177
|
+
renamed = true;
|
|
178
|
+
assertSnapshotPathMatchesDescriptor(filePath, descriptorStats, serialized);
|
|
179
|
+
closeSync(descriptor);
|
|
180
|
+
descriptor = null;
|
|
181
|
+
runSessionScaleFaultHook(afterRename, payload);
|
|
182
|
+
if (process.platform !== "win32") {
|
|
183
|
+
directorySynced = syncParentDirectory(filePath);
|
|
184
|
+
runSessionScaleFaultHook(afterDirectoryFsync, payload);
|
|
185
|
+
}
|
|
186
|
+
} catch (cause) {
|
|
187
|
+
throw annotateSnapshotFailure(cause, {
|
|
188
|
+
targetPath: filePath,
|
|
189
|
+
renamed,
|
|
190
|
+
directorySynced,
|
|
191
|
+
});
|
|
192
|
+
} finally {
|
|
193
|
+
if (descriptor !== null) {
|
|
194
|
+
try {
|
|
195
|
+
closeSync(descriptor);
|
|
196
|
+
} catch {
|
|
197
|
+
// Preserve the persistence failure that stopped publication.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!renamed) {
|
|
201
|
+
try {
|
|
202
|
+
rmSync(temporaryPath, { force: true });
|
|
203
|
+
} catch {
|
|
204
|
+
// A crash may leave an ignored same-directory temp file. Never replace
|
|
205
|
+
// the original persistence failure with best-effort cleanup failure.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
43
211
|
export function sessionMetaPath(dir, sessionId) {
|
|
44
212
|
return join(dir, `${sessionId}.meta.json`);
|
|
45
213
|
}
|
|
@@ -189,6 +357,7 @@ function writeMetaSnapshot(dir, meta) {
|
|
|
189
357
|
// that publication fails. Remove it only after a live generation snapshot
|
|
190
358
|
// commits, so crashes can cause an extra candidate read but never hide a
|
|
191
359
|
// successfully committed tombstone.
|
|
360
|
+
let tombstonePublished = false;
|
|
192
361
|
if (meta.deleted === true) {
|
|
193
362
|
const generation = normalizeSessionGenerationAuthority(meta.generation);
|
|
194
363
|
const marker = {
|
|
@@ -206,21 +375,71 @@ function writeMetaSnapshot(dir, meta) {
|
|
|
206
375
|
Number(meta.deleted_at_ms || meta.updated_at_ms) || 0,
|
|
207
376
|
),
|
|
208
377
|
};
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
378
|
+
writeSnapshotDurably(tombstoneMarker, `${JSON.stringify(marker)}\n`, {
|
|
379
|
+
kind: "tombstone",
|
|
380
|
+
sessionId: meta.id,
|
|
381
|
+
status: "deleted",
|
|
382
|
+
afterTempFsync: "afterTombstoneTempFsync",
|
|
383
|
+
afterRename: "afterTombstoneRename",
|
|
384
|
+
afterDirectoryFsync: "afterTombstoneDirectoryFsync",
|
|
212
385
|
});
|
|
213
|
-
|
|
386
|
+
tombstonePublished = true;
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
writeSnapshotDurably(filePath, `${JSON.stringify(meta)}\n`, {
|
|
390
|
+
kind: "meta",
|
|
391
|
+
sessionId: meta.id,
|
|
392
|
+
status: meta.deleted === true ? "deleted" : "live",
|
|
393
|
+
afterTempFsync: "afterMetaTempFsync",
|
|
394
|
+
afterRename: "afterMetaRename",
|
|
395
|
+
afterDirectoryFsync: "afterMetaDirectoryFsync",
|
|
396
|
+
});
|
|
397
|
+
} catch (cause) {
|
|
398
|
+
// A durable marker is already a conservative deletion fence. If the meta
|
|
399
|
+
// publication then fails, the composite mutation is partial/unknown even
|
|
400
|
+
// when the old meta path itself was left byte-for-byte unchanged.
|
|
401
|
+
if (tombstonePublished && cause?.commitState === "not-committed") {
|
|
402
|
+
cause.commitState = "unknown";
|
|
403
|
+
}
|
|
404
|
+
throw cause;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (meta.deleted !== true && existsSync(tombstoneMarker)) {
|
|
408
|
+
const payload = Object.freeze({
|
|
409
|
+
kind: "tombstone-removal",
|
|
410
|
+
sessionId: meta.id,
|
|
411
|
+
status: "live",
|
|
412
|
+
targetPath: tombstoneMarker,
|
|
413
|
+
parentDirectory: dirname(tombstoneMarker),
|
|
414
|
+
});
|
|
415
|
+
let removed = false;
|
|
416
|
+
let directorySynced = process.platform === "win32";
|
|
417
|
+
try {
|
|
418
|
+
rmSync(tombstoneMarker, { force: true });
|
|
419
|
+
removed = true;
|
|
420
|
+
runSessionScaleFaultHook("afterTombstoneRemoval", payload);
|
|
421
|
+
if (process.platform !== "win32") {
|
|
422
|
+
directorySynced = syncParentDirectory(tombstoneMarker);
|
|
423
|
+
runSessionScaleFaultHook(
|
|
424
|
+
"afterTombstoneRemovalDirectoryFsync",
|
|
425
|
+
payload,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
} catch (cause) {
|
|
429
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
430
|
+
if (!error.commitState) {
|
|
431
|
+
// The live meta was already durably published. Retaining the marker is
|
|
432
|
+
// fail-closed; removing it without a POSIX directory barrier is an
|
|
433
|
+
// unknown publication, while a post-barrier hook is response loss.
|
|
434
|
+
error.commitState =
|
|
435
|
+
removed && directorySynced ? "committed" : "unknown";
|
|
436
|
+
}
|
|
437
|
+
if (!error.persistenceTarget) {
|
|
438
|
+
error.persistenceTarget = tombstoneMarker;
|
|
439
|
+
}
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
214
442
|
}
|
|
215
|
-
// The owning transcript lock serializes this derived snapshot. It need not
|
|
216
|
-
// use a second temp+rename protocol: interruption can only corrupt a cache,
|
|
217
|
-
// which readSessionMeta rejects and rebuildSessionMeta regenerates.
|
|
218
|
-
writeFileSync(filePath, `${JSON.stringify(meta)}\n`, {
|
|
219
|
-
encoding: "utf8",
|
|
220
|
-
mode: 0o600,
|
|
221
|
-
});
|
|
222
|
-
ensurePrivateFile(filePath);
|
|
223
|
-
if (meta.deleted !== true) rmSync(tombstoneMarker, { force: true });
|
|
224
443
|
}
|
|
225
444
|
|
|
226
445
|
/**
|
|
@@ -266,6 +266,12 @@ export function describeAuthorityChain(envelope = {}) {
|
|
|
266
266
|
const parts = [`origin=${envelope.origin || "unknown"}`];
|
|
267
267
|
if (envelope.principalId) parts.push(`principal=${envelope.principalId}`);
|
|
268
268
|
if (envelope.sessionId) parts.push(`session=${envelope.sessionId}`);
|
|
269
|
+
if (envelope.remoteSessionId)
|
|
270
|
+
parts.push(`remote-session=${envelope.remoteSessionId}`);
|
|
271
|
+
if (envelope.sessionEpoch)
|
|
272
|
+
parts.push(`session-epoch=${envelope.sessionEpoch}`);
|
|
273
|
+
if (envelope.membershipEpoch)
|
|
274
|
+
parts.push(`membership-epoch=${envelope.membershipEpoch}`);
|
|
269
275
|
if (envelope.parentAgentId) parts.push(`parent=${envelope.parentAgentId}`);
|
|
270
276
|
if (envelope.correlationId) parts.push(`corr=${envelope.correlationId}`);
|
|
271
277
|
parts.push(`authority=${authorityForOrigin(envelope.origin, envelope)}`);
|
|
@@ -284,6 +284,31 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
|
|
|
284
284
|
const isActive =
|
|
285
285
|
typeof opts.isActive === "function" ? opts.isActive : () => true;
|
|
286
286
|
let confirm = null;
|
|
287
|
+
let authorizationConsumerRequired = false;
|
|
288
|
+
const authorityConfig = Object.freeze({
|
|
289
|
+
map: Object.freeze(
|
|
290
|
+
Object.fromEntries(
|
|
291
|
+
RISK_LEVELS.map((riskLevel) => [
|
|
292
|
+
riskLevel,
|
|
293
|
+
Object.freeze({
|
|
294
|
+
decision: map[riskLevel]?.decision || null,
|
|
295
|
+
reason: map[riskLevel]?.reason || null,
|
|
296
|
+
source: map[riskLevel]?.source || null,
|
|
297
|
+
}),
|
|
298
|
+
]),
|
|
299
|
+
),
|
|
300
|
+
),
|
|
301
|
+
rules: Object.freeze(
|
|
302
|
+
rules.map((rule) =>
|
|
303
|
+
Object.freeze({
|
|
304
|
+
decision: rule.decision || null,
|
|
305
|
+
reason: rule.reason || null,
|
|
306
|
+
source: rule.source || null,
|
|
307
|
+
match: rule.match ? Object.freeze({ ...rule.match }) : null,
|
|
308
|
+
}),
|
|
309
|
+
),
|
|
310
|
+
),
|
|
311
|
+
});
|
|
287
312
|
|
|
288
313
|
return {
|
|
289
314
|
isAutoModeGate: true,
|
|
@@ -303,6 +328,37 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
|
|
|
303
328
|
hasConfirmer() {
|
|
304
329
|
return typeof confirm === "function";
|
|
305
330
|
},
|
|
331
|
+
setAuthorizationConsumer(fn) {
|
|
332
|
+
authorizationConsumerRequired = typeof fn === "function";
|
|
333
|
+
return inner?.setAuthorizationConsumer?.(fn);
|
|
334
|
+
},
|
|
335
|
+
hasAuthorizationConsumer() {
|
|
336
|
+
return authorizationConsumerRequired;
|
|
337
|
+
},
|
|
338
|
+
getAuthorizationPolicySnapshot(sessionId = null) {
|
|
339
|
+
return Object.freeze({
|
|
340
|
+
schema: "chainlesschain.approval-policy-authority/v1",
|
|
341
|
+
kind: "auto-mode",
|
|
342
|
+
active: isActive() === true,
|
|
343
|
+
config: authorityConfig,
|
|
344
|
+
inner:
|
|
345
|
+
inner?.getAuthorizationPolicySnapshot?.(sessionId) ||
|
|
346
|
+
Object.freeze({
|
|
347
|
+
schema: "chainlesschain.approval-policy-authority/v1",
|
|
348
|
+
kind: "unobserved-inner",
|
|
349
|
+
sessionId: sessionId ? String(sessionId) : null,
|
|
350
|
+
}),
|
|
351
|
+
});
|
|
352
|
+
},
|
|
353
|
+
consumeAuthorization(authorization, ctx) {
|
|
354
|
+
if (typeof inner?.consumeAuthorization !== "function") {
|
|
355
|
+
throw new Error("Approval authorization consumer is unavailable");
|
|
356
|
+
}
|
|
357
|
+
return inner.consumeAuthorization(authorization, ctx);
|
|
358
|
+
},
|
|
359
|
+
createAuthorizationBinder() {
|
|
360
|
+
return inner?.createAuthorizationBinder?.() || null;
|
|
361
|
+
},
|
|
306
362
|
async decide(ctx = {}) {
|
|
307
363
|
if (!isActive()) return inner.decide(ctx);
|
|
308
364
|
const riskLevel = RISK_LEVELS.includes(ctx.riskLevel)
|
|
@@ -365,7 +421,15 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
|
|
|
365
421
|
}
|
|
366
422
|
// ask — same confirm semantics as the session-core gate: no confirmer
|
|
367
423
|
// means fail-closed deny (headless installs a deny-confirmer anyway).
|
|
368
|
-
|
|
424
|
+
// Snapshot both mutable callbacks before the first await. A concurrent
|
|
425
|
+
// stop/start or session reconfiguration must not retarget an in-flight
|
|
426
|
+
// remote approval to a different authorization consumer generation.
|
|
427
|
+
const confirmer = confirm;
|
|
428
|
+
const consumerRequired = authorizationConsumerRequired;
|
|
429
|
+
const bindAuthorization = consumerRequired
|
|
430
|
+
? inner?.createAuthorizationBinder?.()
|
|
431
|
+
: null;
|
|
432
|
+
if (typeof confirmer !== "function") {
|
|
369
433
|
return {
|
|
370
434
|
decision: "deny",
|
|
371
435
|
via: "no-confirmer",
|
|
@@ -374,8 +438,38 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
|
|
|
374
438
|
};
|
|
375
439
|
}
|
|
376
440
|
let ok = false;
|
|
441
|
+
let authorization = null;
|
|
442
|
+
let confirmationVia = null;
|
|
377
443
|
try {
|
|
378
|
-
|
|
444
|
+
const confirmation = await confirmer(ctx);
|
|
445
|
+
if (confirmation && typeof confirmation === "object") {
|
|
446
|
+
ok = confirmation.approved === true;
|
|
447
|
+
confirmationVia = confirmation.via || null;
|
|
448
|
+
if (ok && !confirmation.authorization) {
|
|
449
|
+
return {
|
|
450
|
+
decision: "deny",
|
|
451
|
+
via: "authorization-missing",
|
|
452
|
+
base: "confirm",
|
|
453
|
+
...common,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (
|
|
457
|
+
ok &&
|
|
458
|
+
(!consumerRequired || typeof bindAuthorization !== "function")
|
|
459
|
+
) {
|
|
460
|
+
return {
|
|
461
|
+
decision: "deny",
|
|
462
|
+
via: "authorization-consumer-missing",
|
|
463
|
+
base: "confirm",
|
|
464
|
+
...common,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (ok) {
|
|
468
|
+
authorization = bindAuthorization(confirmation.authorization);
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
ok = confirmation === true;
|
|
472
|
+
}
|
|
379
473
|
} catch (error) {
|
|
380
474
|
return {
|
|
381
475
|
decision: "deny",
|
|
@@ -387,8 +481,9 @@ export function createAutoModeApprovalGate(inner, resolved, opts = {}) {
|
|
|
387
481
|
}
|
|
388
482
|
return {
|
|
389
483
|
decision: ok ? "allow" : "deny",
|
|
390
|
-
via: ok ? "user-confirm" : "user-deny",
|
|
484
|
+
via: confirmationVia || (ok ? "user-confirm" : "user-deny"),
|
|
391
485
|
base: "confirm",
|
|
486
|
+
...(authorization ? { authorization } : {}),
|
|
392
487
|
...common,
|
|
393
488
|
};
|
|
394
489
|
},
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { createNdjsonReader } from "./background-session-transport.js";
|
|
4
|
+
import {
|
|
5
|
+
BACKGROUND_AGENT_KEEPER_ARM,
|
|
6
|
+
BACKGROUND_AGENT_KEEPER_ARMED,
|
|
7
|
+
BACKGROUND_AGENT_KEEPER_HELLO,
|
|
8
|
+
BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
|
|
9
|
+
BACKGROUND_AGENT_KEEPER_READY,
|
|
10
|
+
BACKGROUND_AGENT_KEEPER_RETIRE,
|
|
11
|
+
BACKGROUND_AGENT_KEEPER_RETIRED,
|
|
12
|
+
createBackgroundAgentKeeperMessage,
|
|
13
|
+
normalizeBackgroundAgentKeeperHello,
|
|
14
|
+
normalizeBackgroundAgentKeeperTurn,
|
|
15
|
+
resolveBackgroundAgentKeeperRetireTimeoutMs,
|
|
16
|
+
sameBackgroundAgentKeeperTurn,
|
|
17
|
+
} from "./background-agent-keeper-protocol.js";
|
|
18
|
+
|
|
19
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 30_000;
|
|
20
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
|
21
|
+
|
|
22
|
+
function keeperError(message, code = "BACKGROUND_AGENT_KEEPER_UNAVAILABLE") {
|
|
23
|
+
const error = new Error(message);
|
|
24
|
+
error.code = code;
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function writeMessage(socket, message) {
|
|
29
|
+
if (!socket || socket.destroyed) {
|
|
30
|
+
throw keeperError("background agent keeper channel is closed");
|
|
31
|
+
}
|
|
32
|
+
socket.write(`${JSON.stringify(message)}\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function connectOnce(pipePath) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const socket = net.createConnection(pipePath);
|
|
38
|
+
const onError = (error) => {
|
|
39
|
+
socket.destroy();
|
|
40
|
+
reject(error);
|
|
41
|
+
};
|
|
42
|
+
socket.once("error", onError);
|
|
43
|
+
socket.once("connect", () => {
|
|
44
|
+
socket.removeListener("error", onError);
|
|
45
|
+
resolve(socket);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function connectWithDeadline(pipePath, timeoutMs) {
|
|
51
|
+
const deadline = Date.now() + timeoutMs;
|
|
52
|
+
let lastError = null;
|
|
53
|
+
while (Date.now() < deadline) {
|
|
54
|
+
try {
|
|
55
|
+
return await connectOnce(pipePath);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
lastError = error;
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw keeperError(
|
|
62
|
+
`timed out connecting to background agent keeper: ${lastError?.message || "unavailable"}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function connectBackgroundAgentKeeper(options = {}) {
|
|
67
|
+
const hello = normalizeBackgroundAgentKeeperHello(options);
|
|
68
|
+
const connectTimeoutMs = Math.max(
|
|
69
|
+
1,
|
|
70
|
+
Number(options.connectTimeoutMs) || DEFAULT_CONNECT_TIMEOUT_MS,
|
|
71
|
+
);
|
|
72
|
+
const requestTimeoutMs = Math.max(
|
|
73
|
+
1,
|
|
74
|
+
Number(options.requestTimeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS,
|
|
75
|
+
);
|
|
76
|
+
const retireTimeoutMs = resolveBackgroundAgentKeeperRetireTimeoutMs(
|
|
77
|
+
options.retireTimeoutMs,
|
|
78
|
+
);
|
|
79
|
+
const socket = await connectWithDeadline(options.pipePath, connectTimeoutMs);
|
|
80
|
+
const pending = new Map();
|
|
81
|
+
let ready = false;
|
|
82
|
+
let closing = false;
|
|
83
|
+
let activeTurn = null;
|
|
84
|
+
let readyResolve;
|
|
85
|
+
let readyReject;
|
|
86
|
+
const readyPromise = new Promise((resolve, reject) => {
|
|
87
|
+
readyResolve = resolve;
|
|
88
|
+
readyReject = reject;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const failPending = (error) => {
|
|
92
|
+
readyReject(error);
|
|
93
|
+
for (const entry of pending.values()) {
|
|
94
|
+
clearTimeout(entry.timer);
|
|
95
|
+
entry.reject(error);
|
|
96
|
+
}
|
|
97
|
+
pending.clear();
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
socket.on(
|
|
101
|
+
"data",
|
|
102
|
+
createNdjsonReader(
|
|
103
|
+
(message) => {
|
|
104
|
+
if (
|
|
105
|
+
message?.protocolVersion !== BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION
|
|
106
|
+
) {
|
|
107
|
+
socket.destroy(
|
|
108
|
+
keeperError("background agent keeper protocol mismatch"),
|
|
109
|
+
);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!ready) {
|
|
113
|
+
if (
|
|
114
|
+
message.type !== BACKGROUND_AGENT_KEEPER_READY ||
|
|
115
|
+
message.id !== hello.id ||
|
|
116
|
+
message.workerGeneration !== hello.workerGeneration
|
|
117
|
+
) {
|
|
118
|
+
socket.destroy(
|
|
119
|
+
keeperError("background agent keeper ready binding mismatch"),
|
|
120
|
+
);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
ready = true;
|
|
124
|
+
readyResolve();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const entry = pending.get(message.requestId);
|
|
128
|
+
if (!entry) return;
|
|
129
|
+
if (message.type === "background-agent-keeper-error") {
|
|
130
|
+
pending.delete(message.requestId);
|
|
131
|
+
clearTimeout(entry.timer);
|
|
132
|
+
entry.reject(
|
|
133
|
+
keeperError(
|
|
134
|
+
message.message || "background agent keeper rejected request",
|
|
135
|
+
message.code || "BACKGROUND_AGENT_KEEPER_REJECTED",
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (message.type !== entry.expectedType) return;
|
|
141
|
+
if (!sameBackgroundAgentKeeperTurn(message, entry.turn)) {
|
|
142
|
+
socket.destroy(
|
|
143
|
+
keeperError("background agent keeper response binding mismatch"),
|
|
144
|
+
);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
pending.delete(message.requestId);
|
|
148
|
+
clearTimeout(entry.timer);
|
|
149
|
+
entry.resolve(message);
|
|
150
|
+
},
|
|
151
|
+
(error) => socket.destroy(error),
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
socket.on("error", (error) => failPending(error));
|
|
155
|
+
socket.on("close", () => {
|
|
156
|
+
const error = keeperError("background agent keeper channel disconnected");
|
|
157
|
+
failPending(error);
|
|
158
|
+
if (!closing) options.onDisconnect?.(error, activeTurn);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
writeMessage(
|
|
162
|
+
socket,
|
|
163
|
+
createBackgroundAgentKeeperMessage(BACKGROUND_AGENT_KEEPER_HELLO, hello),
|
|
164
|
+
);
|
|
165
|
+
const helloTimer = setTimeout(
|
|
166
|
+
() =>
|
|
167
|
+
socket.destroy(
|
|
168
|
+
keeperError("timed out waiting for background agent keeper ready"),
|
|
169
|
+
),
|
|
170
|
+
requestTimeoutMs,
|
|
171
|
+
);
|
|
172
|
+
helloTimer.unref?.();
|
|
173
|
+
await readyPromise.finally(() => clearTimeout(helloTimer));
|
|
174
|
+
|
|
175
|
+
const request = (
|
|
176
|
+
type,
|
|
177
|
+
expectedType,
|
|
178
|
+
turnValue,
|
|
179
|
+
timeoutMs = requestTimeoutMs,
|
|
180
|
+
) => {
|
|
181
|
+
const turn = normalizeBackgroundAgentKeeperTurn(turnValue);
|
|
182
|
+
const requestId = randomBytes(16).toString("hex");
|
|
183
|
+
return new Promise((resolve, reject) => {
|
|
184
|
+
const timer = setTimeout(() => {
|
|
185
|
+
pending.delete(requestId);
|
|
186
|
+
reject(
|
|
187
|
+
keeperError(
|
|
188
|
+
`timed out waiting for background agent keeper ${type} after ${timeoutMs}ms`,
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
}, timeoutMs);
|
|
192
|
+
timer.unref?.();
|
|
193
|
+
pending.set(requestId, { expectedType, reject, resolve, timer, turn });
|
|
194
|
+
try {
|
|
195
|
+
writeMessage(
|
|
196
|
+
socket,
|
|
197
|
+
createBackgroundAgentKeeperMessage(type, { requestId, ...turn }),
|
|
198
|
+
);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
pending.delete(requestId);
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
reject(error);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return Object.freeze({
|
|
208
|
+
async arm(turn) {
|
|
209
|
+
const normalized = normalizeBackgroundAgentKeeperTurn(turn);
|
|
210
|
+
if (activeTurn) {
|
|
211
|
+
throw keeperError(
|
|
212
|
+
"background agent keeper is already armed",
|
|
213
|
+
"BACKGROUND_AGENT_KEEPER_BUSY",
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
await request(
|
|
217
|
+
BACKGROUND_AGENT_KEEPER_ARM,
|
|
218
|
+
BACKGROUND_AGENT_KEEPER_ARMED,
|
|
219
|
+
normalized,
|
|
220
|
+
);
|
|
221
|
+
activeTurn = normalized;
|
|
222
|
+
return normalized;
|
|
223
|
+
},
|
|
224
|
+
async retire(turn) {
|
|
225
|
+
const normalized = normalizeBackgroundAgentKeeperTurn(turn);
|
|
226
|
+
if (!sameBackgroundAgentKeeperTurn(normalized, activeTurn)) {
|
|
227
|
+
throw keeperError(
|
|
228
|
+
"background agent keeper retire binding mismatch",
|
|
229
|
+
"BACKGROUND_AGENT_KEEPER_BINDING_MISMATCH",
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
await request(
|
|
233
|
+
BACKGROUND_AGENT_KEEPER_RETIRE,
|
|
234
|
+
BACKGROUND_AGENT_KEEPER_RETIRED,
|
|
235
|
+
normalized,
|
|
236
|
+
retireTimeoutMs,
|
|
237
|
+
);
|
|
238
|
+
activeTurn = null;
|
|
239
|
+
return true;
|
|
240
|
+
},
|
|
241
|
+
close() {
|
|
242
|
+
if (closing) return;
|
|
243
|
+
closing = true;
|
|
244
|
+
socket.end();
|
|
245
|
+
},
|
|
246
|
+
activeTurn: () => activeTurn,
|
|
247
|
+
});
|
|
248
|
+
}
|