bazilion 0.12.2 → 0.13.0

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