windows-development-sandbox 1.18.3 → 1.20.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 -1
- package/config.example.json +9 -0
- package/dist/app/features/accounts/accounts.def.d.ts +4 -0
- package/dist/app/features/accounts/accounts.run.d.ts +1 -0
- package/dist/app/features/accounts/services.def.d.ts +3 -0
- package/dist/app/features/accounts/services.def.js +4 -2
- package/dist/app/features/accounts/services.run.d.ts +1 -0
- package/dist/app/features/accounts/services.run.js +3 -1
- package/dist/app/features/agent-commands/agent-commands.def.d.ts +37 -0
- package/dist/app/features/agent-commands/agent-commands.def.js +91 -0
- package/dist/app/features/agent-commands/agent-commands.run.d.ts +28 -0
- package/dist/app/features/agent-commands/agent-commands.run.js +35 -0
- package/dist/app/features/agent-commands/index.d.ts +2 -0
- package/dist/app/features/agent-commands/index.js +3 -0
- package/dist/app/features/agents/agents.def.d.ts +14 -1
- package/dist/app/features/agents/agents.def.js +61 -6
- package/dist/app/features/agents/agents.run.d.ts +1 -0
- package/dist/app/features/agents/agents.run.js +8 -1
- package/dist/app/features/agents/claude-env.def.d.ts +36 -0
- package/dist/app/features/agents/claude-env.def.js +305 -0
- package/dist/app/features/agents/env.def.d.ts +163 -0
- package/dist/app/features/agents/env.def.js +184 -0
- package/dist/app/features/agents/env.run.d.ts +43 -0
- package/dist/app/features/agents/env.run.js +86 -0
- package/dist/app/features/agents/index.d.ts +2 -0
- package/dist/app/features/agents/index.js +2 -0
- package/dist/app/features/defs.d.ts +150 -1
- package/dist/app/features/defs.js +11 -1
- package/dist/app/features/sandbox/sandbox.def.d.ts +22 -0
- package/dist/app/features/sandbox/sandbox.def.js +41 -14
- package/dist/app/features/sandbox/sandbox.run.d.ts +20 -0
- package/dist/app/features/sandbox/sandbox.run.js +41 -1
- package/dist/app/features/settings/categories.def.js +5 -2
- package/dist/app/features/settings/settings-rows.js +5 -1
- package/dist/app/features/settings/settings.run.d.ts +8 -0
- package/dist/app/features/settings/settings.run.js +9 -0
- package/dist/app/features/updates/updates.run.js +12 -2
- package/dist/app/features/vpn/vpn.def.d.ts +33 -0
- package/dist/app/features/vpn/vpn.def.js +30 -6
- package/dist/app/features/vpn/vpn.run.d.ts +11 -0
- package/dist/app/features/vpn/vpn.run.js +4 -1
- package/dist/app/kernel/rpc.d.ts +1 -1
- package/dist/app/registry.js +9 -2
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-M6XUQK53.js → chunk-2IMT7ZCT.js} +1 -1
- package/dist/bin/{chunk-3G53NNZP.js → chunk-DK4NTBUF.js} +69 -26
- package/dist/bin/{chunk-FQWYCF6L.js → chunk-Z2P5OGKL.js} +6427 -5259
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/agent-commands.d.ts +0 -1
- package/dist/cli/commands/agent-commands.js +2 -32
- package/dist/cli/commands/index.d.ts +1 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/launch/launch.js +6 -3
- package/dist/cli/commands/launch/leftover-offer.d.ts +3 -0
- package/dist/cli/commands/launch/leftover-offer.js +26 -0
- package/dist/cli/commands/registry.js +3 -2
- package/dist/cli/commands/repair/checks/sandbox.js +29 -2
- package/dist/cli/manifest.d.ts +1 -1
- package/dist/cli/manifest.js +1 -1
- package/dist/cli/menu/menu.d.ts +1 -1
- package/dist/cli/menu/menu.js +3 -3
- package/dist/cli/menu/pages/agents-page.d.ts +7 -2
- package/dist/cli/menu/pages/agents-page.js +67 -11
- package/dist/cli/menu/pages/maintenance-page.js +1 -2
- package/dist/cli/menu/pages/settings-page.js +1 -0
- package/dist/cli/menu/views.js +2 -1
- package/dist/cli/services/agent-commands-service.d.ts +2 -0
- package/dist/cli/services/agent-commands-service.js +13 -7
- package/dist/cli/services/vpn-service.d.ts +5 -1
- package/dist/cli/services/vpn-service.js +6 -1
- package/dist/cli/tui/dialogs/text-screen.js +2 -1
- package/dist/cli/tui/terminal.js +3 -1
- package/dist/core/config/defaults.js +9 -0
- package/dist/core/detect/toolchains.js +9 -0
- package/dist/core/disk.d.ts +1 -5
- package/dist/core/disk.js +1 -16
- package/dist/core/schema/codex.d.ts +29 -0
- package/dist/core/schema/codex.js +24 -0
- package/dist/core/schema/config.d.ts +46 -14
- package/dist/core/schema/config.js +2 -0
- package/dist/core/schema/index.d.ts +1 -0
- package/dist/core/schema/index.js +1 -0
- package/dist/core/schema/toolchains.d.ts +4 -0
- package/dist/core/schema/toolchains.js +3 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/guest/accounts/apply.d.ts +6 -6
- package/dist/guest/agents/agent-files.d.ts +26 -6
- package/dist/guest/agents/agent-files.js +84 -14
- package/dist/guest/agents/local-state.d.ts +5 -2
- package/dist/guest/agents/local-state.js +24 -10
- package/dist/guest/boot/boot.js +10 -5
- package/dist/guest/daemon/heartbeat.d.ts +7 -2
- package/dist/guest/daemon/heartbeat.js +34 -15
- package/dist/guest/daemon/idle-shutdown.js +2 -0
- package/dist/guest/daemon/sessions.js +2 -1
- package/dist/guest/entries/daemon.js +1 -1
- package/dist/guest/toolchains/installers.d.ts +6 -10
- package/dist/guest/toolchains/installers.js +4 -12
- package/dist/guest/toolchains/native/autosdk.d.ts +3 -0
- package/dist/guest/toolchains/native/autosdk.js +50 -0
- package/dist/guest/toolchains/native/unreal.js +3 -0
- package/dist/guest/toolchains/native/vcruntime.d.ts +0 -3
- package/dist/guest/toolchains/native/vcruntime.js +2 -5
- package/dist/guest/toolchains/native/visual-studio.d.ts +2 -2
- package/dist/guest/toolchains/native/visual-studio.js +100 -16
- package/dist/guest/toolchains/native/vs-request.d.ts +21 -0
- package/dist/guest/toolchains/native/vs-request.js +60 -1
- package/dist/guest/toolchains/not-yet.d.ts +1 -1
- package/dist/guest/toolchains/not-yet.js +3 -3
- package/dist/guest/toolchains/persisted.js +2 -7
- package/dist/guest/toolchains/plan.js +9 -4
- package/dist/guest/toolchains/signature.d.ts +5 -0
- package/dist/guest/toolchains/signature.js +22 -0
- package/dist/guest/vpn/tunnel.d.ts +3 -0
- package/dist/guest/vpn/tunnel.js +10 -12
- package/dist/guest/vpn/vpn.js +6 -1
- package/dist/host/accounts/leases.d.ts +3 -3
- package/dist/host/accounts/source.js +1 -1
- package/dist/host/agent-commands/commands.js +1 -1
- package/dist/host/agent-commands/forwarders.d.ts +14 -2
- package/dist/host/agent-commands/forwarders.js +31 -13
- package/dist/host/agent-commands/reconcile.d.ts +2 -1
- package/dist/host/agent-commands/reconcile.js +7 -8
- package/dist/host/agent-commands/user-path.d.ts +5 -0
- package/dist/host/agent-commands/user-path.js +13 -0
- package/dist/host/health/index.d.ts +1 -0
- package/dist/host/health/index.js +1 -0
- package/dist/host/health/leftovers.d.ts +3 -0
- package/dist/host/health/leftovers.js +40 -0
- package/dist/host/health/stale.d.ts +48 -0
- package/dist/host/health/stale.js +40 -0
- package/dist/host/launch/session-file.js +1 -0
- package/dist/host/life/crash-report.d.ts +1 -1
- package/dist/host/life/crash-report.js +5 -3
- package/dist/host/life/last-end.d.ts +7 -0
- package/dist/host/life/last-end.js +21 -0
- package/dist/host/life/life-record.d.ts +3 -0
- package/dist/host/life/life-record.js +2 -2
- package/dist/host/life/stop-events.d.ts +3 -1
- package/dist/host/life/stop-events.js +59 -0
- package/dist/host/life/stop-warning.js +3 -0
- package/dist/host/profile/accounts.js +2 -4
- package/dist/host/profile/claude-login.d.ts +3 -2
- package/dist/host/profile/claude-login.js +3 -13
- package/dist/host/sandbox/cleanup.d.ts +37 -0
- package/dist/host/sandbox/cleanup.js +82 -0
- package/dist/host/sandbox/index.d.ts +1 -0
- package/dist/host/sandbox/index.js +1 -0
- package/dist/host/sandbox/platform.js +1 -1
- package/dist/host/vpn/index.d.ts +2 -1
- package/dist/host/vpn/index.js +2 -1
- package/dist/host/vpn/last.d.ts +28 -0
- package/dist/host/vpn/last.js +43 -0
- package/dist/host/vpn/login.d.ts +3 -1
- package/dist/host/vpn/login.js +17 -3
- package/dist/runtime/agent.js +31 -1
- package/dist/runtime/daemon.js +1419 -1197
- package/dist/runtime/db.js +33 -1
- package/dist/runtime/main.js +1311 -1037
- package/dist/runtime/netproxy.js +25 -1
- package/dist/runtime/tool.js +241 -209
- package/dist/runtime/view.js +25 -1
- package/dist/selftest/agent-commands.test.js +45 -7
- package/dist/selftest/agent-paths.test.js +1 -12
- package/dist/selftest/agent-settings.test.d.ts +2 -0
- package/dist/selftest/agent-settings.test.js +94 -0
- package/dist/selftest/app.test.js +2 -1
- package/dist/selftest/cli.test.js +4 -0
- package/dist/selftest/heavy-load.test.js +119 -2
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/menu.test.js +5 -3
- package/dist/selftest/pc-installs.test.js +5 -0
- package/dist/selftest/persisted-installs.test.js +69 -0
- package/dist/selftest/service-accounts.test.js +1 -1
- package/dist/selftest/support/engine-golden.js +1 -1
- package/dist/selftest/support/fixtures.d.ts +9 -0
- package/dist/selftest/vpn.test.js +51 -3
- package/dist/shared/accounts/anthropic.d.ts +5 -0
- package/dist/shared/accounts/anthropic.js +15 -0
- package/dist/shared/accounts/index.d.ts +1 -0
- package/dist/shared/accounts/index.js +1 -0
- package/dist/shared/channel/schema.d.ts +20 -0
- package/dist/shared/channel/schema.js +4 -1
- package/dist/shared/io/disk.d.ts +9 -0
- package/dist/shared/io/disk.js +19 -0
- package/dist/shared/io/index.d.ts +1 -0
- package/dist/shared/io/index.js +1 -0
- package/dist/shared/readiness.d.ts +9 -0
- package/dist/shared/readiness.js +11 -0
- package/dist/shared/signer.d.ts +24 -0
- package/dist/shared/signer.js +15 -0
- package/dist/shared/system.d.ts +1 -0
- package/dist/shared/system.js +1 -0
- package/dist/shared/vpn/failure.d.ts +10 -0
- package/dist/shared/vpn/failure.js +28 -0
- package/dist/sync/watcher.js +11 -4
- package/dist/web/index.html +1 -1
- package/dist/web/{main-6XYO5FZC.js → main-OUUQYNK4.js} +75 -74
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/Program.cs +19 -2
- package/native/csharp/HostTools/UserPath.cs +17 -0
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/edit-user-path.ps1 +6 -2
- package/native/scripts/stop-events.ps1 +7 -3
- package/package.json +1 -1
- package/sandbox/scripts/get-signature.ps1 +13 -0
- package/schemas/config.schema.json +64 -0
- package/schemas/sandbox-config.schema.json +8 -0
- package/sandbox/scripts/get-signer.ps1 +0 -8
|
@@ -306,7 +306,7 @@ export const serviceAccountsSuite = {
|
|
|
306
306
|
});
|
|
307
307
|
t.check(empty.message.includes('No saved'), 'no launcher sign-in on this PC: it says so');
|
|
308
308
|
await t.throws(() => loginServiceFlow(fakeIO([]).io, 'epic', { store: flowStore, share: true }), 'nothing to type', 'Epic has nothing to type');
|
|
309
|
-
await t.throws(() => loginServiceFlow(fakeIO([CFX, '']).io, 'cfx', { store: flowStore, share: true }), '
|
|
309
|
+
await t.throws(() => loginServiceFlow(fakeIO([CFX, '']).io, 'cfx', { store: flowStore, share: true }), 'comes in a later version', 'a kind whose host side is not written yet says so');
|
|
310
310
|
t.check(Boolean(HOST_SOURCES.epic.fromPc) && !HOST_SOURCES.epic.fromAnswers, 'Epic comes from this PC (or a sandbox), never typed');
|
|
311
311
|
const gone = await logoutServiceFlow(fakeIO([true]).io, 'epic', t.dir('svc-flow-root'), flowStore, new AccountLeases(t.dir('svc-flow-root')));
|
|
312
312
|
t.check(gone.deleted && flowStore.state('epic') === 'none', 'forgetting deletes it after asking');
|
|
@@ -67,7 +67,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
67
67
|
'sandbox/scripts/enable-vt-input.ps1',
|
|
68
68
|
'sandbox/scripts/expand-archive.ps1',
|
|
69
69
|
'sandbox/scripts/get-gpu-names.ps1',
|
|
70
|
-
'sandbox/scripts/get-
|
|
70
|
+
'sandbox/scripts/get-signature.ps1',
|
|
71
71
|
'sandbox/scripts/install-shell-path.ps1',
|
|
72
72
|
'sandbox/scripts/link-profile-dirs.ps1',
|
|
73
73
|
'sandbox/scripts/new-shortcuts.ps1',
|
|
@@ -194,6 +194,7 @@ export declare function createFixtures(t: Harness): {
|
|
|
194
194
|
acceptLicense: boolean;
|
|
195
195
|
components: string[];
|
|
196
196
|
keepInstallFiles: boolean;
|
|
197
|
+
visualStudio: "full" | "autosdk";
|
|
197
198
|
persistEngines: boolean;
|
|
198
199
|
derivedDataCache: "persisted" | "local";
|
|
199
200
|
};
|
|
@@ -429,6 +430,14 @@ export declare function createFixtures(t: Harness): {
|
|
|
429
430
|
resumeArguments?: string[] | undefined;
|
|
430
431
|
resumeOneArguments?: string[] | undefined;
|
|
431
432
|
}>;
|
|
433
|
+
codex: {
|
|
434
|
+
sandboxMode: "danger-full-access" | "workspace-write" | "read-only";
|
|
435
|
+
approvalPolicy: "never" | "on-request" | "on-failure" | "untrusted";
|
|
436
|
+
model: string;
|
|
437
|
+
reasoningEffort: "default" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
438
|
+
windowsSandbox: "off" | "unelevated" | "elevated";
|
|
439
|
+
mcpLoginsInFile: boolean;
|
|
440
|
+
};
|
|
432
441
|
hooks: {
|
|
433
442
|
failFast: boolean;
|
|
434
443
|
};
|
|
@@ -14,7 +14,8 @@ import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
|
|
|
14
14
|
import { SecretStore } from '../host/secrets/index.js';
|
|
15
15
|
import { clearSecrets } from '../host/secrets/index.js';
|
|
16
16
|
import { generateSandbox } from '../host/launch/index.js';
|
|
17
|
-
import {
|
|
17
|
+
import { classifyFailure, FAILURE_TEXT } from '../shared/vpn/failure.js';
|
|
18
|
+
import { findCountry, loginProblem, loginWarning, nordProfile, prepareVpn, wireguardFileProblem, recommendedServer, VpnLogin, vpnLine, vpnNotes, } from '../host/vpn/index.js';
|
|
18
19
|
import { RunDir } from '../shared/channel/index.js';
|
|
19
20
|
import { OPENVPN_MSI, WIREGUARD_MSI } from '../shared/vpn/download.js';
|
|
20
21
|
import { cleanServers, ipv4Except, killSwitchCommands, removeKillSwitchCommands, tunnelAddressFromLog, tunnelCommands, } from '../shared/vpn/kill-switch.js';
|
|
@@ -221,6 +222,52 @@ export const vpnSuite = {
|
|
|
221
222
|
t.check(login.state('nordvpn') === 'unreadable', 'a login this Windows user cannot open is reported as unreadable');
|
|
222
223
|
login.remove('nordvpn');
|
|
223
224
|
t.check(login.state('nordvpn') === 'none', 'logout deletes it');
|
|
225
|
+
t.section('NordVPN login checks and why a tunnel failed');
|
|
226
|
+
const sampleStatus = () => ({
|
|
227
|
+
enabled: true,
|
|
228
|
+
provider: 'nordvpn',
|
|
229
|
+
protocol: 'udp',
|
|
230
|
+
country: '',
|
|
231
|
+
killSwitch: true,
|
|
232
|
+
login: 'saved',
|
|
233
|
+
loginSavedAt: '2026-11-02T19:00:00.000Z',
|
|
234
|
+
running: null,
|
|
235
|
+
lastFailure: null,
|
|
236
|
+
});
|
|
237
|
+
t.check((loginProblem({ username: 'me@example.com', password: 'x' }, 'nordvpn') ?? '').includes('service credentials') &&
|
|
238
|
+
loginProblem({ username: 'me@example.com', password: 'x' }, 'openvpn-file') === null &&
|
|
239
|
+
(loginProblem({ username: 'abc', password: 'p[200~w' }, 'nordvpn') ?? '').includes('paste'), 'an e-mail is refused for NordVPN only (it needs the service credentials); hidden paste markers are refused');
|
|
240
|
+
login.save('nordvpn', { username: ' ' + USERNAME + ' ', password: ' ' + PASSWORD + ' ' });
|
|
241
|
+
t.equal(login.read('nordvpn'), { username: USERNAME, password: PASSWORD }, 'spaces around the user name and the password are not saved (a pasted login often has them)');
|
|
242
|
+
t.check(loginWarning({ username: 'x', password: 'y' }, 'openvpn-file') === null &&
|
|
243
|
+
loginWarning({ username: 'a'.repeat(24), password: 'B'.repeat(24) }, 'nordvpn') === null &&
|
|
244
|
+
loginWarning({ username: 'a b', password: 'c' }, 'nordvpn') !== null, 'a NordVPN login that does not look like service credentials gets a hint, not a refusal');
|
|
245
|
+
login.remove('nordvpn');
|
|
246
|
+
const failed = (log) => classifyFailure(log)?.code;
|
|
247
|
+
t.equal([
|
|
248
|
+
failed('AUTH: Received control message: AUTH_FAILED'),
|
|
249
|
+
failed('TLS Error: TLS key negotiation failed to occur within 60 seconds'),
|
|
250
|
+
failed('Could not create Wintun interface'),
|
|
251
|
+
failed('Exiting due to fatal error'),
|
|
252
|
+
failed('all fine'),
|
|
253
|
+
], ['auth', 'unreachable', 'driver', 'other', undefined], 'the OpenVPN log is turned into a stable code the host acts on');
|
|
254
|
+
const refusedPage = vpnPage({
|
|
255
|
+
...sampleStatus(),
|
|
256
|
+
lastFailure: { code: 'auth', text: FAILURE_TEXT.auth, at: '2026-11-02T19:15:21.000Z' },
|
|
257
|
+
});
|
|
258
|
+
const refusedText = pageLines(refusedPage).join('\n');
|
|
259
|
+
const refusedOps = pageActions(refusedPage).flatMap((a) => (a.kind === 'operation' ? [[a.op, a.label]] : []));
|
|
260
|
+
t.check(refusedText.includes('Last attempt') &&
|
|
261
|
+
refusedText.includes('login refused') &&
|
|
262
|
+
refusedText.includes('service credentials') &&
|
|
263
|
+
refusedOps.some(([op, label]) => op === 'vpn.login' && label === 'Save the login again…') &&
|
|
264
|
+
!/sandbox (vpn|config|agent)/.test(refusedText), 'after a refused login the VPN page says so and offers to save the login again, without naming a command');
|
|
265
|
+
const unreachablePage = vpnPage({
|
|
266
|
+
...sampleStatus(),
|
|
267
|
+
lastFailure: { code: 'unreachable', text: FAILURE_TEXT.unreachable, at: '2026-11-02T19:15:21.000Z' },
|
|
268
|
+
});
|
|
269
|
+
t.check(pageActions(unreachablePage).some((a) => a.kind === 'operation' && a.input.key === 'vpn.protocol') &&
|
|
270
|
+
!pageActions(vpnPage({ ...sampleStatus(), protocol: 'tcp', lastFailure: { code: 'unreachable', text: 'x', at: 'y' } })).some((a) => a.kind === 'operation' && a.input.key === 'vpn.protocol'), 'a server that could not be reached offers TCP instead of UDP (once)');
|
|
224
271
|
t.section('settings: what a project may and may not do');
|
|
225
272
|
t.check(DEFAULTS.vpn.enabled === false && DEFAULTS.vpn.provider === 'nordvpn' && DEFAULTS.vpn.killSwitch === true, 'the VPN is off by default, NordVPN, with the kill switch on');
|
|
226
273
|
t.check(ConfigSchema.safeParse(DEFAULTS).success, 'the defaults are valid');
|
|
@@ -458,11 +505,12 @@ export const vpnSuite = {
|
|
|
458
505
|
login: 'none',
|
|
459
506
|
loginSavedAt: null,
|
|
460
507
|
running: null,
|
|
508
|
+
lastFailure: null,
|
|
461
509
|
};
|
|
462
510
|
const lines = pageLines(vpnPage(sample)).join('\n');
|
|
463
|
-
t.check(
|
|
511
|
+
t.check(!/sandbox (vpn|config|agent)/.test(lines) &&
|
|
464
512
|
VPN_WARNINGS.every((w) => lines.includes(w)) &&
|
|
465
|
-
!lines.includes('Running now'), 'vpn status page: the facts
|
|
513
|
+
!lines.includes('Running now'), 'vpn status page: the facts and the warnings, no command to type', lines);
|
|
466
514
|
t.equal(pageActions(vpnPage({ ...sample, provider: 'wireguard-file' })).map((a) => a.kind === 'operation' ? a.op : a.kind), ['settings.set'], 'vpn: wireguard needs no login and offers only turning the VPN on or off');
|
|
467
515
|
t.equal(pageActions(vpnPage({ ...sample, login: 'saved', loginSavedAt: '2026-10-14T09:30:00.000Z' })).map((a) => a.kind === 'operation' ? a.op : a.kind), ['settings.set', 'vpn.login', 'vpn.logout'], 'a saved login offers turning the VPN on or off, saving the login again and deleting it');
|
|
468
516
|
t.equal(vpnLoginRowOf(sample).hint, 'not saved', 'the Settings row says not saved when no login is saved');
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const ANTHROPIC_KEY_PATTERN: RegExp;
|
|
2
|
+
/** The only part of a key ever shown: sk-ant-...abcd. */
|
|
3
|
+
export declare const ANTHROPIC_KEY_HINT: RegExp;
|
|
4
|
+
/** What is wrong with a typed or found API key, or null when it has the right shape. Never quotes the key. */
|
|
5
|
+
export declare function anthropicKeyProblem(key: string): string | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// The one shape of an Anthropic API key, for the host and the web (no node imports). A key from the Claude Console
|
|
2
|
+
// starts with sk-ant-api, a key from a user workspace with sk-ant-usr.
|
|
3
|
+
export const ANTHROPIC_KEY_PATTERN = /^sk-ant-(api|usr)\d{2}-[A-Za-z0-9_-]{20,400}$/;
|
|
4
|
+
/** The only part of a key ever shown: sk-ant-...abcd. */
|
|
5
|
+
export const ANTHROPIC_KEY_HINT = /^sk-ant-\.\.\.[A-Za-z0-9_-]{4}$/;
|
|
6
|
+
/** What is wrong with a typed or found API key, or null when it has the right shape. Never quotes the key. */
|
|
7
|
+
export function anthropicKeyProblem(key) {
|
|
8
|
+
if (ANTHROPIC_KEY_PATTERN.test(key))
|
|
9
|
+
return null;
|
|
10
|
+
if (key.startsWith('sk-ant-oat'))
|
|
11
|
+
return 'That is a Claude subscription token (from claude setup-token), not an API key. Save the subscription login instead.';
|
|
12
|
+
if (key.startsWith('sk-ant-admin'))
|
|
13
|
+
return 'That is an Admin key: it cannot run Claude. Make an API key instead.';
|
|
14
|
+
return 'An Anthropic API key starts with sk-ant-api or sk-ant-usr and has only letters, digits, - and _ (copy it from the Claude Console).';
|
|
15
|
+
}
|
|
@@ -93,6 +93,10 @@ export declare const ProjectSpecSchema: z.ZodObject<{
|
|
|
93
93
|
acceptLicense: z.ZodBoolean;
|
|
94
94
|
components: z.ZodArray<z.ZodString>;
|
|
95
95
|
keepInstallFiles: z.ZodBoolean;
|
|
96
|
+
visualStudio: z.ZodEnum<{
|
|
97
|
+
full: "full";
|
|
98
|
+
autosdk: "autosdk";
|
|
99
|
+
}>;
|
|
96
100
|
persistEngines: z.ZodBoolean;
|
|
97
101
|
derivedDataCache: z.ZodEnum<{
|
|
98
102
|
persisted: "persisted";
|
|
@@ -326,6 +330,10 @@ export declare const GuestRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
326
330
|
acceptLicense: z.ZodBoolean;
|
|
327
331
|
components: z.ZodArray<z.ZodString>;
|
|
328
332
|
keepInstallFiles: z.ZodBoolean;
|
|
333
|
+
visualStudio: z.ZodEnum<{
|
|
334
|
+
full: "full";
|
|
335
|
+
autosdk: "autosdk";
|
|
336
|
+
}>;
|
|
329
337
|
persistEngines: z.ZodBoolean;
|
|
330
338
|
derivedDataCache: z.ZodEnum<{
|
|
331
339
|
persisted: "persisted";
|
|
@@ -579,6 +587,10 @@ export declare const GuestRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
579
587
|
acceptLicense: z.ZodBoolean;
|
|
580
588
|
components: z.ZodArray<z.ZodString>;
|
|
581
589
|
keepInstallFiles: z.ZodBoolean;
|
|
590
|
+
visualStudio: z.ZodEnum<{
|
|
591
|
+
full: "full";
|
|
592
|
+
autosdk: "autosdk";
|
|
593
|
+
}>;
|
|
582
594
|
persistEngines: z.ZodBoolean;
|
|
583
595
|
derivedDataCache: z.ZodEnum<{
|
|
584
596
|
persisted: "persisted";
|
|
@@ -828,6 +840,7 @@ export declare const HeartbeatSchema: z.ZodObject<{
|
|
|
828
840
|
phase: z.ZodEnum<{
|
|
829
841
|
ready: "ready";
|
|
830
842
|
working: "working";
|
|
843
|
+
ending: "ending";
|
|
831
844
|
}>;
|
|
832
845
|
}, z.core.$strip>;
|
|
833
846
|
export type Heartbeat = z.output<typeof HeartbeatSchema>;
|
|
@@ -897,6 +910,13 @@ export declare const VpnStatusSchema: z.ZodObject<{
|
|
|
897
910
|
up: "up";
|
|
898
911
|
}>;
|
|
899
912
|
message: z.ZodString;
|
|
913
|
+
code: z.ZodOptional<z.ZodEnum<{
|
|
914
|
+
auth: "auth";
|
|
915
|
+
unreachable: "unreachable";
|
|
916
|
+
driver: "driver";
|
|
917
|
+
timeout: "timeout";
|
|
918
|
+
other: "other";
|
|
919
|
+
}>>;
|
|
900
920
|
killSwitch: z.ZodBoolean;
|
|
901
921
|
killSwitchHeld: z.ZodOptional<z.ZodBoolean>;
|
|
902
922
|
server: z.ZodOptional<z.ZodString>;
|
|
@@ -150,7 +150,8 @@ export const HeartbeatSchema = z.object({
|
|
|
150
150
|
topProcesses: z.array(z.object({ name: Text(64), privateMb: z.number().int().min(0).max(1e7) })).max(5),
|
|
151
151
|
/** The disk queue this PC last reported while asking the sandbox to slow down. */
|
|
152
152
|
diskQueueReported: z.number().min(0).max(100000).optional(),
|
|
153
|
-
|
|
153
|
+
/** "ending": the sandbox is shutting down in order (idle shutdown, sandbox stop, Windows shutting down). */
|
|
154
|
+
phase: z.enum(['ready', 'working', 'ending']),
|
|
154
155
|
});
|
|
155
156
|
/** C:\AgentData\run\responses\<id>.json: the guest's reply. */
|
|
156
157
|
export const GuestResponseSchema = z.object({
|
|
@@ -197,6 +198,8 @@ export const ProjectStatusSchema = z.object({
|
|
|
197
198
|
export const VpnStatusSchema = z.object({
|
|
198
199
|
state: z.enum(['off', 'starting', 'up', 'failed']),
|
|
199
200
|
message: Text(2000),
|
|
201
|
+
/** Why it failed, when it did: `auth` (login refused), `unreachable`, `driver`, `timeout` or `other`. */
|
|
202
|
+
code: z.enum(['auth', 'unreachable', 'driver', 'timeout', 'other']).optional(),
|
|
200
203
|
killSwitch: z.boolean(),
|
|
201
204
|
/** A direct connection was tried after the kill switch was applied: false = it still got out. */
|
|
202
205
|
killSwitchHeld: z.boolean().optional(),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type StatFs = (dir: string) => {
|
|
2
|
+
bavail: number;
|
|
3
|
+
bsize: number;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Free bytes for this user on the drive that holds `dir` (which may not exist yet: the nearest existing parent is
|
|
7
|
+
* asked); null when it cannot be told. `statfs` is injectable for tests.
|
|
8
|
+
*/
|
|
9
|
+
export declare function freeBytesAt(dir: string, statfs?: StatFs): number | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Free room on a disk: one implementation for the PC (core/disk.ts) and the guest (toolchains/persisted.ts).
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Free bytes for this user on the drive that holds `dir` (which may not exist yet: the nearest existing parent is
|
|
6
|
+
* asked); null when it cannot be told. `statfs` is injectable for tests.
|
|
7
|
+
*/
|
|
8
|
+
export function freeBytesAt(dir, statfs = fs.statfsSync) {
|
|
9
|
+
let at = path.resolve(dir);
|
|
10
|
+
while (!fs.existsSync(at) && path.dirname(at) !== at)
|
|
11
|
+
at = path.dirname(at);
|
|
12
|
+
try {
|
|
13
|
+
const s = statfs(at);
|
|
14
|
+
return s.bavail * s.bsize;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/shared/io/index.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const NOT_READY: {
|
|
2
|
+
/** Toolchains whose step is a stub: their settings rows are hidden and "auto" never turns them on. */
|
|
3
|
+
readonly toolchains: readonly ["unity", "godot", "android", "flutter", "dart", "reactNative", "fivem"];
|
|
4
|
+
/** Service sign-ins whose host or guest side is a stub: the menu and the web do not list them. */
|
|
5
|
+
readonly kinds: readonly ["unity", "keystore", "play", "expo", "appstore", "cfx"];
|
|
6
|
+
readonly unreal: readonly ["engines"];
|
|
7
|
+
};
|
|
8
|
+
export declare const isToolchainReady: (name: string) => boolean;
|
|
9
|
+
export declare const isKindReady: (kind: string) => boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// What is not written yet, so no menu, web page or "auto" decision offers it. Each package that lands deletes its own
|
|
2
|
+
// entries here (the end of round 2 leaves it empty). Pure.
|
|
3
|
+
export const NOT_READY = {
|
|
4
|
+
/** Toolchains whose step is a stub: their settings rows are hidden and "auto" never turns them on. */
|
|
5
|
+
toolchains: ['unity', 'godot', 'android', 'flutter', 'dart', 'reactNative', 'fivem'],
|
|
6
|
+
/** Service sign-ins whose host or guest side is a stub: the menu and the web do not list them. */
|
|
7
|
+
kinds: ['unity', 'keystore', 'play', 'expo', 'appstore', 'cfx'],
|
|
8
|
+
unreal: ['engines'],
|
|
9
|
+
};
|
|
10
|
+
export const isToolchainReady = (name) => !NOT_READY.toolchains.includes(name);
|
|
11
|
+
export const isKindReady = (kind) => !NOT_READY.kinds.includes(kind);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Patterns (regex sources, case-insensitive) the signature's subject and issuer must match; every one of them. */
|
|
2
|
+
export interface AppSigner {
|
|
3
|
+
readonly subject: readonly string[];
|
|
4
|
+
readonly issuer?: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
/** A valid Authenticode signature's certificate names (an invalid or missing one is reported as null). */
|
|
7
|
+
export interface Signature {
|
|
8
|
+
readonly subject: string;
|
|
9
|
+
readonly issuer: string;
|
|
10
|
+
}
|
|
11
|
+
/** Whether a signature is the expected signer's: every subject and issuer pattern matches. */
|
|
12
|
+
export declare function signerMatches(signature: Signature | null, signer: AppSigner): boolean;
|
|
13
|
+
/** Who signs the installers the toolchains run (the patterns the signature's subject must match). */
|
|
14
|
+
export declare const SIGNERS: {
|
|
15
|
+
readonly microsoft: {
|
|
16
|
+
readonly subject: readonly ["^CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US$"];
|
|
17
|
+
};
|
|
18
|
+
readonly valve: {
|
|
19
|
+
readonly subject: readonly ["^CN=\"?Valve(,| Corp)"];
|
|
20
|
+
};
|
|
21
|
+
readonly unity: {
|
|
22
|
+
readonly subject: readonly ["^CN=\"?Unity Technologies"];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Who must sign an app's download (AppEntry.signer), for vendors that publish no checksum: the Authenticode subject
|
|
2
|
+
// and issuer as patterns, checked against what sandbox\scripts\get-signature.ps1 reports for a valid signature. Pure.
|
|
3
|
+
/** Whether a signature is the expected signer's: every subject and issuer pattern matches. */
|
|
4
|
+
export function signerMatches(signature, signer) {
|
|
5
|
+
if (!signature || !signature.subject)
|
|
6
|
+
return false;
|
|
7
|
+
const all = (text, patterns) => patterns.every((p) => new RegExp(p, 'i').test(text));
|
|
8
|
+
return all(signature.subject, signer.subject) && all(signature.issuer, signer.issuer ?? []);
|
|
9
|
+
}
|
|
10
|
+
/** Who signs the installers the toolchains run (the patterns the signature's subject must match). */
|
|
11
|
+
export const SIGNERS = {
|
|
12
|
+
microsoft: { subject: ['^CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US$'] },
|
|
13
|
+
valve: { subject: ['^CN="?Valve(,| Corp)'] },
|
|
14
|
+
unity: { subject: ['^CN="?Unity Technologies'] },
|
|
15
|
+
};
|
package/dist/shared/system.d.ts
CHANGED
package/dist/shared/system.js
CHANGED
|
@@ -9,6 +9,7 @@ export const SYSTEM = {
|
|
|
9
9
|
cmd: () => system32('cmd.exe'),
|
|
10
10
|
powershell: () => system32('WindowsPowerShell\\v1.0\\powershell.exe'),
|
|
11
11
|
tasklist: () => system32('tasklist.exe'),
|
|
12
|
+
taskkill: () => system32('taskkill.exe'),
|
|
12
13
|
whoami: () => system32('whoami.exe'),
|
|
13
14
|
icacls: () => system32('icacls.exe'),
|
|
14
15
|
tar: () => system32('tar.exe'),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const VPN_FAILURE_CODES: readonly ["auth", "unreachable", "driver", "timeout", "other"];
|
|
2
|
+
export type VpnFailureCode = (typeof VPN_FAILURE_CODES)[number];
|
|
3
|
+
export interface VpnFailure {
|
|
4
|
+
readonly code: VpnFailureCode;
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
}
|
|
7
|
+
/** What the log says went wrong, or null when it says nothing (yet). */
|
|
8
|
+
export declare function classifyFailure(text: string): VpnFailure | null;
|
|
9
|
+
/** The plain words for a code, for the page ("login refused by NordVPN"). */
|
|
10
|
+
export declare const FAILURE_TEXT: Readonly<Record<VpnFailureCode, string>>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Why an OpenVPN run failed, from its log: a stable code (what the host acts on) and a plain sentence. Pure.
|
|
2
|
+
export const VPN_FAILURE_CODES = ['auth', 'unreachable', 'driver', 'timeout', 'other'];
|
|
3
|
+
/** What the log says went wrong, or null when it says nothing (yet). */
|
|
4
|
+
export function classifyFailure(text) {
|
|
5
|
+
if (/AUTH_FAILED/i.test(text))
|
|
6
|
+
return {
|
|
7
|
+
code: 'auth',
|
|
8
|
+
reason: 'the VPN refused the login (for NordVPN use the service credentials, not your account password)',
|
|
9
|
+
};
|
|
10
|
+
if (/TLS Error: TLS key negotiation failed|TLS handshake failed/i.test(text))
|
|
11
|
+
return {
|
|
12
|
+
code: 'unreachable',
|
|
13
|
+
reason: 'it could not reach the VPN server (the network blocks it, or the server is down)',
|
|
14
|
+
};
|
|
15
|
+
if (/All wintun adapters on this system are in use|Cannot open TUN\/TAP|Could not create Wintun|ovpn-dco.*(error|failed)/i.test(text))
|
|
16
|
+
return { code: 'driver', reason: 'the network driver for the tunnel could not be used' };
|
|
17
|
+
if (/Exiting due to fatal error/i.test(text))
|
|
18
|
+
return { code: 'other', reason: 'OpenVPN stopped with an error' };
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
/** The plain words for a code, for the page ("login refused by NordVPN"). */
|
|
22
|
+
export const FAILURE_TEXT = {
|
|
23
|
+
auth: 'login refused by the VPN',
|
|
24
|
+
unreachable: 'the VPN server could not be reached',
|
|
25
|
+
driver: 'the tunnel driver could not be used',
|
|
26
|
+
timeout: 'the tunnel did not come up in time',
|
|
27
|
+
other: 'the VPN failed',
|
|
28
|
+
};
|
package/dist/sync/watcher.js
CHANGED
|
@@ -13,7 +13,8 @@ import { readForwards, sandboxAddress, startPortForwards } from '../host/network
|
|
|
13
13
|
import { AuditCollector, AuditLog, readAuditSettings } from '../host/audit/index.js';
|
|
14
14
|
import { readMarkedProjects, saveDataSize, watchAndMark } from '../host/guard/index.js';
|
|
15
15
|
import { sizeWithin } from '../core/disk.js';
|
|
16
|
-
import { autoRestartAllowed, causeSince, appendHostMemory, cpuPercentBetween, KeepAwake, LifeRecord, HostMemoryStream, readHostMemoryAsync, readLoadSettings, REQUEST_SECONDS, WATCHDOG_START, watchdogStep, saveCrashReport, SessionLedger, } from '../host/life/index.js';
|
|
16
|
+
import { autoRestartAllowed, causeSince, readHeartbeat, vanishedCause, appendHostMemory, cpuPercentBetween, KeepAwake, LifeRecord, HostMemoryStream, readHostMemoryAsync, readLoadSettings, REQUEST_SECONDS, WATCHDOG_START, watchdogStep, saveCrashReport, SessionLedger, } from '../host/life/index.js';
|
|
17
|
+
import { recordLastVpn } from '../host/vpn/index.js';
|
|
17
18
|
import { SandboxChannel } from '../host/channel/index.js';
|
|
18
19
|
import { ProjectRegistry } from '../host/projects/registry.js';
|
|
19
20
|
import { ContainedStorage, setNameOf, storageOf } from '../host/storage/index.js';
|
|
@@ -67,7 +68,7 @@ export function watcherRunning(dataPath) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
/** The ways the sandbox itself ends badly (the PC going down takes the watcher with it, so it never gets here). */
|
|
70
|
-
const RESTARTABLE = ['sandboxCrash', 'memory'];
|
|
71
|
+
const RESTARTABLE = ['sandboxCrash', 'memory', 'gpu', 'hyperv', 'vanished'];
|
|
71
72
|
/**
|
|
72
73
|
* The background watcher of one data folder: while the sandbox runs it syncs history back every minute, guards the
|
|
73
74
|
* projects' git folders, keeps filtered workspaces in step, forwards ports, marks downloaded files, records the audit
|
|
@@ -276,6 +277,7 @@ export class Watcher {
|
|
|
276
277
|
catch (e) {
|
|
277
278
|
this.note('repo guard failed: ' + errMsg(e));
|
|
278
279
|
}
|
|
280
|
+
recordLastVpn(this.storageRoot, this.dataPath);
|
|
279
281
|
try {
|
|
280
282
|
const s = syncBack(this.dataPath, final);
|
|
281
283
|
if (s.history || final)
|
|
@@ -318,8 +320,13 @@ export class Watcher {
|
|
|
318
320
|
recordEnd() {
|
|
319
321
|
try {
|
|
320
322
|
const last = this.life.read();
|
|
321
|
-
|
|
322
|
-
const
|
|
323
|
+
// Windows' own log first; when it says nothing, a sandbox that was busy and left no word of leaving "vanished".
|
|
324
|
+
const logged = last ? causeSince(last.lastSeen, this.storageRoot) : null;
|
|
325
|
+
const cause = logged ??
|
|
326
|
+
(last
|
|
327
|
+
? vanishedCause(readHeartbeat(this.dataPath), last.started, this.ledger.read()?.closedOnPurpose === true, Date.now())
|
|
328
|
+
: null);
|
|
329
|
+
const report = last && cause ? saveCrashReport(this.storageRoot, this.dataPath, last.lastSeen, cause) : null;
|
|
323
330
|
this.life.end(cause, new Date(), report);
|
|
324
331
|
this.cause = cause;
|
|
325
332
|
// Ended with nothing wrong (its window closed, or it shut itself down idle): on purpose.
|
package/dist/web/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>Sandbox</title>
|
|
7
7
|
<link rel="icon" href="data:," />
|
|
8
8
|
<link rel="stylesheet" href="/main-XT7T6ODM.css" />
|
|
9
|
-
<script type="module" src="/main-
|
|
9
|
+
<script type="module" src="/main-OUUQYNK4.js"></script>
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|