windows-development-sandbox 1.12.0 → 1.13.2
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/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/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 +156 -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-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/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/bin/agent-sandbox.js +7 -1
- package/dist/bin/chunk-IL7BXJF2.js +7 -0
- package/dist/bin/chunk-K4SU3MHJ.js +10432 -0
- package/dist/bin/chunk-VENWSGX2.js +78437 -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/prepare.js +24 -7
- package/dist/cli/commands/launch/running.js +4 -0
- 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/storage/offer.js +1 -1
- 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/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/remote-menu.d.ts +19 -0
- package/dist/cli/menu/remote-menu.js +108 -0
- package/dist/cli/menu/shell.d.ts +2 -0
- package/dist/cli/menu/shell.js +1 -1
- package/dist/cli/menu/views.js +2 -0
- package/dist/cli/operations/handler.js +15 -1
- package/dist/cli/services/accounts-service.d.ts +7 -0
- package/dist/cli/services/accounts-service.js +20 -0
- 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/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/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/package-info.js +1 -1
- 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/sandbox/platform.d.ts +2 -0
- package/dist/host/sandbox/platform.js +19 -4
- 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 -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/layout.test.js +10 -1
- package/dist/selftest/live-daemon.test.js +25 -0
- package/dist/selftest/local-config.test.js +30 -0
- package/dist/selftest/menu-perf.test.js +198 -12
- package/dist/selftest/menu.test.js +4 -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-NBQ3LF4E.js +221 -0
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- 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/bin/claude-host.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/claude-host.ts
|
|
9
|
+
var args = process.argv.slice(2);
|
|
10
|
+
var personal = args.includes("--personal") ? ["--personal"] : [];
|
|
11
|
+
run(["host-claude", ...personal, "--", ...args.filter((a) => a !== "--personal")]);
|
package/dist/bin/claude-sbx.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/claude-sbx.ts
|
|
9
|
+
run(process.argv.slice(2), { harness: "claude" });
|
package/dist/bin/codex-sbx.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/codex-sbx.ts
|
|
9
|
+
run(process.argv.slice(2), { harness: "codex" });
|
package/dist/bin/gemini-sbx.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/gemini-sbx.ts
|
|
9
|
+
run(process.argv.slice(2), { harness: "gemini" });
|
package/dist/bin/sandbox.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import {
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/sandbox.ts
|
|
4
9
|
run(process.argv.slice(2));
|
package/dist/bin/sbx-hide.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/sbx-hide.ts
|
|
9
|
+
run(process.argv.slice(2), { command: "hide" });
|
package/dist/bin/sbx-show.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
run
|
|
5
|
+
} from "./chunk-K4SU3MHJ.js";
|
|
6
|
+
import "./chunk-VENWSGX2.js";
|
|
7
|
+
|
|
8
|
+
// src/bin/sbx-show.ts
|
|
9
|
+
run(process.argv.slice(2), { command: "show" });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
-
import {
|
|
3
|
+
import { AccountsService } from '../../services/index.js';
|
|
4
4
|
import { SandboxChannel } from '../../../host/channel/index.js';
|
|
5
5
|
import { describePrune, hostDataFolder, pruneStorage } from '../../../host/storage/index.js';
|
|
6
6
|
import { RepoGuardStore, readDataSize, saveMarkedProjects } from '../../../host/guard/index.js';
|
|
@@ -73,16 +73,33 @@ async function prepareWorkspaceView(ctx, run) {
|
|
|
73
73
|
'; ' +
|
|
74
74
|
plan.options.view);
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Every agent that keeps a login starts the new sandbox on its default (the launched agent on --account when given),
|
|
78
|
+
* so a session of any agent opened later uses the login you chose. Returns the launched agent's login.
|
|
79
|
+
*/
|
|
80
|
+
function launchAccounts(ctx, launched, wanted) {
|
|
81
|
+
const accounts = new AccountsService(ctx);
|
|
82
|
+
let account = null;
|
|
83
|
+
for (const agent of accounts.agents().filter((a) => a.kept)) {
|
|
84
|
+
if (agent.name === launched) {
|
|
85
|
+
account = accounts.ensure(launched, wanted);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
accounts.ensure(agent.name, null);
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
// Another agent's saved login could not be put in place: the launch goes on, and that agent says so.
|
|
93
|
+
console.warn(c.yellow('Could not switch ' + agent.name + ' to its default account: ' + errMsg(e)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return account;
|
|
97
|
+
}
|
|
76
98
|
/** Everything a real launch does before the sandbox starts (skipped by `generate`, which only writes files). */
|
|
77
99
|
export async function prepare(ctx, run) {
|
|
78
100
|
const { options, dataPath, storageRoot } = ctx;
|
|
79
101
|
const { config } = run;
|
|
80
|
-
const
|
|
81
|
-
const account = options.account ?? accounts.defaultName();
|
|
82
|
-
if (account && run.harnessName === 'claude' && account !== accounts.activeFor(dataPath))
|
|
83
|
-
accounts.activate(dataPath, account);
|
|
84
|
-
else if (run.harnessName === 'claude')
|
|
85
|
-
accounts.syncPlugins(dataPath);
|
|
102
|
+
const account = launchAccounts(ctx, run.harnessName, options.account ?? null);
|
|
86
103
|
const terminal = config.provisioning.terminal.enabled
|
|
87
104
|
? await ensureTerminal(dataPath, config.provisioning.refresh, { pwsh: config.toolchains.pwsh.enabled !== false })
|
|
88
105
|
: { exe: null, message: 'disabled' };
|
|
@@ -6,6 +6,7 @@ import { NeedsRestart } from '../../../host/projects/index.js';
|
|
|
6
6
|
import { RunDir } from '../../../shared/channel/index.js';
|
|
7
7
|
import { errMsg, fail, sleep } from '../../../core/util/index.js';
|
|
8
8
|
import { c, follow, working, table } from '../../output/index.js';
|
|
9
|
+
import { AccountsService } from '../../services/index.js';
|
|
9
10
|
import { bootLine, readyIn, stillWorking } from './boot-progress.js';
|
|
10
11
|
import { hereDir, joinHere, readyHere, offerRestart } from '../projects/index.js';
|
|
11
12
|
/** How long a session may take to start (its project's setup, the agent's install). */
|
|
@@ -65,6 +66,9 @@ export function waitForStart(ctx, requestId, what) {
|
|
|
65
66
|
/** Asks the sandbox for a session; the agent's definition goes along (the sandbox may have booted with another). */
|
|
66
67
|
function requestSession(ctx, channel, harness, args, joined) {
|
|
67
68
|
const harnessSpec = ctx.loaded.config.harnesses[harness];
|
|
69
|
+
// --account into a running sandbox switches its login first (sessions already running switch on their restart).
|
|
70
|
+
if (ctx.options.account && !ctx.borrowed)
|
|
71
|
+
new AccountsService(ctx).ensure(harness, ctx.options.account);
|
|
68
72
|
return channel.requestSession(harness, args, joined.guestDir, {
|
|
69
73
|
...(joined.spec ? { project: joined.spec } : {}),
|
|
70
74
|
...(harnessSpec ? { harnessSpec } : {}),
|
|
@@ -15,6 +15,7 @@ import { agentCommandsCommand } from './agent-commands.js';
|
|
|
15
15
|
import { vpn } from './vpn.js';
|
|
16
16
|
import { github } from './github.js';
|
|
17
17
|
import { web, webServer } from './web.js';
|
|
18
|
+
import { remote } from './remote.js';
|
|
18
19
|
export const HANDLERS = {
|
|
19
20
|
launch: (ctx) => launch(ctx),
|
|
20
21
|
generate: (ctx) => launch(ctx, true),
|
|
@@ -54,5 +55,6 @@ export const HANDLERS = {
|
|
|
54
55
|
vpn: vpn,
|
|
55
56
|
github: github,
|
|
56
57
|
web: web,
|
|
58
|
+
remote: remote,
|
|
57
59
|
_web: webServer,
|
|
58
60
|
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// `sandbox remote [user] [--browser]`: another Windows user's program, used from here (docs/design/remote-access.md).
|
|
2
|
+
// Windows sign-in through the owner's pipe gives a one-time token; the menu then runs over the owner's web UI (every
|
|
3
|
+
// action checked against the role the owner gave, and written to their audit log), or --browser opens it signed in.
|
|
4
|
+
import { allows } from '../../app/kernel/index.js';
|
|
5
|
+
import { fail, safeText } from '../../core/util/index.js';
|
|
6
|
+
import { knockOnDoor } from '../../host/remote/index.js';
|
|
7
|
+
import { currentUserName, runningOwner } from '../../host/sharing/index.js';
|
|
8
|
+
import { openBrowser } from '../../host/web/index.js';
|
|
9
|
+
import { RemoteMenu } from '../menu/remote-menu.js';
|
|
10
|
+
import { remoteClient } from '../services/remote-client.js';
|
|
11
|
+
import { App } from '../tui/index.js';
|
|
12
|
+
import { requireOnPc } from './context.js';
|
|
13
|
+
/** Whose program: the user named, else the owner of the running sandbox when it is someone else's. */
|
|
14
|
+
function ownerOf(ctx) {
|
|
15
|
+
const named = ctx.options.positionals[0];
|
|
16
|
+
if (named)
|
|
17
|
+
return { name: named, id: named };
|
|
18
|
+
const owner = runningOwner();
|
|
19
|
+
if (owner.kind === 'other')
|
|
20
|
+
return { name: owner.user, id: owner.record.sid };
|
|
21
|
+
return fail('Whose web UI? No sandbox of another user is running.', 'Name them: sandbox remote <their Windows user name>');
|
|
22
|
+
}
|
|
23
|
+
export async function remote(ctx) {
|
|
24
|
+
requireOnPc('remote');
|
|
25
|
+
const owner = ownerOf(ctx);
|
|
26
|
+
const answer = await knockOnDoor(ctx.storageRoot, owner.id);
|
|
27
|
+
if (!answer.ok)
|
|
28
|
+
return fail(safeText(answer.reason), 'Ask ' +
|
|
29
|
+
safeText(owner.name) +
|
|
30
|
+
' to turn on web.remote.windowsSignIn, add you to web.remote.users and start the web UI.');
|
|
31
|
+
if (ctx.options.raw.browser === true) {
|
|
32
|
+
openBrowser('http://127.0.0.1:' + answer.port + '/#w=' + answer.token);
|
|
33
|
+
console.log('Opened ' + safeText(owner.name) + "'s web UI in your browser (" + answer.role + '). It runs as them, not you.');
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
const client = await remoteClient(answer.port, answer.token);
|
|
37
|
+
const caller = { kind: 'windows', user: currentUserName(), role: answer.role };
|
|
38
|
+
const app = new App();
|
|
39
|
+
try {
|
|
40
|
+
await new RemoteMenu(app, ctx, client, owner.name, caller).start();
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
app.close();
|
|
44
|
+
client.close();
|
|
45
|
+
}
|
|
46
|
+
if (!allows(caller, 'control'))
|
|
47
|
+
console.log('You had view access: nothing was changed.');
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
@@ -8,6 +8,9 @@ import { c } from '../../output/index.js';
|
|
|
8
8
|
/** Where each running session is on this PC, so it can be reopened after a restart. */
|
|
9
9
|
export function sessionsToReopen(registry, sessions) {
|
|
10
10
|
return sessions.flatMap((s) => {
|
|
11
|
+
// Shells are not reopened: a new one would not be the one that ended.
|
|
12
|
+
if (s.kind === 'shell')
|
|
13
|
+
return [];
|
|
11
14
|
const project = projectForGuestDir([...registry.projects, ...registry.roots.map((r) => ({ ...r }))], s.cwd);
|
|
12
15
|
return project
|
|
13
16
|
? [
|
|
@@ -88,7 +88,7 @@ export async function storageBeforeCommand(ctx, command) {
|
|
|
88
88
|
ensureStorageRoot(ctx.storageRoot, protectPrivateFolder);
|
|
89
89
|
// Contained storage (beta): a mount an earlier sandbox left behind is only mentioned here; repair fixes it.
|
|
90
90
|
if (command !== 'launch' && !ctx.options.json) {
|
|
91
|
-
const stale = staleMountNote(ctx.storageRoot, ctx.loaded.config.storage.dataName, isSandboxRunning
|
|
91
|
+
const stale = staleMountNote(ctx.storageRoot, ctx.loaded.config.storage.dataName, isSandboxRunning);
|
|
92
92
|
if (stale)
|
|
93
93
|
console.error(c.yellow(stale));
|
|
94
94
|
}
|
package/dist/cli/commands/web.js
CHANGED
|
@@ -3,10 +3,16 @@
|
|
|
3
3
|
// running when no sandbox does. startWebOnLaunch is the sandbox start's hook.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import { accessStoreOf } from '../../app/features/access/index.js';
|
|
6
7
|
import { currentServer, START_WAIT_MS, webStatus } from '../../app/features/web/index.js';
|
|
7
8
|
import { createRpcHost } from '../../app/registry.js';
|
|
9
|
+
import { loadConfig } from '../../core/config/index.js';
|
|
8
10
|
import { packageVersion } from '../../core/package-info.js';
|
|
9
11
|
import { sleep, fail } from '../../core/util/index.js';
|
|
12
|
+
import { AuditLog } from '../../host/audit/index.js';
|
|
13
|
+
import { doorPipeName, HostToolsHelper, networkProfile, RemoteGate, TlsStore, } from '../../host/remote/index.js';
|
|
14
|
+
import { SecretStore } from '../../host/secrets/index.js';
|
|
15
|
+
import { currentUserSid } from '../../host/sharing/index.js';
|
|
10
16
|
import { IDLE_EXIT_MS, openBrowser, WebControl, WebRunner, WebStore } from '../../host/web/index.js';
|
|
11
17
|
import { spawnDetached } from '../../host/sandbox/index.js';
|
|
12
18
|
import { WebService } from '../services/index.js';
|
|
@@ -53,6 +59,65 @@ export async function startWebOnLaunch(ctx) {
|
|
|
53
59
|
export async function web(ctx) {
|
|
54
60
|
return (await definitionHandler(webStatus)(ctx)) ?? 0;
|
|
55
61
|
}
|
|
62
|
+
/** The remote settings as in the file now (read again at most every 3 seconds; a broken file keeps the last). */
|
|
63
|
+
function remoteSettings(ctx) {
|
|
64
|
+
let last = ctx.loaded.config.web.remote;
|
|
65
|
+
let readAt = Date.now();
|
|
66
|
+
return () => {
|
|
67
|
+
if (Date.now() - readAt < 3000)
|
|
68
|
+
return last;
|
|
69
|
+
readAt = Date.now();
|
|
70
|
+
try {
|
|
71
|
+
last = loadConfig(ctx.options.config).config.web.remote;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// A settings file being edited or broken: keep what was read last.
|
|
75
|
+
}
|
|
76
|
+
return last;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Remote actions go to the audit log of this data set (k: remote), so the Activity page shows who did what. */
|
|
80
|
+
function remoteAudit(ctx) {
|
|
81
|
+
const { days, maxMb } = ctx.loaded.config.audit;
|
|
82
|
+
const log = new AuditLog(AuditLog.dirFor(ctx.storageRoot, ctx.dataPath), { days, maxMb });
|
|
83
|
+
return (entry) => {
|
|
84
|
+
try {
|
|
85
|
+
log.append([{ t: new Date().toISOString(), k: 'remote', p: entry.what, a: entry.outcome, au: entry.who }]);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// The audit log is a record, never a reason for a request to fail.
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The gate for other Windows users and devices (docs/design/remote-access.md). The Windows helper is started when it
|
|
94
|
+
* can be built here: then the owner's own cookie is bound to the owner's connections too, and the sign-in pipe is
|
|
95
|
+
* served (it refuses everyone while web.remote.windowsSignIn is off).
|
|
96
|
+
*/
|
|
97
|
+
function remoteGate(ctx) {
|
|
98
|
+
const secrets = SecretStore.standard();
|
|
99
|
+
let ownerSid = '';
|
|
100
|
+
try {
|
|
101
|
+
ownerSid = currentUserSid();
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Unknown: Windows sign-in stays off, the owner signs in with the token as before.
|
|
105
|
+
}
|
|
106
|
+
const helper = ownerSid ? HostToolsHelper.start(ctx.storageRoot, doorPipeName(ownerSid)) : null;
|
|
107
|
+
const audit = remoteAudit(ctx);
|
|
108
|
+
helper?.onProblem((text) => audit({ who: 'the web UI', what: 'Windows sign-in: ' + text, outcome: 'failed' }));
|
|
109
|
+
const store = new WebStore(ctx.storageRoot);
|
|
110
|
+
return new RemoteGate({
|
|
111
|
+
store: accessStoreOf(ctx),
|
|
112
|
+
settings: remoteSettings(ctx),
|
|
113
|
+
ownerSid,
|
|
114
|
+
helper,
|
|
115
|
+
audit,
|
|
116
|
+
tls: new TlsStore(store.dir, secrets),
|
|
117
|
+
profile: (address) => networkProfile(address),
|
|
118
|
+
port: () => ctx.loaded.config.web.port,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
56
121
|
/** Body of the background process (`sandbox _web`): runs until stopped. */
|
|
57
122
|
export async function webServer(ctx) {
|
|
58
123
|
const staticRoot = path.join(ctx.packageRoot, 'dist', 'web');
|
|
@@ -66,6 +131,7 @@ export async function webServer(ctx) {
|
|
|
66
131
|
staticRoot,
|
|
67
132
|
version: packageVersion(),
|
|
68
133
|
idleExitMs: IDLE_EXIT_MS,
|
|
134
|
+
gate: remoteGate(ctx),
|
|
69
135
|
}).run();
|
|
70
136
|
process.exit(0);
|
|
71
137
|
}
|
package/dist/cli/manifest.d.ts
CHANGED
|
@@ -769,17 +769,76 @@ declare const COMMAND_LIST: readonly [{
|
|
|
769
769
|
readonly interactive: true;
|
|
770
770
|
}, {
|
|
771
771
|
readonly name: "web";
|
|
772
|
-
readonly summary: "(beta) your sandbox sessions in a browser on this PC
|
|
772
|
+
readonly summary: "(beta) your sandbox sessions in a browser on this PC (and, if you allow it, on your network)";
|
|
773
773
|
readonly group: "Web";
|
|
774
|
-
readonly examples: readonly ["sandbox web open start the web UI and open it in your browser"];
|
|
774
|
+
readonly examples: readonly ["sandbox web open start the web UI and open it in your browser", "sandbox web lan-on let devices you allow use it on your network", "sandbox web allow-device show a one-time code for a phone", "sandbox web devices who may use it; sandbox web revoke <id> signs one out"];
|
|
775
775
|
readonly args: readonly [{
|
|
776
776
|
readonly name: "action";
|
|
777
|
-
readonly description: "start
|
|
777
|
+
readonly description: "start, stop, status, open; devices, allow-device, revoke, revoke-all, lan-on, lan-off (remote access)";
|
|
778
778
|
readonly required: false;
|
|
779
779
|
readonly choices: readonly ["start", "stop", "status", "open"];
|
|
780
|
+
}, {
|
|
781
|
+
readonly name: "id";
|
|
782
|
+
readonly description: "(revoke) the device or sign-in id from sandbox web devices";
|
|
783
|
+
readonly required: false;
|
|
784
|
+
readonly when: "revoke";
|
|
785
|
+
}];
|
|
786
|
+
readonly options: readonly [{
|
|
787
|
+
flag: string;
|
|
788
|
+
type: "string" | "number" | "boolean" | "list";
|
|
789
|
+
description: string;
|
|
790
|
+
short?: string | undefined;
|
|
791
|
+
value?: string | undefined;
|
|
792
|
+
label?: string | undefined;
|
|
793
|
+
default?: string | number | undefined;
|
|
794
|
+
choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
|
|
795
|
+
advanced?: boolean | undefined;
|
|
796
|
+
when?: string | undefined;
|
|
797
|
+
noMenu?: boolean | undefined;
|
|
798
|
+
group?: string | undefined;
|
|
799
|
+
shownAs?: string | undefined;
|
|
800
|
+
}, {
|
|
801
|
+
flag: string;
|
|
802
|
+
type: "string" | "number" | "boolean" | "list";
|
|
803
|
+
description: string;
|
|
804
|
+
short?: string | undefined;
|
|
805
|
+
value?: string | undefined;
|
|
806
|
+
label?: string | undefined;
|
|
807
|
+
default?: string | number | undefined;
|
|
808
|
+
choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
|
|
809
|
+
advanced?: boolean | undefined;
|
|
810
|
+
when?: string | undefined;
|
|
811
|
+
noMenu?: boolean | undefined;
|
|
812
|
+
group?: string | undefined;
|
|
813
|
+
shownAs?: string | undefined;
|
|
780
814
|
}];
|
|
781
|
-
readonly options: readonly [];
|
|
782
815
|
readonly json: true;
|
|
816
|
+
}, {
|
|
817
|
+
readonly name: "remote";
|
|
818
|
+
readonly summary: "(beta) use another Windows user's web UI from here, signed in with your Windows account";
|
|
819
|
+
readonly group: "Web";
|
|
820
|
+
readonly examples: readonly ["sandbox remote the menu over the owner of the running sandbox (their program, your role)", "sandbox remote alice --browser open alice's web UI in your browser, signed in"];
|
|
821
|
+
readonly args: readonly [{
|
|
822
|
+
readonly name: "user";
|
|
823
|
+
readonly description: "whose web UI (default: the owner of the running sandbox)";
|
|
824
|
+
readonly required: false;
|
|
825
|
+
}];
|
|
826
|
+
readonly options: readonly [{
|
|
827
|
+
flag: string;
|
|
828
|
+
type: "string" | "number" | "boolean" | "list";
|
|
829
|
+
description: string;
|
|
830
|
+
short?: string | undefined;
|
|
831
|
+
value?: string | undefined;
|
|
832
|
+
label?: string | undefined;
|
|
833
|
+
default?: string | number | undefined;
|
|
834
|
+
choices?: string[] | "harnesses" | "sessions" | "accounts" | undefined;
|
|
835
|
+
advanced?: boolean | undefined;
|
|
836
|
+
when?: string | undefined;
|
|
837
|
+
noMenu?: boolean | undefined;
|
|
838
|
+
group?: string | undefined;
|
|
839
|
+
shownAs?: string | undefined;
|
|
840
|
+
}];
|
|
841
|
+
readonly interactive: true;
|
|
783
842
|
}, {
|
|
784
843
|
readonly name: "_watch";
|
|
785
844
|
readonly summary: "background history sync";
|
package/dist/cli/manifest.js
CHANGED
|
@@ -540,20 +540,49 @@ const COMMAND_LIST = [
|
|
|
540
540
|
},
|
|
541
541
|
{
|
|
542
542
|
name: 'web',
|
|
543
|
-
summary: '(beta) your sandbox sessions in a browser on this PC
|
|
543
|
+
summary: '(beta) your sandbox sessions in a browser on this PC (and, if you allow it, on your network)',
|
|
544
544
|
group: 'Web',
|
|
545
|
-
examples: [
|
|
545
|
+
examples: [
|
|
546
|
+
'sandbox web open start the web UI and open it in your browser',
|
|
547
|
+
'sandbox web lan-on let devices you allow use it on your network',
|
|
548
|
+
'sandbox web allow-device show a one-time code for a phone',
|
|
549
|
+
'sandbox web devices who may use it; sandbox web revoke <id> signs one out',
|
|
550
|
+
],
|
|
546
551
|
args: [
|
|
547
552
|
{
|
|
548
553
|
name: 'action',
|
|
549
|
-
description: 'start
|
|
554
|
+
description: 'start, stop, status, open; devices, allow-device, revoke, revoke-all, lan-on, lan-off (remote access)',
|
|
550
555
|
required: false,
|
|
551
556
|
choices: ['start', 'stop', 'status', 'open'],
|
|
552
557
|
},
|
|
558
|
+
{
|
|
559
|
+
name: 'id',
|
|
560
|
+
description: '(revoke) the device or sign-in id from sandbox web devices',
|
|
561
|
+
required: false,
|
|
562
|
+
when: 'revoke',
|
|
563
|
+
},
|
|
564
|
+
],
|
|
565
|
+
options: [
|
|
566
|
+
text('--role', 'view|control', '(allow-device) what the device may do (default view)', {
|
|
567
|
+
when: 'allow-device',
|
|
568
|
+
choices: ['view', 'control'],
|
|
569
|
+
}),
|
|
570
|
+
bool('--yes', '(revoke-all, lan-off) do not ask first', { short: '-y', noMenu: true }),
|
|
553
571
|
],
|
|
554
|
-
options: [],
|
|
555
572
|
json,
|
|
556
573
|
},
|
|
574
|
+
{
|
|
575
|
+
name: 'remote',
|
|
576
|
+
summary: "(beta) use another Windows user's web UI from here, signed in with your Windows account",
|
|
577
|
+
group: 'Web',
|
|
578
|
+
examples: [
|
|
579
|
+
'sandbox remote the menu over the owner of the running sandbox (their program, your role)',
|
|
580
|
+
"sandbox remote alice --browser open alice's web UI in your browser, signed in",
|
|
581
|
+
],
|
|
582
|
+
args: [{ name: 'user', description: 'whose web UI (default: the owner of the running sandbox)', required: false }],
|
|
583
|
+
options: [bool('--browser', 'open it in your browser instead of the menu')],
|
|
584
|
+
interactive: true,
|
|
585
|
+
},
|
|
557
586
|
{ name: '_watch', summary: 'background history sync', group: 'Data', args: [], options: [], hidden: true },
|
|
558
587
|
{
|
|
559
588
|
name: '_web',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Action, type AnyDefinition } from '../../app/kernel/index.js';
|
|
1
|
+
import { type Action, type AnyDefinition, type OperationIO } from '../../app/kernel/index.js';
|
|
2
2
|
import type { Context } from '../commands/context.js';
|
|
3
3
|
import { type App } from '../tui/index.js';
|
|
4
4
|
import type { ShellExit } from './shell.js';
|
|
@@ -12,10 +12,12 @@ export interface Performed {
|
|
|
12
12
|
readonly notice?: string;
|
|
13
13
|
readonly reload?: boolean;
|
|
14
14
|
}
|
|
15
|
+
/** How a definition is run: in this process (the menu), or on the owner's web UI (sandbox remote). */
|
|
16
|
+
export type DefinitionRunner = (id: string, input: unknown, io: OperationIO) => Promise<unknown>;
|
|
15
17
|
export declare class MenuOperations {
|
|
16
18
|
private readonly app;
|
|
17
|
-
private readonly
|
|
18
|
-
constructor(app: App, ctx: Context);
|
|
19
|
+
private readonly runner;
|
|
20
|
+
constructor(app: App, ctx: Context, runner?: DefinitionRunner);
|
|
19
21
|
/** Does what an action of a page says: follows a link, or asks for its fields and runs its operation. */
|
|
20
22
|
perform(action: Action, where: Where, busy?: ShowBusy, onOutput?: (output: unknown) => void): Promise<Performed>;
|
|
21
23
|
/** The values of an action's fields, from the editors that fit them (undefined: one was backed out of). */
|
package/dist/cli/menu/io-menu.js
CHANGED
|
@@ -66,10 +66,10 @@ function follow(target) {
|
|
|
66
66
|
}
|
|
67
67
|
export class MenuOperations {
|
|
68
68
|
app;
|
|
69
|
-
|
|
70
|
-
constructor(app, ctx) {
|
|
69
|
+
runner;
|
|
70
|
+
constructor(app, ctx, runner = (id, input, io) => runDefinition(id, ctx, input, io)) {
|
|
71
71
|
this.app = app;
|
|
72
|
-
this.
|
|
72
|
+
this.runner = runner;
|
|
73
73
|
}
|
|
74
74
|
/** Does what an action of a page says: follows a link, or asks for its fields and runs its operation. */
|
|
75
75
|
async perform(action, where, busy, onOutput) {
|
|
@@ -81,7 +81,14 @@ export class MenuOperations {
|
|
|
81
81
|
const answers = await this.fields(action, def, where);
|
|
82
82
|
if (!answers)
|
|
83
83
|
return {};
|
|
84
|
-
|
|
84
|
+
// An operation that opens a session's terminal (a new shell) leaves the menu to attach to it.
|
|
85
|
+
let opened;
|
|
86
|
+
const notice = await this.run(def, { ...action.input, ...answers }, where, busy, (output) => {
|
|
87
|
+
onOutput?.(output);
|
|
88
|
+
opened = def.kind === 'operation' ? def.opens?.(output) : undefined;
|
|
89
|
+
});
|
|
90
|
+
const exit = opened ? follow(opened).exit : undefined;
|
|
91
|
+
return { reload: true, notice, ...(exit ? { exit } : {}) };
|
|
85
92
|
}
|
|
86
93
|
/** The values of an action's fields, from the editors that fit them (undefined: one was backed out of). */
|
|
87
94
|
async fields(action, def, where) {
|
|
@@ -110,7 +117,7 @@ export class MenuOperations {
|
|
|
110
117
|
log: (text, tone) => said.push(tone === 'warn' || tone === 'danger' ? Theme.warn(text) : text),
|
|
111
118
|
});
|
|
112
119
|
try {
|
|
113
|
-
const output = await
|
|
120
|
+
const output = await this.runner(def.id, input, io);
|
|
114
121
|
onOutput?.(output);
|
|
115
122
|
// The notice is one line: an operation that says several lines (accounts use) runs them together.
|
|
116
123
|
const text = def.text?.(output)?.replace(/\n+/g, ' ');
|
package/dist/cli/menu/live.d.ts
CHANGED
|
@@ -5,20 +5,49 @@ export interface LiveOptions {
|
|
|
5
5
|
readonly derive: () => string;
|
|
6
6
|
/** The line changed: draw again. */
|
|
7
7
|
readonly onChange: () => void;
|
|
8
|
+
/** How often the line is derived again (and the shortest gap between two process lists). */
|
|
8
9
|
readonly intervalMs?: number;
|
|
10
|
+
/** The longest gap between two process lists while no sandbox runs and nobody presses a key. */
|
|
11
|
+
readonly idleMaxMs?: number;
|
|
12
|
+
/** Whether a process still runs (default: a signal-0 check, which starts no program). */
|
|
13
|
+
readonly alive?: (pid: number) => boolean;
|
|
14
|
+
/** Whether the process list is needed (default: not inside a sandbox, where "is it running?" is always yes). */
|
|
15
|
+
readonly needsList?: () => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* A cheap fingerprint of what a sandbox start changes (the data folder's run folder): while none runs, a change
|
|
18
|
+
* fetches the list at the next tick instead of after the idle gap.
|
|
19
|
+
*/
|
|
20
|
+
readonly signal?: () => string;
|
|
9
21
|
}
|
|
22
|
+
/** The gap before the next process list after `unchanged` lists in a row found the same: doubling, up to `max`. */
|
|
23
|
+
export declare function listGap(unchanged: number, base: number, max: number): number;
|
|
10
24
|
export declare class LiveStatus {
|
|
11
25
|
private readonly o;
|
|
12
26
|
private tasklist;
|
|
27
|
+
/** The sandbox's processes in the last list (none: no sandbox was running). */
|
|
28
|
+
private pids;
|
|
29
|
+
private fetchedAt;
|
|
30
|
+
/** Lists in a row that found the same answer (the gap to the next one grows with it). */
|
|
31
|
+
private unchanged;
|
|
32
|
+
/** The signal when the list was last fetched. */
|
|
33
|
+
private signalled;
|
|
13
34
|
private text;
|
|
14
35
|
private timer;
|
|
15
36
|
private pending;
|
|
16
37
|
private running;
|
|
17
38
|
constructor(o: LiveOptions);
|
|
39
|
+
private get interval();
|
|
18
40
|
/** The status line as of the last refresh. */
|
|
19
41
|
line(): string;
|
|
20
42
|
/** Reads the process list now and derives the line (one fetch at a time; a caller during one waits for it). */
|
|
21
43
|
refresh(): Promise<void>;
|
|
44
|
+
private fetch;
|
|
45
|
+
private load;
|
|
46
|
+
/** Whether the process list may have changed since it was fetched (checked on every tick, cheaply). */
|
|
47
|
+
private listDue;
|
|
48
|
+
private tick;
|
|
49
|
+
/** A key was pressed: someone is looking, so a list older than one interval is fetched again (after the draw). */
|
|
50
|
+
touch(): void;
|
|
22
51
|
private derive;
|
|
23
52
|
/** Starts keeping the line fresh and answers the host's process questions from it (until stop). */
|
|
24
53
|
start(): Promise<void>;
|