Sessions & Messages

dsh-new-session-cards

supergameboy/dsh-new-session-cards · ★ 0

supergameboy

DSH (DeepSeek Harness) plugin: four-step card wizard for new-session configuration — prevents silently using default task mode/model

License
Unknown
Added
2026-08-16
Maintenance
active
Install kind
github
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-08-19
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add github:supergameboy/dsh-new-session-cards

GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. Why DSH is fun
  2. What this plugin does
  3. Install
  4. Build from source
  5. Pitfalls (for DSH plugin authors)
  6. 1. The @deepseek-ai/* dependency graph on npm is incomplete
  7. 2. --dsw-alias-brand-primary is ink, not brand blue
  8. 3. Data loading is not automatic
  9. 4. Under a composer takeover, the fallback input bar is hidden, not unmounted
  10. 5. Chain re-routing needs a "change" to trigger
  11. 6. Locale namespaces need declaration merging
  12. 7. RpcResult discriminant narrowing dies inside immer callbacks
  13. 8. A client-only plugin cannot register a host settings namespace
  14. 9. Build-configuration nitpicks (tsdown / tsc)
  15. 10. Distribution-chain pitfalls
  16. How it works (30 seconds)
  17. Design docs
  18. License

中文 | English

A standalone plugin that turns the new-session screen of DSH (DeepSeek Harness) Web GUI into a four-step card wizard — so you can never silently start a task with the default task mode or model.

Zero modification to DSH itself: ships as an independent bundle — dsh plugin add to install, remove to uninstall.


Why DSH is fun

The whole DSH product is a tree of Cordis plugins — there is no core to "operate on"; everything can be replaced from the outside:

  • Composer takeover chain: the new-session input area is a selector-routed chain. Any plugin can take it over — turning it into a full-screen UI (that is exactly what this plugin does) — and then step aside so the original input bar comes back automatically.
  • Slot system: sidebar, settings pages, session headers, message rows… all expose injectable slots. Plugins snap onto them like building blocks.
  • Theme tokens: the --dsw-alias-* semantic palette — plugin styles use zero literal colors and adapt to light/dark automatically.
  • Bundle distribution: two manifests (dsh.bundle + dsh.client), installable from a local directory, a tarball, or directly from GitHub.
  • Plugin is code: a client plugin is plain JS/TSX bundled into a browser bundle, loaded under the exact same contract as the built-in plugins.

You do not need to fork DSH to deeply reshape its UI. This plugin is a complete worked example: take over → configure → step aside.

What this plugin does

The original new-session screen (workspace chip, agent-preset chip, small model selector in the corner of the input card) all fall back to deployment defaults silently, so users easily skip configuration and send. This plugin replaces the new-session screen with:

  1. Step 1 — Task mode: agent-preset card grid (single-select); the default preset carries a «默认/Default» badge
  2. Step 2 — Model: model card grid (single-select, provider tag); the default model carries a «默认/Default» badge
  3. Step 3 — Other settings: workspace (full width) + access mode / plan mode (side by side)
  4. Step 4 — Message: after «确认配置并开始/Confirm and start», an in-wizard message composer — input box + Send + the "/" command menu, running on the exact same input machine and command pipeline as the standard input bar. Sending the first message lets the wizard step aside naturally and enter the conversation.

Confirming itself never sends a message. No configuration is ever used without your knowledge.

You can return to the original hero at any time: the «使用标准输入框/Use the standard input bar» link inside the wizard, or Settings → General → New-session configuration wizard toggle (preference stored in browser localStorage).

Install

# Prerequisite: DSH CLI installed with an initialized profile (e.g. web)
dsh plugin --profile web add github:supergameboy/dsh-new-session-cards

A GitHub install fetches sources, and pnpm ≥10 requires explicitly allowing build scripts (only allow sources you trust):

# <profile dir>/pnpm-workspace.yaml
allowBuilds:
  dsh-new-session-cards: true

Then re-run add (the prepare script builds lib/ automatically). Local directory or tarball installs need no build allowance:

dsh plugin --profile web add ./dsh-new-session-cards            # local dir (run pnpm build first)
dsh plugin --profile web add ./dsh-new-session-cards-0.1.0.tgz

Verify and enable:

dsh --profile web --dump-config   # should show a "# == dsh-new-session-cards" layer
dsh --profile web                 # open a new session screen to see the wizard

Already installed and changed the code? The profile links the checkout, so a browser refresh is enough — the server reads the latest bundle file on every request; no service restart needed.

Uninstall:

dsh plugin --profile web remove dsh-new-session-cards

Build from source

pnpm install
pnpm build          # tsdown: lib/index.js (node half) + lib/client.js (browser bundle)
pnpm typecheck      # requires the DSH source tree at ../deepseek-harness (see pitfall #1)

Pitfalls (for DSH plugin authors)

Real pitfalls hit while developing this plugin, recorded as "pitfall → symptom → fix". Read this first if you want to get productive quickly with DSH client plugins.

1. The @deepseek-ai/* dependency graph on npm is incomplete

  • Pitfall: the rc versions of @deepseek-ai/* on the npm registry have missing transitive dependencies (e.g. dsh-user-interaction is unpublished) — pnpm install 404s.
  • Fix: declare no @deepseek-ai dependency in package.json. Runtime needs none — the client bundle's externals (react, dsh-client-runtime/client, and the other platform modules) resolve from the shell's module table and everything else is inlined; the build needs none either (import type is erased). Type-checking resolves them through tsconfig paths pointing at the DSH source tree's build output (../deepseek-harness/packages/*/*/lib/types).

