The web YAML editor is gone. agentPreset.write (arbitrary composition
text) became agentPreset.copy { from, agentPreset, name? }: a host-side
whole-directory copy of ids the host resolves itself — symlinks
dereferenced, modes re-tightened to owner-only with owner-execute kept,
metadata rewritten to keep the source's description but never its name or
roster order. No composition text or path crosses the wire in either
authoring direction, and the entryListSchema/!!js concern dissolves with
assertComposition itself.
The settings section becomes: a read-only viewer over shipped
compositions, a copy dialog (id + optional display name) as the only
create entry, delete for custom rows, and a location action leading into
the preset's own files — agentPreset.openDocument { agentPreset } resolves
the directory host-side and opens it natively, or answers
{ opened: false, path } for the row to show as text where the deployment
has no desktop. agentPreset.list reports hasDocument beside authorable;
the gateway's nativeOpen config pins the capability where
canOpenNativePath platform detection would mislead. The privileged set is
now read/copy/openDocument/remove.
With files as the only composition editor, standing mounts grew
stamp-keyed generations: ensureStanding compares the composition file's
mtime+size and starts the next generation for later sessions, while every
joined session keeps the generation it runs on.
New keyless web lane (agent-preset-authoring, overlay pins
nativeOpen: false so goldens render one branch on every platform) drives
view/copy/reveal/delete end to end; the real-composition CLI e2e switches
to copy semantics.
modeFor read only the exact scope's layer, so a `code`-preset session
advertised the native catalog: the mode is declared on the preset's STANDING
scope, and the agent only parents to it. Nearest scope wins along the chain —
the mode decides what the model SEES, which is the class of fact the chain
inherits. Caught live (the model politely computed with bash and said
run_code was not in its list); the chain test pins it.
The client half of the label fix: the create echo and the session-added
frame's agentPreset now reach the session list (newest wins in the upsert —
every producer of the field reports the CURRENT composition), and a confirmed
blank-session switch publishes through the new ISessions.noteAgentPreset, so
the header label moves with the composition instead of waiting for a reload.
The composer's preset seat stays removed here — this layer moved it to the
hero chip and the session-header action — so only the model seat takes the
`modelSeatLocked` narrowing master introduced.
Conflicts:
apps/web/tests/snapshots/*/*.expected.md
packages/client/connection/README.md
packages/client/connection/README.zh.md
packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
packages/host/apiproxy/src/fetch/client.ts
The Client API carrier's `agentPresets` member was the one member of its class
without an `IApiClient[...]` annotation. Inferring it inlined `AgentPresetEntry`
into the emitted declaration by the specifier TS picks — the host `index.ts` —
dragging the whole gateway, and with it the host `Context` merges, into every
Client program importing the carrier. Annotated like its siblings.
`ApiRemoteAgentOptions.setup` now takes the inspected session rather than its
header alone: this layer resolves a resumed session's preset from the LOG,
because a session that switched while blank ran its turns under the newer
composition and the header is written once at creation.
Conflicts:
apps/web/tests/snapshots/*/*.expected.md
packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
packages/host/apiproxy/src/api-proxy.ts
scripts/doc-budgets.manifest.json
master extracted this layer's inline cold-resume resolver into
@deepseek-ai/dsh-api-remotes, whose `setup` was a fixed AgentSetup. A resumed
session composes the preset ITS header recorded, so the option becomes a
function of that header; the resolver builds the setup before the published
re-checks so those stay adjacent to `resume`.
Conflicts:
docs/cordis-catalog/services.md
docs/module-graph.md
packages/host/apiproxy/package.json
packages/host/apiproxy/src/api-proxy.ts
pnpm-lock.yaml
Deleting the preset a user default names left the setting pointed at an id
nothing will ever supply again, and every session created without an explicit
pick then failed to start — the delete dialog called it 'new sessions cannot
select it', which understates a hard creation error. `remove` now clears the
user layer when it named the preset just deleted, exposing the deployment's own
default underneath. Storing a default that does not exist YET stays deliberate:
the roster is a live directory, so a name absent now may exist by the time a
session asks, and `resolve` still reports that case.
`agentPreset.select` also leaves the loopback set. It was pinned as a real
escalation — one preset mounts the toolset that edits the live runtime — but
`session.create` already takes an `agentPreset`, so pinning only the switch left
the same capability one method over. The deeper reason is that the capability is
not the preset's to grant: the deployment's own default already carries `bash`
and the filesystem tools, so any caller that may start a session at all can
already run commands as this process. `read`/`write`/`remove` stay pinned on
their own footing — those touch files, not sessions.
Adapter schemas now carry only credential references, but the Models join, onboarding readiness, shipped overlays, SDK scaffolding, fixtures, and active decision prose still treated a redacted literal apiKey as a supported compatibility state. That residue made an unsupported field look contractual and pinned Schemastery silent-dropping as behavior.
Delete those branches and examples, and let compositions and scaffolds use adapter-owned reference and environment resolution. Do not add a tombstone validator or change generic unknown-key behavior: literal adapter credentials have no migration contract to preserve.
Code already treats $DSH_HOME/.env as ordinary launch environment and stores managed credentials in .credentials.yaml, but public docs still described the old store, old precedence, removed literal adapter keys, and the deleted TUI. That directed users to the wrong file and overstated the supported configuration surface.
Update the existing English and Chinese owners in place, document inherited > managed > project > user credential resolution, and record the loadLayeredEnv export. Regenerate only pairing records and the source-line catalog; add no new section or site route.
**Reasoning effort leaves the provider cards entirely.** It is a per-MODEL
capability and the models under one provider disagree about which levels
they accept: setting `anthropic` to `max` made six of its eight models
throw UNSUPPORTED_REASONING_EFFORT, and because the catalog build catches
per provider, the whole provider vanished from the picker behind one error
row. A provider-scoped control can only ever be set to a value some of its
models reject.
The composer's model picker already offers each model its own levels, and
a switch there now records provider, model, and effort together as the
next session's default — so the setting has a better home at the right
granularity. The profile field stays in `settings.yaml` for a deployment
that knows its route; only the control is gone, from both cards and both
adapter families. Two `components.spec` cases used the control as the
vehicle for their op assertions and now use `baseURL`, which is what they
were actually testing.
**A rejected Provider ID now reads as a fault.** It shared the neutral
hint paragraph with the field's guidance, so the copy telling the user
what they got wrong looked like advice. Reuses the existing `.error`
style, matching the split the key field already makes.
The line under the create form names the one blocked gate worth naming,
and its fallback arm reads "no models yet". An unmet Provider ID gate fell
through to that arm, so a card with two models listed right above it was
told it needed one. The key gate was already excluded for this reason; the
route gate was assumed excluded because its field explains itself, and was
not.
Tightening the route rule in the previous commit is what made this easy to
hit — a digit-leading id now fails the gate — but the fallthrough predates
it and fires for an empty or taken id just the same.
**A hand-declared route must not offer a reasoning effort.** The earlier
commit read the create card's missing control as drift and added one. It
is the other way round: such a model has no reasoning capability — pi-ai's
installed catalog is what supplies one, and it ships nothing under the
route — so `resolveModel` throws UNSUPPORTED_REASONING_EFFORT for every
model on it and the whole provider drops out of the picker. Verified
against the adapter, not inferred. The create card no longer offers it and
the editor withholds it on the directory's `declared` bit, which is the
real bug: that control has always been wrong for these routes.
**A blocked composer locked the way out of the block.** Reusing the
no-workspace inert posture disabled the model seat along with everything
else, so the bar asked for a model while preventing the one control that
picks one. A block now rides its own `blocked` owner prop: the textarea,
send, commands, plan seat, and access chip all lock, and the model seat
alone stays live.
**A Provider ID could derive an illegal credential reference.** The card
accepted a digit-leading id, whose derived `123_API_KEY` then failed at
the credential seam with a raw regular expression the user cannot act on.
The id must now start with a letter, and a test pins the relation between
the two rules rather than the regex.
Threading a status reader and a patch callback made both call sites long
enough to read as the same code again; handing over the store is one line at
each and says the same thing.
master moved the RPC surface onto the fetch transport, so the two agentPreset
methods arrived there with nothing driving them. Adds the round trip, plus the
two `recompose` edges that had none: an agent that never composed a preset, so
there is nothing to restore, and a restore that fails because the composition
it reaches for is gone — the roster is a live directory.
Marks the deliberate non-Error rejections in the store specs, which is the
branch they exist to cover.