chainlesschain 0.162.183 → 0.162.185
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/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BapS7lz4.js → AIOps-Co4ipAKx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-BpsoDrDq.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-DMUqeBik.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-C6fD_dXr.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DW0YlQlV.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit-DXtHiiFK.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-BgmF62U-.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-D8vhVLQC.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-D7nCtY2X.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-CBZLEV2G.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CI_jT0ru.js → ChatBubbleRenderer-CtDb-2Qu.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-DJLkeD4M.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-Cs6xO8Fd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col-Dtx9wxfT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-DiigJg4n.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-B67oMDDL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-BBDl-Jos.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-Ba98hSsa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-DBWQZIz7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-OcrKzxAt.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-mW2eSbjM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-BmsDPmKJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-CVzmCyRx.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-vVgqKzhh.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-ZGomhjxr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-DyaknbLr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-DQOJIYkG.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CMiY-EEp.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-D6IXuucf.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B_fyjZK9.js → Logs-N0jD-349.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BL1IVXm-.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BIStF9d1.js → Marketplace-WhYuhBxm.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-DTeO28xy.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-MSHzPSE-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-Cyqs3J3W.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-7IN2hWHg.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-MWBnezNM.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-DQu35Nu-.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-CQS_M-DP.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-WD16vQvY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-DqsiSwOq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-D3LoxO-P.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DdcsOqEz.js → OrderTableRenderer-C5jPoTd6.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-48aMBJso.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-j-a2XkhR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-BYpBNYnh.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-ZRXGXDFb.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-6OWUPHyL.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-FcjlDK9E.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-vD_6Tldp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-B_CHOLXp.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-rEiqQ2da.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-Bg8obRAM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-Dgtt97B4.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-DaGVylZO.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DP2_2x3u.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-D29yj4ki.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-Ue2wnBLp.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CrxIHqeC.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-BgSzJPCn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-AwhYOrP9.js} +3 -3
- package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-D2shpEcm.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-BtKzhTbT.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-BY0DsUSm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-B3Sb0UI9.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DtT6rWOX.js → Skills-Ne5w0bDs.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-C8VkaP4T.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-BQdIEYP9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.js → SyncSettings-CwFafrUg.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BKoHztGT.js → Templates-DYn9qCmx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-DkKReHvn.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-r6bd-owt.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-Mq2juUC3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-BnMw7MRi.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-CTzKp4o9.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-BBrSZm9U.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-D9fDvEMq.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-DUi5ve-0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-C8Fi1P8_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-s1LppYi2.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-DJL2C4lj.js} +1 -1
- package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-D3F2ndOB.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-B7739-e3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-CcJbFTF4.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C326bjex.js → createContext-CuP_DEFS.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Cj_36GHq.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D_INJQT_.js → hasIn-6YEXfIik.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-4h3lpWJ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-BIID4XcP.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BOHUo3aM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-BYYSLEjn.js} +1 -1
- package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-Bh4hcNWW.js} +1 -1
- package/src/assets/web-panel/assets/{index-C327pLbw.js → index-BhIKnQAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-Bru_kpZl.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-BsdSnHQE.js} +1 -1
- package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-BxSqVMGh.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-C3ZA_h1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-C3pctdCb.js} +1 -1
- package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-CRuoRapN.js} +1 -1
- package/src/assets/web-panel/assets/{index-qMA4GYdQ.js → index-CY0zd_jc.js} +1 -1
- package/src/assets/web-panel/assets/index-CezFrAmD.js +1 -0
- package/src/assets/web-panel/assets/index-Cxgguez6.js +1 -0
- package/src/assets/web-panel/assets/{index-Cr3uuz0C.js → index-CxradkS6.js} +19 -19
- package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-D0K_rFC1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-D0zdPyTD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-D3haorUt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-D6Ij_2uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-DDRlBaDt.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-DGsE7MQz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-DHvxXXNA.js} +1 -1
- package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DV11jpeO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-DXdnfDVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-D_75jYWk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-DjI9MkId.js} +1 -1
- package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-DlSvpNMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dox44j29.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-DpEKmjoa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Dsy7_x4s.js} +1 -1
- package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-Gxw09e9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-LfRlSmqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-Nz-MUHD1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUU2606o.js → index-b9cuvFEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-eR6H6NR7.js} +1 -1
- package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-fWgUQZ9d.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-mfWQRSLn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-yOZYSNmG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-rYJN_vzf.js} +1 -1
- package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-DnDK2gTP.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-CKnXy7ES.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-3nRc3hWT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Ddjj3sLu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-CbsbphQY.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-D4pvdFj0.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-C2W-vY-S.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-Cbq5150d.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-BA2B7R82.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CLrwg3xg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-8BDGy-YU.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-B2SzFprl.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +74 -1
- package/src/commands/background-session.js +11 -1
- package/src/commands/batch.js +160 -5
- package/src/commands/plugin.js +90 -27
- package/src/commands/session.js +31 -1
- package/src/commands/team.js +212 -6
- package/src/data/changelog.json +26 -0
- package/src/gateways/terminal/PtyManager.js +45 -15
- package/src/gateways/ws/ws-agent-handler.js +15 -1
- package/src/gateways/ws/ws-session-gateway.js +88 -0
- package/src/harness/background-task-command-runner.js +234 -0
- package/src/harness/background-task-manager.js +150 -20
- package/src/harness/background-task-worker.js +17 -11
- package/src/harness/jsonl-session-store.js +57 -6
- package/src/harness/mcp-client.js +65 -43
- package/src/lib/agent-batch.js +4 -1
- package/src/lib/agent-team/team-worktree.js +3 -0
- package/src/lib/async-hook-supervisor.cjs +82 -10
- package/src/lib/async-hook-supervisor.js +50 -4
- package/src/lib/background-agent-supervisor.js +46 -0
- package/src/lib/collaboration-run-store.js +362 -0
- package/src/lib/event-runtime-host.js +12 -10
- package/src/lib/hook-runner.cjs +105 -18
- package/src/lib/hook-runner.js +79 -6
- package/src/lib/hook-shell-command.cjs +148 -0
- package/src/lib/hooks-v2-runtime.js +280 -81
- package/src/lib/hooks-v2-workspace-context.js +258 -0
- package/src/lib/jsonl-session-store.js +2 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +15 -0
- package/src/lib/lsp/lsp-client.js +12 -1
- package/src/lib/plugin-monitor-supervisor.js +53 -16
- package/src/lib/plugin-runtime/bin.js +21 -5
- package/src/lib/plugin-runtime/install.js +249 -57
- package/src/lib/plugin-runtime/mcp.js +21 -2
- package/src/lib/plugin-runtime/monitors.js +19 -3
- package/src/lib/plugin-runtime/sandbox-policy.js +100 -4
- package/src/lib/process-execution-broker/index.js +1151 -44
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1557 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +1378 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +586 -58
- package/src/lib/session-usage.js +202 -2
- package/src/lib/stream-retry.js +29 -0
- package/src/repl/agent-repl.js +40 -3
- package/src/runtime/agent-core.js +304 -42
- package/src/runtime/headless-runner.js +48 -1
- package/src/runtime/headless-stream.js +42 -2
- package/src/workers/background-agent-worker.js +5 -11
- package/src/assets/web-panel/assets/MarkdownRenderer-C0o4FtpD.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
- package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +0 -1
- package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
- package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
- package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
- package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
|
@@ -36,6 +36,13 @@ const {
|
|
|
36
36
|
tryParseDecision,
|
|
37
37
|
resolveHookSandboxPolicy,
|
|
38
38
|
} = require("./hook-runner.cjs");
|
|
39
|
+
const {
|
|
40
|
+
buildExplicitHookShellInvocation,
|
|
41
|
+
issueTrustedHookSandboxContract,
|
|
42
|
+
requireTrustedHookRoot,
|
|
43
|
+
requiresExplicitHookShell,
|
|
44
|
+
sandboxBoundaryError,
|
|
45
|
+
} = require("./hook-shell-command.cjs");
|
|
39
46
|
|
|
40
47
|
const DEFAULT_TIMEOUT_MS = 60000;
|
|
41
48
|
const MAX_TIMEOUT_MS = 600000;
|
|
@@ -117,8 +124,25 @@ class AsyncHookSupervisor {
|
|
|
117
124
|
* the process 'exit' reaper, where async work would be cut off. A child with no
|
|
118
125
|
* real pid (the unit-test fakes) falls back to its own `.kill()`.
|
|
119
126
|
*/
|
|
120
|
-
_killChildTree(
|
|
127
|
+
_killChildTree(
|
|
128
|
+
child,
|
|
129
|
+
signal = "SIGTERM",
|
|
130
|
+
{ sandboxManagedTree = false } = {},
|
|
131
|
+
) {
|
|
121
132
|
if (!child) return;
|
|
133
|
+
if (sandboxManagedTree) {
|
|
134
|
+
// The Linux generic backend launches bubblewrap with --die-with-parent,
|
|
135
|
+
// --new-session, and a private PID namespace. Killing the Broker child
|
|
136
|
+
// therefore tears down the complete sandbox tree; a negative host PID
|
|
137
|
+
// would be both unnecessary and invalid because strong launches must not
|
|
138
|
+
// use detached:true.
|
|
139
|
+
try {
|
|
140
|
+
child.kill(signal);
|
|
141
|
+
} catch {
|
|
142
|
+
/* already gone */
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
122
146
|
const pid = child.pid;
|
|
123
147
|
if (typeof pid !== "number" || pid <= 0) {
|
|
124
148
|
try {
|
|
@@ -361,25 +385,39 @@ class AsyncHookSupervisor {
|
|
|
361
385
|
: DEFAULT_TIMEOUT_MS;
|
|
362
386
|
|
|
363
387
|
let child;
|
|
388
|
+
let sandboxManagedTree = false;
|
|
364
389
|
try {
|
|
365
390
|
const sandboxPolicy = resolveHookSandboxPolicy(hook, opts);
|
|
391
|
+
const explicitSandboxShell = requiresExplicitHookShell(sandboxPolicy);
|
|
392
|
+
const trustedRoot = explicitSandboxShell
|
|
393
|
+
? requireTrustedHookRoot(opts.cwd)
|
|
394
|
+
: null;
|
|
395
|
+
const workingDirectory = trustedRoot || opts.cwd || process.cwd();
|
|
396
|
+
sandboxManagedTree =
|
|
397
|
+
explicitSandboxShell && this._deps.platform === "linux";
|
|
366
398
|
const spawnOptions = {
|
|
367
|
-
cwd:
|
|
368
|
-
shell: true,
|
|
399
|
+
cwd: workingDirectory,
|
|
400
|
+
shell: explicitSandboxShell ? false : true,
|
|
369
401
|
// POSIX: own process group so a timeout / stopAll can signal the whole
|
|
370
402
|
// tree (shell + the real hook command), not just the shell wrapper.
|
|
371
403
|
// No-op on Windows, where the tree is reaped via `taskkill /T` instead.
|
|
372
|
-
detached: this._deps.platform !== "win32",
|
|
404
|
+
detached: this._deps.platform !== "win32" && !sandboxManagedTree,
|
|
373
405
|
env: {
|
|
374
406
|
...process.env,
|
|
375
407
|
CLAUDE_HOOK_EVENT: hook.event || payload.hook_event_name || "",
|
|
376
408
|
},
|
|
377
409
|
...(sandboxPolicy ? { sandboxPolicy } : {}),
|
|
378
410
|
};
|
|
379
|
-
|
|
411
|
+
const brokerRun =
|
|
412
|
+
explicitSandboxShell &&
|
|
413
|
+
opts.broker &&
|
|
414
|
+
typeof opts.broker.spawn === "function"
|
|
415
|
+
? opts.broker.spawn.bind(opts.broker)
|
|
416
|
+
: null;
|
|
417
|
+
if (typeof (brokerRun || this._deps.run) !== "function") {
|
|
380
418
|
throw new Error("async hook process runner unavailable");
|
|
381
419
|
}
|
|
382
|
-
|
|
420
|
+
const processOptions = {
|
|
383
421
|
...spawnOptions,
|
|
384
422
|
origin: hook.origin || "async-hook:command",
|
|
385
423
|
policy: "allow",
|
|
@@ -387,7 +425,39 @@ class AsyncHookSupervisor {
|
|
|
387
425
|
pluginId: hook.pluginId || null,
|
|
388
426
|
pluginVersion: hook.pluginVersion || null,
|
|
389
427
|
pluginSource: hook.pluginSource || null,
|
|
390
|
-
}
|
|
428
|
+
};
|
|
429
|
+
if (explicitSandboxShell) {
|
|
430
|
+
const invocation = buildExplicitHookShellInvocation(hook.command, {
|
|
431
|
+
platform: this._deps.platform,
|
|
432
|
+
});
|
|
433
|
+
const run = brokerRun || this._deps.run;
|
|
434
|
+
let brokerOptions = processOptions;
|
|
435
|
+
if (brokerRun) {
|
|
436
|
+
const issuer =
|
|
437
|
+
opts.broker.issueLinuxWorkspaceSandboxExecutionContract;
|
|
438
|
+
const sandboxExecutionContract = issueTrustedHookSandboxContract({
|
|
439
|
+
issuer,
|
|
440
|
+
receiver: opts.broker,
|
|
441
|
+
file: invocation.file,
|
|
442
|
+
args: invocation.argv,
|
|
443
|
+
options: processOptions,
|
|
444
|
+
trustedRoot,
|
|
445
|
+
label: "trusted async-hook sandbox contract issuance failed",
|
|
446
|
+
});
|
|
447
|
+
if (this._deps.platform === "linux" && !sandboxExecutionContract) {
|
|
448
|
+
throw sandboxBoundaryError(
|
|
449
|
+
"trusted Linux async-hook sandbox contract issuer is unavailable",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
brokerOptions = {
|
|
453
|
+
...processOptions,
|
|
454
|
+
...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
child = run(invocation.file, invocation.argv, brokerOptions);
|
|
458
|
+
} else {
|
|
459
|
+
child = this._deps.run(hook.command, processOptions);
|
|
460
|
+
}
|
|
391
461
|
} catch (err) {
|
|
392
462
|
const sandboxDenied =
|
|
393
463
|
err?.code === "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED";
|
|
@@ -406,7 +476,7 @@ class AsyncHookSupervisor {
|
|
|
406
476
|
let out = "";
|
|
407
477
|
let errOut = "";
|
|
408
478
|
let killedByTimeout = false;
|
|
409
|
-
const rec = { child, timer: null };
|
|
479
|
+
const rec = { child, timer: null, sandboxManagedTree };
|
|
410
480
|
this._running.set(key, rec);
|
|
411
481
|
|
|
412
482
|
try {
|
|
@@ -432,7 +502,7 @@ class AsyncHookSupervisor {
|
|
|
432
502
|
killedByTimeout = true;
|
|
433
503
|
// Tree-kill: a hook that spawned a subprocess (e.g. `npm test`) must not
|
|
434
504
|
// leave that subprocess orphaned when it exceeds its timeout.
|
|
435
|
-
this._killChildTree(child, "SIGTERM");
|
|
505
|
+
this._killChildTree(child, "SIGTERM", { sandboxManagedTree });
|
|
436
506
|
}, timeout);
|
|
437
507
|
if (typeof timer.unref === "function") timer.unref();
|
|
438
508
|
rec.timer = timer;
|
|
@@ -594,7 +664,9 @@ class AsyncHookSupervisor {
|
|
|
594
664
|
}
|
|
595
665
|
// Tree-kill (not a bare child.kill) so a session end / process exit never
|
|
596
666
|
// leaves an orphaned hook subprocess — the guarantee this class documents.
|
|
597
|
-
this._killChildTree(rec.child, "SIGTERM"
|
|
667
|
+
this._killChildTree(rec.child, "SIGTERM", {
|
|
668
|
+
sandboxManagedTree: rec.sandboxManagedTree === true,
|
|
669
|
+
});
|
|
598
670
|
this._running.delete(key);
|
|
599
671
|
}
|
|
600
672
|
try {
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import supervisor from "./async-hook-supervisor.cjs";
|
|
2
2
|
import executionBroker from "./process-execution-broker/index.js";
|
|
3
|
+
import hookShellCommand from "./hook-shell-command.cjs";
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
issueTrustedHookSandboxContract,
|
|
7
|
+
requireTrustedHookRoot,
|
|
8
|
+
requiresExplicitHookShell,
|
|
9
|
+
sandboxBoundaryError,
|
|
10
|
+
} = hookShellCommand;
|
|
3
11
|
|
|
4
12
|
const brokerRunner = executionBroker.spawn.bind(executionBroker);
|
|
5
13
|
const brokerSyncRunner = executionBroker.spawnSync.bind(executionBroker);
|
|
@@ -9,12 +17,50 @@ export const _processDeps = {
|
|
|
9
17
|
runSync: brokerSyncRunner,
|
|
10
18
|
};
|
|
11
19
|
|
|
12
|
-
function
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
function normalizeInvocation(argsOrOptions, maybeOptions) {
|
|
21
|
+
return Array.isArray(argsOrOptions)
|
|
22
|
+
? { args: argsOrOptions, options: maybeOptions || {} }
|
|
23
|
+
: { args: [], options: argsOrOptions || {} };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function runAsyncHook(command, argsOrOptions = {}, maybeOptions) {
|
|
27
|
+
const { args, options } = normalizeInvocation(argsOrOptions, maybeOptions);
|
|
28
|
+
const callerOptions = { ...options };
|
|
29
|
+
delete callerOptions.sandboxExecutionContract;
|
|
30
|
+
const processOptions = {
|
|
31
|
+
...callerOptions,
|
|
32
|
+
origin: callerOptions.origin || "async-hook:command",
|
|
16
33
|
policy: "allow",
|
|
17
34
|
scope: "async-hook",
|
|
35
|
+
};
|
|
36
|
+
const requiresContract = requiresExplicitHookShell(
|
|
37
|
+
processOptions.sandboxPolicy,
|
|
38
|
+
);
|
|
39
|
+
const trustedRoot = requiresContract
|
|
40
|
+
? requireTrustedHookRoot(processOptions.cwd)
|
|
41
|
+
: null;
|
|
42
|
+
const sandboxExecutionContract = requiresContract
|
|
43
|
+
? issueTrustedHookSandboxContract({
|
|
44
|
+
issuer: executionBroker.issueLinuxWorkspaceSandboxExecutionContract,
|
|
45
|
+
receiver: executionBroker,
|
|
46
|
+
file: command,
|
|
47
|
+
args,
|
|
48
|
+
options: processOptions,
|
|
49
|
+
trustedRoot,
|
|
50
|
+
})
|
|
51
|
+
: null;
|
|
52
|
+
if (
|
|
53
|
+
process.platform === "linux" &&
|
|
54
|
+
requiresContract &&
|
|
55
|
+
!sandboxExecutionContract
|
|
56
|
+
) {
|
|
57
|
+
throw sandboxBoundaryError(
|
|
58
|
+
"trusted Linux async-hook sandbox contract could not be issued",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return _processDeps.run(command, args, {
|
|
62
|
+
...processOptions,
|
|
63
|
+
...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
|
|
18
64
|
});
|
|
19
65
|
}
|
|
20
66
|
|
|
@@ -201,6 +201,52 @@ export const _deps = {
|
|
|
201
201
|
killProcessTree: defaultKillProcessTree,
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Stop the active turn launched by an attached background session.
|
|
206
|
+
* Windows must terminate the complete descendant tree; a bare ChildProcess
|
|
207
|
+
* kill only reaches the top-level CLI process and can orphan tool children.
|
|
208
|
+
*/
|
|
209
|
+
export function stopBackgroundAgentChildTree(
|
|
210
|
+
pid,
|
|
211
|
+
{ platform = process.platform, signal = "SIGTERM" } = {},
|
|
212
|
+
) {
|
|
213
|
+
const target = Number(pid);
|
|
214
|
+
if (!Number.isInteger(target) || target <= 0 || target === process.pid) {
|
|
215
|
+
const error = new Error("background_agent_child_pid_invalid");
|
|
216
|
+
error.code = "ERR_BACKGROUND_AGENT_CHILD_PID_INVALID";
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
if (platform === "win32") {
|
|
220
|
+
const result = runSupervisorCommand(
|
|
221
|
+
"taskkill",
|
|
222
|
+
["/PID", String(target), "/T", "/F"],
|
|
223
|
+
{
|
|
224
|
+
windowsHide: true,
|
|
225
|
+
encoding: "utf8",
|
|
226
|
+
},
|
|
227
|
+
"background-agent:session-stop-tree",
|
|
228
|
+
);
|
|
229
|
+
if (result?.error || result?.status !== 0) {
|
|
230
|
+
const error = new Error(
|
|
231
|
+
`background_agent_child_tree_stop_failed: ${
|
|
232
|
+
result?.error?.message ||
|
|
233
|
+
result?.stderr ||
|
|
234
|
+
`taskkill exited ${result?.status}`
|
|
235
|
+
}`,
|
|
236
|
+
);
|
|
237
|
+
error.code = "ERR_BACKGROUND_AGENT_CHILD_TREE_STOP_FAILED";
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
_deps.kill(-target, signal);
|
|
244
|
+
} catch {
|
|
245
|
+
_deps.kill(target, signal);
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
|
|
204
250
|
/** Cached probe (default impl only — injected probes run uncached for tests). */
|
|
205
251
|
function processStartTimeMs(pid) {
|
|
206
252
|
if (_deps.readProcessStartTimeMs !== defaultReadProcessStartTimeMs) {
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable, secret-free governance records for short-lived `cc team` / `cc
|
|
3
|
+
* batch` worktree runs.
|
|
4
|
+
*
|
|
5
|
+
* These are deliberately separate from background-agent state: a team/batch
|
|
6
|
+
* task is not independently attachable or stoppable, and pretending otherwise
|
|
7
|
+
* could make `cc daemon stop` kill the shared coordinator process. The daemon
|
|
8
|
+
* JSON view may project these records read-only for IDE task rows.
|
|
9
|
+
*
|
|
10
|
+
* Prompts, argv, tool arguments, model output, credentials, and side-effect
|
|
11
|
+
* metadata never enter this store.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
renameSync,
|
|
20
|
+
writeFileSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
24
|
+
import { getHomeDir } from "./paths.js";
|
|
25
|
+
|
|
26
|
+
export const COLLABORATION_RUN_VERSION = 1;
|
|
27
|
+
const RUN_KINDS = new Set(["team", "batch"]);
|
|
28
|
+
const PERMISSION_MODES = new Set([
|
|
29
|
+
"manual",
|
|
30
|
+
"auto",
|
|
31
|
+
"dontAsk",
|
|
32
|
+
"default",
|
|
33
|
+
"plan",
|
|
34
|
+
"acceptEdits",
|
|
35
|
+
"bypassPermissions",
|
|
36
|
+
]);
|
|
37
|
+
const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
38
|
+
const UNIT_STATUSES = new Set([
|
|
39
|
+
"pending",
|
|
40
|
+
"running",
|
|
41
|
+
"completed",
|
|
42
|
+
"failed",
|
|
43
|
+
"test-failed",
|
|
44
|
+
"no-changes",
|
|
45
|
+
"cancelled",
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
export const _deps = {
|
|
49
|
+
now: () => Date.now(),
|
|
50
|
+
randomHex: () => randomBytes(3).toString("hex"),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function boundedString(value, max) {
|
|
54
|
+
if (value === null || value === undefined) return null;
|
|
55
|
+
const clean = String(value)
|
|
56
|
+
.replace(/\p{Cc}/gu, "")
|
|
57
|
+
.trim();
|
|
58
|
+
return clean ? clean.slice(0, max) : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function positiveNumber(value) {
|
|
62
|
+
const number = Number(value);
|
|
63
|
+
return Number.isFinite(number) && number > 0 ? number : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nonNegativeInteger(value) {
|
|
67
|
+
const number = Number(value);
|
|
68
|
+
return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function safeKind(kind) {
|
|
72
|
+
const value = String(kind || "");
|
|
73
|
+
if (!RUN_KINDS.has(value)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Unsupported collaboration run kind: ${value || "(empty)"}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function safeRunId(id) {
|
|
82
|
+
const value = String(id || "");
|
|
83
|
+
if (value.length > 160 || !/^(team|batch)-[a-zA-Z0-9._-]+$/.test(value)) {
|
|
84
|
+
throw new Error(`Invalid collaboration run id: ${value || "(empty)"}`);
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function collaborationRunsDir() {
|
|
90
|
+
const dir =
|
|
91
|
+
process.env.CC_COLLABORATION_RUNS_DIR ||
|
|
92
|
+
join(getHomeDir(), "collaboration-runs");
|
|
93
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
94
|
+
return dir;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function createCollaborationRunId(kind) {
|
|
98
|
+
const normalizedKind = safeKind(kind);
|
|
99
|
+
return `${normalizedKind}-${Math.floor(_deps.now())}-${_deps.randomHex()}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function createCollaborationSessionId(runId, key) {
|
|
103
|
+
const safeId = safeRunId(runId);
|
|
104
|
+
const digest = createHash("sha256")
|
|
105
|
+
.update(String(key || "unit"))
|
|
106
|
+
.digest("hex")
|
|
107
|
+
.slice(0, 12);
|
|
108
|
+
return `session-${safeId}-${digest}`.slice(0, 256);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function collaborationRunPath(id) {
|
|
112
|
+
return join(collaborationRunsDir(), `${safeRunId(id)}.json`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function normalizeBudget(resourceBudget) {
|
|
116
|
+
const source =
|
|
117
|
+
resourceBudget && typeof resourceBudget === "object" ? resourceBudget : {};
|
|
118
|
+
return {
|
|
119
|
+
maxTurns: positiveNumber(source.maxTurns),
|
|
120
|
+
maxCostUsd: positiveNumber(source.maxCostUsd),
|
|
121
|
+
maxTasks: positiveNumber(source.maxTasks),
|
|
122
|
+
maxTokens: positiveNumber(source.maxTokens),
|
|
123
|
+
maxWallMs: positiveNumber(source.maxWallMs),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function summarizeSideEffects(source) {
|
|
128
|
+
const ops =
|
|
129
|
+
source && typeof source.list === "function"
|
|
130
|
+
? source.list()
|
|
131
|
+
: Array.isArray(source?.ops)
|
|
132
|
+
? source.ops
|
|
133
|
+
: null;
|
|
134
|
+
if (ops) {
|
|
135
|
+
const count = (state) => ops.filter((op) => op?.state === state).length;
|
|
136
|
+
return {
|
|
137
|
+
total: ops.length,
|
|
138
|
+
unsettled: ops.filter(
|
|
139
|
+
(op) => op?.state === "prepared" || op?.state === "started",
|
|
140
|
+
).length,
|
|
141
|
+
unknown: count("unknown"),
|
|
142
|
+
committed: count("committed"),
|
|
143
|
+
failed: count("failed"),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const value = source && typeof source === "object" ? source : {};
|
|
147
|
+
return {
|
|
148
|
+
total: nonNegativeInteger(value.total),
|
|
149
|
+
unsettled: nonNegativeInteger(value.unsettled),
|
|
150
|
+
unknown: nonNegativeInteger(value.unknown),
|
|
151
|
+
committed: nonNegativeInteger(value.committed),
|
|
152
|
+
failed: nonNegativeInteger(value.failed),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function normalizeUnit(unit, run) {
|
|
157
|
+
const key = boundedString(unit?.key, 256);
|
|
158
|
+
if (!key) throw new Error("Collaboration unit key is required");
|
|
159
|
+
const status = UNIT_STATUSES.has(unit?.status) ? unit.status : "pending";
|
|
160
|
+
const sessionId = boundedString(unit?.sessionId, 256);
|
|
161
|
+
return {
|
|
162
|
+
key,
|
|
163
|
+
owner: `${run.kind}:${run.id}:${key}`.slice(0, 512),
|
|
164
|
+
sessionId,
|
|
165
|
+
branch: boundedString(unit?.branch, 512),
|
|
166
|
+
worktreePath: boundedString(unit?.worktreePath, 4096),
|
|
167
|
+
status,
|
|
168
|
+
startedAt: positiveNumber(unit?.startedAt),
|
|
169
|
+
endedAt: positiveNumber(unit?.endedAt),
|
|
170
|
+
sideEffects: summarizeSideEffects(unit?.sideEffects),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeRun(input) {
|
|
175
|
+
const kind = safeKind(input?.kind);
|
|
176
|
+
const id = safeRunId(input?.id);
|
|
177
|
+
if (!id.startsWith(`${kind}-`)) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`Collaboration run id "${id}" does not match kind "${kind}"`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const permissionMode = PERMISSION_MODES.has(input?.permissionMode)
|
|
183
|
+
? input.permissionMode
|
|
184
|
+
: "default";
|
|
185
|
+
const status = new Set(["running", ...TERMINAL_RUN_STATUSES]).has(
|
|
186
|
+
input?.status,
|
|
187
|
+
)
|
|
188
|
+
? input.status
|
|
189
|
+
: "running";
|
|
190
|
+
const startedAt = positiveNumber(input?.startedAt) || _deps.now();
|
|
191
|
+
const run = {
|
|
192
|
+
version: COLLABORATION_RUN_VERSION,
|
|
193
|
+
id,
|
|
194
|
+
kind,
|
|
195
|
+
owner: `${kind}:${id}`,
|
|
196
|
+
status,
|
|
197
|
+
repoRoot: boundedString(input?.repoRoot, 4096),
|
|
198
|
+
permissionMode,
|
|
199
|
+
resourceBudget: normalizeBudget(input?.resourceBudget),
|
|
200
|
+
startedAt,
|
|
201
|
+
updatedAt: positiveNumber(input?.updatedAt) || startedAt,
|
|
202
|
+
endedAt: TERMINAL_RUN_STATUSES.has(status)
|
|
203
|
+
? positiveNumber(input?.endedAt) || startedAt
|
|
204
|
+
: null,
|
|
205
|
+
units: [],
|
|
206
|
+
};
|
|
207
|
+
const seen = new Set();
|
|
208
|
+
for (const unit of Array.isArray(input?.units)
|
|
209
|
+
? input.units.slice(0, 1000)
|
|
210
|
+
: []) {
|
|
211
|
+
const normalized = normalizeUnit(unit, run);
|
|
212
|
+
if (seen.has(normalized.key)) {
|
|
213
|
+
throw new Error(`Duplicate collaboration unit key: ${normalized.key}`);
|
|
214
|
+
}
|
|
215
|
+
seen.add(normalized.key);
|
|
216
|
+
run.units.push(normalized);
|
|
217
|
+
}
|
|
218
|
+
return run;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function writeCollaborationRun(input) {
|
|
222
|
+
const run = normalizeRun(input);
|
|
223
|
+
const target = collaborationRunPath(run.id);
|
|
224
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
225
|
+
writeFileSync(temp, JSON.stringify(run, null, 2), { mode: 0o600 });
|
|
226
|
+
renameSync(temp, target);
|
|
227
|
+
return run;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function createCollaborationRun(input) {
|
|
231
|
+
const kind = safeKind(input?.kind);
|
|
232
|
+
const id = input?.id ? safeRunId(input.id) : createCollaborationRunId(kind);
|
|
233
|
+
if (existsSync(collaborationRunPath(id))) {
|
|
234
|
+
throw new Error(`Collaboration run already exists: ${id}`);
|
|
235
|
+
}
|
|
236
|
+
return writeCollaborationRun({
|
|
237
|
+
...input,
|
|
238
|
+
id,
|
|
239
|
+
kind,
|
|
240
|
+
status: "running",
|
|
241
|
+
startedAt: input?.startedAt || _deps.now(),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function readCollaborationRun(id) {
|
|
246
|
+
const target = collaborationRunPath(id);
|
|
247
|
+
if (!existsSync(target)) return null;
|
|
248
|
+
try {
|
|
249
|
+
return normalizeRun(JSON.parse(readFileSync(target, "utf8")));
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function updateCollaborationRun(id, updater) {
|
|
256
|
+
const current = readCollaborationRun(id);
|
|
257
|
+
if (!current) throw new Error(`Collaboration run not found: ${id}`);
|
|
258
|
+
const draft = JSON.parse(JSON.stringify(current));
|
|
259
|
+
const changed =
|
|
260
|
+
typeof updater === "function"
|
|
261
|
+
? updater(draft) || draft
|
|
262
|
+
: { ...draft, ...updater };
|
|
263
|
+
return writeCollaborationRun({
|
|
264
|
+
...changed,
|
|
265
|
+
id: current.id,
|
|
266
|
+
kind: current.kind,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function updateCollaborationUnit(runId, key, patch = {}) {
|
|
271
|
+
const targetKey = boundedString(key, 256);
|
|
272
|
+
return updateCollaborationRun(runId, (run) => {
|
|
273
|
+
const index = run.units.findIndex((unit) => unit.key === targetKey);
|
|
274
|
+
if (index < 0) {
|
|
275
|
+
throw new Error(`Collaboration unit not found: ${targetKey}`);
|
|
276
|
+
}
|
|
277
|
+
const current = run.units[index];
|
|
278
|
+
const status = UNIT_STATUSES.has(patch.status)
|
|
279
|
+
? patch.status
|
|
280
|
+
: current.status;
|
|
281
|
+
run.units[index] = {
|
|
282
|
+
...current,
|
|
283
|
+
...(patch.branch !== undefined
|
|
284
|
+
? { branch: boundedString(patch.branch, 512) }
|
|
285
|
+
: {}),
|
|
286
|
+
...(patch.worktreePath !== undefined
|
|
287
|
+
? { worktreePath: boundedString(patch.worktreePath, 4096) }
|
|
288
|
+
: {}),
|
|
289
|
+
status,
|
|
290
|
+
startedAt:
|
|
291
|
+
patch.startedAt !== undefined
|
|
292
|
+
? positiveNumber(patch.startedAt)
|
|
293
|
+
: current.startedAt,
|
|
294
|
+
endedAt:
|
|
295
|
+
patch.endedAt !== undefined
|
|
296
|
+
? positiveNumber(patch.endedAt)
|
|
297
|
+
: current.endedAt,
|
|
298
|
+
sideEffects:
|
|
299
|
+
patch.sideEffects !== undefined
|
|
300
|
+
? summarizeSideEffects(patch.sideEffects)
|
|
301
|
+
: current.sideEffects,
|
|
302
|
+
};
|
|
303
|
+
run.updatedAt = _deps.now();
|
|
304
|
+
return run;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function finalizeCollaborationRun(id, status = "completed") {
|
|
309
|
+
if (!TERMINAL_RUN_STATUSES.has(status)) {
|
|
310
|
+
throw new Error(`Invalid terminal collaboration status: ${status}`);
|
|
311
|
+
}
|
|
312
|
+
return updateCollaborationRun(id, (run) => {
|
|
313
|
+
run.status = status;
|
|
314
|
+
run.updatedAt = _deps.now();
|
|
315
|
+
run.endedAt = run.updatedAt;
|
|
316
|
+
return run;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function listCollaborationRuns({ limit = 200 } = {}) {
|
|
321
|
+
const capped = Math.max(1, Math.min(1000, Number(limit) || 200));
|
|
322
|
+
return readdirSync(collaborationRunsDir())
|
|
323
|
+
.filter((name) => /^(team|batch)-[a-zA-Z0-9._-]+\.json$/.test(name))
|
|
324
|
+
.map((name) => readCollaborationRun(name.slice(0, -5)))
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.sort((a, b) => b.startedAt - a.startedAt)
|
|
327
|
+
.slice(0, capped);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Project run units into the bounded shape consumed by IDE worktree task rows.
|
|
332
|
+
* The caller can publish this as `managedTasks`; no control operation consumes
|
|
333
|
+
* these records.
|
|
334
|
+
*/
|
|
335
|
+
export function projectCollaborationTasks(runs) {
|
|
336
|
+
const rows = [];
|
|
337
|
+
for (const run of Array.isArray(runs) ? runs.slice(0, 200) : []) {
|
|
338
|
+
for (const unit of Array.isArray(run?.units)
|
|
339
|
+
? run.units.slice(0, 1000)
|
|
340
|
+
: []) {
|
|
341
|
+
if (!unit.branch && !unit.worktreePath) continue;
|
|
342
|
+
rows.push({
|
|
343
|
+
managedTaskId: `${run.id}:${unit.key}`.slice(0, 512),
|
|
344
|
+
runId: run.id,
|
|
345
|
+
runKind: run.kind,
|
|
346
|
+
branch: unit.branch,
|
|
347
|
+
worktreePath: unit.worktreePath,
|
|
348
|
+
status: unit.status,
|
|
349
|
+
governance: {
|
|
350
|
+
version: 1,
|
|
351
|
+
owner: unit.owner,
|
|
352
|
+
sessionId: unit.sessionId,
|
|
353
|
+
permissionMode: run.permissionMode,
|
|
354
|
+
resourceBudget: run.resourceBudget,
|
|
355
|
+
},
|
|
356
|
+
sideEffects: summarizeSideEffects(unit.sideEffects),
|
|
357
|
+
});
|
|
358
|
+
if (rows.length >= 1000) return rows;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return rows;
|
|
362
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
10
|
import { EventRuntimeStore } from "./event-runtime-store.js";
|
|
11
11
|
import { EventRuntimeWorker } from "./event-runtime-worker.js";
|
|
12
|
+
import { registerHostHooksV2Workspace } from "./hooks-v2-workspace-context.js";
|
|
12
13
|
|
|
13
14
|
function routeType(event) {
|
|
14
15
|
return String(
|
|
@@ -63,10 +64,8 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
63
64
|
worker ||
|
|
64
65
|
new EventRuntimeWorker({
|
|
65
66
|
store,
|
|
66
|
-
onInbox: (event, record) =>
|
|
67
|
-
|
|
68
|
-
onOutbox: (event, record) =>
|
|
69
|
-
this._dispatch("outbox", event, record),
|
|
67
|
+
onInbox: (event, record) => this._dispatch("inbox", event, record),
|
|
68
|
+
onOutbox: (event, record) => this._dispatch("outbox", event, record),
|
|
70
69
|
});
|
|
71
70
|
}
|
|
72
71
|
|
|
@@ -89,10 +88,7 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
90
|
|
|
92
|
-
registerHandler(
|
|
93
|
-
handler,
|
|
94
|
-
{ queue = "inbox", type = "*", origin = "*" } = {},
|
|
95
|
-
) {
|
|
91
|
+
registerHandler(handler, { queue = "inbox", type = "*", origin = "*" } = {}) {
|
|
96
92
|
if (typeof handler !== "function") {
|
|
97
93
|
throw new TypeError("Event Runtime handler must be a function");
|
|
98
94
|
}
|
|
@@ -223,8 +219,7 @@ export class EventRuntimeHost extends EventEmitter {
|
|
|
223
219
|
break;
|
|
224
220
|
}
|
|
225
221
|
const claimed =
|
|
226
|
-
Number(stats?.inboxClaimed || 0) +
|
|
227
|
-
Number(stats?.outboxClaimed || 0);
|
|
222
|
+
Number(stats?.inboxClaimed || 0) + Number(stats?.outboxClaimed || 0);
|
|
228
223
|
if (claimed === 0) break;
|
|
229
224
|
}
|
|
230
225
|
}
|
|
@@ -272,7 +267,14 @@ export function startDefaultEventRuntimeHost(options = {}) {
|
|
|
272
267
|
if (process.env.CC_EVENT_RUNTIME_DURABLE !== "1" && options.force !== true) {
|
|
273
268
|
return null;
|
|
274
269
|
}
|
|
270
|
+
// The CLI process startup directory is host state. Register it before the
|
|
271
|
+
// first durable claim so a recovered Hooks v2 record can resolve its opaque
|
|
272
|
+
// workspace ID without accepting any path from the record itself.
|
|
273
|
+
const hooksV2WorkspaceBinding = registerHostHooksV2Workspace(
|
|
274
|
+
options.workspaceRoot ?? process.cwd(),
|
|
275
|
+
);
|
|
275
276
|
const host = getDefaultEventRuntimeHost(options);
|
|
277
|
+
host.hooksV2WorkspaceBindingId = hooksV2WorkspaceBinding.bindingId;
|
|
276
278
|
host.start({ immediate: options.immediate !== false });
|
|
277
279
|
return host;
|
|
278
280
|
}
|