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.
- package/CHANGELOG.md +278 -0
- package/CONTRIBUTING.md +129 -39
- package/README.md +20 -20
- package/docs/ARCHITECTURE.md +6 -3
- package/docs/LIMITATIONS.md +13 -0
- package/docs/README.zh.md +20 -20
- package/lib/agent-confined.d.ts +42 -0
- package/lib/agent-confined.js +25 -41
- package/lib/agent-errors.d.ts +31 -0
- package/lib/agent-errors.js +19 -16
- package/lib/agent-exec.d.ts +159 -0
- package/lib/agent-exec.js +151 -165
- package/lib/agent-protocol.d.ts +192 -0
- package/lib/agent-protocol.js +76 -100
- package/lib/agent-shared.d.ts +27 -0
- package/lib/agent-shared.js +12 -12
- package/lib/agent.d.ts +353 -0
- package/lib/agent.js +536 -552
- package/lib/auto-preset.d.ts +110 -0
- package/lib/auto-preset.js +157 -141
- package/lib/bwrap.d.ts +90 -0
- package/lib/bwrap.js +18 -18
- package/lib/file-reference-wsl.d.ts +160 -0
- package/lib/file-reference-wsl.js +192 -173
- package/lib/fs-decisions.d.ts +107 -0
- package/lib/fs-decisions.js +64 -60
- package/lib/fs-routing.d.ts +182 -0
- package/lib/fs-routing.js +173 -116
- package/lib/fs-substrate.d.ts +258 -0
- package/lib/fs-substrate.js +248 -248
- package/lib/fs.d.ts +10 -0
- package/lib/fs.js +5 -0
- package/lib/fsio-agent.d.ts +307 -0
- package/lib/fsio-agent.js +414 -410
- package/lib/fsio-text.d.ts +115 -0
- package/lib/fsio-text.js +104 -84
- package/lib/git-route.d.ts +100 -0
- package/lib/git-route.js +46 -47
- package/lib/index.d.ts +1003 -0
- package/lib/index.js +1101 -1003
- package/lib/listing.d.ts +114 -0
- package/lib/listing.js +68 -55
- package/lib/paths.d.ts +182 -0
- package/lib/paths.js +134 -109
- package/lib/picker.d.ts +266 -0
- package/lib/picker.js +371 -351
- package/lib/ports.d.ts +44 -0
- package/lib/ports.js +39 -29
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +40 -0
- package/lib/probe-cache.js +35 -29
- package/lib/sandbox-core.d.ts +79 -0
- package/lib/sandbox-core.js +71 -88
- package/lib/sandbox.d.ts +48 -0
- package/lib/sandbox.js +4 -4
- package/lib/search-exec.d.ts +96 -0
- package/lib/search-exec.js +52 -79
- package/lib/search-route.d.ts +75 -0
- package/lib/search-route.js +30 -31
- package/lib/shell-env.d.ts +88 -0
- package/lib/shell-env.js +78 -74
- package/lib/shell.d.ts +10 -0
- package/lib/shell.js +5 -0
- package/lib/subprocess.d.ts +313 -0
- package/lib/subprocess.js +267 -262
- package/lib/terminal-activity.d.ts +98 -0
- package/lib/terminal-activity.js +29 -39
- package/lib/terminal-route.d.ts +27 -0
- package/lib/terminal-route.js +16 -8
- package/lib/watcher.d.ts +77 -0
- package/lib/watcher.js +120 -126
- package/lib/workspace-files-route.d.ts +132 -0
- package/lib/workspace-files-route.js +73 -81
- package/lib/workspace-files-wsl.d.ts +257 -0
- package/lib/workspace-files-wsl.js +311 -251
- package/lib/wsl.d.ts +266 -0
- package/lib/wsl.js +144 -155
- 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
|
-
|
|
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
|
-
|
|
56
|
+
return new RemoteError("workspace-file/not-found", `no entry at "${path}"`, { path });
|
|
41
57
|
}
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
65
|
+
return new RemoteError("workspace-file/outside-workspace", `"${path}" is outside the workspace`, { path });
|
|
46
66
|
}
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
75
|
+
return new RemoteError("workspace-file/not-directory", `"${path}" is a ${kind}`, { path, kind });
|
|
51
76
|
}
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
85
|
+
return new RemoteError("workspace-file/not-regular-file", `"${path}" is a ${kind}`, { path, kind });
|
|
56
86
|
}
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
290
|
-
|
|
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;
|