bazilion 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +811 -250
  3. package/dist/cli.js.map +1 -1
  4. package/dist/daemon.js +4778 -1461
  5. package/dist/daemon.js.map +1 -1
  6. package/dist/migrations/0001_init.sql +21 -0
  7. package/dist/web/client/assets/{ProductionTeamPolicyEditor-CSL3W7XM.js → ProductionTeamPolicyEditor-DdpYInwV.js} +1 -1
  8. package/dist/web/client/assets/{ProfileCommunicationEditor-CwU0QkmE.js → ProfileCommunicationEditor-BcSf2Hao.js} +1 -1
  9. package/dist/web/client/assets/{TeamTabs-C1Hakkff.js → TeamTabs-Bk89JfN7.js} +1 -1
  10. package/dist/web/client/assets/{TemplatesTabs-klEydJ8-.js → TemplatesTabs-CyDVzrOl.js} +1 -1
  11. package/dist/web/client/assets/{_id-CnVq-rED.js → _id-CAJTTnee.js} +1 -1
  12. package/dist/web/client/assets/{_id-wkjjWLz0.js → _id-CN6jPaBu.js} +1 -1
  13. package/dist/web/client/assets/{_id-BOC7ioJu.js → _id-DGwJqqDc.js} +1 -1
  14. package/dist/web/client/assets/{_id-e4u7dmBb.js → _id-DWTMH7Eb.js} +1 -1
  15. package/dist/web/client/assets/{activity-uqhwzmiy.js → activity-DK_M0-bn.js} +1 -1
  16. package/dist/web/client/assets/{agents-B2usE-L_.js → agents-B-i5pASm.js} +1 -1
  17. package/dist/web/client/assets/{agents-FaQtkAWs.js → agents-n8U7qNxw.js} +1 -1
  18. package/dist/web/client/assets/{approvals-DqLE6ttw.js → approvals-CmVxCy0Z.js} +1 -1
  19. package/dist/web/client/assets/{attention-CkGozYnJ.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-ZWqx3uiC.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-BoIvl9ZF.js → inbox-D2NypLee.js} +1 -1
  34. package/dist/web/client/assets/{index-nb2MV8X1.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-6_oYn1l7.js → learning-zYDCfVxT.js} +1 -1
  41. package/dist/web/client/assets/{login-DM1ytxQL.js → login-B1Un51Zx.js} +1 -1
  42. package/dist/web/client/assets/{mcp-CetZbyUh.js → mcp-DSVmJBdv.js} +1 -1
  43. package/dist/web/client/assets/{members-DdqWtwZd.js → members-Pl7hXJcN.js} +1 -1
  44. package/dist/web/client/assets/{memory-FNrxFHmC.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-BKPejYRI.js → policy-CHM95pMU.js} +1 -1
  47. package/dist/web/client/assets/{routes-CH1ZTTo-.js → routes-D29qjwG0.js} +1 -1
  48. package/dist/web/client/assets/{services-zk9SqN06.js → services-CCQ_6YWQ.js} +1 -1
  49. package/dist/web/client/assets/{skills-tXDpomHW.js → skills-3xAIvzvP.js} +1 -1
  50. package/dist/web/client/assets/styles-D6RPN7RK.css +2 -0
  51. package/dist/web/client/assets/{teams-BZYjekBN.js → teams-Dm_huGxn.js} +1 -1
  52. package/dist/web/client/assets/{teams-D1c8zlxs.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-D8R-5guA.js → triggers-DlQ2KF1h.js} +1 -1
  56. package/dist/web/client/assets/{welcome-CuK0shDJ.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-fLsYERQ-.js → _tanstack-start-manifest_v-COkJjXt_.js} +50 -49
  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-D2LAsud2.js → config-DGzM28F_.js} +211 -27
  88. package/dist/web/server/assets/{config-TeIoyYYc.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-CQuj7553.js → router-D-6TzLRZ.js} +168 -65
  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/assets/{welcome-IYHGurWx.js → welcome-DDB11L5j.js} +2 -2
  143. package/dist/web/server/server.js +30 -30
  144. package/dist/web-server.js +4 -0
  145. package/dist/web-server.js.map +1 -1
  146. package/dist/worker.js +1982 -537
  147. package/dist/worker.js.map +1 -1
  148. package/package.json +7 -6
  149. package/dist/web/client/assets/chevron-down-CJk0zGNT.js +0 -1
  150. package/dist/web/client/assets/config-xrXoDEWM.js +0 -3
  151. package/dist/web/client/assets/plus-C3GroITC.js +0 -1
  152. package/dist/web/client/assets/styles-DMltY3su.css +0 -2
  153. package/dist/web/client/assets/telegram-DCe1CWMt.js +0 -1
  154. package/dist/web/client/assets/tokens-DAPjRS0N.js +0 -1
  155. package/dist/web/server/assets/context-B-XDf6Ym.js +0 -12
  156. package/dist/web/server/assets/context-BXGo5Vfi.js +0 -12
  157. package/dist/web/server/assets/members-CW5aBe81.js +0 -12
  158. package/dist/web/server/assets/members-VdPwD6CE.js +0 -12
  159. package/dist/web/server/assets/start-WH8WzTL_.js +0 -4
  160. 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",
@@ -279,6 +290,27 @@ var SERVICES = [
279
290
  hint: "MiniMax M2/M3 family via the China endpoint",
280
291
  fields: [{ envVar: "MINIMAX_CN_API_KEY", kind: "secret", label: "API key" }]
281
292
  },
293
+ {
294
+ id: "qwen-token-plan",
295
+ displayName: "Qwen Token Plan",
296
+ category: "provider",
297
+ hint: "Coding-model subscription endpoint in Singapore",
298
+ fields: [{ envVar: "QWEN_TOKEN_PLAN_API_KEY", kind: "secret", label: "API key" }]
299
+ },
300
+ {
301
+ id: "qwen-token-plan-cn",
302
+ displayName: "Qwen Token Plan (CN)",
303
+ category: "provider",
304
+ hint: "Coding-model subscription endpoint in China",
305
+ fields: [{ envVar: "QWEN_TOKEN_PLAN_CN_API_KEY", kind: "secret", label: "API key" }]
306
+ },
307
+ {
308
+ id: "qwen-token-plan-individual",
309
+ displayName: "Qwen Token Plan (Individual)",
310
+ category: "provider",
311
+ hint: "Individual subscription models via the international endpoint",
312
+ fields: [{ envVar: "QWEN_TOKEN_PLAN_API_KEY", kind: "secret", label: "API key" }]
313
+ },
282
314
  {
283
315
  id: "xiaomi",
284
316
  displayName: "Xiaomi MiMo",
@@ -657,14 +689,25 @@ import { randomUUID as randomUUID8 } from "crypto";
657
689
  // ../daemon/src/core/repos/secrets.ts
658
690
  import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
659
691
 
692
+ // ../daemon/src/core/repos/telegram-pairing.ts
693
+ import { createHash as createHash2, randomBytes as randomBytes2, timingSafeEqual } from "crypto";
694
+ var TTL_MS = 10 * 6e4;
695
+
660
696
  // ../daemon/src/core/repos/triggerDispatches.ts
661
697
  import { randomUUID as randomUUID9 } from "crypto";
662
698
 
663
699
  // ../daemon/src/core/repos/triggers.ts
664
700
  import { randomUUID as randomUUID10 } from "crypto";
665
701
 
702
+ // ../daemon/src/core/repos/webSessions.ts
703
+ import { createHash as createHash4, randomBytes as randomBytes4, randomUUID as randomUUID12 } from "crypto";
704
+
666
705
  // ../daemon/src/core/repos/webTokens.ts
667
- import { createHash as 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;
668
711
 
669
712
  // ../daemon/src/core/secrets.ts
670
713
  import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
@@ -704,26 +747,22 @@ function parseSkillContent(raw) {
704
747
  }
705
748
  return { frontmatter: fmObj, body, raw };
706
749
  }
707
- function parseSkillFile(path) {
708
- const raw = readFileSync7(path, "utf8");
709
- return parseSkillContent(raw);
710
- }
711
750
 
712
751
  // ../daemon/src/core/team/delete.ts
713
- import { randomUUID as randomUUID12 } from "crypto";
752
+ import { randomUUID as randomUUID13 } from "crypto";
714
753
  import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
715
754
 
716
755
  // ../daemon/src/core/team-policy/adopt.ts
717
- import { randomUUID as randomUUID13 } from "crypto";
756
+ import { randomUUID as randomUUID14 } from "crypto";
718
757
 
719
758
  // ../daemon/src/core/team-policy/authorization.ts
720
- import { createHash as createHash3, randomUUID as randomUUID14 } from "crypto";
759
+ import { createHash as createHash5, randomUUID as randomUUID15 } from "crypto";
721
760
 
722
761
  // ../daemon/src/core/team-policy/source.ts
723
- import { randomUUID as randomUUID15 } from "crypto";
762
+ import { randomUUID as randomUUID16 } from "crypto";
724
763
 
725
764
  // ../daemon/src/core/team-policy/spawn.ts
726
- import { randomUUID as randomUUID16 } from "crypto";
765
+ import { randomUUID as randomUUID17 } from "crypto";
727
766
  import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
728
767
 
729
768
  // ../daemon/src/core/team-policy/rm-with-retry.ts
@@ -1174,40 +1213,194 @@ function piMessagesToProviderView(messages) {
1174
1213
  }
1175
1214
 
1176
1215
  // ../daemon/src/runtime/pi/session.ts
1177
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, statSync as statSync5 } from "fs";
1178
- 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";
1179
1221
  import {
1180
- AuthStorage,
1181
1222
  createAgentSession,
1182
- createExtensionRuntime,
1183
- ModelRegistry,
1223
+ DefaultResourceLoader,
1184
1224
  SessionManager,
1185
1225
  SettingsManager
1186
1226
  } from "@earendil-works/pi-coding-agent";
1187
1227
 
1188
- // ../daemon/src/runtime/providers/pi-adapter.ts
1189
- import {
1190
- getModel,
1191
- streamSimple,
1192
- Type
1193
- } from "@earendil-works/pi-ai/compat";
1194
- function defaultBaseUrlFor(providerName) {
1228
+ // ../daemon/src/runtime/providers/pi-runtime.ts
1229
+ import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
1230
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
1231
+ var PROVIDER_ALIASES = {
1232
+ bedrock: "amazon-bedrock",
1233
+ "azure-openai": "azure-openai-responses"
1234
+ };
1235
+ var PROTECTED_PROVIDER_NAMES = [
1236
+ "anthropic",
1237
+ "openai",
1238
+ "openai-codex",
1239
+ "google",
1240
+ "google-vertex",
1241
+ "azure-openai",
1242
+ "bedrock",
1243
+ "mistral",
1244
+ "groq",
1245
+ "cerebras",
1246
+ "xai",
1247
+ "zai",
1248
+ "huggingface",
1249
+ "openrouter",
1250
+ "vercel-ai-gateway",
1251
+ "deepseek",
1252
+ "fireworks",
1253
+ "together",
1254
+ "baseten",
1255
+ "moonshotai",
1256
+ "moonshotai-cn",
1257
+ "kimi-coding",
1258
+ "minimax",
1259
+ "minimax-cn",
1260
+ "qwen-token-plan",
1261
+ "qwen-token-plan-cn",
1262
+ "qwen-token-plan-individual",
1263
+ "xiaomi",
1264
+ "xiaomi-token-plan-ams",
1265
+ "xiaomi-token-plan-cn",
1266
+ "xiaomi-token-plan-sgp",
1267
+ "ant-ling",
1268
+ "nvidia",
1269
+ "opencode",
1270
+ "opencode-go",
1271
+ "zai-coding-cn",
1272
+ "github-copilot",
1273
+ "cloudflare-ai-gateway",
1274
+ "cloudflare-workers-ai",
1275
+ "lmstudio",
1276
+ "ollama",
1277
+ "llamacpp"
1278
+ ];
1279
+ var LOCAL_PROVIDERS = {
1280
+ lmstudio: {
1281
+ baseUrl: "http://127.0.0.1:1234/v1",
1282
+ apiKey: "lm-studio",
1283
+ authHeader: false
1284
+ },
1285
+ ollama: { baseUrl: "http://127.0.0.1:11434/v1", apiKey: "ollama", authHeader: false },
1286
+ llamacpp: { baseUrl: "http://127.0.0.1:8080/v1", apiKey: "no-key", authHeader: true }
1287
+ };
1288
+ var PROVIDER_API_KEY_ENV = {
1289
+ anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
1290
+ openai: ["OPENAI_API_KEY"],
1291
+ google: ["GEMINI_API_KEY"],
1292
+ "azure-openai": ["AZURE_OPENAI_API_KEY"],
1293
+ mistral: ["MISTRAL_API_KEY"],
1294
+ groq: ["GROQ_API_KEY"],
1295
+ cerebras: ["CEREBRAS_API_KEY"],
1296
+ xai: ["XAI_API_KEY"],
1297
+ zai: ["ZAI_API_KEY"],
1298
+ huggingface: ["HF_TOKEN"],
1299
+ openrouter: ["OPENROUTER_API_KEY"],
1300
+ "vercel-ai-gateway": ["AI_GATEWAY_API_KEY"],
1301
+ deepseek: ["DEEPSEEK_API_KEY"],
1302
+ fireworks: ["FIREWORKS_API_KEY"],
1303
+ together: ["TOGETHER_API_KEY"],
1304
+ baseten: ["BASETEN_API_KEY"],
1305
+ moonshotai: ["MOONSHOT_API_KEY"],
1306
+ "moonshotai-cn": ["MOONSHOT_CN_API_KEY"],
1307
+ "kimi-coding": ["KIMI_API_KEY"],
1308
+ minimax: ["MINIMAX_API_KEY"],
1309
+ "minimax-cn": ["MINIMAX_CN_API_KEY"],
1310
+ "qwen-token-plan": ["QWEN_TOKEN_PLAN_API_KEY"],
1311
+ "qwen-token-plan-cn": ["QWEN_TOKEN_PLAN_CN_API_KEY"],
1312
+ "qwen-token-plan-individual": ["QWEN_TOKEN_PLAN_API_KEY"],
1313
+ xiaomi: ["XIAOMI_API_KEY"],
1314
+ "xiaomi-token-plan-ams": ["XIAOMI_TOKEN_PLAN_AMS_API_KEY"],
1315
+ "xiaomi-token-plan-cn": ["XIAOMI_TOKEN_PLAN_CN_API_KEY"],
1316
+ "xiaomi-token-plan-sgp": ["XIAOMI_TOKEN_PLAN_SGP_API_KEY"],
1317
+ "ant-ling": ["ANT_LING_API_KEY"],
1318
+ nvidia: ["NVIDIA_API_KEY"],
1319
+ opencode: ["OPENCODE_API_KEY"],
1320
+ "opencode-go": ["OPENCODE_GO_API_KEY"],
1321
+ "zai-coding-cn": ["ZAI_CODING_CN_API_KEY"],
1322
+ "github-copilot": ["COPILOT_GITHUB_TOKEN"],
1323
+ "cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY"],
1324
+ "cloudflare-workers-ai": ["CLOUDFLARE_API_KEY"],
1325
+ lmstudio: ["LMSTUDIO_API_KEY"],
1326
+ ollama: ["OLLAMA_API_KEY"],
1327
+ llamacpp: ["LLAMACPP_API_KEY"]
1328
+ };
1329
+ var PROVIDER_CREDENTIAL_ENV = {
1330
+ bedrock: [
1331
+ "AWS_PROFILE",
1332
+ "AWS_BEARER_TOKEN_BEDROCK",
1333
+ "AWS_ACCESS_KEY_ID",
1334
+ "AWS_SECRET_ACCESS_KEY",
1335
+ "AWS_SESSION_TOKEN",
1336
+ "AWS_REGION",
1337
+ "AWS_DEFAULT_REGION"
1338
+ ],
1339
+ "google-vertex": [
1340
+ "GOOGLE_CLOUD_PROJECT",
1341
+ "GOOGLE_CLOUD_LOCATION",
1342
+ "GOOGLE_APPLICATION_CREDENTIALS"
1343
+ ],
1344
+ "cloudflare-ai-gateway": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_GATEWAY_ID"],
1345
+ "cloudflare-workers-ai": ["CLOUDFLARE_API_KEY", "CLOUDFLARE_ACCOUNT_ID"]
1346
+ };
1347
+ function piProviderName(providerName) {
1348
+ return PROVIDER_ALIASES[providerName] ?? providerName;
1349
+ }
1350
+ function providerApiKey(providerName, env) {
1351
+ for (const name of PROVIDER_API_KEY_ENV[providerName] ?? []) {
1352
+ const value = env[name];
1353
+ if (value) return value;
1354
+ }
1355
+ return LOCAL_PROVIDERS[providerName]?.apiKey;
1356
+ }
1357
+ function providerBaseUrl(providerName, env) {
1195
1358
  switch (providerName) {
1196
1359
  case "lmstudio":
1197
- return process.env.LMSTUDIO_URL ?? "http://127.0.0.1:1234/v1";
1360
+ return env.LMSTUDIO_URL ?? LOCAL_PROVIDERS.lmstudio?.baseUrl;
1198
1361
  case "ollama":
1199
- return process.env.OLLAMA_URL ?? "http://127.0.0.1:11434/v1";
1362
+ return env.OLLAMA_URL ?? LOCAL_PROVIDERS.ollama?.baseUrl;
1363
+ case "llamacpp":
1364
+ return env.LLAMACPP_URL ?? LOCAL_PROVIDERS.llamacpp?.baseUrl;
1365
+ case "vercel-ai-gateway":
1366
+ return env.AI_GATEWAY_BASE_URL;
1367
+ default:
1368
+ return void 0;
1369
+ }
1370
+ }
1371
+ function providerCredentialEnv(providerName, env) {
1372
+ const result = {};
1373
+ for (const name of PROVIDER_CREDENTIAL_ENV[providerName] ?? []) {
1374
+ const value = env[name];
1375
+ if (value !== void 0) result[name] = value;
1376
+ }
1377
+ return Object.keys(result).length > 0 ? result : void 0;
1378
+ }
1379
+ function fallbackApi(providerName) {
1380
+ switch (providerName) {
1381
+ case "anthropic":
1382
+ return "anthropic-messages";
1383
+ case "google":
1384
+ return "google-generative-ai";
1385
+ case "google-vertex":
1386
+ return "google-vertex";
1387
+ case "azure-openai":
1388
+ return "azure-openai-responses";
1389
+ case "bedrock":
1390
+ return "bedrock-converse-stream";
1391
+ case "openai-codex":
1392
+ return "openai-codex-responses";
1200
1393
  default:
1201
- return "";
1394
+ return "openai-completions";
1202
1395
  }
1203
1396
  }
