Files
deepseek-harness/packages/api/job-controller
Yichen Jiang 2c0ca45f76 fix(jobs): carry the projection kind as an open string on the wire
JobView.kind was typed as the merge-extensible JobKind, so the job-controller program, which compiles only the merges it can see, generated a strict codec of bash | subagent for job.rows and job.observe. The first pwsh, pty-send, or workflow job failed frame validation on both ends and the browser dropped the whole roster. The projection now carries kind as a string; JobSpec.kind keeps the Host-side JobKind. A built-artifact smoke parses a pty-send row through the generated schemas, and the seam spec pins the open type.
2026-09-04 12:10:22 +08:00
..

description, kind
description kind
Host and Client job streams: the roster one session can see and one job's retained output, mirrored to the browser without touching the model's consuming cursor. package-reference

Job Controller

English | 中文

Summary

@deepseek-ai/dsh-api-job-controller owns the Host ctx.jobController service and the generated Client ctx.remote.job namespace. Its two Remote streams are projections of ctx.jobs: job.rows mirrors the jobs one session can see as whole-set frames, and job.observe delivers one job's retained output from an absolute byte offset. The Client half installs ctx.jobs, the reference-counted service whose rosters and accumulated views the session-header job list renders. Neither stream touches the model's consuming cursor or the completion-notice ledger.

Table of Contents


Use this package

The Host controller requires the job registry (dsh-jobs-local in the shipped compositions) and fails to load without it. job.rows({ sessionId }) yields the session's visible set — its own jobs plus every unowned job — once on open and again after each coalesced burst of lifecycle commits (registration, progress, stopping, settlement, removal); output appends never refresh the roster, since a settled projection already carries the final byte count. job.observe({ sessionId?, jobId, from? }) reads through visibleTo(sessionId) — an unowned job needs no session — and yields one opened anchor carrying the job projection, coalesced output frames, then one terminal status once the job has settled and its ring is drained, after which the stream closes normally. Both reads are non-consuming: the model's job_output cursor and notice state never observe them.

The Client entry provides ClientJobs (ctx.jobs) over ClientJobsModel. watchRows(sessionId) keeps one roster stream open per watched session however many viewers hold it and drops the rows after the last release; a reconnect's first frame is already the whole truth. observe(sessionId, jobId) opens one Gateway stream per job however many viewers expand it, keeps a bounded render tail per job with gapBefore marking eviction or resume gaps, closes the view on the terminal frame or records a stream failure on it, and drops the view after the last viewer releases. The plugin resolves the Gateway stream factory and the job namespace while its own context is current, because stream (re)opens run on caller stacks whose dynamic context has not declared remote.job.

Config

Field Default Meaning
observeFlushMs 100 Coalescing window after a registry commit before the next rows or output read, in milliseconds
observeMaxFrameBytes 65,536 Soft byte budget per observation output frame; one larger chunk ships whole

The generated configuration catalog is the exhaustive source for accepted fields and their JSDoc.


Model Experience

None, as job observation is browser and Host control state; it registers no prompt, tool, or session event. The model's own view of the same work stays with dsh-tool-jobs.

KV Cache effect

No direct effect; observation reads never touch model requests.

Known Limitations and Deferred Work

  • The ring is a best-effort live preview, not a terminal transcript: producers' pull sources are copied per poll round, so two streams' writes inside one poll window land stdout first, and the client concatenates chunks regardless of channel.
  • Both streams are process-local: a Host restart loses every ring and roster; a resumed observation then anchors on an empty registry, and a re-opened roster starts empty.
  • Per-session fencing is enforced by the registry's visibleTo read; the Remote layer itself serves any connected browser.

Dev Note

Working context for maintainers — click to expand

None.

Runtime invariant: No companion is published. The controller is a stateless projection of ctx.jobs reads; the registry's own @deepseek-ai/dsh-jobs/invariant owns the offset relations these streams forward.