windows-development-sandbox 0.0.0-stage → 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 (426) hide show
  1. package/.sandbox-config.example.json +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -2
  4. package/config.example.json +349 -0
  5. package/dist/api.d.ts +31 -0
  6. package/dist/api.js +31 -0
  7. package/dist/bin/agent-sandbox.d.ts +2 -0
  8. package/dist/bin/agent-sandbox.js +3 -0
  9. package/dist/bin/claude-host.d.ts +2 -0
  10. package/dist/bin/claude-host.js +5 -0
  11. package/dist/bin/claude-sbx.d.ts +2 -0
  12. package/dist/bin/claude-sbx.js +3 -0
  13. package/dist/bin/codex-sbx.d.ts +2 -0
  14. package/dist/bin/codex-sbx.js +3 -0
  15. package/dist/bin/gemini-sbx.d.ts +2 -0
  16. package/dist/bin/gemini-sbx.js +3 -0
  17. package/dist/bin/sandbox.d.ts +2 -0
  18. package/dist/bin/sandbox.js +4 -0
  19. package/dist/bin/sbx-hide.d.ts +2 -0
  20. package/dist/bin/sbx-hide.js +3 -0
  21. package/dist/bin/sbx-show.d.ts +2 -0
  22. package/dist/bin/sbx-show.js +3 -0
  23. package/dist/cli/args.d.ts +52 -0
  24. package/dist/cli/args.js +143 -0
  25. package/dist/cli/colors.d.ts +24 -0
  26. package/dist/cli/colors.js +42 -0
  27. package/dist/cli/commands/accounts.d.ts +2 -0
  28. package/dist/cli/commands/accounts.js +52 -0
  29. package/dist/cli/commands/agent-commands.d.ts +6 -0
  30. package/dist/cli/commands/agent-commands.js +61 -0
  31. package/dist/cli/commands/context.d.ts +23 -0
  32. package/dist/cli/commands/context.js +57 -0
  33. package/dist/cli/commands/data/clean.d.ts +15 -0
  34. package/dist/cli/commands/data/clean.js +101 -0
  35. package/dist/cli/commands/data/collect.d.ts +11 -0
  36. package/dist/cli/commands/data/collect.js +54 -0
  37. package/dist/cli/commands/data/index.d.ts +4 -0
  38. package/dist/cli/commands/data/index.js +45 -0
  39. package/dist/cli/commands/data/remove.d.ts +2 -0
  40. package/dist/cli/commands/data/remove.js +30 -0
  41. package/dist/cli/commands/index.d.ts +10 -0
  42. package/dist/cli/commands/index.js +10 -0
  43. package/dist/cli/commands/launch.d.ts +29 -0
  44. package/dist/cli/commands/launch.js +333 -0
  45. package/dist/cli/commands/maintenance.d.ts +8 -0
  46. package/dist/cli/commands/maintenance.js +154 -0
  47. package/dist/cli/commands/project.d.ts +29 -0
  48. package/dist/cli/commands/project.js +63 -0
  49. package/dist/cli/commands/projects.d.ts +14 -0
  50. package/dist/cli/commands/projects.js +146 -0
  51. package/dist/cli/commands/repair/checks.d.ts +2 -0
  52. package/dist/cli/commands/repair/checks.js +170 -0
  53. package/dist/cli/commands/repair/finders.d.ts +30 -0
  54. package/dist/cli/commands/repair/finders.js +108 -0
  55. package/dist/cli/commands/repair/index.d.ts +12 -0
  56. package/dist/cli/commands/repair/index.js +61 -0
  57. package/dist/cli/commands/repair/types.d.ts +26 -0
  58. package/dist/cli/commands/repair/types.js +3 -0
  59. package/dist/cli/commands/restart.d.ts +6 -0
  60. package/dist/cli/commands/restart.js +85 -0
  61. package/dist/cli/commands/sessions.d.ts +12 -0
  62. package/dist/cli/commands/sessions.js +257 -0
  63. package/dist/cli/commands/setup/checks.d.ts +13 -0
  64. package/dist/cli/commands/setup/checks.js +71 -0
  65. package/dist/cli/commands/setup/facts.d.ts +31 -0
  66. package/dist/cli/commands/setup/facts.js +60 -0
  67. package/dist/cli/commands/setup/index.d.ts +2 -0
  68. package/dist/cli/commands/setup/index.js +43 -0
  69. package/dist/cli/commands/user-config.d.ts +8 -0
  70. package/dist/cli/commands/user-config.js +41 -0
  71. package/dist/cli/history.d.ts +11 -0
  72. package/dist/cli/history.js +21 -0
  73. package/dist/cli/index.d.ts +20 -0
  74. package/dist/cli/index.js +121 -0
  75. package/dist/cli/manifest.d.ts +103 -0
  76. package/dist/cli/manifest.js +107 -0
  77. package/dist/cli/menu-multi.d.ts +12 -0
  78. package/dist/cli/menu-multi.js +82 -0
  79. package/dist/cli/menu.d.ts +14 -0
  80. package/dist/cli/menu.js +290 -0
  81. package/dist/cli/progress.d.ts +9 -0
  82. package/dist/cli/progress.js +49 -0
  83. package/dist/cli/settings/edit.d.ts +13 -0
  84. package/dist/cli/settings/edit.js +19 -0
  85. package/dist/cli/settings/index.d.ts +8 -0
  86. package/dist/cli/settings/index.js +131 -0
  87. package/dist/cli/settings/prompts.d.ts +7 -0
  88. package/dist/cli/settings/prompts.js +48 -0
  89. package/dist/cli/settings/schema.d.ts +33 -0
  90. package/dist/cli/settings/schema.js +29 -0
  91. package/dist/cli/validate.d.ts +17 -0
  92. package/dist/cli/validate.js +89 -0
  93. package/dist/core/config.d.ts +17 -0
  94. package/dist/core/config.js +106 -0
  95. package/dist/core/detect-databases.d.ts +15 -0
  96. package/dist/core/detect-databases.js +147 -0
  97. package/dist/core/detect-pwsh.d.ts +18 -0
  98. package/dist/core/detect-pwsh.js +111 -0
  99. package/dist/core/detect-toolchains.d.ts +30 -0
  100. package/dist/core/detect-toolchains.js +58 -0
  101. package/dist/core/disk.d.ts +16 -0
  102. package/dist/core/disk.js +55 -0
  103. package/dist/core/local-config.d.ts +20 -0
  104. package/dist/core/local-config.js +77 -0
  105. package/dist/core/project-policy.d.ts +15 -0
  106. package/dist/core/project-policy.js +35 -0
  107. package/dist/core/prompt.d.ts +4 -0
  108. package/dist/core/prompt.js +15 -0
  109. package/dist/core/safe-paths.d.ts +4 -0
  110. package/dist/core/safe-paths.js +37 -0
  111. package/dist/core/schema/common.d.ts +17 -0
  112. package/dist/core/schema/common.js +15 -0
  113. package/dist/core/schema/config.d.ts +594 -0
  114. package/dist/core/schema/config.js +63 -0
  115. package/dist/core/schema/databases.d.ts +33 -0
  116. package/dist/core/schema/databases.js +35 -0
  117. package/dist/core/schema/harness.d.ts +12 -0
  118. package/dist/core/schema/harness.js +17 -0
  119. package/dist/core/schema/json-schema.d.ts +5 -0
  120. package/dist/core/schema/json-schema.js +25 -0
  121. package/dist/core/schema/profile.d.ts +27 -0
  122. package/dist/core/schema/profile.js +29 -0
  123. package/dist/core/schema/projects.d.ts +9 -0
  124. package/dist/core/schema/projects.js +11 -0
  125. package/dist/core/schema/sandbox.d.ts +50 -0
  126. package/dist/core/schema/sandbox.js +36 -0
  127. package/dist/core/schema/toolchains.d.ts +93 -0
  128. package/dist/core/schema/toolchains.js +83 -0
  129. package/dist/core/schema.d.ts +6 -0
  130. package/dist/core/schema.js +8 -0
  131. package/dist/core/types.d.ts +190 -0
  132. package/dist/core/types.js +1 -0
  133. package/dist/core/util.d.ts +60 -0
  134. package/dist/core/util.js +154 -0
  135. package/dist/core/workspace.d.ts +18 -0
  136. package/dist/core/workspace.js +128 -0
  137. package/dist/guest/boot-env.d.ts +4 -0
  138. package/dist/guest/boot-env.js +25 -0
  139. package/dist/guest/cmdline.d.ts +12 -0
  140. package/dist/guest/cmdline.js +23 -0
  141. package/dist/guest/daemon/exec.d.ts +5 -0
  142. package/dist/guest/daemon/exec.js +32 -0
  143. package/dist/guest/daemon/project.d.ts +14 -0
  144. package/dist/guest/daemon/project.js +132 -0
  145. package/dist/guest/daemon/session.d.ts +13 -0
  146. package/dist/guest/daemon/session.js +139 -0
  147. package/dist/guest/daemon/shutdown.d.ts +1 -0
  148. package/dist/guest/daemon/shutdown.js +21 -0
  149. package/dist/guest/daemon/state.d.ts +17 -0
  150. package/dist/guest/daemon/state.js +31 -0
  151. package/dist/guest/daemon.d.ts +1 -0
  152. package/dist/guest/daemon.js +53 -0
  153. package/dist/guest/db.d.ts +1 -0
  154. package/dist/guest/db.js +75 -0
  155. package/dist/guest/env.d.ts +51 -0
  156. package/dist/guest/env.js +101 -0
  157. package/dist/guest/gitguard.d.ts +7 -0
  158. package/dist/guest/gitguard.js +60 -0
  159. package/dist/guest/gitrules.d.ts +2 -0
  160. package/dist/guest/gitrules.js +11 -0
  161. package/dist/guest/guide.d.ts +16 -0
  162. package/dist/guest/guide.js +69 -0
  163. package/dist/guest/helpers.d.ts +3 -0
  164. package/dist/guest/helpers.js +26 -0
  165. package/dist/guest/main.d.ts +1 -0
  166. package/dist/guest/main.js +130 -0
  167. package/dist/guest/ports.d.ts +6 -0
  168. package/dist/guest/ports.js +22 -0
  169. package/dist/guest/project-context.d.ts +7 -0
  170. package/dist/guest/project-context.js +10 -0
  171. package/dist/guest/project-env.d.ts +20 -0
  172. package/dist/guest/project-env.js +20 -0
  173. package/dist/guest/project-status.d.ts +5 -0
  174. package/dist/guest/project-status.js +17 -0
  175. package/dist/guest/ptyhost.d.ts +22 -0
  176. package/dist/guest/ptyhost.js +36 -0
  177. package/dist/guest/setup/agents.d.ts +11 -0
  178. package/dist/guest/setup/agents.js +87 -0
  179. package/dist/guest/setup/claude.d.ts +21 -0
  180. package/dist/guest/setup/claude.js +146 -0
  181. package/dist/guest/setup/environment.d.ts +16 -0
  182. package/dist/guest/setup/environment.js +69 -0
  183. package/dist/guest/setup/hooks.d.ts +9 -0
  184. package/dist/guest/setup/hooks.js +50 -0
  185. package/dist/guest/setup/network.d.ts +10 -0
  186. package/dist/guest/setup/network.js +99 -0
  187. package/dist/guest/setup/policy.d.ts +3 -0
  188. package/dist/guest/setup/policy.js +18 -0
  189. package/dist/guest/setup/user.d.ts +7 -0
  190. package/dist/guest/setup/user.js +47 -0
  191. package/dist/guest/toolchains/catalogue.d.ts +55 -0
  192. package/dist/guest/toolchains/catalogue.js +31 -0
  193. package/dist/guest/toolchains/cli.d.ts +10 -0
  194. package/dist/guest/toolchains/cli.js +72 -0
  195. package/dist/guest/toolchains/common.d.ts +54 -0
  196. package/dist/guest/toolchains/common.js +147 -0
  197. package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
  198. package/dist/guest/toolchains/databases/dynamodb.js +52 -0
  199. package/dist/guest/toolchains/databases/index.d.ts +6 -0
  200. package/dist/guest/toolchains/databases/index.js +62 -0
  201. package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
  202. package/dist/guest/toolchains/databases/mongodb.js +31 -0
  203. package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
  204. package/dist/guest/toolchains/databases/mysql.js +38 -0
  205. package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
  206. package/dist/guest/toolchains/databases/postgres.js +38 -0
  207. package/dist/guest/toolchains/databases/redis.d.ts +3 -0
  208. package/dist/guest/toolchains/databases/redis.js +16 -0
  209. package/dist/guest/toolchains/databases/server.d.ts +14 -0
  210. package/dist/guest/toolchains/databases/server.js +52 -0
  211. package/dist/guest/toolchains/git.d.ts +3 -0
  212. package/dist/guest/toolchains/git.js +121 -0
  213. package/dist/guest/toolchains/go.d.ts +3 -0
  214. package/dist/guest/toolchains/go.js +33 -0
  215. package/dist/guest/toolchains/index.d.ts +17 -0
  216. package/dist/guest/toolchains/index.js +80 -0
  217. package/dist/guest/toolchains/java.d.ts +8 -0
  218. package/dist/guest/toolchains/java.js +96 -0
  219. package/dist/guest/toolchains/leftovers.d.ts +2 -0
  220. package/dist/guest/toolchains/leftovers.js +26 -0
  221. package/dist/guest/toolchains/managers.d.ts +16 -0
  222. package/dist/guest/toolchains/managers.js +84 -0
  223. package/dist/guest/toolchains/native/binstall.d.ts +7 -0
  224. package/dist/guest/toolchains/native/binstall.js +52 -0
  225. package/dist/guest/toolchains/native/cpp.d.ts +4 -0
  226. package/dist/guest/toolchains/native/cpp.js +38 -0
  227. package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
  228. package/dist/guest/toolchains/native/dotnet.js +103 -0
  229. package/dist/guest/toolchains/native/mesa.d.ts +7 -0
  230. package/dist/guest/toolchains/native/mesa.js +46 -0
  231. package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
  232. package/dist/guest/toolchains/native/msvc/environment.js +57 -0
  233. package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
  234. package/dist/guest/toolchains/native/msvc/index.js +48 -0
  235. package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
  236. package/dist/guest/toolchains/native/msvc/packages.js +156 -0
  237. package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
  238. package/dist/guest/toolchains/native/msvc/tree.js +21 -0
  239. package/dist/guest/toolchains/native/rust.d.ts +3 -0
  240. package/dist/guest/toolchains/native/rust.js +45 -0
  241. package/dist/guest/toolchains/node.d.ts +3 -0
  242. package/dist/guest/toolchains/node.js +152 -0
  243. package/dist/guest/toolchains/plan.d.ts +22 -0
  244. package/dist/guest/toolchains/plan.js +96 -0
  245. package/dist/guest/toolchains/pwsh.d.ts +4 -0
  246. package/dist/guest/toolchains/pwsh.js +14 -0
  247. package/dist/guest/toolchains/python.d.ts +3 -0
  248. package/dist/guest/toolchains/python.js +54 -0
  249. package/dist/guest/toolchains/runtimes.d.ts +19 -0
  250. package/dist/guest/toolchains/runtimes.js +38 -0
  251. package/dist/guest/toolchains/scripting/php.d.ts +3 -0
  252. package/dist/guest/toolchains/scripting/php.js +71 -0
  253. package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
  254. package/dist/guest/toolchains/scripting/ruby.js +47 -0
  255. package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
  256. package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
  257. package/dist/guest/toolchains/versions.d.ts +47 -0
  258. package/dist/guest/toolchains/versions.js +77 -0
  259. package/dist/guest/toolchains/vscode.d.ts +2 -0
  260. package/dist/guest/toolchains/vscode.js +53 -0
  261. package/dist/guest/view.d.ts +1 -0
  262. package/dist/guest/view.js +42 -0
  263. package/dist/host/accounts.d.ts +32 -0
  264. package/dist/host/accounts.js +128 -0
  265. package/dist/host/agent-commands.d.ts +25 -0
  266. package/dist/host/agent-commands.js +145 -0
  267. package/dist/host/attach.d.ts +17 -0
  268. package/dist/host/attach.js +69 -0
  269. package/dist/host/channel.d.ts +38 -0
  270. package/dist/host/channel.js +162 -0
  271. package/dist/host/claude-profile.d.ts +15 -0
  272. package/dist/host/claude-profile.js +107 -0
  273. package/dist/host/cleanup.d.ts +23 -0
  274. package/dist/host/cleanup.js +57 -0
  275. package/dist/host/database-plan.d.ts +12 -0
  276. package/dist/host/database-plan.js +52 -0
  277. package/dist/host/generate.d.ts +7 -0
  278. package/dist/host/generate.js +98 -0
  279. package/dist/host/join.d.ts +28 -0
  280. package/dist/host/join.js +76 -0
  281. package/dist/host/launch/mappings.d.ts +3 -0
  282. package/dist/host/launch/mappings.js +50 -0
  283. package/dist/host/launch/projects.d.ts +15 -0
  284. package/dist/host/launch/projects.js +56 -0
  285. package/dist/host/launch/stage.d.ts +11 -0
  286. package/dist/host/launch/stage.js +66 -0
  287. package/dist/host/launch/wsb.d.ts +10 -0
  288. package/dist/host/launch/wsb.js +34 -0
  289. package/dist/host/pc-scan.d.ts +6 -0
  290. package/dist/host/pc-scan.js +37 -0
  291. package/dist/host/personalize.d.ts +10 -0
  292. package/dist/host/personalize.js +97 -0
  293. package/dist/host/platform.d.ts +19 -0
  294. package/dist/host/platform.js +126 -0
  295. package/dist/host/project-scan.d.ts +7 -0
  296. package/dist/host/project-scan.js +89 -0
  297. package/dist/host/projects.d.ts +100 -0
  298. package/dist/host/projects.js +147 -0
  299. package/dist/host/repo-guard.d.ts +36 -0
  300. package/dist/host/repo-guard.js +219 -0
  301. package/dist/host/secrets.d.ts +14 -0
  302. package/dist/host/secrets.js +133 -0
  303. package/dist/host/terminal.d.ts +32 -0
  304. package/dist/host/terminal.js +130 -0
  305. package/dist/host/test-guard.d.ts +6 -0
  306. package/dist/host/test-guard.js +15 -0
  307. package/dist/host/window.d.ts +4 -0
  308. package/dist/host/window.js +72 -0
  309. package/dist/host/wsb.d.ts +37 -0
  310. package/dist/host/wsb.js +101 -0
  311. package/dist/runtime/daemon.js +28494 -0
  312. package/dist/runtime/db.js +26387 -0
  313. package/dist/runtime/gitguard.js +84 -0
  314. package/dist/runtime/main.js +28622 -0
  315. package/dist/runtime/view.js +25841 -0
  316. package/dist/selftest/accounts.test.d.ts +2 -0
  317. package/dist/selftest/accounts.test.js +44 -0
  318. package/dist/selftest/agent-commands.test.d.ts +2 -0
  319. package/dist/selftest/agent-commands.test.js +47 -0
  320. package/dist/selftest/channel.test.d.ts +2 -0
  321. package/dist/selftest/channel.test.js +37 -0
  322. package/dist/selftest/cli.test.d.ts +2 -0
  323. package/dist/selftest/cli.test.js +118 -0
  324. package/dist/selftest/cmdline.test.d.ts +2 -0
  325. package/dist/selftest/cmdline.test.js +45 -0
  326. package/dist/selftest/config.test.d.ts +2 -0
  327. package/dist/selftest/config.test.js +38 -0
  328. package/dist/selftest/data.test.d.ts +2 -0
  329. package/dist/selftest/data.test.js +80 -0
  330. package/dist/selftest/detect.test.d.ts +2 -0
  331. package/dist/selftest/detect.test.js +58 -0
  332. package/dist/selftest/fixtures.d.ts +272 -0
  333. package/dist/selftest/fixtures.js +80 -0
  334. package/dist/selftest/generate.test.d.ts +2 -0
  335. package/dist/selftest/generate.test.js +63 -0
  336. package/dist/selftest/git-access.test.d.ts +2 -0
  337. package/dist/selftest/git-access.test.js +26 -0
  338. package/dist/selftest/guest-boot.test.d.ts +2 -0
  339. package/dist/selftest/guest-boot.test.js +76 -0
  340. package/dist/selftest/harness.d.ts +51 -0
  341. package/dist/selftest/harness.js +77 -0
  342. package/dist/selftest/host-sim.test.d.ts +2 -0
  343. package/dist/selftest/host-sim.test.js +93 -0
  344. package/dist/selftest/hostile-input.test.d.ts +2 -0
  345. package/dist/selftest/hostile-input.test.js +54 -0
  346. package/dist/selftest/index.d.ts +8 -0
  347. package/dist/selftest/index.js +155 -0
  348. package/dist/selftest/links.test.d.ts +2 -0
  349. package/dist/selftest/links.test.js +50 -0
  350. package/dist/selftest/live-daemon.test.d.ts +2 -0
  351. package/dist/selftest/live-daemon.test.js +91 -0
  352. package/dist/selftest/live-git.test.d.ts +2 -0
  353. package/dist/selftest/live-git.test.js +60 -0
  354. package/dist/selftest/live-projects.test.d.ts +2 -0
  355. package/dist/selftest/live-projects.test.js +127 -0
  356. package/dist/selftest/live.d.ts +65 -0
  357. package/dist/selftest/live.js +109 -0
  358. package/dist/selftest/local-config.test.d.ts +2 -0
  359. package/dist/selftest/local-config.test.js +107 -0
  360. package/dist/selftest/mirror.test.d.ts +2 -0
  361. package/dist/selftest/mirror.test.js +101 -0
  362. package/dist/selftest/network.test.d.ts +2 -0
  363. package/dist/selftest/network.test.js +28 -0
  364. package/dist/selftest/personalization.test.d.ts +2 -0
  365. package/dist/selftest/personalization.test.js +53 -0
  366. package/dist/selftest/projects.test.d.ts +2 -0
  367. package/dist/selftest/projects.test.js +254 -0
  368. package/dist/selftest/repair.test.d.ts +2 -0
  369. package/dist/selftest/repair.test.js +42 -0
  370. package/dist/selftest/repo-guard.test.d.ts +2 -0
  371. package/dist/selftest/repo-guard.test.js +63 -0
  372. package/dist/selftest/secrets.test.d.ts +2 -0
  373. package/dist/selftest/secrets.test.js +71 -0
  374. package/dist/selftest/setup.test.d.ts +2 -0
  375. package/dist/selftest/setup.test.js +46 -0
  376. package/dist/selftest/suite.d.ts +13 -0
  377. package/dist/selftest/suite.js +1 -0
  378. package/dist/selftest/sync.test.d.ts +2 -0
  379. package/dist/selftest/sync.test.js +94 -0
  380. package/dist/selftest/test-guard.test.d.ts +2 -0
  381. package/dist/selftest/test-guard.test.js +49 -0
  382. package/dist/selftest/toolchains.test.d.ts +2 -0
  383. package/dist/selftest/toolchains.test.js +103 -0
  384. package/dist/selftest/workspace.test.d.ts +2 -0
  385. package/dist/selftest/workspace.test.js +53 -0
  386. package/dist/shared/download.d.ts +60 -0
  387. package/dist/shared/download.js +215 -0
  388. package/dist/shared/guest-paths.d.ts +30 -0
  389. package/dist/shared/guest-paths.js +61 -0
  390. package/dist/shared/netrules.d.ts +18 -0
  391. package/dist/shared/netrules.js +98 -0
  392. package/dist/shared/paths.d.ts +14 -0
  393. package/dist/shared/paths.js +28 -0
  394. package/dist/shared/projects.d.ts +53 -0
  395. package/dist/shared/projects.js +67 -0
  396. package/dist/shared/pty-client.d.ts +86 -0
  397. package/dist/shared/pty-client.js +227 -0
  398. package/dist/shared/rundir.d.ts +39 -0
  399. package/dist/shared/rundir.js +94 -0
  400. package/dist/shared/safe-remove.d.ts +10 -0
  401. package/dist/shared/safe-remove.js +66 -0
  402. package/dist/shared/schema.d.ts +527 -0
  403. package/dist/shared/schema.js +90 -0
  404. package/dist/shared/system.d.ts +15 -0
  405. package/dist/shared/system.js +19 -0
  406. package/dist/shared/test-guard.d.ts +4 -0
  407. package/dist/shared/test-guard.js +37 -0
  408. package/dist/sync/claude-mem.d.ts +8 -0
  409. package/dist/sync/claude-mem.js +79 -0
  410. package/dist/sync/history.d.ts +41 -0
  411. package/dist/sync/history.js +161 -0
  412. package/dist/sync/watcher.d.ts +21 -0
  413. package/dist/sync/watcher.js +135 -0
  414. package/hooks/post-agent/README.txt +1 -0
  415. package/hooks/pre-provision/README.txt +1 -0
  416. package/package.json +91 -4
  417. package/sandbox/bootstrap.ps1 +258 -0
  418. package/sandbox/cpucap.ps1 +51 -0
  419. package/sandbox/gitshim.cs +35 -0
  420. package/sandbox/logon.cmd +18 -0
  421. package/sandbox/openssl.cs +42 -0
  422. package/sandbox/ptyhost.cs +614 -0
  423. package/sandbox/trust.cmd +35 -0
  424. package/sandbox/vswhere.cs +83 -0
  425. package/schemas/config.schema.json +1161 -0
  426. package/schemas/sandbox-config.schema.json +968 -0
