dsh-plugin-wsl-env 0.7.5 → 0.8.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 (79) hide show
  1. package/CHANGELOG.md +278 -0
  2. package/CONTRIBUTING.md +129 -39
  3. package/README.md +20 -20
  4. package/docs/ARCHITECTURE.md +6 -3
  5. package/docs/LIMITATIONS.md +13 -0
  6. package/docs/README.zh.md +20 -20
  7. package/lib/agent-confined.d.ts +42 -0
  8. package/lib/agent-confined.js +25 -41
  9. package/lib/agent-errors.d.ts +31 -0
  10. package/lib/agent-errors.js +19 -16
  11. package/lib/agent-exec.d.ts +159 -0
  12. package/lib/agent-exec.js +151 -165
  13. package/lib/agent-protocol.d.ts +192 -0
  14. package/lib/agent-protocol.js +76 -100
  15. package/lib/agent-shared.d.ts +27 -0
  16. package/lib/agent-shared.js +12 -12
  17. package/lib/agent.d.ts +353 -0
  18. package/lib/agent.js +536 -552
  19. package/lib/auto-preset.d.ts +110 -0
  20. package/lib/auto-preset.js +157 -141
  21. package/lib/bwrap.d.ts +90 -0
  22. package/lib/bwrap.js +18 -18
  23. package/lib/file-reference-wsl.d.ts +160 -0
  24. package/lib/file-reference-wsl.js +192 -173
  25. package/lib/fs-decisions.d.ts +107 -0
  26. package/lib/fs-decisions.js +64 -60
  27. package/lib/fs-routing.d.ts +182 -0
  28. package/lib/fs-routing.js +173 -116
  29. package/lib/fs-substrate.d.ts +258 -0
  30. package/lib/fs-substrate.js +248 -248
  31. package/lib/fs.d.ts +10 -0
  32. package/lib/fs.js +5 -0
  33. package/lib/fsio-agent.d.ts +307 -0
  34. package/lib/fsio-agent.js +414 -410
  35. package/lib/fsio-text.d.ts +115 -0
  36. package/lib/fsio-text.js +104 -84
  37. package/lib/git-route.d.ts +100 -0
  38. package/lib/git-route.js +46 -47
  39. package/lib/index.d.ts +1003 -0
  40. package/lib/index.js +1101 -1003
  41. package/lib/listing.d.ts +114 -0
  42. package/lib/listing.js +68 -55
  43. package/lib/paths.d.ts +182 -0
  44. package/lib/paths.js +134 -109
  45. package/lib/picker.d.ts +266 -0
  46. package/lib/picker.js +371 -351
  47. package/lib/ports.d.ts +44 -0
  48. package/lib/ports.js +39 -29
  49. package/lib/preset-choice.d.ts +33 -0
  50. package/lib/preset-choice.js +15 -7
  51. package/lib/probe-cache.d.ts +40 -0
  52. package/lib/probe-cache.js +35 -29
  53. package/lib/sandbox-core.d.ts +79 -0
  54. package/lib/sandbox-core.js +71 -88
  55. package/lib/sandbox.d.ts +48 -0
  56. package/lib/sandbox.js +4 -4
  57. package/lib/search-exec.d.ts +96 -0
  58. package/lib/search-exec.js +52 -79
  59. package/lib/search-route.d.ts +75 -0
  60. package/lib/search-route.js +30 -31
  61. package/lib/shell-env.d.ts +88 -0
  62. package/lib/shell-env.js +78 -74
  63. package/lib/shell.d.ts +10 -0
  64. package/lib/shell.js +5 -0
  65. package/lib/subprocess.d.ts +313 -0
  66. package/lib/subprocess.js +267 -262
  67. package/lib/terminal-activity.d.ts +98 -0
  68. package/lib/terminal-activity.js +29 -39
  69. package/lib/terminal-route.d.ts +27 -0
  70. package/lib/terminal-route.js +16 -8
  71. package/lib/watcher.d.ts +77 -0
  72. package/lib/watcher.js +120 -126
  73. package/lib/workspace-files-route.d.ts +132 -0
  74. package/lib/workspace-files-route.js +73 -81
  75. package/lib/workspace-files-wsl.d.ts +257 -0
  76. package/lib/workspace-files-wsl.js +311 -251
  77. package/lib/wsl.d.ts +266 -0
  78. package/lib/wsl.js +144 -155
  79. package/package.json +68 -20
