Merge remote-tracking branch 'origin/master' into mergebot/pr711

# Conflicts:
#	apps/cli/README.i18n.yaml
#	docs/module-graph.md
#	packages/client/connection/README.i18n.yaml
#	packages/client/runtime/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
This commit is contained in:
imccyu
2026-07-31 02:02:47 +08:00
455 files changed
+9824 -1402

No files matched your search

@@ -0,0 +1,48 @@
/**
* credentials domain zod schemas (names derived from map keys:
* credentialsDescribeRequestSchema / credentialsDescribeValueSchema / …).
* The reference-name pattern mirrors the seam's `credentialRef` guard so an
* invalid name fails as `bad-request` before reaching the service.
*/
import { z } from 'zod'
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
import type { Wire } from './rpc.schema.ts'
import type { CredentialView } from './credentials.ts'
/** POSIX-portable environment-variable name (the seam's `credentialRef` pattern). */
export const credentialRefNameSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
/** CredentialView entry of credentials.describe. */
export const credentialViewSchema = z.object({
configured: z.boolean(),
source: z.string().optional(),
writable: z.boolean(),
}) satisfies z.ZodType<Wire<CredentialView>>
/** credentials.describe request payload. */
export const credentialsDescribeRequestSchema = z.object({
refs: z.array(credentialRefNameSchema).max(64),
}) satisfies z.ZodType<Wire<RequestPayload<'credentials.describe'>>>
/** credentials.describe response value. */
export const credentialsDescribeValueSchema = z.object({
credentials: z.record(z.string(), credentialViewSchema),
}) satisfies z.ZodType<Wire<ResponseValue<'credentials.describe'>>>
/** credentials.set request payload: the one direction a value crosses this wire. */
export const credentialsSetRequestSchema = z.object({
ref: credentialRefNameSchema,
value: z.string().min(1),
}) satisfies z.ZodType<Wire<RequestPayload<'credentials.set'>>>
/** credentials.set response value. */
export const credentialsSetValueSchema = z.object({}) satisfies z.ZodType<Wire<ResponseValue<'credentials.set'>>>
/** credentials.unset request payload. */
export const credentialsUnsetRequestSchema = z.object({
ref: credentialRefNameSchema,
}) satisfies z.ZodType<Wire<RequestPayload<'credentials.unset'>>>
/** credentials.unset response value. */
export const credentialsUnsetValueSchema = z.object({}) satisfies z.ZodType<Wire<ResponseValue<'credentials.unset'>>>
@@ -0,0 +1,44 @@
/**
* credentials domain contract: the web face of the credential-reference seam
* (`ctx.credentials`). Reads are structurally value-free — a credential view
* carries configured/source/writable and has no slot for the value — and the
* value crosses the wire in exactly one direction, inside `credentials.set`.
* There is no enumeration method by design: clients learn which references
* exist from settings schemas and values (`apiKeyEnv` fields).
*/
import type { RpcRequest, RpcResponse } from './rpc.ts'
/** Wire view of one credential reference's state. */
export interface CredentialView {
/** Whether any layer currently supplies a non-empty value. */
configured: boolean
/** Winning layer when configured (`env`, `file`, …); provider vocabulary. */
source?: string
/** Whether `credentials.set`/`credentials.unset` can affect this reference. */
writable: boolean
}
/** Credentials-domain unary methods (the map keys credentials.* of RpcMethodMap). */
export interface CredentialsApi {
/**
* Describe the named references (batch): configured state, winning source,
* and writability — never values. An invalid reference name is a
* `bad-request`; an unknown-but-valid one describes as unconfigured.
*/
describe(request: RpcRequest<{ refs: string[] }>): Promise<RpcResponse<{ credentials: Record<string, CredentialView> }>>
/**
* Store one credential value in the writable layer. Rejected with
* `credential-rejected` while a read-only layer (the live environment)
* shadows the reference — the write would otherwise appear to succeed while
* resolution keeps returning the shadowing value.
*/
set(request: RpcRequest<{ ref: string; value: string }>): Promise<RpcResponse<{}>>
/**
* Remove one credential from the writable layer; same shadowing rejection
* as `set`. Unsetting an absent reference succeeds (idempotent).
*/
unset(request: RpcRequest<{ ref: string }>): Promise<RpcResponse<{}>>
}
@@ -72,5 +72,8 @@ export const hostFrameSchema = z.discriminatedUnion('type', [
z.object({ type: z.literal('host/workspace-changed'), workspace: workspaceViewSchema }),
z.object({ type: z.literal('host/workspace-removed'), workspaceId: workspaceIdSchema }),
z.object({ type: z.literal('host/commands-changed') }),
z.object({ type: z.literal('host/settings-changed'), ns: z.string() }),
z.object({ type: z.literal('host/credentials-changed'), ref: z.string() }),
z.object({ type: z.literal('host/models-changed') }),
z.object({ type: z.literal('stream/error'), error: rpcErrorSchema }),
]) as unknown as z.ZodType<HostFrame>
+19
View File
@@ -116,4 +116,23 @@ export type HostFrame =
* background rather than diffing.
*/
| { type: 'host/commands-changed' }
/**
* One settings namespace's resolved value changed (`settings/updated`
* passthrough) — an RPC write, an external `settings.yaml` edit, or a
* provider reload all converge here. Clients refetch `settings.describe`;
* values never ride the frame (they would need redaction and can go stale).
*/
| { type: 'host/settings-changed'; ns: string }
/**
* One credential reference's state changed (`credentials/updated`
* passthrough): a set/unset over this wire or an external `.env` edit.
* The ref is an environment-variable NAME — never a value.
*/
| { type: 'host/credentials-changed'; ref: string }
/**
* The provider topology changed (`llm/adapters-updated` passthrough):
* routes registered or dropped, or the configurable directory moved. Pure
* invalidation: clients refetch `llm.providers`/`llm.models`/`session.models`.
*/
| { type: 'host/models-changed' }
| { type: 'stream/error'; error: RpcError }
+9
View File
@@ -11,6 +11,9 @@ 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 { SettingsApi } from './settings.ts'
import type { CredentialsApi } from './credentials.ts'
import type { LlmApi } from './llm.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. */
@@ -22,6 +25,9 @@ export interface ApiProxy {
skills: SkillsApi
events: EventsApi
goals: GoalsApi
settings: SettingsApi
credentials: CredentialsApi
llm: LlmApi
/** Response entry for server-requests (client-response, echoing their rpcId); not a domain method (four-quadrant model). */
respond(message: ClientResponse): Promise<RpcReceipt>
}
@@ -38,6 +44,9 @@ export type { CommandsApi, CommandDescriptor } from './commands.ts'
export type { SkillsApi, SkillEntry } from './skills.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'
export type { CredentialsApi, CredentialView } from './credentials.ts'
export type { ConfigurableProviderView, LlmApi } from './llm.ts'
export type { ApprovalResponsePayload } from './approvals.ts'
export type { QuestionResponsePayload } from './questions.ts'
@@ -0,0 +1,36 @@
/**
* llm domain zod schemas (names derived from map keys: llmProvidersRequestSchema /
* llmProvidersValueSchema / llmModelsRequestSchema / llmModelsValueSchema).
*/
import { z } from 'zod'
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
import type { Wire } from './rpc.schema.ts'
import type { ConfigurableProviderView } from './llm.ts'
import { modelCatalogFailureSchema, modelProviderGroupSchema } from './sessions.schema.ts'
/** ConfigurableProviderView row of llm.providers. */
export const configurableProviderViewSchema = z.object({
provider: z.string().min(1),
displayName: z.string().min(1),
settingsNs: z.string(),
settingsPath: z.array(z.string()),
active: z.boolean(),
}) satisfies z.ZodType<Wire<ConfigurableProviderView>>
/** llm.providers request payload. */
export const llmProvidersRequestSchema = z.object({}) satisfies z.ZodType<Wire<RequestPayload<'llm.providers'>>>
/** llm.providers response value. */
export const llmProvidersValueSchema = z.object({
providers: z.array(configurableProviderViewSchema),
}) satisfies z.ZodType<Wire<ResponseValue<'llm.providers'>>>
/** llm.models request payload. */
export const llmModelsRequestSchema = z.object({}) satisfies z.ZodType<Wire<RequestPayload<'llm.models'>>>
/** llm.models response value. */
export const llmModelsValueSchema = z.object({
groups: z.array(modelProviderGroupSchema),
failures: z.array(modelCatalogFailureSchema),
}) satisfies z.ZodType<Wire<ResponseValue<'llm.models'>>>
+43
View File
@@ -0,0 +1,43 @@
/**
* llm domain contract: host-scoped provider topology for configuration
* surfaces. `llm.providers` merges the configurable-provider directory
* (which providers CAN be configured, and where their settings live) with the
* live route registry; `llm.models` is the session-independent model catalog
* (`session.models` minus the per-session current/unlisted logic). Both
* invalidate on the `host/models-changed` frame.
*/
import type { RpcRequest, RpcResponse } from './rpc.ts'
import type { ModelCatalogFailure, ModelProviderGroup } from './sessions.ts'
/** Wire view of one configurable provider. */
export interface ConfigurableProviderView {
/** Provider route key (`deepseek-official`, `openai`, …). */
provider: string
/** Human-readable name for configuration surfaces. */
displayName: string
/** Settings namespace whose section configures this provider. */
settingsNs: string
/** Path from that section's root to the provider's profile object (empty = whole section). */
settingsPath: string[]
/** Whether the route is currently registered (its models are requestable). */
active: boolean
}
/** Llm-domain unary methods (the map keys llm.* of RpcMethodMap). */
export interface LlmApi {
/**
* List every configurable provider with its live/dormant state, in
* directory declaration order. Routes registered outside the directory
* (an adapter that never declared configurability) are appended with their
* registration identity and no settings address.
*/
providers(request: RpcRequest<{}>): Promise<RpcResponse<{ providers: ConfigurableProviderView[] }>>
/**
* Host-scoped model catalog over every registered provider route: the
* settings surface's models view, needing no session. Per-provider listing
* failures ride `failures` without failing the sound groups.
*/
models(request: RpcRequest<{}>): Promise<RpcResponse<{ groups: ModelProviderGroup[]; failures: ModelCatalogFailure[] }>>
}
+12
View File
@@ -10,6 +10,9 @@ 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 { SettingsApi } from './settings.ts'
import type { CredentialsApi } from './credentials.ts'
import type { LlmApi } from './llm.ts'
import type { RpcResponse } from './rpc.ts'
/**
@@ -48,6 +51,15 @@ export interface RpcMethodMap {
'goal.resume': GoalsApi['resume']
'goal.complete': GoalsApi['complete']
'goal.clear': GoalsApi['clear']
'settings.describe': SettingsApi['describe']
'settings.update': SettingsApi['update']
'settings.replace': SettingsApi['replace']
'settings.mutate': SettingsApi['mutate']
'credentials.describe': CredentialsApi['describe']
'credentials.set': CredentialsApi['set']
'credentials.unset': CredentialsApi['unset']
'llm.providers': LlmApi['providers']
'llm.models': LlmApi['models']
}
/** Business request payload of method K (reaches through the RpcRequest narrow form to payload). */
@@ -50,6 +50,10 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
z.object({ code: z.literal('queue-item-not-found'), 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() }) }),
z.object({ code: z.literal('settings-not-exposed'), message: z.string(), details: z.object({ ns: z.string() }) }),
z.object({ code: z.literal('settings-conflict'), message: z.string(), details: z.object({ ns: z.string(), expected: z.number(), actual: z.number() }) }),
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('internal'), message: z.string(), details: z.object({}) }),
+19
View File
@@ -50,6 +50,25 @@ export interface RpcErrorDetailsMap {
'command-error': {}
/** A leading-/ prompt named no registered command; the message names the token. */
'unknown-command': {}
/**
* A settings write was refused (schema validation, unknown namespace,
* read-only provider, or storage failure); the message is the seam's text.
*/
'settings-rejected': { ns: string }
/**
* A settings namespace exists in the seam but is outside the configuration
* plane's model-provider boundary, so this proxy neither reads nor writes
* it; the message names the namespace.
*/
'settings-not-exposed': { ns: string }
/**
* A settings write carried an `expectedRevision` the namespace has already
* moved past: another writer (tab, editor, or an external file edit) landed
* first. The details carry both revisions so a client can re-read and retry.
*/
'settings-conflict': { ns: string; expected: number; actual: number }
/** A credential write was refused (read-only shadowing layer or storage failure); the message is the seam's own text. */
'credential-rejected': { ref: string }
'title-invalid': { sessionId: SessionId }
'fork-unavailable': { sessionId: SessionId }
'internal': {}
@@ -0,0 +1,72 @@
/**
* settings domain zod schemas (names derived from map keys: settingsDescribeRequestSchema /
* settingsDescribeValueSchema / settingsUpdate* / settingsReplace*).
*/
import { z } from 'zod'
import type { RequestPayload, ResponseValue } from './rpc-map.ts'
import type { Wire } from './rpc.schema.ts'
import type { SettingsNamespaceView, SettingsPathOpView, SettingsSecretView } from './settings.ts'
/** One redacted secret slot. */
export const settingsSecretViewSchema = z.object({
path: z.array(z.string()),
set: z.boolean(),
}) satisfies z.ZodType<Wire<SettingsSecretView>>
/** SettingsNamespaceView row of settings.describe and the write responses. */
export const settingsNamespaceViewSchema = z.object({
ns: z.string().min(1),
schema: z.unknown(),
value: z.unknown(),
base: z.unknown().optional(),
user: z.unknown().optional(),
applies: z.union([z.literal('live'), z.literal('restart')]),
secrets: z.array(settingsSecretViewSchema),
revision: z.number(),
}) satisfies z.ZodType<Wire<SettingsNamespaceView>>
/** settings.describe request payload. */
export const settingsDescribeRequestSchema = z.object({}) satisfies z.ZodType<Wire<RequestPayload<'settings.describe'>>>
/** settings.describe response value. */
export const settingsDescribeValueSchema = z.object({
writable: z.boolean(),
namespaces: z.array(settingsNamespaceViewSchema),
}) satisfies z.ZodType<Wire<ResponseValue<'settings.describe'>>>
/** settings.update request payload. */
export const settingsUpdateRequestSchema = z.object({
ns: z.string().min(1),
patch: z.record(z.string(), z.unknown()),
expectedRevision: z.number().optional(),
}) satisfies z.ZodType<Wire<RequestPayload<'settings.update'>>>
/** settings.update response value: the namespace's new redacted view. */
export const settingsUpdateValueSchema = settingsNamespaceViewSchema satisfies z.ZodType<Wire<ResponseValue<'settings.update'>>>
/** settings.replace request payload. */
export const settingsReplaceRequestSchema = z.object({
ns: z.string().min(1),
section: z.record(z.string(), z.unknown()),
expectedRevision: z.number().optional(),
}) satisfies z.ZodType<Wire<RequestPayload<'settings.replace'>>>
/** One path-addressed edit of settings.mutate. */
export const settingsPathOpSchema = z.discriminatedUnion('op', [
z.object({ op: z.literal('set'), path: z.array(z.string()), value: z.unknown() }),
z.object({ op: z.literal('unset'), path: z.array(z.string()) }),
]) as unknown as z.ZodType<Wire<SettingsPathOpView>>
/** settings.mutate request payload. */
export const settingsMutateRequestSchema = z.object({
ns: z.string().min(1),
ops: z.array(settingsPathOpSchema),
expectedRevision: z.number().optional(),
}) satisfies z.ZodType<Wire<RequestPayload<'settings.mutate'>>>
/** settings.mutate response value: the namespace's new redacted view. */
export const settingsMutateValueSchema = settingsNamespaceViewSchema satisfies z.ZodType<Wire<ResponseValue<'settings.mutate'>>>
/** settings.replace response value. */
export const settingsReplaceValueSchema = settingsNamespaceViewSchema satisfies z.ZodType<Wire<ResponseValue<'settings.replace'>>>
@@ -0,0 +1,90 @@
/**
* settings domain contract: the web face of the user-settings seam
* (`ctx.settings`). Every payload that leaves this domain is redacted by the
* seam (`describe({ redactSecrets: true })` semantics): `role('secret')`
* fields never ride a response in any layer, and the `secrets` slot list is
* how a form learns a write-only field exists and whether it is configured.
*/
import type { RpcRequest, RpcResponse } from './rpc.ts'
/** One schema-declared secret slot inside a redacted namespace value. */
export interface SettingsSecretView {
/** Path from the section root to the removed field. */
path: string[]
/** Whether the slot currently holds a value (the value itself never rides). */
set: boolean
}
/** Wire view of one registered settings namespace. */
export interface SettingsNamespaceView {
/** Namespace key (`llm-deepseek`, `llm-pi-ai`, …). */
ns: string
/** Serialized schemastery schema envelope (`schema.toJSON()`); rehydrate with `new Schema(json)`. */
schema: unknown
/** Redacted resolved value (schema defaults → composition base → user layer). */
value: unknown
/** Redacted composition base layer, when the registrant declared one. */
base?: unknown
/** Redacted raw user section, when one exists; a field's presence here marks it user-overridden. */
user?: unknown
/** When the owner applies changes. */
applies: 'live' | 'restart'
/** Every schema-declared secret slot with its configured state. */
secrets: SettingsSecretView[]
/**
* Monotonic revision of the raw user section this view was read at. Send it
* back as `expectedRevision` on a write so a stale editor is refused rather
* than silently overwriting a concurrent change.
*/
revision: number
}
/**
* One path-addressed edit carried by `settings.mutate`. `set` writes the
* value at the path (creating intermediate objects); `unset` removes it. The
* empty path addresses the section root.
*/
export type SettingsPathOpView =
| { op: 'set'; path: string[]; value: unknown }
| { op: 'unset'; path: string[] }
/** Settings-domain unary methods (the map keys settings.* of RpcMethodMap). */
export interface SettingsApi {
/**
* Describe every registered namespace: redacted layered values plus the
* serialized schema a client renders its form from. `writable: false`
* (read-only provider) tells the client to disable every write control.
*/
describe(request: RpcRequest<{}>): Promise<RpcResponse<{ writable: boolean; namespaces: SettingsNamespaceView[] }>>
/**
* Merge a patch into one namespace's user layer (validate → persist →
* commit). Secret-role fields may be INCLUDED in the patch (write-only
* direction); a form that leaves a secret untouched simply omits it and the
* merge preserves the stored value. Responds with the namespace's new
* redacted view; a schema or storage rejection is `settings-rejected`.
*/
update(request: RpcRequest<{ ns: string; patch: object; expectedRevision?: number }>): Promise<RpcResponse<SettingsNamespaceView>>
/**
* Replace one namespace's user section wholesale — the removal/reset path a
* merge cannot express (`section: {}` resets to composition defaults). Keys
* absent from `section` are dropped, secrets included: a client must first
* fold the descriptor's `user` layer (and re-supply any secret it wants to
* keep) or accept the reset.
*/
replace(request: RpcRequest<{ ns: string; section: object; expectedRevision?: number }>): Promise<RpcResponse<SettingsNamespaceView>>
/**
* Apply path-addressed edits to one namespace's user section, resolved
* against the section as stored — NOT against whatever the caller last
* read. This is the removal path for any client holding the redacted
* descriptor: it names the field it means, so a secret the wire never
* returned cannot be deleted as a side effect. `replace` remains the
* deliberate wholesale reset.
*/
mutate(
request: RpcRequest<{ ns: string; ops: SettingsPathOpView[]; expectedRevision?: number }>,
): Promise<RpcResponse<SettingsNamespaceView>>
}