41 Commits
Author SHA1 Message Date
bowenliang123 16ecd2af38 chore(compat): support the dsh 0.2.1 line
The plugin declared two supported dsh releases while the line it actually
runs on — 0.2.1 — was outside the matrix, so the seams the plugin rides were
never probed against it.

The 0.2.1 line continues the V4 Config-form generation with every seam spelled
as at 0.2.0-rc.2, so the baseline entry is the same with the two facts that
differ: the line vendors cordis 4.0.5, and it adds the `web-deepseek-api`
entry the balance fold falls back to. The compat matrix now runs the plugin
against that tag's real sources like any other line, and the workflows fetch
it.

The driver's staging follows the line: the values package grew a sibling
module its entry re-exports by relative path, so the entry is staged under its
own name and the sibling rides along.
2026-10-20 06:01:36 +08:00
bowenliang123 00d8afa224 fix(host): read the balance facts from the DeepSeek platform's own row
The capsule could ask the platform for a balance it would never get, with a
credential it had no business reading.

The fold matched its settings row by SHAPE — the first section carrying a
top-level `apiKeyEnv` — so a row belonging to another provider supplied the
credential, and that row's `baseURL` was used verbatim as the API root, so a
path-carrying endpoint (`/v1/tools`, `/anthropic`) asked for
`<baseURL>/user/balance` and the capsule stayed empty.

The fold now takes only DeepSeek platform rows: the `llm` provider directory
names the API-key model's own row (whatever the entry is called), and dsh's
stock entry ids are the fallback. Another provider's key is never read and
never sent, and a row whose credential does not resolve sends no request at
all.

The endpoint is the platform ROOT: only the origin of the row's endpoint
override counts, so the account route is always `/user/balance` on the
deployment's own DeepSeek origin. The projection is read redacted, so a row's
literal key never enters the fold.

The compat matrix pins the seams this reads: the redaction option, the
provider directory, and the stock entry ids each line ships.

Fixes #176
2026-10-20 06:00:52 +08:00
bowenliang123 dd8e66955d fix(client): hide an expanded JsonTree row's inline preview
The rule matched `[class$="_summary"]` — the plugin bundle's `[hash]_<local>`
css-module naming — but the tree renders from the shell's Vite build, whose
scoped names end with `_<line>` (`_summary_1alm6_198`). It never matched, so an
expanded node showed its inline preview and then the same values again as its
children.

