dsh-plugin-wsl-env 0.11.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 +181 -0
- package/CONTRIBUTING.md +29 -7
- package/README.md +124 -138
- package/SECURITY.md +11 -1
- package/SUPPORT.md +29 -2
- package/cordis.patch.yml +17 -19
- package/docs/ARCHITECTURE.md +30 -29
- package/docs/CONFIGURATION.md +81 -21
- package/docs/LIMITATIONS.md +71 -6
- 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 +117 -137
- 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 +353 -75
- package/lib/paths.d.ts +15 -0
- package/lib/paths.js +28 -0
- 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 -15
- package/scripts/diagnose.sh +9 -88
- package/scripts/doctor.mjs +548 -0
- package/scripts/profile-preset.mjs +254 -0
- package/lib/workspace-files-route.d.ts +0 -136
- package/lib/workspace-files-route.js +0 -225
- package/lib/workspace-files-wsl.d.ts +0 -257
- package/lib/workspace-files-wsl.js +0 -418
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,16 +300,21 @@ 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
|
-
this.sandbox = config.sandbox
|
|
309
|
+
this.sandbox = config.sandbox
|
|
310
|
+
? new WslSandbox({ wslPath: config.wslPath, maskWindowsDrive: config.maskWindowsDrive })
|
|
311
|
+
: undefined;
|
|
250
312
|
this.processFacts = new Map();
|
|
251
313
|
this.sessionRuntimes = sessionRuntimes;
|
|
252
314
|
this.sessionRuntimeEntries = new Map();
|
|
253
315
|
this.sessionScopeProbe = (argv) => runCapture(argv);
|
|
254
|
-
this.sessionRuntimePreflight =
|
|
316
|
+
this.sessionRuntimePreflight = new Set();
|
|
317
|
+
this.autoSessionScope = new Map();
|
|
255
318
|
// The preset realm disposes when the session ends; the scope must go with
|
|
256
319
|
// it, or a dev server outlives the session that started it.
|
|
257
320
|
ctx.effect(() => () => this.disposeSessionRuntimes().catch(() => { }), "wsl session runtimes");
|
|
@@ -268,7 +331,8 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
268
331
|
return this.mode;
|
|
269
332
|
}
|
|
270
333
|
/**
|
|
271
|
-
* 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.
|
|
272
336
|
*
|
|
273
337
|
* @returns the distro name.
|
|
274
338
|
*/
|
|
@@ -279,10 +343,33 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
279
343
|
return this.resolvedDistro;
|
|
280
344
|
}
|
|
281
345
|
/**
|
|
282
|
-
* The
|
|
283
|
-
* 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.
|
|
284
349
|
*
|
|
285
|
-
* @
|
|
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.
|
|
286
373
|
*/
|
|
287
374
|
async shellName() {
|
|
288
375
|
// Consumed by the shell tool after upstream change A: it names and describes
|
|
@@ -293,8 +380,30 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
293
380
|
return /^[a-z0-9_+-]+$/i.test(name) ? name : "bash";
|
|
294
381
|
}
|
|
295
382
|
/**
|
|
296
|
-
* The shell
|
|
297
|
-
*
|
|
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.
|
|
298
407
|
*
|
|
299
408
|
* @returns an absolute path to the shell inside the distro.
|
|
300
409
|
*/
|
|
@@ -374,14 +483,26 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
374
483
|
* @throws {Error} with the remedy when it cannot.
|
|
375
484
|
*/
|
|
376
485
|
async ensureSessionRuntimeUsable(distro) {
|
|
377
|
-
if (this.sessionRuntimePreflight
|
|
486
|
+
if (this.sessionRuntimePreflight.has(distro))
|
|
378
487
|
return;
|
|
379
488
|
const config = this.config;
|
|
380
489
|
const problem = await probeSessionRuntime({ wslPath: config.wslPath, distro, run: this.sessionScopeProbe });
|
|
381
490
|
if (problem !== null)
|
|
382
491
|
throw new Error(problem);
|
|
383
492
|
await ensureNativeSessionAgent({ wslPath: config.wslPath, distro, setNative: setSessionNativeAgent });
|
|
384
|
-
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);
|
|
385
506
|
}
|
|
386
507
|
/**
|
|
387
508
|
* Release every runtime this executor holds, stopping each scope. Called
|
|
@@ -530,10 +651,11 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
530
651
|
const tail = typeof spec?.relativeWorkdir === "string" ? spec.relativeWorkdir : undefined;
|
|
531
652
|
if (String(spec?.workdir ?? "").trim().length > 0 && tail === undefined)
|
|
532
653
|
return spec;
|
|
533
|
-
const
|
|
654
|
+
const distro = await this.distroFor(spec.workdir, configuredCwd(this.config));
|
|
655
|
+
const home = await distroHome(this, distro);
|
|
534
656
|
if (tail === undefined || tail.length === 0)
|
|
535
657
|
return { ...spec, workdir: home };
|
|
536
|
-
return { ...spec, workdir: toLinuxPath(tail, { distro
|
|
658
|
+
return { ...spec, workdir: toLinuxPath(tail, { distro, cwd: home }) };
|
|
537
659
|
}
|
|
538
660
|
/**
|
|
539
661
|
* Decorate a handle's foreground projection in place, memoized once. The
|
|
@@ -582,6 +704,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
582
704
|
denied: !runnerFailed && matchesSignature(proc.exitCode, stderr, facts.denialSignatures),
|
|
583
705
|
enforcement: facts.enforcement,
|
|
584
706
|
...(facts.windowsDrive ? { windowsDrive: facts.windowsDrive } : {}),
|
|
707
|
+
sessionScope: facts.sessionScope,
|
|
585
708
|
...(runnerFailed ? { runnerFailed } : {}),
|
|
586
709
|
};
|
|
587
710
|
}
|
|
@@ -645,9 +768,9 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
645
768
|
*/
|
|
646
769
|
async execute(spec) {
|
|
647
770
|
const config = this.config;
|
|
648
|
-
const distro = await this.distro();
|
|
649
|
-
const shell = await this.shell();
|
|
650
771
|
const resolved = await this.withDefaultWorkdir(spec);
|
|
772
|
+
const distro = await this.distroFor(resolved.workdir, configuredCwd(config));
|
|
773
|
+
const shell = await this.shellFor(distro);
|
|
651
774
|
this.throwIfWorkdirInAnotherDistro(resolved.workdir, distro);
|
|
652
775
|
if (config.agent) {
|
|
653
776
|
try {
|
|
@@ -698,9 +821,8 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
698
821
|
? await this.sandbox.confine(this.distroArgv(resolved, shell), { ...policy, mode: policy.mode }, { distro, signal: resolved.signal })
|
|
699
822
|
: undefined;
|
|
700
823
|
const mode = policy?.mode;
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const runtime = this.sessionRuntimeFor(policy, resolved, distro);
|
|
824
|
+
const useScope = await this.wantsSessionRuntime(distro);
|
|
825
|
+
const runtime = useScope ? this.sessionRuntimeFor(policy, resolved, distro) : undefined;
|
|
704
826
|
const agent = runtime !== undefined ? runtime.agent : await this.executionAgent(distro);
|
|
705
827
|
// A session runtime's own bwrap profile applies the mounts, so the
|
|
706
828
|
// command must NOT be wrapped a second time: the per-command probe still
|
|
@@ -740,6 +862,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
740
862
|
runnerProgram: profile.argv[0] ?? "",
|
|
741
863
|
workdir: resolved.workdir,
|
|
742
864
|
windowsDrive: profile.windowsDrive,
|
|
865
|
+
sessionScope: useScope ? "session" : "per-command",
|
|
743
866
|
});
|
|
744
867
|
}
|
|
745
868
|
: undefined,
|
|
@@ -750,7 +873,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
750
873
|
if (this.sandbox === undefined)
|
|
751
874
|
return result;
|
|
752
875
|
if (confined === undefined) {
|
|
753
|
-
return ({ ...result, sandbox: { mode, denied: false } });
|
|
876
|
+
return ({ ...result, sandbox: { mode, denied: false, sessionScope: useScope ? "session" : "per-command" } });
|
|
754
877
|
}
|
|
755
878
|
const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
|
|
756
879
|
const runnerFailure = classifyRunnerFailure(result.exitCode, result.stderr.text, runnerFailureRules);
|
|
@@ -767,11 +890,19 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
767
890
|
denied: matchesSignature(result.exitCode, result.stderr.text, denialSignatures),
|
|
768
891
|
enforcement,
|
|
769
892
|
windowsDrive,
|
|
893
|
+
sessionScope: useScope ? "session" : "per-command",
|
|
770
894
|
},
|
|
771
895
|
});
|
|
772
896
|
}, (error) => {
|
|
773
897
|
if (resolved.signal?.aborted === true)
|
|
774
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
|
+
}
|
|
775
906
|
throw error;
|
|
776
907
|
});
|
|
777
908
|
// The agent handle IS the upstream execution shape at runtime; its typedef
|
|
@@ -829,13 +960,14 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
829
960
|
runnerProgram: profile.argv[0] ?? "",
|
|
830
961
|
workdir: resolved.workdir,
|
|
831
962
|
windowsDrive: profile.windowsDrive,
|
|
963
|
+
sessionScope: "per-command",
|
|
832
964
|
});
|
|
833
965
|
});
|
|
834
966
|
return WslShellExecutor.decorateResult(execution, (result) => {
|
|
835
967
|
this.throwIfDistroUnavailable(result, distro);
|
|
836
968
|
this.throwIfWorkdirMissing(result, resolved.workdir);
|
|
837
969
|
if (confined === undefined) {
|
|
838
|
-
return ({ ...result, sandbox: { mode, denied: false } });
|
|
970
|
+
return ({ ...result, sandbox: { mode, denied: false, sessionScope: "per-command" } });
|
|
839
971
|
}
|
|
840
972
|
const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
|
|
841
973
|
// Runner failure outranks denial because the command did not run at all.
|
|
@@ -849,6 +981,7 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
849
981
|
denied: matchesSignature(result.exitCode, result.stderr.text, denialSignatures),
|
|
850
982
|
enforcement,
|
|
851
983
|
windowsDrive,
|
|
984
|
+
sessionScope: "per-command",
|
|
852
985
|
},
|
|
853
986
|
});
|
|
854
987
|
}, (error) => {
|
|
@@ -865,7 +998,10 @@ export class WslShellExecutor extends LocalBashExecutor {
|
|
|
865
998
|
export class WslFileSystem extends LocalFileSystem {
|
|
866
999
|
static inject = ["sandboxPolicy"];
|
|
867
1000
|
static Config = z.object({
|
|
868
|
-
/**
|
|
1001
|
+
/**
|
|
1002
|
+
* Distro mode: empty/`"auto"` follows the target path, `"default"` forces
|
|
1003
|
+
* WSL's default, and an explicit name pins the filesystem.
|
|
1004
|
+
*/
|
|
869
1005
|
distro: z.string().default(""),
|
|
870
1006
|
/**
|
|
871
1007
|
* Default workdir for relative input; accepts a Linux path, a UNC path, or
|
|
@@ -895,7 +1031,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
895
1031
|
* `sessionSandbox`; the preflight refuses with the remedy. Off for direct
|
|
896
1032
|
* constructions; on in the shipped preset.
|
|
897
1033
|
*/
|
|
898
|
-
sessionSandbox: z.boolean().default(false),
|
|
1034
|
+
sessionSandbox: z.union([z.boolean(), z.const("auto")]).default(false),
|
|
899
1035
|
/**
|
|
900
1036
|
* Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
|
|
901
1037
|
* Windows drive from confined commands — same meaning and same honest
|
|
@@ -949,16 +1085,20 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
949
1085
|
if (refusal !== null)
|
|
950
1086
|
throw new Error(refusal.message);
|
|
951
1087
|
super(ctx, config);
|
|
952
|
-
this.resolvedDistro = config.distro;
|
|
1088
|
+
this.resolvedDistro = pinnedDistro(config.distro) ?? "";
|
|
953
1089
|
this.resolvedHome = undefined;
|
|
1090
|
+
this.resolvedHomes = new Map();
|
|
954
1091
|
this.defaultMode = ctx.sandboxPolicy.defaultMode;
|
|
955
1092
|
this.agentSubstrateInstance = undefined;
|
|
1093
|
+
this.agentSubstrates = new Map();
|
|
956
1094
|
this.sandbox = new WslSandbox({ wslPath: config.wslPath, maskWindowsDrive: config.maskWindowsDrive });
|
|
957
1095
|
this.agentInstance = undefined;
|
|
1096
|
+
this.agentInstances = new Map();
|
|
958
1097
|
this.sessionRuntimes = sessionRuntimes;
|
|
959
1098
|
this.sessionRuntimeEntries = new Map();
|
|
960
1099
|
this.sessionScopeProbe = (argv) => runCapture(argv);
|
|
961
|
-
this.sessionRuntimePreflight =
|
|
1100
|
+
this.sessionRuntimePreflight = new Set();
|
|
1101
|
+
this.autoSessionScope = new Map();
|
|
962
1102
|
// The preset's disposer releases the runtime; the shell executor's effect
|
|
963
1103
|
// releases its own reference, so the scope stops after the last one.
|
|
964
1104
|
ctx.effect(() => () => this.disposeSessionRuntimes().catch(() => { }), "wsl fs session runtimes");
|
|
@@ -976,11 +1116,10 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
976
1116
|
return config.sandbox ? this.defaultMode : undefined;
|
|
977
1117
|
}
|
|
978
1118
|
/**
|
|
979
|
-
* The resident agent serving
|
|
980
|
-
* on first use and kept for the provider's lifetime
|
|
981
|
-
* 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.
|
|
982
1121
|
*
|
|
983
|
-
* @returns the agent for the pinned distro.
|
|
1122
|
+
* @returns the agent for the default/pinned distro.
|
|
984
1123
|
*/
|
|
985
1124
|
async publicationAgent() {
|
|
986
1125
|
if (this.agentInstance === undefined) {
|
|
@@ -989,8 +1128,26 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
989
1128
|
return this.agentInstance;
|
|
990
1129
|
}
|
|
991
1130
|
/**
|
|
992
|
-
* The agent
|
|
993
|
-
*
|
|
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.
|
|
994
1151
|
*
|
|
995
1152
|
* @returns the substrate.
|
|
996
1153
|
*/
|
|
@@ -1006,6 +1163,40 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1006
1163
|
}
|
|
1007
1164
|
return this.agentSubstrateInstance;
|
|
1008
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
|
+
}
|
|
1009
1200
|
/**
|
|
1010
1201
|
* The persistent session runtime for one confined mutation, when
|
|
1011
1202
|
* `sessionSandbox` is on. The key is the shell executor's key for the same
|
|
@@ -1044,14 +1235,26 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1044
1235
|
* @throws {Error} with the remedy when it cannot.
|
|
1045
1236
|
*/
|
|
1046
1237
|
async ensureSessionRuntimeUsable(distro) {
|
|
1047
|
-
if (this.sessionRuntimePreflight
|
|
1238
|
+
if (this.sessionRuntimePreflight.has(distro))
|
|
1048
1239
|
return;
|
|
1049
1240
|
const config = this.config;
|
|
1050
1241
|
const problem = await probeSessionRuntime({ wslPath: config.wslPath, distro, run: this.sessionScopeProbe });
|
|
1051
1242
|
if (problem !== null)
|
|
1052
1243
|
throw new Error(problem);
|
|
1053
1244
|
await ensureNativeSessionAgent({ wslPath: config.wslPath, distro, setNative: setSessionNativeAgent });
|
|
1054
|
-
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);
|
|
1055
1258
|
}
|
|
1056
1259
|
/**
|
|
1057
1260
|
* Release every runtime this provider holds. Called from the preset's
|
|
@@ -1076,26 +1279,27 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1076
1279
|
*
|
|
1077
1280
|
* @param policy - the resolved file-effect policy, or undefined when the
|
|
1078
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.
|
|
1079
1284
|
* @returns the agent to publish through.
|
|
1080
1285
|
*/
|
|
1081
|
-
async mutationAgent(policy) {
|
|
1286
|
+
async mutationAgent(policy, forDistro) {
|
|
1082
1287
|
const config = this.config;
|
|
1288
|
+
const distro = forDistro ?? await this.distroForPolicy(policy);
|
|
1083
1289
|
if (!isConfinedMutation({ policy, sandboxEnabled: config.sandbox })) {
|
|
1084
|
-
return this.
|
|
1290
|
+
return this.publicationAgentFor(distro);
|
|
1085
1291
|
}
|
|
1086
1292
|
// The untyped guard decided this is a confined policy but cannot narrow
|
|
1087
1293
|
// the parameter, so the confined shape is asserted once here (type-level
|
|
1088
1294
|
// only; `confinedPolicy` is the same value as `policy`).
|
|
1089
1295
|
const confinedPolicy = policy;
|
|
1090
|
-
const distro = await this.distro();
|
|
1091
1296
|
if (!(await this.sandbox.usable(distro))) {
|
|
1092
1297
|
// The probe's classified remedy — missing vs present-but-broken, with the
|
|
1093
1298
|
// distro's own package family — is what every other refusal surfaces;
|
|
1094
1299
|
// this path has the same failure, so it carries the same text.
|
|
1095
1300
|
throw new SandboxUnavailableError(confinedPolicy.mode, bwrapFailure(config.wslPath, distro) ?? missingBwrapRemedy(distro));
|
|
1096
1301
|
}
|
|
1097
|
-
if (
|
|
1098
|
-
await this.ensureSessionRuntimeUsable(distro);
|
|
1302
|
+
if (await this.wantsSessionRuntime(distro)) {
|
|
1099
1303
|
const runtime = this.sessionRuntimeFor(confinedPolicy, distro);
|
|
1100
1304
|
if (runtime !== undefined)
|
|
1101
1305
|
return runtime.agent;
|
|
@@ -1131,7 +1335,8 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1131
1335
|
return uncToPosix(target.targetKey)?.linuxPath ?? target.displayPath;
|
|
1132
1336
|
}
|
|
1133
1337
|
/**
|
|
1134
|
-
* 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.
|
|
1135
1340
|
*
|
|
1136
1341
|
* @returns the distro name.
|
|
1137
1342
|
*/
|
|
@@ -1141,6 +1346,60 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1141
1346
|
this.resolvedDistro = await defaultDistro({ wslPath: config.wslPath });
|
|
1142
1347
|
return this.resolvedDistro;
|
|
1143
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
|
+
}
|
|
1144
1403
|
/**
|
|
1145
1404
|
* Translate one caller path into the world path, enforcing the distro fence.
|
|
1146
1405
|
*
|
|
@@ -1150,13 +1409,13 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1150
1409
|
*/
|
|
1151
1410
|
async worldPath(path, cwd) {
|
|
1152
1411
|
const config = this.config;
|
|
1153
|
-
const distro = await this.
|
|
1412
|
+
const distro = await this.distroForPath(path, cwd);
|
|
1154
1413
|
let base = cwd ?? config.cwd;
|
|
1155
1414
|
if (String(base ?? "").trim().length === 0 && isRelativeWorldPath(path)) {
|
|
1156
1415
|
// An empty `cwd` means the distro user's home. Only relative input
|
|
1157
1416
|
// consults the base at all, which is also the only case that has to pay
|
|
1158
1417
|
// for the `wsl.exe` query — an absolute path never does.
|
|
1159
|
-
base = await distroHome(this);
|
|
1418
|
+
base = await distroHome(this, distro);
|
|
1160
1419
|
}
|
|
1161
1420
|
const world = toWorldPath(path, { distro, cwd: base });
|
|
1162
1421
|
const refusal = outsideDistroRefusal({ path, world, distro, restrictToDistro: config.restrictToDistro });
|
|
@@ -1181,10 +1440,11 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1181
1440
|
const linux = uncToPosix(world)?.linuxPath ?? windowsToLinuxMount(world);
|
|
1182
1441
|
if (linux === undefined)
|
|
1183
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);
|
|
1184
1444
|
try {
|
|
1185
1445
|
// The substrate's resolved shape carries plain-string keys; the seam's
|
|
1186
1446
|
// branded tokens ride one type-level cast (runtime shape unchanged).
|
|
1187
|
-
return (await (await this.
|
|
1447
|
+
return (await (await this.agentSubstrateFor(distro)).resolve(linux, opts?.signal));
|
|
1188
1448
|
}
|
|
1189
1449
|
catch (error) {
|
|
1190
1450
|
throw this.peerError(error);
|
|
@@ -1220,8 +1480,8 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1220
1480
|
}
|
|
1221
1481
|
if (policy.mode !== "workspace-write")
|
|
1222
1482
|
return target;
|
|
1223
|
-
const
|
|
1224
|
-
const
|
|
1483
|
+
const distro = await this.distroForTarget(target);
|
|
1484
|
+
const fresh = await this.resolve(target.targetKey);
|
|
1225
1485
|
for (const root of wslWritableRoots(policy, distro)) {
|
|
1226
1486
|
if (isWorldPathUnder(fresh.targetKey, root, { distro }))
|
|
1227
1487
|
return fresh;
|
|
@@ -1238,7 +1498,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1238
1498
|
*/
|
|
1239
1499
|
async stat(target, signal) {
|
|
1240
1500
|
try {
|
|
1241
|
-
return (await (await this.
|
|
1501
|
+
return (await (await this.substrateForTarget(target)).stat(target, signal));
|
|
1242
1502
|
}
|
|
1243
1503
|
catch (error) {
|
|
1244
1504
|
throw this.peerError(error);
|
|
@@ -1258,8 +1518,9 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1258
1518
|
const linux = uncToPosix(world)?.linuxPath ?? windowsToLinuxMount(world);
|
|
1259
1519
|
if (linux === undefined)
|
|
1260
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);
|
|
1261
1522
|
try {
|
|
1262
|
-
return (await (await this.
|
|
1523
|
+
return (await (await this.agentSubstrateFor(distro)).lstat(linux, signal));
|
|
1263
1524
|
}
|
|
1264
1525
|
catch (error) {
|
|
1265
1526
|
throw this.peerError(error);
|
|
@@ -1274,7 +1535,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1274
1535
|
*/
|
|
1275
1536
|
async readText(target, signal) {
|
|
1276
1537
|
try {
|
|
1277
|
-
return await (await this.
|
|
1538
|
+
return await (await this.substrateForTarget(target)).readText(target, signal);
|
|
1278
1539
|
}
|
|
1279
1540
|
catch (error) {
|
|
1280
1541
|
throw this.peerError(error);
|
|
@@ -1289,7 +1550,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1289
1550
|
*/
|
|
1290
1551
|
async streamText(target, signal) {
|
|
1291
1552
|
try {
|
|
1292
|
-
return await (await this.
|
|
1553
|
+
return await (await this.substrateForTarget(target)).streamText(target, signal);
|
|
1293
1554
|
}
|
|
1294
1555
|
catch (error) {
|
|
1295
1556
|
throw this.peerError(error);
|
|
@@ -1305,7 +1566,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1305
1566
|
*/
|
|
1306
1567
|
async readBytes(target, signal, maxBytes) {
|
|
1307
1568
|
try {
|
|
1308
|
-
return await (await this.
|
|
1569
|
+
return await (await this.substrateForTarget(target)).readBytes(target, signal, maxBytes);
|
|
1309
1570
|
}
|
|
1310
1571
|
catch (error) {
|
|
1311
1572
|
throw this.peerError(error);
|
|
@@ -1321,7 +1582,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1321
1582
|
*/
|
|
1322
1583
|
async readByteRange(target, range, signal) {
|
|
1323
1584
|
try {
|
|
1324
|
-
return await (await this.
|
|
1585
|
+
return await (await this.substrateForTarget(target)).readByteRange(target, range, signal);
|
|
1325
1586
|
}
|
|
1326
1587
|
catch (error) {
|
|
1327
1588
|
throw this.peerError(error);
|
|
@@ -1337,7 +1598,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1337
1598
|
*/
|
|
1338
1599
|
async listDir(target, signal) {
|
|
1339
1600
|
try {
|
|
1340
|
-
return (await (await this.
|
|
1601
|
+
return (await (await this.substrateForTarget(target)).listDir(target, signal));
|
|
1341
1602
|
}
|
|
1342
1603
|
catch (error) {
|
|
1343
1604
|
throw this.peerError(error);
|
|
@@ -1350,10 +1611,10 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1350
1611
|
* plain callback over image attachments, and `packages/api/session-controller`
|
|
1351
1612
|
* plus the UI's open/reveal path compare the return value against `undefined`
|
|
1352
1613
|
* without awaiting. The mapping is therefore built from facts that are already
|
|
1353
|
-
* known —
|
|
1354
|
-
* the default `
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
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
|
|
1357
1618
|
* relative path before the home is known — answers `undefined`, which every
|
|
1358
1619
|
* consumer reads as "no mapping", the same answer the ssh provider gives for
|
|
1359
1620
|
* a path it cannot map.
|
|
@@ -1371,16 +1632,21 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1371
1632
|
*/
|
|
1372
1633
|
processPathFromHostPath(hostPath) {
|
|
1373
1634
|
const config = this.config;
|
|
1374
|
-
|
|
1635
|
+
const choice = selectDistro({
|
|
1636
|
+
configured: config.distro,
|
|
1637
|
+
candidates: [hostPath, config.cwd],
|
|
1638
|
+
fallback: this.resolvedDistro,
|
|
1639
|
+
});
|
|
1640
|
+
if (choice.distro.length === 0)
|
|
1375
1641
|
return undefined;
|
|
1376
1642
|
let base = config.cwd;
|
|
1377
1643
|
if (String(base ?? "").trim().length === 0 && isRelativeWorldPath(hostPath)) {
|
|
1378
|
-
|
|
1644
|
+
base = this.resolvedHomes.get(choice.distro) ?? (choice.distro === this.resolvedDistro ? this.resolvedHome : undefined);
|
|
1645
|
+
if (base === undefined)
|
|
1379
1646
|
return undefined;
|
|
1380
|
-
base = this.resolvedHome;
|
|
1381
1647
|
}
|
|
1382
|
-
const world = toWorldPath(hostPath, { distro:
|
|
1383
|
-
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 });
|
|
1384
1650
|
return refusal === null ? world : undefined;
|
|
1385
1651
|
}
|
|
1386
1652
|
/**
|
|
@@ -1407,7 +1673,19 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1407
1673
|
if (parsed === undefined) {
|
|
1408
1674
|
throw new FsError(`cannot watch "${target.displayPath}": not a path inside the distro`, "FS_IO_ERROR");
|
|
1409
1675
|
}
|
|
1410
|
-
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;
|
|
1411
1689
|
return armDistroWatcher({
|
|
1412
1690
|
wslPath: config.wslPath,
|
|
1413
1691
|
distro,
|
|
@@ -1437,7 +1715,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1437
1715
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
1438
1716
|
const fenced = await this.checkedTarget(target, policy);
|
|
1439
1717
|
try {
|
|
1440
|
-
return (await (await this.
|
|
1718
|
+
return (await (await this.substrateForTarget(fenced)).writeText(fenced, content, expected, signal, policy));
|
|
1441
1719
|
}
|
|
1442
1720
|
catch (error) {
|
|
1443
1721
|
throw this.peerError(error);
|
|
@@ -1459,7 +1737,7 @@ export class WslFileSystem extends LocalFileSystem {
|
|
|
1459
1737
|
const policy = sandboxPolicy ?? this.ctx.sandboxPolicy.resolve();
|
|
1460
1738
|
const fenced = await this.checkedTarget(target, policy);
|
|
1461
1739
|
try {
|
|
1462
|
-
return (await (await this.
|
|
1740
|
+
return (await (await this.substrateForTarget(fenced)).editText(fenced, edit, expected, signal, policy));
|
|
1463
1741
|
}
|
|
1464
1742
|
catch (error) {
|
|
1465
1743
|
throw this.peerError(error);
|