dsh-code-server-app 0.3.57 → 0.3.59

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 CHANGED
@@ -12,10 +12,12 @@
12
12
 
13
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-*` sub-packages declared **directly on the plugin's dependency table** under their real names (os/cpu-gated per target), with the original import names restored by runtime junctions. 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
14
 
15
- > Since 0.3.22 the ask panel renders the session's new content with **DSH's own Markdown renderer** (the same
16
- > renderer and design tokens as the DSH UI; new content only) and can **answer approval requests in place**
17
- > (writing outside the workspace / running commands). See "Working with DSH: the editor bridge" and section 21 of
18
- > `docs/analysis-code-server-as-dsh-plugin.md`.
15
+ > **The "ask DSH" dialog** renders the session's new content with **DSH's own Markdown renderer** and can
16
+ > **answer approval requests in place** (writing outside the workspace / running commands). The panel is a
17
+ > hand-written React component inside `lib/client.js`: it requires `react-dom/client` and
18
+ > `@deepseek-ai/dsh-client-ui-primitives` straight from the DSH page's module table (the very instance the UI
19
+ > uses), so typography, highlighting and math match the UI and a renderer version mismatch is impossible.
20
+ > There is **no build step anywhere on that chain**. See "Working with DSH: the editor bridge".
19
21
 
20
22
  ## UI carrier and required DSH version (0.2.3: right-sidebar DSH only)
21
23
 
@@ -105,7 +107,7 @@ This plugin registers:
105
107
  `canOpen` share that single file, shipped in the package, so the two cannot drift apart);
106
108
  unit tests: `scripts/test-claim-types.mjs`.
107
109
  - **How the tab body locates the file**: it parses `useTabInfo().tab.navigation.address`
108
- (`src/address.js`, same grammar as DSH's `parseFileAddress`), expands a workspace-relative path with that
110
+ (`lib/client.js`, the "address grammar" section — same grammar as DSH's `parseFileAddress`), expands a workspace-relative path with that
109
111
  session's cwd, and posts the absolute path (plus optional `line`) to the host's
110
112
  `/api/code-server/open-file`; the bundled extension (`dshcs-open-file`) then calls `showTextDocument`
111
113
  (positioned at the line when given).
@@ -119,8 +121,8 @@ This plugin registers:
119
121
  (2) only a tab that becomes visible for the first time consolidates — tab restoration/activation order is not
120
122
  ours to control, and letting every visible tab close its siblings would ping-pong (a `ref` pins "once per mount").
121
123
  These tabs always shared **one resident workbench** (the IDE is a single instance), so closing one never reloads
122
- it: the resident iframe is owned by `src/surface.js`, and a tab is merely its docking host (`Element.moveBefore`).
123
- Regression: `scripts/test-client-bundle-tabs.mjs` renders two tabs against the **built bundle** and asserts the
124
+ it: the resident iframe is owned by the "resident IDE surface" section of `lib/client.js`, and a tab is merely its docking host (`Element.moveBefore`).
125
+ Regression: `scripts/test-client-bundle-tabs.mjs` renders two tabs against the **entry file** and asserts the
124
126
  old one is closed, the new one stays, other panes/hidden tabs are untouched, and an old DSH without `actions`
125
127
  does not throw (negative control: dropping the consolidation call makes it FAIL).
126
128
  - **Why the bundled extension stays**: VS Code Web has no official "open this file from outside" API (the only
@@ -135,7 +137,7 @@ This plugin registers:
135
137
  iframe out of the document and destroys its browsing context; switching back is a full VS Code reload (unsaved buffers
136
138
  lost). Floating the tab into its own panel only worked around it.
137
139
 
138
- **What it does now (`src/surface.js` in the client, 0.2.2)**: the plugin takes the iframe **away from React** and turns
140
+ **What it does now (the resident-surface section of `lib/client.js`, 0.2.2)**: the plugin takes the iframe **away from React** and turns
139
141
  it into a **singleton resident surface**:
140
142
 
141
143
  | Situation | Action | Result |
@@ -225,62 +227,70 @@ only the editor knows, and lets editor gestures drive the current session.
225
227
  | Direction | Capability | Mechanism |
226
228
  |---|---|---|
227
229
  | editor → agent | **unsaved buffers** (disk ≠ what the user sees), active file and selection, **language-server diagnostics** with `file:line`, source and code | agent tools `editor_context` / `editor_diagnostics`; plus a notice attached before writing a dirty file |
228
- | editor → DSH | select code → context menu **"DSH: ask about selection"** → an **ask panel** opens (carrying `file:line` and the selection); the question enters the current session as **user input**, and that session's **new content** is rendered in the panel by DSH's own Markdown renderer | extension command `dsh-code-server.askAboutSelection` (one of the **top two** editor context-menu items) + a webview panel + `POST /ask` + the `thread` field of `/sync` |
229
- | DSH → editor (approval) | when the agent wants to **write outside the workspace or run a command**, the approval request shows up as a card in the panel (tool, reason, countdown); "allow once" / "reject" takes effect immediately | the `approvals` field of `/sync` + `POST /approve` (the bridge's **only** non-read-only route; constraints under "Security model") |
230
+ | editor → DSH | select code → context menu **"DSH: ask about selection"** → an **ask dialog** opens in the DSH page (its title bar carries `file:line`); the question enters the current session as **user input**, and that session's **new content** is rendered in the dialog by DSH's own Markdown renderer | extension command `dsh-code-server.askAboutSelection` (one of the **top two** editor context-menu items) → bridge `POST /event {kind:'ask-open'}` → the client half's `POST /api/code-server/ask/send` |
231
+ | DSH → editor (approval) | when the agent wants to **write outside the workspace or run a command**, the approval request shows up as a card in the dialog (tool, reason, countdown); "allow once" / "reject" takes effect immediately | the `approvals` field of `/api/code-server/ask/state` + `POST /api/code-server/ask/approve` (the plugin's **only** write route; constraints under "Security model") |
230
232
  | agent → editor | the agent changed a file → a **native diff** opens (left = the **full pre-write text**, right = what is on disk now); if that buffer has unsaved changes you get a warning and **no overwrite** | the host reads `result.value.before` (the complete pre-write text) in `tools/post-execute` into a bounded snapshot cache → the `tools/result` event carries an opaque key → the extension fetches the text and opens the diff |
231
233
 
232
234
  - The tools are only registered while the bridge is live (so the model never sees an unusable tool), and the
233
235
  system-prompt section renders only then too.
234
- - **Ask panel** (extension 0.2.0; the official renderer since 0.2.3; a **floating dialog over the DSH UI since
235
- 0.2.5**): the context-menu command no longer opens a panel inside the editor (that is always a tab or a column,
236
- never a dialog) — the plugin's client half pops up a draggable, resizable floating chat window in the DSH page
237
- itself (bottom-right, ✕ closes it), leaving the editor layout alone. Hosts without that capability fall back to
238
- the in-editor webview panel. The selection can still be changed while the dialog stays open.
239
- The two commands **remember their intent** (0.3.21): "ask about selection" carries a **line range + selection text**
240
- only when something is actually selected, while "ask about file" **never carries line numbers or a selection** — the
241
- cursor line is irrelevant to the question and only misleads the agent. With no selection, the selection command also
242
- degrades to the plain file.
243
- - **Injected context is collapsed** (0.3.24): the location line plus the selection code block the bridge adds to the
244
- message are split out into a collapsed Context row (click it to see the code), while the bubble keeps only the user's
245
- own words — the same treatment the DSH UI gives injected context.
246
- - **The panel renders exactly what DSH renders** (0.3.22): the panel bundles DSH's official Markdown renderer
247
- (`MarkdownText` from `@deepseek-ai/dsh-client-ui-primitives`) plus the official design tokens — the same
248
- micromark/mdast pipeline, the same incremental streaming parser, the same shiki highlighting (boot set:
249
- typescript / shellscript / json), KaTeX math and the same heading/table typography. Only **new content** is
250
- rendered (from the moment the panel subscribes); history is **not replayed** and there is no "load earlier".
251
- - **Thinking shows up like in DSH** (0.3.23): assistant reasoning becomes a Think row — **collapsed by default**,
236
+ - **Ask dialog**: the context-menu command only reports its intent to the host; the dialog itself is popped up by
237
+ the plugin's client half inside the DSH page — draggable, resizable (bottom-right, ✕ closes it), leaving the
238
+ editor layout alone. The selection can still be changed while the dialog stays open.
239
+ When the host cannot prove the dialog is alive (page not open / browser still running an old client) you get a
240
+ one-line notice telling you to open or refresh the Code Server tab — there is **no second ask UI** in the editor.
241
+ - **The two commands remember their intent**: "ask about selection" carries a **line range + selection text**
242
+ only when something is actually selected, while "ask about file" **never carries line numbers or a selection** —
243
+ the cursor line is irrelevant to the question and only misleads the agent. With no selection, the selection
244
+ command also degrades to the plain file. The host takes that context from its cached editor state; the extension
245
+ only reports the intent.
246
+ - **Follow-ups are delivered according to DSH's own setting**: `ui-conversation.busyEnter` (Settings → Conversation,
247
+ "Enter while busy") accepts `queue` (the default) or `steer`. Pressing Enter in the dialog is the same gesture as
248
+ pressing Enter in the main composer, so it reads the same value: `steer` ⇒ the host calls `agent.steer()` and the
249
+ follow-up is consumed at the **running turn's next step boundary** (answered within that turn); `queue` ⇒ the host
250
+ calls `agent.followup()` and the question becomes **its own later turn**, leaving the running one alone. If the
251
+ setting cannot be read (namespace unregistered / minimal composition) or the host has no `agent.steer`, delivery
252
+ falls back to `queue` — a setting never makes a question undeliverable. The panel's status row **says which one was
253
+ used** ("inserted into the current turn…" / "queued for the next turn…"), because while `queue` is in effect the
254
+ **DSH main UI cannot show that message yet**: it sits in the host-side pending queue (`next-turn`) and the main
255
+ client does not render pending queues (it joins the chat flow only once it becomes its own turn). The message is
256
+ not lost.
257
+ - **Injected context is collapsed**: the location line plus the selection code block the bridge adds to the
258
+ message are split out into a collapsed Context row (click it to see the code), while the bubble keeps only the
259
+ user's own words — the same treatment the DSH UI gives injected context.
260
+ - **The body is exactly what DSH renders**: it is handed to DSH's official Markdown renderer
261
+ (`MarkdownText` from `@deepseek-ai/dsh-client-ui-primitives`) — the same micromark/mdast pipeline, the same
262
+ incremental streaming parser, the same shiki highlighting (DSH's own lazily-loaded grammar set), KaTeX math and
263
+ the same heading/table typography. Only **new content** is rendered (from the moment the dialog subscribes);
264
+ history is **not replayed** and there is no "load earlier". If the official components cannot be resolved the
265
+ body degrades to plain-text `<pre>` instead of a blank panel.
266
+ - **Thinking shows up like in DSH**: assistant reasoning becomes a Think row — **collapsed by default**,
252
267
  showing its first line (or the latest line while streaming) and expanding on a row click, built from the official
253
268
  `DisclosureRow` plus the official think icon and typography language.
254
- - **Approvals are handled right in the panel** (0.3.22; window fixed in 0.3.23): while the panel is open, that
255
- session's approval requests ask the panel first (5 minutes by default). Clicking "allow once" / "reject" settles it
256
- immediately; **closing the panel** or letting the window expire hands the request back **unchanged** to the official
257
- path (the DSH UI shows the same card). 0.3.22's 8-second window was far too short for a human — the buttons went
258
- grey before anyone could click (reported as "the approval box stopped working"); the window is now 5 minutes and
259
- closing the panel hands off immediately instead of waiting it out.
269
+ - **Approvals are handled right in the dialog**: while it is open, that
270
+ session's approval requests ask the dialog first (5-minute window). Clicking "allow once" / "reject" settles it
271
+ immediately; **closing the dialog** or letting the window expire hands the request back **unchanged** to the official
272
+ path (the DSH UI shows the same card).
260
273
  **Nothing is ever auto-approved** — `allowed-once` can only come from a click, and there is no "always allow".
261
- - The question enters the DSH session as a **plain user message** (`source: { kind: 'user' }`, host 0.3.19): earlier
262
- versions used `{kind:'plugin'}`, which DSH renders as a *context update* — it did not look like something the user
263
- said. Provenance stays in the first line of the text: `From the editor: <file>[:<line>]`.
264
- - **Read-only with one constrained exception**: the bridge never writes files, applies edits, or runs commands; the
265
- single non-read-only route is `POST /approve`, which can only **answer an approval request that already exists**
266
- (see invariant 2 below). The agent's writes still go through its own `fs` tools; the bridge only *knows about* them
267
- and carries your answer back.
274
+ - The question enters the DSH session as a **plain user message** (`source: { kind: 'user' }`): provenance stays in
275
+ the first line of the text (`From the editor: <file>[:<line>]`), and the panel folds it into the Context row.
276
+ - **The bridge is completely read-only**: it never writes files, applies edits, or runs commands — all four routes
277
+ (`/health`, `/sync`, `/old`, `/event`) are reads. The only route that can change state is the DSH-same-origin
278
+ `POST /api/code-server/ask/approve`, which can only **answer an approval request that already exists**
279
+ (see invariant 2 below). The agent's writes still go through its own `fs` tools; the bridge only *knows about*
280
+ them and carries your answer back.
268
281
  - Status bar shows `$(plug) DSH` while connected (click it for the log in the "DSH Editor Bridge" output channel).
269
- - **The extension ships as a built-in** (fixed in 0.3.12): `dshcs-editor-bridge` is installed into
270
- `<tree>/lib/vscode/extensions/` next to `dshcs-open-file`. 0.3.0–0.3.11 installed it as a *user* extension
271
- instead, and the VS Code server marks any extension that sits in the user extensions folder but in no profile
272
- manifest as removed (`.obsolete`, log line `Marked extension as removed`) and then skips it forever — re-marked on
273
- every start, so **the bridge never reported any state**. To turn the bridge off use the plugin setting
274
- `editorBridge=false` (no mount, no tools) rather than uninstalling the extension from the Extensions view.
282
+ - **The extension ships as a built-in**: `dshcs-editor-bridge` is installed into
283
+ `<tree>/lib/vscode/extensions/` next to `dshcs-open-file` — an extension left in the *user* extensions folder is
284
+ marked `.obsolete` (log line `Marked extension as removed`) by the VS Code server and skipped forever.
285
+ To turn the bridge off use the plugin setting `editorBridge=false` (no mount, no tools).
275
286
 
276
287
  ### The channels (since 0.3.13 over **local IPC**: a Windows named pipe / unix socket)
277
288
 
278
289
  ```
