dsh-plugin-wsl-env 0.12.0 → 0.13.1
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/CHANGELOG.md +195 -0
- package/CONTRIBUTING.md +28 -6
- package/README.md +13 -6
- package/SECURITY.md +11 -1
- package/SUPPORT.md +29 -2
- package/cordis.patch.yml +9 -2
- package/docs/ARCHITECTURE.md +7 -5
- package/docs/CONFIGURATION.md +75 -16
- package/docs/LIMITATIONS.md +47 -7
- package/docs/MIGRATION.md +98 -0
- package/docs/PARITY.md +1 -1
- package/docs/PERFORMANCE.md +60 -0
- package/docs/QUICKSTART.md +125 -0
- package/docs/README.zh.md +6 -4
- package/docs/ROADMAP-1.0.md +780 -0
- package/docs/ROADMAP.md +4 -0
- package/docs/UPSTREAM-FILE-REFERENCE-SEAM.md +80 -0
- package/docs/UPSTREAM-FSIO-EXPORT.md +3 -2
- package/docs/UPSTREAM-LSP-SEAM.md +90 -0
- package/docs/UPSTREAM-SESSION-NOTICE.md +95 -0
- package/lib/agent-errors.d.ts +19 -0
- package/lib/agent-errors.js +22 -0
- package/lib/agent-exec.d.ts +9 -2
- package/lib/agent-exec.js +4 -4
- package/lib/agent.d.ts +68 -5
- package/lib/agent.js +129 -22
- package/lib/distro-route.d.ts +75 -0
- package/lib/distro-route.js +86 -0
- package/lib/fs-routing.d.ts +4 -1
- package/lib/index.d.ts +166 -37
- package/lib/index.js +358 -74
- package/lib/preflight.d.ts +46 -3
- package/lib/preflight.js +90 -15
- package/lib/shell-env.d.ts +9 -3
- package/lib/shell-env.js +128 -43
- package/lib/subprocess.d.ts +12 -3
- package/lib/subprocess.js +21 -3
- package/package.json +23 -6
- package/scripts/diagnose.sh +9 -88
- package/scripts/doctor.mjs +548 -0
- package/scripts/profile-preset.mjs +254 -0
package/lib/index.js
CHANGED
|
@@ -46,10 +46,11 @@ import { FsError } from "@deepseek-ai/dsh-fs";
|
|
|
46
46
|
import { canonicalPath, classifyRunnerFailure, isRunnerSpawnFailure, matchesSignature, SandboxUnavailableError, writableRoots } from "@deepseek-ai/dsh-sandbox";
|
|
47
47
|
import { isAnotherDistrosUnc, isRelativeWorldPath, isWorldPathUnder, isWslUnc, posixToUnc, toLinuxPath, toWorldPath, uncToPosix, windowsToLinuxMount } from "./paths.js";
|
|
48
48
|
import { isConfinedMutation, mutationModeRefusal, outsideDistroRefusal, shareSubstrateRefusal, substrateFailure, workspaceWriteDenial } from "./fs-decisions.js";
|
|
49
|
+
import { distroOfUnc, isDefaultDistro, pinnedDistro, selectDistro } from "./distro-route.js";
|
|
49
50
|
import { WslSandbox } from "./sandbox.js";
|
|
50
51
|
import { bwrapFailure, missingBwrapRemedy, withBwrapHint } from "./sandbox-core.js";
|
|
51
52
|
import { armDistroWatcher } from "./watcher.js";
|
|
52
|
-
import { AgentUnavailableError } from "./agent-errors.js";
|
|
53
|
+
import { AgentUnavailableError, SpawnError } from "./agent-errors.js";
|
|
53
54
|
import { agentExecutionHandle } from "./agent-exec.js";
|
|
54
55
|
import { sharedAgent } from "./agent-shared.js";
|
|
55
56
|
import { AgentSubstrate } from "./fs-substrate.js";
|
|
@@ -102,20 +103,32 @@ function configuredCwd(config) {
|
|
|
102
103
|
* An empty `cwd` cannot mean the shipped default (`process.cwd()`, a Windows
|
|
103
104
|
* directory no `wsl.exe --cd` accepts) and it must not mean a hardcoded author
|
|
104
105
|
* path either, so both providers agree on this instead: the distro's own home,
|
|
105
|
-
* asked of the distro once per
|
|
106
|
+
* asked of the distro once per (provider, distro) pair.
|
|
106
107
|
*
|
|
107
108
|
* @param owner - a WslShellExecutor or WslFileSystem.
|
|
109
|
+
* @param distro - the distro whose home is wanted.
|
|
108
110
|
* @returns an absolute Linux path.
|
|
109
111
|
*/
|
|
110
|
-
async function distroHome(owner) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
async function distroHome(owner, distro) {
|
|
113
|
+
const cached = owner.resolvedHomes.get(distro);
|
|
114
|
+
if (cached !== undefined)
|
|
115
|
+
return cached;
|
|
116
|
+
// The single-home field predates the per-distro map and is still the test
|
|
117
|
+
// substitute for "a home is already known"; honour it before spawning.
|
|
118
|
+
if (owner.resolvedHome !== undefined && owner.resolvedDistro.length > 0 && distro === owner.resolvedDistro) {
|
|
119
|
+
owner.resolvedHomes.set(distro, owner.resolvedHome);
|
|
120
|
+
return owner.resolvedHome;
|
|
121
|
+
}
|
|
122
|
+
// Both providers carry `wslPath` in their own config; the base classes'
|
|
123
|
+
// `config` types do not know the key, so the shape is asserted here.
|
|
124
|
+
const config = owner.config;
|
|
125
|
+
const home = await linuxHomePath(distro, { wslPath: config.wslPath });
|
|
126
|
+
owner.resolvedHomes.set(distro, home);
|
|
127
|
+
// The legacy single-home cache is still what `processPathFromHostPath` and
|
|
128
|
+
// the existing tests read; keep it warm for the resolved default/pin.
|
|
129
|
+
if (owner.resolvedDistro.length > 0 && distro === owner.resolvedDistro)
|
|
130
|
+
owner.resolvedHome = home;
|
|
131
|
+
return home;
|
|
119
132
|
}
|
|
120
133
|
/**
|
|
121
134
|
* The roots a `workspace-write` policy may write under, in the vocabulary this
|
|
@@ -147,6 +160,48 @@ function wslWritableRoots(policy, distro) {
|
|
|
147
160
|
return [...roots];
|
|
148
161
|
}
|
|
149
162
|
const inheritedSpawnSpec = LocalBashExecutor.prototype.spawnSpec;
|
|
163
|
+
/**
|
|
164
|
+
* How long an `auto` distro that could not host a session runtime stays
|
|
165
|
+
* downgraded before the probe is retried.
|
|
166
|
+
*/
|
|
167
|
+
const AUTO_SCOPE_RECHECK_MS = 60_000;
|
|
168
|
+
/**
|
|
169
|
+
* Whether a provider should use the session runtime for one distro.
|
|
170
|
+
*
|
|
171
|
+
* - `false` is off;
|
|
172
|
+
* - `true` refuses with the preflight remedy when the platform cannot host it;
|
|
173
|
+
* - `"auto"` downgrades to the per-command sandbox and reports that narrower
|
|
174
|
+
* scope in the result, re-probing a failed verdict at most once a minute so
|
|
175
|
+
* following the remedy (systemd, Linger) upgrades without an app restart.
|
|
176
|
+
*
|
|
177
|
+
* @param value - the configured `sessionSandbox` value.
|
|
178
|
+
* @param distro - the resolved distro.
|
|
179
|
+
* @param ensure - the provider's preflight, which throws the remedy on failure.
|
|
180
|
+
* @param cache - the provider's per-distro `auto` verdicts.
|
|
181
|
+
* @returns whether the session runtime should be used.
|
|
182
|
+
*/
|
|
183
|
+
async function sessionRuntimeWanted(value, distro, ensure, cache) {
|
|
184
|
+
if (value === false)
|
|
185
|
+
return false;
|
|
186
|
+
if (value === true) {
|
|
187
|
+
await ensure();
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const cached = cache.get(distro);
|
|
191
|
+
if (cached?.usable === true)
|
|
192
|
+
return true;
|
|
193
|
+
if (cached !== undefined && Date.now() - cached.checkedAt < AUTO_SCOPE_RECHECK_MS)
|
|
194
|
+
return false;
|
|
195
|
+
try {
|
|
196
|
+
await ensure();
|
|
197
|
+
cache.set(distro, { usable: true, checkedAt: Date.now() });
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
cache.set(distro, { usable: false, checkedAt: Date.now() });
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
150
205
|
/**
|
|
151
206
|
* Bash executor that runs every command inside a WSL distro.
|
|
152
207
|
*
|
|
@@ -160,7 +215,10 @@ const inheritedSpawnSpec = LocalBashExecutor.prototype.spawnSpec;
|
|
|
160
215
|
export class WslShellExecutor extends LocalBashExecutor {
|
|
161
216
|
static inject = ["subprocess", "sandboxPolicy"];
|
|
162
217
|
static Config = z.object({
|
|
163
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* Distro mode: empty/`"auto"` follows the request workdir, `"default"`
|
|
220
|
+
* forces WSL's default, and an explicit name pins every command.
|
|
221
|
+
*/
|
|
164
222
|
distro: z.string().default(""),
|
|
165
223
|
/** Path to `wsl.exe`; overridable for a non-standard install. */
|
|
166
224
|
wslPath: z.string().default("wsl.exe"),
|
|
@@ -233,7 +291,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
233
291
|
* `Linger`; the preflight refuses with the remedy when one is missing, and
|
|
234
292
|
* only a success is cached. Off by default; requires `agent: true`.
|
|
235
293
|
*/
|
|
236
|
-
sessionSandbox: z.boolean().default(false),
|
|
294
|
+
sessionSandbox: z.union([z.boolean(), z.const("auto")]).default(false),
|
|
237
295
|
});
|
|
238
296
|
/**
|
|
239
297
|
* @param ctx - the composition context.
|
|
@@ -242,9 +300,11 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
242
300
|
constructor(ctx, config) {
|
|
243
301
|
super(ctx, config);
|
|
244
302
|
configureAgentBackend(config.agentBackend ?? "auto");
|
|
245
|
-
this.resolvedDistro = config.distro;
|
|
303
|
+
this.resolvedDistro = pinnedDistro(config.distro) ?? "";
|
|
246
304
|
this.resolvedShell = config.shell;
|
|
305
|
+
this.resolvedShells = new Map();
|
|
247
306
|
this.resolvedHome = undefined;
|
|
307
|
+
this.resolvedHomes = new Map();
|
|
248
308
|
this.mode = config.sandbox ? ctx.sandboxPolicy.defaultMode : undefined;
|
|
249
309
|
this.sandbox = config.sandbox
|
|
250
310
|
? new WslSandbox({ wslPath: config.wslPath, maskWindowsDrive: config.maskWindowsDrive })
|
|
@@ -253,7 +313,8 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
253
313
|
this.sessionRuntimes = sessionRuntimes;
|
|
254
314
|
this.sessionRuntimeEntries = new Map();
|
|
255
315
|
this.sessionScopeProbe = (argv) => runCapture(argv);
|
|
256
|
-
this.sessionRuntimePreflight =
|
|
316
|
+
this.sessionRuntimePreflight = new Set();
|
|
317
|
+
this.autoSessionScope = new Map();
|
|
257
318
|
// The preset realm disposes when the session ends; the scope must go with
|
|
258
319
|
// it, or a dev server outlives the session that started it.
|
|
259
320
|
ctx.effect(() => () => this.disposeSessionRuntimes().catch(() => { }), "wsl session runtimes");
|
|
@@ -270,7 +331,8 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
270
331
|
return this.mode;
|
|
271
332
|
}
|
|
272
333
|
/**
|
|
273
|
-
* The distro
|
|
334
|
+
* The distro used when no path supplies evidence: the pinned name, WSL's
|
|
335
|
+
* default, or the default for the automatic modes.
|
|
274
336
|
*
|
|
275
337
|
* @returns the distro name.
|
|
276
338
|
*/
|
|
@@ -281,10 +343,33 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
281
343
|
return this.resolvedDistro;
|
|
282
344
|
}
|
|
283
345
|
/**
|
|
284
|
-
* The
|
|
285
|
-
* the
|
|
346
|
+
* The distro one command belongs to: an explicit pin wins; otherwise the
|
|
347
|
+
* request workdir, then the configured `cwd`, may name a distro; both
|
|
348
|
+
* absent falls back to WSL's default.
|
|
286
349
|
*
|
|
287
|
-
* @
|
|
350
|
+
* @param workdir - the request's workdir, in any coordinate system.
|
|
351
|
+
* @param cwd - the configured default workdir, when the caller has it.
|
|
352
|
+
* @returns the distro name.
|
|
353
|
+
*/
|
|
354
|
+
async distroFor(workdir, cwd) {
|
|
355
|
+
const config = this.config;
|
|
356
|
+
const pinned = pinnedDistro(config.distro);
|
|
357
|
+
if (pinned !== undefined)
|
|
358
|
+
return pinned;
|
|
359
|
+
if (!isDefaultDistro(config.distro)) {
|
|
360
|
+
for (const candidate of [workdir, cwd ?? configuredCwd(this.config)]) {
|
|
361
|
+
const named = distroOfUnc(candidate);
|
|
362
|
+
if (named !== undefined)
|
|
363
|
+
return named;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return this.distro();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The shell name for the default/pinned distro, as the shell tool presents
|
|
370
|
+
* it.
|
|
371
|
+
*
|
|
372
|
+
* @returns the basename of the shell, or `bash` when it cannot be named.
|
|
288
373
|
*/
|
|
289
374
|
async shellName() {
|
|
290
375
|
// Consumed by the shell tool after upstream change A: it names and describes
|
|
@@ -295,8 +380,30 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
295
380
|
return /^[a-z0-9_+-]+$/i.test(name) ? name : "bash";
|
|
296
381
|
}
|
|
297
382
|
/**
|
|
298
|
-
* The shell
|
|
299
|
-
*
|
|
383
|
+
* The shell for one distro: the configured shell, else that distro's login
|
|
384
|
+
* shell. The default/pinned distro keeps the legacy {@link resolvedShell}
|
|
385
|
+
* cache; every other distro gets an entry in {@link resolvedShells}.
|
|
386
|
+
*
|
|
387
|
+
* @param distro - the distro name.
|
|
388
|
+
* @returns an absolute path to the shell inside the distro.
|
|
389
|
+
*/
|
|
390
|
+
async shellFor(distro) {
|
|
391
|
+
const config = this.config;
|
|
392
|
+
if (config.shell.length > 0)
|
|
393
|
+
return config.shell;
|
|
394
|
+
if (this.resolvedDistro.length > 0 && distro === this.resolvedDistro)
|
|
395
|
+
return this.shell();
|
|
396
|
+
const cached = this.resolvedShells.get(distro);
|
|
397
|
+
if (cached !== undefined)
|
|
398
|
+
return cached;
|
|
399
|
+
const resolved = await defaultShell(distro, { wslPath: config.wslPath });
|
|
400
|
+
if (resolved !== undefined)
|
|
401
|
+
this.resolvedShells.set(distro, resolved);
|
|
402
|
+
return resolved ?? "bash";
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* The shell for the default/pinned distro: the configured one, else the
|
|
406
|
+
* login shell, resolved once and cached.
|
|
300
407
|
*
|
|
301
408
|
* @returns an absolute path to the shell inside the distro.
|
|
302
409
|
*/
|
|
@@ -376,14 +483,26 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
376
483
|
* @throws {Error} with the remedy when it cannot.
|
|
377
484
|
*/
|
|
378
485
|
async ensureSessionRuntimeUsable(distro) {
|
|
379
|
-
if (this.sessionRuntimePreflight
|
|
486
|
+
if (this.sessionRuntimePreflight.has(distro))
|
|
380
487
|
return;
|
|
381
488
|
const config = this.config;
|
|
382
489
|
const problem = await probeSessionRuntime({ wslPath: config.wslPath, distro, run: this.sessionScopeProbe });
|
|
383
490
|
if (problem !== null)
|
|
384
491
|
throw new Error(problem);
|
|
385
492
|
await ensureNativeSessionAgent({ wslPath: config.wslPath, distro, setNative: setSessionNativeAgent });
|
|
386
|
-
this.sessionRuntimePreflight
|
|
493
|
+
this.sessionRuntimePreflight.add(distro);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Whether this executor should use the session runtime for one distro, with
|
|
497
|
+
* the `auto` downgrade and its bounded re-probe. `true` keeps the refusing
|
|
498
|
+
* behavior; `false` stays off.
|
|
499
|
+
*
|
|
500
|
+
* @param distro - the resolved distro.
|
|
501
|
+
* @returns whether to acquire the per-session scope.
|
|
502
|
+
*/
|
|
503
|
+
async wantsSessionRuntime(distro) {
|
|
504
|
+
const config = this.config;
|
|
505
|
+
return sessionRuntimeWanted(config.sessionSandbox, distro, () => this.ensureSessionRuntimeUsable(distro), this.autoSessionScope);
|
|
387
506
|
}
|
|
388
507
|
/**
|
|
389
508
|
* Release every runtime this executor holds, stopping each scope. Called
|
|
@@ -532,10 +651,11 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
532
651
|
const tail = typeof spec?.relativeWorkdir === "string" ? spec.relativeWorkdir : undefined;
|
|
533
652
|
if (String(spec?.workdir ?? "").trim().length > 0 && tail === undefined)
|
|
534
653
|
return spec;
|
|
535
|
-
const
|
|
654
|
+
const distro = await this.distroFor(spec.workdir, configuredCwd(this.config));
|
|
655
|
+
const home = await distroHome(this, distro);
|
|
536
656
|
if (tail === undefined || tail.length === 0)
|
|
537
657
|
return { ...spec, workdir: home };
|
|
538
|
-
return { ...spec, workdir: toLinuxPath(tail, { distro
|
|
658
|
+
return { ...spec, workdir: toLinuxPath(tail, { distro, cwd: home }) };
|
|
539
659
|
}
|
|
540
660
|
/**
|
|
541
661
|
* Decorate a handle's foreground projection in place, memoized once. The
|
|
@@ -584,6 +704,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
584
704
|
denied: !runnerFailed && matchesSignature(proc.exitCode, stderr, facts.denialSignatures),
|
|
585
705
|
enforcement: facts.enforcement,
|
|
586
706
|
...(facts.windowsDrive ? { windowsDrive: facts.windowsDrive } : {}),
|
|
707
|
+
sessionScope: facts.sessionScope,
|
|
587
708
|
...(runnerFailed ? { runnerFailed } : {}),
|
|
588
709
|
};
|
|
589
710
|
}
|
|
@@ -647,9 +768,9 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
647
768
|
*/
|
|
648
769
|
async execute(spec) {
|
|
649
770
|
const config = this.config;
|
|
650
|
-
const distro = await this.distro();
|
|
651
|
-
const shell = await this.shell();
|
|
652
771
|
const resolved = await this.withDefaultWorkdir(spec);
|
|
772
|
+
const distro = await this.distroFor(resolved.workdir, configuredCwd(config));
|
|
773
|
+
const shell = await this.shellFor(distro);
|
|
653
774
|
this.throwIfWorkdirInAnotherDistro(resolved.workdir, distro);
|
|
654
775
|
if (config.agent) {
|
|
655
776
|
try {
|
|
@@ -700,9 +821,8 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
700
821
|
? await this.sandbox.confine(this.distroArgv(resolved, shell), { ...policy, mode: policy.mode }, { distro, signal: resolved.signal })
|
|
701
822
|
: undefined;
|
|
702
823
|
const mode = policy?.mode;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
const runtime = this.sessionRuntimeFor(policy, resolved, distro);
|
|
824
|
+
const useScope = await this.wantsSessionRuntime(distro);
|
|
825
|
+
const runtime = useScope ? this.sessionRuntimeFor(policy, resolved, distro) : undefined;
|
|
706
826
|
const agent = runtime !== undefined ? runtime.agent : await this.executionAgent(distro);
|
|
707
827
|
// A session runtime's own bwrap profile applies the mounts, so the
|
|
708
828
|
// command must NOT be wrapped a second time: the per-command probe still
|
|
@@ -711,6 +831,13 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
711
831
|
const argv = runtime !== undefined
|
|
712
832
|
? this.distroArgv(resolved, shell)
|
|
713
833
|
: confined !== undefined ? confined.argv : this.distroArgv(resolved, shell);
|
|
834
|
+
// The managed `DSH_*` snapshot merges last, exactly as the one-shot path's
|
|
835
|
+
// spawn env does (`ShellExecSpec`: `dshEnv` wins over `env`). The agent
|
|
836
|
+
// applies these as SETENV frames immediately before the command's EXEC, so
|
|
837
|
+
// the resident path now carries `DSH_HOME`, `DSH_SESSION_ID` and the
|
|
838
|
+
// `DSH_WSL_*` facts too — without this the whole registry was invisible on
|
|
839
|
+
// the default path while the one-shot fallback forwarded it.
|
|
840
|
+
const env = { ...resolved.env, ...resolved.dshEnv };
|
|
714
841
|
const execution = agentExecutionHandle({
|
|
715
842
|
agent,
|
|
716
843
|
// The one-shot path converts the spec's workdir into the distro's
|
|
@@ -719,6 +846,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
719
846
|
// reach `chdir` (that is the bug this line fixes).
|
|
720
847
|
cwd: toLinuxPath(resolved.workdir, { distro }),
|
|
721
848
|
argv,
|
|
849
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
722
850
|
timeoutMs: resolved.timeoutMs,
|
|
723
851
|
// The agent enforces the collect budget distro-side (the EXEC frame
|
|
724
852
|
// carries it); `maxSpillBytes` stays one-shot-only — the agent path has
|
|
@@ -742,6 +870,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
742
870
|
runnerProgram: profile.argv[0] ?? "",
|
|
743
871
|
workdir: resolved.workdir,
|
|
744
872
|
windowsDrive: profile.windowsDrive,
|
|
873
|
+
sessionScope: useScope ? "session" : "per-command",
|
|
745
874
|
});
|
|
746
875
|
}
|
|
747
876
|
: undefined,
|
|
@@ -752,7 +881,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
752
881
|
if (this.sandbox === undefined)
|
|
753
882
|
return result;
|
|
754
883
|
if (confined === undefined) {
|
|
755
|
-
return ({ ...result, sandbox: { mode, denied: false } });
|
|
884
|
+
return ({ ...result, sandbox: { mode, denied: false, sessionScope: useScope ? "session" : "per-command" } });
|
|
756
885
|
}
|
|
757
886
|
const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
|
|
758
887
|
const runnerFailure = classifyRunnerFailure(result.exitCode, result.stderr.text, runnerFailureRules);
|
|
@@ -769,11 +898,19 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
769
898
|
denied: matchesSignature(result.exitCode, result.stderr.text, denialSignatures),
|
|
770
899
|
enforcement,
|
|
771
900
|
windowsDrive,
|
|
901
|
+
sessionScope: useScope ? "session" : "per-command",
|
|
772
902
|
},
|
|
773
903
|
});
|
|
774
904
|
}, (error) => {
|
|
775
905
|
if (resolved.signal?.aborted === true)
|
|
776
906
|
resolved.signal.throwIfAborted();
|
|
907
|
+
// The native agent reports a missing workdir as a spawn failure without
|
|
908
|
+
// naming it. Retry that command on the one-shot path, whose `--cd`
|
|
909
|
+
// warning names the directory — the failure path pays the extra spawn,
|
|
910
|
+
// the hot path does not.
|
|
911
|
+
if (error instanceof SpawnError) {
|
|
912
|
+
return this.executeOneShot(resolved, distro, shell).then((fallback) => fallback.result());
|
|
913
|
+
}
|
|
777
914
|
throw error;
|
|
778
915
|
});
|
|
779
916
|
// The agent handle IS the upstream execution shape at runtime; its typedef
|
|
@@ -831,13 +968,14 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
831
968
|
runnerProgram: profile.argv[0] ?? "",
|
|
832
969
|
workdir: resolved.workdir,
|
|
833
970
|
windowsDrive: profile.windowsDrive,
|
|
971
|
+
sessionScope: "per-command",
|
|
834
972
|
});
|
|
835
973
|
});
|
|
836
974
|
return WslShellExecutor.decorateResult(execution, (result) => {
|
|
837
975
|
this.throwIfDistroUnavailable(result, distro);
|
|
838
976
|
this.throwIfWorkdirMissing(result, resolved.workdir);
|
|
839
977
|
if (confined === undefined) {
|
|
840
|
-
return ({ ...result, sandbox: { mode, denied: false } });
|
|
978
|
+
return ({ ...result, sandbox: { mode, denied: false, sessionScope: "per-command" } });
|
|
841
979
|
}
|
|
842
980
|
const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
|
|
843
981
|
// Runner failure outranks denial because the command did not run at all.
|
|
@@ -851,6 +989,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
851
989
|
denied: matchesSignature(result.exitCode, result.stderr.text, denialSignatures),
|
|
852
990
|
enforcement,
|
|
853
991
|
windowsDrive,
|
|
992
|
+
sessionScope: "per-command",
|
|
854
993
|
},
|
|
855
994
|
});
|
|
856
995
|
}, (error) => {
|
|
@@ -867,7 +1006,10 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
867
1006
|
export class WslFileSystem extends LocalFileSystem {
|
|
868
1007
|
static inject = ["sandboxPolicy"];
|
|
869
1008
|
static Config = z.object({
|
|
870
|
-
/**
|
|
1009
|
+
/**
|
|
1010
|
+
* Distro mode: empty/`"auto"` follows the target path, `"default"` forces
|
|
1011
|
+
* WSL's default, and an explicit name pins the filesystem.
|
|
1012
|
+
*/
|
|
871
1013
|
distro: z.string().default(""),
|
|
872
1014
|
/**
|
|
873
1015
|
* Default workdir for relative input; accepts a Linux path, a UNC path, or
|
|
@@ -897,7 +1039,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
897
1039
|
* `sessionSandbox`; the preflight refuses with the remedy. Off for direct
|
|
898
1040
|
* constructions; on in the shipped preset.
|
|
899
1041
|
*/
|
|
900
|
-
sessionSandbox: z.boolean().default(false),
|
|
1042
|
+
sessionSandbox: z.union([z.boolean(), z.const("auto")]).default(false),
|
|
901
1043
|
/**
|
|
902
1044
|
* Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
|
|
903
1045
|
* Windows drive from confined commands — same meaning and same honest
|
|
@@ -951,16 +1093,20 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
951
1093
|
if (refusal !== null)
|
|
952
1094
|
throw new Error(refusal.message);
|
|
953
1095
|
super(ctx, config);
|
|
954
|
-
this.resolvedDistro = config.distro;
|
|
1096
|
+
this.resolvedDistro = pinnedDistro(config.distro) ?? "";
|
|
955
1097
|
this.resolvedHome = undefined;
|
|
1098
|
+
this.resolvedHomes = new Map();
|
|
956
1099
|
this.defaultMode = ctx.sandboxPolicy.defaultMode;
|
|
957
1100
|
this.agentSubstrateInstance = undefined;
|
|
1101
|
+
this.agentSubstrates = new Map();
|
|
958
1102
|
this.sandbox = new WslSandbox({ wslPath: config.wslPath, maskWindowsDrive: config.maskWindowsDrive });
|
|
959
1103
|
this.agentInstance = undefined;
|
|
1104
|
+
this.agentInstances = new Map();
|
|
960
1105
|
this.sessionRuntimes = sessionRuntimes;
|
|
961
1106
|
this.sessionRuntimeEntries = new Map();
|
|
962
1107
|
this.sessionScopeProbe = (argv) => runCapture(argv);
|
|
963
|
-
this.sessionRuntimePreflight =
|
|
1108
|
+
this.sessionRuntimePreflight = new Set();
|
|
1109
|
+
this.autoSessionScope = new Map();
|
|
964
1110
|
// The preset's disposer releases the runtime; the shell executor's effect
|
|
965
1111
|
// releases its own reference, so the scope stops after the last one.
|
|
966
1112
|
ctx.effect(() => () => this.disposeSessionRuntimes().catch(() => { }), "wsl fs session runtimes");
|
|
@@ -978,11 +1124,10 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
978
1124
|
return config.sandbox ? this.defaultMode : undefined;
|
|
979
1125
|
}
|
|
980
1126
|
/**
|
|
981
|
-
* The resident agent serving
|
|
982
|
-
* on first use and kept for the provider's lifetime
|
|
983
|
-
* retires the process; a later call starts a fresh one).
|
|
1127
|
+
* The resident agent serving the default/pinned distro's in-distro side
|
|
1128
|
+
* work, created on first use and kept for the provider's lifetime.
|
|
984
1129
|
*
|
|
985
|
-
* @returns the agent for the pinned distro.
|
|
1130
|
+
* @returns the agent for the default/pinned distro.
|
|
986
1131
|
*/
|
|
987
1132
|
async publicationAgent() {
|
|
988
1133
|
if (this.agentInstance === undefined) {
|
|
@@ -991,8 +1136,26 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
991
1136
|
return this.agentInstance;
|
|
992
1137
|
}
|
|
993
1138
|
/**
|
|
994
|
-
* The agent
|
|
995
|
-
*
|
|
1139
|
+
* The resident agent for one distro. The default/pinned distro keeps the
|
|
1140
|
+
* legacy {@link publicationAgent} cache; every other distro gets its own
|
|
1141
|
+
* entry, because the shared agent is a per-distro process singleton.
|
|
1142
|
+
*
|
|
1143
|
+
* @param distro - the distro name.
|
|
1144
|
+
* @returns the agent for that distro.
|
|
1145
|
+
*/
|
|
1146
|
+
async publicationAgentFor(distro) {
|
|
1147
|
+
if (this.resolvedDistro.length > 0 && distro === this.resolvedDistro)
|
|
1148
|
+
return this.publicationAgent();
|
|
1149
|
+
const cached = this.agentInstances.get(distro);
|
|
1150
|
+
if (cached !== undefined)
|
|
1151
|
+
return cached;
|
|
1152
|
+
const agent = sharedAgent(distro);
|
|
1153
|
+
this.agentInstances.set(distro, agent);
|
|
1154
|
+
return agent;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* The agent-backed substrate for the default/pinned distro, built on first
|
|
1158
|
+
* use over the shared resident agent.
|
|
996
1159
|
*
|
|
997
1160
|
* @returns the substrate.
|
|
998
1161
|
*/
|
|
@@ -1008,6 +1171,40 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1008
1171
|
}
|
|
1009
1172
|
return this.agentSubstrateInstance;
|
|
1010
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* The agent-backed substrate for one distro. The default/pinned distro keeps
|
|
1176
|
+
* the legacy {@link agentSubstrate} accessor (and its test override); every
|
|
1177
|
+
* other distro gets a substrate that mints its own UNC identities and
|
|
1178
|
+
* addresses its own resident.
|
|
1179
|
+
*
|
|
1180
|
+
* @param distro - the distro name.
|
|
1181
|
+
* @returns the substrate for that distro.
|
|
1182
|
+
*/
|
|
1183
|
+
async agentSubstrateFor(distro) {
|
|
1184
|
+
if (this.resolvedDistro.length > 0 && distro === this.resolvedDistro)
|
|
1185
|
+
return this.agentSubstrate();
|
|
1186
|
+
const cached = this.agentSubstrates.get(distro);
|
|
1187
|
+
if (cached !== undefined)
|
|
1188
|
+
return cached;
|
|
1189
|
+
const config = this.config;
|
|
1190
|
+
const substrate = new AgentSubstrate({
|
|
1191
|
+
agent: await this.publicationAgentFor(distro),
|
|
1192
|
+
distro,
|
|
1193
|
+
diffBasisMaxBytes: config.diffBasisMaxBytes,
|
|
1194
|
+
agentFor: (policy) => this.mutationAgent(policy, distro),
|
|
1195
|
+
});
|
|
1196
|
+
this.agentSubstrates.set(distro, substrate);
|
|
1197
|
+
return substrate;
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* The substrate for one resolved target, following the target's UNC identity.
|
|
1201
|
+
*
|
|
1202
|
+
* @param target - the resolved target.
|
|
1203
|
+
* @returns the substrate for the target's distro.
|
|
1204
|
+
*/
|
|
1205
|
+
async substrateForTarget(target) {
|
|
1206
|
+
return this.agentSubstrateFor(await this.distroForTarget(target));
|
|
1207
|
+
}
|
|
1011
1208
|
/**
|
|
1012
1209
|
* The persistent session runtime for one confined mutation, when
|
|
1013
1210
|
* `sessionSandbox` is on. The key is the shell executor's key for the same
|
|
@@ -1046,14 +1243,26 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1046
1243
|
* @throws {Error} with the remedy when it cannot.
|
|
1047
1244
|
*/
|
|
1048
1245
|
async ensureSessionRuntimeUsable(distro) {
|
|
1049
|
-
if (this.sessionRuntimePreflight
|
|
1246
|
+
if (this.sessionRuntimePreflight.has(distro))
|
|
1050
1247
|
return;
|
|
1051
1248
|
const config = this.config;
|
|
1052
1249
|
const problem = await probeSessionRuntime({ wslPath: config.wslPath, distro, run: this.sessionScopeProbe });
|
|
1053
1250
|
if (problem !== null)
|
|
1054
1251
|
throw new Error(problem);
|
|
1055
1252
|
await ensureNativeSessionAgent({ wslPath: config.wslPath, distro, setNative: setSessionNativeAgent });
|
|
1056
|
-
this.sessionRuntimePreflight
|
|
1253
|
+
this.sessionRuntimePreflight.add(distro);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Whether this provider should use the session runtime for one distro, with
|
|
1257
|
+
* the `auto` downgrade and its bounded re-probe. `true` keeps the refusing
|
|
1258
|
+
* behavior; `false` stays off.
|
|
1259
|
+
*
|
|
1260
|
+
* @param distro - the resolved distro.
|
|
1261
|
+
* @returns whether to acquire the per-session scope.
|
|
1262
|
+
*/
|
|
1263
|
+
async wantsSessionRuntime(distro) {
|
|
1264
|
+
const config = this.config;
|
|
1265
|
+
return sessionRuntimeWanted(config.sessionSandbox, distro, () => this.ensureSessionRuntimeUsable(distro), this.autoSessionScope);
|
|
1057
1266
|
}
|
|
1058
1267
|
/**
|
|
1059
1268
|
* Release every runtime this provider holds. Called from the preset's
|
|
@@ -1078,26 +1287,27 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1078
1287
|
*
|
|
1079
1288
|
* @param policy - the resolved file-effect policy, or undefined when the
|
|
1080
1289
|
* caller pre-checked.
|
|
1290
|
+
* @param forDistro - the distro the mutation belongs to; derived from the
|
|
1291
|
+
* policy when the caller does not know it.
|
|
1081
1292
|
* @returns the agent to publish through.
|
|
1082
1293
|
*/
|
|
1083
|
-
async mutationAgent(policy) {
|
|
1294
|
+
async mutationAgent(policy, forDistro) {
|
|
1084
1295
|
const config = this.config;
|
|
1296
|
+
const distro = forDistro ?? await this.distroForPolicy(policy);
|
|
1085
1297
|
if (!isConfinedMutation({ policy, sandboxEnabled: config.sandbox })) {
|
|
1086
|
-
return this.
|
|
1298
|
+
return this.publicationAgentFor(distro);
|
|
1087
1299
|
}
|
|
1088
1300
|
// The untyped guard decided this is a confined policy but cannot narrow
|
|
1089
1301
|
// the parameter, so the confined shape is asserted once here (type-level
|
|
1090
1302
|
// only; `confinedPolicy` is the same value as `policy`).
|
|
1091
1303
|
const confinedPolicy = policy;
|
|
1092
|
-
const distro = await this.distro();
|
|
1093
1304
|
if (!(await this.sandbox.usable(distro))) {
|
|
1094
1305
|
// The probe's classified remedy — missing vs present-but-broken, with the
|
|
1095
1306
|
// distro's own package family — is what every other refusal surfaces;
|
|
1096
1307
|
// this path has the same failure, so it carries the same text.
|
|
1097
1308
|
throw new SandboxUnavailableError(confinedPolicy.mode, bwrapFailure(config.wslPath, distro) ?? missingBwrapRemedy(distro));
|
|
1098
1309
|
}
|
|
1099
|
-
if (
|
|
1100
|
-
await this.ensureSessionRuntimeUsable(distro);
|
|
1310
|
+
if (await this.wantsSessionRuntime(distro)) {
|
|
1101
1311
|
const runtime = this.sessionRuntimeFor(confinedPolicy, distro);
|
|
1102
1312
|
if (runtime !== undefined)
|
|
1103
1313
|
return runtime.agent;
|
|
@@ -1133,7 +1343,8 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1133
1343
|
return uncToPosix(target.targetKey)?.linuxPath ?? target.displayPath;
|
|
1134
1344
|
}
|
|
1135
1345
|
/**
|
|
1136
|
-
* The pinned
|
|
1346
|
+
* The distro used when no path supplies evidence: the pinned name, WSL's
|
|
1347
|
+
* default, or the default for the automatic modes.
|
|
1137
1348
|
*
|
|
1138
1349
|
* @returns the distro name.
|
|
1139
1350
|
*/
|
|
@@ -1143,6 +1354,60 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1143
1354
|
this.resolvedDistro = await defaultDistro({ wslPath: config.wslPath });
|
|
1144
1355
|
return this.resolvedDistro;
|
|
1145
1356
|
}
|
|
1357
|
+
/**
|
|
1358
|
+
* The distro one path belongs to: an explicit pin wins; otherwise the path
|
|
1359
|
+
* itself, then the cwd or configured cwd, may name a distro; both absent
|
|
1360
|
+
* falls back to WSL's default.
|
|
1361
|
+
*
|
|
1362
|
+
* @param path - the caller path, in any coordinate system.
|
|
1363
|
+
* @param cwd - the caller's optional cwd override.
|
|
1364
|
+
* @returns the distro name.
|
|
1365
|
+
*/
|
|
1366
|
+
async distroForPath(path, cwd) {
|
|
1367
|
+
const config = this.config;
|
|
1368
|
+
const pinned = pinnedDistro(config.distro);
|
|
1369
|
+
if (pinned !== undefined)
|
|
1370
|
+
return pinned;
|
|
1371
|
+
if (!isDefaultDistro(config.distro)) {
|
|
1372
|
+
for (const candidate of [path, cwd ?? config.cwd]) {
|
|
1373
|
+
const named = distroOfUnc(candidate);
|
|
1374
|
+
if (named !== undefined)
|
|
1375
|
+
return named;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return this.distro();
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* The distro a resolved target belongs to. A UNC identity names it outright;
|
|
1382
|
+
* a host-coordinate target falls back to the configured/default distro.
|
|
1383
|
+
*
|
|
1384
|
+
* @param target - the resolved target.
|
|
1385
|
+
* @returns the distro name.
|
|
1386
|
+
*/
|
|
1387
|
+
async distroForTarget(target) {
|
|
1388
|
+
return uncToPosix(target.targetKey)?.distro ?? this.distroForPath(target.displayPath);
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* The distro a mutation's policy belongs to: the workspace root is the
|
|
1392
|
+
* strongest evidence, then the configured cwd, then the default.
|
|
1393
|
+
*
|
|
1394
|
+
* @param policy - the resolved policy, when the caller has one.
|
|
1395
|
+
* @returns the distro name.
|
|
1396
|
+
*/
|
|
1397
|
+
async distroForPolicy(policy) {
|
|
1398
|
+
const config = this.config;
|
|
1399
|
+
const pinned = pinnedDistro(config.distro);
|
|
1400
|
+
if (pinned !== undefined)
|
|
1401
|
+
return pinned;
|
|
1402
|
+
if (!isDefaultDistro(config.distro)) {
|
|
1403
|
+
for (const candidate of [policy?.workspaceRoot, config.cwd]) {
|
|
1404
|
+
const named = distroOfUnc(candidate);
|
|
1405
|
+
if (named !== undefined)
|
|
1406
|
+
return named;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
return this.distro();
|
|
1410
|
+
}
|
|
1146
1411
|
/**
|
|
1147
1412
|
* Translate one caller path into the world path, enforcing the distro fence.
|
|
1148
1413
|
*
|
|
@@ -1152,13 +1417,13 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1152
1417
|
*/
|
|
1153
1418
|
async worldPath(path, cwd) {
|
|
1154
1419
|
const config = this.config;
|
|
1155
|
-
const distro = await this.
|
|
1420
|
+
const distro = await this.distroForPath(path, cwd);
|
|
1156
1421
|
let base = cwd ?? config.cwd;
|
|
1157
1422
|
if (String(base ?? "").trim().length === 0 && isRelativeWorldPath(path)) {
|
|
1158
1423
|
// An empty `cwd` means the distro user's home. Only relative input
|
|
1159
1424
|
// consults the base at all, which is also the only case that has to pay
|
|
1160
1425
|
// for the `wsl.exe` query — an absolute path never does.
|
|
1161
|
-
base = await distroHome(this);
|
|
1426
|
+
base = await distroHome(this, distro);
|
|
1162
1427
|
}
|
|
1163
1428
|
const world = toWorldPath(path, { distro, cwd: base });
|
|
1164
1429
|
const refusal = outsideDistroRefusal({ path, world, distro, restrictToDistro: config.restrictToDistro });
|
|
@@ -1183,10 +1448,11 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1183
1448
|
const linux = uncToPosix(world)?.linuxPath ?? windowsToLinuxMount(world);
|
|
1184
1449
|
if (linux === undefined)
|
|
1185
1450
|
throw new FsError(`cannot serve "${path}" from inside the distro`, "FS_IO_ERROR");
|
|
1451
|
+
const distro = uncToPosix(world)?.distro ?? await this.distroForPath(path, opts?.cwd);
|
|
1186
1452
|
try {
|
|
1187
1453
|
// The substrate's resolved shape carries plain-string keys; the seam's
|
|
1188
1454
|
// branded tokens ride one type-level cast (runtime shape unchanged).
|
|
1189
|
-
return (await (await this.
|
|
1455
|
+
return (await (await this.agentSubstrateFor(distro)).resolve(linux, opts?.signal));
|
|
1190
1456
|
}
|
|
1191
1457
|
catch (error) {
|
|
1192
1458
|
throw this.peerError(error);
|
|
@@ -1222,8 +1488,8 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1222
1488
|
}
|
|
1223
1489
|
if (policy.mode !== "workspace-write")
|
|
1224
1490
|
return target;
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1491
|
+
const distro = await this.distroForTarget(target);
|
|
1492
|
+
const fresh = await this.resolve(target.targetKey);
|
|
1227
1493
|
for (const root of wslWritableRoots(policy, distro)) {
|
|
1228
1494
|
if (isWorldPathUnder(fresh.targetKey, root, { distro }))
|
|
1229
1495
|
return fresh;
|
|
@@ -1240,7 +1506,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1240
1506
|
*/
|
|
1241
1507
|
async stat(target, signal) {
|
|
1242
1508
|
try {
|
|
1243
|
-
return (await (await this.
|
|
1509
|
+
return (await (await this.substrateForTarget(target)).stat(target, signal));
|
|
1244
1510
|
}
|
|
1245
1511
|
catch (error) {
|
|
1246
1512
|
throw this.peerError(error);
|
|
@@ -1260,8 +1526,9 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1260
1526
|
const linux = uncToPosix(world)?.linuxPath ?? windowsToLinuxMount(world);
|
|
1261
1527
|
if (linux === undefined)
|
|
1262
1528
|
throw new FsError(`cannot inspect "${path}" from inside the distro`, "FS_IO_ERROR");
|
|
1529
|
+
const distro = uncToPosix(world)?.distro ?? await this.distroForPath(path, opts?.cwd);
|
|
1263
1530
|
try {
|
|
1264
|
-
return (await (await this.
|
|
1531
|
+
return (await (await this.agentSubstrateFor(distro)).lstat(linux, signal));
|
|
1265
1532
|
}
|
|
1266
1533
|
catch (error) {
|
|
1267
1534
|
throw this.peerError(error);
|
|
@@ -1276,7 +1543,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1276
1543
|
*/
|
|
1277
1544
|
async readText(target, signal) {
|
|
1278
1545
|
try {
|
|
1279
|
-
return await (await this.
|
|
1546
|
+
return await (await this.substrateForTarget(target)).readText(target, signal);
|
|
1280
1547
|
}
|
|
1281
1548
|
catch (error) {
|
|
1282
1549
|
throw this.peerError(error);
|
|
@@ -1291,7 +1558,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1291
1558
|
*/
|
|
1292
1559
|
async streamText(target, signal) {
|
|
1293
1560
|
try {
|
|
1294
|
-
return await (await this.
|
|
1561
|
+
return await (await this.substrateForTarget(target)).streamText(target, signal);
|
|
1295
1562
|
}
|
|
1296
1563
|
catch (error) {
|
|
1297
1564
|
throw this.peerError(error);
|
|
@@ -1307,7 +1574,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1307
1574
|
*/
|
|
1308
1575
|
async readBytes(target, signal, maxBytes) {
|
|
1309
1576
|
try {
|
|
1310
|
-
return await (await this.
|
|
1577
|
+
return await (await this.substrateForTarget(target)).readBytes(target, signal, maxBytes);
|
|
1311
1578
|
}
|
|
1312
1579
|
catch (error) {
|
|
1313
1580
|
throw this.peerError(error);
|
|
@@ -1323,7 +1590,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1323
1590
|
*/
|
|
1324
1591
|
async readByteRange(target, range, signal) {
|
|
1325
1592
|
try {
|
|
1326
|
-
return await (await this.
|
|
1593
|
+
return await (await this.substrateForTarget(target)).readByteRange(target, range, signal);
|
|
1327
1594
|
}
|
|
1328
1595
|
catch (error) {
|
|
1329
1596
|
throw this.peerError(error);
|
|
@@ -1339,7 +1606,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1339
1606
|
*/
|
|
1340
1607
|
async listDir(target, signal) {
|
|
1341
1608
|
try {
|
|
1342
|
-
return (await (await this.
|
|
1609
|
+
return (await (await this.substrateForTarget(target)).listDir(target, signal));
|
|
1343
1610
|
}
|
|
1344
1611
|
catch (error) {
|
|
1345
1612
|
throw this.peerError(error);
|
|
@@ -1352,10 +1619,10 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1352
1619
|
* plain callback over image attachments, and `packages/api/session-controller`
|
|
1353
1620
|
* plus the UI's open/reveal path compare the return value against `undefined`
|
|
1354
1621
|
* without awaiting. The mapping is therefore built from facts that are already
|
|
1355
|
-
* known —
|
|
1356
|
-
* the default `
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1622
|
+
* known — a UNC host path names its own distro outright; a non-UNC path uses
|
|
1623
|
+
* the pinned/config default once `distro()` has resolved it, and the
|
|
1624
|
+
* configured `cwd`, or the resolved home for a relative path. A call that
|
|
1625
|
+
* would have to ask `wsl.exe` — a cold default distro with no UNC, or a
|
|
1359
1626
|
* relative path before the home is known — answers `undefined`, which every
|
|
1360
1627
|
* consumer reads as "no mapping", the same answer the ssh provider gives for
|
|
1361
1628
|
* a path it cannot map.
|
|
@@ -1373,16 +1640,21 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1373
1640
|
*/
|
|
1374
1641
|
processPathFromHostPath(hostPath) {
|
|
1375
1642
|
const config = this.config;
|
|
1376
|
-
|
|
1643
|
+
const choice = selectDistro({
|
|
1644
|
+
configured: config.distro,
|
|
1645
|
+
candidates: [hostPath, config.cwd],
|
|
1646
|
+
fallback: this.resolvedDistro,
|
|
1647
|
+
});
|
|
1648
|
+
if (choice.distro.length === 0)
|
|
1377
1649
|
return undefined;
|
|
1378
1650
|
let base = config.cwd;
|
|
1379
1651
|
if (String(base ?? "").trim().length === 0 && isRelativeWorldPath(hostPath)) {
|
|
1380
|
-
|
|
1652
|
+
base = this.resolvedHomes.get(choice.distro) ?? (choice.distro === this.resolvedDistro ? this.resolvedHome : undefined);
|
|
1653
|
+
if (base === undefined)
|
|
1381
1654
|
return undefined;
|
|
1382
|
-
base = this.resolvedHome;
|
|
1383
1655
|
}
|
|
1384
|
-
const world = toWorldPath(hostPath, { distro:
|
|
1385
|
-
const refusal = outsideDistroRefusal({ path: hostPath, world, distro:
|
|
1656
|
+
const world = toWorldPath(hostPath, { distro: choice.distro, cwd: base });
|
|
1657
|
+
const refusal = outsideDistroRefusal({ path: hostPath, world, distro: choice.distro, restrictToDistro: config.restrictToDistro });
|
|
1386
1658
|
return refusal === null ? world : undefined;
|
|
1387
1659
|
}
|
|
1388
1660
|
/**
|
|
@@ -1409,7 +1681,19 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1409
1681
|
if (parsed === undefined) {
|
|
1410
1682
|
throw new FsError(`cannot watch "${target.displayPath}": not a path inside the distro`, "FS_IO_ERROR");
|
|
1411
1683
|
}
|
|
1412
|
-
const
|
|
1684
|
+
const pinned = pinnedDistro(config.distro);
|
|
1685
|
+
const configuredName = pinned ?? (isDefaultDistro(config.distro) ? await this.distro() : undefined);
|
|
1686
|
+
if (configuredName !== undefined) {
|
|
1687
|
+
const refusal = outsideDistroRefusal({
|
|
1688
|
+
path: target.displayPath,
|
|
1689
|
+
world: target.targetKey,
|
|
1690
|
+
distro: configuredName,
|
|
1691
|
+
restrictToDistro: config.restrictToDistro,
|
|
1692
|
+
});
|
|
1693
|
+
if (refusal !== null)
|
|
1694
|
+
throw new FsError(refusal.message, refusal.code);
|
|
1695
|
+
}
|
|
1696
|
+
const distro = parsed.distro;
|
|
1413
1697
|
return armDistroWatcher({
|
|
1414
1698
|
wslPath: config.wslPath,
|
|
1415
1699
|
distro,
|
|
@@ -1439,7 +1723,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1439
1723
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
1440
1724
|
const fenced = await this.checkedTarget(target, policy);
|
|
1441
1725
|
try {
|
|
1442
|
-
return (await (await this.
|
|
1726
|
+
return (await (await this.substrateForTarget(fenced)).writeText(fenced, content, expected, signal, policy));
|
|
1443
1727
|
}
|
|
1444
1728
|
catch (error) {
|
|
1445
1729
|
throw this.peerError(error);
|
|
@@ -1461,7 +1745,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1461
1745
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
1462
1746
|
const fenced = await this.checkedTarget(target, policy);
|
|
1463
1747
|
try {
|
|
1464
|
-
return (await (await this.
|
|
1748
|
+
return (await (await this.substrateForTarget(fenced)).editText(fenced, edit, expected, signal, policy));
|
|
1465
1749
|
}
|
|
1466
1750
|
catch (error) {
|
|
1467
1751
|
throw this.peerError(error);
|