Files
deepseek-harness/packages/client/ui-agent-preset
Yichen Jiang 43e93c6a67 feat(web): draft a custom preset in a creator-mode session
The settings section gains the conversational authoring entry beside
copying: a dashed add-card (the Models page's affordance) that stages the
self-referential cordis preset and starts a new session on it, closing the
settings panel through the shell's new section owner-prop `close` — the
one shell affordance a section receives, for flows that leave settings.

The seat learns the difference between picking and staging: `select()`'s
immediate apply meets the still-current running session and drops the
stage as unservable, so the entry uses `stage()` and leaves the apply to
the list-change applier that fires when the started session becomes
current. `load()` stops regressing the display when its reply lands after
an applied stage was consumed — staged pick first, then the composition
the current session already carries, then the deployment default.

The authoring lane connects a workspace and drives the gesture to a
composed host session; the section goldens gain the entry.
2026-08-09 00:19:18 +08:00
..

dsh-client-ui-agent-preset

English | 中文

The agent-preset surfaces: a General-settings row choosing which preset new sessions are composed from, a chip on the new-session screen choosing the next session's, a read-only label in the session header, and a settings section that manages the roster — copy, delete, default, and the way into a preset's own files.

Why it is a new-session preference

A session's preset is fixed when the session is created — the host refuses to adopt an existing session under a different one, because that session's history was produced under the first preset's tools. So this row cannot be a live switch, and it says so: changing it applies to sessions started afterwards while running sessions keep the composition they began with.

The new-session chip

A second surface, beside the workspace picker on the new-session screen. It sits there rather than in the composer because that is where the choice is still open: a control that spends most of its life disabled belongs on the screen where it still works.

The chip opens on the deployment default and its pick is staged — the screen precedes the session it would apply to. The stage reaches a session when one becomes current and is still blank, which covers both the session the workspace connect created and the blank one it reused; riding along on sessions.create would miss the second. It is spent on first use, so the next new session opens on the default again, exactly like the workspace picker beside it.

A session that has started is refused rather than queued: the host answers agent-preset-locked, and the stage is dropped instead of waiting for a session that will never accept it.

The session-header label

A third surface, beside the session title: the preset THIS session runs, as static chrome. A control there would promise a switch the host refuses outright. It reads the preset from the session's own summary — a resumed session runs what it was created with, not today's default — and resolves the display name against the same roster the General row reads.

What it reads and writes

Options and the current default both come from one agentPreset.list call. The roster already reports which id a session with no explicit choice gets, so the row needs no settings-schema introspection; the write targets the agent-presets settings namespace's default field, which is what the host resolves at creation.

A locally authored preset is exactly as privileged as the plugins it names, so the list marks user rows rather than presenting every preset as shipped and vetted.

The row re-reads on settings/changed for its own namespace and on connection/reset: the roster is a live directory and the default is a settings field, so an external edit or a reconnect can both move it.

The management section

A fourth surface, its own settings page (settings.section id agent-presets, ordered after Models — choosing a model is routine, composing an agent is the deployment-shaping act behind it): the roster as cards, a copy dialog as the only way a preset is created, and a read-only viewer over the shipped compositions.

The browser edits no composition text. Editing YAML in a web textarea was a weak surface (no completion, no highlighting, no diff), so a new preset is a host-side copy of an existing one — the dialog collects an id (it becomes the directory name, which is why it must be named up front and cannot change later) and an optional display name, and { from, id, name? } is all that crosses the wire. Everything else — description, composition, skills — is edited in the preset's own files, and the page's other job is getting the user TO those files: the copy completes by opening the new directory, and every custom row keeps a location action. Where the host has no desktop opener (hasDocument: false on the roster; remote and container deployments), the same actions answer the directory as text on the row instead of offering a button that would spawn into nothing.

A shipped preset opens in the read-only viewer. It is the known-good composition a copy starts from, so reading it is the point; it offers no location and no delete — its install is overwritten by upgrades and is not the user's to manage. The intro carries the guidance a create button used to imply: to start from the smallest skeleton, duplicate 极简模式 (31 lines against standard's 233).

Beside copying sits the conversational entry: when the roster carries the self-referential cordis preset, a dashed add-card (the Models page's affordance) stages it and starts a new session — the section closes the settings panel through the shell's owner-prop close and the new-session chip's own applier composes the blank session the workspace flow produces. The seat keeps a late roster load from regressing the display: staged pick first, then the composition the current session already carries, then the deployment default.

The dialog mirrors the host's own containment rule ([a-z0-9][a-z0-9-]*) and refuses a name already in use — a copy never overwrites. Both checks are conveniences: the host re-applies them and its answer is what the dialog reports on failure.

Deleting removes the preset directory. Sessions already composed from it keep running — a composition is mounted once at session creation and nothing re-reads the file.

Setting the default writes the agent-presets settings namespace, which the host exposes to configuration clients (dsh-apiproxy keeps an explicit allowlist — a namespace outside it makes a picker move and then silently forget).

agentPreset.read, copy, openDocument, and remove are loopback-pinned (dsh-client-connection): a composition names the plugins a session runs, so reading one is reconnaissance, and the rest manage the roster and drive the host desktop. agentPreset.list is not — it carries ids, trust, and the two path-free capability flags, and a LAN client's picker needs it.

When the surfaces are absent

A deployment that composes no presets answers with an empty roster, and the row, the chip, the label, and the section all render nothing — every session then shares the host composition, and there is nothing to choose between or manage. A deployment that configures no writable root answers authorable: false, and the section stays a read-only browser: the shipped compositions still open in the viewer, but every copy action is disabled with the reason as its tooltip rather than offering a dialog whose create always fails.

Model Experience

Indirectly, through the preset a later session is composed from; dsh-agent-presets owns what that composition puts in front of the model.

KV Cache effect

No direct invalidation. Changing the default never touches a running session's prefix; a session created afterwards establishes its own prefix from its own composition.

Known Limitations and Deferred Work

  • A preset without metadata is listed by id — display text is optional, and a copy given no name deliberately falls back to its directory name rather than presenting itself identically to its source.
  • A revealed path is display text, not a link — where the host has no desktop opener the row shows the directory to copy by hand; the browser cannot open a host filesystem location itself.
  • Composition edits are invisible to the page — the files are edited outside the browser and nothing on the wire announces a file change, so the roster re-reads on its own actions, settings/changed, and connection/reset, not on every disk edit.