279
- extension → host POST /code-server-bridge/sync one round trip: push editor state (+ which session the panel watches) + take events and thread deltas
280
- extension → host POST /code-server-bridge/ask push an editor question into the current session
281
- extension → host POST /code-server-bridge/approve answer an approval request that **already exists** (the only non-read-only route)
290
+ extension → host POST /code-server-bridge/sync one round trip: push editor state + take events and the capability bit
282
291
  extension → host GET /code-server-bridge/health unauthenticated liveness probe
283
- extension → host POST /code-server-bridge/event extension reports open/close etc. (host log tail)
292
+ extension → host GET /code-server-bridge/old fetch one "pre-write text" snapshot (events carry an opaque key)
293
+ extension → host POST /code-server-bridge/event report intent: open the dialog / open·close a file etc. (host log tail)
284
294
  host → extension <extensionsDir>/.dshcs-bridge/bridge.json endpoint + token, re-read every 5s
285
295
  (the same content is also written **next to the built-in extension** in
286
296
  `<tree>/lib/vscode/extensions/.dshcs-bridge/` — the env var is only injected when the host
@@ -289,14 +299,19 @@ host → extension <extensionsDir>/.dshcs-bridge/bridge.json endpoint + toke
289
299
  ```
290
300
 
291
301
  Requests use `http.request({ socketPath })` (`fetch` has no socket support) and **no port is ever opened**.
302
+ All four routes are **read-only**; questions and approval answers do not go through the bridge but through the
303
+ DSH-same-origin `/api/code-server/ask/*` (called by the plugin's client half inside the DSH page, under DSH's own
304
+ cookie/Origin checks).
292
305
 
293
- The three `/sync` fields the panel actually consumes (0.3.22):
306
+ The four state fields the dialog actually consumes (`GET /api/code-server/ask/state?rev=N`; an unchanged revision
307
+ returns a single number):
294
308
 
295
309
  | Field | Content | How the panel uses it |
296
310
  |---|---|---|
297
- | `thread` | **new content** entries (user / assistant / tool / approval) of the session the panel watches; bounded: ≤120 entries per session, ≤8000 chars per body, ≤4 watched sessions | assistant bodies go to the official renderer; tools and approvals become compact summary rows |
298
- | `approvals` | pending approval requests `[{id, toolName, reason, at}]` (≤4) | renders the card with a countdown; a click posts `/approve` |
299
- | `approvalHoldMs` / `uiVersion` | the approval window (300000 ms = 5 minutes by default) / the DSH UI version | countdown basis; a renderer-version mismatch is surfaced in the panel |
311
+ | `entries` | **new content** entries (user / assistant / tool / approval) of the session the dialog watches; bounded: ≤120 entries per session, ≤8000 chars per body, ≤4 watched sessions | assistant bodies go to the official renderer; tools and approvals become compact summary rows |
312
+ | `approvals` | pending approval requests `[{id, toolName, reason, at}]` (≤4) | renders the card with a countdown; a click posts `/ask/approve` |
313
+ | `approvalHoldMs` | the approval window (300000 ms = 5 minutes by default) | countdown basis |
314
+ | `contextText` / `mode` | the title line (from the host's cached editor state) plus the ask intent | title text; `mode` decides whether line numbers / the selection travel with the question |
300
315
 
301
316
  > **Why not HTTP (settled in 0.3.13, all three measured)**
302
317
  > 1. **Desktop has no HTTP surface at all**: the renderer calls `host.fetch()` through Electron IPC
@@ -355,7 +370,7 @@ user**, so:
355
370
  (d) when no panel is watching, the panel is closed, or the window (5 minutes by default) expires, the request goes
356
371
  **back to the official
357
372
  path** — never auto-approved (DSH's `approval/request` itself fails closed; this bridge can only keep
358
- "nobody answered" as "nobody answered"). `pnpm test:webview` asserts these four plus the host-side whitelist.
373
+ "nobody answered" as "nobody answered"). `pnpm test:ask-dialog` asserts these four plus the host-side whitelist.
359
374
  3. **Any request carrying `Origin` gets 403.** Browsers always send one (including a sandboxed iframe's literal
360
375
  `Origin: null`); the Node extension host never does. Origin is checked **before** the token — otherwise the
361
376
  bridge would be a "did you guess the token right" oracle for a web page.
@@ -404,8 +419,8 @@ Diagnostics: `GET /api/code-server/status` exposes
404
419
  - code-server's workspace **follows the active DSH session/workspace**: switching sessions/workspaces while the IDE is open moves code-server to the new directory
405
420
  (resolution order: current session cwd → session's `workspace.path` → workspace of the most recently active session → first workspace.path;
406
421
  **where "the current session" comes from depends on the DSH version**: ≥ 0.1.6-alpha.2 reads the session-scoped standard prop `sessionId`,
407
- ≤ 0.1.6-alpha.1 falls back to `current` on the session-list snapshot — see the 0.3.48 bullet below; the logic lives in `src/workspace.js`
408
- and the contract for both shapes is pinned by `scripts/test-client-bundle-cwd.mjs` directly against the built bundle);
422
+ ≤ 0.1.6-alpha.1 falls back to `current` on the session-list snapshot — see the 0.3.48 bullet below; the logic is inlined in `lib/client.js`
423
+ (the "workspace resolution" section) and the contract for both shapes is pinned by `scripts/test-client-bundle-cwd.mjs` directly against the entry file);
409
424
  the opened directory is shown inside code-server (`?folder=<cwd>`, the page reloads when following a switch);
410
425
  implementation note: the iframe `src` must carry `?folder=<cwd>` — code-server's front-end remembers the "last workspace" and restores it by itself;
411
426
  a bare root URL only shows the previously opened directory and does not follow switches (verified locally).
@@ -437,8 +452,9 @@ Diagnostics: `GET /api/code-server/status` exposes
437
452
  - 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),
438
453
  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;
439
454
  - `node_modules` and the pack-time artifact `vendor/` are git-ignored; after cloning, follow
440
- "Install the plugin (script-free install; code-server bundled)" below — `pnpm install` → `pnpm run build:client` →
441
- `pnpm run vendor:vscode` → `pnpm pack` + `dsh plugin --profile web add`.
455
+ "Install the plugin (script-free install; code-server bundled)" below — `pnpm install` → `pnpm run vendor:vscode` →
456
+ `pnpm pack` + `dsh plugin --profile web add` (after 0.3.58/0.3.59 the client half **and** the ask panel are
457
+ committed hand-written source: there is no build step anywhere on that chain).
442
458
 
443
459
  > Verified locally (BM: Windows 11 ARM64): the whole tree/dependency chain hangs directly off the plugin's
444
460
  > dependency table — the tree package `@jinsiyu/dshcs-vscode-server` (currently 4.137.0, a 50.8 MB tarball),
@@ -452,14 +468,12 @@ Diagnostics: `GET /api/code-server/status` exposes
452
468
 
453
469
  ```powershell
454
470
  cd C:\Users\User\Desktop\dsh-code-server-app
455
- pnpm install # dev deps (esbuild + the official-renderer bundling deps); allowBuilds is explicit → no postinstall runs
456
- pnpm run build:client # src/factory.js → lib/client.js (not committed; must be built first)
457
- pnpm run build:webview # ask panel: official Markdown renderer + panel shell → webview/thread.{js,css} (not committed; must be built first)
471
+ pnpm install # one dev dependency left (@deepseek-ai/schemastery); allowBuilds is explicit → no postinstall runs
458
472
  pnpm run vendor:check # optional: show the bundled tree version vs the latest code-server release
459
473
  pnpm run vendor:vscode # ① produce vendor/vscode (the trimmed VS Code tree, ~197MB)
460
474
  pnpm run repack:build -- --target win32-arm64,win32-x64 --pack # ② one script builds every sub-package
461
475
  pnpm run publish:repacks # ③ publish every @jinsiyu/* sub-package (default dist-tag: next)
462
- pnpm pack # ④ → dsh-code-server-app-<version>.tgz (~750KB, including the panel renderer assets)
476
+ pnpm pack # ④ → dsh-code-server-app-<version>.tgz
463
477
  pnpm run publish:plugin # ⑤ publish the plugin itself (default dist-tag: next)
464
478
  # once the user has restarted dsh web and confirmed it works, promote latest:
465
479
  pnpm run promote -- <version>
@@ -474,13 +488,11 @@ pnpm run promote -- <version>
474
488
  > so their dist-tags do not affect resolution, but they default to `next` as well.
475
489
  > Inspect the current tags with `npm dist-tag ls dsh-code-server-app`.
476
490
 
477
- > `build:webview` bundles DSH's **official** Markdown renderer and design tokens into the panel assets
478
- > (~1.34MB: 996KB JS + 87KB CSS + 254KB KaTeX fonts), so it needs a local DSH deployment: the script reads the
479
- > `@deepseek-ai/dsh-web-frontend` version from that deployment and compares it with the renderer version pinned in
480
- > devDependencies — a mismatch **fails the build** (unless `--allow-version-mismatch`). Same convention as
481
- > `lib/client.js`: the artifacts are not committed and `prepack` rebuilds them.
482
- > Rationale (why not an iframe, where the tokens come from, size trade-offs) is section 21 of
483
- > `docs/analysis-code-server-as-dsh-plugin.md`.
491
+ > **There is no build step on this chain**: `lib/client.js` — including the "ask DSH" dialog panel — is committed
492
+ > hand-written source, and so is the extension (plain JS). `prepack` is down to `vendor:vscode`, the published
493
+ > package carries no frontend bundle, and `devDependencies` holds exactly one entry (`@deepseek-ai/schemastery`,
494
+ > used by the tests).
495
+ > The per-version analysis of the panel and the DSH page lives in `docs/analysis-code-server-as-dsh-plugin.md`.
484
496
 
485
497
  `repack:build` (`scripts/vendor-repacks.mjs`) is the **single script that produces every sub-package**:
486
498
 
@@ -514,7 +526,7 @@ Both workflows live in `.github/workflows/`, and the regression list exists exac
514
526
 
515
527
  | Workflow | Trigger | What it does |
516
528
  |---|---|---|
517
- | `ci.yml` | push to `main` / PR / manual | `ubuntu-latest` + `windows-latest` matrix: `pnpm install --frozen-lockfile` → `build:client` → `build:webview` → `pnpm test` (the whole suite) → `vendor:check` (report only) → upload `lib/client.js` and the panel assets |
529
+ | `ci.yml` | push to `main` / PR / manual | `ubuntu-latest` + `windows-latest` matrix: `pnpm install --frozen-lockfile` → `pnpm test` (the whole suite; since 0.3.59 there is **no build step in front of it**) → `vendor:check` (report only) |
518
530
  | `release.yml` | push a `v<version>` tag / manual (rehearsal, never publishes) | prepares `vendor/vscode` **at the version pinned in `dependencies`** → builds → full suite → `pnpm pack` → verifies the tarball manifest → **really installs it twice** (windows-latest proves the 16 win32 sub-packages, ubuntu-latest the 10 Linux ones: each deploys a real DSH, installs via the official path `dsh plugin --profile web add <tgz>`, then runs the `test:installed` + `dump-config` assertions; both legs must pass before anything is published) → publishes to npm **`next`** → creates a GitHub Release with the tgz attached |
519
531
  | `linux-repack-probe.yml` | push to this file / manual | **feasibility probe (never publishes; superseded by the Linux legs of `repacks.yml`)**: on Linux, builds the platform-specific repack packages per target (`linux-x64` → `ubuntu-latest`, `linux-arm64` → `ubuntu-24.04-arm`) and reports which modules really produce a `.node` and which are Windows-only. It runs the existing `vendor-repacks.mjs` itself; all writes happen in a copy of the repo under `$RUNNER_TEMP`. **Note**: it emits one notice per module, which hits GitHub's ~20-annotations-per-check-run cap and leaves only the tail; for the full verdict use the Linux legs of `repacks.yml` (one summary line per target) |
520
532
  | `repacks.yml` | manual (`publish` and `probe_oidc` both default to **false**, the four `build_*` legs default to **true**) / push to this file / push `.github/oidc-probe.enabled` | **builds and publishes the platform-specific sub-packages** (`@jinsiyu/dshcs-*`): one host-architecture runner per target (`win32-x64` → `windows-latest`, `win32-arm64` → `windows-11-arm`, `linux-x64` → `ubuntu-latest`, `linux-arm64` → `ubuntu-24.04-arm`); by default it only builds and uploads `repack/tgz/*.tgz`, and only publishes to npm (default `next`) when `publish` is checked. Ownership and ordering (**five legs, disjoint sets**): the `independent` leg runs **first** (windows-latest; it produces the **VS Code tree package + the 8 platform-independent repacks**, which are the same artifact for all four targets and are therefore published only once); the four platform-specific legs `needs: independent`, build with `--skip-independent` and publish with `--only <their own target>` ⇒ a broken base layer blocks the rest (no half-published state) and no package name is ever published twice. **Auth**: with no `NPM_TOKEN` it uses OIDC (per-package trust entries, all with workflow `repacks.yml` — see below). The Linux legs additionally verify that the `lib/vendored.json` / `package.json` they generate match the committed ones (the platform policy is meant to be host-independent). A `probe-oidc` job additionally does a **staged-only** probe of that OIDC route, so the channel can be proven without publishing anything real |
@@ -613,10 +625,15 @@ pnpm test:apply # apply() under a stub ctx
613
625
  pnpm test:claim-types # claim-type syntax and defaults
614
626
  pnpm test:bridge-routes # bridge route whitelist (read-only + /approve + /old) / Origin-vs-token order / token header agreement
615
627
  pnpm test:edit-snapshot # pre-write snapshots: value.before from tools/post-execute, session-cwd path resolution, triple-bounded cache, /old's 400-404-200
616
- pnpm test:bridge-extension # extension-side pure logic (dirty buffers, diagnostics, diff old-side priority, delivery, panel state)
617
- pnpm test:webview # panel bundle: official renderer + tokens, version match, the four /approve constraints
628
+ pnpm test:bridge-extension # extension-side pure logic (dirty buffers, diagnostics, diff old-side priority, delivery, ask intent + "dialog unavailable" notice)
629
+ pnpm test:ask-dialog # ask-dialog wiring: no artifacts/build chain left, the host's four ask routes, the extension only reporting editor state, the four approval constraints, the bridge's safety invariants
618
630
  pnpm test:launcher-routes # launcher HTTP surface (spawns a real process; slow)
619
631
  pnpm test:workspace-switch # switching workspaces does not restart the process
632
+ pnpm test:workspace-cwd # "current workspace directory" resolution (DSH 0.1.6-alpha.2 sessionId vs. the older current)
633
+ pnpm test:client-cwd # the same contract, but asserted against the **client entry** lib/client.js
634
+ pnpm test:client-tabs # "one code-server tab on the DSH side": a new tab closes the old one in the same pane
635
+ pnpm test:client-entry # client-entry guard: classic script + factory wrapper, require whitelist, src/ gone, parity with lib/claim-types.js
636
+ pnpm test:client-seat # which seat the settings card uses: plugins.bundle.config (DSH ≥ 0.1.6-alpha.2) vs settings.plugin.item (≤ alpha.1)
620
637
  pnpm test:fullscreen # opening the tab goes fullscreen
621
638
  pnpm test:vendored # repack table ↔ plugin dependency table (no npm: aliases, no aggregator)
622
639
  pnpm test:installed # install smoke: assert on what was **installed into a profile**
@@ -722,9 +739,6 @@ one itself (with no `NPM_TOKEN` it uses OIDC):
722
739
  probe versions (`npm stage list`, then `npm stage reject <id>`; needs 2FA on your machine) — do **not**
723
740
  approve, since approving is what would turn a probe into a real version. Delete the sentinel file to
724
741
  return to "no automatic probe".
725
- - **Optional** repository variable `DSH_UI_VERSION` = the version of `@deepseek-ai/dsh-web-frontend` in the current
726
- deployment: when set, `release.yml` enforces that the panel renderer matches the deployed UI (the local
727
- `build:webview` always checks this; a runner has no DSH deployment).
728
742
 
729
743
  Things you must know:
730
744
 
@@ -737,8 +751,9 @@ Things you must know:
737
751
  assertion). Packing happens only in `release.yml`, after
738
752
  `node scripts/vendor-vscode-server.mjs --version <pinned>`.
739
753
  - **Release gates** (any failure stops the run; `next` is never advanced): tag ≠ `package.json.version`, the
740
- version already exists on npm, the tree version does not match (`test:vendored`), the suite fails, or
741
- `DSH_UI_VERSION` mismatches.
754
+ version already exists on npm, the tree version does not match (`test:vendored`), the suite fails, or the
755
+ tarball manifest / the two real-install legs disagree. (0.3.59 dropped one gate: the panel renderer no longer
756
+ needs to match the deployed DSH UI — it *is* that instance.)
742
757
  - `@deepseek-ai/schemastery` is a **devDependency** (pinned to 3.18.2, the version the deployment uses):
743
758
  `lib/index.js` normally takes it from the DSH deployment (in production, the copy hoisted inside the
744
759
  profile), and a clean clone / CI runner has no DSH at all — without this devDependency the `apply`-style
@@ -837,6 +852,60 @@ The main package is only **~110KB** (the plugin's own code plus the launcher); e
837
852
  > `serve: loopback`, which behaves exactly like 0.1.43**; switch to `serve: dsh` for same-origin mounting. The install
838
853
  > command is unchanged (`dsh plugin --profile web add dsh-code-server-app@<version>`), and pnpm drops the old
839
854
  > `dshcs-code-server` sub-package.
855
+ ### Why the client half has no build step (since 0.3.58)
856
+
857
+ **`lib/client.js` *is* the source** — hand-written, committed, not minified. What was removed: `src/**`
858
+ (five ES modules), `scripts/build-client.mjs`, `client.banner.js` / `client.footer.js`, and the
859
+ `build:client` step in `prepack`/CI/release.
860
+
861
+ Why it can go away: DSH loads the client entry as a classic `<script src>` (`/plugins/<pkg>/client.js`), so it
862
+ **must be one file** in the `window.__ModuleLoader__.load({id, factory})` shape (no ES modules; package-local
863
+ splitting would need `require.async('client.*.js')`, which this plugin does not use). If the artifact can only be
864
+ a single file, it may as well be the source: no intermediate artifact, no bundler, and no "I forgot to rebuild".
865
+
866
+ Costs and rules (read before editing `lib/client.js`):
867
+
868
+ | Rule | Why | Enforced by |
869
+ |---|---|---|
870
+ | No top-level `import`/`export`/`await` | syntax errors in a classic script ⇒ the whole client half fails to load (empty UI) | `pnpm test:client-entry` E1 + the harness really loading it (E3) |
871
+ | `require(...)` may only name DSH module-table seed words (`react` / `react/jsx-runtime` / `react-dom/client` / `@deepseek-ai/dsh-client-ui-primitives`) | the DSH module table is frozen; anything else throws "unknown module" | E1 (and, when a local DSH install exists, it cross-checks the real `staticModules` list word by word) |
872
+ | Every section's top-level identifiers share one scope | after inlining, a `var`/`function` collision is a **silent overwrite** (real hit: `state` existed in both the surface and the plugin body — the former is now `surfaceState`) | no automatic guard ⇒ grep before adding a top-level name |
873
+ | The claim-types section is a **copy** | a classic script cannot reach the host module `lib/claim-types.js` | E2 compares both over a sample table |
874
+
875
+ Size: ~114 KB uncompressed (was a 49.7 KB minified artifact) — a one-time download; the rev/caching mechanism is
876
+ unchanged. Test hook: the entry exports `__internals` only when `window.__dshcsTestHooks === true` (used by
877
+ `test-workspace-cwd.mjs` / `test-sidebar-fullscreen.mjs` to call pure functions); DSH never sets that flag.
878
+
879
+ ### Why the "ask DSH" dialog has no build step
880
+
881
+ The dialog's panel (conversation stream, collapsible thinking rows, approval cards, input box) lives **inside
882
+ `lib/client.js`** — hand-written, committed, with no artifact, no `/ask/bundle`, no injected `<script>` and no fake
883
+ `acquireVsCodeApi`.
884
+
885
+ Why it can work that way: the dialog already runs **inside the DSH page**, and the shell's module table
886
+ (`staticModules` of `dsh-web-frontend`) freezes `react` / `react/jsx-runtime` / `react-dom` / `react-dom/client` /
887
+ `@deepseek-ai/dsh-client-ui-primitives` / … — so the panel simply requires them:
888
+
889
+ - the renderer, the design tokens, the KaTeX styles and the shiki grammars **all come from the page** (the same
890
+ instance the DSH UI uses) ⇒ typography matches the UI and a version mismatch is impossible;
891
+ - the panel creates its own React root via `require('react-dom/client')` (its container is the dialog's own div);
892
+ - opening the dialog has no "fetch + parse + execute" step to wait for — there is no artifact.
893
+
894
+ Costs and rules:
895
+
896
+ | Rule | Why | Enforced by |
897
+ |---|---|---|
898
+ | CSS injected into the page may only target `.dshcs-*` | the styles land in DSH's own document; touching `:root`/`body` would restyle the whole UI | `pnpm test:ask-panel` P3 (every selector + a ban on at-rules) |
899
+ | Every `var(--vscode-*)` needs a fallback | the DSH page has no `--vscode-*`; a bare `var()` is invalid at computed-value time ⇒ transparent buttons/inputs | P3 |
900
+ | A component with hooks may only be written as `React.createElement(Name, …)` | there is no JSX here; `Name({…})` puts the child's `useState` into the parent's hook chain, and a changed branch throws "Rendered more hooks than during the previous render" | P4 (source-level lookbehind regex) |
901
+ | Official components must be resolved as "function **or** `{$$typeof}` object" | `MarkdownText` is a `React.memo` product (an **object**); testing `typeof === 'function'` silently degraded every body to `<pre>` | P4's `askComponent` cases |
902
+ | Missing seed words / official components must degrade | the panel is the primary path; a blank panel means asking is broken | P5 (`<pre>` body, native `button`) + an error boundary |
903
+ | Three message routes (ask / approve / close) | panel and shell share one window (no postMessage), so messages must reach `/ask/send|approve|close` | P6 |
904
+
905
+ The extension side has no build step either: it is plain JS (`extension.js` + `lib/*.js`) and only reports intent.
906
+ Those two "no build step" claims are guarded by `pnpm test:client-entry` / `pnpm test:ask-panel` (the panel itself)
907
+ and `pnpm test:ask-dialog` (the wiring, plus "not one trace of the build chain may remain").
908
+
840
909
  ### Development: install from source (changes take effect immediately)
841
910
 
842
911
  ```powershell
@@ -848,11 +917,12 @@ dsh plugin --profile web add C:\Users\User\Desktop\dsh-code-server-app
848
917
  > too — but the not-yet-published local `@jinsiyu/*` packages must either be published first, or the
849
918
  > `repack/tgz/*.tgz` files must be installed into the profile as `file:` dependencies.
850
919
  >
851
- > **Changing the client bundle**: edit `src/factory.js` then run `pnpm run build:client`
852
- > to regenerate `lib/client.js` (that artifact is not tracked; a browser refresh picks it up — no host restart needed).
853
- > **Changing the ask panel**: edit `assets/extensions/dshcs-editor-bridge/webview/src/*` then run
854
- > `pnpm run build:webview` (same convention: generated, not tracked; the IDE must be restarted once to pick it up,
855
- > because the extension host caches the webview resources).
920
+ > **Changing the client half / the ask dialog**: edit `lib/client.js` directly (it is **hand-written source**:
921
+ > no build step, no `src/**` layer; the format rules are in that file's header and are enforced by
922
+ > `pnpm test:client-entry` and `pnpm test:ask-panel`). After installing into a profile a hard refresh picks it up.
923
+ > **Changing the extension**: edit `assets/extensions/dshcs-editor-bridge/{extension.js,lib/*.js}` (plain JS, no
924
+ > build). The IDE side needs one restart to load the new extension code, because the extension host caches loaded
925
+ > extensions.
856
926
 
857
927
  ### Pack-machine environment (the user machine needs nothing)
858
928
 
@@ -1072,16 +1142,12 @@ What remains on the plugin side:
1072
1142
  or by simply calling `editor_context` — 0.3.0–0.3.11 sat in the state "health says bridge:true, extension never
1073
1143
  loaded" (cause above: the user-level install was marked `.obsolete`).
1074
1144
  - **Bridged state can lag by up to 600 ms**, and the tools say "stale" rather than serving data older than 10 s.
1075
- - **The ask panel renders only "new content" (0.3.22)**: the subscription starts when the panel opens, the history
1145
+ - **The dialog renders only "new content"**: the subscription starts when the dialog opens, the history
1076
1146
  `records` from `follow`'s opening frame are discarded, and the panel has **no "load earlier"** (the history-paging
1077
1147
  API `sessionController.page()` is deliberately not called in this version). Switch to the DSH UI for older content.
1078
- - **Panel highlighting ships only DSH's boot grammar set** (typescript / shellscript / json): the rest of the
1079
- official grammars load lazily through `import()` (~1.6MB total), and the panel is a single-file IIFE with no
1080
- lazy loading, so those languages render as plain text (exactly like DSH's own first render, no errors).
1081
- For the full set: `node scripts/build-webview.mjs --all-grammars`.
1082
- - **Panel assets are pinned to the DSH version**: the renderer is bundled against the UI version of the deployed
1083
- DSH, so after upgrading DSH you must rebuild the panel (`pnpm run build:webview`; the build fails loudly on a
1084
- version mismatch). The panel also shows a mismatch notice at runtime instead of silently using the wrong renderer.
1148
+ - **Highlighting follows DSH's own lazily-loaded grammar set**: the panel uses the page's renderer instance, so
1149
+ there is no "the artifact only carries a few grammars" limitation.
1150
+ - **A renderer version mismatch is impossible**: the panel requires the very instance the UI uses.
1085
1151
  - **The approval window in the panel is 5 minutes**: while the panel is open, approvals ask the panel first (the card
1086
1152
  shows a countdown); **closing the panel** or letting the 5 minutes run out hands the request back to the DSH UI —
1087
1153
  after that, that request can **only** be answered there (the card disappears from the panel and the thread keeps an