chainlesschain 0.162.187 → 0.162.189
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 +1 -1
- package/src/assets/web-panel/assets/{AIOps-CETaO3s8.js → AIOps-DbZft8w0.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CAe7E9TW.js → ActionButton-DoOZpwfN.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BGr2mzwO.js → Analytics-B0J0KLTW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BEoY-NcT.js → AppLayout-D9eIR0qq.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DUZivCgt.js → Artifacts-BnZXEVeo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C3yBs68k.js → Audit-X1ce4Bjn.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C9KwiY3O.js → BackgroundAgents-BvWR-y5v.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BoSWD_C8.js → Backup-BublSGEd.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Dp09GkxL.js → BaseInput-CR_77Hso.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-5t9d-Ja0.js → Chat-DixW52tq.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DmVku4GH.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-8QJFlkqI.js → Checkbox-CCv1C87K.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BJVFQ3lL.js → Codegen-B03Cc3dd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-6hdLD0oB.js → Col-CA06-6pU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B8opo0uY.js → Community-Bj881J52.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dv8HudiN.js → Compact-UZLTPoaL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DhxuCS5N.js → Compliance-kGwzRb_0.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Dggy6VIL.js → Cowork-KundoYM8.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DSmDqJTT.js → Cron-BYZyoqDe.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-7bZq0j5f.js → Crosschain-BhrrppBX.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BWY4kh-H.js → DID-CdC6EguZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-bOzPZxlu.js → Dashboard-CcQx3J4y.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BTFJHqQu.js → Dropdown-BpijtEzQ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BC3MOdtT.js → EmailListRenderer-DfZjeZTe.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CFHAdTTY.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DZ_4aF_n.js → Federation-Okdi3pJm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BZje6UTj.js → FormItemContext-mH-HebKW.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D7QfCvjN.js → GenericCardRenderer-xivV69x7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DuG7qANG.js → Git-1rh_YBwg.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BDmYAgTh.js → Governance-eAXkNxdY.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DSXAfc1Q.js → Inference-CS6dMVTN.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DusleniP.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CpImmA8p.js → Logs-KchHtiHt.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BZ6LJBkk.js → MarkdownRenderer-TabqHW9B.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DXS8CXkE.js → Marketplace-CsmGcX5X.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D151UfUI.js → McpTools-BaPFnnyz.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DKDxnoA1.js → Memory-2m-sUUf7.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BFZ95iKM.js → MobileBridge-D5nVZBRu.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CDAR-grF.js → MobileProjects-Bo8MLZvT.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-OGJ95xJs.js → Mtc-3GDvamGa.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-BmGTo1XC.js → MtcAudit-D2zGuZCH.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-XumJRqOa.js → Multisig-BBVQ4t6t.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DHhhRZed.js → NLProgramming-BIOCyOGp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B9rGxNNr.js → Notes-DVwBGpzc.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DClLbMYV.js → NotificationSettings-BDnHfIfX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-I8OohT7N.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-M6WzaYVa.js → Organization-D3SRKaa7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cb3B5KtM.js → Overflow-C2B8ahyn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BGk57kya.js → P2P-2iwQzG65.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CXcKyPqK.js → PdhVaultBrowser-CoPiWqMN.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-hsfJ-Y0e.js → Permissions-yV5XNXo5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BSBii4js.js → PersonalDataHub-uCDJhMUF.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BNw52rey.js → Pipeline-hN7ucMOA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DUpfue4E.js → Privacy-CgU0ptN4.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C68NsP3h.js → ProjectInit-CfYPiT6j.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BJ2It9v4.js → ProjectSettings-B0A2C4nw.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BE5C0ajn.js +1 -0
- package/src/assets/web-panel/assets/{Providers-EnU4VWwS.js → Providers-CstQPf8W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BRt5-B_I.js → QrScannerModal-Co3z_ynk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Jd5L9UL2.js → QuickAsk-DMTS6pQ6.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BNDhoVIu.js → Recommend-ETw4odUa.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D1wQPUNv.js → Reputation-BTVqQgKq.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DnjRegul.js → Row-CBKKwwtO.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C_T4I-nF.js → RssFeed-BHUQ438I.js} +2 -2
- package/src/assets/web-panel/assets/{Search-C4lrxlLh.js → Search-B4NqeUsF.js} +1 -1
- package/src/assets/web-panel/assets/{Security-ByYT-VzP.js → Security-BRjoYG5_.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BHpyEqB5.js → Services-CxQzu3p3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C0I23vG-.js → Skeleton-BKzwwmWO.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DGZXz22K.js → Skills-DrsFcgth.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DL_urP9A.js → Sla-D21tmXCH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-6x-hUNQ_.js → SpeechSettings-CjyjLfp8.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Crk5TCXq.js → SyncSettings-Cu45-tnx.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-rZGq5PZs.js → Tasks-oMcILYOj.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DT5el8Q8.js → Templates-Bh-Zcr5R.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-mlgeyMK8.js → Tenant-68ns63aI.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C8HEA-lM.js → Terminal-BLJkFykE.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DvlqBayB.js → TimelineRenderer-xcrJLq-O.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C7lDcNmT.js → Tokens-C6PpFJtf.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CM8nCnIJ.js → Trigger-BaKA7tdM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BQDLUQ9x.js → Trust-BzhA57Jc.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CVqM0OFE.js → UkeySign-4_U3JkIR.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BvAcQpzT.js → VideoEditing-162AANxX.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-zFVFaAZ2.js → Wallet-B1viuql_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Cc522ums.js → WebAuthn-ZVWazhCy.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Bg_wxpDo.js → WorkflowEditor-tzdyUk-1.js} +1 -1
- package/src/assets/web-panel/assets/{chat-jg0QwC8K.js → chat-DmrHNuwq.js} +1 -1
- package/src/assets/web-panel/assets/{colors-ALzYz1gO.js → colors-DAJ_M-PK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DXHYUoqi.js → compact-item-uIzmYwLs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BsGsBM5m.js → createContext-D_ZmuLwb.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BccMLzxA.js → hasIn-CDt_shTL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cg9cOVe9.js → index-BAvSmJXW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTTeAiOP.js → index-BDF6EQBs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3k3ZwkQ.js → index-BHvlm0In.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ1ORdp-.js → index-BRa4kJ_S.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcJzphrP.js → index-BTCXf1t-.js} +1 -1
- package/src/assets/web-panel/assets/{index-rIy96PeM.js → index-Bbxjfstq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSFESurl.js → index-Bx4d6c1k.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cmg_Npx5.js → index-ByzGvpR9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahDve8M.js → index-C16fYKFD.js} +1 -1
- package/src/assets/web-panel/assets/{index-2VstcImq.js → index-C5WcdJH2.js} +3 -3
- package/src/assets/web-panel/assets/{index-C3Rf8wIM.js → index-CG0lY4h3.js} +1 -1
- package/src/assets/web-panel/assets/{index-uzykuL_R.js → index-CKyd472S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBVIFX-x.js → index-CaelyMgF.js} +1 -1
- package/src/assets/web-panel/assets/{index-TeCof1s3.js → index-CbD_WW_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfF0OGKU.js → index-CcZ9xJI9.js} +1 -1
- package/src/assets/web-panel/assets/{index-oWltafBn.js → index-CeUpKjvi.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWLWBgfe.js → index-Cvak6Oe2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0AbXsDy.js → index-CzXqvMuP.js} +1 -1
- package/src/assets/web-panel/assets/index-D-Swwqld.js +1 -0
- package/src/assets/web-panel/assets/{index-BfV8RDlg.js → index-D4n1lGTB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dxqiu9-F.js → index-D9OE8B3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdCmJ_bG.js → index-DBFmcaCQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgjQ-kdT.js → index-DdUMcwRB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBbbr1YB.js → index-DeGBoLQA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmk7dD-T.js → index-DfSSy9uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-JS2LXdd0.js → index-DqDSF2RW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9oV2iRR.js → index-DvFhhe6r.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvsII10i.js → index-DwfvPe-j.js} +1 -1
- package/src/assets/web-panel/assets/index-FMZAMKy8.js +1 -0
- package/src/assets/web-panel/assets/{index-CrZHUsOZ.js → index-FyplHuNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLzPzN75.js → index-GcKOHD_c.js} +1 -1
- package/src/assets/web-panel/assets/{index-koT8Q86Z.js → index-OzTxFIJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-08diKcTm.js → index-QsgfSZrV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJg6Wsjc.js → index-Z8P4BexE.js} +1 -1
- package/src/assets/web-panel/assets/{index-srqW5_-X.js → index-clSX4PEc.js} +1 -1
- package/src/assets/web-panel/assets/{index-mIQUoUx1.js → index-ifxmu8SY.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_s25m3.js → index-rS3gl_68.js} +1 -1
- package/src/assets/web-panel/assets/{index-UQXedWQz.js → index-rXdwUeE3.js} +1 -1
- package/src/assets/web-panel/assets/{index-rcwgNMc0.js → index-saf3VNeP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-xafkqrW3.js → initDefaultProps-pmfaD-aK.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BrtyQXLk.js → motion-CaQ2CpYF.js} +1 -1
- package/src/assets/web-panel/assets/{move-BunUyg59.js → move-CkKTqNbI.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BPwjBeuE.js → mtc-parser-Dd4xsgRI.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BpyqZlNO.js → omit-BFuA5YEs.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DDrV0XjE.js → pickAttrs-DDfxOAJc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-1A18Pcl2.js → placementArrow-Be595JCp.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B8Fnlmso.js → responsiveObserve-7byljUM3.js} +1 -1
- package/src/assets/web-panel/assets/{slide-x-Fwb2Zm.js → slide-CTWfOTaY.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-aAG5yJl_.js → statusUtils-CNZOuIxM.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BRQSTMRL.js → styleChecker-DwOcBV78.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D5X_KozY.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DN_YIjP-.js → useFs-CzyQjH9r.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BizxAvjw.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B5oAxN93.js → vnode-DVfZiS5N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DJhvyzKv.js → zoom-BYme7fbY.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +22 -0
- package/src/commands/auto-mode.js +47 -0
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint.js +3 -0
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/data/auto-mode-safety-eval-v1.json +3294 -0
- package/src/data/changelog.json +26 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +319 -22
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/auto-mode-safety-classifier.js +2034 -0
- package/src/lib/auto-mode-safety-eval.js +1022 -0
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/process-execution-broker/index.js +438 -5
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- 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 +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +3556 -0
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +140 -23
- package/src/repl/agent-repl.js +19 -1
- package/src/runtime/agent-core.js +366 -122
- package/src/runtime/headless-runner.js +12 -0
- package/src/runtime/headless-stream.js +3 -0
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/assets/web-panel/assets/Projects-D2FHLUa-.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-BII41Mel.js +0 -5
- package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +0 -1
- package/src/assets/web-panel/assets/index-8zbMbbuf.js +0 -1
- package/src/assets/web-panel/assets/index-C97f71oi.js +0 -1
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-API filesystem identity checks for secure path reads.
|
|
3
|
+
*
|
|
4
|
+
* Node 22.12 ships libuv 1.49.1. On Windows hosts that expose
|
|
5
|
+
* GetFileInformationByName, libuv 1.49/1.50 can project a path stat's device as
|
|
6
|
+
* zero while fstat on the same opened file reports the real volume serial.
|
|
7
|
+
* libuv 1.51 fixed the structure layout and device projection.
|
|
8
|
+
*
|
|
9
|
+
* Keep this compatibility deliberately narrow: only the affected Windows
|
|
10
|
+
* libuv range may ignore the path API's projected device, the handle device
|
|
11
|
+
* must match a separately opened parent/volume-root directory handle, and
|
|
12
|
+
* every other identity or mutation field remains exact.
|
|
13
|
+
*
|
|
14
|
+
* Holding a parent descriptor does not give Node's path-based fs APIs openat
|
|
15
|
+
* semantics. Callers must keep the canonical parent authority outside the
|
|
16
|
+
* control of untrusted concurrent writers. The checks below detect ordinary
|
|
17
|
+
* path/handle replacement, but a complete rename-away/replace/restore ABA can
|
|
18
|
+
* happen between checks and is deliberately not represented as prevented.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
|
|
24
|
+
const IDENTITY_FIELDS = Object.freeze([
|
|
25
|
+
"dev",
|
|
26
|
+
"ino",
|
|
27
|
+
"mode",
|
|
28
|
+
"nlink",
|
|
29
|
+
"size",
|
|
30
|
+
"mtimeNs",
|
|
31
|
+
"ctimeNs",
|
|
32
|
+
]);
|
|
33
|
+
const IDENTITY_FIELDS_EXCEPT_DEVICE = Object.freeze(
|
|
34
|
+
IDENTITY_FIELDS.filter((field) => field !== "dev"),
|
|
35
|
+
);
|
|
36
|
+
const DIRECTORY_IDENTITY_FIELDS = Object.freeze(["dev", "ino", "mode"]);
|
|
37
|
+
const DIRECTORY_IDENTITY_FIELDS_EXCEPT_DEVICE = Object.freeze(
|
|
38
|
+
DIRECTORY_IDENTITY_FIELDS.filter((field) => field !== "dev"),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export const SECURE_FILE_IDENTITY_ERROR = Object.freeze({
|
|
42
|
+
INVALID_PARENT: "SECURE_FILE_IDENTITY_INVALID_PARENT",
|
|
43
|
+
PARENT_RACE: "SECURE_FILE_IDENTITY_PARENT_RACE",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export class SecureFileIdentityError extends Error {
|
|
47
|
+
constructor(code, message, details = {}, cause = null) {
|
|
48
|
+
super(message, cause ? { cause } : undefined);
|
|
49
|
+
this.name = "SecureFileIdentityError";
|
|
50
|
+
this.code = code;
|
|
51
|
+
Object.assign(this, details);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function identityError(code, message, details = {}, cause = null) {
|
|
56
|
+
return new SecureFileIdentityError(code, message, details, cause);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function timestampValue(stat, nanosecondsKey, millisecondsKey) {
|
|
60
|
+
if (stat?.[nanosecondsKey] !== undefined && stat?.[nanosecondsKey] !== null) {
|
|
61
|
+
return String(stat[nanosecondsKey]);
|
|
62
|
+
}
|
|
63
|
+
if (stat?.[millisecondsKey] !== undefined) {
|
|
64
|
+
const value = Number(stat[millisecondsKey]);
|
|
65
|
+
return Number.isFinite(value)
|
|
66
|
+
? String(Math.trunc(value * 1_000_000))
|
|
67
|
+
: null;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function scalarValue(stat, field) {
|
|
73
|
+
return stat?.[field] === undefined || stat?.[field] === null
|
|
74
|
+
? null
|
|
75
|
+
: String(stat[field]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Normalize either an fs.Stats/fs.BigIntStats object or an already normalized
|
|
80
|
+
* identity. Nanosecond fields are preferred so BigInt stat callers retain the
|
|
81
|
+
* exact timestamps returned by the kernel.
|
|
82
|
+
*/
|
|
83
|
+
export function fileStatIdentity(stat) {
|
|
84
|
+
return {
|
|
85
|
+
dev: scalarValue(stat, "dev"),
|
|
86
|
+
ino: scalarValue(stat, "ino"),
|
|
87
|
+
mode: scalarValue(stat, "mode"),
|
|
88
|
+
nlink: scalarValue(stat, "nlink"),
|
|
89
|
+
size: scalarValue(stat, "size"),
|
|
90
|
+
mtimeNs: timestampValue(stat, "mtimeNs", "mtimeMs"),
|
|
91
|
+
ctimeNs: timestampValue(stat, "ctimeNs", "ctimeMs"),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hasCompleteIdentity(identity) {
|
|
96
|
+
return IDENTITY_FIELDS.every((field) => identity[field] !== null);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function hasFields(identity, fields) {
|
|
100
|
+
return fields.every((field) => identity[field] !== null);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sameFields(left, right, fields) {
|
|
104
|
+
return fields.every((field) => left[field] === right[field]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function runtimePlatform(runtime) {
|
|
108
|
+
return String(runtime?.platform || process.platform);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function windowsVolumeOrShareRoot(canonicalPath) {
|
|
112
|
+
const normalized = path.win32.normalize(String(canonicalPath));
|
|
113
|
+
const extendedUnc = /^\\\\\?\\UNC\\([^\\]+)\\([^\\]+)(?:\\|$)/iu.exec(
|
|
114
|
+
normalized,
|
|
115
|
+
);
|
|
116
|
+
if (extendedUnc) {
|
|
117
|
+
return `\\\\?\\UNC\\${extendedUnc[1]}\\${extendedUnc[2]}\\`;
|
|
118
|
+
}
|
|
119
|
+
return path.win32.parse(normalized).root || path.parse(normalized).root;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function sameFileStatIdentity(leftStat, rightStat) {
|
|
123
|
+
const left = fileStatIdentity(leftStat);
|
|
124
|
+
const right = fileStatIdentity(rightStat);
|
|
125
|
+
return (
|
|
126
|
+
hasCompleteIdentity(left) &&
|
|
127
|
+
hasCompleteIdentity(right) &&
|
|
128
|
+
sameFields(left, right, IDENTITY_FIELDS)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function sameDirectoryStatIdentity(leftStat, rightStat) {
|
|
133
|
+
const left = fileStatIdentity(leftStat);
|
|
134
|
+
const right = fileStatIdentity(rightStat);
|
|
135
|
+
return (
|
|
136
|
+
hasFields(left, DIRECTORY_IDENTITY_FIELDS) &&
|
|
137
|
+
hasFields(right, DIRECTORY_IDENTITY_FIELDS) &&
|
|
138
|
+
sameFields(left, right, DIRECTORY_IDENTITY_FIELDS)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isAffectedWindowsZeroDeviceStatRuntime({
|
|
143
|
+
platform = process.platform,
|
|
144
|
+
uvVersion = process.versions.uv,
|
|
145
|
+
} = {}) {
|
|
146
|
+
if (platform !== "win32") return false;
|
|
147
|
+
const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(String(uvVersion || ""));
|
|
148
|
+
if (!match) return false;
|
|
149
|
+
const major = Number(match[1]);
|
|
150
|
+
const minor = Number(match[2]);
|
|
151
|
+
return major === 1 && (minor === 49 || minor === 50);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Compare a path stat with an opened-handle stat (or a previously normalized
|
|
156
|
+
* handle identity) while binding the file to a trusted directory device.
|
|
157
|
+
*/
|
|
158
|
+
export function samePathHandleFileIdentity(
|
|
159
|
+
pathStat,
|
|
160
|
+
handleStat,
|
|
161
|
+
expectedDevice,
|
|
162
|
+
runtime = undefined,
|
|
163
|
+
) {
|
|
164
|
+
const pathIdentity = fileStatIdentity(pathStat);
|
|
165
|
+
const handleIdentity = fileStatIdentity(handleStat);
|
|
166
|
+
const trustedDevice =
|
|
167
|
+
expectedDevice === undefined || expectedDevice === null
|
|
168
|
+
? null
|
|
169
|
+
: String(expectedDevice);
|
|
170
|
+
if (
|
|
171
|
+
!hasCompleteIdentity(pathIdentity) ||
|
|
172
|
+
!hasCompleteIdentity(handleIdentity) ||
|
|
173
|
+
trustedDevice === null ||
|
|
174
|
+
handleIdentity.dev !== trustedDevice
|
|
175
|
+
) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (sameFields(pathIdentity, handleIdentity, IDENTITY_FIELDS)) return true;
|
|
179
|
+
return (
|
|
180
|
+
isAffectedWindowsZeroDeviceStatRuntime(runtime) &&
|
|
181
|
+
pathIdentity.dev !== handleIdentity.dev &&
|
|
182
|
+
handleIdentity.dev !== "0" &&
|
|
183
|
+
sameFields(pathIdentity, handleIdentity, IDENTITY_FIELDS_EXCEPT_DEVICE)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Directory contents can legitimately change while a child is written, so a
|
|
189
|
+
* parent recheck excludes content-derived metadata and the directory link
|
|
190
|
+
* count, retaining only stable identity/type/permission fields.
|
|
191
|
+
*/
|
|
192
|
+
export function samePathHandleDirectoryIdentity(
|
|
193
|
+
pathStat,
|
|
194
|
+
handleStat,
|
|
195
|
+
expectedDevice,
|
|
196
|
+
runtime = undefined,
|
|
197
|
+
) {
|
|
198
|
+
const pathIdentity = fileStatIdentity(pathStat);
|
|
199
|
+
const handleIdentity = fileStatIdentity(handleStat);
|
|
200
|
+
const trustedDevice =
|
|
201
|
+
expectedDevice === undefined || expectedDevice === null
|
|
202
|
+
? null
|
|
203
|
+
: String(expectedDevice);
|
|
204
|
+
if (
|
|
205
|
+
!hasFields(pathIdentity, DIRECTORY_IDENTITY_FIELDS) ||
|
|
206
|
+
!hasFields(handleIdentity, DIRECTORY_IDENTITY_FIELDS) ||
|
|
207
|
+
trustedDevice === null ||
|
|
208
|
+
handleIdentity.dev !== trustedDevice
|
|
209
|
+
) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
if (sameDirectoryStatIdentity(pathIdentity, handleIdentity)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return (
|
|
216
|
+
isAffectedWindowsZeroDeviceStatRuntime(runtime) &&
|
|
217
|
+
pathIdentity.dev !== handleIdentity.dev &&
|
|
218
|
+
handleIdentity.dev !== "0" &&
|
|
219
|
+
sameFields(
|
|
220
|
+
pathIdentity,
|
|
221
|
+
handleIdentity,
|
|
222
|
+
DIRECTORY_IDENTITY_FIELDS_EXCEPT_DEVICE,
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function canonicalRealpath(runtimeFs, target) {
|
|
228
|
+
const realpathSync = runtimeFs?.realpathSync;
|
|
229
|
+
if (typeof realpathSync?.native === "function") {
|
|
230
|
+
return realpathSync.native(target);
|
|
231
|
+
}
|
|
232
|
+
if (typeof realpathSync === "function") {
|
|
233
|
+
return realpathSync.call(runtimeFs, target);
|
|
234
|
+
}
|
|
235
|
+
throw new TypeError("runtime fs does not provide realpathSync");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Open and retain the canonical parent directory while a caller inspects and
|
|
240
|
+
* opens a child. On Windows, a separately opened canonical volume/share root
|
|
241
|
+
* supplies the independently trusted device used by the identity comparators.
|
|
242
|
+
*
|
|
243
|
+
* Security precondition: the caller must ensure that an untrusted concurrent
|
|
244
|
+
* writer cannot rename the canonical parent (or one of its ancestors) during
|
|
245
|
+
* the callback. Node does not expose the handle-relative open/rename primitives
|
|
246
|
+
* required to make this helper an openat-style authority boundary.
|
|
247
|
+
*/
|
|
248
|
+
export function withTrustedFileParentSync(
|
|
249
|
+
runtimeFs,
|
|
250
|
+
filePath,
|
|
251
|
+
callback,
|
|
252
|
+
{ runtime = undefined } = {},
|
|
253
|
+
) {
|
|
254
|
+
if (!runtimeFs || typeof callback !== "function") {
|
|
255
|
+
throw new TypeError("runtime fs and callback are required");
|
|
256
|
+
}
|
|
257
|
+
if (callback.constructor?.name === "AsyncFunction") {
|
|
258
|
+
throw new TypeError("secure file parent callback must be synchronous");
|
|
259
|
+
}
|
|
260
|
+
const requested = path.resolve(filePath);
|
|
261
|
+
const requestedParent = path.dirname(requested);
|
|
262
|
+
let canonicalParent;
|
|
263
|
+
try {
|
|
264
|
+
canonicalParent = canonicalRealpath(runtimeFs, requestedParent);
|
|
265
|
+
} catch (cause) {
|
|
266
|
+
throw identityError(
|
|
267
|
+
SECURE_FILE_IDENTITY_ERROR.INVALID_PARENT,
|
|
268
|
+
`Could not canonicalize secure file parent: ${requestedParent}`,
|
|
269
|
+
{ filePath: requested, parentPath: requestedParent },
|
|
270
|
+
cause,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let authorityDescriptor = null;
|
|
275
|
+
let descriptor = null;
|
|
276
|
+
try {
|
|
277
|
+
const flags =
|
|
278
|
+
Number(runtimeFs.constants?.O_RDONLY ?? fs.constants.O_RDONLY) |
|
|
279
|
+
Number(runtimeFs.constants?.O_NOFOLLOW ?? fs.constants.O_NOFOLLOW ?? 0);
|
|
280
|
+
let trustedDevice = null;
|
|
281
|
+
if (runtimePlatform(runtime) === "win32") {
|
|
282
|
+
const authorityPath = windowsVolumeOrShareRoot(canonicalParent);
|
|
283
|
+
if (!authorityPath) {
|
|
284
|
+
throw identityError(
|
|
285
|
+
SECURE_FILE_IDENTITY_ERROR.INVALID_PARENT,
|
|
286
|
+
`Could not identify secure file parent volume/share root: ${canonicalParent}`,
|
|
287
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
authorityDescriptor = runtimeFs.openSync(authorityPath, flags);
|
|
292
|
+
const authority = runtimeFs.fstatSync(authorityDescriptor, {
|
|
293
|
+
bigint: true,
|
|
294
|
+
});
|
|
295
|
+
if (!authority.isDirectory() || String(authority.dev) === "0") {
|
|
296
|
+
throw identityError(
|
|
297
|
+
SECURE_FILE_IDENTITY_ERROR.INVALID_PARENT,
|
|
298
|
+
`Secure file parent volume/share root is not a stable directory: ${authorityPath}`,
|
|
299
|
+
{
|
|
300
|
+
filePath: requested,
|
|
301
|
+
parentPath: canonicalParent,
|
|
302
|
+
authorityPath,
|
|
303
|
+
},
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
trustedDevice = String(authority.dev);
|
|
307
|
+
} catch (cause) {
|
|
308
|
+
if (cause instanceof SecureFileIdentityError) throw cause;
|
|
309
|
+
throw identityError(
|
|
310
|
+
SECURE_FILE_IDENTITY_ERROR.INVALID_PARENT,
|
|
311
|
+
`Could not open secure file parent volume/share root: ${authorityPath}`,
|
|
312
|
+
{
|
|
313
|
+
filePath: requested,
|
|
314
|
+
parentPath: canonicalParent,
|
|
315
|
+
authorityPath,
|
|
316
|
+
},
|
|
317
|
+
cause,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
let before;
|
|
323
|
+
try {
|
|
324
|
+
before = runtimeFs.lstatSync(canonicalParent, { bigint: true });
|
|
325
|
+
} catch (cause) {
|
|
326
|
+
throw identityError(
|
|
327
|
+
SECURE_FILE_IDENTITY_ERROR.INVALID_PARENT,
|
|
328
|
+
`Could not inspect secure file parent: ${canonicalParent}`,
|
|
329
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
330
|
+
cause,
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (before.isSymbolicLink() || !before.isDirectory()) {
|
|
334
|
+
throw identityError(
|
|
335
|
+
SECURE_FILE_IDENTITY_ERROR.INVALID_PARENT,
|
|
336
|
+
`Secure file parent must be a real directory: ${canonicalParent}`,
|
|
337
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (trustedDevice === null) {
|
|
341
|
+
trustedDevice = scalarValue(before, "dev");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
descriptor = runtimeFs.openSync(canonicalParent, flags);
|
|
345
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
346
|
+
if (
|
|
347
|
+
!opened.isDirectory() ||
|
|
348
|
+
!samePathHandleDirectoryIdentity(before, opened, trustedDevice, runtime)
|
|
349
|
+
) {
|
|
350
|
+
throw identityError(
|
|
351
|
+
SECURE_FILE_IDENTITY_ERROR.PARENT_RACE,
|
|
352
|
+
`Secure file parent changed while opening: ${canonicalParent}`,
|
|
353
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
let result;
|
|
357
|
+
let callbackFailure = null;
|
|
358
|
+
try {
|
|
359
|
+
result = callback({
|
|
360
|
+
canonicalPath: path.join(canonicalParent, path.basename(requested)),
|
|
361
|
+
parentPath: canonicalParent,
|
|
362
|
+
parentDevice: String(opened.dev),
|
|
363
|
+
parentDescriptor: descriptor,
|
|
364
|
+
});
|
|
365
|
+
if (result && typeof result.then === "function") {
|
|
366
|
+
callbackFailure = new TypeError(
|
|
367
|
+
"secure file parent callback must not return a promise",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
} catch (cause) {
|
|
371
|
+
callbackFailure = cause;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let heldAfter;
|
|
375
|
+
try {
|
|
376
|
+
heldAfter = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
377
|
+
} catch (cause) {
|
|
378
|
+
throw identityError(
|
|
379
|
+
SECURE_FILE_IDENTITY_ERROR.PARENT_RACE,
|
|
380
|
+
`Secure file parent handle became unavailable: ${canonicalParent}`,
|
|
381
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
382
|
+
cause,
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (
|
|
386
|
+
!heldAfter.isDirectory() ||
|
|
387
|
+
!sameDirectoryStatIdentity(heldAfter, opened)
|
|
388
|
+
) {
|
|
389
|
+
throw identityError(
|
|
390
|
+
SECURE_FILE_IDENTITY_ERROR.PARENT_RACE,
|
|
391
|
+
`Secure file parent handle changed during operation: ${canonicalParent}`,
|
|
392
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
393
|
+
callbackFailure,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let after;
|
|
398
|
+
try {
|
|
399
|
+
after = runtimeFs.lstatSync(canonicalParent, { bigint: true });
|
|
400
|
+
} catch (cause) {
|
|
401
|
+
throw identityError(
|
|
402
|
+
SECURE_FILE_IDENTITY_ERROR.PARENT_RACE,
|
|
403
|
+
`Secure file parent became unavailable: ${canonicalParent}`,
|
|
404
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
405
|
+
cause,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if (
|
|
409
|
+
after.isSymbolicLink() ||
|
|
410
|
+
!after.isDirectory() ||
|
|
411
|
+
!samePathHandleDirectoryIdentity(after, heldAfter, opened.dev, runtime)
|
|
412
|
+
) {
|
|
413
|
+
throw identityError(
|
|
414
|
+
SECURE_FILE_IDENTITY_ERROR.PARENT_RACE,
|
|
415
|
+
`Secure file parent changed during operation: ${canonicalParent}`,
|
|
416
|
+
{ filePath: requested, parentPath: canonicalParent },
|
|
417
|
+
callbackFailure,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (callbackFailure) throw callbackFailure;
|
|
421
|
+
return result;
|
|
422
|
+
} finally {
|
|
423
|
+
try {
|
|
424
|
+
if (descriptor !== null) {
|
|
425
|
+
runtimeFs.closeSync(descriptor);
|
|
426
|
+
}
|
|
427
|
+
} finally {
|
|
428
|
+
if (authorityDescriptor !== null) {
|
|
429
|
+
runtimeFs.closeSync(authorityDescriptor);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
package/src/lib/turn-binding.js
CHANGED
|
@@ -69,6 +69,20 @@ export function classifyToolKind(name) {
|
|
|
69
69
|
* mutation WITHOUT a checkpoint → NONE (files unrecoverable)
|
|
70
70
|
*/
|
|
71
71
|
export function computeCoverage(flags = {}) {
|
|
72
|
+
const managedCoverage =
|
|
73
|
+
flags.managedCheckpointCoverage === TURN_COVERAGE.FULL ||
|
|
74
|
+
flags.managedCheckpointCoverage === TURN_COVERAGE.PARTIAL ||
|
|
75
|
+
flags.managedCheckpointCoverage === TURN_COVERAGE.NONE
|
|
76
|
+
? flags.managedCheckpointCoverage
|
|
77
|
+
: null;
|
|
78
|
+
// An explicit managed-checkpoint verdict is stronger than the legacy
|
|
79
|
+
// tool-name heuristic. `none` means the requested writer lifetime could not
|
|
80
|
+
// be bounded; `partial` means workspace recovery exists but at least one
|
|
81
|
+
// path/effect remains outside it.
|
|
82
|
+
if (managedCoverage === TURN_COVERAGE.NONE) return TURN_COVERAGE.NONE;
|
|
83
|
+
if (managedCoverage === TURN_COVERAGE.PARTIAL) {
|
|
84
|
+
return TURN_COVERAGE.PARTIAL;
|
|
85
|
+
}
|
|
72
86
|
const sideEffects = Boolean(
|
|
73
87
|
flags.ranShell ||
|
|
74
88
|
flags.spawnedExternalProcess ||
|
|
@@ -76,10 +90,23 @@ export function computeCoverage(flags = {}) {
|
|
|
76
90
|
);
|
|
77
91
|
if (sideEffects) return TURN_COVERAGE.PARTIAL;
|
|
78
92
|
if (!flags.mutatedFiles) return TURN_COVERAGE.FULL;
|
|
79
|
-
if (flags.hasFileCheckpoint)
|
|
93
|
+
if (managedCoverage === TURN_COVERAGE.FULL || flags.hasFileCheckpoint) {
|
|
94
|
+
return TURN_COVERAGE.FULL;
|
|
95
|
+
}
|
|
80
96
|
return TURN_COVERAGE.NONE;
|
|
81
97
|
}
|
|
82
98
|
|
|
99
|
+
function mergeManagedCoverage(current, next) {
|
|
100
|
+
const rank = {
|
|
101
|
+
[TURN_COVERAGE.FULL]: 2,
|
|
102
|
+
[TURN_COVERAGE.PARTIAL]: 1,
|
|
103
|
+
[TURN_COVERAGE.NONE]: 0,
|
|
104
|
+
};
|
|
105
|
+
if (!(next in rank)) return current ?? null;
|
|
106
|
+
if (!(current in rank)) return next;
|
|
107
|
+
return rank[next] < rank[current] ? next : current;
|
|
108
|
+
}
|
|
109
|
+
|
|
83
110
|
function emptyRecord(turnId, conversationOffset) {
|
|
84
111
|
return {
|
|
85
112
|
turnId,
|
|
@@ -92,6 +119,7 @@ function emptyRecord(turnId, conversationOffset) {
|
|
|
92
119
|
permissionDecisionIds: [],
|
|
93
120
|
childAgentIds: [],
|
|
94
121
|
childBindings: [],
|
|
122
|
+
managedCheckpoints: [],
|
|
95
123
|
worktreeId: null,
|
|
96
124
|
// side-effect flags → coverage (kept internal; surfaced via `coverage`)
|
|
97
125
|
_flags: {
|
|
@@ -100,6 +128,7 @@ function emptyRecord(turnId, conversationOffset) {
|
|
|
100
128
|
userEditedDuringTurn: false,
|
|
101
129
|
mutatedFiles: false,
|
|
102
130
|
hasFileCheckpoint: false,
|
|
131
|
+
managedCheckpointCoverage: null,
|
|
103
132
|
},
|
|
104
133
|
};
|
|
105
134
|
}
|
|
@@ -206,6 +235,62 @@ export class TurnBindingLog {
|
|
|
206
235
|
return this;
|
|
207
236
|
}
|
|
208
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Bind durable Process Broker workspace-transaction evidence without
|
|
240
|
+
* overloading `fileCheckpointId` (which belongs to the legacy git rewind
|
|
241
|
+
* engine). Multiple tool calls may settle in one turn, so the worst coverage
|
|
242
|
+
* verdict governs the turn.
|
|
243
|
+
*/
|
|
244
|
+
recordManagedCheckpoint(turnId, checkpoint = {}) {
|
|
245
|
+
const coverage =
|
|
246
|
+
checkpoint.coverage === TURN_COVERAGE.FULL ||
|
|
247
|
+
checkpoint.coverage === TURN_COVERAGE.PARTIAL ||
|
|
248
|
+
checkpoint.coverage === TURN_COVERAGE.NONE
|
|
249
|
+
? checkpoint.coverage
|
|
250
|
+
: TURN_COVERAGE.NONE;
|
|
251
|
+
const normalized = {
|
|
252
|
+
toolUseId:
|
|
253
|
+
checkpoint.toolUseId == null ? null : String(checkpoint.toolUseId),
|
|
254
|
+
tool: checkpoint.tool == null ? null : String(checkpoint.tool),
|
|
255
|
+
transactionId:
|
|
256
|
+
checkpoint.transactionId == null
|
|
257
|
+
? null
|
|
258
|
+
: String(checkpoint.transactionId),
|
|
259
|
+
checkpointId:
|
|
260
|
+
checkpoint.checkpointId == null
|
|
261
|
+
? null
|
|
262
|
+
: String(checkpoint.checkpointId),
|
|
263
|
+
evidenceDigest:
|
|
264
|
+
checkpoint.evidenceDigest == null
|
|
265
|
+
? null
|
|
266
|
+
: String(checkpoint.evidenceDigest),
|
|
267
|
+
phase: checkpoint.phase == null ? null : String(checkpoint.phase),
|
|
268
|
+
coverage,
|
|
269
|
+
fileCoverage:
|
|
270
|
+
checkpoint.fileCoverage === TURN_COVERAGE.FULL ||
|
|
271
|
+
checkpoint.fileCoverage === TURN_COVERAGE.PARTIAL ||
|
|
272
|
+
checkpoint.fileCoverage === TURN_COVERAGE.NONE
|
|
273
|
+
? checkpoint.fileCoverage
|
|
274
|
+
: TURN_COVERAGE.NONE,
|
|
275
|
+
};
|
|
276
|
+
const rec = this._rec(turnId);
|
|
277
|
+
const existingIndex = rec.managedCheckpoints.findIndex(
|
|
278
|
+
(item) =>
|
|
279
|
+
(normalized.transactionId &&
|
|
280
|
+
item.transactionId === normalized.transactionId) ||
|
|
281
|
+
(!normalized.transactionId &&
|
|
282
|
+
normalized.toolUseId &&
|
|
283
|
+
item.toolUseId === normalized.toolUseId),
|
|
284
|
+
);
|
|
285
|
+
if (existingIndex === -1) rec.managedCheckpoints.push(normalized);
|
|
286
|
+
else rec.managedCheckpoints[existingIndex] = normalized;
|
|
287
|
+
rec._flags.managedCheckpointCoverage = mergeManagedCoverage(
|
|
288
|
+
rec._flags.managedCheckpointCoverage,
|
|
289
|
+
coverage,
|
|
290
|
+
);
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
209
294
|
setWorktree(turnId, worktreeId) {
|
|
210
295
|
this._rec(turnId).worktreeId =
|
|
211
296
|
worktreeId == null ? null : String(worktreeId);
|
|
@@ -271,6 +356,13 @@ export class TurnBindingLog {
|
|
|
271
356
|
checkpointIds: [...binding.checkpointIds],
|
|
272
357
|
toolUseIds: [...binding.toolUseIds],
|
|
273
358
|
})),
|
|
359
|
+
...(rec.managedCheckpoints.length > 0
|
|
360
|
+
? {
|
|
361
|
+
managedCheckpoints: rec.managedCheckpoints.map((checkpoint) => ({
|
|
362
|
+
...checkpoint,
|
|
363
|
+
})),
|
|
364
|
+
}
|
|
365
|
+
: {}),
|
|
274
366
|
worktreeId: rec.worktreeId,
|
|
275
367
|
coverage: computeCoverage(rec._flags),
|
|
276
368
|
};
|
|
@@ -280,7 +372,13 @@ export class TurnBindingLog {
|
|
|
280
372
|
return {
|
|
281
373
|
turns: this._order.map((id) => {
|
|
282
374
|
const r = this._turns.get(id);
|
|
283
|
-
return {
|
|
375
|
+
return {
|
|
376
|
+
...r,
|
|
377
|
+
managedCheckpoints: r.managedCheckpoints.map((checkpoint) => ({
|
|
378
|
+
...checkpoint,
|
|
379
|
+
})),
|
|
380
|
+
_flags: { ...r._flags },
|
|
381
|
+
};
|
|
284
382
|
}),
|
|
285
383
|
};
|
|
286
384
|
}
|
|
@@ -316,6 +414,39 @@ export class TurnBindingLog {
|
|
|
316
414
|
worktreePath: binding.worktreePath ?? null,
|
|
317
415
|
}))
|
|
318
416
|
: [],
|
|
417
|
+
managedCheckpoints: Array.isArray(r.managedCheckpoints)
|
|
418
|
+
? r.managedCheckpoints.map((checkpoint) => ({
|
|
419
|
+
toolUseId:
|
|
420
|
+
checkpoint?.toolUseId == null
|
|
421
|
+
? null
|
|
422
|
+
: String(checkpoint.toolUseId),
|
|
423
|
+
tool: checkpoint?.tool == null ? null : String(checkpoint.tool),
|
|
424
|
+
transactionId:
|
|
425
|
+
checkpoint?.transactionId == null
|
|
426
|
+
? null
|
|
427
|
+
: String(checkpoint.transactionId),
|
|
428
|
+
checkpointId:
|
|
429
|
+
checkpoint?.checkpointId == null
|
|
430
|
+
? null
|
|
431
|
+
: String(checkpoint.checkpointId),
|
|
432
|
+
evidenceDigest:
|
|
433
|
+
checkpoint?.evidenceDigest == null
|
|
434
|
+
? null
|
|
435
|
+
: String(checkpoint.evidenceDigest),
|
|
436
|
+
phase:
|
|
437
|
+
checkpoint?.phase == null ? null : String(checkpoint.phase),
|
|
438
|
+
coverage:
|
|
439
|
+
checkpoint?.coverage === TURN_COVERAGE.FULL ||
|
|
440
|
+
checkpoint?.coverage === TURN_COVERAGE.PARTIAL
|
|
441
|
+
? checkpoint.coverage
|
|
442
|
+
: TURN_COVERAGE.NONE,
|
|
443
|
+
fileCoverage:
|
|
444
|
+
checkpoint?.fileCoverage === TURN_COVERAGE.FULL ||
|
|
445
|
+
checkpoint?.fileCoverage === TURN_COVERAGE.PARTIAL
|
|
446
|
+
? checkpoint.fileCoverage
|
|
447
|
+
: TURN_COVERAGE.NONE,
|
|
448
|
+
}))
|
|
449
|
+
: [],
|
|
319
450
|
worktreeId: r.worktreeId ?? null,
|
|
320
451
|
_flags: { ...emptyRecord(id)._flags, ...(r._flags || {}) },
|
|
321
452
|
});
|
|
@@ -537,6 +668,33 @@ export function createTurnBindingFeed({
|
|
|
537
668
|
dirty = true;
|
|
538
669
|
}
|
|
539
670
|
break;
|
|
671
|
+
case "managed-checkpoint-settled":
|
|
672
|
+
log.recordManagedCheckpoint(turnId, {
|
|
673
|
+
toolUseId: event.tool_use_id,
|
|
674
|
+
tool: event.tool,
|
|
675
|
+
transactionId: event.transaction_id,
|
|
676
|
+
checkpointId: event.id,
|
|
677
|
+
evidenceDigest: event.evidence_digest,
|
|
678
|
+
phase: event.phase,
|
|
679
|
+
coverage: event.coverage,
|
|
680
|
+
fileCoverage: event.file_coverage,
|
|
681
|
+
});
|
|
682
|
+
dirty = true;
|
|
683
|
+
break;
|
|
684
|
+
case "managed-checkpoint-error":
|
|
685
|
+
if (event.phase !== "prepare" && event.recovery_required === true) {
|
|
686
|
+
log.recordManagedCheckpoint(turnId, {
|
|
687
|
+
toolUseId: event.tool_use_id,
|
|
688
|
+
tool: event.tool,
|
|
689
|
+
transactionId: event.transaction_id,
|
|
690
|
+
checkpointId: event.checkpoint_id,
|
|
691
|
+
phase: event.phase || "settle",
|
|
692
|
+
coverage: TURN_COVERAGE.NONE,
|
|
693
|
+
fileCoverage: TURN_COVERAGE.NONE,
|
|
694
|
+
});
|
|
695
|
+
dirty = true;
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
540
698
|
case "tool-executing": {
|
|
541
699
|
const callId =
|
|
542
700
|
event.tool_use_id == null
|