dsh-wsl-workspace 0.4.2 → 0.4.3

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/lib/index.js CHANGED
@@ -68,6 +68,38 @@ function wslWorldGroup(shellPath, fsPath, includeEditor) {
68
68
  }
69
69
  /** The sentence appended to a standard-like persona when the variant runs in WSL. */
70
70
  const PERSONA_APPEND = " Your working directory {{cwd}} is inside a WSL (Windows Subsystem for Linux) distribution: the bash tool and the file read/write/edit tools use Linux paths, and the Windows filesystem is reachable as /mnt/<drive> for file migration.";
71
+ /** The upstream local-skill provider row, whose watcher cannot watch a UNC share. */
72
+ const SKILL_FILESYSTEM_ROW = "skill-filesystem";
73
+ /**
74
+ * Turn the local skill provider's watcher off inside a WSL variant.
75
+ *
76
+ * `@deepseek-ai/dsh-skill-filesystem` reports an INCOMPLETE observation when
77
+ * its watcher fails to start, and `dsh-tool-skill` withholds the whole catalog
78
+ * while a snapshot is incomplete. chokidar cannot watch
79
+ * `\\wsl.localhost\<distro>\...`, so in a WSL session the model never sees the
80
+ * catalog at all - `skill` still loads one by name, but nothing tells the model
81
+ * which skills exist. With the watcher off the discovery completes (the
82
+ * plugin's own provider rescans the share on demand, and the next session gets
83
+ * a fresh catalog); the price is no live refresh inside one running session,
84
+ * which never worked on this substrate anyway.
85
+ * @param block - the row's lines.
86
+ * @returns the row's lines with `watch: false` merged into its config.
87
+ */
88
+ function disableSkillWatch(block) {
89
+ const lines = [...block];
90
+ if (lines.some((line) => /^\s*watch:/.test(line))) return lines;
91
+ const configIndex = lines.findIndex((line) => /^\s*config:\s*$/.test(line));
92
+ if (configIndex >= 0) {
93
+ const childIndent = `${/^(\s*)/.exec(lines[configIndex] ?? "")?.[1] ?? " "} `;
94
+ lines.splice(configIndex + 1, 0, `${childIndent}watch: false`);
95
+ return lines;
96
+ }
97
+ const rowIndent = /^(\s*)/.exec(lines[0] ?? "")?.[1] ?? "";
98
+ let insertAt = lines.length;
99
+ while (insertAt > 0 && (lines[insertAt - 1] ?? "").trim() === "") insertAt -= 1;
100
+ lines.splice(insertAt, 0, `${rowIndent} config:`, `${rowIndent} watch: false`);
101
+ return lines;
102
+ }
71
103
  /** The top-level rows of one composition, as (startLine, endLineExclusive) spans. */
