dsh-wsl-workspace 0.3.2 → 0.4.2
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/README.md +31 -0
- package/TESTING.md +11 -5
- package/lib/client.js +884 -764
- package/lib/client.js.map +1 -1
- package/lib/fs.js +193 -188
- package/lib/fs.js.map +1 -1
- package/lib/index.js +1001 -923
- package/lib/index.js.map +1 -1
- package/lib/shell.js +95 -58
- package/lib/shell.js.map +1 -1
- package/lib/{wsl-C5_mxGPM.js → wsl-6VcgOvF9.js} +24 -17
- package/lib/wsl-6VcgOvF9.js.map +1 -0
- package/lib/{wsl-credentials-BI4v5TNZ.js → wsl-credentials-B6wIXsKt.js} +7 -14
- package/lib/wsl-credentials-B6wIXsKt.js.map +1 -0
- package/package.json +19 -1
- package/src/client/index.ts +149 -24
- package/src/client/locales.ts +68 -68
- package/src/host/wsl-skills.ts +113 -9
- package/lib/wsl-C5_mxGPM.js.map +0 -1
- package/lib/wsl-credentials-BI4v5TNZ.js.map +0 -1
package/lib/fs.js
CHANGED
|
@@ -1,189 +1,194 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import z from "@deepseek-ai/schemastery";
|
|
3
|
-
import { link, lstat, rename } from "node:fs/promises";
|
|
4
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
-
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
6
|
-
import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @param
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* @param
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
const
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
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
|
-
|
|
188
|
-
|
|
1
|
+
import { defaultDistroSync, isAbsoluteLinuxPath, joinUnc, mntToWindowsPath, parseWslUnc, windowsToMntPath } from "./wsl-6VcgOvF9.js";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
|
+
import { link, lstat, rename } from "node:fs/promises";
|
|
4
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
+
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
6
|
+
import { LocalFileSystem } from "@deepseek-ai/dsh-fs-local";
|
|
7
|
+
|
|
8
|
+
//#region src/fs.ts
|
|
9
|
+
/**
|
|
10
|
+
* The WSL filesystem backend. Identity keys are canonical UNC paths; the
|
|
11
|
+
* Linux form is derived on demand, so both worlds stay in sync across
|
|
12
|
+
* aliases and symlinks.
|
|
13
|
+
*/
|
|
14
|
+
var WslFileSystem = class WslFileSystem extends LocalFileSystem {
|
|
15
|
+
static Config = z.object({
|
|
16
|
+
cwd: z.string(),
|
|
17
|
+
distro: z.string(),
|
|
18
|
+
diffBasisMaxBytes: z.number().default(10 * 1024 * 1024)
|
|
19
|
+
});
|
|
20
|
+
distro;
|
|
21
|
+
executionCwd = new AsyncLocalStorage();
|
|
22
|
+
constructor(ctx, config) {
|
|
23
|
+
super(ctx, config);
|
|
24
|
+
this.distro = config.distro;
|
|
25
|
+
ctx.on("tools/execute", (exec, next) => this.executionCwd.run(exec.agent?.session.header.cwd, next));
|
|
26
|
+
this.internals = {
|
|
27
|
+
linkFile: WslFileSystem.publishNoReplace,
|
|
28
|
+
replaceFile: WslFileSystem.replaceOverWrite,
|
|
29
|
+
copyFileDacl: WslFileSystem.skipDaclCopy
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* No-replace publication for filesystems without hard links. A real
|
|
34
|
+
* collision (a concurrent external creator won) must still surface as the
|
|
35
|
+
* original EEXIST so the guarded-create failure path classifies it; an
|
|
36
|
+
* absent target falls back to rename, which on Windows publishes without
|
|
37
|
+
* replacing anything. Safe against this backend's own writers because the
|
|
38
|
+
* per-target lock serializes them.
|
|
39
|
+
* @param tempPath - the staged file.
|
|
40
|
+
* @param destPath - the destination to create.
|
|
41
|
+
*/
|
|
42
|
+
static async publishNoReplace(tempPath, destPath) {
|
|
43
|
+
try {
|
|
44
|
+
await link(tempPath, destPath);
|
|
45
|
+
return;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
let exists = false;
|
|
48
|
+
try {
|
|
49
|
+
await lstat(destPath);
|
|
50
|
+
exists = true;
|
|
51
|
+
} catch {}
|
|
52
|
+
if (exists) throw error;
|
|
53
|
+
await rename(tempPath, destPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Security-preserving replacement boundary: Windows rename replaces an
|
|
58
|
+
* existing destination atomically; no DACL preservation is needed over 9P.
|
|
59
|
+
* @param destPath - the file being replaced.
|
|
60
|
+
* @param tempPath - the staged replacement.
|
|
61
|
+
*/
|
|
62
|
+
static async replaceOverWrite(destPath, tempPath) {
|
|
63
|
+
await rename(tempPath, destPath);
|
|
64
|
+
}
|
|
65
|
+
/** 9P files inherit their directory's DACL; nothing to preserve. */
|
|
66
|
+
static async skipDaclCopy() {}
|
|
67
|
+
/** Translate a model/plugin path into Windows-side coordinates. */
|
|
68
|
+
translate(path, cwd) {
|
|
69
|
+
const unc = parseWslUnc(path);
|
|
70
|
+
if (unc !== null) return {
|
|
71
|
+
input: joinUnc(unc.distro, unc.linuxPath),
|
|
72
|
+
cwd: this.cwdOr(cwd)
|
|
73
|
+
};
|
|
74
|
+
if (isAbsoluteLinuxPath(path)) {
|
|
75
|
+
const win = mntToWindowsPath(path);
|
|
76
|
+
if (win !== null) return {
|
|
77
|
+
input: win,
|
|
78
|
+
cwd: this.cwdOr(cwd)
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
input: joinUnc(this.distroFor(cwd), path),
|
|
82
|
+
cwd: this.cwdOr(cwd)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (windowsToMntPath(path) !== null) return {
|
|
86
|
+
input: path,
|
|
87
|
+
cwd: this.cwdOr(cwd)
|
|
88
|
+
};
|
|
89
|
+
const base = this.uncCwd(cwd);
|
|
90
|
+
return {
|
|
91
|
+
input: path,
|
|
92
|
+
cwd: base
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** A base for absolute inputs (unused by resolution, but the parent needs one). */
|
|
96
|
+
cwdOr(cwd) {
|
|
97
|
+
return cwd ?? this.executionCwd.getStore() ?? this.config.cwd ?? process.cwd();
|
|
98
|
+
}
|
|
99
|
+
uncCwd(cwd) {
|
|
100
|
+
const base = cwd ?? this.executionCwd.getStore() ?? this.config.cwd;
|
|
101
|
+
if (base === void 0 || base === "") throw new FsError("wsl-fs: no cwd and no configured base for relative resolution", "FS_IO_ERROR");
|
|
102
|
+
const unc = parseWslUnc(base);
|
|
103
|
+
if (unc !== null) return joinUnc(unc.distro, unc.linuxPath);
|
|
104
|
+
if (isAbsoluteLinuxPath(base)) return joinUnc(this.distroFor(base), base);
|
|
105
|
+
if (windowsToMntPath(base) !== null) return base;
|
|
106
|
+
throw new FsError(`wsl-fs: cwd "${base}" is not in the WSL execution world`, "FS_IO_ERROR");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the distribution an absolute Linux path opens inside. The chain:
|
|
110
|
+
* the caller cwd when it is a WSL UNC path, then the current tool
|
|
111
|
+
* execution's session cwd, then the configured `distro`,
|
|
112
|
+
* then the host's default distribution from the Lxss registry. The registry
|
|
113
|
+
* fallback is reserved for calls that genuinely have no session.
|
|
114
|
+
*/
|
|
115
|
+
distroFor(cwd) {
|
|
116
|
+
const fromCwd = parseWslUnc(cwd ?? "");
|
|
117
|
+
if (fromCwd !== null) return fromCwd.distro;
|
|
118
|
+
const fromExecution = parseWslUnc(this.executionCwd.getStore() ?? "");
|
|
119
|
+
if (fromExecution !== null) return fromExecution.distro;
|
|
120
|
+
const distro = this.distro;
|
|
121
|
+
if (distro !== void 0 && distro !== "") return distro;
|
|
122
|
+
const fallback = defaultDistroSync();
|
|
123
|
+
if (fallback !== void 0) return fallback;
|
|
124
|
+
throw new FsError("wsl-fs: Linux path carries no distribution and none is configured", "FS_IO_ERROR");
|
|
125
|
+
}
|
|
126
|
+
/** The Linux display path for a resolved Windows-side path. */
|
|
127
|
+
linuxDisplay(raw) {
|
|
128
|
+
const unc = parseWslUnc(raw);
|
|
129
|
+
if (unc !== null) return unc.linuxPath;
|
|
130
|
+
const mnt = windowsToMntPath(raw);
|
|
131
|
+
if (mnt !== null) return mnt;
|
|
132
|
+
throw new FsError(`wsl-fs: resolved path "${raw}" is outside the WSL execution world`, "FS_IO_ERROR");
|
|
133
|
+
}
|
|
134
|
+
async resolve(path, opts) {
|
|
135
|
+
if (opts?.signal?.aborted) throw new FsError("resolve aborted", "FS_ABORTED");
|
|
136
|
+
const { input, cwd } = this.translate(path, opts?.cwd);
|
|
137
|
+
const local = await super.resolve(input, {
|
|
138
|
+
cwd,
|
|
139
|
+
...opts?.signal !== void 0 ? { signal: opts.signal } : {}
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
targetKey: local.targetKey,
|
|
143
|
+
displayPath: this.linuxDisplay(String(local.displayPath))
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
processPath(target) {
|
|
147
|
+
const key = String(target.targetKey);
|
|
148
|
+
const unc = parseWslUnc(key);
|
|
149
|
+
if (unc !== null) return unc.linuxPath;
|
|
150
|
+
const mnt = windowsToMntPath(key);
|
|
151
|
+
if (mnt !== null) return mnt;
|
|
152
|
+
throw new FsError(`wsl-fs: target "${target.displayPath}" is outside the WSL execution world`, "FS_IO_ERROR");
|
|
153
|
+
}
|
|
154
|
+
fileUrl(target) {
|
|
155
|
+
const linux = this.processPath(target);
|
|
156
|
+
const encoded = linux.split("/").map(encodeURIComponent).join("/");
|
|
157
|
+
return `file://${encoded}`;
|
|
158
|
+
}
|
|
159
|
+
contains(parent, child) {
|
|
160
|
+
const parentWorld = this.worldPath(parent);
|
|
161
|
+
const childWorld = this.worldPath(child);
|
|
162
|
+
if (parentWorld.distro !== childWorld.distro) return false;
|
|
163
|
+
const parentPath = parentWorld.linuxPath;
|
|
164
|
+
const childPath = childWorld.linuxPath;
|
|
165
|
+
if (childPath === parentPath) return true;
|
|
166
|
+
return parentPath === "/" ? true : childPath.startsWith(`${parentPath}/`);
|
|
167
|
+
}
|
|
168
|
+
/** One target's (distro, linuxPath) pair for containment; `undefined` distro = Windows world. */
|
|
169
|
+
worldPath(target) {
|
|
170
|
+
const key = String(target.targetKey);
|
|
171
|
+
const unc = parseWslUnc(key);
|
|
172
|
+
if (unc !== null) return {
|
|
173
|
+
distro: unc.distro,
|
|
174
|
+
linuxPath: unc.linuxPath
|
|
175
|
+
};
|
|
176
|
+
const mnt = windowsToMntPath(key);
|
|
177
|
+
if (mnt !== null) return {
|
|
178
|
+
distro: void 0,
|
|
179
|
+
linuxPath: mnt
|
|
180
|
+
};
|
|
181
|
+
throw new FsError(`wsl-fs: target "${target.displayPath}" is outside the WSL execution world`, "FS_IO_ERROR");
|
|
182
|
+
}
|
|
183
|
+
async lstat(path, opts, signal) {
|
|
184
|
+
if (signal?.aborted) throw new FsError("lstat aborted", "FS_ABORTED");
|
|
185
|
+
if (path.trim().length === 0) throw new FsError("file_path must be a non-empty string", "FS_NOT_FOUND");
|
|
186
|
+
const { input, cwd } = this.translate(path, opts?.cwd);
|
|
187
|
+
return super.lstat(input, { cwd }, signal);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var fs_default = WslFileSystem;
|
|
191
|
+
|
|
192
|
+
//#endregion
|
|
193
|
+
export { WslFileSystem, fs_default as default };
|
|
189
194
|
//# sourceMappingURL=fs.js.map
|
package/lib/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","names":[],"sources":["../src/fs.ts"],"sourcesContent":["/**\r\n * WSL Service Provider for the `ctx.fs` capability seam. Backed by the host\r\n * filesystem over the `\\\\wsl.localhost\\<distro>\\…` 9P share — zero install\r\n * inside the distribution — while every model/UI-facing path is the Linux\r\n * path a WSL process would open (`processPath`, `displayPath`, `fileUrl`).\r\n * Reuses `LocalFileSystem`'s mechanics (realpath identity, atomic writes,\r\n * per-target locks, version guards) unchanged, because those operate on the\r\n * UNC path Node can open directly.\r\n *\r\n * Both UNC paths and Linux absolute paths resolve; Windows drive paths\r\n * resolve through their `/mnt/<drive>` form, so a WSL-composed session can\r\n * still touch the Windows filesystem coherently.\r\n * @module dsh-wsl-workspace/fs\r\n */\r\n\r\nimport { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport { AsyncLocalStorage } from 'node:async_hooks'\r\nimport { link, lstat, rename } from 'node:fs/promises'\r\nimport { FsError } from '@deepseek-ai/dsh-fs'\r\nimport type { FsPathInfo, FsTarget } from '@deepseek-ai/dsh-fs'\r\nimport { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'\r\nimport {\r\n isAbsoluteLinuxPath,\r\n joinUnc,\r\n mntToWindowsPath,\r\n parseWslUnc,\r\n windowsToMntPath,\r\n} from './shared/paths.ts'\r\nimport { defaultDistroSync } from './shared/wsl.ts'\r\n\r\n/** Plugin config. `cwd`/`distro` are optional because UNC workdirs carry both. */\r\nexport interface Config {\r\n /** Base directory for relative paths without a per-call cwd (UNC or Linux). */\r\n cwd?: string\r\n /** Default distribution for Linux-absolute paths without a UNC cwd. */\r\n distro?: string\r\n /** Exclusive UTF-8 byte limit on each overwrite-diff side (see fs-local). */\r\n diffBasisMaxBytes?: number\r\n}\r\n\r\n/** One translated coordinate: the input the local backend opens plus its cwd. */\r\ninterface Translated {\r\n /** Absolute path to hand to the local backend (UNC or Windows drive). */\r\n input: string\r\n /** Absolute Windows-side base for relative inputs (UNC or Windows drive). */\r\n cwd: string\r\n}\r\n\r\ninterface ToolExecution {\r\n agent?: { session: { header: { cwd?: string } } }\r\n}\r\n\r\n/**\r\n * The WSL filesystem backend. Identity keys are canonical UNC paths; the\r\n * Linux form is derived on demand, so both worlds stay in sync across\r\n * aliases and symlinks.\r\n */\r\nexport class WslFileSystem extends LocalFileSystem {\r\n static override Config: z<Config> = z.object({\r\n cwd: z.string(),\r\n distro: z.string(),\r\n diffBasisMaxBytes: z.number().default(10 * 1024 * 1024),\r\n })\r\n\r\n private readonly distro: string | undefined\r\n private readonly executionCwd = new AsyncLocalStorage<string | undefined>()\r\n\r\n constructor(ctx: Context, config: Config) {\r\n // schemastery fills the defaults before construction; the parent validates\r\n // `diffBasisMaxBytes` and stores the resolved shape.\r\n super(ctx, config)\r\n this.distro = config.distro\r\n ctx.on('tools/execute', (exec: ToolExecution, next: () => unknown) => (\r\n this.executionCwd.run(exec.agent?.session.header.cwd, next)\r\n ))\r\n // The 9P/drvfs substrate has no hard links and no Win32 security semantics:\r\n // replace the atomic-publication boundaries the parent's fsio defaults to.\r\n this.internals = {\r\n linkFile: WslFileSystem.publishNoReplace,\r\n replaceFile: WslFileSystem.replaceOverWrite,\r\n copyFileDacl: WslFileSystem.skipDaclCopy,\r\n }\r\n }\r\n\r\n /**\r\n * No-replace publication for filesystems without hard links. A real\r\n * collision (a concurrent external creator won) must still surface as the\r\n * original EEXIST so the guarded-create failure path classifies it; an\r\n * absent target falls back to rename, which on Windows publishes without\r\n * replacing anything. Safe against this backend's own writers because the\r\n * per-target lock serializes them.\r\n * @param tempPath - the staged file.\r\n * @param destPath - the destination to create.\r\n */\r\n private static async publishNoReplace(tempPath: string, destPath: string): Promise<void> {\r\n try {\r\n await link(tempPath, destPath)\r\n return\r\n } catch (error) {\r\n let exists = false\r\n try {\r\n await lstat(destPath)\r\n exists = true\r\n } catch {\r\n // Absent destination: rename publishes the staged file.\r\n }\r\n if (exists) throw error\r\n await rename(tempPath, destPath)\r\n }\r\n }\r\n\r\n /**\r\n * Security-preserving replacement boundary: Windows rename replaces an\r\n * existing destination atomically; no DACL preservation is needed over 9P.\r\n * @param destPath - the file being replaced.\r\n * @param tempPath - the staged replacement.\r\n */\r\n private static async replaceOverWrite(destPath: string, tempPath: string): Promise<void> {\r\n await rename(tempPath, destPath)\r\n }\r\n\r\n /** 9P files inherit their directory's DACL; nothing to preserve. */\r\n private static async skipDaclCopy(): Promise<void> {}\r\n\r\n /** Translate a model/plugin path into Windows-side coordinates. */\r\n private translate(path: string, cwd?: string): Translated {\r\n const unc = parseWslUnc(path)\r\n if (unc !== null) {\r\n return { input: joinUnc(unc.distro, unc.linuxPath), cwd: this.cwdOr(cwd) }\r\n }\r\n if (isAbsoluteLinuxPath(path)) {\r\n // /mnt/<drive>/… names the Windows filesystem inside the Linux world\r\n // (the dual-access path for migration): open the drive path directly so\r\n // both worlds stay coherent — the display stays the /mnt form.\r\n const win = mntToWindowsPath(path)\r\n if (win !== null) return { input: win, cwd: this.cwdOr(cwd) }\r\n return { input: joinUnc(this.distroFor(cwd), path), cwd: this.cwdOr(cwd) }\r\n }\r\n if (windowsToMntPath(path) !== null) {\r\n // Windows drive paths open directly; the Linux world reaches them via /mnt.\r\n return { input: path, cwd: this.cwdOr(cwd) }\r\n }\r\n // Relative: resolve against the caller cwd (or the configured base).\r\n const base = this.uncCwd(cwd)\r\n return { input: path, cwd: base }\r\n }\r\n\r\n /** A base for absolute inputs (unused by resolution, but the parent needs one). */\r\n private cwdOr(cwd?: string): string {\r\n return cwd ?? this.executionCwd.getStore() ?? this.config.cwd ?? process.cwd()\r\n }\r\n\r\n private uncCwd(cwd?: string): string {\r\n const base = cwd ?? this.executionCwd.getStore() ?? this.config.cwd\r\n if (base === undefined || base === '') {\r\n throw new FsError('wsl-fs: no cwd and no configured base for relative resolution', 'FS_IO_ERROR')\r\n }\r\n const unc = parseWslUnc(base)\r\n if (unc !== null) return joinUnc(unc.distro, unc.linuxPath)\r\n if (isAbsoluteLinuxPath(base)) return joinUnc(this.distroFor(base), base)\r\n if (windowsToMntPath(base) !== null) return base\r\n throw new FsError(`wsl-fs: cwd \"${base}\" is not in the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n /**\r\n * Resolve the distribution an absolute Linux path opens inside. The chain:\r\n * the caller cwd when it is a WSL UNC path, then the current tool\r\n * execution's session cwd, then the configured `distro`,\r\n * then the host's default distribution from the Lxss registry. The registry\r\n * fallback is reserved for calls that genuinely have no session.\r\n */\r\n private distroFor(cwd?: string): string {\r\n const fromCwd = parseWslUnc(cwd ?? '')\r\n if (fromCwd !== null) return fromCwd.distro\r\n const fromExecution = parseWslUnc(this.executionCwd.getStore() ?? '')\r\n if (fromExecution !== null) return fromExecution.distro\r\n const distro = this.distro\r\n if (distro !== undefined && distro !== '') return distro\r\n const fallback = defaultDistroSync()\r\n if (fallback !== undefined) return fallback\r\n throw new FsError('wsl-fs: Linux path carries no distribution and none is configured', 'FS_IO_ERROR')\r\n }\r\n\r\n /** The Linux display path for a resolved Windows-side path. */\r\n private linuxDisplay(raw: string): string {\r\n const unc = parseWslUnc(raw)\r\n if (unc !== null) return unc.linuxPath\r\n const mnt = windowsToMntPath(raw)\r\n if (mnt !== null) return mnt\r\n throw new FsError(`wsl-fs: resolved path \"${raw}\" is outside the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n override async resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise<FsTarget> {\r\n if (opts?.signal?.aborted) throw new FsError('resolve aborted', 'FS_ABORTED')\r\n const { input, cwd } = this.translate(path, opts?.cwd)\r\n const local = await super.resolve(input, {\r\n cwd,\r\n ...opts?.signal !== undefined ? { signal: opts.signal } : {},\r\n })\r\n return { targetKey: local.targetKey, displayPath: this.linuxDisplay(String(local.displayPath)) }\r\n }\r\n\r\n override processPath(target: FsTarget): string {\r\n const key = String(target.targetKey)\r\n const unc = parseWslUnc(key)\r\n if (unc !== null) return unc.linuxPath\r\n const mnt = windowsToMntPath(key)\r\n if (mnt !== null) return mnt\r\n throw new FsError(`wsl-fs: target \"${target.displayPath}\" is outside the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n override fileUrl(target: FsTarget): string {\r\n const linux = this.processPath(target)\r\n const encoded = linux.split('/').map(encodeURIComponent).join('/')\r\n return `file://${encoded}`\r\n }\r\n\r\n override contains(parent: FsTarget, child: FsTarget): boolean {\r\n const parentWorld = this.worldPath(parent)\r\n const childWorld = this.worldPath(child)\r\n if (parentWorld.distro !== childWorld.distro) return false\r\n const parentPath = parentWorld.linuxPath\r\n const childPath = childWorld.linuxPath\r\n if (childPath === parentPath) return true\r\n return parentPath === '/' ? true : childPath.startsWith(`${parentPath}/`)\r\n }\r\n\r\n /** One target's (distro, linuxPath) pair for containment; `undefined` distro = Windows world. */\r\n private worldPath(target: FsTarget): { distro: string | undefined; linuxPath: string } {\r\n const key = String(target.targetKey)\r\n const unc = parseWslUnc(key)\r\n if (unc !== null) return { distro: unc.distro, linuxPath: unc.linuxPath }\r\n const mnt = windowsToMntPath(key)\r\n if (mnt !== null) return { distro: undefined, linuxPath: mnt }\r\n throw new FsError(`wsl-fs: target \"${target.displayPath}\" is outside the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n override async lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise<FsPathInfo | undefined> {\r\n if (signal?.aborted) throw new FsError('lstat aborted', 'FS_ABORTED')\r\n if (path.trim().length === 0) throw new FsError('file_path must be a non-empty string', 'FS_NOT_FOUND')\r\n const { input, cwd } = this.translate(path, opts?.cwd)\r\n return super.lstat(input, { cwd }, signal)\r\n }\r\n}\r\n\r\nexport default WslFileSystem\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fs.js","names":[],"sources":["../src/fs.ts"],"sourcesContent":["/**\r\n * WSL Service Provider for the `ctx.fs` capability seam. Backed by the host\r\n * filesystem over the `\\\\wsl.localhost\\<distro>\\…` 9P share — zero install\r\n * inside the distribution — while every model/UI-facing path is the Linux\r\n * path a WSL process would open (`processPath`, `displayPath`, `fileUrl`).\r\n * Reuses `LocalFileSystem`'s mechanics (realpath identity, atomic writes,\r\n * per-target locks, version guards) unchanged, because those operate on the\r\n * UNC path Node can open directly.\r\n *\r\n * Both UNC paths and Linux absolute paths resolve; Windows drive paths\r\n * resolve through their `/mnt/<drive>` form, so a WSL-composed session can\r\n * still touch the Windows filesystem coherently.\r\n * @module dsh-wsl-workspace/fs\r\n */\r\n\r\nimport { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport { AsyncLocalStorage } from 'node:async_hooks'\r\nimport { link, lstat, rename } from 'node:fs/promises'\r\nimport { FsError } from '@deepseek-ai/dsh-fs'\r\nimport type { FsPathInfo, FsTarget } from '@deepseek-ai/dsh-fs'\r\nimport { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'\r\nimport {\r\n isAbsoluteLinuxPath,\r\n joinUnc,\r\n mntToWindowsPath,\r\n parseWslUnc,\r\n windowsToMntPath,\r\n} from './shared/paths.ts'\r\nimport { defaultDistroSync } from './shared/wsl.ts'\r\n\r\n/** Plugin config. `cwd`/`distro` are optional because UNC workdirs carry both. */\r\nexport interface Config {\r\n /** Base directory for relative paths without a per-call cwd (UNC or Linux). */\r\n cwd?: string\r\n /** Default distribution for Linux-absolute paths without a UNC cwd. */\r\n distro?: string\r\n /** Exclusive UTF-8 byte limit on each overwrite-diff side (see fs-local). */\r\n diffBasisMaxBytes?: number\r\n}\r\n\r\n/** One translated coordinate: the input the local backend opens plus its cwd. */\r\ninterface Translated {\r\n /** Absolute path to hand to the local backend (UNC or Windows drive). */\r\n input: string\r\n /** Absolute Windows-side base for relative inputs (UNC or Windows drive). */\r\n cwd: string\r\n}\r\n\r\ninterface ToolExecution {\r\n agent?: { session: { header: { cwd?: string } } }\r\n}\r\n\r\n/**\r\n * The WSL filesystem backend. Identity keys are canonical UNC paths; the\r\n * Linux form is derived on demand, so both worlds stay in sync across\r\n * aliases and symlinks.\r\n */\r\nexport class WslFileSystem extends LocalFileSystem {\r\n static override Config: z<Config> = z.object({\r\n cwd: z.string(),\r\n distro: z.string(),\r\n diffBasisMaxBytes: z.number().default(10 * 1024 * 1024),\r\n })\r\n\r\n private readonly distro: string | undefined\r\n private readonly executionCwd = new AsyncLocalStorage<string | undefined>()\r\n\r\n constructor(ctx: Context, config: Config) {\r\n // schemastery fills the defaults before construction; the parent validates\r\n // `diffBasisMaxBytes` and stores the resolved shape.\r\n super(ctx, config)\r\n this.distro = config.distro\r\n ctx.on('tools/execute', (exec: ToolExecution, next: () => unknown) => (\r\n this.executionCwd.run(exec.agent?.session.header.cwd, next)\r\n ))\r\n // The 9P/drvfs substrate has no hard links and no Win32 security semantics:\r\n // replace the atomic-publication boundaries the parent's fsio defaults to.\r\n this.internals = {\r\n linkFile: WslFileSystem.publishNoReplace,\r\n replaceFile: WslFileSystem.replaceOverWrite,\r\n copyFileDacl: WslFileSystem.skipDaclCopy,\r\n }\r\n }\r\n\r\n /**\r\n * No-replace publication for filesystems without hard links. A real\r\n * collision (a concurrent external creator won) must still surface as the\r\n * original EEXIST so the guarded-create failure path classifies it; an\r\n * absent target falls back to rename, which on Windows publishes without\r\n * replacing anything. Safe against this backend's own writers because the\r\n * per-target lock serializes them.\r\n * @param tempPath - the staged file.\r\n * @param destPath - the destination to create.\r\n */\r\n private static async publishNoReplace(tempPath: string, destPath: string): Promise<void> {\r\n try {\r\n await link(tempPath, destPath)\r\n return\r\n } catch (error) {\r\n let exists = false\r\n try {\r\n await lstat(destPath)\r\n exists = true\r\n } catch {\r\n // Absent destination: rename publishes the staged file.\r\n }\r\n if (exists) throw error\r\n await rename(tempPath, destPath)\r\n }\r\n }\r\n\r\n /**\r\n * Security-preserving replacement boundary: Windows rename replaces an\r\n * existing destination atomically; no DACL preservation is needed over 9P.\r\n * @param destPath - the file being replaced.\r\n * @param tempPath - the staged replacement.\r\n */\r\n private static async replaceOverWrite(destPath: string, tempPath: string): Promise<void> {\r\n await rename(tempPath, destPath)\r\n }\r\n\r\n /** 9P files inherit their directory's DACL; nothing to preserve. */\r\n private static async skipDaclCopy(): Promise<void> {}\r\n\r\n /** Translate a model/plugin path into Windows-side coordinates. */\r\n private translate(path: string, cwd?: string): Translated {\r\n const unc = parseWslUnc(path)\r\n if (unc !== null) {\r\n return { input: joinUnc(unc.distro, unc.linuxPath), cwd: this.cwdOr(cwd) }\r\n }\r\n if (isAbsoluteLinuxPath(path)) {\r\n // /mnt/<drive>/… names the Windows filesystem inside the Linux world\r\n // (the dual-access path for migration): open the drive path directly so\r\n // both worlds stay coherent — the display stays the /mnt form.\r\n const win = mntToWindowsPath(path)\r\n if (win !== null) return { input: win, cwd: this.cwdOr(cwd) }\r\n return { input: joinUnc(this.distroFor(cwd), path), cwd: this.cwdOr(cwd) }\r\n }\r\n if (windowsToMntPath(path) !== null) {\r\n // Windows drive paths open directly; the Linux world reaches them via /mnt.\r\n return { input: path, cwd: this.cwdOr(cwd) }\r\n }\r\n // Relative: resolve against the caller cwd (or the configured base).\r\n const base = this.uncCwd(cwd)\r\n return { input: path, cwd: base }\r\n }\r\n\r\n /** A base for absolute inputs (unused by resolution, but the parent needs one). */\r\n private cwdOr(cwd?: string): string {\r\n return cwd ?? this.executionCwd.getStore() ?? this.config.cwd ?? process.cwd()\r\n }\r\n\r\n private uncCwd(cwd?: string): string {\r\n const base = cwd ?? this.executionCwd.getStore() ?? this.config.cwd\r\n if (base === undefined || base === '') {\r\n throw new FsError('wsl-fs: no cwd and no configured base for relative resolution', 'FS_IO_ERROR')\r\n }\r\n const unc = parseWslUnc(base)\r\n if (unc !== null) return joinUnc(unc.distro, unc.linuxPath)\r\n if (isAbsoluteLinuxPath(base)) return joinUnc(this.distroFor(base), base)\r\n if (windowsToMntPath(base) !== null) return base\r\n throw new FsError(`wsl-fs: cwd \"${base}\" is not in the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n /**\r\n * Resolve the distribution an absolute Linux path opens inside. The chain:\r\n * the caller cwd when it is a WSL UNC path, then the current tool\r\n * execution's session cwd, then the configured `distro`,\r\n * then the host's default distribution from the Lxss registry. The registry\r\n * fallback is reserved for calls that genuinely have no session.\r\n */\r\n private distroFor(cwd?: string): string {\r\n const fromCwd = parseWslUnc(cwd ?? '')\r\n if (fromCwd !== null) return fromCwd.distro\r\n const fromExecution = parseWslUnc(this.executionCwd.getStore() ?? '')\r\n if (fromExecution !== null) return fromExecution.distro\r\n const distro = this.distro\r\n if (distro !== undefined && distro !== '') return distro\r\n const fallback = defaultDistroSync()\r\n if (fallback !== undefined) return fallback\r\n throw new FsError('wsl-fs: Linux path carries no distribution and none is configured', 'FS_IO_ERROR')\r\n }\r\n\r\n /** The Linux display path for a resolved Windows-side path. */\r\n private linuxDisplay(raw: string): string {\r\n const unc = parseWslUnc(raw)\r\n if (unc !== null) return unc.linuxPath\r\n const mnt = windowsToMntPath(raw)\r\n if (mnt !== null) return mnt\r\n throw new FsError(`wsl-fs: resolved path \"${raw}\" is outside the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n override async resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise<FsTarget> {\r\n if (opts?.signal?.aborted) throw new FsError('resolve aborted', 'FS_ABORTED')\r\n const { input, cwd } = this.translate(path, opts?.cwd)\r\n const local = await super.resolve(input, {\r\n cwd,\r\n ...opts?.signal !== undefined ? { signal: opts.signal } : {},\r\n })\r\n return { targetKey: local.targetKey, displayPath: this.linuxDisplay(String(local.displayPath)) }\r\n }\r\n\r\n override processPath(target: FsTarget): string {\r\n const key = String(target.targetKey)\r\n const unc = parseWslUnc(key)\r\n if (unc !== null) return unc.linuxPath\r\n const mnt = windowsToMntPath(key)\r\n if (mnt !== null) return mnt\r\n throw new FsError(`wsl-fs: target \"${target.displayPath}\" is outside the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n override fileUrl(target: FsTarget): string {\r\n const linux = this.processPath(target)\r\n const encoded = linux.split('/').map(encodeURIComponent).join('/')\r\n return `file://${encoded}`\r\n }\r\n\r\n override contains(parent: FsTarget, child: FsTarget): boolean {\r\n const parentWorld = this.worldPath(parent)\r\n const childWorld = this.worldPath(child)\r\n if (parentWorld.distro !== childWorld.distro) return false\r\n const parentPath = parentWorld.linuxPath\r\n const childPath = childWorld.linuxPath\r\n if (childPath === parentPath) return true\r\n return parentPath === '/' ? true : childPath.startsWith(`${parentPath}/`)\r\n }\r\n\r\n /** One target's (distro, linuxPath) pair for containment; `undefined` distro = Windows world. */\r\n private worldPath(target: FsTarget): { distro: string | undefined; linuxPath: string } {\r\n const key = String(target.targetKey)\r\n const unc = parseWslUnc(key)\r\n if (unc !== null) return { distro: unc.distro, linuxPath: unc.linuxPath }\r\n const mnt = windowsToMntPath(key)\r\n if (mnt !== null) return { distro: undefined, linuxPath: mnt }\r\n throw new FsError(`wsl-fs: target \"${target.displayPath}\" is outside the WSL execution world`, 'FS_IO_ERROR')\r\n }\r\n\r\n override async lstat(path: string, opts?: { cwd?: string }, signal?: AbortSignal): Promise<FsPathInfo | undefined> {\r\n if (signal?.aborted) throw new FsError('lstat aborted', 'FS_ABORTED')\r\n if (path.trim().length === 0) throw new FsError('file_path must be a non-empty string', 'FS_NOT_FOUND')\r\n const { input, cwd } = this.translate(path, opts?.cwd)\r\n return super.lstat(input, { cwd }, signal)\r\n }\r\n}\r\n\r\nexport default WslFileSystem\r\n"],"mappings":"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"}
|