Merge remote-tracking branch 'origin/master' into codex/basic-session-search

# Conflicts:
#	apps/web/tests/snapshots/question-composer/answered.expected.md
#	packages/client/connection/tests/fake-api.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/tests/fake-api.ts
#	packages/client/ui-workspace/README.i18n.yaml
#	packages/client/ui-workspace/README.md
#	packages/client/ui-workspace/README.zh.md
#	packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx
#	packages/client/ui-workspace/tests/apply.spec.ts
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.md
#	packages/host/apiproxy/README.zh.md
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/index.ts
#	packages/host/apiproxy/src/api/sessions.schema.ts
#	packages/host/apiproxy/src/fetch/client.ts
#	packages/host/apiproxy/src/fetch/handler.ts
#	packages/host/apiproxy/tests/fetch-carrier.spec.ts
#	packages/host/apiproxy/tests/rpc-schemas.spec.ts
#	packages/session-query/session-query/tests/search-helpers.spec.ts
This commit is contained in:
Hypatia May
2026-07-28 09:27:28 +08:00
1288 changed files with 40508 additions and 19164 deletions

View File

@@ -63,14 +63,16 @@ export type MuxFrame =
| { type: 'question/requested'; sessionId: SessionId; questions: AskUserQuestionItem[] }
| { type: 'question/resolved'; sessionId: SessionId; questionRpcId: RpcId; outcome: 'answered' | 'cancelled' }
/**
* A message entered the addressed agent's inbox (`agent/queued` passthrough:
* a queued message is not model-visible, so there is no session event to
* ride — this transient frame is the only wire signal). On stream open the
* A message entered the addressed agent's inbox. A queued message is not
* model-visible, so there is no session event to carry it; this transient
* frame is the only wire signal. On stream open the
* host replays the current queue snapshot for every attached session (same
* refresh-recovery baseline as pending questions); queue clearing on cancel
* has no dedicated frame — clients fold it from the status flip.
* source carries the prompt's rpcId when the message came over this wire
* (the client's provisional-echo reconciliation key).
* `steering` is the host's acceptance-time queue classification and remains
* authoritative in reconnect snapshots. `source` carries the prompt's rpcId
* when the message came over this wire (the client's provisional-echo
* reconciliation key).
*/
| { type: 'session/queued'; sessionId: SessionId; content: ContentBlock[]; source: MessageSource; steering: boolean }
| { type: 'stream/error'; error: RpcError }

View File

@@ -17,3 +17,11 @@ export const hostDescribeValueSchema = z.object({
model: z.string().optional(),
attachedSessions: z.number().int().nonnegative(),
}) satisfies z.ZodType<Wire<ResponseValue<'host.describe'>>>
/** host.pickDirectory request payload (empty object literal). */
export const hostPickDirectoryRequestSchema = z.object({}) satisfies z.ZodType<Wire<RequestPayload<'host.pickDirectory'>>>
/** host.pickDirectory response value; null means the user cancelled. */
export const hostPickDirectoryValueSchema = z.object({
path: z.string().nullable(),
}) satisfies z.ZodType<Wire<ResponseValue<'host.pickDirectory'>>>

View File

@@ -22,4 +22,10 @@ export interface HostApi {
model?: string
attachedSessions: number
}>>
/** Open the operating system's single-directory picker; cancellation returns null. */
pickDirectory(
request: RpcRequest<{}>,
signal: AbortSignal,
): Promise<RpcResponse<{ path: string | null }>>
}

View File

@@ -25,7 +25,10 @@ export interface ApiProxy {
}
// ---- Domain interfaces and payload entities ----
export type { HistoryEntry, SessionSearchItem, SessionsApi, SessionSummary } from './sessions.ts'
export type {
HistoryEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
ModelReasoningEffort, ModelTarget, SessionModels, SessionSearchItem, SessionsApi, SessionSummary,
} from './sessions.ts'
export type { HostApi } from './host.ts'
export type { WorkspaceApi, WorkspaceId, WorkspaceView } from './workspace.ts'
export type { CommandsApi, CommandDescriptor, CommandExecuteResult } from './commands.ts'

View File

