bazilion 0.12.1 → 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 +3 -3
- package/dist/cli.js +811 -250
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +4778 -1461
- package/dist/daemon.js.map +1 -1
- package/dist/migrations/0001_init.sql +21 -0
- package/dist/web/client/assets/{ProductionTeamPolicyEditor-CSL3W7XM.js → ProductionTeamPolicyEditor-DdpYInwV.js} +1 -1
- package/dist/web/client/assets/{ProfileCommunicationEditor-CwU0QkmE.js → ProfileCommunicationEditor-BcSf2Hao.js} +1 -1
- package/dist/web/client/assets/{TeamTabs-C1Hakkff.js → TeamTabs-Bk89JfN7.js} +1 -1
- package/dist/web/client/assets/{TemplatesTabs-klEydJ8-.js → TemplatesTabs-CyDVzrOl.js} +1 -1
- package/dist/web/client/assets/{_id-CnVq-rED.js → _id-CAJTTnee.js} +1 -1
- package/dist/web/client/assets/{_id-wkjjWLz0.js → _id-CN6jPaBu.js} +1 -1
- package/dist/web/client/assets/{_id-BOC7ioJu.js → _id-DGwJqqDc.js} +1 -1
- package/dist/web/client/assets/{_id-e4u7dmBb.js → _id-DWTMH7Eb.js} +1 -1
- package/dist/web/client/assets/{activity-uqhwzmiy.js → activity-DK_M0-bn.js} +1 -1
- package/dist/web/client/assets/{agents-B2usE-L_.js → agents-B-i5pASm.js} +1 -1
- package/dist/web/client/assets/{agents-FaQtkAWs.js → agents-n8U7qNxw.js} +1 -1
- package/dist/web/client/assets/{approvals-DqLE6ttw.js → approvals-CmVxCy0Z.js} +1 -1
- package/dist/web/client/assets/{attention-CkGozYnJ.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-ZWqx3uiC.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-BoIvl9ZF.js → inbox-D2NypLee.js} +1 -1
- package/dist/web/client/assets/{index-nb2MV8X1.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-6_oYn1l7.js → learning-zYDCfVxT.js} +1 -1
- package/dist/web/client/assets/{login-DM1ytxQL.js → login-B1Un51Zx.js} +1 -1
- package/dist/web/client/assets/{mcp-CetZbyUh.js → mcp-DSVmJBdv.js} +1 -1
- package/dist/web/client/assets/{members-DdqWtwZd.js → members-Pl7hXJcN.js} +1 -1
- package/dist/web/client/assets/{memory-FNrxFHmC.js → memory-Co3BEaAq.js} +1 -1
- package/dist/web/client/assets/plus-Dor-_wmL.js +1 -0
- package/dist/web/client/assets/{policy-BKPejYRI.js → policy-CHM95pMU.js} +1 -1
- package/dist/web/client/assets/{routes-CH1ZTTo-.js → routes-D29qjwG0.js} +1 -1
- package/dist/web/client/assets/{services-zk9SqN06.js → services-CCQ_6YWQ.js} +1 -1
- package/dist/web/client/assets/{skills-tXDpomHW.js → skills-3xAIvzvP.js} +1 -1
- package/dist/web/client/assets/styles-D6RPN7RK.css +2 -0
- package/dist/web/client/assets/{teams-BZYjekBN.js → teams-Dm_huGxn.js} +1 -1
- package/dist/web/client/assets/{teams-D1c8zlxs.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-D8R-5guA.js → triggers-DlQ2KF1h.js} +1 -1
- package/dist/web/client/assets/{welcome-CuK0shDJ.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-fLsYERQ-.js → _tanstack-start-manifest_v-COkJjXt_.js} +50 -49
- 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-D2LAsud2.js → config-DGzM28F_.js} +211 -27
- package/dist/web/server/assets/{config-TeIoyYYc.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-CQuj7553.js → router-D-6TzLRZ.js} +168 -65
- 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/assets/{welcome-IYHGurWx.js → welcome-DDB11L5j.js} +2 -2
- 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 +1982 -537
- package/dist/worker.js.map +1 -1
- package/package.json +7 -6
- package/dist/web/client/assets/chevron-down-CJk0zGNT.js +0 -1
- package/dist/web/client/assets/config-xrXoDEWM.js +0 -3
- package/dist/web/client/assets/plus-C3GroITC.js +0 -1
- package/dist/web/client/assets/styles-DMltY3su.css +0 -2
- package/dist/web/client/assets/telegram-DCe1CWMt.js +0 -1
- package/dist/web/client/assets/tokens-DAPjRS0N.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",
|
|
@@ -279,6 +290,27 @@ var SERVICES = [
|
|
|
279
290
|
hint: "MiniMax M2/M3 family via the China endpoint",
|
|
280
291
|
fields: [{ envVar: "MINIMAX_CN_API_KEY", kind: "secret", label: "API key" }]
|
|
281
292
|
},
|
|
293
|
+
{
|
|
294
|
+
id: "qwen-token-plan",
|
|
295
|
+
displayName: "Qwen Token Plan",
|
|
296
|
+
category: "provider",
|
|
297
|
+
hint: "Coding-model subscription endpoint in Singapore",
|
|
298
|
+
fields: [{ envVar: "QWEN_TOKEN_PLAN_API_KEY", kind: "secret", label: "API key" }]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: "qwen-token-plan-cn",
|
|
302
|
+
displayName: "Qwen Token Plan (CN)",
|
|
303
|
+
category: "provider",
|
|
304
|
+
hint: "Coding-model subscription endpoint in China",
|
|
305
|
+
fields: [{ envVar: "QWEN_TOKEN_PLAN_CN_API_KEY", kind: "secret", label: "API key" }]
|
|
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
|
+
},
|
|
282
314
|
{
|
|
283
315
|
id: "xiaomi",
|
|
284
316
|
displayName: "Xiaomi MiMo",
|
|
@@ -657,14 +689,25 @@ import { randomUUID as randomUUID8 } from "crypto";
|
|
|
657
689
|
// ../daemon/src/core/repos/secrets.ts
|
|
658
690
|
import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
|
|
659
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
|
+
|
|
660
696
|
// ../daemon/src/core/repos/triggerDispatches.ts
|
|
661
697
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
662
698
|
|
|
663
699
|
// ../daemon/src/core/repos/triggers.ts
|
|
664
700
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
665
701
|
|
|
702
|
+
// ../daemon/src/core/repos/webSessions.ts
|
|
703
|
+
import { createHash as createHash4, randomBytes as randomBytes4, randomUUID as randomUUID12 } from "crypto";
|
|
704
|
+
|
|
666
705
|
// ../daemon/src/core/repos/webTokens.ts
|
|
667
|
-
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;
|
|
668
711
|
|
|
669
712
|
// ../daemon/src/core/secrets.ts
|
|
670
713
|
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
@@ -704,26 +747,22 @@ function parseSkillContent(raw) {
|
|
|
704
747
|
}
|
|
705
748
|
return { frontmatter: fmObj, body, raw };
|
|
706
749
|
}
|
|
707
|
-
function parseSkillFile(path) {
|
|
708
|
-
const raw = readFileSync7(path, "utf8");
|
|
709
|
-
return parseSkillContent(raw);
|
|
710
|
-
}
|
|
711
750
|
|
|
712
751
|
// ../daemon/src/core/team/delete.ts
|
|
713
|
-
import { randomUUID as
|
|
752
|
+
import { randomUUID as randomUUID13 } from "crypto";
|
|
714
753
|
import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
|
|
715
754
|
|
|
716
755
|
// ../daemon/src/core/team-policy/adopt.ts
|
|
717
|
-
import { randomUUID as
|
|
756
|
+
import { randomUUID as randomUUID14 } from "crypto";
|
|
718
757
|
|
|
719
758
|
// ../daemon/src/core/team-policy/authorization.ts
|
|
720
|
-
import { createHash as
|
|
759
|
+
import { createHash as createHash5, randomUUID as randomUUID15 } from "crypto";
|
|
721
760
|
|
|
722
761
|
// ../daemon/src/core/team-policy/source.ts
|
|
723
|
-
import { randomUUID as
|
|
762
|
+
import { randomUUID as randomUUID16 } from "crypto";
|
|
724
763
|
|
|
725
764
|
// ../daemon/src/core/team-policy/spawn.ts
|
|
726
|
-
import { randomUUID as
|
|
765
|
+
import { randomUUID as randomUUID17 } from "crypto";
|
|
727
766
|
import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
|
|
728
767
|
|
|
729
768
|
// ../daemon/src/core/team-policy/rm-with-retry.ts
|
|
@@ -1174,40 +1213,194 @@ function piMessagesToProviderView(messages) {
|
|
|
1174
1213
|
}
|
|
1175
1214
|
|
|
1176
1215
|
// ../daemon/src/runtime/pi/session.ts
|
|
1177
|
-
import { existsSync as
|
|
1178
|
-
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";
|
|
1179
1221
|
import {
|
|
1180
|
-
AuthStorage,
|
|
1181
1222
|
createAgentSession,
|
|
1182
|
-
|
|
1183
|
-
ModelRegistry,
|
|
1223
|
+
DefaultResourceLoader,
|
|
1184
1224
|
SessionManager,
|
|
1185
1225
|
SettingsManager
|
|
1186
1226
|
} from "@earendil-works/pi-coding-agent";
|
|
1187
1227
|
|
|
1188
|
-
// ../daemon/src/runtime/providers/pi-
|
|
1189
|
-
import {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1228
|
+
// ../daemon/src/runtime/providers/pi-runtime.ts
|
|
1229
|
+
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
1230
|
+
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
1231
|
+
var PROVIDER_ALIASES = {
|
|
1232
|
+
bedrock: "amazon-bedrock",
|
|
1233
|
+
"azure-openai": "azure-openai-responses"
|
|
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
|
+
];
|
|
1279
|
+
var LOCAL_PROVIDERS = {
|
|
1280
|
+
lmstudio: {
|
|
1281
|
+
baseUrl: "http://127.0.0.1:1234/v1",
|
|
1282
|
+
apiKey: "lm-studio",
|
|
1283
|
+
authHeader: false
|
|
1284
|
+
},
|
|
1285
|
+
ollama: { baseUrl: "http://127.0.0.1:11434/v1", apiKey: "ollama", authHeader: false },
|
|
1286
|
+
llamacpp: { baseUrl: "http://127.0.0.1:8080/v1", apiKey: "no-key", authHeader: true }
|
|
1287
|
+
};
|
|
1288
|
+
var PROVIDER_API_KEY_ENV = {
|
|
1289
|
+
anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
|
1290
|
+
openai: ["OPENAI_API_KEY"],
|
|
1291
|
+
google: ["GEMINI_API_KEY"],
|
|
1292
|
+
"azure-openai": ["AZURE_OPENAI_API_KEY"],
|
|
1293
|
+
mistral: ["MISTRAL_API_KEY"],
|
|
1294
|
+
groq: ["GROQ_API_KEY"],
|
|
1295
|
+
cerebras: ["CEREBRAS_API_KEY"],
|
|
1296
|
+
xai: ["XAI_API_KEY"],
|
|
1297
|
+
zai: ["ZAI_API_KEY"],
|
|
1298
|
+
huggingface: ["HF_TOKEN"],
|
|
1299
|
+
openrouter: ["OPENROUTER_API_KEY"],
|
|
1300
|
+
"vercel-ai-gateway": ["AI_GATEWAY_API_KEY"],
|
|
1301
|
+
deepseek: ["DEEPSEEK_API_KEY"],
|
|
1302
|
+
fireworks: ["FIREWORKS_API_KEY"],
|
|
1303
|
+
together: ["TOGETHER_API_KEY"],
|
|
1304
|
+
baseten: ["BASETEN_API_KEY"],
|
|
1305
|
+
moonshotai: ["MOONSHOT_API_KEY"],
|
|
1306
|
+
"moonshotai-cn": ["MOONSHOT_CN_API_KEY"],
|
|
1307
|
+
"kimi-coding": ["KIMI_API_KEY"],
|
|
1308
|
+
minimax: ["MINIMAX_API_KEY"],
|
|
1309
|
+
"minimax-cn": ["MINIMAX_CN_API_KEY"],
|
|
1310
|
+
"qwen-token-plan": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
1311
|
+
"qwen-token-plan-cn": ["QWEN_TOKEN_PLAN_CN_API_KEY"],
|
|
1312
|
+
"qwen-token-plan-individual": ["QWEN_TOKEN_PLAN_API_KEY"],
|
|
1313
|
+
xiaomi: ["XIAOMI_API_KEY"],
|
|
1314
|
+
"xiaomi-token-plan-ams": ["XIAOMI_TOKEN_PLAN_AMS_API_KEY"],
|
|
1315
|
+
"xiaomi-token-plan-cn": ["XIAOMI_TOKEN_PLAN_CN_API_KEY"],
|
|
1316
|
+
"xiaomi-token-plan-sgp": ["XIAOMI_TOKEN_PLAN_SGP_API_KEY"],
|
|
1317
|
+
"ant-ling": ["ANT_LING_API_KEY"],
|
|
1318
|
+
nvidia: ["NVIDIA_API_KEY"],
|
|
1319
|
+
opencode: ["OPENCODE_API_KEY"],
|
|
1320
|
+
"opencode-go": ["OPENCODE_GO_API_KEY"],
|
|
1321
|
+
"zai-coding-cn": ["ZAI_CODING_CN_API_KEY"],
|
|
1322
|
+
"github-copilot": ["COPILOT_GITHUB_TOKEN"],
|
|
1323
|
+
"cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY"],
|
|
1324
|
+
"cloudflare-workers-ai": ["CLOUDFLARE_API_KEY"],
|
|
1325
|
+
lmstudio: ["LMSTUDIO_API_KEY"],
|
|
1326
|
+
ollama: ["OLLAMA_API_KEY"],
|
|
1327
|
+
llamacpp: ["LLAMACPP_API_KEY"]
|
|
1328
|
+
};
|
|
1329
|
+
var PROVIDER_CREDENTIAL_ENV = {
|
|
1330
|
+
bedrock: [
|
|
1331
|
+
"AWS_PROFILE",
|
|
1332
|
+
"AWS_BEARER_TOKEN_BEDROCK",
|
|
1333
|
+
"AWS_ACCESS_KEY_ID",
|
|
1334
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
1335
|
+
"AWS_SESSION_TOKEN",
|
|
1336
|
+
"AWS_REGION",
|
|
1337
|
+
"AWS_DEFAULT_REGION"
|
|
1338
|
+
],
|
|
1339
|
+
"google-vertex": [
|
|
1340
|
+
"GOOGLE_CLOUD_PROJECT",
|
|
1341
|
+
"GOOGLE_CLOUD_LOCATION",
|
|
1342
|
+
"GOOGLE_APPLICATION_CREDENTIALS"
|
|
1343
|
+
],
|
|
1344
|
+
"cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_GATEWAY_ID"],
|
|
1345
|
+
"cloudflare-workers-ai": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID"]
|
|
1346
|
+
};
|
|
1347
|
+
function piProviderName(providerName) {
|
|
1348
|
+
return PROVIDER_ALIASES[providerName] ?? providerName;
|
|
1349
|
+
}
|
|
1350
|
+
function providerApiKey(providerName, env) {
|
|
1351
|
+
for (const name of PROVIDER_API_KEY_ENV[providerName] ?? []) {
|
|
1352
|
+
const value = env[name];
|
|
1353
|
+
if (value) return value;
|
|
1354
|
+
}
|
|
1355
|
+
return LOCAL_PROVIDERS[providerName]?.apiKey;
|
|
1356
|
+
}
|
|
1357
|
+
function providerBaseUrl(providerName, env) {
|
|
1195
1358
|
switch (providerName) {
|
|
1196
1359
|
case "lmstudio":
|
|
1197
|
-
return
|
|
1360
|
+
return env.LMSTUDIO_URL ?? LOCAL_PROVIDERS.lmstudio?.baseUrl;
|
|
1198
1361
|
case "ollama":
|
|
1199
|
-
return
|
|
1362
|
+
return env.OLLAMA_URL ?? LOCAL_PROVIDERS.ollama?.baseUrl;
|
|
1363
|
+
case "llamacpp":
|
|
1364
|
+
return env.LLAMACPP_URL ?? LOCAL_PROVIDERS.llamacpp?.baseUrl;
|
|
1365
|
+
case "vercel-ai-gateway":
|
|
1366
|
+
return env.AI_GATEWAY_BASE_URL;
|
|
1367
|
+
default:
|
|
1368
|
+
return void 0;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
function providerCredentialEnv(providerName, env) {
|
|
1372
|
+
const result = {};
|
|
1373
|
+
for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {
|
|
1374
|
+
const value = env[name];
|
|
1375
|
+
if (value !== void 0) result[name] = value;
|
|
1376
|
+
}
|
|
1377
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
1378
|
+
}
|
|
1379
|
+
function fallbackApi(providerName) {
|
|
1380
|
+
switch (providerName) {
|
|
1381
|
+
case "anthropic":
|
|
1382
|
+
return "anthropic-messages";
|
|
1383
|
+
case "google":
|
|
1384
|
+
return "google-generative-ai";
|
|
1385
|
+
case "google-vertex":
|
|
1386
|
+
return "google-vertex";
|
|
1387
|
+
case "azure-openai":
|
|
1388
|
+
return "azure-openai-responses";
|
|
1389
|
+
case "bedrock":
|
|
1390
|
+
return "bedrock-converse-stream";
|
|
1391
|
+
case "openai-codex":
|
|
1392
|
+
return "openai-codex-responses";
|
|
1200
1393
|
default:
|
|
1201
|
-
return "";
|
|
1394
|
+
return "openai-completions";
|
|
1202
1395
|
}
|
|
1203
1396
|
}
|
|
1204
|
-
function
|
|
1397
|
+
function fallbackPiModel(providerName, modelId, baseUrl) {
|
|
1205
1398
|
return {
|
|
1206
1399
|
id: modelId,
|
|
1207
1400
|
name: modelId,
|
|
1208
|
-
api:
|
|
1209
|
-
provider:
|
|
1210
|
-
baseUrl:
|
|
1401
|
+
api: fallbackApi(providerName),
|
|
1402
|
+
provider: piProviderName(providerName),
|
|
1403
|
+
baseUrl: baseUrl ?? "",
|
|
1211
1404
|
reasoning: false,
|
|
1212
1405
|
input: ["text"],
|
|
1213
1406
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
@@ -1215,28 +1408,116 @@ function buildModelLiteral(cfg, modelId) {
|
|
|
1215
1408
|
maxTokens: 4096
|
|
1216
1409
|
};
|
|
1217
1410
|
}
|
|
1218
|
-
function
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1411
|
+
async function createBazilionPiRuntime(options) {
|
|
1412
|
+
const providerName = options.providerName;
|
|
1413
|
+
const canonicalName = piProviderName(providerName);
|
|
1414
|
+
const credentials = new InMemoryCredentialStore();
|
|
1415
|
+
const apiKey = options.apiKey ?? providerApiKey(providerName, options.env);
|
|
1416
|
+
const credentialEnv = providerCredentialEnv(providerName, options.env);
|
|
1417
|
+
if (apiKey || credentialEnv) {
|
|
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
|
+
}
|
|
1224
1434
|
);
|
|
1225
|
-
if (known && typeof known === "object" && "api" in known) {
|
|
1226
|
-
if (cfg.baseUrl) return { ...known, baseUrl: cfg.baseUrl };
|
|
1227
|
-
return known;
|
|
1228
|
-
}
|
|
1229
|
-
} catch {
|
|
1230
1435
|
}
|
|
1231
|
-
|
|
1436
|
+
const runtime = await ModelRuntime.create({ credentials, modelsPath: null });
|
|
1437
|
+
const local = LOCAL_PROVIDERS[providerName];
|
|
1438
|
+
const baseUrl = options.baseUrl ?? providerBaseUrl(providerName, options.env);
|
|
1439
|
+
if (local) {
|
|
1440
|
+
const modelId = options.modelId ?? "model-name";
|
|
1441
|
+
const model = fallbackPiModel(providerName, modelId, baseUrl ?? local.baseUrl);
|
|
1442
|
+
runtime.registerProvider(canonicalName, {
|
|
1443
|
+
baseUrl: baseUrl ?? local.baseUrl,
|
|
1444
|
+
api: "openai-completions",
|
|
1445
|
+
authHeader: local.authHeader,
|
|
1446
|
+
apiKey: apiKey ?? local.apiKey,
|
|
1447
|
+
models: [model]
|
|
1448
|
+
});
|
|
1449
|
+
} else if (baseUrl) {
|
|
1450
|
+
runtime.registerProvider(canonicalName, { baseUrl });
|
|
1451
|
+
}
|
|
1452
|
+
return runtime;
|
|
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
|
+
}
|
|
1465
|
+
function resolvePiModel(runtime, providerName, modelId, baseUrl) {
|
|
1466
|
+
const known = runtime.getModel(piProviderName(providerName), modelId);
|
|
1467
|
+
if (known) return baseUrl ? { ...known, baseUrl } : known;
|
|
1468
|
+
return fallbackPiModel(providerName, modelId, baseUrl);
|
|
1232
1469
|
}
|
|
1233
1470
|
|
|
1234
1471
|
// ../daemon/src/runtime/auth/openai-codex.ts
|
|
1235
|
-
import {
|
|
1472
|
+
import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
|
|
1473
|
+
|
|
1474
|
+
// ../daemon/src/runtime/providers/pi-adapter.ts
|
|
1475
|
+
import {
|
|
1476
|
+
Type
|
|
1477
|
+
} from "@earendil-works/pi-ai";
|
|
1236
1478
|
|
|
1237
1479
|
// ../daemon/src/runtime/session/prompt.ts
|
|
1238
|
-
import { existsSync as existsSync10, readdirSync as readdirSync6
|
|
1480
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
|
|
1239
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
|
|
1240
1521
|
var SANDBOX_SKILLS_DIR = "/skills";
|
|
1241
1522
|
function loadPromptSkills(skillsDir, attachedNames) {
|
|
1242
1523
|
if (!existsSync10(skillsDir) || attachedNames.length === 0) return [];
|
|
@@ -1246,7 +1527,7 @@ function loadPromptSkills(skillsDir, attachedNames) {
|
|
|
1246
1527
|
for (const entry of discovered) {
|
|
1247
1528
|
const hostDir = join13(skillsDir, entry.name);
|
|
1248
1529
|
try {
|
|
1249
|
-
const parsed =
|
|
1530
|
+
const parsed = parseSkillContent(readRegularFileNoFollow(join13(hostDir, "SKILL.md")));
|
|
1250
1531
|
const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, "_") || "skill";
|
|
1251
1532
|
loaded.push({
|
|
1252
1533
|
name: entry.name,
|
|
@@ -1261,13 +1542,30 @@ function loadPromptSkills(skillsDir, attachedNames) {
|
|
|
1261
1542
|
return loaded;
|
|
1262
1543
|
}
|
|
1263
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
|
+
}
|
|
1264
1563
|
function buildSystemPrompt(agent, options = {}) {
|
|
1265
1564
|
const parts = [];
|
|
1565
|
+
const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir);
|
|
1266
1566
|
const contextBlocks = [];
|
|
1267
1567
|
for (const file of CONTEXT_FILE_ORDER) {
|
|
1268
|
-
const
|
|
1269
|
-
if (!existsSync10(path)) continue;
|
|
1270
|
-
const content = readFileSync9(path, "utf8").trimEnd();
|
|
1568
|
+
const content = homeDocuments[file]?.trimEnd();
|
|
1271
1569
|
if (!content) continue;
|
|
1272
1570
|
contextBlocks.push(`## ${file}
|
|
1273
1571
|
|
|
@@ -1278,27 +1576,24 @@ ${content}`);
|
|
|
1278
1576
|
|
|
1279
1577
|
${contextBlocks.join("\n\n")}`);
|
|
1280
1578
|
}
|
|
1281
|
-
const
|
|
1282
|
-
if (
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
].join("\n")
|
|
1300
|
-
);
|
|
1301
|
-
}
|
|
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
|
+
);
|
|
1302
1597
|
}
|
|
1303
1598
|
parts.push(
|
|
1304
1599
|
[
|
|
@@ -1343,7 +1638,7 @@ ${contextBlocks.join("\n\n")}`);
|
|
|
1343
1638
|
const teamLines = [
|
|
1344
1639
|
"# Team",
|
|
1345
1640
|
"",
|
|
1346
|
-
`- ${agent.team.id} (${agent.team.name}): ${agent.team.path}`,
|
|
1641
|
+
`- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === "docker" ? "/workspace" : agent.team.path}`,
|
|
1347
1642
|
"",
|
|
1348
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`."
|
|
1349
1644
|
];
|
|
@@ -1393,8 +1688,8 @@ function boundReviewedLessons(lessons, maxCharacters = 8e3) {
|
|
|
1393
1688
|
}
|
|
1394
1689
|
|
|
1395
1690
|
// ../daemon/src/runtime/shell/tooling.ts
|
|
1396
|
-
import { existsSync as existsSync11, lstatSync as
|
|
1397
|
-
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";
|
|
1398
1693
|
import {
|
|
1399
1694
|
createBashToolDefinition,
|
|
1400
1695
|
createLocalBashOperations,
|
|
@@ -1781,11 +2076,11 @@ function createApprovalGatedBashTool(base, host) {
|
|
|
1781
2076
|
|
|
1782
2077
|
// ../daemon/src/runtime/shell/docker.ts
|
|
1783
2078
|
import { spawn } from "child_process";
|
|
1784
|
-
import { randomUUID as
|
|
1785
|
-
import { closeSync as
|
|
1786
|
-
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";
|
|
1787
2082
|
import { tmpdir as tmpdir2 } from "os";
|
|
1788
|
-
import { isAbsolute as
|
|
2083
|
+
import { delimiter, isAbsolute as isAbsolute3, join as join14, relative as relative3, sep as sep3 } from "path";
|
|
1789
2084
|
var CONTAINER_WORKDIR = "/workspace";
|
|
1790
2085
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
1791
2086
|
var MAX_STDERR_CHARS = 16 * 1024;
|
|
@@ -1821,6 +2116,14 @@ var CONTAINER_LITERAL_ENV_KEYS = /* @__PURE__ */ new Set([
|
|
|
1821
2116
|
"TMPDIR",
|
|
1822
2117
|
"TZ"
|
|
1823
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
|
+
};
|
|
1824
2127
|
function buildDockerRunSpec(input) {
|
|
1825
2128
|
const executable = input.dockerPath ?? "docker";
|
|
1826
2129
|
validateDockerPath(executable);
|
|
@@ -1903,6 +2206,143 @@ function buildDockerRunSpec(input) {
|
|
|
1903
2206
|
` } : {}
|
|
1904
2207
|
};
|
|
1905
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
|
+
}
|
|
1906
2346
|
function createDockerBashOperations(options) {
|
|
1907
2347
|
const image = options.image;
|
|
1908
2348
|
const env = { ...options.env };
|
|
@@ -1932,7 +2372,7 @@ function createDockerBashOperations(options) {
|
|
|
1932
2372
|
const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
|
|
1933
2373
|
const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
|
|
1934
2374
|
if (signal?.aborted) throw new Error("aborted");
|
|
1935
|
-
const containerName = `bazilion-bash-${process.pid}-${
|
|
2375
|
+
const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
|
|
1936
2376
|
const spec = buildDockerRunSpec({
|
|
1937
2377
|
image,
|
|
1938
2378
|
env,
|
|
@@ -1963,13 +2403,13 @@ function executeDockerRun(spec, options) {
|
|
|
1963
2403
|
)
|
|
1964
2404
|
);
|
|
1965
2405
|
}
|
|
1966
|
-
return new Promise((
|
|
2406
|
+
return new Promise((resolve6, reject) => {
|
|
1967
2407
|
const child = spawn(spec.executable, spec.args, {
|
|
1968
2408
|
stdio: ["ignore", "pipe", "pipe", envFileFd ?? "ignore"],
|
|
1969
2409
|
windowsHide: true,
|
|
1970
2410
|
env: spec.processEnv
|
|
1971
2411
|
});
|
|
1972
|
-
if (envFileFd !== void 0)
|
|
2412
|
+
if (envFileFd !== void 0) closeSync3(envFileFd);
|
|
1973
2413
|
let settled = false;
|
|
1974
2414
|
let termination;
|
|
1975
2415
|
let timeoutHandle;
|
|
@@ -2006,7 +2446,7 @@ function executeDockerRun(spec, options) {
|
|
|
2006
2446
|
reject(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
|
|
2007
2447
|
return;
|
|
2008
2448
|
}
|
|
2009
|
-
|
|
2449
|
+
resolve6({ exitCode });
|
|
2010
2450
|
};
|
|
2011
2451
|
child.stdout?.on("data", options.onData);
|
|
2012
2452
|
child.stderr?.on("data", (data) => {
|
|
@@ -2030,11 +2470,11 @@ function openAnonymousEnvFile(content) {
|
|
|
2030
2470
|
let fd;
|
|
2031
2471
|
try {
|
|
2032
2472
|
writeFileSync7(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
|
|
2033
|
-
fd =
|
|
2473
|
+
fd = openSync3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
2034
2474
|
rmSync10(dir, { recursive: true, force: true });
|
|
2035
2475
|
return fd;
|
|
2036
2476
|
} catch (error) {
|
|
2037
|
-
if (fd !== void 0)
|
|
2477
|
+
if (fd !== void 0) closeSync3(fd);
|
|
2038
2478
|
rmSync10(dir, { recursive: true, force: true });
|
|
2039
2479
|
throw error;
|
|
2040
2480
|
}
|
|
@@ -2052,7 +2492,7 @@ function resolveTimeoutMs(timeout) {
|
|
|
2052
2492
|
}
|
|
2053
2493
|
async function resolveMountSource(cwd) {
|
|
2054
2494
|
try {
|
|
2055
|
-
await access(cwd,
|
|
2495
|
+
await access(cwd, constants3.F_OK);
|
|
2056
2496
|
const info = await stat(cwd);
|
|
2057
2497
|
if (!info.isDirectory()) {
|
|
2058
2498
|
throw new Error(`Docker sandbox mount is not a directory: ${cwd}`);
|
|
@@ -2106,7 +2546,7 @@ async function resolveLocalDockerClientEnv(dockerPath, hostEnv) {
|
|
|
2106
2546
|
const discoveryEnv = buildDockerClientEnv(hostEnv);
|
|
2107
2547
|
const endpoint = await inspectDockerEndpoint(dockerPath, discoveryEnv);
|
|
2108
2548
|
const prefix = "unix://";
|
|
2109
|
-
if (!endpoint.startsWith(prefix) || !
|
|
2549
|
+
if (!endpoint.startsWith(prefix) || !isAbsolute3(endpoint.slice(prefix.length))) {
|
|
2110
2550
|
throw new Error(
|
|
2111
2551
|
`Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint)}`
|
|
2112
2552
|
);
|
|
@@ -2175,7 +2615,7 @@ async function inspectSandboxImage(dockerPath, image, processEnv) {
|
|
|
2175
2615
|
return id;
|
|
2176
2616
|
}
|
|
2177
2617
|
function runDockerInspection(dockerPath, args, processEnv, subject) {
|
|
2178
|
-
return new Promise((
|
|
2618
|
+
return new Promise((resolve6, reject) => {
|
|
2179
2619
|
const child = spawn(dockerPath, args, {
|
|
2180
2620
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2181
2621
|
windowsHide: true,
|
|
@@ -2192,7 +2632,7 @@ function runDockerInspection(dockerPath, args, processEnv, subject) {
|
|
|
2192
2632
|
reject(error);
|
|
2193
2633
|
return;
|
|
2194
2634
|
}
|
|
2195
|
-
|
|
2635
|
+
resolve6(stdout.trim());
|
|
2196
2636
|
};
|
|
2197
2637
|
const timeoutHandle = setTimeout(() => {
|
|
2198
2638
|
child.kill("SIGKILL");
|
|
@@ -2257,7 +2697,7 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
|
|
|
2257
2697
|
const targets = /* @__PURE__ */ new Set([CONTAINER_WORKDIR, "/tmp"]);
|
|
2258
2698
|
for (const mount of mounts) {
|
|
2259
2699
|
validateMountArgument(mount.source);
|
|
2260
|
-
if (mount.sourceRoot !== void 0 && (!
|
|
2700
|
+
if (mount.sourceRoot !== void 0 && (!isAbsolute3(mount.sourceRoot) || mount.sourceRoot.includes("\0") || mount.sourceRoot.includes(","))) {
|
|
2261
2701
|
throw new Error(
|
|
2262
2702
|
`Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`
|
|
2263
2703
|
);
|
|
@@ -2277,8 +2717,8 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
|
|
|
2277
2717
|
}
|
|
2278
2718
|
}
|
|
2279
2719
|
function isWithin(root, candidate) {
|
|
2280
|
-
const rel =
|
|
2281
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !
|
|
2720
|
+
const rel = relative3(root, candidate);
|
|
2721
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute3(rel);
|
|
2282
2722
|
}
|
|
2283
2723
|
function validateHostIdentity(uid, gid) {
|
|
2284
2724
|
if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {
|
|
@@ -2315,7 +2755,7 @@ async function removeContainer(dockerPath, containerName, processEnv) {
|
|
|
2315
2755
|
last = await runCleanupCommand(dockerPath, args, processEnv);
|
|
2316
2756
|
if (last.ok) return;
|
|
2317
2757
|
if (attempt < CLEANUP_ATTEMPTS - 1) {
|
|
2318
|
-
await new Promise((
|
|
2758
|
+
await new Promise((resolve6) => setTimeout(resolve6, CLEANUP_RETRY_DELAY_MS));
|
|
2319
2759
|
}
|
|
2320
2760
|
}
|
|
2321
2761
|
if (last && !last.missing) {
|
|
@@ -2325,7 +2765,7 @@ async function removeContainer(dockerPath, containerName, processEnv) {
|
|
|
2325
2765
|
}
|
|
2326
2766
|
}
|
|
2327
2767
|
function runCleanupCommand(dockerPath, args, processEnv) {
|
|
2328
|
-
return new Promise((
|
|
2768
|
+
return new Promise((resolve6) => {
|
|
2329
2769
|
let done = false;
|
|
2330
2770
|
let stderr = "";
|
|
2331
2771
|
const child = spawn(dockerPath, args, {
|
|
@@ -2338,7 +2778,7 @@ function runCleanupCommand(dockerPath, args, processEnv) {
|
|
|
2338
2778
|
done = true;
|
|
2339
2779
|
clearTimeout(timeoutHandle);
|
|
2340
2780
|
const detail3 = error || stderr.trim();
|
|
2341
|
-
|
|
2781
|
+
resolve6({
|
|
2342
2782
|
ok,
|
|
2343
2783
|
missing: /no such container/i.test(detail3),
|
|
2344
2784
|
error: detail3
|
|
@@ -2362,6 +2802,26 @@ var HOST_FILE_TOOL_NAMES = ["read", "edit", "write", "grep", "find", "ls"];
|
|
|
2362
2802
|
var CONTAINER_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
2363
2803
|
var SANDBOX_INPUTS_DIR = "/inputs";
|
|
2364
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
|
+
}
|
|
2365
2825
|
function buildSandboxContainerEnv(env, allowlist) {
|
|
2366
2826
|
return {
|
|
2367
2827
|
...buildScrubbedShellEnv(env, allowlist),
|
|
@@ -2390,9 +2850,9 @@ function createSessionShellTools(cwd, env, options = {}) {
|
|
|
2390
2850
|
env: buildSandboxContainerEnv(env, config.envAllowlist),
|
|
2391
2851
|
readOnlyMounts: [
|
|
2392
2852
|
...existsSync11(join15(cwd, "memory")) ? [safeReadOnlyMount(join15(cwd, "memory"), SANDBOX_MEMORY_DIR, cwd)] : [],
|
|
2393
|
-
...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR,
|
|
2853
|
+
...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname4(options.inputsDir))] : [],
|
|
2394
2854
|
...(options.skillMounts ?? []).map(
|
|
2395
|
-
(mount) => safeReadOnlyMount(mount.source, mount.target,
|
|
2855
|
+
(mount) => safeReadOnlyMount(mount.source, mount.target, dirname4(mount.source))
|
|
2396
2856
|
)
|
|
2397
2857
|
]
|
|
2398
2858
|
});
|
|
@@ -2406,85 +2866,726 @@ function createSessionShellTools(cwd, env, options = {}) {
|
|
|
2406
2866
|
return { config, hostToolNames: [], customBash };
|
|
2407
2867
|
}
|
|
2408
2868
|
function safeReadOnlyMount(source, target, expectedRoot) {
|
|
2409
|
-
const info =
|
|
2869
|
+
const info = lstatSync3(source);
|
|
2410
2870
|
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
2411
2871
|
throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`);
|
|
2412
2872
|
}
|
|
2413
|
-
const sourceRoot =
|
|
2414
|
-
const canonicalSource =
|
|
2415
|
-
const rel =
|
|
2416
|
-
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);
|
|
2417
2877
|
if (!contained) {
|
|
2418
2878
|
throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`);
|
|
2419
2879
|
}
|
|
2420
2880
|
return { source: canonicalSource, target, sourceRoot };
|
|
2421
2881
|
}
|
|
2422
2882
|
|
|
2423
|
-
// ../daemon/src/runtime/
|
|
2424
|
-
import {
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
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
|
+
}
|
|
2430
2977
|
return {
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
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
|
+
);
|
|
2441
3022
|
}
|
|
2442
|
-
return "BOOTSTRAP.md was already removed.";
|
|
2443
3023
|
}
|
|
2444
|
-
|
|
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
|
+
}
|
|
2445
3152
|
}
|
|
2446
|
-
|
|
2447
|
-
// ../daemon/src/runtime/tools/browser.ts
|
|
2448
|
-
var REF_DESC = 'Element ref from the latest browser_snapshot, e.g. "e5".';
|
|
2449
|
-
function browserTools(host, agentId) {
|
|
2450
|
-
const proxy = (name, action, description, properties, required = []) => ({
|
|
2451
|
-
def: {
|
|
2452
|
-
name,
|
|
2453
|
-
description,
|
|
2454
|
-
parameters: { type: "object", properties, required, additionalProperties: false }
|
|
2455
|
-
},
|
|
2456
|
-
invoke: (args) => host.invoke(agentId, action, args)
|
|
2457
|
-
});
|
|
3153
|
+
function protectedRuntimeSecrets(runtime) {
|
|
2458
3154
|
return [
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
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
|
+
|
|
3524
|
+
// ../daemon/src/runtime/pi/tools.ts
|
|
3525
|
+
import { Type as Type2 } from "typebox";
|
|
3526
|
+
|
|
3527
|
+
// ../daemon/src/runtime/tools/bootstrap.ts
|
|
3528
|
+
import { existsSync as existsSync13, rmSync as rmSync12 } from "fs";
|
|
3529
|
+
import { join as join17 } from "path";
|
|
3530
|
+
function bootstrapTool(agentDir) {
|
|
3531
|
+
return {
|
|
3532
|
+
def: {
|
|
3533
|
+
name: "bootstrap_done",
|
|
3534
|
+
description: "Call this once you have finished your bootstrap conversation (introduced yourself, learned the user, updated IDENTITY.md). Removes BOOTSTRAP.md so it does not appear in future sessions.",
|
|
3535
|
+
parameters: { type: "object", properties: {} }
|
|
3536
|
+
},
|
|
3537
|
+
async invoke() {
|
|
3538
|
+
const path = join17(agentDir, "BOOTSTRAP.md");
|
|
3539
|
+
if (existsSync13(path)) {
|
|
3540
|
+
rmSync12(path);
|
|
3541
|
+
return "BOOTSTRAP.md removed. Bootstrap is complete.";
|
|
3542
|
+
}
|
|
3543
|
+
return "BOOTSTRAP.md was already removed.";
|
|
3544
|
+
}
|
|
3545
|
+
};
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
// ../daemon/src/runtime/tools/browser.ts
|
|
3549
|
+
var REF_DESC = 'Element ref from the latest browser_snapshot, e.g. "e5".';
|
|
3550
|
+
function browserTools(host, agentId) {
|
|
3551
|
+
const proxy = (name, action, description, properties, required = []) => ({
|
|
3552
|
+
def: {
|
|
3553
|
+
name,
|
|
3554
|
+
description,
|
|
3555
|
+
parameters: { type: "object", properties, required, additionalProperties: false }
|
|
3556
|
+
},
|
|
3557
|
+
invoke: (args) => host.invoke(agentId, action, args)
|
|
3558
|
+
});
|
|
3559
|
+
return [
|
|
3560
|
+
proxy(
|
|
3561
|
+
"browser_navigate",
|
|
3562
|
+
"navigate",
|
|
3563
|
+
"Open a URL in the browser and return an accessibility snapshot of the loaded page. Starts a persistent browser session that survives across turns (cookies/logins carry over).",
|
|
3564
|
+
{ url: { type: "string", description: "Absolute URL to navigate to." } },
|
|
3565
|
+
["url"]
|
|
3566
|
+
),
|
|
3567
|
+
proxy(
|
|
3568
|
+
"browser_snapshot",
|
|
3569
|
+
"snapshot",
|
|
3570
|
+
'Capture the current page as an accessibility tree (YAML) with [ref=eN] element references. This is the primary way to "see" the page \u2014 prefer it over screenshots. Use the refs with browser_click/type/etc.',
|
|
3571
|
+
{}
|
|
3572
|
+
),
|
|
3573
|
+
proxy(
|
|
3574
|
+
"browser_click",
|
|
3575
|
+
"click",
|
|
3576
|
+
"Click an element identified by its snapshot ref. Returns a fresh snapshot.",
|
|
3577
|
+
{ ref: { type: "string", description: REF_DESC } },
|
|
3578
|
+
["ref"]
|
|
3579
|
+
),
|
|
3580
|
+
proxy(
|
|
3581
|
+
"browser_type",
|
|
3582
|
+
"type",
|
|
3583
|
+
"Type text into an input/textarea identified by its ref. Set submit=true to press Enter afterwards. Returns a fresh snapshot.",
|
|
3584
|
+
{
|
|
3585
|
+
ref: { type: "string", description: REF_DESC },
|
|
3586
|
+
text: { type: "string", description: "Text to type into the field." },
|
|
3587
|
+
submit: { type: "boolean", description: "Press Enter after typing." }
|
|
3588
|
+
},
|
|
2488
3589
|
["ref", "text"]
|
|
2489
3590
|
),
|
|
2490
3591
|
proxy(
|
|
@@ -2575,15 +3676,15 @@ function browserTools(host, agentId) {
|
|
|
2575
3676
|
|
|
2576
3677
|
// ../daemon/src/runtime/tools/deliver-file.ts
|
|
2577
3678
|
import {
|
|
2578
|
-
closeSync as
|
|
2579
|
-
constants as
|
|
2580
|
-
fstatSync as
|
|
2581
|
-
openSync as
|
|
3679
|
+
closeSync as closeSync4,
|
|
3680
|
+
constants as constants5,
|
|
3681
|
+
fstatSync as fstatSync3,
|
|
3682
|
+
openSync as openSync4,
|
|
2582
3683
|
readFileSync as readFileSync10,
|
|
2583
|
-
realpathSync as
|
|
2584
|
-
statSync as
|
|
3684
|
+
realpathSync as realpathSync5,
|
|
3685
|
+
statSync as statSync4
|
|
2585
3686
|
} from "fs";
|
|
2586
|
-
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";
|
|
2587
3688
|
var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
|
|
2588
3689
|
var MIME = {
|
|
2589
3690
|
".pdf": "application/pdf",
|
|
@@ -2600,14 +3701,14 @@ var MIME = {
|
|
|
2600
3701
|
".webp": "image/webp"
|
|
2601
3702
|
};
|
|
2602
3703
|
function isWithin2(root, candidate) {
|
|
2603
|
-
const rel =
|
|
2604
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${
|
|
3704
|
+
const rel = relative6(root, candidate);
|
|
3705
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep6}`) && !isAbsolute6(rel);
|
|
2605
3706
|
}
|
|
2606
3707
|
function deliverFileTool(cwd, sink) {
|
|
2607
|
-
const workspacePath =
|
|
3708
|
+
const workspacePath = resolve5(cwd);
|
|
2608
3709
|
let workspaceRealPath = null;
|
|
2609
3710
|
try {
|
|
2610
|
-
workspaceRealPath =
|
|
3711
|
+
workspaceRealPath = realpathSync5(workspacePath);
|
|
2611
3712
|
} catch {
|
|
2612
3713
|
}
|
|
2613
3714
|
return {
|
|
@@ -2629,10 +3730,10 @@ function deliverFileTool(cwd, sink) {
|
|
|
2629
3730
|
async invoke(args) {
|
|
2630
3731
|
const p = String(args.path ?? "");
|
|
2631
3732
|
if (!p) throw new Error("deliver_file: path is required");
|
|
2632
|
-
if (
|
|
3733
|
+
if (isAbsolute6(p)) {
|
|
2633
3734
|
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2634
3735
|
}
|
|
2635
|
-
const abs =
|
|
3736
|
+
const abs = resolve5(workspacePath, p);
|
|
2636
3737
|
if (!isWithin2(workspacePath, abs)) {
|
|
2637
3738
|
throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
|
|
2638
3739
|
}
|
|
@@ -2642,8 +3743,8 @@ function deliverFileTool(cwd, sink) {
|
|
|
2642
3743
|
let real;
|
|
2643
3744
|
let expected;
|
|
2644
3745
|
try {
|
|
2645
|
-
real =
|
|
2646
|
-
expected =
|
|
3746
|
+
real = realpathSync5(abs);
|
|
3747
|
+
expected = statSync4(real);
|
|
2647
3748
|
} catch {
|
|
2648
3749
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
2649
3750
|
}
|
|
@@ -2652,13 +3753,13 @@ function deliverFileTool(cwd, sink) {
|
|
|
2652
3753
|
}
|
|
2653
3754
|
let fd;
|
|
2654
3755
|
try {
|
|
2655
|
-
fd =
|
|
3756
|
+
fd = openSync4(real, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
2656
3757
|
} catch {
|
|
2657
3758
|
throw new Error(`deliver_file: no such file: ${p}`);
|
|
2658
3759
|
}
|
|
2659
3760
|
let data;
|
|
2660
3761
|
try {
|
|
2661
|
-
const stat2 =
|
|
3762
|
+
const stat2 = fstatSync3(fd);
|
|
2662
3763
|
if (!stat2.isFile()) {
|
|
2663
3764
|
throw new Error(`deliver_file: not a regular file: ${p}`);
|
|
2664
3765
|
}
|
|
@@ -2667,7 +3768,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
2667
3768
|
}
|
|
2668
3769
|
let confirmedReal;
|
|
2669
3770
|
try {
|
|
2670
|
-
confirmedReal =
|
|
3771
|
+
confirmedReal = realpathSync5(abs);
|
|
2671
3772
|
} catch {
|
|
2672
3773
|
throw new Error(`deliver_file: path changed during validation: ${p}`);
|
|
2673
3774
|
}
|
|
@@ -2681,7 +3782,7 @@ function deliverFileTool(cwd, sink) {
|
|
|
2681
3782
|
}
|
|
2682
3783
|
data = readFileSync10(fd).toString("base64");
|
|
2683
3784
|
} finally {
|
|
2684
|
-
|
|
3785
|
+
closeSync4(fd);
|
|
2685
3786
|
}
|
|
2686
3787
|
const name = basename2(abs);
|
|
2687
3788
|
const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -2692,8 +3793,16 @@ function deliverFileTool(cwd, sink) {
|
|
|
2692
3793
|
}
|
|
2693
3794
|
|
|
2694
3795
|
// ../daemon/src/runtime/tools/home.ts
|
|
2695
|
-
import {
|
|
2696
|
-
|
|
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";
|
|
2697
3806
|
var HOME_FILES_READABLE = [
|
|
2698
3807
|
"IDENTITY.md",
|
|
2699
3808
|
"SOUL.md",
|
|
@@ -2703,6 +3812,7 @@ var HOME_FILES_READABLE = [
|
|
|
2703
3812
|
];
|
|
2704
3813
|
var HOME_FILES_WRITABLE = ["IDENTITY.md", "SOUL.md", "AGENTS.md", "TOOLS.md"];
|
|
2705
3814
|
function homeTools(agentDir) {
|
|
3815
|
+
const homeRoot = resolvePrivateHomeRoot(agentDir);
|
|
2706
3816
|
return [
|
|
2707
3817
|
{
|
|
2708
3818
|
def: {
|
|
@@ -2723,9 +3833,9 @@ function homeTools(agentDir) {
|
|
|
2723
3833
|
`home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
|
|
2724
3834
|
);
|
|
2725
3835
|
}
|
|
2726
|
-
const path =
|
|
3836
|
+
const path = privateHomeFile(homeRoot, file);
|
|
2727
3837
|
try {
|
|
2728
|
-
return
|
|
3838
|
+
return readRegularFileNoFollow(path);
|
|
2729
3839
|
} catch (err) {
|
|
2730
3840
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2731
3841
|
throw new Error(`home_read: could not read ${file}: ${msg}`);
|
|
@@ -2753,8 +3863,23 @@ function homeTools(agentDir) {
|
|
|
2753
3863
|
);
|
|
2754
3864
|
}
|
|
2755
3865
|
const content = typeof args.content === "string" ? args.content : "";
|
|
2756
|
-
const path =
|
|
2757
|
-
|
|
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
|
+
}
|
|
2758
3883
|
return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
|
|
2759
3884
|
}
|
|
2760
3885
|
},
|
|
@@ -2767,9 +3892,10 @@ function homeTools(agentDir) {
|
|
|
2767
3892
|
async invoke() {
|
|
2768
3893
|
const entries = [];
|
|
2769
3894
|
for (const file of HOME_FILES_READABLE) {
|
|
2770
|
-
const path =
|
|
3895
|
+
const path = privateHomeFile(homeRoot, file);
|
|
2771
3896
|
try {
|
|
2772
|
-
const s =
|
|
3897
|
+
const s = lstatSync5(path);
|
|
3898
|
+
if (s.isSymbolicLink() || !s.isFile()) continue;
|
|
2773
3899
|
entries.push(`${file} (${s.size}b)`);
|
|
2774
3900
|
} catch {
|
|
2775
3901
|
}
|
|
@@ -2777,7 +3903,7 @@ function homeTools(agentDir) {
|
|
|
2777
3903
|
if (entries.length === 0) {
|
|
2778
3904
|
const dirEntries = (() => {
|
|
2779
3905
|
try {
|
|
2780
|
-
return readdirSync7(
|
|
3906
|
+
return readdirSync7(homeRoot);
|
|
2781
3907
|
} catch {
|
|
2782
3908
|
return [];
|
|
2783
3909
|
}
|
|
@@ -2789,6 +3915,30 @@ function homeTools(agentDir) {
|
|
|
2789
3915
|
}
|
|
2790
3916
|
];
|
|
2791
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
|
+
}
|
|
2792
3942
|
|
|
2793
3943
|
// ../daemon/src/runtime/tools/mcp.ts
|
|
2794
3944
|
function mcpProxyTools(host, tools) {
|
|
@@ -3161,6 +4311,7 @@ function extractReadable(html, url, mode) {
|
|
|
3161
4311
|
// ../daemon/src/runtime/tools/web-ssrf.ts
|
|
3162
4312
|
import { lookup as dnsLookupCb } from "dns";
|
|
3163
4313
|
import { lookup as dnsLookup } from "dns/promises";
|
|
4314
|
+
import { isIP } from "net";
|
|
3164
4315
|
import { Agent, fetch as undiciFetch } from "undici";
|
|
3165
4316
|
var SsrFBlockedError = class extends Error {
|
|
3166
4317
|
constructor(message) {
|
|
@@ -3169,7 +4320,6 @@ var SsrFBlockedError = class extends Error {
|
|
|
3169
4320
|
}
|
|
3170
4321
|
};
|
|
3171
4322
|
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "metadata.google.internal"]);
|
|
3172
|
-
var PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
|
|
3173
4323
|
function normalizeHostname(hostname) {
|
|
3174
4324
|
let h = hostname.trim().toLowerCase().replace(/\.$/, "");
|
|
3175
4325
|
if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
|
|
@@ -3177,11 +4327,32 @@ function normalizeHostname(hostname) {
|
|
|
3177
4327
|
}
|
|
3178
4328
|
function parseIpv4(address) {
|
|
3179
4329
|
const parts = address.split(".");
|
|
3180
|
-
if (parts.length !== 4) return null;
|
|
3181
|
-
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);
|
|
3182
4332
|
if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null;
|
|
3183
4333
|
return nums;
|
|
3184
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
|
+
}
|
|
3185
4356
|
function isPrivateIpv4(parts) {
|
|
3186
4357
|
const [a, b] = parts;
|
|
3187
4358
|
if (a === 0 || a === 10 || a === 127) return true;
|
|
@@ -3191,21 +4362,38 @@ function isPrivateIpv4(parts) {
|
|
|
3191
4362
|
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
3192
4363
|
return false;
|
|
3193
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
|
+
}
|
|
3194
4380
|
function isPrivateIpAddress(address) {
|
|
3195
4381
|
let norm = address.trim().toLowerCase();
|
|
3196
4382
|
if (norm.startsWith("[") && norm.endsWith("]")) norm = norm.slice(1, -1);
|
|
3197
4383
|
if (!norm) return false;
|
|
3198
|
-
if (norm.startsWith("::ffff:")) {
|
|
3199
|
-
const mapped = norm.slice("::ffff:".length);
|
|
3200
|
-
const ipv42 = parseIpv4(mapped);
|
|
3201
|
-
if (ipv42) return isPrivateIpv4(ipv42);
|
|
3202
|
-
}
|
|
3203
|
-
if (norm.includes(":")) {
|
|
3204
|
-
if (norm === "::" || norm === "::1") return true;
|
|
3205
|
-
return PRIVATE_IPV6_PREFIXES.some((p) => norm.startsWith(p));
|
|
3206
|
-
}
|
|
3207
4384
|
const ipv4 = parseIpv4(norm);
|
|
3208
|
-
|
|
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;
|
|
3209
4397
|
}
|
|
3210
4398
|
function isBlockedHostname(hostname) {
|
|
3211
4399
|
const h = normalizeHostname(hostname);
|
|
@@ -3262,6 +4450,14 @@ async function resolveAndCheck(hostname) {
|
|
|
3262
4450
|
function isRedirectStatus(s) {
|
|
3263
4451
|
return s === 301 || s === 302 || s === 303 || s === 307 || s === 308;
|
|
3264
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
|
+
}
|
|
3265
4461
|
async function closeDispatcher(d) {
|
|
3266
4462
|
if (!d) return;
|
|
3267
4463
|
try {
|
|
@@ -3282,6 +4478,7 @@ async function guardedFetch(opts) {
|
|
|
3282
4478
|
});
|
|
3283
4479
|
}
|
|
3284
4480
|
let current = opts.url;
|
|
4481
|
+
let requestInit = { ...opts.init ?? {} };
|
|
3285
4482
|
const visited = /* @__PURE__ */ new Set();
|
|
3286
4483
|
let redirects = 0;
|
|
3287
4484
|
let dispatcher = null;
|
|
@@ -3309,7 +4506,7 @@ async function guardedFetch(opts) {
|
|
|
3309
4506
|
dispatcher = canPin ? new Agent({ connect: { lookup: createPinnedLookup(parsed.hostname, addrs) } }) : null;
|
|
3310
4507
|
}
|
|
3311
4508
|
const init = {
|
|
3312
|
-
...
|
|
4509
|
+
...requestInit,
|
|
3313
4510
|
redirect: "manual",
|
|
3314
4511
|
signal: abortController.signal
|
|
3315
4512
|
};
|
|
@@ -3339,6 +4536,12 @@ async function guardedFetch(opts) {
|
|
|
3339
4536
|
}
|
|
3340
4537
|
visited.add(next);
|
|
3341
4538
|
void res.body?.cancel();
|
|
4539
|
+
if (new URL(next).origin !== parsed.origin) {
|
|
4540
|
+
requestInit = {
|
|
4541
|
+
...requestInit,
|
|
4542
|
+
headers: withoutCrossOriginCredentials(requestInit.headers)
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
3342
4545
|
current = next;
|
|
3343
4546
|
continue;
|
|
3344
4547
|
}
|
|
@@ -3494,6 +4697,17 @@ function cacheSet(cache, key, value, ttlMs, maxEntries) {
|
|
|
3494
4697
|
}
|
|
3495
4698
|
}
|
|
3496
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) {
|
|
3497
4711
|
const fetchFn = opts?.fetchImpl ?? undiciFetch2;
|
|
3498
4712
|
const env = opts?.env ?? process.env;
|
|
3499
4713
|
const allowPrivate = opts?.allowPrivate ?? false;
|
|
@@ -3503,8 +4717,9 @@ function webTools(opts) {
|
|
|
3503
4717
|
const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
3504
4718
|
const firecrawlDisabled = opts?.firecrawlDisabled ?? false;
|
|
3505
4719
|
const cache = /* @__PURE__ */ new Map();
|
|
3506
|
-
|
|
3507
|
-
|
|
4720
|
+
const tools = [];
|
|
4721
|
+
if (includeSearch) {
|
|
4722
|
+
tools.push({
|
|
3508
4723
|
def: {
|
|
3509
4724
|
name: "web_search",
|
|
3510
4725
|
description: "Search the internet. Returns a list of titles, URLs, and snippets. Requires BRAVE_API_KEY or SEARXNG_URL to be configured.",
|
|
@@ -3535,99 +4750,100 @@ function webTools(opts) {
|
|
|
3535
4750
|
"web_search: no search backend configured. Set BRAVE_API_KEY (free at https://brave.com/search/api/) or SEARXNG_URL."
|
|
3536
4751
|
);
|
|
3537
4752
|
}
|
|
3538
|
-
}
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
extract_mode: {
|
|
3552
|
-
type: "string",
|
|
3553
|
-
enum: ["markdown", "text"],
|
|
3554
|
-
description: 'Output format for HTML pages. Default "markdown".'
|
|
3555
|
-
}
|
|
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)"
|
|
3556
4766
|
},
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
const url = String(args.url ?? "");
|
|
3562
|
-
if (!url) throw new Error("web_fetch: url is required");
|
|
3563
|
-
const maxLen = typeof args.max_length === "number" ? args.max_length : DEFAULT_MAX_LENGTH;
|
|
3564
|
-
const mode = args.extract_mode === "text" ? "text" : "markdown";
|
|
3565
|
-
const cacheKey = `${mode}|${url}`;
|
|
3566
|
-
const cached = cacheGet(cache, cacheKey);
|
|
3567
|
-
if (cached) return formatOutput(cached, maxLen);
|
|
3568
|
-
let result = null;
|
|
3569
|
-
try {
|
|
3570
|
-
result = await guardedFetch({
|
|
3571
|
-
url,
|
|
3572
|
-
fetchImpl: opts?.fetchImpl,
|
|
3573
|
-
allowPrivate,
|
|
3574
|
-
timeoutMs,
|
|
3575
|
-
init: {
|
|
3576
|
-
headers: {
|
|
3577
|
-
"user-agent": DEFAULT_USER_AGENT,
|
|
3578
|
-
accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*",
|
|
3579
|
-
"accept-language": "en-US,en;q=0.9"
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
});
|
|
3583
|
-
if (!result.response.ok) {
|
|
3584
|
-
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".'
|
|
3585
4771
|
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
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"
|
|
3597
4796
|
}
|
|
3598
|
-
}
|
|
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 {
|
|
3599
4812
|
extracted = { text: body };
|
|
3600
4813
|
}
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
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}
|
|
3607
4823
|
|
|
3608
4824
|
[content rendered via Firecrawl fallback \u2014 primary extraction returned ${extracted.text.length} chars]`
|
|
3609
|
-
|
|
3610
|
-
}
|
|
4825
|
+
};
|
|
3611
4826
|
}
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
4827
|
+
}
|
|
4828
|
+
if (truncated) {
|
|
4829
|
+
extracted = {
|
|
4830
|
+
...extracted,
|
|
4831
|
+
text: `${extracted.text}
|
|
3616
4832
|
|
|
3617
4833
|
[raw body truncated at ${maxBodyBytes} bytes before extraction \u2014 page exceeded the size cap]`
|
|
3618
|
-
|
|
3619
|
-
}
|
|
3620
|
-
cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
|
|
3621
|
-
return formatOutput(extracted, maxLen);
|
|
3622
|
-
} catch (err) {
|
|
3623
|
-
if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
|
|
3624
|
-
throw new Error(`web_fetch: ${describeError(err)}`);
|
|
3625
|
-
} finally {
|
|
3626
|
-
if (result) await result.release();
|
|
4834
|
+
};
|
|
3627
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();
|
|
3628
4843
|
}
|
|
3629
4844
|
}
|
|
3630
|
-
|
|
4845
|
+
});
|
|
4846
|
+
return tools;
|
|
3631
4847
|
}
|
|
3632
4848
|
function formatOutput(r, maxLen) {
|
|
3633
4849
|
const body = r.title ? `# ${r.title}
|
|
@@ -3690,8 +4906,26 @@ function createBazilionCustomTools(opts) {
|
|
|
3690
4906
|
}
|
|
3691
4907
|
return handlers.map(ourToolToPiTool);
|
|
3692
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
|
+
}
|
|
3693
4921
|
|
|
3694
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.`;
|
|
3695
4929
|
async function createBazilionSession(opts) {
|
|
3696
4930
|
const {
|
|
3697
4931
|
agent,
|
|
@@ -3713,44 +4947,30 @@ async function createBazilionSession(opts) {
|
|
|
3713
4947
|
if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
|
|
3714
4948
|
throw new Error(`${providerName} provider is disabled \u2014 enable it on the /config page`);
|
|
3715
4949
|
}
|
|
3716
|
-
const
|
|
3717
|
-
const
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
},
|
|
4950
|
+
const apiKey = opts.apiKey ?? providerApiKey(providerName, env);
|
|
4951
|
+
const baseUrl = providerBaseUrl(providerName, env);
|
|
4952
|
+
const modelRuntime = await createBazilionPiRuntime({
|
|
4953
|
+
providerName,
|
|
4954
|
+
env,
|
|
4955
|
+
...apiKey ? { apiKey } : {},
|
|
4956
|
+
...baseUrl ? { baseUrl } : {},
|
|
3724
4957
|
modelId
|
|
3725
|
-
);
|
|
3726
|
-
const
|
|
3727
|
-
const authStorage = AuthStorage.inMemory();
|
|
3728
|
-
if (apiKey) {
|
|
3729
|
-
authStorage.setRuntimeApiKey(piProviderName, apiKey);
|
|
3730
|
-
}
|
|
3731
|
-
const modelRegistry = ModelRegistry.inMemory(authStorage);
|
|
3732
|
-
if (providerName === "lmstudio" || providerName === "ollama") {
|
|
3733
|
-
modelRegistry.registerProvider(piProviderName, {
|
|
3734
|
-
baseUrl: model.baseUrl,
|
|
3735
|
-
api: "openai-completions",
|
|
3736
|
-
authHeader: false,
|
|
3737
|
-
apiKey: apiKey ?? "dummy"
|
|
3738
|
-
});
|
|
3739
|
-
}
|
|
4958
|
+
});
|
|
4959
|
+
const model = resolvePiModel(modelRuntime, providerName, modelId, baseUrl);
|
|
3740
4960
|
const cwd = agent.team.path;
|
|
3741
|
-
if (!
|
|
4961
|
+
if (!existsSync14(cwd)) mkdirSync7(cwd, { recursive: true });
|
|
3742
4962
|
const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills);
|
|
3743
|
-
const uploadsDir =
|
|
4963
|
+
const uploadsDir = join19(agent.agent.dir, "uploads");
|
|
3744
4964
|
const shellTools = restricted ? null : createSessionShellTools(cwd, env, {
|
|
3745
|
-
...
|
|
4965
|
+
...existsSync14(uploadsDir) ? { inputsDir: uploadsDir } : {},
|
|
3746
4966
|
skillMounts: promptSkills.map((skill) => ({
|
|
3747
4967
|
source: skill.hostDir,
|
|
3748
4968
|
target: skill.sandboxDir
|
|
3749
4969
|
})),
|
|
3750
4970
|
approvalHost: bashApprovalHost
|
|
3751
4971
|
});
|
|
3752
|
-
const sessionDir = restricted?.sessionDir ??
|
|
3753
|
-
|
|
4972
|
+
const sessionDir = restricted?.sessionDir ?? join19(paths.agentDir(agent.agent.id), "sessions");
|
|
4973
|
+
mkdirSync7(sessionDir, { recursive: true });
|
|
3754
4974
|
const existing = restricted ? null : findMostRecent(sessionDir);
|
|
3755
4975
|
const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
|
|
3756
4976
|
const settingsManager = SettingsManager.inMemory({
|
|
@@ -3766,7 +4986,17 @@ async function createBazilionSession(opts) {
|
|
|
3766
4986
|
skills: promptSkills,
|
|
3767
4987
|
sandboxMode: shellTools?.config.sandboxMode ?? "off"
|
|
3768
4988
|
});
|
|
3769
|
-
const resourceLoader =
|
|
4989
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
4990
|
+
cwd,
|
|
4991
|
+
agentDir: join19(paths.home, "pi"),
|
|
4992
|
+
settingsManager,
|
|
4993
|
+
noExtensions: true,
|
|
4994
|
+
noSkills: true,
|
|
4995
|
+
noPromptTemplates: true,
|
|
4996
|
+
noThemes: true,
|
|
4997
|
+
noContextFiles: true,
|
|
4998
|
+
appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : void 0
|
|
4999
|
+
});
|
|
3770
5000
|
await resourceLoader.reload();
|
|
3771
5001
|
const bazilionTools = restricted ? restricted.tools : createBazilionCustomTools({
|
|
3772
5002
|
agent,
|
|
@@ -3783,20 +5013,19 @@ async function createBazilionSession(opts) {
|
|
|
3783
5013
|
const allowedTools = [...shellTools?.hostToolNames ?? [], ...customTools.map((t) => t.name)];
|
|
3784
5014
|
const { session } = await createAgentSession({
|
|
3785
5015
|
cwd,
|
|
3786
|
-
agentDir:
|
|
5016
|
+
agentDir: join19(paths.home, "pi"),
|
|
3787
5017
|
model,
|
|
3788
5018
|
thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),
|
|
3789
5019
|
tools: allowedTools,
|
|
3790
5020
|
customTools,
|
|
3791
5021
|
sessionManager,
|
|
3792
5022
|
settingsManager,
|
|
3793
|
-
|
|
3794
|
-
modelRegistry,
|
|
5023
|
+
modelRuntime,
|
|
3795
5024
|
resourceLoader
|
|
3796
5025
|
});
|
|
3797
5026
|
if (refreshApiKey) {
|
|
3798
5027
|
session.agent.getApiKey = async (requestedProvider) => {
|
|
3799
|
-
if (requestedProvider !== piProviderName) return void 0;
|
|
5028
|
+
if (requestedProvider !== piProviderName(providerName)) return void 0;
|
|
3800
5029
|
try {
|
|
3801
5030
|
return await refreshApiKey(providerName);
|
|
3802
5031
|
} catch {
|
|
@@ -3811,73 +5040,264 @@ async function createBazilionSession(opts) {
|
|
|
3811
5040
|
}
|
|
3812
5041
|
};
|
|
3813
5042
|
}
|
|
3814
|
-
function
|
|
3815
|
-
const
|
|
3816
|
-
if (
|
|
3817
|
-
throw new Error(
|
|
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");
|
|
3818
5047
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
if (
|
|
3823
|
-
|
|
3824
|
-
}
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
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 });
|
|
3841
5187
|
}
|
|
5188
|
+
return fields.length > 0 ? fields : void 0;
|
|
3842
5189
|
}
|
|
3843
|
-
function
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
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
|
+
});
|
|
3847
5200
|
}
|
|
3848
|
-
function
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
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 {
|
|
3879
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
|
+
}
|
|
5295
|
+
function splitModelString(s) {
|
|
5296
|
+
const idx = s.indexOf(":");
|
|
5297
|
+
if (idx === -1) {
|
|
5298
|
+
throw new Error(`invalid model string "${s}": expected "provider:model"`);
|
|
3880
5299
|
}
|
|
5300
|
+
return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) };
|
|
3881
5301
|
}
|
|
3882
5302
|
function toPiThinkingLevel(level) {
|
|
3883
5303
|
switch (level) {
|
|
@@ -3892,28 +5312,12 @@ function toPiThinkingLevel(level) {
|
|
|
3892
5312
|
return "medium";
|
|
3893
5313
|
}
|
|
3894
5314
|
}
|
|
3895
|
-
function createBazilionResourceLoader(appendSystemPrompt) {
|
|
3896
|
-
const extensions = { extensions: [], errors: [], runtime: createExtensionRuntime() };
|
|
3897
|
-
return {
|
|
3898
|
-
getExtensions: () => extensions,
|
|
3899
|
-
getSkills: () => ({ skills: [], diagnostics: [] }),
|
|
3900
|
-
getPrompts: () => ({ prompts: [], diagnostics: [] }),
|
|
3901
|
-
getThemes: () => ({ themes: [], diagnostics: [] }),
|
|
3902
|
-
getAgentsFiles: () => ({ agentsFiles: [] }),
|
|
3903
|
-
getSystemPrompt: () => void 0,
|
|
3904
|
-
getAppendSystemPrompt: () => appendSystemPrompt ? [appendSystemPrompt] : [],
|
|
3905
|
-
extendResources: () => {
|
|
3906
|
-
},
|
|
3907
|
-
async reload() {
|
|
3908
|
-
}
|
|
3909
|
-
};
|
|
3910
|
-
}
|
|
3911
5315
|
function findMostRecent(sessionDir) {
|
|
3912
|
-
if (!
|
|
5316
|
+
if (!existsSync14(sessionDir)) return null;
|
|
3913
5317
|
let newest = null;
|
|
3914
5318
|
for (const entry of readdirSync8(sessionDir)) {
|
|
3915
5319
|
if (!entry.endsWith(".jsonl")) continue;
|
|
3916
|
-
const path =
|
|
5320
|
+
const path = join19(sessionDir, entry);
|
|
3917
5321
|
try {
|
|
3918
5322
|
const s = statSync5(path);
|
|
3919
5323
|
if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
|
|
@@ -3926,7 +5330,7 @@ function findMostRecent(sessionDir) {
|
|
|
3926
5330
|
// ../daemon/src/runtime/worker/api-key-refresh.ts
|
|
3927
5331
|
function createIpcApiKeyRefresher(call, context) {
|
|
3928
5332
|
return async (providerName) => {
|
|
3929
|
-
|
|
5333
|
+
assertSelectedProvider(providerName, context.providerName);
|
|
3930
5334
|
return call("refreshApiKey", {
|
|
3931
5335
|
providerName,
|
|
3932
5336
|
agentId: context.agentId,
|
|
@@ -3934,14 +5338,14 @@ function createIpcApiKeyRefresher(call, context) {
|
|
|
3934
5338
|
});
|
|
3935
5339
|
};
|
|
3936
5340
|
}
|
|
3937
|
-
function
|
|
3938
|
-
if (expected
|
|
5341
|
+
function assertSelectedProvider(requested, expected) {
|
|
5342
|
+
if (!expected || requested !== expected) {
|
|
3939
5343
|
throw new Error("unexpected API key refresh provider");
|
|
3940
5344
|
}
|
|
3941
5345
|
}
|
|
3942
5346
|
|
|
3943
5347
|
// ../daemon/src/runtime/worker/ipc-client.ts
|
|
3944
|
-
import { randomUUID as
|
|
5348
|
+
import { randomUUID as randomUUID19 } from "crypto";
|
|
3945
5349
|
function createIpcClient(channel) {
|
|
3946
5350
|
const pending = /* @__PURE__ */ new Map();
|
|
3947
5351
|
let disconnected = false;
|
|
@@ -3968,10 +5372,10 @@ function createIpcClient(channel) {
|
|
|
3968
5372
|
if (!send) {
|
|
3969
5373
|
return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
|
|
3970
5374
|
}
|
|
3971
|
-
const id =
|
|
5375
|
+
const id = randomUUID19();
|
|
3972
5376
|
const message = { type: "rpc", id, method, args };
|
|
3973
|
-
return new Promise((
|
|
3974
|
-
pending.set(id, { resolve: (value) =>
|
|
5377
|
+
return new Promise((resolve6, reject) => {
|
|
5378
|
+
pending.set(id, { resolve: (value) => resolve6(value), reject });
|
|
3975
5379
|
try {
|
|
3976
5380
|
send(message, (error) => {
|
|
3977
5381
|
if (!error) return;
|
|
@@ -3992,29 +5396,40 @@ function isIpcReply(message) {
|
|
|
3992
5396
|
}
|
|
3993
5397
|
|
|
3994
5398
|
// ../daemon/src/runtime/worker/entry.ts
|
|
5399
|
+
var activeAccessTokens = [];
|
|
3995
5400
|
function emit(frame) {
|
|
3996
|
-
process.stdout.write(`${JSON.stringify(frame)}
|
|
5401
|
+
process.stdout.write(`${JSON.stringify(redactJsonValue(frame, activeAccessTokens))}
|
|
3997
5402
|
`);
|
|
3998
5403
|
}
|
|
3999
5404
|
async function readInput() {
|
|
4000
5405
|
const chunks = [];
|
|
4001
|
-
for await (const
|
|
5406
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4002
5407
|
const raw = Buffer.concat(chunks).toString("utf8").trim();
|
|
4003
5408
|
if (!raw) throw new Error("worker: empty stdin input");
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
);
|
|
4009
|
-
}
|
|
4010
|
-
if (parsed.mode === "review" && !parsed.review?.reviewId) {
|
|
4011
|
-
throw new Error("worker: review mode requires review metadata");
|
|
4012
|
-
}
|
|
4013
|
-
const providerName = parsed.agent.model.split(":", 1)[0] ?? "";
|
|
4014
|
-
if (parsed.apiKeyRefreshEnabled && (providerName !== "openai-codex" || typeof parsed.apiKey !== "string")) {
|
|
4015
|
-
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");
|
|
4016
5414
|
}
|
|
4017
|
-
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
|
+
};
|
|
4018
5433
|
}
|
|
4019
5434
|
function createIpcMessagingHost(call) {
|
|
4020
5435
|
return {
|
|
@@ -4048,81 +5463,27 @@ function createIpcBashApprovalHost(call, input) {
|
|
|
4048
5463
|
return {
|
|
4049
5464
|
async requestApproval(request) {
|
|
4050
5465
|
const result = await call("bashApproval", {
|
|
4051
|
-
id:
|
|
5466
|
+
id: randomUUID20(),
|
|
4052
5467
|
turnId: input.turnId,
|
|
4053
5468
|
toolCallId: request.toolCallId,
|
|
4054
|
-
agentId: input.
|
|
4055
|
-
teamId: input.
|
|
5469
|
+
agentId: input.agentId,
|
|
5470
|
+
teamId: input.teamId,
|
|
4056
5471
|
command: request.command,
|
|
4057
5472
|
risks: [...request.risks],
|
|
4058
|
-
mode: input.
|
|
5473
|
+
mode: input.mode
|
|
4059
5474
|
});
|
|
4060
5475
|
return result.decision === "allow" ? "approved" : "denied";
|
|
4061
5476
|
}
|
|
4062
5477
|
};
|
|
4063
5478
|
}
|
|
4064
|
-
|
|
4065
|
-
let aborted = false;
|
|
4066
|
-
let abortSession = null;
|
|
4067
|
-
const onSignal = () => {
|
|
4068
|
-
aborted = true;
|
|
4069
|
-
if (abortSession) {
|
|
4070
|
-
abortSession();
|
|
4071
|
-
} else {
|
|
4072
|
-
try {
|
|
4073
|
-
emit({ kind: "event", event: { type: "error", error: "cancelled" } });
|
|
4074
|
-
} catch {
|
|
4075
|
-
}
|
|
4076
|
-
process.exit(0);
|
|
4077
|
-
}
|
|
4078
|
-
};
|
|
4079
|
-
process.on("SIGTERM", onSignal);
|
|
4080
|
-
process.on("SIGINT", onSignal);
|
|
4081
|
-
const {
|
|
4082
|
-
agent,
|
|
4083
|
-
message,
|
|
4084
|
-
enabledProviders,
|
|
4085
|
-
apiKey,
|
|
4086
|
-
apiKeyRefreshEnabled,
|
|
4087
|
-
browserEnabled,
|
|
4088
|
-
mcpTools,
|
|
4089
|
-
images,
|
|
4090
|
-
turnId,
|
|
4091
|
-
bashApprovalMode,
|
|
4092
|
-
mode = "chat",
|
|
4093
|
-
review
|
|
4094
|
-
} = await readInput();
|
|
4095
|
-
const paths = resolvePaths();
|
|
4096
|
-
const memory = qmdBackend(join19(agent.team.path, "memory"));
|
|
4097
|
-
await memory.init();
|
|
4098
|
-
const ipcCall = createIpcClient({
|
|
4099
|
-
send: process.send ? (ipcMessage, done) => {
|
|
4100
|
-
process.send?.(ipcMessage, void 0, void 0, done);
|
|
4101
|
-
} : void 0,
|
|
4102
|
-
onMessage: (listener) => process.on("message", listener),
|
|
4103
|
-
onDisconnect: (listener) => process.on("disconnect", listener)
|
|
4104
|
-
});
|
|
4105
|
-
const refreshApiKey = apiKeyRefreshEnabled ? createIpcApiKeyRefresher(ipcCall, {
|
|
4106
|
-
providerName: agent.model.split(":", 1)[0] ?? "",
|
|
4107
|
-
agentId: agent.agent.id,
|
|
4108
|
-
turnId
|
|
4109
|
-
}) : void 0;
|
|
4110
|
-
const messagingHost = createIpcMessagingHost(ipcCall);
|
|
4111
|
-
const userMdHost = createIpcUserMdHost(ipcCall);
|
|
4112
|
-
const browserHost = browserEnabled ? createIpcBrowserHost(ipcCall) : void 0;
|
|
4113
|
-
const mcpHost = mcpTools && mcpTools.length > 0 ? createIpcMcpHost(ipcCall) : void 0;
|
|
4114
|
-
const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {
|
|
4115
|
-
agent,
|
|
4116
|
-
turnId,
|
|
4117
|
-
bashApprovalMode
|
|
4118
|
-
});
|
|
4119
|
-
const reviewSessionDir = mode === "review" ? mkdtempSync3(join19(paths.logsDir, "review-session-")) : null;
|
|
5479
|
+
function createReviewState(evidenceItems) {
|
|
4120
5480
|
const proposals = [];
|
|
4121
5481
|
let proposalBatchSubmitted = false;
|
|
5482
|
+
let reviewValidationFailures = 0;
|
|
4122
5483
|
const allowedEvidence = new Set(
|
|
4123
|
-
|
|
5484
|
+
evidenceItems.map((item) => `${item.sessionId}:${item.entryOrdinal}`)
|
|
4124
5485
|
);
|
|
4125
|
-
const
|
|
5486
|
+
const tools = [
|
|
4126
5487
|
{
|
|
4127
5488
|
name: "propose_lesson",
|
|
4128
5489
|
label: "propose_lesson",
|
|
@@ -4142,8 +5503,8 @@ async function main() {
|
|
|
4142
5503
|
}),
|
|
4143
5504
|
async execute(_toolCallId, params) {
|
|
4144
5505
|
if (proposalBatchSubmitted) throw new Error("propose_lesson may be called only once");
|
|
4145
|
-
proposalBatchSubmitted = true;
|
|
4146
5506
|
const input = params;
|
|
5507
|
+
const validated = [];
|
|
4147
5508
|
for (const candidate of input.proposals) {
|
|
4148
5509
|
const text = candidate.text.trim();
|
|
4149
5510
|
if (!text || text.length > 500) throw new Error("proposal text must be 1-500 characters");
|
|
@@ -4152,8 +5513,10 @@ async function main() {
|
|
|
4152
5513
|
throw new Error("proposal cites evidence outside the supplied digest");
|
|
4153
5514
|
}
|
|
4154
5515
|
}
|
|
4155
|
-
|
|
5516
|
+
validated.push({ ...candidate, text });
|
|
4156
5517
|
}
|
|
5518
|
+
proposalBatchSubmitted = true;
|
|
5519
|
+
proposals.push(...validated);
|
|
4157
5520
|
return {
|
|
4158
5521
|
content: [{ type: "text", text: `accepted ${proposals.length} proposals` }],
|
|
4159
5522
|
details: {},
|
|
@@ -4162,86 +5525,168 @@ async function main() {
|
|
|
4162
5525
|
}
|
|
4163
5526
|
}
|
|
4164
5527
|
];
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
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,
|
|
4169
5608
|
memory,
|
|
4170
|
-
enabledProviders: new Set(enabledProviders),
|
|
4171
5609
|
messagingHost,
|
|
4172
5610
|
userMdHost,
|
|
4173
|
-
apiKey,
|
|
4174
|
-
refreshApiKey,
|
|
4175
|
-
browserHost,
|
|
4176
|
-
mcpHost,
|
|
4177
|
-
mcpTools,
|
|
4178
5611
|
bashApprovalHost,
|
|
4179
|
-
|
|
4180
|
-
fileSink: (
|
|
4181
|
-
...mode === "review" && reviewSessionDir ? {
|
|
4182
|
-
restricted: {
|
|
4183
|
-
systemPrompt: REVIEW_SYSTEM_PROMPT,
|
|
4184
|
-
tools: reviewTools,
|
|
4185
|
-
sessionDir: reviewSessionDir,
|
|
4186
|
-
reasoningLevel: agent.agent.reviewReasoningLevel
|
|
4187
|
-
}
|
|
4188
|
-
} : {}
|
|
5612
|
+
refreshApiKey,
|
|
5613
|
+
fileSink: (file) => emit({ kind: "event", event: { type: "file", ...file } })
|
|
4189
5614
|
});
|
|
4190
|
-
|
|
4191
|
-
|
|
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
|
+
}
|
|
4192
5630
|
};
|
|
4193
|
-
|
|
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;
|
|
4194
5642
|
const unsubscribe = session.subscribe((piEvent) => {
|
|
4195
|
-
if (
|
|
4196
|
-
|
|
4197
|
-
if (
|
|
4198
|
-
}
|
|
4199
|
-
for (const ev of translatePiEvent(piEvent)) {
|
|
4200
|
-
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();
|
|
4201
5646
|
}
|
|
5647
|
+
for (const event of translatePiEvent(piEvent)) emit({ kind: "event", event });
|
|
4202
5648
|
});
|
|
4203
5649
|
try {
|
|
4204
|
-
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) => ({
|
|
4205
5653
|
type: "image",
|
|
4206
|
-
data:
|
|
4207
|
-
mimeType:
|
|
5654
|
+
data: image.data,
|
|
5655
|
+
mimeType: image.mimeType
|
|
4208
5656
|
}));
|
|
4209
|
-
await session.prompt(
|
|
5657
|
+
await session.prompt(
|
|
5658
|
+
promptMessage,
|
|
5659
|
+
promptImages.length > 0 ? { images: promptImages } : void 0
|
|
5660
|
+
);
|
|
4210
5661
|
await session.agent.waitForIdle();
|
|
4211
|
-
if (
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
throw new Error("review output did not submit a proposal batch");
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
emit({
|
|
4220
|
-
kind: "done",
|
|
4221
|
-
messages: piMessagesToProviderView(session.agent.state.messages)
|
|
4222
|
-
});
|
|
4223
|
-
}
|
|
4224
|
-
} catch (err) {
|
|
4225
|
-
if (!aborted) {
|
|
4226
|
-
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) });
|
|
4227
5670
|
}
|
|
4228
|
-
|
|
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 });
|
|
4229
5675
|
process.exitCode = 1;
|
|
4230
5676
|
} finally {
|
|
4231
5677
|
unsubscribe();
|
|
4232
5678
|
process.off("SIGTERM", onSignal);
|
|
4233
5679
|
process.off("SIGINT", onSignal);
|
|
4234
|
-
dispose();
|
|
4235
|
-
if (reviewSessionDir) rmSync12(reviewSessionDir, { recursive: true, force: true });
|
|
5680
|
+
handle.dispose();
|
|
4236
5681
|
try {
|
|
4237
5682
|
process.disconnect?.();
|
|
4238
5683
|
} catch {
|
|
4239
5684
|
}
|
|
4240
5685
|
}
|
|
4241
5686
|
}
|
|
4242
|
-
main().catch((
|
|
5687
|
+
main().catch((error) => {
|
|
4243
5688
|
try {
|
|
4244
|
-
emit({ kind: "fatal", error:
|
|
5689
|
+
emit({ kind: "fatal", error: error instanceof Error ? error.message : String(error) });
|
|
4245
5690
|
} catch {
|
|
4246
5691
|
}
|
|
4247
5692
|
process.exit(1);
|