@@ -26,45 +26,95 @@
26
26
  * they are unit-testable with a fake agent; the class methods are thin
27
27
  * coordinate guards around them.
28
28
  *
29
+ * This is a TypeScript source built to `lib/workspace-files-wsl.js`; edit THIS
30
+ * file and run `pnpm run build` — the artifact under `lib/` is generated, and
31
+ * `pnpm test` fails when it drifts.
32
+ *
29
33
  * @module dsh-plugin-wsl/workspace-files-wsl
30
34
  */
31
-
32
35
  import { RemoteError } from "@deepseek-ai/dsh-typert-protocol";
33
36
  import { WorkspaceFiles } from "@deepseek-ai/dsh-api-workspace-files";
34
37
  import { isDistroWorkspace, linuxJoin, listDistroArgv, parseDirListing, parseStatRecord, parseStatRecordFromStderr, pageArgv, statRecordArgv, bytesArgv } from "./workspace-files-route.js";
35
38
  import { posixToUnc, uncToPosix } from "./paths.js";
36
39
  import { sharedAgent } from "./agent-shared.js";
37
-
38
- /** Throw the wire's not-found refusal. */
40
+ /**
41
+ * Read this row's validated config through the one documented cast.
42
+ *
43
+ * @param self - the service instance.
44
+ * @returns its own config.
45
+ */
46
+ function ownConfig(self) {
47
+ return self.config;
48
+ }
49
+ /**
50
+ * Throw the wire's not-found refusal.
51
+ *
52
+ * @param path - the requested path.
53
+ * @returns the refusal.
54
+ */
39
55
  function notFound(path) {
40
- return new RemoteError("workspace-file/not-found", `no entry at "${path}"`, { path });
56
+ return new RemoteError("workspace-file/not-found", `no entry at "${path}"`, { path });
41
57
  }
42
-
43
- /** Throw the wire's outside-workspace refusal. */
58
+ /**
59
+ * Throw the wire's outside-workspace refusal.
60
+ *
61
+ * @param path - the requested path.
62
+ * @returns the refusal.
63
+ */
44
64
  function outsideWorkspace(path) {
45
- return new RemoteError("workspace-file/outside-workspace", `"${path}" is outside the workspace`, { path });
65
+ return new RemoteError("workspace-file/outside-workspace", `"${path}" is outside the workspace`, { path });
46
66
  }
47
-
48
- /** Throw the wire's not-directory refusal. */
67
+ /**
68
+ * Throw the wire's not-directory refusal.
69
+ *
70
+ * @param path - the requested path.
71
+ * @param kind - what the entry is.
72
+ * @returns the refusal.
73
+ */
49
74
  function notDirectory(path, kind) {
50
- return new RemoteError("workspace-file/not-directory", `"${path}" is a ${kind}`, { path, kind });
75
+ return new RemoteError("workspace-file/not-directory", `"${path}" is a ${kind}`, { path, kind });
51
76
  }
52
-
53
- /** Throw the wire's not-regular-file refusal. */
77
+ /**
78
+ * Throw the wire's not-regular-file refusal.
79
+ *
80
+ * @param path - the requested path.
81
+ * @param kind - what the entry is.
82
+ * @returns the refusal.
83
+ */
54
84
  function notRegularFile(path, kind) {
55
- return new RemoteError("workspace-file/not-regular-file", `"${path}" is a ${kind}`, { path, kind });
85
+ return new RemoteError("workspace-file/not-regular-file", `"${path}" is a ${kind}`, { path, kind });
56
86
  }
57
-
58
- /** Throw the wire's too-large refusal. */
87
+ /**
88
+ * Throw the wire's too-large refusal.
89
+ *
90
+ * @param path - the requested path.
91
+ * @param limit - the full-file cap.
92
+ * @returns the refusal.
93
+ */
59
94
  function tooLarge(path, limit) {
60
- return new RemoteError("workspace-file/too-large", `"${path}" exceeds the ${limit} byte full-file cap`, { path, limit });
95
+ return new RemoteError("workspace-file/too-large", `"${path}" exceeds the ${limit} byte full-file cap`, { path, limit });
96
+ }
97
+ /**
98
+ * Throw the wire's too-large refusal for a byte window that asked past the cap
99
+ * — upstream's byte-read wording, distinct from the full-file cap's.
100
+ *
101
+ * @param path - the requested path.
102
+ * @param length - the requested window length.
103
+ * @param limit - the window cap.
104
+ * @returns the refusal.
105
+ */
106
+ function windowTooLarge(path, length, limit) {
107
+ return new RemoteError("workspace-file/too-large", `${length} bytes of "${path}" exceed the ${limit} byte cap`, { path, limit });
61
108
  }
