windows-development-sandbox 1.4.0 → 1.4.1
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.
- package/.sandbox-config.example.json +2 -1
- package/config.example.json +1 -0
- package/dist/cli/boot-progress.d.ts +22 -0
- package/dist/cli/boot-progress.js +59 -0
- package/dist/cli/commands/launch.d.ts +5 -2
- package/dist/cli/commands/launch.js +77 -22
- package/dist/cli/commands/maintenance.js +36 -3
- package/dist/cli/commands/project.d.ts +10 -6
- package/dist/cli/commands/project.js +42 -3
- package/dist/cli/commands/restart.js +17 -7
- package/dist/cli/commands/sessions.js +2 -1
- package/dist/cli/manifest.js +1 -1
- package/dist/cli/progress.d.ts +15 -0
- package/dist/cli/progress.js +52 -0
- package/dist/core/config.js +1 -1
- package/dist/core/local-config.d.ts +16 -1
- package/dist/core/local-config.js +15 -9
- package/dist/core/project-policy.d.ts +50 -3
- package/dist/core/project-policy.js +90 -4
- package/dist/core/schema/config.d.ts +3 -2
- package/dist/core/schema/config.js +1 -1
- package/dist/core/schema/projects.d.ts +1 -0
- package/dist/core/schema/projects.js +1 -0
- package/dist/core/schema/sandbox.d.ts +1 -1
- package/dist/core/schema/sandbox.js +1 -1
- package/dist/core/types.d.ts +18 -1
- package/dist/core/workspace.d.ts +21 -4
- package/dist/core/workspace.js +87 -26
- package/dist/guest/env.d.ts +6 -2
- package/dist/guest/env.js +15 -3
- package/dist/guest/main.js +5 -1
- package/dist/guest/toolchains/index.js +29 -3
- package/dist/host/channel.d.ts +2 -2
- package/dist/host/channel.js +3 -2
- package/dist/host/generate.d.ts +1 -1
- package/dist/host/generate.js +13 -6
- package/dist/host/join.d.ts +7 -0
- package/dist/host/join.js +23 -9
- package/dist/host/launch/mappings.d.ts +7 -2
- package/dist/host/launch/mappings.js +64 -18
- package/dist/host/launch/projects.d.ts +2 -0
- package/dist/host/launch/projects.js +11 -5
- package/dist/host/projects.d.ts +25 -2
- package/dist/host/projects.js +54 -5
- package/dist/runtime/daemon.js +71 -9
- package/dist/runtime/db.js +33 -3
- package/dist/runtime/main.js +191 -30
- package/dist/runtime/netproxy.js +15 -3
- package/dist/runtime/view.js +262 -128
- package/dist/selftest/channel.test.js +38 -2
- package/dist/selftest/fixtures.d.ts +2 -1
- package/dist/selftest/index.js +2 -1
- package/dist/selftest/live-terminal.test.d.ts +2 -0
- package/dist/selftest/live-terminal.test.js +169 -0
- package/dist/selftest/local-config.test.js +28 -3
- package/dist/selftest/mirror.test.js +36 -7
- package/dist/selftest/projects.test.js +76 -7
- package/dist/shared/console-input.d.ts +11 -0
- package/dist/shared/console-input.js +61 -0
- package/dist/shared/download.d.ts +3 -0
- package/dist/shared/download.js +25 -2
- package/dist/shared/guest-paths.d.ts +27 -0
- package/dist/shared/guest-paths.js +68 -0
- package/dist/shared/projects.d.ts +27 -4
- package/dist/shared/projects.js +72 -13
- package/dist/shared/pty-client.d.ts +17 -0
- package/dist/shared/pty-client.js +65 -8
- package/dist/shared/schema.d.ts +24 -0
- package/dist/shared/schema.js +12 -0
- package/package.json +1 -1
- package/sandbox/ptyhost.cs +60 -3
- package/schemas/config.schema.json +15 -1
- package/schemas/sandbox-config.schema.json +4 -3
package/dist/runtime/main.js
CHANGED
|
@@ -5534,7 +5534,7 @@ var require_lodash = __commonJS({
|
|
|
5534
5534
|
// src/guest/main.ts
|
|
5535
5535
|
import fs36 from "node:fs";
|
|
5536
5536
|
import path50 from "node:path";
|
|
5537
|
-
import { spawn as
|
|
5537
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
5538
5538
|
|
|
5539
5539
|
// src/guest/guide.ts
|
|
5540
5540
|
import fs6 from "node:fs";
|
|
@@ -5694,6 +5694,10 @@ var checksumRequired = false;
|
|
|
5694
5694
|
var setChecksumRequired = (on) => {
|
|
5695
5695
|
checksumRequired = on;
|
|
5696
5696
|
};
|
|
5697
|
+
var downloadWatcher = null;
|
|
5698
|
+
var watchDownloads = (watcher) => {
|
|
5699
|
+
downloadWatcher = watcher;
|
|
5700
|
+
};
|
|
5697
5701
|
var download = async (url2, destination, expected = null) => {
|
|
5698
5702
|
const sums = typeof expected === "string" || expected === null ? { sha256: expected } : expected;
|
|
5699
5703
|
if (checksumRequired && !sums.sha256?.trim() && !sums.sha512?.trim()) {
|
|
@@ -5706,8 +5710,25 @@ var download = async (url2, destination, expected = null) => {
|
|
|
5706
5710
|
const want = sums[kind]?.trim().toLowerCase();
|
|
5707
5711
|
return want ? [{ want, hash: crypto.createHash(kind) }] : [];
|
|
5708
5712
|
});
|
|
5713
|
+
const size = Number(response.headers.get("content-length")) || 0;
|
|
5714
|
+
const report = size > 0 ? downloadWatcher?.() ?? null : null;
|
|
5715
|
+
let received = 0;
|
|
5716
|
+
let tenth = 0;
|
|
5717
|
+
let reportedAt = Date.now();
|
|
5709
5718
|
const tap = new Transform({ transform(chunk, _encoding, done) {
|
|
5710
5719
|
for (const c of checks) c.hash.update(chunk);
|
|
5720
|
+
if (report) {
|
|
5721
|
+
received += chunk.length;
|
|
5722
|
+
const now2 = Math.floor(received * 10 / size);
|
|
5723
|
+
if (now2 !== tenth || Date.now() - reportedAt > 5e3) {
|
|
5724
|
+
tenth = now2;
|
|
5725
|
+
reportedAt = Date.now();
|
|
5726
|
+
try {
|
|
5727
|
+
report(Math.min(1, received / size));
|
|
5728
|
+
} catch {
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
}
|
|
5711
5732
|
done(null, chunk);
|
|
5712
5733
|
} });
|
|
5713
5734
|
const partial2 = destination + ".part";
|
|
@@ -25603,7 +25624,7 @@ var ClaudeProfile = external_exports.strictObject({
|
|
|
25603
25624
|
var GuestWorkspace = external_exports.union([external_exports.literal("auto"), external_exports.literal("mirror"), GuestPath]).describe('Where the workspace appears inside the sandbox. "auto" = C:\\Workspace\\<folder name>. "mirror" = the same path as on this PC, so absolute paths in configs and build caches match (another drive letter such as F: is a subst drive onto C:\\Mirror\\F); paths the sandbox reserves (C:\\Users\\..., C:\\Program Files\\...) fall back to "auto".');
|
|
25604
25625
|
var Mapping = external_exports.strictObject({
|
|
25605
25626
|
host: external_exports.string().min(1).describe("Folder on this PC (relative paths are relative to the config file)."),
|
|
25606
|
-
guest:
|
|
25627
|
+
guest: external_exports.string().min(1).max(1024).optional().describe("Where it appears inside the sandbox, e.g. C:\\Assets, or on another drive letter (D: to Z:) such as F:\\SteamLibrary\\steamapps\\common\\Game (shown as F: at boot). Leave it out to use the same path as on this PC (a folder inside the project stays where it is in the project). A path the sandbox cannot use (C:\\Windows\\..., C:\\Users\\..., one already in use) goes to C:\\Mapped\\<drive>\\<path> instead, and the launch says so."),
|
|
25607
25628
|
readOnly: external_exports.boolean().describe("true = the agent can only read it."),
|
|
25608
25629
|
optional: external_exports.boolean().optional().describe("true = skip it quietly when the host folder does not exist.")
|
|
25609
25630
|
});
|
|
@@ -25891,6 +25912,7 @@ var Projects = external_exports.strictObject({
|
|
|
25891
25912
|
roots: external_exports.array(external_exports.string().min(1)).describe('Folders whose subfolders are your projects (e.g. "F:\\\\Code"), mapped read/write at every start. Any project inside one, including ones you create later, opens in the running sandbox at once, without a restart. Every agent in the sandbox can see all of them.'),
|
|
25892
25913
|
askToAddRoot: external_exports.boolean().describe("When you start a project whose parent folder is not a root yet, ask once whether to add it."),
|
|
25893
25914
|
declinedRoots: external_exports.array(external_exports.string().min(1)).describe("Parent folders you said no to (not asked again)."),
|
|
25915
|
+
allowOutsideMappings: external_exports.union([external_exports.literal("ask"), external_exports.boolean()]).describe(`A project's .sandbox-config.json may map folders from outside the project (e.g. a game folder on another drive). "ask" = an interactive launch asks once per project and set of folders, and remembers your answer; true = always allowed (still listed when you approve the file); false = never (the launch stops and says which folders). Only your own settings decide this, never a project file.`),
|
|
25894
25916
|
wsbShare: external_exports.boolean().describe("On Windows 11 24H2+ with the Windows Sandbox command line (wsb), add other folders to the running sandbox with `wsb share` (untested). Otherwise starting a project outside every root offers a restart.")
|
|
25895
25917
|
});
|
|
25896
25918
|
|
|
@@ -25933,7 +25955,7 @@ var LocalOverrides = external_exports.strictObject({
|
|
|
25933
25955
|
readOnly: external_exports.boolean().describe("Map this project read-only."),
|
|
25934
25956
|
// A project may turn the filter on and keep more out, never let ignored files in.
|
|
25935
25957
|
workspaceFilter: WorkspaceFilter.pick({ enabled: true, exclude: true }).partial(),
|
|
25936
|
-
mappings: external_exports.array(Mapping).describe("Extra folders
|
|
25958
|
+
mappings: external_exports.array(Mapping).describe("Extra folders (relative paths are relative to this file), e.g. a read-only data folder at another place in the sandbox. Folders outside the project are used only as your projects.allowOutsideMappings setting allows (by default you are asked once)."),
|
|
25937
25959
|
scripts: Strings.describe("Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."),
|
|
25938
25960
|
// This PC's resources are yours to share: CPU and paging file (this PC's disk) are not a project's to set.
|
|
25939
25961
|
sandbox: Sandbox.omit({ cpuLimit: true, pageFileMb: true }).partial(),
|
|
@@ -25964,6 +25986,15 @@ var AttachEndpoint = external_exports.strictObject({
|
|
|
25964
25986
|
port: external_exports.number().int().min(1024).max(65535),
|
|
25965
25987
|
token: Text(256)
|
|
25966
25988
|
});
|
|
25989
|
+
var BootProgress = external_exports.strictObject({
|
|
25990
|
+
step: external_exports.number().int().min(1).max(32),
|
|
25991
|
+
steps: external_exports.number().int().min(1).max(32),
|
|
25992
|
+
title: Text(120),
|
|
25993
|
+
/** Being set up now; percent while it downloads (when the size is known). */
|
|
25994
|
+
active: external_exports.array(external_exports.strictObject({ name: Text(64), percent: external_exports.number().int().min(0).max(100).optional() })).max(8),
|
|
25995
|
+
done: external_exports.number().int().min(0).max(1e3),
|
|
25996
|
+
total: external_exports.number().int().min(0).max(1e3)
|
|
25997
|
+
});
|
|
25967
25998
|
var SandboxStatus = external_exports.object({
|
|
25968
25999
|
launchId: Text(64),
|
|
25969
26000
|
phase: external_exports.enum(["booting", "provisioning", "ready", "failed"]),
|
|
@@ -25973,7 +26004,9 @@ var SandboxStatus = external_exports.object({
|
|
|
25973
26004
|
guestWorkspace: Text(),
|
|
25974
26005
|
headless: external_exports.boolean(),
|
|
25975
26006
|
/** The sandbox's IPv4 addresses (for network.forwardPorts; the host only uses ones on its Hyper-V network). */
|
|
25976
|
-
addresses: external_exports.array(external_exports.string().max(45)).max(8).optional()
|
|
26007
|
+
addresses: external_exports.array(external_exports.string().max(45)).max(8).optional(),
|
|
26008
|
+
/** A malformed progress is dropped, never the status (the phase still counts). */
|
|
26009
|
+
progress: BootProgress.optional().catch(void 0)
|
|
25977
26010
|
});
|
|
25978
26011
|
var Version = external_exports.strictObject({ spec: Text(128), source: Text(256) });
|
|
25979
26012
|
var DatabasePlan = external_exports.strictObject({
|
|
@@ -26136,16 +26169,25 @@ var mkdirp = (p) => {
|
|
|
26136
26169
|
return p;
|
|
26137
26170
|
};
|
|
26138
26171
|
var logPath = logFile;
|
|
26139
|
-
var
|
|
26172
|
+
var progress;
|
|
26173
|
+
var setSandboxStatus = (phase, message, update) => {
|
|
26140
26174
|
try {
|
|
26175
|
+
if (update) progress = fitProgress({ step: 1, steps: 1, title: "", active: [], done: 0, total: 0, ...progress, ...update });
|
|
26141
26176
|
writeJsonAtomicChecked(phase, message);
|
|
26142
26177
|
} catch (e) {
|
|
26143
26178
|
warn("Could not update sandbox status: " + errMsg(e));
|
|
26144
26179
|
}
|
|
26145
26180
|
};
|
|
26181
|
+
var fitProgress = (p) => ({
|
|
26182
|
+
...p,
|
|
26183
|
+
title: p.title.slice(0, 120),
|
|
26184
|
+
done: Math.min(p.done, 1e3),
|
|
26185
|
+
total: Math.min(p.total, 1e3),
|
|
26186
|
+
active: p.active.slice(0, 8).map((a) => ({ name: a.name.slice(0, 64), ...a.percent === void 0 ? {} : { percent: Math.max(0, Math.min(100, Math.round(a.percent))) } }))
|
|
26187
|
+
});
|
|
26146
26188
|
var ownAddresses = () => Object.values(os3.networkInterfaces()).flat().filter((i) => Boolean(i) && i.family === "IPv4" && !i.internal).map((i) => i.address).slice(0, 8);
|
|
26147
26189
|
var writeJsonAtomicChecked = (phase, message) => {
|
|
26148
|
-
writeJsonAtomic(run.sandbox, { launchId: session.launchId, phase, message, updatedAt: now(), workspaceHost: session.workspaceHost, guestWorkspace: workspace, headless: session.headless, addresses: ownAddresses() });
|
|
26190
|
+
writeJsonAtomic(run.sandbox, { launchId: session.launchId, phase, message, updatedAt: now(), workspaceHost: session.workspaceHost, guestWorkspace: workspace, headless: session.headless, addresses: ownAddresses(), ...progress ? { progress } : {} });
|
|
26149
26191
|
};
|
|
26150
26192
|
var exec = (file3, args, options = {}) => {
|
|
26151
26193
|
const result = spawnSync(file3, args, { stdio: "inherit", windowsHide: true, ...options });
|
|
@@ -26160,7 +26202,7 @@ var execOutput = (file3, args, options = {}) => {
|
|
|
26160
26202
|
const result = spawnSync(file3, args, { encoding: "utf8", windowsHide: true, ...options });
|
|
26161
26203
|
return result.status === 0 ? String(result.stdout ?? "").trim() : "";
|
|
26162
26204
|
};
|
|
26163
|
-
var powershell = (
|
|
26205
|
+
var powershell = (script2) => exec(SYSTEM.powershell(), ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script2]);
|
|
26164
26206
|
var robocopy = (from, to, files = []) => {
|
|
26165
26207
|
if (!isWin) {
|
|
26166
26208
|
if (!files.length) {
|
|
@@ -26239,6 +26281,9 @@ var removeGuide = (file3) => {
|
|
|
26239
26281
|
if (start >= 0 && end > start) fs6.writeFileSync(file3, (current.slice(0, start) + current.slice(end + END.length)).trim() + "\n", "utf8");
|
|
26240
26282
|
};
|
|
26241
26283
|
|
|
26284
|
+
// src/guest/toolchains/index.ts
|
|
26285
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
26286
|
+
|
|
26242
26287
|
// src/guest/toolchains/common.ts
|
|
26243
26288
|
import fs7 from "node:fs";
|
|
26244
26289
|
import path8 from "node:path";
|
|
@@ -26846,8 +26891,8 @@ var setupDynamo = async (env, plan2) => {
|
|
|
26846
26891
|
env.summary.push("dynamodb (DynamoDB Local): " + endpoint + " (AWS SDKs use it automatically via AWS_ENDPOINT_URL_DYNAMODB; dummy credentials)");
|
|
26847
26892
|
if (!plan2.adminPort) return;
|
|
26848
26893
|
const prefix = tool("dynamodb-admin");
|
|
26849
|
-
const
|
|
26850
|
-
if (!exists(
|
|
26894
|
+
const script2 = path13.join(prefix, "node_modules", "dynamodb-admin", "dist", "dynamodb-admin.js");
|
|
26895
|
+
if (!exists(script2)) {
|
|
26851
26896
|
log("Installing dynamodb-admin, the DynamoDB web UI (~20 MB once) ...");
|
|
26852
26897
|
const npmCli = path13.join(path13.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
|
|
26853
26898
|
await execAsync(process.execPath, [npmCli, "install", "--global", "--prefix", prefix, "dynamodb-admin", "--no-fund", "--no-audit"], { stdio: "ignore" });
|
|
@@ -26857,7 +26902,7 @@ var setupDynamo = async (env, plan2) => {
|
|
|
26857
26902
|
return;
|
|
26858
26903
|
}
|
|
26859
26904
|
const out = fs12.openSync(path13.join(logsDir, "dynamodb-admin.log"), "a");
|
|
26860
|
-
spawn3(process.execPath, [
|
|
26905
|
+
spawn3(process.execPath, [script2, "--port", String(plan2.adminPort), "--dynamo-endpoint", endpoint, "--skip-default-credentials"], { detached: true, stdio: ["ignore", out, out], windowsHide: true, env: { ...process.env, ...aws } }).unref();
|
|
26861
26906
|
fs12.closeSync(out);
|
|
26862
26907
|
if (!await waitForPort(plan2.adminPort, 30)) throw new Error("dynamodb-admin did not start; see " + path13.join(logsDir, "dynamodb-admin.log"));
|
|
26863
26908
|
env.summary.push("dynamodb-admin: http://localhost:" + plan2.adminPort + " (browse and edit tables in the sandbox's browser)");
|
|
@@ -27315,7 +27360,7 @@ var DotnetMarker = external_exports.record(external_exports.string(), external_e
|
|
|
27315
27360
|
var setupDotnet = async (env, config2, refresh) => {
|
|
27316
27361
|
const root = tool("dotnet");
|
|
27317
27362
|
const marker = path24.join(root, ".agent-sandbox-installed.json");
|
|
27318
|
-
const
|
|
27363
|
+
const script2 = tool("dotnet-install.ps1");
|
|
27319
27364
|
if (dryRun) {
|
|
27320
27365
|
log("(dry run) would provision .NET SDK channels " + config2.channels.join(", "));
|
|
27321
27366
|
return;
|
|
@@ -27328,9 +27373,9 @@ var setupDotnet = async (env, config2, refresh) => {
|
|
|
27328
27373
|
...exists(globalJson) ? [["global.json:" + fs17.readFileSync(globalJson, "utf8").replace(/\s+/g, ""), ["-JSonFile", globalJson]]] : []
|
|
27329
27374
|
];
|
|
27330
27375
|
for (const [key, args] of installs.filter(([k]) => !done[k])) {
|
|
27331
|
-
await downloadFile("https://dot.net/v1/dotnet-install.ps1",
|
|
27376
|
+
await downloadFile("https://dot.net/v1/dotnet-install.ps1", script2);
|
|
27332
27377
|
log("Installing the .NET SDK " + (key.startsWith("channel:") ? key.slice(8) : "pinned in global.json") + " (~250 MB once) ...");
|
|
27333
|
-
await execAsync(SYSTEM.powershell(), ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File",
|
|
27378
|
+
await execAsync(SYSTEM.powershell(), ["-NoLogo", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script2, ...args, "-InstallDir", root, "-NoPath"]);
|
|
27334
27379
|
done[key] = true;
|
|
27335
27380
|
writeJsonAtomic(marker, done);
|
|
27336
27381
|
}
|
|
@@ -27881,8 +27926,8 @@ var shimPair = (folder, version2, dir) => {
|
|
|
27881
27926
|
shim(folder, "pip" + version2, python + " -m pip");
|
|
27882
27927
|
};
|
|
27883
27928
|
var pyLauncher = (folder, py3, py2) => {
|
|
27884
|
-
const
|
|
27885
|
-
fs24.writeFileSync(
|
|
27929
|
+
const script2 = path33.join(mkdirp(folder), "py-launcher.py");
|
|
27930
|
+
fs24.writeFileSync(script2, [
|
|
27886
27931
|
"import subprocess, sys",
|
|
27887
27932
|
"PY3, PY2 = " + JSON.stringify(path33.join(py3, "python.exe")) + ", " + JSON.stringify(py2 ? path33.join(py2, "python.exe") : null),
|
|
27888
27933
|
"args = sys.argv[1:]",
|
|
@@ -27895,7 +27940,7 @@ var pyLauncher = (folder, py3, py2) => {
|
|
|
27895
27940
|
"sys.exit(subprocess.call([exe] + args))",
|
|
27896
27941
|
""
|
|
27897
27942
|
].join("\n"));
|
|
27898
|
-
shim(folder, "py", '"' + path33.join(py3, "python.exe") + '" "' +
|
|
27943
|
+
shim(folder, "py", '"' + path33.join(py3, "python.exe") + '" "' + script2 + '"');
|
|
27899
27944
|
};
|
|
27900
27945
|
var setupPython = async (env, config2, versions) => {
|
|
27901
27946
|
if (dryRun) {
|
|
@@ -29706,6 +29751,7 @@ var plan = (config2, versions, o) => {
|
|
|
29706
29751
|
|
|
29707
29752
|
// src/guest/toolchains/index.ts
|
|
29708
29753
|
var PARALLEL2 = 4;
|
|
29754
|
+
var currentStep = new AsyncLocalStorage2();
|
|
29709
29755
|
var locks = /* @__PURE__ */ new Map();
|
|
29710
29756
|
var lockKey = (name) => name.split(" ")[0].toLowerCase();
|
|
29711
29757
|
var exclusive = async (name, work) => {
|
|
@@ -29731,10 +29777,19 @@ var runSteps = async (steps, o) => {
|
|
|
29731
29777
|
const running = /* @__PURE__ */ new Set();
|
|
29732
29778
|
const waiting = [];
|
|
29733
29779
|
let done = 0;
|
|
29734
|
-
const
|
|
29780
|
+
const percent = /* @__PURE__ */ new Map();
|
|
29781
|
+
const active = () => [...running].map((name) => ({ name, ...percent.has(name) ? { percent: percent.get(name) } : {} }));
|
|
29782
|
+
const say = o.report ?? ((message) => setSandboxStatus("provisioning", message, { active: active(), done, total: steps.length }));
|
|
29735
29783
|
const report = () => {
|
|
29736
29784
|
if (running.size) say("Installing " + [...running].join(", ") + " (" + done + " of " + steps.length + " done)");
|
|
29737
29785
|
};
|
|
29786
|
+
if (!o.report) watchDownloads(() => {
|
|
29787
|
+
const name = currentStep.getStore();
|
|
29788
|
+
return name ? (fraction) => {
|
|
29789
|
+
percent.set(name, Math.round(fraction * 100));
|
|
29790
|
+
report();
|
|
29791
|
+
} : null;
|
|
29792
|
+
});
|
|
29738
29793
|
for (const s of steps) {
|
|
29739
29794
|
finished.set(s.name, (async () => {
|
|
29740
29795
|
await Promise.all((s.after ?? []).map((name) => finished.get(name)));
|
|
@@ -29748,16 +29803,22 @@ var runSteps = async (steps, o) => {
|
|
|
29748
29803
|
running.add(s.name);
|
|
29749
29804
|
report();
|
|
29750
29805
|
try {
|
|
29751
|
-
await exclusive(s.name, () => step(s.name, envs.get(s.name), async () => s.run(envs.get(s.name))));
|
|
29806
|
+
await currentStep.run(s.name, () => exclusive(s.name, () => step(s.name, envs.get(s.name), async () => s.run(envs.get(s.name)))));
|
|
29752
29807
|
} finally {
|
|
29753
29808
|
running.delete(s.name);
|
|
29809
|
+
percent.delete(s.name);
|
|
29754
29810
|
done++;
|
|
29755
29811
|
report();
|
|
29756
29812
|
waiting.shift()?.();
|
|
29757
29813
|
}
|
|
29758
29814
|
})());
|
|
29759
29815
|
}
|
|
29760
|
-
|
|
29816
|
+
try {
|
|
29817
|
+
await Promise.all(finished.values());
|
|
29818
|
+
} finally {
|
|
29819
|
+
if (!o.report) watchDownloads(null);
|
|
29820
|
+
}
|
|
29821
|
+
if (!o.report) setSandboxStatus("provisioning", "Set up " + steps.length + " toolchains and databases", { active: [], done, total: steps.length });
|
|
29761
29822
|
return envs;
|
|
29762
29823
|
};
|
|
29763
29824
|
var setupToolchains = async (config2, versions, options, run3 = {}) => {
|
|
@@ -29976,13 +30037,13 @@ var agentEnv = (tools, target = { workspace, project: session.project, base: pro
|
|
|
29976
30037
|
var persistEnvironment = (env, toolVars2) => {
|
|
29977
30038
|
if (!isWin || dryRun || testMode) return;
|
|
29978
30039
|
const vars = Object.fromEntries([...toolVars2, ...AGENT_VARS].filter((n) => env[n] !== void 0).map((n) => [n, env[n]]));
|
|
29979
|
-
const
|
|
30040
|
+
const script2 = [
|
|
29980
30041
|
"$vars = '" + JSON.stringify(vars).replace(/'/g, "''") + "' | ConvertFrom-Json",
|
|
29981
30042
|
"foreach ($p in $vars.PSObject.Properties) { [Environment]::SetEnvironmentVariable($p.Name, [string]$p.Value, 'Machine') }",
|
|
29982
30043
|
"[Environment]::SetEnvironmentVariable('Path', '" + (env.PATH ?? "").replace(/'/g, "''") + "', 'Machine')"
|
|
29983
30044
|
].join("\n");
|
|
29984
30045
|
try {
|
|
29985
|
-
powershell(
|
|
30046
|
+
powershell(script2);
|
|
29986
30047
|
} catch (e) {
|
|
29987
30048
|
warn("Could not persist the environment machine-wide: " + errMsg(e));
|
|
29988
30049
|
}
|
|
@@ -30441,8 +30502,65 @@ var enableProcessAudit = () => {
|
|
|
30441
30502
|
|
|
30442
30503
|
// src/shared/pty-client.ts
|
|
30443
30504
|
import net2 from "node:net";
|
|
30505
|
+
|
|
30506
|
+
// src/shared/console-input.ts
|
|
30507
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
30508
|
+
var script = (pid) => [
|
|
30509
|
+
'Add-Type -TypeDefinition @"',
|
|
30510
|
+
"using System; using System.Runtime.InteropServices;",
|
|
30511
|
+
"public static class SbxVtInput {",
|
|
30512
|
+
' [DllImport("kernel32.dll")] static extern bool FreeConsole();',
|
|
30513
|
+
' [DllImport("kernel32.dll")] static extern bool AttachConsole(uint pid);',
|
|
30514
|
+
' [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr CreateFileW(string name, uint access, uint share, IntPtr security, uint creation, uint flags, IntPtr template);',
|
|
30515
|
+
' [DllImport("kernel32.dll")] static extern bool GetConsoleMode(IntPtr handle, out uint mode);',
|
|
30516
|
+
' [DllImport("kernel32.dll")] static extern bool SetConsoleMode(IntPtr handle, uint mode);',
|
|
30517
|
+
' [DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);',
|
|
30518
|
+
" public static int On(uint pid) {",
|
|
30519
|
+
" FreeConsole();",
|
|
30520
|
+
" if (!AttachConsole(pid)) return 2;",
|
|
30521
|
+
' IntPtr input = CreateFileW("CONIN$", 0xC0000000, 3, IntPtr.Zero, 3, 0, IntPtr.Zero);',
|
|
30522
|
+
" if (input == new IntPtr(-1)) return 3;",
|
|
30523
|
+
" uint mode; bool ok = GetConsoleMode(input, out mode) && SetConsoleMode(input, mode | 0x200);",
|
|
30524
|
+
" CloseHandle(input);",
|
|
30525
|
+
" FreeConsole();",
|
|
30526
|
+
" return ok ? 0 : 4;",
|
|
30527
|
+
" }",
|
|
30528
|
+
"}",
|
|
30529
|
+
'"@',
|
|
30530
|
+
"exit [SbxVtInput]::On(" + pid + ")"
|
|
30531
|
+
].join("\n");
|
|
30532
|
+
var enableVtInput = () => {
|
|
30533
|
+
if (process.platform !== "win32" || !process.stdin.isTTY) return { ready: Promise.resolve(false), stop: () => Promise.resolve() };
|
|
30534
|
+
let child = null;
|
|
30535
|
+
let exited = Promise.resolve();
|
|
30536
|
+
const ready = new Promise((resolve2) => {
|
|
30537
|
+
try {
|
|
30538
|
+
child = spawn7(SYSTEM.powershell(), ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(script(process.pid), "utf16le").toString("base64")], { stdio: "ignore", windowsHide: true });
|
|
30539
|
+
} catch {
|
|
30540
|
+
resolve2(false);
|
|
30541
|
+
return;
|
|
30542
|
+
}
|
|
30543
|
+
exited = new Promise((done) => {
|
|
30544
|
+
child?.on("exit", (code) => {
|
|
30545
|
+
resolve2(code === 0);
|
|
30546
|
+
done();
|
|
30547
|
+
});
|
|
30548
|
+
child?.on("error", () => {
|
|
30549
|
+
resolve2(false);
|
|
30550
|
+
done();
|
|
30551
|
+
});
|
|
30552
|
+
});
|
|
30553
|
+
});
|
|
30554
|
+
return { ready, stop: () => {
|
|
30555
|
+
if (child && child.exitCode === null && child.signalCode === null) child.kill();
|
|
30556
|
+
return exited;
|
|
30557
|
+
} };
|
|
30558
|
+
};
|
|
30559
|
+
|
|
30560
|
+
// src/shared/pty-client.ts
|
|
30444
30561
|
var FrameType = { Hello: 1, Input: 2, Resize: 3, Restart: 4, Ping: 5, Output: 16, Exit: 17, Error: 18, Restarting: 19, Status: 20, Console: 21 };
|
|
30445
30562
|
var PING_INTERVAL_MS = 5e3;
|
|
30563
|
+
var MOUSE_OFF = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1005l\x1B[?1006l\x1B[?1015l";
|
|
30446
30564
|
var encodeFrame = (type, payload = Buffer.alloc(0)) => {
|
|
30447
30565
|
const header = Buffer.alloc(5);
|
|
30448
30566
|
header.writeUInt8(type, 0);
|
|
@@ -30618,6 +30736,34 @@ var fixWindowsKeys = (chunk) => {
|
|
|
30618
30736
|
if (!chunk.includes(8) && !chunk.includes(127)) return chunk;
|
|
30619
30737
|
return Buffer.from(chunk.map((b) => b === 8 ? 127 : b === 127 ? 8 : b));
|
|
30620
30738
|
};
|
|
30739
|
+
var JOIN_MS = 30;
|
|
30740
|
+
var MAX_HELD = 64;
|
|
30741
|
+
var InputJoiner = class {
|
|
30742
|
+
held = Buffer.alloc(0);
|
|
30743
|
+
/** The bytes to send now; an unfinished sequence at the end is kept for the next chunk. */
|
|
30744
|
+
push(chunk) {
|
|
30745
|
+
const data = this.held.length ? Buffer.concat([this.held, chunk]) : chunk;
|
|
30746
|
+
const cut = unfinishedSequence(data);
|
|
30747
|
+
this.held = Buffer.from(data.subarray(cut));
|
|
30748
|
+
return data.subarray(0, cut);
|
|
30749
|
+
}
|
|
30750
|
+
holding() {
|
|
30751
|
+
return this.held.length > 0;
|
|
30752
|
+
}
|
|
30753
|
+
flush() {
|
|
30754
|
+
const out = this.held;
|
|
30755
|
+
this.held = Buffer.alloc(0);
|
|
30756
|
+
return out;
|
|
30757
|
+
}
|
|
30758
|
+
};
|
|
30759
|
+
var unfinishedSequence = (data) => {
|
|
30760
|
+
const start = data.lastIndexOf(27, -1);
|
|
30761
|
+
if (start === -1 || data.length - start > MAX_HELD) return data.length;
|
|
30762
|
+
const rest = data.subarray(start + 1);
|
|
30763
|
+
if (rest.length === 0) return start;
|
|
30764
|
+
if (rest[0] === 91 || rest[0] === 79) return rest.subarray(1).every((b) => b >= 32 && b <= 63) && (rest[0] === 91 || rest.length === 1) ? start : data.length;
|
|
30765
|
+
return data.length;
|
|
30766
|
+
};
|
|
30621
30767
|
var runTerminalClient = (socket, token, options = {}) => {
|
|
30622
30768
|
const stdin = process.stdin;
|
|
30623
30769
|
const stdout = process.stdout;
|
|
@@ -30625,13 +30771,14 @@ var runTerminalClient = (socket, token, options = {}) => {
|
|
|
30625
30771
|
const [c, r] = stdout.isTTY ? stdout.getWindowSize() : [0, 0];
|
|
30626
30772
|
return [c || 120, r || 30];
|
|
30627
30773
|
};
|
|
30628
|
-
|
|
30774
|
+
let swapKeys = process.platform === "win32" && stdin.isTTY;
|
|
30629
30775
|
return new Promise((resolve2) => {
|
|
30630
30776
|
let done = false;
|
|
30631
30777
|
const reader = new FrameReader();
|
|
30632
30778
|
const filter = new OutputFilter();
|
|
30633
30779
|
let sent = size();
|
|
30634
30780
|
let resizeTimer;
|
|
30781
|
+
let vtInput;
|
|
30635
30782
|
const finish = (result) => {
|
|
30636
30783
|
if (done) return;
|
|
30637
30784
|
done = true;
|
|
@@ -30640,18 +30787,28 @@ var runTerminalClient = (socket, token, options = {}) => {
|
|
|
30640
30787
|
clearTimeout(resizeTimer);
|
|
30641
30788
|
clearInterval(heartbeat);
|
|
30642
30789
|
clearInterval(sizeWatch);
|
|
30643
|
-
|
|
30644
|
-
stdin.pause();
|
|
30790
|
+
clearTimeout(joinTimer);
|
|
30645
30791
|
socket.destroy();
|
|
30646
|
-
stdout.write("\x1B[0m\x1B[?25h\r\n");
|
|
30647
|
-
|
|
30792
|
+
stdout.write("\x1B[0m\x1B[?25h" + MOUSE_OFF + "\r\n");
|
|
30793
|
+
void (vtInput?.stop() ?? Promise.resolve()).then(() => {
|
|
30794
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
30795
|
+
stdin.pause();
|
|
30796
|
+
resolve2(result);
|
|
30797
|
+
});
|
|
30798
|
+
};
|
|
30799
|
+
const joiner = new InputJoiner();
|
|
30800
|
+
let joinTimer;
|
|
30801
|
+
const sendInput = (data) => {
|
|
30802
|
+
if (data.length && !done) socket.write(encodeFrame(FrameType.Input, swapKeys ? fixWindowsKeys(data) : data));
|
|
30648
30803
|
};
|
|
30649
30804
|
const onInput = (chunk) => {
|
|
30650
30805
|
if (options.detachKey !== void 0 && chunk.includes(options.detachKey)) {
|
|
30651
30806
|
finish({ reason: "detach" });
|
|
30652
30807
|
return;
|
|
30653
30808
|
}
|
|
30654
|
-
|
|
30809
|
+
clearTimeout(joinTimer);
|
|
30810
|
+
sendInput(joiner.push(chunk));
|
|
30811
|
+
if (joiner.holding()) joinTimer = setTimeout(() => sendInput(joiner.flush()), JOIN_MS);
|
|
30655
30812
|
};
|
|
30656
30813
|
let passthrough = false;
|
|
30657
30814
|
const sendSize = () => {
|
|
@@ -30683,7 +30840,7 @@ var runTerminalClient = (socket, token, options = {}) => {
|
|
|
30683
30840
|
for (const frame of frames) {
|
|
30684
30841
|
if (frame.type === FrameType.Output) stdout.write(filter.push(frame.payload));
|
|
30685
30842
|
else if (frame.type === FrameType.Console) passthrough = frame.payload.toString("latin1") === "conpty.dll";
|
|
30686
|
-
else if (frame.type === FrameType.Restarting) stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
30843
|
+
else if (frame.type === FrameType.Restarting) stdout.write(MOUSE_OFF + "\x1B[2J\x1B[3J\x1B[H");
|
|
30687
30844
|
else if (frame.type === FrameType.Exit) finish({ reason: "exit", exitCode: frame.payload.length >= 4 ? frame.payload.readInt32LE(0) : 1 });
|
|
30688
30845
|
else if (frame.type === FrameType.Error) finish({ reason: "error", message: frame.payload.toString("utf8").slice(0, 500).replace(/[\x00-\x1f\x7f]/g, " ") });
|
|
30689
30846
|
}
|
|
@@ -30694,6 +30851,10 @@ var runTerminalClient = (socket, token, options = {}) => {
|
|
|
30694
30851
|
stdout.write("\x1B[2J\x1B[3J\x1B[H");
|
|
30695
30852
|
socket.write(helloFrame(token, c, r));
|
|
30696
30853
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
30854
|
+
vtInput = enableVtInput();
|
|
30855
|
+
void vtInput.ready.then((on) => {
|
|
30856
|
+
if (on) swapKeys = false;
|
|
30857
|
+
});
|
|
30697
30858
|
stdin.on("data", onInput);
|
|
30698
30859
|
stdin.resume();
|
|
30699
30860
|
stdout.on("resize", onResize);
|
|
@@ -30794,7 +30955,7 @@ var saveProjectStatus = (name, workspace2, phase, message, databases, summary) =
|
|
|
30794
30955
|
var STEPS = 8;
|
|
30795
30956
|
var step2 = (n, title) => {
|
|
30796
30957
|
log("\n\x1B[36m[" + n + "/" + STEPS + "] " + title + "\x1B[0m");
|
|
30797
|
-
setSandboxStatus(n === 1 ? "booting" : "provisioning", "[" + n + "/" + STEPS + "] " + title);
|
|
30958
|
+
setSandboxStatus(n === 1 ? "booting" : "provisioning", "[" + n + "/" + STEPS + "] " + title, { step: n, steps: STEPS, title: title.replace(/ \(.*\)$/, ""), active: [], done: 0, total: 0 });
|
|
30798
30959
|
};
|
|
30799
30960
|
var soft = async (what, work) => {
|
|
30800
30961
|
try {
|
|
@@ -30804,7 +30965,7 @@ var soft = async (what, work) => {
|
|
|
30804
30965
|
}
|
|
30805
30966
|
};
|
|
30806
30967
|
var startDaemon = (env) => {
|
|
30807
|
-
|
|
30968
|
+
spawn8(process.execPath, [path50.join(path50.dirname(process.argv[1]), "daemon.js")], { detached: true, stdio: "ignore", windowsHide: true, env: { ...env, AGENT_LOG_PREFIX: "daemon" } }).unref();
|
|
30808
30969
|
};
|
|
30809
30970
|
var writeDbCommand = () => {
|
|
30810
30971
|
const dir = mkdirp(path50.join(localDir, "bin"));
|
package/dist/runtime/netproxy.js
CHANGED
|
@@ -25404,7 +25404,7 @@ var ClaudeProfile = external_exports.strictObject({
|
|
|
25404
25404
|
var GuestWorkspace = external_exports.union([external_exports.literal("auto"), external_exports.literal("mirror"), GuestPath]).describe('Where the workspace appears inside the sandbox. "auto" = C:\\Workspace\\<folder name>. "mirror" = the same path as on this PC, so absolute paths in configs and build caches match (another drive letter such as F: is a subst drive onto C:\\Mirror\\F); paths the sandbox reserves (C:\\Users\\..., C:\\Program Files\\...) fall back to "auto".');
|
|
25405
25405
|
var Mapping = external_exports.strictObject({
|
|
25406
25406
|
host: external_exports.string().min(1).describe("Folder on this PC (relative paths are relative to the config file)."),
|
|
25407
|
-
guest:
|
|
25407
|
+
guest: external_exports.string().min(1).max(1024).optional().describe("Where it appears inside the sandbox, e.g. C:\\Assets, or on another drive letter (D: to Z:) such as F:\\SteamLibrary\\steamapps\\common\\Game (shown as F: at boot). Leave it out to use the same path as on this PC (a folder inside the project stays where it is in the project). A path the sandbox cannot use (C:\\Windows\\..., C:\\Users\\..., one already in use) goes to C:\\Mapped\\<drive>\\<path> instead, and the launch says so."),
|
|
25408
25408
|
readOnly: external_exports.boolean().describe("true = the agent can only read it."),
|
|
25409
25409
|
optional: external_exports.boolean().optional().describe("true = skip it quietly when the host folder does not exist.")
|
|
25410
25410
|
});
|
|
@@ -25661,6 +25661,7 @@ var Projects = external_exports.strictObject({
|
|
|
25661
25661
|
roots: external_exports.array(external_exports.string().min(1)).describe('Folders whose subfolders are your projects (e.g. "F:\\\\Code"), mapped read/write at every start. Any project inside one, including ones you create later, opens in the running sandbox at once, without a restart. Every agent in the sandbox can see all of them.'),
|
|
25662
25662
|
askToAddRoot: external_exports.boolean().describe("When you start a project whose parent folder is not a root yet, ask once whether to add it."),
|
|
25663
25663
|
declinedRoots: external_exports.array(external_exports.string().min(1)).describe("Parent folders you said no to (not asked again)."),
|
|
25664
|
+
allowOutsideMappings: external_exports.union([external_exports.literal("ask"), external_exports.boolean()]).describe(`A project's .sandbox-config.json may map folders from outside the project (e.g. a game folder on another drive). "ask" = an interactive launch asks once per project and set of folders, and remembers your answer; true = always allowed (still listed when you approve the file); false = never (the launch stops and says which folders). Only your own settings decide this, never a project file.`),
|
|
25664
25665
|
wsbShare: external_exports.boolean().describe("On Windows 11 24H2+ with the Windows Sandbox command line (wsb), add other folders to the running sandbox with `wsb share` (untested). Otherwise starting a project outside every root offers a restart.")
|
|
25665
25666
|
});
|
|
25666
25667
|
|
|
@@ -25703,7 +25704,7 @@ var LocalOverrides = external_exports.strictObject({
|
|
|
25703
25704
|
readOnly: external_exports.boolean().describe("Map this project read-only."),
|
|
25704
25705
|
// A project may turn the filter on and keep more out, never let ignored files in.
|
|
25705
25706
|
workspaceFilter: WorkspaceFilter.pick({ enabled: true, exclude: true }).partial(),
|
|
25706
|
-
mappings: external_exports.array(Mapping).describe("Extra folders
|
|
25707
|
+
mappings: external_exports.array(Mapping).describe("Extra folders (relative paths are relative to this file), e.g. a read-only data folder at another place in the sandbox. Folders outside the project are used only as your projects.allowOutsideMappings setting allows (by default you are asked once)."),
|
|
25707
25708
|
scripts: Strings.describe("Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."),
|
|
25708
25709
|
// This PC's resources are yours to share: CPU and paging file (this PC's disk) are not a project's to set.
|
|
25709
25710
|
sandbox: Sandbox.omit({ cpuLimit: true, pageFileMb: true }).partial(),
|
|
@@ -25734,6 +25735,15 @@ var AttachEndpoint = external_exports.strictObject({
|
|
|
25734
25735
|
port: external_exports.number().int().min(1024).max(65535),
|
|
25735
25736
|
token: Text(256)
|
|
25736
25737
|
});
|
|
25738
|
+
var BootProgress = external_exports.strictObject({
|
|
25739
|
+
step: external_exports.number().int().min(1).max(32),
|
|
25740
|
+
steps: external_exports.number().int().min(1).max(32),
|
|
25741
|
+
title: Text(120),
|
|
25742
|
+
/** Being set up now; percent while it downloads (when the size is known). */
|
|
25743
|
+
active: external_exports.array(external_exports.strictObject({ name: Text(64), percent: external_exports.number().int().min(0).max(100).optional() })).max(8),
|
|
25744
|
+
done: external_exports.number().int().min(0).max(1e3),
|
|
25745
|
+
total: external_exports.number().int().min(0).max(1e3)
|
|
25746
|
+
});
|
|
25737
25747
|
var SandboxStatus = external_exports.object({
|
|
25738
25748
|
launchId: Text(64),
|
|
25739
25749
|
phase: external_exports.enum(["booting", "provisioning", "ready", "failed"]),
|
|
@@ -25743,7 +25753,9 @@ var SandboxStatus = external_exports.object({
|
|
|
25743
25753
|
guestWorkspace: Text(),
|
|
25744
25754
|
headless: external_exports.boolean(),
|
|
25745
25755
|
/** The sandbox's IPv4 addresses (for network.forwardPorts; the host only uses ones on its Hyper-V network). */
|
|
25746
|
-
addresses: external_exports.array(external_exports.string().max(45)).max(8).optional()
|
|
25756
|
+
addresses: external_exports.array(external_exports.string().max(45)).max(8).optional(),
|
|
25757
|
+
/** A malformed progress is dropped, never the status (the phase still counts). */
|
|
25758
|
+
progress: BootProgress.optional().catch(void 0)
|
|
25747
25759
|
});
|
|
25748
25760
|
var Version = external_exports.strictObject({ spec: Text(128), source: Text(256) });
|
|
25749
25761
|
var DatabasePlan = external_exports.strictObject({
|