windows-development-sandbox 1.13.0 → 1.14.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.
- package/dist/app/features/accounts/accounts.def.d.ts +1 -0
- package/dist/app/features/accounts/accounts.def.js +12 -3
- package/dist/app/features/accounts/accounts.run.d.ts +1 -0
- package/dist/app/features/accounts/accounts.run.js +20 -11
- package/dist/app/features/defs.d.ts +1 -0
- package/dist/app/features/settings/settings-file.d.ts +7 -0
- package/dist/app/features/settings/settings-file.js +19 -2
- package/dist/app/features/settings/settings-rows.js +9 -1
- package/dist/app/features/storage/storage.run.js +1 -1
- package/dist/bin/agent-sandbox.js +7 -1
- package/dist/bin/chunk-BQ23GBLC.js +78790 -0
- package/dist/bin/chunk-F7IMME5G.js +10432 -0
- package/dist/bin/chunk-LQHEUN2A.js +7 -0
- package/dist/bin/claude-host.js +10 -4
- package/dist/bin/claude-sbx.js +8 -2
- package/dist/bin/codex-sbx.js +8 -2
- package/dist/bin/gemini-sbx.js +8 -2
- package/dist/bin/sandbox.js +7 -2
- package/dist/bin/sbx-hide.js +8 -2
- package/dist/bin/sbx-show.js +8 -2
- package/dist/cli/commands/launch/launch.js +1 -1
- package/dist/cli/commands/launch/prepare.js +24 -7
- package/dist/cli/commands/launch/running.js +4 -0
- package/dist/cli/commands/repair/checks/contained.js +1 -1
- package/dist/cli/commands/storage/offer.js +1 -1
- package/dist/cli/menu/live.d.ts +29 -0
- package/dist/cli/menu/live.js +92 -21
- package/dist/cli/menu/menu.d.ts +2 -1
- package/dist/cli/menu/menu.js +16 -2
- package/dist/cli/menu/pages/list-page.d.ts +5 -0
- package/dist/cli/menu/pages/list-page.js +19 -0
- package/dist/cli/menu/shell.d.ts +2 -0
- package/dist/cli/menu/shell.js +1 -1
- package/dist/cli/services/accounts-service.d.ts +7 -0
- package/dist/cli/services/accounts-service.js +20 -0
- package/dist/cli/tui/app.d.ts +4 -0
- package/dist/cli/tui/app.js +5 -0
- package/dist/cli/tui/terminal.d.ts +6 -0
- package/dist/cli/tui/terminal.js +8 -1
- package/dist/cli/validate.js +8 -4
- package/dist/core/package-info.js +1 -1
- package/dist/guest/daemon/sessions.d.ts +7 -0
- package/dist/guest/daemon/sessions.js +23 -2
- package/dist/guest/entries/daemon.js +1 -0
- package/dist/guest/sessions/pty-host.d.ts +10 -0
- package/dist/guest/sessions/pty-host.js +48 -3
- package/dist/host/channel/channel.js +1 -1
- package/dist/host/install/install-record.d.ts +1 -0
- package/dist/host/install/install-record.js +2 -0
- package/dist/host/sandbox/contained-deps.d.ts +2 -1
- package/dist/host/sandbox/contained-deps.js +6 -4
- package/dist/host/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +19 -4
- package/dist/host/sharing/lifecycle.js +9 -1
- package/dist/host/storage/contained/contained-storage.js +13 -0
- package/dist/host/storage/contained/disk-port.d.ts +5 -0
- package/dist/host/storage/contained/helper-port.d.ts +72 -0
- package/dist/host/storage/contained/helper-port.js +162 -0
- package/dist/host/storage/contained/index.d.ts +1 -0
- package/dist/host/storage/contained/index.js +1 -0
- package/dist/host/storage/contained/state.d.ts +3 -2
- package/dist/host/storage/contained/state.js +5 -2
- package/dist/host/web/bridge.d.ts +5 -0
- package/dist/host/web/bridge.js +11 -1
- package/dist/host/web/server.d.ts +2 -0
- package/dist/host/web/server.js +11 -1
- package/dist/runtime/agent.js +9 -0
- package/dist/runtime/daemon.js +57 -3
- package/dist/runtime/db.js +9 -0
- package/dist/runtime/main.js +195 -12
- package/dist/runtime/netproxy.js +9 -0
- package/dist/runtime/view.js +195 -12
- package/dist/selftest/channel.test.js +72 -1
- package/dist/selftest/contained.test.js +1 -37
- package/dist/selftest/hcs.test.js +5 -2
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/install.test.js +11 -0
- package/dist/selftest/layout.test.js +10 -1
- package/dist/selftest/live-daemon.test.js +51 -1
- package/dist/selftest/live-terminal.test.js +112 -4
- package/dist/selftest/menu-perf.test.js +198 -12
- package/dist/selftest/menu.test.js +4 -0
- package/dist/selftest/storage-helper.test.d.ts +2 -0
- package/dist/selftest/storage-helper.test.js +493 -0
- package/dist/selftest/support/vhdx.d.ts +14 -0
- package/dist/selftest/support/vhdx.js +72 -0
- package/dist/selftest/web.test.js +14 -2
- package/dist/shared/channel/run-dir.d.ts +6 -0
- package/dist/shared/channel/run-dir.js +9 -0
- package/dist/shared/pty/index.d.ts +3 -1
- package/dist/shared/pty/index.js +3 -1
- package/dist/shared/pty/key-gate.d.ts +24 -0
- package/dist/shared/pty/key-gate.js +45 -0
- package/dist/shared/pty/mouse-modes.d.ts +27 -0
- package/dist/shared/pty/mouse-modes.js +155 -0
- package/dist/shared/pty/terminal.d.ts +0 -5
- package/dist/shared/pty/terminal.js +51 -20
- package/dist/sync/watcher.js +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/{main-Z2WYAE2Z.js → main-6XNBTJLT.js} +34 -34
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +15 -0
- package/native/csharp/StorageHelper/Client.cs +297 -0
- package/native/csharp/StorageHelper/Log.cs +177 -0
- package/native/csharp/StorageHelper/MountRecords.cs +160 -0
- package/native/csharp/StorageHelper/Operations.cs +596 -0
- package/native/csharp/StorageHelper/Pins.cs +270 -0
- package/native/csharp/StorageHelper/Privileges.cs +78 -0
- package/native/csharp/StorageHelper/Program.cs +349 -0
- package/native/csharp/StorageHelper/Protocol.cs +107 -0
- package/native/csharp/StorageHelper/RateLimiter.cs +97 -0
- package/native/csharp/StorageHelper/Rules.cs +156 -0
- package/native/csharp/StorageHelper/Server.cs +656 -0
- package/native/csharp/StorageHelper/Settings.cs +297 -0
- package/native/csharp/StorageHelper/StorageService.cs +53 -0
- package/native/csharp/StorageHelper/UserSettings.cs +206 -0
- package/native/csharp/StorageHelper/Vhdx.cs +230 -0
- package/native/csharp/StorageHelper/VirtualDisk.cs +241 -0
- package/native/csharp/StorageHelper/Volumes.cs +379 -0
- package/native/csharp/StorageHelper/Watchdog.cs +128 -0
- package/native/csharp/StorageHelper/Win32.cs +250 -0
- package/native/scripts/set-storage-helper.ps1 +203 -0
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +236 -1
|
@@ -155,12 +155,15 @@ export function mountedSets(storageRoot) {
|
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* A line for the start of a command when the record says an image is mounted that no running process holds and no
|
|
158
|
-
* sandbox uses (nothing is looked up on the disk: this is only the record), or null.
|
|
158
|
+
* sandbox uses (nothing is looked up on the disk: this is only the record), or null. `sandboxRunning` can be a
|
|
159
|
+
* function: it is only asked (it runs tasklist) when the record says an image is mounted.
|
|
159
160
|
*/
|
|
160
161
|
export function staleMountNote(storageRoot, set, sandboxRunning, alive = processAlive) {
|
|
161
162
|
const record = setStateOf(new JsonFile(ContainedStateSchema, path.join(path.resolve(storageRoot), 'mounts', 'state.json')).read() ??
|
|
162
163
|
EMPTY_STATE, set);
|
|
163
|
-
if (!record.mounted ||
|
|
164
|
+
if (!record.mounted || record.holders.some((h) => alive(h.pid)))
|
|
165
|
+
return null;
|
|
166
|
+
if (typeof sandboxRunning === 'function' ? sandboxRunning() : sandboxRunning)
|
|
164
167
|
return null;
|
|
165
168
|
return 'Contained storage is still mounted from an earlier sandbox. Run sandbox repair to dismount it (Windows asks for permission).';
|
|
166
169
|
}
|
|
@@ -23,6 +23,11 @@ export declare class TerminalBridge {
|
|
|
23
23
|
start(): Promise<void>;
|
|
24
24
|
private fromBrowser;
|
|
25
25
|
private fromSession;
|
|
26
|
+
/**
|
|
27
|
+
* The program's mouse tracking ends with it: the browser's terminal stops sending clicks and moves, and a drag
|
|
28
|
+
* selects text again (an ended terminal stays on screen).
|
|
29
|
+
*/
|
|
30
|
+
private mouseOff;
|
|
26
31
|
/** Stops reading from the session until the browser has taken what it was sent. */
|
|
27
32
|
private hold;
|
|
28
33
|
private tell;
|
package/dist/host/web/bridge.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { safeText } from '../../core/util/index.js';
|
|
2
|
-
import { connect, encodeFrame, FrameReader, FrameType, helloFrame, OutputFilter, PING_INTERVAL_MS, resizeFrame, } from '../../shared/pty/index.js';
|
|
2
|
+
import { connect, encodeFrame, FrameReader, FrameType, helloFrame, MOUSE_OFF, OutputFilter, PING_INTERVAL_MS, resizeFrame, } from '../../shared/pty/index.js';
|
|
3
3
|
import { parseClientMessage } from '../../shared/web/index.js';
|
|
4
4
|
/** The browser is slower than the sandbox: past this much waiting to be sent, output is held back in the socket. */
|
|
5
5
|
const MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
|
|
@@ -94,9 +94,11 @@ export class TerminalBridge {
|
|
|
94
94
|
break;
|
|
95
95
|
}
|
|
96
96
|
case FrameType.Restarting:
|
|
97
|
+
this.mouseOff();
|
|
97
98
|
this.tell({ t: 'restarting' });
|
|
98
99
|
break;
|
|
99
100
|
case FrameType.Exit:
|
|
101
|
+
this.mouseOff();
|
|
100
102
|
this.tell({ t: 'exit', code: frame.payload.length >= 4 ? frame.payload.readInt32LE(0) : 1 });
|
|
101
103
|
this.finish(1000, 'exit');
|
|
102
104
|
return;
|
|
@@ -111,6 +113,14 @@ export class TerminalBridge {
|
|
|
111
113
|
if (this.ws.bufferedAmount > MAX_BUFFERED_BYTES)
|
|
112
114
|
this.hold();
|
|
113
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* The program's mouse tracking ends with it: the browser's terminal stops sending clicks and moves, and a drag
|
|
118
|
+
* selects text again (an ended terminal stays on screen).
|
|
119
|
+
*/
|
|
120
|
+
mouseOff() {
|
|
121
|
+
if (this.ws.readyState === this.ws.OPEN)
|
|
122
|
+
this.ws.send(Buffer.from(MOUSE_OFF, 'latin1'), { binary: true });
|
|
123
|
+
}
|
|
114
124
|
/** Stops reading from the session until the browser has taken what it was sent. */
|
|
115
125
|
hold() {
|
|
116
126
|
if (this.drain || !this.socket)
|
|
@@ -55,6 +55,7 @@ export declare class WebServer {
|
|
|
55
55
|
private readonly live;
|
|
56
56
|
private actualPort;
|
|
57
57
|
private lastUsed;
|
|
58
|
+
private lastCheck;
|
|
58
59
|
private heartbeat;
|
|
59
60
|
private idleCheck;
|
|
60
61
|
constructor(options: WebServerOptions);
|
|
@@ -69,6 +70,7 @@ export declare class WebServer {
|
|
|
69
70
|
private beat;
|
|
70
71
|
/** No browser has used the server for a while: it ends (sooner when no sandbox runs). */
|
|
71
72
|
private checkIdle;
|
|
73
|
+
private checkEveryMs;
|
|
72
74
|
/** How long unused before it ends when no sandbox runs. */
|
|
73
75
|
private quietMs;
|
|
74
76
|
/** Counts a new socket; at the limit, the oldest of its kind makes room (a new tab always works). */
|
package/dist/host/web/server.js
CHANGED
|
@@ -55,6 +55,7 @@ export class WebServer {
|
|
|
55
55
|
live;
|
|
56
56
|
actualPort = 0;
|
|
57
57
|
lastUsed = Date.now();
|
|
58
|
+
lastCheck = Date.now();
|
|
58
59
|
heartbeat;
|
|
59
60
|
idleCheck;
|
|
60
61
|
constructor(options) {
|
|
@@ -82,7 +83,8 @@ export class WebServer {
|
|
|
82
83
|
this.heartbeat = setInterval(() => this.beat(), this.options.heartbeatMs ?? HEARTBEAT_MS);
|
|
83
84
|
this.heartbeat.unref();
|
|
84
85
|
if (this.options.idleExitMs) {
|
|
85
|
-
this.
|
|
86
|
+
this.lastCheck = Date.now();
|
|
87
|
+
this.idleCheck = setInterval(() => void this.checkIdle(), this.checkEveryMs());
|
|
86
88
|
this.idleCheck.unref();
|
|
87
89
|
}
|
|
88
90
|
const gate = this.options.gate;
|
|
@@ -127,6 +129,11 @@ export class WebServer {
|
|
|
127
129
|
/** No browser has used the server for a while: it ends (sooner when no sandbox runs). */
|
|
128
130
|
async checkIdle() {
|
|
129
131
|
const limit = this.options.idleExitMs ?? 0;
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
// A check much later than planned: the PC slept. Its tabs get time to reconnect before anything counts as idle.
|
|
134
|
+
if (now - this.lastCheck > 3 * this.checkEveryMs())
|
|
135
|
+
this.lastUsed = now;
|
|
136
|
+
this.lastCheck = now;
|
|
130
137
|
if (!limit || this.tracked.size > 0)
|
|
131
138
|
return;
|
|
132
139
|
const idle = Date.now() - this.lastUsed;
|
|
@@ -143,6 +150,9 @@ export class WebServer {
|
|
|
143
150
|
}
|
|
144
151
|
this.options.onStop?.();
|
|
145
152
|
}
|
|
153
|
+
checkEveryMs() {
|
|
154
|
+
return Math.min(60_000, this.quietMs());
|
|
155
|
+
}
|
|
146
156
|
/** How long unused before it ends when no sandbox runs. */
|
|
147
157
|
quietMs() {
|
|
148
158
|
return Math.min(this.options.idleExitMs ?? 0, this.options.idleNoSandboxMs ?? IDLE_NO_SANDBOX_MS);
|
package/dist/runtime/agent.js
CHANGED
|
@@ -27173,6 +27173,11 @@ var RunDir = class {
|
|
|
27173
27173
|
projectsDir;
|
|
27174
27174
|
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
27175
27175
|
auditDir;
|
|
27176
|
+
/**
|
|
27177
|
+
* Each running session's process tree, recorded by its ptyhost (guest only: the daemon ends what a ptyhost that was
|
|
27178
|
+
* itself ended left running).
|
|
27179
|
+
*/
|
|
27180
|
+
treesDir;
|
|
27176
27181
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
27177
27182
|
secretsDir;
|
|
27178
27183
|
constructor(dataDir2) {
|
|
@@ -27186,6 +27191,7 @@ var RunDir = class {
|
|
|
27186
27191
|
this.sessionsDir = path5.join(this.root, "sessions");
|
|
27187
27192
|
this.projectsDir = path5.join(this.root, "projects");
|
|
27188
27193
|
this.auditDir = path5.join(this.root, "audit");
|
|
27194
|
+
this.treesDir = path5.join(this.root, "trees");
|
|
27189
27195
|
this.secretsDir = path5.join(dataDir2, "secrets");
|
|
27190
27196
|
}
|
|
27191
27197
|
requestFile(id) {
|
|
@@ -27197,6 +27203,9 @@ var RunDir = class {
|
|
|
27197
27203
|
sessionFile(id) {
|
|
27198
27204
|
return path5.join(this.sessionsDir, safeId(id) + ".json");
|
|
27199
27205
|
}
|
|
27206
|
+
treeFile(id) {
|
|
27207
|
+
return path5.join(this.treesDir, safeId(id) + ".json");
|
|
27208
|
+
}
|
|
27200
27209
|
projectFile(name) {
|
|
27201
27210
|
return path5.join(this.projectsDir, safeId(name) + ".json");
|
|
27202
27211
|
}
|
package/dist/runtime/daemon.js
CHANGED
|
@@ -27338,6 +27338,11 @@ var RunDir = class {
|
|
|
27338
27338
|
projectsDir;
|
|
27339
27339
|
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
27340
27340
|
auditDir;
|
|
27341
|
+
/**
|
|
27342
|
+
* Each running session's process tree, recorded by its ptyhost (guest only: the daemon ends what a ptyhost that was
|
|
27343
|
+
* itself ended left running).
|
|
27344
|
+
*/
|
|
27345
|
+
treesDir;
|
|
27341
27346
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
27342
27347
|
secretsDir;
|
|
27343
27348
|
constructor(dataDir2) {
|
|
@@ -27351,6 +27356,7 @@ var RunDir = class {
|
|
|
27351
27356
|
this.sessionsDir = path7.join(this.root, "sessions");
|
|
27352
27357
|
this.projectsDir = path7.join(this.root, "projects");
|
|
27353
27358
|
this.auditDir = path7.join(this.root, "audit");
|
|
27359
|
+
this.treesDir = path7.join(this.root, "trees");
|
|
27354
27360
|
this.secretsDir = path7.join(dataDir2, "secrets");
|
|
27355
27361
|
}
|
|
27356
27362
|
requestFile(id) {
|
|
@@ -27362,6 +27368,9 @@ var RunDir = class {
|
|
|
27362
27368
|
sessionFile(id) {
|
|
27363
27369
|
return path7.join(this.sessionsDir, safeId(id) + ".json");
|
|
27364
27370
|
}
|
|
27371
|
+
treeFile(id) {
|
|
27372
|
+
return path7.join(this.treesDir, safeId(id) + ".json");
|
|
27373
|
+
}
|
|
27365
27374
|
projectFile(name) {
|
|
27366
27375
|
return path7.join(this.projectsDir, safeId(name) + ".json");
|
|
27367
27376
|
}
|
|
@@ -32426,7 +32435,7 @@ function shellLaunch(asked, paths) {
|
|
|
32426
32435
|
// src/guest/sessions/pty-host.ts
|
|
32427
32436
|
import fs50 from "node:fs";
|
|
32428
32437
|
import path65 from "node:path";
|
|
32429
|
-
import { spawn as spawn6 } from "node:child_process";
|
|
32438
|
+
import { execFile, spawn as spawn6 } from "node:child_process";
|
|
32430
32439
|
function ensurePtyHost() {
|
|
32431
32440
|
return ensureCompiled("ptyhost");
|
|
32432
32441
|
}
|
|
@@ -32458,7 +32467,12 @@ var conptyDll = () => {
|
|
|
32458
32467
|
return session.provisioning.conpty?.enabled !== false && fs50.existsSync(dll) && fs50.existsSync(path65.join(toolsDir, "conpty", "OpenConsole.exe")) ? dll : null;
|
|
32459
32468
|
};
|
|
32460
32469
|
function startPtyHost(o) {
|
|
32461
|
-
|
|
32470
|
+
fs50.mkdirSync(run.treesDir, { recursive: true });
|
|
32471
|
+
const env = {
|
|
32472
|
+
...o.env,
|
|
32473
|
+
PTYHOST_BIND: o.remote ? "any" : "loopback",
|
|
32474
|
+
PTYHOST_TREE_FILE: run.treeFile(o.id)
|
|
32475
|
+
};
|
|
32462
32476
|
delete env.PTYHOST_CONPTY;
|
|
32463
32477
|
const dll = conptyDll();
|
|
32464
32478
|
if (dll) env.PTYHOST_CONPTY = dll;
|
|
@@ -32468,6 +32482,29 @@ function startPtyHost(o) {
|
|
|
32468
32482
|
{ cwd: o.cwd, env, detached: true, stdio: "ignore", windowsHide: true }
|
|
32469
32483
|
);
|
|
32470
32484
|
}
|
|
32485
|
+
var END_TREE_MS = 3e4;
|
|
32486
|
+
function endLeftoverTree(id, exe = ensurePtyHost()) {
|
|
32487
|
+
const file3 = run.treeFile(id);
|
|
32488
|
+
if (!fs50.existsSync(file3)) return Promise.resolve(null);
|
|
32489
|
+
return new Promise((resolve2) => {
|
|
32490
|
+
execFile(exe, ["--end-tree", file3], { timeout: END_TREE_MS, windowsHide: true }, (error62, stdout) => {
|
|
32491
|
+
const code = error62 ? typeof error62.code === "number" ? error62.code : -1 : 0;
|
|
32492
|
+
if (code !== 3)
|
|
32493
|
+
try {
|
|
32494
|
+
fs50.rmSync(file3, { force: true });
|
|
32495
|
+
} catch {
|
|
32496
|
+
}
|
|
32497
|
+
resolve2(code === 0 ? stdout.trim().slice(0, 200) : code === 3 ? null : "could not end them (code " + code + ")");
|
|
32498
|
+
});
|
|
32499
|
+
});
|
|
32500
|
+
}
|
|
32501
|
+
function recordedTrees() {
|
|
32502
|
+
try {
|
|
32503
|
+
return fs50.readdirSync(run.treesDir).filter((n) => /^[A-Za-z0-9_-]{1,64}\.json$/.test(n)).map((n) => n.slice(0, -".json".length));
|
|
32504
|
+
} catch {
|
|
32505
|
+
return [];
|
|
32506
|
+
}
|
|
32507
|
+
}
|
|
32471
32508
|
|
|
32472
32509
|
// src/guest/agents/agent-installer.ts
|
|
32473
32510
|
import fs51 from "node:fs";
|
|
@@ -32797,7 +32834,23 @@ var SessionManager = class {
|
|
|
32797
32834
|
}
|
|
32798
32835
|
rootStarted(id, child, from) {
|
|
32799
32836
|
if (child.pid) this.ptyHosts.set(id, { session: id, pid: child.pid, from });
|
|
32800
|
-
child.once("exit", () =>
|
|
32837
|
+
child.once("exit", () => {
|
|
32838
|
+
this.rootEnded(id);
|
|
32839
|
+
this.endLeftovers(id);
|
|
32840
|
+
});
|
|
32841
|
+
}
|
|
32842
|
+
/** What a session's ptyhost left running when it was itself ended (Task Manager, a crash) is ended now. */
|
|
32843
|
+
endLeftovers(id) {
|
|
32844
|
+
void endLeftoverTree(id).then((done) => {
|
|
32845
|
+
if (done) log("Session " + id + ": its session host was ended from outside; " + done + " it left running");
|
|
32846
|
+
}).catch((e) => warn("Ending what session " + id + " left running failed: " + errMsg(e)));
|
|
32847
|
+
}
|
|
32848
|
+
/**
|
|
32849
|
+
* At the daemon's start: trees recorded by session hosts that are gone (a daemon that ended with them) are ended
|
|
32850
|
+
* too. One whose session host still runs is left alone (ptyhost --end-tree checks).
|
|
32851
|
+
*/
|
|
32852
|
+
sweepLeftovers() {
|
|
32853
|
+
for (const id of recordedTrees()) if (!this.live.has(id)) this.endLeftovers(id);
|
|
32801
32854
|
}
|
|
32802
32855
|
rootEnded(id) {
|
|
32803
32856
|
const r = this.ptyHosts.get(id);
|
|
@@ -33505,6 +33558,7 @@ adoptBootSecrets();
|
|
|
33505
33558
|
log("Session daemon started (pid " + process.pid + ").");
|
|
33506
33559
|
safely("port forwards", startRelays);
|
|
33507
33560
|
safely("audit", () => startProcessCollector(() => sessions.roots()));
|
|
33561
|
+
safely("leftover processes", () => sessions.sweepLeftovers());
|
|
33508
33562
|
safely("memory governor", () => governor.start());
|
|
33509
33563
|
log("Memory governor: " + governor.describe());
|
|
33510
33564
|
new Daemon().start();
|
package/dist/runtime/db.js
CHANGED
|
@@ -27211,6 +27211,11 @@ var RunDir = class {
|
|
|
27211
27211
|
projectsDir;
|
|
27212
27212
|
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
27213
27213
|
auditDir;
|
|
27214
|
+
/**
|
|
27215
|
+
* Each running session's process tree, recorded by its ptyhost (guest only: the daemon ends what a ptyhost that was
|
|
27216
|
+
* itself ended left running).
|
|
27217
|
+
*/
|
|
27218
|
+
treesDir;
|
|
27214
27219
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
27215
27220
|
secretsDir;
|
|
27216
27221
|
constructor(dataDir2) {
|
|
@@ -27224,6 +27229,7 @@ var RunDir = class {
|
|
|
27224
27229
|
this.sessionsDir = path5.join(this.root, "sessions");
|
|
27225
27230
|
this.projectsDir = path5.join(this.root, "projects");
|
|
27226
27231
|
this.auditDir = path5.join(this.root, "audit");
|
|
27232
|
+
this.treesDir = path5.join(this.root, "trees");
|
|
27227
27233
|
this.secretsDir = path5.join(dataDir2, "secrets");
|
|
27228
27234
|
}
|
|
27229
27235
|
requestFile(id) {
|
|
@@ -27235,6 +27241,9 @@ var RunDir = class {
|
|
|
27235
27241
|
sessionFile(id) {
|
|
27236
27242
|
return path5.join(this.sessionsDir, safeId(id) + ".json");
|
|
27237
27243
|
}
|
|
27244
|
+
treeFile(id) {
|
|
27245
|
+
return path5.join(this.treesDir, safeId(id) + ".json");
|
|
27246
|
+
}
|
|
27238
27247
|
projectFile(name) {
|
|
27239
27248
|
return path5.join(this.projectsDir, safeId(name) + ".json");
|
|
27240
27249
|
}
|
package/dist/runtime/main.js
CHANGED
|
@@ -27322,6 +27322,11 @@ var RunDir = class {
|
|
|
27322
27322
|
projectsDir;
|
|
27323
27323
|
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
27324
27324
|
auditDir;
|
|
27325
|
+
/**
|
|
27326
|
+
* Each running session's process tree, recorded by its ptyhost (guest only: the daemon ends what a ptyhost that was
|
|
27327
|
+
* itself ended left running).
|
|
27328
|
+
*/
|
|
27329
|
+
treesDir;
|
|
27325
27330
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
27326
27331
|
secretsDir;
|
|
27327
27332
|
constructor(dataDir2) {
|
|
@@ -27335,6 +27340,7 @@ var RunDir = class {
|
|
|
27335
27340
|
this.sessionsDir = path6.join(this.root, "sessions");
|
|
27336
27341
|
this.projectsDir = path6.join(this.root, "projects");
|
|
27337
27342
|
this.auditDir = path6.join(this.root, "audit");
|
|
27343
|
+
this.treesDir = path6.join(this.root, "trees");
|
|
27338
27344
|
this.secretsDir = path6.join(dataDir2, "secrets");
|
|
27339
27345
|
}
|
|
27340
27346
|
requestFile(id) {
|
|
@@ -27346,6 +27352,9 @@ var RunDir = class {
|
|
|
27346
27352
|
sessionFile(id) {
|
|
27347
27353
|
return path6.join(this.sessionsDir, safeId(id) + ".json");
|
|
27348
27354
|
}
|
|
27355
|
+
treeFile(id) {
|
|
27356
|
+
return path6.join(this.treesDir, safeId(id) + ".json");
|
|
27357
|
+
}
|
|
27349
27358
|
projectFile(name) {
|
|
27350
27359
|
return path6.join(this.projectsDir, safeId(name) + ".json");
|
|
27351
27360
|
}
|
|
@@ -35339,12 +35348,175 @@ var CtrlCGuard = class {
|
|
|
35339
35348
|
}
|
|
35340
35349
|
};
|
|
35341
35350
|
|
|
35342
|
-
// src/shared/pty/
|
|
35343
|
-
var MOUSE_OFF = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1005l\x1B[?1006l\x1B[?1015l";
|
|
35351
|
+
// src/shared/pty/key-gate.ts
|
|
35344
35352
|
function fixWindowsKeys(chunk) {
|
|
35345
35353
|
if (!chunk.includes(8) && !chunk.includes(127)) return chunk;
|
|
35346
35354
|
return Buffer.from(chunk.map((b) => b === 8 ? 127 : b === 127 ? 8 : b));
|
|
35347
35355
|
}
|
|
35356
|
+
var KeyGate = class {
|
|
35357
|
+
pending = [];
|
|
35358
|
+
decided;
|
|
35359
|
+
swap = false;
|
|
35360
|
+
/** windowsConsole: input comes from a Windows console in raw mode (its mode is decided later); else it goes as is. */
|
|
35361
|
+
constructor(windowsConsole) {
|
|
35362
|
+
this.decided = !windowsConsole;
|
|
35363
|
+
}
|
|
35364
|
+
/** The bytes to send now (empty while the mode is not known yet). */
|
|
35365
|
+
push(data) {
|
|
35366
|
+
if (!data.length) return data;
|
|
35367
|
+
if (!this.decided) {
|
|
35368
|
+
this.pending.push(data);
|
|
35369
|
+
return Buffer.alloc(0);
|
|
35370
|
+
}
|
|
35371
|
+
return this.swap ? fixWindowsKeys(data) : data;
|
|
35372
|
+
}
|
|
35373
|
+
/** The console's mode is known: what was held, ready to send. Later calls only change how new input is read. */
|
|
35374
|
+
decide(vtInput) {
|
|
35375
|
+
this.swap = !vtInput;
|
|
35376
|
+
this.decided = true;
|
|
35377
|
+
const held = Buffer.concat(this.pending);
|
|
35378
|
+
this.pending = [];
|
|
35379
|
+
return this.swap ? fixWindowsKeys(held) : held;
|
|
35380
|
+
}
|
|
35381
|
+
};
|
|
35382
|
+
|
|
35383
|
+
// src/shared/pty/mouse-modes.ts
|
|
35384
|
+
var TRACKING = [9, 1e3, 1002, 1003];
|
|
35385
|
+
var MAX_PARAMS = 64;
|
|
35386
|
+
var MOTION = 32;
|
|
35387
|
+
var NO_BUTTON = 3;
|
|
35388
|
+
var MouseModes = class {
|
|
35389
|
+
on = /* @__PURE__ */ new Set();
|
|
35390
|
+
/** 0 text, 1 after ESC, 2 after ESC[, 3 in ESC[? parameters, 4 after ESC[! */
|
|
35391
|
+
state = 0;
|
|
35392
|
+
params = "";
|
|
35393
|
+
/** Reads the program's output (after OutputFilter): the mode changes in it. */
|
|
35394
|
+
watch(output3) {
|
|
35395
|
+
for (const b of output3) {
|
|
35396
|
+
if (b === 27) {
|
|
35397
|
+
this.state = 1;
|
|
35398
|
+
continue;
|
|
35399
|
+
}
|
|
35400
|
+
if (this.state === 1) {
|
|
35401
|
+
if (b === 99) this.on.clear();
|
|
35402
|
+
this.state = b === 91 ? 2 : 0;
|
|
35403
|
+
} else if (this.state === 2) {
|
|
35404
|
+
this.state = b === 63 ? 3 : b === 33 ? 4 : 0;
|
|
35405
|
+
this.params = "";
|
|
35406
|
+
} else if (this.state === 4) {
|
|
35407
|
+
if (b === 112) this.on.clear();
|
|
35408
|
+
this.state = 0;
|
|
35409
|
+
} else if (this.state === 3) {
|
|
35410
|
+
if (b >= 48 && b <= 57 || b === 59) {
|
|
35411
|
+
this.params += String.fromCharCode(b);
|
|
35412
|
+
if (this.params.length > MAX_PARAMS) this.state = 0;
|
|
35413
|
+
} else {
|
|
35414
|
+
if (b === 104 || b === 108) this.apply(this.params, b === 104);
|
|
35415
|
+
this.state = 0;
|
|
35416
|
+
}
|
|
35417
|
+
}
|
|
35418
|
+
}
|
|
35419
|
+
}
|
|
35420
|
+
/** The program is gone (exited or restarting): its modes went with it. */
|
|
35421
|
+
reset() {
|
|
35422
|
+
this.on.clear();
|
|
35423
|
+
this.state = 0;
|
|
35424
|
+
this.params = "";
|
|
35425
|
+
}
|
|
35426
|
+
/** True while the program tracks the mouse in any way. */
|
|
35427
|
+
tracking() {
|
|
35428
|
+
return TRACKING.some((m) => this.on.has(m));
|
|
35429
|
+
}
|
|
35430
|
+
/** True while the program has a mode on (2004: bracketed paste). */
|
|
35431
|
+
has(mode) {
|
|
35432
|
+
return this.on.has(mode);
|
|
35433
|
+
}
|
|
35434
|
+
/** The mode numbers on now (for the report and tests). */
|
|
35435
|
+
modes() {
|
|
35436
|
+
return [...this.on].sort((a, b) => a - b);
|
|
35437
|
+
}
|
|
35438
|
+
/** `input` without the mouse reports the program has not asked for. Sequences must be whole (InputJoiner). */
|
|
35439
|
+
filter(input2) {
|
|
35440
|
+
if (!input2.includes(27)) return input2;
|
|
35441
|
+
const parts2 = [];
|
|
35442
|
+
let from = 0;
|
|
35443
|
+
let i = input2.indexOf(27);
|
|
35444
|
+
while (i !== -1) {
|
|
35445
|
+
const report = mouseReport(input2, i, this.on.has(1005), this.on.has(1015));
|
|
35446
|
+
if (report && !this.allows(report.button)) {
|
|
35447
|
+
parts2.push(input2.subarray(from, i));
|
|
35448
|
+
from = i + report.length;
|
|
35449
|
+
i = input2.indexOf(27, from);
|
|
35450
|
+
} else i = input2.indexOf(27, i + 1);
|
|
35451
|
+
}
|
|
35452
|
+
if (from === 0) return input2;
|
|
35453
|
+
parts2.push(input2.subarray(from));
|
|
35454
|
+
return Buffer.concat(parts2);
|
|
35455
|
+
}
|
|
35456
|
+
allows(button) {
|
|
35457
|
+
if (!this.tracking()) return false;
|
|
35458
|
+
if ((button & MOTION) === 0) return true;
|
|
35459
|
+
if (this.on.has(1003)) return true;
|
|
35460
|
+
return this.on.has(1002) && (button & NO_BUTTON) !== NO_BUTTON;
|
|
35461
|
+
}
|
|
35462
|
+
apply(params, set2) {
|
|
35463
|
+
for (const p of params.split(";")) {
|
|
35464
|
+
const mode = Number(p);
|
|
35465
|
+
if (!p || !Number.isSafeInteger(mode)) continue;
|
|
35466
|
+
if (set2) this.on.add(mode);
|
|
35467
|
+
else this.on.delete(mode);
|
|
35468
|
+
}
|
|
35469
|
+
}
|
|
35470
|
+
};
|
|
35471
|
+
function mouseReport(data, at, utf8, urxvt) {
|
|
35472
|
+
if (data[at + 1] !== 91) return null;
|
|
35473
|
+
const sgr = data[at + 2] === 60;
|
|
35474
|
+
if (sgr || urxvt && data[at + 2] >= 48 && data[at + 2] <= 57) {
|
|
35475
|
+
let i = at + (sgr ? 3 : 2);
|
|
35476
|
+
let digits = "";
|
|
35477
|
+
let fields = 1;
|
|
35478
|
+
for (; i < data.length && i - at < 32; i++) {
|
|
35479
|
+
const b = data[i];
|
|
35480
|
+
if (b >= 48 && b <= 57) {
|
|
35481
|
+
if (fields === 1) digits += String.fromCharCode(b);
|
|
35482
|
+
} else if (b === 59) fields++;
|
|
35483
|
+
else break;
|
|
35484
|
+
}
|
|
35485
|
+
if (i >= data.length || fields !== 3 || !digits) return null;
|
|
35486
|
+
if (sgr ? data[i] !== 77 && data[i] !== 109 : data[i] !== 77) return null;
|
|
35487
|
+
const button = Number(digits) - (sgr ? 0 : 32);
|
|
35488
|
+
return button >= 0 ? { button, length: i - at + 1 } : null;
|
|
35489
|
+
}
|
|
35490
|
+
if (data[at + 2] === 77 && !utf8 && data.length - at >= 6) {
|
|
35491
|
+
const button = data[at + 3] - 32;
|
|
35492
|
+
return button >= 0 ? { button, length: 6 } : null;
|
|
35493
|
+
}
|
|
35494
|
+
return null;
|
|
35495
|
+
}
|
|
35496
|
+
|
|
35497
|
+
// src/shared/pty/terminal.ts
|
|
35498
|
+
var MOUSE_OFF = "\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1005l\x1B[?1006l\x1B[?1015l";
|
|
35499
|
+
var SETTLE_QUIET_MS = 50;
|
|
35500
|
+
var SETTLE_MAX_MS = 300;
|
|
35501
|
+
function drainInput(stream) {
|
|
35502
|
+
return new Promise((done) => {
|
|
35503
|
+
let quiet;
|
|
35504
|
+
const end = () => {
|
|
35505
|
+
clearTimeout(quiet);
|
|
35506
|
+
clearTimeout(cap);
|
|
35507
|
+
stream.off("data", onData);
|
|
35508
|
+
done();
|
|
35509
|
+
};
|
|
35510
|
+
const onData = () => {
|
|
35511
|
+
clearTimeout(quiet);
|
|
35512
|
+
quiet = setTimeout(end, SETTLE_QUIET_MS);
|
|
35513
|
+
};
|
|
35514
|
+
const cap = setTimeout(end, SETTLE_MAX_MS);
|
|
35515
|
+
stream.on("data", onData);
|
|
35516
|
+
onData();
|
|
35517
|
+
});
|
|
35518
|
+
}
|
|
35519
|
+
var VT_WAIT_MS = 1e4;
|
|
35348
35520
|
function runTerminalClient(socket, token, options = {}) {
|
|
35349
35521
|
const stdin = process.stdin;
|
|
35350
35522
|
const stdout = process.stdout;
|
|
@@ -35352,14 +35524,16 @@ function runTerminalClient(socket, token, options = {}) {
|
|
|
35352
35524
|
const [c, r] = stdout.isTTY ? stdout.getWindowSize() : [0, 0];
|
|
35353
35525
|
return [c || 120, r || 30];
|
|
35354
35526
|
};
|
|
35355
|
-
|
|
35527
|
+
const keys = new KeyGate(process.platform === "win32" && Boolean(stdin.isTTY));
|
|
35356
35528
|
return new Promise((resolve2) => {
|
|
35357
35529
|
let done = false;
|
|
35358
35530
|
const reader = new FrameReader();
|
|
35359
35531
|
const filter = new OutputFilter({ clipboardWrites: options.clipboardWrites });
|
|
35360
35532
|
let sent = size();
|
|
35361
35533
|
let resizeTimer;
|
|
35362
|
-
let vtInput;
|
|
35534
|
+
let vtInput, vtTimer;
|
|
35535
|
+
const mouse = new MouseModes();
|
|
35536
|
+
let passthrough = false;
|
|
35363
35537
|
const finish = (result) => {
|
|
35364
35538
|
if (done) return;
|
|
35365
35539
|
done = true;
|
|
@@ -35370,9 +35544,13 @@ function runTerminalClient(socket, token, options = {}) {
|
|
|
35370
35544
|
clearInterval(sizeWatch);
|
|
35371
35545
|
clearTimeout(joinTimer);
|
|
35372
35546
|
clearTimeout(hintTimer);
|
|
35547
|
+
clearTimeout(vtTimer);
|
|
35373
35548
|
socket.destroy();
|
|
35374
|
-
|
|
35375
|
-
|
|
35549
|
+
const tracked = mouse.tracking();
|
|
35550
|
+
const reset = "\x1B[0m\x1B[?25h" + MOUSE_OFF + (mouse.has(2004) ? "\x1B[?2004l" : "") + "\r\n";
|
|
35551
|
+
const flushed = new Promise((written) => stdout.write(reset, () => written()));
|
|
35552
|
+
const settled = tracked ? flushed.then(() => drainInput(stdin)) : Promise.resolve();
|
|
35553
|
+
void Promise.all([settled, vtInput?.stop()]).then(() => {
|
|
35376
35554
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
35377
35555
|
stdin.pause();
|
|
35378
35556
|
resolve2(result);
|
|
@@ -35380,9 +35558,10 @@ function runTerminalClient(socket, token, options = {}) {
|
|
|
35380
35558
|
};
|
|
35381
35559
|
const joiner = new InputJoiner();
|
|
35382
35560
|
let joinTimer;
|
|
35383
|
-
const
|
|
35384
|
-
if (data.length && !done) socket.write(encodeFrame(FrameType.Input,
|
|
35561
|
+
const write = (data) => {
|
|
35562
|
+
if (data.length && !done) socket.write(encodeFrame(FrameType.Input, data));
|
|
35385
35563
|
};
|
|
35564
|
+
const sendInput = (data) => write(keys.push(passthrough ? mouse.filter(data) : data));
|
|
35386
35565
|
const guard = options.confirmCtrlC ? new CtrlCGuard() : null;
|
|
35387
35566
|
let hintTimer;
|
|
35388
35567
|
const onInput = (chunk) => {
|
|
@@ -35404,7 +35583,6 @@ function runTerminalClient(socket, token, options = {}) {
|
|
|
35404
35583
|
sendInput(joiner.push(chunk));
|
|
35405
35584
|
if (joiner.holding()) joinTimer = setTimeout(() => sendInput(joiner.flush()), JOIN_MS);
|
|
35406
35585
|
};
|
|
35407
|
-
let passthrough = false;
|
|
35408
35586
|
const sendSize = () => {
|
|
35409
35587
|
const [c2, r2] = size();
|
|
35410
35588
|
if (c2 === sent[0] && r2 === sent[1]) return;
|
|
@@ -35434,11 +35612,14 @@ function runTerminalClient(socket, token, options = {}) {
|
|
|
35434
35612
|
for (const frame of frames) {
|
|
35435
35613
|
if (frame.type === FrameType.Output) {
|
|
35436
35614
|
const out = filter.push(frame.payload);
|
|
35615
|
+
mouse.watch(out);
|
|
35437
35616
|
guard?.watchOutput(out.toString("latin1"));
|
|
35438
35617
|
stdout.write(out);
|
|
35439
35618
|
} else if (frame.type === FrameType.Console) passthrough = frame.payload.toString("latin1") === "conpty.dll";
|
|
35440
|
-
else if (frame.type === FrameType.Restarting)
|
|
35441
|
-
|
|
35619
|
+
else if (frame.type === FrameType.Restarting) {
|
|
35620
|
+
mouse.reset();
|
|
35621
|
+
stdout.write(MOUSE_OFF + "\x1B[2J\x1B[3J\x1B[H");
|
|
35622
|
+
} else if (frame.type === FrameType.Exit)
|
|
35442
35623
|
finish({ reason: "exit", exitCode: frame.payload.length >= 4 ? frame.payload.readInt32LE(0) : 1 });
|
|
35443
35624
|
else if (frame.type === FrameType.Error)
|
|
35444
35625
|
finish({
|
|
@@ -35454,8 +35635,10 @@ function runTerminalClient(socket, token, options = {}) {
|
|
|
35454
35635
|
socket.write(helloFrame(token, c, r));
|
|
35455
35636
|
if (stdin.isTTY) stdin.setRawMode(true);
|
|
35456
35637
|
vtInput = enableVtInput();
|
|
35638
|
+
vtTimer = setTimeout(() => write(keys.decide(false)), VT_WAIT_MS);
|
|
35457
35639
|
void vtInput.ready.then((on) => {
|
|
35458
|
-
|
|
35640
|
+
clearTimeout(vtTimer);
|
|
35641
|
+
write(keys.decide(on));
|
|
35459
35642
|
});
|
|
35460
35643
|
stdin.on("data", onInput);
|
|
35461
35644
|
stdin.resume();
|
package/dist/runtime/netproxy.js
CHANGED
|
@@ -27202,6 +27202,11 @@ var RunDir = class {
|
|
|
27202
27202
|
projectsDir;
|
|
27203
27203
|
/** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
|
|
27204
27204
|
auditDir;
|
|
27205
|
+
/**
|
|
27206
|
+
* Each running session's process tree, recorded by its ptyhost (guest only: the daemon ends what a ptyhost that was
|
|
27207
|
+
* itself ended left running).
|
|
27208
|
+
*/
|
|
27209
|
+
treesDir;
|
|
27205
27210
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
27206
27211
|
secretsDir;
|
|
27207
27212
|
constructor(dataDir2) {
|
|
@@ -27215,6 +27220,7 @@ var RunDir = class {
|
|
|
27215
27220
|
this.sessionsDir = path3.join(this.root, "sessions");
|
|
27216
27221
|
this.projectsDir = path3.join(this.root, "projects");
|
|
27217
27222
|
this.auditDir = path3.join(this.root, "audit");
|
|
27223
|
+
this.treesDir = path3.join(this.root, "trees");
|
|
27218
27224
|
this.secretsDir = path3.join(dataDir2, "secrets");
|
|
27219
27225
|
}
|
|
27220
27226
|
requestFile(id) {
|
|
@@ -27226,6 +27232,9 @@ var RunDir = class {
|
|
|
27226
27232
|
sessionFile(id) {
|
|
27227
27233
|
return path3.join(this.sessionsDir, safeId(id) + ".json");
|
|
27228
27234
|
}
|
|
27235
|
+
treeFile(id) {
|
|
27236
|
+
return path3.join(this.treesDir, safeId(id) + ".json");
|
|
27237
|
+
}
|
|
27229
27238
|
projectFile(name) {
|
|
27230
27239
|
return path3.join(this.projectsDir, safeId(name) + ".json");
|
|
27231
27240
|
}
|