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,765 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Physical append-only, hash-chained persistence for canonical Team merge
|
|
3
|
+
* reviews. Each line is one complete, strict event containing the resulting
|
|
4
|
+
* content-minimized review snapshot. A torn, reordered, truncated or edited
|
|
5
|
+
* line fails closed during replay.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { TextDecoder } from "node:util";
|
|
11
|
+
import { withFileLock } from "../with-file-lock.js";
|
|
12
|
+
import { ensurePrivateFile } from "../secure-fs.js";
|
|
13
|
+
import {
|
|
14
|
+
sameFileStatIdentity,
|
|
15
|
+
samePathHandleFileIdentity,
|
|
16
|
+
SecureFileIdentityError,
|
|
17
|
+
withTrustedFileParentSync,
|
|
18
|
+
} from "../secure-file-identity.js";
|
|
19
|
+
import {
|
|
20
|
+
applyMergeReviewDecision,
|
|
21
|
+
assertMergeReviewSuccessor,
|
|
22
|
+
canonicalMergeReviewJson,
|
|
23
|
+
digestMergeReview,
|
|
24
|
+
TEAM_MERGE_REVIEW_SCHEMA,
|
|
25
|
+
TEAM_MERGE_REVIEW_SCHEMA_VERSION,
|
|
26
|
+
validateMergeReview,
|
|
27
|
+
transitionMergeReview,
|
|
28
|
+
} from "./team-merge-review.js";
|
|
29
|
+
|
|
30
|
+
export const TEAM_MERGE_REVIEW_STORE_EVENT_VERSION = 1;
|
|
31
|
+
export const TEAM_MERGE_REVIEW_STORE_MAX_BYTES = 64 * 1024 * 1024;
|
|
32
|
+
export const TEAM_MERGE_REVIEW_STORE_MAX_EVENTS = 10_000;
|
|
33
|
+
export const TEAM_MERGE_REVIEW_STORE_MAX_EVENT_BYTES = 8 * 1024 * 1024;
|
|
34
|
+
|
|
35
|
+
export const TEAM_MERGE_REVIEW_STORE_ERROR = Object.freeze({
|
|
36
|
+
INVALID: "TEAM_MERGE_REVIEW_STORE_INVALID",
|
|
37
|
+
UNSAFE_PATH: "TEAM_MERGE_REVIEW_STORE_UNSAFE_PATH",
|
|
38
|
+
LOCK_UNAVAILABLE: "TEAM_MERGE_REVIEW_STORE_LOCK_UNAVAILABLE",
|
|
39
|
+
READ_FAILED: "TEAM_MERGE_REVIEW_STORE_READ_FAILED",
|
|
40
|
+
WRITE_FAILED: "TEAM_MERGE_REVIEW_STORE_WRITE_FAILED",
|
|
41
|
+
CORRUPT: "TEAM_MERGE_REVIEW_STORE_CORRUPT",
|
|
42
|
+
NOT_FOUND: "TEAM_MERGE_REVIEW_STORE_NOT_FOUND",
|
|
43
|
+
CONFLICT: "TEAM_MERGE_REVIEW_STORE_CONFLICT",
|
|
44
|
+
STALE: "TEAM_MERGE_REVIEW_STORE_STALE",
|
|
45
|
+
LIMIT: "TEAM_MERGE_REVIEW_STORE_LIMIT",
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const EVENT_TYPES = new Set([
|
|
49
|
+
"review.created",
|
|
50
|
+
"review.decided",
|
|
51
|
+
"review.transitioned",
|
|
52
|
+
]);
|
|
53
|
+
const EVENT_KEYS = new Set([
|
|
54
|
+
"schema",
|
|
55
|
+
"schemaVersion",
|
|
56
|
+
"eventVersion",
|
|
57
|
+
"sequence",
|
|
58
|
+
"type",
|
|
59
|
+
"at",
|
|
60
|
+
"reviewId",
|
|
61
|
+
"previousDigest",
|
|
62
|
+
"review",
|
|
63
|
+
"digest",
|
|
64
|
+
]);
|
|
65
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
66
|
+
|
|
67
|
+
export class TeamMergeReviewStoreError extends Error {
|
|
68
|
+
constructor(code, message, details = {}, cause = null) {
|
|
69
|
+
super(message, cause ? { cause } : undefined);
|
|
70
|
+
this.name = "TeamMergeReviewStoreError";
|
|
71
|
+
this.code = code;
|
|
72
|
+
for (const [key, value] of Object.entries(details)) {
|
|
73
|
+
if (value !== undefined && value !== null) this[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function storeError(code, message, details = {}, cause = null) {
|
|
79
|
+
return new TeamMergeReviewStoreError(code, message, details, cause);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isStoreError(error) {
|
|
83
|
+
return Object.values(TEAM_MERGE_REVIEW_STORE_ERROR).includes(error?.code);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function assertPlainObject(value, label) {
|
|
87
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
88
|
+
throw storeError(
|
|
89
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
90
|
+
`${label} must be an object`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const prototype = Object.getPrototypeOf(value);
|
|
94
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
95
|
+
throw storeError(
|
|
96
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
97
|
+
`${label} must be a plain object`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
101
|
+
if (
|
|
102
|
+
Reflect.ownKeys(value).some((key) => typeof key !== "string") ||
|
|
103
|
+
Object.values(descriptors).some(
|
|
104
|
+
(descriptor) =>
|
|
105
|
+
descriptor.get || descriptor.set || descriptor.enumerable !== true,
|
|
106
|
+
)
|
|
107
|
+
) {
|
|
108
|
+
throw storeError(
|
|
109
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
110
|
+
`${label} must not contain symbols or accessors`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function assertExactKeys(value, expected, label) {
|
|
116
|
+
assertPlainObject(value, label);
|
|
117
|
+
const actual = Reflect.ownKeys(value);
|
|
118
|
+
if (
|
|
119
|
+
actual.length !== expected.size ||
|
|
120
|
+
actual.some((key) => !expected.has(key))
|
|
121
|
+
) {
|
|
122
|
+
throw storeError(
|
|
123
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
124
|
+
`${label} has unexpected or missing fields`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function clone(value) {
|
|
130
|
+
return JSON.parse(JSON.stringify(value));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function eventMaterial(event) {
|
|
134
|
+
const material = { ...event };
|
|
135
|
+
delete material.digest;
|
|
136
|
+
return material;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function computeTeamMergeReviewStoreEventDigest(event) {
|
|
140
|
+
return digestMergeReview(
|
|
141
|
+
"cc-team-merge-review-store-event-v1",
|
|
142
|
+
eventMaterial(event),
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function cursor(runtime) {
|
|
147
|
+
return {
|
|
148
|
+
sequence: runtime.events.length,
|
|
149
|
+
digest: runtime.events.at(-1)?.digest || null,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function emptyRuntime() {
|
|
154
|
+
return {
|
|
155
|
+
reviews: new Map(),
|
|
156
|
+
events: [],
|
|
157
|
+
physicalBytes: 0,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function validateCursor(value, label, { nullable = false } = {}) {
|
|
162
|
+
if (nullable && value == null) return null;
|
|
163
|
+
assertPlainObject(value, label);
|
|
164
|
+
const sequence = value.sequence;
|
|
165
|
+
const digest = value.digest;
|
|
166
|
+
if (
|
|
167
|
+
typeof sequence !== "number" ||
|
|
168
|
+
!Number.isSafeInteger(sequence) ||
|
|
169
|
+
sequence < 0 ||
|
|
170
|
+
(sequence === 0 ? digest !== null : !DIGEST.test(String(digest || "")))
|
|
171
|
+
) {
|
|
172
|
+
throw storeError(
|
|
173
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.INVALID,
|
|
174
|
+
`${label} is invalid`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return { sequence, digest };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function requireCurrentCursor(runtime, expectedCursor) {
|
|
181
|
+
if (expectedCursor == null) return;
|
|
182
|
+
const expected = validateCursor(expectedCursor, "expectedCursor");
|
|
183
|
+
const current = cursor(runtime);
|
|
184
|
+
if (
|
|
185
|
+
expected.sequence !== current.sequence ||
|
|
186
|
+
expected.digest !== current.digest
|
|
187
|
+
) {
|
|
188
|
+
throw storeError(
|
|
189
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.STALE,
|
|
190
|
+
"merge-review store cursor is stale",
|
|
191
|
+
{ expectedCursor: expected, cursor: current },
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function requireAnchor(runtime, anchor) {
|
|
197
|
+
if (anchor == null) return;
|
|
198
|
+
const expected = validateCursor(anchor, "anchor");
|
|
199
|
+
if (expected.sequence > runtime.events.length) {
|
|
200
|
+
throw storeError(
|
|
201
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.STALE,
|
|
202
|
+
"merge-review store anchor is ahead of the current log",
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const digest =
|
|
206
|
+
expected.sequence === 0
|
|
207
|
+
? null
|
|
208
|
+
: runtime.events[expected.sequence - 1]?.digest || null;
|
|
209
|
+
if (digest !== expected.digest) {
|
|
210
|
+
throw storeError(
|
|
211
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.STALE,
|
|
212
|
+
"merge-review store anchor is not a prefix of the current log",
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function validateEvent(value, previousDigest, sequence) {
|
|
218
|
+
assertExactKeys(value, EVENT_KEYS, `event ${sequence}`);
|
|
219
|
+
if (
|
|
220
|
+
value.schema !== TEAM_MERGE_REVIEW_SCHEMA ||
|
|
221
|
+
value.schemaVersion !== TEAM_MERGE_REVIEW_SCHEMA_VERSION ||
|
|
222
|
+
value.eventVersion !== TEAM_MERGE_REVIEW_STORE_EVENT_VERSION ||
|
|
223
|
+
value.sequence !== sequence ||
|
|
224
|
+
!EVENT_TYPES.has(value.type) ||
|
|
225
|
+
value.previousDigest !== previousDigest ||
|
|
226
|
+
typeof value.at !== "string" ||
|
|
227
|
+
!Number.isFinite(Date.parse(value.at)) ||
|
|
228
|
+
typeof value.reviewId !== "string" ||
|
|
229
|
+
!DIGEST.test(String(value.digest || ""))
|
|
230
|
+
) {
|
|
231
|
+
throw storeError(
|
|
232
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
233
|
+
`event ${sequence} has an invalid envelope`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
if (computeTeamMergeReviewStoreEventDigest(value) !== value.digest) {
|
|
237
|
+
throw storeError(
|
|
238
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
239
|
+
`event ${sequence} digest mismatch`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
let review;
|
|
243
|
+
try {
|
|
244
|
+
review = validateMergeReview(value.review);
|
|
245
|
+
} catch (cause) {
|
|
246
|
+
throw storeError(
|
|
247
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
248
|
+
`event ${sequence} contains invalid review evidence`,
|
|
249
|
+
{ sequence },
|
|
250
|
+
cause,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (review.reviewId !== value.reviewId || review.updatedAt !== value.at) {
|
|
254
|
+
throw storeError(
|
|
255
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
256
|
+
`event ${sequence} review binding mismatch`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return { ...value, review };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function replayEvents(events) {
|
|
263
|
+
const runtime = emptyRuntime();
|
|
264
|
+
let previousDigest = null;
|
|
265
|
+
events.forEach((raw, index) => {
|
|
266
|
+
const sequence = index + 1;
|
|
267
|
+
const event = validateEvent(raw, previousDigest, sequence);
|
|
268
|
+
const previous = runtime.reviews.get(event.reviewId) || null;
|
|
269
|
+
if (event.type === "review.created") {
|
|
270
|
+
if (
|
|
271
|
+
previous ||
|
|
272
|
+
event.review.revision !== 0 ||
|
|
273
|
+
event.review.state !== "planned" ||
|
|
274
|
+
event.review.decision !== null ||
|
|
275
|
+
event.review.createdAt !== event.review.updatedAt
|
|
276
|
+
) {
|
|
277
|
+
throw storeError(
|
|
278
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
279
|
+
`event ${sequence} is not a unique planned review creation`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
if (!previous) {
|
|
284
|
+
throw storeError(
|
|
285
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
286
|
+
`event ${sequence} updates an unknown review`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
assertMergeReviewSuccessor(previous, event.review, event.type);
|
|
291
|
+
} catch (cause) {
|
|
292
|
+
throw storeError(
|
|
293
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
294
|
+
`event ${sequence} is not a legal review successor`,
|
|
295
|
+
{ sequence, reviewId: event.reviewId },
|
|
296
|
+
cause,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
runtime.reviews.set(event.reviewId, event.review);
|
|
301
|
+
runtime.events.push(event);
|
|
302
|
+
previousDigest = event.digest;
|
|
303
|
+
});
|
|
304
|
+
return runtime;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function buildEvent(runtime, type, review) {
|
|
308
|
+
const event = {
|
|
309
|
+
schema: TEAM_MERGE_REVIEW_SCHEMA,
|
|
310
|
+
schemaVersion: TEAM_MERGE_REVIEW_SCHEMA_VERSION,
|
|
311
|
+
eventVersion: TEAM_MERGE_REVIEW_STORE_EVENT_VERSION,
|
|
312
|
+
sequence: runtime.events.length + 1,
|
|
313
|
+
type,
|
|
314
|
+
at: review.updatedAt,
|
|
315
|
+
reviewId: review.reviewId,
|
|
316
|
+
previousDigest: runtime.events.at(-1)?.digest || null,
|
|
317
|
+
review,
|
|
318
|
+
digest: null,
|
|
319
|
+
};
|
|
320
|
+
event.digest = computeTeamMergeReviewStoreEventDigest(event);
|
|
321
|
+
return event;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function safeFileEntry(runtimeFs, filePath, { allowMissing = false } = {}) {
|
|
325
|
+
let entry;
|
|
326
|
+
try {
|
|
327
|
+
entry = runtimeFs.lstatSync(filePath, { bigint: true });
|
|
328
|
+
} catch (cause) {
|
|
329
|
+
if (allowMissing && cause?.code === "ENOENT") return null;
|
|
330
|
+
throw cause;
|
|
331
|
+
}
|
|
332
|
+
if (entry.isSymbolicLink() || !entry.isFile() || Number(entry.nlink) !== 1) {
|
|
333
|
+
throw storeError(
|
|
334
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
335
|
+
"merge-review store must be a regular, single-link file",
|
|
336
|
+
{ filePath },
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (process.platform !== "win32" && (Number(entry.mode) & 0o7777) !== 0o600) {
|
|
340
|
+
throw storeError(
|
|
341
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
342
|
+
"merge-review store must be owner-only (0600)",
|
|
343
|
+
{ filePath },
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return entry;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export class TeamMergeReviewStore {
|
|
350
|
+
constructor({
|
|
351
|
+
filePath,
|
|
352
|
+
runtimeFs = fs,
|
|
353
|
+
lock = withFileLock,
|
|
354
|
+
secureFileParent = withTrustedFileParentSync,
|
|
355
|
+
ensureOwnerOnlyFile = ensurePrivateFile,
|
|
356
|
+
maxBytes = TEAM_MERGE_REVIEW_STORE_MAX_BYTES,
|
|
357
|
+
maxEvents = TEAM_MERGE_REVIEW_STORE_MAX_EVENTS,
|
|
358
|
+
maxEventBytes = TEAM_MERGE_REVIEW_STORE_MAX_EVENT_BYTES,
|
|
359
|
+
lockTimeoutMs = 5000,
|
|
360
|
+
lockStaleMs = 30_000,
|
|
361
|
+
} = {}) {
|
|
362
|
+
if (typeof filePath !== "string" || !filePath.trim()) {
|
|
363
|
+
throw storeError(
|
|
364
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.INVALID,
|
|
365
|
+
"merge-review store filePath is required",
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
this.filePath = path.resolve(filePath);
|
|
369
|
+
this._fs = runtimeFs;
|
|
370
|
+
this._lock = lock;
|
|
371
|
+
this._secureFileParent = secureFileParent;
|
|
372
|
+
this._ensureOwnerOnlyFile = ensureOwnerOnlyFile;
|
|
373
|
+
this.maxBytes = Math.max(1024, Math.floor(Number(maxBytes) || 0));
|
|
374
|
+
this.maxEvents = Math.max(1, Math.floor(Number(maxEvents) || 0));
|
|
375
|
+
this.maxEventBytes = Math.max(1024, Math.floor(Number(maxEventBytes) || 0));
|
|
376
|
+
this.lockTimeoutMs = lockTimeoutMs;
|
|
377
|
+
this.lockStaleMs = lockStaleMs;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
_secureRead() {
|
|
381
|
+
try {
|
|
382
|
+
return this._secureFileParent(
|
|
383
|
+
this._fs,
|
|
384
|
+
this.filePath,
|
|
385
|
+
({ canonicalPath, parentDevice }) => {
|
|
386
|
+
const before = safeFileEntry(this._fs, canonicalPath);
|
|
387
|
+
if (Number(before.size) > this.maxBytes) {
|
|
388
|
+
throw storeError(
|
|
389
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LIMIT,
|
|
390
|
+
`merge-review store cannot exceed ${this.maxBytes} bytes`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const flags =
|
|
394
|
+
Number(this._fs.constants?.O_RDONLY ?? fs.constants.O_RDONLY) |
|
|
395
|
+
Number(
|
|
396
|
+
this._fs.constants?.O_NOFOLLOW ?? fs.constants.O_NOFOLLOW ?? 0,
|
|
397
|
+
);
|
|
398
|
+
let descriptor = null;
|
|
399
|
+
try {
|
|
400
|
+
descriptor = this._fs.openSync(canonicalPath, flags);
|
|
401
|
+
const opened = this._fs.fstatSync(descriptor, { bigint: true });
|
|
402
|
+
if (
|
|
403
|
+
!opened.isFile() ||
|
|
404
|
+
Number(opened.nlink) !== 1 ||
|
|
405
|
+
!samePathHandleFileIdentity(before, opened, parentDevice)
|
|
406
|
+
) {
|
|
407
|
+
throw storeError(
|
|
408
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
409
|
+
"merge-review store identity changed while opening",
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
const bytes = this._fs.readFileSync(descriptor);
|
|
413
|
+
const after = this._fs.fstatSync(descriptor, { bigint: true });
|
|
414
|
+
if (
|
|
415
|
+
!sameFileStatIdentity(opened, after) ||
|
|
416
|
+
Number(after.size) !== bytes.length
|
|
417
|
+
) {
|
|
418
|
+
throw storeError(
|
|
419
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
420
|
+
"merge-review store changed while reading",
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return bytes;
|
|
424
|
+
} finally {
|
|
425
|
+
if (descriptor !== null) this._fs.closeSync(descriptor);
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
);
|
|
429
|
+
} catch (cause) {
|
|
430
|
+
if (isStoreError(cause)) throw cause;
|
|
431
|
+
if (cause instanceof SecureFileIdentityError) {
|
|
432
|
+
throw storeError(
|
|
433
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
434
|
+
"merge-review store parent identity is unsafe",
|
|
435
|
+
{ filePath: this.filePath },
|
|
436
|
+
cause,
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
throw storeError(
|
|
440
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.READ_FAILED,
|
|
441
|
+
"could not securely read merge-review store",
|
|
442
|
+
{ filePath: this.filePath },
|
|
443
|
+
cause,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
_decode(bytes) {
|
|
449
|
+
if (bytes.length === 0) return emptyRuntime();
|
|
450
|
+
let text;
|
|
451
|
+
try {
|
|
452
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
453
|
+
} catch (cause) {
|
|
454
|
+
throw storeError(
|
|
455
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
456
|
+
"merge-review store is not valid UTF-8",
|
|
457
|
+
{},
|
|
458
|
+
cause,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
if (!text.endsWith("\n")) {
|
|
462
|
+
throw storeError(
|
|
463
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
464
|
+
"merge-review store has a truncated final event",
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const lines = text.slice(0, -1).split("\n");
|
|
468
|
+
if (lines.length > this.maxEvents) {
|
|
469
|
+
throw storeError(
|
|
470
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LIMIT,
|
|
471
|
+
`merge-review store exceeds ${this.maxEvents} events`,
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const events = lines.map((line, index) => {
|
|
475
|
+
if (!line || Buffer.byteLength(line, "utf8") > this.maxEventBytes) {
|
|
476
|
+
throw storeError(
|
|
477
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LIMIT,
|
|
478
|
+
`merge-review event ${index + 1} is empty or oversized`,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
const event = JSON.parse(line);
|
|
483
|
+
if (line !== canonicalMergeReviewJson(event)) {
|
|
484
|
+
throw storeError(
|
|
485
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
486
|
+
`merge-review event ${index + 1} is not canonically encoded`,
|
|
487
|
+
{ sequence: index + 1 },
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
return event;
|
|
491
|
+
} catch (cause) {
|
|
492
|
+
if (isStoreError(cause)) throw cause;
|
|
493
|
+
throw storeError(
|
|
494
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CORRUPT,
|
|
495
|
+
`merge-review event ${index + 1} is invalid JSON`,
|
|
496
|
+
{ sequence: index + 1 },
|
|
497
|
+
cause,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
const runtime = replayEvents(events);
|
|
502
|
+
runtime.physicalBytes = bytes.length;
|
|
503
|
+
return runtime;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
_readRuntime() {
|
|
507
|
+
const entry = safeFileEntry(this._fs, this.filePath, {
|
|
508
|
+
allowMissing: true,
|
|
509
|
+
});
|
|
510
|
+
return entry ? this._decode(this._secureRead()) : emptyRuntime();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
_append(runtime, type, review) {
|
|
514
|
+
if (runtime.events.length >= this.maxEvents) {
|
|
515
|
+
throw storeError(
|
|
516
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LIMIT,
|
|
517
|
+
`merge-review store cannot exceed ${this.maxEvents} events`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
const event = buildEvent(runtime, type, review);
|
|
521
|
+
const line = `${canonicalMergeReviewJson(event)}\n`;
|
|
522
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
523
|
+
if (lineBytes > this.maxEventBytes) {
|
|
524
|
+
throw storeError(
|
|
525
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LIMIT,
|
|
526
|
+
`merge-review event cannot exceed ${this.maxEventBytes} bytes`,
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const currentBytes = runtime.physicalBytes;
|
|
530
|
+
if (currentBytes + lineBytes > this.maxBytes) {
|
|
531
|
+
throw storeError(
|
|
532
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LIMIT,
|
|
533
|
+
`merge-review store cannot exceed ${this.maxBytes} bytes`,
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
this._secureFileParent(
|
|
538
|
+
this._fs,
|
|
539
|
+
this.filePath,
|
|
540
|
+
({ canonicalPath, parentDescriptor, parentDevice }) => {
|
|
541
|
+
const before = safeFileEntry(this._fs, canonicalPath, {
|
|
542
|
+
allowMissing: true,
|
|
543
|
+
});
|
|
544
|
+
if (
|
|
545
|
+
(!before && currentBytes !== 0) ||
|
|
546
|
+
(before && Number(before.size) !== currentBytes)
|
|
547
|
+
) {
|
|
548
|
+
throw storeError(
|
|
549
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.STALE,
|
|
550
|
+
"merge-review store changed before append",
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
const flags =
|
|
554
|
+
Number(this._fs.constants?.O_WRONLY ?? fs.constants.O_WRONLY) |
|
|
555
|
+
Number(this._fs.constants?.O_APPEND ?? fs.constants.O_APPEND) |
|
|
556
|
+
Number(this._fs.constants?.O_CREAT ?? fs.constants.O_CREAT) |
|
|
557
|
+
Number(
|
|
558
|
+
this._fs.constants?.O_NOFOLLOW ?? fs.constants.O_NOFOLLOW ?? 0,
|
|
559
|
+
);
|
|
560
|
+
let descriptor = null;
|
|
561
|
+
try {
|
|
562
|
+
descriptor = this._fs.openSync(canonicalPath, flags, 0o600);
|
|
563
|
+
const opened = this._fs.fstatSync(descriptor, { bigint: true });
|
|
564
|
+
if (
|
|
565
|
+
!opened.isFile() ||
|
|
566
|
+
Number(opened.nlink) !== 1 ||
|
|
567
|
+
(before &&
|
|
568
|
+
!samePathHandleFileIdentity(before, opened, parentDevice))
|
|
569
|
+
) {
|
|
570
|
+
throw storeError(
|
|
571
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
572
|
+
"merge-review store identity changed while appending",
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
this._fs.fchmodSync(descriptor, 0o600);
|
|
576
|
+
this._ensureOwnerOnlyFile(canonicalPath, {
|
|
577
|
+
applyWindowsAcl: true,
|
|
578
|
+
failIfUnavailable: true,
|
|
579
|
+
});
|
|
580
|
+
this._fs.writeFileSync(descriptor, line, "utf8");
|
|
581
|
+
this._fs.fsyncSync(descriptor);
|
|
582
|
+
const after = this._fs.fstatSync(descriptor, { bigint: true });
|
|
583
|
+
if (
|
|
584
|
+
!after.isFile() ||
|
|
585
|
+
Number(after.nlink) !== 1 ||
|
|
586
|
+
Number(after.size) !== currentBytes + lineBytes
|
|
587
|
+
) {
|
|
588
|
+
throw storeError(
|
|
589
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.WRITE_FAILED,
|
|
590
|
+
"merge-review append size verification failed",
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
if (process.platform !== "win32") {
|
|
594
|
+
this._fs.fsyncSync(parentDescriptor);
|
|
595
|
+
}
|
|
596
|
+
} finally {
|
|
597
|
+
if (descriptor !== null) this._fs.closeSync(descriptor);
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
);
|
|
601
|
+
} catch (cause) {
|
|
602
|
+
if (isStoreError(cause)) throw cause;
|
|
603
|
+
if (cause instanceof SecureFileIdentityError) {
|
|
604
|
+
throw storeError(
|
|
605
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.UNSAFE_PATH,
|
|
606
|
+
"merge-review store parent identity is unsafe",
|
|
607
|
+
{ filePath: this.filePath },
|
|
608
|
+
cause,
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
throw storeError(
|
|
612
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.WRITE_FAILED,
|
|
613
|
+
"could not append merge-review event",
|
|
614
|
+
{ filePath: this.filePath },
|
|
615
|
+
cause,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
const persisted = this._readRuntime();
|
|
619
|
+
if (persisted.events.at(-1)?.digest !== event.digest) {
|
|
620
|
+
throw storeError(
|
|
621
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.WRITE_FAILED,
|
|
622
|
+
"merge-review append readback verification failed",
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
return persisted;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
_withLock(operation, callback) {
|
|
629
|
+
try {
|
|
630
|
+
return this._lock(this.filePath, () => callback(this._readRuntime()), {
|
|
631
|
+
timeoutMs: this.lockTimeoutMs,
|
|
632
|
+
staleMs: this.lockStaleMs,
|
|
633
|
+
failIfUnavailable: true,
|
|
634
|
+
});
|
|
635
|
+
} catch (cause) {
|
|
636
|
+
if (isStoreError(cause) || cause?.name === "TeamMergeReviewError") {
|
|
637
|
+
throw cause;
|
|
638
|
+
}
|
|
639
|
+
if (
|
|
640
|
+
cause?.code === "STATE_LOCK_UNAVAILABLE" ||
|
|
641
|
+
cause?.code === "STATE_LOCK_OWNERSHIP_LOST"
|
|
642
|
+
) {
|
|
643
|
+
throw storeError(
|
|
644
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.LOCK_UNAVAILABLE,
|
|
645
|
+
`could not hold merge-review lock for ${operation}`,
|
|
646
|
+
{ filePath: this.filePath },
|
|
647
|
+
cause,
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
throw storeError(
|
|
651
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.WRITE_FAILED,
|
|
652
|
+
`merge-review store ${operation} failed closed`,
|
|
653
|
+
{ filePath: this.filePath },
|
|
654
|
+
cause,
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
_result(runtime, review, extra = {}) {
|
|
660
|
+
return {
|
|
661
|
+
review: clone(review),
|
|
662
|
+
cursor: cursor(runtime),
|
|
663
|
+
...extra,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
read({ anchor = null } = {}) {
|
|
668
|
+
return this._withLock("read", (runtime) => {
|
|
669
|
+
requireAnchor(runtime, anchor);
|
|
670
|
+
return {
|
|
671
|
+
reviews: [...runtime.reviews.values()].map(clone),
|
|
672
|
+
cursor: cursor(runtime),
|
|
673
|
+
};
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
list(options = {}) {
|
|
678
|
+
return this.read(options).reviews;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
get(reviewId, { anchor = null } = {}) {
|
|
682
|
+
return this._withLock("get", (runtime) => {
|
|
683
|
+
requireAnchor(runtime, anchor);
|
|
684
|
+
const review = runtime.reviews.get(String(reviewId || ""));
|
|
685
|
+
return review ? clone(review) : null;
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
create(value, { expectedCursor = null } = {}) {
|
|
690
|
+
const review = validateMergeReview(value);
|
|
691
|
+
if (
|
|
692
|
+
review.revision !== 0 ||
|
|
693
|
+
review.state !== "planned" ||
|
|
694
|
+
review.decision !== null ||
|
|
695
|
+
review.createdAt !== review.updatedAt
|
|
696
|
+
) {
|
|
697
|
+
throw storeError(
|
|
698
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.INVALID,
|
|
699
|
+
"only a pristine revision-0 planned review can be created",
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
return this._withLock("create", (runtime) => {
|
|
703
|
+
requireCurrentCursor(runtime, expectedCursor);
|
|
704
|
+
const existing = runtime.reviews.get(review.reviewId);
|
|
705
|
+
if (existing) {
|
|
706
|
+
if (existing.evidenceDigest !== review.evidenceDigest) {
|
|
707
|
+
throw storeError(
|
|
708
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.CONFLICT,
|
|
709
|
+
"merge-review ID already binds different evidence",
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
return this._result(runtime, existing, { duplicate: true });
|
|
713
|
+
}
|
|
714
|
+
const persisted = this._append(runtime, "review.created", review);
|
|
715
|
+
return this._result(persisted, persisted.reviews.get(review.reviewId), {
|
|
716
|
+
duplicate: false,
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
decide(reviewId, request = {}, { expectedCursor = null } = {}) {
|
|
722
|
+
return this._withLock("decide", (runtime) => {
|
|
723
|
+
requireCurrentCursor(runtime, expectedCursor);
|
|
724
|
+
const current = runtime.reviews.get(String(reviewId || ""));
|
|
725
|
+
if (!current) {
|
|
726
|
+
throw storeError(
|
|
727
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.NOT_FOUND,
|
|
728
|
+
"merge-review was not found",
|
|
729
|
+
{ reviewId },
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
const next = applyMergeReviewDecision(current, request);
|
|
733
|
+
if (next.revision === current.revision) {
|
|
734
|
+
return this._result(runtime, current, { duplicate: true });
|
|
735
|
+
}
|
|
736
|
+
const persisted = this._append(runtime, "review.decided", next);
|
|
737
|
+
return this._result(persisted, persisted.reviews.get(current.reviewId), {
|
|
738
|
+
duplicate: false,
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
transition(reviewId, request = {}, { expectedCursor = null } = {}) {
|
|
744
|
+
return this._withLock("transition", (runtime) => {
|
|
745
|
+
requireCurrentCursor(runtime, expectedCursor);
|
|
746
|
+
const current = runtime.reviews.get(String(reviewId || ""));
|
|
747
|
+
if (!current) {
|
|
748
|
+
throw storeError(
|
|
749
|
+
TEAM_MERGE_REVIEW_STORE_ERROR.NOT_FOUND,
|
|
750
|
+
"merge-review was not found",
|
|
751
|
+
{ reviewId },
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
const next = transitionMergeReview(current, request);
|
|
755
|
+
const persisted = this._append(runtime, "review.transitioned", next);
|
|
756
|
+
return this._result(persisted, persisted.reviews.get(current.reviewId), {
|
|
757
|
+
duplicate: false,
|
|
758
|
+
});
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export function mergeReviewStoreSnapshotDigest(value) {
|
|
764
|
+
return digestMergeReview("cc-team-merge-review-store-snapshot-v1", value);
|
|
765
|
+
}
|