windows-development-sandbox 1.0.5 → 1.1.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 (368) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -6
  3. package/config.example.json +17 -0
  4. package/dist/api.js +2 -1
  5. package/dist/cli/args.d.ts +20 -0
  6. package/dist/cli/args.js +35 -3
  7. package/dist/cli/colors.d.ts +24 -0
  8. package/dist/cli/colors.js +42 -0
  9. package/dist/cli/commands/agent-commands.d.ts +6 -0
  10. package/dist/cli/commands/agent-commands.js +61 -0
  11. package/dist/cli/commands/context.d.ts +2 -0
  12. package/dist/cli/commands/context.js +3 -0
  13. package/dist/cli/commands/data/clean.d.ts +15 -0
  14. package/dist/cli/commands/data/clean.js +101 -0
  15. package/dist/cli/commands/data/collect.d.ts +11 -0
  16. package/dist/cli/commands/data/collect.js +54 -0
  17. package/dist/cli/commands/data/index.d.ts +4 -0
  18. package/dist/cli/commands/data/index.js +45 -0
  19. package/dist/cli/commands/data/remove.d.ts +2 -0
  20. package/dist/cli/commands/data/remove.js +30 -0
  21. package/dist/cli/commands/index.d.ts +5 -3
  22. package/dist/cli/commands/index.js +5 -3
  23. package/dist/cli/commands/launch.d.ts +26 -2
  24. package/dist/cli/commands/launch.js +220 -39
  25. package/dist/cli/commands/maintenance.js +33 -4
  26. package/dist/cli/commands/project.d.ts +29 -0
  27. package/dist/cli/commands/project.js +63 -0
  28. package/dist/cli/commands/projects.d.ts +14 -0
  29. package/dist/cli/commands/projects.js +146 -0
  30. package/dist/cli/commands/repair/checks.d.ts +2 -0
  31. package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
  32. package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
  33. package/dist/cli/commands/repair/finders.js +108 -0
  34. package/dist/cli/commands/repair/index.d.ts +12 -0
  35. package/dist/cli/commands/repair/index.js +61 -0
  36. package/dist/cli/commands/repair/types.d.ts +26 -0
  37. package/dist/cli/commands/repair/types.js +3 -0
  38. package/dist/cli/commands/restart.d.ts +6 -0
  39. package/dist/cli/commands/restart.js +85 -0
  40. package/dist/cli/commands/sessions.d.ts +2 -1
  41. package/dist/cli/commands/sessions.js +189 -45
  42. package/dist/cli/commands/setup/checks.d.ts +13 -0
  43. package/dist/cli/commands/setup/checks.js +71 -0
  44. package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
  45. package/dist/cli/commands/setup/facts.js +60 -0
  46. package/dist/cli/commands/setup/index.d.ts +2 -0
  47. package/dist/cli/commands/setup/index.js +43 -0
  48. package/dist/cli/commands/user-config.d.ts +8 -0
  49. package/dist/cli/commands/user-config.js +41 -0
  50. package/dist/cli/history.d.ts +11 -0
  51. package/dist/cli/history.js +21 -0
  52. package/dist/cli/index.d.ts +7 -1
  53. package/dist/cli/index.js +38 -13
  54. package/dist/cli/manifest.d.ts +8 -0
  55. package/dist/cli/manifest.js +35 -15
  56. package/dist/cli/menu-multi.d.ts +12 -0
  57. package/dist/cli/menu-multi.js +82 -0
  58. package/dist/cli/menu.d.ts +5 -13
  59. package/dist/cli/menu.js +149 -46
  60. package/dist/cli/progress.d.ts +9 -0
  61. package/dist/cli/progress.js +49 -0
  62. package/dist/cli/settings/edit.d.ts +13 -0
  63. package/dist/cli/settings/edit.js +19 -0
  64. package/dist/cli/settings/index.d.ts +8 -0
  65. package/dist/cli/settings/index.js +131 -0
  66. package/dist/cli/settings/prompts.d.ts +7 -0
  67. package/dist/cli/settings/prompts.js +48 -0
  68. package/dist/cli/settings/schema.d.ts +33 -0
  69. package/dist/cli/settings/schema.js +29 -0
  70. package/dist/cli/validate.d.ts +17 -0
  71. package/dist/cli/validate.js +89 -0
  72. package/dist/core/config.js +4 -1
  73. package/dist/core/disk.d.ts +16 -0
  74. package/dist/core/disk.js +55 -0
  75. package/dist/core/local-config.d.ts +8 -0
  76. package/dist/core/local-config.js +18 -5
  77. package/dist/core/project-policy.d.ts +15 -0
  78. package/dist/core/project-policy.js +35 -0
  79. package/dist/core/prompt.d.ts +4 -0
  80. package/dist/core/prompt.js +15 -0
  81. package/dist/core/safe-paths.d.ts +4 -0
  82. package/dist/core/safe-paths.js +37 -0
  83. package/dist/core/schema/common.d.ts +17 -0
  84. package/dist/core/schema/common.js +15 -0
  85. package/dist/core/schema/config.d.ts +594 -0
  86. package/dist/core/schema/config.js +63 -0
  87. package/dist/core/schema/databases.d.ts +33 -0
  88. package/dist/core/schema/databases.js +35 -0
  89. package/dist/core/schema/harness.d.ts +12 -0
  90. package/dist/core/schema/harness.js +17 -0
  91. package/dist/core/schema/json-schema.d.ts +5 -0
  92. package/dist/core/schema/json-schema.js +25 -0
  93. package/dist/core/schema/profile.d.ts +27 -0
  94. package/dist/core/schema/profile.js +29 -0
  95. package/dist/core/schema/projects.d.ts +9 -0
  96. package/dist/core/schema/projects.js +11 -0
  97. package/dist/core/schema/sandbox.d.ts +50 -0
  98. package/dist/core/schema/sandbox.js +36 -0
  99. package/dist/core/schema/toolchains.d.ts +93 -0
  100. package/dist/core/schema/toolchains.js +83 -0
  101. package/dist/core/schema.d.ts +6 -604
  102. package/dist/core/schema.js +8 -264
  103. package/dist/core/types.d.ts +29 -1
  104. package/dist/core/util.d.ts +7 -0
  105. package/dist/core/util.js +12 -3
  106. package/dist/core/workspace.d.ts +0 -5
  107. package/dist/core/workspace.js +12 -46
  108. package/dist/guest/boot-env.d.ts +4 -0
  109. package/dist/guest/boot-env.js +25 -0
  110. package/dist/guest/cmdline.d.ts +12 -0
  111. package/dist/guest/cmdline.js +23 -0
  112. package/dist/guest/daemon/exec.d.ts +5 -0
  113. package/dist/guest/daemon/exec.js +32 -0
  114. package/dist/guest/daemon/project.d.ts +14 -0
  115. package/dist/guest/daemon/project.js +132 -0
  116. package/dist/guest/daemon/session.d.ts +13 -0
  117. package/dist/guest/daemon/session.js +139 -0
  118. package/dist/guest/daemon/shutdown.d.ts +1 -0
  119. package/dist/guest/daemon/shutdown.js +21 -0
  120. package/dist/guest/daemon/state.d.ts +17 -0
  121. package/dist/guest/daemon/state.js +31 -0
  122. package/dist/guest/daemon.js +8 -141
  123. package/dist/guest/db.js +18 -3
  124. package/dist/guest/env.d.ts +2 -0
  125. package/dist/guest/env.js +4 -2
  126. package/dist/guest/guide.d.ts +6 -2
  127. package/dist/guest/guide.js +5 -4
  128. package/dist/guest/helpers.d.ts +3 -0
  129. package/dist/guest/helpers.js +26 -0
  130. package/dist/guest/main.js +17 -7
  131. package/dist/guest/ports.d.ts +6 -0
  132. package/dist/guest/ports.js +22 -0
  133. package/dist/guest/project-context.d.ts +7 -0
  134. package/dist/guest/project-context.js +10 -0
  135. package/dist/guest/project-env.d.ts +20 -0
  136. package/dist/guest/project-env.js +20 -0
  137. package/dist/guest/project-status.d.ts +5 -0
  138. package/dist/guest/project-status.js +17 -0
  139. package/dist/guest/ptyhost.d.ts +0 -18
  140. package/dist/guest/ptyhost.js +5 -63
  141. package/dist/guest/setup/agents.d.ts +5 -2
  142. package/dist/guest/setup/agents.js +22 -8
  143. package/dist/guest/setup/claude.js +11 -3
  144. package/dist/guest/setup/environment.d.ts +13 -3
  145. package/dist/guest/setup/environment.js +14 -10
  146. package/dist/guest/setup/hooks.d.ts +9 -1
  147. package/dist/guest/setup/hooks.js +28 -14
  148. package/dist/guest/toolchains/catalogue.d.ts +4 -0
  149. package/dist/guest/toolchains/catalogue.js +2 -2
  150. package/dist/guest/toolchains/cli.js +25 -17
  151. package/dist/guest/toolchains/common.d.ts +18 -1
  152. package/dist/guest/toolchains/common.js +34 -11
  153. package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
  154. package/dist/guest/toolchains/databases/dynamodb.js +52 -0
  155. package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
  156. package/dist/guest/toolchains/databases/index.js +62 -0
  157. package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
  158. package/dist/guest/toolchains/databases/mongodb.js +31 -0
  159. package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
  160. package/dist/guest/toolchains/databases/mysql.js +38 -0
  161. package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
  162. package/dist/guest/toolchains/databases/postgres.js +38 -0
  163. package/dist/guest/toolchains/databases/redis.d.ts +3 -0
  164. package/dist/guest/toolchains/databases/redis.js +16 -0
  165. package/dist/guest/toolchains/databases/server.d.ts +14 -0
  166. package/dist/guest/toolchains/databases/server.js +52 -0
  167. package/dist/guest/toolchains/git.js +25 -16
  168. package/dist/guest/toolchains/go.js +1 -1
  169. package/dist/guest/toolchains/index.d.ts +13 -9
  170. package/dist/guest/toolchains/index.js +40 -82
  171. package/dist/guest/toolchains/java.js +5 -5
  172. package/dist/guest/toolchains/leftovers.d.ts +2 -0
  173. package/dist/guest/toolchains/leftovers.js +26 -0
  174. package/dist/guest/toolchains/managers.js +12 -10
  175. package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
  176. package/dist/guest/toolchains/native/cpp.d.ts +4 -0
  177. package/dist/guest/toolchains/native/cpp.js +38 -0
  178. package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
  179. package/dist/guest/toolchains/native/dotnet.js +103 -0
  180. package/dist/guest/toolchains/native/mesa.d.ts +7 -0
  181. package/dist/guest/toolchains/native/mesa.js +46 -0
  182. package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
  183. package/dist/guest/toolchains/native/msvc/environment.js +57 -0
  184. package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
  185. package/dist/guest/toolchains/native/msvc/index.js +48 -0
  186. package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
  187. package/dist/guest/toolchains/native/msvc/packages.js +156 -0
  188. package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
  189. package/dist/guest/toolchains/native/msvc/tree.js +21 -0
  190. package/dist/guest/toolchains/native/rust.d.ts +3 -0
  191. package/dist/guest/toolchains/native/rust.js +45 -0
  192. package/dist/guest/toolchains/node.js +26 -6
  193. package/dist/guest/toolchains/plan.d.ts +22 -0
  194. package/dist/guest/toolchains/plan.js +96 -0
  195. package/dist/guest/toolchains/pwsh.js +1 -1
  196. package/dist/guest/toolchains/python.js +17 -10
  197. package/dist/guest/toolchains/runtimes.d.ts +2 -0
  198. package/dist/guest/toolchains/runtimes.js +6 -8
  199. package/dist/guest/toolchains/scripting/php.d.ts +3 -0
  200. package/dist/guest/toolchains/scripting/php.js +71 -0
  201. package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
  202. package/dist/guest/toolchains/scripting/ruby.js +47 -0
  203. package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
  204. package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
  205. package/dist/guest/view.js +21 -1
  206. package/dist/host/accounts.js +14 -5
  207. package/dist/host/agent-commands.d.ts +25 -0
  208. package/dist/host/agent-commands.js +145 -0
  209. package/dist/host/attach.d.ts +17 -0
  210. package/dist/host/attach.js +69 -0
  211. package/dist/host/channel.d.ts +23 -22
  212. package/dist/host/channel.js +66 -84
  213. package/dist/host/claude-profile.js +7 -3
  214. package/dist/host/cleanup.js +2 -11
  215. package/dist/host/database-plan.d.ts +12 -0
  216. package/dist/host/database-plan.js +52 -0
  217. package/dist/host/generate.d.ts +4 -12
  218. package/dist/host/generate.js +48 -135
  219. package/dist/host/join.d.ts +28 -0
  220. package/dist/host/join.js +76 -0
  221. package/dist/host/launch/mappings.d.ts +3 -0
  222. package/dist/host/launch/mappings.js +50 -0
  223. package/dist/host/launch/projects.d.ts +15 -0
  224. package/dist/host/launch/projects.js +56 -0
  225. package/dist/host/launch/stage.d.ts +11 -0
  226. package/dist/host/launch/stage.js +66 -0
  227. package/dist/host/launch/wsb.d.ts +10 -0
  228. package/dist/host/launch/wsb.js +34 -0
  229. package/dist/host/pc-scan.d.ts +6 -0
  230. package/dist/host/pc-scan.js +37 -0
  231. package/dist/host/platform.d.ts +2 -0
  232. package/dist/host/platform.js +13 -1
  233. package/dist/host/project-scan.d.ts +1 -17
  234. package/dist/host/project-scan.js +2 -86
  235. package/dist/host/projects.d.ts +100 -0
  236. package/dist/host/projects.js +147 -0
  237. package/dist/host/repo-guard.d.ts +15 -2
  238. package/dist/host/repo-guard.js +103 -12
  239. package/dist/host/terminal.js +3 -0
  240. package/dist/host/test-guard.d.ts +6 -0
  241. package/dist/host/test-guard.js +15 -0
  242. package/dist/host/window.js +7 -3
  243. package/dist/host/wsb.d.ts +37 -0
  244. package/dist/host/wsb.js +101 -0
  245. package/dist/runtime/daemon.js +10997 -2794
  246. package/dist/runtime/db.js +6428 -464
  247. package/dist/runtime/main.js +7359 -1135
  248. package/dist/runtime/view.js +5922 -47
  249. package/dist/selftest/accounts.test.d.ts +2 -0
  250. package/dist/selftest/accounts.test.js +44 -0
  251. package/dist/selftest/agent-commands.test.d.ts +2 -0
  252. package/dist/selftest/agent-commands.test.js +47 -0
  253. package/dist/selftest/channel.test.d.ts +2 -0
  254. package/dist/selftest/channel.test.js +37 -0
  255. package/dist/selftest/cli.test.d.ts +2 -2
  256. package/dist/selftest/cli.test.js +115 -27
  257. package/dist/selftest/cmdline.test.d.ts +2 -0
  258. package/dist/selftest/cmdline.test.js +45 -0
  259. package/dist/selftest/config.test.d.ts +2 -3
  260. package/dist/selftest/config.test.js +36 -72
  261. package/dist/selftest/data.test.d.ts +2 -2
  262. package/dist/selftest/data.test.js +77 -70
  263. package/dist/selftest/detect.test.d.ts +2 -2
  264. package/dist/selftest/detect.test.js +54 -38
  265. package/dist/selftest/fixtures.d.ts +44 -4
  266. package/dist/selftest/fixtures.js +40 -23
  267. package/dist/selftest/generate.test.d.ts +2 -0
  268. package/dist/selftest/generate.test.js +63 -0
  269. package/dist/selftest/git-access.test.d.ts +2 -0
  270. package/dist/selftest/git-access.test.js +26 -0
  271. package/dist/selftest/guest-boot.test.d.ts +2 -0
  272. package/dist/selftest/guest-boot.test.js +76 -0
  273. package/dist/selftest/harness.d.ts +44 -7
  274. package/dist/selftest/harness.js +62 -21
  275. package/dist/selftest/host-sim.test.d.ts +2 -0
  276. package/dist/selftest/host-sim.test.js +93 -0
  277. package/dist/selftest/hostile-input.test.d.ts +2 -0
  278. package/dist/selftest/hostile-input.test.js +54 -0
  279. package/dist/selftest/index.d.ts +8 -1
  280. package/dist/selftest/index.js +134 -35
  281. package/dist/selftest/links.test.d.ts +2 -0
  282. package/dist/selftest/links.test.js +50 -0
  283. package/dist/selftest/live-daemon.test.d.ts +2 -0
  284. package/dist/selftest/live-daemon.test.js +91 -0
  285. package/dist/selftest/live-git.test.d.ts +2 -0
  286. package/dist/selftest/live-git.test.js +60 -0
  287. package/dist/selftest/live-projects.test.d.ts +2 -0
  288. package/dist/selftest/live-projects.test.js +127 -0
  289. package/dist/selftest/live.d.ts +65 -0
  290. package/dist/selftest/live.js +109 -0
  291. package/dist/selftest/local-config.test.d.ts +2 -0
  292. package/dist/selftest/local-config.test.js +107 -0
  293. package/dist/selftest/mirror.test.d.ts +2 -3
  294. package/dist/selftest/mirror.test.js +92 -65
  295. package/dist/selftest/network.test.d.ts +2 -0
  296. package/dist/selftest/network.test.js +28 -0
  297. package/dist/selftest/personalization.test.d.ts +2 -0
  298. package/dist/selftest/personalization.test.js +53 -0
  299. package/dist/selftest/projects.test.d.ts +2 -0
  300. package/dist/selftest/projects.test.js +254 -0
  301. package/dist/selftest/repair.test.d.ts +2 -0
  302. package/dist/selftest/repair.test.js +42 -0
  303. package/dist/selftest/repo-guard.test.d.ts +2 -0
  304. package/dist/selftest/repo-guard.test.js +63 -0
  305. package/dist/selftest/secrets.test.d.ts +2 -0
  306. package/dist/selftest/secrets.test.js +71 -0
  307. package/dist/selftest/setup.test.d.ts +2 -2
  308. package/dist/selftest/setup.test.js +45 -22
  309. package/dist/selftest/suite.d.ts +13 -0
  310. package/dist/selftest/suite.js +1 -0
  311. package/dist/selftest/sync.test.d.ts +2 -3
  312. package/dist/selftest/sync.test.js +90 -78
  313. package/dist/selftest/test-guard.test.d.ts +2 -0
  314. package/dist/selftest/test-guard.test.js +49 -0
  315. package/dist/selftest/toolchains.test.d.ts +2 -0
  316. package/dist/selftest/toolchains.test.js +103 -0
  317. package/dist/selftest/workspace.test.d.ts +2 -0
  318. package/dist/selftest/workspace.test.js +53 -0
  319. package/dist/shared/download.d.ts +3 -1
  320. package/dist/shared/download.js +19 -4
  321. package/dist/shared/paths.d.ts +5 -0
  322. package/dist/shared/paths.js +6 -0
  323. package/dist/shared/projects.d.ts +53 -0
  324. package/dist/shared/projects.js +67 -0
  325. package/dist/shared/rundir.d.ts +10 -1
  326. package/dist/shared/rundir.js +12 -4
  327. package/dist/shared/safe-remove.js +11 -0
  328. package/dist/shared/schema.d.ts +443 -0
  329. package/dist/shared/schema.js +41 -2
  330. package/dist/shared/test-guard.d.ts +4 -0
  331. package/dist/shared/test-guard.js +37 -0
  332. package/dist/sync/claude-mem.js +3 -1
  333. package/dist/sync/history.d.ts +23 -12
  334. package/dist/sync/history.js +52 -20
  335. package/dist/sync/watcher.d.ts +8 -0
  336. package/dist/sync/watcher.js +23 -4
  337. package/package.json +6 -6
  338. package/sandbox/bootstrap.ps1 +258 -258
  339. package/sandbox/cpucap.ps1 +51 -0
  340. package/sandbox/logon.cmd +18 -16
  341. package/sandbox/openssl.cs +42 -0
  342. package/sandbox/ptyhost.cs +15 -4
  343. package/sandbox/trust.cmd +35 -35
  344. package/schemas/config.schema.json +100 -1
  345. package/schemas/sandbox-config.schema.json +17 -1
  346. package/dist/cli/commands/data.d.ts +0 -46
  347. package/dist/cli/commands/data.js +0 -249
  348. package/dist/cli/commands/setup.js +0 -152
  349. package/dist/cli/settings.d.ts +0 -11
  350. package/dist/cli/settings.js +0 -156
  351. package/dist/guest/toolchains/databases.js +0 -226
  352. package/dist/guest/toolchains/msvc.d.ts +0 -10
  353. package/dist/guest/toolchains/msvc.js +0 -216
  354. package/dist/guest/toolchains/native.d.ts +0 -12
  355. package/dist/guest/toolchains/native.js +0 -220
  356. package/dist/guest/toolchains/scripting.d.ts +0 -10
  357. package/dist/guest/toolchains/scripting.js +0 -137
  358. package/dist/selftest/guest.test.d.ts +0 -3
  359. package/dist/selftest/guest.test.js +0 -61
  360. package/dist/selftest/helpers.test.d.ts +0 -2
  361. package/dist/selftest/helpers.test.js +0 -125
  362. package/dist/selftest/host.test.d.ts +0 -2
  363. package/dist/selftest/host.test.js +0 -71
  364. package/dist/selftest/live.test.d.ts +0 -7
  365. package/dist/selftest/live.test.js +0 -127
  366. package/dist/selftest/security.test.d.ts +0 -3
  367. package/dist/selftest/security.test.js +0 -143
  368. /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