2. --dsw-alias-brand-primary is ink, not brand blue

  • Pitfall: brand-primary resolves to neutral-bluish-1000 (near-black ink). Using it as a button background with label-primary (also ink) as text → ink on ink — button text completely invisible.
  • Fix: use --dsw-alias-state-business-primary (the DeepSeek brand blue) for emphasis/selection — the InputBar source itself comments: "Business blue, not brand-primary: that token resolves to ink in this sheet"; for primary buttons use --dsw-alias-button-primary-fill background + --dsw-alias-label-primary-foreground (white) text — better yet, just reuse the Button component from ui-primitives.

3. Data loading is not automatic

  • Pitfall: an injected load callback is only defined — nothing ever calls it, so cards stay on skeletons forever (no text) and the Next button stays disabled (it depends on status === 'ready') — the user is stuck in the wizard.
  • Fix: call load() on mount: useEffect(() => { load() }, [load]) (copy the shipped ModelSelect pattern).

4. Under a composer takeover, the fallback input bar is hidden, not unmounted

  • Pitfall: conversation.composer.bar keeps its DOM and is only CSS-hidden during a takeover (the textarea is always "in" the DOM). Everything anchored to it hides with it: the conversation.input.overlay slot (command menu MenuView, popupSelect shell) anchors inside the hidden InputBar and is not portaled.
  • Fix: render your own "/" command menu in the takeover UI — but reuse the pipeline: sessions.scope(sessionId) gets the session scope, inputTriggers.sessionOf(actx) gets the per-session controller (menu store / track / arbitrate / pick), and the menu UI renders from the menu store.

5. Chain re-routing needs a "change" to trigger

  • Pitfall: chain selectors are pure (owner props only). After confirmation the state changed but the owner props did not → no re-route → the wizard never leaves.
  • Fix: make ConversationRoot re-render. Two working triggers: (a) conversation.blocks.set(sessionId, undefined) (the root subscribes to block changes); (b) after sending the first message, composerPhase flips from blank to active (what this plugin finally uses: Step4 send naturally steps aside, no manual flip needed).

6. Locale namespaces need declaration merging

  • Pitfall: ctx.locale.register(NS, ...) is typed N extends keyof LocaleNamespaceMap — a custom NS fails to compile.
  • Fix: in the plugin, declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { myNs: MyKeyUnion } }. In components use PropsLocale<typeof NS> (NS is a value — needs typeof).

7. RpcResult discriminant narrowing dies inside immer callbacks

  • Pitfall: response.result.ok narrowing fails inside store.update((draft) => { ... response.result.error.message }) (TS does not propagate narrowing into the mutator callback) → "Property 'error' does not exist".
  • Fix: extract to a local first: const message = response.result.error.message, use message in the callback.

8. A client-only plugin cannot register a host settings namespace

  • Pitfall: settings persistence goes through settingsScope.bind(), but namespaces are registered host-side (needs host plugin code); a client-only plugin's namespace is always unavailable.
  • Fix: store the preference in browser localStorage (e.g. dsh-new-session-cards:useWizard), wrapped in a createSnapshotStore reactive mirror; the settings row (settings.general.item) reads/writes it — the UI is identical to the built-in settings rows.

9. Build-configuration nitpicks (tsdown / tsc)

  • CSS Modules virtual ids must resolve absolute paths from the importer (resolve(dirname(importer), source)), otherwise ENOENT.
  • The node half needs fixedExtension: false, otherwise it emits index.mjs while package.json exports point at .js.
  • tsconfig paths wildcards allow only one * per pattern (packages/*/*/lib/types won't work — expand per group).
  • Type-checking against the DSH source tree needs: allowImportingTsExtensions, lib: ["ES2023"] (source uses findLast), types: ["node"], and a CSS Modules declaration file (src/css-modules.d.ts).
  • Installing @deepseek-ai/cordis from npm (4.x exists) is not required: cordis types are resolved from vendor/cordis via paths as well.

10. Distribution-chain pitfalls

  • GitHub installs are source installs: you need a prepare script (runs after pnpm ≥10's allowBuilds) that builds self-contained — no monorepo sibling context may be assumed.
  • The dsh.client manifest's exports["./client"] must point at a real bundle file (clientModules validates at boot; a miss fails the plugin).
  • Profile bundle order = configuration layer order: when several plugins are installed, later layers override same-id rows from earlier ones.

How it works (30 seconds)

flowchart LR
    A[New-session hero] -->|conversation.composer takeover chain| B[Four-step wizard]
    B -->|Confirm| C[Step 4 Message]
    C -->|Send first message| D[Conversation]
    D -->|New session| A
  • Takeover: registers a conversation.composer chain entry; the selector matches only "blank session + preference on + not confirmed"; sending flips blank → active and the chain steps aside automatically
  • Data: presets via agentPresets.list/select RPC; models via the modelDirectories service; workspace/sessions via the standard stores; the command menu reuses the inputTriggers pipeline
  • Styling: all --dsw-alias-* tokens, CSS Modules, WCAG 2.1 AA, prefers-reduced-motion fallbacks, zh/en copy
  • Preference: localStorage (useWizard toggle), one row in Settings → General

Design docs

docs/design/ holds the full fractal UI design documents (L0 decision chain → page → component library → style spec, two rounds of independent validation passed).

License

MIT