Merge branch 'worktree-llm-dynamic-config' into worktree-llm-web-config

# Conflicts:
#	apps/cli/cordis.yml
#	apps/web/tests/snapshots/code-mode-round/session.jsonl
#	apps/web/tests/snapshots/cordis-tool-round/session.jsonl
#	apps/web/tests/snapshots/fresh-round-trip/session.jsonl
#	apps/web/tests/snapshots/lifecycle-chrome/session.jsonl
#	apps/web/tests/snapshots/live-interactions/session.jsonl
#	apps/web/tests/snapshots/navigation-panes/seed.jsonl
#	apps/web/tests/snapshots/question-composer/session.jsonl
#	apps/web/tests/snapshots/seeded-history/seed.jsonl
#	apps/web/tests/snapshots/steering/session.jsonl
#	docs/cordis-catalog/events.md
#	docs/cordis-catalog/services.md
#	docs/core-data-structures/core.i18n.yaml
#	docs/core-data-structures/settings.i18n.yaml
#	docs/event-producer-consumer.md
#	docs/module-graph.md
#	examples/acp-agent/tests/snapshots/workspace-context/session.jsonl
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/src/index.ts
#	packages/client/connection/tests/node-half.spec.ts
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/README.md
#	packages/client/runtime/README.zh.md
#	packages/client/runtime/src/client/index.ts
#	packages/client/runtime/tests/fake-api.ts
#	packages/client/ui-models/README.i18n.yaml
#	packages/examples/tui-demo/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/package.json
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/rpc.schema.ts
#	packages/host/apiproxy/src/api/rpc.ts
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/llm/llm-deepseek/README.zh.md
#	packages/llm/llm-pi-ai/README.i18n.yaml
#	packages/llm/llm/README.i18n.yaml
#	packages/llm/llm/README.zh.md
#	packages/sdk/sdk-client/README.i18n.yaml
#	packages/settings/settings/README.i18n.yaml
#	packages/settings/settings/README.md
#	packages/settings/settings/README.zh.md
#	packages/subagent/subagent-dsh-sdk/README.i18n.yaml
#	packages/subagent/subagent-dsh-sdk/README.zh.md
#	packages/support/llm-replay/README.i18n.yaml
#	packages/ui/jsonrpc/README.i18n.yaml
#	packages/ui/jsonrpc/README.zh.md
#	packages/ui/tui/tests/snapshots/model-selector.expected.txt
#	packages/ui/tui/tests/snapshots/model-switching.expected.txt
#	packages/ui/tui/tests/snapshots/resume-sessions.expected.txt
#	packages/ui/tui/tests/snapshots/status-diagnostics-narrow.expected.txt
#	packages/ui/tui/tests/snapshots/status-diagnostics.expected.txt
#	packages/ui/tui/tests/tui.snapshot.ts
#	pnpm-lock.yaml
#	python/sdk/README.i18n.yaml
#	scripts/snapshots/translation-prompt-v4/request-response.expected.json
This commit is contained in:
Yichen Jiang
2026-07-30 15:18:26 +08:00
1346 changed files with 58389 additions and 9936 deletions

View File

@@ -1,6 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write
# pnpm run verify-translation-pairing --write packages/workflow/workflow-workerthread/README.md
README.md: 9da420bdd67ac5b0a4bfffa312c6fe7b2dabf8f5
README.zh.md: e280562b95e9dbebb243aad0f765771f4737b312
README.zh.md: cc2ed2043db7f6a51088930c07c5cf5a2223f480

View File