62
-
63
- /** Throw the wire's not-text refusal. */
109
+ /**
110
+ * Throw the wire's not-text refusal.
111
+ *
112
+ * @param path - the requested path.
113
+ * @returns the refusal.
114
+ */
64
115
  function notText(path) {
65
- return new RemoteError("workspace-file/not-text", `"${path}" is not UTF-8 text`, { path });
116
+ return new RemoteError("workspace-file/not-text", `"${path}" is not UTF-8 text`, { path });
66
117
  }
67
-
68
118
  /**
69
119
  * List one distro directory through the runner.
70
120
  *
@@ -74,61 +124,55 @@ function notText(path) {
74
124
  * upstream child semantics).
75
125
  *
76
126
  * @param object - the listing's inputs.
77
- * @param {object} object.runner - the agent-like runner (`exec`).
78
- * @param {string} object.distro - the distro name.
79
- * @param {string} object.linuxRoot - the workspace root in Linux form.
80
- * @param {string} object.path - the requested directory, any coordinate system.
81
- * @param {number} object.maxEntries - the entry cap.
82
- * @param {AbortSignal} [object.signal] - caller cancellation.
83
- * @returns {Promise<{path: string, entries: {name: string, type: string}[], truncated: boolean}>}
84
- * the listing, with `path` relative to the workspace root (empty for it).
127
+ * @returns the listing, with `path` relative to the workspace root (empty for it).
85
128
  */
86
129
  export async function distroList({ runner, distro, linuxRoot, path, maxEntries, signal }) {
87
- const linuxDir = linuxJoin(linuxRoot, path);
88
- const result = await runner.exec({
89
- cwd: "/",
90
- argv: listDistroArgv(linuxRoot, linuxDir, maxEntries),
91
- maxOutputBytes: 4_000_000,
92
- timeoutMs: 30_000,
93
- signal,
94
- });
95
- if (result.exitCode === 3) throw outsideWorkspace(path);
96
- if (result.exitCode === 4) throw notDirectory(path, "directory");
97
- if (result.exitCode === 2) throw notFound(path);
98
- if (result.exitCode !== 0) throw new Error(`listing "${linuxDir}" failed: ${String(result.stderr).trim() || `exit ${result.exitCode}`}`);
99
- const { entries, truncated } = parseDirListing(result.stdout, maxEntries);
100
- const relative = linuxDir === linuxRoot ? "" : linuxDir.slice(linuxRoot.length + 1);
101
- return { path: relative, entries, truncated };
130
+ const linuxDir = linuxJoin(linuxRoot, path);
131
+ const result = await runner.exec({
132
+ cwd: "/",
133
+ argv: listDistroArgv(linuxRoot, linuxDir, maxEntries),
134
+ maxOutputBytes: 4_000_000,
135
+ timeoutMs: 30_000,
136
+ signal,
137
+ });
138
+ if (result.exitCode === 3)
139
+ throw outsideWorkspace(path);
140
+ // The exit-4 relay carries no kind, and the refusal's message reads back
141
+ // `"x" is a directory` inside a not-directory refusal — the passed kind is
142
+ // wrong. Fixing it honestly needs the relay to report what the entry IS; a
143
+ // protocol decision, deliberately left visible.
144
+ // @ts-expect-error — the wire's kind is unknown on exit 4; see above.
145
+ if (result.exitCode === 4)
146
+ throw notDirectory(path, "directory");
147
+ if (result.exitCode === 2)
148
+ throw notFound(path);
149
+ if (result.exitCode !== 0)
150
+ throw new Error(`listing "${linuxDir}" failed: ${String(result.stderr).trim() || `exit ${result.exitCode}`}`);
151
+ const { entries, truncated } = parseDirListing(result.stdout, maxEntries);
152
+ const relative = linuxDir === linuxRoot ? "" : linuxDir.slice(linuxRoot.length + 1);
153
+ return { path: relative, entries, truncated };
102
154
  }
