Merge remote-tracking branch 'origin/master' into codex/pr-555-ci-fix
# Conflicts: # docs/config-catalog.i18n.yaml # docs/config-catalog.md # docs/config-catalog.zh.md # docs/module-graph.i18n.yaml # docs/module-graph.md # docs/module-graph.zh.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/client/connection/src/client/fixture.ts # packages/host/apiproxy/src/api-proxy.ts
This commit is contained in:
88
packages/host/apiproxy/src/api/agent-presets.schema.ts
Normal file
88
packages/host/apiproxy/src/api/agent-presets.schema.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* agent-presets domain zod schemas (names derived from map keys:
|
||||
* agentPresetListRequestSchema / agentPresetListValueSchema).
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
|
||||
import type { Wire } from './rpc.schema.ts'
|
||||
import { sessionIdSchema } from './sessions.schema.ts'
|
||||
import type { AgentPresetEntry } from './agent-presets.ts'
|
||||
|
||||
/** AgentPresetEntry row of agentPreset.list. */
|
||||
export const agentPresetEntrySchema = z.object({
|
||||
id: z.string().min(1),
|
||||
trust: z.union([z.literal('system'), z.literal('user')]),
|
||||
isDefault: z.boolean(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
broken: z.string().min(1).optional(),
|
||||
}) satisfies z.ZodType<Wire<AgentPresetEntry>>
|
||||
|
||||
/** agentPreset.list request payload. */
|
||||
export const agentPresetListRequestSchema = z.object({
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'agentPreset.list'>>>
|
||||
|
||||
/** agentPreset.list response value. */
|
||||
export const agentPresetListValueSchema = z.object({
|
||||
presets: z.array(agentPresetEntrySchema),
|
||||
authorable: z.boolean(),
|
||||
hasDocument: z.boolean(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'agentPreset.list'>>>
|
||||
|
||||
/** agentPreset.select request payload. */
|
||||
export const agentPresetSelectRequestSchema = z.object({
|
||||
sessionId: sessionIdSchema,
|
||||
agentPreset: z.string().min(1),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'agentPreset.select'>>>
|
||||
|
||||
/** agentPreset.select response value. */
|
||||
export const agentPresetSelectValueSchema = z.object({
|
||||
agentPreset: z.string(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'agentPreset.select'>>>
|
||||
|
||||
/** agentPreset.read request payload. */
|
||||
export const agentPresetReadRequestSchema = z.object({
|
||||
agentPreset: z.string().min(1),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'agentPreset.read'>>>
|
||||
|
||||
/** agentPreset.read response value. */
|
||||
export const agentPresetReadValueSchema = z.object({
|
||||
agentPreset: z.string(),
|
||||
trust: z.union([z.literal('system'), z.literal('user')]),
|
||||
content: z.string(),
|
||||
name: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'agentPreset.read'>>>
|
||||
|
||||
/** agentPreset.copy request payload. */
|
||||
export const agentPresetCopyRequestSchema = z.object({
|
||||
from: z.string().min(1),
|
||||
agentPreset: z.string().min(1),
|
||||
name: z.string().optional(),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'agentPreset.copy'>>>
|
||||
|
||||
/** agentPreset.copy response value. */
|
||||
export const agentPresetCopyValueSchema = z.object({
|
||||
agentPreset: z.string(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'agentPreset.copy'>>>
|
||||
|
||||
/** agentPreset.openDocument request payload. */
|
||||
export const agentPresetOpenDocumentRequestSchema = z.object({
|
||||
agentPreset: z.string().min(1),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'agentPreset.openDocument'>>>
|
||||
|
||||
/** agentPreset.openDocument response value. */
|
||||
export const agentPresetOpenDocumentValueSchema = z.union([
|
||||
z.object({ opened: z.literal(true) }),
|
||||
z.object({ opened: z.literal(false), path: z.string() }),
|
||||
]) satisfies z.ZodType<Wire<ResponseValue<'agentPreset.openDocument'>>>
|
||||
|
||||
/** agentPreset.remove request payload. */
|
||||
export const agentPresetRemoveRequestSchema = z.object({
|
||||
agentPreset: z.string().min(1),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'agentPreset.remove'>>>
|
||||
|
||||
/** agentPreset.remove response value. */
|
||||
export const agentPresetRemoveValueSchema = z.object({
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'agentPreset.remove'>>>
|
||||
116
packages/host/apiproxy/src/api/agent-presets.ts
Normal file
116
packages/host/apiproxy/src/api/agent-presets.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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<RpcResponse<{ presets: readonly AgentPresetEntry[]; authorable: boolean; hasDocument: boolean }>>
|
||||
|
||||
/**
|
||||
* 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<RpcResponse<{ agentPreset: string }>>
|
||||
|
||||
/**
|
||||
* 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<RpcResponse<{
|
||||
agentPreset: string
|
||||
trust: 'system' | 'user'
|
||||
content: string
|
||||
name?: string
|
||||
description?: string
|
||||
}>>
|
||||
|
||||
/**
|
||||
* 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<RpcResponse<{ agentPreset: string }>>
|
||||
|
||||
/**
|
||||
* 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<RpcResponse<{ opened: true } | { opened: false; path: string }>>
|
||||
|
||||
/** Delete a locally authored preset. Shipped presets are refused. */
|
||||
remove(request: RpcRequest<{ agentPreset: string }>): Promise<RpcResponse<{}>>
|
||||
}
|
||||
@@ -73,6 +73,7 @@ export const hostFrameSchema = z.discriminatedUnion('type', [
|
||||
parentSessionId: sessionIdSchema.optional(),
|
||||
origin: z.literal('subagent').optional(),
|
||||
cwd: z.string().optional(),
|
||||
agentPreset: z.string().optional(),
|
||||
}),
|
||||
z.object({ type: z.literal('host/session-removed'), sessionId: sessionIdSchema }),
|
||||
z.object({ type: z.literal('host/session-status'), sessionId: sessionIdSchema, running: z.boolean() }),
|
||||
|
||||
@@ -116,6 +116,7 @@ export type HostFrame =
|
||||
parentSessionId?: SessionId
|
||||
origin?: 'subagent'
|
||||
cwd?: string
|
||||
agentPreset?: string
|
||||
}
|
||||
| { type: 'host/session-removed'; sessionId: SessionId }
|
||||
| { type: 'host/session-status'; sessionId: SessionId; running: boolean }
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { SessionsApi } from './sessions.ts'
|
||||
import type { HostApi } from './host.ts'
|
||||
import type { WorkspaceApi } from './workspace.ts'
|
||||
import type { CommandsApi } from './commands.ts'
|
||||
import type { AgentPresetsApi } from './agent-presets.ts'
|
||||
import type { SkillsApi } from './skills.ts'
|
||||
import type { SubagentsApi } from './subagents.ts'
|
||||
import type { EventsApi } from './events.ts'
|
||||
@@ -25,6 +26,7 @@ export interface ApiProxy {
|
||||
workspace: WorkspaceApi
|
||||
commands: CommandsApi
|
||||
skills: SkillsApi
|
||||
agentPresets: AgentPresetsApi
|
||||
events: EventsApi
|
||||
goals: GoalsApi
|
||||
settings: SettingsApi
|
||||
@@ -49,6 +51,7 @@ export type {
|
||||
export type { WorkspaceApi, WorkspaceId, WorkspaceView } from './workspace.ts'
|
||||
export type { CommandsApi, CommandDescriptor } from './commands.ts'
|
||||
export type { SkillsApi, SkillEntry } from './skills.ts'
|
||||
export type { AgentPresetsApi, AgentPresetEntry } from './agent-presets.ts'
|
||||
export type { EventsApi, MuxFrame, HostFrame, QueuedInboxItem, ToolCallView, ToolEventView, ToolResultView } from './events.ts'
|
||||
export type { GoalsApi, GoalId, GoalRef } from './goals.ts'
|
||||
export type { SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView } from './settings.ts'
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { SessionsApi } from './sessions.ts'
|
||||
import type { HostApi } from './host.ts'
|
||||
import type { WorkspaceApi } from './workspace.ts'
|
||||
import type { CommandsApi } from './commands.ts'
|
||||
import type { AgentPresetsApi } from './agent-presets.ts'
|
||||
import type { SkillsApi } from './skills.ts'
|
||||
import type { GoalsApi } from './goals.ts'
|
||||
import type { SettingsApi } from './settings.ts'
|
||||
@@ -52,6 +53,12 @@ export interface RpcMethodMap {
|
||||
'command.list': CommandsApi['list']
|
||||
'command.execute': CommandsApi['execute']
|
||||
'skill.list': SkillsApi['list']
|
||||
'agentPreset.list': AgentPresetsApi['list']
|
||||
'agentPreset.select': AgentPresetsApi['select']
|
||||
'agentPreset.read': AgentPresetsApi['read']
|
||||
'agentPreset.copy': AgentPresetsApi['copy']
|
||||
'agentPreset.openDocument': AgentPresetsApi['openDocument']
|
||||
'agentPreset.remove': AgentPresetsApi['remove']
|
||||
'goal.create': GoalsApi['create']
|
||||
'goal.edit': GoalsApi['edit']
|
||||
'goal.pause': GoalsApi['pause']
|
||||
|
||||
@@ -46,6 +46,11 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
|
||||
z.object({ code: z.literal('directory-exists'), message: z.string(), details: z.object({ path: z.string() }) }),
|
||||
z.object({ code: z.literal('directory-create-failed'), message: z.string(), details: z.object({ path: z.string() }) }),
|
||||
z.object({ code: z.literal('directory-picker-unavailable'), message: z.string(), details: z.object({ capability: z.string() }) }),
|
||||
z.object({ code: z.literal('agent-preset-read-only'), message: z.string(), details: z.object({ agentPreset: z.string(), reason: z.string() }) }),
|
||||
z.object({ code: z.literal('agent-preset-locked'), message: z.string(), details: z.object({ sessionId: z.string(), agentPreset: z.string() }) }),
|
||||
z.object({ code: z.literal('agent-preset-conflict'), message: z.string(), details: z.object({ sessionId: z.string(), requestedPreset: z.string(), existingPreset: z.string().optional() }) }),
|
||||
z.object({ code: z.literal('agent-preset-not-found'), message: z.string(), details: z.object({ agentPreset: z.string(), available: z.array(z.string()) }) }),
|
||||
z.object({ code: z.literal('agent-preset-invalid'), message: z.string(), details: z.object({ agentPreset: z.string(), reason: z.string() }) }),
|
||||
z.object({ code: z.literal('agent-busy'), message: z.string(), details: z.object({ reason: z.string() }) }),
|
||||
z.object({ code: z.literal('attachment-error'), message: z.string(), details: z.object({ reason: z.string() }) }),
|
||||
z.object({ code: z.literal('queue-item-not-found'), message: z.string(), details: z.object({ itemId: z.string() }) }),
|
||||
|
||||
@@ -44,6 +44,11 @@ export interface RpcErrorDetailsMap {
|
||||
'directory-exists': { path: string }
|
||||
'directory-create-failed': { path: string }
|
||||
'directory-picker-unavailable': { capability: string }
|
||||
'agent-preset-read-only': { agentPreset: string; reason: string }
|
||||
'agent-preset-locked': { sessionId: SessionId; agentPreset: string }
|
||||
'agent-preset-conflict': { sessionId: SessionId; requestedPreset: string; existingPreset?: string }
|
||||
'agent-preset-not-found': { agentPreset: string; available: string[] }
|
||||
'agent-preset-invalid': { agentPreset: string; reason: string }
|
||||
'agent-busy': { reason: string }
|
||||
'attachment-error': { reason: string }
|
||||
'queue-item-not-found': { itemId: MessageId }
|
||||
|
||||
@@ -56,6 +56,7 @@ export const sessionSummarySchema = z.object({
|
||||
parentSessionId: sessionIdSchema.optional(),
|
||||
origin: z.literal('subagent').optional(),
|
||||
cwd: z.string().optional(),
|
||||
agentPreset: z.string().optional(),
|
||||
projections: z.lazy(() => sessionProjectionsBlockSchema).optional(),
|
||||
}) as unknown as z.ZodType<Wire<SessionSummary>>
|
||||
|
||||
@@ -101,6 +102,7 @@ export const sessionCreateRequestSchema = z.object({
|
||||
workspaceId: workspaceIdSchema.optional(),
|
||||
cwd: z.string().optional(),
|
||||
sessionId: sessionIdSchema.optional(),
|
||||
agentPreset: z.string().optional(),
|
||||
}).refine(
|
||||
payload => payload.workspaceId === undefined || payload.cwd === undefined,
|
||||
{ message: 'session.create accepts workspaceId or cwd, not both' },
|
||||
@@ -109,6 +111,7 @@ export const sessionCreateRequestSchema = z.object({
|
||||
/** session.create response value. */
|
||||
export const sessionCreateValueSchema = z.object({
|
||||
sessionId: sessionIdSchema,
|
||||
agentPreset: z.string().optional(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'session.create'>>>
|
||||
|
||||
/** session.rename request payload (raw title; host-side normalization decides acceptance). */
|
||||
|
||||
@@ -171,6 +171,13 @@ export interface SessionSummary {
|
||||
origin?: 'subagent'
|
||||
/** Session working directory (header.cwd passthrough); absent when unrecorded. */
|
||||
cwd?: string
|
||||
/**
|
||||
* Agent preset this session's agent was composed from (header passthrough);
|
||||
* absent when the deployment composes no presets. A surface offering a
|
||||
* switch reads this to show what the session actually runs rather than what
|
||||
* the deployment currently defaults to.
|
||||
*/
|
||||
agentPreset?: string
|
||||
/**
|
||||
* Projection baseline for this row, with zero log loads: attached sessions
|
||||
* read the registry's live watermark cut; cold sessions read the persisted
|
||||
@@ -214,9 +221,16 @@ export interface SessionsApi {
|
||||
* session, while a different cwd fails with `session-conflict`. Workspace
|
||||
* creation attaches the session after publication; an attach failure
|
||||
* returns `workspace-attach-failed` with the published session id.
|
||||
*
|
||||
* `agentPreset` names the composition the new session's agent is built
|
||||
* from; omitted, the effective default applies — the user's stored choice
|
||||
* where one exists, else the deployment's own. The resolved id is stored on
|
||||
* the session header, so a later resume rebuilds the same agent. An unknown
|
||||
* id fails with `agent-preset-not-found`, and a preset whose composition
|
||||
* cannot be mounted fails with `agent-preset-invalid`.
|
||||
*/
|
||||
create(request: RpcRequest<{ workspaceId?: WorkspaceId; cwd?: string; sessionId?: SessionId }>):
|
||||
Promise<RpcResponse<{ sessionId: SessionId }>>
|
||||
create(request: RpcRequest<{ workspaceId?: WorkspaceId; cwd?: string; sessionId?: SessionId; agentPreset?: string }>):
|
||||
Promise<RpcResponse<{ sessionId: SessionId; agentPreset?: string }>>
|
||||
|
||||
/**
|
||||
* Reads a window of history events; page boundaries align to append-origin message
|
||||
|
||||
Reference in New Issue
Block a user