Commit Graph
222 Commits
Author SHA1 Message Date
bowenliang123 eb5c8ebf2d fix(client): wrap long image attachment filenames instead of truncating 2026-08-22 18:08:35 +08:00
bowenliang123 9859f4702a chore: bump version to 0.24.0 2026-08-22 16:31:55 +08:00
bowenliang123 9dccec4e85 docs: composition card reads the official meter projections; image cards show token costs 2026-08-22 16:31:13 +08:00
bowenliang123 c003916360 feat(client): two equal-width image attachment columns with token badges
The attachment grid becomes a two-column equal-width grid
(repeat(2, minmax(0, 1fr))) and every image card gains a token-cost
line (≈N tokens, with an explanatory tooltip) estimated through the
official DeepSeek image-size conversion on the stored dimensions.
2026-08-22 16:31:13 +08:00
bowenliang123 d3cdc263d6 feat(client): read the official contextBreakdown projection for the composition card
The composition legend and tooltips now show the exact system/tools/
messages counts of the chat composer ring's click-open panel (the
token-meter's contextBreakdown projection) instead of the fold's own
sums re-anchored to the provider total: parts carry the heuristic count
(raw) for display beside the provider-anchored bar width (value), and
the message bucket subdivides into the four surface categories by the
fold's per-category ratios, always summing exactly to the official
message figure. Absent the projection (older harness), the fold's own
sums serve unchanged.
2026-08-22 16:31:05 +08:00
bowenliang123 d9c09782fc feat(host): price image blocks by DeepSeek's official image token calculator
The token-meter heuristic prices image blocks through its generic JSON
branch (~40 tokens for the durable ref), while DeepSeek's vision model
actually bills 117-384 tokens per image by pixel dimensions. Port the
official docs image-token calculator (patch grid 14px, downsample x3,
384-token cap, minPixels 384^2) into src/shared/imageTokens.ts and price
image blocks with it in the fold; unknown dimensions keep the JSON
fallback. stateVersion bumps to 6 so cached rows refold.

The differential fold check replays a parallel image-delta surface so
image-bearing logs still match the official contextBreakdown exactly,
and adapts to the 0.1.1 wire.view contract.
2026-08-22 16:30:55 +08:00
bowenliang123 1ddd99978f docs: update compatibility section in AGENTS.md to clarify runtime dependencies 2026-08-22 13:36:03 +08:00
bowenliang123 1fd605d2d2 docs: add dsh version compatibility contract to AGENTS.md 2026-08-22 13:29:16 +08:00
bowenliang123 641322a351 chore: bump version to 0.23.0 2026-08-22 04:34:10 +08:00
bowenliang123 8066ce5b86 docs: add guidelines for committing mixed changes separately 2026-08-22 04:34:08 +08:00
bowenliang123 75d1ec8917 test: shift ContextView hook slot indices for the focusTurn state
The clickable turn strip added a focusTurn useState between granularity
and hoverCat, shifting hoverCat to slot 6 and toolFocus to slot 8; the
composition-link and tool-chip-bridge specs still addressed the old
slots.
2026-08-22 04:33:45 +08:00
bowenliang123 ad1e6872ba feat(client): collapsed browser rows lead with an inverted tool breadcrumb + call summary
Tool-result and call-bearing assistant rows now open with an inverted
mono chip naming the tool(s) (label/surface tokens swapped — black on
white in light mode, white on black in dark), multiple calls joining as
a breadcrumb (bash › write). The preview then carries the call's own
summary line: a bash-style `description`, else the target path of
path-taking tools (edit/read/write via file_path/path/filePath), falling
back to the reply text or the generic label. The redundant 'calls: …'
preview is gone — the chip says it once.
2026-08-22 04:31:34 +08:00
bowenliang123 654eca8cdb feat(client): tool calls in assistant messages and tool results share one arg card
Both render the call as a card whose head names the tool (→ / ←) with
the argument count, the body lists arguments as name/value rows — the
type column is gone, the name sits in a narrow wrapping left column,
the value wraps in a mono right column (height-capped). The tool name
steps up to 13px mono. The tool result's raw lc-br-call block is
replaced by the same card.
2026-08-22 04:16:56 +08:00
bowenliang123 bef73a5723 feat(client): clickable turn strip — click a turn block to jump to turn granularity and center it 2026-08-22 03:58:26 +08:00
bowenliang123 db56081cd0 fix: keep the trend chart scrollbar off the turn strip
Reserve 8px below the scrollable chart so the horizontal scrollbar no
longer overlays the turn labels.
2026-08-22 03:50:57 +08:00
bowenliang123 c55ab3da3d perf(client): memoize trend-chart bars; fix stale retention comment
The retained request log is bounded by maxKeptTurns/maxRequestSteps
(defaults 300/1500), not 160 as the comment claimed — a long session's
chart is thousands of bar nodes, and every bar hover re-rendered all of
them through ContextViewBody.

