DSH Plugins Marketplace

DSH Plugins

Plugins

/

Sessions & Messages

/

dsh-subagent-model

m

dsh-subagent-model

Manifest valid★ 1

Model visibility for DeepSeek Harness: a header chip naming the model and reasoning effort each Session runs on, a subagent dropdown marking which child carries its own route, and sidebar hover cards showing what each conversation did.

UI (client)hasBundlePatch

Pazalo

dsh-subagent-model

npm version npm downloads Node Tests Licence

See which model a DeepSeek Harness session is really running on. A chip in the header names the model and reasoning effort of whichever conversation is open — including a subagent's — a dropdown marks which child carries its own route instead of inheriting its parent's, and sidebar hover cards say what each conversation actually did.

Install

From the GUI, paste this into Add plugin:

dsh-subagent-model

Or from a terminal:

dsh plugin add dsh-subagent-model

No build step is involved and there are no dependencies at all. Every entry point ships as committed source: the Host reads src/client.js and serves it to the page verbatim, so pnpm never has to run anything and you are never asked to allowlist a build. Restart the profile afterwards and the chip appears.

The only dependency-shaped field is a peer range on the runtime itself — @deepseek-ai/dsh-app-boot: >=0.2.0-rc.2 <0.3.0 — and that is the field DSH actually evaluates: a plugin whose dsh peer range does not satisfy the running runtime is skipped at boot, with a warning naming the mismatch. Accept it on an unexpected runtime with dsh plugin allow-version, or wait for a release that fits the range.

Installing by other routes
RouteCommand
npm, the defaultdsh plugin add dsh-subagent-model
Pinned to a versiondsh plugin add dsh-subagent-model@1.0.0
Pinned to a commitdsh plugin add github:mirelconstantin/dsh-subagent-model#<sha>
Tarballdsh plugin add ./dsh-subagent-model-1.0.0.tgz
Local checkoutdsh plugin add /absolute/path/to/dsh-subagent-model

What it shows

seatwhat it showswhere
header chipmodel: <id> (effort) for the active Sessionbeside the subagent pill in the conversation header
subagent treethe shipped subagent dropdown, with a route marker per rowthe subagent-catalog header cell
hover-card rowsmodel, effort, work done, active time, tokens, cache, context window, task, kindinside every Session's hover card in the sidebar
per-turn rowwhich model each turn actually ran on, and what it switched fromthe action row under a completed turn

The chip's tooltip also names the reasoning efforts a model offers and which one is its default, read from the live model catalog. A model the catalog does not list adds nothing: the catalog is advisory and routing accepts ids it does not advertise, so its absence is never treated as a fault.

The effort appears only when one is set — it is optional on the same route record the model comes from, and a session with none configured stays compact.

Presentation only. The model, its tools, and its session log are untouched: no tool is registered, no service is published, and nothing the model can observe changes.

The problem it solves

DeepSeek Harness routes a session by model, and a subagent may run a different one than its parent — but nothing on the page said so. Two things follow:

A subagent's model was invisible until you opened it. Session projections reach the browser in two places, and they hold different sessions. projectionsBySession only carries a session whose projection store is resident in the client, and stores are created lazily (dsh-api-session-controller/lib/index.js:2525) — so a subagent the user never opened was simply absent. The list rows carry the same values under byId[id].projectionValues (:2942, :3521, :3549), synthesized from the parent's subagentCatalog (:3518-3542), which is what covers a child that is listed but never loaded. Reading both sources is the whole fix.

An inherited route was unrecoverable. With model selection off — the subagent default (dsh-tool-subagent/README.md:47) — every child inherits its parent's route (:67), and that route is nowhere in the child's own log. The plugin walks parentId up the lineage until an ancestor records an explicit one, so an opened subagent names a real model marked (inherited) rather than nothing.

Where the value comes from

The modelSelection Session projection, which the harness already registers and already ships to the browser:

dsh-api-session-controller/lib/types/model-selection-projection.js
  stateSchema : { lastUsed, pending }   (routes carry optional reasoningEffort)
  wire view   : { lastUsed, next }      // next = pending ?? lastUsed
  registered  : lib/index.js:2114 (installModelSelectionProjection)

lastUsed is folded from each request's own request/header config, so it is the route the session actually used, not the one it was configured with. next is the pending selection while one is queued and lastUsed otherwise, so reading next answers "which model is this session on" in both states. That is the same read the shipped Agent Team row performs (dsh-experimental-client-ui-agent-team/lib/client.js:89).

Subagents, honestly