@@ -0,0 +1,101 @@
1
+ // Shared state for the guest scripts (main, daemon, view): fixed paths, the session description, logging, status.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { moveIntoPlace, setChecksumRequired, spawnAsync, stagingFolder } from '../shared/download.js';
6
+ import { now, runPaths, writeJsonAtomic } from '../shared/rundir.js';
7
+ import { safeRemove } from '../shared/safe-remove.js';
8
+ import { SYSTEM } from '../shared/system.js';
9
+ export const isWin = process.platform === 'win32';
10
+ export const dryRun = process.env.AGENT_DRY_RUN === '1';
11
+ /** Real provisioning, but nothing machine-wide (environment variables): for end-to-end tests outside a sandbox. */
12
+ export const testMode = process.env.AGENT_SANDBOX_TEST === '1';
13
+ export const launchDir = process.env.AGENT_LAUNCH_DIR ?? 'C:\\AgentLaunch';
14
+ export const dataDir = process.env.AGENT_DATA_DIR ?? 'C:\\AgentData';
15
+ export const hostToolsDir = process.env.AGENT_HOST_TOOLS_DIR ?? 'C:\\HostTools';
16
+ /** Folders that must stay on the sandbox's own disk (symlinks, SQLite) rather than in a mapped folder. */
17
+ export const localDir = process.env.AGENT_LOCAL_DIR ?? 'C:\\nvm';
18
+ export const toolsDir = path.join(dataDir, 'tools');
19
+ export const logsDir = path.join(dataDir, 'logs');
20
+ export const run = runPaths(dataDir);
21
+ export const session = JSON.parse(fs.readFileSync(path.join(launchDir, 'session.json'), 'utf8').replace(/^\uFEFF/, ''));
22
+ setChecksumRequired(session.downloads?.requireChecksum === true);
23
+ export const workspace = process.env.AGENT_WORKSPACE ?? session.workspace;
24
+ export const stateDir = path.join(dataDir, session.persistAgentState ? 'state' : 'state-session');
25
+ export const claudeDir = path.join(stateDir, 'claude');
26
+ fs.mkdirSync(logsDir, { recursive: true });
27
+ const logFile = path.join(logsDir, (process.env.AGENT_LOG_PREFIX ?? 'guest') + '-' + new Date().toISOString().replace(/[:.]/g, '-') + '.log');
28
+ const append = (line) => { try {
29
+ fs.appendFileSync(logFile, line + '\n');
30
+ }
31
+ catch { /* logging must never break the run */ } };
32
+ export const log = (message) => { console.log(message); append(message); };
33
+ export const warn = (message) => { console.warn('\x1b[33mWARNING: ' + message + '\x1b[0m'); append('WARNING: ' + message); };
34
+ export const errMsg = (e) => (e instanceof Error ? e.message : String(e));
35
+ export const exists = (p) => fs.existsSync(p);
36
+ export const mkdirp = (p) => { fs.mkdirSync(p, { recursive: true }); return p; };
37
+ export const logPath = logFile;
38
+ /** Best-effort: progress reporting must never be what breaks the boot. */
39
+ export const setSandboxStatus = (phase, message) => {
40
+ try {
41
+ writeJsonAtomicChecked(phase, message);
42
+ }
43
+ catch (e) {
44
+ warn('Could not update sandbox status: ' + errMsg(e));
45
+ }
46
+ };
47
+ const writeJsonAtomicChecked = (phase, message) => {
48
+ writeJsonAtomic(run.sandbox, { launchId: session.launchId, phase, message, updatedAt: now(), workspaceHost: session.workspaceHost, guestWorkspace: workspace, headless: session.headless });
49
+ };
50
+ /** Runs a program, streaming output, and throws with a readable message when it fails. */
51
+ export const exec = (file, args, options = {}) => {
52
+ const result = spawnSync(file, args, { stdio: 'inherit', windowsHide: true, ...options });
53
+ if (result.error)
54
+ throw result.error;
55
+ if (result.status !== 0)
56
+ throw new Error(path.basename(file) + ' ' + args.slice(0, 3).join(' ') + ' failed with exit code ' + result.status);
57
+ };
58
+ /** exec without blocking: other toolchains keep downloading while this one runs an installer. */
59
+ export const execAsync = async (file, args, options = {}) => {
60
+ const code = await spawnAsync(file, args, { stdio: 'inherit', ...options });
61
+ if (code !== 0)
62
+ throw new Error(path.basename(file) + ' ' + args.slice(0, 3).join(' ') + ' failed with exit code ' + code);
63
+ };
64
+ export const execOutput = (file, args, options = {}) => {
65
+ const result = spawnSync(file, args, { encoding: 'utf8', windowsHide: true, ...options });
66
+ return result.status === 0 ? String(result.stdout ?? '').trim() : '';
67
+ };
68
+ export const powershell = (script) => exec(SYSTEM.powershell(), ['-NoLogo', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script]);
69
+ /** Copies a folder with robocopy (fast, restartable), or just the named files in it; exit codes below 8 mean success. */
70
+ export const robocopy = (from, to, files = []) => {
71
+ if (!isWin) {
72
+ if (!files.length) {
73
+ fs.cpSync(from, to, { recursive: true });
74
+ return;
75
+ }
76
+ fs.mkdirSync(to, { recursive: true });
77
+ for (const f of files.filter((f) => fs.existsSync(path.join(from, f))))
78
+ fs.copyFileSync(path.join(from, f), path.join(to, f));
79
+ return;
80
+ }
81
+ const what = files.length ? files : ['/E', '/MT:16'];
82
+ const result = spawnSync(SYSTEM.robocopy(), [from, to, ...what, '/NFL', '/NDL', '/NJH', '/NJS', '/NP', '/R:1', '/W:1', '/XJ'], { stdio: 'ignore', windowsHide: true });
83
+ if ((result.status ?? 16) >= 8)
84
+ throw new Error('robocopy ' + from + ' -> ' + to + ' failed (exit ' + result.status + ')');
85
+ };
86
+ /**
87
+ * A Node install without its global packages (nvm keeps those per version: often tens of thousands of small files,
88
+ * which is what made copying from this PC slow). Same file list as Copy-NodeFromHost in sandbox/bootstrap.ps1.
89
+ * Copied into a staging folder and renamed into place: node.exe alone (an interrupted copy) must not look like a full install.
90
+ */
91
+ export const copyNode = (from, to) => {
92
+ const work = stagingFolder(to);
93
+ try {
94
+ robocopy(from, work, ['node.exe', 'npm', 'npm.cmd', 'npx', 'npx.cmd']);
95
+ robocopy(path.join(from, 'node_modules', 'npm'), path.join(work, 'node_modules', 'npm'));
96
+ moveIntoPlace(work, to);
97
+ }
98
+ finally {
99
+ safeRemove(work, path.dirname(work));
100
+ }
101
+ };
@@ -0,0 +1,7 @@
1
+ export type Decision = {
2
+ allow: true;
3
+ } | {
4
+ allow: false;
5
+ reason: string;
6
+ };
7
+ export declare const gitDecision: (args: string[], access: string) => Decision;
@@ -0,0 +1,60 @@
1
+ // `git` wrapper used when provisioning.git.access is 'readonly' or 'none'. Installed first on PATH (git.cmd) and as a
2
+ // bash function (BASH_ENV), it decides per call and then runs the real git. Standalone on purpose: it runs on every
3
+ // git call, so it reads only two environment variables (AGENT_GIT_ACCESS, AGENT_REAL_GIT).
4
+ import { spawnSync } from 'node:child_process';
5
+ /** Subcommands that only read. */
6
+ const READ_ONLY = new Set([
7
+ 'status', 'log', 'diff', 'show', 'blame', 'annotate', 'ls-files', 'ls-tree', 'ls-remote', 'rev-parse', 'rev-list', 'describe', 'shortlog',
8
+ 'grep', 'cat-file', 'reflog', 'whatchanged', 'name-rev', 'for-each-ref', 'show-ref', 'merge-base', 'count-objects', 'check-ignore',
9
+ 'check-attr', 'var', 'version', 'help', 'diff-tree', 'diff-files', 'diff-index', 'cherry', 'range-diff', 'show-branch', 'verify-commit', 'verify-tag',
10
+ ]);
11
+ /**
12
+ * `git branch` / `git tag` list when every argument is a listing flag, or when a listing flag is present and the
13
+ * rest are patterns/commits (`git branch --contains HEAD`). A bare name (`git branch new`) creates, so it is refused.
14
+ */
15
+ const listingOnly = (flags, listFlags) => (rest) => rest.every((a) => flags.test(a)) || (rest.some((a) => listFlags.test(a)) && rest.every((a) => flags.test(a) || !a.startsWith('-')));
16
+ /** Subcommands that are read-only only in their listing forms. */
17
+ const LISTING = {
18
+ branch: listingOnly(/^(-a|-r|-v|-vv|--all|--remotes|--list|-l|--show-current|--verbose|--no-color|--color(=.*)?|--contains|--no-contains|--merged|--no-merged|--sort=.*|--format=.*)$/, /^(--list|-l|--contains|--no-contains|--merged|--no-merged)$/),
19
+ tag: listingOnly(/^(-l|--list|-n\d*|--contains|--no-contains|--merged|--no-merged|--points-at|--sort=.*|--format=.*)$/, /^(-l|--list|--contains|--no-contains|--merged|--no-merged|--points-at)$/),
20
+ remote: (rest) => rest.length === 0 || ['-v', '--verbose', 'show', 'get-url'].includes(rest[0]),
21
+ config: (rest) => rest.some((a) => /^(--get|--get-all|--get-regexp|--list|-l|--show-origin|--show-scope|--name-only)$/.test(a)) && !rest.some((a) => /^(--add|--unset|--unset-all|--replace-all|--rename-section|--remove-section|-e|--edit)$/.test(a)),
22
+ stash: (rest) => ['list', 'show'].includes(rest[0] ?? ''),
23
+ worktree: (rest) => rest[0] === 'list',
24
+ submodule: (rest) => rest.length === 0 || rest[0] === 'status' || rest[0] === 'summary',
25
+ notes: (rest) => rest.length === 0 || ['list', 'show'].includes(rest[0]),
26
+ };
27
+ /** Global options that take a value (`git -C dir status`). Config overrides (-c, --config-env) are refused outright. */
28
+ const WITH_VALUE = new Set(['-C', '--namespace']);
29
+ const FORBIDDEN_GLOBAL = /^(-c|--config-env|--exec-path|--git-dir|--work-tree|--super-prefix)(=|$)/;
30
+ export const gitDecision = (args, access) => {
31
+ if (access === 'full')
32
+ return { allow: true };
33
+ if (access !== 'readonly')
34
+ return { allow: false, reason: 'git is disabled in this sandbox (provisioning.git.access = ' + (access || 'none') + ').' };
35
+ let i = 0;
36
+ while (i < args.length && args[i].startsWith('-')) {
37
+ if (FORBIDDEN_GLOBAL.test(args[i]))
38
+ return { allow: false, reason: 'git ' + args[i] + ' is not allowed: git is read-only in this sandbox.' };
39
+ i += WITH_VALUE.has(args[i]) ? 2 : 1;
40
+ }
41
+ const [command, ...rest] = args.slice(i);
42
+ if (command === undefined || command === '--version' || READ_ONLY.has(command) || (LISTING[command]?.(rest) ?? false))
43
+ return { allow: true };
44
+ return { allow: false, reason: 'git ' + command + ' is blocked: git is read-only in this sandbox (provisioning.git.access = readonly). Reading (status, log, diff, show, blame) is allowed.' };
45
+ };
46
+ if (process.argv[1]?.toLowerCase().endsWith('gitguard.js')) {
47
+ const args = process.argv.slice(2);
48
+ const decision = gitDecision(args, process.env.AGENT_GIT_ACCESS ?? 'none');
49
+ if (!decision.allow) {
50
+ console.error('fatal: ' + decision.reason);
51
+ process.exit(128);
52
+ }
53
+ const real = process.env.AGENT_REAL_GIT;
54
+ if (!real) {
55
+ console.error('fatal: git is not available in this sandbox.');
56
+ process.exit(128);
57
+ }
58
+ const result = spawnSync(real, args, { stdio: 'inherit', windowsHide: true });
59
+ process.exit(result.status ?? 1);
60
+ }
@@ -0,0 +1,2 @@
1
+ import type { GitAccess } from '../core/types.js';
2
+ export declare const gitDenyRules: (access: GitAccess) => string[];
@@ -0,0 +1,11 @@
1
+ const SHELLS = ['Bash', 'PowerShell'];
2
+ const WRITE_COMMANDS = ['commit', 'push', 'add', 'rm', 'mv', 'merge', 'rebase', 'reset', 'checkout', 'switch', 'restore', 'stash', 'tag', 'cherry-pick', 'revert', 'am', 'apply', 'clean', 'pull', 'fetch', 'clone', 'init', 'gc', 'prune', 'update-ref', 'update-index', 'filter-branch', 'replace', 'worktree add', 'branch -d', 'branch -D', 'branch -m', 'branch -M', 'config --add', 'config --unset', 'remote add', 'remote remove', 'remote set-url'];
3
+ const GIT_DIR_PATHS = ['**/.git', '**/.git/**'];
4
+ export const gitDenyRules = (access) => {
5
+ if (access === 'full')
6
+ return [];
7
+ const commands = access === 'none' ? ['git'] : WRITE_COMMANDS.map((c) => 'git ' + c);
8
+ const shellRules = SHELLS.flatMap((shell) => commands.map((c) => shell + '(' + c + ':*)'));
9
+ const fileRules = GIT_DIR_PATHS.flatMap((p) => (access === 'none' ? ['Read(' + p + ')', 'Edit(' + p + ')'] : ['Edit(' + p + ')']));
10
+ return [...shellRules, ...fileRules];
11
+ };
@@ -0,0 +1,16 @@
1
+ /** Deliberately tiny: it is loaded into every conversation. */
2
+ export interface GuideProject {
3
+ guest: string;
4
+ host: string;
5
+ }
6
+ export declare const sandboxGuide: (toolSummary: string[], lan?: "blocked" | "open" | "offline", projects?: GuideProject[]) => string;
7
+ /** Inserts or replaces the generated section, keeping whatever else the file contains (the host's CLAUDE.md). */
8
+ export declare const upsertGuide: (file: string, guide: string) => void;
9
+ /**
10
+ * Claude Code loads the machine-wide managed memory (C:\Program Files\ClaudeCode\CLAUDE.md on Windows) before the
11
+ * user and project CLAUDE.md, in every session. The sandbox is disposable and we are admin, so the manifest lives
12
+ * there: the user's own global CLAUDE.md and every project's CLAUDE.md stay exactly as they are.
13
+ * Codex/Gemini have no machine-level file, so the block goes into their sandbox-only global instruction files.
14
+ */
15
+ export declare const managedClaudeMd: () => string;
16
+ export declare const writeGuides: (toolSummary: string[], lan: "blocked" | "open" | "offline", projects?: GuideProject[]) => string;
@@ -0,0 +1,69 @@
1
+ // Tells the agent where it is. A generated section (between markers, rewritten every boot) is added to the agent's
2
+ // global instructions: Claude's CLAUDE.md and Codex's AGENTS.md inside the sandbox. It never travels back to this PC.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { claudeDir, dataDir, launchDir, session, stateDir, toolsDir, workspace } from './env.js';
6
+ const BEGIN = '<!-- agent-sandbox:begin (generated every boot, do not edit) -->';
7
+ const END = '<!-- agent-sandbox:end -->';
8
+ /** Drives other than C: are subst drives onto C:\Mirror\<letter> (mapped folders shown at their PC paths). */
9
+ const mirrorLine = () => {
10
+ // Only the ones bootstrap.ps1 could create (a letter already in use is reported there and skipped).
11
+ const drives = (session.substDrives ?? []).filter((d) => fs.existsSync(d.letter + ':\\'));
12
+ if (!drives.length)
13
+ return [];
14
+ return ['- ' + drives.map((d) => d.letter + ':').join(', ') + ' = mapped folders at their PC paths (subst onto `' + drives.map((d) => d.target).join('`, `') + '`; real-path tools may print that form).'];
15
+ };
16
+ export const sandboxGuide = (toolSummary, lan = 'blocked', projects = []) => [
17
+ BEGIN,
18
+ '## Sandbox manifest — you are in a DISPOSABLE Windows Sandbox',
19
+ 'Wiped on close: everything except the paths below (installers, winget/choco, C:\\Users, Program Files, registry).',
20
+ '- RW, persists: `' + workspace + '` = host folder ' + session.workspaceHost,
21
+ // Projects started from other folders while this sandbox runs: every agent here can see them.
22
+ ...projects.map((p) => '- RW, persists: `' + p.guest + '` = host folder ' + p.host + ' (another project in this sandbox; your own is `%AGENT_WORKSPACE%`)'),
23
+ '- RW, persists: `' + toolsDir + '` (toolchains) — put portable exes in `' + path.join(toolsDir, 'bin') + '` (on PATH)',
24
+ '- RW, persists: `' + stateDir + '` (agent config/history; do not edit logins) · logs: `' + path.join(dataDir, 'logs') + '`',
25
+ '- RO: `' + launchDir + '` (this boot\'s scripts) · lost on close: everything else, incl. `C:\\nvm` (Node symlink)',
26
+ ...mirrorLine(),
27
+ '- Tools: ' + toolSummary.join(' · '),
28
+ '- Persistent installs: `npm i -g` (per nvm version), `pip`, `cargo install`, `dotnet tool -g`, `uv tool`, `bun add -g`. Real installers must re-run each boot via a hook (ask the user).',
29
+ '- Mapped folders (workspace, C:\\AgentData) allow NO symlinks or junctions: pnpm runs hoisted (set), npm workspaces / `file:` deps and deno node_modules dirs fail there (use a folder on C:\\ outside them), bun cannot save its lockfile there (`bun install --no-save`).',
30
+ '- Network: ' + (lan === 'offline' ? 'OFF' : lan === 'blocked' ? 'internet yes, host LAN blocked' : 'internet yes, host LAN NOT blocked this time') + '. Other sessions may share this workspace; the user may be typing from the host.',
31
+ END,
32
+ ].join('\n');
33
+ /** Inserts or replaces the generated section, keeping whatever else the file contains (the host's CLAUDE.md). */
34
+ export const upsertGuide = (file, guide) => {
35
+ fs.mkdirSync(path.dirname(file), { recursive: true });
36
+ const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
37
+ const start = current.indexOf(BEGIN);
38
+ const end = current.indexOf(END);
39
+ const without = start >= 0 && end > start ? current.slice(0, start) + current.slice(end + END.length) : current;
40
+ fs.writeFileSync(file, without.trimEnd() + (without.trim() ? '\n\n' : '') + guide + '\n', 'utf8');
41
+ };
42
+ /**
43
+ * Claude Code loads the machine-wide managed memory (C:\Program Files\ClaudeCode\CLAUDE.md on Windows) before the
44
+ * user and project CLAUDE.md, in every session. The sandbox is disposable and we are admin, so the manifest lives
45
+ * there: the user's own global CLAUDE.md and every project's CLAUDE.md stay exactly as they are.
46
+ * Codex/Gemini have no machine-level file, so the block goes into their sandbox-only global instruction files.
47
+ */
48
+ export const managedClaudeMd = () => path.join(process.env.AGENT_MANAGED_DIR ?? path.join(process.env.ProgramFiles ?? 'C:\\Program Files', 'ClaudeCode'), 'CLAUDE.md');
49
+ export const writeGuides = (toolSummary, lan, projects = []) => {
50
+ const guide = sandboxGuide(toolSummary, lan, projects);
51
+ const managed = managedClaudeMd();
52
+ fs.mkdirSync(path.dirname(managed), { recursive: true });
53
+ fs.writeFileSync(managed, guide + '\n', 'utf8');
54
+ fs.writeFileSync(path.join(dataDir, 'SANDBOX.md'), guide + '\n', 'utf8');
55
+ upsertGuide(path.join(stateDir, 'codex', 'AGENTS.md'), guide);
56
+ upsertGuide(path.join(stateDir, 'gemini', '.gemini', 'GEMINI.md'), guide);
57
+ removeGuide(path.join(claudeDir, 'CLAUDE.md'));
58
+ return managed;
59
+ };
60
+ /** Older versions put the block into the user's CLAUDE.md; take it out again. */
61
+ const removeGuide = (file) => {
62
+ if (!fs.existsSync(file))
63
+ return;
64
+ const current = fs.readFileSync(file, 'utf8');
65
+ const start = current.indexOf(BEGIN);
66
+ const end = current.indexOf(END);
67
+ if (start >= 0 && end > start)
68
+ fs.writeFileSync(file, (current.slice(0, start) + current.slice(end + END.length)).trim() + '\n', 'utf8');
69
+ };
@@ -0,0 +1,3 @@
1
+ export type Helper = 'ptyhost' | 'gitshim' | 'vswhere' | 'openssl';
2
+ /** %TEMP%\agent-sandbox\<name>-<hash>.exe, compiled from this launch's sandbox\<name>.cs the first time it is needed. */
3
+ export declare const ensureCompiled: (name: Helper) => string;
@@ -0,0 +1,26 @@
1
+ // Builds the sandbox's small C# helpers (ptyhost.cs, gitshim.cs, vswhere.cs, openssl.cs) with the .NET Framework
2
+ // compiler that ships with Windows.
3
+ //
4
+ // The helpers are compiled onto the sandbox's own disk (wiped on close) on every boot, never cached in persistent
5
+ // storage: a tampered ptyhost.exe there would survive the wipe and see every keystroke typed from this PC.
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import crypto from 'node:crypto';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { exists, launchDir, mkdirp } from './env.js';
12
+ const CSC = path.join(process.env.WINDIR ?? 'C:\\Windows', 'Microsoft.NET', 'Framework64', 'v4.0.30319', 'csc.exe');
13
+ const sourceHash = (name) => crypto.createHash('sha256').update(fs.readFileSync(path.join(launchDir, 'sandbox', name + '.cs'))).digest('hex').slice(0, 12);
14
+ /** %TEMP%\agent-sandbox\<name>-<hash>.exe, compiled from this launch's sandbox\<name>.cs the first time it is needed. */
15
+ export const ensureCompiled = (name) => {
16
+ const exe = path.join(os.tmpdir(), 'agent-sandbox', name + '-' + sourceHash(name) + '.exe');
17
+ if (exists(exe))
18
+ return exe;
19
+ if (!exists(CSC))
20
+ throw new Error('The .NET Framework C# compiler was not found at ' + CSC + ' (it ships with Windows 10/11).');
21
+ mkdirp(path.dirname(exe));
22
+ const result = spawnSync(CSC, ['/nologo', '/optimize', '/target:exe', '/out:' + exe, path.join(launchDir, 'sandbox', name + '.cs')], { encoding: 'utf8', windowsHide: true });
23
+ if (result.status !== 0)
24
+ throw new Error('Compiling ' + name + ' failed: ' + ((result.stdout ?? '') + (result.stderr ?? '')).trim().slice(0, 1500));
25
+ return exe;
26
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,130 @@
1
+ // Guest entry point, started by bootstrap.ps1 in the sandbox window. Provisions everything, starts the session
2
+ // daemon, asks it for the first agent session and then shows that session in this window.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ import { writeGuides } from './guide.js';
7
+ import { setupToolchains } from './toolchains/index.js';
8
+ import { provisionHarness, resolveLaunch } from './setup/agents.js';
9
+ import { restoreClaudeMem, syncClaudeProfile } from './setup/claude.js';
10
+ import { agentEnv, persistEnvironment } from './setup/environment.js';
11
+ import { runHooks } from './setup/hooks.js';
12
+ import { applyNetworkPolicy } from './setup/network.js';
13
+ import { writeManagedSettings } from './setup/policy.js';
14
+ import { applyPersonalization, setupGitConfig } from './setup/user.js';
15
+ import { ensurePtyHost } from './ptyhost.js';
16
+ import { pruneToolLeftovers } from './toolchains/leftovers.js';
17
+ import { now, readSession, writeJsonAtomic } from '../shared/rundir.js';
18
+ import { viewSession } from './view.js';
19
+ import { saveBootEnv } from './boot-env.js';
20
+ import { primaryProject, saveProjectStatus } from './project-status.js';
21
+ import { dataDir, dryRun, errMsg, isWin, localDir, log, logPath, logsDir, mkdirp, run, session, setSandboxStatus, toolsDir, warn, workspace } from './env.js';
22
+ const STEPS = 8;
23
+ const step = (n, title) => { log('\n\x1b[36m[' + n + '/' + STEPS + '] ' + title + '\x1b[0m'); setSandboxStatus(n === 1 ? 'booting' : 'provisioning', '[' + n + '/' + STEPS + '] ' + title); };
24
+ const soft = async (what, work) => { try {
25
+ await work();
26
+ }
27
+ catch (e) {
28
+ warn(what + ' failed (continuing): ' + errMsg(e));
29
+ } };
30
+ const startDaemon = (env) => {
31
+ spawn(process.execPath, [path.join(path.dirname(process.argv[1]), 'daemon.js')], { detached: true, stdio: 'ignore', windowsHide: true, env: { ...env, AGENT_LOG_PREFIX: 'daemon' } }).unref();
32
+ };
33
+ /** `sbx-db` for terminals and `sandbox db`: this boot's Node running runtime\db.js (on the sandbox's disk, rewritten each boot). */
34
+ const writeDbCommand = () => {
35
+ const dir = mkdirp(path.join(localDir, 'bin'));
36
+ const db = path.join(path.dirname(process.argv[1]), 'db.js');
37
+ fs.writeFileSync(path.join(dir, 'sbx-db.cmd'), '@setlocal\r\n@set AGENT_LOG_PREFIX=db\r\n@"' + process.execPath + '" "' + db + '" %*\r\n');
38
+ return dir;
39
+ };
40
+ const waitForPrimary = async () => {
41
+ for (let i = 0; i < 600; i++) {
42
+ const s = readSession(dataDir, session.launchId);
43
+ if (s?.phase === 'running')
44
+ return true;
45
+ if (s?.phase === 'failed' || s?.phase === 'exited') {
46
+ warn(s.message);
47
+ return false;
48
+ }
49
+ await new Promise((r) => setTimeout(r, 500));
50
+ }
51
+ return false;
52
+ };
53
+ const main = async () => {
54
+ log('Agent sandbox: ' + session.project + ' | workspace ' + workspace + ' | agent ' + session.harnessName + ' | log ' + logPath);
55
+ if (!fs.statSync(workspace, { throwIfNoEntry: false })?.isDirectory())
56
+ throw new Error('Workspace folder is missing in the sandbox: ' + workspace);
57
+ mkdirp(toolsDir);
58
+ mkdirp(run.run);
59
+ step(1, 'Network and session host');
60
+ let ptyHost = null;
61
+ if (isWin && !dryRun && session.attach.enabled)
62
+ await soft('Building the session host', () => { ptyHost = ensurePtyHost(); });
63
+ let lan = session.networking ? 'open' : 'offline';
64
+ await soft('Private-network blocking', async () => { lan = await applyNetworkPolicy(ptyHost); });
65
+ step(2, 'Pre-provision hooks and desktop');
66
+ runHooks('pre-provision', process.env);
67
+ await soft('Personalisation', applyPersonalization);
68
+ const gitAccess = session.provisioning.git.access ?? 'full';
69
+ step(3, 'Developer tools (the first start downloads them once; later starts are quick)');
70
+ const basePath = process.env.PATH ?? '';
71
+ const tools = await setupToolchains(session.toolchains, session.versions, {
72
+ gitEnabled: session.provisioning.git.enabled, gitAccess, hostGit: session.hostTools.git, refresh: session.provisioning.refresh, databases: session.databases ?? [],
73
+ });
74
+ if (!dryRun)
75
+ tools.prepend.push(writeDbCommand());
76
+ const env = agentEnv(tools);
77
+ persistEnvironment(env, Object.keys(tools.vars));
78
+ saveBootEnv({ basePath, toolVars: Object.keys(tools.vars), steps: tools.steps, summary: tools.summary, lan });
79
+ saveProjectStatus(primaryProject(), workspace, 'ready', 'Ready', session.databases ?? [], tools.summary);
80
+ for (const line of tools.summary)
81
+ log(' ' + line);
82
+ await soft('Cleaning old tool builds', () => pruneToolLeftovers());
83
+ step(4, 'Git identity');
84
+ await soft('Git config', () => setupGitConfig(env));
85
+ step(5, 'Claude settings, history and claude-mem');
86
+ if (session.harnessName === 'claude')
87
+ await soft('Claude settings', syncClaudeProfile);
88
+ await soft('claude-mem restore', restoreClaudeMem);
89
+ await soft('Sandbox guide for the agent', () => writeGuides(tools.summary, lan));
90
+ await soft('Claude managed settings', () => writeManagedSettings(gitAccess));
91
+ step(6, 'Agent: ' + session.harnessName);
92
+ if (session.harness)
93
+ await provisionHarness(session.harnessName, session.harness, env);
94
+ step(7, 'Pre-agent hooks');
95
+ runHooks('pre-agent', env);
96
+ step(8, 'Starting ' + session.harnessName);
97
+ setSandboxStatus('ready', 'Ready');
98
+ // No agent, or a restart that adds a project (the host opens its sessions itself once the sandbox is ready).
99
+ if (!session.harness || session.autoSession === false) {
100
+ if (!dryRun)
101
+ startDaemon(env);
102
+ log('Ready' + (session.harness ? '' : ' (no agent requested)') + '. Workspace: ' + workspace);
103
+ return;
104
+ }
105
+ if (dryRun) {
106
+ const launch = resolveLaunch(session.harnessName, session.harness);
107
+ log('(dry run) would run: ' + [launch.file, ...launch.args, ...session.harness.arguments, ...session.harnessArguments].join(' ') + ' | cwd=' + workspace);
108
+ runHooks('post-agent', env);
109
+ return;
110
+ }
111
+ startDaemon(env);
112
+ writeJsonAtomic(run.request(session.launchId), { kind: 'session', id: session.launchId, harness: session.harnessName, args: session.harnessArguments, cwd: workspace, createdAt: now() });
113
+ if (!(await waitForPrimary()))
114
+ throw new Error('The agent session did not start. Logs: ' + logsDir);
115
+ log('If Claude asks you to sign in, do it once; the login is saved in persistent storage.');
116
+ if (process.stdin.isTTY)
117
+ await viewSession(session.launchId);
118
+ log('\nThis window stays open in ' + workspace + '. Other sessions keep running in their own tabs.');
119
+ };
120
+ main().catch((e) => {
121
+ const message = errMsg(e) + (e instanceof Error && e.stack ? '\n' + e.stack : '');
122
+ try {
123
+ setSandboxStatus('failed', errMsg(e));
124
+ fs.writeFileSync(path.join(logsDir, 'guest-error.txt'), message);
125
+ }
126
+ catch { /* ignore */ }
127
+ // The window gets the plain message; the stack trace is in guest-error.txt.
128
+ console.error('\n\x1b[31mThe sandbox could not start: ' + errMsg(e) + '\x1b[0m\nDetails: ' + path.join(logsDir, 'guest-error.txt'));
129
+ process.exitCode = 1;
130
+ });
@@ -0,0 +1,6 @@
1
+ /** A free port on loopback: finding one must not count as "node listens on the network" (that raises firewall prompts). */
2
+ export declare const freePort: () => Promise<number>;
3
+ /** Whether something accepts connections on 127.0.0.1:port. */
4
+ export declare const portOpen: (port: number) => Promise<boolean>;
5
+ /** Polls until the port accepts connections; false when `seconds` pass first. */
6
+ export declare const waitForPort: (port: number, seconds: number) => Promise<boolean>;
@@ -0,0 +1,22 @@
1
+ // Local TCP port helpers shared by the session host (ptyhost) and the databases.
2
+ import net from 'node:net';
3
+ /** A free port on loopback: finding one must not count as "node listens on the network" (that raises firewall prompts). */
4
+ export const freePort = () => new Promise((resolve, reject) => {
5
+ const server = net.createServer();
6
+ server.once('error', reject);
7
+ server.listen(0, '127.0.0.1', () => { const { port } = server.address(); server.close(() => resolve(port)); });
8
+ });
9
+ /** Whether something accepts connections on 127.0.0.1:port. */
10
+ export const portOpen = (port) => new Promise((resolve) => {
11
+ const socket = net.connect({ host: '127.0.0.1', port }, () => { socket.destroy(); resolve(true); });
12
+ socket.once('error', () => resolve(false));
13
+ });
14
+ /** Polls until the port accepts connections; false when `seconds` pass first. */
15
+ export const waitForPort = async (port, seconds) => {
16
+ for (const end = Date.now() + seconds * 1000; Date.now() < end;) {
17
+ if (await portOpen(port))
18
+ return true;
19
+ await new Promise((r) => setTimeout(r, 300));
20
+ }
21
+ return false;
22
+ };
@@ -0,0 +1,7 @@
1
+ export interface ProjectContext {
2
+ workspace: string;
3
+ duringSession: boolean;
4
+ }
5
+ export declare const withProject: <T>(context: ProjectContext, work: () => Promise<T>) => Promise<T>;
6
+ export declare const projectWorkspace: () => string;
7
+ export declare const duringSession: () => boolean;
@@ -0,0 +1,10 @@
1
+ // Which project a toolchain step is provisioning. At boot that is the launch folder; the daemon provisions projects
2
+ // registered later (another folder on the PC) while other sessions keep running, several at once. Steps read version
3
+ // files and markers from projectWorkspace(), and duringSession() tells them to leave machine-wide state alone (the
4
+ // nvm symlink, shims, compiled helpers in use) because sessions of other projects depend on it.
5
+ import { AsyncLocalStorage } from 'node:async_hooks';
6
+ import { workspace } from './env.js';
7
+ const store = new AsyncLocalStorage();
8
+ export const withProject = (context, work) => store.run(context, work);
9
+ export const projectWorkspace = () => store.getStore()?.workspace ?? workspace;
10
+ export const duringSession = () => store.getStore()?.duringSession ?? false;
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const BootEnv: z.ZodObject<{
3
+ basePath: z.ZodString;
4
+ toolVars: z.ZodArray<z.ZodString>;
5
+ steps: z.ZodRecord<z.ZodString, z.ZodObject<{
6
+ vars: z.ZodRecord<z.ZodString, z.ZodString>;
7
+ prepend: z.ZodArray<z.ZodString>;
8
+ append: z.ZodArray<z.ZodString>;
9
+ summary: z.ZodArray<z.ZodString>;
10
+ }, z.core.$strip>>;
11
+ summary: z.ZodArray<z.ZodString>;
12
+ lan: z.ZodEnum<{
13
+ open: "open";
14
+ blocked: "blocked";
15
+ offline: "offline";
16
+ }>;
17
+ }, z.core.$strip>;
18
+ export type BootEnvT = z.output<typeof BootEnv>;
19
+ /** The environment a later project starts from: the daemon's own, without the boot project's toolchains. */
20
+ export declare const baseEnvFor: (boot: BootEnvT | null, current?: NodeJS.ProcessEnv) => NodeJS.ProcessEnv;
@@ -0,0 +1,20 @@
1
+ // Pure (no guest state): the environment a project that joined after boot starts from. Kept apart from boot-env.ts,
2
+ // which reads the guest's session, so the host-side tests can import it.
3
+ import { z } from 'zod';
4
+ const Env = z.object({ vars: z.record(z.string(), z.string()), prepend: z.array(z.string()), append: z.array(z.string()), summary: z.array(z.string()) });
5
+ export const BootEnv = z.object({
6
+ basePath: z.string(), toolVars: z.array(z.string()), steps: z.record(z.string(), Env),
7
+ /** The boot's tool summary and LAN state, for the agent guide that lists every project. */
8
+ summary: z.array(z.string()), lan: z.enum(['blocked', 'open', 'offline']),
9
+ });
10
+ /** Variables the boot set for its own project (its workspace, its toolchains): never inherited by another project. */
11
+ const PROJECT_VARS = /^(AGENT_WORKSPACE|AGENT_PROJECT|AGENT_SANDBOX_WORKSPACE|AGENT_PROJECT_FILE|CARGO_TARGET_DIR|VIRTUAL_ENV)$/i;
12
+ /** The environment a later project starts from: the daemon's own, without the boot project's toolchains. */
13
+ export const baseEnvFor = (boot, current = process.env) => {
14
+ if (!boot)
15
+ return { ...current };
16
+ const drop = new Set(boot.toolVars.map((v) => v.toLowerCase()));
17
+ // Windows spells it Path; a plain object would keep both spellings.
18
+ const base = Object.fromEntries(Object.entries(current).filter(([k]) => !drop.has(k.toLowerCase()) && !PROJECT_VARS.test(k) && k.toLowerCase() !== 'path'));
19
+ return { ...base, PATH: boot.basePath };
20
+ };
@@ -0,0 +1,5 @@
1
+ import type { DatabasePlan } from '../core/types.js';
2
+ import type { ProjectStatusT } from '../shared/schema.js';
3
+ /** The launch folder's project id (older launch folders: derived from its name). */
4
+ export declare const primaryProject: () => string;
5
+ export declare const saveProjectStatus: (name: string, workspace: string, phase: ProjectStatusT["phase"], message: string, databases: DatabasePlan[], summary: string[]) => void;
@@ -0,0 +1,17 @@
1
+ import { now, writeJsonAtomic } from '../shared/rundir.js';
2
+ import { hubName } from '../shared/projects.js';
3
+ import { errMsg, run, session, warn } from './env.js';
4
+ /** The launch folder's project id (older launch folders: derived from its name). */
5
+ export const primaryProject = () => session.projectId ?? hubName(session.workspaceHost || session.project, []);
6
+ export const saveProjectStatus = (name, workspace, phase, message, databases, summary) => {
7
+ try {
8
+ writeJsonAtomic(run.project(name), {
9
+ name, workspace, phase, message: message.slice(0, 3500), updatedAt: now(),
10
+ databases: databases.filter((d) => d.database !== 'sqlserver').slice(0, 16).map((d) => ({ database: d.database, port: d.port })),
11
+ summary: summary.slice(0, 64).map((s) => s.slice(0, 500)),
12
+ });
13
+ }
14
+ catch (e) {
15
+ warn('Could not write the status of project ' + name + ': ' + errMsg(e));
16
+ }
17
+ };
@@ -0,0 +1,22 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export declare const ensurePtyHost: () => string;
3
+ /** Resolves when ptyhost listens; rejects early if it exits first (e.g. the agent could not be started). */
4
+ export declare const waitForPtyHost: (child: ChildProcess, port: number, timeoutMs: number) => Promise<void>;
5
+ export declare const ptyHostLog: (id: string) => string;
6
+ /** The last lines of a session's ptyhost log (the agent's final output is written there when it exits). */
7
+ export declare const ptyHostLogTail: (id: string, lines?: number) => string;
8
+ export interface PtyHostOptions {
9
+ exe: string;
10
+ port: number;
11
+ token: string;
12
+ cols: number;
13
+ rows: number;
14
+ command: string;
15
+ restartCommand: string;
16
+ cwd: string;
17
+ env: NodeJS.ProcessEnv;
18
+ id: string;
19
+ remote: boolean;
20
+ }
21
+ /** Starts ptyhost detached, so closing a terminal tab never kills the agent; only the agent exiting ends it. */
22
+ export declare const startPtyHost: (o: PtyHostOptions) => ChildProcess;
@@ -0,0 +1,36 @@
1
+ // Starts one ptyhost (the ConPTY bridge, sandbox/ptyhost.cs) per agent session and waits for it to listen.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawn } from 'node:child_process';
5
+ import { ensureCompiled } from './helpers.js';
6
+ import { portOpen } from './ports.js';
7
+ import { logsDir } from './env.js';
8
+ export const ensurePtyHost = () => ensureCompiled('ptyhost');
9
+ /** Resolves when ptyhost listens; rejects early if it exits first (e.g. the agent could not be started). */
10
+ export const waitForPtyHost = async (child, port, timeoutMs) => {
11
+ let exitCode;
12
+ child.once('exit', (code) => { exitCode = code; });
13
+ const deadline = Date.now() + timeoutMs;
14
+ while (Date.now() < deadline) {
15
+ if (exitCode !== undefined)
16
+ throw new Error('the session host stopped right away (exit ' + exitCode + ')');
17
+ if (await portOpen(port))
18
+ return;
19
+ await new Promise((r) => setTimeout(r, 200));
20
+ }
21
+ throw new Error('the session host did not start listening on port ' + port);
22
+ };
23
+ export const ptyHostLog = (id) => path.join(logsDir, 'ptyhost-' + id + '.log');
24
+ /** The last lines of a session's ptyhost log (the agent's final output is written there when it exits). */
25
+ export const ptyHostLogTail = (id, lines = 12) => {
26
+ try {
27
+ return fs.readFileSync(ptyHostLog(id), 'utf8').trim().split(/\r?\n/).slice(-lines).join('\n');
28
+ }
29
+ catch {
30
+ return '';
31
+ }
32
+ };
33
+ /** Starts ptyhost detached, so closing a terminal tab never kills the agent; only the agent exiting ends it. */
34
+ export const startPtyHost = (o) => spawn(o.exe, [String(o.port), o.token, String(o.cols), String(o.rows), ptyHostLog(o.id), o.command, o.restartCommand], {
35
+ cwd: o.cwd, env: { ...o.env, PTYHOST_BIND: o.remote ? 'any' : 'loopback' }, detached: true, stdio: 'ignore', windowsHide: true,
36
+ });