# Conflicts: # docs/capability-seams.i18n.yaml # docs/capability-seams.md # docs/capability-seams.zh.md # docs/config-catalog.i18n.yaml # docs/config-catalog.md # docs/config-catalog.zh.md # docs/event-producer-consumer.i18n.yaml # docs/module-graph.i18n.yaml # docs/module-graph.md # docs/module-graph.zh.md # knip.json # packages/api/remotes/src/client/index.ts # packages/api/remotes/tsconfig.client.json # packages/bundle/web-app/package.json # packages/client/ui-chat/src/client/contract/slots.ts # packages/client/ui-chat/src/client/conversation-nodes/chat-snapshot-builder.ts # packages/client/ui-primitives/README.i18n.yaml # packages/client/ui-subagent/README.i18n.yaml # packages/extensions/cordis-client-runner/src/client/slot-catalog.ts # scripts/gen-doc-graphs.ts # tsconfig.client.json # tsconfig.host.json
description, kind
| description | kind |
|---|---|
| Activity Remote streams for browsers: the activity.control roster stream, per-activity activity.observe output streams with flushMs coalescing and maxFrameBytes budgets, and the ctx.activityFeed client model. | package-reference |
@deepseek-ai/dsh-api-activity-controller
English | 中文
Summary
dsh-api-activity-controller streams the live-activity plane to browsers: a client follows one roster stream for every visible task row and opens one observation stream per expanded panel, resuming across reconnects from its own cursor. 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. A composition without ctx.activities still serves an empty baseline, so the client degrades to no live rows rather than an error.
Table of Contents
- Use this package
- Understand the implementation
- Further Exploration
- Model Experience
- Known Limitations and Deferred Work
- Dev Note
Use this package
Mount the host row beside dsh-typert-registry and load the client half through the plugin manifest; UI features then consume ctx.activityFeed instead of raw streams.
The two Remote streams
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 generated configuration catalog is the exhaustive source for every accepted field.
The client feed
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.
Understand the implementation
Implementation internals — click to expand
The Host side mirrors the registry into owner buckets (ActivityFeed), fans control frames out to per-generation followers, and runs each observation as an async generator that subscribes before its first read so no append lands between anchor and stream. The client side keeps observation state per activity with a resume cursor; release closures bind the exact entry they were minted for, so a superseded observation's late release can neither tear down nor clear its successor.
| File | Role |
|---|---|
src/index.ts |
ActivityController: the two Remote stream methods |
src/feed.ts |
Owner-bucket roster mirror and control-stream followers |
src/observe.ts |
Per-activity observation generator: anchor, coalescing, terminal status |
src/client/model.ts |
Roster and observation state with the bounded render tail |
src/client/service.ts |
ctx.activityFeed: reference-counted observation streams |
src/client/index.ts |
Client plugin: roster stream wiring and the service install |
Further Exploration
dsh-activity— the observation contract this transport reads.dsh-client-ui-activity— the task list renderingctx.activityFeed.- docs/subsystems/activity.md — the plane end to end, wire shapes included.
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
These limits define when the transport is a poor fit. They are current package constraints, not a task backlog.
- 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.
Dev Note
Working context for maintainers — click to expand
None.