niceeval 0.13.1 → 0.13.2-canary.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +1 -0
- package/README.md +9 -10
- package/README.zh.md +9 -10
- package/dist/agents/ai-sdk.cjs.map +1 -1
- package/dist/agents/ai-sdk.d.cts +1 -1
- package/dist/agents/ai-sdk.d.mts +1 -1
- package/dist/agents/ai-sdk.d.ts +1 -1
- package/dist/agents/coding-cli-versions.cjs +10 -1
- package/dist/agents/coding-cli-versions.cjs.map +1 -1
- package/dist/agents/coding-cli-versions.d.cts +8 -3
- package/dist/agents/coding-cli-versions.d.mts +8 -3
- package/dist/agents/coding-cli-versions.d.ts +8 -3
- package/dist/agents/coding-cli-versions.js +18 -12
- package/dist/agents/deepseek-harness.cjs +103 -0
- package/dist/agents/deepseek-harness.cjs.map +1 -0
- package/dist/agents/deepseek-harness.d.cts +12 -0
- package/dist/agents/deepseek-harness.d.mts +12 -0
- package/dist/agents/deepseek-harness.d.ts +12 -0
- package/dist/agents/deepseek-harness.js +7 -0
- package/dist/agents/deepseek-harness.js.map +1 -0
- package/dist/agents/index.cjs +5 -1
- package/dist/agents/index.cjs.map +1 -1
- package/dist/agents/index.d.cts +4 -0
- package/dist/agents/index.d.mts +4 -0
- package/dist/agents/index.d.ts +4 -0
- package/dist/agents/index.js +54 -50
- package/dist/agents/index.mjs +54 -50
- package/dist/agents/omp.cjs +170 -0
- package/dist/agents/omp.cjs.map +1 -0
- package/dist/agents/omp.d.cts +14 -0
- package/dist/agents/omp.d.mts +14 -0
- package/dist/agents/omp.d.ts +14 -0
- package/dist/agents/omp.js +7 -0
- package/dist/agents/omp.js.map +1 -0
- package/dist/agents/types.cjs.map +1 -1
- package/dist/agents/types.d.cts +2 -2
- package/dist/agents/types.d.mts +2 -2
- package/dist/agents/types.d.ts +2 -2
- package/dist/analysis/api.cjs +9 -1
- package/dist/analysis/api.cjs.map +1 -1
- package/dist/analysis/api.d.cts +21 -0
- package/dist/analysis/api.d.mts +21 -0
- package/dist/analysis/api.d.ts +21 -0
- package/dist/analysis/api.js +8 -6
- package/dist/i18n/en.cjs +7 -2
- package/dist/i18n/en.cjs.map +1 -1
- package/dist/i18n/en.d.cts +7 -2
- package/dist/i18n/en.d.mts +7 -2
- package/dist/i18n/en.d.ts +7 -2
- package/dist/o11y/parsers/hermes.cjs +1 -1
- package/dist/o11y/parsers/hermes.cjs.map +1 -1
- package/dist/report/built-in/analysis-values.cjs +21 -5
- package/dist/report/built-in/analysis-values.cjs.map +1 -1
- package/dist/report/built-in/analysis-values.d.cts +11 -9
- package/dist/report/built-in/analysis-values.d.mts +11 -9
- package/dist/report/built-in/analysis-values.d.ts +11 -9
- package/dist/report/built-in/overview.cjs +1 -0
- package/dist/report/built-in/overview.cjs.map +1 -1
- package/dist/report/built-in/result-components.cjs +58 -12
- package/dist/report/built-in/result-components.cjs.map +1 -1
- package/dist/report/built-in/result-components.d.cts +15 -0
- package/dist/report/built-in/result-components.d.mts +15 -0
- package/dist/report/built-in/result-components.d.ts +15 -0
- package/dist/report/built-in/run-membership-overview.cjs +100 -4
- package/dist/report/built-in/run-membership-overview.cjs.map +1 -1
- package/dist/report/built-in/standard.cjs +1 -0
- package/dist/report/built-in/standard.cjs.map +1 -1
- package/dist/report/built-in/standard.d.cts +1 -0
- package/dist/report/built-in/standard.d.mts +1 -0
- package/dist/report/built-in/standard.d.ts +1 -0
- package/dist/report/components/entity-lists/compute.cjs +40 -3
- package/dist/report/components/entity-lists/compute.cjs.map +1 -1
- package/dist/report/components/entity-lists/compute.d.cts +8 -6
- package/dist/report/components/entity-lists/compute.d.mts +8 -6
- package/dist/report/components/entity-lists/compute.d.ts +8 -6
- package/dist/report/components/entity-lists/content.cjs +31 -6
- package/dist/report/components/entity-lists/content.cjs.map +1 -1
- package/dist/report/components/entity-lists/index.cjs +3 -2
- package/dist/report/components/entity-lists/index.cjs.map +1 -1
- package/dist/report/components/entity-lists/index.d.cts +1 -1
- package/dist/report/components/entity-lists/index.d.mts +1 -1
- package/dist/report/components/entity-lists/index.d.ts +1 -1
- package/dist/report/definition/cell.cjs +3 -0
- package/dist/report/definition/cell.cjs.map +1 -1
- package/dist/report/definition/cell.d.cts +2 -0
- package/dist/report/definition/cell.d.mts +2 -0
- package/dist/report/definition/cell.d.ts +2 -0
- package/dist/report/definition/primitives.cjs +9 -2
- package/dist/report/definition/primitives.cjs.map +1 -1
- package/dist/runner/feedback/human.cjs +180 -16
- package/dist/runner/feedback/human.cjs.map +1 -1
- package/dist/runner/run.cjs +43 -0
- package/dist/runner/run.cjs.map +1 -1
- package/dist/sample/capability.cjs +93 -0
- package/dist/sample/capability.cjs.map +1 -1
- package/dist/sample/capability.d.cts +3 -1
- package/dist/sample/capability.d.mts +3 -1
- package/dist/sample/capability.d.ts +3 -1
- package/dist/sample/capability.js +8 -6
- package/dist/sandbox/docker-agent-image.cjs +5 -1
- package/dist/sandbox/docker-agent-image.cjs.map +1 -1
- package/dist/sandbox/docker-agent-image.d.cts +2 -0
- package/dist/sandbox/docker-agent-image.d.mts +2 -0
- package/dist/sandbox/docker-agent-image.d.ts +2 -0
- package/dist/sandbox/docker-agent-image.js +14 -10
- package/dist/sandbox/index.cjs +4 -2
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.d.cts +1 -1
- package/dist/sandbox/index.d.mts +1 -1
- package/dist/sandbox/index.d.ts +1 -1
- package/dist/sandbox/index.js +120 -116
- package/dist/sandbox/index.mjs +120 -116
- package/docs-site/images/hitl-handshake-zh.svg +3 -3
- package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +3 -3
- package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/codex-sdk.mdx +5 -5
- package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
- package/docs-site/zh/examples/integrations/pi-sdk.mdx +5 -5
- package/docs-site/zh/explanation/adapter.mdx +5 -5
- package/docs-site/zh/explanation/assert.mdx +1 -1
- package/docs-site/zh/explanation/drive.mdx +3 -1
- package/docs-site/zh/explanation/evals.mdx +3 -1
- package/docs-site/zh/explanation/hitl.mdx +7 -7
- package/docs-site/zh/reference/builtin-agents.mdx +4 -4
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/events.mdx +10 -10
- package/docs-site/zh/reference/official-adapters.mdx +1 -1
- package/docs-site/zh/reference/results-data.mdx +6 -0
- package/docs-site/zh/tutorials/docker-in-docker.mdx +3 -0
- package/docs-site/zh/tutorials/nixos-managed-dind.mdx +160 -0
- package/docs-site/zh/tutorials/rerun-and-cache.mdx +16 -0
- package/docs-site/zh/tutorials/viewing-results.mdx +19 -1
- package/docs-site/zh/tutorials/write-send.mdx +7 -6
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ HITL(human-in-the-loop,人工介入)指 agent 在执行中间停下来,
|
|
|
14
14
|
- **Codex**:审批模式(suggest / auto-edit 等)决定哪些动作要先经人确认,本质是同一扇门。
|
|
15
15
|
- **AI SDK 应用**:工具不带 `execute` 时,工具调用会浮到前端,由界面上的人确认后把结果交回(`addToolResult`),模型再继续——这是自研应用里最常见的审批门写法。
|
|
16
16
|
|
|
17
|
-
对被测对象来说,这条"停下来等人"的分支和别的行为一样需要回归:批准后该发生的事发生了吗?拒绝后真的没做吗?问错了人、停错了地方呢?但评估用例是自动跑的,里面没有人。[NiceEval](https://niceeval.com/) 的做法是把"人"
|
|
17
|
+
对被测对象来说,这条"停下来等人"的分支和别的行为一样需要回归:批准后该发生的事发生了吗?拒绝后真的没做吗?问错了人、停错了地方呢?但评估用例是自动跑的,里面没有人。[NiceEval](https://niceeval.com/) 的做法是把"人"折叠成三个可编排的动作:用 `t.check(turn.status, equals("waiting"))` 确认 agent 停下、用 `t.requireInputRequest()` 检查它在等什么、再用 `t.respond()` 替人回答。批准、拒绝、补充信息,从此都是能写进评估用例的路径。
|
|
18
18
|
|
|
19
19
|
## 停轮如何表达:waiting + input.requested
|
|
20
20
|
|
|
@@ -36,10 +36,10 @@ HITL(human-in-the-loop,人工介入)指 agent 在执行中间停下来,
|
|
|
36
36
|
## 评估侧:三个动作
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
import { toolMatch } from "niceeval/expect";
|
|
39
|
+
import { equals, toolMatch } from "niceeval/expect";
|
|
40
40
|
|
|
41
41
|
const draft = await t.send("给老板发一封周报邮件。");
|
|
42
|
-
draft.
|
|
42
|
+
t.check(draft.status, equals("waiting")); // ① 断言这一轮停下了
|
|
43
43
|
|
|
44
44
|
const req = t.requireInputRequest({ action: "send_email" }); // ② 取出待答请求
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ await t.respond({ request: req, optionId: "approve" }); // ③ 替人回答,发
|
|
|
47
47
|
t.calledTool(toolMatch("send_email", { status: "completed" }));
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
- `t.
|
|
50
|
+
- `t.check(draft.status, equals("waiting"))` 断言这一轮确实以 waiting 停下。反过来 `draft.succeeded()` 在 waiting 轮上会失败——停轮是显式状态,不会被当成"跑完了"。
|
|
51
51
|
- `t.requireInputRequest(filter)` 从待处理请求里精确取一个(按 `id` / `prompt` / `action` / `optionIds` 等字段匹配),匹配到 0 个或超过 1 个都会抛,多个请求并停时靠它消歧。
|
|
52
52
|
- `t.respond(...)` 回答并发出下一轮。同类请求要给同一个答案时(比如逐个批准一批改动),`t.respondAll(optionId)` 一次处理完。
|
|
53
53
|
|
|
@@ -57,13 +57,13 @@ t.calledTool(toolMatch("send_email", { status: "completed" }));
|
|
|
57
57
|
|
|
58
58
|
HITL 对 Adapter 的全部要求就三件事——前两件发生在停下的那一轮,第三件发生在回答到达的下一轮:
|
|
59
59
|
|
|
60
|
-
1. **停轮时如实返回 `status: "waiting"`**,不要把挂起报成 `"completed"`,否则 `
|
|
60
|
+
1. **停轮时如实返回 `status: "waiting"`**,不要把挂起报成 `"completed"`,否则 `t.check(turn.status, equals("waiting"))` 会失败、`succeeded()` 可能假通过。
|
|
61
61
|
2. **每个待回答的问题吐一条 `input.requested`**,`id` 稳定、字段尽量填全——评估侧的检查和对位全靠它们。
|
|
62
62
|
3. **下一次 `send` 先交裁决、再续跑**:从 `input.responses` 按 `requestId` 把裁决交回应用(不要按顺序猜),然后接着上一轮挂起的地方继续,而不是重发请求。
|
|
63
63
|
|
|
64
64
|
"挂起的现场"(比如读了一半的 SSE 流)存在本会话线的 `ctx.session` 上。Adapter 用 `createSessionSlot<Pending>()` 声明私有 slot,停轮时 `ctx.session.set(slot, pending)`,回答轮 `ctx.session.take(slot)` 取回,取到即清除。怎么和流式驱动拼起来,见[写 send](/zh/tutorials/write-send#第五步:hitl) 第五步的完整骨架。
|
|
65
65
|
|
|
66
|
-
和 [NiceEval](https://niceeval.com/) 的其它能力一样,HITL 没有布尔声明:**做到了就是有**。send 返回过 `"waiting"` 并吐了 `input.requested`,`t.respond` 就能工作。没做到,eval
|
|
66
|
+
和 [NiceEval](https://niceeval.com/) 的其它能力一样,HITL 没有布尔声明:**做到了就是有**。send 返回过 `"waiting"` 并吐了 `input.requested`,`t.respond` 就能工作。没做到,eval 会在状态检查或 `requireInputRequest()` 上明确失败,而不是静默假通过。能力如何从构造中来,见 [Adapter](/zh/explanation/adapter#能力从哪来:构造证明,不是问卷)。
|
|
67
67
|
|
|
68
68
|
## 拒绝不是故障
|
|
69
69
|
|
|
@@ -81,7 +81,7 @@ HITL 对 Adapter 的全部要求就三件事——前两件发生在停下的那
|
|
|
81
81
|
|
|
82
82
|
## 相关阅读
|
|
83
83
|
|
|
84
|
-
- [Drive](/zh/explanation/drive#人工介入-hitl) —
|
|
84
|
+
- [Drive](/zh/explanation/drive#人工介入-hitl) — 评估侧的完整用法:状态检查、`t.requireInputRequest()`、`t.respond()` / `t.respondAll()`。
|
|
85
85
|
- [Adapter](/zh/explanation/adapter#不同回答的入参) — 回答到 Adapter 的结构化入参,四种典型形态。
|
|
86
86
|
- [写 send](/zh/tutorials/write-send) — Adapter 侧实操:typed session slot 与流式 + HITL 的完整骨架。
|
|
87
87
|
- [接入你的 Agent](/zh/tutorials/connect-your-agent) — 接入全景:最小接入、参数通道与增量地图。
|
|
@@ -10,17 +10,17 @@ description: "NiceEval 内置的 claude-code、codex、bub 适配器分别做到
|
|
|
10
10
|
|
|
11
11
|
| 能力 | 对应的断言 / API |
|
|
12
12
|
|---|---|
|
|
13
|
-
| 收发消息(基础契约) | `t.send()`(可多次调用)、`t.reply`、`turn.
|
|
14
|
-
| 事件流完整性 | `calledTool` / `
|
|
13
|
+
| 收发消息(基础契约) | `t.send()`(可多次调用)、`t.reply`、`t.check(turn.data, equals(...))` / `matches(...)`、按状态判断的 `t.succeeded()` |
|
|
14
|
+
| 事件流完整性 | `calledTool` / `usedNoTools` / `maxToolCalls` / `noFailedActions` / `event` 等作用域断言;`toolOrder` / `eventOrder` 只在 Turn 或 Session 上接收 matcher。消息用 `t.check(turn.message, includes(...))` 或 `t.check(turn.message, pattern(...))`。**负断言(`notCalledTool` 等)有完整事件流才可信** |
|
|
15
15
|
| 会话续接 | 跨轮记忆断言、`t.newSession()` 会话隔离 |
|
|
16
|
-
| HITL(人工介入) | `t.
|
|
16
|
+
| HITL(人工介入) | `t.check(turn.status, equals("waiting"))`、`t.requireInputRequest()`、`t.respond()` / `t.respondAll()` |
|
|
17
17
|
| `tracing` | trace decoding、`niceeval view` 的调用瀑布图 |
|
|
18
18
|
|
|
19
19
|
## 三个内置 agent 分别做到哪
|
|
20
20
|
|
|
21
21
|
| Agent | 收发 | 事件流 | 会话续接 | HITL | tracing | 备注 |
|
|
22
22
|
|---|---|---|---|---|---|---|
|
|
23
|
-
| `claude-code` | ✅ | ✅ | ✅(`claude --resume <id>`) | ❌ | ✅(`http/protobuf` → OTLP,beta 开关) | 内置 parser 自动吐 `compaction`
|
|
23
|
+
| `claude-code` | ✅ | ✅ | ✅(`claude --resume <id>`) | ❌ | ✅(`http/protobuf` → OTLP,beta 开关) | 内置 parser 自动吐 `compaction` 原始事件,供报告和诊断读取;当前没有公开 `eventMatch` selector |
|
|
24
24
|
| `codex` | ✅ | ✅ | ✅(`codex exec resume <id>`) | ❌ | ✅(`http/json` → OTLP) | 内置 parser 自动吐 `compaction` 事件 |
|
|
25
25
|
| `bub` | ✅ | ✅ | ✅(`--session-id` + tape 续接) | ❌ | ✅(`http/protobuf` → OTLP) | 内置 parser 自动吐 `compaction` 事件 |
|
|
26
26
|
|
|
@@ -19,7 +19,7 @@ description: "Agent 的构造方式、运行时行为与必填的六通道 evide
|
|
|
19
19
|
| `tracing`(OTLP 接收 → `niceeval view` 瀑布图) | CLI 型 Agent 配置 `tracing`。长驻应用在 config 配置固定的 `telemetry` 端口 | Span 只进时间瀑布图,不能填补行为证据缺口 |
|
|
20
20
|
| 跨轮续接与 `t.newSession()` 隔离 | 用 `ctx.session.id` / `capture` 续接后端会话,或用 `ctx.session.get` / `set` 保存 Adapter 私有的强类型历史 | 会话接法决定实际行为,不从一个 evidence bit 推断 |
|
|
21
21
|
| HITL(`t.respond()`) | `send` 返回 `status: "waiting"` 和 `input.requested` | 下一轮回答按结构化 request ID 对位 |
|
|
22
|
-
| Compaction 可见 | 官方 parser 或完整的手写映射发出 `compaction` 事件 |
|
|
22
|
+
| Compaction 可见 | 官方 parser 或完整的手写映射发出 `compaction` 事件 | 原始事件进入报告与诊断;当前没有公开 `eventMatch` selector |
|
|
23
23
|
|
|
24
24
|
## 必填的六通道声明
|
|
25
25
|
|
|
@@ -200,14 +200,14 @@ token 用量或 OTel span 反推得到)。与顶层 `estimatedCostUSD`(价目表
|
|
|
200
200
|
| `message` | agent(或用户)说了一段话 | `turn.message`、`turn.event(eventMatch("message", ...))`、Judge 的材料 |
|
|
201
201
|
| `operation.started`(`kind: "tool"`) | 发起一次工具调用 | `turn.calledTool(...)`、`turn.toolOrder(...)`、`turn.event(...)` |
|
|
202
202
|
| `operation.finished`(`kind: "tool"`) | 该次工具调用的结果 | 带状态条件的 `ToolMatch` |
|
|
203
|
-
| `operation.started`(`kind: "subagent"`) | 委派一个子 agent |
|
|
204
|
-
| `operation.finished`(`kind: "subagent"`) | 子 agent 返回 | `
|
|
205
|
-
| `input.requested` | 停下来等人输入(HITL) | `t.
|
|
206
|
-
| `thinking` | 思考文本 | `
|
|
207
|
-
| `compaction` | 上下文被压缩 | `
|
|
208
|
-
| `error` | 本轮出错 | `
|
|
203
|
+
| `operation.started`(`kind: "subagent"`) | 委派一个子 agent | 原始事件与报告;当前没有子 agent 专用断言 |
|
|
204
|
+
| `operation.finished`(`kind: "subagent"`) | 子 agent 返回 | 原始事件与报告;失败会进入 `noFailedActions()` 的汇总事实 |
|
|
205
|
+
| `input.requested` | 停下来等人输入(HITL) | `t.check(turn.status, equals("waiting"))`、`t.requireInputRequest()` |
|
|
206
|
+
| `thinking` | 思考文本 | view 展示;当前没有 `eventMatch` selector |
|
|
207
|
+
| `compaction` | 上下文被压缩 | view 展示;当前没有 `eventMatch` selector |
|
|
208
|
+
| `error` | 本轮出错 | view 展示;当前没有 `eventMatch` selector |
|
|
209
209
|
|
|
210
|
-
事件断言共用三个入口:`event(match)`、`notEvent(match)` 与 `eventOrder(matches)`;它们都接收 `eventMatch(...)`,不另造 selector object 或匿名事件 predicate。
|
|
210
|
+
事件断言共用三个入口:`event(match)`、`notEvent(match)` 与 `eventOrder(matches)`;它们都接收 `eventMatch(...)`,不另造 selector object 或匿名事件 predicate。当前 `eventMatch` 只有 `message`、工具 `operation.started` 与工具 `operation.finished` 三种形状,不能把全部原始 `StreamEvent` 当成可断言 selector。
|
|
211
211
|
|
|
212
212
|
## 逐事件说明
|
|
213
213
|
|
|
@@ -280,7 +280,7 @@ events.push({
|
|
|
280
280
|
output?: JsonValue, status: "completed" | "failed" }
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
-
被测系统把任务委派给子 agent(等它返回)时吐这一对,`operationId`
|
|
283
|
+
被测系统把任务委派给子 agent(等它返回)时吐这一对,`operationId` 配对规则同上。它们会保留在原始事件流与报告中,但当前公开 Assert-first API 没有 `calledSubagent`,`eventMatch(...)` 也不能选择子 agent operation;不要凭空换成另一个 selector。
|
|
284
284
|
|
|
285
285
|
### `input.requested` —— 停下等人(HITL)
|
|
286
286
|
|
|
@@ -304,11 +304,11 @@ agent 停轮等人时,每个待回答的问题吐一条,同时该 Turn 的 `
|
|
|
304
304
|
{ type: "error", message: string }
|
|
305
305
|
```
|
|
306
306
|
|
|
307
|
-
有就吐,没有不硬造。`compaction` 主要来自 coding agent CLI
|
|
307
|
+
有就吐,没有不硬造。`compaction` 主要来自 coding agent CLI(上下文满了自动压缩)。这几种原始事件保留给报告与诊断;当前公开 `eventMatch` 不能选择它们。
|
|
308
308
|
|
|
309
309
|
## 映射的三条纪律
|
|
310
310
|
|
|
311
|
-
1. **时序即事实**:事件按真实发生顺序排。`toolOrder` / `eventOrder` 用单调 cursor 匹配不同 occurrence
|
|
311
|
+
1. **时序即事实**:事件按真实发生顺序排。`toolOrder` / `eventOrder` 用单调 cursor 匹配不同 occurrence 的子序列,顺序错了断言就失真。`eventOrder` 只排公开 `eventMatch` 支持的事件。
|
|
312
312
|
2. **`operationId` 配对**:每个 started operation 都要有同 `kind`、同 id 的 finished operation。只有配对完成,框架才能把状态与 input 归到同一个逻辑工具 occurrence。
|
|
313
313
|
3. **完整性必须显式**:官方转换器按实际 adapter 能力声明 evidence coverage。负向断言在相关输入或 action coverage 不完整时是 `unavailable`,不会把“没观察到”冒充“没有发生”。手工映射同样要如实声明覆盖范围,见[能力位参考](/zh/reference/capabilities)。
|
|
314
314
|
|
|
@@ -16,7 +16,7 @@ description: "NiceEval 内置的 Sandbox 和非 Sandbox 适配器分别是什么
|
|
|
16
16
|
|
|
17
17
|
- **鉴权**:`ANTHROPIC_API_KEY`(工厂参数 `apiKey` 可覆盖),可选 `ANTHROPIC_BASE_URL`(工厂参数 `baseUrl`)。
|
|
18
18
|
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段写进 Sandbox 里用户级的 `~/.claude.json`(顶层 `mcpServers` 字段)。两种形态按字段区分:本地 stdio 进程写 `command`(可带 `args` / `env`)。远程 Streamable HTTP 端点写 `url`(可带 `headers`,逐字进请求头,常用于 `Authorization`),写成 `{ "type": "http", "url": …, "headers": … }` 条目。`url` 要 Sandbox 内可达:服务跑在你自己机器上时,先用 cloudflared / tailscale 这类隧道暴露成公网地址。
|
|
19
|
-
- **装 Skill**:`skills: SkillSpec[]`。本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进 Sandbox 的 project 级 `.claude/skills/<name>/`,claude CLI
|
|
19
|
+
- **装 Skill**:`skills: SkillSpec[]`。本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进 Sandbox 的 project 级 `.claude/skills/<name>/`,claude CLI 原生发现。adapter 会把原生 `Skill` 调用归一为 `skill.loaded` 原始事件,不重复记成工具调用;当前公开 Assert-first API 没有专用 Skill 断言,也不能把它伪装成 `calledTool("Skill", ...)`。
|
|
20
20
|
- **装原生 Plugin**:`plugins: ClaudeCodePluginSpec[]`,每一项声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 claude-code,传不进 codex。复用 Sandbox 的 Experiment 不用额外处理残留:每条 Attempt 开始前,同名 marketplace 注册与已装 Plugin 都会按声明的 `source` / `ref` 重新安装。
|
|
21
21
|
- **官方配置文件**:`settingsFile` 是运行 NiceEval 的机器上的本地项目路径,不是 Sandbox 内路径。它指向一份完整的 Claude Code `settings.json`。路径相对项目根,只允许普通相对路径或 `./` 前缀。`..`、绝对路径、`~` 和解析后逃出项目根的符号链接都会报错。
|
|
22
22
|
|
|
@@ -12,6 +12,8 @@ Sample 固定本次选择的 Run、logical Slot 与分母。每个位置保留 `
|
|
|
12
12
|
|
|
13
13
|
`--run` 保留指定 Run 的完整计划范围。`project-current` 只保留仍与当前项目身份匹配的范围。两种选择都不会因为某个度量缺少输入而缩小分母。
|
|
14
14
|
|
|
15
|
+
`project-current` 的成员单位是匹配的 Run slot occurrence,不是去重后的 `(experiment, eval, ordinal)`。同一位置出现在多个匹配 Run 中时,每个 occurrence 都进入 `MetricValue.total`。重复运行或逐条 `accept` 因此可能增加分母。
|
|
16
|
+
|
|
15
17
|
Sample 只能在 `load()`、Page 或组合组件执行期间传给 Report facade。回调结束后,页面只保留已经关闭的值,不能再次读取结果。
|
|
16
18
|
|
|
17
19
|
## 用 `aggregate()` 取得 rows
|
|
@@ -58,6 +60,10 @@ Attempt Evidence、Observability、File Changes、Sources 和 Sandbox history
|
|
|
58
60
|
|
|
59
61
|
`available`、`partial`、`empty`、`unsupported` 和 `failed` 都是不同状态。不要把 `null` 当成零,也不要用只剩下的可见行数替换总分母。
|
|
60
62
|
|
|
63
|
+
例如 `formatMetricValue()` 输出的 `2 / 4 slot · partial` 表示四个已选 occurrence 中只有两个提供该 Measure 的输入。这组计数只描述覆盖率;它不是 earned score、通过率或工作完成比例。Measure 自己的 `value` 才是读数。
|
|
64
|
+
|
|
65
|
+
默认 Overview 不把这组计数重复追加到每个指标单元格。它按题型显示通过率或 earned score,并在结果不完整时用独立的 `Result coverage` 区域显示可用结果数。终端把这个区域渲染成有标题的边框;底层 `MetricValue` 的字段保持不变。
|
|
66
|
+
|
|
61
67
|
## 相关页面
|
|
62
68
|
|
|
63
69
|
- [报告 API](/zh/reference/report-components):Page、组件、JSON 和全站构建边界。
|
|
@@ -180,6 +180,9 @@ export default defineExperiment({
|
|
|
180
180
|
共享宿主或不可信 Agent使用 managed模式。它在 inner daemon之外增加 profile attestation、单容器
|
|
181
181
|
资源限制、跨进程容量准入、独占外层网络和 watchdog恢复:
|
|
182
182
|
|
|
183
|
+
NixOS宿主先按照[在 NixOS上配置 Managed DinD](/zh/tutorials/nixos-managed-dind)部署并验证 profile,
|
|
184
|
+
再配置下面的 Experiment。
|
|
185
|
+
|
|
183
186
|
```ts title="experiments/dind-managed.ts"
|
|
184
187
|
import { defineExperiment } from "niceeval";
|
|
185
188
|
import { codexAgent } from "niceeval/adapter";
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "在 NixOS 上配置 Managed DinD"
|
|
3
|
+
sidebarTitle: "NixOS Managed DinD"
|
|
4
|
+
description: "使用 NiceEval 的 NixOS module 部署 managed rootless Docker profile,并通过 doctor 验证宿主环境。"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Managed rootless DinD 适合在共享 NixOS 宿主上运行不可信 Agent。NiceEval 的 NixOS module 会创建专用
|
|
8
|
+
系统用户、rootless Docker daemon、容量受限的数据盘、资源 slice 和 watchdog。日常运行 NiceEval
|
|
9
|
+
不需要 `sudo`,但首次部署和更新系统配置需要管理员权限。
|
|
10
|
+
|
|
11
|
+
如果任务可以信任 Agent,或者只在一次性 VM 中运行,先阅读[让 Sandbox 使用 Docker](/zh/tutorials/docker-in-docker)
|
|
12
|
+
选择 Docker socket 或 raw privileged DinD。它们不需要本页的宿主 profile。
|
|
13
|
+
|
|
14
|
+
## 前置条件
|
|
15
|
+
|
|
16
|
+
开始前确认:
|
|
17
|
+
|
|
18
|
+
- NixOS 使用 flake 管理系统配置;
|
|
19
|
+
- 宿主使用 systemd 和 cgroup v2;
|
|
20
|
+
- 你知道日常运行 NiceEval 的 NixOS 用户名;
|
|
21
|
+
- 宿主有足够的 CPU、内存和磁盘留给评估容器。
|
|
22
|
+
|
|
23
|
+
下面假设系统配置仓库已经有 `flake.nix`,配置名是 `my-host`,日常用户名是 `alice`。请把它们替换成
|
|
24
|
+
自己的值。
|
|
25
|
+
|
|
26
|
+
## 引入 NiceEval module
|
|
27
|
+
|
|
28
|
+
在系统配置的 `flake.nix` 中加入 NiceEval input,并把 module 放进主机的 `modules`:
|
|
29
|
+
|
|
30
|
+
```nix title="flake.nix"
|
|
31
|
+
{
|
|
32
|
+
inputs = {
|
|
33
|
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
34
|
+
niceeval.url = "github:NiceEval/NiceEval";
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
outputs = { self, nixpkgs, niceeval, ... }: {
|
|
38
|
+
nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
|
|
39
|
+
system = "x86_64-linux";
|
|
40
|
+
modules = [
|
|
41
|
+
niceeval.nixosModules.default
|
|
42
|
+
./configuration.nix
|
|
43
|
+
];
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
第一次运行 Nix 命令后,Nix 会把 NiceEval 的具体 commit 写进你的 `flake.lock`。提交这份锁文件,避免
|
|
50
|
+
不同机器在未确认的时间使用不同 module 版本。
|
|
51
|
+
|
|
52
|
+
## 声明 profile
|
|
53
|
+
|
|
54
|
+
在 NixOS 配置中声明一个名为 `default` 的 profile:
|
|
55
|
+
|
|
56
|
+
```nix title="configuration.nix"
|
|
57
|
+
{ ... }:
|
|
58
|
+
{
|
|
59
|
+
services.niceeval.dockerProfiles.default = {
|
|
60
|
+
enable = true;
|
|
61
|
+
accessUsers = [ "alice" ];
|
|
62
|
+
|
|
63
|
+
capacity = {
|
|
64
|
+
cpus = 4;
|
|
65
|
+
memory = "8G";
|
|
66
|
+
pids = 4096;
|
|
67
|
+
maxContainers = 2;
|
|
68
|
+
maxBuilds = 1;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
aggregate = {
|
|
72
|
+
cpus = 6;
|
|
73
|
+
memory = "12G";
|
|
74
|
+
pids = 6144;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
storage = {
|
|
78
|
+
size = "30G";
|
|
79
|
+
backing = "loop-ext4";
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`capacity` 是 NiceEval 可以分配给评估任务的预算。`aggregate` 是 daemon、构建进程、watchdog 和评估
|
|
86
|
+
容器共同受到的硬限制,因此每一项都不能小于 `capacity`。两者之间的余量留给宿主侧开销。
|
|
87
|
+
|
|
88
|
+
`loop-ext4` 会在 `/var/lib/niceeval/docker-profiles/default.img` 创建稀疏镜像,并挂载为 Docker
|
|
89
|
+
data root。`size` 是这个 profile 的磁盘上限,不代表 rebuild 时立即占用同等物理空间。
|
|
90
|
+
|
|
91
|
+
## 应用系统配置
|
|
92
|
+
|
|
93
|
+
先检查配置能够求值,再切换系统:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
nix flake check
|
|
97
|
+
sudo nixos-rebuild switch --flake .#my-host
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
module 会把 `alice` 加入 profile 的 access group。rebuild 完成后退出当前登录会话并重新登录,让新组
|
|
101
|
+
成员身份生效。只在旧 shell 中重新运行命令不会刷新 supplementary groups。
|
|
102
|
+
|
|
103
|
+
重新登录后检查服务:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
systemctl status niceeval-docker-profile-default.service
|
|
107
|
+
systemctl status niceeval-docker-profile-watchdog-default.service
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
两个服务都应显示 `active (running)`。失败时读取对应 unit 的日志:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
journalctl -u niceeval-docker-profile-default.service -b
|
|
114
|
+
journalctl -u niceeval-docker-profile-watchdog-default.service -b
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 验证 profile
|
|
118
|
+
|
|
119
|
+
进入已经安装 NiceEval 的评估项目,以日常用户运行:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
pnpm exec niceeval docker profile list
|
|
123
|
+
pnpm exec niceeval docker profile doctor default --smoke
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`list` 应显示 `default`。`doctor --smoke` 会检查 descriptor、Unix socket、cgroup、容量和 watchdog,
|
|
127
|
+
然后启动一个受限的外层容器及一个内层 Alpine 容器。所有项目都通过后,宿主 profile 才可以用于正式
|
|
128
|
+
评估。
|
|
129
|
+
|
|
130
|
+
不要用 `sudo pnpm exec niceeval ...` 绕过权限错误。日常用户无法访问 profile 时,先确认用户名已经写入
|
|
131
|
+
`accessUsers`,再重新登录并重跑 doctor。
|
|
132
|
+
|
|
133
|
+
## 在 Experiment 中使用
|
|
134
|
+
|
|
135
|
+
宿主通过验收后,在 Experiment 的 Docker Sandbox 中引用同一个别名:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
dockerAccess: {
|
|
139
|
+
mode: "dind",
|
|
140
|
+
isolation: "managed-rootless",
|
|
141
|
+
profile: "default",
|
|
142
|
+
},
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Experiment 还必须声明单容器 CPU、内存、PID、只读 rootfs 和 tmpfs。完整配置见
|
|
146
|
+
[让 Sandbox 使用 Docker](/zh/tutorials/docker-in-docker#managed-rootless-dind)。先用较小的
|
|
147
|
+
`maxConcurrency` 运行一次,再根据 profile 的 `capacity` 增加并发。
|
|
148
|
+
|
|
149
|
+
## 更新 module
|
|
150
|
+
|
|
151
|
+
需要更新 NiceEval module 时,在系统配置仓库更新对应 input,检查 diff 后重新构建:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
nix flake update niceeval
|
|
155
|
+
nix flake check
|
|
156
|
+
sudo nixos-rebuild switch --flake .#my-host
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
更新会改变宿主服务,不能只更新评估项目里的 npm 依赖。保留旧 generation,确认 doctor 和实际评估都
|
|
160
|
+
通过后再清理 NixOS generation。
|
|
@@ -70,6 +70,22 @@ npx niceeval accept @1K1P0VJAPVJ12
|
|
|
70
70
|
|
|
71
71
|
`accept` 先检查所有输入,再发布一个以 `reference` 采用该 Attempt 的新 Run。操作者理由、差异和资格会随该 Run 保存。它不会修改源 Attempt,也不会复制源 Attempt 的业务值。
|
|
72
72
|
|
|
73
|
+
新 Run 保留该 Experiment 当前完整的 expected-slot 分母。命令中选中的 slot 写作 `accepted`,同 Run 中没有被选中的 sibling slot 写作 `not-dispatched`。因此一次只接受一个 locator 时,这个 Run 的其它位置可能没有指标输入。
|
|
74
|
+
|
|
75
|
+
每次 `accept` 调用都会发布新 Run,不会修改或合并上一次调用。连续接受同一 Experiment 的多个 locator 后,`project-current` 会保留这些仍匹配的 Run occurrence。默认 Overview 会在有边框的 `Result coverage` 区域提示只有部分预期结果可用;这里的可用数是结果覆盖率,不是通过率或 score。
|
|
76
|
+
|
|
77
|
+
需要接受同一 Experiment 的多个结果时,把 locator 放进同一条命令:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
npx niceeval accept @1K1P0VJAPVJ12 @1K1P0VJAPVJ13
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
同一次调用会为该 Experiment 发布一个 Run,并让两个目标 slot 都成为 `accepted`。需要核对某次接受的精确分母时,复制成功反馈中的 Run ID:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
npx niceeval show --run <accepted-run-id>
|
|
87
|
+
```
|
|
88
|
+
|
|
73
89
|
## 发布后不可修改
|
|
74
90
|
|
|
75
91
|
Attempt 发布后不可修改。每一次查看都读取当时 Record 中已发布的值;新 Run 或新事实只会以新的发布出现。若 Attempt 或引用无效,Sample 将该 slot 标为 `core-invalid`,而不是 `not-recorded`。
|
|
@@ -29,6 +29,8 @@ pnpm exec niceeval show --run 7b8d2ea4-b840-4870-9840-f85a436a5527
|
|
|
29
29
|
|
|
30
30
|
不带 locator、`--run` 或 `--experiment` 时,命令使用 `project-current`:它保留全部仍与当前项目身份匹配的已发布结果,而不是只选最新一条。需要阅读历史结果时,传入完整 `--run`;要限制当前项目结果时,重复传入完整的 `--experiment <id>`。
|
|
31
31
|
|
|
32
|
+
`project-current` 按 Run 中的 slot occurrence 保留结果。它不会把多个 Run 里同名的 Experiment、评估用例和 Attempt 序号合并成一个位置。重复运行或分多次执行 `accept` 都可能增加当前 `Sample` 的分母。
|
|
33
|
+
|
|
32
34
|
Sample 记录每个位置的状态:
|
|
33
35
|
|
|
34
36
|
| 状态 | 含义 |
|
|
@@ -66,6 +68,22 @@ pnpm exec niceeval show --run <run-id> --report ./reports/quality.tsx --json
|
|
|
66
68
|
|
|
67
69
|
JSON 不包含其它 Page、HTML、React tree、站点 identity 或 Record 读取能力。
|
|
68
70
|
|
|
71
|
+
## 按题型读取主读数
|
|
72
|
+
|
|
73
|
+
默认报告按评估类型选择主读数:
|
|
74
|
+
|
|
75
|
+
- Pass Eval 显示通过率。
|
|
76
|
+
- Score Eval 显示 earned score。
|
|
77
|
+
- mixed Experiment 显示 Score,并在 Pass Eval 子行显示通过率,不把两者合成百分比。
|
|
78
|
+
|
|
79
|
+
Score Eval 上声明的 `points` 是评分项的贡献权重,不是 Experiment 的固定满分。动态执行、条件分支或不可用材料可能让评分项集合不同,因此默认报告不会自动把 earned score 除以所有源码里声明的 `points`。
|
|
80
|
+
|
|
81
|
+
项目没有配置 Report 时,`project-current` 使用单页 `default-overview`。显式传入 `--report standard` 会使用相同的题型主读数,并额外提供 Attempts、Traces、Experiment 和 Attempt 详情页。增加详情页不会改变 Score 或通过率的计算口径。
|
|
82
|
+
|
|
83
|
+
默认 Overview 让主读数保持独立:Score 只显示 earned score,耗时和 token 也只显示各自的值。它不会在这些单元格后面追加容易与分数混淆的 `2 / 4 slot · partial`。
|
|
84
|
+
|
|
85
|
+
如果部分预期结果不可用,Overview 会在 KPI 和实验表之外显示独立的 `Result coverage` 区域。终端中的这个区域有边框,标题行直接写出“多少个结果可用”;它说明结果完整度,不表示分数、通过率或工作完成比例。结果完整时不显示这个区域。
|
|
86
|
+
|
|
69
87
|
## 在浏览器读取完整站点
|
|
70
88
|
|
|
71
89
|
```sh
|
|
@@ -81,7 +99,7 @@ view 在启动 server 前枚举所有显式声明的普通 Page 与参数 Page
|
|
|
81
99
|
|
|
82
100
|
## 读懂不完整数据
|
|
83
101
|
|
|
84
|
-
`MetricValue` 的 `samples` 是实际贡献数,`total` 是既定分母,`state` 和 `issues`
|
|
102
|
+
`MetricValue` 的 `samples` 是实际贡献数,`total` 是既定分母,`state` 和 `issues` 说明为什么数据不完整。自定义 Report 使用 `formatMetricValue()` 时仍可得到 `2 / 4 slot · partial` 这类紧凑格式;它表示四个已选位置中有两个提供该指标,不是 score,也不是“完成了一半”。默认 Overview 改用上面的独立覆盖区域。`partial` 可以有数值;`empty`、`unsupported` 与 `failed` 不是零。Evidence refs 保留可复核的定位信息。
|
|
85
103
|
|
|
86
104
|
Analysis 数据问题继续作为页面内容显示。相反,选中 Page 的 callback、参数 key 或成员资格失败会让 `show` 返回单目标错误;任何全站 Page、路径、资源或预算失败都会阻止新的站点发布。
|
|
87
105
|
|
|
@@ -75,7 +75,7 @@ if (!res.requestId) {
|
|
|
75
75
|
|
|
76
76
|
`progress` 不落盘。diagnostic 会写入 Attempt-owned 通道,并从已选 Run 的详情页面回顾。HTTP 连接失败或响应无法解析时直接抛错;runner 会记录错误发生在 `agent.run`,并在 `niceeval.verdict` 通道形成 `errored` Verdict。
|
|
77
77
|
|
|
78
|
-
**这一步支持**:`t.reply
|
|
78
|
+
**这一步支持**:`t.reply` 的直接值检查(`t.check(t.reply, includes(...))` 或 `pattern(...)`)、Judge 的全部对话材料,以及 Experiment 侧的**模型对比**。`ctx.model` 来自 Experiment 的 `model`。运行器原样传递,Adapter 只负责转发。接入等级见 [Tier](/zh/explanation/tier)。
|
|
79
79
|
|
|
80
80
|
它有两个明显的局限:每轮都是一场全新对话(第二次 `t.send` 接不上第一次),工具调用完全看不见。后面两步各解决一个。
|
|
81
81
|
|
|
@@ -236,10 +236,10 @@ type StreamEvent =
|
|
|
236
236
|
|
|
237
237
|
| 你吐的事件 | 解锁的断言 |
|
|
238
238
|
|---|---|
|
|
239
|
-
| `message` | `t.reply`、`t.
|
|
239
|
+
| `message` | `t.reply`、`t.check(turn.message, includes(...))` / `pattern(...)`、Judge 的对话材料 |
|
|
240
240
|
| tool `operation.started` / `operation.finished`(靠 `operationId` 配对) | `turn.calledTool()` / `turn.toolOrder()` / `turn.maxToolCalls()` / `turn.noFailedActions()`… |
|
|
241
|
-
| `input.requested`(配合 status `"waiting"`) | `t.
|
|
242
|
-
| `thinking` / `compaction` / `error` |
|
|
241
|
+
| `input.requested`(配合 status `"waiting"`) | `t.check(turn.status, equals("waiting"))`、`t.requireInputRequest()`、`t.respond()`(第五步) |
|
|
242
|
+
| `thinking` / `compaction` / `error` | 报告与诊断;当前没有对应的公开 `eventMatch` selector |
|
|
243
243
|
|
|
244
244
|
Chat Completions 响应**不保证包含完整过程记录**。应用可能在服务端完成工具循环,只返回最终答案。因此,`turnFromChatCompletion` 的返回不带完整性证明:`calledTool` 等正断言可用,`notCalledTool` 等负断言会提示证据不完整。Responses 协议要求 `output` 数组记录完整过程,`turnFromResponses` 的返回带完整性证明,负断言可信。两者的可信度差异来自接口契约。
|
|
245
245
|
|
|
@@ -249,7 +249,7 @@ Chat Completions 响应**不保证包含完整过程记录**。应用可能在
|
|
|
249
249
|
|
|
250
250
|
应用中途停下来等人(工具审批、等补充信息)时,`send` 两侧各有义务:
|
|
251
251
|
|
|
252
|
-
- **停轮**:返回 `status: "waiting"`,并且每个待回答的问题吐一条带稳定 `id` 的 `input.requested` 事件——`t.
|
|
252
|
+
- **停轮**:返回 `status: "waiting"`,并且每个待回答的问题吐一条带稳定 `id` 的 `input.requested` 事件——`t.check(turn.status, equals("waiting"))` 与 `t.requireInputRequest()` 读取它,回答靠这个 `id` 对位。
|
|
253
253
|
- **回答轮**:评估用例里的 `t.respond(...)` 到 Adapter 是**又一次普通的 `send`**(还是同一条会话线、同一份状态),人的裁决以结构化形式随 `input.responses` 到达,每条带 `requestId`、`optionId` 或 `text`(形态见[不同回答的入参](/zh/explanation/adapter#不同回答的入参))。Adapter 先把裁决交回应用,再接着取结果。被人拒绝的调用,tool `operation.finished` 的 `status` 置 `"rejected"` 而不是 `"failed"`——拒绝是人的决定、不是工具故障,`noFailedActions()` 不误伤。
|
|
254
254
|
|
|
255
255
|
"停轮时读了一半的现场"(比如一条读到一半的 SSE 流)也存在 `ctx.session` 上。Adapter 先用 `createSessionSlot<Pending>()` 创建私有 slot,停轮时 `ctx.session.set(slot, pending)`,回答轮开头 `ctx.session.take(slot)` 取回。取到即清除,一次消费。
|
|
@@ -312,7 +312,8 @@ export default defineAgent({
|
|
|
312
312
|
|
|
313
313
|
不需要 HITL 的接口,删掉停轮现场相关的三处(`Pending`、`hold`、开头的 `take` 分支)即可,其余不变。停轮 / 回答 / 续跑的完整心智模型见 [HITL](/zh/explanation/hitl)。
|
|
314
314
|
|
|
315
|
-
**这一步支持**:`t.
|
|
315
|
+
**这一步支持**:`t.check(turn.status, equals("waiting"))`、`t.requireInputRequest()`、`t.respond()` / `t.respondAll()`。
|
|
316
|
+
被人拒绝的调用可用 `calledTool(toolMatch(..., { status: "rejected" }))` 精确断言。
|
|
316
317
|
|
|
317
318
|
## 第六步:接上 OTel trace
|
|
318
319
|
|