Match the `_summary`/`_preview`/`_punctuation` locals as substrings and hide the
comma stranded behind the preview, with a regression test covering the shell
build and test spellings. Drop the compat pin that described the plugin build
instead of the shell's, and add `css.punctuation` to the JsonTree seam needles.
2026-10-18 16:38:38 +08:00
bowenliang123 aca1574c25 feat(client): render JSON-detected section bodies as the harness JsonTree
Whole-text JSON payloads (a leading { or [ that parses) open on the shell's
JsonTree inspector instead of markdown: the switch offers Raw|JSON there
(markdown is meaningless on structured payloads), the tree opens two levels
deep via the expanders' own click path (focus restored, capped at 100), and
an expanded row drops its inline preview Chrome-DevTools style. The JsonTree
faces are pinned per baseline and probed by the compat matrix.
2026-10-16 18:45:26 +08:00
bowenliang123 6184f0fbc0 test(compat): pin the sliced cold fold's faces in the baseline matrix (issue #145)
The backfill's sliced path is optional-by-contract: restore/hydrate/prepare/
write exist on both supported baselines, and any absence falls the session
back to the single-shot coldSnapshot. Pin all four needles (plus coldSnapshot)
per baseline tag so a removal names the seam instead of silently degrading
the pass to whole-log blocks, and record the rule in docs/compatibility.md
§6.
2026-10-14 02:50:46 +08:00
bowenliang123 6487602f1e feat(client): render message attachments in order and preview them in the sidebar
A user message's image and file blocks now render as one ordered attachment
block in the Context browser: images keep their session-authorized thumbnail,
other files show their type glyph, extension, and stored size, and the row
chip counts every attachment. One click IS the preview — the card hands the
attachment's named host path to dsh's own preview column (the File Activity
card's `previewAddressOf` → `openResourceVia` path), which renders markdown,
code, PDF, office, and image viewers under the file's real name. A refused
open reports on that card alone; the image lightbox and the per-type modal it
replaced are gone with `images.tsx`.

The path comes from the plugin's own `/api/dsh-context/attachment` route (the
same authenticated `/api` fence, Connection exact-route registry, and
cold-read gate as the detail and fleet routes). It answers only an id the
session's own log cites — a depth-bounded, total walk over the event payload
(user content, tool-result blocks, spliced inbox deliveries) — and answers
the store's named alias: `attachments.fileHostPath` for verbatim files, and a
published-or-reused named hard link for images, whose objects are digest-named
and suffixless while the preview column dispatches viewers by suffix. The
link shares the immutable object bytes and lives in a disposable cache
(`<home>/cache/dsh-context/previews/`); a store that is not host-file-backed,
or a media type the harness never admits, resolves the typed null the card
reports as an unavailable preview.

`Section` moves to its own module so the attachment block heads itself
without importing the tool card, and the compat matrix probes `FileTypeIcon`
alongside the icons it already pins.
2026-10-13 21:19:47 +08:00
bowenliang123 9873eb5836 fix(client): mount the client bundle once per page (issue #137)
The same package can mount twice into one page's slot ledger — a static
profile bundle plus a later activation of the same package. The
client-plugin runner force-assigns a fresh shadowing priority to every
registration, so the registry's same-id duplicate guard never fires across
the two mounts, and the conversation header — which renders the raw ledger
and matches the active view by entry id — paints both copies of every
list-slot seat.

apply() now reads the raw ledger first (SlotRegistry.entries) and mounts
nothing when this bundle's chat-to-Context jump entry is already seated.
The read fails open: an absent, shape-shifted, or hostile face reports
unseated and the mount proceeds, so an older line never blanks a working
mount. The entries seam is pinned per baseline.

The harness-side defects (the runner forcing a rank on additive list
slots, and the header rendering the raw ledger) are left to
deepseek-ai/deepseek-harness.
2026-10-12 08:39:17 +08:00
bowenliang123 d75b539419 feat: system prompt prefix/suffix settings joined into every session
Two new per-user preferences, `promptPrefix`/`promptSuffix`, hold free text
joined at the very start / end of the harness's assembled system prompt for
all agents (subagents included) — a per-deployment persona or behavioral rule
riding the harness's own prompt registry instead of session-log surgery:

- src/host/promptInjection.ts registers two global prompt sections through a
  deferred `ctx.inject(['systemPrompt'], …)` (a composition without the
  registry degrades to a no-op; the registration unwinds with this plugin's
  fiber). Orders anchor on the harness's own section-order registry, so a
  central renumbering keeps the prefix first and the suffix last. Text is
  registered `interpolate: false`, so `{{...}}` in user text stays literal.
- The section text providers read the volatile config references the loader
  rewrites in place on preference edits, so an edit applies from the next
  turn without remounting the entry; new sessions carry the text from their
  first turn through the harness's own system-prompt projection.
- The settings card grows two free-text rows (local draft, committed on blur
  and on unmount; a host push never clobbers in-progress typing), zh/en
  strings, and the served-value sanitize/degrade path for the string fields.

Verified against a running harness: the committed text persists in the
profile layer, and a new session's system prompt starts with the prefix and
ends with the suffix. docs/compatibility.md documents the seams, and the
compat matrix probes them on both supported lines.
2026-10-11 13:55:14 +08:00
bowenliang123 ace15cac11 feat(client): move the agent network onto its own Fleet tab
The Agent network card leaves the Context tab's foot for a page of its own —
the Fleet / 编队 tab, right of Context (order 30). The card is unchanged,
but both mounts now read one shared `useContextSession` data path and fold
the current agent's figures with one `agentSelfOf`, so the panel and the tab
cannot disagree; the Fleet page keeps the same cold-start retry note and the
same baseline gate. The tab label key splits into `tab.context` / `tab.fleet`.

The right Sidebar's Context panel keeps the card inline — its column has no
room for a tab of its own — so the center Context tab renders it only for the
sidebar host, and nothing else leaves that tab.

The stats board's Agent Network cell used to scroll to the card further down
the same page; it now activates the Fleet tab (activateContextTab is
generalized to activateViewTab). Its miss arm is unreachable for our own
tabs: the board and the Fleet tab ride the same placement gate.

The bundle smoke's fake registry now records `register()` calls instead of an
inject callback's return value, matching the real registry — one inject may
declare several registrations, which the second tab exposed.
2026-10-10 20:10:55 +08:00
bowenliang123 f50f33d58b fix(ci): run the compat seam matrix on pull requests
The matrix skipped itself on every PR: pr-checks.yml never fetched the
baseline tags and never set DSH_REPO, so skipReasons() always reported a
missing checkout and describe.skipIf dropped the whole per-baseline block —
the registry apply, the wire keys, the checkpoint JSON gate, cold restore
and every client seam probe. Only the release workflow fetched them, so
the compatibility proof compatibility.md claims for "every pnpm test" was
a no-op until a tag was cut.

Both workflows now fetch the tags and export DSH_REPO, and a skip under CI
fails the run instead of hiding behind green: a missing prerequisite there
is a broken gate, not an absent one. The two test runs collapse into one,
which also brings the lint gate the release workflow had and PRs did not.

The matrix's own vocabulary guard compared a hand-copied constant against
hand-copied baseline lists, so it could never see a drift in the fold — and
had drifted: approval/asked, approval/decided and session/end-seed are
folded but declared by no baseline. It now reads the fold's case labels out
of src/host/fold.ts, so a new fold case cannot go unprobed, and the history
probe pins the `records` envelope historyPage's rowsOf rejects without, not
just the remote's service name.
2026-10-10 02:16:22 +08:00
bowenliang123 87a2893190 refactor(comments): clean up the test-suite comments to the guideline
Apply the AGENTS.md comments guideline to every .ts under tests/: remove
what the code already expresses, keep only what it cannot.

Removed: spec-header coverage inventories the describe/test tree already
names; comments restating the next line's assertion or its message; helper
JSDoc whose body already says it; within-file duplicates; five stale
claims (trendChart's "six aggregated turns" against agg.length === 8,
agentGraph's "six categories" against CATS' seven, fold-timing's "cheapest
call LAST" against a loop that feeds it first, fold-surface's unreachable
plain-JSON note, contextView's "two turns" for a one-turn fixture).

Kept and condensed: the projection-freeze class, strict wire/state-schema
hazards, retention and pricing windows, pairing rules, hostile-input
rationale, fixture and expectation-array ground truth, jsdom mechanics,
and issue-numbered regressions.

Comment lines 2604 -> 2014 (-22%); 85 module headers kept, 30 coverage
inventories dropped; every surviving comment line is within the 140-column
limit, with 36 wrapped lines joined onto fuller ones.

No logic, string, identifier, import or directive changed: a comment-strip
gate proves each file's comment-free code is byte-identical to HEAD, and a
line-level proof pairs all 845 changed lines with an unchanged code part.
pnpm run lint && pnpm run test (106 files / 2061 tests, per-file 100%
coverage) && pnpm run build all pass.
2026-10-10 00:50:55 +08:00
bowenliang123 7c87ce2b76 refactor(comments): rewrap test comments onto fuller lines
Merge consecutive short comment lines in tests/** up to the 140-col limit
without touching their text: comment-stripped content, comment text (whitespace
normalised) and line-comment runs are all proven identical per file. This is the
line-count half of the cleanup only; the content pass over tests/** is still
outstanding.
2026-10-10 00:26:42 +08:00
bowenliang123 a988c9a8fb feat(compat): drop dsh 0.1.5-rc.1, floor the support matrix at 0.1.7-rc.2
The supported set is now the two V4-generation lines (0.1.7-rc.2+ and
0.2.0-rc.2+): the manifest's dshReleases, the peer floors (>=0.1.7-rc.2,
cordis ^4.0.4 — both lines vendor 4.0.4), the devDependency pins, the
baseline matrix, the release workflow's tag fetch, and the compatibility
docs.

With every supported line on session format V4, the V3-only paths leave:
the fold reads the tool result's first-class message alone (source.callId
pairing, message.isError — the admission validation refuses any row whose
data.error outlives it); the settings.register host face module is gone
(the Config-form entry schema serves the preferences); the client drops
the settingsScope/settings.plugin.item card seat, the retired sessions.open
jump fallback, the legacy icon spellings, and the settings-dialog leg of
the settings jump; the history page's tool/result mapper reads the V4
message (its V3 wrapper reads were dropping result content on supported
lines); the cache probe rides the 2-arg cachedSnapshot face. The baseline
gate stays: a below-floor harness composes the fallback units and the
upgrade prompt, and an undetectable version still fails open.

Verified: pnpm run lint:fix && pnpm run test && pnpm run build green (1918
tests, 100% per-file coverage); the compat matrix passes against the real
dsh sources at both baseline tags; live on dsh 0.2.0-rc.2 the plugin
composes, the Plugins-page preference card renders and round-trips a
write, the insights warm-up fills the heatmap, and the session jump and
Context tab work end to end.
2026-10-07 23:37:52 +08:00
bowenliang123 96c8b92caa feat(insights): promote the dashboard to a first-level sidebar panel page
The Context Insights surface moves from a sidebar-foot button opening a
frame-wide overlay to a first-level panel, a sibling of the shipped
Plugins and Automation tasks pages: the page registers on the layout's
keyed `main` slot and the entry on `sidebar.panellist` under one id
(`dsh-context`), so the shell owns the entry row and panel switching —
mounting the page IS opening it (no plugin-side open state, the footer
entry, the overlay seat, and the overviewStore all go away). The
`insightsEntry` preference now mounts/unwinds the registration pair.
Both seats ship on every supported line (0.1.5-rc.1+), pinned per
baseline by the new insightPage seam probe; runtime-verified on a
disposable 0.1.5-rc.1 profile.

The chrome mirrors the shipped first-level pages: one scroll region
with the 960px centered column, the 20px page heading beside the range
group and the balance capsule, and the page element as the `lc-ov`
query container. Inline-size containment makes the page the containing
block for fixed descendants, so the heatmap/usage/balance tooltips can
no longer ride the harness Tooltip (its portal prop is 0.1.7+): they
now carry `data-lc-tip` served by the page's own hover tip — one fixed
bubble portaled to <body>, delegated off the page root (the image
lightbox's precedent), correct on every supported line.

Session card jumps need no close (openSession's own navigation returns
to the conversation); the settings row jump unmounts the page by
selecting the Plugins panel. Verified across 1440-390px in both
locales, and the /context modal is untouched.
2026-10-05 11:20:59 +08:00
quifox b65939e887 fix(host): fold V4 tool-registry developer messages into the timeline
V4 harness releases log a `developer/message` event whenever the effective tool registry changes mid-session (first shipped in 0.1.7-rc.2): the message nests under data.message with role `developer`, source kind `tool-registry`, and `tool-addition`/`tool-removal` content blocks, lands on the session surface, and the harness's own token meter prices it like any non-system message. The timeline fold had no case for it, so every such message vanished from the plugin's accounting — injection totals, per-request composition, the live surface, and compaction archives under-reported, and the Context tab's numbers could not reconcile with the harness meter or a compaction shadow price.

The fold now treats the event like an injected user message: the nested message is read structurally (the pinned 0.1.5-rc.1 dependency's deriveEventMessage predates the family), the node prices through the shared estimator with the harness's empty-content-is-zero rule, and the injection record takes the producer kind as its label. Developer messages never count as human inputs whatever a hostile source claims; malformed payloads leave the state reference-equal.

stateVersion 24 -> 25: earlier cached rows omitted these nodes and later events cannot recover them, so on upgrade every timeline row refolds once from the durable log (the dashboard warm-up rebuilds idle sessions). The wire shape and durable logs are unchanged. The V4 baselines now carry the event family, and the real-source compat matrix asserts surface tokens, request composition, warm restore, and a v24-row rebuild on both supported V4 lines.

Validation: 102 test files / 1,910 tests with per-file coverage 100%; the real-source matrix passed for 0.1.5-rc.1, 0.1.7-rc.2, and 0.2.0-rc.2; GitHub PR checks green (486069f fixed the workflow's pnpm setup that had failed every external PR at its first step).
2026-10-04 00:03:27 +08:00
bowenliang123 423ea4f2ac fix(host): read the balance facts through both settings generations
The 0.1.7 line's SettingsForms service retired the `get(ns)` section
read, so the balance route never resolved the DeepSeek API key on V4+
deployments and the capsule stayed hidden. deepseekSectionOf now folds
over whichever face the running line serves: the registered
`llm-deepseek` section through V3, and on V4+ the Config-form
`describe()` projection, matching the provider's row by its own
apiKeyEnv-bearing section shape and preferring the settings ids the
generations have served it as. Rows fold in isolation, and a describe
that throws is no section — the capsule still arms nowhere it cannot.
The per-baseline `balance` probe pins the settings face and the
provider row's sources against each tag's real sources.
2026-09-27 02:15:38 +08:00
bowenliang123 646344162a fix(client): jump to sessions through the view owner's navigation verb (issue #90)
dsh's 0.1.6 selection refactor moved session selection to the view owner:
uiWorkspace.openSession is the verb the built-in sidebar's row click uses,
and the sessions service no longer carries open(). The plugin's jump (the
Context Dashboard's session cards and the Agent network card's nodes) still
called the retired sessions.open spelling, so on 0.1.7-rc.2 the panel closed
but no session opened — silently, by the verb's fail-soft design.

One shared openSessionVia (services.ts) now serves both call sites: it
re-proves both faces per call, prefers the view owner's verb (invoked bound
to the service instance), and keeps the retired sessions.open as the
degradation path. The per-baseline sessionNav seam pins both spellings
presence-as-declared in the real-code matrix, and the always-on client-face
probe drives the jump through each generation's exact composition (the V4
baseline serves no sessions-side selection at all — the exact shape issue
#90 died on).
2026-09-25 21:37:32 +08:00
bowenliang123 9acbf7eb45 feat: support dsh 0.1.5-rc.1+ and 0.1.7-rc.2+ as the supported lines
The supported range is now two baselines - dsh-v0.1.5-rc.1 (session
format V3) and dsh-v0.1.7-rc.2 (V4) - enforced by the runtime gate at
BASELINE_DSH_VERSION = 0.1.5-rc.1; below it the fallback units and the
upgrade modal take over (0.1.2/0.1.3 hosts were previously served by the
real folds).

The pre-V3 log shapes are dead code on every supported harness: the
running harness migrates historical logs on every read path (or refuses
the session wholesale), and 0.1.5-rc.1 rejects a request/header carrying
header.system outright. The fold therefore drops the V0/V2-only paths -
the header.system envelope system prompt, assistant/chunk floods,
{start, end} replacement endpoints, tool/code-dispatch - and the state
fields only they wrote (systemsFromHeader, the stepStart decode
accumulators). stateVersion stays 20: no supported log folds
differently, and pre-V3-era cached rows are discarded by the projection
cache's identity gate (and the strict stepStart sub-schema) instead of
served. The headers unit folds metadata-only epochs (envelope
systemTokens write dropped; the v1 cached-row read-compat and its pinned
stateVersion 1 stay); replaceRangeOf speaks startSeq/endSeq only; the
browser's epoch fetch maps request/header to tools and system/message to
the prompt text.

tests/baselines.ts re-fits to the two entries (the right Sidebar seam is
required on both), the compat matrix drives both tags' REAL sources
(canonical logs re-shaped to V3, the hostile probe renumbered past them),
and the version fixtures pin the new floor. Manifest: dshReleases
{0.1.5-rc.1, 0.1.7-rc.2}, dsh peers >=0.1.5-rc.1 (satisfies the 0.1.7
admission check under includePrerelease), devDeps pinned to the
0.1.5-rc.1 surface plus the ui-primitives runtime closure the client
lane inlines (test-only), and the release workflow fetches the two tags.
docs/compatibility.md is rewritten around the two-line matrix; README,
AGENTS.md and the issue template follow.

Also riding this move, from the audit pass: the fold re-proves an inject
source's form before it books the event record (the harness validates
`kind` but not `form` - a hostile one previously failed the strict
wire/state schemas on every delivery, the #44 freeze class), the headers
unit guards a data-less envelope (it has no try/catch of its own), the
schemastery schema annotations drop the explicit z<T> wrappers the 3.18.4
Mode generics reject, and the version-history note records why
stateVersion is deliberately not bumped.
2026-09-25 01:08:38 +08:00
bowenliang123 325f3f4024 test(compat): pin the preferences card seat and transport per baseline
The settings seam now names the browser half's faces per generation —
the keyed card slot (settings.plugin.item vs plugins.bundle.config) and
the transport service (settingsScope vs configForms) — each asserted
both ways against the tag's sources, like the sidebar seam, so a moved
seat cannot read as unsupported.
2026-09-23 15:58:48 +08:00
bowenliang123 390399ff24 feat(compat): support dsh 0.1.7-alpha.1
Add the V4 baseline (session format 4, cordis 4.0.3): the fold's switched
event families are unchanged, so its entry rides the V3 seam face with a
per-generation settings surface — the `register` face and the retired
`settings.plugin.item` slot are asserted BOTH ways (present through V3,
absent on V4+), and the modal dock probe now pins the sidebar-leading
grid track the measure actually reads instead of one generation's exact
template. New ICON_SEAMS probe: every icon the client renders must exist
in at least one spelling per tag, so a platform rename fails the matrix
instead of the browser. Declare the release in the manifest, fetch its
tag in the release workflow, and document the V4 generation with its
known deltas (deferred: the preferences card's Plugins-page migration,
image-offload accounting).
2026-09-22 16:28:46 +08:00
Bowen Liang 2f265f3f18 feat(client): add the Context Dashboard panel above Settings
A cross-session insight surface: the sidebar-foot entry (stacked above
Settings by the harness's own foot layout, with a running-sessions badge)
opens a frame-wide overlay through the shared module store. The panel reads
the root-scope useSessions/useWorkspaces standard kit — every list row's
host-cached projection values — so no session log needs opening:

- KPI band over the picked range (7d / 30d / all): active sessions, billed
  tokens, estimated cost (models.dev book), cache-hit rate.
- Activity heatmap (GitHub-style, 12 weeks, square cells filling the card):
  a day click pins the session grid to that day.
- Aggregate composition donut over the range's sessions.
- Session cards with a mini composition ring, billed/turns/cost figures and
  a workspace-group / project breadcrumb (deduped when both names match);
  grid filters by range, pinned day, group chips (incl. Ungrouped), and
  search; sorts by recency, tokens, or context size; a card click opens the
  session through the harness's own sessions.open and closes the panel.

Subagent-origin rows are excluded, mirroring the workspace browser's own
predicate (plain open() cannot address them). Every row is sanitized in
isolation — a hostile row degrades to a metadata-only card, never an error.
2026-09-16 08:56:30 +08:00
Bowen Liang e33deda752 feat(host): add the contextActivity daily-ledger projection with a cold-read backfill
The Context Dashboard's heatmap and session cards read the session list's
projection column, which only serves durable cache rows. The new
contextActivity unit folds each assistant settlement into a per-day ledger
(billed tokens + completed requests, keyed by local day, 400-day retention)
alongside the existing contextTimeline/contextHeaders units.

A session folded before a unit existed — or one that predates the plugin
entirely — has no row until it next goes live, so a one-pass warm-up
(backfill.ts) cold-reads every such session's durable log through the
projection cache's own cold-read ladder (coldSnapshot): each unit seeds
from its cached rows, folds the remainder, and the refreshed checkpoint is
written back. Live sessions are skipped (a throwing liveness probe skips
conservatively too), unreadable logs are logged and skipped per session,
and the whole pass rides a deferred inject across the
sessionQuery/sessionProjectionCache/sessionPersistence/sessions services.
2026-09-16 08:55:49 +08:00
bowenliang123 97ae8ff2ee feat(client): preview File Activity files in the right Sidebar
Clicking a file name in the File Activity card now opens the file's preview
tab in the right Sidebar — the shipped document-preview viewer, reached the
same way the built-in Files sidebar reaches it — on harnesses that serve that
column (dsh 0.1.5-rc.1+). The address is built with the harness's own
browser-safe `fileAddressFor`, inlined by the client bundle exactly as the
Sidebar's file types inline it, so a path under the workspace collapses to the
session-relative spelling and every route to one file settles on one tab.

The capability is OPTIONAL by contract, like the tab registration itself: the
opener reads `ctx.sidebarRight.openResource` through the runtime boundary and
re-proves it per call, and reports whether the column took the address. An
absent column, a face that refuses the address, or an unencodable log path
falls through to the pre-existing system-open behavior, so an older line is
fully functional and nothing becomes an inert click. Pattern rows and
directory targets are never previewable.

Adds the `@deepseek-ai/dsh-util-workspace-path` dev dependency (itself
browser-safe), allows it in the client bundle purity gate, pins the
resource-navigation seam in the compat matrix, and covers the address builder,
the card's preview-then-fallback click, and the optional opener per baseline.
2026-09-11 18:45:28 +08:00
bowenliang123 8212526d6c feat(client): brand the sidebar Context tab with the document emblem
Bundle the colourful document sheet as the plugin's own icon and fill the
right-Sidebar tab type's two glyph seats with it, the way the shipped files
type does: the guide capsule's `icon`, and a new `sidebar.right.pane.tab.title`
registration that draws the emblem beside the chip label.

The chip's close control sits out of flow over the title's last 14px and an
active chip fades the 16px before it, so the shipped files chip stays readable
only by its short label. The label now carries a trailing gutter so that fade
lands past the text instead of on the last glyphs.

Registration turns rollback-safe (each disposer is tracked) so a partial
failure unwinds cleanly, and the obsolete primitives-icon resilience spec goes
with the icon read it guarded.
2026-09-10 17:32:40 +08:00
bowenliang123 86fb240ce3 compat: move the V3 baseline to dsh 0.1.5-rc.1
The supported V3 line follows the newest release: the baseline becomes
v0.1.5-rc.1 (tag, vendored session package) and the declared release map
names 0.1.5-rc.1. The release workflow fetches the new tag, and the
disposable-profile install/uninstall was verified for real against the
published 0.1.5-rc.1 CLI (install OK -> 1 composed row -> uninstall OK ->
0 rows).

At 0.1.5-rc.1 `SidebarRightGuideEntry` regained its optional `description`
line, so the guideEntry probe pins order/title/description/icon and the
now-pointless "absent" assertion plumbing is dropped.

Every support/capability label states the 0.1.5-rc.1+ floor;
`0.1.5-alpha.x+` remains only as the V3 LOG-generation label (the format
first appeared at 0.1.5-alpha.1). The docs record the verified matrix and
the seam movement across alpha.1/alpha.2/rc.1.
2026-09-10 11:37:04 +08:00
Bowen Liang a4bd0d40f9 compat: move the V3 baseline to dsh 0.1.5-alpha.2
The supported V3 line follows the newest release: the baseline becomes
v0.1.5-alpha.2 (tag, vendored session package) and the declared release
map names 0.1.5-alpha.2. The release workflow fetches the new tag.

The alpha.2 client seams are pinned in the matrix: the conversation
surface moved under the keyed `main` panel (`main.conversation`) while
the plugin's `conversation.view` seat keeps its name, and
`SidebarRightGuideEntry` dropped its description line. tests/baselines.ts
therefore gains a guideEntry probe (the fields present plus the dropped
copy field asserted absent).

Every support/capability label states the 0.1.5-alpha.2+ floor;
`0.1.5-alpha.x+` remains only as the V3 LOG-generation label. The docs
record the verified matrix, the two moved seams, and the install row
that still waits on the CLI's npm publication (the `alpha` dist-tag
lags the release).
2026-09-09 23:03:10 +08:00
Bowen Liang b93b0e24f9 feat(client): add the Context tab to the right Sidebar 2026-09-09 02:43:04 +08:00
Bowen Liang f16f034e63 test(compat): add the v0.1.3-alpha.2 and v0.1.5-alpha.1 baselines
Each line carries its own durable-event vocabulary (assistant/chunk and tool/code-dispatch are V0/V2-only; system/message and tool/ptc-dispatch are V3-only), so Baseline.foldEventTypes replaces the single global list and a matrix test asserts the union still covers every fold case. The release workflow fetches both new tags before pnpm test.
2026-09-09 02:09:49 +08:00
Bowen Liang 7c081433c4 fix(client): remove the chat stats-line jump affordance
The hover-underline/click jump the plugin wired onto the harness chat
stats row conflicts with the reworked stats-line detail view shipping
in dsh 0.1.5. Drop the composer-dock entry, its styles, i18n keys, and
tests wholesale - the stats line renders untouched again.

Closes #53.
2026-09-09 01:19:26 +08:00
bowenliang123 ba074e078e fix(host): backfill pre-step message ids so stripped messages cannot brick sessions
A durable `user/message` event lacking a non-empty string `data.id` passes
the runtime append unchecked but makes the harness refuse the whole session
at its next load (`... lacks an identified message`, issue #51). The archived
wrap-up notice carried a rebuild signature (role-first key order, `id` and
`summary` stripped) that no shipped harness producer emits, so the strip
happens upstream of the append, outside both audited codebases.

Guard the durability boundary from the plugin side: a prepended
`agent/pre-step` listener runs last in the waterfall and mints a `dshctx-`
prefixed id for any decision message that would persist unidentified,
leaving everything else — including the decision's reference identity —
verbatim, and failing open on hostile entries. The seam is pinned per
baseline (the event dispatch, the user/message append that consumes it,
and the prepend option) in the real-code matrix.
2026-09-07 15:12:33 +08:00
Bowen Liang 4d144147a0 refactor(client): import React members directly, dropping the central react module
Every client file reached hooks and element types through the central
require seam (src/client/react.ts) and spelled them React.useState /
ReactNS.ReactElement. Import the members directly instead — named value
imports for hooks, inline type-only imports for element types — and let
the bundler own the single require('react'), which is exactly what the
platform module table answers.

This needs JSX off the classic runtime (it required a React identifier
in every tsx scope): tsconfig moves to jsx: react-jsx, so the bundle
sources jsx from 'react/jsx-runtime', already seeded by every supported
harness (verified against the dsh-v0.1.2-rc.1 platform.ts). The bundle
smoke's fake module table gains the specifier to stay a faithful shell
stand-in. h() survives as a local createElement alias at the slot
render-prop seams and the lightbox portal imports createPortal from
react-dom directly.

Verified: the built client.js requires react, react/jsx-runtime, and
react-dom exactly once each; the compat lane (bundle smoke + the
real-baseline matrix) passes against the fresh artifacts; the full gate
stays green with per-file coverage at 100%.
2026-09-07 08:59:03 +08:00
Bowen Liang 5c99a476f3 feat: fold the file-operation log host-side (full-log File Activity coverage)
The File Activity card derived its ops client-side from the served tool
nodes joined with the conversation WINDOW: a call whose arguments had aged
out of the loaded tail named no target and vanished from the card silently,
so the "up to latest" scope actually covered only the chat window. The join
was also the only meta/subCalls carrier, and the per-call op assembly was
written twice (top-level and Code-Mode nested paths).

The derivation moves to the host fold, which sees every lifecycle event with
its full payload: `tool/call` arms name + raw arguments (the pending-call
map's entries grow `argsRaw`), `tool/result` books the ops off the armed
arguments plus the result's presentation meta (grep's by-file match groups,
glob's path list, read's exact window), and `tool/code-dispatch` buffers
nested Code-Mode ops under their run_code root until the parent result
flushes them with `parent` + the program description. The log is bounded
(`maxFileOps`, default 400) with a `fileOpsFloor` coverage stamp, rides the
detail payload (and the inline value on channel-less hosts), and is plain
JSON end to end. stateVersion bumps to 14; cached rows refold from the log
to rebuild the op log.

The per-call parsing is ONE shared module (shared/fileOps.ts — opsOfCall
plus the kind/path/delta/read-window/search-attribution primitives),
consumed by the host fold and by the client's inline-generation fallback
(the legacy window derivation stays for old hosts, now through the same
parser, closing the duplicated assembly). The client aggregates + filters
(`activityOfOps` joins the archive's `gone` stamps at render time; nested
ops scope by their parent result's seq, matching the legacy node-level
rule); the card, its chips, rows, deltas, and locate bridge render
unchanged.

Verified: 1142 tests with per-file 100% coverage (new parser matrix,
fold-ops, aggregation, end-to-end through the real registry + detail
endpoint); the compat vocabulary probe gains tool/code-dispatch (present at
the 0.1.2-rc.1 tag). Live on the real deployment: the big session's card
grew from 4 window-bound files to 37 files / 135 ops spanning every turn,
and folding the real Code-Mode logs books nested ops with parent + program
stamps (e.g. 136 nested reads across one session, errors flagged).

Known upstream boundary (unchanged): `tool/code-dispatch` persists no
result meta, so nested reads keep the limit-estimate footprint and nested
searches the call-target form until the harness carries meta there.
2026-09-06 09:47:26 +08:00
Bowen Liang 62ae0f9c69 perf: split the context timeline into a slim wire head plus an on-demand detail channel
The contextTimeline projection value rode every harness delivery channel
WHOLE: each session.list row, every control baseline, every follow
snapshot, and a full-value broadcast on every state change (with a
full-value schema parse per push). The heavy collections (request records,
context events, the served surface window, the removed-node archive) are
only ever rendered by an OPEN Context tab/modal, so they moved off the
wire value.

Wire head (buildTimelineHead, ~1KB): the composition breakdown, the stats
board's precomputed counters (turns/steps/injects/compactions/prunes), the
newest request's billing summary (the headline's derived anchor), the
timing/cost totals, and `detailRev` — a revision marker that bumps exactly
on detail-mutating folds, the open tab's refetch signal.

Detail channel (host/detail.ts): the pre-v0.9 generic Connection RPC shape
returns as one narrow endpoint, `/dsh-context` `detail`, serving the
collections off the registry's live fold state (`stateOf` — no second
fold, no private copy). A nested `ctx.inject(['connection', 'sessions'])`
arms it load-order-free; a deployment whose faces never compose keeps the
inline wire value unchanged. The baseline-gate fallback path is untouched.

Client (client/timelineSource.ts): one per-session detail store shared by
the tab and the /context modal — fetch on open, then debounced
trailing-edge refetches whenever the pushed detailRev outruns the served
detail (single-flight, latest-wins by revision, refold reset, failure
backoff, absence stops trailing). Detail-driven cards show a pending/retry
note instead of a misleading empty state; the counters and headline render
off the head immediately. Both wire generations are reconciled per value
(new client + old host renders the inline value untouched; old client + new
host degrades to empty charts in the stale-watch window, never a white
screen). The chat-jump relay holds its one-shot pin until the detail lands.

Fold state keeps the full shape: no stateVersion bump, no cached-row
invalidation (the slim view is recomputed per read for cold checkpoints).
Compat matrix gains the detail-channel seam probes (HostConnectionRpc,
the browser caller, the registry stateOf), verified against the
0.1.2-rc.1 tag.

Measured over 263 real session logs from a live deployment, plus live RPC:
- per-session wire value: 84–150KB (top sessions) -> 0.6–0.9KB (~100-165x)
- all sessions aggregate (session.list rows / control baselines):
  4.4MB -> 0.2MB (~22x)
- per-client push traffic over one big session's lifetime:
  157MB -> 1.8MB (~87x), and the per-push full-value zod parse shrinks
  by the same factor
- detail payloads now flow only for the session being viewed, only while
  its tab/modal is open (120KB measured live for the biggest session);
  background sessions cost zero detail traffic

Verified visually against the running harness (big/small/cold sessions,
a live turn refreshing in place, past-step reconstruction, header epoch
and tool schema reads, the /context modal, the Agent network card) with
byte-identical card output. 1110 tests, per-file 100% coverage.
2026-09-06 04:08:20 +08:00
Bowen Liang 3e0673319c test: give the compat matrix driver hook the driver's 60s budget
A cold stage dir (fresh clone, cache miss, dependency re-pin) runs a
real npm install inside the beforeAll, which the 10s default hook
ceiling kills; align the hook with the driver spawn's own 60s timeout.
2026-09-05 00:55:05 +08:00
bowenliang123 fbf32ecc67 feat(client): open the Context tab from the chat stats line
The harness stats row (the composer dock's own `stats` entry) gains a
hover underline and opens the Context tab on click or Enter/Space. The
plugin mounts an invisible sibling entry on the officially plugin-facing
`conversation.composer.dock` seat; its anchor spots the single stats row
beside it inside the slot anchor, decorates it in place, and re-spots it
on dock churn (the row mounts only after the first stats exist, and
tooltip bubbles come and go beside it). A drag-selection click is
ignored so copying the numbers never switches tabs.

Every shape without a spotless row degrades to a silent feature-off:
a missing dock seat, ambiguous candidates (other plugins' entries,
crash faces, non-HTML markup), and rows that throw mid-wiring — and
unmount strips every mark and listener. The seat is pinned in the
compat matrix (SLOT_SEAMS) and the bundle smoke counts the new entry.
2026-09-04 18:27:01 +08:00
bowenliang123 33068ee1c9 compat: narrow the support matrix to dsh 0.1.2-rc.1+
The install/runtime compatibility floor moves from 0.1.1-rc2+ and
0.1.2-alpha2+ to 0.1.2-rc.1+. The 0.1.1 line and the 0.1.2-alpha previews
leave the support matrix, so every pre-0.1.2 fallback they existed for is
removed outright:

- client history: drop the legacy `connection.api.sessions.history` face,
  the `result`-nested envelope, and the `events` rows field — the gateway
  `remote.session.page` face and its ClientResult envelope are the only
  served shapes.
- client image loader: drop the pre-0.1.2 `conversation.resolveImage` face;
  `uiConversation.imageUrl` only.
- client node join: drop the pre-0.1.2 `useSession.nodes` seat;
  `useChat`'s ChatSnapshot `legacy.nodes` only (UseSessionLike removed).
- tests/baselines.ts: single baseline `v0.1.2-rc.1`; HostSeam dropped (the
  registry generation hard-codes header-passing init and seq-0 eager
  seeding, now mirrored with the exact `init(header, inheritedEventCount)`
  arity); ClientSeam slimmed to the consumed probes.
- deps: devDeps re-pinned to the 0.1.2-rc.1 generation (types compile
  against the real rc.1 surfaces); cordis ^4.0.2 and schemastery ^3.18.2
  per the rc.1 peer requirements; dsh-scope/dsh-invariants declared as dev
  deps; peer floors >=0.1.2-rc.1; dshReleases table trimmed.
- release workflow fetches only the dsh-v0.1.2-rc.1 tag; docs updated
  (AGENTS.md, README, docs/compatibility.md).
- verified against the rc.1 sources: the tool-attribution map (names and
  packages, incl. the documented `subagent_fork` alias), MCP naming, the
  page/imageUrl/legacy.nodes faces, and the registry contract.
2026-09-04 17:23:08 +08:00
Bowen Liang 4580692389 test(compat): pin the issue-44 freeze and recovery on the real registries
The matrix driver gains two probes over each baseline's REAL
SessionProjectionRegistry: hostile gateway usage must keep the served view
schema-valid on every later delivery (billed from the sanitized buckets),
and a poisoned upstream wire unit must reproduce the dsh containment gap -
the schema violation escapes the drive loop per event, every unit
registered after it starves, and snapshot() (every reconnect baseline)
throws wholesale. That seam is the registry's to fix; the probe names it
per generation.
2026-09-04 00:19:25 +08:00
Bowen Liang 48806646d8 test(compat): extend the seam matrix to the alpha.5 and rc.1 baselines
Add the two heads of the declared 0.1.2 lines as matrix baselines: the
preview window's head (dsh-v0.1.2-alpha.5, where the DSH STORE
latest-three compatibility window currently ends) and the next-channel
release candidate (dsh-v0.1.2-rc.1). Their seam faces verified identical
to alpha.2, so the generation's client faces collapse into one shared
CLIENT_0_1_2 constant.

The new baselines surfaced a real staging drift: since 0.1.2-alpha.3 the
session-projection sources import SessionLogOffset/SessionSeq from
@deepseek-ai/dsh-session as runtime values, so the compat driver now
installs each baseline's own vendored dsh-session release next to cordis
(Baseline.session).

The release workflow fetches the two new baseline tags before testing.

docs/compatibility.md records the per-release verification evidence,
including the 2026-09-03 disposable-profile install/uninstall matrix
across 0.1.1-rc.2, 0.1.2-alpha.3/4/5 and 0.1.2-rc.1 (ref
AI-Scarlett/DSH-Store#439).
2026-09-03 23:12:19 +08:00
Bowen Liang b11a1d2151 feat(client): dock the /context modal mask beside the shell sidebar
The fixed backdrop masked the whole viewport; inset it by the sidebar
track parsed off the app frame's inline grid template (the sole inline
gridTemplateColumns in the dsh client, identical on every supported
baseline), so the dialog centers over the main column and the sidebar
stays visible and interactive. An unresolved frame degrades to the
full-viewport mask.

The measure runs in a layout effect (no 0-inset first paint) and, while
open, a MutationObserver on the frame's style attribute follows sidebar
drags, collapse toggles and narrow-viewport re-solves. A per-baseline
compat probe pins the frame seam.
2026-09-01 22:48:26 +08:00
bowenliang123 a234d0caeb test: fold the bundle smoke and the compat matrix into one pnpm test
Drop the test:bundle / test:compat scripts. The bundle smoke moves into
the `compat` vitest project (tests/compat/bundle.spec.ts): the
__ModuleLoader__ handoff, the CSS channel, slot/trigger registrations,
and HMR safety over the BUILT lib/client.js — so `pnpm test` runs the
whole verification (host, client, built artifacts, real-code compat
matrix) in one pass with the per-file 100% coverage gate. The
built-artifact specs skip cleanly when lib/ is absent; the release
workflow builds before Test, so they always run there.

Also drop the standalone Bundle smoke step from the release workflow
and refresh the docs/comments that referenced the removed scripts.
2026-08-31 15:20:59 +08:00
bowenliang123 986d45a2bb test: add the cross-baseline compatibility matrix
tests/baselines.ts is the single source of truth for the supported dsh
baselines and their seam faces. Three layers verify them:

- always-on host registry-contract drivers over the REAL projection
  definitions (plain-JSON cache-write gate, wire delivery, checkpoint
  restore, init-header tolerance) — the recurring host incidents,
- always-on client-face matrix (nodes seat, image loader service,
  history face + envelope, markdown chrome, full tab render) — the
  recurring client incidents,
- the `compat` vitest project: stages the REAL harness sources at each
  baseline tag, boots the tag's registry on the cordis release that line
  vendors, applies the BUILT plugin, and probes slots/faces/vocabulary/
  platform table from the tag's sources. The release workflow fetches
  the baseline tags before `pnpm test` so the matrix always runs there.

Adding a future baseline = one tests/baselines.ts entry + one fetched
tag; a failing probe names the seam to re-fit.
2026-08-31 14:58:08 +08:00