103
-
104
155
  /**
105
156
  * Stat one distro path through the runner.
106
157
  *
107
158
  * @param object - the stat's inputs.
108
- * @param {object} object.runner - the agent-like runner (`exec`).
109
- * @param {string} object.distro - the distro name.
110
- * @param {string} object.linuxRoot - the workspace root in Linux form.
111
- * @param {string} object.path - the requested path, any coordinate system.
112
- * @param {string} object.distroWorkspaceRoot - the UNC workspace root, for the
113
- * absolutePath display form.
114
- * @param {AbortSignal} [object.signal] - caller cancellation.
115
- * @returns {Promise<{absolutePath: string, version: string, bytes?: number}>}
159
+ * @returns the wire stat row.
116
160
  */
117
161
  export async function distroStat({ runner, distro, linuxRoot, path, distroWorkspaceRoot, signal }) {
118
- const linuxFile = linuxJoin(linuxRoot, path);
119
- const result = await runner.exec({
120
- cwd: "/",
121
- argv: statRecordArgv(linuxFile),
122
- maxOutputBytes: 4096,
123
- timeoutMs: 30_000,
124
- signal,
125
- });
126
- if (result.exitCode !== 0) throw notFound(path);
127
- const record = parseStatRecord(result.stdout);
128
- const stat = { absolutePath: posixToUnc(distro, linuxFile), version: record.version };
129
- return record.size === undefined || Number.isNaN(record.size) ? stat : { ...stat, bytes: record.size };
162
+ const linuxFile = linuxJoin(linuxRoot, path);
163
+ const result = await runner.exec({
164
+ cwd: "/",
165
+ argv: statRecordArgv(linuxFile),
166
+ maxOutputBytes: 4096,
167
+ timeoutMs: 30_000,
168
+ signal,
169
+ });
170
+ if (result.exitCode !== 0)
171
+ throw notFound(path);
172
+ const record = parseStatRecord(result.stdout);
173
+ const stat = { absolutePath: posixToUnc(distro, linuxFile), version: record.version };
174
+ return record.size === undefined || Number.isNaN(record.size) ? stat : { ...stat, bytes: record.size };
130
175
  }
131
-
132
176
  /**
133
177
  * Read one line-paged UTF-8 window through the runner, mirroring the
134
178
  * upstream `cutPage` semantics: a NUL byte marks the page binary, a page
@@ -136,206 +180,222 @@ export async function distroStat({ runner, distro, linuxRoot, path, distroWorksp
136
180
  * when a character exists past the page.
137
181
  *
138
182
  * @param object - the read's inputs.
139
- * @param {object} object.runner - the agent-like runner (`exec`).
140
- * @param {string} object.distro - the distro name.
141
- * @param {string} object.linuxRoot - the workspace root in Linux form.
142
- * @param {string} object.path - the requested path, any coordinate system.
143
- * @param {number} object.offset - first line to return (1-based).
144
- * @param {number} object.limit - maximum lines to return.
145
- * @param {number} object.maxBytes - the page's byte cap.
146
- * @param {string} object.distroWorkspaceRoot - the UNC workspace root, for the
147
- * absolutePath display form.
148
- * @param {AbortSignal} [object.signal] - caller cancellation.
149
- * @returns {Promise<{absolutePath: string, version: string, bytes?: number, offset: number, text: string, lines: number, eof: boolean}>}
183
+ * @returns the wire text page.
150
184
  */
