Drop the details-panel Duration toggle: Duration rows always show
integer milliseconds, matching the cell time column. Timeline labels
(Total/TTFT/Decoding) and step-group descriptions previously fell back
to second labels at or above one second; they now also show exact
milliseconds via the shared formatDurationMillis formatter.
The react-loop simplification folds the workspace baseline into the first
entering request, so the --dev CLI smoke no longer sees a workspace-free
probe request before the projected one. Resolve on the first provider request
and drop the obsolete no-workspace initial assertions.
- docs: fix cross-class note links (../feature/...), mirror the membership
reuse criterion in README.zh.md and the connectWorkspace architecture
note (EN/ZH/sidecars re-recorded)
- test(client): stray + member blank at the same path — the scan skips the
stray and reuses the member
- test(web): re-registration after delete now mints a fresh blank session
(the old cwd-only reuse is gone), so the account is no longer empty;
assert the retained seeded session is still not re-adopted
Injected context reached the transcript as one anonymous shape whose
expanded body was the whole message serialized as inline JSON, so the
escaping collapsed the only readable part — the model-facing prose —
into a single line.
`MessageSource` gains an optional producer-declared `form`, a small
semantic vocabulary of information shapes independent of `kind`: kind
says who produced the context, form says what shape it is, so several
producers may share one presentation. Two values ship.
`instructions` (workspace-context) lists the reconciled files above
their text and keeps the `<system-reminder>` framing verbatim, because
the framing is part of what the model read. `catalog` moves dsh-tool-skill
off the shared plugin kind onto a `skill-catalog` source carrying the
published name/description entries, and the body lists those instead of
re-parsing `<available_skills>` out of the prose. Catalog identity moves
with it: the republish digest now covers the durable entries, deleting
the text-slicing that recovered them from a logged message.
Everything else renders the opaque body — the model-facing text with its
real line breaks, then the remaining provenance as fields. That is the
documented default, not a leftover: a resumed, forked, or foreign log
must render whether or not its producer is mounted here, which is why
the classification lives in the durable source rather than a client-side
table keyed by producer.
The pwsh-terminal e2e probed the bare 'pwsh' literal, which knip flagged as
an unlisted binary. Reuse resolvePwshPath from @deepseek-ai/dsh-pwsh-local
(the judgment the tool-pwsh tests already use), so the probe matches the
executor's own resolution and the reference stays indirect; record mode
skips the lane anyway, so the probe stays inert there. Regenerate the
cordis and config catalogs the seam moves left stale (line-number shifts).
A completed pwsh foreground call now presents as the bash tool's terminal
card with the parsed exit-status pill instead of a generic console fence,
and the collapsed row classifies as the shell family (Pwsh title). The
marker-to-exit-status parse moves from dsh-tool-bash's private render
module into the @deepseek-ai/dsh-bash seam so both shell tools share one
inverse of the marker contract (the bash-env precedent). The Web UI needs
no per-tool code: the terminal-card bridge maps any card:'terminal' view.
Coverage: dsh-bash owns the parse edge cases; the tool-pwsh presenter
suite mirrors tool-bash's; the client row-model suite pins the Pwsh shell
row; the new keyless pwsh-terminal web lane seeds an authored session,
presents it through the real tool on replay, and pins the card golden.
TUI is out of scope: the TUI package was removed (ed30088adb), so the Web
surface is the only UI the gap affected; the roadmap proposal's stage 2
is updated accordingly.
Review follow-ups on the context provenance row:
- the producer name now follows ToolRow's aria-hidden separator dot instead
of a literal middot inside the text, so the two disclosure rows expose one
accessible-name shape;
- the English steering caption becomes the noun `Interjection`, parallel to
the Chinese 插话 and to the label this bubble carried before;
- the history fold gets its own provenance assertion, so the mapping it
declares separately from TranscriptAdapter is pinned on both sides;
- the superseded-in-part bullet on the no-interjection-chrome note now
enumerates the third Decision bullet too, whose no-badge and no-IconActions
clauses had both lapsed.
Every logged non-user user/message collapsed into one identical
「上下文注入」 row, and mid-turn steering rendered in exactly the bubble a
turn-opening prompt uses, so the transcript could not say what had been
added or which message interrupted a running turn.
TranscriptAdapter and the history fold now attach a provenance view to
each context node, computed by contextProvenance() from the durable
source alone: a role (inject, or recall for a cross-session snapshot)
and a producer name read out of the log — instruction paths for
workspace-instructions, session titles for session-reference, the plugin
id for a plugin source, and the bare kind for any other. No client-side
table of producer names, so a renamed or newly mounted producer stays
identifiable without a client release and a foreign log projects like a
live one. ContextInjectionRow titles itself from the role and shows the
name beside it; MessageItem captions durable and pending steering
bubbles.
The caption reverses one clause of the no-interjection-chrome decision,
which removed it because the composer could not steer; composer steering
shipped afterwards without amending that note, so this change supplies
the product decision its reintroduction clause required and corrects the
stale facts left in it.
Fixes#1291