windows-development-sandbox 1.14.0 → 1.15.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/config.example.json +2 -1
- package/dist/app/features/defs.d.ts +1 -1
- package/dist/app/features/projects/projects.def.d.ts +1 -1
- package/dist/app/features/projects/projects.run.d.ts +1 -1
- package/dist/app/features/projects/projects.run.js +10 -2
- package/dist/app/features/sandbox/sandbox.def.js +3 -1
- package/dist/app/features/settings/categories.def.js +1 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-BQ23GBLC.js → chunk-D7CM63BS.js} +1893 -1405
- package/dist/bin/{chunk-F7IMME5G.js → chunk-HTQT5SBY.js} +1 -1
- package/dist/bin/{chunk-LQHEUN2A.js → chunk-RQ4BWBYX.js} +1 -1
- 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/projects/here.js +2 -0
- package/dist/cli/services/health-service.js +15 -3
- package/dist/cli/services/projects-service.d.ts +8 -3
- package/dist/cli/services/projects-service.js +21 -4
- package/dist/core/config/defaults.js +1 -0
- package/dist/core/schema/config.d.ts +4 -0
- package/dist/core/schema/projects.d.ts +4 -0
- package/dist/core/schema/projects.js +3 -0
- package/dist/guest/boot/boot.js +8 -2
- package/dist/guest/daemon/daemon.js +3 -0
- package/dist/guest/daemon/live-folders.d.ts +48 -0
- package/dist/guest/daemon/live-folders.js +245 -0
- package/dist/guest/daemon/live.d.ts +4 -0
- package/dist/guest/daemon/live.js +57 -0
- package/dist/guest/entries/daemon.js +4 -2
- package/dist/guest/runtime/csc.d.ts +1 -0
- package/dist/guest/runtime/csc.js +1 -0
- package/dist/guest/runtime/secrets.d.ts +5 -2
- package/dist/guest/runtime/secrets.js +12 -5
- package/dist/host/engines/hcs/live-folders.d.ts +117 -0
- package/dist/host/engines/hcs/live-folders.js +347 -0
- package/dist/host/engines/index.d.ts +1 -0
- package/dist/host/engines/index.js +1 -0
- package/dist/host/launch/generate.js +8 -1
- package/dist/host/projects/join.d.ts +4 -0
- package/dist/host/projects/join.js +11 -3
- package/dist/host/projects/mapper.d.ts +34 -5
- package/dist/host/projects/mapper.js +88 -20
- package/dist/host/projects/registry.d.ts +2 -0
- package/dist/host/projects/registry.js +1 -1
- package/dist/host/secrets/launch-secrets.js +2 -1
- package/dist/runtime/agent.js +14 -0
- package/dist/runtime/daemon.js +466 -132
- package/dist/runtime/db.js +14 -0
- package/dist/runtime/main.js +26 -3
- package/dist/runtime/netproxy.js +14 -0
- package/dist/runtime/view.js +14 -0
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/layout.test.js +1 -0
- package/dist/selftest/live-folders.test.d.ts +2 -0
- package/dist/selftest/live-folders.test.js +588 -0
- package/dist/selftest/projects.test.js +7 -7
- package/dist/selftest/support/engine-golden.js +1 -0
- package/dist/selftest/support/fixtures.d.ts +1 -0
- package/dist/selftest/support/live.js +2 -0
- package/dist/shared/channel/index.d.ts +1 -0
- package/dist/shared/channel/index.js +1 -0
- package/dist/shared/channel/live.d.ts +53 -0
- package/dist/shared/channel/live.js +92 -0
- package/dist/shared/channel/run-dir.d.ts +5 -0
- package/dist/shared/channel/run-dir.js +11 -0
- package/dist/shared/channel/secrets.d.ts +1 -0
- package/dist/shared/channel/secrets.js +5 -0
- package/dist/shared/paths/projects.d.ts +6 -1
- package/dist/shared/paths/projects.js +21 -1
- package/dist/sync/watcher.js +9 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-6XNBTJLT.js → main-7DJZ2M27.js} +1 -1
- package/native/csharp/HcsHost/AssemblyInfo.cs +4 -4
- package/native/csharp/HcsHost/FolderAccess.cs +361 -0
- package/native/csharp/HcsHost/Hcs.cs +31 -3
- package/native/csharp/HcsHost/Program.cs +54 -6
- package/native/csharp/HcsHost/Share.cs +64 -11
- package/native/csharp/HcsHost/WsbGuard.cs +130 -0
- package/native/csharp/HcsHost/WsbShare.cs +295 -0
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/contained-common.ps1 +4 -0
- package/native/scripts/hcs-live-share.ps1 +191 -0
- package/package.json +1 -1
- package/sandbox/csharp/BindMount.cs +120 -0
- package/schemas/config.schema.json +8 -0
|
@@ -98,6 +98,8 @@ export function startDaemon(t, f, options) {
|
|
|
98
98
|
AGENT_DATA_DIR: data,
|
|
99
99
|
AGENT_WORKSPACE: options.workspace,
|
|
100
100
|
AGENT_LOCAL_DIR: f.at(path.basename(data) + '-local'),
|
|
101
|
+
// The daemon's live-folder state: a temp folder, never C:\ProgramData of the machine running the test.
|
|
102
|
+
AGENT_DAEMON_STATE_DIR: f.at(path.basename(data) + '-daemon-state'),
|
|
101
103
|
AGENT_LOG_PREFIX: 'daemon',
|
|
102
104
|
AGENT_NO_TABS: '1',
|
|
103
105
|
...options.env,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** This tool's vSMB share names: never the 64-hex names Windows Sandbox gives its own shares. */
|
|
3
|
+
export declare const LIVE_SHARE_NAME: RegExp;
|
|
4
|
+
/** A folder name under C:\Workspace: no dots or spaces, so no "..", "foo." or "foo " aliases of other folders. */
|
|
5
|
+
export declare const LIVE_FOLDER_NAME: RegExp;
|
|
6
|
+
export declare const LIVE_KEY: RegExp;
|
|
7
|
+
export declare const LIVE_TOKEN: RegExp;
|
|
8
|
+
/** How old (or how far in the future) a request may be, by the guest's clock. */
|
|
9
|
+
export declare const LIVE_MAX_AGE_MS: number;
|
|
10
|
+
export declare const LivePayloadSchema: z.ZodObject<{
|
|
11
|
+
v: z.ZodLiteral<1>;
|
|
12
|
+
kind: z.ZodEnum<{
|
|
13
|
+
map: "map";
|
|
14
|
+
canary: "canary";
|
|
15
|
+
unmap: "unmap";
|
|
16
|
+
}>;
|
|
17
|
+
id: z.ZodString;
|
|
18
|
+
launchId: z.ZodString;
|
|
19
|
+
share: z.ZodOptional<z.ZodString>;
|
|
20
|
+
folder: z.ZodString;
|
|
21
|
+
readOnly: z.ZodBoolean;
|
|
22
|
+
createdAt: z.ZodString;
|
|
23
|
+
}, z.core.$strict>;
|
|
24
|
+
export type LivePayload = z.output<typeof LivePayloadSchema>;
|
|
25
|
+
export declare const LiveEnvelopeSchema: z.ZodObject<{
|
|
26
|
+
payload: z.ZodString;
|
|
27
|
+
mac: z.ZodString;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
export type LiveEnvelope = z.output<typeof LiveEnvelopeSchema>;
|
|
30
|
+
/** run\live-replies\<id>.json (guest-written: checked, capped; the host only ever compares the token). */
|
|
31
|
+
export declare const LiveReplySchema: z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
ok: z.ZodBoolean;
|
|
34
|
+
message: z.ZodString;
|
|
35
|
+
token: z.ZodOptional<z.ZodString>;
|
|
36
|
+
finishedAt: z.ZodString;
|
|
37
|
+
}, z.core.$strict>;
|
|
38
|
+
export type LiveReply = z.output<typeof LiveReplySchema>;
|
|
39
|
+
/** A signed request for `payload` (host side). */
|
|
40
|
+
export declare function signLive(key: string, payload: LivePayload): LiveEnvelope;
|
|
41
|
+
/** Why a request is refused (guest side), or the payload it carries. */
|
|
42
|
+
export declare function openLive(key: string | null, envelope: unknown, o: {
|
|
43
|
+
launchId: string;
|
|
44
|
+
now: number;
|
|
45
|
+
}): {
|
|
46
|
+
payload: LivePayload;
|
|
47
|
+
} | {
|
|
48
|
+
refused: string;
|
|
49
|
+
};
|
|
50
|
+
/** A new random share name (host side). */
|
|
51
|
+
export declare function newShareName(): string;
|
|
52
|
+
/** A new launch key (host side). */
|
|
53
|
+
export declare function newLiveKey(): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Live folders through HCS (opt-in alpha, docs/design/hcs-live-folders.md): the host -> guest messages that make a
|
|
2
|
+
// folder the host shared into the running sandbox appear at C:\Workspace\<name>. Requests go in run\live, replies in
|
|
3
|
+
// run\live-replies. Every request is signed: {payload: "<the JSON text>", mac: HMAC-SHA256(key, payload)}; the MAC is
|
|
4
|
+
// over the exact payload text, never over a re-serialised object. The key is made per launch, travels to the sandbox
|
|
5
|
+
// only in the one-shot secrets file (other users of a shared sandbox may write run\, never secrets\) and is kept on the
|
|
6
|
+
// host sealed with DPAPI. The agent is an administrator in the sandbox, so it can read the key there: the signature
|
|
7
|
+
// keeps other users and stale or replayed files from driving the daemon, and the real boundary is that only the host
|
|
8
|
+
// decides what is shared (a guest can only show or hide what it was given).
|
|
9
|
+
import crypto from 'node:crypto';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
import { IdSchema } from './schema.js';
|
|
12
|
+
/** This tool's vSMB share names: never the 64-hex names Windows Sandbox gives its own shares. */
|
|
13
|
+
export const LIVE_SHARE_NAME = /^agentsbx-[0-9a-f]{8,32}$/;
|
|
14
|
+
/** A folder name under C:\Workspace: no dots or spaces, so no "..", "foo." or "foo " aliases of other folders. */
|
|
15
|
+
export const LIVE_FOLDER_NAME = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
16
|
+
export const LIVE_KEY = /^[0-9a-f]{64}$/;
|
|
17
|
+
export const LIVE_TOKEN = /^[0-9a-f]{32}$/;
|
|
18
|
+
/** How old (or how far in the future) a request may be, by the guest's clock. */
|
|
19
|
+
export const LIVE_MAX_AGE_MS = 2 * 60 * 1000;
|
|
20
|
+
const MAX_PAYLOAD = 4096;
|
|
21
|
+
export const LivePayloadSchema = z.strictObject({
|
|
22
|
+
v: z.literal(1),
|
|
23
|
+
/** canary: show the share, read token.txt, hide it again and reply with the token; map / unmap: the real folder. */
|
|
24
|
+
kind: z.enum(['canary', 'map', 'unmap']),
|
|
25
|
+
id: IdSchema,
|
|
26
|
+
launchId: z.string().min(1).max(64),
|
|
27
|
+
share: z.string().regex(LIVE_SHARE_NAME).optional(),
|
|
28
|
+
folder: z.string().regex(LIVE_FOLDER_NAME),
|
|
29
|
+
readOnly: z.boolean(),
|
|
30
|
+
createdAt: z.string().max(64),
|
|
31
|
+
});
|
|
32
|
+
export const LiveEnvelopeSchema = z.strictObject({
|
|
33
|
+
payload: z.string().min(2).max(MAX_PAYLOAD),
|
|
34
|
+
mac: z.string().regex(/^[0-9a-f]{64}$/),
|
|
35
|
+
});
|
|
36
|
+
/** run\live-replies\<id>.json (guest-written: checked, capped; the host only ever compares the token). */
|
|
37
|
+
export const LiveReplySchema = z.strictObject({
|
|
38
|
+
id: IdSchema,
|
|
39
|
+
ok: z.boolean(),
|
|
40
|
+
message: z.string().max(1000),
|
|
41
|
+
token: z.string().regex(LIVE_TOKEN).optional(),
|
|
42
|
+
finishedAt: z.string().max(64),
|
|
43
|
+
});
|
|
44
|
+
function mac(key, payload) {
|
|
45
|
+
return crypto.createHmac('sha256', Buffer.from(key, 'hex')).update(payload, 'utf8').digest('hex');
|
|
46
|
+
}
|
|
47
|
+
/** A signed request for `payload` (host side). */
|
|
48
|
+
export function signLive(key, payload) {
|
|
49
|
+
if (!LIVE_KEY.test(key))
|
|
50
|
+
throw new Error('not a live-folder key');
|
|
51
|
+
const text = JSON.stringify(LivePayloadSchema.parse(payload));
|
|
52
|
+
return { payload: text, mac: mac(key, text) };
|
|
53
|
+
}
|
|
54
|
+
/** Why a request is refused (guest side), or the payload it carries. */
|
|
55
|
+
export function openLive(key, envelope, o) {
|
|
56
|
+
if (!key || !LIVE_KEY.test(key))
|
|
57
|
+
return { refused: 'no live-folder key (live folders were off when the sandbox started)' };
|
|
58
|
+
const env = LiveEnvelopeSchema.safeParse(envelope);
|
|
59
|
+
if (!env.success)
|
|
60
|
+
return { refused: 'not a live-folder request' };
|
|
61
|
+
const want = Buffer.from(mac(key, env.data.payload), 'hex');
|
|
62
|
+
const got = Buffer.from(env.data.mac, 'hex');
|
|
63
|
+
if (want.length !== got.length || !crypto.timingSafeEqual(want, got))
|
|
64
|
+
return { refused: 'the signature does not match' };
|
|
65
|
+
let json;
|
|
66
|
+
try {
|
|
67
|
+
json = JSON.parse(env.data.payload);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return { refused: 'the signed payload is not JSON' };
|
|
71
|
+
}
|
|
72
|
+
const parsed = LivePayloadSchema.safeParse(json);
|
|
73
|
+
if (!parsed.success)
|
|
74
|
+
return { refused: 'the signed payload has the wrong shape' };
|
|
75
|
+
const p = parsed.data;
|
|
76
|
+
if (p.launchId !== o.launchId)
|
|
77
|
+
return { refused: 'it is for another launch' };
|
|
78
|
+
const at = Date.parse(p.createdAt);
|
|
79
|
+
if (!Number.isFinite(at) || Math.abs(o.now - at) > LIVE_MAX_AGE_MS)
|
|
80
|
+
return { refused: 'it is too old' };
|
|
81
|
+
if (p.kind !== 'unmap' && !p.share)
|
|
82
|
+
return { refused: 'it names no share' };
|
|
83
|
+
return { payload: p };
|
|
84
|
+
}
|
|
85
|
+
/** A new random share name (host side). */
|
|
86
|
+
export function newShareName() {
|
|
87
|
+
return 'agentsbx-' + crypto.randomBytes(8).toString('hex');
|
|
88
|
+
}
|
|
89
|
+
/** A new launch key (host side). */
|
|
90
|
+
export function newLiveKey() {
|
|
91
|
+
return crypto.randomBytes(32).toString('hex');
|
|
92
|
+
}
|
|
@@ -24,7 +24,12 @@ export declare class RunDir {
|
|
|
24
24
|
readonly treesDir: string;
|
|
25
25
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
26
26
|
readonly secretsDir: string;
|
|
27
|
+
/** Live folders (live.ts): signed host requests, and the daemon's replies. */
|
|
28
|
+
readonly liveDir: string;
|
|
29
|
+
readonly liveRepliesDir: string;
|
|
27
30
|
constructor(dataDir: string);
|
|
31
|
+
liveFile(id: string): string;
|
|
32
|
+
liveReplyFile(id: string): string;
|
|
28
33
|
requestFile(id: string): string;
|
|
29
34
|
responseFile(id: string): string;
|
|
30
35
|
sessionFile(id: string): string;
|
|
@@ -51,6 +51,9 @@ export class RunDir {
|
|
|
51
51
|
treesDir;
|
|
52
52
|
/** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
|
|
53
53
|
secretsDir;
|
|
54
|
+
/** Live folders (live.ts): signed host requests, and the daemon's replies. */
|
|
55
|
+
liveDir;
|
|
56
|
+
liveRepliesDir;
|
|
54
57
|
constructor(dataDir) {
|
|
55
58
|
this.root = path.join(dataDir, 'run');
|
|
56
59
|
this.sandboxFile = path.join(this.root, 'sandbox.json');
|
|
@@ -64,6 +67,14 @@ export class RunDir {
|
|
|
64
67
|
this.auditDir = path.join(this.root, 'audit');
|
|
65
68
|
this.treesDir = path.join(this.root, 'trees');
|
|
66
69
|
this.secretsDir = path.join(dataDir, 'secrets');
|
|
70
|
+
this.liveDir = path.join(this.root, 'live');
|
|
71
|
+
this.liveRepliesDir = path.join(this.root, 'live-replies');
|
|
72
|
+
}
|
|
73
|
+
liveFile(id) {
|
|
74
|
+
return path.join(this.liveDir, safeId(id) + '.json');
|
|
75
|
+
}
|
|
76
|
+
liveReplyFile(id) {
|
|
77
|
+
return path.join(this.liveRepliesDir, safeId(id) + '.json');
|
|
67
78
|
}
|
|
68
79
|
requestFile(id) {
|
|
69
80
|
return path.join(this.requestsDir, safeId(id) + '.json');
|
|
@@ -10,6 +10,7 @@ export declare const SecretsFileSchema: z.ZodObject<{
|
|
|
10
10
|
}, z.core.$strict>>;
|
|
11
11
|
wireguardConfig: z.ZodOptional<z.ZodString>;
|
|
12
12
|
githubToken: z.ZodOptional<z.ZodString>;
|
|
13
|
+
liveFoldersKey: z.ZodOptional<z.ZodString>;
|
|
13
14
|
}, z.core.$strict>;
|
|
14
15
|
export type SecretsFile = z.output<typeof SecretsFileSchema>;
|
|
15
16
|
interface WithHarnesses {
|
|
@@ -28,6 +28,11 @@ export const SecretsFileSchema = z.strictObject({
|
|
|
28
28
|
.optional(),
|
|
29
29
|
/** The GitHub token you saved for sandboxes (github.shareToken), only with git access "full". */
|
|
30
30
|
githubToken: GithubTokenSchema.optional(),
|
|
31
|
+
/** projects.liveFolders "hcs": this launch's key for signed live-folder requests (live.ts). */
|
|
32
|
+
liveFoldersKey: z
|
|
33
|
+
.string()
|
|
34
|
+
.regex(/^[0-9a-f]{64}$/)
|
|
35
|
+
.optional(),
|
|
31
36
|
});
|
|
32
37
|
/** The environment a harness gets: its own, then the values that travelled in the secrets file. */
|
|
33
38
|
const withEnvironment = (harness, environment) => (environment ? { ...harness, environment: { ...harness.environment, ...environment } } : harness);
|
|
@@ -11,7 +11,7 @@ export declare const WORKSPACES_GUEST = "C:\\Workspace";
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function autoGuestFolder(hostDir: string): string;
|
|
13
13
|
/** How a project got into the sandbox. */
|
|
14
|
-
export type Mechanism = 'launch' | 'root' | 'boot' | 'wsb';
|
|
14
|
+
export type Mechanism = 'launch' | 'root' | 'boot' | 'wsb' | 'hcs';
|
|
15
15
|
/** One project the running sandbox works on. */
|
|
16
16
|
export interface ProjectEntry {
|
|
17
17
|
name: string;
|
|
@@ -65,6 +65,11 @@ export declare function usableRoots(roots: string[], exists: (dir: string) => bo
|
|
|
65
65
|
* when the sandbox can show it there, else C:\Workspace\<name>. -2, -3, ... when that overlaps a folder in `taken`.
|
|
66
66
|
*/
|
|
67
67
|
export declare function ownGuestFolder(hostDir: string, mirror: boolean, taken?: string[]): string;
|
|
68
|
+
/**
|
|
69
|
+
* Where a live folder (projects.liveFolders "hcs") appears: C:\Workspace\<name>, the name in the strict form the guest
|
|
70
|
+
* daemon accepts (lower-case letters, digits, '-' and '_'; no dots or spaces), -2, -3, ... when it overlaps `taken`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function liveGuestFolder(hostDir: string, taken?: string[]): string;
|
|
68
73
|
/**
|
|
69
74
|
* Guest folders for the project roots (C:\Projects\<name>, unique; with guestWorkspace "mirror" at its PC path when
|
|
70
75
|
* the sandbox can show it there) and the projects mapped at boot (ownGuestFolder, unique against `taken` and each
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// - a folder inside a project root (projects.roots: whole folders of projects, mapped at every start), including
|
|
5
5
|
// projects created after boot;
|
|
6
6
|
// - projects mapped at boot because they were in use when the sandbox was restarted to add them;
|
|
7
|
-
// - a folder shared into the running sandbox with `wsb share` (Windows 11 24H2+, untested)
|
|
7
|
+
// - a folder shared into the running sandbox with `wsb share` (Windows 11 24H2+, untested);
|
|
8
|
+
// - a folder shared into the running sandbox through HCS (projects.liveFolders "hcs", alpha, untested on a PC).
|
|
8
9
|
// A project is at the same place inside the sandbox whichever project started it (git worktrees and Claude's project
|
|
9
10
|
// folders store absolute paths):
|
|
10
11
|
// - a project inside a root is always under the root's folder, C:\Projects\<root>\<name> (also as the launch folder);
|
|
@@ -115,6 +116,25 @@ export function ownGuestFolder(hostDir, mirror, taken = []) {
|
|
|
115
116
|
if (free(base + '-' + i))
|
|
116
117
|
return base + '-' + i;
|
|
117
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Where a live folder (projects.liveFolders "hcs") appears: C:\Workspace\<name>, the name in the strict form the guest
|
|
121
|
+
* daemon accepts (lower-case letters, digits, '-' and '_'; no dots or spaces), -2, -3, ... when it overlaps `taken`.
|
|
122
|
+
*/
|
|
123
|
+
export function liveGuestFolder(hostDir, taken = []) {
|
|
124
|
+
const base = path.win32
|
|
125
|
+
.basename(resolveDir(hostDir))
|
|
126
|
+
.toLowerCase()
|
|
127
|
+
.replace(/[^a-z0-9_-]+/g, '-')
|
|
128
|
+
.replace(/^[-_]+|-+$/g, '')
|
|
129
|
+
.slice(0, 56) || 'project';
|
|
130
|
+
const free = (guest) => !taken.some((t) => isWithin(t, guest) || isWithin(guest, t));
|
|
131
|
+
const at = (name) => WORKSPACES_GUEST + '\\' + name;
|
|
132
|
+
if (free(at(base)))
|
|
133
|
+
return at(base);
|
|
134
|
+
for (let i = 2;; i++)
|
|
135
|
+
if (free(at(base + '-' + i)))
|
|
136
|
+
return at(base + '-' + i);
|
|
137
|
+
}
|
|
118
138
|
/**
|
|
119
139
|
* Guest folders for the project roots (C:\Projects\<name>, unique; with guestWorkspace "mirror" at its PC path when
|
|
120
140
|
* the sandbox can show it there) and the projects mapped at boot (ownGuestFolder, unique against `taken` and each
|
package/dist/sync/watcher.js
CHANGED
|
@@ -18,6 +18,7 @@ import { SandboxChannel } from '../host/channel/index.js';
|
|
|
18
18
|
import { ProjectRegistry } from '../host/projects/registry.js';
|
|
19
19
|
import { ContainedStorage, setNameOf, storageOf } from '../host/storage/index.js';
|
|
20
20
|
import { clearSecrets } from '../host/secrets/index.js';
|
|
21
|
+
import { clearLiveRecord } from '../host/engines/index.js';
|
|
21
22
|
import { endSharing, sharingDeps } from '../host/sharing/index.js';
|
|
22
23
|
import { PACKAGE_ROOT } from '../core/package-info.js';
|
|
23
24
|
import { syncBack } from './sync-cycle.js';
|
|
@@ -373,6 +374,14 @@ export class Watcher {
|
|
|
373
374
|
catch (e) {
|
|
374
375
|
this.note('could not remove the one-shot secrets: ' + errMsg(e));
|
|
375
376
|
}
|
|
377
|
+
// Live folders (HCS): their shares ended with the sandbox, so their record and key go too.
|
|
378
|
+
try {
|
|
379
|
+
if (!isSandboxRunning() && clearLiveRecord(this.storageRoot, this.dataPath))
|
|
380
|
+
this.note('live folders: record cleared');
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
this.note('could not clear the live-folder record: ' + errMsg(e));
|
|
384
|
+
}
|
|
376
385
|
// An all-users install: the record of whose sandbox this was goes, and the group loses access to the folders.
|
|
377
386
|
const sharing = sharingDeps(PACKAGE_ROOT);
|
|
378
387
|
if (sharing)
|
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-UAQPI5XD.css" />
|
|
9
|
-
<script type="module" src="/main-
|
|
9
|
+
<script type="module" src="/main-7DJZ2M27.js"></script>
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|