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.
- package/CHANGELOG.md +345 -8
- package/CONTRIBUTING.md +221 -68
- package/README.md +50 -35
- package/SECURITY.md +18 -2
- package/SUPPORT.md +23 -7
- package/cordis.patch.yml +9 -0
- package/docs/ARCHITECTURE.md +200 -57
- package/docs/CONFIGURATION.md +180 -16
- package/docs/LIMITATIONS.md +91 -33
- package/docs/PARITY.md +66 -38
- package/docs/PEER-PARITY.md +95 -32
- package/docs/README.zh.md +49 -31
- package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
- package/docs/UPSTREAM-TERMINAL-TITLE.md +49 -14
- 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 +1106 -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/preflight.d.ts +91 -0
- package/lib/preflight.js +94 -0
- package/lib/preset-choice.d.ts +33 -0
- package/lib/preset-choice.js +15 -7
- package/lib/probe-cache.d.ts +51 -0
- package/lib/probe-cache.js +43 -29
- package/lib/sandbox-core.d.ts +149 -0
- package/lib/sandbox-core.js +239 -87
- 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 +150 -155
- package/locale/preflight/en.json +6 -0
- package/locale/preflight/zh.json +6 -0
- package/package.json +78 -22
- package/scripts/diagnose.sh +92 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The distro-side filesystem substrate: fsio's orchestration, run over the
|
|
3
|
+
* agent's FS frames instead of Node's fs on the 9p share.
|
|
4
|
+
*
|
|
5
|
+
* Where `@deepseek-ai/dsh-fs-local` opens paths on the host, this module asks
|
|
6
|
+
* the resident in-distro agent (`agent/wsl-agent.sh`) to stat, list, read and
|
|
7
|
+
* write on ext4 — where symlinks resolve, mode bits stick, and `find` walks
|
|
8
|
+
* the native filesystem. Everything the peer does HOST-side after the bytes
|
|
9
|
+
* arrive stays here, byte-for-byte: the binary NUL sample, the fatal UTF-8
|
|
10
|
+
* decode, the LF normalization, the edit algorithm (see `fsio-text.js`).
|
|
11
|
+
*
|
|
12
|
+
* Coordinate systems: every method takes and returns LINUX paths; `targetKey`
|
|
13
|
+
* identity remains the distro's UNC form (the whole harness keys caches and
|
|
14
|
+
* guards on it), translated at this boundary via `paths.js`. The module is
|
|
15
|
+
* policy-free — the fence lives in the provider — and peer-free, so it is
|
|
16
|
+
* unit-testable in a bare checkout against a scripted agent.
|
|
17
|
+
*
|
|
18
|
+
* This is a TypeScript source built to `lib/fsio-agent.js`; edit THIS file and
|
|
19
|
+
* run `pnpm run build` — the artifact under `lib/` is generated, and `pnpm test`
|
|
20
|
+
* fails when it drifts.
|
|
21
|
+
*
|
|
22
|
+
* @module dsh-plugin-wsl/fsio-agent
|
|
23
|
+
*/
|
|
24
|
+
import { normalizeLineEndings, restoreLineEndings, applyLiteralEdit } from "./fsio-text.js";
|
|
25
|
+
import type { WslAgent } from "./agent.js";
|
|
26
|
+
import type { ResolvedTarget } from "./fs-substrate.js";
|
|
27
|
+
/** One agent FS round trip's result — the shape `WslAgent#fs` resolves to. */
|
|
28
|
+
export interface AgentFsResult {
|
|
29
|
+
/** The op's exit status. */
|
|
30
|
+
exitCode: number;
|
|
31
|
+
/** The op's payload bytes. */
|
|
32
|
+
stdout: Buffer;
|
|
33
|
+
/** The op's diagnostic line(s). */
|
|
34
|
+
stderr: Buffer;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalize one agent timestamp ("seconds.frac", 9 or 10 fractional digits
|
|
38
|
+
* depending on whether stat or find produced it) into the peer's nanosecond
|
|
39
|
+
* string: exactly nine fractional digits, no dot.
|
|
40
|
+
*
|
|
41
|
+
* @param value - the wire timestamp.
|
|
42
|
+
* @returns seconds followed by nine fractional digits.
|
|
43
|
+
*/
|
|
44
|
+
export declare function nanoseconds(value: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* The peer's `versionOf`: dev, inode, size and both nanosecond timestamps.
|
|
47
|
+
* Self-consistency within this provider is what versions need; the ingredients
|
|
48
|
+
* come from one kernel, so a change in any byte of the file changes the string.
|
|
49
|
+
*
|
|
50
|
+
* @param ingredients - the stat record's identity fields.
|
|
51
|
+
* @returns the opaque version string.
|
|
52
|
+
*/
|
|
53
|
+
export declare function versionOf({ dev, ino, size, mtimeNs, ctimeNs }: {
|
|
54
|
+
dev: string;
|
|
55
|
+
ino: string;
|
|
56
|
+
size: string | number;
|
|
57
|
+
mtimeNs: string;
|
|
58
|
+
ctimeNs: string;
|
|
59
|
+
}): string;
|
|
60
|
+
/**
|
|
61
|
+
* Parse one stat record as the agent emits it:
|
|
62
|
+
* `type \t mode \t size \t dev \t ino \t mtime \t ctime`.
|
|
63
|
+
*
|
|
64
|
+
* @param stdout - the op's payload.
|
|
65
|
+
* @returns the parsed row.
|
|
66
|
+
*/
|
|
67
|
+
export declare function parseStatRecord(stdout: Buffer): {
|
|
68
|
+
type: string;
|
|
69
|
+
mode: number;
|
|
70
|
+
size: number;
|
|
71
|
+
dev: string;
|
|
72
|
+
ino: string;
|
|
73
|
+
version: string;
|
|
74
|
+
};
|
|
75
|
+
/** One `find` listing row, as `listChildren` shapes it. */
|
|
76
|
+
export interface ChildRow {
|
|
77
|
+
name: string;
|
|
78
|
+
type: string;
|
|
79
|
+
target: {
|
|
80
|
+
displayPath: string;
|
|
81
|
+
targetKey: string;
|
|
82
|
+
};
|
|
83
|
+
version?: string;
|
|
84
|
+
size?: number;
|
|
85
|
+
}
|
|
86
|
+
/** The stat row `stat` returns. */
|
|
87
|
+
export interface StatRow {
|
|
88
|
+
type: string;
|
|
89
|
+
mode: number;
|
|
90
|
+
size: number;
|
|
91
|
+
version: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One distro's filesystem face. Owns the agent round trips and the host-side
|
|
95
|
+
* validation; the provider layers the fence on top.
|
|
96
|
+
*/
|
|
97
|
+
export declare class DistroFs {
|
|
98
|
+
/** The resident in-distro agent. */
|
|
99
|
+
agent: WslAgent;
|
|
100
|
+
/** The pinned distro name, for UNC translation. */
|
|
101
|
+
distro: string;
|
|
102
|
+
/**
|
|
103
|
+
* @param deps - the substrate's collaborators.
|
|
104
|
+
*/
|
|
105
|
+
constructor({ agent, distro }: {
|
|
106
|
+
/** The resident in-distro agent. */
|
|
107
|
+
agent: WslAgent;
|
|
108
|
+
/** The pinned distro name, for UNC translation. */
|
|
109
|
+
distro: string;
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Run one FS op, mapping an aborted request onto the structured code the
|
|
113
|
+
* provider's callers expect. A mutation may name the agent that must run it
|
|
114
|
+
* — the confined resident whose mount table matches the granted rights —
|
|
115
|
+
* while reads ride the plain resident.
|
|
116
|
+
*
|
|
117
|
+
* @param op - the FS op name (`stat`, `lstat`, `realpath`, `list`, `read`, `write`).
|
|
118
|
+
* @param args - the op's arguments, one wire line each.
|
|
119
|
+
* @param signal - the caller's cancellation.
|
|
120
|
+
* @param agent - the agent that must run the op; the plain resident by default.
|
|
121
|
+
* @returns the op's result.
|
|
122
|
+
*/
|
|
123
|
+
private request;
|
|
124
|
+
/**
|
|
125
|
+
* Turn a failed op into the coded error, reading the agent's
|
|
126
|
+
* `dsh-fs|<reason>|<b64 message>` line; a frame without one is a raw I/O
|
|
127
|
+
* fault carrying whatever stderr the agent did produce. On a WRITE, the
|
|
128
|
+
* kernel's read-only-bind denial is classified as the sandbox refusal it is,
|
|
129
|
+
* mirroring the command path's denial signatures.
|
|
130
|
+
*
|
|
131
|
+
* @param op - the failed op (`read` or `write`).
|
|
132
|
+
* @param result - the failed round trip's result.
|
|
133
|
+
* @param displayPath - the caller-facing path for the message.
|
|
134
|
+
* @returns the structured failure.
|
|
135
|
+
*/
|
|
136
|
+
private fail;
|
|
137
|
+
/**
|
|
138
|
+
* Stat one Linux path, following or not. A missing path is `null`, exactly
|
|
139
|
+
* like the peer's `probe`; everything else that is not found-class is a
|
|
140
|
+
* structured failure.
|
|
141
|
+
*
|
|
142
|
+
* @param linuxPath - the path inside the distro.
|
|
143
|
+
* @param options - the stat flavour.
|
|
144
|
+
* @returns the stat row, or null when the path is absent.
|
|
145
|
+
*/
|
|
146
|
+
stat(linuxPath: string, { follow, signal }?: {
|
|
147
|
+
follow?: boolean;
|
|
148
|
+
signal?: AbortSignal;
|
|
149
|
+
}): Promise<StatRow | null>;
|
|
150
|
+
/**
|
|
151
|
+
* The distro-side identity of one path: the strict realpath, or on a missing
|
|
152
|
+
* target the nearest existing ancestor with the missing suffix — the peer's
|
|
153
|
+
* `resolveLocalTarget`, run where the symlinks live.
|
|
154
|
+
*
|
|
155
|
+
* @param linuxPath - the absolute Linux path to resolve.
|
|
156
|
+
* @param signal - cancels the round trip.
|
|
157
|
+
* @returns the canonical Linux path.
|
|
158
|
+
*/
|
|
159
|
+
canonicalPath(linuxPath: string, signal?: AbortSignal): Promise<string>;
|
|
160
|
+
/**
|
|
161
|
+
* Resolve one Linux path to the target shape the provider and tooling use:
|
|
162
|
+
* the Linux spelling the model sees and the UNC identity the harness keys on.
|
|
163
|
+
*
|
|
164
|
+
* @param linuxPath - the absolute Linux path to resolve.
|
|
165
|
+
* @param signal - cancels the round trip.
|
|
166
|
+
* @returns the resolved target.
|
|
167
|
+
*/
|
|
168
|
+
resolveTarget(linuxPath: string, signal?: AbortSignal): Promise<{
|
|
169
|
+
displayPath: string;
|
|
170
|
+
targetKey: string;
|
|
171
|
+
}>;
|
|
172
|
+
/**
|
|
173
|
+
* List one directory's direct children in name order — one `find` pass in
|
|
174
|
+
* the distro, sorted and shaped host-side like the peer's `listDirectory`.
|
|
175
|
+
* The wire type is the entry's own (lstat): a symlink child then gets one
|
|
176
|
+
* follow-up stat (its target's version and size) plus a realpath (its
|
|
177
|
+
* identity), and a dangling symlink degrades to the peer's `other` with no
|
|
178
|
+
* version — exactly what the peer's null follow-probe produces.
|
|
179
|
+
*
|
|
180
|
+
* @param target - the resolved directory to list.
|
|
181
|
+
* @param signal - aborts between children.
|
|
182
|
+
* @returns the child rows, name-sorted.
|
|
183
|
+
*/
|
|
184
|
+
listChildren(target: ResolvedTarget, signal?: AbortSignal): Promise<ChildRow[]>;
|
|
185
|
+
/**
|
|
186
|
+
* Stat the target as a regular file or refuse, mirroring the peer's
|
|
187
|
+
* `statRegularFile` messages.
|
|
188
|
+
*
|
|
189
|
+
* @param target - the resolved file.
|
|
190
|
+
* @param verb - the operation name for the messages.
|
|
191
|
+
* @param signal - the caller's cancellation.
|
|
192
|
+
* @returns the stat row of the regular file.
|
|
193
|
+
*/
|
|
194
|
+
private statRegularFile;
|
|
195
|
+
/**
|
|
196
|
+
* Read the whole file's bytes through windowed frames.
|
|
197
|
+
*
|
|
198
|
+
* @param target - the resolved file.
|
|
199
|
+
* @param signal - the caller's cancellation.
|
|
200
|
+
* @param maxBytes - inclusive byte cap on the complete content.
|
|
201
|
+
* @returns the file's bytes.
|
|
202
|
+
*/
|
|
203
|
+
private readWhole;
|
|
204
|
+
/**
|
|
205
|
+
* Read a whole regular UTF-8 file, rejecting binaries and invalid UTF-8.
|
|
206
|
+
*
|
|
207
|
+
* @param target - the resolved file.
|
|
208
|
+
* @param signal - aborts the read.
|
|
209
|
+
* @returns the full decoded text, byte-for-byte.
|
|
210
|
+
*/
|
|
211
|
+
readWholeText(target: ResolvedTarget, signal?: AbortSignal): Promise<string>;
|
|
212
|
+
/**
|
|
213
|
+
* Read a whole regular file as raw bytes, bounded by `maxBytes`.
|
|
214
|
+
*
|
|
215
|
+
* @param target - the resolved file.
|
|
216
|
+
* @param signal - aborts the read.
|
|
217
|
+
* @param maxBytes - inclusive byte cap on the complete content.
|
|
218
|
+
* @returns the full raw content, at most `maxBytes` long.
|
|
219
|
+
*/
|
|
220
|
+
readWholeBytes(target: ResolvedTarget, signal: AbortSignal | undefined, maxBytes: number): Promise<Uint8Array>;
|
|
221
|
+
/**
|
|
222
|
+
* Read the bytes at `[offset, offset + length)` of a regular file. A window
|
|
223
|
+
* at or past the end is empty.
|
|
224
|
+
*
|
|
225
|
+
* @param target - the resolved file.
|
|
226
|
+
* @param range - the byte window.
|
|
227
|
+
* @param signal - aborts the read.
|
|
228
|
+
* @returns the window's bytes.
|
|
229
|
+
*/
|
|
230
|
+
readByteWindow(target: ResolvedTarget, range: {
|
|
231
|
+
offset: number;
|
|
232
|
+
length: number;
|
|
233
|
+
}, signal?: AbortSignal): Promise<Uint8Array>;
|
|
234
|
+
/**
|
|
235
|
+
* Stream a whole regular UTF-8 file as decoded text chunks — the binary
|
|
236
|
+
* sample scan and cross-chunk decoding stay host-side, exactly the peer's.
|
|
237
|
+
*
|
|
238
|
+
* @param target - the resolved file.
|
|
239
|
+
* @param signal - aborts the stream.
|
|
240
|
+
* @returns decoded text chunks in file order.
|
|
241
|
+
*/
|
|
242
|
+
streamWholeText(target: ResolvedTarget, signal?: AbortSignal): AsyncGenerator<string>;
|
|
243
|
+
/**
|
|
244
|
+
* Publish `content` over `linuxPath` the POSIX way: the agent stages into a
|
|
245
|
+
* private 0700 sibling, fsyncs, chmods, and renames — or hard links for a
|
|
246
|
+
* guarded create. A `replace` publish carrying `expectedVersion` is
|
|
247
|
+
* re-verified by the agent against a fresh stat one syscall before the
|
|
248
|
+
* rename: a concurrent writer (an editor save, a `git stash`) wins, and the
|
|
249
|
+
* stale write refuses with `FS_STALE_VERSION` instead of landing.
|
|
250
|
+
*
|
|
251
|
+
* @param linuxPath - the target path inside the distro.
|
|
252
|
+
* @param content - the full new content.
|
|
253
|
+
* @param options - the publish options.
|
|
254
|
+
* @returns resolves when the publish landed.
|
|
255
|
+
*/
|
|
256
|
+
writeFileAtomic(linuxPath: string, content: string | Uint8Array, { mode, createIfAbsent, expectedVersion, signal, agent }?: {
|
|
257
|
+
/**
|
|
258
|
+
* The mode to publish (omitted keeps the temp's 0600 for a create; the
|
|
259
|
+
* caller passes the incumbent's for an edit).
|
|
260
|
+
*/
|
|
261
|
+
mode?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Guarded-create intent: publish with a no-replace link carrying this
|
|
264
|
+
* display path in errors.
|
|
265
|
+
*/
|
|
266
|
+
createIfAbsent?: {
|
|
267
|
+
displayPath: string;
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* The version string this write was based on (a stat-derived one);
|
|
271
|
+
* re-verified distro-side before the rename.
|
|
272
|
+
*/
|
|
273
|
+
expectedVersion?: string;
|
|
274
|
+
/** Aborts the write. */
|
|
275
|
+
signal?: AbortSignal;
|
|
276
|
+
/**
|
|
277
|
+
* The agent this mutation rides (the confined resident under a confined
|
|
278
|
+
* policy).
|
|
279
|
+
*/
|
|
280
|
+
agent?: WslAgent;
|
|
281
|
+
}): Promise<void>;
|
|
282
|
+
/**
|
|
283
|
+
* Read and decode a file for editing: LF-normalized content plus the style
|
|
284
|
+
* to restore on write-back.
|
|
285
|
+
*
|
|
286
|
+
* @param target - the resolved file.
|
|
287
|
+
* @param signal - aborts the read.
|
|
288
|
+
* @returns the LF-normalized content and its line-ending style.
|
|
289
|
+
*/
|
|
290
|
+
readForEdit(target: ResolvedTarget, signal?: AbortSignal): Promise<{
|
|
291
|
+
content: string;
|
|
292
|
+
lineEndings: "LF" | "CRLF";
|
|
293
|
+
}>;
|
|
294
|
+
/**
|
|
295
|
+
* Best-effort overwrite diff basis: `null` whenever the file cannot serve
|
|
296
|
+
* one (binary, at/above the bound, vanished), so the write still succeeds
|
|
297
|
+
* and presentation falls back to a whole-file diff.
|
|
298
|
+
*
|
|
299
|
+
* @param target - the resolved file.
|
|
300
|
+
* @param maxBytes - exclusive upper bound for the held basis.
|
|
301
|
+
* @param signal - cancellation propagates, unlike I/O failure.
|
|
302
|
+
* @returns the LF-normalized text, or null.
|
|
303
|
+
*/
|
|
304
|
+
readTextForDiff(target: ResolvedTarget, maxBytes: number, signal?: AbortSignal): Promise<string | null>;
|
|
305
|
+
}
|
|
306
|
+
export { applyLiteralEdit, normalizeLineEndings, restoreLineEndings };
|
|
307
|
+
export default DistroFs;
|