Branch for hardware verification on a host that meets v0.24.1's peer range.
Base: upstream `dsh-better-sidebar` v0.24.1 (tag v0.24.1 = a2751cf), MIT.
Measured delta carried over from the v0.19.1 line: 78 rename-only files, 11 real
edits, 10 additions. Ported with the same measurement method as the v0.19.1
re-base: build a normalized base (upstream tree + the fork rename, excluding
README.md / package.json / docs/plans/* which deliberately keep the upstream
name), extract the delta against it, then three-way apply onto the renamed
v0.24.1 tree (`git apply -3`). 13 conflicts resolved by hand.
Identity:
- version 1.3.0, SIDEBAR_SERVICE_VERSION 1.3.0 (package.json ↔ service.ts pair
is asserted by tests/service.spec.ts), forkedFrom -> v0.24.1/a2751cf.
- package.json keeps the union of both sides' metadata: upstream's new
`zustand` + `icon`/`locale` files entries, plus the fork's `scripts/switch.sh`
entry and its `forkedFrom` block.
- README.md takes upstream v0.24.1 wholesale (its own v0.24.1 changelog and all
16 sections) and keeps the fork's two deltas: the fork-notice block at the top
(now documenting the 0.2.0-rc.1+ requirement) and the fork package name.
- peerDependencies stay upstream's `^0.2.0-rc.1` — this line does NOT load on
DSH 0.1.x: the host's semver pre-check rejects a caret range across a minor
and silently disables the row (no error, no load).
Carried features, all re-verified against the new base:
- apes:* information-layer tabs (src/client/apes/) — the subtree's session feed
changed shape in 0.1.7 and the port follows the host's own migration notes in
context-types.ts: the per-session catalog is now
`projectionsBySession[id].values.subagentCatalog` (a flat entry list) instead
of 0.1.6's `subagentsByParent` catalog with `entries`/`kind`/`activity`, and
the removed `list.current` is answered by `props.scope.sessionId`. Rows are
normalized to { id, label, mode } with activity read from the durable
summaries, so catalog and byId sources render alike.
- HTML preview delivery through srcdoc (sandbox kept intact).
- Panel host mounted inside the shell's framed content viewport.
Gate: typecheck clean; build clean; 1515 passed / 9 skipped (141 files).
NOT applied to ~/.dsh/profiles/desktop — that profile runs DSH 2.0.10 with
@deepseek-ai/dsh 0.1.5-rc.2, where this line is silently disabled by design.
The rc.1 adapter first went out as 0.21.0-rc.1, but DSH 0.1.7-rc.1 is
upstream's release batch, so the plugin that targets it must not sit on a
prerelease channel. Re-cut it as v0.21.1: the version is now free of `-`,
so release.yml publishes it under npm's `latest` instead of `alpha`.
Version literals only — package.json, dsh.plugin.json and
SIDEBAR_SERVICE_VERSION (tests/service.spec.ts still pins the last two
together). No code change: the peer floor stays ^0.1.7-rc.1 and the
support line stays "DSH 0.1.7-rc.1 and later".
Docs follow the channel move: README/README_EN/guide drop the "alpha
channel" framing, the install command becomes @latest, and every older
DSH line now gets an explicitly pinned command in the version table
(@0.19.1 and so on) because `latest` no longer covers them. The rc.1
design record keeps its history and gains a §8 for the re-cut.
Move the support line from 0.1.7-alpha.1 to 0.1.7-rc.1 (peer floor
`^0.1.7-rc.1`). 0.21.0-alpha.1 never reached npm, and once the code-card
labels below are fixed the same build crashes on alpha.1 (its DiffBlock
still reads the `labels.files` upstream deleted), so declaring only the
host we verify against is the honest floor — and from rc.1 on the host
enforces peers at boot anyway: a row whose `@deepseek-ai/dsh-*` peers do
not satisfy the runtime is silently disabled.
Dependencies: 28 devDependency pins and 14 peers move to 0.1.7-rc.1.
rc.1 also raised the vendored runtime (cordis 4.0.4, schemastery 3.18.4,
cordis-plugin-group 1.0.4, cordis-plugin-include 1.0.9) and publishes DSH
cross-references as exact versions, so bumping schemastery/cordis alone
left four unmet transitive peers — the three vendor packages are hoisted
into devDependencies, which is what makes `pnpm peers check` clean again.
One compile break: rc.1 added `CodeToolbarLabels` (codeLabel / wrapLabel /
unwrapLabel, all required), made DiffBlockLabels / ReadBlockLabels extend
it and deleted `DiffBlockLabels.files`. Fixing SideChatView then leaving
markdown on the legacy banner would render two different code-card styles
inside one side-chat view, so the plugin adopts the card everywhere:
`markdownTextProps()` now passes `labels.code.toolbarLabels` with the
host's own wording (codeBlock.title|wrap|unwrap, added to all 20
dictionaries; `sideChatBlockFiles` retires with the deleted footer).
Adopting the card surfaced a silent regression the suite caught: the new
toolbar shows `codeLabel` for languages the highlighter does not know, and
mermaid has no shiki grammar, so the info string no longer reaches the DOM
and the banner-text probe in mermaid.tsx matched nothing — mermaid fences
stopped being swapped for diagrams. Detection now matches the FENCE BODY
from the source that produced the render.
`tests/market-manifest.spec.ts` pins every DSH peer as a floating caret
range: an exact pin (the devDependency style) or a comparator without a
prerelease would fail rc.1's preflight and silently stop the plugin from
loading, and `pnpm peers check` cannot see that — it validates the
resolved dev tree, not the range shape the host reads.
Two things the Plugins list made obvious once the icon rendered beside the
built-ins.
The artwork was flat: one blue panel, one darker blue rail. The built-ins
each carry their own two-hue palette — the file header says so outright
("these glyphs carry their own brand colors and gradients instead of riding
currentColor") — and at the 30px a row renders at, a single-hue mark reads as
a plain block. Three candidates were rendered next to the real built-in
artwork at the real sizes (30 / 36 / 72, light and dark) before picking one: a
violet rail sat too close to the panel's deep blue to read as a second colour,
and a pale teal panel washed out. The shipped pair is blue for the panel
(gradient) and teal-green for the rail (gradient), which stays legible at row
size and matches the Subagent artwork's proven blue/green combination without
sharing its silhouette.
The description sold the plugin as someone else's sidebar. It now leads with
what it is — better — in one sentence rather than a colon and a feature list,
in both locales, and both `package.json` (the npm blurb and the list's
fallback label) and `dsh.plugin.json` say the same thing. The README never
framed it that way, so nothing there changed.
The artwork guard gained the two properties this decision rests on: the mark
must be colourised (two gradients, no currentColor) and must not use a
CSS-only paint, since the icon ships as an <img> and the search artwork's
foreignObject technique would stay empty there. Red/green verified — a flat
single-hue redraw fails it.
Verified on the live 3080 instance: the row renders the new artwork and
"更好用的右侧边栏——文件树、编辑器、文件变动、任务和侧边对话都收在这一栏里,每个会话互不干扰。"
with no VSCode framing anywhere in the copy.
0.1.7 reads `package.json#icon` plus `locale/<lang>.json` to illustrate and
label a plugin in the Plugins page and the `plugin_manager` list. The plugin
had neither, so it rendered as a bare row (and every other third-party entry
in this profile still does — ours is now the only one with a real mark).
The icon follows the two icons DSH ships for this feature
(packages/experimental/*/icon.svg): a 36x36, fill-only geometric mark with a
blue gradient and no stroked outlines or text. It reads as a panel with a
docked right rail — the sidebar this plugin is. The first cut used two blocks
separated by a gap, but the rail shared the panel's light tone at the top, so
the split leaned on a 3px gap that shrinks away at list size; a darker rail
flush inside one panel survives any downscale.
`locale/en.json` is the discovery baseline English needs to be present for the
whole directory to be read — and `locale/zh.json` carries the Chinese title
and blurb. The manifest's own `description` still advertised the terminal and
the browser, which this plugin handed to DSH in 0.20.0; it is now user-visible
as the fallback label, so it was corrected too.
Every failure in this feature is silent: bad metadata only ever degrades to a
plain text row. `tests/plugin-meta.spec.ts` therefore pins both ends — the
manifest wiring and the packed tarball — plus the house style of the artwork,
and was red/green verified (removing `icon.svg` from `files` fails it twice,
once against the real tarball).
Verified on the live 3080 instance: `readPluginMeta` resolves the icon to a
`data:image/svg+xml` URI and the page renders it (`ourIconExact: 1`, no page
errors).
DSH 0.1.7-alpha.1 is 1299 commits past 0.1.6-alpha.2 and rewrites four
contracts this plugin was built on. The peer floor moves to
^0.1.7-alpha.1 (semver's prerelease rule means ^0.1.6-alpha.2 can never
match a 0.1.7 prerelease); 0.1.6-alpha.2 and earlier stay on v0.19.1.
Released as 0.21.0-alpha.1 (dist-tag alpha).
Hard breaks:
- dsh-settings was rewritten: register(ns, schema) is gone, a form is
addressed by the plugin row's Loader entry id and read from the
module's exported Config. The preferences are merged into Config and
marked volatile — which requires DSH's own schemastery, because only
that build wraps a volatile field in the cosmokit reference the
loader's volatile commit walks. With the public package a settings
write reports success and revision 1 while the effective value never
changes.
- ui-primitives renamed its whole icon family (IconXxx14/16 ->
IconXxxRegular/Medium): 26 imported symbols, 17 files. The failing
mode is render-time, not load-time. Five call sites that relied on the
old 14px default now pass size={14} explicitly.
- Session format v4 rejects source.kind === 'plugin'; the side-chat
boundary injection now writes plugin:dsh-better-sidebar, the same kind
DSH's own v3->v4 migration produces, so historical threads survive.
Tool results moved to role 'tool' with top-level content/isError; all
four parsers accept both shapes.
- The client session snapshot dropped subagentsByParent/jobsBySession
and the on-demand subagent-catalog observation API. The Tasks page
reads projectionsBySession and a new jobs.list route.
Yields and convergence:
- The read-only previews go to DSH's documentpreview (Office->PDF, a
spreadsheet worker, zoom, per-directory refresh); editor.canOpen
refuses those 26 extensions. Markdown, HTML (with its sandbox
switches) and the editable code buffer stay.
- The host browser kind is desktop-profile-only now, so the web e2e
lanes expect no browser entry and assert its absence.
- Link takeover claims only urlTarget links and passes the rest to the
host, fixing a regression from the 0.1.6 round: the fallback pointed
at a 'browser' type the plugin no longer registers, so http links in
its own markdown did nothing.
Also fixed, found along the way:
- The file tree refreshes live (new directory watch over the plugin's
own socket), since the plugin owns the `files` kind.
- ctx.sessions.list has no `current` field at either tag; the three
readers were dead. They now use ctx.sidebarRight.mounted.
- package.json.files was missing lib/client-locale.js.
- Two genuinely dead call sites: sessions.openSubagent/open/
refreshSubagents do not exist on the current baseline, so the
topology click, the jump-back and the retry were all no-ops.
- The test harness window shim lacked addEventListener, which the 0.1.7
primitives call at module-evaluation time; and the e2e token exchange
needs Playwright's request stack, not Node's fetch.
DSH 0.1.6-alpha.2 ships a right-Sidebar terminal and a right-Sidebar browser
of its own. Both were things this plugin had built from scratch, so keeping
them meant two implementations of one surface plus a plugin that shadows a
host type the product is actively developing.
Terminal: the plugin's whole PTY stack goes — the UI-tab manager, the
agent-owned registry, the lazy node-pty loader, the eight model-facing
terminal_* tools, the xterm view and its font/link/scrollback helpers, and
the cross-session 'pinned terminal' rail that existed only to keep a terminal
alive across a session switch. The host's terminal kind becomes the only
provider, which also retires the node-pty dependency, its pnpm allowBuilds
entry, and the installer's -Repair mode (issue #140's whole class of
'node-pty failed to load' support).
Model-facing capability note: the plugin used to inject terminal_* tools
(off by default). Those are the model's only cross-call persistent terminal,
and DSH's own equivalent (@deepseek-ai/dsh-tool-terminal) is not mounted by
any shipped bundle, so removing them leaves the model with the one-shot
bash/pwsh tools only. That is the accepted cost of not maintaining a second
PTY surface; the README documents how to enable the upstream toolset.
Browser: the plugin's browser tab type is dropped, so the host's own browser
kind owns embedded pages. The DOM-level link takeover STAYS (it is the only
thing that can route an https link to the system browser by protocol, which
the host does not do) but now delegates to the host's browser tab via
ctx.sidebarRight.openTab('browser', …) with a window.open fallback, and the
two settings that only ever configured the plugin's own iframe (sandbox
escape hatch, loopback allowlist) go with it — along with the host-side
browser.probe route they fed.
Cards: the empty-pane welcome cards adopt the host's guide-capsule recipe
(0.5px l4 hairline, 24px radius, layer-1 fill, 56px floor, 14px/20px padding)
so the plugin's 'pick what opens here' surface reads as the same design as
DSH's own. A theme.spec guard pins the geometry and the token-only paint —
including that the upstream-only --dsw-alias-bg-l1/-l2 spellings (defined
nowhere, so their declarations are silently dropped upstream) never appear
here.
Also drops the dead state the removed features left behind (SidebarTab.pin,
nextTerminal, agentWaits, bottomOpenedOnce, the agent-tab helpers, the
pinned-tab memo key), re-points every test fixture that used them at
surviving equivalents, and prunes the specs of the deleted features.
The peer floor had to move: semver's prerelease rule needs a comparator on the
same [major,minor,patch] tuple, so `^0.1.5-rc.1` can never match any 0.1.6
prerelease. The floor becomes `^0.1.6-alpha.2` (which does admit 0.1.6 alpha/rc
prereleases and excludes 0.1.5), and `engines.dsh` follows. This is a deliberate
support-line break, matching how v0.19.0 dropped 0.1.2: 0.1.5-rc hosts stay on
0.19.1.
Three upstream changes actually reach this plugin and are adapted here:
- `IconSendOutline16` no longer exists in ui-primitives (only
`IconSendOutline14`).
- `TerminalBlockLabels` gained a required `noExitCode` pill text.
- `ConnectionIndicator` dropped `reconnectLabel`: the outage label is now the
one static label that already names the retry action, so the label copy moves
there, matching the host's own '连接异常,点击立即重连'.
ui-primitives still declares no `dependencies` while its bundle bare-imports
more than it used to: `diff` and `simple-icons` were previously satisfied only
transitively and had to be hoisted into devDependencies, exactly like the
anser/shiki/katex group before them. `@deepseek-ai/dsh-code-runtime` is gone
upstream (the package no longer exists) and leaves the dev tree.
The e2e fallback npx spec is pinned rather than version-less: an unpinned
`npx @deepseek-ai/dsh` resolves npm's `latest`, which is still the 0.1.5 line
this plugin no longer supports.
The `--maxWorkers=2` fix from the previous commit was falsified by real CI:
run 34574847774 still ended with `[vitest-pool]: Worker forks emitted error /
Worker exited unexpectedly` while reporting 126 of 127 files passed and zero
FAIL lines. The worker that died was the one running
tests/native-surface.spec.ts — it produced NO results at all, which is the
same shape as the two earlier crashes (125/126 and 123/124: N-1 files
reported plus a silent worker exit at teardown).
So the consistent signature across all four crashes is a worker-recycling
race, not a test failure. `test:windows` now runs the whole suite in ONE fork
(`--maxWorkers=1`), which removes recycling entirely; the cost is wall-clock
(14 s -> 41 s locally, and the Windows step stays well inside its 30-minute
budget) and the signal is unchanged. Retries are still deliberately not used —
they would hide a real regression instead of removing the race.
The CI comment now records both Windows failure classes with their evidence,
so the next person can tell what this flag is for.
The pinned baseline moves to DSH 0.1.5-rc.2 (npm `next`; `latest` is still
rc.1) while the plugin's peer floor stays ^0.1.5-rc.1, so rc.1 hosts need no
DSH upgrade to run this version. Nothing in the rc.1 -> rc.2 delta touches
this plugin: of 300 changed upstream files, the overwhelming majority are
single-line `version` bumps in per-package package.json files, and the only
real code edits are the message-feedback dialog, deliverables card CSS and
the CodeFileIcon SVG artwork split (`code-file-icon-artwork.ts`). There are
zero changes under packages/api|host|session|agent and zero type-surface
changes beyond one line in message-feedback/types.ts, so the adaptation is
the pin itself — devDependencies, the CI mount lane and
SIDEBAR_SERVICE_VERSION — with no plugin-side code change and no runtime
compatibility layer.
- package.json: devDependency pins 0.1.5-rc.1 -> 0.1.5-rc.2 (including
dsh-client-locale, which is still published on the DSH tuple); peers keep
the ^0.1.5-rc.1 floor; version -> 0.19.1
- dsh.plugin.json: version -> 0.19.1; engines.dsh keeps >=0.1.5-rc.1 (same
supported range as the peer floor — the two must not fork)
- pnpm-lock.yaml: lockfile realigned to rc.2
- src/client/service.ts: SIDEBAR_SERVICE_VERSION -> 0.19.1 (tests/service.spec.ts
asserts it equals package.json's version)
Verified locally on macOS arm64 against the rc.2 tree: pnpm install clean,
`pnpm peers check` reports no peer dependency issues, typecheck / lint /
check:consumer-types green, `pnpm test` 126 files / 1312 passed / 9 skipped.
The real-mount smoke (scripts/e2e-mount.sh with DSH_CMD pinned to rc.2) and
the aggregate double-mount lane both pass — evidence recorded in
docs/plans/2026-09-10-dsh-0.1.5-rc.2-adaptation.md.
Baseline moves to DSH 0.1.5-rc.1 — the 0.1.5 line's first release
candidate, and the npm `latest` dist-tag now: peer floor ^0.1.5-rc.1,
devDependency pins, the CI mount-lane pin and dsh.plugin.json engines
all follow. 0.1.5-alpha.2 users stay on v0.19.0-alpha.1.
The upstream delta from alpha.2 is small: 373 changed files, mostly
version bumps, sidebar preview polish and test snapshots. Exactly two
changes touch this plugin, and only one needs work:
- `SidebarRightGuideEntry.description` is back (optional). The native
guide renders it only while it lists at most 4 entries; a longer list
drops every description. The plugin restores `TabDescriptor.description`
and the six built-in `guideDesc*` lines (20 dictionaries) verbatim from
0b6ac5d^, but deliberately does NOT restore the old generic
`nativeGuideDesc` fallback: the host has no fallback of its own, so a
descriptor that declares none now ships no `description` field at all
and renders as a clean title-only capsule.
Consequence worth knowing: the plugin contributes six guide entries, so
the default composition is over the 4-entry limit and shows titles only.
The mount lane proves the restored field end to end by shrinking the
enabled set to three types through the plugin's own settings route
(scratch profile only) and requiring the Files capsule to grow its line.
- The built-in `files` type declares a guide description again and uses a
coloured folder glyph; the plugin owns that kind, so nothing to do.
Verified unchanged from alpha.2 (so nobody re-checks them): the global
main-panel model, the file address grammar (util/workspace-path changed
only its package.json), the native tab-body host contract (.paneBody is
still a block scroller with a definite height), the core/agent/session/
subagent host APIs and the ui-primitives export surface.
Restore caveat fixed in the lane: the settings route merges patches
key-wise, so undoing a temporary `tabsEnabled` change by posting the
original (empty) map leaves the disabled keys behind — the e2e restores
every key it touched explicitly.
Baseline moves to DSH 0.1.5-alpha.2 only (peer floor ^0.1.5-alpha.2,
CI pin, manifest engines); alpha.1 users stay on v0.19.0-alpha.0.
Host-contract changes handled:
- the native guide entry lost its `description` field (entries are
icon+title capsules now): `TabDescriptor.description` and the six
`guideDesc*` keys plus the `nativeGuideDesc` fallback are removed.
- the file address grammar changed: `fileAddressFor` always returns a
session-scoped address (absolute paths keep their leading `/`),
`parseFileAddress` is prefix-based and strips `?`/`#`. The plugin's
purity-gated mirror follows.
- global main panels replaced the `conversation` root slot with the
keyed `main` slot (`main.conversation`): the bottom workbench's
center-column locator now accepts both keys and skips
`display: contents` slot hosts.
Fixes a pre-existing fill bug the alpha.2 probe made obvious: the native
tab body host is a block scroller with a definite height, so tab roots
that only declared `flex: 1` collapsed to content height and the side
chat composer drifted off the pane bottom. The native adapter now wraps
every tab body in a `height: 100%` column flex host.
Also promotes the dependencies `dsh-client-ui-primitives@0.1.5-alpha.2`
dropped from its manifest (anser / shiki / mdast-util-* / micromark-* /
katex / @shikijs/langs) and the newly unmet `dsh-session-persistence`
peer into devDependencies.
Move the plugin onto the DSH 0.1.5-alpha.1 baseline (peer floor
^0.1.5-alpha.1, CI pinned to the alpha dist-tag) and fix the two host
contract changes that alpha line introduced.
Baseline:
- all @deepseek-ai/dsh-* peer/dev deps -> 0.1.5-alpha.1; optional peer
@deepseek-ai/dsh-client-ui-sidebar-right (declared, unused until the
native-surface phase); devDep @deepseek-ai/dsh-agent-loop (inbox fold)
- dsh.plugin.json engines >=0.1.5-alpha.1; version 0.19.0-alpha.0;
SIDEBAR_SERVICE_VERSION follows
Live assistant stream (assistant/chunk is gone):
- 0.1.5 publishes in-flight model deltas as process-local
agent/assistant-stream frames instead of durable assistant/chunk events
and embeds the settled stream in assistant/message / assistant/attempt.
src/assistant-live.ts folds the frames into a bounded per-session buffer;
sidechat.events serves the current attempt as `live` rows (replaced every
poll, never a delta) and the transcript renders them; the inherited
in-progress snapshot reads message content / attempt stream / live frames.
Cold session reads (persistence.inspect is gone):
- 0.1.5 reads a stored session through an explicit read handle
(open(id,'read') -> handle.read() -> close()). src/session-store.ts owns
that protocol; every cold read (sidechat transcript, cold-resume preset,
session.cwd, changes replay) goes through it.
Dead code and docs:
- drop the remote.session.openWorkspacePath interception: 0.1.5's chat opens
files through ctx.sidebarRight.openResource instead, so the wrapper had no
caller and would hijack the host's "open in app" gesture; the
interceptOpenPath pref, its settings row and its locale keys go with it
- AGENTS.md §3 + external-plugin-guide.md describe the new baseline
- vitest setup bridges Node's localStorage accessor to jsdom's store (the
pre-existing 26 failures on Node >= 22)
- desktop-layout e2e seeds through host RPC and types into the Lexical
composer (0.1.5 replaced the textarea) instead of driving the directory
picker
Verified: pnpm typecheck / lint / build / test (124 files, 1327 tests) /
check:consumer-types, plus pnpm test:mount against a real
@deepseek-ai/dsh@0.1.5-alpha.1 host (18/18).
Graduate from the alpha track to the stable channel:
- Baseline: CI mount pin, dsh.plugin.json engines floor, and every
@deepseek-ai/* peer floor / devDependencies pin move to 0.1.2-rc.1
(published on the `next` dist-tag). alpha.5 -> rc.1 is a pure version
bump in the harness (all package.json "version" lines, zero source
delta), so no code adaptation; every host contract was re-verified
unchanged and the real-host mount smoke passes.
- dsh-client-locale resumed publishing; its trailing-pin exception is
gone — peers and devDeps align at 0.1.2-rc.1 (pnpm peers check clean).
- Version: fold the unpublished v0.18.1-alpha.0 / v0.19.0-alpha.0 bumps
into the v0.18.0 stable release; SIDEBAR_SERVICE_VERSION follows
package.json (asserted by tests/service.spec.ts).
- Docs: README/README_EN badges + channel notes + a full "since v0.17.1"
changelog entry (the two folded alpha numbers are annotated), AGENTS.md
§2/§3 baseline wording, external-plugin-guide version line, and the
e2e-host-protocol header's re-verified list gains 0.1.2-rc.1.
- pins: CI mount-gate CLI, dsh.plugin.json engines floor, and the
@deepseek-ai/* peer floor / devDependencies baseline move to
0.1.2-alpha.5 (dsh-client-locale trails at 0.1.2-alpha.3 — no such
release upstream; ^0.1.2-alpha.3 already accepts the alpha.5 runtime)
- code: migrate the alpha.4 Session.events removal to snapshotEvents()
(sidechat transcript live reads, fork inheritance, jobs.output replay,
subagent activity) and drop the seedLength meta field the host removed
from CreateAgentOptions.meta
- gates: pnpm peers check clean, typecheck + 1137 unit tests green,
real-host mount smoke 14/14 on npm-published 0.1.2-alpha.5
Replace the pnpm-workspace.yaml overrides with the plain-repo pattern:
install every peer the dev tree touches (dsh-attachment, dsh-code-runtime,
dsh-scope, dsh-session-projection, dsh-system-prompt, dsh-user-approval,
dsh-util-time) as direct devDependencies pinned to the baseline, same
as the thirteen already there. Root-level hoisting makes pnpm resolve
the peers natively (peers check: zero unmet, no global forcing), and
the list extends the existing bump workflow instead of introducing a
new mechanism.
Verified: typecheck clean, 107 test files / 1137 tests green, and the
real-host mount smoke passes 14/14 against @deepseek-ai/dsh@0.1.2-alpha.3.
Point-by-point audit of all 117 commits between alpha.2 and alpha.3:
every host contract this plugin relies on (token auth, slash RPC,
MarkdownText labels, the event-stream/persistence APIs behind
sidechat.events, SettingsNamespaceInput, SUBAGENT_DESCRIPTOR_VERSION
(still 3), dsh-client-store, the profile loader, the node-pty pin) is
unchanged, so no plugin code needed rewiring. alpha.3's breaking
changes (required BeginSubmissionInput.mode, the renamed
attachment-invalid subagent error, the removed SQLite persistence
backend, the identity-gated projection change feed) do not touch this
plugin.
What did need adapting was the dependency tree: the alpha.3 npm
artifacts ship with workspace-era metadata that does not match their
published code —
- dsh-subagent@0.1.2-alpha.3 imports `admitPromptContent` from
@deepseek-ai/dsh-attachment without declaring that dependency, so
resolution floats to a stale 0.1.1-rc.1 copy that lacks the export
(broke 5 test files at load time);
- six transitive peers (dsh-scope, dsh-session-projection,
dsh-system-prompt, dsh-user-approval, dsh-util-time, dsh-code-runtime)
drifted to pre-alpha.3 versions against the ^0.1.2-alpha.3 ranges the
alpha.3 packages declare;
- the alpha.3 line peers on @deepseek-ai/cordis ^4.0.2 (was ^4.0.1).
Fix: pin the seven stragglers via pnpm-workspace.yaml overrides (with
a note to drop them once upstream fixes its metadata), narrow the
cordis peer to ^4.0.2, and bump the baseline pins — CI mount gate,
dsh.plugin.json engines, peer/devDependencies, README badges, plus
SIDEBAR_SERVICE_VERSION in lockstep.
Verified on the npm-published @deepseek-ai/dsh@0.1.2-alpha.3:
typecheck clean, 107 test files / 1137 tests green, `pnpm peers check`
reports zero unmet peers, and the real-host mount smoke passes 14/14.
DSH itself is still on its 0.1.2-alpha line (npm `alpha` dist-tag), so the
plugin mirrors it: v0.18.0-alpha.0 publishes under the npm `alpha`
dist-tag and supports DSH 0.1.2-alpha.x only. Stable-DSH (0.1.0-rc.8 ~
0.1.1-rc.2) users stay on v0.17.1 (npm `latest`). Verified end-to-end
against a real 0.1.2-alpha.2 host: full vitest suite, typecheck,
build/pack, consumer type surface, and the headless mount smoke 14/14
(the prerelease version also passes `dsh plugin add file:`).
- peer floor raised `^0.1.0-rc.8` -> `^0.1.2-alpha.2` (the mount install
no longer warns: the old floor could not semver-match a prerelease
host); dsh.plugin.json engines.dsh `>=0.1.2-alpha.2`.
- Dropped the pre-alpha compatibility layer:
- e2e host RPC collapsed from the dot/slash dual dialect to slash-only
(rpcAttempt; the 404-probe negotiation and its cache are gone), and
parseLaunchUrl now requires the ?token= launch URL;
- MarkdownText labels collapsed to the nested single shape (the cast
became unnecessary against the alpha.2 declarations);
- the defunct `@deepseek-ai/dsh-client-runtime` row is gone from the
chunk externals allowlist (chunk-loader / tsdown / two specs) and
from dsh.client.inject.
- market-manifest guard now allows an exact prerelease version (alpha
cuts are not the market's verified `latest` target); release.yml picks
the npm dist-tag from the version (prerelease -> `alpha`, else
`latest`) so an alpha can never hijack `latest`.
- Docs retargeted: AGENTS.md §2/§3, README/README_EN badges + v0.18.0
-alpha.0 section + install note (`dsh-better-sidebar@alpha`), guide.
DSH 0.1.2-alpha.2 is on npm under the `alpha` dist-tag, so the CI mount
pin and the @deepseek-ai/* devDependencies baseline move up from
0.1.1-rc.1/rc.2 (the dead dsh-client-runtime devDep goes away with
them). Verified end-to-end against a real 0.1.2-alpha.2 host: full
vitest suite (1106), typecheck, build/pack, consumer type surface, and
the headless mount smoke 14/14 — the token URL, slash RPC and
MarkdownText labels contracts are unchanged from alpha.1.
- dsh-settings dropped the runtime `settingsNamespace` export:
namespaces are validated at compile time (SettingsNamespaceInput), so
the host passes the 'dsh-better-sidebar' constant directly and the
fake settings service in smoke.spec casts to the branded type.
- dsh-subagent bumped SUBAGENT_DESCRIPTOR_VERSION 2 -> 3: the version is
stamped by the host package; the sidechat seed assertion now follows
the constant instead of pinning a literal.
- Restored SessionEvent.ignorable and the RemoteError wrapping were
verified to have no impact on this plugin.
- e2e scratch profiles (e2e-mount.sh / e2e-aggregate-mount.sh) add
'@deepseek-ai/*' to minimumReleaseAgeExclude (mirroring the repo
root): alpha releases are often <24h old when the lane runs, which
pnpm 11's default minimumReleaseAge would refuse to install.
- vitest excludes .worktrees/** — local task worktrees carried stale
code against the shared node_modules and broke on the devDeps bump.
- Version lockstep 0.17.2 (package.json, dsh.plugin.json,
SIDEBAR_SERVICE_VERSION, README/AGENTS/guide docs).
Dual-version compatibility: the plugin now works identically on
0.1.0-rc.8 ~ 0.1.1-rc.2 and 0.1.2-alpha.1 (the latter verified against a
source build of the dsh-v0.1.2-alpha.1 tag: mount smoke 14/14 + aggregate
double-mount; 0.1.1-rc.2 re-verified 14/14).
- MarkdownText: alpha.1 renamed the flat optional `codeLabels` prop to a
required nested `labels: { code: {copyLabel, copiedLabel}, footnotes }`;
the old prop alone crashes fence rendering with "reading 'code'". All
four render sites (mermaid / MarkdownHtml / TextEditor / SideChatView)
now go through src/client/markdown-labels.tsx (dual-shape object passed
under both prop names).
- e2e mount lanes speak both host dialects (tests/e2e/host-protocol.ts +
host.ts, unit-locked by tests/e2e-host-protocol.spec.ts):
- one-time-token browser auth: the launch URL grep extends past the
`/?token=` query (truncating at `/` loses the token), goto navigates
the token URL (browser cookie exchange), and stamped navigations
pre-seed the context cookie via addCookies — the exchange 303 drops
sibling query params;
- Remote gateway: dot endpoints first, slash endpoints on 404 fallback,
with args keyed by the controller parameter name (workspace/create ->
{request}, session/list -> {_request}; verified on a live host).
- package.json: drop the @deepseek-ai/dsh-client-runtime peer (package
removed upstream in alpha.1; zero references in the source — the chunk
externals entry stays and is resolution-tolerant).
- scripts/e2e-mount.sh: fix a latent set -u crash in the npx fallback
branch ($DSH_CMD followed by a full-width comma parsed as part of the
variable name).
- CI pin stays at @deepseek-ai/dsh@0.1.1-rc.2 until alpha.1 publishes to
npm (comment updated); README/AGENTS document the new contracts.
- Version lockstep bumped to 0.17.1.
#1 GitView: hide/disable 'open in editor' for external linked worktrees
(path outside session workspace is rejected by the fence); add
isWithinWorkspace client helper for the gate.
#2 DiffTab: untracked diff read resolves against diff.repoRoot (child-repo
paths are repo-root-relative, not cwd-relative); fs.read route threads
selectedRepoOf into resolveGitPath so the host resolves the right base.
#3 GitView: chooseRepo handler mirrors chooseWorktree — clears status/
history/log and bumps generation before async refresh, preventing stale
rows from acting on the newly selected repository.
#4 git.ts: repoRoots/repoRoot compare roots via pathIdentity() (platform-
aware) instead of raw includes(), fixing win32 forward/backslash
mismatch that silently fell back to roots[0].
#5 markdown-images: mask fenced code blocks and inline code spans before
rewriting; only rewrite reference definitions whose label is actually
referenced by image syntax (not plain links). +3 tests.
#6 package.json: downgrade unrun ^0.3.1 → ^0.2.39 (0.3.x requires Node
^22.13, incompatible with declared engines >=20).
#7 market-manifest.spec: shell:true on win32 so execFileSync resolves the
pnpm .cmd shim.
#8 smoke.spec: build html-route URL via encodeHtmlUrl() (production
encoder) so the Windows drive separator is correct.
#9 index.ts git.worktrees route: thread selectedRepoOf so a workspace
container's selected child repo's linked worktrees appear.
- bump version: package.json / dsh.plugin.json / SIDEBAR_SERVICE_VERSION
- README / README_EN: feature tour converted to a table (two screenshots
per row, saves space); add v0.15.2 changelog (file-tree open-in-app
menu #334, tab context menu #331, diff collapse by default #270, prune
empty restored panes #268, hide git windows on Windows #301, untracked
files inside new folders #242, toggle/drag per-frame re-render perf
#315); community QR codes landed in #325
* feat(editor): file tree 'open with' submenu (external editors, pin, SSH-remote, custom editors)
The file tree right-click menu gains an 'Open with >' submenu with the
resource manager (reveal in file manager), VS Code, Cursor and Zed, plus
user-defined custom editors (name + URL template + VSCode-family flag).
Each submenu row carries a pushpin that promotes the target to a direct
top-level menu item.
A configurable SSH host (settings: Files card gear) switches VSCode-family
openers to <scheme>://vscode-remote/ssh-remote+<host>/<path> and hides
host-local targets (file manager / Zed / non-VSCode custom editors).
External opens run through the new fenced /sidebar/api/open.external route
which spawns the platform opener with an argv array (no shell); the client
never navigates raw scheme URLs itself. The settings seam's render panel now
appends after the declarative rows instead of replacing them.
* feat(editor): polish open-with menu UI — brand icons, pushpin, chevron, row alignment
- Submenu rows: the custom label is now a block-level flex row filling the
primitives itemLabel, so the pushpin docks at the row's far right (matching
the trailing check of every other menu); menu row icons normalized to 16px
(DSH native menus use 16, ours were 14).
- The submenu parent row gains a right-aligned chevron (the primitives Menu
renders no arrow for submenu parents).
- Icons via react-icons: codicons VscPin/VscPinned pushpins (replacing the
hand-drawn pin), VscFile/VscFolder/VscFolderOpened tree glyphs (replacing
the </> file mark), VscLinkExternal for the open-with parent and the
browser tab's open-external button (globally), SiCursor/SiZedindustries
brand silhouettes; the VS Code mark is inlined from simple-icons@11.0.0
(CC0) as IconVscode16 since later simple-icons releases dropped it. All
monochrome currentColor to follow the active skin.
- tsdown: pin react-icons si/vsc to their ESM entries via resolve.alias —
the package's exports map lists require before import, so the shared
conditionNames picked the unshakeable CJS entry and bloated the core
bundle by ~6.4 MB; tree-shaken it now adds only ~12 KB.
tsdown 0.22 resolves its config file through unrun; without it declared,
git-hosted installs (which run `pnpm install` + prepare inside the fetched
source) fail with "Failed to import module unrun". Declare it as a dev
dependency so the prepare step can load tsdown.config.ts.