): ContentBlock[] | undefined
+ /**
+ * Cooperative tool-call timeout budget in milliseconds. Omit for no deadline.
+ * Enforced by `@deepseek-ai/dsh-timeout-policy` (a `tools/execute` wrapper); it
+ * is NEVER sent to the model — `schemas()` whitelists only name/description/
+ * parameters. Declaring it asserts this tool forwards `exec.signal` to a
+ * cooperative implementation that can reach quiescence when the signal aborts.
+ */
+ timeoutMs?: number
+ /**
+ * Pure synchronous classifier for overlap with sibling tool calls. Only
+ * `true` opts in; omission, exceptions, non-`true` returns, and invalid
+ * `defineTool` arguments are exclusive. This metadata is never model-visible.
+ *
+ * Opted-in executions must not mutate parent-owned state. Shared state must
+ * tolerate concurrent dispatch; recorder races are permitted only when they
+ * commute or fail closed. See the
+ * [parallel-tool-call Agent Note](../../../../.agents/notes/implemented/feature/2026-07-10-parallel-tool-call-execution.md)
+ * for the full contract.
+ * @param args - parsed arguments; `defineTool` validates before calling.
+ * @returns Whether this call may join a parallel group.
+ */
+ isConcurrencySafe?(args: unknown): boolean
+ /**
+ * Optional: how to present the PENDING state of one call in a UI, derived from
+ * the call's `args` (parsed arguments, `unknown` — the tool validates/narrows
+ * its own input). Returns a {@link ToolCallView} (a `card`-tagged render intent),
+ * or `undefined` (or omit the method) to fall back to a generic presentation
+ * (title = tool name, raw args as input). Pure and side-effect-free: a UI may
+ * call it during live streaming AND a session-log replay, so it must depend
+ * only on `args`.
+ */
+ presentCall?(args: unknown): ToolCallView | undefined
+ /**
+ * Optional: how to present the COMPLETED state, given the same `args` and the
+ * durable result projection (`content`, failure state, and optional `meta`). Returns a
+ * {@link ToolResultView}, or `undefined` (or omit the method) to keep the
+ * pending title and render the raw result content. Pure and side-effect-free
+ * for the same replay reason.
+ */
+ presentResult?(args: unknown, result: ToolResult): ToolResultView | undefined
+}
+```
+
+`execute` 接收 `args: unknown`——原始的 `ToolDefinition` 自行校验输入。第一方工具不需要手写校验;它们使用 `defineTool`,由后者代为校验并收窄参数类型、根据 `output.schema` 推导函数体返回类型,并为两个输出投影器提供类型约束。`finalizeContent` 特意接收不可变的执行对象而非类型化参数,因为无效输入和外层流水线失败也会到达该回调;它可以施加工具自有的内容限制,同时保留 `isError`、规范值、结构化错误身份、延迟上下文与展示元数据。
+
+## 统一的 JSON 值 schema DSL
+
+插件作者使用同一套词汇描述类型化参数和类型化输出值。`ValueSchemaSpec` 支持 `string`、`number`、`integer`、`boolean`、`null`、`array`、`object`、仅作者侧可用的 `json`,以及要求恰好命中一个分支的 `oneOf`;标量 `enum` 和 `const` 值必须与节点类型匹配。显式对象节点始终声明 `additionalProperties: true | false`。参数定义仍是隐式的开放对象属性映射,每个必填属性都附带 `required: true`。
+
+源码:[`packages/core/tools/src/schema.ts`](../../packages/core/tools/src/schema.ts)
+
+```ts type-equiv
+/** One author-facing schema for any lossless JSON value root. */
+type ValueSchemaSpec =
+ | StringValueSchemaSpec
+ | NumberValueSchemaSpec
+ | IntegerValueSchemaSpec
+ | BooleanValueSchemaSpec
+ | NullValueSchemaSpec
+ | ArrayValueSchemaSpec
+ | ObjectValueSchemaSpec
+ | JsonValueSchemaSpec
+ | OneOfValueSchemaSpec
+```
+
+```ts type-equiv
+/** One implicit parameter-root property, optionally required. */
+type ParameterPropertySpec = ValueSchemaSpec & { required?: true }
+```
+
+```ts type-equiv
+/**
+ * Tool parameter schema. The map itself is an implicit open object root;
+ * requiredness remains a per-property `required: true` annotation.
+ */
+type ParameterSchemaSpec = {
+ [key: string]: ParameterPropertySpec
+ [key: symbol]: never
+}
+```
+
+`{ type: 'json' }` 推导为 `JsonValue`,并编译成仅含注解、不施加约束的原始 schema。输出根可以是对象、数组、标量或 null。`InferValue` 在 16 层容器内保留字面量约束与对象开放性,之后回退为 `JsonValue`,避免耗尽 TypeScript 的类型实例化栈。`InferArgs` 依据逐属性的必填标记生成必填和可选的字符串键:
+
+```ts type-equiv
+/**
+ * Infer the TypeScript value accepted by an author-facing value schema. Exact
+ * inference is bounded to 16 container levels, then falls back to `JsonValue`.
+ */
+type InferValue = InferValueAt
+```
+
+```ts type-equiv
+/** Infer the TypeScript argument object for an implicit parameter schema. */
+type InferArgs = InferProperties
+```
+
+`defineTool({ name, description, parameters, output, execute, … })` 将参数推导与 `parameterSchemaSpecToJsonSchema()` 和 `validateArgs()` 绑定,并将 `execute`/`render`/`presentationMeta` 与 `InferValue` 绑定。Schema 记录只包含自有且可枚举的字符串键,schema 数组是稠密的内建数组,因此推导、编译与校验观察到的是同一份声明。精确推导保持到 16 层容器,之后放宽为 `JsonValue`;运行时校验仍会继续遍历完整 schema。`valueSchemaSpecToJsonSchema()` 通过同一套已强制执行的原始子集编译输出声明。参数不匹配时抛出 `ToolArgsError`(`INVALID_ARGS`);函数体或后置策略产生的值无效时抛出 `ToolOutputError`(`INVALID_TOOL_OUTPUT`)。两者都经由常规工具错误路径处理。原始 JSON Schema 默认保持开放;不支持的关键字会被拒绝,而不会在未强制执行的情况下获准进入。
+
+注册是一个受信任的同进程契约。注册表以 readonly 输入借用类型化定义,要求它声明 `output`,校验其原始 schema,并检查 `timeoutMs` 必须为正有限值等语义要求;`schemas()` 在模型边界处物化显式的面向模型投影,使执行和展示共享同一份已解析定义,而不会将回调泄漏到协议上。
+
+## `ToolRestriction` — 单个作用域的实时全局过滤器
+
+`ToolRestriction` 仅作用于实时的部署全局工具层。注册表将 readonly 名称编译为私有集合,对多个限制取交集,再叠加作用域本地工具。仅 deny 的过滤器允许后续未列出的全局工具通过,而 allow 列表则排除它们。
+
+```ts type-equiv
+/**
+ * Per-scope filter over global tools. Restrictions intersect and do not affect
+ * scoped registrations or the reserved Code Mode transport.
+ */
+interface ToolRestriction {
+ /** Global tool names that stay visible; everything else is removed. */
+ readonly allow?: readonly string[]
+ /** Global tool names removed from visibility. */
+ readonly deny?: readonly string[]
+}
+```
+
+## 执行:可扩展的 waterfall(瀑布式事件)加单调策略
+
+`ctx.tools.execute()` 接受由调用方拥有且包含必需 readonly `signal` 的 `ToolExecutionInput`,将其解析后的 JSON 参数一次性物化为流水线拥有的 `ToolExecution`,然后让调用依次经过 `tools/pre-execute`(可重排的 allow/deny/ask waterfall)→ 已注册的单调 guard → `tools/execute`(环绕分派包装层)→ `tools/post-execute`(检查/替换结果)→ 可选且由定义拥有的 `finalizeContent` → `tools/result`(不可变的权威结果)。只有 `tools/execute` 视图可以替换必需的 signal。最终产出为 `ToolExecutionResult`。
+
+```ts type-equiv
+/** Opaque call identity that permits correlation without exposing mutable execution state. */
+type ToolExecutionToken = symbol & { readonly [toolExecutionTokenBrand]: true }
+```
+
+```ts type-equiv
+/**
+ * Caller-supplied description of one tool call. {@link ToolRegistry.execute}
+ * adds the registry-owned token to form a pipeline {@link ToolExecution};
+ * callers do not choose that token.
+ */
+interface ToolExecutionInput {
+ readonly callId: CallId
+ readonly name: string
+ /** Losslessly JSON-serializable parsed arguments (tools validate their own schema). */
+ readonly arguments: unknown
+ /** The agent on whose behalf the call runs (set by the agent loop). */
+ readonly agent?: Agent
+ /**
+ * Opaque token of the enclosing transport execution, when one exists. Code
+ * Mode sets this on SDK sub-dispatches so commit-style observers can wait for
+ * the outer `run_code` outcome without receiving its live mutable execution.
+ */
+ readonly parent?: ToolExecutionToken
+ /** Required caller-owned cancellation for this invocation. */
+ readonly signal: AbortSignal
+}
+```
+
+工具函数体接收运行时扩展。`deferContext()` 是组合工具的通道:它记录嵌套分派产生的上下文,而不会在外层调用尚未结束时注入这些上下文。
+
+```ts type-equiv
+/**
+ * Runtime context handed to a tool implementation after the registry has
+ * accepted a {@link ToolExecution}. A composite tool uses
+ * {@link deferContext} to ferry context produced by nested dispatches back to
+ * the outer result; the loop appends it only after the outer `tool/result`.
+ */
+interface ToolRunContext extends ToolExecution {
+ /**
+ * Defer one nested-dispatch context until this tool's final result reaches
+ * the agent loop. Contexts retain their individual source and metadata and
+ * are emitted in call order.
+ */
+ deferContext(context: HookContext): void
+}
+```
+
+agent loop(智能体循环)向注册表查询每个待处理调用的执行模式,并据此形成独占屏障和滚动池并行执行:
+
+```ts type-equiv
+/**
+ * Scheduling mode for one pending call. `parallel` may overlap with siblings;
+ * `exclusive` runs alone and forms an ordering barrier.
+ */
+type ToolExecutionMode =
+ | { kind: 'parallel' }
+ | { kind: 'exclusive' }
+```
+
+```ts type-equiv
+/**
+ * One pending tool call inside the registry pipeline. Parsed arguments cross
+ * one lossless-JSON materialization boundary before policy and are deep-frozen;
+ * call identity, the caller signal, and the registry-assigned {@link token} are
+ * readonly. The registry freezes the complete object before `tools/result`
+ * observers run.
+ */
+interface ToolExecution extends ToolExecutionInput {
+ /** Registry-assigned identity shared with nested calls only as their opaque `parent` token. */
+ readonly token: ToolExecutionToken
+}
+```
+
+```ts type-equiv
+/**
+ * Around-dispatch view of a {@link ToolExecution}. A `tools/execute` wrapper
+ * may replace the signal for its delegated lifetime, but it cannot remove it.
+ * The registry fuses every replacement with the captured caller signal.
+ */
+interface ToolDispatchExecution extends Omit {
+ /** Cancellation signal visible to the next wrapper or tool body. */
+ signal: AbortSignal
+}
+```
+
+`ToolExecutionToken` 是不透明的运行时 `Symbol`,仅用于身份比较。策略执行前,`execute()` 会物化并冻结参数、拒绝非 JSON 输入并分配 token。身份字段、调用方必需的 signal 和可选的 parent token 均保持 readonly。`ToolDispatchExecution` 包装层可以替换 signal 但不能移除;注册表会在调用工具函数体前重新融合调用方的 signal。最终观察者接收冻结的执行身份。
+
+`ToolGuard` 是感知作用域的最终预分派策略。其形状有意不包含 allow 结果:`undefined` 保留 waterfall 的决策,而返回的 reason 只能缩减权限,因此后续监听器无法撤销它。
+
+```ts type-equiv
+/**
+ * A monotonic execution guard evaluated after every `tools/pre-execute`
+ * listener and before the tool body. Returning a reason denies the call;
+ * returning `undefined` leaves it unchanged. Because guards have no allow
+ * result, listener ordering cannot turn a denial back into permission.
+ * @param execution - the identity-protected call after extensible pre-execute policy completed.
+ * @returns a final denial reason, or `undefined` to leave the call allowed.
+ */
+type ToolGuard = (execution: Readonly) => string | undefined
+```
+
+```ts type-equiv
+/** Canonical failure detail; internal routing information remains optional. */
+interface ToolFailure {
+ /** Human-readable failure message without the Native `Error: ` envelope. */
+ message: string
+ /** Internal error class/code used by policy and durable diagnostics. */
+ info?: ToolErrorInfo
+}
+```
+
+```ts type-equiv
+/** Successful canonical tool execution, including its Native/model projection. */
+interface ToolExecutionSuccess {
+ readonly isError: false
+ /** Execution-local canonical value; deliberately omitted from durable events. */
+ readonly value: JsonValue
+ readonly content: ContentBlock[]
+ readonly error?: never
+ readonly meta?: JsonValue
+ readonly additionalContexts?: HookContext[]
+}
+```
+
+```ts type-equiv
+/** Failed canonical tool execution; failures never carry a successful value. */
+interface ToolExecutionFailure {
+ readonly isError: true
+ readonly error: ToolFailure
+ readonly value?: never
+ readonly content: ContentBlock[]
+ readonly meta?: JsonValue
+ readonly additionalContexts?: HookContext[]
+}
+```
+
+```ts type-equiv
+/** The discriminated, execution-local outcome of one tool call. */
+type ToolExecutionResult = ToolExecutionSuccess | ToolExecutionFailure
+```
+
+结果仅承载产出。调用身份保留在不可变的 `ToolExecution` 上,后者伴随结果经过每个钩子,并出现在持久化的 `tool/call` / `tool/result` 会话事件上,因此包装层无法创建第二个相互矛盾的身份。规范的 `value` 仅存在于执行期间:循环只持久化 `content`、`error` 和 `meta`,`tool/code-dispatch` 则存储有界摘要。回放可以重现展示,却无法重建中间值。
+
+成功时,注册表会快照并校验函数体返回值,将其冻结,然后调用纯渲染器;对于直接的外层调用,还会调用可选的元数据投影器。注册表会在 `tools/result` 之前另行物化持久展示字段;无效值、渲染器/投影器失败或非 JSON 展示都会转为 JSON 安全的 `isError`。因此,最终实时观察者能看到精确的执行期值,以及可安全用于后续持久追加的字段。
+
+在得到最终内容之前,注册表会物化候选结果;若内容、结构化错误、附加上下文或展示元数据无法物化,则会转为仍可到达 `finalizeContent` 的 JSON 安全 `isError` 结果。注册表恰好调用该回调一次,随后在 `tools/result` 之前立即物化并冻结已接受的结果,因此实时观察到的产出可安全用于后续持久化的 `tool/result` 追加。
+
+每个拦截 waterfall 返回一个类型化的 **Decision**(与 `agent/*` seam 共享的惯用模式)。`tools/pre-execute` 监听器接收 `(exec, next)` 并返回 `PreToolDecision`;`tools/execute` 包装层返回 `ToolExecutionResult`;`tools/post-execute` 监听器接收 `(exec, result, next)` 并返回 `PostToolDecision`:
+
+```ts type-equiv
+/**
+ * Pre-dispatch decision. `allow` runs the call; `deny` materializes an error;
+ * `ask` runs only after an approval service returns `allowed-once` and otherwise
+ * denies. Input rewriting is excluded because arguments are already logged and
+ * presented.
+ */
+type PreToolDecision =
+ | { kind: 'allow' }
+ | { kind: 'deny'; reason: string }
+ | { kind: 'ask'; reason?: string }
+```
+
+```ts type-equiv
+/**
+ * Post-dispatch decision: accept, replace one projection, attach context for the
+ * next request, or block by turning corrective feedback into an error result.
+ */
+type PostToolDecision =
+ | { kind: 'accept'; content?: ContentBlock[]; value?: never; additionalContexts?: HookContext[] }
+ | { kind: 'accept'; value: JsonValue; content?: never; additionalContexts?: HookContext[] }
+ | { kind: 'block'; feedback: ContentBlock[]; additionalContexts?: HookContext[] }
+```
+
+调用 `next()` 获取默认决策,或直接返回一个决策以短路。前置策略可以 deny 或 ask;只有 `allowed-once` 才继续执行,而未授权、缺少审批通道或服务、或无 agent 的请求都会变为拒绝。Guard 仍可施加最终拒绝。参数不可被改写,因为历史记录、审计、UI 和执行必须保持一致。
+
+后置策略可以替换内容或值,但不能同时替换两者。替换内容会保留规范值和现有元数据;替换值会重新校验并重新计算内容/元数据;阻止会移除值,并转为包含纠正反馈的 `isError`。内容替换是展示策略,而非保密策略;需要隐藏程序化值的监听器必须阻止或替换该值。`tools/result` 在归一化后接收冻结的执行和结果;观察者无法对其进行变换,观察者的失败也会被隔离。未知工具和抛出异常的工具都会变为结构化错误(`ToolNotFoundError` 映射为 `UNKNOWN_TOOL`),调用失败但不终止当前轮次。
+
+## 已强制执行的原始 JSON Schema 子集
+
+subagent、工作流、MCP 和动态注册提供的原始 schema 使用作者侧 DSL 在协议层的对应表示。`assertSupportedJsonSchema()` 接受任意 JSON 根,`validateJsonSchemaValue()` 强制执行该 schema,`JsonSchemaError` 则报告每条不受支持或格式错误的 schema 路径。仅含注解的空节点表示不受约束的无损 JSON。`oneOf` 至少要求两个分支,且一个值必须恰好匹配其中一个。仍要求对象根的消费方调用 `assertObjectJsonSchema()` 并携带 `ObjectJsonSchema`;这样,subagent/工作流中由调用方定义的结构化输出可以继续以对象为根,而不会限制共享词汇。
+
+```ts type-equiv
+/** Scalar JSON values supported by `enum` and `const`. */
+type JsonSchemaScalar = string | number | boolean | null
+```
+
+```ts type-equiv
+/** Single-type keywords accepted by the enforced subset. */
+type JsonSchemaType = 'object' | 'array' | 'string' | 'number' | 'integer' | 'boolean' | 'null'
+```
+
+```ts type-equiv
+/**
+ * One raw JSON Schema node in the enforced subset. The optional fields express
+ * the external wire shape; {@link assertSupportedJsonSchema} rejects invalid
+ * combinations before a caller treats the node as trusted.
+ */
+interface JsonSchemaNode {
+ /** Omit with no constraints for any JSON value, or use `oneOf`. */
+ type?: JsonSchemaType
+ /** Exactly one branch must validate; at least two branches are required. */
+ oneOf?: JsonSchemaNode[]
+ /** Nested property schemas (`type: 'object'` only). */
+ properties?: Record
+ /** Required property names; each must appear in `properties`. */
+ required?: string[]
+ /** `false` rejects undeclared keys; absent/`true` follows JSON Schema's open default. */
+ additionalProperties?: boolean
+ /** Item schema (`type: 'array'` only); absent accepts any JSON item. */
+ items?: JsonSchemaNode
+ /** Allowed values for a scalar node. */
+ enum?: JsonSchemaScalar[]
+ /** The single allowed value for a scalar node. */
+ const?: JsonSchemaScalar
+ /** Annotation, ignored for validation. */
+ description?: string
+ /** Annotation, ignored for validation. */
+ title?: string
+ /** Annotation, ignored for validation but required to be lossless JSON. */
+ default?: JsonValue
+ /** Annotation, ignored for validation but required to be lossless JSON. */
+ examples?: JsonValue
+}
+```
+
+```ts type-equiv
+/** A consumer-constrained object-rooted schema. */
+type ObjectJsonSchema = JsonSchemaNode & { type: 'object' }
+```
+
+## 工具展示 UI 词汇
+
+工具希望其调用在 UI 中如何呈现(编辑器工具调用卡片、CLI(命令行界面)日志行),提供方无关,使工具在不依赖任何客户端协议的情况下描述自身。`presentCall`/`presentResult` 返回一个 **`card` 标签的渲染意图**——一个可辨识联合类型,UI 桥接层据此分发:
+
+- `ToolCallView`(待执行):`{ card: 'generic', title, kind?, rawInput?, content?, locations? }`(默认卡片;`locations` 是 `{ path, line? }[]`,表示调用读取/修改的文件,供编辑器跟随)、`{ card: 'terminal', title, description?, cwd? }`(shell 命令→终端卡片)、或 `{ card: 'diff', title, diffs, locations? }`(文件创建/修改→行内 diff 卡片;`diffs` 是 `{ path, oldText, newText }[]`,新文件时 `oldText: null`)。
+- `ToolResultView`(已完成):`{ card: 'generic', title?, content? }`、`{ card: 'terminal', title?, output?, exitCode?, signal? }`(捕获的运行输出 + 退出状态;有能力的 UI 显示退出状态标签,无能力的 UI 获得桥接层从 `output` 派生的围栏 ` ```console ` 回退)、或 `{ card: 'diff', title?, diffs }`(已完成的文件变更→要展示的变更,通常是从变更前后内容计算出带上下文行的已应用 hunk,或在没有前像时的整文件 diff——例如文件创建。`tool_call_update` 的内容会替换调用的内容,因此变更工具即使与调用时的片段重复也要返回此卡片,以防结果文本覆盖 diff)。
+
+`ToolCallKind`(`'read' | 'edit' | 'delete' | 'move' | 'search' | 'execute' | 'fetch' | 'other'`)用于为通用卡片选择图标。`FileLocation`(`{ path, line? }`)与 `FileDiff`(`{ path, oldText, newText }`)是共享的文件卡片词汇。该设计由[渲染意图联合类型 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md)固定;ACP(Agent Client Protocol)桥接层将 `diff` 卡片映射为 `{ type: 'diff' }` 内容块,将 `terminal` 卡片映射为 `_meta` 终端约定,并根据会话 cwd 将文件卡片标题转换为相对路径。
+
+完整的展示字段文档见 [`packages/core/tools/src/presentation.ts`](../../packages/core/tools/src/presentation.ts)。`bash` schema 与执行器见 [bash.md](bash.md);通用后台控制见 [tasks.md](tasks.md)。
diff --git a/docs/core-data-structures/user-interaction.i18n.yaml b/docs/core-data-structures/user-interaction.i18n.yaml
new file mode 100644
index 0000000000..126f18729e
--- /dev/null
+++ b/docs/core-data-structures/user-interaction.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+user-interaction.md: 7ddc2f46bdbbe25f2a8979b029d0c6b958f85b09
+user-interaction.zh.md: 75b9b7795a36e0706672f0d17ba0b399c20cc644
diff --git a/docs/core-data-structures/user-interaction.md b/docs/core-data-structures/user-interaction.md
index e010b48987..7ddc2f46bd 100644
--- a/docs/core-data-structures/user-interaction.md
+++ b/docs/core-data-structures/user-interaction.md
@@ -1,5 +1,7 @@
# User Interaction
+English | [中文](user-interaction.zh.md)
+
The user-interaction seam of [dsh-user-interaction](../../packages/ui/user-interaction). It is the provider-neutral vocabulary a tool or permission plugin uses when it needs the human to answer before the agent can continue. UI surfaces provide the active `UserInteractionProvider`: `dsh-tui` uses keyboard-driven overlays, and `dsh-acp` maps questions to ACP form elicitations.
Source: [`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts)
diff --git a/docs/core-data-structures/user-interaction.zh.md b/docs/core-data-structures/user-interaction.zh.md
new file mode 100644
index 0000000000..75b9b7795a
--- /dev/null
+++ b/docs/core-data-structures/user-interaction.zh.md
@@ -0,0 +1,108 @@
+# 用户交互
+
+[English](user-interaction.md) | 中文
+
+[dsh-user-interaction](../../packages/ui/user-interaction) 的用户交互 seam。它是工具或权限插件需要人类回答后 agent(智能体)才能继续时所使用的、提供方无关的词汇。UI surface 提供活跃的 `UserInteractionProvider`:`dsh-tui` 使用键盘驱动的 overlay,`dsh-acp` 则把问题映射为 ACP(Agent Client Protocol)表单 elicitation。
+
+源码:[`packages/ui/user-interaction/src/index.ts`](../../packages/ui/user-interaction/src/index.ts)
+
+## 问题选项
+
+`AskUserQuestionOption` 是可选择项的形状。`label` 是面向用户的选项文字,同时也是面向模型的选中值;`description` 是可选的 UI 帮助文本。
+
+```ts type-equiv
+/** One selectable answer offered to the user. */
+interface AskUserQuestionOption {
+ /** User-facing label. */
+ label: string
+ /** Optional extra context rendered by capable UIs. */
+ description?: string
+}
+```
+
+## 问题条目
+
+`AskUserQuestionItem` 是请求中的一个问题。调用方提供稳定的 `id`,它会随答案原样返回,使批量问题仍可路由。可选的 `detail` 携带辅助文本;提供方会将其随问题渲染,但不会放入可选 option label。
+
+```ts type-equiv
+/** One question in a user-interaction request. */
+interface AskUserQuestionItem {
+ /** Stable caller-provided question id, echoed in the answer. */
+ id: string
+ /** The question to display. */
+ question: string
+ /** Optional supporting detail rendered with the question but kept out of option labels. */
+ detail?: string
+ /** Optional short heading/group label. */
+ header?: string
+ /** Optional choices the UI can render as a menu. */
+ options?: AskUserQuestionOption[]
+ /** Whether more than one option may be selected. Defaults to single-select. */
+ multiSelect?: boolean
+}
+```
+
+## 提问请求
+
+`AskUserQuestionRequest` 是跨包(package)的请求。`questions` 是数组,这样 UI 可以在一个流程中呈现相关提示,同时保持每个回答有稳定的 id。
+
+```ts type-equiv
+/** Request for a human answer. */
+interface AskUserQuestionRequest {
+ /** Questions to display. */
+ questions: AskUserQuestionItem[]
+ /** Calling agent, when the request came from an agent tool call. */
+ agent?: Agent
+ /** Abort signal for the owning tool/step. */
+ signal?: AbortSignal
+}
+```
+
+## 回答
+
+提供方为每个问题 id 返回一个回答项。`selected` 包含选中的选项标签,`custom` 在用户输入自由文本时携带「其他」回答。当 `custom` 存在时,`selected` 为空;自定义文本是对选中项的覆盖,而非补充。UI 也可以使用 `selected` 为空且不含 `custom` 的回答项,在其余问题均已完成的批次中保留被跳过的问题。
+
+```ts type-equiv
+/** Answer to one question. */
+interface AskUserQuestionAnswerItem {
+ /** The answered question id. */
+ id: string
+ /** Selected option labels. Empty for custom or unanswered choices. */
+ selected: string[]
+ /** Optional free-text "Other" answer. */
+ custom?: string
+}
+```
+
+```ts type-equiv
+/** The human's answer. */
+interface AskUserQuestionAnswer {
+ /** Structured answers keyed by question id. */
+ answers: AskUserQuestionAnswerItem[]
+}
+```
+
+## 提供方
+
+同一上下文中只能有一个活跃的提供方。提供方注册绑定到 effect,因此 HMR(热模块替换)或 dispose(资源释放)会移除当前活跃的 UI。
+
+```ts type-equiv
+/** UI-side provider for user questions. */
+interface UserInteractionProvider {
+ ask(request: AskUserQuestionRequest): Promise
+}
+```
+
+## 错误
+
+`UserInteractionError` 继承 `HarnessError`,因此 `ctx.tools.execute()` 会保留 `{ name, code }`,用于面向模型的工具失败,如 `EMPTY_QUESTIONS`、`NO_PROVIDER`、`ASK_ABORTED` 或 ACP 侧取消。
+
+```ts type-equiv
+/** Stable error taxonomy for user-interaction failures. */
+class UserInteractionError extends HarnessError {
+ constructor(message: string, code: string, options?: ErrorOptions) {
+ super(message, code, options)
+ this.name = 'UserInteractionError'
+ }
+}
+```
diff --git a/docs/core-data-structures/web.i18n.yaml b/docs/core-data-structures/web.i18n.yaml
new file mode 100644
index 0000000000..912c1decbe
--- /dev/null
+++ b/docs/core-data-structures/web.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+web.md: 20d07240c9d9fea2f1f5abbac810f349a3e81f9b
+web.zh.md: 68ceed04bb0b80f32ed704118f1fc25f48a0da70
diff --git a/docs/core-data-structures/web.md b/docs/core-data-structures/web.md
index 22909b8dfb..20d07240c9 100644
--- a/docs/core-data-structures/web.md
+++ b/docs/core-data-structures/web.md
@@ -1,5 +1,7 @@
# Web Access
+English | [中文](web.zh.md)
+
The web access seam — a [capability seam](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md) that spans **two capabilities** (search and fetch) on one `ctx.web` service, split across packages: interface ([dsh-web](../../packages/web/web), `ctx.web` + the provider registries), implementations ([dsh-web-search-exa](../../packages/web/web-search-exa), [dsh-web-search-perplexity](../../packages/web/web-search-perplexity), [dsh-web-search-deepseek](../../packages/web/web-search-deepseek), [dsh-web-fetch-local](../../packages/web/web-fetch-local)), and consumer ([dsh-tool-web](../../packages/web/tool-web), the `web_search`/`web_fetch` tool schemas). Web is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A search-provider swap does not change how the model asks for a query, and a fetch-implementation swap does not change how the model asks for a URL.
Source: [`packages/web/web/src/types.ts`](../../packages/web/web/src/types.ts)
diff --git a/docs/core-data-structures/web.zh.md b/docs/core-data-structures/web.zh.md
new file mode 100644
index 0000000000..68ceed04bb
--- /dev/null
+++ b/docs/core-data-structures/web.zh.md
@@ -0,0 +1,135 @@
+# Web 访问
+
+[English](web.md) | 中文
+
+Web 访问 seam 是一个[能力 seam](../../.agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md),在同一个 `ctx.web` 服务上横跨**两项能力**(search 与 fetch),并拆分到多个包(package):接口([dsh-web](../../packages/web/web),`ctx.web` + 提供方注册表)、实现([dsh-web-search-exa](../../packages/web/web-search-exa)、[dsh-web-search-perplexity](../../packages/web/web-search-perplexity)、[dsh-web-search-deepseek](../../packages/web/web-search-deepseek)、[dsh-web-fetch-local](../../packages/web/web-fetch-local))与消费方([dsh-tool-web](../../packages/web/tool-web),即 `web_search`/`web_fetch` 工具 schema)。Web 是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](core.md) 中。更换 search 提供方不会改变模型请求 query 的方式,更换 fetch 实现也不会改变模型请求 URL 的方式。
+
+源码:[`packages/web/web/src/types.ts`](../../packages/web/web/src/types.ts)
+
+## 为什么两项能力合为一个 seam
+
+搜索与抓取既不共享请求 schema,也不共享业务逻辑,但它们被有意设计为同一个 `ctx.web` 中间层:一个提供方选择策略的所有者、一套 abort/error 词汇、一个面向产品的「此 harness 如何访问 Web」配置界面。代价是服务上并行的 `searchX`/`fetchX` 方法对;这种并行是有意为之,而非遗漏的提取。提供方注册的是**能力**(`WebSearchProvider` 或 `WebFetchProvider`),而非工具;面向模型的名称、schema、提示词引导与展示全部集中在唯一的消费方 `dsh-tool-web` 中。
+
+## 搜索请求与结果
+
+面向模型的工具参数仅为一个 `query`;`maxResults` 是消费方自有的上限(`dsh-tool-web` 的 `searchMaxResults` 配置,默认 `8`),通过 seam 传递并在返回时强制执行——如果提供方返回超量,seam 截断 `sources[]` 并设置 `truncated`。
+
+```ts type-equiv
+/**
+ * What one search-capable backend can return. The model-facing argument is just
+ * a query; `maxResults` is a `dsh-tool-web`-layer bound passed through unchanged
+ * and enforced on the way back by the seam (see {@link WebSearchResult}).
+ */
+interface WebSearchRequest {
+ readonly query: string
+ /**
+ * Upper bound on returned sources; the seam truncates to it. Omitted = no
+ * bound. `dsh-tool-web` always sets it. A provider whose API supports a
+ * result-count control (Exa's `numResults`) should apply it at the request
+ * layer as a cost/latency optimization; the seam enforces the bound
+ * regardless.
+ */
+ readonly maxResults?: number
+}
+```
+
+```ts type-equiv
+/**
+ * Normalized search outcome. `content` is optional provider-generated answer
+ * text or summary (Exa returns none; Perplexity returns a generated answer).
+ * `sources[]` is the portable citation surface. `truncated` is set by the seam
+ * when it cut `sources[]` down to `maxResults`.
+ */
+interface WebSearchResult {
+ /** Optional provider-generated answer text, search context, or summary. */
+ readonly content?: string
+ /** Citeable sources, already truncated to the request's `maxResults`. */
+ readonly sources: readonly WebSearchSource[]
+ /** True when the seam dropped sources to honor `maxResults`. */
+ readonly truncated: boolean
+}
+```
+
+`content` 是提供方可选生成的回答文本(Exa 和 DeepSeek 不返回;Perplexity 返回生成式回答)。`sources[]` 是一套可跨提供方使用的引用数据结构。一个 source 必有 `url`;`title`/`snippet`/`publishedAt` 可选,因为并非每个提供方都返回它们——Perplexity 的引用可能只有 URL,强迫适配器编造其余字段会让 seam 说谎。`dsh-tool-web` 渲染时使用 `title ?? hostname(url)`。
+
+```ts type-equiv
+/**
+ * One citeable source. A source always has a URL; `title`, `snippet`, and
+ * `publishedAt` are optional because not every provider returns them — forcing
+ * adapters to invent them would make the seam lie (Perplexity citations may be
+ * URL-only). `dsh-tool-web` renders `title ?? hostname(url)` for display.
+ */
+interface WebSearchSource {
+ readonly url: string
+ readonly title?: string
+ readonly snippet?: string
+ /** Publication/crawl timestamp as a provider-supplied ISO-8601 string. */
+ readonly publishedAt?: string
+}
+```
+
+## 抓取请求与结果
+
+```ts type-equiv
+/**
+ * What one fetch-capable backend is asked to retrieve. The request deliberately
+ * omits timeout, format, prompt, and extraction controls: cancellation is a
+ * direct execution argument, while presentation and higher-level LLM concerns
+ * belong outside safe retrieval.
+ */
+interface WebFetchRequest {
+ readonly url: string
+}
+```
+
+HTTP 状态码是被抓取资源状态的一部分,不自动视为失败:成功的网络抓取返回 `404`/`500` 时,仍产出一个带状态码和有界解码 body 的 `WebFetchResult`。`url` 是经过允许的重定向后的最终 URL。`WebError` 仅用于无法安全获取或表示资源的情况。
+
+```ts type-equiv
+/**
+ * Normalized fetch outcome. A successful network fetch of a non-2xx response is
+ * a result, not an error: the status code is part of the fetched resource
+ * state. {@link WebError} is reserved for failures to safely retrieve or
+ * represent the resource.
+ */
+interface WebFetchResult {
+ /** The final URL after allowed redirects (the request URL is in the request). */
+ readonly url: string
+ /** HTTP status code of the fetched response. */
+ readonly statusCode: number
+ /** Decoded body, classified by content kind. */
+ readonly body: WebFetchBody
+ /** True when the provider capped the decoded body. */
+ readonly truncated: boolean
+}
+```
+
+`WebFetchBody` 是 `dsh-web` 拥有的**封闭**可辨识联合类型(不是可合并扩展的 map):提供方解码 kind,`dsh-tool-web` 渲染它,因此新增一个 kind 是已知包之间的协调变更,而非插件扩展。消费方对 `kind` 做 `switch` 并以 `default: assertNever(...)` 结尾,所以新增 kind 会在每个消费方处编译失败,直到被处理。即使各分支当前字段一致,每个分支仍保持独立的对象字面量,为将来分支特有字段留出空间(例如未来 `pdf` body 的 `pageCount`)。
+
+```ts type-equiv
+/**
+ * The decoded body of a fetched resource. A CLOSED discriminated union owned by
+ * `dsh-web`: the provider decodes the kind and `dsh-tool-web` renders it, so a
+ * new kind is a coordinated change across known packages, not a plugin
+ * extension. Consumers `switch` on `kind` ending in `default: assertNever(...)`
+ * so adding a kind breaks compilation at every consumer until handled. Each arm
+ * stays its own object literal even where fields coincide today, leaving room
+ * for arm-specific fields later (a `pdf` body's `pageCount`).
+ */
+type WebFetchBody =
+ | { readonly kind: 'html'; readonly content: string }
+ | { readonly kind: 'text'; readonly content: string }
+```
+
+## 提供方可用性
+
+提供方的 `available(): boolean` 是一个廉价的本地检查(凭证是否存在、配置是否可解析),**禁止发起网络调用**。它是执行时选择的输入,而非健康检查系统:`search()`/`fetch()` 读取它以选出可用的提供方,选择失败以结构化的 `WebError` 呈现给调用方路由——其 code 和 message 携带可分支的细节(缺失的 id 或有歧义的候选集)。
+
+选择从不依赖注册顺序、配置顺序或 HMR(热模块替换)顺序:一项能力要么有显式的提供方 id(配置 `searchProvider`/`fetchProvider`,或填充同一字段的对应环境变量),要么在恰好只有一个可用提供方注册时自动选择;多个可用提供方且未配置 id 时为 `WEB_PROVIDER_AMBIGUOUS`,而非先注册先赢。
+
+## 错误
+
+`WebError extends HarnessError`([core.md](core.md) 错误分类体系),带有 `code: string`(开放式,与其他 seam 的错误一致——`LlmError`、`SubagentError`),而非封闭联合类型:提供方可以在不修改 `dsh-web` 的情况下抛出自己的 code,消费方必须容忍未知 code。code 按所有者划分。seam 中立的 code 由 `WebService` 选择逻辑和共享契约抛出:`WEB_PROVIDER_UNAVAILABLE`、`WEB_PROVIDER_CONFIGURED_MISSING`、`WEB_PROVIDER_CONFIGURED_UNAVAILABLE`、`WEB_PROVIDER_AMBIGUOUS`、`WEB_DUPLICATE_PROVIDER`(注册时的编程错误,类似 `LlmService` 的 `DUPLICATE_ADAPTER`)、`WEB_ABORTED`,以及 `WEB_PROVIDER_ERROR`(提供方自身故障通过 seam 暴露的兜底 code,包括网络/传输失败——DNS、连接被拒、TLS)。抓取传输层 code 由 `dsh-web-fetch-local` 实现拥有,不同的抓取后端无需抛出它们:`WEB_INVALID_URL`、`WEB_BLOCKED_URL`、`WEB_REDIRECT_BLOCKED`、`WEB_FETCH_TOO_LARGE`、`WEB_FETCH_TIMEOUT`、`WEB_UNSUPPORTED_CONTENT_TYPE`。
+
+## 服务
+
+`WebService` 注册搜索与抓取提供方,以 `WEB_DUPLICATE_PROVIDER` 拒绝重复 id,并在执行时以结构化的选择错误解析提供方。本地抓取后端仅接受 HTTP(S)、拒绝凭证、限制重定向次数、字节数、字符数和时间、对每一跳同源重定向重新校验,并解码 body;展示由工具负责。私有网络阻断尚未实现,因此请勿在可触及敏感内部目标的环境中启用 `web_fetch`。
diff --git a/docs/core-data-structures/workflow.i18n.yaml b/docs/core-data-structures/workflow.i18n.yaml
new file mode 100644
index 0000000000..492a9bea08
--- /dev/null
+++ b/docs/core-data-structures/workflow.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+workflow.md: 8d271b89e71de6f6bef548aa8da61402ef9ada6e
+workflow.zh.md: b8ed699eb52d9f0cef23c513f625de7e82c46c45
diff --git a/docs/core-data-structures/workflow.md b/docs/core-data-structures/workflow.md
index 8d8e47fc79..8d271b89e7 100644
--- a/docs/core-data-structures/workflow.md
+++ b/docs/core-data-structures/workflow.md
@@ -1,5 +1,7 @@
# Workflow
+English | [中文](workflow.zh.md)
+
The workflow seam — an agent running a model-written orchestration SCRIPT that fans out subagents. Like [subagent](subagent.md) it is **one optional capability**, not part of the agent-loop spine, so its vocabulary lives here rather than in [core.md](core.md). Unlike the subagent registry it takes the bash shape: ONE engine implementation per context provides `ctx.workflows`; there is no named-provider registry (a second engine is a plugin swap, not a co-resident).
Interface: [dsh-workflow](../../packages/workflow/workflow) (`ctx.workflows` + the vocabulary below). The implementation is [dsh-workflow-workerthread](../../packages/workflow/workflow-workerthread) (a `node:worker_threads` engine — one worker per run, the script's vm context inside it); the model-facing consumer is [dsh-tool-workflow](../../packages/workflow/tool-workflow). The proposal and rationale: [the dynamic-workflows Agent Note](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md).
diff --git a/docs/core-data-structures/workflow.zh.md b/docs/core-data-structures/workflow.zh.md
new file mode 100644
index 0000000000..b8ed699eb5
--- /dev/null
+++ b/docs/core-data-structures/workflow.zh.md
@@ -0,0 +1,132 @@
+# 工作流
+
+[English](workflow.md) | 中文
+
+工作流 seam:一个 agent(智能体)运行由模型编写的编排脚本(SCRIPT),扇出 subagent。与 [subagent](subagent.md) 一样,它是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此处而非 [core.md](core.md)。与 subagent 注册表不同,它采用 bash 形态:每个上下文只有一个引擎实现提供 `ctx.workflows`;没有命名提供方注册表(第二个引擎是插件替换,而非共存)。
+
+接口:[dsh-workflow](../../packages/workflow/workflow)(`ctx.workflows` + 下文词汇)。实现是 [dsh-workflow-workerthread](../../packages/workflow/workflow-workerthread)(一个 `node:worker_threads` 引擎——每个 run 一个 worker,脚本的 vm 上下文位于其中);面向模型的消费方是 [dsh-tool-workflow](../../packages/workflow/tool-workflow)。提案与设计理由见 [dynamic-workflows Agent Note(agent 决策记录)](../../.agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md)。
+
+源码:[`packages/workflow/workflow/src/types.ts`](../../packages/workflow/workflow/src/types.ts)
+
+## 启动请求
+
+调用方启动 run 时提出的请求。普通工作流工具根据模型的 `{ script, meta, args }` 调用与发起调用的 agent 构建它;专用消费方还可以为该 run 选择一个引擎级 `subagentProvider` 并调低 `maxTotalAgents`,但脚本无法观察或替换这两项策略。`meta` 与 `args` 是普通 JSON 数据(引擎会对 `meta` 做形状校验,并在任何内容运行前大声拒绝——绝不会通过求值脚本文本来获取它)。`parent` 是必填字段——脚本生成的每个子 agent 都归属于它(cwd、谱系与深度通过 [subagent seam](subagent.md) 流转)。
+
+```ts type-equiv
+/**
+ * What a caller asks for when starting a workflow run. `meta` and `args` are
+ * plain JSON DATA by the seam contract (the tool builds both from the model's
+ * schema-validated call; the engine validates `meta`'s shape and rejects loud
+ * before anything runs) — an engine never evaluates script text to obtain
+ * them. `parent` is REQUIRED — every `agent()` the script spawns is
+ * attributed to it (cwd, lineage, depth flow through the subagent seam).
+ */
+interface WorkflowStartRequest {
+ /** The plain-JS script body (top-level await allowed; ends with `return `). */
+ script: string
+ /** The workflow's identity block, as plain JSON data (shape-validated by the engine). */
+ meta: WorkflowMeta
+ /** Optional input exposed verbatim to the script as the `args` global. */
+ args?: unknown
+ /**
+ * Optional engine-wide child-provider override for this run. The workflow
+ * script cannot observe or replace it; omission uses the engine's configured
+ * provider.
+ */
+ subagentProvider?: string
+ /**
+ * Optional per-run total-child ceiling. Implementations reject values above
+ * their deployment ceiling before publishing the run.
+ */
+ maxTotalAgents?: number
+ /** The agent on whose behalf the run executes (parent of every child). */
+ parent: Agent
+ /** Cancels the run when aborted (the tool's `exec.signal`). */
+ signal?: AbortSignal
+}
+```
+
+## 工作流的身份标识:`WorkflowMeta`
+
+作为数据附在启动请求上的身份块(工具的 `meta` 参数;字段词汇与 Claude Code 动态工作流的 meta 块一致)。`phases` 仅用于进度展示:`phase()` 调用与标题匹配,供观察者使用;不暗示任何执行结构。
+
+```ts type-equiv
+/**
+ * The script's identity block, provided as plain JSON data alongside the
+ * script body (the model-facing tool carries it as its `meta` parameter) and
+ * validated by the engine before the body runs. `name`/`description` are
+ * required; the rest is optional annotation. The field vocabulary matches the
+ * Claude Code dynamic-workflows meta block.
+ */
+interface WorkflowMeta {
+ /** Short kebab-case workflow name (display + persistence key). */
+ name: string
+ /** One-line description of what the workflow does. */
+ description: string
+ /** Optional guidance on when this workflow applies (shown in listings). */
+ whenToUse?: string
+ /** Optional phase declarations matched by `phase()` calls. */
+ phases?: WorkflowPhase[]
+}
+```
+
+## 终态结果:`WorkflowResult`
+
+一次运行的结果,由 `WorkflowRun.result` resolve。`value` 是脚本的物化返回值——纯宿主域 JSON 数据(脚本无返回值时为 `null`)——仅在 `completed` 时有意义。`stopReason` 是封闭联合类型(引擎所有;消费方可穷举):`completed` | `cancelled` | `error`。非 `completed` 的原因在 `error` 中携带失败信息,消费方将其映射为 `isError` 工具结果,而非把部分输出当作成功上报。
+
+```ts type-equiv
+/**
+ * The outcome of one run, resolved by {@link WorkflowRun.result}. `value` is
+ * the script's materialized return value (plain host-realm JSON data; `null`
+ * when the script returned `undefined`) — meaningful only for `completed`.
+ * A non-`completed` reason carries the failure in `error`; the consumer maps
+ * it to an `isError` tool result rather than reporting partial output.
+ */
+interface WorkflowResult {
+ /** The script's return value (host JSON data; `null` for no return). */
+ value: unknown
+ /** Why the run settled. */
+ stopReason: WorkflowStopReason
+ /** The failure message (present iff `stopReason` is not `completed`). */
+ error?: string
+ /**
+ * How many `agent()` calls the run accepted over its whole lifetime. On a
+ * graceful settlement this is the script-side count (calls still queued for
+ * a concurrency slot included); on a termination path (grace force-settle,
+ * worker death) it degrades to the host-observed count — calls queued
+ * inside a terminated script are unknowable then.
+ */
+ agentsStarted: number
+}
+```
+
+## 活跃运行:`WorkflowRun`
+
+脚本执行期间消费方持有的句柄。消费方 await `result`,可中途 `cancel`,且必须在每条路径上 `dispose`(资源释放)。`result` 不会 reject:脚本失败以 `stopReason: 'error'` resolve;一旦运行被取消,即使脚本本身永不 settle,它也会在引擎的有界宽限期内 settle(引擎强制以 `cancelled` settle;worker-thread 引擎随后终止脚本的 worker),因此消费方 await `result` 不会在取消后卡死。`dispose()` = cancel + 有界 settle + 等待子 agent 停稳;它不会因脚本卡死而挂起。
+
+```ts type-equiv
+/**
+ * Holder-owned live workflow. `result` never rejects and settles within the
+ * engine's cancellation grace; failures resolve through `stopReason`. Consumers
+ * may cancel and must call idempotent `dispose()` on every path to await bounded
+ * script settlement and child quiescence.
+ */
+interface WorkflowRun {
+ readonly id: WorkflowRunId
+ /** The validated meta block (available before the body runs). */
+ readonly meta: WorkflowMeta
+ readonly result: Promise
+ /** Cancel the run: children abort, pending hooks reject, the script dies at its next await (or is force-settled at the grace). */
+ cancel(reason?: string): void
+ /** Cancel + bounded-grace settle; safe to call on every path (idempotent). */
+ dispose(): Promise
+}
+```
+
+## 失败纪律:`WorkflowError.fatal`
+
+脚本内部的钩子误用:错误参数、未知或延迟的 `agent()` 选项、超出[结构化输出子集](../../packages/core/tools/README.md)的 schema、触发的上限、seam 启动失败、取消,都会抛出 `fatal: true` 的 `WorkflowError`。`parallel()`/`pipeline()` 组合器对 fatal 错误直接重新抛出,而非将该项映射为 `null`:一个拼写错误的选项必须让脚本大声失败,绝不能消融为看似普通子 agent 失败的结果。逐项的 `null` 保留给子运行失败(非 `completed` 的 stop reason)和阶段内的普通脚本错误。
+
+## 事件
+
+`workflow/*` 事件(`workflow/start`、`workflow/phase`、`workflow/log`、`workflow/agent-start`、`workflow/agent-end`、`workflow/end`,见[事件目录](../cordis-catalog/events.md))是**仅供观察**的 emit,携带数据快照:每个 payload 以 `WorkflowRunInfo`(id + meta)开头,而非活跃的 `WorkflowRun`,因此订阅者无法获得 `cancel`/`dispose`;`workflow/end` 刻意省略 result value(观察结果的监听器不得收到调用方 result 的可变别名)。每次 emit 对每个监听器隔离:抛出异常的订阅者被记录日志但不传播,不会饿死在它之后注册的监听器;每个监听器收到自己的 payload 克隆,因此修改它既不会损坏引擎也不会影响其他监听器。这种隔离方式与 `subagent/start`/`subagent/end` 一致。
diff --git a/docs/defensive-patterns.i18n.yaml b/docs/defensive-patterns.i18n.yaml
new file mode 100644
index 0000000000..96c938a74f
--- /dev/null
+++ b/docs/defensive-patterns.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+defensive-patterns.md: c69094db461048f5dbca5f8bdd1fb5581b08a962
+defensive-patterns.zh.md: eb57f035ad0bd67e62e285d451502d41e4efc2bc
diff --git a/docs/defensive-patterns.md b/docs/defensive-patterns.md
index a6fe43ef69..c69094db46 100644
--- a/docs/defensive-patterns.md
+++ b/docs/defensive-patterns.md
@@ -1,5 +1,7 @@
# Defensive patterns
+English | [中文](defensive-patterns.zh.md)
+
Hard-won bug-class rules: each pattern below is a class of defect that actually shipped or nearly shipped here, stated as the rule that prevents its recurrence. Read this before writing lifecycle, concurrency, subprocess, or teardown code. Test-tier counterparts (real entry path, world-verification, resource ownership) are in [testing.md](testing.md).
## Report orthogonal outcomes independently
diff --git a/docs/defensive-patterns.zh.md b/docs/defensive-patterns.zh.md
new file mode 100644
index 0000000000..eb57f035ad
--- /dev/null
+++ b/docs/defensive-patterns.zh.md
@@ -0,0 +1,29 @@
+# 防御性模式
+
+[English](defensive-patterns.md) | 中文
+
+来之不易的缺陷类别规则:下面每条模式都是本项目实际发布或差点发布的一类缺陷,以防止其复发的规则形式陈述。在编写生命周期、并发、子进程或清理代码之前请先阅读本文。测试层面的对应规则(真实入口路径、world 验证、资源归属)见 [testing.md](testing.md)。
+
+## 正交结果独立上报
+
+一个结果可以同时具有多重性质:进程可能既超时又以 exit 0 退出,因为它捕获了信号。每个独立事实(`timedOut`、`signal`、`exitCode`)都应独立暴露;切勿将某个 flag 的上报嵌套在另一个 flag 的分支内,否则调用方会把一次被截断的运行误读为正常成功。
+
+## 跨 seam 契约两侧都要遵守
+
+当一个接口文档记录了两种合法的信号方式时——例如适配器可以通过从 `stream()` 抛出异常来报告失败,也可以通过以 `finish {kind:'error'|'aborted'}` 分片结束流来报告——消费方必须同时处理两种路径,而不是只处理第一个实现恰好使用的那种。依赖库的适配器可能无法在流中途抛出异常,只能走带内路径;如果 agent loop(智能体循环)只捕获抛出的异常,就会把提供方的 401 错误变成一个正常完成的轮次。请在类型定义处记录契约;通过真实消费方测试每个分支。
+
+## 异步状态不是同步状态
+
+`agent.followup()` 不会在返回前翻转状态;后台任务的完成与轮次边界存在竞争;`reader.close()` 在 EOF 和 dispose(资源释放)两种情况下都会触发。切勿基于一个刚刚请求的状态来控制流程——应以实际触发的事件/promise(`agent/status`、`task.done`)驱动生命周期,并观察状态转换(先看到 `running` 再看到 `idle`),而不是把状态当作逐次 `followup()` 的结果:多次排队的 `followup()` 会在同一个 `running` 区间内连续运行多个轮次,而取消或资源释放可能丢弃尚未启动的项。这条守则是双向的:如果等待的转换永远不会发生(EOF 时没有提交过任何工作 → 永远不会进入 `running`),等待就会挂起——请显式处理「无需等待」的分支。
+
+## Dispose 必须达到完全停稳,而不仅仅是请求停止
+
+一个清理流程如果发出 kill/abort 后就返回、而不等待工作实际停止,就会留下孤儿进程。请让清理逻辑异步化并 await 子进程退出(kill → await `done`),并在 kill 之前关闭监听器/通知注册表,使迟到的完成事件保持静默。测试应证明 dispose 确实等待了(`await fiber.dispose()` 之后 pid 已不存在),而不仅仅是进程最终会死。
+
+## 在边界处包容回调异常
+
+用户提供的监听器如果抛出异常,不得导致它所在的 promise 被 reject,也不得饿死排在它后面的监听器。请用 try/catch 包裹分发循环并记录日志;一个行为不当的订阅者绝不能破坏核心生命周期。
+
+## 绝不将环境变量或可预测路径暴露给不可信输出
+
+spawn 的命令应获得一份经过清洗的 env(去除 `*KEY*`/`*SECRET*`/`*TOKEN*`),使 harness 凭证无法泄漏到输出、`env` 或溢出文件中。临时/溢出文件应使用私有(0700)目录、随机文件名和排他的仅所有者可访问打开方式(`'wx'`、`0o600`)——可预测的全局可读路径会招致符号链接竞争和信息泄露。
diff --git a/docs/glossary.i18n.yaml b/docs/glossary.i18n.yaml
new file mode 100644
index 0000000000..b63e41b87b
--- /dev/null
+++ b/docs/glossary.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+glossary.md: 0270a2d0dba558483e8e458a932a27b0151f2c93
+glossary.zh.md: ed3009a054815f1c7165fc322e44cc9521527643
diff --git a/docs/glossary.md b/docs/glossary.md
index e290543d2c..0270a2d0db 100644
--- a/docs/glossary.md
+++ b/docs/glossary.md
@@ -1,5 +1,7 @@
# Glossary
+English | [中文](glossary.zh.md)
+
Domain vocabulary for the DeepSeek Harness SDK uses one canonical term per concept. Terms link to their entries with standard Markdown anchors; implementation detail stays in package READMEs and Agent Notes.
FIXME(glossary-completeness): Expand this glossary before the first release so it covers the SDK's other core and capability subsystems, not only agent scope.
@@ -19,7 +21,7 @@ FIXME(glossary-completeness): Expand this glossary before the first release so i
## goal
- **goal** — one durable completion objective attached to an existing session, with a revisioned `active` / `paused` / `blocked` / `complete` phase and a goal-round cap; `blocked` retains a policy code and explanation. A goal is state, not a scheduler or a separate conversation; the session log remains its source of truth.
-- **goal round** — one continuation cycle admitted for the current goal. The same-session driver materializes a goal round as one goal-sourced [turn](#turn), which can contain multiple steps; unrelated human turns in the same session do not consume the goal-round cap.
+- **goal round** — one continuation cycle admitted for the current goal. The same-session driver materializes a goal round as one goal-sourced [turn](#turn), which can contain zero or more steps; unrelated human turns in the same session do not consume the goal-round cap.
- **goal activation** — process-local permission for a continuation consumer to admit another goal round. Activation is either `armed` or `disarmed`; it is deliberately absent from durable replay, so resume and fork require a later human-authorized resume mutation through `/goal` or the model tool before automatic work.
## human command
@@ -31,7 +33,7 @@ FIXME(glossary-completeness): Expand this glossary before the first release so i
## loop hierarchy
- **turn** — one drain of admitted input in a session, ending after the model and its tools stop or a terminal policy intervenes.
-- **step** — one model request plus the tool executions caused by its response; a turn contains one or more steps.
+- **step** — one model request plus the tool executions caused by its response; a turn contains zero or more steps.
- **round** — an outer policy iteration containing a turn, such as a [goal round](#goal-round) or one fresh-agent Ralph attempt. Round counters belong to that policy and do not count every turn in a session.
## Ralph
diff --git a/docs/glossary.zh.md b/docs/glossary.zh.md
new file mode 100644
index 0000000000..ed3009a054
--- /dev/null
+++ b/docs/glossary.zh.md
@@ -0,0 +1,43 @@
+# 术语表
+
+[English](glossary.md) | 中文
+
+DeepSeek Harness SDK 的领域词汇为每个概念规定一个规范术语。各术语通过标准 Markdown 锚点链接到相应条目;实现细节留在各包(package)的 README 与 Agent Note(agent 决策记录)中。
+
+FIXME(glossary-completeness): 首次发布前扩充本术语表,使其覆盖 SDK 的其他核心与能力子系统,而非仅限于 agent scope。
+
+## agent-scope
+
+- **scope**:按 agent(智能体)划分的注册单位。一项贡献(工具、提示词片段、变量、限制、监听器)要么是*全局的*(对所有 agent 可见),要么是*有范围的*(归属于恰好一个 [scope key](#scope-key))。只有两层,扁平结构:有范围的注册不会向下继承给 subagent;子树行为通过 [lineage](#lineage) 数据表达,从不通过 scope 结构。
+- **scope key**:scope 的不透明标识,按对象同一性比较。harness 约定:一个活跃的 agent 就是其自身 scope 的 key。
+- **agent 上下文(`agent.ctx`)**:agent 的有范围上下文;通过它进行的注册既是 scope 可见的,也是 scope 生命周期的(同一事实决定两者),其上的监听器参与该 agent 的 scope 过滤分发。注册表主体事件可以在各自的事件契约下保持故意不过滤。
+- **scope carrier**:scope 过滤分发所携带的 `thisArg`(由 `scopeTarget` 构建);其过滤器放行无标签监听器加上主体自身的监听器。*无主体*的 carrier(没有 key)只放行无标签监听器。
+- **scoped dispatch**:规则是:关于某个 agent 活动的事件以该 agent 的 carrier 进行分发。关于注册表本身的事件(如「一个工具被添加了」)属于*注册表主体*事件,保持不过滤。
+- **shadowing**:最具体者胜出的名称解析:一个有范围的工具/片段/变量仅在该 scope 内替换同名的全局对应项。这是按 agent 定制 persona 和按 agent 定制工具变体的机制。
+- **restriction / scope-local 注册**:restriction(`tools.restrict`)为单个 scope 过滤全局工具表面(多个 restriction 取交集组合);scope-local 注册在过滤之后合并。被过滤掉的全局工具既不出现在提示词中,也拒绝执行,与不存在的工具无法区分。
+- **setup window**:创建者组装 agent 有范围世界的创建时隙(`CreateAgentOptions.setup`):在 scope 和 agent 对象已存在、但 agent 或会话尚未发布、`agent/session-start` 尚未触发、首次提示词尚未组装之前。setup 只做注册,从不驱动 agent。
+- **lineage**:以数据形式携带的父子关系事实(`parentSession`、持久的 `delegationDepth`、运行时 `subagentDepth`);从不影响可见性。
+
+## 目标
+
+- **目标**:附着在现有会话上的单个持久完成目标,带有按修订号演进的 `active` / `paused` / `blocked` / `complete` 阶段和 Goal Round 上限;`blocked` 保留策略代码与说明。目标是一种状态,不是调度器,也不是一段独立对话;会话日志仍是其真源。
+- **Goal Round**:为当前目标接纳的一次续行周期。同会话驱动器将 Goal Round 具体化为一个来源为目标的[轮次](#turn),其中可包含零个或多个步骤;同一会话中无关的人类轮次不消耗 Goal Round 上限。
+- **目标激活**:续行消费方接纳下一个 Goal Round 的进程本地权限。激活态为 `armed` 或 `disarmed`;它有意不参与持久回放,因此恢复和 fork 后,必须由人类随后通过 `/goal` 或模型工具授权恢复变更,自动工作才可开始。
+
+## 人类命令
+
+- **人类命令**:以斜杠开头的指令,由面向人类的适配器通过 `ctx.commands` 解释并执行,不会成为模型消息。它既不同于面向模型的工具,也不同于通过 `ctx.bash` 执行 shell 命令。
+- **命令平面**:由 UI 适配器与命令插件拥有的发现、解析、分发、取消和结果渲染。除非处理器另行改变持久领域,否则命令输出属于 UI 状态。
+- **目标命令**:`/goal` 是由 `dsh-command-goal` 提供的人类命令;它直接观察或更改当前目标,而目标领域拥有每条持久且模型可见的记录。
+
+## 循环层级
+
+- **轮次**:会话中一次对已接纳输入的排空过程,在模型及其工具停止工作或终止策略介入后结束。
+- **步骤**:一次模型请求,以及由模型响应引发的工具执行;一个轮次包含零个或多个步骤。
+- **Round**:承载一个轮次的外层策略迭代,例如一个 [Goal Round](#goal-round) 或一次使用全新 agent 的 Ralph 尝试。Round 计数器归该策略所有,并不统计会话中的每个轮次。
+
+## Ralph
+
+- **Ralph 循环**:一次面向不可变目标的前台全新 agent 工作流运行。它是由工作流和 subagent 原语组合而成的面向模型的工具策略,不是同会话目标、agent loop(智能体循环)模式、调度器或通用工作流脚本功能。
+- **Ralph Round**:[Ralph 循环](#ralph-loop)中的一个全新子会话。子会话不接收父会话或此前子会话的对话种子;共享工作区和一份有界的 [Ralph 交接](#ralph-handoff)承载跨 Round 的状态。
+- **Ralph 交接**:从一个仍需继续的 Ralph Round 传给下一个 Ralph Round 的规范化、有界结构化报告,包含状态、摘要、证据、后续步骤和阻塞说明。它补充共享工作区,而不取代工作区的权威地位。
diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml
index d48ab803ee..602699a178 100644
--- a/docs/i18n/README.i18n.yaml
+++ b/docs/i18n/README.i18n.yaml
@@ -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
-README.md: 430c499afbbfb786928276f6348cc0cedf14f94d
-README.zh.md: 7ac7f4a2a8983c753def61df6f6d86a26405a3a0
+README.md: 77d7b3210216c7c12d7d06b1ed16396d02ef1d16
+README.zh.md: de15fc3b5f30c1280ce6b38c1afd2475be7f9671
diff --git a/docs/i18n/README.md b/docs/i18n/README.md
index 430c499afb..77d7b32102 100644
--- a/docs/i18n/README.md
+++ b/docs/i18n/README.md
@@ -40,7 +40,7 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co
**Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them):
-- `docs/cordis-catalog/`, `docs/tool-catalog/`, `docs/config-catalog.md`, `docs/persistence-catalog.md`, and `docs/module-graph.md` — generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list.
+- `docs/cordis-catalog/`, `docs/tool-catalog/`, `docs/config-catalog.md`, `docs/persistence-catalog.md`, `docs/module-graph.md`, `docs/agent-lifecycle.md`, `docs/capability-seams.md`, `docs/event-producer-consumer.md`, `docs/graph-atlas.md`, and `docs/tool-execution-pipeline.md` — generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list.
- `docs/AGENTS.md` and `.agents/notes/**/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`.
- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.
- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.
diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md
index 7ac7f4a2a8..de15fc3b5f 100644
--- a/docs/i18n/README.zh.md
+++ b/docs/i18n/README.zh.md
@@ -40,7 +40,7 @@
**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):
-- `docs/cordis-catalog/`、`docs/tool-catalog/`、`docs/config-catalog.md`、`docs/persistence-catalog.md` 与 `docs/module-graph.md`:生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。
+- `docs/cordis-catalog/`、`docs/tool-catalog/`、`docs/config-catalog.md`、`docs/persistence-catalog.md`、`docs/module-graph.md`、`docs/agent-lifecycle.md`、`docs/capability-seams.md`、`docs/event-producer-consumer.md`、`docs/graph-atlas.md` 与 `docs/tool-execution-pipeline.md`:生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。
- `docs/AGENTS.md` 与 `.agents/notes/**/AGENTS.md`:agent 指令,与根 `AGENTS.md` 一样只以英文维护。
- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。
- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。
diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md
index 74ce7ad969..ae28258b9b 100644
--- a/docs/i18n/terminology.md
+++ b/docs/i18n/terminology.md
@@ -52,6 +52,7 @@
| loader | loader | | | |
| manifest | manifest | manifest(元数据清单) | | |
| monorepo | monorepo | | | |
+| Round | Round | | 回合、目标回合、Ralph 回合 | 外层策略使用 Round 时,领域层级为 Session > Round > Turn(轮次) > Step(步骤);Round 是可选的外层策略迭代,并非每个会话轮次都具有的通用层级。Goal Round 与 Ralph Round 均保留英文。一个 Round 承载一个轮次,步骤隶属于该轮次;明确的零步骤轮次仍保持原义。 |
| schema | schema | | | |
| schema DSL | schema DSL | | | |
| seam | seam | | | 与 `extension point` 是不同概念;根据具体语境,可译为`服务边界`或`可替换点` |
@@ -103,10 +104,12 @@
| durability | 持久性 | | | |
| feature requirement | 功能依赖 | | | 功能或功能选项通过 `requires` 声明的关系 |
| enforcement frontier | 执行红线 | | 强制边界 | i18n 配对机制用语:manifest `required` 清单所划的门禁生效范围;与金标样例(style-samples ⑦)一致 |
+| ergonomics | 易用性 / 开发体验 | | 人体工学 | API 或面向模型的接口用「易用性」;工具链或开发者工作流用「开发体验」 |
| event | 事件 | | | |
| event log | 事件日志 | | | |
| event stream | 事件流 | | | |
| event-sourced | 事件溯源 | | | 沿用 DDD 社区通行译法 |
+| Executive summary | 摘要 | | | 事故复盘标题用语 |
| executor | 执行器 | | | |
| expected output | 预期输出 | | 金标 | 指 snapshot 比较产物;翻译语料的人工校准样例不在此列 |
| extension | 扩展 | | | |
@@ -131,11 +134,14 @@
| mod | 模组 | | | |
| model provider | 模型提供方 | | | |
| module | 模块 | | | |
+| non-escalation | 非升权 | | 非升级、不可升级 | 仅用于安全与权限语境,指主体不得获得超出既有授权的权限;普通升级不适用此行 |
| npm dependency | NPM 依赖 | | | `package.json` 中的包关系;`dependencies`、`devDependencies` 等字段保持原样 |
+| opt-out ratio | opt-out 比例 | | 退出检查比例 | |
| orphan | 遗留 | | 孤儿、孤立 | 指英文源已不存在的 `.zh.md`(如「遗留译文」);进程语境按 OS 惯用语译「孤儿进程」 |
| orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 |
| package | 包 | 包(package) | | 指 npm 包(`@deepseek-ai/dsh-*`);`package.json` 等代码标识保持原样 |
| pairing | 配对 | | | |
+| parent-subset grants | 父级子集授权 | | 父集合授权 | 指授权范围仅限于父级所持授权的子集 |
| peer dependency | 对等依赖 | 对等依赖(peer dependency) | | |
| permission | 权限 | | | |
| persistence | 持久化 | | | |
@@ -145,12 +151,14 @@
| provider | 提供方 | | | |
| provider-neutral | 提供方无关 | | | |
| quality gate | 质量门禁 | | | |
+| quiescence | 完全停稳 | | 静默、静止状态 | 指生命周期工作全部结算后的状态 |
| reasoning | 推理 | 推理(reasoning) | | 需要和 `inference` 区分时保留英文括注 |
| reasoning_content | 思考内容 | | | |
| registry | 注册表 | | | |
| replay | 回放 | | | |
| resume | 恢复 | | | |
| runtime | 运行时 | | | |
+| same-world subprocess | 与宿主共享文件系统和内核的子进程 | | 同世界子进程 | |
| sandbox | 沙箱 | | | |
| service | 服务 | | | |
| serving surface | 对外服务接口 | | | |
@@ -167,6 +175,7 @@
| stream | 流 | | | |
| streaming | 流式输出 | | | |
| structural signature | 结构签名 | | | i18n 配对机制用语:门禁比对两侧文件时提取的有序结构序列(标题层级、代码块、列表等) |
+| Summary | 概述 | | | 事故复盘标题用语 |
| system prompt | 系统提示词 | | | |
| taxonomy | 分类体系 | | | |
| token usage | token 用量 | | | |
diff --git a/docs/postmortem/0001-acp-default-export-drops-inject.i18n.yaml b/docs/postmortem/0001-acp-default-export-drops-inject.i18n.yaml
new file mode 100644
index 0000000000..d48af64198
--- /dev/null
+++ b/docs/postmortem/0001-acp-default-export-drops-inject.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+0001-acp-default-export-drops-inject.md: ab3efc880cb5290dc149b6bacb276ccf581968c1
+0001-acp-default-export-drops-inject.zh.md: 763b2e6230cf42dcd6985df3d7e2da8766176fd5
diff --git a/docs/postmortem/0001-acp-default-export-drops-inject.md b/docs/postmortem/0001-acp-default-export-drops-inject.md
index 10e88c390c..ab3efc880c 100644
--- a/docs/postmortem/0001-acp-default-export-drops-inject.md
+++ b/docs/postmortem/0001-acp-default-export-drops-inject.md
@@ -1,5 +1,7 @@
# Post-mortem 0001: ACP server crashed on connect — `export default` dropped the plugin's `inject`
+English | [中文](0001-acp-default-export-drops-inject.zh.md)
+
Status: resolved (fix in PR #41 `feat/acp-2-bridge`)
## Executive summary
diff --git a/docs/postmortem/0001-acp-default-export-drops-inject.zh.md b/docs/postmortem/0001-acp-default-export-drops-inject.zh.md
new file mode 100644
index 0000000000..763b2e6230
--- /dev/null
+++ b/docs/postmortem/0001-acp-default-export-drops-inject.zh.md
@@ -0,0 +1,113 @@
+# 事故复盘(postmortem) 0001:ACP(Agent Client Protocol)服务器在连接时崩溃——`export default` 丢弃了插件的 `inject`
+
+[English](0001-acp-default-export-drops-inject.md) | 中文
+
+Status: resolved (fix in PR(Pull Request) #41 `feat/acp-2-bridge`)
+
+## 摘要
+
+两个集成错误在单元测试全覆盖的情况下仍然导致 ACP 崩溃:一个 default export 使 Loader 丢弃了 `inject`,一个经 traceable 代理的可选服务查找在 shadow 边界上失败。手动挂载的测试绕过了这两条路径。修复方案增加了无需 API key 的真实 Loader 覆盖率,并为插件导出和可选服务访问制定了包(package)级规则。
+
+## 概述
+
+ACP 服务器(`examples/acp-agent`、`@deepseek-ai/dsh-acp`)在真实编辑器(Zed)连接的瞬间崩溃:第一个 `session/new` 请求返回 `Internal error: cannot get property "agents" without inject`,`session/load` 对 `sessionPersistence` 返回同样的错误。尽管有 178 个绿色单元测试和 100% 行覆盖率,bridge 在生产环境中完全无法工作。两个独立的 bug 隐藏在同一个错误字符串背后,测试套件之所以两个都没捕获,原因也相同:所有测试都通过一条不会触及插件真实加载方式和服务真实解析方式的路径来挂载插件。
+
+## 影响
+
+ACP 服务器无法创建或加载任何一个会话——而这正是编辑器最先调用的两个 RPC。任何将 agent(智能体)接入 Zed 的人都会立即遭遇硬性失败。无数据丢失(崩溃前没有任何内容被持久化);代价完全是「功能不可用」加上两次定位原因的调试时间。
+
+## 时间线
+
+- bridge(RFC 010)落地时附带完整的单元测试套件(codec、内存传输、基于属性的协议形状测试、失败路径、HMR(热模块替换))、一个需要 key 的真实 API e2e 测试,以及一个无需 key 的 stdout 纯净性 e2e 测试。全部绿色,100% 覆盖率。
+- 真实 Zed 会话在 `session/new` 上立即失败,报错 `cannot get property "agents" without inject`。
+- 调查最初追踪了一个 Cordis「traceable/shadow」理论(看似合理,且该机制确实存在——见 Bug #2),随后在 vendor 的 `reflect.ts` 中对实际 fiber 遍历做了插桩,并运行了真实子进程。trace 显示 throw 发生在 `apply()` 第 179 行、*插件加载时*,位于 ROOT fiber 且没有 shadow——推翻了 shadow 理论对 `session/new` 的解释。
+- 找到根因 #1:一行多余的 `export default apply`。删除后 `session/new` 修复。
+- 删除后暴露了 Bug #2:`session/load` 仍然在 `sessionPersistence` 上抛错——这是一个真正不同的机制(shadow 遍历),通过隔离修复并重新运行真实子进程得到确认。
+
+## 根因 #1——`export default apply` 丢弃了插件的 `inject`(导致 `session/new` 崩溃)
+
+`packages/ui/acp/src/index.ts` 是一个*命名空间插件*:它将 `name`、`inject`、`Config` 和 `apply` 作为独立的命名导出——与仓库中其他所有插件(`invariants`、`llm-deepseek`、`tool-bash`、`tui` 等)形状相同。但它*还*多了一行其他插件都没有的代码:
+
+```ts ignore-check
+export const name = 'acp'
+export const inject = ['agents', 'sessions', 'sessionPersistence']
+export function apply(ctx: Context, config: AcpConfig): void { /* … */ }
+// …
+export default apply // ← the bug
+```
+
+当插件从 `cordis.yml` 加载时,Cordis Loader 通过 `Loader.unwrapExports`(`vendor/loader/src/index.ts`)对导入的模块进行规范化:
+
+```ts ignore-check
+unwrapExports(exports: any) {
+ if (isNullable(exports)) return exports
+ exports = exports.default ?? exports // ← prefers `.default`
+ if (!exports.__esModule) return exports
+ return exports.default ?? exports
+}
+```
+
+存在 default export 时,`exports.default ?? exports` 解析为**裸 `apply` 函数**。裸函数没有 `inject`、没有 `name`、没有 `Config` 属性——这些作为*兄弟*命名导出存在于模块命名空间上,而 unwrap 到 `.default` 把整个命名空间丢弃了。Loader 随后基于空的 `inject` 构建了插件的 fiber。
+
+因此 `apply` 在一个**没有注入任何服务**的 fiber 中运行。第一行 `const agents = ctx.agents` 遍历 fiber 树(ROOT → Include → Loader → ROOT),在所有 fiber 的 store 中都找不到 `agents`,到达根 fiber(`runtime === null`)后抛出 `cannot get property "agents" without inject`。崩溃发生在*加载时*,而非后续的请求处理器中——请求只是恰好触发了加载。
+
+**修复:** 删除 `export default apply`。Loader 随后使用模块命名空间,正确识别 `inject`/`name`/`Config`,`apply` 在一个真正授予了声明服务的 fiber 中运行。
+
+## 根因 #2——可选服务读取通过 traceable shadow 触发 inject 守卫(导致 `session/load` 崩溃)
+
+修复 #1 后,`session/new` 正常工作,但 `session/load` 仍然抛出 `cannot get property "sessionPersistence" without inject`。这个问题*确实*是 Cordis 的 traceable/shadow 机制,值得精确理解。
+
+`session/load` 调用 `agents.resume(...)`,后者委托给 `AgentLoop.resume()`,其中读取了 `this.ctx.sessionPersistence`。`AgentLoop` 的 `static inject` 故意不包含 `sessionPersistence`——注入它会导致非持久化的演示永远挂起,等待一个永远不会加载的后端。该服务由一个独立的兄弟插件/fiber 提供,以机会性方式读取。
+
+Cordis 中的服务访问通过上下文代理(`vendor/cordis/src/reflect.ts`)进行。当通过从外部 fiber 获取的 *traceable 代理*调用服务方法时(此处:bridge fiber 调用 `ctx.agents.resume`,注册表返回 `this.factory`——即 `AgentLoop`——重新包装为绑定到调用方的新 traceable 代理),`createShadowMethod`(`vendor/cordis/src/utils.ts`)将 `this` 重新绑定到一个 *shadow* 对象,其 `ctx` 携带 `[symbols.shadow]` 指向 `AgentLoop` 自身的构造上下文。在 `resume` 内部,`this.ctx.sessionPersistence` 的解析从 shadow 的 fiber 开始遍历:
+
+```ts ignore-check
+// reflect.ts get handler
+let fiber = (ctx[symbols.shadow] as Context ?? ctx).fiber // ← starts at AgentLoop's fiber
+while (true) {
+ const impl = fiber.store?.[prop]
+ if (impl) return getTraceable(ctx, impl.value)
+ if (prop in fiber.inject) { /* inactive-context error */ }
+ if (!fiber.runtime) throw error // ← reached root, throw
+ if (fiber.parent[symbols.isolate][prop] !== key) throw error
+ fiber = fiber.parent.fiber // ← ancestor-only
+}
+```
+
+遍历**仅向祖先方向**进行。`sessionPersistence` 既不在 `AgentLoop` 的 fiber store 中(不在其 `static inject` 中),也不在通往 root 的任何祖先上(它位于一个*兄弟*分支),因此遍历到达根 fiber 后抛错。
+
+为什么内存中的 `AgentLoop` 恢复测试没有捕获这个问题?因为它们从测试代码直接调用 `ctx.agents.resume(...)`——*在任何插件 fiber 之外*。此时 `ctx.fiber.runtime` 为 `null`,代理处理器走了一条提前绕过的路径:
+
+```ts ignore-check
+if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false) // ← direct global-store lookup, no fiber walk
+```
+
+`ctx.reflect.get(name, false)` 是基于 isolate symbol 的全局服务 store 直接查找——完全忽略 fiber 拓扑,能找到服务。因此从顶层测试读取可以成功;而从真实插件 fiber 内部、经由 shadow 到达时则抛错。bridge 恰好是后者。
+
+**修复:** 使用 `ctx.get('sessionPersistence')` 读取可选服务,该方法使用全局 isolate-keyed store 同时保留活跃状态检查。对于插件声明注入集中的服务,直接属性读取仍然适用。
+
+## 为什么所有测试都没有捕获(真正的失败)
+
+两个 bug 共享同一个流程缺口:**没有任何测试通过插件的真实加载路径或真实调用拓扑来驱动它。**
+
+- 内存 harness 通过手动构建插件对象来挂载 bridge:`ctx.plugin({ name, inject, apply })`。这手动提供了 `inject`,因此永远无法复现 Bug #1——`unwrapExports` 只被 *Loader* 调用,`ctx.plugin` 从不调用它。即使 `ctx.plugin(NamespaceImport)` 也无法捕获。
+- 同一个 harness 将所有内容平铺挂载在一个根上下文上,因此从中触达的 `AgentLoop` 恢复要么运行在顶层(`!runtime` 绕过),要么通过一个 origin 仍然解析在 root 上的 shadow——掩盖了 Bug #2 的祖先遍历失败。
+- 唯一的无 key e2e 发送 `initialize` 并检查 stdout 纯净性。`initialize` 从不触达 factory,因此两个 bug 都安然通过。
+- 唯一驱动 `session/new`/`session/load` 的测试需要 key 才能运行,因此 CI(无 key)跳过了它——而本地它之所以「通过」,只是因为一个陈旧的已构建 `lib/`(包含旧代码)恰好满足了模块解析。
+
+100% 行覆盖率始终满足。覆盖率证明代码行*被执行过*;它不能说明功能是否*按交付方式正常工作*。
+
+## 新增的防护措施
+
+- **删除 `export default apply`**(`packages/ui/acp/src/index.ts`)——Bug #1 的修复。
+- **`AgentLoop.resume` 使用 `this.ctx.get('sessionPersistence')`**(`packages/core/agent-loop/src/index.ts`)——Bug #2 的修复,附注释说明 shadow 遍历陷阱。
+- **无需 key 的 `session/new` e2e,通过真实 stdio 运行**(`examples/acp-agent/tests/acp.e2e.ts`):以子进程方式通过真实 Loader 启动示例,并断言 `session/new` 正常返回。无需 API key 即可在 Bug #1 上大声失败。已验证恢复 `export default apply` 时测试失败。
+- **e2e spawn 中设置 `TSX_TSCONFIG_PATH`**:子进程从临时 cwd 运行,tsx 无法通过向上搜索找到仓库根的 tsconfig `paths` 映射——因此 dsh-* 的 import 静默回退到已构建的 `lib/`。将 tsx 指向仓库 tsconfig 使解析不依赖 cwd,确保测试运行的是*源码*而非可能陈旧的构建产物。
+- **[docs/testing.md](../testing.md) 规则**:「测试真实入口路径」,行覆盖率不等于行为覆盖率——将这一教训编纂为所有未来插件的规则。
+
+## 经验教训
+
+- 命名空间插件与 default export 在 Cordis Loader 下互斥。选择命名空间形式(`name`/`inject`/`Config`/`apply`),不要添加 `export default`——`unwrapExports` 会丢弃命名空间。
+- 对于插件机会性读取但未在 `static inject` 中声明的服务,使用 `ctx.get(name)`,绝不使用 `ctx.`。属性代理通过仅向祖先方向的 fiber 遍历解析,经由外部 shadow 时会失败;`ctx.get(name)` 是拓扑无关的查找(且默认严格——非活跃后端读取为 `undefined`,而非在 teardown 过程中被交出)。
+- 手动构建插件的测试无法验证插件的加载方式。至少一个测试必须端到端地驱动真实的 Loader/export 路径。当核心操作不调用模型时,该测试无需 API key——因此它属于 CI,而非 key 门控之后。
+- 相信 trace,不要相信理论。优雅的 shadow 解释是真实的,但它是*第二个* bug;*第一个*是一行导出错误,在数小时看似合理但实际错误的推理之后,一个 fiber 遍历的 `console.error` 在几分钟内就找到了它。
diff --git a/docs/postmortem/0002-js-expression-disabled-filesystem-tools.i18n.yaml b/docs/postmortem/0002-js-expression-disabled-filesystem-tools.i18n.yaml
new file mode 100644
index 0000000000..2aad7141c5
--- /dev/null
+++ b/docs/postmortem/0002-js-expression-disabled-filesystem-tools.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+0002-js-expression-disabled-filesystem-tools.md: 30ff9d920821a8d55c4bea5f120f1aeeca6634b3
+0002-js-expression-disabled-filesystem-tools.zh.md: b103ec6de5d6d6406ba48ec34f6ebb479e472352
diff --git a/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md b/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md
index ccdb725bfb..30ff9d9208 100644
--- a/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md
+++ b/docs/postmortem/0002-js-expression-disabled-filesystem-tools.md
@@ -1,5 +1,7 @@
# Post-mortem 0002: Filesystem snapshot tools were permanently disabled
+English | [中文](0002-js-expression-disabled-filesystem-tools.zh.md)
+
Status: resolved
## Executive summary
diff --git a/docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md b/docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md
new file mode 100644
index 0000000000..b103ec6de5
--- /dev/null
+++ b/docs/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md
@@ -0,0 +1,47 @@
+# 事故复盘(postmortem) 0002:文件系统快照工具被永久禁用
+
+[English](0002-js-expression-disabled-filesystem-tools.md) | 中文
+
+Status: resolved
+
+## 摘要
+
+ACP(Agent Client Protocol)示例试图通过 `disabled: !!js ...` 有条件地启用文件系统插件,但 Cordis 仅在插件 `config` 内部对 JavaScript 表达式求值。原始的表达式对象为 truthy,因此文件系统栈始终处于禁用状态。快照刷新随后将 `UNKNOWN_TOOL` 结果接受为新的预期输出。修复方案改用显式的文件系统 overlay,并增加了静态配置守卫和快照结果守卫。
+
+## 概述
+
+默认的 ACP 组合有意只启用 bash,因为其沙箱无法约束进程内的文件系统提供方。文件系统快照场景仍然需要 `read`、`write` 和 `edit`,因此这些插件被放在默认的 `cordis.yml` 中,并附带一个 `disabled` 表达式,意图仅在全权限启动和快照模式下启用它们。
+
+Cordis Include 将每个 `!!js` 标量解析为一个表达式对象。Loader 递归地对插件的 `config` 进行插值,但直接消费 `disabled` 等入口元数据。因此每个文件系统入口看到的都是一个 truthy 对象,在所有模式下均保持禁用。
+
+## 影响
+
+七个文件系统场景和一个混合工作区编辑场景调用了注册表中不存在的工具。其结构化会话日志携带 `ToolNotFoundError`(code 为 `UNKNOWN_TOOL`),stdout 渲染出通用的失败工具卡片。快照套件通过了,因为结构化会话日志和 stdout 渲染出的通用失败工具卡片均与刷新后的 fixture(测试前置数据)匹配;它证明的是回归的确定性回放,而非文件系统行为的正确性。
+
+实际运行的受限默认模式并未获得意外的文件系统访问权限。一个简单的插值修复反而会制造该风险:权限预设在运行时更新 bash 沙箱和审批状态,但无法挂载、卸载或约束文件系统栈。
+
+## 时间线
+
+- PR(Pull Request) #261 整合了 ACP 组合并刷新了文件系统快照,同时引入了条件式文件系统入口。
+- 所有单元测试、覆盖率、快照、文档、构建和 hygiene 检查均通过。
+- 对刷新后的文件系统预期输出的评审发现了通用的失败卡片和结构化的 `UNKNOWN_TOOL` 结果。
+- 一次真实的 Loader 启动确认:每个 `disabled` 值仍为表达式对象,每个文件系统 fiber 均未创建。
+
+## 根因
+
+实现时假设 `!!js` 适用于整个 Loader 入口。其实际边界更窄:`Entry._resolveConfig()` 仅对 `entry.options.config` 进行插值;`Entry.disabled` 直接测试 `entry.options.disabled`,不经过插值。YAML 标签在语法上合法,因此加载过程不产生任何诊断信息。
+
+快照框架将任何确定性的 transcript(文本记录)视为有效行为。Header pin 验证了组合后的工具 schema,但文件系统场景共享来自默认组合的 pin,因此未独立证明其所需工具已注册。刷新在任何语义断言拒绝缺失工具之前,就已重写了预期的 stdout 和会话日志。
+
+## 已添加的防护措施
+
+- 文件系统场景启动 `fs.cordis.yml`:一个显式的固定全权限 overlay,配有对应的回放配置和独立的 request-header 类。
+- [`AGENTS.md`](../../AGENTS.md) 与 [Cordis 入门](../cordis-primer.md#loader-configuration)明确说明 `!!js` 仅在插件 `config` 内有效,条件式组合应使用 overlay。
+- `verify-cordis-config` 解析仓库中的 Cordis YAML,拒绝 Loader 入口元数据中的表达式节点(包括 include patch 和插入的入口)。
+- `dsh-acp-snapshot` 在新鲜运行和已提交的会话 fixture 中拒绝结构化的 `UNKNOWN_TOOL` 结果,防止其被提交为预期输出。
+
+## 教训
+
+- 语法上被接受的配置值不一定在该位置被求值;应记录并验证插值边界。
+- 快照刷新是 fixture 的生产过程,不是正确性审查。诸如已注册工具缺失这类语义上不可能的结果,需要独立于预期输出的断言。
+- 权限控制只应描述其实际管辖的能力。组合时的文件系统访问无法安全地跟随运行时的 bash-only 预设。
diff --git a/docs/postmortem/README.i18n.yaml b/docs/postmortem/README.i18n.yaml
new file mode 100644
index 0000000000..e68d3a1a07
--- /dev/null
+++ b/docs/postmortem/README.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+README.md: df0e2fcb8540aeed005153dbecc451d781ca5ff1
+README.zh.md: 2ce6de475c705b02cd9dabfb2181929d81478e2c
diff --git a/docs/postmortem/README.md b/docs/postmortem/README.md
index 7114f65916..df0e2fcb85 100644
--- a/docs/postmortem/README.md
+++ b/docs/postmortem/README.md
@@ -1,5 +1,7 @@
# Post-mortems
+English | [中文](README.zh.md)
+
Incident write-ups: a bug reached a place it shouldn't have (a real user, a merged PR, a release), and the interesting part is *why our process let it through*, not just the one-line fix.
A post-mortem is NOT an [Agent Note](../../.agents/notes/README.md) (which records a deliberate design decision and its rejected alternatives, or proposes future work). It is a backward-looking record of a failure: what broke, the mechanism, why every safety net missed it, and the concrete guardrails added so the same class of bug fails loudly next time.
diff --git a/docs/postmortem/README.zh.md b/docs/postmortem/README.zh.md
new file mode 100644
index 0000000000..2ce6de475c
--- /dev/null
+++ b/docs/postmortem/README.zh.md
@@ -0,0 +1,16 @@
+# 事故复盘(postmortem)
+
+[English](README.md) | 中文
+
+事故复盘:一个 bug 到达了它不该到达的地方(真实用户、已合并的 PR(Pull Request)、已发布的版本),值得关注的是*为什么我们的流程放过了它*,而不仅仅是那一行修复。
+
+事故复盘不是 [Agent Note(agent 决策记录)](../../.agents/notes/README.md)(Agent Note 记录一个经过深思熟虑的设计决策及其被否决的替代方案,或提出未来工作)。它是一份回顾性的失败记录:什么坏了、机制是什么、为什么每道安全网都没拦住、以及加了哪些具体的防护措施使同类 bug 下次能被显式暴露。
+
+当一个 bug 满足以下条件时,请撰写事故复盘:**隐蔽**(机制不显而易见,即使是细心的工程师也得费力重新推导)、**系统性**(逃逸的原因是测试/工具/约定的缺口,而非一次性的笔误)、**重新发现的代价高**(它消耗了真实的调试时间,且下次还会如此)。请链接该事故复盘所推动建立的防护措施(测试、AGENTS.md 规则、ADR)。
+
+每篇事故复盘以一段**摘要**开头:一个简短段落,让忙碌的读者在三十秒内吸收要点——什么坏了、用直白的话说根因是什么、为什么逃逸了、持久的教训是什么——然后才是后续的详细「概述 / 时间线 / 根因 / 防护措施」各节。
+
+| # | 标题 |
+|---|---|
+| [0001](0001-acp-default-export-drops-inject.md) | ACP(Agent Client Protocol)服务器在连接时崩溃:`export default` 丢失了插件的 `inject` |
+| [0002](0002-js-expression-disabled-filesystem-tools.md) | 文件系统快照工具被一个字面量 `!!js` 对象永久禁用 |
diff --git a/docs/testing.i18n.yaml b/docs/testing.i18n.yaml
new file mode 100644
index 0000000000..8ebdff8c55
--- /dev/null
+++ b/docs/testing.i18n.yaml
@@ -0,0 +1,6 @@
+# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
+# 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
+testing.md: fd38fb7b20d76ef48c81c86badcf501f7c0dbd4e
+testing.zh.md: 4584492350aefd5d72692093b08c0dcd4910a8af
diff --git a/docs/testing.md b/docs/testing.md
index 85798cee3e..fd38fb7b20 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -1,21 +1,23 @@
# Testing policy
+English | [中文](testing.zh.md)
+
How this repo tests, tier by tier, and the rules that keep a green suite meaningful. Commands live in root [AGENTS.md](../AGENTS.md); linked Agent Notes carry the rationale.
## Tiers
-- **Unit** (`pnpm run test`): vitest over `packages|examples/*/tests/**/*.spec.ts`, colocated with what they test. Every registry gets an HMR-safety test (dispose the contributing fiber, assert cleanup). Prefer edge cases, error paths, event ordering, concurrency races, and permanent contract regressions (see `packages/core/agent-loop/tests/contract-regressions.spec.ts`).
+- **Unit** (`pnpm run test`): vitest over package and example specs under their `tests/**` directories plus repository script specs under `scripts/**/*.spec.ts`; tests stay with the code area they exercise. Every registry gets an HMR-safety test (dispose the contributing fiber, assert cleanup). Prefer edge cases, error paths, event ordering, concurrency races, and permanent contract regressions (see `packages/core/agent-loop/tests/contract-regressions.spec.ts`).
- **Coverage gate** (`pnpm run test:coverage`): the gating run, per-file 100% on `packages/*/*/src`. An uncovered line is often dead code the gate is correctly flagging for deletion, not a missing test to bolt on. Line coverage is necessary, never sufficient — it proves lines ran, not that the feature works as shipped.
- **Real-API e2e** (`pnpm run test:e2e`): with-key tests against live provider APIs — the DeepSeek model plus provider-specific smokes that gate on their own keys (`EXA_API_KEY`, `PERPLEXITY_API_KEY`, …); each suite self-skips without its key so keyless CI stays green ([real-API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md)).
-- **Snapshot** (`pnpm run test:snapshot`): transport-specific keyless expected outputs cover external presentation. ACP suites boot the real example subprocess, replay a recorded session, and diff normalized JSON-RPC plus the re-persisted log ([ACP snapshot Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md)); the headless suite independently pins `stream-json` through its real one-shot subprocess. TUI completed journeys replay recorded primary/child JSONL through the real agent loop and tools before projecting ANSI into semantic terminal-state expected outputs; package-local snapshots retain transient renderer states, and a real PTY conversation covers the process boundary ([TUI snapshot Agent Note](../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md)). Use `pnpm run test:snapshot:record` when a model transcript must change and `pnpm run test:snapshot:refresh` when committed replay input remains correct; review every JSONL and expected-output diff. System-prompt/tool-schema content is pinned by one ACP scenario (`text-turn`) and tokenized in every other fixture, so a prompt or schema edit churns one committed line ([pinned-header Agent Note](../.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)).
+- **Snapshot** (`pnpm run test:snapshot`): keyless expected outputs cover external presentation. ACP boots the real example, replays a recorded session, and diffs normalized JSON-RPC plus the re-persisted log ([ACP snapshot Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md)); headless pins `stream-json` through its real one-shot process. TUI journeys replay primary/child JSONL through the real loop and tools, then project ANSI into semantic terminal-state outputs; package snapshots retain transient states and a real PTY covers the process boundary ([TUI snapshot Agent Note](../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md)). Use `pnpm run test:snapshot:record` when a model transcript changes and `pnpm run test:snapshot:refresh` when replay input remains valid; review every JSONL and expected-output diff. One ACP scenario (`text-turn`) pins full system-prompt/tool-schema content; other fixtures tokenize it so an edit churns one line ([pinned-header Agent Note](../.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md)).
## The with-key policy: inference is cheap here
-We are DeepSeek — do not ration real-API tests. A no-key test proves plumbing; only a with-key run proves the agent works against a real model. Write many: file-writing prompts, multi-turn conversations, tool use, cancellation mid-stream. Highest-value are **smoke tests** that boot the real example, send one real prompt, and check the world — they catch the "green unit tests, broken product" class that mocks structurally cannot ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md)). The self-skip exists only so secretless CI and keyless contributors aren't blocked; it is not a cost signal. Every example ships both a keyless smoke and a with-key smoke ([examples/AGENTS.md](../examples/AGENTS.md)).
+We are DeepSeek — do not ration real-API tests. A no-key test proves plumbing; only a with-key run proves the agent works against a real model. Cover file-writing prompts, multi-turn conversations, tool use, and mid-stream cancellation. Highest-value are **smoke tests** that boot the real example, send one prompt, and check the world — they catch the "green unit tests, broken product" class that mocks cannot ([postmortem 0001](postmortem/0001-acp-default-export-drops-inject.md)). Self-skip keeps secretless CI and keyless contributors unblocked; it is not a cost signal. Every example ships keyless and with-key smokes ([examples/AGENTS.md](../examples/AGENTS.md)).
## Prefer the real implementation over a mock
-Mock only the genuinely expensive or non-deterministic boundary (the LLM adapter, the network, the clock); keep everything downstream real. A hand-rolled stand-in proves the bridge moves bytes, not that the shipping tool behaves as asserted — the two drift while the test stays green. Example: bridge tool-call tests run the scripted mock MODEL but the real tool + real executor (`makeBridgeHarness({ withBash: true })` plugs `dsh-bash-local` + `dsh-tool-bash` and runs an actual `echo`).
+Mock only the expensive or non-deterministic boundary (LLM adapter, network, clock); keep everything downstream real. A hand-rolled stand-in proves the bridge moves bytes, not that the shipping tool behaves as asserted. Bridge tool-call tests use the scripted mock model with the real tool and executor: `makeBridgeHarness({ withBash: true })` plugs in `dsh-bash-local` and `dsh-tool-bash`, then runs `echo`.
Recovery tests separate pre/post-chunk failures by step and prove failed chunks derive no message or tool side effect. Cover exhaustion, cancellation, policy composition, persistence, status, wire counts, transport-closing idle timeouts, and shipping Loader composition.
diff --git a/docs/testing.zh.md b/docs/testing.zh.md
new file mode 100644
index 0000000000..4584492350
--- /dev/null
+++ b/docs/testing.zh.md
@@ -0,0 +1,46 @@
+# 测试策略
+
+[English](testing.md) | 中文
+
+本文说明本仓库的分层测试方式,以及保持绿色测试套件有意义的规则。命令见根目录 [AGENTS.md](../AGENTS.md);相关 Agent Note(agent 决策记录)承载设计动机。
+
+## 层级
+
+- **单元测试**(`pnpm run test`):vitest 运行包(package)和示例各自的 `tests/**` 目录下的测试,以及匹配 `scripts/**/*.spec.ts` 的仓库脚本测试;测试文件与其所覆盖的代码区域放在一起。每个注册表都有一个 HMR(热模块替换)安全测试(dispose(资源释放)贡献的 fiber,断言清理完成)。优先覆盖边界情况、错误路径、事件顺序、并发竞态,以及永久性契约回归(见 `packages/core/agent-loop/tests/contract-regressions.spec.ts`)。
+- **覆盖率门禁**(`pnpm run test:coverage`):门禁级运行,对 `packages/*/*/src` 按文件 100% 覆盖。未覆盖的行往往是门禁正确标记出的死代码(应删除),而非需要补写的测试。行覆盖率是必要条件,但永远不是充分条件:它证明行被执行过,不证明功能按交付预期工作。
+- **真实 API e2e**(`pnpm run test:e2e`):带密钥测试调用真实提供方 API,包括 DeepSeek 模型以及各提供方特有的冒烟测试;这些测试各自由自己的密钥控制(`EXA_API_KEY`、`PERPLEXITY_API_KEY` 等),缺少密钥时套件会自动跳过,使 keyless CI 保持绿色([真实 API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md))。
+- **快照**(`pnpm run test:snapshot`):无密钥预期输出覆盖对外呈现。ACP 启动真实示例、回放录制会话,并对归一化 JSON-RPC 与重新持久化的日志执行 diff([ACP 快照 Agent Note](../.agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md));headless 通过真实单次运行进程固定 `stream-json`。TUI 旅程通过真实循环与工具回放主会话与子会话 JSONL,再将 ANSI 投影为语义化终端状态输出;包级快照保留瞬态状态,真实 PTY 覆盖进程边界([TUI 快照 Agent Note](../.agents/notes/implemented/testing/2026-07-18-tui-terminal-state-snapshots.md))。当模型 transcript(文本记录)发生变化时使用 `pnpm run test:snapshot:record`,回放输入仍然有效时使用 `pnpm run test:snapshot:refresh`;请审查每一处 JSONL 与预期输出差异。一个 ACP 场景(`text-turn`)固定完整的系统提示词与工具 schema 内容;其他 fixture(测试前置数据)将其 token 化,因此修改只会扰动一行([pinned-header Agent Note](../.agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md))。
+
+## 带密钥策略:推理在这里很便宜
+
+我们是 DeepSeek,不要吝惜真实 API 测试。无密钥测试只能证明底层通路;只有带密钥运行才能证明 agent(智能体)能对接真实模型正常工作。覆盖文件写入提示词、包含多个轮次的对话、工具使用和流中取消。价值最高的是**冒烟测试**:启动真实示例、发送一条提示词,并检查外部世界;它们能捕获「单元测试全绿、产品却坏了」这一类 mock 无法发现的问题([事故复盘 0001](postmortem/0001-acp-default-export-drops-inject.md))。自动跳过让无密钥 CI 和无密钥贡献者不受阻塞;它不是成本信号。每个示例都提供无密钥和带密钥冒烟测试([examples/AGENTS.md](../examples/AGENTS.md))。
+
+## 优先使用真实实现而非 mock
+
+只 mock 开销高或不确定的边界(LLM(大语言模型)适配器、网络、时钟);下游一切保持真实。手写替身只能证明桥接层在搬运字节,不能证明交付的工具行为符合断言。桥接工具调用测试将脚本化 mock 模型与真实工具和执行器配合使用:`makeBridgeHarness({ withBash: true })` 接入 `dsh-bash-local` 与 `dsh-tool-bash`,然后运行 `echo`。
+
+恢复测试按步骤区分分片前与分片后的失败,并证明失败分片不会派生出消息或工具副作用。覆盖耗尽、取消、策略组合、持久化、状态、协议计数、会关闭传输的空闲超时,以及交付的 Loader 组合。
+
+## 验证外部世界,而非自我报告
+
+e2e 断言应重新运行命令或从外部重新读取文件;对 agent 自身输出做关键词探测会让作弊的 agent 通过。断言未修改的文件逐字节一致。e2e 测试自行管理资源:在测试中创建 harness,在 `afterEach` 中 dispose(即使失败/重试/超时也要释放);共享 fixture 放在普通的 `tests/harness.ts` 中,绝不放在另一个 `*.e2e.ts` 中(导入一个 spec 会重新注册其 `describe`,导致真实 API 调用重复执行)。
+
+## 测试真实入口路径
+
+- 产品可见的插件必须有一个非单元的真实组合测试。手动构建的 `ctx.plugin(...)` 套件不够:通过 Loader 和 app/process 启动仅用于测试的 `cordis.yml`,只 mock 外部/不确定边界,断言模型可见的请求/日志、持久状态或用户可见输出。不要把 opt-in 选项混入交付默认值。
+- 一个守卫只有在回归真的能让它失败时才有效。对于没有 `inject` 的插件(bundle/组合插件),Loader 冒烟测试在导出形状损坏时仍然绿着——需要添加显式的 `expect('default' in mod).toBe(false)` 加 `unwrapExports` 往返断言,并证明它有效:引入回归、观察变红、回退。
+- 「真实入口路径」指已发布的产物:包的 `bin` 所运行的是构建后的 `lib/bin.js`,并由普通 `node` 执行,从而暴露 tsx 会掩盖的失败(等待稳定时的竞态、模块解析、被吞掉的加载失败)。同样的规则适用于非 index 运行时入口(worker-thread 的同级文件 `lib/worker.cjs`),也适用于多个 bundle 共享的单例模块(`packages/ui/jsonrpc/tests/built-scope-carrier.e2e.ts`)。保持构建产物冒烟测试绿色(`packages/ui/*/tests/built-bin.e2e.ts`、`packages/code-runtime/code-runtime-worker/tests/built-lib.e2e.ts`),并断言真正缺失的配置以非零状态退出。
+
+## 测试解析:仅限源码
+
+- 每个 vitest 配置都将 vite-tsconfig-paths 指向 `tsconfig.base.json`;工作区包的裸导入解析到 `src`([布局](development.md#typescript-project-layout)),绝不会经由包的 `exports` 解析到构建后的 `lib/`,因为其中的陈旧产物会加载第二份模块单例。构建产物只在显式指定时使用:以 `lib` 模式运行的子进程,以及下文的构建产物冒烟测试。
+
+## 测试子进程启动模式
+
+- CI 与已有构建产物的测试通道通过共享双模式启动器,从构建后的 `lib/` 运行每个示例或 Cordis 配置子进程。不要为这些子进程手写 `--import tsx`。
+- 不加载 Cordis 的协议与操作系统 fixture 直接通过 Node 运行使用可擦除语法的 `.ts` 文件,不经过 tsx 或根路径映射。
+- 只有测试对象本身是源码路径解析时,才可以选择 `src`;在测试中写明这一契约。
+
+## 何时需要快照测试
+
+每项非平凡的模型可见或人类可见变更,都必须在同一 PR 中,通过可运行示例所属的快照套件添加或更新无密钥场景。包测试、e2e 断言、mock 与仅测试组合、PR 理由都不能取代组装后的 transcript;必要时应扩展 harness。ACP 接口使用 `examples//tests/snapshots/`,即基于 [`dsh-acp-snapshot`](../packages/support/acp-snapshot/README.md) 套件工厂的场景表(`examples/acp-agent` 为主套件);`examples/headless-agent` 拥有 `stream-json` 快照与回放 fixture。已完成的交互式终端旅程使用 `examples/tui-agent/tests/snapshots/` 下由 JSONL 驱动的场景;瞬态呈现使用包内语义矩阵,输入、Loader 选择或终端清理发生变化时还要添加 PTY 用例。新的能力 seam、生命周期形态或 transcript 呈现接口在计划阶段就要列出每个覆盖层级,并在实现前验证 harness 能够表达它们。
diff --git a/scripts/cordis-config-files.spec.ts b/scripts/cordis-config-files.spec.ts
new file mode 100644
index 0000000000..49da2c6aaf
--- /dev/null
+++ b/scripts/cordis-config-files.spec.ts
@@ -0,0 +1,36 @@
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { afterEach, describe, expect, it } from 'vitest'
+import { cordisConfigFiles } from './cordis-config-files.ts'
+
+const roots: string[] = []
+
+afterEach(() => {
+ for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
+})
+
+describe('cordisConfigFiles', () => {
+ it('finds Loader YAML without treating translation records as configs', () => {
+ const root = mkdtempSync(join(tmpdir(), 'dsh-cordis-config-files-'))
+ roots.push(root)
+ for (const directory of ['.claude', 'docs', 'examples', 'node_modules/pkg', 'vendor/pkg']) {
+ mkdirSync(join(root, directory), { recursive: true })
+ }
+ for (const file of [
+ '.claude/hidden.cordis.yml',
+ 'docs/cordis-primer.i18n.yaml',
+ 'examples/agent.cordis.yaml',
+ 'examples/headless.cordis.yml',
+ 'node_modules/pkg/hidden.cordis.yml',
+ 'vendor/pkg/hidden.cordis.yml',
+ ]) {
+ writeFileSync(join(root, file), '[]\n')
+ }
+
+ expect(cordisConfigFiles(root)).toEqual([
+ 'examples/agent.cordis.yaml',
+ 'examples/headless.cordis.yml',
+ ])
+ })
+})
diff --git a/scripts/cordis-config-files.ts b/scripts/cordis-config-files.ts
new file mode 100644
index 0000000000..9473779efe
--- /dev/null
+++ b/scripts/cordis-config-files.ts
@@ -0,0 +1,18 @@
+/** Cordis Loader configuration file discovery. */
+
+import { globSync } from 'node:fs'
+
+/**
+ * Return repository-relative Cordis Loader YAML paths under `root`.
+ *
+ * Translation consistency records are YAML sidecars, never Loader inputs.
+ *
+ * @param root Repository root to scan.
+ * @returns Sorted repository-relative Loader configuration paths.
+ */
+export function cordisConfigFiles(root: string): string[] {
+ return globSync(['**/*cordis*.yml', '**/*cordis*.yaml'], {
+ cwd: root,
+ exclude: ['.claude/**', 'node_modules/**', 'vendor/**', '**/*.i18n.yaml'],
+ }).sort()
+}
diff --git a/scripts/project-doc-site.spec.ts b/scripts/project-doc-site.spec.ts
index 7d1c3c1550..e6cc11ae6e 100644
--- a/scripts/project-doc-site.spec.ts
+++ b/scripts/project-doc-site.spec.ts
@@ -172,13 +172,13 @@ describe('rewriteMarkdown', () => {
})
describe('docsPages locale routes', () => {
- it('publishes every route in both locales and selects paired user sources', () => {
+ it('publishes every route in both locales and selects paired sources', () => {
const byRoute = new Map(docsPages.map(page => [page.route, page]))
for (const page of docsPages.filter(page => page.locale === 'root')) {
const counterpart = byRoute.get(`en/${page.route}`)
expect(counterpart, page.route).toBeDefined()
expect(counterpart?.locale).toBe('en')
- if (page.source.startsWith('docs/user/')) {
+ if (page.contentLocale === 'zh-CN') {
expect(page.source).toMatch(/\.zh\.md$/)
expect(page.contentLocale).toBe('zh-CN')
expect(counterpart?.source).toBe(page.source.replace(/\.zh\.md$/, '.md'))
@@ -190,6 +190,22 @@ describe('docsPages locale routes', () => {
}
})
+ it('projects translated core-data pages while retaining explicit English fallbacks', () => {
+ const rootPages = docsPages.filter(page => (
+ page.locale === 'root' && page.route.startsWith('reference/core-data-structures/')
+ ))
+ const translated = rootPages.filter(page => page.contentLocale === 'zh-CN')
+ const fallbacks = rootPages.filter(page => page.contentLocale === 'en-US')
+
+ expect(translated).toHaveLength(18)
+ expect(translated.every(page => page.source.endsWith('.zh.md'))).toBe(true)
+ expect(fallbacks.map(page => page.source).sort()).toEqual([
+ 'docs/core-data-structures/commands.md',
+ 'docs/core-data-structures/goal.md',
+ 'docs/core-data-structures/pty.md',
+ ])
+ })
+
it('publishes the Cordis core API under matching locale structures', () => {
const files = ['context.md', 'events.md', 'fiber.md', 'registry.md', 'service.md']
for (const file of files) {
diff --git a/scripts/snapshots/translation-prompt-v4/request-response.expected.json b/scripts/snapshots/translation-prompt-v4/request-response.expected.json
index b0a3a3d526..9aa08c3793 100644
--- a/scripts/snapshots/translation-prompt-v4/request-response.expected.json
+++ b/scripts/snapshots/translation-prompt-v4/request-response.expected.json
@@ -4,7 +4,7 @@
"messages": [
{
"role": "system",
- "content": "# Translation Prompt\n\nYou are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the given source document from English to Chinese, producing natural, professional technical prose.\n\n## Quality Requirements\n\n### Structure and Format Preservation\n- Output a complete translated document that maintains exactly the same structure as the source: heading hierarchy, list shape, table columns, link targets, and code blocks.\n- Fenced code blocks must be byte-identical to the source, including ALL comments inside them. Do NOT translate comments inside code blocks. This is a hard rule with no exceptions.\n- Inline code spans (commands, flags, paths, API names, version numbers) must be kept verbatim. Never translate or reformat them.\n- Every relative link must point to the same target as in the source. Link text is translated; link targets are not.\n- Language switcher line: when translating into Chinese, write `[English](source-filename.md) | 中文`. When translating into English, write `English | [中文](source-filename.zh.md)`. Do NOT copy the switcher line from the source file unchanged — you must flip the link direction.\n- After a closing bold marker `**`, insert a space before the next character when that character is a Latin letter, digit, or CJK ideograph. Never insert a space before any punctuation (full-width or half-width).\n\n### Tone and Style\n- The translation must read as if originally written in the target language by a native speaker. If an expression sounds like a word-for-word rendering from the source language, rephrase it.\n- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions.\n- Use polite imperative forms where the text instructs the reader to do something.\n- Keep the author's register: concise stays concise, detailed stays detailed.\n\n### Sentence Structure\n- Break long sentences with commas or semicolons. Avoid run-on sentences.\n- Prefer active voice. Convert passive constructions to active if it reads more naturally.\n- Translate meaning, not words. Restructure sentences where the target language grammar requires it.\n- Do not invent words or expressions that do not exist in natural technical writing of the target language.\n\n### Word Choice\n- Prefer precise, formal vocabulary over casual or colloquial alternatives.\n- When multiple synonyms exist, choose the one most commonly used in professional technical documentation of the target language.\n- Avoid slang, internal jargon, or overly literal translations that would not be recognized by the general developer audience.\n- Do not use the same word to translate two different source-language terms that carry distinct meanings.\n- Avoid repeating the same verb in close proximity; vary word choice for readability.\n\n#### When translating into Chinese\n- When a number modifies a noun, always include a Chinese classifier or measure word (量词). For example: \"three-package seam\" → \"由三个包构成的 seam\", not \"三包 seam\".\n\n### Punctuation\n\n#### When translating into Chinese\n- Use full-width Chinese punctuation in prose: `,。:;?!()「」`.\n- Strongly prefer replacing all em-dashes (——) with colons, periods, commas, or parentheses. Keep an em-dash only if no other punctuation works at all.\n- Use enumeration commas (、) between parallel items, not regular commas.\n- List item endings: use semicolons or no punctuation. Do not end list items with commas.\n- Put one half-width space between Chinese text and Latin words/numbers.\n- For RFC 2119 keywords (MUST, MUST NOT, SHOULD, MAY), translate to the corresponding Chinese term (必须、禁止、应当、可以) and keep the SOURCE emphasis marker: plain source stays plain (必须), italic source stays italic (*必须*), and bold source stays bold (**必须**).\n\n#### When translating into English\n(To be added.)\n\n## Terminology\n\nA terminology table is provided below. Follow it strictly:\n- Render every listed term exactly as specified.\n- When the target language is Chinese, use the \"中文\" column. On first occurrence, write the \"首次出现\" value with its parenthetical gloss; on subsequent occurrences, write only the part before the parentheses.\n- When the target language is English, use the \"English\" column without a Chinese gloss; do not copy the \"中文\" or \"首次出现\" value into English prose.\n- If a term has already been glossed as part of a compound term, do not gloss it again when it appears alone later.\n- NEVER use translations listed in the \"不要译作\" column.\n- For technical terms not in the table, follow the target language: for a Chinese target, use an established Chinese rendering from a major Chinese-language OSS or vendor source, or keep the source term and flag it as pending when no such precedent exists; for an English target, use the established English technical term, or preserve an ambiguous source term with a short English gloss and flag it as pending. Do not invent a translation. This rule applies to terminology only; for general prose, freely restructure and paraphrase for natural expression.\n\n# Terminology\n\n本表约定本仓库的中英术语统一译法。\n\n**通用规则:**\n- \"中文\"列为中文译文的正文默认用词。若该列为英文,则中文译文的正文中保留英文不翻译。\n- 首次出现按\"首次出现\"列书写(带括号注释);后续出现只写括号前的部分(可能为中文,也可能为英文),不出现括号内的注释。\n- \"不要译作\"列为严格禁止的译法。\n- 如果某术语已经作为另一个术语的组成部分被括注过(如 `agent loop(智能体循环)` 中已包含 `agent` 的括注),则该术语后续单独出现时无需再次括注。\n\n## 缩写类(中英文文本中均使用缩写)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| ACP | ACP | ACP(Agent Client Protocol) | | |\n| AI | AI | AI(人工智能) | | |\n| API | API | | | |\n| CI | CI | | | |\n| CLI | CLI | CLI(命令行界面) | | |\n| e2e | e2e | | | |\n| HMR | HMR | HMR(热模块替换) | | |\n| JSON Schema | JSON Schema | | | |\n| JSONL | JSONL | | | |\n| LLM | LLM | LLM(大语言模型) | | |\n| MCP | MCP | | | |\n| PR | PR | PR(Pull Request) | | |\n| RAG | RAG | RAG(检索增强生成) | | |\n| SDK | SDK | | | |\n| SSE | SSE | SSE(Server-Sent Events) | | |\n\n## 英文类(中英文文本中均使用英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| agent | agent | agent(智能体) | | |\n| Agent Note | Agent Note | Agent Note(agent 决策记录) | 智能体注记、智能体笔记 | 本仓库中由 agent 撰写的提案与决策记录 |\n| agent harness | agent harness | agent harness(智能体框架) | | agent 组合词(agent harness/workflow/loop/skill 等)整体保留英文;未括注过 agent 时首现按对应组合词或 agent 行处理 |\n| agent loop | agent loop | agent loop(智能体循环) | | |\n| backlog | backlog | backlog(待翻清单) | | 仅在双语翻译语境里括注`待翻清单` |\n| blob hash | blob hash | | | `git hash-object` 的结果 |\n| Cordis | Cordis | | | |\n| dispose | dispose | dispose(资源释放) | | |\n| doc-sync | doc-sync | doc-sync(文档同步门禁) | | |\n| fiber | fiber | | | |\n| fixture | fixture | fixture(测试前置数据) | | |\n| fork | fork | | | |\n| Function Calling | Function Calling | Function Calling(函数调用) | | |\n| harness | harness | | | |\n| harness engineering | harness engineering | | | |\n| lint | lint | | | |\n| mock | mock | | | 保留英文;指测试替身 |\n| loader | loader | | | |\n| manifest | manifest | manifest(元数据清单) | | |\n| monorepo | monorepo | | | |\n| schema | schema | | | |\n| schema DSL | schema DSL | | | |\n| seam | seam | | | 与 `extension point` 是不同概念;根据具体语境,可译为`服务边界`或`可替换点` |\n| skill | skill | skill(技能) | | |\n| spawn | spawn | | | |\n| steering | steering | steering(中途引导) | | |\n| task id | task id | | 任务 id | 保留英文 |\n| subagent | subagent | | | |\n| thinking | thinking | | | API 字段保留英文;描述模型模式时译为`思考` |\n| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志 |\n| waterfall | waterfall | waterfall(瀑布式事件) | | |\n| wheel | wheel 包 | | | Python 打包格式 |\n| worktree | worktree | | | git 工作区概念 |\n| Zstandard | Zstandard | | | RFC 8878 compression format; `zstd` remains a code value. |\n\n## 双语类(中英文文本各自使用中英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| adapter | 适配器 | | | |\n| adapter contract | 适配器契约 | 适配器契约(adapter contract) | | |\n| append-only | 仅追加 | | | |\n| artifact | 产物 | | | |\n| backend | 后端 | | | |\n| background task | 后台任务 | | | |\n| block | 块 | | | |\n| build target | 构建目标 | | | |\n| cancel | 取消 | | | |\n| feature | 功能 | | 能力 | SDK 产品与工程模型中的可管理产品单元 |\n| feature option | 功能选项 | | variant | 一项 SDK 功能内有限、可选择的实现或配置 |\n| checkpoint | 检查点 | | | |\n| chunk | 分片 | | | |\n| compaction | 压缩 | 压缩(compaction) | | |\n| companion tool | 配套工具 | | | |\n| Cordis plugin config | Cordis 插件配置 | | | Cordis 插件公开的 `Config` 对象或配置结构 |\n| config key | 配置键 | | | Cordis 插件配置中的单个字段 |\n| consumer | 消费方 | | | |\n| content block | 内容块 | | | |\n| Cookbook | 实操手册 | | | 文档标题用语 |\n| context | 上下文 | | | |\n| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指\"另一侧\"时可写「另一侧」 |\n| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | |\n| contract | 契约 | | | 如:`pairing contract` →`配对契约` |\n| Cordis config entry | Cordis 配置项 | | | 指 `cordis.yml` 插件列表中的一项;插件实现本身写`Cordis 插件` |\n| Cordis plugin | Cordis 插件 | | | Cordis 加载的插件实现,不指 `cordis.yml` 中的一项配置 |\n| coverage | 覆盖率 | | | |\n| crash recovery | 崩溃恢复 | | | |\n| deploy root | 部署根目录 | | | |\n| durability | 持久性 | | | |\n| feature requirement | 功能依赖 | | | 功能或功能选项通过 `requires` 声明的关系 |\n| enforcement frontier | 执行红线 | | 强制边界 | i18n 配对机制用语:manifest `required` 清单所划的门禁生效范围;与金标样例(style-samples ⑦)一致 |\n| event | 事件 | | | |\n| event log | 事件日志 | | | |\n| event stream | 事件流 | | | |\n| event-sourced | 事件溯源 | | | 沿用 DDD 社区通行译法 |\n| executor | 执行器 | | | |\n| expected output | 预期输出 | | 金标 | 指 snapshot 比较产物;翻译语料的人工校准样例不在此列 |\n| extension | 扩展 | | | |\n| extension point | 扩展点 | | | 注意与 `seam` 区分 |\n| fail-fast | 快速失败 | | | |\n| fenced code block | 围栏代码块 | | | 沿用 MDN 中文翻译 |\n| fingerprint | 指纹 | | | 通用内容指纹;双语配对机制使用 sidecar record 记录两侧 blob hash |\n| finish reason | 结束原因 | | | |\n| foreground run | 前台运行 | | | |\n| freshness | 新鲜度 | | | 沿用 MDN 中文翻译;在本项目中指译文相对源文的同步状态 |\n| hook | 钩子 | | | |\n| implementation | 实现 | | | |\n| inference | 推理 | 推理(inference) | | 需要和 `reasoning` 区分时保留英文括注 |\n| info string | 信息字符串 | | | 沿用 CommonMark 中文翻译;指代码围栏 ``` 之后的语言标注 |\n| injection | 注入 | | | |\n| integration | 集成 | | | |\n| interface | 接口 | | | |\n| language switcher | 语言切换行 | | | i18n 配对机制用语:双语配对文件顶部的互链行 |\n| memory | 记忆 / 内存 | | | 与 `agent` 搭配时译为`记忆`(如 `agent memory` →`智能体记忆`);指系统资源时译为`内存` |\n| merge | 合并 | | | |\n| message | 消息 | | | |\n| mod | 模组 | | | |\n| model provider | 模型提供方 | | | |\n| module | 模块 | | | |\n| npm dependency | NPM 依赖 | | | `package.json` 中的包关系;`dependencies`、`devDependencies` 等字段保持原样 |\n| orphan | 遗留 | | 孤儿、孤立 | 指英文源已不存在的 `.zh.md`(如「遗留译文」);进程语境按 OS 惯用语译「孤儿进程」 |\n| orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 |\n| package | 包 | 包(package) | | 指 npm 包(`@deepseek-ai/dsh-*`);`package.json` 等代码标识保持原样 |\n| pairing | 配对 | | | |\n| peer dependency | 对等依赖 | 对等依赖(peer dependency) | | |\n| permission | 权限 | | | |\n| persistence | 持久化 | | | |\n| pipeline | 流水线 | | | |\n| plugin | 插件 | | | |\n| prompt | 提示词 | | | |\n| provider | 提供方 | | | |\n| provider-neutral | 提供方无关 | | | |\n| quality gate | 质量门禁 | | | |\n| reasoning | 推理 | 推理(reasoning) | | 需要和 `inference` 区分时保留英文括注 |\n| reasoning_content | 思考内容 | | | |\n| registry | 注册表 | | | |\n| replay | 回放 | | | |\n| resume | 恢复 | | | |\n| runtime | 运行时 | | | |\n| sandbox | 沙箱 | | | |\n| service | 服务 | | | |\n| serving surface | 对外服务接口 | | | |\n| session | 会话 | | | |\n| session event | 会话事件 | | | |\n| sidecar record | 伴随记录 | | 旁挂记录 | 指与文档同目录的伴随记录文件 |\n| smoke test | 冒烟测试 | | | |\n| snapshot | 快照 | | | |\n| source of truth | 真源 | | | |\n| spine | 主干 | | | |\n| staged | 暂存 | | | 沿用 git 官方中文翻译 |\n| stale | 陈旧 | | 过期 | 与 `fresh`(`新鲜`)成对;门禁输出中保留英文 `stale` 不翻译;`expired` 才译为`过期` |\n| step | 步骤 | | | |\n| stream | 流 | | | |\n| streaming | 流式输出 | | | |\n| structural signature | 结构签名 | | | i18n 配对机制用语:门禁比对两侧文件时提取的有序结构序列(标题层级、代码块、列表等) |\n| system prompt | 系统提示词 | | | |\n| taxonomy | 分类体系 | | | |\n| token usage | token 用量 | | | |\n| tool | 工具 | | | |\n| tool call | 工具调用 | | | |\n| tool result | 工具结果 | | | |\n| tool schema | 工具 schema | | | |\n| toolkit | 工具包 | | | |\n| turn | 轮次 | | | |\n| VFS | VFS | 虚拟文件系统(VFS) | | |\n| typecheck | 类型检查 | | | |\n| vocabulary | 词汇 | | | |\n| wire format | 协议格式 | 协议格式(wire format) | | |\n| workflow | 工作流 | | | |\n| wrapper | 包装层 | | | 软件层或 SDK 包装层 |\n| wrapper script | 包装脚本 | | | 可执行脚本包装层 |\n\n\n## Output Format\n\nProduce your output in three XML sections:\n\nThe outer section tags are framing. If Markdown inside any section body contains a line consisting only of ``, ``, ``, ``, ``, or ``, prefix that line with `\\`. If the original line already has one or more backslashes immediately before the tag, add one more. The parser removes exactly one framing escape; tags mentioned inline need no escaping.\n\n```xml\n\n(Complete translation of the source document)\n\n\n\n(Self-review notes, one correction per line with category tag, e.g.)\n- [Tone] \"旁挂记录\" → \"伴随记录\"(生造词)\n- [Sentence] 第 3 段补充逗号断句\n- [Punctuation] 两处破折号替换为冒号\n- 无修正\n\n\n\n(Final translation after corrections)\n\n```\n\n## Self-Review Instructions\n\nAfter writing ``, re-read it in the target language only, without looking at the source. Check by category:\n\n**Structure**\n- Is the heading hierarchy, list shape, and code block content identical to the source?\n- Are ALL comments inside code blocks left untranslated (byte-identical to source)?\n- Is the language switcher line correctly flipped (not copied from source)?\n- Are link targets preserved, and are spaces after bold markers present only before Latin letters, digits, or CJK ideographs?\n- Are wrapper-tag lines inside section bodies escaped with one additional backslash?\n\n**Tone & Style**\n- Does every sentence read as if originally written by a native speaker?\n- Is there any colloquial, casual, or overly informal phrasing?\n\n**Sentence Structure**\n- Are there run-on sentences that need breaking?\n- Are there stiff passive constructions that should be converted to active voice?\n\n**Word Choice**\n- Are there overly literal translations that sound unnatural?\n- Is the same target-language word used to translate two distinct source concepts?\n- Is any slang or internal jargon present?\n\n**Terminology**\n- For a Chinese target, are first-occurrence glosses correctly applied (not missing, not repeated)? For an English target, are Chinese glosses absent?\n- Are any \"不要译作\" forbidden translations present?\n- For unlisted terms, does a Chinese target use established Chinese precedent or retain the source term as pending, and does an English target use established English terminology or preserve only an ambiguous source term with a short English gloss?\n\n**Punctuation** (when target is Chinese)\n- Are there em-dashes that should be replaced with colons, periods, or commas?\n- Are list items ending with commas instead of semicolons?\n- Do RFC 2119 keywords preserve the source emphasis exactly?\n\nRecord corrections in `` with category tags. Then output the corrected version in ``. If no corrections are needed, write \"无修正\" in `` and copy the translation unchanged into ``.\n\n## Examples\n\nBelow are representative examples of common problems and their corrections. Follow the \"Good\" versions.\n\n### Colloquial verb → Professional verb\n- Source: `The repo pins pnpm@11.7.0 in package.json`\n- Bad: `仓库在 package.json 中钉住 pnpm@11.7.0`\n- Good: `该仓库在 package.json 中固定使用 pnpm@11.7.0`\n\n### Run-on sentence → Natural phrasing with pause\n- Source: `Read docs/architecture.md before changing anything under packages/.`\n- Bad: `改动 packages/ 下的任何东西之前先读 docs/architecture.md。`\n- Good: `在修改 packages/ 目录下的任何内容之前,请先阅读 docs/architecture.md。`\n\n### Stiff passive voice → Active and natural\n- Source: `a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.`\n- Bad: `门禁绿意味着这对文档曾在当前内容上被确认一致,不意味着这次确认本身是对的。`\n- Good: `门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。`\n\n### Invented word → Natural expression\n- Source: `A sidecar record of both blob hashes makes consistency checkable`\n- Bad: `旁挂记录两侧 blob hash,使一致性可检查`\n- Good: `伴随记录保存两侧 blob hash,使一致性可检查`\n\n### Em-dash → Colon/period\n- Source: `FIXME — an issue that should block a new release. A release should not ship with an open FIXME unless reviewers explicitly agree the change can be merged anyway.`\n- Bad: `FIXME——应当阻塞新版本发布的问题。除非评审者明确同意可以照常合入,发布不应带着未解决的 FIXME 出门。`\n- Good: `FIXME:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 FIXME。`\n\n### Overly literal → Meaningful rendering\n- Source: `awkward phrasing is easier to hear without the source anchoring you`\n- Bad: `没有源文锚着,别扭的表述更容易被听出来`\n- Good: `不对照原文时,更容易察觉别扭的表达`\n\n### Terminology — do not translate what should be kept in English\n- Source: `typed service seams, and explicit extension points`\n- Bad: `类型化的服务 seam(扩展点)与显式扩展点`\n- Good: `类型化的服务 seam 与显式扩展点`\n\n### Slang/jargon → Professional phrasing\n- Source: `The committed agent workflow lives in .agents/skills/dsh-translate-docs`\n- Bad: `进仓的 agent 工作流见 .agents/skills/dsh-translate-docs`\n- Good: `仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs`\n\n### \"For humans\" — translate the intent, not the word\n- Source: `For humans, start with the development guide`\n- Bad: `对于人工读者,请先从开发指南开始`(\"人工读者\"生硬)\n- Good: `面向开发者:请先阅读开发指南`(\"开发者\"自然,且中文里冒号在此处更自然)\n\n### Code block comments — NEVER translate\n- Source code block contains: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)`\n- Bad: `# 全屏 TUI coding agent(需要 DEEPSEEK_API_KEY)`\n- Good: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)` (keep exactly as-is, byte-for-byte)\n\n### Language switcher — flip direction\n- Source file (English) has: `English | [中文](README.zh.md)`\n- Bad (copying source unchanged): `English | [中文](README.zh.md)`\n- Good (flipped for Chinese file): `[English](README.md) | 中文`\n\n---\n\nNow translate the following document:"
+ "content": "# Translation Prompt\n\nYou are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the given source document from English to Chinese, producing natural, professional technical prose.\n\n## Quality Requirements\n\n### Structure and Format Preservation\n- Output a complete translated document that maintains exactly the same structure as the source: heading hierarchy, list shape, table columns, link targets, and code blocks.\n- Fenced code blocks must be byte-identical to the source, including ALL comments inside them. Do NOT translate comments inside code blocks. This is a hard rule with no exceptions.\n- Inline code spans (commands, flags, paths, API names, version numbers) must be kept verbatim. Never translate or reformat them.\n- Every relative link must point to the same target as in the source. Link text is translated; link targets are not.\n- Language switcher line: when translating into Chinese, write `[English](source-filename.md) | 中文`. When translating into English, write `English | [中文](source-filename.zh.md)`. Do NOT copy the switcher line from the source file unchanged — you must flip the link direction.\n- After a closing bold marker `**`, insert a space before the next character when that character is a Latin letter, digit, or CJK ideograph. Never insert a space before any punctuation (full-width or half-width).\n\n### Tone and Style\n- The translation must read as if originally written in the target language by a native speaker. If an expression sounds like a word-for-word rendering from the source language, rephrase it.\n- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions.\n- Use polite imperative forms where the text instructs the reader to do something.\n- Keep the author's register: concise stays concise, detailed stays detailed.\n\n### Sentence Structure\n- Break long sentences with commas or semicolons. Avoid run-on sentences.\n- Prefer active voice. Convert passive constructions to active if it reads more naturally.\n- Translate meaning, not words. Restructure sentences where the target language grammar requires it.\n- Do not invent words or expressions that do not exist in natural technical writing of the target language.\n\n### Word Choice\n- Prefer precise, formal vocabulary over casual or colloquial alternatives.\n- When multiple synonyms exist, choose the one most commonly used in professional technical documentation of the target language.\n- Avoid slang, internal jargon, or overly literal translations that would not be recognized by the general developer audience.\n- Do not use the same word to translate two different source-language terms that carry distinct meanings.\n- Avoid repeating the same verb in close proximity; vary word choice for readability.\n\n#### When translating into Chinese\n- When a number modifies a noun, always include a Chinese classifier or measure word (量词). For example: \"three-package seam\" → \"由三个包构成的 seam\", not \"三包 seam\".\n\n### Punctuation\n\n#### When translating into Chinese\n- Use full-width Chinese punctuation in prose: `,。:;?!()「」`.\n- Strongly prefer replacing all em-dashes (——) with colons, periods, commas, or parentheses. Keep an em-dash only if no other punctuation works at all.\n- Use enumeration commas (、) between parallel items, not regular commas.\n- List item endings: use semicolons or no punctuation. Do not end list items with commas.\n- Put one half-width space between Chinese text and Latin words/numbers.\n- For RFC 2119 keywords (MUST, MUST NOT, SHOULD, MAY), translate to the corresponding Chinese term (必须、禁止、应当、可以) and keep the SOURCE emphasis marker: plain source stays plain (必须), italic source stays italic (*必须*), and bold source stays bold (**必须**).\n\n#### When translating into English\n(To be added.)\n\n## Terminology\n\nA terminology table is provided below. Follow it strictly:\n- Render every listed term exactly as specified.\n- When the target language is Chinese, use the \"中文\" column. On first occurrence, write the \"首次出现\" value with its parenthetical gloss; on subsequent occurrences, write only the part before the parentheses.\n- When the target language is English, use the \"English\" column without a Chinese gloss; do not copy the \"中文\" or \"首次出现\" value into English prose.\n- If a term has already been glossed as part of a compound term, do not gloss it again when it appears alone later.\n- NEVER use translations listed in the \"不要译作\" column.\n- For technical terms not in the table, follow the target language: for a Chinese target, use an established Chinese rendering from a major Chinese-language OSS or vendor source, or keep the source term and flag it as pending when no such precedent exists; for an English target, use the established English technical term, or preserve an ambiguous source term with a short English gloss and flag it as pending. Do not invent a translation. This rule applies to terminology only; for general prose, freely restructure and paraphrase for natural expression.\n\n# Terminology\n\n本表约定本仓库的中英术语统一译法。\n\n**通用规则:**\n- \"中文\"列为中文译文的正文默认用词。若该列为英文,则中文译文的正文中保留英文不翻译。\n- 首次出现按\"首次出现\"列书写(带括号注释);后续出现只写括号前的部分(可能为中文,也可能为英文),不出现括号内的注释。\n- \"不要译作\"列为严格禁止的译法。\n- 如果某术语已经作为另一个术语的组成部分被括注过(如 `agent loop(智能体循环)` 中已包含 `agent` 的括注),则该术语后续单独出现时无需再次括注。\n\n## 缩写类(中英文文本中均使用缩写)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| ACP | ACP | ACP(Agent Client Protocol) | | |\n| AI | AI | AI(人工智能) | | |\n| API | API | | | |\n| CI | CI | | | |\n| CLI | CLI | CLI(命令行界面) | | |\n| e2e | e2e | | | |\n| HMR | HMR | HMR(热模块替换) | | |\n| JSON Schema | JSON Schema | | | |\n| JSONL | JSONL | | | |\n| LLM | LLM | LLM(大语言模型) | | |\n| MCP | MCP | | | |\n| PR | PR | PR(Pull Request) | | |\n| RAG | RAG | RAG(检索增强生成) | | |\n| SDK | SDK | | | |\n| SSE | SSE | SSE(Server-Sent Events) | | |\n\n## 英文类(中英文文本中均使用英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| agent | agent | agent(智能体) | | |\n| Agent Note | Agent Note | Agent Note(agent 决策记录) | 智能体注记、智能体笔记 | 本仓库中由 agent 撰写的提案与决策记录 |\n| agent harness | agent harness | agent harness(智能体框架) | | agent 组合词(agent harness/workflow/loop/skill 等)整体保留英文;未括注过 agent 时首现按对应组合词或 agent 行处理 |\n| agent loop | agent loop | agent loop(智能体循环) | | |\n| backlog | backlog | backlog(待翻清单) | | 仅在双语翻译语境里括注`待翻清单` |\n| blob hash | blob hash | | | `git hash-object` 的结果 |\n| Cordis | Cordis | | | |\n| dispose | dispose | dispose(资源释放) | | |\n| doc-sync | doc-sync | doc-sync(文档同步门禁) | | |\n| fiber | fiber | | | |\n| fixture | fixture | fixture(测试前置数据) | | |\n| fork | fork | | | |\n| Function Calling | Function Calling | Function Calling(函数调用) | | |\n| harness | harness | | | |\n| harness engineering | harness engineering | | | |\n| lint | lint | | | |\n| mock | mock | | | 保留英文;指测试替身 |\n| loader | loader | | | |\n| manifest | manifest | manifest(元数据清单) | | |\n| monorepo | monorepo | | | |\n| Round | Round | | 回合、目标回合、Ralph 回合 | 外层策略使用 Round 时,领域层级为 Session > Round > Turn(轮次) > Step(步骤);Round 是可选的外层策略迭代,并非每个会话轮次都具有的通用层级。Goal Round 与 Ralph Round 均保留英文。一个 Round 承载一个轮次,步骤隶属于该轮次;明确的零步骤轮次仍保持原义。 |\n| schema | schema | | | |\n| schema DSL | schema DSL | | | |\n| seam | seam | | | 与 `extension point` 是不同概念;根据具体语境,可译为`服务边界`或`可替换点` |\n| skill | skill | skill(技能) | | |\n| spawn | spawn | | | |\n| steering | steering | steering(中途引导) | | |\n| task id | task id | | 任务 id | 保留英文 |\n| subagent | subagent | | | |\n| thinking | thinking | | | API 字段保留英文;描述模型模式时译为`思考` |\n| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志 |\n| waterfall | waterfall | waterfall(瀑布式事件) | | |\n| wheel | wheel 包 | | | Python 打包格式 |\n| worktree | worktree | | | git 工作区概念 |\n| Zstandard | Zstandard | | | RFC 8878 compression format; `zstd` remains a code value. |\n\n## 双语类(中英文文本各自使用中英文)\n\n| English | 中文 | 首次出现 | 不要译作 | 备注 |\n|---|---|---|---|---|\n| adapter | 适配器 | | | |\n| adapter contract | 适配器契约 | 适配器契约(adapter contract) | | |\n| append-only | 仅追加 | | | |\n| artifact | 产物 | | | |\n| backend | 后端 | | | |\n| background task | 后台任务 | | | |\n| block | 块 | | | |\n| build target | 构建目标 | | | |\n| cancel | 取消 | | | |\n| feature | 功能 | | 能力 | SDK 产品与工程模型中的可管理产品单元 |\n| feature option | 功能选项 | | variant | 一项 SDK 功能内有限、可选择的实现或配置 |\n| checkpoint | 检查点 | | | |\n| chunk | 分片 | | | |\n| compaction | 压缩 | 压缩(compaction) | | |\n| companion tool | 配套工具 | | | |\n| Cordis plugin config | Cordis 插件配置 | | | Cordis 插件公开的 `Config` 对象或配置结构 |\n| config key | 配置键 | | | Cordis 插件配置中的单个字段 |\n| consumer | 消费方 | | | |\n| content block | 内容块 | | | |\n| Cookbook | 实操手册 | | | 文档标题用语 |\n| context | 上下文 | | | |\n| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指\"另一侧\"时可写「另一侧」 |\n| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | |\n| contract | 契约 | | | 如:`pairing contract` →`配对契约` |\n| Cordis config entry | Cordis 配置项 | | | 指 `cordis.yml` 插件列表中的一项;插件实现本身写`Cordis 插件` |\n| Cordis plugin | Cordis 插件 | | | Cordis 加载的插件实现,不指 `cordis.yml` 中的一项配置 |\n| coverage | 覆盖率 | | | |\n| crash recovery | 崩溃恢复 | | | |\n| deploy root | 部署根目录 | | | |\n| durability | 持久性 | | | |\n| feature requirement | 功能依赖 | | | 功能或功能选项通过 `requires` 声明的关系 |\n| enforcement frontier | 执行红线 | | 强制边界 | i18n 配对机制用语:manifest `required` 清单所划的门禁生效范围;与金标样例(style-samples ⑦)一致 |\n| ergonomics | 易用性 / 开发体验 | | 人体工学 | API 或面向模型的接口用「易用性」;工具链或开发者工作流用「开发体验」 |\n| event | 事件 | | | |\n| event log | 事件日志 | | | |\n| event stream | 事件流 | | | |\n| event-sourced | 事件溯源 | | | 沿用 DDD 社区通行译法 |\n| Executive summary | 摘要 | | | 事故复盘标题用语 |\n| executor | 执行器 | | | |\n| expected output | 预期输出 | | 金标 | 指 snapshot 比较产物;翻译语料的人工校准样例不在此列 |\n| extension | 扩展 | | | |\n| extension point | 扩展点 | | | 注意与 `seam` 区分 |\n| fail-fast | 快速失败 | | | |\n| fenced code block | 围栏代码块 | | | 沿用 MDN 中文翻译 |\n| fingerprint | 指纹 | | | 通用内容指纹;双语配对机制使用 sidecar record 记录两侧 blob hash |\n| finish reason | 结束原因 | | | |\n| foreground run | 前台运行 | | | |\n| freshness | 新鲜度 | | | 沿用 MDN 中文翻译;在本项目中指译文相对源文的同步状态 |\n| hook | 钩子 | | | |\n| implementation | 实现 | | | |\n| inference | 推理 | 推理(inference) | | 需要和 `reasoning` 区分时保留英文括注 |\n| info string | 信息字符串 | | | 沿用 CommonMark 中文翻译;指代码围栏 ``` 之后的语言标注 |\n| injection | 注入 | | | |\n| integration | 集成 | | | |\n| interface | 接口 | | | |\n| language switcher | 语言切换行 | | | i18n 配对机制用语:双语配对文件顶部的互链行 |\n| memory | 记忆 / 内存 | | | 与 `agent` 搭配时译为`记忆`(如 `agent memory` →`智能体记忆`);指系统资源时译为`内存` |\n| merge | 合并 | | | |\n| message | 消息 | | | |\n| mod | 模组 | | | |\n| model provider | 模型提供方 | | | |\n| module | 模块 | | | |\n| non-escalation | 非升权 | | 非升级、不可升级 | 仅用于安全与权限语境,指主体不得获得超出既有授权的权限;普通升级不适用此行 |\n| npm dependency | NPM 依赖 | | | `package.json` 中的包关系;`dependencies`、`devDependencies` 等字段保持原样 |\n| opt-out ratio | opt-out 比例 | | 退出检查比例 | |\n| orphan | 遗留 | | 孤儿、孤立 | 指英文源已不存在的 `.zh.md`(如「遗留译文」);进程语境按 OS 惯用语译「孤儿进程」 |\n| orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 |\n| package | 包 | 包(package) | | 指 npm 包(`@deepseek-ai/dsh-*`);`package.json` 等代码标识保持原样 |\n| pairing | 配对 | | | |\n| parent-subset grants | 父级子集授权 | | 父集合授权 | 指授权范围仅限于父级所持授权的子集 |\n| peer dependency | 对等依赖 | 对等依赖(peer dependency) | | |\n| permission | 权限 | | | |\n| persistence | 持久化 | | | |\n| pipeline | 流水线 | | | |\n| plugin | 插件 | | | |\n| prompt | 提示词 | | | |\n| provider | 提供方 | | | |\n| provider-neutral | 提供方无关 | | | |\n| quality gate | 质量门禁 | | | |\n| quiescence | 完全停稳 | | 静默、静止状态 | 指生命周期工作全部结算后的状态 |\n| reasoning | 推理 | 推理(reasoning) | | 需要和 `inference` 区分时保留英文括注 |\n| reasoning_content | 思考内容 | | | |\n| registry | 注册表 | | | |\n| replay | 回放 | | | |\n| resume | 恢复 | | | |\n| runtime | 运行时 | | | |\n| same-world subprocess | 与宿主共享文件系统和内核的子进程 | | 同世界子进程 | |\n| sandbox | 沙箱 | | | |\n| service | 服务 | | | |\n| serving surface | 对外服务接口 | | | |\n| session | 会话 | | | |\n| session event | 会话事件 | | | |\n| sidecar record | 伴随记录 | | 旁挂记录 | 指与文档同目录的伴随记录文件 |\n| smoke test | 冒烟测试 | | | |\n| snapshot | 快照 | | | |\n| source of truth | 真源 | | | |\n| spine | 主干 | | | |\n| staged | 暂存 | | | 沿用 git 官方中文翻译 |\n| stale | 陈旧 | | 过期 | 与 `fresh`(`新鲜`)成对;门禁输出中保留英文 `stale` 不翻译;`expired` 才译为`过期` |\n| step | 步骤 | | | |\n| stream | 流 | | | |\n| streaming | 流式输出 | | | |\n| structural signature | 结构签名 | | | i18n 配对机制用语:门禁比对两侧文件时提取的有序结构序列(标题层级、代码块、列表等) |\n| Summary | 概述 | | | 事故复盘标题用语 |\n| system prompt | 系统提示词 | | | |\n| taxonomy | 分类体系 | | | |\n| token usage | token 用量 | | | |\n| tool | 工具 | | | |\n| tool call | 工具调用 | | | |\n| tool result | 工具结果 | | | |\n| tool schema | 工具 schema | | | |\n| toolkit | 工具包 | | | |\n| turn | 轮次 | | | |\n| VFS | VFS | 虚拟文件系统(VFS) | | |\n| typecheck | 类型检查 | | | |\n| vocabulary | 词汇 | | | |\n| wire format | 协议格式 | 协议格式(wire format) | | |\n| workflow | 工作流 | | | |\n| wrapper | 包装层 | | | 软件层或 SDK 包装层 |\n| wrapper script | 包装脚本 | | | 可执行脚本包装层 |\n\n\n## Output Format\n\nProduce your output in three XML sections:\n\nThe outer section tags are framing. If Markdown inside any section body contains a line consisting only of ``, ``, ``, ``, ``, or ``, prefix that line with `\\`. If the original line already has one or more backslashes immediately before the tag, add one more. The parser removes exactly one framing escape; tags mentioned inline need no escaping.\n\n```xml\n\n(Complete translation of the source document)\n\n\n\n(Self-review notes, one correction per line with category tag, e.g.)\n- [Tone] \"旁挂记录\" → \"伴随记录\"(生造词)\n- [Sentence] 第 3 段补充逗号断句\n- [Punctuation] 两处破折号替换为冒号\n- 无修正\n\n\n\n(Final translation after corrections)\n\n```\n\n## Self-Review Instructions\n\nAfter writing ``, re-read it in the target language only, without looking at the source. Check by category:\n\n**Structure**\n- Is the heading hierarchy, list shape, and code block content identical to the source?\n- Are ALL comments inside code blocks left untranslated (byte-identical to source)?\n- Is the language switcher line correctly flipped (not copied from source)?\n- Are link targets preserved, and are spaces after bold markers present only before Latin letters, digits, or CJK ideographs?\n- Are wrapper-tag lines inside section bodies escaped with one additional backslash?\n\n**Tone & Style**\n- Does every sentence read as if originally written by a native speaker?\n- Is there any colloquial, casual, or overly informal phrasing?\n\n**Sentence Structure**\n- Are there run-on sentences that need breaking?\n- Are there stiff passive constructions that should be converted to active voice?\n\n**Word Choice**\n- Are there overly literal translations that sound unnatural?\n- Is the same target-language word used to translate two distinct source concepts?\n- Is any slang or internal jargon present?\n\n**Terminology**\n- For a Chinese target, are first-occurrence glosses correctly applied (not missing, not repeated)? For an English target, are Chinese glosses absent?\n- Are any \"不要译作\" forbidden translations present?\n- For unlisted terms, does a Chinese target use established Chinese precedent or retain the source term as pending, and does an English target use established English terminology or preserve only an ambiguous source term with a short English gloss?\n\n**Punctuation** (when target is Chinese)\n- Are there em-dashes that should be replaced with colons, periods, or commas?\n- Are list items ending with commas instead of semicolons?\n- Do RFC 2119 keywords preserve the source emphasis exactly?\n\nRecord corrections in `` with category tags. Then output the corrected version in ``. If no corrections are needed, write \"无修正\" in `` and copy the translation unchanged into ``.\n\n## Examples\n\nBelow are representative examples of common problems and their corrections. Follow the \"Good\" versions.\n\n### Colloquial verb → Professional verb\n- Source: `The repo pins pnpm@11.7.0 in package.json`\n- Bad: `仓库在 package.json 中钉住 pnpm@11.7.0`\n- Good: `该仓库在 package.json 中固定使用 pnpm@11.7.0`\n\n### Run-on sentence → Natural phrasing with pause\n- Source: `Read docs/architecture.md before changing anything under packages/.`\n- Bad: `改动 packages/ 下的任何东西之前先读 docs/architecture.md。`\n- Good: `在修改 packages/ 目录下的任何内容之前,请先阅读 docs/architecture.md。`\n\n### Stiff passive voice → Active and natural\n- Source: `a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.`\n- Bad: `门禁绿意味着这对文档曾在当前内容上被确认一致,不意味着这次确认本身是对的。`\n- Good: `门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。`\n\n### Invented word → Natural expression\n- Source: `A sidecar record of both blob hashes makes consistency checkable`\n- Bad: `旁挂记录两侧 blob hash,使一致性可检查`\n- Good: `伴随记录保存两侧 blob hash,使一致性可检查`\n\n### Em-dash → Colon/period\n- Source: `FIXME — an issue that should block a new release. A release should not ship with an open FIXME unless reviewers explicitly agree the change can be merged anyway.`\n- Bad: `FIXME——应当阻塞新版本发布的问题。除非评审者明确同意可以照常合入,发布不应带着未解决的 FIXME 出门。`\n- Good: `FIXME:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 FIXME。`\n\n### Overly literal → Meaningful rendering\n- Source: `awkward phrasing is easier to hear without the source anchoring you`\n- Bad: `没有源文锚着,别扭的表述更容易被听出来`\n- Good: `不对照原文时,更容易察觉别扭的表达`\n\n### Terminology — do not translate what should be kept in English\n- Source: `typed service seams, and explicit extension points`\n- Bad: `类型化的服务 seam(扩展点)与显式扩展点`\n- Good: `类型化的服务 seam 与显式扩展点`\n\n### Slang/jargon → Professional phrasing\n- Source: `The committed agent workflow lives in .agents/skills/dsh-translate-docs`\n- Bad: `进仓的 agent 工作流见 .agents/skills/dsh-translate-docs`\n- Good: `仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs`\n\n### \"For humans\" — translate the intent, not the word\n- Source: `For humans, start with the development guide`\n- Bad: `对于人工读者,请先从开发指南开始`(\"人工读者\"生硬)\n- Good: `面向开发者:请先阅读开发指南`(\"开发者\"自然,且中文里冒号在此处更自然)\n\n### Code block comments — NEVER translate\n- Source code block contains: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)`\n- Bad: `# 全屏 TUI coding agent(需要 DEEPSEEK_API_KEY)`\n- Good: `# full-screen TUI coding agent (needs DEEPSEEK_API_KEY)` (keep exactly as-is, byte-for-byte)\n\n### Language switcher — flip direction\n- Source file (English) has: `English | [中文](README.zh.md)`\n- Bad (copying source unchanged): `English | [中文](README.zh.md)`\n- Good (flipped for Chinese file): `[English](README.md) | 中文`\n\n---\n\nNow translate the following document:"
},
{
"role": "user",
@@ -24,11 +24,11 @@
},
{
"role": "user",
- "content": "# Bilingual documentation\n\nEnglish | [中文](README.zh.md)\n\nThis repo's documentation is read by people and agents both inside and outside the company, so the README, Agent Notes, and docs tree are maintained in English and Simplified Chinese. This page defines the pairing contract, the enforcement gate, and the rollout policy; [translation-rules.md](translation-rules.md) defines how to translate; [terminology.md](terminology.md) is the terminology source of truth. The committed agent workflow lives in [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md).\n\n## The pairing contract\n\n- **Both languages carry equal authority.** A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.\n- **A pair is three sibling files.** The English `foo.md`, the Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files.\n- **The consistency record.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p `), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. After bringing the pair back in line, `pnpm run verify-translation-pairing --write` re-records both hashes; that yaml diff is the reviewable act of confirming consistency.\n- **Language switcher.** Both files link to each other immediately after their H1 heading: the English file carries `English | [中文](foo.zh.md)` and the Chinese file carries `[English](foo.md) | 中文`.\n- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, link targets, and verbatim code blocks match one to one across the pair — see [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).\n\n## The gate: verify-translation-pairing\n\n`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:\n\n1. Every file listed as `required` in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair.\n2. Every pair that exists at all — required or not — is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and every link target apart from the switcher.\n3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all.\n4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named Agent Notes merge bilingual from birth.\n\n`pnpm run verify-translation-pairing --list` prints the current pairing state of every document in scope — missing, out-of-sync, or ok — and is the work list for translation batches. It never fails; it reports.\n\nThe practical rule this gate creates: **when a PR edits either side of a paired document, the same PR updates the counterpart and re-records the pair** (run the [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill, then `--write`), exactly like the repo's existing doc-sync rule for code and READMEs. A PR that leaves a pair out of sync goes red in CI.\n\nThe gate's limit, stated plainly: **a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.** It checks hashes and shape; it cannot judge whether the two sides actually say the same thing, or whether the wording is accurate, well-termed, and natural — that is the reviewer's half of the contract, per [translation-rules.md](translation-rules.md). A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review.\n\n## Scope, exclusions, and rollout\n\n**Scope**: the root `README.md`, everything under `.agents/notes/**`, `docs/**`, and `python/**`. Package READMEs (`packages/**`) join the scope in a later batch.\n\n**Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them):\n\n- `docs/cordis-catalog/`, `docs/tool-catalog/`, `docs/config-catalog.md`, `docs/persistence-catalog.md`, and `docs/module-graph.md` — generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list.\n- `docs/AGENTS.md` and `.agents/notes/**/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`.\n- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.\n- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.\n\n**Rollout**: a date-named document (`yyyy-mm-dd-*.md`, i.e. an Agent Note) dated on or after the manifest's `requiredSince` cutoff must merge with its pair. Earlier dates are backlog, including files created on the cutoff's eve. An Agent Note filename records its first-proposed date, so backdating past the cutoff is a review-visible violation. The manifest's `required` list is the current enforcement frontier, not the goal of full coverage. Translation batches add paths to `required`, ratcheting the gate forward. Unlisted documents remain visible in `--list`, while every existing pair is governed by the full contract. Because later edits must update both sides, expand `required` only as fast as translation review can support.\n\n## Division of labor\n\nCounterparts here are produced by an agent running [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) and reviewed by a human — inference is cheap here, review attention is the scarce resource. The gate checks pair completeness, recorded hashes, switchers, and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode. The prompt contract is executable: [scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) renders the committed template (terminology injected; the template carries its own calibrated rules) into either direction and parses the three-section response, while `verify-translation-prompt` exercises both render directions and the checked-in example in `doc-sync`.\n"
+ "content": "# Bilingual documentation\n\nEnglish | [中文](README.zh.md)\n\nThis repo's documentation is read by people and agents both inside and outside the company, so the README, Agent Notes, and docs tree are maintained in English and Simplified Chinese. This page defines the pairing contract, the enforcement gate, and the rollout policy; [translation-rules.md](translation-rules.md) defines how to translate; [terminology.md](terminology.md) is the terminology source of truth. The committed agent workflow lives in [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md).\n\n## The pairing contract\n\n- **Both languages carry equal authority.** A document may be authored and reviewed in either language first — a Chinese-first Agent Note is as legitimate as an English-first one — and the counterpart is translated from it. Neither file outranks the other; what binds them is that they must say the same thing.\n- **A pair is three sibling files.** The English `foo.md`, the Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`, all in the same directory. No locale directories, no separate translation repo, no interleaved bilingual files. Pairs merge whole: a PR never lands one language without the other two files.\n- **The consistency record.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last time the two were confirmed to say the same thing:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p `), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. After bringing the pair back in line, `pnpm run verify-translation-pairing --write` re-records both hashes; that yaml diff is the reviewable act of confirming consistency.\n- **Language switcher.** Both files link to each other immediately after their H1 heading: the English file carries `English | [中文](foo.zh.md)` and the Chinese file carries `[English](foo.md) | 中文`.\n- **Structure mirrors the counterpart.** Heading depths and order, list kinds, ordered-list starts, list item counts, table row and column counts, link targets, and verbatim code blocks match one to one across the pair — see [translation-rules.md](translation-rules.md) for the full preservation rules. Existing Markdown gates apply to `.zh.md` files unchanged (`verify-md-wrap`, `verify-md-links`).\n\n## The gate: verify-translation-pairing\n\n`pnpm run verify-translation-pairing` (part of `doc-sync`, which contributors run locally for documentation changes and CI runs exhaustively) enforces the contract mechanically:\n\n1. Every file listed as `required` in [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) has a complete pair.\n2. Every pair that exists at all — required or not — is complete and consistent: all three files present, each side's current blob hash equals the recorded one (editing either side without re-confirming the pair goes red), both sides carry the language switcher, and the structural signatures match in order — heading depths, verbatim code blocks (info string and content), table row and column counts, list kinds, ordered-list starts, item counts, and every link target apart from the switcher.\n3. Files listed as `excluded` have no `.zh.md` and no `.i18n.yaml` at all.\n4. Every date-named document (`yyyy-mm-dd-*.md`) dated on or after the manifest's `requiredSince` cutoff has a complete pair — new date-named Agent Notes merge bilingual from birth.\n\n`pnpm run verify-translation-pairing --list` prints the current pairing state of every document in scope — missing, out-of-sync, or ok — and is the work list for translation batches. It never fails; it reports.\n\nThe practical rule this gate creates: **when a PR edits either side of a paired document, the same PR updates the counterpart and re-records the pair** (run the [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill, then `--write`), exactly like the repo's existing doc-sync rule for code and READMEs. A PR that leaves a pair out of sync goes red in CI.\n\nThe gate's limit, stated plainly: **a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.** It checks hashes and shape; it cannot judge whether the two sides actually say the same thing, or whether the wording is accurate, well-termed, and natural — that is the reviewer's half of the contract, per [translation-rules.md](translation-rules.md). A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review.\n\n## Scope, exclusions, and rollout\n\n**Scope**: the root `README.md`, everything under `.agents/notes/**`, `docs/**`, and `python/**`. Package READMEs (`packages/**`) join the scope in a later batch.\n\n**Excluded** (never paired, and the gate rejects a `.zh.md` or `.i18n.yaml` for them):\n\n- `docs/cordis-catalog/`, `docs/tool-catalog/`, `docs/config-catalog.md`, `docs/persistence-catalog.md`, `docs/module-graph.md`, `docs/agent-lifecycle.md`, `docs/capability-seams.md`, `docs/event-producer-consumer.md`, `docs/graph-atlas.md`, and `docs/tool-execution-pipeline.md` — generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list.\n- `docs/AGENTS.md` and `.agents/notes/**/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`.\n- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction.\n- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior.\n\n**Rollout**: a date-named document (`yyyy-mm-dd-*.md`, i.e. an Agent Note) dated on or after the manifest's `requiredSince` cutoff must merge with its pair. Earlier dates are backlog, including files created on the cutoff's eve. An Agent Note filename records its first-proposed date, so backdating past the cutoff is a review-visible violation. The manifest's `required` list is the current enforcement frontier, not the goal of full coverage. Translation batches add paths to `required`, ratcheting the gate forward. Unlisted documents remain visible in `--list`, while every existing pair is governed by the full contract. Because later edits must update both sides, expand `required` only as fast as translation review can support.\n\n## Division of labor\n\nCounterparts here are produced by an agent running [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) and reviewed by a human — inference is cheap here, review attention is the scarce resource. The gate checks pair completeness, recorded hashes, switchers, and its documented structural signature. Review still owns translation quality, terminology, and structural requirements that the signature does not encode. The prompt contract is executable: [scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) renders the committed template (terminology injected; the template carries its own calibrated rules) into either direction and parses the three-section response, while `verify-translation-prompt` exercises both render directions and the checked-in example in `doc-sync`.\n"
},
{
"role": "assistant",
- "content": "# 双语文档\n\n[English](README.md) | 中文\n\n本仓库的文档会被公司内外的人和 agent(智能体)阅读,因此 README、Agent Note(agent 决策记录)与 docs 目录树以英文和简体中文双语维护。本页定义配对契约、强制门禁与推进策略;[translation-rules.md](translation-rules.md) 定义如何翻译;[terminology.md](terminology.md) 是术语真源。仓库内置的 agent 工作流见 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md)。\n\n## 配对契约\n\n- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。\n- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。\n- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 git blob hash:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n 用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。记录的 hash 还能还原任一侧上次确认时的确切文本(`git cat-file -p `),所以失去同步的配对是「把被改的一侧与其上次确认状态做 diff、再最小化地修补另一侧」,从不整篇重译。两侧对齐后,`pnpm run verify-translation-pairing --write` 重新记录两个 hash;那份 yaml diff 就是「确认一致」这个动作本身,可以被评审。\n- **语言切换行。** 两个文件在各自 H1 标题之后立即互链:英文文件带 `English | [中文](foo.zh.md)`,中文文件带 `[English](foo.md) | 中文`。\n- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、链接目标与逐字节一致的代码块在配对两侧一一对应;完整保持规则见 [translation-rules.md](translation-rules.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。\n\n## 门禁:verify-translation-pairing\n\n`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份契约:\n\n1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。\n2. 任何已存在的配对(无论是否 required)都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致:标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。\n3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。\n4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,都必须有完整配对;新建的日期命名 Agent Note 从创建起便须配齐中英文。\n\n`pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态(missing、out-of-sync 或 ok),是翻译批次的工作清单。它从不失败;它只报告。\n\n这个门禁带来的实际规则是:**当一个 PR 修改了已配对文档的任一侧时,同一个 PR 更新另一侧并重新记录配对**(运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill(技能),再 `--write`),与本仓库既有的代码与 README 的 doc-sync 规则完全一致。留下失去同步的配对的 PR 会在 CI 变红。\n\n把门禁的边界说白:**门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。** 它检查记录的 hash 与结构签名;它无法判断两侧是否真的在说同样的话,也无法判断措辞是否准确、术语是否得当、行文是否自然;这部分契约由评审者把关,见 [translation-rules.md](translation-rules.md)。重新记录了 hash 但另一侧翻得潦草的配对能通过门禁;它不得通过评审。\n\n## 范围、排除与推进\n\n**范围**:根 `README.md`,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部内容。包(package)README(`packages/**`)在后续批次加入范围。\n\n**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):\n\n- `docs/cordis-catalog/`、`docs/tool-catalog/`、`docs/config-catalog.md`、`docs/persistence-catalog.md` 与 `docs/module-graph.md`:生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。\n- `docs/AGENTS.md` 与 `.agents/notes/**/AGENTS.md`:agent 指令,与根 `AGENTS.md` 一样只以英文维护。\n- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。\n- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。\n\n**推进**:以日期命名的文档(`yyyy-mm-dd-*.md`,即 Agent Note),只要标注日期等于或晚于 manifest 的 `requiredSince` 分界日期,合并时就必须配齐双语文件。更早日期的文件属于 backlog(待翻清单),包括分界前夜创建的文件。Agent Note 文件名记录首次提出日期,因此倒填日期绕过分界属于评审可见的违规。manifest 中的 `required` 列表是当前执行红线,并非全量覆盖这一最终目标。翻译批次将路径加入 `required`,使门禁只向前收紧。未列入的文档仍可通过 `--list` 查看,而任何已存在的配对都受完整契约约束。后续修改必须同步更新两侧,因此 `required` 的扩展速度不能超过翻译评审的承载能力。\n\n## 分工\n\n这里的对侧文件由运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 的 agent 生成,再由人评审:在这里推理(inference)很便宜,评审注意力才是稀缺资源。门禁负责检查配对是否完整、记录的 hash、语言切换行以及本文列出的结构签名;翻译质量、术语和签名未涵盖的结构要求仍由评审把关。提示词契约也有可执行实现:[scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) 会把仓库内置的模板(注入术语表;模板自带经人工校准的规则)渲染为英译中或中译英两个方向的提示词,并解析三段式响应;`doc-sync` 中的 `verify-translation-prompt` 会检查两个渲染方向与仓库内示例。\n"
+ "content": "# 双语文档\n\n[English](README.md) | 中文\n\n本仓库的文档会被公司内外的人和 agent(智能体)阅读,因此 README、Agent Note(agent 决策记录)与 docs 目录树以英文和简体中文双语维护。本页定义配对契约、强制门禁与推进策略;[translation-rules.md](translation-rules.md) 定义如何翻译;[terminology.md](terminology.md) 是术语真源。仓库内置的 agent 工作流见 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md)。\n\n## 配对契约\n\n- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。\n- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。\n- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 git blob hash:\n\n ```yaml\n foo.md: 3f786850e387550fdab836ed7e6dc881de23001b\n foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b\n ```\n\n 用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。记录的 hash 还能还原任一侧上次确认时的确切文本(`git cat-file -p `),所以失去同步的配对是「把被改的一侧与其上次确认状态做 diff、再最小化地修补另一侧」,从不整篇重译。两侧对齐后,`pnpm run verify-translation-pairing --write` 重新记录两个 hash;那份 yaml diff 就是「确认一致」这个动作本身,可以被评审。\n- **语言切换行。** 两个文件在各自 H1 标题之后立即互链:英文文件带 `English | [中文](foo.zh.md)`,中文文件带 `[English](foo.md) | 中文`。\n- **结构与另一侧一一对应。** 标题深度与顺序、列表类型、有序列表起始编号、列表项数量、表格行列数、链接目标与逐字节一致的代码块在配对两侧一一对应;完整保持规则见 [translation-rules.md](translation-rules.md)。既有 Markdown 门禁对 `.zh.md` 文件原样生效(`verify-md-wrap`、`verify-md-links`)。\n\n## 门禁:verify-translation-pairing\n\n`pnpm run verify-translation-pairing`(`doc-sync`(文档同步门禁)的一环,贡献者会针对文档变更在本地运行,CI 则会完整运行)机械地强制执行这份契约:\n\n1. [scripts/translation-pairing.manifest.json](../../scripts/translation-pairing.manifest.json) 中 `required` 列出的每个文件都有完整配对。\n2. 任何已存在的配对(无论是否 required)都完整且一致:三个文件齐全、每一侧的当前 blob hash 等于记录值(改了任一侧而没重新确认配对就变红)、双方都带语言切换行、结构签名按序一致:标题深度、逐字节一致的代码块(信息字符串与内容)、表格行列数、列表类型、有序列表起始编号、列表项数量,以及除切换行之外的每个链接目标。\n3. 列为 `excluded` 的文件完全没有 `.zh.md`,也没有 `.i18n.yaml`。\n4. 凡文件名符合 `yyyy-mm-dd-*.md` 且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,都必须有完整配对;新建的日期命名 Agent Note 从创建起便须配齐中英文。\n\n`pnpm run verify-translation-pairing --list` 打印范围内每篇文档的当前配对状态(missing、out-of-sync 或 ok),是翻译批次的工作清单。它从不失败;它只报告。\n\n这个门禁带来的实际规则是:**当一个 PR 修改了已配对文档的任一侧时,同一个 PR 更新另一侧并重新记录配对**(运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) skill(技能),再 `--write`),与本仓库既有的代码与 README 的 doc-sync 规则完全一致。留下失去同步的配对的 PR 会在 CI 变红。\n\n把门禁的边界说白:**门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。** 它检查记录的 hash 与结构签名;它无法判断两侧是否真的在说同样的话,也无法判断措辞是否准确、术语是否得当、行文是否自然;这部分契约由评审者把关,见 [translation-rules.md](translation-rules.md)。重新记录了 hash 但另一侧翻得潦草的配对能通过门禁;它不得通过评审。\n\n## 范围、排除与推进\n\n**范围**:根 `README.md`,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部内容。包(package)README(`packages/**`)在后续批次加入范围。\n\n**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):\n\n- `docs/cordis-catalog/`、`docs/tool-catalog/`、`docs/config-catalog.md`、`docs/persistence-catalog.md`、`docs/module-graph.md`、`docs/agent-lifecycle.md`、`docs/capability-seams.md`、`docs/event-producer-consumer.md`、`docs/graph-atlas.md` 与 `docs/tool-execution-pipeline.md`:生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。\n- `docs/AGENTS.md` 与 `.agents/notes/**/AGENTS.md`:agent 指令,与根 `AGENTS.md` 一样只以英文维护。\n- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md):二者本身即为中英对照文档。\n- [translation-prompt.md](translation-prompt.md):自动翻译流水线的提示词模板;正文逐字进入模型请求,配对翻译会改变流水线行为。\n\n**推进**:以日期命名的文档(`yyyy-mm-dd-*.md`,即 Agent Note),只要标注日期等于或晚于 manifest 的 `requiredSince` 分界日期,合并时就必须配齐双语文件。更早日期的文件属于 backlog(待翻清单),包括分界前夜创建的文件。Agent Note 文件名记录首次提出日期,因此倒填日期绕过分界属于评审可见的违规。manifest 中的 `required` 列表是当前执行红线,并非全量覆盖这一最终目标。翻译批次将路径加入 `required`,使门禁只向前收紧。未列入的文档仍可通过 `--list` 查看,而任何已存在的配对都受完整契约约束。后续修改必须同步更新两侧,因此 `required` 的扩展速度不能超过翻译评审的承载能力。\n\n## 分工\n\n这里的对侧文件由运行 [dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md) 的 agent 生成,再由人评审:在这里推理(inference)很便宜,评审注意力才是稀缺资源。门禁负责检查配对是否完整、记录的 hash、语言切换行以及本文列出的结构签名;翻译质量、术语和签名未涵盖的结构要求仍由评审把关。提示词契约也有可执行实现:[scripts/translation-prompt.ts](../../scripts/translation-prompt.ts) 会把仓库内置的模板(注入术语表;模板自带经人工校准的规则)渲染为英译中或中译英两个方向的提示词,并解析三段式响应;`doc-sync` 中的 `verify-translation-prompt` 会检查两个渲染方向与仓库内示例。\n"
},
{
"role": "user",
@@ -40,11 +40,11 @@
},
{
"role": "user",
- "content": "# Agent Note: Bilingual documentation via paired sibling files and a pairing gate\n\nStatus: implemented\n\nEnglish | [中文](2026-07-02-bilingual-docs-and-pairing-gate.zh.md)\n\n## Problem\n\nThis repo's README and docs tree are read by people and agents inside and outside the company, in both English and Chinese. Maintaining a second language by hand, with no mechanism, is how translations rot: one side moves on, the other silently lies, and no gate notices. The repo's standing answer to invariants of this kind is to encode them as a mechanical check (see [quality gates](2026-06-11-quality-gates.md) and [doc-sync enforcement](2026-06-11-doc-sync-enforcement.md)), so the bilingual policy ships with one.\n\n## Decision\n\n- **Paired sibling files with equal authority.** A documentation pair is three sibling files: English `foo.md`, Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`. Neither language is canonical — a document may be authored and reviewed Chinese-first and translated to English afterwards, or the reverse; what binds the pair is that both sides must say the same thing, and pairs merge whole (both languages plus the record, never one alone). Policy: [docs/i18n/README.md](../../../../docs/i18n/README.md); translation rules: [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md); terminology source of truth: [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md).\n- **A sidecar record of both blob hashes makes consistency checkable.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last confirmed-consistent state. An edit to either side without re-confirming the pair is then mechanically detectable as a pure content comparison — no history lookup — and the hashes are computable for files edited in the same PR, which a commit-hash record is not. Re-recording (`verify-translation-pairing --write`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.\n- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: required pairs exist, every existing pair is complete (all three files) and consistent (both hashes match, switcher links both ways, structural signatures identical), excluded (generated or bilingual-by-construction) files stay unpaired, and date-named documents on or after the manifest's `requiredSince` cutoff have complete pairs. The `required` list in [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) is a ratchet: each merged translation batch adds its files, so coverage only grows.\n- **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent.\n\n## Alternatives considered\n\n- **English as the canonical source with a fingerprint inside the translation** — the design first proposed for this Agent Note: `.zh.md` files carried an HTML comment recording the English source's blob hash, and translation flowed EN → ZH only. Revised in review: the team wants Chinese-first authoring (write and review a Chinese Agent Note, then translate to English) with the two languages holding equal authority, which a one-directional canonical model cannot express. The sidecar record covering BOTH sides replaced the in-file one-directional fingerprint; the blob-hash mechanics survived unchanged.\n- **Locale directories (`docs/en/` + `docs/zh/`, the Kubernetes/ECharts model)** — rejected: this repo has no docs-site framework to map locales to routes, moving every English file would churn every existing cross-reference, and `verify-md-links`/`verify-doc-refs` would need path-mapping logic instead of working unchanged.\n- **A separate translation repo (the PingCAP `docs`/`docs-cn` model)** — rejected: right for a docs product with independent release trains, overkill for a monorepo's own documentation; it also puts the translation outside the reach of this repo's gates.\n- **Interleaved bilingual files (single file, both languages)** — rejected: doubles every diff, breaks the one-line-per-paragraph convention's diff ergonomics, and makes partial inconsistency invisible.\n- **Commit-hash records (the MDN `l10n.sourceCommit` model)** — rejected in favor of blob hashes: a same-PR edit has no commit hash yet, so the MDN model cannot express \"consistent as of the state this PR introduces\", and verifying it requires git history instead of file content.\n- **Comparing git timestamps of the pair (no record)** — rejected: formatting-only edits would false-positive, and a counterpart committed after an unrelated edit would false-negative; content identity is the only signal that means what the gate claims.\n\n## Industry precedent\n\nPaired sibling files with locale suffixes are the dominant Chinese big-tech convention (ant-design `index.zh-CN.md`/`index.en-US.md`; arco-design `README.zh-CN.md` with a top-of-file switcher; Apache ShardingSphere's 387 `.cn.md`/`.en.md` pairs) — but none of those repos *enforce* pairing or consistency in CI; the convention holds by review alone. Consistency automation exists outside China: MDN's `l10n.sourceCommit` front-matter fingerprint, Vue's Ryu-Cho action (upstream-commit watcher that opens issues/PRs for stale translations), Kubernetes' localization drift scripts, and Microsoft's Azure co-op-translator (source-hash-driven LLM re-translation in CI). This design combines the two: the Chinese-ecosystem file layout with a hash-pair gate, plus a committed agent skill in place of a bot service.\n\n## Consequences\n\n- Editing either side of a paired document obligates the same PR to update the counterpart and re-record the pair — the gate makes the doc-sync rule bilingual, and CI (not reviewer memory) carries the invariant.\n- Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, \"who confirmed these consistent, and when\" is answerable from git blame on the yaml.\n- When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring.\n- Generated docs (`cordis-catalog/`, `tool-catalog/`, `module-graph.md`) are excluded for now; the planned follow-up is to teach their generators to emit Chinese alongside English, at which point they leave the exclusion list.\n- Rollout is incremental by design: documents outside `required` are visible backlog (`--list`), not red CI, so pairs land in reviewable batches without a big-bang PR. A date-named document dated on or after the manifest's `requiredSince` cutoff merges bilingual or not at all, so new date-named Agent Notes do not enlarge that backlog.\n- The recorded hashes double as the update tool (`git cat-file -p ` recovers either side's last-confirmed text for a minimal diff-based update), so re-translation of whole files is never forced by the mechanism.\n"
+ "content": "# Agent Note: Bilingual documentation via paired sibling files and a pairing gate\n\nStatus: implemented\n\nEnglish | [中文](2026-07-02-bilingual-docs-and-pairing-gate.zh.md)\n\n## Problem\n\nThis repo's README and docs tree are read by people and agents inside and outside the company, in both English and Chinese. Maintaining a second language by hand, with no mechanism, is how translations rot: one side moves on, the other silently lies, and no gate notices. The repo's standing answer to invariants of this kind is to encode them as a mechanical check (see [quality gates](2026-06-11-quality-gates.md) and [doc-sync enforcement](2026-06-11-doc-sync-enforcement.md)), so the bilingual policy ships with one.\n\n## Decision\n\n- **Paired sibling files with equal authority.** A documentation pair is three sibling files: English `foo.md`, Chinese `foo.zh.md`, and a consistency record `foo.i18n.yaml`. Neither language is canonical — a document may be authored and reviewed Chinese-first and translated to English afterwards, or the reverse; what binds the pair is that both sides must say the same thing, and pairs merge whole (both languages plus the record, never one alone). Policy: [docs/i18n/README.md](../../../../docs/i18n/README.md); translation rules: [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md); terminology source of truth: [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md).\n- **A sidecar record of both blob hashes makes consistency checkable.** `foo.i18n.yaml` holds the full git blob hash of each side as of the last confirmed-consistent state. An edit to either side without re-confirming the pair is then mechanically detectable as a pure content comparison — no history lookup — and the hashes are computable for files edited in the same PR, which a commit-hash record is not. Re-recording (`verify-translation-pairing --write`) produces a reviewable yaml diff: confirming consistency is an explicit, visible act in the PR.\n- **`verify-translation-pairing` joins `doc-sync`.** The gate ([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts)) enforces: required pairs exist, every existing pair is complete (all three files) and consistent (both hashes match, switcher links both ways, structural signatures identical), excluded (generated or bilingual-by-construction) files stay unpaired, and date-named documents on or after the manifest's `requiredSince` cutoff have complete pairs. The `required` list in [scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) is a ratchet: each merged translation batch adds its files, so coverage only grows.\n- **The enforcement frontier advances in coherent review batches.** A related set enters `required` only when reviewers can evaluate it as a unit. The core frontier groups [architecture](../../../../docs/architecture.md), the [Cordis primer](../../../../docs/cordis-primer.md), [defensive patterns](../../../../docs/defensive-patterns.md), the [glossary](../../../../docs/glossary.md), and [testing](../../../../docs/testing.md) because their terminology, links, and contributor contracts inform one another; admitting only part would leave the enforced corpus internally inconsistent. A site-published pair uses `pairedPages()` so the root locale projects `.zh.md` and `/en/` projects `.md`; creating a counterpart alone does not publish it.\n- **Pairing records are metadata, not Cordis Loader configuration.** Cordis configuration discovery accepts actual `.cordis.yml` and `.cordis.yaml` files while excluding `*.i18n.yaml`, even when the document name contains `cordis`. This preserves validation of executable Loader entries without parsing translation hashes as configuration.\n- **Translation is agent work with human review.** The committed workflow is [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md), following the same pattern as [dsh-code-review](../../../skills/dsh-code-review/SKILL.md): the skill carries the workflow and defers to the docs as sources of truth. The skill directs the orchestrating agent to delegate translation writing to a subagent.\n\n## Verification\n\nThe verification contract covers each boundary independently. `verify-translation-pairing` pins pair completeness, hashes, switchers, and structure; [`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) pins locale-specific source selection for published pairs; [`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) pins discovery of Loader YAML and exclusion of translation records; and the [translation-prompt runnable snapshot](../../../../scripts/translation-prompt.snapshot.ts) pins the rendered system message, five reviewed example pairs, source request, and consumed response. Together these checks make pair drift, publication drift, configuration misclassification, and model-visible prompt drift review-visible.\n\n## Alternatives considered\n\n- **English as the canonical source with a fingerprint inside the translation** — the design first proposed for this Agent Note: `.zh.md` files carried an HTML comment recording the English source's blob hash, and translation flowed EN → ZH only. Revised in review: the team wants Chinese-first authoring (write and review a Chinese Agent Note, then translate to English) with the two languages holding equal authority, which a one-directional canonical model cannot express. The sidecar record covering BOTH sides replaced the in-file one-directional fingerprint; the blob-hash mechanics survived unchanged.\n- **Locale directories (`docs/en/` + `docs/zh/`, the Kubernetes/ECharts model)** — rejected: this repo has no docs-site framework to map locales to routes, moving every English file would churn every existing cross-reference, and `verify-md-links`/`verify-doc-refs` would need path-mapping logic instead of working unchanged.\n- **A separate translation repo (the PingCAP `docs`/`docs-cn` model)** — rejected: right for a docs product with independent release trains, overkill for a monorepo's own documentation; it also puts the translation outside the reach of this repo's gates.\n- **Interleaved bilingual files (single file, both languages)** — rejected: doubles every diff, breaks the one-line-per-paragraph convention's diff ergonomics, and makes partial inconsistency invisible.\n- **Commit-hash records (the MDN `l10n.sourceCommit` model)** — rejected in favor of blob hashes: a same-PR edit has no commit hash yet, so the MDN model cannot express \"consistent as of the state this PR introduces\", and verifying it requires git history instead of file content.\n- **Comparing git timestamps of the pair (no record)** — rejected: formatting-only edits would false-positive, and a counterpart committed after an unrelated edit would false-negative; content identity is the only signal that means what the gate claims.\n\n## Industry precedent\n\nPaired sibling files with locale suffixes are the dominant Chinese big-tech convention (ant-design `index.zh-CN.md`/`index.en-US.md`; arco-design `README.zh-CN.md` with a top-of-file switcher; Apache ShardingSphere's 387 `.cn.md`/`.en.md` pairs) — but none of those repos *enforce* pairing or consistency in CI; the convention holds by review alone. Consistency automation exists outside China: MDN's `l10n.sourceCommit` front-matter fingerprint, Vue's Ryu-Cho action (upstream-commit watcher that opens issues/PRs for stale translations), Kubernetes' localization drift scripts, and Microsoft's Azure co-op-translator (source-hash-driven LLM re-translation in CI). This design combines the two: the Chinese-ecosystem file layout with a hash-pair gate, plus a committed agent skill in place of a bot service.\n\n## Consequences\n\n- Editing either side of a paired document obligates the same PR to update the counterpart and re-record the pair — the gate makes the doc-sync rule bilingual, and CI (not reviewer memory) carries the invariant.\n- Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, \"who confirmed these consistent, and when\" is answerable from git blame on the yaml.\n- When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring.\n- Generated docs (`cordis-catalog/`, `tool-catalog/`, `module-graph.md`) are excluded for now; the planned follow-up is to teach their generators to emit Chinese alongside English, at which point they leave the exclusion list.\n- Rollout is incremental by design: documents outside `required` are visible backlog (`--list`), not red CI, so pairs land in reviewable batches without a big-bang PR. A date-named document dated on or after the manifest's `requiredSince` cutoff merges bilingual or not at all, so new date-named Agent Notes do not enlarge that backlog.\n- The recorded hashes double as the update tool (`git cat-file -p ` recovers either side's last-confirmed text for a minimal diff-based update), so re-translation of whole files is never forced by the mechanism.\n"
},
{
"role": "assistant",
- "content": "# Agent Note:通过配对兄弟文件与配对门禁实现双语文档\n\nStatus: implemented\n\n[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文\n\n## 问题\n\n本仓库的 README 与 docs 目录树会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁会注意到。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。\n\n## 决策\n\n- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。\n- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。\n- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:required 的配对必须存在;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)不得配对;凡文件名以日期开头且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,也必须有完整配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单只进不退:每个合并的翻译批次将自己的文件加入其中,覆盖面只增不减。\n- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同:skill(技能)承载工作流,并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。\n\n## 曾考虑的替代方案\n\n- **英文为正典源、指纹放在译文内**:本 Agent Note 最初提出的设计:`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash,翻译只沿 EN → ZH 单向流动。评审中修订:团队需要中文先行的撰写方式(先写、先审中文 Agent Note,再译英文),两种语言同权,而单向正典模型无法表达这一点。覆盖**两侧**的伴随记录取代了文件内的单向指纹;blob hash 的机制本身保持不变。\n- **语言目录(`docs/en/` + `docs/zh/`,Kubernetes/ECharts 模式)**:否决。本仓库没有将 locale 映射到路由的文档站框架;如果移动所有英文文件,所有既有交叉引用都要随之修改;且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑,而非原样工作。\n- **独立翻译仓库(PingCAP `docs`/`docs-cn` 模式)**:否决。适合有独立发布节奏的文档产品,对 monorepo 自身的文档而言过重;还会把译文置于本仓库门禁触及不到的地方。\n- **中英混排单文件(一个文件、两种语言)**:否决。每个 diff 都翻倍,破坏一段一行约定的 diff 易读性,且局部不一致不可见。\n- **Commit hash 式记录(MDN `l10n.sourceCommit` 模式)**:否决,改用 blob hash。同一个 PR 内的改动还没有 commit hash,MDN 模式无法表达「与本 PR 引入的状态一致」,且校验它需要 git 历史而非文件内容。\n- **比较配对两侧的 git 时间戳(无记录)**:否决。纯格式化的改动会误报,一次无关改动之后提交的对侧文件会漏报;只有内容同一性这个信号才与门禁的承诺名实相符。\n\n## 业界先例\n\n带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个仓库内置的 agent skill 替代 bot 服务。\n\n## 后果\n\n- 修改已配对文档的任一侧,同一个 PR 就有义务更新对侧并重新记录配对。门禁将 doc-sync 规则双语化,不变式由 CI(而非评审者的记忆)承载。\n- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。\n- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。\n- 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让生成器在输出英文的同时输出中文,届时将这些文件移出排除清单。\n- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(待翻清单,`--list`),而非红色的 CI;因此配对按可评审的批次落地,无需一个巨型 PR。凡文件名以日期开头且日期不早于 manifest 中 `requiredSince` 分界日期的文档,都必须配齐双语文件,因此新建的日期命名 Agent Note 不会增加这份 backlog。\n- 记录的 hash 兼作更新工具(`git cat-file -p ` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),因此这套机制从不强迫整篇重译。\n"
+ "content": "# Agent Note:通过配对兄弟文件与配对门禁实现双语文档\n\nStatus: implemented\n\n[English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文\n\n## 问题\n\n本仓库的 README 与 docs 目录树会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁会注意到。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。\n\n## 决策\n\n- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../../docs/i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../../docs/i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../../docs/i18n/terminology.md)。\n- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR(Pull Request)内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。\n- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:required 的配对必须存在;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)不得配对;凡文件名以日期开头且日期不早于 manifest(元数据清单)中 `requiredSince` 分界日期的文档,也必须有完整配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单只进不退:每个合并的翻译批次将自己的文件加入其中,覆盖面只增不减。\n- **执行红线按连贯的评审批次推进。** 一组相关文档只有在评审者能够将其作为整体评估时,才进入 `required`。核心红线将[架构](../../../../docs/architecture.md)、[Cordis 入门](../../../../docs/cordis-primer.md)、[防御性模式](../../../../docs/defensive-patterns.md)、[术语表](../../../../docs/glossary.md)和[测试](../../../../docs/testing.md)归为一组,因为它们的术语、链接和贡献者契约相互关联;只纳入其中一部分会使受门禁约束的文档集合内部不一致。发布到文档站的配对使用 `pairedPages()`,由根 locale 投影 `.zh.md`,由 `/en/` 投影 `.md`;仅创建对侧文件并不会发布它。\n- **配对记录是元数据,而不是 Cordis Loader 配置。** Cordis 配置发现会接受实际的 `.cordis.yml` 和 `.cordis.yaml` 文件,同时排除 `*.i18n.yaml`,即使文档名中包含 `cordis` 也不例外。这样既能继续校验可执行的 Loader 配置项,又不会把翻译 hash 当作配置来解析。\n- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../skills/dsh-code-review/SKILL.md) 模式相同:skill(技能)承载工作流,并将文档作为真源。该 skill 要求编排 agent 把翻译写作委派给 subagent。\n\n## 验证\n\n验证契约分别覆盖每个边界。`verify-translation-pairing` 固定配对完整性、hash、切换行和结构;[`project-doc-site.spec.ts`](../../../../scripts/project-doc-site.spec.ts) 固定已发布配对按 locale 选择对应源文件;[`cordis-config-files.spec.ts`](../../../../scripts/cordis-config-files.spec.ts) 固定 Loader YAML 的发现以及翻译记录的排除;[翻译提示词可运行快照](../../../../scripts/translation-prompt.snapshot.ts)则固定渲染后的系统消息、五对经评审的示例、源请求和响应消费结果。这些检查共同使配对漂移、发布漂移、配置误分类和模型可见提示词漂移都可在评审中看见。\n\n## 曾考虑的替代方案\n\n- **英文为正典源、指纹放在译文内**:本 Agent Note 最初提出的设计:`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash,翻译只沿 EN → ZH 单向流动。评审中修订:团队需要中文先行的撰写方式(先写、先审中文 Agent Note,再译英文),两种语言同权,而单向正典模型无法表达这一点。覆盖**两侧**的伴随记录取代了文件内的单向指纹;blob hash 的机制本身保持不变。\n- **语言目录(`docs/en/` + `docs/zh/`,Kubernetes/ECharts 模式)**:否决。本仓库没有将 locale 映射到路由的文档站框架;如果移动所有英文文件,所有既有交叉引用都要随之修改;且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑,而非原样工作。\n- **独立翻译仓库(PingCAP `docs`/`docs-cn` 模式)**:否决。适合有独立发布节奏的文档产品,对 monorepo 自身的文档而言过重;还会把译文置于本仓库门禁触及不到的地方。\n- **中英混排单文件(一个文件、两种语言)**:否决。每个 diff 都翻倍,破坏一段一行约定的 diff 易读性,且局部不一致不可见。\n- **Commit hash 式记录(MDN `l10n.sourceCommit` 模式)**:否决,改用 blob hash。同一个 PR 内的改动还没有 commit hash,MDN 模式无法表达「与本 PR 引入的状态一致」,且校验它需要 git 历史而非文件内容。\n- **比较配对两侧的 git 时间戳(无记录)**:否决。纯格式化的改动会误报,一次无关改动之后提交的对侧文件会漏报;只有内容同一性这个信号才与门禁的承诺名实相符。\n\n## 业界先例\n\n带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个仓库内置的 agent skill 替代 bot 服务。\n\n## 后果\n\n- 修改已配对文档的任一侧,同一个 PR 就有义务更新对侧并重新记录配对。门禁将 doc-sync 规则双语化,不变式由 CI(而非评审者的记忆)承载。\n- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。\n- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。\n- 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让生成器在输出英文的同时输出中文,届时将这些文件移出排除清单。\n- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(待翻清单,`--list`),而非红色的 CI;因此配对按可评审的批次落地,无需一个巨型 PR。凡文件名以日期开头且日期不早于 manifest 中 `requiredSince` 分界日期的文档,都必须配齐双语文件,因此新建的日期命名 Agent Note 不会增加这份 backlog。\n- 记录的 hash 兼作更新工具(`git cat-file -p ` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),因此这套机制从不强迫整篇重译。\n"
},
{
"role": "user",
diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json
index a4fcc93352..a6503ce282 100644
--- a/scripts/translation-pairing.manifest.json
+++ b/scripts/translation-pairing.manifest.json
@@ -1,24 +1,201 @@
{
"requiredSince": "2026-07-14",
"required": [
+ ".agents/notes/README.md",
+ ".agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.md",
+ ".agents/notes/implemented/architecture/2026-06-11-custom-schema-dsl.md",
+ ".agents/notes/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md",
+ ".agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.md",
+ ".agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.md",
+ ".agents/notes/implemented/architecture/2026-06-11-runtime-arg-validation.md",
+ ".agents/notes/implemented/architecture/2026-06-11-structured-error-taxonomy.md",
+ ".agents/notes/implemented/architecture/2026-06-11-tool-schemas-in-prompt-assembly.md",
+ ".agents/notes/implemented/architecture/2026-06-13-capability-seams.md",
+ ".agents/notes/implemented/architecture/2026-06-13-twin-llm-adapters.md",
+ ".agents/notes/implemented/architecture/2026-06-14-session-persistence.md",
+ ".agents/notes/implemented/architecture/2026-06-15-turn-enclosure-invariant.md",
+ ".agents/notes/implemented/architecture/2026-06-17-filesystem-capability-seam.md",
+ ".agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.md",
+ ".agents/notes/implemented/architecture/2026-06-18-session-surface.md",
+ ".agents/notes/implemented/architecture/2026-06-18-shared-persistence-write-coordinator.md",
+ ".agents/notes/implemented/architecture/2026-06-20-branded-ids.md",
+ ".agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md",
+ ".agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md",
+ ".agents/notes/implemented/architecture/2026-06-20-package-hierarchy.md",
+ ".agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md",
+ ".agents/notes/implemented/architecture/2026-06-24-web-capability-seam.md",
+ ".agents/notes/implemented/architecture/2026-06-26-file-context-as-event-gate.md",
+ ".agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md",
+ ".agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.md",
+ ".agents/notes/implemented/architecture/2026-07-02-fs-per-session-cwd.md",
+ ".agents/notes/implemented/architecture/2026-07-02-result-time-applied-hunk-diffs.md",
+ ".agents/notes/implemented/architecture/2026-07-02-tool-render-intent-union.md",
+ ".agents/notes/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md",
+ ".agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md",
+ ".agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md",
+ ".agents/notes/implemented/architecture/2026-07-05-subagent-provider-lifecycle-events.md",
+ ".agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md",
+ ".agents/notes/implemented/architecture/2026-07-07-tool-call-timeout-policy.md",
+ ".agents/notes/implemented/architecture/2026-07-08-agent-scope-contexts.md",
".agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.md",
+ ".agents/notes/implemented/architecture/2026-07-12-agent-scope-runtime-design.md",
".agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md",
".agents/notes/implemented/architecture/2026-07-19-gui-layering-and-rpc-protocol.md",
".agents/notes/implemented/architecture/2026-07-19-gui-web-client-architecture.md",
".agents/notes/implemented/architecture/2026-07-22-slot-type-chain-implementation.md",
".agents/notes/implemented/architecture/2026-07-23-client-plugin-loading-model.md",
+ ".agents/notes/implemented/feature/2026-06-14-acp-agent-client-protocol.md",
+ ".agents/notes/implemented/feature/2026-06-14-acp-multi-session.md",
+ ".agents/notes/implemented/feature/2026-06-15-code-mode.md",
+ ".agents/notes/implemented/feature/2026-06-17-filesystem-tool-schemas.md",
+ ".agents/notes/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md",
+ ".agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md",
+ ".agents/notes/implemented/feature/2026-06-21-subagent-capability-seam.md",
+ ".agents/notes/implemented/feature/2026-06-22-acp-subagent-backend.md",
+ ".agents/notes/implemented/feature/2026-06-25-ask-user-question.md",
+ ".agents/notes/implemented/feature/2026-06-29-todo-write-tool.md",
+ ".agents/notes/implemented/feature/2026-06-30-hook-bridges.md",
+ ".agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.md",
+ ".agents/notes/implemented/feature/2026-06-30-interception-seams.md",
+ ".agents/notes/implemented/feature/2026-06-30-session-store-fork-api.md",
+ ".agents/notes/implemented/feature/2026-06-30-subagent-observe-enrich.md",
+ ".agents/notes/implemented/feature/2026-07-05-dynamic-workflows.md",
+ ".agents/notes/implemented/feature/2026-07-05-skill-system.md",
+ ".agents/notes/implemented/feature/2026-07-06-approval-seam.md",
+ ".agents/notes/implemented/feature/2026-07-06-explicit-tool-order.md",
+ ".agents/notes/implemented/feature/2026-07-06-sandbox.md",
+ ".agents/notes/implemented/feature/2026-07-07-mcp-client-plugin.md",
+ ".agents/notes/implemented/feature/2026-07-07-session-prefix.md",
+ ".agents/notes/implemented/feature/2026-07-08-repeat-tool-guard.md",
+ ".agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md",
+ ".agents/notes/implemented/feature/2026-07-10-session-query-service.md",
+ ".agents/notes/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md",
+ ".agents/notes/implemented/process/2026-06-11-doc-sync-enforcement.md",
+ ".agents/notes/implemented/process/2026-06-11-quality-gates.md",
+ ".agents/notes/implemented/process/2026-06-11-tsdown-over-dumble.md",
+ ".agents/notes/implemented/process/2026-06-11-vendor-cordis-as-source.md",
+ ".agents/notes/implemented/process/2026-06-16-pnpm-over-yarn.md",
+ ".agents/notes/implemented/process/2026-06-17-ts-build-config.md",
+ ".agents/notes/implemented/process/2026-06-18-markdown-cross-link-lint.md",
+ ".agents/notes/implemented/process/2026-06-20-agent-note-classification.md",
+ ".agents/notes/implemented/process/2026-06-20-core-data-structures-catalog.md",
+ ".agents/notes/implemented/process/2026-06-20-generated-cordis-catalog.md",
".agents/notes/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md",
+ ".agents/notes/implemented/process/2026-07-02-tool-schema-catalog.md",
+ ".agents/notes/implemented/process/2026-07-03-documentation-graph-atlas.md",
+ ".agents/notes/implemented/process/2026-07-04-cordis-jsdoc-completeness-gate.md",
+ ".agents/notes/implemented/process/2026-07-04-doc-tiers-and-budgets.md",
+ ".agents/notes/implemented/process/2026-07-04-persistence-log-catalog.md",
+ ".agents/notes/implemented/process/2026-07-05-uniform-agent-note-format.md",
+ ".agents/notes/implemented/process/2026-07-06-export-surface-jsdoc-gate.md",
+ ".agents/notes/implemented/process/2026-07-06-generated-config-catalog.md",
+ ".agents/notes/implemented/process/2026-07-06-node-engine-floor.md",
+ ".agents/notes/implemented/process/2026-07-06-parallel-github-ci-gates.md",
+ ".agents/notes/implemented/process/2026-07-06-parallel-pre-push-gates.md",
+ ".agents/notes/implemented/process/2026-07-10-readme-known-limitations-gate.md",
+ ".agents/notes/implemented/process/2026-07-12-package-model-experience-contract.md",
".agents/notes/implemented/process/2026-07-19-web-styling-system.md",
+ ".agents/notes/implemented/simplification/2026-06-19-drop-mutable-session-summary.md",
+ ".agents/notes/implemented/simplification/2026-06-20-collapse-trace-only-session-events.md",
+ ".agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-adapter-change-event.md",
+ ".agents/notes/implemented/simplification/2026-06-20-drop-unconsumed-llm-assembled-surfaces.md",
+ ".agents/notes/implemented/simplification/2026-06-20-prune-dead-seam-methods.md",
+ ".agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.md",
+ ".agents/notes/implemented/simplification/2026-06-20-remove-agent-boundary-mirror-events.md",
+ ".agents/notes/implemented/simplification/2026-06-20-unify-agent-and-session-id.md",
+ ".agents/notes/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md",
+ ".agents/notes/implemented/simplification/2026-07-02-remove-stream-chunk-mirror.md",
+ ".agents/notes/implemented/simplification/2026-07-04-drop-image-content-block.md",
+ ".agents/notes/implemented/simplification/2026-07-04-drop-inert-request-knobs.md",
+ ".agents/notes/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md",
+ ".agents/notes/implemented/simplification/2026-07-04-fold-stdio-ui-helper.md",
+ ".agents/notes/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md",
+ ".agents/notes/implemented/simplification/2026-07-04-prune-write-only-fs-surface.md",
+ ".agents/notes/implemented/simplification/2026-07-04-remove-agent-steering-mirror.md",
+ ".agents/notes/implemented/simplification/2026-07-04-share-app-bin-boot-glue.md",
+ ".agents/notes/implemented/simplification/2026-07-04-tighten-hook-protocol-contract.md",
+ ".agents/notes/implemented/simplification/2026-07-04-trim-acp-bridge-unreachable-surface.md",
+ ".agents/notes/implemented/simplification/2026-07-12-drop-unconsumed-skill-provider-events.md",
+ ".agents/notes/implemented/simplification/2026-07-12-prune-unused-web-seam-fields.md",
+ ".agents/notes/implemented/simplification/2026-07-12-simplify-session-log-representation.md",
+ ".agents/notes/implemented/testing/2026-06-11-property-based-testing.md",
+ ".agents/notes/implemented/testing/2026-06-19-acp-snapshot-tests.md",
+ ".agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md",
+ ".agents/notes/implemented/testing/2026-06-20-remove-redundant-snapshot-log-expected-output.md",
+ ".agents/notes/implemented/testing/2026-06-22-fork-child-replay-seed-boundary.md",
+ ".agents/notes/implemented/testing/2026-06-22-fork-snapshot-scenarios.md",
+ ".agents/notes/implemented/testing/2026-06-22-subagent-snapshot-replay.md",
+ ".agents/notes/implemented/testing/2026-07-04-hook-snapshot-matrix.md",
+ ".agents/notes/implemented/testing/2026-07-04-single-source-acp-replay-config.md",
+ ".agents/notes/implemented/testing/2026-07-06-pin-request-header-content-in-one-scenario.md",
+ ".agents/notes/implemented/testing/2026-07-08-shared-acp-snapshot-package.md",
+ ".agents/notes/proposed/architecture/2026-06-16-typed-event-schemas.md",
+ ".agents/notes/proposed/feature/2026-06-30-pre-tool-input-rewrite.md",
+ ".agents/notes/proposed/feature/2026-07-07-claude-code-and-codex-subagent-backends.md",
+ ".agents/notes/proposed/feature/2026-07-08-interactive-side-sessions.md",
+ ".agents/notes/implemented/feature/2026-07-10-sqlite-session-query-provider.md",
+ ".agents/notes/proposed/feature/2026-07-13-stream-workflow-progress-through-tool-calls.md",
+ ".agents/notes/proposed/process/2026-06-11-api-extractor-reports.md",
+ ".agents/notes/proposed/process/2026-06-11-architectural-conformance.md",
+ ".agents/notes/proposed/process/2026-06-11-supply-chain-and-vendor-drift.md",
+ ".agents/notes/proposed/process/2026-06-20-discover-package-inventory.md",
+ ".agents/notes/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md",
+ ".agents/notes/proposed/testing/2026-06-11-deterministic-and-stress-testing.md",
+ ".agents/notes/proposed/testing/2026-06-11-mutation-testing.md",
+ ".agents/notes/rejected/architecture/2026-06-11-immutable-public-surfaces.md",
+ ".agents/notes/rejected/architecture/2026-06-20-providerless-example-base.md",
+ ".agents/notes/rejected/process/2026-07-04-generate-agent-note-index-tables.md",
+ ".agents/notes/rejected/simplification/2026-06-20-assembled-assistant-messages-only.md",
+ ".agents/notes/rejected/simplification/2026-06-20-drop-acp-session-load.md",
+ ".agents/notes/rejected/simplification/2026-06-20-drop-acp-terminal-meta.md",
+ ".agents/notes/rejected/simplification/2026-06-20-drop-bash-output-spill-files.md",
+ ".agents/notes/rejected/simplification/2026-06-20-drop-durable-step-boundaries.md",
+ ".agents/notes/rejected/simplification/2026-06-20-drop-unused-session-lineage.md",
+ ".agents/notes/rejected/simplification/2026-06-20-fold-session-persistence-interface.md",
+ ".agents/notes/rejected/simplification/2026-06-20-generic-tool-rendering.md",
+ ".agents/notes/rejected/simplification/2026-06-20-retire-mid-turn-steering.md",
+ ".agents/notes/rejected/simplification/2026-06-20-single-session-acp-bridge.md",
+ ".agents/notes/rejected/simplification/2026-06-20-truncate-interrupted-turns.md",
+ ".agents/notes/rejected/simplification/2026-07-04-prune-unimplemented-subagent-vocabulary.md",
+ ".agents/notes/rejected/simplification/2026-07-12-collapse-workflow-to-foreground-core.md",
+ ".agents/notes/rejected/simplification/2026-07-12-prune-unused-skill-registry-surface.md",
"README.md",
+ "docs/architecture.md",
"docs/cookbook/adding-a-package.md",
"docs/cookbook/adding-a-tool.md",
"docs/cookbook/adding-a-vendored-package.md",
"docs/cookbook/adding-an-llm-adapter.md",
"docs/cookbook/extension-cookbook.md",
"docs/cookbook/responding-to-pr-review-on-a-stack.md",
+ "docs/cordis-primer.md",
+ "docs/core-data-structures/approval.md",
+ "docs/core-data-structures/bash.md",
+ "docs/core-data-structures/code-runtime.md",
+ "docs/core-data-structures/compaction.md",
+ "docs/core-data-structures/core.md",
+ "docs/core-data-structures/filesystem.md",
+ "docs/core-data-structures/llm-streaming.md",
+ "docs/core-data-structures/persistence.md",
+ "docs/core-data-structures/sandbox.md",
+ "docs/core-data-structures/scope.md",
+ "docs/core-data-structures/session-query.md",
+ "docs/core-data-structures/session.md",
+ "docs/core-data-structures/skills.md",
+ "docs/core-data-structures/subagent.md",
+ "docs/core-data-structures/system-prompt.md",
+ "docs/core-data-structures/tools.md",
+ "docs/core-data-structures/user-interaction.md",
+ "docs/core-data-structures/web.md",
+ "docs/core-data-structures/workflow.md",
+ "docs/defensive-patterns.md",
"docs/development.md",
+ "docs/glossary.md",
"docs/i18n/README.md",
"docs/i18n/translation-rules.md",
+ "docs/postmortem/0001-acp-default-export-drops-inject.md",
+ "docs/postmortem/0002-js-expression-disabled-filesystem-tools.md",
+ "docs/postmortem/README.md",
+ "docs/testing.md",
"docs/user/develop/basic/config.md",
"docs/user/develop/basic/index.md",
"docs/user/develop/basic/tool.md",
@@ -39,14 +216,19 @@
".agents/notes/AGENTS.md",
".agents/notes/implemented/AGENTS.md",
"docs/AGENTS.md",
+ "docs/agent-lifecycle.md",
+ "docs/capability-seams.md",
"docs/config-catalog.md",
"docs/cordis-catalog/",
+ "docs/event-producer-consumer.md",
+ "docs/graph-atlas.md",
"docs/i18n/style-samples.md",
"docs/i18n/terminology.md",
"docs/i18n/translation-prompt.md",
"docs/module-graph.md",
"docs/persistence-catalog.md",
"docs/tool-catalog.md",
+ "docs/tool-execution-pipeline.md",
"python/sdk-runtime/src/deepseek_harness_runtime/runtime/node/"
]
}
diff --git a/scripts/type-equiv.manifest.json b/scripts/type-equiv.manifest.json
index 922921385c..40a5f51759 100644
--- a/scripts/type-equiv.manifest.json
+++ b/scripts/type-equiv.manifest.json
@@ -625,26 +625,6 @@
"symbol": "SessionEventTrace",
"source": "packages/session-query/session-query/src/types.ts"
},
- {
- "doc": "docs/core-data-structures/session-reference.md",
- "symbol": "SessionReferenceInput",
- "source": "packages/context/session-reference/src/types.ts"
- },
- {
- "doc": "docs/core-data-structures/session-reference.md",
- "symbol": "SessionReferenceCandidate",
- "source": "packages/context/session-reference/src/types.ts"
- },
- {
- "doc": "docs/core-data-structures/session-reference.md",
- "symbol": "PreparedReferencedMessage",
- "source": "packages/context/session-reference/src/types.ts"
- },
- {
- "doc": "docs/core-data-structures/session-reference.md",
- "symbol": "SessionReferenceErrorCode",
- "source": "packages/context/session-reference/src/config.ts"
- },
{
"doc": "docs/core-data-structures/session-title.md",
"symbol": "SessionTitleProviderId",
@@ -700,6 +680,26 @@
"symbol": "SessionTitleProvider",
"source": "packages/session-title/session-title/src/index.ts"
},
+ {
+ "doc": "docs/core-data-structures/session-reference.md",
+ "symbol": "SessionReferenceInput",
+ "source": "packages/context/session-reference/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-reference.md",
+ "symbol": "SessionReferenceCandidate",
+ "source": "packages/context/session-reference/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-reference.md",
+ "symbol": "PreparedReferencedMessage",
+ "source": "packages/context/session-reference/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-reference.md",
+ "symbol": "SessionReferenceErrorCode",
+ "source": "packages/context/session-reference/src/config.ts"
+ },
{
"doc": "docs/core-data-structures/tools.md",
"symbol": "ToolOutputDefinition",
@@ -1394,6 +1394,929 @@
"doc": "docs/core-data-structures/session-query.md",
"symbol": "SessionSearchHit",
"source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "Branded",
+ "source": "packages/util/brand/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "ContentBlockMap",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "AssistantProvenance",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "Message",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "MessageSourceMap",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "FinishReasonMap",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "LlmProviderInfo",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "LlmModelInfo",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "LlmModelContext",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "GenerateOptions",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "ToolSchema",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "LlmCallConfig",
+ "source": "packages/llm/llm/src/call-config.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "SessionEvent",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "SendOptions",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "AgentCancelCause",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "InjectOptions",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "ResolvedAgentInput",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "AgentMessageId",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "AgentMessage",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "CancelOptions",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "Agent",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "HookContext",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "PromptDecision",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "ContinuationDecision",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "RequestError",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "RequestErrorDecision",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "ContinuationStop",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/core.zh.md",
+ "symbol": "SessionStartSource",
+ "source": "packages/core/agent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/scope.zh.md",
+ "symbol": "ScopeKey",
+ "source": "packages/core/scope/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/scope.zh.md",
+ "symbol": "Scoped",
+ "source": "packages/core/scope/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/scope.zh.md",
+ "symbol": "Scope",
+ "source": "packages/core/scope/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/scope.zh.md",
+ "symbol": "ScopeLayer",
+ "source": "packages/core/scope/src/store.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/system-prompt.zh.md",
+ "symbol": "AssembleContext",
+ "source": "packages/core/system-prompt/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/system-prompt.zh.md",
+ "symbol": "PromptSection",
+ "source": "packages/core/system-prompt/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/system-prompt.zh.md",
+ "symbol": "ToolProviderResult",
+ "source": "packages/core/system-prompt/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "StreamChunk",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "LlmFailure",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "TokenUsage",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "ContentBlockMap",
+ "source": "packages/llm/llm/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "AppIdentity",
+ "source": "packages/llm/llm/src/attribution.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "BlockAssembler",
+ "source": "packages/llm/llm/src/assembler.ts",
+ "projection": "public-api"
+ },
+ {
+ "doc": "docs/core-data-structures/llm-streaming.zh.md",
+ "symbol": "LlmAdapter",
+ "source": "packages/llm/llm/src/index.ts",
+ "projection": "public-api"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "PromptMessageData",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SessionEventMap",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "OutOfBandSessionEventMap",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "EpochHeader",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "TodoItem",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SessionEvent",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "TurnTriggerMap",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "TurnEndReasonMap",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SurfaceEventType",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SurfaceOp",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SurfaceIntent",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SessionSurface",
+ "source": "packages/core/session/src/surface.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SurfaceFoldReplacement",
+ "source": "packages/core/session/src/surface.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "SurfaceFoldResult",
+ "source": "packages/core/session/src/surface.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session.zh.md",
+ "symbol": "Session",
+ "source": "packages/core/session/src/index.ts",
+ "projection": "public-api"
+ },
+ {
+ "doc": "docs/core-data-structures/persistence.zh.md",
+ "symbol": "SessionHeader",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/persistence.zh.md",
+ "symbol": "CreateSessionOptions",
+ "source": "packages/core/session/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/persistence.zh.md",
+ "symbol": "SessionLocation",
+ "source": "packages/session-persistence/session-persistence/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/persistence.zh.md",
+ "symbol": "SessionPersistenceRevision",
+ "source": "packages/session-persistence/session-persistence/src/revision.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/persistence.zh.md",
+ "symbol": "SessionPersistenceSnapshot",
+ "source": "packages/session-persistence/session-persistence/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventSurface",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionRecord",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionLogSnapshot",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionSurfaceSnapshot",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventRecord",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionResultFilter",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventResultFilter",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventSearchDocument",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionSearchCursor",
+ "source": "packages/session-query/session-query/src/cursor.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionSearchRequest",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventSearchRequest",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionSearchPage",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventSearchHit",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionSearchHit",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionLineageNode",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionLineageTrace",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionQueryErrorCode",
+ "source": "packages/session-query/session-query/src/config.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventReadRequest",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventWindow",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventTraceRequest",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/session-query.zh.md",
+ "symbol": "SessionEventTrace",
+ "source": "packages/session-query/session-query/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolOutputDefinition",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolDefinition",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ValueSchemaSpec",
+ "source": "packages/core/tools/src/schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ParameterPropertySpec",
+ "source": "packages/core/tools/src/schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ParameterSchemaSpec",
+ "source": "packages/core/tools/src/schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "InferValue",
+ "source": "packages/core/tools/src/schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "InferArgs",
+ "source": "packages/core/tools/src/schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecutionToken",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecutionInput",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecution",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolDispatchExecution",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecutionMode",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolRunContext",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolGuard",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolRestriction",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolFailure",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecutionSuccess",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecutionFailure",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ToolExecutionResult",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "PreToolDecision",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "PostToolDecision",
+ "source": "packages/core/tools/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "JsonSchemaScalar",
+ "source": "packages/core/tools/src/json-schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "JsonSchemaType",
+ "source": "packages/core/tools/src/json-schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "JsonSchemaNode",
+ "source": "packages/core/tools/src/json-schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/tools.zh.md",
+ "symbol": "ObjectJsonSchema",
+ "source": "packages/core/tools/src/json-schema.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "AskUserQuestionOption",
+ "source": "packages/ui/user-interaction/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "AskUserQuestionItem",
+ "source": "packages/ui/user-interaction/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "AskUserQuestionRequest",
+ "source": "packages/ui/user-interaction/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "AskUserQuestionAnswerItem",
+ "source": "packages/ui/user-interaction/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "AskUserQuestionAnswer",
+ "source": "packages/ui/user-interaction/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "UserInteractionProvider",
+ "source": "packages/ui/user-interaction/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/user-interaction.zh.md",
+ "symbol": "UserInteractionError",
+ "source": "packages/ui/user-interaction/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/approval.zh.md",
+ "symbol": "ApprovalRequestId",
+ "source": "packages/ui/user-approval/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/approval.zh.md",
+ "symbol": "ApprovalOutcome",
+ "source": "packages/ui/user-approval/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/approval.zh.md",
+ "symbol": "ApprovalPolicy",
+ "source": "packages/ui/user-approval/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/approval.zh.md",
+ "symbol": "ApprovalRequest",
+ "source": "packages/ui/user-approval/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "DshEnvironmentKey",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "DshEnvironment",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "BashExecRequest",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "BashExecSpec",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "BashRunResult",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "BashSandboxInfo",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "CollectedOutput",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "BashProcess",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/bash.zh.md",
+ "symbol": "BashProcessRead",
+ "source": "packages/bash/bash/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "SandboxMode",
+ "source": "packages/sandbox/sandbox/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "ConfinedSandboxMode",
+ "source": "packages/sandbox/sandbox/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "SandboxExecutionPolicy",
+ "source": "packages/sandbox/sandbox/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "SandboxEnforcement",
+ "source": "packages/sandbox/sandbox/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "SandboxPolicy",
+ "source": "packages/sandbox/sandbox/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "SandboxPolicyRequest",
+ "source": "packages/sandbox/sandbox-policy/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/sandbox.zh.md",
+ "symbol": "ConfinedArgv",
+ "source": "packages/sandbox/sandbox/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeJsonValue",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeRunRequest",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeRunResult",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeBindingNamespace",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeBindingErrorClass",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeBindingFunction",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/code-runtime.zh.md",
+ "symbol": "CodeRunFailure",
+ "source": "packages/code-runtime/code-runtime/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsTarget",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsTargetKey",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsVersion",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsInfo",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsPathInfo",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsDirEntry",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsWriteIntent",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsWriteOutcome",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsEditRequest",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsEditOutcome",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsErrorCode",
+ "source": "packages/fs/fs/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FsPolicyExec",
+ "source": "packages/fs/fs-policy/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/filesystem.zh.md",
+ "symbol": "FileReadOutcome",
+ "source": "packages/fs/tool-fs/src/read-render.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillSource",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillResourceBase",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillSummary",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillCandidate",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillDefinition",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillRegistration",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillLookupOptions",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "SkillProvider",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/skills.zh.md",
+ "symbol": "Config",
+ "source": "packages/skill/skill/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/compaction.zh.md",
+ "symbol": "CompactionResult",
+ "source": "packages/compact/compact/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/compaction.zh.md",
+ "symbol": "CompactionTrigger",
+ "source": "packages/compact/compact/src/index.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/compaction.zh.md",
+ "symbol": "PrunedEntry",
+ "source": "packages/compact/compact-tool-result-prune/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/compaction.zh.md",
+ "symbol": "PruneResult",
+ "source": "packages/compact/compact-tool-result-prune/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subagent.zh.md",
+ "symbol": "SubagentCapabilities",
+ "source": "packages/subagent/subagent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subagent.zh.md",
+ "symbol": "SubagentStartRequest",
+ "source": "packages/subagent/subagent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subagent.zh.md",
+ "symbol": "SubagentResult",
+ "source": "packages/subagent/subagent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subagent.zh.md",
+ "symbol": "SubagentStopReasonMap",
+ "source": "packages/subagent/subagent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subagent.zh.md",
+ "symbol": "SubagentRun",
+ "source": "packages/subagent/subagent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/subagent.zh.md",
+ "symbol": "SubagentProvider",
+ "source": "packages/subagent/subagent/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/web.zh.md",
+ "symbol": "WebSearchRequest",
+ "source": "packages/web/web/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/web.zh.md",
+ "symbol": "WebSearchResult",
+ "source": "packages/web/web/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/web.zh.md",
+ "symbol": "WebSearchSource",
+ "source": "packages/web/web/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/web.zh.md",
+ "symbol": "WebFetchRequest",
+ "source": "packages/web/web/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/web.zh.md",
+ "symbol": "WebFetchResult",
+ "source": "packages/web/web/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/web.zh.md",
+ "symbol": "WebFetchBody",
+ "source": "packages/web/web/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/workflow.zh.md",
+ "symbol": "WorkflowStartRequest",
+ "source": "packages/workflow/workflow/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/workflow.zh.md",
+ "symbol": "WorkflowMeta",
+ "source": "packages/workflow/workflow/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/workflow.zh.md",
+ "symbol": "WorkflowResult",
+ "source": "packages/workflow/workflow/src/types.ts"
+ },
+ {
+ "doc": "docs/core-data-structures/workflow.zh.md",
+ "symbol": "WorkflowRun",
+ "source": "packages/workflow/workflow/src/types.ts"
}
]
}
diff --git a/scripts/verify-cordis-config.ts b/scripts/verify-cordis-config.ts
index 916db4a168..6041be78ed 100644
--- a/scripts/verify-cordis-config.ts
+++ b/scripts/verify-cordis-config.ts
@@ -12,6 +12,7 @@ import { globSync, readFileSync } from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import * as yaml from 'js-yaml'
import ts from 'typescript'
+import { cordisConfigFiles } from './cordis-config-files.ts'
interface JsExpr {
__jsExpr: string
@@ -39,10 +40,7 @@ const jsExprType = new yaml.Type('tag:yaml.org,2002:js', {
})
const schema = yaml.JSON_SCHEMA.extend(jsExprType)
-const files = globSync(['**/*cordis*.yml', '**/*cordis*.yaml'], {
- cwd: root,
- exclude: ['.claude/**', 'node_modules/**', 'vendor/**'],
-}).sort()
+const files = cordisConfigFiles(root)
const errors: string[] = []
const examplePluginReferences: PluginReference[] = []
diff --git a/website/docs.ts b/website/docs.ts
index ee7ad64061..1888cd908c 100644
--- a/website/docs.ts
+++ b/website/docs.ts
@@ -226,14 +226,53 @@ const cordisTutorial = mirroredPages(([
...(file === 'index.md' ? { sourceAliases: ['docs/cordis-tutorial'] } : {}),
})))
+const cordisPrimerReference = pairedPages([
+ {
+ source: 'docs/cordis-primer.md',
+ route: 'reference/cordis-primer.md',
+ label: { root: 'Cordis 入门', en: 'Cordis primer' },
+ sidebar: { root: 'zh-reference', en: 'en-reference' },
+ section: { root: '概念', en: 'Concepts' },
+ order: 1,
+ },
+])
+
+const coreDataReference = pairedPages(([
+ ['core.md', '核心数据结构', 'Core data structures', 0],
+ ['scope.md', '作用域', 'Scopes', 1],
+ ['session.md', '会话', 'Sessions', 2],
+ ['system-prompt.md', '系统提示词', 'System prompts', 4],
+ ['tools.md', '工具', 'Tools', 5],
+ ['llm-streaming.md', 'LLM 流式响应', 'LLM streaming', 6],
+ ['bash.md', 'Bash 执行', 'Bash execution', 7],
+ ['filesystem.md', '文件系统', 'Filesystem', 9],
+ ['code-runtime.md', '代码运行时', 'Code runtime', 10],
+ ['compaction.md', '上下文压缩', 'Compaction', 11],
+ ['subagent.md', '子代理', 'Subagents', 12],
+ ['workflow.md', '工作流', 'Workflows', 13],
+ ['skills.md', '技能', 'Skills', 14],
+ ['approval.md', '审批', 'Approvals', 15],
+ ['user-interaction.md', '用户交互', 'User interaction', 16],
+ ['sandbox.md', '沙箱', 'Sandboxing', 18],
+ ['web.md', 'Web 访问', 'Web access', 19],
+ ['persistence.md', '会话持久化', 'Session persistence', 20],
+] as const).map(([file, rootLabel, enLabel, order]): PairedPage => ({
+ source: `docs/core-data-structures/${file}`,
+ route: `reference/core-data-structures/${file}`,
+ label: { root: rootLabel, en: enLabel },
+ sidebar: { root: 'zh-reference', en: 'en-reference' },
+ section: { root: '数据结构', en: 'Data structures' },
+ order,
+ ...(file === 'core.md' ? { sourceAliases: ['docs/core-data-structures'] } : {}),
+})))
+
const reference = mirroredPages([
...([
- ['docs/architecture.md', 'reference/index.md', '架构', 'Architecture'],
- ['docs/cordis-primer.md', 'reference/cordis-primer.md', 'Cordis 入门', 'Cordis primer'],
- ['docs/capability-seams.md', 'reference/capability-seams.md', '能力服务', 'Capability services'],
- ['docs/agent-lifecycle.md', 'reference/agent-lifecycle.md', 'Agent 生命周期', 'Agent lifecycle'],
- ['docs/tool-execution-pipeline.md', 'reference/tool-execution-pipeline.md', 'Tool 执行', 'Tool execution'],
- ] as const).map(([source, route, rootLabel, enLabel], order): MirroredPage => ({
+ ['docs/architecture.md', 'reference/index.md', '架构', 'Architecture', 0],
+ ['docs/capability-seams.md', 'reference/capability-seams.md', '能力服务', 'Capability services', 2],
+ ['docs/agent-lifecycle.md', 'reference/agent-lifecycle.md', 'Agent 生命周期', 'Agent lifecycle', 3],
+ ['docs/tool-execution-pipeline.md', 'reference/tool-execution-pipeline.md', 'Tool 执行', 'Tool execution', 4],
+ ] as const).map(([source, route, rootLabel, enLabel, order]): MirroredPage => ({
source,
route,
contentLocale: 'en-US',
@@ -273,28 +312,10 @@ const reference = mirroredPages([
order,
})),
...([
- ['core.md', '核心数据结构', 'Core data structures'],
- ['scope.md', '作用域', 'Scopes'],
- ['session.md', '会话', 'Sessions'],
- ['goal.md', '目标', 'Goals'],
- ['system-prompt.md', '系统提示词', 'System prompts'],
- ['tools.md', '工具', 'Tools'],
- ['llm-streaming.md', 'LLM 流式响应', 'LLM streaming'],
- ['bash.md', 'Bash 执行', 'Bash execution'],
- ['pty.md', 'PTY 会话', 'PTY sessions'],
- ['filesystem.md', '文件系统', 'Filesystem'],
- ['code-runtime.md', '代码运行时', 'Code runtime'],
- ['compaction.md', '上下文压缩', 'Compaction'],
- ['subagent.md', '子代理', 'Subagents'],
- ['workflow.md', '工作流', 'Workflows'],
- ['skills.md', '技能', 'Skills'],
- ['approval.md', '审批', 'Approvals'],
- ['user-interaction.md', '用户交互', 'User interaction'],
- ['commands.md', '命令', 'Human commands'],
- ['sandbox.md', '沙箱', 'Sandboxing'],
- ['web.md', 'Web 访问', 'Web access'],
- ['persistence.md', '会话持久化', 'Session persistence'],
- ] as const).map(([file, rootLabel, enLabel], order): MirroredPage => ({
+ ['goal.md', '目标', 'Goals', 3],
+ ['pty.md', 'PTY 会话', 'PTY sessions', 8],
+ ['commands.md', '命令', 'Human commands', 17],
+ ] as const).map(([file, rootLabel, enLabel, order]): MirroredPage => ({
source: `docs/core-data-structures/${file}`,
route: `reference/core-data-structures/${file}`,
contentLocale: 'en-US',
@@ -302,7 +323,6 @@ const reference = mirroredPages([
sidebar: { root: 'zh-reference', en: 'en-reference' },
section: { root: '数据结构', en: 'Data structures' },
order,
- ...(file === 'core.md' ? { sourceAliases: ['docs/core-data-structures'] } : {}),
})),
...([
['adding-a-package.md', '新增 Package', 'Adding a package'],
@@ -325,5 +345,7 @@ export const docsPages: DocsPage[] = [
...homeAndGuide,
...develop,
...cordisTutorial,
+ ...cordisPrimerReference,
+ ...coreDataReference,
...reference,
]