dsh-plugin-wsl-env 0.7.5 → 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.
Files changed (92) hide show
  1. package/CHANGELOG.md +345 -8
  2. package/CONTRIBUTING.md +221 -68
  3. package/README.md +50 -35
  4. package/SECURITY.md +18 -2
  5. package/SUPPORT.md +23 -7
  6. package/cordis.patch.yml +9 -0
  7. package/docs/ARCHITECTURE.md +200 -57
  8. package/docs/CONFIGURATION.md +180 -16
  9. package/docs/LIMITATIONS.md +91 -33
  10. package/docs/PARITY.md +66 -38
  11. package/docs/PEER-PARITY.md +95 -32
  12. package/docs/README.zh.md +49 -31
  13. package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
  14. package/docs/UPSTREAM-TERMINAL-TITLE.md +49 -14
  15. package/lib/agent-confined.d.ts +42 -0
  16. package/lib/agent-confined.js +25 -41
  17. package/lib/agent-errors.d.ts +31 -0
  18. package/lib/agent-errors.js +19 -16
  19. package/lib/agent-exec.d.ts +159 -0
  20. package/lib/agent-exec.js +151 -165
  21. package/lib/agent-protocol.d.ts +192 -0
  22. package/lib/agent-protocol.js +76 -100
  23. package/lib/agent-shared.d.ts +27 -0
  24. package/lib/agent-shared.js +12 -12
  25. package/lib/agent.d.ts +353 -0
  26. package/lib/agent.js +536 -552
  27. package/lib/auto-preset.d.ts +110 -0
  28. package/lib/auto-preset.js +157 -141
  29. package/lib/bwrap.d.ts +90 -0
  30. package/lib/bwrap.js +18 -18
  31. package/lib/file-reference-wsl.d.ts +160 -0
  32. package/lib/file-reference-wsl.js +192 -173
  33. package/lib/fs-decisions.d.ts +107 -0
  34. package/lib/fs-decisions.js +64 -60
  35. package/lib/fs-routing.d.ts +182 -0
  36. package/lib/fs-routing.js +173 -116
  37. package/lib/fs-substrate.d.ts +258 -0
  38. package/lib/fs-substrate.js +248 -248
  39. package/lib/fs.d.ts +10 -0
  40. package/lib/fs.js +5 -0
  41. package/lib/fsio-agent.d.ts +307 -0
  42. package/lib/fsio-agent.js +414 -410
  43. package/lib/fsio-text.d.ts +115 -0
  44. package/lib/fsio-text.js +104 -84
  45. package/lib/git-route.d.ts +100 -0
  46. package/lib/git-route.js +46 -47
  47. package/lib/index.d.ts +1003 -0
  48. package/lib/index.js +1106 -1003
  49. package/lib/listing.d.ts +114 -0
  50. package/lib/listing.js +68 -55
  51. package/lib/paths.d.ts +182 -0
  52. package/lib/paths.js +134 -109
  53. package/lib/picker.d.ts +266 -0
  54. package/lib/picker.js +371 -351
  55. package/lib/ports.d.ts +44 -0
  56. package/lib/ports.js +39 -29
  57. package/lib/preflight.d.ts +91 -0
  58. package/lib/preflight.js +94 -0
  59. package/lib/preset-choice.d.ts +33 -0
  60. package/lib/preset-choice.js +15 -7
  61. package/lib/probe-cache.d.ts +51 -0
  62. package/lib/probe-cache.js +43 -29
  63. package/lib/sandbox-core.d.ts +149 -0
  64. package/lib/sandbox-core.js +239 -87
  65. package/lib/sandbox.d.ts +48 -0
  66. package/lib/sandbox.js +4 -4
  67. package/lib/search-exec.d.ts +96 -0
  68. package/lib/search-exec.js +52 -79
  69. package/lib/search-route.d.ts +75 -0
  70. package/lib/search-route.js +30 -31
  71. package/lib/shell-env.d.ts +88 -0
  72. package/lib/shell-env.js +78 -74
  73. package/lib/shell.d.ts +10 -0
  74. package/lib/shell.js +5 -0
  75. package/lib/subprocess.d.ts +313 -0
  76. package/lib/subprocess.js +267 -262
  77. package/lib/terminal-activity.d.ts +98 -0
  78. package/lib/terminal-activity.js +29 -39
  79. package/lib/terminal-route.d.ts +27 -0
  80. package/lib/terminal-route.js +16 -8
  81. package/lib/watcher.d.ts +77 -0
  82. package/lib/watcher.js +120 -126
  83. package/lib/workspace-files-route.d.ts +132 -0
  84. package/lib/workspace-files-route.js +73 -81
  85. package/lib/workspace-files-wsl.d.ts +257 -0
  86. package/lib/workspace-files-wsl.js +311 -251
  87. package/lib/wsl.d.ts +266 -0
  88. package/lib/wsl.js +150 -155
  89. package/locale/preflight/en.json +6 -0
  90. package/locale/preflight/zh.json +6 -0
  91. package/package.json +78 -22
  92. package/scripts/diagnose.sh +92 -0
