The agent plane of this stack's compositions lives behind agent presets,
whose per-preset skills realm has no seam for a deployment-level provider
yet: the fixture's dsh.skills made the prepared wrapper wait forever on a
host skills registry the web/headless profiles no longer compose. Remove
the skill root and its assertions from the fixture and record the gap in
the package's Known Limitations; MCP and entry contributions stay covered
because the tools registry is host-plane and layered.
A hand-damaged preset was silent until the worst moment. An unparsable
composition listed as an ordinary selectable row and failed only at the
next session start — set as default, every new session failed. A
directory whose composition file was deleted vanished from the roster
while still occupying its id: copy answered "delete the existing preset
first" while remove answered "not found", a dead end.
Discovery now owns health: every id-shaped directory is a roster slot,
broken when its composition is missing or unloadable, checked with the
loader's own entryListSchema dialect (!!js included) so health never
rejects what the loader accepts. `broken` rides AgentPreset, the
agentPreset.list entry, and the UI row; mount/recompose/standingKeyFor
refuse broken up front with the discovery-reported reason, while
resolve/read/remove still answer. The section renders marked red cards —
unselectable, uncopyable, deletable, location kept on custom rows — and
both pickers drop broken rows entirely.
The cordis preset's persona now forbids editing the shipped install
(corrupting cordis would disable the mode itself) and points authoring
at $DSH_HOME/.agent-presets; its skill teaches preset.yml metadata, the
copy-first workflow, the one-escalation sandbox reality, and honest
verification. Exercised live: asked to edit the shipped composition the
composed agent refuses citing both rules; asked for real presets (simple
and complex) it lands them under the user root with one approved
escalation each and self-checks with the loader dialect.
Copying an existing preset — or drafting one in Creator mode — replaces
the copy-minimal skeleton hint: the creator entry exists now, so the intro
names the two real paths instead of steering everyone at one source.
The stub's confirmed-switch write-back landed with the header-label work
but nothing exercised it, and the module graph regen after the master
merge had been swallowed by a silenced generator run.
The exported mountPreset carries its own unscoped-context guard beside the
service's, and the per-file gate wants it exercised where it lives. The
module graph regen after the master merge had been swallowed by a silenced
generator run.
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.
The duotone folder_open_16 reads a rung heavier than the …Outline16 family
— its 20%-opacity inner fill looks filled beside pure-outline neighbors —
so an icon-button row mixing them looks mismatched. Same geometry, outline
layer only; the workspace surfaces keep the duotone original.
The roots are an assembly fact (the shipped set beside this app's config,
the user's own under $DSH_HOME) but only `dsh web` patched them in, so the
merged `dsh run` booted the roster with no roots and failed resolving
`standard`. The shared profile boot now owns the patch for every launcher,
and master's interrupt_agent tool joins the standard composition's exact
catalog. The roster helpers the wire layer added (standingKeyFor,
serviceForAgent's chain guards, the direct mountPreset boundary) gain the
unit coverage the per-file gate requires.
The roots are an assembly fact (the shipped set beside this app's config,
the user's own under $DSH_HOME) but only `dsh web` patched them in, so the
merged `dsh run` booted the roster with no roots and failed resolving
`standard`. The shared profile boot now owns the patch for every launcher;
the one-shot transcript header consequently records its composing preset,
and master's interrupt_agent tool joins the standard composition's exact
catalog.
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.
20 files, each +2 lines: the label icon and 标准模式 in the banner — the DOM
the label fix makes appear for sessions created in-tab, which these goldens
recorded the absence of.
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.
session.create's reply named only the id, and the session-added frame's
agentPreset — which the server already derives via sessionListFields — was
stripped by the wire schema it never joined. A client therefore could not
label a session it just created (or learned live) until the next full list
refresh: the header's preset label rendered nothing for exactly the sessions
made in this tab. The create reply is the commit point that knows the
RESOLVED composition (a caller that named none gets the default the header
recorded), so both carriers now say it.
Authoring meets standing mounts: write() and remove() drop the standing
pointer so the NEXT session composes the edited roster, while every session
already joined keeps the generation it runs on — a superseded generation is
never disposed while the process lives. The settings-dialog golden re-records
with this layer's Agent Preset nav entry, which the incoming layer-3 record
had overwritten.
recompose becomes a parent re-link: the new preset's standing mount is
ensured BEFORE the link moves, so a failed switch leaves the agent exactly as
it was — the unmount-then-restore dance (and unmountPresetFor with it) is
gone, and the restore-failure test now asserts the agent KEEPS its tools with
the source directory deleted, because the standing mount is not the file.
Rewires this layer onto the standing-mount model:
- serviceForAgent roots its search at the agent's standing mount (parent
scope key → live mount fiber) — the composition no longer lives under the
agent's own fiber, and two agents on one preset now address ONE instance,
which the sharing test asserts instead of distinctness.
- viewFor/historyPage take a registry view SCOPE. A live agent is that scope;
a cold read uses the recorded preset's standing key via standingKeyFor —
composing plugins but starting no agent, session, or turn. A header without
a preset (a pre-roster log) renders through the DEFAULT preset's standing
layer; an unusable preset degrades the read to generic cards, never fails
it. This turns produced-files and chat-scroll green structurally, with the
token counts untouched (no resume, so the projections fold stays detached).
- The detached projections baseline now includes every standing unit's key at
its empty fold (todos: null): the standing mount registers units
deterministically, which is what makes the client's "omitted key =
capability absence → clear" rule safe again. seeded-history's contract test
asserts the new shape.
- The standard preset's realm preamble no longer claims a shared label pools
instances — provide() throws on the second registration under one realm
symbol; labels join REALMS.
A preset is now ONE composition per process, not one per session. The roster
mounts it once under a synthetic standing scope; each agent joins by having
its scope key parented to the mount's. Two mechanisms in dsh-scope carry the
whole change: registration views walk the parent chain (global → preset →
agent, nearest shadowing farthest — ScopedLayers.chainLayers), and scoped
event dispatch admits a listener tagged with an ancestor of the carrier key,
which is what lets a standing composition's plan/compaction/token listeners
observe each agent composed under it while a sibling preset's stay deaf.
The preset plugins already key their state by Session/Agent — they predate
presets and were written for the shared world — so sharing one instance is a
return to their design, not a rewrite. Preset ymls are unchanged: one mount
per preset means one Entry per preset, whose entry-local realms keep two
presets' services apart exactly as they kept two sessions' apart before.
The standing scope hangs off the service's UNTRACED context (selfCtx): a
method invoked through the traceable proxy sees this.ctx rebound to the
caller and carrying its shadow, and a subtree minted from that resolves every
service through the shadow's fiber instead of each entry's own inject store —
preset rows then fail on the very services they declare.
A standing mount survives its agents deliberately. The composition a running
session joined must outlive the file changing or disappearing underneath it;
reclamation happens at whole-tree teardown, and file edits reach only future
generations (the authoring layer swaps the pointer, never disposes a joined
generation).
`cordis` and `code` are full copies of `standard`, so each carried its own
copy of the goals group the Gateway cannot reach through. Same change, same
reason.
The Gateway serves the goal domain as Remote endpoints, and a Remote method
picks its receiver Service from a generated descriptor — `clear(agent, ref)`
takes its agent as a PARAMETER, so the invocation is direct and the receiver
resolves on the host. Behind the preset's entry-local realm there was nothing
to resolve, and every browser goal call answered `service-unavailable`: the
composer's Clear goal button left the bar on screen.
That is the `bash-env` criterion read from the other side. Injection is not the
only host relationship a Service can have; being READ from a host row is one
too. The registry is keyed by session, so one host instance serves every
session exactly as it did before presets. The preset keeps the model-facing
tool, which is the choice a preset is for.
Also reverts the cold-transcript presenter resolve: resuming an agent to reach
its presenters made the context meter drop its cache and token counts on every
cold-opened session, trading one silent degradation for another. The card
degradation it addressed is diagnosed and still open.
A preset registers its tools into the agent's OWN layer, so `session.history`
served while no agent exists found no presenter at all and every card degraded
to the generic renderer — silently, because a viewless entry is also what a
tool with no presenter produces. The web client opens a session by reading its
transcript, so this was the ordinary path, not an edge: the write row lost its
diff card, and with it the `locations` the produced-files row derives from.
The read now resolves the agent through the same deduplicated resume every
other session method takes, but only when a roster is composed: a deployment
without presets keeps its tools on the host layer, which needs no agent to
address, and keeps the storage-only read unchanged. A resolution failure stays
a successful read.
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
The debounced writer assigns undefined on flush, which a plain optional
NodeJS.Timeout rejects under exactOptionalPropertyTypes; the error had
been masked by stale build state until a residue cleanup invalidated it.
Logged as local modification 14 in the vendor manifest.
Invocation is an ordinary session.prompt again: the pre-step gesture
boundary makes it deterministic host-side for every front end, so the
dedicated RPC (handler, wire schema, error codes, client face, fixtures)
and ui-skill's claim machinery are net deletions. The menu keeps decision
21 exactly — a pick lands literal /name text — plus the user-only marker
from skill.list's modelInvocable flag.
A whitespace-bounded /name token anywhere in a claimed user message,
naming a user-invocable skill in the workspace directory, now injects that
skill's renderSkillContent as instructions context appended after every
other injection of the step — the same agent/pre-step seam the catalog,
workspace instructions, and the runtime snapshot ride. Closed-set matching
mirrors the command registry (a miss stays plain prose), only user-source
messages are scanned, the policy check runs on the loaded definition, and
this is the sole entry point for disable-model-invocation skills. The
catalog's no-reload sentence now names the gesture boundary.