Model selection is off by default, so a subagent reading Inherited from parent is the expected case, not a bug — nothing was overridden. What differs by construction:

  • A continuable subagent with its own route snapshots agentProvider / agentModel / agentReasoningEffort in its descriptor (dsh-subagent/lib/types/descriptor.js:38-45), so its explicit model is recovered and shown in full.
  • A one-shot subagent with its own route deliberately omits the agent route (:37, :118-126) because cold resume does not need it. An explicitly selected one-shot model is therefore not durable, and the plugin says Inherited from parent rather than guessing. That is a limit of the durable format, not of this plugin.

To pin every subagent's model, set modelSelectionSettings: true on the subagent tool and pass a route per call; list_subagent_models then becomes available for discovery.

Four seats, and why the tree replaces rather than adds

The chip, the hover rows and the per-turn row ADD a cell each, so nothing shipped is displaced. The tree cannot: the shipped CatalogRows renders no per-row slot (dsh-client-ui-subagent/lib/client.js:164-338), so a marker beside a subagent requires owning the subagent-catalog cell itself. It therefore claims that id at a written priority: -9998, which is what makes a list slot keep it instead of the shipped occupant (dsh-client-ui-slots/lib/index.js:221, :278-293) while a duplicate id is rejected only at the same priority (:183-184).

That number is -9998 and not -9999 on purpose: the sibling dsh-subagent-tree claims the same cell at -9999, and co-installed the two would collide at one priority and throw, blanking the header.

That takeover is the one decision worth knowing about: removing the SHADOWED_CELL_ID registration in src/client.js restores the shipped dropdown and loses the markers, and nothing else depends on it.

slotdeclared bythis plugin
conversation.session.header.actionsdsh-client-ui-conversation/lib/index.js:18268-18271id 'subagent-model' (chip), and id 'subagent-catalog' at priority -9998 (tree)
sidebar.session.row.hoverdsh-client-ui-workspace/lib/index.js:4322-4325id 'subagent-model'
conversation.chat.turnTaildsh-cordis-client-runner/lib/client.js:2564-2566id 'dsh-subagent-model-turn-route' (per-turn row)
conversation.chat.nodedsh-client-ui-chat/lib/client.js (keyed by node kind)the same kind, visibility: hidden — the store the tail row reads

What each seat tells you

The chip names the effective route of whichever conversation is open, root or subagent, and marks it (switching) while a queued selection has not been consumed, (inherited) when it came from a parent. Its tooltip adds the model breakdown across descendants — 3× deepseek-chat, 2× deepseek-reasoner — and, when a generation actually splits that breakdown, the same breakdown cut by generation. That is the integration with the sibling dsh-subagent-tree: both facts come from one walk over subagentCatalog, so they read in one place.

The tree puts a marker next to a subagent whose route is its own, and a quieter one when it merely inherits. With selection off every child inherits, so marking each one would mark all of them and say nothing — a marker therefore appears only for a subagent that differs, and its tooltip names the parent's model for comparison.

The hover card answers "what was this conversation". Each row is read from a projection the client already holds and appears only when its value is present, so a zero never reads as "did nothing": model and effort, turns and steps, active time, generated tokens, the delegating agent's own task label, and the mode that decides whether the session can be continued.

Per-turn model history

request/header is the event recording each request's route. Nothing in SessionProjectionMap ships it, so the chip reads the modelSelection projection — but the event journal itself reaches the browser unfiltered, and request/header is in it (dsh-api-session-controller/lib/client.js:166, the stream at :398). The harness's own chat view already reads it that way (dsh-client-ui-chat/lib/client.js:9389-9394).

So each turn's real route is recovered client-side, with no Host work: a request/header conversation Definition folds the events into a per-turn map, and a conversation.chat.turnTail entry reads it. Because the opening journal frame is a snapshot, a session opened before the plugin mounted still gets its history rather than only events from that moment on.

reason decides where a turn's route is anchored. initial, resume and series all anchor; a bare change is a mid-series re-route and does not, unless it carries startsSeries. An absent reasoningEffort is not "off" — a adapterDefaults.reasoningEffort means the adapter supplies it, and the row says so rather than implying no effort.

The older Route change <from> → <to>, not yet applied row is the disagreement between next and lastUsed, and still reports a queued switch that no request has consumed yet.

Honesty about missing data

Four states are kept apart, because collapsing them would print a lie:

statechiphover card
loaded, model knownmodel: deepseek-chat (high)Model deepseek-chat (high)
loaded, no request yet(nothing)Model No model yet
listed in a catalog, never opened(nothing)Model <model>, or Inherited from parent
neither store knows it(nothing)(nothing)

Files

pathrole
cordis.patch.ymlexactly one Loader row — the anchor
src/index.jsHost anchor; publishes nothing
src/model.jsthe pure logic, as plain ESM for the unit suite
src/client.jsthe browser half; registers all seats
src/client.model.jsthe same pure logic as a browser chunk the half loads
src/client.route-history.jsthe request/header fold and the per-turn route map
locale/{en,ro}.jsondictionaries for namespace dsh-subagent-model
scripts/unit.mjsnode --test suite for src/model.js and locale parity
scripts/check-plugin.mjsmanifest, patch, exports, browser-half contract

