/** * agent-presets domain contract: the roster a browser offers when starting a * session, plus the authoring calls behind it. * * `list` is ordinary: it carries ids and trust, and every preset picker needs * it. The authoring calls are privileged and loopback-pinned — a composition * names the plugins a session runs, so reading one is reconnaissance, and * although authoring is copy-only (no caller supplies composition text or a * path), copying and deleting still rearrange what the deployment offers. */ import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { RpcRequest, RpcResponse } from './rpc.ts' /** One preset the deployment can compose a session's agent from. */ export interface AgentPresetEntry { /** Stable identifier, also the display name until presets carry metadata. */ readonly id: string /** * Whether the preset ships with the deployment or was authored locally. * A `user` preset is exactly as privileged as the plugins it names, so a * surface offering one should say so rather than present it as vetted. */ readonly trust: 'system' | 'user' /** Whether a session that names no preset gets this one. */ readonly isDefault: boolean /** * Display name the preset published, absent when it published none. A * surface falls back to {@link id}; it is never a second identity, and it * never decides trust — a locally authored preset cannot name itself into * the shipped set. */ readonly name?: string /** One sentence on what the preset is for, when it published one. */ readonly description?: string /** * Why this preset cannot compose a session, absent when it can. A broken * preset stays listed — its directory still occupies the id, so a surface * must be able to show and delete it — but offering it for selection would * only defer this reason to a failed session start. */ readonly broken?: string } /** agent-preset-domain unary methods (the map key agentPreset.* of RpcMethodMap). */ export interface AgentPresetsApi { /** * Lists every preset the deployment currently supplies, in root-precedence * order — the roots as configured, each root's own presets sorted by id, * and the first root to supply an id wins. The order is not globally * sorted: a user root's preset sits in that root's block, not among the * shipped ids. * An empty roster means the deployment composes no presets at all, and * every session shares the host composition. `authorable` reports whether * the deployment configures a root new presets can be written to, and * `hasDocument` whether `openDocument` can hand a preset directory to a * native opener — both deployment facts rather than per-preset ones, and * neither exposes a Host path. */ list(request: RpcRequest<{}>): Promise> /** * Recompose one session's agent from a different preset. * * Allowed only while the session is blank — no turn has run. Once a * conversation starts, its history was produced under that preset's tools, * and swapping them would leave logged tool calls the new composition cannot * make; the attempt answers `agent-preset-locked`. */ select(request: RpcRequest<{ sessionId: SessionId; agentPreset: string }>): Promise> /** * Read one preset's composition text, for the read-only viewer. * * Privileged: a composition names the plugins a session runs, so reading * one is reconnaissance. */ read(request: RpcRequest<{ agentPreset: string }>): Promise> /** * Create a locally authored preset by copying an existing one whole. * * The only authoring write. No composition text and no path crosses the * wire: `from` and `agentPreset` are ids the Host resolves against its own * roots, so a copy is exactly as loadable as its source and grants nothing * the roster did not already carry. The copy keeps the source's description * (the file is the author's to edit afterwards) but not its name — `name` * here or the id fallback is what distinguishes the rows. */ copy(request: RpcRequest<{ from: string; agentPreset: string; name?: string }>): Promise> /** * Hand one locally authored preset's DIRECTORY to the platform opener, for * editing the files that are now the only composition editor. The request * carries an id, never a path — the Host resolves it — so no browser * payload can select an arbitrary filesystem target. Where the deployment * has no native opener (`hasDocument: false` on `list`), the reply carries * the resolved directory for the surface to show as text instead. Shipped * presets are refused: their install is not the user's to manage. */ openDocument(request: RpcRequest<{ agentPreset: string }>, signal: AbortSignal): Promise> /** Delete a locally authored preset. Shipped presets are refused. */ remove(request: RpcRequest<{ agentPreset: string }>): Promise> }