windows-development-sandbox 1.12.0 → 1.13.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/README.md +1 -0
- package/config.example.json +11 -1
- package/dist/app/features/access/access.def.d.ts +190 -0
- package/dist/app/features/access/access.def.js +273 -0
- package/dist/app/features/access/access.run.d.ts +79 -0
- package/dist/app/features/access/access.run.js +129 -0
- package/dist/app/features/access/index.d.ts +2 -0
- package/dist/app/features/access/index.js +3 -0
- package/dist/app/features/agents/agent-entry.js +5 -0
- package/dist/app/features/agents/agents.def.js +1 -0
- package/dist/app/features/apps/apps.def.js +1 -0
- package/dist/app/features/audit/audit.def.d.ts +11 -2
- package/dist/app/features/audit/audit.def.js +7 -4
- package/dist/app/features/audit/audit.run.d.ts +3 -0
- package/dist/app/features/defs.d.ts +155 -3
- package/dist/app/features/defs.js +12 -1
- package/dist/app/features/files/files.def.js +1 -0
- package/dist/app/features/health/health.def.js +1 -0
- package/dist/app/features/sandbox/sandbox.def.js +3 -0
- package/dist/app/features/sessions/sessions.def.d.ts +36 -0
- package/dist/app/features/sessions/sessions.def.js +77 -9
- package/dist/app/features/sessions/sessions.run.d.ts +25 -2
- package/dist/app/features/sessions/sessions.run.js +71 -3
- package/dist/app/features/settings/settings.def.d.ts +8 -0
- package/dist/app/features/settings/settings.def.js +17 -0
- package/dist/app/features/web/web.def.d.ts +9 -0
- package/dist/app/features/web/web.def.js +26 -1
- package/dist/app/features/web/web.run.d.ts +3 -0
- package/dist/app/features/web/web.run.js +11 -3
- package/dist/app/kernel/access.d.ts +33 -0
- package/dist/app/kernel/access.js +44 -0
- package/dist/app/kernel/define.d.ts +14 -1
- package/dist/app/kernel/index.d.ts +1 -0
- package/dist/app/kernel/index.js +1 -0
- package/dist/app/kernel/rpc.d.ts +5 -2
- package/dist/app/registry.d.ts +4 -1
- package/dist/app/registry.js +24 -4
- package/dist/cli/commands/registry.js +2 -0
- package/dist/cli/commands/remote.d.ts +2 -0
- package/dist/cli/commands/remote.js +49 -0
- package/dist/cli/commands/sessions/reopen.js +3 -0
- package/dist/cli/commands/web.js +66 -0
- package/dist/cli/manifest.d.ts +63 -4
- package/dist/cli/manifest.js +33 -4
- package/dist/cli/menu/io-menu.d.ts +5 -3
- package/dist/cli/menu/io-menu.js +12 -5
- package/dist/cli/menu/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/views.js +2 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/remote-client.d.ts +16 -0
- package/dist/cli/services/remote-client.js +141 -0
- package/dist/cli/services/sessions-service.d.ts +5 -0
- package/dist/cli/services/sessions-service.js +17 -1
- package/dist/core/config/defaults.js +11 -1
- package/dist/core/local-config/file.js +16 -2
- package/dist/core/local-config/overrides.js +7 -0
- package/dist/core/run/versions.js +9 -8
- package/dist/core/schema/config.d.ts +22 -3
- package/dist/core/schema/config.js +39 -2
- package/dist/core/schema/web.d.ts +51 -0
- package/dist/core/schema/web.js +46 -1
- package/dist/core/util/fs.d.ts +5 -0
- package/dist/core/util/fs.js +23 -0
- package/dist/core/util/index.d.ts +1 -1
- package/dist/core/util/index.js +1 -1
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/daemon/idle-shutdown.js +2 -1
- package/dist/guest/daemon/sessions.d.ts +16 -2
- package/dist/guest/daemon/sessions.js +126 -20
- package/dist/guest/git/decision.js +7 -0
- package/dist/guest/sessions/cmdline.d.ts +14 -0
- package/dist/guest/sessions/cmdline.js +10 -0
- package/dist/guest/toolchains/setup.js +3 -0
- package/dist/host/audit/log.d.ts +3 -2
- package/dist/host/audit/log.js +3 -3
- package/dist/host/channel/channel.d.ts +5 -1
- package/dist/host/channel/channel.js +12 -0
- package/dist/host/guard/agent-files.js +7 -1
- package/dist/host/guard/commands.js +13 -1
- package/dist/host/guard/git-config.js +27 -7
- package/dist/host/guard/git-dirs.js +78 -3
- package/dist/host/guard/mark-of-the-web.js +3 -1
- package/dist/host/guard/nested-repos.js +7 -2
- package/dist/host/guard/store.js +1 -0
- package/dist/host/guard/types.d.ts +2 -0
- package/dist/host/launch/mappings.js +11 -1
- package/dist/host/launch/project-scan.js +2 -21
- package/dist/host/launch/stage.js +4 -0
- package/dist/host/life/session-ledger.js +2 -1
- package/dist/host/remote/access-store.d.ts +156 -0
- package/dist/host/remote/access-store.js +308 -0
- package/dist/host/remote/cert.d.ts +20 -0
- package/dist/host/remote/cert.js +103 -0
- package/dist/host/remote/door-client.d.ts +13 -0
- package/dist/host/remote/door-client.js +54 -0
- package/dist/host/remote/firewall.d.ts +44 -0
- package/dist/host/remote/firewall.js +118 -0
- package/dist/host/remote/gate.d.ts +125 -0
- package/dist/host/remote/gate.js +391 -0
- package/dist/host/remote/index.d.ts +8 -0
- package/dist/host/remote/index.js +10 -0
- package/dist/host/remote/lan.d.ts +24 -0
- package/dist/host/remote/lan.js +88 -0
- package/dist/host/remote/tls-store.d.ts +22 -0
- package/dist/host/remote/tls-store.js +63 -0
- package/dist/host/remote/windows-helper.d.ts +67 -0
- package/dist/host/remote/windows-helper.js +129 -0
- package/dist/host/web/bridge.d.ts +5 -1
- package/dist/host/web/bridge.js +6 -2
- package/dist/host/web/browser.js +1 -1
- package/dist/host/web/control.d.ts +4 -1
- package/dist/host/web/control.js +28 -4
- package/dist/host/web/rpc.d.ts +16 -2
- package/dist/host/web/rpc.js +37 -4
- package/dist/host/web/runner.d.ts +4 -0
- package/dist/host/web/runner.js +12 -0
- package/dist/host/web/server.d.ts +18 -2
- package/dist/host/web/server.js +97 -24
- package/dist/host/web/state.d.ts +5 -0
- package/dist/host/web/state.js +8 -0
- package/dist/runtime/agent.js +74 -3
- package/dist/runtime/daemon.js +205 -24
- package/dist/runtime/db.js +74 -3
- package/dist/runtime/gitguard.js +6 -0
- package/dist/runtime/main.js +77 -3
- package/dist/runtime/netproxy.js +74 -3
- package/dist/runtime/view.js +76 -3
- package/dist/selftest/app.test.js +41 -6
- package/dist/selftest/channel.test.js +16 -0
- package/dist/selftest/cmdline.test.js +16 -0
- package/dist/selftest/git-access.test.js +8 -0
- package/dist/selftest/hostile-input.test.js +12 -1
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/remote.test.d.ts +2 -0
- package/dist/selftest/remote.test.js +536 -0
- package/dist/selftest/repo-guard.test.js +56 -2
- package/dist/selftest/resume.test.js +2 -1
- package/dist/selftest/sharing.test.js +9 -2
- package/dist/selftest/support/fixtures.d.ts +13 -0
- package/dist/selftest/support/web-preview.js +63 -0
- package/dist/selftest/web.test.js +2 -0
- package/dist/shared/channel/schema.d.ts +23 -0
- package/dist/shared/channel/schema.js +8 -0
- package/dist/shared/pty/protocol.d.ts +2 -0
- package/dist/shared/pty/protocol.js +2 -0
- package/dist/web/index.html +1 -1
- package/dist/web/main-Z2WYAE2Z.js +221 -0
- package/native/csharp/HostTools/Program.cs +17 -1
- package/native/csharp/HostTools/WebHelper.cs +499 -0
- package/native/scripts/get-network-profile.ps1 +19 -0
- package/native/scripts/set-folder-access.ps1 +11 -0
- package/native/scripts/set-web-firewall.ps1 +103 -0
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +20 -0
- package/schemas/config.schema.json +65 -0
- package/schemas/sandbox-config.schema.json +0 -18
- package/dist/web/main-UD4QCXZR.js +0 -219
package/dist/host/web/server.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// The web UI's HTTP server: the app's files, a small JSON API for sessions, the terminal WebSocket and the RPC
|
|
2
2
|
// WebSocket of the shared core. Loopback only; nothing works without the token's cookie, and Host / Origin / CSRF
|
|
3
3
|
// checks keep other sites out (docs/web-ui.md).
|
|
4
|
+
import crypto from 'node:crypto';
|
|
4
5
|
import fs from 'node:fs';
|
|
5
6
|
import http from 'node:http';
|
|
6
7
|
import { WebSocketServer } from 'ws';
|
|
8
|
+
import { allows, callerAccess, callerLabel, OWNER } from '../../app/kernel/index.js';
|
|
7
9
|
import { UserError, safeText } from '../../core/util/index.js';
|
|
8
10
|
import { MAX_CLIENT_MESSAGE_BYTES, MAX_COLS, MAX_ROWS, MIN_COLS, MIN_ROWS } from '../../shared/web/index.js';
|
|
9
11
|
import { cookieHeader, csrfOk, hostAllowed, originAllowed, WebAuth } from './auth.js';
|
|
@@ -28,6 +30,9 @@ export const IDLE_EXIT_MS = 20 * 60_000;
|
|
|
28
30
|
export const IDLE_NO_SANDBOX_MS = 5 * 60_000;
|
|
29
31
|
const SESSION_PATH = /^\/api\/sessions\/([A-Za-z0-9_-]{1,64})\/terminal$/;
|
|
30
32
|
export const RPC_PATH = '/api/rpc';
|
|
33
|
+
/** What a service worker registered on this origin by someone else gets when the browser updates it: gone. */
|
|
34
|
+
const UNREGISTER_WORKER = "self.addEventListener('install',()=>self.skipWaiting());" +
|
|
35
|
+
"self.addEventListener('activate',(e)=>e.waitUntil(self.registration.unregister()));";
|
|
31
36
|
class HttpFailure extends Error {
|
|
32
37
|
status;
|
|
33
38
|
constructor(status, message) {
|
|
@@ -80,7 +85,19 @@ export class WebServer {
|
|
|
80
85
|
this.idleCheck = setInterval(() => void this.checkIdle(), Math.min(60_000, this.quietMs()));
|
|
81
86
|
this.idleCheck.unref();
|
|
82
87
|
}
|
|
83
|
-
|
|
88
|
+
const gate = this.options.gate;
|
|
89
|
+
if (!gate)
|
|
90
|
+
return resolve();
|
|
91
|
+
gate.start();
|
|
92
|
+
void gate
|
|
93
|
+
.startLan({
|
|
94
|
+
request: (req, res) => void this.handle(req, res),
|
|
95
|
+
upgrade: (req, socket, head) => {
|
|
96
|
+
this.lastUsed = Date.now();
|
|
97
|
+
this.upgrade(req, socket, head);
|
|
98
|
+
},
|
|
99
|
+
})
|
|
100
|
+
.finally(resolve);
|
|
84
101
|
});
|
|
85
102
|
});
|
|
86
103
|
}
|
|
@@ -155,7 +172,8 @@ export class WebServer {
|
|
|
155
172
|
for (const client of this.sockets.clients)
|
|
156
173
|
client.close(1001, 'server stopping');
|
|
157
174
|
this.server.closeAllConnections();
|
|
158
|
-
|
|
175
|
+
const gate = this.options.gate?.stop();
|
|
176
|
+
return new Promise((resolve) => this.server.close(() => void Promise.resolve(gate).then(() => resolve())));
|
|
159
177
|
}
|
|
160
178
|
headers(extra = {}) {
|
|
161
179
|
return {
|
|
@@ -175,7 +193,7 @@ export class WebServer {
|
|
|
175
193
|
async handle(req, res) {
|
|
176
194
|
this.lastUsed = Date.now();
|
|
177
195
|
try {
|
|
178
|
-
if (!
|
|
196
|
+
if (!this.hostOk(req))
|
|
179
197
|
throw new HttpFailure(421, 'Wrong host name.');
|
|
180
198
|
const url = new URL(req.url ?? '/', 'http://127.0.0.1');
|
|
181
199
|
if (url.pathname.startsWith('/api/'))
|
|
@@ -192,9 +210,35 @@ export class WebServer {
|
|
|
192
210
|
this.json(res, 500, { error: 'Something went wrong.' });
|
|
193
211
|
}
|
|
194
212
|
}
|
|
213
|
+
/** The LAN listener's own address:port, or loopback with our port. */
|
|
214
|
+
hostOk(req) {
|
|
215
|
+
const gate = this.options.gate;
|
|
216
|
+
if (gate?.isLan(req.socket))
|
|
217
|
+
return gate.lanHostAllowed(req.headers.host);
|
|
218
|
+
return hostAllowed(req.headers.host, this.actualPort);
|
|
219
|
+
}
|
|
220
|
+
originOk(req) {
|
|
221
|
+
const gate = this.options.gate;
|
|
222
|
+
if (gate?.isLan(req.socket))
|
|
223
|
+
return gate.lanOriginAllowed(req.headers.origin, req.headers.host);
|
|
224
|
+
return originAllowed(req.headers.origin, req.headers.host, this.actualPort);
|
|
225
|
+
}
|
|
226
|
+
/** Who sent the request: the owner's token cookie (loopback only), or whoever the gate lets in. Null: nobody. */
|
|
227
|
+
async callerOf(req) {
|
|
228
|
+
const gate = this.options.gate;
|
|
229
|
+
const ownerCookie = !gate?.isLan(req.socket) && this.auth.signedIn(req.headers.cookie);
|
|
230
|
+
if (!gate)
|
|
231
|
+
return { caller: ownerCookie ? OWNER : null, ownerCookie };
|
|
232
|
+
return { caller: await gate.caller(req, ownerCookie), ownerCookie };
|
|
233
|
+
}
|
|
195
234
|
serveFile(req, res, pathname) {
|
|
196
235
|
if (req.method !== 'GET' && req.method !== 'HEAD')
|
|
197
236
|
throw new HttpFailure(405, 'Not allowed.');
|
|
237
|
+
if (req.headers['service-worker'] === 'script') {
|
|
238
|
+
res.writeHead(200, this.headers({ 'Content-Type': 'text/javascript; charset=utf-8', 'Cache-Control': 'no-store' }));
|
|
239
|
+
res.end(UNREGISTER_WORKER);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
198
242
|
const file = this.files.find(pathname);
|
|
199
243
|
if (!file)
|
|
200
244
|
throw new HttpFailure(404, 'Not found.');
|
|
@@ -211,14 +255,21 @@ export class WebServer {
|
|
|
211
255
|
}
|
|
212
256
|
/** A call that changes something: the custom header, and an Origin that is this page. */
|
|
213
257
|
requireSameSite(req) {
|
|
214
|
-
if (!csrfOk(req.headers) || !
|
|
258
|
+
if (!csrfOk(req.headers) || !this.originOk(req))
|
|
215
259
|
throw new HttpFailure(403, 'Refused.');
|
|
216
260
|
}
|
|
217
261
|
async api(req, res, pathname) {
|
|
218
262
|
const method = req.method ?? 'GET';
|
|
219
263
|
const { backend } = this.options;
|
|
220
264
|
if (pathname === '/api/health' && method === 'GET')
|
|
221
|
-
return this.json(res, 200,
|
|
265
|
+
return this.json(res, 200, this.health(req));
|
|
266
|
+
if (this.options.gate &&
|
|
267
|
+
(await this.options.gate.route(req, pathname, {
|
|
268
|
+
body: () => this.body(req),
|
|
269
|
+
json: (status, body, headers) => this.json(res, status, body, headers),
|
|
270
|
+
requireSameSite: () => this.requireSameSite(req),
|
|
271
|
+
})))
|
|
272
|
+
return;
|
|
222
273
|
if (pathname === '/api/login' && method === 'POST') {
|
|
223
274
|
this.requireSameSite(req);
|
|
224
275
|
const body = await this.body(req);
|
|
@@ -227,14 +278,19 @@ export class WebServer {
|
|
|
227
278
|
throw new HttpFailure(result.status, result.status === 429 ? 'Too many tries.' : 'Wrong token.');
|
|
228
279
|
return this.json(res, 200, { ok: true }, { 'Set-Cookie': cookieHeader(result.cookie) });
|
|
229
280
|
}
|
|
230
|
-
|
|
281
|
+
const { caller } = await this.callerOf(req);
|
|
282
|
+
if (!caller)
|
|
231
283
|
throw new HttpFailure(401, 'Not signed in.');
|
|
284
|
+
if (pathname === '/api/me' && method === 'GET')
|
|
285
|
+
return this.json(res, 200, { kind: caller.kind, who: callerLabel(caller), access: callerAccess(caller) });
|
|
232
286
|
if (pathname === '/api/info' && method === 'GET')
|
|
233
287
|
return this.json(res, 200, await backend.info());
|
|
234
288
|
if (pathname === '/api/sessions' && method === 'GET')
|
|
235
289
|
return this.json(res, 200, await backend.sessions());
|
|
236
290
|
if (pathname === '/api/stop' && method === 'POST') {
|
|
237
291
|
this.requireSameSite(req);
|
|
292
|
+
if (caller.kind !== 'owner')
|
|
293
|
+
throw new HttpFailure(403, 'Only the owner of this PC can stop the web UI.');
|
|
238
294
|
this.json(res, 200, { ok: true });
|
|
239
295
|
setTimeout(() => this.options.onStop?.(), 100);
|
|
240
296
|
return;
|
|
@@ -278,31 +334,48 @@ export class WebServer {
|
|
|
278
334
|
if (!match && !rpc)
|
|
279
335
|
return refuse('404 Not Found');
|
|
280
336
|
// The Origin must be this page (a page of another site can open WebSockets too) and the cookie is the login.
|
|
281
|
-
if (!
|
|
337
|
+
if (!this.hostOk(req) || !this.originOk(req))
|
|
282
338
|
return refuse('403 Forbidden');
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
339
|
+
void this.callerOf(req).then(({ caller, ownerCookie }) => {
|
|
340
|
+
if (!caller)
|
|
341
|
+
return refuse('401 Unauthorized');
|
|
342
|
+
if (rpc)
|
|
343
|
+
return this.upgradeRpc(req, socket, head, rpc, caller, ownerCookie);
|
|
344
|
+
if (!match)
|
|
345
|
+
return refuse('404 Not Found');
|
|
346
|
+
const endpoint = this.options.backend.endpoint(match[1]);
|
|
347
|
+
if (!endpoint)
|
|
348
|
+
return refuse('404 Not Found');
|
|
349
|
+
const cols = bounded(url.searchParams.get('cols'), MIN_COLS, MAX_COLS, 120);
|
|
350
|
+
const rows = bounded(url.searchParams.get('rows'), MIN_ROWS, MAX_ROWS, 30);
|
|
351
|
+
// View only: the terminal shows, and the bridge drops every keystroke and resize.
|
|
352
|
+
const readOnly = !allows(caller, 'control');
|
|
353
|
+
this.sockets.handleUpgrade(req, socket, head, (ws) => {
|
|
354
|
+
this.track(ws, 'terminal', MAX_TERMINALS);
|
|
355
|
+
this.options.gate?.track(ws, req, ownerCookie, caller);
|
|
356
|
+
void new TerminalBridge(ws, endpoint, cols, rows, this.options.dial, readOnly).start();
|
|
357
|
+
});
|
|
297
358
|
});
|
|
298
359
|
}
|
|
299
|
-
/** The RPC socket (after the same Origin and
|
|
300
|
-
upgradeRpc(req, socket, head, rpc) {
|
|
360
|
+
/** The RPC socket (after the same Origin and caller checks as a terminal). */
|
|
361
|
+
upgradeRpc(req, socket, head, rpc, caller, ownerCookie) {
|
|
301
362
|
this.sockets.handleUpgrade(req, socket, head, (ws) => {
|
|
302
363
|
this.track(ws, 'rpc', MAX_RPC_SOCKETS);
|
|
303
|
-
|
|
364
|
+
const gate = this.options.gate;
|
|
365
|
+
const now = gate ? gate.track(ws, req, ownerCookie, caller) : () => caller;
|
|
366
|
+
new RpcSession(ws, rpc, this.live, now, gate ? (entry) => gate.recordCall(entry) : undefined).start();
|
|
304
367
|
});
|
|
305
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* GET /api/health: alive, and with ?challenge=<hex> on loopback the proof that this server holds the token (an
|
|
371
|
+
* HMAC of the challenge), so the CLI never hands the token link to another program on the port.
|
|
372
|
+
*/
|
|
373
|
+
health(req) {
|
|
374
|
+
const challenge = new URL(req.url ?? '/', 'http://127.0.0.1').searchParams.get('challenge') ?? '';
|
|
375
|
+
if (this.options.gate?.isLan(req.socket) || !/^[0-9a-f]{32,128}$/.test(challenge))
|
|
376
|
+
return { ok: true };
|
|
377
|
+
return { ok: true, proof: crypto.createHmac('sha256', this.options.token).update(challenge).digest('hex') };
|
|
378
|
+
}
|
|
306
379
|
}
|
|
307
380
|
function bounded(text, min, max, fallback) {
|
|
308
381
|
const n = Number(text);
|
package/dist/host/web/state.d.ts
CHANGED
|
@@ -5,6 +5,11 @@ export declare const WebStateSchema: z.ZodObject<{
|
|
|
5
5
|
token: z.ZodString;
|
|
6
6
|
startedAt: z.ZodString;
|
|
7
7
|
version: z.ZodOptional<z.ZodString>;
|
|
8
|
+
lan: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
url: z.ZodNullable<z.ZodString>;
|
|
10
|
+
fingerprint: z.ZodNullable<z.ZodString>;
|
|
11
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
12
|
+
}, z.core.$strict>>;
|
|
8
13
|
}, z.core.$strict>;
|
|
9
14
|
export type WebState = z.output<typeof WebStateSchema>;
|
|
10
15
|
export declare class WebStore {
|
package/dist/host/web/state.js
CHANGED
|
@@ -12,6 +12,14 @@ export const WebStateSchema = z.strictObject({
|
|
|
12
12
|
startedAt: z.string().max(64),
|
|
13
13
|
/** The version that started it (missing: an older one). Another version is replaced, never reused. */
|
|
14
14
|
version: z.string().max(64).optional(),
|
|
15
|
+
/** LAN mode (docs/design/remote-access.md): its address for phones, the certificate, or why it is not running. */
|
|
16
|
+
lan: z
|
|
17
|
+
.strictObject({
|
|
18
|
+
url: z.string().max(64).nullable(),
|
|
19
|
+
fingerprint: z.string().max(100).nullable(),
|
|
20
|
+
problem: z.string().max(500).nullable(),
|
|
21
|
+
})
|
|
22
|
+
.optional(),
|
|
15
23
|
});
|
|
16
24
|
export class WebStore {
|
|
17
25
|
storageRoot;
|
package/dist/runtime/agent.js
CHANGED
|
@@ -26660,12 +26660,39 @@ var ResumeSchema = external_exports.strictObject({
|
|
|
26660
26660
|
});
|
|
26661
26661
|
|
|
26662
26662
|
// src/core/schema/web.ts
|
|
26663
|
+
var REMOTE_ROLES = ["view", "control"];
|
|
26664
|
+
var WindowsUserNameSchema = external_exports.string().trim().min(1).max(120).regex(
|
|
26665
|
+
/^(?:[A-Za-z0-9][A-Za-z0-9._ -]{0,62}\\)?[^\\/"[\]:;|=,+*?<>@]{1,64}$/,
|
|
26666
|
+
"a Windows user name (name or DOMAIN\\name)"
|
|
26667
|
+
);
|
|
26668
|
+
var RemoteUserSchema = external_exports.strictObject({
|
|
26669
|
+
name: WindowsUserNameSchema.describe("The Windows user (name, or DOMAIN\\name)"),
|
|
26670
|
+
role: external_exports.enum(REMOTE_ROLES).describe("view: sees everything, terminals read-only. control: everything but remote access, logins and tokens.")
|
|
26671
|
+
});
|
|
26672
|
+
var WebLanSchema = external_exports.strictObject({
|
|
26673
|
+
enabled: external_exports.boolean().describe(
|
|
26674
|
+
"(Beta) Devices on your network (a phone): an HTTPS address on this PC for devices you pair with a one-time code (sandbox web pair). Needs a firewall rule (sandbox web lan on asks Windows once). Off: every paired device is signed out."
|
|
26675
|
+
),
|
|
26676
|
+
port: external_exports.number().int().min(1024).max(65535).describe("HTTPS port for paired devices on your network."),
|
|
26677
|
+
address: external_exports.string().trim().max(15).regex(/^$|^\d{1,3}(\.\d{1,3}){3}$/, "an IPv4 address or empty").describe("This PC's private address to listen on. Empty: the one of its network adapter (never a public one).")
|
|
26678
|
+
});
|
|
26679
|
+
var WebRemoteSchema = external_exports.strictObject({
|
|
26680
|
+
windowsSignIn: external_exports.boolean().describe(
|
|
26681
|
+
"(Beta) Other Windows users of this PC named in web.remote.users sign in to your web UI (and sandbox remote) with their own Windows account: no password, no link."
|
|
26682
|
+
),
|
|
26683
|
+
users: external_exports.array(RemoteUserSchema).max(32).describe("Who else may sign in with Windows, and their role (view or control)."),
|
|
26684
|
+
rememberDays: external_exports.number().int().min(1).max(90).describe(
|
|
26685
|
+
"How many days a paired device or a Windows sign-in stays signed in (from when it signed in, not renewed)."
|
|
26686
|
+
),
|
|
26687
|
+
lan: WebLanSchema
|
|
26688
|
+
});
|
|
26663
26689
|
var WebSchema = external_exports.strictObject({
|
|
26664
26690
|
enabled: external_exports.boolean().describe(
|
|
26665
26691
|
"(Beta) The web UI: your sandbox sessions in a browser on this PC (http://127.0.0.1:<port>, this PC only). On: every sandbox start also starts it in the background (it keeps running until sandbox web stop or a restart of the PC). Off: only sandbox web start runs it. Open it signed in with sandbox web open."
|
|
26666
26692
|
),
|
|
26667
26693
|
port: external_exports.number().int().min(1024).max(65535).describe("Port of the web UI on 127.0.0.1 (this PC only). Pick another when it is taken."),
|
|
26668
|
-
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts.")
|
|
26694
|
+
openOnLaunch: external_exports.boolean().describe("(Beta) With the web UI on: open it in your browser, already signed in, every time a sandbox starts."),
|
|
26695
|
+
remote: WebRemoteSchema.describe("(Beta) Access for other Windows users of this PC and for paired devices.")
|
|
26669
26696
|
});
|
|
26670
26697
|
|
|
26671
26698
|
// src/core/schema/vpn.ts
|
|
@@ -26695,6 +26722,36 @@ var GithubSchema = external_exports.strictObject({
|
|
|
26695
26722
|
});
|
|
26696
26723
|
|
|
26697
26724
|
// src/core/schema/config.ts
|
|
26725
|
+
var RESERVED_ENV_PREFIX = /^(AGENT_|GIT_|GH_|GITHUB_|NODE_|NPM_CONFIG_(USERCONFIG|GLOBALCONFIG|PREFIX|SCRIPT_SHELL)|SSL_|CLAUDE|ANTHROPIC_|OPENAI_|CODEX_|GEMINI_|GOOGLE_|COPILOT_|AWS_|AZURE_)/i;
|
|
26726
|
+
var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
|
|
26727
|
+
var RESERVED_ENV_NAMES = new Set(
|
|
26728
|
+
[
|
|
26729
|
+
"PATH",
|
|
26730
|
+
"PATHEXT",
|
|
26731
|
+
"BASH_ENV",
|
|
26732
|
+
"ENV",
|
|
26733
|
+
"COMSPEC",
|
|
26734
|
+
"PSMODULEPATH",
|
|
26735
|
+
"PYTHONPATH",
|
|
26736
|
+
"PYTHONSTARTUP",
|
|
26737
|
+
"PERL5OPT",
|
|
26738
|
+
"RUBYOPT",
|
|
26739
|
+
"JAVA_TOOL_OPTIONS",
|
|
26740
|
+
"LD_PRELOAD",
|
|
26741
|
+
"SYSTEMROOT",
|
|
26742
|
+
"WINDIR",
|
|
26743
|
+
"HOME",
|
|
26744
|
+
"USERPROFILE",
|
|
26745
|
+
"APPDATA",
|
|
26746
|
+
"LOCALAPPDATA",
|
|
26747
|
+
"PROGRAMDATA",
|
|
26748
|
+
"TEMP",
|
|
26749
|
+
"TMP"
|
|
26750
|
+
].map((n) => n.toUpperCase())
|
|
26751
|
+
);
|
|
26752
|
+
function reservedEnvName(name) {
|
|
26753
|
+
return RESERVED_ENV_PREFIX.test(name) || RESERVED_ENV_SUFFIX.test(name) || RESERVED_ENV_NAMES.has(name.toUpperCase());
|
|
26754
|
+
}
|
|
26698
26755
|
var ConfigSchema = external_exports.strictObject({
|
|
26699
26756
|
$schema: SchemaRefSchema.optional(),
|
|
26700
26757
|
defaultHarness: NameSchema.describe("Agent started by `sandbox launch` (sandbox claude always runs claude)."),
|
|
@@ -26761,10 +26818,14 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
26761
26818
|
scripts: StringsSchema.describe(
|
|
26762
26819
|
"Scripts (.ps1 / .mjs / .cmd, relative to this file) run inside the sandbox before the agent."
|
|
26763
26820
|
),
|
|
26764
|
-
// This PC's resources are yours to share: CPU
|
|
26821
|
+
// This PC's resources are yours to share: CPU, paging file (this PC's disk) and the RAM kept for this PC are not a
|
|
26822
|
+
// project's to set.
|
|
26765
26823
|
sandbox: SandboxSchema.omit({
|
|
26766
26824
|
cpuLimit: true,
|
|
26767
26825
|
pageFileMb: true,
|
|
26826
|
+
reserveHostMb: true,
|
|
26827
|
+
maxMemoryPercent: true,
|
|
26828
|
+
maxMemoryMb: true,
|
|
26768
26829
|
engine: true,
|
|
26769
26830
|
agentMemoryHeadroomMb: true,
|
|
26770
26831
|
pressureResponse: true
|
|
@@ -26778,7 +26839,10 @@ var LocalOverridesSchema = external_exports.strictObject({
|
|
|
26778
26839
|
// Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
|
|
26779
26840
|
toolchains: external_exports.strictObject({
|
|
26780
26841
|
...import_lodash.default.mapValues(import_lodash.default.omit(ToolchainsSchema.shape, "environment"), (shape) => shape.partial()),
|
|
26781
|
-
environment: ToolchainsSchema.shape.environment
|
|
26842
|
+
environment: ToolchainsSchema.shape.environment.refine(
|
|
26843
|
+
(env) => !Object.keys(env).some(reservedEnvName),
|
|
26844
|
+
"names the sandbox sets or that carry logins (AGENT_*, GIT_*, PATH, BASH_ENV, NODE_*, proxies, ANTHROPIC_*, CLAUDE_* ...) cannot be set by a project"
|
|
26845
|
+
)
|
|
26782
26846
|
}).partial(),
|
|
26783
26847
|
databases: external_exports.strictObject(import_lodash.default.mapValues(DatabasesSchema.shape, (shape) => shape.partial())).partial(),
|
|
26784
26848
|
attach: AttachSchema.partial(),
|
|
@@ -26848,6 +26912,7 @@ var ProjectSpecSchema = external_exports.strictObject({
|
|
|
26848
26912
|
/** Its hooks were staged in C:\AgentLaunch\projects\<name>\hooks. */
|
|
26849
26913
|
hooks: external_exports.boolean()
|
|
26850
26914
|
});
|
|
26915
|
+
var ShellChoiceSchema = external_exports.enum(["auto", "pwsh", "powershell", "cmd"]);
|
|
26851
26916
|
var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
26852
26917
|
external_exports.strictObject({
|
|
26853
26918
|
kind: external_exports.literal("session"),
|
|
@@ -26857,8 +26922,12 @@ var GuestRequestSchema = external_exports.discriminatedUnion("kind", [
|
|
|
26857
26922
|
cwd: Text(),
|
|
26858
26923
|
project: ProjectSpecSchema.optional(),
|
|
26859
26924
|
harnessSpec: HarnessSchema.optional(),
|
|
26925
|
+
/** A plain shell instead of an agent (harness is then "shell" and args are ignored). */
|
|
26926
|
+
shell: ShellChoiceSchema.optional(),
|
|
26860
26927
|
createdAt: TimeSchema
|
|
26861
26928
|
}),
|
|
26929
|
+
/** End a running session (an agent or a shell) and everything it started. */
|
|
26930
|
+
external_exports.strictObject({ kind: external_exports.literal("close"), id: IdSchema, session: IdSchema, createdAt: TimeSchema }),
|
|
26862
26931
|
/** Provision a registered project without starting an agent (sandbox start / exec / db from its folder). */
|
|
26863
26932
|
external_exports.strictObject({ kind: external_exports.literal("project"), id: IdSchema, project: ProjectSpecSchema, createdAt: TimeSchema }),
|
|
26864
26933
|
external_exports.strictObject({
|
|
@@ -26924,6 +26993,8 @@ var SessionStatusSchema = external_exports.object({
|
|
|
26924
26993
|
id: IdSchema,
|
|
26925
26994
|
launchId: Text(64),
|
|
26926
26995
|
harness: Text(64),
|
|
26996
|
+
/** A plain shell (harness is then the shell: pwsh, powershell or cmd); absent: an agent. */
|
|
26997
|
+
kind: external_exports.enum(["agent", "shell"]).optional(),
|
|
26927
26998
|
cwd: Text(),
|
|
26928
26999
|
/** The project it runs in: its hub name, or the launch folder's for the first project. */
|
|
26929
27000
|
project: Text(64).optional(),
|