dsh-plugin-wsl-env 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +488 -12
- package/CONTRIBUTING.md +125 -62
- package/README.md +99 -68
- package/SECURITY.md +18 -2
- package/SUPPORT.md +26 -10
- package/agent/native/linux-aarch64/dsh-wsl-agent +0 -0
- package/agent/native/linux-x86_64/dsh-wsl-agent +0 -0
- package/agent/wsl-agent.sh +83 -16
- package/cordis.patch.yml +50 -35
- package/docs/ARCHITECTURE.md +203 -55
- package/docs/CONFIGURATION.md +185 -18
- package/docs/LIMITATIONS.md +125 -45
- package/docs/PARITY.md +68 -38
- package/docs/PEER-PARITY.md +103 -33
- package/docs/README.zh.md +105 -64
- package/docs/UPSTREAM-FSIO-EXPORT.md +159 -50
- package/docs/UPSTREAM-TERMINAL-TITLE.md +56 -14
- package/examples/preset-wsl-session-sandbox.cordis.patch.yml +178 -0
- package/lib/agent-protocol.d.ts +10 -4
- package/lib/agent-protocol.js +10 -3
- package/lib/agent.d.ts +84 -0
- package/lib/agent.js +167 -15
- package/lib/bwrap.d.ts +8 -0
- package/lib/bwrap.js +4 -1
- package/lib/fs-routing.d.ts +40 -4
- package/lib/fs-routing.js +80 -12
- package/lib/fsio-agent.js +1 -1
- package/lib/index.d.ts +134 -0
- package/lib/index.js +202 -9
- package/lib/native-agent.d.ts +124 -0
- package/lib/native-agent.js +198 -0
- package/lib/ports.d.ts +13 -2
- package/lib/ports.js +18 -5
- package/lib/preflight.d.ts +91 -0
- package/lib/preflight.js +117 -0
- package/lib/probe-cache.d.ts +12 -1
- package/lib/probe-cache.js +11 -3
- package/lib/sandbox-core.d.ts +70 -0
- package/lib/sandbox-core.js +187 -18
- package/lib/session-runtime.d.ts +161 -0
- package/lib/session-runtime.js +220 -0
- package/lib/session-scope.d.ts +124 -0
- package/lib/session-scope.js +191 -0
- package/lib/shell-env.js +10 -3
- package/lib/watcher.d.ts +25 -12
- package/lib/watcher.js +120 -32
- package/lib/workspace-files-route.d.ts +8 -4
- package/lib/workspace-files-route.js +42 -14
- package/lib/workspace-files-wsl.js +25 -8
- package/lib/wsl.js +10 -4
- package/locale/preflight/en.json +6 -0
- package/locale/preflight/zh.json +6 -0
- package/package.json +44 -27
- package/scripts/diagnose.sh +92 -0
|
@@ -1,54 +1,90 @@
|
|
|
1
|
-
# Upstream proposal: export the fsio
|
|
1
|
+
# Upstream proposal: export the fsio text helpers from `dsh-fs-local`
|
|
2
2
|
|
|
3
|
-
Status: draft,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Status: draft, not yet filed upstream — no discussion link yet (the filing
|
|
4
|
+
convention is [UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md)).
|
|
5
|
+
Targeting `deepseek-harness` @ `packages/fs/fs-local`, the `0.2.1-rc.x` line
|
|
6
|
+
this plugin pins exactly (`@deepseek-ai/dsh-fs-local@0.2.1-rc.2`, in both the
|
|
7
|
+
dev and peer dependency blocks of `package.json`). This is the Shape-A
|
|
8
|
+
upstream half of the in-distro I/O refactor: with the fsio text mechanics
|
|
9
|
+
importable, `dsh-plugin-wsl-env` deletes its copies of them.
|
|
7
10
|
|
|
8
11
|
## The change
|
|
9
12
|
|
|
10
|
-
The fsio module already exists
|
|
11
|
-
|
|
12
|
-
`"./package.json"`, and `lib/index.js` does not re-export the helpers. Two
|
|
13
|
-
lines of surface, no behaviour change:
|
|
13
|
+
The fsio module already exists and is fully documented in the shipped
|
|
14
|
+
declarations (`lib/types/fsio.d.ts`). Two facts keep it out of reach:
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
16
|
+
- `package.json` exports exactly three entries — `"."`, `"./src/*"` and
|
|
17
|
+
`"./package.json"` (lines 16–23). `"./src/*"` is not a way in: `files` is
|
|
18
|
+
`["lib/index.js", "lib/types/**/*.d.ts"]` (lines 24–27), so the sources are
|
|
19
|
+
in the repository but not in the tarball (`npm pack --dry-run` lists nine
|
|
20
|
+
files, none of them under `src/`).
|
|
21
|
+
- The published runtime is one bundled file. The fsio code is a region of
|
|
22
|
+
`lib/index.js` (`//#region lib/types/fsio.js`, line 90) — there is no
|
|
23
|
+
`lib/fsio.js` — and that file's only export statement is
|
|
24
|
+
`export { LocalFileSystem, LocalFileSystem as default };` (line 909).
|
|
25
|
+
|
|
26
|
+
So the reachable seam is the package entry, not a new subpath: re-export the
|
|
27
|
+
four text helpers from it, in the runtime and in the declarations.
|
|
28
|
+
|
|
29
|
+
(A `"./fsio"` subpath does not work as-is — the runtime file it would name does
|
|
30
|
+
not exist. Pointing its `types` at `lib/types/fsio.d.ts` instead would publish
|
|
31
|
+
the whole fsio surface — `readWholeText`, `readWholeBytes`, `readByteWindow`,
|
|
32
|
+
`streamWholeText`, `writeFileAtomic`, `probe`, `probeNoFollow`,
|
|
33
|
+
`listDirectory`, `resolveLocalTarget`, `readForEdit`, `readTextForDiff`, … —
|
|
34
|
+
while the runtime side offered four names. Types and runtime must promise the
|
|
35
|
+
same surface.)
|
|
32
36
|
|
|
33
37
|
```diff
|
|
34
38
|
--- a/packages/fs/fs-local/lib/index.js
|
|
35
39
|
+++ b/packages/fs/fs-local/lib/index.js
|
|
36
|
-
@@
|
|
40
|
+
@@ -906,4 +906,11 @@
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
37
44
|
-export { LocalFileSystem, LocalFileSystem as default };
|
|
38
45
|
+export {
|
|
39
|
-
+
|
|
40
|
-
+
|
|
41
|
-
+
|
|
42
|
-
+
|
|
43
|
-
+
|
|
44
|
-
+
|
|
46
|
+
+ LocalFileSystem,
|
|
47
|
+
+ LocalFileSystem as default,
|
|
48
|
+
+ applyLiteralEdit,
|
|
49
|
+
+ detectLineEndings,
|
|
50
|
+
+ normalizeLineEndings,
|
|
51
|
+
+ restoreLineEndings,
|
|
45
52
|
+};
|
|
46
53
|
```
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
```diff
|
|
56
|
+
--- a/packages/fs/fs-local/lib/types/index.d.ts
|
|
57
|
+
+++ b/packages/fs/fs-local/lib/types/index.d.ts
|
|
58
|
+
@@ -66,4 +66,5 @@
|
|
59
|
+
private versionAfterWrite;
|
|
60
|
+
}
|
|
61
|
+
export default LocalFileSystem;
|
|
62
|
+
+export { applyLiteralEdit, detectLineEndings, normalizeLineEndings, restoreLineEndings } from './fsio.ts';
|
|
63
|
+
//# sourceMappingURL=index.d.ts.map
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Both files are generated; the change itself belongs in the sources that emit
|
|
68
|
+
them (`src/index.ts` re-exporting from the fsio module, and that module
|
|
69
|
+
exporting `detectLineEndings`). The artifact diffs are shown because they are
|
|
70
|
+
what a consumer resolves — and they apply as-is to the published `0.2.1-rc.2`
|
|
71
|
+
files.
|
|
72
|
+
|
|
73
|
+
`detectLineEndings` is the one genuinely new name: it is not in
|
|
74
|
+
`lib/types/fsio.d.ts` at all — in the bundle it is a non-exported function
|
|
75
|
+
(`function detectLineEndings(raw)`, line 587). Declaring it is a
|
|
76
|
+
`declare function detectLineEndings(raw: string): LineEndings;` beside its two
|
|
77
|
+
siblings, plus the name in the export list at `fsio.d.ts:223` (`LineEndings`
|
|
78
|
+
itself is already exported, `:166`). The other three are already declared
|
|
79
|
+
there: `normalizeLineEndings` (`:173`), `restoreLineEndings` (`:182`),
|
|
80
|
+
`applyLiteralEdit` (`:219`), with the first two exported at `:223`. If upstream
|
|
81
|
+
would rather not widen the surface at all, leave `detectLineEndings` out and the
|
|
82
|
+
consumer keeps its four-line local copy.
|
|
83
|
+
|
|
84
|
+
`readForEdit` and `readTextForDiff` are not part of this. They are exported by
|
|
85
|
+
the fsio module (`fsio.d.ts:191`, `:207`), but they are I/O orchestration —
|
|
86
|
+
which the plugin drives against its own substrate — so the proposal simply does
|
|
87
|
+
not re-export them.
|
|
52
88
|
|
|
53
89
|
## Why the plugin needs it
|
|
54
90
|
|
|
@@ -56,27 +92,100 @@ substrate — only the pure text mechanics are needed.)
|
|
|
56
92
|
`lib/fs-substrate.js`) re-runs fsio's orchestration inside a WSL distro: reads,
|
|
57
93
|
writes, identities and edits execute where the files live, and the host side
|
|
58
94
|
keeps only the validation and edit mechanics. Those mechanics —
|
|
59
|
-
`applyLiteralEdit`, the line-ending trio, the binary NUL sample, the fatal
|
|
60
|
-
decode — are replicated today
|
|
61
|
-
export them. The replica is
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
95
|
+
`applyLiteralEdit`, the line-ending trio, the binary NUL sample, the fatal
|
|
96
|
+
UTF-8 decode — are replicated today in `src/fsio-text.ts` (built to
|
|
97
|
+
`lib/fsio-text.js`) because the package does not export them. The replica is
|
|
98
|
+
byte-for-byte faithful, including error messages, because the tool layer
|
|
99
|
+
matches on both the code and the text the model reads — and that faithfulness
|
|
100
|
+
is verified, not assumed: [PEER-PARITY.md](PEER-PARITY.md) records the
|
|
101
|
+
function-by-function and string-by-string comparison against the pinned peer's
|
|
102
|
+
shipped code (verified 2026-10-03; the read/edit split re-verified
|
|
103
|
+
2026-10-06). But a replica is a fork surface: every upstream change to the edit
|
|
104
|
+
algorithm has to be re-ported by hand.
|
|
65
105
|
|
|
66
|
-
With the export in place the plugin
|
|
106
|
+
With the export in place the plugin imports the four functions and deletes its
|
|
107
|
+
copies:
|
|
67
108
|
|
|
68
109
|
```js
|
|
69
|
-
import { applyLiteralEdit, detectLineEndings, normalizeLineEndings, restoreLineEndings } from "@deepseek-ai/dsh-fs-local
|
|
110
|
+
import { applyLiteralEdit, detectLineEndings, normalizeLineEndings, restoreLineEndings } from "@deepseek-ai/dsh-fs-local";
|
|
70
111
|
```
|
|
71
112
|
|
|
113
|
+
The rest of the module stays. `BINARY_SAMPLE_BYTES`, `FsCodedError`,
|
|
114
|
+
`notTextError`, `decodeUtf8`, `decodeUtf8Stream` and `throwIfAborted`
|
|
115
|
+
(`src/fsio-text.ts:23`, `:30`, `:53`, `:67`, `:86`, `:179`) have no counterpart
|
|
116
|
+
in the fsio declarations, and the module's other consumers keep importing them
|
|
117
|
+
locally (`src/fsio-agent.ts:27-36`, `src/fs-substrate.ts:37`,
|
|
118
|
+
`src/index.ts:57`). `lib/fsio-text.js` is not deleted; four of its ten exports
|
|
119
|
+
are.
|
|
120
|
+
|
|
121
|
+
Importing from the entry costs this consumer nothing new: it already imports
|
|
122
|
+
`LocalFileSystem` from the same entry (`src/index.ts:45`,
|
|
123
|
+
`src/fs-routing.ts:35`) and `@deepseek-ai/dsh-fs` (`src/index.ts:46`). The
|
|
124
|
+
entry's non-builtin static imports are `chokidar` (`lib/index.js:3`),
|
|
125
|
+
`@deepseek-ai/schemastery` (`:6`) and the `@deepseek-ai/dsh-fs` peer (`:7`) —
|
|
126
|
+
all three already in this consumer's graph; `koffi` loads lazily (`await
|
|
127
|
+
import("koffi")`, `:25`), so no native dependency is evaluated at import time.
|
|
128
|
+
|
|
72
129
|
## Why this is safe upstream
|
|
73
130
|
|
|
74
|
-
-
|
|
75
|
-
|
|
131
|
+
- Additive: three of the four names are already declared in the shipped
|
|
132
|
+
`lib/types/fsio.d.ts`; `detectLineEndings` is one new pure function the edit
|
|
133
|
+
path already calls (`readForEdit`, `lib/index.js:629` — the class reaches it
|
|
134
|
+
through `editText`).
|
|
76
135
|
- No behaviour change: the class's own call sites are untouched.
|
|
77
|
-
- The functions
|
|
78
|
-
them cannot widen the I/O contract.
|
|
79
|
-
-
|
|
136
|
+
- The functions do no I/O and hold no state — text in, text out, structured
|
|
137
|
+
errors — so exporting them cannot widen the I/O contract.
|
|
138
|
+
- They are pure but **not peer-free**: the module they live in imports
|
|
139
|
+
`FsError`, `FsTargetKey` and `FsVersion` from the `@deepseek-ai/dsh-fs` peer
|
|
140
|
+
(`lib/index.js:7`) and uses them throughout (`FsError` thrown at `:700`,
|
|
141
|
+
`:703`, `:704`; `FsTargetKey` at `:175`, `:196`, `:207`; `FsVersion` at
|
|
142
|
+
`:146`), so importers of these helpers take that peer at runtime. They do not
|
|
143
|
+
take cordis — the bundle imports none. The plugin accepts the peer
|
|
144
|
+
dependency: both packages are already runtime imports of it, and its tool
|
|
145
|
+
layer maps the `code` token and the message, not the error class.
|
|
146
|
+
- Semver: an additive export. On the `0.2.0-rc.x` line it ships in the next
|
|
147
|
+
pre-release — the `0.2` position does not move, so this is not a minor bump
|
|
148
|
+
of a released line; and because the consuming plugin pins the exact version,
|
|
149
|
+
adoption is an explicit pin bump, never an automatic upgrade.
|
|
150
|
+
|
|
151
|
+
## Landing it (consumer side)
|
|
152
|
+
|
|
153
|
+
1. Bump both pins in `package.json` (`devDependencies` and `peerDependencies`)
|
|
154
|
+
to the peer that carries the export.
|
|
155
|
+
2. `pnpm run build` — `lib/` is committed, and `test/build-freshness.mjs`
|
|
156
|
+
enforces that it is a byte-identical, closed image of `src/`.
|
|
157
|
+
3. Re-run the comparison in [PEER-PARITY.md](PEER-PARITY.md) (its Maintenance
|
|
158
|
+
section) against the new pin before trusting anything else.
|
|
159
|
+
4. Delete the four replicas and point the call sites at the peer, then
|
|
160
|
+
`pnpm test`.
|
|
161
|
+
|
|
162
|
+
Files that move with it:
|
|
163
|
+
|
|
164
|
+
- `src/fsio-text.ts` — drop `applyLiteralEdit`, `detectLineEndings`,
|
|
165
|
+
`normalizeLineEndings`, `restoreLineEndings`; keep the rest.
|
|
166
|
+
- `src/fsio-agent.ts` (imports `:27-36`, `detectLineEndings` used at `:528`)
|
|
167
|
+
and `src/fs-substrate.ts` (`:37`) — take the four from the peer. Where they
|
|
168
|
+
land decides the module's peer story: importing them directly keeps
|
|
169
|
+
`src/fsio-text.ts` peer-free — and `test/fsio-text.test.mjs`, which imports
|
|
170
|
+
nothing but node builtins and the local module (`:9-18`), runnable without
|
|
171
|
+
the peer — while re-exporting them through it does not. Either way the
|
|
172
|
+
"peer-free" self-descriptions in `src/fsio-agent.ts:14-16` and
|
|
173
|
+
`src/fs-substrate.ts:25` need a pass.
|
|
174
|
+
- `lib/*` — the rebuilt artifacts.
|
|
175
|
+
- `test/fsio-text.test.mjs` (the replica's tests, including the
|
|
176
|
+
`detectLineEndings` checks at `:69-72`) and `test/fsio-agent.test.mjs:22`,
|
|
177
|
+
which imports two of the four from `lib/fsio-text.js`. If that test file is
|
|
178
|
+
dropped or renamed, also the `test:coverage` and `test:unit` lists at
|
|
179
|
+
`package.json:114` and `:116`.
|
|
180
|
+
- The documents that describe the replica as current: `docs/ARCHITECTURE.md:173`
|
|
181
|
+
and `:416` (the two entries marked "replicated … pending the … export"),
|
|
182
|
+
`docs/PEER-PARITY.md:3` and `:17` (plus its Maintenance section),
|
|
183
|
+
`docs/upstream/promotion-map.md:82` — and this file, whose status then becomes
|
|
184
|
+
"landed".
|
|
185
|
+
|
|
186
|
+
Done when: the pinned peer resolves the four names from both its runtime entry
|
|
187
|
+
and its declarations, the plugin imports them there, `pnpm test` passes, and no
|
|
188
|
+
document still calls `lib/fsio-text.js` a replica of them.
|
|
80
189
|
|
|
81
190
|
## Follow-up (separate proposal, not required)
|
|
82
191
|
|
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
Status: proposed upstream as
|
|
4
4
|
[deepseek-ai/deepseek-harness#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769)
|
|
5
|
-
(targeting `packages/api/terminal-controller`, version line `0.2.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
(targeting `packages/api/terminal-controller`, version line `0.2.1-rc.x`; the
|
|
6
|
+
gap was re-checked against `dsh-v0.2.1-rc.2` on 2026-10-17 and is unchanged —
|
|
7
|
+
`packages/api/terminal-controller/src/index.ts:327` still builds the terminal
|
|
8
|
+
info with `title: shell.name`, and the spawn spec still carries no display-name
|
|
9
|
+
field). The
|
|
10
|
+
plugin worked around the execution half of this gap — a Windows-folder
|
|
11
|
+
session's terminal runs a host shell (`subprocess-wsl.hostSessions`) — and
|
|
12
|
+
could not work around the title half. This is the record: the GUI titles every
|
|
13
|
+
tab from the composition's single shell profile, so a per-session shell choice
|
|
14
|
+
is invisible in the title.
|
|
9
15
|
|
|
10
16
|
## The gap
|
|
11
17
|
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
The display title is fixed from the resolved shell profile and never revisited.
|
|
19
|
+
The private `TerminalController.spawn` builds `info`, with `title: shell.name`,
|
|
20
|
+
only after `await subprocess.spawnTerminal(...)` has returned, and it does not
|
|
21
|
+
read back what that call started:
|
|
14
22
|
|
|
15
23
|
```ts
|
|
24
|
+
const handle = await subprocess.spawnTerminal({
|
|
25
|
+
argv: [shell.path, ...shell.args], cwd: environment.cwd, cols: request.cols, ...
|
|
26
|
+
})
|
|
16
27
|
const info: WebTerminalInfo = {
|
|
17
28
|
id: request.id, shell, title: shell.name, cwd: environment.cwd, ...
|
|
18
29
|
}
|
|
@@ -48,10 +59,18 @@ Three escape hatches were evaluated against `0.2.0-rc.2` and each is closed:
|
|
|
48
59
|
inserts `terminal-controller`, and the client model that the
|
|
49
60
|
`dsh-client-ui-sidebar-terminal` entry waits for mounts with
|
|
50
61
|
`inject = ['remote', 'remote.terminal']` — the `terminal` namespace the host
|
|
51
|
-
controller registers in its constructor. Disabling the row fails web boot
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
controller registers in its constructor. Disabling the row fails web boot
|
|
63
|
+
with this two-line error:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
web boot: 1 entry did not activate
|
|
67
|
+
@deepseek-ai/dsh-client-ui-sidebar-terminal: pending (waiting for service: webTerminals)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Verified live against `dsh-desktop 0.2.0-rc.2` (Electron 44.0.0); the
|
|
71
|
+
row-id and `inject` facts the argument rests on are readable in the
|
|
72
|
+
`dsh-v0.2.1-rc.2` tree and did not change with the terminal controller's
|
|
73
|
+
0.2.1 rework (`attach`/`write` → sequenced `TerminalInput`).
|
|
55
74
|
3. **Per-session config.** The `shell` profile is composition state; `!!js`
|
|
56
75
|
expressions evaluate once at config resolution and reach no Session
|
|
57
76
|
identity.
|
|
@@ -62,7 +81,7 @@ Any one of these closes the gap; the first is the smallest and keeps the
|
|
|
62
81
|
profile name as the fallback:
|
|
63
82
|
|
|
64
83
|
- `SubprocessTerminalSpawnSpec` gains an optional `displayTitle` (or the handle
|
|
65
|
-
exposes the launched `argv`), and `
|
|
84
|
+
exposes the launched `argv`), and `spawn()` uses it for `info.title` when
|
|
66
85
|
present — falling back to `shell.name`. A provider that rewrites a launch
|
|
67
86
|
states the program it started; every other launch keeps the profile name.
|
|
68
87
|
- Alternatively, the controller derives the title from the effective program
|
|
@@ -71,11 +90,34 @@ profile name as the fallback:
|
|
|
71
90
|
|
|
72
91
|
With either, this plugin deletes the limitation paragraph in
|
|
73
92
|
[LIMITATIONS.md](LIMITATIONS.md) and the `WSL`-over-PowerShell title, for any
|
|
74
|
-
provider that rewrites launches — not just WSL.
|
|
93
|
+
provider that rewrites launches — not just WSL. The same limitation is stated
|
|
94
|
+
as current fact in four places. Three go stale the day the proposal lands; the
|
|
95
|
+
fourth is already out of step and needs the corrections above whenever it is
|
|
96
|
+
next touched:
|
|
97
|
+
|
|
98
|
+
- the GUI-terminal comment block in [`cordis.patch.yml`](../cordis.patch.yml);
|
|
99
|
+
- the closing paragraph of "Why the terminal provider is app-level" in
|
|
100
|
+
[ARCHITECTURE.md](ARCHITECTURE.md);
|
|
101
|
+
- R4's `残差` (residual) column in [REQUIREMENTS.zh.md](REQUIREMENTS.zh.md);
|
|
102
|
+
- the `WSL` tab-title bullet under "Terminals" in
|
|
103
|
+
[LIMITATIONS.md](LIMITATIONS.md) — the paragraph named just above. It says
|
|
104
|
+
the controller titles a tab *before* the provider rewrites the launch, the
|
|
105
|
+
reverse of the ordering corrected at the top of this file, and it carries the
|
|
106
|
+
one-line `shellCandidates` workaround that drops the row's `shell` block.
|
|
75
107
|
|
|
76
108
|
## Workarounds until then
|
|
77
109
|
|
|
78
110
|
- Double-click a tab's title to rename it (a shipped client feature; the
|
|
79
111
|
controller's `rename` Remote API accepts 1–120 characters).
|
|
80
|
-
-
|
|
81
|
-
manually selected shell is titled after itself.
|
|
112
|
+
- Re-declare the `terminal-controller` row in a user layer with a non-empty
|
|
113
|
+
`shellCandidates`: a manually selected shell is titled after itself. A patch
|
|
114
|
+
config replaces the whole config rather than deep-merging it
|
|
115
|
+
(`packages/boot/app-boot/src/config-schema/document.ts`), so the override has
|
|
116
|
+
to carry this plugin's `shell` block verbatim — `path: wsl.exe`, `name: WSL`,
|
|
117
|
+
`args: []` — or the terminal stops being a distro terminal. Expect the menu
|
|
118
|
+
to grow the host shells discovery finds on the root provider, which receives
|
|
119
|
+
no directory: executables this terminal never starts (the second bullet of
|
|
120
|
+
the GUI-terminal comment in [`cordis.patch.yml`](../cordis.patch.yml)).
|
|
121
|
+
[LIMITATIONS.md](LIMITATIONS.md) still carries the older one-line form of
|
|
122
|
+
this tip, which drops the `shell` block; the version above is the one that
|
|
123
|
+
works.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# A profile-patch fragment for `$DSH_HOME/profiles/<name>/cordis.patch.yml`.
|
|
2
|
+
# It is the plugin's ENTIRE `preset-wsl` row (a row's config is replaced whole,
|
|
3
|
+
# never deep-merged) with `sessionSandbox: true` on the `wsl-shell` AND
|
|
4
|
+
# `wsl-fs` plugins.
|
|
5
|
+
#
|
|
6
|
+
# Paste it in place of any existing `- id: preset-wsl` entry in that profile
|
|
7
|
+
# layer, then restart the app. Regenerate it from the plugin's
|
|
8
|
+
# `cordis.patch.yml` whenever the shipped preset changes.
|
|
9
|
+
|
|
10
|
+
- id: preset-wsl
|
|
11
|
+
name: '@deepseek-ai/dsh-agent-preset'
|
|
12
|
+
config:
|
|
13
|
+
id: wsl
|
|
14
|
+
order: 2
|
|
15
|
+
plugins:
|
|
16
|
+
- id: wsl-env
|
|
17
|
+
name: cordis:group
|
|
18
|
+
group: true
|
|
19
|
+
isolate:
|
|
20
|
+
shell: true
|
|
21
|
+
fs: true
|
|
22
|
+
config:
|
|
23
|
+
# ── the WSL environment for this preset only ────────────────────
|
|
24
|
+
# No `cwd`: an empty default workdir means the distro user's home,
|
|
25
|
+
# asked of the distro itself. Pin a directory here to override it —
|
|
26
|
+
# a value that is right on one machine is wrong on every other.
|
|
27
|
+
#
|
|
28
|
+
# Commands run in one persistent systemd user scope per session
|
|
29
|
+
# (ADR 0001): a background dev server survives the command that
|
|
30
|
+
# started it, `/tmp` is the distro's real `/tmp`, and `ps` sees the
|
|
31
|
+
# session. This needs systemd as PID 1 (`[boot] systemd=true` in
|
|
32
|
+
# /etc/wsl.conf) and a lingering user manager (`loginctl
|
|
33
|
+
# enable-linger <user>`); the preflight refuses with those remedies
|
|
34
|
+
# when one is missing. Set `sessionSandbox: false` for the old
|
|
35
|
+
# per-command shape. Bubblewrap is still required for the mount
|
|
36
|
+
# profile; without it the providers fail closed.
|
|
37
|
+
- id: wsl-shell
|
|
38
|
+
name: 'dsh-plugin-wsl-env'
|
|
39
|
+
config:
|
|
40
|
+
distro: ''
|
|
41
|
+
loginShell: true
|
|
42
|
+
timeoutMs: 120000
|
|
43
|
+
maxTimeoutMs: 600000
|
|
44
|
+
sessionSandbox: true
|
|
45
|
+
agentBackend: auto
|
|
46
|
+
- id: wsl-fs
|
|
47
|
+
name: 'dsh-plugin-wsl-env/fs'
|
|
48
|
+
config:
|
|
49
|
+
distro: ''
|
|
50
|
+
restrictToDistro: true
|
|
51
|
+
# agent (the default): reads/writes/identities run inside the
|
|
52
|
+
# distro over the resident agent (lib/fs-substrate.js) —
|
|
53
|
+
# native symlinks and mode bits, the write guard survives to
|
|
54
|
+
# publication, and confined mutations are kernel-enforced.
|
|
55
|
+
# share: the opt-out — the Windows-side host fs stack over
|
|
56
|
+
# the 9p share.
|
|
57
|
+
substrate: agent
|
|
58
|
+
# Confined writes ride the session runtime (ADR 0001), so they
|
|
59
|
+
# share the command's real /tmp and workspace mounts.
|
|
60
|
+
sessionSandbox: true
|
|
61
|
+
- id: persona
|
|
62
|
+
name: '@deepseek-ai/dsh-persona'
|
|
63
|
+
config:
|
|
64
|
+
prefix: You are a coding agent powered by the {{model}} model.
|
|
65
|
+
- id: agent-instructions
|
|
66
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
67
|
+
config:
|
|
68
|
+
maxBytes: 65536
|
|
69
|
+
- id: time-context
|
|
70
|
+
name: '@deepseek-ai/dsh-time-context'
|
|
71
|
+
- id: tool-bash
|
|
72
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
73
|
+
disabled: false
|
|
74
|
+
- id: tool-pwsh
|
|
75
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
76
|
+
disabled: true
|
|
77
|
+
- id: tool-fs
|
|
78
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
79
|
+
- id: tool-fs-search
|
|
80
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
81
|
+
config:
|
|
82
|
+
sampleOverCapGlobResults: false
|
|
83
|
+
- id: tool-jobs
|
|
84
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
85
|
+
- id: tool-schedule
|
|
86
|
+
name: '@deepseek-ai/dsh-tool-schedule'
|
|
87
|
+
- id: skill-filesystem
|
|
88
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
89
|
+
- id: tool-skill
|
|
90
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
91
|
+
- id: command-goal
|
|
92
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
93
|
+
- id: tool-goal
|
|
94
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
95
|
+
- id: planning
|
|
96
|
+
name: cordis:group
|
|
97
|
+
group: true
|
|
98
|
+
isolate:
|
|
99
|
+
planMode: true
|
|
100
|
+
config:
|
|
101
|
+
- id: plan-mode
|
|
102
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
103
|
+
config:
|
|
104
|
+
section: |
|
|
105
|
+
You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
106
|
+
|
|
107
|
+
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
108
|
+
|
|
109
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
110
|
+
|
|
111
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
112
|
+
|
|
113
|
+
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
114
|
+
|
|
115
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
116
|
+
- id: compaction
|
|
117
|
+
name: cordis:group
|
|
118
|
+
group: true
|
|
119
|
+
isolate:
|
|
120
|
+
compaction: true
|
|
121
|
+
toolResultPruner: true
|
|
122
|
+
config:
|
|
123
|
+
- id: compaction-basic
|
|
124
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
125
|
+
- id: command-compact
|
|
126
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
127
|
+
- id: tool-result-pruner
|
|
128
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
129
|
+
config:
|
|
130
|
+
thresholdChars: 8192
|
|
131
|
+
headChars: 4096
|
|
132
|
+
tailChars: 1024
|
|
133
|
+
- id: delegation
|
|
134
|
+
name: cordis:group
|
|
135
|
+
group: true
|
|
136
|
+
isolate:
|
|
137
|
+
workflowEngine: true
|
|
138
|
+
config:
|
|
139
|
+
- id: tool-subagent-control
|
|
140
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
141
|
+
- id: tool-subagent-list-agents
|
|
142
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
143
|
+
- id: tool-subagent
|
|
144
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
145
|
+
config:
|
|
146
|
+
provider: spawn
|
|
147
|
+
toolName: subagent
|
|
148
|
+
modelSelectionSettings: true
|
|
149
|
+
- id: tool-subagent-fork
|
|
150
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
151
|
+
config:
|
|
152
|
+
provider: fork
|
|
153
|
+
toolName: subagent_fork
|
|
154
|
+
- id: workflow-ptc
|
|
155
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
156
|
+
config:
|
|
157
|
+
provider: spawn
|
|
158
|
+
- id: tool-workflow
|
|
159
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
160
|
+
- id: tool-ask-user
|
|
161
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
162
|
+
- id: tool-todo
|
|
163
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
164
|
+
config:
|
|
165
|
+
allowParallelInProgress: true
|
|
166
|
+
- id: tool-web
|
|
167
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
168
|
+
config:
|
|
169
|
+
fetch: true
|
|
170
|
+
searchTimeoutMs: 60000
|
|
171
|
+
- id: present
|
|
172
|
+
name: '@deepseek-ai/dsh-tool-present'
|
|
173
|
+
- id: tool-plugin-manager
|
|
174
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
175
|
+
disabled: true
|
|
176
|
+
- id: tool-plugin-market
|
|
177
|
+
name: '@deepseek-ai/dsh-plugin-market/tools'
|
|
178
|
+
disabled: true
|
package/lib/agent-protocol.d.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @module dsh-plugin-wsl/agent-protocol
|
|
15
15
|
*/
|
|
16
16
|
/** The protocol version this host side speaks; the agent must match. */
|
|
17
|
-
export declare const PROTOCOL_VERSION =
|
|
17
|
+
export declare const PROTOCOL_VERSION = 5;
|
|
18
18
|
/**
|
|
19
19
|
* Upper bound on one `read` op's payload, set by the HOST looping over windowed
|
|
20
20
|
* requests. Keeping each frame's decoded payload here means the agent's RES
|
|
@@ -127,15 +127,21 @@ export interface FsFrameRequest {
|
|
|
127
127
|
*/
|
|
128
128
|
export declare function encodeFsFrame({ id, op, args, timeoutMs }: FsFrameRequest): string[];
|
|
129
129
|
/**
|
|
130
|
-
* `HELLO|name|version|digest` — `digest` is the sha256 of the script file
|
|
131
|
-
* agent is executing (empty when it could not hash it); the host compares
|
|
132
|
-
* against its own shipped copy.
|
|
130
|
+
* `HELLO|name|version|digest|pid` — `digest` is the sha256 of the script file
|
|
131
|
+
* the agent is executing (empty when it could not hash it); the host compares
|
|
132
|
+
* it against its own shipped copy. `pid` is the agent shell's PID inside the
|
|
133
|
+
* distro, present from protocol 5 on: the host uses it to deliver an
|
|
134
|
+
* out-of-band `USR1` cancellation while the agent is blocked in `wait`, which
|
|
135
|
+
* the serial stdin loop cannot do by frame. It is absent when the agent
|
|
136
|
+
* itself runs inside a PID namespace (the confined resident), where that PID
|
|
137
|
+
* is not addressable from a second `wsl.exe`.
|
|
133
138
|
*/
|
|
134
139
|
export interface HelloMessage {
|
|
135
140
|
type: "hello";
|
|
136
141
|
name: string;
|
|
137
142
|
version: number;
|
|
138
143
|
digest: string;
|
|
144
|
+
pid?: number;
|
|
139
145
|
}
|
|
140
146
|
/** `PONG` — the liveness answer to `PING`. */
|
|
141
147
|
export interface PongMessage {
|
package/lib/agent-protocol.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* @module dsh-plugin-wsl/agent-protocol
|
|
15
15
|
*/
|
|
16
16
|
/** The protocol version this host side speaks; the agent must match. */
|
|
17
|
-
export const PROTOCOL_VERSION =
|
|
17
|
+
export const PROTOCOL_VERSION = 5;
|
|
18
18
|
/**
|
|
19
19
|
* Upper bound on one `read` op's payload, set by the HOST looping over windowed
|
|
20
20
|
* requests. Keeping each frame's decoded payload here means the agent's RES
|
|
@@ -124,8 +124,15 @@ export function encodeFsFrame({ id, op, args, timeoutMs = 0 }) {
|
|
|
124
124
|
*/
|
|
125
125
|
export function parseAgentLine(line) {
|
|
126
126
|
if (line.startsWith("HELLO|")) {
|
|
127
|
-
const [, name, version, digest] = line.split("|");
|
|
128
|
-
|
|
127
|
+
const [, name, version, digest, pid] = line.split("|");
|
|
128
|
+
const agentPid = Number(pid);
|
|
129
|
+
return {
|
|
130
|
+
type: "hello",
|
|
131
|
+
name: name ?? "",
|
|
132
|
+
version: Number(version),
|
|
133
|
+
digest: digest ?? "",
|
|
134
|
+
...(Number.isSafeInteger(agentPid) && agentPid > 0 ? { pid: agentPid } : {}),
|
|
135
|
+
};
|
|
129
136
|
}
|
|
130
137
|
if (line === "PONG")
|
|
131
138
|
return { type: "pong" };
|