- banner/intro/footer three-part closure-factory wrap (verbatim from
packages/client/tsdown.client.ts), intro now owns var module/exports
- deps.neverBundle/alwaysBundle guards: host keeps production deps
external, client keeps only module-table specifiers external
- bundle purity gate: any non-requested @deepseek-ai/* value import is
a build error (INLINE_SAFE / vendored-library / generated-remote
allowances mirrored)
- NODE_ENV + import.meta.env defines for inlined node-idiom deps
- client sourcemap on, matching the official artifact contract
- client smoke test now runs the bundle verbatim through the
__ModuleLoader__ handoff instead of slicing the factory body by
exact output strings (decoupled from bundler layout)
Adapt to DeepSeek Harness 0.1.1's multimodal pipeline (and the vision
capability of DeepSeek-V4-Flash-Vision-Exp): a user message carrying
durable image refs expands into a card layout — prose in the text card,
image attachments as thumbnail cards (name, normalized + original
dimensions, stored size), and unrecognized blocks as raw JSON. Image
blocks in assistant messages and tool results (e.g. read_image output)
now render too, instead of being dropped or dumped as JSON.
Thumbnails load through the harness conversation service's resolveImage
(the same session-authorized loader the chat history uses); when the
service is absent the cards degrade to metadata only. The text card
uses an un-padded RichCard variant so all cards align at one indent.
Sanitize the contextTimeline projection into a render-safe shape at the
services boundary (collections -> arrays, non-object entries dropped,
current -> numeric breakdown, wrong-typed scalars dropped) so a corrupt
or failed host payload degrades to empty sections instead of throwing
during render, keeping every usable piece of data visible.
Wrap the Context tab and /context dialog bodies in a React error
boundary (styled error card + Retry) as the backstop for any residual
render error; harden fmtTime against invalid timestamps, headersOf
against malformed epochs, and the cache-hit/cost math against NaN.
Add client regression tests: corrupt payloads render with usable data,
the modal degrades, and a throwing projection reader lands in the error
card.
dsh 0.1.1-rc.1 changed the session-projection unit contract: registration
now reads stateSchema (persisted fold state) + wire (client view), and a
unit without a wire block is treated as host-only — its value is never
delivered to the browser. The units still emitted only the pre-0.1.1
fields (schema + top-level view), so on 0.1.1-rc.1 the Context tab stayed
on its loading screen forever: contextTimeline/contextHeaders never
arrived through useProjection.
Emit BOTH contracts on each unit: schema/view for dsh <= 0.1.0-rc.8, and
stateSchema/wire for dsh >= 0.1.1-rc.1 (each registry reads its own
fields off the same definition and ignores the extras). Added a strict
timelineStateSchema for the persisted state (the cold-read rung also
parses checkpoint rows with it) and mirror checks in host.test.mjs.
Verified against the real registries of both versions (snapshot
delivery, change feed, checkpoint, viewCheckpoint, restore) and the real
plugin-install flow on the 0.1.0-rc.7 and 0.1.1-rc.1 CLIs.
zod is already a dependency of @deepseek-ai/dsh-session-projection,
so the host dsh environment always provides it. Moving it to
peerDependencies avoids duplicate zod installations and lets the
plugin resolve the same zod instance as the framework.
- dependencies: remove zod (now empty)
- peerDependencies: add zod ^4.4.3
- peerDependenciesMeta: mark zod as optional
- tsup.config.ts: update comment to reflect peer dep status
- version: bump 0.20.0 -> 0.20.1
Stats board gains an 预估费用/Est. Cost cell pricing the complete
session's billed tokens at hardcoded DeepSeek V4 list prices: the host
folds per-request usage totals (peak/off-peak split, cache hit/miss
input, output incl. reasoning) from the session log, flash and pro on
any provider, displayed in the locale's currency. The cell carries a
"?" marker whose hover bubble explains the whole-session estimate and
lists the per-1M-token price table straight from the pricing table, so
the printed rates can never drift from the math.
Lower @deepseek-ai/dsh-client-ui-primitives, @deepseek-ai/dsh-session and
@deepseek-ai/dsh-session-projection peer and dev deps from ^0.1.0-rc.8 to
^0.1.0-rc.7 so the package declares compatibility with the DSH Community
Market desktop runtime (currently @deepseek-ai/dsh 0.1.0-rc.7).
The contextTimeline fold persisted `undefined`-valued properties (model/provider/lastModel/contextWindow at init, pendingShadowedSeqs set to undefined on consume, and turn/step written as undefined when absent from assistant/message data). The harness projection cache snapshots the whole session checkpoint through the lossless-JSON snapshotter, which rejects any `undefined` property — so every projection-cache write failed and the `title` projection (session list) degraded after restart. stateVersion 3→4 discards stale rows; a regression test now drives the real snapshotJsonValue.
Fixes#5, #7.
Co-authored-by: kiligzzz <kiligzzz@users.noreply.github.com>
Co-authored-by: Bowen Liang <liang.bowen.123@qq.com>
The context browser's detail cards (system prompt, tool description,
user/assistant message and injection bodies) gain a segmented Raw |
Markdown switch at the card's top-right corner — the same pill chrome
as the trend chart's Step/Turn control. Markdown is the default view,
rendered through the harness's shared MarkdownText (GFM, sanitized,
resolved from the platform module table — zero plugin-side markdown
dependency); Raw restores the exact source <pre>. Each card owns its
mode state via the new shared richText kit (explicit render props, no
React context), so two open details switch independently. Reasoning
blocks, tool-call args and tool results stay raw.
Markdown chrome is scoped to the detail cards: base size 13px (a
proportional face matching the raw view's 12px monospace in visual
weight), headings collapse to a weight-only bump, inline code keeps
full size and is pinned under the paragraph line so wrapped chips no
longer stick across lines.
Replace the hand-rolled esbuild script (scripts/build.mjs) with a single
tsup config that already existed but never ran: tsup re-bundles the config
into a temp .mjs, so import.meta.url pointed at the temp dir and the
package.json read failed, silently leaving no entries. Read the manifest
from process.cwd() instead. The host half now keeps @deepseek-ai/dsh-session
external (resolving to the harness's single runtime instance) instead of
inlining it; the client half keeps the __ModuleLoader__.load wrap.
Align deps with the official dsh convention: runtime harness packages
(cordis, dsh-session, client-ui-primitives, react) move to peerDependencies
with dev mirrors (client-only ones optional); zod stays a dependency;
exact rc pins become caret ranges (^0.1.0-rc.8) so rc9/0.2 releases don't
break installs. Drop the esbuild devDependency (tsup brings its own) and
add tsup itself.
The selected (locked) bar was declared before the hovered rule at equal
specificity, so the dashed hover frame kept overriding the solid lock
frame while the pointer was still on the just-clicked bar — the lock only
became visible after the pointer left. Declare .lc-bar-selected last and
use the label-primary (near-black) frame so clicking immediately shows
the black solid frame and clicking again reverts to the dashed one.
Pull the tab's inline 当前构成 card (headline + composition bar with the
auto-compaction reserve band + legend + the ranked tool-chip bridge) into
a shared pure CurrentComposition component. The Context tab and the
/context modal now render the SAME card, and the modal wires the tool
chips into its embedded Context browser and drops the duplicated
model/provider from the dialog header (the card subtitle carries it).
The /context dialog drops the last-10-turn history chart (TrendChart +
RequestDetail + markers) and hosts the shared ContextBrowser card
instead, so step picking, accordion, reconstruction and paging ride the
same component as the tab. The modal now reads the contextHeaders key for
the browser's system/tools sections, its command description is updated,
the dead lc-modal-trend rule is removed, and the modal render test is
rewritten to assert the embedded browser.
The current-composition bar gains a striped band over the rightmost
(1-ratio) of the window (AUTO_COMPACT_RATIO = 0.8, mirroring DSH's
dsh-compaction-basic default thresholdRatio, which DSH does not publish
to plugins): hovering it explains that this area is auto-compaction
headroom, normally not actually occupied, and clears the segment hover.
Rendered whenever the window is known, on both the overview card and the
/context modal bar, laid out in window units so it stays correct once use
passes the threshold. Mirrored/detail bars stay clean.
The occupied-region frame turns from a dashed black box into a SOLID
light-gray box (label-tertiary), and the free track mirrors it with a
dashed 2px box (border, not the old 1px inset shadow) — the two strokes
now share one 2px weight with roles inverted. box-sizing on the free
track keeps the hover border from shifting its width.
Each category row gains two direction-colored pills — an element-count Δ
after the item count and a token Δ hugging the left of the token figure —
each hidden while its figure held. Both baseline against the PREVIOUS
turn's last request regardless of step/turn granularity (live = last
request), with a short caption in the card header stating that baseline.
The count pill's redundant tooltip is dropped now that the token swing is
a visible pill.
The Current Composition legend stops being wrapped inline chips and
becomes an auto-flowing grid: each category is a label-left /
numbers-right cell (as many columns as the card fits, fewer as it
narrows) with the name bold at rest and a pointer cursor on hover.
The chip hover-link assertion now compares the label text via textOf,
since the label is its own span.
The context stats board gains a right-most 缓存命中 cell that reuses the
token-meter tokenUsage projection read through the standard useProjection
seat — the exact same data and buckets as the chat stats line below the
input box. The share is truncated (not rounded) to two decimal places,
with a float-noise guard; absent the projection it shows a dash. Adds the
bilingual stats.cacheHit label and test coverage (sixth cell, 80.00%
case, and a truncation proof).
A 320px cap (matching the raw JSON block) swallowed most real tool
descriptions whole — they read as a wall of text instead of a header-style
prose summary. 160px gives ~8 lines visible at the body font, so a
reader sees the gist at a glance and scrolls for the rest.
scrollbar-width: thin keeps the cap from looking heavy when the body
stays short enough to fit.
Each tool row in the Context browser now reads as three stacked titled
sections:
* Description - the prose, in its own card
* Parameters - one row per JSON-Schema property (name, type, required
chip, description); type labels cover the common
vocabulary (array<inner>, object{N}, enum, anyOf/oneOf
unions)
* Raw JSON - collapsed behind a slim toggle; the <pre> only mounts
when opened, so the default body stays compact and the
JSON.stringify pass runs on demand instead of every
render
The two top sections share one card chrome (bordered block with a small
title), so a reader can scan them as siblings rather than mixed prose and
UI.
Bump @deepseek-ai/dsh-session, dsh-session-projection, dsh-client-ui-primitives
to 0.1.0-rc.8 to match the released @deepseek-ai/dsh@0.1.0-rc.8 contract.
Drop @deepseek-ai/dsh-token-meter: only referenced in comments, never
imported. Runtime occupancy is read from the official 'contextPressure'
projection key registered by dsh itself; pricing is computed locally in
src/host/pricing.ts.
@deepseek-ai/cordis stays at ^4.0.1 (already matches dsh 0.1.0-rc.8).
List the HeaderTools largest-first instead of the producer's header
order, mirroring the overview's top-tools chips. Tests: extended the
header fixture with a deliberately out-of-order pair and assert the
rows come back ranked.
Make the overview '工具定义 Top' label, its '等 N 个' overflow link and
each tool chip clickable (hover underline) and bridge them into the
Context browser: the label/overflow link open the tools category, a chip
also expands that specific tool's row. One-shot focus is applied by the
browser and cleared back so the same chip can be clicked again.
Surface-node category rows in the Context browser were shown oldest
first; reverse them so the newest timestamped message is at the top,
mirroring the NodeList card.
Card titles are bare text nodes inside the .lc-card-title flex row;
when a longer title (e.g. Context History) shares the row with the
hint and the Step/Turn toggle, it could wrap in place. Wrap every
title in an explicit span with flex: none + white-space: nowrap so
titles never break, while sibling hints/toggles keep their layout.