chainlesschain 0.162.184 → 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-DzAl5fGx.js → AIOps-Co4ipAKx.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DAHtMGeC.js → ActionButton-BpsoDrDq.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BHQaexv5.js → Analytics-DMUqeBik.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Phz_pD0n.js → AppLayout-C6fD_dXr.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DHt47kCA.js → Artifacts-DW0YlQlV.js} +1 -1
- package/src/assets/web-panel/assets/{Audit--Y_8FULl.js → Audit-DXtHiiFK.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-N1zchH7D.js → BackgroundAgents-BgmF62U-.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B_BoyN30.js → Backup-D8vhVLQC.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Gl4uQHcT.js → BaseInput-D7nCtY2X.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Cg-eIyAZ.js → Chat-CBZLEV2G.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CtDb-2Qu.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-D0MGR2wA.js → Checkbox-DJLkeD4M.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BdObJEFn.js → Codegen-Cs6xO8Fd.js} +1 -1
- package/src/assets/web-panel/assets/{Col--ghxXT3z.js → Col-Dtx9wxfT.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BUFeXOqz.js → Community-DiigJg4n.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DhQO2_Mr.js → Compact-B67oMDDL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-3HoTqKYd.js → Compliance-BBDl-Jos.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B3zmO4VW.js → Cowork-Ba98hSsa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CJDrZOcc.js → Cron-DBWQZIz7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-SMl15obd.js → Crosschain-OcrKzxAt.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CgTrTWrl.js → DID-mW2eSbjM.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-dXFKaKol.js → Dashboard-BmsDPmKJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-qQHEhCvI.js → Dropdown-CVzmCyRx.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C_ej0MWH.js → EmailListRenderer-vVgqKzhh.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D6ajoE7I.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Cio2y49h.js → Federation-ZGomhjxr.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-ClJz1cYp.js → FormItemContext-DyaknbLr.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Dr_8Maca.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CevLwp5u.js → Git-DQOJIYkG.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CW9YyQUY.js → Governance-CMiY-EEp.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CQqeAyER.js → Inference-D6IXuucf.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-cuGVxHrw.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DIuHri5d.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-CY1VhFGx.js → Marketplace-WhYuhBxm.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BBAbi1vt.js → McpTools-DTeO28xy.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BWYGHxJA.js → Memory-MSHzPSE-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CPIFkW7A.js → MobileBridge-Cyqs3J3W.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DQyWxCho.js → MobileProjects-7IN2hWHg.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Bo3GnQbx.js → Mtc-MWBnezNM.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-C8-c4rSW.js → MtcAudit-DQu35Nu-.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-ChqMTtoq.js → Multisig-CQS_M-DP.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-C48TyhvI.js → NLProgramming-WD16vQvY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-zogUFWxp.js → Notes-DqsiSwOq.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-v-K9XpSB.js → NotificationSettings-D3LoxO-P.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-C5jPoTd6.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Boi1dQfR.js → Organization-48aMBJso.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DM5YWwgA.js → Overflow-j-a2XkhR.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-C57LKpKK.js → P2P-BYpBNYnh.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BFkPqu6Q.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D6fB9YSp.js → Permissions-ZRXGXDFb.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Bp7EefEo.js → PersonalDataHub-6OWUPHyL.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-BVln3hZf.js → Pipeline-FcjlDK9E.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bf8P3nF6.js → Privacy-vD_6Tldp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DM6wA2E2.js → ProjectInit-B_CHOLXp.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CGbc5Iho.js → ProjectSettings-rEiqQ2da.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-D0p9EOxm.js → Projects-Bg8obRAM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B2kJcxS6.js → Providers-Dgtt97B4.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cxu7dXYy.js → QrScannerModal-DaGVylZO.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DTlrTPms.js → QuickAsk-DP2_2x3u.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cm3X-cCj.js → Recommend-D29yj4ki.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DNb2kjvt.js → RemoteSession-Ue2wnBLp.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CZUFFjc1.js → Reputation-CrxIHqeC.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DAPgloxA.js → Row-BgSzJPCn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-dsx9S793.js → RssFeed-AwhYOrP9.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DnnC8kz-.js → Search-D2shpEcm.js} +1 -1
- package/src/assets/web-panel/assets/{Security-VuZknRMK.js → Security-BtKzhTbT.js} +4 -4
- package/src/assets/web-panel/assets/{Services-B3XNX8Vx.js → Services-BY0DsUSm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Dl1nVtCj.js → Skeleton-B3Sb0UI9.js} +1 -1
- package/src/assets/web-panel/assets/Skills-Ne5w0bDs.js +1 -0
- package/src/assets/web-panel/assets/{Sla-HzU56n1z.js → Sla-C8VkaP4T.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CRle7Jl2.js → SpeechSettings-BQdIEYP9.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DWadz7Sl.js → SyncSettings-CwFafrUg.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BBAgvrlM.js → Templates-DYn9qCmx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BsbbvGN8.js → Tenant-DkKReHvn.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-uscpZI4q.js → Terminal-r6bd-owt.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-mTyjNE6r.js → TimelineRenderer-Mq2juUC3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bz97oBgs.js → Tokens-BnMw7MRi.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BTRKC140.js → Trigger-CTzKp4o9.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B4A_KuyX.js → Trust-BBrSZm9U.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DES9TmLU.js → UkeySign-D9fDvEMq.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClXs7Qxp.js → VideoEditing-DUi5ve-0.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-IzZqFz-7.js → Wallet-C8Fi1P8_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-BtOP04g_.js → WebAuthn-s1LppYi2.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-_0cqnopY.js → WorkflowEditor-DJL2C4lj.js} +1 -1
- package/src/assets/web-panel/assets/{chat-ax7193uU.js → chat-D3F2ndOB.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Bu4WGJah.js → colors-B7739-e3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BX1dlTW0.js → compact-item-CcJbFTF4.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CktA_Dj7.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-D5wd817l.js → hasIn-6YEXfIik.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3HmzCLt.js → index-4h3lpWJ3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjEJjl5C.js → index-BIID4XcP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQtoRyTO.js → index-BOHUo3aM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP8ELJBc.js → index-BYYSLEjn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfzxR61U.js → index-Bh4hcNWW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmPJ6T6F.js → index-BhIKnQAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8DkWyNr.js → index-Bru_kpZl.js} +1 -1
- package/src/assets/web-panel/assets/{index-iHcPtQ2e.js → index-BsdSnHQE.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9op8poE.js → index-BxSqVMGh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGdL3sG.js → index-C3ZA_h1j.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmVUeWUk.js → index-C3pctdCb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cv7bVWJ5.js → index-CRuoRapN.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-o4Rwjk.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-Ci3y1ZAv.js → index-CxradkS6.js} +19 -19
- package/src/assets/web-panel/assets/{index-CUForzsG.js → index-D0K_rFC1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLLsUNDc.js → index-D0zdPyTD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CeVZaJSC.js → index-D3haorUt.js} +1 -1
- package/src/assets/web-panel/assets/{index-o14NNnxw.js → index-D6Ij_2uw.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db3CpWTr.js → index-DDRlBaDt.js} +1 -1
- package/src/assets/web-panel/assets/{index-h3KJZr95.js → index-DGsE7MQz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAtBNfkr.js → index-DHvxXXNA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DF1TysIF.js → index-DV11jpeO.js} +1 -1
- package/src/assets/web-panel/assets/{index-5PYYkr8S.js → index-DXdnfDVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-tYPSfGbd.js → index-D_75jYWk.js} +1 -1
- package/src/assets/web-panel/assets/{index-jzlzm4VK.js → index-DjI9MkId.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_cycOET.js → index-DlSvpNMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dzdr0zDf.js → index-Dox44j29.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYRE0NJA.js → index-DpEKmjoa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad2CYeH.js → index-Dsy7_x4s.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8tLR2TG.js → index-Gxw09e9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-ytp2H5Zx.js → index-LfRlSmqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkD1K-fF.js → index-Nz-MUHD1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRfXoRuh.js → index-b9cuvFEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYo9S5OT.js → index-eR6H6NR7.js} +1 -1
- package/src/assets/web-panel/assets/{index-A4uBPQCz.js → index-fWgUQZ9d.js} +1 -1
- package/src/assets/web-panel/assets/{index-PBLzwYnu.js → index-mfWQRSLn.js} +1 -1
- package/src/assets/web-panel/assets/{index-bFCaM7Eq.js → index-yOZYSNmG.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D2Iut83B.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BuaJVdK9.js → motion-rYJN_vzf.js} +1 -1
- package/src/assets/web-panel/assets/{move-CaEuM92y.js → move-DnDK2gTP.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-wuKNBwys.js → mtc-parser-CKnXy7ES.js} +1 -1
- package/src/assets/web-panel/assets/{omit-7a3eklaq.js → omit-3nRc3hWT.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Dn4z7KvJ.js → pickAttrs-Ddjj3sLu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DVn_qT4-.js → placementArrow-CbsbphQY.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Dodz1OMQ.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CxoGskbp.js → slide-D4pvdFj0.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DBh_epQH.js → statusUtils-C2W-vY-S.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-RXQ4gG5P.js → styleChecker-Cbq5150d.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-1Wxm0HEG.js → useFlexGapSupport-BA2B7R82.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CI_ySNxJ.js → useFs-CLrwg3xg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C6973rZP.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CalynwKr.js → vnode-8BDGy-YU.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CM1cd-Q0.js → zoom-B2SzFprl.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +13 -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/lib/background-agent-supervisor.js +46 -0
- package/src/lib/event-runtime-host.js +12 -10
- package/src/lib/hook-runner.cjs +7 -1
- package/src/lib/hook-runner.js +2 -0
- package/src/lib/hooks-v2-runtime.js +94 -6
- package/src/lib/hooks-v2-workspace-context.js +258 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +1 -4
- package/src/lib/plugin-runtime/bin.js +20 -4
- package/src/lib/plugin-runtime/sandbox-policy.js +1 -4
- package/src/lib/process-execution-broker/index.js +706 -40
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +212 -2
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +124 -6
- package/src/lib/process-execution-broker/platform-sandbox.js +6 -2
- package/src/repl/agent-repl.js +8 -0
- package/src/runtime/agent-core.js +272 -38
- package/src/runtime/headless-runner.js +11 -1
- package/src/runtime/headless-stream.js +8 -0
- package/src/workers/background-agent-worker.js +5 -11
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-R91Qw5JA.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +0 -1
- package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-x877iKKS.js +0 -1
- package/src/assets/web-panel/assets/index-D7pfPouB.js +0 -1
- package/src/assets/web-panel/assets/index-e4i-VLBd.js +0 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import executionBroker from "../lib/process-execution-broker/index.js";
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_BOUNDARIES = new Set(["filesystem", "network"]);
|
|
6
|
+
|
|
7
|
+
export const _backgroundTaskCommandDeps = {
|
|
8
|
+
execSync: (...args) => executionBroker.execSync(...args),
|
|
9
|
+
execFile: (...args) => executionBroker.execFile(...args),
|
|
10
|
+
issueLinuxWorkspaceSandboxExecutionContract: (...args) =>
|
|
11
|
+
executionBroker.issueLinuxWorkspaceSandboxExecutionContract(...args),
|
|
12
|
+
platform: process.platform,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function sandboxError(code, reason, message, requiredBoundaries = []) {
|
|
16
|
+
const error = new Error(message);
|
|
17
|
+
error.code = code;
|
|
18
|
+
error.sandboxReason = reason;
|
|
19
|
+
error.sandboxFailClosed = true;
|
|
20
|
+
error.requiredBoundaries = [...requiredBoundaries];
|
|
21
|
+
error.actualGuarantees = [];
|
|
22
|
+
error.missingBoundaries = [...requiredBoundaries];
|
|
23
|
+
error.sandboxBackend = null;
|
|
24
|
+
error.sandboxCandidateBackend =
|
|
25
|
+
_backgroundTaskCommandDeps.platform === "linux"
|
|
26
|
+
? "linux-bwrap-workspace"
|
|
27
|
+
: null;
|
|
28
|
+
return error;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeRequiredBoundaries(raw) {
|
|
32
|
+
if (raw === undefined || raw === null || raw === "") return [];
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
36
|
+
} catch (cause) {
|
|
37
|
+
const error = sandboxError(
|
|
38
|
+
"ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
|
|
39
|
+
"background_sandbox_envelope_invalid",
|
|
40
|
+
"Background task sandbox boundary envelope is invalid.",
|
|
41
|
+
);
|
|
42
|
+
error.cause = cause;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (!Array.isArray(parsed)) {
|
|
46
|
+
throw sandboxError(
|
|
47
|
+
"ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
|
|
48
|
+
"background_sandbox_envelope_invalid",
|
|
49
|
+
"Background task sandbox boundaries must use filesystem/network.",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (parsed.length === 0) return [];
|
|
53
|
+
if (
|
|
54
|
+
parsed.some(
|
|
55
|
+
(boundary) =>
|
|
56
|
+
typeof boundary !== "string" || !SUPPORTED_BOUNDARIES.has(boundary),
|
|
57
|
+
)
|
|
58
|
+
) {
|
|
59
|
+
throw sandboxError(
|
|
60
|
+
"ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
|
|
61
|
+
"background_sandbox_envelope_invalid",
|
|
62
|
+
"Background task sandbox boundaries must use filesystem/network.",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return [...new Set(parsed)].sort();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function canonicalBinding(workspaceCwd, executionCwd, boundaries) {
|
|
69
|
+
try {
|
|
70
|
+
if (
|
|
71
|
+
typeof workspaceCwd !== "string" ||
|
|
72
|
+
!path.isAbsolute(workspaceCwd) ||
|
|
73
|
+
typeof executionCwd !== "string" ||
|
|
74
|
+
!path.isAbsolute(executionCwd)
|
|
75
|
+
) {
|
|
76
|
+
throw new Error("sandbox paths must be absolute");
|
|
77
|
+
}
|
|
78
|
+
const canonicalRoot = fs.realpathSync.native(workspaceCwd);
|
|
79
|
+
const canonicalCwd = fs.realpathSync.native(executionCwd);
|
|
80
|
+
if (
|
|
81
|
+
canonicalRoot !== workspaceCwd ||
|
|
82
|
+
canonicalCwd !== executionCwd ||
|
|
83
|
+
!fs.statSync(canonicalRoot).isDirectory() ||
|
|
84
|
+
!fs.statSync(canonicalCwd).isDirectory()
|
|
85
|
+
) {
|
|
86
|
+
throw new Error("sandbox paths must be canonical directories");
|
|
87
|
+
}
|
|
88
|
+
const relative = path.relative(canonicalRoot, canonicalCwd);
|
|
89
|
+
if (
|
|
90
|
+
relative === ".." ||
|
|
91
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
92
|
+
path.isAbsolute(relative)
|
|
93
|
+
) {
|
|
94
|
+
throw new Error("execution cwd escapes workspace");
|
|
95
|
+
}
|
|
96
|
+
return { workspaceRoot: canonicalRoot, workingDirectory: canonicalCwd };
|
|
97
|
+
} catch (cause) {
|
|
98
|
+
const error = sandboxError(
|
|
99
|
+
"ERR_BACKGROUND_TASK_SANDBOX_BINDING_INVALID",
|
|
100
|
+
"background_sandbox_binding_invalid",
|
|
101
|
+
"Background task sandbox workspace/cwd binding is invalid.",
|
|
102
|
+
boundaries,
|
|
103
|
+
);
|
|
104
|
+
error.cause = cause;
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function explicitShellInvocation(command) {
|
|
110
|
+
if (_backgroundTaskCommandDeps.platform === "win32") {
|
|
111
|
+
return {
|
|
112
|
+
file: process.env.ComSpec || "cmd.exe",
|
|
113
|
+
args: ["/d", "/s", "/c", command],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { file: "/bin/sh", args: ["-c", command] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function executeBackgroundTaskCommand({
|
|
120
|
+
command,
|
|
121
|
+
cwd,
|
|
122
|
+
type,
|
|
123
|
+
workspaceCwd = "",
|
|
124
|
+
requiredBoundaries = [],
|
|
125
|
+
}) {
|
|
126
|
+
if (
|
|
127
|
+
typeof command !== "string" ||
|
|
128
|
+
command.length === 0 ||
|
|
129
|
+
command.includes("\0")
|
|
130
|
+
) {
|
|
131
|
+
throw new TypeError("background task command must be non-empty");
|
|
132
|
+
}
|
|
133
|
+
const boundaries = normalizeRequiredBoundaries(requiredBoundaries);
|
|
134
|
+
const hasSandboxEnvelope =
|
|
135
|
+
typeof workspaceCwd === "string" && workspaceCwd.length > 0;
|
|
136
|
+
if (hasSandboxEnvelope !== boundaries.length > 0) {
|
|
137
|
+
throw sandboxError(
|
|
138
|
+
"ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
|
|
139
|
+
"background_sandbox_envelope_invalid",
|
|
140
|
+
"Background task sandbox root and boundaries must be supplied together.",
|
|
141
|
+
boundaries,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
boundaries.length > 0 &&
|
|
146
|
+
_backgroundTaskCommandDeps.platform !== "linux"
|
|
147
|
+
) {
|
|
148
|
+
throw sandboxError(
|
|
149
|
+
"ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED",
|
|
150
|
+
"background_platform_backend_unavailable",
|
|
151
|
+
"Background task strong sandbox execution is only available on Linux.",
|
|
152
|
+
boundaries,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const commonOptions = {
|
|
157
|
+
cwd: cwd || process.cwd(),
|
|
158
|
+
encoding: "utf-8",
|
|
159
|
+
timeout: 300000,
|
|
160
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
161
|
+
origin: `background-task:command:${type || "unknown"}`,
|
|
162
|
+
policy: "allow",
|
|
163
|
+
scope: "background-task",
|
|
164
|
+
};
|
|
165
|
+
if (boundaries.length === 0) {
|
|
166
|
+
return _backgroundTaskCommandDeps.execSync(command, {
|
|
167
|
+
...commonOptions,
|
|
168
|
+
shell: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const binding = canonicalBinding(workspaceCwd, commonOptions.cwd, boundaries);
|
|
173
|
+
const sandboxPolicy = Object.freeze({
|
|
174
|
+
requiredBoundaries: Object.freeze(boundaries),
|
|
175
|
+
});
|
|
176
|
+
const invocation = explicitShellInvocation(command);
|
|
177
|
+
const spawnOptions = {
|
|
178
|
+
...commonOptions,
|
|
179
|
+
cwd: binding.workingDirectory,
|
|
180
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
181
|
+
shell: false,
|
|
182
|
+
sandboxPolicy,
|
|
183
|
+
};
|
|
184
|
+
let sandboxExecutionContract;
|
|
185
|
+
try {
|
|
186
|
+
sandboxExecutionContract =
|
|
187
|
+
_backgroundTaskCommandDeps.issueLinuxWorkspaceSandboxExecutionContract(
|
|
188
|
+
invocation.file,
|
|
189
|
+
invocation.args,
|
|
190
|
+
spawnOptions,
|
|
191
|
+
binding.workspaceRoot,
|
|
192
|
+
{ sync: false },
|
|
193
|
+
);
|
|
194
|
+
} catch (cause) {
|
|
195
|
+
const error = sandboxError(
|
|
196
|
+
"ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED",
|
|
197
|
+
"background_linux_execution_contract_unavailable",
|
|
198
|
+
"Background task could not bind its Linux sandbox contract.",
|
|
199
|
+
boundaries,
|
|
200
|
+
);
|
|
201
|
+
error.cause = cause;
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
_backgroundTaskCommandDeps.platform === "linux" &&
|
|
206
|
+
!sandboxExecutionContract
|
|
207
|
+
) {
|
|
208
|
+
throw sandboxError(
|
|
209
|
+
"ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED",
|
|
210
|
+
"background_linux_execution_contract_unavailable",
|
|
211
|
+
"Background task could not bind its Linux sandbox contract.",
|
|
212
|
+
boundaries,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return new Promise((resolve, reject) => {
|
|
216
|
+
const options = {
|
|
217
|
+
...spawnOptions,
|
|
218
|
+
...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
|
|
219
|
+
};
|
|
220
|
+
try {
|
|
221
|
+
_backgroundTaskCommandDeps.execFile(
|
|
222
|
+
invocation.file,
|
|
223
|
+
invocation.args,
|
|
224
|
+
options,
|
|
225
|
+
(error, stdout) => {
|
|
226
|
+
if (error) reject(error);
|
|
227
|
+
else resolve(stdout);
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
reject(error);
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
@@ -8,8 +8,15 @@
|
|
|
8
8
|
* Feature-flag gated: BACKGROUND_TASKS
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
|
|
11
|
+
import {
|
|
12
|
+
existsSync,
|
|
13
|
+
mkdirSync,
|
|
14
|
+
appendFileSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
realpathSync,
|
|
17
|
+
statSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
20
|
import { createHash } from "node:crypto";
|
|
14
21
|
import { EventEmitter } from "node:events";
|
|
15
22
|
import { getHomeDir } from "../lib/paths.js";
|
|
@@ -17,6 +24,7 @@ import executionBroker from "../lib/process-execution-broker/index.js";
|
|
|
17
24
|
|
|
18
25
|
export const _deps = {
|
|
19
26
|
spawn: executionBroker.spawn.bind(executionBroker),
|
|
27
|
+
platform: process.platform,
|
|
20
28
|
};
|
|
21
29
|
|
|
22
30
|
function getTasksDir() {
|
|
@@ -41,6 +49,7 @@ const RECOVERABLE_TASK_STATUSES = new Set([
|
|
|
41
49
|
TaskStatus.PENDING,
|
|
42
50
|
TaskStatus.RUNNING,
|
|
43
51
|
]);
|
|
52
|
+
const SUPPORTED_STRONG_BOUNDARIES = new Set(["filesystem", "network"]);
|
|
44
53
|
|
|
45
54
|
function validatePinnedSandboxPolicy(policy) {
|
|
46
55
|
if (policy == null) return null;
|
|
@@ -51,7 +60,9 @@ function validatePinnedSandboxPolicy(policy) {
|
|
|
51
60
|
!Array.isArray(policy.requiredBoundaries) ||
|
|
52
61
|
policy.requiredBoundaries.length === 0 ||
|
|
53
62
|
policy.requiredBoundaries.some(
|
|
54
|
-
(boundary) =>
|
|
63
|
+
(boundary) =>
|
|
64
|
+
typeof boundary !== "string" ||
|
|
65
|
+
!SUPPORTED_STRONG_BOUNDARIES.has(boundary),
|
|
55
66
|
) ||
|
|
56
67
|
!Object.isFrozen(policy) ||
|
|
57
68
|
!Object.isFrozen(policy.requiredBoundaries)
|
|
@@ -65,22 +76,88 @@ function validatePinnedSandboxPolicy(policy) {
|
|
|
65
76
|
return policy;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
function
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
-
error.code = "ERR_BACKGROUND_TASK_SANDBOX_UNSUPPORTED";
|
|
74
|
-
error.sandboxReason = "background_execution_unsupported";
|
|
79
|
+
function createSandboxError(code, reason, message, requiredBoundaries = []) {
|
|
80
|
+
const error = new Error(message);
|
|
81
|
+
error.code = code;
|
|
82
|
+
error.sandboxReason = reason;
|
|
75
83
|
error.sandboxFailClosed = true;
|
|
76
|
-
error.requiredBoundaries = requiredBoundaries;
|
|
84
|
+
error.requiredBoundaries = [...requiredBoundaries];
|
|
77
85
|
error.actualGuarantees = [];
|
|
78
86
|
error.missingBoundaries = [...requiredBoundaries];
|
|
79
87
|
error.sandboxBackend = null;
|
|
80
|
-
error.sandboxCandidateBackend =
|
|
88
|
+
error.sandboxCandidateBackend =
|
|
89
|
+
_deps.platform === "linux" ? "linux-bwrap-workspace" : null;
|
|
81
90
|
return error;
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
function canonicalSandboxBinding(workspaceCwd, executionCwd) {
|
|
94
|
+
try {
|
|
95
|
+
if (
|
|
96
|
+
typeof workspaceCwd !== "string" ||
|
|
97
|
+
!isAbsolute(workspaceCwd) ||
|
|
98
|
+
typeof executionCwd !== "string"
|
|
99
|
+
) {
|
|
100
|
+
throw new Error("sandbox paths must be absolute");
|
|
101
|
+
}
|
|
102
|
+
const workspaceRoot = realpathSync.native(resolve(workspaceCwd));
|
|
103
|
+
const workingDirectory = realpathSync.native(resolve(executionCwd));
|
|
104
|
+
if (
|
|
105
|
+
!statSync(workspaceRoot).isDirectory() ||
|
|
106
|
+
!statSync(workingDirectory).isDirectory()
|
|
107
|
+
) {
|
|
108
|
+
throw new Error("sandbox paths must be directories");
|
|
109
|
+
}
|
|
110
|
+
const rel = relative(workspaceRoot, workingDirectory);
|
|
111
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
112
|
+
throw new Error("execution cwd escapes workspace");
|
|
113
|
+
}
|
|
114
|
+
return { workspaceRoot, workingDirectory };
|
|
115
|
+
} catch (cause) {
|
|
116
|
+
const error = createSandboxError(
|
|
117
|
+
"ERR_BACKGROUND_TASK_SANDBOX_BINDING_INVALID",
|
|
118
|
+
"background_sandbox_binding_invalid",
|
|
119
|
+
"Background task sandbox workspace/cwd binding is invalid.",
|
|
120
|
+
);
|
|
121
|
+
error.cause = cause;
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function sameBoundaries(left, right) {
|
|
127
|
+
const normalizedLeft = Array.isArray(left) ? [...left].sort() : null;
|
|
128
|
+
const normalizedRight = Array.isArray(right) ? [...right].sort() : null;
|
|
129
|
+
return (
|
|
130
|
+
normalizedLeft &&
|
|
131
|
+
normalizedRight &&
|
|
132
|
+
normalizedLeft.length === normalizedRight.length &&
|
|
133
|
+
normalizedLeft.every((value, index) => value === normalizedRight[index])
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function strongWorkerEnvironment(taskId) {
|
|
138
|
+
const blocked = new Set([
|
|
139
|
+
"BASH_ENV",
|
|
140
|
+
"CC_TASK_ID",
|
|
141
|
+
"ELECTRON_RUN_AS_NODE",
|
|
142
|
+
"ENV",
|
|
143
|
+
"GCONV_PATH",
|
|
144
|
+
"OPENSSL_CONF",
|
|
145
|
+
"RUBYOPT",
|
|
146
|
+
]);
|
|
147
|
+
const blockedPrefixes = ["DYLD_", "LD_", "NODE_", "PYTHON"];
|
|
148
|
+
return Object.fromEntries([
|
|
149
|
+
...Object.entries(process.env).filter(([key, value]) => {
|
|
150
|
+
const normalizedKey = key.toUpperCase();
|
|
151
|
+
return (
|
|
152
|
+
value != null &&
|
|
153
|
+
!blocked.has(normalizedKey) &&
|
|
154
|
+
!blockedPrefixes.some((prefix) => normalizedKey.startsWith(prefix))
|
|
155
|
+
);
|
|
156
|
+
}),
|
|
157
|
+
["CC_TASK_ID", taskId],
|
|
158
|
+
]);
|
|
159
|
+
}
|
|
160
|
+
|
|
84
161
|
export class BackgroundTaskManager extends EventEmitter {
|
|
85
162
|
constructor(options = {}) {
|
|
86
163
|
super();
|
|
@@ -123,14 +200,17 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
123
200
|
}
|
|
124
201
|
|
|
125
202
|
const cwd = spec.cwd || process.cwd();
|
|
126
|
-
this.
|
|
203
|
+
const sandboxPolicy = this._resolvePinnedSandboxPolicy(cwd);
|
|
204
|
+
const sandboxBinding = sandboxPolicy
|
|
205
|
+
? canonicalSandboxBinding(this._policyCwd, cwd)
|
|
206
|
+
: null;
|
|
127
207
|
|
|
128
208
|
const id = `task-${Date.now()}-${createHash("sha256").update(Math.random().toString()).digest("hex").slice(0, 6)}`;
|
|
129
209
|
const task = {
|
|
130
210
|
id,
|
|
131
211
|
type: spec.type || "shell",
|
|
132
212
|
command: spec.command,
|
|
133
|
-
cwd,
|
|
213
|
+
cwd: sandboxBinding?.workingDirectory || cwd,
|
|
134
214
|
description: spec.description || spec.command,
|
|
135
215
|
status: TaskStatus.PENDING,
|
|
136
216
|
createdAt: Date.now(),
|
|
@@ -145,6 +225,10 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
145
225
|
recoverySourceStatus: null,
|
|
146
226
|
ownerNodeId: spec.ownerNodeId || this.nodeId,
|
|
147
227
|
recoveryDecision: null,
|
|
228
|
+
sandboxWorkspaceCwd: sandboxBinding?.workspaceRoot || null,
|
|
229
|
+
sandboxRequiredBoundaries: sandboxPolicy
|
|
230
|
+
? [...sandboxPolicy.requiredBoundaries].sort()
|
|
231
|
+
: null,
|
|
148
232
|
_seq: ++this._createSeq,
|
|
149
233
|
};
|
|
150
234
|
|
|
@@ -164,7 +248,34 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
164
248
|
throw new Error(`Task ${taskId} is not pending (status: ${task.status})`);
|
|
165
249
|
}
|
|
166
250
|
|
|
167
|
-
this.
|
|
251
|
+
const sandboxPolicy = this._resolvePinnedSandboxPolicy(task.cwd);
|
|
252
|
+
const pinnedBoundaries = task.sandboxRequiredBoundaries;
|
|
253
|
+
if (
|
|
254
|
+
Boolean(sandboxPolicy) !== Array.isArray(pinnedBoundaries) ||
|
|
255
|
+
(sandboxPolicy &&
|
|
256
|
+
!sameBoundaries(sandboxPolicy.requiredBoundaries, pinnedBoundaries))
|
|
257
|
+
) {
|
|
258
|
+
throw createSandboxError(
|
|
259
|
+
"ERR_BACKGROUND_TASK_SANDBOX_POLICY_CHANGED",
|
|
260
|
+
"background_sandbox_policy_changed",
|
|
261
|
+
"Background task sandbox policy changed after creation; recreate the task.",
|
|
262
|
+
sandboxPolicy?.requiredBoundaries || pinnedBoundaries || [],
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (sandboxPolicy) {
|
|
266
|
+
const currentBinding = canonicalSandboxBinding(this._policyCwd, task.cwd);
|
|
267
|
+
if (
|
|
268
|
+
currentBinding.workspaceRoot !== task.sandboxWorkspaceCwd ||
|
|
269
|
+
currentBinding.workingDirectory !== task.cwd
|
|
270
|
+
) {
|
|
271
|
+
throw createSandboxError(
|
|
272
|
+
"ERR_BACKGROUND_TASK_SANDBOX_BINDING_CHANGED",
|
|
273
|
+
"background_sandbox_binding_changed",
|
|
274
|
+
"Background task sandbox workspace/cwd identity changed after creation; recreate the task.",
|
|
275
|
+
pinnedBoundaries,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
168
279
|
|
|
169
280
|
task.status = TaskStatus.RUNNING;
|
|
170
281
|
task.startedAt = Date.now();
|
|
@@ -179,11 +290,21 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
179
290
|
);
|
|
180
291
|
const child = _deps.spawn(
|
|
181
292
|
process.execPath,
|
|
182
|
-
[
|
|
293
|
+
[
|
|
294
|
+
...(sandboxPolicy ? [] : process.execArgv),
|
|
295
|
+
workerPath,
|
|
296
|
+
task.command,
|
|
297
|
+
task.cwd,
|
|
298
|
+
task.type,
|
|
299
|
+
task.sandboxWorkspaceCwd || "",
|
|
300
|
+
JSON.stringify(task.sandboxRequiredBoundaries || []),
|
|
301
|
+
],
|
|
183
302
|
{
|
|
184
303
|
cwd: task.cwd,
|
|
185
304
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
186
|
-
env:
|
|
305
|
+
env: sandboxPolicy
|
|
306
|
+
? strongWorkerEnvironment(taskId)
|
|
307
|
+
: { ...process.env, CC_TASK_ID: taskId },
|
|
187
308
|
origin: "background-task:worker",
|
|
188
309
|
policy: "allow",
|
|
189
310
|
scope: "background-task",
|
|
@@ -199,6 +320,9 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
199
320
|
} else if (msg.type === "result") {
|
|
200
321
|
this._complete(taskId, TaskStatus.COMPLETED, msg.data, null);
|
|
201
322
|
} else if (msg.type === "error") {
|
|
323
|
+
task.errorCode = msg.code || null;
|
|
324
|
+
task.sandboxReason = msg.sandboxReason || null;
|
|
325
|
+
task.sandboxFailClosed = msg.sandboxFailClosed === true;
|
|
202
326
|
this._complete(taskId, TaskStatus.FAILED, null, msg.error);
|
|
203
327
|
}
|
|
204
328
|
});
|
|
@@ -240,7 +364,7 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
240
364
|
return task;
|
|
241
365
|
}
|
|
242
366
|
|
|
243
|
-
|
|
367
|
+
_resolvePinnedSandboxPolicy(executionCwd) {
|
|
244
368
|
if (!this._resolveSandboxPolicy) return;
|
|
245
369
|
const sandboxPolicy = validatePinnedSandboxPolicy(
|
|
246
370
|
this._resolveSandboxPolicy({
|
|
@@ -248,9 +372,15 @@ export class BackgroundTaskManager extends EventEmitter {
|
|
|
248
372
|
executionCwd,
|
|
249
373
|
}),
|
|
250
374
|
);
|
|
251
|
-
if (sandboxPolicy) {
|
|
252
|
-
throw
|
|
375
|
+
if (sandboxPolicy && _deps.platform !== "linux") {
|
|
376
|
+
throw createSandboxError(
|
|
377
|
+
"ERR_BACKGROUND_TASK_SANDBOX_UNSUPPORTED",
|
|
378
|
+
"background_platform_backend_unavailable",
|
|
379
|
+
"Background task strong sandbox execution is only available on Linux.",
|
|
380
|
+
sandboxPolicy.requiredBoundaries,
|
|
381
|
+
);
|
|
253
382
|
}
|
|
383
|
+
return sandboxPolicy;
|
|
254
384
|
}
|
|
255
385
|
|
|
256
386
|
get(taskId) {
|
|
@@ -5,24 +5,27 @@
|
|
|
5
5
|
* Sends messages to parent: { type: "heartbeat"|"result"|"error", ... }
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import { executeBackgroundTaskCommand } from "./background-task-command-runner.js";
|
|
9
9
|
|
|
10
|
-
const [
|
|
10
|
+
const [
|
|
11
|
+
command,
|
|
12
|
+
cwd,
|
|
13
|
+
type,
|
|
14
|
+
sandboxWorkspaceCwd = "",
|
|
15
|
+
sandboxRequiredBoundaries = "",
|
|
16
|
+
] = process.argv.slice(2);
|
|
11
17
|
|
|
12
18
|
const heartbeat = setInterval(() => {
|
|
13
19
|
if (process.send) process.send({ type: "heartbeat" });
|
|
14
20
|
}, 5000);
|
|
15
21
|
|
|
16
22
|
try {
|
|
17
|
-
const result =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
policy: "allow",
|
|
24
|
-
scope: "background-task",
|
|
25
|
-
shell: true,
|
|
23
|
+
const result = await executeBackgroundTaskCommand({
|
|
24
|
+
command,
|
|
25
|
+
cwd,
|
|
26
|
+
type,
|
|
27
|
+
workspaceCwd: sandboxWorkspaceCwd,
|
|
28
|
+
requiredBoundaries: sandboxRequiredBoundaries,
|
|
26
29
|
});
|
|
27
30
|
|
|
28
31
|
if (process.send) {
|
|
@@ -33,6 +36,9 @@ try {
|
|
|
33
36
|
process.send({
|
|
34
37
|
type: "error",
|
|
35
38
|
error: err.stderr || err.message || String(err),
|
|
39
|
+
code: err.code || null,
|
|
40
|
+
sandboxReason: err.sandboxReason || null,
|
|
41
|
+
sandboxFailClosed: err.sandboxFailClosed === true,
|
|
36
42
|
});
|
|
37
43
|
}
|
|
38
44
|
process.exitCode = 1;
|
|
@@ -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) {
|
|
@@ -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
|
}
|
package/src/lib/hook-runner.cjs
CHANGED
|
@@ -438,7 +438,13 @@ function _runCommandHookInner(command, input = {}, opts = {}) {
|
|
|
438
438
|
function interpretHookOutcome(res) {
|
|
439
439
|
// A surfaced spawn error (timeout / ENOENT, status null) is non-blocking — a
|
|
440
440
|
// broken hook must never wedge the agent; only an explicit block blocks.
|
|
441
|
-
|
|
441
|
+
// spawnSync can also report EPIPE after a fast-exiting hook closes stdin,
|
|
442
|
+
// even though the child has a real exit status and complete output. The
|
|
443
|
+
// async runner already treats that early stdin close as harmless; preserve
|
|
444
|
+
// the exit-code/stdout protocol on the sync path as well.
|
|
445
|
+
const inputClosedAfterExit =
|
|
446
|
+
res.error?.code === "EPIPE" && Number.isInteger(res.status);
|
|
447
|
+
if (res.error && !inputClosedAfterExit) {
|
|
442
448
|
return {
|
|
443
449
|
decision: HOOK_DECISIONS.CONTINUE,
|
|
444
450
|
reason: `hook error: ${res.error.message}`,
|
package/src/lib/hook-runner.js
CHANGED
|
@@ -107,6 +107,8 @@ function runHookProcessSync(file, argsOrOptions, maybeOptions) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
export function _restoreProcessRunners() {
|
|
110
|
+
_processDeps.run = brokerRunner;
|
|
111
|
+
_processDeps.runSync = brokerSyncRunner;
|
|
110
112
|
runner._deps.run = runHookProcess;
|
|
111
113
|
runner._deps.runSync = runHookProcessSync;
|
|
112
114
|
}
|