1204
- function buildModelLiteral(cfg, modelId) {
1397
+ function fallbackPiModel(providerName, modelId, baseUrl) {
1205
1398
  return {
1206
1399
  id: modelId,
1207
1400
  name: modelId,
1208
- api: cfg.fallbackApi,
1209
- provider: cfg.providerName,
1210
- baseUrl: cfg.baseUrl ?? defaultBaseUrlFor(cfg.providerName),
1401
+ api: fallbackApi(providerName),
1402
+ provider: piProviderName(providerName),
1403
+ baseUrl: baseUrl ?? "",
1211
1404
  reasoning: false,
1212
1405
  input: ["text"],
1213
1406
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
@@ -1215,28 +1408,116 @@ function buildModelLiteral(cfg, modelId) {
1215
1408
  maxTokens: 4096
1216
1409
  };
1217
1410
  }
1218
- function resolveModel(cfg, modelId) {
1219
- const lookupName = cfg.piProviderName ?? cfg.providerName;
1220
- try {
1221
- const known = getModel(
1222
- lookupName,
1223
- modelId
1411
+ async function createBazilionPiRuntime(options) {
1412
+ const providerName = options.providerName;
1413
+ const canonicalName = piProviderName(providerName);
1414
+ const credentials = new InMemoryCredentialStore();
1415
+ const apiKey = options.apiKey ?? providerApiKey(providerName, options.env);
1416
+ const credentialEnv = providerCredentialEnv(providerName, options.env);
1417
+ if (apiKey || credentialEnv) {
1418
+ await credentials.modify(
1419
+ canonicalName,
1420
+ async () => canonicalName === "openai-codex" && apiKey ? {
1421
+ type: "oauth",
1422
+ access: apiKey,
1423
+ // Bazilion owns refresh outside Pi. This deliberately empty value
1424
+ // satisfies Pi's canonical OAuth shape but is never used because
1425
+ // the token is long-lived only inside this one-shot runtime and
1426
+ // mid-turn replacement is supplied through Agent.getApiKey.
1427
+ refresh: "",
1428
+ expires: Number.MAX_SAFE_INTEGER
1429
+ } : {
1430
+ type: "api_key",
1431
+ ...apiKey ? { key: apiKey } : {},
1432
+ ...credentialEnv ? { env: credentialEnv } : {}
1433
+ }
1224
1434
  );
1225
- if (known && typeof known === "object" && "api" in known) {
1226
- if (cfg.baseUrl) return { ...known, baseUrl: cfg.baseUrl };
1227
- return known;
1228
- }
1229
- } catch {
1230
1435
  }
1231
- return buildModelLiteral(cfg, modelId);
1436
+ const runtime = await ModelRuntime.create({ credentials, modelsPath: null });
1437
+ const local = LOCAL_PROVIDERS[providerName];
1438
+ const baseUrl = options.baseUrl ?? providerBaseUrl(providerName, options.env);
1439
+ if (local) {
1440
+ const modelId = options.modelId ?? "model-name";
1441
+ const model = fallbackPiModel(providerName, modelId, baseUrl ?? local.baseUrl);
1442
+ runtime.registerProvider(canonicalName, {
1443
+ baseUrl: baseUrl ?? local.baseUrl,
1444
+ api: "openai-completions",
1445
+ authHeader: local.authHeader,
1446
+ apiKey: apiKey ?? local.apiKey,
1447
+ models: [model]
1448
+ });
1449
+ } else if (baseUrl) {
1450
+ runtime.registerProvider(canonicalName, { baseUrl });
1451
+ }
1452
+ return runtime;
1453
+ }
1454
+ async function createProtectedPiRuntime(options) {
1455
+ const env = {};
1456
+ for (const field of options.credentialEnv ?? []) env[field.name] = field.value;
1457
+ return createBazilionPiRuntime({
1458
+ providerName: options.providerName,
1459
+ modelId: options.modelId,
1460
+ env,
1461
+ apiKey: options.apiKey,
1462
+ baseUrl: options.baseUrl
1463
+ });
1464
+ }
1465
+ function resolvePiModel(runtime, providerName, modelId, baseUrl) {
1466
+ const known = runtime.getModel(piProviderName(providerName), modelId);
1467
+ if (known) return baseUrl ? { ...known, baseUrl } : known;
1468
+ return fallbackPiModel(providerName, modelId, baseUrl);
1232
1469
  }
1233
1470
 
1234
1471
  // ../daemon/src/runtime/auth/openai-codex.ts
1235
- import { loginOpenAICodex, refreshOpenAICodexToken } from "@earendil-works/pi-ai/oauth";
1472
+ import { openaiCodexProvider } from "@earendil-works/pi-ai/providers/openai-codex";
1473
+
1474
+ // ../daemon/src/runtime/providers/pi-adapter.ts
1475
+ import {
1476
+ Type
1477
+ } from "@earendil-works/pi-ai";
1236
1478
 
1237
1479
  // ../daemon/src/runtime/session/prompt.ts
1238
- import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
1480
+ import { existsSync as existsSync10, readdirSync as readdirSync6 } from "fs";
1239
1481
  import { join as join13 } from "path";
1482
+
1483
+ // ../daemon/src/runtime/safe-files.ts
1484
+ import {
1485
+ closeSync as closeSync2,
1486
+ constants as constants2,
1487
+ fstatSync as fstatSync2,
1488
+ lstatSync as lstatSync2,
1489
+ mkdirSync as mkdirSync5,
1490
+ openSync as openSync2,
1491
+ readFileSync as readFileSync9,
1492
+ realpathSync as realpathSync2
1493
+ } from "fs";
1494
+ import { dirname as dirname3, isAbsolute as isAbsolute2, relative as relative2, resolve as resolve4 } from "path";
1495
+ function resolveRealDirectory(path) {
1496
+ const requested = resolve4(path);
1497
+ const entry = lstatSync2(requested);
1498
+ if (entry.isSymbolicLink() || !entry.isDirectory()) {
1499
+ throw new Error("expected a real directory");
1500
+ }
1501
+ return realpathSync2(requested);
1502
+ }
1503
+ function readRegularFileNoFollow(path) {
1504
+ const entry = lstatSync2(path);
1505
+ if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
1506
+ if (!entry.isFile()) throw new Error("target is not a regular file");
1507
+ let fd;
1508
+ try {
1509
+ fd = openSync2(path, constants2.O_RDONLY | noFollowFlag());
1510
+ if (!fstatSync2(fd).isFile()) throw new Error("target is not a regular file");
1511
+ return readFileSync9(fd, "utf8");
1512
+ } finally {
1513
+ if (fd !== void 0) closeSync2(fd);
1514
+ }
1515
+ }
1516
+ function noFollowFlag() {
1517
+ return typeof constants2.O_NOFOLLOW === "number" ? constants2.O_NOFOLLOW : 0;
1518
+ }
1519
+
1520
+ // ../daemon/src/runtime/session/prompt.ts
1240
1521
  var SANDBOX_SKILLS_DIR = "/skills";
1241
1522
  function loadPromptSkills(skillsDir, attachedNames) {
1242
1523
  if (!existsSync10(skillsDir) || attachedNames.length === 0) return [];
@@ -1246,7 +1527,7 @@ function loadPromptSkills(skillsDir, attachedNames) {
1246
1527
  for (const entry of discovered) {
1247
1528
  const hostDir = join13(skillsDir, entry.name);
1248
1529
  try {
1249
- const parsed = parseSkillFile(join13(hostDir, "SKILL.md"));
1530
+ const parsed = parseSkillContent(readRegularFileNoFollow(join13(hostDir, "SKILL.md")));
1250
1531
  const safeLabel = entry.name.replace(/[^A-Za-z0-9._-]/g, "_") || "skill";
1251
1532
  loaded.push({
1252
1533
  name: entry.name,
@@ -1261,13 +1542,30 @@ function loadPromptSkills(skillsDir, attachedNames) {
1261
1542
  return loaded;
1262
1543
  }
1263
1544
  var CONTEXT_FILE_ORDER = ["AGENTS.md", "SOUL.md", "TOOLS.md", "IDENTITY.md"];
1545
+ function loadProtectedHomeDocuments(agentDir) {
1546
+ const root = resolveRealDirectory(agentDir);
1547
+ const read = (file) => {
1548
+ try {
1549
+ return readRegularFileNoFollow(join13(root, file));
1550
+ } catch (error) {
1551
+ if (error.code === "ENOENT") return null;
1552
+ throw new Error(`private Agent home is unsafe at ${file}`);
1553
+ }
1554
+ };
1555
+ return {
1556
+ "AGENTS.md": read("AGENTS.md"),
1557
+ "SOUL.md": read("SOUL.md"),
1558
+ "TOOLS.md": read("TOOLS.md"),
1559
+ "IDENTITY.md": read("IDENTITY.md"),
1560
+ "BOOTSTRAP.md": read("BOOTSTRAP.md")
1561
+ };
1562
+ }
1264
1563
  function buildSystemPrompt(agent, options = {}) {
1265
1564
  const parts = [];
1565
+ const homeDocuments = options.homeDocuments ?? loadProtectedHomeDocuments(agent.agent.dir);
1266
1566
  const contextBlocks = [];
1267
1567
  for (const file of CONTEXT_FILE_ORDER) {
1268
- const path = join13(agent.agent.dir, file);
1269
- if (!existsSync10(path)) continue;
1270
- const content = readFileSync9(path, "utf8").trimEnd();
1568
+ const content = homeDocuments[file]?.trimEnd();
1271
1569
  if (!content) continue;
1272
1570
  contextBlocks.push(`## ${file}
1273
1571
 
@@ -1278,27 +1576,24 @@ ${content}`);
1278
1576
 
1279
1577
  ${contextBlocks.join("\n\n")}`);
1280
1578
  }
1281
- const bootstrapPath = join13(agent.agent.dir, "BOOTSTRAP.md");
1282
- if (existsSync10(bootstrapPath)) {
1283
- const bootstrap = readFileSync9(bootstrapPath, "utf8").trimEnd();
1284
- if (bootstrap) {
1285
- parts.push(
1286
- [
1287
- "# First-Run Ritual",
1288
- "",
1289
- "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.",
1290
- "",
1291
- "## Hard rules",
1292
- "- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
1293
- "- Each subsequent turn: at most one new question. Wait between turns.",
1294
- "- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
1295
- "",
1296
- "## BOOTSTRAP.md",
1297
- "",
1298
- bootstrap
1299
- ].join("\n")
1300
- );
1301
- }
1579
+ const bootstrap = homeDocuments["BOOTSTRAP.md"]?.trimEnd();
1580
+ if (bootstrap) {
1581
+ parts.push(
1582
+ [
1583
+ "# First-Run Ritual",
1584
+ "",
1585
+ "This is your first session. The document below is **conversational guidance**, not a checklist to execute in one shot. It describes a multi-turn Q&A you should have with the human, one question per turn.",
1586
+ "",
1587
+ "## Hard rules",
1588
+ "- Your first reply is ONLY a greeting + ONE question. No tool calls. Wait for the human to answer.",
1589
+ "- Each subsequent turn: at most one new question. Wait between turns.",
1590
+ "- Only after the ritual is complete (you have enough to write IDENTITY.md): call `home_write` once, then `bootstrap_done`.",
1591
+ "",
1592
+ "## BOOTSTRAP.md",
1593
+ "",
1594
+ bootstrap
1595
+ ].join("\n")
1596
+ );
1302
1597
  }
1303
1598
  parts.push(
1304
1599
  [
@@ -1343,7 +1638,7 @@ ${contextBlocks.join("\n\n")}`);
1343
1638
  const teamLines = [
1344
1639
  "# Team",
1345
1640
  "",
1346
- `- ${agent.team.id} (${agent.team.name}): ${agent.team.path}`,
1641
+ `- ${agent.team.id} (${agent.team.name}): ${options.sandboxMode === "docker" ? "/workspace" : agent.team.path}`,
1347
1642
  "",
1348
1643
  "Your team is where work product lives \u2014 code, docs, artefacts, shared scratch. It may be shared with other agents in the same team. Use the workspace tools currently exposed to work there. Never use workspace tools to edit your identity/soul/behaviour files \u2014 those live in your private home and are reached via `home_write` / `home_read`."
1349
1644
  ];
@@ -1393,8 +1688,8 @@ function boundReviewedLessons(lessons, maxCharacters = 8e3) {
1393
1688
  }
1394
1689
 
1395
1690
  // ../daemon/src/runtime/shell/tooling.ts
1396
- import { existsSync as existsSync11, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
1397
- 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";
1398
1693
  import {
1399
1694
  createBashToolDefinition,
1400
1695
  createLocalBashOperations,
@@ -1781,11 +2076,11 @@ function createApprovalGatedBashTool(base, host) {
1781
2076
 
1782
2077
  // ../daemon/src/runtime/shell/docker.ts
1783
2078
  import { spawn } from "child_process";
1784
- import { randomUUID as randomUUID17 } from "crypto";
1785
- import { closeSync as closeSync2, constants as constants2, mkdtempSync as mkdtempSync2, openSync as openSync2, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
1786
- import { access, realpath, stat } from "fs/promises";
2079
+ import { randomUUID as randomUUID18 } from "crypto";
2080
+ import { closeSync as closeSync3, constants as constants3, mkdtempSync as mkdtempSync2, openSync as openSync3, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
2081
+ import { access, lstat, realpath, stat } from "fs/promises";
1787
2082
  import { tmpdir as tmpdir2 } from "os";
1788
- import { isAbsolute as 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";
1789
2084
  var CONTAINER_WORKDIR = "/workspace";
1790
2085
  var MAX_TIMEOUT_MS = 2147483647;
1791
2086
  var MAX_STDERR_CHARS = 16 * 1024;
@@ -1821,6 +2116,14 @@ var CONTAINER_LITERAL_ENV_KEYS = /* @__PURE__ */ new Set([
1821
2116
  "TMPDIR",
1822
2117
  "TZ"
1823
2118
  ]);
2119
+ var PROTECTED_CONTAINER_ENV = {
2120
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
2121
+ HOME: "/tmp",
2122
+ LANG: "C.UTF-8",
2123
+ LC_ALL: "C.UTF-8",
2124
+ SHELL: "/bin/bash",
2125
+ TMPDIR: "/tmp"
2126
+ };
1824
2127
  function buildDockerRunSpec(input) {
1825
2128
  const executable = input.dockerPath ?? "docker";
1826
2129
  validateDockerPath(executable);
@@ -1903,6 +2206,143 @@ function buildDockerRunSpec(input) {
1903
2206
  ` } : {}
1904
2207
  };
1905
2208
  }
2209
+ function createPreparedDockerBashOperations(runtime) {
2210
+ validatePreparedDockerRuntime(runtime);
2211
+ return {
2212
+ async exec(command, cwd, { onData, signal, timeout }) {
2213
+ const timeoutMs = resolveTimeoutMs(timeout);
2214
+ if (signal?.aborted) throw new Error("aborted");
2215
+ const workspace = await recheckProtectedMount(runtime.workspace);
2216
+ const requestedCwd = await resolveMountSource(cwd);
2217
+ if (requestedCwd !== workspace.source) {
2218
+ throw new Error("Docker sandbox execution workspace no longer matches preflight");
2219
+ }
2220
+ const readOnlyMounts = await Promise.all(
2221
+ runtime.readOnlyMounts.map((mount) => recheckProtectedMount(mount))
2222
+ );
2223
+ await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity);
2224
+ await assertLocalDockerSocket(runtime.endpoint);
2225
+ const dockerClientEnv = { DOCKER_HOST: runtime.endpoint };
2226
+ const currentImageId = await inspectSandboxImage(
2227
+ runtime.dockerPath,
2228
+ runtime.image,
2229
+ dockerClientEnv
2230
+ );
2231
+ if (currentImageId !== runtime.imageId) {
2232
+ throw new Error("Docker sandbox image no longer matches its preflighted immutable id");
2233
+ }
2234
+ await assertDockerExecutableIdentity(runtime.dockerPath, runtime.executableIdentity);
2235
+ if (signal?.aborted) throw new Error("aborted");
2236
+ const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
2237
+ const spec = buildDockerRunSpec({
2238
+ image: runtime.image,
2239
+ resolvedImage: runtime.imageId,
2240
+ env: { ...runtime.containerEnv },
2241
+ dockerPath: runtime.dockerPath,
2242
+ hostEnv: dockerClientEnv,
2243
+ command,
2244
+ mountSource: workspace.source,
2245
+ readOnlyMounts,
2246
+ containerName,
2247
+ uid: runtime.uid,
2248
+ gid: runtime.gid
2249
+ });
2250
+ return executeDockerRun(spec, { onData, signal, timeout, timeoutMs });
2251
+ }
2252
+ };
2253
+ }
2254
+ async function recheckProtectedMount(mount) {
2255
+ const sourceInfo = await lstat(mount.source);
2256
+ if (sourceInfo.isSymbolicLink() || !sourceInfo.isDirectory()) {
2257
+ throw new Error("Docker sandbox mount no longer matches preflight");
2258
+ }
2259
+ const sourceRoot = await resolveRequiredRoot(mount.sourceRoot);
2260
+ const source = await resolveMountSource(mount.source);
2261
+ if (sourceRoot !== mount.sourceRoot || source !== mount.source || !isWithin(sourceRoot, source)) {
2262
+ throw new Error("Docker sandbox mount no longer matches preflight");
2263
+ }
2264
+ return { ...mount, source, sourceRoot };
2265
+ }
2266
+ async function resolveRequiredRoot(root) {
2267
+ if (!isAbsolute3(root)) throw new Error("Docker sandbox mount root must be absolute");
2268
+ return resolveMountSource(root);
2269
+ }
2270
+ async function inspectDockerExecutableIdentity(dockerPath) {
2271
+ try {
2272
+ await access(dockerPath, constants3.X_OK);
2273
+ const info = await stat(dockerPath, { bigint: true });
2274
+ if (!info.isFile()) throw new Error("not a regular file");
2275
+ return {
2276
+ device: info.dev.toString(),
2277
+ inode: info.ino.toString(),
2278
+ mode: info.mode.toString(),
2279
+ size: info.size.toString(),
2280
+ modifiedTimeNs: info.mtimeNs.toString(),
2281
+ changedTimeNs: info.ctimeNs.toString()
2282
+ };
2283
+ } catch {
2284
+ throw new Error(`Docker sandbox executable is unavailable: ${dockerPath}`);
2285
+ }
2286
+ }
2287
+ async function assertDockerExecutableIdentity(dockerPath, expected) {
2288
+ let current;
2289
+ try {
2290
+ current = await inspectDockerExecutableIdentity(dockerPath);
2291
+ } catch {
2292
+ throw new Error("Docker sandbox executable no longer matches its preflighted identity");
2293
+ }
2294
+ if (JSON.stringify(current) !== JSON.stringify(expected)) {
2295
+ throw new Error("Docker sandbox executable no longer matches its preflighted identity");
2296
+ }
2297
+ }
2298
+ async function assertLocalDockerSocket(endpoint) {
2299
+ const prefix = "unix://";
2300
+ if (!endpoint.startsWith(prefix) || !isAbsolute3(endpoint.slice(prefix.length))) {
2301
+ throw new Error("Docker sandbox requires a local Unix-socket context");
2302
+ }
2303
+ const socketPath = endpoint.slice(prefix.length);
2304
+ try {
2305
+ const info = await stat(socketPath);
2306
+ if (!info.isSocket()) throw new Error("not a Unix socket");
2307
+ await access(socketPath, constants3.R_OK | constants3.W_OK);
2308
+ } catch {
2309
+ throw new Error("Docker sandbox local Unix socket is unavailable");
2310
+ }
2311
+ }
2312
+ function validatePreparedDockerRuntime(runtime) {
2313
+ if (!isAbsolute3(runtime.dockerPath)) {
2314
+ throw new Error("protected Docker executable must be absolute");
2315
+ }
2316
+ validateDockerPath(runtime.dockerPath);
2317
+ validateDockerExecutableIdentity(runtime.executableIdentity);
2318
+ validateImage(runtime.image);
2319
+ validateImage(runtime.imageId);
2320
+ validateHostIdentity(runtime.uid, runtime.gid);
2321
+ if (!runtime.endpoint.startsWith("unix://")) {
2322
+ throw new Error("protected Docker endpoint must be a local Unix socket");
2323
+ }
2324
+ validateReadOnlyMounts(runtime.readOnlyMounts, runtime.workspace.source);
2325
+ const expectedEnvironment = JSON.stringify(PROTECTED_CONTAINER_ENV);
2326
+ if (JSON.stringify(runtime.containerEnv) !== expectedEnvironment) {
2327
+ throw new Error("protected Docker container environment does not match policy");
2328
+ }
2329
+ }
2330
+ function validateDockerExecutableIdentity(identity) {
2331
+ if (typeof identity !== "object" || identity === null) {
2332
+ throw new Error("protected Docker executable identity is invalid");
2333
+ }
2334
+ const fields = [
2335
+ identity.device,
2336
+ identity.inode,
2337
+ identity.mode,
2338
+ identity.size,
2339
+ identity.modifiedTimeNs,
2340
+ identity.changedTimeNs
2341
+ ];
2342
+ if (fields.some((field) => typeof field !== "string" || !/^\d+$/.test(field))) {
2343
+ throw new Error("protected Docker executable identity is invalid");
2344
+ }
2345
+ }
1906
2346
  function createDockerBashOperations(options) {
1907
2347
  const image = options.image;
1908
2348
  const env = { ...options.env };
@@ -1932,7 +2372,7 @@ function createDockerBashOperations(options) {
1932
2372
  const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
1933
2373
  const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
1934
2374
  if (signal?.aborted) throw new Error("aborted");
1935
- const containerName = `bazilion-bash-${process.pid}-${randomUUID17().replaceAll("-", "")}`;
2375
+ const containerName = `bazilion-bash-${process.pid}-${randomUUID18().replaceAll("-", "")}`;
1936
2376
  const spec = buildDockerRunSpec({
1937
2377
  image,
1938
2378
  env,
@@ -1963,13 +2403,13 @@ function executeDockerRun(spec, options) {
1963
2403
  )
1964
2404
  );
1965
2405
  }
1966
- return new Promise((resolve5, reject) => {
2406
+ return new Promise((resolve6, reject) => {
1967
2407
  const child = spawn(spec.executable, spec.args, {
1968
2408
  stdio: ["ignore", "pipe", "pipe", envFileFd ?? "ignore"],
1969
2409
  windowsHide: true,
1970
2410
  env: spec.processEnv
1971
2411
  });
1972
- if (envFileFd !== void 0) closeSync2(envFileFd);
2412
+ if (envFileFd !== void 0) closeSync3(envFileFd);
1973
2413
  let settled = false;
1974
2414
  let termination;
1975
2415
  let timeoutHandle;
@@ -2006,7 +2446,7 @@ function executeDockerRun(spec, options) {
2006
2446
  reject(new Error(`Docker sandbox CLI for container "${spec.containerName}" was killed`));
2007
2447
  return;
2008
2448
  }
2009
- resolve5({ exitCode });
2449
+ resolve6({ exitCode });
2010
2450
  };
2011
2451
  child.stdout?.on("data", options.onData);
2012
2452
  child.stderr?.on("data", (data) => {
@@ -2030,11 +2470,11 @@ function openAnonymousEnvFile(content) {
2030
2470
  let fd;
2031
2471
  try {
2032
2472
  writeFileSync7(path, content, { encoding: "utf8", flag: "wx", mode: 384 });
2033
- fd = openSync2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
2473
+ fd = openSync3(path, constants3.O_RDONLY | constants3.O_NOFOLLOW);
2034
2474
  rmSync10(dir, { recursive: true, force: true });
2035
2475
  return fd;
2036
2476
  } catch (error) {
2037
- if (fd !== void 0) closeSync2(fd);
2477
+ if (fd !== void 0) closeSync3(fd);
2038
2478
  rmSync10(dir, { recursive: true, force: true });
2039
2479
  throw error;
2040
2480
  }
@@ -2052,7 +2492,7 @@ function resolveTimeoutMs(timeout) {
2052
2492
  }
2053
2493
  async function resolveMountSource(cwd) {
2054
2494
  try {
2055
- await access(cwd, constants2.F_OK);
2495
+ await access(cwd, constants3.F_OK);
2056
2496
  const info = await stat(cwd);
2057
2497
  if (!info.isDirectory()) {
2058
2498
  throw new Error(`Docker sandbox mount is not a directory: ${cwd}`);
@@ -2106,7 +2546,7 @@ async function resolveLocalDockerClientEnv(dockerPath, hostEnv) {
2106
2546
  const discoveryEnv = buildDockerClientEnv(hostEnv);
2107
2547
  const endpoint = await inspectDockerEndpoint(dockerPath, discoveryEnv);
2108
2548
  const prefix = "unix://";
2109
- if (!endpoint.startsWith(prefix) || !isAbsolute2(endpoint.slice(prefix.length))) {
2549
+ if (!endpoint.startsWith(prefix) || !isAbsolute3(endpoint.slice(prefix.length))) {
2110
2550
  throw new Error(
2111
2551
  `Docker sandbox requires a local Unix-socket context; active endpoint is ${JSON.stringify(endpoint)}`
2112
2552
  );
@@ -2175,7 +2615,7 @@ async function inspectSandboxImage(dockerPath, image, processEnv) {
2175
2615
  return id;
2176
2616
  }
2177
2617
  function runDockerInspection(dockerPath, args, processEnv, subject) {
2178
- return new Promise((resolve5, reject) => {
2618
+ return new Promise((resolve6, reject) => {
2179
2619
  const child = spawn(dockerPath, args, {
2180
2620
  stdio: ["ignore", "pipe", "pipe"],
2181
2621
  windowsHide: true,
@@ -2192,7 +2632,7 @@ function runDockerInspection(dockerPath, args, processEnv, subject) {
2192
2632
  reject(error);
2193
2633
  return;
2194
2634
  }
2195
- resolve5(stdout.trim());
2635
+ resolve6(stdout.trim());
2196
2636
  };
2197
2637
  const timeoutHandle = setTimeout(() => {
2198
2638
  child.kill("SIGKILL");
@@ -2257,7 +2697,7 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
2257
2697
  const targets = /* @__PURE__ */ new Set([CONTAINER_WORKDIR, "/tmp"]);
2258
2698
  for (const mount of mounts) {
2259
2699
  validateMountArgument(mount.source);
2260
- if (mount.sourceRoot !== void 0 && (!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(","))) {
2261
2701
  throw new Error(
2262
2702
  `Docker sandbox read-only mount root is invalid: ${JSON.stringify(mount.sourceRoot)}`
2263
2703
  );
@@ -2277,8 +2717,8 @@ function validateReadOnlyMounts(mounts, workspaceSource) {
2277
2717
  }
2278
2718
  }
2279
2719
  function isWithin(root, candidate) {
2280
- const rel = relative2(root, candidate);
2281
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute2(rel);
2720
+ const rel = relative3(root, candidate);
2721
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute3(rel);
2282
2722
  }
2283
2723
  function validateHostIdentity(uid, gid) {
2284
2724
  if (!Number.isSafeInteger(uid) || uid < 0 || !Number.isSafeInteger(gid) || gid < 0) {
@@ -2315,7 +2755,7 @@ async function removeContainer(dockerPath, containerName, processEnv) {
2315
2755
  last = await runCleanupCommand(dockerPath, args, processEnv);
2316
2756
  if (last.ok) return;
2317
2757
  if (attempt < CLEANUP_ATTEMPTS - 1) {
2318
- await new Promise((resolve5) => setTimeout(resolve5, CLEANUP_RETRY_DELAY_MS));
2758
+ await new Promise((resolve6) => setTimeout(resolve6, CLEANUP_RETRY_DELAY_MS));
2319
2759
  }
2320
2760
  }
2321
2761
  if (last && !last.missing) {
@@ -2325,7 +2765,7 @@ async function removeContainer(dockerPath, containerName, processEnv) {
2325
2765
  }
2326
2766
  }
2327
2767
  function runCleanupCommand(dockerPath, args, processEnv) {
2328
- return new Promise((resolve5) => {
2768
+ return new Promise((resolve6) => {
2329
2769
  let done = false;
2330
2770
  let stderr = "";
2331
2771
  const child = spawn(dockerPath, args, {
@@ -2338,7 +2778,7 @@ function runCleanupCommand(dockerPath, args, processEnv) {
2338
2778
  done = true;
2339
2779
  clearTimeout(timeoutHandle);
2340
2780
  const detail3 = error || stderr.trim();
2341
- resolve5({
2781
+ resolve6({
2342
2782
  ok,
2343
2783
  missing: /no such container/i.test(detail3),
2344
2784
  error: detail3
@@ -2362,6 +2802,26 @@ var HOST_FILE_TOOL_NAMES = ["read", "edit", "write", "grep", "find", "ls"];
2362
2802
  var CONTAINER_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
2363
2803
  var SANDBOX_INPUTS_DIR = "/inputs";
2364
2804
  var SANDBOX_MEMORY_DIR = "/workspace/memory";
2805
+ function createProtectedSessionShellTools(cwd, runtime, approvalHost) {
2806
+ const base = createBashToolDefinition(cwd, {
2807
+ operations: createPreparedDockerBashOperations(runtime)
2808
+ });
2809
+ const dockerBash = defineTool2({
2810
+ ...base,
2811
+ label: "bash (protected Docker sandbox)",
2812
+ description: `${base.description} Runs in an ephemeral, network-disabled Docker container with the Team workspace mounted read/write plus only the preflighted read-only memory, skill, and attachment mounts. Host files, host tools, and host credentials are unavailable.`
2813
+ });
2814
+ return {
2815
+ config: {
2816
+ sandboxMode: "docker",
2817
+ approvalMode: "dangerous",
2818
+ sandboxImage: runtime.image,
2819
+ envAllowlist: []
2820
+ },
2821
+ hostToolNames: [],
2822
+ customBash: createApprovalGatedBashTool(dockerBash, approvalHost)
2823
+ };
2824
+ }
2365
2825
  function buildSandboxContainerEnv(env, allowlist) {
2366
2826
  return {
2367
2827
  ...buildScrubbedShellEnv(env, allowlist),
@@ -2390,9 +2850,9 @@ function createSessionShellTools(cwd, env, options = {}) {
2390
2850
  env: buildSandboxContainerEnv(env, config.envAllowlist),
2391
2851
  readOnlyMounts: [
2392
2852
  ...existsSync11(join15(cwd, "memory")) ? [safeReadOnlyMount(join15(cwd, "memory"), SANDBOX_MEMORY_DIR, cwd)] : [],
2393
- ...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname3(options.inputsDir))] : [],
2853
+ ...options.inputsDir ? [safeReadOnlyMount(options.inputsDir, SANDBOX_INPUTS_DIR, dirname4(options.inputsDir))] : [],
2394
2854
  ...(options.skillMounts ?? []).map(
2395
- (mount) => safeReadOnlyMount(mount.source, mount.target, dirname3(mount.source))
2855
+ (mount) => safeReadOnlyMount(mount.source, mount.target, dirname4(mount.source))
2396
2856
  )
2397
2857
  ]
2398
2858
  });
@@ -2406,85 +2866,726 @@ function createSessionShellTools(cwd, env, options = {}) {
2406
2866
  return { config, hostToolNames: [], customBash };
2407
2867
  }
2408
2868
  function safeReadOnlyMount(source, target, expectedRoot) {
2409
- const info = lstatSync2(source);
2869
+ const info = lstatSync3(source);
2410
2870
  if (info.isSymbolicLink() || !info.isDirectory()) {
2411
2871
  throw new Error(`Docker sandbox read-only mount must be a real directory: ${source}`);
2412
2872
  }
2413
- const sourceRoot = realpathSync2(expectedRoot);
2414
- const canonicalSource = realpathSync2(source);
2415
- const rel = relative3(sourceRoot, canonicalSource);
2416
- 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);
2417
2877
  if (!contained) {
2418
2878
  throw new Error(`Docker sandbox read-only mount escaped its configured root: ${source}`);
2419
2879
  }
2420
2880
  return { source: canonicalSource, target, sourceRoot };
2421
2881
  }
2422
2882
 
2423
- // ../daemon/src/runtime/pi/tools.ts
2424
- import { Type as Type2 } from "typebox";
2425
-
2426
- // ../daemon/src/runtime/tools/bootstrap.ts
2427
- import { existsSync as existsSync12, rmSync as rmSync11 } from "fs";
2428
- import { join as join16 } from "path";
2429
- function bootstrapTool(agentDir) {
2883
+ // ../daemon/src/runtime/worker/runtime.ts
2884
+ import {
2885
+ constants as constants4,
2886
+ existsSync as existsSync12,
2887
+ lstatSync as lstatSync4,
2888
+ mkdirSync as mkdirSync6,
2889
+ mkdtempSync as mkdtempSync3,
2890
+ realpathSync as realpathSync4,
2891
+ rmSync as rmSync11,
2892
+ statSync as statSync3
2893
+ } from "fs";
2894
+ import { access as access2 } from "fs/promises";
2895
+ import { tmpdir as tmpdir3 } from "os";
2896
+ import { isAbsolute as isAbsolute5, join as join16, relative as relative5, sep as sep5 } from "path";
2897
+ var REDACTED_VALUE = "[REDACTED]";
2898
+ var REASONING_LEVELS = /* @__PURE__ */ new Set([
2899
+ "off",
2900
+ "minimal",
2901
+ "low",
2902
+ "medium",
2903
+ "high",
2904
+ "xhigh"
2905
+ ]);
2906
+ var CONFIGURED_KEYS = /* @__PURE__ */ new Set([
2907
+ "kind",
2908
+ "agent",
2909
+ "message",
2910
+ "enabledProviders",
2911
+ "apiKey",
2912
+ "apiKeyRefreshEnabled",
2913
+ "browserEnabled",
2914
+ "mcpTools",
2915
+ "images",
2916
+ "turnId",
2917
+ "bashApprovalMode"
2918
+ ]);
2919
+ var CONFIGURED_REQUIRED_KEYS = /* @__PURE__ */ new Set([
2920
+ "kind",
2921
+ "agent",
2922
+ "message",
2923
+ "enabledProviders",
2924
+ "apiKeyRefreshEnabled",
2925
+ "turnId",
2926
+ "bashApprovalMode"
2927
+ ]);
2928
+ var PROTECTED_KEYS = /* @__PURE__ */ new Set([
2929
+ "kind",
2930
+ "agent",
2931
+ "message",
2932
+ "images",
2933
+ "turnId",
2934
+ "bashApprovalMode",
2935
+ "runtime",
2936
+ "paths",
2937
+ "docker",
2938
+ "webFetchEnabled",
2939
+ "apiKeyRefreshEnabled",
2940
+ "scratch"
2941
+ ]);
2942
+ var PROTECTED_REQUIRED_KEYS = new Set([...PROTECTED_KEYS].filter((key) => key !== "images"));
2943
+ var REVIEW_KEYS = /* @__PURE__ */ new Set([
2944
+ "kind",
2945
+ "agentId",
2946
+ "message",
2947
+ "turnId",
2948
+ "runtime",
2949
+ "review",
2950
+ "apiKeyRefreshEnabled",
2951
+ "scratch"
2952
+ ]);
2953
+ function minimalWorkerProcessEnv(scratch, platform = process.platform, hostEnv = process.env) {
2954
+ assertMinimalWorkerScratch(scratch);
2955
+ if (platform === "win32") {
2956
+ const systemRoot = requireCanonicalWindowsDirectory(
2957
+ hostEnv.SystemRoot ?? hostEnv.WINDIR,
2958
+ "SystemRoot"
2959
+ );
2960
+ const windir = requireCanonicalWindowsDirectory(hostEnv.WINDIR ?? systemRoot, "WINDIR");
2961
+ const comSpec = requireCanonicalWindowsFile(hostEnv.ComSpec, "ComSpec");
2962
+ const pathExt = hostEnv.PATHEXT;
2963
+ if (!pathExt || pathExt.includes("\0") || pathExt.includes("\n") || pathExt.includes("\r")) {
2964
+ throw new Error("minimal worker bootstrap requires a valid PATHEXT on Windows");
2965
+ }
2966
+ return {
2967
+ USERPROFILE: scratch.homeDir,
2968
+ TMPDIR: scratch.tempDir,
2969
+ TMP: scratch.tempDir,
2970
+ TEMP: scratch.tempDir,
2971
+ SystemRoot: systemRoot,
2972
+ WINDIR: windir,
2973
+ ComSpec: comSpec,
2974
+ PATHEXT: pathExt
2975
+ };
2976
+ }
2430
2977
  return {
2431
- def: {
2432
- name: "bootstrap_done",
2433
- description: "Call this once you have finished your bootstrap conversation (introduced yourself, learned the user, updated IDENTITY.md). Removes BOOTSTRAP.md so it does not appear in future sessions.",
2434
- parameters: { type: "object", properties: {} }
2435
- },
2436
- async invoke() {
2437
- const path = join16(agentDir, "BOOTSTRAP.md");
2438
- if (existsSync12(path)) {
2439
- rmSync11(path);
2440
- return "BOOTSTRAP.md removed. Bootstrap is complete.";
2978
+ HOME: scratch.homeDir,
2979
+ TMPDIR: scratch.tempDir,
2980
+ TMP: scratch.tempDir,
2981
+ TEMP: scratch.tempDir,
2982
+ LANG: "C",
2983
+ LC_ALL: "C"
2984
+ };
2985
+ }
2986
+ function validateMinimalWorkerProcessEnv(env, scratch, platform = process.platform) {
2987
+ const expected = minimalWorkerProcessEnv(scratch, platform, env);
2988
+ const actualKeys = Object.keys(env).sort();
2989
+ const expectedKeys = Object.keys(expected).sort();
2990
+ if (actualKeys.length !== expectedKeys.length || actualKeys.some((key, index) => key !== expectedKeys[index])) {
2991
+ throw new Error(`minimal worker environment has unexpected keys: ${actualKeys.join(", ")}`);
2992
+ }
2993
+ for (const key of expectedKeys) {
2994
+ if (env[key] !== expected[key]) {
2995
+ throw new Error(`minimal worker environment has an invalid ${key} value`);
2996
+ }
2997
+ }
2998
+ }
2999
+ function parseWorkerInput(value) {
3000
+ const input = objectRecord(value, "worker input");
3001
+ const kind = input.kind;
3002
+ if (kind === "configured_operator_http") {
3003
+ assertExactKeys(input, CONFIGURED_KEYS, "configured worker input", CONFIGURED_REQUIRED_KEYS);
3004
+ if (!isResolvedAgent(input.agent)) throw new Error("worker: configured input requires an agent");
3005
+ requireString(input.message, "message");
3006
+ if (!Array.isArray(input.enabledProviders) || !input.enabledProviders.every(isString)) {
3007
+ throw new Error("worker: configured input requires enabledProviders");
3008
+ }
3009
+ requireString(input.turnId, "turnId");
3010
+ if (input.bashApprovalMode !== "interactive" && input.bashApprovalMode !== "auto_deny") {
3011
+ throw new Error("worker: configured input requires a valid bashApprovalMode");
3012
+ }
3013
+ if (typeof input.apiKeyRefreshEnabled !== "boolean") {
3014
+ throw new Error("worker: configured input requires apiKeyRefreshEnabled");
3015
+ }
3016
+ if (input.apiKeyRefreshEnabled) {
3017
+ const providerName = input.agent.model.split(":", 1)[0] ?? "";
3018
+ if (providerName !== "openai-codex" || !nonEmptyString(input.apiKey)) {
3019
+ throw new Error(
3020
+ "worker: API key refresh requires an openai-codex turn with an initial token"
3021
+ );
2441
3022
  }
2442
- return "BOOTSTRAP.md was already removed.";
2443
3023
  }
2444
- };
3024
+ return value;
3025
+ }
3026
+ if (kind === "protected") {
3027
+ assertExactKeys(input, PROTECTED_KEYS, "protected worker input", PROTECTED_REQUIRED_KEYS);
3028
+ if (!isResolvedAgent(input.agent)) throw new Error("worker: protected input requires an agent");
3029
+ requireString(input.message, "message");
3030
+ requireString(input.turnId, "turnId");
3031
+ if (input.bashApprovalMode !== "auto_deny") {
3032
+ throw new Error("worker: protected input requires auto-deny shell approval");
3033
+ }
3034
+ if (input.webFetchEnabled !== true) {
3035
+ throw new Error("worker: protected input requires guarded web_fetch");
3036
+ }
3037
+ assertProtectedProviderRuntime(input.runtime);
3038
+ const runtime = input.runtime;
3039
+ const separator = input.agent.model.indexOf(":");
3040
+ if (runtime.providerName !== input.agent.model.slice(0, separator) || runtime.modelId !== input.agent.model.slice(separator + 1)) {
3041
+ throw new Error("worker: protected runtime does not match the resolved Agent model");
3042
+ }
3043
+ assertProtectedWorkerPaths(input.paths, input.agent);
3044
+ assertProtectedDockerRuntime(input.docker, input.paths);
3045
+ if (input.apiKeyRefreshEnabled !== true) {
3046
+ throw new Error("worker: protected input requires bound API key refresh");
3047
+ }
3048
+ assertMinimalWorkerScratch(input.scratch);
3049
+ return value;
3050
+ }
3051
+ if (kind === "restricted_review") {
3052
+ assertExactKeys(input, REVIEW_KEYS, "restricted review worker input", REVIEW_KEYS);
3053
+ requireString(input.agentId, "agentId");
3054
+ requireString(input.message, "message");
3055
+ requireString(input.turnId, "turnId");
3056
+ assertProtectedProviderRuntime(input.runtime);
3057
+ if (input.apiKeyRefreshEnabled !== true) {
3058
+ throw new Error("worker: restricted review requires bound API key refresh");
3059
+ }
3060
+ assertMinimalWorkerScratch(input.scratch);
3061
+ const review = objectRecord(input.review, "review metadata");
3062
+ assertExactKeys(
3063
+ review,
3064
+ /* @__PURE__ */ new Set(["reviewId", "evidence"]),
3065
+ "review metadata",
3066
+ /* @__PURE__ */ new Set(["reviewId", "evidence"])
3067
+ );
3068
+ requireString(review.reviewId, "reviewId");
3069
+ if (!Array.isArray(review.evidence)) throw new Error("worker: review evidence must be an array");
3070
+ for (const item of review.evidence) {
3071
+ const evidence = objectRecord(item, "review evidence");
3072
+ assertExactKeys(
3073
+ evidence,
3074
+ /* @__PURE__ */ new Set(["sessionId", "entryOrdinal"]),
3075
+ "review evidence",
3076
+ /* @__PURE__ */ new Set(["sessionId", "entryOrdinal"])
3077
+ );
3078
+ requireString(evidence.sessionId, "sessionId");
3079
+ if (!Number.isInteger(evidence.entryOrdinal) || Number(evidence.entryOrdinal) < 0) {
3080
+ throw new Error("worker: review evidence entryOrdinal must be a non-negative integer");
3081
+ }
3082
+ }
3083
+ return value;
3084
+ }
3085
+ throw new Error("worker: unknown or missing runtime kind");
3086
+ }
3087
+ function assertProtectedProviderRuntime(value) {
3088
+ const runtime = objectRecord(value, "protected provider runtime");
3089
+ assertExactKeys(
3090
+ runtime,
3091
+ /* @__PURE__ */ new Set([
3092
+ "providerName",
3093
+ "modelId",
3094
+ "reasoningLevel",
3095
+ "apiKey",
3096
+ "baseUrl",
3097
+ "credentialEnv",
3098
+ "credentialFile"
3099
+ ]),
3100
+ "protected provider runtime",
3101
+ /* @__PURE__ */ new Set(["providerName", "modelId", "reasoningLevel"])
3102
+ );
3103
+ if (!PROTECTED_PROVIDER_NAMES.includes(runtime.providerName)) {
3104
+ throw new Error("protected execution requires a registered provider");
3105
+ }
3106
+ if (!nonEmptyString(runtime.modelId))
3107
+ throw new Error("protected provider runtime requires a model id");
3108
+ if (!REASONING_LEVELS.has(runtime.reasoningLevel)) {
3109
+ throw new Error("protected provider runtime requires a valid reasoning level");
3110
+ }
3111
+ if (runtime.apiKey !== void 0 && !nonEmptyString(runtime.apiKey)) {
3112
+ throw new Error("protected provider runtime contains an invalid selected credential");
3113
+ }
3114
+ if (runtime.baseUrl !== void 0) requireString(runtime.baseUrl, "baseUrl");
3115
+ if (runtime.credentialEnv !== void 0) {
3116
+ if (!Array.isArray(runtime.credentialEnv)) {
3117
+ throw new Error("protected provider credential fields must be an array");
3118
+ }
3119
+ const allowed = new Set(PROVIDER_CREDENTIAL_ENV[String(runtime.providerName)] ?? []);
3120
+ const seen = /* @__PURE__ */ new Set();
3121
+ for (const rawField of runtime.credentialEnv) {
3122
+ const field = objectRecord(rawField, "protected provider credential field");
3123
+ assertExactKeys(
3124
+ field,
3125
+ /* @__PURE__ */ new Set(["name", "value"]),
3126
+ "protected provider credential field",
3127
+ /* @__PURE__ */ new Set(["name", "value"])
3128
+ );
3129
+ requireString(field.name, "credential field name");
3130
+ requireString(field.value, "credential field value");
3131
+ if (!allowed.has(field.name) || seen.has(field.name)) {
3132
+ throw new Error("protected provider runtime contains an unexpected credential field");
3133
+ }
3134
+ seen.add(field.name);
3135
+ }
3136
+ }
3137
+ if (runtime.credentialFile !== void 0) {
3138
+ const file = objectRecord(runtime.credentialFile, "protected provider credential file");
3139
+ assertExactKeys(
3140
+ file,
3141
+ /* @__PURE__ */ new Set(["envName", "content"]),
3142
+ "protected provider credential file",
3143
+ /* @__PURE__ */ new Set(["envName", "content"])
3144
+ );
3145
+ if (runtime.providerName !== "google-vertex" || file.envName !== "GOOGLE_APPLICATION_CREDENTIALS" || !nonEmptyString(file.content) || Buffer.byteLength(file.content, "utf8") > 64 * 1024) {
3146
+ throw new Error("protected provider runtime contains an invalid credential file");
3147
+ }
3148
+ }
3149
+ if (!nonEmptyString(runtime.apiKey) && !Array.isArray(runtime.credentialEnv) && runtime.credentialFile === void 0) {
3150
+ throw new Error("protected provider runtime requires selected credentials");
3151
+ }
2445
3152
  }
2446
-
2447
- // ../daemon/src/runtime/tools/browser.ts
2448
- var REF_DESC = 'Element ref from the latest browser_snapshot, e.g. "e5".';
2449
- function browserTools(host, agentId) {
2450
- const proxy = (name, action, description, properties, required = []) => ({
2451
- def: {
2452
- name,
2453
- description,
2454
- parameters: { type: "object", properties, required, additionalProperties: false }
2455
- },
2456
- invoke: (args) => host.invoke(agentId, action, args)
2457
- });
3153
+ function protectedRuntimeSecrets(runtime) {
2458
3154
  return [
2459
- proxy(
2460
- "browser_navigate",
2461
- "navigate",
2462
- "Open a URL in the browser and return an accessibility snapshot of the loaded page. Starts a persistent browser session that survives across turns (cookies/logins carry over).",
2463
- { url: { type: "string", description: "Absolute URL to navigate to." } },
2464
- ["url"]
2465
- ),
2466
- proxy(
2467
- "browser_snapshot",
2468
- "snapshot",
2469
- 'Capture the current page as an accessibility tree (YAML) with [ref=eN] element references. This is the primary way to "see" the page \u2014 prefer it over screenshots. Use the refs with browser_click/type/etc.',
2470
- {}
2471
- ),
2472
- proxy(
2473
- "browser_click",
2474
- "click",
2475
- "Click an element identified by its snapshot ref. Returns a fresh snapshot.",
2476
- { ref: { type: "string", description: REF_DESC } },
2477
- ["ref"]
2478
- ),
2479
- proxy(
2480
- "browser_type",
2481
- "type",
2482
- "Type text into an input/textarea identified by its ref. Set submit=true to press Enter afterwards. Returns a fresh snapshot.",
2483
- {
2484
- ref: { type: "string", description: REF_DESC },
2485
- text: { type: "string", description: "Text to type into the field." },
2486
- submit: { type: "boolean", description: "Press Enter after typing." }
2487
- },
3155
+ runtime.apiKey,
3156
+ ...(runtime.credentialEnv ?? []).map((field) => field.value),
3157
+ runtime.credentialFile?.content
3158
+ ].filter((value) => Boolean(value));
3159
+ }
3160
+ function redactExactValues(text, values) {
3161
+ let redacted = text;
3162
+ for (const value of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) {
3163
+ redacted = redacted.replaceAll(value, REDACTED_VALUE);
3164
+ }
3165
+ return redacted;
3166
+ }
3167
+ function redactJsonValue(value, secrets) {
3168
+ if (!secrets || Array.isArray(secrets) && secrets.length === 0) return value;
3169
+ const values = typeof secrets === "string" ? [secrets] : secrets;
3170
+ if (typeof value === "string") return redactExactValues(value, values);
3171
+ if (Array.isArray(value)) return value.map((item) => redactJsonValue(item, values));
3172
+ if (value && typeof value === "object") {
3173
+ return Object.fromEntries(
3174
+ Object.entries(value).map(([key, item]) => [key, redactJsonValue(item, values)])
3175
+ );
3176
+ }
3177
+ return value;
3178
+ }
3179
+ function assertProtectedWorkerPaths(value, agent) {
3180
+ const paths = objectRecord(value, "protected worker paths");
3181
+ assertExactKeys(
3182
+ paths,
3183
+ /* @__PURE__ */ new Set([
3184
+ "agentDir",
3185
+ "teamDir",
3186
+ "memoryDir",
3187
+ "sessionDir",
3188
+ "uploadsDir",
3189
+ "skills",
3190
+ "homeDocuments"
3191
+ ]),
3192
+ "protected worker paths",
3193
+ /* @__PURE__ */ new Set(["agentDir", "teamDir", "memoryDir", "sessionDir", "skills", "homeDocuments"])
3194
+ );
3195
+ for (const key of ["agentDir", "teamDir", "memoryDir", "sessionDir"]) {
3196
+ requireAbsolutePath(paths[key], key);
3197
+ }
3198
+ if (paths.uploadsDir !== void 0) requireAbsolutePath(paths.uploadsDir, "uploadsDir");
3199
+ if (paths.agentDir !== agent.agent.dir || paths.teamDir !== agent.team.path) {
3200
+ throw new Error("worker: protected paths do not match the resolved Agent");
3201
+ }
3202
+ const canonicalAgentDir = requireCanonicalRealDirectory(
3203
+ paths.agentDir,
3204
+ "Agent directory"
3205
+ );
3206
+ const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir, "Team directory");
3207
+ requireCanonicalContainedDirectory(
3208
+ canonicalAgentDir,
3209
+ paths.sessionDir,
3210
+ "session directory"
3211
+ );
3212
+ if (paths.uploadsDir !== void 0) {
3213
+ requireCanonicalContainedDirectory(
3214
+ canonicalAgentDir,
3215
+ paths.uploadsDir,
3216
+ "uploads directory"
3217
+ );
3218
+ }
3219
+ requireCanonicalContainedDirectory(
3220
+ canonicalTeamDir,
3221
+ paths.memoryDir,
3222
+ "memory directory"
3223
+ );
3224
+ if (!Array.isArray(paths.skills)) throw new Error("worker: protected skills must be an array");
3225
+ for (const item of paths.skills) {
3226
+ const skill = objectRecord(item, "protected skill");
3227
+ assertExactKeys(
3228
+ skill,
3229
+ /* @__PURE__ */ new Set(["name", "description", "body", "hostDir", "sandboxDir"]),
3230
+ "protected skill"
3231
+ );
3232
+ requireString(skill.name, "skill name");
3233
+ requireString(skill.description, "skill description");
3234
+ requireString(skill.body, "skill body");
3235
+ requireAbsolutePath(skill.hostDir, "skill hostDir");
3236
+ requireCanonicalRealDirectory(skill.hostDir, "skill hostDir");
3237
+ if (typeof skill.sandboxDir !== "string" || !skill.sandboxDir.startsWith("/skills/")) {
3238
+ throw new Error("worker: protected skill sandbox path must be under /skills");
3239
+ }
3240
+ }
3241
+ const homeDocuments = objectRecord(paths.homeDocuments, "protected home documents");
3242
+ const homeDocumentKeys = /* @__PURE__ */ new Set([
3243
+ "AGENTS.md",
3244
+ "SOUL.md",
3245
+ "TOOLS.md",
3246
+ "IDENTITY.md",
3247
+ "BOOTSTRAP.md"
3248
+ ]);
3249
+ assertExactKeys(homeDocuments, homeDocumentKeys, "protected home documents", homeDocumentKeys);
3250
+ for (const [name, contents] of Object.entries(homeDocuments)) {
3251
+ if (contents !== null && typeof contents !== "string") {
3252
+ throw new Error(`worker: protected home document ${name} must be text or null`);
3253
+ }
3254
+ }
3255
+ }
3256
+ function assertProtectedDockerRuntime(value, paths) {
3257
+ const runtime = objectRecord(value, "protected Docker runtime");
3258
+ assertExactKeys(
3259
+ runtime,
3260
+ /* @__PURE__ */ new Set([
3261
+ "dockerPath",
3262
+ "executableIdentity",
3263
+ "endpoint",
3264
+ "image",
3265
+ "imageId",
3266
+ "uid",
3267
+ "gid",
3268
+ "workspace",
3269
+ "readOnlyMounts",
3270
+ "containerEnv"
3271
+ ]),
3272
+ "protected Docker runtime",
3273
+ /* @__PURE__ */ new Set([
3274
+ "dockerPath",
3275
+ "executableIdentity",
3276
+ "endpoint",
3277
+ "image",
3278
+ "imageId",
3279
+ "uid",
3280
+ "gid",
3281
+ "workspace",
3282
+ "readOnlyMounts",
3283
+ "containerEnv"
3284
+ ])
3285
+ );
3286
+ requireAbsolutePath(runtime.dockerPath, "Docker executable");
3287
+ const executableIdentity = objectRecord(
3288
+ runtime.executableIdentity,
3289
+ "protected Docker executable identity"
3290
+ );
3291
+ const executableIdentityKeys = /* @__PURE__ */ new Set([
3292
+ "device",
3293
+ "inode",
3294
+ "mode",
3295
+ "size",
3296
+ "modifiedTimeNs",
3297
+ "changedTimeNs"
3298
+ ]);
3299
+ assertExactKeys(
3300
+ executableIdentity,
3301
+ executableIdentityKeys,
3302
+ "protected Docker executable identity",
3303
+ executableIdentityKeys
3304
+ );
3305
+ for (const [name, field] of Object.entries(executableIdentity)) {
3306
+ if (typeof field !== "string" || !/^\d+$/.test(field)) {
3307
+ throw new Error(`worker: protected Docker executable identity has invalid ${name}`);
3308
+ }
3309
+ }
3310
+ if (typeof runtime.endpoint !== "string" || !runtime.endpoint.startsWith("unix://") || !isAbsolute5(runtime.endpoint.slice("unix://".length)) || containsUnsafeDockerArgument(runtime.endpoint)) {
3311
+ throw new Error("worker: protected Docker endpoint must be a local Unix socket");
3312
+ }
3313
+ if (!nonEmptyString(runtime.image) || runtime.image.startsWith("-") || runtime.image.includes("\0") || runtime.image.includes("\n") || runtime.image.includes("\r")) {
3314
+ throw new Error("worker: protected Docker image is required");
3315
+ }
3316
+ if (typeof runtime.imageId !== "string" || !/^sha256:[a-fA-F0-9]{64}$/.test(runtime.imageId)) {
3317
+ throw new Error("worker: protected Docker runtime requires an immutable image id");
3318
+ }
3319
+ if (!Number.isSafeInteger(runtime.uid) || !Number.isSafeInteger(runtime.gid)) {
3320
+ throw new Error("worker: protected Docker runtime requires a numeric host identity");
3321
+ }
3322
+ if (Number(runtime.uid) < 0 || Number(runtime.gid) < 0) {
3323
+ throw new Error("worker: protected Docker runtime requires a non-negative host identity");
3324
+ }
3325
+ const workspace = objectRecord(runtime.workspace, "protected Docker workspace");
3326
+ assertExactKeys(
3327
+ workspace,
3328
+ /* @__PURE__ */ new Set(["source", "target", "sourceRoot"]),
3329
+ "protected Docker workspace",
3330
+ /* @__PURE__ */ new Set(["source", "target", "sourceRoot"])
3331
+ );
3332
+ requireAbsolutePath(workspace.source, "Docker workspace source");
3333
+ requireAbsolutePath(workspace.sourceRoot, "Docker workspace root");
3334
+ assertSafeDockerHostPath(workspace.source, "Docker workspace source");
3335
+ assertSafeDockerHostPath(workspace.sourceRoot, "Docker workspace root");
3336
+ if (workspace.target !== "/workspace") {
3337
+ throw new Error("worker: protected Docker workspace target must be /workspace");
3338
+ }
3339
+ const canonicalTeamDir = resolveCanonicalDirectory(paths.teamDir, "Team directory");
3340
+ if (workspace.source !== canonicalTeamDir || workspace.sourceRoot !== canonicalTeamDir) {
3341
+ throw new Error("worker: protected Docker workspace does not match the canonical Team root");
3342
+ }
3343
+ if (!Array.isArray(runtime.readOnlyMounts)) {
3344
+ throw new Error("worker: protected Docker read-only mounts must be an array");
3345
+ }
3346
+ const mountTargets = /* @__PURE__ */ new Set(["/workspace", "/tmp"]);
3347
+ for (const item of runtime.readOnlyMounts) {
3348
+ const mount = objectRecord(item, "protected Docker read-only mount");
3349
+ assertExactKeys(
3350
+ mount,
3351
+ /* @__PURE__ */ new Set(["source", "target", "sourceRoot"]),
3352
+ "protected Docker read-only mount",
3353
+ /* @__PURE__ */ new Set(["source", "target", "sourceRoot"])
3354
+ );
3355
+ requireAbsolutePath(mount.source, "Docker read-only mount source");
3356
+ requireAbsolutePath(mount.sourceRoot, "Docker read-only mount root");
3357
+ assertSafeDockerHostPath(mount.source, "Docker read-only mount source");
3358
+ assertSafeDockerHostPath(mount.sourceRoot, "Docker read-only mount root");
3359
+ if (typeof mount.target !== "string" || !mount.target.startsWith("/") || containsUnsafeDockerArgument(mount.target) || mount.target.split("/").includes("..") || !mount.target.startsWith("/skills/") && mount.target !== "/inputs" && mount.target !== "/workspace/memory") {
3360
+ throw new Error("worker: protected Docker read-only mount target is not permitted");
3361
+ }
3362
+ if (!lexicallyWithin(mount.sourceRoot, mount.source)) {
3363
+ throw new Error("worker: protected Docker read-only mount escaped its root");
3364
+ }
3365
+ if (mountTargets.has(mount.target)) {
3366
+ throw new Error("worker: protected Docker mount target is duplicated");
3367
+ }
3368
+ mountTargets.add(mount.target);
3369
+ }
3370
+ assertExactProtectedDockerMounts(
3371
+ runtime.readOnlyMounts,
3372
+ paths
3373
+ );
3374
+ const containerEnv = objectRecord(runtime.containerEnv, "protected Docker container environment");
3375
+ const expectedContainerEnv = {
3376
+ PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
3377
+ HOME: "/tmp",
3378
+ LANG: "C.UTF-8",
3379
+ LC_ALL: "C.UTF-8",
3380
+ SHELL: "/bin/bash",
3381
+ TMPDIR: "/tmp"
3382
+ };
3383
+ const expectedKeys = new Set(Object.keys(expectedContainerEnv));
3384
+ assertExactKeys(
3385
+ containerEnv,
3386
+ expectedKeys,
3387
+ "protected Docker container environment",
3388
+ expectedKeys
3389
+ );
3390
+ for (const [key, expected] of Object.entries(expectedContainerEnv)) {
3391
+ if (containerEnv[key] !== expected) {
3392
+ throw new Error(`worker: protected Docker container environment has invalid ${key}`);
3393
+ }
3394
+ }
3395
+ }
3396
+ function assertExactProtectedDockerMounts(mounts, paths) {
3397
+ const expected = new Map([
3398
+ ["/workspace/memory", paths.memoryDir],
3399
+ ...paths.uploadsDir ? [["/inputs", paths.uploadsDir]] : [],
3400
+ ...paths.skills.map((skill) => [skill.sandboxDir, skill.hostDir])
3401
+ ]);
3402
+ if (mounts.length !== expected.size) {
3403
+ throw new Error("worker: protected Docker read-only mount set is incomplete");
3404
+ }
3405
+ for (const mount of mounts) {
3406
+ const source = expected.get(mount.target);
3407
+ if (!source || mount.source !== source) {
3408
+ throw new Error("worker: protected Docker read-only mount set does not match protected paths");
3409
+ }
3410
+ }
3411
+ }
3412
+ function assertSafeDockerHostPath(value, name) {
3413
+ if (typeof value !== "string" || containsUnsafeDockerArgument(value)) {
3414
+ throw new Error(`worker: ${name} cannot be represented safely`);
3415
+ }
3416
+ }
3417
+ function containsUnsafeDockerArgument(value) {
3418
+ return value.includes("\0") || value.includes(",") || value.includes("\n") || value.includes("\r");
3419
+ }
3420
+ function requireCanonicalRealDirectory(value, name) {
3421
+ if (typeof value !== "string") throw new Error(`worker: ${name} must be a path`);
3422
+ try {
3423
+ const entry = lstatSync4(value);
3424
+ if (entry.isSymbolicLink() || !entry.isDirectory()) {
3425
+ throw new Error("unsafe entry");
3426
+ }
3427
+ const canonical = realpathSync4(value);
3428
+ if (canonical !== value) throw new Error("non-canonical entry");
3429
+ return canonical;
3430
+ } catch {
3431
+ throw new Error(`worker: ${name} must be a real directory`);
3432
+ }
3433
+ }
3434
+ function resolveCanonicalDirectory(value, name) {
3435
+ try {
3436
+ const entry = statSync3(value);
3437
+ if (!entry.isDirectory()) throw new Error("not a directory");
3438
+ return realpathSync4(value);
3439
+ } catch {
3440
+ throw new Error(`worker: ${name} is unavailable or unsafe`);
3441
+ }
3442
+ }
3443
+ function requireCanonicalContainedDirectory(root, value, name) {
3444
+ const canonical = requireCanonicalRealDirectory(value, name);
3445
+ if (!lexicallyWithin(root, canonical)) throw new Error(`worker: ${name} escaped its root`);
3446
+ return canonical;
3447
+ }
3448
+ function assertMinimalWorkerScratch(value) {
3449
+ const scratch = objectRecord(value, "minimal worker scratch");
3450
+ assertExactKeys(
3451
+ scratch,
3452
+ /* @__PURE__ */ new Set(["root", "homeDir", "tempDir", "piAgentDir", "reviewCwd", "reviewSessionDir"]),
3453
+ "minimal worker scratch",
3454
+ /* @__PURE__ */ new Set(["root", "homeDir", "tempDir", "piAgentDir", "reviewCwd", "reviewSessionDir"])
3455
+ );
3456
+ requireAbsolutePath(scratch.root, "scratch root");
3457
+ for (const key of [
3458
+ "homeDir",
3459
+ "tempDir",
3460
+ "piAgentDir",
3461
+ "reviewCwd",
3462
+ "reviewSessionDir"
3463
+ ]) {
3464
+ requireAbsolutePath(scratch[key], key);
3465
+ if (!lexicallyWithin(scratch.root, scratch[key])) {
3466
+ throw new Error(`worker: ${key} escaped the scratch root`);
3467
+ }
3468
+ }
3469
+ }
3470
+ function requireCanonicalWindowsDirectory(value, name) {
3471
+ if (!value || !isAbsolute5(value) || !existsSync12(value) || !statSync3(value).isDirectory()) {
3472
+ throw new Error(`minimal worker bootstrap requires a valid ${name} directory`);
3473
+ }
3474
+ return realpathSync4(value);
3475
+ }
3476
+ function requireCanonicalWindowsFile(value, name) {
3477
+ if (!value || !isAbsolute5(value) || !existsSync12(value) || !statSync3(value).isFile()) {
3478
+ throw new Error(`minimal worker bootstrap requires a valid ${name} executable`);
3479
+ }
3480
+ return realpathSync4(value);
3481
+ }
3482
+ function objectRecord(value, label) {
3483
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3484
+ throw new Error(`worker: ${label} must be an object`);
3485
+ }
3486
+ return value;
3487
+ }
3488
+ function assertExactKeys(value, allowed, label, required = allowed) {
3489
+ const unexpected = Object.keys(value).filter((key) => !allowed.has(key));
3490
+ if (unexpected.length > 0) {
3491
+ throw new Error(`worker: ${label} contains unexpected fields: ${unexpected.sort().join(", ")}`);
3492
+ }
3493
+ const missing = [...required].filter((key) => !(key in value));
3494
+ if (missing.length > 0) {
3495
+ throw new Error(`worker: ${label} is missing required fields: ${missing.sort().join(", ")}`);
3496
+ }
3497
+ }
3498
+ function isResolvedAgent(value) {
3499
+ if (!value || typeof value !== "object") return false;
3500
+ const resolved = value;
3501
+ return Boolean(
3502
+ resolved.agent && typeof resolved.agent.id === "string" && typeof resolved.agent.dir === "string" && resolved.team && typeof resolved.team.id === "string" && typeof resolved.team.path === "string" && typeof resolved.model === "string"
3503
+ );
3504
+ }
3505
+ function requireString(value, name) {
3506
+ if (typeof value !== "string") throw new Error(`worker: ${name} must be a string`);
3507
+ }
3508
+ function nonEmptyString(value) {
3509
+ return typeof value === "string" && value.trim().length > 0;
3510
+ }
3511
+ function isString(value) {
3512
+ return typeof value === "string";
3513
+ }
3514
+ function requireAbsolutePath(value, name) {
3515
+ if (typeof value !== "string" || !isAbsolute5(value) || value.includes("\0")) {
3516
+ throw new Error(`worker: ${name} must be an absolute path`);
3517
+ }
3518
+ }
3519
+ function lexicallyWithin(root, candidate) {
3520
+ const rel = relative5(root, candidate);
3521
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute5(rel);
3522
+ }
3523
+
3524
+ // ../daemon/src/runtime/pi/tools.ts
3525
+ import { Type as Type2 } from "typebox";
3526
+
3527
+ // ../daemon/src/runtime/tools/bootstrap.ts
3528
+ import { existsSync as existsSync13, rmSync as rmSync12 } from "fs";
3529
+ import { join as join17 } from "path";
3530
+ function bootstrapTool(agentDir) {
3531
+ return {
3532
+ def: {
3533
+ name: "bootstrap_done",
3534
+ description: "Call this once you have finished your bootstrap conversation (introduced yourself, learned the user, updated IDENTITY.md). Removes BOOTSTRAP.md so it does not appear in future sessions.",
3535
+ parameters: { type: "object", properties: {} }
3536
+ },
3537
+ async invoke() {
3538
+ const path = join17(agentDir, "BOOTSTRAP.md");
3539
+ if (existsSync13(path)) {
3540
+ rmSync12(path);
3541
+ return "BOOTSTRAP.md removed. Bootstrap is complete.";
3542
+ }
3543
+ return "BOOTSTRAP.md was already removed.";
3544
+ }
3545
+ };
3546
+ }
3547
+
3548
+ // ../daemon/src/runtime/tools/browser.ts
3549
+ var REF_DESC = 'Element ref from the latest browser_snapshot, e.g. "e5".';
3550
+ function browserTools(host, agentId) {
3551
+ const proxy = (name, action, description, properties, required = []) => ({
3552
+ def: {
3553
+ name,
3554
+ description,
3555
+ parameters: { type: "object", properties, required, additionalProperties: false }
3556
+ },
3557
+ invoke: (args) => host.invoke(agentId, action, args)
3558
+ });
3559
+ return [
3560
+ proxy(
3561
+ "browser_navigate",
3562
+ "navigate",
3563
+ "Open a URL in the browser and return an accessibility snapshot of the loaded page. Starts a persistent browser session that survives across turns (cookies/logins carry over).",
3564
+ { url: { type: "string", description: "Absolute URL to navigate to." } },
3565
+ ["url"]
3566
+ ),
3567
+ proxy(
3568
+ "browser_snapshot",
3569
+ "snapshot",
3570
+ 'Capture the current page as an accessibility tree (YAML) with [ref=eN] element references. This is the primary way to "see" the page \u2014 prefer it over screenshots. Use the refs with browser_click/type/etc.',
3571
+ {}
3572
+ ),
3573
+ proxy(
3574
+ "browser_click",
3575
+ "click",
3576
+ "Click an element identified by its snapshot ref. Returns a fresh snapshot.",
3577
+ { ref: { type: "string", description: REF_DESC } },
3578
+ ["ref"]
3579
+ ),
3580
+ proxy(
3581
+ "browser_type",
3582
+ "type",
3583
+ "Type text into an input/textarea identified by its ref. Set submit=true to press Enter afterwards. Returns a fresh snapshot.",
3584
+ {
3585
+ ref: { type: "string", description: REF_DESC },
3586
+ text: { type: "string", description: "Text to type into the field." },
3587
+ submit: { type: "boolean", description: "Press Enter after typing." }
3588
+ },
2488
3589
  ["ref", "text"]
2489
3590
  ),
2490
3591
  proxy(
@@ -2575,15 +3676,15 @@ function browserTools(host, agentId) {
2575
3676
 
2576
3677
  // ../daemon/src/runtime/tools/deliver-file.ts
2577
3678
  import {
2578
- closeSync as closeSync3,
2579
- constants as constants3,
2580
- fstatSync as fstatSync2,
2581
- openSync as openSync3,
3679
+ closeSync as closeSync4,
3680
+ constants as constants5,
3681
+ fstatSync as fstatSync3,
3682
+ openSync as openSync4,
2582
3683
  readFileSync as readFileSync10,
2583
- realpathSync as realpathSync3,
2584
- statSync as statSync3
3684
+ realpathSync as realpathSync5,
3685
+ statSync as statSync4
2585
3686
  } from "fs";
2586
- 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";
2587
3688
  var MAX_DELIVER_BYTES = 25 * 1024 * 1024;
2588
3689
  var MIME = {
2589
3690
  ".pdf": "application/pdf",
@@ -2600,14 +3701,14 @@ var MIME = {
2600
3701
  ".webp": "image/webp"
2601
3702
  };
2602
3703
  function isWithin2(root, candidate) {
2603
- const rel = relative4(root, candidate);
2604
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep5}`) && !isAbsolute4(rel);
3704
+ const rel = relative6(root, candidate);
3705
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep6}`) && !isAbsolute6(rel);
2605
3706
  }
2606
3707
  function deliverFileTool(cwd, sink) {
2607
- const workspacePath = resolve4(cwd);
3708
+ const workspacePath = resolve5(cwd);
2608
3709
  let workspaceRealPath = null;
2609
3710
  try {
2610
- workspaceRealPath = realpathSync3(workspacePath);
3711
+ workspaceRealPath = realpathSync5(workspacePath);
2611
3712
  } catch {
2612
3713
  }
2613
3714
  return {
@@ -2629,10 +3730,10 @@ function deliverFileTool(cwd, sink) {
2629
3730
  async invoke(args) {
2630
3731
  const p = String(args.path ?? "");
2631
3732
  if (!p) throw new Error("deliver_file: path is required");
2632
- if (isAbsolute4(p)) {
3733
+ if (isAbsolute6(p)) {
2633
3734
  throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
2634
3735
  }
2635
- const abs = resolve4(workspacePath, p);
3736
+ const abs = resolve5(workspacePath, p);
2636
3737
  if (!isWithin2(workspacePath, abs)) {
2637
3738
  throw new Error(`deliver_file: path must stay within the workspace: ${p}`);
2638
3739
  }
@@ -2642,8 +3743,8 @@ function deliverFileTool(cwd, sink) {
2642
3743
  let real;
2643
3744
  let expected;
2644
3745
  try {
2645
- real = realpathSync3(abs);
2646
- expected = statSync3(real);
3746
+ real = realpathSync5(abs);
3747
+ expected = statSync4(real);
2647
3748
  } catch {
2648
3749
  throw new Error(`deliver_file: no such file: ${p}`);
2649
3750
  }
@@ -2652,13 +3753,13 @@ function deliverFileTool(cwd, sink) {
2652
3753
  }
2653
3754
  let fd;
2654
3755
  try {
2655
- fd = openSync3(real, constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK);
3756
+ fd = openSync4(real, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
2656
3757
  } catch {
2657
3758
  throw new Error(`deliver_file: no such file: ${p}`);
2658
3759
  }
2659
3760
  let data;
2660
3761
  try {
2661
- const stat2 = fstatSync2(fd);
3762
+ const stat2 = fstatSync3(fd);
2662
3763
  if (!stat2.isFile()) {
2663
3764
  throw new Error(`deliver_file: not a regular file: ${p}`);
2664
3765
  }
@@ -2667,7 +3768,7 @@ function deliverFileTool(cwd, sink) {
2667
3768
  }
2668
3769
  let confirmedReal;
2669
3770
  try {
2670
- confirmedReal = realpathSync3(abs);
3771
+ confirmedReal = realpathSync5(abs);
2671
3772
  } catch {
2672
3773
  throw new Error(`deliver_file: path changed during validation: ${p}`);
2673
3774
  }
@@ -2681,7 +3782,7 @@ function deliverFileTool(cwd, sink) {
2681
3782
  }
2682
3783
  data = readFileSync10(fd).toString("base64");
2683
3784
  } finally {
2684
- closeSync3(fd);
3785
+ closeSync4(fd);
2685
3786
  }
2686
3787
  const name = basename2(abs);
2687
3788
  const mimeType = MIME[extname(abs).toLowerCase()] ?? "application/octet-stream";
@@ -2692,8 +3793,16 @@ function deliverFileTool(cwd, sink) {
2692
3793
  }
2693
3794
 
2694
3795
  // ../daemon/src/runtime/tools/home.ts
2695
- import { readdirSync as readdirSync7, readFileSync as readFileSync11, statSync as statSync4, writeFileSync as writeFileSync8 } from "fs";
2696
- 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";
2697
3806
  var HOME_FILES_READABLE = [
2698
3807
  "IDENTITY.md",
2699
3808
  "SOUL.md",
@@ -2703,6 +3812,7 @@ var HOME_FILES_READABLE = [
2703
3812
  ];
2704
3813
  var HOME_FILES_WRITABLE = ["IDENTITY.md", "SOUL.md", "AGENTS.md", "TOOLS.md"];
2705
3814
  function homeTools(agentDir) {
3815
+ const homeRoot = resolvePrivateHomeRoot(agentDir);
2706
3816
  return [
2707
3817
  {
2708
3818
  def: {
@@ -2723,9 +3833,9 @@ function homeTools(agentDir) {
2723
3833
  `home_read: "file" must be one of ${HOME_FILES_READABLE.join(", ")}; got "${file}"`
2724
3834
  );
2725
3835
  }
2726
- const path = join17(agentDir, file);
3836
+ const path = privateHomeFile(homeRoot, file);
2727
3837
  try {
2728
- return readFileSync11(path, "utf8");
3838
+ return readRegularFileNoFollow(path);
2729
3839
  } catch (err) {
2730
3840
  const msg = err instanceof Error ? err.message : String(err);
2731
3841
  throw new Error(`home_read: could not read ${file}: ${msg}`);
@@ -2753,8 +3863,23 @@ function homeTools(agentDir) {
2753
3863
  );
2754
3864
  }
2755
3865
  const content = typeof args.content === "string" ? args.content : "";
2756
- const path = join17(agentDir, file);
2757
- writeFileSync8(path, content, "utf8");
3866
+ const path = privateHomeFile(homeRoot, file);
3867
+ let fd;
3868
+ try {
3869
+ rejectExistingSymlinkOrNonFile(path);
3870
+ fd = openSync5(
3871
+ path,
3872
+ constants6.O_WRONLY | constants6.O_CREAT | constants6.O_TRUNC | noFollowFlag(),
3873
+ 384
3874
+ );
3875
+ if (!fstatSync4(fd).isFile()) throw new Error("target is not a regular file");
3876
+ writeFileSync8(fd, content, "utf8");
3877
+ } catch (err) {
3878
+ const msg = err instanceof Error ? err.message : String(err);
3879
+ throw new Error(`home_write: could not write ${file}: ${msg}`);
3880
+ } finally {
3881
+ if (fd !== void 0) closeSync5(fd);
3882
+ }
2758
3883
  return `wrote ${file} (${Buffer.byteLength(content, "utf8")} bytes)`;
2759
3884
  }
2760
3885
  },
@@ -2767,9 +3892,10 @@ function homeTools(agentDir) {
2767
3892
  async invoke() {
2768
3893
  const entries = [];
2769
3894
  for (const file of HOME_FILES_READABLE) {
2770
- const path = join17(agentDir, file);
3895
+ const path = privateHomeFile(homeRoot, file);
2771
3896
  try {
2772
- const s = statSync4(path);
3897
+ const s = lstatSync5(path);
3898
+ if (s.isSymbolicLink() || !s.isFile()) continue;
2773
3899
  entries.push(`${file} (${s.size}b)`);
2774
3900
  } catch {
2775
3901
  }
@@ -2777,7 +3903,7 @@ function homeTools(agentDir) {
2777
3903
  if (entries.length === 0) {
2778
3904
  const dirEntries = (() => {
2779
3905
  try {
2780
- return readdirSync7(agentDir);
3906
+ return readdirSync7(homeRoot);
2781
3907
  } catch {
2782
3908
  return [];
2783
3909
  }
@@ -2789,6 +3915,30 @@ function homeTools(agentDir) {
2789
3915
  }
2790
3916
  ];
2791
3917
  }
3918
+ function resolvePrivateHomeRoot(agentDir) {
3919
+ try {
3920
+ return resolveRealDirectory(agentDir);
3921
+ } catch {
3922
+ throw new Error("home tools require a real private Agent directory");
3923
+ }
3924
+ }
3925
+ function privateHomeFile(homeRoot, file) {
3926
+ return join18(homeRoot, file);
3927
+ }
3928
+ function rejectSymlinkOrNonFile(path) {
3929
+ const entry = lstatSync5(path);
3930
+ if (entry.isSymbolicLink()) throw new Error("symbolic links are not allowed");
3931
+ if (!entry.isFile()) throw new Error("target is not a regular file");
3932
+ }
3933
+ function rejectExistingSymlinkOrNonFile(path) {
3934
+ try {
3935
+ rejectSymlinkOrNonFile(path);
3936
+ } catch (error) {
3937
+ const code = error.code;
3938
+ if (code === "ENOENT") return;
3939
+ throw error;
3940
+ }
3941
+ }
2792
3942
 
2793
3943
  // ../daemon/src/runtime/tools/mcp.ts
2794
3944
  function mcpProxyTools(host, tools) {
@@ -3161,6 +4311,7 @@ function extractReadable(html, url, mode) {
3161
4311
  // ../daemon/src/runtime/tools/web-ssrf.ts
3162
4312
  import { lookup as dnsLookupCb } from "dns";
3163
4313
  import { lookup as dnsLookup } from "dns/promises";
4314
+ import { isIP } from "net";
3164
4315
  import { Agent, fetch as undiciFetch } from "undici";
3165
4316
  var SsrFBlockedError = class extends Error {
3166
4317
  constructor(message) {
@@ -3169,7 +4320,6 @@ var SsrFBlockedError = class extends Error {
3169
4320
  }
3170
4321
  };
3171
4322
  var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "metadata.google.internal"]);
3172
- var PRIVATE_IPV6_PREFIXES = ["fe80:", "fec0:", "fc", "fd"];
3173
4323
  function normalizeHostname(hostname) {
3174
4324
  let h = hostname.trim().toLowerCase().replace(/\.$/, "");
3175
4325
  if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
@@ -3177,11 +4327,32 @@ function normalizeHostname(hostname) {
3177
4327
  }
3178
4328
  function parseIpv4(address) {
3179
4329
  const parts = address.split(".");
3180
- if (parts.length !== 4) return null;
3181
- const nums = parts.map((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);
3182
4332
  if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return null;
3183
4333
  return nums;
3184
4334
  }
4335
+ function parseIpv6(address) {
4336
+ const zoneIndex = address.indexOf("%");
4337
+ let source = zoneIndex === -1 ? address : address.slice(0, zoneIndex);
4338
+ if (isIP(source) !== 6) return null;
4339
+ if (source.includes(".")) {
4340
+ const separator = source.lastIndexOf(":");
4341
+ const ipv4 = parseIpv4(source.slice(separator + 1));
4342
+ if (separator === -1 || !ipv4) return null;
4343
+ const [a, b, c, d] = ipv4;
4344
+ source = `${source.slice(0, separator)}:${(a << 8 | b).toString(16)}:${(c << 8 | d).toString(16)}`;
4345
+ }
4346
+ const halves = source.split("::");
4347
+ if (halves.length > 2) return null;
4348
+ const left = halves[0] ? halves[0].split(":") : [];
4349
+ const right = halves[1] ? halves[1].split(":") : [];
4350
+ const omitted = 8 - left.length - right.length;
4351
+ if (halves.length === 1 && omitted !== 0 || halves.length === 2 && omitted < 1) return null;
4352
+ return [...left, ...Array.from({ length: omitted }, () => "0"), ...right].map(
4353
+ (word) => Number.parseInt(word, 16)
4354
+ );
4355
+ }
3185
4356
  function isPrivateIpv4(parts) {
3186
4357
  const [a, b] = parts;
3187
4358
  if (a === 0 || a === 10 || a === 127) return true;
@@ -3191,21 +4362,38 @@ function isPrivateIpv4(parts) {
3191
4362
  if (a === 100 && b >= 64 && b <= 127) return true;
3192
4363
  return false;
3193
4364
  }
4365
+ function embeddedIpv4(words) {
4366
+ const [a, b, c, d, e, f, g, h] = words;
4367
+ const low32 = [g >> 8, g & 255, h >> 8, h & 255];
4368
+ if (a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 0 || a === 0 && b === 0 && c === 0 && d === 0 && e === 0 && f === 65535 || a === 0 && b === 0 && c === 0 && d === 0 && e === 65535 && f === 0) {
4369
+ return low32;
4370
+ }
4371
+ if (a === 100 && b === 65435 && c === 0 && d === 0 && e === 0 && f === 0) {
4372
+ return low32;
4373
+ }
4374
+ if (a === 100 && b === 65435 && c === 1 && e >> 8 === 0) {
4375
+ return [d >> 8, d & 255, e & 255, f >> 8];
4376
+ }
4377
+ if (a === 8194) return [b >> 8, b & 255, c >> 8, c & 255];
4378
+ return null;
4379
+ }
3194
4380
  function isPrivateIpAddress(address) {
3195
4381
  let norm = address.trim().toLowerCase();
3196
4382
  if (norm.startsWith("[") && norm.endsWith("]")) norm = norm.slice(1, -1);
3197
4383
  if (!norm) return false;
3198
- if (norm.startsWith("::ffff:")) {
3199
- const mapped = norm.slice("::ffff:".length);
3200
- const ipv42 = parseIpv4(mapped);
3201
- if (ipv42) return isPrivateIpv4(ipv42);
3202
- }
3203
- if (norm.includes(":")) {
3204
- if (norm === "::" || norm === "::1") return true;
3205
- return PRIVATE_IPV6_PREFIXES.some((p) => norm.startsWith(p));
3206
- }
3207
4384
  const ipv4 = parseIpv4(norm);
3208
- 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;
3209
4397
  }
3210
4398
  function isBlockedHostname(hostname) {
3211
4399
  const h = normalizeHostname(hostname);
@@ -3262,6 +4450,14 @@ async function resolveAndCheck(hostname) {
3262
4450
  function isRedirectStatus(s) {
3263
4451
  return s === 301 || s === 302 || s === 303 || s === 307 || s === 308;
3264
4452
  }
4453
+ function withoutCrossOriginCredentials(headersInit) {
4454
+ if (!headersInit) return void 0;
4455
+ const headers = new Headers(headersInit);
4456
+ headers.delete("authorization");
4457
+ headers.delete("cookie");
4458
+ headers.delete("proxy-authorization");
4459
+ return headers;
4460
+ }
3265
4461
  async function closeDispatcher(d) {
3266
4462
  if (!d) return;
3267
4463
  try {
@@ -3282,6 +4478,7 @@ async function guardedFetch(opts) {
3282
4478
  });
3283
4479
  }
3284
4480
  let current = opts.url;
4481
+ let requestInit = { ...opts.init ?? {} };
3285
4482
  const visited = /* @__PURE__ */ new Set();
3286
4483
  let redirects = 0;
3287
4484
  let dispatcher = null;
@@ -3309,7 +4506,7 @@ async function guardedFetch(opts) {
3309
4506
  dispatcher = canPin ? new Agent({ connect: { lookup: createPinnedLookup(parsed.hostname, addrs) } }) : null;
3310
4507
  }
3311
4508
  const init = {
3312
- ...opts.init ?? {},
4509
+ ...requestInit,
3313
4510
  redirect: "manual",
3314
4511
  signal: abortController.signal
3315
4512
  };
@@ -3339,6 +4536,12 @@ async function guardedFetch(opts) {
3339
4536
  }
3340
4537
  visited.add(next);
3341
4538
  void res.body?.cancel();
4539
+ if (new URL(next).origin !== parsed.origin) {
4540
+ requestInit = {
4541
+ ...requestInit,
4542
+ headers: withoutCrossOriginCredentials(requestInit.headers)
4543
+ };
4544
+ }
3342
4545
  current = next;
3343
4546
  continue;
3344
4547
  }
@@ -3494,6 +4697,17 @@ function cacheSet(cache, key, value, ttlMs, maxEntries) {
3494
4697
  }
3495
4698
  }
3496
4699
  function webTools(opts) {
4700
+ return buildWebTools(opts, true);
4701
+ }
4702
+ function protectedWebFetchTool(opts) {
4703
+ const tool = buildWebTools(
4704
+ { ...opts, env: {}, allowPrivate: false, firecrawlDisabled: true },
4705
+ false
4706
+ )[0];
4707
+ if (!tool) throw new Error("protected web_fetch tool construction failed");
4708
+ return tool;
4709
+ }
4710
+ function buildWebTools(opts, includeSearch) {
3497
4711
  const fetchFn = opts?.fetchImpl ?? undiciFetch2;
3498
4712
  const env = opts?.env ?? process.env;
3499
4713
  const allowPrivate = opts?.allowPrivate ?? false;
@@ -3503,8 +4717,9 @@ function webTools(opts) {
3503
4717
  const maxBodyBytes = opts?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
3504
4718
  const firecrawlDisabled = opts?.firecrawlDisabled ?? false;
3505
4719
  const cache = /* @__PURE__ */ new Map();
3506
- return [
3507
- {
4720
+ const tools = [];
4721
+ if (includeSearch) {
4722
+ tools.push({
3508
4723
  def: {
3509
4724
  name: "web_search",
3510
4725
  description: "Search the internet. Returns a list of titles, URLs, and snippets. Requires BRAVE_API_KEY or SEARXNG_URL to be configured.",
@@ -3535,99 +4750,100 @@ function webTools(opts) {
3535
4750
  "web_search: no search backend configured. Set BRAVE_API_KEY (free at https://brave.com/search/api/) or SEARXNG_URL."
3536
4751
  );
3537
4752
  }
3538
- },
3539
- {
3540
- def: {
3541
- name: "web_fetch",
3542
- 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.",
3543
- parameters: {
3544
- type: "object",
3545
- properties: {
3546
- url: { type: "string", description: "The URL to fetch (http or https)" },
3547
- max_length: {
3548
- type: "number",
3549
- description: "Max characters to return (default 20000)"
3550
- },
3551
- extract_mode: {
3552
- type: "string",
3553
- enum: ["markdown", "text"],
3554
- description: 'Output format for HTML pages. Default "markdown".'
3555
- }
4753
+ });
4754
+ }
4755
+ tools.push({
4756
+ def: {
4757
+ name: "web_fetch",
4758
+ description: firecrawlDisabled ? "Fetch a public HTTP or HTTPS URL and return its readable content. Private, loopback, link-local, metadata, and DNS-rebinding targets are blocked. HTML is extracted via Readability and converted to markdown. Results are cached for 15 minutes." : "Fetch a URL and return its readable content. HTML is extracted via Readability and converted to markdown. When the primary extraction returns near-empty content from a 2xx HTML page (typical of JS-only shells), the tool automatically retries via Firecrawl if FIRECRAWL_API_KEY is configured. Results are cached for 15 minutes.",
4759
+ parameters: {
4760
+ type: "object",
4761
+ properties: {
4762
+ url: { type: "string", description: "The URL to fetch (http or https)" },
4763
+ max_length: {
4764
+ type: "number",
4765
+ description: "Max characters to return (default 20000)"
3556
4766
  },
3557
- required: ["url"]
3558
- }
3559
- },
3560
- async invoke(args) {
3561
- const url = String(args.url ?? "");
3562
- if (!url) throw new Error("web_fetch: url is required");
3563
- const maxLen = typeof args.max_length === "number" ? args.max_length : DEFAULT_MAX_LENGTH;
3564
- const mode = args.extract_mode === "text" ? "text" : "markdown";
3565
- const cacheKey = `${mode}|${url}`;
3566
- const cached = cacheGet(cache, cacheKey);
3567
- if (cached) return formatOutput(cached, maxLen);
3568
- let result = null;
3569
- try {
3570
- result = await guardedFetch({
3571
- url,
3572
- fetchImpl: opts?.fetchImpl,
3573
- allowPrivate,
3574
- timeoutMs,
3575
- init: {
3576
- headers: {
3577
- "user-agent": DEFAULT_USER_AGENT,
3578
- accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*",
3579
- "accept-language": "en-US,en;q=0.9"
3580
- }
3581
- }
3582
- });
3583
- if (!result.response.ok) {
3584
- throw new Error(`${result.response.status} ${result.response.statusText}`);
4767
+ extract_mode: {
4768
+ type: "string",
4769
+ enum: ["markdown", "text"],
4770
+ description: 'Output format for HTML pages. Default "markdown".'
3585
4771
  }
3586
- const ct = result.response.headers.get("content-type") ?? "";
3587
- const isHtml = ct.includes("text/html") || ct.includes("xhtml");
3588
- const { text: body, truncated } = await readBodyCapped(result.response, maxBodyBytes);
3589
- let extracted;
3590
- if (isHtml) {
3591
- extracted = extractReadable(body, result.finalUrl, mode);
3592
- } else if (ct.includes("application/json")) {
3593
- try {
3594
- extracted = { text: JSON.stringify(JSON.parse(body), null, 2) };
3595
- } catch {
3596
- 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"
3597
4796
  }
3598
- } 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 {
3599
4812
  extracted = { text: body };
3600
4813
  }
3601
- if (isHtml && !firecrawlDisabled && extracted.text.length < FIRECRAWL_FALLBACK_THRESHOLD) {
3602
- const rescued = await firecrawlScrape(result.finalUrl, mode, env, fetchFn, timeoutMs);
3603
- if (rescued) {
3604
- extracted = {
3605
- ...rescued,
3606
- 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}
3607
4823
 
3608
4824
  [content rendered via Firecrawl fallback \u2014 primary extraction returned ${extracted.text.length} chars]`
3609
- };
3610
- }
4825
+ };
3611
4826
  }
3612
- if (truncated) {
3613
- extracted = {
3614
- ...extracted,
3615
- text: `${extracted.text}
4827
+ }
4828
+ if (truncated) {
4829
+ extracted = {
4830
+ ...extracted,
4831
+ text: `${extracted.text}
3616
4832
 
3617
4833
  [raw body truncated at ${maxBodyBytes} bytes before extraction \u2014 page exceeded the size cap]`
3618
- };
3619
- }
3620
- cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
3621
- return formatOutput(extracted, maxLen);
3622
- } catch (err) {
3623
- if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
3624
- throw new Error(`web_fetch: ${describeError(err)}`);
3625
- } finally {
3626
- if (result) await result.release();
4834
+ };
3627
4835
  }
4836
+ cacheSet(cache, cacheKey, extracted, cacheTtlMs, cacheMax);
4837
+ return formatOutput(extracted, maxLen);
4838
+ } catch (err) {
4839
+ if (err instanceof SsrFBlockedError) throw new Error(`web_fetch: ${err.message}`);
4840
+ throw new Error(`web_fetch: ${describeError(err)}`);
4841
+ } finally {
4842
+ if (result) await result.release();
3628
4843
  }
3629
4844
  }
3630
- ];
4845
+ });
4846
+ return tools;
3631
4847
  }
3632
4848
  function formatOutput(r, maxLen) {
3633
4849
  const body = r.title ? `# ${r.title}
@@ -3690,8 +4906,26 @@ function createBazilionCustomTools(opts) {
3690
4906
  }
3691
4907
  return handlers.map(ourToolToPiTool);
3692
4908
  }
4909
+ function createProtectedBazilionCustomTools(opts) {
4910
+ const handlers = [
4911
+ ...memoryTools(opts.memory),
4912
+ ...homeTools(opts.agent.agent.dir),
4913
+ bootstrapTool(opts.agent.agent.dir),
4914
+ protectedWebFetchTool(),
4915
+ ...messagingTools(opts.messagingHost, opts.agent.agent.id),
4916
+ ...userMdTools(opts.userMdHost, opts.agent.team.id),
4917
+ deliverFileTool(opts.agent.team.path, opts.fileSink)
4918
+ ];
4919
+ return handlers.map(ourToolToPiTool);
4920
+ }
3693
4921
 
3694
4922
  // ../daemon/src/runtime/pi/session.ts
4923
+ var PROTECTED_MODEL_CWD = "/workspace";
4924
+ var REVIEW_MODEL_CWD = "/review";
4925
+ var PROTECTED_BASE_SYSTEM_PROMPT = `You are an assistant operating inside Bazilion's protected
4926
+ Agent runtime. Use only the tools exposed for this turn. Coding commands execute in an ephemeral,
4927
+ network-disabled Docker container whose Team workspace is /workspace. Host paths and host tools are
4928
+ not available. Be concise and show container paths clearly when working with files.`;
3695
4929
  async function createBazilionSession(opts) {
3696
4930
  const {
3697
4931
  agent,
@@ -3713,44 +4947,30 @@ async function createBazilionSession(opts) {
3713
4947
  if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
3714
4948
  throw new Error(`${providerName} provider is disabled \u2014 enable it on the /config page`);
3715
4949
  }
3716
- const piProviderName = mapProviderName(providerName);
3717
- const model = resolveModel(
3718
- {
3719
- providerName,
3720
- piProviderName,
3721
- fallbackApi: pickFallbackApi(providerName),
3722
- baseUrl: resolveBaseUrl(providerName, env)
3723
- },
4950
+ const apiKey = opts.apiKey ?? providerApiKey(providerName, env);
4951
+ const baseUrl = providerBaseUrl(providerName, env);
4952
+ const modelRuntime = await createBazilionPiRuntime({
4953
+ providerName,
4954
+ env,
4955
+ ...apiKey ? { apiKey } : {},
4956
+ ...baseUrl ? { baseUrl } : {},
3724
4957
  modelId
3725
- );
3726
- const apiKey = opts.apiKey ?? resolveApiKey(providerName, env);
3727
- const authStorage = AuthStorage.inMemory();
3728
- if (apiKey) {
3729
- authStorage.setRuntimeApiKey(piProviderName, apiKey);
3730
- }
3731
- const modelRegistry = ModelRegistry.inMemory(authStorage);
3732
- if (providerName === "lmstudio" || providerName === "ollama") {
3733
- modelRegistry.registerProvider(piProviderName, {
3734
- baseUrl: model.baseUrl,
3735
- api: "openai-completions",
3736
- authHeader: false,
3737
- apiKey: apiKey ?? "dummy"
3738
- });
3739
- }
4958
+ });
4959
+ const model = resolvePiModel(modelRuntime, providerName, modelId, baseUrl);
3740
4960
  const cwd = agent.team.path;
3741
- if (!existsSync13(cwd)) mkdirSync5(cwd, { recursive: true });
4961
+ if (!existsSync14(cwd)) mkdirSync7(cwd, { recursive: true });
3742
4962
  const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills);
3743
- const uploadsDir = join18(agent.agent.dir, "uploads");
4963
+ const uploadsDir = join19(agent.agent.dir, "uploads");
3744
4964
  const shellTools = restricted ? null : createSessionShellTools(cwd, env, {
3745
- ...existsSync13(uploadsDir) ? { inputsDir: uploadsDir } : {},
4965
+ ...existsSync14(uploadsDir) ? { inputsDir: uploadsDir } : {},
3746
4966
  skillMounts: promptSkills.map((skill) => ({
3747
4967
  source: skill.hostDir,
3748
4968
  target: skill.sandboxDir
3749
4969
  })),
3750
4970
  approvalHost: bashApprovalHost
3751
4971
  });
3752
- const sessionDir = restricted?.sessionDir ?? join18(paths.agentDir(agent.agent.id), "sessions");
3753
- mkdirSync5(sessionDir, { recursive: true });
4972
+ const sessionDir = restricted?.sessionDir ?? join19(paths.agentDir(agent.agent.id), "sessions");
4973
+ mkdirSync7(sessionDir, { recursive: true });
3754
4974
  const existing = restricted ? null : findMostRecent(sessionDir);
3755
4975
  const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
3756
4976
  const settingsManager = SettingsManager.inMemory({
@@ -3766,7 +4986,17 @@ async function createBazilionSession(opts) {
3766
4986
  skills: promptSkills,
3767
4987
  sandboxMode: shellTools?.config.sandboxMode ?? "off"
3768
4988
  });
3769
- const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
4989
+ const resourceLoader = new DefaultResourceLoader({
4990
+ cwd,
4991
+ agentDir: join19(paths.home, "pi"),
4992
+ settingsManager,
4993
+ noExtensions: true,
4994
+ noSkills: true,
4995
+ noPromptTemplates: true,
4996
+ noThemes: true,
4997
+ noContextFiles: true,
4998
+ appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : void 0
4999
+ });
3770
5000
  await resourceLoader.reload();
3771
5001
  const bazilionTools = restricted ? restricted.tools : createBazilionCustomTools({
3772
5002
  agent,
@@ -3783,20 +5013,19 @@ async function createBazilionSession(opts) {
3783
5013
  const allowedTools = [...shellTools?.hostToolNames ?? [], ...customTools.map((t) => t.name)];
3784
5014
  const { session } = await createAgentSession({
3785
5015
  cwd,
3786
- agentDir: join18(paths.home, "pi"),
5016
+ agentDir: join19(paths.home, "pi"),
3787
5017
  model,
3788
5018
  thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),
3789
5019
  tools: allowedTools,
3790
5020
  customTools,
3791
5021
  sessionManager,
3792
5022
  settingsManager,
3793
- authStorage,
3794
- modelRegistry,
5023
+ modelRuntime,
3795
5024
  resourceLoader
3796
5025
  });
3797
5026
  if (refreshApiKey) {
3798
5027
  session.agent.getApiKey = async (requestedProvider) => {
3799
- if (requestedProvider !== piProviderName) return void 0;
5028
+ if (requestedProvider !== piProviderName(providerName)) return void 0;
3800
5029
  try {
3801
5030
  return await refreshApiKey(providerName);
3802
5031
  } catch {
@@ -3811,73 +5040,264 @@ async function createBazilionSession(opts) {
3811
5040
  }
3812
5041
  };
3813
5042
  }
3814
- function splitModelString(s) {
3815
- const idx = s.indexOf(":");
3816
- if (idx === -1) {
3817
- throw new Error(`invalid model string "${s}": expected "provider:model"`);
5043
+ async function createProtectedBazilionSession(opts) {
5044
+ const { providerName, modelId } = splitModelString(opts.agent.model);
5045
+ if (providerName !== opts.runtime.providerName || modelId !== opts.runtime.modelId) {
5046
+ throw new Error("protected Agent model does not match the prepared provider runtime");
3818
5047
  }
3819
- return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) };
3820
- }
3821
- function mapProviderName(name) {
3822
- if (name === "bedrock") return "amazon-bedrock";
3823
- return name;
3824
- }
3825
- function pickFallbackApi(providerName) {
3826
- switch (providerName) {
3827
- case "anthropic":
3828
- return "anthropic-messages";
3829
- case "google":
3830
- return "google-generative-ai";
3831
- case "google-vertex":
3832
- return "google-vertex";
3833
- case "azure-openai":
3834
- return "azure-openai-responses";
3835
- case "bedrock":
3836
- return "bedrock-converse-stream";
3837
- case "openai-codex":
3838
- return "openai-codex-responses";
3839
- default:
3840
- return "openai-completions";
5048
+ if (opts.agent.agent.dir !== opts.paths.agentDir || opts.agent.team.path !== opts.paths.teamDir) {
5049
+ throw new Error("protected session paths do not match the resolved Agent");
5050
+ }
5051
+ if (!existsSync14(opts.paths.teamDir)) {
5052
+ throw new Error("protected Team workspace is unavailable");
5053
+ }
5054
+ const modelRuntime = await createProtectedPiRuntime({
5055
+ providerName: opts.runtime.providerName,
5056
+ modelId: opts.runtime.modelId,
5057
+ apiKey: opts.runtime.apiKey,
5058
+ baseUrl: opts.runtime.baseUrl,
5059
+ credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch)
5060
+ });
5061
+ const model = resolvePiModel(
5062
+ modelRuntime,
5063
+ opts.runtime.providerName,
5064
+ opts.runtime.modelId,
5065
+ opts.runtime.baseUrl
5066
+ );
5067
+ const shellTools = createProtectedSessionShellTools(
5068
+ opts.paths.teamDir,
5069
+ opts.docker,
5070
+ opts.bashApprovalHost
5071
+ );
5072
+ mkdirSync7(opts.paths.sessionDir, { recursive: true });
5073
+ const existing = findMostRecent(opts.paths.sessionDir);
5074
+ const sessionManager = existing ? SessionManager.open(existing, opts.paths.sessionDir, opts.paths.teamDir) : SessionManager.create(opts.paths.teamDir, opts.paths.sessionDir);
5075
+ const settingsManager = createInMemorySettingsManager();
5076
+ const bazilionPrompt = redactJsonValue(
5077
+ buildSystemPrompt(opts.agent, {
5078
+ skills: opts.paths.skills,
5079
+ sandboxMode: "docker",
5080
+ homeDocuments: opts.paths.homeDocuments
5081
+ }),
5082
+ protectedRuntimeSecrets(opts.runtime)
5083
+ );
5084
+ const resourceLoader = new DefaultResourceLoader({
5085
+ cwd: PROTECTED_MODEL_CWD,
5086
+ agentDir: opts.scratch.piAgentDir,
5087
+ settingsManager,
5088
+ noExtensions: true,
5089
+ noSkills: true,
5090
+ noPromptTemplates: true,
5091
+ noThemes: true,
5092
+ noContextFiles: true,
5093
+ systemPrompt: PROTECTED_BASE_SYSTEM_PROMPT,
5094
+ appendSystemPrompt: bazilionPrompt ? [bazilionPrompt] : void 0
5095
+ });
5096
+ await resourceLoader.reload();
5097
+ const bazilionTools = createProtectedBazilionCustomTools({
5098
+ agent: opts.agent,
5099
+ memory: opts.memory,
5100
+ messagingHost: opts.messagingHost,
5101
+ userMdHost: opts.userMdHost,
5102
+ fileSink: opts.fileSink
5103
+ });
5104
+ if (!shellTools.customBash) throw new Error("protected Docker bash tool is unavailable");
5105
+ const customTools = [...bazilionTools, shellTools.customBash];
5106
+ const { session } = await createAgentSession({
5107
+ cwd: PROTECTED_MODEL_CWD,
5108
+ agentDir: opts.scratch.piAgentDir,
5109
+ model,
5110
+ thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),
5111
+ tools: customTools.map((tool) => tool.name),
5112
+ customTools,
5113
+ sessionManager,
5114
+ settingsManager,
5115
+ modelRuntime,
5116
+ resourceLoader
5117
+ });
5118
+ installProtectedCredentialBoundary(
5119
+ session,
5120
+ opts.runtime.providerName,
5121
+ protectedRuntimeSecrets(opts.runtime),
5122
+ opts.refreshApiKey
5123
+ );
5124
+ return sessionHandle(session);
5125
+ }
5126
+ async function createRestrictedReviewSession(opts) {
5127
+ const modelRuntime = await createProtectedPiRuntime({
5128
+ providerName: opts.runtime.providerName,
5129
+ modelId: opts.runtime.modelId,
5130
+ apiKey: opts.runtime.apiKey,
5131
+ baseUrl: opts.runtime.baseUrl,
5132
+ credentialEnv: materializeProtectedCredentialFile(opts.runtime, opts.scratch)
5133
+ });
5134
+ const model = resolvePiModel(
5135
+ modelRuntime,
5136
+ opts.runtime.providerName,
5137
+ opts.runtime.modelId,
5138
+ opts.runtime.baseUrl
5139
+ );
5140
+ const settingsManager = createInMemorySettingsManager();
5141
+ const sessionManager = SessionManager.create(
5142
+ opts.scratch.reviewCwd,
5143
+ opts.scratch.reviewSessionDir
5144
+ );
5145
+ const resourceLoader = new DefaultResourceLoader({
5146
+ cwd: REVIEW_MODEL_CWD,
5147
+ agentDir: opts.scratch.piAgentDir,
5148
+ settingsManager,
5149
+ noExtensions: true,
5150
+ noSkills: true,
5151
+ noPromptTemplates: true,
5152
+ noThemes: true,
5153
+ noContextFiles: true,
5154
+ systemPrompt: opts.systemPrompt
5155
+ });
5156
+ await resourceLoader.reload();
5157
+ const { session } = await createAgentSession({
5158
+ cwd: REVIEW_MODEL_CWD,
5159
+ agentDir: opts.scratch.piAgentDir,
5160
+ model,
5161
+ thinkingLevel: toPiThinkingLevel(opts.runtime.reasoningLevel),
5162
+ tools: opts.tools.map((tool) => tool.name),
5163
+ customTools: opts.tools,
5164
+ sessionManager,
5165
+ settingsManager,
5166
+ modelRuntime,
5167
+ resourceLoader
5168
+ });
5169
+ installProtectedCredentialBoundary(
5170
+ session,
5171
+ opts.runtime.providerName,
5172
+ protectedRuntimeSecrets(opts.runtime),
5173
+ opts.refreshApiKey
5174
+ );
5175
+ return sessionHandle(session);
5176
+ }
5177
+ function materializeProtectedCredentialFile(runtime, scratch) {
5178
+ const fields = [...runtime.credentialEnv ?? []];
5179
+ if (runtime.credentialFile) {
5180
+ const path = join19(scratch.tempDir, "provider-credential.json");
5181
+ writeFileSync9(path, runtime.credentialFile.content, {
5182
+ encoding: "utf8",
5183
+ mode: 384,
5184
+ flag: "wx"
5185
+ });
5186
+ fields.push({ name: runtime.credentialFile.envName, value: path });
3841
5187
  }
5188
+ return fields.length > 0 ? fields : void 0;
3842
5189
  }
3843
- function resolveBaseUrl(providerName, env) {
3844
- if (providerName === "lmstudio") return env.LMSTUDIO_URL ?? "http://127.0.0.1:1234/v1";
3845
- if (providerName === "ollama") return env.OLLAMA_URL ?? "http://127.0.0.1:11434/v1";
3846
- return void 0;
5190
+ function createInMemorySettingsManager() {
5191
+ return SettingsManager.inMemory({
5192
+ compaction: { enabled: false },
5193
+ retry: {
5194
+ enabled: true,
5195
+ maxRetries: 2,
5196
+ baseDelayMs: 500,
5197
+ provider: { maxRetryDelayMs: 8e3 }
5198
+ }
5199
+ });
3847
5200
  }
3848
- function resolveApiKey(providerName, env) {
3849
- switch (providerName) {
3850
- case "anthropic":
3851
- return env.ANTHROPIC_OAUTH_TOKEN ?? env.ANTHROPIC_API_KEY;
3852
- case "openai":
3853
- return env.OPENAI_API_KEY;
3854
- case "google":
3855
- return env.GEMINI_API_KEY;
3856
- case "mistral":
3857
- return env.MISTRAL_API_KEY;
3858
- case "groq":
3859
- return env.GROQ_API_KEY;
3860
- case "cerebras":
3861
- return env.CEREBRAS_API_KEY;
3862
- case "xai":
3863
- return env.XAI_API_KEY;
3864
- case "zai":
3865
- return env.ZAI_API_KEY;
3866
- case "huggingface":
3867
- return env.HF_TOKEN;
3868
- case "openrouter":
3869
- return env.OPENROUTER_API_KEY;
3870
- case "vercel-ai-gateway":
3871
- return env.AI_GATEWAY_API_KEY;
3872
- case "azure-openai":
3873
- return env.AZURE_OPENAI_API_KEY;
3874
- case "lmstudio":
3875
- return env.LMSTUDIO_API_KEY ?? "lm-studio";
3876
- case "ollama":
3877
- return env.OLLAMA_API_KEY ?? "ollama";
3878
- default:
5201
+ function installProtectedCredentialBoundary(session, providerName, initialSecrets, refreshApiKey) {
5202
+ const activeTokens = [...new Set(initialSecrets.filter(Boolean))];
5203
+ session.agent.state.messages = redactJsonValue(session.agent.state.messages, activeTokens);
5204
+ const originalStream = session.agent.streamFunction;
5205
+ session.agent.streamFunction = async (model, context, options) => {
5206
+ let upstream;
5207
+ try {
5208
+ upstream = await originalStream(model, context, options);
5209
+ } catch (error) {
5210
+ throw credentialSafeError(error, activeTokens);
5211
+ }
5212
+ const sanitized = createAssistantMessageEventStream();
5213
+ void (async () => {
5214
+ let latestPartial;
5215
+ try {
5216
+ for await (const event of upstream) {
5217
+ const safeEvent = redactJsonValue(event, activeTokens);
5218
+ if ("partial" in safeEvent) latestPartial = safeEvent.partial;
5219
+ sanitized.push(safeEvent);
5220
+ }
5221
+ } catch (error) {
5222
+ const safeError = credentialSafeError(error, activeTokens);
5223
+ sanitized.push({
5224
+ type: "error",
5225
+ reason: "error",
5226
+ error: credentialSafeAssistantError(model, latestPartial, safeError, activeTokens)
5227
+ });
5228
+ } finally {
5229
+ sanitized.end();
5230
+ }
5231
+ })();
5232
+ return sanitized;
5233
+ };
5234
+ const originalAfterToolCall = session.agent.afterToolCall;
5235
+ session.agent.afterToolCall = async (context, signal) => {
5236
+ let prior;
5237
+ try {
5238
+ prior = await originalAfterToolCall?.(context, signal);
5239
+ } catch (error) {
5240
+ throw credentialSafeError(error, activeTokens);
5241
+ }
5242
+ return {
5243
+ ...prior,
5244
+ content: redactJsonValue(prior?.content ?? context.result.content, activeTokens),
5245
+ details: redactJsonValue(prior?.details ?? context.result.details, activeTokens)
5246
+ };
5247
+ };
5248
+ session.agent.getApiKey = async (requestedProvider) => {
5249
+ if (requestedProvider !== providerName) return void 0;
5250
+ try {
5251
+ const token = await refreshApiKey(providerName);
5252
+ if (token && !activeTokens.includes(token)) activeTokens.push(token);
5253
+ return token || void 0;
5254
+ } catch {
3879
5255
  return void 0;
5256
+ }
5257
+ };
5258
+ }
5259
+ function credentialSafeError(error, activeTokens) {
5260
+ const message = error instanceof Error ? error.message : String(error);
5261
+ return new Error(redactJsonValue(message, activeTokens));
5262
+ }
5263
+ function credentialSafeAssistantError(model, latestPartial, error, activeTokens) {
5264
+ const safePartial = latestPartial ? redactJsonValue(latestPartial, activeTokens) : {
5265
+ role: "assistant",
5266
+ content: [],
5267
+ api: model.api,
5268
+ provider: model.provider,
5269
+ model: model.id,
5270
+ usage: {
5271
+ input: 0,
5272
+ output: 0,
5273
+ cacheRead: 0,
5274
+ cacheWrite: 0,
5275
+ totalTokens: 0,
5276
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
5277
+ },
5278
+ timestamp: Date.now()
5279
+ };
5280
+ return {
5281
+ ...safePartial,
5282
+ stopReason: "error",
5283
+ errorMessage: error.message,
5284
+ timestamp: Date.now()
5285
+ };
5286
+ }
5287
+ function sessionHandle(session) {
5288
+ return {
5289
+ session,
5290
+ dispose() {
5291
+ session.dispose();
5292
+ }
5293
+ };
5294
+ }
5295
+ function splitModelString(s) {
5296
+ const idx = s.indexOf(":");
5297
+ if (idx === -1) {
5298
+ throw new Error(`invalid model string "${s}": expected "provider:model"`);
3880
5299
  }
5300
+ return { providerName: s.slice(0, idx), modelId: s.slice(idx + 1) };
3881
5301
  }
3882
5302
  function toPiThinkingLevel(level) {
3883
5303
  switch (level) {
@@ -3892,28 +5312,12 @@ function toPiThinkingLevel(level) {
3892
5312
  return "medium";
3893
5313
  }
3894
5314
  }
3895
- function createBazilionResourceLoader(appendSystemPrompt) {
3896
- const extensions = { extensions: [], errors: [], runtime: createExtensionRuntime() };
3897
- return {
3898
- getExtensions: () => extensions,
3899
- getSkills: () => ({ skills: [], diagnostics: [] }),
3900
- getPrompts: () => ({ prompts: [], diagnostics: [] }),
3901
- getThemes: () => ({ themes: [], diagnostics: [] }),
3902
- getAgentsFiles: () => ({ agentsFiles: [] }),
3903
- getSystemPrompt: () => void 0,
3904
- getAppendSystemPrompt: () => appendSystemPrompt ? [appendSystemPrompt] : [],
3905
- extendResources: () => {
3906
- },
3907
- async reload() {
3908
- }
3909
- };
3910
- }
3911
5315
  function findMostRecent(sessionDir) {
3912
- if (!existsSync13(sessionDir)) return null;
5316
+ if (!existsSync14(sessionDir)) return null;
3913
5317
  let newest = null;
3914
5318
  for (const entry of readdirSync8(sessionDir)) {
3915
5319
  if (!entry.endsWith(".jsonl")) continue;
3916
- const path = join18(sessionDir, entry);
5320
+ const path = join19(sessionDir, entry);
3917
5321
  try {
3918
5322
  const s = statSync5(path);
3919
5323
  if (!newest || s.mtimeMs > newest.mtimeMs) newest = { path, mtimeMs: s.mtimeMs };
@@ -3926,7 +5330,7 @@ function findMostRecent(sessionDir) {
3926
5330
  // ../daemon/src/runtime/worker/api-key-refresh.ts
3927
5331
  function createIpcApiKeyRefresher(call, context) {
3928
5332
  return async (providerName) => {
3929
- assertOpenAICodexProvider(providerName, context.providerName);
5333
+ assertSelectedProvider(providerName, context.providerName);
3930
5334
  return call("refreshApiKey", {
3931
5335
  providerName,
3932
5336
  agentId: context.agentId,
@@ -3934,14 +5338,14 @@ function createIpcApiKeyRefresher(call, context) {
3934
5338
  });
3935
5339
  };
3936
5340
  }
3937
- function assertOpenAICodexProvider(requested, expected) {
3938
- if (expected !== "openai-codex" || requested !== expected) {
5341
+ function assertSelectedProvider(requested, expected) {
5342
+ if (!expected || requested !== expected) {
3939
5343
  throw new Error("unexpected API key refresh provider");
3940
5344
  }
3941
5345
  }
3942
5346
 
3943
5347
  // ../daemon/src/runtime/worker/ipc-client.ts
3944
- import { randomUUID as randomUUID18 } from "crypto";
5348
+ import { randomUUID as randomUUID19 } from "crypto";
3945
5349
  function createIpcClient(channel) {
3946
5350
  const pending = /* @__PURE__ */ new Map();
3947
5351
  let disconnected = false;
@@ -3968,10 +5372,10 @@ function createIpcClient(channel) {
3968
5372
  if (!send) {
3969
5373
  return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
3970
5374
  }
3971
- const id = randomUUID18();
5375
+ const id = randomUUID19();
3972
5376
  const message = { type: "rpc", id, method, args };
3973
- return new Promise((resolve5, reject) => {
3974
- pending.set(id, { resolve: (value) => resolve5(value), reject });
5377
+ return new Promise((resolve6, reject) => {
5378
+ pending.set(id, { resolve: (value) => resolve6(value), reject });
3975
5379
  try {
3976
5380
  send(message, (error) => {
3977
5381
  if (!error) return;
@@ -3992,29 +5396,40 @@ function isIpcReply(message) {
3992
5396
  }
3993
5397
 
3994
5398
  // ../daemon/src/runtime/worker/entry.ts
5399
+ var activeAccessTokens = [];
3995
5400
  function emit(frame) {
3996
- process.stdout.write(`${JSON.stringify(frame)}
5401
+ process.stdout.write(`${JSON.stringify(redactJsonValue(frame, activeAccessTokens))}
3997
5402
  `);
3998
5403
  }
3999
5404
  async function readInput() {
4000
5405
  const chunks = [];
4001
- for await (const c of process.stdin) chunks.push(c);
5406
+ for await (const chunk of process.stdin) chunks.push(chunk);
4002
5407
  const raw = Buffer.concat(chunks).toString("utf8").trim();
4003
5408
  if (!raw) throw new Error("worker: empty stdin input");
4004
- const parsed = JSON.parse(raw);
4005
- 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") {
4006
- throw new Error(
4007
- "worker: stdin must include {agent, message, enabledProviders, turnId, bashApprovalMode}"
4008
- );
4009
- }
4010
- if (parsed.mode === "review" && !parsed.review?.reviewId) {
4011
- throw new Error("worker: review mode requires review metadata");
4012
- }
4013
- const providerName = parsed.agent.model.split(":", 1)[0] ?? "";
4014
- if (parsed.apiKeyRefreshEnabled && (providerName !== "openai-codex" || typeof parsed.apiKey !== "string")) {
4015
- throw new Error("worker: API key refresh requires an openai-codex turn with an initial token");
5409
+ let parsed;
5410
+ try {
5411
+ parsed = JSON.parse(raw);
5412
+ } catch {
5413
+ throw new Error("worker: stdin is not valid JSON");
4016
5414
  }
4017
- return parsed;
5415
+ return parseWorkerInput(parsed);
5416
+ }
5417
+ function createWorkerIpcCall() {
5418
+ return createIpcClient({
5419
+ send: process.send ? (ipcMessage, done) => {
5420
+ process.send?.(ipcMessage, void 0, void 0, done);
5421
+ } : void 0,
5422
+ onMessage: (listener) => process.on("message", listener),
5423
+ onDisconnect: (listener) => process.on("disconnect", listener)
5424
+ });
5425
+ }
5426
+ function createTrackedIpcApiKeyRefresher(call, context) {
5427
+ const refresh = createIpcApiKeyRefresher(call, context);
5428
+ return async (providerName) => {
5429
+ const token = await refresh(providerName);
5430
+ if (token && !activeAccessTokens.includes(token)) activeAccessTokens.push(token);
5431
+ return token;
5432
+ };
4018
5433
  }
4019
5434
  function createIpcMessagingHost(call) {
4020
5435
  return {
@@ -4048,81 +5463,27 @@ function createIpcBashApprovalHost(call, input) {
4048
5463
  return {
4049
5464
  async requestApproval(request) {
4050
5465
  const result = await call("bashApproval", {
4051
- id: randomUUID19(),
5466
+ id: randomUUID20(),
4052
5467
  turnId: input.turnId,
4053
5468
  toolCallId: request.toolCallId,
4054
- agentId: input.agent.agent.id,
4055
- teamId: input.agent.team.id,
5469
+ agentId: input.agentId,
5470
+ teamId: input.teamId,
4056
5471
  command: request.command,
4057
5472
  risks: [...request.risks],
4058
- mode: input.bashApprovalMode
5473
+ mode: input.mode
4059
5474
  });
4060
5475
  return result.decision === "allow" ? "approved" : "denied";
4061
5476
  }
4062
5477
  };
4063
5478
  }
4064
- async function main() {
4065
- let aborted = false;
4066
- let abortSession = null;
4067
- const onSignal = () => {
4068
- aborted = true;
4069
- if (abortSession) {
4070
- abortSession();
4071
- } else {
4072
- try {
4073
- emit({ kind: "event", event: { type: "error", error: "cancelled" } });
4074
- } catch {
4075
- }
4076
- process.exit(0);
4077
- }
4078
- };
4079
- process.on("SIGTERM", onSignal);
4080
- process.on("SIGINT", onSignal);
4081
- const {
4082
- agent,
4083
- message,
4084
- enabledProviders,
4085
- apiKey,
4086
- apiKeyRefreshEnabled,
4087
- browserEnabled,
4088
- mcpTools,
4089
- images,
4090
- turnId,
4091
- bashApprovalMode,
4092
- mode = "chat",
4093
- review
4094
- } = await readInput();
4095
- const paths = resolvePaths();
4096
- const memory = qmdBackend(join19(agent.team.path, "memory"));
4097
- await memory.init();
4098
- const ipcCall = createIpcClient({
4099
- send: process.send ? (ipcMessage, done) => {
4100
- process.send?.(ipcMessage, void 0, void 0, done);
4101
- } : void 0,
4102
- onMessage: (listener) => process.on("message", listener),
4103
- onDisconnect: (listener) => process.on("disconnect", listener)
4104
- });
4105
- const refreshApiKey = apiKeyRefreshEnabled ? createIpcApiKeyRefresher(ipcCall, {
4106
- providerName: agent.model.split(":", 1)[0] ?? "",
4107
- agentId: agent.agent.id,
4108
- turnId
4109
- }) : void 0;
4110
- const messagingHost = createIpcMessagingHost(ipcCall);
4111
- const userMdHost = createIpcUserMdHost(ipcCall);
4112
- const browserHost = browserEnabled ? createIpcBrowserHost(ipcCall) : void 0;
4113
- const mcpHost = mcpTools && mcpTools.length > 0 ? createIpcMcpHost(ipcCall) : void 0;
4114
- const bashApprovalHost = createIpcBashApprovalHost(ipcCall, {
4115
- agent,
4116
- turnId,
4117
- bashApprovalMode
4118
- });
4119
- const reviewSessionDir = mode === "review" ? mkdtempSync3(join19(paths.logsDir, "review-session-")) : null;
5479
+ function createReviewState(evidenceItems) {
4120
5480
  const proposals = [];
4121
5481
  let proposalBatchSubmitted = false;
5482
+ let reviewValidationFailures = 0;
4122
5483
  const allowedEvidence = new Set(
4123
- (review?.evidence ?? []).map((item) => `${item.sessionId}:${item.entryOrdinal}`)
5484
+ evidenceItems.map((item) => `${item.sessionId}:${item.entryOrdinal}`)
4124
5485
  );
4125
- const reviewTools = [
5486
+ const tools = [
4126
5487
  {
4127
5488
  name: "propose_lesson",
4128
5489
  label: "propose_lesson",
@@ -4142,8 +5503,8 @@ async function main() {
4142
5503
  }),
4143
5504
  async execute(_toolCallId, params) {
4144
5505
  if (proposalBatchSubmitted) throw new Error("propose_lesson may be called only once");
4145
- proposalBatchSubmitted = true;
4146
5506
  const input = params;
5507
+ const validated = [];
4147
5508
  for (const candidate of input.proposals) {
4148
5509
  const text = candidate.text.trim();
4149
5510
  if (!text || text.length > 500) throw new Error("proposal text must be 1-500 characters");
@@ -4152,8 +5513,10 @@ async function main() {
4152
5513
  throw new Error("proposal cites evidence outside the supplied digest");
4153
5514
  }
4154
5515
  }
4155
- proposals.push({ ...candidate, text });
5516
+ validated.push({ ...candidate, text });
4156
5517
  }
5518
+ proposalBatchSubmitted = true;
5519
+ proposals.push(...validated);
4157
5520
  return {
4158
5521
  content: [{ type: "text", text: `accepted ${proposals.length} proposals` }],
4159
5522
  details: {},
@@ -4162,86 +5525,168 @@ async function main() {
4162
5525
  }
4163
5526
  }
4164
5527
  ];
4165
- const { session, dispose } = await createBazilionSession({
4166
- agent,
4167
- paths,
4168
- 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,
4169
5608
  memory,
4170
- enabledProviders: new Set(enabledProviders),
4171
5609
  messagingHost,
4172
5610
  userMdHost,
4173
- apiKey,
4174
- refreshApiKey,
4175
- browserHost,
4176
- mcpHost,
4177
- mcpTools,
4178
5611
  bashApprovalHost,
4179
- // deliver_file emits a `file` event straight onto our stdout frame stream.
4180
- fileSink: (f) => emit({ kind: "event", event: { type: "file", ...f } }),
4181
- ...mode === "review" && reviewSessionDir ? {
4182
- restricted: {
4183
- systemPrompt: REVIEW_SYSTEM_PROMPT,
4184
- tools: reviewTools,
4185
- sessionDir: reviewSessionDir,
4186
- reasoningLevel: agent.agent.reviewReasoningLevel
4187
- }
4188
- } : {}
5612
+ refreshApiKey,
5613
+ fileSink: (file) => emit({ kind: "event", event: { type: "file", ...file } })
4189
5614
  });
4190
- abortSession = () => {
4191
- 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
+ }
4192
5630
  };
4193
- 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;
4194
5642
  const unsubscribe = session.subscribe((piEvent) => {
4195
- if (mode === "review" && piEvent.type === "tool_execution_end" && piEvent.toolName === "propose_lesson" && piEvent.isError) {
4196
- reviewValidationFailures += 1;
4197
- if (reviewValidationFailures >= 2) void session.abort();
4198
- }
4199
- for (const ev of translatePiEvent(piEvent)) {
4200
- emit({ kind: "event", event: ev });
5643
+ if (reviewState && piEvent.type === "tool_execution_end" && piEvent.toolName === "propose_lesson" && piEvent.isError) {
5644
+ reviewState.recordValidationFailure();
5645
+ if (reviewState.validationFailures() >= 2) void session?.abort();
4201
5646
  }
5647
+ for (const event of translatePiEvent(piEvent)) emit({ kind: "event", event });
4202
5648
  });
4203
5649
  try {
4204
- const 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) => ({
4205
5653
  type: "image",
4206
- data: img.data,
4207
- mimeType: img.mimeType
5654
+ data: image.data,
5655
+ mimeType: image.mimeType
4208
5656
  }));
4209
- 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
+ );
4210
5661
  await session.agent.waitForIdle();
4211
- if (mode === "review" && reviewValidationFailures >= 2) {
4212
- throw new Error("review output remained invalid after one schema-repair attempt");
4213
- }
4214
- if (mode === "review" && !proposalBatchSubmitted) {
4215
- throw new Error("review output did not submit a proposal batch");
4216
- }
4217
- if (mode === "review") emit({ kind: "review_result", proposals });
4218
- else {
4219
- emit({
4220
- kind: "done",
4221
- messages: piMessagesToProviderView(session.agent.state.messages)
4222
- });
4223
- }
4224
- } catch (err) {
4225
- if (!aborted) {
4226
- emit({ kind: "event", event: { type: "error", error: err.message } });
5662
+ if (reviewState) {
5663
+ if (reviewState.validationFailures() >= 2) {
5664
+ throw new Error("review output remained invalid after one schema-repair attempt");
5665
+ }
5666
+ if (!reviewState.submitted()) throw new Error("review output did not submit a proposal batch");
5667
+ emit({ kind: "review_result", proposals: reviewState.proposals });
5668
+ } else {
5669
+ emit({ kind: "done", messages: piMessagesToProviderView(session.agent.state.messages) });
4227
5670
  }
4228
- 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 });
4229
5675
  process.exitCode = 1;
4230
5676
  } finally {
4231
5677
  unsubscribe();
4232
5678
  process.off("SIGTERM", onSignal);
4233
5679
  process.off("SIGINT", onSignal);
4234
- dispose();
4235
- if (reviewSessionDir) rmSync12(reviewSessionDir, { recursive: true, force: true });
5680
+ handle.dispose();
4236
5681
  try {
4237
5682
  process.disconnect?.();
4238
5683
  } catch {
4239
5684
  }
4240
5685
  }
4241
5686
  }
4242
- main().catch((err) => {
5687
+ main().catch((error) => {
4243
5688
  try {
4244
- emit({ kind: "fatal", error: err?.message ?? String(err) });
5689
+ emit({ kind: "fatal", error: error instanceof Error ? error.message : String(error) });
4245
5690
  } catch {
4246
5691
  }
4247
5692
  process.exit(1);