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.
@deepseek-ai/dsh-api-activity-controller
English | 中文
Activity Remote streams over the optional ctx.activities registry, plus their React-free client model. A pure read surface: it starts nothing, cancels nothing, and never touches the model-facing consuming cursors — the wire path reads only the registry's non-consuming offsets.
The Host ActivityController (namespace activity) exposes two @Remote({ mode: 'stream' }) methods:
control(signal)— one complete roster baseline, then whole-bucket replacement frames per owner session on every registry change (the self-healing shape the session control stream uses for jobs). A composition without the registry serves an empty baseline. The Remote tier deliberately serves every session's rows to any connected browser, andobservesatisfies the registry's owner fence with the mirrored owner: the per-session fence is an in-process contract, and this single-user local BFF reads across it exactly as the session control stream does forjobsBySession.observe({ activityId, from? }, signal)— oneopenedanchor, coalescedoutputframes (flushMswindow,maxFrameBytessoft budget; one larger chunk ships whole), then one terminalstatusafter the settled activity is drained, after which the generation closes normally. Reconnecting callers resume by passing the last frame'snext; a resume behind the retained window arrives flaggedlossy. Status rides the same stream as output, so settlement can never race a still-open output channel.
| Config | Default | Meaning |
|---|---|---|
flushMs |
100 |
Coalescing window after new output before an observation read. |
maxFrameBytes |
65536 |
Soft byte budget per observation output frame. |
The client half (./client) installs ctx.activityFeed: ClientActivityModel mirrors the roster (last-wins buckets, generation replace) and accumulates each observed activity's bounded render tail, and ClientActivityFeed.observe(id) opens reference-counted observation streams that resume from the model's cursor across carrier generations.
Model Experience
None, as activity transport is browser and Host observation state and registers no prompt, tool, or session event.
KV Cache effect
None; the package never assembles or alters provider requests.
Known Limitations and Deferred Work
- Registry presence is sampled at construction — a registry mounted after the controller stays unobserved until the controller reloads.
- The roster names no watermark — an observation stream anchors itself; correlating roster rows against observation offsets is client policy.