@migor/agentia 0.6.3 → 0.7.1

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.
Files changed (76) hide show
  1. package/CHANGELOG.md +250 -1
  2. package/README.en.md +4 -1
  3. package/README.md +23 -1
  4. package/dist/AGENTS.md +211 -24
  5. package/dist/container/container.d.ts +8 -1
  6. package/dist/container/container.js +8 -1
  7. package/dist/container/container.js.map +1 -1
  8. package/dist/core/run.d.ts +5 -1
  9. package/dist/core/timeout.d.ts +8 -0
  10. package/dist/core/timeout.js +14 -3
  11. package/dist/core/timeout.js.map +1 -1
  12. package/dist/core/tool.d.ts +64 -0
  13. package/dist/engine/budget.d.ts +17 -4
  14. package/dist/engine/budget.js +3 -3
  15. package/dist/engine/budget.js.map +1 -1
  16. package/dist/engine/loop.d.ts +7 -0
  17. package/dist/engine/loop.js +102 -6
  18. package/dist/engine/loop.js.map +1 -1
  19. package/dist/engine/spec.d.ts +18 -2
  20. package/dist/engine/spec.js.map +1 -1
  21. package/dist/engine/tracer.d.ts +11 -0
  22. package/dist/engine/tracer.js +25 -12
  23. package/dist/engine/tracer.js.map +1 -1
  24. package/dist/engine/turn.d.ts +25 -3
  25. package/dist/engine/turn.js +63 -4
  26. package/dist/engine/turn.js.map +1 -1
  27. package/dist/engine/types.d.ts +28 -1
  28. package/dist/engine/types.js.map +1 -1
  29. package/dist/index.d.ts +4 -4
  30. package/dist/index.js +4 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/integrations/anthropic.js +50 -5
  33. package/dist/integrations/anthropic.js.map +1 -1
  34. package/dist/integrations/mcp.d.ts +116 -11
  35. package/dist/integrations/mcp.js +523 -2
  36. package/dist/integrations/mcp.js.map +1 -1
  37. package/dist/integrations/metrics.d.ts +15 -0
  38. package/dist/integrations/metrics.js +114 -77
  39. package/dist/integrations/metrics.js.map +1 -1
  40. package/dist/integrations/openai.js +46 -0
  41. package/dist/integrations/openai.js.map +1 -1
  42. package/dist/integrations/otlp.js +15 -6
  43. package/dist/integrations/otlp.js.map +1 -1
  44. package/dist/runtime/run.d.ts +6 -0
  45. package/dist/runtime/run.js +23 -0
  46. package/dist/runtime/run.js.map +1 -1
  47. package/dist/store/fsStore.d.ts +2 -1
  48. package/dist/store/fsStore.js +15 -4
  49. package/dist/store/fsStore.js.map +1 -1
  50. package/dist/store/sqliteStore.js +7 -5
  51. package/dist/store/sqliteStore.js.map +1 -1
  52. package/dist/store/store.d.ts +17 -1
  53. package/dist/store/store.js +3 -2
  54. package/dist/store/store.js.map +1 -1
  55. package/dist/toolkit/asset.d.ts +3 -2
  56. package/dist/toolkit/asset.js +11 -2
  57. package/dist/toolkit/asset.js.map +1 -1
  58. package/dist/toolkit/module.d.ts +3 -2
  59. package/dist/toolkit/module.js +3 -0
  60. package/dist/toolkit/module.js.map +1 -1
  61. package/dist/toolkit/skill.js +46 -8
  62. package/dist/toolkit/skill.js.map +1 -1
  63. package/dist/toolkit/subagent.js +26 -1
  64. package/dist/toolkit/subagent.js.map +1 -1
  65. package/dist/toolkit/tool.d.ts +6 -0
  66. package/dist/toolkit/tool.js +1 -0
  67. package/dist/toolkit/tool.js.map +1 -1
  68. package/dist/transport/async.d.ts +74 -1
  69. package/dist/transport/async.js +257 -12
  70. package/dist/transport/async.js.map +1 -1
  71. package/dist/transport/http.d.ts +5 -0
  72. package/dist/transport/http.js +74 -2
  73. package/dist/transport/http.js.map +1 -1
  74. package/dist/transport/scheduler.js +33 -2
  75. package/dist/transport/scheduler.js.map +1 -1
  76. package/package.json +8 -4