No build step, no bundler, no transpiler and no installed dependency — the one declared peer is the runtime this plugin was verified against, and DSH resolves it from the installation.

Why the pure logic lives in a second chunk

A classic script cannot import, but it does not have to: the require the factory is handed carries an async method that fetches a package-local chunk (dsh-client-modules/lib/client.js:707-713), and CLIENT_CHUNK = /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/ (:169, served at :915-932) is the pattern that file has to match. Three shipped packages already ship a second chunk this way — dsh-client-ui-sidebar-terminal and dsh-client-ui-sidebar-documentpreview — so this is a supported shape, not a workaround.

src/model.js is a plain ESM module, which no browser script can reach, so src/client.model.js holds the same functions as a self-registering chunk and src/client.js loads it once. The duplication was previously the whole cost of shipping both; it is now confined to one chunk whose exports are checked against src/model.js.

Every seat renders nothing until that chunk resolves, which is what keeps a failed fetch from taking the conversation header down. The trade is that the chip appears a tick later than a sync import would allow; nothing else on the page waits on it.

The browser half

Two loading rules are load-bearing, both measured on a live page and not assumed. See src/client.js for the full notes.

inject stays empty. Naming slots makes fiber.await() park the row until the renderer provides it, while the renderer waits on the UI packages this row sits beside. The two wait on each other and the whole boot fails with web boot: 1 entry did not activate …, taking the entire GUI down. With an empty gate apply runs before slots exists, so whenService polls with setTimeout. Do not substitute queueMicrotask: a microtask poll re-queues itself ahead of the macrotasks every package activation depends on, starves the boot, and reproduces the same failure.

ro is added to the language catalog. en and zh are shipped catalog languages; ro is not, and the fallback chain walks the catalog rather than the registered dictionaries — so a ro dictionary alone is unreachable until locale.addLanguage adds the language. Both locale/en.json and locale/ro.json ship the same key set, and scripts/check-plugin.mjs fails the build if they drift apart.

Verify

node --test scripts/unit.mjs    # 84 tests: model logic, cycles, depth grouping, locale parity
node scripts/check-plugin.mjs   # 31 checks: manifest, dsh peer range, patch, exports, browser contract

Licence

Apache-2.0. See LICENSE.

Comments

Loading…

From the same category

billion-context

by ranxianglei

基本稳定可用 100K tokens is enough. Universal context-compression proxy for ALL AI coding agents,10w上下文足矣

Sessions & MessagesManifest valid

★ 929

↓ 465.9k/wk

MIT

TypeScript

Oct 12, 2026

dsh plugin --profile web add billion-context

by Han-1413141

DeepSeek Harness session cost meter plugin: session/daily cost, budget, history, OpenCode Go quota, official & custom-provider balance, Codex-like token heatmap, peak/off-peak pricing with pre-switch

Sessions & MessagesManifest valid

★ 391

↓ 24.3k/wk

MIT

JavaScript

Oct 12, 2026

dsh plugin --profile web add dsh-cost-meter

by Nwflower

Import 14+ external agent chat histories (Claude Code, Codex, ChatGPT, Cursor, Gemini, Reasonix, opencode, ZCode, Grok Build, OpenClaw, Pi, Hermes, Kimi CLI, DSH) into DeepSeek Harness as resumable se

Sessions & MessagesManifest valid

★ 226

MIT

JavaScript

Oct 7, 2026

dsh plugin --profile web add dsh-chat-import

by Totoro-qaq

DeepSeek Harness plugin for previewable cross-preset session migration. Fixed-schema handoffs preserve state, source-model intent, and unresolved images; the original session stays untouched.

Sessions & MessagesManifest valid

★ 165

MIT

JavaScript

Oct 11, 2026

dsh plugin --profile web add dsh-plugin-bridge

by Anionex

deepseek harness对话和代码状态回退插件 | DSH — rewind conversation and workspace state, powered by a persistent Change Ledger

Sessions & MessagesManifest valid

★ 132

BSD-3-Clause

JavaScript

Oct 1, 2026

dsh plugin --profile web add @anionex/dsh-turn-rewind

by SiriLee

DSH 插件:真正便捷无感的同窗口内对话回退,从不新建分支;自带轻量工作区备份,可一并还原文件(完整 Claude Code /rewind 语义)。 · DSH plugin: genuinely effortless in-window conversation rewind — never forking a new session; ships a lightweight workspac

Sessions & MessagesManifest valid

★ 125

↓ 5.5k/wk

MIT

TypeScript

Oct 9, 2026

dsh plugin --profile web add dsh-rewind-plugin