Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker

# Conflicts:
#	packages/host/apiproxy/package.json
#	pnpm-lock.yaml
This commit is contained in:
creatixchu
2026-07-29 06:43:09 +08:00
130 changed files with 3705 additions and 429 deletions
@@ -0,0 +1,79 @@
/**
* goals domain zod schemas. Mutation-only shapes: every value schema is a
* `{ ref }` acknowledgement (clear: `{ cleared }`) — the current goal state
* travels exclusively on the 'goal' session projection.
*/
import { z } from 'zod'
import type { Wire } from './rpc.schema.ts'
import type { GoalRef, RequestPayload, ResponseValue } from './index.ts'
/** GoalRef schema. */
export const goalRefSchema = z.object({
id: z.string(),
revision: z.number().int().positive(),
}) as unknown as z.ZodType<Wire<GoalRef>>
/** Shared `{ ref }` acknowledgement value of every non-clear mutation. */
const goalRefValueSchema = z.object({ ref: goalRefSchema })
/** goal.create request payload. */
export const goalCreateRequestSchema = z.object({
sessionId: z.string(),
objective: z.string().min(1),
maxGoalRounds: z.number().int().positive().optional(),
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.create'>>>
/** goal.create response value. */
export const goalCreateValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.create'>>>
/** goal.edit request payload. */
export const goalEditRequestSchema = z.object({
sessionId: z.string(),
ref: goalRefSchema,
objective: z.string().min(1).optional(),
maxGoalRounds: z.number().int().positive().optional(),
}).refine(value => value.objective !== undefined || value.maxGoalRounds !== undefined, {
message: 'goal.edit requires objective or maxGoalRounds',
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.edit'>>>
/** goal.edit response value. */
export const goalEditValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.edit'>>>
/** goal.pause request payload. */
export const goalPauseRequestSchema = z.object({
sessionId: z.string(),
ref: goalRefSchema,
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.pause'>>>
/** goal.pause response value. */
export const goalPauseValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.pause'>>>
/** goal.resume request payload. */
export const goalResumeRequestSchema = z.object({
sessionId: z.string(),
ref: goalRefSchema,
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.resume'>>>
/** goal.resume response value. */
export const goalResumeValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.resume'>>>
/** goal.complete request payload. */
export const goalCompleteRequestSchema = z.object({
sessionId: z.string(),
ref: goalRefSchema,
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.complete'>>>
/** goal.complete response value. */
export const goalCompleteValueSchema = goalRefValueSchema as unknown as z.ZodType<Wire<ResponseValue<'goal.complete'>>>
/** goal.clear request payload. */
export const goalClearRequestSchema = z.object({
sessionId: z.string(),
ref: goalRefSchema,
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.clear'>>>
/** goal.clear response value. */
export const goalClearValueSchema = z.object({
cleared: z.literal(true),
}) as unknown as z.ZodType<Wire<ResponseValue<'goal.clear'>>>
+50
View File
@@ -0,0 +1,50 @@
/**
* goals domain contract. Method signatures are the source of truth:
* unary methods take the RpcRequest<P> narrow form and the impl echoes rpcId.
*
* Mutations only: the read side is the 'goal' session projection (history
* tail-page projections block + session/projection frames), so there is no
* goal.get and no wire goal view — responses acknowledge with the new CAS
* ref and never feed client state (the committed goal/change event reaches
* every client through the mux stream carrying the same whole value).
*/
import type { Branded } from '@deepseek-ai/dsh-brand'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import type { RpcRequest, RpcResponse } from './rpc.ts'
/** Identifies one goal across its durable revisions. */
export type GoalId = Branded<'GoalId'>
/** Compare-and-set identity for one exact goal revision. */
export interface GoalRef {
readonly id: GoalId
readonly revision: number
}
/** Goal-domain unary methods (every mutation resolves the session's agent and applies one CAS-guarded verb). */
export interface GoalsApi {
/** Create and arm a goal. */
create(request: RpcRequest<{ sessionId: SessionId; objective: string; maxGoalRounds?: number }>):
Promise<RpcResponse<{ ref: GoalRef }>>
/** Edit objective and/or round cap without changing phase. */
edit(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef; objective?: string; maxGoalRounds?: number }>):
Promise<RpcResponse<{ ref: GoalRef }>>
/** Pause an active goal and disarm automatic continuation. */
pause(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ ref: GoalRef }>>
/** Resume and arm a stopped goal. */
resume(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ ref: GoalRef }>>
/** Mark a current non-complete goal complete and disarm it. */
complete(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ ref: GoalRef }>>
/** Clear the current goal while retaining a durable tombstone and history. */
clear(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ cleared: true }>>
}
+3
View File
@@ -10,6 +10,7 @@ import type { WorkspaceApi } from './workspace.ts'
import type { CommandsApi } from './commands.ts'
import type { SkillsApi } from './skills.ts'
import type { EventsApi } from './events.ts'
import type { GoalsApi } from './goals.ts'
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. */
@@ -20,6 +21,7 @@ export interface ApiProxy {
commands: CommandsApi
skills: SkillsApi
events: EventsApi
goals: GoalsApi
/** Response entry for server-requests (client-response, echoing their rpcId); not a domain method (four-quadrant model). */
respond(message: ClientResponse): Promise<RpcReceipt>
}
@@ -34,6 +36,7 @@ export type { WorkspaceApi, WorkspaceId, WorkspaceView } from './workspace.ts'
export type { CommandsApi, CommandDescriptor } from './commands.ts'
export type { SkillsApi, SkillEntry } from './skills.ts'
export type { EventsApi, MuxFrame, HostFrame, ToolCallView, ToolEventView, ToolResultView } from './events.ts'
export type { GoalsApi, GoalId, GoalRef } from './goals.ts'
export type { ApprovalResponsePayload } from './approvals.ts'
export type { QuestionResponsePayload } from './questions.ts'
@@ -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 { GoalsApi } from './goals.ts'
import type { RpcResponse } from './rpc.ts'
/**
@@ -37,6 +38,12 @@ export interface RpcMethodMap {
'command.list': CommandsApi['list']
'command.execute': CommandsApi['execute']
'skill.list': SkillsApi['list']
'goal.create': GoalsApi['create']
'goal.edit': GoalsApi['edit']
'goal.pause': GoalsApi['pause']
'goal.resume': GoalsApi['resume']
'goal.complete': GoalsApi['complete']
'goal.clear': GoalsApi['clear']
}
/** Business request payload of method K (reaches through the RpcRequest narrow form to payload). */
@@ -47,6 +47,8 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
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-busy'), message: z.string(), details: z.object({ reason: 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('internal'), message: z.string(), details: z.object({}) }),
]) as unknown as z.ZodType<RpcError>
+4
View File
@@ -44,6 +44,10 @@ export interface RpcErrorDetailsMap {
'directory-create-failed': { path: string }
'directory-picker-unavailable': { capability: string }
'agent-busy': { reason: string }
/** 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. */
'unknown-command': {}
'internal': {}
}
@@ -193,9 +193,13 @@ export const sessionPromptRequestSchema = z.object({
content: z.array(contentBlockSchema),
}) as unknown as z.ZodType<RequestPayload<'session.prompt'>>
/** session.prompt response value. */
/** session.prompt response value (the command slot appears only when the prompt dispatched a slash command). */
export const sessionPromptValueSchema = z.object({
accepted: z.literal(true),
command: z.object({
kind: z.literal('success'),
text: z.string().optional(),
}).optional(),
}) satisfies z.ZodType<Wire<ResponseValue<'session.prompt'>>>
/** session.cancel request payload. */
+16 -7
View File
@@ -132,11 +132,13 @@ export interface SessionSummary {
/** Status of the attached agent; always false for cold (unattached) sessions. */
running: boolean
/**
* Derived emptiness bit: true while the session log holds zero events (no
* user message yet). Clients hide blank sessions from lists and reuse them
* for New Session on the same workspace. Always false for cold sessions —
* lazy persistence keeps a never-appended session out of the store, so a
* listed cold session necessarily has events.
* Derived conversation-not-started bit: true while no turn has run (no
* prompt was accepted yet). Standalone plugin events — command lifecycle
* records, plan/mode, titles, goals — do not open a turn and therefore do
* not clear it. Clients hide blank sessions from lists and reuse them for
* New Session on the same workspace. Always false for cold sessions —
* lazy persistence keeps a never-appended session out of the store, and a
* listed cold session's log holds its turns.
*/
blank: boolean
/** fork/spawn lineage (session.header.parentSession passthrough); absent for root sessions. */
@@ -206,9 +208,16 @@ export interface SessionsApi {
}>):
Promise<RpcResponse<{ selected: ModelTarget }>>
/** Sends a message. content is core's ContentBlock[] verbatim; mode maps 1:1 — queue→send, steer→steer. */
/**
* Sends a message. content is core's ContentBlock[] verbatim; mode maps 1:1 — queue→send, steer→steer.
* A prompt whose content is exactly one text block starting with '/' is a slash command: the host
* executes it through the command registry (mode-agnostic) and it is never sent to the model. A
* successful command returns ok with the command slot (its success text, when the command produced
* one — carried for future rendering; the state change is the feedback). A usage/state error is an
* RPC error with code command-error; an unrecognized name is an RPC error with code unknown-command.
*/
prompt(request: RpcRequest<{ sessionId: SessionId; mode: 'queue' | 'steer'; content: ContentBlock[] }>):
Promise<RpcResponse<{ accepted: true }>>
Promise<RpcResponse<{ accepted: true; command?: { kind: 'success'; text?: string } }>>
/** Stops: clears both FIFOs + aborts the current step (1:1 with agent.cancel). */
cancel(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<{ accepted: true }>>