package/CHANGELOG.md CHANGED
@@ -5,6 +5,253 @@
5
5
  (0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
6
6
  决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
7
7
 
8
+ ## [0.7.1] - 2026-09-19
9
+
10
+ ### 变更
11
+
12
+ > 本版主题(窗口 `0.7.0 → 0.7.1`,含 #71–#75):**HITL 人工审批**(挂起 / 恢复、跨进程耐久)、
13
+ > **gRPC 宿主配方与可跑示例**,以及**连续四轮复审收口**(第八轮「时间维度」、外部四路复审、
14
+ > 外部复核的复核)。这批改动绝大多数是同一族病灶 —— **「不报错地不干活」**:超时不清簿记、
15
+ > 重试不排空响应体、非流式回落零校验、回调通道缺失、幂等键赢家跨重启易主、门禁被 `&&`
16
+ > 短路静默跳过。
17
+ > **两处使用者会遇到的行为变更**:① OpenAI 兼容端点返回 legacy `function_call` 形态时,
18
+ > 不再静默以 `end_turn` 收尾,而是抛 400(确定性不兼容 ⇒ 不重试),见下方
19
+ > 「legacy `function_call` 形态响亮失败」;② MCP / 预算护栏 / 异步宿主的若干语义收紧
20
+ > (会话过期自愈加互斥、`close()` 超时不挂死、未配 `sessionStore` 时 `submit` 响亮失败),
21
+ > **都不需要改代码**。
22
+ > **一处破坏性变更(仅类型面,运行时行为不变)**:`RecorderBackend` 新增必填成员 `usage()`;
23
+ > `BudgetGuard.check` 入参由 `Trace` 收窄为 `{ readonly totalUsage: Usage }` ——
24
+ > 迁移写法见本节末的「迁移」小节。
25
+
26
+ ### 修复(外部四路复审收口:文档承诺了代码没做的事)
27
+
28
+ - **`AsyncRunner.approve` 补并发重入闸 + 真落库再派发**(HITL 补丁):并发 approve
29
+ (双击「批准」/两个审批人同时批)曾在 store 往返窗口内各自判「决定齐了」、
30
+ **各派发一次**(同一任务重复执行);且它用着吞错的 `#safeSave` 却在注释里承诺
31
+ 「先落库再派发」。现在并发调用共享在飞那次(第一次决定赢),落库失败 ⇒
32
+ 调用方收到 reject、**绝不派发**。
33
+ - **MCP StreamableHTTP 连接器:`tools/call` 不再起第二个计时器**(`timeoutMs` 只管
34
+ 装配期的握手/`tools/list`,与 stdio 侧对称;工具调用的裁判仍是引擎的
35
+ `toolTimeoutMs`)。顺带修复:非 SSE 响应的 id 改为**等值配对**(原只验「id 是
36
+ number」,串包时会把别的请求的结果当本次的返回)。
37
+ - **gRPC 示例 `getTask` 补 try/catch**:grpc-js 不接管 async handler 的 Promise,
38
+ store 抛错会以 unhandledRejection 终止进程。
39
+ - **soak 脚本两处假绿**:采样不足时「跳过内存断言」却照打「内存有界」⇒ 采样间隔
40
+ 随时长缩放、样本不足硬失败;宽区间失败率断言换成**逐笔对账**(每个不可重试故障
41
+ 恰好杀死一个 run:`failed ≥ 注入数` 且超出部分 ≤ 请求的 0.1%)。
42
+ - 文档对齐:usage-guide 曾写同步 `/run` 返回「含 `suspendedMessages`」(响应体没有
43
+ 该字段)—— 改文档:要审批请走 `POST /tasks` 异步宿主。
44
+
45
+ ### 修复(第八轮复审:时间维度的三处破口)
46
+
47
+ - **子 agent / skill 被 `toolTimeoutMs` 超时后,capability span 在交付的 trace 里永不收尾,
48
+ 且后台继续烧的 token 不进任何观测面**。新增 `ToolRunContext.abandoned`:引擎「放弃等待」
49
+ 时 abort 它 —— @SubAgent / @Skill 收到信号即中止子循环(在飞请求被掐掉),capability
50
+ span 立刻以 `error`(`timeout`)收尾(trace 是浅拷交付的,等子循环自己 settle 再关
51
+ 就进不了已交付的那份)。自定义工具要「真停」同样监听它。
52
+ - **Scheduler 的 `at()` / `every()` 挡 32 位定时器溢出**:超过 2³¹-1ms(约 24.86 天)的
53
+ 延迟会被 Node **静默**钳到 1ms —— `at(30 天后)` 变成立即触发、`every(34 天)` 退化成
54
+ 每毫秒空转。现在构造期抛错(与 `runTimeoutMs` 同款防线)。
55
+ - **skill 方法体 `try/catch` 掉 `ctx.llm()` 失败并降级时,capability span 不再被误标
56
+ error**(旧实现把「子运行失败」提前烙进 span,幂等守卫让整体成功的调用翻不了案)。
57
+
58
+ ### 新增(HITL 人工审批:挂起/恢复,跨进程耐久)
59
+
60
+ - **审批 = 异步 tool_result**:`@Tool({ approval: 'required' })`(或裸 `AgentTool` 的 `approval`
61
+ 字段)声明后,模型每次调用该工具都会把任务**挂起**:run 状态变为 `awaiting_approval`,
62
+ **整个回合一个工具都不执行**(回合级全有或全无 —— 协议要求每个 tool_use 配对 tool_result);
63
+ 完整消息历史(末尾是含未决 tool_use 的 assistant 消息)与待决清单(`pendingApprovals`)
64
+ 随 `TaskRecord` 落库,进程重启不丢。
65
+ - **恢复**:`runner.approve(taskId, decisions, { decidedBy? })` 或
66
+ `POST /tasks/:id/approve`(body `{ decisions: { <tool_use_id>: { approved, reason? } }, decidedBy? }`)。
67
+ **逐 tool_use_id 幂等**(第一次决定赢);决定齐了整个回合恢复执行:批准的工具正常执行
68
+ (工具体内经 `ToolRunContext.approval` 读到决定),拒绝的得 `tool_result(is_error,
69
+ '审批被拒绝:…')`(理由回给模型,可自行换路)。恢复后再遇未决审批 ⇒ 再次挂起(可等多轮)。
70
+ 引擎侧的恢复是**通用**的:任何「assistant 结尾带 tool_use」的消息历史喂给
71
+ `runAgent` / `app.run` 都会先解决这些 tool_use 再调模型。
72
+ - **状态语义**:`awaiting_approval` 是**非终态**(`awaitTask` 继续等)、不占并发槽、
73
+ 不触发 `TaskSink.onFinished`、`resumePending` 不捡(它不是孤儿,是在等人)、
74
+ InMemory 淘汰跳过、同幂等键重复 submit 返回等待中的任务。挂起段的 trace **照常投递
75
+ sinks**;恢复段是一棵新树,经根 span 的 `links` 挂到上一段 runId。
76
+ - **审批超时兜底**:`new AsyncRunner(app, { approvalTimeoutMs })`(缺省 0 = 一直等)。
77
+ **惰性判定、不起定时器**:`approve` / `poll` / `resumePending` 读到过期挂起任务时,
78
+ 自动把全部待决项写成「拒绝:审批超时」并恢复执行。
79
+ - 新公共面:`ApprovalDecision` 类型;`RunAgentOptions.approvals` / `RunInvocationOptions.approvals`;
80
+ `AgentRunResult.suspendedMessages` / `pendingApprovals`(字段恒在场,未挂起为 `undefined`);
81
+ `AgentStopReason` 与 `RunStatus` 各增 `'awaiting_approval'`;`AsyncRunner.approve` /
82
+ `approvalTimeoutMs`;trace 事件 `approval.requested` / `approval.decided`(带 waitedMs)。
83
+ - 已知边界(详见 usage-guide §7):超时惰性判定;指标按段计;批准后崩溃 ⇒ at-least-once
84
+ 重执行;预算口径在恢复段重新起算;嵌套能力(子 agent / skill 子循环)内的审批工具
85
+ 不支持挂起整个 run;同步 `POST /run` 撞上审批会带 `awaiting_approval` 返回(要审批请走
86
+ `/tasks`)。
87
+
88
+ ### 修复(外部复核的复核 + 三条一致性缺口收口)
89
+
90
+ 对上一轮「四路复审」的修复(31 个文件)逐条复核并实测通过;另补三条「守卫没覆盖自己
91
+ 声称范围」的缺口与一处护栏开销:
92
+
93
+ - **MCP HTTP `close()` 的 DELETE 现在过 `guard`**:此前直接 `await fetchImpl(...)`,外层
94
+ `catch` 只兜得住**抛错**、兜不住**挂死** —— server 接受连接后不回(半开 / 卡在代理后面),
95
+ `close()` 就永久挂住,而调用方是**宿主停机路径**(挂住比失败更糟)。fetch 与 body 排空
96
+ 一起进 guard(只护住响应头,`readText` 照样能卡)。摘掉修复 ⇒ 新用例在 8s 测试预算下
97
+ 被 `cancelledByParent` 取消。
98
+ - **`asset()` 拦绝对路径**:守卫此前只拦带 scheme 的 `rel`,但 `/etc/passwd` 与
99
+ `file:///etc/passwd` 是同一类(`new URL` 会把 base 的路径部分整个丢掉)——
100
+ 「以为读了能力目录里的文件,实际读了别处」(macOS 上**真能读到**)。`../` 仍放行
101
+ (它是相对 base 解析的,base 没被忽略)。
102
+ - **预算护栏改走廉价 usage**:新增 `TraceRecorder.usage()`(只扫 spans 求和、不拷
103
+ attributes/events),`snapshot().totalUsage` 改为调它 ⇒ 两条路不可能漂移。护栏每回合
104
+ 要判**两次**(回合入口 + 回合末,是不同决策点,**刻意不合并**),此前每次都全量
105
+ `snapshot()` ⇒ O(回合 × 累计事件量) 的白拷。
106
+ - 测试基建:`mcpConnector` 的 stdio 握手缺省预算 5s → 20s(`node --test` 按文件并行,
107
+ 重负载下**子进程启动**本身就可能吃掉数秒;断超时行为的那条用例自带 `timeoutMs`)。
108
+ - 复核结论一条「查过但不改」:`interruptibleSleep(0, 已中止的 signal)` 仍 **resolve** ——
109
+ 它与 `withTimeout(p, 0)` 的「非正数 = 机制关掉」是同一口径,不是缺口(本轮曾误改,
110
+ 被既有用例拦下后回退;那条用例的断言已从隐式 `await` 改成显式 `assert.doesNotReject`
111
+ 并写明理由,见 spec §10 2026-09-19 ④)。
112
+
113
+ **迁移(自己实现这两个结构面时)**
114
+
115
+ - `BudgetGuard.check` 的入参由 `Trace` 收窄为 `{ readonly totalUsage: Usage }`。
116
+ **传整份 `Trace` 的调用方不受影响**(结构上满足);自己实现 `BudgetGuard` 的代码需把
117
+ 签名改宽,且**不得再读 `spans`**(类型上已读不到 —— 「只看 totalUsage」由注释变成约束)。
118
+ - `RecorderBackend` 新增必填成员 `usage(): Usage`。自己实现该结构面(或自建 recorder
119
+ 替身)的代码需补上。
120
+
121
+ ### 新增(gRPC 宿主配方与可跑示例)
122
+
123
+ - **不对 Kafka / gRPC 做「服务包」**,但把真缺的那一块做成配方 + 可跑示例:gRPC 宿主必须
124
+ 自己接上的**四处**(deadline / 取消 → `signal`、`metadata` 的 `traceparent` → `traceContext`、
125
+ 框架错误 → gRPC 状态码、trace → sink),四处漏掉**都不报错**。判别规则只有一条 ——
126
+ 客户端是不是标准库(MCP 用 `spawn` + `fetch` 所以能内置,gRPC / Kafka 要引第三方客户端),
127
+ 决策见 spec §10 2026-09-18 ⑪。
128
+ - `docs/usage-guide.md` §6.2 新增「gRPC 宿主」配方;`examples/grpc-host/` 给了 proto + 宿主 +
129
+ 客户端 + README(一元 / 服务端流 / 异步投递 / 查任务,`PORT=0` 自报端口、无抢占窗口);
130
+ `scripts/e2e-grpc.ts` 真构建真起宿主、用它自带的客户端跑四个 RPC,并入 `npm run e2e` 第四步。
131
+
132
+ ### 修复(OpenAI 兼容端点:legacy `function_call` 形态响亮失败)
133
+
134
+ - 兼容端点把工具调用放在 `message.function_call` 时,适配器只读 `tool_calls` ⇒ 此前落进
135
+ `default: return 'end_turn'`,**模型要调的工具被丢掉、run 却以成功收尾**(模块头写的正是
136
+ 「上游故障绝不映射成成功」)。现在 `function_call` 抛 `OpenAICompatApiError(400)`:这是
137
+ **确定性不兼容** ⇒ 落 `classifyError` 的 `api` / 不可重试(用 500 会被引擎重试三次,每次
138
+ 都重复丢弃同一个调用);也**不做 legacy 兼容**——请求侧只发 `tool_calls`,回灌的
139
+ `role:'tool'` legacy-only 端点同样吃不下,半吊子支持比不支持更糟。`default` 仍是有意的
140
+ `end_turn`(未知值**且有正文**),并补 `eos_token` 用例把这个有意默认钉住,防后人顺手改成抛错。
141
+
142
+ ## [0.7.0] - 2026-09-18
143
+
144
+ ### 变更
145
+
146
+ > 本版两个主题:**MCP 连接器出厂自带**(新增公共 API:`createStdioMcpConnector` /
147
+ > `createStreamableHttpMcpConnector` / `McpConnector` / `MCP_CLOSE_GRACE_MS`),
148
+ > 以及一轮复审与「已知边界」收口。**两处行为变更,都不需要使用者改代码**:
149
+ > ① StreamableHTTP 会话过期从「抛错、需重建连接器」变成**自愈**(多了个可选的
150
+ > `onSessionExpired` 钩子);② `close()` 从「到点即返回」变成「返回即子进程已终止」。
151
+ > **无破坏性变更** ⇒ 不需要迁移动作。
152
+
153
+ ### 修复(MCP 连接器两条「已知边界」收掉)
154
+
155
+ - **StreamableHTTP 会话过期不再需要人工重建连接器**(`404` 自愈)。带会话 id 收到 `404` 的语义是
156
+ 「这个会话我不认识」⇒ **该请求没有被 server 执行** ⇒ 连接器丢会话、重新握手、把**这一次**重试一次
157
+ (**只一次**,第二次再 404 直接抛,不循环)—— 这也是 MCP 规范对客户端的要求。
158
+ 此前按不可重试的 `api` 错抛出,长跑宿主的会话一过期就得**重建整个连接器**。
159
+ 自愈本身是静默的,所以给了 **`onSessionExpired`** 钩子:不挂它就没人知道恢复发生过 ——
160
+ 「静默恢复」和「静默失效」在监控上看不出区别。
161
+ - **`close()` 现在保证返回时子进程已终止**(stdio)。此前「到点即 resolve」:SIGTERM → 等
162
+ `MCP_CLOSE_GRACE_MS` → SIGKILL **并立刻返回**,此刻子进程往往还在(未回收)—— 调用方以为
163
+ 进程没了,实际留下一个孤儿。现在 SIGKILL 之后**继续等真正的 `'exit'`**(SIGKILL 不可被捕获,
164
+ 该事件必达)。
165
+ - 验证:`tests/integrations/mcpConnector.test.ts` 27 → 31 例;
166
+ **变异电池 6/6 全部被抓到、0 漏网**(删自愈分支 / 丢掉 `sessionId !== null` 前置 / 重试透传
167
+ `allowReinit`(无限重试)/ 去掉 `onSessionExpired` / `close()` 恢复不等 reap / 夹具不再忽略
168
+ SIGTERM ⇒ 证明那条用例真在测 SIGKILL 路径)。
169
+
170
+ ### 修复(官网手写数字:补上真正没被守的那几个)
171
+
172
+ - `api.html` 的 `0 个运行时依赖` / `4 类能力` 与 `index.html` 首屏的 `4 类能力` / `0 个运行时依赖` /
173
+ `3 类触发` **此前没有任何断言** —— 加一个运行时依赖、增删一类能力或触发宿主,页面会继续写旧数字
174
+ 而没人拦。现在逐条对源码核(`package.json` 的 `dependencies` 数 / 四个能力装饰器 / 三个传输宿主)。
175
+ 变异电池 3/3 会咬(改成 1 / 5 / 4 各判红一次)。
176
+ ⚠️ 顺带**更正一处过度声明**:`210 个导出` 与 `9 个层次` **本来就有守卫**
177
+ (`api-page.test.ts` 已有:前者对 `src/index.ts` 导出数、后者对页面 section 数)——
178
+ 它们从来不是缺口。首屏 `1:1 run ↔ trace`(真守卫在 `traceLink.test.ts`)与 `0 反射`
179
+ (策略声明,无法从源码计数推导)**刻意不推导**,已在 `guards.md` §2「待守」登记。
180
+
181
+ ### 新增(MCP 连接器**出厂自带**:stdio + StreamableHTTP)
182
+
183
+ - **`createStdioMcpConnector(cmd, opts?)`** / **`createStreamableHttpMcpConnector(url, opts?)`**
184
+ —— MCP 的两种传输现在随框架发布。此前只有 `mcpTools()` 那条 duck-typed 桥,**连接器要自己写**:
185
+ 官方示例只有 `scripts/e2e-mcp.ts` 里一份 94 行的最小参考(还是内联在脚本里的私有副本)。
186
+ 公共面另加 `McpConnector`(`McpClientLike` + `close()`)与 `MCP_CLOSE_GRACE_MS`。
187
+ **只用标准库**(`node:child_process` + 全局 `fetch`)⇒ 不新增任何第三方依赖,「零运行时依赖」不变;
188
+ 也**没有**放宽「第三方 SDK 对用户不可见」—— 一个 MCP SDK 都没 import,`McpClientLike` 这条缝原样保留
189
+ (接官方 SDK / 远程 server / 自研传输照旧走它)。
190
+ - **本条反转了 2026-09-11 的 F7**(「连接器放独立可选包 `@migor/mcp`」)。那个包**从未发布**,
191
+ 而仓库里有 5 处注释把它当既成事实引用(含 `src/index.ts` 的公共面注释)—— 现在全部改正。
192
+ 为什么反:F7 的理由「守住零运行时依赖」不成立(该口径 = 不依赖**第三方包**;`spawn` / `fetch` 都是
193
+ 标准库),且 `store/` 的三层形状里「只用标准库的平台能力」一律内置(`FileTaskStore` / `SqliteTaskStore` /
194
+ HTTP 宿主),独立包才是那个例外。完整论证与可逆性判据见 `docs/spec.md` §10 **2026-09-18 ⑨**。
195
+ - **连接器替你兜住三件只有它能做的事**(此前只活在 `scripts/e2e-mcp.ts` 那段内联副本里,无门禁守着):
196
+ ① spawn 失败的 `'error'` 是**异步事件**,不接住就是未捕获异常(真实宿主进程直接崩,没有 try/catch
197
+ 接得住);② stdout 必须按 `\n` **攒包**(一条报文可能跨多个 chunk);③ **协议层 `isError: true`
198
+ 转成抛错** —— 否则模型收到一条「成功」的结果、trace 也把这次失败的调用记成成功。
199
+ - **连接器的 `timeoutMs` 只管装配期**(握手 + `tools/list`):那两步此前**没有任何裁判**,server 卡住会让
200
+ `createApp` 永久挂起;`callTool` 的裁判仍是引擎 / 桥(延续「一次调用只有一个裁判」)。
201
+ - **已知边界**:StreamableHTTP 会话过期(带会话 id 收到 `404`)**不自动重握手**,按不可重试的 `api` 错抛出;
202
+ `close()` 幂等且**有界**(SIGTERM → 2000 ms 后 SIGKILL),但**不保证等到子进程被 reap**。
203
+ - 验证:新增 `tests/integrations/mcpConnector.test.ts` 27 例(stdio 侧起**真子进程**);
204
+ **变异电池 9/9 全部被抓到、0 漏网**;`npm run e2e:mcp` 改为走出厂连接器后真第三方 server 全绿 ——
205
+ 此前那条端到端证明测的是它自己那份私有副本,现在测的是用户拿到的东西。
206
+
207
+ ### 修复(第七轮复审收口:三处「不报错地不干活」)
208
+
209
+ - **Anthropic 适配器:流被截断 / 空流现在抛带 `status` 的错误**(`AnthropicApiError(500)`)。
210
+ 此前两处抛裸 `Error` ⇒ `classifyError` 判 `unknown` + `retryable:false`:上游故障被记成「模型的
211
+ 协议问题」(排障方向被带偏),且**引擎层重试一次都不会发生**。更糟的是「已吐出半句之后断流」——
212
+ 内容非空使旧判据(`!started`)不触发,`stop_reason: null` 落 `unknown_stop_reason`(同样不可重试)。
213
+ 现在按 openai 侧同款判据:**既无 `message_stop`、也无 `message_delta` 的 `stop_reason` ⇒ 抛 500 可重试**。
214
+ - **OpenAI 适配器:流内 error 分片的 status 反推分三档**(此前只把限流判 429、其余一律 500 + 可重试)。
215
+ `invalid_request_error` / `context_length_exceeded` / `model_not_found` / 鉴权 / `content_filter` 这类
216
+ **改配置才有救**的 4xx 病因现在判 400 且**不可重试**(此前白重试 3 次、trace 记成 `server` 而非 `api`)。
217
+ - **OpenAI 适配器:非流式路径的「200 + 空补全」不再记成成功**。`choices[0].message.content = null`
218
+ + `finish_reason: 'stop'` 此前映射成「空文本 + end_turn」成功收尾,而同一响应在**流式**路径上会被判
219
+ 失败 —— 两条路径结论相反,且与模块头「上游故障绝不映射成成功」相反。`content_filter` 的合法空回复
220
+ 仍豁免(与流式同款例外)。
221
+ - **`AsyncRunner.resumePending` 补重入闸**:并发/重入调用现在**共享同一次扫描的结果**(返回在飞那个
222
+ Promise,而不是谎报 0)。「先落库再派发」只堵住了**串行**重扫 —— 认领是异步的,两个并发调用都在任一
223
+ `save` 落地前 `list()` 到旧快照,`ownerId` 过滤双双失效 ⇒ 同一任务被派发两次(副作用与花费翻倍)。
224
+ - **`metricsSink`:基数折叠在 `/metrics` 上可见** —— 新增 gauge 家族
225
+ `agentia_dropped_keys{kind="capability"|"model"|"score"}`(Prometheus 与 OTLP 两侧都出,恒定发三个样本)。
226
+ 此前 `droppedCapabilities/Models/Scores` 只存在于 `snapshot()`,而文档让用户把 `metricsSink()` 接到
227
+ `GET /metrics`(只消费 `render()`)⇒ Prometheus-only 的部署**完全看不见折叠发生**(静默丢失);
228
+ 对照:同一份文件对「算不出成本的 turn」专门发了 `model_unpriced_turns_total`。
229
+ - **`metrics.ts` 的 OTLP 导出失败改抛 `MetricsExportError`(带数值 `status`)**:裸 `Error` 被判
230
+ `unknown`,宿主在 `onExportError` 里拿不到 status,无法区分「collector 拒收(4xx,改配置)」与
231
+ 「collector 挂了(5xx,等它回来)」。
232
+
233
+ ### 修复(守卫自身的洞 —— 本版主题是「把没门禁的约定收口」,而守卫自己有洞)
234
+
235
+ - **`tests/architecture/transport-errors.test.ts` 的注释剥离会瘫痪整份文件的扫描**(严重):
236
+ 旧实现把每行「截到 `//` 为止」,而 `//` 会出现在**字符串里**(最典型是 URL)。截断会切掉该行闭合的
237
+ `)` 与反引号 ⇒ `bareErrorThrows` 的括号配平一路吃到文件末尾 ⇒ **那一行之后的每一处抛错都再也扫不到、
238
+ 且全程不报错**。实测 `src/integrations/metrics.ts`:8 处裸抛错只剩 2 处可见,被吞掉的正好包括
239
+ `OTLP metrics 导出失败: HTTP ${res.status}`(本守卫存在的理由)。改为「整行丢弃注释行」,
240
+ 并以合成样本复现该机制作回归钉(旧实现下会红)。
241
+ - `tests/docs/guards-registry.test.ts` 的**条目密度下限由 8 提到 24**(实测 32):旧下限意味着删掉
242
+ 注册表 §1.1–§1.3 整整三节仍会绿 —— 防「抽词器退化」的护栏同时替「整节被删」放了行。
243
+
244
+ ### 仍未做(如实标注,下一轮)
245
+
246
+ - `tests/integrations/adapter-parity.test.ts` 的矩阵**结构上测不到「缺省 `maxRetries` 对称」**:
247
+ `make(maxRetries: number)` 是必填、所有场景都显式 `a.make(2)` ⇒ 把 openai 的缺省改成 0 仍全绿。
248
+ 而该矩阵被造出来正是为守这件事(修法:加一条走缺省的场景)。
249
+ - `maxToolConcurrency` 是否该随 `ToolRunContext` 透传给嵌套能力(`docs/guards.md` §2 已登记的
250
+ 「手写转发列表不得漏字段」形状,历史事故 = `runAgentScoped` 漏 `toolTimeoutMs`)—— 待口径判定:
251
+ 转发,或在 `types.ts` 注明「只作用本层循环」。
252
+
253
+ ## [Unreleased]
254
+
8
255
  ## [0.6.3] - 2026-09-18
9
256
 
10
257
  > 本版主题:把第六轮 16 条的共同根因(「约定写在文档里、但没有门禁」)收口 ——
@@ -368,7 +615,9 @@
368
615
  首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
369
616
  框架本体单包;CLI 独立成包(workspaces)。
370
617
 
371
- [Unreleased]: https://github.com/retrychx/agentia/compare/v0.6.3...HEAD
618
+ [Unreleased]: https://github.com/retrychx/agentia/compare/v0.7.1...HEAD
619
+ [0.7.1]: https://github.com/retrychx/agentia/releases/tag/v0.7.1
620
+ [0.7.0]: https://github.com/retrychx/agentia/releases/tag/v0.7.0
372
621
  [0.6.3]: https://github.com/retrychx/agentia/releases/tag/v0.6.3
373
622
  [0.6.2]: https://github.com/retrychx/agentia/releases/tag/v0.6.2
374
623
  [0.6.1]: https://github.com/retrychx/agentia/releases/tag/v0.6.1
package/README.en.md CHANGED
@@ -124,7 +124,10 @@ One run == one trace (`traceId === runId`), **built in from turn 0** — not a b
124
124
  - **Models** — `createAnthropicClient()` (default) and `createOpenAIClient()` for any OpenAI-compatible
125
125
  endpoint (DeepSeek, **Ollama**, gateways). You never need to touch a vendor SDK: pass
126
126
  `createAnthropicClient({ baseURL })` / `createOpenAIClient({ baseURL })`.
127
- - **MCP** — `mcpTools()` bridges an MCP server's tools into the menu (duck-typed, zero dependency)
127
+ - **MCP** — `createStdioMcpConnector()` / `createStreamableHttpMcpConnector()` ship with the framework
128
+ (stdlib only: `node:child_process` + global `fetch`); `mcpTools()` bridges the server's tools into the
129
+ menu. The framework never imports the MCP SDK, and the `McpClientLike` seam stays open
130
+ (bring the official SDK / a remote server / your own transport).
128
131
  - **Memory** — `memory: { store, keys }` for cross-run state; task stores: memory / JSONL / SQLite / Redis
129
132
  - **Evals** — `scriptedClient` + `defineEval` turn mocked runs into a first-class diagnostic loop
130
133
  - **Platforms** — send traces to Langfuse / Phoenix / etc. via OTLP or a tiny custom `TraceSink`
package/README.md CHANGED
@@ -30,7 +30,7 @@ $EDITOR my-app/.env # 填 ANTHROPIC_API_KEY(也可直接 expo
30
30
  > 框架**不自动**读 `.env`:脚手架 `src/main.ts` 首行的 `loadEnvFile()` 负责把它读进 `process.env`。
31
31
  > 真实环境变量优先(CI / docker / 命令行永远赢过文件),`loadEnvFile({ override: true })` 才反过来。
32
32
 
33
- > **版本**:`0.6.3`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
33
+ > **版本**:`0.7.1`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
34
34
  > `file:../..` 指向本仓库而不是 npm 版本 —— 跑的是**工作区代码**,理由见
35
35
  > [`examples/README.md`](examples/README.md) 的「依赖说明」。
36
36
 
@@ -283,6 +283,28 @@ import { createHttpHandler } from '@migor/agentia';
283
283
  createServer(createHttpHandler(app, { runner })).listen(8080);
284
284
  ```
285
285
 
286
+ ### MCP(连接器出厂自带)
287
+
288
+ MCP server 的工具映射成菜单项 —— 连接器(stdio / StreamableHTTP)**随框架发布**,只用标准库(`node:child_process` + 全局 `fetch`),不新增第三方依赖:
289
+
290
+ ```ts
291
+ import { createApp, createStdioMcpConnector, createStreamableHttpMcpConnector, mcpTools } from '@migor/agentia';
292
+
293
+ // stdio:spawn 一个 MCP server 子进程
294
+ const mcp = createStdioMcpConnector(['uvx', 'mcp-server-time']);
295
+ const tools = await mcpTools(mcp, { server: 'time' }); // → mcp_time_get_current_time …
296
+
297
+ // 远程 server:StreamableHTTP(一个 endpoint,鉴权走 headers)
298
+ const remote = createStreamableHttpMcpConnector('https://mcp.example/mcp', {
299
+ headers: { authorization: 'Bearer …' },
300
+ });
301
+
302
+ // 与本地 @Tool 同池:同过中间件链、同进重名查重
303
+ createApp({ system, providers: [...], tools });
304
+ ```
305
+
306
+ `McpClientLike` 这条缝仍然在:接官方 SDK / 远程 server / 自研传输时实现 `listTools()` + `callTool()` 两个方法即可(框架**不 import** MCP SDK)。
307
+
286
308
  ### 多模型(OpenAI 兼容端点)
287
309
 
288
310
  ```ts