@@ -21,9 +21,12 @@ export interface RpcMethodMap {
'session.search': SessionsApi['search']
'session.create': SessionsApi['create']
'session.history': SessionsApi['history']
'session.models': SessionsApi['models']
'session.selectModel': SessionsApi['selectModel']
'session.prompt': SessionsApi['prompt']
'session.cancel': SessionsApi['cancel']
'host.describe': HostApi['describe']
'host.pickDirectory': HostApi['pickDirectory']
'workspace.list': WorkspaceApi['list']
'workspace.create': WorkspaceApi['create']
'workspace.rename': WorkspaceApi['rename']

View File

@@ -35,6 +35,7 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
z.object({ code: z.literal('bad-request'), message: z.string(), details: z.object({ issues: z.array(z.custom<ZodIssue>()) }) }),
z.object({ code: z.literal('cancelled'), message: z.string(), details: z.object({}) }),
z.object({ code: z.literal('session-not-found'), message: z.string(), details: z.object({ sessionId: z.string() }) }),
z.object({ code: z.literal('model-unavailable'), message: z.string(), details: z.object({ provider: z.string(), model: z.string() }) }),
z.object({ code: z.literal('session-conflict'), message: z.string(), details: z.object({ sessionId: z.string(), requestedCwd: z.string(), existingCwd: z.string().optional() }) }),
z.object({ code: z.literal('workspace-attach-failed'), message: z.string(), details: z.object({ sessionId: z.string(), workspaceId: z.string() }) }),
z.object({ code: z.literal('workspace-not-found'), message: z.string(), details: z.object({ workspaceId: z.string() }) }),

View File

@@ -32,6 +32,7 @@ export interface RpcErrorDetailsMap {
'bad-request': { issues: ZodIssue[] }
'cancelled': {}
'session-not-found': { sessionId: SessionId }
'model-unavailable': { provider: string; model: string }
'session-conflict': { sessionId: SessionId; requestedCwd: string; existingCwd?: string }
'workspace-attach-failed': { sessionId: SessionId; workspaceId: string }
'workspace-not-found': { workspaceId: string }

View File

@@ -9,7 +9,10 @@ import { z } from 'zod'
import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session/types'
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
import type { Wire } from './rpc.schema.ts'
import type { HistoryEntry, SessionSearchItem, SessionSummary } from './sessions.ts'
import type {
HistoryEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning,
ModelReasoningEffort, ModelTarget, SessionSearchItem, SessionSummary,
} from './sessions.ts'
import type { ToolEventView } from './events.ts'
import type { WorkspaceId } from './workspace.ts'
import {
@@ -108,6 +111,49 @@ export const sessionHistoryRequestSchema = z.object({
maxMessages: z.number().int().positive().optional(),
}) satisfies z.ZodType<Wire<RequestPayload<'session.history'>>>
/** Complete provider/model target. */
export const modelTargetSchema = z.object({
provider: z.string().min(1),
model: z.string().min(1),
reasoningEffort: z.string().min(1).optional(),
}) satisfies z.ZodType<Wire<ModelTarget>>
/** One adapter-owned reasoning effort. */
export const modelReasoningEffortSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
description: z.string().optional(),
}) satisfies z.ZodType<Wire<ModelReasoningEffort>>
/** Exact-model reasoning metadata. */
export const modelReasoningSchema = z.object({
efforts: z.array(modelReasoningEffortSchema).min(1),
defaultEffort: z.string().min(1).optional(),
}) satisfies z.ZodType<Wire<ModelReasoning>>
/** One advisory model entry inside a provider group. */
export const modelCatalogModelSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
description: z.string().optional(),
unlisted: z.literal(true).optional(),
reasoning: modelReasoningSchema.optional(),
}) satisfies z.ZodType<Wire<ModelCatalogModel>>
/** One successfully loaded provider group. */
export const modelProviderGroupSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
models: z.array(modelCatalogModelSchema),
}) satisfies z.ZodType<Wire<ModelProviderGroup>>
/** One provider-local catalog failure. */
export const modelCatalogFailureSchema = z.object({
id: z.string().min(1),
name: z.string().min(1),
message: z.string(),
}) satisfies z.ZodType<Wire<ModelCatalogFailure>>
/**
* ToolEventView passthrough: lock only the `for` discriminant and the presence
* of a card-tagged `view` object. The view interior is a host-computed product
@@ -138,6 +184,31 @@ export const sessionHistoryValueSchema = z.object({
todos: z.array(todoItemSchema).optional(),
}) satisfies z.ZodType<Wire<ResponseValue<'session.history'>>>
/** session.models request payload. */
export const sessionModelsRequestSchema = z.object({
sessionId: sessionIdSchema,
}) satisfies z.ZodType<Wire<RequestPayload<'session.models'>>>
/** session.models response value. */
export const sessionModelsValueSchema = z.object({
current: modelTargetSchema,
groups: z.array(modelProviderGroupSchema),
failures: z.array(modelCatalogFailureSchema),
}) satisfies z.ZodType<Wire<ResponseValue<'session.models'>>>
/** session.selectModel request payload. */
export const sessionSelectModelRequestSchema = z.object({
sessionId: sessionIdSchema,
provider: z.string().min(1),
model: z.string().min(1),
reasoningEffort: z.string().min(1).optional(),
}) satisfies z.ZodType<Wire<RequestPayload<'session.selectModel'>>>
/** session.selectModel response value. */
export const sessionSelectModelValueSchema = z.object({
selected: modelTargetSchema,
}) satisfies z.ZodType<Wire<ResponseValue<'session.selectModel'>>>
/** ContentBlock passthrough: core is merge-extensible — the type discriminant envelope is strict, the rest stays wide. */
export const contentBlockSchema = z.looseObject({ type: z.string() })

