dsh-wsl-workspace 0.4.3 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -3
- package/README.zh.md +101 -2
- package/TESTING.md +32 -6
- package/lib/client.js +44 -6
- package/lib/client.js.map +1 -1
- package/lib/fs.js +135 -4
- package/lib/fs.js.map +1 -1
- package/lib/index.js +508 -83
- package/lib/index.js.map +1 -1
- package/lib/links-BQxdJOMy.js +66 -0
- package/lib/links-BQxdJOMy.js.map +1 -0
- package/lib/shell.js +2 -2
- package/lib/{wsl-6VcgOvF9.js → wsl-CECXiGZH.js} +15 -2
- package/lib/{wsl-6VcgOvF9.js.map → wsl-CECXiGZH.js.map} +1 -1
- package/lib/{wsl-credentials-B6wIXsKt.js → wsl-credentials-BJXc9tfi.js} +2 -2
- package/lib/{wsl-credentials-B6wIXsKt.js.map → wsl-credentials-BJXc9tfi.js.map} +1 -1
- package/lib/wsl-jobs.js +139 -0
- package/lib/wsl-jobs.js.map +1 -0
- package/lib/wsl-relay.js +64 -0
- package/lib/wsl-relay.js.map +1 -0
- package/lib/wsl-sandbox.js +27 -0
- package/lib/wsl-sandbox.js.map +1 -0
- package/lib/wsl-search.js +957 -0
- package/lib/wsl-search.js.map +1 -0
- package/package.json +24 -4
- package/src/client/help.tsx +11 -0
- package/src/client/locales.ts +14 -6
- package/src/client/styles.ts +9 -0
- package/src/fs.ts +210 -6
- package/src/host/variants.ts +211 -24
- package/src/host/wsl-jobs.ts +229 -0
- package/src/host/wsl-relay.ts +89 -0
- package/src/host/wsl-sandbox.ts +47 -0
- package/src/host/wsl-search.ts +1166 -0
- package/src/host/wsl-skills.ts +353 -66
- package/src/index.ts +120 -6
- package/src/shared/links.ts +90 -0
package/README.md
CHANGED
|
@@ -33,12 +33,140 @@ Click "Create & open" to start a new session in the workspace. In the new sessio
|
|
|
33
33
|
## Behavior notes
|
|
34
34
|
|
|
35
35
|
- **bash tool**: runs inside the WSL distribution as the configured username (empty = the distro default user, often `root`), so it can read and write anywhere in the distro. The Windows ACL sandbox cannot wrap `wsl.exe` — its children run on the Linux kernel side — so WSL itself is the isolation boundary and the DSH file policy does not apply to bash.
|
|
36
|
-
- **File tools (`read`/`write`/`edit`)**: go through the Windows-side WSL 9P share
|
|
37
|
-
- **
|
|
36
|
+
- **File tools (`read`/`write`/`edit`)**: go through the Windows-side WSL 9P share; the username field does not affect them. Two seams the host's own provider would supply are re-established inside the WSL world, because a variant mounts this plugin's `fs` provider in the preset's isolate realm where the host's `fs-sandbox` wrapper is not in the call path. **Symlinks**: the share lists a Linux link but cannot resolve it, so a link path used to look like a missing file; `resolve`/`lstat` now ask the distribution (`wsl.exe … readlink -f`) and continue at the real path, and a link is never replaced by a regular file. **The access mode**: `write`/`edit` are fenced by `ctx.sandboxPolicy` exactly as the host backend fences them — the same `writableRoots` allow-list (plus the distribution's `/tmp`, the temp area of the world the session runs in), the same `FS_SANDBOX_DENIED` the tool layer renders as a denial, and the same `sandboxMode` fact it reads for escalation. Because the fence runs after resolution it judges the real path: a link out of the workspace is an outside write and is denied under `workspace-write`. A deployment that mounts no policy service fences nothing, as on the host.
|
|
37
|
+
- **File search (`grep`/`glob`)**: the host's discovery suite spawns a packaged *Windows* ripgrep, so a WSL variant used to drop the row and leave the model to search through the shell. The world now mounts its own twin (`src/host/wsl-search.ts` → `lib/wsl-search.js`), which runs the search **inside the distribution**: the tool names, parameter schemas, caps, output schema (`Line N:` grouping, found-count header, capped-result footer), search cards and formatted-result spill all come from `@deepseek-ai/dsh-tool-fs-search`'s own exported pieces, so what the model sees matches the host. `grep` uses the distribution's GNU grep (`-rnIEH -Z`; POSIX ERE — `\d`, `\w`, `\b` and `(?i)` work, lookaround and backreferences do not), skips hidden files and directories plus `node_modules` like ripgrep's defaults do, does **not** read `.gitignore`, expands `{a,b}` into one `--include` per alternative, matches a `include` containing `/` in this process (ripgrep semantics), and fails loudly on a distribution without GNU grep rather than framing unreadable records. `glob` lists with GNU `find` (`-printf` gives each file's mtime without a stat per file) and matches gitignore-style patterns here (`*` never crosses a separator, `**` does, `?`, `[...]`, `{a,b}` and a leading `!` negation), ordering oldest-first exactly as `rg --sort=modified` does. Both search the Linux tree directly — symlinks, permissions and `.gitignore`-free traversal included — never the 9P share, both prune VCS metadata directories, and neither follows a symlink found during recursion, which is ripgrep's default too. Modes whose source preset mounts no search suite (Minimal) gain none.
|
|
38
|
+
- **Skill catalog**: the session's skill catalog is discovered starting at the session cwd's nearest `.git` ancestor (falling back to the cwd itself), then scanning downward for `.dsh/skills` / `.agents/skills` — including nested projects — bounded to 4 directory levels, 64 skill directories and 4096 visited directories. Register the workspace at the project root you work in; if the registered workspace itself sits inside a larger git repository, the scan starts at that repository's root (matching the host's own rule) and sibling projects may surface. A Linux symlink the Windows-side share cannot resolve is resolved through the distribution instead (`wsl.exe … readlink -f`, at most 32 per lookup, four in flight) and the walk continues at the real path, so a project linked in with `ln -s` — and nested projects below it — is discovered and deduplicated by that real path. Skill bodies always load live. The generated preset pins `watch: false` on the `skill-filesystem` row because watching a `\\wsl.localhost\...` path fails, so the plugin polls instead, in two passes. The cheap pass runs every 3 seconds over the skills directories it published and re-stats each skill file: an added, removed or **edited** skill therefore reaches the model's next turn, and an edit is why the stamp matters — the model's catalog is rebuilt only when the registry's revision moves, and a directory listing cannot tell a rewritten `SKILL.md` from an untouched one. The full re-discovery walk runs every 30 seconds, because only a walk can find a skills directory that did not exist before (a new nested project's first `.dsh/skills`, say). Neither pass re-reads a skill body.
|
|
39
|
+
- **Shell lifetime**: `bash` is a **stateful** shell — `cd`, exported variables, activated virtualenvs and background jobs survive between calls. The world mounts the host's PTY registry and its config-driven backend (`@deepseek-ai/dsh-terminal-bash`) pointed at this plugin's relay (`src/host/wsl-relay.ts` → `lib/wsl-relay.js`, run by the host's own node), which hands the PTY to `wsl.exe … bash`: the distribution comes from the session's UNC cwd (else `DSH_WSL_DISTRO`), the optional username from `DSH_WSL_USER`, and the relay runs `bash -lc 'cd … && exec bash -i'` so the login environment is loaded while the session directory survives (a plain `bash -l` can be sent to `$HOME` by a profile). That tool registers the `bash` name, so it replaces the one-shot `dsh-tool-bash` row a non-WSL preset would use — the world also provides its own no-op `sandbox` capability (`src/host/wsl-sandbox.ts`), because the host's Windows ACL runner cannot read a `\\wsl.localhost\…` path's security descriptor and the PTY backend confines through it before spawning. Both shells run inside the distribution and are outside the DSH file policy — WSL is their isolation boundary. **Two consequences of the host's own wrapping are stated in the tool's description** (which this plugin overrides, because the host default mentions neither): the shell is one process for the whole Agent, so a `cd` in one call decides where the next call starts — use absolute paths or an explicit `cd`; and the host wraps each command as `eval -- $'…'`, so a command ending in `&` backgrounds the *whole* wrapped command — the call then returns immediately with exit code 0 and no output while the real output arrives later, possibly inside the next call's. Write background work as `( long-job > log 2>&1 ) &` on its own line, or use the background-job tool.
|
|
40
|
+
- **Tracked background jobs**: `bash_background` starts one command in the background and returns a registry job id immediately; the host's `job_list`, `job_output` (incremental reads, status transitions, completion notices) and `job_kill` then work on it exactly as they do for the host's one-shot tool. The row exists because the persistent shell's schema declares only `command`: without a producer, `job_list` always answered "no background jobs" and a `run_in_background: true` argument passed to `bash` was silently ignored — the parameter schema does not forbid extra properties, so nothing reported the mistake. A real session found exactly that. The tool is mounted only alongside the persistent shell; a world that keeps the one-shot bash row already has `run_in_background` on that tool.
|
|
41
|
+
- **Older hosts fall back to a one-shot shell**: the persistent stack is the *host's* code, and on Windows it needs a platform process inspector that only exists from `0.1.0-rc.8` on — in `0.1.0-rc.7` `spawnTerminal` throws `subprocess-local: terminal inspection is unsupported on platform win32` before any process starts, so every `bash` call in that release fails outright (grep/glob, which never touch the PTY, keep working). The plugin therefore *probes* the substrate at startup instead of assuming: it hands `spawnTerminal` a program that cannot exist, which reaches the inspector check and nothing else — no process is created either way, and the rejection says which half failed. When the answer is "no inspector", the generated world keeps the one-shot `dsh-tool-bash` row (this plugin's own `ctx.shell` provider, no PTY) and the model gets a working, stateless shell instead of an error on every call. `0.1.0-rc.7` is the only declared release in that state; every later one gets the persistent shell.
|
|
38
42
|
- The garbled `localhost` port-forwarding banner `wsl.exe` prints to stderr when the distro was not running yet is harmless.
|
|
39
43
|
|
|
40
44
|
## Changelog
|
|
41
45
|
|
|
46
|
+
### 0.7.2 — 2026-09-21
|
|
47
|
+
|
|
48
|
+
- **The WSL skill catalog is no longer re-walked on the request path (issue #25).**
|
|
49
|
+
The host rebuilds the catalog during a request and awaits each provider's
|
|
50
|
+
`list()`, and this provider kept its own answer for only 10 s — so every time the
|
|
51
|
+
catalog was re-collected (a new session or scope, or simply a lookup more than
|
|
52
|
+
10 s after the last one) that request paid a full walk of the workspace, one
|
|
53
|
+
directory at a time: two `stat`s and one `readdir` each. A `readdir` over the
|
|
54
|
+
`\\wsl.localhost\…` 9P share measures 3-16 ms here and the walk's budget is 4096
|
|
55
|
+
directories, which is why a large workspace cost 20.4 s, on the request path. A
|
|
56
|
+
published catalog is now served as-is, and only the provider's own change detector
|
|
57
|
+
can drop it: a repeat lookup costs 1-3 ms and no filesystem traffic. The freshness
|
|
58
|
+
contract is unchanged — a new nested skills directory still appears within 30 s,
|
|
59
|
+
and an added, removed or edited skill within 3 s.
|
|
60
|
+
- The walk itself is cheaper: one BFS layer is probed concurrently (bounded) and
|
|
61
|
+
published in frontier order, so the catalog stays deterministic, and a
|
|
62
|
+
directory's `.dsh/skills` / `.agents/skills` are probed only when its own listing
|
|
63
|
+
showed that marker. The budget-sized walk went from 20.4 s to 4.8 s on the same
|
|
64
|
+
machine; node's filesystem thread pool caps the real parallelism.
|
|
65
|
+
|
|
66
|
+
### 0.7.1 — 2026-09-20
|
|
67
|
+
|
|
68
|
+
- **`npm install dsh-wsl-workspace` no longer fails.** Verifying the published
|
|
69
|
+
artifact turned up a regression this release introduced: npm auto-installs
|
|
70
|
+
missing peer dependencies, and the `@deepseek-ai/dsh-tool-fs-search` peer added in
|
|
71
|
+
0.6.0 itself peers on `@deepseek-ai/dsh-retention`, which is **not published** — so
|
|
72
|
+
a plain `npm install` died with `E404 … @deepseek-ai/dsh-retention` (0.4.3 installs
|
|
73
|
+
fine, so it was ours). `dsh plugin add` uses pnpm, which only *warns* about unmet
|
|
74
|
+
peers, which is why every harness run and real install passed. All ten host peers
|
|
75
|
+
are now marked optional in `peerDependenciesMeta`: the package still declares what
|
|
76
|
+
the host must provide, but npm no longer tries to fetch it.
|
|
77
|
+
|
|
78
|
+
### 0.7.0 — 2026-09-20
|
|
79
|
+
|
|
80
|
+
The WSL world now matches the host everywhere a session can tell the difference,
|
|
81
|
+
and the last two known issues are closed. Everything below ships together: a WSL
|
|
82
|
+
variant gets Linux symlinks, the session's access mode, in-distribution search, a
|
|
83
|
+
live skill catalog, a stateful shell, and tracked background jobs.
|
|
84
|
+
|
|
85
|
+
- **The host's `bash` contract is stated in the tool description.** The persistent
|
|
86
|
+
tool wraps each command as `eval -- $'…'`, so a command ending in `&` backgrounds
|
|
87
|
+
the *whole* wrapped command — the call returns immediately with exit code 0 and
|
|
88
|
+
no output while the real output arrives later, possibly inside the next call's.
|
|
89
|
+
And the shell is one process for the whole Agent, so a `cd` carries into the next
|
|
90
|
+
call. The host default says neither, and DSH's own Minimal preset recommends the
|
|
91
|
+
hazardous form (`sleep 10 &`). The world now overrides `description` (a supported
|
|
92
|
+
key on every declared release) with both facts and the safe forms.
|
|
93
|
+
- **`0.1.0-rc.7` falls back to a working one-shot shell.** That release's
|
|
94
|
+
`dsh-subprocess-local` has no Windows process inspector, so the host's PTY-backed
|
|
95
|
+
persistent shell cannot start on Windows at all — every `bash` call failed with
|
|
96
|
+
`subprocess-local: terminal inspection is unsupported on platform win32` (the
|
|
97
|
+
host ships the same gap: its Minimal preset mounts `persistent-bash` there with
|
|
98
|
+
no Windows guard). The plugin now *probes* the substrate instead of assuming —
|
|
99
|
+
it hands `spawnTerminal` a program that cannot exist, which reaches the inspector
|
|
100
|
+
check and nothing else — and when the answer is no, the world keeps the one-shot
|
|
101
|
+
`dsh-tool-bash` row: a working, stateless shell rather than an error per call.
|
|
102
|
+
- **Tracked background jobs, restored.** Replacing the one-shot bash tool with the
|
|
103
|
+
persistent one also removed the only thing that started a registry job, so
|
|
104
|
+
`job_list` always answered "no background jobs" and a `run_in_background: true`
|
|
105
|
+
argument handed to `bash` was silently ignored (the parameter schema allows extra
|
|
106
|
+
properties, so nothing complained). The world now mounts `bash_background`
|
|
107
|
+
(`src/host/wsl-jobs.ts`), a thin producer over the host's own `ctx.jobs.start`
|
|
108
|
+
plus this plugin's `ctx.shell.start`: it returns a job id, and
|
|
109
|
+
`job_list`/`job_output`/`job_kill` work on it as usual. It is mounted only where
|
|
110
|
+
the source mode also mounts the `job_*` tools, and only alongside the persistent
|
|
111
|
+
shell.
|
|
112
|
+
- **Six defects found by hunting the new code with worst-case input**: a hidden-file
|
|
113
|
+
guard that also applied to an explicitly named file (`grep path=.env` returned
|
|
114
|
+
nothing), a discarded `find` exit status (`glob path=/nope-missing` looked like an
|
|
115
|
+
empty directory), a line-terminated glob header (a root whose name contains a
|
|
116
|
+
newline came back truncated), untranslated Windows paths (`grep path='D:\proj'`
|
|
117
|
+
failed where `read` worked), a closed spill schema (which would have failed the
|
|
118
|
+
tool's own output validation on every capped search), and a catalog detector that
|
|
119
|
+
could stack polls on a slow share. Plus two in the new producer: it was mounted in
|
|
120
|
+
a mode with no `job_*` tools to read its ids, and it defaulted a job's working
|
|
121
|
+
directory to the host process's rather than the session workspace.
|
|
122
|
+
- **Verification**: thirteen checks on each of the eight declared releases
|
|
123
|
+
(`0.1.0-rc.7` … `0.1.5-rc.2`) — `search-real` drives the real tools against a real
|
|
124
|
+
distribution fixture — leaving only the two pre-existing baseline failures
|
|
125
|
+
(`typecheck`, and `host-api` which needs a live server). 152 unit tests, including
|
|
126
|
+
a parity check of every renderer against the host suite's own formatters. Real
|
|
127
|
+
browser sessions on five releases for the tool behaviour, and a **frontend pass on
|
|
128
|
+
all eight** (entry button, dialog, path check, create & open, mode picker, help
|
|
129
|
+
panel with v0.7.0 and 8 release chips), with the session log as evidence for the
|
|
130
|
+
tool set, the search results, the catalog replacement, the shell fallback and the
|
|
131
|
+
background-job lifecycle.
|
|
132
|
+
|
|
133
|
+
### 0.6.0 — 2026-09-19
|
|
134
|
+
|
|
135
|
+
- **WSL sessions get `grep` and `glob`**: the host suite spawns the packaged Windows ripgrep and every path the model hands it is a Linux one, so the generated world dropped `tool-fs-search` and left the model to grep through the shell — the last bullet of the panel's known issues. The world now mounts an in-distribution twin that keeps the host suite's contract: the same tool names, parameter schemas, inline caps (250 matches / 100 paths), output schema, `Line N:` grouping, found-count header, capped-result footer, search card and formatted-result spill — the rendering comes from `@deepseek-ai/dsh-tool-fs-search`'s own exported formatters, and the two projections that package keeps private (the card metadata and the glob page) are reproduced and compared against it in unit tests. `grep` runs GNU grep inside the distribution (`-rnIEH -Z`, POSIX ERE, hidden entries and `node_modules` skipped like ripgrep's defaults, no `.gitignore` support), `glob` uses GNU `find` with in-process gitignore-style matching and ripgrep's oldest-first modification order. Model-controlled values travel as separate argv elements after a fixed script, so nothing the model types is ever parsed by a shell.
|
|
136
|
+
- **The skill catalog notices an edited skill, not just an added one**: the catalog message is rebuilt only when the registry's revision moves, and the old detector compared directory listings — so rewriting an existing `SKILL.md` (a description, say) changed nothing it could see and the model kept the old text until a new session. The cheap pass now also stamps every skill file with its modification time and size, and runs every 3 seconds instead of 10. The full re-discovery walk — the only pass that can find a skills directory that did not exist before — moved to its own 30-second cadence, so the change detection is both faster and cheaper than the single 10-second poll it replaces.
|
|
137
|
+
- **`lib/` is rebuilt deterministically**: `tsdown` writes into a committed `lib/`, and stale code-split chunks from an earlier build survived every rebuild (`clean: false` plus two configurations sharing one output directory). Local build tooling now clears the directory first, and the three new runtime peers (`@deepseek-ai/dsh-tool-fs-search`, `@deepseek-ai/dsh-tools`, `@deepseek-ai/schemastery`) are declared, which is also what keeps them external instead of bundling a second copy of DSH's tool stack into this plugin.
|
|
138
|
+
- **Four defects found by hunting the new code with worst-case input**: a hidden-file guard that also applied to an explicitly named file (`grep path=.env` returned nothing), a discarded `find` exit status (`glob path=/nope-missing` looked like an empty directory), a line-terminated glob header (a root whose name contains a newline came back truncated) and untranslated Windows paths (`grep path='D:\proj'` failed where `read` worked). The spill schema was also closed, which would have failed the tool's own output validation on every capped search, and the catalog detector gained an in-flight guard so a slow poll cannot stack `wsl.exe` calls.
|
|
139
|
+
- **The host's `bash` wrapper is documented in the tool description**: the persistent tool wraps each command as `eval -- $'…'`, so a trailing `&` backgrounds the whole wrapped command and the call reports exit code 0 with no output; and the shell is one process, so a `cd` carries into the next call. The host default says neither, and DSH's own Minimal preset recommends `sleep 10 &` — the world now overrides the description with both facts and the safe forms.
|
|
140
|
+
- **`0.1.0-rc.7` no longer gets a broken shell**: that release's `dsh-subprocess-local` has no Windows process inspector, so the host's PTY-backed persistent shell cannot start on Windows at all (the host ships the same gap: its Minimal preset mounts `persistent-bash` there with no Windows guard). The world probes the substrate at startup and, when the answer is no, keeps the one-shot `bash` row — a working stateless shell — instead of failing every call. Verified by a real session on that release.
|
|
141
|
+
- **Tracked background jobs are back in WSL sessions**: replacing the one-shot bash tool with the persistent one also removed the only thing that started a registry job, so `job_list` always said "no background jobs" and `run_in_background: true` handed to `bash` was silently ignored (the parameter schema allows extra properties, so nothing complained) — the exact defect an operator's session surfaced. The world now mounts `bash_background` (`src/host/wsl-jobs.ts`), a thin producer over the host's own `ctx.jobs.start` + this plugin's `ctx.shell.start`: the tool returns a job id, and `job_list`/`job_output`/`job_kill` work on it as usual. Verified in a real session: `started background job bash-1` → `job_list` shows `running` → incremental `job_output` reads (`tick 1`, `tick 2`, then `tick 3`) → `[status: completed, exit code: 0]` plus the runtime's completion notice.
|
|
142
|
+
- **Verification**: thirteen checks on each of the eight declared releases (`0.1.0-rc.7` … `0.1.5-rc.2`) — `search-real` joins the suite, driving the real tools against a real distribution fixture (framing, includes and braces, caps and footers, spill, cards, error codes, argv-safety, explicit dot-files, unreadable roots, odd root names, `/mnt` paths, abort and overflow, glob ordering and pruning) — leaving only the two pre-existing baseline failures (`typecheck`, and `host-api` which needs a live server). Unit tests add `tests/wsl-search.test.ts` (33 cases) and the refresh, cadence, stacking and description cases; `skills-real` proves an edited skill file invalidates the catalog through the share's own modification times. Real browser sessions on five releases confirm the behaviour end to end, including the `0.1.0-rc.7` fallback.
|
|
143
|
+
|
|
144
|
+
### 0.5.0 — 2026-09-19
|
|
145
|
+
|
|
146
|
+
- **The file tools now follow Linux symlinks**: the `\\wsl.localhost` share lists a link entry but cannot describe it — `lstat`, `stat` and `readFile` on the link all fail and `resolve()` hands back a lexical identity for it — so a link path behaved like a missing file and a linked-in project's files could not be read or written at all. `resolve`/`lstat` now ask the distribution (`wsl.exe … readlink -f`, the same resolver the skill scan uses) whenever this share cannot already describe the path, and continue at the real path. A link is never replaced by a regular file, and writing through a dangling link creates its target while keeping the link.
|
|
147
|
+
- **The access mode constrains a WSL session again**: a variant mounts its own `fs` provider in the preset's isolate realm, so the host's `fs-sandbox` wrapper was not in the call path and `workspace-write` did not stop a write outside the workspace (measured before the fix: a Linux path and a `D:\...` path both went through). `writeText`/`editText` now fence the mutation exactly as `@deepseek-ai/dsh-fs-sandbox` does: `ctx.sandboxPolicy` (the tool layer's per-call value, else the service), the same `writableRoots` allow-list plus the distribution's `/tmp`, the same `FS_SANDBOX_DENIED`, and the `sandboxMode` getter the tool reads to advertise escalation. Because the fence runs after link resolution it judges the real path, so a link out of the workspace is an outside write.
|
|
148
|
+
- **Live skill catalog**: with `watch: false` pinned on the UNC-hostile watcher, a skill added while a session ran only appeared in the next session. The provider now keeps a change detector per scan root it has served, re-checking the published directory shape every 10 s (roots plus entry names and kinds, never re-reading skill files) and calling `control.invalidate()` when it changed, which makes the catalog middleware re-collect on the session's next turn.
|
|
149
|
+
- **`bash` is now a stateful WSL shell** — the capability the per-mode matrix kept showing was missing (every `bash` call used to be a fresh process). DSH's PTY registry takes replaceable backends and `@deepseek-ai/dsh-terminal-bash` is a config-driven one, so the world mounts it (inside its own `persistent-shell` group, because the registry is an agent-owned service) with `backendType: wsl` and points it at this plugin's relay (`src/host/wsl-relay.ts` → `lib/wsl-relay.js`) run by the host's own node. The relay resolves the distribution (session UNC cwd → `DSH_WSL_DISTRO` → host default) and the optional `DSH_WSL_USER`, then hands its stdio — the PTY — to `wsl.exe -d … --cd … -e bash -lc 'cd … && exec bash -i'`: login environment, interactive, and the session directory preserved. `@deepseek-ai/dsh-tool-bash-persistent` registers the **`bash`** name, so it takes the place of the one-shot `dsh-tool-bash` row (mounting both fails the whole preset — the same collision DSH's Minimal mode sidesteps by being a persistent-shell-only agent). The world also isolates and provides its own no-op `sandbox` capability: the PTY backend confines through `ctx.sandbox` before spawning, and the host's Windows runner cannot read the security descriptor of a `\\wsl.localhost\…` workspace root (`GetNamedSecurityInfoW failed (Win32 1)`), so a WSL session declares `enforcement: 'partial'` and keeps the policy where it is meaningful — in the file tools.
|
|
150
|
+
- **Verification**: the eight declared releases (`0.1.0-rc.7` … `0.1.5-rc.2`) run twelve harness checks — `fs-real` (link resolution through the real backend, reads through links and chains, dangling-link creation, link preservation, the fence on an outside link target, the distro `/tmp` allowance) and `relay-real` (stateful shell against real WSL, distribution and user resolution, clean exit) are new — with the same two documented baseline failures (`typecheck`, and `host-api` needing a live server). Unit: `tests/fs-policy.test.ts` (7 fence cases) and the skill-provider refresh cases, on top of the existing suite.
|
|
151
|
+
|
|
152
|
+
### 0.4.5 — 2026-09-19
|
|
153
|
+
|
|
154
|
+
- **A project linked into a WSL workspace is discoverable now**: the `\\wsl.localhost` 9P share lists a Linux symlink but cannot resolve its target, so the skill scan — which already followed directory links on substrates that resolve them — skipped every linked-in project, and with it every nested project below it (the layout issue [#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10) describes). When the share reports a link it cannot follow, the provider now asks the distribution itself (`wsl.exe -d <distro> -- readlink -f <linux path>`) and continues the walk at the real path. The fallback is bounded on purpose: at most 32 links per lookup, four calls in flight, a 10 s timeout each, and the existing depth / visited-directory / skill-directory budgets are untouched. Because the walk continues at the resolved path, a project reachable both directly and through a link is visited once, and a link that points back at the workspace root is absorbed by the visited set instead of looping.
|
|
155
|
+
- **What the fallback does not cover**: `read/write/edit` still resolve their paths through `WslFileSystem`, which does not follow Linux links, so reading or writing a link path reports it missing — use the real path. The help panel's known-issues list now states that instead of promising a fallback "not implemented yet".
|
|
156
|
+
- **Why one `wsl.exe` per link** (measured, and worth recording): `wsl.exe` silently drops the arguments that follow a command (`sh -c 'echo $#' sh a b c` answers 0), and its command-line parser truncates an argument containing a double quote, so a batched `sh` loop cannot be made reliable through it. A bare `readlink -f a b c` is no better: GNU `readlink` stops at the first path it cannot resolve and still exits non-zero, which would silently starve the rest of the batch. Passing each path as a process argument to one short call avoids quoting entirely — paths with spaces, quotes and backslashes all resolve — at the cost of one process per link (about 35 ms warm; six links cost 179 ms end to end on this machine, and a workspace with no links never starts a distribution process at all).
|
|
157
|
+
- **Verification**: the eight declared releases (`0.1.0-rc.7` … `0.1.5-rc.2`) pass the same 8/10 harness checks as 0.4.4 — only the documented `typecheck` baseline and the check that needs a live server fail. The real-9P check now builds a fixture whose only path in is a symlink and asserts the linked project, its nested project and its service through `get()`; the same walk with the fallback face removed finds neither, which is the pre-fix behaviour reproduced in the same run. On a live WSL fixture (`/home/mille/symprobe/ws`: a link out of the workspace, a link chain, a file link, a dangling link and a loop back to the root) the catalog went from 2 skills to 5, and `get()` read every body through the resolved locator.
|
|
158
|
+
|
|
159
|
+
### 0.4.4 — 2026-09-19
|
|
160
|
+
|
|
161
|
+
- **A preset built on top of a WSL variant could not be used at all**: this generator recognises its own output by id prefix (`wsl-`), so a user preset that started life as a copy of `wsl-standard` or `wsl-cordis` — a "data mode" that carries its own world, say — was treated as a plain source preset and had a *second* world group appended to it. DSH refuses a composition carrying two `wsl-world` rows, so choosing that mode failed outright with `无法切换到「WSL · <name>」:duplicate loader entry id: wsl-world`; on a release that mounts the group before validating row ids the same duplication surfaces one step later as `tool "str replace editor" is already registered in this scope` (the report in [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)). The generator now replaces the world group it finds — identified by the mounted `shell-wsl`/`fs-wsl` provider ids, so a copy whose group was renamed is caught too — and every variant ends up with exactly one world pointing at this installation's providers. A top-level row id that appears twice in a source is reduced to its first occurrence as well, because DSH rejects the whole preset on a duplicate id rather than the offending row.
|
|
162
|
+
- **`tool-str-replace-editor` rows are replaced like the older `str-replace-editor` row** ([#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)): newer rosters name the editor row that way, and it registers the same `str_replace_editor` tool as the world group's own editor row, so the source row is dropped just like its predecessor and the WSL-aware editor the variant injects stays.
|
|
163
|
+
- **Variant display names are no longer double-quoted**: the variant's `preset.yml` copied the source's `name:` scalar verbatim, so a quoted `name: 'Data mode'` reached the mode picker as `WSL · ''Data mode''`. The scalar is unquoted before it is re-emitted.
|
|
164
|
+
- **Not adopted from [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)**: disabling the `tool-cordis` row to avoid a duplicate inspect-provider registration. A `disabled` row never applies, so the WSL variant of Creator mode silently lost `cordis_inspect_list` / `cordis_inspect_query` (checked against 0.4.3, where both are present and answer with the host and the client providers); the PR's own description that the model "can still see the tools in the catalog" is not what happens. The registration their report shows needs that row applied twice, which the row-id reduction above now prevents where a copied preset caused it.
|
|
165
|
+
- **Help panel tidied up**: the panel now opens with a greeting line and the repository link, carries a "What's new" section for this build, and lists only the limitations that still apply — the historical "fixed in 0.4.3" note and the per-generation API walkthrough are gone. The compatibility chips are untouched: they are the manifest this build declares, not history.
|
|
166
|
+
- **Verification**: the eight declared releases (`0.1.0-rc.7` … `0.1.5-rc.2`) pass the same 8/10 harness checks as 0.4.3 — only the documented `typecheck` baseline and the check that needs a live server fail; 136 transforms over every shipped preset of the 17 installed runtimes are unchanged apart from the repair, and all 68 "copied variant" cases resolve to a single fresh world group.
|
|
167
|
+
- **Per-mode matrix with a real model** (every WSL variant, not just the default one): on `0.1.0-rc.7`, `0.1.1-rc.2`, `0.1.3-alpha.2` and `0.1.5-rc.2` each of the four variants — Standard, PTC, Minimal, Creator — was driven through the browser and asked to write a file with its file tool, run `uname -r; pwd; whoami` in bash and land that output in the workspace, then read the file back. Every mode produced `MODE-<mode>-OK` and a WSL2 kernel line in `/home/mille/<workspace>/notes/` with no loader error; the follow-up bash call lands in the workspace again, which is the documented per-call shell (the PTY group stays dropped). `0.1.2-rc.1` and `0.1.5-rc.1` were driven through all four modes without the file/bash assertions.
|
|
168
|
+
- Browser + real-model spot checks of the copied-variant mode (mounts and answers), Creator mode (inspect tools intact) and the `0.1.0-rc.7` standard flow complete the pass.
|
|
169
|
+
|
|
42
170
|
### 0.4.3 — 2026-09-11
|
|
43
171
|
|
|
44
172
|
- **The persona text moved in `0.1.3-alpha.2`** ([#22](https://github.com/6Mikao9/dsh-wsl-workspace/issues/22)): DSH renamed the persona's model-facing scalar from `text` to an inline `suffix` plus a folded `prefix`, and the variant generator only recognised `text: >-`. On that line the WSL environment sentence was never appended - the session still ran inside the distribution, but the model was never told that its working directory is a Linux path reachable from Windows as `/mnt/<drive>`. The generator now amends `suffix`, `text` or `prefix` (folding an inline scalar into a block scalar when needed, so the sentence joins the working-directory line exactly where the legacy `text` block put it), and a persona carrying `complete: true` is still left alone. Verified on seven releases: the five older ones keep their persona block byte-identical, and the two newer ones now carry the sentence into the model's system message.
|
|
@@ -74,7 +202,7 @@ Click "Create & open" to start a new session in the workspace. In the new sessio
|
|
|
74
202
|
Follow-ups from the [#12](https://github.com/6Mikao9/dsh-wsl-workspace/issues/12) limitation list and the [#13](https://github.com/6Mikao9/dsh-wsl-workspace/issues/13) compatibility work:
|
|
75
203
|
|
|
76
204
|
- **Lookup cache**: completed skill-catalog lookups are cached per scan root for 10 seconds, so repeated catalog builds no longer rescan the workspace over the slow 9P share; `get()` keeps reading skill bodies live, and freshly added skills appear within the TTL window.
|
|
77
|
-
- **Symlinked projects — investigated,
|
|
205
|
+
- **Symlinked projects — investigated in 0.4.0, resolved in 0.4.5**: the discovery walk now recognizes directory symlinks explicitly and prunes them safely (no crashes, no loops), and the probe showed that following them is impossible over the `\\wsl.localhost` share itself (the Windows side cannot resolve Linux symlink targets: `readlink` → `EISDIR`, `stat`/`readdir` → `ENOENT`); 0.4.5 resolves them through the distribution instead, so linked-in projects are discoverable (see that changelog entry). A name+body fingerprint dedupe also guarantees aliased skill files can never publish twice on substrates that do resolve links.
|
|
78
206
|
- **Block-scalar frontmatter**: `description:` / `whenToUse:` written as YAML block scalars (`|` literal, `>` folded) now parse — such skills were silently dropped before.
|
|
79
207
|
- **Compatibility manifest**: `dsh.compatibility.dshReleases` declares per-release compatibility with the official DSH versions, backed by reproducible disposable-Profile install/start/uninstall evidence (`scripts/verify-dsh-compat.sh`), and `engines` declares the Node.js floor.
|
|
80
208
|
- **Guard scripts**: `scripts/check-rank-parity.mjs` fails the release when the copied project-rank constants drift from the host's `dsh-skill-filesystem`.
|
package/README.zh.md
CHANGED
|
@@ -32,10 +32,109 @@ dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
|
|
|
32
32
|
## 行为与权限说明
|
|
33
33
|
|
|
34
34
|
- **bash 工具**:以配置的用户名在 WSL 发行版内运行(留空 = 发行版默认用户,通常为 root),可对发行版内任意路径读写。Windows 的 ACL 沙箱无法包裹 `wsl.exe`(子进程运行在 Linux 内核侧),WSL 自身即隔离边界,DSH 文件策略不作用于 bash。
|
|
35
|
-
- **文件工具(read/write/edit)**:经 Windows 侧的 WSL 9P
|
|
36
|
-
-
|
|
35
|
+
- **文件工具(read/write/edit)**:经 Windows 侧的 WSL 9P 共享访问;用户名设置不影响它们。宿主原本会提供的两处能力,现在由 WSL 世界自己在内部补上——变体在预设的 isolate realm 里挂自己的 `fs` 提供者,宿主的 `fs-sandbox` 包装层不在调用路径上。**符号链接**:共享只列得出链接、解不开目标,链接路径以前会被当成不存在的文件;现在 `resolve`/`lstat` 会向发行版问一次真实路径(`wsl.exe … readlink -f`)并从那里继续,链接本身绝不会被普通文件替换。**访问模式**:`write`/`edit` 完全按宿主后端的做法由 `ctx.sandboxPolicy` 围栏——同一份 `writableRoots` 白名单(另加发行版的 `/tmp`,即会话所在世界的临时区)、同样的 `FS_SANDBOX_DENIED`(工具层会渲染成拒绝)、同样的 `sandboxMode`(工具据此声明升级)。由于围栏在解析之后执行,判定的是真实路径:链接指向工作区外就按工作区外处理,`workspace-write` 下会被拒绝。完全没有策略服务的部署则不围栏,与宿主一致。
|
|
36
|
+
- **文件搜索(grep / glob)**:宿主的搜索套件驱动的是打包进来的 **Windows** ripgrep,模型交给它的路径却全是 Linux 路径,所以 WSL 变体一直把这一行丢掉、让模型自己在 shell 里搜。现在世界挂上自己的同类实现(`src/host/wsl-search.ts` → `lib/wsl-search.js`),搜索**在发行版内**执行:工具名、参数 schema、条数上限、输出 schema(`Line N:` 分组、命中数表头、超限尾部提示)、搜索卡片与超限结果落盘全部沿用 `@deepseek-ai/dsh-tool-fs-search` 自己导出的部件,所以模型看到的东西与宿主一致。`grep` 用发行版自带的 GNU grep(`-rnIEH -Z`,POSIX ERE:`\d`、`\w`、`\b`、`(?i)` 可用,环视与反向引用不支持),像 ripgrep 默认那样跳过隐藏文件/目录与 `node_modules`,**不读 `.gitignore`**,`{a,b}` 会展开成多个 `--include`,含 `/` 的 include 在插件进程里按相对路径匹配(ripgrep 语义);发行版没有 GNU grep 时明确报错,而不是给出错位的记录。`glob` 用 GNU `find` 列出文件(`-printf` 直接带出修改时间,不必逐个 stat),在插件里按 gitignore 风格匹配(`*` 不跨分隔符、`**` 跨、`?`、`[...]`、`{a,b}`,前导 `!` 取反),并按修改时间从旧到新排序——与 `rg --sort=modified` 一致。两者都直接搜 Linux 真实目录(符号链接、权限、不理会忽略规则),不走 9P 共享;都跳过版本库目录,也都不跟进递归中遇到的符号链接(同样是 ripgrep 的默认)。源预设没有搜索套件的模式(极简)不会凭空多出这两个工具。
|
|
37
|
+
- **技能目录(skill catalog)**:从会话 cwd 最近的 `.git` 祖先开始(没有 `.git` 祖先则用 cwd 本身)向下扫描 `.dsh/skills` 与 `.agents/skills`(含嵌套项目),上限为 4 层目录、64 个技能目录、4096 个已访问目录。建议把工作区注册在你实际工作的项目根;若注册目录本身在更大的 git 仓库里,扫描会从该仓库根开始(与宿主规则一致),同级项目可能一并出现。Windows 侧共享解不开的 Linux 符号链接会改由发行版解析(`wsl.exe … readlink -f`,每次查找最多 32 条、并发 4 条),解析后从真实路径继续扫描,因此 `ln -s` 链进来的项目、以及它下面的嵌套项目都能被发现,并按真实路径去重。技能正文始终实时读取。生成预设会把 `skill-filesystem` 行的 `watch` 固定为 `false`(对 `\\wsl.localhost\...` 开监视会失败),插件改为自己轮询,分两档:轻量档每 3 秒重查一次已发布的技能目录、并给每个技能文件记一次修改时间与大小,所以会话中途**新增、删除或改写**技能都会在下一个回合生效——改写这条正是时间戳的意义:模型看到的目录只在注册表修订号变化时重建,而光看目录列表分不出一个被改写的 `SKILL.md` 和没动过的文件。完整重新发现每 30 秒一次,因为只有走一遍才可能找到此前不存在的技能目录(比如新项目里的第一个 `.dsh/skills`)。两档都不重读技能正文。
|
|
38
|
+
- **shell 的生命周期**:`bash` 现在**有状态**——`cd`、export 的变量、激活的 venv、后台任务都跨调用保留。世界里挂了宿主的 PTY 注册表与它的可配置后端(`@deepseek-ai/dsh-terminal-bash`),并把后端指向本插件的中继脚本(`src/host/wsl-relay.ts` → `lib/wsl-relay.js`,由宿主自己的 node 运行):中继把 PTY 交给 `wsl.exe … bash`,发行版取自会话的 UNC cwd(否则 `DSH_WSL_DISTRO`)、可选用户名取自 `DSH_WSL_USER`,并执行 `bash -lc 'cd … && exec bash -i'`——既加载登录环境,又保住会话目录(有些 profile 会把 `bash -l` 带回 `$HOME`)。该工具注册的名字是 `bash`,因此它取代非 WSL 预设里的 `dsh-tool-bash` 一行;世界同时隔离并提供自己的 no-op `sandbox` 能力(`src/host/wsl-sandbox.ts`)——PTY 后端在启动前会走 `ctx.sandbox`,而宿主的 Windows ACL 运行器读不了 `\\wsl.localhost\…` 工作区根的安全描述符。两个 shell 都在发行版内运行,不受 DSH 文件策略约束——WSL 自身就是它们的隔离边界。**宿主包装命令带来的两个后果写在工具描述里**(本插件覆盖了默认描述,因为宿主默认两条都没提):整轮会话只有这一只 shell,所以某次调用里的 `cd` 决定下一次从哪里开始——请用绝对路径或在命令开头显式 `cd`;宿主把每条命令包成 `eval -- $'…'`,所以命令以 `&` 结尾会把**整条包装命令**后台化——调用会立刻返回 exit code 0 且没有输出,真正的输出晚到,甚至落进下一次调用的输出里。后台任务请写成单独一行的 `( 长任务 > log 2>&1 ) &`,或使用后台任务工具。
|
|
39
|
+
- **可跟踪的后台任务**:`bash_background` 把一条命令放到后台并立刻返回注册表里的 job id,之后宿主的 `job_list` / `job_output`(增量读取、状态流转、完成通告)/ `job_kill` 都照常工作。之所以需要这一行:持久 shell 的 schema 只声明了 `command`,没有生产者时 `job_list` 永远回答"没有后台任务",而传给 `bash` 的 `run_in_background: true` 会被**静默忽略**——参数 schema 不禁止额外属性,所以没有任何地方报错。这是真实会话里被发现的问题。该工具只在持久 shell 存在时挂载;保留一次性 bash 行的世界本来就有 `run_in_background`。
|
|
40
|
+
- **老宿主回退到一次性 shell**:持久 shell 是**宿主**的代码,而在 Windows 上它需要一个平台进程检查器,那个东西从 `0.1.0-rc.8` 才有——在 `0.1.0-rc.7` 里 `spawnTerminal` 会在启动任何进程之前抛 `subprocess-local: terminal inspection is unsupported on platform win32`,于是该版本里**每一次 `bash` 调用都直接失败**(不碰 PTY 的 grep/glob 仍正常)。因此插件在启动时**探测**底座而不是假设:它把一个不存在的程序交给 `spawnTerminal`,这只会走到检查器那一步、不会真的创建进程,报错信息就能说明是哪一半失败。答案是否时,生成的世界保留一次性的 `dsh-tool-bash` 行(走本插件自己的 `ctx.shell`,不经 PTY),模型得到的是一个能用的无状态 shell,而不是每次调用都报错。八个已声明版本里只有 `0.1.0-rc.7` 是这种状态,之后的版本都拿到持久 shell。
|
|
37
41
|
- `wsl.exe` 在发行版尚未启动时向 stderr 打印的 localhost 端口转发提示(乱码但无害)可忽略。
|
|
38
42
|
|
|
43
|
+
## 更新日志
|
|
44
|
+
|
|
45
|
+
英文完整历史见 [README.md](README.md),本节为对应中文记录(0.4.3 及更早为摘要)。
|
|
46
|
+
|
|
47
|
+
### 0.7.2 — 2026-09-21
|
|
48
|
+
|
|
49
|
+
- **WSL 技能目录不再在请求路径上重走(issue #25)**。宿主会在请求期间重建技能目录并等待每个 provider 的 `list()`,而本插件只把答案保留 10 秒——于是每次重新收集(新会话、新作用域,或距上次查询超过 10 秒)都会让那个请求付一次整棵工作区的走查,而且是逐个目录:每目录两次 `stat` 加一次 `readdir`。本机 `\\wsl.localhost\…` 9P 共享的单次 `readdir` 实测 3~16 毫秒,走查预算固定 4096 个目录,所以大工作区那次走查是 20.4 秒、并且付在请求路径上。现在已发布的目录直接照原样返回,只有插件自己的变更探针能丢弃它:重复查找 1~3 毫秒、完全不碰文件系统。新鲜度契约不变——新增的嵌套技能目录仍在 30 秒内出现,新增、删除或改写技能仍在 3 秒内生效。
|
|
50
|
+
- 走查本身也变便宜了:BFS 同一层并发探测(有上限)、按 frontier 顺序发布以保证目录顺序确定,且只在目录自身的清单里出现 `.dsh` / `.agents` 时才探测对应 `skills`。同机实测命中预算的走查从 20.4 秒降到 4.8 秒;真正的并行度由 node 的文件系统线程池决定。
|
|
51
|
+
|
|
52
|
+
### 0.7.1 — 2026-09-20
|
|
53
|
+
|
|
54
|
+
- **`npm install dsh-wsl-workspace` 不再失败**。校验已发布产物时发现了这次发布自己引入的回归:npm 会自动安装缺失的同伴依赖,而 0.6.0 新加的 `@deepseek-ai/dsh-tool-fs-search` 同伴自己又依赖 `@deepseek-ai/dsh-retention`,后者**并未公开发布**——于是纯 `npm install` 直接以 `E404 … @deepseek-ai/dsh-retention` 失败(0.4.3 装得没问题,所以是我们引入的)。`dsh plugin add` 走 pnpm,对未满足的同伴依赖**只警告**,这正是所有门禁与真实安装都没抓到的原因。十个宿主同伴依赖现在都在 `peerDependenciesMeta` 里标为 optional:包仍然声明宿主必须提供什么,但 npm 不再尝试去下载它们。
|
|
55
|
+
|
|
56
|
+
### 0.7.0 — 2026-09-20
|
|
57
|
+
|
|
58
|
+
WSL 世界现在在"会话能察觉到的每一处"都与宿主一致,最后两条已知问题也关掉了。以下内容一起发布:WSL 变体拿到 Linux 符号链接、会话的访问模式、发行版内的搜索、实时的技能目录、有状态的 shell,以及可跟踪的后台任务。
|
|
59
|
+
|
|
60
|
+
- **把宿主 `bash` 的契约写进工具描述**:持久工具把每条命令包成 `eval -- $'…'`,所以命令以 `&` 结尾会把**整条**包装命令后台化——调用立刻返回 exit code 0、没有输出,真正的输出晚到,甚至落进下一次调用的输出里;而整轮会话只有这一只 shell,`cd` 会带到下一次调用。宿主默认描述两条都没提,DSH 自己的极简模式还建议危险写法(`sleep 10 &`)。世界现在覆盖 `description`(八个已声明版本都支持这个键),写明这两点与安全写法。
|
|
61
|
+
- **`0.1.0-rc.7` 回退到能用的无状态 shell**:那个版本的 `dsh-subprocess-local` 没有 Windows 进程检查器,宿主的 PTY 持久 shell 在 Windows 上根本起不来——每次 `bash` 都报 `subprocess-local: terminal inspection is unsupported on platform win32`(宿主自己也有这个缺口:它那个版本的极简模式挂 `persistent-bash` 时没有 Windows 守卫)。插件改为在启动时**探测**底座——把一个不存在的程序交给 `spawnTerminal`,只会走到检查器那一步——答案是否就保留一次性的 `dsh-tool-bash` 行:一个能用的无状态 shell,而不是每次调用都报错。
|
|
62
|
+
- **可跟踪的后台任务回来了**:用持久 shell 取代一次性 bash 工具时,也把唯一会启动注册表任务的东西去掉了,于是 `job_list` 永远回答"没有后台任务",而传给 `bash` 的 `run_in_background: true` 被静默忽略(参数 schema 允许额外属性,没人报错)。世界现在挂 `bash_background`(`src/host/wsl-jobs.ts`),一个架在宿主 `ctx.jobs.start` 与本插件 `ctx.shell.start` 之上的薄生产者:返回 job id,`job_list`/`job_output`/`job_kill` 照常工作。只在源模式本身挂了 `job_*` 工具、且持久 shell 存在时才挂载。
|
|
63
|
+
- **按最坏输入复查新代码抓到六个缺陷**:显式点名的隐藏文件被守卫排掉(`grep path=.env` 返回 0 条)、`find` 退出码被丢弃(`glob path=/nope-missing` 看起来像空目录)、glob 头部按行结尾(名字含换行的根被截断)、Windows 路径没翻译(`grep path='D:\proj'` 失败而 `read` 能读)、落盘 schema 闭合(会让每次"超限且有落盘后端"的调用在返回时校验失败)、技能变更探测会叠加慢 pass。外加新生产者里的两个:它曾被挂在没有 `job_*` 工具的模式里、并且把任务的工作目录默认成了宿主进程而不是会话工作区。
|
|
64
|
+
- **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)各跑十三项门禁,`search-real` 用真实发行版夹具驱动真实工具,只剩既有的两项基线失败(`typecheck` 与需要在线服务的 `host-api`)。152 例单测,含"每个渲染器与宿主套件自己的格式化函数逐字节对比"的平价检查。五个版本的浏览器真实会话覆盖工具行为,另外**八个版本全部做了前端验证**(入口按钮、对话框、路径检查、创建并打开、模式选择器、含 v0.7.0 与 8 个版本标签的帮助面板),并以会话日志为证据覆盖工具集、搜索结果、目录替换、shell 回退与后台任务生命周期。
|
|
65
|
+
|
|
66
|
+
### 0.6.0 — 2026-09-19
|
|
67
|
+
|
|
68
|
+
- **WSL 会话补上了 `grep` 与 `glob`**:宿主的搜索套件跑的是打包的 Windows ripgrep,而模型给的路径全是 Linux 路径,于是生成的世界干脆丢掉了 `tool-fs-search`,让模型自己在 shell 里搜——这正是面板已知问题里的最后一条。现在世界挂上发行版内的同类实现,并保留宿主套件的契约:同样的工具名、参数 schema、条数上限(250 条命中 / 100 个路径)、输出 schema、`Line N:` 分组、命中数表头、超限尾部提示、搜索卡片与超限结果落盘;渲染直接调用 `@deepseek-ai/dsh-tool-fs-search` 自己导出的格式化函数,只有该包未导出的两处投影(卡片元数据与 glob 分页)在本插件里复刻,并有单测与它逐字节对比。`grep` 在发行版内跑 GNU grep(`-rnIEH -Z`、POSIX ERE、像 ripgrep 默认那样跳过隐藏项与 `node_modules`、不读 `.gitignore`),`glob` 用 GNU `find` 列文件并在插件内按 gitignore 风格匹配、按 ripgrep 的"最旧优先"修改时间排序。模型给的每个值都作为独立 argv 传给固定脚本,任何输入都不会被 shell 解析。
|
|
69
|
+
- **技能目录现在能感知"改写",而不只是"新增"**:模型的目录消息只在注册表修订号变化时重建,而旧的探测只比对目录列表——所以改写一个已有的 `SKILL.md`(比如描述)它看不见,模型会一直用旧文案直到下个会话。轻量档现在额外给每个技能文件记修改时间与大小,并且从 10 秒改成 3 秒一次;完整重新发现——唯一能发现"此前不存在的技能目录"的一遍——挪到自己的 30 秒节奏。于是检测既更快,也比被它取代的"10 秒一遍"更省。
|
|
70
|
+
- **`lib/` 现在确定性重建**:`tsdown` 的产物是提交进仓库的,而 `clean: false` 加两个配置共用一个输出目录,导致早先构建留下的分包块每次都活下来。本地构建工具现在先清空目录,并补声明了三个运行时同伴依赖(`@deepseek-ai/dsh-tool-fs-search`、`@deepseek-ai/dsh-tools`、`@deepseek-ai/schemastery`)——这同时是它们保持 external、不把 DSH 工具栈再打包一份进本插件的原因。
|
|
71
|
+
- **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)各跑十三项门禁,新增 `search-real`——用真实发行版上的固定夹具驱动真实工具(记录框定、include 与 `{}` 展开、上限与尾部提示、落盘、卡片、错误码、argv 安全性、显式点名的点文件、读不了的根、名字含换行的根、`/mnt` 路径、超时中断与输出溢出、glob 排序与剪枝)——只剩既有的两项基线失败(`typecheck` 与需要在线服务的 `host-api`)。单测新增 `tests/wsl-search.test.ts`(33 例)与两例刷新用例;`skills-real` 现在也证明"改写技能文件"能通过共享自己的修改时间让目录失效。五个版本的浏览器真实会话端到端确认。
|
|
72
|
+
- **按"最坏输入"复查新代码抓到的四个缺陷**:隐藏文件守卫连"显式点名的文件"一起排掉(`grep path=.env` 返回 0 条)、`find` 的退出码被丢弃(`glob path=/nope-missing` 看起来像空目录)、glob 头部按行结尾(根目录名里带换行时被截断)、Windows 路径没做翻译(`grep path='D:\proj'` 失败,而 `read` 能读)。另外把落盘 schema 收紧成了闭合对象——那会让每次"超限且有落盘后端"的调用在返回时校验失败;技能变更探测也加了在飞行中守卫,一次慢 pass 不会再叠出多层 `wsl.exe` 调用。
|
|
73
|
+
- **宿主 `bash` 包装命令的行为写进了工具描述**:持久工具把每条命令包成 `eval -- $'…'`,所以命令末尾的 `&` 会把整条包装命令后台化——调用立刻返回、exit code 0、没有输出;而这只 shell 是整轮会话一个进程,`cd` 会带到下一次调用。宿主默认描述两条都没提,DSH 自己的极简模式还建议 `sleep 10 &`;世界现在覆盖描述,写明这两点与安全写法。
|
|
74
|
+
- **`0.1.0-rc.7` 不再拿到一个坏掉的 shell**:那个版本的 `dsh-subprocess-local` 没有 Windows 进程检查器,宿主的 PTY 持久 shell 在 Windows 上根本起不来(宿主自己也有这个缺口:它那个版本的极简模式挂 `persistent-bash` 时没有 Windows 守卫)。世界改为在启动时探测底座,答案是否就保留一次性的 `bash` 行——一个能用的无状态 shell——而不是每次都报错。已在真实会话里验证。
|
|
75
|
+
- **WSL 会话重新有了可跟踪的后台任务**:用持久 shell 取代一次性 bash 工具时,也把唯一会启动注册表任务的东西一起去掉了,于是 `job_list` 永远回答"没有后台任务",而传给 `bash` 的 `run_in_background: true` 被静默忽略(参数 schema 允许额外属性,没人报错)——正是运营方会话里暴露出来的缺陷。世界现在挂 `bash_background`(`src/host/wsl-jobs.ts`),一个架在宿主 `ctx.jobs.start` 与本插件 `ctx.shell.start` 之上的薄生产者:工具返回 job id,`job_list`/`job_output`/`job_kill` 照常工作。真实会话已验证:`started background job bash-1` → `job_list` 显示 `running` → `job_output` 增量读到 `tick 1`、`tick 2`,再读 `tick 3` → `[status: completed, exit code: 0]`,并附带运行时推送的完成通告。
|
|
76
|
+
|
|
77
|
+
### 0.5.0 — 2026-09-19
|
|
78
|
+
|
|
79
|
+
- **文件工具现在跟随 Linux 符号链接**:`\\wsl.localhost` 共享只列得出链接条目、描述不了它——对链接的 `lstat`、`stat`、`readFile` 全部失败,而 `resolve()` 会回一个词法身份——于是链接路径被当成不存在的文件,链接进来的项目**根本读写不了**。现在只要这份共享描述不了该路径,`resolve`/`lstat` 就向发行版问一次(`wsl.exe … readlink -f`,与技能扫描同一个实现)并从真实路径继续。链接不会被普通文件替换;向悬空链接写入会创建它的目标并保留链接。
|
|
80
|
+
- **访问模式重新约束 WSL 会话**:变体在预设的 isolate realm 里挂自己的 `fs` 提供者,宿主的 `fs-sandbox` 包装层不在调用路径上,所以 `workspace-write` 拦不住工作区外写入(修复前实测:Linux 路径与 `D:\...` 路径都能写)。现在 `writeText`/`editText` 完全按 `@deepseek-ai/dsh-fs-sandbox` 的方式围栏:`ctx.sandboxPolicy`(工具层按次传入的优先,否则服务自解析)、同一份 `writableRoots` 白名单加发行版 `/tmp`、同样的 `FS_SANDBOX_DENIED`,并提供工具读取的 `sandboxMode` 以便声明升级。围栏在链接解析之后执行,判定的是真实路径——链接指向工作区外就按工作区外拒绝。
|
|
81
|
+
- **技能目录实时刷新**:此前对 UNC 固定 `watch: false`,会话中途加入的技能只能等下个会话。现在 provider 为每个服务过的扫描根维护一个变更探测,每 10 秒重查已发布的目录形状(技能根 + 条目名与类型,**不重读技能文件**),有变化就调 `control.invalidate()`,目录中间件会在会话下一个回合重新收集。
|
|
82
|
+
- **`bash` 换成有状态的 WSL shell**——逐模式矩阵反复暴露的那个缺口(每条 `bash` 都是新进程)。DSH 的 PTY 注册表支持替换后端,而 `@deepseek-ai/dsh-terminal-bash` 是配置驱动的,于是世界把它挂进自己的 `persistent-shell` 组(注册表是 agent 级服务),用 `backendType: wsl` 指向本插件的中继脚本(`src/host/wsl-relay.ts` → `lib/wsl-relay.js`),由宿主自己的 node 运行。中继解析发行版(会话 UNC cwd → `DSH_WSL_DISTRO` → 宿主默认)与可选用户名(`DSH_WSL_USER`),然后把自己的 stdio——也就是那个 PTY——交给 `wsl.exe -d … --cd … -e bash -lc 'cd … && exec bash -i'`:登录环境、交互式、且保留会话目录。`@deepseek-ai/dsh-tool-bash-persistent` 注册的工具名就是 **`bash`**,所以它取代了一次性的 `dsh-tool-bash` 行(同时挂载会让整个预设挂载失败——DSH 自己的极简模式正是用"只给持久 shell"来避开这个冲突)。世界还隔离并提供自己的 no-op `sandbox` 能力:PTY 后端在启动前会调 `ctx.sandbox`,而宿主的 Windows 运行器读不了 `\\wsl.localhost\…` 工作区根的安全描述符(`GetNamedSecurityInfoW failed (Win32 1)`),所以 WSL 会话声明 `enforcement: 'partial'`,把策略留在真正有意义的地方——文件工具里。
|
|
83
|
+
- **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)跑十二项门禁——新增 `fs-real`(真实后端上的链接解析、经链接与链接链读取、悬空链接创建、链接保留、出工作区链接的围栏、发行版 `/tmp` 允许)与 `relay-real`(真实 WSL 上的有状态 shell、发行版与用户名解析、干净退出)——仅剩既有的两项基线失败(`typecheck` 与需要在线服务的 `host-api`)。单测:`tests/fs-policy.test.ts`(7 例围栏)+ 技能 provider 的刷新用例,叠加在原有套件之上。
|
|
84
|
+
|
|
85
|
+
### 0.4.5 — 2026-09-19
|
|
86
|
+
|
|
87
|
+
- **链进 WSL 工作区的项目现在能被发现了**:`\\wsl.localhost` 9P 共享会把 Linux 符号链接当作条目列出来,却解析不了它的目标,于是技能扫描(本来就会在能解析链接的底层上跟随目录链接)直接跳过所有链接进来的项目,连带跳过它下面的嵌套项目(即 [#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10) 描述的那种布局)。现在只要共享报出一个它跟随不了的链接,插件就回头问发行版本身(`wsl.exe -d <发行版> -- readlink -f <Linux 路径>`),拿到真实路径后从那里继续走。这条回退是有意加了上限的:每次查找最多 32 条链接、最多 4 个调用并发、单次超时 10 秒,原有的深度 / 已访问目录 / 技能目录预算不变。由于继续扫描的位置是解析后的真实路径,同一项目既被直接访问又被链接访问时只会走一次,指回工作区根目录的链接环也会被已访问集合吸收,不会打转。
|
|
88
|
+
- **这条回退不覆盖什么**:`read/write/edit` 仍然走 `WslFileSystem` 的路径解析,它不跟随 Linux 链接,因此直接读写链接路径会报路径不存在——请用真实路径。帮助面板的「已知问题」现在如实写这一点,而不再是"可以用 readlink 补,但尚未实现"。
|
|
89
|
+
- **为什么一条链接一个 `wsl.exe`**(实测记录):`wsl.exe` 会**丢掉**命令之后的参数(`sh -c 'echo $#' sh a b c` 返回 0),而且它的命令行解析会把含双引号的参数截断,所以批量的 `sh` 循环没法可靠地透过它工作。直接 `readlink -f a b c` 也不行:GNU `readlink` 遇到第一个解析不了的路径就停下(仍以非零退出),批次里后面的链接会被无声地饿死。把每个路径作为进程参数交给一次短调用,就完全绕开了引号问题——带空格、引号、反斜杠的路径都能解析——代价是每条链接一个进程(热态约 35 ms;本机上 6 条链接端到端 179 ms;没有链接的工作区则完全不会启动发行版进程)。
|
|
90
|
+
- **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)跑通与 0.4.4 相同的 8/10 项检查(仅剩既有的 `typecheck` 基线与一项需要在线服务的检查)。真实 9P 检查现在会构造"只能靠符号链接进入"的 fixture,断言链接进来的项目、它下面的嵌套项目以及 `get()` 取正文;同一次运行里把回退能力摘掉再走一遍,两者都找不到,即修复前的行为在原位复现。在真实 WSL fixture 上(`/home/mille/symprobe/ws`:链到工作区外的目录、链接链、指向文件的链接、悬空链接、指回根目录的环)技能目录从 2 个变成 5 个,`get()` 也都能通过解析后的定位读回正文。
|
|
91
|
+
|
|
92
|
+
### 0.4.4 — 2026-09-19
|
|
93
|
+
|
|
94
|
+
- **在 WSL 变体基础上改出来的自定义模式完全用不了**:本插件靠 id 前缀(`wsl-`)识别自己的产物,于是「把生成的 `wsl-standard` / `wsl-cordis` 复制改名再改」得到的用户预设会被当成普通源预设,被**再追加一个世界组**。DSH 拒绝含两个 `wsl-world` 行的组合,选中该模式时直接失败:`无法切换到「WSL · <名称>」:duplicate loader entry id: wsl-world`;在"先挂载组、后校验行 id"的版本上,同一处重复会晚一步表现为 `tool "str replace editor" is already registered in this scope`(即 [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24) 报告的现象)。现在生成器会**替换**它找到的世界组(按挂载的 `shell-wsl` / `fs-wsl` provider id 识别,改过组名也能认出),每个变体最终只挂一个世界,且指向本机安装的 provider。源里重复出现的顶层行 id 也只保留第一处——DSH 遇到重复 id 是**整个预设**不可用,而不是只丢那一行。
|
|
95
|
+
- **`tool-str-replace-editor` 行与旧的 `str-replace-editor` 行一样被替换**([#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)):较新的名单用这个 id,而它注册的工具名与注入世界组里那个编辑器行相同,所以源里的那行会被丢弃,变体注入的、走 WSL 文件系统的编辑器保留。
|
|
96
|
+
- **变体显示名不再多出一层引号**:变体的 `preset.yml` 原先逐字复制源里的 `name:` 标量,于是 `name: 'Data mode'` 到了模式选择器里变成 `WSL · ''Data mode''`;现在会先去掉一层 YAML 引号再写出。
|
|
97
|
+
- **未采纳 [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24) 的做法**:禁用 `tool-cordis` 行来规避 inspect provider 重复注册。`disabled` 的行根本不会 apply,结果是 WSL 创造模式**直接丢掉** `cordis_inspect_list` / `cordis_inspect_query`(已与 0.4.3 对照:0.4.3 里两个工具都在,且能返回 host 与 client 两侧的 provider);PR 描述里"模型仍能在工具目录看到、只是不能用"与实际不符。其报告中的重复注册需要该行被 apply 两次,而这在"由复制预设引起"的情形下已由上面的行 id 去重解决。
|
|
98
|
+
- **帮助面板整理**:面板最前面是一句问候语与仓库链接,新增「本次更新」一节,已知问题只保留仍然成立的条目——历史上的「0.4.3 已修复」说明与按版本讲旧 API 的段落已删除。兼容性 chips 保持原样:它们是本构建声明的清单,不是历史。
|
|
99
|
+
- **逐模式矩阵(真模型)**:在 `0.1.0-rc.7`、`0.1.1-rc.2`、`0.1.3-alpha.2`、`0.1.5-rc.2` 上,四个 WSL 变体(标准 / PTC / 极简 / 创造)各自跑一遍:用文件工具写文件、用 bash 执行 `uname -r; pwd; whoami` 并把输出重定向落盘、再读回文件。每个模式都在 `/home/mille/<工作区>/notes/` 里留下了 `MODE-<模式>-OK` 与 WSL2 内核输出,零 loader 报错;随后单独一次 bash 调用又回到工作区目录,即文档所写的「按次 shell」(PTY 组仍然不注入)。`0.1.2-rc.1` 与 `0.1.5-rc.1` 只做了四模式切换与真实回合,没有文件/bash 断言。
|
|
100
|
+
- **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)跑通与 0.4.3 相同的 8/10 项检查(仅剩既有的 `typecheck` 基线与一项需要在线服务的检查);17 个已安装运行时里全部 shipped 预设共 136 次变换,除本次修复外结果不变;68 个"复制变体"场景全部收敛为单一新世界组。另做浏览器 + 真模型验证:复制变体模式本身、创造模式(检查工具完整)以及 `0.1.0-rc.7` 的标准流程。
|
|
101
|
+
|
|
102
|
+
### 0.4.3 — 2026-09-11
|
|
103
|
+
|
|
104
|
+
- **persona 文本位置变更**([#22](https://github.com/6Mikao9/dsh-wsl-workspace/issues/22)):DSH 把 persona 面向模型的字段从 `text` 改为内联 `suffix` + 折叠 `prefix`,而变体生成器只识别 `text: >-`,于是 WSL 环境说明从未追加(会话仍在发行版内运行,但模型不知道自己的 cwd 是 Linux 路径)。现在按 `suffix` → `text` → `prefix` 依次补写(内联标量会先折成块标量,句子落在原 `text` 块的位置),带 `complete: true` 的 persona 依旧不动。
|
|
105
|
+
- **帮助面板**:对话框新增「?」按钮,就地展示本构建声明的 DSH 版本(直接从 `package.json` 经宿主路由读取,不会与清单脱节)、插件的用法与特性,以及无法修复的已知限制。
|
|
106
|
+
- **UNC 工作区终于能收到技能目录**:宿主技能提供者用 `fs.watch` 监视工作区,对 `\\wsl.localhost\...` 会抛 `EISDIR`,该次观测被判为不完整,而 `dsh-tool-skill` 在快照不完整时会丢弃**整条**目录消息,于是 WSL 会话的模型一个技能都看不到。现在生成预设时把 `skill-filesystem` 行的 `watch` 固定为 `false`(若该行已有 `config:` 就并入,源里自己声明了 `watch` 则不动),目录改为在会话启动时扫描一次。代价是不再实时刷新:会话运行中途加入的技能要等下一个会话(技能正文仍实时读取)。
|
|
107
|
+
- **`verify-lib` 加固**:其注释/字符串剥离器会把注释里的孤立单引号与后面的引号配对、吞掉剩余 bundle,使所有 `node:*` 导入看起来都被 tree-shake 掉;现在引号规则遇换行即终止,与 JavaScript 字符串一致。
|
|
108
|
+
|
|
109
|
+
### 0.4.2 — 2026-09-10
|
|
110
|
+
|
|
111
|
+
- **在 `0.1.2-rc.1` 工作区里「创建并打开」**:会话启动器改为在对话框写入时解析——本插件 apply 早于发布 `uiWorkspace` 的 UI 域注册服务,apply 时缓存的值整页都是 `undefined`,于是「创建并打开」只建了工作区、没开会话,而对话框仍报成功。两种 API 都不存在的版本现在会在写入前直接失败,不再留下孤立工作区。
|
|
112
|
+
- **技能正文完整性**:`findFrontmatterEnd` 返回的就是正文首字符下标,此前的偏移会把首字符吃掉;同时**带 UTF-8 BOM 的 `SKILL.md` 不再被丢弃**(BOM 会在围栏检查前剥离)。
|
|
113
|
+
- **绑定收敛于迟到输入**:agent preset 名单与已注册的 `/mnt/<drive>` 工作区集都是绑定的输入且都异步到达,现在各自到达后重跑一遍,而不是等一个可能永远不来的会话存储事件。
|
|
114
|
+
- **兼容性清单修正**:`0.1.3-alpha.1` 并未发布(`npm view` 为 404),替换为已发布的 `0.1.3-alpha.2`。
|
|
115
|
+
- **可复现发布**:新增 `.gitattributes`(`* text=auto eol=lf`、`lib/** -text`)。`core.autocrlf=true` 会在检出时把文本文件改写成 CRLF,而 `lib/` 是提交并原样发布的,导致同一提交在不同机器上产出不同的 npm 包。
|
|
116
|
+
- **闭环测试**:`tests/client-lifecycle.test.mjs` 用发布出去的 `lib/client.js` 跑通新旧两种服务形态(`connection.api.agentPresets` + `workspaces.startSession` 与 `remote.agentPresets` + `uiWorkspace`),并断言「创建并打开」的正常、迟到注册与无启动器三种情况。
|
|
117
|
+
|
|
118
|
+
### 0.4.1 — 2026-09-03
|
|
119
|
+
|
|
120
|
+
- **DSH `0.1.2-rc.1` 兼容**:运行时按特性检测自动选用新旧 API——`uiWorkspace.startSession()`(`0.1.2-rc.1+`)/ `workspaces.startSession()`(`0.1.1-rc.2` 及更早)、`summary.projectionValues?.agentPreset`(`0.1.2-rc.1+`)/ `summary.agentPreset`(`0.1.1-rc.2` 及更早);兼容性清单加入 `0.1.2-rc.1`。
|
|
121
|
+
- **修复 `0.1.2-rc.1+` 上的 `without inject` 崩溃**:agent preset 名单改经 `ctx.get('remote.agentPresets')` 读取(拓扑无关的服务查找),不再走 `remote` 聚合上的 `agentPresets` 属性——Cordis 的 associate 代理会拒绝未在 `inject` 声明的点号属性。`inject` 仍只保留两代 DSH 共有的服务(`slots`、`locale`、`sessions`、`workspaces`)。
|
|
122
|
+
|
|
123
|
+
### 0.4.0 — 2026-08-29
|
|
124
|
+
|
|
125
|
+
- **查询缓存**:已完成的技能目录查询按扫描根缓存 10 秒,避免在慢速 9P 共享上反复重扫;`get()` 仍实时读正文,新技能在 TTL 窗口内出现。
|
|
126
|
+
- **符号链接项目**:0.4.0 时显式识别目录符号链接并安全剪枝(不崩、不循环),并实测出"光靠共享本身跟不了"——Windows 侧无法解析 Linux 符号链接(`readlink` → `EISDIR`,`stat`/`readdir` → `ENOENT`);0.4.5 改为交给发行版解析,链进来的项目因此可被发现(见该版本说明)。另加"名称 + 正文"指纹去重,保证能解析链接的平台上别名技能不会被发布两次。
|
|
127
|
+
- **块标量 frontmatter**:`description:` / `whenToUse:` 写成 YAML 块标量(`|`、`>`)现在能解析,此前这类技能会被静默丢弃。
|
|
128
|
+
- **兼容性清单**:`dsh.compatibility.dshReleases` 逐版本声明兼容性,并附可复现的一次性 Profile 安装/启动/卸载证据;`engines` 声明 Node.js 下限。
|
|
129
|
+
- **门禁脚本**:`scripts/check-rank-parity.mjs` 在项目等级常量与宿主 `dsh-skill-filesystem` 漂移时让发布失败。
|
|
130
|
+
|
|
131
|
+
### 0.3.2 — 2026-08-29
|
|
132
|
+
|
|
133
|
+
- **WSL 会话注入嵌套项目的技能目录**([#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10)):注册工作区之下的嵌套项目里的 `.dsh/skills` / `.agents/skills` 会带宿主的项目等级与来源一并发布,模型看到的目录与会话 cwd 就在项目里时一致;发现过程有深度与预算上限,会剪掉 `node_modules` 与点目录,且不改动非 WSL 会话。
|
|
134
|
+
- **扫描根对齐宿主**:从项目子目录发起查询先就近解析 `.git` 祖先,深层 cwd 也能看到所属项目的技能,且不会泄漏该祖先之上的技能。
|
|
135
|
+
- **加固**:技能根预算按次强制,`skills.registerProvider` 调用加了保护,宿主 `skills` 服务形状不同时不再拖垮插件加载。
|
|
136
|
+
- **清理**:移除历史预构建 `lib/` chunk 中残留的死 vendor 代码(含内联的 schemastery 副本),并补充嵌套技能目录的回归测试与 TESTING.md 章节。
|
|
137
|
+
|
|
39
138
|
## 许可与出处
|
|
40
139
|
|
|
41
140
|
MIT,详见 [LICENSE](LICENSE) 与 [NOTICE](NOTICE),NOTICE 精确列明:
|
package/TESTING.md
CHANGED
|
@@ -59,12 +59,17 @@ This exercises the filesystem round-trip (resolve/write/read/edit/stat/version/l
|
|
|
59
59
|
node scripts/verify-lib.mjs
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The `build` script chains
|
|
62
|
+
The `build` script clears the committed `lib/` first and chains the gate after `tsdown`:
|
|
63
63
|
|
|
64
64
|
```powershell
|
|
65
|
-
pnpm build # tsdown && node scripts/verify-lib.mjs
|
|
65
|
+
pnpm build # node scripts/clean-lib.mjs && tsdown && node scripts/verify-lib.mjs
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
The clean step is not optional: `tsdown` runs with `clean: false` and the node and
|
|
69
|
+
client configurations share `lib/` as their output directory, so without it every
|
|
70
|
+
code-split chunk an earlier build emitted stays in the tree (and, once a file is
|
|
71
|
+
renamed, ships in the tarball as dead weight).
|
|
72
|
+
|
|
68
73
|
## Nested skill-catalog regression (issue #10)
|
|
69
74
|
|
|
70
75
|
The WSL skill provider publishes `.dsh/skills` / `.agents/skills` from nested projects below a WSL workspace (and from the cwd's nearest `.git` ancestor). Regression-test it on the real 9P share:
|
|
@@ -99,11 +104,32 @@ After installing the plugin into a profile and restarting `dsh web`:
|
|
|
99
104
|
|
|
100
105
|
## Release checklist
|
|
101
106
|
|
|
102
|
-
1. `pnpm build` —
|
|
103
|
-
2. `node --experimental-strip-types --test tests
|
|
107
|
+
1. `pnpm build` — clears `lib/`, rebuilds it, and runs the verification gate.
|
|
108
|
+
2. `node --experimental-strip-types --test tests/*.test.ts` — all green (locales, variants, paths, shell, fs execution context, fs policy, wsl skills, wsl search).
|
|
104
109
|
3. `node tests/host-materialize.mjs` — all assertions pass.
|
|
105
110
|
4. `node --experimental-strip-types tests/smoke.ts` — real-WSL round-trip passes.
|
|
106
111
|
5. `node scripts/check-rank-parity.mjs` — host rank constants still match our copies.
|
|
107
112
|
6. `node scripts/repro-e2e.mjs` (after `scripts/repro-setup.sh`) — nested skill-catalog assertions pass.
|
|
108
|
-
7. `npm pack --dry-run` — confirm the tarball carries only live `lib/` chunks, `src/`, `cordis.patch.yml`, READMEs, `LICENSE`, and `NOTICE
|
|
109
|
-
8.
|
|
113
|
+
7. `npm pack --dry-run` — confirm the tarball carries only live `lib/` chunks, `src/`, `cordis.patch.yml`, READMEs, `LICENSE`, and `NOTICE`.
|
|
114
|
+
8. `npm run verify:install` — packs the tree and installs the tarball with **plain npm** into a scratch directory, with no pnpm and no host packages present. This is the gate that would have caught 0.7.0, whose `peerDependencies` made npm auto-install an unpublished package (`E404 @deepseek-ai/dsh-retention`): every other check and every real session goes through `dsh plugin add` (pnpm), which only *warns* about unmet peers and installs anyway. `prepublishOnly` runs it, so `npm publish` now refuses to ship a package that npm users cannot install.
|
|
115
|
+
9. Install the tarball into a clean profile (`dsh plugin --profile web add <tarball>`), restart `dsh web`, and run the end-to-end checks above plus the nested-skill probe. When the compatibility manifest changes, also run `scripts/verify-dsh-compat.sh` for every declared release.
|
|
116
|
+
10. For a release, install the *published* version by name into one isolated case per declared release and confirm each boots (the launcher only reports ready once the plugin's API route answers) — the check that proves the artifact on the registry, not just the local tree.
|
|
117
|
+
|
|
118
|
+
### The multi-release check harness
|
|
119
|
+
|
|
120
|
+
`scripts/compatibility/` prepares one isolated case per declared release (its own
|
|
121
|
+
`DSH_HOME`, its own dependency tree pinned to that release, the plugin installed
|
|
122
|
+
into it) and runs a fixed check list inside it. The drivers require PowerShell 7.2,
|
|
123
|
+
so on a Windows PowerShell 5.1 host use the Node equivalent:
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
node .test-runs/harness.mjs <runId> 0.1.0-rc.7 0.1.5-rc.2 # prepare + check
|
|
127
|
+
node .test-runs/harness.mjs <runId> --checks 0.1.5-rc.2 # re-check an existing case
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Four checks need a live WSL distribution (`skills-real`, `fs-real`, `relay-real`,
|
|
131
|
+
`search-real`); they build their own fixtures under `/tmp/dsh-wsl-compat` (override
|
|
132
|
+
with `WSL_COMPAT_ROOT`, and the distribution with `WSL_COMPAT_DISTRO`) and remove
|
|
133
|
+
them again. `host-api` needs a running `dsh web` for the case, so it is expected to
|
|
134
|
+
fail in a sweep. The `typecheck` baseline is two pre-existing errors from the
|
|
135
|
+
harness's own type declarations.
|
package/lib/client.js
CHANGED
|
@@ -286,6 +286,20 @@ function WslHelp({ t, description }) {
|
|
|
286
286
|
role: "region",
|
|
287
287
|
"aria-label": t("help.button"),
|
|
288
288
|
children: [
|
|
289
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
290
|
+
className: "dww-help-greeting",
|
|
291
|
+
children: [
|
|
292
|
+
t("help.greeting"),
|
|
293
|
+
" ",
|
|
294
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
295
|
+
className: "dww-help-link",
|
|
296
|
+
href: "https://github.com/6Mikao9/dsh-wsl-workspace",
|
|
297
|
+
target: "_blank",
|
|
298
|
+
rel: "noreferrer",
|
|
299
|
+
children: t("help.greeting.repo")
|
|
300
|
+
})
|
|
301
|
+
]
|
|
302
|
+
}),
|
|
289
303
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
|
|
290
304
|
title: t("help.compat.title"),
|
|
291
305
|
children: [
|
|
@@ -306,6 +320,13 @@ function WslHelp({ t, description }) {
|
|
|
306
320
|
})
|
|
307
321
|
]
|
|
308
322
|
}),
|
|
323
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
324
|
+
title: t("help.news.title"),
|
|
325
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
326
|
+
className: "dww-help-list",
|
|
327
|
+
children: bullets(t("help.news.body")).map((line) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: line }, line))
|
|
328
|
+
})
|
|
329
|
+
}),
|
|
309
330
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
|
|
310
331
|
title: t("help.usage.title"),
|
|
311
332
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
@@ -1017,6 +1038,15 @@ const STYLES = `
|
|
|
1017
1038
|
padding-right: 4px;
|
|
1018
1039
|
}
|
|
1019
1040
|
.dww-help-section { display: flex; flex-direction: column; gap: 6px; }
|
|
1041
|
+
.dww-help-greeting {
|
|
1042
|
+
margin: 0;
|
|
1043
|
+
padding: 8px 10px;
|
|
1044
|
+
border-radius: 8px;
|
|
1045
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
1046
|
+
font-size: 13px;
|
|
1047
|
+
line-height: 1.5;
|
|
1048
|
+
color: var(--dsw-alias-label-primary);
|
|
1049
|
+
}
|
|
1020
1050
|
.dww-help-title {
|
|
1021
1051
|
margin: 0;
|
|
1022
1052
|
font-size: 13px;
|
|
@@ -1100,14 +1130,18 @@ const zh = {
|
|
|
1100
1130
|
"error.pathNotFound": "该路径不存在或是文件,请选择一个文件夹",
|
|
1101
1131
|
"error.createFailed": "创建工作区失败",
|
|
1102
1132
|
"help.button": "插件说明",
|
|
1133
|
+
"help.greeting": "当你看到这句话的时候,说明你的插件已经Cia进来llo~(∠・ω< )⌒★,star一下吗?",
|
|
1134
|
+
"help.greeting.repo": "github.com/6Mikao9/dsh-wsl-workspace",
|
|
1103
1135
|
"help.compat.title": "兼容性",
|
|
1104
1136
|
"help.compat.versionLabel": "插件版本",
|
|
1105
1137
|
"help.compat.unknown": "未能读取版本与兼容声明(宿主端没有响应)",
|
|
1106
|
-
"help.compat.body": "上方是这份构建声明兼容的 DSH 版本,每一条都在隔离实例上实测过(独立 DSH_HOME、依赖固定到该版本、跑满十项门禁)。\n
|
|
1138
|
+
"help.compat.body": "上方是这份构建声明兼容的 DSH 版本,每一条都在隔离实例上实测过(独立 DSH_HOME、依赖固定到该版本、跑满十项门禁)。\n插件在运行时自动识别 DSH 版本并选用对应的 API;两边都不支持时会明确报错,而不是留下一个空工作区。\n版本不在列表里通常仍然可用,但未经验证。",
|
|
1139
|
+
"help.news.title": "本次更新(0.7.2)",
|
|
1140
|
+
"help.news.body": "修掉了 WSL 技能目录在请求路径上重走的问题(issue #25):宿主会在请求期间重建技能目录并等待各 provider 的 list(),而插件只把答案保留 10 秒,于是每次重新收集都得重走整棵工作区,工作区一大就正好卡在走查的 4096 目录预算上。\n9P 共享上单次 readdir 实测 3~16 毫秒,所以命中预算的工作区那次走查要 20 秒;现在已发布的目录直接照原样返回,只有插件自己的探针确认变化时才丢弃缓存,重复查找降到 1~3 毫秒、且完全不碰文件系统。\n走查本身也变便宜:同一层并发探测(有上限)、按 frontier 顺序发布以保证目录顺序确定,且只在目录清单里出现 .dsh / .agents 时才探测对应 skills。同机实测命中预算的走查从 20.4 秒降到 4.8 秒。\n修掉了 npm 安装失败:npm 会自动安装缺失的同伴依赖,而 @deepseek-ai/dsh-tool-fs-search 自己又依赖并未公开发布的 @deepseek-ai/dsh-retention,于是 npm install dsh-wsl-workspace 直接 E404 失败(dsh plugin 走 pnpm、只警告,所以一直没暴露)。\n十个宿主同伴依赖现在都标为 optional:包仍声明宿主必须提供什么,但 npm 不再尝试下载它们。\nWSL 世界现在在会话能察觉到的每一处都与宿主一致:Linux 符号链接、会话的访问模式、发行版内的 grep / glob、实时技能目录、有状态的 shell,以及可跟踪的后台任务。\n新增 bash_background:持久 shell 的 schema 只有 command,没有生产者时 job_list 永远说\"没有后台任务\",而传给 bash 的 run_in_background 会被静默忽略;现在后台任务可跟踪,job_output 增量读取、job_kill 取消都照常工作。\nbash 的工具描述写明两件宿主没说的事:cd / export 跨调用保留,命令以 & 结尾会把整条命令后台化(请写成单独一行的 ( 长任务 > log 2>&1 ) &)。0.1.0-rc.7 的宿主缺少 Windows 进程检查器、持久 shell 起不来,插件会在启动时探测并回退到一次性 bash,不再让每次调用都报错。",
|
|
1107
1141
|
"help.usage.title": "用法与特性",
|
|
1108
|
-
"help.usage.body": "点击侧边栏底部的 W 按钮 → 选发行版 → 输入或浏览 Linux 路径 → 检查 → 创建并打开。\n创建出的会话里,bash 与文件工具都落在该发行版内,模型看到的路径全是 Linux 路径;Windows 盘可从会话内通过 /mnt/<盘符> 访问。\n四种模式(标准 / PTC / 极简 / 创造)各有 WSL 变体,在模式选择器里直接选即可,名字形如 WSL · Standard mode(标准模式)。\n用户名可选,等价于 wsl.exe -u <用户名>,只改变 bash 的运行身份;文件工具走 Windows 侧共享,不受它影响。\n技能目录从会话 cwd 最近的 .git 祖先开始向下扫描 .dsh/skills 与 .agents/skills(含嵌套项目),上限 4 层目录 / 64 个技能目录 / 4096 个已访问目录,并按扫描根缓存 10
|
|
1142
|
+
"help.usage.body": "点击侧边栏底部的 W 按钮 → 选发行版 → 输入或浏览 Linux 路径 → 检查 → 创建并打开。\n创建出的会话里,bash 与文件工具都落在该发行版内,模型看到的路径全是 Linux 路径;Windows 盘可从会话内通过 /mnt/<盘符> 访问。\n四种模式(标准 / PTC / 极简 / 创造)各有 WSL 变体,在模式选择器里直接选即可,名字形如 WSL · Standard mode(标准模式)。\n用户名可选,等价于 wsl.exe -u <用户名>,只改变 bash 与 persistent-bash 的运行身份;文件工具走 Windows 侧共享,不受它影响。\n技能目录从会话 cwd 最近的 .git 祖先开始向下扫描 .dsh/skills 与 .agents/skills(含嵌套项目),上限 4 层目录 / 64 个技能目录 / 4096 个已访问目录,并按扫描根缓存 10 秒。9P 解不开的符号链接交给发行版 readlink 解析出真实路径(每次查找最多 32 条)后继续扫描,链接进来的项目与它下面的嵌套项目都能扫到。\n目录不再冻结:对 UNC 关闭文件监视之后,插件每 3 秒重查一次已发布的技能目录、并比对每个技能文件的修改时间与大小,所以新增、删除与改写都会在下一个回合生效;完整重新发现每 30 秒一次,用于找到此前不存在的技能目录(技能正文始终实时读取)。\n文件搜索由发行版内的 grep / glob 提供,源预设没有这两个工具的模式(极简)不会多出来:grep 用 GNU grep -E(\\d、\\w、(?i) 可用,环视与反向引用不支持),跳过隐藏项与 node_modules,不读 .gitignore;glob 用 GNU find 列文件、按 gitignore 风格匹配(* 不跨目录、** 跨、{a,b}、前导 ! 取反),按修改时间从旧到新排序。\n文件工具在链接处按真实路径工作,并按真实路径判策略:链接指向工作区外就等于工作区外。\nbash 由 PTY 承载的持久 shell 提供:登录环境、起始目录就是会话工作区,cd / export / venv / 后台任务跨调用保留(它取代了原先一次性 bash)。\n需要可跟踪的后台任务时用 bash_background:它立刻返回 job id,job_list / job_output(增量读取)/ job_kill 都作用于它;bash 本身没有 run_in_background 参数,传了会被忽略。job id 只在本次 DSH 进程内唯一,重启后会重新编号,引用前先用 job_list 确认。\nbash 与文件工具的 shell 都在发行版内运行、不受 DSH 文件策略约束;文件工具(read/write/edit)受策略约束,工作区内修改模式下只能写工作区内。",
|
|
1109
1143
|
"help.known.title": "已知问题",
|
|
1110
|
-
"help.known.body": "
|
|
1144
|
+
"help.known.body": "grep 用的是发行版自带的 GNU grep:方言是 POSIX ERE(环视与反向引用不支持),且不读 .gitignore,被 git 忽略的文件照样会被搜到;只有隐藏项、node_modules 与版本库目录会被跳过。发行版没有 GNU grep(如 Alpine 的 busybox)时会明确报错,而不是给出错位的结果。\nglob 的\"按修改时间排序\"依赖 GNU find -printf,busybox 会退化成路径排序;含 / 的 include 在插件进程里过滤,因此那种调用会先扫描全部文件再筛。\n技能目录刷新仍是轮询:已发布目录内的增删改约 3 秒生效,而一个新项目里第一次出现的技能目录要等下一次完整重新发现(最多 30 秒)。\n0.1.0-rc.7 的宿主没有 Windows 进程检查器,PTY 持久 shell 无法启动(宿主自己也这样),插件回退到一次性 bash:能正常用,但 cd / export 不跨调用保留。\n极简模式本身不挂 job_* 工具,所以那个模式里也没有 bash_background(与宿主的极简模式一致)。",
|
|
1111
1145
|
"help.footer.npm": "npm 包",
|
|
1112
1146
|
"help.footer.repo": "GitHub 仓库"
|
|
1113
1147
|
};
|
|
@@ -1140,14 +1174,18 @@ const en = {
|
|
|
1140
1174
|
"error.pathNotFound": "The path does not exist or is a file; choose a folder",
|
|
1141
1175
|
"error.createFailed": "Failed to create the workspace",
|
|
1142
1176
|
"help.button": "About this plugin",
|
|
1177
|
+
"help.greeting": "If you can read this, the plugin has already Cia~llo'd its way in~(∠・ω< )⌒★ Care to star the repo?",
|
|
1178
|
+
"help.greeting.repo": "github.com/6Mikao9/dsh-wsl-workspace",
|
|
1143
1179
|
"help.compat.title": "Compatibility",
|
|
1144
1180
|
"help.compat.versionLabel": "Plugin version",
|
|
1145
1181
|
"help.compat.unknown": "Version and compatibility declaration unavailable (the host side did not answer)",
|
|
1146
|
-
"help.compat.body": "The chips above are the DSH releases this build declares, each verified on an isolated instance (own DSH_HOME, dependencies pinned to that release, full check suite).\nThe plugin detects the DSH generation at runtime
|
|
1182
|
+
"help.compat.body": "The chips above are the DSH releases this build declares, each verified on an isolated instance (own DSH_HOME, dependencies pinned to that release, full check suite).\nThe plugin detects the DSH generation at runtime and picks the matching API; a release exposing neither fails loudly instead of leaving an empty workspace.\nA release outside the list usually still works, but is unverified.",
|
|
1183
|
+
"help.news.title": "What's new in 0.7.2",
|
|
1184
|
+
"help.news.body": "Fixed the WSL skill catalog being re-walked on the request path (issue #25): the host rebuilds the catalog during a request and awaits each provider's list(), while this plugin kept its answer for only 10 s - so every re-collection re-walked the whole workspace.\nA readdir over the WSL 9P share costs 3-16 ms and the walk's budget is 4096 directories, so a large workspace sat 20 s on it. The published catalog is now served as-is until the provider's own watcher sees a change, which takes a repeat lookup to 1-3 ms with no filesystem traffic at all.\nThe walk is cheaper too: one BFS layer is probed concurrently and published in frontier order, and a directory's .dsh/skills is probed only when its own listing showed .dsh. A budget-sized walk measured 20.4 s before and 4.8 s after.\nFixed a failed npm install: npm auto-installs missing peers, and @deepseek-ai/dsh-tool-fs-search peers on @deepseek-ai/dsh-retention, which is not published - so npm install dsh-wsl-workspace died with E404.\nThe ten host peer dependencies are now marked optional, so npm leaves them to the host (dsh plugin add uses pnpm and only warned, which is why this stayed hidden).\nThe WSL world now matches the host everywhere a session can tell the difference: Linux symlinks, the session's access mode, in-distribution grep / glob, a live skill catalog, a stateful shell, and tracked background jobs.\nNew: bash_background. The persistent shell takes `command` only, so without a producer job_list always answered \"no background jobs\" and a run_in_background argument passed to bash was silently ignored; background work is now tracked, with incremental job_output reads and job_kill.\nThe bash tool description now states two things the host does not: cd / exports carry into the next call, and a command ending in & backgrounds the whole command - background the subshell instead, as ( long-job > log 2>&1 ) &.\n0.1.0-rc.7 lacks the Windows process inspector the persistent shell needs, so the plugin probes for it and falls back to a one-shot bash instead of failing every call.",
|
|
1147
1185
|
"help.usage.title": "Usage and features",
|
|
1148
|
-
"help.usage.body": "Click the W button at the sidebar foot, pick a distribution, type or browse to a Linux path, press Check, then Create & open.\nIn that session the bash tool and the file tools run inside the distribution, so every path the model sees is a Linux path; Windows drives stay reachable as /mnt/<drive>.\nEach mode (Standard / PTC / Minimal / Creator) has a WSL variant in the mode picker, named like WSL · Standard mode.\nThe optional username behaves like wsl.exe -u <user> for
|
|
1186
|
+
"help.usage.body": "Click the W button at the sidebar foot, pick a distribution, type or browse to a Linux path, press Check, then Create & open.\nIn that session the bash tool and the file tools run inside the distribution, so every path the model sees is a Linux path; Windows drives stay reachable as /mnt/<drive>.\nEach mode (Standard / PTC / Minimal / Creator) has a WSL variant in the mode picker, named like WSL · Standard mode.\nThe optional username behaves like wsl.exe -u <user> for bash and persistent-bash; the file tools go through the Windows-side share and are unaffected.\nThe skill catalog is discovered from the nearest .git ancestor of the session cwd downwards (.dsh/skills and .agents/skills, nested projects included), bounded to 4 levels / 64 skill directories / 4096 visited directories, and cached per scan root for 10 seconds.\nA link the share cannot follow is resolved through the distribution (wsl.exe readlink, at most 32 per lookup) and the scan continues at the real path, so a linked-in project and its own nested projects are found too.\nThe catalog is not frozen: published skills directories are re-checked every 3 seconds (skill file mtime + size), so an add, remove or edit appears on the next turn; a 30-second walk finds a skills directory that did not exist before.\nFile search comes from grep / glob inside the distribution, and a mode that mounts no search suite (Minimal) gains none. grep is GNU grep -E (no lookaround or backreferences), skips hidden entries and node_modules, and does not read .gitignore; glob matches gitignore-style patterns here, oldest first.\nThe file tools work at the resolved real path of a link, and the policy is judged there too: a link out of the workspace is an outside write.\n`bash` is a PTY-backed stateful shell: login environment, starting directory the session workspace, and cd / exports / background jobs survive between calls.\nFor a tracked background job use bash_background: it returns a job id immediately, and job_list / job_output (incremental) / job_kill act on it.\n`bash` itself has no run_in_background parameter and ignores one; job ids are unique within this DSH process, so confirm with job_list before acting on one.\nBoth it and the file tools' shell run inside the distribution, outside the DSH file policy; read/write/edit are inside it, and workspace-write only writes inside the workspace.",
|
|
1149
1187
|
"help.known.title": "Known issues",
|
|
1150
|
-
"help.known.body": "
|
|
1188
|
+
"help.known.body": "grep is the distribution's GNU grep: POSIX ERE (no lookaround or backreferences), and it does not read .gitignore, so git-ignored files are searched too; only hidden entries, node_modules and VCS directories are skipped.\nglob's modification-time order needs GNU find -printf (busybox falls back to path order), and an include containing \"/\" is filtered in this process, so that call scans every file first.\nThe catalog refresh is still a poll: an add, remove or edit inside a published skills directory lands within about 3 seconds, while a new project's first skills directory waits for the next full re-discovery (up to 30 seconds).\n0.1.0-rc.7 has no Windows process inspector, so its PTY persistent shell cannot start (the host has the same gap) and the plugin falls back to a one-shot bash: it works, but cd / exports do not survive.\nMinimal mode mounts no job_* tools, so it gets no bash_background either - the same as the host's own Minimal mode.",
|
|
1151
1189
|
"help.footer.npm": "npm package",
|
|
1152
1190
|
"help.footer.repo": "GitHub repository"
|
|
1153
1191
|
};
|