docs(i18n): include complete proofreading corpus
This commit is contained in:
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/api-gateway.md
|
||||
api-gateway.md: c60793532d621585fc9878b6197497b45015856b
|
||||
api-gateway.zh.md: daf46f6ad208f4cb959028ff59d98d9de7a072d6
|
||||
api-gateway.zh.md: 21711e5b7e1463f8ce90e60791957b49caee596f
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
[English](api-gateway.md) | 中文
|
||||
|
||||
本文是 TypeRT API Gateway 的当前状态参考。它描述业务 Service 如何声明一元 Remote 方法、构建如何生成 Host 与 Client 约定,以及调用如何复用 Connection 的 RPC 与 `/api` 路由。会话事件、增量数据和其他流协议不属于本文范围;它们可以使用同一个 Connection,但不使用 Remote 方法描述符。
|
||||
本文是 TypeRT API Gateway 的当前状态参考。它描述业务服务如何声明一元 Remote 方法、构建如何生成 Host 与 Client 约定,以及调用如何复用 Connection 的 RPC 与 `/api` 路由。会话事件、增量数据和其他流协议不属于本文范围;它们可以使用同一个 Connection,但不使用 Remote 方法描述符。
|
||||
|
||||
## 编程模型
|
||||
|
||||
业务 Service 通过 `@Remote` 或 `@RemoteScope` 选择对 Client 开放的方法。未标记的方法不会进入生成的 Client 类型或运行时贡献,也不能通过 `ctx.remote` 调用。
|
||||
业务服务通过 `@Remote` 或 `@RemoteScope` 选择对 Client 开放的方法。未标记的方法不会进入生成的 Client 类型或运行时贡献,也不能通过 `ctx.remote` 调用。
|
||||
|
||||
`@Remote` 表示调用根 Host Context 中注册的 Cordis Service。复杂的 Host 对象不能直接跨 wire 传输;业务包必须通过 `TypeRTLookupMap` 声明它与 wire identity 的关联,并在运行时向 `ctx.typert.lookups` 注册默认解析提供方。例如 `Agent` 参数在 Host 签名中名为 `agent`,生成的 wire 字段为 `agentId`,Gateway 在调用业务方法前将 id 解析为 Host 对象。Host 组合可以用 `ctx.typert.lookups.configure()` 覆盖某个 lookup key 的解析策略,而不改变业务包拥有的参数名、wire 字段或规范类型 symbol。
|
||||
`@Remote` 表示调用根 Host Context 中注册的 Cordis 服务。复杂的 Host 对象不能直接跨 wire 传输;业务包必须通过 `TypeRTLookupMap` 声明它与 wire identity 的关联,并在运行时向 `ctx.typert.lookups` 注册默认解析提供方。例如 `Agent` 参数在 Host 签名中名为 `agent`,生成的 wire 字段为 `agentId`,Gateway 在调用业务方法前将 id 解析为 Host 对象。Host 组合可以用 `ctx.typert.lookups.configure()` 覆盖某个 lookup key 的解析策略,而不改变业务包拥有的参数名、wire 字段或规范类型 symbol。
|
||||
|
||||
`@RemoteScope(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context,再从该 Context 取得 Service 并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。
|
||||
`@RemoteScope(key)` 表示先通过 `ctx.typert.contexts` 把 identity 解析为一个作用域 Context,再从该 Context 取得服务并调用方法。它适用于方法本身依赖作用域组合、而不需要显式接收 `Agent` 等对象的情形。
|
||||
|
||||
Service 通常继承 `GatewayService`,让 Cordis service key 与默认 Remote namespace 在构造器中显式绑定。已有其他基类的 Service 可以改为声明 `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`;两种方式都会留下可检查的公开 binding,不依赖编译器向构造函数注入 symbol。
|
||||
服务通常继承 `GatewayService`,让 Cordis 服务 key 与默认 Remote namespace 在构造器中显式绑定。已有其他基类的服务可以改为声明 `readonly typertGateway = bindTypeRTGateway(this, serviceKey)`;两种方式都会留下可检查的公开 binding,不依赖编译器向构造函数注入 symbol。
|
||||
|
||||
```ts
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
@@ -55,7 +55,7 @@ export class GoalService extends GatewayService {
|
||||
|
||||
Remote 方法可以同步返回或返回 Promise。若需要协作式取消,Host 签名的最后一个参数必须是全局类型的 `signal: AbortSignal`;它记录在描述符中而不是进入 `args`,Client 生成的方法则接受最后一个可选的 `AbortSignal`。
|
||||
|
||||
Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直接调用与作用域调用分别出现在 `ctx.remote.<namespace>` 和 `agentCtx.remote.<namespace>`。每个 namespace 都是注册为 `remote.<namespace>` 的可追踪 Cordis 子 Service;Client assembly 通过 `ctx.remote.$mount()` 挂载贡献,最后一个方法撤回后该 namespace 随即卸载。依赖声明归实际调用方所有:只有读取 `ctx.remote.<namespace>` 或 `agentCtx.remote.<namespace>` 的业务包才在自己的 `inject` 中同时声明 `remote` 与 `remote.<namespace>`;只负责挂载 contribution 的 assembly,以及不调用该 namespace 的上层 runtime,不代业务包声明 namespace 依赖。当一个 `@Remote` 方法恰好有一个 lookup 参数、且同名 `TypeRTContextMap` 使用相同 wire identity 时,生成的作用域签名会省略该 identity 参数。`@RemoteScope` 只生成作用域调用界面。
|
||||
Client 使用普通对象上的具体函数,不使用 JavaScript Proxy。直接调用与作用域调用分别出现在 `ctx.remote.<namespace>` 和 `agentCtx.remote.<namespace>`。每个 namespace 都是注册为 `remote.<namespace>` 的可追踪 Cordis 子服务;Client assembly 通过 `ctx.remote.$mount()` 挂载贡献,最后一个方法撤回后该 namespace 随即卸载。依赖声明归实际调用方所有:只有读取 `ctx.remote.<namespace>` 或 `agentCtx.remote.<namespace>` 的业务包才在自己的 `inject` 中同时声明 `remote` 与 `remote.<namespace>`;只负责挂载 contribution 的 assembly,以及不调用该 namespace 的上层运行时,不代业务包声明 namespace 依赖。当一个 `@Remote` 方法恰好有一个 lookup 参数、且同名 `TypeRTContextMap` 使用相同 wire identity 时,生成的作用域签名会省略该 identity 参数。`@RemoteScope` 只生成作用域调用接口。
|
||||
|
||||
```ts ignore-check
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session/types'
|
||||
@@ -85,20 +85,20 @@ Client 应用只装配 `@deepseek-ai/dsh-api-remotes`。该包以运行时值导
|
||||
| 构建 | `@deepseek-ai/dsh-typert-generator` | 从 Host `ts.Program` 严格分析 Remote 签名、类型图、lookup、Context 与源码位置,并生成 Host 和 Host-for-Client 产物 |
|
||||
| Host | `@deepseek-ai/dsh-typert-registry` 与 Loader | 把生成的 Host 描述符、schema 及业务包注册项放入 `ctx.typert`,并持有 lookup 与 Context 提供方 |
|
||||
| Host | `@deepseek-ai/dsh-api-remotes` | 负责应用的 Agent/Session 身份策略,并配置对应的 TypeRT lookup |
|
||||
| Host | `@deepseek-ai/dsh-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,解析对象或 Context,调用实时 Cordis Service,并校验请求值和返回值 |
|
||||
| Client | `@deepseek-ai/dsh-api-gateway/client` | 提供 `ctx.remote` 与 `remote.<namespace>` 子 Service,把生成的描述符挂成具体方法,并通过 Connection 发起、校验和取消调用 |
|
||||
| Host | `@deepseek-ai/dsh-api-gateway` | 提供 `ctx.typertGateway`,认领 Remote endpoint,解析对象或 Context,调用实时 Cordis 服务,并校验请求值和返回值 |
|
||||
| Client | `@deepseek-ai/dsh-api-gateway/client` | 提供 `ctx.remote` 与 `remote.<namespace>` 子服务,把生成的描述符挂成具体方法,并通过 Connection 发起、校验和取消调用 |
|
||||
| Client | `@deepseek-ai/dsh-api-remotes/client` | 显式选择并挂载本应用允许使用的 `/remote` 贡献,向业务代码带入对应的声明合并 |
|
||||
| 双侧 | `@deepseek-ai/dsh-client-connection` | 提供 RPC carrier、请求关联、信任边界、取消、响应 envelope 与 `/api` HTTP bridge |
|
||||
|
||||
API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对等入口,但两侧构建不会进入同一个 `ts.Program`。Host 入口不导入 Client 的 Cordis `Context` 合并,Client 入口也不导入 Host Gateway 服务。
|
||||
|
||||
## 严格生成链路
|
||||
## 严格生成流水线
|
||||
|
||||
根构建依次执行 `build:lib:host`、`build:lib:client` 与 `build:web`。Host lib 阶段先运行 `tsc -b tsconfig.host.json`,再运行 `tsdown --env.DSH_BUILD_FACE host`;TypeRT generator 由正常 Host Project Reference 图编译,并在这次 tsdown 中以 Host aggregate 为唯一 `ts.Program` 种子运行。Client lib 阶段随后运行 `tsc -b tsconfig.client.json` 与 `tsdown --env.DSH_BUILD_FACE client`,使用刚生成的 Remote Client 声明和运行时贡献,但不再次启动 TypeRT。
|
||||
|
||||
两次 tsdown 都接收完整 workspace,且都只打包 `lib/types` 中由对应 tsc 阶段发射的 JavaScript。根配置不扫描 Client 产物、不按 package 名分类,也不向 tsdown 传维护式 filter;各包的本地配置根据 `DSH_BUILD_FACE` 返回当前阶段的入口。普通 Client plugin 在 Client 阶段一起生成 Node loader 入口与 browser bundle。
|
||||
两次 tsdown 都接收完整 workspace,且都只打包 `lib/types` 中由对应 tsc 阶段发射的 JavaScript。根配置不扫描 Client 产物、不按包名分类,也不向 tsdown 传维护式 filter;各包的本地配置根据 `DSH_BUILD_FACE` 返回当前阶段的入口。普通 Client 插件在 Client 阶段一起生成 Node loader 入口与 browser bundle。
|
||||
|
||||
`api-remotes` 是唯一拆分 TypeScript face 的 package 特例。它的 Host project 负责 Agent/Session lookup 策略,Client project 则依赖业务包在 Host tsdown 中生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用 `api/remotes/tsconfig.host.json` 或 `api/remotes/tsconfig.client.json`。包内 `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。其他 package 仍只登记在一个 aggregate 中。
|
||||
`api-remotes` 是唯一拆分 TypeScript face 的包特例。它的 Host project 负责 Agent/Session lookup 策略,Client project 则依赖业务包在 Host tsdown 中生成的 `/remote` 声明;根 aggregate 与直接消费方必须分别引用 `api/remotes/tsconfig.host.json` 或 `api/remotes/tsconfig.client.json`。包内 `clientBundle(..., { hostPhase: true })` 让 Host 入口在 Host tsdown 中生成,让 Client tsdown 只生成 browser 入口。其他包仍只登记在一个 aggregate 中。
|
||||
|
||||
每个贡献业务包把生成文件写入自己的 `lib/`,而不是源码目录:
|
||||
|
||||
@@ -110,7 +110,7 @@ API Gateway 包同时拥有 Host dispatcher 与 Client Remote endpoint 两个对
|
||||
| `typert.remote-client.d.ts` | Client 类型系统 | `TypeRTRemoteNamespaceMap` 与 `TypeRTRemoteScopeMap` 的声明合并及 Client-safe 类型引用 |
|
||||
| `typert.remote-client.d.ts.map` | 编辑器 | 将生成的方法属性映射回 Host 包中的 Remote 方法声明 |
|
||||
|
||||
业务包通过 `./typert` 暴露 Host Loader 入口,通过 `./remote` 暴露 Host-for-Client 入口。生成器同时校验这些 package export 及发布文件清单;只有具备相应入口的显式贡献包才会生成产物。
|
||||
业务包通过 `./typert` 暴露 Host Loader 入口,通过 `./remote` 暴露 Host-for-Client 入口。生成器同时校验这些包 export 及发布文件清单;只有具备相应入口的显式贡献包才会生成产物。
|
||||
|
||||
Remote Client 声明中的参数名来自 wire 字段,参数和返回类型则引用原业务包导出的 Client-safe 类型。声明 map 把 `ctx.remote.goals.create` 最终解析到的生成属性映射到带 `@Remote` 的 Host 源方法,因此支持 declaration-map 的编辑器可以从 Client 调用跳到真实实现,而不是停在生成的 `.d.ts`。
|
||||
|
||||
@@ -120,19 +120,19 @@ Remote Client 声明中的参数名来自 wire 字段,参数和返回类型则
|
||||
|
||||
Remote 与 API Proxy 共用 Connection 的 `/api` 路由。Client Remote 调用 `connection.rpc.call('/api', '<namespace>/<method>', { args }, signal)`;HTTP carrier 对应 `POST /api/<namespace>/<method>`,payload 只包含一个具名 `args` 对象。
|
||||
|
||||
Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内按 interceptor 顺序分发。TypeRT Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint;未认领的请求回退到既有 API Proxy。Connection 拥有传输、RPC id、响应 envelope 和 request cancellation,Gateway 只拥有 Remote 数据协议和业务分发。未来替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程界面。
|
||||
Connection 在 HTTP bridge 之前执行 `/api` 的统一信任检查,再在共享 FetchHandler 内按 interceptor 顺序分发。TypeRT Gateway 只认领存在严格描述符或活跃 SRC marker 的两段式 endpoint;未认领的请求回退到既有 API Proxy。Connection 拥有传输、RPC id、响应 envelope 和请求取消,Gateway 只拥有 Remote 数据协议和业务分发。未来替换 Connection carrier 不要求改变 Remote 描述符或 Client 编程接口。
|
||||
|
||||
Gateway 每次调用都从当前注册表解析描述符和实时 Service,不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context provider 解析对象或接收者,最后调用 binding 指向的 Service 方法并校验返回值。缺少 provider、identity 未命中、binding 不一致、参数多缺、schema 失败和方法不存在都会在进入业务代码前或离开业务代码后失败。
|
||||
Gateway 每次调用都从当前注册表解析描述符和实时服务,不缓存业务对象。它要求 `args` 的字段集合与描述符完全一致,先用 codec 校验 wire 值,再通过注册的 lookup 或 Context 提供方解析对象或接收者,最后调用 binding 指向的服务方法并校验返回值。缺少提供方、identity 未命中、binding 不一致、参数缺失或多余、schema 失败和方法不存在都会在进入业务代码前或离开业务代码后失败。
|
||||
|
||||
lookup provider 的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于 provider 挂载;没有 provider 时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复 provider 默认策略。API Remotes 负责 `agent` 与 `session` 的标准 `agentFor()` 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity;`session` lookup 返回该 Agent 的 Session。Web API Proxy 提供 Agent 默认值与 scope 设置,再让旧方法使用同一个 resolver。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。
|
||||
lookup 提供方的 `register()` 同时提供稳定声明和默认 resolver;`configure()` 提供由 Host 组合拥有、可异步执行且受 effect 生命周期约束的 resolver。配置可以先于提供方挂载;没有提供方时调用仍以 `lookup-unavailable` 失败,配置卸载后则恢复提供方默认策略。API Remotes 负责 `agent` 与 `session` 的标准 `agentFor()` 语义:复用 live Agent,自动恢复普通冷会话,对并发恢复去重,并拒绝由 subagent routing 拥有的 identity;`session` lookup 返回该 Agent 的 Session。Web API Proxy 提供 Agent 默认值与 scope 设置,再让旧方法使用同一个 resolver。恢复失败和 ownership fence 通过既有 RPC error 原样返回,不折叠为 Gateway 的 `internal` 错误。
|
||||
|
||||
Client 卸载一个贡献时会一起移除描述符和具体方法,中止其进行中的调用,并使外部仍持有的旧方法句柄拒绝继续调用。Host 上已经注册过的严格 endpoint 被撤回后也不会降级到 SRC 推断,以免热卸载悄然降低校验强度。
|
||||
Client 卸载一个贡献时会一起移除描述符和具体方法,中止其进行中的调用,并使外部仍持有的陈旧方法句柄拒绝继续调用。Host 上已经注册过的严格 endpoint 被撤回后也不会降级到 SRC 推断,以免热卸载悄然降低校验强度。
|
||||
|
||||
## SRC 开发回退
|
||||
|
||||
Host 通过 `node --import tsx/esm` 从源码启动时不会执行 TypeRT 编译插件。标准 decorator 初始化器仍会把方法名和调用模式记录到模块私有 `WeakMap`,`GatewayService` 或 `bindTypeRTGateway()` 则提供显式 service binding;Gateway 因而可以在不启动 `ts.Program` 的情况下构造一个较弱的临时描述符。
|
||||
Host 通过 `node --import tsx/esm` 从源码启动时不会执行 TypeRT 编译插件。标准 decorator 初始化器仍会把方法名和调用模式记录到模块私有 `WeakMap`,`GatewayService` 或 `bindTypeRTGateway()` 则提供显式服务 binding;Gateway 因而可以在不启动 `ts.Program` 的情况下构造一个较弱的临时描述符。
|
||||
|
||||
SRC 回退从运行中函数解析简单参数名。参数名与某个已注册 lookup 的 `parameter` 相同,例如 `agent` 或 `session`,就使用其 `agentId` 或 `sessionId` wire 字段并在 Host 解析对象;其他参数只检查值是否为无循环、无特殊 prototype 的 JSON-safe 数据。`@RemoteScope` 直接使用已注册 Host Context provider 的 wire 字段。SRC 不读取 TypeScript 类型,不生成 Zod schema,不推断可选参数,也不支持解构、默认值、rest 或重复参数名。
|
||||
SRC 回退从运行中函数解析简单参数名。参数名与某个已注册 lookup 的 `parameter` 相同,例如 `agent` 或 `session`,就使用其 `agentId` 或 `sessionId` wire 字段并在 Host 解析对象;其他参数只检查值是否为无循环、无特殊 prototype 的 JSON-safe 数据。`@RemoteScope` 直接使用已注册 Host Context 提供方的 wire 字段。SRC 不读取 TypeScript 类型,不生成 Zod schema,不推断可选参数,也不支持解构、默认值、rest 或重复参数名。
|
||||
|
||||
SRC 只解决 Host 源码进程的分发问题。Client 不会从运行中的 Host 发现 decorator,Client Remote 也拒绝挂载缺少严格 codec 的 SRC 描述符;其类型、codec 和 Remote 注册值始终来自最近一次生成的 `lib/typert.remote-client.*`。
|
||||
|
||||
@@ -145,7 +145,7 @@ pnpm dsh web
|
||||
pnpm run dev:web
|
||||
```
|
||||
|
||||
`dsh` 通过 tsx 启动 Host 源码,所以 Host 可以使用 SRC 回退;`dev:web` 只监听带 `dsh.client` 声明的 Client plugin 并重写其 `lib/client.js`,它不会分析 Host decorator,也不会生成 Remote Client DTS。
|
||||
`dsh` 通过 tsx 启动 Host 源码,所以 Host 可以使用 SRC 回退;`dev:web` 只监听带 `dsh.client` 声明的 Client 插件并重写其 `lib/client.js`,它不会分析 Host decorator,也不会生成 Remote Client DTS。
|
||||
|
||||
只修改 Remote 方法实现体而不改变约定时,无需重新生成 TypeRT 文件。新增或删除 decorator、修改导出名、namespace、参数、返回值、lookup、Context 或取消签名时,重新执行有序 lib 构建,让 Host 先生成严格约定,再让 Client 编译并打包新的贡献:
|
||||
|
||||
@@ -157,7 +157,7 @@ pnpm run build:lib
|
||||
|
||||
## 边界
|
||||
|
||||
Remote 只处理有单个请求与单个结果的一元方法调用。Session event stream、分页、增量 reduce、projection 和实体子流需要独立的数据协议与注册模型;即使它们复用 Connection,也不应伪装成 Remote 方法或放入调用描述符。
|
||||
Remote 只处理有单个请求与单个结果的一元方法调用。会话事件流、分页、增量 reduce、projection 和实体子流需要独立的数据协议与注册模型;即使它们复用 Connection,也不应伪装成 Remote 方法或放入调用描述符。
|
||||
|
||||
API 各层按 `remotes → gateway → connection → webserver` 组织。BFF 与 TypeRT RPC 层位于 `packages/api`;Connection 与 WebServer 位于 `packages/client/connection` 和 `packages/host/webserver`。位于 `packages/host/apiproxy` 的 API Proxy 处理没有 Remote 描述符的 endpoint。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/architecture.md
|
||||
architecture.md: f5ebff879929079870c7936b424f03a02089c9d5
|
||||
architecture.zh.md: 1769f6febc4f156f6abccc5a19363f6eb55b6139
|
||||
architecture.zh.md: 00c0f51f6fb0df06f115b7ea31b9a98c17ddb1cd
|
||||
|
||||
@@ -124,7 +124,7 @@ idle inject:
|
||||
|
||||
适配器选择、分发与迭代失败会成为 error 或 aborted 类型的终止 `finish` 分片。`agent/request-error` 接收请求坐标、标准化 `LlmFailure`、可用的重试策略和信号;middleware 与消费方错误仍在恢复之外。失败分片既不提交消息,也不提交工具调用。
|
||||
|
||||
其他故障使用 `agent/error`;取消和 dispose(资源释放)优先于恢复。在提交请求头之前,轮次信号会取消能力准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 会在清空队列和中止前报告原因;空闲调用不发事件。driver 会处理在 abort 开始后、收敛前收到的唤醒输入;`disposed` 取消会让该输入保持待处理状态([取消收敛窗口唤醒锁存](../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md))。持久性以 `aborted` 区分取消,以 `disposed` 区分会等待完全停稳的拆卸([决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。
|
||||
其他故障使用 `agent/error`;取消和 dispose(资源释放)优先于恢复。在提交请求头之前,轮次信号会取消能力准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 会先报告原因,再执行清理和中止;空闲调用不发事件。driver 会处理在 abort 开始后、收敛前收到的唤醒输入;`disposed` 取消会让该输入保持待处理状态([取消收敛窗口唤醒锁存](../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md))。持久性以 `aborted` 区分取消,以 `disposed` 区分会等待完全停稳的拆卸([决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。
|
||||
|
||||
轮次和步骤事件均位于轮次边界内;loop 只会在轮次内从进入步骤的批次追加 `user/message`。轮次会在首次领取与 pre-step 之前打开,因此拒绝、空输入、取消或失败会关闭一个不包含任何步骤事件的持久轮次。独立的 `compact/* { turn: null }` 事件不占用轮次,其锁定时刻标记可以与 inbox splice 交错。重新加载会为中断的轮次合成结束事件;`session/end-seed` 区分陈旧的压缩遗留项与活跃锁。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](subsystems/session.md#why-a-turn-ended-turnendreasonmap)。
|
||||
|
||||
@@ -166,11 +166,11 @@ idle inject:
|
||||
|
||||
### 组合包与应用
|
||||
|
||||
`dsh-agent-spine-demo` 组合一套主干和可选目标。应用包负责 CLI(命令行界面)、ACP 自动化入口和 JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[acp/](../packages/acp/README.md)、[interaction/](../packages/interaction/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 在配置缺失时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。
|
||||
`dsh-agent-spine-demo` 组合一套主干和可选目标。应用包负责 CLI(命令行界面)、ACP 自动化和 JSON-RPC 入口([README](../packages/examples/agent-spine-demo/README.md)、[acp/](../packages/acp/README.md)、[interaction/](../packages/interaction/README.md))。`dsh-jsonrpc-agent` 启动外部 `cordis.yml`;Python SDK 在配置缺失时提供默认项([Python SDK](../python/README.md))。轻量部署使用可替换后端和可选工具([examples/](../examples/AGENTS.md)、[可运行接线](cookbook/extension-cookbook.md#runnable-wirings)、[图谱](graph-atlas.md))。
|
||||
|
||||
### Agent Preset
|
||||
|
||||
部署可为每个会话分别组装面向模型的插件集合。**agent preset** 是一个含 `agent.cordis.yml` 的目录,在 `setup(agentCtx)` 期间作为 `include` 子树挂到该 agent 的 scope 之下,其工具与提示词注册因而归档进该 agent 的分层并随之卸载,注册表无需新增层级。宿主组装保留必须共享的部分:注册表本身、跨会话设施、沙箱与审批栈、模型路由。`ctx.agentPresets` 负责发现与把关,拒绝未激活的行和把服务发布进根 realm 的行。详见 [按会话组装 agent preset](../.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md)、[preset/](../packages/preset/README.md)。
|
||||
部署可为每个会话分别组装面向模型的插件集合。**agent preset** 是一个含 `agent.cordis.yml` 的目录,在 `setup(agentCtx)` 期间作为 `include` 子树挂到该 agent 的 scope 之下,其工具与提示词注册因而归档进该 agent 的分层并随之卸载,注册表无需新增层级。宿主组装保留必须共享的部分:注册表本身、跨会话设施、沙箱与审批栈、模型路由。`ctx.agentPresets` 负责发现与受控挂载,拒绝从未激活的行,以及把服务发布进根服务 realm 的行。详见 [按会话组装 agent preset](../.agents/notes/implemented/architecture/2026-08-03-per-session-agent-presets.md)、[preset/](../packages/preset/README.md)。
|
||||
|
||||
### 新行为的归属位置
|
||||
|
||||
@@ -180,7 +180,7 @@ idle inject:
|
||||
|---|---|
|
||||
| 添加模型提供方 | 在 `ctx.llm` 上注册其适配器 |
|
||||
| 添加面向模型的能力 | 在 `ctx.tools` 上注册;schema 加入提示词组装 |
|
||||
| 让某个会话拥有不同的能力集合 | 在 agent preset 中组装它;其中的 service 行需要 `isolate` realm |
|
||||
| 让某个会话拥有不同的能力集合 | 在 agent preset 中组装它;其中的服务行需要 `isolate` realm |
|
||||
| 添加 shell 执行 | 实现并注册 `ctx.bash` 后端;本地后端通过 `ctx.subprocess` spawn 进程 |
|
||||
| 添加持久化终端执行 | 注册 `ctx.pty` 后端和 `dsh-tool-pty` |
|
||||
| 添加用户命令 | 在 `ctx.commands` 上注册;适配器无需模型轮次即可发现并分派 |
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||
config-catalog.md: e7ab120218c6de909bbd799c19b38eef524c7555
|
||||
config-catalog.zh.md: 622a0372de2f194e7063312ad7b8343a0bc58f06
|
||||
config-catalog.zh.md: 6c8c7ae55935a591329bac055def71c08468c3b6
|
||||
|
||||
@@ -124,7 +124,7 @@ export interface Config {
|
||||
|
||||
依赖:[`AgentOptions`](subsystems/core.md) · [`SessionId`](subsystems/core.md)
|
||||
|
||||
来源:[`packages/core/agent-loop/src/index.ts:236`](../packages/core/agent-loop/src/index.ts)
|
||||
来源:[`packages/core/agent-loop/src/index.ts:255`](../packages/core/agent-loop/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-agent-presets`
|
||||
|
||||
@@ -317,7 +317,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/bash/bash-local/src/index.ts:40`](../packages/bash/bash-local/src/index.ts)
|
||||
来源:[`packages/bash/bash-local/src/index.ts:41`](../packages/bash/bash-local/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-bash-sandbox`
|
||||
|
||||
@@ -535,7 +535,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/fs/fs-local/src/index.ts:40`](../packages/fs/fs-local/src/index.ts)
|
||||
来源:[`packages/fs/fs-local/src/index.ts:41`](../packages/fs/fs-local/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-fs-sandbox`
|
||||
|
||||
@@ -1024,7 +1024,7 @@ export interface ReplayModelConfig {
|
||||
export type Config = Readonly<Record<string, never>>
|
||||
```
|
||||
|
||||
来源:[`packages/llm/llm-retry/src/index.ts:46`](../packages/llm/llm-retry/src/index.ts)
|
||||
来源:[`packages/llm/llm-retry/src/index.ts:24`](../packages/llm/llm-retry/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-lsp-local`
|
||||
|
||||
@@ -1135,7 +1135,7 @@ export interface ReconnectConfig {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/mcp/mcp-client/src/index.ts:94`](../packages/mcp/mcp-client/src/index.ts)
|
||||
来源:[`packages/mcp/mcp-client/src/index.ts:98`](../packages/mcp/mcp-client/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-message-feedback`
|
||||
|
||||
@@ -1294,7 +1294,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/bash/pwsh-local/src/index.ts:54`](../packages/bash/pwsh-local/src/index.ts)
|
||||
来源:[`packages/bash/pwsh-local/src/index.ts:58`](../packages/bash/pwsh-local/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-pwsh-sandbox`
|
||||
|
||||
@@ -1438,7 +1438,7 @@ export interface Config {
|
||||
export type JsonlCompression = 'zstd' | 'none'
|
||||
```
|
||||
|
||||
来源:[`packages/session/session-persistence-jsonl/src/index.ts:59`](../packages/session/session-persistence-jsonl/src/index.ts)
|
||||
来源:[`packages/session/session-persistence-jsonl/src/index.ts:60`](../packages/session/session-persistence-jsonl/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-persistence-sqlite`
|
||||
|
||||
@@ -1602,7 +1602,7 @@ export enum TelemetryMode {
|
||||
|
||||
依赖:`BatchLogRecordProcessorOptions`(`@opentelemetry/sdk-logs`)· `OTLPExporterNodeConfigBase`(`@opentelemetry/otlp-exporter-base`)
|
||||
|
||||
来源:[`packages/session/session-telemetry-otel/src/index.ts:79`](../packages/session/session-telemetry-otel/src/index.ts)
|
||||
来源:[`packages/session/session-telemetry-otel/src/index.ts:91`](../packages/session/session-telemetry-otel/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-session-title`
|
||||
|
||||
@@ -2139,7 +2139,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/fs/tool-fs/src/index.ts:24`](../packages/fs/tool-fs/src/index.ts)
|
||||
来源:[`packages/fs/tool-fs/src/index.ts:25`](../packages/fs/tool-fs/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-tool-fs-search`
|
||||
|
||||
@@ -2641,7 +2641,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/web/web-search-deepseek/src/index.ts:44`](../packages/web/web-search-deepseek/src/index.ts)
|
||||
来源:[`packages/web/web-search-deepseek/src/index.ts:46`](../packages/web/web-search-deepseek/src/index.ts)
|
||||
|
||||
## `@deepseek-ai/dsh-web-search-exa`
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/adding-a-package.md
|
||||
adding-a-package.md: 4320d9737da1a6144f77fc3f02e2801e723f62b4
|
||||
adding-a-package.zh.md: 5e2c9094fbd0fb3f03c115598afdb32ac42e1e01
|
||||
adding-a-package.zh.md: 07f4216a68601700d11c11f393444dea7d070b56
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 实操手册:添加 workspace 包(package)
|
||||
# 实操手册:添加 workspace 包
|
||||
|
||||
[English](adding-a-package.md) | 中文
|
||||
|
||||
为新建 `@deepseek-ai/dsh-<name>` 包提供的逐文件清单。本清单以 bash 和 adapter 这两个包为模板进行验证;如果清单与模板有出入,请在此修正。
|
||||
为新建 `@deepseek-ai/dsh-<name>` 包提供的逐文件清单。本清单以 bash 和适配器这两个包为模板进行验证;如果清单与模板有出入,请在此修正。
|
||||
|
||||
## 1. 创建包
|
||||
|
||||
@@ -36,7 +36,7 @@ package.json 不变式(由 `pnpm run constraints` / `scripts/check-workspace-c
|
||||
|
||||
`packages/client/*` 包改为 extends `tsconfig.base.client.json`(而非 `tsconfig.base.json`);client 插件包还需在 package.json 声明 `dsh.client`、导出 `./client`、调用共享 tsdown preset(`packages/client/tsdown.client.ts`)——client 侧见 [packages/client/AGENTS.md](../../packages/client/AGENTS.md)。
|
||||
|
||||
以下内容由 glob 或包 manifest 发现机制自动覆盖,无需手动编辑:根 `package.json` workspaces、`scripts/publint-all.ts`、`tsdown.config.ts`、`.oxlintrc.json`、`scripts/check-workspace-constraints.ts`。
|
||||
以下内容由 glob 或包 manifest(元数据清单)发现机制自动覆盖,无需手动编辑:根 `package.json` workspaces、`scripts/publint-all.ts`、`tsdown.config.ts`、`.oxlintrc.json`、`scripts/check-workspace-constraints.ts`。
|
||||
|
||||
## 3. 确定包拓扑
|
||||
|
||||
@@ -87,4 +87,4 @@ pnpm run constraints && pnpm run typecheck && pnpm run lint
|
||||
pnpm run build && pnpm run hygiene
|
||||
```
|
||||
|
||||
请遵循[仓库测试政策](../testing.md),为新包运行行为所需的专项检查并达到相应覆盖率。
|
||||
请遵循[仓库测试政策](../testing.md),执行新包所需的行为专项检查并达到相应覆盖率。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/adding-a-tool.md
|
||||
adding-a-tool.md: 291e3a8b684a0d042102502279fe4c4393e34735
|
||||
adding-a-tool.zh.md: 69bd7ee150f30b1d3e6be08d87347e52952a360c
|
||||
adding-a-tool.zh.md: 6b2b1b9627db366c3385d4ebca3d8afd4cf6ed79
|
||||
|
||||
@@ -52,17 +52,17 @@ export function apply(ctx: Context) {
|
||||
|
||||
通过 producer 配置控制 `run_in_background`,然后使用 `ctx.tasks.start({ kind, label, owner: exec.agent, run })` 注册任务。注册表会在进入 producer 主体前将已预先中止的调用判为失败;运行时会在 `run()` 启动工作前校验 owner 和任务控制器是否可用,随后提供 id、会话围栏、通用控制工具、通知和 owner cleanup。成功的后台分支会返回类型化的规范句柄,如 `{ kind: 'background', taskId }`;其 Native 渲染器可以保留 `started background task bash-1` 这类供人阅读的自然语言,但 Code Mode 绝不能通过解析该文本取得 id。
|
||||
|
||||
producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及可选的消费式 `readOutput`(负责有界输出的格式化)。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.tasks.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `task_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台 task 运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与 `dsh-tool-bash`。
|
||||
producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的 `done`,以及可选的消费式 `readOutput`(负责有界输出的格式化)。预先中止的调用属于失败,因为此时没有任务,其 id 无法满足成功输出 schema。`ctx.tasks.start()` 发布 id 后,应使用任务自有的取消信号,而不是 `exec.signal`:之后取消外层调用只会停止等待本次调用,不会终止已经发布的工作;该生命周期归 `task_kill`、owner dispose 和服务 teardown 所有。前台工作仍与 `exec.signal` 耦合。流式 producer 的示例和完整约定见[后台任务运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)与 `dsh-tool-bash`。
|
||||
|
||||
## 执行策略与观测
|
||||
|
||||
尽量不要把部署策略内建到工具中。使用 `tools/pre-execute` 实现可扩展的允许/拒绝/询问策略(见[权限门禁示例](extension-cookbook.md#a-hook-plugin-permission-gate-example));使用 `ctx.tools.guard()` 设置最终的单调拒绝,后续监听器无法撤销;使用 `tools/execute` 为分发添加截止时间、重试或指标收集;使用 `tools/post-execute` 替换展示内容或返回值、阻止结果,或附加模型可见上下文;使用 `tools/result` 观测不可变的归一化结果而不改变它。替换内容不会阻止程序化访问 `value`;保密策略必须阻止调用或替换值。沙箱实现也可以在工具的执行器实现中运行;[`dsh-tools` README](../../packages/core/tools/README.md#extension-points) 定义每个扩展点的输入、顺序、返回值和失败行为。
|
||||
尽量不要把部署策略内建到工具中。使用 `tools/pre-execute` 实现可扩展的允许/拒绝/询问策略(见[权限门禁示例](extension-cookbook.md#a-hook-plugin-permission-gate-example));使用 `ctx.tools.guard()` 设置最终的单调拒绝,后续监听器无法撤销;使用 `tools/execute` 为分发添加截止时间、重试或指标收集;使用 `tools/post-execute` 替换展示内容或返回值、阻止结果,或附加模型可见上下文;使用 `tools/result` 观测不可变的归一化结果而不改变它。替换内容不会阻止程序化访问 `value`;保密策略会屏蔽或替换该值。沙箱实现也可以在工具的执行器实现中运行;[`dsh-tools` README](../../packages/core/tools/README.md#extension-points) 定义每个扩展点的输入、顺序、返回值和失败行为。
|
||||
|
||||
## Code Mode 自动触达你的工具
|
||||
|
||||
在 [Code Mode](../../packages/core/tools/README.md) 中,每个可见的已注册工具都可通过 `await tools.<name>(args)` 调用,无需额外集成。生成的 `ToolArgsMap` 和 `ToolOutputMap` 会根据同一组 schema 分别派生精确的参数类型与规范返回类型,调用则重新进入正常的执行流水线。成功调用会解析为策略处理后的最终规范 JSON 值,而不是渲染后的 Native 内容。失败调用会以真正的 `ToolCallError` reject;程序只能检查其 `name`、`toolName` 和可供人阅读的 `message`,无法取得内部错误代码或失败联合。
|
||||
|
||||
请把 `output.schema` 设计为实用的程序化 API:直接返回句柄与字段;当标量、数组或 null 确实就是结果时,允许采用相应的根类型;将面向人类的解释放入 `output.render`。中间值只存在于执行期间,不会被持久化或按提示词上限截断,也不设字节上限,因此生产方如实声明的采集边界和进程内存仍然重要。只有外层 `run_code` 日志/结果会受到可配置输出上限和面向模型的输出落盘流水线约束。
|
||||
请把 `output.schema` 设计为实用的程序化 API:直接返回句柄与字段;当标量、数组或 null 确实就是结果时,允许采用相应的根类型;将面向人类的解释放入 `output.render`。中间值只存在于执行期间,不会被持久化或按提示词上限截断,也不设字节上限,因此生产方如实声明的采集边界和进程内存仍然重要。只有外层 `run_code` 日志/结果会受到可配置输出上限和面向模型的 spill 流水线约束。
|
||||
|
||||
## 工具在 UI 中的渲染方式
|
||||
|
||||
@@ -71,19 +71,19 @@ producer 提供同步的 `cancel`、在资源清理后 settle 且不 reject 的
|
||||
两个方法都返回一个 **`card` 标签的渲染意图**——选择与你的工具行为匹配的卡片类型:
|
||||
|
||||
- `presentCall(args)` → 一个 `ToolCallView`(PENDING 卡片):
|
||||
- `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`——默认。设置 `kind` 获取图标(`read`/`search`/…);设置 `locations: [{ path, line? }]` 标注工具涉及的文件,使有能力的编辑器跟随/跳转。
|
||||
- `{ card: 'generic', title, kind?, rawInput?, content?, locations? }`——默认。设置 `kind` 获取图标(`read`/`search`/…);设置 `locations: [{ path, line? }]` 标注工具涉及的文件,使有能力的编辑器跟随/跳转。
|
||||
- `{ card: 'terminal', title, description?, cwd? }`——你的调用本身就是 shell 命令。`title` 是命令,`description` 渲染在终端卡片上方。(tool-bash。)
|
||||
- `{ card: 'diff', title, diffs, locations? }`——你的调用创建或修改文件。`diffs: [{ path, oldText, newText }]`(新文件时 `oldText: null`)渲染为内联 diff 卡片。(tool-fs `write`/`edit`。)
|
||||
- `{ card: 'diff', title, diffs, locations? }`——你的调用创建或修改文件。`diffs: [{ path, oldText, newText }]`(新文件时 `oldText: null`)渲染为内联 diff 卡片。(tool-fs `write`/`edit`。)
|
||||
- `presentResult(args, { content, isError, meta? })` 返回完成后的卡片:
|
||||
- `generic` 提供可选的标题和内容。
|
||||
- `terminal` 提供原始输出和可选的退出元数据;各 UI 根据自身能力渲染对应视图或回退视图。
|
||||
- `diff` 提供已应用的 hunk,通常由 `output.presentationMeta` 派生并通过持久化的 `result.meta` 携带,使回放能重现它们。变更类工具保留 diff 结果,因为完成后的视图会替换 pending 卡片。
|
||||
- `search` 提供从持久化 `result.meta` 重建的发现型结果:按文件分组的匹配(`shape: 'matches'`,grep)或扁平路径列表(`shape: 'paths'`,glob),外加 `truncated`/`total` 使 UI 永不把被截断的结果当作完整结果呈现。该视图不携带结果文本(无 search 卡片的 UI 回退到原始结果内容),也没有 `search` 调用视图——发现型调用的 pending 状态保持为 generic 卡片,因为匹配只在 `execute` 之后才存在。(tool-fs-search 的 `grep`/`glob`。)
|
||||
- `search` 提供从持久化 `result.meta` 重建的发现型结果:按文件分组的匹配(`shape: 'matches'`,grep)或扁平路径列表(`shape: 'paths'`,glob),外加 `truncated`/`total` 使 UI 永不把被截断的结果当作完整结果呈现。该视图不携带结果文本(无 search 卡片的 UI 回退到原始结果内容),也没有 `search` 调用视图——发现型调用的 pending 状态保持为 generic 卡片,因为匹配只在 `execute` 之后才存在。(tool-fs-search 的 `grep`/`glob`。)
|
||||
- `web` 提供已完成的 web 检索,以 `kind: 'search' | 'fetch'` 区分(结构化的搜索来源或抓取摘要),由 `result.meta` 派生;它不携带正文副本,因此不具备 `web` 能力的 UI 回退到原始结果内容。(tool-web `web_search`/`web_fetch`。)
|
||||
|
||||
硬性规则(违反会出问题):
|
||||
|
||||
- **纯函数。** 这些方法在实时流式输出和会话日志回放时都会运行,因此必须是 `args`(加 result)的纯函数——不做 I/O、不读会话状态、不用时钟/随机数。diff 从 args 派生(`write` 使用 `oldText: null`,因为调用时的展示器没有文件先前内容);会话上下文由 UI 适配器而非工具提供。如果你发现自己想在 `presentCall` 内获取文件旧内容或工作目录,请停下:那属于持久结果元数据或适配器,不属于展示器。
|
||||
- **纯函数。** 这些方法在实时流式输出和会话日志回放时都会运行,因此必须是 `args`(加 result)的纯函数——不做 I/O、不读会话状态、不用时钟/随机数。diff 从 args 派生(`write` 使用 `oldText: null`,因为调用时的展示器没有文件先前内容);会话上下文由 UI 适配器而非工具提供。如果你发现自己想在 `presentCall` 内获取文件旧内容或工作目录,请停下:那属于持久结果元数据或适配器,不属于展示器。
|
||||
- **UI 格式不进入模型结果。** 围栏 ` ```console ` 块、diff、相对化路径均不应仅为服务 UI 而进入规范值或 Native 内容。`output.render` 负责模型可见的自然语言;`presentationMeta` 和卡片展示器负责可回放的 UI 状态。`terminal` 结果视图携带原始输出,由适配器按需添加回退格式。
|
||||
- **`defineTool` 对展示路径做软校验。** 格式错误或旧版日志中的参数会使包装器返回 `undefined`(通用回退)而非抛异常——展示绝不能导致回放崩溃。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/adding-a-vendored-package.md
|
||||
adding-a-vendored-package.md: 239ac27565204332559038014fabae83fc2d1057
|
||||
adding-a-vendored-package.zh.md: 2bf5c7eeaffec594dbc4d13e0aa33b12a9eccf4f
|
||||
adding-a-vendored-package.zh.md: 48abac4dd055319c467261878764c2af4c43fa91
|
||||
|
||||
@@ -56,4 +56,4 @@ pnpm run typecheck
|
||||
pnpm run build && pnpm run constraints
|
||||
```
|
||||
|
||||
请运行[测试政策](../testing.md)所选择的行为检查。源码 `paths` 映射只在 `tsconfig.base.json` 存在一份,服务所有图。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor/<dir>/tsconfig.json` 被引用,而非被拉入某个聚合的严格程序中([布局](../development.md#typescript-project-layout))。
|
||||
请运行[测试政策](../testing.md)所选择的行为检查。源码 `paths` 映射只在 `tsconfig.base.json` 存在一份,服务所有图。重要的隔离边界是 project-reference 图:vendored 源码必须通过其自身的 `vendor/<dir>/tsconfig.json` 被引用,而非被拉入某个聚合项目启用严格检查的 TypeScript 程序中([布局](../development.md#typescript-project-layout))。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/extension-cookbook.md
|
||||
extension-cookbook.md: f292075dfdad5016d81521318b38594e3d7ee8b4
|
||||
extension-cookbook.zh.md: 0623c49d9d7075b3823c1fd340b36a5fba21f31e
|
||||
extension-cookbook.zh.md: d1d2e0a8f3505678518fc192587da52e84314825
|
||||
|
||||
@@ -105,7 +105,7 @@ export function apply(ctx: Context) {
|
||||
| `/loop` | 在 `turn/end` 会话事件上 `followup()` 下一次迭代;或强制继续 |
|
||||
| 动态工作流 | `ctx.workflows` + worker-thread 引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的提示词/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和结构化输出执行的单调 `concludeTurn()` 标记来强制输出 |
|
||||
| 排队消息 + steering | 核心 `Agent.followup()` / `Agent.steer()` |
|
||||
| 上下文压缩(context compaction)(自动 + 手动) | `ctx.compact` seam + `dsh-compact-basic`;自动压力检查运行在串行 `agent/pre-step`,规范化溢出恢复运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)) |
|
||||
| 上下文压缩(context compaction)(自动 + 手动) | `ctx.compact` seam + `dsh-compact-basic`;自动压力检查运行在串行 `agent/pre-step`,标准的溢出恢复机制运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)) |
|
||||
| 系统提示词可配置性 | `ctx.systemPrompt.section()`,支持排序与作用域局部覆盖 |
|
||||
| AGENTS.md(根目录) | 一个读取该文件的 section 提供方 |
|
||||
| AGENTS.md(子目录,按需触发)+ 文件变更通知 | 从 watcher / 工具结果监听器调用 `agent.inject()` |
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cookbook/maintaining-dsh-code-review.md
|
||||
maintaining-dsh-code-review.md: a8c2a66c065aaec5c03f0ab6965377d1d1eb14bd
|
||||
maintaining-dsh-code-review.zh.md: c72323c4ab0c31fa30aa3ef0e8ea41b129ad0c1d
|
||||
maintaining-dsh-code-review.zh.md: 60c10ca59d36aa5f36177487c4aa7927397bf2f3
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 维护 dsh-code-review skill(技能)
|
||||
# 维护 dsh-code-review skill
|
||||
|
||||
[English](maintaining-dsh-code-review.md) | 中文
|
||||
|
||||
[`dsh-code-review`](../../.agents/skills/dsh-code-review/SKILL.md) skill 由一名指定操作员通过私有的周期维护工具持续更新。本实操手册既是该操作员和接任者的入口,也帮助仓库贡献者理解为何 skill 更新会以小型周期 PR(Pull Request)的形式出现,而不是一次性审计。工作流本身由[人工评审 skill 维护 Agent Note](../../.agents/notes/proposed/process/2026-07-13-human-review-skill-maintenance.md)规定。
|
||||
[`dsh-code-review`](../../.agents/skills/dsh-code-review/SKILL.md) skill(技能)由一名指定操作员通过私有的周期维护工具持续更新。本实操手册既是该操作员和接任者的入口,也帮助仓库贡献者理解为何 skill 更新会以小型周期 PR(Pull Request)的形式出现,而不是一次性审计。工作流本身由[人工评审 skill 维护 Agent Note](../../.agents/notes/proposed/process/2026-07-13-human-review-skill-maintenance.md)规定。
|
||||
|
||||
## 维护者会收到什么
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cordis-api/context.md
|
||||
context.md: 320b23de1c8d19e2dacdf0ec9f2361037bcf8048
|
||||
context.zh.md: 9b1f92a329ff4d75b3fafe7bf6bc8db317bcc267
|
||||
context.zh.md: 32b513e2a29d2f6bd42c0a12134722691da37080
|
||||
|
||||
@@ -230,7 +230,7 @@ static is(value: any): value is Context
|
||||
|
||||
- `value`:要测试的值。
|
||||
|
||||
如果返回 `true`,则 `value` 是 Cordis 上下文,并会收窄其类型。
|
||||
**返回** `true` 时,`value` 是 Cordis 上下文,并会收窄其类型。
|
||||
|
||||
[源码](../../vendor/cordis/src/context.ts#L61)
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cordis-api/registry.md
|
||||
registry.md: e6ddce5700ab071c2a4fedae19d99e2cb6e994ec
|
||||
registry.zh.md: c10509f569adfe65b4dbee4ca5b7139085dce1f7
|
||||
registry.zh.md: d928ff20c9d764a36d84ce0d0aec0af7f41b630f
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
inject(deps: Inject, callback: Plugin.Function<void>): Fiber & PromiseLike<Fiber>
|
||||
```
|
||||
|
||||
当请求的服务可用后,运行一次回调。
|
||||
请求的服务可用后,运行回调。
|
||||
|
||||
这是 `ctx.plugin({ inject, apply: callback })` 的简写形式:每当某个必需服务发生变化时,系统都会卸载并重新运行该回调。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cordis-tutorial/04-events.md
|
||||
04-events.md: db911b9a06d7304d73030a2020de3d465fde2f9c
|
||||
04-events.zh.md: e0357b2c04785d52a54f839d1b8ff3ecccb027ed
|
||||
04-events.zh.md: e72c94e8548a6ac621f9a020db81910bcf22b541
|
||||
|
||||
@@ -89,7 +89,7 @@ export function apply(ctx: Context) {
|
||||
| bail | `ctx.bail(name, ...args)` | serial 的同步版本。 |
|
||||
| waterfall(瀑布式事件) | `ctx.waterfall(name, ...args, next)` | 环绕中间件,见下文。 |
|
||||
|
||||
每个 harness 事件都会在其所属[子系统页面](../subsystems/core.md)的生成参考中记录其模式。
|
||||
每个 harness 事件都会在其所属[子系统页面](../subsystems/core.md)自动生成的参考文档中记录其模式。
|
||||
|
||||
## waterfall:转换或短路
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/cordis-tutorial/index.md
|
||||
index.md: fb700344e6d07d3864655009d2edac15ee9eede8
|
||||
index.zh.md: a68e931d81e745164d8f9a5dc7ec9aec4cd0e590
|
||||
index.zh.md: 9759123c42db893e91c2f3660f2f64a6c466b0a9
|
||||
|
||||
@@ -6,7 +6,7 @@ Cordis 是 DeepSeek Harness SDK 底层的插件框架:它是一个小型运行
|
||||
|
||||
本教程面向 agent 开发者。你不需要深入掌握 TypeScript;下文的 [TypeScript 说明](#typescript-notes)会解释可能陌生的语法,并且每一章都会给出确切命令和预期输出。
|
||||
|
||||
如果你想阅读精简的概念参考,而不是逐步实践,请参阅 [Cordis 入门](../cordis-primer.md)。详尽的 API 参考见[子系统页面](../subsystems/core.md)上生成的 `cordis-surface` 区块,以及 [Cordis 核心 API](../cordis-api/context.md)页面。
|
||||
如果你想阅读精简的概念参考,而不是逐步实践,请参阅 [Cordis 入门](../cordis-primer.md)。详尽的 API 参考见[子系统页面](../subsystems/core.md)上生成的 `cordis-surface` 区块,以及 [Cordis 核心 API](../cordis-api/context.md) 页面。
|
||||
|
||||
## 准备工作
|
||||
|
||||
@@ -49,7 +49,7 @@ node --import tsx ../../vendor/cordis/bin.js
|
||||
|
||||
这些示例使用了普通现代 JavaScript 之外的三项 TypeScript 功能:
|
||||
|
||||
- **类型注解** 描述值,但不会改变运行时行为:`ctx: Context` 表示 `ctx` 具备 Cordis 上下文 API,`who: string` 接受文本,而 `string[]` 表示字符串数组。
|
||||
- **类型注解**描述值,但不会改变运行时行为:`ctx: Context` 表示 `ctx` 具备 Cordis 上下文 API,`who: string` 接受文本,而 `string[]` 表示字符串数组。
|
||||
- **`import type { Context } from '@deepseek-ai/cordis'`** 只导入类型信息。它在运行时会消失,因此仅为类型注解使用 `Context` 的插件文件不会增加运行时依赖。
|
||||
- **声明合并**(`declare module '@deepseek-ai/cordis' { ... }`)会为 Cordis 已经声明的接口添加你的条目,例如新 `ctx.greeter` 属性的类型或事件名称。它不会生成任何运行时接线;插件必须另行提供服务或发出事件。第 3 章会完整展示该模式。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/defensive-patterns.md
|
||||
defensive-patterns.md: 368c9876f1a4e7042b003f6acfb30af3b2daf402
|
||||
defensive-patterns.zh.md: c7d4c1bf37ef17947913ac4011624d04ffd8c1a3
|
||||
defensive-patterns.zh.md: 89db2149275e54ab21addc5a4ac1a945d06c0b70
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
|
||||
## 公共约定两侧都要遵守
|
||||
|
||||
当一个实现收到同一结果的多种表示时,应在通过公共 API 返回前将其规范化。`LlmAdapter.stream()` 的实现可以抛出异常或发出 `finish {kind:'error'|'aborted'}`,但 `LlmService.stream()` 只会通过终止型 finish 分片暴露模型请求失败;middleware 缺陷与消费方缺陷仍会以异常形式抛出。这使消费方不必猜测捕获的异常究竟来自提供方、包装层、chunk 日志记录还是自身组装逻辑。请在类型定义处记录规范化后的约定;通过真实消费方覆盖每种来源形式。
|
||||
当一个实现收到同一结果的多种表示时,应在通过公共 API 返回前将其规范化。`LlmAdapter.stream()` 的实现可以抛出异常或发出 `finish {kind:'error'|'aborted'}`,但 `LlmService.stream()` 只会通过终止型 finish 分片暴露模型请求失败;middleware 缺陷与消费方缺陷仍会以异常形式抛出。这使消费方不必猜测捕获的异常究竟来自提供方、包装层、分片日志记录还是自身组装逻辑。请在类型定义处记录规范化后的约定;通过真实消费方覆盖每种来源形式。
|
||||
|
||||
## 异步状态不是同步状态
|
||||
|
||||
`agent.followup()` 没有逐消息的完成状态或结果;后台任务的完成与轮次边界存在竞争;`reader.close()` 在 EOF 和 dispose(资源释放)两种情况下都会触发。切勿把 `agent/status` 或 `whenIdle()` 当作某次 `followup()` 的结果:多条已排队的后续消息、steering(中途引导)和注入工作可能共用同一个 `running` 区间,而取消或资源释放可能丢弃尚未启动的项。真正拥有一次运行的自动化调用方必须显式定义其区间——例如从消息的持久 inbox 回执到整个 agent 下一次进入 `idle`——并将选取的任何输出描述为整个区间的输出,而不是把因果关系归于该消息。这条守则是双向的:如果等待的转换永远不会发生,等待就会挂起,因此应显式处理「无需等待」的分支。
|
||||
`agent.followup()` 没有逐消息的完成状态或结果;后台任务的完成与轮次边界存在竞争;`reader.close()` 在 EOF 和 dispose(资源释放)两种情况下都会触发。切勿把 `agent/status` 或 `whenIdle()` 当作某次 `followup()` 的结果:多条已排队的后续消息、steering(中途引导)和注入工作可能共用同一个 `running` 区间,而取消或资源释放可能丢弃尚未启动的项。真正拥有一次运行的自动化调用方必须显式定义其区间——例如从消息的持久 inbox 回执到整个 agent(智能体)下一次进入 `idle`——并将选取的任何输出描述为整个区间的输出,而不是把因果关系归于该消息。这条守则是双向的:如果等待的转换永远不会发生,等待就会挂起,因此应显式处理「无需等待」的分支。
|
||||
|
||||
## Dispose 必须达到完全停稳,而不仅仅是请求停止
|
||||
## dispose 必须达到完全停稳,而不仅仅是请求停止
|
||||
|
||||
如果清理流程只发出终止或中止信号便返回,而不等待工作真正停止,就会留下孤儿进程。清理逻辑应采用异步流程,并等待子进程退出(发出终止信号后等待 `done`);还应在终止进程前关闭监听器和通知注册表,使迟到的完成事件保持静默。
|
||||
如果清理流程只发出终止或中止信号便返回,而不等待工作真正停止,就会留下孤儿进程。清理逻辑应采用异步流程,并等待子进程退出(发出终止信号后等待 `done`);还应在终止进程前关闭监听器注册表和通知注册表,使迟到的完成事件保持静默。
|
||||
|
||||
## 在分发器中隔离回调异常
|
||||
|
||||
@@ -26,4 +26,4 @@
|
||||
|
||||
## 绝不将环境变量或可预测路径暴露给不可信输出
|
||||
|
||||
启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*`、`*SECRET*`、`*TOKEN*` 或 `*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'`、`0o600`);可预测且全局可读的路径会引发符号链接竞态和信息泄露。
|
||||
启动的命令应使用经过清理的环境变量,移除名称匹配 `*KEY*`、`*SECRET*`、`*TOKEN*` 或 `*PASSWORD*` 的项,防止 harness 凭证通过命令输出、`env` 或 spill 文件泄漏。临时文件和 spill 文件应放在权限为 0700 的私有目录中,使用随机文件名,并以独占且仅所有者可访问的方式打开(`'wx'`、`0o600`);可预测且所有用户均可读的路径会引发符号链接竞态和信息泄露。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/development.md
|
||||
development.md: d5bd18f83f6f2f8fd3d507ec133eea4d4e06abef
|
||||
development.zh.md: 0725cfaad74ec2aa3e52f6523a1dbdbdd9da8a39
|
||||
development.zh.md: f2c7985a2e2018d600c1f135a9c81403d4edbcd9
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](development.md) | 中文
|
||||
|
||||
搭建教程引导新贡献者从准备前置条件开始,直到检出通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 组织方式。设计依据与实现细节属于链接的 Agent Note 和脚本。
|
||||
搭建教程引导新贡献者从准备前置条件开始,直到检出目录通过检查。后面的贡献者参考介绍仓库布局、日常工作流和 CI 组织方式。设计依据与实现细节属于链接的 Agent Note 和脚本。
|
||||
|
||||
## 搭建教程
|
||||
|
||||
@@ -43,21 +43,21 @@ pnpm run typecheck
|
||||
|
||||
### TypeScript 项目布局
|
||||
|
||||
仓库使用相互隔离的 Host 与 Client aggregate。普通 package 只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。
|
||||
仓库使用相互隔离的 Host 与 Client aggregate。普通包只登记进其中一个 aggregate;Host 包进入 `tsconfig.host.json`,Client 包进入 `tsconfig.client.json`。
|
||||
|
||||
| 文件 | 角色 | 是否构成 program? |
|
||||
|---|---|---|
|
||||
| `tsconfig.json` | solution 根:`extends` base、`files: []`、引用两个 aggregate。它是 tsserver 发现入口,也是显式执行整张 Project Reference 图时的入口;经继承的 `paths` 充当 tsx 运行 `examples/` 与 `scripts/` 时的解析配置。 | 否 |
|
||||
| `tsconfig.host.json` | Host aggregate:Host package、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |
|
||||
| `tsconfig.client.json` | Client aggregate:`packages/client/*` package 及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |
|
||||
| `tsconfig.host.json` | Host aggregate:Host 包、示例、测试、脚本和 website,以及 `api/remotes` 的 Host 特例 project。 | 是 |
|
||||
| `tsconfig.client.json` | Client aggregate:`packages/client/*` 包及其测试、`apps/web`,以及 `api/remotes` 的 Client 特例 project。 | 是 |
|
||||
| `tsconfig.base.json` | 共享 compilerOptions 与源码 `paths` 映射。同时是各 vitest 配置让 vite-tsconfig-paths 指向的解析门面:它没有 `include`,因此其 `paths` 适用于任何 importer。 | 否 |
|
||||
| `tsconfig.base.client.json` | 浏览器编译设置(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` package extends。 | 否 |
|
||||
| `tsconfig.base.client.json` | 浏览器编译设置(`jsx`、DOM lib、`types: []`),由 Client aggregate 和每个 `packages/client/*` 包 extends。 | 否 |
|
||||
|
||||
Host 与 Client 保持两个 aggregate program,是因为两侧在相同键下以不同服务对 cordis `Context` 接口做声明合并;单一 program 同时看到两份合并会报冲突。这种冲突只存在于 `ts.Program` 内部——模块解析永远不会触发它——所以 solution 可以同时引用两个 aggregate,一个 paths 门面也可以横跨两侧。由此推出三条纪律:
|
||||
|
||||
- `tsconfig.base.json` 永不添加 `include` 或 `files`:它们会泄漏进每个 extends 它的包项目,并收窄门面的全匹配范围。
|
||||
- 构造全仓 `ts.Program` 的脚本显式种子 `tsconfig.host.json` 或 `tsconfig.client.json`——永不种子根 solution,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。
|
||||
- 新 package 只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client plugin 的两份运行时产物都在 Client 构建阶段生成。
|
||||
- 构造全仓 `ts.Program` 的脚本显式以 `tsconfig.host.json` 或 `tsconfig.client.json` 为种子——根 solution 永不作为种子,因为把两个 aggregate 展平进一个 program 会撞上 `Context` 合并冲突。
|
||||
- 新包只登记进一个 aggregate。包同时具有 Node loader 入口和 browser 入口并不构成拆分理由;普通 Client 插件的两份运行时产物都在 Client 构建阶段生成。
|
||||
|
||||
`api/remotes` 是唯一拆分 Host/Client tsconfig 的仓库特例。它的 Host 入口必须进入 Host TypeRT 图,而 Client 入口导入 Host tsdown 才会生成的 `/remote` 声明,因此本包根 `tsconfig.json` 只作为 solution,两个 aggregate 和直接消费方分别引用 `tsconfig.host.json` 或 `tsconfig.client.json`。workspace `constraints` 门禁遍历可达的 Project Reference 图,并按各引用 project 自身的 compiler face 检查:只有单一配置的目标可由任一 face 引用,拆分配置的目标则必须引用匹配的 leaf,不得引用 solution 根或另一侧 leaf;该门禁按「两个 leaf 配置同时存在」自动发现拆分包,所以新拆分的包会自动纳入管辖。不要把该结构推广到其他包;[`api-remotes` README](../packages/api/remotes/README.md) 说明 Host/Client 拆分与构建顺序。
|
||||
|
||||
@@ -71,13 +71,13 @@ tsdown --env.DSH_BUILD_FACE client
|
||||
pnpm run build:web
|
||||
```
|
||||
|
||||
两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client package,也不维护 Host/Client package 过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client plugin 在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。
|
||||
两次 tsdown 都使用同一组完整 workspace 匹配,不扫描构建产物来发现 Client 包,也不维护 Host/Client 包过滤表。包内 tsdown 配置根据 `DSH_BUILD_FACE` 决定当前阶段的入口:普通 Client 插件在 Client 阶段同时生成 Node loader 与 browser bundle;`api-remotes` 通过 `hostPhase: true` 提前生成 Host 入口,再在 Client 阶段只生成 browser bundle。tsdown 只消费 `lib/types` 中由前置 tsc 发射的 JavaScript。
|
||||
|
||||
TypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分析 Host 类型并生成 Host 反射产物及 Host-for-Client Remote 投影;Client tsdown 不启动 TypeRT。`pnpm run typecheck` 因此先执行完整 Host lib 阶段,再运行 Client tsc;`pnpm run build` 继续执行 Client tsdown 和 Web 构建。该顺序的决策记录见 [API Remotes 生成约定构建 Note](../.agents/notes/implemented/process/2026-08-08-api-remotes-generated-contract-build.md)。
|
||||
|
||||
静态分析和测试通过 base 的 `paths` 映射把工作区 import 解析到 `src`,且必须在干净树上通过;消费构建产物 `lib/` 的门禁显式声明该依赖。生成的 Host-for-Client Remote 声明是有意设置的例外:公共 `typecheck`、`lint` 和 `doc-typecheck` 命令会先生成这些声明,而内部 `*:contracts-ready` 脚本假定调用它的公共命令或调度器门禁已经依赖 TypeRT 约定生成阶段或完整构建。两个 aggregate 的设置见 [solution-root Note](../.agents/notes/implemented/process/2026-07-22-tsconfig-solution-root-two-aggregates.md),tsc-first 发射职责见 [ts-build-config Note](../.agents/notes/implemented/process/2026-06-17-ts-build-config.md),门禁准备约定见 [TypeRT Remote Agent Note](../.agents/notes/implemented/architecture/2026-08-02-typert-remote-method-calls.md)。
|
||||
|
||||
业务 Service 在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。
|
||||
业务服务在 Host 使用 `@Remote` 或 `@RemoteScope` 声明可调用方法;Host 构建生成 Host-for-Client 类型与运行时贡献,Client 的 `api-remotes` 组合加载这些贡献并挂到 `ctx.remote` 与作用域 `agentCtx.remote` namespace。两侧的生成产物、装配关系、SRC 开发回退和 Web 构建顺序见 [API Gateway](api-gateway.md)。
|
||||
|
||||
如果相关的本地检查需要使用构建后的包产物,请先构建一次:
|
||||
|
||||
@@ -85,7 +85,7 @@ TypeRT 只在 Host tsdown 中以 `tsconfig.host.json` 为种子运行。它分
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验 package 入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。
|
||||
`pnpm run hygiene` 包含 `publint`(用构建出的 `lib/*.js` 文件校验包入口点)和 `verify-node-next-types`(用一个临时的 NodeNext 消费方校验构建出的声明文件)。新 worktree 在 `pnpm run build` 运行之前没有打包的 JS 和声明文件;普通提交和推送无需构建,除非所选检查会使用这些产物。
|
||||
|
||||
### 环境变量
|
||||
|
||||
@@ -122,7 +122,7 @@ keyless [CI 工作流](../.github/workflows/ci.yml) 将独立门禁分组到若
|
||||
|
||||
### 日常命令
|
||||
|
||||
根目录的[贡献者说明](../AGENTS.md#commands)概述常用命令,[`package.json`](../package.json) 与 [scripts/run-gates.ts](../scripts/run-gates.ts) 则负责当前脚本和门禁清单。请选择覆盖变更表面的最小检查集。文档变更使用 `pnpm run doc-sync`;package 公开行为变更还需更新所属 README 或 JSDoc,而基于构建产物的检查需要先运行 `pnpm run build`。
|
||||
根目录的[贡献者说明](../AGENTS.md#commands)概述常用命令,[`package.json`](../package.json) 与 [scripts/run-gates.ts](../scripts/run-gates.ts) 则负责当前脚本和门禁清单。请选择覆盖变更表面的最小检查集。文档变更使用 `pnpm run doc-sync`;包公开行为变更还需更新所属 README 或 JSDoc,而基于构建产物的检查需要先运行 `pnpm run build`。
|
||||
|
||||
### 演示
|
||||
|
||||
@@ -154,7 +154,7 @@ pnpm run demo:acp
|
||||
|
||||
请选择与紧急程度匹配的标签,让浏览代码的人一眼分清「发布阻塞」和「有空再说」。
|
||||
|
||||
### 逐字记录类型(`ts type-equiv`)
|
||||
### 逐字记录类型定义(`ts type-equiv`)
|
||||
|
||||
[子系统](subsystems/README.md)页面会把与源码等价的声明及其原始 JSDoc 一并粘贴,让读者看到确切类型定义和源码约定。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号:
|
||||
|
||||
@@ -162,4 +162,4 @@ pnpm run demo:acp
|
||||
{ "doc": "docs/subsystems/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" }
|
||||
```
|
||||
|
||||
`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码约定和确切类型定义。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。
|
||||
`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明及其附带的 JSDoc,并断言代码块同时匹配两者。对于不应把实现体写进目录的类,请使用 ` ```ts public-api ` 并设置 `"projection": "public-api"`;门禁检查的投影会保留公共字段、构造函数、访问器、方法以及类和成员的原始 JSDoc,同时省略实现体和私有或受保护成员。比对会忽略空白和非 JSDoc 注释,但要求保留每条原始 JSDoc(包括成员文档),让读者同时看到源码约定和确切类型定义。该门禁按文档、符号和投影,在主块与 manifest 条目之间强制 1:1 对应;只有当配对 `.zh.md` 块的完整受跟踪围栏序列与其无后缀兄弟文件按字节一致且顺序相同时,才会复用后者的条目。`doc-typecheck` 对可编译围栏应用同一派生规则,同时跳过两种源码等价围栏的编译,并将其排除在 opt-out 比例的计算之外。当你改动一个已记录的类型声明或其 JSDoc 时,门禁会失败直到你更新粘贴内容;当你增删一个主块时,请在同一个变更里更新 manifest。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/event-producer-consumer.md
|
||||
event-producer-consumer.md: d3cf5c89f6eb1371ac9962bff59b53b6045f0b9c
|
||||
event-producer-consumer.zh.md: 1d99765b5b83b14552e2948fc7560739634a87c3
|
||||
event-producer-consumer.zh.md: d2dfba8ff2c3f1ce3020dd526b07dd8803b8d6f6
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
| 事件 | 模式 | 声明位置 | 派发方 | 监听方 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:182`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
|
||||
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:183`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - |
|
||||
| `agent-preset/selected` | `emit` | [`packages/preset/agent-presets/src/types.ts:13`](../packages/preset/agent-presets/src/types.ts) | [`agent-presets`](../packages/preset/agent-presets) (`emit`) | `apiproxy` |
|
||||
| `agent/created` | `emit` | [`packages/core/agent/src/runtime-types.ts:159`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-presets`](../packages/preset/agent-presets), [`goal-session`](../packages/goal/goal-session), [`tool-schedule`](../packages/schedule/tool-schedule) |
|
||||
| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:168`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`goal-session`](../packages/goal/goal-session), [`subagent`](../packages/subagent/subagent) |
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/glossary.md
|
||||
glossary.md: 77c6c729417968c683d0128699804c6ca3a7fee7
|
||||
glossary.zh.md: d6a8e423ff2a6324ce0cdd65dd3edde573fac5b1
|
||||
glossary.zh.md: 375527fd4bcec4083cbb8c5047077079c630ba7e
|
||||
|
||||
@@ -6,25 +6,25 @@ DeepSeek Harness SDK 的领域词汇为每个概念规定一个规范术语。
|
||||
|
||||
## capability-seam
|
||||
|
||||
- **seam**:一种包含三种角色的*可替换能力*:**Service Definition**(拥有自身 `ctx.<key>` 和词汇类型的 Cordis `Service`——可以是 `BashExecutor` 这样的抽象类,也可以是 `WebService` 这样的具体注册表,从不是 TypeScript `interface`)、一个或多个 **Service provider**,以及一个或多个注入该服务的 **Consumer**。`packages/bash` 是规范范例:`dsh-bash`(Service Definition)、`dsh-bash-local` / `dsh-bash-sandbox`(提供方),以及 `dsh-tool-bash`(Consumer)。角色需要独立演进时通常位于不同包,但属于同一关注点时,一个包也可以承担多个角色(`dsh-llm` 同时承担 Service Definition 和 Consumer)。seam 是完整能力,绝不是其中一个角色;该术语仅保留此义,能力成员应按其角色、类、服务、约定或扩展点命名。
|
||||
- **seam**:一种包含三种角色的*可替换能力*:**Service Definition**(拥有自身 `ctx.<key>` 和词汇类型的 Cordis `Service`——可以是 `BashExecutor` 这样的抽象类,也可以是 `WebService` 这样的具体注册表,绝不是 TypeScript `interface`)、一个或多个 **Service provider**,以及一个或多个注入该服务的 **Consumer**。`packages/bash` 是规范范例:`dsh-bash`(Service Definition)、`dsh-bash-local` / `dsh-bash-sandbox`(提供方),以及 `dsh-tool-bash`(Consumer)。角色需要独立演进时通常位于不同包,但属于同一关注点时,一个包也可以承担多个角色(`dsh-llm` 同时承担 Service Definition 和 Consumer)。seam 是完整能力,绝不是其中一个角色;该术语仅保留此义,能力成员应按其角色、类、服务、约定或扩展点命名。
|
||||
|
||||
## agent-scope
|
||||
|
||||
- **scope**:按 agent(智能体)划分的注册单位。一项贡献(工具、提示词片段、变量、限制、监听器)要么是*全局的*(对所有 agent 可见),要么是*带作用域的*(归属于恰好一个 [scope key](#scope-key))。只有两层,采用扁平结构:带作用域的注册不会向下继承给 subagent;子树行为通过 [lineage](#lineage) 数据表达,从不通过 scope 结构。
|
||||
- **scope**:按 agent(智能体)划分的注册单位。一项贡献(工具、提示词段、变量、限制、监听器)要么是*全局的*(对所有 agent 可见),要么是*带作用域的*(归属于恰好一个 [scope key](#scope-key))。只有两层,采用扁平结构:带作用域的注册不会向下继承给 subagent;子树行为通过 [lineage](#lineage) 数据表达,从不通过 scope 结构。
|
||||
- **scope key**:scope 的不透明标识,按对象同一性比较。harness 约定:一个活跃的 agent 就是其自身 scope 的 key。<a id="scope-key"></a>
|
||||
- **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。
|
||||
- **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`);从不影响可见性。<a id="lineage"></a>
|
||||
|
||||
## 目标
|
||||
|
||||
- **目标**:附着在现有会话上的单个持久完成目标,带有按修订号演进的 `active` / `paused` / `blocked` / `complete` 阶段和 Goal Round 上限;`blocked` 保留策略代码与说明。目标是一种状态,不是调度器,也不是一段独立对话;会话日志仍是其真源。
|
||||
- **Goal Round**:为当前目标接纳的一次续行周期。同会话驱动器将 Goal Round 具体化为一个由目标触发的[轮次](#turn),其中可包含零个或多个步骤;同一会话中无关的人类轮次不消耗 Goal Round 上限。<a id="goal-round"></a>
|
||||
- **目标激活**:续行消费方接纳下一个 Goal Round 的进程本地权限。激活态为 `armed` 或 `disarmed`;它有意不参与持久回放,因此在恢复或 fork 后,只有人类随后通过 `/goal` 或模型工具授权一次恢复操作,自动工作才能开始。
|
||||
- **目标激活**:续行消费方接纳下一个 Goal Round 的进程本地权限。激活态为 `armed` 或 `disarmed`;它有意不参与持久回放,因此在恢复或 fork 后,只有随后通过 `/goal` 或模型工具执行一次经人类授权的恢复变更,自动工作才能开始。
|
||||
|
||||
## 人类命令
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/i18n/README.md
|
||||
README.md: 23400801426f77dae5136406cd747dbe4b06a4c5
|
||||
README.zh.md: c722d10e0aab5f64de2a172ffb21760bceba502b
|
||||
README.zh.md: 445037ac3afba081a0235c40663286d2c35b9a92
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
- **两种语言同权。** 一篇文档可以先用任一语言撰写和评审(先写中文的 Agent Note 与先写英文的一样正当),另一侧由它翻译而来。两个文件谁也不高于谁;约束它们的是二者必须说同样的话。
|
||||
- **一对文档是三个同目录文件。** 英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`,都在同一目录。不用语言目录,不用独立翻译仓库,不用中英混排的单文件。配对必须整体合并:PR(Pull Request)永远不会只带一种语言而缺其余两个文件。
|
||||
- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 git blob hash:
|
||||
- **一致性记录。**`foo.i18n.yaml` 保存两侧文件在上一次被确认「说同样的话」时各自的完整 Git blob hash:
|
||||
|
||||
```yaml
|
||||
foo.md: 3f786850e387550fdab836ed7e6dc881de23001b
|
||||
foo.zh.md: 89e6c98d92887913cadf06b2adb97f26cde4849b
|
||||
```
|
||||
|
||||
用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。`--write` 会先把这些快照存入本地 Git 对象库再写下记录,未提交的 worktree 内容也不例外;它还会在内容寻址的 `refs/dsh/translation-pairing/snapshots/` ref 下固定每个不同的已存 blob,使垃圾回收无法让已记录的恢复指针失效。因此记录的 hash 能还原任一侧上次确认时的确切文本,所以失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译。日常工作会直接完成这份修补;用户显式调用扩展工作流时,可改由 `pnpm run gen-translation-brief <pair>` 以能安全对齐的最窄粒度汇集这次更新,并由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md))。两侧对齐后,`pnpm run verify-translation-pairing --write <pair>` 重新记录两个 hash;那份 yaml diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。
|
||||
用 blob hash 而不是 commit hash,这样同一个 PR 里改动的文件也能算出记录(`git hash-object foo.md`),一致性是纯内容比较。`--write` 会先把这些快照存入本地 Git 对象库再写下记录,未提交的 worktree 内容也不例外;它还会在内容寻址的 `refs/dsh/translation-pairing/snapshots/` ref 下固定每个不同的已存 blob,使垃圾回收无法让已记录的恢复指针失效。因此记录的 hash 能还原任一侧上次确认时的确切文本,所以失去同步的配对是「按被改一侧的 diff 最小化地修补另一侧」,从不整篇重译。日常工作会直接完成这份修补;用户显式调用扩展工作流时,可改由 `pnpm run gen-translation-brief <pair>` 以能安全对齐的最窄粒度汇集这次更新,并由 `--apply` 在结构校验后拼接仅涉及围栏代码块的改动([briefed-updates Agent Note](../../.agents/notes/implemented/process/2026-07-26-briefed-minimal-translation-updates.md))。两侧对齐后,`pnpm run verify-translation-pairing --write <pair>` 重新记录两个 hash;那份 YAML diff 就是「确认一致」这个动作本身,可以被评审,也正因如此,`--write` 要求点名你确认过的配对(`--write --all` 是显式的全语料形式)。
|
||||
|
||||
当两个分支都包含同一配对的有效确认时,已安装的 `dsh-translation-pairing` Git 合并驱动只会在 Git 默认文本合并能分别干净合并记录所指向的英文三方 blob 与中文三方 blob,且合并后的配对仍保留必需的语言切换行和结构签名时,组合出一份新记录。中文文件必须保留指向英文的反向链接;普通撰写的英文源必须保留指向中文的链接,而清单内的生成英文源不作此要求。任何合并驱动无法验证的结构都保留为普通冲突;`pnpm run resolve-translation-pairing-conflicts` 会对已经停止的合并执行同一套遇错即保留冲突的操作,暂存每份可安全生成的配对记录,并在还有其他配对冲突时以非零状态退出。[自动配对合并 Agent Note](../../.agents/notes/implemented/process/2026-08-08-automatic-translation-pairing-merges.md) 负责记录该机制与备选方案。
|
||||
- **语言切换行。** 中文文件一律在 H1 标题后立即以 `[English](foo.md) | 中文` 链回英文。普通撰写的英文文件在同一位置以 `English | [中文](foo.zh.md)` 互链;清单内的生成英文源省略此行,以便与生成器输出逐字节一致。
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
**范围**:根目录 CONTRIBUTING 文档、除 vendor 源码外的全部 README,以及 `.agents/notes/**`、`docs/**` 与 `python/**` 下的全部活跃文档。匹配 README 时只看文件名且不区分大小写,因此今后新增的目录无需再修改 manifest。依赖目录、被忽略的构建产物目录以及冻结的 `.agents/notes/archived/` 目录树只在发现阶段排除,不属于持续演进的翻译源文档。
|
||||
|
||||
有经评审中文对侧的生成英文参考文档和图文档遵循配对规则。生成器仍是英文真源,新鲜度门禁与配对门禁各自独立强制其约束;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。生成页的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。
|
||||
有经评审的中文对侧的生成英文参考文档和图文档遵循配对规则。生成器仍是英文真源,新鲜度门禁与配对门禁各自独立强制其约束;重新生成导致英文变化后,配对会保持失去同步状态,直至经评审的中文对侧完成更新并重新记录。生成的英文源文件不含普通撰写文档所带的语言切换行,因为添加该行会使生成器新鲜度检查失败;中文对侧仍链接回英文源。生成页的中文对侧只能改写若直译便不再符合经评审译文事实的自指生成与维护说明;所有技术内容仍受普通忠实性规则约束。
|
||||
|
||||
**排除**(永不配对,门禁拒绝为它们建 `.zh.md` 或 `.i18n.yaml`):
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
|
||||
persistence-catalog.md: 6db61cee82596b0d76a40a2aabd309ffffbb1c91
|
||||
persistence-catalog.zh.md: 05a8467a96948fb4568ab151b29250c5eeb47fb0
|
||||
persistence-catalog.zh.md: 15a209b73507599adaf15500fc33691ab26c3413
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
[English](persistence-catalog.md) | 中文
|
||||
|
||||
会话持久事件日志中可能出现的所有事件类型:完整持久化的 `SessionEvent` 信封,以及可通过合并扩展的 `SessionEventMap` 中的每个成员,包括 `@deepseek-ai/dsh-session` 所属的词汇和本仓库中每个插件对 `@deepseek-ai/dsh-session/types` 的声明合并,并附有源 JSDoc、完整 payload 声明、surface 标记和声明位置。本文档是 [session.md](subsystems/session.md)(surface 排序与 `deriveMessages()` 投影)、[persistence.md](subsystems/persistence.md)(如何让日志持久化)和 [session.md](subsystems/session.md#cordis-surface) 中生成区域(实时总线接线;日志事件**不是** cordis 事件,它通过唯一一次 `session/event` emit 到达监听器)的补充。
|
||||
会话持久事件日志中可能出现的所有事件类型:完整持久化的 `SessionEvent` 信封,以及可通过合并扩展的 `SessionEventMap` 中的每个成员,包括 `@deepseek-ai/dsh-session` 所属的词汇和本仓库中每个插件对 `@deepseek-ai/dsh-session/types` 的声明合并,并附有源 JSDoc、完整 payload 声明、surface 标记和声明位置。本文档是 [session.md](subsystems/session.md)(surface 排序与 `deriveMessages()` 投影)、[persistence.md](subsystems/persistence.md)(如何让日志持久化)和 [session.md](subsystems/session.md#cordis-surface) 中生成区域(实时总线接线;日志事件**不是** cordis 事件,它通过唯一的 `session/event` emit 到达监听器)的补充。
|
||||
|
||||
英文源文件根据源码生成(`scripts/gen-persistence-catalog.ts`),并由 `pnpm run verify-persistence-catalog`(`doc-sync`(文档同步门禁)的一部分)验证新鲜度;本中文文件作为经评审对侧通过双语配对维护。声明块保留源码声明和嵌套属性的 JSDoc,只移除其所在接口/模块带来的缩进,并使用 `ts persistence-catalog` 围栏(doc-typecheck 会跳过这些围栏,因为声明引用了其所属模块中的类型)。payload 中的类型名称会链接到记录该类型的页面。参见 [persistence-log-catalog Agent Note](../.agents/notes/archived/process/2026-07-04-persistence-log-catalog.md)。
|
||||
|
||||
@@ -92,7 +92,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}[T]
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:316`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:323`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:352`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:384`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:336`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:343`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:372`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:404`](../packages/core/session/src/types.ts)
|
||||
|
||||
## 事件
|
||||
|
||||
@@ -205,7 +205,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[StreamChunk](subsystems/llm-streaming.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:246`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:266`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `assistant/message` — surface
|
||||
|
||||
@@ -221,7 +221,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TokenUsage](subsystems/llm-streaming.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:253`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:273`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `command/*`
|
||||
|
||||
@@ -377,7 +377,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'feedback/record': { text: string }
|
||||
```
|
||||
|
||||
来源:[`packages/feedback/command-feedback/src/index.ts:25`](../packages/feedback/command-feedback/src/index.ts)
|
||||
来源:[`packages/feedback/command-feedback/src/index.ts:62`](../packages/feedback/command-feedback/src/index.ts)
|
||||
|
||||
### `goal/*`
|
||||
|
||||
@@ -501,7 +501,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'request/context': RequestContext
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:289`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:309`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `request/header` — log-only
|
||||
|
||||
@@ -513,7 +513,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:284`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:304`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `sandbox/*`
|
||||
|
||||
@@ -582,7 +582,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'session/end-seed': Record<string, never>
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:312`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:332`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `session/title` — log-only
|
||||
|
||||
@@ -618,7 +618,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'step/end': { turn: number; step: number }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:236`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:256`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `step/start` — log-only
|
||||
|
||||
@@ -627,7 +627,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'step/start': { turn: number; step: number }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:234`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:254`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `subagent/*`
|
||||
|
||||
@@ -657,7 +657,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TodoItem](subsystems/session.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:279`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:299`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `tool/*`
|
||||
|
||||
@@ -674,7 +674,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[CallId](subsystems/core.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:259`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:279`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `tool/code-dispatch` — log-only
|
||||
|
||||
@@ -743,7 +743,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:271`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:291`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `tool-workflow/*`
|
||||
|
||||
@@ -813,7 +813,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
|
||||
类型:[TurnEndReason](subsystems/session.md)
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:232`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:252`](../packages/core/session/src/types.ts)
|
||||
|
||||
#### `turn/start` — log-only
|
||||
|
||||
@@ -827,7 +827,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'turn/start': { turn: number }
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:223`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:243`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `user/*`
|
||||
|
||||
@@ -844,7 +844,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
|
||||
'user/message': UserMessage
|
||||
```
|
||||
|
||||
来源:[`packages/core/session/src/types.ts:244`](../packages/core/session/src/types.ts)
|
||||
来源:[`packages/core/session/src/types.ts:264`](../packages/core/session/src/types.ts)
|
||||
|
||||
### `web/*`
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/postmortem/0001-acp-default-export-drops-inject.md
|
||||
0001-acp-default-export-drops-inject.md: f8474bde0b81b24573f813d9a0fb017962751f49
|
||||
0001-acp-default-export-drops-inject.zh.md: 1e64f123d1dd0b5e7e81d3a8c4a5e6f77e6411ff
|
||||
0001-acp-default-export-drops-inject.zh.md: 9e0c4b289fca5798489434b55cc1482085512832
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# 事故复盘(postmortem) 0001:ACP(Agent Client Protocol)服务器在连接时崩溃——`export default` 丢弃了插件的 `inject`
|
||||
# 事故复盘(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`)
|
||||
状态:已解决;修复见 PR(Pull Request)#41 `feat/acp-2-bridge`
|
||||
|
||||
## 摘要
|
||||
|
||||
两个集成错误在单元测试全覆盖的情况下仍然导致 ACP 崩溃:一个默认导出使 Loader 丢弃了 `inject`,一个经可追踪代理的可选服务查找在 shadow 边界上失败。手动挂载的测试绕过了这两条路径。修复方案增加了无需 API key 的真实 Loader 覆盖率,并为插件导出和可选服务访问制定了包级规则。
|
||||
两个集成错误在单元测试全覆盖的情况下仍然导致 ACP 崩溃:一个默认导出使 Loader 丢弃了 `inject`,一个经可追踪代理的可选服务查找在 shadow 边界上失败。手动挂载的测试绕过了这两条路径。修复方案增加了无需 API key 的真实 Loader 测试覆盖,并为插件导出和可选服务访问制定了包级规则。
|
||||
|
||||
## 概述
|
||||
|
||||
@@ -20,7 +20,7 @@ ACP 服务器无法创建或加载任何一个会话——而这正是编辑器
|
||||
|
||||
- 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 遍历做了插桩,并运行了真实子进程。跟踪结果显示,异常在 `apply()` 第 179 行、*插件加载时*抛出,位于 ROOT fiber 且没有 shadow——推翻了 shadow 理论对 `session/new` 的解释。
|
||||
- 调查最初沿着一个 Cordis「traceable/shadow」理论展开(看似合理,且该机制确实存在——见 Bug #2),随后在 vendor 目录中的 `reflect.ts` 里对实际 fiber 遍历做了插桩,并运行了真实子进程。跟踪结果显示,异常在 `apply()` 第 179 行、*插件加载时*抛出,位于 ROOT fiber 且没有 shadow——推翻了 shadow 理论对 `session/new` 的解释。
|
||||
- 找到根因 #1:一行多余的 `export default apply`。删除后 `session/new` 修复。
|
||||
- 删除后暴露了 Bug #2:`session/load` 仍然在 `sessionPersistence` 上抛错——这是一个真正不同的机制(shadow 遍历),通过隔离修复并重新运行真实子进程得到确认。
|
||||
|
||||
@@ -47,11 +47,11 @@ unwrapExports(exports: any) {
|
||||
}
|
||||
```
|
||||
|
||||
存在默认导出时,`exports.default ?? exports` 解析为**裸 `apply` 函数**。裸函数没有 `inject`、没有 `name`、没有 `Config` 属性——这些作为*兄弟*命名导出存在于模块命名空间上,而 unwrap 到 `.default` 把整个命名空间丢弃了。Loader 随后基于空的 `inject` 构建了插件的 fiber。
|
||||
存在默认导出时,`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 中运行。
|
||||
**修复:**删除 `export default apply`。Loader 随后使用模块命名空间,正确识别 `inject`/`name`/`Config`,`apply` 在一个确实注入了所声明服务的 fiber 中运行。
|
||||
|
||||
## 根因 #2——可选服务读取通过可追踪 shadow 触发 inject 守卫(导致 `session/load` 崩溃)
|
||||
|
||||
@@ -84,14 +84,14 @@ if (!ctx.fiber.runtime) return ctx.reflect.get(prop, false) // ← direct glob
|
||||
|
||||
`ctx.reflect.get(name, false)` 是基于 isolate symbol 的全局服务 store 直接查找——完全忽略 fiber 拓扑,能找到服务。因此从顶层测试读取可以成功;而从真实插件 fiber 内部、经由 shadow 到达时则抛错。bridge 恰好是后者。
|
||||
|
||||
**修复:** 使用 `ctx.get('sessionPersistence')` 读取可选服务,该方法使用全局 isolate-keyed store 同时保留活跃状态检查。对于插件声明注入集中的服务,直接属性读取仍然适用。
|
||||
**修复:**使用 `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 的祖先遍历失败。
|
||||
- 同一个 harness 将所有内容平铺挂载在一个根上下文上,因此从中触达的 `AgentLoop` 恢复要么运行在顶层(`!runtime` 绕过),要么经由 shadow 运行,而该 shadow 的 origin 仍然解析到 root——掩盖了 Bug #2 的祖先遍历失败。
|
||||
- 唯一的无 key e2e 发送 `initialize` 并检查 stdout 纯净性。`initialize` 从不触达 factory,因此两个 bug 都安然通过。
|
||||
- 唯一驱动 `session/new`/`session/load` 的测试需要 key 才能运行,因此 CI(无 key)跳过了它——而本地它之所以「通过」,只是因为一个陈旧的已构建 `lib/`(包含旧代码)恰好满足了模块解析。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/postmortem/0002-js-expression-disabled-filesystem-tools.md
|
||||
0002-js-expression-disabled-filesystem-tools.md: 93c62261f54e273ea5dbb2ca1a236daf9b2a6f15
|
||||
0002-js-expression-disabled-filesystem-tools.zh.md: 7a26f8456c13ad22535cd04fdec061dccd2ed85d
|
||||
0002-js-expression-disabled-filesystem-tools.zh.md: c0c1e1e18d5024ccaf09e199150f4319aa2b8ace
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](0002-js-expression-disabled-filesystem-tools.md) | 中文
|
||||
|
||||
Status: resolved
|
||||
状态:已解决
|
||||
|
||||
## 摘要
|
||||
|
||||
@@ -22,7 +22,7 @@ Cordis Include 将每个 `!!js` 标量解析为一个表达式对象。Loader
|
||||
|
||||
## 时间线
|
||||
|
||||
- PR(Pull Request) #261 整合了 ACP 组合并刷新了文件系统快照,同时引入了条件式文件系统入口。
|
||||
- PR(Pull Request) #261 整合了 ACP 组合并刷新了文件系统快照,同时引入了条件式文件系统配置项。
|
||||
- 所有单元测试、覆盖率、快照、文档、构建和 hygiene 检查均通过。
|
||||
- 对刷新后的文件系统预期输出的评审发现了通用的失败卡片和结构化的 `UNKNOWN_TOOL` 结果。
|
||||
- 一次真实的 Loader 启动确认:每个 `disabled` 值仍为表达式对象,每个文件系统 fiber 均未创建。
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/postmortem/0003-web-agent-gui-feedback-loop.md
|
||||
0003-web-agent-gui-feedback-loop.md: 0d8c07d9aca3305ea6bc85134e8578ae1bd8f387
|
||||
0003-web-agent-gui-feedback-loop.zh.md: 07faa3a302d1b124efcbdc57446995aefeb8bd87
|
||||
0003-web-agent-gui-feedback-loop.zh.md: c3e67179eefe48265e14fc47ffa57f75d3cf423c
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
[English](0003-web-agent-gui-feedback-loop.md) | 中文
|
||||
|
||||
Status: resolved
|
||||
状态:已解决
|
||||
|
||||
## 摘要
|
||||
|
||||
Web agent 修改了 GUI 源码,却不知道由哪个 URL 和进程承载当前会话。它把验收交还给用户,随后在 `window.__DSH_BOOT__` 缺失导致白屏的情况下,仍把裸 Vite 返回的 HTTP 200 当作成功;最后,原页面其实已经加载了重建产物,它却去验收另一个端口上的替代 `dsh web` 服务器。修复让当前 URL 和运行模式对模型可见且可由 shell 查询,在独立 Vite 开始监听前拒绝启动,并依据外部状态验收生产模式刷新与开发模式 HMR(热模块替换)。
|
||||
Web agent 修改了 GUI 源码,却不知道当前会话对应哪个 URL、由哪个进程承载。它把验收交还给用户,随后在 `window.__DSH_BOOT__` 缺失导致白屏的情况下,仍把裸 Vite 返回的 HTTP 200 当作成功;最后,原页面其实已经加载了重建产物,它却去验收另一个端口上的替代 `dsh web` 服务器。修复让当前 URL 和运行模式对模型可见且可由 shell 查询,在独立 Vite 开始监听前拒绝启动,并依据外部状态验收生产模式刷新与开发模式 HMR(热模块替换)。
|
||||
|
||||
## 概述
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/README.md
|
||||
README.md: b4049888106aeedbf6c94b937199d59dee964dd1
|
||||
README.zh.md: 09ce59e71d7be53f33553d93faef201357049422
|
||||
README.zh.md: c052a187c92bf6c211b9628d57d15ac2015e5f8f
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
| 页面 | 负责内容 |
|
||||
|---|---|
|
||||
| [core.md](core.md) | `packages/core` 如何控制 agent loop:逐包的循环说明、agent 创建与所有权(`AgentHandle`)、`Agent` 句柄的投递/取消/拦截约定,以及全仓通用类型模式(`…Map → 派生联合`、品牌化 id) |
|
||||
| [core.md](core.md) | `packages/core` 如何控制 agent loop(智能体循环):逐包的循环说明、agent 创建与所有权(`AgentHandle`)、`Agent` 句柄的投递/取消/拦截约定,以及全仓通用类型模式(`…Map → 派生联合`、品牌化 id) |
|
||||
| [llm-streaming.md](llm-streaming.md) | `packages/llm` 的对话类型——`Message`/`ContentBlock`、组装完成的模型请求、`StreamChunk` wire protocol 和适配器约定(adapter contract)、`BlockAssembler`,以及 `LlmAdapter` 提供方约定 |
|
||||
| [token-meter.md](token-meter.md) | 不可变的标量与位置回放度量,附带已消费日志修订号 |
|
||||
| [scope.md](scope.md) | 作用域注册标识、dispatch 载体,以及拥有的 `Scope` 上下文 |
|
||||
@@ -17,7 +17,7 @@
|
||||
| [session.md](session.md) | 完整的 `SessionEventMap` 变体目录、`TurnTrigger`/`TurnEndReason`、`deriveMessages()`、执行封闭与独立事件 |
|
||||
| [persistence.md](persistence.md) | 持久性 seam:`SessionPersistence`、JSONL + SQLite 后端、`session/flush`、崩溃恢复、`SessionHeader` |
|
||||
| [settings.md](settings.md) | 用户设置 seam:`SettingsNamespace` 注册、分层解析(默认值 → 组合 `base` → 用户文档)、owner scope、热提交 |
|
||||
| [credentials.md](credentials.md) | 凭据 seam:配置中的 `CredentialRef` 引用(绝不含值)、按操作解析、对 UI 安全的 `CredentialInfo`、provider 来源层 |
|
||||
| [credentials.md](credentials.md) | 凭据 seam:配置中的 `CredentialRef` 引用(绝不含值)、按操作解析、对 UI 安全的 `CredentialInfo`、提供方来源层 |
|
||||
| [session-query.md](session-query.md) | 逻辑记录、有界精确事件读取、关系追踪、语义筛选器/文档与全文检索结果页 |
|
||||
| [feedback.md](feedback.md) | 绑定生命周期的逐消息反馈记录、乐观版本、伴随记录持久化与 Host Remote 契约 |
|
||||
| [session-title.md](session-title.md) | 持久标题快照、被引用的来源消息 seq 与异步提供方约定 |
|
||||
@@ -40,11 +40,11 @@
|
||||
| [web.md](web.md) | Web 访问 seam:`WebSearchRequest`/`Result`、`WebFetchRequest`/`Result`、`WebFetchBody`、提供方可用性、`WebError` |
|
||||
| [spill.md](spill.md) | spill 存储 seam:`SaveTextSpill`、`SpillOwner`/`SpillSource`、`SpillRef`、品牌类型 `SpillLocator` |
|
||||
| [workflow.md](workflow.md) | 工作流 seam:`WorkflowStartRequest`、`WorkflowMeta`、`WorkflowRun`/`Result`、`workflow/*` 事件载荷、`WorkflowError` 致命性 |
|
||||
| [tasks.md](tasks.md) | 后台任务运行时:品牌化 `TaskId`、producer 约定、consumer 视图和 `ctx.tasks` 服务行为 |
|
||||
| [tasks.md](tasks.md) | 后台任务运行时:品牌化 `TaskId`、producer 约定、消费方视图和 `ctx.tasks` 服务行为 |
|
||||
| [permission.md](permission.md) | 权限预设层:`PresetSpec`/`PresetOption`、派生的 `custom` 状态、仅记日志的 `permission/preset` 事件 |
|
||||
| [plan.md](plan.md) | 计划模式:仅记日志的 `plan/mode` 状态、待定选择的冲刷、`PlanModeConfig`、`exit_plan_mode` 审阅流程 |
|
||||
| [invariants.md](invariants.md) | 运行时不变式注册表:选择配置 `Config`、`InvariantInstaller`/`InvariantFailure`、空配套插件约定 |
|
||||
| [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、可认领的回退席位、index 转换 |
|
||||
| [http-server.md](http-server.md) | HTTP 载体:`WebRouteKind`/`WebRoute`、匹配顺序、可认领的回退席位、index 渲染挂接点 |
|
||||
| [storage.md](storage.md) | 存储子系统:后端约定(`StorageBackend`)、`StorageForms`、`DomainSpec`/`Domain`、`domain/changed` |
|
||||
| [workspace.md](workspace.md) | 工作区注册表:`Workspace`/`WorkspaceId`、注册与解析、与会话 `cwd` 的关系 |
|
||||
| [client-modules.md](client-modules.md) | Web 插件表:`dsh.client` 声明、`WebBootGraph` 线上组合、bundle 路由与 index 转换 |
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/approval.md
|
||||
approval.md: 6ade53d7755f1070a17513d9f3efd4252d6d255f
|
||||
approval.zh.md: 3e2ab1d5c56fbf3eeb0ae12ec3baa1e3cb3d2a71
|
||||
approval.zh.md: 482c37cd3b92e23843048f04390178a2061990ec
|
||||
|
||||
@@ -50,7 +50,7 @@ type ApprovalPolicy = 'ask' | 'never'
|
||||
|
||||
## 审批请求
|
||||
|
||||
`ApprovalRequest` 以足够精确的方式标识 agent 和工具操作,以便路由和审计该问题。它有意省略工具参数:应答者通过 `callId` 将提示附加到已流式输出的工具调用上,而非渲染一份可能漂移的副本。
|
||||
`ApprovalRequest` 以足够精确的方式标识 agent 和工具操作,以便路由和审计该问题。它有意省略工具参数:应答者通过 `callId` 将提示附加到已流式输出的工具调用上,而非渲染另一份可能漂移的副本。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/bash.md
|
||||
bash.md: 1f85dc1a0e570b403c5f20c398520eda9ea845e0
|
||||
bash.zh.md: 13d382299cc46e00975619dcf3042bea32420222
|
||||
bash.zh.md: 4b73908a1d8f6e0e34b393782650711ed04c1c58
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
[English](bash.md) | 中文
|
||||
|
||||
bash 执行 seam 分为 Service Definition([dsh-bash](../../packages/bash/bash),`ctx.bash`)、Service provider([dsh-bash-local](../../packages/bash/bash-local) 与 [dsh-bash-sandbox](../../packages/bash/bash-sandbox))和 Consumer([dsh-tool-bash](../../packages/bash/tool-bash),即 `bash` schema)。通用后台任务的 task id、所有权与控制位于 [tasks.md](tasks.md);本 seam 返回一个不含任务概念的进程句柄。原始进程组机制位于[进程管理器 seam](subprocess.md)之后。
|
||||
bash 执行 seam 分为 Service Definition([dsh-bash](../../packages/bash/bash),`ctx.bash`)、Service provider([dsh-bash-local](../../packages/bash/bash-local) 与 [dsh-bash-sandbox](../../packages/bash/bash-sandbox))和 Consumer([dsh-tool-bash](../../packages/bash/tool-bash),即 `bash` schema)。通用后台任务的 task id、所有权与控制位于 [tasks.md](tasks.md);本 seam 返回一个不含任务概念的进程句柄。原始进程组机制封装在[子进程 seam](subprocess.md)之后。
|
||||
|
||||
源码:[`packages/bash/bash/src/types.ts`](../../packages/bash/bash/src/types.ts)
|
||||
|
||||
## 受管 shell 环境命名空间
|
||||
|
||||
`DSH_*` 变量是归 Harness 所有的子进程事实。面向模型的 bash 工具通过 `ctx.bashEnv` 收集它们,再经由 `BashExecRequest.dshEnv` 传递;进程管理器在合并当前快照之前会移除继承而来的 `DSH_*` 名称。`DshEnvironmentKey`/`DshEnvironment` 词汇归[进程管理器 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
|
||||
`DSH_*` 变量是归 Harness 所有的子进程事实。面向模型的 bash 工具通过 `ctx.bashEnv` 收集它们,再经由 `BashExecRequest.dshEnv` 传递;子进程服务在合并当前快照之前会移除继承而来的 `DSH_*` 名称。`DshEnvironmentKey`/`DshEnvironment` 词汇归[子进程 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
|
||||
|
||||
## 请求与规格:`resolve()` 拆分
|
||||
|
||||
@@ -100,7 +100,7 @@ interface BashExecSpec {
|
||||
|
||||
`stdin` 和 `env` 是受信任的进程内插件输入,不由 `dsh-tool-bash` 暴露。本地执行器会先清除环境中的凭据,再合并调用方显式提供的 env。见 [bash-stdin-env Agent Note](../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.md)。
|
||||
|
||||
`stdoutMaxBytes` 同样仅供受信任插件使用。它让前台消费方能在有界解析预算内请求完整 stdout,而不会改变 stderr、后台任务或面向模型的 bash 工具的常规输出上限。
|
||||
`stdoutMaxBytes` 同样仅供受信任插件使用。它让前台 Consumer 能在有界解析预算内请求完整 stdout,而不会改变 stderr、后台任务或面向模型的 bash 工具的常规输出上限。
|
||||
|
||||
## 前台运行:`BashRunResult`
|
||||
|
||||
@@ -136,7 +136,7 @@ interface BashRunResult {
|
||||
}
|
||||
```
|
||||
|
||||
每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息;截断时,`text` 是**尾部**,完整流溢出到一个私有文件。这些字段归[进程管理器 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
|
||||
每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息;截断时,`text` 是**尾部**,完整流溢出到一个私有文件。这些字段归[子进程 seam](subprocess.md)所有,由 `dsh-bash` 重导出。
|
||||
|
||||
## 文件沙箱:`BashSandboxInfo`
|
||||
|
||||
@@ -218,7 +218,7 @@ interface BashProcessRead {
|
||||
|
||||
## 服务
|
||||
|
||||
`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[进程管理器](subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。`dsh-bash` 拥有 shell 工具共享的退出状态约定:导出的 `parseExitStatus`/`ParsedExitStatus` 是 `dsh-tool-bash` 的 `renderResult` 与 `dsh-tool-pwsh` 的 `renderPwshResult` 所追加的 `[exit code: N]` / `[killed by signal: X]` 标记的逆解析,两个工具的 `presentResult` 都用它把渲染文本拆分为 terminal 卡的输出正文与退出状态 pill。
|
||||
`BashExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[子进程服务](subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](tasks.md)。`dsh-bash` 拥有 shell 工具共享的退出状态约定:导出的 `parseExitStatus`/`ParsedExitStatus` 是 `dsh-tool-bash` 的 `renderResult` 与 `dsh-tool-pwsh` 的 `renderPwshResult` 所追加的 `[exit code: N]` / `[killed by signal: X]` 标记的逆解析,两个工具的 `presentResult` 都用它把渲染文本拆分为 terminal 卡的输出正文与退出状态 pill。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/client-modules.md
|
||||
client-modules.md: bd0ad83f6ebbbab3be8c91e8d51b1c3492e41e6c
|
||||
client-modules.zh.md: aea573ee99be2781427d6843b12936406d563283
|
||||
client-modules.zh.md: ac77a87bd4c06c995ace77ab25d3f94d15eacc53
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
[English](client-modules.md) | 中文
|
||||
|
||||
Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModuleHost`(`ClientModuleHostService`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包(package),组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins/<id>/client.js` 提供各个 bundle,并经 index 转换(index tap)注入启动 manifest(元数据清单)——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[http-server.md](http-server.md) 所述的载体提供本服务注册的前缀路由与 index 转换。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。
|
||||
Web 插件表:[dsh-client-modules](../../packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModuleHost`(`ClientModuleHostService`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包,组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins/<id>/client.js` 提供各个 bundle,并经 index 转换(index tap)注入启动 manifest(元数据清单)——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](../../packages/host/webserver) 的消费方:[http-server.md](http-server.md) 所述的载体提供本服务注册的前缀路由与 index 转换。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](../../packages/client/modules/README.md)中,不在本页。
|
||||
|
||||
源码:[`packages/client/modules/src/client/manifest.ts`](../../packages/client/modules/src/client/manifest.ts)
|
||||
|
||||
## wire
|
||||
|
||||
图是 Node 半与浏览器半之间协议层的唯一事实来源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图作为 `<head>` 中的第一个脚本注入(`window.__DSH_BOOT__`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。
|
||||
图是 Node 半与浏览器半之间协议层的真源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图作为 `<head>` 中的第一个脚本注入(`window.__DSH_BOOT__`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/code-runtime.md
|
||||
code-runtime.md: ef5f2b9b1f39c5e7218cc1f5d9ae6435ef725e3a
|
||||
code-runtime.zh.md: 3a136c4160eb30a38968ea4d482201d22ef62fde
|
||||
code-runtime.zh.md: fc9d262d0c9ef93ca19a14742f535c2b4b38d67e
|
||||
|
||||
@@ -61,7 +61,7 @@ interface CodeRunResult {
|
||||
|
||||
## 绑定:宿主函数作为程序全局变量
|
||||
|
||||
每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode 消费方传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道消费方的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
|
||||
每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ type CodeBindingFunction = (args: unknown) => Promise<CodeJsonValue>
|
||||
|
||||
## 捕获的输出与失败分类体系
|
||||
|
||||
日志是按发出顺序排列的纯字符串。运行时捕获程序的 console 与流输出,但通道和 console 方法的元数据不属于 seam,因为消费方只渲染文本。实现会对序列化后的外层日志数组,以及完成值或失败消息的组合载荷设置上限;固定的结果封装语法与消费方展示空白不计入这份可变载荷计量。超限会显式失败,而不会在值中插入替代内容。
|
||||
日志是按发出顺序排列的纯字符串。运行时捕获程序的 console 与流输出,但通道和 console 方法的元数据不属于 seam,因为 Consumer 只渲染文本。实现会对序列化后的外层日志数组,以及完成值或失败消息的组合载荷设置上限;固定的结果封装语法与 Consumer 展示空白不计入这份可变载荷计量。超限会显式失败,而不会在值中插入替代内容。
|
||||
|
||||
失败类型是**正交的结果,独立报告**(见 [defensive-patterns](../defensive-patterns.md)):预算耗尽不是异常,中止不是超时,基底崩溃(如 OOM)也不是二者中的任何一个:
|
||||
|
||||
@@ -158,7 +158,7 @@ interface CodeRunFailure {
|
||||
|
||||
## 服务
|
||||
|
||||
`CodeRuntime`(`ctx.codeRuntime`,抽象服务,定义于 [`packages/code-runtime/code-runtime/src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts))由 `run(request)` 加两个只读描述符组成:`language`(程序必须使用的语言,已知值为 `'typescript'` 与 `'python'`,即 `dsh-tools` 能呈现的那些,其中只有 `'typescript'` 有已发布的后端;生成语言相关展示的消费方据此切换,遇到无法展示的语言时应显式报错)和 `isolation`(执行基底,`'worker-thread'`、`'process'`、`'container'`;仅为诊断标签,**不构成安全承诺**)。实现必须保证各次运行彼此隔离(无跨运行状态),并在 dispose(资源释放)时等待系统完全停稳:teardown 要等到所有进行中的运行均已终止并结算后才完成。
|
||||
`CodeRuntime`(`ctx.codeRuntime`,抽象服务,定义于 [`packages/code-runtime/code-runtime/src/index.ts`](../../packages/code-runtime/code-runtime/src/index.ts))由 `run(request)` 加两个只读描述符组成:`language`(程序必须使用的语言,已知值为 `'typescript'` 与 `'python'`,即 `dsh-tools` 能呈现的那些,其中只有 `'typescript'` 有已发布的后端;生成语言相关展示的 Consumer 据此切换,遇到无法展示的语言时应显式报错)和 `isolation`(执行基底,`'worker-thread'`、`'process'`、`'container'`;仅为诊断标签,**不构成安全承诺**)。实现必须保证各次运行彼此隔离(无跨运行状态),并在 dispose(资源释放)时等待系统完全停稳:teardown 要等到所有进行中的运行均已终止并结算后才完成。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/compaction.md
|
||||
compaction.md: 9286a8be3f9030ac383701c6390c6c3582ec83a2
|
||||
compaction.zh.md: b4b353fef903b5e1a9cd29cee3147004b657c3bb
|
||||
compaction.zh.md: dd05138c025597b919765c85443f5ed8e7a74c6c
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
|
||||
| 事件 | 载荷 | 作用 |
|
||||
|---|---|---|
|
||||
| `compact/start` | `{ turn }` | 获取日志记录的锁;数字标识打开的自动轮次,`null` 标识独立手动尝试 |
|
||||
| `compact/summary` | `{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }` | 安全摘要投影、可选的完整 provider 输出与 usage、生成结果时恰好通过此上下文的 `ctx.llm.stream()` 发起一次调用所带的 `llmStreamCall: true` 标记(此时必须提供完整的 `rawOutput`)、被遮蔽的 surface 边界对(`start`/`end` seq——位置跨度,而非数值区间)、按 surface 顺序排列的被遮蔽 seq、估算 token 数,以及摘要调用的 envelope(`provider`、`model`,若有生成上限则还包括该上限)——写入日志后,该一次性请求可由日志 + 代码重建(见可重建性 Agent Note);未带标记的 `rawOutput` 并不能判定调用路径 |
|
||||
| `compact/start` | `{ turn }` | 获取日志记录的锁;数字标识尚未结束的自动轮次,`null` 标识独立手动尝试 |
|
||||
| `compact/summary` | `{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }` | 安全摘要投影、可选的完整提供方输出与 usage、生成结果时恰好通过此上下文的 `ctx.llm.stream()` 发起一次调用所带的 `llmStreamCall: true` 标记(此时必须提供完整的 `rawOutput`)、被遮蔽的 surface 边界对(`start`/`end` seq——位置跨度,而非数值区间)、按 surface 顺序排列的被遮蔽 seq、估算 token 数,以及摘要调用的 envelope(`provider`、`model`,若有生成上限则还包括该上限)——写入日志后,该一次性请求可由日志 + 代码重建(见可重建性 Agent Note);未带标记的 `rawOutput` 并不能判定调用路径 |
|
||||
| `compact/end` | `{ turn, error? }` | 使用相同的数字或 `null` 归属值释放锁(`error` 记录失败尝试) |
|
||||
|
||||
锁括住**整个**操作:先追加 `compact/start`,然后执行摘要生成、写入 `compact/summary` 记录与 `user/message` 替换,最后才追加 `compact/end`。最后释放锁意味着操作中途崩溃会表现为可检测的遗留锁(有 `compact/start` 而无匹配的 `compact/end`),而非一个虚假声称压缩已完成的 `compact/end`。
|
||||
@@ -66,7 +66,7 @@ interface CompactionResult {
|
||||
type CompactionTrigger = 'pressure' | 'context-overflow'
|
||||
```
|
||||
|
||||
`CompactService` 暴露 `compactIfNeeded(agent, trigger, signal)` 以执行自动 `pressure` 或 `context-overflow` 策略,暴露 `compactNow(agent, signal)` 以便即使未达到压力也对空闲会话进行一次有效缩减,还针对显式、两端均包含的 surface 范围暴露 `compactRegion(...)`。`compactNow()` 作为轮次之间的 agent maintenance 运行;没有有效范围时返回 `null` 且不写入;在摘要前记录独立的 `turn: null` 标记对,并在后续排队提示词能够从新表层派生前 flush 已闭合尝试。每个后端都使用 `compactCheckpointSource(compactionId, sourceCommandId?)` 创建替换用 `user/message` 的源;client 与 wire 消费方从无 cordis 的 `@deepseek-ai/dsh-compact/checkpoint` 子路径导入该构造函数、`CompactCheckpointSource` 和 `isCompactCheckpointSource()`,包根则为 host 消费方重新导出它们。必填的事务身份会关联替换检查点,而该判定函数使检查点识别不依赖任一特定后端。实现必须把传入的 signal 转发给摘要流程。该 seam 不拥有计价 API:单例 [`ctx.tokenMeter`](token-meter.md) 直接拥有估算与回放,而 `dsh-compact-basic` 拥有保留策略、事件排序、按路由执行的摘要调用及其配置。
|
||||
`CompactService` 暴露 `compactIfNeeded(agent, trigger, signal)` 以执行自动 `pressure` 或 `context-overflow` 策略,暴露 `compactNow(agent, signal)` 以便即使未达到压力也对空闲会话进行一次有效缩减,还针对显式、两端均包含的 surface 范围暴露 `compactRegion(...)`。`compactNow()` 作为轮次之间的 agent maintenance 运行;没有有效范围时返回 `null` 且不写入;在摘要前记录独立的 `turn: null` 标记对,并在后续排队提示词能够从新表层派生前 flush 已闭合尝试。每个后端都使用 `compactCheckpointSource(compactionId, sourceCommandId?)` 创建替换用 `user/message` 的源;client 与 wire 消费方从无 Cordis 的 `@deepseek-ai/dsh-compact/checkpoint` 子路径导入该构造函数、`CompactCheckpointSource` 和 `isCompactCheckpointSource()`,包根则为 host 消费方重新导出它们。必填的事务身份会关联替换检查点,而该判定函数使检查点识别不依赖任一特定后端。实现必须把传入的 signal 转发给摘要流程。该 seam 不拥有计价 API:单例 [`ctx.tokenMeter`](token-meter.md) 直接拥有估算与回放,而 `dsh-compact-basic` 拥有保留策略、事件排序、按路由执行的摘要调用及其配置。
|
||||
|
||||
预期的手动失败使用 `ManualCompactionErrorCode`:
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/core.md
|
||||
core.md: e52a7619085b2956496be6234f711441902fc259
|
||||
core.zh.md: e9cfe19129e33a1c17e87561397b13139a6d7537
|
||||
core.zh.md: fab8a79045272aed9f00b0f87b5c151dbd6021e8
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
[English](core.md) | 中文
|
||||
|
||||
**核心**子系统即 [`packages/core`](../../packages/core/README.md),包含每个组合都会启动的包:事件溯源的会话日志、系统提示词组装、工具注册表、agent 类型,以及驱动它们的具体循环。本页说明 `agent`/`agent-loop` 这对包所声明的内容:agent 如何被创建与拥有,以及 `Agent` 句柄的投递、取消与拦截约定;本页还说明每个子系统都遵循的两个类型模式。该组的专属页面与目录其余部分见[子系统 README](README.md)。
|
||||
**核心**子系统即 [`packages/core`](../../packages/core/README.md),包含每个组合都会启动的包:事件溯源的会话日志、系统提示词组装、工具注册表、agent(智能体)类型,以及驱动它们的具体循环。本页说明 `agent`/`agent-loop` 这对包所声明的内容:agent 如何被创建与拥有,以及 `Agent` 句柄的投递、取消与拦截约定;本页还说明每个子系统都遵循的两个类型模式。该组的专属页面与目录其余部分见[子系统 README](README.md)。
|
||||
|
||||
## 主干逐包速览
|
||||
|
||||
一个轮次按同一条循环流经六个包:[`agent-loop`](../../packages/core/agent-loop) 中的 driver 认领一条排队的 prompt,在[会话日志](session.md)(`ctx.sessions`)上开启轮次,通过 [system-prompt](system-prompt.md)(`ctx.systemPrompt`)组装请求前缀并从日志派生历史,经 [LLM seam](llm-streaming.md) 流式获取模型响应,经[工具注册表](tools.md)(`ctx.tools`)分发工具调用,并把每个模型可见的事实追加回日志,供下一步派生。循环搬运的对话词汇——`Message`、`ContentBlock`、`StreamChunk`、模型请求——由 [`packages/llm`](../../packages/llm/README.md) 声明,记录在 [llm-streaming.md](llm-streaming.md)。
|
||||
一个轮次按同一条循环流经六个包:[`agent-loop`](../../packages/core/agent-loop) 中的 driver 认领一条排队的提示词,在[会话日志](session.md)(`ctx.sessions`)上开启轮次,通过 [system-prompt](system-prompt.md)(`ctx.systemPrompt`)组装请求前缀并从日志派生历史,经 [LLM(大语言模型) seam](llm-streaming.md) 流式获取模型响应,经[工具注册表](tools.md)(`ctx.tools`)分发工具调用,并把每个模型可见的事实追加回日志,供下一步派生。循环搬运的对话词汇——`Message`、`ContentBlock`、`StreamChunk`、模型请求——由 [`packages/llm`](../../packages/llm/README.md) 声明,记录在 [llm-streaming.md](llm-streaming.md)。
|
||||
|
||||
| 包 | 负责内容 | 页面 |
|
||||
|---|---|---|
|
||||
| `session/` | 只追加的 `SessionEvent` 日志与内存 store——唯一事实来源(`ctx.sessions`) | [session.md](session.md) |
|
||||
| `session/` | 仅追加的 `SessionEvent` 日志与内存 store——唯一真源(`ctx.sessions`) | [session.md](session.md) |
|
||||
| `system-prompt/` | 提示词段落与工具 schema 组装(`ctx.systemPrompt`) | [system-prompt.md](system-prompt.md) |
|
||||
| `tools/` | 带作用域的工具注册表与受保护的执行流水线(`ctx.tools`) | [tools.md](tools.md) |
|
||||
| `agent/` | `Agent` 接口、实时注册表、发起者作用域与 `agent/*` 事件词汇(`ctx.agents`) | 本页 |
|
||||
@@ -48,7 +48,7 @@ interface AgentHandle {
|
||||
}
|
||||
```
|
||||
|
||||
`CreateAgentOptions` 携带共享标识以及新 agent 发布前所需的一切:会话元数据(`meta`——已校验的 `cwd`、fork 谱系、seed 边界、来源分类、委派深度)、fork 用的可选 `seed` 回放前缀、按 agent 的 `AgentOptions`、仅创建期有效的取消 `signal`,以及 `setup`。`ResumeAgentOptions` 是持久标识的对应物:`resumeSessionId`、`agentOptions`、`signal` 与 `setup`。`setup` 回调(`AgentSetup`)在两个 id 都尚未发布时组装 agent 的作用域世界——凡经 `agentCtx` 注册的内容都先于 `agent/created` 与第一次提示词组装存在——并可返回一个在发布前一刻调用的同步 commit;setup 拒绝、commit 抛出或所有者 dispose 都会回滚事务,两个 id 均不发布。
|
||||
`CreateAgentOptions` 携带共享标识以及新 agent 发布前所需的一切:会话元数据(`meta`——已校验的 `cwd`、fork 谱系、seed 边界、来源分类、委派深度)、fork 用的可选 `seed` 回放前缀、按 agent 的 `AgentOptions`、仅创建期有效的取消 `signal`,以及 `setup`。`ResumeAgentOptions` 是持久标识的对应项:`resumeSessionId`、`agentOptions`、`signal` 与 `setup`。`setup` 回调(`AgentSetup`)在两个 id 都尚未发布时组装 agent 的作用域世界——凡经 `agentCtx` 注册的内容都先于 `agent/created` 与第一次提示词组装存在——并可返回一个在发布前一刻调用的同步 commit;setup 拒绝、commit 抛出或所有者 dispose(资源释放)都会回滚事务,两个 id 均不发布。
|
||||
|
||||
`AgentFactory` 是注册表背后的创建接口:循环经 `ctx.agents.setFactory()` 注册其工厂,因此消费方使用 `ctx.agents` 时无需依赖具体循环包。确切的 `create`/`resume` 签名及回滚约定见下方[生成区块](#ctxagents--agentregistry)。
|
||||
|
||||
@@ -156,7 +156,7 @@ interface Agent {
|
||||
type AgentStatus = 'idle' | 'running'
|
||||
```
|
||||
|
||||
`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。dispose(资源释放)会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`followup()` 不返回句柄:其 `MessageId` 标识的是持久的 inbox 插入、认领与丢弃事实,而非之后的助手输出或轮次结束。`whenIdle()` 观察的是整个 agent,因此只有当调用方明确拥有从回执到空闲的这段区间时,才能把它称为一次 run([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。
|
||||
`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。dispose 会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`followup()` 不返回句柄:其 `MessageId` 标识的是持久的 inbox 插入、认领与丢弃事实,而非之后的助手输出或轮次结束。`whenIdle()` 观察的是整个 agent,因此只有当调用方明确拥有从回执到空闲的这段区间时,才能把它称为一次 run([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。
|
||||
|
||||
```ts type-equiv
|
||||
/** Merge-extensible agent creation options. Persona belongs to system-prompt sections. */
|
||||
@@ -179,7 +179,7 @@ inbox 即投递词汇——agent 以持久投影形式拥有的两条有序待
|
||||
type InboxTarget = 'next-turn' | 'next-step'
|
||||
```
|
||||
|
||||
每个待处理入队项就是其 `UserMessage`;`MessageId` 是唯一标识。`Inbox.append`、`prepend`、`replace`、`remove`、`clear`、`splice` 与 `claim` 会记录规范化的持久 `agent/inbox/spliced` 变更,并拒绝重复的待处理 id。`replace(messageId, newMessage)` 与 `remove(messageId)` 通过 `MessageId` 跨两份列表定位待处理消息;替换可以改变标识,并先将旧消息作为 discarded 发布,再将新消息作为 inserted 发布。普通删除和 `clear()` 都表示取消。`claim(target)` 通过无 outcome 的纯删除 splice 移除拟进入步骤的批次——全部 `next-step` 输入,外加轮次边界上的一条 `next-turn` 消息——且不发出 discarded 通知;循环另行逐条发出 claimed 通知。UI 投影等整体队列消费方通过持久 splice 重建 `nextTurn` 与 `nextStep`,而跟踪单条消息的消费方使用精确的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知。
|
||||
每个待处理入队项就是其 `UserMessage`;`MessageId` 是唯一标识。`Inbox.append`、`prepend`、`replace`、`remove`、`clear`、`splice` 与 `claim` 会记录规范化的持久 `agent/inbox/spliced` 变更,并拒绝重复的待处理 id。`replace(messageId, newMessage)` 与 `remove(messageId)` 通过 `MessageId` 跨两份列表定位待处理消息;替换可以改变标识,并先将旧消息作为 discarded 发布,再将新消息作为 inserted 发布。普通删除和 `clear()` 都表示取消。`claim(target)` 通过纯删除 splice 移除拟进入步骤的批次——全部 `next-step` 输入,外加轮次边界上的一条 `next-turn` 消息——且不发出 discarded 通知;循环另行逐条发出 claimed 通知。UI 投影等整体队列消费方通过持久 splice 重建 `nextTurn` 与 `nextStep`,而跟踪单条消息的消费方使用精确的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知。
|
||||
|
||||
取消:
|
||||
|
||||
@@ -249,7 +249,7 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
|
||||
|
||||
## 会话
|
||||
|
||||
`Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM(大语言模型)消息历史从日志*派生*(`deriveMessages()`),而非单独存储。每个条目携带单调的 `seq`、`time` 与按 `type` 判别的 `data` payload;surface 变体还可以在 `sourceEventSeqs` 中列出被引用的较早事件,并携带 `surfaceOp`。
|
||||
`Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM 消息历史从日志*派生*(`deriveMessages()`),而非单独存储。每个条目携带单调的 `seq`、`time` 与按 `type` 判别的 `data` payload;surface 变体还可以在 `sourceEventSeqs` 中列出被引用的较早事件,并携带 `surfaceOp`。
|
||||
|
||||
`SessionEvent` 信封的确切条件字段、十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` 接口、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.md)** 中。
|
||||
|
||||
@@ -303,7 +303,7 @@ declare module '@deepseek-ai/dsh-llm' {
|
||||
|
||||
在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `CallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
|
||||
|
||||
`Branded<B>` 原语位于独立的纯类型包 [dsh-brand](../../packages/util/brand) 中(没有运行时代码,也不依赖 Harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。
|
||||
`Branded<B>` 原语位于独立的纯类型包 [dsh-brand](../../packages/util/brand) 中(没有运行时代码,也不依赖 harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。
|
||||
|
||||
源码:[`packages/util/brand/src/index.ts`](../../packages/util/brand/src/index.ts)
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/credentials.md
|
||||
credentials.md: 9bfc0f46fbc13bef7f0132f17aa0de9007edf26d
|
||||
credentials.zh.md: 649771f53a1e00fd2b4302d8c31f35235a2961a3
|
||||
credentials.zh.md: 9e34f729320c4ab4d2499dcdb540936b61f282cf
|
||||
|
||||
@@ -17,7 +17,7 @@ type CredentialRef = Branded<'CredentialRef'>
|
||||
|
||||
## 解析
|
||||
|
||||
`resolve(ref)` 返回值,连同提供该值、由提供方定义的来源层;未配置期间返回 `undefined`。消费方在每个操作中重新解析,绝不跨操作缓存——这种按操作进行的读取正是热更新机制。
|
||||
`resolve(ref)` 返回值及提供该值的来源层(由提供方定义);未配置期间返回 `undefined`。消费方在每个操作中重新解析,绝不跨操作缓存——这种按操作进行的读取正是热更新机制。
|
||||
|
||||
```ts type-equiv
|
||||
/** One resolved credential value and the source layer that supplied it. */
|
||||
@@ -45,9 +45,9 @@ interface CredentialInfo {
|
||||
}
|
||||
```
|
||||
|
||||
## 变更提交
|
||||
## 已提交的变更
|
||||
|
||||
`credentials/updated (ref)` 在提供方管理的来源发生已提交变更后触发——`set`、`unset` 或在存储中观察到的外部编辑。进程环境自身的变化不可观测,永不发出事件。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。
|
||||
`credentials/updated (ref)` 在提供方管理的来源发生已提交变更后发出——`set`、`unset` 或在存储中观察到的外部编辑。进程环境自身的变化不可观测,永不发出事件。消费方不需要该事件(它们按操作重新解析);它服务于配置界面刷新「已配置」徽标。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/filesystem.md
|
||||
filesystem.md: 5e491e08604f13b1862b44037a3e9a32d7ab0c96
|
||||
filesystem.zh.md: fadbf0178f7dde67d7d5b378c0760141dde28f7a
|
||||
filesystem.zh.md: d81758edb9d9b061c24ae3ae716b15991317fd16
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
可选的文件系统能力由四个部分组成:[dsh-fs](../../packages/fs/fs) 拥有 `ctx.fs` 以及带可选守卫的原子文本操作;[dsh-fs-local](../../packages/fs/fs-local) 实现本地磁盘后端;[dsh-fs-policy](../../packages/fs/fs-policy) 记录观测到的存在或缺失状态,并通过事件(而非服务)添加新鲜度规则;[dsh-tool-fs](../../packages/fs/tool-fs) 直接执行面向模型的 read/write/edit 调用并渲染窗口。它位于 agent loop(智能体循环)主干之外;替换后端不会改变策略或工具 schema。
|
||||
|
||||
`dsh-fs-policy` 是可选插件。没有该插件时,`FileSystem` 服务定义、一个提供方和 `dsh-tool-fs` 消费方组成完整且不受约束的文件系统 seam:`write` 无条件创建或覆盖,`edit` 无条件替换字面文本。策略插件通过裁决 `fs/*` waterfall(瀑布式事件)来改变这些操作。移除该插件不会破坏工具,因为工具调用 `ctx.fs` 并分发事件,而不调用策略方法。加载了 `dsh-tool-fs` 的部署通常也应加载 `dsh-fs-policy`,使默认行为为「先读后写/编辑」。
|
||||
`dsh-fs-policy` 是可选插件。没有该插件时,`FileSystem` 服务定义、一个提供方和 `dsh-tool-fs` 消费方组成完整且不受约束的文件系统 seam:`write` 无条件创建或覆盖,`edit` 无条件替换字面文本。策略插件通过裁决 `fs/*` waterfall(瀑布式事件)来改变这些操作。移除该插件不会破坏工具,因为工具调用 `ctx.fs` 并分发事件,而不调用策略方法。加载了 `dsh-tool-fs` 的部署也应加载 `dsh-fs-policy`,使默认行为为「先读后写/编辑」。
|
||||
|
||||
提供方源码:[`packages/fs/fs/src/types.ts`](../../packages/fs/fs/src/types.ts) 与 [`packages/fs/fs/src/index.ts`](../../packages/fs/fs/src/index.ts)。策略源码:[`packages/fs/fs-policy/src/types.ts`](../../packages/fs/fs-policy/src/types.ts)。读取渲染源码:[`packages/fs/tool-fs/src/read-render.ts`](../../packages/fs/tool-fs/src/read-render.ts)。
|
||||
|
||||
@@ -180,9 +180,9 @@ interface FsEditOutcome {
|
||||
|
||||
## fs 策略事件(提供方约定词汇)
|
||||
|
||||
`dsh-fs` 拥有三个事件,由工具分发、策略插件监听,使发射方(`dsh-tool-fs`)与监听方(`dsh-fs-policy`)共享词汇,而发射方无需依赖策略插件。它们只携带 `dsh-fs` 词汇加一个不透明的 `object` actor,不含面向模型的概念,也不含 agent/会话所有者结构。
|
||||
`dsh-fs` 拥有三个事件,由工具分发、策略插件监听,使事件发出方(`dsh-tool-fs`)与监听方(`dsh-fs-policy`)共享词汇,而事件发出方无需依赖策略插件。它们只携带 `dsh-fs` 词汇加一个不透明的 `object` actor,不含面向模型的概念,也不含 agent/会话所有者结构。
|
||||
|
||||
`fs/write-intent` 与 `fs/edit-intent` 是**单槽决策 waterfall**:工具分发时附带一个默认 thunk(返回 `undefined`,即裸提供方),监听方完全决策而不调用 `next()`。该槽按注册顺序先到先得——由策略插件占据是部署约定,而非强制不变式。`fs/observed` 是一个即发即弃的记录事件,携带 `FsObservation`:存在于某个版本,或确认缺失。该事件通过普通 `ctx.emit` 分发;其监听方必须是同步的、仅产生副作用,因为工具不会捕获该 emit 抛出的异常——抛出异常的监听方可能取代读取操作原本待返回的错误,或使工具在变更已经成功后返回 `isError` 结果。下方生成的 [cordis surface](#cordis-surface) 展示确切签名。
|
||||
`fs/write-intent` 与 `fs/edit-intent` 是**单槽决策 waterfall**:工具分发时附带一个默认 thunk(返回 `undefined`,即裸提供方),监听方完全决策而不调用 `next()`。该 slot 按注册顺序先到先得——由策略插件占据是部署约定,而非强制不变式。`fs/observed` 是一个即发即弃的记录事件,携带 `FsObservation`:存在于某个版本,或确认缺失。该事件通过普通 `ctx.emit` 分发;其监听方必须是同步的、仅产生副作用,因为工具不会捕获该 emit 抛出的异常——抛出异常的监听方可能取代读取操作原本待返回的错误,或使工具在变更已经成功后返回 `isError` 结果。下方生成的 [cordis surface](#cordis-surface) 展示确切签名。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -221,7 +221,7 @@ interface FsPolicyExec {
|
||||
|
||||
## 读取结果(消费方 / 读取渲染)
|
||||
|
||||
文本读取受行窗口、字节上限和后端限制约束。达到字节上限后,扫描仍会继续,但不再保留更多行,因此 `totalLines` 仍为精确值。面向模型的 `read` 工具渲染的结果纯粹是展示性的;不存在 `full`/`partial` 视图区分——授权基于新鲜度(工具 emit 表示存在的 `fs/observed`,并直接携带 stat 的版本),因此任何窗口化读取在文件未变时都能授权后续的 write/edit。元数据未命中时,工具会在返回 `FS_NOT_FOUND` 前 emit 缺失观测,使后续带守卫的写入可以重新创建外部删除的目标,但不会授权 edit。拥有读取操作的执行器 `dsh-tool-fs` 实现读取窗口化并构造该结果;策略插件不执行这些操作。
|
||||
文本读取受行窗口、字节上限和后端限制约束。达到字节上限后,扫描仍会继续,但不再保留更多行,因此 `totalLines` 仍为精确值。面向模型的 `read` 工具渲染的结果纯粹是展示性的;不存在 `full`/`partial` 视图区分——授权基于新鲜度(工具发出表示目标存在的 `fs/observed` 事件,并直接携带 stat 的版本),因此任何窗口化读取在文件未变时都能授权后续的 write/edit。元数据未命中时,工具会在返回 `FS_NOT_FOUND` 前 emit 缺失观测,使后续带守卫的写入可以重新创建外部删除的目标,但不会授权 edit。拥有读取操作的执行器 `dsh-tool-fs` 实现读取窗口化并构造该结果;策略插件不执行这些操作。
|
||||
|
||||
```ts type-equiv
|
||||
/** Outcome of a bounded text read — what {@link formatReadOutput} renders. */
|
||||
@@ -271,7 +271,7 @@ type FsErrorCode =
|
||||
|
||||
## 文件 IO 不设超时
|
||||
|
||||
`read`/`write`/`edit` **不**接受 `timeoutMs`,提供方约定也不设置截止时间——不同于 bash 与 web(它们消费 [`@deepseek-ai/dsh-timeout`](../../packages/util/timeout/README.md))以及 subprocess 支撑的 `glob`/`grep`(其声明的 `timeoutMs` 由 `@deepseek-ai/dsh-timeout-policy` 强制执行):那些是进程支撑的,截止时间可以真正终止工作。本地系统调用至多是尽力中止——超时无法迫使进行中的 `fsync`/`rename` 停下,因此这里的 `timeoutMs` 会成为 seam 无法强制执行的截止时间,而且恰好落在"显式优于隐式"禁止隐式默认值的位置。取消仍通过工具执行 signal 传播,在系统调用边界尽力中止。
|
||||
`read`/`write`/`edit` **不**接受 `timeoutMs`,提供方约定也不设置截止时间——不同于 bash 与 web(它们消费 [`@deepseek-ai/dsh-timeout`](../../packages/util/timeout/README.md))以及 subprocess 支撑的 `glob`/`grep`(其声明的 `timeoutMs` 由 `@deepseek-ai/dsh-timeout-policy` 强制执行):那些是进程支撑的,截止时间可以真正终止工作。本地系统调用至多是尽力中止——超时无法迫使进行中的 `fsync`/`rename` 停下,因此这里的 `timeoutMs` 会成为 seam 无法强制执行的截止时间,而且恰好落在「显式优于隐式」禁止隐式默认值的位置。取消仍通过工具执行 signal 传播,在系统调用边界尽力中止。
|
||||
|
||||
## 服务与插件
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/goal.md
|
||||
goal.md: 676b2f49d00681ac7f05b894cda0157ebba4cfcd
|
||||
goal.zh.md: 7f83e6f58461e99b05f04794e83c0dfa9bd58d1b
|
||||
goal.zh.md: 1d46bc2c9ba1f3b35026ff132d323e46ccd3bc19
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](goal.md) | 中文
|
||||
|
||||
事件溯源目标服务及其策略消费方共享的类型。[目标领域 Agent Note](../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md)负责记录持久化与激活决策;本页记录 [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts) 中的确切字段和变体。
|
||||
事件溯源目标服务及其策略消费方共享的类型。[目标领域 Agent Note](../../.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.md) 负责记录持久化与激活决策;本页记录 [`packages/goal/goal/src/types.ts`](../../packages/goal/goal/src/types.ts) 中的确切字段和变体。
|
||||
|
||||
## 标识与生命周期
|
||||
|
||||
@@ -142,7 +142,7 @@ interface GoalChanged {
|
||||
|
||||
## 服务行为
|
||||
|
||||
[`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从持久 `goal/change` 事件执行严格回放折叠、校验确切的活跃 agent 身份、以比较并设置方式执行变更,并发出 `goal/changed` 通知;监听器故障会被隔离。包 [README](../../packages/goal/goal/README.md) 定义可调用 API 和面向模型的约定。
|
||||
[`GoalService`](../../packages/goal/goal/src/index.ts) 解析创建默认值、从持久 `goal/change` 事件执行严格回放折叠、校验传入的 agent(智能体)是注册表中的确切活跃实例、以比较并设置方式执行变更,并发出 `goal/changed` 通知;监听器故障会被隔离。包 [README](../../packages/goal/goal/README.md) 定义可调用 API 和面向模型的约定。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/http-server.md
|
||||
http-server.md: a8a8aece5fd49cda6db0111ac882d83ae07135c4
|
||||
http-server.zh.md: 66fc6a6cfa468a3f96a29200bb2fe0aae581f44b
|
||||
http-server.zh.md: 011a1413e05b74a00229e979ffd0d29000a89437
|
||||
|
||||
@@ -42,9 +42,9 @@ interface Config {
|
||||
|
||||
## 服务
|
||||
|
||||
`HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会使初始化被拒绝,启动进程会报告失败的 fiber。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层约定,冲突即配置错误。`tapIndex(transform)` 添加一个纯的 html 到 html 转换,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,包括 `config.port` 为 0 时操作系统分配的端口。
|
||||
`HttpServerService`(`ctx.httpServer`)在激活时立即监听;监听失败(EADDRINUSE 等)会使初始化被拒绝,启动进程会报告失败的 fiber。`register(route)` 添加一条具名路由并返回其 disposer;重复的 `(kind, path)` 抛出异常,因为路由模式是组合层约定,冲突即配置错误。`tapIndex(transform)` 添加一个纯 HTML 到 HTML 转换函数,按注册顺序应用于每个 index 响应(`/` 和每次 SPA 回退);[dsh-client-modules](../../packages/client/modules) 用它注入启动 manifest(元数据清单)。`port` 读取监听端口,包括 `config.port` 为 0 时操作系统分配的端口。
|
||||
|
||||
处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能像 SSE(Server-Sent Events)那样保持响应打开,而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包(package)从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。
|
||||
处理过程中抛出异常的请求(畸形的 % 转义撞上 `decodeURIComponent`、客户端在请求体中途断开)会记录为警告并应答 400(响应头已发出时则销毁 socket),绝不导致进程退出。dispose(资源释放)把 `close()` 与 `closeAllConnections()` 配对使用,因为处理器可能像 SSE(Server-Sent Events)那样保持响应打开,而这类连接永远不会自行结束;没有强制关闭,拆卸就会挂起。该包从不打印输出:URL 行归 shell 所有。逐包运维细节(含开发模式的 bundle 监视流水线)留在 [README](../../packages/host/webserver/README.md) 中。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/invariants.md
|
||||
invariants.md: d6f5f175fb277af09e620b94e8604e04ce2a9d26
|
||||
invariants.zh.md: b8e08fcf05f76cd69d0f14d64f38dfd15aa6346a
|
||||
invariants.zh.md: c0205224f78260db53504070a1ea5130a0e65e6f
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](invariants.md) | 中文
|
||||
|
||||
[dsh-invariants](../../packages/support/invariants) 是面向包(package)自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 配套插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定;注册表设计由[不变式服务 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md)规定。
|
||||
[dsh-invariants](../../packages/support/invariants) 是面向包自有运行时不变式检查的可配置注册表服务(`ctx.invariants`)。它是一个 support 组的包,不是三包能力 seam,也不属于 agent loop(智能体循环)主干:注册表拥有选择逻辑、名称保留、子 fiber 生命周期和归因到包的失败,而每个工作区包发布一个 `./invariant` 配套插件,以自己确切的 npm 包名注册检查。检查可以断言什么(权威事件流或可变数据,绝不是服务或方法是否存在)是 [AGENTS.md](../../AGENTS.md#conventions) 中的运行时不变式约定;注册表设计由[不变式服务 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-owned-invariant-service.md)规定。
|
||||
|
||||
源码:[`packages/support/invariants/src/index.ts`](../../packages/support/invariants/src/index.ts)
|
||||
|
||||
@@ -20,7 +20,7 @@ interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时大声失败:空白、首尾带空白、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/support/invariants/README.md))。
|
||||
一个包被选中的条件是:服务已启用,允许列表为空或至少一个模式匹配其完整 npm 名称,且没有任何阻止列表模式匹配;阻止列表匹配优先于允许列表匹配。条目用 `new RegExp(source)` 编译:除非模式自带 `^` 和 `$`,匹配不锚定;`/pattern/flags` 语法不被解析。校验在服务启动时明确报错:空白、首尾带空白、重复或无效的条目会抛出异常,而不是被跳过。有效模式可以不匹配任何当前已加载的包,因此后续加载与 HMR(热模块替换)保持确定性;过滤器在服务生命周期内固定不变([README](../../packages/support/invariants/README.md))。
|
||||
|
||||
## 安装器
|
||||
|
||||
@@ -56,7 +56,7 @@ interface InvariantInstaller {
|
||||
|
||||
## 配套插件约定
|
||||
|
||||
每个工作区包都拥有一个 `./invariant` 配套插件([包约定](../../packages/AGENTS.md));发布与注册是穷尽式的,但刻意不合成断言。只有当包对某个可观察事件或可变数据拥有所有权关系时,配套插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md))。可执行配套插件的目录与标准组合方式见[包 README](../../packages/support/invariants/README.md)。
|
||||
每个工作区包都拥有一个 `./invariant` 配套插件([包约定](../../packages/AGENTS.md));发布与注册是穷尽式的,但刻意不合成断言。只有当包拥有某个可观察事件或某种可变数据关系时,配套插件才安装检查;否则它导出一个空安装器,其起始注释以 `No runtime invariant:` 开头,针对该包具体解释为什么没有可检查项。`pnpm run verify-package-invariants` 机械地拒绝「生成文件」标记、无解释的空安装器、遗漏或忽略报告器的非空安装器、错误的注册名称,以及不完整的导出、发布、依赖或打包接线([机械规则 Agent Note](../../.agents/notes/implemented/architecture/2026-07-19-package-invariant-runtime-contracts.md))。可执行配套插件的目录与标准组合方式见[包 README](../../packages/support/invariants/README.md)。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/llm-streaming.md
|
||||
llm-streaming.md: 41ae943b1945209bc7996379dc326434b5803d17
|
||||
llm-streaming.zh.md: b67cdfd4eb5a3c1320e7afe333d4d06516295f7d
|
||||
llm-streaming.zh.md: fae5719e3185a0499cb561e1b4b2b451129df58d
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](llm-streaming.md) | 中文
|
||||
|
||||
[`packages/llm`](../../packages/llm/README.md) 提供对话与流式输出类型:每个请求和持久历史共用的 `Message`/`ContentBlock` 变体、完整组装的模型请求、原始 `StreamChunk` 协议、每个适配器必须实现的适配器约定(adapter contract),以及共享的 assembler。[核心包](core.md)在每个轮次持有并记录这些值;本页声明它们。
|
||||
[`packages/llm`](../../packages/llm/README.md) 提供对话与流式输出类型:每个请求和持久历史共用的 `Message`/`ContentBlock` 变体、完整组装的模型请求、原始 `StreamChunk` 协议、每个适配器必须实现的适配器约定,以及共享的 assembler。[核心包](core.md)在每个轮次持有并记录这些值;本页声明它们。
|
||||
|
||||
源码:[`packages/llm/llm/src/types.ts`](../../packages/llm/llm/src/types.ts)
|
||||
|
||||
@@ -28,7 +28,7 @@ interface ContentBlockMap {
|
||||
}
|
||||
```
|
||||
|
||||
各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ImageBlock`(一个持久的[图片附件](attachment.md))、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`),以及 `ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。仅当适配器、UI、压缩和持久回放路径均支持某种新模态时,才将其纳入可合并扩展的 map。
|
||||
各块接口(完整字段见源码):`TextBlock`(`text`)、`ReasoningBlock`(thinking,区别于可见文本)、`ImageBlock`(一个持久的[图片附件](attachment.md))、`ToolCallBlock`(`id: CallId`、`name`、原始 JSON `arguments`),以及 `ToolResultBlock`(`toolCallId`、嵌套 `content: ContentBlock[]`、`isError?`)。`ContentBlock = ContentBlockMap[ContentBlockType]`。仅当适配器、UI、压缩(compaction)和持久回放路径均支持某种新模态时,才将其纳入可合并扩展的 map。
|
||||
|
||||
源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts)
|
||||
|
||||
@@ -209,7 +209,7 @@ interface LlmFailure {
|
||||
|
||||
- **`usage` 在 `finish` 之前,`finish` 之后不再有任何分片。** 将两者都推迟到提供方的流结束标记,这样尾部的 usage-only 分片就不会违反顺序。
|
||||
- **工具调用的 `arguments` 全程保持原始 JSON 字符串。** 部分片段通过 `argumentsDelta` 流式传输;如果提供方返回的是已解析的对象,适配器在 `block-end` 时重新序列化为字符串。
|
||||
- **两条受支持的错误路径,共用一个 `LlmFailure` 类型。** 失败可以从 `stream()` 抛出(传输/协议错误),**或者**以 `finish {kind:'error'|'aborted', failure}` 结束流(无法在流中途抛异常的适配器用它表示提供方带内错误)。`LlmError.failure` 携带同一个 `LlmFailure`。调用选定适配器后,流会保留被抛出的确切 `Error` 对象,并将不可变事实以及实际服务注册所对应的不可变重试策略关联到该调用;agent loop(智能体循环)关闭失败步骤,再把错误、事实、不可变的先前已重试失败事实、实际服务策略和轮次信号提供给 `agent/request-error`。处理该错误的 listener 在其 await 的修复完成后返回 `{ kind: 'retry' }`;若未恢复,结构化失败会成为轮次错误,并且该次尝试不会提交正常 assistant 消息或工具副作用。
|
||||
- **两条受支持的错误路径,共用一个 `LlmFailure` 类型。** 失败可以从 `stream()` 抛出(传输/协议错误),**或者**以 `finish {kind:'error'|'aborted', failure}` 结束流(无法在流中途抛异常的适配器用它表示提供方带内错误)。`LlmError.failure` 携带同一个 `LlmFailure`。调用选定适配器后,流会保留被抛出的确切 `Error` 对象,并将不可变事实以及实际服务注册所对应的不可变重试策略关联到该调用;agent loop(智能体循环)关闭失败步骤,再把错误、事实、不可变的先前已重试失败事实、实际服务策略和轮次信号提供给 `agent/request-error`。处理该错误的 listener 在其 await 的修复完成后返回 `{ kind: 'retry' }`;若未恢复,结构化失败会成为轮次错误,并且该次尝试不会提交正常 assistant 消息或工具副作用。
|
||||
- **一次适配器调用就是一次提供方尝试。** 适配器禁用库重试。agent 层恢复会打开另一个持久、带编号的轮次;直接调用 `ctx.llm.stream()` 的调用方仍然只尝试一次。
|
||||
- **提供方停顿在传输层受到时限约束。** 两个已交付的远程适配器都暴露正数且有限的 `streamIdleTimeoutMs`,默认五分钟。watchdog 只在 iterator `next()` 尚未完成时启动,整个请求使用同一个稳定 signal,把自身到期映射为 `TIMEOUT`,并把更早发生的调用方中止保留为 `ABORTED`。
|
||||
- **上下文溢出只有一个规范 code。** 两个 DeepSeek 适配器都通过 `isContextWindowExceededError()` 对提供方的显式细节分类并暴露 `CONTEXT_WINDOW_EXCEEDED`,无论失败以抛出的 HTTP `LlmError` 还是带内 finish error 到达。消费方按 code 路由,绝不依赖提供方文本。
|
||||
@@ -223,7 +223,7 @@ interface LlmFailure {
|
||||
|
||||
## `AppIdentity`:应用归属
|
||||
|
||||
每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该约定有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包(package) manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md)。
|
||||
每个适配器都会向提供方发送的静态公开应用标识([`packages/llm/llm/src/attribution.ts`](../../packages/llm/llm/src/attribution.ts))。`attributionHeaders(identity?)` 只把它映射到标准 `User-Agent` header;该约定有意不支持 OpenRouter 特有的应用归属 header。默认 `APP_IDENTITY` 从包 manifest(元数据清单)获取版本;每个字段都是公开产品事实——不含 secret、路径、会话 id 或逐用户标识,且任何逐请求信息都不得影响这些值。设计理由见[强制 `User-Agent` 归属](../../.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md)。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -594,11 +594,11 @@ interface LlmDiscoveredModel {
|
||||
|
||||
### 请求信封:`LlmCallConfig` 与记录的 header
|
||||
|
||||
循环从已记录状态构建每个请求。`EpochHeader` 记录调用配置,标记由适配器默认值提供的字段,并通过完整的 `request/header` 快照记录渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note(agent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。
|
||||
循环从已记录状态构建每个请求。`EpochHeader` 记录调用配置,标记由适配器默认值提供的字段,并通过完整的 `request/header` 快照记录渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。
|
||||
|
||||
`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置以及由适配器默认值提供的字段。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。
|
||||
`agent/request` 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall(瀑布式事件)开始前,循环会移除标记为适配器默认值的值,使确切模型准备过程填入所选路由的当前值;未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID(不自动调整),并记录生效配置以及由适配器默认值提供的字段。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。
|
||||
|
||||
在协议中,循环构建的请求先读取 `system` 槽位(渲染后的提示词组装),再读取派生历史。已记录的请求快照会以最新的 `user/message`(轮次首步)或上一步的工具结果(后续步骤)结尾。开发不变式针对每个循环构建的请求精确重算此等式。
|
||||
在协议中,循环构建的请求先读取 `system` slot(渲染后的提示词组装),再读取派生历史。已记录的请求快照会以最新的 `user/message`(轮次首步)或上一步的工具结果(后续步骤)结尾。开发不变式针对每个循环构建的请求精确重算此等式。
|
||||
|
||||
FIXME(call-config-shape):重新审视其余哪些字段出于缓存目的确实属于 epoch 层级(`model` 和模型持有的推理强度已明确属于;采样标量目前出于谨慎保留在此)。
|
||||
|
||||
@@ -632,7 +632,7 @@ interface LlmCallConfigAdapterDefaults {
|
||||
|
||||
## 服务与提供方约定
|
||||
|
||||
`LlmAdapter` 是提供方约定:创建子类、实现 `stream()`,再用 `ctx.llm.registerAdapter(providers, adapter)` 注册一个适配器实例。`GenerateOptions.provider` 选择已注册适配器;`GenerateOptions.model` 会传给该适配器,无需在生命周期启动时注册。重复提供方路由会原子失败。可选的 `providerRetryPolicy()` 会按路由捕获并填入 normal 默认值,`providerInfo()` 与异步 `listModels()` 方法则为 `LlmService.listProviders()` / `listModels()` 提供分离的 selector 元数据。该目录仅供参考,不是请求白名单:适配器仍是权威,并可接受未列出的模型 id。单次异步 `resolveModel()` 查询返回确切模型身份,以及可选的对正确性敏感的上下文容量、适配器配置的 `defaultMaxTokens`、由模型持有的有序推理强度 ID 和部署默认值;字段缺失表示元数据不可用或保留提供方持有的行为,而不表示目录成员关系无效。解析器会接收可选的取消信号,并且必须在信号中止后迅速完成结算。`LlmService.resolveModelInfo()` 会校验聚合结果并返回分离值。在最终适配器边界,`resolveCallConfig()` 仅在 `maxTokens` 缺失时填入输出默认值,并校验和填入推理强度,因此直接调用也无法绕过任何一项已配置行为;直接分派会在等待解析前捕获一项适配器注册。agent loop 则使用 `prepareCall()`,使模型解析、请求头持久记录和分派全程使用同一项注册,保留来自同一次查询的分离上下文元数据,并报告适配器填入的配置字段。适配器查找发生在 `llm/stream` waterfall(瀑布式事件)的终端 continuation,因此 listener 可以在查找前短路调用,或路由一个可变的一次性请求。AgentLoop 在外层 waterfall 返回流句柄时观察到一次请求尝试;这个有限边界不能证明惰性终端适配器已构造完成或开始提供方 I/O。`block-start` / `block-end` 的 `index` 关联与 assembler 共同意味着适配器只需 emit 格式正确的分片——块重组不是每个适配器各自的问题。消费方 surface(`ctx.llm.stream()`)与 `llm/stream` waterfall 见 [architecture.md § 内容块与流式传输](../architecture.md#model-content)。
|
||||
`LlmAdapter` 是提供方约定:创建子类、实现 `stream()`,再用 `ctx.llm.registerAdapter(providers, adapter)` 注册一个适配器实例。`GenerateOptions.provider` 选择已注册适配器;`GenerateOptions.model` 会传给该适配器,无需在生命周期启动时注册。重复提供方路由会原子失败。可选的 `providerRetryPolicy()` 会按路由捕获并填入 normal 默认值,`providerInfo()` 与异步 `listModels()` 方法则为 `LlmService.listProviders()` / `listModels()` 提供分离的 selector 元数据。该目录仅供参考,不是请求白名单:适配器仍是权威,并可接受未列出的模型 id。单次异步 `resolveModel()` 查询返回确切模型身份,以及可选的对正确性敏感的上下文容量、适配器配置的 `defaultMaxTokens`、由模型持有的有序推理强度 ID 和可选的部署默认值;字段缺失表示元数据不可用或保留提供方持有的行为,而不表示目录成员关系无效。解析器会接收可选的取消信号,并且必须在信号中止后迅速完成结算。`LlmService.resolveModelInfo()` 会校验聚合结果并返回分离值。在最终适配器边界,`resolveCallConfig()` 仅在 `maxTokens` 缺失时填入输出默认值,并校验和填入推理强度,因此直接调用也无法绕过任何一项已配置行为;直接分派会在等待解析前捕获一项适配器注册。agent loop 则使用 `prepareCall()`,使模型解析、请求头持久记录和分派全程使用同一项注册,保留来自同一次查询的分离上下文元数据,并报告适配器填入的配置字段。适配器查找发生在 `llm/stream` waterfall 的终端 continuation,因此 listener 可以在查找前短路调用,或路由一个可变的一次性请求。AgentLoop 在外层 waterfall 返回流句柄时观察到一次请求尝试;这个有限边界不能证明惰性终端适配器已构造完成或开始提供方 I/O。`block-start` / `block-end` 的 `index` 关联与 assembler 共同意味着适配器只需 emit 格式正确的分片——块重组不是每个适配器各自的问题。消费方 surface(`ctx.llm.stream()`)与 `llm/stream` waterfall 见 [architecture.md § 内容块与流式传输](../architecture.md#model-content)。
|
||||
|
||||
```ts type-equiv
|
||||
/** One model call whose config and adapter registration were resolved together. */
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/persistence.md
|
||||
persistence.md: fde8348d64a200eda5133abf66deedee6be09857
|
||||
persistence.zh.md: 7a334501ee7fcbefda9d1381dfe41a79679d33b3
|
||||
persistence.zh.md: a2a836d81d153a2697c1d739345501e7e6770eef
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
后端重新加载一个在轮次中途崩溃的日志时,会发现一个已打开的 `turn/start` 却没有 `turn/end`。它**不会**截断日志:在长周期任务中,单个轮次可能非常庞大(许多步骤、大量工具输出),而这些事件在崩溃前已被持久追加。后端改为用一个合成的 `turn/end { reason: { kind: 'interrupted' } }` 关闭这个遗留轮次,在不改变其前后任何独立事件的情况下配平被中断的执行。`interrupted` 是唯一一个不由循环发出的 `TurnEndReason`(见 [session.md](session.md#why-a-turn-ended-turnendreasonmap))。
|
||||
|
||||
修复仅适用于冷会话。对于活跃 id,`SessionPersistence.load(id)` 会等待权威内存快照完成持久化,并且只在日志平衡时返回;若活跃轮次仍未闭合,则拒绝操作,而不是添加合成的中断边界。HMR 会接管活跃前缀,而不会关闭其中正在进行的轮次。
|
||||
修复仅适用于冷会话。对于活跃 id,`SessionPersistence.load(id)` 会等待权威内存快照完成持久化,并且只在日志平衡时返回;若活跃轮次仍未闭合,则拒绝操作,而不是添加合成的中断边界。HMR(热模块替换)会接管活跃前缀,而不会关闭其中正在进行的轮次。
|
||||
|
||||
`SessionPersistence.inspect(id)` 会构造一个不可变的逻辑 Session,但不发布它,也不写入恢复内容。冷检查会在内存中配平中断的轮次,同时保持撕裂的物理尾部不变;检查已经实时存在的 Session 则借用其当前不可变快照,因此可能包含打开的 turn。使用协调器的实现会在有界 LRU 中保留这个精确的冷未发布 Session,因此重复历史读取与后续 `prepare(id)` 可复用同一次读取、解压、验证、冻结及 Session 构造。`prepare(id)` 会预留该 Session、提交待处理修复并返回可 dispose 的发布句柄;`load(id)` 使用相同机制提交修复,但不会发布 Session。该生命周期由 [Session 准备阶段决策](../../.agents/notes/implemented/architecture/2026-08-05-session-preparation.md)定义。
|
||||
`SessionPersistence.inspect(id)` 会构造一个不可变的逻辑 Session,但不发布它,也不写入恢复内容。冷检查会在内存中配平中断的轮次,同时保持撕裂的物理尾部不变;检查已处于活跃状态的 Session 则借用其当前不可变快照,因此可能包含未闭合的轮次。使用协调器的实现会在有界 LRU 中保留这个精确的冷未发布 Session,因此重复历史读取与后续 `prepare(id)` 可复用同一次读取、解压、验证、冻结及 Session 构造。`prepare(id)` 会预留该 Session、提交待处理修复并返回可 dispose 的发布句柄;`load(id)` 使用相同机制提交修复,但不会发布 Session。该生命周期由 [Session 准备阶段决策](../../.agents/notes/implemented/architecture/2026-08-05-session-preparation.md)定义。
|
||||
|
||||
## `SessionLocation`——可选的逐会话产物目标
|
||||
|
||||
`SessionPersistence.locate(meta)` 会同步解析一个归后端所有的独立产物,而不会读取、创建或 flush 它。JSONL 返回其项目/会话目录内 transcript(文本记录)的绝对路径;SQLite 因各会话共享一个数据库而返回 `undefined`。因此,返回的路径可能指向尚不存在、或还不包含当前尚未 flush 的轮次;它是位置提示,不是授权或新鲜度保证。
|
||||
`SessionPersistence.locate(meta)` 会同步解析一个归后端所有的独立产物,而不会读取、创建或 flush 它。JSONL 返回其项目/会话目录内 transcript(文本记录)的绝对路径;SQLite 因各会话共享一个数据库而返回 `undefined`。因此,返回的路径可能指向尚不存在的文件,或指向还不包含当前尚未 flush 轮次的文件;它是位置提示,不是授权或新鲜度保证。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -93,7 +93,7 @@ interface SessionHeader {
|
||||
|
||||
## `CreateSessionOptions`:seed 与元数据
|
||||
|
||||
通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)与 `meta`(store 折叠进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方可以提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、可选的粗粒度 `origin`、`delegationDepth`、该 agent 所依据组装的 `agentPreset` 以及已有的 `createdAt`。`origin: 'subagent'` 让产品导航能够隐藏重复的 child 行;它不证明描述符有效,也不证明 child 可以恢复。
|
||||
通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)与 `meta`(store 整合进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方可以提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、可选的粗粒度 `origin`、`delegationDepth`、用于组装该 agent(智能体)的 `agentPreset` 以及已有的 `createdAt`。`origin: 'subagent'` 让产品导航能够隐藏重复的 child 行;它不证明描述符有效,也不证明 child 可以恢复。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -140,7 +140,7 @@ interface SessionRawArtifact {
|
||||
|
||||
## 准备与恢复所有权
|
||||
|
||||
`SessionStore.prepare()` 接收普通创建选项,或通过 `RestoredSessionOptions` 转移所有权的新鲜持久化对象图。恢复分支会直接验证并冻结转移来的 header 与事件,因此调用方不得保留可变别名。`SessionPreparation` 随后持有该精确的未发布 Session,直至发布或回滚;dispose 是同步且幂等的。持久化检查只暴露 `SessionInspection`,即从同一个已准备 Session 借用的不可变逻辑视图。
|
||||
`SessionStore.prepare()` 接收普通创建选项,或通过 `RestoredSessionOptions` 转移所有权的全新的持久化对象图。恢复分支会就地验证并冻结转移来的 header 与事件,因此调用方不得保留可变别名。`SessionPreparation` 随后持有该精确的未发布 Session,直至发布或回滚;dispose 是同步且幂等的。持久化检查只暴露 `SessionInspection`,即从同一个已准备 Session 借用的不可变逻辑视图。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/plan.md
|
||||
plan.md: bedc2a37cac94b4a7b623432fe9eae9ce552c853
|
||||
plan.zh.md: 5d6a5bae2a1be83a8ca21a660125822ab97e0c5e
|
||||
plan.zh.md: 3172fd69d0db049a16dc1cc08678113cd9a1cb53
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](plan.md) | 中文
|
||||
|
||||
计划模式是 [dsh-plan-mode](../../packages/plan/plan-mode) 拥有的、记录到日志的逐 agent(智能体)协作状态(`ctx.planMode`,`PlanModeService`):激活期间,每个模型请求都会包含一段部署持有的指引。计划模式是**软性指引**。[沙箱模式](sandbox.md)与[审批策略](approval.md)分别强制限制;两者都不读写计划状态,因此部署需要分别配置它们。该包(package)是可选项,agent loop(智能体循环)不依赖它。它贡献 `plan:policy` 提示词段落,并注册 `exit_plan_mode` 工具和 `/plan` 命令。[设计说明](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)负责决策依据;[包 README](../../packages/plan/plan-mode/README.md)负责模型体验与限制细节。
|
||||
计划模式是 [dsh-plan-mode](../../packages/plan/plan-mode) 拥有的、记录到日志的逐 agent(智能体)协作状态(`ctx.planMode`,`PlanModeService`):激活期间,每个模型请求都会包含一段部署持有的指引。计划模式是**软性指引**。[沙箱模式](sandbox.md)与[审批策略](approval.md)分别强制限制;两者都不读写计划状态,因此部署需要分别配置它们。该包是可选项,agent loop(智能体循环)不依赖它。它贡献 `plan:policy` 提示词段落,并注册 `exit_plan_mode` 工具和 `/plan` 命令。[设计说明](../../.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)负责决策依据;[包 README](../../packages/plan/plan-mode/README.md)负责模型体验与限制细节。
|
||||
|
||||
源码:[`packages/plan/plan-mode/src/index.ts`](../../packages/plan/plan-mode/src/index.ts)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
由于每个会话事件都位于轮次之内,用户选择会保持待生效状态,直到下一个被接受的轮内 pre-step 在派生请求之前追加该选择,无论该 pre-step 位于哪个轮次。选择不会强制续行,因此在某轮最后一个被接受的 pre-step 之后作出的选择会在之后的轮次追加。`set(agent, active)` 记录待生效选择(目标值与已记录或已在等待的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`:用于组装当前步骤的已记录状态,以及等待追加的已选状态。
|
||||
|
||||
agent 运行时,唯一的追加点是前置(prepend)注册的 `agent/pre-step` 监听器。它会观察每个候选请求步骤,包括第 1 轮第 1 步和请求恢复重试;它先调用下游监听器,只在下游接受该步骤后追加。提示词提交发生在轮次开启之前,无法追加 `plan/mode`,因此在提示词处作出的选择由它开启的轮次内第一个被接受的 pre-step 追加。追加失败不能阻塞轮次,且该选择会继续等待之后被接受的轮内 pre-step。追加用户选择时还会记录一条插件来源的 `user/message` 通知,但仅当最后记录的请求头描述的是另一种状态时才记录,因此模型恰好在上下文变化时收到通知,且绝不重复。在某轮最后一个被接受的 pre-step 之后作出的选择只存在于进程内;如果进程在另一个被接受的轮内 pre-step 之前退出,该选择会丢失([README 限制](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。
|
||||
agent 运行时,唯一的追加点是前置(prepend)注册的 `agent/pre-step` 监听器。它会观察每个候选请求步骤,包括第 1 轮第 1 步和请求恢复重试;它先调用下游监听器,只在下游接受该步骤后追加。提示词准入发生在轮次开启之前,无法追加 `plan/mode`,因此在提示词处作出的选择由它开启的轮次内第一个被接受的 pre-step 追加。追加失败不能阻塞轮次,且该选择会继续等待之后被接受的轮内 pre-step。追加用户选择时还会记录一条插件来源的 `user/message` 通知,但仅当最后记录的请求头描述的是另一种状态时才记录,因此模型恰好在上下文变化时收到通知,且绝不重复。在某轮最后一个被接受的 pre-step 之后作出的选择只存在于进程内;如果进程在另一个被接受的轮内 pre-step 之前退出,该选择会丢失([README 限制](../../packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。
|
||||
|
||||
## 配置
|
||||
|
||||
@@ -30,7 +30,7 @@ interface PlanModeConfig {
|
||||
|
||||
## 退出工具与 `/plan` 命令
|
||||
|
||||
[`exit_plan_mode`](../tool-catalog.md#deepseek-aidsh-plan-mode) 在计划模式未激活时仍保持注册,因此进入或离开计划模式只改变提示词段落,绝不改变请求的工具目录;在计划模式之外执行会失败。在计划模式中,它要求一份以 `#` 标题开头的完整 markdown 计划,并通过[用户交互 seam](user-interaction.md) 呈交评审。批准返回 `{ approved: true }`,并记录一个静默(不叙述)的待定退出,由下一个被接受的轮内 pre-step 追加。因此,计划指引在 assistant 当前这批工具调用的剩余部分继续生效,而工具结果本身会报告这次转换。「继续规划」则是一次携带用户反馈的失败调用,模型据此修订并再次呈交;评审期间交互通道缺失或服务重载同样使调用失败,而不是静默离开计划模式。
|
||||
[`exit_plan_mode`](../tool-catalog.md#deepseek-aidsh-plan-mode) 在计划模式未激活时仍保持注册,因此进入或离开计划模式只改变提示词段落,绝不改变请求的工具目录;在计划模式之外执行会失败。在计划模式中,它要求一份以 `#` 标题开头的完整 markdown 计划,并通过[用户交互 seam](user-interaction.md) 呈交评审。批准返回 `{ approved: true }`,并记录一个静默(不叙述)的待生效退出,由下一个被接受的轮内 pre-step 追加。因此,计划指引在 assistant 当前这批工具调用的剩余部分继续生效,而工具结果本身会报告这次转换。「继续规划」则是一次携带用户反馈的失败调用,模型据此修订并再次呈交;评审期间交互通道缺失或服务重载同样使调用失败,而不是静默离开计划模式。
|
||||
|
||||
当 [`ctx.commands`](commands.md) 被组合时,插件注册 `/plan [off|message]`:单独的 `/plan` 选择计划模式;任何其他非空消息先选择计划模式,再通过 `agent.steer()` 提交该文本,使其在计划指引下成为下一步骤的普通已记录用户消息;确切参数 `off` 选择未激活,这还会在待生效条目被追加并对请求可见之前将其取消。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/sandbox.md
|
||||
sandbox.md: 0b770c89b176b6459d6ea2f8a85cd2e18a8800b8
|
||||
sandbox.zh.md: 45510c6636daf279582017009742fb6ef4b3e2ce
|
||||
sandbox.zh.md: 1add4fa1b7bd60a9f02c25315620e196b073b32a
|
||||
|
||||
@@ -40,7 +40,7 @@ type SandboxEnforcement = 'full' | 'partial'
|
||||
|
||||
## 逐调用策略
|
||||
|
||||
完整执行策略会按每次能力调用解析并携带。它包括 `danger-full-access`,因此消费方可以只解析一次策略,再决定是否绕过约束。普通工具调用从调用会话的不可变 cwd 派生 `workspaceRoot`;部署配置是没有 agent(智能体)时的回退值。root 会先按文件系统语义规范化,再做词法规范化,因此包含 `symlink/..` 的 cwd 会标识所生成进程实际运行的目录。
|
||||
完整执行策略会按每次能力调用解析并携带。它包括 `danger-full-access`,因此消费方可以只解析一次策略,再决定是否绕过约束。普通工具调用从调用会话的不可变 cwd 派生 `workspaceRoot`;部署配置是没有 agent(智能体)时的回退值。root 会先按文件系统语义规范化,再做词法规范化,因此包含 `symlink/..` 的 cwd 会标识 spawn 出的进程实际运行的目录。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -153,7 +153,7 @@ interface ConfinedArgv {
|
||||
|
||||
`ctx.sandbox.confine(argv, policy)` 返回一个 `ConfinedArgv`,或在没有可用后端时抛出 `SandboxUnavailableError`(错误码 `SANDBOX_UNAVAILABLE`)。消费方也可以在 spawn 或观察所返回的 argv 时对失败进行分类;该归因属于消费方约定。对于受限策略,静默的无隔离透传永远不合法。
|
||||
|
||||
提供方选择、探测、缓存和后端专有的强制执行报告归[本地提供方](../../packages/sandbox/sandbox-local/README.md)所有。
|
||||
提供方选择、探测、缓存和后端特定的强制执行报告归[本地提供方](../../packages/sandbox/sandbox-local/README.md)所有。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md
|
||||
session-projection.md: 281e7630eed6480de07a66bf7050798c83396f77
|
||||
session-projection.zh.md: 71a8a3f85d698b2d0828a797bc81069a0617d17b
|
||||
session-projection.zh.md: bc1448e8baa0f38b3a9ffed1c6005e8905c93bcd
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](session-projection.md) | 中文
|
||||
|
||||
会话投影 seam 是一项[能力 seam](../capability-seams.md):领域 host 插件经由它向客户端载体供给按会话的日志派生状态的当前全量值;三方分别是 Service Definition 与注册表([dsh-session-projection](../../packages/session/session-projection),`ctx.sessionProjections`)、领域贡献方(每个领域注册一个纯单元)与载体([dsh-host-apiproxy](../../packages/host/apiproxy) 的历史尾页与 `session/projection` 推送帧)。它是一项可选能力,不属于 agent loop(智能体循环)主干。框架负责驱动,领域负责计算:注册表只订阅一次 `session/event`,并把每个已提交事件折叠进每个单元;领域不持有任何订阅,客户端也从不折叠领域事件——它们收到的是成品值。设计权威:[session-projection RFC](../../.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md);驱动、缓存与变更流约定:[包(package)README](../../packages/session/session-projection/README.md)。
|
||||
会话投影 seam 是一项[能力 seam](../capability-seams.md):领域 host 插件经由它向客户端载体供给按会话的日志派生状态的当前全量值;三方分别是 Service Definition 与注册表([dsh-session-projection](../../packages/session/session-projection),`ctx.sessionProjections`)、领域贡献方(每个领域注册一个纯单元)与载体([dsh-host-apiproxy](../../packages/host/apiproxy) 的历史尾页与 `session/projection` 推送帧)。它是一项可选能力,不属于 agent loop(智能体循环)主干。框架负责驱动,领域负责计算:注册表只订阅一次 `session/event`,并把每个已提交事件折叠进每个单元;领域不持有任何订阅,客户端也从不折叠领域事件——它们收到的是成品值。设计权威:[session-projection RFC](../../.agents/notes/proposed/architecture/2026-07-27-session-projection-and-command-log.md);驱动、缓存与变更流约定:[包 README](../../packages/session/session-projection/README.md)。
|
||||
|
||||
源码:[`packages/session/session-projection/src/index.ts`](../../packages/session/session-projection/src/index.ts)
|
||||
|
||||
@@ -90,7 +90,7 @@ type ProjectionChangeListener = (
|
||||
|
||||
## 注册表:`ctx.sessionProjections`
|
||||
|
||||
`SessionProjectionRegistry`([签名](#ctxsessionprojections--sessionprojectionregistry))拥有驱动权:一份 `session/event` 订阅、对每个已注册单元的正向 `apply`,以及每会话每单元的水位线(watermark)cell。cell 惰性构建:在事件流过之后才注册的单元,或比注册表更早的会话,都在首次触达(事件或读取)时从 `init` 出发在内存日志上折叠。注册是一个 effect,其 disposer 随调用方 fiber 走:领域插件卸载后,其 key(连同缓存的 cell)从后续驱动与快照中消失,客户端将其读作能力缺失;key 重复直接 throw。领域插件在 `ctx.inject(['sessionProjections'], …)` 下注册,因此不带注册表的 headless 组装完全不受影响。
|
||||
`SessionProjectionRegistry`([签名](#ctxsessionprojections--sessionprojectionregistry))拥有驱动权:一份 `session/event` 订阅、对每个已注册单元即时调用 `apply`,以及每会话每单元的水位线(watermark)cell。cell 惰性构建:在事件流过之后才注册的单元,或比注册表更早的会话,都在首次触达(事件或读取)时从 `init` 出发在内存日志上折叠。注册是一个 effect,其 disposer 随调用方 fiber 走:领域插件卸载后,其 key(连同缓存的 cell)从后续驱动与快照中消失,客户端将其读作能力缺失;key 重复直接 throw。领域插件在 `ctx.inject(['sessionProjections'], …)` 下注册,因此不带注册表的 headless 组装完全不受影响。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/session-query.md
|
||||
session-query.md: cd21b4c01dc70e0a1e5c498f321694472aad77d9
|
||||
session-query.zh.md: 3f18cda064ecf5fbaa635cc0fd768596c74cfeb1
|
||||
session-query.zh.md: a00cb72b7d11c64af04b16f9e8fe19a110ed01c4
|
||||
|
||||
@@ -258,7 +258,7 @@ type SessionLineageTrace = {
|
||||
|
||||
## 有界事件读取
|
||||
|
||||
请求指定一个原始 seq 及可选的邻近数量。结果携带 `SessionHeader` 而非可用性标志,使已知的实时目标可以独立于持久化健康状态。
|
||||
请求指定一个原始 seq 及可选的邻近数量。结果携带 `SessionHeader` 而非可用性标志,使已知的 live 目标可以独立于持久化健康状态。
|
||||
|
||||
```ts type-equiv
|
||||
/** Request for one event plus raw neighboring log context. */
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/session.md
|
||||
session.md: bdedaeb67434fb39f9f8e4925d3e62c3024bbd93
|
||||
session.zh.md: 7756ee772363f5f2759ec0e8826201fdfb49d459
|
||||
session.zh.md: 6b06d67ff0caf456da114fdff0c0bf24e8f41684
|
||||
|
||||
@@ -308,7 +308,7 @@ interface SurfaceIntent {
|
||||
}
|
||||
```
|
||||
|
||||
对 `SurfaceEventType` 事件必填:每个产生消息的事件都必须声明它如何加入 surface(派生模型历史的唯一来源)。面向人类的记录(transcript)是另一个投影,读取的是日志中追加来源的事件,因为 surface 会有意遮蔽替换所概括的范围(见 [dsh-session](../../packages/core/session/README.md) 的 `isAppendSurfaceEvent`)。非 surface 类型在编译期拒绝此参数。
|
||||
对 `SurfaceEventType` 事件必填:每个产生消息的事件都必须声明它如何加入 surface(派生模型历史的唯一来源)。面向人类的 transcript(文本记录)是另一个投影,读取的是日志中追加来源的事件,因为 surface 会有意遮蔽替换所概括的范围(见 [dsh-session](../../packages/core/session/README.md) 的 `isAppendSurfaceEvent`)。非 surface 类型在编译期拒绝此参数。
|
||||
|
||||
只有 `assistant/message` 可以携带存在但为空的 `sourceEventSeqs`;字段不存在时,该事件没有记录这条消息由哪些早期事件产生,但提供方仍可能发出过分片。
|
||||
|
||||
@@ -527,7 +527,7 @@ declare class Session {
|
||||
- `tool/result` → 一条携带 `tool-result` 块的 user 消息。
|
||||
- `user/message`(注入上下文,即非 `user` 来源)→ 按时间顺序在相应位置生成一条 user-role 消息,并原样承载其 `content`;其类型化 source 标明生产方,并携带所有生产方专用数据。
|
||||
|
||||
其余所有事件(`turn/*`、`step/*`、插件所有的 `llm/retry`)均为结构信息,不会投影为消息。token 记账读取每个步骤的 `assistant/chunk { type: 'usage' }` 记录;如果没有用量分片,则将 `assistant/message.usage` 作为已提交步骤的后备。失败的模型请求尝试没有 assistant 消息,因此其用量分片是持久化的记账记录。由于这一尚未发布的格式有意不提供兼容性承诺,seed/load 校验会拒绝没有提供方/模型的请求头和 assistant 消息,而不会猜测历史数据应走的提供方路由。
|
||||
其余所有事件(`turn/*`、`step/*`、插件所属的 `llm/retry`)均为结构信息,不会投影为消息。token 记账读取每个步骤的 `assistant/chunk { type: 'usage' }` 记录;如果没有用量分片,则将 `assistant/message.usage` 作为已提交步骤的后备。失败的模型请求尝试没有 assistant 消息,因此其用量分片是持久化的记账记录。由于这一尚未发布的格式有意不提供兼容性承诺,seed/load 校验会拒绝没有提供方/模型的请求头和 assistant 消息,而不会猜测历史数据应走的提供方路由。
|
||||
|
||||
## 活跃会话 fork API
|
||||
|
||||
@@ -541,7 +541,7 @@ declare class Session {
|
||||
|
||||
## 轮次的结束原因:`TurnEndReasonMap`
|
||||
|
||||
`turn/start` 没有 trigger 字段。返回 enter 的 pre-step 所产生的 `user/message` 批次记录进入轮次的内容,`llm/retry` 记录请求恢复,idle 注入则保持待处理,直到唤醒交付抵达后续 pre-step。实时轮次会保留停止驱动器的类型化 [`AgentCancelCause`](core.md#the-agent-handle);只有在导入受支持的粗粒度取消记录且记录未保存调用方时,持久化才使用额外的 `{ kind: 'legacy' }` 原因。
|
||||
`turn/start` 没有 trigger 字段。已进入的 `user/message` 批次记录进入每个步骤的内容,`llm/retry` 记录请求恢复,idle 注入则保持待处理,直到唤醒交付抵达后续 pre-step。实时轮次会保留停止驱动器的类型化 [`AgentCancelCause`](core.md#the-agent-handle);只有在导入受支持的粗粒度取消记录且记录未保存调用方时,持久化才使用额外的 `{ kind: 'legacy' }` 原因。
|
||||
|
||||
```ts type-equiv
|
||||
/** Durable cancellation cause, including imports whose original coarse record carried no cause. */
|
||||
@@ -578,7 +578,7 @@ interface TurnEndReasonMap {
|
||||
|
||||
## 执行封闭与独立事件
|
||||
|
||||
一个轮次包围一次模型循环执行,而不是整个会话日志。AgentLoop 只会从轮次内返回 enter 的 pre-step 批次记录注入的 `user/message` 事件;插件所属的纯日志事件仍可出现在 `turn/end` 与下一个 `turn/start` 之间,占用事件 seq 但不递增轮次编号。持久化会将每个连续且已接受的事件纳入有界持久化批次,而崩溃修复只关闭确实仍处于开放状态的尾部轮次。需要即时持久性屏障的生产方会显式等待 `ctx.sessions.flush(session)`。
|
||||
一个轮次包围一次模型循环执行,而不是整个会话日志。AgentLoop 只会在轮次内进入 pre-step 批次时记录注入的 `user/message` 事件;插件所属的纯日志事件仍可出现在 `turn/end` 与下一个 `turn/start` 之间,占用事件 seq 但不递增轮次编号。持久化会将每个连续且已接受的事件纳入有界持久化批次,而崩溃修复只关闭确实仍处于开放状态的尾部轮次。需要即时持久性屏障的生产方会显式等待 `ctx.sessions.flush(session)`。
|
||||
|
||||
可选的 `dsh-session/invariant` 配套插件会强制核心拥有的关系:轮次与步骤编号、执行事件封闭,以及同一步骤内的工具调用/结果配对。可合并扩展事件的关系由声明它的插件拥有,因此核心不会仅因没有开放轮次就拒绝未知事件。见[独立事件决策](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md)。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/settings.md
|
||||
settings.md: 04360b2c332c37e6602bf792a28b77f6755a5b34
|
||||
settings.zh.md: 206c305059126c5cf95abfc1980214352b9ce840
|
||||
settings.zh.md: 2d269c77b8f5924221f740995acf44dae7fb1c9b
|
||||
|
||||
@@ -125,7 +125,7 @@ interface SettingsDescriptor {
|
||||
}
|
||||
```
|
||||
|
||||
只持有脱敏 descriptor 的调用方无法安全地重建分节,因此删除改以路径 op 传递。每个 descriptor 还携带针对原始分节的 `revision`;写入可以把它作为 `expectedRevision` 送回,不再匹配的写入会被拒绝,而不是覆盖在先落地的那个写方之上。
|
||||
只持有脱敏 descriptor 的调用方无法安全地重建分节,因此删除改以路径 op 传递。每个 descriptor 还携带针对原始分节的 `revision`;写入可以把它作为 `expectedRevision` 送回,不再匹配的写入会被拒绝,而不会覆盖先落地的写入。
|
||||
```ts type-equiv
|
||||
/**
|
||||
* One path-addressed edit to a namespace's user section. Path mutation exists
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/skills.md
|
||||
skills.md: 05cc0575544a66f48f85df316e3b35a2e1a55b25
|
||||
skills.zh.md: fc09aa970a7707ee6169512da48ff33cbcea9125
|
||||
skills.zh.md: b5deedd2b4aa5f22e9eae979591699907670b15c
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
`ctx.skills` 组合本地、内嵌、远程或其他提供方。注册是同步的;远程初始化与发现属于 `list()` 的 await 阶段。提供方对象、选项与候选项以只读方式借用,语义字段会被校验。
|
||||
|
||||
注册表采用宿主 + 按 scope 的分层结构,即[工具注册表](tools.md)在 [dsh-scope](../../packages/core/scope) 之上确立的形态:注册会落入调用方上下文 scope 对应的层——宿主行与 repository 插件落入全局层,由 agent preset 常驻组合挂载的插件落入该 preset 的层——提供方名称在每层内唯一,而非进程级唯一。读取时将全局层与观察 scope 的链合并:最近层的条目直接赢得重名 skill,下文的 rank 顺序只在单层内裁决重名。发现缓存以解析后的 scope 链为键,因此重设 scope 父级(空会话重组)无需注册表变更即可被下一次读取看到。
|
||||
注册表采用宿主 + 按 scope 的分层结构,即[工具注册表](tools.md)在 [dsh-scope](../../packages/core/scope) 之上确立的形态:注册会落入调用方上下文 scope 对应的层——宿主行与 repository 插件落入全局层,由 agent(智能体) preset 常驻组合挂载的插件落入该 preset 的层——提供方名称在每层内唯一,而非进程级唯一。读取时将全局层与观察 scope 的链合并:最近层的条目直接赢得重名 skill,下文的 rank 顺序只在单层内裁决重名。发现缓存以解析后的 scope 链为键,因此重设 scope 父级(空会话重组)无需注册表变更即可被下一次读取看到。
|
||||
|
||||
在单层内,重名项依次按 rank、提供方顺序和本地顺序确定优先级;摘要按名称排序。提供方的 `list()` 被拒绝时,系统会记录日志,并从不完整观测中省略该提供方的结果;显式的不完整观测会提供可用候选项,但不会使结果变得可缓存;格式错误的候选项快速失败。每个提供方工厂都会接收一项注册作用域内的控制能力;仅当该精确注册仍处于活动状态时,其 `invalidate()` 才会清除已完成目录;注册失败或释放时,其信号会中止。若提供方代次在发现进行期间发生变化,该发现会重试一次;若再次变化,则返回最新候选项,并将结果标为不完整且不予缓存。提供方和运行时变更会发出不带过滤条件的 `skills/change` 失效事件;该事件不携带 diff,因此消费方会使用自身的查找选项重新获取 `snapshot()`。
|
||||
在单层内,重名项依次按 rank、提供方顺序和本地顺序确定优先级;摘要按名称排序。提供方的 `list()` 被拒绝时,系统会记录日志,并从不完整观测中省略该提供方的结果;显式的不完整观测会提供可用候选项,但不会使结果变得可缓存;格式错误的候选项快速失败。每个提供方工厂都会接收一项注册作用域内的控制能力;仅当该精确注册仍处于活动状态时,其 `invalidate()` 才会清除已完成目录;注册失败或 dispose(资源释放)时,其信号会中止。若提供方代次在发现进行期间发生变化,该发现会重试一次;若再次变化,则返回最新候选项,并将结果标为不完整且不予缓存。提供方和运行时变更会发出不带过滤条件的 `skills/change` 失效事件;该事件不携带 diff,因此消费方会使用自身的查找选项重新获取 `snapshot()`。
|
||||
|
||||
`SkillProvider.list()` 返回的数组是完整发现的简写形式。`SkillProviderObservation` 允许提供方公开仍可直接加载的候选项,同时报告该观测不具权威性。
|
||||
|
||||
@@ -78,7 +78,7 @@ interface SkillProviderControl {
|
||||
|
||||
`dsh-skill-badge` 在 `BUNDLED_SKILL_RANK` 注册一个不可变的 `bundled` 候选项,并通过 `resourceBase` 公开其随包资产目录。交付的 CLI(命令行界面)将该插件声明为禁用,因此启用其组合配置行即为显式选择加入。
|
||||
|
||||
Chokidar 会监视现有根目录中直属 bundle 和平铺条目的添加与移除,以及直属 skill 条目的变更。缺失的根目录会从最近的现有祖先开始,逐个跟踪缺失路径段,直至 Chokidar 可以附加。bundle 下的资源文件变更不属于目录变更。面向模型的 `write` 和 `edit` 观测会在目标路径相关时同步使提供方目录失效,而宿主 watcher 覆盖 IDE、Git、shell 和外部进程产生的变更。watcher 失败会使当前观测不完整,但不会在直接加载时隐藏可读候选项;项目作用域 watcher 使用按配置设限的 LRU。
|
||||
Chokidar 会监视现有根目录中直属 bundle 和平铺条目的添加与移除,以及直属 skill 条目的变更。缺失的根目录会从最近的现有祖先开始,逐个跟踪缺失路径段,直至 Chokidar 可以附加。bundle 下的资源文件变更不属于目录变更。面向模型的 `write` 和 `edit` 观测会在目标路径与目录相关时同步使提供方目录失效,而宿主 watcher 覆盖 IDE、Git、shell 和外部进程产生的变更。watcher 失败会使当前观测不完整,但不会在直接加载时隐藏可读候选项;项目作用域 watcher 使用按配置设限的 LRU。
|
||||
|
||||
## skill 身份
|
||||
|
||||
@@ -189,7 +189,7 @@ type SkillRegistration = Omit<SkillDefinition, 'invocation' | 'provider'> & {
|
||||
|
||||
## 查找与配置
|
||||
|
||||
skill 查找对 cwd 敏感,因为提供方可能暴露工作区本地的 skill;可选的 signal 为调用方取消提供方的工作。注册表读取还通过 `SkillViewOptions` 携带观察 scope——消费方传入调用中的 agent,agent 本身就是自己的 scope key;注册表消费 `scope` 做层选择,提供方只从同一个借用的选项对象中读取其 `SkillLookupOptions` 契约。取消在目录选择前后(包括缓存命中时)都会检查,并与发现和完整定义加载竞争。如果找不到 git root,本地提供方将所提供的 cwd 本身视为项目根目录。
|
||||
skill 查找对 cwd 敏感,因为提供方可能暴露工作区本地的 skill;可选的 signal 为调用方取消提供方的工作。注册表读取还通过 `SkillViewOptions` 携带观察 scope——消费方传入调用中的 agent,agent 本身就是自己的 scope key;注册表消费 `scope` 做层选择,提供方只从同一个借用的选项对象中读取其 `SkillLookupOptions` 约定。取消在目录选择前后(包括缓存命中时)都会检查,并与发现和完整定义加载竞争。如果找不到 git root,本地提供方将所提供的 cwd 本身视为项目根目录。
|
||||
|
||||
注册表不缓存完整定义。每次调用 `get()` 都会携所选候选项调用胜出提供方,因此本地提供方会重新读取当前正文。名称与该候选项不再匹配的定义会被拒绝,并使该提供方实例失效以便重新发现。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/spill.md
|
||||
spill.md: 37fd446deb7da94eb09d3f7d198f9f9e44e7856d
|
||||
spill.zh.md: e07a16ce3d7b2d94a0a2448b09a20c66e83c1830
|
||||
spill.zh.md: 5e8007e6295da7bf25a5678e28c7e720c32dee23
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 落盘存储
|
||||
# spill 存储
|
||||
|
||||
[English](spill.md) | 中文
|
||||
|
||||
落盘存储 seam 是一项[能力 seam](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md),它持久保存工具的超大文本,并返回面向模型的定位符与检索指引;该能力拆分到三个包:Service Definition([dsh-spill](../../packages/spill/spill),`ctx.spillStore`)、Service provider([dsh-spill-local](../../packages/spill/spill-local),宿主文件系统中会话作用域的私有文件)和 Consumer([dsh-spill-policy](../../packages/spill/spill-policy),`tools/post-execute` 策略)。落盘是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇记录在此处,而不在 [core.md](core.md) 中。预览机制仍归 [dsh-retention](../../packages/util/retention) 所有;该 seam 只保存策略交给它的最终文本。
|
||||
spill 存储 seam 是一项[能力 seam](../../.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.md),它持久保存工具的超大文本,并返回面向模型的定位符与检索指引;该能力拆分到三个包:Service Definition([dsh-spill](../../packages/spill/spill),`ctx.spillStore`)、Service provider([dsh-spill-local](../../packages/spill/spill-local),宿主文件系统中会话作用域的私有文件)和 Consumer([dsh-spill-policy](../../packages/spill/spill-policy),`tools/post-execute` 策略)。spill 是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇记录在此处,而不在 [core.md](core.md) 中。预览机制仍归 [dsh-retention](../../packages/util/retention) 所有;该 seam 只保存策略交给它的最终文本。
|
||||
|
||||
源码:[`packages/spill/spill/src/types.ts`](../../packages/spill/spill/src/types.ts)
|
||||
|
||||
@@ -38,7 +38,7 @@ interface SpillOwner {
|
||||
}
|
||||
```
|
||||
|
||||
`SpillOwner.sessionId` 是保存时的存储命名空间。fork 后的会话会从种子日志继承已有的落盘定位符;这些产物不会被复制或重新取得所有权,fork 后产生的落盘则使用子会话 id。保留期清理可以连同其他旧会话产物一起使旧定位符失效;落盘 seam 不定义逐会话的清理策略。
|
||||
`SpillOwner.sessionId` 是保存时的存储命名空间。fork 后的会话会从种子日志继承已有的 spill 定位符;这些产物不会被复制或重新取得所有权,fork 后产生的 spill 则使用子会话 id。保留期清理可以连同其他旧会话产物一起使旧定位符失效;spill seam 不定义逐会话的清理策略。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -82,7 +82,7 @@ type SpillLocator = Branded<'SpillLocator'>
|
||||
|
||||
`SpillStore`(`ctx.spillStore`,定义于 [`packages/spill/spill/src/index.ts`](../../packages/spill/spill/src/index.ts))是只有一个方法的抽象服务:`saveText(input) → Promise<SpillRef>`。它持久保存完整的 `content`,并在实际存储失败(权限、ENOSPC、后端不可用)时拒绝。该 seam 只负责存储:不负责保留策略、工具结果替换或检索/搜索 API。
|
||||
|
||||
本地后端([dsh-spill-local](../../packages/spill/spill-local))写入 `<root>/session-<hash>/<random>-<safeName>`:根目录是已配置或延迟创建的私有(0700)目录,会话子目录采用 `sha256(sessionId)`,并通过排他的仅所有者可访问写入(`open(path, 'wx', 0o600)`)防止预先植入的符号链接重定向写入。其 `locator` 是本地路径,`retrievalHint` 则告知模型在该路径上使用 `read` 或 `grep`。策略消费方([dsh-spill-policy](../../packages/spill/spill-policy))会把超过 `maxInlineBytes` 的纯文本最终结果替换为保留库生成的首尾预览和落盘引用;该过程尽力而为:保存失败时保留原始内联结果,而不会把成功的调用变成 `isError`。
|
||||
本地后端([dsh-spill-local](../../packages/spill/spill-local))写入 `<root>/session-<hash>/<random>-<safeName>`:根目录是已配置或延迟创建的私有(0700)目录,会话子目录采用 `sha256(sessionId)`,并通过排他且仅所有者可访问的写入(`open(path, 'wx', 0o600)`)防止预先植入的符号链接重定向写入。其 `locator` 是本地路径,`retrievalHint` 则告知模型在该路径上使用 `read` 或 `grep`。策略消费方([dsh-spill-policy](../../packages/spill/spill-policy))会把超过 `maxInlineBytes` 的纯文本最终结果替换为保留库生成的首尾预览和 spill 引用;该过程尽力而为:保存失败时保留原始内联结果,而不会把成功的调用变成 `isError`。
|
||||
|
||||
<!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/storage.md
|
||||
storage.md: 33933f70176e1f4960eb423f9a1067f50b6762e4
|
||||
storage.zh.md: a1925306f322bcead7b1e303d45f194eaec1cfd2
|
||||
storage.zh.md: 2ab3473a83b7588f186ce8f3b83819b29985ae88
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
[English](storage.md) | 中文
|
||||
|
||||
存储子系统持久保存一切不属于会话事件日志的数据(会话日志有自己的 seam——见 [persistence.md](persistence.md))。它是一项可选能力,不属于 agent loop(智能体循环)主干,并按[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md)拆分:枢纽(hub)与 Service Definition([dsh-storage](../../packages/storage/storage),`ctx.storage`)、Service provider(注册为 `json` 的 [dsh-storage-json](../../packages/storage/storage-json) 与注册为 `sqlite` 的 [dsh-storage-sqlite](../../packages/storage/storage-sqlite)),以及 Consumer 数据形式([dsh-storage-domain](../../packages/storage/storage-domain),`ctx.storageDomain`,也可经 `ctx.storage.domain` 访问)——它是后端约定的唯一 Consumer,也是其他一切所使用的类型化 API。枢纽自身不做任何 IO:后端拥有介质,数据形式拥有语义,产品包绝不直接触碰后端。设计记录:[领域 KV 存储 Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)。
|
||||
存储子系统持久保存一切不属于会话事件日志的数据(会话日志有自己的 seam——见 [persistence.md](persistence.md))。它是一项可选能力,不属于 agent loop(智能体循环)主干,并按[能力 seam](../../.agents/notes/implemented/architecture/2026-06-13-capability-seams.md) 拆分:枢纽(hub)与 Service Definition([dsh-storage](../../packages/storage/storage),`ctx.storage`)、Service provider(注册为 `json` 的 [dsh-storage-json](../../packages/storage/storage-json) 与注册为 `sqlite` 的 [dsh-storage-sqlite](../../packages/storage/storage-sqlite)),以及 Consumer 数据形式([dsh-storage-domain](../../packages/storage/storage-domain),`ctx.storageDomain`,也可经 `ctx.storage.domain` 访问)——它是后端约定的唯一 Consumer,也是其他一切所使用的类型化 API。枢纽自身不做任何 IO:后端拥有介质,数据形式拥有语义,产品包绝不直接触碰后端。设计记录:[领域 KV 存储 Agent Note](../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md)。
|
||||
|
||||
源码:[`packages/storage/storage/src/backend.ts`](../../packages/storage/storage/src/backend.ts) · [`packages/storage/storage-domain/src/spec.ts`](../../packages/storage/storage-domain/src/spec.ts) · [`packages/storage/storage-domain/src/events.ts`](../../packages/storage/storage-domain/src/events.ts)
|
||||
|
||||
## 枢纽:`ctx.storage`
|
||||
|
||||
`Storage`([签名](#ctxstorage--storage))是汇合点,不是存储本体。`ctx.storage.backend` 是一张名称 → 后端的表:多个后端并排保持挂载,哪个后端服务哪个消费方由该消费方自己的配置决定(即领域层的路由表),绝不是枢纽全局的选择。`register(name, backend)` 返回 disposer;重复名称与查找未知名称都抛出 `StorageError`。dispose(资源释放)只注销名称——由拥有插件在注销之后自行关闭后端。每个后端插件还会发布一个仅用于生命周期的服务键(`storageBackendServiceKey(name)`),数据形式提供方注入它,使自身激活不会与后端注册发生竞态。
|
||||
`Storage`([签名](#ctxstorage--storage))是汇合点,不是存储本体。`ctx.storage.backend` 是一张名称 → 后端的表:多个后端并排保持挂载,哪个后端服务哪个消费方由该消费方自己的配置决定(即领域层的路由表),绝不是枢纽全局的选择。`register(name, backend)` 返回 disposer;重复名称与查找未知名称都抛出 `StorageError`。dispose(资源释放)只注销名称——由拥有它的插件在注销之后自行关闭后端。每个后端插件还会发布一个仅用于生命周期的服务键(`storageBackendServiceKey(name)`),数据形式提供方注入它,使自身激活不会与后端注册发生竞态。
|
||||
|
||||
数据形式以一张可合并扩展的键 map 挂载到枢纽上:
|
||||
|
||||
@@ -44,7 +44,7 @@ interface StorageBackend {
|
||||
}
|
||||
```
|
||||
|
||||
一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并提供可选的操作组;目前 `kv` 是唯一一组。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、格式版本、表名清单,以及是否存在全局单例槽位——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。介质上记录的版本与之不同时拒绝 `version-mismatch`;无法按该 unit 解析的介质拒绝 `malformed-medium`(不做迁移:预发布立场)。[`backend.ts`](../../packages/storage/storage/src/backend.ts) 是逐条款的规范性约定,[`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) 中的共享一致性套件会针对每个后端检查每项条款。[json 后端](../../packages/storage/storage-json/README.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](../../packages/storage/storage-sqlite/README.md)在单个数据库中每行存储一份文档,用于频繁更新的数据。
|
||||
一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并提供可选的操作组;目前 `kv` 是唯一一组。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、格式版本、表名清单,以及是否存在全局单例 slot——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。介质上记录的版本与之不同时拒绝 `version-mismatch`;无法按该 unit 解析的介质拒绝 `malformed-medium`(不做迁移:预发布立场)。[`backend.ts`](../../packages/storage/storage/src/backend.ts) 是逐条款的规范性约定,[`tests/contract.ts`](../../packages/storage/storage/tests/contract.ts) 中的共享一致性套件会针对每个后端检查每项条款。[json 后端](../../packages/storage/storage-json/README.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](../../packages/storage/storage-sqlite/README.md)在单个数据库中每行存储一份文档,用于频繁更新的数据。
|
||||
|
||||
## 声明领域
|
||||
|
||||
@@ -64,7 +64,7 @@ interface DomainSpec {
|
||||
}
|
||||
```
|
||||
|
||||
`defineDomain(spec)` 固定 spec 的字面量类型,并在拥有方的模块加载时、任何介质被触碰之前就大声失败:领域名或表名不匹配 `UNIT_NAME_RE`、版本不是非负整数、global schema 接受 `null`,这些都会抛出(`null` 是介质的「从未写入」哨兵值,可空的 global 一旦存储就无法往返还原)。`domainTable<K, V>(schema)` 声明一张表,其键类型是仅存在于编译期的 phantom 类型(通常是[品牌化 id](core.md#branded-ids));`descriptorOf(spec)` 投影出面向后端的 unit 描述符。
|
||||
`defineDomain(spec)` 固定 spec 的字面量类型,并在拥有方的模块加载时、任何介质被触碰之前就明确报错:领域名或表名不匹配 `UNIT_NAME_RE`、版本不是非负整数、global schema 接受 `null`,这些都会抛出(`null` 是介质的「从未写入」哨兵值,可空的 global 一旦存储就无法往返还原)。`domainTable<K, V>(schema)` 声明一张表,其键类型是仅存在于编译期的 phantom 类型(通常是[品牌化 id](core.md#branded-ids));`descriptorOf(spec)` 投影出面向后端的 unit 描述符。
|
||||
|
||||
## 打开的领域
|
||||
|
||||
@@ -95,7 +95,7 @@ interface Domain<S extends DomainSpec> {
|
||||
}
|
||||
```
|
||||
|
||||
读取是同步的,来自权威的内存态:`KvTable` 暴露 `get`/`entries`/`keys`/`size`(快照迭代器,在排队写入落地期间保持稳定),global 句柄的 `get()` 在第一次 `set` 将槽位物化到介质之前一直返回 spec 的 `initial`。每次写入——`put`、`delete`、`update`、`global.set`——都在同一条逐领域写链上排队,先到达后端持久性,再更新内存,最后发出 `domain/changed`;后端写入被拒时内存原样不动,因此读取绝不会偏离介质。`update(key, fn)` 在其写链槽位上是一次原子的读-改-写(键缺失时拒绝 `missing-key`);`delete` 一个不存在的键 resolve 为 `false`,不产生写入也不产生事件。返回的记录就是存储的对象本身,不是副本——请经 `put`/`update` 整体替换,绝不要就地修改。
|
||||
读取是同步的,来自权威的内存态:`KvTable` 暴露 `get`/`entries`/`keys`/`size`(快照迭代器,在排队写入落地期间保持稳定),global 句柄的 `get()` 在第一次 `set` 将 slot 物化到介质之前一直返回 spec 的 `initial`。每次写入——`put`、`delete`、`update`、`global.set`——都在同一条逐领域写链上排队,先在后端完成持久化,再更新内存,最后发出 `domain/changed`;后端写入被拒时内存原样不动,因此读取绝不会偏离介质。`update(key, fn)` 在其写链 slot 上是一次原子的读-改-写(键缺失时拒绝 `missing-key`);`delete` 一个不存在的键 resolve 为 `false`,不产生写入也不产生事件。返回的记录就是存储的对象本身,不是副本——请经 `put`/`update` 整体替换,绝不要就地修改。
|
||||
|
||||
## 领域 facility:`ctx.storageDomain`
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/subagent.md
|
||||
subagent.md: 647b1034eae6f305c50113dab7f20e4797a7dc9e
|
||||
subagent.zh.md: e3a2a10489a1bd72f62f94b128593f1e03446319
|
||||
subagent.zh.md: 37b52c8635de434ef4a1d997e1807516c7c87e60
|
||||
|
||||
@@ -307,7 +307,7 @@ type SubagentDescendantListEntry = SubagentListEntry & {
|
||||
|
||||
## 终态结果:`SubagentResult`
|
||||
|
||||
一次 run 的最终产出,由 `SubagentRun.result` resolve。`structured` 仅在请求了 `outputSchema` 且成功满足时才存在;请求 schema 不保证一定能得到它,当子 agent 失败或结束时未产出有效 capture 时,提供方可能返回 `stopReason: 'error'`。非 `completed` 的 `stopReason` 意味着 `output` 可能不完整——消费方将其映射为 `isError` 的工具结果,而非将部分输出报告为成功。
|
||||
单次 run 的最终产出,由 `SubagentRun.result` resolve。`structured` 仅在请求了 `outputSchema` 且成功满足时才存在;请求 schema 不保证一定能得到它,当子 agent 失败或结束时未产出有效 capture 时,提供方可能返回 `stopReason: 'error'`。非 `completed` 的 `stopReason` 意味着 `output` 可能不完整——消费方将其映射为 `isError` 的工具结果,而非将部分输出报告为成功。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/subprocess.md
|
||||
subprocess.md: 456156a687474ae49619cd8a1f4b5179c27ad01d
|
||||
subprocess.zh.md: 97c6604c1c600dae301d3f34b50c93cc6f5a7c7d
|
||||
subprocess.zh.md: 35fa15a63f1dbc8fb36bc3cd002b80f4ab7b8092
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](subprocess.md) | 中文
|
||||
|
||||
进程管理器 seam 分为 Service Definition([dsh-subprocess](../../packages/subprocess/subprocess),`ctx.subprocess`)与 Service provider([dsh-subprocess-local](../../packages/subprocess/subprocess-local));它的 Consumer 是其他能力 seam 与进程外后端:[bash 执行器家族](bash.md)使用收集模式的批量输出,LSP 使用原始协议管道,PTY 后端使用终端原语,ACP(Agent Client Protocol)subagent 后端则使用管道化 ndjson 加 inherit 的 stderr。该 seam 拥有受管的 `DSH_*` 环境命名空间、共享的凭据清除(`scrubbedParentEnv`)与 `CollectedOutput` 形状;[dsh-bash](../../packages/bash/bash) 重导出这套词汇,使 bash 消费方保持单一导入入口。
|
||||
子进程 seam 分为 Service Definition([dsh-subprocess](../../packages/subprocess/subprocess),`ctx.subprocess`)与 Service provider([dsh-subprocess-local](../../packages/subprocess/subprocess-local));它的 Consumer 是其他能力 seam 与进程外后端:[bash 执行器家族](bash.md)使用收集模式的批量输出,LSP 使用原始协议管道,PTY 后端使用终端原语,ACP(Agent Client Protocol)subagent 后端则使用通过管道传输的 ndjson,并让 stderr 采用 inherit。该 seam 拥有受管的 `DSH_*` 环境命名空间、共享的凭据清除(`scrubbedParentEnv`)与 `CollectedOutput` 形状;[dsh-bash](../../packages/bash/bash) 重导出这套词汇,使 bash 消费方保持单一导入入口。
|
||||
|
||||
源码:[`packages/subprocess/subprocess/src/types.ts`](../../packages/subprocess/subprocess/src/types.ts) 与 [`packages/subprocess/subprocess/src/index.ts`](../../packages/subprocess/subprocess/src/index.ts)
|
||||
|
||||
@@ -131,7 +131,7 @@ interface SubprocessSpawnSpec {
|
||||
|
||||
## 句柄:流、读取器与以进程树为范围的终止
|
||||
|
||||
spawn 会立即返回一个活动句柄。收集模式的读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;管道化的流归调用方所有。终止在每个平台上都以进程树为范围:`terminate()`(唯一的终止动词)执行 SIGTERM→宽限期→SIGKILL 升级,`waitForExit()` 观察整棵进程树。这足以让消费方构建自己的拆卸阶梯;ACP 后端的 `disposeAcpChild` 以 stdin EOF 开始,即为仓库内模板。
|
||||
spawn 会立即返回一个活动句柄。收集模式的读取器接受全流字节偏移量且从不消费,因此独立的读取器不会抢走彼此的增量;管道化的流归调用方所有。终止在每个平台上都以进程树为范围:`terminate()`(唯一的终止动词)执行 SIGTERM→宽限期→SIGKILL 升级,`waitForExit()` 观察整棵进程树。这足以让消费方构建自己的分级清理流程;ACP 后端的 `disposeAcpChild` 会先关闭 stdin,让子进程收到 EOF,是仓库内的参考实现。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/tasks.md
|
||||
tasks.md: 37807bc446f607c3e1635a49432670e86760f2a4
|
||||
tasks.zh.md: 39014acb13f6051431a40ae084c1e03298ae8993
|
||||
tasks.zh.md: 420c133f768d1b48cb5e3ffee3a5f35e46a313f4
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](tasks.md) | 中文
|
||||
|
||||
长时间运行的生产方、`ctx.tasks` 与任务控制命令共用的类型。[运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)负责设计;本页记录 [`packages/tasks/tasks/src/types.ts`](../../packages/tasks/tasks/src/types.ts) 中的确切字段和变体。
|
||||
长时间运行的生产方、`ctx.tasks` 与任务控制命令共用的类型。[运行时 Agent Note](../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md) 负责设计;本页记录 [`packages/tasks/tasks/src/types.ts`](../../packages/tasks/tasks/src/types.ts) 中的确切字段和变体。
|
||||
|
||||
## ID 与状态
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/telemetry.md
|
||||
telemetry.md: 30d0497fd69a2f7b0f0fa77a040c11e0ee338d53
|
||||
telemetry.zh.md: de4415c1a2fc9d36e6abf4bb3207ba1e95f990ea
|
||||
telemetry.zh.md: 39688a8e427350149f5c77164a4682bc09d60024
|
||||
|
||||
@@ -54,7 +54,7 @@ interface TelemetryRecord {
|
||||
}
|
||||
```
|
||||
|
||||
每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此导出流中的 `seq` 缺口是常态,绝不是丢失信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(无游标的重新接管、SDK 重试),因此接收端对 ledger 记录基于 `(session.id, event.seq)` 去重;ops 记录刻意省略这类标识——它们是用于告警的信号,而非用于累加的条目,重复被容忍而非被去重。
|
||||
每个 `(turn, step)` 只发出第一条 `assistant/chunk`,即「流已开始」的信号;其余分片在捕获时丢弃,因此传输中的 `seq` 缺口是常态,绝不是数据丢失的信号。其他所有[会话事件](session.md)类型都会完整透传,包括该 seam 从未听说过、由插件合并进来的事件类型。投递是尽力而为的:游标标记的是「已交接」而非「已送达」,记录可能丢失(崩溃、重载窗口)也可能重复(无游标的重新接管、SDK 重试),因此接收端对 ledger 记录基于 `(session.id, event.seq)` 去重;ops 记录刻意省略这类标识——它们是用于告警的信号,而非用于累加的条目,重复被容忍而非被去重。
|
||||
|
||||
## 共享披露
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/tools.md
|
||||
tools.md: 60f982e1934a37f80236382141ebc54fdef5d7b4
|
||||
tools.zh.md: 07e5e5729bfb8329b2696c64c25b852cb8a91350
|
||||
tools.zh.md: d1cea35ee5753389cdedd62b753d765db7e20ee2
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](tools.md) | 中文
|
||||
|
||||
[dsh-tools](../../packages/core/tools) 的工具处理流程。[core.md](core.md) 介绍了核心包共用的流程编写类型 `ToolDefinition`;面向模型的 [`ToolSchema`](llm-streaming.md#the-model-request-and-result) 协议类型与模型请求一起声明。本页记录 `ToolDefinition` 的每个字段、用于构建它的类型化 schema DSL、带守卫的执行类型和 UI 展示类型。
|
||||
[dsh-tools](../../packages/core/tools) 的工具流水线。[core.md](core.md) 介绍了核心包共用、用于编写流水线的类型 `ToolDefinition`;面向模型的 [`ToolSchema`](llm-streaming.md#the-model-request-and-result) 协议类型与模型请求一起声明。本页记录 `ToolDefinition` 的每个字段、用于构建它的类型化 schema DSL、带守卫的执行类型和 UI 展示类型。
|
||||
|
||||
源码:[`packages/core/tools/src/index.ts`](../../packages/core/tools/src/index.ts) · [`packages/core/tools/src/schema.ts`](../../packages/core/tools/src/schema.ts) · [`packages/core/tools/src/presentation.ts`](../../packages/core/tools/src/presentation.ts)
|
||||
|
||||
@@ -146,7 +146,7 @@ type InferValue<S> = InferValueAt<S, []>
|
||||
type InferArgs<S> = InferProperties<S, []>
|
||||
```
|
||||
|
||||
`defineTool({ name, description, parameters, output, execute, … })` 将参数推导与 `parameterSchemaSpecToJsonSchema()` 和 `validateArgs()` 绑定,并将 `execute`/`render`/`presentationMeta` 与 `InferValue<OutputSchema>` 绑定。Schema 记录只包含自有且可枚举的字符串键,schema 数组是稠密的内建数组,因此推导、编译与校验观察到的是同一份声明。精确推导保持到 16 层容器,之后放宽为 `JsonValue`;运行时校验仍会继续遍历完整 schema。`valueSchemaSpecToJsonSchema()` 通过同一套已强制执行的原始子集编译输出声明。参数不匹配时抛出 `ToolArgsError`(`INVALID_ARGS`);函数体或后置策略产生的值无效时抛出 `ToolOutputError`(`INVALID_TOOL_OUTPUT`)。两者都经由常规工具错误路径处理。原始 JSON Schema 默认保持开放;不支持的关键字会被拒绝,而不会在未强制执行的情况下获准进入。
|
||||
`defineTool({ name, description, parameters, output, execute, … })` 将参数推导与 `parameterSchemaSpecToJsonSchema()` 和 `validateArgs()` 绑定,并将 `execute`/`render`/`presentationMeta` 与 `InferValue<OutputSchema>` 绑定。schema 记录只包含自有且可枚举的字符串键,schema 数组是稠密的内建数组,因此推导、编译与校验观察到的是同一份声明。精确推导保持到 16 层容器,之后放宽为 `JsonValue`;运行时校验仍会继续遍历完整 schema。`valueSchemaSpecToJsonSchema()` 通过同一套已强制执行的原始子集编译输出声明。参数不匹配时抛出 `ToolArgsError`(`INVALID_ARGS`);函数体或后置策略产生的值无效时抛出 `ToolOutputError`(`INVALID_TOOL_OUTPUT`)。两者都经由常规工具错误路径处理。原始 JSON Schema 默认保持开放;不支持的关键字会被拒绝,而不会在未强制执行的情况下获准进入。
|
||||
|
||||
注册是一项受信任的同进程约定。注册表以 readonly 输入借用已类型化定义,要求它声明 `output`,校验其原始 schema,并检查 `timeoutMs` 必须为正有限值等语义要求;`schemas()` 在构建请求时生成面向模型的投影,使执行和展示共享同一份已解析定义,而不会将回调泄漏到协议上。
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/user-interaction.md
|
||||
user-interaction.md: 1f7ab09970f131928ada6e4014468b3d9fad64cf
|
||||
user-interaction.zh.md: 293b7fb391d8e6b2ec4179011437b4442b7fa5e4
|
||||
user-interaction.zh.md: eaaec2c399456193c30906fd1a7850ded0e32844
|
||||
|
||||
@@ -22,7 +22,7 @@ interface AskUserQuestionOption {
|
||||
|
||||
## 呈现意图
|
||||
|
||||
`AskUserQuestionIntent` 可选地声明一种已知的决定类型。它按 `kind` 打标签,因此可以增加新的意图;不认识某个标签的 UI 渲染通用选项列表。意图只改变呈现方式——遵循它的 UI 回答的仍是通用 UI 会发送的那些 option label,因此调用方两种情况下读到的回答字段相同。`approve` 指名肯定选项,而不依赖选项顺序。有两项断言是任何类型都承载不了的,`ask()` 会拒绝它们:`approve` 未命中该问题自身的任一选项,以及意图落在没有 `detail` 的问题上。
|
||||
`AskUserQuestionIntent` 可选地声明一种已知的决策类型。它按 `kind` 打标签,因此可以增加新的意图;不认识某个标签的 UI 渲染通用选项列表。意图只改变呈现方式——遵循它的 UI 回答的仍是通用 UI 会发送的那些选项标签,因此调用方两种情况下读到的回答字段相同。`approve` 指名肯定选项,而不依赖选项顺序。`ask()` 会拒绝两种无法由类型系统表达的情况:`approve` 未指向该问题自身的任何选项,以及为没有 `detail` 的问题指定意图。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
@@ -46,7 +46,7 @@ type AskUserQuestionIntent = {
|
||||
|
||||
## 问题条目
|
||||
|
||||
`AskUserQuestionItem` 是请求中的一个问题。调用方提供稳定的 `id`,它会随答案原样返回,使批量问题仍可路由。可选的 `detail` 携带辅助文本;提供方会将其随问题渲染,但不会放入可选 option label。
|
||||
`AskUserQuestionItem` 是请求中的一个问题。调用方提供稳定的 `id`,它会随答案原样返回,使批量问题仍可路由。可选的 `detail` 携带辅助文本;提供方会将其随问题渲染,但不会放入可选选项标签。
|
||||
|
||||
```ts type-equiv
|
||||
/** One question in a user-interaction request. */
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user