windows-development-sandbox 0.0.0-stage → 1.0.5

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 (231) hide show
  1. package/.sandbox-config.example.json +14 -0
  2. package/README.md +78 -2
  3. package/config.example.json +332 -0
  4. package/dist/api.d.ts +31 -0
  5. package/dist/api.js +30 -0
  6. package/dist/bin/agent-sandbox.d.ts +2 -0
  7. package/dist/bin/agent-sandbox.js +3 -0
  8. package/dist/bin/claude-host.d.ts +2 -0
  9. package/dist/bin/claude-host.js +5 -0
  10. package/dist/bin/claude-sbx.d.ts +2 -0
  11. package/dist/bin/claude-sbx.js +3 -0
  12. package/dist/bin/codex-sbx.d.ts +2 -0
  13. package/dist/bin/codex-sbx.js +3 -0
  14. package/dist/bin/gemini-sbx.d.ts +2 -0
  15. package/dist/bin/gemini-sbx.js +3 -0
  16. package/dist/bin/sandbox.d.ts +2 -0
  17. package/dist/bin/sandbox.js +4 -0
  18. package/dist/bin/sbx-hide.d.ts +2 -0
  19. package/dist/bin/sbx-hide.js +3 -0
  20. package/dist/bin/sbx-show.d.ts +2 -0
  21. package/dist/bin/sbx-show.js +3 -0
  22. package/dist/cli/args.d.ts +32 -0
  23. package/dist/cli/args.js +111 -0
  24. package/dist/cli/commands/accounts.d.ts +2 -0
  25. package/dist/cli/commands/accounts.js +52 -0
  26. package/dist/cli/commands/context.d.ts +21 -0
  27. package/dist/cli/commands/context.js +54 -0
  28. package/dist/cli/commands/data.d.ts +46 -0
  29. package/dist/cli/commands/data.js +249 -0
  30. package/dist/cli/commands/index.d.ts +8 -0
  31. package/dist/cli/commands/index.js +8 -0
  32. package/dist/cli/commands/launch.d.ts +5 -0
  33. package/dist/cli/commands/launch.js +152 -0
  34. package/dist/cli/commands/maintenance.d.ts +8 -0
  35. package/dist/cli/commands/maintenance.js +125 -0
  36. package/dist/cli/commands/repair.d.ts +59 -0
  37. package/dist/cli/commands/repair.js +332 -0
  38. package/dist/cli/commands/sessions.d.ts +11 -0
  39. package/dist/cli/commands/sessions.js +113 -0
  40. package/dist/cli/commands/setup.d.ts +45 -0
  41. package/dist/cli/commands/setup.js +152 -0
  42. package/dist/cli/index.d.ts +14 -0
  43. package/dist/cli/index.js +96 -0
  44. package/dist/cli/manifest.d.ts +95 -0
  45. package/dist/cli/manifest.js +87 -0
  46. package/dist/cli/menu.d.ts +22 -0
  47. package/dist/cli/menu.js +187 -0
  48. package/dist/cli/settings.d.ts +11 -0
  49. package/dist/cli/settings.js +156 -0
  50. package/dist/core/config.d.ts +17 -0
  51. package/dist/core/config.js +103 -0
  52. package/dist/core/detect-databases.d.ts +15 -0
  53. package/dist/core/detect-databases.js +147 -0
  54. package/dist/core/detect-pwsh.d.ts +18 -0
  55. package/dist/core/detect-pwsh.js +111 -0
  56. package/dist/core/detect-toolchains.d.ts +30 -0
  57. package/dist/core/detect-toolchains.js +58 -0
  58. package/dist/core/local-config.d.ts +12 -0
  59. package/dist/core/local-config.js +64 -0
  60. package/dist/core/schema.d.ts +604 -0
  61. package/dist/core/schema.js +264 -0
  62. package/dist/core/types.d.ts +162 -0
  63. package/dist/core/types.js +1 -0
  64. package/dist/core/util.d.ts +53 -0
  65. package/dist/core/util.js +145 -0
  66. package/dist/core/workspace.d.ts +23 -0
  67. package/dist/core/workspace.js +162 -0
  68. package/dist/guest/daemon.d.ts +1 -0
  69. package/dist/guest/daemon.js +186 -0
  70. package/dist/guest/db.d.ts +1 -0
  71. package/dist/guest/db.js +60 -0
  72. package/dist/guest/env.d.ts +49 -0
  73. package/dist/guest/env.js +99 -0
  74. package/dist/guest/gitguard.d.ts +7 -0
  75. package/dist/guest/gitguard.js +60 -0
  76. package/dist/guest/gitrules.d.ts +2 -0
  77. package/dist/guest/gitrules.js +11 -0
  78. package/dist/guest/guide.d.ts +12 -0
  79. package/dist/guest/guide.js +68 -0
  80. package/dist/guest/main.d.ts +1 -0
  81. package/dist/guest/main.js +120 -0
  82. package/dist/guest/ptyhost.d.ts +40 -0
  83. package/dist/guest/ptyhost.js +94 -0
  84. package/dist/guest/setup/agents.d.ts +8 -0
  85. package/dist/guest/setup/agents.js +73 -0
  86. package/dist/guest/setup/claude.d.ts +21 -0
  87. package/dist/guest/setup/claude.js +138 -0
  88. package/dist/guest/setup/environment.d.ts +6 -0
  89. package/dist/guest/setup/environment.js +65 -0
  90. package/dist/guest/setup/hooks.d.ts +1 -0
  91. package/dist/guest/setup/hooks.js +36 -0
  92. package/dist/guest/setup/network.d.ts +10 -0
  93. package/dist/guest/setup/network.js +99 -0
  94. package/dist/guest/setup/policy.d.ts +3 -0
  95. package/dist/guest/setup/policy.js +18 -0
  96. package/dist/guest/setup/user.d.ts +7 -0
  97. package/dist/guest/setup/user.js +47 -0
  98. package/dist/guest/toolchains/binstall.d.ts +7 -0
  99. package/dist/guest/toolchains/binstall.js +55 -0
  100. package/dist/guest/toolchains/catalogue.d.ts +51 -0
  101. package/dist/guest/toolchains/catalogue.js +31 -0
  102. package/dist/guest/toolchains/cli.d.ts +10 -0
  103. package/dist/guest/toolchains/cli.js +64 -0
  104. package/dist/guest/toolchains/common.d.ts +37 -0
  105. package/dist/guest/toolchains/common.js +124 -0
  106. package/dist/guest/toolchains/databases.d.ts +7 -0
  107. package/dist/guest/toolchains/databases.js +226 -0
  108. package/dist/guest/toolchains/git.d.ts +3 -0
  109. package/dist/guest/toolchains/git.js +112 -0
  110. package/dist/guest/toolchains/go.d.ts +3 -0
  111. package/dist/guest/toolchains/go.js +33 -0
  112. package/dist/guest/toolchains/index.d.ts +13 -0
  113. package/dist/guest/toolchains/index.js +122 -0
  114. package/dist/guest/toolchains/java.d.ts +8 -0
  115. package/dist/guest/toolchains/java.js +96 -0
  116. package/dist/guest/toolchains/managers.d.ts +16 -0
  117. package/dist/guest/toolchains/managers.js +82 -0
  118. package/dist/guest/toolchains/msvc.d.ts +10 -0
  119. package/dist/guest/toolchains/msvc.js +216 -0
  120. package/dist/guest/toolchains/native.d.ts +12 -0
  121. package/dist/guest/toolchains/native.js +220 -0
  122. package/dist/guest/toolchains/node.d.ts +3 -0
  123. package/dist/guest/toolchains/node.js +132 -0
  124. package/dist/guest/toolchains/pwsh.d.ts +4 -0
  125. package/dist/guest/toolchains/pwsh.js +14 -0
  126. package/dist/guest/toolchains/python.d.ts +3 -0
  127. package/dist/guest/toolchains/python.js +47 -0
  128. package/dist/guest/toolchains/runtimes.d.ts +17 -0
  129. package/dist/guest/toolchains/runtimes.js +40 -0
  130. package/dist/guest/toolchains/scripting.d.ts +10 -0
  131. package/dist/guest/toolchains/scripting.js +137 -0
  132. package/dist/guest/toolchains/versions.d.ts +47 -0
  133. package/dist/guest/toolchains/versions.js +77 -0
  134. package/dist/guest/toolchains/vscode.d.ts +2 -0
  135. package/dist/guest/toolchains/vscode.js +53 -0
  136. package/dist/guest/view.d.ts +1 -0
  137. package/dist/guest/view.js +22 -0
  138. package/dist/host/accounts.d.ts +32 -0
  139. package/dist/host/accounts.js +119 -0
  140. package/dist/host/channel.d.ts +37 -0
  141. package/dist/host/channel.js +180 -0
  142. package/dist/host/claude-profile.d.ts +15 -0
  143. package/dist/host/claude-profile.js +103 -0
  144. package/dist/host/cleanup.d.ts +23 -0
  145. package/dist/host/cleanup.js +66 -0
  146. package/dist/host/generate.d.ts +15 -0
  147. package/dist/host/generate.js +185 -0
  148. package/dist/host/personalize.d.ts +10 -0
  149. package/dist/host/personalize.js +97 -0
  150. package/dist/host/platform.d.ts +17 -0
  151. package/dist/host/platform.js +114 -0
  152. package/dist/host/project-scan.d.ts +23 -0
  153. package/dist/host/project-scan.js +173 -0
  154. package/dist/host/repo-guard.d.ts +23 -0
  155. package/dist/host/repo-guard.js +128 -0
  156. package/dist/host/secrets.d.ts +14 -0
  157. package/dist/host/secrets.js +133 -0
  158. package/dist/host/terminal.d.ts +32 -0
  159. package/dist/host/terminal.js +127 -0
  160. package/dist/host/window.d.ts +4 -0
  161. package/dist/host/window.js +68 -0
  162. package/dist/runtime/daemon.js +20291 -0
  163. package/dist/runtime/db.js +20423 -0
  164. package/dist/runtime/gitguard.js +84 -0
  165. package/dist/runtime/main.js +22398 -0
  166. package/dist/runtime/view.js +19966 -0
  167. package/dist/selftest/cli.test.d.ts +2 -0
  168. package/dist/selftest/cli.test.js +30 -0
  169. package/dist/selftest/config.test.d.ts +3 -0
  170. package/dist/selftest/config.test.js +74 -0
  171. package/dist/selftest/data.test.d.ts +2 -0
  172. package/dist/selftest/data.test.js +73 -0
  173. package/dist/selftest/detect.test.d.ts +2 -0
  174. package/dist/selftest/detect.test.js +42 -0
  175. package/dist/selftest/fixtures.d.ts +232 -0
  176. package/dist/selftest/fixtures.js +63 -0
  177. package/dist/selftest/guest.test.d.ts +3 -0
  178. package/dist/selftest/guest.test.js +61 -0
  179. package/dist/selftest/harness.d.ts +14 -0
  180. package/dist/selftest/harness.js +36 -0
  181. package/dist/selftest/helpers.test.d.ts +2 -0
  182. package/dist/selftest/helpers.test.js +125 -0
  183. package/dist/selftest/host.test.d.ts +2 -0
  184. package/dist/selftest/host.test.js +71 -0
  185. package/dist/selftest/index.d.ts +1 -0
  186. package/dist/selftest/index.js +56 -0
  187. package/dist/selftest/live.test.d.ts +7 -0
  188. package/dist/selftest/live.test.js +127 -0
  189. package/dist/selftest/mirror.test.d.ts +3 -0
  190. package/dist/selftest/mirror.test.js +74 -0
  191. package/dist/selftest/security.test.d.ts +3 -0
  192. package/dist/selftest/security.test.js +143 -0
  193. package/dist/selftest/setup.test.d.ts +2 -0
  194. package/dist/selftest/setup.test.js +23 -0
  195. package/dist/selftest/sync.test.d.ts +3 -0
  196. package/dist/selftest/sync.test.js +82 -0
  197. package/dist/shared/download.d.ts +58 -0
  198. package/dist/shared/download.js +200 -0
  199. package/dist/shared/guest-paths.d.ts +30 -0
  200. package/dist/shared/guest-paths.js +61 -0
  201. package/dist/shared/netrules.d.ts +18 -0
  202. package/dist/shared/netrules.js +98 -0
  203. package/dist/shared/paths.d.ts +9 -0
  204. package/dist/shared/paths.js +22 -0
  205. package/dist/shared/pty-client.d.ts +86 -0
  206. package/dist/shared/pty-client.js +227 -0
  207. package/dist/shared/rundir.d.ts +30 -0
  208. package/dist/shared/rundir.js +86 -0
  209. package/dist/shared/safe-remove.d.ts +10 -0
  210. package/dist/shared/safe-remove.js +55 -0
  211. package/dist/shared/schema.d.ts +84 -0
  212. package/dist/shared/schema.js +51 -0
  213. package/dist/shared/system.d.ts +15 -0
  214. package/dist/shared/system.js +19 -0
  215. package/dist/sync/claude-mem.d.ts +8 -0
  216. package/dist/sync/claude-mem.js +77 -0
  217. package/dist/sync/history.d.ts +30 -0
  218. package/dist/sync/history.js +129 -0
  219. package/dist/sync/watcher.d.ts +13 -0
  220. package/dist/sync/watcher.js +116 -0
  221. package/hooks/post-agent/README.txt +1 -0
  222. package/hooks/pre-provision/README.txt +1 -0
  223. package/package.json +91 -4
  224. package/sandbox/bootstrap.ps1 +258 -0
  225. package/sandbox/gitshim.cs +35 -0
  226. package/sandbox/logon.cmd +16 -0
  227. package/sandbox/ptyhost.cs +603 -0
  228. package/sandbox/trust.cmd +35 -0
  229. package/sandbox/vswhere.cs +83 -0
  230. package/schemas/config.schema.json +1062 -0
  231. package/schemas/sandbox-config.schema.json +952 -0