View File

@@ -32,6 +32,78 @@ export interface HistoryEntry {
view?: ToolEventView
}
/** Complete model target selected for one session. */
export interface ModelTarget {
/** Registered provider route. */
provider: string
/** Provider-owned model id. */
model: string
/** Adapter-owned reasoning effort; absence preserves adapter/provider default behavior. */
reasoningEffort?: string
}
/** One adapter-owned reasoning effort displayed for an exact model route. */
export interface ModelReasoningEffort {
/** Opaque value submitted back to the owning adapter. */
id: string
/** Adapter-supplied display name. */
name: string
/** Optional adapter-supplied description. */
description?: string
}
/** Selectable reasoning metadata for one exact model route. */
export interface ModelReasoning {
/** Efforts in adapter-preferred display order. */
efforts: ModelReasoningEffort[]
/** Adapter-configured default; absence preserves the provider default. */
defaultEffort?: string
}
/** One model displayed inside its provider group. */
export interface ModelCatalogModel {
/** Provider-owned model id. */
id: string
/** Provider-supplied display name. */
name: string
/** Optional provider-supplied description. */
description?: string
/** The current model was inserted because the advisory catalog omitted it. */
unlisted?: true
/** Exact-route reasoning metadata when the adapter exposes it. */
reasoning?: ModelReasoning
}
/** One provider and the models it advertised successfully. */
export interface ModelProviderGroup {
/** Provider route id used for requests. */
id: string
/** Provider display name. */
name: string
/** Models in provider-preferred order. */
models: ModelCatalogModel[]
}
/** A provider whose asynchronous catalog lookup failed. */
export interface ModelCatalogFailure {
/** Provider route id. */
id: string
/** Provider display name. */
name: string
/** Lookup failure diagnostic. */
message: string
}
/** Detached model-directory snapshot for one session. */
export interface SessionModels {
/** Target selected for the session's next assembled step. */
current: ModelTarget
/** Successfully loaded provider groups. */
groups: ModelProviderGroup[]
/** Provider-local failures; successful groups remain usable. */
failures: ModelCatalogFailure[]
}
/** Session list entry (v1 builds no index: list does readdir+stat). */
export interface SessionSummary {
sessionId: SessionId
@@ -102,6 +174,22 @@ export interface SessionsApi {
history(request: RpcRequest<{ sessionId: SessionId; beforeSeq?: number; maxMessages?: number }>):
Promise<RpcResponse<{ events: HistoryEntry[]; hasMore: boolean; todos?: TodoItem[] }>>
/** Reads a fresh advisory model directory for this session. Provider lookups run independently. */
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.
*/
selectModel(request: RpcRequest<{
sessionId: SessionId
provider: string
model: string
reasoningEffort?: string
}>):
Promise<RpcResponse<{ selected: ModelTarget }>>
/** Sends a message. content is core's ContentBlock[] verbatim; mode maps 1:1 — queue→send, steer→steer. */
prompt(request: RpcRequest<{ sessionId: SessionId; mode: 'queue' | 'steer'; content: ContentBlock[] }>):
Promise<RpcResponse<{ accepted: true }>>