bazilion 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +795 -240
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +4532 -1277
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +21 -0
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-CsWpTsy6.js → ProductionTeamPolicyEditor-DdpYInwV.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-C43HvUe-.js → ProfileCommunicationEditor-BcSf2Hao.js} +1 -1
- package/dist/web/client/assets/{TeamTabs-DvqWieCq.js → TeamTabs-Bk89JfN7.js} +1 -1
- package/dist/web/client/assets/{TemplatesTabs-6Jd_xQj7.js → TemplatesTabs-CyDVzrOl.js} +1 -1
- package/dist/web/client/assets/{_id-BrA4Crh_.js → _id-CAJTTnee.js} +1 -1
- package/dist/web/client/assets/{_id-BUuz7Drv.js → _id-CN6jPaBu.js} +1 -1
- package/dist/web/client/assets/{_id-BegmVRmT.js → _id-DGwJqqDc.js} +1 -1
- package/dist/web/client/assets/{_id-AOAT0H_5.js → _id-DWTMH7Eb.js} +1 -1
- package/dist/web/client/assets/{activity-n0Fw9_w3.js → activity-DK_M0-bn.js} +1 -1
- package/dist/web/client/assets/{agents-C50pdxp7.js → agents-B-i5pASm.js} +1 -1
- package/dist/web/client/assets/{agents-B33VQtbl.js → agents-n8U7qNxw.js} +1 -1
- package/dist/web/client/assets/{approvals-DVW0hX8h.js → approvals-CmVxCy0Z.js} +1 -1
- package/dist/web/client/assets/{attention-B2490jcN.js → attention-F0lUQCWE.js} +1 -1
- package/dist/web/client/assets/chevron-down-OndG7r1U.js +1 -0
- package/dist/web/client/assets/config-DuApZ9xS.js +3 -0
- package/dist/web/client/assets/{context-DcFzsfZ3.js → context-C1sMnW6K.js} +1 -1
- package/dist/web/client/assets/dm-sans-latin-400-normal-BwCSEQnW.woff +0 -0
- package/dist/web/client/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 +0 -0
- package/dist/web/client/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 +0 -0
- package/dist/web/client/assets/dm-sans-latin-500-normal-Dr3UlScf.woff +0 -0
- package/dist/web/client/assets/dm-sans-latin-600-normal-Aqo67rzb.woff2 +0 -0
- package/dist/web/client/assets/dm-sans-latin-600-normal-BmdmIIQ2.woff +0 -0
- package/dist/web/client/assets/dm-sans-latin-700-normal-CUSSCpQX.woff +0 -0
- package/dist/web/client/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 +0 -0
- package/dist/web/client/assets/dm-serif-display-latin-400-normal-C5_t9oOD.woff2 +0 -0
- package/dist/web/client/assets/dm-serif-display-latin-400-normal-_kVFYEpx.woff +0 -0
- package/dist/web/client/assets/{inbox-Bn1GbUsJ.js → inbox-D2NypLee.js} +1 -1
- package/dist/web/client/assets/{index-3nrK5iFr.js → index-B3vdrEEg.js} +4 -4
- package/dist/web/client/assets/index-Cofc7zhl.css +1 -0
- package/dist/web/client/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/web/client/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/web/client/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/web/client/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/web/client/assets/{learning-BUX1MFu2.js → learning-zYDCfVxT.js} +1 -1
- package/dist/web/client/assets/{login-CMWpGDKv.js → login-B1Un51Zx.js} +1 -1
- package/dist/web/client/assets/{mcp-lZV08dqc.js → mcp-DSVmJBdv.js} +1 -1
- package/dist/web/client/assets/{members-DAeaY2Cy.js → members-Pl7hXJcN.js} +1 -1
- package/dist/web/client/assets/{memory-HmMWxxdF.js → memory-Co3BEaAq.js} +1 -1
- package/dist/web/client/assets/plus-Dor-_wmL.js +1 -0
- package/dist/web/client/assets/{policy-DaijITy7.js → policy-CHM95pMU.js} +1 -1
- package/dist/web/client/assets/{routes-BNk270AM.js → routes-D29qjwG0.js} +1 -1
- package/dist/web/client/assets/{services-DtrpJrdo.js → services-CCQ_6YWQ.js} +1 -1
- package/dist/web/client/assets/{skills-Cv93A3P3.js → skills-3xAIvzvP.js} +1 -1
- package/dist/web/client/assets/styles-D6RPN7RK.css +2 -0
- package/dist/web/client/assets/{teams-D92R9Pb7.js → teams-Dm_huGxn.js} +1 -1
- package/dist/web/client/assets/{teams-CCFnSNKi.js → teams-UGLyjHYj.js} +1 -1
- package/dist/web/client/assets/telegram-3KvrJVO6.js +1 -0
- package/dist/web/client/assets/tokens-DUSFmV5_.js +1 -0
- package/dist/web/client/assets/{triggers-NT24i4hZ.js → triggers-DlQ2KF1h.js} +1 -1
- package/dist/web/client/assets/{welcome-D0cy68eZ.js → welcome-DkP8qzK1.js} +1 -1
- package/dist/web/server/assets/{_id-CwyGjJeP.js → _id-BENqNBfK.js} +4 -4
- package/dist/web/server/assets/{_id-D3Ap7zZA.js → _id-BFELaMIN.js} +1 -1
- package/dist/web/server/assets/{_id-LrvXU4Nr.js → _id-BQ2zoMiR.js} +1 -1
- package/dist/web/server/assets/{_id-D27T85mU.js → _id-BZWunvy7.js} +4 -4
- package/dist/web/server/assets/{_id-BHYLL8M-.js → _id-BreRHYV2.js} +1 -1
- package/dist/web/server/assets/{_id-C3HY4lWg.js → _id-CPIXIqyO.js} +1 -1
- package/dist/web/server/assets/{_id-FcHdSocl.js → _id-CvYqVJPU.js} +4 -4
- package/dist/web/server/assets/{_id-DinTp_7X.js → _id-DkrNqqTw.js} +3 -3
- package/dist/web/server/assets/{_id-IzadFZV9.js → _id-DvlDkk04.js} +4 -4
- package/dist/web/server/assets/{_id-BhOB8Umi.js → _id-DxCnEFpm.js} +4 -4
- package/dist/web/server/assets/{_id-tB47Vsfp.js → _id-HMy7jM3P.js} +3 -3
- package/dist/web/server/assets/{_id-Dj1yZ3zJ.js → _id-xTD_6ApX.js} +4 -4
- package/dist/web/server/assets/{_tanstack-start-manifest_v-BGDBdnUs.js → _tanstack-start-manifest_v-COkJjXt_.js} +57 -56
- package/dist/web/server/assets/{activity-DohitgwW.js → activity-9VP6QDTH.js} +3 -3
- package/dist/web/server/assets/{activity-BCI4mfNS.js → activity-B6N1T_5N.js} +1 -1
- package/dist/web/server/assets/{activity-D9SMqBhy.js → activity-BazZNfvf.js} +3 -3
- package/dist/web/server/assets/{agents-BY74v7fM.js → agents-BJAv919x.js} +3 -3
- package/dist/web/server/assets/{agents-C9xlWK0Z.js → agents-BpSeOA30.js} +4 -4
- package/dist/web/server/assets/{agents-BvttypqB.js → agents-DY630p6x.js} +3 -3
- package/dist/web/server/assets/{agents-DZuFEj0D.js → agents-DfoLnTyD.js} +1 -1
- package/dist/web/server/assets/{agents-BPKOshR5.js → agents-DjyQk94u.js} +4 -4
- package/dist/web/server/assets/{agents-CgZMHj80.js → agents-wSYNPuyK.js} +1 -1
- package/dist/web/server/assets/{approvals-DCaVw1Mz.js → approvals-BYajNOdE.js} +1 -1
- package/dist/web/server/assets/{approvals-BGU-XGIO.js → approvals-BYi84gwl.js} +3 -3
- package/dist/web/server/assets/{approvals-CWcFHlor.js → approvals-BkUGmYU9.js} +3 -3
- package/dist/web/server/assets/{attention-BxkTf337.js → attention-Bw_wiiqI.js} +3 -3
- package/dist/web/server/assets/{attention-CM-Ks1N9.js → attention-UhqPK_Ro.js} +3 -3
- package/dist/web/server/assets/{attention-w21GQIGH.js → attention-Z3bGuxOD.js} +1 -1
- package/dist/web/server/assets/{auth-byfSx6Ja.js → auth-7BHTH-KS.js} +5 -5
- package/dist/web/server/assets/{config-BDCJdtj2.js → config-BMiajTZP.js} +10 -5
- package/dist/web/server/assets/{config-BmixCpz-.js → config-DGzM28F_.js} +197 -15
- package/dist/web/server/assets/{config-1w2GiJUn.js → config-wYtIhkRm.js} +3 -3
- package/dist/web/server/assets/context-BkDDwbby.js +12 -0
- package/dist/web/server/assets/context-DMc0KyHh.js +12 -0
- package/dist/web/server/assets/{context-DrkrIWun.js → context-poimKI7w.js} +1 -1
- package/dist/web/server/assets/{createServerRpc-pU-IQV2Y.js → createServerRpc-B7Yr1K8i.js} +1 -1
- package/dist/web/server/assets/{createSsrRpc-DFsIAfFd.js → createSsrRpc-6LHNEsSK.js} +1 -1
- package/dist/web/server/assets/{daemon-client-2NRMzlTr.js → daemon-client-CI5NHwNC.js} +16 -4
- package/dist/web/server/assets/{inbox-ZvDNAjaM.js → inbox-0iiSg-Y1.js} +4 -4
- package/dist/web/server/assets/{inbox-BLUzA5sQ.js → inbox-DTTfSuGU.js} +1 -1
- package/dist/web/server/assets/{inbox-CzlKT9Wg.js → inbox-Dv55rGYq.js} +4 -4
- package/dist/web/server/assets/{learning-8zav4pIT.js → learning-C2upa1CE.js} +4 -4
- package/dist/web/server/assets/{learning-B7PBkvER.js → learning-DpyLNVe8.js} +4 -4
- package/dist/web/server/assets/{learning-Cq7K2heb.js → learning-fG8316uc.js} +1 -1
- package/dist/web/server/assets/{login-Bx5LEyGP.js → login-CrFVVUaE.js} +1 -1
- package/dist/web/server/assets/{login-CmWvxfTK.js → login-D_GDeTrB.js} +7 -7
- package/dist/web/server/assets/{mcp-DIWTogHM.js → mcp-BQtp2T87.js} +3 -3
- package/dist/web/server/assets/{mcp-2pwT9HU8.js → mcp-BvnvlVFd.js} +3 -3
- package/dist/web/server/assets/{mcp-BJjLIdP0.js → mcp-DlgXUcAS.js} +1 -1
- package/dist/web/server/assets/members-BP3UTqu1.js +12 -0
- package/dist/web/server/assets/{members-CCv17cjQ.js → members-CteHKPSF.js} +1 -1
- package/dist/web/server/assets/members-DXu2oAS_.js +12 -0
- package/dist/web/server/assets/{memory-Dngc13Nw.js → memory-BqV-Afy1.js} +4 -4
- package/dist/web/server/assets/{memory-a8Kz57Kh.js → memory-C6fX_FKI.js} +1 -1
- package/dist/web/server/assets/{memory-pZ4pZT7p.js → memory-Iq2M0-Yc.js} +4 -4
- package/dist/web/server/assets/{policy-Bb-vEgqg.js → policy-18fdTFO4.js} +3 -3
- package/dist/web/server/assets/{policy-BcGELcc9.js → policy-Bg6u-rTq.js} +1 -1
- package/dist/web/server/assets/{policy-C30AeFIb.js → policy-D7kj3Cy7.js} +3 -3
- package/dist/web/server/assets/public-origin-xV0lm0Ty.js +25 -0
- package/dist/web/server/assets/{router-CIVjTgNp.js → router-D-6TzLRZ.js} +167 -64
- package/dist/web/server/assets/{routes-BrA-U_Hm.js → routes-CNRzy79H.js} +4 -4
- package/dist/web/server/assets/{routes-B6cMLIPk.js → routes-CiPfEldd.js} +1 -1
- package/dist/web/server/assets/{routes-C19CMLZt.js → routes-D933ngZS.js} +4 -4
- package/dist/web/server/assets/{services-B9yBWDX0.js → services-DJRA1A0-.js} +1 -1
- package/dist/web/server/assets/{services-wG8I06oS.js → services-DozuJtzX.js} +3 -3
- package/dist/web/server/assets/{services-C4szdhPx.js → services-OB4LAr2A.js} +3 -3
- package/dist/web/server/assets/{skills-BZdqiDig.js → skills-Babb9jsQ.js} +1 -1
- package/dist/web/server/assets/{skills-C-UgkuCW.js → skills-D5DQL8bM.js} +3 -3
- package/dist/web/server/assets/{skills-a9VSWHWq.js → skills-DfGSJTCB.js} +3 -3
- package/dist/web/server/assets/start-uKQNK6if.js +70 -0
- package/dist/web/server/assets/{teams-CpQ0YgWf.js → teams-Bk6ZT0MD.js} +1 -1
- package/dist/web/server/assets/{teams-jBtvnk59.js → teams-Cc2Czynq.js} +1 -1
- package/dist/web/server/assets/{teams-DqfrEoH6.js → teams-DPnAX4wD.js} +4 -4
- package/dist/web/server/assets/{teams-DEpQR2QA.js → teams-DqC4wZxu.js} +3 -3
- package/dist/web/server/assets/{teams-OIkHuxy1.js → teams-Dz6ZEMRo.js} +3 -3
- package/dist/web/server/assets/{teams-MjqgsmC3.js → teams-be05ci17.js} +3 -3
- package/dist/web/server/assets/{telegram-DNgCWebD.js → telegram-1AWL6lFw.js} +3 -3
- package/dist/web/server/assets/{telegram-CBF8SJzB.js → telegram-DUbDIVGX.js} +122 -3
- package/dist/web/server/assets/{telegram-XwyKSEtj.js → telegram-J4T0XhwQ.js} +3 -3
- package/dist/web/server/assets/{tokens-yyTSfBSN.js → tokens-CJM_0NDa.js} +126 -22
- package/dist/web/server/assets/{tokens-BnwFbV7T.js → tokens-DO5BzNOn.js} +4 -4
- package/dist/web/server/assets/tokens-DjM8GsgS.js +19 -0
- package/dist/web/server/assets/{triggers-CSNNzRIi.js → triggers-B87fycBl.js} +1 -1
- package/dist/web/server/assets/{triggers-BZPl5CHs.js → triggers-BdBx-HwR.js} +4 -4
- package/dist/web/server/assets/{triggers-D79NoSXk.js → triggers-Nezfw-Bd.js} +4 -4
- package/dist/web/server/server.js +30 -30
- package/dist/web-server.js +4 -0
- package/dist/web-server.js.map +1 -1
- package/dist/worker.js +1744 -362
- package/dist/worker.js.map +1 -1
- package/package.json +7 -6
- package/dist/web/client/assets/chevron-down-B0aNNOyn.js +0 -1
- package/dist/web/client/assets/config-CMMxxeX2.js +0 -3
- package/dist/web/client/assets/plus-CPzRJg1V.js +0 -1
- package/dist/web/client/assets/styles-DMltY3su.css +0 -2
- package/dist/web/client/assets/telegram-BF1vdgDB.js +0 -1
- package/dist/web/client/assets/tokens-BXj6hWW-.js +0 -1
- package/dist/web/server/assets/context-B-XDf6Ym.js +0 -12
- package/dist/web/server/assets/context-BXGo5Vfi.js +0 -12
- package/dist/web/server/assets/members-CW5aBe81.js +0 -12
- package/dist/web/server/assets/members-VdPwD6CE.js +0 -12
- package/dist/web/server/assets/start-WH8WzTL_.js +0 -4
- package/dist/web/server/assets/tokens-DPDMSN5O.js +0 -12
package/dist/worker.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// ../daemon/src/runtime/worker/entry.ts
|
|
4
|
-
import { randomUUID as
|
|
5
|
-
import { mkdtempSync as mkdtempSync3, rmSync as rmSync12 } from "fs";
|
|
6
|
-
import { join as join19 } from "path";
|
|
4
|
+
import { randomUUID as randomUUID20 } from "crypto";
|
|
7
5
|
import { Type as Type3 } from "typebox";
|
|
8
6
|
|
|
9
7
|
// ../daemon/src/core/agent/delete.ts
|
|
@@ -166,6 +164,12 @@ var SERVICES = [
|
|
|
166
164
|
kind: "config",
|
|
167
165
|
label: "GCP region",
|
|
168
166
|
placeholder: "us-central1"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
envVar: "GOOGLE_VERTEX_CREDENTIALS_JSON",
|
|
170
|
+
kind: "secret",
|
|
171
|
+
label: "Explicit credentials JSON (protected turns)",
|
|
172
|
+
description: "Service-account or ADC JSON projected only into the selected protected worker; ambient ADC files remain unsupported."
|
|
169
173
|
}
|
|
170
174
|
]
|
|
171
175
|
},
|
|
@@ -244,6 +248,13 @@ var SERVICES = [
|
|
|
244
248
|
hint: "Open-weight models \xB7 together.ai",
|
|
245
249
|
fields: [{ envVar: "TOGETHER_API_KEY", kind: "secret", label: "API key" }]
|
|
246
250
|
},
|
|
251
|
+
{
|
|
252
|
+
id: "baseten",
|
|
253
|
+
displayName: "Baseten",
|
|
254
|
+
category: "provider",
|
|
255
|
+
hint: "Hosted open-weight models \xB7 baseten.co",
|
|
256
|
+
fields: [{ envVar: "BASETEN_API_KEY", kind: "secret", label: "API key" }]
|
|
257
|
+
},
|
|
247
258
|
{
|
|
248
259
|
id: "moonshotai",
|
|
249
260
|
displayName: "Moonshot AI",
|
|
@@ -293,6 +304,13 @@ var SERVICES = [
|
|
|
293
304
|
hint: "Coding-model subscription endpoint in China",
|
|
294
305
|
fields: [{ envVar: "QWEN_TOKEN_PLAN_CN_API_KEY", kind: "secret", label: "API key" }]
|
|
295
306
|
},
|
|
307
|
+
{
|
|
308
|
+
id: "qwen-token-plan-individual",
|
|
309
|
+
displayName: "Qwen Token Plan (Individual)",
|
|
310
|
+
category: "provider",
|
|
311
|
+
hint: "Individual subscription models via the international endpoint",
|
|
312
|
+
fields: [{ envVar: "QWEN_TOKEN_PLAN_API_KEY", kind: "secret", label: "API key" }]
|
|
313
|
+
},
|
|
296
314
|
{
|
|
297
315
|
id: "xiaomi",
|
|
298
316
|
displayName: "Xiaomi MiMo",
|
|
@@ -671,14 +689,25 @@ import { randomUUID as randomUUID8 } from "crypto";
|
|
|
671
689
|
// ../daemon/src/core/repos/secrets.ts
|
|
672
690
|
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
673
691
|
|
|
692
|
+
// ../daemon/src/core/repos/telegram-pairing.ts
|
|
693
|
+
import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
694
|
+
var TTL_MS = 10 * 6e4;
|
|
695
|
+
|
|
674
696
|
// ../daemon/src/core/repos/triggerDispatches.ts
|
|
675
697
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
676
698
|
|
|
677
699
|
// ../daemon/src/core/repos/triggers.ts
|
|
678
700
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
679
701
|
|
|
702
|
+
// ../daemon/src/core/repos/webSessions.ts
|
|
703
|
+
import { createHash as createHash4, randomBytes as randomBytes4, randomUUID as randomUUID12 } from "crypto";
|
|
704
|
+
|
|
680
705
|
// ../daemon/src/core/repos/webTokens.ts
|
|
681
|
-
import { createHash as
|
|
706
|
+
import { createHash as createHash3, randomBytes as randomBytes3, randomUUID as randomUUID11 } from "crypto";
|
|
707
|
+
|
|
708
|
+
// ../daemon/src/core/repos/webSessions.ts
|
|
709
|
+
var SESSION_IDLE_MS = 12 * 60 * 60 * 1e3;
|
|
710
|
+
var SESSION_ABSOLUTE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
682
711
|
|
|
683
712
|
// ../daemon/src/core/secrets.ts
|
|
684
713
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
@@ -718,26 +747,22 @@ function parseSkillContent(raw) {
|
|
|
718
747
|
}
|
|
719
748
|
return { frontmatter: fmObj, body, raw };
|
|
720
749
|
}
|
|
721
|
-
function parseSkillFile(path) {
|
|
722
|
-
const raw = readFileSync7(path, "utf8");
|
|
723
|
-
return parseSkillContent(raw);
|
|
724
|
-
}
|
|
725
750
|
|
|
726
751
|
// ../daemon/src/core/team/delete.ts
|
|
727
|
-
import { randomUUID as
|
|
752
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
728
753
|
import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
|
|
729
754
|
|
|
730
755
|
// ../daemon/src/core/team-policy/adopt.ts
|
|
731
|
-
import { randomUUID as
|
|
756
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
732
757
|
|
|
733
758
|
// ../daemon/src/core/team-policy/authorization.ts
|
|
734
|
-
import { createHash as
|
|
759
|
+
import { createHash as createHash5, randomUUID as randomUUID15 } from "crypto";
|
|
735
760
|
|
|
736
761
|
// ../daemon/src/core/team-policy/source.ts
|
|
737
|
-
import { randomUUID as
|
|
762
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
738
763
|
|
|
739
764
|
// ../daemon/src/core/team-policy/spawn.ts
|
|
740
|
-
import { randomUUID as
|
|
765
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
741
766
|
import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
|
|
742
767
|
|
|
743
768
|
// ../daemon/src/core/team-policy/rm-with-retry.ts
|
|
@@ -1188,8 +1213,11 @@ function piMessagesToProviderView(messages) {
|
|
|
1188
1213
|
}
|
|
1189
1214
|
|
|
1190
1215
|
// ../daemon/src/runtime/pi/session.ts
|
|
1191
|
-
import { existsSync as
|
|
1192
|
-
import { basename as basename3, join as
|
|
1216
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readdirSync as readdirSync8, statSync as statSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
1217
|
+
import { basename as basename3, join as join19 } from "path";
|
|
1218
|
+
import {
|
|
1219
|
+
createAssistantMessageEventStream
|
|
1220
|
+
} from "@earendil-works/pi-ai";
|
|
1193
1221
|
import {
|
|
1194
1222
|
createAgentSession,
|
|
1195
1223
|
DefaultResourceLoader,
|
|
@@ -1204,6 +1232,50 @@ var PROVIDER_ALIASES = {
|
|
|
1204
1232
|
bedrock: "amazon-bedrock",
|
|
1205
1233
|
"azure-openai": "azure-openai-responses"
|
|
1206
1234
|
};
|
|
1235
|
+
var PROTECTED_PROVIDER_NAMES = [
|
|
1236
|
+
"anthropic",
|
|
1237
|
+
"openai",
|
|
1238
|
+
"openai-codex",
|
|
1239
|
+
"google",
|
|
1240
|
+
"google-vertex",
|
|
1241
|
+
"azure-openai",
|
|
1242
|
+
"bedrock",
|
|
1243
|
+
"mistral",
|
|
1244
|
+
"groq",
|
|
1245
|
+
"cerebras",
|
|
1246
|
+
"xai",
|
|
1247
|
+
"zai",
|
|
1248
|
+
"huggingface",
|
|
1249
|
+
"openrouter",
|
|
1250
|
+
"vercel-ai-gateway",
|
|
1251
|
+
"deepseek",
|
|
1252
|
+
"fireworks",
|
|
1253
|
+
"together",
|
|
1254
|
+
"baseten",
|
|
1255
|
+
"moonshotai",
|
|
1256
|
+
"moonshotai-cn",
|
|
1257
|
+
"kimi-coding",
|
|
1258
|
+
"minimax",
|
|
1259
|
+
"minimax-cn",
|
|
1260
|
+
"qwen-token-plan",
|
|
1261
|
+
"qwen-token-plan-cn",
|
|
1262
|
+
"qwen-token-plan-individual",
|
|
1263
|
+
"xiaomi",
|
|
1264
|
+
"xiaomi-token-plan-ams",
|
|
1265
|
+
"xiaomi-token-plan-cn",
|
|
1266
|
+
"xiaomi-token-plan-sgp",
|
|
1267
|
+
"ant-ling",
|
|
1268
|
+
"nvidia",
|
|
1269
|
+
"opencode",
|
|
1270
|
+
"opencode-go",
|
|
1271
|
+
"zai-coding-cn",
|
|
1272
|
+
"github-copilot",
|
|
1273
|
+
"cloudflare-ai-gateway",
|
|
1274
|
+
"cloudflare-workers-ai",
|
|
1275
|
+
"lmstudio",
|
|
1276
|
+
"ollama",
|
|
1277
|
+
"llamacpp"
|
|
1278
|
+
];
|
|
1207
1279
|
var LOCAL_PROVIDERS = {
|
|
1208
1280
|
lmstudio: {
|
|
1209
1281
|
baseUrl: "http://127.0.0.1:1234/v1",
|
|
@@ -1213,7 +1285,7 @@ var LOCAL_PROVIDERS = {
|
|
|
1213
1285
|
ollama: { baseUrl: "http://127.0.0.1:11434/v1", apiKey: "ollama", authHeader: false },
|
|
1214
1286
|
llamacpp: { baseUrl: "http://127.0.0.1:8080/v1", apiKey: "no-key", authHeader: true }
|
|
1215
1287
|
};
|
|
1216
|
-
var
|
|
1288
|
+
var PROVIDER_API_KEY_ENV = {
|
|
1217
1289
|
anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
|
1218
1290
|
openai: ["OPENAI_API_KEY"],
|
|
1219
1291
|
google: ["GEMINI_API_KEY"],
|
|
@@ -1229,6 +1301,7 @@ var API_KEY_ENV = {
|
|
|
1229
1301
|
deepseek: ["DEEPSEEK_API_KEY"],
|
|
1230
1302
|
fireworks: ["FIREWORKS_API_KEY"],
|
|
1231
1303
|
together: ["TOGETHER_API_KEY"],
|
|
1304
|
+
baseten: ["BASETEN_API_KEY"],
|
|
1232
1305
|
moonshotai: ["MOONSHOT_API_KEY"],
|
|
1233
1306
|
"moonshotai-cn": ["MOONSHOT_CN_API_KEY"],
|
|
1234
1307
|
"kimi-coding": ["KIMI_API_KEY"],
|
|
@@ -1236,6 +1309,7 @@ var API_KEY_ENV = {
|
|
|
1236
1309
|
"minimax-cn": ["MINIMAX_CN_API_KEY"],
|
|
1237
1310
|
"qwen-token-plan": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
1238
1311
|
"qwen-token-plan-cn": ["QWEN_TOKEN_PLAN_CN_API_KEY"],
|
|
1312
|
+
"qwen-token-plan-individual": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
1239
1313
|
xiaomi: ["XIAOMI_API_KEY"],
|
|
1240
1314
|
"xiaomi-token-plan-ams": ["XIAOMI_TOKEN_PLAN_AMS_API_KEY"],
|
|
1241
1315
|
"xiaomi-token-plan-cn": ["XIAOMI_TOKEN_PLAN_CN_API_KEY"],
|
|
@@ -1252,7 +1326,7 @@ var API_KEY_ENV = {
|
|
|
1252
1326
|
ollama: ["OLLAMA_API_KEY"],
|
|
1253
1327
|
llamacpp: ["LLAMACPP_API_KEY"]
|
|
1254
1328
|
};
|
|
1255
|
-
var
|
|
1329
|
+
var PROVIDER_CREDENTIAL_ENV = {
|
|
1256
1330
|
bedrock: [
|
|
1257
1331
|
"AWS_PROFILE",
|
|
1258
1332
|
"AWS_BEARER_TOKEN_BEDROCK",
|
|
@@ -1274,7 +1348,7 @@ function piProviderName(providerName) {
|
|
|
1274
1348
|
return PROVIDER_ALIASES[providerName] ?? providerName;
|
|
1275
1349
|
}
|
|
1276
1350
|
function providerApiKey(providerName, env) {
|
|
1277
|
-
for (const name of
|
|
1351
|
+
for (const name of PROVIDER_API_KEY_ENV[providerName] ?? []) {
|
|
1278
1352
|
const value = env[name];
|
|
1279
1353
|
if (value) return value;
|
|
1280
1354
|
}
|
|
@@ -1296,7 +1370,7 @@ function providerBaseUrl(providerName, env) {
|
|
|
1296
1370
|
}
|
|
1297
1371
|
function providerCredentialEnv(providerName, env) {
|
|
1298
1372
|
const result = {};
|
|
1299
|
-
for (const name of
|
|
1373
|
+
for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {
|
|
1300
1374
|
const value = env[name];
|
|
1301
1375
|
if (value !== void 0) result[name] = value;
|
|
1302
1376
|
}
|
|
@@ -1341,11 +1415,23 @@ async function createBazilionPiRuntime(options) {
|
|
|
1341
1415
|
const apiKey = options.apiKey ?? providerApiKey(providerName, options.env);
|
|
1342
1416
|
const credentialEnv = providerCredentialEnv(providerName, options.env);
|
|
1343
1417
|
if (apiKey || credentialEnv) {
|
|
1344
|
-
await credentials.modify(
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1418
|
+
await credentials.modify(
|
|
1419
|
+
canonicalName,
|
|
1420
|
+
async () => canonicalName === "openai-codex" && apiKey ? {
|
|
1421
|
+
type: "oauth",
|
|
1422
|
+
access: apiKey,
|
|
1423
|
+
// Bazilion owns refresh outside Pi. This deliberately empty value
|
|
1424
|
+
// satisfies Pi's canonical OAuth shape but is never used because
|
|
1425
|
+
// the token is long-lived only inside this one-shot runtime and
|
|
1426
|
+
// mid-turn replacement is supplied through Agent.getApiKey.
|
|
1427
|
+
refresh: "",
|
|
1428
|
+
expires: Number.MAX_SAFE_INTEGER
|
|
1429
|
+
} : {
|
|
1430
|
+
type: "api_key",
|
|
1431
|
+
...apiKey ? { key: apiKey } : {},
|
|
1432
|
+
...credentialEnv ? { env: credentialEnv } : {}
|
|
1433
|
+
}
|
|
1434
|
+
);
|
|
1349
1435
|
}
|
|
1350
1436
|
const runtime = await ModelRuntime.create({ credentials, modelsPath: null });
|
|
1351
1437
|
const local = LOCAL_PROVIDERS[providerName];
|
|
@@ -1365,6 +1451,17 @@ async function createBazilionPiRuntime(options) {
|
|
|
1365
1451
|
}
|
|
1366
1452
|
return runtime;
|
|
1367
1453
|
}
|
|
1454
|
+
async function createProtectedPiRuntime(options) {
|
|
1455
|
+
const env = {};
|
|
1456
|
+
for (const field of options.credentialEnv ?? []) env[field.name] = field.value;
|
|
1457
|
+
return createBazilionPiRuntime({
|
|
1458
|
+
providerName: options.providerName,
|
|
1459
|
+
modelId: options.modelId,
|
|
1460
|
+
env,
|
|
1461
|
+
apiKey: options.apiKey,
|
|
1462
|
+
baseUrl: options.baseUrl
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1368
1465
|
function resolvePiModel(runtime, providerName, modelId, baseUrl) {
|
|
1369
1466
|
const known = runtime.getModel(piProviderName(providerName), modelId);
|
|
1370
1467
|
if (known) return baseUrl ? { ...known, baseUrl } : known;
|
|
@@ -1380,8 +1477,47 @@ import {
|
|
|
1380
1477
|
} from "@earendil-works/pi-ai";
|
|
1381
1478
|
|
|
1382
1479
|
// ../daemon/src/runtime/session/prompt.ts
|
|
1383
|
-
import { existsSync as existsSync10, readdirSync as readdirSync6
|
|
1480
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
|
|
1384
1481
|
import { join as join13 } from "path";
|
|
1482
|
+
|
|
1483
|
+
// ../daemon/src/runtime/safe-files.ts
|
|
1484
|
+
import {
|
|
1485
|
+
closeSync as closeSync2,
|
|
1486
|
+
constants as constants2,
|
|
1487
|
+
fstatSync as fstatSync2,
|
|
1488
|
+
lstatSync as lstatSync2,
|
|
1489
|
+
mkdirSync as mkdirSync5,
|
|
1490
|
+
openSync as openSync2,
|
|
1491
|
+
readFileSync as readFileSync9,
|
|
1492
|
+
realpathSync as realpathSync2
|
|
1493
|
+
} from "fs";
|
|
1494
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve4 } from "path";
|
|
1495
|
+
function resolveRealDirectory(path) {
|
|
1496
|
+
const requested = resolve4(path);
|
|
1497
|
+
const entry = lstatSync2(requested);
|
|
1498
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
1499
|
+
throw new Error("expected a real directory");
|
|
1500
|
+
}
|
|
1501
|
+
return realpathSync2(requested);
|
|
1502
|
+
}
|
|
1503
|
+
function readRegularFileNoFollow(path) {
|
|
1504
|
+
const entry = lstatSync2(path);
|
|
1505
|
+
if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
|
|
1506
|
+
if (!entry.isFile()) throw new Error("target is not a regular file");
|
|
1507
|
+
let fd;
|
|
1508
|
+
try {
|
|
1509
|
+
fd = openSync2(path, constants2.O_RDONLY | noFollowFlag());
|
|
1510
|
+
if (!fstatSync2(fd).isFile()) throw new Error("target is not a regular file");
|
|
1511
|
+
return readFileSync9(fd, "utf8");
|
|
1512
|
+
} finally {
|
|
1513
|
+
if (fd !== void 0) closeSync2(fd);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function noFollowFlag() {
|
|
1517
|
+
return typeof constants2.O_NOFOLLOW === "number" ? constants2.O_NOFOLLOW : 0;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// ../daemon/src/runtime/session/prompt.ts
|
|
1385
1521
|
var SANDBOX_SKILLS_DIR = "/skills";
|
|
1386
1522
|
function loadPromptSkills(skillsDir, attachedNames) {
|
|
1387
1523
|
if (!existsSync10(skillsDir) || attachedNames.length === 0) return [];
|
|
@@ -1391,7 +1527,7 @@ function loadPromptSkills(skillsDir, attachedNames) {
|
|
|
1391
1527
|
for (const entry of discovered) {
|
|
1392
1528
|
const hostDir = join13(skillsDir, entry.name);
|
|
1393
1529
|
try {
|
|
1394
|
-
const parsed =
|
|
1530
|
+
const parsed = parseSkillContent(readRegularFileNoFollow(join13(hostDir, "SKILL.md")));
|
|
1395
1531
|
const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, "_") || "skill";
|
|
1396
1532
|
loaded.push({
|
|
1397
1533
|
name: entry.name,
|
|
@@ -1406,13 +1542,30 @@ function loadPromptSkills(skillsDir, attachedNames) {
|
|
|
1406
1542
|
return loaded;
|
|
1407
1543
|
}
|
|
1408
1544
|
var CONTEXT_FILE_ORDER = ["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"];
|
|
1545
|
+
function loadProtectedHomeDocuments(agentDir) {
|
|
1546
|
+
const root = resolveRealDirectory(agentDir);
|
|
1547
|
+
const read = (file) => {
|
|
1548
|
+
try {
|
|
1549
|
+
return readRegularFileNoFollow(join13(root, file));
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
if (error.code === "ENOENT") return null;
|
|
1552
|
+
throw new Error(`private Agent home is unsafe at ${file}`);
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
return {
|
|
1556
|
+
"AGENTS.md": read("AGENTS.md"),
|
|
1557
|
+
"SOUL.md": read("SOUL.md"),
|
|
1558
|
+
"TOOLS.md": read("TOOLS.md"),
|
|
1559
|
+
"IDENTITY.md": read("IDENTITY.md"),
|
|
1560
|
+
"BOOTSTRAP.md": read("BOOTSTRAP.md")
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1409
1563
|
function buildSystemPrompt(agent, options = {}) {
|
|
1410
1564
|
const parts = [];
|
|
1565
|
+
const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir);
|
|
1411
1566
|
const contextBlocks = [];
|
|
1412
1567
|
for (const file of CONTEXT_FILE_ORDER) {
|
|
1413
|
-
const
|
|
1414
|
-
if (!existsSync10(path)) continue;
|
|
1415
|
-
const content = readFileSync9(path, "utf8").trimEnd();
|
|
1568
|
+
const content = homeDocuments[file]?.trimEnd();
|
|
1416
1569
|
if (!content) continue;
|
|
1417
1570
|
contextBlocks.push(`## ${file}
|
|
1418
1571
|
|
|
@@ -1423,27 +1576,24 @@ ${content}`);
|
|
|
1423
1576
|
|
|
1424
1577
|
${contextBlocks.join("\n\n")}`);
|
|
1425
1578
|
}
|
|
1426
|
-
const
|
|
1427
|
-
if (
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
].join("\n")
|
|
1445
|
-
);
|
|
1446
|
-
}
|
|
1579
|
+
const bootstrap = homeDocuments["BOOTSTRAP.md"]?.trimEnd();
|
|
1580
|
+
if (bootstrap) {
|
|
1581
|
+
parts.push(
|
|
1582
|
+
[
|
|
1583
|
+
"# First-Run Ritual",
|
|
1584
|
+
"",
|
|
1585
|
+
"This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.",
|
|
1586
|
+
"",
|
|
1587
|
+
"## Hard rules",
|
|
1588
|
+
"- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
|
|
1589
|
+
"- Each subsequent turn: at most one new question. Wait between turns.",
|
|
1590
|
+
"- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
|
|
1591
|
+
"",
|
|
1592
|
+
"## BOOTSTRAP.md",
|
|
1593
|
+
"",
|
|
1594
|
+
bootstrap
|
|
1595
|
+
].join("\n")
|
|
1596
|
+
);
|
|
1447
1597
|
}
|
|
1448
1598
|
parts.push(
|
|
1449
1599
|
[
|
|
@@ -1488,7 +1638,7 @@ ${contextBlocks.join("\n\n")}`);
|
|
|
1488
1638
|
const teamLines = [
|
|
1489
1639
|
"# Team",
|
|
1490
1640
|
"",
|
|
1491
|
-
`- ${agent.team.id} (${agent.team.name}): ${agent.team.path}`,
|
|
1641
|
+
`- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === "docker" ? "/workspace" : agent.team.path}`,
|
|
1492
1642
|
"",
|
|
1493
1643
|
"Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Use the workspace tools currently exposed to work there. Never use workspace tools to edit your identity/soul/behaviour files \u2014 those live in your private home and are reached via `home_write` / `home_read`."
|
|
1494
1644
|
];
|
|
@@ -1538,8 +1688,8 @@ function boundReviewedLessons(lessons, maxCharacters = 8e3) {
|
|
|
1538
1688
|
}
|
|
1539
1689
|
|
|
1540
1690
|
// ../daemon/src/runtime/shell/tooling.ts
|
|
1541
|
-
import { existsSync as existsSync11, lstatSync as
|
|
1542
|
-
import { dirname as
|
|
1691
|
+
import { existsSync as existsSync11, lstatSync as lstatSync3, realpathSync as realpathSync3 } from "fs";
|
|
1692
|
+
import { dirname as dirname4, isAbsolute as isAbsolute4, join as join15, relative as relative4, sep as sep4 } from "path";
|
|
1543
1693
|
import {
|
|
1544
1694
|
createBashToolDefinition,
|
|
1545
1695
|
createLocalBashOperations,
|
|
@@ -1926,11 +2076,11 @@ function createApprovalGatedBashTool(base, host) {
|
|
|
1926
2076
|
|
|
1927
2077
|
// ../daemon/src/runtime/shell/docker.ts
|
|
1928
2078
|
import { spawn } from "child_process";
|
|
1929
|
-
import { randomUUID as
|
|
1930
|
-
import { closeSync as
|
|
1931
|
-
import { access, realpath, stat } from "fs/promises";
|
|
2079
|
+
import { randomUUID as randomUUID18 } from "crypto";
|
|
2080
|
+
import { closeSync as closeSync3, constants as constants3, mkdtempSync as mkdtempSync2, openSync as openSync3, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
2081
|
+
import { access, lstat, realpath, stat } from "fs/promises";
|
|
1932
2082
|
import { tmpdir as tmpdir2 } from "os";
|
|
1933
|
-
import { isAbsolute as
|
|
2083
|
+
import { delimiter, isAbsolute as isAbsolute3, join as join14, relative as relative3, sep as sep3 } from "path";
|
|
1934
2084
|
var CONTAINER_WORKDIR = "/workspace";
|
|
1935
2085
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
1936
2086
|
var MAX_STDERR_CHARS = 16 * 1024;
|
|
@@ -1966,6 +2116,14 @@ var CONTAINER_LITERAL_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
1966
2116
|
"TMPDIR",
|
|
1967
2117
|
"TZ"
|
|
1968
2118
|
]);
|
|
2119
|
+
var PROTECTED_CONTAINER_ENV = {
|
|
2120
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
2121
|
+
HOME: "/tmp",
|
|
2122
|
+
LANG: "C.UTF-8",
|
|
2123
|
+
LC_ALL: "C.UTF-8",
|
|
2124
|
+
SHELL: "/bin/bash",
|
|
2125
|
+
TMPDIR: "/tmp"
|
|
2126
|
+
};
|
|
1969
2127
|
function buildDockerRunSpec(input) {
|
|
1970
2128
|
const executable = input.dockerPath ?? "docker";
|
|
1971
2129
|
validateDockerPath(executable);
|
|
@@ -2048,6 +2206,143 @@ function buildDockerRunSpec(input) {
|
|
|
2048
2206
|
` } : {}
|
|
2049
2207
|
};
|
|
2050
2208
|
}
|
|
2209
|
+
function createPreparedDockerBashOperations(runtime) {
|
|
2210
|
+
validatePreparedDockerRuntime(runtime);
|
|
2211
|
+
return {
|
|
2212
|
+
async exec(command, cwd, { onData, signal, timeout }) {
|
|
2213
|
+
const timeoutMs = resolveTimeoutMs(timeout);
|
|
2214
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
2215
|
+
const workspace = await recheckProtectedMount(runtime.workspace);
|
|
2216
|
+
const requestedCwd = await resolveMountSource(cwd);
|
|
2217
|
+
if (requestedCwd !== workspace.source) {
|
|
2218
|
+
throw new Error("Docker sandbox execution workspace no longer matches preflight");
|
|
2219
|
+
}
|
|
2220
|
+
const readOnlyMounts = await Promise.all(
|
|
2221
|
+
runtime.readOnlyMounts.map((mount) => recheckProtectedMount(mount))
|
|
2222
|
+
);
|
|
2223
|
+
await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity);
|
|
2224
|
+
await assertLocalDockerSocket(runtime.endpoint);
|
|
2225
|
+
const dockerClientEnv = { DOCKER_HOST: runtime.endpoint };
|
|
2226
|
+
const currentImageId = await inspectSandboxImage(
|
|
2227
|
+
runtime.dockerPath,
|
|
2228
|
+
runtime.image,
|
|
2229
|
+
dockerClientEnv
|
|
2230
|
+
);
|
|
2231
|
+
if (currentImageId !== runtime.imageId) {
|
|
2232
|
+
throw new Error("Docker sandbox image no longer matches its preflighted immutable id");
|
|
2233
|
+
}
|
|
2234
|
+
await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity);
|
|
2235
|
+
if (signal?.aborted) throw new Error("aborted");
|
|
2236
|
+
const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
2237
|
+
const spec = buildDockerRunSpec({
|
|
2238
|
+
image: runtime.image,
|
|
2239
|
+
resolvedImage: runtime.imageId,
|
|
2240
|
+
env: { ...runtime.containerEnv },
|
|
2241
|
+
dockerPath: runtime.dockerPath,
|
|
2242
|
+
hostEnv: dockerClientEnv,
|
|
2243
|
+
command,
|
|
2244
|
+
mountSource: workspace.source,
|
|
2245
|
+
readOnlyMounts,
|
|
2246
|
+
containerName,
|
|
2247
|
+
uid: runtime.uid,
|
|
2248
|
+
gid: runtime.gid
|
|
2249
|
+
});
|
|
2250
|
+
return executeDockerRun(spec, { onData, signal, timeout, timeoutMs });
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
async function recheckProtectedMount(mount) {
|
|
2255
|
+
const sourceInfo = await lstat(mount.source);
|
|
2256
|
+
if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {
|
|
2257
|
+
throw new Error("Docker sandbox mount no longer matches preflight");
|
|
2258
|
+
}
|
|
2259
|
+
const sourceRoot = await resolveRequiredRoot(mount.sourceRoot);
|
|
2260
|
+
const source = await resolveMountSource(mount.source);
|
|
2261
|
+
if (sourceRoot !== mount.sourceRoot || source !== mount.source || !isWithin(sourceRoot, source)) {
|
|
2262
|
+
throw new Error("Docker sandbox mount no longer matches preflight");
|
|
2263
|
+
}
|
|
2264
|
+
return { ...mount, source, sourceRoot };
|
|
2265
|
+
}
|
|
2266
|
+
async function resolveRequiredRoot(root) {
|
|
2267
|
+
if (!isAbsolute3(root)) throw new Error("Docker sandbox mount root must be absolute");
|
|
2268
|
+
return resolveMountSource(root);
|
|
2269
|
+
}
|
|
2270
|
+
async function inspectDockerExecutableIdentity(dockerPath) {
|
|
2271
|
+
try {
|
|
2272
|
+
await access(dockerPath, constants3.X_OK);
|
|
2273
|
+
const info = await stat(dockerPath, { bigint: true });
|
|
2274
|
+
if (!info.isFile()) throw new Error("not a regular file");
|
|
2275
|
+
return {
|
|
2276
|
+
device: info.dev.toString(),
|
|
2277
|
+
inode: info.ino.toString(),
|
|
2278
|
+
mode: info.mode.toString(),
|
|
2279
|
+
size: info.size.toString(),
|
|
2280
|
+
modifiedTimeNs: info.mtimeNs.toString(),
|
|
2281
|
+
changedTimeNs: info.ctimeNs.toString()
|
|
2282
|
+
};
|
|
2283
|
+
} catch {
|
|
2284
|
+
throw new Error(`Docker sandbox executable is unavailable: ${dockerPath}`);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
async function assertDockerExecutableIdentity(dockerPath, expected) {
|
|
2288
|
+
let current;
|
|
2289
|
+
try {
|
|
2290
|
+
current = await inspectDockerExecutableIdentity(dockerPath);
|
|
2291
|
+
} catch {
|
|
2292
|
+
throw new Error("Docker sandbox executable no longer matches its preflighted identity");
|
|
2293
|
+
}
|
|
2294
|
+
if (JSON.stringify(current) !== JSON.stringify(expected)) {
|
|
2295
|
+
throw new Error("Docker sandbox executable no longer matches its preflighted identity");
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
async function assertLocalDockerSocket(endpoint) {
|
|
2299
|
+
const prefix = "unix://";
|
|
2300
|
+
if (!endpoint.startsWith(prefix) || !isAbsolute3(endpoint.slice(prefix.length))) {
|
|
2301
|
+
throw new Error("Docker sandbox requires a local Unix-socket context");
|
|
2302
|
+
}
|
|
2303
|
+
const socketPath = endpoint.slice(prefix.length);
|
|
2304
|
+
try {
|
|
2305
|
+
const info = await stat(socketPath);
|
|
2306
|
+
if (!info.isSocket()) throw new Error("not a Unix socket");
|
|
2307
|
+
await access(socketPath, constants3.R_OK | constants3.W_OK);
|
|
2308
|
+
} catch {
|
|
2309
|
+
throw new Error("Docker sandbox local Unix socket is unavailable");
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
function validatePreparedDockerRuntime(runtime) {
|
|
2313
|
+
if (!isAbsolute3(runtime.dockerPath)) {
|
|
2314
|
+
throw new Error("protected Docker executable must be absolute");
|
|
2315
|
+
}
|
|
2316
|
+
validateDockerPath(runtime.dockerPath);
|
|
2317
|
+
validateDockerExecutableIdentity(runtime.executableIdentity);
|
|
2318
|
+
validateImage(runtime.image);
|
|
2319
|
+
validateImage(runtime.imageId);
|
|
2320
|
+
validateHostIdentity(runtime.uid, runtime.gid);
|
|
2321
|
+
if (!runtime.endpoint.startsWith("unix://")) {
|
|
2322
|
+
throw new Error("protected Docker endpoint must be a local Unix socket");
|
|
2323
|
+
}
|
|
2324
|
+
validateReadOnlyMounts(runtime.readOnlyMounts, runtime.workspace.source);
|
|
2325
|
+
const expectedEnvironment = JSON.stringify(PROTECTED_CONTAINER_ENV);
|
|
2326
|
+
if (JSON.stringify(runtime.containerEnv) !== expectedEnvironment) {
|
|
2327
|
+
throw new Error("protected Docker container environment does not match policy");
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
function validateDockerExecutableIdentity(identity) {
|
|
2331
|
+
if (typeof identity !== "object" || identity === null) {
|
|
2332
|
+
throw new Error("protected Docker executable identity is invalid");
|
|
2333
|
+
}
|
|
2334
|
+
const fields = [
|
|
2335
|
+
identity.device,
|
|
2336
|
+
identity.inode,
|
|
2337
|
+
identity.mode,
|
|
2338
|
+
identity.size,
|
|
2339
|
+
identity.modifiedTimeNs,
|
|
2340
|
+
identity.changedTimeNs
|
|
2341
|
+
];
|
|
2342
|
+
if (fields.some((field) => typeof field !== "string" || !/^\d+$/.test(field))) {
|
|
2343
|
+
throw new Error("protected Docker executable identity is invalid");
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2051
2346
|
function createDockerBashOperations(options) {
|
|
2052
2347
|
const image = options.image;
|
|
2053
2348
|
const env = { ...options.env };
|
|
@@ -2077,7 +2372,7 @@ function createDockerBashOperations(options) {
|
|
|
2077
2372
|
const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
|
|
2078
2373
|
const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
|
|
2079
2374
|
if (signal?.aborted) throw new Error("aborted");
|
|
2080
|
-
const containerName = `bazilion-bash-${process.pid}-${
|
|
2375
|
+
const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
2081
2376
|
const spec = buildDockerRunSpec({
|
|
2082
2377
|
image,
|
|
2083
2378
|
env,
|
|
@@ -2108,13 +2403,13 @@ function executeDockerRun(spec, options) {
|
|
|
2108
2403
|
)
|
|
2109
2404
|
);
|
|
2110
2405
|
}
|
|
2111
|
-
return new Promise((
|
|
2406
|
+
return new Promise((resolve6, reject) => {
|
|
2112
2407
|
const child = spawn(spec.executable, spec.args, {
|
|
2113
2408
|
stdio: ["ignore", "pipe", "pipe", envFileFd ?? "ignore"],
|
|
2114
2409
|
windowsHide: true,
|
|
2115
2410
|
env: spec.processEnv
|
|
2116
2411
|
});
|
|
2117
|
-
if (envFileFd !== void 0)
|
|
2412
|
+
if (envFileFd !== void 0) closeSync3(envFileFd);
|
|
2118
2413
|
let settled = false;
|
|
2119
2414
|
let termination;
|
|
2120
2415
|
let timeoutHandle;
|
|
@@ -2151,7 +2446,7 @@ function executeDockerRun(spec, options) {
|
|
|
2151
2446
|
reject(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
|
|
2152
2447
|
return;
|
|
2153
2448
|
}
|
|
2154
|
-
|
|
2449
|
+
resolve6({ exitCode });
|
|
2155
2450
|
};
|
|
2156
2451
|
child.stdout?.on("data", options.onData);
|
|
2157
2452
|
child.stderr?.on("data", (data) => {
|
|
@@ -2175,11 +2470,11 @@ function openAnonymousEnvFile(content) {
|
|
|
2175
2470
|
let fd;
|
|
2176
2471
|
try {
|
|
2177
2472
|
writeFileSync7(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
2178
|
-
fd =
|
|
2473
|
+
fd = openSync3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
2179
2474
|
rmSync10(dir, { recursive: true, force: true });
|
|
2180
2475
|
return fd;
|
|
2181
2476
|
} catch (error) {
|
|
2182
|
-
if (fd !== void 0)
|
|
2477
|
+
if (fd !== void 0) closeSync3(fd);
|
|
2183
2478
|
rmSync10(dir, { recursive: true, force: true });
|
|
2184
2479
|
throw error;
|
|
2185
2480
|
}
|
|
@@ -2197,7 +2492,7 @@ function resolveTimeoutMs(timeout) {
|
|
|
2197
2492
|
}
|
|
2198
2493
|
async function resolveMountSource(cwd) {
|
|
2199
2494
|
try {
|
|
2200
|
-
await access(cwd,
|
|
2495
|
+
await access(cwd, constants3.F_OK);
|
|
2201
2496
|
const info = await stat(cwd);
|
|
2202
2497
|
if (!info.isDirectory()) {
|
|
2203
2498
|
throw new Error(`Docker sandbox mount is not a directory: ${cwd}`);
|
|
@@ -2251,7 +2546,7 @@ async function resolveLocalDockerClientEnv(dockerPath, hostEnv) {
|
|
|
2251
2546
|
const discoveryEnv = buildDockerClientEnv(hostEnv);
|
|
2252
2547
|
const endpoint = await inspectDockerEndpoint(dockerPath, discoveryEnv);
|
|
2253
2548
|
const prefix = "unix://";
|
|
2254
|
-
if (!endpoint.startsWith(prefix) || !
|
|
2549
|
+
if (!endpoint.startsWith(prefix) || !isAbsolute3(endpoint.slice(prefix.length))) {
|
|
2255
2550
|
throw new Error(
|
|
2256
2551
|
`Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint)}`
|
|
2257
2552
|
);
|
|
@@ -2320,7 +2615,7 @@ async function inspectSandboxImage(dockerPath, image, processEnv) {
|
|
|
2320
2615
|
return id;
|
|
2321
2616
|
}
|
|
2322
2617
|
function runDockerInspection(dockerPath, args, processEnv, subject) {
|
|
2323
|
-
return new Promise((
|
|
2618
|
+
return new Promise((resolve6, reject) => {
|
|
2324
2619
|
const child = spawn(dockerPath, args, {
|
|
2325
2620
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2326
2621
|
windowsHide: true,
|
|
@@ -2337,7 +2632,7 @@ function runDockerInspection(dockerPath, args, processEnv, subject) {
|
|
|
2337
2632
|
reject(error);
|
|
2338
2633
|
return;
|
|
2339
2634
|
}
|
|
2340
|
-
|
|
2635
|
+
resolve6(stdout.trim());
|
|
2341
2636
|
};
|
|
2342
2637
|
const timeoutHandle = setTimeout(() => {
|
|
2343
2638
|
child.kill("SIGKILL");
|
|
@@ -2402,7 +2697,7 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
|
|
|
2402
2697
|
const targets = /* @__PURE__ */ new Set([CONTAINER_WORKDIR, "/tmp"]);
|
|
2403
2698
|
for (const mount of mounts) {
|
|
2404
2699
|
validateMountArgument(mount.source);
|
|
2405
|
-
if (mount.sourceRoot !== void 0 && (!
|
|
2700
|
+
if (mount.sourceRoot !== void 0 && (!isAbsolute3(mount.sourceRoot) || mount.sourceRoot.includes("\0") || mount.sourceRoot.includes(","))) {
|
|
2406
2701
|
throw new Error(
|
|
2407
2702
|
`Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`
|
|
2408
2703
|
);
|
|
@@ -2422,8 +2717,8 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
|
|
|
2422
2717
|
}
|
|
2423
2718
|
}
|
|
2424
2719
|
function isWithin(root, candidate) {
|
|
2425
|
-
const rel =
|
|
2426
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !
|
|
2720
|
+
const rel = relative3(root, candidate);
|
|
2721
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute3(rel);
|
|
2427
2722
|
}
|
|
2428
2723
|
function validateHostIdentity(uid, gid) {
|
|
2429
2724
|
if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {
|
|
@@ -2460,7 +2755,7 @@ async function removeContainer(dockerPath, containerName, processEnv) {
|
|
|
2460
2755
|
last = await runCleanupCommand(dockerPath, args, processEnv);
|
|
2461
2756
|
if (last.ok) return;
|
|
2462
2757
|
if (attempt < CLEANUP_ATTEMPTS - 1) {
|
|
2463
|
-
await new Promise((
|
|
2758
|
+
await new Promise((resolve6) => setTimeout(resolve6, CLEANUP_RETRY_DELAY_MS));
|
|
2464
2759
|
}
|
|
2465
2760
|
}
|
|
2466
2761
|
if (last && !last.missing) {
|
|
@@ -2470,7 +2765,7 @@ async function removeContainer(dockerPath, containerName, processEnv) {
|
|
|
2470
2765
|
}
|
|
2471
2766
|
}
|
|
2472
2767
|
function runCleanupCommand(dockerPath, args, processEnv) {
|
|
2473
|
-
return new Promise((
|
|
2768
|
+
return new Promise((resolve6) => {
|
|
2474
2769
|
let done = false;
|
|
2475
2770
|
let stderr = "";
|
|
2476
2771
|
const child = spawn(dockerPath, args, {
|
|
@@ -2483,7 +2778,7 @@ function runCleanupCommand(dockerPath, args, processEnv) {
|
|
|
2483
2778
|
done = true;
|
|
2484
2779
|
clearTimeout(timeoutHandle);
|
|
2485
2780
|
const detail3 = error || stderr.trim();
|
|
2486
|
-
|
|
2781
|
+
resolve6({
|
|
2487
2782
|
ok,
|
|
2488
2783
|
missing: /no such container/i.test(detail3),
|
|
2489
2784
|
error: detail3
|
|
@@ -2507,6 +2802,26 @@ var HOST_FILE_TOOL_NAMES = ["read", "edit", "write", "grep", "find", "ls"];
|
|
|
2507
2802
|
var CONTAINER_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
2508
2803
|
var SANDBOX_INPUTS_DIR = "/inputs";
|
|
2509
2804
|
var SANDBOX_MEMORY_DIR = "/workspace/memory";
|
|
2805
|
+
function createProtectedSessionShellTools(cwd, runtime, approvalHost) {
|
|
2806
|
+
const base = createBashToolDefinition(cwd, {
|
|
2807
|
+
operations: createPreparedDockerBashOperations(runtime)
|
|
2808
|
+
});
|
|
2809
|
+
const dockerBash = defineTool2({
|
|
2810
|
+
...base,
|
|
2811
|
+
label: "bash (protected Docker sandbox)",
|
|
2812
|
+
description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the Team workspace mounted read/write plus only the preflighted read-only memory, skill, and attachment mounts. Host files, host tools, and host credentials are unavailable.`
|
|
2813
|
+
});
|
|
2814
|
+
return {
|
|
2815
|
+
config: {
|
|
2816
|
+
sandboxMode: "docker",
|
|
2817
|
+
approvalMode: "dangerous",
|
|
2818
|
+
sandboxImage: runtime.image,
|
|
2819
|
+
envAllowlist: []
|
|
2820
|
+
},
|
|
2821
|
+
hostToolNames: [],
|
|
2822
|
+
customBash: createApprovalGatedBashTool(dockerBash, approvalHost)
|
|
2823
|
+
};
|
|
2824
|
+
}
|
|
2510
2825
|
function buildSandboxContainerEnv(env, allowlist) {
|
|
2511
2826
|
return {
|
|
2512
2827
|
...buildScrubbedShellEnv(env, allowlist),
|
|
@@ -2535,9 +2850,9 @@ function createSessionShellTools(cwd, env, options = {}) {
|
|
|
2535
2850
|
env: buildSandboxContainerEnv(env, config.envAllowlist),
|
|
2536
2851
|
readOnlyMounts: [
|
|
2537
2852
|
...existsSync11(join15(cwd, "memory")) ? [safeReadOnlyMount(join15(cwd, "memory"), SANDBOX_MEMORY_DIR, cwd)] : [],
|
|
2538
|
-
...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR,
|
|
2853
|
+
...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname4(options.inputsDir))] : [],
|
|
2539
2854
|
...(options.skillMounts ?? []).map(
|
|
2540
|
-
(mount) => safeReadOnlyMount(mount.source, mount.target,
|
|
2855
|
+
(mount) => safeReadOnlyMount(mount.source, mount.target, dirname4(mount.source))
|
|
2541
2856
|
)
|
|
2542
2857
|
]
|
|
2543
2858
|
});
|
|
@@ -2551,26 +2866,667 @@ function createSessionShellTools(cwd, env, options = {}) {
|
|
|
2551
2866
|
return { config, hostToolNames: [], customBash };
|
|
2552
2867
|
}
|
|
2553
2868
|
function safeReadOnlyMount(source, target, expectedRoot) {
|
|
2554
|
-
const info =
|
|
2869
|
+
const info = lstatSync3(source);
|
|
2555
2870
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
2556
2871
|
throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`);
|
|
2557
2872
|
}
|
|
2558
|
-
const sourceRoot =
|
|
2559
|
-
const canonicalSource =
|
|
2560
|
-
const rel =
|
|
2561
|
-
const contained = rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !
|
|
2873
|
+
const sourceRoot = realpathSync3(expectedRoot);
|
|
2874
|
+
const canonicalSource = realpathSync3(source);
|
|
2875
|
+
const rel = relative4(sourceRoot, canonicalSource);
|
|
2876
|
+
const contained = rel === "" || rel !== ".." && !rel.startsWith(`..${sep4}`) && !isAbsolute4(rel);
|
|
2562
2877
|
if (!contained) {
|
|
2563
2878
|
throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`);
|
|
2564
2879
|
}
|
|
2565
2880
|
return { source: canonicalSource, target, sourceRoot };
|
|
2566
2881
|
}
|
|
2567
2882
|
|
|
2883
|
+
// ../daemon/src/runtime/worker/runtime.ts
|
|
2884
|
+
import {
|
|
2885
|
+
constants as constants4,
|
|
2886
|
+
existsSync as existsSync12,
|
|
2887
|
+
lstatSync as lstatSync4,
|
|
2888
|
+
mkdirSync as mkdirSync6,
|
|
2889
|
+
mkdtempSync as mkdtempSync3,
|
|
2890
|
+
realpathSync as realpathSync4,
|
|
2891
|
+
rmSync as rmSync11,
|
|
2892
|
+
statSync as statSync3
|
|
2893
|
+
} from "fs";
|
|
2894
|
+
import { access as access2 } from "fs/promises";
|
|
2895
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
2896
|
+
import { isAbsolute as isAbsolute5, join as join16, relative as relative5, sep as sep5 } from "path";
|
|
2897
|
+
var REDACTED_VALUE = "[REDACTED]";
|
|
2898
|
+
var REASONING_LEVELS = /* @__PURE__ */ new Set([
|
|
2899
|
+
"off",
|
|
2900
|
+
"minimal",
|
|
2901
|
+
"low",
|
|
2902
|
+
"medium",
|
|
2903
|
+
"high",
|
|
2904
|
+
"xhigh"
|
|
2905
|
+
]);
|
|
2906
|
+
var CONFIGURED_KEYS = /* @__PURE__ */ new Set([
|
|
2907
|
+
"kind",
|
|
2908
|
+
"agent",
|
|
2909
|
+
"message",
|
|
2910
|
+
"enabledProviders",
|
|
2911
|
+
"apiKey",
|
|
2912
|
+
"apiKeyRefreshEnabled",
|
|
2913
|
+
"browserEnabled",
|
|
2914
|
+
"mcpTools",
|
|
2915
|
+
"images",
|
|
2916
|
+
"turnId",
|
|
2917
|
+
"bashApprovalMode"
|
|
2918
|
+
]);
|
|
2919
|
+
var CONFIGURED_REQUIRED_KEYS = /* @__PURE__ */ new Set([
|
|
2920
|
+
"kind",
|
|
2921
|
+
"agent",
|
|
2922
|
+
"message",
|
|
2923
|
+
"enabledProviders",
|
|
2924
|
+
"apiKeyRefreshEnabled",
|
|
2925
|
+
"turnId",
|
|
2926
|
+
"bashApprovalMode"
|
|
2927
|
+
]);
|
|
2928
|
+
var PROTECTED_KEYS = /* @__PURE__ */ new Set([
|
|
2929
|
+
"kind",
|
|
2930
|
+
"agent",
|
|
2931
|
+
"message",
|
|
2932
|
+
"images",
|
|
2933
|
+
"turnId",
|
|
2934
|
+
"bashApprovalMode",
|
|
2935
|
+
"runtime",
|
|
2936
|
+
"paths",
|
|
2937
|
+
"docker",
|
|
2938
|
+
"webFetchEnabled",
|
|
2939
|
+
"apiKeyRefreshEnabled",
|
|
2940
|
+
"scratch"
|
|
2941
|
+
]);
|
|
2942
|
+
var PROTECTED_REQUIRED_KEYS = new Set([...PROTECTED_KEYS].filter((key) => key !== "images"));
|
|
2943
|
+
var REVIEW_KEYS = /* @__PURE__ */ new Set([
|
|
2944
|
+
"kind",
|
|
2945
|
+
"agentId",
|
|
2946
|
+
"message",
|
|
2947
|
+
"turnId",
|
|
2948
|
+
"runtime",
|
|
2949
|
+
"review",
|
|
2950
|
+
"apiKeyRefreshEnabled",
|
|
2951
|
+
"scratch"
|
|
2952
|
+
]);
|
|
2953
|
+
function minimalWorkerProcessEnv(scratch, platform = process.platform, hostEnv = process.env) {
|
|
2954
|
+
assertMinimalWorkerScratch(scratch);
|
|
2955
|
+
if (platform === "win32") {
|
|
2956
|
+
const systemRoot = requireCanonicalWindowsDirectory(
|
|
2957
|
+
hostEnv.SystemRoot ?? hostEnv.WINDIR,
|
|
2958
|
+
"SystemRoot"
|
|
2959
|
+
);
|
|
2960
|
+
const windir = requireCanonicalWindowsDirectory(hostEnv.WINDIR ?? systemRoot, "WINDIR");
|
|
2961
|
+
const comSpec = requireCanonicalWindowsFile(hostEnv.ComSpec, "ComSpec");
|
|
2962
|
+
const pathExt = hostEnv.PATHEXT;
|
|
2963
|
+
if (!pathExt || pathExt.includes("\0") || pathExt.includes("\n") || pathExt.includes("\r")) {
|
|
2964
|
+
throw new Error("minimal worker bootstrap requires a valid PATHEXT on Windows");
|
|
2965
|
+
}
|
|
2966
|
+
return {
|
|
2967
|
+
USERPROFILE: scratch.homeDir,
|
|
2968
|
+
TMPDIR: scratch.tempDir,
|
|
2969
|
+
TMP: scratch.tempDir,
|
|
2970
|
+
TEMP: scratch.tempDir,
|
|
2971
|
+
SystemRoot: systemRoot,
|
|
2972
|
+
WINDIR: windir,
|
|
2973
|
+
ComSpec: comSpec,
|
|
2974
|
+
PATHEXT: pathExt
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
return {
|
|
2978
|
+
HOME: scratch.homeDir,
|
|
2979
|
+
TMPDIR: scratch.tempDir,
|
|
2980
|
+
TMP: scratch.tempDir,
|
|
2981
|
+
TEMP: scratch.tempDir,
|
|
2982
|
+
LANG: "C",
|
|
2983
|
+
LC_ALL: "C"
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
function validateMinimalWorkerProcessEnv(env, scratch, platform = process.platform) {
|
|
2987
|
+
const expected = minimalWorkerProcessEnv(scratch, platform, env);
|
|
2988
|
+
const actualKeys = Object.keys(env).sort();
|
|
2989
|
+
const expectedKeys = Object.keys(expected).sort();
|
|
2990
|
+
if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
|
|
2991
|
+
throw new Error(`minimal worker environment has unexpected keys: ${actualKeys.join(", ")}`);
|
|
2992
|
+
}
|
|
2993
|
+
for (const key of expectedKeys) {
|
|
2994
|
+
if (env[key] !== expected[key]) {
|
|
2995
|
+
throw new Error(`minimal worker environment has an invalid ${key} value`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
function parseWorkerInput(value) {
|
|
3000
|
+
const input = objectRecord(value, "worker input");
|
|
3001
|
+
const kind = input.kind;
|
|
3002
|
+
if (kind === "configured_operator_http") {
|
|
3003
|
+
assertExactKeys(input, CONFIGURED_KEYS, "configured worker input", CONFIGURED_REQUIRED_KEYS);
|
|
3004
|
+
if (!isResolvedAgent(input.agent)) throw new Error("worker: configured input requires an agent");
|
|
3005
|
+
requireString(input.message, "message");
|
|
3006
|
+
if (!Array.isArray(input.enabledProviders) || !input.enabledProviders.every(isString)) {
|
|
3007
|
+
throw new Error("worker: configured input requires enabledProviders");
|
|
3008
|
+
}
|
|
3009
|
+
requireString(input.turnId, "turnId");
|
|
3010
|
+
if (input.bashApprovalMode !== "interactive" && input.bashApprovalMode !== "auto_deny") {
|
|
3011
|
+
throw new Error("worker: configured input requires a valid bashApprovalMode");
|
|
3012
|
+
}
|
|
3013
|
+
if (typeof input.apiKeyRefreshEnabled !== "boolean") {
|
|
3014
|
+
throw new Error("worker: configured input requires apiKeyRefreshEnabled");
|
|
3015
|
+
}
|
|
3016
|
+
if (input.apiKeyRefreshEnabled) {
|
|
3017
|
+
const providerName = input.agent.model.split(":", 1)[0] ?? "";
|
|
3018
|
+
if (providerName !== "openai-codex" || !nonEmptyString(input.apiKey)) {
|
|
3019
|
+
throw new Error(
|
|
3020
|
+
"worker: API key refresh requires an openai-codex turn with an initial token"
|
|
3021
|
+
);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
return value;
|
|
3025
|
+
}
|
|
3026
|
+
if (kind === "protected") {
|
|
3027
|
+
assertExactKeys(input, PROTECTED_KEYS, "protected worker input", PROTECTED_REQUIRED_KEYS);
|
|
3028
|
+
if (!isResolvedAgent(input.agent)) throw new Error("worker: protected input requires an agent");
|
|
3029
|
+
requireString(input.message, "message");
|
|
3030
|
+
requireString(input.turnId, "turnId");
|
|
3031
|
+
if (input.bashApprovalMode !== "auto_deny") {
|
|
3032
|
+
throw new Error("worker: protected input requires auto-deny shell approval");
|
|
3033
|
+
}
|
|
3034
|
+
if (input.webFetchEnabled !== true) {
|
|
3035
|
+
throw new Error("worker: protected input requires guarded web_fetch");
|
|
3036
|
+
}
|
|
3037
|
+
assertProtectedProviderRuntime(input.runtime);
|
|
3038
|
+
const runtime = input.runtime;
|
|
3039
|
+
const separator = input.agent.model.indexOf(":");
|
|
3040
|
+
if (runtime.providerName !== input.agent.model.slice(0, separator) || runtime.modelId !== input.agent.model.slice(separator + 1)) {
|
|
3041
|
+
throw new Error("worker: protected runtime does not match the resolved Agent model");
|
|
3042
|
+
}
|
|
3043
|
+
assertProtectedWorkerPaths(input.paths, input.agent);
|
|
3044
|
+
assertProtectedDockerRuntime(input.docker, input.paths);
|
|
3045
|
+
if (input.apiKeyRefreshEnabled !== true) {
|
|
3046
|
+
throw new Error("worker: protected input requires bound API key refresh");
|
|
3047
|
+
}
|
|
3048
|
+
assertMinimalWorkerScratch(input.scratch);
|
|
3049
|
+
return value;
|
|
3050
|
+
}
|
|
3051
|
+
if (kind === "restricted_review") {
|
|
3052
|
+
assertExactKeys(input, REVIEW_KEYS, "restricted review worker input", REVIEW_KEYS);
|
|
3053
|
+
requireString(input.agentId, "agentId");
|
|
3054
|
+
requireString(input.message, "message");
|
|
3055
|
+
requireString(input.turnId, "turnId");
|
|
3056
|
+
assertProtectedProviderRuntime(input.runtime);
|
|
3057
|
+
if (input.apiKeyRefreshEnabled !== true) {
|
|
3058
|
+
throw new Error("worker: restricted review requires bound API key refresh");
|
|
3059
|
+
}
|
|
3060
|
+
assertMinimalWorkerScratch(input.scratch);
|
|
3061
|
+
const review = objectRecord(input.review, "review metadata");
|
|
3062
|
+
assertExactKeys(
|
|
3063
|
+
review,
|
|
3064
|
+
/* @__PURE__ */ new Set(["reviewId", "evidence"]),
|
|
3065
|
+
"review metadata",
|
|
3066
|
+
/* @__PURE__ */ new Set(["reviewId", "evidence"])
|
|
3067
|
+
);
|
|
3068
|
+
requireString(review.reviewId, "reviewId");
|
|
3069
|
+
if (!Array.isArray(review.evidence)) throw new Error("worker: review evidence must be an array");
|
|
3070
|
+
for (const item of review.evidence) {
|
|
3071
|
+
const evidence = objectRecord(item, "review evidence");
|
|
3072
|
+
assertExactKeys(
|
|
3073
|
+
evidence,
|
|
3074
|
+
/* @__PURE__ */ new Set(["sessionId", "entryOrdinal"]),
|
|
3075
|
+
"review evidence",
|
|
3076
|
+
/* @__PURE__ */ new Set(["sessionId", "entryOrdinal"])
|
|
3077
|
+
);
|
|
3078
|
+
requireString(evidence.sessionId, "sessionId");
|
|
3079
|
+
if (!Number.isInteger(evidence.entryOrdinal) || Number(evidence.entryOrdinal) < 0) {
|
|
3080
|
+
throw new Error("worker: review evidence entryOrdinal must be a non-negative integer");
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
return value;
|
|
3084
|
+
}
|
|
3085
|
+
throw new Error("worker: unknown or missing runtime kind");
|
|
3086
|
+
}
|
|
3087
|
+
function assertProtectedProviderRuntime(value) {
|
|
3088
|
+
const runtime = objectRecord(value, "protected provider runtime");
|
|
3089
|
+
assertExactKeys(
|
|
3090
|
+
runtime,
|
|
3091
|
+
/* @__PURE__ */ new Set([
|
|
3092
|
+
"providerName",
|
|
3093
|
+
"modelId",
|
|
3094
|
+
"reasoningLevel",
|
|
3095
|
+
"apiKey",
|
|
3096
|
+
"baseUrl",
|
|
3097
|
+
"credentialEnv",
|
|
3098
|
+
"credentialFile"
|
|
3099
|
+
]),
|
|
3100
|
+
"protected provider runtime",
|
|
3101
|
+
/* @__PURE__ */ new Set(["providerName", "modelId", "reasoningLevel"])
|
|
3102
|
+
);
|
|
3103
|
+
if (!PROTECTED_PROVIDER_NAMES.includes(runtime.providerName)) {
|
|
3104
|
+
throw new Error("protected execution requires a registered provider");
|
|
3105
|
+
}
|
|
3106
|
+
if (!nonEmptyString(runtime.modelId))
|
|
3107
|
+
throw new Error("protected provider runtime requires a model id");
|
|
3108
|
+
if (!REASONING_LEVELS.has(runtime.reasoningLevel)) {
|
|
3109
|
+
throw new Error("protected provider runtime requires a valid reasoning level");
|
|
3110
|
+
}
|
|
3111
|
+
if (runtime.apiKey !== void 0 && !nonEmptyString(runtime.apiKey)) {
|
|
3112
|
+
throw new Error("protected provider runtime contains an invalid selected credential");
|
|
3113
|
+
}
|
|
3114
|
+
if (runtime.baseUrl !== void 0) requireString(runtime.baseUrl, "baseUrl");
|
|
3115
|
+
if (runtime.credentialEnv !== void 0) {
|
|
3116
|
+
if (!Array.isArray(runtime.credentialEnv)) {
|
|
3117
|
+
throw new Error("protected provider credential fields must be an array");
|
|
3118
|
+
}
|
|
3119
|
+
const allowed = new Set(PROVIDER_CREDENTIAL_ENV[String(runtime.providerName)] ?? []);
|
|
3120
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3121
|
+
for (const rawField of runtime.credentialEnv) {
|
|
3122
|
+
const field = objectRecord(rawField, "protected provider credential field");
|
|
3123
|
+
assertExactKeys(
|
|
3124
|
+
field,
|
|
3125
|
+
/* @__PURE__ */ new Set(["name", "value"]),
|
|
3126
|
+
"protected provider credential field",
|
|
3127
|
+
/* @__PURE__ */ new Set(["name", "value"])
|
|
3128
|
+
);
|
|
3129
|
+
requireString(field.name, "credential field name");
|
|
3130
|
+
requireString(field.value, "credential field value");
|
|
3131
|
+
if (!allowed.has(field.name) || seen.has(field.name)) {
|
|
3132
|
+
throw new Error("protected provider runtime contains an unexpected credential field");
|
|
3133
|
+
}
|
|
3134
|
+
seen.add(field.name);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (runtime.credentialFile !== void 0) {
|
|
3138
|
+
const file = objectRecord(runtime.credentialFile, "protected provider credential file");
|
|
3139
|
+
assertExactKeys(
|
|
3140
|
+
file,
|
|
3141
|
+
/* @__PURE__ */ new Set(["envName", "content"]),
|
|
3142
|
+
"protected provider credential file",
|
|
3143
|
+
/* @__PURE__ */ new Set(["envName", "content"])
|
|
3144
|
+
);
|
|
3145
|
+
if (runtime.providerName !== "google-vertex" || file.envName !== "GOOGLE_APPLICATION_CREDENTIALS" || !nonEmptyString(file.content) || Buffer.byteLength(file.content, "utf8") > 64 * 1024) {
|
|
3146
|
+
throw new Error("protected provider runtime contains an invalid credential file");
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
if (!nonEmptyString(runtime.apiKey) && !Array.isArray(runtime.credentialEnv) && runtime.credentialFile === void 0) {
|
|
3150
|
+
throw new Error("protected provider runtime requires selected credentials");
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
function protectedRuntimeSecrets(runtime) {
|
|
3154
|
+
return [
|
|
3155
|
+
runtime.apiKey,
|
|
3156
|
+
...(runtime.credentialEnv ?? []).map((field) => field.value),
|
|
3157
|
+
runtime.credentialFile?.content
|
|
3158
|
+
].filter((value) => Boolean(value));
|
|
3159
|
+
}
|
|
3160
|
+
function redactExactValues(text, values) {
|
|
3161
|
+
let redacted = text;
|
|
3162
|
+
for (const value of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) {
|
|
3163
|
+
redacted = redacted.replaceAll(value, REDACTED_VALUE);
|
|
3164
|
+
}
|
|
3165
|
+
return redacted;
|
|
3166
|
+
}
|
|
3167
|
+
function redactJsonValue(value, secrets) {
|
|
3168
|
+
if (!secrets || Array.isArray(secrets) && secrets.length === 0) return value;
|
|
3169
|
+
const values = typeof secrets === "string" ? [secrets] : secrets;
|
|
3170
|
+
if (typeof value === "string") return redactExactValues(value, values);
|
|
3171
|
+
if (Array.isArray(value)) return value.map((item) => redactJsonValue(item, values));
|
|
3172
|
+
if (value && typeof value === "object") {
|
|
3173
|
+
return Object.fromEntries(
|
|
3174
|
+
Object.entries(value).map(([key, item]) => [key, redactJsonValue(item, values)])
|
|
3175
|
+
);
|
|
3176
|
+
}
|
|
3177
|
+
return value;
|
|
3178
|
+
}
|
|
3179
|
+
function assertProtectedWorkerPaths(value, agent) {
|
|
3180
|
+
const paths = objectRecord(value, "protected worker paths");
|
|
3181
|
+
assertExactKeys(
|
|
3182
|
+
paths,
|
|
3183
|
+
/* @__PURE__ */ new Set([
|
|
3184
|
+
"agentDir",
|
|
3185
|
+
"teamDir",
|
|
3186
|
+
"memoryDir",
|
|
3187
|
+
"sessionDir",
|
|
3188
|
+
"uploadsDir",
|
|
3189
|
+
"skills",
|
|
3190
|
+
"homeDocuments"
|
|
3191
|
+
]),
|
|
3192
|
+
"protected worker paths",
|
|
3193
|
+
/* @__PURE__ */ new Set(["agentDir", "teamDir", "memoryDir", "sessionDir", "skills", "homeDocuments"])
|
|
3194
|
+
);
|
|
3195
|
+
for (const key of ["agentDir", "teamDir", "memoryDir", "sessionDir"]) {
|
|
3196
|
+
requireAbsolutePath(paths[key], key);
|
|
3197
|
+
}
|
|
3198
|
+
if (paths.uploadsDir !== void 0) requireAbsolutePath(paths.uploadsDir, "uploadsDir");
|
|
3199
|
+
if (paths.agentDir !== agent.agent.dir || paths.teamDir !== agent.team.path) {
|
|
3200
|
+
throw new Error("worker: protected paths do not match the resolved Agent");
|
|
3201
|
+
}
|
|
3202
|
+
const canonicalAgentDir = requireCanonicalRealDirectory(
|
|
3203
|
+
paths.agentDir,
|
|
3204
|
+
"Agent directory"
|
|
3205
|
+
);
|
|
3206
|
+
const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir, "Team directory");
|
|
3207
|
+
requireCanonicalContainedDirectory(
|
|
3208
|
+
canonicalAgentDir,
|
|
3209
|
+
paths.sessionDir,
|
|
3210
|
+
"session directory"
|
|
3211
|
+
);
|
|
3212
|
+
if (paths.uploadsDir !== void 0) {
|
|
3213
|
+
requireCanonicalContainedDirectory(
|
|
3214
|
+
canonicalAgentDir,
|
|
3215
|
+
paths.uploadsDir,
|
|
3216
|
+
"uploads directory"
|
|
3217
|
+
);
|
|
3218
|
+
}
|
|
3219
|
+
requireCanonicalContainedDirectory(
|
|
3220
|
+
canonicalTeamDir,
|
|
3221
|
+
paths.memoryDir,
|
|
3222
|
+
"memory directory"
|
|
3223
|
+
);
|
|
3224
|
+
if (!Array.isArray(paths.skills)) throw new Error("worker: protected skills must be an array");
|
|
3225
|
+
for (const item of paths.skills) {
|
|
3226
|
+
const skill = objectRecord(item, "protected skill");
|
|
3227
|
+
assertExactKeys(
|
|
3228
|
+
skill,
|
|
3229
|
+
/* @__PURE__ */ new Set(["name", "description", "body", "hostDir", "sandboxDir"]),
|
|
3230
|
+
"protected skill"
|
|
3231
|
+
);
|
|
3232
|
+
requireString(skill.name, "skill name");
|
|
3233
|
+
requireString(skill.description, "skill description");
|
|
3234
|
+
requireString(skill.body, "skill body");
|
|
3235
|
+
requireAbsolutePath(skill.hostDir, "skill hostDir");
|
|
3236
|
+
requireCanonicalRealDirectory(skill.hostDir, "skill hostDir");
|
|
3237
|
+
if (typeof skill.sandboxDir !== "string" || !skill.sandboxDir.startsWith("/skills/")) {
|
|
3238
|
+
throw new Error("worker: protected skill sandbox path must be under /skills");
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
const homeDocuments = objectRecord(paths.homeDocuments, "protected home documents");
|
|
3242
|
+
const homeDocumentKeys = /* @__PURE__ */ new Set([
|
|
3243
|
+
"AGENTS.md",
|
|
3244
|
+
"SOUL.md",
|
|
3245
|
+
"TOOLS.md",
|
|
3246
|
+
"IDENTITY.md",
|
|
3247
|
+
"BOOTSTRAP.md"
|
|
3248
|
+
]);
|
|
3249
|
+
assertExactKeys(homeDocuments, homeDocumentKeys, "protected home documents", homeDocumentKeys);
|
|
3250
|
+
for (const [name, contents] of Object.entries(homeDocuments)) {
|
|
3251
|
+
if (contents !== null && typeof contents !== "string") {
|
|
3252
|
+
throw new Error(`worker: protected home document ${name} must be text or null`);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
function assertProtectedDockerRuntime(value, paths) {
|
|
3257
|
+
const runtime = objectRecord(value, "protected Docker runtime");
|
|
3258
|
+
assertExactKeys(
|
|
3259
|
+
runtime,
|
|
3260
|
+
/* @__PURE__ */ new Set([
|
|
3261
|
+
"dockerPath",
|
|
3262
|
+
"executableIdentity",
|
|
3263
|
+
"endpoint",
|
|
3264
|
+
"image",
|
|
3265
|
+
"imageId",
|
|
3266
|
+
"uid",
|
|
3267
|
+
"gid",
|
|
3268
|
+
"workspace",
|
|
3269
|
+
"readOnlyMounts",
|
|
3270
|
+
"containerEnv"
|
|
3271
|
+
]),
|
|
3272
|
+
"protected Docker runtime",
|
|
3273
|
+
/* @__PURE__ */ new Set([
|
|
3274
|
+
"dockerPath",
|
|
3275
|
+
"executableIdentity",
|
|
3276
|
+
"endpoint",
|
|
3277
|
+
"image",
|
|
3278
|
+
"imageId",
|
|
3279
|
+
"uid",
|
|
3280
|
+
"gid",
|
|
3281
|
+
"workspace",
|
|
3282
|
+
"readOnlyMounts",
|
|
3283
|
+
"containerEnv"
|
|
3284
|
+
])
|
|
3285
|
+
);
|
|
3286
|
+
requireAbsolutePath(runtime.dockerPath, "Docker executable");
|
|
3287
|
+
const executableIdentity = objectRecord(
|
|
3288
|
+
runtime.executableIdentity,
|
|
3289
|
+
"protected Docker executable identity"
|
|
3290
|
+
);
|
|
3291
|
+
const executableIdentityKeys = /* @__PURE__ */ new Set([
|
|
3292
|
+
"device",
|
|
3293
|
+
"inode",
|
|
3294
|
+
"mode",
|
|
3295
|
+
"size",
|
|
3296
|
+
"modifiedTimeNs",
|
|
3297
|
+
"changedTimeNs"
|
|
3298
|
+
]);
|
|
3299
|
+
assertExactKeys(
|
|
3300
|
+
executableIdentity,
|
|
3301
|
+
executableIdentityKeys,
|
|
3302
|
+
"protected Docker executable identity",
|
|
3303
|
+
executableIdentityKeys
|
|
3304
|
+
);
|
|
3305
|
+
for (const [name, field] of Object.entries(executableIdentity)) {
|
|
3306
|
+
if (typeof field !== "string" || !/^\d+$/.test(field)) {
|
|
3307
|
+
throw new Error(`worker: protected Docker executable identity has invalid ${name}`);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
if (typeof runtime.endpoint !== "string" || !runtime.endpoint.startsWith("unix://") || !isAbsolute5(runtime.endpoint.slice("unix://".length)) || containsUnsafeDockerArgument(runtime.endpoint)) {
|
|
3311
|
+
throw new Error("worker: protected Docker endpoint must be a local Unix socket");
|
|
3312
|
+
}
|
|
3313
|
+
if (!nonEmptyString(runtime.image) || runtime.image.startsWith("-") || runtime.image.includes("\0") || runtime.image.includes("\n") || runtime.image.includes("\r")) {
|
|
3314
|
+
throw new Error("worker: protected Docker image is required");
|
|
3315
|
+
}
|
|
3316
|
+
if (typeof runtime.imageId !== "string" || !/^sha256:[a-fA-F0-9]{64}$/.test(runtime.imageId)) {
|
|
3317
|
+
throw new Error("worker: protected Docker runtime requires an immutable image id");
|
|
3318
|
+
}
|
|
3319
|
+
if (!Number.isSafeInteger(runtime.uid) || !Number.isSafeInteger(runtime.gid)) {
|
|
3320
|
+
throw new Error("worker: protected Docker runtime requires a numeric host identity");
|
|
3321
|
+
}
|
|
3322
|
+
if (Number(runtime.uid) < 0 || Number(runtime.gid) < 0) {
|
|
3323
|
+
throw new Error("worker: protected Docker runtime requires a non-negative host identity");
|
|
3324
|
+
}
|
|
3325
|
+
const workspace = objectRecord(runtime.workspace, "protected Docker workspace");
|
|
3326
|
+
assertExactKeys(
|
|
3327
|
+
workspace,
|
|
3328
|
+
/* @__PURE__ */ new Set(["source", "target", "sourceRoot"]),
|
|
3329
|
+
"protected Docker workspace",
|
|
3330
|
+
/* @__PURE__ */ new Set(["source", "target", "sourceRoot"])
|
|
3331
|
+
);
|
|
3332
|
+
requireAbsolutePath(workspace.source, "Docker workspace source");
|
|
3333
|
+
requireAbsolutePath(workspace.sourceRoot, "Docker workspace root");
|
|
3334
|
+
assertSafeDockerHostPath(workspace.source, "Docker workspace source");
|
|
3335
|
+
assertSafeDockerHostPath(workspace.sourceRoot, "Docker workspace root");
|
|
3336
|
+
if (workspace.target !== "/workspace") {
|
|
3337
|
+
throw new Error("worker: protected Docker workspace target must be /workspace");
|
|
3338
|
+
}
|
|
3339
|
+
const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir, "Team directory");
|
|
3340
|
+
if (workspace.source !== canonicalTeamDir || workspace.sourceRoot !== canonicalTeamDir) {
|
|
3341
|
+
throw new Error("worker: protected Docker workspace does not match the canonical Team root");
|
|
3342
|
+
}
|
|
3343
|
+
if (!Array.isArray(runtime.readOnlyMounts)) {
|
|
3344
|
+
throw new Error("worker: protected Docker read-only mounts must be an array");
|
|
3345
|
+
}
|
|
3346
|
+
const mountTargets = /* @__PURE__ */ new Set(["/workspace", "/tmp"]);
|
|
3347
|
+
for (const item of runtime.readOnlyMounts) {
|
|
3348
|
+
const mount = objectRecord(item, "protected Docker read-only mount");
|
|
3349
|
+
assertExactKeys(
|
|
3350
|
+
mount,
|
|
3351
|
+
/* @__PURE__ */ new Set(["source", "target", "sourceRoot"]),
|
|
3352
|
+
"protected Docker read-only mount",
|
|
3353
|
+
/* @__PURE__ */ new Set(["source", "target", "sourceRoot"])
|
|
3354
|
+
);
|
|
3355
|
+
requireAbsolutePath(mount.source, "Docker read-only mount source");
|
|
3356
|
+
requireAbsolutePath(mount.sourceRoot, "Docker read-only mount root");
|
|
3357
|
+
assertSafeDockerHostPath(mount.source, "Docker read-only mount source");
|
|
3358
|
+
assertSafeDockerHostPath(mount.sourceRoot, "Docker read-only mount root");
|
|
3359
|
+
if (typeof mount.target !== "string" || !mount.target.startsWith("/") || containsUnsafeDockerArgument(mount.target) || mount.target.split("/").includes("..") || !mount.target.startsWith("/skills/") && mount.target !== "/inputs" && mount.target !== "/workspace/memory") {
|
|
3360
|
+
throw new Error("worker: protected Docker read-only mount target is not permitted");
|
|
3361
|
+
}
|
|
3362
|
+
if (!lexicallyWithin(mount.sourceRoot, mount.source)) {
|
|
3363
|
+
throw new Error("worker: protected Docker read-only mount escaped its root");
|
|
3364
|
+
}
|
|
3365
|
+
if (mountTargets.has(mount.target)) {
|
|
3366
|
+
throw new Error("worker: protected Docker mount target is duplicated");
|
|
3367
|
+
}
|
|
3368
|
+
mountTargets.add(mount.target);
|
|
3369
|
+
}
|
|
3370
|
+
assertExactProtectedDockerMounts(
|
|
3371
|
+
runtime.readOnlyMounts,
|
|
3372
|
+
paths
|
|
3373
|
+
);
|
|
3374
|
+
const containerEnv = objectRecord(runtime.containerEnv, "protected Docker container environment");
|
|
3375
|
+
const expectedContainerEnv = {
|
|
3376
|
+
PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
3377
|
+
HOME: "/tmp",
|
|
3378
|
+
LANG: "C.UTF-8",
|
|
3379
|
+
LC_ALL: "C.UTF-8",
|
|
3380
|
+
SHELL: "/bin/bash",
|
|
3381
|
+
TMPDIR: "/tmp"
|
|
3382
|
+
};
|
|
3383
|
+
const expectedKeys = new Set(Object.keys(expectedContainerEnv));
|
|
3384
|
+
assertExactKeys(
|
|
3385
|
+
containerEnv,
|
|
3386
|
+
expectedKeys,
|
|
3387
|
+
"protected Docker container environment",
|
|
3388
|
+
expectedKeys
|
|
3389
|
+
);
|
|
3390
|
+
for (const [key, expected] of Object.entries(expectedContainerEnv)) {
|
|
3391
|
+
if (containerEnv[key] !== expected) {
|
|
3392
|
+
throw new Error(`worker: protected Docker container environment has invalid ${key}`);
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
function assertExactProtectedDockerMounts(mounts, paths) {
|
|
3397
|
+
const expected = new Map([
|
|
3398
|
+
["/workspace/memory", paths.memoryDir],
|
|
3399
|
+
...paths.uploadsDir ? [["/inputs", paths.uploadsDir]] : [],
|
|
3400
|
+
...paths.skills.map((skill) => [skill.sandboxDir, skill.hostDir])
|
|
3401
|
+
]);
|
|
3402
|
+
if (mounts.length !== expected.size) {
|
|
3403
|
+
throw new Error("worker: protected Docker read-only mount set is incomplete");
|
|
3404
|
+
}
|
|
3405
|
+
for (const mount of mounts) {
|
|
3406
|
+
const source = expected.get(mount.target);
|
|
3407
|
+
if (!source || mount.source !== source) {
|
|
3408
|
+
throw new Error("worker: protected Docker read-only mount set does not match protected paths");
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
function assertSafeDockerHostPath(value, name) {
|
|
3413
|
+
if (typeof value !== "string" || containsUnsafeDockerArgument(value)) {
|
|
3414
|
+
throw new Error(`worker: ${name} cannot be represented safely`);
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
function containsUnsafeDockerArgument(value) {
|
|
3418
|
+
return value.includes("\0") || value.includes(",") || value.includes("\n") || value.includes("\r");
|
|
3419
|
+
}
|
|
3420
|
+
function requireCanonicalRealDirectory(value, name) {
|
|
3421
|
+
if (typeof value !== "string") throw new Error(`worker: ${name} must be a path`);
|
|
3422
|
+
try {
|
|
3423
|
+
const entry = lstatSync4(value);
|
|
3424
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
3425
|
+
throw new Error("unsafe entry");
|
|
3426
|
+
}
|
|
3427
|
+
const canonical = realpathSync4(value);
|
|
3428
|
+
if (canonical !== value) throw new Error("non-canonical entry");
|
|
3429
|
+
return canonical;
|
|
3430
|
+
} catch {
|
|
3431
|
+
throw new Error(`worker: ${name} must be a real directory`);
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
function resolveCanonicalDirectory(value, name) {
|
|
3435
|
+
try {
|
|
3436
|
+
const entry = statSync3(value);
|
|
3437
|
+
if (!entry.isDirectory()) throw new Error("not a directory");
|
|
3438
|
+
return realpathSync4(value);
|
|
3439
|
+
} catch {
|
|
3440
|
+
throw new Error(`worker: ${name} is unavailable or unsafe`);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
function requireCanonicalContainedDirectory(root, value, name) {
|
|
3444
|
+
const canonical = requireCanonicalRealDirectory(value, name);
|
|
3445
|
+
if (!lexicallyWithin(root, canonical)) throw new Error(`worker: ${name} escaped its root`);
|
|
3446
|
+
return canonical;
|
|
3447
|
+
}
|
|
3448
|
+
function assertMinimalWorkerScratch(value) {
|
|
3449
|
+
const scratch = objectRecord(value, "minimal worker scratch");
|
|
3450
|
+
assertExactKeys(
|
|
3451
|
+
scratch,
|
|
3452
|
+
/* @__PURE__ */ new Set(["root", "homeDir", "tempDir", "piAgentDir", "reviewCwd", "reviewSessionDir"]),
|
|
3453
|
+
"minimal worker scratch",
|
|
3454
|
+
/* @__PURE__ */ new Set(["root", "homeDir", "tempDir", "piAgentDir", "reviewCwd", "reviewSessionDir"])
|
|
3455
|
+
);
|
|
3456
|
+
requireAbsolutePath(scratch.root, "scratch root");
|
|
3457
|
+
for (const key of [
|
|
3458
|
+
"homeDir",
|
|
3459
|
+
"tempDir",
|
|
3460
|
+
"piAgentDir",
|
|
3461
|
+
"reviewCwd",
|
|
3462
|
+
"reviewSessionDir"
|
|
3463
|
+
]) {
|
|
3464
|
+
requireAbsolutePath(scratch[key], key);
|
|
3465
|
+
if (!lexicallyWithin(scratch.root, scratch[key])) {
|
|
3466
|
+
throw new Error(`worker: ${key} escaped the scratch root`);
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
function requireCanonicalWindowsDirectory(value, name) {
|
|
3471
|
+
if (!value || !isAbsolute5(value) || !existsSync12(value) || !statSync3(value).isDirectory()) {
|
|
3472
|
+
throw new Error(`minimal worker bootstrap requires a valid ${name} directory`);
|
|
3473
|
+
}
|
|
3474
|
+
return realpathSync4(value);
|
|
3475
|
+
}
|
|
3476
|
+
function requireCanonicalWindowsFile(value, name) {
|
|
3477
|
+
if (!value || !isAbsolute5(value) || !existsSync12(value) || !statSync3(value).isFile()) {
|
|
3478
|
+
throw new Error(`minimal worker bootstrap requires a valid ${name} executable`);
|
|
3479
|
+
}
|
|
3480
|
+
return realpathSync4(value);
|
|
3481
|
+
}
|
|
3482
|
+
function objectRecord(value, label) {
|
|
3483
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3484
|
+
throw new Error(`worker: ${label} must be an object`);
|
|
3485
|
+
}
|
|
3486
|
+
return value;
|
|
3487
|
+
}
|
|
3488
|
+
function assertExactKeys(value, allowed, label, required = allowed) {
|
|
3489
|
+
const unexpected = Object.keys(value).filter((key) => !allowed.has(key));
|
|
3490
|
+
if (unexpected.length > 0) {
|
|
3491
|
+
throw new Error(`worker: ${label} contains unexpected fields: ${unexpected.sort().join(", ")}`);
|
|
3492
|
+
}
|
|
3493
|
+
const missing = [...required].filter((key) => !(key in value));
|
|
3494
|
+
if (missing.length > 0) {
|
|
3495
|
+
throw new Error(`worker: ${label} is missing required fields: ${missing.sort().join(", ")}`);
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
function isResolvedAgent(value) {
|
|
3499
|
+
if (!value || typeof value !== "object") return false;
|
|
3500
|
+
const resolved = value;
|
|
3501
|
+
return Boolean(
|
|
3502
|
+
resolved.agent && typeof resolved.agent.id === "string" && typeof resolved.agent.dir === "string" && resolved.team && typeof resolved.team.id === "string" && typeof resolved.team.path === "string" && typeof resolved.model === "string"
|
|
3503
|
+
);
|
|
3504
|
+
}
|
|
3505
|
+
function requireString(value, name) {
|
|
3506
|
+
if (typeof value !== "string") throw new Error(`worker: ${name} must be a string`);
|
|
3507
|
+
}
|
|
3508
|
+
function nonEmptyString(value) {
|
|
3509
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
3510
|
+
}
|
|
3511
|
+
function isString(value) {
|
|
3512
|
+
return typeof value === "string";
|
|
3513
|
+
}
|
|
3514
|
+
function requireAbsolutePath(value, name) {
|
|
3515
|
+
if (typeof value !== "string" || !isAbsolute5(value) || value.includes("\0")) {
|
|
3516
|
+
throw new Error(`worker: ${name} must be an absolute path`);
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
function lexicallyWithin(root, candidate) {
|
|
3520
|
+
const rel = relative5(root, candidate);
|
|
3521
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute5(rel);
|
|
3522
|
+
}
|
|
3523
|
+
|
|
2568
3524
|
// ../daemon/src/runtime/pi/tools.ts
|
|
2569
3525
|
import { Type as Type2 } from "typebox";
|
|
2570
3526
|
|
|
2571
3527
|
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
2572
|
-
import { existsSync as
|
|
2573
|
-
import { join as
|
|
3528
|
+
import { existsSync as existsSync13, rmSync as rmSync12 } from "fs";
|
|
3529
|
+
import { join as join17 } from "path";
|
|
2574
3530
|
function bootstrapTool(agentDir) {
|
|
2575
3531
|
return {
|
|
2576
3532
|
def: {
|
|
@@ -2579,9 +3535,9 @@ function bootstrapTool(agentDir) {
|
|
|
2579
3535
|
parameters: { type: "object", properties: {} }
|
|
2580
3536
|
},
|
|
2581
3537
|
async invoke() {
|
|
2582
|
-
const path =
|
|
2583
|
-
if (
|
|
2584
|
-
|
|
3538
|
+
const path = join17(agentDir, "BOOTSTRAP.md");
|
|
3539
|
+
if (existsSync13(path)) {
|
|
3540
|
+
rmSync12(path);
|
|
2585
3541
|
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
2586
3542
|
}
|
|
2587
3543
|
return "BOOTSTRAP.md was already removed.";
|
|
@@ -2720,15 +3676,15 @@ function browserTools(host, agentId) {
|
|
|
2720
3676
|
|
|
2721
3677
|
// ../daemon/src/runtime/tools/deliver-file.ts
|
|
2722
3678
|
import {
|
|
2723
|
-
closeSync as
|
|
2724
|
-
constants as
|
|
2725
|
-
fstatSync as
|
|
2726
|
-
openSync as
|
|
3679
|
+
closeSync as closeSync4,
|
|
3680
|
+
constants as constants5,
|
|
3681
|
+
fstatSync as fstatSync3,
|
|
3682
|
+
openSync as openSync4,
|
|
2727
3683
|
readFileSync as readFileSync10,
|
|
2728
|
-
realpathSync as
|
|
2729
|
-
statSync as
|
|
3684
|
+
realpathSync as realpathSync5,
|
|
3685
|
+
statSync as statSync4
|
|
2730
3686
|
} from "fs";
|
|
2731
|
-
import { basename as basename2, extname, isAbsolute as
|
|
3687
|
+
import { basename as basename2, extname, isAbsolute as isAbsolute6, relative as relative6, resolve as resolve5, sep as sep6 } from "path";
|
|
2732
3688
|
var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
|
|
2733
3689
|
var MIME = {
|
|
2734
3690
|
".pdf": "application/pdf",
|
|
@@ -2745,14 +3701,14 @@ var MIME = {
|
|
|
2745
3701
|
".webp": "image/webp"
|
|
2746
3702
|
};
|
|
2747
3703
|
function isWithin2(root, candidate) {
|
|
2748
|
-
const rel =
|
|
2749
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
3704
|
+
const rel = relative6(root, candidate);
|
|
3705
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep6}`) && !isAbsolute6(rel);
|
|
2750
3706
|
}
|
|
2751
3707
|
function deliverFileTool(cwd, sink) {
|
|
2752
|
-
const workspacePath =
|
|
3708
|
+
const workspacePath = resolve5(cwd);
|
|
2753
3709
|
let workspaceRealPath = null;
|
|
2754
3710
|
try {
|
|
2755
|
-
workspaceRealPath =
|
|
3711
|
+
workspaceRealPath = realpathSync5(workspacePath);
|
|
2756
3712
|
} catch {
|
|
2757
3713
|
}
|
|
2758
3714
|
return {
|
|
@@ -2774,10 +3730,10 @@ function deliverFileTool(cwd, sink) {
|
|
|
2774
3730
|
async invoke(args) {
|
|
2775
3731
|
const p = String(args.path ?? "");
|
|
2776
3732
|
if (!p) throw new Error("deliver_file: path is required");
|
|
2777
|
-
if (
|
|
3733
|
+
if (isAbsolute6(p)) {
|
|
2778
3734
|
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2779
3735
|
}
|
|
2780
|
-
const abs =
|
|
3736
|
+
const abs = resolve5(workspacePath, p);
|
|
2781
3737
|
if (!isWithin2(workspacePath, abs)) {
|
|
2782
3738
|
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2783
3739
|
}
|
|
@@ -2787,8 +3743,8 @@ function deliverFileTool(cwd, sink) {
|
|
|
2787
3743
|
let real;
|
|
2788
3744
|
let expected;
|
|
2789
3745
|
try {
|
|
2790
|
-
real =
|
|
2791
|
-
expected =
|
|
3746
|
+
real = realpathSync5(abs);
|
|
3747
|
+
expected = statSync4(real);
|
|
2792
3748
|
} catch {
|
|
2793
3749
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
2794
3750
|
}
|
|
@@ -2797,13 +3753,13 @@ function deliverFileTool(cwd, sink) {
|
|
|
2797
3753
|
}
|
|
2798
3754
|
let fd;
|
|
2799
3755
|
try {
|
|
2800
|
-
fd =
|
|
3756
|
+
fd = openSync4(real, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
2801
3757
|
} catch {
|
|
2802
3758
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
2803
3759
|
}
|
|
2804
3760
|
let data;
|
|
2805
3761
|
try {
|
|
2806
|
-
const stat2 =
|
|
3762
|
+
const stat2 = fstatSync3(fd);
|
|
2807
3763
|
if (!stat2.isFile()) {
|
|
2808
3764
|
throw new Error(`deliver_file: not a regular file: ${p}`);
|
|
2809
3765
|
}
|
|
@@ -2812,7 +3768,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
2812
3768
|
}
|
|
2813
3769
|
let confirmedReal;
|
|
2814
3770
|
try {
|
|
2815
|
-
confirmedReal =
|
|
3771
|
+
confirmedReal = realpathSync5(abs);
|
|
2816
3772
|
} catch {
|
|
2817
3773
|
throw new Error(`deliver_file: path changed during validation: ${p}`);
|
|
2818
3774
|
}
|
|
@@ -2826,7 +3782,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
2826
3782
|
}
|
|
2827
3783
|
data = readFileSync10(fd).toString("base64");
|
|
2828
3784
|
} finally {
|
|
2829
|
-
|
|
3785
|
+
closeSync4(fd);
|
|
2830
3786
|
}
|
|
2831
3787
|
const name = basename2(abs);
|
|
2832
3788
|
const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -2837,8 +3793,16 @@ function deliverFileTool(cwd, sink) {
|
|
|
2837
3793
|
}
|
|
2838
3794
|
|
|
2839
3795
|
// ../daemon/src/runtime/tools/home.ts
|
|
2840
|
-
import {
|
|
2841
|
-
|
|
3796
|
+
import {
|
|
3797
|
+
closeSync as closeSync5,
|
|
3798
|
+
constants as constants6,
|
|
3799
|
+
fstatSync as fstatSync4,
|
|
3800
|
+
lstatSync as lstatSync5,
|
|
3801
|
+
openSync as openSync5,
|
|
3802
|
+
readdirSync as readdirSync7,
|
|
3803
|
+
writeFileSync as writeFileSync8
|
|
3804
|
+
} from "fs";
|
|
3805
|
+
import { join as join18 } from "path";
|
|
2842
3806
|
var HOME_FILES_READABLE = [
|
|
2843
3807
|
"IDENTITY.md",
|
|
2844
3808
|
"SOUL.md",
|
|
@@ -2848,6 +3812,7 @@ var HOME_FILES_READABLE = [
|
|
|
2848
3812
|
];
|
|
2849
3813
|
var HOME_FILES_WRITABLE = ["IDENTITY.md", "SOUL.md", "AGENTS.md", "TOOLS.md"];
|
|
2850
3814
|
function homeTools(agentDir) {
|
|
3815
|
+
const homeRoot = resolvePrivateHomeRoot(agentDir);
|
|
2851
3816
|
return [
|
|
2852
3817
|
{
|
|
2853
3818
|
def: {
|
|
@@ -2868,9 +3833,9 @@ function homeTools(agentDir) {
|
|
|
2868
3833
|
`home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
|
|
2869
3834
|
);
|
|
2870
3835
|
}
|
|
2871
|
-
const path =
|
|
3836
|
+
const path = privateHomeFile(homeRoot, file);
|
|
2872
3837
|
try {
|
|
2873
|
-
return
|
|
3838
|
+
return readRegularFileNoFollow(path);
|
|
2874
3839
|
} catch (err) {
|
|
2875
3840
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2876
3841
|
throw new Error(`home_read: could not read ${file}: ${msg}`);
|
|
@@ -2898,12 +3863,27 @@ function homeTools(agentDir) {
|
|
|
2898
3863
|
);
|
|
2899
3864
|
}
|
|
2900
3865
|
const content = typeof args.content === "string" ? args.content : "";
|
|
2901
|
-
const path =
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3866
|
+
const path = privateHomeFile(homeRoot, file);
|
|
3867
|
+
let fd;
|
|
3868
|
+
try {
|
|
3869
|
+
rejectExistingSymlinkOrNonFile(path);
|
|
3870
|
+
fd = openSync5(
|
|
3871
|
+
path,
|
|
3872
|
+
constants6.O_WRONLY | constants6.O_CREAT | constants6.O_TRUNC | noFollowFlag(),
|
|
3873
|
+
384
|
|
3874
|
+
);
|
|
3875
|
+
if (!fstatSync4(fd).isFile()) throw new Error("target is not a regular file");
|
|
3876
|
+
writeFileSync8(fd, content, "utf8");
|
|
3877
|
+
} catch (err) {
|
|
3878
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3879
|
+
throw new Error(`home_write: could not write ${file}: ${msg}`);
|
|
3880
|
+
} finally {
|
|
3881
|
+
if (fd !== void 0) closeSync5(fd);
|
|
3882
|
+
}
|
|
3883
|
+
return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
|
|
3884
|
+
}
|
|
3885
|
+
},
|
|
3886
|
+
{
|
|
2907
3887
|
def: {
|
|
2908
3888
|
name: "home_list",
|
|
2909
3889
|
description: "List your home files with their sizes.",
|
|
@@ -2912,9 +3892,10 @@ function homeTools(agentDir) {
|
|
|
2912
3892
|
async invoke() {
|
|
2913
3893
|
const entries = [];
|
|
2914
3894
|
for (const file of HOME_FILES_READABLE) {
|
|
2915
|
-
const path =
|
|
3895
|
+
const path = privateHomeFile(homeRoot, file);
|
|
2916
3896
|
try {
|
|
2917
|
-
const s =
|
|
3897
|
+
const s = lstatSync5(path);
|
|
3898
|
+
if (s.isSymbolicLink() || !s.isFile()) continue;
|
|
2918
3899
|
entries.push(`${file} (${s.size}b)`);
|
|
2919
3900
|
} catch {
|
|
2920
3901
|
}
|
|
@@ -2922,7 +3903,7 @@ function homeTools(agentDir) {
|
|
|
2922
3903
|
if (entries.length === 0) {
|
|
2923
3904
|
const dirEntries = (() => {
|
|
2924
3905
|
try {
|
|
2925
|
-
return readdirSync7(
|
|
3906
|
+
return readdirSync7(homeRoot);
|
|
2926
3907
|
} catch {
|
|
2927
3908
|
return [];
|
|
2928
3909
|
}
|
|
@@ -2934,6 +3915,30 @@ function homeTools(agentDir) {
|
|
|
2934
3915
|
}
|
|
2935
3916
|
];
|
|
2936
3917
|
}
|
|
3918
|
+
function resolvePrivateHomeRoot(agentDir) {
|
|
3919
|
+
try {
|
|
3920
|
+
return resolveRealDirectory(agentDir);
|
|
3921
|
+
} catch {
|
|
3922
|
+
throw new Error("home tools require a real private Agent directory");
|
|
3923
|
+
}
|
|
3924
|
+
}
|
|
3925
|
+
function privateHomeFile(homeRoot, file) {
|
|
3926
|
+
return join18(homeRoot, file);
|
|
3927
|
+
}
|
|
3928
|
+
function rejectSymlinkOrNonFile(path) {
|
|
3929
|
+
const entry = lstatSync5(path);
|
|
3930
|
+
if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
|
|
3931
|
+
if (!entry.isFile()) throw new Error("target is not a regular file");
|
|
3932
|
+
}
|
|
3933
|
+
function rejectExistingSymlinkOrNonFile(path) {
|
|
3934
|
+
try {
|
|
3935
|
+
rejectSymlinkOrNonFile(path);
|
|
3936
|
+
} catch (error) {
|
|
3937
|
+
const code = error.code;
|
|
3938
|
+
if (code === "ENOENT") return;
|
|
3939
|
+
throw error;
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
2937
3942
|
|
|
2938
3943
|
// ../daemon/src/runtime/tools/mcp.ts
|
|
2939
3944
|
function mcpProxyTools(host, tools) {
|
|
@@ -3306,6 +4311,7 @@ function extractReadable(html, url, mode) {
|
|
|
3306
4311
|
// ../daemon/src/runtime/tools/web-ssrf.ts
|
|
3307
4312
|
import { lookup as dnsLookupCb } from "dns";
|
|
3308
4313
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4314
|
+
import { isIP } from "net";
|
|
3309
4315
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
3310
4316
|
var SsrFBlockedError = class extends Error {
|
|
3311
4317
|
constructor(message) {
|
|
@@ -3314,7 +4320,6 @@ var SsrFBlockedError = class extends Error {
|
|
|
3314
4320
|
}
|
|
3315
4321
|
};
|
|
3316
4322
|
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "metadata.google.internal"]);
|
|
3317
|
-
var PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
|
|
3318
4323
|
function normalizeHostname(hostname) {
|
|
3319
4324
|
let h = hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
3320
4325
|
if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
|
|
@@ -3322,11 +4327,32 @@ function normalizeHostname(hostname) {
|
|
|
3322
4327
|
}
|
|
3323
4328
|
function parseIpv4(address) {
|
|
3324
4329
|
const parts = address.split(".");
|
|
3325
|
-
if (parts.length !== 4) return null;
|
|
3326
|
-
const nums = parts.map(
|
|
4330
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part))) return null;
|
|
4331
|
+
const nums = parts.map(Number);
|
|
3327
4332
|
if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null;
|
|
3328
4333
|
return nums;
|
|
3329
4334
|
}
|
|
4335
|
+
function parseIpv6(address) {
|
|
4336
|
+
const zoneIndex = address.indexOf("%");
|
|
4337
|
+
let source = zoneIndex === -1 ? address : address.slice(0, zoneIndex);
|
|
4338
|
+
if (isIP(source) !== 6) return null;
|
|
4339
|
+
if (source.includes(".")) {
|
|
4340
|
+
const separator = source.lastIndexOf(":");
|
|
4341
|
+
const ipv4 = parseIpv4(source.slice(separator + 1));
|
|
4342
|
+
if (separator === -1 || !ipv4) return null;
|
|
4343
|
+
const [a, b, c, d] = ipv4;
|
|
4344
|
+
source = `${source.slice(0, separator)}:${(a << 8 | b).toString(16)}:${(c << 8 | d).toString(16)}`;
|
|
4345
|
+
}
|
|
4346
|
+
const halves = source.split("::");
|
|
4347
|
+
if (halves.length > 2) return null;
|
|
4348
|
+
const left = halves[0] ? halves[0].split(":") : [];
|
|
4349
|
+
const right = halves[1] ? halves[1].split(":") : [];
|
|
4350
|
+
const omitted = 8 - left.length - right.length;
|
|
4351
|
+
if (halves.length === 1 && omitted !== 0 || halves.length === 2 && omitted < 1) return null;
|
|
4352
|
+
return [...left, ...Array.from({ length: omitted }, () => "0"), ...right].map(
|
|
4353
|
+
(word) => Number.parseInt(word, 16)
|
|
4354
|
+
);
|
|
4355
|
+
}
|
|
3330
4356
|
function isPrivateIpv4(parts) {
|
|
3331
4357
|
const [a, b] = parts;
|
|
3332
4358
|
if (a === 0 || a === 10 || a === 127) return true;
|
|
@@ -3336,21 +4362,38 @@ function isPrivateIpv4(parts) {
|
|
|
3336
4362
|
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
3337
4363
|
return false;
|
|
3338
4364
|
}
|
|
4365
|
+
function embeddedIpv4(words) {
|
|
4366
|
+
const [a, b, c, d, e, f, g, h] = words;
|
|
4367
|
+
const low32 = [g >> 8, g & 255, h >> 8, h & 255];
|
|
4368
|
+
if (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0 || a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 65535 || a === 0 && b === 0 && c === 0 && d === 0 && e === 65535 && f === 0) {
|
|
4369
|
+
return low32;
|
|
4370
|
+
}
|
|
4371
|
+
if (a === 100 && b === 65435 && c === 0 && d === 0 && e === 0 && f === 0) {
|
|
4372
|
+
return low32;
|
|
4373
|
+
}
|
|
4374
|
+
if (a === 100 && b === 65435 && c === 1 && e >> 8 === 0) {
|
|
4375
|
+
return [d >> 8, d & 255, e & 255, f >> 8];
|
|
4376
|
+
}
|
|
4377
|
+
if (a === 8194) return [b >> 8, b & 255, c >> 8, c & 255];
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
3339
4380
|
function isPrivateIpAddress(address) {
|
|
3340
4381
|
let norm = address.trim().toLowerCase();
|
|
3341
4382
|
if (norm.startsWith("[") && norm.endsWith("]")) norm = norm.slice(1, -1);
|
|
3342
4383
|
if (!norm) return false;
|
|
3343
|
-
if (norm.startsWith("::ffff:")) {
|
|
3344
|
-
const mapped = norm.slice("::ffff:".length);
|
|
3345
|
-
const ipv42 = parseIpv4(mapped);
|
|
3346
|
-
if (ipv42) return isPrivateIpv4(ipv42);
|
|
3347
|
-
}
|
|
3348
|
-
if (norm.includes(":")) {
|
|
3349
|
-
if (norm === "::" || norm === "::1") return true;
|
|
3350
|
-
return PRIVATE_IPV6_PREFIXES.some((p) => norm.startsWith(p));
|
|
3351
|
-
}
|
|
3352
4384
|
const ipv4 = parseIpv4(norm);
|
|
3353
|
-
|
|
4385
|
+
if (ipv4) return isPrivateIpv4(ipv4);
|
|
4386
|
+
const words = parseIpv6(norm);
|
|
4387
|
+
if (!words) return false;
|
|
4388
|
+
const [first] = words;
|
|
4389
|
+
if (words.every((word) => word === 0)) return true;
|
|
4390
|
+
if (words.slice(0, 7).every((word) => word === 0) && words[7] === 1) return true;
|
|
4391
|
+
if ((first & 65024) === 64512) return true;
|
|
4392
|
+
if ((first & 65472) === 65152) return true;
|
|
4393
|
+
if ((first & 65472) === 65216) return true;
|
|
4394
|
+
if ((first & 65280) === 65280) return true;
|
|
4395
|
+
const embedded = embeddedIpv4(words);
|
|
4396
|
+
return embedded ? isPrivateIpv4(embedded) : false;
|
|
3354
4397
|
}
|
|
3355
4398
|
function isBlockedHostname(hostname) {
|
|
3356
4399
|
const h = normalizeHostname(hostname);
|
|
@@ -3407,6 +4450,14 @@ async function resolveAndCheck(hostname) {
|
|
|
3407
4450
|
function isRedirectStatus(s) {
|
|
3408
4451
|
return s === 301 || s === 302 || s === 303 || s === 307 || s === 308;
|
|
3409
4452
|
}
|
|
4453
|
+
function withoutCrossOriginCredentials(headersInit) {
|
|
4454
|
+
if (!headersInit) return void 0;
|
|
4455
|
+
const headers = new Headers(headersInit);
|
|
4456
|
+
headers.delete("authorization");
|
|
4457
|
+
headers.delete("cookie");
|
|
4458
|
+
headers.delete("proxy-authorization");
|
|
4459
|
+
return headers;
|
|
4460
|
+
}
|
|
3410
4461
|
async function closeDispatcher(d) {
|
|
3411
4462
|
if (!d) return;
|
|
3412
4463
|
try {
|
|
@@ -3427,6 +4478,7 @@ async function guardedFetch(opts) {
|
|
|
3427
4478
|
});
|
|
3428
4479
|
}
|
|
3429
4480
|
let current = opts.url;
|
|
4481
|
+
let requestInit = { ...opts.init ?? {} };
|
|
3430
4482
|
const visited = /* @__PURE__ */ new Set();
|
|
3431
4483
|
let redirects = 0;
|
|
3432
4484
|
let dispatcher = null;
|
|
@@ -3454,7 +4506,7 @@ async function guardedFetch(opts) {
|
|
|
3454
4506
|
dispatcher = canPin ? new Agent({ connect: { lookup: createPinnedLookup(parsed.hostname, addrs) } }) : null;
|
|
3455
4507
|
}
|
|
3456
4508
|
const init = {
|
|
3457
|
-
...
|
|
4509
|
+
...requestInit,
|
|
3458
4510
|
redirect: "manual",
|
|
3459
4511
|
signal: abortController.signal
|
|
3460
4512
|
};
|
|
@@ -3484,6 +4536,12 @@ async function guardedFetch(opts) {
|
|
|
3484
4536
|
}
|
|
3485
4537
|
visited.add(next);
|
|
3486
4538
|
void res.body?.cancel();
|
|
4539
|
+
if (new URL(next).origin !== parsed.origin) {
|
|
4540
|
+
requestInit = {
|
|
4541
|
+
...requestInit,
|
|
4542
|
+
headers: withoutCrossOriginCredentials(requestInit.headers)
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
3487
4545
|
current = next;
|
|
3488
4546
|
continue;
|
|
3489
4547
|
}
|
|
@@ -3639,6 +4697,17 @@ function cacheSet(cache, key, value, ttlMs, maxEntries) {
|
|
|
3639
4697
|
}
|
|
3640
4698
|
}
|
|
3641
4699
|
function webTools(opts) {
|
|
4700
|
+
return buildWebTools(opts, true);
|
|
4701
|
+
}
|
|
4702
|
+
function protectedWebFetchTool(opts) {
|
|
4703
|
+
const tool = buildWebTools(
|
|
4704
|
+
{ ...opts, env: {}, allowPrivate: false, firecrawlDisabled: true },
|
|
4705
|
+
false
|
|
4706
|
+
)[0];
|
|
4707
|
+
if (!tool) throw new Error("protected web_fetch tool construction failed");
|
|
4708
|
+
return tool;
|
|
4709
|
+
}
|
|
4710
|
+
function buildWebTools(opts, includeSearch) {
|
|
3642
4711
|
const fetchFn = opts?.fetchImpl ?? undiciFetch2;
|
|
3643
4712
|
const env = opts?.env ?? process.env;
|
|
3644
4713
|
const allowPrivate = opts?.allowPrivate ?? false;
|
|
@@ -3648,8 +4717,9 @@ function webTools(opts) {
|
|
|
3648
4717
|
const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
3649
4718
|
const firecrawlDisabled = opts?.firecrawlDisabled ?? false;
|
|
3650
4719
|
const cache = /* @__PURE__ */ new Map();
|
|
3651
|
-
|
|
3652
|
-
|
|
4720
|
+
const tools = [];
|
|
4721
|
+
if (includeSearch) {
|
|
4722
|
+
tools.push({
|
|
3653
4723
|
def: {
|
|
3654
4724
|
name: "web_search",
|
|
3655
4725
|
description: "Search the internet. Returns a list of titles, URLs, and snippets. Requires BRAVE_API_KEY or SEARXNG_URL to be configured.",
|
|
@@ -3680,99 +4750,100 @@ function webTools(opts) {
|
|
|
3680
4750
|
"web_search: no search backend configured. Set BRAVE_API_KEY (free at https://brave.com/search/api/) or SEARXNG_URL."
|
|
3681
4751
|
);
|
|
3682
4752
|
}
|
|
3683
|
-
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
extract_mode: {
|
|
3697
|
-
type: "string",
|
|
3698
|
-
enum: ["markdown", "text"],
|
|
3699
|
-
description: 'Output format for HTML pages. Default "markdown".'
|
|
3700
|
-
}
|
|
4753
|
+
});
|
|
4754
|
+
}
|
|
4755
|
+
tools.push({
|
|
4756
|
+
def: {
|
|
4757
|
+
name: "web_fetch",
|
|
4758
|
+
description: firecrawlDisabled ? "Fetch a public HTTP or HTTPS URL and return its readable content. Private, loopback, link-local, metadata, and DNS-rebinding targets are blocked. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes." : "Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. When the primary extraction returns near-empty content from a 2xx HTML page (typical of JS-only shells), the tool automatically retries via Firecrawl if FIRECRAWL_API_KEY is configured. Results are cached for 15 minutes.",
|
|
4759
|
+
parameters: {
|
|
4760
|
+
type: "object",
|
|
4761
|
+
properties: {
|
|
4762
|
+
url: { type: "string", description: "The URL to fetch (http or https)" },
|
|
4763
|
+
max_length: {
|
|
4764
|
+
type: "number",
|
|
4765
|
+
description: "Max characters to return (default 20000)"
|
|
3701
4766
|
},
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
const url = String(args.url ?? "");
|
|
3707
|
-
if (!url) throw new Error("web_fetch: url is required");
|
|
3708
|
-
const maxLen = typeof args.max_length === "number" ? args.max_length : DEFAULT_MAX_LENGTH;
|
|
3709
|
-
const mode = args.extract_mode === "text" ? "text" : "markdown";
|
|
3710
|
-
const cacheKey = `${mode}|${url}`;
|
|
3711
|
-
const cached = cacheGet(cache, cacheKey);
|
|
3712
|
-
if (cached) return formatOutput(cached, maxLen);
|
|
3713
|
-
let result = null;
|
|
3714
|
-
try {
|
|
3715
|
-
result = await guardedFetch({
|
|
3716
|
-
url,
|
|
3717
|
-
fetchImpl: opts?.fetchImpl,
|
|
3718
|
-
allowPrivate,
|
|
3719
|
-
timeoutMs,
|
|
3720
|
-
init: {
|
|
3721
|
-
headers: {
|
|
3722
|
-
"user-agent": DEFAULT_USER_AGENT,
|
|
3723
|
-
accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*",
|
|
3724
|
-
"accept-language": "en-US,en;q=0.9"
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
});
|
|
3728
|
-
if (!result.response.ok) {
|
|
3729
|
-
throw new Error(`${result.response.status} ${result.response.statusText}`);
|
|
4767
|
+
extract_mode: {
|
|
4768
|
+
type: "string",
|
|
4769
|
+
enum: ["markdown", "text"],
|
|
4770
|
+
description: 'Output format for HTML pages. Default "markdown".'
|
|
3730
4771
|
}
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
4772
|
+
},
|
|
4773
|
+
required: ["url"]
|
|
4774
|
+
}
|
|
4775
|
+
},
|
|
4776
|
+
async invoke(args) {
|
|
4777
|
+
const url = String(args.url ?? "");
|
|
4778
|
+
if (!url) throw new Error("web_fetch: url is required");
|
|
4779
|
+
const maxLen = typeof args.max_length === "number" ? args.max_length : DEFAULT_MAX_LENGTH;
|
|
4780
|
+
const mode = args.extract_mode === "text" ? "text" : "markdown";
|
|
4781
|
+
const cacheKey = `${mode}|${url}`;
|
|
4782
|
+
const cached = cacheGet(cache, cacheKey);
|
|
4783
|
+
if (cached) return formatOutput(cached, maxLen);
|
|
4784
|
+
let result = null;
|
|
4785
|
+
try {
|
|
4786
|
+
result = await guardedFetch({
|
|
4787
|
+
url,
|
|
4788
|
+
fetchImpl: opts?.fetchImpl,
|
|
4789
|
+
allowPrivate,
|
|
4790
|
+
timeoutMs,
|
|
4791
|
+
init: {
|
|
4792
|
+
headers: {
|
|
4793
|
+
"user-agent": DEFAULT_USER_AGENT,
|
|
4794
|
+
accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*",
|
|
4795
|
+
"accept-language": "en-US,en;q=0.9"
|
|
3742
4796
|
}
|
|
3743
|
-
}
|
|
4797
|
+
}
|
|
4798
|
+
});
|
|
4799
|
+
if (!result.response.ok) {
|
|
4800
|
+
throw new Error(`${result.response.status} ${result.response.statusText}`);
|
|
4801
|
+
}
|
|
4802
|
+
const ct = result.response.headers.get("content-type") ?? "";
|
|
4803
|
+
const isHtml = ct.includes("text/html") || ct.includes("xhtml");
|
|
4804
|
+
const { text: body, truncated } = await readBodyCapped(result.response, maxBodyBytes);
|
|
4805
|
+
let extracted;
|
|
4806
|
+
if (isHtml) {
|
|
4807
|
+
extracted = extractReadable(body, result.finalUrl, mode);
|
|
4808
|
+
} else if (ct.includes("application/json")) {
|
|
4809
|
+
try {
|
|
4810
|
+
extracted = { text: JSON.stringify(JSON.parse(body), null, 2) };
|
|
4811
|
+
} catch {
|
|
3744
4812
|
extracted = { text: body };
|
|
3745
4813
|
}
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
4814
|
+
} else {
|
|
4815
|
+
extracted = { text: body };
|
|
4816
|
+
}
|
|
4817
|
+
if (isHtml && !firecrawlDisabled && extracted.text.length < FIRECRAWL_FALLBACK_THRESHOLD) {
|
|
4818
|
+
const rescued = await firecrawlScrape(result.finalUrl, mode, env, fetchFn, timeoutMs);
|
|
4819
|
+
if (rescued) {
|
|
4820
|
+
extracted = {
|
|
4821
|
+
...rescued,
|
|
4822
|
+
text: `${rescued.text}
|
|
3752
4823
|
|
|
3753
4824
|
[content rendered via Firecrawl fallback \u2014 primary extraction returned ${extracted.text.length} chars]`
|
|
3754
|
-
|
|
3755
|
-
}
|
|
4825
|
+
};
|
|
3756
4826
|
}
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
4827
|
+
}
|
|
4828
|
+
if (truncated) {
|
|
4829
|
+
extracted = {
|
|
4830
|
+
...extracted,
|
|
4831
|
+
text: `${extracted.text}
|
|
3761
4832
|
|
|
3762
4833
|
[raw body truncated at ${maxBodyBytes} bytes before extraction \u2014 page exceeded the size cap]`
|
|
3763
|
-
|
|
3764
|
-
}
|
|
3765
|
-
cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
|
|
3766
|
-
return formatOutput(extracted, maxLen);
|
|
3767
|
-
} catch (err) {
|
|
3768
|
-
if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
|
|
3769
|
-
throw new Error(`web_fetch: ${describeError(err)}`);
|
|
3770
|
-
} finally {
|
|
3771
|
-
if (result) await result.release();
|
|
4834
|
+
};
|
|
3772
4835
|
}
|
|
4836
|
+
cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
|
|
4837
|
+
return formatOutput(extracted, maxLen);
|
|
4838
|
+
} catch (err) {
|
|
4839
|
+
if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
|
|
4840
|
+
throw new Error(`web_fetch: ${describeError(err)}`);
|
|
4841
|
+
} finally {
|
|
4842
|
+
if (result) await result.release();
|
|
3773
4843
|
}
|
|
3774
4844
|
}
|
|
3775
|
-
|
|
4845
|
+
});
|
|
4846
|
+
return tools;
|
|
3776
4847
|
}
|
|
3777
4848
|
function formatOutput(r, maxLen) {
|
|
3778
4849
|
const body = r.title ? `# ${r.title}
|
|
@@ -3835,8 +4906,26 @@ function createBazilionCustomTools(opts) {
|
|
|
3835
4906
|
}
|
|
3836
4907
|
return handlers.map(ourToolToPiTool);
|
|
3837
4908
|
}
|
|
4909
|
+
function createProtectedBazilionCustomTools(opts) {
|
|
4910
|
+
const handlers = [
|
|
4911
|
+
...memoryTools(opts.memory),
|
|
4912
|
+
...homeTools(opts.agent.agent.dir),
|
|
4913
|
+
bootstrapTool(opts.agent.agent.dir),
|
|
4914
|
+
protectedWebFetchTool(),
|
|
4915
|
+
...messagingTools(opts.messagingHost, opts.agent.agent.id),
|
|
4916
|
+
...userMdTools(opts.userMdHost, opts.agent.team.id),
|
|
4917
|
+
deliverFileTool(opts.agent.team.path, opts.fileSink)
|
|
4918
|
+
];
|
|
4919
|
+
return handlers.map(ourToolToPiTool);
|
|
4920
|
+
}
|
|
3838
4921
|
|
|
3839
4922
|
// ../daemon/src/runtime/pi/session.ts
|
|
4923
|
+
var PROTECTED_MODEL_CWD = "/workspace";
|
|
4924
|
+
var REVIEW_MODEL_CWD = "/review";
|
|
4925
|
+
var PROTECTED_BASE_SYSTEM_PROMPT = `You are an assistant operating inside Bazilion's protected
|
|
4926
|
+
Agent runtime. Use only the tools exposed for this turn. Coding commands execute in an ephemeral,
|
|
4927
|
+
network-disabled Docker container whose Team workspace is /workspace. Host paths and host tools are
|
|
4928
|
+
not available. Be concise and show container paths clearly when working with files.`;
|
|
3840
4929
|
async function createBazilionSession(opts) {
|
|
3841
4930
|
const {
|
|
3842
4931
|
agent,
|
|
@@ -3869,19 +4958,19 @@ async function createBazilionSession(opts) {
|
|
|
3869
4958
|
});
|
|
3870
4959
|
const model = resolvePiModel(modelRuntime, providerName, modelId, baseUrl);
|
|
3871
4960
|
const cwd = agent.team.path;
|
|
3872
|
-
if (!
|
|
4961
|
+
if (!existsSync14(cwd)) mkdirSync7(cwd, { recursive: true });
|
|
3873
4962
|
const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills);
|
|
3874
|
-
const uploadsDir =
|
|
4963
|
+
const uploadsDir = join19(agent.agent.dir, "uploads");
|
|
3875
4964
|
const shellTools = restricted ? null : createSessionShellTools(cwd, env, {
|
|
3876
|
-
...
|
|
4965
|
+
...existsSync14(uploadsDir) ? { inputsDir: uploadsDir } : {},
|
|
3877
4966
|
skillMounts: promptSkills.map((skill) => ({
|
|
3878
4967
|
source: skill.hostDir,
|
|
3879
4968
|
target: skill.sandboxDir
|
|
3880
4969
|
})),
|
|
3881
4970
|
approvalHost: bashApprovalHost
|
|
3882
4971
|
});
|
|
3883
|
-
const sessionDir = restricted?.sessionDir ??
|
|
3884
|
-
|
|
4972
|
+
const sessionDir = restricted?.sessionDir ?? join19(paths.agentDir(agent.agent.id), "sessions");
|
|
4973
|
+
mkdirSync7(sessionDir, { recursive: true });
|
|
3885
4974
|
const existing = restricted ? null : findMostRecent(sessionDir);
|
|
3886
4975
|
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
3887
4976
|
const settingsManager = SettingsManager.inMemory({
|
|
@@ -3899,7 +4988,7 @@ async function createBazilionSession(opts) {
|
|
|
3899
4988
|
});
|
|
3900
4989
|
const resourceLoader = new DefaultResourceLoader({
|
|
3901
4990
|
cwd,
|
|
3902
|
-
agentDir:
|
|
4991
|
+
agentDir: join19(paths.home, "pi"),
|
|
3903
4992
|
settingsManager,
|
|
3904
4993
|
noExtensions: true,
|
|
3905
4994
|
noSkills: true,
|
|
@@ -3924,7 +5013,7 @@ async function createBazilionSession(opts) {
|
|
|
3924
5013
|
const allowedTools = [...shellTools?.hostToolNames ?? [], ...customTools.map((t) => t.name)];
|
|
3925
5014
|
const { session } = await createAgentSession({
|
|
3926
5015
|
cwd,
|
|
3927
|
-
agentDir:
|
|
5016
|
+
agentDir: join19(paths.home, "pi"),
|
|
3928
5017
|
model,
|
|
3929
5018
|
thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),
|
|
3930
5019
|
tools: allowedTools,
|
|
@@ -3951,6 +5040,258 @@ async function createBazilionSession(opts) {
|
|
|
3951
5040
|
}
|
|
3952
5041
|
};
|
|
3953
5042
|
}
|
|
5043
|
+
async function createProtectedBazilionSession(opts) {
|
|
5044
|
+
const { providerName, modelId } = splitModelString(opts.agent.model);
|
|
5045
|
+
if (providerName !== opts.runtime.providerName || modelId !== opts.runtime.modelId) {
|
|
5046
|
+
throw new Error("protected Agent model does not match the prepared provider runtime");
|
|
5047
|
+
}
|
|
5048
|
+
if (opts.agent.agent.dir !== opts.paths.agentDir || opts.agent.team.path !== opts.paths.teamDir) {
|
|
5049
|
+
throw new Error("protected session paths do not match the resolved Agent");
|
|
5050
|
+
}
|
|
5051
|
+
if (!existsSync14(opts.paths.teamDir)) {
|
|
5052
|
+
throw new Error("protected Team workspace is unavailable");
|
|
5053
|
+
}
|
|
5054
|
+
const modelRuntime = await createProtectedPiRuntime({
|
|
5055
|
+
providerName: opts.runtime.providerName,
|
|
5056
|
+
modelId: opts.runtime.modelId,
|
|
5057
|
+
apiKey: opts.runtime.apiKey,
|
|
5058
|
+
baseUrl: opts.runtime.baseUrl,
|
|
5059
|
+
credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch)
|
|
5060
|
+
});
|
|
5061
|
+
const model = resolvePiModel(
|
|
5062
|
+
modelRuntime,
|
|
5063
|
+
opts.runtime.providerName,
|
|
5064
|
+
opts.runtime.modelId,
|
|
5065
|
+
opts.runtime.baseUrl
|
|
5066
|
+
);
|
|
5067
|
+
const shellTools = createProtectedSessionShellTools(
|
|
5068
|
+
opts.paths.teamDir,
|
|
5069
|
+
opts.docker,
|
|
5070
|
+
opts.bashApprovalHost
|
|
5071
|
+
);
|
|
5072
|
+
mkdirSync7(opts.paths.sessionDir, { recursive: true });
|
|
5073
|
+
const existing = findMostRecent(opts.paths.sessionDir);
|
|
5074
|
+
const sessionManager = existing ? SessionManager.open(existing, opts.paths.sessionDir, opts.paths.teamDir) : SessionManager.create(opts.paths.teamDir, opts.paths.sessionDir);
|
|
5075
|
+
const settingsManager = createInMemorySettingsManager();
|
|
5076
|
+
const bazilionPrompt = redactJsonValue(
|
|
5077
|
+
buildSystemPrompt(opts.agent, {
|
|
5078
|
+
skills: opts.paths.skills,
|
|
5079
|
+
sandboxMode: "docker",
|
|
5080
|
+
homeDocuments: opts.paths.homeDocuments
|
|
5081
|
+
}),
|
|
5082
|
+
protectedRuntimeSecrets(opts.runtime)
|
|
5083
|
+
);
|
|
5084
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
5085
|
+
cwd: PROTECTED_MODEL_CWD,
|
|
5086
|
+
agentDir: opts.scratch.piAgentDir,
|
|
5087
|
+
settingsManager,
|
|
5088
|
+
noExtensions: true,
|
|
5089
|
+
noSkills: true,
|
|
5090
|
+
noPromptTemplates: true,
|
|
5091
|
+
noThemes: true,
|
|
5092
|
+
noContextFiles: true,
|
|
5093
|
+
systemPrompt: PROTECTED_BASE_SYSTEM_PROMPT,
|
|
5094
|
+
appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : void 0
|
|
5095
|
+
});
|
|
5096
|
+
await resourceLoader.reload();
|
|
5097
|
+
const bazilionTools = createProtectedBazilionCustomTools({
|
|
5098
|
+
agent: opts.agent,
|
|
5099
|
+
memory: opts.memory,
|
|
5100
|
+
messagingHost: opts.messagingHost,
|
|
5101
|
+
userMdHost: opts.userMdHost,
|
|
5102
|
+
fileSink: opts.fileSink
|
|
5103
|
+
});
|
|
5104
|
+
if (!shellTools.customBash) throw new Error("protected Docker bash tool is unavailable");
|
|
5105
|
+
const customTools = [...bazilionTools, shellTools.customBash];
|
|
5106
|
+
const { session } = await createAgentSession({
|
|
5107
|
+
cwd: PROTECTED_MODEL_CWD,
|
|
5108
|
+
agentDir: opts.scratch.piAgentDir,
|
|
5109
|
+
model,
|
|
5110
|
+
thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),
|
|
5111
|
+
tools: customTools.map((tool) => tool.name),
|
|
5112
|
+
customTools,
|
|
5113
|
+
sessionManager,
|
|
5114
|
+
settingsManager,
|
|
5115
|
+
modelRuntime,
|
|
5116
|
+
resourceLoader
|
|
5117
|
+
});
|
|
5118
|
+
installProtectedCredentialBoundary(
|
|
5119
|
+
session,
|
|
5120
|
+
opts.runtime.providerName,
|
|
5121
|
+
protectedRuntimeSecrets(opts.runtime),
|
|
5122
|
+
opts.refreshApiKey
|
|
5123
|
+
);
|
|
5124
|
+
return sessionHandle(session);
|
|
5125
|
+
}
|
|
5126
|
+
async function createRestrictedReviewSession(opts) {
|
|
5127
|
+
const modelRuntime = await createProtectedPiRuntime({
|
|
5128
|
+
providerName: opts.runtime.providerName,
|
|
5129
|
+
modelId: opts.runtime.modelId,
|
|
5130
|
+
apiKey: opts.runtime.apiKey,
|
|
5131
|
+
baseUrl: opts.runtime.baseUrl,
|
|
5132
|
+
credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch)
|
|
5133
|
+
});
|
|
5134
|
+
const model = resolvePiModel(
|
|
5135
|
+
modelRuntime,
|
|
5136
|
+
opts.runtime.providerName,
|
|
5137
|
+
opts.runtime.modelId,
|
|
5138
|
+
opts.runtime.baseUrl
|
|
5139
|
+
);
|
|
5140
|
+
const settingsManager = createInMemorySettingsManager();
|
|
5141
|
+
const sessionManager = SessionManager.create(
|
|
5142
|
+
opts.scratch.reviewCwd,
|
|
5143
|
+
opts.scratch.reviewSessionDir
|
|
5144
|
+
);
|
|
5145
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
5146
|
+
cwd: REVIEW_MODEL_CWD,
|
|
5147
|
+
agentDir: opts.scratch.piAgentDir,
|
|
5148
|
+
settingsManager,
|
|
5149
|
+
noExtensions: true,
|
|
5150
|
+
noSkills: true,
|
|
5151
|
+
noPromptTemplates: true,
|
|
5152
|
+
noThemes: true,
|
|
5153
|
+
noContextFiles: true,
|
|
5154
|
+
systemPrompt: opts.systemPrompt
|
|
5155
|
+
});
|
|
5156
|
+
await resourceLoader.reload();
|
|
5157
|
+
const { session } = await createAgentSession({
|
|
5158
|
+
cwd: REVIEW_MODEL_CWD,
|
|
5159
|
+
agentDir: opts.scratch.piAgentDir,
|
|
5160
|
+
model,
|
|
5161
|
+
thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),
|
|
5162
|
+
tools: opts.tools.map((tool) => tool.name),
|
|
5163
|
+
customTools: opts.tools,
|
|
5164
|
+
sessionManager,
|
|
5165
|
+
settingsManager,
|
|
5166
|
+
modelRuntime,
|
|
5167
|
+
resourceLoader
|
|
5168
|
+
});
|
|
5169
|
+
installProtectedCredentialBoundary(
|
|
5170
|
+
session,
|
|
5171
|
+
opts.runtime.providerName,
|
|
5172
|
+
protectedRuntimeSecrets(opts.runtime),
|
|
5173
|
+
opts.refreshApiKey
|
|
5174
|
+
);
|
|
5175
|
+
return sessionHandle(session);
|
|
5176
|
+
}
|
|
5177
|
+
function materializeProtectedCredentialFile(runtime, scratch) {
|
|
5178
|
+
const fields = [...runtime.credentialEnv ?? []];
|
|
5179
|
+
if (runtime.credentialFile) {
|
|
5180
|
+
const path = join19(scratch.tempDir, "provider-credential.json");
|
|
5181
|
+
writeFileSync9(path, runtime.credentialFile.content, {
|
|
5182
|
+
encoding: "utf8",
|
|
5183
|
+
mode: 384,
|
|
5184
|
+
flag: "wx"
|
|
5185
|
+
});
|
|
5186
|
+
fields.push({ name: runtime.credentialFile.envName, value: path });
|
|
5187
|
+
}
|
|
5188
|
+
return fields.length > 0 ? fields : void 0;
|
|
5189
|
+
}
|
|
5190
|
+
function createInMemorySettingsManager() {
|
|
5191
|
+
return SettingsManager.inMemory({
|
|
5192
|
+
compaction: { enabled: false },
|
|
5193
|
+
retry: {
|
|
5194
|
+
enabled: true,
|
|
5195
|
+
maxRetries: 2,
|
|
5196
|
+
baseDelayMs: 500,
|
|
5197
|
+
provider: { maxRetryDelayMs: 8e3 }
|
|
5198
|
+
}
|
|
5199
|
+
});
|
|
5200
|
+
}
|
|
5201
|
+
function installProtectedCredentialBoundary(session, providerName, initialSecrets, refreshApiKey) {
|
|
5202
|
+
const activeTokens = [...new Set(initialSecrets.filter(Boolean))];
|
|
5203
|
+
session.agent.state.messages = redactJsonValue(session.agent.state.messages, activeTokens);
|
|
5204
|
+
const originalStream = session.agent.streamFunction;
|
|
5205
|
+
session.agent.streamFunction = async (model, context, options) => {
|
|
5206
|
+
let upstream;
|
|
5207
|
+
try {
|
|
5208
|
+
upstream = await originalStream(model, context, options);
|
|
5209
|
+
} catch (error) {
|
|
5210
|
+
throw credentialSafeError(error, activeTokens);
|
|
5211
|
+
}
|
|
5212
|
+
const sanitized = createAssistantMessageEventStream();
|
|
5213
|
+
void (async () => {
|
|
5214
|
+
let latestPartial;
|
|
5215
|
+
try {
|
|
5216
|
+
for await (const event of upstream) {
|
|
5217
|
+
const safeEvent = redactJsonValue(event, activeTokens);
|
|
5218
|
+
if ("partial" in safeEvent) latestPartial = safeEvent.partial;
|
|
5219
|
+
sanitized.push(safeEvent);
|
|
5220
|
+
}
|
|
5221
|
+
} catch (error) {
|
|
5222
|
+
const safeError = credentialSafeError(error, activeTokens);
|
|
5223
|
+
sanitized.push({
|
|
5224
|
+
type: "error",
|
|
5225
|
+
reason: "error",
|
|
5226
|
+
error: credentialSafeAssistantError(model, latestPartial, safeError, activeTokens)
|
|
5227
|
+
});
|
|
5228
|
+
} finally {
|
|
5229
|
+
sanitized.end();
|
|
5230
|
+
}
|
|
5231
|
+
})();
|
|
5232
|
+
return sanitized;
|
|
5233
|
+
};
|
|
5234
|
+
const originalAfterToolCall = session.agent.afterToolCall;
|
|
5235
|
+
session.agent.afterToolCall = async (context, signal) => {
|
|
5236
|
+
let prior;
|
|
5237
|
+
try {
|
|
5238
|
+
prior = await originalAfterToolCall?.(context, signal);
|
|
5239
|
+
} catch (error) {
|
|
5240
|
+
throw credentialSafeError(error, activeTokens);
|
|
5241
|
+
}
|
|
5242
|
+
return {
|
|
5243
|
+
...prior,
|
|
5244
|
+
content: redactJsonValue(prior?.content ?? context.result.content, activeTokens),
|
|
5245
|
+
details: redactJsonValue(prior?.details ?? context.result.details, activeTokens)
|
|
5246
|
+
};
|
|
5247
|
+
};
|
|
5248
|
+
session.agent.getApiKey = async (requestedProvider) => {
|
|
5249
|
+
if (requestedProvider !== providerName) return void 0;
|
|
5250
|
+
try {
|
|
5251
|
+
const token = await refreshApiKey(providerName);
|
|
5252
|
+
if (token && !activeTokens.includes(token)) activeTokens.push(token);
|
|
5253
|
+
return token || void 0;
|
|
5254
|
+
} catch {
|
|
5255
|
+
return void 0;
|
|
5256
|
+
}
|
|
5257
|
+
};
|
|
5258
|
+
}
|
|
5259
|
+
function credentialSafeError(error, activeTokens) {
|
|
5260
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5261
|
+
return new Error(redactJsonValue(message, activeTokens));
|
|
5262
|
+
}
|
|
5263
|
+
function credentialSafeAssistantError(model, latestPartial, error, activeTokens) {
|
|
5264
|
+
const safePartial = latestPartial ? redactJsonValue(latestPartial, activeTokens) : {
|
|
5265
|
+
role: "assistant",
|
|
5266
|
+
content: [],
|
|
5267
|
+
api: model.api,
|
|
5268
|
+
provider: model.provider,
|
|
5269
|
+
model: model.id,
|
|
5270
|
+
usage: {
|
|
5271
|
+
input: 0,
|
|
5272
|
+
output: 0,
|
|
5273
|
+
cacheRead: 0,
|
|
5274
|
+
cacheWrite: 0,
|
|
5275
|
+
totalTokens: 0,
|
|
5276
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
5277
|
+
},
|
|
5278
|
+
timestamp: Date.now()
|
|
5279
|
+
};
|
|
5280
|
+
return {
|
|
5281
|
+
...safePartial,
|
|
5282
|
+
stopReason: "error",
|
|
5283
|
+
errorMessage: error.message,
|
|
5284
|
+
timestamp: Date.now()
|
|
5285
|
+
};
|
|
5286
|
+
}
|
|
5287
|
+
function sessionHandle(session) {
|
|
5288
|
+
return {
|
|
5289
|
+
session,
|
|
5290
|
+
dispose() {
|
|
5291
|
+
session.dispose();
|
|
5292
|
+
}
|
|
5293
|
+
};
|
|
5294
|
+
}
|
|
3954
5295
|
function splitModelString(s) {
|
|
3955
5296
|
const idx = s.indexOf(":");
|
|
3956
5297
|
if (idx === -1) {
|
|
@@ -3972,11 +5313,11 @@ function toPiThinkingLevel(level) {
|
|
|
3972
5313
|
}
|
|
3973
5314
|
}
|
|
3974
5315
|
function findMostRecent(sessionDir) {
|
|
3975
|
-
if (!
|
|
5316
|
+
if (!existsSync14(sessionDir)) return null;
|
|
3976
5317
|
let newest = null;
|
|
3977
5318
|
for (const entry of readdirSync8(sessionDir)) {
|
|
3978
5319
|
if (!entry.endsWith(".jsonl")) continue;
|
|
3979
|
-
const path =
|
|
5320
|
+
const path = join19(sessionDir, entry);
|
|
3980
5321
|
try {
|
|
3981
5322
|
const s = statSync5(path);
|
|
3982
5323
|
if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
|
|
@@ -3989,7 +5330,7 @@ function findMostRecent(sessionDir) {
|
|
|
3989
5330
|
// ../daemon/src/runtime/worker/api-key-refresh.ts
|
|
3990
5331
|
function createIpcApiKeyRefresher(call, context) {
|
|
3991
5332
|
return async (providerName) => {
|
|
3992
|
-
|
|
5333
|
+
assertSelectedProvider(providerName, context.providerName);
|
|
3993
5334
|
return call("refreshApiKey", {
|
|
3994
5335
|
providerName,
|
|
3995
5336
|
agentId: context.agentId,
|
|
@@ -3997,14 +5338,14 @@ function createIpcApiKeyRefresher(call, context) {
|
|
|
3997
5338
|
});
|
|
3998
5339
|
};
|
|
3999
5340
|
}
|
|
4000
|
-
function
|
|
4001
|
-
if (expected
|
|
5341
|
+
function assertSelectedProvider(requested, expected) {
|
|
5342
|
+
if (!expected || requested !== expected) {
|
|
4002
5343
|
throw new Error("unexpected API key refresh provider");
|
|
4003
5344
|
}
|
|
4004
5345
|
}
|
|
4005
5346
|
|
|
4006
5347
|
// ../daemon/src/runtime/worker/ipc-client.ts
|
|
4007
|
-
import { randomUUID as
|
|
5348
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
4008
5349
|
function createIpcClient(channel) {
|
|
4009
5350
|
const pending = /* @__PURE__ */ new Map();
|
|
4010
5351
|
let disconnected = false;
|
|
@@ -4031,10 +5372,10 @@ function createIpcClient(channel) {
|
|
|
4031
5372
|
if (!send) {
|
|
4032
5373
|
return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
|
|
4033
5374
|
}
|
|
4034
|
-
const id =
|
|
5375
|
+
const id = randomUUID19();
|
|
4035
5376
|
const message = { type: "rpc", id, method, args };
|
|
4036
|
-
return new Promise((
|
|
4037
|
-
pending.set(id, { resolve: (value) =>
|
|
5377
|
+
return new Promise((resolve6, reject) => {
|
|
5378
|
+
pending.set(id, { resolve: (value) => resolve6(value), reject });
|
|
4038
5379
|
try {
|
|
4039
5380
|
send(message, (error) => {
|
|
4040
5381
|
if (!error) return;
|
|
@@ -4055,29 +5396,40 @@ function isIpcReply(message) {
|
|
|
4055
5396
|
}
|
|
4056
5397
|
|
|
4057
5398
|
// ../daemon/src/runtime/worker/entry.ts
|
|
5399
|
+
var activeAccessTokens = [];
|
|
4058
5400
|
function emit(frame) {
|
|
4059
|
-
process.stdout.write(`${JSON.stringify(frame)}
|
|
5401
|
+
process.stdout.write(`${JSON.stringify(redactJsonValue(frame, activeAccessTokens))}
|
|
4060
5402
|
`);
|
|
4061
5403
|
}
|
|
4062
5404
|
async function readInput() {
|
|
4063
5405
|
const chunks = [];
|
|
4064
|
-
for await (const
|
|
5406
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4065
5407
|
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
4066
5408
|
if (!raw) throw new Error("worker: empty stdin input");
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
);
|
|
4072
|
-
}
|
|
4073
|
-
if (parsed.mode === "review" && !parsed.review?.reviewId) {
|
|
4074
|
-
throw new Error("worker: review mode requires review metadata");
|
|
4075
|
-
}
|
|
4076
|
-
const providerName = parsed.agent.model.split(":", 1)[0] ?? "";
|
|
4077
|
-
if (parsed.apiKeyRefreshEnabled && (providerName !== "openai-codex" || typeof parsed.apiKey !== "string")) {
|
|
4078
|
-
throw new Error("worker: API key refresh requires an openai-codex turn with an initial token");
|
|
5409
|
+
let parsed;
|
|
5410
|
+
try {
|
|
5411
|
+
parsed = JSON.parse(raw);
|
|
5412
|
+
} catch {
|
|
5413
|
+
throw new Error("worker: stdin is not valid JSON");
|
|
4079
5414
|
}
|
|
4080
|
-
return parsed;
|
|
5415
|
+
return parseWorkerInput(parsed);
|
|
5416
|
+
}
|
|
5417
|
+
function createWorkerIpcCall() {
|
|
5418
|
+
return createIpcClient({
|
|
5419
|
+
send: process.send ? (ipcMessage, done) => {
|
|
5420
|
+
process.send?.(ipcMessage, void 0, void 0, done);
|
|
5421
|
+
} : void 0,
|
|
5422
|
+
onMessage: (listener) => process.on("message", listener),
|
|
5423
|
+
onDisconnect: (listener) => process.on("disconnect", listener)
|
|
5424
|
+
});
|
|
5425
|
+
}
|
|
5426
|
+
function createTrackedIpcApiKeyRefresher(call, context) {
|
|
5427
|
+
const refresh = createIpcApiKeyRefresher(call, context);
|
|
5428
|
+
return async (providerName) => {
|
|
5429
|
+
const token = await refresh(providerName);
|
|
5430
|
+
if (token && !activeAccessTokens.includes(token)) activeAccessTokens.push(token);
|
|
5431
|
+
return token;
|
|
5432
|
+
};
|
|
4081
5433
|
}
|
|
4082
5434
|
function createIpcMessagingHost(call) {
|
|
4083
5435
|
return {
|
|
@@ -4111,81 +5463,27 @@ function createIpcBashApprovalHost(call, input) {
|
|
|
4111
5463
|
return {
|
|
4112
5464
|
async requestApproval(request) {
|
|
4113
5465
|
const result = await call("bashApproval", {
|
|
4114
|
-
id:
|
|
5466
|
+
id: randomUUID20(),
|
|
4115
5467
|
turnId: input.turnId,
|
|
4116
5468
|
toolCallId: request.toolCallId,
|
|
4117
|
-
agentId: input.
|
|
4118
|
-
teamId: input.
|
|
5469
|
+
agentId: input.agentId,
|
|
5470
|
+
teamId: input.teamId,
|
|
4119
5471
|
command: request.command,
|
|
4120
5472
|
risks: [...request.risks],
|
|
4121
|
-
mode: input.
|
|
5473
|
+
mode: input.mode
|
|
4122
5474
|
});
|
|
4123
5475
|
return result.decision === "allow" ? "approved" : "denied";
|
|
4124
5476
|
}
|
|
4125
5477
|
};
|
|
4126
5478
|
}
|
|
4127
|
-
|
|
4128
|
-
let aborted = false;
|
|
4129
|
-
let abortSession = null;
|
|
4130
|
-
const onSignal = () => {
|
|
4131
|
-
aborted = true;
|
|
4132
|
-
if (abortSession) {
|
|
4133
|
-
abortSession();
|
|
4134
|
-
} else {
|
|
4135
|
-
try {
|
|
4136
|
-
emit({ kind: "event", event: { type: "error", error: "cancelled" } });
|
|
4137
|
-
} catch {
|
|
4138
|
-
}
|
|
4139
|
-
process.exit(0);
|
|
4140
|
-
}
|
|
4141
|
-
};
|
|
4142
|
-
process.on("SIGTERM", onSignal);
|
|
4143
|
-
process.on("SIGINT", onSignal);
|
|
4144
|
-
const {
|
|
4145
|
-
agent,
|
|
4146
|
-
message,
|
|
4147
|
-
enabledProviders,
|
|
4148
|
-
apiKey,
|
|
4149
|
-
apiKeyRefreshEnabled,
|
|
4150
|
-
browserEnabled,
|
|
4151
|
-
mcpTools,
|
|
4152
|
-
images,
|
|
4153
|
-
turnId,
|
|
4154
|
-
bashApprovalMode,
|
|
4155
|
-
mode = "chat",
|
|
4156
|
-
review
|
|
4157
|
-
} = await readInput();
|
|
4158
|
-
const paths = resolvePaths();
|
|
4159
|
-
const memory = qmdBackend(join19(agent.team.path, "memory"));
|
|
4160
|
-
await memory.init();
|
|
4161
|
-
const ipcCall = createIpcClient({
|
|
4162
|
-
send: process.send ? (ipcMessage, done) => {
|
|
4163
|
-
process.send?.(ipcMessage, void 0, void 0, done);
|
|
4164
|
-
} : void 0,
|
|
4165
|
-
onMessage: (listener) => process.on("message", listener),
|
|
4166
|
-
onDisconnect: (listener) => process.on("disconnect", listener)
|
|
4167
|
-
});
|
|
4168
|
-
const refreshApiKey = apiKeyRefreshEnabled ? createIpcApiKeyRefresher(ipcCall, {
|
|
4169
|
-
providerName: agent.model.split(":", 1)[0] ?? "",
|
|
4170
|
-
agentId: agent.agent.id,
|
|
4171
|
-
turnId
|
|
4172
|
-
}) : void 0;
|
|
4173
|
-
const messagingHost = createIpcMessagingHost(ipcCall);
|
|
4174
|
-
const userMdHost = createIpcUserMdHost(ipcCall);
|
|
4175
|
-
const browserHost = browserEnabled ? createIpcBrowserHost(ipcCall) : void 0;
|
|
4176
|
-
const mcpHost = mcpTools && mcpTools.length > 0 ? createIpcMcpHost(ipcCall) : void 0;
|
|
4177
|
-
const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {
|
|
4178
|
-
agent,
|
|
4179
|
-
turnId,
|
|
4180
|
-
bashApprovalMode
|
|
4181
|
-
});
|
|
4182
|
-
const reviewSessionDir = mode === "review" ? mkdtempSync3(join19(paths.logsDir, "review-session-")) : null;
|
|
5479
|
+
function createReviewState(evidenceItems) {
|
|
4183
5480
|
const proposals = [];
|
|
4184
5481
|
let proposalBatchSubmitted = false;
|
|
5482
|
+
let reviewValidationFailures = 0;
|
|
4185
5483
|
const allowedEvidence = new Set(
|
|
4186
|
-
|
|
5484
|
+
evidenceItems.map((item) => `${item.sessionId}:${item.entryOrdinal}`)
|
|
4187
5485
|
);
|
|
4188
|
-
const
|
|
5486
|
+
const tools = [
|
|
4189
5487
|
{
|
|
4190
5488
|
name: "propose_lesson",
|
|
4191
5489
|
label: "propose_lesson",
|
|
@@ -4205,8 +5503,8 @@ async function main() {
|
|
|
4205
5503
|
}),
|
|
4206
5504
|
async execute(_toolCallId, params) {
|
|
4207
5505
|
if (proposalBatchSubmitted) throw new Error("propose_lesson may be called only once");
|
|
4208
|
-
proposalBatchSubmitted = true;
|
|
4209
5506
|
const input = params;
|
|
5507
|
+
const validated = [];
|
|
4210
5508
|
for (const candidate of input.proposals) {
|
|
4211
5509
|
const text = candidate.text.trim();
|
|
4212
5510
|
if (!text || text.length > 500) throw new Error("proposal text must be 1-500 characters");
|
|
@@ -4215,8 +5513,10 @@ async function main() {
|
|
|
4215
5513
|
throw new Error("proposal cites evidence outside the supplied digest");
|
|
4216
5514
|
}
|
|
4217
5515
|
}
|
|
4218
|
-
|
|
5516
|
+
validated.push({ ...candidate, text });
|
|
4219
5517
|
}
|
|
5518
|
+
proposalBatchSubmitted = true;
|
|
5519
|
+
proposals.push(...validated);
|
|
4220
5520
|
return {
|
|
4221
5521
|
content: [{ type: "text", text: `accepted ${proposals.length} proposals` }],
|
|
4222
5522
|
details: {},
|
|
@@ -4225,86 +5525,168 @@ async function main() {
|
|
|
4225
5525
|
}
|
|
4226
5526
|
}
|
|
4227
5527
|
];
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
5528
|
+
return {
|
|
5529
|
+
proposals,
|
|
5530
|
+
submitted: () => proposalBatchSubmitted,
|
|
5531
|
+
validationFailures: () => reviewValidationFailures,
|
|
5532
|
+
recordValidationFailure: () => {
|
|
5533
|
+
reviewValidationFailures += 1;
|
|
5534
|
+
},
|
|
5535
|
+
tools
|
|
5536
|
+
};
|
|
5537
|
+
}
|
|
5538
|
+
async function createSessionForInput(input, ipcCall) {
|
|
5539
|
+
if (input.kind === "configured_operator_http") {
|
|
5540
|
+
const paths = resolvePaths();
|
|
5541
|
+
const memory2 = qmdBackend(`${input.agent.team.path}/memory`);
|
|
5542
|
+
await memory2.init();
|
|
5543
|
+
const messagingHost2 = createIpcMessagingHost(ipcCall);
|
|
5544
|
+
const userMdHost2 = createIpcUserMdHost(ipcCall);
|
|
5545
|
+
const browserHost = input.browserEnabled ? createIpcBrowserHost(ipcCall) : void 0;
|
|
5546
|
+
const mcpHost = input.mcpTools?.length ? createIpcMcpHost(ipcCall) : void 0;
|
|
5547
|
+
const bashApprovalHost2 = createIpcBashApprovalHost(ipcCall, {
|
|
5548
|
+
agentId: input.agent.agent.id,
|
|
5549
|
+
teamId: input.agent.team.id,
|
|
5550
|
+
turnId: input.turnId,
|
|
5551
|
+
mode: input.bashApprovalMode
|
|
5552
|
+
});
|
|
5553
|
+
const refreshApiKey2 = input.apiKeyRefreshEnabled ? createTrackedIpcApiKeyRefresher(ipcCall, {
|
|
5554
|
+
providerName: input.agent.model.split(":", 1)[0] ?? "",
|
|
5555
|
+
agentId: input.agent.agent.id,
|
|
5556
|
+
turnId: input.turnId
|
|
5557
|
+
}) : void 0;
|
|
5558
|
+
const handle2 = await createBazilionSession({
|
|
5559
|
+
agent: input.agent,
|
|
5560
|
+
paths,
|
|
5561
|
+
env: process.env,
|
|
5562
|
+
memory: memory2,
|
|
5563
|
+
enabledProviders: new Set(input.enabledProviders),
|
|
5564
|
+
messagingHost: messagingHost2,
|
|
5565
|
+
userMdHost: userMdHost2,
|
|
5566
|
+
apiKey: input.apiKey,
|
|
5567
|
+
refreshApiKey: refreshApiKey2,
|
|
5568
|
+
browserHost,
|
|
5569
|
+
mcpHost,
|
|
5570
|
+
mcpTools: input.mcpTools,
|
|
5571
|
+
bashApprovalHost: bashApprovalHost2,
|
|
5572
|
+
fileSink: (file) => emit({ kind: "event", event: { type: "file", ...file } })
|
|
5573
|
+
});
|
|
5574
|
+
return { handle: handle2 };
|
|
5575
|
+
}
|
|
5576
|
+
const refreshApiKey = createTrackedIpcApiKeyRefresher(ipcCall, {
|
|
5577
|
+
providerName: input.runtime.providerName,
|
|
5578
|
+
agentId: input.kind === "protected" ? input.agent.agent.id : input.agentId,
|
|
5579
|
+
turnId: input.turnId
|
|
5580
|
+
});
|
|
5581
|
+
if (input.kind === "restricted_review") {
|
|
5582
|
+
const reviewState = createReviewState(input.review.evidence);
|
|
5583
|
+
const handle2 = await createRestrictedReviewSession({
|
|
5584
|
+
runtime: input.runtime,
|
|
5585
|
+
scratch: input.scratch,
|
|
5586
|
+
systemPrompt: REVIEW_SYSTEM_PROMPT,
|
|
5587
|
+
tools: reviewState.tools,
|
|
5588
|
+
refreshApiKey
|
|
5589
|
+
});
|
|
5590
|
+
return { handle: handle2, reviewState };
|
|
5591
|
+
}
|
|
5592
|
+
const memory = qmdBackend(input.paths.memoryDir);
|
|
5593
|
+
await memory.init();
|
|
5594
|
+
const messagingHost = createIpcMessagingHost(ipcCall);
|
|
5595
|
+
const userMdHost = createIpcUserMdHost(ipcCall);
|
|
5596
|
+
const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {
|
|
5597
|
+
agentId: input.agent.agent.id,
|
|
5598
|
+
teamId: input.agent.team.id,
|
|
5599
|
+
turnId: input.turnId,
|
|
5600
|
+
mode: "auto_deny"
|
|
5601
|
+
});
|
|
5602
|
+
const handle = await createProtectedBazilionSession({
|
|
5603
|
+
agent: input.agent,
|
|
5604
|
+
runtime: input.runtime,
|
|
5605
|
+
paths: input.paths,
|
|
5606
|
+
scratch: input.scratch,
|
|
5607
|
+
docker: input.docker,
|
|
4232
5608
|
memory,
|
|
4233
|
-
enabledProviders: new Set(enabledProviders),
|
|
4234
5609
|
messagingHost,
|
|
4235
5610
|
userMdHost,
|
|
4236
|
-
apiKey,
|
|
4237
|
-
refreshApiKey,
|
|
4238
|
-
browserHost,
|
|
4239
|
-
mcpHost,
|
|
4240
|
-
mcpTools,
|
|
4241
5611
|
bashApprovalHost,
|
|
4242
|
-
|
|
4243
|
-
fileSink: (
|
|
4244
|
-
...mode === "review" && reviewSessionDir ? {
|
|
4245
|
-
restricted: {
|
|
4246
|
-
systemPrompt: REVIEW_SYSTEM_PROMPT,
|
|
4247
|
-
tools: reviewTools,
|
|
4248
|
-
sessionDir: reviewSessionDir,
|
|
4249
|
-
reasoningLevel: agent.agent.reviewReasoningLevel
|
|
4250
|
-
}
|
|
4251
|
-
} : {}
|
|
5612
|
+
refreshApiKey,
|
|
5613
|
+
fileSink: (file) => emit({ kind: "event", event: { type: "file", ...file } })
|
|
4252
5614
|
});
|
|
4253
|
-
|
|
4254
|
-
|
|
5615
|
+
return { handle };
|
|
5616
|
+
}
|
|
5617
|
+
async function main() {
|
|
5618
|
+
let aborted = false;
|
|
5619
|
+
let session = null;
|
|
5620
|
+
const onSignal = () => {
|
|
5621
|
+
aborted = true;
|
|
5622
|
+
if (session) void session.abort();
|
|
5623
|
+
else {
|
|
5624
|
+
try {
|
|
5625
|
+
emit({ kind: "event", event: { type: "error", error: "cancelled" } });
|
|
5626
|
+
} catch {
|
|
5627
|
+
}
|
|
5628
|
+
process.exit(0);
|
|
5629
|
+
}
|
|
4255
5630
|
};
|
|
4256
|
-
|
|
5631
|
+
process.on("SIGTERM", onSignal);
|
|
5632
|
+
process.on("SIGINT", onSignal);
|
|
5633
|
+
const input = await readInput();
|
|
5634
|
+
if (input.kind !== "configured_operator_http") {
|
|
5635
|
+
validateMinimalWorkerProcessEnv(process.env, input.scratch);
|
|
5636
|
+
}
|
|
5637
|
+
const initialAccessTokens = input.kind === "configured_operator_http" ? input.apiKey ? [input.apiKey] : [] : protectedRuntimeSecrets(input.runtime);
|
|
5638
|
+
activeAccessTokens.push(...initialAccessTokens.filter(Boolean));
|
|
5639
|
+
const ipcCall = createWorkerIpcCall();
|
|
5640
|
+
const { handle, reviewState } = await createSessionForInput(input, ipcCall);
|
|
5641
|
+
session = handle.session;
|
|
4257
5642
|
const unsubscribe = session.subscribe((piEvent) => {
|
|
4258
|
-
if (
|
|
4259
|
-
|
|
4260
|
-
if (
|
|
4261
|
-
}
|
|
4262
|
-
for (const ev of translatePiEvent(piEvent)) {
|
|
4263
|
-
emit({ kind: "event", event: ev });
|
|
5643
|
+
if (reviewState && piEvent.type === "tool_execution_end" && piEvent.toolName === "propose_lesson" && piEvent.isError) {
|
|
5644
|
+
reviewState.recordValidationFailure();
|
|
5645
|
+
if (reviewState.validationFailures() >= 2) void session?.abort();
|
|
4264
5646
|
}
|
|
5647
|
+
for (const event of translatePiEvent(piEvent)) emit({ kind: "event", event });
|
|
4265
5648
|
});
|
|
4266
5649
|
try {
|
|
4267
|
-
const
|
|
5650
|
+
const promptMessage = redactJsonValue(input.message, activeAccessTokens);
|
|
5651
|
+
const sanitizedImages = input.kind === "restricted_review" ? [] : redactJsonValue(input.images ?? [], activeAccessTokens);
|
|
5652
|
+
const promptImages = input.kind === "restricted_review" ? [] : sanitizedImages.map((image) => ({
|
|
4268
5653
|
type: "image",
|
|
4269
|
-
data:
|
|
4270
|
-
mimeType:
|
|
5654
|
+
data: image.data,
|
|
5655
|
+
mimeType: image.mimeType
|
|
4271
5656
|
}));
|
|
4272
|
-
await session.prompt(
|
|
5657
|
+
await session.prompt(
|
|
5658
|
+
promptMessage,
|
|
5659
|
+
promptImages.length > 0 ? { images: promptImages } : void 0
|
|
5660
|
+
);
|
|
4273
5661
|
await session.agent.waitForIdle();
|
|
4274
|
-
if (
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
throw new Error("review output did not submit a proposal batch");
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
emit({
|
|
4283
|
-
kind: "done",
|
|
4284
|
-
messages: piMessagesToProviderView(session.agent.state.messages)
|
|
4285
|
-
});
|
|
4286
|
-
}
|
|
4287
|
-
} catch (err) {
|
|
4288
|
-
if (!aborted) {
|
|
4289
|
-
emit({ kind: "event", event: { type: "error", error: err.message } });
|
|
5662
|
+
if (reviewState) {
|
|
5663
|
+
if (reviewState.validationFailures() >= 2) {
|
|
5664
|
+
throw new Error("review output remained invalid after one schema-repair attempt");
|
|
5665
|
+
}
|
|
5666
|
+
if (!reviewState.submitted()) throw new Error("review output did not submit a proposal batch");
|
|
5667
|
+
emit({ kind: "review_result", proposals: reviewState.proposals });
|
|
5668
|
+
} else {
|
|
5669
|
+
emit({ kind: "done", messages: piMessagesToProviderView(session.agent.state.messages) });
|
|
4290
5670
|
}
|
|
4291
|
-
|
|
5671
|
+
} catch (error) {
|
|
5672
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5673
|
+
if (!aborted) emit({ kind: "event", event: { type: "error", error: message } });
|
|
5674
|
+
emit({ kind: "fatal", error: message });
|
|
4292
5675
|
process.exitCode = 1;
|
|
4293
5676
|
} finally {
|
|
4294
5677
|
unsubscribe();
|
|
4295
5678
|
process.off("SIGTERM", onSignal);
|
|
4296
5679
|
process.off("SIGINT", onSignal);
|
|
4297
|
-
dispose();
|
|
4298
|
-
if (reviewSessionDir) rmSync12(reviewSessionDir, { recursive: true, force: true });
|
|
5680
|
+
handle.dispose();
|
|
4299
5681
|
try {
|
|
4300
5682
|
process.disconnect?.();
|
|
4301
5683
|
} catch {
|
|
4302
5684
|
}
|
|
4303
5685
|
}
|
|
4304
5686
|
}
|
|
4305
|
-
main().catch((
|
|
5687
|
+
main().catch((error) => {
|
|
4306
5688
|
try {
|
|
4307
|
-
emit({ kind: "fatal", error:
|
|
5689
|
+
emit({ kind: "fatal", error: error instanceof Error ? error.message : String(error) });
|
|
4308
5690
|
} catch {
|
|
4309
5691
|
}
|
|
4310
5692
|
process.exit(1);
|