@@ -2,9 +2,9 @@
[English](README.md) | 中文
本包为 `WorkflowService` 提供实现,每次运行使用一个 Node worker thread。worker 执行编排脚本;子 agent智能体留在宿主上通过带类型的宿主/worker 协议访问 `ctx.subagents`
本包package`WorkflowService` 提供实现,每次运行使用一个 Node worker thread。worker 执行编排脚本;子 agent智能体留在宿主上脚本通过带类型的宿主worker 协议经由 `ctx.subagents` 访问它们
包根目录默认导出引擎插件及其 `Config`worker 协议、运行时和会话模块均为实现私有。操作入口 `./worker` 仍是引擎的派生目标。
包根目录默认导出引擎插件及其 `Config`worker 协议、运行时和会话模块均为实现私有。操作入口 `./worker` 仍是引擎的 spawn 目标。
这种拆分只有一个主要目的:同步脚本循环不能阻塞 harness 事件循环,忽略取消的脚本可以连同其 worker 一起终止。它不是安全沙箱。
@@ -16,14 +16,14 @@ worker 仍提供实用的隔离:
- 脚本 CPU 工作和同步自旋不会占用宿主事件循环;
- `worker.terminate()` 为 dispose资源释放提供真实的最终停止手段
- 除未构建 loader 的管道变量worker 以空环境启动,因此环境凭据不会通过 `process.env` 跨越边界;
- 除未构建 loader 所需的衔接配置worker 以空环境启动,因此环境凭据不会通过 `process.env` 跨越边界;
- 宿主/worker 消息使用结构化克隆数据,并在脚本边界执行普通 JSON 校验。
真正的不可信脚本沙箱需要在同一 workflow seam 后采用不同引擎。
真正的不可信脚本沙箱需要在同一工作流 seam 后采用不同引擎。
## 脚本契约
工作流的 `meta` 是宿主提供的数据,而不是待求值的脚本文本。引擎会校验必需的 `name``description`、拒绝未知字段,并在返回运行前检查函数体能否解析。
工作流的 `meta` 是宿主提供的数据,而不是待求值的脚本文本。引擎会校验必需的 `name``description`、拒绝未知字段,并在返回运行前检查脚本正文能否解析。
在 worker 内,脚本会收到 `args` 以及以下钩子:
@@ -32,16 +32,16 @@ worker 仍提供实用的隔离:
- `pipeline(items, ...stages)` 在没有跨阶段屏障的情况下传递 `(previous, item, index)`
- `phase(title)``log(message)` 发出观察器叙述。
未知选项、格式错误的参数、不支持的 schema、触发的上限、提供方启动失败和基础设施结果失败都属于致命工作流错误。有意不注入 timer、文件系统 API 或 Node 全局变量,但上述信任注意事项仍然适用。
未知选项、格式错误的参数、不支持的 schema、超出上限、提供方启动失败和基础设施结果失败都属于致命工作流错误。有意不注入 timer、文件系统 API 或 Node 全局变量,但上述信任注意事项仍然适用。
## 运行顺序
`start()` 会校验 meta、解析函数体、解析一个已注册且规范化的提供方路由,并解析每次运行的子 agent 总数上限,然后才创建 worker 或发布 `workflow/start`。请求的 `maxTotalAgents` 必须是正安全整数,且不能超过引擎配置的部署上限。源代码模式通过 data URL bootstrap 安装 TypeScript 转换;构建模式把同级 `lib/worker.cjs` 作为文件系统路径传入,因为 pkg 的 VFS 钩子要求 CommonJS。两者都能在普通 Node 下运行。ready/go 握手可以避免启动信号取消与 worker 启动发生竞态,导致脚本最初的同步片段被执行。
`start()` 会校验 meta、解析脚本正文、解析一个已注册且规范化的提供方路由,并解析每次运行的子 agent 总数上限,然后才创建 worker 或发布 `workflow/start`。请求的 `maxTotalAgents` 必须是正安全整数,且不能超过引擎配置的部署上限。源代码模式通过 data URL bootstrap 安装 TypeScript 转换;构建模式把同级 `lib/worker.cjs` 作为文件系统路径传入,因为 pkg 的虚拟文件系统(VFS钩子要求 CommonJS。两者都能在普通 Node 下运行。ready/go 握手可以避免启动信号取消与 worker 启动发生竞态,导致脚本最初的同步片段被执行。
对于每次 `agent()` 调用:
1. worker 发送 `child-start`,其中包含普通数据提示词和选项。
2. 宿主通过异步 `SubagentService.start` 调用启动请求中的提供方覆盖值,否则调用已配置提供方;调用会传入工作流父级和次运行唯一的规范中止信号。提供方选择应用于该次运行的每个子 agent对脚本不可见。
2. 宿主通过异步 `SubagentService.start` 调用启动请求中指定的提供方,否则调用已配置提供方;调用会传入工作流父级和次运行共用的唯一中止信号。提供方选择应用于该次运行的每个子 agent对脚本不可见。
3. 如果启动被拒绝,宿主会发送 `child-start-error`;提供方启动已经完全停稳,不会发出子 agent 生命周期事件。
4. 如果启动兑现时工作流仍接纳工作,宿主会记录该运行、观察 `result`,然后发送 `child-started`。即使结果已经结算,也只会随后转发,以保持先启动、后结果的顺序。
5. worker 发出成对的 `workflow/agent-start``workflow/agent-end` 叙述,并在收集后请求 dispose 子 agent。
@@ -56,9 +56,9 @@ worker 仍提供实用的隔离:
## 取消与 dispose
`WorkflowRun.cancel()` 会记录第一个原因、通知 worker 取消、中止每个待处理及已发布子 agent 共享的唯一信号,并启动 `disposeGraceMs` timer。worker 钩子会在下次 await 时抛出 `CANCELLED`。如果运行到期限仍未结算,宿主会将其以已取消状态兑现、为悬空的子 agent 生命周期事件配对,并终止 worker。
`WorkflowRun.cancel()` 会记录第一个原因、通知 worker 取消、中止每个待处理及已发布子 agent 共享的唯一信号,并启动 `disposeGraceMs` 定时器。worker 钩子会在下次 await 时抛出 `CANCELLED`。如果运行到期限仍未结算,宿主会将其以已取消状态兑现、为悬空的子 agent 生命周期事件配对,并终止 worker。
subagent seam 只有一个取消通道:请求信号。不存在单独的子 agent 取消 RPC。已发布子 agent 使用 `run.dispose()` 清理;待处理提供方启动在其 promise 拒绝或兑现前仍由提供方拥有
subagent seam 只有一个取消通道:请求信号。不存在单独的子 agent 取消 RPC。已发布子 agent 使用 `run.dispose()` 清理;待处理提供方启动在其 promise 拒绝或兑现前仍由提供方负责
正常结算也会中止待处理启动,并在结果对外结算前开始 dispose 所有已发布但无需等待的子 agent。宿主的完全停稳条件同时包括待处理启动和已发布子 agent 的 dispose因此清理不会遗漏异步启动事务。
@@ -66,9 +66,9 @@ subagent seam 只有一个取消通道:请求信号。不存在单独的子 ag
## 结果与事件保证
在宿主主张点,终态结果遵循先到者胜。已接受的外部取消会覆盖后到的非取消 worker 结果;先完成主张的结果或 worker 死亡不能被可重入清理回调改写。
在宿主的结果确认点,终态结果遵循先到者胜。已接受的外部取消会覆盖后到的非取消 worker 结果;先完成确认的结果或 worker 死亡不能被可重入清理回调改写。
worker 错误、消息失败或提前退出会在清理前关闭消息接纳,然后以 `error` 兑现;如果取消已经拥有该运行,则不覆盖取消。后到的排队消息无法在该逻辑边界后创建子 agent 或发出叙述。
worker 错误、消息失败或提前退出会在清理前关闭消息接纳,然后以 `error` 兑现;如果取消已经接管该运行,则不覆盖取消。后到的排队消息无法在该逻辑边界后创建子 agent 或发出叙述。
宿主会维护已转发子 agent 启动的台账。优雅退出的 worker 会提供对应的结束事件;死亡或强制终止会把缺失的结束事件合成为已取消。因此,每个已转发的 `workflow/agent-start` 都会且只会配对一次,不过已经到达的工作流结果之后的清理可能稍后才完成。
@@ -83,7 +83,7 @@ worker 错误、消息失败或提前退出会在清理前关闭消息接纳,
| `syncTimeoutMs` | `5000` | 脚本最初同步片段的 VM 超时时间。 |
| `disposeGraceMs` | `5000` | 强制结算/终止之前的期限,也是公开 dispose 的期限。 |
所属消费方可以为一次运行设置 `WorkflowStartRequest.subagentProvider``WorkflowStartRequest.maxTotalAgents`。它们属于引擎级策,不是脚本钩子或面向模型的选项;普通 `workflow` 工具不会设置两者。每次运行的子 agent 总数上限可以降低、但绝不能提高已配置的 `maxTotalAgents` 上限。
负责该引擎的消费方可以为一次运行设置 `WorkflowStartRequest.subagentProvider``WorkflowStartRequest.maxTotalAgents`。它们属于引擎级策,不是脚本钩子或面向模型的选项;普通 `workflow` 工具不会设置两者。每次运行的子 agent 总数上限可以降低、但绝不能提高已配置的 `maxTotalAgents` 上限。
## 模型体验
@@ -91,7 +91,7 @@ worker 错误、消息失败或提前退出会在清理前关闭消息接纳,
#### 模型看到的内容
脚本每次调用 `agent()`,都会把提示词逐字发送给 subagent 提供方,并附带可选模型或结构化输出 schema。每个子 agent 看到该提供方自己的上下文phase 和 log 叙述只留在观察器事件中。
脚本每次调用 `agent()`,都会把提示词原样发送给 subagent 提供方,并附带可选模型或结构化输出 schema。每个子 agent 看到该提供方自己的上下文phase 和 log 叙述只留在观察器事件中。
#### Token 影响
@@ -109,16 +109,16 @@ worker 错误、消息失败或提前退出会在清理前关闭消息接纳,
#### Token 影响
本引擎不会直接向父级添加 token。最终结果大小由工具消费方限制并保留到上下文压缩compaction为止。
本引擎不会直接向父级添加 token。最终结果大小由工具消费方限制并保留到压缩compaction为止。
#### KV Cache 影响
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV-cache 条目失效。
## 已知限制与延期工作
## 已知限制与暂缓事项
- **worker/vm 不是安全边界**:模型编写的代码可以逃逸 `node:vm` 并取得 worker 的进程权限;不可信代码部署需要独立进程或容器引擎。
- **每次运行都要支付一个 worker thread 的成本**:没有池、预热运行时或跨运行脚本缓存。
- **不注入环境 timer、文件系统或网络,但逃逸代码仍可访问 Node**:缺失的全局变量用于保证 API 可移植性,而非隔离。
- **终止只能报告宿主观察到的启动**`agentsStarted` 不包括仍在 worker 侧排队等待并发、且在强制终止后无法得知的调用。
- **不注入默认可用的定时器、文件系统或网络,但逃逸代码仍可访问 Node**这些缺失的全局变量属于可移植性 API 设计,而非隔离措施
- **终止只能报告宿主观察到的启动**`agentsStarted` 不包括因并发限制仍在 worker 侧排队、且在强制终止后无法得知的调用。
- **跨 realm 错误在脚本内无法通过 `instanceof Error`**:工作流作者必须根据 `name``code` 等稳定字段分支。

View File

@@ -548,7 +548,7 @@ describe('dsh-workflow-workerthread', () => {
// The rejection VALUE's own coercion throws: a warn built with bare
// String(error) would itself throw, skipping the ChildDisposed ack
// and wedging the script's finally until the grace/terminate path.
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- the non-Error rejection IS the scenario under test
// oxlint-disable-next-line typescript/prefer-promise-reject-errors -- the non-Error rejection IS the scenario under test
dispose: () => Promise.reject({ toString: () => { throw new Error('coercion trap') } }),
}),
}