72
104
  function topLevelSpans(lines) {
73
105
  const spans = [];
@@ -89,35 +121,109 @@ function topLevelSpans(lines) {
89
121
  function spanId(lines, span) {
90
122
  return /^- id: ([A-Za-z0-9_.-]+)/.exec(lines[span.start] ?? "")?.[1];
91
123
  }
92
- /** Whether a top-level span is a `persona` row with an appendable folded text. */
93
- function appendablePersona(lines, span) {
94
- const block = lines.slice(span.start, span.end).join("\n");
95
- if (!block.includes("complete: true") && /text: [>|-]/.test(block)) {
96
- const textLine = block.split("\n").find((line) => /^(\s*)text: [>|-]/.test(line));
97
- if (textLine !== void 0) {
98
- const indent = /^(\s*)/.exec(textLine)?.[1]?.length ?? 0;
99
- return block.split("\n").some((line) => line.length > indent && /^\s+/.test(line) && !line.includes(":"));
100
- }
124
+ /** Persona config scalars that carry model-facing text, in append preference order. */
125
+ const PERSONA_TARGET_FIELDS = [
126
+ "suffix",
127
+ "text",
128
+ "prefix"
129
+ ];
130
+ /** Strip one layer of YAML quoting so a value can move into a block scalar. */
131
+ function unquoteScalar(value) {
132
+ const single = /^'(.*)'$/s.exec(value);
133
+ if (single !== null) return (single[1] ?? "").replace(/''/g, "'");
134
+ const double = /^"(.*)"$/s.exec(value);
135
+ return double !== null ? double[1] ?? "" : value;
136
+ }
137
+ /**
138
+ * Locate the persona scalar this transform amends.
139
+ *
140
+ * DSH moved the model-facing prompt text across releases: `text` up to
141
+ * v0.1.2-rc.1, and `prefix` + `suffix` from v0.1.3-alpha.2 on. `suffix` wins
142
+ * when both exist because it carries the working-directory sentence, so the
143
+ * appended note lands exactly where the legacy `text` block put it; `prefix`
144
+ * is the last resort for a composition that has neither. A known field with an
145
+ * empty value is not amendable.
146
+ * @param block - the persona row's lines.
147
+ * @returns the target scalar, or undefined when the row carries none.
148
+ */
149
+ function personaTarget(block) {
150
+ for (const field of PERSONA_TARGET_FIELDS) for (let index = 0; index < block.length; index += 1) {
151
+ const match = (/* @__PURE__ */ new RegExp(`^(\\s*)${field}:\\s*(.*)$`)).exec(block[index] ?? "");
152
+ if (match === null) continue;
153
+ const indent = match[1]?.length ?? 0;
154
+ const rest = (match[2] ?? "").trim();
155
+ if (rest === "" || /^[|>][+-]?$/.test(rest)) return {
156
+ index,
157
+ field,
158
+ indent,
159
+ header: rest,
160
+ inline: ""
161
+ };
162
+ return {
163
+ index,
164
+ field,
165
+ indent,
166
+ header: "",
167
+ inline: unquoteScalar(rest)
168
+ };
101
169
  }
102
- return false;
170
+ return void 0;
103
171
  }
104
- /** Append the WSL sentence to a persona row's folded text (in place of its last text line). */
105
- function appendPersona(lines, span) {
106
- const block = lines.slice(span.start, span.end);
107
- const textIndex = block.findIndex((line) => /^(\s*)text: [>|-]/.test(line));
108
- if (textIndex < 0) return [...block];
109
- const indent = /^(\s*)/.exec(block[textIndex] ?? "")?.[1]?.length ?? 0;
110
- let lastText = -1;
111
- for (let index = textIndex + 1; index < block.length; index++) {
172
+ /**
173
+ * Index of the last line belonging to the block scalar whose header is at
174
+ * `headerIndex`. A non-blank line at or above the header's indentation is the
175
+ * next sibling key and ends the scalar — without that stop a persona carrying
176
+ * both `suffix` and `prefix` blocks would take the note in the wrong one.
177
+ * @param block - the persona row's lines.
178
+ * @param headerIndex - index of the `field: >-` header line.
179
+ * @param indent - the header's own indentation.
180
+ * @returns the last content line's index, or -1 when the scalar is empty.
181
+ */
182
+ function lastBlockLine(block, headerIndex, indent) {
183
+ let last = -1;
184
+ for (let index = headerIndex + 1; index < block.length; index += 1) {
112
185
  const line = block[index] ?? "";
113
186
  if (line.trim() === "") continue;
114
- if (line.length > indent && /^\s+/.test(line)) lastText = index;
187
+ const lineIndent = /^(\s*)/.exec(line)?.[1]?.length ?? 0;
188
+ if (lineIndent <= indent) break;
189
+ last = index;
115
190
  }
116
- if (lastText < 0) return [...block];
117
- const updated = [...block];
118
- const textIndent = /^(\s*)/.exec(block[lastText] ?? "")?.[1] ?? " ";
119
- updated.splice(lastText + 1, 0, `${textIndent}${PERSONA_APPEND}`);
120
- return updated;
191
+ return last;
192
+ }
193
+ /** Whether a top-level span is a `persona` row this transform may amend. */
194
+ function appendablePersona(lines, span) {
195
+ const block = lines.slice(span.start, span.end);
196
+ if (block.join("\n").includes("complete: true")) return false;
197
+ const target = personaTarget(block);
198
+ if (target === void 0) return false;
199
+ return target.header === "" ? target.inline !== "" : lastBlockLine(block, target.index, target.indent) >= 0;
200
+ }
201
+ /**
202
+ * Append the WSL sentence to a persona row's model-facing scalar, in place.
203
+ *
204
+ * A block scalar takes the sentence as a sibling line, which is what the
205
+ * legacy `text: >-` form always did. An inline scalar cannot: it is folded into
206
+ * a block scalar first, so the sentence joins it the same way (and the model
207
+ * sees the same text it saw before v0.1.3-alpha.2).
208
+ * @param lines - the whole composition's lines.
209
+ * @param span - the persona row's span.
210
+ * @returns the persona row's lines, amended when a target was found.
211
+ */
212
+ function appendPersona(lines, span) {
213
+ const block = [...lines.slice(span.start, span.end)];
214
+ const target = personaTarget(block);
215
+ if (target === void 0) return block;
216
+ const pad = " ".repeat(target.indent);
217
+ if (target.header !== "") {
218
+ const last = lastBlockLine(block, target.index, target.indent);
219
+ if (last < 0) return block;
220
+ const textIndent = /^(\s*)/.exec(block[last] ?? "")?.[1] ?? `${pad} `;
221
+ block.splice(last + 1, 0, `${textIndent}${PERSONA_APPEND}`);
222
+ return block;
223
+ }
224
+ const child = `${pad} `;
225
+ block.splice(target.index, 1, `${pad}${target.field}: >-`, `${child}${target.inline}`, `${child}${PERSONA_APPEND.trim()}`);
226
+ return block;
121
227
  }
122
228
  /**
123
229
  * Transform one source preset composition into its WSL variant: drop the
@@ -148,6 +254,10 @@ function transformPresetForWsl(source, shellPath, fsPath) {
148
254
  continue;
149
255
  }
150
256
  if (WORLD_ROWS.has(id)) continue;
257
+ if (id === SKILL_FILESYSTEM_ROW) {
258
+ kept.push(...disableSkillWatch(lines.slice(span.start, span.end)));
259
+ continue;
260
+ }
151
261
  if (id === "persona" && !personaAppended && appendablePersona(lines, span)) {
152
262
  kept.push(...appendPersona(lines, span));
153
263
  personaAppended = true;
@@ -753,6 +863,35 @@ function listWslDir(distro, linuxPath) {
753
863
  entries
754
864
  };
755
865
  }
866
+ /** Cached self-description for the dialog's help panel. */
867
+ let selfDescription;
868
+ /**
869
+ * Read this plugin's own `package.json` for the dialog's help panel: the
870
+ * published version and the declared `dsh.compatibility.dshReleases` matrix.
871
+ * A plugin directory that cannot be read reports empty values rather than
872
+ * failing the dialog, and the result is cached for the process lifetime.
873
+ * @returns the self-description served by the `describe` method.
874
+ */
875
+ function describeSelf() {
876
+ if (selfDescription !== void 0) return selfDescription;
877
+ try {
878
+ const raw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
879
+ const parsed = JSON.parse(raw);
880
+ selfDescription = {
881
+ version: typeof parsed.version === "string" ? parsed.version : "unknown",
882
+ releases: Object.entries(parsed.dsh?.compatibility?.dshReleases ?? {}).map(([id, status]) => ({
883
+ id,
884
+ status: String(status)
885
+ }))
886
+ };
887
+ } catch {
888
+ selfDescription = {
889
+ version: "unknown",
890
+ releases: []
891
+ };
892
+ }
893
+ return selfDescription;
894
+ }
756
895
  /** Route one method dispatch. */
757
896
  async function dispatch(method, params) {
758
897
  switch (method) {
@@ -795,6 +934,7 @@ async function dispatch(method, params) {
795
934
  return null;
796
935
  }
797
936
  case "listWorkspaces": return listWorkspaceKeys();
937
+ case "describe": return describeSelf();
798
938
  case "setUser": {
799
939
  const path = requireWslUnc(params.path);
800
940
  const username = params.username;
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["spans: { start: number; end: number }[]","kept: string[]","nodeSkillIo: WslSkillIo","roots: SkillRoot[]","frontier: [string, number][]","next: [string, number][]","entries: Dirent[]","result: SkillRoot[]","dirents: Dirent[]","entries: SkillEntry[]","joinWindowsPath","raw: string","value","name","collected: string[]","indent: string | undefined","candidates: WslSkillCandidate[]","MODE_DISPLAY_LABELS: Readonly<Record<string, { en: string; zh: string }>>","chunks: Buffer[]","entries: WslDirEntryWire[]","kind: WslDirEntryWire['kind']","name","Config: z<Config>","body: Record<string, unknown>"],"sources":["../src/host/variants.ts","../src/host/wsl-skills.ts","../src/index.ts"],"sourcesContent":["/**\r\n * WSL preset-variant generator. For every healthy source preset the roster\r\n * supplies, a `wsl-<id>` variant is materialized under the roster's user\r\n * root: the source composition with its shell/filesystem world replaced by\r\n * the WSL providers, so any mode (standard, minimal, code, cordis, user\r\n * presets) can run on top of a WSL execution world. The execution world is\r\n * therefore orthogonal to the mode instead of a mode itself.\r\n *\r\n * The transformation is text-level on the top-level rows of the composition\r\n * (the shape all shipped presets share), with surgical edits for the known\r\n * special groups; unknown shapes are kept verbatim where possible.\r\n * @module dsh-wsl-workspace/host/variants\r\n */\r\n\r\n/** Top-level rows that name the execution world and are replaced by the variant's own. */\r\nconst WORLD_ROWS = new Set(['tool-bash', 'tool-pwsh', 'tool-fs', 'tool-fs-search', 'str-replace-editor', 'filesystem', 'persistent-shell', 'custom-bash', 'bootstrap-filesystem'])\r\n\r\n/** The injected WSL world group: providers + the bash/fs consumers, entry-local. */\r\nfunction wslWorldGroup(shellPath: string, fsPath: string, includeEditor: boolean): string {\r\n return [\r\n '# ── WSL execution world (dsh-wsl-workspace variant) ─────────────────────',\r\n '# The shell and fs services are provided entry-locally (the isolate',\r\n '# realm); host services (tools registry, shell-env, jobs) fall through.',\r\n '# tool-fs-search is intentionally absent: the packaged ripgrep runs on',\r\n '# the Windows host and cannot open Linux paths; WSL sessions search with',\r\n '# shell tools instead.',\r\n '- id: wsl-world',\r\n \" name: cordis:group\",\r\n ' group: true',\r\n ' isolate:',\r\n ' shell: true',\r\n ' fs: true',\r\n ' config:',\r\n ` - id: shell-wsl`,\r\n ` name: '${shellPath.replace(/'/g, \"''\")}'`,\r\n ' - id: fs-wsl',\r\n ` name: '${fsPath.replace(/'/g, \"''\")}'`,\r\n ' - id: tool-bash',\r\n \" name: '@deepseek-ai/dsh-tool-bash'\",\r\n ' - id: tool-fs',\r\n \" name: '@deepseek-ai/dsh-tool-fs'\",\r\n // The editor resolves through this entry-local WSL fs. Older editor builds\r\n // pass no cwd, so the provider inherits it from the current tool execution.\r\n // Anchored-family presets require this name during bootstrap.\r\n ...(includeEditor\r\n ? [\r\n ' - id: str-replace-editor',\r\n \" name: '@deepseek-ai/dsh-tool-str-replace-editor'\",\r\n ' config:',\r\n ' maxOutputChars: 16000',\r\n ]\r\n : []),\r\n '',\r\n ].join('\\n')\r\n}\r\n\r\n/** The sentence appended to a standard-like persona when the variant runs in WSL. */\r\nconst PERSONA_APPEND = ' Your working directory {{cwd}} is inside a WSL (Windows Subsystem for Linux) distribution: the bash tool and the file read/write/edit tools use Linux paths, and the Windows filesystem is reachable as /mnt/<drive> for file migration.'\r\n\r\n/** The top-level rows of one composition, as (startLine, endLineExclusive) spans. */\r\nfunction topLevelSpans(lines: readonly string[]): { start: number; end: number }[] {\r\n const spans: { start: number; end: number }[] = []\r\n let start = -1\r\n for (let index = 0; index < lines.length; index++) {\r\n if (lines[index]?.startsWith('- id: ') === true) {\r\n if (start >= 0) spans.push({ start, end: index })\r\n start = index\r\n }\r\n }\r\n if (start >= 0) spans.push({ start, end: lines.length })\r\n return spans\r\n}\r\n\r\n/** The row id of a top-level span, or undefined when the first line is malformed. */\r\nfunction spanId(lines: readonly string[], span: { start: number; end: number }): string | undefined {\r\n return /^- id: ([A-Za-z0-9_.-]+)/.exec(lines[span.start] ?? '')?.[1]\r\n}\r\n\r\n/** Whether a top-level span is a `persona` row with an appendable folded text. */\r\nfunction appendablePersona(lines: readonly string[], span: { start: number; end: number }): boolean {\r\n const block = lines.slice(span.start, span.end).join('\\n')\r\n if (!block.includes('complete: true') && /text: [>|-]/.test(block)) {\r\n // Append only when the folded text actually has content lines.\r\n const textLine = block.split('\\n').find(line => /^(\\s*)text: [>|-]/.test(line))\r\n if (textLine !== undefined) {\r\n const indent = /^(\\s*)/.exec(textLine)?.[1]?.length ?? 0\r\n return block.split('\\n').some(line => line.length > indent && /^\\s+/.test(line) && !line.includes(':'))\r\n }\r\n }\r\n return false\r\n}\r\n\r\n/** Append the WSL sentence to a persona row's folded text (in place of its last text line). */\r\nfunction appendPersona(lines: readonly string[], span: { start: number; end: number }): string[] {\r\n const block = lines.slice(span.start, span.end)\r\n const textIndex = block.findIndex(line => /^(\\s*)text: [>|-]/.test(line))\r\n if (textIndex < 0) return [...block]\r\n const indent = /^(\\s*)/.exec(block[textIndex] ?? '')?.[1]?.length ?? 0\r\n let lastText = -1\r\n for (let index = textIndex + 1; index < block.length; index++) {\r\n const line = block[index] ?? ''\r\n if (line.trim() === '') continue\r\n if (line.length > indent && /^\\s+/.test(line)) lastText = index\r\n }\r\n if (lastText < 0) return [...block]\r\n const updated = [...block]\r\n const textIndent = /^(\\s*)/.exec(block[lastText] ?? '')?.[1] ?? ' '\r\n updated.splice(lastText + 1, 0, `${textIndent}${PERSONA_APPEND}`)\r\n return updated\r\n}\r\n\r\n/**\r\n * Transform one source preset composition into its WSL variant: drop the\r\n * execution-world rows, keep everything else verbatim, and append the WSL\r\n * world group. The persistent-shell group is NOT re-added: it registers the\r\n * same `bash` tool name as the WSL world's `dsh-tool-bash`, and the tools\r\n * registry rejects duplicates within one preset layer — the whole variant\r\n * fails to mount and the session falls back to another preset. Its PTY\r\n * backend additionally cannot run on this plugin's Windows host\r\n * (`dsh-subprocess-local`: \"terminal inspection is unsupported on platform\r\n * win32\"), so the group could never spawn a shell here anyway. The WSL\r\n * world's ordinary `bash` tool covers command execution for every variant.\r\n * @param source - the source composition text.\r\n * @param shellPath - absolute path of the plugin's built WSL shell provider.\r\n * @param fsPath - absolute path of the plugin's built WSL fs provider.\r\n * @returns the variant composition text.\r\n */\r\nexport function transformPresetForWsl(source: string, shellPath: string, fsPath: string): string {\r\n const lines = source.split('\\n')\r\n const spans = topLevelSpans(lines)\r\n const kept: string[] = []\r\n let sawEditor = false\r\n let personaAppended = false\r\n for (const span of spans) {\r\n const id = spanId(lines, span)\r\n if (id === undefined) {\r\n kept.push(...lines.slice(span.start, span.end))\r\n continue\r\n }\r\n if (WORLD_ROWS.has(id)) continue\r\n if (id === 'persona' && !personaAppended && appendablePersona(lines, span)) {\r\n kept.push(...appendPersona(lines, span))\r\n personaAppended = true\r\n continue\r\n }\r\n kept.push(...lines.slice(span.start, span.end))\r\n if (id === 'str-replace-editor') sawEditor = true\r\n }\r\n if (source.includes('str-replace-editor')) sawEditor = true\r\n const result = [...kept]\r\n if (result.length > 0 && result[result.length - 1] !== '') result.push('')\r\n result.push(wslWorldGroup(shellPath, fsPath, sawEditor))\r\n return result.join('\\n').replace(/\\n{3,}/g, '\\n\\n').replace(/\\n+$/, '\\n')\r\n}\r\n\r\n/** Whether an id is one of this plugin's own preset directories. */\r\nexport function isWslVariantId(id: string): boolean {\r\n return id === 'wsl' || /^wsl-[a-z0-9-]+$/.test(id)\r\n}\r\n\r\n/** The variant id for one source preset id. */\r\nexport function variantIdFor(sourceId: string): string {\r\n return `wsl-${sourceId.toLowerCase()}`\r\n}\r\n","/**\r\n * WSL workspace skill provider (host half).\r\n *\r\n * DSH's shipped skill-filesystem provider scans only the session cwd's\r\n * project root (the nearest `.git` ancestor) for `.dsh/skills` / `.agents/skills`\r\n * and never descends into nested projects. A WSL workspace whose project\r\n * folders live below the registered workspace root therefore shows an empty\r\n * skill catalog, even though the same layout works when the session cwd is\r\n * the project folder itself (issue #10).\r\n *\r\n * This provider mirrors the host's discovery rules for WSL UNC session\r\n * workspaces: it starts at the session cwd's nearest `.git` ancestor (the\r\n * host's project-root rule; the cwd itself when no ancestor has a `.git`\r\n * marker), then walks that root (depth- and budget-bounded). Directory\r\n * symlinks are followed when the substrate resolves them and pruned safely\r\n * when it does not — the `\\\\wsl.localhost` 9P share cannot resolve Linux\r\n * symlink targets, so linked-in projects stay undiscoverable there today.\r\n * The walk collects every\r\n * `.dsh/skills` and `.agents/skills` directory it finds — including nested\r\n * projects — and publishes their skills with the same\r\n * project ranks and sources the host uses, so precedence and duplicate\r\n * resolution behave identically. Non-WSL lookups return nothing and leave\r\n * the host's own providers untouched.\r\n *\r\n * All filesystem reads go through `node:fs` against the `\\\\wsl.localhost\\…`\r\n * 9P share (the same substrate `WslFileSystem` uses); an injectable IO face\r\n * keeps the discovery logic unit-testable without a live distro.\r\n *\r\n * @module dsh-wsl-workspace/host/wsl-skills\r\n */\r\n\r\nimport { readdir, readFile, stat } from 'node:fs/promises'\r\nimport type { Dirent } from 'node:fs'\r\nimport { join as joinWindowsPath, posix } from 'node:path'\r\nimport { joinUnc, parseWslUnc } from '../shared/paths.ts'\r\n\r\n/** Project ranks copied from @deepseek-ai/dsh-skill-filesystem so WSL and host entries interleave identically. */\r\nconst PROJECT_DSH_RANK = 100\r\nconst PROJECT_AGENTS_RANK = 200\r\n\r\n/** How many directory levels below the workspace root are scanned. */\r\nconst MAX_SCAN_DEPTH = 4\r\n/** Maximum distinct skill directories published per lookup. */\r\nconst MAX_SKILL_ROOTS = 64\r\n/** Maximum directories visited per lookup (an absolute blast-radius cap). */\r\nconst MAX_VISITED_DIRECTORIES = 4096\r\n/** How many parent levels above the session cwd are searched for a `.git` project marker. */\r\nconst MAX_ANCESTOR_WALK = 64\r\n/** How long a completed lookup is served from cache before the next rescan. */\r\nconst CACHE_TTL_MS = 10_000\r\n/** Maximum cached lookups (one entry per distinct scan root across sessions). */\r\nconst CACHE_MAX_ENTRIES = 32\r\n\r\n/** Kebab-case skill names, matching the host grammar. */\r\nconst SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\r\n\r\n/** Directory names that never contain project skill roots (safe to prune while walking). */\r\nconst PRUNED_DIRECTORY_NAMES = new Set([\r\n '.git', '.hg', '.svn', '.bzr', 'node_modules', '.venv', 'venv', '.tox',\r\n '.pants.d', '.next', '.nuxt', 'dist', 'build', 'out', 'coverage',\r\n '__pycache__', '.mypy_cache', '.pytest_cache', '.ruff_cache', '.cache',\r\n '.idea', '.vscode', '.serverless', '.terraform', '.yarn', '.pnpm-store',\r\n])\r\n\r\n/** One `name: value` frontmatter line pair the parser understands. */\r\ninterface ParsedSkill {\r\n name: string\r\n description: string\r\n whenToUse?: string\r\n invocation: { modelInvocable: boolean; userInvocable: boolean }\r\n content: string\r\n}\r\n\r\n/** The provider's minimal skill-candidate contract (mirrors @deepseek-ai/dsh-skill). */\r\nexport interface WslSkillCandidate {\r\n readonly name: string\r\n readonly description: string\r\n readonly whenToUse?: string\r\n readonly invocation: { modelInvocable: boolean; userInvocable: boolean }\r\n readonly source: string\r\n readonly provider: string\r\n readonly rank: number\r\n readonly locator: { path: string; directory: string }\r\n readonly path: string\r\n}\r\n\r\n/** The provider's minimal skill-definition contract (candidate plus body). */\r\nexport interface WslSkillDefinition extends WslSkillCandidate {\r\n readonly content: string\r\n}\r\n\r\n/** Lookup options the registry passes to `list`/`get`. */\r\nexport interface WslSkillLookupOptions {\r\n readonly cwd?: string\r\n readonly signal?: AbortSignal\r\n}\r\n\r\n/** Registration-scoped lifecycle face passed to the provider constructor. */\r\nexport interface WslSkillProviderControl {\r\n readonly signal: AbortSignal\r\n readonly invalidate: () => void\r\n}\r\n\r\n/** The `ctx.skills` registry face this provider registers on (optional service). */\r\nexport interface WslSkillsRegistryFace {\r\n registerProvider(create: (control: WslSkillProviderControl) => {\r\n readonly name: string\r\n list(options: WslSkillLookupOptions): Promise<unknown>\r\n get(candidate: WslSkillCandidate, options: WslSkillLookupOptions): Promise<unknown>\r\n }): () => void\r\n}\r\n\r\n/** Injectable filesystem face (defaults to node:fs/promises on the real 9P share). */\r\nexport interface WslSkillIo {\r\n readdir(path: string, options: { withFileTypes: true }): Promise<Dirent[]>\r\n readFile(path: string, options: { encoding: 'utf8' }): Promise<string>\r\n stat(path: string): Promise<{ isDirectory(): boolean }>\r\n}\r\n\r\n/** The node:fs/promises implementation the provider uses in production. */\r\nexport const nodeSkillIo: WslSkillIo = {\r\n readdir: async (path, options) => readdir(path, options),\r\n readFile: async (path, options) => readFile(path, options),\r\n stat: async path => stat(path),\r\n}\r\n\r\n/** One discovered skill directory under a WSL workspace. */\r\ninterface SkillRoot {\r\n /** Absolute UNC path of the skills directory (`…\\.dsh\\skills`). */\r\n path: string\r\n /** Host source label ('project-dsh' | 'project-agents'). */\r\n source: 'project-dsh' | 'project-agents'\r\n /** Host project rank so same-name wins and precedence stay consistent. */\r\n rank: number\r\n}\r\n\r\n/** Whether a skills-directory entry is a directory-bundle or a flat markdown skill. */\r\ninterface SkillEntry {\r\n name: string\r\n kind: 'bundle' | 'flat'\r\n path: string\r\n}\r\n\r\n/**\r\n * Locate the nearest ancestor of `linuxDir` (the directory itself included)\r\n * containing a `.git` marker, mirroring the host skill-filesystem's\r\n * project-root rule. `.git` may be a directory or a worktree pointer file;\r\n * existence is enough. Bounded so a pathological path cannot spin the walk.\r\n * @param distro - the WSL distribution name.\r\n * @param linuxDir - the session cwd's absolute Linux path.\r\n * @param io - filesystem face.\r\n * @returns the project root's Linux path, or `undefined` when no ancestor carries a `.git`.\r\n */\r\nasync function nearestGitAncestor(distro: string, linuxDir: string, io: WslSkillIo): Promise<string | undefined> {\r\n let current = linuxDir\r\n for (let levels = 0; levels <= MAX_ANCESTOR_WALK; levels += 1) {\r\n try {\r\n await io.stat(joinUnc(distro, posix.join(current, '.git')))\r\n return current\r\n } catch {\r\n // No `.git` marker at this level; keep walking towards the filesystem root.\r\n }\r\n const parent = posix.dirname(current)\r\n if (parent === current) return undefined\r\n current = parent\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * Scan a WSL workspace root for nested skill directories.\r\n * @param distro - the WSL distribution name.\r\n * @param linuxRoot - the workspace's absolute Linux path.\r\n * @param io - filesystem face.\r\n * @returns discovered skill directories, bounded by depth and budget.\r\n */\r\nasync function discoverSkillRoots(distro: string, linuxRoot: string, io: WslSkillIo): Promise<SkillRoot[]> {\r\n const roots: SkillRoot[] = []\r\n const visited = new Set<string>()\r\n // BFS layers so the budget prunes the widest, most redundant levels first\r\n // (shallow skill dirs matter most): [path, depth] pairs.\r\n let frontier: [string, number][] = [[linuxRoot, 0]]\r\n while (frontier.length > 0 && roots.length < MAX_SKILL_ROOTS) {\r\n const next: [string, number][] = []\r\n for (const [dir, depth] of frontier) {\r\n if (visited.size >= MAX_VISITED_DIRECTORIES) return roots\r\n if (visited.has(dir)) continue\r\n visited.add(dir)\r\n if (roots.length < MAX_SKILL_ROOTS) {\r\n const directoryRoots = await skillRootsOfDirectory(distro, dir, io)\r\n roots.push(...directoryRoots.slice(0, MAX_SKILL_ROOTS - roots.length))\r\n }\r\n if (depth >= MAX_SCAN_DEPTH) continue\r\n let entries: Dirent[]\r\n try {\r\n entries = await io.readdir(joinUnc(distro, dir), { withFileTypes: true })\r\n } catch {\r\n // An unreadable directory (permissions, vanished mid-walk) prunes its subtree.\r\n continue\r\n }\r\n for (const entry of entries) {\r\n if (PRUNED_DIRECTORY_NAMES.has(entry.name)) continue\r\n if (entry.name.startsWith('.') && entry.name !== '.dsh' && entry.name !== '.agents') continue\r\n if (entry.name === '.dsh' || entry.name === '.agents') continue\r\n const childPath = posix.join(dir, entry.name)\r\n if (entry.isDirectory()) {\r\n next.push([childPath, depth + 1])\r\n continue\r\n }\r\n if (entry.isSymbolicLink()) {\r\n // A project may be linked into the workspace via a directory\r\n // symlink; follow it when the target is a directory. Symlink\r\n // cycles stay bounded: every hop increments the depth (capped by\r\n // MAX_SCAN_DEPTH) and the walk as a whole by MAX_VISITED_DIRECTORIES.\r\n try {\r\n const target = await io.stat(joinUnc(distro, childPath))\r\n if (target.isDirectory()) next.push([childPath, depth + 1])\r\n } catch {\r\n // Dangling symlink: nothing to traverse.\r\n }\r\n }\r\n }\r\n }\r\n frontier = next\r\n }\r\n return roots\r\n}\r\n\r\n/**\r\n * Publish the skill roots of one scanned directory (its `.dsh/skills` and\r\n * `.agents/skills`, each with the host's project ranks).\r\n * @param distro - the WSL distribution name.\r\n * @param linuxDir - the scanned directory's Linux path.\r\n * @param io - filesystem face.\r\n * @returns the directory's skill roots that exist.\r\n */\r\nasync function skillRootsOfDirectory(distro: string, linuxDir: string, io: WslSkillIo): Promise<SkillRoot[]> {\r\n const result: SkillRoot[] = []\r\n for (const [marker, source, rank] of [\r\n ['.dsh', 'project-dsh', PROJECT_DSH_RANK],\r\n ['.agents', 'project-agents', PROJECT_AGENTS_RANK],\r\n ] as const) {\r\n const path = joinUnc(distro, posix.join(linuxDir, marker, 'skills'))\r\n try {\r\n const info = await io.stat(path)\r\n if (info.isDirectory()) result.push({ path, source, rank })\r\n } catch {\r\n // Absent skills directory: nothing to publish.\r\n }\r\n }\r\n return result\r\n}\r\n\r\n/** List one skills directory's entries (directory bundles and flat `.md` skills). */\r\nasync function listSkillEntries(root: SkillRoot, io: WslSkillIo): Promise<SkillEntry[]> {\r\n let dirents: Dirent[]\r\n try {\r\n dirents = await io.readdir(root.path, { withFileTypes: true })\r\n } catch {\r\n return []\r\n }\r\n const entries: SkillEntry[] = []\r\n for (const entry of dirents) {\r\n if (entry.isDirectory()) {\r\n entries.push({ name: entry.name, kind: 'bundle', path: joinWindowsPath(root.path, entry.name, 'SKILL.md') })\r\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\r\n entries.push({ name: entry.name.slice(0, -3), kind: 'flat', path: joinWindowsPath(root.path, entry.name) })\r\n }\r\n }\r\n return entries.sort((a, b) => a.name.localeCompare(b.name))\r\n}\r\n\r\n/** Read and parse one skill file; `undefined` when missing or unparsable. */\r\nasync function readSkill(path: string, io: WslSkillIo, signal?: AbortSignal): Promise<ParsedSkill | undefined> {\r\n signal?.throwIfAborted()\r\n let raw: string\r\n try {\r\n raw = await io.readFile(path, { encoding: 'utf8' })\r\n } catch {\r\n return undefined\r\n }\r\n signal?.throwIfAborted()\r\n return parseSkillFrontmatter(raw, path)\r\n}\r\n\r\n/**\r\n * Parse the frontmatter subset skill files use: `---` fenced YAML with\r\n * `name` / `description` / `whenToUse` / `user-invocable` /\r\n * `disable-model-invocation`. Single-line scalars and block scalars\r\n * (`|` literal, `>` folded) are understood; anything else is skipped,\r\n * matching the shipped provider's leniency: a bad file must not fail\r\n * the catalog.\r\n */\r\nfunction parseSkillFrontmatter(raw: string, path: string): ParsedSkill | undefined {\r\n // Windows editors save UTF-8 with a BOM; a leading BOM must not make the\r\n // opening `---` line unmatchable and silently drop the skill.\r\n if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1)\r\n const firstLineEnd = raw.indexOf('\\n')\r\n if (firstLineEnd < 0) return undefined\r\n if (raw.slice(0, firstLineEnd).replace(/\\r$/, '') !== '---') return undefined\r\n const start = firstLineEnd + 1\r\n const closing = findFrontmatterEnd(raw, start)\r\n if (closing === undefined) return undefined\r\n const lines = raw.slice(start, closing).split('\\n')\r\n const fields = new Map<string, string>()\r\n for (let index = 0; index < lines.length; index += 1) {\r\n const line = (lines[index] ?? '').replace(/\\r$/, '')\r\n const block = /^([A-Za-z0-9-]+):\\s*([|>])[+-]?\\s*$/.exec(line)\r\n if (block !== null) {\r\n const value = parseBlockScalar(lines, index, block[2] === '>')\r\n index = value.nextLineIndex\r\n if (value.text !== '') fields.set(block[1] ?? '', value.text)\r\n continue\r\n }\r\n const match = /^([A-Za-z0-9-]+):\\s*(.*)$/.exec(line)\r\n if (match === null) continue\r\n const value = match[2]?.trim() ?? ''\r\n if (value !== '') fields.set(match[1] ?? '', unquote(value))\r\n }\r\n const name = fields.get('name') ?? ''\r\n const description = fields.get('description') ?? ''\r\n if (!SKILL_NAME.test(name) || description === '') {\r\n return undefined\r\n }\r\n const whenToUse = fields.get('whenToUse')\r\n return {\r\n name,\r\n description,\r\n ...whenToUse !== undefined && whenToUse !== '' ? { whenToUse } : {},\r\n invocation: {\r\n modelInvocable: !frontmatterBoolean(fields, 'disable-model-invocation'),\r\n userInvocable: frontmatterBoolean(fields, 'user-invocable', true),\r\n },\r\n // `findFrontmatterEnd` returns the index of the body's FIRST character\r\n // (the newline after the closing `---` plus one), so slicing at `closing`\r\n // is what keeps it: `closing + 1` dropped the body's first character and\r\n // made the byte after the delimiter look like the body.\r\n content: raw.slice(closing).trim(),\r\n }\r\n}\r\n\r\n/**\r\n * Collect a YAML block scalar (`key: |` literal or `key: >` folded) starting\r\n * at `startIndex`'s following lines. The block runs until the first\r\n * non-indented, non-blank line; its common indentation is stripped.\r\n * @returns the scalar text and the index of the last consumed line.\r\n */\r\nfunction parseBlockScalar(\r\n lines: string[],\r\n startIndex: number,\r\n folded: boolean,\r\n): { text: string; nextLineIndex: number } {\r\n const collected: string[] = []\r\n let indent: string | undefined\r\n let index = startIndex\r\n while (index + 1 < lines.length) {\r\n index += 1\r\n const next = (lines[index] ?? '').replace(/\\r$/, '')\r\n if (next.trim() === '') {\r\n collected.push('')\r\n continue\r\n }\r\n const indented = /^([ \\t]+)(.*)$/.exec(next)\r\n if (indented === null) {\r\n index -= 1\r\n break\r\n }\r\n indent ??= indented[1]\r\n collected.push(indented[1]?.startsWith(indent) === true ? indented[2] : indented[1].replace(/^[ \\t]+/, '') + indented[2])\r\n }\r\n while (collected.length > 0 && collected[collected.length - 1] === '') collected.pop()\r\n const text = (folded ? collected.filter(line => line !== '').join(' ') : collected.join('\\n')).trim()\r\n return { text, nextLineIndex: index }\r\n}\r\n\r\n/** Locate the closing `---` line of a frontmatter block. */\r\nfunction findFrontmatterEnd(raw: string, start: number): number | undefined {\r\n let lineStart = start\r\n while (lineStart <= raw.length) {\r\n const nextNewline = raw.indexOf('\\n', lineStart)\r\n const lineEnd = nextNewline < 0 ? raw.length : nextNewline\r\n if (raw.slice(lineStart, lineEnd).replace(/\\r$/, '') === '---') return lineEnd + 1\r\n if (nextNewline < 0) return undefined\r\n lineStart = nextNewline + 1\r\n }\r\n return undefined\r\n}\r\n\r\n/** Strip one level of matching quotes from a scalar value. */\r\nfunction unquote(value: string): string {\r\n if (value.length >= 2) {\r\n const first = value[0]\r\n const last = value[value.length - 1]\r\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\r\n return value.slice(1, -1)\r\n }\r\n }\r\n return value\r\n}\r\n\r\n/** Boolean semantics for `user-invocable` / `disable-model-invocation` (matches the host parser). */\r\nfunction frontmatterBoolean(fields: Map<string, string>, key: string, dflt = false): boolean {\r\n const value = fields.get(key)\r\n if (value === undefined) return dflt\r\n switch (value.toLowerCase()) {\r\n case 'true':\r\n case 'yes':\r\n case 'on':\r\n case '1':\r\n return true\r\n case 'false':\r\n case 'no':\r\n case 'off':\r\n case '0':\r\n return false\r\n default:\r\n return dflt\r\n }\r\n}\r\n\r\n/**\r\n * The WSL workspace skill provider. Registered on the host's `ctx.skills`\r\n * registry; serves only lookups whose cwd is a WSL UNC workspace path.\r\n *\r\n * Completed `list()` lookups are cached per scan root for CACHE_TTL_MS so\r\n * repeated catalog builds over the slow 9P share do not rescan the tree;\r\n * `get()` always re-reads the skill file so body edits are picked up\r\n * immediately. `control.invalidate` remains reserved for host-driven\r\n * invalidation; the provider self-invalidates through the TTL.\r\n */\r\nexport class WslSkillsProvider {\r\n readonly name = 'wsl-workspace'\r\n private readonly control: WslSkillProviderControl\r\n private readonly io: WslSkillIo\r\n private readonly now: () => number\r\n private readonly cache = new Map<string, { expiresAt: number; candidates: WslSkillCandidate[] }>()\r\n\r\n constructor(control: WslSkillProviderControl, io: WslSkillIo = nodeSkillIo, now: () => number = Date.now) {\r\n this.control = control\r\n this.io = io\r\n this.now = now\r\n }\r\n\r\n /**\r\n * Discover nested project skills for a WSL UNC session workspace.\r\n * @param options - lookup options; `cwd` selects the WSL workspace.\r\n * @returns candidates for every `.dsh/skills` / `.agents/skills` under the\r\n * session's scan root — the nearest `.git` ancestor of the cwd, else the\r\n * cwd itself — or an empty array for non-WSL lookups.\r\n */\r\n async list(options: WslSkillLookupOptions): Promise<WslSkillCandidate[]> {\r\n this.control.signal.throwIfAborted()\r\n options.signal?.throwIfAborted()\r\n const unc = options.cwd === undefined ? null : parseWslUnc(options.cwd)\r\n if (unc === null) return []\r\n // Host parity: the session's project root is the nearest `.git` ancestor\r\n // of the cwd, so lookups from inside a project subtree still see that\r\n // project's skills; nested projects below it join via the bounded BFS.\r\n // Without a `.git` ancestor the session cwd itself is the scan root (the\r\n // issue #10 workspace layout).\r\n const scanRoot = (await nearestGitAncestor(unc.distro, unc.linuxPath, this.io)) ?? unc.linuxPath\r\n const cacheKey = `${unc.distro}\\u0000${scanRoot}`\r\n const cached = this.cache.get(cacheKey)\r\n if (cached !== undefined && cached.expiresAt > this.now()) {\r\n this.cache.delete(cacheKey)\r\n this.cache.set(cacheKey, cached)\r\n return [...cached.candidates]\r\n }\r\n const roots = await discoverSkillRoots(unc.distro, scanRoot, this.io)\r\n const candidates: WslSkillCandidate[] = []\r\n const seenSkills = new Set<string>()\r\n for (const root of roots) {\r\n const entries = await listSkillEntries(root, this.io)\r\n for (const entry of entries) {\r\n options.signal?.throwIfAborted()\r\n const parsed = await readSkill(entry.path, this.io, options.signal)\r\n if (parsed === undefined) continue\r\n // A project reachable through both its real path and a directory\r\n // symlink yields aliasing roots whose locators differ; publish each\r\n // distinct (name, body) once so the catalog shows no duplicates.\r\n const fingerprint = `${parsed.name}\\u0000${parsed.content}`\r\n if (seenSkills.has(fingerprint)) continue\r\n seenSkills.add(fingerprint)\r\n candidates.push({\r\n name: parsed.name,\r\n description: parsed.description,\r\n ...parsed.whenToUse !== undefined ? { whenToUse: parsed.whenToUse } : {},\r\n invocation: parsed.invocation,\r\n source: root.source,\r\n provider: this.name,\r\n rank: root.rank,\r\n locator: {\r\n path: entry.path,\r\n directory: entry.kind === 'bundle'\r\n ? joinWindowsPath(entry.path, '..')\r\n : root.path,\r\n },\r\n path: entry.path,\r\n })\r\n }\r\n }\r\n this.cache.set(cacheKey, { expiresAt: this.now() + CACHE_TTL_MS, candidates })\r\n while (this.cache.size > CACHE_MAX_ENTRIES) {\r\n const oldest = this.cache.keys().next().value\r\n if (oldest === undefined) break\r\n this.cache.delete(oldest)\r\n }\r\n return candidates\r\n }\r\n\r\n /**\r\n * Load a complete skill body for a previously listed candidate.\r\n * @param candidate - the candidate this provider returned.\r\n * @param options - lookup options whose signal cancels the read.\r\n * @returns the full skill, or `undefined` if the file disappeared.\r\n */\r\n async get(candidate: WslSkillCandidate, options: WslSkillLookupOptions): Promise<WslSkillDefinition | undefined> {\r\n this.control.signal.throwIfAborted()\r\n const parsed = await readSkill(candidate.locator.path, this.io, options.signal)\r\n if (parsed === undefined || parsed.name !== candidate.name) return undefined\r\n return {\r\n name: parsed.name,\r\n description: parsed.description,\r\n ...parsed.whenToUse !== undefined ? { whenToUse: parsed.whenToUse } : {},\r\n invocation: parsed.invocation,\r\n source: candidate.source,\r\n provider: candidate.provider,\r\n rank: candidate.rank,\r\n locator: candidate.locator,\r\n path: candidate.path,\r\n content: parsed.content,\r\n }\r\n }\r\n}","/**\r\n * Host half of dsh-wsl-workspace. Three responsibilities:\r\n *\r\n * 1. Materialize a `wsl-<mode>` variant for every healthy roster preset\r\n * under `<dshHome>/.agent-presets/` (the roster's auto-scanned user\r\n * root), so the WSL execution world — `shell-wsl` + `fs-wsl` behind one\r\n * entry-local realm, with `tool-bash`/`tool-fs` consumers — composes with\r\n * ANY mode instead of being a mode itself; the legacy standalone `wsl`\r\n * preset directory and stale variants are removed on boot. The preset\r\n * rows name THIS package's built lib files by absolute path, which the\r\n * preset mount resolves to `file:` URLs without relying on bare specifier\r\n * resolution from the preset's home directory.\r\n *\r\n * 2. Serve the browser dialog's data route (`/wsl-workspace/api`):\r\n * distribution discovery, one-level directory listing, path checks, and\r\n * the per-workspace username store — all over the 9P UNC share.\r\n * Loopback-only, matching the sensitivity of the privileged configuration\r\n * surface.\r\n *\r\n * 3. Contribute the per-session `DSH_WSL_DISTRO` managed-env fact so the WSL\r\n * shell executor can resolve a plain Linux `workdir` to the calling\r\n * session's distribution.\r\n * @module dsh-wsl-workspace\r\n */\r\n\r\nimport { Context } from '@deepseek-ai/cordis'\r\nimport z from '@deepseek-ai/schemastery'\r\nimport { cpSync, existsSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'\r\nimport { fileURLToPath } from 'node:url'\r\nimport { dirname, join } from 'node:path'\r\nimport type { IncomingMessage, ServerResponse } from 'node:http'\r\nimport { homedir } from 'node:os'\r\nimport { joinUnc, mntToWindowsPath, normalizeLinuxPath, isAbsoluteLinuxPath, isValidWslUsername, parseWslUnc } from './shared/paths.ts'\r\nimport { canonicalWslUnc, getWindowsWorkspace, getWorkspaceUsername, listWorkspaceKeys, registerWindowsWorkspace, setWorkspaceUsername } from './shared/wsl-credentials.ts'\r\nimport { defaultDistro, listDistros } from './shared/wsl.ts'\r\nimport { isWslVariantId, transformPresetForWsl, variantIdFor } from './host/variants.ts'\r\nimport { WslSkillsProvider, type WslSkillsRegistryFace } from './host/wsl-skills.ts'\r\n\r\n/** The HTTP route this plugin serves (a relative, same-origin path). */\r\nexport const DEFAULT_ROUTE = '/wsl-workspace/api'\r\n\r\n/**\r\n * Bilingual display labels for the shipped source modes, matching the app's\r\n * own built-in copy in each language — note the `code` preset is \"PTC 模式\"\r\n * in the Chinese copy but \"Code mode\" in English. The DSH picker localizes\r\n * only the four built-in ids itself; `wsl-*` variant ids render the\r\n * preset.yml text verbatim, so the plugin writes one bilingual string so\r\n * both locales can identify each variant. Custom presets keep their own\r\n * name.\r\n */\r\nconst MODE_DISPLAY_LABELS: Readonly<Record<string, { en: string; zh: string }>> = {\r\n standard: { en: 'Standard mode', zh: '标准模式' },\r\n code: { en: 'Code mode', zh: 'PTC 模式' },\r\n minimal: { en: 'Minimal mode', zh: '极简模式' },\r\n cordis: { en: 'Creator mode', zh: '创造模式' },\r\n}\r\n\r\n/**\r\n * Quote a value as a single-line YAML single-quoted scalar. Plain scalars\r\n * cannot contain `: ` (colon + space), which plain English sentences do —\r\n * written unquoted they make the whole preset.yml unparsable, dropping the\r\n * name, description and order together.\r\n */\r\nfunction yamlScalar(value: string): string {\r\n return `'${value.replace(/'/g, \"''\")}'`\r\n}\r\n\r\n/** The variant name for one shipped mode (bilingual) or a custom preset. */\r\nfunction variantName(presetId: string, sourceName: string): string {\r\n const labels = MODE_DISPLAY_LABELS[presetId]\r\n return labels === undefined ? `WSL · ${sourceName}` : `WSL · ${labels.en}(${labels.zh})`\r\n}\r\n\r\n/** The variant description for one shipped mode (bilingual) or a custom preset. */\r\nfunction variantDescription(presetId: string): string {\r\n const labels = MODE_DISPLAY_LABELS[presetId]\r\n const display = labels === undefined ? presetId : `${labels.en}(${labels.zh})`\r\n return `WSL execution world for ${display}: bash and file tools run inside the WSL distribution.`\r\n}\r\n\r\n/** Plugin config. */\r\nexport interface Config {\r\n /** The route under which the dialog data API is served. */\r\n route?: string\r\n}\r\n\r\n/** The shape after schemastery applied the defaults. */\r\ntype ResolvedConfig = Required<Config>\r\n\r\n/** The `webServer.register` route contract this plugin consumes. */\r\ninterface WebServerRoute {\r\n kind: 'exact'\r\n path: string\r\n handler(req: IncomingMessage, res: ServerResponse): Promise<void>\r\n}\r\n\r\ninterface WebServerService {\r\n register(route: WebServerRoute): () => void\r\n}\r\n\r\n/** The `ctx.shellEnv` registry face this plugin consumes (optional service). */\r\ninterface ShellEnvService {\r\n register(contributor: {\r\n name: string\r\n variables: Readonly<Record<string, { description: string }>>\r\n resolve(execution: {\r\n agent?: { session: { header: { cwd?: string } } }\r\n }): Readonly<Partial<Record<string, string>>>\r\n }): () => void\r\n}\r\n\r\n/** One directory entry the dialog lists. */\r\ninterface WslDirEntryWire {\r\n name: string\r\n kind: 'directory' | 'file' | 'other'\r\n}\r\n\r\n/** One directory level plus its breadcrumb ancestry. */\r\ninterface WslDirListingWire {\r\n path: string\r\n parent: string | null\r\n entries: WslDirEntryWire[]\r\n}\r\n\r\n/** The wire envelope every method answers with. */\r\ntype Envelope<T> = { ok: true; value: T } | { ok: false; error: string }\r\n\r\nconst MAX_BODY_BYTES = 1024 * 1024\r\n\r\n/** Valid WSL distribution names: one path-safe segment (no separators, no dot-dirs). */\r\nconst DISTRO_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/\r\n\r\n/** The loopback hostnames the data route answers to (DNS-rebinding fence). */\r\nconst LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1'])\r\n\r\n/** True when a socket address is loopback (any IPv4/IPv6 spelling). */\r\nfunction isLoopback(address: string | undefined): boolean {\r\n return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'\r\n}\r\n\r\n/** The hostname part of a `Host` header value (port and IPv6 brackets stripped). */\r\nfunction hostNameOf(host: string): string {\r\n if (host.startsWith('[')) {\r\n const end = host.indexOf(']')\r\n return end >= 0 ? host.slice(1, end) : host\r\n }\r\n return host.split(':')[0] ?? ''\r\n}\r\n\r\n/** True when the request's `Host` header names a loopback host. */\r\nfunction isLoopbackHost(host: string | undefined): boolean {\r\n return host !== undefined && LOOPBACK_HOSTNAMES.has(hostNameOf(host).toLowerCase())\r\n}\r\n\r\n/**\r\n * Validate a wire-supplied distribution name before it becomes a UNC segment:\r\n * an attacker-controlled segment containing separators or `..` would escape\r\n * the `\\\\wsl.localhost\\` share structure into arbitrary UNC paths.\r\n * @param value - the raw wire value.\r\n * @returns the validated distro name.\r\n */\r\nfunction requireDistro(value: unknown): string {\r\n if (typeof value !== 'string' || !DISTRO_PATTERN.test(value) || value === '.' || value === '..') {\r\n throw new Error('distro must be a valid WSL distribution name')\r\n }\r\n return value\r\n}\r\n\r\n/** Human text for an unknown rejection. */\r\nfunction messageOf(value: unknown): string {\r\n return value instanceof Error ? value.message : String(value)\r\n}\r\n\r\n/** Write one JSON envelope. */\r\nfunction json(res: ServerResponse, status: number, body: Envelope<unknown>): void {\r\n res.writeHead(status, {\r\n 'content-type': 'application/json; charset=utf-8',\r\n 'cache-control': 'no-store',\r\n 'x-content-type-options': 'nosniff',\r\n })\r\n res.end(JSON.stringify(body))\r\n}\r\n\r\n/** Collect and parse the request body, bounded. */\r\nasync function readBody(req: IncomingMessage): Promise<Record<string, unknown>> {\r\n const chunks: Buffer[] = []\r\n let size = 0\r\n for await (const chunk of req) {\r\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\r\n size += buffer.length\r\n if (size > MAX_BODY_BYTES) throw new Error('request body is too large')\r\n chunks.push(buffer)\r\n }\r\n const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown\r\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\r\n throw new Error('request body must be a JSON object')\r\n }\r\n return parsed as Record<string, unknown>\r\n}\r\n\r\n/** Normalize a Linux path for the wire (rejecting non-absolute input). */\r\nfunction requireLinuxPath(value: unknown, label: string): string {\r\n if (typeof value !== 'string' || !isAbsoluteLinuxPath(value)) {\r\n throw new Error(`${label} must be an absolute Linux path`)\r\n }\r\n return normalizeLinuxPath(value)\r\n}\r\n\r\n/** Validate a wire-supplied workspace path and return its canonical UNC form. */\r\nfunction requireWslUnc(value: unknown): string {\r\n if (typeof value !== 'string') throw new Error('path must be a string')\r\n const canonical = canonicalWslUnc(value)\r\n if (canonical === null) throw new Error('path must be a WSL UNC workspace path')\r\n return canonical\r\n}\r\n\r\n/**\r\n * Resolve one directory listing for the dialog. The 9P share (`\\\\wsl.localhost\\…`)\r\n * serves only the ext4 volume: `/mnt/<drive>` (drvfs) reads return Access\r\n * denied, so drvfs paths are read through their Windows drive spelling and\r\n * `/mnt` itself is synthesized from the drives present on the host.\r\n */\r\nfunction listWslDir(distro: string, linuxPath: string): WslDirListingWire {\r\n if (linuxPath === '/mnt') {\r\n const entries: WslDirEntryWire[] = []\r\n for (let i = 0; i < 26; i++) {\r\n const letter = String.fromCharCode(65 + i)\r\n try {\r\n const info = statSync(`${letter}:\\\\`)\r\n if (info.isDirectory()) entries.push({ name: letter.toLowerCase(), kind: 'directory' })\r\n } catch {\r\n // Absent drive: skip.\r\n }\r\n }\r\n return { path: '/mnt', parent: '/', entries }\r\n }\r\n const winPath = mntToWindowsPath(linuxPath)\r\n const readPath = winPath !== null ? winPath : joinUnc(distro, linuxPath)\r\n const dirents = readdirSync(readPath, { withFileTypes: true })\r\n const entries: WslDirEntryWire[] = dirents\r\n .slice(0, 1000)\r\n .map((dirent): WslDirEntryWire => {\r\n const kind: WslDirEntryWire['kind'] = dirent.isDirectory()\r\n ? 'directory'\r\n : dirent.isFile() ? 'file' : 'other'\r\n return { name: dirent.name, kind }\r\n })\r\n .sort((a, b) => {\r\n if (a.kind === 'directory' && b.kind !== 'directory') return -1\r\n if (a.kind !== 'directory' && b.kind === 'directory') return 1\r\n return a.name.localeCompare(b.name)\r\n })\r\n const parent = linuxPath === '/' ? null : linuxPath.split('/').slice(0, -1).join('/') || '/'\r\n return { path: linuxPath, parent, entries }\r\n}\r\n\r\n/** Route one method dispatch. */\r\nasync function dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {\r\n switch (method) {\r\n case 'listDistros': {\r\n const distros = await listDistros()\r\n const fallback = await defaultDistro()\r\n if (fallback !== undefined && distros.includes(fallback)) {\r\n return [fallback, ...distros.filter(name => name !== fallback)]\r\n }\r\n return distros\r\n }\r\n case 'listDir': {\r\n const distro = requireDistro(params.distro)\r\n const path = requireLinuxPath(params.path, 'path')\r\n return listWslDir(distro, path)\r\n }\r\n case 'check': {\r\n const distro = requireDistro(params.distro)\r\n const path = requireLinuxPath(params.path, 'path')\r\n // drvfs paths (Access denied over 9P) are checked through their drive spelling.\r\n const winPath = mntToWindowsPath(path)\r\n const readPath = winPath !== null ? winPath : joinUnc(distro, path)\r\n try {\r\n const info = statSync(readPath)\r\n return { exists: true, isDirectory: info.isDirectory() }\r\n } catch {\r\n return { exists: false, isDirectory: false }\r\n }\r\n }\r\n case 'registerWindows': {\r\n // A `/mnt/<drive>` workspace registers under its Windows drive path\r\n // (the registry realpath/stats it, and 9P cannot serve drvfs) with the\r\n // distro stored for the per-session env contributor.\r\n const distro = requireDistro(params.distro)\r\n const linuxPath = requireLinuxPath(params.linuxPath, 'path')\r\n const winPath = mntToWindowsPath(linuxPath)\r\n if (winPath === null) throw new Error('registerWindows requires a /mnt/<drive> Linux path')\r\n const username = typeof params.username === 'string' ? params.username : undefined\r\n registerWindowsWorkspace(winPath, distro, username)\r\n return null\r\n }\r\n case 'listWorkspaces': {\r\n // Every registered WSL workspace key (UNC and Windows drive spellings):\r\n // the client uses the drive keys to recognize `/mnt` workspaces.\r\n return listWorkspaceKeys()\r\n }\r\n case 'setUser': {\r\n const path = requireWslUnc(params.path)\r\n const username = params.username\r\n if (username === undefined || username === '') {\r\n setWorkspaceUsername(path, undefined)\r\n } else {\r\n if (typeof username !== 'string' || !isValidWslUsername(username)) {\r\n throw new Error('username must match the Linux username pattern [A-Za-z_][A-Za-z0-9_.-]*')\r\n }\r\n setWorkspaceUsername(path, username)\r\n }\r\n return null\r\n }\r\n default:\r\n throw new Error(`unknown method \"${method}\"`)\r\n }\r\n}\r\n\r\n/** The `ctx.agentPresets` roster face this plugin consumes (optional service). */\r\ninterface AgentPresetsService {\r\n list(): Promise<{ id: string; broken?: string; path: string }[]>\r\n read(id: string): Promise<string>\r\n}\r\n\r\n/**\r\n * Publish a fully staged preset directory while preserving the last complete\r\n * variant if publication fails. Stable sibling names also let the next boot\r\n * recover an interrupted old-to-backup rename before doing new work.\r\n */\r\nfunction publishVariant(staging: string, dest: string): void {\r\n const previous = `${dest}.previous`\r\n if (!existsSync(dest) && existsSync(previous)) renameSync(previous, dest)\r\n if (existsSync(previous)) rmSync(previous, { recursive: true, force: true })\r\n if (existsSync(dest)) renameSync(dest, previous)\r\n try {\r\n renameSync(staging, dest)\r\n } catch (error) {\r\n if (!existsSync(dest) && existsSync(previous)) renameSync(previous, dest)\r\n throw error\r\n }\r\n rmSync(previous, { recursive: true, force: true })\r\n}\r\n\r\n/** Materialize one WSL variant per healthy source preset. */\r\nasync function materializeVariants(\r\n agentPresets: AgentPresetsService,\r\n dshHome: string,\r\n shellPath: string,\r\n fsPath: string,\r\n): Promise<void> {\r\n const presets = await agentPresets.list()\r\n const userRoot = join(dshHome, '.agent-presets')\r\n const generated = new Set<string>()\r\n for (const preset of presets) {\r\n if (preset.broken !== undefined) continue\r\n if (isWslVariantId(preset.id)) continue\r\n const variantId = variantIdFor(preset.id)\r\n const source = await agentPresets.read(preset.id)\r\n const transformed = transformPresetForWsl(source, shellPath, fsPath)\r\n const dir = join(userRoot, variantId)\r\n const staging = `${dir}.staging`\r\n rmSync(staging, { recursive: true, force: true })\r\n // A preset directory is an opaque, self-contained plugin unit. Mirror it\r\n // without interpreting local code or asset names, then overwrite only the\r\n // two files owned by this generator.\r\n cpSync(dirname(preset.path), staging, { recursive: true, force: true })\r\n writeFileSync(join(staging, 'agent.cordis.yml'), transformed, 'utf8')\r\n const labels = MODE_DISPLAY_LABELS[preset.id]\r\n let name = variantName(preset.id, preset.id)\r\n let orderLine = ''\r\n try {\r\n const meta = readFileSync(join(dirname(preset.path), 'preset.yml'), 'utf8')\r\n if (labels === undefined) {\r\n // Custom presets keep their own display name; shipped modes use the\r\n // bilingual labels above so both locales can identify the variant.\r\n const match = /^name:\\s*(.+)$/m.exec(meta)\r\n if (match?.[1] !== undefined && match[1].trim() !== '') {\r\n name = variantName(preset.id, match[1].trim())\r\n }\r\n }\r\n // Inherit the source's declared order so the WSL variants line up with\r\n // the local modes in the roster (standard, PTC, minimal, cordis).\r\n const orderMatch = /^order:\\s*(\\d+)\\s*$/m.exec(meta)\r\n if (orderMatch?.[1] !== undefined) orderLine = `order: ${orderMatch[1]}\\n`\r\n } catch {\r\n // Absent or unreadable display metadata falls back to the id-based name.\r\n }\r\n writeFileSync(\r\n join(staging, 'preset.yml'),\r\n `name: ${yamlScalar(name)}\\n`\r\n + orderLine\r\n + `description: ${yamlScalar(variantDescription(preset.id))}\\n`,\r\n 'utf8',\r\n )\r\n publishVariant(staging, dir)\r\n generated.add(variantId)\r\n }\r\n for (const entry of readdirSync(userRoot, { withFileTypes: true })) {\r\n if (!entry.isDirectory()) continue\r\n if (entry.name === 'wsl') {\r\n // The legacy standalone WSL mode: folded into the variants above.\r\n rmSync(join(userRoot, entry.name), { recursive: true, force: true })\r\n continue\r\n }\r\n if (!/^wsl-[a-z0-9-]+$/.test(entry.name)) continue\r\n if (!generated.has(entry.name)) rmSync(join(userRoot, entry.name), { recursive: true, force: true })\r\n }\r\n}\r\n\r\n/** Function-plugin plugin contract. */\r\nexport const name = 'dsh-wsl-workspace'\r\n\r\n/** Required services. */\r\nexport const inject = ['webServer']\r\n\r\n/** Validated plugin config (schemastery applied the defaults). */\r\nexport const Config: z<Config> = z.object({\r\n route: z.string().default(DEFAULT_ROUTE),\r\n})\r\n\r\n/**\r\n * Apply the host half: materialize a `wsl-<mode>` variant for every healthy\r\n * roster preset, register the data route, and\r\n * contribute the per-session `DSH_WSL_DISTRO` managed-env fact so the WSL\r\n * shell executor can resolve a plain Linux `workdir` to the calling\r\n * session's distribution.\r\n * @param ctx - the host plugin context.\r\n * @param config - the validated configuration.\r\n */\r\nexport function apply(ctx: Context, config: Config): void {\r\n const resolved = config as ResolvedConfig\r\n const dshHome = process.env.DSH_HOME ?? join(homedir(), '.dsh')\r\n const packageRoot = fileURLToPath(new URL('..', import.meta.url))\r\n const shellPath = join(packageRoot, 'lib', 'shell.js').replace(/\\\\/g, '/')\r\n const fsPath = join(packageRoot, 'lib', 'fs.js').replace(/\\\\/g, '/')\r\n\r\n const agentPresets = ctx.get('agentPresets') as unknown as AgentPresetsService | undefined\r\n if (agentPresets !== undefined) {\r\n ctx.effect(() => {\r\n void materializeVariants(agentPresets, dshHome, shellPath, fsPath).catch((error) => {\r\n // Variant generation is best-effort over a live roster: a missing or\r\n // unreadable source preset must not take the whole plugin down, but\r\n // the failure is surfaced loudly rather than hidden.\r\n console.error(`dsh-wsl-workspace: WSL preset-variant generation failed: ${messageOf(error)}`)\r\n })\r\n return () => {}\r\n }, 'dsh-wsl-workspace: WSL preset variants')\r\n }\r\n\r\n const skills = ctx.get('skills') as unknown as WslSkillsRegistryFace | undefined\r\n if (skills !== undefined && typeof skills.registerProvider === 'function') {\r\n // The shipped skill-filesystem provider scans only the session cwd's\r\n // project root, so WSL workspaces whose `.dsh/skills` live in nested\r\n // projects would show an empty skill catalog. This provider mirrors the\r\n // host's project discovery for WSL UNC workspaces, bounded to the\r\n // workspace root (issue #10). The method check keeps a host whose\r\n // `skills` service exists with a different shape from breaking plugin\r\n // load; the provider is an enhancement, never a load-time requirement.\r\n ctx.effect(() => skills.registerProvider(\r\n control => new WslSkillsProvider(control),\r\n ), 'dsh-wsl-workspace: WSL workspace skills provider')\r\n }\r\n\r\n const shellEnv = ctx.get('shellEnv') as unknown as ShellEnvService | undefined\r\n if (shellEnv !== undefined) {\r\n ctx.effect(() => shellEnv.register({\r\n name: 'wsl-workspace-distro',\r\n variables: {\r\n DSH_WSL_DISTRO: {\r\n description: 'The WSL distribution of the calling session workspace, when the session cwd is a WSL UNC path.',\r\n },\r\n DSH_WSL_USER: {\r\n description: 'The Linux user of the calling session workspace, when the workspace has one configured.',\r\n },\r\n },\r\n resolve(execution) {\r\n const cwd = execution.agent?.session.header.cwd\r\n const unc = cwd === undefined ? null : parseWslUnc(cwd)\r\n if (unc !== null) {\r\n const username = getWorkspaceUsername(joinUnc(unc.distro, unc.linuxPath))\r\n return username === undefined || username === ''\r\n ? { DSH_WSL_DISTRO: unc.distro }\r\n : { DSH_WSL_DISTRO: unc.distro, DSH_WSL_USER: username }\r\n }\r\n // A Windows-drive cwd belongs to a `/mnt/<drive>` WSL workspace (9P\r\n // cannot serve drvfs, so those register under their drive path): the\r\n // stored distro drives `wsl.exe -d` for bash.\r\n if (cwd !== undefined && /^[A-Za-z]:[\\\\/]/.test(cwd)) {\r\n const entry = getWindowsWorkspace(cwd)\r\n if (entry !== undefined && entry.distro !== undefined && entry.distro !== '') {\r\n return entry.username === undefined || entry.username === ''\r\n ? { DSH_WSL_DISTRO: entry.distro }\r\n : { DSH_WSL_DISTRO: entry.distro, DSH_WSL_USER: entry.username }\r\n }\r\n }\r\n return {}\r\n },\r\n }), 'dsh-wsl-workspace: per-session distro env fact')\r\n }\r\n\r\n const webServer = ctx.get('webServer') as unknown as WebServerService\r\n ctx.effect(() => webServer.register({\r\n kind: 'exact',\r\n path: resolved.route,\r\n handler: async (req, res) => {\r\n if (!isLoopback(req.socket.remoteAddress) || !isLoopbackHost(req.headers.host)) {\r\n json(res, 403, { ok: false, error: 'loopback-only' })\r\n return\r\n }\r\n if (req.method !== 'POST') {\r\n json(res, 405, { ok: false, error: 'method not allowed' })\r\n return\r\n }\r\n let body: Record<string, unknown>\r\n try {\r\n body = await readBody(req)\r\n } catch (error) {\r\n json(res, 400, { ok: false, error: messageOf(error) })\r\n return\r\n }\r\n const method = typeof body.method === 'string' ? body.method : ''\r\n const params = body.params === undefined ? {} : body.params\r\n if (params === null || typeof params !== 'object' || Array.isArray(params)) {\r\n json(res, 400, { ok: false, error: 'params must be an object' })\r\n return\r\n }\r\n try {\r\n const value = await dispatch(method, params as Record<string, unknown>)\r\n json(res, 200, { ok: true, value })\r\n } catch (error) {\r\n json(res, 200, { ok: false, error: messageOf(error) })\r\n }\r\n },\r\n }), 'dsh-wsl-workspace: dialog data route')\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["spans: { start: number; end: number }[]","kept: string[]","nodeSkillIo: WslSkillIo","roots: SkillRoot[]","frontier: [string, number][]","next: [string, number][]","entries: Dirent[]","result: SkillRoot[]","dirents: Dirent[]","entries: SkillEntry[]","joinWindowsPath","raw: string","value","name","collected: string[]","indent: string | undefined","candidates: WslSkillCandidate[]","MODE_DISPLAY_LABELS: Readonly<Record<string, { en: string; zh: string }>>","chunks: Buffer[]","entries: WslDirEntryWire[]","kind: WslDirEntryWire['kind']","selfDescription: { version: string; releases: { id: string; status: string }[] } | undefined","name","Config: z<Config>","body: Record<string, unknown>"],"sources":["../src/host/variants.ts","../src/host/wsl-skills.ts","../src/index.ts"],"sourcesContent":["/**\n * WSL preset-variant generator. For every healthy source preset the roster\n * supplies, a `wsl-<id>` variant is materialized under the roster's user\n * root: the source composition with its shell/filesystem world replaced by\n * the WSL providers, so any mode (standard, minimal, code, cordis, user\n * presets) can run on top of a WSL execution world. The execution world is\n * therefore orthogonal to the mode instead of a mode itself.\n *\n * The transformation is text-level on the top-level rows of the composition\n * (the shape all shipped presets share), with surgical edits for the known\n * special groups; unknown shapes are kept verbatim where possible.\n * @module dsh-wsl-workspace/host/variants\n */\n\n/** Top-level rows that name the execution world and are replaced by the variant's own. */\nconst WORLD_ROWS = new Set(['tool-bash', 'tool-pwsh', 'tool-fs', 'tool-fs-search', 'str-replace-editor', 'filesystem', 'persistent-shell', 'custom-bash', 'bootstrap-filesystem'])\n\n/** The injected WSL world group: providers + the bash/fs consumers, entry-local. */\nfunction wslWorldGroup(shellPath: string, fsPath: string, includeEditor: boolean): string {\n return [\n '# ── WSL execution world (dsh-wsl-workspace variant) ─────────────────────',\n '# The shell and fs services are provided entry-locally (the isolate',\n '# realm); host services (tools registry, shell-env, jobs) fall through.',\n '# tool-fs-search is intentionally absent: the packaged ripgrep runs on',\n '# the Windows host and cannot open Linux paths; WSL sessions search with',\n '# shell tools instead.',\n '- id: wsl-world',\n \" name: cordis:group\",\n ' group: true',\n ' isolate:',\n ' shell: true',\n ' fs: true',\n ' config:',\n ` - id: shell-wsl`,\n ` name: '${shellPath.replace(/'/g, \"''\")}'`,\n ' - id: fs-wsl',\n ` name: '${fsPath.replace(/'/g, \"''\")}'`,\n ' - id: tool-bash',\n \" name: '@deepseek-ai/dsh-tool-bash'\",\n ' - id: tool-fs',\n \" name: '@deepseek-ai/dsh-tool-fs'\",\n // The editor resolves through this entry-local WSL fs. Older editor builds\n // pass no cwd, so the provider inherits it from the current tool execution.\n // Anchored-family presets require this name during bootstrap.\n ...(includeEditor\n ? [\n ' - id: str-replace-editor',\n \" name: '@deepseek-ai/dsh-tool-str-replace-editor'\",\n ' config:',\n ' maxOutputChars: 16000',\n ]\n : []),\n '',\n ].join('\\n')\n}\n\n/** The sentence appended to a standard-like persona when the variant runs in WSL. */\nconst PERSONA_APPEND = ' Your working directory {{cwd}} is inside a WSL (Windows Subsystem for Linux) distribution: the bash tool and the file read/write/edit tools use Linux paths, and the Windows filesystem is reachable as /mnt/<drive> for file migration.'\n\n/** The upstream local-skill provider row, whose watcher cannot watch a UNC share. */\nconst SKILL_FILESYSTEM_ROW = 'skill-filesystem'\n\n/**\n * Turn the local skill provider's watcher off inside a WSL variant.\n *\n * `@deepseek-ai/dsh-skill-filesystem` reports an INCOMPLETE observation when\n * its watcher fails to start, and `dsh-tool-skill` withholds the whole catalog\n * while a snapshot is incomplete. chokidar cannot watch\n * `\\\\wsl.localhost\\<distro>\\...`, so in a WSL session the model never sees the\n * catalog at all - `skill` still loads one by name, but nothing tells the model\n * which skills exist. With the watcher off the discovery completes (the\n * plugin's own provider rescans the share on demand, and the next session gets\n * a fresh catalog); the price is no live refresh inside one running session,\n * which never worked on this substrate anyway.\n * @param block - the row's lines.\n * @returns the row's lines with `watch: false` merged into its config.\n */\nfunction disableSkillWatch(block: readonly string[]): string[] {\n const lines = [...block]\n // An explicit `watch:` from the author wins.\n if (lines.some(line => /^\\s*watch:/.test(line))) return lines\n const configIndex = lines.findIndex(line => /^\\s*config:\\s*$/.test(line))\n if (configIndex >= 0) {\n const childIndent = `${/^(\\s*)/.exec(lines[configIndex] ?? '')?.[1] ?? ' '} `\n lines.splice(configIndex + 1, 0, `${childIndent}watch: false`)\n return lines\n }\n const rowIndent = /^(\\s*)/.exec(lines[0] ?? '')?.[1] ?? ''\n // Insert before the row block's trailing blank line, not after it.\n let insertAt = lines.length\n while (insertAt > 0 && (lines[insertAt - 1] ?? '').trim() === '') insertAt -= 1\n lines.splice(insertAt, 0, `${rowIndent} config:`, `${rowIndent} watch: false`)\n return lines\n}\n\n/** The top-level rows of one composition, as (startLine, endLineExclusive) spans. */\nfunction topLevelSpans(lines: readonly string[]): { start: number; end: number }[] {\n const spans: { start: number; end: number }[] = []\n let start = -1\n for (let index = 0; index < lines.length; index++) {\n if (lines[index]?.startsWith('- id: ') === true) {\n if (start >= 0) spans.push({ start, end: index })\n start = index\n }\n }\n if (start >= 0) spans.push({ start, end: lines.length })\n return spans\n}\n\n/** The row id of a top-level span, or undefined when the first line is malformed. */\nfunction spanId(lines: readonly string[], span: { start: number; end: number }): string | undefined {\n return /^- id: ([A-Za-z0-9_.-]+)/.exec(lines[span.start] ?? '')?.[1]\n}\n\n/** Persona config scalars that carry model-facing text, in append preference order. */\nconst PERSONA_TARGET_FIELDS = ['suffix', 'text', 'prefix'] as const\n\n/** One persona config scalar: where it sits, and whether it is a block header. */\ninterface PersonaTarget {\n /** Line index of the scalar inside the persona block. */\n index: number\n /** Field name: `suffix`, `text` or `prefix`. */\n field: string\n /** Indentation of the scalar's own line. */\n indent: number\n /** Block header (`>-`, `|-`, …) when the scalar is folded/literal, else ''. */\n header: string\n /** Unquoted inline value when the scalar is inline, else ''. */\n inline: string\n}\n\n/** Strip one layer of YAML quoting so a value can move into a block scalar. */\nfunction unquoteScalar(value: string): string {\n const single = /^'(.*)'$/s.exec(value)\n if (single !== null) return (single[1] ?? '').replace(/''/g, \"'\")\n const double = /^\"(.*)\"$/s.exec(value)\n return double !== null ? (double[1] ?? '') : value\n}\n\n/**\n * Locate the persona scalar this transform amends.\n *\n * DSH moved the model-facing prompt text across releases: `text` up to\n * v0.1.2-rc.1, and `prefix` + `suffix` from v0.1.3-alpha.2 on. `suffix` wins\n * when both exist because it carries the working-directory sentence, so the\n * appended note lands exactly where the legacy `text` block put it; `prefix`\n * is the last resort for a composition that has neither. A known field with an\n * empty value is not amendable.\n * @param block - the persona row's lines.\n * @returns the target scalar, or undefined when the row carries none.\n */\nfunction personaTarget(block: readonly string[]): PersonaTarget | undefined {\n for (const field of PERSONA_TARGET_FIELDS) {\n for (let index = 0; index < block.length; index += 1) {\n const match = new RegExp(`^(\\\\s*)${field}:\\\\s*(.*)$`).exec(block[index] ?? '')\n if (match === null) continue\n const indent = match[1]?.length ?? 0\n const rest = (match[2] ?? '').trim()\n if (rest === '' || /^[|>][+-]?$/.test(rest)) {\n return { index, field, indent, header: rest, inline: '' }\n }\n return { index, field, indent, header: '', inline: unquoteScalar(rest) }\n }\n }\n return undefined\n}\n\n/**\n * Index of the last line belonging to the block scalar whose header is at\n * `headerIndex`. A non-blank line at or above the header's indentation is the\n * next sibling key and ends the scalar — without that stop a persona carrying\n * both `suffix` and `prefix` blocks would take the note in the wrong one.\n * @param block - the persona row's lines.\n * @param headerIndex - index of the `field: >-` header line.\n * @param indent - the header's own indentation.\n * @returns the last content line's index, or -1 when the scalar is empty.\n */\nfunction lastBlockLine(block: readonly string[], headerIndex: number, indent: number): number {\n let last = -1\n for (let index = headerIndex + 1; index < block.length; index += 1) {\n const line = block[index] ?? ''\n if (line.trim() === '') continue\n const lineIndent = /^(\\s*)/.exec(line)?.[1]?.length ?? 0\n if (lineIndent <= indent) break\n last = index\n }\n return last\n}\n\n/** Whether a top-level span is a `persona` row this transform may amend. */\nfunction appendablePersona(lines: readonly string[], span: { start: number; end: number }): boolean {\n const block = lines.slice(span.start, span.end)\n // `complete: true` asks the persona plugin for no runtime context at all.\n if (block.join('\\n').includes('complete: true')) return false\n const target = personaTarget(block)\n if (target === undefined) return false\n return target.header === ''\n ? target.inline !== ''\n : lastBlockLine(block, target.index, target.indent) >= 0\n}\n\n/**\n * Append the WSL sentence to a persona row's model-facing scalar, in place.\n *\n * A block scalar takes the sentence as a sibling line, which is what the\n * legacy `text: >-` form always did. An inline scalar cannot: it is folded into\n * a block scalar first, so the sentence joins it the same way (and the model\n * sees the same text it saw before v0.1.3-alpha.2).\n * @param lines - the whole composition's lines.\n * @param span - the persona row's span.\n * @returns the persona row's lines, amended when a target was found.\n */\nfunction appendPersona(lines: readonly string[], span: { start: number; end: number }): string[] {\n const block = [...lines.slice(span.start, span.end)]\n const target = personaTarget(block)\n if (target === undefined) return block\n const pad = ' '.repeat(target.indent)\n if (target.header !== '') {\n const last = lastBlockLine(block, target.index, target.indent)\n if (last < 0) return block\n const textIndent = /^(\\s*)/.exec(block[last] ?? '')?.[1] ?? `${pad} `\n block.splice(last + 1, 0, `${textIndent}${PERSONA_APPEND}`)\n return block\n }\n const child = `${pad} `\n block.splice(\n target.index,\n 1,\n `${pad}${target.field}: >-`,\n `${child}${target.inline}`,\n `${child}${PERSONA_APPEND.trim()}`,\n )\n return block\n}\n\n/**\n * Transform one source preset composition into its WSL variant: drop the\n * execution-world rows, keep everything else verbatim, and append the WSL\n * world group. The persistent-shell group is NOT re-added: it registers the\n * same `bash` tool name as the WSL world's `dsh-tool-bash`, and the tools\n * registry rejects duplicates within one preset layer — the whole variant\n * fails to mount and the session falls back to another preset. Its PTY\n * backend additionally cannot run on this plugin's Windows host\n * (`dsh-subprocess-local`: \"terminal inspection is unsupported on platform\n * win32\"), so the group could never spawn a shell here anyway. The WSL\n * world's ordinary `bash` tool covers command execution for every variant.\n * @param source - the source composition text.\n * @param shellPath - absolute path of the plugin's built WSL shell provider.\n * @param fsPath - absolute path of the plugin's built WSL fs provider.\n * @returns the variant composition text.\n */\nexport function transformPresetForWsl(source: string, shellPath: string, fsPath: string): string {\n const lines = source.split('\\n')\n const spans = topLevelSpans(lines)\n const kept: string[] = []\n let sawEditor = false\n let personaAppended = false\n for (const span of spans) {\n const id = spanId(lines, span)\n if (id === undefined) {\n kept.push(...lines.slice(span.start, span.end))\n continue\n }\n if (WORLD_ROWS.has(id)) continue\n if (id === SKILL_FILESYSTEM_ROW) {\n kept.push(...disableSkillWatch(lines.slice(span.start, span.end)))\n continue\n }\n if (id === 'persona' && !personaAppended && appendablePersona(lines, span)) {\n kept.push(...appendPersona(lines, span))\n personaAppended = true\n continue\n }\n kept.push(...lines.slice(span.start, span.end))\n if (id === 'str-replace-editor') sawEditor = true\n }\n if (source.includes('str-replace-editor')) sawEditor = true\n const result = [...kept]\n if (result.length > 0 && result[result.length - 1] !== '') result.push('')\n result.push(wslWorldGroup(shellPath, fsPath, sawEditor))\n return result.join('\\n').replace(/\\n{3,}/g, '\\n\\n').replace(/\\n+$/, '\\n')\n}\n\n/** Whether an id is one of this plugin's own preset directories. */\nexport function isWslVariantId(id: string): boolean {\n return id === 'wsl' || /^wsl-[a-z0-9-]+$/.test(id)\n}\n\n/** The variant id for one source preset id. */\nexport function variantIdFor(sourceId: string): string {\n return `wsl-${sourceId.toLowerCase()}`\n}\n","/**\n * WSL workspace skill provider (host half).\n *\n * DSH's shipped skill-filesystem provider scans only the session cwd's\n * project root (the nearest `.git` ancestor) for `.dsh/skills` / `.agents/skills`\n * and never descends into nested projects. A WSL workspace whose project\n * folders live below the registered workspace root therefore shows an empty\n * skill catalog, even though the same layout works when the session cwd is\n * the project folder itself (issue #10).\n *\n * This provider mirrors the host's discovery rules for WSL UNC session\n * workspaces: it starts at the session cwd's nearest `.git` ancestor (the\n * host's project-root rule; the cwd itself when no ancestor has a `.git`\n * marker), then walks that root (depth- and budget-bounded). Directory\n * symlinks are followed when the substrate resolves them and pruned safely\n * when it does not — the `\\\\wsl.localhost` 9P share cannot resolve Linux\n * symlink targets, so linked-in projects stay undiscoverable there today.\n * The walk collects every\n * `.dsh/skills` and `.agents/skills` directory it finds — including nested\n * projects — and publishes their skills with the same\n * project ranks and sources the host uses, so precedence and duplicate\n * resolution behave identically. Non-WSL lookups return nothing and leave\n * the host's own providers untouched.\n *\n * All filesystem reads go through `node:fs` against the `\\\\wsl.localhost\\…`\n * 9P share (the same substrate `WslFileSystem` uses); an injectable IO face\n * keeps the discovery logic unit-testable without a live distro.\n *\n * @module dsh-wsl-workspace/host/wsl-skills\n */\n\nimport { readdir, readFile, stat } from 'node:fs/promises'\nimport type { Dirent } from 'node:fs'\nimport { join as joinWindowsPath, posix } from 'node:path'\nimport { joinUnc, parseWslUnc } from '../shared/paths.ts'\n\n/** Project ranks copied from @deepseek-ai/dsh-skill-filesystem so WSL and host entries interleave identically. */\nconst PROJECT_DSH_RANK = 100\nconst PROJECT_AGENTS_RANK = 200\n\n/** How many directory levels below the workspace root are scanned. */\nconst MAX_SCAN_DEPTH = 4\n/** Maximum distinct skill directories published per lookup. */\nconst MAX_SKILL_ROOTS = 64\n/** Maximum directories visited per lookup (an absolute blast-radius cap). */\nconst MAX_VISITED_DIRECTORIES = 4096\n/** How many parent levels above the session cwd are searched for a `.git` project marker. */\nconst MAX_ANCESTOR_WALK = 64\n/** How long a completed lookup is served from cache before the next rescan. */\nconst CACHE_TTL_MS = 10_000\n/** Maximum cached lookups (one entry per distinct scan root across sessions). */\nconst CACHE_MAX_ENTRIES = 32\n\n/** Kebab-case skill names, matching the host grammar. */\nconst SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\n/** Directory names that never contain project skill roots (safe to prune while walking). */\nconst PRUNED_DIRECTORY_NAMES = new Set([\n '.git', '.hg', '.svn', '.bzr', 'node_modules', '.venv', 'venv', '.tox',\n '.pants.d', '.next', '.nuxt', 'dist', 'build', 'out', 'coverage',\n '__pycache__', '.mypy_cache', '.pytest_cache', '.ruff_cache', '.cache',\n '.idea', '.vscode', '.serverless', '.terraform', '.yarn', '.pnpm-store',\n])\n\n/** One `name: value` frontmatter line pair the parser understands. */\ninterface ParsedSkill {\n name: string\n description: string\n whenToUse?: string\n invocation: { modelInvocable: boolean; userInvocable: boolean }\n content: string\n}\n\n/** The provider's minimal skill-candidate contract (mirrors @deepseek-ai/dsh-skill). */\nexport interface WslSkillCandidate {\n readonly name: string\n readonly description: string\n readonly whenToUse?: string\n readonly invocation: { modelInvocable: boolean; userInvocable: boolean }\n readonly source: string\n readonly provider: string\n readonly rank: number\n readonly locator: { path: string; directory: string }\n readonly path: string\n}\n\n/** The provider's minimal skill-definition contract (candidate plus body). */\nexport interface WslSkillDefinition extends WslSkillCandidate {\n readonly content: string\n}\n\n/** Lookup options the registry passes to `list`/`get`. */\nexport interface WslSkillLookupOptions {\n readonly cwd?: string\n readonly signal?: AbortSignal\n}\n\n/** Registration-scoped lifecycle face passed to the provider constructor. */\nexport interface WslSkillProviderControl {\n readonly signal: AbortSignal\n readonly invalidate: () => void\n}\n\n/** The `ctx.skills` registry face this provider registers on (optional service). */\nexport interface WslSkillsRegistryFace {\n registerProvider(create: (control: WslSkillProviderControl) => {\n readonly name: string\n list(options: WslSkillLookupOptions): Promise<unknown>\n get(candidate: WslSkillCandidate, options: WslSkillLookupOptions): Promise<unknown>\n }): () => void\n}\n\n/** Injectable filesystem face (defaults to node:fs/promises on the real 9P share). */\nexport interface WslSkillIo {\n readdir(path: string, options: { withFileTypes: true }): Promise<Dirent[]>\n readFile(path: string, options: { encoding: 'utf8' }): Promise<string>\n stat(path: string): Promise<{ isDirectory(): boolean }>\n}\n\n/** The node:fs/promises implementation the provider uses in production. */\nexport const nodeSkillIo: WslSkillIo = {\n readdir: async (path, options) => readdir(path, options),\n readFile: async (path, options) => readFile(path, options),\n stat: async path => stat(path),\n}\n\n/** One discovered skill directory under a WSL workspace. */\ninterface SkillRoot {\n /** Absolute UNC path of the skills directory (`…\\.dsh\\skills`). */\n path: string\n /** Host source label ('project-dsh' | 'project-agents'). */\n source: 'project-dsh' | 'project-agents'\n /** Host project rank so same-name wins and precedence stay consistent. */\n rank: number\n}\n\n/** Whether a skills-directory entry is a directory-bundle or a flat markdown skill. */\ninterface SkillEntry {\n name: string\n kind: 'bundle' | 'flat'\n path: string\n}\n\n/**\n * Locate the nearest ancestor of `linuxDir` (the directory itself included)\n * containing a `.git` marker, mirroring the host skill-filesystem's\n * project-root rule. `.git` may be a directory or a worktree pointer file;\n * existence is enough. Bounded so a pathological path cannot spin the walk.\n * @param distro - the WSL distribution name.\n * @param linuxDir - the session cwd's absolute Linux path.\n * @param io - filesystem face.\n * @returns the project root's Linux path, or `undefined` when no ancestor carries a `.git`.\n */\nasync function nearestGitAncestor(distro: string, linuxDir: string, io: WslSkillIo): Promise<string | undefined> {\n let current = linuxDir\n for (let levels = 0; levels <= MAX_ANCESTOR_WALK; levels += 1) {\n try {\n await io.stat(joinUnc(distro, posix.join(current, '.git')))\n return current\n } catch {\n // No `.git` marker at this level; keep walking towards the filesystem root.\n }\n const parent = posix.dirname(current)\n if (parent === current) return undefined\n current = parent\n }\n return undefined\n}\n\n/**\n * Scan a WSL workspace root for nested skill directories.\n * @param distro - the WSL distribution name.\n * @param linuxRoot - the workspace's absolute Linux path.\n * @param io - filesystem face.\n * @returns discovered skill directories, bounded by depth and budget.\n */\nasync function discoverSkillRoots(distro: string, linuxRoot: string, io: WslSkillIo): Promise<SkillRoot[]> {\n const roots: SkillRoot[] = []\n const visited = new Set<string>()\n // BFS layers so the budget prunes the widest, most redundant levels first\n // (shallow skill dirs matter most): [path, depth] pairs.\n let frontier: [string, number][] = [[linuxRoot, 0]]\n while (frontier.length > 0 && roots.length < MAX_SKILL_ROOTS) {\n const next: [string, number][] = []\n for (const [dir, depth] of frontier) {\n if (visited.size >= MAX_VISITED_DIRECTORIES) return roots\n if (visited.has(dir)) continue\n visited.add(dir)\n if (roots.length < MAX_SKILL_ROOTS) {\n const directoryRoots = await skillRootsOfDirectory(distro, dir, io)\n roots.push(...directoryRoots.slice(0, MAX_SKILL_ROOTS - roots.length))\n }\n if (depth >= MAX_SCAN_DEPTH) continue\n let entries: Dirent[]\n try {\n entries = await io.readdir(joinUnc(distro, dir), { withFileTypes: true })\n } catch {\n // An unreadable directory (permissions, vanished mid-walk) prunes its subtree.\n continue\n }\n for (const entry of entries) {\n if (PRUNED_DIRECTORY_NAMES.has(entry.name)) continue\n if (entry.name.startsWith('.') && entry.name !== '.dsh' && entry.name !== '.agents') continue\n if (entry.name === '.dsh' || entry.name === '.agents') continue\n const childPath = posix.join(dir, entry.name)\n if (entry.isDirectory()) {\n next.push([childPath, depth + 1])\n continue\n }\n if (entry.isSymbolicLink()) {\n // A project may be linked into the workspace via a directory\n // symlink; follow it when the target is a directory. Symlink\n // cycles stay bounded: every hop increments the depth (capped by\n // MAX_SCAN_DEPTH) and the walk as a whole by MAX_VISITED_DIRECTORIES.\n try {\n const target = await io.stat(joinUnc(distro, childPath))\n if (target.isDirectory()) next.push([childPath, depth + 1])\n } catch {\n // Dangling symlink: nothing to traverse.\n }\n }\n }\n }\n frontier = next\n }\n return roots\n}\n\n/**\n * Publish the skill roots of one scanned directory (its `.dsh/skills` and\n * `.agents/skills`, each with the host's project ranks).\n * @param distro - the WSL distribution name.\n * @param linuxDir - the scanned directory's Linux path.\n * @param io - filesystem face.\n * @returns the directory's skill roots that exist.\n */\nasync function skillRootsOfDirectory(distro: string, linuxDir: string, io: WslSkillIo): Promise<SkillRoot[]> {\n const result: SkillRoot[] = []\n for (const [marker, source, rank] of [\n ['.dsh', 'project-dsh', PROJECT_DSH_RANK],\n ['.agents', 'project-agents', PROJECT_AGENTS_RANK],\n ] as const) {\n const path = joinUnc(distro, posix.join(linuxDir, marker, 'skills'))\n try {\n const info = await io.stat(path)\n if (info.isDirectory()) result.push({ path, source, rank })\n } catch {\n // Absent skills directory: nothing to publish.\n }\n }\n return result\n}\n\n/** List one skills directory's entries (directory bundles and flat `.md` skills). */\nasync function listSkillEntries(root: SkillRoot, io: WslSkillIo): Promise<SkillEntry[]> {\n let dirents: Dirent[]\n try {\n dirents = await io.readdir(root.path, { withFileTypes: true })\n } catch {\n return []\n }\n const entries: SkillEntry[] = []\n for (const entry of dirents) {\n if (entry.isDirectory()) {\n entries.push({ name: entry.name, kind: 'bundle', path: joinWindowsPath(root.path, entry.name, 'SKILL.md') })\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n entries.push({ name: entry.name.slice(0, -3), kind: 'flat', path: joinWindowsPath(root.path, entry.name) })\n }\n }\n return entries.sort((a, b) => a.name.localeCompare(b.name))\n}\n\n/** Read and parse one skill file; `undefined` when missing or unparsable. */\nasync function readSkill(path: string, io: WslSkillIo, signal?: AbortSignal): Promise<ParsedSkill | undefined> {\n signal?.throwIfAborted()\n let raw: string\n try {\n raw = await io.readFile(path, { encoding: 'utf8' })\n } catch {\n return undefined\n }\n signal?.throwIfAborted()\n return parseSkillFrontmatter(raw, path)\n}\n\n/**\n * Parse the frontmatter subset skill files use: `---` fenced YAML with\n * `name` / `description` / `whenToUse` / `user-invocable` /\n * `disable-model-invocation`. Single-line scalars and block scalars\n * (`|` literal, `>` folded) are understood; anything else is skipped,\n * matching the shipped provider's leniency: a bad file must not fail\n * the catalog.\n */\nfunction parseSkillFrontmatter(raw: string, path: string): ParsedSkill | undefined {\n // Windows editors save UTF-8 with a BOM; a leading BOM must not make the\n // opening `---` line unmatchable and silently drop the skill.\n if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1)\n const firstLineEnd = raw.indexOf('\\n')\n if (firstLineEnd < 0) return undefined\n if (raw.slice(0, firstLineEnd).replace(/\\r$/, '') !== '---') return undefined\n const start = firstLineEnd + 1\n const closing = findFrontmatterEnd(raw, start)\n if (closing === undefined) return undefined\n const lines = raw.slice(start, closing).split('\\n')\n const fields = new Map<string, string>()\n for (let index = 0; index < lines.length; index += 1) {\n const line = (lines[index] ?? '').replace(/\\r$/, '')\n const block = /^([A-Za-z0-9-]+):\\s*([|>])[+-]?\\s*$/.exec(line)\n if (block !== null) {\n const value = parseBlockScalar(lines, index, block[2] === '>')\n index = value.nextLineIndex\n if (value.text !== '') fields.set(block[1] ?? '', value.text)\n continue\n }\n const match = /^([A-Za-z0-9-]+):\\s*(.*)$/.exec(line)\n if (match === null) continue\n const value = match[2]?.trim() ?? ''\n if (value !== '') fields.set(match[1] ?? '', unquote(value))\n }\n const name = fields.get('name') ?? ''\n const description = fields.get('description') ?? ''\n if (!SKILL_NAME.test(name) || description === '') {\n return undefined\n }\n const whenToUse = fields.get('whenToUse')\n return {\n name,\n description,\n ...whenToUse !== undefined && whenToUse !== '' ? { whenToUse } : {},\n invocation: {\n modelInvocable: !frontmatterBoolean(fields, 'disable-model-invocation'),\n userInvocable: frontmatterBoolean(fields, 'user-invocable', true),\n },\n // `findFrontmatterEnd` returns the index of the body's FIRST character\n // (the newline after the closing `---` plus one), so slicing at `closing`\n // is what keeps it: `closing + 1` dropped the body's first character and\n // made the byte after the delimiter look like the body.\n content: raw.slice(closing).trim(),\n }\n}\n\n/**\n * Collect a YAML block scalar (`key: |` literal or `key: >` folded) starting\n * at `startIndex`'s following lines. The block runs until the first\n * non-indented, non-blank line; its common indentation is stripped.\n * @returns the scalar text and the index of the last consumed line.\n */\nfunction parseBlockScalar(\n lines: string[],\n startIndex: number,\n folded: boolean,\n): { text: string; nextLineIndex: number } {\n const collected: string[] = []\n let indent: string | undefined\n let index = startIndex\n while (index + 1 < lines.length) {\n index += 1\n const next = (lines[index] ?? '').replace(/\\r$/, '')\n if (next.trim() === '') {\n collected.push('')\n continue\n }\n const indented = /^([ \\t]+)(.*)$/.exec(next)\n if (indented === null) {\n index -= 1\n break\n }\n indent ??= indented[1]\n collected.push(indented[1]?.startsWith(indent) === true ? indented[2] : indented[1].replace(/^[ \\t]+/, '') + indented[2])\n }\n while (collected.length > 0 && collected[collected.length - 1] === '') collected.pop()\n const text = (folded ? collected.filter(line => line !== '').join(' ') : collected.join('\\n')).trim()\n return { text, nextLineIndex: index }\n}\n\n/** Locate the closing `---` line of a frontmatter block. */\nfunction findFrontmatterEnd(raw: string, start: number): number | undefined {\n let lineStart = start\n while (lineStart <= raw.length) {\n const nextNewline = raw.indexOf('\\n', lineStart)\n const lineEnd = nextNewline < 0 ? raw.length : nextNewline\n if (raw.slice(lineStart, lineEnd).replace(/\\r$/, '') === '---') return lineEnd + 1\n if (nextNewline < 0) return undefined\n lineStart = nextNewline + 1\n }\n return undefined\n}\n\n/** Strip one level of matching quotes from a scalar value. */\nfunction unquote(value: string): string {\n if (value.length >= 2) {\n const first = value[0]\n const last = value[value.length - 1]\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\")) {\n return value.slice(1, -1)\n }\n }\n return value\n}\n\n/** Boolean semantics for `user-invocable` / `disable-model-invocation` (matches the host parser). */\nfunction frontmatterBoolean(fields: Map<string, string>, key: string, dflt = false): boolean {\n const value = fields.get(key)\n if (value === undefined) return dflt\n switch (value.toLowerCase()) {\n case 'true':\n case 'yes':\n case 'on':\n case '1':\n return true\n case 'false':\n case 'no':\n case 'off':\n case '0':\n return false\n default:\n return dflt\n }\n}\n\n/**\n * The WSL workspace skill provider. Registered on the host's `ctx.skills`\n * registry; serves only lookups whose cwd is a WSL UNC workspace path.\n *\n * Completed `list()` lookups are cached per scan root for CACHE_TTL_MS so\n * repeated catalog builds over the slow 9P share do not rescan the tree;\n * `get()` always re-reads the skill file so body edits are picked up\n * immediately. `control.invalidate` remains reserved for host-driven\n * invalidation; the provider self-invalidates through the TTL.\n */\nexport class WslSkillsProvider {\n readonly name = 'wsl-workspace'\n private readonly control: WslSkillProviderControl\n private readonly io: WslSkillIo\n private readonly now: () => number\n private readonly cache = new Map<string, { expiresAt: number; candidates: WslSkillCandidate[] }>()\n\n constructor(control: WslSkillProviderControl, io: WslSkillIo = nodeSkillIo, now: () => number = Date.now) {\n this.control = control\n this.io = io\n this.now = now\n }\n\n /**\n * Discover nested project skills for a WSL UNC session workspace.\n * @param options - lookup options; `cwd` selects the WSL workspace.\n * @returns candidates for every `.dsh/skills` / `.agents/skills` under the\n * session's scan root — the nearest `.git` ancestor of the cwd, else the\n * cwd itself — or an empty array for non-WSL lookups.\n */\n async list(options: WslSkillLookupOptions): Promise<WslSkillCandidate[]> {\n this.control.signal.throwIfAborted()\n options.signal?.throwIfAborted()\n const unc = options.cwd === undefined ? null : parseWslUnc(options.cwd)\n if (unc === null) return []\n // Host parity: the session's project root is the nearest `.git` ancestor\n // of the cwd, so lookups from inside a project subtree still see that\n // project's skills; nested projects below it join via the bounded BFS.\n // Without a `.git` ancestor the session cwd itself is the scan root (the\n // issue #10 workspace layout).\n const scanRoot = (await nearestGitAncestor(unc.distro, unc.linuxPath, this.io)) ?? unc.linuxPath\n const cacheKey = `${unc.distro}\\u0000${scanRoot}`\n const cached = this.cache.get(cacheKey)\n if (cached !== undefined && cached.expiresAt > this.now()) {\n this.cache.delete(cacheKey)\n this.cache.set(cacheKey, cached)\n return [...cached.candidates]\n }\n const roots = await discoverSkillRoots(unc.distro, scanRoot, this.io)\n const candidates: WslSkillCandidate[] = []\n const seenSkills = new Set<string>()\n for (const root of roots) {\n const entries = await listSkillEntries(root, this.io)\n for (const entry of entries) {\n options.signal?.throwIfAborted()\n const parsed = await readSkill(entry.path, this.io, options.signal)\n if (parsed === undefined) continue\n // A project reachable through both its real path and a directory\n // symlink yields aliasing roots whose locators differ; publish each\n // distinct (name, body) once so the catalog shows no duplicates.\n const fingerprint = `${parsed.name}\\u0000${parsed.content}`\n if (seenSkills.has(fingerprint)) continue\n seenSkills.add(fingerprint)\n candidates.push({\n name: parsed.name,\n description: parsed.description,\n ...parsed.whenToUse !== undefined ? { whenToUse: parsed.whenToUse } : {},\n invocation: parsed.invocation,\n source: root.source,\n provider: this.name,\n rank: root.rank,\n locator: {\n path: entry.path,\n directory: entry.kind === 'bundle'\n ? joinWindowsPath(entry.path, '..')\n : root.path,\n },\n path: entry.path,\n })\n }\n }\n this.cache.set(cacheKey, { expiresAt: this.now() + CACHE_TTL_MS, candidates })\n while (this.cache.size > CACHE_MAX_ENTRIES) {\n const oldest = this.cache.keys().next().value\n if (oldest === undefined) break\n this.cache.delete(oldest)\n }\n return candidates\n }\n\n /**\n * Load a complete skill body for a previously listed candidate.\n * @param candidate - the candidate this provider returned.\n * @param options - lookup options whose signal cancels the read.\n * @returns the full skill, or `undefined` if the file disappeared.\n */\n async get(candidate: WslSkillCandidate, options: WslSkillLookupOptions): Promise<WslSkillDefinition | undefined> {\n this.control.signal.throwIfAborted()\n const parsed = await readSkill(candidate.locator.path, this.io, options.signal)\n if (parsed === undefined || parsed.name !== candidate.name) return undefined\n return {\n name: parsed.name,\n description: parsed.description,\n ...parsed.whenToUse !== undefined ? { whenToUse: parsed.whenToUse } : {},\n invocation: parsed.invocation,\n source: candidate.source,\n provider: candidate.provider,\n rank: candidate.rank,\n locator: candidate.locator,\n path: candidate.path,\n content: parsed.content,\n }\n }\n}","/**\n * Host half of dsh-wsl-workspace. Three responsibilities:\n *\n * 1. Materialize a `wsl-<mode>` variant for every healthy roster preset\n * under `<dshHome>/.agent-presets/` (the roster's auto-scanned user\n * root), so the WSL execution world — `shell-wsl` + `fs-wsl` behind one\n * entry-local realm, with `tool-bash`/`tool-fs` consumers — composes with\n * ANY mode instead of being a mode itself; the legacy standalone `wsl`\n * preset directory and stale variants are removed on boot. The preset\n * rows name THIS package's built lib files by absolute path, which the\n * preset mount resolves to `file:` URLs without relying on bare specifier\n * resolution from the preset's home directory.\n *\n * 2. Serve the browser dialog's data route (`/wsl-workspace/api`):\n * distribution discovery, one-level directory listing, path checks, and\n * the per-workspace username store — all over the 9P UNC share.\n * Loopback-only, matching the sensitivity of the privileged configuration\n * surface.\n *\n * 3. Contribute the per-session `DSH_WSL_DISTRO` managed-env fact so the WSL\n * shell executor can resolve a plain Linux `workdir` to the calling\n * session's distribution.\n * @module dsh-wsl-workspace\n */\n\nimport { Context } from '@deepseek-ai/cordis'\nimport z from '@deepseek-ai/schemastery'\nimport { cpSync, existsSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, join } from 'node:path'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { homedir } from 'node:os'\nimport { joinUnc, mntToWindowsPath, normalizeLinuxPath, isAbsoluteLinuxPath, isValidWslUsername, parseWslUnc } from './shared/paths.ts'\nimport { canonicalWslUnc, getWindowsWorkspace, getWorkspaceUsername, listWorkspaceKeys, registerWindowsWorkspace, setWorkspaceUsername } from './shared/wsl-credentials.ts'\nimport { defaultDistro, listDistros } from './shared/wsl.ts'\nimport { isWslVariantId, transformPresetForWsl, variantIdFor } from './host/variants.ts'\nimport { WslSkillsProvider, type WslSkillsRegistryFace } from './host/wsl-skills.ts'\n\n/** The HTTP route this plugin serves (a relative, same-origin path). */\nexport const DEFAULT_ROUTE = '/wsl-workspace/api'\n\n/**\n * Bilingual display labels for the shipped source modes, matching the app's\n * own built-in copy in each language — note the `code` preset is \"PTC 模式\"\n * in the Chinese copy but \"Code mode\" in English. The DSH picker localizes\n * only the four built-in ids itself; `wsl-*` variant ids render the\n * preset.yml text verbatim, so the plugin writes one bilingual string so\n * both locales can identify each variant. Custom presets keep their own\n * name.\n */\nconst MODE_DISPLAY_LABELS: Readonly<Record<string, { en: string; zh: string }>> = {\n standard: { en: 'Standard mode', zh: '标准模式' },\n code: { en: 'Code mode', zh: 'PTC 模式' },\n minimal: { en: 'Minimal mode', zh: '极简模式' },\n cordis: { en: 'Creator mode', zh: '创造模式' },\n}\n\n/**\n * Quote a value as a single-line YAML single-quoted scalar. Plain scalars\n * cannot contain `: ` (colon + space), which plain English sentences do —\n * written unquoted they make the whole preset.yml unparsable, dropping the\n * name, description and order together.\n */\nfunction yamlScalar(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`\n}\n\n/** The variant name for one shipped mode (bilingual) or a custom preset. */\nfunction variantName(presetId: string, sourceName: string): string {\n const labels = MODE_DISPLAY_LABELS[presetId]\n return labels === undefined ? `WSL · ${sourceName}` : `WSL · ${labels.en}(${labels.zh})`\n}\n\n/** The variant description for one shipped mode (bilingual) or a custom preset. */\nfunction variantDescription(presetId: string): string {\n const labels = MODE_DISPLAY_LABELS[presetId]\n const display = labels === undefined ? presetId : `${labels.en}(${labels.zh})`\n return `WSL execution world for ${display}: bash and file tools run inside the WSL distribution.`\n}\n\n/** Plugin config. */\nexport interface Config {\n /** The route under which the dialog data API is served. */\n route?: string\n}\n\n/** The shape after schemastery applied the defaults. */\ntype ResolvedConfig = Required<Config>\n\n/** The `webServer.register` route contract this plugin consumes. */\ninterface WebServerRoute {\n kind: 'exact'\n path: string\n handler(req: IncomingMessage, res: ServerResponse): Promise<void>\n}\n\ninterface WebServerService {\n register(route: WebServerRoute): () => void\n}\n\n/** The `ctx.shellEnv` registry face this plugin consumes (optional service). */\ninterface ShellEnvService {\n register(contributor: {\n name: string\n variables: Readonly<Record<string, { description: string }>>\n resolve(execution: {\n agent?: { session: { header: { cwd?: string } } }\n }): Readonly<Partial<Record<string, string>>>\n }): () => void\n}\n\n/** One directory entry the dialog lists. */\ninterface WslDirEntryWire {\n name: string\n kind: 'directory' | 'file' | 'other'\n}\n\n/** One directory level plus its breadcrumb ancestry. */\ninterface WslDirListingWire {\n path: string\n parent: string | null\n entries: WslDirEntryWire[]\n}\n\n/** The wire envelope every method answers with. */\ntype Envelope<T> = { ok: true; value: T } | { ok: false; error: string }\n\nconst MAX_BODY_BYTES = 1024 * 1024\n\n/** Valid WSL distribution names: one path-safe segment (no separators, no dot-dirs). */\nconst DISTRO_PATTERN = /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/\n\n/** The loopback hostnames the data route answers to (DNS-rebinding fence). */\nconst LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1', '::ffff:127.0.0.1'])\n\n/** True when a socket address is loopback (any IPv4/IPv6 spelling). */\nfunction isLoopback(address: string | undefined): boolean {\n return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'\n}\n\n/** The hostname part of a `Host` header value (port and IPv6 brackets stripped). */\nfunction hostNameOf(host: string): string {\n if (host.startsWith('[')) {\n const end = host.indexOf(']')\n return end >= 0 ? host.slice(1, end) : host\n }\n return host.split(':')[0] ?? ''\n}\n\n/** True when the request's `Host` header names a loopback host. */\nfunction isLoopbackHost(host: string | undefined): boolean {\n return host !== undefined && LOOPBACK_HOSTNAMES.has(hostNameOf(host).toLowerCase())\n}\n\n/**\n * Validate a wire-supplied distribution name before it becomes a UNC segment:\n * an attacker-controlled segment containing separators or `..` would escape\n * the `\\\\wsl.localhost\\` share structure into arbitrary UNC paths.\n * @param value - the raw wire value.\n * @returns the validated distro name.\n */\nfunction requireDistro(value: unknown): string {\n if (typeof value !== 'string' || !DISTRO_PATTERN.test(value) || value === '.' || value === '..') {\n throw new Error('distro must be a valid WSL distribution name')\n }\n return value\n}\n\n/** Human text for an unknown rejection. */\nfunction messageOf(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/** Write one JSON envelope. */\nfunction json(res: ServerResponse, status: number, body: Envelope<unknown>): void {\n res.writeHead(status, {\n 'content-type': 'application/json; charset=utf-8',\n 'cache-control': 'no-store',\n 'x-content-type-options': 'nosniff',\n })\n res.end(JSON.stringify(body))\n}\n\n/** Collect and parse the request body, bounded. */\nasync function readBody(req: IncomingMessage): Promise<Record<string, unknown>> {\n const chunks: Buffer[] = []\n let size = 0\n for await (const chunk of req) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)\n size += buffer.length\n if (size > MAX_BODY_BYTES) throw new Error('request body is too large')\n chunks.push(buffer)\n }\n const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')) as unknown\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error('request body must be a JSON object')\n }\n return parsed as Record<string, unknown>\n}\n\n/** Normalize a Linux path for the wire (rejecting non-absolute input). */\nfunction requireLinuxPath(value: unknown, label: string): string {\n if (typeof value !== 'string' || !isAbsoluteLinuxPath(value)) {\n throw new Error(`${label} must be an absolute Linux path`)\n }\n return normalizeLinuxPath(value)\n}\n\n/** Validate a wire-supplied workspace path and return its canonical UNC form. */\nfunction requireWslUnc(value: unknown): string {\n if (typeof value !== 'string') throw new Error('path must be a string')\n const canonical = canonicalWslUnc(value)\n if (canonical === null) throw new Error('path must be a WSL UNC workspace path')\n return canonical\n}\n\n/**\n * Resolve one directory listing for the dialog. The 9P share (`\\\\wsl.localhost\\…`)\n * serves only the ext4 volume: `/mnt/<drive>` (drvfs) reads return Access\n * denied, so drvfs paths are read through their Windows drive spelling and\n * `/mnt` itself is synthesized from the drives present on the host.\n */\nfunction listWslDir(distro: string, linuxPath: string): WslDirListingWire {\n if (linuxPath === '/mnt') {\n const entries: WslDirEntryWire[] = []\n for (let i = 0; i < 26; i++) {\n const letter = String.fromCharCode(65 + i)\n try {\n const info = statSync(`${letter}:\\\\`)\n if (info.isDirectory()) entries.push({ name: letter.toLowerCase(), kind: 'directory' })\n } catch {\n // Absent drive: skip.\n }\n }\n return { path: '/mnt', parent: '/', entries }\n }\n const winPath = mntToWindowsPath(linuxPath)\n const readPath = winPath !== null ? winPath : joinUnc(distro, linuxPath)\n const dirents = readdirSync(readPath, { withFileTypes: true })\n const entries: WslDirEntryWire[] = dirents\n .slice(0, 1000)\n .map((dirent): WslDirEntryWire => {\n const kind: WslDirEntryWire['kind'] = dirent.isDirectory()\n ? 'directory'\n : dirent.isFile() ? 'file' : 'other'\n return { name: dirent.name, kind }\n })\n .sort((a, b) => {\n if (a.kind === 'directory' && b.kind !== 'directory') return -1\n if (a.kind !== 'directory' && b.kind === 'directory') return 1\n return a.name.localeCompare(b.name)\n })\n const parent = linuxPath === '/' ? null : linuxPath.split('/').slice(0, -1).join('/') || '/'\n return { path: linuxPath, parent, entries }\n}\n\n/** Cached self-description for the dialog's help panel. */\nlet selfDescription: { version: string; releases: { id: string; status: string }[] } | undefined\n\n/**\n * Read this plugin's own `package.json` for the dialog's help panel: the\n * published version and the declared `dsh.compatibility.dshReleases` matrix.\n * A plugin directory that cannot be read reports empty values rather than\n * failing the dialog, and the result is cached for the process lifetime.\n * @returns the self-description served by the `describe` method.\n */\nfunction describeSelf(): { version: string; releases: { id: string; status: string }[] } {\n if (selfDescription !== undefined) return selfDescription\n try {\n const raw = readFileSync(new URL('../package.json', import.meta.url), 'utf8')\n const parsed = JSON.parse(raw) as {\n version?: unknown\n dsh?: { compatibility?: { dshReleases?: Record<string, unknown> } }\n }\n selfDescription = {\n version: typeof parsed.version === 'string' ? parsed.version : 'unknown',\n releases: Object.entries(parsed.dsh?.compatibility?.dshReleases ?? {})\n .map(([id, status]) => ({ id, status: String(status) })),\n }\n } catch {\n selfDescription = { version: 'unknown', releases: [] }\n }\n return selfDescription\n}\n\n/** Route one method dispatch. */\nasync function dispatch(method: string, params: Record<string, unknown>): Promise<unknown> {\n switch (method) {\n case 'listDistros': {\n const distros = await listDistros()\n const fallback = await defaultDistro()\n if (fallback !== undefined && distros.includes(fallback)) {\n return [fallback, ...distros.filter(name => name !== fallback)]\n }\n return distros\n }\n case 'listDir': {\n const distro = requireDistro(params.distro)\n const path = requireLinuxPath(params.path, 'path')\n return listWslDir(distro, path)\n }\n case 'check': {\n const distro = requireDistro(params.distro)\n const path = requireLinuxPath(params.path, 'path')\n // drvfs paths (Access denied over 9P) are checked through their drive spelling.\n const winPath = mntToWindowsPath(path)\n const readPath = winPath !== null ? winPath : joinUnc(distro, path)\n try {\n const info = statSync(readPath)\n return { exists: true, isDirectory: info.isDirectory() }\n } catch {\n return { exists: false, isDirectory: false }\n }\n }\n case 'registerWindows': {\n // A `/mnt/<drive>` workspace registers under its Windows drive path\n // (the registry realpath/stats it, and 9P cannot serve drvfs) with the\n // distro stored for the per-session env contributor.\n const distro = requireDistro(params.distro)\n const linuxPath = requireLinuxPath(params.linuxPath, 'path')\n const winPath = mntToWindowsPath(linuxPath)\n if (winPath === null) throw new Error('registerWindows requires a /mnt/<drive> Linux path')\n const username = typeof params.username === 'string' ? params.username : undefined\n registerWindowsWorkspace(winPath, distro, username)\n return null\n }\n case 'listWorkspaces': {\n // Every registered WSL workspace key (UNC and Windows drive spellings):\n // the client uses the drive keys to recognize `/mnt` workspaces.\n return listWorkspaceKeys()\n }\n case 'describe': {\n // The dialog's help panel reports what this build declares, so the\n // compatibility list can never drift from package.json.\n return describeSelf()\n }\n case 'setUser': {\n const path = requireWslUnc(params.path)\n const username = params.username\n if (username === undefined || username === '') {\n setWorkspaceUsername(path, undefined)\n } else {\n if (typeof username !== 'string' || !isValidWslUsername(username)) {\n throw new Error('username must match the Linux username pattern [A-Za-z_][A-Za-z0-9_.-]*')\n }\n setWorkspaceUsername(path, username)\n }\n return null\n }\n default:\n throw new Error(`unknown method \"${method}\"`)\n }\n}\n\n/** The `ctx.agentPresets` roster face this plugin consumes (optional service). */\ninterface AgentPresetsService {\n list(): Promise<{ id: string; broken?: string; path: string }[]>\n read(id: string): Promise<string>\n}\n\n/**\n * Publish a fully staged preset directory while preserving the last complete\n * variant if publication fails. Stable sibling names also let the next boot\n * recover an interrupted old-to-backup rename before doing new work.\n */\nfunction publishVariant(staging: string, dest: string): void {\n const previous = `${dest}.previous`\n if (!existsSync(dest) && existsSync(previous)) renameSync(previous, dest)\n if (existsSync(previous)) rmSync(previous, { recursive: true, force: true })\n if (existsSync(dest)) renameSync(dest, previous)\n try {\n renameSync(staging, dest)\n } catch (error) {\n if (!existsSync(dest) && existsSync(previous)) renameSync(previous, dest)\n throw error\n }\n rmSync(previous, { recursive: true, force: true })\n}\n\n/** Materialize one WSL variant per healthy source preset. */\nasync function materializeVariants(\n agentPresets: AgentPresetsService,\n dshHome: string,\n shellPath: string,\n fsPath: string,\n): Promise<void> {\n const presets = await agentPresets.list()\n const userRoot = join(dshHome, '.agent-presets')\n const generated = new Set<string>()\n for (const preset of presets) {\n if (preset.broken !== undefined) continue\n if (isWslVariantId(preset.id)) continue\n const variantId = variantIdFor(preset.id)\n const source = await agentPresets.read(preset.id)\n const transformed = transformPresetForWsl(source, shellPath, fsPath)\n const dir = join(userRoot, variantId)\n const staging = `${dir}.staging`\n rmSync(staging, { recursive: true, force: true })\n // A preset directory is an opaque, self-contained plugin unit. Mirror it\n // without interpreting local code or asset names, then overwrite only the\n // two files owned by this generator.\n cpSync(dirname(preset.path), staging, { recursive: true, force: true })\n writeFileSync(join(staging, 'agent.cordis.yml'), transformed, 'utf8')\n const labels = MODE_DISPLAY_LABELS[preset.id]\n let name = variantName(preset.id, preset.id)\n let orderLine = ''\n try {\n const meta = readFileSync(join(dirname(preset.path), 'preset.yml'), 'utf8')\n if (labels === undefined) {\n // Custom presets keep their own display name; shipped modes use the\n // bilingual labels above so both locales can identify the variant.\n const match = /^name:\\s*(.+)$/m.exec(meta)\n if (match?.[1] !== undefined && match[1].trim() !== '') {\n name = variantName(preset.id, match[1].trim())\n }\n }\n // Inherit the source's declared order so the WSL variants line up with\n // the local modes in the roster (standard, PTC, minimal, cordis).\n const orderMatch = /^order:\\s*(\\d+)\\s*$/m.exec(meta)\n if (orderMatch?.[1] !== undefined) orderLine = `order: ${orderMatch[1]}\\n`\n } catch {\n // Absent or unreadable display metadata falls back to the id-based name.\n }\n writeFileSync(\n join(staging, 'preset.yml'),\n `name: ${yamlScalar(name)}\\n`\n + orderLine\n + `description: ${yamlScalar(variantDescription(preset.id))}\\n`,\n 'utf8',\n )\n publishVariant(staging, dir)\n generated.add(variantId)\n }\n for (const entry of readdirSync(userRoot, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n if (entry.name === 'wsl') {\n // The legacy standalone WSL mode: folded into the variants above.\n rmSync(join(userRoot, entry.name), { recursive: true, force: true })\n continue\n }\n if (!/^wsl-[a-z0-9-]+$/.test(entry.name)) continue\n if (!generated.has(entry.name)) rmSync(join(userRoot, entry.name), { recursive: true, force: true })\n }\n}\n\n/** Function-plugin plugin contract. */\nexport const name = 'dsh-wsl-workspace'\n\n/** Required services. */\nexport const inject = ['webServer']\n\n/** Validated plugin config (schemastery applied the defaults). */\nexport const Config: z<Config> = z.object({\n route: z.string().default(DEFAULT_ROUTE),\n})\n\n/**\n * Apply the host half: materialize a `wsl-<mode>` variant for every healthy\n * roster preset, register the data route, and\n * contribute the per-session `DSH_WSL_DISTRO` managed-env fact so the WSL\n * shell executor can resolve a plain Linux `workdir` to the calling\n * session's distribution.\n * @param ctx - the host plugin context.\n * @param config - the validated configuration.\n */\nexport function apply(ctx: Context, config: Config): void {\n const resolved = config as ResolvedConfig\n const dshHome = process.env.DSH_HOME ?? join(homedir(), '.dsh')\n const packageRoot = fileURLToPath(new URL('..', import.meta.url))\n const shellPath = join(packageRoot, 'lib', 'shell.js').replace(/\\\\/g, '/')\n const fsPath = join(packageRoot, 'lib', 'fs.js').replace(/\\\\/g, '/')\n\n const agentPresets = ctx.get('agentPresets') as unknown as AgentPresetsService | undefined\n if (agentPresets !== undefined) {\n ctx.effect(() => {\n void materializeVariants(agentPresets, dshHome, shellPath, fsPath).catch((error) => {\n // Variant generation is best-effort over a live roster: a missing or\n // unreadable source preset must not take the whole plugin down, but\n // the failure is surfaced loudly rather than hidden.\n console.error(`dsh-wsl-workspace: WSL preset-variant generation failed: ${messageOf(error)}`)\n })\n return () => {}\n }, 'dsh-wsl-workspace: WSL preset variants')\n }\n\n const skills = ctx.get('skills') as unknown as WslSkillsRegistryFace | undefined\n if (skills !== undefined && typeof skills.registerProvider === 'function') {\n // The shipped skill-filesystem provider scans only the session cwd's\n // project root, so WSL workspaces whose `.dsh/skills` live in nested\n // projects would show an empty skill catalog. This provider mirrors the\n // host's project discovery for WSL UNC workspaces, bounded to the\n // workspace root (issue #10). The method check keeps a host whose\n // `skills` service exists with a different shape from breaking plugin\n // load; the provider is an enhancement, never a load-time requirement.\n ctx.effect(() => skills.registerProvider(\n control => new WslSkillsProvider(control),\n ), 'dsh-wsl-workspace: WSL workspace skills provider')\n }\n\n const shellEnv = ctx.get('shellEnv') as unknown as ShellEnvService | undefined\n if (shellEnv !== undefined) {\n ctx.effect(() => shellEnv.register({\n name: 'wsl-workspace-distro',\n variables: {\n DSH_WSL_DISTRO: {\n description: 'The WSL distribution of the calling session workspace, when the session cwd is a WSL UNC path.',\n },\n DSH_WSL_USER: {\n description: 'The Linux user of the calling session workspace, when the workspace has one configured.',\n },\n },\n resolve(execution) {\n const cwd = execution.agent?.session.header.cwd\n const unc = cwd === undefined ? null : parseWslUnc(cwd)\n if (unc !== null) {\n const username = getWorkspaceUsername(joinUnc(unc.distro, unc.linuxPath))\n return username === undefined || username === ''\n ? { DSH_WSL_DISTRO: unc.distro }\n : { DSH_WSL_DISTRO: unc.distro, DSH_WSL_USER: username }\n }\n // A Windows-drive cwd belongs to a `/mnt/<drive>` WSL workspace (9P\n // cannot serve drvfs, so those register under their drive path): the\n // stored distro drives `wsl.exe -d` for bash.\n if (cwd !== undefined && /^[A-Za-z]:[\\\\/]/.test(cwd)) {\n const entry = getWindowsWorkspace(cwd)\n if (entry !== undefined && entry.distro !== undefined && entry.distro !== '') {\n return entry.username === undefined || entry.username === ''\n ? { DSH_WSL_DISTRO: entry.distro }\n : { DSH_WSL_DISTRO: entry.distro, DSH_WSL_USER: entry.username }\n }\n }\n return {}\n },\n }), 'dsh-wsl-workspace: per-session distro env fact')\n }\n\n const webServer = ctx.get('webServer') as unknown as WebServerService\n ctx.effect(() => webServer.register({\n kind: 'exact',\n path: resolved.route,\n handler: async (req, res) => {\n if (!isLoopback(req.socket.remoteAddress) || !isLoopbackHost(req.headers.host)) {\n json(res, 403, { ok: false, error: 'loopback-only' })\n return\n }\n if (req.method !== 'POST') {\n json(res, 405, { ok: false, error: 'method not allowed' })\n return\n }\n let body: Record<string, unknown>\n try {\n body = await readBody(req)\n } catch (error) {\n json(res, 400, { ok: false, error: messageOf(error) })\n return\n }\n const method = typeof body.method === 'string' ? body.method : ''\n const params = body.params === undefined ? {} : body.params\n if (params === null || typeof params !== 'object' || Array.isArray(params)) {\n json(res, 400, { ok: false, error: 'params must be an object' })\n return\n }\n try {\n const value = await dispatch(method, params as Record<string, unknown>)\n json(res, 200, { ok: true, value })\n } catch (error) {\n json(res, 200, { ok: false, error: messageOf(error) })\n }\n },\n }), 'dsh-wsl-workspace: dialog data route')\n}\n"],"mappings":"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"}