@@ -0,0 +1,137 @@
1
+ // Ruby (RubyInstaller) and PHP (windows.php.net, with Composer), plus the Visual C++ runtime PHP and other native
2
+ // tools need. Gems and Composer's global packages live in persistent storage.
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { download, fetchJson, fetchText, installArchive, spawnAsync } from '../../shared/download.js';
6
+ import { SYSTEM } from '../../shared/system.js';
7
+ import { dryRun, execAsync, execOutput, exists, log, mkdirp } from '../env.js';
8
+ import { installOnce, resolveCached, tool, versionFromFiles, versionOf } from './common.js';
9
+ import { pickPackageVersion } from './versions.js';
10
+ /** Writes `<name>.cmd` into tools\shims (on PATH). */
11
+ const shim = (env, name, command) => {
12
+ fs.writeFileSync(path.join(mkdirp(tool('shims')), name + '.cmd'), '@echo off\r\n' + command + ' %*\r\n');
13
+ env.prepend.push(tool('shims'));
14
+ };
15
+ // ---- Visual C++ runtime ----------------------------------------------------------------------------------------------
16
+ const VC_REDIST = 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
17
+ /**
18
+ * vcruntime140.dll & co. A real installer (it writes to System32), so it runs on every boot; the installer itself is
19
+ * kept in persistent storage and only run when Microsoft's signature on it checks out. Resolves true when this call
20
+ * ran the installer. Only the "Visual C++ runtime" step reports it (`summary`); PHP calls it again just to be sure.
21
+ */
22
+ export const setupVcRuntime = async (env, summary = true) => {
23
+ if (dryRun) {
24
+ log('(dry run) would install the Visual C++ runtime');
25
+ return false;
26
+ }
27
+ const system32 = path.join(process.env.WINDIR ?? 'C:\\Windows', 'System32');
28
+ const missing = !exists(path.join(system32, 'vcruntime140.dll')) || !exists(path.join(system32, 'msvcp140.dll'));
29
+ if (missing) {
30
+ const installer = tool('vcredist', 'vc_redist.x64.exe');
31
+ if (!exists(installer)) {
32
+ log('Downloading the Visual C++ runtime ...');
33
+ await download(VC_REDIST, installer);
34
+ }
35
+ const signer = execOutput(SYSTEM.powershell(), ['-NoLogo', '-NoProfile', '-Command', "$s = Get-AuthenticodeSignature -LiteralPath '" + installer.replace(/'/g, "''") + "'; if ($s.Status -eq 'Valid') { $s.SignerCertificate.Subject }"]);
36
+ if (signer.trim() !== 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US') {
37
+ fs.rmSync(installer, { force: true });
38
+ throw new Error('vc_redist.x64.exe is not signed by Microsoft; deleted it (it is downloaded again next boot)');
39
+ }
40
+ log('Installing the Visual C++ runtime ...');
41
+ // 1638: a newer version is already installed; 3010: installed, a restart would finish it (not needed for the DLLs).
42
+ const code = await spawnAsync(installer, ['/install', '/quiet', '/norestart']);
43
+ if (![0, 1638, 3010].includes(code ?? -1) || !exists(path.join(system32, 'vcruntime140.dll')))
44
+ throw new Error('the Visual C++ runtime installer failed (exit ' + code + ')');
45
+ }
46
+ if (summary)
47
+ env.summary.push('vc++ runtime: installed');
48
+ return missing;
49
+ };
50
+ const RUBY_ASSET = /^rubyinstaller-(\d+\.\d+\.\d+)-(\d+)-x64\.7z$/;
51
+ /** RubyInstaller builds per Ruby version (newest build of each), from its GitHub releases. */
52
+ const rubyBuilds = async () => {
53
+ const releases = await fetchJson('https://api.github.com/repos/oneclick/rubyinstaller2/releases?per_page=100');
54
+ const builds = new Map();
55
+ for (const asset of releases.filter((r) => !r.draft && !r.prerelease).flatMap((r) => r.assets)) {
56
+ const match = RUBY_ASSET.exec(asset.name);
57
+ const known = match && builds.get(match[1]);
58
+ if (match && (!known || Number(RUBY_ASSET.exec(known.name)[2]) < Number(match[2])))
59
+ builds.set(match[1], asset);
60
+ }
61
+ return builds;
62
+ };
63
+ export const setupRuby = async (env, config) => {
64
+ const wantedSpec = versionFromFiles(['.ruby-version']) ?? { spec: config.version, source: 'config' };
65
+ if (dryRun) {
66
+ log('(dry run) would provision Ruby ' + wantedSpec.spec);
67
+ return;
68
+ }
69
+ const version = await resolveCached('ruby:' + wantedSpec.spec, async () => pickPackageVersion([...(await rubyBuilds()).keys()], wantedSpec.spec) ?? Promise.reject(new Error('No RubyInstaller build matches "' + wantedSpec.spec + '"')));
70
+ const home = tool('ruby', version);
71
+ if (!exists(path.join(home, 'bin', 'ruby.exe'))) {
72
+ const asset = (await rubyBuilds()).get(version);
73
+ if (!asset)
74
+ throw new Error('RubyInstaller ' + version + ' is no longer published');
75
+ log('Downloading ' + asset.name + ' ...');
76
+ await installArchive(asset.browser_download_url, home, { sha256: asset.digest?.startsWith('sha256:') ? asset.digest.slice(7) : null, locate: 'ruby.exe', up: 1, fileName: asset.name });
77
+ }
78
+ const gems = tool('ruby-gems', version.split('.').slice(0, 2).join('.'));
79
+ Object.assign(env.vars, { GEM_HOME: gems, BUNDLE_USER_HOME: tool('bundle-home') });
80
+ env.prepend.push(path.join(home, 'bin'), path.join(gems, 'bin'));
81
+ env.summary.push('ruby: ' + versionOf(path.join(home, 'bin', 'ruby.exe')) + ' (from ' + wantedSpec.source + '; gems with C extensions need MSYS2, not included)');
82
+ const gem = path.join(home, 'bin', 'gem.cmd');
83
+ await installOnce(env, 'gem@' + version, config.gems, (pkg) => execAsync(SYSTEM.cmd(), ['/d', '/c', gem, 'install', pkg, '--no-document'], { env: { ...process.env, GEM_HOME: gems } }));
84
+ };
85
+ const PHP_EXTENSIONS = ['curl', 'fileinfo', 'gd', 'intl', 'mbstring', 'openssl', 'pdo_mysql', 'pdo_pgsql', 'pdo_sqlite', 'sodium', 'sqlite3', 'zip'];
86
+ /** The non-thread-safe x64 zip of the newest release matching `spec` ("8" -> newest 8.x, "8.3" -> newest 8.3.x). */
87
+ const phpBuild = async (spec) => {
88
+ const releases = await fetchJson('https://windows.php.net/downloads/releases/releases.json');
89
+ const pick = pickPackageVersion(Object.values(releases).map((r) => r.version), spec);
90
+ const release = Object.values(releases).find((r) => r.version === pick);
91
+ const build = release && Object.entries(release).find(([key, value]) => /^nts-vs\d+-x64$/.test(key) && typeof value === 'object' && value.zip)?.[1];
92
+ if (!release || typeof build !== 'object' || !build.zip)
93
+ throw new Error('No PHP build for Windows matches "' + spec + '"');
94
+ return { version: release.version, ...build.zip };
95
+ };
96
+ /** php.ini from the development template with the common extensions switched on. */
97
+ const writePhpIni = (home) => {
98
+ const ini = path.join(home, 'php.ini');
99
+ if (exists(ini) || !exists(path.join(home, 'php.ini-development')))
100
+ return;
101
+ let text = fs.readFileSync(path.join(home, 'php.ini-development'), 'utf8')
102
+ .replace(/^;\s*extension_dir\s*=\s*"ext"/m, 'extension_dir = "' + path.join(home, 'ext') + '"');
103
+ for (const ext of PHP_EXTENSIONS.filter((e) => exists(path.join(home, 'ext', 'php_' + e + '.dll'))))
104
+ text = text.replace(new RegExp('^;(extension=' + ext + ')\\s*$', 'm'), '$1');
105
+ fs.writeFileSync(ini, text);
106
+ };
107
+ export const setupPhp = async (env, config) => {
108
+ const wantedSpec = versionFromFiles(['.php-version']) ?? { spec: config.version, source: 'config' };
109
+ if (dryRun) {
110
+ log('(dry run) would provision PHP ' + wantedSpec.spec + (config.composer ? ' + Composer' : ''));
111
+ return;
112
+ }
113
+ // No summary line of its own: the Visual C++ runtime step (which runs first) already reports it.
114
+ await setupVcRuntime(env, false);
115
+ const version = await resolveCached('php:' + wantedSpec.spec, async () => (await phpBuild(wantedSpec.spec)).version);
116
+ const home = tool('php', version);
117
+ if (!exists(path.join(home, 'php.exe'))) {
118
+ const build = await phpBuild(version);
119
+ log('Downloading ' + build.path + ' ...');
120
+ await installArchive('https://windows.php.net/downloads/releases/' + build.path, home, { sha256: build.sha256, locate: 'php.exe', fileName: build.path });
121
+ }
122
+ writePhpIni(home);
123
+ env.prepend.push(home);
124
+ env.summary.push('php: ' + version + ' (from ' + wantedSpec.source + ')');
125
+ if (!config.composer)
126
+ return;
127
+ const phar = tool('composer', 'composer.phar');
128
+ if (!exists(phar)) {
129
+ log('Downloading Composer ...');
130
+ await download('https://getcomposer.org/download/latest-stable/composer.phar', phar, (await fetchText('https://getcomposer.org/download/latest-stable/composer.phar.sha256sum')).trim().split(/\s+/)[0]);
131
+ }
132
+ const composerHome = tool('composer-home');
133
+ env.vars.COMPOSER_HOME = composerHome;
134
+ env.prepend.push(path.join(composerHome, 'vendor', 'bin'));
135
+ shim(env, 'composer', '"' + path.join(home, 'php.exe') + '" "' + phar + '"');
136
+ env.summary.push('composer: ' + (/\d+\.\d+\.\d+/.exec(execOutput(path.join(home, 'php.exe'), [phar, '--version', '--no-ansi']))?.[0] ?? 'installed'));
137
+ };
@@ -0,0 +1,47 @@
1
+ export interface NodeRelease {
2
+ version: string;
3
+ lts: string | false;
4
+ }
5
+ /** Turns an .nvmrc-style spec (20, v20.11.1, lts/*, lts/iron, node, latest) into an exact version like 20.11.1. */
6
+ export declare const pickNodeVersion: (releases: NodeRelease[], spec: string) => string | null;
7
+ /**
8
+ * Which of this PC's nvm versions to copy along with `current`: at most `max` in total, current included, the rest
9
+ * spread across the range (newest of each major first, then evenly spaced from oldest to newest).
10
+ */
11
+ export declare const pickSpreadVersions: (available: string[], current: string, max: number) => string[];
12
+ /** Newest stable version matching a prefix spec ("3" -> 3.13.1, "3.12" -> 3.12.10). */
13
+ export declare const pickPackageVersion: (all: string[], spec: string) => string | null;
14
+ /** Version spec from a version file's text (.ruby-version "ruby-3.3.1", .java-version "temurin-21", ...): its first x[.y[.z]]. */
15
+ export declare const specFromVersionFile: (text: string) => string | null;
16
+ /** Java major version from .java-version ("21", "17.0.9", "1.8", "temurin-21") or .sdkmanrc ("java=21.0.2-tem"). */
17
+ export declare const javaMajorFrom: (text: string, sdkmanrc?: boolean) => string | null;
18
+ export interface GoRelease {
19
+ version: string;
20
+ stable: boolean;
21
+ files: {
22
+ filename: string;
23
+ os: string;
24
+ arch: string;
25
+ kind: string;
26
+ sha256: string;
27
+ }[];
28
+ }
29
+ /** "stable" / "latest" -> newest stable release; "1.23" -> newest 1.23.x; "1.23.2" -> exactly that. Returns go.dev's entry. */
30
+ export declare const pickGoRelease: (releases: GoRelease[], spec: string) => GoRelease | null;
31
+ /**
32
+ * Does the project use a language? `entries` are file names in the workspace root and one folder down ("api/go.mod");
33
+ * markers are exact names or "*.ext".
34
+ */
35
+ export declare const projectUses: (entries: readonly string[], markers: readonly string[]) => boolean;
36
+ /** Newest MSVC tools version among VS manifest package ids ("microsoft.vc.14.44.17.14.tools.hostx64.targetx64.base"). */
37
+ export declare const pickMsvcVersion: (packageIds: string[], spec?: string) => string | null;
38
+ /** Newest Windows SDK build among component ids ("microsoft.visualstudio.component.windows11sdk.26100"). */
39
+ export declare const pickWindowsSdk: (packageIds: string[], spec?: string) => {
40
+ build: string;
41
+ componentId: string;
42
+ } | null;
43
+ /** The .cab files an SDK .msi refers to (their names are inside the msi as plain "<32 hex>.cab"). */
44
+ export declare const msiCabs: (msi: Buffer) => string[];
45
+ /** The oldest CMake many projects (OpenMoHAA among them) accept. */
46
+ export declare const CMAKE_FLOOR = "3.25";
47
+ export declare const cmakeTooOld: (version: string) => boolean;
@@ -0,0 +1,77 @@
1
+ // Version picking for the toolchains. Pure functions only (no environment, no I/O), so the host and the tests can
2
+ // import them without loading the guest runtime.
3
+ /** Turns an .nvmrc-style spec (20, v20.11.1, lts/*, lts/iron, node, latest) into an exact version like 20.11.1. */
4
+ export const pickNodeVersion = (releases, spec) => {
5
+ const s = spec.trim().toLowerCase().replace(/^v/, '');
6
+ const hit = ['node', 'latest', 'current', 'stable'].includes(s) ? releases[0]
7
+ : s === 'lts' || s === 'lts/*' ? releases.find((r) => r.lts)
8
+ : s.startsWith('lts/') ? releases.find((r) => typeof r.lts === 'string' && r.lts.toLowerCase() === s.slice(4))
9
+ : releases.find((r) => r.version === 'v' + s || r.version.startsWith('v' + s + '.'));
10
+ return hit ? hit.version.slice(1) : null;
11
+ };
12
+ /**
13
+ * Which of this PC's nvm versions to copy along with `current`: at most `max` in total, current included, the rest
14
+ * spread across the range (newest of each major first, then evenly spaced from oldest to newest).
15
+ */
16
+ export const pickSpreadVersions = (available, current, max) => {
17
+ const byVersion = (a, b) => a.localeCompare(b, undefined, { numeric: true });
18
+ const exact = available.filter((v) => /^\d+\.\d+\.\d+$/.test(v) && v !== current).sort(byVersion);
19
+ const newestPerMajor = exact.filter((v, i) => exact[i + 1]?.split('.')[0] !== v.split('.')[0]);
20
+ const pool = newestPerMajor.filter((v) => v.split('.')[0] !== current.split('.')[0]);
21
+ const want = Math.max(0, max - 1);
22
+ const picked = want >= pool.length ? pool
23
+ : want === 1 ? [pool[pool.length - 1]]
24
+ : Array.from({ length: want }, (_, i) => pool[Math.round(i * (pool.length - 1) / (want - 1))]);
25
+ return [current, ...picked];
26
+ };
27
+ /** Newest stable version matching a prefix spec ("3" -> 3.13.1, "3.12" -> 3.12.10). */
28
+ export const pickPackageVersion = (all, spec) => {
29
+ const s = spec.trim().replace(/^python-?/i, '');
30
+ const stable = all.filter((v) => /^\d+(\.\d+)*$/.test(v)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
31
+ return stable.find((v) => v === s || v.startsWith(s + '.')) ?? null;
32
+ };
33
+ /** Version spec from a version file's text (.ruby-version "ruby-3.3.1", .java-version "temurin-21", ...): its first x[.y[.z]]. */
34
+ export const specFromVersionFile = (text) => /(\d+(?:\.\d+){0,2})/.exec(text.split(/\r?\n/).map((l) => l.replace(/#.*$/, '').trim()).find(Boolean) ?? '')?.[1] ?? null;
35
+ /** Java major version from .java-version ("21", "17.0.9", "1.8", "temurin-21") or .sdkmanrc ("java=21.0.2-tem"). */
36
+ export const javaMajorFrom = (text, sdkmanrc = false) => {
37
+ const line = sdkmanrc ? /^\s*java\s*=\s*(.+)$/m.exec(text)?.[1] : text;
38
+ const spec = line ? specFromVersionFile(line) : null;
39
+ if (!spec)
40
+ return null;
41
+ const [major, minor] = spec.split('.');
42
+ return major === '1' && minor ? minor : major;
43
+ };
44
+ /** "stable" / "latest" -> newest stable release; "1.23" -> newest 1.23.x; "1.23.2" -> exactly that. Returns go.dev's entry. */
45
+ export const pickGoRelease = (releases, spec) => {
46
+ const s = spec.trim().toLowerCase().replace(/^go/, '');
47
+ const stable = releases.filter((r) => r.stable);
48
+ if (s === 'stable' || s === 'latest')
49
+ return stable[0] ?? null;
50
+ return stable.find((r) => r.version === 'go' + s || r.version.startsWith('go' + s + '.')) ?? null;
51
+ };
52
+ /**
53
+ * Does the project use a language? `entries` are file names in the workspace root and one folder down ("api/go.mod");
54
+ * markers are exact names or "*.ext".
55
+ */
56
+ export const projectUses = (entries, markers) => entries.some((entry) => {
57
+ const name = entry.split(/[\/]/).pop().toLowerCase();
58
+ return markers.some((m) => (m.startsWith('*.') ? name.endsWith(m.slice(1).toLowerCase()) : name === m.toLowerCase()));
59
+ });
60
+ /** Newest MSVC tools version among VS manifest package ids ("microsoft.vc.14.44.17.14.tools.hostx64.targetx64.base"). */
61
+ export const pickMsvcVersion = (packageIds, spec = 'latest') => {
62
+ const versions = packageIds.map((id) => /^microsoft\.vc\.(\d+\.\d+\.\d+\.\d+)\.tools\.hostx64\.targetx64\.base$/i.exec(id)?.[1]).filter((v) => Boolean(v));
63
+ const sorted = [...new Set(versions)].sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
64
+ return (spec === 'latest' ? sorted[0] : sorted.find((v) => v === spec || v.startsWith(spec + '.'))) ?? null;
65
+ };
66
+ /** Newest Windows SDK build among component ids ("microsoft.visualstudio.component.windows11sdk.26100"). */
67
+ export const pickWindowsSdk = (packageIds, spec = 'latest') => {
68
+ const found = packageIds.map((id) => ({ id, build: /^microsoft\.visualstudio\.component\.windows1[01]sdk\.(\d+)$/i.exec(id)?.[1] })).filter((x) => Boolean(x.build));
69
+ const sorted = found.sort((a, b) => Number(b.build) - Number(a.build));
70
+ const hit = spec === 'latest' ? sorted[0] : sorted.find((x) => x.build === spec.replace(/^10\.0\./, '').split('.')[0]);
71
+ return hit ? { build: hit.build, componentId: hit.id } : null;
72
+ };
73
+ /** The .cab files an SDK .msi refers to (their names are inside the msi as plain "<32 hex>.cab"). */
74
+ export const msiCabs = (msi) => [...new Set([...msi.toString('latin1').matchAll(/([0-9a-f]{32}\.cab)/gi)].map((m) => m[1].toLowerCase()))];
75
+ /** The oldest CMake many projects (OpenMoHAA among them) accept. */
76
+ export const CMAKE_FLOOR = '3.25';
77
+ export const cmakeTooOld = (version) => /^\d+\.\d+/.test(version) && version.localeCompare(CMAKE_FLOOR, undefined, { numeric: true }) < 0;
@@ -0,0 +1,2 @@
1
+ import { type ToolEnv } from './common.js';
2
+ export declare const setupVscode: (env: ToolEnv, refresh: boolean) => Promise<void>;
@@ -0,0 +1,53 @@
1
+ // VS Code for debugging inside the sandbox: the portable zip build, checksum-verified. A `data` folder next to Code.exe
2
+ // switches it to portable mode, so settings and extensions live in C:\AgentData\tools\vscode\data and persist.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fetchJson, installArchive } from '../../shared/download.js';
7
+ import { dryRun, exists, isWin, log, mkdirp, powershell } from '../env.js';
8
+ import { arch, tool } from './common.js';
9
+ /** Newer builds keep their files under a commit-hash folder, so the installed version is recorded here. */
10
+ const VERSION_MARKER = '.agent-sandbox-version';
11
+ const desktopShortcut = (exe) => {
12
+ if (!isWin)
13
+ return;
14
+ const link = path.join(os.homedir(), 'Desktop', 'Visual Studio Code.lnk');
15
+ const q = (s) => "'" + s.replace(/'/g, "''") + "'";
16
+ powershell('$s = (New-Object -ComObject WScript.Shell).CreateShortcut(' + q(link) + '); $s.TargetPath = ' + q(exe) + '; $s.Arguments = ' + q('"' + (process.env.AGENT_WORKSPACE ?? '') + '"') + '; $s.Save()');
17
+ };
18
+ export const setupVscode = async (env, refresh) => {
19
+ const root = tool('vscode');
20
+ const exe = path.join(root, 'Code.exe');
21
+ if (dryRun) {
22
+ log('(dry run) would provision VS Code');
23
+ return;
24
+ }
25
+ if (!exists(exe) || refresh) {
26
+ const release = await fetchJson('https://update.code.visualstudio.com/api/update/win32-' + (arch === 'arm64' ? 'arm64' : 'x64') + '-archive/stable/latest');
27
+ log('Downloading VS Code ' + release.productVersion + ' (~150 MB once) ...');
28
+ // Keep settings/extensions across a refresh: move the portable data folder aside and back, also when the install
29
+ // fails (finally). A folder still parked from an interrupted earlier run is the one to keep.
30
+ const keep = path.join(root, 'data');
31
+ const parked = root + '-data';
32
+ if (exists(keep)) {
33
+ fs.rmSync(parked, { recursive: true, force: true });
34
+ fs.renameSync(keep, parked);
35
+ }
36
+ try {
37
+ await installArchive(release.url, root, { sha256: release.sha256hash, locate: 'Code.exe', fileName: 'vscode.zip' });
38
+ fs.writeFileSync(path.join(root, VERSION_MARKER), release.productVersion);
39
+ }
40
+ finally {
41
+ if (exists(parked)) {
42
+ mkdirp(root);
43
+ fs.rmSync(keep, { recursive: true, force: true });
44
+ fs.renameSync(parked, keep);
45
+ }
46
+ }
47
+ }
48
+ mkdirp(path.join(root, 'data'));
49
+ env.prepend.push(path.join(root, 'bin'));
50
+ desktopShortcut(exe);
51
+ const version = exists(path.join(root, VERSION_MARKER)) ? fs.readFileSync(path.join(root, VERSION_MARKER), 'utf8').trim() : 'installed';
52
+ env.summary.push('vscode: ' + version + ' (portable; run code . or use the desktop shortcut)');
53
+ };
@@ -0,0 +1 @@
1
+ export declare const viewSession: (id: string) => Promise<number>;
@@ -0,0 +1,22 @@
1
+ // A terminal tab inside the sandbox that shows one agent session (same pseudo console the host can attach to).
2
+ // Usage: node view.js <sessionId>
3
+ import { connect, runTerminalClient } from '../shared/pty-client.js';
4
+ import { readSession } from '../shared/rundir.js';
5
+ import { dataDir } from './env.js';
6
+ export const viewSession = async (id) => {
7
+ const session = readSession(dataDir, id);
8
+ if (!session?.attach) {
9
+ console.error('Session ' + id + ' is not running.');
10
+ return 1;
11
+ }
12
+ const socket = await connect({ ...session.attach, addresses: ['127.0.0.1'] });
13
+ const result = await runTerminalClient(socket, session.attach.token);
14
+ if (result.reason === 'exit')
15
+ console.log(session.harness + ' exited with code ' + result.exitCode + '.');
16
+ else if (result.reason === 'error')
17
+ console.log('Session error: ' + result.message);
18
+ return result.reason === 'exit' ? result.exitCode : 1;
19
+ };
20
+ if (process.argv[1]?.endsWith('view.js') && process.argv[2]) {
21
+ viewSession(process.argv[2]).then((code) => { process.exitCode = code; }, (e) => { console.error(e instanceof Error ? e.message : String(e)); process.exitCode = 1; });
22
+ }
@@ -0,0 +1,32 @@
1
+ export interface Account {
2
+ name: string;
3
+ savedAt: string;
4
+ email: string | null;
5
+ credentials: unknown;
6
+ oauthAccount: unknown;
7
+ }
8
+ export interface AccountRow {
9
+ name: string;
10
+ email: string | null;
11
+ isDefault: boolean;
12
+ isActive: boolean;
13
+ savedAt: string;
14
+ }
15
+ export declare const accountsDir: (storageRoot: string) => string;
16
+ export declare const sandboxClaudeDir: (dataPath: string) => string;
17
+ export declare const readAccount: (storageRoot: string, name: string) => Account | null;
18
+ export declare const defaultAccount: (storageRoot: string) => string | null;
19
+ export declare const activeAccount: (dataPath: string) => string | null;
20
+ export declare const listAccounts: (storageRoot: string, dataPath: string) => AccountRow[];
21
+ /** Saves the sandbox's current login (or this PC's, with fromHost) under `name`. */
22
+ export declare const saveAccount: (storageRoot: string, dataPath: string, name: string, fromHost: {
23
+ claudeDir: string;
24
+ globalStateFile: string;
25
+ } | null) => Account;
26
+ /**
27
+ * Makes `name` the sandbox's login. Claude refreshes (rotates) tokens while it runs, so the outgoing account's
28
+ * current tokens are saved back first; otherwise its stored refresh token could already be invalid.
29
+ */
30
+ export declare const activateAccount: (storageRoot: string, dataPath: string, name: string) => Account;
31
+ export declare const setDefaultAccount: (storageRoot: string, name: string | null) => void;
32
+ export declare const removeAccount: (storageRoot: string, name: string) => void;
@@ -0,0 +1,119 @@
1
+ // Several Claude logins for the sandbox. Each account is a saved copy of Claude's .credentials.json plus the
2
+ // `oauthAccount` block of .claude.json, kept in host-only storage (<storage>\accounts, never mapped into a sandbox).
3
+ // Switching writes them into the sandbox's persistent Claude state; when the sandbox is running the agent is then
4
+ // restarted in place with --continue so the conversation carries on.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { assertNoLinks, fail, fileExists, isLink, removeLink, tryReadJson, writeJson } from '../core/util.js';
8
+ const NAME = /^[A-Za-z0-9_-]{1,40}$/;
9
+ const ACCOUNT_CACHES = ['remote-settings.json', 'policy-limits.json', 'policy-limits.json.stamp.json'];
10
+ const MAX_LOGIN_BYTES = 1024 * 1024;
11
+ /** .claude.json holds per-project state and can be large; it is still capped so a hostile file cannot exhaust memory. */
12
+ const MAX_STATE_BYTES = 256 * 1024 * 1024;
13
+ export const accountsDir = (storageRoot) => path.join(storageRoot, 'accounts');
14
+ export const sandboxClaudeDir = (dataPath) => path.join(dataPath, 'state', 'claude');
15
+ const storageOf = (dataPath) => path.dirname(path.dirname(dataPath));
16
+ const dataName = (dataPath) => path.basename(dataPath);
17
+ const accountFile = (storageRoot, name) => path.join(accountsDir(storageRoot), name + '.json');
18
+ const indexFile = (storageRoot) => path.join(accountsDir(storageRoot), 'index.json');
19
+ const readIndex = (storageRoot) => tryReadJson(indexFile(storageRoot)) ?? {};
20
+ const updateIndex = (storageRoot, change) => writeJson(indexFile(storageRoot), change(readIndex(storageRoot)));
21
+ const assertName = (name) => { if (!NAME.test(name))
22
+ fail('Invalid account name "' + name + '".', 'Use letters, numbers, - and _ only (max 40), e.g. work or personal.'); };
23
+ const emailOf = (oauthAccount) => {
24
+ const email = oauthAccount?.emailAddress;
25
+ return typeof email === 'string' ? email.slice(0, 200) : null;
26
+ };
27
+ export const readAccount = (storageRoot, name) => (NAME.test(name) ? tryReadJson(accountFile(storageRoot, name)) : null);
28
+ export const defaultAccount = (storageRoot) => readIndex(storageRoot).default ?? null;
29
+ export const activeAccount = (dataPath) => readIndex(storageOf(dataPath)).active?.[dataName(dataPath)] ?? null;
30
+ const setActive = (dataPath, name) => updateIndex(storageOf(dataPath), (i) => ({ ...i, active: { ...i.active, [dataName(dataPath)]: name } }));
31
+ export const listAccounts = (storageRoot, dataPath) => {
32
+ const dir = accountsDir(storageRoot);
33
+ if (!fs.existsSync(dir))
34
+ return [];
35
+ const def = defaultAccount(storageRoot);
36
+ const active = activeAccount(dataPath);
37
+ return fs.readdirSync(dir)
38
+ .filter((f) => f.endsWith('.json') && f !== 'index.json')
39
+ .map((f) => readAccount(storageRoot, f.slice(0, -5)))
40
+ .filter((a) => a !== null)
41
+ .map((a) => ({ name: a.name, email: a.email, isDefault: a.name === def, isActive: a.name === active, savedAt: a.savedAt }));
42
+ };
43
+ /** Reads a JSON file that may be sandbox-written: no links, size-capped. */
44
+ const readUntrusted = (file, max) => (fileExists(file) && !isLink(file) && fs.statSync(file).size <= max ? tryReadJson(file) : null);
45
+ /** The login currently in a Claude config folder (+ its global state file). */
46
+ const readLogin = (claudeDir, globalStateFile) => {
47
+ const credentials = readUntrusted(path.join(claudeDir, '.credentials.json'), MAX_LOGIN_BYTES);
48
+ if (!credentials || typeof credentials !== 'object')
49
+ fail('No Claude login found in ' + claudeDir + '.', 'Sign in to Claude first (inside the sandbox, or on this PC with --from-host), then save it again.');
50
+ return { credentials, oauthAccount: readUntrusted(globalStateFile, MAX_STATE_BYTES)?.oauthAccount ?? null };
51
+ };
52
+ const store = (storageRoot, name, login) => {
53
+ const account = { name, savedAt: new Date().toISOString(), email: emailOf(login.oauthAccount), ...login };
54
+ writeJson(accountFile(storageRoot, name), account);
55
+ return account;
56
+ };
57
+ const sandboxLogin = (dataPath) => {
58
+ assertNoLinks(dataPath, sandboxClaudeDir(dataPath));
59
+ return { claudeDir: sandboxClaudeDir(dataPath), globalStateFile: path.join(sandboxClaudeDir(dataPath), '.claude.json') };
60
+ };
61
+ /** Saves the sandbox's current login (or this PC's, with fromHost) under `name`. */
62
+ export const saveAccount = (storageRoot, dataPath, name, fromHost) => {
63
+ assertName(name);
64
+ const source = fromHost ?? sandboxLogin(dataPath);
65
+ const account = store(storageRoot, name, readLogin(source.claudeDir, source.globalStateFile));
66
+ if (!fromHost)
67
+ setActive(dataPath, name);
68
+ if (!defaultAccount(storageRoot))
69
+ setDefaultAccount(storageRoot, name);
70
+ return account;
71
+ };
72
+ /** Replaces a file that may be a sandbox-planted link instead of writing through it. */
73
+ const writeSandboxJson = (file, value) => {
74
+ if (isLink(file))
75
+ removeLink(file);
76
+ writeJson(file, value);
77
+ };
78
+ /**
79
+ * Makes `name` the sandbox's login. Claude refreshes (rotates) tokens while it runs, so the outgoing account's
80
+ * current tokens are saved back first; otherwise its stored refresh token could already be invalid.
81
+ */
82
+ export const activateAccount = (storageRoot, dataPath, name) => {
83
+ const account = readAccount(storageRoot, name) ?? fail('Unknown account "' + name + '".', 'Saved accounts: ' + (listAccounts(storageRoot, dataPath).map((a) => a.name).join(', ') || 'none yet; save one with sandbox account save <name>'));
84
+ const { claudeDir, globalStateFile } = sandboxLogin(dataPath);
85
+ const outgoing = activeAccount(dataPath);
86
+ if (outgoing && outgoing !== name && readAccount(storageRoot, outgoing) && fileExists(path.join(claudeDir, '.credentials.json'))) {
87
+ try {
88
+ store(storageRoot, outgoing, readLogin(claudeDir, globalStateFile));
89
+ }
90
+ catch { /* nothing usable to save back */ }
91
+ }
92
+ fs.mkdirSync(claudeDir, { recursive: true });
93
+ // Caches tied to the previous account (Claude deletes these on /logout); otherwise its org policy could linger.
94
+ for (const cache of ACCOUNT_CACHES) {
95
+ const file = path.join(claudeDir, cache);
96
+ if (isLink(file))
97
+ removeLink(file);
98
+ else
99
+ fs.rmSync(file, { force: true });
100
+ }
101
+ writeSandboxJson(path.join(claudeDir, '.credentials.json'), account.credentials);
102
+ const state = readUntrusted(globalStateFile, MAX_STATE_BYTES);
103
+ if (account.oauthAccount && (state || !fileExists(globalStateFile)))
104
+ writeSandboxJson(globalStateFile, { ...(state ?? {}), oauthAccount: account.oauthAccount });
105
+ setActive(dataPath, name);
106
+ return account;
107
+ };
108
+ export const setDefaultAccount = (storageRoot, name) => {
109
+ if (name !== null && !readAccount(storageRoot, name))
110
+ fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
111
+ updateIndex(storageRoot, (i) => ({ ...i, default: name ?? undefined }));
112
+ };
113
+ export const removeAccount = (storageRoot, name) => {
114
+ if (!readAccount(storageRoot, name))
115
+ fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
116
+ fs.rmSync(accountFile(storageRoot, name));
117
+ if (defaultAccount(storageRoot) === name)
118
+ setDefaultAccount(storageRoot, null);
119
+ };
@@ -0,0 +1,37 @@
1
+ import os from 'node:os';
2
+ import type { GuestRequest, GuestResponse, SandboxStatus, SessionStatus } from '../core/types.js';
3
+ import { type Viewers } from '../shared/pty-client.js';
4
+ /**
5
+ * True only for IPv4 addresses inside the subnet of one of this PC's Hyper-V adapters ("vEthernet ..."), which is
6
+ * where Windows Sandbox lives. Stops a compromised sandbox from pointing the host (and your keystrokes) anywhere else.
7
+ */
8
+ export declare const isSandboxAddress: (ip: string, interfaces?: NodeJS.Dict<os.NetworkInterfaceInfo[]>) => boolean;
9
+ /** Waits until the sandbox finished provisioning, printing each boot step. */
10
+ export declare const waitForReady: (dataPath: string, launchId: string | null, isAlive: () => boolean) => Promise<SandboxStatus>;
11
+ export declare const sendRequest: (dataPath: string, request: GuestRequest) => void;
12
+ export declare const waitForResponse: (dataPath: string, requestId: string, isAlive: () => boolean, timeoutMs: number) => Promise<GuestResponse>;
13
+ export declare const requestSession: (dataPath: string, harness: string, args: string[], cwd: string) => string;
14
+ export declare const waitForSession: (dataPath: string, sessionId: string, isAlive: () => boolean, timeoutMs?: number) => Promise<SessionStatus>;
15
+ /** Runs a command inside the sandbox (in the workspace or a folder below it) and returns its output. */
16
+ export declare const execInSandbox: (dataPath: string, command: string[], cwd: string, timeoutSeconds: number, isAlive: () => boolean) => Promise<GuestResponse>;
17
+ /** Restarts running sessions in place (they resume via the harness's resumeArguments, e.g. claude --continue). */
18
+ export declare const restartSessions: (dataPath: string, harness: string | null, isAlive: () => boolean) => Promise<GuestResponse>;
19
+ export declare const pingSandbox: (dataPath: string, isAlive: () => boolean) => Promise<GuestResponse>;
20
+ export declare const runningSessions: (dataPath: string) => SessionStatus[];
21
+ /** Most sessions the host will ever contact for one listing: a sandbox can write as many session files as it likes. */
22
+ export declare const MAX_QUERIED_SESSIONS = 32;
23
+ /** Running sessions of the sandbox that is running now (its launch id), newest last, capped. */
24
+ export declare const currentSessions: (dataPath: string) => SessionStatus[];
25
+ /** The session with this id (or id prefix), else the most recently started running one. */
26
+ export declare const pickSession: (dataPath: string, sessionId?: string) => SessionStatus;
27
+ /** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
28
+ export declare const sessionViewers: (session: SessionStatus) => Promise<Viewers | null>;
29
+ /**
30
+ * The newest running session of this agent in exactly this folder that no terminal on this PC is attached to, so
31
+ * sandbox claude -t picks it back up instead of starting another. A session that cannot answer is never reused.
32
+ */
33
+ export declare const findUnwatchedSession: (dataPath: string, harness: string, cwd: string) => Promise<SessionStatus | null>;
34
+ /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
35
+ export declare const attachTerminal: (session: SessionStatus) => Promise<number>;
36
+ /** Forgets status from a previous sandbox run so nothing stale is picked up. */
37
+ export declare const resetRunDir: (dataPath: string) => void;