@migor/agentia 0.9.4 → 0.10.0
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/CHANGELOG.md +1280 -4
- package/README.en.md +61 -3
- package/README.md +66 -4
- package/dist/AGENTS.md +330 -45
- package/dist/container/container.js +21 -0
- package/dist/container/container.js.map +1 -1
- package/dist/core/limits.d.ts +101 -14
- package/dist/core/limits.js +103 -13
- package/dist/core/limits.js.map +1 -1
- package/dist/core/line-framing.d.ts +36 -0
- package/dist/core/line-framing.js +39 -0
- package/dist/core/line-framing.js.map +1 -0
- package/dist/core/message.d.ts +11 -0
- package/dist/core/run.d.ts +68 -4
- package/dist/core/run.js +27 -1
- package/dist/core/run.js.map +1 -1
- package/dist/core/schema.js +59 -8
- package/dist/core/schema.js.map +1 -1
- package/dist/core/timeout.d.ts +43 -1
- package/dist/core/timeout.js +76 -3
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/tool.d.ts +25 -1
- package/dist/core/trace.d.ts +40 -1
- package/dist/core/trace.js +26 -0
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/budget.js +14 -0
- package/dist/engine/budget.js.map +1 -1
- package/dist/engine/defer.d.ts +49 -0
- package/dist/engine/defer.js +72 -0
- package/dist/engine/defer.js.map +1 -0
- package/dist/engine/errors.d.ts +23 -1
- package/dist/engine/errors.js +24 -0
- package/dist/engine/errors.js.map +1 -1
- package/dist/engine/forwarded.d.ts +14 -5
- package/dist/engine/forwarded.js +4 -1
- package/dist/engine/forwarded.js.map +1 -1
- package/dist/engine/loop-result.d.ts +65 -7
- package/dist/engine/loop-result.js +24 -5
- package/dist/engine/loop-result.js.map +1 -1
- package/dist/engine/loop.d.ts +2 -0
- package/dist/engine/loop.js +157 -17
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/mcp-server-http.d.ts +2 -0
- package/dist/engine/mcp-server-http.js +215 -0
- package/dist/engine/mcp-server-http.js.map +1 -0
- package/dist/engine/mcp-server-stdio.d.ts +15 -0
- package/dist/engine/mcp-server-stdio.js +106 -0
- package/dist/engine/mcp-server-stdio.js.map +1 -0
- package/dist/engine/mcp-server.d.ts +134 -0
- package/dist/engine/mcp-server.js +232 -0
- package/dist/engine/mcp-server.js.map +1 -0
- package/dist/engine/menu-drift.d.ts +34 -0
- package/dist/engine/menu-drift.js +81 -0
- package/dist/engine/menu-drift.js.map +1 -0
- package/dist/engine/policy.d.ts +2 -1
- package/dist/engine/policy.js +7 -1
- package/dist/engine/policy.js.map +1 -1
- package/dist/engine/replay.d.ts +2 -1
- package/dist/engine/replay.js +3 -2
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/resume-input.d.ts +10 -0
- package/dist/engine/resume-input.js +11 -0
- package/dist/engine/resume-input.js.map +1 -1
- package/dist/engine/run-config.d.ts +26 -2
- package/dist/engine/run-config.js +60 -0
- package/dist/engine/run-config.js.map +1 -1
- package/dist/engine/spec.d.ts +33 -4
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/tool-context.d.ts +10 -5
- package/dist/engine/tool-context.js +5 -1
- package/dist/engine/tool-context.js.map +1 -1
- package/dist/engine/tracer.d.ts +43 -5
- package/dist/engine/tracer.js +73 -10
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/trimming.js +28 -2
- package/dist/engine/trimming.js.map +1 -1
- package/dist/engine/turn.d.ts +60 -3
- package/dist/engine/turn.js +180 -71
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +89 -3
- package/dist/engine/types.js +38 -2
- package/dist/engine/types.js.map +1 -1
- package/dist/eval/export.d.ts +48 -0
- package/dist/eval/export.js +146 -0
- package/dist/eval/export.js.map +1 -0
- package/dist/eval/harvest.js +7 -2
- package/dist/eval/harvest.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/adapter-options.d.ts +22 -0
- package/dist/integrations/adapter-options.js +51 -0
- package/dist/integrations/adapter-options.js.map +1 -1
- package/dist/integrations/anthropic.d.ts +4 -2
- package/dist/integrations/anthropic.js +27 -27
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/file-sink.d.ts +20 -0
- package/dist/integrations/file-sink.js +27 -0
- package/dist/integrations/file-sink.js.map +1 -0
- package/dist/integrations/mcp-http.d.ts +3 -3
- package/dist/integrations/mcp-http.js +1 -1
- package/dist/integrations/mcp-http.js.map +1 -1
- package/dist/integrations/mcp-protocol.d.ts +115 -0
- package/dist/integrations/mcp-protocol.js +69 -0
- package/dist/integrations/mcp-protocol.js.map +1 -0
- package/dist/integrations/mcp-stdio.d.ts +3 -3
- package/dist/integrations/mcp-stdio.js +17 -3
- package/dist/integrations/mcp-stdio.js.map +1 -1
- package/dist/integrations/mcp.d.ts +6 -95
- package/dist/integrations/mcp.js +24 -66
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics-otlp.js +52 -7
- package/dist/integrations/metrics-otlp.js.map +1 -1
- package/dist/integrations/metrics-render.d.ts +15 -0
- package/dist/integrations/metrics-render.js +79 -2
- package/dist/integrations/metrics-render.js.map +1 -1
- package/dist/integrations/metrics-state.d.ts +99 -1
- package/dist/integrations/metrics-state.js +128 -4
- package/dist/integrations/metrics-state.js.map +1 -1
- package/dist/integrations/metrics.d.ts +62 -6
- package/dist/integrations/metrics.js +49 -8
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.js +9 -31
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.d.ts +3 -3
- package/dist/integrations/report.d.ts +7 -0
- package/dist/integrations/report.js +20 -1
- package/dist/integrations/report.js.map +1 -1
- package/dist/runtime/memory.d.ts +61 -1
- package/dist/runtime/memory.js +47 -7
- package/dist/runtime/memory.js.map +1 -1
- package/dist/runtime/run.d.ts +12 -2
- package/dist/runtime/run.js +126 -17
- package/dist/runtime/run.js.map +1 -1
- package/dist/store/fsStore.js +10 -4
- package/dist/store/fsStore.js.map +1 -1
- package/dist/store/record.d.ts +41 -0
- package/dist/store/record.js +54 -0
- package/dist/store/record.js.map +1 -0
- package/dist/store/redisStore.d.ts +24 -1
- package/dist/store/redisStore.js +42 -7
- package/dist/store/redisStore.js.map +1 -1
- package/dist/store/sqliteStore.d.ts +23 -0
- package/dist/store/sqliteStore.js +103 -8
- package/dist/store/sqliteStore.js.map +1 -1
- package/dist/store/store.d.ts +65 -2
- package/dist/store/store.js +5 -2
- package/dist/store/store.js.map +1 -1
- package/dist/toolkit/capability-cycles.d.ts +50 -0
- package/dist/toolkit/capability-cycles.js +88 -0
- package/dist/toolkit/capability-cycles.js.map +1 -0
- package/dist/toolkit/capability-slice.d.ts +86 -0
- package/dist/toolkit/capability-slice.js +98 -0
- package/dist/toolkit/capability-slice.js.map +1 -0
- package/dist/toolkit/module.d.ts +7 -1
- package/dist/toolkit/module.js +65 -32
- package/dist/toolkit/module.js.map +1 -1
- package/dist/transport/approval-policy.d.ts +1 -1
- package/dist/transport/approval-policy.js +8 -4
- package/dist/transport/approval-policy.js.map +1 -1
- package/dist/transport/approval-supervisor.d.ts +73 -0
- package/dist/transport/approval-supervisor.js +252 -0
- package/dist/transport/approval-supervisor.js.map +1 -0
- package/dist/transport/async.d.ts +159 -56
- package/dist/transport/async.js +627 -269
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/drain-gate.d.ts +6 -2
- package/dist/transport/drain-gate.js +14 -2
- package/dist/transport/drain-gate.js.map +1 -1
- package/dist/transport/http-endpoints.d.ts +151 -0
- package/dist/transport/http-endpoints.js +480 -0
- package/dist/transport/http-endpoints.js.map +1 -0
- package/dist/transport/http-io.d.ts +66 -0
- package/dist/transport/http-io.js +145 -0
- package/dist/transport/http-io.js.map +1 -0
- package/dist/transport/http-route.d.ts +10 -3
- package/dist/transport/http-route.js +18 -3
- package/dist/transport/http-route.js.map +1 -1
- package/dist/transport/http-shapes.d.ts +11 -0
- package/dist/transport/http-shapes.js +31 -1
- package/dist/transport/http-shapes.js.map +1 -1
- package/dist/transport/http.d.ts +22 -35
- package/dist/transport/http.js +98 -476
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/owner-id.d.ts +29 -0
- package/dist/transport/owner-id.js +46 -0
- package/dist/transport/owner-id.js.map +1 -0
- package/dist/transport/owner-liveness.d.ts +8 -0
- package/dist/transport/owner-liveness.js +56 -0
- package/dist/transport/owner-liveness.js.map +1 -0
- package/dist/transport/resume-policy.d.ts +48 -10
- package/dist/transport/resume-policy.js +25 -10
- package/dist/transport/resume-policy.js.map +1 -1
- package/dist/transport/resume-scanner.d.ts +55 -0
- package/dist/transport/resume-scanner.js +219 -0
- package/dist/transport/resume-scanner.js.map +1 -0
- package/dist/transport/scheduler.js +9 -23
- package/dist/transport/scheduler.js.map +1 -1
- package/dist/transport/signal-supervisor.d.ts +80 -0
- package/dist/transport/signal-supervisor.js +129 -0
- package/dist/transport/signal-supervisor.js.map +1 -0
- package/dist/transport/slot-pool.d.ts +9 -1
- package/dist/transport/slot-pool.js +9 -1
- package/dist/transport/slot-pool.js.map +1 -1
- package/dist/transport/task-events.d.ts +34 -3
- package/dist/transport/task-events.js +93 -5
- package/dist/transport/task-events.js.map +1 -1
- package/dist/transport/wake-policy.d.ts +50 -0
- package/dist/transport/wake-policy.js +43 -0
- package/dist/transport/wake-policy.js.map +1 -0
- package/package.json +14 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,1229 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.10.0] - 2026-09-29
|
|
9
|
+
|
|
10
|
+
> 本版主题(窗口 `0.9.5 → 0.10.0`):**从「跑得起来」到「敢上线、也找得到」**。
|
|
11
|
+
> 两条主线 —— ① **把对外承诺从「文档上的一句话」改成「断言」**:稳定性策略 / 三个不发布的包 /
|
|
12
|
+
> CI 运行环境全都写代码核实,README 顶部每一个数字由脚本现算,公开 API 边界由 `exports` 字段
|
|
13
|
+
> (而不是一句承诺)封住;② **补两处对外空白**:「从 demo 到生产」的 `docs/deployment.md`、
|
|
14
|
+
> 官网第一次写出「什么场景该选别人」的 `/tradeoffs` 页。
|
|
15
|
+
> ⚠️ **本版有破坏性变更**:`exports` 收窄后深路径导入会被拒 —— 只有 import 过
|
|
16
|
+
> `@migor/agentia/dist/**` 的使用者需要动手,动作写在下面 ⑭ 那条的「**迁移**」小节。
|
|
17
|
+
> 其余 15 条条目**都不改变既有使用者的行为**。
|
|
18
|
+
|
|
19
|
+
### 工程 · 发版闸门「骨架未填」的判据改成精确锚(2026-09-29 ⑳)
|
|
20
|
+
|
|
21
|
+
- **是什么**:`scripts/release-surface.mjs` 判断「本版 CHANGELOG 是不是还停在 bump 插的骨架」时,
|
|
22
|
+
用的是粗判据 `sec.body.includes('TODO')`。**本次发版当场被它拦下 —— 而且是误伤**:本版 ⑫ 那条的
|
|
23
|
+
正文里合法地写着「发版流程文档里的两个必填 TODO、四步顺序」(它描述的正是 `docs/CONTRIBUTING.md`
|
|
24
|
+
里那两个必填标记)⇒ 一个**已经写完**的版本节被判成「没写」,`check-release` 直接 `exit 1`。
|
|
25
|
+
- **改法**:锚改成 bump **真正插入的那串**标记(`TODO` 紧跟 `(发版)`)—— 它由 `release.mjs` 的 `INSERT_OPS`
|
|
26
|
+
集中生成,永远带 `(发版)`。同文件里 `--allow-pending` 的降级判据一并改:否则 bump 后的自检会把
|
|
27
|
+
「正文里正常提到 TODO」当成 pending 警告。
|
|
28
|
+
- 这是本仓反复出现的那个形态:**守卫测的东西 ≠ 它想测的东西**。粗判据在「正文恰好提到 TODO」时红,
|
|
29
|
+
而它想拦的只有一种情况 —— bump 插的骨架还没被人填。
|
|
30
|
+
- **反向验证 2 条**:① 往 `[0.10.0]` 一节插回那段骨架标记 ⇒ 严格闸门**恰好点名**这一条(`exit 1`);
|
|
31
|
+
② 同一状态下加 `--allow-pending` ⇒ 降级为 ⚠ 警告(`exit 0`,bump 后的预期行为)。
|
|
32
|
+
还原后 `sha256` 逐字节一致。
|
|
33
|
+
- 影响面:`scripts/release-surface.mjs`(两处判据 + 为什么不能用裸 `TODO` 的注释)、本节。
|
|
34
|
+
`tests/scripts/release-scripts.test.ts` **10/10 仍绿** —— 它的两条断言锚的本就是那段骨架标记
|
|
35
|
+
与「输出含 TODO」,精确化之后照样成立。
|
|
36
|
+
|
|
37
|
+
### 文档 · 推广稿重写成「可直接发出去的定稿」(2026-09-29 ⑲)
|
|
38
|
+
|
|
39
|
+
- **是什么**:`docs/promotion.md` 此前是**素材清单**(Product Hunt / X / 短版 / awesome 条目 /
|
|
40
|
+
视频旁白,各一段),且口径是「配合 30 秒演示视频」的草稿 ⇒ 真要发的人还得自己组织成文。
|
|
41
|
+
重写为**八节定稿**:发之前自检 / 中文主稿(长文,可整段粘贴)/ 短版 / X 英文线程 /
|
|
42
|
+
Product Hunt / awesome PR 条目 / 视频旁白 / **评论区预设应答(7 条)**。
|
|
43
|
+
- 补上此前**缺的两块**:① 中文长稿 —— 旧稿只有英文 PH + 中文短版,中文社区**没有可直接发的长文**;
|
|
44
|
+
② 评论区预设应答 —— 发出去后必然被问的七个问题(零依赖怎么调模型 / 与 LangGraph·Mastra·
|
|
45
|
+
Vercel AI SDK 的区别 / `0.x` 敢不敢用 / RAG 怎么办 / 为什么不做日志与存储 / 附属包为什么 404 /
|
|
46
|
+
性能),每条都**指回仓内真源**,不替框架总结立场。
|
|
47
|
+
- **诚实段落进正文**:主稿第六节就是「我劝退你的部分」,并指向官网 `/tradeoffs` —— 与本节
|
|
48
|
+
⑱ 那条同源:选型时最有用的不是优点清单,而是作者愿意把劝退条件写在哪、写多细。
|
|
49
|
+
- **数字纪律**:稿里**只写**与记分牌同源的下限(三处)与结构性事实(4 个装饰器 / Node 18+);
|
|
50
|
+
**体积刻意不写** —— 解包体积每次发布都会小幅变,抄进稿里就是第二个会腐烂的副本
|
|
51
|
+
(需要时报 npm 页上的值,或抄 README 顶部那行 —— 那行由守卫现算)。
|
|
52
|
+
- **代码块真编译过**:`promotion.md` 不在 `code-fences` 守卫的取样面里(取样面是「对外阅读面」的
|
|
53
|
+
固定清单),但它是**被照抄概率最高**的文档,复制出去后没有任何东西盯着它 ⇒ 用一次性探针
|
|
54
|
+
`.workbuddy/probes/verify-promotion-blocks.mjs` 按同一套 tsconfig(`paths` 指向 `src/index.ts`,
|
|
55
|
+
`strict` + `exactOptionalPropertyTypes`)把两个 `ts` 块(装配 / HTTP 宿主)真编译一遍,全过。
|
|
56
|
+
- **反向验证 2 条**:稿里中文、英文各改一处守卫条数 ⇒ 各**恰好点名** `scoreboard` 那条
|
|
57
|
+
(另外两条 README 断言不受影响),还原后 `sha256` 逐字节一致。
|
|
58
|
+
- 影响面:`docs/promotion.md`(重写)、本节。
|
|
59
|
+
|
|
60
|
+
### 官网 · 加一页「取舍对照」(2026-09-29 ⑱,评审 P1-1)
|
|
61
|
+
|
|
62
|
+
- **是什么**:新页面 `/tradeoffs`(`packages/website/src/pages/tradeoffs.astro` + 片段
|
|
63
|
+
`src/fragments/tradeoffs.html`)。评审要的是「**取舍对照**」而不是「**优势对照**」——
|
|
64
|
+
明写**不做什么**、代价是什么、以及**什么场景该选别人**。
|
|
65
|
+
- 六节:定位(刻意只占「运行时 + 可观测」两层)/ **不适用场景** / 刻意不做的事(空白由谁填)/
|
|
66
|
+
代价清单 / 什么时候该选它 / 怎么自己核实。
|
|
67
|
+
- **此前对外表达面从来没有「不适用场景」**(实测 `grep -rn "不适用" packages/website/src
|
|
68
|
+
README.md` ⇒ 0 命中)⇒ 读者只能从「它有什么」反推边界,而**反推出来的边界必然比真实边界宽松**。
|
|
69
|
+
- 页面里每一条「不做」都**指回仓内真源**(`spec.md` §9.3「已锁定」/ `usage-guide.md` 的判据 /
|
|
70
|
+
`no-runtime-deps.test.ts` / `examples/grpc-host/`),不替框架总结立场 —— 与 `deployment.md`
|
|
71
|
+
同一条口径:**决定清单 + 指回真源**,每多抄一份正文就多一处会分叉的口径。
|
|
72
|
+
- **守卫** `tests/docs/tradeoffs-page.test.ts`:页面与片段在 / 含「不适用场景」节(评审验收点)/
|
|
73
|
+
导航 + sitemap + llms.txt **三处都登记**(Nav 要求 ≥2 —— 首页与非首页分支各一个)/
|
|
74
|
+
正文站内锚点真实存在 + 自证。**反向验证 4 条,各恰好点名**,还原后 `sha256` 逐字节一致。
|
|
75
|
+
- 影响面:新增 2 个页面文件、`Nav.astro`、`sitemap.xml.ts`、`llms.txt.ts`、
|
|
76
|
+
`tests/docs/tradeoffs-page.test.ts`(新)、`docs/guards.md` §1.4、本节。
|
|
77
|
+
|
|
78
|
+
### 文档 · 推广稿的「50+ 条守卫」改成现算值并纳入守卫(2026-09-29 ⑰,评审 P1-2)
|
|
79
|
+
|
|
80
|
+
- **是什么**:`docs/promotion.md` 三处写着「50+ invariants / 50+ registered invariants /
|
|
81
|
+
50+ 条守卫」,而 `docs/guards.md` §1 实际是 **99 条** ⇒ **自贬**,而且是那种**没人会发现**
|
|
82
|
+
的自贬(推广稿不是测试、没有门禁)。改成与 README 记分牌同源的下限 `90+`。
|
|
83
|
+
- **光改一次不够** ⇒ 一并纳入 `tests/docs/scoreboard.test.ts`(推广稿是最容易「抄一次就
|
|
84
|
+
再也不更新」的文本:发完就没人回头核)。只钉「守卫条数」这一个数 —— 推广稿里其余数字
|
|
85
|
+
(4 个装饰器 / Node 18+)是**结构性事实**,改了就是产品变了、不是腐烂。
|
|
86
|
+
- 文件头加了一句「这个数别手改 + 发之前跑 `npm test`」,且**刻意不写那个数**(多写一处就
|
|
87
|
+
多一处要同步,而它恰恰是会被守卫点名的字面量 —— 这条是被变异验证逼出来的,见 guards.md)。
|
|
88
|
+
- **反向验证 7 条**(原 4 条 + 推广稿三处各改一处,各恰好点名),还原后 `sha256` 逐字节一致。
|
|
89
|
+
- ⚠️ 评审 P1-2 的另一半是「**执行** promotion.md(发到 Product Hunt / X / V2EX 等)」——
|
|
90
|
+
那一步要账号与发布时机,**不是代码能做的**,留给仓库所有者。
|
|
91
|
+
- 影响面:`docs/promotion.md`、`tests/docs/scoreboard.test.ts`、`docs/guards.md` §1.4、本节。
|
|
92
|
+
|
|
93
|
+
### 文档 · 五个 bench 的数字对外 + 三面守卫(2026-09-29 ⑯)
|
|
94
|
+
|
|
95
|
+
- **是什么**:`README.md` / `README.en.md` / 官网 docs 页各加一节「性能量级」,把仓里五个
|
|
96
|
+
基准(`scripts/bench-*.ts`)的读数搬出来 —— 此前对外文档面**一个性能数字都没有**。
|
|
97
|
+
每个数都配了**形状结论**(差多少倍 / 往返几次 / 哪一笔跟什么成正比)与**复跑命令**。
|
|
98
|
+
- ⚠️ **这一节的射程要如实说清**:五个基准是**计时类**的,本仓早就定了「不进 verify-all / CI,
|
|
99
|
+
要看时跑」(`docs/plans/2026-09-22-dev-debug-loop.md`)⇒ **毫秒数守不住**(机器一换就变、
|
|
100
|
+
且零信号),守卫也**刻意不去跑基准**。所以表格里明确写着「**毫秒不是承诺**」。
|
|
101
|
+
真正恒定的是**形状**(常驻 runner 换能力选择只付 ≈8 ms 而非 ≈4 s ⇒ ~500×;MCP 冷 280 ms /
|
|
102
|
+
热 0.3 ms ⇒ ~900×;sqlite 到期索引 0.1 ms vs 全表 62 ms ⇒ ~600×;trace 不截断是大出参档的
|
|
103
|
+
13.6×、小出参档只差 1.0× ⇒ 代价**按出参字节**计)。
|
|
104
|
+
- **守卫守的是「读者能不能自己复算出那一列」**:五个基准每个都有可跑的复跑命令且脚本真实
|
|
105
|
+
存在 + 三面都写了「不是承诺」+ 三面都写了「刻意不进 CI」+ `BENCH_SCRIPTS` 与 `scripts/` 目录
|
|
106
|
+
双向相等。**反向验证过 5 条,各恰好点名**,还原后 `sha256` 逐字节一致。
|
|
107
|
+
- 影响面:`README.md`、`README.en.md`、`packages/website/src/fragments/docs.html`(+ 侧栏锚点)、
|
|
108
|
+
`tests/docs/bench-numbers.test.ts`(新)、`docs/guards.md` §1.4。**框架代码零改动。**
|
|
109
|
+
|
|
110
|
+
### 文档 · `docs/deployment.md` 上线清单 + 守卫(2026-09-29 ⑮)
|
|
111
|
+
|
|
112
|
+
- **是什么**:从「跑通 demo」到「推上生产」之间那篇文档 —— store 选型(含「单宿主写者」前提)、
|
|
113
|
+
鉴权边界(`/healthz` 与 `/metrics` 不鉴权)、优雅停机的四步顺序(宽限 > drain 超时 > store 后关)、
|
|
114
|
+
预算旋钮、多副本写路径协调、回滚(含「旧代码读新数据不保证」的如实口径)、§10 上线自检复选框。
|
|
115
|
+
- **三条口径**:只做「决定清单 + 指回真源」,不重复 usage-guide §7 与 `examples/deploy`;
|
|
116
|
+
每条来自已核实的仓内事实(`redisStore.ts` 头注 / `record.ts` 读时归一层),不是通用模板;
|
|
117
|
+
完备性由 `tests/docs/deployment.test.ts` 守(节骨架 / 路径真实 / 五个事故级机制词 /
|
|
118
|
+
「不鉴权」如实锚 / 复选框下限;5 条变异反向验证)。
|
|
119
|
+
|
|
120
|
+
### 变更 · 加 `exports` 字段:公开 API 边界从「文档承诺」变成「机制」(2026-09-29 ⑭)
|
|
121
|
+
|
|
122
|
+
**可观测面变更**(`0.x` 期间 minor 可含破坏性变更 —— 你要做的动作写在下面「迁移」一节)。
|
|
123
|
+
|
|
124
|
+
**起因**:「上生产 × 推广」评审的 `P2-2`。`README.md` / 官网的「稳定性与版本策略」节早就写明
|
|
125
|
+
「**不**承诺深路径导入(`dist/**`)」—— 但两个**已发布**包的 `package.json` 里**没有 `exports`
|
|
126
|
+
字段**,于是那句话是一句**没有机制的话**:2026-09-29 实测 `import('@migor/agentia/dist/index.js')`
|
|
127
|
+
**真的能进**(`require('@migor/agentia')` 也照常拿到 81 个导出)。任何一个这么写的使用者都会把
|
|
128
|
+
内部模块路径当成 API —— 一次重构就把他打碎,而且**没有任何信号**。这正是本仓反复记的那个形态:
|
|
129
|
+
**承诺写在文档里、没有门禁**(`docs/guards.md` 头注)。
|
|
130
|
+
|
|
131
|
+
**改动**:
|
|
132
|
+
|
|
133
|
+
- `@migor/agentia` 新增 `exports`:根入口 `{"types" → ./dist/index.d.ts, "default" → ./dist/index.js}`
|
|
134
|
+
(`types` **排第一** —— TS 按书写顺序匹配条件)+ `"./package.json"`;
|
|
135
|
+
- `@migor/cli` 新增 `exports`,**只放开 `"./package.json"`**。
|
|
136
|
+
|
|
137
|
+
**为什么用 `default` 而不是 `import` / `require` 两条**:本包**只有 ESM 一种产物** —— 写两条会暗示
|
|
138
|
+
存在 CJS 构建。`default` 同时接住 `import` 与 `require`(后者靠 Node 的 `require(esm)`),
|
|
139
|
+
**行为与改动前逐字一致**(Node 18 上照样是 `ERR_REQUIRE_ESM`)。
|
|
140
|
+
|
|
141
|
+
**为什么 `@migor/cli` 的 `exports` 只留 manifest**:它的公共面是**可执行文件**(`bin.agentia`),
|
|
142
|
+
不是模块 —— 入口 `packages/cli/src/cli.ts` 末尾是 `process.exitCode = main(process.argv.slice(2))`
|
|
143
|
+
(**顶层副作用,没有 main 守卫**)⇒ 给 `"."` 等于承诺「import 这个包会直接跑 CLI」。只留 manifest
|
|
144
|
+
(工具读清单的常规请求),其余封死。
|
|
145
|
+
|
|
146
|
+
**守卫** `tests/architecture/package-exports.test.ts`(登记 `docs/guards.md` §1.4):四类判据 ——
|
|
147
|
+
① 两个已发布包都声明 `exports` 且放开 `./package.json`;② 根入口 `types` 排第一、目标与顶层
|
|
148
|
+
`main`/`types` 指向同一文件(`./` 前缀归一后比较 —— 两侧写法天然不同);③ 除根入口与 manifest 外
|
|
149
|
+
**不开放任何子路径**,且目标都在 `./dist/**`(源码不进 tarball)+ `files` 含 `dist` 的自证;
|
|
150
|
+
④ **真解析**:自建临时夹具(真 manifest 的逐字拷贝 + 占位文件)用 `require.resolve` 跑一遍 Node
|
|
151
|
+
的解析算法 —— 根入口/manifest 必须解析成功、深路径必须报 `ERR_PACKAGE_PATH_NOT_EXPORTED`。
|
|
152
|
+
⚠️ 刻意**不用符号链接指向仓库**(那会让夹具依赖 `dist/` 已构建,且批量删会被环境的安全删除垫片拦下)。
|
|
153
|
+
|
|
154
|
+
**反向验证 5 条(各恰好点名,还原后 `sha256` 逐字节一致)**:摘掉根包 `exports` ⇒ 四条全红;
|
|
155
|
+
`types` 挪到 `default` 之后 ⇒ 只红「types 排第一」那条;加一条 `"./dist/*"` ⇒ 红「不开放子路径」+
|
|
156
|
+
「真解析」;CLI 摘掉 `./package.json` ⇒ 红 ①③④;把 `default` 指到别的文件 ⇒ 红 ②④。
|
|
157
|
+
(①②③ 是**文本**判据、④ 是**行为**判据 —— 后者是前者的兜底:`exports` 的错法有一半是形态对、
|
|
158
|
+
指向错。)
|
|
159
|
+
|
|
160
|
+
**实测(真发布形态:真 pack → 真装进空项目 → 真跑)**:`import`/`require` 根入口各 **81 个导出**
|
|
161
|
+
(与改动前**同数** ⇒ 导出面零变化);`require.resolve('@migor/agentia')` → `dist/index.js`;
|
|
162
|
+
`@migor/agentia/package.json` 与 `@migor/cli/package.json` 可解析;`@migor/agentia/dist/index.js`、
|
|
163
|
+
`@migor/agentia/dist/engine/loop.js`、`@migor/cli`、`@migor/cli/dist/cli.js` **四条全被拒**
|
|
164
|
+
(`ERR_PACKAGE_PATH_NOT_EXPORTED`);装出来的 CLI `cli.js --version` 与 PATH 上的 `agentia --version`
|
|
165
|
+
都是 `0.9.5`(bin 解析不经过 `exports`)。CI 的 `scripts/e2e-cli.ts` 第 9 步本来就在跑这条链。
|
|
166
|
+
|
|
167
|
+
**迁移**
|
|
168
|
+
|
|
169
|
+
只有一种情况需要你动手:**你 import 过 `@migor/agentia` 的深路径**(例如
|
|
170
|
+
`import … from '@migor/agentia/dist/engine/loop.js'`)。改成从根入口导入 ——
|
|
171
|
+
公开面一直是 `src/index.ts` 的具名导出,深路径**从来不在承诺范围内**,只是此前没被封。
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// 改前(现在会抛 ERR_PACKAGE_PATH_NOT_EXPORTED)
|
|
175
|
+
import { runAgent } from '@migor/agentia/dist/engine/loop.js';
|
|
176
|
+
// 改后
|
|
177
|
+
import { runAgent } from '@migor/agentia';
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`@migor/cli` 是**命令行工具**,不受影响。读清单的 `…/@migor/*/package.json` 也照旧可用。
|
|
181
|
+
|
|
182
|
+
**登记**:`docs/guards.md` §1.4、`docs/spec.md` §10 ⑭。
|
|
183
|
+
|
|
184
|
+
### 文档 · README 顶部加「实测记分牌」——数字一律脚本现算(2026-09-29 ⑬)
|
|
185
|
+
|
|
186
|
+
**使用者可见行为零变更**(README 顶部一行 + 一条新守卫):框架代码**零改动**。
|
|
187
|
+
|
|
188
|
+
**起因**:「上生产 × 推广」评审的 `P0-2` 与它引用的那条外部判据 —— 当前最大的选型陷阱是
|
|
189
|
+
**GitHub star bias**(「为了 50 行的问题上一个重型多层框架」)。本仓的答案正是
|
|
190
|
+
「**1 个包 / 0 运行时依赖 / ≈2 MB**」,而这个数字 `grep -nE "2\.7|1 个包" README.md` ⇒ **0 命中**:
|
|
191
|
+
README 里只有形容词「零依赖」。**形容词不参与比较,数字才参与。**
|
|
192
|
+
|
|
193
|
+
**改动**:`README.md` 与 `README.en.md` 的品牌句之后各加一行记分牌
|
|
194
|
+
(`**1 个包 · ≈2 MB · 0 运行时依赖 · 90+ 条守卫**`),并新增 `tests/docs/scoreboard.test.ts`
|
|
195
|
+
(登记在 `docs/guards.md` §1.4)把它钉住 —— 评审原文要求「**必须脚本现算,不许手写**」。
|
|
196
|
+
|
|
197
|
+
**三条口径(都不是随手定的)**:
|
|
198
|
+
|
|
199
|
+
1. **「会涨的数」用下限**(`90+ 条守卫`,断言 `count >= 90`)—— 精确值会让「每加一条守卫都要改
|
|
200
|
+
README」成为绕开守卫的理由(本仓已知的失效模式);下限是可机械复算的,且只在**掉下来**时咬人。
|
|
201
|
+
2. **体积按「四舍五入到整数 MB」判定**,让数字**自带精度**:`≈2 MB` 覆盖 1.5–2.5 MB;
|
|
202
|
+
写 `2.1 MB` 会因几十 KB 的正常增长而红 —— 那不是信号,是噪声。
|
|
203
|
+
3. **不把 `usage-guide §7` 的已知边界条数搬上来**:那个数已有专属守卫(`boundary-table.test.ts`
|
|
204
|
+
的行集合对拍 + 三处读数断言),再放一份 = 造**第二个会腐烂的副本**,且两处口径必然分叉
|
|
205
|
+
(§7 首列是散文,归一化规则只为那张表定义)。
|
|
206
|
+
|
|
207
|
+
⚠️ **先定口径再写数**:评审里那个「2.7 MB」量的是 `du -sh node_modules`(含文件系统块分配),
|
|
208
|
+
而 npm 页上显示的是 `dist.unpackedSize`(`npm pack --dry-run --json` 现算 = **2.10 MB**)。
|
|
209
|
+
两者差 ~30% ⇒ 数字落纸之前**必须先选口径**,否则记分牌自己就是一句不可比的假话。
|
|
210
|
+
|
|
211
|
+
**代价如实标注**:`npm pack` 在本机约 **11s**(I/O 为主,`--offline` / 关 notifier 都压不动)
|
|
212
|
+
⇒ 守卫里**记忆化**,整轮只真跑一次。**不用自己遍历 `dist` 加字节**:实测差 ~1.5%
|
|
213
|
+
(自己数 2.08 MB vs npm 2.10 MB),而复刻 npm 的选文件规则(`files` 与 `.npmignore` 的交互)
|
|
214
|
+
是一件会静默失准的事。
|
|
215
|
+
|
|
216
|
+
**登记**:`docs/guards.md` §1.4、`docs/spec.md` §10 ⑬。
|
|
217
|
+
### 工程 · CI 的 Node 上沿跟到当前 LTS + 补上发版流程文档(2026-09-29 ⑫)
|
|
218
|
+
|
|
219
|
+
**使用者可见行为零变更**(CI 配置 + 文档):框架代码**零改动**。
|
|
220
|
+
|
|
221
|
+
**起因**:「上生产 × 推广」评审里那条 `A2` —— **CI 的上沿停在 Node 22,而当前 LTS 已经是 24**。
|
|
222
|
+
当时的 README / README.en / CONTRIBUTING / `usage-guide.md` 四处都写着「CI 在 18/20/22 上守」:
|
|
223
|
+
**那句话是真的**,问题是①上沿没跟 LTS 走;②**没有任何东西盯着这四处散文**(矩阵一改就静默腐烂)。
|
|
224
|
+
|
|
225
|
+
**改动**:`.github/workflows/ci.yml` 里四个主 job(`verify` / `lint` / `e2e-mcp` / `deploy-website`)
|
|
226
|
+
的 `node-version` 由 `'22'` 改成 **`'24'`**,并把**覆盖规则**写进 ci.yml 顶部:
|
|
227
|
+
`import-floor` 的 matrix 只覆盖 `engines` 声明的**下限**,其余 job 跟**当前 LTS** 走
|
|
228
|
+
⇒ 覆盖集合 = **18 / 20 / 24**;四处散文同步改成这句(`docs/usage-guide.md` 的已知边界表也复述了一处)。
|
|
229
|
+
|
|
230
|
+
> ⚠️ **为什么不顺手把 24 加进 `import-floor` 的 matrix**:**matrix 取值会进必需检查名**
|
|
231
|
+
> (分支保护里现在钉的是「导入下限(Node 18)」与「(Node 20)」)。增删取值 = 改仓库的分支保护配置,
|
|
232
|
+
> 否则新取值那条不是必需检查、旧取值那条永远等不到。所以「跑下限」与「跟上沿」**刻意分成两件事**,
|
|
233
|
+
> 并把这条坑写在 ci.yml 顶部(此前只写在 `AGENTS.md` 的会话记录里)。
|
|
234
|
+
|
|
235
|
+
另:`publish` 目前仍是**本机手动**两条命令(顺序「发布 → 合并 → 打 tag」,理由见 `release.mjs` 头注)。
|
|
236
|
+
`npm publish --provenance` 只支持在 CI 里生成 ⇒ 本轮**没有动它**,作为独立议题留下(要动就要在
|
|
237
|
+
GitHub Actions 里加发布工作流 + 一个仓库 secret,那是仓库所有者的运维决定)。
|
|
238
|
+
|
|
239
|
+
**补文档**:`CONTRIBUTING.md` 新增「**发版流程**」一节(`release.mjs bump/tag` 两条命令、
|
|
240
|
+
两个必填 TODO、四步顺序与**为什么不能换**、以及几条纪律)。此前 CONTRIBUTING 里
|
|
241
|
+
`grep -niE "release|发布流程|version"` ⇒ **0 命中** —— 一条对外可见的流程却没在任何面向人的文档里。
|
|
242
|
+
|
|
243
|
+
**登记**:`docs/spec.md` §10 ⑫。
|
|
244
|
+
### 文档与守卫 · 把「对外承诺」变成可执行的断言(稳定性策略 / 三个不发布的包 / CI 运行环境)(2026-09-29 ⑪)
|
|
245
|
+
|
|
246
|
+
**使用者可见行为零变更**(纯文档 + 一条新守卫):框架代码**零改动、零新依赖**。
|
|
247
|
+
|
|
248
|
+
**先说结论(三次误报的定性收口)**:`@migor/trace-view`、`@migor/agentia-observability`、
|
|
249
|
+
`@migor/agentia-eval-gate` **三个都不发布到 registry** —— 三个 `package.json` 都是
|
|
250
|
+
`"private": true`,**这是机制、是刻意的**(`roadmap.md` 早已写明「不进 npm,不是待修项」;
|
|
251
|
+
`eval-gate/README.md` 写明「这是宿主的发布流程,框架不内建」)。⇒ 正确动作不是「发布它们」,
|
|
252
|
+
而是**把文档口径补齐、再用守卫钉住**。
|
|
253
|
+
|
|
254
|
+
**新增 `README.md`「稳定性与版本策略」节**(+ `README.en.md` 对应节 + 官网 docs 页 `id="versioning"` 一节):
|
|
255
|
+
|
|
256
|
+
- **`0.x` 的变更策略**:minor 可以含破坏性变更,但**必须留痕** —— `CHANGELOG.md` 对应版本里
|
|
257
|
+
至少要有一段专门写它(小节标题含「迁移」或「破坏性变更」),写清「你要改什么」。
|
|
258
|
+
⚠️ **如实标注**:这条纪律是**逐步收紧**的 —— 同一个意思历史上有三种形态(`### 迁移` /
|
|
259
|
+
`### 破坏性变更 · …`(`0.9.0` 脚手架模板)/ 行内 `**迁移**:无`(`0.8.1`~`0.8.3`)),
|
|
260
|
+
所以文档给的枚举命令是**下限**而非全集。
|
|
261
|
+
- **承诺范围的四行表**:公共 API(`src/index.ts` 的具名导出 / CLI 命令行面)· 行为(以「迁移」小节为界)·
|
|
262
|
+
运行环境(`engines.node` + CI 覆盖的每个版本)· 依赖(运行时零第三方依赖,有守卫)——
|
|
263
|
+
各自对应「**不**承诺什么」(深路径导入 / `examples/*` / Deno·Bun·edge / devDependencies)。
|
|
264
|
+
- **1.0 的三条门槛**:`spec.md` §11 开放项逐条落地或明确划掉 · `guards.md` §2「待守」清空 ·
|
|
265
|
+
公共导出面连续 3 个 minor 无破坏性变更。
|
|
266
|
+
- **刻意不写死数字**:不用「N 个版本里只有 M 个带迁移小节」这种会腐烂的读数,改为给
|
|
267
|
+
**现数命令**;而**那条命令本身也由守卫自证**(必须真在 `CHANGELOG.md` 上跑得出结果 ——
|
|
268
|
+
文档给一条零命中的命令,效果与写错数字一样)。
|
|
269
|
+
|
|
270
|
+
⚠️ **落地前回源核对抓到的真错**(写下来是因为它就是「为什么会有那条命令自证」的由来):初稿写的是
|
|
271
|
+
「**没有「迁移」小节的版本,就是不需要你改代码的版本**」,配 `grep -nE '^#{3,4} .*迁移'` ——
|
|
272
|
+
**这句话是假的**:同一个意思历史上有三种形态(`### 迁移` / `### 破坏性变更 · …`(`0.9.0`)/
|
|
273
|
+
行内 `**迁移**:无`(`0.8.1`~`0.8.3`)),只认第一种会漏掉 `0.9.0`(**它真的有破坏性变更**)
|
|
274
|
+
与 `0.8.2` 那句「唯一需要动作的是 `maxRetries`」。⇒ 改成「至少要有一段专门写它」+ 如实标注
|
|
275
|
+
「这条纪律是逐步收紧的、那条命令是下限」,并顺手把**命令的非空**也钉成断言。
|
|
276
|
+
|
|
277
|
+
**补齐「怎么拿到它」(此前只有 observability 一支写全)**:`examples/eval-gate/README.md` 与
|
|
278
|
+
`docs/eval-gate.md` 现在都明写「**没发布到 registry,`npm i` 会 404**;把目录拷进工程、以
|
|
279
|
+
`"file:…"` 引入,或直接照 `src/gate.ts` 抄走」;`examples/observability/README.md` 也在自己的
|
|
280
|
+
文档里复述了 404(此前只说「沿用 `trace-view` 的同一套办法」)。
|
|
281
|
+
|
|
282
|
+
**新增守卫** `tests/docs/stability.test.ts`(登记在 `docs/guards.md` §1.4):
|
|
283
|
+
|
|
284
|
+
1. **不发布的包不得漏写口径** —— 扫 `根 + packages/* + examples/*` 的 `@migor/*` 包,
|
|
285
|
+
`private: true` 的集合与文件内的登记表**双向相等**(幽灵与漏登都报;`@migor/website` 走
|
|
286
|
+
带理由的豁免),再逐包断言 `private === true` + 入口文档写了「不发 npm」+ 写了「怎么拿到」;
|
|
287
|
+
2. **稳定性承诺三面互为镜像** —— `README.md` / `README.en.md` / 官网 docs 页各抽节,断言同一组
|
|
288
|
+
承诺锚点与 1.0 的三条门槛都在(三面**刻意各写一份**,代价由这条守卫兜底);
|
|
289
|
+
3. **CI 的 Node 集合 == 四份文档写的集合** —— 从 `ci.yml` 抽实际值(matrix + 各 job 的
|
|
290
|
+
`node-version`),与 `README.md` / `README.en.md` / `CONTRIBUTING.md` / `docs/usage-guide.md`
|
|
291
|
+
里复述的那串版本号**集合相等**,并断言 `engines.node` 的下限**真在 CI 里被跑到**;
|
|
292
|
+
`usage-guide.md` 是权威文档,它的 §7 已知边界表复述了同一句话 ⇒ 射程覆盖它才算数;
|
|
293
|
+
4. **文档里那条「数一数」命令必须真跑得出结果** —— 给一条零命中的命令,效果与写错数字一样。
|
|
294
|
+
|
|
295
|
+
**反向验证 6 条**(各恰好点名):版本句改 `18/20` ⇒ 只剩集合相等那条红;eval-gate 摘
|
|
296
|
+
`private` ⇒ 集合相等 + 机读 private 两条红;官网节改名 ⇒ 三面镜像三条红;eval-gate README
|
|
297
|
+
**整段**摘掉「先看这条」⇒ 只红「入口文档说了不发 npm」那条;那条「数一数」命令改成零命中
|
|
298
|
+
⇒ 只红命令自证那条;`usage-guide.md` 的复述处改成 `18/20/21` ⇒ 红「四份文档 == ci.yml」
|
|
299
|
+
那条(把射程从三处扩到四处时补做的 —— 扩射程必须配一次变异)。
|
|
300
|
+
|
|
301
|
+
⚠️ **一条教训**:④ 的**第一版变异只删掉一句、结果全绿** —— 同一件事在那一节里出现不止一次
|
|
302
|
+
(「不发布的包」/「别 `npm i`」)。**变异要摘干净**,否则测的是「这个词还在不在」,
|
|
303
|
+
不是「这件事还说没说」。
|
|
304
|
+
|
|
305
|
+
**登记**:`docs/guards.md` §1.4、`docs/spec.md` §10 ⑪。
|
|
306
|
+
|
|
307
|
+
### 测试与工具 · 补上「完整装配 × 时间维度」这一格 + examples 清单守卫(2026-09-29 ⑩)
|
|
308
|
+
|
|
309
|
+
**使用者可见行为零变更**(新增一个手跑脚本 + 一条新守卫 + 一处文档订正):框架代码**零改动**。
|
|
310
|
+
|
|
311
|
+
**起因**:竞争力评审里那句「没有一个基于框架的复杂 demo 长期运行验证」,查下来**一半对** ——
|
|
312
|
+
长期运行验证**有**(`scripts/e2e-soak.ts`,2 小时档实测 811 万请求、内存有界与否皆有断言),
|
|
313
|
+
完整装配的 demo 也**有**(`examples/complete`),但**两者的射程不重叠**:`e2e-soak` 的 import 面
|
|
314
|
+
只有 `createOpenAIClient` / `metricsSink` / `runAgent` / `AgentTool`(**无** createApp / 容器 /
|
|
315
|
+
store / scheduler / AsyncRunner / 恢复扫描)⇒ 压的是**无状态单点**;而跑完整装配的
|
|
316
|
+
`e2e-examples` 是**线性脚本、只跑一轮**。⇒ **「完整装配 × 时间维度」这一格是空的。**
|
|
317
|
+
|
|
318
|
+
**新增 `scripts/e2e-soak-app.ts`**(`npm run e2e:soak:app`;黑盒——只走 HTTP 与子进程,
|
|
319
|
+
不 import 框架):起 `examples/complete` 的 dist + 本地假 OpenAI 端点(五类故障注入),
|
|
320
|
+
中途用假端点**闸门**把任务精确停在 running 再 SIGKILL。七条断言:不丢(全到终态)/ 不重
|
|
321
|
+
(幂等簿记)/ 猝死续跑(同库重启真把活干完)/ 故障按比例落地 / 定时触发真在派 / 干净退出 /
|
|
322
|
+
DB 每任务字节数有界。
|
|
323
|
+
|
|
324
|
+
**读数**(30s × 6 并发):4452 个任务**全部终态**(succeeded=4286 / failed=166)、**零 stuck**、
|
|
325
|
+
幂等簿记准、猝死续跑 5 个且都跑完、故障对账 **335 vs 336**、DB **39.1MB**(≈ 9.0KB/任务)。
|
|
326
|
+
8s 短档**重复跑 3 次**同样全绿 —— 参见下面那条关于「抖动」的说明。
|
|
327
|
+
|
|
328
|
+
⚠️ **一条比判据更值得记的事:变异验证全过,也不等于断言可靠。**
|
|
329
|
+
初次落盘时 5 条变异全部「各恰好点名那条」,看着已经足够扎实;但其中 ③(闸门任务续跑后
|
|
330
|
+
必须 `succeeded`)是**概率性**的 —— 续跑那次模型调用仍走故障注入,3% 的不可重试 400 抽中就
|
|
331
|
+
假红(30s 首跑绿是运气)。**8s 档重复跑**时才抓到。⇒ 修法:给假端点加 `pauseFaults()` /
|
|
332
|
+
`resumeFaults()`,**只**在「等闸门任务跑完」那一小段关掉注入(判的是恢复,不是运气),并补一条
|
|
333
|
+
**自证**:窗口之后注入数必须高于窗口前读数,否则「忘了重开」会让阶段 B 静默变成无故障负载。
|
|
334
|
+
阶段 A / B 的几千个请求照旧吃满注入,④ 在那里对账 —— **注入机制的存活性没有因此放松**。
|
|
335
|
+
|
|
336
|
+
⚠️ **两条判据上的新认识**(都写进脚本头注;依据不同,别混写):
|
|
337
|
+
① 装配层里「一个不可重试故障**恰好**杀死一个 run」**不成立**——依据是**代码语义**(子能力的失败
|
|
338
|
+
由能力层包成 `is_error` 回主循环、**不杀 run**,见 `src/engine/loop.ts` / `src/engine/spec.ts`;
|
|
339
|
+
本例装配里真有子 agent 与 skill,而假端点服务的**所有**模型调用都可能是 400),再加上猝死窗口内
|
|
340
|
+
被续跑成功的任务会把那次失败洗掉 ⇒ 本脚本用量级判据,**不是** `e2e-soak` 那种逐笔对账
|
|
341
|
+
(实测 335 vs 336 已经很接近,但**接近不等于恒等**,不该断言恒等式);
|
|
342
|
+
② 故障注入是**按请求**的,而请求同时来自同步 `/run` 与异步任务 ⇒ **两边都要统计**——这条是
|
|
343
|
+
**实测**出来的:只对账异步那一半会得出「注入数 > 失败数」的假结论(首跑 129 vs 267 就是这么来的)。
|
|
344
|
+
|
|
345
|
+
**顺带修掉一处腐烂读数**:`examples/README.md` 那张表**此前零守卫**,已烂成三个互不相同的
|
|
346
|
+
读数——文案写「四个示例」、表 5 行、目录 6 个(`eval-gate` 整行缺席);依赖说明那段的
|
|
347
|
+
「三个应用示例」也对不上实况(六个**全部**在用 `file:../..`)。已补齐,并**不再写死数字**。
|
|
348
|
+
|
|
349
|
+
**新增守卫** `tests/docs/examples-table.test.ts`:表 ↔ 目录**双向相等** + 每行链接指向真实目录
|
|
350
|
+
+ 条目密度下限(解析器退化成 0 行时红)。反向验证 2 条(删掉 `eval-gate` 那一行 / 加一行幽灵
|
|
351
|
+
目录),各恰好点名那条。
|
|
352
|
+
|
|
353
|
+
**登记**:`docs/guards.md` §1.4 两行(含反向验证读数)、`docs/spec.md` §10 ⑩。
|
|
354
|
+
|
|
355
|
+
### 测试与文档 · 文档围栏代码块真编译(竞评收官:换尺子)+ 三处「照抄会坏」的片段修正(2026-09-29 ⑨)
|
|
356
|
+
|
|
357
|
+
**使用者可见行为零变更**(纯文档 + 一条新守卫):框架代码**零改动**。
|
|
358
|
+
|
|
359
|
+
**发现方式**:产品竞争力评审走到「说不出还有什么缺口」时换的尺子 —— 不再问「缺什么」,
|
|
360
|
+
改问「**已经写下来的东西是不是真的**」,取样对象取**代码围栏块**(读者会逐字照抄),
|
|
361
|
+
因为它**恰好不在既有守卫面上**(`usage-guide.test.ts` 只收表格行、`run-output-shape.test.ts`
|
|
362
|
+
只钉 `.finalText` 一种形状)。实测:88 个围栏块 / 22 个含具名导入的 ts 块 ⇒ **3 个照抄坏掉**,
|
|
363
|
+
且**没有任何一条是新引入的 —— 是从未被检查过**:
|
|
364
|
+
|
|
365
|
+
1. `README.md` 的 `scheduler.every(60_000, '巡检一次', { idempotencyKey: 'patrol' })` ——
|
|
366
|
+
`ScheduleEveryOptions` 里没这个键,真名是 **`idempotencyPrefix`**。TS 报 `TS2353`;
|
|
367
|
+
**JS 用户静默无幂等**,而示例上方那句注释正写着「幂等键去重」。⚠️ 同一块上一行的
|
|
368
|
+
`runner.submit(…, { idempotencyKey })` 是**对的** —— 两个同名旋钮,一个在一个不在。
|
|
369
|
+
2. `docs/usage-guide.md` 的 `ctx!.deferUntil(due)` —— `ToolRunContext.deferUntil` 是**可选成员**,
|
|
370
|
+
`!` 只消掉了 `ctx` 的 null ⇒ `TS2722`;改为 `ctx?.deferUntil?.(due)`。
|
|
371
|
+
3. `docs/usage-guide.md` 的出站传播片段是**裸方法**(`@Tool` 挂在函数声明上 + 漏必填 `schema`)
|
|
372
|
+
⇒ **不是合法 TS、无法被任何编译器检查**;补成完整的 `class OrderTools { … }`。
|
|
373
|
+
|
|
374
|
+
**新增守卫** `tests/docs/code-fences.test.ts`:把「含 `@migor/agentia` 具名导入」的 ts 块落成文件、
|
|
375
|
+
**一次性交给 `tsc`**(`paths` 指向 `src/`,不依赖先 build,约 0.3s)。三条反假阳性机制缺一不可 ——
|
|
376
|
+
① **语法错会连坐**:实测 `tsc 7`(tsgo)只要程序里存在语法错误就**整体跳过语义阶段**(22 个块只报
|
|
377
|
+
3 条语法错、其余全被吞 ⇒ 守卫会变成静默全绿),故迭代摘出语法错的块并**断言摘掉数为 0**;
|
|
378
|
+
② **省略垫片**(吃掉「名字不存在」、吃不掉「类型形状不对」);③ **省略记号归一成展开 any**
|
|
379
|
+
而不是空容器(后者会造出守卫自己的假阳性)。另有**金丝雀**(故意写错的块必须被报出来)与
|
|
380
|
+
**取样下限 20** 两条自证。
|
|
381
|
+
|
|
382
|
+
**验证**:4 条变异各恰好点名那条(两条真错各 1 条 + 裸方法 1 条 + 金丝雀自证 1 条),
|
|
383
|
+
还原后 `sha256` 逐字节一致。`docs/guards.md` §1.4 登记一行、`docs/spec.md` §10 ⑨ 记决策。
|
|
384
|
+
|
|
385
|
+
**顺带订正一处腐烂读数**:`docs/observability.md` 的日志层那格原写「2026-09-15 实测 9 处
|
|
386
|
+
`console.error/warn`」,现测 **22 处(散在 12 个文件)** ⇒ 不再写死数字,改为给现数命令
|
|
387
|
+
`grep -rn "console\." src/`。
|
|
388
|
+
|
|
389
|
+
**如实记**:`usage-guide.md` 里 `from '@migor/agentia-observability'` 的块首次对拍曾报 `TS2307`,
|
|
390
|
+
但它是**按设计的配方**(`docs/observability.md` 已明写「`npm i` 会 404,这是本地小包」,
|
|
391
|
+
`examples/observability` 真有那些导出)⇒ **不计为缺口**,守卫按 `tsconfig.tests.json` 的
|
|
392
|
+
`paths` 映射过去,不再误报。
|
|
393
|
+
|
|
394
|
+
### 测试与文档 · 时钟回拨下三条时间判据的退化方向(`DEEP-AUDIT` §3.4 的 L0 补栏)(2026-09-29 ④)
|
|
395
|
+
|
|
396
|
+
**使用者可见行为零变更**(纯测试 + 文档):`tests/transport/{approval-policy,wake-policy,resume-policy}.test.ts`
|
|
397
|
+
各补一组「NTP 回拨」用例(共 6 条),钉住三条时间判据在回拨下的**退化方向**;`docs/guards.md` §1.2
|
|
398
|
+
登记一行;`docs/spec.md` §10 ④ 记决策。同轮把 `docs/reviews/2026-09-28/README.md` §4 原来那句
|
|
399
|
+
「它是本仓**质量门禁**的下一步来源」**订正为「测试策略」**(那两节盘点的是测试覆盖缺口,不是闸门待办),
|
|
400
|
+
并把四栏逐栏判过:L0 补用例(本轮)、L1 已由抽件红利覆盖、**L2 定时器注入缝记为「有意不做」**、
|
|
401
|
+
L3 是「运维化」而不是「门禁化」。
|
|
402
|
+
|
|
403
|
+
- **为什么值得记**:三条判据里 `resumeSkipReason` 的**新鲜度档**是回拨下**唯一一条让系统更不安全的
|
|
404
|
+
方向** —— `too-fresh` 变长 ⇒ 判不了主人(异主机 / 升级前旧格式 ownerId)的崩溃孤儿**饿死窗口
|
|
405
|
+
≈ 回拨幅度**(扫描是惰性的,扫不到就再没人扫);而 `approvalExpired` / `timerDue` 只是**推迟**
|
|
406
|
+
(等更久 / 睡更久)。另一条纪律被钉住:`fillTimeoutDenials` 的 `decidedAt` **可以早于**
|
|
407
|
+
`requestedAt` —— 判据层**不许**把负差值钳掉,钳掉回拨就从 trace 上消失了。
|
|
408
|
+
- **验证**:6 条用例 + **5 条变异**逐条亲跑点名(`git checkout --` 还原后 `sha256` 逐字节一致)。
|
|
409
|
+
⚠️ 其中两条新用例**如实记为不独立**(会同时打红既有用例),已写进用例头注与 `guards.md`。
|
|
410
|
+
- **有意不做**:`AsyncRunnerOptions` 上的 `now()` / `timers` 注入缝 —— ① 半假时钟比不注入更坏;
|
|
411
|
+
② 整链可注入是一次跨编排层的时钟抽象(全仓 `Date.now()` 39 处),不是加个可选参数;
|
|
412
|
+
③ 判据是「先有病例,再有设备」。
|
|
413
|
+
|
|
414
|
+
### 测试与工具 · soak 的错误分类判据改成穷尽表(2026-09-29 ⑤)
|
|
415
|
+
|
|
416
|
+
**使用者可见行为零变更**(判据 + 新导出常量):`src/engine/errors.ts` 新增
|
|
417
|
+
`ERROR_TYPES` / `ErrorType`(`classifyError` 的返回类型收窄成 `ClassifiedError`,**不改运行期
|
|
418
|
+
行为**;`SpanError.type` 仍是公共类型上的 `string` —— 收紧它才是破坏性变更,没做)。
|
|
419
|
+
|
|
420
|
+
**这一条是「把 2 小时 soak 真跑起来」拿到的第一份结果,而且那轮是红的** —— 红的是**判据**:
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
AssertionError: 存在未知错误分类:connection
|
|
424
|
+
run:total=7189536 failed=325235(4.52%)thrown=0,吞吐 998.5 run/s
|
|
425
|
+
端点:requests=8114503 … 内存:heap 61.6MB → 69.6MB,rss 峰值 204.7MB(27 个采样)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
`e2e-soak.ts` 的断言里手写着一份三名字白名单(`api` / `server` / `rate_limit`),漏了框架
|
|
429
|
+
自己的合法类 `connection` —— 719 万 run 里它出现 **1 次**,默认 60 秒的跑法永远撞不到。
|
|
430
|
+
现在判据单源化:`scripts/soak-error-posture.ts` 的 `SOAK_ERROR_POSTURE` 是
|
|
431
|
+
`Record<ErrorType, 规则>` 穷尽表(加一类不表态 ⇒ `TS2741`,每条必须写 `why`),
|
|
432
|
+
`soakErrorVerdict` 对**表外一切**判 `hard-red`;`unknown` / `aborted` / `timeout` 判红,
|
|
433
|
+
注入的四类允许但**分类计数打印出来**(允许 ≠ 不看)。
|
|
434
|
+
|
|
435
|
+
- **验证**:9 条用例(`tests/scripts/soak-error-posture.test.ts`,**不跑 soak 本体**)+ **6 条
|
|
436
|
+
变异**逐条亲跑点名,还原后 `sha256` 逐字节一致;短档 20s 实测全过且新打印行在场。
|
|
437
|
+
- ⚠️ **一条命令教训**:处置表在 `scripts/`,而根 `tsconfig.json` 只 include `src` ⇒ 守它的是
|
|
438
|
+
`npm run typecheck:tests`。第一次跑变异拿 `typecheck` 验,结果是**假绿** —— 已写进头注并补
|
|
439
|
+
承重条件用例。
|
|
440
|
+
|
|
441
|
+
### 修复 · 外部深评施工收口(7 条 + 施工中新发现的第 8 条)+ §7 边界表回填 + 官网注入已知边界表(2026-09-29 ⑥)
|
|
442
|
+
|
|
443
|
+
**来源与性质**:`FIX-READINESS-2026-09-29.md` 给出的施工顺序 1–7(A1 / D1 / B1 / C1 / F1 / F2 / F3
|
|
444
|
+
+ 结构建议① + 官网建议①③),外加施工中**实测发现的第 8 条 C5b**。本轮的共同点仍是前几轮那条:
|
|
445
|
+
**把静默换成有声,把判据换成真能咬人的那种**。结构建议②(`async.ts` 抽审批监督簇 + 行数上限闸)
|
|
446
|
+
按该报告 §8「各自独立 PR」另起一轮,**未随本批**。
|
|
447
|
+
|
|
448
|
+
**使用者可见行为变更(4 处,均为「静默 → 有声」或判据更准)**:
|
|
449
|
+
|
|
450
|
+
- **构造期拒 `NaN` 上限(施工中新发现,C5b)**:`src/core/limits.ts` 的 `BadValuePolicy` 新增第 5 类
|
|
451
|
+
`'rejects-nan'`,`BudgetGuardOptions.maxTotalTokens` / `maxCostUsd` 由 `'none'` 改用它;
|
|
452
|
+
`src/engine/budget.ts` 加 `assertNotNaN`(抛 `RangeError`)。**为什么必须**:闸的判据是 `used > max`,
|
|
453
|
+
而 `x > NaN` 恒为 `false` ⇒ 传了 `NaN` 的闸**静默失效**,方向与「设了个上限」正好相反。
|
|
454
|
+
0 / 负数 / `±Infinity` 仍照旧放行(各有语义),**只拒 `NaN`**。
|
|
455
|
+
- **容器注入判据从「有 `then`」改为「`instanceof Promise`」(D1)**:`src/container/container.ts`。
|
|
456
|
+
原判据会**误伤** knex / mongoose 的 query builder —— 实证两者的 builder **同时**带 `then` 与 `catch`
|
|
457
|
+
(knex `builder-interface-augmenter.js`;mongoose 官方文档明列 `Query.prototype.catch()`),
|
|
458
|
+
所以「`then` 且 `catch`」这个加固方向**无效**;而「忘了 `await`」的产物恒为原生 Promise ⇒
|
|
459
|
+
`instanceof` 精确命中且不误伤。**如实边界**:跨 realm 的 Promise(vm 产物)会漏,已写进注释;
|
|
460
|
+
逃逸口 `useValue: { promise }` 保留。
|
|
461
|
+
- **`RedisTaskStore.close()`(C1)**:`src/store/redisStore.ts` 的 `RedisLike` 加 `quit?(): Promise<unknown>`,
|
|
462
|
+
`RedisTaskStore` 新增**幂等** `close()`(无 `quit` 则 no-op;重复关闭 / 已断连时的 reject 都吞掉)。
|
|
463
|
+
取 `quit` 的理由:它是 ioredis / node-redis **唯一共名**的关闭出口(node-redis v5 起标 deprecated、
|
|
464
|
+
后继 `close()`,已写进接口注释)。**框架仍不主动替你调**(store 可能共享,drain 不关 store)。
|
|
465
|
+
- **记忆水合失败的两条 warn(A1)**:`src/runtime/run.ts`。① 水合 `load` 抛错不再无声吞
|
|
466
|
+
(`catch {}` → `console.warn`);② `flushMemory` 的 CAS 分支开头判 `rev === undefined` ⇒
|
|
467
|
+
**跳过 CAS 回写**并说真话(「这一次读失败了」≠「并发抢写」)。**为什么**:能走到该分支的 store 必有
|
|
468
|
+
`loadWithRev`(成对性由 `assertMemoryStoreShape` 入口保证),所以此处 `rev === undefined` ⟺
|
|
469
|
+
水合失败;旧代码把它夹带到几十行之后的 CAS 回写、归因成并发 —— **查错会一开始就查错方向**。
|
|
470
|
+
|
|
471
|
+
**测试与文档(使用者可见行为零变更)**:
|
|
472
|
+
|
|
473
|
+
- **F3**:删 `tests/toolkit/capability-slice.test.ts` 的恒真断言(源码里 `CAPABILITY_KINDS = Object.keys(KIND_SPEC)`,
|
|
474
|
+
那句是 `x === x`);保留精确键序断言与 #184 的 `TS2741` 穷尽护栏 —— 删了不降低任何防线强度。
|
|
475
|
+
- **F2**:`AGENTS.md` 的 toolkit 段补 `capability-cycles.ts`(`841f2ac` / #170 引入、地图漏了);
|
|
476
|
+
新建 `tests/architecture/toolkit-map.test.ts` —— 判据是**「露名或显式豁免」**(实证「全量穷尽」不可行:
|
|
477
|
+
`src/toolkit/` 13 个文件里 9 个用**概念词**覆盖而非文件名词,属正常);两条用例(未登记集合必须为空 +
|
|
478
|
+
F2 当事人必须被点名),已登记 `docs/guards.md` §1.1。
|
|
479
|
+
- **`AGENTS.md` 结构数字**:`:66` 区域「910 行的类」→ **1554 行**,并注「计划写于 09-20(当时 895 行),
|
|
480
|
+
后续三步一步没走,类反长 115%」—— 这类**写死的读数会腐烂且无守卫**,改到结构时顺手改真值。
|
|
481
|
+
- **B1**:`src/transport/task-events.ts` 两处注释订正(零行为变化)—— `:204` 那句「**不会**回收刚 push
|
|
482
|
+
的这条」是**假的**(单热流场景下若真跳过它,候选集恒空 ⇒ 配额完全失效 ⇒ 无界增长,比现状更坏;
|
|
483
|
+
故取「改注释」而非「改行为」);`#recycleNonTerminal` 补第 3 条代价(退订不触发回收 ⇒
|
|
484
|
+
配额真实上界 = `nonTerminalBuffers + 曾有订阅者的流数`)。
|
|
485
|
+
- **F1**:`tests/docs/boundary-table.test.ts` 的 31 条 `gap` 逐条回源核对 ⇒ **7 条回填为 `pin`**、
|
|
486
|
+
1 条订正理由(读数 **pin 49→56 / gap 31→24**,§7 仍 **90 行**);三处写死的行数读数一并同步
|
|
487
|
+
(文件头注 88→90、A4 断言文案 89→90、登记表注释);`docs/guards.md §2 待守` 登记一行
|
|
488
|
+
「`gap` 表需随每轮盘点复核」—— `gap` 表一旦写下就再没人复算,09-29 抽样 3 条**3 条全过期**。
|
|
489
|
+
- **官网(建议①③)**:`packages/website/src/pages/docs.astro` 在**构建期**从单源
|
|
490
|
+
`docs/usage-guide.md?raw` 抠出 §7 表,注入 `docs.html` 新增的 `#limits` 段(占位符找不到 / 行数 < 70
|
|
491
|
+
都**硬失败**,仿 `llms-full.txt.ts` 的 `?raw` 手法,单源不漂移);`docs.html` 两处「去看
|
|
492
|
+
`/llms-full.txt`」的指路改成 `#limits` 锚点。产物实测 **90 行表 + `href="#limits"`×2**。
|
|
493
|
+
|
|
494
|
+
**验证**:本轮累计 **5 条变异**逐条亲跑点名(F2 守卫改名 ⇒ 两条同时红;C5b `assertNotNaN` 判据失效 ⇒
|
|
495
|
+
恰好那条红;D1 判据退回 `typeof then` ⇒ 恰好新用例红;C1 `quit` 未被调 ⇒ 恰好那条红;A1 跳过分支失效 ⇒
|
|
496
|
+
恰好那条红),`git checkout --` 还原后全绿。门禁三件套全绿(`typecheck` / `typecheck:tests` /
|
|
497
|
+
`lint` 443 文件零告警);相关套件:runtime 56 · engine 404 · container 11 · store 56 · transport 352 ·
|
|
498
|
+
toolkit 142 · architecture 57 · limits 32 · boundary-table 6 · 各 docs/官网守卫 —— 全绿。
|
|
499
|
+
|
|
500
|
+
**有意不做(留独立一轮)**:`async.ts` 抽「审批监督」簇 + 行数上限闸。回源实测其真实跨度**大于**报告描述
|
|
501
|
+
(`#suspended` Map 还被恢复簇的 `#resyncSuspended` 重建、6 处 `#unmarkSuspended` 调用点、对外 getter
|
|
502
|
+
`suspendedSummary`、`#dispatch` 是私有方法需回调注入)—— 这是**零行为变化的纯结构重构**,与前面
|
|
503
|
+
「修缺陷」性质不同,报告 §8 自己也标「各自独立 PR」;仓促混进本批会让「零行为变化」难以严格保证。
|
|
504
|
+
|
|
505
|
+
### 重构 · `AsyncRunner` 的审批监督簇抽成独立件(结构体检建议②)(2026-09-29 ⑦)
|
|
506
|
+
|
|
507
|
+
**使用者可见行为零变更**(纯结构重构:逐字搬迁 + 依赖改构造注入)。
|
|
508
|
+
|
|
509
|
+
`src/transport/async.ts` 里 `AsyncRunner` 的**审批监督簇** —— 挂起登记簿(`#suspended`,即
|
|
510
|
+
`/healthz` 的 `suspended` 读数)+ 在飞审批闸(`#inflightApprovals`)+ `approve` / `#approveInner`
|
|
511
|
+
+ 审批超时恢复(`#expireAndResume` / `#expireAndResumeInner`)—— 抽到新件
|
|
512
|
+
`src/transport/approval-supervisor.ts`(`ApprovalSupervisor`)。`async.ts` **1929 → 1756 行**。
|
|
513
|
+
|
|
514
|
+
- **为什么是这一簇**:结构体检(`STRUCTURE-INVENTORY-2026-09-29.md` 建议②)指出它的方向与
|
|
515
|
+
「认领 / 槽位 / 派发」**正交** —— 它管的是「谁挂着、谁在等审批、超时了没」。抽之前它被私字段
|
|
516
|
+
与私有方法缝在主类里,横跨 **5 处外部调用点**(`cancel` / `signalTask` / 到期唤醒 /
|
|
517
|
+
`resumePending` 扫描 / `#executeInner` 的挂起与终态出口)+ 对外 getter。
|
|
518
|
+
- **依赖怎么解**:构造注入 `store` / `ownerId` / `approvalTimeoutMs` / **`dispatch` 回调**。
|
|
519
|
+
`#dispatch` 仍归 `AsyncRunner`(停机闸 + 唯一派发口 + 一次性告警),审批簇只**消费**它 ⇒
|
|
520
|
+
依赖单向,也不会出现两模块互相 `import`。`TaskApproveError` / `ApprovalDecisions` 随簇迁走,
|
|
521
|
+
`async.ts` 顶部 **re-export** ⇒ 一切 `from './async.js'` 的既有 import 路径不变。
|
|
522
|
+
- **一处有意的等价简化**:`#redispatch` 的超时扫描原写作
|
|
523
|
+
`rec.status !== 'suspended' || !approvalExpired(...)`,而 `approvalExpired` 内部**已经**判
|
|
524
|
+
`status === 'suspended'` 与 `reason === 'approval'`(见 `approval-policy.ts`)⇒ 那句短路是
|
|
525
|
+
**冗余的**,合并进 `expireIfExpired` 不改变任何可观测行为(字面量多重集对拍已证:唯一
|
|
526
|
+
「消失」的字面量就是它)。
|
|
527
|
+
- **一处随之而变的守卫**:`tests/transport/dispatch-guard.test.ts` 的「`#dispatch(` 调用点下限」
|
|
528
|
+
射程**扩到两个文件**(两条恢复路径的派发点形如 `this.#deps.dispatch(`)—— 否则这次搬迁会把
|
|
529
|
+
计数从 7 掉到 6 而无谓判红。⚠️ **该守卫在改动中真的先红了**(先见红、再改守卫,不是反过来)。
|
|
530
|
+
- **验证**:3 条变异逐条亲跑 —— ① `expireIfExpired` 恒不触发 ⇒ 恰好 3 条红;② `approve` 的 409
|
|
531
|
+
判据退回只看 `status` ⇒ 恰好 1 条红;③ `rebuild` 的 reason 判据摘掉 ⇒ **全绿**(如实记为
|
|
532
|
+
**既存**覆盖缺口,已登记 `guards.md §2`)。三条还原后 `sha256` 逐字节一致。另跑 B.3「纯结构
|
|
533
|
+
拆分复核清单」第 2 条要求的**字符串字面量多重集对拍**(无丢失)。门禁三件套 + transport 352 /
|
|
534
|
+
architecture 57 / toolkit 142 / runtime 56 / engine 404 / container 11 / store 56 —— 全绿。
|
|
535
|
+
- **顺带登记**(`guards.md §2`):上面那条覆盖缺口,以及**行数上限闸为何暂不做**(口径未定、
|
|
536
|
+
易误报,与 §3「宁可窄,不要误报」有张力;替代形状见那一行)。
|
|
537
|
+
|
|
538
|
+
### 重构 · `AsyncRunner` 再抽两簇(信号投递 / 恢复扫描)+ **源码体量闸**(结构体检建议③)(2026-09-29 ⑧)
|
|
539
|
+
|
|
540
|
+
**使用者可见行为零变更**(纯结构重构:逐字搬迁 + 依赖改构造注入)。
|
|
541
|
+
|
|
542
|
+
⑦ 抽走审批簇后 `async.ts` 仍有 1756 行。本轮把剩下两个方向正交的簇也抽走:
|
|
543
|
+
|
|
544
|
+
- **信号投递**(`SignalSupervisor`,`src/transport/signal-supervisor.ts`,182 行 / **74 含代码行**):
|
|
545
|
+
`signalTask` / `#signalInner` / 在飞信号闸 + `TaskEventError` + 两个上限常量
|
|
546
|
+
(`MAX_DELIVERED_EVENT_IDS` 256 / `MAX_PENDING_EVENTS` 64)。它与 `ApprovalSupervisor.approve`
|
|
547
|
+
是**同一族**(挂起任务的外部唤醒入口;形状逐条对应:读记录 → 校验挂起 → 改状态 → 先落库再
|
|
548
|
+
派发 → `unmarkSuspended` → `dispatch`),两处真实差异:① 并发闸不同款(`approve` 共享在飞那次;
|
|
549
|
+
事件**串行成链**);② 多一道容量闸(到上限 409,不静默丢)。
|
|
550
|
+
- **恢复扫描与接管**(`ResumeScanner`,`src/transport/resume-scanner.ts`,261 行 / **126 含代码行**):
|
|
551
|
+
`resumePending` / `#redispatch` / `#ownerLiveness` / `wakeDue` / `#wakeDueInner` + 两把在飞闸。
|
|
552
|
+
一次扫描**按序**干四件事(顺序有语义):① `approvals.rebuild` ② `approvals.expireIfExpired`
|
|
553
|
+
③ `wakeDue` ④ `#redispatch` 认领重投(`wakeDue` 由私有改公开,两个调用点)。
|
|
554
|
+
|
|
555
|
+
`src/transport/async.ts` **1756 → 1460 行**(**664 含代码行**)。依赖仍走**构造注入**:两个监督件都只
|
|
556
|
+
**消费** `AsyncRunner.#dispatch`(停机闸 + 唯一派发口 + 一次性告警)⇒ 依赖单向、无模块环。
|
|
557
|
+
`TaskEventError` / `ResumePendingOptions` 随簇迁走 + `async.ts` 顶部 **re-export** ⇒ 一切
|
|
558
|
+
`from './async.js'` 的既有 import 路径不变。
|
|
559
|
+
|
|
560
|
+
**新增防回涨的闸**:`tests/architecture/source-size.test.ts` —— `src/**` 单文件**含代码行** ≤ 750。
|
|
561
|
+
|
|
562
|
+
- ⚠️ **口径与报告建议刻意不一致**:报告写「`async.ts ≤ 1600` **行**」,但本仓注释占 29~55%
|
|
563
|
+
(`async.ts` **55%**)、注释是证据载体(记着「判据只挡终态,别写成 `!== 'queued'`」这类踩坑)⇒
|
|
564
|
+
按**行数**设闸的第一个作用是**逼人删注释**(静默、且删的是最贵的资产);且按行数 `1460 < 1600`
|
|
565
|
+
**当下根本不触发**(事后不疼)。⇒ 与注释解耦:整行注释与空行一律不计入。
|
|
566
|
+
- **上限 750 的由来**:`async.ts` 664 与第二名 `turn.ts` 521 之间隔着一大截(143 行)—— 750 落在这段
|
|
567
|
+
空档里,给 `async.ts` 留约 13% 正常迭代余量,又远离合法长文件那一档(报告 §2 那批「不该拆」的
|
|
568
|
+
最长只有 260 行代码)⇒ 不误伤。
|
|
569
|
+
- **超了只有两条路、都必须显式**:① 抽件(本仓既有做法);② 改 `MAX_CODE_LINES`(改动落在这个
|
|
570
|
+
文件里 ⇒ review 时一定看得见,而不是悄悄长过去)。
|
|
571
|
+
- ⚠️ **刻意不用「剥掉注释再数非空行」**:本仓多处 URL 字面量与含 `//` 的字符串,朴素剥离会**吃掉
|
|
572
|
+
真实的代码行** ⇒ 读数偏低 ⇒ 闸静默失效。逐行只问「这一行是不是从头到尾都是注释」。
|
|
573
|
+
- **变异验证过**:上限压到 500 ⇒ 精确点名 `transport/async.ts: 664` / `engine/turn.ts: 521`
|
|
574
|
+
(当下越线的确实只有这两个);还原后复绿。
|
|
575
|
+
|
|
576
|
+
**两条随之而变 / 补上的守卫**:
|
|
577
|
+
|
|
578
|
+
- `tests/transport/dispatch-guard.test.ts` 的「`#dispatch(` 调用点」射程从**两个文件扩到四个**
|
|
579
|
+
(三条恢复路径的派发点形如 `this.#deps.dispatch(`),并**钉住每个监督件的精确数**
|
|
580
|
+
(approval 2 / signal 1 / scanner 3)+ 真空护栏。⚠️ 与 ⑦ 同款:**这条守卫每次搬件都会先掉一格
|
|
581
|
+
判红** —— 修法是**扩射程**、不是放宽下限(「路径总数」这一事实没变)。
|
|
582
|
+
- **补一条既存覆盖缺口**:变异「把 `signalTask` 的串行闸摘掉」⇒ **全绿**,说明这条闸**此前没有
|
|
583
|
+
任何用例钉住**(搬迁前内联在 `async.ts` 里时就没有)⇒ 补 `tests/transport/task-events-input.test.ts`
|
|
584
|
+
的「两条并发事件串行成链 ⇒ 只唤醒一次」(用**有往返的异步 store** 让并发窗口真实存在)。
|
|
585
|
+
|
|
586
|
+
**验证**:5 条变异逐条亲跑 —— ① `wakeDue` 的 drain 闸失效 ⇒ 恰 2 条红(`drain-race` + `durable-timer`);
|
|
587
|
+
② 认领后异步派发摘掉 ⇒ 恰 3 条红(`async.test`);③ 串行闸摘掉 ⇒ **全绿(既存缺口,已补用例)**;
|
|
588
|
+
④ 容量闸失效 ⇒ 恰 3 条红(`event-buffer-cap`);⑤ 补用例后重跑串行闸 ⇒ **恰好那条新用例红**。
|
|
589
|
+
全部 `cp` 还原后 `sha256` 逐字节一致。另跑 B.3「纯结构拆分复核清单」第 2 条的**字符串字面量多重集
|
|
590
|
+
对拍**(可复跑探针 `.workbuddy/probes/literal-multiset-diff.mjs`,逐字符状态机剥注释):旧(`async.ts`
|
|
591
|
+
+ `approval-supervisor.ts`)**81 种 / 142 个** vs 新(四文件)**83 种 / 154 个** —— **丢失 0 项**,
|
|
592
|
+
新增 6 项**全是 import 路径** ⇒ 纯搬迁。门禁三件套 + 套件:transport 353 / architecture 57 /
|
|
593
|
+
toolkit 142 / runtime 55 / engine 403 / container 10 / store 55 —— 全绿。
|
|
594
|
+
|
|
595
|
+
**顺带**:`guards.md §2` 那条「`async.ts` 没有行数上限闸」**闭环**(划掉、移入 §1.1);
|
|
596
|
+
`AGENTS.md` 的 `transport/` 段补登记三个监督件,并订正那句已过时的「910 行的类 / 后续抽块三步一步
|
|
597
|
+
没走」(抽块其实已走了三步)。
|
|
598
|
+
|
|
599
|
+
## [Unreleased]
|
|
600
|
+
|
|
601
|
+
## [0.9.5] - 2026-09-29
|
|
602
|
+
|
|
603
|
+
> 本版主题(窗口 `0.9.4 → 0.9.5`):**外部深评 P2 表的最后三条收口(K5 / T4 / K2)+ 两处「报告的判据要订正」**。
|
|
604
|
+
> 三块的共同点都是**把静默换成有声**:① 记忆回写从此**能发现**并发丢写(可选 CAS:冲突时**不写 + 出声**,
|
|
605
|
+
> 不替你合并);② 挂着的任务不再各占一份 ≈1 MB 的事件缓冲(新旋钮按**条数**配额,回收的是**缓冲**、
|
|
606
|
+
> 留表项与序号 ⇒ 读者先收一帧 `stream.truncated` 再转实时,而不是被谎报成「别的进程」);
|
|
607
|
+
> ③ 加第五类能力从「改五处按类分支、漏一处不报错」变成「给注册表加一行,不表态就 `tsc` 报 `TS2741`」。
|
|
608
|
+
> **无破坏性变更**:新加的两个能力都是**可选**的(不实现 ⇒ 行为与 0.9.4 逐字相同),
|
|
609
|
+
> `AsyncRunner.streamNonTerminalBuffers` 的缺省值只影响「回放看不全」那一档(且看得见)。
|
|
610
|
+
> 既有使用者**不需要任何动作**。
|
|
611
|
+
|
|
612
|
+
### 重构 · 四类能力的装配差异收进一张注册表(外部深评 K2)(2026-09-29 ③)
|
|
613
|
+
|
|
614
|
+
来源:`DEEP-AUDIT-VERIFIED-2026-09-28.md` 的 P2 表 `K2`。性质不是「代码不好看」:加第五类能力
|
|
615
|
+
此前要在 `toolkit/module.ts` 改**五处按类分支**(收集 / 可用名单 / `buildSlice` 展开 /
|
|
616
|
+
`@Prompt` 版本表 / 孤儿能力计数),漏一处只表现为「那类能力静默不进菜单 / 不进版本表 /
|
|
617
|
+
不计入孤儿告警」—— 构建与全部用例照样全绿。
|
|
618
|
+
|
|
619
|
+
- **新增 `src/toolkit/capability-slice.ts`**:`CapabilityPayloads` 定义「一类能力」是什么,
|
|
620
|
+
`KIND_SPEC` 的类型是映射类型 `{[K in CapabilityKind]: CapabilityKindSpec<K>}` ⇒
|
|
621
|
+
**加了 payload 成员却不给表项,`tsc` 报 `TS2741`**(与 `SUCCESS_STOP_REASON` /
|
|
622
|
+
`TERMINAL_STATUS` 同款护栏);`CAPABILITY_KINDS` **从表的键派生** ⇒ 五处遍历自动覆盖。
|
|
623
|
+
- **`module.ts` 的五处按类分支换成对表的遍历**(`collectCapabilities` / `capabilityToolNames` /
|
|
624
|
+
`buildCapabilitySlice` / `capabilityCount` / `capabilityVersions`)—— 行为零变更
|
|
625
|
+
(既有 toolkit 用例 142 条 + 全套件 1597 / 195 / 27 全绿即证据)。
|
|
626
|
+
- **两处刻意留在表外并写明理由**:菜单重名校验(跑在合并后的工具列表上,与类别无关)、
|
|
627
|
+
能力引用图(只有 `@SubAgent` / `@Skill` 有 `tools` 引用 —— 语义不对称,不是漏项)。
|
|
628
|
+
- 唯一的类型擦除集中在 `specOf()`(相关联合,TS#30581 的已知限制),安全性由「每行在定义处
|
|
629
|
+
逐个收窄 + 键集从表派生 + 逐类驱动用例」共同兜住 —— 不是靠这一句断言。
|
|
630
|
+
|
|
631
|
+
### 修复 · 非终态流的缓冲配额(外部深评 T4)(2026-09-29 ②)
|
|
632
|
+
|
|
633
|
+
来源:`DEEP-AUDIT-VERIFIED-2026-09-28.md` 的 P2 表 `T4`。报告当日判据不完整(单流**条数**上限
|
|
634
|
+
从 #115 起就在),**仍然成立**的是那半:**非终态流的缓冲只增不减** —— 每任务 ≤`streamBufferEvents`
|
|
635
|
+
(500) 条 ≈ 1 MB,而等审批 / 等事件 / 等定时器的任务**永远走不到终态** ⇒ 挂着的任务越多内存越大。
|
|
636
|
+
|
|
637
|
+
- **`TaskEventStreams` 新增 `nonTerminalBuffers`(缺省 32 条流)**,并经
|
|
638
|
+
**`AsyncRunner.streamNonTerminalBuffers`** 暴露给宿主(`0` = 不为没人读的非终态流留回放缓冲)。
|
|
639
|
+
超出配额即从**最旧且无订阅者**的非终态流开始**回收缓冲**。
|
|
640
|
+
- **回收的是缓冲,不是表项**(这是本次对报告判据的订正):表项留着当**桩**(`has()` 仍为真、
|
|
641
|
+
`nextIndex` 接着走),只把回放历史丢掉并用 `firstAvailable` 明示缺口 ⇒ 连上来的读者先收到
|
|
642
|
+
一帧 `stream.truncated` 再转实时,**不会**被谎报成 `stream.unavailable`(那是「别的进程」的台词,
|
|
643
|
+
而任务就在本进程跑着)。删表项会**静默破坏**「序号是流自己的」那条设计约束:恢复段重开
|
|
644
|
+
`nextIndex = 1` 的流,拿 `Last-Event-ID` 续订的读者从此永远筛不出东西。
|
|
645
|
+
- **代价如实**:① 正在被订阅的流**不受配额约束**(不抽走正在读的数据)⇒ 同时被读的流很多时
|
|
646
|
+
缓冲总量仍会超配额;② 每条非终态任务留一个桩(约百字节),桩本身不设上限(同因);
|
|
647
|
+
③ 「回放看不全」是**看得见**的降级(`stream.truncated` + `droppedBefore`),不是静默丢事件。
|
|
648
|
+
- `0` 语义登记进 `core/limits.ts`(`disabled`),探针在 `tests/limits.test.ts`(走公共旋钮,
|
|
649
|
+
两处接线一起驱动)。
|
|
650
|
+
|
|
651
|
+
### 变更 · 记忆回写的 CAS(可选、成对、冲突出声)(2026-09-29 ①)
|
|
652
|
+
|
|
653
|
+
来源:`DEEP-AUDIT-VERIFIED-2026-09-28.md` 的 P2 表 `K5`(**未做**那三条之一)。
|
|
654
|
+
病灶:`flushMemory` 是无条件**全量读改写** —— 同 keys 的并发 run 之间是 last-write-wins,
|
|
655
|
+
后写的那条把先写的改动整片覆盖,**双方都看不出异常**。
|
|
656
|
+
|
|
657
|
+
- **`MemoryStore` 新增两个可选成员**(`runtime/memory.ts`):`loadWithRev(keys) → { values, rev }`
|
|
658
|
+
与 `saveIfRev(entries, rev) → { committed, reason? }`(`rev` 是**不透明**版本句柄,框架不解释、
|
|
659
|
+
只在本 run 回写时原样递回)。**成对实现**:只实现一个是装配错误 ⇒ `executeRun` **入口**抛
|
|
660
|
+
`TypeError`(半个 CAS 与「不支持」等效,却让「有没有被覆盖」看起来有据可查)。类型级守卫在
|
|
661
|
+
`tests/types/memory-surface.types.ts`(两个成员必须在**接口上**且**可选**)。
|
|
662
|
+
- **冲突时:不写 + 出声**(`runtime/run.ts`):`saveIfRev` 回 `committed !== true`(含忘返回
|
|
663
|
+
`undefined` —— 不知道写没写就不许记成写成功)⇒ 该 key 集**一个字都不写**并打一条
|
|
664
|
+
`console.warn`(含 keys 与 `conflict`)。**框架不合并、不重试** —— 「后写赢 / 逐键赢 / 按时间戳赢」
|
|
665
|
+
都是策略,属 store 与宿主;框架只负责把「丢了一条写」变成有声。回写被拒**不改 run 结局**
|
|
666
|
+
(仍 `succeeded`,与 sink 抛错同款)。代价如实:这一轮记忆**没落库**,重试配方见 `usage-guide` §6.5。
|
|
667
|
+
- **不支持 CAS 的 store:出声降级**。只有 `load` / `save` 时行为**一字不改**(照旧 last-write-wins),
|
|
668
|
+
但**第一次**用这个 store 实例时打一条**一次性**提示(按实例去重,不是每轮刷屏)。
|
|
669
|
+
框架**检测不了**没有版本号的覆盖 —— 版本号是唯一依据,写进 `usage-guide` §7 边界表。
|
|
670
|
+
- `InMemoryMemoryStore` 顺带实现这一对:版本是**整店一个单调计数**(粒度粗 ⇒ 宁可多报冲突,
|
|
671
|
+
也不漏报真实覆盖;`loadWithRev` 的返回形状 = `MemorySnapshot`)。
|
|
672
|
+
|
|
673
|
+
### 变更 · store 的可选能力进接口(S5)+ 两处如实口径(K1 / T6)(2026-09-28 ㉓)
|
|
674
|
+
|
|
675
|
+
- **`TaskStore` 增加两个可选成员**(`store/store.ts`):`compact?()` 与 `close?()`。
|
|
676
|
+
`FileTaskStore.compact()` / `SqliteTaskStore.close()` 此前只活在具体类上,宿主拿到接口类型时
|
|
677
|
+
只能 `as` 强转(换 store 后运行期才炸)。现在调用点写 `store.compact?.()` 即可。
|
|
678
|
+
**框架不替你调**:`drain()` 不关 store(可能是共享的,关掉是宿主的生命周期决定),压实同理归宿主。
|
|
679
|
+
类型级守卫在 `tests/types/store-surface.types.ts`(`npm run typecheck:types`)。
|
|
680
|
+
- **文档写明「嵌套能力共用一块黑板、不隔离」**(`usage-guide` §5.1,零代码改动):
|
|
681
|
+
`@SubAgent` / `@Skill` 与父 run 是同一份 `RunContext`(`withRunContext` 全仓单点调用),
|
|
682
|
+
子 agent 写的键父 run 看得见(有意);并发子 agent 写同一键是 last-wins(与 `flushMemory` 同款取舍),
|
|
683
|
+
要隔离请自己命名空间。「按子树隔离」保留为 `spec.md` §11 开放项。
|
|
684
|
+
- **初始 `save` 不 await:定案不改**(外部深评 T6,报告已自行降级 P3):
|
|
685
|
+
`submit` 是**同步门面**(await 会把它变成 async = 破坏性变更,只换来「多等一个网络往返」);
|
|
686
|
+
「静默吞错」这一半已有处置(迟到的 reject 转成任务 failed,且只在任务未被推进时改判)并用例守着
|
|
687
|
+
(`tests/transport/async.test.ts`)。判据与承重理由写进 `spec.md` §10 ㉓。
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
### 修复 · 四条「静默失效」收口(2026-09-28 ㉒)
|
|
691
|
+
|
|
692
|
+
来源:`DEEP-AUDIT-VERIFIED-2026-09-28.md` 的 P2 表(`S1` / `S2` / `S3` / `S6`),
|
|
693
|
+
逐条在当前 `main` 上复核后动刀。四条同一族:**错了 / 算不出 / 超限了,却没有任何信号**。
|
|
694
|
+
|
|
695
|
+
- **redis store 的 TTL 不再吃掉非终态记录**(`store/redisStore.ts`,**行为变更**):
|
|
696
|
+
此前 `applyTtl` 不看状态,一条**等审批中**(`suspended`)的任务会在 TTL 到点时从 redis 里消失 ——
|
|
697
|
+
「批了它」的决定无家可归(`approve` 404);而内存 store 的淘汰逻辑里明写着 `suspended` 不可淘汰
|
|
698
|
+
(同一事实的两处读数方向相反)。现在「终态」判定单源到 `core/run.ts` 的 `isTerminalStatus`
|
|
699
|
+
(穷尽分类表:加成员不表态 ⇒ `tsc` 报缺属性),只有**终态**记录才拿到查询窗口(非终态覆写靠不带选项的
|
|
700
|
+
`SET` 清除既有 TTL),幂等索引与记录同生共死。**代价如实**:永远等不到审批的挂起记录会永久驻留
|
|
701
|
+
(批不批是人的事,store 无从判死)—— 与内存 store 同款取舍,清理属部署层职责。
|
|
702
|
+
- **上游没回报 usage 时不再静默填 0**(`integrations/anthropic.ts` / `integrations/openai.ts` /
|
|
703
|
+
`engine/turn.ts`):`?? 0` 让「端点不回 usage」(或忽略 `stream_options.include_usage`)与
|
|
704
|
+
「真的只花了 0」在数值上无法区分,于是 `maxCostUsd` 这条成本护栏**静默失效**。现在四条响应路径
|
|
705
|
+
(流式 / 非流式 × 两条适配器)都标 `MessageUsage.unreported`,引擎在 llm.turn span 上记
|
|
706
|
+
`usage.missing` 事件,报告出 `usageMissingTurns` / `usageMissingModels`、指标出
|
|
707
|
+
`agentia_model_usage_missing_turns_total`(render + OTLP 两出口)。与 `usage.unpriced`(模型不在
|
|
708
|
+
价格表里)**分开计数**:后果都是「成本看起来是 0」,但一个要换模型、一个要查端点/网关。
|
|
709
|
+
- **`statusOfStreamError` 收成一份**(`integrations/adapter-options.ts`):原本两条适配器各写一份、
|
|
710
|
+
靠注释互相喊话对齐 —— 合并时发现它们**早就不一致**(`not_found_error` 一侧 400 另一侧 500,
|
|
711
|
+
等于把「模型名错了」记成 `server`)。新增 `tests/integrations/stream-error-parity.test.ts`:
|
|
712
|
+
一张表跑两条真适配器 + 真 SSE,两行 `wasDivergent` 标出分歧点。
|
|
713
|
+
- **两条 MCP 桥的分帧缓冲加上限**(`core/line-framing.ts`,`MAX_FRAME_CHARS` = 8M 码元):
|
|
714
|
+
`buf += chunk` 遇到「不带换行的巨型行」就是无界增长(先 OOM 再谈协议;MCP server 往 stdout
|
|
715
|
+
混日志本来就常见)。连接器侧超限即拒绝全部在途请求并终止子进程;反向桥侧回一条协议错、
|
|
716
|
+
丢弃到下一个换行**重新对齐**(其后的帧照常派发)。
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
### 文档 · 外部深评的 5 份报告入库 + 逐条落地盘点(2026-09-28 ⑲)
|
|
720
|
+
|
|
721
|
+
**改了「复核报告不随仓提交」这个惯例**:那轮外部深评产出的 5 份报告**原文逐字入库**到
|
|
722
|
+
`docs/reviews/2026-09-28/`(入库前后逐份 `shasum -a 256` 比对,**逐字节一致**),
|
|
723
|
+
配一份索引 `docs/reviews/2026-09-28/README.md`,里面是**逐条落地状态表**。
|
|
724
|
+
|
|
725
|
+
- **为什么改**:① 剩余条目是真实的待办来源(P2 表还有 **14 条**未做 + 1 条「有意为之」),
|
|
726
|
+
不入库 ⇒ 只有当天在场的人知道还剩什么;② 本仓文档有 **7 处**引用这些报告,
|
|
727
|
+
而它们此前**指向一个不在仓里的文件** —— 「指向一个读不到的东西」本身就是坏引用。
|
|
728
|
+
- **代价如实**(写进索引 §0):入库即意味着这些文字会随代码**漂移**。所以正文逐字保留、
|
|
729
|
+
「现在还剩什么」一律以索引表为准、**每落地一条回来更新那张表**(本目录唯一要维护的地方)。
|
|
730
|
+
- **盘点结论**(逐条按其自报位置在当前 `main` 上重跑定点 `grep` / 读源码,不采信报告自述):
|
|
731
|
+
P1 表 9 条 **8 条已落地**(`C1`/`C2`/`T1` → #169,`E1`/`E2`/`T2`/`T3`/`J1` → #170);
|
|
732
|
+
P2 表 26 条 **11 条已落地**(`C6` → #171,`C3`/`C4`/`C5` → #172,`E3` → #170,`E8` → #174,
|
|
733
|
+
`T5` → #173,`S4` → #176),**14 条未做**,`T7` 为「有意为之」;
|
|
734
|
+
`DEEP-REVIEW` / `PR-164-REVIEW` / `SRC-STRUCTURE` 三份的残留项除两条「定案不做」外全部已落地。
|
|
735
|
+
- **顺带校正报告一处判据**(`T4`):报告说「非终态流的缓冲永不淘汰」,而**单流条数上限**
|
|
736
|
+
(500、丢最旧)在审计当时就已存在(#115 起)—— 报告漏看;仍成立的是「非终态流的**表项**
|
|
737
|
+
不被回收」。**判据不同、修法就不同**,所以这条保留为「未做」但把判据写准。
|
|
738
|
+
- **7 处引用**:`spec.md:4224`/`:4256`/`:4438`/`:4489` 与 `CHANGELOG.md:12` 原文里的
|
|
739
|
+
「(**未随本仓提交**)」自本条起不再成立 —— 按 spec 的**只增不改**纪律原文一字未动,
|
|
740
|
+
权威更正是 `docs/spec.md` §10 ⑲,逐条对照在索引 §3。
|
|
741
|
+
另一处(`CHANGELOG.md:315` 指向「**仓库根** `SRC-STRUCTURE-2026-09-28.md`」)是**坏引用**
|
|
742
|
+
(那个路径从来没存在过),已**就地修成真实路径** —— 修的是坏引用,不是历史事实。
|
|
743
|
+
|
|
744
|
+
### 修复 · 外部深评五条 P1 收口(2026-09-28 ⑫)
|
|
745
|
+
|
|
746
|
+
来源同上:`DEEP-AUDIT-VERIFIED-2026-09-28.md`(**未随本仓提交**)里「本轮复核方未覆盖、保留原判」
|
|
747
|
+
的其余五条 P1,**逐条实证后**才动刀(含一条被自己的诊断否掉的原判,见 spec §10 ⑫)。
|
|
748
|
+
|
|
749
|
+
- **上下文策略抛错不再判死整条 run**(`engine/turn.ts`):`ContextPolicy.beforeTurn` 里可能**走网络**
|
|
750
|
+
(compaction 的 `summarize` 是模型调用),一次 429 / 超时就会让整条 run 以「请求失败」收尾 ——
|
|
751
|
+
而上下文压缩对一条 run 是**尽力而为**的优化,不是前置条件。现在降级成「本回合原样放行」,
|
|
752
|
+
**且出声**:run 根上记一条 `context.policy_failed { iteration, message }` + 一条 `console.warn`
|
|
753
|
+
——否则「历史一直很长、token 慢慢涨」没人知道为什么。
|
|
754
|
+
- **压缩失败的滞回额度不再被烧掉**(`engine/policy.ts`):`lastCompactAt` 原先在
|
|
755
|
+
`compactMessages(...)` **之前**就写 —— 摘要器失败时额度已经用掉,于是「这一回合没压成」
|
|
756
|
+
还连带让后面 `compactEvery - 1` 个回合都不再尝试。现在记的是「上次**成功**压缩的回合」,
|
|
757
|
+
失败不记账、下一回合自然重试。⚠️ 代价说明:摘要器**持续**失败时每回合都会再调一次
|
|
758
|
+
(每次多一次失败的模型往返)—— 要抑制请在 `summarize` 里自己退避,别指望滞回额度兜。
|
|
759
|
+
- **MCP HTTP 传输的意外不再带走宿主**(`engine/mcp-server-http.ts`):请求处理那层 `try` 此前
|
|
760
|
+
**只有 `finally`** ⇒ 协议面之外的异常(例如某条 `inputSchema` 是带 getter 的对象,
|
|
761
|
+
`validateJsonSchema` 一读就抛)冒泡出 async handler = unhandled rejection = Node ≥15 默认
|
|
762
|
+
**终止进程**。同文件的鉴权钩子、`JSON.parse`、stdio 那条传输都有兜底,只有这里漏了 ⇒
|
|
763
|
+
「同一份协议面在两条传输上一条活着、一条把宿主带走」。现在回 **200 + JSON-RPC `-32603`**
|
|
764
|
+
(协议面的错误是 200 里的错误对象,不是进程级 5xx),响应头已发时只收口。
|
|
765
|
+
- **`drain()` 不再可能等一个永远不结束的等待**(`transport/http.ts` + `http-endpoints.ts`):
|
|
766
|
+
唯一能结束一条「自己不结束的 SSE」的机制(收口)原先排在等待**之后**,而缺省 `timeoutMs = 0`
|
|
767
|
+
是**不限** ⇒ 死锁。现在**活连接不享受无限窗口**:没有 deadline 时先收口再等,有 deadline 时
|
|
768
|
+
保持既有的优雅等待。返回值也**如实分两种收口**:切过在飞的 run ⇒ `false`(工作是被 abort 收尾的,
|
|
769
|
+
回 `true` 等于把「切了」说成「等干净了」);只关掉旁观者长连(`/tasks/<id>/stream`)
|
|
770
|
+
⇒ 不损失任何工作,不参与这个判断(把它算成没排干净是另一种说错话)。
|
|
771
|
+
- **能力引用成环在装配期即拒**(新增 `toolkit/capability-cycles.ts` + `module.ts` 接线):
|
|
772
|
+
`subagentToTool` 的第二参是**延迟求值 thunk**、装配期把引用全建满 ⇒ 环是**纯运行期**递归,
|
|
773
|
+
而 `maxIterations` 只限**宽度**、深度没有任何闸 ⇒ 模型一次自我调用就是无限递归
|
|
774
|
+
(树按 `宽度^深度` 炸开)。现在装配期 DFS 找环并抛错,错误信息给出**环上的节点序列**。
|
|
775
|
+
全仓 grep 确认**没有任何既有用例**写自引用 ⇒ 不误伤既有装配。
|
|
776
|
+
- **续跑认领从「新鲜度启发式」升级成「租约」**(新增 `transport/owner-id.ts`、`owner-liveness.ts`,
|
|
777
|
+
改 `resume-policy.ts` / `async.ts`):`staleAfterMs` 只回答「记录起跑多久了」,不回答
|
|
778
|
+
「主人还在不在」,两个方向都会错 —— ① 单段 run 跑超窗口 ⇒ 他进程当孤儿抢走 ⇒
|
|
779
|
+
**同一任务跑两遍**(真·重复执行);② 崩溃进程留下的记录**仍带 ownerId**(`submit` 恒写)
|
|
780
|
+
⇒ 落在保鲜期内没人捡 ⇒ **饿死**(扫描是**惰性**的,扫完就没人再扫)。
|
|
781
|
+
现在 `ownerId` 形状为 `p<pid>@<host>-<8 位十六进制>`,`staleAfterMs > 0` 时**同主机直接问
|
|
782
|
+
「那个 pid 还在不在」**(`process.kill(pid, 0)`):**在的绝不抢**(不管记录多老)、
|
|
783
|
+
**不在的立刻可抢**(= 崩溃孤儿,不等保鲜期);**判不了**(异主机 / 升级前写下的旧格式 /
|
|
784
|
+
自定义标识)才退回新鲜度启发式 ≡ 升级前行为。
|
|
785
|
+
- ⚠️ **`ownerId` 是内部标识,宿主别解析它**(格式本次变了)。混版本部署安全:旧进程写下的
|
|
786
|
+
无 `@host` 记录一律走「判不了 ⇒ 新鲜度」,行为与升级前一致。
|
|
787
|
+
- 残留风险如实记(两种都只会「**晚一点**才捡」,不会「抢错」):**PID 复用**与**僵尸进程**
|
|
788
|
+
(父进程未收尸)都会被判成「主人在」,要等那个 pid 真的消失才可捡。刻意**不加时间硬上限**
|
|
789
|
+
兜它们 —— 加上限 = 把租约换回新鲜度启发式,「长跑被抢」那个 bug 原样回来。
|
|
790
|
+
|
|
791
|
+
### 修复 · 定时器上限单源(2026-09-28 ⑪)
|
|
792
|
+
|
|
793
|
+
- **超过 `2^31-1`ms(≈24.86 天)的定时器延迟不再是「配置合法、行为相反」**:Node 不会遵守它
|
|
794
|
+
(只在 stderr 留一行 `TimeoutOverflowWarning`,随后把延迟钳到 **1ms**),于是「配 30 天超时」
|
|
795
|
+
会变成「每个调用立即超时」、「退避到限流窗口之后」会变成「热重试打风暴」。此前防线只在
|
|
796
|
+
`Scheduler.every` / `at`,而**派生出来的等待**(`withTimeout` ← 引擎 `toolTimeoutMs`;
|
|
797
|
+
`interruptibleSleep` ← 重试退避;`AsyncRunner.runTimeoutMs`;`createAnthropicClient.timeout`;
|
|
798
|
+
`DrainGate.waitForIdle` ← `drain` 的等待预算)一律只查「非负 / 有限」。现在上限单源在
|
|
799
|
+
`core/timeout.ts`(`MAX_TIMER_DELAY_MS`),五处站点在**构造期 / 入口**一并拒绝。
|
|
800
|
+
- **`NaN` 也拒**(同一处校验):`withTimeout(p, NaN)` 此前被那句 `!(x > 0)` 静默读成
|
|
801
|
+
「不设超时」(与「我设了个预算」正好相反,桥还会因此起自己的 60s 兜底 ⇒ 双计时器、双账本);
|
|
802
|
+
`interruptibleSleep(NaN)` 此前穿透「非正」判直落 `setTimeout(NaN)`(同样被钳到 1ms)。
|
|
803
|
+
- **上游的 `Retry-After` 走「夹」不走「拒」**:`Retry-After: 99999999` 不是使用者的错,
|
|
804
|
+
为它把一次 429 升级成硬失败是错的;现在被夹到上限,不再静默变成「1ms 后立刻重试」。
|
|
805
|
+
- **`cancel` 的 409 文案分成两种说法**(`AsyncRunner`):原先一律说「正在运行但**不在本进程**」,
|
|
806
|
+
而同一判据还覆盖「**已在本进程受理、正在等并发槽位**」(四条恢复路径先置 `running` 再派发,
|
|
807
|
+
句柄要到拿到槽位才登记)—— 对它说反话。现在两支分开措辞。**闸不放开**(放开会让
|
|
808
|
+
`onFinished` 双发)。
|
|
809
|
+
- ⚠️ **`limits` 表的 `withTimeout.ms` / `interruptibleSleep.ms` 的 `badValue` 由 `none` 改 `throws`**:
|
|
810
|
+
传给 `RunAgentOptions.toolTimeoutMs` 的 `NaN`、或任何超过上限的超时值,现在会**抛错**。
|
|
811
|
+
这两种值此前的行为都与配置意图相反(一个静默变「立即超时」,一个静默变「不设超时」)。
|
|
812
|
+
|
|
813
|
+
### 修复 · 复审收口(外部深评 2026-09-28)
|
|
814
|
+
|
|
815
|
+
- **schema 的环与深度护栏**(C6):`validateJsonSchema` 按 properties / items 递归,
|
|
816
|
+
**环 + 深值**会钻到 V8 的 `RangeError: Maximum call stack size exceeded` —— 症状没有辨识度
|
|
817
|
+
(看着像「校验不了」,而不是「schema 有环」)。现在两道闸:`assertNoSchemaCycle()` **前置**抓环
|
|
818
|
+
(与值深度无关,点名路径;用祖先集,**共享子树不算环**)+ `MAX_SCHEMA_DEPTH = 256` 兜
|
|
819
|
+
「合法但深到失控」。⚠️ 只加深度闸是**不够的**:递归深度 = min(schema 深度, 值深度),
|
|
820
|
+
所以「环 + 浅值」根本不会爆栈(我也正是这么写出第一版红用例的)。
|
|
821
|
+
- **limits 表的对账收口**(C4 + C5 家族):`LIMIT_SEMANTICS` 里声明「坏值抛错」的旋钮有 18 个,
|
|
822
|
+
而「报错文案真的取自同一张表」的对账 case 只覆盖 10 个,且那个清单是个 `Array` ——
|
|
823
|
+
新增一个 throws 旋钮却忘了对账,**不会有任何东西红**(「表说它有构造期校验」与「文案真同源」
|
|
824
|
+
之间没有机器守着)。现在:**A4 单向穷尽断言**(每个 throws 旋钮都必须有 case)+ 补齐 8 条
|
|
825
|
+
case + 把实现侧的文案接上 `zeroClauseOf`(7 处此前是手写或被通用文案盖掉)。
|
|
826
|
+
另补登 **5 个**从未进表的旋钮:`BudgetGuardOptions.maxTotalTokens` / `maxCostUsd`
|
|
827
|
+
(v0.5.0 起就存在)与 `metricsSink.maxCapabilities` / `maxModels` / `maxScores`。
|
|
828
|
+
顺带修 8 条 `zeroClause` 的**双层括号**(表里自带「必须为正数(…)」而实现又加一层),
|
|
829
|
+
并把 `withTimeout` 的 **async 站点**(坏值表现为 rejection 而非同步 throw)纳入对账。
|
|
830
|
+
- **排队段的上限 `AsyncRunnerOptions.maxQueued`**(T5,缺省 0 = 不限):`concurrency` 只约束
|
|
831
|
+
「同时在跑几个」,而「跑不上、排在后面」那一段**此前没有任何上限** —— `submit` 永远收单、
|
|
832
|
+
`POST /tasks` 永远 202,排队段随调用方灌入无界增长(每条排队任务 = 一条记录 + 一棵悬挂的
|
|
833
|
+
`#execute` promise + 一个槽位等待者,全在内存)。现在超限 `submit` 抛 `TaskQueueFullError`
|
|
834
|
+
(带 `status: 503` 自陈字段),HTTP 宿主回 **503 + `Retry-After`**(与 `maxConcurrentRuns`
|
|
835
|
+
同款,**不是 400** —— 排队满不是调用方的错)。
|
|
836
|
+
① **判据不是「已受理未持槽的条数」**,而是「**真正在排队的深度** = `max(0, 已受理未持槽数
|
|
837
|
+
− 空槽位数)`」:前者把「池子还空着、马上就能跑」的也算成排队的,`concurrency: 1` +
|
|
838
|
+
`maxQueued: 1` 下会把第 2 条**合法**提交误拒(第一版就是这么写的,被用例当场抓出来)。
|
|
839
|
+
② 为此把「出排队段」挪到 `await acquire()` **之前**做一次快照判断(`inUse < concurrency`
|
|
840
|
+
就当场出集合):只走 acquire 的连续体时,「占槽」(`running++`)是同步的、「出排队段」却要
|
|
841
|
+
等一个微任务,同一条任务在那一瞬被算两遍 ⇒ 深度虚高一格。
|
|
842
|
+
⚠️ 刻意**不**把它做成「同步快路径替代 acquire」:那会顺带少掉一次微任务,把「置 running」
|
|
843
|
+
相对引擎推进的时刻**提前**,踩到既有用例里「等到 `running` 就当工具已挂在飞」的隐含假设
|
|
844
|
+
(`cancel.test.ts` 的前置断言当场红,实测踩到过)—— **记账改原子,时序一个字不改**。
|
|
845
|
+
③ **只在 `submit` 判**:恢复路径(approve / 到期唤醒 / 崩溃重投)推进的是**已受理**的任务,
|
|
846
|
+
拦下来等于把它搁死在 store 里。④ 幂等键命中的重复提交**不吃** 503(闸在去重之后)。
|
|
847
|
+
⑤ `get queued` 与闸**同用** `#queueDepth`:读数即判据,不会各说各话。
|
|
848
|
+
顺带把 `SlotPool.inUse` 的「测试用」自述改成真话(它从此是闸的一半)。
|
|
849
|
+
- **工具 I/O 事件名单源到 `core/trace.ts`**(E8):`tool.input` / `tool.output` 跨
|
|
850
|
+
**engine → eval → integrations** 三层被手写(**12 处**取值 —— 报告写「7 处」偏小,我逐条点数是 12)。
|
|
851
|
+
漏一处的后果不是「少记一条事件」,是**静默归零**:`integrations/report.ts` 与
|
|
852
|
+
`metrics-state.ts` 都按名字过滤,改名漏一处 ⇒ 报表说「没有工具调用」而 trace 里明明有。
|
|
853
|
+
现在常量 + `ToolIoEventName` 类型落在 **`core/trace.ts`**(⚠️ 报告建议的落点
|
|
854
|
+
`engine/tool-events.ts` **不可行**:`integrations` 只许依赖 `core`,够不着 engine —— 那样
|
|
855
|
+
integrations 侧只能再手写一份,等于把单源补成两处);11 处取值 + 6 处 import 改引常量。
|
|
856
|
+
`eval/harvest.ts` 里**生成出去的用例源码文本**保留字面量:生成的脚本不 import 本仓常量,
|
|
857
|
+
改了会得到一个引用不存在标识符的脚本(跑起来才炸)。
|
|
858
|
+
配**源码级守卫** `tests/architecture/tool-event-names.test.ts`(自带遮蔽器,与
|
|
859
|
+
`lib/source-scan.ts` 相反 —— 那个按设计遮蔽字符串):三条自证样本 + 白名单按「文件 + 恰好
|
|
860
|
+
条数」+ 射程钉 + 走查下限;变异三条全部具名复红(含「守卫自身的守卫」:遮蔽器退化)。
|
|
861
|
+
- **删掉内部死方法 `TaskEventStreams.forget`**(外部深评「forget(taskId) 未处理」那条的收口):
|
|
862
|
+
全仓**零调用点**(`grep -rn 'forget(' src/ tests/ packages/ examples/ scripts/` 命中的
|
|
863
|
+
`scheduler.ts` 那 4 处是它自己的**局部函数** `const forget = (taskId) => inFlight.delete(taskId)`,
|
|
864
|
+
操作的是 `inFlight`,与事件流无关)。§10 上一轮已如实登记它「不该为它写测试 —— 那等于锁死
|
|
865
|
+
死代码」,本轮直接把它**删掉**:淘汰这件事唯一的真实机制就是 `#evictTerminal`(按 LRU 丢终态流、
|
|
866
|
+
有订阅者的不丢)。原地留一段注释说明「它曾经在、为什么删、真要再加回来该怎么设计」。
|
|
867
|
+
⚠️ 这一条**不是**「补文档」:删掉公开方法会让它的名字从 API 面消失,对使用者是**无感**的
|
|
868
|
+
(它从未从 `src/index.ts` 导出),但少一个「代码里有、文档里承诺、实际永不发生」的错位。
|
|
869
|
+
- **OTLP 导出不再每次 flush 白算分位**(S4):`metrics-otlp.ts` 原来每次 flush 调一次
|
|
870
|
+
`state.snapshot()`,却只从结果里读 `runs` / `failed` / `costUsd` **三个标量** —— 而这三个
|
|
871
|
+
**本来就是 `MetricsState` 的公开字段**;它对 capabilities / models / runLabels 全是**直接遍历公开 map**
|
|
872
|
+
(`grep latencyP50\|latencyP95 src/integrations/metrics-otlp.ts` → 空),一个分位字段都不读。
|
|
873
|
+
而 `snapshot()` 会为**每个**组合各算 p50 + p95,`percentile()` 的实现是 `[...ring].sort()`。
|
|
874
|
+
实测(`npm run bench:otlp`,满 1024 窗口 / 30 个组合):单个 p50+p95 **233.8 µs**、
|
|
875
|
+
`snapshot()` 整体 **5929.6 µs**、而 `buildOtlpPayload()` 只要 **41.6 µs** ⇒ 白算 **99.3%**,
|
|
876
|
+
且**每个 flush 都付一次**(拉取式 Prometheus 侧无此问题:那里确实要分位)。
|
|
877
|
+
修法**不是**报告建议的「让 `snapshot()` 的分位可选 / 另给一个不算分位的视图」—— 那是给一个
|
|
878
|
+
本不该存在的调用加开关。OTLP 只需要三个公开字段,**删掉这次调用**即可,依赖面同时变诚实:
|
|
879
|
+
它不再依赖 `MetricsSnapshot`(Prometheus 侧渲染的产物)这个形状。**无行为变更**(payload 逐字段不变,
|
|
880
|
+
1322 条框架用例全绿)。配**行为级**守卫 `tests/architecture/otlp-no-snapshot.test.ts`:
|
|
881
|
+
把 `state.snapshot` 换成会抛的桩再跑导出路径(⚠️ 不用源码扫描 —— 被守的源码注释里**本来就写着
|
|
882
|
+
`state.snapshot()`**,靠遮蔽器区分注释与代码等于把守卫的成败押在遮蔽器上);含阳性对照
|
|
883
|
+
「这枚雷真的会炸」防真空 + 「改字段 payload 跟着变」防读副本。变异两条具名复红:
|
|
884
|
+
① 把 `snapshot()` 加回去;② 三个标量改读错来源。
|
|
885
|
+
新增 `npm run bench:otlp`(`scripts/bench-otlp-snapshot.ts`,与 `bench:trace` 同档、不进 CI)。
|
|
886
|
+
- **engine 层的四处「如实性」收口**(外部深评 E4 / E5 / E6 / E7):
|
|
887
|
+
- **E4 · 请求失败出口不再硬写 `eventsDelivered: false`**:那条出口在 `loop.ts` 的 catch 里,
|
|
888
|
+
而 `eventsDelivered` 原先只挂在**内层** `LoopContext` 上 —— 外层够不到,于是只能硬写。
|
|
889
|
+
现在它挪到**跨段共享**的 `progress` 对象上(`turn.ts:233` 的注释本来就写着「抛错时调用方仍读得到」),
|
|
890
|
+
由 catch 按事实传入。**零行为变更**(`pendingEvents` 在终态分支本来就无条件清),
|
|
891
|
+
但**对外字段不再说谎**:它随 `rec.result` 落库、在导出面上,报错方向会让宿主以为该重投。
|
|
892
|
+
配回归用例(事件已注入 → 续跑段请求失败 ⇒ `eventsDelivered === true`):**变异撤回传参即红**。
|
|
893
|
+
- **E5 · `stop reason` 的成败分类改成穷尽表**:`isSuccessStopReason` 的
|
|
894
|
+
`reason === 'end_turn' || …` 换成 `Record<AgentStopReason, boolean>` 表 ——
|
|
895
|
+
「往联合里加一个成员而没在表里表态」**在类型上写不出来**(实测 `TS2741: Property 'brand_new_reason'
|
|
896
|
+
is missing`),三处消费者(run 状态 / trace 状态 / 子 agent 交回判定)因此不可能各说各话。
|
|
897
|
+
⚠️ 比原判要求的 `never` 断言更强:**编译期**就拦,且新增成员时逼你补一行语义。
|
|
898
|
+
配 `tests/types/stop-reason.types.ts`(类型级):`@ts-expect-error` 是**正控** ——
|
|
899
|
+
谁把表放宽成 `Partial<Record<…>>` / 加 `?? false` 兜底,那条指令就变成未使用 ⇒ 类型检查红。
|
|
900
|
+
- **E6 · `budgetTokens` 的口径文案改准**:它**只估 `messages`** —— system prompt 与 tools schema
|
|
901
|
+
不在这个数里(`policy.ts` 只把 `messages` 喂给 `countTokens`),所以它是「对话历史多大」的尺子、
|
|
902
|
+
不是「这次请求多大」的尺子(真实 input tokens 恒 ≥ 它)。`types.ts` 与该行 `usage-guide` 同改。
|
|
903
|
+
- **E7 · `setAttribute` 的闸口径改准(只改口径,代码未动)**:报告写「无闸」,逐行复核后更准的说法是
|
|
904
|
+
「**不过 `maxEvents` 那道数量闸**、值**不截断**」;有界的部分写清楚(正文受 `maxEventChars` 截断、
|
|
905
|
+
条数受调用点上限约束)。⚠️ 并写明**为什么不顺手加截断**:属性是交付 trace 与增量流**共用的同一份载荷**,
|
|
906
|
+
而 `core/trace.ts` 那条折叠契约(按 `seq` 折回**逐字等于** `snapshot()`)的两端就是它们 ——
|
|
907
|
+
只截一侧就当场毁约(`tests/engine/trace-events.test.ts` 钉着它)。要收就收 run 入口那几条的入参大小。
|
|
908
|
+
⚠️ **本批顺带订正上一批(#177 / ⑲)的两处计数错**:⑲ 写「P2 表 26 条:11 已落地 / 14 未做」是
|
|
909
|
+
**我的算术错**(逐行数出来是 8 / 17+1),且 ⑲ 的索引表**漏了整行 `K6`**(三态 `system` 的差异)。
|
|
910
|
+
索引表已补齐并逐行改成可数的状态,详见 `spec.md` §10 ⑳ 的订正段。**本批之后的 P2 账:
|
|
911
|
+
26 行 = 12 已落地 / 13 未做 / 1 有意为之。**
|
|
912
|
+
- **runtime / container / 文档三处「静默给错东西」收口**(外部深评 K3 / K4 / K6,⛔ **行为变更**):
|
|
913
|
+
- **K3 · `Run.finish()` 补守卫**:`start()` / `suspend()` 都有守卫,`finish()` 没有 ⇒
|
|
914
|
+
对一条**挂起中**的 run 调它会**静默翻成终态**(挂起带着 `suspendedMessages` 等审批/唤醒,
|
|
915
|
+
翻掉之后宿主的恢复路径再也接不上,零信号)。`Run` 是公开导出 ⇒ 这是对外承诺。
|
|
916
|
+
顺带把 `cancelled` 的**可达性**写进类型注:进程内 `Run` 到不了它(取消表现为
|
|
917
|
+
`stopReason: 'aborted'` ⇒ 记 `failed`),它由宿主(`TaskRecord.status`)落 ——
|
|
918
|
+
同一份联合类型两个消费者、可达集不同,是有意的,但此前没写在类型上。
|
|
919
|
+
- **K4 · async 工厂不再被静默当成值**:容器**同步**解析,`useFactory` 写成 `async` ⇒
|
|
920
|
+
返回的 Promise 被**原样缓存成「值」**,下游注入到 Promise 本身(首次属性访问全 `undefined`、
|
|
921
|
+
零报错、`tsc` 看不出)。现在在 `cache.set` **之前**检测 thenable 并抛 `TypeError`,
|
|
922
|
+
文案给出两条出路(装配前 await 好再用 `useValue`;或包一层 `useValue: { promise }`)。
|
|
923
|
+
检测器**就地写**(`container` 是纯叶子,`layering.test.ts` 里 `container: []`)、
|
|
924
|
+
**覆盖三个分支**(只查工厂等于把同类错留在另两处)。逃逸口配了测试。
|
|
925
|
+
- **K6 · 三态 `system` 的差异进 `usage-guide`**(纯文档):`string` / `SystemPrompt` 两形态
|
|
926
|
+
会自动追加 `REPORT_HINT`,**函数形态不追加**(有意 —— 返回值由使用者全权决定),
|
|
927
|
+
但使用者此前读不到这个差异。
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
- **停机窗口里的派发收成唯一入口**(P1-1 / P2-1):所有「先落库再派发」的路径(`submit` /
|
|
931
|
+
`approve` / `signalTask` / 到期唤醒 / **审批超时兜底** / **`resumePending` 的认领**)形状相同,
|
|
932
|
+
而停机闸原先散在各支里 ⇒ 「覆盖几条路径」成了**靠人记**的清单:上一批修了三支,第四支
|
|
933
|
+
(审批超时兜底,由 `GET /tasks/:id` 的惰性闸驱动,而「停机中照常可轮询」是宿主的明确承诺)
|
|
934
|
+
与第五支(认领循环)漏了 —— 两处都在 `drain()` 返回 `true`(「排空干净」是 SIGTERM 决策依据)
|
|
935
|
+
之后**又起了新 run**。现在闸只在 `#dispatch()` 里判一次;配**源码级穷尽守卫**
|
|
936
|
+
(`this.#execute(` 与 `this.#executeInner(` 各只许出现在自己的那一个家里)⇒ 新增恢复路径而
|
|
937
|
+
绕过派发口时构建红,不靠记性。实证:`tests/transport/drain-race.test.ts`(五条路径各一条
|
|
938
|
+
用例 + 阳性对照)。
|
|
939
|
+
- **`approve` 只许批本任务待决的 id**(P1-2):原先 `decisions` 里多出来的键照样写进
|
|
940
|
+
`rec.approvals`(随每次 `save` 全文重写、并随任务**永久保留** —— 终态也不清),一次调用就能把
|
|
941
|
+
记录从 246 字节撑到 419 KB(实证),反复调用可无限叠加。现在多出任何一个 id ⇒
|
|
942
|
+
**400 + 整批拒 + 记录一字不动**(与 `parseApproveBody`「全有或全无」、`parseEventBody`
|
|
943
|
+
「多一个字段即拒」同一纪律)。**不加常量上限**:单次 approve 能写的键被钉在当前
|
|
944
|
+
`pendingApprovals` 里,跨轮累计也只随真实挂起轮次增长 —— 单次输入无法放大体积。
|
|
945
|
+
- 缓冲满的 409 文案按挂起原因分两种说法(`approval`:唯一触发源是 approve、不在投递方手里;
|
|
946
|
+
`timer`:到点自己醒来,approve 对它不适用)。
|
|
947
|
+
- **trace 截断在两条缝上都看得见**(P3-1):`traceLimits.maxEvents` 超限后,run 根上会留**两笔**
|
|
948
|
+
`trace.truncated` —— **起点标记** `{ limit }`(第一笔丢弃那一刻,标「从这里起有缺」)与
|
|
949
|
+
**收尾摘要** `{ droppedEvents, limit }`(交付时的最终计数)。两笔**同时**进交付的 trace 与
|
|
950
|
+
`onTraceEvent` / `subscribe` 增量流 ⇒ 「按 `seq` 折回**逐字等于** `snapshot()`」那条折叠契约
|
|
951
|
+
在截断下**无例外地**成立(此前它在设了上限且真截断时是假的:摘要从不出现在增量流里,
|
|
952
|
+
只是默认不设上限所以没人碰到)。两笔簿记**不占** `maxEvents` 配额(截断时事件数上限是
|
|
953
|
+
`maxEvents + 2`)。⚠️ 起点标记会进交付的 trace —— 有意的:让制品自己标出「从这里断的」。
|
|
954
|
+
- **`task.event` 留痕的 `delivered` 改名 `injected`**(P3-2,**可观测面变更**):口径修正为
|
|
955
|
+
「注入进本段消息流」—— 原名超前于事实(该段若在首个模型请求前中止/失败,事件不进
|
|
956
|
+
持久化历史,但 trace 上已写着 delivered)。消费这条 trace 事件的下游请同步改字段名。
|
|
957
|
+
|
|
958
|
+
### 修复 · PR #164 复核收口(2026-09-28 ⑨)
|
|
959
|
+
|
|
960
|
+
- **停机窗口的拒绝不再静默**:`#dispatch()` 在停机中拒掉一条派发时,会打**一条**
|
|
961
|
+
`[agentia] 停机中:…` 的 `console.warn`(说清「谁、什么状态、谁来认领、宿主该做什么」;
|
|
962
|
+
只报一条 —— 窗口里所有记录的原因完全相同)。此前是一条裸 `return`,运维只看到一个停在
|
|
963
|
+
`running` 的任务,无从解释。
|
|
964
|
+
- **`drain()` 的契约写全了**:它是**单向闩**(置位后没有复位路径)⇒ 返回 `false`(排空超时)
|
|
965
|
+
同样意味着**宿主必须退出**。否则停机窗口里已落库的记录(`running`/`queued` + 本进程 ownerId)
|
|
966
|
+
在本进程内**没有自愈路径**:`resumePending` 按 `own-process` 跳过、`submit` 已关闭 ⇒
|
|
967
|
+
只能等下一次启动认领(探针实证:认领数 0、`awaitTask` 不返回)。订正 `drain-gate.ts` 里
|
|
968
|
+
「false 不改任何共享状态」那句(`draining` 恰恰是保持置位的),并给 usage-guide §7 加一行
|
|
969
|
+
「`drain()` 之后有些任务停在 `running` 不动」。
|
|
970
|
+
- **守卫补上 `#executeInner` 这个旁路**:只钉 `this.#execute(` 拦不住它 —— 绕过它会**同时**
|
|
971
|
+
绕过停机闸与 `active++` / `#streams.open`(`drain()` 会在一条 run 真在跑时返回 `true`)。
|
|
972
|
+
- **文案订正**:`submit` 的「进闸与派发之间刚开始停机的窄窗口」**不存在**(`submit` 全同步,
|
|
973
|
+
没有插入点 —— 走 `#dispatch` 是结构一致性,不是行为依赖);`sse-frames.test.ts` 把
|
|
974
|
+
`trace.truncated` 说成「run 根的 attribute」(它是**事件**);`api.html` 的
|
|
975
|
+
`createHttpHandler` 行补上 ⑦ 新增的语义级 400。
|
|
976
|
+
|
|
977
|
+
### 新增
|
|
978
|
+
|
|
979
|
+
- **run 事件投入口(spec §10 2026-09-28 ⑥,定案 A3+B1)**:外部系统可以把一条**事件**
|
|
980
|
+
投给一条**挂起**的 run —— `AsyncRunner.signalTask(taskId, event)` 与
|
|
981
|
+
`POST /tasks/:id/events`(与 approve/cancel 对称),醒来时事件进消息历史。
|
|
982
|
+
- 🔒 **投毒面焊成窄的**:事件体是白名单 `{ eventId?, type, payload }`(全是字符串,
|
|
983
|
+
多一个字段 → 400;`payload` 上限与 `maxBodyBytes` 同口径 → 413)—— 外部永远不能
|
|
984
|
+
构造消息块,引擎把它渲染成**一条 user 文本消息**,在未决 tool_use 解决之后注入
|
|
985
|
+
(直接追加到历史末尾会破坏续跑判定:`tailToolUses` 只认末尾一条)。
|
|
986
|
+
- **只对挂起生效**:不存在 → 404;非 `suspended`(含已终态)→ 409;先落库再派发
|
|
987
|
+
(崩在窗口里不丢事件);timer 挂起收到事件**提前醒**且不沿旧 `wakeAt`。
|
|
988
|
+
- **幂等**:给 `eventId` 就按它去重(`TaskRecord.deliveredEventIds`,随记录落库、
|
|
989
|
+
有界 FIFO)—— 重复投递 → 409;不给则重复投递 = 重复进历史(如实,不假装恰好一次)。
|
|
990
|
+
- **待注入缓冲也有上限(64 条)**:满了 → 409,且**不收下**(文案说清「本次事件没有被
|
|
991
|
+
记录」)—— 定案 B「说出来,不静默」。**不选「丢最旧」**:丢缓冲若不同时摘
|
|
992
|
+
`deliveredEventIds`,发件方重投会拿到「已投递」的 409 而事件其实已经没了(静默丢事件)。
|
|
993
|
+
- **留痕**:续跑段 run 根记 `task.event { injected, event_type, event_id? }`(口径是
|
|
994
|
+
「注入进本段消息流」—— 本轮改名,见上方 [Unreleased] 的 P3-2 条目);
|
|
995
|
+
离开挂起态时挂起读数照常除名。
|
|
996
|
+
- 新增导出:`TaskEvent`(类型);`RunInvocationOptions.events` / `TaskRecord.pendingEvents`
|
|
997
|
+
/ `TaskRecord.deliveredEventIds` / `AgentRunResult.eventsDelivered`(全部加法,无破坏性)。
|
|
998
|
+
`eventsDelivered` 是引擎对「本段把事件真注入了没有」的如实报告 —— 宿主据此清簿记,
|
|
999
|
+
保证注入后再次挂起也不会在下次续跑**重复注入**(复审探针抓出的缝,已闭合)。
|
|
1000
|
+
- **sqlite 到期索引(spec §10 2026-09-28 ⑤ 落地)**:`TaskStore` 新增**可选**方法
|
|
1001
|
+
`listDue?(before)`(接口加法,不破自定义 store),`SqliteTaskStore` 实现它
|
|
1002
|
+
(派生列 `wake_at` + `(status, wake_at)` 索引;存量库构造期就地迁移并回填,
|
|
1003
|
+
列漂移读时自愈)。`AsyncRunner` 的「到期唤醒」那一半扫描有索引走索引
|
|
1004
|
+
(10k 记录:全表 list ~38ms → listDue ~0.1ms),没有回退全表、语义不变。
|
|
1005
|
+
InMemory / File store **不实现**(它们的 list 本来就在内存里)。
|
|
1006
|
+
⚠️ `resumePending` 的其余三条职责(挂起读数重建 / 审批超时 / 孤儿认领)仍以全表
|
|
1007
|
+
`list()` 为输入 —— listDue 只替代到期唤醒那一半。
|
|
1008
|
+
|
|
1009
|
+
- **时间挂起(durable timer,spec §10 2026-09-28 ②)**:工具可以在**执行期**调
|
|
1010
|
+
`ctx.deferUntil(at)` 说「现在还不是时候,T 之后再问我」—— 引擎把该回合收尾成挂起
|
|
1011
|
+
(`stopReason: 'suspended'` + `suspendedReason: 'timer'` + 目标时刻 `wakeAt`),
|
|
1012
|
+
到点由宿主续跑并**重跑这一批**工具。用途:等批处理作业、等限流窗口、等外部系统回填。
|
|
1013
|
+
- ⏱️ **时刻必须是将来**:非有限数或 `at <= now` 当场抛 `TypeError`(该条 `tool_result`
|
|
1014
|
+
记 is_error、run 照常往前走,**不**挂起)—— 允许过去时刻会让「醒来 → 再请求同一个过去
|
|
1015
|
+
时刻」自己打转。同回合多条请求取**最早**的那个。
|
|
1016
|
+
- ⚠️ **整批语义**:挂起是回合级的(协议要求每个 `tool_use` 都有配对 `tool_result`),
|
|
1017
|
+
所以同回合**已经执行完**的其他工具会在醒来后**重跑**(副作用重复)。框架把它变成看得见的:
|
|
1018
|
+
trace 记 `defer.requested { wake_at, tool_use_ids, discarded }`,真有兄弟工具被作废时
|
|
1019
|
+
落一条 `console.warn`。要精确控制就让模型单独调它,或把它做成幂等读。
|
|
1020
|
+
- 🩺 **可见性**:`GET /healthz` 新增 `suspended: { approval, timer, nextWakeAt }`
|
|
1021
|
+
(本进程口径,与 `inFlight` 同一张表;无时间挂起时 `nextWakeAt` 是 `null` 不是 `0`),
|
|
1022
|
+
另有 `AsyncRunner.suspendedSummary`。
|
|
1023
|
+
- 🛑 **停机**:`drain()` 之后**不再唤醒**睡着的 run(停机 = 不再往前推);重启后由新进程的
|
|
1024
|
+
首次 `resumePending` 唤醒。取消用 `AsyncRunner.cancel` / `POST /tasks/<id>/cancel`
|
|
1025
|
+
(见下「取消 API」)—— 对睡着的 run 它就是「不再醒」的合法出路。
|
|
1026
|
+
⚠️ 这道闸(2026-09-28 复审第二轮补齐)覆盖**三条**把挂起任务推进起来的路径:
|
|
1027
|
+
到期唤醒 / `approve` / `signalTask` —— 三者形状相同(先落库成 `running`、再派发),
|
|
1028
|
+
落库与派发之间隔着 store 往返的窗口,`drain()` 若在窗口里完成,那条任务会在
|
|
1029
|
+
**停机完成之后**才开跑。窗口过后若已在停机:不派发,记录留给下次启动的
|
|
1030
|
+
`resumePending` 认领(决定 / 事件都在记录里,不丢)。此前只有到期唤醒那一支有闸。
|
|
1031
|
+
- ⛔ **abort 优先于延后**:同回合里工具请求了延后、而 run 又被中止(`runTimeoutMs` 到点 /
|
|
1032
|
+
`cancel`),run 以 `aborted` 收尾、**不**挂成 timer —— 否则会到点自己醒来接着跑,
|
|
1033
|
+
等于把取消/超时吃掉。
|
|
1034
|
+
- 新增字段(**全部加法,无破坏性**):`ToolRunContext.deferUntil`、`AgentRunResult.wakeAt`、
|
|
1035
|
+
`RunMeta.wakeAt`、`TaskRecord.wakeAt`、`HealthResponse.suspended`。另顺手修两处:
|
|
1036
|
+
`resumePending` 跳过挂起记录时的原因不再是 `'terminal'` 而是 `'suspended'`(诊断不说错话);
|
|
1037
|
+
时间挂起醒来时的续跑段**不再**重复注入会话历史、且 trace 会 link 上一段 run。
|
|
1038
|
+
- **旧任务记录的读时归一(迁移垫片,spec §10 2026-09-28 ③)**:store 读回记录时会把 0.9.5 之前
|
|
1039
|
+
落库的旧形状归一 —— 状态值 `awaiting_approval` → `suspended` 并补 `suspendedReason: 'approval'`,
|
|
1040
|
+
挂起时刻 `approvalPendingSince` → `suspendedSince`(旧键删掉)。**升级不再需要宿主手工改库**:
|
|
1041
|
+
此前那种记录读回来会被 `isTerminalTask` 判成终态、`resumePending` 按 `'terminal'` 跳过 ⇒
|
|
1042
|
+
一条在等审批的 run 成孤儿(既不续跑、也无法再被审批)。
|
|
1043
|
+
- 六个「bytes → 记录」点(`FileTaskStore` 全量扫 + 残行探测、`SqliteTaskStore` 的
|
|
1044
|
+
get / byIdempotency / list、`RedisTaskStore` 单键读)收成**唯一入口** `src/store/record.ts`;
|
|
1045
|
+
静态守卫:`src/store/*.ts` 里除它之外不得直接 `JSON.parse`。
|
|
1046
|
+
- `SqliteTaskStore` 额外把派生列 `status` 拉回与 json 一致 —— 否则外部/DBA 的
|
|
1047
|
+
`SELECT status, count(*) FROM tasks GROUP BY status` 会**继续**报旧值。
|
|
1048
|
+
- 归一失败/坏 JSON 的取舍与各 store 既有口径一致;本改动**无公共 API 变化**。
|
|
1049
|
+
- **取消 API(spec §10 2026-09-28 ④)**:`AsyncRunner.cancel(taskId)` 与 `POST /tasks/<id>/cancel`
|
|
1050
|
+
—— 在跑的**真中断**、在睡的**不再醒**、在排队的**绝不起跑**,落库 `status: 'cancelled'`
|
|
1051
|
+
(**新状态**,与 `failed` 分开:取消不是失败)。
|
|
1052
|
+
- 机制是同一条 abort signal,差别在**意图**:`runTimeoutMs` 超时仍然落 `failed`
|
|
1053
|
+
(`error.type === 'timeout'`);取消带 `error.type === 'aborted'`。
|
|
1054
|
+
- **不假装**:在跑的 run 不在本进程、或宿主不认 `signal`(2s 宽限内没收尾)⇒
|
|
1055
|
+
`TaskCancelError(409)`,记录一个字节不动。
|
|
1056
|
+
- 顺带补一个洞:排队期间被取消的任务原先**照样会跑**(认领处不重判状态)。
|
|
1057
|
+
- 复审再补两格:abort 后 **reject** 的宿主(包 fetch 类客户端的常见写法)也按意图落
|
|
1058
|
+
`cancelled`(不落 `failed`);同幂等键重提一条被取消的任务会**真跑**
|
|
1059
|
+
(去重不再把 `cancelled` 当「已有」)。
|
|
1060
|
+
- 另:`awaitTask` 的终态集合改成 `isTerminalTask`(原先手写两值 ⇒ 新终态被漏掉,
|
|
1061
|
+
症状是「取消后一直等到超时」)。
|
|
1062
|
+
- **菜单漂移不再静默(R8 候选 3,spec §10 2026-09-27 ⑧)**:挂起段之后**续跑**时,未决
|
|
1063
|
+
tool_use 引用的工具若已不在当前菜单(删了 / 改名了),框架把这件事记成**三处信号** ——
|
|
1064
|
+
`menu.drift` 事件(`{ missing, tool_use_ids, menu_size }`;时间线与
|
|
1065
|
+
`GET /tasks/:id/stream` 都看得到)、父 span 的 `menu.drift` attribute(如 `missing:danger`)、
|
|
1066
|
+
一条 `console.warn`。⚠️ **run 照常收尾**(不判失败:挂起是合法态、改代码是发布常态 ——
|
|
1067
|
+
理由与「严格模式为什么不给」见 spec §10 2026-09-27 ⑧)。另新增 run 根 attribute
|
|
1068
|
+
`tools.names` / `tools.menuHash`(装配后菜单的名字清单 + 名字与**输入 schema** 的摘要,
|
|
1069
|
+
与 `prompts.versions` 同动机:质量回归能定位到具体菜单版本)。口径:判据只看名字;签名与
|
|
1070
|
+
菜单顺序、schema 键序无关,`description` **不参与**。**公共 API 零变化**。
|
|
1071
|
+
- **MCP 反向桥(`createMcpServer`,R8-P5)**:把 app 的能力菜单(装配后、过中间件的那份)
|
|
1072
|
+
暴露成 MCP server —— Claude Code / Cursor / 任何 MCP 宿主能直接调你的 `@Tool`。
|
|
1073
|
+
传输二选一(都只用标准库):`stdio`(换行分隔 JSON-RPC,日志只去 stderr)与
|
|
1074
|
+
StreamableHTTP(POST 收报文回 `application/json`;`initialize` 铸 `mcp-session-id`
|
|
1075
|
+
头但**不校验**(无状态 server,宽容是有意的);GET → 405、DELETE → 200;客户端断连
|
|
1076
|
+
中止该次调用的 signal;`auth` 钩子只给缝 —— 读 body 之前、抛错即 401,与
|
|
1077
|
+
`createHttpHandler` 同纪律)。协议范围只到 tools(initialize / tools/list / tools/call
|
|
1078
|
+
+ ping)。**trace 叙事不破**:每次 tools/call 造一棵 trace(run 根 `mcp.tools/call` +
|
|
1079
|
+
capability span + 与引擎同形状的 `tool.input`/`tool.output` 事件)投递 `opts.sinks`;
|
|
1080
|
+
结果映射与正向桥方向对称(抛错 → 协议层成功 + `isError: true`)。公共面新增
|
|
1081
|
+
`createMcpServer` 与 `McpServerApp` / `McpServerOptions` / `McpServer` 类型。
|
|
1082
|
+
决策见 spec §10 2026-09-27 ⑦。
|
|
1083
|
+
- **租户归因 labels(`labels: Record<string, string>`,R8-P4)**:`createApp` 缺省 +
|
|
1084
|
+
`app.run` 单次覆盖(**整体替换**不合并)+ `runAgent` 直连三层同语义。落 run 根的
|
|
1085
|
+
`labels.<key>` 属性(trace 侧无基数问题;与框架自写的 `source` 触发来源审计正交);
|
|
1086
|
+
可序列化,异步任务随 TaskRecord 落库、续跑不丢;键空 / 值非字符串在 run 入口抛
|
|
1087
|
+
TypeError。进 metrics 是**另一个开关**:`metricsSink({ labelKeys, maxLabelValues? })`
|
|
1088
|
+
显式点名哪些键上指标标签(缺省一个都不上),每键相异值数封顶(缺省 100、
|
|
1089
|
+
必须为正数),超出折叠进 `__other__`(只丢粒度不丢量),被折叠数见
|
|
1090
|
+
`snapshot().droppedLabelValues` 与 `dropped_keys{kind="label:<key>"}`;四个 run 级
|
|
1091
|
+
家族(`runs_total` / `runs_failed_total` / `tokens_total` / `cost_usd_total`)在全局
|
|
1092
|
+
样本外追加带标签样本(⚠️ 开了以后 `sum(agentia_runs_total)` 会重复计数,总量用
|
|
1093
|
+
不带标签的序列)。公共面新增 `RunLabelMetrics` 类型。决策见 spec §10 2026-09-27 ⑥。
|
|
1094
|
+
- **CLI `agentia export`:trace 落盘文件 → 训练数据集**(JSONL,一行一份
|
|
1095
|
+
`{ messages, meta }`;R8-P3b)—— harvest(产回归用例)的孪生。开了
|
|
1096
|
+
`traceContent: 'full'` 的 run 导出带真 assistant 文本的完整对话;没开的导出工具
|
|
1097
|
+
轨迹,缺文本**不造占位**(占位文本进训练数据是投毒),缺口进 `meta.incomplete`。
|
|
1098
|
+
过滤:`--ok-only` / `--min-score n`(没带分数的 run 被排除:没判过 ≠ 及格);
|
|
1099
|
+
`--out` 落盘(stdout 是产物的纪律同 harvest)。框架侧 `src/eval/export.ts` 的
|
|
1100
|
+
`exportRun` 是 module 级(不进公共面,同 harvestEvalCase 纪律),CLI 侧为去类型
|
|
1101
|
+
移植副本 + 逐字对拍守护。决策见 spec §10 2026-09-27 ⑤。
|
|
1102
|
+
- **opt-in 记录 assistant 文本进 trace(`traceContent: 'full'`)**:每回合的模型文本落
|
|
1103
|
+
该 llm.turn span 的 `output.text` 属性(多块 `\n` 连接;过 `maxEventChars` 同一道
|
|
1104
|
+
截断闸 —— 它管「多长」,`traceContent` 管「记不记」)。缺省不记,现状逐字不变;
|
|
1105
|
+
纯 tool_use 回合不记。透传子 agent / skill 子循环(`forwarded.ts` 同树同口径)。
|
|
1106
|
+
run 根快照记 `config.traceContent`。⚠️ 实测代价:3 回合、每回合约 1600 字符输出的
|
|
1107
|
+
run,trace 体积 5 564 → 11 010 字节(约 2×);且模型输出从此进入要脱敏的面 ——
|
|
1108
|
+
出库前走 `docs/observability.md` 配方 2.4。这是「trace → 训练数据导出」(R8-P3b)
|
|
1109
|
+
的引擎侧前提。决策见 spec §10 2026-09-27 ④。
|
|
1110
|
+
- **模型 fallback 链(引擎级,`fallbacks: [{ model, client? }]`)**:`createApp` 缺省 +
|
|
1111
|
+
`app.run` 单次覆盖 + `runAgent` 直连三层同语义。主模型本回合最终失败(含其
|
|
1112
|
+
`maxRetries` 用尽)且错误可换(`classifyError` 的 retryable 类:rate_limit / server /
|
|
1113
|
+
timeout / connection)时按序换环重试本回合 —— **每一环开自己的 llm.turn span**
|
|
1114
|
+
(model 名正确 ⇒ 成本归因与 `usage.unpriced` 探测天然对),切换在新 span 记
|
|
1115
|
+
`llm.fallback { from, to, errorType }` 事件,run 根快照记 `config.fallbacks`。
|
|
1116
|
+
护栏:`aborted` 永不换(用户取消不是故障)、本回合吐过字不换(与 retry 的
|
|
1117
|
+
`!emitted` 同一护栏)、每回合从主环重新起;子 agent / skill 子循环不继承。
|
|
1118
|
+
链环 `client` 缺省复用本次 run 的 client(同端点换模型是主用例);run 入口校验
|
|
1119
|
+
坏环/死 client(持久化反序列化空壳)响亮抛 TypeError。决策见 spec §10 2026-09-27 ③。
|
|
1120
|
+
- **`jsonlTraceSink({ path })`:JSONL 文件 sink 进框架** —— CLI 三件套(`agentia report` /
|
|
1121
|
+
`diff` / `harvest`)消费 `trace.jsonl`,而产出侧此前要用户手写 `appendFileSync`(usage-guide
|
|
1122
|
+
曾这么教)。现在一行接入:`createApp({ sinks: [jsonlTraceSink({ path: 'trace.jsonl' })] })`。
|
|
1123
|
+
- **metrics exemplars(指标 ↔ trace 互跳)**:`metricsSink` 记账时跟踪两个代表性现场 ——
|
|
1124
|
+
`runs_failed_total` 挂**最近一次失败** run、`run_duration_ms` histogram 挂**迄今最慢** run
|
|
1125
|
+
的 traceId/spanId(`snapshot().exemplars` 可见;`export: 'openmetrics'` 与 `'otlp'`
|
|
1126
|
+
两个出口会挂上,缺省 `prometheus` 输出逐字节不变)。Grafana 配好 exemplar 跳转后,
|
|
1127
|
+
指标尖峰可以一键跳到那条 trace(配置见 `docs/observability.md` §4)。
|
|
1128
|
+
内置 `GET /metrics` 路由的 Content-Type 跟 sink 的 `contentType` 走 —— openmetrics 模式
|
|
1129
|
+
直接喂给 `createHttpHandler({ metrics })` 即可,不用自己挂路由。
|
|
1130
|
+
- **摘要渲染折叠纯载荷长串**(#147):长上下文摘要器此前有三条封顶(图片占位 / 未知块 /
|
|
1131
|
+
参数截断),但工具把 base64 这类载荷当**字符串**返回时整段进摘要器。现在连续 ≥4000
|
|
1132
|
+
字符的载荷串折成 `⟨载荷 N 字符已折叠⟩` —— 只影响摘要渲染,trace 本体与 token 估算不变。
|
|
1133
|
+
(PEM 折行载荷够不着下限,已知边界已在文档声明。)
|
|
1134
|
+
|
|
1135
|
+
### 变更
|
|
1136
|
+
|
|
1137
|
+
- **挂起改成「一个状态 + 一个原因」:`awaiting_approval` → `suspended` + `suspendedReason`**
|
|
1138
|
+
(spec §10 2026-09-28 ①;为 durable timer 铺路):`RunStatus` 与 `AgentStopReason` 的成员名
|
|
1139
|
+
都改成 `'suspended'`,「为什么挂起」由新字段承载 —— `'approval'`(等人工决定)/
|
|
1140
|
+
`'timer'`(等一个时刻)。两条判据因此落在**原因**上:`approvalTimeoutMs` 只对 `approval`
|
|
1141
|
+
成立、`approve` 对 `timer` 挂起一律 409 —— 否则一条等时刻的 run 会被「审批超时」提前叫醒
|
|
1142
|
+
并重派。`TaskRecord.approvalPendingSince` 改名 `suspendedSince`(它本来就是挂起时刻)。
|
|
1143
|
+
新增导出 `SuspendedReason`(`api.html` 计数 224 → 225)。**破坏性** —— 迁移见下。
|
|
1144
|
+
- **脱敏配方 2.4 升级**(`examples/observability` 的 `redactSink`,框架 `src/` 零改动):
|
|
1145
|
+
新增内置正则预设(Bearer / JWT / AWS access key / LLM `sk-` key / 邮箱 / 手机号),
|
|
1146
|
+
**缺省全开**(拷走即用),`presets` 可开子集或 `false` 全关;预设命中的替换文案带类别
|
|
1147
|
+
标签(`[REDACTED:email]`),自定义 `patterns` 与 `keys` 命中的仍是裸 `[REDACTED]`。
|
|
1148
|
+
脱敏不内建进框架是已锁定决策(spec §9.3 / §10 2026-09-14 ⑥),这是配方层的升级。
|
|
1149
|
+
- **sink 投递失败不再完全静默**:`flushSinks` 吞掉 sink 异常的纪律不变(观测不击穿业务),
|
|
1150
|
+
但吞之前现在会落一条 `console.warn`(文案含「trace sink」,可 grep)——「观测的观测」
|
|
1151
|
+
此前是零信号:sink 天天挂、面板一切如常。决策见 `docs/spec.md` §10 2026-09-27 ②。
|
|
1152
|
+
|
|
1153
|
+
### 重构(纯结构,零行为变化)· 结构收口(2026-09-28,底盘见 `docs/reviews/2026-09-28/SRC-STRUCTURE-2026-09-28.md` 的落地顺序 1–4)
|
|
1154
|
+
|
|
1155
|
+
- **HTTP 宿主拆成三件 —— `http.ts` 拆分第三步**:`src/transport/http.ts` **840 → 306 行**,
|
|
1156
|
+
最大单函数 `createHttpHandler` **489 → 123 行**(拆分前这一个函数占全文 58%)。
|
|
1157
|
+
同系列前两步(#99 外移形状口径 → `http-shapes.ts`、#100 外移路由判定 → `http-route.ts`)
|
|
1158
|
+
移走的都是**纯件**,占大头的那一半(九条路由的**体**)原地不动 —— 于是文件从 533 行又长回
|
|
1159
|
+
840。这一步把它搬走:
|
|
1160
|
+
- `http-endpoints.ts`(新)= **端点体**:派发表 `handleRoute` + 九条 `handleXxx`
|
|
1161
|
+
(读 body → 调 runner → 写响应 → 选状态码)。免鉴权组(`/healthz` / `/metrics`)连同
|
|
1162
|
+
它们自己的 405 也在这张表里 —— 表**不认识鉴权**,闸在宿主侧(`handler` 里那两行否定
|
|
1163
|
+
条件:「不是免鉴权组才过闸」)。
|
|
1164
|
+
- `http-io.ts`(新)= **收发原语**:`sendJson` / `readBody` / `parseJsonBody` / 405 / 413 /
|
|
1165
|
+
503 / 500 的机械动作。**零内部依赖**(只 import `node:http` 的类型)⇒ 宿主与端点都引它
|
|
1166
|
+
而**不可能**成环 —— 无环是构造性的,不是碰巧。
|
|
1167
|
+
- `http.ts` = 宿主契约 + 宿主状态 + 准入/停机。原先散在闭包里的三个 `let`(在飞 run 计数 /
|
|
1168
|
+
停机态 / SSE 收口表)收成一个**有名字的对象** `HttpState`,按引用交给每个请求 ——
|
|
1169
|
+
「健康检查与停机判断看的是同一个数」这条承诺从此有个明主。
|
|
1170
|
+
- **行为零变化**:既有 HTTP 套件 146/146 未改一字全过;公开面(`src/index.ts`)一行未改
|
|
1171
|
+
(`HealthResponse` 随实现搬走并在 `http.ts` 转出,与 #99 转出两个形状类型同款)。
|
|
1172
|
+
- **配源码级守卫** `tests/transport/http-boundary-guard.test.ts`:端点体绕不开的五个记号
|
|
1173
|
+
(`route.kind` / `sse.event(` / 三个形状解析器)必须在端点文件、必须不在宿主文件;准入
|
|
1174
|
+
判定只许留在宿主;原语层不许长内部依赖;派发表的穷尽断言必须在场。反向验证过三条
|
|
1175
|
+
(放回拆分前的文件 / 删掉穷尽断言 / 给原语层加内部依赖 —— 各恰好一条红)。
|
|
1176
|
+
- 顺带修好一条**盯着实现排布而非契约**的守卫:`tests/docs/sse-frames.test.ts` 原先写死扫
|
|
1177
|
+
`src/transport/http.ts` 的 `sse.event('…')`,帧一搬家就误红(帧一条没少)—— 改成扫整个
|
|
1178
|
+
`src/transport/` 目录。断言方向是「文档提到的 ⊆ 实现 emit 的」,所以扫宽不会假绿。
|
|
1179
|
+
|
|
1180
|
+
- **MCP 反向桥拆成三件 —— `createMcpServer` 441 → 47 行**:`src/engine/mcp-server.ts`
|
|
1181
|
+
**564 → 409 行**,最大单函数 `createMcpServer` **441 → 47 行**(拆分前占全文 78%)。
|
|
1182
|
+
先例是正向桥按传输分文件,于是:
|
|
1183
|
+
- `mcp-server.ts` = **协议 + 执行 + 装配**:顶层的 `rpcError` / `rpcResult` / `dispatch`
|
|
1184
|
+
(报文分派,两传输共用)/ `callTool`(执行一次 `tools/call`、造一棵 trace)—— 拆前它们是
|
|
1185
|
+
441 行闭包里的无名块。`createMcpServer` 只余「读选项 → 造 core → 按 `opts.transport` 挑传输」。
|
|
1186
|
+
- `mcp-server-stdio.ts`(新,72 行)= stdio 传输:stdin/stdout 换行分隔 JSON-RPC,
|
|
1187
|
+
`process.stdout` 的 EPIPE 必须吞(对端走了的次生现象)。
|
|
1188
|
+
- `mcp-server-http.ts`(新,199 行)= StreamableHTTP 传输:POST 收 JSON-RPC、GET→405、
|
|
1189
|
+
DELETE→200、`initialize` 发 `mcp-session-id`、客户端断连中止本次工具调用的 `signal`。
|
|
1190
|
+
- **无环是构造性的(不照抄正向桥)**:协议与执行经 `McpCore` **注入**给传输,传输只从
|
|
1191
|
+
`mcp-server.ts` 取**类型**。正向桥的 `mcp.ts ↔ mcp-stdio.ts` 是**真实的值环**(靠 ESM 函数
|
|
1192
|
+
提升侥幸无恙);反向桥刻意做成注入式单向。
|
|
1193
|
+
- **「为什么留在 engine」复核仍成立**:本文件需 engine 侧 4 个值 + integrations 侧 2 个值,
|
|
1194
|
+
而 integrations 只许依赖 core ⇒ engine 是唯一能同时够到两边的层(搬走会造出越权边与环)。
|
|
1195
|
+
- **不再往下拆传输**(反例):`startHttpTransport` 172 行 / `startStdioTransport` 57 行属
|
|
1196
|
+
「一个宿主的生命周期」,该是一个协作者,且比正向桥同位函数(265 / 235 行)更瘦。
|
|
1197
|
+
- **行为零变化**:MCP 反向桥单测 22/22 全过,`npm run e2e:mcp:server` 全绿。
|
|
1198
|
+
|
|
1199
|
+
- **`#executeInner`(247 行)的编排归属 = 定案「不拆」**(**零代码改动**,产出是一份有依据的裁定):
|
|
1200
|
+
`async.ts` 最大可调用体 247 ÷ 总行 1690 = **14.6%**、可调用体 **40** 个 ⇒ 按判据属「**大类**」病
|
|
1201
|
+
(抽协作者,**别切方法**),而纯协作者已抽过六轮(`slot-pool` / `approval-policy` / `drain-gate` /
|
|
1202
|
+
`resume-policy` / `task-waiters` / `task-events`)。它触及 **14 个 runner 内部成员**,外移编排
|
|
1203
|
+
等于重建 `AsyncRunner`;且调用结构**早已被** `tests/transport/dispatch-guard.test.ts` **钉死**
|
|
1204
|
+
(`#dispatch` = 派发口 / `#execute` = 计数与开流包裹层 / `#executeInner` = 状态机主体)。
|
|
1205
|
+
唯一成块的纯件 `callOpts` 装配(~42 行)因「单调用者 + 不产生可守卫边界」也判为不外移。
|
|
1206
|
+
|
|
1207
|
+
- **文件级无环守卫 + 断开 MCP 正向桥的**真实值环** —— 第 4 件**:
|
|
1208
|
+
- 新增 `tests/architecture/file-cycles.test.ts`:`layering.test.ts` 的 `layerOf()` 取路径第一段,
|
|
1209
|
+
**层内环是盲区**;本守卫建**文件级值边图**(Tarjan SCC)断言**运行期无环**。口径只算**值边** ——
|
|
1210
|
+
`import type` 运行期擦除、不构成环(`runtime/context ↔ run`、`engine/mcp-server ↔
|
|
1211
|
+
mcp-server-{stdio,http}` 两条 type-only 环**留着**)。两条变异反向验证过(注入值边 / 把真实
|
|
1212
|
+
`import type` 改成值导入 ⇒ 各恰好 1 条红);另加「注释里的导入字面量不算边」自证。
|
|
1213
|
+
- 断环:桥(`integrations/mcp.ts`)与两个连接器**共用**的协议面(7 helper + 4 结构类型)
|
|
1214
|
+
抽到新文件 `src/integrations/mcp-protocol.ts` —— 此前 `mcp.ts` re-export 连接器(值)+
|
|
1215
|
+
连接器反向取 helper(值)是一条**真实的值环**(靠 ESM 函数提升侥幸无恙)。抽后成单向 DAG,
|
|
1216
|
+
**公共面不变**(`src/index.ts` 未改;`mcp.ts` 继续转出这些符号)。
|
|
1217
|
+
- ⚠️ 盘点报告初稿写「两条环的回边都是 `import type`」**是错的** —— 实测 `mcp.ts:350/352` 是
|
|
1218
|
+
**re-export 值**。结论与修法见 `docs/spec.md` §10 2026-09-28 ⑩ 第 4 条。
|
|
1219
|
+
|
|
1220
|
+
### 迁移
|
|
1221
|
+
|
|
1222
|
+
- **升级前停在 `awaiting_approval` 的任务记录**(`FileTaskStore` / `SqliteTaskStore` /
|
|
1223
|
+
`RedisTaskStore` 里已写好的 JSON):**本版内置兼容读,宿主不需要做任何动作** —— store 读回记录
|
|
1224
|
+
时会把旧形状归一(`awaiting_approval` → `suspended` 并补 `suspendedReason: 'approval'`;
|
|
1225
|
+
`approvalPendingSince` → `suspendedSince`,旧键删掉),`SqliteTaskStore` 还会把派生列 `status`
|
|
1226
|
+
一并拉正。细节与门禁见 `docs/spec.md` §10 2026-09-28 ③。
|
|
1227
|
+
⚠️ 本小节此前写的是「框架**不**内置兼容读、请宿主在升级前手工改库」—— 垫片落地后那段要求
|
|
1228
|
+
**已作废**(手工处置仍可行,但不再是必须;在等审批的 run 不会再被判成终态)。
|
|
1229
|
+
`SuspendedReason` 仍然是**类型**而不是别名:不为旧值引入第二个运行时名字。
|
|
1230
|
+
|
|
8
1231
|
## [0.9.4] - 2026-09-26
|
|
9
1232
|
|
|
10
1233
|
> 本版主题(窗口 `0.9.3 → 0.9.4`):**成本与限制的账本对齐 + 「能不能发」有了判据**。
|
|
@@ -27,6 +1250,33 @@
|
|
|
27
1250
|
- **`tool_use` 的参数同样有界**:同一类「大载荷灌进摘要器」的洞,两处各配回归用例
|
|
28
1251
|
(含「小参数不许被截断」的阳性对照),变异验证 4/4 咬人。
|
|
29
1252
|
|
|
1253
|
+
### 变更 · 复审收口(2026-09-27,九条「测试没覆盖的缝」)
|
|
1254
|
+
|
|
1255
|
+
> 来源:对 R8 五项实现(P1–P5)的逐条复审 —— 单测全绿、全链 8/8,问题全在用例之外。
|
|
1256
|
+
> 每条的取证与复现读数记在各自提交里;下面按模块列**行为变化**。
|
|
1257
|
+
|
|
1258
|
+
- **MCP 反向桥三处**:① `tools/call` 现在走引擎**同一份** `inputSchema` 校验器,入参不合法
|
|
1259
|
+
回 `-32602` 且方法体零调用(此前缺必填项被当成功调用,实测返回 `你好,undefined`);
|
|
1260
|
+
② `auth` 钩子提到方法/路径判定**之前**(此前未鉴权能拿到 `405 allow: POST, DELETE` 与
|
|
1261
|
+
`DELETE → 200`,与 `createHttpHandler` 的「其余先鉴权」纪律不符);③ stdio 的 stdout
|
|
1262
|
+
挂了 `error` 守卫吞 EPIPE(此前宿主先关读端会把 server 打成栈回溯 + exit 1;正向连接器
|
|
1263
|
+
对子进程 stdin 一直是这么吞的)。
|
|
1264
|
+
- **`metricsSink` 归因标签补上第四道基数上限 `maxLabelCombos`**(缺省 200,进 limits 真源表):
|
|
1265
|
+
`maxLabelValues` 只封每个键的**值域**,而进内存的是键的**组合**(叉乘)—— 缺省 100 值域
|
|
1266
|
+
配 3 个键 = 1,030,301 条常驻,且 `droppedLabelValues` 看不见它。超限的**新组合**折进一个
|
|
1267
|
+
全 `__other__` 的桶(量不丢),折叠数经 `snapshot().droppedLabelCombos` 与
|
|
1268
|
+
`dropped_keys{kind="label:combos"}` 可见。**单键配置(最常见)组合数 ≈ 值数 ⇒ 行为不变。**
|
|
1269
|
+
- **标签 combo 身份不再裸拼接**:值里出现 `,`/`=`/`\` 时会给 `\` 前缀转义 —— 此前两个不同
|
|
1270
|
+
标签集能拼出同一个键(实测 `a="x,b=y",b="z"` 与 `a="x",b="y,b=z"` 并成一本账、且按前者
|
|
1271
|
+
的标签渲染,后者的量被错配)。正常值(不含这三个字符)的键与展示形**逐字不变**。
|
|
1272
|
+
- **`agentia export` / `exportRun` 三处**:① 汇总现在**也**在 stdout 模式打(走 stderr)——
|
|
1273
|
+
此前 `agentia export x.jsonl > dataset.jsonl` 静默丢坏行、而 `--out` 形态与 `report` 都会报;
|
|
1274
|
+
② `incomplete` 新增 `assistant-text` 的**整棵 trace 无正文**判据(混合回合的真文本此前静默
|
|
1275
|
+
缺席却不当缺口)与 `no-final-assistant`(末条 user ⇒ 无 loss 目标);③ 侧产物与 CLI 移植
|
|
1276
|
+
副本在「trace 缺 `status` / span 缺 `attributes`」的裸 trace 上不再分叉(框架侧此前会抛)。
|
|
1277
|
+
**⚠️ 迁移注意**:`meta.incomplete` 会多出 `no-final-assistant` 这个词 —— 按等值断言
|
|
1278
|
+
`incomplete` 的消费方要放宽成「包含」或补上这个词。
|
|
1279
|
+
|
|
30
1280
|
### 变更 · 三个旋钮的 `0` 从「静默失效」改成构造期报错
|
|
31
1281
|
|
|
32
1282
|
- **`Scheduler.every.maxInFlight: 0`**:闸门判据是 `inFlight.size >= maxInFlight`,`0` / 负数时
|
|
@@ -41,9 +1291,10 @@
|
|
|
41
1291
|
|
|
42
1292
|
- **`ModelPricing` 新增 `cacheRead` / `cacheWrite`**(缺省读 0.1×、写 1.25×;**1h 档写 2×** 由
|
|
43
1293
|
`priceOverrides` 表达),非法乘数(负数 / NaN)同样构造期抛错。
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
1294
|
+
- ~~未定价模型会「响」~~ **(订正 2026-09-27:这条不是本版新增,发布时误记)**:
|
|
1295
|
+
`usage.unpriced` 事件 / 指标 `model_unpriced_turns_total` / `onUnpricedModel` 回调自
|
|
1296
|
+
**v0.6.0** 起就在(#75),本窗口对它们零改动。本版真实的成本口径改动只有上面那条
|
|
1297
|
+
cache 乘数与下面的图片估算。条目不删、留痕。
|
|
47
1298
|
- **图片块的 token 按尺寸上界估**:官方按**尺寸**计费(28×28 像素 = 1 visual token),与文件字节数
|
|
48
1299
|
无关;框架取「长边缩到 1568px」的上界 3136 token/块 —— 宁可高估(`maxTotalTokens` 提前拦)
|
|
49
1300
|
也不低估(护栏迟触发)。
|
|
@@ -66,6 +1317,29 @@
|
|
|
66
1317
|
|
|
67
1318
|
### 仓库自身(不面向使用者)
|
|
68
1319
|
|
|
1320
|
+
- **补两份「口径单测」**(`tests/integrations/mcp-protocol.test.ts` + `tests/transport/http-io.test.ts`,
|
|
1321
|
+
2026-09-28,结构收口的尾巴):这两组 helper 是「桥 + 两个连接器」与「宿主 + 端点」的**共用面**,
|
|
1322
|
+
抽成独立文件(断值环 / 拆路由体)后**没有同名单测** —— 行覆盖靠既有套件就够(都是 100% 行),
|
|
1323
|
+
但**口径**从没被正面钉过。按 `http-shapes.test.ts` / `http-route.test.ts` 的先例补两份、共 35 条。
|
|
1324
|
+
**不追覆盖率**:纯防御分支**刻意不补** —— 典型是 `readBody` 的 `if (done) return`
|
|
1325
|
+
(Promise 二次 `resolve` 是 no-op,少这行行为完全一样),为它写断言就是真空变绿。
|
|
1326
|
+
⚠️ 同一轮查清一个会误导人的读数:`src/engine/mcp-server-stdio.ts` 报 **22.22% 行覆盖**不是缺口,
|
|
1327
|
+
是**采集假象**(用例走真子进程,c8 只采父进程;实测该用例 22/22 全过),且该数字拆分前就存在。
|
|
1328
|
+
变异验证 17/17 逐条咬人。详见 `docs/spec.md` §10 2026-09-28 ⑩ 第 5 条。
|
|
1329
|
+
|
|
1330
|
+
- **补 7 条「正常输入下可达、却从没走过」的功能分支**(2026-09-28,同轮尾巴;跨 3 个测试文件 +8 条用例):
|
|
1331
|
+
先纠正一个读法 —— c8 的 branch **不是 if/else 两条臂**,而是「**代码块**」(全仓条目**全是
|
|
1332
|
+
1 个 location**,`b[id]` = 该块被进入的次数,`0` = 这条路径**从未走到**),所以它比语句覆盖细。
|
|
1333
|
+
据此排查后补:`toolkit/subagent.ts` 的 **`spec.system` 函数形态**(三态联合里唯一「框架不追加
|
|
1334
|
+
REPORT_HINT、作者全权」的一态);`engine/trace-diff.ts` 的 **status / error 差异**三条判定
|
|
1335
|
+
(顺带带出 `errorEqual` 里那句从未执行过的三元组比较,并钉住「比内容不比对象引用」);
|
|
1336
|
+
`transport/async.ts` 的**缓冲溢出** `truncated` 帧与「**已终态后**才连上来」的 `end` 收口;
|
|
1337
|
+
`transport/http-endpoints.ts` 的 SSE `stream.truncated` 帧。
|
|
1338
|
+
**分母不变大的前提下**:语句 295 → **287** 未覆盖、块 347 → **338**、分支 91.82% → **92.04%**,
|
|
1339
|
+
用例 1465 → **1473**。函数那一档**刻意不动**(那 11 个「从未被调用的函数」全是只读访问器,
|
|
1340
|
+
另有一个 `TaskEventStreams.forget` 是**未导出的内部死方法** —— 为它写测试等于锁死死代码)。
|
|
1341
|
+
变异验证 7/7 逐条咬人。详见 `docs/spec.md` §10 2026-09-28 ⑩ 第 6 条。
|
|
1342
|
+
|
|
69
1343
|
- **`packages/cli/test/inspector.test.mjs` 新增 ⑫「选完文件夹 → 透传给 run 的就是所选目录」**:
|
|
70
1344
|
2026-09-24 真用户反馈「选完之后透传给 agent 的不是所选文件夹」,逐环核实后**链路是通的**
|
|
71
1345
|
(`createApp` 把显式 `providers` 拼在 discover 结果**之后** ⇒ 模板的 `WORKDIR = opts.workdir`
|
|
@@ -1459,7 +2733,9 @@
|
|
|
1459
2733
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
1460
2734
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
1461
2735
|
|
|
1462
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.
|
|
2736
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.10.0...HEAD
|
|
2737
|
+
[0.10.0]: https://github.com/retrychx/agentia/releases/tag/v0.10.0
|
|
2738
|
+
[0.9.5]: https://github.com/retrychx/agentia/releases/tag/v0.9.5
|
|
1463
2739
|
[0.9.4]: https://github.com/retrychx/agentia/releases/tag/v0.9.4
|
|
1464
2740
|
[0.9.3]: https://github.com/retrychx/agentia/releases/tag/v0.9.3
|
|
1465
2741
|
[0.9.2]: https://github.com/retrychx/agentia/releases/tag/v0.9.2
|