Merge remote-tracking branch 'origin/worktree/web-multimodal-image-input' into worktree/pr555-simplify

# Conflicts:
#	.agents/notes/implemented/feature/2026-07-22-web-multimodal-image-input-and-durable-attachments.i18n.yaml
#	apps/web/tests/image-display.snapshot.ts
#	docs/core-data-structures/core.i18n.yaml
#	packages/client/ui-trajectory/tests/client-bundle.spec.ts
This commit is contained in:
creatixchu
2026-07-30 13:02:44 +08:00
257 changed files with 19105 additions and 3305 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/cordis/tool-cordis/README.md
README.md: 5b58e665dae95aea0d0ad094238fef5d3dc0fb97
README.zh.md: 237b72244be2336a82c8c48cb341d7f9796d08f4
README.md: eda135d93e2912bbb4e111af40d176409b383b5b
README.zh.md: 6eef10086142d56dd809e5114b4e0e712f726ecc
+1 -1
View File
@@ -28,7 +28,7 @@ The sandbox isolates globals but is not a security boundary. Node globals are ab
## The generated API catalog
`src/api-catalog.ts` is generated by `scripts/gen-cordis-api.ts` from the same AST walk as [docs/cordis-catalog](../../../docs/cordis-catalog/services.md) and freshness-gated by `pnpm run verify-cordis-api` (in `doc-sync`) — never edit it by hand. `cordis_inspect` intersects it with the live service store at call time. Broad `api` / `events` reports render summaries and signatures only; an exact `name` opts into the retained method/event JSDoc, and unknown or non-running service targets fail loud.
`src/api-catalog.ts` is generated from the same Typert `FaceModel` projection as [docs/cordis-catalog](../../../docs/cordis-catalog/services.md) and freshness-gated by `pnpm run verify-cordis-api` (in `doc-sync`) — never edit it by hand. `scripts/gen-cordis-api.ts` is a compatibility entry point for that unified projection, not a second collector. `cordis_inspect` intersects the committed catalog with the live service store at call time; it has no runtime Typert dependency. Broad `api` / `events` reports render summaries and signatures only; an exact `name` opts into the retained method/event JSDoc, and unknown or non-running service targets fail loud.
## Rendering
+1 -1
View File
@@ -28,7 +28,7 @@
## 生成的 API 目录
`src/api-catalog.ts` 由 `scripts/gen-cordis-api.ts` 生成,使用与 [docs/cordis-catalog](../../../docs/cordis-catalog/services.md) 相同的 AST 遍历,并由 `pnpm run verify-cordis-api`(位于 `doc-sync` 中)实施新鲜度门禁,绝不可手工编辑。`cordis_inspect` 在调用时把该目录与存活服务 store 取交集。宽泛的 `api`/`events` 报告只渲染摘要与签名;精确 `name` 会选择保留的方法/事件 JSDoc,未知或未运行的服务目标会明确报错。
`src/api-catalog.ts` 与 [docs/cordis-catalog](../../../docs/cordis-catalog/services.md) 由同一个 Typert `FaceModel` 投影生成,并由 `pnpm run verify-cordis-api`(位于 `doc-sync` 中)实施新鲜度门禁,绝不可手工编辑。`scripts/gen-cordis-api.ts` 是该统一投影的兼容入口,而非第二套收集器。`cordis_inspect` 在调用时把已提交的目录与存活服务 store 取交集;它在运行时不依赖 Typert。宽泛的 `api`/`events` 报告只渲染摘要与签名;精确 `name` 会选择保留的方法/事件 JSDoc,未知或未运行的服务目标会高声失败。
## 渲染
+92 -30
View File
@@ -507,7 +507,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
jsDoc: '/**\n * Deliver an allowed signal through an owned backend session.\n * @param owner - exact session owner.\n * @param id - target PTY identity.\n * @param signal - allowed POSIX signal name.\n * @returns delivered foreground process-group identity.\n */',
},
{
signature: 'async kill(owner: Agent, id: PtySessionId, reason = \'model request\'): Promise<boolean>',
signature: 'async kill(owner: Agent, id: PtySessionId, reason: string = \'model request\'): Promise<boolean>',
jsDoc: '/**\n * Close one owned session and remove it only after quiescent backend cleanup.\n * @param owner - exact session owner.\n * @param id - target PTY identity.\n * @param reason - diagnostic cleanup reason.\n * @returns true for a newly closed session, false when the same close is already in flight.\n */',
},
{
@@ -697,7 +697,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
summary: 'Exact-read consumer that prepares immutable cross-session message context.',
methods: [
{
signature: 'async listCandidates( agent: Agent, query = \'\', limit = this.config.candidateLimit, signal?: AbortSignal, ): Promise<SessionReferenceCandidate[]>',
signature: 'async listCandidates( agent: Agent, query: string = \'\', limit: number = this.config.candidateLimit, signal?: AbortSignal, ): Promise<SessionReferenceCandidate[]>',
jsDoc: '/**\n * List reference candidates, ranked by working-directory affinity.\n * @param agent - target agent; self is excluded and its cwd drives ranking.\n * @param query - optional case-insensitive session-id/cwd/title substring.\n * @param limit - optional positive result cap.\n * @param signal - optional cancellation boundary for host autocomplete teardown.\n * @returns candidates labeled by latest title or, when absent, session id.\n */',
},
{
@@ -1020,6 +1020,40 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
},
],
},
{
key: 'typert',
summary: 'Registry of generated schemas and package reflection.',
methods: [
{
signature: 'register(contribution: TypertContribution): () => void',
jsDoc: '/**\n * Register one generated contribution atomically for the calling fiber.\n * Duplicate package-face identities or schema keys reject the whole batch.\n * @param contribution - generated schemas and package metadata.\n * @returns the exact effect disposer that removes this contribution.\n */',
},
{
signature: 'get(key: string): TypertSchemaRecord | undefined',
jsDoc: '/**\n * Look up one schema by `<package>#<name>`.\n * @param key - global schema key.\n * @returns the live schema record, or `undefined` when absent.\n */',
},
{
signature: 'resolve(key: string): TypertSchemaRecord',
jsDoc: '/**\n * Resolve one required schema.\n * @param key - global schema key.\n * @returns the live schema record.\n * @throws when the key is malformed, the package face is absent, or the schema is not contributed.\n */',
},
{
signature: 'list(filter: TypertSchemaFilter = {}): TypertSchemaRecord[]',
jsDoc: '/**\n * Enumerate live schemas in registration order.\n * @param filter - optional package and face restriction.\n * @returns matching schema records.\n */',
},
{
signature: 'getPackage(packageName: string, face: TypertFace = \'host\'): TypertPackageRecord | undefined',
jsDoc: '/**\n * Look up generated reflection for one package face.\n * @param packageName - exact npm package name.\n * @param face - face to query; defaults to the host runtime.\n * @returns the live package record, or `undefined` when absent.\n */',
},
{
signature: 'listPackages(filter: TypertPackageFilter = {}): TypertPackageRecord[]',
jsDoc: '/**\n * Enumerate generated package reflection in registration order.\n * @param filter - optional package and face restriction.\n * @returns matching package records.\n */',
},
{
signature: 'toJSONSchema(key: string, params?: z.core.ToJSONSchemaParams): z.core.JSONSchema.BaseSchema',
jsDoc: '/**\n * Project a live Zod schema to JSON Schema without caching the result.\n * @param key - global schema key.\n * @param params - Zod projection parameters.\n * @returns a fresh JSON Schema document.\n */',
},
],
},
{
key: 'userInteraction',
summary: '`ctx.userInteraction`: one active UI provider plus an `ask()` surface.',
@@ -1299,34 +1333,6 @@ export const EVENT_API: readonly EventApiEntry[] = [
jsDoc: '/**\n * A skill provider, runtime contribution, or provider-backed catalog may\n * have changed. This is an unfiltered invalidation notification; consumers\n * refetch the catalog for their own lookup options. Listener failures are\n * contained and cannot veto the registry mutation.\n * @mode emit\n */',
summary: 'A skill provider, runtime contribution, or provider-backed catalog may have changed.',
},
{
name: 'slash/input-begin-command',
mode: 'bail',
signature: '\'slash/input-begin-command\'(request: BeginCommandRequest): true | undefined',
jsDoc: '/**\n * Applies one command claim to the scoped Input. Dispatched with the\n * session\'s scope carrier; the owning session\'s input listener returns\n * `true` only after the phase and span CAS checks pass and the machine\n * actually mutated — producers treat anything else as "not applied".\n * @param request - Claim and menu-time span CAS.\n * @mode bail\n */',
summary: 'Applies one command claim to the scoped Input.',
},
{
name: 'slash/input-consume-token',
mode: 'bail',
signature: '\'slash/input-consume-token\'(request: ConsumeTokenRequest): true | undefined',
jsDoc: '/**\n * Consumes one command token after business success (popup settle /\n * menu-pick execute). Same carrier routing and applied-truth contract.\n * @param request - Exact span or bare-token guard.\n * @mode bail\n */',
summary: 'Consumes one command token after business success (popup settle / menu-pick execute).',
},
{
name: 'slash/input-insert-reference',
mode: 'bail',
signature: '\'slash/input-insert-reference\'(request: InsertReferenceRequest): true | undefined',
jsDoc: '/**\n * Inserts one reference into the scoped Input (same carrier routing and\n * applied-truth contract as begin-command).\n * @param request - Reference and menu-time span CAS.\n * @mode bail\n */',
summary: 'Inserts one reference into the scoped Input (same carrier routing and applied-truth contract as begin-command).',
},
{
name: 'slash/input-insert-text',
mode: 'bail',
signature: '\'slash/input-insert-text\'(request: InsertTextRequest): true | undefined',
jsDoc: '/**\n * Replaces the trigger token span with literal text — the plain-text\n * reference path (decision 21). Same carrier routing and applied-truth\n * contract; the draft gains ordinary characters, no occurrence entry.\n * @param request - Replacement text and menu-time span CAS.\n * @mode bail\n */',
summary: 'Replaces the trigger token span with literal text — the plain-text reference path (decision 21).',
},
{
name: 'subagent/end',
mode: 'emit',
@@ -2748,6 +2754,62 @@ export const TYPE_API: readonly TypeApiEntry[] = [
name: 'TurnTriggerMap',
declaration: 'export interface TurnTriggerMap {\n message: {\n kind: \'message\';\n source: MessageSource;\n };\n retry: {\n kind: \'retry\';\n };\n injection: {\n kind: \'injection\';\n source: MessageSource;\n };\n}',
},
{
name: 'TypertContribution',
declaration: 'export interface TypertContribution {\n readonly package: string;\n readonly face: TypertFace;\n readonly schemas: readonly TypertSchema[];\n readonly model: TypertPackageModel;\n}',
},
{
name: 'TypertDocTag',
declaration: 'export interface TypertDocTag {\n readonly name: string;\n readonly argument?: string;\n readonly comment?: string;\n readonly text: string;\n}',
},
{
name: 'TypertDocumentation',
declaration: 'export interface TypertDocumentation {\n readonly description?: string;\n readonly summary?: string;\n readonly tags: readonly TypertDocTag[];\n readonly jsDoc?: string;\n}',
},
{
name: 'TypertEventModel',
declaration: 'export interface TypertEventModel extends TypertDocumentation {\n readonly name: string;\n readonly mode?: string;\n readonly signature: string;\n}',
},
{
name: 'TypertMemberModel',
declaration: 'export interface TypertMemberModel {\n readonly kind: \'property\' | \'method\' | \'getter\' | \'setter\' | \'call\' | \'construct\' | \'index\';\n readonly name: string;\n readonly signature: string;\n readonly summary?: string;\n readonly jsDoc?: string;\n}',
},
{
name: 'TypertObjectModel',
declaration: 'export interface TypertObjectModel extends TypertDocumentation {\n readonly name: string;\n readonly exportName: string;\n readonly members: readonly TypertMemberModel[];\n readonly types: readonly TypertTypeModel[];\n}',
},
{
name: 'TypertPackageFilter',
declaration: 'export interface TypertPackageFilter {\n readonly package?: string;\n readonly face?: TypertFace;\n}',
},
{
name: 'TypertPackageModel',
declaration: 'export interface TypertPackageModel {\n readonly services: readonly TypertServiceModel[];\n readonly events: readonly TypertEventModel[];\n readonly objects: readonly TypertObjectModel[];\n}',
},
{
name: 'TypertPackageRecord',
declaration: 'export interface TypertPackageRecord {\n readonly package: string;\n readonly face: TypertFace;\n readonly key: string;\n readonly model: TypertPackageModel;\n}',
},
{
name: 'TypertSchema',
declaration: 'export interface TypertSchema {\n readonly name: string;\n readonly schema: z.ZodType;\n}',
},
{
name: 'TypertSchemaFilter',
declaration: 'export interface TypertSchemaFilter {\n readonly package?: string;\n readonly face?: TypertFace;\n}',
},
{
name: 'TypertSchemaRecord',
declaration: 'export interface TypertSchemaRecord extends TypertSchema {\n readonly package: string;\n readonly face: TypertFace;\n readonly key: string;\n}',
},
{
name: 'TypertServiceModel',
declaration: 'export interface TypertServiceModel extends TypertDocumentation {\n readonly key: string;\n readonly exportName: string;\n readonly members: readonly TypertMemberModel[];\n readonly types: readonly TypertTypeModel[];\n}',
},
{
name: 'TypertTypeModel',
declaration: 'export interface TypertTypeModel {\n readonly name: string;\n readonly declaration: string;\n}',
},
{
name: 'UserInteractionProvider',
declaration: 'export interface UserInteractionProvider {\n ask(request: AskUserQuestionRequest): Promise<AskUserQuestionAnswer>;\n}',