Extract the bar as a React.memo component (hover flips flags on at most
two bars; req/marker identities stay stable between projection pushes)
and memoize the parent's turn-aggregation and marker attachment so turn
mode stops allocating fresh bar props on every hover render. Bars carry
data-seq for identification now that the element key lives on the
memoized component; the fake-React test beds learn memo().
2026-08-22 03:49:32 +08:00
bowenliang123 5967200a9b perf(host): consume callNames entries on tool-result fold
The callId→name map is looked up exactly once — when the result event
folds in — but entries were kept for the session's whole lifetime. As
persisted state, the map was serialized on every projection checkpoint
and shallow-copied by every fold step's lazy clone.

Rebuild the map without the consumed ids at lookup time (no dynamic
delete, per repo lint); consume-once holds it at pending-call size, so
the copy is trivial.
2026-08-22 03:45:50 +08:00
bowenliang123 903b779f89 docs: emphasize the importance of modular code for maintainability in AGENTS.md 2026-08-22 03:38:50 +08:00
bowenliang123 b5b96887c5 docs: clarify GitHub Actions workflow for automated publishing in AGENTS.md 2026-08-22 03:36:23 +08:00
bowenliang123 ad1677f579 docs: add versioning information to AGENTS.md 2026-08-22 03:35:06 +08:00
bowenliang123 dd81b5b437 ci: fold the plugin check into the publish job
One job instead of two: the dsh-plugin-checker step now runs right after
checkout inside publish, dropping the needs: handoff (saves a runner
spin-up per release; the check still gates every later step).
2026-08-22 03:33:18 +08:00
bowenliang123 f6ec9434d9 chore: bump version to 0.22.2 2026-08-22 03:28:03 +08:00
bowenliang123 47b7d9eacd feat(client): assistant tool calls render as parsed argument cards
A tool call inside an assistant message's blocks now forms its own card,
mirroring the tool definition's parameter card: the head names the call
target with the parsed argument count, the body lists each argument as a
name/type/value row (values render raw when strings, JSON otherwise).
Arguments that are not a parseable JSON object fall back to the raw
payload inside the same card.

The spec now arms two calls — one parseable, one not — and asserts the
card chrome, the parsed row, the count badge, and the raw fallback. The
view-bed text helpers learned to read number children (React renders
them as text), which the count badge needs.

No new CSS: the card and rows reuse the tool-schema classes.
2026-08-22 03:25:26 +08:00
bowenliang123 cccad03224 chore: bump version to 0.22.1 2026-08-22 03:17:30 +08:00
bowenliang123 8692c31162 build: adopt the official lint stack (oxlint + tsgolint, type-aware)
Mirror dsh's own lint setup (oxlint 1.76.0 pinned exact, oxlint-tsgolint
7.0.2001 with options.typeAware, correctness category off, hand-picked
rule list; jsPlugins: eslint-plugin-sonarjs + @stylistic/eslint-plugin
with the official style profile: indent 2, semi never, single quotes,
comma-dangle multiline, max-len 140). Scope mirrors official too: src TS
only, generated/bundled output (lib, *.js, *.mjs, *.config.ts) ignored.

Wiring: pnpm run lint / lint:fix scripts, plus a Lint step in the
release workflow between Build and Test.

Fixes applied (runtime behavior unchanged; verified against dsh
0.1.0-rc.7 and 0.1.1-rc.2 wire shapes, see below):

- autofix pass: arrow-parens, comma-dangle, eol-last, indent, quotes
- restrict-plus-operands: 31 string+number concatenations -> template
  literals (restrict-template-expressions allows numbers)
- no-unused-vars: drop unused imports/destructured names
- no-unnecessary-type-conversion: drop redundant String() where the
  field is already string-typed
- no-unsafe-*: type the fetch-json boundary in latestVersion, narrow
  unknown before spread/cast instead of trusting any