151
185
  export async function distroRead({ runner, distro, linuxRoot, path, offset, limit, maxBytes, distroWorkspaceRoot, signal }) {
152
- const linuxFile = linuxJoin(linuxRoot, path);
153
- const result = await runner.exec({
154
- cwd: "/",
155
- argv: pageArgv(linuxFile, offset, limit),
156
- maxOutputBytes: (maxBytes + 1) * 4,
157
- timeoutMs: 60_000,
158
- signal,
159
- });
160
- if (result.exitCode === 2) throw notFound(path);
161
- if (result.exitCode !== 0 && result.exitCode !== 1) {
162
- throw new Error(`reading "${linuxFile}" failed: ${String(result.stderr).trim() || `exit ${result.exitCode}`}`);
163
- }
164
- const record = parseStatRecordFromStderr(result.stderr);
165
- if (record === undefined) throw notFound(path);
166
- if (record.type !== "file") throw notRegularFile(path, record.type);
167
- const raw = Buffer.from(result.stdout);
168
- const strict = new TextDecoder("utf-8", { fatal: true });
169
- let text;
170
- try {
171
- text = strict.decode(raw);
172
- } catch {
173
- throw notText(path);
174
- }
175
- const lines = text.length === 0 ? 0 : text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
176
- const bytes = Buffer.byteLength(text, "utf8") + lines;
177
- if (bytes > maxBytes) {
178
- throw new RemoteError(
179
- "workspace-file/too-large",
180
- `lines ${offset}-${offset + limit - 1} of "${path}" exceed the ${maxBytes} byte cap`,
181
- { path, limit: maxBytes },
182
- );
183
- }
184
- if (text.includes(String.fromCharCode(0))) throw notText(path);
185
- const eof = lines < limit;
186
- const stat = { absolutePath: posixToUnc(distro, linuxFile), version: record.version };
187
- return { ...stat, offset, text, lines, eof };
186
+ const linuxFile = linuxJoin(linuxRoot, path);
187
+ const result = await runner.exec({
188
+ cwd: "/",
189
+ argv: pageArgv(linuxFile, offset, limit),
190
+ maxOutputBytes: (maxBytes + 1) * 4,
191
+ timeoutMs: 60_000,
192
+ signal,
193
+ });
194
+ if (result.exitCode === 2)
195
+ throw notFound(path);
196
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
197
+ throw new Error(`reading "${linuxFile}" failed: ${String(result.stderr).trim() || `exit ${result.exitCode}`}`);
198
+ }
199
+ const record = parseStatRecordFromStderr(result.stderr);
200
+ if (record === undefined)
201
+ throw notFound(path);
202
+ // The stat record's kind is whatever the distro named — a socket or a fifo
203
+ // reaches this line too — and the refusal embeds it verbatim; the narrowed
204
+ // parameter is the upstream wire's three-kind vocabulary.
205
+ if (record.type !== "file")
206
+ throw notRegularFile(path, record.type);
207
+ const raw = Buffer.from(result.stdout);
208
+ const strict = new TextDecoder("utf-8", { fatal: true });
209
+ let text;
210
+ try {
211
+ text = strict.decode(raw);
212
+ }
213
+ catch {
214
+ throw notText(path);
215
+ }
216
+ const lines = text.length === 0 ? 0 : text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
217
+ const bytes = Buffer.byteLength(text, "utf8") + lines;
218
+ if (bytes > maxBytes) {
219
+ throw new RemoteError("workspace-file/too-large", `lines ${offset}-${offset + limit - 1} of "${path}" exceed the ${maxBytes} byte cap`, { path, limit: maxBytes });
220
+ }
221
+ if (text.includes(String.fromCharCode(0)))
222
+ throw notText(path);
223
+ const eof = lines < limit;
224
+ const stat = { absolutePath: posixToUnc(distro, linuxFile), version: record.version };
225
+ return { ...stat, offset, text, lines, eof };
188
226
  }
189
-
190
227
  /**
191
228
  * Read one byte window (or the capped whole file) through the runner.
192
229
  *
193
230
  * @param object - the read's inputs.
194
- * @param {object} object.runner - the agent-like runner (`exec`).
195
- * @param {string} object.distro - the distro name.
196
- * @param {string} object.linuxRoot - the workspace root in Linux form.
197
- * @param {string} object.path - the requested path, any coordinate system.
198
- * @param {number|undefined} object.offset - byte offset (0-based).
199
- * @param {number|undefined} object.length - byte count.
200
- * @param {number} object.maxBytes - the window cap.
201
- * @param {number} object.maxFileBytes - the complete-file cap.
202
- * @param {string} object.distroWorkspaceRoot - the UNC workspace root, for the
203
- * absolutePath display form.
204
- * @param {AbortSignal} [object.signal] - caller cancellation.
205
- * @returns {Promise<{absolutePath: string, version: string, bytes?: number, offset: number, data: Uint8Array, eof: boolean}>}
231
+ * @returns the wire byte read.
206
232
  */
