* feat(workspace): stop a Session's running work before archiving it Archiving was a pure visibility write, so a Session archived mid-turn kept running out of sight. The Workspace registry now owns archive admission through two Host events: `workspace/session-activity` reports what still runs and refuses the plain archive as `workspace/session-active`; `workspace/session-stop`, requested with `stopActivity`, stops that work the way the user's own stop actions do before the archive write. The API Session Controller reports and stops turns, subagent descendants, and owned jobs and rejects any model step for an archived Session; the Schedule plugin reports and deletes active reminders. The sidebar turns the refusal into a stop-and-archive confirmation listing the Host's answer. * test(workspace): pin the widened archive signature and prove child liveness by heartbeat The Client inspect catalog spec pins archiveSession's new options parameter, and the archive e2e reads the Bash child's liveness from a heartbeat file instead of a pid: the Linux sandbox runs the tool in its own PID namespace, so the pid it reports is not comparable from the test. * test(workspace): cover the archive-admission providers and dialog to the per-file gate Drop the provider's unused disposer and the dialog's unreachable confirm guard, read reminders without a catch that no composition can reach, and add the cases the coverage lane found missing: a stop without a jobs registry, a looping lineage, a descendant whose projections reject, the dialog's Escape guard and non-Error reason, and a family merged by another provider. * fix(workspace): write the archive before stopping, gate subagent lineage, and delete reminders under the schedule barriers Review round on #4765. The registry now writes the archive set before it dispatches `workspace/session-stop`, so the durable set already gates every wake the stops induce; the controller's `agent/pre-step` gate also blocks subagent descendants of an archived Session and contains each cancel or kill on its own. The Schedule plugin's stop listener runs as a management delete in the agent's transaction queue between the same two persistence barriers the `schedule_delete` tool uses. The confirm dialog selects singular or plural English lines and keys list rows by index. Docs: archive-admission section with type-equiv blocks on the workspace subsystem page, write-first order and residual windows in the READMEs and the Agent Note, and the missing zh reminders sentence. * test(web): scope the question composer's New Session wait to the Sessions tree The wide sidebar's New Session button carries the same visible label as a fresh session row, so the unscoped `getByText` either settled on the button before the row existed or hit a strict-mode violation once the row rendered first, which is how the snapshots lane failed on ece68c2ab5. Waiting inside the Sessions tree names the row alone. * fix(schedule): answer archive admission from the owned runtime's fold, not the projection registry The `sessionProjections.snapshot` API is the Client projection view and is not a Host read path (review on #4765). `ScheduleRuntime.activeRecords()` folds the live log the way every drive does, and the archive-admission listeners ask the owned runtime for that fold: a session without a live agent or without an owned runtime has nothing armed to fire, so it reports nothing and has nothing to stop. Covers the runtime-less agent, a fold that turns unreadable at the persistence barrier, and the stopping/faulted runtime answering nothing. * refactor(subagent): report and stop subagent descendants from the Subagent runtime The `subagent` family of archive admission moves out of the API Session Controller into `dsh-subagent`, which records the lineage at delegation and owns the descriptor (review on #4765): `SubagentRuntime` answers `workspace/session-activity` with the running descendants, labelled from each child's descriptor through a live Session observation, and `workspace/session-stop` by cancelling each of them with the parent cause. The controller keeps turns, owned jobs, and the lineage step gate, and composes its provider through `ctx.plugin(SessionActivityProvider)` with declared injections instead of a bare installer call. `dsh-workspace` becomes an optional type-only peer of `dsh-subagent`. * refactor(jobs): answer archive admission for owned jobs from the job registry seam The `job` family moves out of the API Session Controller into `dsh-jobs` (review on #4765): the seam's constructor installs the `workspace/session-activity` and `workspace/session-stop` listeners for every implementation through the abstract `list` and `kill` alone, so the controller no longer knows the jobs vocabulary and keeps only the turn family and the archived-lineage step gate. `dsh-workspace` becomes an optional type-only peer of `dsh-jobs`. * refactor(workspace): leave SessionActivityKindMap empty and let each provider merge its family The workspace package no longer names any activity family (review on #4765): `SessionActivityKindMap` is empty, and each provider merges its own key from a module both its faces import — `turn` in the API Session Controller's types, `job` in the job registry seam's view, `subagent` in the Subagent runtime's control types, `schedule` in the Schedule plugin's types. The confirm dialog imports those modules for its cases and keeps the generic line for any other key; the registry and controller suites merge test-owned families instead of borrowing the providers'. * refactor(agent): report and stop the running turn from the Agent registry The `turn` family moves out of the API Session Controller into `dsh-agent` (review on #4765): the registry's constructor installs the `workspace/session-activity` and `workspace/session-stop` listeners for every Agent it publishes, answering with its own lookup and cancelling the turn with the user cause, and merges the `turn` key from its client-safe types. The controller keeps only the archived-lineage step gate, renamed `ArchivedSessionGate`. `dsh-workspace` becomes an optional type-only peer of `dsh-agent`; the workspace package depends on none of the providers, so the edge stays acyclic. Since #3127 a foreground Bash command is a registered job from its start, so the archive dialog lists it beside the turn; the e2e and its ARIA golden expect that row, and the capture keeps the running job by design.
description, kind
| description | kind |
|---|---|
| Host and Client workspace control: mutate workspace navigation and follow its complete projection. | package-reference |
Workspace Controller
English | 中文
Summary
@deepseek-ai/dsh-api-workspace-controller owns the Host ctx.workspaceController service and the generated Client ctx.remote.workspace namespace. Its Remote methods create, rename, remove, and reorder Workspaces, reorder Sessions within a Workspace, archive and unarchive Sessions from Workspace navigation, and follow the complete Workspace projection. Use it through API Gateway when a Client must change or follow Workspace navigation. The package also owns ctx.directoryPickerController and the generated ctx.remote.directoryPicker namespace, because the directory-picking seam it carries is abstract and never a Loader entry of its own.
Table of Contents
Use this package
The Host controller serializes mutations whose correctness depends on current registry state and throws RemoteError with a stable error code for expected failures. Its follow() stream synchronously attaches to durable Workspace changes, emits one complete baseline first, then emits ordered upsert, remove, order, archived, and pinned increments. Archive and pin sets are Session id arrays, with the most recently pinned id first in the pin array. A reconnect starts another generation with a replacement baseline, so consumers do not depend on receiving every increment while disconnected. archiveSession without stopActivity refuses a Session with running work as workspace/session-active, whose details list that work by family (turn, subagent, job, schedule) with item ids and labels; with stopActivity: true the registry's providers stop the work first and the response arrives once the archive set is durable, while the stops settle in the background.
The Client entry provides ClientWorkspaceModel and createWorkspaceStateStream(). The model owns Workspace rows, registry order, archived and pinned Session identities, unary mutation echoes, and stream/unary race resolution. A newer Host row wins by updatedAt; a committed stream order outranks an older unary response; a removed Workspace id cannot be resurrected by delayed data. Pin snapshots change only when their identities or order change. The package exposes framework-neutral snapshots and subscriptions, leaving navigation policy and React hooks to the UI owner. WorkspaceController.archiveSession(sessionId, { stopActivity }) throws WorkspaceArchiveError with the Host's rpcError, so a surface can tell the running-work refusal from a missing Session or a carrier fault and offer to stop the work.
First-use Workspace
workspace.initializeDefault({ directoryName, title }) returns the durable default Workspace; the Client service exposes the same request as workspaces.initializeDefault(request, signal?). The Workspace Client resolves these names from its startup language. The Host places the directory under its account's <Documents>/deepseek-harness, including on remote Web hosts. The directory name must be one non-blank segment without separators, colon, NUL, surrounding whitespace, or a trailing dot; the title must be non-blank. OS filename restrictions also apply. Linux system lookup requires xdg-user-dir with an enabled Documents directory; hosts without it must configure documentsDirectory or use the folder picker.
The Workspace registry owns eligibility, directory creation, and durable initialization. An existing default Workspace is returned without another Documents lookup; request names do not rename it. Ineligible first use returns undefined, so startup can leave directory selection to the user. Invalid names reject with gateway/bad-request; lookup and creation failures use standard Remote error handling. Initialization creates no Session and sends no message.
| Configuration | Default | Purpose |
|---|---|---|
documentsDirectory |
System Documents directory | Fully qualified Host directory override |
documentsLookupTimeoutMs |
10000 |
Positive maximum duration of OS directory lookup, in milliseconds |
Documents lookup holds the registry mutation queue, so other Workspace mutations, including registration of a picked directory, can wait up to documentsLookupTimeoutMs. Cancellation can stop the lookup; after resolution succeeds, it does not roll back creation or registration.
Model Experience
None, as Workspace organization is browser and Host control state and registers no prompt, tool, or session event.
KV Cache effect
No direct effect; Workspace mutations do not alter model requests.
Known Limitations and Deferred Work
follow()replaces the whole projection after reconnect and has no durable cursor or incremental catch-up protocol.- Process-local deletion markers prevent delayed data from reviving a removed Workspace only for the lifetime of the Client model.
Dev Note
Working context for maintainers — click to expand
None.
Runtime invariant: No companion is published. Workspace Registry owns persistence; every stream generation is a full projection.