package/lib/index.d.ts ADDED
@@ -0,0 +1,1003 @@
1
+ /**
2
+ * dsh-plugin-wsl — run a DeepSeek Harness session against a WSL distro.
3
+ *
4
+ * DeepSeek Harness is a Cordis application whose capabilities are exposed as
5
+ * service seams: `ctx.fs` (filesystem), `ctx.shell` (command execution),
6
+ * `ctx.sandbox` (confinement), `ctx.directoryPicker`, `ctx.workspaceRegistry`.
7
+ * The model-facing tools (`dsh-tool-fs`, `dsh-tool-fs-search`, `dsh-tool-bash`)
8
+ * consume those seams and never talk to a filesystem or a shell directly. A WSL
9
+ * integration is therefore a seam provider, not a new tool.
10
+ *
11
+ * This package provides two of them:
12
+ *
13
+ * - {@link WslShellExecutor} registers `ctx.shell` and runs every command
14
+ * inside the distro as `wsl.exe -d <distro> --cd <linux dir> --exec <shell> -lc <cmd>`,
15
+ * where `<shell>` is the distro user's login shell (not a hardcoded bash).
16
+ * - {@link WslFileSystem} registers `ctx.fs` and maps Linux paths onto the
17
+ * distro's UNC share so the host's own fs stack (including the packaged
18
+ * ripgrep used by `dsh-tool-fs-search`) reads and writes real distro files.
19
+ *
20
+ * ## The one constraint that shapes everything
21
+ *
22
+ * On Windows, DSH confines commands with `dsh-sandbox-windows-acl`: a
23
+ * restricted, low-integrity token plus a capability-SID write allowlist. That
24
+ * token cannot reach WSL at all — `wsl.exe` fails with `Wsl/E_ACCESSDENIED` and
25
+ * the UNC share `\\wsl.localhost\<distro>` reports access denied. Both work
26
+ * normally outside the sandbox.
27
+ *
28
+ * The confinement is applied by the *sandboxing* executor (`pwsh-sandbox`),
29
+ * which wraps the argv through `ctx.sandbox.confine`. This plugin therefore
30
+ * subclasses the NON-sandboxing `LocalBashExecutor`, and inherits
31
+ * `sandboxMode === undefined` from the seam — which is the honest capability
32
+ * fact: the tool layer reads it and advertises that these commands are not
33
+ * confined. WSL access is inherently outside the harness's file sandbox; do not
34
+ * paper over that by reporting a mode this provider does not enforce.
35
+ *
36
+ * This is a TypeScript source built to `lib/index.js`; edit THIS file and run
37
+ * `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
38
+ * fails when it drifts.
39
+ *
40
+ * @module dsh-plugin-wsl
41
+ */
42
+ import z from "@deepseek-ai/schemastery";
43
+ import { LocalBashExecutor } from "@deepseek-ai/dsh-bash-local";
44
+ import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
45
+ import { FsError } from "@deepseek-ai/dsh-fs";
46
+ import { WslSandbox } from "./sandbox.js";
47
+ import type { WslAgent } from "./agent.js";
48
+ import { AgentSubstrate } from "./fs-substrate.js";
49
+ import type { Context } from "@deepseek-ai/cordis";
50
+ import type { FsDirEntry, FsEditOutcome, FsEditRequest, FsInfo, FsPathInfo, FsTarget, FsVersion, FsWriteIntent, FsWriteOutcome } from "@deepseek-ai/dsh-fs";
51
+ import type { SandboxExecutionPolicy, SandboxMode } from "@deepseek-ai/dsh-sandbox";
52
+ import type { SubprocessSpawnSpec } from "@deepseek-ai/dsh-subprocess";
53
+ /**
54
+ * Upstream shell vocabulary, derived through `@deepseek-ai/dsh-bash-local` —
55
+ * this package does not depend on `@deepseek-ai/dsh-shell` directly, so the
56
+ * execution types ride in through the executor's own signatures.
57
+ */
58
+ export type ShellExecSpec = Parameters<LocalBashExecutor["execute"]>[0];
59
+ export type ShellExecRequest = Parameters<LocalBashExecutor["resolve"]>[0];
60
+ export type ShellExecution = Awaited<ReturnType<LocalBashExecutor["execute"]>>;
61
+ /**
62
+ * One confined command's distro-side profile, as the confinement returns it:
63
+ * the wrapped argv, the enforcement completeness, the denial dialect, the
64
+ * runner-failure rules and the Windows-drive masking fact.
65
+ */
66
+ export type ConfinedProfile = Awaited<ReturnType<InstanceType<typeof WslSandbox>["confine"]>>;
67
+ /**
68
+ * The resolved spec this executor stamps with its home-marker fields: an empty
69
+ * `workdir` plus the relative tail that {@link WslShellExecutor.withDefaultWorkdir}
70
+ * joins against the distro home. The extra keys are this provider's own; the
71
+ * seam's `ShellExecSpec` does not know them.
72
+ */
73
+ export type WslExecSpec = ShellExecSpec & {
74
+ relativeWorkdir?: string;
75
+ };
76
+ /**
77
+ * The WSL shell executor's validated config — the keys of
78
+ * {@link WslShellExecutor.Config}, which the upstream `LocalBashExecutor`
79
+ * config type does not know. The seven volatile fields arrive as live
80
+ * wrappers, so they are spelled as their `.get()` shape (the reading
81
+ * {@link configuredCwd} and `hostCwd` perform).
82
+ */
83
+ export interface WslShellExecutorConfig {
84
+ /** The pinned distro; empty means WSL's default. */
85
+ distro: string;
86
+ /** The `wsl.exe` path. */
87
+ wslPath: string;
88
+ /** The distro shell; empty resolves the login shell. */
89
+ shell: string;
90
+ /** Whether `<shell> -lc` semantics apply. */
91
+ loginShell: boolean;
92
+ /** Whether commands confine inside the distro. */
93
+ sandbox: boolean;
94
+ /** Whether confined profiles mask `/mnt`. */
95
+ maskWindowsDrive: boolean;
96
+ /** The volatile default workdir. */
97
+ cwd: {
98
+ get(): string | undefined;
99
+ };
100
+ /** The volatile Windows spawn directory. */
101
+ hostCwd: {
102
+ get(): string | undefined;
103
+ };
104
+ /** Extra env names forwarded through WSLENV. */
105
+ forwardEnv: string[];
106
+ /** The volatile default timeout. */
107
+ timeoutMs: {
108
+ get(): number;
109
+ };
110
+ /** The volatile timeout cap. */
111
+ maxTimeoutMs: {
112
+ get(): number;
113
+ };
114
+ /** The volatile per-stream output cap. */
115
+ maxOutputBytes: {
116
+ get(): number;
117
+ };
118
+ /** The volatile spill-file cap. */
119
+ maxSpillBytes: {
120
+ get(): number;
121
+ };
122
+ /** The volatile kill-escalation grace. */
123
+ graceMs: {
124
+ get(): number;
125
+ };
126
+ /** Whether execution prefers the resident agent. */
127
+ agent: boolean;
128
+ }
129
+ /**
130
+ * The upstream executor's spawn-spec builder, re-opened for this subclass —
131
+ * see docs/UPSTREAM-SPAWN-SEAM.md. Type-level bridge only; the runtime class
132
+ * is untouched.
133
+ */
134
+ export type SpawnSeamExecutor = Omit<LocalBashExecutor, "spawnSpec"> & {
135
+ spawnSpec: (spec: ShellExecSpec, argv: string[], stdoutMaxBytes: number, signal?: AbortSignal) => SubprocessSpawnSpec;
136
+ };
137
+ /** The per-process confinement facts retained until settlement. */
138
+ interface ProcessFacts {
139
+ /** The mode the process ran under. */
140
+ mode: SandboxMode | undefined;
141
+ /** The seam's enforcement completeness. */
142
+ enforcement: "full" | "partial";
143
+ /** The case-insensitive stderr substrings a denial produces. */
144
+ denialSignatures: string[];
145
+ /** Structured runner-failure evidence rules. */
146
+ runnerFailureRules: Array<{
147
+ fatalSignatures: string[];
148
+ allowedExitCodes?: number[];
149
+ informationalLines?: string[];
150
+ }>;
151
+ /** The confinement runner program (`argv[0]`), for spawn-failure attribution. */
152
+ runnerProgram: string;
153
+ /** The Linux workdir the process was given. */
154
+ workdir: string;
155
+ /** Whether the profile masks the Windows drive. */
156
+ windowsDrive: "masked" | "visible";
157
+ }
158
+ /**
159
+ * Bash executor that runs every command inside a WSL distro.
160
+ *
161
+ * Extends the unconfined `LocalBashExecutor`, so command defaulting, deadline
162
+ * handling, output caps, spill files and background-process management are the
163
+ * shipped implementation's; this subclass replaces only the argv (prefix with
164
+ * `wsl.exe`) and the process working directory (wsl.exe must be started from a
165
+ * Windows directory, while the Linux directory rides on `--cd`).
166
+ */
167
+ export declare class WslShellExecutor extends LocalBashExecutor {
168
+ static inject: string[];
169
+ static Config: z<Schemastery.ObjectS<NoInfer<{
170
+ /** Distro name; empty means WSL's own default distro. */
171
+ distro: z<string, string, "defined">;
172
+ /** Path to `wsl.exe`; overridable for a non-standard install. */
173
+ wslPath: z<string, string, "defined">;
174
+ /**
175
+ * The shell commands run in. Empty means "resolve the login shell of the
176
+ * distro's user" — hardcoding bash silently ignores a user whose passwd
177
+ * entry points at zsh, fish, dash, or anything else, losing their PATH and
178
+ * rc setup. Set an absolute path inside the distro to pin one.
179
+ */
180
+ shell: z<string, string, "defined">;
181
+ /**
182
+ * Login-shell semantics (`<shell> -lc`) source the profile scripts, which is
183
+ * what makes an interactive WSL setup's PATH (nvm, cargo, pyenv) visible.
184
+ * Set false for a fast, fully deterministic `<shell> -c`. Applies only to the
185
+ * POSIX family — see `shellArgs` for why other shells get a bare `-c`.
186
+ */
187
+ loginShell: z<boolean, boolean, "defined">;
188
+ /**
189
+ * Confine every command inside the distro with `bwrap`, and report the
190
+ * policy's mode through {@link sandboxMode}. Fails closed: a distro without
191
+ * a usable `bwrap` produces `SANDBOX_UNAVAILABLE` rather than an unconfined
192
+ * run. `false` restores the pre-sandbox behaviour exactly — commands run
193
+ * unconfined and `sandboxMode` is `undefined`, so the model is told so.
194
+ */
195
+ sandbox: z<boolean, boolean, "defined">;
196
+ /**
197
+ * Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
198
+ * Windows drive from confined commands: its files cannot be read or
199
+ * exfiltrated and its executables cannot be launched through interop —
200
+ * the hole that lets a "sandboxed" command reach your full Windows token.
201
+ * A narrowing, not a closure (a command can still write an executable
202
+ * into the workspace and run it — binfmt dispatches on content, not
203
+ * location), so enforcement stays `partial` and the sandbox fact reports
204
+ * `windowsDrive: "masked"`. Costs: confined commands cannot read or touch
205
+ * anything under `/mnt`, and tools that probe the drive degrade. The
206
+ * complete closure is distro-level: `[interop] enabled=false` in
207
+ * `wsl.conf`.
208
+ */
209
+ maskWindowsDrive: z<boolean, boolean, "defined">;
210
+ /**
211
+ * Default workdir for a request that names none; accepts `/home/andy/proj`,
212
+ * a UNC path, or `C:\...`. Empty — the default — means the distro user's
213
+ * home, resolved inside the distro.
214
+ */
215
+ cwd: z<string, string, "volatile">;
216
+ /** Windows directory the `wsl.exe` process itself starts in. */
217
+ hostCwd: z<string, string, "volatile">;
218
+ /** Extra env names to forward into the distro through WSLENV. */
219
+ forwardEnv: z<string[], string[], "defined">;
220
+ timeoutMs: z<number, number, "volatile-defined">;
221
+ maxTimeoutMs: z<number, number, "volatile-defined">;
222
+ maxOutputBytes: z<number, number, "volatile-defined">;
223
+ maxSpillBytes: z<number, number, "volatile-defined">;
224
+ graceMs: z<number, number, "volatile-defined">;
225
+ /**
226
+ * Run commands through the resident agent when it is up (one long-lived
227
+ * in-distro process; in-distro timeout semantics), falling back to the
228
+ * one-shot `wsl.exe` path whenever the agent is out. `false` never uses
229
+ * the agent. The confinement, the output budgets' shapes and the result
230
+ * decoration are identical on both paths.
231
+ */
232
+ agent: z<boolean, boolean, "defined">;
233
+ }>>, Schemastery.ObjectT<NoInfer<{
234
+ /** Distro name; empty means WSL's own default distro. */
235
+ distro: z<string, string, "defined">;
236
+ /** Path to `wsl.exe`; overridable for a non-standard install. */
237
+ wslPath: z<string, string, "defined">;
238
+ /**
239
+ * The shell commands run in. Empty means "resolve the login shell of the
240
+ * distro's user" — hardcoding bash silently ignores a user whose passwd
241
+ * entry points at zsh, fish, dash, or anything else, losing their PATH and
242
+ * rc setup. Set an absolute path inside the distro to pin one.
243
+ */
244
+ shell: z<string, string, "defined">;
245
+ /**
246
+ * Login-shell semantics (`<shell> -lc`) source the profile scripts, which is
247
+ * what makes an interactive WSL setup's PATH (nvm, cargo, pyenv) visible.
248
+ * Set false for a fast, fully deterministic `<shell> -c`. Applies only to the
249
+ * POSIX family — see `shellArgs` for why other shells get a bare `-c`.
250
+ */
251
+ loginShell: z<boolean, boolean, "defined">;
252
+ /**
253
+ * Confine every command inside the distro with `bwrap`, and report the
254
+ * policy's mode through {@link sandboxMode}. Fails closed: a distro without
255
+ * a usable `bwrap` produces `SANDBOX_UNAVAILABLE` rather than an unconfined
256
+ * run. `false` restores the pre-sandbox behaviour exactly — commands run
257
+ * unconfined and `sandboxMode` is `undefined`, so the model is told so.
258
+ */
259
+ sandbox: z<boolean, boolean, "defined">;
260
+ /**
261
+ * Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
262
+ * Windows drive from confined commands: its files cannot be read or
263
+ * exfiltrated and its executables cannot be launched through interop —
264
+ * the hole that lets a "sandboxed" command reach your full Windows token.
265
+ * A narrowing, not a closure (a command can still write an executable
266
+ * into the workspace and run it — binfmt dispatches on content, not
267
+ * location), so enforcement stays `partial` and the sandbox fact reports
268
+ * `windowsDrive: "masked"`. Costs: confined commands cannot read or touch
269
+ * anything under `/mnt`, and tools that probe the drive degrade. The
270
+ * complete closure is distro-level: `[interop] enabled=false` in
271
+ * `wsl.conf`.
272
+ */
273
+ maskWindowsDrive: z<boolean, boolean, "defined">;
274
+ /**
275
+ * Default workdir for a request that names none; accepts `/home/andy/proj`,
276
+ * a UNC path, or `C:\...`. Empty — the default — means the distro user's
277
+ * home, resolved inside the distro.
278
+ */
279
+ cwd: z<string, string, "volatile">;
280
+ /** Windows directory the `wsl.exe` process itself starts in. */
281
+ hostCwd: z<string, string, "volatile">;
282
+ /** Extra env names to forward into the distro through WSLENV. */
283
+ forwardEnv: z<string[], string[], "defined">;
284
+ timeoutMs: z<number, number, "volatile-defined">;
285
+ maxTimeoutMs: z<number, number, "volatile-defined">;
286
+ maxOutputBytes: z<number, number, "volatile-defined">;
287
+ maxSpillBytes: z<number, number, "volatile-defined">;
288
+ graceMs: z<number, number, "volatile-defined">;
289
+ /**
290
+ * Run commands through the resident agent when it is up (one long-lived
291
+ * in-distro process; in-distro timeout semantics), falling back to the
292
+ * one-shot `wsl.exe` path whenever the agent is out. `false` never uses
293
+ * the agent. The confinement, the output budgets' shapes and the result
294
+ * decoration are identical on both paths.
295
+ */
296
+ agent: z<boolean, boolean, "defined">;
297
+ }>>, "plain">;
298
+ /** Cached default distro, resolved once from `wsl.exe -l -q`. */
299
+ resolvedDistro: string;
300
+ /** Cached distro-user login shell, resolved once. */
301
+ resolvedShell: string;
302
+ /** Cached distro-user home, the workdir when `cwd` is empty. */
303
+ resolvedHome: string | undefined;
304
+ /** The default mode — the capability fact the tool layer reads. */
305
+ mode: SandboxMode | undefined;
306
+ /** The distro-side confinement, or nothing when the operator opted out. */
307
+ sandbox: InstanceType<typeof WslSandbox> | undefined;
308
+ /**
309
+ * Per-process confinement facts retained until settlement. Overlapping calls
310
+ * can carry different policies, so a shared latest-wrap value would classify
311
+ * a process against the wrong facts. Unconfined processes have no entry.
312
+ */
313
+ processFacts: Map<ShellExecution, ProcessFacts>;
314
+ /**
315
+ * @param ctx - the composition context.
316
+ * @param config - the validated plugin config.
317
+ */
318
+ constructor(ctx: Context, config: WslShellExecutorConfig);
319
+ /**
320
+ * The mode this executor confines with, or `undefined` when it does not
321
+ * confine at all. `dsh-tool-bash` reads exactly this to decide whether to
322
+ * advertise the escalation field, so it must disappear together with the
323
+ * enforcement — never claim a boundary that is not applied.
324
+ *
325
+ * @returns the deployment default mode, or `undefined` when unconfined.
326
+ */
327
+ get sandboxMode(): SandboxMode | undefined;
328
+ /**
329
+ * The distro every command runs in, resolving WSL's default on first use.
330
+ *
331
+ * @returns the distro name.
332
+ */
333
+ distro(): Promise<string>;
334
+ /**
335
+ * The shell every command runs in: the configured one, else the login shell of
336
+ * the distro user, resolved once and cached.
337
+ *
338
+ * @returns an absolute path to the shell inside the distro.
339
+ */
340
+ shellName(): Promise<string>;
341
+ /**
342
+ * The shell every command runs in, resolving the distro login shell on
343
+ * first use.
344
+ *
345
+ * @returns an absolute path to the shell inside the distro.
346
+ */
347
+ shell(): Promise<string>;
348
+ /**
349
+ * Windows directory the `wsl.exe` process is started from.
350
+ *
351
+ * @returns the host directory to spawn in.
352
+ */
353
+ hostCwd(): string;
354
+ /**
355
+ * The shared resident agent serving this executor's distro.
356
+ *
357
+ * @param distro - the resolved distro name.
358
+ * @returns the shared agent.
359
+ */
360
+ executionAgent(distro: string): WslAgent;
361
+ /**
362
+ * The argv that runs INSIDE the distro for one spec: the resolved shell, its
363
+ * login/command flags, and the command string. This is the argv a
364
+ * distro-side sandbox wraps, which is why it is built separately from the
365
+ * `wsl.exe` invocation around it.
366
+ *
367
+ * @param spec - the resolved spec from {@link LocalBashExecutor.resolve}.
368
+ * @param shell - the resolved distro shell.
369
+ * @returns the distro-side argv.
370
+ */
371
+ distroArgv(spec: ShellExecSpec, shell: string): string[];
372
+ /**
373
+ * Build the `wsl.exe` argv for one resolved spec. The command string is
374
+ * passed as a single argv element to the resolved shell, so there is no
375
+ * quoting layer of our own; Node's argv array reaches `wsl.exe` verbatim.
376
+ *
377
+ * `--exec` is load-bearing — see the comment inside.
378
+ *
379
+ * @param spec - the resolved spec from {@link LocalBashExecutor.resolve}.
380
+ * @param distro - the resolved distro name.
381
+ * @param shell - the resolved distro shell.
382
+ * @param inner - the already-confined distro argv, when confinement applies.
383
+ * @returns the argv to spawn.
384
+ */
385
+ argv(spec: ShellExecSpec, distro: string, shell: string, inner?: string[]): string[];
386
+ /**
387
+ * Override only the spawn's working directory and environment. `spec.workdir`
388
+ * is a Linux path, which a Windows CreateProcess call cannot use as `cwd`.
389
+ *
390
+ * The inherited builder is private, so the call goes through the
391
+ * {@link SpawnSeamExecutor} bridge (`inheritedSpawnSpec`) instead of `super`
392
+ * — see docs/UPSTREAM-SPAWN-SEAM.md.
393
+ *
394
+ * @param spec - resolved execution settings.
395
+ * @param argv - the argv from {@link WslShellExecutor.argv}.
396
+ * @param stdoutMaxBytes - per-call stdout cap.
397
+ * @param signal - the spawn cancellation signal.
398
+ * @returns the subprocess spawn spec.
399
+ */
400
+ spawnSpec(spec: ShellExecSpec, argv: string[], stdoutMaxBytes: number, signal?: AbortSignal): SubprocessSpawnSpec;
401
+ /**
402
+ * Resolve a request into a spec, replacing the parent's `process.cwd()`
403
+ * fallback with this provider's distro-home marker and stamping the per-call
404
+ * sandbox policy.
405
+ *
406
+ * The seam requires this step to be synchronous — the tool layer calls
407
+ * `ctx.shell.execute(ctx.shell.resolve(…))` with no `await` between them — so
408
+ * neither the home nor a confinement decision can be made here. Two request
409
+ * shapes default to the home when no `cwd` is configured: one that names no
410
+ * `workdir` at all, and one that names it only RELATIVELY — the parent
411
+ * resolves the first against `process.cwd()` (a Windows directory) and has
412
+ * nothing of its own to join the second onto. Both are stamped as the empty
413
+ * marker, with a relative tail carried verbatim in `relativeWorkdir`;
414
+ * {@link withDefaultWorkdir} resolves the pair against the home. The policy
415
+ * travels on the spec to {@link execute}, which awaits the wrap.
416
+ *
417
+ * @param request - the caller's request.
418
+ * @returns the fully-resolved spec.
419
+ */
420
+ resolve(request: ShellExecRequest): WslExecSpec;
421
+ /**
422
+ * The spec to execute, with the home marker resolved against the distro
423
+ * user's home: `workdir: ""` becomes the home itself, and a relative tail
424
+ * carried by {@link WslShellExecutor.resolve} is joined under it. That is one
425
+ * `sh -c 'printf %s "$HOME"'` per provider instance, cached, and it happens
426
+ * only for a request that defaulted — an absolute workdir never reaches here.
427
+ *
428
+ * @param spec - the resolved spec.
429
+ * @returns the spec to execute.
430
+ */
431
+ withDefaultWorkdir(spec: WslExecSpec): Promise<WslExecSpec>;
432
+ /**
433
+ * Decorate a handle's foreground projection in place, memoized once. The
434
+ * handle keeps its identity — never wrapped in a second object — because the
435
+ * per-process facts and `onProcessDone` key on the exact instance.
436
+ *
437
+ * @template H - the concrete handle type, preserved so the decorated handle
438
+ * keeps its identity in the callers' types; the settled result row the maps
439
+ * are spelled in is derived from its `result()`.
440
+ * @param ex - the execution handle to decorate.
441
+ * @param map - maps the settled result.
442
+ * @param mapError - maps a rejection, when the caller has one.
443
+ * @returns the same handle.
444
+ */
445
+ static decorateResult<H extends {
446
+ result(): Promise<unknown>;
447
+ }>(ex: H, map: (result: Awaited<ReturnType<H["result"]>>) => Awaited<ReturnType<H["result"]>>, mapError: (error: unknown) => Awaited<ReturnType<H["result"]>>): H;
448
+ /**
449
+ * Stamp the per-process sandbox facts before `done` settles, so a background
450
+ * process reports the same mode, denial and enforcement a foreground run
451
+ * would. Signal deaths are not denials.
452
+ *
453
+ * @param proc - the settled process handle.
454
+ * @param stderr - the process's retained stderr tail used by subclasses for settlement classification.
455
+ * @param providerRejected - whether the subprocess promise rejected without a direct outcome.
456
+ * @param providerError - the provider rejection reason, which may itself be undefined.
457
+ */
458
+ onProcessDone(proc: ShellExecution, stderr: string, providerRejected: boolean, providerError?: unknown): void;
459
+ /**
460
+ * Reject a settled command that failed because the distro itself is missing,
461
+ * stopped or unregistered.
462
+ *
463
+ * `wsl.exe` exits non-zero for that with an EMPTY stderr and its diagnostic on
464
+ * stdout, so nothing else here can see it: the denial signatures read stderr, and
465
+ * the result would otherwise reach the model as a bare non-zero code with no
466
+ * cause. The message names the remedy and says plainly that a wider permission
467
+ * cannot help — a sandbox flag does not create a distro.
468
+ *
469
+ * @param result - the settled shell result.
470
+ * @param distro - the distro the command asked for.
471
+ * @throws {Error} carrying the `WSL_E_*` code and the remedy.
472
+ */
473
+ throwIfDistroUnavailable(result: {
474
+ exitCode?: number | null;
475
+ stdout?: {
476
+ text?: string;
477
+ };
478
+ stderr?: {
479
+ text?: string;
480
+ };
481
+ } | undefined, distro: string): void;
482
+ /**
483
+ * Reject a command that ran in the wrong directory.
484
+ *
485
+ * `wsl.exe --cd <path>` does not fail on a missing directory: it warns on stderr,
486
+ * runs the command in `/`, and exits 0. Reporting that as success would let a write
487
+ * or a build act on the wrong tree while every signal says it worked, so it becomes
488
+ * an error naming the directory that could not be entered.
489
+ *
490
+ * @param result - the settled shell result.
491
+ * @param workdir - the Linux directory the request asked for.
492
+ * @throws {Error} naming the directory and the fallback that happened.
493
+ */
494
+ throwIfWorkdirMissing(result: {
495
+ exitCode?: number | null;
496
+ stdout?: {
497
+ text?: string;
498
+ };
499
+ stderr?: {
500
+ text?: string;
501
+ };
502
+ } | undefined, workdir: string): void;
503
+ /**
504
+ * Execute one resolved spec inside the distro, confined by the per-call policy
505
+ * when there is one.
506
+ *
507
+ * Confinement wraps the DISTRO-side argv (`<shell> -lc <command>`) in `bwrap`
508
+ * and hands the result to `wsl.exe --exec`, so the sandbox is built inside the
509
+ * distro while the process that spawns it stays the ordinary Windows one. The
510
+ * `danger-full-access` escalation — and an operator's `sandbox: false` — skip
511
+ * the wrap entirely and run the command as before.
512
+ *
513
+ * @param spec - resolved execution settings.
514
+ * @returns the live shell process handle.
515
+ */
516
+ execute(spec: ShellExecSpec): Promise<ShellExecution>;
517
+ /**
518
+ * Refuse a workdir that names another WSL distro — the shell-side twin of
519
+ * the filesystem's `FS_OUTSIDE_DISTRO` fence. Left unguarded, `toLinuxPath`
520
+ * strips the UNC's distro name and the command runs inside THIS distro
521
+ * against whatever tree happens to share the path, silently, while the fs
522
+ * side refuses the same workspace outright. The wording mirrors the fs
523
+ * refusal (minus `restrictToDistro`, which is an fs-only key).
524
+ *
525
+ * @param workdir - the resolved workdir (UNC, Linux path, or Windows path).
526
+ * @param distro - the pinned distro name.
527
+ * @throws {Error} naming both distros and both ways out.
528
+ */
529
+ throwIfWorkdirInAnotherDistro(workdir: string, distro: string): void;
530
+ /**
531
+ * The agent-backed execution path: one long-lived in-distro process, the
532
+ * confinement argv unchanged, the handle shape unchanged. Any
533
+ * {@link AgentUnavailableError} propagates for the caller's fallback.
534
+ *
535
+ * @param resolved - the resolved spec from {@link WslShellExecutor.execute}.
536
+ * @param distro - the resolved distro name.
537
+ * @param shell - the resolved distro shell.
538
+ * @returns the decorated execution result.
539
+ */
540
+ executeViaAgent(resolved: WslExecSpec, distro: string, shell: string): Promise<ShellExecution>;
541
+ /**
542
+ * The pre-agent execution path, verbatim: one `wsl.exe` per command through
543
+ * the inherited `executeArgv` machinery. Kept as the permanent fallback for
544
+ * an out-of-service agent and as the `agent: false` behaviour.
545
+ *
546
+ * @param resolved - the resolved spec.
547
+ * @param distro - the resolved distro name.
548
+ * @param shell - the resolved distro shell.
549
+ * @returns the decorated execution result.
550
+ */
551
+ executeOneShot(resolved: WslExecSpec, distro: string, shell: string): Promise<ShellExecution>;
552
+ }
553
+ /**
554
+ * Filesystem backend over a WSL distro's UNC share.
555
+ *
556
+ * Reads, listings, stats and searches reuse the host backend verbatim: the
557
+ * share is a real filesystem to Node, and the packaged ripgrep walks it too.
558
+ * Two things are deliberately replaced.
559
+ *
560
+ * **Resolution** accepts Linux paths (`/home/andy/proj/src/a.ts`) and shows them
561
+ * back as Linux paths, while `targetKey` stays the UNC path the host I/O uses —
562
+ * the seam treats `targetKey` as opaque and reserves `displayPath` for model and
563
+ * UI output, so this rewrite is contract-legal.
564
+ *
565
+ * **Mutation guards are hoisted.** The host backend publishes a guarded
566
+ * *creation* by hard-linking a staged file into place, and the WSL UNC share
567
+ * rejects hard links with `ENOTSUP`; it also refuses `symlink` with `EPERM`.
568
+ * Guarded creation through the share would therefore fail with `FS_IO_ERROR` on
569
+ * the most common write there is — creating a new file. This backend checks the
570
+ * caller's guard itself and then delegates the unguarded publication, which the
571
+ * share does support. The residual window is the same check-then-act race
572
+ * `dsh-fs-sandbox` already documents and accepts.
573
+ *
574
+ * **Publication is replaced.** Unguarded is not enough on its own: the host
575
+ * backend's Windows branch also copies the replaced file's security descriptor
576
+ * and publishes with `ReplaceFileW`, and neither exists on a 9p share — the
577
+ * descriptor read fails every write to an existing file outright. See the
578
+ * constructor for what runs instead.
579
+ */
580
+ /**
581
+ * The WSL filesystem provider's validated config — the keys of
582
+ * {@link WslFileSystem.Config}, which the upstream `LocalFileSystem` config
583
+ * type does not know. This provider has no volatile fields: every key is read
584
+ * as its plain value.
585
+ */
586
+ export interface WslFileSystemConfig {
587
+ /** The pinned distro; empty resolves WSL's default. */
588
+ distro: string;
589
+ /** The base directory for relative input. */
590
+ cwd: string;
591
+ /** The `wsl.exe` path, for default-distro resolution. */
592
+ wslPath: string;
593
+ /**
594
+ * The overwrite-diff byte bound per side; schema-defaulted when mounted
595
+ * through cordis, and defaulted again by the substrate when absent.
596
+ */
597
+ diffBasisMaxBytes?: number;
598
+ /** Whether mutations are fenced by the policy. */
599
+ sandbox: boolean;
600
+ /** Whether confined profiles mask `/mnt`. */
601
+ maskWindowsDrive: boolean;
602
+ /** Whether paths leaving the distro are refused. */
603
+ restrictToDistro: boolean;
604
+ /** The in-distro watch scan depth bound. */
605
+ watchMaxDepth: number;
606
+ /**
607
+ * Which I/O substrate serves the file tools; schema-defaulted when mounted
608
+ * through cordis, absent on direct construction (the router), which the
609
+ * constructor's refusal treats as `"agent"`.
610
+ */
611
+ substrate?: string;
612
+ }
613
+ export declare class WslFileSystem extends LocalFileSystem {
614
+ static inject: string[];
615
+ static Config: z<Schemastery.ObjectS<NoInfer<{
616
+ /** Distro name; empty resolves WSL's default on first use. */
617
+ distro: z<string, string, "defined">;
618
+ /**
619
+ * Default workdir for relative input; accepts a Linux path, a UNC path, or
620
+ * `C:\...`. Empty — the default — means the distro user's home, resolved
621
+ * inside the distro.
622
+ */
623
+ cwd: z<string, string, "defined">;
624
+ /** Path to `wsl.exe`, used only to resolve the default distro. */
625
+ wslPath: z<string, string, "defined">;
626
+ /** UTF-8 byte limit per overwrite-diff side, mirroring the host backend. */
627
+ diffBasisMaxBytes: z<number, number, "defined">;
628
+ /**
629
+ * Fence mutations by `ctx.sandboxPolicy`, and report the mode through
630
+ * {@link sandboxMode}. `false` restores the pre-sandbox behaviour exactly —
631
+ * an unfenced backend that reports `undefined` — for an operator who wants
632
+ * the old posture and accepts what it means: the model is told these
633
+ * operations are unconfined.
634
+ */
635
+ sandbox: z<boolean, boolean, "defined">;
636
+ /**
637
+ * Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
638
+ * Windows drive from confined commands — same meaning and same honest
639
+ * limits as the `wsl-shell` key of the same name (its doc carries the
640
+ * full story). Confined fs mutations publish through the masked resident;
641
+ * reads and the share substrate are unaffected.
642
+ */
643
+ maskWindowsDrive: z<boolean, boolean, "defined">;
644
+ /**
645
+ * Refuse any path that leaves the pinned distro. A WSL filesystem is a
646
+ * whole-Linux view whose `/mnt/c` also reaches the Windows disk, so without
647
+ * this the backend would silently widen the session far beyond the
648
+ * workspace the operator selected.
649
+ */
650
+ restrictToDistro: z<boolean, boolean, "defined">;
651
+ /**
652
+ * How deep the in-distro watch scan walks, `1` = the watched directory and
653
+ * its direct children. `0` — the default — walks the whole tree, which is
654
+ * what the invalidation contract asks for but a real cost every poll over
655
+ * a tree containing a `node_modules`; a bound trades that cost for a
656
+ * documented blind spot: a change below the bound is not seen until
657
+ * another change above it fires the same callback.
658
+ */
659
+ watchMaxDepth: z<number, number, "defined">;
660
+ /**
661
+ * Which I/O substrate serves the file tools. Only `"agent"` exists — the
662
+ * resident in-distro agent (`lib/fs-substrate.js`): reads, writes and
663
+ * identities run where the files live, symlinks and mode bits are native,
664
+ * and the mutation guard survives to the write's no-replace publication,
665
+ * kernel-enforced by the confined resident when the policy is confined.
666
+ * The former `"share"` opt-out — the Windows-side host filesystem stack
667
+ * over the distro's 9p share — is retired: it was the one model-facing
668
+ * I/O path that still crossed the share, and the agent has no fallback
669
+ * that needs it. A profile carrying `"share"` is refused at construction
670
+ * with the migration (`shareSubstrateRefusal`). `watch()` rides the
671
+ * in-distro poll loop for the substrate regardless (`lib/watcher.js`) —
672
+ * it never ran through either substrate. The substrate never changes
673
+ * `sandboxMode`'s honesty.
674
+ */
675
+ substrate: z<"agent" | "share", "agent" | "share", "defined">;
676
+ }>>, Schemastery.ObjectT<NoInfer<{
677
+ /** Distro name; empty resolves WSL's default on first use. */
678
+ distro: z<string, string, "defined">;
679
+ /**
680
+ * Default workdir for relative input; accepts a Linux path, a UNC path, or
681
+ * `C:\...`. Empty — the default — means the distro user's home, resolved
682
+ * inside the distro.
683
+ */
684
+ cwd: z<string, string, "defined">;
685
+ /** Path to `wsl.exe`, used only to resolve the default distro. */
686
+ wslPath: z<string, string, "defined">;
687
+ /** UTF-8 byte limit per overwrite-diff side, mirroring the host backend. */
688
+ diffBasisMaxBytes: z<number, number, "defined">;
689
+ /**
690
+ * Fence mutations by `ctx.sandboxPolicy`, and report the mode through
691
+ * {@link sandboxMode}. `false` restores the pre-sandbox behaviour exactly —
692
+ * an unfenced backend that reports `undefined` — for an operator who wants
693
+ * the old posture and accepts what it means: the model is told these
694
+ * operations are unconfined.
695
+ */
696
+ sandbox: z<boolean, boolean, "defined">;
697
+ /**
698
+ * Shadow `/mnt` with an empty tmpfs in every confined profile, hiding the
699
+ * Windows drive from confined commands — same meaning and same honest
700
+ * limits as the `wsl-shell` key of the same name (its doc carries the
701
+ * full story). Confined fs mutations publish through the masked resident;
702
+ * reads and the share substrate are unaffected.
703
+ */
704
+ maskWindowsDrive: z<boolean, boolean, "defined">;
705
+ /**
706
+ * Refuse any path that leaves the pinned distro. A WSL filesystem is a
707
+ * whole-Linux view whose `/mnt/c` also reaches the Windows disk, so without
708
+ * this the backend would silently widen the session far beyond the
709
+ * workspace the operator selected.
710
+ */
711
+ restrictToDistro: z<boolean, boolean, "defined">;
712
+ /**
713
+ * How deep the in-distro watch scan walks, `1` = the watched directory and
714
+ * its direct children. `0` — the default — walks the whole tree, which is
715
+ * what the invalidation contract asks for but a real cost every poll over
716
+ * a tree containing a `node_modules`; a bound trades that cost for a
717
+ * documented blind spot: a change below the bound is not seen until
718
+ * another change above it fires the same callback.
719
+ */
720
+ watchMaxDepth: z<number, number, "defined">;
721
+ /**
722
+ * Which I/O substrate serves the file tools. Only `"agent"` exists — the
723
+ * resident in-distro agent (`lib/fs-substrate.js`): reads, writes and
724
+ * identities run where the files live, symlinks and mode bits are native,
725
+ * and the mutation guard survives to the write's no-replace publication,
726
+ * kernel-enforced by the confined resident when the policy is confined.
727
+ * The former `"share"` opt-out — the Windows-side host filesystem stack
728
+ * over the distro's 9p share — is retired: it was the one model-facing
729
+ * I/O path that still crossed the share, and the agent has no fallback
730
+ * that needs it. A profile carrying `"share"` is refused at construction
731
+ * with the migration (`shareSubstrateRefusal`). `watch()` rides the
732
+ * in-distro poll loop for the substrate regardless (`lib/watcher.js`) —
733
+ * it never ran through either substrate. The substrate never changes
734
+ * `sandboxMode`'s honesty.
735
+ */
736
+ substrate: z<"agent" | "share", "agent" | "share", "defined">;
737
+ }>>, "plain">;
738
+ /** Cached default distro, resolved once from `wsl.exe -l -q`. */
739
+ resolvedDistro: string;
740
+ /** Cached distro-user home, the base for relative input when `cwd` is empty. */
741
+ resolvedHome: string | undefined;
742
+ /** The deployment default mode — the capability fact the tool layer reads. */
743
+ defaultMode: SandboxMode;
744
+ /** The lazy agent-backed substrate, built on first use (see `agentSubstrate`). */
745
+ agentSubstrateInstance: AgentSubstrate | undefined;
746
+ /** The distro-side bwrap usability probe, shared with the confined agents. */
747
+ sandbox: InstanceType<typeof WslSandbox>;
748
+ /** The resident agent serving publication (see `publicationAgent`). */
749
+ agentInstance: WslAgent | undefined;
750
+ /**
751
+ * @param ctx - the composition context.
752
+ * @param config - the validated plugin config.
753
+ */
754
+ constructor(ctx: Context, config: WslFileSystemConfig);
755
+ /**
756
+ * The mode this backend fences mutations with, or `undefined` when the
757
+ * operator turned the fence off. This is the capability fact `dsh-tool-fs`
758
+ * reads to advertise the escalation fields honestly, so it must disappear
759
+ * together with the enforcement — never claim a boundary that is not applied.
760
+ *
761
+ * @returns the deployment default mode, or `undefined` when unfenced.
762
+ */
763
+ get sandboxMode(): SandboxMode | undefined;
764
+ /**
765
+ * The resident agent serving this filesystem's in-distro side work, created
766
+ * on first use and kept for the provider's lifetime (its own idle timer
767
+ * retires the process; a later call starts a fresh one).
768
+ *
769
+ * @returns the agent for the pinned distro.
770
+ */
771
+ publicationAgent(): Promise<WslAgent>;
772
+ /**
773
+ * The agent-backed substrate, built on first use over the same resident
774
+ * agent every other distro side work shares.
775
+ *
776
+ * @returns the substrate.
777
+ */
778
+ agentSubstrate(): Promise<AgentSubstrate>;
779
+ /**
780
+ * The agent a MUTATION runs on under this resolved policy — stage-two
781
+ * routing. A confined policy addresses the confined resident whose bwrap
782
+ * profile binds exactly what the mode grants, so the kernel enforces what
783
+ * the check decided; an escalated write (`danger-full-access`) or a disabled
784
+ * sandbox addresses the plain resident, which is what those mean. A distro
785
+ * without a usable bwrap refuses confined mutations instead of downgrading
786
+ * them — the same closed failure the command path has.
787
+ *
788
+ * @param policy - the resolved file-effect policy, or undefined when the
789
+ * caller pre-checked.
790
+ * @returns the agent to publish through.
791
+ */
792
+ mutationAgent(policy: {
793
+ mode: string;
794
+ workspaceRoot: string;
795
+ } | undefined): Promise<WslAgent>;
796
+ /**
797
+ * Map a substrate error onto the peer's `FsError`, so the tool layer reads
798
+ * the same refusal dialect on both substrates. An agent that is out becomes
799
+ * an I/O error naming the cause — the share backend's "distro is gone" case
800
+ * is the same availability, but the message says which substrate failed.
801
+ *
802
+ * @param error - what the substrate raised.
803
+ * @returns the error to surface.
804
+ */
805
+ peerError(error: unknown): FsError;
806
+ /**
807
+ * One resolved target's Linux path — the substrate's coordinate — falling
808
+ * back to the display spelling when the identity is not a distro share path.
809
+ *
810
+ * @param target - the resolved target.
811
+ * @returns the absolute Linux path.
812
+ */
813
+ linuxOf(target: FsTarget): string;
814
+ /**
815
+ * The pinned distro, resolving WSL's default on first use.
816
+ *
817
+ * @returns the distro name.
818
+ */
819
+ distro(): Promise<string>;
820
+ /**
821
+ * Translate one caller path into the world path, enforcing the distro fence.
822
+ *
823
+ * @param path - a Linux, UNC or Windows path.
824
+ * @param cwd - the caller's override of the configured default workdir.
825
+ * @returns the host path to operate on.
826
+ */
827
+ worldPath(path: string, cwd?: string): Promise<string>;
828
+ /**
829
+ * Resolve a path to a target whose `targetKey` is the UNC identity the host
830
+ * I/O stack uses and whose `displayPath` is the Linux path the model sees.
831
+ *
832
+ * @param path - a Linux, UNC or Windows path.
833
+ * @param opts - optional `cwd` override and `signal`.
834
+ * @returns the resolved `FsTarget`.
835
+ */
836
+ resolve(path: string, opts?: {
837
+ cwd?: string;
838
+ signal?: AbortSignal;
839
+ }): Promise<FsTarget>;
840
+ /**
841
+ * Enforce the per-call file-effect policy against one target and return the
842
+ * EXACT target the mutation must use, so the checked identity is the mutated
843
+ * one (no check-here-write-there window).
844
+ *
845
+ * `read-only` denies outright. `workspace-write` re-resolves the target NOW
846
+ * (reflecting a symlink ancestor swapped since the tool resolved it), requires
847
+ * containment under a writable root — the workspace plus the temp area of the
848
+ * world the workspace lives in — and hands back that fresh target.
849
+ * `danger-full-access` is the approved escalation and is not fenced. Refusal
850
+ * is the structured `FS_SANDBOX_DENIED` the tool layer maps to its
851
+ * model-facing `[sandbox: …]` marker and escalation hint.
852
+ *
853
+ * @param target - the resolved target to mutate.
854
+ * @param sandboxPolicy - the per-call mode and workspace root; omit for the
855
+ * deployment policy.
856
+ * @returns the target the mutation must use.
857
+ * @throws {FsError} with `FS_SANDBOX_DENIED` when the policy refuses it.
858
+ */
859
+ checkedTarget(target: FsTarget, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsTarget>;
860
+ /**
861
+ * One target's metadata, answered from the distro.
862
+ *
863
+ * @param target - the resolved target.
864
+ * @param signal - optional cancellation.
865
+ * @returns the metadata, or undefined when absent.
866
+ */
867
+ stat(target: FsTarget, signal?: AbortSignal): Promise<FsInfo | undefined>;
868
+ /**
869
+ * No-follow stat one path. The agent substrate answers from the distro,
870
+ * where a symlink is a first-class entry.
871
+ *
872
+ * @param path - a Linux, UNC or Windows path.
873
+ * @param opts - optional `cwd` override.
874
+ * @param signal - optional cancellation.
875
+ * @returns the path info, or undefined when absent.
876
+ */
877
+ lstat(path: string, opts?: {
878
+ cwd?: string;
879
+ }, signal?: AbortSignal): Promise<FsPathInfo | undefined>;
880
+ /**
881
+ * Read one whole regular UTF-8 file from the distro.
882
+ *
883
+ * @param target - the resolved target.
884
+ * @param signal - optional cancellation.
885
+ * @returns the file text.
886
+ */
887
+ readText(target: FsTarget, signal?: AbortSignal): Promise<string>;
888
+ /**
889
+ * Stream one whole regular UTF-8 file from the distro.
890
+ *
891
+ * @param target - the resolved target.
892
+ * @param signal - optional cancellation.
893
+ * @returns the text chunks.
894
+ */
895
+ streamText(target: FsTarget, signal?: AbortSignal): Promise<AsyncIterable<string>>;
896
+ /**
897
+ * Read one whole regular file as raw bytes, bounded.
898
+ *
899
+ * @param target - the resolved target.
900
+ * @param signal - optional cancellation.
901
+ * @param maxBytes - inclusive byte cap on the content.
902
+ * @returns the bytes.
903
+ */
904
+ readBytes(target: FsTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
905
+ /**
906
+ * Read one byte window of a regular file.
907
+ *
908
+ * @param target - the resolved target.
909
+ * @param range - the window.
910
+ * @param signal - optional cancellation.
911
+ * @returns the window's bytes.
912
+ */
913
+ readByteRange(target: FsTarget, range: {
914
+ offset: number;
915
+ length: number;
916
+ }, signal?: AbortSignal): Promise<Uint8Array>;
917
+ /**
918
+ * List one directory level. The substrate's rows already carry Linux display
919
+ * paths and canonical identities — nothing to rewrite.
920
+ *
921
+ * @param target - the resolved directory.
922
+ * @param signal - optional cancellation.
923
+ * @returns the directory rows.
924
+ */
925
+ listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>;
926
+ /**
927
+ * Map an absolute host path into this execution world, SYNCHRONOUSLY — the
928
+ * upstream contract (`FileSystem.processPathFromHostPath`) and what its
929
+ * consumers rely on: `packages/spill/spill-policy` passes this method as a
930
+ * plain callback over image attachments, and `packages/api/session-controller`
931
+ * plus the UI's open/reveal path compare the return value against `undefined`
932
+ * without awaiting. The mapping is therefore built from facts that are already
933
+ * known — the distro, once `distro()` has resolved it (the pinned config, or
934
+ * the default `wsl.exe` answered earlier: the first filesystem operation
935
+ * resolves it), and the configured `cwd`, or the resolved home for a relative
936
+ * path. A call that would have to ask `wsl.exe` — a cold default distro, or a
937
+ * relative path before the home is known — answers `undefined`, which every
938
+ * consumer reads as "no mapping", the same answer the ssh provider gives for
939
+ * a path it cannot map.
940
+ *
941
+ * The VALUE is the world spelling the backend keys on (the UNC identity for a
942
+ * distro path), so the consumers' round trip — map, `resolve`, `processPath`,
943
+ * compare with the original host path — closes exactly as it does for the
944
+ * host backend. Real operations are unaffected: they take the async
945
+ * {@link WslFileSystem.worldPath} and its loud refusals.
946
+ *
947
+ * @param hostPath - an absolute host path (a relative one maps only when the
948
+ * configured `cwd` or a known home gives it a base).
949
+ * @returns the world path for the same file, or undefined when it cannot be
950
+ * mapped with what is already known.
951
+ */
952
+ processPathFromHostPath(hostPath: string): string | undefined;
953
+ /**
954
+ * Watch from inside the distro, where the kernel can actually report change:
955
+ * a long-lived `wsl.exe` poll loop (`lib/watcher.js`) fires the seam's
956
+ * invalidation callback. The seam asks for coarse invalidation, not events,
957
+ * so a poll is honest — and creations and deletions are both seen, because
958
+ * they bump the parent directory's mtime. Replaces the previous flat
959
+ * refusal (`FS_IO_ERROR`), which was the correct answer over 9p only.
960
+ *
961
+ * A target that disappears mid-watch ends the loop with a report through
962
+ * `changed` — never the armed-but-never-fires silence a missing directory
963
+ * would otherwise produce. The scan's depth is `watchMaxDepth`.
964
+ *
965
+ * @param target - the resolved file or directory to observe.
966
+ * @param changed - the invalidation callback.
967
+ * @param signal - cancels initialization; the caller closes an armed watcher.
968
+ * @returns resolves once active, with the async close function.
969
+ */
970
+ watch(target: FsTarget, changed: (error?: Error) => void, signal: AbortSignal): Promise<() => Promise<void>>;
971
+ /**
972
+ * Fence the write by the per-call policy, then publish through the
973
+ * substrate. The fence's fresh target is the one checked and written, so a
974
+ * refusal costs no I/O and cannot be raced by a swapped symlink; the guard
975
+ * rides the write op, where the agent re-verifies it one syscall before the
976
+ * rename and the confined resident enforces it in the kernel.
977
+ *
978
+ * @param target - the resolved target.
979
+ * @param content - the full new file content.
980
+ * @param expected - the write intent.
981
+ * @param signal - optional cancellation.
982
+ * @param sandboxPolicy - the per-call mode and workspace root; omit for the
983
+ * deployment policy.
984
+ * @returns the write outcome.
985
+ */
986
+ writeText(target: FsTarget, content: string, expected?: FsWriteIntent, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsWriteOutcome>;
987
+ /**
988
+ * Fence the edit by the same policy, keeping the guard's observable
989
+ * behaviour.
990
+ *
991
+ * @param target - the resolved target.
992
+ * @param edit - the literal search/replace request.
993
+ * @param expected - the version guard.
994
+ * @param signal - optional cancellation.
995
+ * @param sandboxPolicy - the per-call mode and workspace root; omit for the
996
+ * deployment policy.
997
+ * @returns the edit outcome.
998
+ */
999
+ editText(target: FsTarget, edit: FsEditRequest, expected?: {
1000
+ version: FsVersion;
1001
+ }, signal?: AbortSignal, sandboxPolicy?: SandboxExecutionPolicy): Promise<FsEditOutcome>;
1002
+ }
1003
+ export {};