207
- export async function distroReadBytes({ runner, distro, linuxRoot, path, offset, length, maxFileBytes, distroWorkspaceRoot, signal }) {
208
- const linuxFile = linuxJoin(linuxRoot, path);
209
- // One exec carries both halves: the stat record on stderr (for the version,
210
- // the size, and the whole-file refusal), the window on stdout.
211
- const window = offset === undefined && length === undefined
212
- ? { offset: 0, length: maxFileBytes }
213
- : { offset: offset ?? 0, length: length ?? maxFileBytes };
214
- const result = await runner.exec({
215
- cwd: "/",
216
- argv: bytesArgv(linuxFile, window.offset, window.length),
217
- maxOutputBytes: window.length + 1024,
218
- timeoutMs: 60_000,
219
- signal,
220
- });
221
- const record = parseStatRecordFromStderr(result.stderr);
222
- if (record === undefined) throw notFound(path);
223
- if (record.type !== "file") throw notRegularFile(path, record.type);
224
- const data = new Uint8Array(Buffer.from(result.stdout).subarray(0, window.length));
225
- // The whole-file form mirrors the upstream refusal: a file bigger than the
226
- // cap is refused, never silently truncated (the stderr record names the
227
- // real size even though head capped the stdout).
228
- if (offset === undefined && length === undefined && record.size > maxFileBytes) {
229
- throw tooLarge(path, maxFileBytes);
230
- }
231
- return {
232
- absolutePath: posixToUnc(distro, linuxFile),
233
- version: record.version,
234
- offset: window.offset,
235
- data,
236
- eof: window.offset + data.length >= record.size,
237
- };
233
+ export async function distroReadBytes({ runner, distro, linuxRoot, path, offset, length, maxBytes, maxFileBytes, distroWorkspaceRoot, signal }) {
234
+ const linuxFile = linuxJoin(linuxRoot, path);
235
+ // One exec carries both halves: the stat record on stderr (for the version,
236
+ // the size, and the whole-file refusal), the window on stdout.
237
+ const whole = offset === undefined && length === undefined;
238
+ // Upstream's `resolveWindow` defaulting: an absent length means the WINDOW
239
+ // cap (not the whole-file cap), so a ranged read never silently grows past it.
240
+ const window = whole
241
+ ? { offset: 0, length: maxFileBytes }
242
+ : { offset: offset ?? 0, length: length ?? maxBytes ?? maxFileBytes };
243
+ // The window form mirrors the upstream refusal the same way the whole-file
244
+ // form does: a window asking for more than the page cap is refused, never
245
+ // shortened. `readByteRange` passes the request as its own cap, so only the
246
+ // ranged `readBytes` form can trip this.
247
+ if (!whole && maxBytes !== undefined && window.length > maxBytes)
248
+ throw windowTooLarge(path, window.length, maxBytes);
249
+ const result = await runner.exec({
250
+ cwd: "/",
251
+ argv: bytesArgv(linuxFile, window.offset, window.length),
252
+ maxOutputBytes: window.length + 1024,
253
+ timeoutMs: 60_000,
254
+ signal,
255
+ });
256
+ const record = parseStatRecordFromStderr(result.stderr);
257
+ if (record === undefined)
258
+ throw notFound(path);
259
+ // Same wire reality as the text read above: the kind is the distro's own
260
+ // naming, embedded verbatim.
261
+ if (record.type !== "file")
262
+ throw notRegularFile(path, record.type);
263
+ const data = new Uint8Array(Buffer.from(result.stdout).subarray(0, window.length));
264
+ // The whole-file form mirrors the upstream refusal: a file bigger than the
265
+ // cap is refused, never silently truncated (the stderr record names the
266
+ // real size even though head capped the stdout).
267
+ if (offset === undefined && length === undefined && record.size > maxFileBytes) {
268
+ throw tooLarge(path, maxFileBytes);
269
+ }
270
+ return {
271
+ absolutePath: posixToUnc(distro, linuxFile),
272
+ version: record.version,
273
+ offset: window.offset,
274
+ data,
275
+ eof: window.offset + data.length >= record.size,
276
+ };
238
277
  }
239
-
240
278
  /**
241
279
  * The distro-routed `workspace-files` service.
242
280
  */
243
281
  export class WorkspaceFilesWsl extends WorkspaceFiles {
244
- static Config = WorkspaceFiles.Config;
245
-
246
- /** The agent runner for one distro; a hook tests substitute. */
247
- runnerFor(distro) {
248
- return sharedAgent(distro);
249
- }
250
-
251
- /** The distro coordinates of one workspace scope. */
252
- coords(workspaceRoot) {
253
- const parsed = uncToPosix(workspaceRoot);
254
- return { distro: parsed.distro, linuxRoot: parsed.linuxPath.length > 0 ? parsed.linuxPath : "/" };
255
- }
256
-
257
- async list(workspaceFileScope, path, signal) {
258
- if (!isDistroWorkspace(workspaceFileScope.workspaceRoot)) return super.list(workspaceFileScope, path, signal);
259
- const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
260
- const { path: relative, entries, truncated } = await distroList({
261
- runner: this.runnerFor(distro),
262
- distro,
263
- linuxRoot,
264
- path,
265
- maxEntries: this.config.maxEntries,
266
- signal,
267
- });
268
- return {
269
- path: relative,
270
- entries: entries.map((entry) => entry.size === undefined ? entry : { ...entry, size: entry.size }),
271
- truncated,
272
- };
273
- }
274
-
275
- async stat(workspaceFileScope, path, signal) {
276
- if (!isDistroWorkspace(workspaceFileScope.workspaceRoot)) return super.stat(workspaceFileScope, path, signal);
277
- const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
278
- return distroStat({ runner: this.runnerFor(distro), distro, linuxRoot, path, distroWorkspaceRoot: workspaceFileScope.workspaceRoot, signal });
279
- }
280
-
281
- async read(workspaceFileScope, path, range, signal) {
282
- if (!isDistroWorkspace(workspaceFileScope.workspaceRoot)) return super.read(workspaceFileScope, path, range, signal);
283
- const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
284
- const offset = range.offset === undefined ? 1 : range.offset;
285
- const limit = range.limit === undefined ? this.config.maxLines : range.limit;
286
- if (!Number.isSafeInteger(offset) || offset < 1) {
287
- throw new RemoteError("gateway/bad-request", "offset must be a safe integer of at least 1", {});
282
+ static Config = WorkspaceFiles.Config;
283
+ /**
284
+ * The agent runner for one distro; a hook tests substitute.
285
+ *
286
+ * @param distro - the distro name.
287
+ * @returns the agent runner.
288
+ */
289
+ runnerFor(distro) {
290
+ return sharedAgent(distro);
288
291
  }
289
- if (limit > this.config.maxLines) {
290
- throw new RemoteError("gateway/bad-request", `limit must be at most ${this.config.maxLines}`, {});
292
+ /**
293
+ * The distro coordinates of one workspace scope.
294
+ *
295
+ * @param workspaceRoot - the scope's workspace root.
296
+ * @returns the distro and its workspace root in Linux form.
297
+ */
298
+ coords(workspaceRoot) {
299
+ const parsed = uncToPosix(workspaceRoot);
300
+ if (parsed === undefined)
301
+ throw new Error(`"${workspaceRoot}" is not a distro workspace root`);
302
+ return { distro: parsed.distro, linuxRoot: parsed.linuxPath.length > 0 ? parsed.linuxPath : "/" };
303
+ }
304
+ /**
305
+ * List one workspace directory; distro UNC roots list inside the distro.
306
+ *
307
+ * @param workspaceFileScope - the Session's file scope.
308
+ * @param path - the directory to list.
309
+ * @param signal - caller cancellation.
310
+ */
311
+ async list(workspaceFileScope, path, signal) {
312
+ if (!isDistroWorkspace(workspaceFileScope.workspaceRoot))
313
+ return super.list(workspaceFileScope, path, signal);
314
+ const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
315
+ const { path: relative, entries, truncated } = await distroList({
316
+ runner: this.runnerFor(distro),
317
+ distro,
318
+ linuxRoot,
319
+ path,
320
+ maxEntries: ownConfig(this).maxEntries,
321
+ signal,
322
+ });
323
+ return {
324
+ path: relative,
325
+ entries: entries.map((entry) => entry.size === undefined ? entry : { ...entry, size: entry.size }),
326
+ truncated,
327
+ };
328
+ }
329
+ /**
330
+ * Stat one workspace file; distro UNC roots stat inside the distro.
331
+ *
332
+ * @param workspaceFileScope - the Session's file scope.
333
+ * @param path - the file to stat.
334
+ * @param signal - caller cancellation.
335
+ */
336
+ async stat(workspaceFileScope, path, signal) {
337
+ if (!isDistroWorkspace(workspaceFileScope.workspaceRoot))
338
+ return super.stat(workspaceFileScope, path, signal);
339
+ const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
340
+ return distroStat({ runner: this.runnerFor(distro), distro, linuxRoot, path, distroWorkspaceRoot: workspaceFileScope.workspaceRoot, signal });
341
+ }
342
+ /**
343
+ * Read one line page; distro UNC roots read inside the distro.
344
+ *
345
+ * @param workspaceFileScope - the Session's file scope.
346
+ * @param path - the file to read.
347
+ * @param range - the line window.
348
+ * @param signal - caller cancellation.
349
+ */
350
+ async read(workspaceFileScope, path, range, signal) {
351
+ if (!isDistroWorkspace(workspaceFileScope.workspaceRoot))
352
+ return super.read(workspaceFileScope, path, range, signal);
353
+ const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
354
+ const offset = range.offset === undefined ? 1 : range.offset;
355
+ const limit = range.limit === undefined ? ownConfig(this).maxLines : range.limit;
356
+ if (!Number.isSafeInteger(offset) || offset < 1) {
357
+ throw new RemoteError("gateway/bad-request", "offset must be a safe integer of at least 1", {});
358
+ }
359
+ if (limit > ownConfig(this).maxLines) {
360
+ throw new RemoteError("gateway/bad-request", `limit must be at most ${ownConfig(this).maxLines}`, {});
361
+ }
362
+ return distroRead({
363
+ runner: this.runnerFor(distro),
364
+ distro,
365
+ linuxRoot,
366
+ path,
367
+ offset,
368
+ limit,
369
+ maxBytes: ownConfig(this).maxBytes,
370
+ distroWorkspaceRoot: workspaceFileScope.workspaceRoot,
371
+ signal,
372
+ });
373
+ }
374
+ /**
375
+ * Read one byte window or the complete file; distro UNC roots read inside
376
+ * the distro.
377
+ *
378
+ * @param workspaceFileScope - the Session's file scope.
379
+ * @param path - the file to read.
380
+ * @param options - the byte window.
381
+ * @param signal - caller cancellation.
382
+ */
383
+ async readBytes(workspaceFileScope, path, options, signal) {
384
+ if (!isDistroWorkspace(workspaceFileScope.workspaceRoot))
385
+ return super.readBytes(workspaceFileScope, path, options, signal);
386
+ const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
387
+ return distroReadBytes({
388
+ runner: this.runnerFor(distro),
389
+ distro,
390
+ linuxRoot,
391
+ path,
392
+ offset: options.range?.offset,
393
+ length: options.range?.length,
394
+ maxBytes: ownConfig(this).maxBytes,
395
+ maxFileBytes: ownConfig(this).maxFileBytes,
396
+ distroWorkspaceRoot: workspaceFileScope.workspaceRoot,
397
+ signal,
398
+ });
291
399
  }
292
- return distroRead({
293
- runner: this.runnerFor(distro),
294
- distro,
295
- linuxRoot,
296
- path,
297
- offset,
298
- limit,
299
- maxBytes: this.config.maxBytes,
300
- distroWorkspaceRoot: workspaceFileScope.workspaceRoot,
301
- signal,
302
- });
303
- }
304
-
305
- async readBytes(workspaceFileScope, path, options, signal) {
306
- if (!isDistroWorkspace(workspaceFileScope.workspaceRoot)) return super.readBytes(workspaceFileScope, path, options, signal);
307
- const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
308
- return distroReadBytes({
309
- runner: this.runnerFor(distro),
310
- distro,
311
- linuxRoot,
312
- path,
313
- offset: options.range?.offset,
314
- length: options.range?.length,
315
- maxBytes: this.config.maxBytes,
316
- maxFileBytes: this.config.maxFileBytes,
317
- path,
318
- distroWorkspaceRoot: workspaceFileScope.workspaceRoot,
319
- signal,
320
- });
321
- }
322
-
323
- async readByteRange(workspaceFileScope, path, offset, length, signal) {
324
- if (!isDistroWorkspace(workspaceFileScope.workspaceRoot)) return super.readByteRange(workspaceFileScope, path, offset, length, signal);
325
- const { distro, linuxRoot } = this.coords(workspaceFileScope.workspaceRoot);
326
- return distroReadBytes({
327
- runner: this.runnerFor(distro),
328
- distro,
329
- linuxRoot,
330
- path,
331
- offset,
332
- length,
333
- maxBytes: length,
334
- maxFileBytes: Number.MAX_SAFE_INTEGER,
335
- distroWorkspaceRoot: workspaceFileScope.workspaceRoot,
336
- signal,
337
- });
338
- }
339
400
  }
340
-
341
401
  export default WorkspaceFilesWsl;