Both ds-review-bot criticals verified and fixed:
- ClientActivityFeed release closures resolved the entries map at release
time, not the entry they were minted for. A late release of a superseded
observation decremented (and could tear down) the successor's stream, and
the post-dispose model clear could land after a re-observation inside the
dispose round-trip re-anchored the view — blanking the panel until the
popover reopened (React StrictMode's double effect hits the same window).
Releasers now capture their entry, reconcile the map only when it still
holds that entry, and skip the view clear whenever a successor owns the
key. Both regressions fail on the previous code.
- The background observation tap mapped the process outcome as soon as the
pump settled — including its failure path, where the process is usually
still running: processOutcome requires a settled handle, so an early
mapping froze 'exit code: 0' onto a live task and first-wins end discarded
the real terminal state. The tap now awaits proc.done before mapping (the
promise never rejects) and swallows mapping failures per the module's
contract, in bash and its pwsh mirror.
Also applied from the same review: the Remote tier's cross-session
visibility is now stated in the controller README, ActivityRead.next
documents its chunk-boundary nature that client concatenation relies on,
mergeRows records the one-activity-per-job assumption, and the 08-24 Agent
Note now reflects the shipped unified task list.
Browser acceptance against a live scaffold surfaced two defects:
- A fresh observation anchors at the registry's earliest retained byte, so
the client's gap predicate (anchor behind earliest) never fired for it: a
viewer who watched eviction live saw the gap notice, while a reload of the
same activity silently hid that the head was discarded. A fresh view (no
accumulated text) anchored past offset zero now carries the gap mark;
resumes keep their recorded state.
- The trigger-anchored popover (left: 0, width 440px) overflowed the right
viewport edge on narrow windows — max-width alone cannot see the anchor
offset. The open popover now measures its anchor and shifts left to keep
the Menu primitive's 12px viewport margin, re-fitting on window resize and
falling back to the pure CSS anchor when layout reports no width (jsdom).
JobListAction shares the latent overflow; tracked separately.
Add the activity capability seam: ctx.activities is a pure observation
plane where long-running work (background bash/pwsh, workflow runs) opens a
handle, appends transient output into a byte-offset ring buffer, and settles
exactly once. Nothing model-visible changes: reads are non-consuming and
lossy-by-design (absolute UTF-8 offsets survive eviction and report gaps),
correlation carries {callId, jobId}, and visibility is owner-fenced like jobs.
The seam is strictly optional. Producers tap it through ctx.get('activities')
and contain every observation failure, so an unregistered or broken registry
never affects the underlying job (proven by broken-registry tests).
Ship the full path in one change:
- @deepseek-ai/dsh-activity: Service Definition, pumpActivityOutput pull
substrate, invariant companion; dsh-activity-local: ring-buffer provider
with retention config and owner cleanup.
- shell: ShellProcess.observed exposes non-consuming stdout/stderr readers;
tool-bash/tool-pwsh mirror background jobs into activities.
- tool-workflow: phase/log/agent lines mirrored as a workflow activity.
- api-activity-controller: Typert Remote control (roster baseline+buckets)
and observe (opened/output/status frames, coalesced and byte-capped)
streams plus the client feed service with reference-counted observation.
- ui-activity: session-header action rendering live output through
TerminalBlock, which now shows output while a command is still running.
Docs: activity subsystem page, architecture row, regenerated catalogs, and
the Agent Note (2026-08-24-activity-observation-seam), all bilingual. A
keyless web e2e replays a real run_in_background stream to ARIA goldens.