Files
deepseek-harness/packages/api/activity-controller
Yichen Jiang 69d8b6e7ea fix(activity): bind observation cleanup to its generation and settle before mapping the outcome
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.
2026-08-25 15:46:15 +08:00
..

@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, and observe satisfies 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 for jobsBySession.
  • observe({ activityId, from? }, signal) — one opened anchor, coalesced output frames (flushMs window, maxFrameBytes soft budget; one larger chunk ships whole), then one terminal status after the settled activity is drained, after which the generation closes normally. Reconnecting callers resume by passing the last frame's next; a resume behind the retained window arrives flagged lossy. 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.