dsh-plugin-wsl-env 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +154 -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-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.js +2 -2
- 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 +350 -74
- package/lib/preflight.d.ts +46 -3
- package/lib/preflight.js +83 -14
- package/lib/shell-env.d.ts +9 -3
- package/lib/shell-env.js +121 -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
|
|
@@ -742,6 +862,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
742
862
|
runnerProgram: profile.argv[0] ?? "",
|
|
743
863
|
workdir: resolved.workdir,
|
|
744
864
|
windowsDrive: profile.windowsDrive,
|
|
865
|
+
sessionScope: useScope ? "session" : "per-command",
|
|
745
866
|
});
|
|
746
867
|
}
|
|
747
868
|
: undefined,
|
|
@@ -752,7 +873,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
752
873
|
if (this.sandbox === undefined)
|
|
753
874
|
return result;
|
|
754
875
|
if (confined === undefined) {
|
|
755
|
-
return ({ ...result, sandbox: { mode, denied: false } });
|
|
876
|
+
return ({ ...result, sandbox: { mode, denied: false, sessionScope: useScope ? "session" : "per-command" } });
|
|
756
877
|
}
|
|
757
878
|
const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
|
|
758
879
|
const runnerFailure = classifyRunnerFailure(result.exitCode, result.stderr.text, runnerFailureRules);
|
|
@@ -769,11 +890,19 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
769
890
|
denied: matchesSignature(result.exitCode, result.stderr.text, denialSignatures),
|
|
770
891
|
enforcement,
|
|
771
892
|
windowsDrive,
|
|
893
|
+
sessionScope: useScope ? "session" : "per-command",
|
|
772
894
|
},
|
|
773
895
|
});
|
|
774
896
|
}, (error) => {
|
|
775
897
|
if (resolved.signal?.aborted === true)
|
|
776
898
|
resolved.signal.throwIfAborted();
|
|
899
|
+
// The native agent reports a missing workdir as a spawn failure without
|
|
900
|
+
// naming it. Retry that command on the one-shot path, whose `--cd`
|
|
901
|
+
// warning names the directory — the failure path pays the extra spawn,
|
|
902
|
+
// the hot path does not.
|
|
903
|
+
if (error instanceof SpawnError) {
|
|
904
|
+
return this.executeOneShot(resolved, distro, shell).then((fallback) => fallback.result());
|
|
905
|
+
}
|
|
777
906
|
throw error;
|
|
778
907
|
});
|
|
779
908
|
// The agent handle IS the upstream execution shape at runtime; its typedef
|
|
@@ -831,13 +960,14 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
831
960
|
runnerProgram: profile.argv[0] ?? "",
|
|
832
961
|
workdir: resolved.workdir,
|
|
833
962
|
windowsDrive: profile.windowsDrive,
|
|
963
|
+
sessionScope: "per-command",
|
|
834
964
|
});
|
|
835
965
|
});
|
|
836
966
|
return WslShellExecutor.decorateResult(execution, (result) => {
|
|
837
967
|
this.throwIfDistroUnavailable(result, distro);
|
|
838
968
|
this.throwIfWorkdirMissing(result, resolved.workdir);
|
|
839
969
|
if (confined === undefined) {
|
|
840
|
-
return ({ ...result, sandbox: { mode, denied: false } });
|
|
970
|
+
return ({ ...result, sandbox: { mode, denied: false, sessionScope: "per-command" } });
|
|
841
971
|
}
|
|
842
972
|
const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
|
|
843
973
|
// Runner failure outranks denial because the command did not run at all.
|
|
@@ -851,6 +981,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
851
981
|
denied: matchesSignature(result.exitCode, result.stderr.text, denialSignatures),
|
|
852
982
|
enforcement,
|
|
853
983
|
windowsDrive,
|
|
984
|
+
sessionScope: "per-command",
|
|
854
985
|
},
|
|
855
986
|
});
|
|
856
987
|
}, (error) => {
|
|
@@ -867,7 +998,10 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
867
998
|
export class WslFileSystem extends LocalFileSystem {
|
|
868
999
|
static inject = ["sandboxPolicy"];
|
|
869
1000
|
static Config = z.object({
|
|
870
|
-
/**
|
|
1001
|
+
/**
|
|
1002
|
+
* Distro mode: empty/`"auto"` follows the target path, `"default"` forces
|
|
1003
|
+
* WSL's default, and an explicit name pins the filesystem.
|
|
1004
|
+
*/
|
|
871
1005
|
distro: z.string().default(""),
|
|
872
1006
|
/**
|
|
873
1007
|
* Default workdir for relative input; accepts a Linux path, a UNC path, or
|
|
@@ -897,7 +1031,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
897
1031
|
* `sessionSandbox`; the preflight refuses with the remedy. Off for direct
|
|
898
1032
|
* constructions; on in the shipped preset.
|
|
899
1033
|
*/
|
|
900
|
-
sessionSandbox: z.boolean().default(false),
|
|
1034
|
+
sessionSandbox: z.union([z.boolean(), z.const("auto")]).default(false),
|
|
901
1035
|
/**
|
|
902
1036
|
* Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
|
|
903
1037
|
* Windows drive from confined commands — same meaning and same honest
|
|
@@ -951,16 +1085,20 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
951
1085
|
if (refusal !== null)
|
|
952
1086
|
throw new Error(refusal.message);
|
|
953
1087
|
super(ctx, config);
|
|
954
|
-
this.resolvedDistro = config.distro;
|
|
1088
|
+
this.resolvedDistro = pinnedDistro(config.distro) ?? "";
|
|
955
1089
|
this.resolvedHome = undefined;
|
|
1090
|
+
this.resolvedHomes = new Map();
|
|
956
1091
|
this.defaultMode = ctx.sandboxPolicy.defaultMode;
|
|
957
1092
|
this.agentSubstrateInstance = undefined;
|
|
1093
|
+
this.agentSubstrates = new Map();
|
|
958
1094
|
this.sandbox = new WslSandbox({ wslPath: config.wslPath, maskWindowsDrive: config.maskWindowsDrive });
|
|
959
1095
|
this.agentInstance = undefined;
|
|
1096
|
+
this.agentInstances = new Map();
|
|
960
1097
|
this.sessionRuntimes = sessionRuntimes;
|
|
961
1098
|
this.sessionRuntimeEntries = new Map();
|
|
962
1099
|
this.sessionScopeProbe = (argv) => runCapture(argv);
|
|
963
|
-
this.sessionRuntimePreflight =
|
|
1100
|
+
this.sessionRuntimePreflight = new Set();
|
|
1101
|
+
this.autoSessionScope = new Map();
|
|
964
1102
|
// The preset's disposer releases the runtime; the shell executor's effect
|
|
965
1103
|
// releases its own reference, so the scope stops after the last one.
|
|
966
1104
|
ctx.effect(() => () => this.disposeSessionRuntimes().catch(() => { }), "wsl fs session runtimes");
|
|
@@ -978,11 +1116,10 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
978
1116
|
return config.sandbox ? this.defaultMode : undefined;
|
|
979
1117
|
}
|
|
980
1118
|
/**
|
|
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).
|
|
1119
|
+
* The resident agent serving the default/pinned distro's in-distro side
|
|
1120
|
+
* work, created on first use and kept for the provider's lifetime.
|
|
984
1121
|
*
|
|
985
|
-
* @returns the agent for the pinned distro.
|
|
1122
|
+
* @returns the agent for the default/pinned distro.
|
|
986
1123
|
*/
|
|
987
1124
|
async publicationAgent() {
|
|
988
1125
|
if (this.agentInstance === undefined) {
|
|
@@ -991,8 +1128,26 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
991
1128
|
return this.agentInstance;
|
|
992
1129
|
}
|
|
993
1130
|
/**
|
|
994
|
-
* The agent
|
|
995
|
-
*
|
|
1131
|
+
* The resident agent for one distro. The default/pinned distro keeps the
|
|
1132
|
+
* legacy {@link publicationAgent} cache; every other distro gets its own
|
|
1133
|
+
* entry, because the shared agent is a per-distro process singleton.
|
|
1134
|
+
*
|
|
1135
|
+
* @param distro - the distro name.
|
|
1136
|
+
* @returns the agent for that distro.
|
|
1137
|
+
*/
|
|
1138
|
+
async publicationAgentFor(distro) {
|
|
1139
|
+
if (this.resolvedDistro.length > 0 && distro === this.resolvedDistro)
|
|
1140
|
+
return this.publicationAgent();
|
|
1141
|
+
const cached = this.agentInstances.get(distro);
|
|
1142
|
+
if (cached !== undefined)
|
|
1143
|
+
return cached;
|
|
1144
|
+
const agent = sharedAgent(distro);
|
|
1145
|
+
this.agentInstances.set(distro, agent);
|
|
1146
|
+
return agent;
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* The agent-backed substrate for the default/pinned distro, built on first
|
|
1150
|
+
* use over the shared resident agent.
|
|
996
1151
|
*
|
|
997
1152
|
* @returns the substrate.
|
|
998
1153
|
*/
|
|
@@ -1008,6 +1163,40 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1008
1163
|
}
|
|
1009
1164
|
return this.agentSubstrateInstance;
|
|
1010
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* The agent-backed substrate for one distro. The default/pinned distro keeps
|
|
1168
|
+
* the legacy {@link agentSubstrate} accessor (and its test override); every
|
|
1169
|
+
* other distro gets a substrate that mints its own UNC identities and
|
|
1170
|
+
* addresses its own resident.
|
|
1171
|
+
*
|
|
1172
|
+
* @param distro - the distro name.
|
|
1173
|
+
* @returns the substrate for that distro.
|
|
1174
|
+
*/
|
|
1175
|
+
async agentSubstrateFor(distro) {
|
|
1176
|
+
if (this.resolvedDistro.length > 0 && distro === this.resolvedDistro)
|
|
1177
|
+
return this.agentSubstrate();
|
|
1178
|
+
const cached = this.agentSubstrates.get(distro);
|
|
1179
|
+
if (cached !== undefined)
|
|
1180
|
+
return cached;
|
|
1181
|
+
const config = this.config;
|
|
1182
|
+
const substrate = new AgentSubstrate({
|
|
1183
|
+
agent: await this.publicationAgentFor(distro),
|
|
1184
|
+
distro,
|
|
1185
|
+
diffBasisMaxBytes: config.diffBasisMaxBytes,
|
|
1186
|
+
agentFor: (policy) => this.mutationAgent(policy, distro),
|
|
1187
|
+
});
|
|
1188
|
+
this.agentSubstrates.set(distro, substrate);
|
|
1189
|
+
return substrate;
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* The substrate for one resolved target, following the target's UNC identity.
|
|
1193
|
+
*
|
|
1194
|
+
* @param target - the resolved target.
|
|
1195
|
+
* @returns the substrate for the target's distro.
|
|
1196
|
+
*/
|
|
1197
|
+
async substrateForTarget(target) {
|
|
1198
|
+
return this.agentSubstrateFor(await this.distroForTarget(target));
|
|
1199
|
+
}
|
|
1011
1200
|
/**
|
|
1012
1201
|
* The persistent session runtime for one confined mutation, when
|
|
1013
1202
|
* `sessionSandbox` is on. The key is the shell executor's key for the same
|
|
@@ -1046,14 +1235,26 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1046
1235
|
* @throws {Error} with the remedy when it cannot.
|
|
1047
1236
|
*/
|
|
1048
1237
|
async ensureSessionRuntimeUsable(distro) {
|
|
1049
|
-
if (this.sessionRuntimePreflight
|
|
1238
|
+
if (this.sessionRuntimePreflight.has(distro))
|
|
1050
1239
|
return;
|
|
1051
1240
|
const config = this.config;
|
|
1052
1241
|
const problem = await probeSessionRuntime({ wslPath: config.wslPath, distro, run: this.sessionScopeProbe });
|
|
1053
1242
|
if (problem !== null)
|
|
1054
1243
|
throw new Error(problem);
|
|
1055
1244
|
await ensureNativeSessionAgent({ wslPath: config.wslPath, distro, setNative: setSessionNativeAgent });
|
|
1056
|
-
this.sessionRuntimePreflight
|
|
1245
|
+
this.sessionRuntimePreflight.add(distro);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Whether this provider should use the session runtime for one distro, with
|
|
1249
|
+
* the `auto` downgrade and its bounded re-probe. `true` keeps the refusing
|
|
1250
|
+
* behavior; `false` stays off.
|
|
1251
|
+
*
|
|
1252
|
+
* @param distro - the resolved distro.
|
|
1253
|
+
* @returns whether to acquire the per-session scope.
|
|
1254
|
+
*/
|
|
1255
|
+
async wantsSessionRuntime(distro) {
|
|
1256
|
+
const config = this.config;
|
|
1257
|
+
return sessionRuntimeWanted(config.sessionSandbox, distro, () => this.ensureSessionRuntimeUsable(distro), this.autoSessionScope);
|
|
1057
1258
|
}
|
|
1058
1259
|
/**
|
|
1059
1260
|
* Release every runtime this provider holds. Called from the preset's
|
|
@@ -1078,26 +1279,27 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1078
1279
|
*
|
|
1079
1280
|
* @param policy - the resolved file-effect policy, or undefined when the
|
|
1080
1281
|
* caller pre-checked.
|
|
1282
|
+
* @param forDistro - the distro the mutation belongs to; derived from the
|
|
1283
|
+
* policy when the caller does not know it.
|
|
1081
1284
|
* @returns the agent to publish through.
|
|
1082
1285
|
*/
|
|
1083
|
-
async mutationAgent(policy) {
|
|
1286
|
+
async mutationAgent(policy, forDistro) {
|
|
1084
1287
|
const config = this.config;
|
|
1288
|
+
const distro = forDistro ?? await this.distroForPolicy(policy);
|
|
1085
1289
|
if (!isConfinedMutation({ policy, sandboxEnabled: config.sandbox })) {
|
|
1086
|
-
return this.
|
|
1290
|
+
return this.publicationAgentFor(distro);
|
|
1087
1291
|
}
|
|
1088
1292
|
// The untyped guard decided this is a confined policy but cannot narrow
|
|
1089
1293
|
// the parameter, so the confined shape is asserted once here (type-level
|
|
1090
1294
|
// only; `confinedPolicy` is the same value as `policy`).
|
|
1091
1295
|
const confinedPolicy = policy;
|
|
1092
|
-
const distro = await this.distro();
|
|
1093
1296
|
if (!(await this.sandbox.usable(distro))) {
|
|
1094
1297
|
// The probe's classified remedy — missing vs present-but-broken, with the
|
|
1095
1298
|
// distro's own package family — is what every other refusal surfaces;
|
|
1096
1299
|
// this path has the same failure, so it carries the same text.
|
|
1097
1300
|
throw new SandboxUnavailableError(confinedPolicy.mode, bwrapFailure(config.wslPath, distro) ?? missingBwrapRemedy(distro));
|
|
1098
1301
|
}
|
|
1099
|
-
if (
|
|
1100
|
-
await this.ensureSessionRuntimeUsable(distro);
|
|
1302
|
+
if (await this.wantsSessionRuntime(distro)) {
|
|
1101
1303
|
const runtime = this.sessionRuntimeFor(confinedPolicy, distro);
|
|
1102
1304
|
if (runtime !== undefined)
|
|
1103
1305
|
return runtime.agent;
|
|
@@ -1133,7 +1335,8 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1133
1335
|
return uncToPosix(target.targetKey)?.linuxPath ?? target.displayPath;
|
|
1134
1336
|
}
|
|
1135
1337
|
/**
|
|
1136
|
-
* The pinned
|
|
1338
|
+
* The distro used when no path supplies evidence: the pinned name, WSL's
|
|
1339
|
+
* default, or the default for the automatic modes.
|
|
1137
1340
|
*
|
|
1138
1341
|
* @returns the distro name.
|
|
1139
1342
|
*/
|
|
@@ -1143,6 +1346,60 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1143
1346
|
this.resolvedDistro = await defaultDistro({ wslPath: config.wslPath });
|
|
1144
1347
|
return this.resolvedDistro;
|
|
1145
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* The distro one path belongs to: an explicit pin wins; otherwise the path
|
|
1351
|
+
* itself, then the cwd or configured cwd, may name a distro; both absent
|
|
1352
|
+
* falls back to WSL's default.
|
|
1353
|
+
*
|
|
1354
|
+
* @param path - the caller path, in any coordinate system.
|
|
1355
|
+
* @param cwd - the caller's optional cwd override.
|
|
1356
|
+
* @returns the distro name.
|
|
1357
|
+
*/
|
|
1358
|
+
async distroForPath(path, cwd) {
|
|
1359
|
+
const config = this.config;
|
|
1360
|
+
const pinned = pinnedDistro(config.distro);
|
|
1361
|
+
if (pinned !== undefined)
|
|
1362
|
+
return pinned;
|
|
1363
|
+
if (!isDefaultDistro(config.distro)) {
|
|
1364
|
+
for (const candidate of [path, cwd ?? config.cwd]) {
|
|
1365
|
+
const named = distroOfUnc(candidate);
|
|
1366
|
+
if (named !== undefined)
|
|
1367
|
+
return named;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return this.distro();
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* The distro a resolved target belongs to. A UNC identity names it outright;
|
|
1374
|
+
* a host-coordinate target falls back to the configured/default distro.
|
|
1375
|
+
*
|
|
1376
|
+
* @param target - the resolved target.
|
|
1377
|
+
* @returns the distro name.
|
|
1378
|
+
*/
|
|
1379
|
+
async distroForTarget(target) {
|
|
1380
|
+
return uncToPosix(target.targetKey)?.distro ?? this.distroForPath(target.displayPath);
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* The distro a mutation's policy belongs to: the workspace root is the
|
|
1384
|
+
* strongest evidence, then the configured cwd, then the default.
|
|
1385
|
+
*
|
|
1386
|
+
* @param policy - the resolved policy, when the caller has one.
|
|
1387
|
+
* @returns the distro name.
|
|
1388
|
+
*/
|
|
1389
|
+
async distroForPolicy(policy) {
|
|
1390
|
+
const config = this.config;
|
|
1391
|
+
const pinned = pinnedDistro(config.distro);
|
|
1392
|
+
if (pinned !== undefined)
|
|
1393
|
+
return pinned;
|
|
1394
|
+
if (!isDefaultDistro(config.distro)) {
|
|
1395
|
+
for (const candidate of [policy?.workspaceRoot, config.cwd]) {
|
|
1396
|
+
const named = distroOfUnc(candidate);
|
|
1397
|
+
if (named !== undefined)
|
|
1398
|
+
return named;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return this.distro();
|
|
1402
|
+
}
|
|
1146
1403
|
/**
|
|
1147
1404
|
* Translate one caller path into the world path, enforcing the distro fence.
|
|
1148
1405
|
*
|
|
@@ -1152,13 +1409,13 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1152
1409
|
*/
|
|
1153
1410
|
async worldPath(path, cwd) {
|
|
1154
1411
|
const config = this.config;
|
|
1155
|
-
const distro = await this.
|
|
1412
|
+
const distro = await this.distroForPath(path, cwd);
|
|
1156
1413
|
let base = cwd ?? config.cwd;
|
|
1157
1414
|
if (String(base ?? "").trim().length === 0 && isRelativeWorldPath(path)) {
|
|
1158
1415
|
// An empty `cwd` means the distro user's home. Only relative input
|
|
1159
1416
|
// consults the base at all, which is also the only case that has to pay
|
|
1160
1417
|
// for the `wsl.exe` query — an absolute path never does.
|
|
1161
|
-
base = await distroHome(this);
|
|
1418
|
+
base = await distroHome(this, distro);
|
|
1162
1419
|
}
|
|
1163
1420
|
const world = toWorldPath(path, { distro, cwd: base });
|
|
1164
1421
|
const refusal = outsideDistroRefusal({ path, world, distro, restrictToDistro: config.restrictToDistro });
|
|
@@ -1183,10 +1440,11 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1183
1440
|
const linux = uncToPosix(world)?.linuxPath ?? windowsToLinuxMount(world);
|
|
1184
1441
|
if (linux === undefined)
|
|
1185
1442
|
throw new FsError(`cannot serve "${path}" from inside the distro`, "FS_IO_ERROR");
|
|
1443
|
+
const distro = uncToPosix(world)?.distro ?? await this.distroForPath(path, opts?.cwd);
|
|
1186
1444
|
try {
|
|
1187
1445
|
// The substrate's resolved shape carries plain-string keys; the seam's
|
|
1188
1446
|
// branded tokens ride one type-level cast (runtime shape unchanged).
|
|
1189
|
-
return (await (await this.
|
|
1447
|
+
return (await (await this.agentSubstrateFor(distro)).resolve(linux, opts?.signal));
|
|
1190
1448
|
}
|
|
1191
1449
|
catch (error) {
|
|
1192
1450
|
throw this.peerError(error);
|
|
@@ -1222,8 +1480,8 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1222
1480
|
}
|
|
1223
1481
|
if (policy.mode !== "workspace-write")
|
|
1224
1482
|
return target;
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1483
|
+
const distro = await this.distroForTarget(target);
|
|
1484
|
+
const fresh = await this.resolve(target.targetKey);
|
|
1227
1485
|
for (const root of wslWritableRoots(policy, distro)) {
|
|
1228
1486
|
if (isWorldPathUnder(fresh.targetKey, root, { distro }))
|
|
1229
1487
|
return fresh;
|
|
@@ -1240,7 +1498,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1240
1498
|
*/
|
|
1241
1499
|
async stat(target, signal) {
|
|
1242
1500
|
try {
|
|
1243
|
-
return (await (await this.
|
|
1501
|
+
return (await (await this.substrateForTarget(target)).stat(target, signal));
|
|
1244
1502
|
}
|
|
1245
1503
|
catch (error) {
|
|
1246
1504
|
throw this.peerError(error);
|
|
@@ -1260,8 +1518,9 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1260
1518
|
const linux = uncToPosix(world)?.linuxPath ?? windowsToLinuxMount(world);
|
|
1261
1519
|
if (linux === undefined)
|
|
1262
1520
|
throw new FsError(`cannot inspect "${path}" from inside the distro`, "FS_IO_ERROR");
|
|
1521
|
+
const distro = uncToPosix(world)?.distro ?? await this.distroForPath(path, opts?.cwd);
|
|
1263
1522
|
try {
|
|
1264
|
-
return (await (await this.
|
|
1523
|
+
return (await (await this.agentSubstrateFor(distro)).lstat(linux, signal));
|
|
1265
1524
|
}
|
|
1266
1525
|
catch (error) {
|
|
1267
1526
|
throw this.peerError(error);
|
|
@@ -1276,7 +1535,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1276
1535
|
*/
|
|
1277
1536
|
async readText(target, signal) {
|
|
1278
1537
|
try {
|
|
1279
|
-
return await (await this.
|
|
1538
|
+
return await (await this.substrateForTarget(target)).readText(target, signal);
|
|
1280
1539
|
}
|
|
1281
1540
|
catch (error) {
|
|
1282
1541
|
throw this.peerError(error);
|
|
@@ -1291,7 +1550,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1291
1550
|
*/
|
|
1292
1551
|
async streamText(target, signal) {
|
|
1293
1552
|
try {
|
|
1294
|
-
return await (await this.
|
|
1553
|
+
return await (await this.substrateForTarget(target)).streamText(target, signal);
|
|
1295
1554
|
}
|
|
1296
1555
|
catch (error) {
|
|
1297
1556
|
throw this.peerError(error);
|
|
@@ -1307,7 +1566,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1307
1566
|
*/
|
|
1308
1567
|
async readBytes(target, signal, maxBytes) {
|
|
1309
1568
|
try {
|
|
1310
|
-
return await (await this.
|
|
1569
|
+
return await (await this.substrateForTarget(target)).readBytes(target, signal, maxBytes);
|
|
1311
1570
|
}
|
|
1312
1571
|
catch (error) {
|
|
1313
1572
|
throw this.peerError(error);
|
|
@@ -1323,7 +1582,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1323
1582
|
*/
|
|
1324
1583
|
async readByteRange(target, range, signal) {
|
|
1325
1584
|
try {
|
|
1326
|
-
return await (await this.
|
|
1585
|
+
return await (await this.substrateForTarget(target)).readByteRange(target, range, signal);
|
|
1327
1586
|
}
|
|
1328
1587
|
catch (error) {
|
|
1329
1588
|
throw this.peerError(error);
|
|
@@ -1339,7 +1598,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1339
1598
|
*/
|
|
1340
1599
|
async listDir(target, signal) {
|
|
1341
1600
|
try {
|
|
1342
|
-
return (await (await this.
|
|
1601
|
+
return (await (await this.substrateForTarget(target)).listDir(target, signal));
|
|
1343
1602
|
}
|
|
1344
1603
|
catch (error) {
|
|
1345
1604
|
throw this.peerError(error);
|
|
@@ -1352,10 +1611,10 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1352
1611
|
* plain callback over image attachments, and `packages/api/session-controller`
|
|
1353
1612
|
* plus the UI's open/reveal path compare the return value against `undefined`
|
|
1354
1613
|
* without awaiting. The mapping is therefore built from facts that are already
|
|
1355
|
-
* known —
|
|
1356
|
-
* the default `
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1614
|
+
* known — a UNC host path names its own distro outright; a non-UNC path uses
|
|
1615
|
+
* the pinned/config default once `distro()` has resolved it, and the
|
|
1616
|
+
* configured `cwd`, or the resolved home for a relative path. A call that
|
|
1617
|
+
* would have to ask `wsl.exe` — a cold default distro with no UNC, or a
|
|
1359
1618
|
* relative path before the home is known — answers `undefined`, which every
|
|
1360
1619
|
* consumer reads as "no mapping", the same answer the ssh provider gives for
|
|
1361
1620
|
* a path it cannot map.
|
|
@@ -1373,16 +1632,21 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1373
1632
|
*/
|
|
1374
1633
|
processPathFromHostPath(hostPath) {
|
|
1375
1634
|
const config = this.config;
|
|
1376
|
-
|
|
1635
|
+
const choice = selectDistro({
|
|
1636
|
+
configured: config.distro,
|
|
1637
|
+
candidates: [hostPath, config.cwd],
|
|
1638
|
+
fallback: this.resolvedDistro,
|
|
1639
|
+
});
|
|
1640
|
+
if (choice.distro.length === 0)
|
|
1377
1641
|
return undefined;
|
|
1378
1642
|
let base = config.cwd;
|
|
1379
1643
|
if (String(base ?? "").trim().length === 0 && isRelativeWorldPath(hostPath)) {
|
|
1380
|
-
|
|
1644
|
+
base = this.resolvedHomes.get(choice.distro) ?? (choice.distro === this.resolvedDistro ? this.resolvedHome : undefined);
|
|
1645
|
+
if (base === undefined)
|
|
1381
1646
|
return undefined;
|
|
1382
|
-
base = this.resolvedHome;
|
|
1383
1647
|
}
|
|
1384
|
-
const world = toWorldPath(hostPath, { distro:
|
|
1385
|
-
const refusal = outsideDistroRefusal({ path: hostPath, world, distro:
|
|
1648
|
+
const world = toWorldPath(hostPath, { distro: choice.distro, cwd: base });
|
|
1649
|
+
const refusal = outsideDistroRefusal({ path: hostPath, world, distro: choice.distro, restrictToDistro: config.restrictToDistro });
|
|
1386
1650
|
return refusal === null ? world : undefined;
|
|
1387
1651
|
}
|
|
1388
1652
|
/**
|
|
@@ -1409,7 +1673,19 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1409
1673
|
if (parsed === undefined) {
|
|
1410
1674
|
throw new FsError(`cannot watch "${target.displayPath}": not a path inside the distro`, "FS_IO_ERROR");
|
|
1411
1675
|
}
|
|
1412
|
-
const
|
|
1676
|
+
const pinned = pinnedDistro(config.distro);
|
|
1677
|
+
const configuredName = pinned ?? (isDefaultDistro(config.distro) ? await this.distro() : undefined);
|
|
1678
|
+
if (configuredName !== undefined) {
|
|
1679
|
+
const refusal = outsideDistroRefusal({
|
|
1680
|
+
path: target.displayPath,
|
|
1681
|
+
world: target.targetKey,
|
|
1682
|
+
distro: configuredName,
|
|
1683
|
+
restrictToDistro: config.restrictToDistro,
|
|
1684
|
+
});
|
|
1685
|
+
if (refusal !== null)
|
|
1686
|
+
throw new FsError(refusal.message, refusal.code);
|
|
1687
|
+
}
|
|
1688
|
+
const distro = parsed.distro;
|
|
1413
1689
|
return armDistroWatcher({
|
|
1414
1690
|
wslPath: config.wslPath,
|
|
1415
1691
|
distro,
|
|
@@ -1439,7 +1715,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1439
1715
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
1440
1716
|
const fenced = await this.checkedTarget(target, policy);
|
|
1441
1717
|
try {
|
|
1442
|
-
return (await (await this.
|
|
1718
|
+
return (await (await this.substrateForTarget(fenced)).writeText(fenced, content, expected, signal, policy));
|
|
1443
1719
|
}
|
|
1444
1720
|
catch (error) {
|
|
1445
1721
|
throw this.peerError(error);
|
|
@@ -1461,7 +1737,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1461
1737
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
1462
1738
|
const fenced = await this.checkedTarget(target, policy);
|
|
1463
1739
|
try {
|
|
1464
|
-
return (await (await this.
|
|
1740
|
+
return (await (await this.substrateForTarget(fenced)).editText(fenced, edit, expected, signal, policy));
|
|
1465
1741
|
}
|
|
1466
1742
|
catch (error) {
|
|
1467
1743
|
throw this.peerError(error);
|