Merge remote-tracking branch 'origin/master' into worktree/custom-deepseek-models
# Conflicts: # apps/web/tests/snapshots/message-actions/ui.expected.md # apps/web/tests/snapshots/seeded-history/command-row.expected.md # apps/web/tests/snapshots/seeded-history/ui.expected.md # packages/client/ui-model/README.i18n.yaml # packages/client/ui-model/README.md # packages/client/ui-model/README.zh.md # packages/client/ui-model/tests/model-select.spec.tsx # packages/host/apiproxy/README.i18n.yaml
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* commands domain contract: the web catalog/dispatch face of the host command
|
||||
* registry (`ctx.commands`). Both methods address one session's agent via
|
||||
* `sessionId` — every served session has an Agent (Session+Agent are born
|
||||
* together), so there is no agent-less surface on this wire.
|
||||
* registry (`ctx.commands`). Both methods address an ordinary session's Agent
|
||||
* via `sessionId`, resuming it when cold. Session-backed subagents reject with
|
||||
* `agent-busy` and retain their dedicated continuation owner.
|
||||
*/
|
||||
|
||||
import type { CommandId } from '@deepseek-ai/dsh-commands/brand'
|
||||
@@ -27,7 +27,8 @@ export interface CommandDescriptor {
|
||||
export interface CommandsApi {
|
||||
/**
|
||||
* Lists the addressed agent's effective command catalog (name-sorted,
|
||||
* globals plus its scoped shadows).
|
||||
* globals plus its scoped shadows). Session-backed subagents reject with
|
||||
* `agent-busy`.
|
||||
*/
|
||||
list(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<{ commands: readonly CommandDescriptor[] }>>
|
||||
|
||||
@@ -42,6 +43,7 @@ export interface CommandsApi {
|
||||
* pairing id, letting the issuing client correlate this acknowledgment
|
||||
* with that flow node. The signal rides beside the request, never on the
|
||||
* wire: the fetch carrier's request signal cancels the running handler.
|
||||
* Session-backed subagents reject with `agent-busy` before dispatch.
|
||||
*/
|
||||
execute(request: RpcRequest<{ sessionId: SessionId; line: string }>, signal: AbortSignal):
|
||||
Promise<RpcResponse<{ matched: boolean; commandId?: CommandId }>>
|
||||
|
||||
@@ -54,6 +54,7 @@ export const muxFrameSchema = z.discriminatedUnion('type', [
|
||||
sessionId: sessionIdSchema,
|
||||
items: z.array(z.object({
|
||||
id: inboxItemIdSchema,
|
||||
placement: z.union([z.literal('queued'), z.literal('steering')]),
|
||||
message: messageSchema,
|
||||
})),
|
||||
}),
|
||||
@@ -65,7 +66,14 @@ export const muxFrameSchema = z.discriminatedUnion('type', [
|
||||
|
||||
/** HostFrame union (payload slot of a host-stream ServerRequest). */
|
||||
export const hostFrameSchema = z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('host/session-added'), sessionId: sessionIdSchema, blank: z.boolean(), parentSessionId: sessionIdSchema.optional(), cwd: z.string().optional() }),
|
||||
z.object({
|
||||
type: z.literal('host/session-added'),
|
||||
sessionId: sessionIdSchema,
|
||||
blank: z.boolean(),
|
||||
parentSessionId: sessionIdSchema.optional(),
|
||||
origin: z.literal('subagent').optional(),
|
||||
cwd: 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() }),
|
||||
z.object({ type: z.literal('host/agent-error'), sessionId: sessionIdSchema, message: z.string() }),
|
||||
|
||||
@@ -32,10 +32,12 @@ export type ToolEventView =
|
||||
| { for: 'call'; view: ToolCallView }
|
||||
| { for: 'result'; view: ToolResultView }
|
||||
|
||||
/** One pending queued occurrence in an authoritative queue snapshot. */
|
||||
/** One pending inbox occurrence in the authoritative `session/queue` snapshot. */
|
||||
export interface QueuedInboxItem {
|
||||
/** Agent-owned occurrence identity used by queue mutations. */
|
||||
/** Agent-owned occurrence identity; queue mutations address only `queued` items. */
|
||||
id: InboxItemId
|
||||
/** Agent-resolved FIFO placement; clients render queued and steering items on different surfaces. */
|
||||
placement: 'queued' | 'steering'
|
||||
/** Complete pending message; it is not durable until the Agent claims it. */
|
||||
message: Message
|
||||
}
|
||||
@@ -71,11 +73,12 @@ export type MuxFrame =
|
||||
| { type: 'question/requested'; sessionId: SessionId; questions: AskUserQuestionItem[] }
|
||||
| { type: 'question/resolved'; sessionId: SessionId; questionRpcId: RpcId; outcome: 'answered' | 'cancelled' }
|
||||
/**
|
||||
* Complete transient queue state after every enqueue, mutation, claim, or
|
||||
* Complete transient inbox state after every enqueue, mutation, claim, or
|
||||
* discard. Pending work is not model-visible and therefore has no durable
|
||||
* session event; the whole snapshot makes edit, deletion, cancel, and
|
||||
* reconnect converge through one authoritative signal. Pending steering is
|
||||
* outside this Web queue projection.
|
||||
* reconnect converge through one authoritative signal. `session/queue`
|
||||
* covers both resolved placements: queued items render
|
||||
* in QueueDock, while pending steering renders at the conversation tail.
|
||||
*/
|
||||
| { type: 'session/queue'; sessionId: SessionId; items: QueuedInboxItem[] }
|
||||
/**
|
||||
@@ -90,9 +93,9 @@ export type MuxFrame =
|
||||
| { type: 'stream/error'; error: RpcError }
|
||||
|
||||
/**
|
||||
* Host stream frames. session-added carries the lineage anchor, the project
|
||||
* cwd, and the blank bit (the list-summary fields a client cannot wait for a
|
||||
* refresh to learn); the frame fires at session/created, so blank is
|
||||
* Host stream frames. session-added carries the lineage anchor, product
|
||||
* origin, project cwd, and blank bit (the list-summary fields a client cannot
|
||||
* wait for a refresh to learn); the frame fires at session/created, so blank is
|
||||
* constantly true — clients flip it on the session's first
|
||||
* `host/session-status(running:true)` (a blank session never runs), and a
|
||||
* reconnecting client takes `session.list`'s summary.blank as authoritative.
|
||||
@@ -106,7 +109,14 @@ export type MuxFrame =
|
||||
* workspace-changed — `workspace.list` re-baselines it on reconnect).
|
||||
*/
|
||||
export type HostFrame =
|
||||
| { type: 'host/session-added'; sessionId: SessionId; blank: boolean; parentSessionId?: SessionId; cwd?: string }
|
||||
| {
|
||||
type: 'host/session-added'
|
||||
sessionId: SessionId
|
||||
blank: boolean
|
||||
parentSessionId?: SessionId
|
||||
origin?: 'subagent'
|
||||
cwd?: string
|
||||
}
|
||||
| { type: 'host/session-removed'; sessionId: SessionId }
|
||||
| { type: 'host/session-status'; sessionId: SessionId; running: boolean }
|
||||
| { type: 'host/agent-error'; sessionId: SessionId; message: string }
|
||||
|
||||
@@ -22,7 +22,11 @@ export interface GoalRef {
|
||||
readonly revision: number
|
||||
}
|
||||
|
||||
/** Goal-domain unary methods (every mutation resolves the session's agent and applies one CAS-guarded verb). */
|
||||
/**
|
||||
* Goal-domain unary methods. Every mutation resolves an ordinary session's
|
||||
* Agent and applies one CAS-guarded verb; session-backed subagents reject with
|
||||
* `agent-busy`.
|
||||
*/
|
||||
export interface GoalsApi {
|
||||
/** Create and arm a goal. */
|
||||
create(request: RpcRequest<{ sessionId: SessionId; objective: string; maxGoalRounds?: number }>):
|
||||
|
||||
@@ -9,6 +9,7 @@ import type { HostApi } from './host.ts'
|
||||
import type { WorkspaceApi } from './workspace.ts'
|
||||
import type { CommandsApi } from './commands.ts'
|
||||
import type { SkillsApi } from './skills.ts'
|
||||
import type { SubagentsApi } from './subagents.ts'
|
||||
import type { EventsApi } from './events.ts'
|
||||
import type { GoalsApi } from './goals.ts'
|
||||
import type { SettingsApi } from './settings.ts'
|
||||
@@ -19,6 +20,7 @@ import type { ClientResponse, RpcReceipt } from './rpc.ts'
|
||||
/** Root interface of the unified API surface. New client-request domain = one new file pair + one field here + one map row. */
|
||||
export interface ApiProxy {
|
||||
sessions: SessionsApi
|
||||
subagents: SubagentsApi
|
||||
host: HostApi
|
||||
workspace: WorkspaceApi
|
||||
commands: CommandsApi
|
||||
@@ -39,6 +41,9 @@ export type {
|
||||
SessionsApi, SessionSummary,
|
||||
} from './sessions.ts'
|
||||
export type { DirectoryEntry, DirectoryListing, HostApi } from './host.ts'
|
||||
export type {
|
||||
SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, SubagentsApi,
|
||||
} from './subagents.ts'
|
||||
export type { WorkspaceApi, WorkspaceId, WorkspaceView } from './workspace.ts'
|
||||
export type { CommandsApi, CommandDescriptor } from './commands.ts'
|
||||
export type { SkillsApi, SkillEntry } from './skills.ts'
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { GoalsApi } from './goals.ts'
|
||||
import type { SettingsApi } from './settings.ts'
|
||||
import type { CredentialsApi } from './credentials.ts'
|
||||
import type { LlmApi } from './llm.ts'
|
||||
import type { SubagentsApi } from './subagents.ts'
|
||||
import type { RpcResponse } from './rpc.ts'
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,9 @@ export interface RpcMethodMap {
|
||||
'session.prompt': SessionsApi['prompt']
|
||||
'session.updateQueue': SessionsApi['updateQueue']
|
||||
'session.cancel': SessionsApi['cancel']
|
||||
'subagent.list': SubagentsApi['list']
|
||||
'subagent.history': SubagentsApi['history']
|
||||
'subagent.prompt': SubagentsApi['prompt']
|
||||
'host.describe': HostApi['describe']
|
||||
'host.pickDirectory': HostApi['pickDirectory']
|
||||
'host.listDirectory': HostApi['listDirectory']
|
||||
|
||||
@@ -48,6 +48,7 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
|
||||
z.object({ code: z.literal('directory-picker-unavailable'), message: z.string(), details: z.object({ capability: z.string() }) }),
|
||||
z.object({ code: z.literal('agent-busy'), 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() }) }),
|
||||
z.object({ code: z.literal('steer-unavailable'), message: z.string(), details: z.object({ itemId: z.string() }) }),
|
||||
z.object({ code: z.literal('command-error'), message: z.string(), details: z.object({}) }),
|
||||
z.object({ code: z.literal('unknown-command'), message: z.string(), details: z.object({}) }),
|
||||
z.object({ code: z.literal('settings-rejected'), message: z.string(), details: z.object({ ns: z.string() }) }),
|
||||
@@ -56,6 +57,16 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
|
||||
z.object({ code: z.literal('credential-rejected'), message: z.string(), details: z.object({ ref: z.string() }) }),
|
||||
z.object({ code: z.literal('title-invalid'), message: z.string(), details: z.object({ sessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('fork-unavailable'), message: z.string(), details: z.object({ sessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('subagent-parent-unavailable'), message: z.string(), details: z.object({ parentSessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('subagent-not-found'), message: z.string(), details: z.object({ parentSessionId: z.string(), childSessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('subagent-catalog-diagnostic'), message: z.string(), details: z.object({
|
||||
parentSessionId: z.string(),
|
||||
childSessionId: z.string(),
|
||||
reason: z.union([z.literal('corrupt'), z.literal('unsupported'), z.literal('unavailable')]),
|
||||
}) }),
|
||||
z.object({ code: z.literal('subagent-not-resumable'), message: z.string(), details: z.object({ childSessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('subagent-unauthorized'), message: z.string(), details: z.object({ childSessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('subagent-delivery-unavailable'), message: z.string(), details: z.object({ childSessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('internal'), message: z.string(), details: z.object({}) }),
|
||||
]) as unknown as z.ZodType<RpcError>
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface RpcErrorDetailsMap {
|
||||
'directory-picker-unavailable': { capability: string }
|
||||
'agent-busy': { reason: string }
|
||||
'queue-item-not-found': { itemId: InboxItemId }
|
||||
'steer-unavailable': { itemId: InboxItemId }
|
||||
/** A known slash command reported a usage/state error; the message is the command's own text. */
|
||||
'command-error': {}
|
||||
/** A leading-/ prompt named no registered command; the message names the token. */
|
||||
@@ -71,6 +72,16 @@ export interface RpcErrorDetailsMap {
|
||||
'credential-rejected': { ref: string }
|
||||
'title-invalid': { sessionId: SessionId }
|
||||
'fork-unavailable': { sessionId: SessionId }
|
||||
'subagent-parent-unavailable': { parentSessionId: SessionId }
|
||||
'subagent-not-found': { parentSessionId: SessionId; childSessionId: SessionId }
|
||||
'subagent-catalog-diagnostic': {
|
||||
parentSessionId: SessionId
|
||||
childSessionId: SessionId
|
||||
reason: 'corrupt' | 'unsupported' | 'unavailable'
|
||||
}
|
||||
'subagent-not-resumable': { childSessionId: SessionId }
|
||||
'subagent-unauthorized': { childSessionId: SessionId }
|
||||
'subagent-delivery-unavailable': { childSessionId: SessionId }
|
||||
'internal': {}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ export const sessionSummarySchema = z.object({
|
||||
running: z.boolean(),
|
||||
blank: z.boolean(),
|
||||
parentSessionId: sessionIdSchema.optional(),
|
||||
origin: z.literal('subagent').optional(),
|
||||
cwd: z.string().optional(),
|
||||
projections: z.lazy(() => sessionProjectionsBlockSchema).optional(),
|
||||
}) as unknown as z.ZodType<Wire<SessionSummary>>
|
||||
@@ -193,10 +194,10 @@ export const toolEventViewSchema = z.discriminatedUnion('for', [
|
||||
]) as unknown as z.ZodType<ToolEventView>
|
||||
|
||||
/** One session.history item: the session event plus its optional host-computed tool view. */
|
||||
export const historyEntrySchema = z.object({
|
||||
export const historyEntrySchema: z.ZodType<Wire<HistoryEntry>> = z.object({
|
||||
event: sessionEventSchema,
|
||||
view: toolEventViewSchema.optional(),
|
||||
}) satisfies z.ZodType<Wire<HistoryEntry>>
|
||||
}) as unknown as z.ZodType<Wire<HistoryEntry>>
|
||||
|
||||
/**
|
||||
* Projection baseline passthrough: `values` stays a wide record — each value
|
||||
@@ -210,11 +211,11 @@ export const sessionProjectionsBlockSchema = z.object({
|
||||
}) as unknown as z.ZodType<SessionProjectionsBlock>
|
||||
|
||||
/** session.history response value (projections rides the tail page only). */
|
||||
export const sessionHistoryValueSchema = z.object({
|
||||
export const sessionHistoryValueSchema: z.ZodType<Wire<ResponseValue<'session.history'>>> = z.object({
|
||||
events: z.array(historyEntrySchema),
|
||||
hasMore: z.boolean(),
|
||||
projections: sessionProjectionsBlockSchema.optional(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'session.history'>>>
|
||||
})
|
||||
|
||||
/** session.models request payload. */
|
||||
export const sessionModelsRequestSchema = z.object({
|
||||
@@ -267,6 +268,7 @@ export const sessionUpdateQueueRequestSchema = z.object({
|
||||
action: z.discriminatedUnion('kind', [
|
||||
z.object({ kind: z.literal('edit'), content: z.array(contentBlockSchema) }),
|
||||
z.object({ kind: z.literal('remove') }),
|
||||
z.object({ kind: z.literal('steer') }),
|
||||
]),
|
||||
}) as unknown as z.ZodType<RequestPayload<'session.updateQueue'>>
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ export interface SessionModels {
|
||||
export type QueueAction =
|
||||
| { kind: 'edit'; content: ContentBlock[] }
|
||||
| { kind: 'remove' }
|
||||
| { kind: 'steer' }
|
||||
|
||||
/** Session list entry (v1 builds no index: list does readdir+stat). */
|
||||
export interface SessionSummary {
|
||||
@@ -151,6 +152,8 @@ export interface SessionSummary {
|
||||
blank: boolean
|
||||
/** fork/spawn lineage (session.header.parentSession passthrough); absent for root sessions. */
|
||||
parentSessionId?: SessionId
|
||||
/** Coarse durable origin used by navigation surfaces; never proves resumability. */
|
||||
origin?: 'subagent'
|
||||
/** Session working directory (header.cwd passthrough); absent when unrecorded. */
|
||||
cwd?: string
|
||||
/**
|
||||
@@ -215,17 +218,22 @@ export interface SessionsApi {
|
||||
* the client needs a fresh baseline already pulls the tail page, and
|
||||
* loadOlder (the only beforeSeq path) is the only path that never needs one.
|
||||
* A deployment without the registry serves histories without the block.
|
||||
* Reading history uses an attached Session or persistence inspection and
|
||||
* never resumes or publishes an Agent.
|
||||
*/
|
||||
history(request: RpcRequest<{ sessionId: SessionId; beforeSeq?: number; maxMessages?: number }>):
|
||||
Promise<RpcResponse<{ events: HistoryEntry[]; hasMore: boolean; projections?: SessionProjectionsBlock }>>
|
||||
|
||||
/** Reads a fresh advisory model directory for this session. Provider lookups run independently. */
|
||||
/**
|
||||
* Reads a fresh advisory model directory for an ordinary session. Provider
|
||||
* lookups run independently; subagents reject with `agent-busy`.
|
||||
*/
|
||||
models(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<SessionModels>>
|
||||
|
||||
/**
|
||||
* Selects the complete target for this session. Exact model metadata
|
||||
* validates an optional reasoning effort, while catalog membership remains
|
||||
* advisory.
|
||||
* advisory. Session-backed subagents reject with `agent-busy`.
|
||||
*/
|
||||
selectModel(request: RpcRequest<{
|
||||
sessionId: SessionId
|
||||
@@ -241,6 +249,7 @@ export interface SessionsApi {
|
||||
* normalized accepted title and the title event's seq return so the caller
|
||||
* can settle its projection cell without waiting for the push frame. A
|
||||
* title that normalizes to empty fails with `title-invalid`.
|
||||
* Session-backed subagents reject with `agent-busy`.
|
||||
*/
|
||||
rename(request: RpcRequest<{ sessionId: SessionId; title: string }>):
|
||||
Promise<RpcResponse<{ title: string; seq: number }>>
|
||||
@@ -261,23 +270,31 @@ export interface SessionsApi {
|
||||
* falls back to the source's last completed turn. An in-log anchor whose
|
||||
* turn is still open fails with `fork-unavailable` instead of clipping to
|
||||
* an earlier turn. The child inherits the source cwd, latest logged model
|
||||
* target, workspace attachment, and `parentSessionId` lineage; the seed
|
||||
* prefix carries the source title.
|
||||
* target and `parentSessionId` lineage; the seed prefix carries the source
|
||||
* title. Reading the source uses attached state or persistence inspection
|
||||
* without acquiring an Agent. Workspace attachment follows the source
|
||||
* directly, or the nearest workspace-owning ancestor when the source is a
|
||||
* subagent.
|
||||
*/
|
||||
fork(request: RpcRequest<{ sessionId: SessionId; atSeq?: number }>):
|
||||
Promise<RpcResponse<{ sessionId: SessionId }>>
|
||||
|
||||
/** Sends a message. content is core's ContentBlock[] verbatim; mode maps 1:1 — queue→send, steer→steer. */
|
||||
/** Sends a message to an ordinary session Agent. Session-backed subagents reject with `agent-busy` and use `subagent.prompt`. */
|
||||
prompt(request: RpcRequest<{ sessionId: SessionId; mode: 'queue' | 'steer'; content: ContentBlock[] }>):
|
||||
Promise<RpcResponse<{ accepted: true; command?: { kind: 'success'; text?: string } }>>
|
||||
|
||||
/**
|
||||
* Edits or removes one pending queued occurrence.
|
||||
* Edits, removes, or strictly steers one pending queued occurrence on an ordinary session.
|
||||
* Session-backed subagents reject with `agent-busy`.
|
||||
*/
|
||||
updateQueue(request: RpcRequest<{ sessionId: SessionId; itemId: InboxItemId; action: QueueAction }>):
|
||||
Promise<RpcResponse<{ accepted: true }>>
|
||||
|
||||
/** Stops: clears both FIFOs + aborts the current step (1:1 with agent.cancel). */
|
||||
/**
|
||||
* Stops an ordinary session's active turn, preserving pending inbox work
|
||||
* that resumes in FIFO order after cancellation settles. Session-backed
|
||||
* subagents reject with `agent-busy`.
|
||||
*/
|
||||
cancel(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<{ accepted: true }>>
|
||||
|
||||
}
|
||||
|
||||
77
packages/host/apiproxy/src/api/subagents.schema.ts
Normal file
77
packages/host/apiproxy/src/api/subagents.schema.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/** Zod schemas for the browser-safe subagent domain. */
|
||||
|
||||
import { z } from 'zod'
|
||||
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
|
||||
import type { Wire } from './rpc.schema.ts'
|
||||
import {
|
||||
contentBlockSchema, historyEntrySchema, sessionIdSchema, sessionProjectionsBlockSchema,
|
||||
} from './sessions.schema.ts'
|
||||
import type { SubagentListEntry } from './subagents.ts'
|
||||
|
||||
/** Healthy and diagnostic durable catalog rows. */
|
||||
export const subagentListEntrySchema = z.union([
|
||||
z.object({
|
||||
kind: z.literal('child'),
|
||||
id: sessionIdSchema,
|
||||
mode: z.literal('one-shot'),
|
||||
activity: z.union([z.literal('running'), z.literal('inactive')]),
|
||||
hasChildren: z.boolean(),
|
||||
label: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
kind: z.literal('child'),
|
||||
id: sessionIdSchema,
|
||||
mode: z.literal('continuable'),
|
||||
activity: z.union([z.literal('running'), z.literal('inactive')]),
|
||||
hasChildren: z.boolean(),
|
||||
label: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
kind: z.literal('diagnostic'),
|
||||
id: sessionIdSchema,
|
||||
reason: z.union([z.literal('corrupt'), z.literal('unsupported'), z.literal('unavailable')]),
|
||||
}),
|
||||
]) satisfies z.ZodType<Wire<SubagentListEntry>>
|
||||
|
||||
/** subagent.list request payload. */
|
||||
export const subagentListRequestSchema = z.object({
|
||||
parentSessionId: sessionIdSchema,
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'subagent.list'>>>
|
||||
|
||||
/** subagent.list response value. */
|
||||
export const subagentListValueSchema = z.object({
|
||||
entries: z.array(subagentListEntrySchema),
|
||||
parentAvailable: z.boolean(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'subagent.list'>>>
|
||||
|
||||
/** subagent.history request payload. */
|
||||
export const subagentHistoryRequestSchema = z.object({
|
||||
parentSessionId: sessionIdSchema,
|
||||
childSessionId: sessionIdSchema,
|
||||
mode: z.union([z.literal('one-shot'), z.literal('continuable')]),
|
||||
beforeSeq: z.number().int().nonnegative().optional(),
|
||||
maxMessages: z.number().int().positive().optional(),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'subagent.history'>>>
|
||||
|
||||
/** subagent.history response value. */
|
||||
export const subagentHistoryValueSchema = z.object({
|
||||
events: z.array(historyEntrySchema),
|
||||
hasMore: z.boolean(),
|
||||
projections: sessionProjectionsBlockSchema.optional(),
|
||||
}) as unknown as z.ZodType<Wire<ResponseValue<'subagent.history'>>>
|
||||
|
||||
/** subagent.prompt request payload. */
|
||||
export const subagentPromptRequestSchema = z.object({
|
||||
parentSessionId: sessionIdSchema,
|
||||
childSessionId: sessionIdSchema,
|
||||
mode: z.literal('continuable'),
|
||||
content: z.array(contentBlockSchema),
|
||||
}) as unknown as z.ZodType<RequestPayload<'subagent.prompt'>>
|
||||
|
||||
const messageIdSchema = z.string() as unknown as z.ZodType<MessageId>
|
||||
|
||||
/** subagent.prompt response value. */
|
||||
export const subagentPromptValueSchema = z.object({
|
||||
messageId: messageIdSchema,
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'subagent.prompt'>>>
|
||||
96
packages/host/apiproxy/src/api/subagents.ts
Normal file
96
packages/host/apiproxy/src/api/subagents.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Browser-safe subagent domain contract. Persisted transcript reads never
|
||||
* activate an Agent, while continuable prompts route through the exact live
|
||||
* direct parent into the child's Agent inbox.
|
||||
*/
|
||||
|
||||
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
|
||||
import type { ContentBlock } from '@deepseek-ai/dsh-llm/types'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
import type { RpcRequest, RpcResponse } from './rpc.ts'
|
||||
import type { HistoryEntry, SessionProjectionsBlock } from './sessions.ts'
|
||||
|
||||
/** Complete durable direct-child catalog row. */
|
||||
export type SubagentListEntry =
|
||||
| {
|
||||
kind: 'child'
|
||||
id: SessionId
|
||||
/** Whether the child Agent driver is running at the Host sampling boundary. */
|
||||
activity: 'running' | 'inactive'
|
||||
/** Whether a direct descendant has durable `origin: 'subagent'`. */
|
||||
hasChildren: boolean
|
||||
} & (
|
||||
| {
|
||||
mode: 'one-shot'
|
||||
label?: string
|
||||
}
|
||||
| {
|
||||
mode: 'continuable'
|
||||
label: string
|
||||
}
|
||||
)
|
||||
| {
|
||||
kind: 'diagnostic'
|
||||
id: SessionId
|
||||
reason: 'corrupt' | 'unsupported' | 'unavailable'
|
||||
}
|
||||
|
||||
/** Inbox identity returned once the continuation accepts one human message. */
|
||||
export interface SubagentPromptReceipt {
|
||||
messageId: MessageId
|
||||
}
|
||||
|
||||
/** Durable parent/child address that selects subagent transport in the client. */
|
||||
export type SubagentAddress =
|
||||
& {
|
||||
parentSessionId: SessionId
|
||||
childSessionId: SessionId
|
||||
}
|
||||
& (
|
||||
| { mode: 'one-shot' }
|
||||
| { mode: 'continuable' }
|
||||
)
|
||||
|
||||
/** Complete direct-child catalog plus the delivery-time parent availability hint. */
|
||||
export interface SubagentCatalog {
|
||||
entries: SubagentListEntry[]
|
||||
parentAvailable: boolean
|
||||
}
|
||||
|
||||
/** Subagent-domain unary methods. */
|
||||
export interface SubagentsApi {
|
||||
/**
|
||||
* Lists direct session-backed children without loading either side. Parent
|
||||
* availability is a hint; continuable prompt performs the authoritative
|
||||
* check.
|
||||
*/
|
||||
list(
|
||||
request: RpcRequest<{ parentSessionId: SessionId }>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<RpcResponse<SubagentCatalog>>
|
||||
|
||||
/**
|
||||
* Reads one healthy catalog child's persisted raw log with ordinary
|
||||
* message-aligned pagination and render intents, without Agent activation.
|
||||
*/
|
||||
history(
|
||||
request: RpcRequest<SubagentAddress & { beforeSeq?: number; maxMessages?: number }>,
|
||||
signal?: AbortSignal,
|
||||
): Promise<RpcResponse<{
|
||||
events: HistoryEntry[]
|
||||
hasMore: boolean
|
||||
projections?: SessionProjectionsBlock
|
||||
}>>
|
||||
|
||||
/**
|
||||
* Delivers human content to a continuable child through the exact live
|
||||
* parent's continuation owner. Success identifies the message accepted by
|
||||
* the child's FIFO inbox; later execution is independent of this request.
|
||||
*/
|
||||
prompt(
|
||||
request: RpcRequest<
|
||||
Extract<SubagentAddress, { mode: 'continuable' }> & { content: ContentBlock[] }
|
||||
>,
|
||||
signal: AbortSignal,
|
||||
): Promise<RpcResponse<SubagentPromptReceipt>>
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export interface WorkspaceView {
|
||||
workspaceId: WorkspaceId
|
||||
/** Canonical directory path (host-side realpath canon). */
|
||||
path: string
|
||||
/** Unique display title (defaults to the path basename at create). */
|
||||
/** Display title (defaults to the path basename at create). */
|
||||
title: string
|
||||
/**
|
||||
* Sessions accounted under this workspace, in manually owned order
|
||||
@@ -53,8 +53,8 @@ export interface WorkspaceApi {
|
||||
* root before registering. Either spelling resolving to a directory already
|
||||
* owned by a workspace returns that workspace (`created: false`) for the
|
||||
* existing-folder spelling. Create-by-name rejects an existing title with
|
||||
* `workspace-name-conflict`; a new path whose basename duplicates another
|
||||
* Workspace title is rejected by the registry with the same code.
|
||||
* `workspace-name-conflict`; path adoption allows distinct canonical paths
|
||||
* whose basenames produce the same display title.
|
||||
* A new name-created workspace uses `name` as both directory name and title;
|
||||
* a path-created workspace uses the registry's basename title default.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user