Merge remote-tracking branch 'origin/master' into xtr/react-loop-simplification

# Conflicts:
#	.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-11-content-block-vocabulary.zh.md
#	.agents/notes/implemented/architecture/2026-06-11-event-sourced-sessions.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-11-microkernel-event-taxonomy.zh.md
#	.agents/notes/implemented/architecture/2026-06-18-agent-lifecycle-and-ownership-seams.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-18-session-surface.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.zh.md
#	.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.i18n.yaml
#	.agents/notes/implemented/architecture/2026-06-30-event-domain-semantics.zh.md
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.zh.md
#	.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.zh.md
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-10-single-file-executable-sdk-runtime-distribution.zh.md
#	.agents/notes/implemented/architecture/2026-07-14-provider-routed-llm-adapters.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-15-replay-token-meter-service.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.zh.md
#	.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-24-separate-context-injection-from-turn-execution.zh.md
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-25-web-client-session-scope-and-provide-channel.zh.md
#	.agents/notes/implemented/architecture/2026-07-28-identified-immutable-message-values.i18n.yaml
#	.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.i18n.yaml
#	.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.zh.md
#	.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.zh.md
#	.agents/notes/implemented/feature/2026-06-24-workspace-context.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-24-workspace-context.zh.md
#	.agents/notes/implemented/feature/2026-06-30-hook-bridges.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-hook-protocol-lib.zh.md
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.i18n.yaml
#	.agents/notes/implemented/feature/2026-06-30-interception-seams.zh.md
#	.agents/notes/implemented/feature/2026-07-06-sandbox.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-06-sandbox.zh.md
#	.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-16-durable-per-step-time-context.zh.md
#	.agents/notes/implemented/feature/2026-07-16-harness-level-loop.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-16-harness-level-loop.zh.md
#	.agents/notes/implemented/feature/2026-07-19-human-goal-command.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-model-facing-goal-tools.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-persisted-same-session-goal-domain.zh.md
#	.agents/notes/implemented/feature/2026-07-19-plugin-command-registration.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-19-same-session-goal-round-driver.zh.md
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-21-cross-session-references.zh.md
#	.agents/notes/implemented/feature/2026-07-27-tmux-location-context.i18n.yaml
#	.agents/notes/implemented/simplification/2026-06-20-public-agent-stop-surface.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.zh.md
#	.agents/notes/implemented/simplification/2026-07-20-unwrap-injected-content-envelopes.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.zh.md
#	.agents/notes/implemented/simplification/2026-07-24-agent-loop-observable-state-machine.i18n.yaml
#	.agents/notes/implemented/simplification/2026-07-27-request-error-retry-action.i18n.yaml
#	docs/core-data-structures/compaction.i18n.yaml
#	docs/core-data-structures/goal.i18n.yaml
#	docs/core-data-structures/goal.zh.md
#	docs/core-data-structures/llm-streaming.i18n.yaml
#	docs/core-data-structures/session.i18n.yaml
#	docs/core-data-structures/session.zh.md
#	docs/core-data-structures/skills.i18n.yaml
#	docs/core-data-structures/skills.zh.md
#	docs/core-data-structures/system-prompt.i18n.yaml
#	docs/defensive-patterns.i18n.yaml
#	docs/defensive-patterns.zh.md
#	docs/user/develop/framework/events.i18n.yaml
#	docs/user/develop/framework/events.zh.md
#	packages/acp/acp/README.i18n.yaml
#	packages/client/ui-goal/README.i18n.yaml
#	packages/compact/compact-basic/README.i18n.yaml
#	packages/compact/compact/README.i18n.yaml
#	packages/context/time-context/README.i18n.yaml
#	packages/context/tmux-context/README.i18n.yaml
#	packages/core/agent-loop/README.i18n.yaml
#	packages/core/agent-loop/README.zh.md
#	packages/core/agent/README.i18n.yaml
#	packages/core/agent/README.zh.md
#	packages/core/session/README.i18n.yaml
#	packages/core/session/README.zh.md
#	packages/core/system-prompt/README.i18n.yaml
#	packages/core/system-prompt/README.zh.md
#	packages/examples/cli-demo/README.i18n.yaml
#	packages/goal/command-goal/README.i18n.yaml
#	packages/goal/goal-session/README.i18n.yaml
#	packages/goal/goal/README.i18n.yaml
#	packages/goal/tool-goal/README.i18n.yaml
#	packages/goal/tool-goal/README.zh.md
#	packages/guard/README.i18n.yaml
#	packages/guard/README.zh.md
#	packages/guard/repeat-tool-guard/README.i18n.yaml
#	packages/hooks/hooks-claude/README.i18n.yaml
#	packages/hooks/hooks-codex/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.zh.md
#	packages/llm/llm/README.i18n.yaml
#	packages/plan/plan-mode/README.i18n.yaml
#	packages/plan/plan-mode/README.zh.md
#	packages/sdk/sdk-client/README.i18n.yaml
#	packages/sdk/sdk-client/README.zh.md
#	packages/sdk/sdk-protocol/README.i18n.yaml
#	packages/session-persistence/session-persistence/README.i18n.yaml
#	packages/session-persistence/session-persistence/README.zh.md
#	packages/skill/tool-skill/README.i18n.yaml
#	packages/subagent/subagent-dsh-sdk/README.i18n.yaml
#	packages/subagent/subagent-inprocess/README.i18n.yaml
#	packages/subagent/subagent-inprocess/README.zh.md
#	packages/ui/jsonrpc/README.i18n.yaml
This commit is contained in:
_Kerman
2026-08-05 13:09:53 +08:00
948 changed files with 3046 additions and 2959 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 docs/user/develop/basic/config.md
config.md: 26d2d48ebede74194fbf306aa97d214bdb99b722
config.zh.md: 9ed389b16779f25c633d0c8772f8658197ba4322
config.zh.md: 1ca727b56b661df315026902d48fbcaf507fe08b