@@ -1,258 +1,258 @@
1
- #Requires -Version 5.1
2
- # Runs INSIDE Windows Sandbox (Windows PowerShell 5.1) in the sandbox window, started by logon.cmd.
3
- # Its only job: make sure the runner's Node exists in persistent storage, then hand over to runtime\main.js.
4
- # (That Node only runs agent-sandbox itself; the agent's `node` comes from nvm.)
5
- $ErrorActionPreference = 'Stop'
6
- $ProgressPreference = 'SilentlyContinue' # progress bars make PS 5.1 downloads extremely slow
7
- [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
8
-
9
- $launchDir = 'C:\AgentLaunch'
10
- $dataDir = 'C:\AgentData'
11
- $hostNodeDir = 'C:\HostTools\node'
12
- $hostNvm = 'C:\HostTools\nvm'
13
- $toolsDir = Join-Path $dataDir 'tools'
14
- # The runner's Node lives with nvm's versions (tools\nvm\v<version>), so nvm reuses it instead of copying it again.
15
- $nvmDir = Join-Path $toolsDir 'nvm'
16
- $runnerFile = Join-Path $toolsDir 'runner-node.txt'
17
- $nodeDir = $null
18
- $nodeExe = $null
19
- $logsDir = Join-Path $dataDir 'logs'
20
- $runDir = Join-Path $dataDir 'run'
21
- foreach ($dir in @($logsDir, $toolsDir, $nvmDir, $runDir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
22
- $host.UI.RawUI.WindowTitle = 'Agent Sandbox'
23
- Write-Host 'Agent sandbox bootstrap started.' -ForegroundColor Cyan
24
- $transcript = Join-Path $logsDir ('bootstrap-' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.log')
25
- Start-Transcript -Path $transcript | Out-Null
26
- $transcriptStopped = $false
27
- # -Encoding UTF8: Windows PowerShell 5.1 otherwise reads it in the ANSI code page (non-ASCII paths and names break).
28
- $session = Get-Content -LiteralPath (Join-Path $launchDir 'session.json') -Raw -Encoding UTF8 | ConvertFrom-Json
29
- $hostVersion = [string]$session.hostTools.nodeVersion
30
-
31
- function Set-Status([string]$Phase, [string]$Message) {
32
- # Same shape as SandboxStatus in src/shared/schema.ts; written atomically so the host never reads half a file.
33
- $status = [ordered]@{ launchId = $session.launchId; phase = $Phase; message = $Message; updatedAt = (Get-Date).ToUniversalTime().ToString('o'); workspaceHost = $session.workspaceHost; guestWorkspace = $session.workspace; headless = [bool]$session.headless }
34
- $file = Join-Path $runDir 'sandbox.json'
35
- [IO.File]::WriteAllText("$file.tmp", ($status | ConvertTo-Json -Compress))
36
- Move-Item -LiteralPath "$file.tmp" -Destination $file -Force
37
- }
38
-
39
- function Invoke-Download([string]$Uri, [string]$OutFile) {
40
- # The sandbox network can take a few seconds to come up, so retry.
41
- for ($attempt = 1; $attempt -le 5; $attempt++) {
42
- try {
43
- if ($OutFile) { Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing; return $null }
44
- $response = Invoke-WebRequest -Uri $Uri -UseBasicParsing
45
- if ($response.Content -is [byte[]]) { return [Text.Encoding]::UTF8.GetString($response.Content) }
46
- return [string]$response.Content
47
- }
48
- catch {
49
- if ($attempt -eq 5) { throw }
50
- Write-Warning "Download failed (attempt $attempt of 5): $($_.Exception.Message). Retrying ..."
51
- Start-Sleep -Seconds 4
52
- }
53
- }
54
- }
55
-
56
- function Set-Runner([string]$Version) {
57
- $script:nodeDir = Join-Path $nvmDir ('v' + $Version.TrimStart('v'))
58
- $script:nodeExe = Join-Path $script:nodeDir 'node.exe'
59
- }
60
-
61
- function Test-Node {
62
- if (-not (Test-Path -LiteralPath $nodeExe)) { return $false }
63
- try { $null = & $nodeExe --version; return ($LASTEXITCODE -eq 0) } catch { return $false }
64
- }
65
-
66
- function Find-CachedRunner {
67
- # This PC's version first (nvm will want it anyway), else whichever version the last boot used.
68
- $last = if (Test-Path -LiteralPath $runnerFile) { (Get-Content -LiteralPath $runnerFile -Raw -Encoding UTF8).Trim() } else { '' }
69
- foreach ($version in @($hostVersion, $last)) {
70
- if ($version -notmatch '^\d+\.\d+\.\d+$') { continue }
71
- Set-Runner $version
72
- if (Test-Node) { return $true }
73
- }
74
- return $false
75
- }
76
-
77
- function Publish-Directory([string]$Staging, [string]$Destination) {
78
- if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Recurse -Force }
79
- Move-Item -LiteralPath $Staging -Destination $Destination
80
- }
81
-
82
- function Get-Size([string]$Path) {
83
- if (-not (Test-Path -LiteralPath $Path)) { return 0 }
84
- $sum = (Get-ChildItem -LiteralPath $Path -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
85
- if ($null -eq $sum) { return 0 } else { return [long]$sum }
86
- }
87
-
88
- function Invoke-Robocopy([string]$Label, [string]$From, [string]$To, [string[]]$Options, [long]$Total) {
89
- # Runs robocopy in the background and prints how far it has got, so a slow copy never looks frozen.
90
- # /R:2 /W:1: robocopy's default is 1,000,000 retries 30 s apart, which hangs "forever" on one unreadable file.
91
- $totalMb = [math]::Round($Total / 1MB, 1)
92
- Write-Host " $Label ($totalMb MB) ..."
93
- $argList = @("`"$From`"", "`"$To`"") + $Options + @('/XJ', '/R:2', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NP')
94
- $process = Start-Process -FilePath 'robocopy.exe' -ArgumentList $argList -NoNewWindow -PassThru
95
- $null = $process.Handle # keeps ExitCode readable after the process exits
96
- $started = Get-Date
97
- while (-not $process.WaitForExit(2000)) {
98
- $done = Get-Size $To
99
- $pct = if ($Total -gt 0) { [math]::Min(100, [math]::Floor($done * 100 / $Total)) } else { 0 }
100
- $secs = [int]((Get-Date) - $started).TotalSeconds
101
- Write-Host (" {0}: {1} of {2} MB ({3}%), {4}s" -f $Label, [math]::Round($done / 1MB, 1), $totalMb, $pct, $secs)
102
- }
103
- $code = $process.ExitCode
104
- if ($code -ge 8) { Write-Warning "robocopy failed copying $Label (exit code $code)"; return $false }
105
- Write-Host (" {0}: done in {1}s" -f $Label, [int]((Get-Date) - $started).TotalSeconds)
106
- return $true
107
- }
108
-
109
- function Copy-NodeFromHost {
110
- # The host maps one of these: nvm (when this PC's Node is an nvm version) or the plain Node folder.
111
- if ($hostVersion -notmatch '^\d+\.\d+\.\d+$') { return $false }
112
- $hostNode = @((Join-Path $hostNvm "v$hostVersion"), $hostNodeDir) | Where-Object { Test-Path -LiteralPath (Join-Path $_ 'node.exe') } | Select-Object -First 1
113
- if (-not $hostNode) { return $false }
114
- # Only node.exe, the npm shims and node_modules\npm (same list as copyNode in src/guest/env.ts): nothing else is
115
- # needed, and the folder can hold every global npm package, i.e. tens of thousands of small files over a slow mapping.
116
- Write-Host "Copying Node $hostVersion from this PC ($hostNode) ..."
117
- Set-Status 'booting' 'Copying Node from this PC'
118
- $staging = Join-Path $toolsDir 'node-staging'
119
- if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
120
- $files = @('node.exe', 'npm', 'npm.cmd', 'npx', 'npx.cmd')
121
- $filesSize = ($files | ForEach-Object { Get-Item -LiteralPath (Join-Path $hostNode $_) -ErrorAction SilentlyContinue } | Measure-Object -Property Length -Sum).Sum
122
- $npmFrom = Join-Path $hostNode 'node_modules\npm'
123
- $ok = (Invoke-Robocopy -Label 'node.exe' -From $hostNode -To $staging -Options ($files + '/J') -Total ([long]$filesSize)) -and
124
- (Invoke-Robocopy -Label 'npm' -From $npmFrom -To (Join-Path $staging 'node_modules\npm') -Options @('/E', '/MT:16') -Total (Get-Size $npmFrom))
125
- if (-not $ok) { return $false }
126
- Set-Runner $hostVersion
127
- Publish-Directory -Staging $staging -Destination $nodeDir
128
- return (Test-Node)
129
- }
130
-
131
- function Mount-MirrorDrives {
132
- # Windows Sandbox can only map folders onto C:, so a guest path like F:\Games\X is mapped to C:\Mirror\F\Games\X
133
- # and F: is made here with subst (session.substDrives, written by the host; see src/shared/guest-paths.ts).
134
- # subst drives belong to the logon session: everything started from here (main.js, the daemon, ptyhost, hooks,
135
- # Windows Terminal tabs) and Explorer share it, because the sandbox user has one full token (UAC is off).
136
- # Returns the letters that could NOT be mounted; an existing drive is reported, never replaced.
137
- $subst = Join-Path $env:SystemRoot 'System32\subst.exe'
138
- $failed = @()
139
- foreach ($drive in @($session.substDrives)) {
140
- if ($null -eq $drive) { continue }
141
- $letter = ([string]$drive.letter).ToUpperInvariant()
142
- $target = [string]$drive.target
143
- if ($letter -notmatch '^[D-Z]$' -or $target -ne "C:\Mirror\$letter") { throw "Invalid subst drive in session.json: '$letter' -> '$target'." }
144
- $existing = (& $subst) | ForEach-Object { if ($_ -match "^${letter}:\\: => (.+)$") { $Matches[1].Trim() } } | Select-Object -First 1
145
- if ($existing -and $existing -eq $target) { Write-Host " ${letter}: already points to $target."; continue }
146
- if ([IO.Directory]::GetLogicalDrives() | Where-Object { $_.Substring(0, 1).ToUpperInvariant() -eq $letter }) {
147
- Write-Warning "Drive ${letter}: already exists in the sandbox, so it was left alone. Folders mapped to ${letter}:\... are at $target\... instead."
148
- $failed += $letter
149
- continue
150
- }
151
- if (-not (Test-Path -LiteralPath $target -PathType Container)) { New-Item -ItemType Directory -Path $target -Force | Out-Null }
152
- # subst.exe exits 0 even when it fails, so check the drive itself.
153
- & $subst "${letter}:" $target | Out-Host
154
- if (Test-Path -LiteralPath "${letter}:\") { Write-Host " ${letter}: -> $target" }
155
- else { Write-Warning "subst ${letter}: $target did not work. Folders mapped to ${letter}:\... are at $target\... instead."; $failed += $letter }
156
- }
157
- return ,$failed
158
- }
159
-
160
- function Update-Environment {
161
- # main.js writes PATH and the tool variables machine-wide; reload them so this (-NoExit) shell has nvm, git, etc.
162
- foreach ($scope in @('Machine', 'User')) {
163
- $vars = [Environment]::GetEnvironmentVariables($scope)
164
- foreach ($name in $vars.Keys) { if ($name -ne 'Path') { Set-Item -LiteralPath "env:$name" -Value ([string]$vars[$name]) } }
165
- }
166
- $env:PATH = ([Environment]::GetEnvironmentVariable('Path', 'Machine'), [Environment]::GetEnvironmentVariable('Path', 'User') | Where-Object { $_ }) -join ';'
167
- }
168
-
169
- function Install-NodeFromWeb {
170
- $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
171
- Write-Host 'Looking up the Node LTS release ...'
172
- $index = (Invoke-Download -Uri 'https://nodejs.org/dist/index.json') | ConvertFrom-Json
173
- $release = $index | Where-Object { $_.lts -and ($_.files -contains "win-$arch-zip") } | Select-Object -First 1
174
- if ($null -eq $release) { throw "No Node LTS release found ($arch)." }
175
- $version = [string]$release.version
176
- if ($version -notmatch '^v\d+\.\d+\.\d+$') { throw "Unexpected Node version string: $version" }
177
- $zipName = "node-$version-win-$arch.zip"
178
- $baseUrl = "https://nodejs.org/dist/$version"
179
- $work = Join-Path $toolsDir ('node-work-' + [guid]::NewGuid().ToString('N'))
180
- New-Item -ItemType Directory -Path $work -Force | Out-Null
181
- try {
182
- $zipPath = Join-Path $work $zipName
183
- Write-Host "Downloading $zipName ..."
184
- $null = Invoke-Download -Uri "$baseUrl/$zipName" -OutFile $zipPath
185
- $sums = Invoke-Download -Uri "$baseUrl/SHASUMS256.txt"
186
- $line = ($sums -split "`n") | Where-Object { $_.Trim() -like "* $zipName" } | Select-Object -First 1
187
- if (-not $line) { throw "No checksum entry for $zipName" }
188
- $expected = ($line.Trim() -split '\s+')[0]
189
- if ((Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash -ne $expected.ToUpper()) { throw 'Node download failed checksum verification.' }
190
- $extractDir = Join-Path $work 'extracted'
191
- New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
192
- & tar.exe -xf $zipPath -C $extractDir
193
- if ($LASTEXITCODE -ne 0) { $global:LASTEXITCODE = 0; Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force }
194
- $inner = Join-Path $extractDir "node-$version-win-$arch"
195
- if (-not (Test-Path -LiteralPath (Join-Path $inner 'node.exe'))) { throw 'Unexpected Node archive layout.' }
196
- Set-Runner $version
197
- Publish-Directory -Staging $inner -Destination $nodeDir
198
- }
199
- finally {
200
- if (Test-Path -LiteralPath $work) { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue }
201
- }
202
- }
203
-
204
- try {
205
- # First: hooks, toolchains and the agent all run in (or below) the workspace, which may be on one of these drives.
206
- $unmounted = Mount-MirrorDrives
207
- $workspaceDrive = ([string]$session.workspace).Substring(0, 1).ToUpperInvariant()
208
- if ($unmounted -contains $workspaceDrive) {
209
- throw "The workspace $($session.workspace) needs drive ${workspaceDrive}:, which could not be created in the sandbox (see the warning above). Set guestWorkspace to ""auto"" (C:\Workspace\<name>) for this project."
210
- }
211
- Set-Status 'booting' 'Preparing the runner'
212
- $provisioning = $session.provisioning
213
- if (-not $provisioning.refresh -and (Find-CachedRunner)) {
214
- Write-Host ('Runner Node ' + (& $nodeExe --version) + ' cached in persistent storage.')
215
- }
216
- else {
217
- $ready = $false
218
- if ($provisioning.copyFromHost) {
219
- $ready = Copy-NodeFromHost
220
- if (-not $ready) {
221
- if (-not $provisioning.fallbackToInstall) { throw 'Could not copy Node from this PC and provisioning.fallbackToInstall is false.' }
222
- Write-Warning 'Host Node unavailable; downloading Node instead.'
223
- }
224
- }
225
- if (-not $ready) { Install-NodeFromWeb }
226
- if (-not (Test-Node)) { throw 'Node is not runnable after provisioning.' }
227
- Write-Host ('Runner Node ' + (& $nodeExe --version) + ' ready.')
228
- }
229
- [IO.File]::WriteAllText($runnerFile, (Split-Path -Leaf $nodeDir).TrimStart('v'))
230
- # Older versions kept a full copy of the host's Node folder here.
231
- $legacy = Join-Path $toolsDir 'node'
232
- if (Test-Path -LiteralPath $legacy) { Remove-Item -LiteralPath $legacy -Recurse -Force -ErrorAction SilentlyContinue }
233
-
234
- $env:PATH = "$nodeDir;$env:PATH"
235
- Set-Location -LiteralPath $session.workspace
236
- Stop-Transcript | Out-Null
237
- $transcriptStopped = $true
238
- & $nodeExe (Join-Path $launchDir 'runtime\main.js')
239
- $code = $LASTEXITCODE
240
- if ($code -ne 0) {
241
- # main.js reports its own failures; this catches it dying before it could (e.g. a broken Node).
242
- $current = Get-Content -LiteralPath (Join-Path $runDir 'sandbox.json') -Raw -Encoding UTF8 -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue
243
- if ($null -eq $current -or $current.phase -ne 'failed') { Set-Status 'failed' ("The sandbox runner stopped (exit code $code). See C:\AgentData\logs.") }
244
- Write-Host "The sandbox runner stopped with exit code $code. Logs: $logsDir" -ForegroundColor Red
245
- }
246
- Update-Environment
247
- Write-Host "This window stays open in $($session.workspace)."
248
- }
249
- catch {
250
- $report = ($_ | Format-List * -Force | Out-String) + "`r`n" + $_.ScriptStackTrace
251
- Set-Content -Path (Join-Path $logsDir 'bootstrap-error.txt') -Value $report -Encoding UTF8
252
- try { Set-Status 'failed' ('Bootstrap failed: ' + $_.Exception.Message) } catch { }
253
- Write-Host $report -ForegroundColor Red
254
- Write-Host "Saved to $logsDir\bootstrap-error.txt" -ForegroundColor Yellow
255
- }
256
- finally {
257
- if (-not $transcriptStopped) { try { Stop-Transcript | Out-Null } catch { } }
258
- }
1
+ #Requires -Version 5.1
2
+ # Runs INSIDE Windows Sandbox (Windows PowerShell 5.1) in the sandbox window, started by logon.cmd.
3
+ # Its only job: make sure the runner's Node exists in persistent storage, then hand over to runtime\main.js.
4
+ # (That Node only runs agent-sandbox itself; the agent's `node` comes from nvm.)
5
+ $ErrorActionPreference = 'Stop'
6
+ $ProgressPreference = 'SilentlyContinue' # progress bars make PS 5.1 downloads extremely slow
7
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
8
+
9
+ $launchDir = 'C:\AgentLaunch'
10
+ $dataDir = 'C:\AgentData'
11
+ $hostNodeDir = 'C:\HostTools\node'
12
+ $hostNvm = 'C:\HostTools\nvm'
13
+ $toolsDir = Join-Path $dataDir 'tools'
14
+ # The runner's Node lives with nvm's versions (tools\nvm\v<version>), so nvm reuses it instead of copying it again.
15
+ $nvmDir = Join-Path $toolsDir 'nvm'
16
+ $runnerFile = Join-Path $toolsDir 'runner-node.txt'
17
+ $nodeDir = $null
18
+ $nodeExe = $null
19
+ $logsDir = Join-Path $dataDir 'logs'
20
+ $runDir = Join-Path $dataDir 'run'
21
+ foreach ($dir in @($logsDir, $toolsDir, $nvmDir, $runDir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
22
+ $host.UI.RawUI.WindowTitle = 'Agent Sandbox'
23
+ Write-Host 'Agent sandbox bootstrap started.' -ForegroundColor Cyan
24
+ $transcript = Join-Path $logsDir ('bootstrap-' + (Get-Date -Format 'yyyyMMdd-HHmmss') + '.log')
25
+ Start-Transcript -Path $transcript | Out-Null
26
+ $transcriptStopped = $false
27
+ # -Encoding UTF8: Windows PowerShell 5.1 otherwise reads it in the ANSI code page (non-ASCII paths and names break).
28
+ $session = Get-Content -LiteralPath (Join-Path $launchDir 'session.json') -Raw -Encoding UTF8 | ConvertFrom-Json
29
+ $hostVersion = [string]$session.hostTools.nodeVersion
30
+
31
+ function Set-Status([string]$Phase, [string]$Message) {
32
+ # Same shape as SandboxStatus in src/shared/schema.ts; written atomically so the host never reads half a file.
33
+ $status = [ordered]@{ launchId = $session.launchId; phase = $Phase; message = $Message; updatedAt = (Get-Date).ToUniversalTime().ToString('o'); workspaceHost = $session.workspaceHost; guestWorkspace = $session.workspace; headless = [bool]$session.headless }
34
+ $file = Join-Path $runDir 'sandbox.json'
35
+ [IO.File]::WriteAllText("$file.tmp", ($status | ConvertTo-Json -Compress))
36
+ Move-Item -LiteralPath "$file.tmp" -Destination $file -Force
37
+ }
38
+
39
+ function Invoke-Download([string]$Uri, [string]$OutFile) {
40
+ # The sandbox network can take a few seconds to come up, so retry.
41
+ for ($attempt = 1; $attempt -le 5; $attempt++) {
42
+ try {
43
+ if ($OutFile) { Invoke-WebRequest -Uri $Uri -OutFile $OutFile -UseBasicParsing; return $null }
44
+ $response = Invoke-WebRequest -Uri $Uri -UseBasicParsing
45
+ if ($response.Content -is [byte[]]) { return [Text.Encoding]::UTF8.GetString($response.Content) }
46
+ return [string]$response.Content
47
+ }
48
+ catch {
49
+ if ($attempt -eq 5) { throw }
50
+ Write-Warning "Download failed (attempt $attempt of 5): $($_.Exception.Message). Retrying ..."
51
+ Start-Sleep -Seconds 4
52
+ }
53
+ }
54
+ }
55
+
56
+ function Set-Runner([string]$Version) {
57
+ $script:nodeDir = Join-Path $nvmDir ('v' + $Version.TrimStart('v'))
58
+ $script:nodeExe = Join-Path $script:nodeDir 'node.exe'
59
+ }
60
+
61
+ function Test-Node {
62
+ if (-not (Test-Path -LiteralPath $nodeExe)) { return $false }
63
+ try { $null = & $nodeExe --version; return ($LASTEXITCODE -eq 0) } catch { return $false }
64
+ }
65
+
66
+ function Find-CachedRunner {
67
+ # This PC's version first (nvm will want it anyway), else whichever version the last boot used.
68
+ $last = if (Test-Path -LiteralPath $runnerFile) { (Get-Content -LiteralPath $runnerFile -Raw -Encoding UTF8).Trim() } else { '' }
69
+ foreach ($version in @($hostVersion, $last)) {
70
+ if ($version -notmatch '^\d+\.\d+\.\d+$') { continue }
71
+ Set-Runner $version
72
+ if (Test-Node) { return $true }
73
+ }
74
+ return $false
75
+ }
76
+
77
+ function Publish-Directory([string]$Staging, [string]$Destination) {
78
+ if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Recurse -Force }
79
+ Move-Item -LiteralPath $Staging -Destination $Destination
80
+ }
81
+
82
+ function Get-Size([string]$Path) {
83
+ if (-not (Test-Path -LiteralPath $Path)) { return 0 }
84
+ $sum = (Get-ChildItem -LiteralPath $Path -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum
85
+ if ($null -eq $sum) { return 0 } else { return [long]$sum }
86
+ }
87
+
88
+ function Invoke-Robocopy([string]$Label, [string]$From, [string]$To, [string[]]$Options, [long]$Total) {
89
+ # Runs robocopy in the background and prints how far it has got, so a slow copy never looks frozen.
90
+ # /R:2 /W:1: robocopy's default is 1,000,000 retries 30 s apart, which hangs "forever" on one unreadable file.
91
+ $totalMb = [math]::Round($Total / 1MB, 1)
92
+ Write-Host " $Label ($totalMb MB) ..."
93
+ $argList = @("`"$From`"", "`"$To`"") + $Options + @('/XJ', '/R:2', '/W:1', '/NFL', '/NDL', '/NJH', '/NJS', '/NP')
94
+ $process = Start-Process -FilePath 'robocopy.exe' -ArgumentList $argList -NoNewWindow -PassThru
95
+ $null = $process.Handle # keeps ExitCode readable after the process exits
96
+ $started = Get-Date
97
+ while (-not $process.WaitForExit(2000)) {
98
+ $done = Get-Size $To
99
+ $pct = if ($Total -gt 0) { [math]::Min(100, [math]::Floor($done * 100 / $Total)) } else { 0 }
100
+ $secs = [int]((Get-Date) - $started).TotalSeconds
101
+ Write-Host (" {0}: {1} of {2} MB ({3}%), {4}s" -f $Label, [math]::Round($done / 1MB, 1), $totalMb, $pct, $secs)
102
+ }
103
+ $code = $process.ExitCode
104
+ if ($code -ge 8) { Write-Warning "robocopy failed copying $Label (exit code $code)"; return $false }
105
+ Write-Host (" {0}: done in {1}s" -f $Label, [int]((Get-Date) - $started).TotalSeconds)
106
+ return $true
107
+ }
108
+
109
+ function Copy-NodeFromHost {
110
+ # The host maps one of these: nvm (when this PC's Node is an nvm version) or the plain Node folder.
111
+ if ($hostVersion -notmatch '^\d+\.\d+\.\d+$') { return $false }
112
+ $hostNode = @((Join-Path $hostNvm "v$hostVersion"), $hostNodeDir) | Where-Object { Test-Path -LiteralPath (Join-Path $_ 'node.exe') } | Select-Object -First 1
113
+ if (-not $hostNode) { return $false }
114
+ # Only node.exe, the npm shims and node_modules\npm (same list as copyNode in src/guest/env.ts): nothing else is
115
+ # needed, and the folder can hold every global npm package, i.e. tens of thousands of small files over a slow mapping.
116
+ Write-Host "Copying Node $hostVersion from this PC ($hostNode) ..."
117
+ Set-Status 'booting' 'Copying Node from this PC'
118
+ $staging = Join-Path $toolsDir 'node-staging'
119
+ if (Test-Path -LiteralPath $staging) { Remove-Item -LiteralPath $staging -Recurse -Force }
120
+ $files = @('node.exe', 'npm', 'npm.cmd', 'npx', 'npx.cmd')
121
+ $filesSize = ($files | ForEach-Object { Get-Item -LiteralPath (Join-Path $hostNode $_) -ErrorAction SilentlyContinue } | Measure-Object -Property Length -Sum).Sum
122
+ $npmFrom = Join-Path $hostNode 'node_modules\npm'
123
+ $ok = (Invoke-Robocopy -Label 'node.exe' -From $hostNode -To $staging -Options ($files + '/J') -Total ([long]$filesSize)) -and
124
+ (Invoke-Robocopy -Label 'npm' -From $npmFrom -To (Join-Path $staging 'node_modules\npm') -Options @('/E', '/MT:16') -Total (Get-Size $npmFrom))
125
+ if (-not $ok) { return $false }
126
+ Set-Runner $hostVersion
127
+ Publish-Directory -Staging $staging -Destination $nodeDir
128
+ return (Test-Node)
129
+ }
130
+
131
+ function Mount-MirrorDrives {
132
+ # Windows Sandbox can only map folders onto C:, so a guest path like F:\Games\X is mapped to C:\Mirror\F\Games\X
133
+ # and F: is made here with subst (session.substDrives, written by the host; see src/shared/guest-paths.ts).
134
+ # subst drives belong to the logon session: everything started from here (main.js, the daemon, ptyhost, hooks,
135
+ # Windows Terminal tabs) and Explorer share it, because the sandbox user has one full token (UAC is off).
136
+ # Returns the letters that could NOT be mounted; an existing drive is reported, never replaced.
137
+ $subst = Join-Path $env:SystemRoot 'System32\subst.exe'
138
+ $failed = @()
139
+ foreach ($drive in @($session.substDrives)) {
140
+ if ($null -eq $drive) { continue }
141
+ $letter = ([string]$drive.letter).ToUpperInvariant()
142
+ $target = [string]$drive.target
143
+ if ($letter -notmatch '^[D-Z]$' -or $target -ne "C:\Mirror\$letter") { throw "Invalid subst drive in session.json: '$letter' -> '$target'." }
144
+ $existing = (& $subst) | ForEach-Object { if ($_ -match "^${letter}:\\: => (.+)$") { $Matches[1].Trim() } } | Select-Object -First 1
145
+ if ($existing -and $existing -eq $target) { Write-Host " ${letter}: already points to $target."; continue }
146
+ if ([IO.Directory]::GetLogicalDrives() | Where-Object { $_.Substring(0, 1).ToUpperInvariant() -eq $letter }) {
147
+ Write-Warning "Drive ${letter}: already exists in the sandbox, so it was left alone. Folders mapped to ${letter}:\... are at $target\... instead."
148
+ $failed += $letter
149
+ continue
150
+ }
151
+ if (-not (Test-Path -LiteralPath $target -PathType Container)) { New-Item -ItemType Directory -Path $target -Force | Out-Null }
152
+ # subst.exe exits 0 even when it fails, so check the drive itself.
153
+ & $subst "${letter}:" $target | Out-Host
154
+ if (Test-Path -LiteralPath "${letter}:\") { Write-Host " ${letter}: -> $target" }
155
+ else { Write-Warning "subst ${letter}: $target did not work. Folders mapped to ${letter}:\... are at $target\... instead."; $failed += $letter }
156
+ }
157
+ return ,$failed
158
+ }
159
+
160
+ function Update-Environment {
161
+ # main.js writes PATH and the tool variables machine-wide; reload them so this (-NoExit) shell has nvm, git, etc.
162
+ foreach ($scope in @('Machine', 'User')) {
163
+ $vars = [Environment]::GetEnvironmentVariables($scope)
164
+ foreach ($name in $vars.Keys) { if ($name -ne 'Path') { Set-Item -LiteralPath "env:$name" -Value ([string]$vars[$name]) } }
165
+ }
166
+ $env:PATH = ([Environment]::GetEnvironmentVariable('Path', 'Machine'), [Environment]::GetEnvironmentVariable('Path', 'User') | Where-Object { $_ }) -join ';'
167
+ }
168
+
169
+ function Install-NodeFromWeb {
170
+ $arch = if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') { 'arm64' } else { 'x64' }
171
+ Write-Host 'Looking up the Node LTS release ...'
172
+ $index = (Invoke-Download -Uri 'https://nodejs.org/dist/index.json') | ConvertFrom-Json
173
+ $release = $index | Where-Object { $_.lts -and ($_.files -contains "win-$arch-zip") } | Select-Object -First 1
174
+ if ($null -eq $release) { throw "No Node LTS release found ($arch)." }
175
+ $version = [string]$release.version
176
+ if ($version -notmatch '^v\d+\.\d+\.\d+$') { throw "Unexpected Node version string: $version" }
177
+ $zipName = "node-$version-win-$arch.zip"
178
+ $baseUrl = "https://nodejs.org/dist/$version"
179
+ $work = Join-Path $toolsDir ('node-work-' + [guid]::NewGuid().ToString('N'))
180
+ New-Item -ItemType Directory -Path $work -Force | Out-Null
181
+ try {
182
+ $zipPath = Join-Path $work $zipName
183
+ Write-Host "Downloading $zipName ..."
184
+ $null = Invoke-Download -Uri "$baseUrl/$zipName" -OutFile $zipPath
185
+ $sums = Invoke-Download -Uri "$baseUrl/SHASUMS256.txt"
186
+ $line = ($sums -split "`n") | Where-Object { $_.Trim() -like "* $zipName" } | Select-Object -First 1
187
+ if (-not $line) { throw "No checksum entry for $zipName" }
188
+ $expected = ($line.Trim() -split '\s+')[0]
189
+ if ((Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash -ne $expected.ToUpper()) { throw 'Node download failed checksum verification.' }
190
+ $extractDir = Join-Path $work 'extracted'
191
+ New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
192
+ & tar.exe -xf $zipPath -C $extractDir
193
+ if ($LASTEXITCODE -ne 0) { $global:LASTEXITCODE = 0; Expand-Archive -LiteralPath $zipPath -DestinationPath $extractDir -Force }
194
+ $inner = Join-Path $extractDir "node-$version-win-$arch"
195
+ if (-not (Test-Path -LiteralPath (Join-Path $inner 'node.exe'))) { throw 'Unexpected Node archive layout.' }
196
+ Set-Runner $version
197
+ Publish-Directory -Staging $inner -Destination $nodeDir
198
+ }
199
+ finally {
200
+ if (Test-Path -LiteralPath $work) { Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue }
201
+ }
202
+ }
203
+
204
+ try {
205
+ # First: hooks, toolchains and the agent all run in (or below) the workspace, which may be on one of these drives.
206
+ $unmounted = Mount-MirrorDrives
207
+ $workspaceDrive = ([string]$session.workspace).Substring(0, 1).ToUpperInvariant()
208
+ if ($unmounted -contains $workspaceDrive) {
209
+ throw "The workspace $($session.workspace) needs drive ${workspaceDrive}:, which could not be created in the sandbox (see the warning above). Set guestWorkspace to ""auto"" (C:\Workspace\<name>) for this project."
210
+ }
211
+ Set-Status 'booting' 'Preparing the runner'
212
+ $provisioning = $session.provisioning
213
+ if (-not $provisioning.refresh -and (Find-CachedRunner)) {
214
+ Write-Host ('Runner Node ' + (& $nodeExe --version) + ' cached in persistent storage.')
215
+ }
216
+ else {
217
+ $ready = $false
218
+ if ($provisioning.copyFromHost) {
219
+ $ready = Copy-NodeFromHost
220
+ if (-not $ready) {
221
+ if (-not $provisioning.fallbackToInstall) { throw 'Could not copy Node from this PC and provisioning.fallbackToInstall is false.' }
222
+ Write-Warning 'Host Node unavailable; downloading Node instead.'
223
+ }
224
+ }
225
+ if (-not $ready) { Install-NodeFromWeb }
226
+ if (-not (Test-Node)) { throw 'Node is not runnable after provisioning.' }
227
+ Write-Host ('Runner Node ' + (& $nodeExe --version) + ' ready.')
228
+ }
229
+ [IO.File]::WriteAllText($runnerFile, (Split-Path -Leaf $nodeDir).TrimStart('v'))
230
+ # Older versions kept a full copy of the host's Node folder here.
231
+ $legacy = Join-Path $toolsDir 'node'
232
+ if (Test-Path -LiteralPath $legacy) { Remove-Item -LiteralPath $legacy -Recurse -Force -ErrorAction SilentlyContinue }
233
+
234
+ $env:PATH = "$nodeDir;$env:PATH"
235
+ Set-Location -LiteralPath $session.workspace
236
+ Stop-Transcript | Out-Null
237
+ $transcriptStopped = $true
238
+ & $nodeExe (Join-Path $launchDir 'runtime\main.js')
239
+ $code = $LASTEXITCODE
240
+ if ($code -ne 0) {
241
+ # main.js reports its own failures; this catches it dying before it could (e.g. a broken Node).
242
+ $current = Get-Content -LiteralPath (Join-Path $runDir 'sandbox.json') -Raw -Encoding UTF8 -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue
243
+ if ($null -eq $current -or $current.phase -ne 'failed') { Set-Status 'failed' ("The sandbox runner stopped (exit code $code). See C:\AgentData\logs.") }
244
+ Write-Host "The sandbox runner stopped with exit code $code. Logs: $logsDir" -ForegroundColor Red
245
+ }
246
+ Update-Environment
247
+ Write-Host "This window stays open in $($session.workspace)."
248
+ }
249
+ catch {
250
+ $report = ($_ | Format-List * -Force | Out-String) + "`r`n" + $_.ScriptStackTrace
251
+ Set-Content -Path (Join-Path $logsDir 'bootstrap-error.txt') -Value $report -Encoding UTF8
252
+ try { Set-Status 'failed' ('Bootstrap failed: ' + $_.Exception.Message) } catch { }
253
+ Write-Host $report -ForegroundColor Red
254
+ Write-Host "Saved to $logsDir\bootstrap-error.txt" -ForegroundColor Yellow
255
+ }
256
+ finally {
257
+ if (-not $transcriptStopped) { try { Stop-Transcript | Out-Null } catch { } }
258
+ }
@@ -0,0 +1,51 @@
1
+ #Requires -Version 5.1
2
+ # Caps how much of the PC's CPU the sandbox may use (sandbox.cpuLimit). Windows Sandbox has no CPU setting and gets
3
+ # every processor, so heavy builds inside can take the whole PC. This puts logon.cmd (the parent of this script),
4
+ # before it starts anything, into a Windows job object with a hard CPU-rate cap; everything it starts inherits it:
5
+ # Windows Terminal and its tabs, the boot, the agents, installs and builds. Programs started from the sandbox's Start
6
+ # menu are not covered. Never stops the boot: any problem is logged and the sandbox starts uncapped.
7
+ param([int]$ProcessId = 0, [int]$Percent = 0)
8
+ $ErrorActionPreference = 'Stop'
9
+ $log = 'C:\AgentData\logs\logon-hit.txt'
10
+ try {
11
+ if ($Percent -eq 0) {
12
+ $session = Get-Content -LiteralPath 'C:\AgentLaunch\session.json' -Raw -Encoding UTF8 | ConvertFrom-Json
13
+ if ($null -eq $session.cpuLimitPercent) { exit 0 }
14
+ $Percent = [int]$session.cpuLimitPercent
15
+ }
16
+ if ($Percent -lt 1 -or $Percent -ge 100) { exit 0 }
17
+ if ($ProcessId -eq 0) { $ProcessId = [int](Get-CimInstance Win32_Process -Filter "ProcessId=$PID").ParentProcessId }
18
+ Add-Type -TypeDefinition @'
19
+ using System;
20
+ using System.ComponentModel;
21
+ using System.Runtime.InteropServices;
22
+ public static class SandboxCpuCap {
23
+ [StructLayout(LayoutKind.Sequential)]
24
+ private struct CpuRate { public uint ControlFlags; public uint Rate; }
25
+ [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr CreateJobObject(IntPtr attributes, string name);
26
+ [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetInformationJobObject(IntPtr job, int infoClass, ref CpuRate info, int length);
27
+ [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
28
+ [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
29
+ [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr handle);
30
+ // JobObjectCpuRateControlInformation; ENABLE | HARD_CAP; the rate is in 1/100 % of all processors.
31
+ public static void Apply(int pid, int percent) {
32
+ IntPtr job = CreateJobObject(IntPtr.Zero, null);
33
+ if (job == IntPtr.Zero) throw new Win32Exception();
34
+ CpuRate rate = new CpuRate();
35
+ rate.ControlFlags = 0x1 | 0x4;
36
+ rate.Rate = (uint)(percent * 100);
37
+ if (!SetInformationJobObject(job, 15, ref rate, Marshal.SizeOf(typeof(CpuRate)))) throw new Win32Exception();
38
+ // PROCESS_SET_QUOTA | PROCESS_TERMINATE: what AssignProcessToJobObject needs.
39
+ IntPtr process = OpenProcess(0x0100 | 0x0001, false, pid);
40
+ if (process == IntPtr.Zero) throw new Win32Exception();
41
+ try { if (!AssignProcessToJobObject(job, process)) throw new Win32Exception(); } finally { CloseHandle(process); }
42
+ // The job's handle may close: it lives on while any process in it runs.
43
+ }
44
+ }
45
+ '@
46
+ [SandboxCpuCap]::Apply($ProcessId, $Percent)
47
+ "CPU capped at $Percent% of the PC (sandbox.cpuLimit)." | Add-Content -LiteralPath $log
48
+ } catch {
49
+ "CPU cap not applied (the sandbox runs uncapped): $($_.Exception.Message)" | Add-Content -LiteralPath $log
50
+ }
51
+ exit 0
package/sandbox/logon.cmd CHANGED
@@ -1,16 +1,18 @@
1
- @echo off
2
- rem Windows Sandbox runs LogonCommand in a hidden window, so this opens the VISIBLE sandbox window: Windows Terminal
3
- rem (downloaded once by the host into persistent storage), or a plain PowerShell window if it is missing or fails.
4
- if not exist C:\AgentData\logs mkdir C:\AgentData\logs
5
- echo logon.cmd ran at %date% %time% > C:\AgentData\logs\logon-hit.txt
6
- call C:\AgentLaunch\sandbox\trust.cmd
7
- set "PS=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
8
- set "BOOT=%PS% -NoLogo -NoProfile -ExecutionPolicy Bypass -NoExit -File C:\AgentLaunch\sandbox\bootstrap.ps1"
9
- if not exist C:\AgentData\tools\terminal\wt.exe goto plain
10
- start "" C:\AgentData\tools\terminal\wt.exe --maximized new-tab --title "Agent Sandbox" -d C:\AgentLaunch %BOOT%
11
- rem If Windows Terminal could not start, fall back to PowerShell so there is always a window.
12
- %SystemRoot%\System32\timeout.exe /t 10 /nobreak >nul
13
- %SystemRoot%\System32\tasklist.exe /fi "imagename eq WindowsTerminal.exe" | %SystemRoot%\System32\find.exe /i "WindowsTerminal" >nul && exit /b 0
14
- echo Windows Terminal did not start; using PowerShell. >> C:\AgentData\logs\logon-hit.txt
15
- :plain
16
- start "Agent Sandbox" %BOOT%
1
+ @echo off
2
+ rem Windows Sandbox runs LogonCommand in a hidden window, so this opens the VISIBLE sandbox window: Windows Terminal
3
+ rem (downloaded once by the host into persistent storage), or a plain PowerShell window if it is missing or fails.
4
+ if not exist C:\AgentData\logs mkdir C:\AgentData\logs
5
+ echo logon.cmd ran at %date% %time% > C:\AgentData\logs\logon-hit.txt
6
+ call C:\AgentLaunch\sandbox\trust.cmd
7
+ set "PS=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
8
+ rem sandbox.cpuLimit: this window joins a CPU-capped job first, so everything started below inherits the cap.
9
+ "%PS%" -NoLogo -NoProfile -ExecutionPolicy Bypass -File C:\AgentLaunch\sandbox\cpucap.ps1
10
+ set "BOOT=%PS% -NoLogo -NoProfile -ExecutionPolicy Bypass -NoExit -File C:\AgentLaunch\sandbox\bootstrap.ps1"
11
+ if not exist C:\AgentData\tools\terminal\wt.exe goto plain
12
+ start "" C:\AgentData\tools\terminal\wt.exe --maximized new-tab --title "Agent Sandbox" -d C:\AgentLaunch %BOOT%
13
+ rem If Windows Terminal could not start, fall back to PowerShell so there is always a window.
14
+ %SystemRoot%\System32\timeout.exe /t 10 /nobreak >nul
15
+ %SystemRoot%\System32\tasklist.exe /fi "imagename eq WindowsTerminal.exe" | %SystemRoot%\System32\find.exe /i "WindowsTerminal" >nul && exit /b 0
16
+ echo Windows Terminal did not start; using PowerShell. >> C:\AgentData\logs\logon-hit.txt
17
+ :plain
18
+ start "Agent Sandbox" %BOOT%