feat(web): compose a web session's agent from a named preset

`session.create` takes an optional `agentPreset`, and the host resolves it,
mounts it during pre-publication setup, and records the resolved id on the
session header so a later resume rebuilds the same agent.

Resolution happens BEFORE the session exists, not inside setup: the session
boundary snapshots `meta` before asynchronous setup begins, so an id discovered
during setup could never reach the header. Mounting still happens in setup,
where a failure rolls the whole creation back rather than publishing a session
whose capabilities are half-installed.

Resume ignores whatever the request names and rebuilds from the stored id. A
resumed session's history was produced under that composition; restoring a
different one would replay tool calls the model can no longer make.

`dsh-agent-presets` now throws `UnknownPresetError` / `PresetMountError` so
the host can tell a bad request from a broken preset — they become
`agent-preset-not-found` and `agent-preset-invalid`.

Ships the two built-in compositions (`standard`, `core-web`) and the persona
row that lets them differ in identity. Nothing mounts them yet: no roster is
configured, so `composeAgent` finds no service and every session keeps the host
composition. Wiring the roster and moving base's agent-plane rows behind it is
the next commit, so the switch happens atomically with a real-composition test.
This commit is contained in:
Yichen Jiang
2026-08-03 22:38:11 +08:00
parent 739042a804
commit 91b55b9245
22 changed files with 458 additions and 39 deletions
+9 -1
View File
@@ -77,12 +77,19 @@ interface SessionHeader {
* resume — a runtime-only depth would reset a resumed child to top-level.
*/
readonly delegationDepth?: number
/**
* Id of the agent preset this session's agent was composed from, when the
* deployment composes per session. Durable because the preset decides the
* session's tools and prompt: a resume that restored a different composition
* would replay history the model can no longer act on.
*/
readonly agentPreset?: string
}
```
## `CreateSessionOptions` — seeding and metadata
Creating a `Session` through the store takes a `seed` (initial replay or fork history) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller may supply the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the optional coarse `origin`, the `delegationDepth`, and an existing `createdAt`. `origin: 'subagent'` lets product navigation hide duplicate child rows; it does not prove that a descriptor is valid or that the child can resume.
Creating a `Session` through the store takes a `seed` (initial replay or fork history) and `meta` (the storage-level fields the store folds into a `SessionHeader`). The store fills in `version`/`id` and defaults `createdAt`; the caller may supply the validated absolute `cwd`, the `parentSession` lineage, the `seedLength` seed boundary, the optional coarse `origin`, the `delegationDepth`, the `agentPreset` the agent was composed from, and an existing `createdAt`. `origin: 'subagent'` lets product navigation hide duplicate child rows; it does not prove that a descriptor is valid or that the child can resume.
```ts type-equiv
/**
@@ -104,6 +111,7 @@ interface CreateSessionOptions {
readonly seedLength?: number
readonly origin?: 'subagent'
readonly delegationDepth?: number
readonly agentPreset?: string
}
}
```