dsh-plugin-wsl-env 0.8.0 → 0.9.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.
@@ -2,17 +2,24 @@
2
2
 
3
3
  Status: proposed upstream as
4
4
  [deepseek-ai/deepseek-harness#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769)
5
- (targeting `packages/api/terminal-controller`, version line `0.2.0-rc.x`). This
6
- is the record of a gap this plugin worked around, could not work around, and
7
- now documents: the GUI terminal titles every tab from the composition's single
8
- shell profile, so a per-session shell choice is invisible in the title.
5
+ (targeting `packages/api/terminal-controller`, version line `0.2.0-rc.x`). The
6
+ plugin worked around the execution half of this gap — a Windows-folder
7
+ session's terminal runs a host shell (`subprocess-wsl.hostSessions`) — and
8
+ could not work around the title half. This is the record: the GUI titles every
9
+ tab from the composition's single shell profile, so a per-session shell choice
10
+ is invisible in the title.
9
11
 
10
12
  ## The gap
11
13
 
12
- `TerminalController.spawn` fixes a terminal's display title from the resolved
13
- shell profile before the provider is ever consulted:
14
+ The display title is fixed from the resolved shell profile and never revisited.
15
+ The private `TerminalController.spawn` builds `info`, with `title: shell.name`,
16
+ only after `await subprocess.spawnTerminal(...)` has returned, and it does not
17
+ read back what that call started:
14
18
 
15
19
  ```ts
20
+ const handle = await subprocess.spawnTerminal({
21
+ argv: [shell.path, ...shell.args], cwd: environment.cwd, cols: request.cols, ...
22
+ })
16
23
  const info: WebTerminalInfo = {
17
24
  id: request.id, shell, title: shell.name, cwd: environment.cwd, ...
18
25
  }
@@ -48,10 +55,15 @@ Three escape hatches were evaluated against `0.2.0-rc.2` and each is closed:
48
55
  inserts `terminal-controller`, and the client model that the
49
56
  `dsh-client-ui-sidebar-terminal` entry waits for mounts with
50
57
  `inject = ['remote', 'remote.terminal']` — the `terminal` namespace the host
51
- controller registers in its constructor. Disabling the row fails web boot:
52
- `1 entry did not activate: @deepseek-ai/dsh-client-ui-sidebar-terminal:
53
- pending (waiting for service: webTerminals)`. Verified live against
54
- `dsh-desktop 0.2.0-rc.2`.
58
+ controller registers in its constructor. Disabling the row fails web boot
59
+ with this two-line error:
60
+
61
+ ```text
62
+ web boot: 1 entry did not activate
63
+ @deepseek-ai/dsh-client-ui-sidebar-terminal: pending (waiting for service: webTerminals)
64
+ ```
65
+
66
+ Verified live against `dsh-desktop 0.2.0-rc.2` (Electron 44.0.0).
55
67
  3. **Per-session config.** The `shell` profile is composition state; `!!js`
56
68
  expressions evaluate once at config resolution and reach no Session
57
69
  identity.
@@ -62,7 +74,7 @@ Any one of these closes the gap; the first is the smallest and keeps the
62
74
  profile name as the fallback:
63
75
 
64
76
  - `SubprocessTerminalSpawnSpec` gains an optional `displayTitle` (or the handle
65
- exposes the launched `argv`), and `create()` uses it for `info.title` when
77
+ exposes the launched `argv`), and `spawn()` uses it for `info.title` when
66
78
  present — falling back to `shell.name`. A provider that rewrites a launch
67
79
  states the program it started; every other launch keeps the profile name.
68
80
  - Alternatively, the controller derives the title from the effective program
@@ -71,11 +83,34 @@ profile name as the fallback:
71
83
 
72
84
  With either, this plugin deletes the limitation paragraph in
73
85
  [LIMITATIONS.md](LIMITATIONS.md) and the `WSL`-over-PowerShell title, for any
74
- provider that rewrites launches — not just WSL.
86
+ provider that rewrites launches — not just WSL. The same limitation is stated
87
+ as current fact in four places. Three go stale the day the proposal lands; the
88
+ fourth is already out of step and needs the corrections above whenever it is
89
+ next touched:
90
+
91
+ - the GUI-terminal comment block in [`cordis.patch.yml`](../cordis.patch.yml);
92
+ - the closing paragraph of "Why the terminal provider is app-level" in
93
+ [ARCHITECTURE.md](ARCHITECTURE.md);
94
+ - R4's `残差` (residual) column in [REQUIREMENTS.zh.md](REQUIREMENTS.zh.md);
95
+ - the `WSL` tab-title bullet under "Terminals" in
96
+ [LIMITATIONS.md](LIMITATIONS.md) — the paragraph named just above. It says
97
+ the controller titles a tab *before* the provider rewrites the launch, the
98
+ reverse of the ordering corrected at the top of this file, and it carries the
99
+ one-line `shellCandidates` workaround that drops the row's `shell` block.
75
100
 
76
101
  ## Workarounds until then
77
102
 
78
103
  - Double-click a tab's title to rename it (a shipped client feature; the
79
104
  controller's `rename` Remote API accepts 1–120 characters).
80
- - Add `shellCandidates` to the `terminal-controller` row in a user layer: a
81
- manually selected shell is titled after itself.
105
+ - Re-declare the `terminal-controller` row in a user layer with a non-empty
106
+ `shellCandidates`: a manually selected shell is titled after itself. A patch
107
+ config replaces the whole config rather than deep-merging it
108
+ (`packages/boot/app-boot/src/config-schema/document.ts`), so the override has
109
+ to carry this plugin's `shell` block verbatim — `path: wsl.exe`, `name: WSL`,
110
+ `args: []` — or the terminal stops being a distro terminal. Expect the menu
111
+ to grow the host shells discovery finds on the root provider, which receives
112
+ no directory: executables this terminal never starts (the second bullet of
113
+ the GUI-terminal comment in [`cordis.patch.yml`](../cordis.patch.yml)).
114
+ [LIMITATIONS.md](LIMITATIONS.md) still carries the older one-line form of
115
+ this tip, which drops the `shell` block; the version above is the one that
116
+ works.
package/lib/index.js CHANGED
@@ -47,6 +47,7 @@ import { canonicalPath, classifyRunnerFailure, isRunnerSpawnFailure, matchesSign
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
49
  import { WslSandbox } from "./sandbox.js";
50
+ import { bwrapFailure, missingBwrapRemedy, withBwrapHint } from "./sandbox-core.js";
50
51
  import { armDistroWatcher } from "./watcher.js";
51
52
  import { AgentUnavailableError } from "./agent-errors.js";
52
53
  import { agentExecutionHandle } from "./agent-exec.js";
@@ -649,7 +650,10 @@ export class WslShellExecutor extends LocalBashExecutor {
649
650
  const { enforcement, denialSignatures, runnerFailureRules, windowsDrive } = confined;
650
651
  const runnerFailure = classifyRunnerFailure(result.exitCode, result.stderr.text, runnerFailureRules);
651
652
  if (runnerFailure !== undefined) {
652
- throw new SandboxUnavailableError(mode, runnerFailure.detail);
653
+ // This path computes `mode` as `policy?.mode`, which the seam spells as
654
+ // the wider union: a runner failure is only ever thrown under a
655
+ // confinement, and there the mode is one of the two confined ones.
656
+ throw new SandboxUnavailableError(mode, withBwrapHint(runnerFailure.detail, distro));
653
657
  }
654
658
  return ({
655
659
  ...result,
@@ -732,7 +736,7 @@ export class WslShellExecutor extends LocalBashExecutor {
732
736
  // Runner failure outranks denial because the command did not run at all.
733
737
  const runnerFailure = classifyRunnerFailure(result.exitCode, result.stderr.text, runnerFailureRules);
734
738
  if (runnerFailure !== undefined)
735
- throw new SandboxUnavailableError(mode, runnerFailure.detail);
739
+ throw new SandboxUnavailableError(mode, withBwrapHint(runnerFailure.detail, distro));
736
740
  return ({
737
741
  ...result,
738
742
  sandbox: {
@@ -747,7 +751,7 @@ export class WslShellExecutor extends LocalBashExecutor {
747
751
  if (resolved.signal?.aborted === true)
748
752
  resolved.signal.throwIfAborted();
749
753
  if (confined !== undefined && isRunnerSpawnFailure(error, confined.argv[0], resolved.workdir)) {
750
- throw new SandboxUnavailableError(mode, String(error));
754
+ throw new SandboxUnavailableError(mode, withBwrapHint(String(error), distro));
751
755
  }
752
756
  throw error;
753
757
  });
@@ -903,9 +907,10 @@ export class WslFileSystem extends LocalFileSystem {
903
907
  const confinedPolicy = policy;
904
908
  const distro = await this.distro();
905
909
  if (!(await this.sandbox.usable(distro))) {
906
- throw new SandboxUnavailableError(confinedPolicy.mode, `bwrap is not usable inside distro "${distro}". Install it with ` +
907
- `"scripts/bootstrap.sh ${distro} --install" (from the package), or directly: ` +
908
- `wsl.exe -d ${distro} -u root -- apt-get install -y bubblewrap`);
910
+ // The probe's classified remedy — missing vs present-but-broken, with the
911
+ // distro's own package family — is what every other refusal surfaces;
912
+ // this path has the same failure, so it carries the same text.
913
+ throw new SandboxUnavailableError(confinedPolicy.mode, bwrapFailure(config.wslPath, distro) ?? missingBwrapRemedy(distro));
909
914
  }
910
915
  return confinedAgent({ distro, wslPath: config.wslPath, policy: confinedPolicy, maskWindowsDrive: config.maskWindowsDrive });
911
916
  }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Session-start sandbox preflight: surface a missing or broken `bwrap` when a
3
+ * distro session OPENS, not when its first command fails closed.
4
+ *
5
+ * Without this the discovery moment is the model's first command, whose
6
+ * `SANDBOX_UNAVAILABLE` error scrolls past as one tool failure among many; the
7
+ * operator may not read it until several commands have failed the same way.
8
+ * The probe itself is not new — it is the providers' own usability probe, run
9
+ * through the same shared cache (`lib/sandbox-core.js`), so a session-start
10
+ * check costs a real probe only once per process, and the first confined
11
+ * command after it pays nothing. Nothing else changes: the failure semantics
12
+ * stay exactly as fail-closed as before, this service only MOVES THE NEWS.
13
+ *
14
+ * Advisory by construction. A preflight that cannot run (no WSL, a wedged
15
+ * `wsl.exe`) degrades to a log line; it must never fail a session that has not
16
+ * even asked for confinement yet. It also never installs anything: the remedy
17
+ * it prints is the same one the refusals carry, and running an installer under
18
+ * root uninvited would be a posture this plugin explicitly rejects.
19
+ *
20
+ * This is a TypeScript source built to `lib/preflight.js`; edit THIS file and
21
+ * run `pnpm run build` — the artifact under `lib/` is generated, and
22
+ * `pnpm test` fails when it drifts.
23
+ *
24
+ * @module dsh-plugin-wsl/preflight
25
+ */
26
+ import z from "@deepseek-ai/schemastery";
27
+ import type { Context } from "@deepseek-ai/cordis";
28
+ export declare const name = "wsl-preflight";
29
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
30
+ /** Distro to preflight; empty means this machine's default distro. */
31
+ distro: z<string, string, "defined">;
32
+ /** The `wsl.exe` path the probe spawns; keep in step with the providers' `wslPath`. */
33
+ wslPath: z<string, string, "defined">;
34
+ /** Whether the session-start sandbox preflight runs at all. */
35
+ sandbox: z<boolean, boolean, "defined">;
36
+ }>>, Schemastery.ObjectT<NoInfer<{
37
+ /** Distro to preflight; empty means this machine's default distro. */
38
+ distro: z<string, string, "defined">;
39
+ /** The `wsl.exe` path the probe spawns; keep in step with the providers' `wslPath`. */
40
+ wslPath: z<string, string, "defined">;
41
+ /** Whether the session-start sandbox preflight runs at all. */
42
+ sandbox: z<boolean, boolean, "defined">;
43
+ }>>, "plain">;
44
+ /** The config schema's validated output. */
45
+ export interface PreflightConfig {
46
+ /** Distro to preflight; empty means this machine's default distro. */
47
+ distro?: string;
48
+ /** The `wsl.exe` path the probe spawns. */
49
+ wslPath?: string;
50
+ /** Whether the session-start sandbox preflight runs at all. */
51
+ sandbox?: boolean;
52
+ }
53
+ /**
54
+ * The slice of a created agent this service inspects; the harness's agent
55
+ * object is structural here, the same view `auto-preset` uses.
56
+ */
57
+ export interface PreflightAgent {
58
+ /** The agent's id. */
59
+ id: string;
60
+ /** The session stub, when mounted. */
61
+ session?: {
62
+ header?: {
63
+ cwd?: string | undefined;
64
+ };
65
+ } | undefined;
66
+ }
67
+ /**
68
+ * The cordis context extended with the slices this service consumes. `logger`
69
+ * and the agent service belong to the harness, whose declarations are not
70
+ * visible to this plugin, so they are spelled structurally on top of the
71
+ * framework's {@link Context}.
72
+ */
73
+ export type PreflightContext = Context & {
74
+ logger?: {
75
+ debug?: (message: string) => void;
76
+ warn?: (message: string) => void;
77
+ };
78
+ agents?: {
79
+ list: () => PreflightAgent[];
80
+ };
81
+ on: (event: "agent/created", listener: (payload: {
82
+ agent?: PreflightAgent;
83
+ } | undefined) => void) => () => boolean;
84
+ };
85
+ /**
86
+ * Mount the preflight service.
87
+ *
88
+ * @param ctx - the cordis context.
89
+ * @param config - the schema-validated plugin config.
90
+ */
91
+ export declare function apply(ctx: PreflightContext, config?: PreflightConfig): Promise<void>;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Session-start sandbox preflight: surface a missing or broken `bwrap` when a
3
+ * distro session OPENS, not when its first command fails closed.
4
+ *
5
+ * Without this the discovery moment is the model's first command, whose
6
+ * `SANDBOX_UNAVAILABLE` error scrolls past as one tool failure among many; the
7
+ * operator may not read it until several commands have failed the same way.
8
+ * The probe itself is not new — it is the providers' own usability probe, run
9
+ * through the same shared cache (`lib/sandbox-core.js`), so a session-start
10
+ * check costs a real probe only once per process, and the first confined
11
+ * command after it pays nothing. Nothing else changes: the failure semantics
12
+ * stay exactly as fail-closed as before, this service only MOVES THE NEWS.
13
+ *
14
+ * Advisory by construction. A preflight that cannot run (no WSL, a wedged
15
+ * `wsl.exe`) degrades to a log line; it must never fail a session that has not
16
+ * even asked for confinement yet. It also never installs anything: the remedy
17
+ * it prints is the same one the refusals carry, and running an installer under
18
+ * root uninvited would be a posture this plugin explicitly rejects.
19
+ *
20
+ * This is a TypeScript source built to `lib/preflight.js`; edit THIS file and
21
+ * run `pnpm run build` — the artifact under `lib/` is generated, and
22
+ * `pnpm test` fails when it drifts.
23
+ *
24
+ * @module dsh-plugin-wsl/preflight
25
+ */
26
+ import z from "@deepseek-ai/schemastery";
27
+ import { isWslUnc } from "./paths.js";
28
+ import { defaultDistro } from "./wsl.js";
29
+ import { bwrapFailure, bwrapUsable, missingBwrapRemedy } from "./sandbox-core.js";
30
+ export const name = "wsl-preflight";
31
+ export const Config = z.object({
32
+ /** Distro to preflight; empty means this machine's default distro. */
33
+ distro: z.string().default(""),
34
+ /** The `wsl.exe` path the probe spawns; keep in step with the providers' `wslPath`. */
35
+ wslPath: z.string().default("wsl.exe"),
36
+ /** Whether the session-start sandbox preflight runs at all. */
37
+ sandbox: z.boolean().default(true),
38
+ });
39
+ /**
40
+ * Mount the preflight service.
41
+ *
42
+ * @param ctx - the cordis context.
43
+ * @param config - the schema-validated plugin config.
44
+ */
45
+ export async function apply(ctx, config = {}) {
46
+ if (!(config.sandbox ?? true))
47
+ return;
48
+ const wslPath = config.wslPath ?? "wsl.exe";
49
+ const warn = (message) => ctx.logger?.warn?.(message);
50
+ /**
51
+ * Probe the distro once and surface an unusable `bwrap` as a warning.
52
+ *
53
+ * @param agent - the session that just opened, for the workspace scope.
54
+ */
55
+ const check = async (agent) => {
56
+ // Only a distro workspace has commands that confine: a host session would
57
+ // warn about a sandbox it never uses.
58
+ const cwd = agent?.session?.header?.cwd;
59
+ if (cwd === undefined || !isWslUnc(cwd))
60
+ return;
61
+ let distro = config.distro ?? "";
62
+ if (distro.length === 0) {
63
+ try {
64
+ distro = await defaultDistro({ wslPath });
65
+ }
66
+ catch (error) {
67
+ // No WSL at all is the missing-wsl probe's territory; the first
68
+ // command's own error is the precise one. Stay quiet at warn level.
69
+ ctx.logger?.debug?.(`wsl-preflight: no default distro to preflight (${error?.message ?? error})`);
70
+ return;
71
+ }
72
+ }
73
+ // bwrapUsable answers false on a failed probe — it never rejects — so
74
+ // nothing here can throw except a broken context, which is the caller's
75
+ // bug, not the session's.
76
+ const usable = await bwrapUsable({ wslPath, distro });
77
+ if (usable) {
78
+ ctx.logger?.debug?.(`wsl-preflight: bwrap is usable inside distro "${distro}"`);
79
+ return;
80
+ }
81
+ warn(`wsl-preflight: ${bwrapFailure(wslPath, distro) ?? missingBwrapRemedy(distro)}`);
82
+ };
83
+ ctx.on("agent/created", (payload) => void check(payload?.agent));
84
+ // Mount order is not a guarantee: a session created during startup can finish
85
+ // before this service mounts and miss the event entirely (dsh-headless does
86
+ // exactly that, and auto-preset sweeps for the same reason).
87
+ try {
88
+ for (const agent of ctx.agents?.list() ?? [])
89
+ void check(agent);
90
+ }
91
+ catch (error) {
92
+ ctx.logger?.debug?.(`wsl-preflight: could not sweep already-live agents: ${error?.message ?? error}`);
93
+ }
94
+ }
@@ -25,10 +25,21 @@ export interface ProbeCache {
25
25
  *
26
26
  * @param key - the probe's cache key.
27
27
  * @param probe - the probe to run on a miss. A throw is a failed probe, never
28
- * a propagated error.
28
+ * a propagated error; its message is remembered for {@link failure}.
29
29
  * @returns the verdict; a success is remembered, a failure is not.
30
30
  */
31
31
  run(key: string, probe: () => Promise<boolean> | boolean): Promise<boolean>;
32
+ /**
33
+ * The message of the probe failure the key last recorded, if any. A failed
34
+ * verdict is not remembered as a verdict, but its reason outlives the miss:
35
+ * the remedy a caller prints is composed from WHY the probe failed, and the
36
+ * next caller — often the very next command — must still see it.
37
+ *
38
+ * @param key - the probe's cache key.
39
+ * @returns the thrown error's message, or undefined while no failure is
40
+ * recorded; cleared on the key's next success.
41
+ */
42
+ failure(key: string): string | undefined;
32
43
  /** Remembered successes, for tests and diagnostics. */
33
44
  readonly size: number;
34
45
  }
@@ -25,6 +25,7 @@
25
25
  */
26
26
  export function createProbeCache() {
27
27
  const verdicts = new Map();
28
+ const failures = new Map();
28
29
  return {
29
30
  async run(key, probe) {
30
31
  const cached = verdicts.get(key);
@@ -34,18 +35,25 @@ export function createProbeCache() {
34
35
  try {
35
36
  return await probe();
36
37
  }
37
- catch {
38
+ catch (error) {
39
+ failures.set(key, error instanceof Error ? error.message : String(error));
38
40
  return false;
39
41
  }
40
42
  })();
41
43
  verdicts.set(key, pending);
42
44
  const verdict = await pending;
43
- if (verdict)
45
+ if (verdict) {
44
46
  verdicts.set(key, true);
45
- else
47
+ failures.delete(key);
48
+ }
49
+ else {
46
50
  verdicts.delete(key);
51
+ }
47
52
  return verdict;
48
53
  },
54
+ failure(key) {
55
+ return failures.get(key);
56
+ },
49
57
  /** Remembered successes, for tests and diagnostics. */
50
58
  get size() {
51
59
  return verdicts.size;
@@ -16,6 +16,76 @@
16
16
  *
17
17
  * @module dsh-plugin-wsl/sandbox-core
18
18
  */
19
+ /**
20
+ * The canonical remedy for a distro whose `bwrap` is missing — also the
21
+ * fallback for callers that know only "the probe failed", without a classified
22
+ * reason. The direct command is family-aware when the caller knows the family;
23
+ * the bootstrap script is always the first-choice path because it detects the
24
+ * family itself.
25
+ *
26
+ * @param distro - the distro to install into.
27
+ * @param family - the detected package family, when known.
28
+ * @returns the operator-facing remedy text.
29
+ */
30
+ export declare function missingBwrapRemedy(distro: string, family?: string): string;
31
+ /**
32
+ * The remedy for a bwrap that PROBED healthy but failed a run — the runtime
33
+ * twin of {@link missingBwrapRemedy}. No family knowledge exists on this path
34
+ * (the probe succeeded, so no family probe ever ran), and reinstalling is only
35
+ * one of the plausible fixes, so the hint leads with diagnosis.
36
+ *
37
+ * @param distro - the distro whose bwrap failed at run time.
38
+ * @returns the operator-facing hint text.
39
+ */
40
+ export declare function bwrapInstallHint(distro: string): string;
41
+ /**
42
+ * Append {@link bwrapInstallHint} to a runner-failure detail that names bwrap.
43
+ * A probe-time failure composes its own classified remedy; this covers the
44
+ * OTHER order — bwrap probed healthy, then failed a real command — where no
45
+ * probe failure is on record and the raw `bwrap: …` line is all the model
46
+ * would see. Details that do not name bwrap pass through untouched: the
47
+ * runner-failure rules match the confinement runner, and the confinement
48
+ * runner is always bwrap here, but the wording stays the decider so a future
49
+ * rule change cannot attach a bwrap remedy to a foreign failure.
50
+ *
51
+ * @param detail - the classified runner-failure detail.
52
+ * @param distro - the distro the command ran in.
53
+ * @returns the detail, with the remedy appended when it names bwrap.
54
+ */
55
+ export declare function withBwrapHint(detail: string, distro: string): string;
56
+ /**
57
+ * Probe one distro's `bwrap` under the shared verdict cache. The probe mirrors
58
+ * upstream's: apply the real read-only profile around `true`, and treat a zero
59
+ * exit as the kernel having accepted and enforced it. A missing `bwrap` fails
60
+ * the spawn and probes unusable, which is what makes the providers fail closed
61
+ * rather than silently run unconfined.
62
+ *
63
+ * A success is remembered for the process lifetime; a failure is not, so a
64
+ * `bubblewrap` installed while the app is running is believed on the next
65
+ * command. The failure's composed remedy stays readable through
66
+ * {@link bwrapFailure}, which is how every refusal and the preflight surface it.
67
+ *
68
+ * @param options - the `wsl.exe` path and the distro to probe.
69
+ * @param options.wslPath - the `wsl.exe` path.
70
+ * @param options.distro - the distro to probe.
71
+ * @param options.signal - optional cancellation.
72
+ * @returns true when a confined command can run there.
73
+ */
74
+ export declare function bwrapUsable(options: {
75
+ wslPath: string;
76
+ distro: string;
77
+ signal?: AbortSignal | undefined;
78
+ }): Promise<boolean>;
79
+ /**
80
+ * The remedy text of a key's last failed probe, as {@link bwrapUsable} composed
81
+ * it — the same string the providers' refusals carry. Undefined while the probe
82
+ * never failed (or last succeeded).
83
+ *
84
+ * @param wslPath - the `wsl.exe` path the probe used.
85
+ * @param distro - the distro the probe ran in.
86
+ * @returns the composed remedy, or undefined.
87
+ */
88
+ export declare function bwrapFailure(wslPath: string, distro: string): string | undefined;
19
89
  /**
20
90
  * The seam's `ConfinedArgv` settlement facts, plus this core's `windowsDrive`
21
91
  * mitigation flag.