View File

@@ -75,7 +75,7 @@ Schema 在插件加载时执行校验。如果配置不合法,插件会加载
### 无硬编码可调参数
Harness 的约定:**任何两个部署可能想要不同值的东西,都应该是配置字段**。
Harness 的约定:**凡是不同部署可能需要采用不同值的参数,都必须定义为配置字段**。
```ts
// Wrong: hardcoded timeout.
@@ -110,7 +110,7 @@ export function apply(ctx: Context, config: ModelConfig) {
## 配合 HMR
配置变更会触发插件热替换:修改 `cordis.yml` 中某个插件的 `config`,框架会卸载旧实例加载新实例。由于注册都是效果(自动清理),这个过程是安全的
配置变更会触发插件热替换:修改 `cordis.yml` 中某个插件的 `config`,框架会卸载旧实例加载新实例。由于注册都属于 effect 并会自动清理,替换后不会保留旧实例的注册
## 下一步

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 docs/user/develop/basic/index.md
index.md: 5a9f8dfb8f2d87dfbd2ba30b4d09d002ae9b635c
index.zh.md: 08aca87cbc02d1b0dfbe6fe2d92b3f6e87075097
index.zh.md: 6f8926aeac5dcf667e4d311505ef7b2a321659c2

View File

@@ -2,7 +2,7 @@
[English](index.md) | 中文
本文带你编写一个最小的 Harness 插件并加载到 Agent 中。
本文带你编写一个最小的 Harness 插件并加载到 agent(智能体)中。
## 插件是什么
@@ -18,7 +18,7 @@ export function apply(ctx: Context) {
}
```
就这么简单
这就是完整结构
## 创建插件文件
@@ -48,7 +48,7 @@ export function apply(ctx: Context) {
## 自动清理
通过 `ctx` 注册的任何东西——事件监听、tool、定时器——在插件卸载时都会被自动清理。你不需要手动 removeListener 或 clearInterval。
通过 `ctx` 注册的任何东西——事件监听、工具、定时器——在插件卸载时都会被自动清理。你不需要手动 removeListener 或 clearInterval。
如果你有需要手动清理的资源(比如一个网络连接),用 `ctx.effect()` 告诉框架怎么清理:
@@ -118,11 +118,11 @@ export default class MyService extends Service {
}
```
大多数情况下,函数形式足够了。类形式用于需要对外提供服务的插件(见 [服务与依赖](../framework/service.md))。
大多数情况下,函数形式足够了。当插件需要向其他插件提供服务时,可使用类形式(见 [服务与依赖](../framework/service.md))。
## 完整示例
最小的工具插件会在 `ctx.tools` 上注册其定义:
最小的工具插件会在 `ctx.tools` 上注册其定义:
```ts
import type { Context } from 'cordis'
@@ -151,5 +151,5 @@ export function apply(ctx: Context) {
## 下一步
- [开发一个 Tool](./tool.md) — 详细了解 tool 定义 DSL
- [开发一个工具](./tool.md) — 详细了解工具定义 DSL
- [插件配置](./config.md) — 让插件接受用户配置

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 docs/user/develop/basic/tool.md
tool.md: 0d7cbc3f0b86f88fb67aeff6aa61181dff2912ee
tool.zh.md: 30cc871d7b417bdf7f33025b22e3f0965e2b8805
tool.zh.md: 37362c510a07fcc32e9eee2b578d82907788e471

View File

@@ -1,8 +1,8 @@
# 开发一个 Tool
# 开发一个工具
[English](tool.md) | 中文
Tool 是模型可以调用的能力。本文介绍如何用 `defineTool` 编写一个 tool
工具是模型可以调用的能力。本文介绍如何用 `defineTool` 编写一个工具
## 最小示例
@@ -156,9 +156,9 @@ async execute(args) {
你不需要在 `execute` 里手动校验参数类型。
## 展示层 (Presentation)
## 展示层Presentation
Tool 可以定义与传输方式无关的展示方法,供终端和 Web 客户端使用:
工具可以定义与传输方式无关的展示方法,供终端和 Web 客户端使用:
```ts ignore-check
defineTool({
@@ -187,7 +187,7 @@ defineTool({
## 注册与卸载
`ctx.tools.register()` 返回值就是 disposer。但由于你在 `ctx` 上调用,框架已经自动追踪了这个注册——插件卸载时会自动移除 tool。你不需要手动调用 disposer。
`ctx.tools.register()` 返回值就是 disposer。但由于你在 `ctx` 上调用,框架已经自动追踪了这个注册——插件卸载时会自动移除工具。你不需要手动调用 disposer。
```ts ignore-check
// This is sufficient:
@@ -196,9 +196,9 @@ ctx.tools.register(defineTool({ /* ... */ }))
// No saved disposer or extra cleanup registration is needed.
```
## 完整实战示例
## 完整示例
一个文件计数 tool
一个文件计数工具
```ts
import type { Context } from 'cordis'
@@ -241,5 +241,5 @@ export function apply(ctx: Context) {
## 下一步
- [插件配置](./config.md) — 让你的 tool 可配置
- [能力三件套](../practice/) — 了解 seam/impl/consumer 模式
- [插件配置](./config.md) — 让你的工具可配置
- [能力分层](../practice/) — 了解接口/实现/消费方模式

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 docs/user/develop/framework/events.md
events.md: fcbdb5f39bf2078032affbc6469f7eecc795d3ba
events.zh.md: 8b96f27fd6930ca9a481be60f93bcd834901d5dc
events.zh.md: 1979e0bc1dbb71e46f50a051dacee8ae3a1172a7

View File

@@ -22,11 +22,11 @@ ctx.emit('event-name', payload)
## 事件模式
Cordis 提供多种事件触发模式,适用于不同场景
Cordis 提供多种事件模式,适用于不同的交互契约
### emit — 广播
所有监听器同步执行,不关心返回值:
所有监听器同步执行,返回值会被忽略
```ts ignore-check
// Emit
@@ -40,7 +40,7 @@ ctx.on('my-plugin/ready', ({ id }) => {
### bail — 短路
依次调用监听器,第一个返回非 `undefined` 值的结果作为最终
依次调用监听器,第一个非 `undefined` 的返回值将作为最终结果
```ts ignore-check
// Dispatch
@@ -61,7 +61,7 @@ ctx.on('some-check', (input) => {
await ctx.serial('setup-phase', context)
```
### waterfall — 管道
### waterfall(瀑布式事件)— 流水线
每个监听器可以包装下游返回值,形成处理链。**必须调用 `next()` 传递给下游**,不调用即为否决:
@@ -77,10 +77,10 @@ ctx.on('my-plugin/transform', async (_input, next) => {
```
::: warning
Waterfall 监听器**必须调用 `next()`**。不调用 `next` 等于否决整个管道,这是故意为之的设计——用于实现拦截/网关逻辑。
waterfall 监听器**必须调用 `next()`**。不调用 `next` 等于否决整个流水线,这是故意为之的设计——用于实现拦截/网关逻辑。
:::
## Typed Events
## 类型安全的事件
Harness 使用 TypeScript 声明合并来为事件提供类型安全:
@@ -105,7 +105,7 @@ Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/pre-st
`turn/*`、`step/*`、`tool/call`、`tool/result` 和 `compact/*` 是持久化的会话事件类型,不是同名 Cordis 事件。需要观察它们时,监听 `session/event` 并检查 `event.type`。
## 事件也是效果
## 事件监听器也是效果
通过 `ctx.on()` 注册的监听器会在插件卸载时自动移除:
@@ -116,9 +116,9 @@ export function apply(ctx: Context) {
}
```
## 实战示例:日志插件
## 示例:日志插件
一个记录所有 tool 调用的简单插件
这个插件记录工具调用和工具结果
```ts
import type { Context } from 'cordis'
@@ -139,5 +139,5 @@ export function apply(ctx: Context) {
## 下一步
- [能力三件套](../practice/) — 事件在 capability seam 中的角色
- [LLM 适配器](../practice/llm-adapter.md) — 实现一个完整的 LLM 后端
- [能力分层](../practice/) — 了解能力接口中的事件
- [LLM(大语言模型)适配器](../practice/llm-adapter.md) — 实现一个完整的 LLM 后端

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 docs/user/develop/framework/index.md
index.md: 79e925b54509da41535735527e283850384257ec
index.zh.md: 62be8c706510704f7b07286f166f14fa81235a0a
index.zh.md: 962677dc468c9cc233a51d50758247e028d9c3ed

View File

@@ -2,11 +2,11 @@
[English](index.md) | 中文
深入了解 Cordis 插件模型和生命周期状态机。
本页介绍 Cordis 插件模型和生命周期状态机。
## Fiber 状态机
每个被加载的插件对应一个 **Fiber**作用域。Fiber 有以下状态
每个被加载的插件都拥有一个 **Fiber** 作用域,其状态如下
```
PENDING → LOADING → ACTIVE
@@ -16,16 +16,16 @@ ACTIVE → UNLOADING → DISPOSED
| 状态 | 含义 |
|------|------|
| PENDING | 已声明依赖未就绪 |
| PENDING | 已声明,但所需依赖未就绪 |
| LOADING | 依赖就绪,正在执行 `apply` |
| ACTIVE | 插件运行中 |
| FAILED | `apply` 抛出异常 |
| UNLOADING | 正在卸载,清理中 |
| UNLOADING | 插件正在卸载并释放资源 |
| DISPOSED | 已完全卸载 |
## 依赖驱动的加载
声明了 `inject` 的插件不会立即加载,而是等待依赖的服务就绪:
声明了 `inject` 的插件会等待所有必需服务就绪:
```ts ignore-check
export const inject = ['tools', 'llm']
@@ -35,7 +35,7 @@ export function apply(ctx: Context) {
}
```
如果依赖的服务消失(如提供者被热替换插件会被自动卸载ACTIVE → DISPOSED待服务恢复后重新加载。
如果依赖的服务消失(如提供方被替换插件会被自动卸载ACTIVE → DISPOSED待服务恢复后重新加载。
## 自动清理机制
@@ -56,11 +56,11 @@ export function apply(ctx: Context) {
以下操作都会被自动追踪和清理:
- `ctx.on(event, handler)` — 事件监听
- `ctx.tools.register(tool)` — tool 注册
- `ctx.llm.registerAdapter(names, adapter)` — LLM 适配器注册
- `ctx.tools.register(tool)` — 工具注册
- `ctx.llm.registerAdapter(names, adapter)` — LLM(大语言模型)适配器注册
- `ctx.effect(() => cleanup)` — 自定义资源
插件卸载时,处置器按注册顺序的反向发起,但多个异步处置器会并发执行,不保证逐个完成。存在顺序依赖的清理步骤必须放进同一个 `ctx.effect()` 返回的处置器中,由该处置器负责串行等待。
插件卸载时,处置器按注册顺序的逆序开始调用,但多个异步处置器会并发执行,不保证逐个完成。存在顺序依赖的清理步骤必须放进同一个 `ctx.effect()` 返回的处置器中,由该处置器负责串行等待。
## 嵌套上下文
@@ -75,7 +75,7 @@ export function apply(ctx: Context) {
}
```
## dispose 语义
## dispose(资源释放)语义
当你需要提前终止一个插件实例:
@@ -92,21 +92,21 @@ await fiber.dispose()
```
`dispose` 保证:
1. 该插件注册的所有东西被撤销
1. 该插件拥有的所有注册均被移除
2. 它的子插件也被递归卸载
3. 所有异步清理完成后 Promise resolve
3. 返回的 Promise 会在所有异步清理完成后兑现
## 热替换 (HMR)
## HMR热模块替换
在开发环境中(`cordis.yml` 加载 `@cordisjs/plugin-hmr`,修改插件源文件会自动触发:
通过 `cordis.yml` 加载 `@cordisjs/plugin-hmr`,修改插件源文件会触发:
1. 卸载旧插件(清理所有注册)
2. 重新加载新代码
3. 执行新的 `apply`
因为所有注册会被自动清理,所以热替换天然安全——不会留下旧状态
因为插件注册会被自动清理,所以热替换不会保留旧实例的注册
## 实战:理解生命周期
## 生命周期示例
```ts ignore-check
export function apply(ctx: Context) {
@@ -132,5 +132,5 @@ effect cleaned up
## 下一步
- [服务与依赖](./service.md) — 让你的插件对外提供能力
- [事件系统](./events.md) — 插件间通信的核心机制
- [服务与依赖](./service.md) — 让插件向其他插件提供能力
- [事件系统](./events.md) — 插件间通信

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 docs/user/develop/framework/service.md
service.md: 1bf28cb3c7dfdfbd6d0babfa3b1688ac65eea01e
service.zh.md: 17785c056ab9a0a21974e6ed8bbe7f7de05fa00e
service.zh.md: 78b9887a734b235bcff1d94a623e37cfeb2f41e3

View File

@@ -2,7 +2,7 @@
[English](service.md) | 中文
服务 (Service) 是插件对外暴露能力的方式。依赖 (inject) 是插件声明自己需要哪些服务。
服务是一个插件向其他插件公开的能力。inject 声明插件需要哪些服务。
## 什么是服务
@@ -14,7 +14,7 @@ ctx.llm // LLM service
ctx.agents // Agent service
```
任何插件都可以提供一个新服务,供其他插件使用。
任何插件都可以提供服务,供其他插件使用。
## 使用服务
@@ -52,7 +52,7 @@ export default class MetricsService extends Service {
}
```
加载这个插件后,其他插件就可以通过 `ctx.metrics` 访问它:
加载这个插件后,消费方就可以通过 `ctx.metrics` 访问它:
```ts ignore-check
export const inject = ['metrics']
@@ -86,7 +86,7 @@ export default class MetricsService extends Service {
## 依赖的行为
### 必依赖 vs 可选依赖
### 必依赖可选依赖
```ts ignore-check
// Required: the plugin does not load while the service is absent.
@@ -101,12 +101,12 @@ export function apply(ctx: Context) {
### 服务消失时的行为
如果一个必选依赖的服务在运行时消失(比如提供者被卸载):
如果应用运行期间某项必需服务消失(例如其提供方卸载):
1. 依赖它的插件自动 dispose
1. 依赖它的插件自动 dispose(资源释放)
2. 当服务重新出现时,插件自动重新加载
保证了不会出现"调用一个已不存在的服务"的情况
可以防止插件调用已不存在的服务。
## 服务隔离
@@ -136,13 +136,13 @@ export function apply(ctx: Context) {
- name: './src/plugin-b.ts'
```
`plugin-a` 和 `plugin-b` 各自看到自己组内的 bash 实例,互不影响。
`plugin-a` 和 `plugin-b` 各自看到自己组内的 Bash 实例,互不影响。
## Harness 内置服务
服务名、公开方法和源码位置由仓库自动生成,见[服务目录](../../../cordis-catalog/services.md)。开发插件时应以该目录和服务接口的 TypeScript 类型为准,不要复制一份静态清单。
仓库自动生成[服务目录](../../../cordis-catalog/services.md),其中包含服务名、公开方法和源码位置。开发插件时应以该目录和服务的 TypeScript 接口为准,不要维护另一份静态清单。
## 下一步
- [事件系统](./events.md) — 插件间松耦合通信
- [能力三件套](../practice/) — 服务在 seam 模式中的应
- [能力分层](../practice/) — 服务用作能力接口

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 docs/user/develop/practice/index.md
index.md: e197d499d7f5bd9911ea60bebf584251cd4ed915
index.zh.md: 8b8d08f9d0c6d0ca8d95fbaa3281c98b7a600fe4
index.zh.md: cacf13e8b23060ea5c309d30e51d732f09c76000

View File

@@ -2,15 +2,15 @@
[English](index.md) | 中文
当一能力(插件)足够通用(比如"执行 bash 命令"Harness 会把它拆成三个包:**接口**、**实现****消费**。这样可独立替换其中任何一层。
当一能力足够通用,需要支持可替换的实现时(例如 Bash 执行Harness 会将其拆成三个包:**接口**、**实现****消费**。这样便可独立替换其中任何一层。
## 以 Bash 为例
考虑 "Bash 执行" 这个能力:
Bash 执行能力为例
- **接口** (`dsh-bash`) — 定义"bash 执行"长什么样:输入是什么、输出是什么
- **实现** (`dsh-bash-local`) — 真正在本地跑命令的代码
- **消费** (`dsh-tool-bash`) — 把这个能力包装成模型调用的 tool
- **接口** (`dsh-bash`):定义 Bash 请求和结果的结构
- **实现** (`dsh-bash-local`):在本地计算机上执行命令
- **消费** (`dsh-tool-bash`):将该能力公开为模型调用的工具
```
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
@@ -38,23 +38,23 @@
# endpoint: 'https://sandbox.example.com'
```
接口不变、tool 不变,只换实现
更换实现时,接口和工具均保持不变
### 独立演进
- 接口定义稳定后很少改动
- 实现可以独立优化(性能、安全)
- 消费tool可以调整模型呈现方式
- 消费可以调整能力向模型呈现方式
### 依赖解耦
- 实现 depend on 接口
- 消费者 depend on 接口
- 实现和消费**互不依赖**
- 实现依赖接口
- 消费方依赖接口
- 实现和消费**互不依赖**
## Harness 中内置的三件套
| 能力 | 接口 (seam) | 实现 | 消费者 (tool) |
| 能力 | 接口seam | 实现 | 消费方(工具) |
|------|-------------|------|---------------|
| Bash | `dsh-bash` | `dsh-bash-local` | `dsh-tool-bash` |
| 文件系统 | `dsh-fs` | `dsh-fs-local` + `dsh-fs-policy` | `dsh-tool-fs` |
@@ -115,7 +115,7 @@ export function apply(ctx: Context) {
}
```
### 第三步:编写消费者 (tool)
### 第三步:编写消费
```ts ignore-check
// packages/my-cap/tool-my-cap/src/index.ts
@@ -153,10 +153,10 @@ export function apply(ctx: Context) {
## 设计要点
- **不要预防性拆分** — 只有当你确实需要可替换实现时才拆三件套。一个简单的 tool 插件不需要拆分。
- **接口定义 Request/Result 类型**实现和消费只依赖接口包。
- **Explicit > Implicit** — 实现中的默认值处理应该是显式的 `resolve(request): Spec` 步骤,不是隐藏在 `run()` 中 `?? default`。
- **不要预防性拆分**:只有确实需要可替换实现时才拆分为三个包。简单的工具插件无需拆分。
- **接口拥有 Request/Result 类型**实现和消费只依赖接口包。
- **显式优于隐式**:实现应通过显式的 `resolve(request): Spec` 步骤处理默认值,而不是在 `run()` 中隐藏 `?? default`。
## 下一步
- [LLM 适配器](./llm-adapter.md)实现一个 LLM 后端(最常见的 seam 扩展
- [LLM 适配器](./llm-adapter.md)实现一个 LLM 后端,这是一种常见的能力 seam 扩展

View File

@@ -3,4 +3,4 @@
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/user/develop/practice/llm-adapter.md
llm-adapter.md: 7445688530c1ba61e5c065f9f5e49db6498da5b1
llm-adapter.zh.md: c30200314a01f7a61d48e3f47288013c31e5aef4
llm-adapter.zh.md: e491bc0b6ef633b0b40cb094faf07fc84445c026

View File

@@ -2,11 +2,11 @@
[English](llm-adapter.md) | 中文
本文介绍如何为 Harness 接入一个新的 LLM 提供方。
本文介绍如何为 Harness 接入新的模型提供方。
## 概述
LLM 适配器是一个继承 `LlmAdapter` 的类,实现 `stream()` 方法将 Harness 的统一请求格式转换为具体 API 调用。
LLM 适配器是一个继承 `LlmAdapter` 实现 `stream()` 方法的类,它会将 Harness 的提供方无关请求转换为具体提供方的 API 调用,并将响应转换回 Harness 分片
## 最小实现
@@ -51,7 +51,7 @@ export function apply(ctx: Context, config: Config) {
## StreamChunk 协议
`stream()` 必须按以下协议 yield chunk
`stream()` 必须按以下协议生成分片
```ts
import { CallId, type StreamChunk } from '@deepseek-ai/dsh-llm'
@@ -102,17 +102,17 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
### 关键规则
- 每个 `block-start` 必须有对应的 `block-end`
- `index` 从 0 递增,标识内容块顺序
- `tool-call-delta``argumentsDelta` 是 JSON 字符串的增量可以一次 yield 全部,也可以分多次)
- `finish` 必须是最后一个 chunk
- `usage``finish` 之前 yield
- 每个 `block-start` 必须有与之对应的 `block-end`
- `index` 从 0 开始递增,用于标识内容块顺序
- `tool-call-delta``argumentsDelta`原始 JSON 文本的增量可以在一个分片中完整生成,也可以分多个分片生成。
- `finish` 必须是最后一个分片。
- `usage` 必须`finish` 之前生成。
## GenerateOptions
`stream()` 接收仓库导出的 `GenerateOptions`。它包含模型名、由适配器有的推理强度 ID、对话历史、系统提示词、tool schema、生成参数、停止序列和中止信号完整字段以 `@deepseek-ai/dsh-llm` 导出的 TypeScript 类型为准。适配器必须将支持的字段映射到具体 API无法支持字段应抛出带稳定 code 的 `LlmError`,不静默丢弃。
`stream()` 接收仓库导出的 `GenerateOptions`。它包含模型适配器有的推理强度 ID、对话历史、系统提示词、工具 schema、生成参数、停止序列和中止信号完整字段以 `@deepseek-ai/dsh-llm` 导出的 TypeScript 类型为准。适配器必须将支持的字段映射到具体 API如果无法支持某个字段应抛出带稳定 code 的 `LlmError`,不静默丢弃。
请覆写 `resolveModel(provider, model, signal?)`,在一次查询中返回确切的提供方/模型身份以及可选的 `context``reasoning` 元数据。推理元数据包含有序的不透明 ID、展示名称以及可选的配置默认值请保留适配器给出的权威可选列表包括其上游能力 API 返回的 `off`不要将这些值提升为核心枚举。异步查询必须响应这个可选信号,取消和资源释放都能达到完全停稳。服务会校验聚合结果,并在调用 `stream()` 前拒绝显式指定但不受支持的推理强度;省略 `reasoning` 表示该模型没有可选的推理强度能力。
请覆写 `resolveModel(provider, model, signal?)`,在一次查询中返回确切的提供方模型身份以及可选的 `context``reasoning` 元数据。推理元数据包含有序的不透明 ID、展示名称以及可选的配置默认值请保留适配器给出的权威可选列表包括其上游能力 API 返回的 `off`,不要将这些值提升为核心枚举。异步查询必须响应可选信号,使取消和资源释放过程完全停稳。服务会校验聚合结果,并在调用 `stream()` 前拒绝显式指定但不受支持的推理强度;省略 `reasoning` 表示该模型没有可选的推理强度能力。
## 注册适配器
@@ -120,7 +120,7 @@ async function* exampleChunks(): AsyncIterable<StreamChunk> {
ctx.llm.registerAdapter(['model-name-1', 'model-name-2'], adapter)
```
第一个参数是该适配器支持的模型名列表。当用户在 `cordis.yml` 中配置 `model: model-name-1` 时,框架会路由到这个适配器。
第一个参数是该适配器支持的模型名列表。当用户在 `cordis.yml` 中配置 `model: model-name-1` 时,框架会将请求路由到适配器。
## 在 cordis.yml 中使用
@@ -145,7 +145,7 @@ ctx.llm.registerAdapter(['model-name-1', 'model-name-2'], adapter)
## 实战参考
仓库中两个完整实现可供参考
仓库中包含以下两个完整实现:
- `packages/llm/llm-deepseek/` — DeepSeek API 适配器OpenAI 兼容格式)
- `packages/llm/llm-pi-ai/` — Pi AI 适配器(不同的 API 格式)
@@ -154,7 +154,7 @@ ctx.llm.registerAdapter(['model-name-1', 'model-name-2'], adapter)
## 错误处理
适配器应将传输和协议故障作为带稳定 code 的 `LlmError` 抛出agent loop 会保留该错误及其 code诊断和策略使用。不要依赖普通 `Error` 被自动转换。每个提供方 HTTP 请求还必须合并 `attributionHeaders()`,并传递 `options.signal`。
适配器应通过带稳定 code 的 `LlmError` 抛出传输和协议故障agent loop(智能体循环)会保留该错误及其 code用于诊断和策略处理。不要依赖普通 `Error` 被自动转换。每个提供方 HTTP 请求还必须合并 `attributionHeaders()`,并传递 `options.signal`。
```ts
import {