feat(web): rewrite subagent conversations for FIFO activation

This commit is contained in:
Dudu-0223
2026-07-30 23:33:07 +08:00
committed by Tianyi Cui
parent f0ab04273d
commit 8a518e353b
52 changed files with 829 additions and 420 deletions

View File

@@ -65,7 +65,7 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
}) }),
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-not-delivered'), 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>

View File

@@ -80,7 +80,7 @@ export interface RpcErrorDetailsMap {
}
'subagent-not-resumable': { childSessionId: SessionId }
'subagent-unauthorized': { childSessionId: SessionId }
'subagent-not-delivered': { childSessionId: SessionId }
'subagent-delivery-unavailable': { childSessionId: SessionId }
'internal': {}
}

View File

@@ -1,19 +1,29 @@
/** 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 } from './sessions.schema.ts'
import type { MessageId } from '@deepseek-ai/dsh-llm/brand'
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.discriminatedUnion('kind', [
export const subagentListEntrySchema = z.union([
z.object({
kind: z.literal('child'),
id: sessionIdSchema,
label: z.string(),
mode: z.literal('one-shot'),
activity: z.union([z.literal('running'), z.literal('inactive')]),
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')]),
label: z.string(),
}),
z.object({
kind: z.literal('diagnostic'),
@@ -37,6 +47,7 @@ export const subagentListValueSchema = z.object({
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'>>>
@@ -45,12 +56,14 @@ export const subagentHistoryRequestSchema = z.object({
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'>>

View File

@@ -1,23 +1,31 @@
/**
* Browser-safe subagent domain contract. Persisted transcript reads never
* activate an Agent, while prompts route through the direct parent's
* Activation-backed continuation owner.
* 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 } from './sessions.ts'
import type { HistoryEntry, SessionProjectionsBlock } from './sessions.ts'
/** Complete durable direct-child catalog row. */
export type SubagentListEntry =
| {
kind: 'child'
id: SessionId
label: string
activity: 'running' | 'inactive'
}
} & (
| {
mode: 'one-shot'
label?: string
}
| {
mode: 'continuable'
label: string
}
)
| {
kind: 'diagnostic'
id: SessionId
@@ -30,10 +38,15 @@ export interface SubagentPromptReceipt {
}
/** Durable parent/child address that selects subagent transport in the client. */
export interface SubagentAddress {
parentSessionId: SessionId
childSessionId: SessionId
}
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 {
@@ -44,8 +57,9 @@ export interface SubagentCatalog {
/** Subagent-domain unary methods. */
export interface SubagentsApi {
/**
* Lists direct continuable children without loading either side. Parent
* availability is a hint; prompt performs the authoritative check.
* 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 }>,
@@ -59,14 +73,21 @@ export interface SubagentsApi {
history(
request: RpcRequest<SubagentAddress & { beforeSeq?: number; maxMessages?: number }>,
signal?: AbortSignal,
): Promise<RpcResponse<{ events: HistoryEntry[]; hasMore: boolean }>>
): Promise<RpcResponse<{
events: HistoryEntry[]
hasMore: boolean
projections?: SessionProjectionsBlock
}>>
/**
* Delivers human content through the exact live parent's continuation
* owner. Success identifies the accepted inbox message.
* 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<SubagentAddress & { content: ContentBlock[] }>,
signal?: AbortSignal,
request: RpcRequest<
Extract<SubagentAddress, { mode: 'continuable' }> & { content: ContentBlock[] }
>,
signal: AbortSignal,
): Promise<RpcResponse<SubagentPromptReceipt>>
}