- no-unnecessary-condition, three honest categories:
  * boundary sanitizers (services.timelineOf/headersOf, RawBlocks,
    headers.recordOf) now read through Record<string, unknown> instead
    of casting first, so the defensive checks are type-meaningful again
  * post-sanitize consumers (contextView/headline/assemble/browser)
    drop the fallback-empty-array on collections the sanitizer
    guarantees
  * array-index-undefined gaps (fold turn attach, headers dedupe) use
    .at() so the past-the-end guard is real to the type system
- no-non-null-assertion: stackedBar reserve carries the checked max
- no-floating-promises: void the fire-and-forget version check
- intentional-contract suppressions with rationale: react.ts keeps its
  injected require (closure-factory loader contract)
- type-honest hardening kept deliberately: MessageSource.sections
  entries stay nullable (foreign plugins author snapshot sources) and
  isInjection still rejects a null source — foreign message payloads
  must never crash the fold

Cross-version audit (0.1.0-rc.7 vs 0.1.1-rc.2, message parsing focus):
- core/session types.ts: only additive optional interrupted on
  assistant/message (unread by us); every event payload we fold
  (request/header, request/context, tool/call, tool/result,
  user/message, assistant/message, compaction/*) is byte-identical
- llm types.ts / message.ts / content.ts block shapes: identical
- token-meter estimate.ts: identical, and official has NO null-entry
  guards on content blocks — our removed guards match official trust,
  our retained fallback-empty-string / typeof reads stay a leniency
  superset
- deriveEventMessage (core/session surface.ts): identical
- ui-conversation chat-nodes contract: unchanged; our client reads are
  minimally-typed unknowns by design
- tool/call callId is a branded string in both versions, so the
  narrowed typeof guard drops only an impossible coercion
2026-08-22 03:10:41 +08:00
bowenliang123 680951aed9 test(client): assistant thinking/answer split into cards with independent raw/markdown switches
The assistant-message detail renders each thinking / answer block as its
own card (tool-schema chrome) carrying its own raw/markdown switch, so a
mixed reply's parts toggle independently; tool calls stay raw. The spec
arms an assistant conversation node (reasoning + text + tool call) and
asserts the two cards, their default markdown views, per-card switching
independence, and the raw tool-call row.
2026-08-22 02:41:36 +08:00
bowenliang123 1a8a240d43 fix(deps): zod is a required peer, not optional
The host half value-imports zod (Config schema, timeline/headers wire
validators) and the build keeps it external, so a profile install without
zod would fail at plugin load. optional:true would silence the one
warning that catches that. react and dsh-client-ui-primitives stay
optional: only the browser half uses them and the module table provides
them there.
2026-08-22 02:39:51 +08:00
bowenliang123 598a2e06fa refactor(client): move styles to a real stylesheet through the official CSS channel
- tsdown.config.ts gains the three CSS virtual loaders mirrored verbatim
  from packages/client/tsdown.client.ts: .module.css (lightningcss-hashed
  class map + tagged injection), .css?inline (compiled text), and plain
  .css (unhashed tagged injection), each registered as a watch dependency
- src/client/styles.ts (223 rules as a string array) becomes
  src/client/styles.css — real stylesheet authoring, minified by
  lightningcss at build (client bundle 134.8 -> 130.3 kB)
- injection now happens at factory materialization with the official tag
  shape (data-plugin + data-plugin-css + dedupe guard), replacing the
  hand-rolled ctx.effect; the HMR receiver owns removal by data-plugin,
  so the hmr spec pins that lifecycle instead of fiber disposal
- Decision: class names keep the flat lc-* namespace (the anti-collision
  discipline, per the research note) instead of per-component hashed
  modules — the stylesheet's rules are interconnected across components
  (parent:hover child, shared state classes) and 241 test selectors read
  literal classes off the built bundle, which a content hash would make
  unknowable; the hashed .module.css channel is mirrored and available
  for future self-contained components
- globals.d.ts declares the *.css / *.module.css module shapes for the
  strict typecheck
2026-08-22 02:39:19 +08:00
bowenliang123 49f1439dae test: migrate to vitest, split the monolithic client spec into modules
- vitest is the runner (matches the official harness); the two bare-node
  scripts and the manual repro become 13 spec files under tests/
- the 1637-line client.test.mjs splits into focused modules (chart /
  browser / linkage / composition-link / tool-chip-bridge /
  browser-autoload / images / modal / resilience), each booting a fresh
  view bed from tests/helpers/viewBed.mjs — the stateful fake-React
  harness, fake ctx, fixtures, and tree walkers now live in one place
- tests/hmr-safety.client.spec.mjs (jsdom lane, @vitest-environment):
  fiber dispose removes every registration — locale dicts, slot rows,
  trigger source, provide contribution, and the plugin-owned style tag
- host spec gains the same dispose-removes-registrations pin for the two
  session-projection units
- the real-React jsdom mount (was npm run repro, broken since the tsdown
  switch by its own string surgery) now runs the bundle verbatim through
  __ModuleLoader__ and is part of the suite; fixed two latent harness
  bugs along the way: the slot-register capture was overwritten by the
  later overlay registration, and the granularity query needed scoping
  to the trend row (the events card reuses the pill-button classes)
- bundle evaluation in all specs runs the artifact verbatim via the
  boot handoff — no string surgery, decoupled from bundler layout
- scripts.test is now typecheck + vitest run; scripts.repro is gone
2026-08-22 02:33:28 +08:00
bowenliang123 9905006ef8 feat(host): publish type declarations for the host contract
- tsdown host config now emits lib/index.d.ts (Config zod schema,
  timeline/header/snapshot wire types, plugin entry surface)
- package.json gains top-level "types" and a types condition on the
  "." export, mirroring the official exports shape
- the d.ts inlines the type-only dsh-session-projection wire layer as
  ambient module declarations, so consumers typecheck without extra
  installs; verified with a synthetic consumer (tsc nodenext, full lib
  check) — remaining upstream d.ts lib requirements unchanged
- ./client keeps no types condition: its surface is the CJS
  module.exports handoff consumed by the browser loader, not TS
  importers (the official client bundle config is likewise dts:false)
2026-08-22 02:16:55 +08:00
bowenliang123 021db5f2a2 build: migrate tsup to tsdown, mirroring the official client-bundle preset
- 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)
2026-08-22 02:14:44 +08:00
bowenliang123 7df335114a docs(research): dsh monorepo and plugin practices investigation 2026-08-22 02:09:21 +08:00
bowenliang123 798d902c4d chore: bump version to 0.22.0 2026-08-22 01:43:37 +08:00
bowenliang123 af14769c0f feat(client): card-style message detail with image attachment rendering
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.
2026-08-22 01:43:37 +08:00
bowenliang123 587d55f0da chore: avoid adding unnecessary code comments 2026-08-22 01:30:15 +08:00
bowenliang123 2ca9f76720 feat(client): title reasoning/response blocks in assistant message detail 2026-08-22 01:27:46 +08:00
bowenliang123 bfb99a4827 chore: bump version to 0.21.1 2026-08-21 23:15:22 +08:00
bowenliang123 925c9ccca6 fix(client): no white screen on malformed backend timeline payload
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.
2026-08-21 23:12:51 +08:00
bowenliang123 0a028ace6f chore: bump version to 0.21.0 2026-08-21 20:41:18 +08:00
bowenliang123 1a625fef96 fix(host): dual-contract session-projection units for dsh 0.1.1-rc.1 compatibility
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.
2026-08-21 20:41:10 +08:00
bowenliang123 57f7ca013d chore: move zod from dependencies to peerDependencies (optional)
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
2026-08-21 18:00:41 +08:00
bowenliang123 52fbb1bfa7 chore: bump version to 0.20.0 2026-08-21 17:01:32 +08:00
bowenliang123 1cc4da0a9f feat(stats): estimate whole-session cost at DeepSeek V4 list prices
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.
2026-08-21 17:01:22 +08:00
bowenliang123 dec8b16e62 chore: shorten homepage link 2026-08-21 14:05:13 +08:00
Bowen Liang 85076ae10c chore: bump version to 0.19.2
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).
2026-08-20 23:38:48 +08:00
Bowen Liang aa768c76a1 chore: bump version to 0.19.1 2026-08-20 22:44:29 +08:00
4d9ffcd3ca fix(host): keep timeline state plain JSON for the projection cache
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>
2026-08-20 22:39:42 +08:00
Bowen Liang ad5433bf34 chore: bump version to 0.19.0 2026-08-20 21:59:11 +08:00
Bowen Liang 81415caad4 browser: raw/markdown view switch on detail cards
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.
2026-08-20 21:55:27 +08:00
bowenliang123 35e6a8b2e8 doc: update build instructions to use pnpm and add watch command 2026-08-20 17:07:54 +08:00