dsh-code-server-app 0.2.7 → 0.2.8
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.en.md +521 -521
- package/README.md +505 -505
- package/lib/client.js +3 -2
- package/lib/index.js +24 -1
- package/lib/launcher.mjs +433 -362
- package/lib/serve-dsh.mjs +162 -162
- package/package.json +4 -2
- package/vendor/VENDOR.json +1 -1
package/README.en.md
CHANGED
|
@@ -1,522 +1,522 @@
|
|
|
1
|
-
# dsh-code-server-app — Integrate code-server (VS Code in the browser) into DSH
|
|
2
|
-
|
|
3
|
-
> Source repository: see `repository` / `homepage` in `package.json`.
|
|
4
|
-
|
|
5
|
-
> ## ⚠️ Extension Marketplace Note (important)
|
|
6
|
-
>
|
|
7
|
-
> - **code-server's extension store is [Open VSX](https://open-vsx.org/), not the Microsoft Visual Studio Marketplace**;
|
|
8
|
-
> - Microsoft's Marketplace terms **prohibit third-party products (including code-server) from using its API**, so code-server cannot query Microsoft's extension list;
|
|
9
|
-
> - As a result, Microsoft **commercial/proprietary** extensions (e.g. **GitHub Copilot, the Remote series like Remote-SSH, Azure tools, IntelliCode**) are **not available** in the store — this is Microsoft's distribution policy, not a defect;
|
|
10
|
-
> - Microsoft **open-source** extensions (Python, TypeScript debugger, ESLint, …) are mirrored on Open VSX and install normally by search;
|
|
11
|
-
> - **If you need a proprietary Microsoft extension**: download the `.vsix` from the Marketplace page and install it manually with `code-server --install-extension <file>` (or drop it into `--extensions-dir`).
|
|
12
|
-
|
|
13
|
-
A static profile plugin (npm package with host + client bundle) that ships the **VS Code server tree** from a [code-server](https://github.com/coder/code-server) release as a **platform-independent dependency package** (pack-time artifact `vendor/vscode` → `@jinsiyu/dshcs-vscode-server`, no install scripts, no postinstall). The code-server **Node service layer is replaced by the plugin's own `lib/launcher.mjs`**: it drives `<tree>/lib/vscode/out/server-main.js` (`loadCodeWithNls()` / `createServer()` / `handleRequest()` / `handleUpgrade()`) directly and re-adds the few HTTP endpoints code-server used to provide (`/healthz`, `/manifest.json`, `/_static/*`, `/proxy/:port`). The 16 native modules (node-pty / @vscode/sqlite3 / spdlog / …) come from `@jinsiyu/dshcs-*-win32-<arch>` platform packages selected automatically per architecture by the platform aggregator. VS Code's inner dependencies and the prebuilt native modules are **all installed by the package manager together with the plugin** — no global npm install, no `bin` configuration, no profile config changes, no second install command, **no argon2/C++ toolchain**.
|
|
14
|
-
|
|
15
|
-
## UI carrier and required DSH version (0.2.3: right-sidebar DSH only)
|
|
16
|
-
|
|
17
|
-
| DSH version | Carrier | Entry points |
|
|
18
|
-
|---|---|---|
|
|
19
|
-
| **>= 0.1.5-alpha.1** (has `sidebarRight` / `sidebarRightTabs`) | **Right-sidebar tab** (kind `code-server`, chip `Code Server`), which also **claims file addresses** (see below) | ① DSH's own **produced-file chips / presented-file card previews / inline file names in prose** (since 0.2.5, via the official `openFile` → file address → this tab); ② the **Code Server box** on the sidebar's guide ("开始") page; ③ Settings → Plugins → Code Server → **"Open in right sidebar"** |
|
|
20
|
-
| older (no sidebar service) | **Unsupported**: nothing but one notice on the settings page | none (Settings → Plugins → Code Server shows an upgrade notice) |
|
|
21
|
-
|
|
22
|
-
- Detection: first a synchronous `ctx.get('sidebarRightTabs') / ctx.get('sidebarRight')` probe; because the services may come up after this plugin, `ctx.inject(['sidebarRightTabs','sidebarRight'], …)` is awaited and a **2.5 s timeout marks the DSH as legacy** (no version comparison, and the plugin's own activation is never blocked).
|
|
23
|
-
Since 0.2.4 that verdict is **reversible** and registration no longer relies on `ctx` property access (which on desktop silently skipped registration — the symptom was "settings card looks normal but the sidebar has no entry"):
|
|
24
|
-
- services are looked up as `ctx.<name>` first and `ctx.get(name)` second, so either context shape registers;
|
|
25
|
-
- when the sync probe already sees the services but `inject` never calls back, registration falls back to the sync services after **1.5 s**;
|
|
26
|
-
- at 2.5 s only the settings notice appears; only after **10 s** does the client tell the host to recycle/stop prestarting (so a slow host is not punished);
|
|
27
|
-
- services arriving late automatically revoke the legacy verdict, register the sidebar, and report `{sidebar:true}` so the host re-enables;
|
|
28
|
-
- a failed registration is no longer silent: it logs an error and the card's entry row says "right-sidebar services were found but the tab could not be registered".
|
|
29
|
-
- **0.2.3 dropped legacy-DSH compatibility**: the floating ball and the internal floating window are **deleted**. When the DSH is detected as legacy the plugin
|
|
30
|
-
- registers only the settings card (an upgrade notice) — no ball, no floating window, **no file-address claim**, no IDE preload;
|
|
31
|
-
- reports `/api/code-server/ui-mode { sidebar:false }` to the host (after the 10 s grace above); the host then **recycles an instance it auto-prestarted** and stops prestarting (a user-started/adopted instance is never touched), and `{sidebar:true}` reverses that if the services show up later;
|
|
32
|
-
- upgrading DSH needs **no reinstall** — refresh the page and the card turns back into the full settings card.
|
|
33
|
-
- The sidebar tab hosts the code-server page (iframe) and follows the current session workspace; the panel can be collapsed/split/floated/fullscreened by DSH's right sidebar.
|
|
34
|
-
- **Resident IDE (0.2.2, on by default)**: switching to another tab or collapsing the sidebar and coming back **no longer reloads** code-server — unsaved editor buffers, terminals and debug sessions all stay put (see "Why switching tabs no longer reloads" below).
|
|
35
|
-
- The settings card has exactly **two settings**: "**Claim scope**" and "Resident in background" — no other rows (0.2.7 removed the "Entry", "dependency install" and "environment check" rows).
|
|
36
|
-
Open the IDE from the **Code Server box** on the sidebar's guide page, or by clicking DSH's own produced-file chips / delivered-file previews / inline file names;
|
|
37
|
-
diagnostics stay out of the UI — the `[code-server]` lines in the DSH host log are the place to look (`/api/code-server/status` still returns `env` for scripts).
|
|
38
|
-
The old `windowedOpen` (open in a window) and `reserveComposer` were **removed in 0.2.6**: leftover keys in an old settings document neither fail nor apply (they are no longer part of the schema).
|
|
39
|
-
To use the IDE in a browser tab, visit the loopback address `http://127.0.0.1:<port>/` (or DSH's `/code-server/` under `serve: dsh`).
|
|
40
|
-
|
|
41
|
-
## Opening files (official entry points since 0.2.5)
|
|
42
|
-
|
|
43
|
-
DSH names files with **resource addresses**; `openFile` only hands the address to the right sidebar, which decides who draws it:
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
DSH's produced-file chip / presented-file card preview / inline prose mention
|
|
47
|
-
→ openFile(path, { line? }) (provided by ui-chat)
|
|
48
|
-
→ dsh-resource://file/session/<sessionId>/<path> (or …/file/absolute/<path>)
|
|
49
|
-
→ ctx.sidebarRight.openResource(address)
|
|
50
|
-
→ claimed by the tab type whose patterns match (band extension(3) > builtin(2) > fallback(1),
|
|
51
|
-
then the longest matching pattern, then registration order)
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
This plugin registers:
|
|
55
|
-
|
|
56
|
-
| Field | Value | Effect |
|
|
57
|
-
|---|---|---|
|
|
58
|
-
| `patterns` | `['dsh-resource://file/**']` | claims file addresses (a pattern containing `:` is matched against the **whole address**) |
|
|
59
|
-
| `priority` | `'extension'` | beats the built-in plain-text preview, which sits in `fallback` on purpose — DSH's own comment calls that band "the position VS Code's text editor holds among its editors", i.e. one any more specific type should beat |
|
|
60
|
-
| `canOpen` | see below | vetoes by the "claim scope" setting; unclaimed addresses fall back to DSH's built-in preview |
|
|
61
|
-
| `title` | last address segment (= file name) | the tab chip shows the file name; a page tab (`sidebar://code-server`) still reads `Code Server` |
|
|
62
|
-
|
|
63
|
-
- **Claim scope** (switchable in the settings card, `fileOpenScope`):
|
|
64
|
-
- `session` (default) — claim only `dsh-resource://file/session/…` (everything a session produces: deliverables, declared deliveries, prose mentions, tool views);
|
|
65
|
-
- `all` — also claim session-less `dsh-resource://file/absolute/…` addresses.
|
|
66
|
-
- **How the tab body locates the file**: it parses `useTabInfo().tab.navigation.address`
|
|
67
|
-
(`src/address.js`, same grammar as DSH's `parseFileAddress`), expands a workspace-relative path with that
|
|
68
|
-
session's cwd, and posts the absolute path (plus optional `line`) to the host's
|
|
69
|
-
`/api/code-server/open-file`; the bundled extension (`dshcs-open-file`) then calls `showTextDocument`
|
|
70
|
-
(positioned at the line when given).
|
|
71
|
-
- **One address = one tab** (DSH semantics: `contentId` *is* the address): three files mean three chips, but they
|
|
72
|
-
share the single resident workbench — switching tabs just re-aims the workbench at the corresponding file.
|
|
73
|
-
- **Why the bundled extension stays**: VS Code Web has no official "open this file from outside" API (the only
|
|
74
|
-
entry is `?folder=`, which picks the workspace), so aiming the workbench at a file has to be done by an
|
|
75
|
-
extension inside the tree. The host writes a signal file, the extension polls it and calls
|
|
76
|
-
`showTextDocument`, keeping the signal for retry when no window is connected yet.
|
|
77
|
-
|
|
78
|
-
## Why switching tabs no longer reloads (resident IDE)
|
|
79
|
-
|
|
80
|
-
**The old trap**: DSH's right sidebar (ui-dockkit) renders **only the active tab's body**
|
|
81
|
-
(`TabPanel.tsx:412` → `renderTab(active)`) — switching to another tab unmounts that body in React, which moves the
|
|
82
|
-
iframe out of the document and destroys its browsing context; switching back is a full VS Code reload (unsaved buffers
|
|
83
|
-
lost). Floating the tab into its own panel only worked around it.
|
|
84
|
-
|
|
85
|
-
**What it does now (`src/surface.js` in the client, 0.2.2)**: the plugin takes the iframe **away from React** and turns
|
|
86
|
-
it into a **singleton resident surface**:
|
|
87
|
-
|
|
88
|
-
| Situation | Action | Result |
|
|
89
|
-
|---|---|---|
|
|
90
|
-
| tab becomes active | `host.moveBefore(frame, null)` into the visible dock slot | state-preserving atomic move, **no reload** |
|
|
91
|
-
| tab deactivates / sidebar collapses | move back into a document-level park container (offscreen, keeps last docked size, `inert` + `aria-hidden`) | never destroyed, keeps running in the background |
|
|
92
|
-
| workspace / port changes | assign `src` explicitly | the only normal "reload" entry point |
|
|
93
|
-
|
|
94
|
-
- **Why `moveBefore`**: measured in a real browser (Edge/Chromium 151), a plain `appendChild` move resets the iframe's
|
|
95
|
-
internal timers (i.e. reloads it), while `Element.moveBefore()` (Chromium ≥133) preserves state (a probe counter keeps
|
|
96
|
-
counting 1→2).
|
|
97
|
-
- **Degradation is never silent**: when `moveBefore` is missing, or the host was already detached by React and it throws
|
|
98
|
-
`HierarchyRequestError: invalid hierarchy` (passive effect cleanup runs after DOM removal), the code falls back to
|
|
99
|
-
`appendChild` — one reload, but the frame is **never lost** — and reports `degraded` / `lastMoveError` so the UI can
|
|
100
|
-
say "residency unavailable".
|
|
101
|
-
- **Repaint fallback (measured)**: in the real GUI the surface was seen once with correct size, hit testing and
|
|
102
|
-
`visibility` that simply **stopped repainting** (a fully white panel, byte-identical screenshots proving no new frame).
|
|
103
|
-
`translateZ(0)` and `opacity` nudges did nothing; `display:none → forced reflow → restore` inside a single JS task
|
|
104
|
-
restored it without reloading the iframe document, without losing internal state and without a visible flash.
|
|
105
|
-
**The trigger could not be reproduced**: in a probe page an offscreen `moveBefore` park of 337 s (past Chrome's
|
|
106
|
-
~5 min cross-origin throttle window) followed by a dock with the fallback disabled still painted normally. It is
|
|
107
|
-
therefore kept as a **fallback**: every park→dock transition runs one `nudgeRepaint()` (counted as
|
|
108
|
-
`surfaceSnapshot().nudgeCount`; `setNudgeEnabled(false)` A/Bs it live).
|
|
109
|
-
- **Warm-up**: with `keepResident` (default `true`) the host builds the surface right after plugin start and leaves it
|
|
110
|
-
parked, so the first tab open needs no cold start; preloading never yanks a surface that is currently docked.
|
|
111
|
-
- **Debug handle**: `window.__dshcsSurface` (`snapshot()`, `setParkStrategy('offscreen'|'behind')`, `dock()`, `park()`,
|
|
112
|
-
`nudge()`, `setNudgeEnabled(false)`, `destroy()`).
|
|
113
|
-
|
|
114
|
-
**Measured** (DSH web GUI, real mouse clicks between sidebar tabs): switching away → `docked:false`, same iframe node,
|
|
115
|
-
in-frame probe still alive, `degraded:false`; switching back → `docked:true`, unchanged `src`, IDE pixels and editing
|
|
116
|
-
state preserved (no full reload). Full evidence and probe scripts: `docs/analysis-code-server-as-dsh-plugin.md`.
|
|
117
|
-
|
|
118
|
-
## Serving mode (`serve`)
|
|
119
|
-
|
|
120
|
-
| Mode | What it does | Requires |
|
|
121
|
-
|---|---|---|
|
|
122
|
-
| **`loopback` (default)** | the plugin listens on its own loopback port (`host:port`) and the sidebar iframe connects cross-origin; the process can be adopted after a DSH host restart | nothing |
|
|
123
|
-
| **`dsh`** | the IDE is mounted on **DSH's own HTTP port** at `/code-server/*` (HTTP prefix route) plus `/code-server/<quality>-<commit>` (exact WebSocket route), forwarded to the launcher's **named pipe**; **no extra port**; every request (including the WS handshake) first passes `ctx.connection.requestRejection()` — the same Host/Origin fence and browser-cookie authentication as `/api` | DSH providing `webServer` (web profile); desktop falls back to loopback automatically |
|
|
124
|
-
|
|
125
|
-
- Switch it in `config.serve` in `cordis.patch.yml` or in Settings → Plugins → Code Server (takes effect on the next start).
|
|
126
|
-
- Benefits of `dsh`: a single URL/port (remote access to DSH gives you the IDE), no extra loopback listener, authentication on par with DSH.
|
|
127
|
-
- Two **known trade-offs** of `dsh`: the iframe shares DSH's origin, so `sandbox` is dropped there (same-origin plus
|
|
128
|
-
`allow-same-origin` is escapable by the frame itself; in `loopback` mode the iframe is cross-origin and `sandbox` stays
|
|
129
|
-
as real protection — clipboard is still granted via `allow="clipboard-read; clipboard-write"`); and forwarded-port
|
|
130
|
-
**WebSockets** cannot be routed because `registerUpgrade` matches exact paths while `/proxy/:port` carries the port in
|
|
131
|
-
the path (HTTP forwarding works; use `loopback` when you need WS forwarding).
|
|
132
|
-
|
|
133
|
-
- In `loopback` mode every upgrade passes a **code-server-equivalent Origin check** (since 0.2.1): when an `Origin`
|
|
134
|
-
header is present its host must equal `Host` (honouring `Forwarded: host=` / `X-Forwarded-Host`, like code-server),
|
|
135
|
-
otherwise the handshake gets `403`; non-browser requests without `Origin` are allowed. Without that check any local
|
|
136
|
-
browser page could complete a handshake against `ws://127.0.0.1:<port>/stable-<commit>` and drive the IDE.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
## Legacy DSH (unsupported since 0.2.3)
|
|
140
|
-
|
|
141
|
-
**Behaviour**: when `sidebarRightTabs` / `sidebarRight` cannot be found, the plugin registers a single settings card:
|
|
142
|
-
|
|
143
|
-
> **Code Server** — this DSH version is unsupported (no right-sidebar service)
|
|
144
|
-
> Since 0.2.3 this plugin no longer supports older DSH versions.
|
|
145
|
-
> The right-sidebar plugin services `sidebarRightTabs` / `sidebarRight` were not detected, so the plugin exposes no
|
|
146
|
-
> entry point at all (the old floating ball and floating window have been removed) and will not start the IDE in the
|
|
147
|
-
> background. Upgrade DSH to a version with the right sidebar (>= 0.1.5-alpha.1): Code Server then appears as a
|
|
148
|
-
> right-sidebar tab, this page shows the full settings again, and no reinstall is needed — a page refresh is enough.
|
|
149
|
-
|
|
150
|
-
- **No other UI**: no `shell.overlay` registration (floating ball), no file-address claim, no resident preload.
|
|
151
|
-
- **Host side**: the client posts `/api/code-server/ui-mode { sidebar:false }`; the host then ① stops auto-prestarting
|
|
152
|
-
the IDE (`maybePrestart` returns immediately) and ② **recycles** an instance it had just auto-prestarted (unless it
|
|
153
|
-
was adopted), so no unusable IDE process or port is left behind. A user-started/adopted instance is never stopped.
|
|
154
|
-
- **Why delete instead of keeping**: the internal floating window was a stopgap from the era of early-2026 DSH builds
|
|
155
|
-
without right-sidebar services. The resident surface, clipboard handling, shortcuts and panel collapsing all build on
|
|
156
|
-
DSH's right sidebar, so maintaining two carriers costs more than it is worth. Older-DSH users should stay on `0.2.2`
|
|
157
|
-
(`dsh plugin --profile web add dsh-code-server-app@0.2.2`).
|
|
158
|
-
|
|
159
|
-
## code-server workspace and process lifecycle
|
|
160
|
-
|
|
161
|
-
- code-server's workspace **follows the active DSH session/workspace**: switching sessions/workspaces while the IDE is open restarts code-server to the new directory
|
|
162
|
-
(resolution order: current session cwd → session's workspace.path → recentWorkspace.path → first workspace.path);
|
|
163
|
-
the opened directory is shown inside code-server (`?folder=<cwd>`, the page reloads when following a switch);
|
|
164
|
-
implementation note: the iframe `src` must carry `?folder=<cwd>` — code-server's front-end remembers the "last workspace" and restores it by itself;
|
|
165
|
-
a bare root URL only shows the previously opened directory and does not follow switches (verified locally).
|
|
166
|
-
**Windows path format (verified)**: the `folder` parameter must start with `/` and use forward slashes only, e.g. `/C:/Users/User/Desktop/biss`;
|
|
167
|
-
a bare Windows path (`C:\...`) is parsed as a URI scheme and the drive letter is stripped (page shows `\Users\User\...` with an empty file tree),
|
|
168
|
-
while `file:///C:/...` reports "Workspace does not exist".
|
|
169
|
-
- Process lifecycle is managed by the host plugin: startup writes `$DSH_HOME/code-server/pid.json`, stop kills the tree (`taskkill /T` or process-group SIGKILL),
|
|
170
|
-
crash/exit updates status live; after a DSH host restart the plugin **adopts** a still-running instance (verifies pid + `/healthz`), without duplicate start or killing unrelated processes;
|
|
171
|
-
- `node_modules` and the pack-time artifact `vendor/` are git-ignored; after cloning, follow
|
|
172
|
-
"Install the plugin (script-free install; code-server bundled)" below — `pnpm install` → `pnpm run build:client` →
|
|
173
|
-
`pnpm run vendor:vscode` → `pnpm pack` + `dsh plugin --profile web add`.
|
|
174
|
-
|
|
175
|
-
> Verified locally (BM: Windows 11 ARM64): `code-server@4.136.2` (with Code 1.136.1) bundled in the plugin,
|
|
176
|
-
> placed offline at activation → VS Code internal deps installed → started → healthz 200 →
|
|
177
|
-
> cwd switch restart while running → stopped → fully recycled.
|
|
178
|
-
|
|
179
|
-
## Packaging (how to build the tarball)
|
|
180
|
-
|
|
181
|
-
```powershell
|
|
182
|
-
cd C:\Users\User\Desktop\dsh-code-server-app
|
|
183
|
-
pnpm install # dev deps (esbuild + motion); allowBuilds is explicit → no postinstall runs
|
|
184
|
-
pnpm run build:client # src/factory.js → lib/client.js (not committed; must be built first)
|
|
185
|
-
pnpm run vendor:check # optional: show the bundled tree version vs the latest code-server release
|
|
186
|
-
pnpm run vendor:vscode # ① produce vendor/vscode (the trimmed VS Code tree, ~197MB)
|
|
187
|
-
pnpm run repack:build -- --target win32-arm64,win32-x64 --pack # ② one script builds every sub-package
|
|
188
|
-
pnpm run publish:repacks # ③ publish every @jinsiyu/* sub-package (default dist-tag: next)
|
|
189
|
-
pnpm pack # ④ → dsh-code-server-app-<version>.tgz (~107KB)
|
|
190
|
-
pnpm run publish:plugin # ⑤ publish the plugin itself (default dist-tag: next)
|
|
191
|
-
# once the user has restarted dsh web and confirmed it works, promote latest:
|
|
192
|
-
pnpm run promote -- <version>
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
> **dist-tag policy (mandatory)**: every release goes to **`next`** and **never touches `latest`**;
|
|
196
|
-
> `latest` always points at the most recent *confirmed bug-free* version and is only moved by
|
|
197
|
-
> `pnpm run promote -- <version>` (= `npm dist-tag add dsh-code-server-app@<version> latest`)
|
|
198
|
-
> **after the user restarts `dsh web` and confirms it works**. That way
|
|
199
|
-
> `dsh plugin add dsh-code-server-app` (no version) — and anything else resolving `latest` — never picks up an
|
|
200
|
-
> unverified build. Sub-packages (`@jinsiyu/dshcs-*`, the aggregators) are referenced by exact/caret versions,
|
|
201
|
-
> so their dist-tags do not affect resolution, but they default to `next` as well.
|
|
202
|
-
> Inspect the current tags with `npm dist-tag ls dsh-code-server-app`.
|
|
203
|
-
|
|
204
|
-
`repack:build` (`scripts/vendor-repacks.mjs`) is the **single script that produces every sub-package**:
|
|
205
|
-
|
|
206
|
-
| Sub-package | Content | os/cpu |
|
|
207
|
-
|---|---|---|
|
|
208
|
-
| `@jinsiyu/dshcs-vscode-server@<code-server version>` | the trimmed VS Code tree (`lib/vscode` + `out/browser` + `src/browser`; **without** code-server's `out/node` and its 136 runtime deps) | platform-independent |
|
|
209
|
-
| `@jinsiyu/dshcs-<name>[-win32-<arch>]` ×24 | the VS Code inner packages that need building (node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / …) | gated when platform-specific |
|
|
210
|
-
| `@jinsiyu/dsh-code-server-runtime-win32-<arch>` | platform aggregator: its `dependencies` map those 16 natives back to their original names via `npm:` aliases | win32-<arch> |
|
|
211
|
-
|
|
212
|
-
| Goal | Command |
|
|
213
|
-
|---|---|
|
|
214
|
-
| **Build from the latest upstream release** | `pnpm run vendor:latest` (= `--force`): pulls `code-server@latest`'s tree into `vendor/vscode`; afterwards you **must** re-run `repack:build` and republish every sub-package |
|
|
215
|
-
| **Pin a version** | `pnpm run vendor:vscode -- --version 4.136.2` |
|
|
216
|
-
| **Snapshot from an existing tree** | `pnpm run vendor:vscode -- --from <code-server dir>` (seconds) |
|
|
217
|
-
| **Rebuild every sub-package** | `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack` (without `--from` it npm-installs and compiles the source tree itself — slow) |
|
|
218
|
-
| **Rebuild only the tree/aggregator packages** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack` (reuses the natives already in `repack/build`) |
|
|
219
|
-
| **Publish sub-packages** | `pnpm run publish:repacks` (`--dry-run` to preview; `--only <substr>` to filter; `--otp <code>` / `--limit N` for 2FA) |
|
|
220
|
-
| **Publish the plugin itself** | `pnpm run publish:plugin` (publishes the exact tarball that was verified; no re-packing; default dist-tag `next`) |
|
|
221
|
-
| **Promote `latest`** | `pnpm run promote -- <version>` (only after the user restarted and confirmed; `--dry-run` shows the current tags first) |
|
|
222
|
-
| **Just report versions** | `pnpm run vendor:check` |
|
|
223
|
-
|
|
224
|
-
> `pnpm pack`'s `prepack` runs the vendor-code-server script once; when `vendor/code-server` already exists it is
|
|
225
|
-
> a **no-op that takes seconds**, so after ordinary code changes you can just run `pnpm pack` (it will never
|
|
226
|
-
> silently upgrade code-server). Upgrading code-server requires an explicit `pnpm run vendor:latest`
|
|
227
|
-
> (or `--force` / `--version`) **plus** republishing the sub-packages.
|
|
228
|
-
|
|
229
|
-
## Install the plugin (one command; all dependencies installed by the package manager)
|
|
230
|
-
|
|
231
|
-
```powershell
|
|
232
|
-
# no postinstall in the package → no pnpm approve-builds / allowBuilds; one command installs everything
|
|
233
|
-
dsh plugin --profile web add dsh-code-server-app@0.2.1
|
|
234
|
-
# a local tarball works the same way:
|
|
235
|
-
dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-server-app-0.2.1.tgz
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Ready to use immediately — **no second step, no "Install environment", no install-guide modal**.
|
|
239
|
-
The main package is only **~110KB** (the plugin's own code plus the launcher); everything else is dependencies:
|
|
240
|
-
|
|
241
|
-
- **the VS Code tree** (`lib/vscode` 196.9MB + `out/browser` + `src/browser`) is a **platform-independent package**
|
|
242
|
-
`@jinsiyu/dshcs-vscode-server@<code-server version>` declared in the plugin's `dependencies`; it runs from
|
|
243
|
-
`<profile>\node_modules\@jinsiyu\dshcs-vscode-server\vscode` (the legacy full tree at
|
|
244
|
-
`@jinsiyu/dshcs-code-server/code-server` is still recognised as a fallback);
|
|
245
|
-
- the **pure-JS part** of VS Code's inner dependencies (35 packages: xterm / katex / typescript / ws / tar …) is
|
|
246
|
-
declared in the plugin's `dependencies` and installed by pnpm into the profile's `node_modules` (hoisted);
|
|
247
|
-
- the **binary part** comes entirely from `@jinsiyu/dshcs-*` platform packages: 16 native packages mapped back to their
|
|
248
|
-
**original names** (`node-pty` / `@vscode/sqlite3` / `@vscode/spdlog` / …) by the **platform aggregator**
|
|
249
|
-
`@jinsiyu/dsh-code-server-runtime-win32-<arch>` using `npm:` aliases; the aggregators sit in the plugin's
|
|
250
|
-
`optionalDependencies`, so pnpm auto-selects the right platform;
|
|
251
|
-
- consequently the dependency graph contains **no package with pre/install/postinstall or a `binding.gyp`** →
|
|
252
|
-
no profile `allowBuilds`, no build script ever runs, and **the user machine needs no C++ toolchain**;
|
|
253
|
-
- **upgrading the plugin no longer re-downloads the tree**: the tree package is cached by version
|
|
254
|
-
(~60MB, ~197MB unpacked).
|
|
255
|
-
|
|
256
|
-
### Install mechanism (why it is built this way)
|
|
257
|
-
|
|
258
|
-
- **The pnpm 11 hard constraint**: any package in the dependency graph whose manifest has
|
|
259
|
-
`preinstall|install|postinstall` (or that ships a `binding.gyp`/`.hooks`) counts as "needs building" and must be
|
|
260
|
-
approved by the **host profile's** `pnpm-workspace.yaml` via `allowBuilds`, otherwise `dsh plugin add` exits 1 with
|
|
261
|
-
`[ERR_PNPM_IGNORED_BUILDS]`. A dependency's own `pnpm.allowBuilds`, `.npmrc`, `patch:` protocol and
|
|
262
|
-
`optionalDependencies` do not help (measured 2026-09, pnpm 11.25);
|
|
263
|
-
- **the tree** is prepared at pack time with `npm install code-server@<version> --ignore-scripts` (skipping the official
|
|
264
|
-
`sh ./postinstall.sh`, which cannot run on Windows), then `scripts/vendor-vscode-server.mjs` keeps **only the VS Code
|
|
265
|
-
tree**: `lib/vscode/**`, `out/browser/**`, `src/browser/**` plus the license files are copied to `vendor/vscode/`, and a
|
|
266
|
-
generated root `package.json` records the upstream code-server version. code-server's own `out/node/**` and its 136
|
|
267
|
-
runtime dependencies **no longer ship** — they are replaced by `lib/launcher.mjs`;
|
|
268
|
-
- **the packages that need a toolchain** are repacked into `@jinsiyu/dshcs-*` by `scripts/vendor-repacks.mjs`:
|
|
269
|
-
the compiled package directory is copied and its `scripts` / `files` / `binding.gyp` / `.hooks` / `.npmignore` are
|
|
270
|
-
**removed** (the built `.node` and every runtime file stay) → sibling packages in its dependency list become `npm:`
|
|
271
|
-
aliases → platform-specific ones get `os`/`cpu` plus a `-<platform>-<arch>` suffix. For win32 targets the script also
|
|
272
|
-
verifies each `.node` PE machine (0x8664=x64 / 0xaa64=arm64) so a cross-compiled artifact cannot ship the wrong arch;
|
|
273
|
-
- **the platform aggregator** maps those repacks back to their original names (e.g.
|
|
274
|
-
`"node-pty": "npm:@jinsiyu/dshcs-node-pty@1.2.0-beta.15"`), so VS Code's `import('node-pty')` needs no change; the
|
|
275
|
-
aggregator is itself `os`/`cpu` gated, and the plugin declares both win32-arm64 and win32-x64 in
|
|
276
|
-
`optionalDependencies`, so one command picks the right one;
|
|
277
|
-
- **resolution path**: the host finds the tree with `require.resolve('@jinsiyu/dshcs-vscode-server/package.json')`
|
|
278
|
-
(then the inner `vscode/` directory) and the entry is `vscode/lib/vscode/out/server-main.js`; VS Code's inner deps are
|
|
279
|
-
resolved upwards from that root (`vscode/lib/vscode/node_modules` → package `node_modules` → `<profile>/node_modules`).
|
|
280
|
-
The legacy full tree (`@jinsiyu/dshcs-code-server/code-server`) is still recognised as a fallback;
|
|
281
|
-
- **runtime layout self-healing** (`ensureRuntimeLayout()` in `lib/native.js`, idempotent, run **at activation before
|
|
282
|
-
`envCheck` and again before every start**): the host adds two kinds of **junctions** (Windows junctions / POSIX dir
|
|
283
|
-
symlinks) into the tree:
|
|
284
|
-
1. `ensureAliasLinks()`: re-links the native aliases the aggregator carries into `<tree>/node_modules` — pnpm nests
|
|
285
|
-
`os`/`cpu`-gated packages under the aggregator's own `node_modules`, and `lib/vscode/out/server-main.js` uses
|
|
286
|
-
**ESM imports** (ESM ignores `NODE_PATH`), so a missing link means an immediate 500;
|
|
287
|
-
2. `ensureInnerModuleLinks()`: restores VS Code's **inner dependency directories**
|
|
288
|
-
`lib/vscode/node_modules` and `lib/vscode/extensions/node_modules` from the two `package.json` files — the trimmed
|
|
289
|
-
tree ships neither, and code that builds dependency paths explicitly (e.g. the bundled TypeScript extension looking
|
|
290
|
-
for `<ext>/../node_modules/typescript/lib/tsserver.js`) otherwise reports
|
|
291
|
-
"VS Code's tsserver was deleted by another application…" (measured with 1.136.1).
|
|
292
|
-
> **Size note**: the plugin tarball is **~110KB**; `@jinsiyu/dshcs-vscode-server` is **~60MB** (~197MB unpacked);
|
|
293
|
-
> the 16 native packages add ~250MB. A full install downloads roughly 310MB. Neither `vendor/` nor `repack/` is committed to git (see `.gitignore`).
|
|
294
|
-
|
|
295
|
-
> **Upgrading from ≤ 0.1.43**: the tree package changed from `@jinsiyu/dshcs-code-server` (the full code-server tree with
|
|
296
|
-
> `out/node` and 136 runtime deps) to `@jinsiyu/dshcs-vscode-server` (the trimmed tree). **The new code defaults to
|
|
297
|
-
> `serve: loopback`, which behaves exactly like 0.1.43**; switch to `serve: dsh` for same-origin mounting. The install
|
|
298
|
-
> command is unchanged (`dsh plugin --profile web add dsh-code-server-app@<version>`), and pnpm drops the old
|
|
299
|
-
> `dshcs-code-server` sub-package.
|
|
300
|
-
### Development: install from source (changes take effect immediately)
|
|
301
|
-
|
|
302
|
-
```powershell
|
|
303
|
-
dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
> A source path installs via `link:`. On a dev machine without `vendor/code-server`, run
|
|
307
|
-
> `pnpm run vendor:vscode -- --dev-links` first. Dependencies (inner JS deps + the platform aggregator) are installed by pnpm
|
|
308
|
-
> too — but the not-yet-published local `@jinsiyu/*` packages must either be published first, or the
|
|
309
|
-
> `repack/tgz/*.tgz` files must be installed into the profile as `file:` dependencies.
|
|
310
|
-
>
|
|
311
|
-
> **Changing the client bundle**: edit `src/factory.js` then run `pnpm run build:client`
|
|
312
|
-
> to regenerate `lib/client.js` (that artifact is not tracked; a browser refresh picks it up — no host restart needed).
|
|
313
|
-
> Window animations are driven by the embedded `motion`; feel parameters live in `winPhysics` (one spot) in `src/factory.js`.
|
|
314
|
-
|
|
315
|
-
### Pack-machine environment (the user machine needs nothing)
|
|
316
|
-
|
|
317
|
-
**A toolchain is needed at pack time only — never on the user machine.**
|
|
318
|
-
|
|
319
|
-
| Env | Version / requirement | User machine | Pack machine |
|
|
320
|
-
|---|---|---|---|
|
|
321
|
-
| Node.js | **v24.x** (latest code-server requirement; v24.13.1 here) | required | required |
|
|
322
|
-
| npm / pnpm | npm ships with Node; pnpm comes from DSH | required (installs deps) | required |
|
|
323
|
-
| **MSVC build tools** | **VS Community 2026 + C++ desktop workload** | ❌ **not needed** | pack time (16 native packages) |
|
|
324
|
-
| **VS Spectre-mitigated libs** | one set for ARM64 **and** one for x86/x64 ("MSVC v14x Spectre-mitigated libs") | ❌ not needed | pack time (otherwise MSB8040) |
|
|
325
|
-
| Python | **3.13.x** | ❌ not needed | pack time (node-gyp) |
|
|
326
|
-
| node-gyp | **13.x** (older versions don't recognize VS 2026) | ❌ not needed | pack time |
|
|
327
|
-
|
|
328
|
-
> **Packing still works without the Spectre libs**: when a package fails to compile, `vendor-repacks.mjs` downgrades
|
|
329
|
-
> `SpectreMitigation` to `false` in that architecture's `*.gyp` files and retries (only the Spectre hardening is
|
|
330
|
-
> lost, functionality is unaffected) and says so in the log.
|
|
331
|
-
|
|
332
|
-
### Windows native build notes (pack time, verified locally ARM64)
|
|
333
|
-
|
|
334
|
-
- **VS needs the Spectre-mitigated libraries** (MSB8040): Visual Studio Installer → Individual components →
|
|
335
|
-
"MSVC v14x Spectre-mitigated libs" — **install the ARM64 and the x86/x64 sets separately**.
|
|
336
|
-
- **node-gyp 13.x** (9.x does not recognize VS 2026): `npm install -g node-gyp@latest`.
|
|
337
|
-
- **x64 cross-compiling**: `vendor-repacks.mjs` uses `npm install --os=win32 --cpu=x64 --ignore-scripts` to fetch
|
|
338
|
-
the packages, then `npm rebuild --arch=x64` per package; the resulting PE machine types were verified
|
|
339
|
-
(kerberos / sqlite3 / spdlog …).
|
|
340
|
-
- Latest code-server requires **Node v24**.
|
|
341
|
-
- If you don't need the self-contained install (e.g. a global code-server already exists), skip it:
|
|
342
|
-
the plugin falls back to a configured/PATH `bin` (see the "Config" table).
|
|
343
|
-
|
|
344
|
-
### Upgrading the VS Code tree (upstream = a code-server release)
|
|
345
|
-
|
|
346
|
-
- **The version is decided at pack time**: `pnpm run vendor:latest` (= `--force`) pulls the tree of the npm **latest**
|
|
347
|
-
release; or use `pnpm run vendor:vscode -- --version 4.136.2` / `DSHCS_CODE_SERVER_VERSION`.
|
|
348
|
-
With an existing `vendor/vscode`, a plain `pnpm pack` never upgrades (it is a no-op).
|
|
349
|
-
- **Check first**: `pnpm run vendor:check` prints the bundled version / upstream latest.
|
|
350
|
-
- **A version bump means rebuilding and republishing the sub-packages** (all with the same script):
|
|
351
|
-
1. `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack` → the new tree package
|
|
352
|
-
(`@jinsiyu/dshcs-vscode-server@<new version>`) and the natives rebuilt against the new inner dependencies (the
|
|
353
|
-
script also rewrites the plugin's pure-JS `dependencies` and both aggregator versions);
|
|
354
|
-
2. `pnpm run republish:repacks` (`pnpm run publish:repacks`) → publish; then bump the plugin version → `pnpm pack`
|
|
355
|
-
→ publish the plugin.
|
|
356
|
-
- `productPath` (`<quality>-<commit>`, part of the client WebSocket path) is **computed from `lib/vscode/product.json`**,
|
|
357
|
-
so upgrading the tree needs no code change — but the routes are registered at activation, so restart `dsh web` afterwards.
|
|
358
|
-
- **No runtime auto-upgrade anymore**: nothing fetches latest at startup; the version is fully determined by the bundled artifact.
|
|
359
|
-
- Bundled locally right now: the tree of `code-server@4.136.2` (VS Code 1.136.1, `productPath=stable-8d5f383f…`).
|
|
360
|
-
|
|
361
|
-
### Compatibility with the old install locations
|
|
362
|
-
|
|
363
|
-
Host probe order: `@jinsiyu/dshcs-vscode-server/vscode` (**the real layout since 0.2.0**) >
|
|
364
|
-
`@jinsiyu/dshcs-code-server/code-server` (the full tree, 0.1.40–0.1.43) >
|
|
365
|
-
`@jinsiyu/dshcs-code-server-<platform>-<arch>/code-server` (the 0.1.37 platform sub-packages) >
|
|
366
|
-
the in-package `vendor/vscode` > the in-package `vendor/code-server` (development). The old install root
|
|
367
|
-
`<profile>\.code-server-app` is only mentioned in a startup log line; nothing writes to it any more.
|
|
368
|
-
## Settings card (Settings → Plugins → Code Server)
|
|
369
|
-
|
|
370
|
-
Modeled after dsh-auto-open-web's custom card, registered on the `settings.plugin.item` slot,
|
|
371
|
-
persisted via the official settings domain (`settingsScope`, namespace `code-server`) into the official settings document:
|
|
372
|
-
|
|
373
|
-
| Key | Default | Description |
|
|
374
|
-
|---|---|---|
|
|
375
|
-
| `fileOpenScope` | `session` | **Claim scope** (0.2.5): `session` claims only session-scoped file addresses (`dsh-resource://file/session/…` — DSH's produced files, declared deliveries, prose mentions); `all` also claims session-less `…/file/absolute/…`. Unclaimed addresses fall back to DSH's built-in preview |
|
|
376
|
-
| `keepResident` | `true` | **Resident in background**: on, the host preloads the IDE into a parked surface right after start — switching tabs or collapsing the sidebar never reloads it and the first open needs no cold start; off loads it only when the panel is opened (saves memory) |
|
|
377
|
-
|
|
378
|
-
(Since 0.2.6 the card keeps only those two settings; `windowedOpen` and `reserveComposer` are gone — leftover keys in an old
|
|
379
|
-
settings document neither fail nor apply. `serve` remains a key in the settings namespace (usable from a settings document) but
|
|
380
|
-
has **no card row** — see "Serving mode".)
|
|
381
|
-
|
|
382
|
-
> Card changes take effect immediately via `scope.watch` (the host status API returns `keepResident` and
|
|
383
|
-
> `fileOpenScope`; the client applies them at once); no dsh restart needed. **After adding new setting keys, restart dsh web before first use**,
|
|
384
|
-
> so the host re-registers the settings namespace (schema includes the new key); otherwise save/validation of the new key won't work.
|
|
385
|
-
|
|
386
|
-
Since 0.2.7 the card has **no** "Entry", "dependency install" or "environment check" rows: the entry lives in the sidebar's
|
|
387
|
-
guide page (and in DSH's own file clicks), and diagnostics stay out of the UI — the `/api/code-server/status` `env` field still
|
|
388
|
-
reports the tree version / `productPath` / server entry, VS Code inner dependencies and **prebuilt native packages**
|
|
389
|
-
(platform aggregator name + resolved module count) for scripts, and the DSH host log carries the `[code-server]` lines.
|
|
390
|
-
|
|
391
|
-
## Config (`config` in cordis.patch.yml; all have defaults)
|
|
392
|
-
|
|
393
|
-
| Key | Default | Description |
|
|
394
|
-
|---|---|---|
|
|
395
|
-
| `bin` | `code-server` (placeholder) | Launch priority: explicit `bin` in config > the tree package `@jinsiyu/dshcs-code-server/code-server/out/node/entry.js` > the old platform sub-packages `@jinsiyu/dshcs-code-server-<platform>-<arch>` > the in-package `vendor/code-server` > the old install root `.code-server-app` > plugin-internal `node_modules` > `code-server` on PATH. None present → startup error with troubleshooting hints |
|
|
396
|
-
| `host` | `127.0.0.1` | Bind address; `auth: none` only allows loopback (localhost/127.0.0.1/::1) |
|
|
397
|
-
| `port` | `8090` | Port; on conflict startup fails with diagnostics (no automatic port change) |
|
|
398
|
-
| `auth` | `none` | `none` \| `password`; non-loopback host automatically requires password |
|
|
399
|
-
| `passwordToken` | `''` | Token for password mode (passed to code-server via the `PASSWORD` env var) |
|
|
400
|
-
| `userDataDir` | `$DSH_HOME/code-server/user-data` | User-data isolation directory |
|
|
401
|
-
| `extensionsDir` | `$DSH_HOME/code-server/extensions` | Extensions directory |
|
|
402
|
-
| `readyTimeoutMs` | `60000` | `/healthz` readiness probe timeout |
|
|
403
|
-
|
|
404
|
-
User-level override example (write in `$DSH_HOME/profiles/web/cordis.patch.yml`, using the `- id: code-server` row):
|
|
405
|
-
|
|
406
|
-
```yaml
|
|
407
|
-
- id: code-server
|
|
408
|
-
config:
|
|
409
|
-
port: 8091
|
|
410
|
-
# Explicit (overrides dependency-install probing): a globally installed shim, or any entry.js
|
|
411
|
-
bin: C:\Users\User\AppData\Roaming\npm\code-server.cmd
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## JSON API (same-origin fetch; identical paths in web and desktop)
|
|
415
|
-
|
|
416
|
-
**No `webServer` dependency**: the host half registers its routes on DSH Connection's shared `/api` channel through
|
|
417
|
-
`ctx.connection.fetch.register`. In the web profile Connection mounts the `/api` prefix on webServer itself (with the
|
|
418
|
-
Host/Origin fence and browser auth); in the desktop profile `apps/desktop-host` feeds `/api/*` into the same
|
|
419
|
-
`createSharedFetchHandler('/api')` (IPC framed pipe, no HTTP server). The client only writes relative paths
|
|
420
|
-
(`fetch('/api/code-server/<op>')`), so both carriers behave identically.
|
|
421
|
-
|
|
422
|
-
| Method | Path | Description |
|
|
423
|
-
|---|---|---|
|
|
424
|
-
| GET | `/api/code-server/status` | `{ ok, running, status, host, port, pid, cwd, url, version, error, logTail, adopted }` (also `env` environment check and the `setup` compatibility field) |
|
|
425
|
-
| POST | `/api/code-server/start` | body `{ cwd? }` (omit cwd to keep the current workspace); idempotent |
|
|
426
|
-
| POST | `/api/code-server/stop` | Stop and recycle the process tree |
|
|
427
|
-
| POST | `/api/code-server/setup` | **Compatibility no-op**: since 0.1.36 dependencies are installed by the package manager, so this only re-runs the env self-check and returns |
|
|
428
|
-
| POST | `/api/code-server/open-file` | body `{ file }` — writes the signal consumed by the built-in `dshcs-open-file` extension to open the file in code-server |
|
|
429
|
-
|
|
430
|
-
> The plugin no longer registers `/code-server/*` webServer-only routes, and the code-server icon is inlined as a data URI
|
|
431
|
-
> in the client bundle — the client requests no plugin-owned HTTP resource at all.
|
|
432
|
-
|
|
433
|
-
## DSH Desktop (no webServer)
|
|
434
|
-
|
|
435
|
-
- The host half is `inject = ['connection', 'settings']` (**no `webServer`**) — the desktop profile disables webserver/web-runtime
|
|
436
|
-
and the plugin still works: `/api/*` requests travel Electron `dsh-app://` protocol handler → IPC framed pipe → `createSharedFetchHandler('/api')`.
|
|
437
|
-
- The right-sidebar tab, guide entry box, file-address claim, and settings card behave the same as in web (code-server remains an
|
|
438
|
-
iframe to the local `http://127.0.0.1:<port>`; the desktop renderer uses `webSecurity: true` with no CSP, so the cross-origin iframe loads).
|
|
439
|
-
The desktop build ships `dsh-client-ui-sidebar-right` in its seed package set as well, so the 0.2.3 "right-sidebar DSH only" rule is
|
|
440
|
-
not a regression for desktop; the only difference is the missing `webServer`, where `serve: dsh` falls back to loopback.
|
|
441
|
-
- Install into the desktop profile through the **desktop plugin manager** (not the CLI, see below).
|
|
442
|
-
- **Desktop installs face a 24-hour supply-chain policy (measured 2026-09-10; this is how 0.2.4 got installed)**:
|
|
443
|
-
- the CLI path is unavailable: `dsh plugin --profile desktop …` is rejected (*"profile "desktop" is managed exclusively by the
|
|
444
|
-
Electron application"*), so desktop installs only go through the app's package transaction (`pnpm add <spec> --save-exact`,
|
|
445
|
-
executed in `~/.dsh/desktop/staging/<uuid>/profile` before activation);
|
|
446
|
-
- that transaction's pnpm (the app bundles **11.7.0**, patched by DeepSeek) runs a **lockfile supply-chain verification** before
|
|
447
|
-
`add` ("Verifying lockfile against supply-chain policies (717 entries)") which requires packages to be **at least 24 h old**,
|
|
448
|
-
otherwise `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`;
|
|
449
|
-
- **the two stages behave differently (measured)**:
|
|
450
|
-
- verifying an **existing lockfile**: `minimumReleaseAgeExclude` is *not* honoured (exact versions and bare package names
|
|
451
|
-
were both tried);
|
|
452
|
-
- **resolution** (no lockfile to verify, e.g. after `pnpm clean --lockfile`): the list *is* honoured, and pnpm even appends
|
|
453
|
-
entries itself (the install log prints *"Added N entries to minimumReleaseAgeExclude…"*);
|
|
454
|
-
- so the working recipe for a **just-published** (<24 h) version on desktop is to start from a clean, lockfile-free profile:
|
|
455
|
-
1. `pnpm clean --lockfile` (**note: it also deletes `node_modules`**, leaving the profile to be reinstalled);
|
|
456
|
-
2. with the app's bundled runtime, run `add <spec> --save-exact --trust-lockfile` in the profile directory
|
|
457
|
-
(runtime/store/config live under `~/.dsh/desktop/pnpm/{store,cache,state,config,home}`,
|
|
458
|
-
`--config.userconfig=…/config/npmrc`, otherwise pnpm fails with `ERR_PNPM_UNEXPECTED_STORE` /
|
|
459
|
-
`…UNEXPECTED_VIRTUAL_STORE`);
|
|
460
|
-
3. the app's boot command (`install --offline --frozen-lockfile --trust-lockfile`) then passes (lockfile matches
|
|
461
|
-
package.json, packages are in the store); if the plugin name is already in `dsh.profile.bundles` nothing else is needed.
|
|
462
|
-
- do **not** try to bypass it with `minimumReleaseAge: 0`: it does clear the check, but that key is **not** one of the policy
|
|
463
|
-
sections the app tolerates (`project-manager.ts` ignores only `minimumReleaseAgeExclude:` / `trustPolicyExclude:` and validates
|
|
464
|
-
the manifest before every `mutate()`), so persisting it makes the app fail with
|
|
465
|
-
*"core package mapping does not match desktop-packages.json"*.
|
|
466
|
-
- Alternatively **wait out the 24 h** and install normally from the plugin manager. The web profile is unaffected: its
|
|
467
|
-
`pnpm-workspace.yaml` sets `minimumReleaseAge: false`.
|
|
468
|
-
- this plugin's closure contains platform native sub-packages (`@jinsiyu/dsh-code-server-runtime-win32-*`) published together with
|
|
469
|
-
the plugin itself, so every new version hits that policy on desktop.
|
|
470
|
-
- **Desktop client bundles are cached by Electron; restarting the app does not guarantee a new one** (measured 2026-09, hit while shipping 0.2.5):
|
|
471
|
-
- symptom: `lib/client.js` in the profile is the new version, yet the renderer keeps running the old code —
|
|
472
|
-
`%APPDATA%\@deepseek-ai\dsh-desktop\Code Cache\js` only contains strings unique to the old version (e.g. `dshcs-artifacts`)
|
|
473
|
-
and none unique to the new one (`fileOpenScope`), and `Cache\` still holds an old response body referencing
|
|
474
|
-
`dsh-code-server-app`. The same applies to first-party plugins (the cached `ui-deliverables` even lacks the current
|
|
475
|
-
`data-presented-files-row` marker);
|
|
476
|
-
- diagnosis (byte level — do **not** use `Select-String`, which reads files with the console encoding and gives false
|
|
477
|
-
negatives on non-ASCII markers): search `Code Cache\js` for an **ASCII** marker unique to the new version
|
|
478
|
-
(ours is `fileOpenScope`); a hit proves the new bundle really was compiled;
|
|
479
|
-
- fix: fully close the app, delete the `Cache`, `Code Cache` and `GPUCache` directories, then start it (cache only —
|
|
480
|
-
profiles, sessions and settings are untouched):
|
|
481
|
-
```powershell
|
|
482
|
-
Remove-Item -Recurse -Force "$env:APPDATA\@deepseek-ai\dsh-desktop\Cache","$env:APPDATA\@deepseek-ai\dsh-desktop\Code Cache","$env:APPDATA\@deepseek-ai\dsh-desktop\GPUCache"
|
|
483
|
-
```
|
|
484
|
-
- scope: this is not specific to this plugin — **any** client plugin may keep running old code after an upgrade;
|
|
485
|
-
after a release, confirm with the marker trick above that the renderer actually swapped bundles.
|
|
486
|
-
|
|
487
|
-
## File-open plumbing (what the plugin itself still does)
|
|
488
|
-
|
|
489
|
-
DSH's own chips and preview buttons are what users click (see "Opening files" above); this plugin adds no row of its own.
|
|
490
|
-
What remains on the plugin side:
|
|
491
|
-
|
|
492
|
-
- the tab body parses `navigation.address` and posts the absolute path (plus an optional `line`) to
|
|
493
|
-
`/api/code-server/open-file`, which writes a signal file;
|
|
494
|
-
- the bundled `dshcs-open-file` extension polls that file and calls `showTextDocument` — VS Code Web has no official
|
|
495
|
-
"open this file from outside" API, so this is the only way to aim the workbench at a file. It is installed as a
|
|
496
|
-
**built-in** extension (in `lib/vscode/extensions`), so users cannot remove it from the extensions panel, and the
|
|
497
|
-
installer re-syncs it whenever its content changes.
|
|
498
|
-
|
|
499
|
-
## Known limitations
|
|
500
|
-
|
|
501
|
-
- ~~No sub-path~~ **no longer true (corrected with measurements in 0.2.0)**: the workbench HTML VS Code renders references
|
|
502
|
-
**only relative URLs** (9 references measured, 0 absolute; `serverBasePath="."`, `rootEndpoint="."`), and the client
|
|
503
|
-
builds its WebSocket path from `location.pathname + join(serverBasePath ?? '/', <quality>-<commit>)`. The IDE can
|
|
504
|
-
therefore be mounted directly under DSH's own `/code-server/*` (`serve: dsh`) — no second port, no HTML rewriting.
|
|
505
|
-
Item-by-item evidence: `docs/analysis-code-server-as-dsh-plugin.md`.
|
|
506
|
-
- **`serve: dsh` cannot proxy forwarded-port WebSockets**: `registerUpgrade` matches exact paths while `/proxy/:port`
|
|
507
|
-
carries the port in the path, so WebSocket forwarding for the Ports panel is unavailable in that mode (HTTP forwarding
|
|
508
|
-
works). Use `serve: loopback` when you need it.
|
|
509
|
-
- **`serve: dsh` shares DSH's origin**, so the iframe is not sandboxed there (same-origin plus `allow-same-origin` is
|
|
510
|
-
escapable by the frame itself); in `loopback` mode the iframe is cross-origin and `sandbox` stays as real protection.
|
|
511
|
-
- **Single instance across sessions**: one shared IDE per host; switching cwd requires a restart (the sidebar tab
|
|
512
|
-
handles it and hints).
|
|
513
|
-
- **Older DSH versions are unsupported (since 0.2.3)**: on a DSH without `sidebarRightTabs` / `sidebarRight` the plugin
|
|
514
|
-
offers nothing but an upgrade notice on the settings page; older-DSH users should stay on `0.2.2`
|
|
515
|
-
(`dsh plugin --profile web add dsh-code-server-app@0.2.2`).
|
|
516
|
-
- **Sidebar tab switching** (no longer reloads since 0.2.2): DSH's right sidebar renders only the active tab's body, and
|
|
517
|
-
a React unmount moves the iframe away; the plugin keeps it as a singleton resident surface and shuttles it between the
|
|
518
|
-
dock slot and a document-level park container with `Element.moveBefore()` (a state-preserving atomic move), so
|
|
519
|
-
switching tabs or collapsing the sidebar and back **does not reload** it. Browsers without `moveBefore` fall back to
|
|
520
|
-
the old behaviour (`appendChild` → full reload), reported as `degraded`; see "Why switching tabs no longer reloads".
|
|
521
|
-
- **Remote access**: with `serve: dsh` the browser only needs to reach DSH itself (one port, protected exactly like `/api`);
|
|
1
|
+
# dsh-code-server-app — Integrate code-server (VS Code in the browser) into DSH
|
|
2
|
+
|
|
3
|
+
> Source repository: see `repository` / `homepage` in `package.json`.
|
|
4
|
+
|
|
5
|
+
> ## ⚠️ Extension Marketplace Note (important)
|
|
6
|
+
>
|
|
7
|
+
> - **code-server's extension store is [Open VSX](https://open-vsx.org/), not the Microsoft Visual Studio Marketplace**;
|
|
8
|
+
> - Microsoft's Marketplace terms **prohibit third-party products (including code-server) from using its API**, so code-server cannot query Microsoft's extension list;
|
|
9
|
+
> - As a result, Microsoft **commercial/proprietary** extensions (e.g. **GitHub Copilot, the Remote series like Remote-SSH, Azure tools, IntelliCode**) are **not available** in the store — this is Microsoft's distribution policy, not a defect;
|
|
10
|
+
> - Microsoft **open-source** extensions (Python, TypeScript debugger, ESLint, …) are mirrored on Open VSX and install normally by search;
|
|
11
|
+
> - **If you need a proprietary Microsoft extension**: download the `.vsix` from the Marketplace page and install it manually with `code-server --install-extension <file>` (or drop it into `--extensions-dir`).
|
|
12
|
+
|
|
13
|
+
A static profile plugin (npm package with host + client bundle) that ships the **VS Code server tree** from a [code-server](https://github.com/coder/code-server) release as a **platform-independent dependency package** (pack-time artifact `vendor/vscode` → `@jinsiyu/dshcs-vscode-server`, no install scripts, no postinstall). The code-server **Node service layer is replaced by the plugin's own `lib/launcher.mjs`**: it drives `<tree>/lib/vscode/out/server-main.js` (`loadCodeWithNls()` / `createServer()` / `handleRequest()` / `handleUpgrade()`) directly and re-adds the few HTTP endpoints code-server used to provide (`/healthz`, `/manifest.json`, `/_static/*`, `/proxy/:port`). The 16 native modules (node-pty / @vscode/sqlite3 / spdlog / …) come from `@jinsiyu/dshcs-*-win32-<arch>` platform packages selected automatically per architecture by the platform aggregator. VS Code's inner dependencies and the prebuilt native modules are **all installed by the package manager together with the plugin** — no global npm install, no `bin` configuration, no profile config changes, no second install command, **no argon2/C++ toolchain**.
|
|
14
|
+
|
|
15
|
+
## UI carrier and required DSH version (0.2.3: right-sidebar DSH only)
|
|
16
|
+
|
|
17
|
+
| DSH version | Carrier | Entry points |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **>= 0.1.5-alpha.1** (has `sidebarRight` / `sidebarRightTabs`) | **Right-sidebar tab** (kind `code-server`, chip `Code Server`), which also **claims file addresses** (see below) | ① DSH's own **produced-file chips / presented-file card previews / inline file names in prose** (since 0.2.5, via the official `openFile` → file address → this tab); ② the **Code Server box** on the sidebar's guide ("开始") page; ③ Settings → Plugins → Code Server → **"Open in right sidebar"** |
|
|
20
|
+
| older (no sidebar service) | **Unsupported**: nothing but one notice on the settings page | none (Settings → Plugins → Code Server shows an upgrade notice) |
|
|
21
|
+
|
|
22
|
+
- Detection: first a synchronous `ctx.get('sidebarRightTabs') / ctx.get('sidebarRight')` probe; because the services may come up after this plugin, `ctx.inject(['sidebarRightTabs','sidebarRight'], …)` is awaited and a **2.5 s timeout marks the DSH as legacy** (no version comparison, and the plugin's own activation is never blocked).
|
|
23
|
+
Since 0.2.4 that verdict is **reversible** and registration no longer relies on `ctx` property access (which on desktop silently skipped registration — the symptom was "settings card looks normal but the sidebar has no entry"):
|
|
24
|
+
- services are looked up as `ctx.<name>` first and `ctx.get(name)` second, so either context shape registers;
|
|
25
|
+
- when the sync probe already sees the services but `inject` never calls back, registration falls back to the sync services after **1.5 s**;
|
|
26
|
+
- at 2.5 s only the settings notice appears; only after **10 s** does the client tell the host to recycle/stop prestarting (so a slow host is not punished);
|
|
27
|
+
- services arriving late automatically revoke the legacy verdict, register the sidebar, and report `{sidebar:true}` so the host re-enables;
|
|
28
|
+
- a failed registration is no longer silent: it logs an error and the card's entry row says "right-sidebar services were found but the tab could not be registered".
|
|
29
|
+
- **0.2.3 dropped legacy-DSH compatibility**: the floating ball and the internal floating window are **deleted**. When the DSH is detected as legacy the plugin
|
|
30
|
+
- registers only the settings card (an upgrade notice) — no ball, no floating window, **no file-address claim**, no IDE preload;
|
|
31
|
+
- reports `/api/code-server/ui-mode { sidebar:false }` to the host (after the 10 s grace above); the host then **recycles an instance it auto-prestarted** and stops prestarting (a user-started/adopted instance is never touched), and `{sidebar:true}` reverses that if the services show up later;
|
|
32
|
+
- upgrading DSH needs **no reinstall** — refresh the page and the card turns back into the full settings card.
|
|
33
|
+
- The sidebar tab hosts the code-server page (iframe) and follows the current session workspace; the panel can be collapsed/split/floated/fullscreened by DSH's right sidebar.
|
|
34
|
+
- **Resident IDE (0.2.2, on by default)**: switching to another tab or collapsing the sidebar and coming back **no longer reloads** code-server — unsaved editor buffers, terminals and debug sessions all stay put (see "Why switching tabs no longer reloads" below).
|
|
35
|
+
- The settings card has exactly **two settings**: "**Claim scope**" and "Resident in background" — no other rows (0.2.7 removed the "Entry", "dependency install" and "environment check" rows).
|
|
36
|
+
Open the IDE from the **Code Server box** on the sidebar's guide page, or by clicking DSH's own produced-file chips / delivered-file previews / inline file names;
|
|
37
|
+
diagnostics stay out of the UI — the `[code-server]` lines in the DSH host log are the place to look (`/api/code-server/status` still returns `env` for scripts).
|
|
38
|
+
The old `windowedOpen` (open in a window) and `reserveComposer` were **removed in 0.2.6**: leftover keys in an old settings document neither fail nor apply (they are no longer part of the schema).
|
|
39
|
+
To use the IDE in a browser tab, visit the loopback address `http://127.0.0.1:<port>/` (or DSH's `/code-server/` under `serve: dsh`).
|
|
40
|
+
|
|
41
|
+
## Opening files (official entry points since 0.2.5)
|
|
42
|
+
|
|
43
|
+
DSH names files with **resource addresses**; `openFile` only hands the address to the right sidebar, which decides who draws it:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
DSH's produced-file chip / presented-file card preview / inline prose mention
|
|
47
|
+
→ openFile(path, { line? }) (provided by ui-chat)
|
|
48
|
+
→ dsh-resource://file/session/<sessionId>/<path> (or …/file/absolute/<path>)
|
|
49
|
+
→ ctx.sidebarRight.openResource(address)
|
|
50
|
+
→ claimed by the tab type whose patterns match (band extension(3) > builtin(2) > fallback(1),
|
|
51
|
+
then the longest matching pattern, then registration order)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This plugin registers:
|
|
55
|
+
|
|
56
|
+
| Field | Value | Effect |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `patterns` | `['dsh-resource://file/**']` | claims file addresses (a pattern containing `:` is matched against the **whole address**) |
|
|
59
|
+
| `priority` | `'extension'` | beats the built-in plain-text preview, which sits in `fallback` on purpose — DSH's own comment calls that band "the position VS Code's text editor holds among its editors", i.e. one any more specific type should beat |
|
|
60
|
+
| `canOpen` | see below | vetoes by the "claim scope" setting; unclaimed addresses fall back to DSH's built-in preview |
|
|
61
|
+
| `title` | last address segment (= file name) | the tab chip shows the file name; a page tab (`sidebar://code-server`) still reads `Code Server` |
|
|
62
|
+
|
|
63
|
+
- **Claim scope** (switchable in the settings card, `fileOpenScope`):
|
|
64
|
+
- `session` (default) — claim only `dsh-resource://file/session/…` (everything a session produces: deliverables, declared deliveries, prose mentions, tool views);
|
|
65
|
+
- `all` — also claim session-less `dsh-resource://file/absolute/…` addresses.
|
|
66
|
+
- **How the tab body locates the file**: it parses `useTabInfo().tab.navigation.address`
|
|
67
|
+
(`src/address.js`, same grammar as DSH's `parseFileAddress`), expands a workspace-relative path with that
|
|
68
|
+
session's cwd, and posts the absolute path (plus optional `line`) to the host's
|
|
69
|
+
`/api/code-server/open-file`; the bundled extension (`dshcs-open-file`) then calls `showTextDocument`
|
|
70
|
+
(positioned at the line when given).
|
|
71
|
+
- **One address = one tab** (DSH semantics: `contentId` *is* the address): three files mean three chips, but they
|
|
72
|
+
share the single resident workbench — switching tabs just re-aims the workbench at the corresponding file.
|
|
73
|
+
- **Why the bundled extension stays**: VS Code Web has no official "open this file from outside" API (the only
|
|
74
|
+
entry is `?folder=`, which picks the workspace), so aiming the workbench at a file has to be done by an
|
|
75
|
+
extension inside the tree. The host writes a signal file, the extension polls it and calls
|
|
76
|
+
`showTextDocument`, keeping the signal for retry when no window is connected yet.
|
|
77
|
+
|
|
78
|
+
## Why switching tabs no longer reloads (resident IDE)
|
|
79
|
+
|
|
80
|
+
**The old trap**: DSH's right sidebar (ui-dockkit) renders **only the active tab's body**
|
|
81
|
+
(`TabPanel.tsx:412` → `renderTab(active)`) — switching to another tab unmounts that body in React, which moves the
|
|
82
|
+
iframe out of the document and destroys its browsing context; switching back is a full VS Code reload (unsaved buffers
|
|
83
|
+
lost). Floating the tab into its own panel only worked around it.
|
|
84
|
+
|
|
85
|
+
**What it does now (`src/surface.js` in the client, 0.2.2)**: the plugin takes the iframe **away from React** and turns
|
|
86
|
+
it into a **singleton resident surface**:
|
|
87
|
+
|
|
88
|
+
| Situation | Action | Result |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| tab becomes active | `host.moveBefore(frame, null)` into the visible dock slot | state-preserving atomic move, **no reload** |
|
|
91
|
+
| tab deactivates / sidebar collapses | move back into a document-level park container (offscreen, keeps last docked size, `inert` + `aria-hidden`) | never destroyed, keeps running in the background |
|
|
92
|
+
| workspace / port changes | assign `src` explicitly | the only normal "reload" entry point |
|
|
93
|
+
|
|
94
|
+
- **Why `moveBefore`**: measured in a real browser (Edge/Chromium 151), a plain `appendChild` move resets the iframe's
|
|
95
|
+
internal timers (i.e. reloads it), while `Element.moveBefore()` (Chromium ≥133) preserves state (a probe counter keeps
|
|
96
|
+
counting 1→2).
|
|
97
|
+
- **Degradation is never silent**: when `moveBefore` is missing, or the host was already detached by React and it throws
|
|
98
|
+
`HierarchyRequestError: invalid hierarchy` (passive effect cleanup runs after DOM removal), the code falls back to
|
|
99
|
+
`appendChild` — one reload, but the frame is **never lost** — and reports `degraded` / `lastMoveError` so the UI can
|
|
100
|
+
say "residency unavailable".
|
|
101
|
+
- **Repaint fallback (measured)**: in the real GUI the surface was seen once with correct size, hit testing and
|
|
102
|
+
`visibility` that simply **stopped repainting** (a fully white panel, byte-identical screenshots proving no new frame).
|
|
103
|
+
`translateZ(0)` and `opacity` nudges did nothing; `display:none → forced reflow → restore` inside a single JS task
|
|
104
|
+
restored it without reloading the iframe document, without losing internal state and without a visible flash.
|
|
105
|
+
**The trigger could not be reproduced**: in a probe page an offscreen `moveBefore` park of 337 s (past Chrome's
|
|
106
|
+
~5 min cross-origin throttle window) followed by a dock with the fallback disabled still painted normally. It is
|
|
107
|
+
therefore kept as a **fallback**: every park→dock transition runs one `nudgeRepaint()` (counted as
|
|
108
|
+
`surfaceSnapshot().nudgeCount`; `setNudgeEnabled(false)` A/Bs it live).
|
|
109
|
+
- **Warm-up**: with `keepResident` (default `true`) the host builds the surface right after plugin start and leaves it
|
|
110
|
+
parked, so the first tab open needs no cold start; preloading never yanks a surface that is currently docked.
|
|
111
|
+
- **Debug handle**: `window.__dshcsSurface` (`snapshot()`, `setParkStrategy('offscreen'|'behind')`, `dock()`, `park()`,
|
|
112
|
+
`nudge()`, `setNudgeEnabled(false)`, `destroy()`).
|
|
113
|
+
|
|
114
|
+
**Measured** (DSH web GUI, real mouse clicks between sidebar tabs): switching away → `docked:false`, same iframe node,
|
|
115
|
+
in-frame probe still alive, `degraded:false`; switching back → `docked:true`, unchanged `src`, IDE pixels and editing
|
|
116
|
+
state preserved (no full reload). Full evidence and probe scripts: `docs/analysis-code-server-as-dsh-plugin.md`.
|
|
117
|
+
|
|
118
|
+
## Serving mode (`serve`)
|
|
119
|
+
|
|
120
|
+
| Mode | What it does | Requires |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| **`loopback` (default)** | the plugin listens on its own loopback port (`host:port`) and the sidebar iframe connects cross-origin; the process can be adopted after a DSH host restart | nothing |
|
|
123
|
+
| **`dsh`** | the IDE is mounted on **DSH's own HTTP port** at `/code-server/*` (HTTP prefix route) plus `/code-server/<quality>-<commit>` (exact WebSocket route), forwarded to the launcher's **named pipe**; **no extra port**; every request (including the WS handshake) first passes `ctx.connection.requestRejection()` — the same Host/Origin fence and browser-cookie authentication as `/api` | DSH providing `webServer` (web profile); desktop falls back to loopback automatically |
|
|
124
|
+
|
|
125
|
+
- Switch it in `config.serve` in `cordis.patch.yml` or in Settings → Plugins → Code Server (takes effect on the next start).
|
|
126
|
+
- Benefits of `dsh`: a single URL/port (remote access to DSH gives you the IDE), no extra loopback listener, authentication on par with DSH.
|
|
127
|
+
- Two **known trade-offs** of `dsh`: the iframe shares DSH's origin, so `sandbox` is dropped there (same-origin plus
|
|
128
|
+
`allow-same-origin` is escapable by the frame itself; in `loopback` mode the iframe is cross-origin and `sandbox` stays
|
|
129
|
+
as real protection — clipboard is still granted via `allow="clipboard-read; clipboard-write"`); and forwarded-port
|
|
130
|
+
**WebSockets** cannot be routed because `registerUpgrade` matches exact paths while `/proxy/:port` carries the port in
|
|
131
|
+
the path (HTTP forwarding works; use `loopback` when you need WS forwarding).
|
|
132
|
+
|
|
133
|
+
- In `loopback` mode every upgrade passes a **code-server-equivalent Origin check** (since 0.2.1): when an `Origin`
|
|
134
|
+
header is present its host must equal `Host` (honouring `Forwarded: host=` / `X-Forwarded-Host`, like code-server),
|
|
135
|
+
otherwise the handshake gets `403`; non-browser requests without `Origin` are allowed. Without that check any local
|
|
136
|
+
browser page could complete a handshake against `ws://127.0.0.1:<port>/stable-<commit>` and drive the IDE.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
## Legacy DSH (unsupported since 0.2.3)
|
|
140
|
+
|
|
141
|
+
**Behaviour**: when `sidebarRightTabs` / `sidebarRight` cannot be found, the plugin registers a single settings card:
|
|
142
|
+
|
|
143
|
+
> **Code Server** — this DSH version is unsupported (no right-sidebar service)
|
|
144
|
+
> Since 0.2.3 this plugin no longer supports older DSH versions.
|
|
145
|
+
> The right-sidebar plugin services `sidebarRightTabs` / `sidebarRight` were not detected, so the plugin exposes no
|
|
146
|
+
> entry point at all (the old floating ball and floating window have been removed) and will not start the IDE in the
|
|
147
|
+
> background. Upgrade DSH to a version with the right sidebar (>= 0.1.5-alpha.1): Code Server then appears as a
|
|
148
|
+
> right-sidebar tab, this page shows the full settings again, and no reinstall is needed — a page refresh is enough.
|
|
149
|
+
|
|
150
|
+
- **No other UI**: no `shell.overlay` registration (floating ball), no file-address claim, no resident preload.
|
|
151
|
+
- **Host side**: the client posts `/api/code-server/ui-mode { sidebar:false }`; the host then ① stops auto-prestarting
|
|
152
|
+
the IDE (`maybePrestart` returns immediately) and ② **recycles** an instance it had just auto-prestarted (unless it
|
|
153
|
+
was adopted), so no unusable IDE process or port is left behind. A user-started/adopted instance is never stopped.
|
|
154
|
+
- **Why delete instead of keeping**: the internal floating window was a stopgap from the era of early-2026 DSH builds
|
|
155
|
+
without right-sidebar services. The resident surface, clipboard handling, shortcuts and panel collapsing all build on
|
|
156
|
+
DSH's right sidebar, so maintaining two carriers costs more than it is worth. Older-DSH users should stay on `0.2.2`
|
|
157
|
+
(`dsh plugin --profile web add dsh-code-server-app@0.2.2`).
|
|
158
|
+
|
|
159
|
+
## code-server workspace and process lifecycle
|
|
160
|
+
|
|
161
|
+
- code-server's workspace **follows the active DSH session/workspace**: switching sessions/workspaces while the IDE is open restarts code-server to the new directory
|
|
162
|
+
(resolution order: current session cwd → session's workspace.path → recentWorkspace.path → first workspace.path);
|
|
163
|
+
the opened directory is shown inside code-server (`?folder=<cwd>`, the page reloads when following a switch);
|
|
164
|
+
implementation note: the iframe `src` must carry `?folder=<cwd>` — code-server's front-end remembers the "last workspace" and restores it by itself;
|
|
165
|
+
a bare root URL only shows the previously opened directory and does not follow switches (verified locally).
|
|
166
|
+
**Windows path format (verified)**: the `folder` parameter must start with `/` and use forward slashes only, e.g. `/C:/Users/User/Desktop/biss`;
|
|
167
|
+
a bare Windows path (`C:\...`) is parsed as a URI scheme and the drive letter is stripped (page shows `\Users\User\...` with an empty file tree),
|
|
168
|
+
while `file:///C:/...` reports "Workspace does not exist".
|
|
169
|
+
- Process lifecycle is managed by the host plugin: startup writes `$DSH_HOME/code-server/pid.json`, stop kills the tree (`taskkill /T` or process-group SIGKILL),
|
|
170
|
+
crash/exit updates status live; after a DSH host restart the plugin **adopts** a still-running instance (verifies pid + `/healthz`), without duplicate start or killing unrelated processes;
|
|
171
|
+
- `node_modules` and the pack-time artifact `vendor/` are git-ignored; after cloning, follow
|
|
172
|
+
"Install the plugin (script-free install; code-server bundled)" below — `pnpm install` → `pnpm run build:client` →
|
|
173
|
+
`pnpm run vendor:vscode` → `pnpm pack` + `dsh plugin --profile web add`.
|
|
174
|
+
|
|
175
|
+
> Verified locally (BM: Windows 11 ARM64): `code-server@4.136.2` (with Code 1.136.1) bundled in the plugin,
|
|
176
|
+
> placed offline at activation → VS Code internal deps installed → started → healthz 200 →
|
|
177
|
+
> cwd switch restart while running → stopped → fully recycled.
|
|
178
|
+
|
|
179
|
+
## Packaging (how to build the tarball)
|
|
180
|
+
|
|
181
|
+
```powershell
|
|
182
|
+
cd C:\Users\User\Desktop\dsh-code-server-app
|
|
183
|
+
pnpm install # dev deps (esbuild + motion); allowBuilds is explicit → no postinstall runs
|
|
184
|
+
pnpm run build:client # src/factory.js → lib/client.js (not committed; must be built first)
|
|
185
|
+
pnpm run vendor:check # optional: show the bundled tree version vs the latest code-server release
|
|
186
|
+
pnpm run vendor:vscode # ① produce vendor/vscode (the trimmed VS Code tree, ~197MB)
|
|
187
|
+
pnpm run repack:build -- --target win32-arm64,win32-x64 --pack # ② one script builds every sub-package
|
|
188
|
+
pnpm run publish:repacks # ③ publish every @jinsiyu/* sub-package (default dist-tag: next)
|
|
189
|
+
pnpm pack # ④ → dsh-code-server-app-<version>.tgz (~107KB)
|
|
190
|
+
pnpm run publish:plugin # ⑤ publish the plugin itself (default dist-tag: next)
|
|
191
|
+
# once the user has restarted dsh web and confirmed it works, promote latest:
|
|
192
|
+
pnpm run promote -- <version>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
> **dist-tag policy (mandatory)**: every release goes to **`next`** and **never touches `latest`**;
|
|
196
|
+
> `latest` always points at the most recent *confirmed bug-free* version and is only moved by
|
|
197
|
+
> `pnpm run promote -- <version>` (= `npm dist-tag add dsh-code-server-app@<version> latest`)
|
|
198
|
+
> **after the user restarts `dsh web` and confirms it works**. That way
|
|
199
|
+
> `dsh plugin add dsh-code-server-app` (no version) — and anything else resolving `latest` — never picks up an
|
|
200
|
+
> unverified build. Sub-packages (`@jinsiyu/dshcs-*`, the aggregators) are referenced by exact/caret versions,
|
|
201
|
+
> so their dist-tags do not affect resolution, but they default to `next` as well.
|
|
202
|
+
> Inspect the current tags with `npm dist-tag ls dsh-code-server-app`.
|
|
203
|
+
|
|
204
|
+
`repack:build` (`scripts/vendor-repacks.mjs`) is the **single script that produces every sub-package**:
|
|
205
|
+
|
|
206
|
+
| Sub-package | Content | os/cpu |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `@jinsiyu/dshcs-vscode-server@<code-server version>` | the trimmed VS Code tree (`lib/vscode` + `out/browser` + `src/browser`; **without** code-server's `out/node` and its 136 runtime deps) | platform-independent |
|
|
209
|
+
| `@jinsiyu/dshcs-<name>[-win32-<arch>]` ×24 | the VS Code inner packages that need building (node-pty / @vscode/sqlite3 / kerberos / koffi / ssh2 / …) | gated when platform-specific |
|
|
210
|
+
| `@jinsiyu/dsh-code-server-runtime-win32-<arch>` | platform aggregator: its `dependencies` map those 16 natives back to their original names via `npm:` aliases | win32-<arch> |
|
|
211
|
+
|
|
212
|
+
| Goal | Command |
|
|
213
|
+
|---|---|
|
|
214
|
+
| **Build from the latest upstream release** | `pnpm run vendor:latest` (= `--force`): pulls `code-server@latest`'s tree into `vendor/vscode`; afterwards you **must** re-run `repack:build` and republish every sub-package |
|
|
215
|
+
| **Pin a version** | `pnpm run vendor:vscode -- --version 4.136.2` |
|
|
216
|
+
| **Snapshot from an existing tree** | `pnpm run vendor:vscode -- --from <code-server dir>` (seconds) |
|
|
217
|
+
| **Rebuild every sub-package** | `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack` (without `--from` it npm-installs and compiles the source tree itself — slow) |
|
|
218
|
+
| **Rebuild only the tree/aggregator packages** | `node scripts/vendor-repacks.mjs --reuse --target win32-arm64,win32-x64 --pack` (reuses the natives already in `repack/build`) |
|
|
219
|
+
| **Publish sub-packages** | `pnpm run publish:repacks` (`--dry-run` to preview; `--only <substr>` to filter; `--otp <code>` / `--limit N` for 2FA) |
|
|
220
|
+
| **Publish the plugin itself** | `pnpm run publish:plugin` (publishes the exact tarball that was verified; no re-packing; default dist-tag `next`) |
|
|
221
|
+
| **Promote `latest`** | `pnpm run promote -- <version>` (only after the user restarted and confirmed; `--dry-run` shows the current tags first) |
|
|
222
|
+
| **Just report versions** | `pnpm run vendor:check` |
|
|
223
|
+
|
|
224
|
+
> `pnpm pack`'s `prepack` runs the vendor-code-server script once; when `vendor/code-server` already exists it is
|
|
225
|
+
> a **no-op that takes seconds**, so after ordinary code changes you can just run `pnpm pack` (it will never
|
|
226
|
+
> silently upgrade code-server). Upgrading code-server requires an explicit `pnpm run vendor:latest`
|
|
227
|
+
> (or `--force` / `--version`) **plus** republishing the sub-packages.
|
|
228
|
+
|
|
229
|
+
## Install the plugin (one command; all dependencies installed by the package manager)
|
|
230
|
+
|
|
231
|
+
```powershell
|
|
232
|
+
# no postinstall in the package → no pnpm approve-builds / allowBuilds; one command installs everything
|
|
233
|
+
dsh plugin --profile web add dsh-code-server-app@0.2.1
|
|
234
|
+
# a local tarball works the same way:
|
|
235
|
+
dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app\dsh-code-server-app-0.2.1.tgz
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Ready to use immediately — **no second step, no "Install environment", no install-guide modal**.
|
|
239
|
+
The main package is only **~110KB** (the plugin's own code plus the launcher); everything else is dependencies:
|
|
240
|
+
|
|
241
|
+
- **the VS Code tree** (`lib/vscode` 196.9MB + `out/browser` + `src/browser`) is a **platform-independent package**
|
|
242
|
+
`@jinsiyu/dshcs-vscode-server@<code-server version>` declared in the plugin's `dependencies`; it runs from
|
|
243
|
+
`<profile>\node_modules\@jinsiyu\dshcs-vscode-server\vscode` (the legacy full tree at
|
|
244
|
+
`@jinsiyu/dshcs-code-server/code-server` is still recognised as a fallback);
|
|
245
|
+
- the **pure-JS part** of VS Code's inner dependencies (35 packages: xterm / katex / typescript / ws / tar …) is
|
|
246
|
+
declared in the plugin's `dependencies` and installed by pnpm into the profile's `node_modules` (hoisted);
|
|
247
|
+
- the **binary part** comes entirely from `@jinsiyu/dshcs-*` platform packages: 16 native packages mapped back to their
|
|
248
|
+
**original names** (`node-pty` / `@vscode/sqlite3` / `@vscode/spdlog` / …) by the **platform aggregator**
|
|
249
|
+
`@jinsiyu/dsh-code-server-runtime-win32-<arch>` using `npm:` aliases; the aggregators sit in the plugin's
|
|
250
|
+
`optionalDependencies`, so pnpm auto-selects the right platform;
|
|
251
|
+
- consequently the dependency graph contains **no package with pre/install/postinstall or a `binding.gyp`** →
|
|
252
|
+
no profile `allowBuilds`, no build script ever runs, and **the user machine needs no C++ toolchain**;
|
|
253
|
+
- **upgrading the plugin no longer re-downloads the tree**: the tree package is cached by version
|
|
254
|
+
(~60MB, ~197MB unpacked).
|
|
255
|
+
|
|
256
|
+
### Install mechanism (why it is built this way)
|
|
257
|
+
|
|
258
|
+
- **The pnpm 11 hard constraint**: any package in the dependency graph whose manifest has
|
|
259
|
+
`preinstall|install|postinstall` (or that ships a `binding.gyp`/`.hooks`) counts as "needs building" and must be
|
|
260
|
+
approved by the **host profile's** `pnpm-workspace.yaml` via `allowBuilds`, otherwise `dsh plugin add` exits 1 with
|
|
261
|
+
`[ERR_PNPM_IGNORED_BUILDS]`. A dependency's own `pnpm.allowBuilds`, `.npmrc`, `patch:` protocol and
|
|
262
|
+
`optionalDependencies` do not help (measured 2026-09, pnpm 11.25);
|
|
263
|
+
- **the tree** is prepared at pack time with `npm install code-server@<version> --ignore-scripts` (skipping the official
|
|
264
|
+
`sh ./postinstall.sh`, which cannot run on Windows), then `scripts/vendor-vscode-server.mjs` keeps **only the VS Code
|
|
265
|
+
tree**: `lib/vscode/**`, `out/browser/**`, `src/browser/**` plus the license files are copied to `vendor/vscode/`, and a
|
|
266
|
+
generated root `package.json` records the upstream code-server version. code-server's own `out/node/**` and its 136
|
|
267
|
+
runtime dependencies **no longer ship** — they are replaced by `lib/launcher.mjs`;
|
|
268
|
+
- **the packages that need a toolchain** are repacked into `@jinsiyu/dshcs-*` by `scripts/vendor-repacks.mjs`:
|
|
269
|
+
the compiled package directory is copied and its `scripts` / `files` / `binding.gyp` / `.hooks` / `.npmignore` are
|
|
270
|
+
**removed** (the built `.node` and every runtime file stay) → sibling packages in its dependency list become `npm:`
|
|
271
|
+
aliases → platform-specific ones get `os`/`cpu` plus a `-<platform>-<arch>` suffix. For win32 targets the script also
|
|
272
|
+
verifies each `.node` PE machine (0x8664=x64 / 0xaa64=arm64) so a cross-compiled artifact cannot ship the wrong arch;
|
|
273
|
+
- **the platform aggregator** maps those repacks back to their original names (e.g.
|
|
274
|
+
`"node-pty": "npm:@jinsiyu/dshcs-node-pty@1.2.0-beta.15"`), so VS Code's `import('node-pty')` needs no change; the
|
|
275
|
+
aggregator is itself `os`/`cpu` gated, and the plugin declares both win32-arm64 and win32-x64 in
|
|
276
|
+
`optionalDependencies`, so one command picks the right one;
|
|
277
|
+
- **resolution path**: the host finds the tree with `require.resolve('@jinsiyu/dshcs-vscode-server/package.json')`
|
|
278
|
+
(then the inner `vscode/` directory) and the entry is `vscode/lib/vscode/out/server-main.js`; VS Code's inner deps are
|
|
279
|
+
resolved upwards from that root (`vscode/lib/vscode/node_modules` → package `node_modules` → `<profile>/node_modules`).
|
|
280
|
+
The legacy full tree (`@jinsiyu/dshcs-code-server/code-server`) is still recognised as a fallback;
|
|
281
|
+
- **runtime layout self-healing** (`ensureRuntimeLayout()` in `lib/native.js`, idempotent, run **at activation before
|
|
282
|
+
`envCheck` and again before every start**): the host adds two kinds of **junctions** (Windows junctions / POSIX dir
|
|
283
|
+
symlinks) into the tree:
|
|
284
|
+
1. `ensureAliasLinks()`: re-links the native aliases the aggregator carries into `<tree>/node_modules` — pnpm nests
|
|
285
|
+
`os`/`cpu`-gated packages under the aggregator's own `node_modules`, and `lib/vscode/out/server-main.js` uses
|
|
286
|
+
**ESM imports** (ESM ignores `NODE_PATH`), so a missing link means an immediate 500;
|
|
287
|
+
2. `ensureInnerModuleLinks()`: restores VS Code's **inner dependency directories**
|
|
288
|
+
`lib/vscode/node_modules` and `lib/vscode/extensions/node_modules` from the two `package.json` files — the trimmed
|
|
289
|
+
tree ships neither, and code that builds dependency paths explicitly (e.g. the bundled TypeScript extension looking
|
|
290
|
+
for `<ext>/../node_modules/typescript/lib/tsserver.js`) otherwise reports
|
|
291
|
+
"VS Code's tsserver was deleted by another application…" (measured with 1.136.1).
|
|
292
|
+
> **Size note**: the plugin tarball is **~110KB**; `@jinsiyu/dshcs-vscode-server` is **~60MB** (~197MB unpacked);
|
|
293
|
+
> the 16 native packages add ~250MB. A full install downloads roughly 310MB. Neither `vendor/` nor `repack/` is committed to git (see `.gitignore`).
|
|
294
|
+
|
|
295
|
+
> **Upgrading from ≤ 0.1.43**: the tree package changed from `@jinsiyu/dshcs-code-server` (the full code-server tree with
|
|
296
|
+
> `out/node` and 136 runtime deps) to `@jinsiyu/dshcs-vscode-server` (the trimmed tree). **The new code defaults to
|
|
297
|
+
> `serve: loopback`, which behaves exactly like 0.1.43**; switch to `serve: dsh` for same-origin mounting. The install
|
|
298
|
+
> command is unchanged (`dsh plugin --profile web add dsh-code-server-app@<version>`), and pnpm drops the old
|
|
299
|
+
> `dshcs-code-server` sub-package.
|
|
300
|
+
### Development: install from source (changes take effect immediately)
|
|
301
|
+
|
|
302
|
+
```powershell
|
|
303
|
+
dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
> A source path installs via `link:`. On a dev machine without `vendor/code-server`, run
|
|
307
|
+
> `pnpm run vendor:vscode -- --dev-links` first. Dependencies (inner JS deps + the platform aggregator) are installed by pnpm
|
|
308
|
+
> too — but the not-yet-published local `@jinsiyu/*` packages must either be published first, or the
|
|
309
|
+
> `repack/tgz/*.tgz` files must be installed into the profile as `file:` dependencies.
|
|
310
|
+
>
|
|
311
|
+
> **Changing the client bundle**: edit `src/factory.js` then run `pnpm run build:client`
|
|
312
|
+
> to regenerate `lib/client.js` (that artifact is not tracked; a browser refresh picks it up — no host restart needed).
|
|
313
|
+
> Window animations are driven by the embedded `motion`; feel parameters live in `winPhysics` (one spot) in `src/factory.js`.
|
|
314
|
+
|
|
315
|
+
### Pack-machine environment (the user machine needs nothing)
|
|
316
|
+
|
|
317
|
+
**A toolchain is needed at pack time only — never on the user machine.**
|
|
318
|
+
|
|
319
|
+
| Env | Version / requirement | User machine | Pack machine |
|
|
320
|
+
|---|---|---|---|
|
|
321
|
+
| Node.js | **v24.x** (latest code-server requirement; v24.13.1 here) | required | required |
|
|
322
|
+
| npm / pnpm | npm ships with Node; pnpm comes from DSH | required (installs deps) | required |
|
|
323
|
+
| **MSVC build tools** | **VS Community 2026 + C++ desktop workload** | ❌ **not needed** | pack time (16 native packages) |
|
|
324
|
+
| **VS Spectre-mitigated libs** | one set for ARM64 **and** one for x86/x64 ("MSVC v14x Spectre-mitigated libs") | ❌ not needed | pack time (otherwise MSB8040) |
|
|
325
|
+
| Python | **3.13.x** | ❌ not needed | pack time (node-gyp) |
|
|
326
|
+
| node-gyp | **13.x** (older versions don't recognize VS 2026) | ❌ not needed | pack time |
|
|
327
|
+
|
|
328
|
+
> **Packing still works without the Spectre libs**: when a package fails to compile, `vendor-repacks.mjs` downgrades
|
|
329
|
+
> `SpectreMitigation` to `false` in that architecture's `*.gyp` files and retries (only the Spectre hardening is
|
|
330
|
+
> lost, functionality is unaffected) and says so in the log.
|
|
331
|
+
|
|
332
|
+
### Windows native build notes (pack time, verified locally ARM64)
|
|
333
|
+
|
|
334
|
+
- **VS needs the Spectre-mitigated libraries** (MSB8040): Visual Studio Installer → Individual components →
|
|
335
|
+
"MSVC v14x Spectre-mitigated libs" — **install the ARM64 and the x86/x64 sets separately**.
|
|
336
|
+
- **node-gyp 13.x** (9.x does not recognize VS 2026): `npm install -g node-gyp@latest`.
|
|
337
|
+
- **x64 cross-compiling**: `vendor-repacks.mjs` uses `npm install --os=win32 --cpu=x64 --ignore-scripts` to fetch
|
|
338
|
+
the packages, then `npm rebuild --arch=x64` per package; the resulting PE machine types were verified
|
|
339
|
+
(kerberos / sqlite3 / spdlog …).
|
|
340
|
+
- Latest code-server requires **Node v24**.
|
|
341
|
+
- If you don't need the self-contained install (e.g. a global code-server already exists), skip it:
|
|
342
|
+
the plugin falls back to a configured/PATH `bin` (see the "Config" table).
|
|
343
|
+
|
|
344
|
+
### Upgrading the VS Code tree (upstream = a code-server release)
|
|
345
|
+
|
|
346
|
+
- **The version is decided at pack time**: `pnpm run vendor:latest` (= `--force`) pulls the tree of the npm **latest**
|
|
347
|
+
release; or use `pnpm run vendor:vscode -- --version 4.136.2` / `DSHCS_CODE_SERVER_VERSION`.
|
|
348
|
+
With an existing `vendor/vscode`, a plain `pnpm pack` never upgrades (it is a no-op).
|
|
349
|
+
- **Check first**: `pnpm run vendor:check` prints the bundled version / upstream latest.
|
|
350
|
+
- **A version bump means rebuilding and republishing the sub-packages** (all with the same script):
|
|
351
|
+
1. `pnpm run repack:build -- --target win32-arm64,win32-x64 --pack` → the new tree package
|
|
352
|
+
(`@jinsiyu/dshcs-vscode-server@<new version>`) and the natives rebuilt against the new inner dependencies (the
|
|
353
|
+
script also rewrites the plugin's pure-JS `dependencies` and both aggregator versions);
|
|
354
|
+
2. `pnpm run republish:repacks` (`pnpm run publish:repacks`) → publish; then bump the plugin version → `pnpm pack`
|
|
355
|
+
→ publish the plugin.
|
|
356
|
+
- `productPath` (`<quality>-<commit>`, part of the client WebSocket path) is **computed from `lib/vscode/product.json`**,
|
|
357
|
+
so upgrading the tree needs no code change — but the routes are registered at activation, so restart `dsh web` afterwards.
|
|
358
|
+
- **No runtime auto-upgrade anymore**: nothing fetches latest at startup; the version is fully determined by the bundled artifact.
|
|
359
|
+
- Bundled locally right now: the tree of `code-server@4.136.2` (VS Code 1.136.1, `productPath=stable-8d5f383f…`).
|
|
360
|
+
|
|
361
|
+
### Compatibility with the old install locations
|
|
362
|
+
|
|
363
|
+
Host probe order: `@jinsiyu/dshcs-vscode-server/vscode` (**the real layout since 0.2.0**) >
|
|
364
|
+
`@jinsiyu/dshcs-code-server/code-server` (the full tree, 0.1.40–0.1.43) >
|
|
365
|
+
`@jinsiyu/dshcs-code-server-<platform>-<arch>/code-server` (the 0.1.37 platform sub-packages) >
|
|
366
|
+
the in-package `vendor/vscode` > the in-package `vendor/code-server` (development). The old install root
|
|
367
|
+
`<profile>\.code-server-app` is only mentioned in a startup log line; nothing writes to it any more.
|
|
368
|
+
## Settings card (Settings → Plugins → Code Server)
|
|
369
|
+
|
|
370
|
+
Modeled after dsh-auto-open-web's custom card, registered on the `settings.plugin.item` slot,
|
|
371
|
+
persisted via the official settings domain (`settingsScope`, namespace `code-server`) into the official settings document:
|
|
372
|
+
|
|
373
|
+
| Key | Default | Description |
|
|
374
|
+
|---|---|---|
|
|
375
|
+
| `fileOpenScope` | `session` | **Claim scope** (0.2.5): `session` claims only session-scoped file addresses (`dsh-resource://file/session/…` — DSH's produced files, declared deliveries, prose mentions); `all` also claims session-less `…/file/absolute/…`. Unclaimed addresses fall back to DSH's built-in preview |
|
|
376
|
+
| `keepResident` | `true` | **Resident in background**: on, the host preloads the IDE into a parked surface right after start — switching tabs or collapsing the sidebar never reloads it and the first open needs no cold start; off loads it only when the panel is opened (saves memory) |
|
|
377
|
+
|
|
378
|
+
(Since 0.2.6 the card keeps only those two settings; `windowedOpen` and `reserveComposer` are gone — leftover keys in an old
|
|
379
|
+
settings document neither fail nor apply. `serve` remains a key in the settings namespace (usable from a settings document) but
|
|
380
|
+
has **no card row** — see "Serving mode".)
|
|
381
|
+
|
|
382
|
+
> Card changes take effect immediately via `scope.watch` (the host status API returns `keepResident` and
|
|
383
|
+
> `fileOpenScope`; the client applies them at once); no dsh restart needed. **After adding new setting keys, restart dsh web before first use**,
|
|
384
|
+
> so the host re-registers the settings namespace (schema includes the new key); otherwise save/validation of the new key won't work.
|
|
385
|
+
|
|
386
|
+
Since 0.2.7 the card has **no** "Entry", "dependency install" or "environment check" rows: the entry lives in the sidebar's
|
|
387
|
+
guide page (and in DSH's own file clicks), and diagnostics stay out of the UI — the `/api/code-server/status` `env` field still
|
|
388
|
+
reports the tree version / `productPath` / server entry, VS Code inner dependencies and **prebuilt native packages**
|
|
389
|
+
(platform aggregator name + resolved module count) for scripts, and the DSH host log carries the `[code-server]` lines.
|
|
390
|
+
|
|
391
|
+
## Config (`config` in cordis.patch.yml; all have defaults)
|
|
392
|
+
|
|
393
|
+
| Key | Default | Description |
|
|
394
|
+
|---|---|---|
|
|
395
|
+
| `bin` | `code-server` (placeholder) | Launch priority: explicit `bin` in config > the tree package `@jinsiyu/dshcs-code-server/code-server/out/node/entry.js` > the old platform sub-packages `@jinsiyu/dshcs-code-server-<platform>-<arch>` > the in-package `vendor/code-server` > the old install root `.code-server-app` > plugin-internal `node_modules` > `code-server` on PATH. None present → startup error with troubleshooting hints |
|
|
396
|
+
| `host` | `127.0.0.1` | Bind address; `auth: none` only allows loopback (localhost/127.0.0.1/::1) |
|
|
397
|
+
| `port` | `8090` | Port; on conflict startup fails with diagnostics (no automatic port change) |
|
|
398
|
+
| `auth` | `none` | `none` \| `password`; non-loopback host automatically requires password |
|
|
399
|
+
| `passwordToken` | `''` | Token for password mode (passed to code-server via the `PASSWORD` env var) |
|
|
400
|
+
| `userDataDir` | `$DSH_HOME/code-server/user-data` | User-data isolation directory |
|
|
401
|
+
| `extensionsDir` | `$DSH_HOME/code-server/extensions` | Extensions directory |
|
|
402
|
+
| `readyTimeoutMs` | `60000` | `/healthz` readiness probe timeout |
|
|
403
|
+
|
|
404
|
+
User-level override example (write in `$DSH_HOME/profiles/web/cordis.patch.yml`, using the `- id: code-server` row):
|
|
405
|
+
|
|
406
|
+
```yaml
|
|
407
|
+
- id: code-server
|
|
408
|
+
config:
|
|
409
|
+
port: 8091
|
|
410
|
+
# Explicit (overrides dependency-install probing): a globally installed shim, or any entry.js
|
|
411
|
+
bin: C:\Users\User\AppData\Roaming\npm\code-server.cmd
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## JSON API (same-origin fetch; identical paths in web and desktop)
|
|
415
|
+
|
|
416
|
+
**No `webServer` dependency**: the host half registers its routes on DSH Connection's shared `/api` channel through
|
|
417
|
+
`ctx.connection.fetch.register`. In the web profile Connection mounts the `/api` prefix on webServer itself (with the
|
|
418
|
+
Host/Origin fence and browser auth); in the desktop profile `apps/desktop-host` feeds `/api/*` into the same
|
|
419
|
+
`createSharedFetchHandler('/api')` (IPC framed pipe, no HTTP server). The client only writes relative paths
|
|
420
|
+
(`fetch('/api/code-server/<op>')`), so both carriers behave identically.
|
|
421
|
+
|
|
422
|
+
| Method | Path | Description |
|
|
423
|
+
|---|---|---|
|
|
424
|
+
| GET | `/api/code-server/status` | `{ ok, running, status, host, port, pid, cwd, url, version, error, logTail, adopted }` (also `env` environment check and the `setup` compatibility field) |
|
|
425
|
+
| POST | `/api/code-server/start` | body `{ cwd? }` (omit cwd to keep the current workspace); idempotent |
|
|
426
|
+
| POST | `/api/code-server/stop` | Stop and recycle the process tree |
|
|
427
|
+
| POST | `/api/code-server/setup` | **Compatibility no-op**: since 0.1.36 dependencies are installed by the package manager, so this only re-runs the env self-check and returns |
|
|
428
|
+
| POST | `/api/code-server/open-file` | body `{ file }` — writes the signal consumed by the built-in `dshcs-open-file` extension to open the file in code-server |
|
|
429
|
+
|
|
430
|
+
> The plugin no longer registers `/code-server/*` webServer-only routes, and the code-server icon is inlined as a data URI
|
|
431
|
+
> in the client bundle — the client requests no plugin-owned HTTP resource at all.
|
|
432
|
+
|
|
433
|
+
## DSH Desktop (no webServer)
|
|
434
|
+
|
|
435
|
+
- The host half is `inject = ['connection', 'settings']` (**no `webServer`**) — the desktop profile disables webserver/web-runtime
|
|
436
|
+
and the plugin still works: `/api/*` requests travel Electron `dsh-app://` protocol handler → IPC framed pipe → `createSharedFetchHandler('/api')`.
|
|
437
|
+
- The right-sidebar tab, guide entry box, file-address claim, and settings card behave the same as in web (code-server remains an
|
|
438
|
+
iframe to the local `http://127.0.0.1:<port>`; the desktop renderer uses `webSecurity: true` with no CSP, so the cross-origin iframe loads).
|
|
439
|
+
The desktop build ships `dsh-client-ui-sidebar-right` in its seed package set as well, so the 0.2.3 "right-sidebar DSH only" rule is
|
|
440
|
+
not a regression for desktop; the only difference is the missing `webServer`, where `serve: dsh` falls back to loopback.
|
|
441
|
+
- Install into the desktop profile through the **desktop plugin manager** (not the CLI, see below).
|
|
442
|
+
- **Desktop installs face a 24-hour supply-chain policy (measured 2026-09-10; this is how 0.2.4 got installed)**:
|
|
443
|
+
- the CLI path is unavailable: `dsh plugin --profile desktop …` is rejected (*"profile "desktop" is managed exclusively by the
|
|
444
|
+
Electron application"*), so desktop installs only go through the app's package transaction (`pnpm add <spec> --save-exact`,
|
|
445
|
+
executed in `~/.dsh/desktop/staging/<uuid>/profile` before activation);
|
|
446
|
+
- that transaction's pnpm (the app bundles **11.7.0**, patched by DeepSeek) runs a **lockfile supply-chain verification** before
|
|
447
|
+
`add` ("Verifying lockfile against supply-chain policies (717 entries)") which requires packages to be **at least 24 h old**,
|
|
448
|
+
otherwise `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`;
|
|
449
|
+
- **the two stages behave differently (measured)**:
|
|
450
|
+
- verifying an **existing lockfile**: `minimumReleaseAgeExclude` is *not* honoured (exact versions and bare package names
|
|
451
|
+
were both tried);
|
|
452
|
+
- **resolution** (no lockfile to verify, e.g. after `pnpm clean --lockfile`): the list *is* honoured, and pnpm even appends
|
|
453
|
+
entries itself (the install log prints *"Added N entries to minimumReleaseAgeExclude…"*);
|
|
454
|
+
- so the working recipe for a **just-published** (<24 h) version on desktop is to start from a clean, lockfile-free profile:
|
|
455
|
+
1. `pnpm clean --lockfile` (**note: it also deletes `node_modules`**, leaving the profile to be reinstalled);
|
|
456
|
+
2. with the app's bundled runtime, run `add <spec> --save-exact --trust-lockfile` in the profile directory
|
|
457
|
+
(runtime/store/config live under `~/.dsh/desktop/pnpm/{store,cache,state,config,home}`,
|
|
458
|
+
`--config.userconfig=…/config/npmrc`, otherwise pnpm fails with `ERR_PNPM_UNEXPECTED_STORE` /
|
|
459
|
+
`…UNEXPECTED_VIRTUAL_STORE`);
|
|
460
|
+
3. the app's boot command (`install --offline --frozen-lockfile --trust-lockfile`) then passes (lockfile matches
|
|
461
|
+
package.json, packages are in the store); if the plugin name is already in `dsh.profile.bundles` nothing else is needed.
|
|
462
|
+
- do **not** try to bypass it with `minimumReleaseAge: 0`: it does clear the check, but that key is **not** one of the policy
|
|
463
|
+
sections the app tolerates (`project-manager.ts` ignores only `minimumReleaseAgeExclude:` / `trustPolicyExclude:` and validates
|
|
464
|
+
the manifest before every `mutate()`), so persisting it makes the app fail with
|
|
465
|
+
*"core package mapping does not match desktop-packages.json"*.
|
|
466
|
+
- Alternatively **wait out the 24 h** and install normally from the plugin manager. The web profile is unaffected: its
|
|
467
|
+
`pnpm-workspace.yaml` sets `minimumReleaseAge: false`.
|
|
468
|
+
- this plugin's closure contains platform native sub-packages (`@jinsiyu/dsh-code-server-runtime-win32-*`) published together with
|
|
469
|
+
the plugin itself, so every new version hits that policy on desktop.
|
|
470
|
+
- **Desktop client bundles are cached by Electron; restarting the app does not guarantee a new one** (measured 2026-09, hit while shipping 0.2.5):
|
|
471
|
+
- symptom: `lib/client.js` in the profile is the new version, yet the renderer keeps running the old code —
|
|
472
|
+
`%APPDATA%\@deepseek-ai\dsh-desktop\Code Cache\js` only contains strings unique to the old version (e.g. `dshcs-artifacts`)
|
|
473
|
+
and none unique to the new one (`fileOpenScope`), and `Cache\` still holds an old response body referencing
|
|
474
|
+
`dsh-code-server-app`. The same applies to first-party plugins (the cached `ui-deliverables` even lacks the current
|
|
475
|
+
`data-presented-files-row` marker);
|
|
476
|
+
- diagnosis (byte level — do **not** use `Select-String`, which reads files with the console encoding and gives false
|
|
477
|
+
negatives on non-ASCII markers): search `Code Cache\js` for an **ASCII** marker unique to the new version
|
|
478
|
+
(ours is `fileOpenScope`); a hit proves the new bundle really was compiled;
|
|
479
|
+
- fix: fully close the app, delete the `Cache`, `Code Cache` and `GPUCache` directories, then start it (cache only —
|
|
480
|
+
profiles, sessions and settings are untouched):
|
|
481
|
+
```powershell
|
|
482
|
+
Remove-Item -Recurse -Force "$env:APPDATA\@deepseek-ai\dsh-desktop\Cache","$env:APPDATA\@deepseek-ai\dsh-desktop\Code Cache","$env:APPDATA\@deepseek-ai\dsh-desktop\GPUCache"
|
|
483
|
+
```
|
|
484
|
+
- scope: this is not specific to this plugin — **any** client plugin may keep running old code after an upgrade;
|
|
485
|
+
after a release, confirm with the marker trick above that the renderer actually swapped bundles.
|
|
486
|
+
|
|
487
|
+
## File-open plumbing (what the plugin itself still does)
|
|
488
|
+
|
|
489
|
+
DSH's own chips and preview buttons are what users click (see "Opening files" above); this plugin adds no row of its own.
|
|
490
|
+
What remains on the plugin side:
|
|
491
|
+
|
|
492
|
+
- the tab body parses `navigation.address` and posts the absolute path (plus an optional `line`) to
|
|
493
|
+
`/api/code-server/open-file`, which writes a signal file;
|
|
494
|
+
- the bundled `dshcs-open-file` extension polls that file and calls `showTextDocument` — VS Code Web has no official
|
|
495
|
+
"open this file from outside" API, so this is the only way to aim the workbench at a file. It is installed as a
|
|
496
|
+
**built-in** extension (in `lib/vscode/extensions`), so users cannot remove it from the extensions panel, and the
|
|
497
|
+
installer re-syncs it whenever its content changes.
|
|
498
|
+
|
|
499
|
+
## Known limitations
|
|
500
|
+
|
|
501
|
+
- ~~No sub-path~~ **no longer true (corrected with measurements in 0.2.0)**: the workbench HTML VS Code renders references
|
|
502
|
+
**only relative URLs** (9 references measured, 0 absolute; `serverBasePath="."`, `rootEndpoint="."`), and the client
|
|
503
|
+
builds its WebSocket path from `location.pathname + join(serverBasePath ?? '/', <quality>-<commit>)`. The IDE can
|
|
504
|
+
therefore be mounted directly under DSH's own `/code-server/*` (`serve: dsh`) — no second port, no HTML rewriting.
|
|
505
|
+
Item-by-item evidence: `docs/analysis-code-server-as-dsh-plugin.md`.
|
|
506
|
+
- **`serve: dsh` cannot proxy forwarded-port WebSockets**: `registerUpgrade` matches exact paths while `/proxy/:port`
|
|
507
|
+
carries the port in the path, so WebSocket forwarding for the Ports panel is unavailable in that mode (HTTP forwarding
|
|
508
|
+
works). Use `serve: loopback` when you need it.
|
|
509
|
+
- **`serve: dsh` shares DSH's origin**, so the iframe is not sandboxed there (same-origin plus `allow-same-origin` is
|
|
510
|
+
escapable by the frame itself); in `loopback` mode the iframe is cross-origin and `sandbox` stays as real protection.
|
|
511
|
+
- **Single instance across sessions**: one shared IDE per host; switching cwd requires a restart (the sidebar tab
|
|
512
|
+
handles it and hints).
|
|
513
|
+
- **Older DSH versions are unsupported (since 0.2.3)**: on a DSH without `sidebarRightTabs` / `sidebarRight` the plugin
|
|
514
|
+
offers nothing but an upgrade notice on the settings page; older-DSH users should stay on `0.2.2`
|
|
515
|
+
(`dsh plugin --profile web add dsh-code-server-app@0.2.2`).
|
|
516
|
+
- **Sidebar tab switching** (no longer reloads since 0.2.2): DSH's right sidebar renders only the active tab's body, and
|
|
517
|
+
a React unmount moves the iframe away; the plugin keeps it as a singleton resident surface and shuttles it between the
|
|
518
|
+
dock slot and a document-level park container with `Element.moveBefore()` (a state-preserving atomic move), so
|
|
519
|
+
switching tabs or collapsing the sidebar and back **does not reload** it. Browsers without `moveBefore` fall back to
|
|
520
|
+
the old behaviour (`appendChild` → full reload), reported as `degraded`; see "Why switching tabs no longer reloads".
|
|
521
|
+
- **Remote access**: with `serve: dsh` the browser only needs to reach DSH itself (one port, protected exactly like `/api`);
|
|
522
522
|
`serve: loopback` stays loopback-only with `auth: none`, and 0.2.0 no longer supports `auth: password`.
|