@migor/agentia 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +515 -1
  2. package/README.en.md +6 -4
  3. package/README.md +4 -3
  4. package/dist/AGENTS.md +47 -8
  5. package/dist/engine/mcp-server-http.js +68 -35
  6. package/dist/engine/mcp-server-http.js.map +1 -1
  7. package/dist/engine/mcp-server-stdio.d.ts +0 -13
  8. package/dist/engine/mcp-server-stdio.js +35 -22
  9. package/dist/engine/mcp-server-stdio.js.map +1 -1
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/integrations/mcp-http.js +12 -86
  13. package/dist/integrations/mcp-http.js.map +1 -1
  14. package/dist/integrations/mcp-protocol.d.ts +51 -0
  15. package/dist/integrations/mcp-protocol.js +128 -0
  16. package/dist/integrations/mcp-protocol.js.map +1 -1
  17. package/dist/integrations/mcp-stdio.js +63 -48
  18. package/dist/integrations/mcp-stdio.js.map +1 -1
  19. package/dist/integrations/openai.d.ts +4 -1
  20. package/dist/integrations/openai.js +25 -6
  21. package/dist/integrations/openai.js.map +1 -1
  22. package/dist/runtime/memory.d.ts +5 -0
  23. package/dist/runtime/memory.js.map +1 -1
  24. package/dist/store/store.d.ts +6 -0
  25. package/dist/store/store.js.map +1 -1
  26. package/dist/transport/async.d.ts +2 -4
  27. package/dist/transport/async.js +38 -120
  28. package/dist/transport/async.js.map +1 -1
  29. package/dist/transport/run-call-options.d.ts +77 -0
  30. package/dist/transport/run-call-options.js +72 -0
  31. package/dist/transport/run-call-options.js.map +1 -0
  32. package/dist/transport/run-outcome.d.ts +109 -0
  33. package/dist/transport/run-outcome.js +100 -0
  34. package/dist/transport/run-outcome.js.map +1 -0
  35. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,518 @@
5
5
  (0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
6
6
  决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
7
7
 
8
+ ## [0.10.2] - 2026-10-11
9
+
10
+ > 本版主题(窗口 `0.10.1 → 0.10.2`):**对外那一面各做实一格 —— 一个人能不能找到它、
11
+ > 能不能 5 分钟跑起来**;对内把框架最深的两块体积债还清。三块 —— ① **交付面**:脚手架生成的
12
+ > 新工程默认入口就是**可上线的 HTTP 服务**(`npm start` 直接起服务,不再是需要自己接线的批处理
13
+ > 样例);② **分发面**:官网全站**英文版**(`/en`,五页逐节对译 + 英文 404 + 语言自动检测与手动
14
+ > 切换)—— 对外检索面第一次有英文落点;③ **内部债**:两轮**纯结构**重构(方法级体量闸与三处巨型体
15
+ > 外移;`#executeInner` 的结果记账外移 + 补 14 条直接单测),顺带把 c8 分支覆盖率棘轮从 88 收到 89。
16
+ > **无破坏性变更**:脚手架是**生成物**,已生成的工程有自己的 `package.json` / `src/`,不会被本版动到
17
+ > ⇒ 既有使用者**不需要任何动作**;`@migor/agentia` 的公共 API 与 `exports` **逐字未变**(本版唯一
18
+ > 碰 `src/index.ts` 的是版本常量那一行)。
19
+
20
+ ### 官网 · 全站英文版(`/en`)+ 语言自动检测与手动切换(2026-10-08 ①)
21
+
22
+ - **是什么**:站点从中文单语扩成中 / 英两版 —— `index / docs / api / playground / tradeoffs`
23
+ 五页**逐节对译** + 英文 404,挂在 `/en/*`(中文仍在根路径,是默认入口)。此前整站只有中文
24
+ (`fragments/` 下没有 `en/`、也没有 `/en` 路由)⇒ 对外检索面**没有英文落点**,而这正是本仓
25
+ 「瓶颈在产品面 / 分发面」那句话里最直接的一格。
26
+ - **语言怎么定**:`<head>` 最前的一段 `is:inline` 脚本,三条判据 —— ① 先读
27
+ `localStorage['agentia-lang']`(用户**显式**切过);② 否则看 `navigator.languages` 里有没有
28
+ 中文;③ 拿不到语言信息就**不跳**(宁可停在默认的中文版,也不猜)。⚠️ 自动跳转**只读不写**
29
+ localStorage —— 写了就等于把「自动」记成「用户选择」,用户之后手动点回中文会被**立刻弹走**;
30
+ 只有 Nav 的切换控件才写。手动切换在导航右侧(`中 / EN`),指向**同页**的另一种语言。
31
+ - **URL 形态**:`build.format: 'file'` 下英文首页的产物是**扁平的 `en.html`** ⇒ 首页地址是
32
+ `/en`(无尾斜杠);`public/_worker.js` 加一条 `/en/` → `/en` 的 308 收敛(访客看到
33
+ `/en/docs` 会顺手猜 `/en/`)。站点地址一律干净形态(`.html` 会被 Cloudflare Pages 308)。
34
+ - **SEO / agent 面**:两版各自 canonical 指自身,`hreflang` 三处互指(`x-default` 指中文版);
35
+ `sitemap.xml.ts` 增 5 条 `/en/*`;`llms.txt.ts` 加「Documentation (English)」节;
36
+ `scripts/check-website-agent-readiness.mjs` 与 `scripts/build-md-variants.mjs` 的页面枚举改成
37
+ **递归** —— 不改则英文页**全部在产物守卫射程外**,且拿不到 `.md` 变体(GEO 档 C/D 直接退化)。
38
+ - **英文 docs 页不摆第二份「已知边界」表**:那张表是构建期从**中文** `docs/usage-guide.md` §7
39
+ 抠的,英文页手写第二份必然漂移 ⇒ 改成「一句说明 + 指回单源」;`llms-full.txt` 保持中文单源不动。
40
+ - **客户端脚本文案**(首屏 trace 自播 / 回放面板 / BYOK 真实模式)是**运行期**插进 DOM 的,
41
+ 构建期无从替换 ⇒ 新增 `packages/website/src/scripts/lang.js` 的 `pt(zh, en)` 接缝,中英**贴在一起写**
42
+ (不抽两份平行数组 —— 那会漂移,而且没有任何东西看得出来);语言取 `<html lang>`
43
+ (服务端已经定下的事实),**不读 `localStorage`**(否则「服务端渲了英文页、脚本文案出中文」
44
+ 这种错配就有了可能)。
45
+ - **守卫 `tests/docs/website-i18n.test.ts`**:页面集合一一对应 / 中英 fragment 的 `id` 集合逐页
46
+ 相等 + 剧本骨架(span id / 事件类型 / 顺序 / `usage`)逐例相等 / 英文面 0 CJK(fragment **与
47
+ 真跑客户端脚本**)/ `<html lang>` 映射与三处 `hreflang` / 检测脚本**只读不写** localStorage /
48
+ 五条 `/en` 路由已登记进 sitemap + llms.txt + `_worker.js`。**反向验证过 19 条**(各恰好点名
49
+ 那条,复原后 `sha256` 逐字节一致)—— 其中第一版有一条**假守卫**(裸正则被文件头注释里的
50
+ `lang='en'` 满足),是变异验证当场抓出来的。
51
+ - ⚠️ 两处如实标注的**射程**:① 该守卫**不看 `dist/`** —— `npm test` 在 `verify-all` 里早于官网
52
+ 构建(步 6 vs 步 8);产物侧仍由步 8 的 `scripts/check-website-agent-readiness.mjs` 守。
53
+ ② 「英文面 0 CJK」走**行为断言**(子进程喂假 DOM + 假 `fetch` 真跑,见
54
+ `tests/fixtures/website-i18n-harness.mjs`)而非源码扫描:本仓 `typescript` 是 7.x(tsgo),
55
+ 主入口已不再导出经典 parser API,而手写扫描器会被 `playground.js` 里**带反引号 / 引号的正则**
56
+ (`/^\s*```/`、`/[&<>"']/g`)带偏(实测 24 处假命中)⇒ 它能抓「新增一段没包 `pt()` 的中文」,
57
+ 抓不到「**永远跑不到的死分支**」里漏包。
58
+ - 官网**不随包发布**(`packages/website` 是 `private: true`)⇒ 本版**无包的对外面变更**,
59
+ 既有使用者不需要任何动作。
60
+
61
+ ### 官网 · 手机版重点特性卡的 `Core` 徽章压住标题(2026-10-08 ②)
62
+
63
+ - **病例**:390px 手机截图 —— 英文首页「Built for production」那张重点卡的 `Core` 徽章叠在
64
+ 标题 `Observable: trace as a first-class citizen` 的首行上(暗色小字下像两个字叠印)。
65
+ - **根因**:`.feature-flag` 是 `position: absolute; top: 16px; right: 18px`。桌面端这张卡
66
+ `grid-column: span 2` 跨两列、标题一侧有富余,撞不上;**单列窄屏下标题换行**,首行末端
67
+ 直接钻进徽章底下。中英共用同一套 CSS ⇒ 不是「英文站专有」,标题一长就复现。
68
+ - **修法**:在**已有的单列断点**(`@media (max-width: 560px)`,即那条把 `.feature--key` 的
69
+ `grid-column` 收回 `auto` 的地方)把徽章放回正常流:`position: static` +
70
+ `display: inline-block` + `margin-bottom`。⚠️ **只写 `position: static` 不够** ——
71
+ `<span>` 默认 `display: inline`,内联元素的 `padding` / `border` 不参与行高计算,
72
+ 徽章框照样会叠到下一行去。
73
+ - **守卫**:`tests/docs/website-css.test.ts` 增两条 —— ①「单列断点里 `.feature-flag` 必须
74
+ 收回正常流(且不是内联)」;②「该断点须覆盖手机宽度(≥480px)」。**反向验证过 4 条**
75
+ (删掉整条规则 / `static`→`absolute` / 去掉 `display` / 断点 `560→360`),各点名对应断言,
76
+ 复原后 `sha256` 逐字节一致。⚠️ 断言前**先剥注释**:本仓踩过「守卫被同文件注释满足」的假绿。
77
+ - 顺手把 `docs/guards.md` §1 里 `website-css.test.ts` 那行补准:**2026-09-30 加的
78
+ 「折叠菜单 CTA 边框」守卫一直没登记**(那行仍写着「反向验证过 3 条」)。本次给它补做
79
+ 4 条变异验证,该行一并更新。同样**不涉及包**(改的是 `packages/website` 的样式与网站守卫)。
80
+
81
+ ### 官网 · 部署后的 agent 打分不再在半传播状态下跑;`/en/` 的 308 补缓存头(2026-10-08 ③)
82
+
83
+ - **修掉一条腐烂的判据(真缺陷,非本次引入)**:`ci.yml` 的 `deploy-website` 在打分前要「等新
84
+ 版本真上线」,而那条判据是「`robots.txt` 里出现 `Sitemap: ` 行」—— 注释写着「它是本轮新增的
85
+ 产物,只有新版才可能有」。**那一行自 PR #119 起每一版都有** ⇒ 条件恒为真、从不等待。实测当场
86
+ 中了:第 1 次探测就报「已在线上」,而 AFDocs 读到的是**上一版的 sitemap**,据此刷出两条 **假
87
+ FAIL**(`llms-txt-links-resolve` 报「2 条坏链」、`llms-txt-coverage` 报「5 links not in
88
+ sitemap」—— 而 11 条链接逐条复测全是 200)。
89
+ ⇒ 判据换成「**本次运行的提交**」:站点侧由 `packages/website/src/pages/robots.txt.ts` 在构建期
90
+ 写下 `# build: <GITHUB_SHA>`(本地构建**不写** —— 「这一行在不在」本身就是「这份产物是不是 CI
91
+ 构建的」的判据,不给它编假缺省值),CI 侧轮询等线上出现**同一个 SHA**。等不到不再静默:轮询
92
+ 超时留 `::warning::`,而构建产物里没有该行则 `::error::` 并**跳过打分**。
93
+ ⚠️ **教训(写进 `ci.yml` 与 `robots.txt.ts` 的注释)**:判据不能是「某个只有新版才有的字面量」
94
+ —— 它会随版本变旧、然后恒真;只能是「本次运行自己的身份」。
95
+ - **`/en/` 的 308 补上缓存头**:`public/_worker.js` 原先用 `Response.redirect()`,它只给一个
96
+ `location`、**不带任何 cache 头**(Pages 自己那条尾斜杠 308 同样不带)—— 而这条规则由我们接手
97
+ 的理由**正是响应头**(否则没有任何地方能补)。改成显式 `new Response(...)` 并带上与站内其余响应
98
+ 同一档的 `public, max-age=0, must-revalidate`。行为用例增 3 条(`/en/` ⇒ 308 + 目标 + **缓存头**
99
+ + 不劳烦 `ASSETS`;带 `Accept: text/markdown` 仍 308;`/en` 本体不拦),**反向验证 2 条**
100
+ 各点名对应断言。
101
+ ⚠️ 两处更正:① 那条注释原先写着「Pages 对 `/en/` 只会回 404」,**是错的**(实测 Pages 会把
102
+ `/docs/`、`/en/docs/` 这类「产物里存在 `x.html`」的目录形态自己 308 到干净形态);② 上一轮我曾把
103
+ 那次 `cache-header-hygiene` FAIL 的账算在**这条 308** 头上 —— **也不是它**(AFDocs 会跟随重定向,
104
+ 逐端点复测静态资源 / `.md` / llms / sitemap 全合格)。那次的真因就是上面那条**腐烂的判据**。
105
+ 补这个头是显式的**卫生**,不是那次红的解药。
106
+ - **守卫加固(并更正一处误判)**:`tests/docs/website-i18n.test.ts` 里「检测脚本用了
107
+ `location.replace`」这条断言原先直接匹配脚本原文 —— 一条**写着同样字面量的注释**就能把它喂绿。
108
+ 现在先剥**整行注释**再断言,并给剥壳器加了自证断言。⚠️ 上一轮我把风险说成「靠那段 HTML 注释
109
+ 用了全角括号才没爆」—— **不对**:那段注释在 `<script>` 块**之外**、根本不在被断言的字符串里;
110
+ 真实(也较窄)的风险是**脚本块内**的注释。同一守卫里另一条「`_worker.js` 的 `/en/` 规范化必须是
111
+ 308」原先钉死了整条 `Response.redirect(...)` 表达式 ⇒ 上面那次**行为等价**的改写把它打红了;已改成
112
+ 只核**登记**(分支在场 + 是 308),行为交给真跑用例 —— **钉实现的字面量会把「等价改写」判成回归**。
113
+ - **新增元守卫**:`tests/scripts/verify-all-wiring.test.ts` 增一条 —— 把「等新版上线」判据的
114
+ **两端契约**钉住(`ci.yml` 侧必须按本次提交比;生产侧**真跑** `robots.txt.ts`,按打桩的
115
+ `GITHUB_SHA` 核**渲染出来的产物**,并钉住「没给 SHA 时不输出这一行」)。写这条守卫时**我自己先
116
+ 写出两条假守卫**(判据侧匹配整个 job body ⇒ 被同一 job 里另一处同样的串满足;生产侧核源码文本
117
+ ⇒ 被文件自己的注释满足),都是变异验证当场抓到的,已收紧。
118
+ - 同样**不涉及包**(改的是 `.github/workflows/ci.yml`、`packages/website` 与 `tests/`)。
119
+
120
+ ### 官网 · 语言跳转从客户端 JS 搬到服务端 302(2026-10-09 ①)
121
+
122
+ - **病例(真 FAIL,非判据抖动)**:AFDocs(`agentdocsspec.com` 的 0.20.0 打分器)的
123
+ `redirect-behavior` **逐页**判 `js-redirect` 而 FAIL —— 这是 CI 上唯一一条**真** FAIL
124
+ (`cache-header-hygiene` 那条是部署半传播的连带假红,早前已排除)。根因:语言自动检测是
125
+ `Base.astro` 里一段 `is:inline` 脚本(`location.replace`),而它**留在每一页 HTML 里**
126
+ (英文页也有,只是运行期不触发);AFDocs 判的是「这份 HTML 里有没有 JS 跳转」,不区分它会不会真跑。
127
+ - **搬到服务端**:`packages/website/public/_worker.js`(Pages **advanced mode** worker,源码 ==
128
+ 线上行为)按 `Accept-Language` 对**中文页面路径**回 **302** 到 `/en` 等价路径,并带
129
+ **`Vary: Accept-Language`**(少了它 CDN 会把中文那份 200 回给英文访客 —— 跳转直接失效)。
130
+ 客户端那段脚本连同 `localStorage['agentia-lang']` 一起下线。⚠️ 设计稿
131
+ `docs/plans/2026-10-08-website-i18n.md` §7 那条「静态托管没有服务端、JS 是唯一手段」当场被推翻
132
+ (它没把 `_worker.js` 算进去),已在原处划掉并补 §9。
133
+ - **显式选择优先于自动检测(防循环的那把锁)**:`Nav.astro` 的中 / EN 切换控件改写成
134
+ **`hl=zh` / `hl=en` cookie**(`path=/`、一年有效);`/` 见到 `hl=zh` 就**不跳** —— 没有它,
135
+ 英文浏览器点「中」落回 `/` 后会被自动检测再次弹回 `/en`,用户**出不来**。
136
+ - **四条刻意边界**(写进 `_worker.js` 注释 + 设计稿 §9.3):① 只从中文路径跳到 `/en`、**不反向**
137
+ (`/en` 是本体、永不拦;代价如实标注:中文偏好访客循外链落到 `/en` 时**不会**被送回 `/`,
138
+ 旧 JS 会 —— 有意收窄);② **只在非 markdown 请求上做**(带 `Accept: text/markdown` 的 agent
139
+ 要它请求的那个 URL 的 `.md`);③ 只处理 `GET` / `HEAD`;④ 302 **不给长 `max-age`**
140
+ (与站内其余响应同档 `public, max-age=0, must-revalidate`)。
141
+ - **守卫**:新增 `tests/docs/website-lang-negotiation.test.ts`(**真跑** `_worker.js`,喂假
142
+ `env.ASSETS`)11 条 —— 用户点名的五条(`hl=zh` 不跳 / 无 cookie + `Accept-Language: en` ⇒
143
+ 302 到 `/en` / `zh-CN` 不跳 / `/en` 本体不被拦 / **手动切回中文后不再被弹回**)外加 `Vary` +
144
+ 缓存头、深层页 query 保留、`POST` 与 markdown 不跳、`/en/` 308 与内容协商**不回归**。
145
+ `tests/docs/website-i18n.test.ts` 两条改写(客户端跳转**已下线**:断言打在**两层剥壳**后的源码上;
146
+ Nav 改核 **`hl` cookie**、且只认真实 `onclick` 处理器里的写入 —— Nav 注释里就写着 `localStorage`)。
147
+ **反向验证过 8 条**(各恰好点名那条,复原后 `sha256` 逐字节一致):整段停用 ⇒ **4 条** /
148
+ 忽略 cookie ⇒ **2 条** / `Vary` 换成 `Accept`、去掉 `/en` 排除、去掉「非 markdown」闸、
149
+ 去掉方法闸 ⇒ **各 1 条**(服务端);Nav 退回 `localStorage`、Base 加回客户端脚本 ⇒ **各 1 条**。
150
+ - **验收读数(本地真跑,8 页 curated 样本)**:`redirect-behavior` 由 **FAIL(js-redirect,逐页)
151
+ 翻成 PASS**(「No redirects detected across 8 pages」),`URL Stability and Redirects` 100/100;
152
+ 同一份产物在**退回旧实现**时该检查重新 FAIL —— 红→绿两向都实测过。
153
+ - ⚠️ **一处自己踩到的坑(顺手记下)**:第一版把「已下线」的说明写在 `<head>` 的 **HTML 注释**里,
154
+ 而 Astro 会把它**渲进每一页**(`location.replace` 字面量又回到了产物 HTML 里 —— 正是这条检查
155
+ 在看的字面量);已把说明移进**构建期才执行的 frontmatter** 注释,`<head>` 只留一句不含该字面量的短注。
156
+ - 官网**不随包发布**(`packages/website` 是 `private: true`)⇒ 本版**无包的对外面变更**,
157
+ 既有使用者不需要任何动作。
158
+
159
+ ### 深度评审收口:框架记账 / patrol 软链边界 / soak 假红假绿 / i18n 守卫记录面(2026-10-09 ②)
160
+
161
+ > 对 0.9.5 → 0.10.1 全窗口 + 在制品做了三轮深度评审(框架内核 / CLI / exports / soak /
162
+ > terminal-bench / patrol / 官网 i18n / CI),本节是发现的修复批次。每处修法都带变异验证
163
+ > (改坏 → 点名断言红 → 复原核 diff)。
164
+
165
+ - **`createOpenAIClient` 接 DeepSeek 缓存计量**(`src/integrations/openai.ts`):DeepSeek 的
166
+ `prompt_cache_hit_tokens` 此前被整个丢弃且 cache 恒记 0 —— agent loop 前缀缓存命中率常达
167
+ 80–95%,成本读数系统性高估数倍。现映射进 `cache_read_input_tokens`,且因 DeepSeek 口径
168
+ `prompt_tokens` **已含**命中部分(与 Anthropic 相反),`input_tokens` 拆出未命中部分防双计;
169
+ 无这两个字段的端点行为逐字不变。决策依据见 spec §10 2026-10-09 ①。
170
+ **对包的对外面**:DeepSeek 端点的记账口径变准(原先恒 0)—— 修正而非破坏;其余无公共 API 变更。
171
+ - **patrol 示例的边界校验补上 realpath**(`examples/patrol/src/workspace.ts`):原实现只有词法判定,
172
+ 根内指向外部的符号链接可读出界(数据外泄给模型)、`quarantine_path` 可把界外真文件移进隔离区
173
+ (审批人只见 benign 相对路径);头注却声称「软链逃逸一律抛错」。现为词法 + 真身两道校验,
174
+ walk 不跟随软链,注释/README 与实现对齐。
175
+ - **terminal-bench 适配器五处**:基础设施错误(限流/5xx/超时/连接/上下文超长)不再落进能力分母
176
+ (新增 exit 4 桶);bash 哨兵锚定整行(`set -x` 不再污染);`harbor_agent.py` 版本从真值派生
177
+ (不再硬编码);任务转换不再静默丢 `docker-compose.yaml`(单服务提取 ENV/WORKDIR/ENTRYPOINT,
178
+ 做不到的响亮告警)与原版 `run-tests.sh`(保留原文 + 尾部接 reward 落盘,测试依赖安装不再丢失);
179
+ 容器内静态 node 包补 SHASUMS256 校验。
180
+ - **soak 验证工具**:`e2e-soak-suspend` 看门狗余量从真实预算逐轮派生(旧写死 +30s 在长档必假红)、
181
+ approve 臂补「人工批落地 ≥1」闸(微档不再空转恒绿)、续跑段 link 校验从「存在」升级为
182
+ 「指向上一段」;`e2e-soak-app` 头注改为如实表述(假端点只回 echo,subagent/skill 链路本档未压到)。
183
+ - **官网 i18n 守卫补记录面**(`tests/fixtures/website-i18n-harness.mjs`):`placeholder`/`value`/
184
+ `title`/`aria-label`/`setAttribute` 通道此前不被记录 —— 英文页漏包 `pt()` 直接显示中文也全绿;
185
+ 假 fetch 剧本补 429 与网络错误分支。`_worker.js` 的 `/en/` → 308 补上 query/hash 保留。
186
+ ⚠️ 本批另有两条**在 rebase 时被 ①(服务端 302)吸收**:客户端检测脚本已删除 ⇒
187
+ 「404 页跳过跳转」与「只读 localStorage 断言加强」两条随之失效(对象不存在了);
188
+ 404 形态的残留(坏路径被 302 到 `/en/<坏路径>` 再 404)记为已知边界,见 PR #222 描述。
189
+ - 契约注释两条(不改行为):`MemorySnapshot.rev` 声明「水合成功时不得为 undefined」;
190
+ `TaskStore.get` 声明「必须返回副本而非内部活引用」(transport 的「先落库再派发」纪律依赖它)。
191
+
192
+ ### 脚手架 README:「`npm start` 部署/Docker 用这条」的误导表述(2026-10-09 ③)
193
+
194
+ - **病例**:`agentia create` 生成的 README 写「`npm start` ……(部署/Docker 用这条)」,但脚手架的
195
+ `main.ts` 是**一次性批处理入口**(`app.run` → 打印 → 退出:无端口、无 `/healthz`、无 drain)——
196
+ 那句话会读成「脚手架能直接上线」。全仓此前没有一处明说「脚手架给的是 CLI 形态,服务看
197
+ `examples/deploy/`」。
198
+ - **修法**:模板 README 的「构建与生产运行」节把 `npm start` 如实标为 Job 形态(适合 cron / CI /
199
+ 容器一次性任务),并给出服务化的三个指引:`createHttpHandler` + `AsyncRunner` 的配方在
200
+ `examples/deploy/`、上线清单在 `docs/deployment.md`、API 见项目内 `AGENTS.md` 的「触发与宿主」节。
201
+ - 只改文案,不改生成逻辑与脚本。CLI 套件 210/210 绿。
202
+
203
+ ### 脚手架默认入口 = 可上线的 HTTP 服务(`npm start`)(2026-10-09 ④)
204
+
205
+ - **病例**:`agentia create` 生成的项目只有 **Job 形态**(`npm start` = 跑一次就退出的
206
+ `main.ts`),而框架的交付承诺是「build 产物可以直接上线当服务」—— 上一版
207
+ (2026-10-09 ③)只把 README 那句误导文案改诚实了,服务化仍要用户自己去
208
+ `examples/deploy/` 抄配方再接线。
209
+ - **形态(对齐 NestJS 口径)**:`npm run build && npm start` 起来的就是**一个在监听的 HTTP
210
+ 服务**。为此把两个入口调了个个儿 —— **`src/main.ts` = 服务**(NestJS 的 `main.ts` 就是那个
211
+ 在 `listen` 的入口),原来的薄入口挪成 **`src/batch.ts`**(`npm run start:batch -- "你的问题"`)。
212
+ 脚本表:`start` / `start:prod`(**同物**,显式生产名)/ `start:batch`;**不再有 `start:server`**
213
+ —— 它的角色被 `start` 吸收,留一个同物的旧名只会让人以为服务还有另一种起法。
214
+ 决策记录(含「换骨 vs 换皮」「SQLite vs FileTaskStore」两个取舍)见
215
+ `docs/plans/2026-10-09-scaffold-default-service.md` 与 `docs/spec.md` §10 2026-10-09 ②。
216
+ - **服务面**:`src/main.ts` 复用 `src/app.ts` 的 `createAgentApp()` 工厂(dev 环与生产共享
217
+ 同一份装配,旁路会被 `templates.test.mjs` 的形状断言挡住),接上 `createHttpHandler` +
218
+ `AsyncRunner` + `SqliteTaskStore`:`/healthz`、同步 `POST /run`(含 SSE)、异步
219
+ `POST /tasks`、**崩溃续跑**(`resumePending`)、**优雅停机**(SIGTERM/SIGINT →
220
+ `drain({ timeoutMs: 15_000 })` → `server.close`,二次信号直接退出)。配套:模板
221
+ `package.json` / `.gitignore` / README 三节重写、`create` 的后续步骤文案、
222
+ `docs/usage-guide.md` §2.1 文件分工表两行。
223
+ - **鉴权取舍**:可选,不强制 —— 设了 `AGENTIA_TOKEN` 就启用 Bearer 校验(`/healthz`
224
+ 除外);**不设则启动时打一条响亮警告**(「HTTP 面无任何鉴权,仅应监听回环/内网」)。
225
+ 与 `examples/deploy` 同档:框架「鉴权只是缝」(策略是宿主/反代的事),patrol 那种
226
+ 强制模式对第一天的新工程太重。
227
+ - **两处顺手修掉的静默**(评审实报,都不是新功能):① `AGENTIA_DB` 的缺省从 **cwd 相对**
228
+ 改成按本文件位置落**工程根** —— 服务形态常换工作目录启动(systemd / 容器 `-w`),
229
+ cwd 相对会让同一个工程用上两个库,而 `resumePending()` 静默找不到上一个进程的任务
230
+ (表面看像「任务丢了」);② `PORT=`(空串)此前经 `Number('')` ⇒ `0` ⇒ **静默随机端口**,
231
+ 现在显式当缺省,非整数 / 越界则响亮报错(⚠️ **不能**改成 `|| 3000` —— 那会把冒烟测试
232
+ 依赖的 `PORT=0` 一起吃掉)。另把 `PORT` / `AGENTIA_DB` / `AGENTIA_TOKEN` 补进 `.env.example`
233
+ 的变量清单。
234
+ - **Node ≥ 22.5 边界如实标注**:`SqliteTaskStore` 用 `node:sqlite`,更低版本**起服务时**
235
+ 构造期抛可读报错(框架对它延迟加载)。默认入口变成服务之后,这条从「只有 `start:server`
236
+ 需要」变成「**脚手架默认路径就需要**」—— README、`docs/usage-guide.md` 与上线清单
237
+ `docs/deployment.md` §1 三处都写明,并给出 `FileTaskStore` 这条 Node 18 的退路(同一套
238
+ API,代价是单写者前提)。框架**库**的 `engines: >=18` 与 `import-floor` job **不变**
239
+ —— 抬起来的是脚手架默认路径的下限,不是包的下限。
240
+ - **只影响新建工程**:脚手架是**生成物**(`agentia create` 的那一刻写出去),
241
+ 已生成的工程有自己的 `package.json` / `src/`,不会被这一版动到 ⇒ **既有使用者不需要任何动作**
242
+ (不构成破坏性变更)。
243
+ - **交付面同步(补做)**:这次翻转改的是「哪个文件是默认入口」,而**散文面当时没跟着改** ——
244
+ 官网中英两版 docs 页、仓库根 `README.md` 与 `packages/cli/README.md`(**随 npm 发布**)
245
+ 都还写着翻转前的说法,而 `tsc`、单测与八步门禁**全绿**:它们扫源码与产物,不看散文。
246
+ 已补齐,并加了 `tests/docs/scaffold-entry-contract.test.ts`(四层判据 + 三条防真空下限,
247
+ 12 条变异验证)。⚠️ **射程如实写在该文件头**:目录树里「一行只写一个文件」的列举、以及
248
+ 散文把两个入口名折到相邻两行的写法**不在射程内** —— 硬判会误红,而误报的门禁会被关掉。
249
+ ⚠️ 这次漂移此前**零守卫**:`check-website-agent-readiness.mjs` 只核 `llms-full.txt` 与单源
250
+ `docs/usage-guide.md` 逐字节相等,**官网 fragments 整片不在它的射程内**。
251
+ - **同一类病的第二例(顺手修掉)**:2026-09-22 把装配拆成 `src/app.ts` 时,官网两版 api 参考
252
+ 仍把读 `.env` 说成由默认入口 `src/main.ts` 首行做(它实际在 `src/app.ts`)—— 比本次翻转还早,
253
+ 停了半个月。已一并改掉,并给上面那条守卫补了第 ④ 层判据(同一行不得把读 `.env` 的 API
254
+ 挂在默认入口上、却不提装配模块)。⚠️ 这次也**零守卫**:`templates.test.mjs` 的同一条
255
+ 不变量只守模板源码,守不到散文面。
256
+ - **验证**:`templates.test.mjs` 的服务面形状断言改钉 `main.ts`(从 `./app.js` 取工厂、不许
257
+ 旁路 `createApp`、AsyncRunner + createHttpHandler + SqliteTaskStore + resumePending +
258
+ `drain({` + SIGTERM、缺省无 token 必须有警告文案),原 `main.ts` 那两条**平移**到 `batch.ts`
259
+ (薄入口 + `result.error` 非零退出);`scripts/e2e-cli.ts` 一步真起 `node dist/main.js`
260
+ (PORT=0、临时 DB、假 Anthropic 端点)—— `/healthz` 200、无 token `POST /run` 401、
261
+ 带 token 真跑一轮 200、SIGTERM 干净退出,另一条从**两个 cwd** 真跑 `dist/batch.js`。
262
+ 零 `src/` 框架改动。
263
+
264
+ ### 重构(纯结构,零行为变化)· 方法级体量闸 + 三处巨型体外移(2026-10-11 ①)
265
+
266
+ 来源:一轮框架深度评审的 §8 建议 ①②③。**零语义变更**(下游行为一字未改)⇒ 按
267
+ `CONTRIBUTING.md:59` 的判据**不写 `docs/spec.md` §10**;决策留档在
268
+ `docs/plans/2026-10-11-method-size-guard-and-debt.md`。
269
+
270
+ - **新增方法级体量闸 `tests/architecture/method-size.test.ts`**(`src/**` 单个可调用体 ≤
271
+ **240** 行)+ 自带扫描器 `tests/architecture/lib/callable-spans.ts`。文件级已有闸
272
+ (`source-size`,单文件「含代码行」≤ 750),但**方法级没有** —— 而文件级闸的盲区正好是
273
+ 最大那一档的生长方式:一个工厂把周边全吞进自己的花括号,**文件总行数几乎不变**。
274
+ ⚠️ **判据是行数、不是比例** —— 评审报告建议的「比例」判据经**实测后否掉**:抽件在减分子的
275
+ 同时也减分母(`mcp-http.ts` 抽走 74 行后仍在 78%),比例的下限被文件的脚手架行数顶住,
276
+ 要当时就咬只能落在离阈值 1~4pt 处 ⇒ 常态化误报(违 `guards.md` §3 纪律①)。
277
+ 扫描器**刻意不是 AST**:`typescript` 7.0.2 是 tsgo、**已无经典 compiler API**(实测
278
+ `import * as ts` 只剩 `{default, version, versionMajorMinor}`)⇒ 走文本,并与结构体检技能
279
+ 的独立尺对拍(13 行读数**逐字相等**)。
280
+ - **三笔债按新闸的射程外移**(判据:这段代码能不能**局部问答**,能才搬):
281
+ - `integrations/mcp-http.ts` 的 `createStreamableHttpMcpConnector` **265 → 191 行**
282
+ (文件 316 → 245)—— 两个连接器**各自抄了一份**的协议面(JSON-RPC 结果提取 / HTTP 错误
283
+ 包装 / `tools` 清单校验 / `isError` 拒绝 / 会话自愈判定,共 7 个)收进既有的
284
+ `mcp-protocol.ts`(158 → 294)。同一条错误文案曾存在两份,**改一处漏一处没有用例看得见**。
285
+ - `integrations/mcp-stdio.ts` 的 `createStdioMcpConnector` **252 → 213 行** —— 子进程退出
286
+ 观察面(`trackChildExit`)与收尾(`reapChild`)提出去;stdout 分帧派发提出 `dispatchLine`。
287
+ - `engine/mcp-server-http.ts` 的 `startHttpTransport` **192 → 145 行** —— 五个纯件
288
+ (`sendJson` / `readBody` / `jsonRpcIdOf` / `startListening` / `closeOwnServer`)提到
289
+ **同文件模块级**;`mcp-server-stdio.ts` 同款提出 `dispatchLine`(那个体本来就在 240 以下,
290
+ 属顺手对齐,不是欠债)。
291
+ - `transport/async.ts` 的 `#executeInner` **257 → 217 行**(文件 1460 → 1419)—— 42 行 run
292
+ 选项装配 + 幂等去重的终态采纳搬到**新文件** `transport/run-call-options.ts`。⚠️ 搬迁
293
+ **逐字**:那些 `...(cond ? {k: v} : {})` 的形状是承重的(本仓开着
294
+ `exactOptionalPropertyTypes`,「没这个键」与「键是 undefined」下游语义不同)。
295
+ - ⚠️ **反直觉读数,写明免得下次误判**:外移让这七个文件的**总行数上升**(2569 → 2801,
296
+ **+232**)。原因不是多写了代码,而是**注释是证据载体** —— 搬走的每一块都带着自己的
297
+ 「为什么外移」。这个方向**没有守卫会被触发**(`source-size` 是单文件闸,没有全仓总量闸)。
298
+ - ⚠️ **本轮自己踩到并修掉的两个「守卫骗人」**:① 第一版自证样本把花括号放在**单独一行**,
299
+ 而收尾判据**对整体深度平移是免疫的** ⇒ **摘掉遮蔽照样绿**(假守卫)—— 已换成「花括号与体的
300
+ 开括号**同一行**」的真病灶形态。② 外移时漏掉**第三个写者**:基线 `markExited` 挂在
301
+ `error` / `exit` / `close` **三个**监听器上,只搬两个 ⇒ 表现为「关不掉」或「多等一个宽限期」,
302
+ **用例全绿、`tsc` 全绿、67 个 MCP 用例全过**。这正是 `guards.md` 附 B.3 第 1 条问的东西,
303
+ 它是**被机械对拍(字面量 `'exit'` 计数 1→2)带出来的线索**,已补回并保持 1:1 映射。
304
+ - **验证**:变异电池六条(4 条扫描器口径 + 棘轮 + 真空护栏)**咬住 6 / 漏网 0 / 还原异常 0**,
305
+ 每条都是精确字面替换(命中数 ≠ 1 即中断)、备份先于一切变异、还原后 `sha256` 逐字节回到基线;
306
+ 其中最强的一条是**把 `mcp-http.ts` 用 `git show HEAD` 退回拆分前** ⇒ 断言原文点名
307
+ `mcp-http.ts:52 createStreamableHttpMcpConnector = 265 行(占文件 84%,该文件可调用体 1 个)`
308
+ —— 即**本闸本来就能在拆分前咬住那笔债**。另按附 B.3 第 2 条做**字符串字面量机械对拍**
309
+ (163 种 / 300 处 → 164 种 / 302 处,5 处丢失逐条核过,只有三条 MCP 错误文案 2→1,
310
+ 而那是本次的**目的**)。`guards.md` §1.1 补登记行(含两条已知偏离:**比例盲区** 与
311
+ 「多行调用传对象字面量实参 `foo({` 会被判成一个体」——全量实测 538 个体里 4 个、最大 9 行)。
312
+
313
+ ### 重构(纯结构,零行为变化)· ③ 做全:`#executeInner` 的结果记账外移 + 直接单测(2026-10-11 ②)
314
+
315
+ - **先更正上一份评审的口径**:它把 ③ 写成「`async.ts` 的 **drain 协调**(`#executeInner` 257 行含
316
+ **4 个嵌套 `try`**)」—— **两半都不准**。① drain 的**等待闸**早已抽成 `src/transport/drain-gate.ts`
317
+ (**在飞计数刻意不搬**:它同时是 `/healthz` 的 `inFlight` 口径、由任务生命周期加减);
318
+ ② 现测是 **6 个 `try {`**(`catch` 只有 3 个,另 3 个是 `finally`;缩进 profile 4/6/8/8/10/8)
319
+ ⇒ **最深四级 `try` 嵌套**,不是 4 个。**根因是 `AGENTS.md` 自相矛盾**:`transport/`
320
+ 导览一处写「只剩 drain 协调未抽」,几行之后就是 `drain-gate.ts` 的说明。⇒ 本轮**就地更正那句话**
321
+ 并留下后果说明(正是它让人把已完成的抽件报成未偿的债)。
322
+ - **做了什么**:抽 `src/transport/run-outcome.ts`(同族的第五件,前四件是 slot-pool →
323
+ approval-policy → drain-gate → run-call-options)——`readClaimed`(认领前重读;**读失败不升级成
324
+ 「任务起不来」**,这条分支此前只能透过 runner 行为间接观察)+ `applySuspendedOutcome` /
325
+ `applyTerminalOutcome`(挂起与终态两支的**记录映射**)。逐字搬迁,判据与注释一并搬走。
326
+ - **纯的边界**:实例副作用以**返回值**交回调用点。其中「用户输入快照必须在 `rec.spec` 被覆盖
327
+ **之前**取」是**承重的顺序事实** —— 取错时刻是**静默的错**(会话历史翻倍、token 复利),
328
+ 所以它必须是一条类型上看得见的返回值,而不是藏在函数体里的回调。
329
+ - **读数**:`#executeInner` **217 → 176 行**;其中的 `try` **6 → 5**、`catch` **3 → 2**
330
+ (`readClaimed` 摘掉一个 `try`+`catch`)。⚠️ **最深 `try` 嵌套没变,仍是四级** —— 去掉的那个与
331
+ run 段**平级**,不在 4→6→8→10 那条最深链上。⇒ 本轮降的是**体量与 `try` 个数**,不是嵌套深度;
332
+ `async.ts` **1419 → 1379 行**。⚠️ **剩下的 5 个各有不同语义**(最外层挡「异步 store 任何一处
333
+ reject 逃逸成 unhandled rejection」/ 认领终态闸 / run 段错误映射(`cancelIntent` 必须声明在
334
+ `try` **外**才能被 catch 看见)/ 收尾摘监听器 / 落库失败也要释放槽位)⇒ **抽平它们只会改变语义
335
+ 或把 `try` 搬个地方**。所以本轮的判据是「**能抽的抽干净 + 把剩下的为什么不能抽写下来**」,
336
+ 不是「把 `try` 数降到 1」。另**如实记一条**:本轮抽件**没有降低嵌套深度**(4 级 → 4 级),
337
+ 降的是**体量与 `try`/`catch` 个数**;「抽走了一个 `try`」≠「嵌套浅了一层」。
338
+ - **新增单测 `tests/transport/run-outcome.test.ts`**(14 个 `it`):快照的三条判据**各缺一条都拿不到
339
+ 快照**、`eventsDelivered` **两档都断**(只断一档的话「无条件清」也能绿)、终态 5 个挂起痕迹字段
340
+ + 返回值 3 组合(`isResume × run.status`,且非取消那支**用 `failed` 试**——只用 `succeeded`
341
+ 的话两种写法都绿)、「取消但已有 error ⇒ 不覆盖」、以及**异步 store**(`get` 返回 Promise)那一支。
342
+ fixture **逐字段给值**、不用 `{} as AgentRunResult` —— 本件真正读的就是那几个字段,空对象 cast
343
+ 会让「字段名写错」在 `tsc` 下全绿、运行时读成 `undefined`(本仓第 5 条踩过的原样形态)。
344
+ transport 套件 **353 → 367** 全绿(零行为变化的行为侧判据)。
345
+ - **变异电池 16 条**(`.workbuddy/probes/2026-10-11-run-outcome-mutation-battery.py`):咬住 **16** /
346
+ 漏网 **0**。⚠️ 被改的文件是**本轮新增、尚未入库**的(`git status` 里是 `??`)⇒ 脚本按本仓第 15 条
347
+ **第四形态**写:备份先于一切变异、**开跑就把恢复点打印出来**、装 SIGTERM/SIGINT 处理器先还原再退
348
+ (`try/finally` 挡不住 SIGTERM:默认处置是立即终止、不展开栈)。
349
+ - **覆盖率棘轮:分支阈值 88 → 89**。阈值按 `docs/guards.md` 附 B.2 那条推 —— **阈值 = 水位 − ~3pt**。
350
+ **改前基线**(在 `HEAD`(`303c870`)上开独立 `git worktree` 跑,里面只有改前那 353 条用例):
351
+ `All files` 行 98.57 / **分支 92.82** / 函数 98.44,其中 `transport/async.ts` 分支 **94.91**;
352
+ **改后现测**(367 条用例):行 98.58 / **分支 92.85** / 函数 98.45,`transport/async.ts` 分支
353
+ **95.08**,新件 `run-outcome.ts` **100/100/100/100**(那 14 条单测确实把它的分支走遍了)。
354
+ ⇒ 88 的余量 4.85pt 超出「~3pt」的必要范围,收口到 **89(余量 3.85pt)**;⚠️ **收 90 只剩 2.85pt
355
+ (低于 3pt)故不取** —— 阈值离水位太远会放掉一整个模块的测试,太近会把正常抖动当退化报。
356
+ **行 / 函数刻意不动** —— 行 95 的余量 3.58pt、函数 95 的余量 3.45pt,本来就在 ~3pt 上。
357
+ ⚠️ 基线那次跑有 2 条红,**与代码无关**,如实记下:`worktree` 里没有 `dist/` ⇒ `tests/docs` 的
358
+ 记分牌量不了**发布体积**(`dist/index.js 不在场`)。那两条不执行 `src/` 代码、也不影响覆盖率读数。
359
+ - **忠实性按 `docs/guards.md` 附 B.3 六条复核过**:字面量多重集对拍 `async.ts` **72 种 → 72 种**
360
+ (少的 3 处是 `'cancelled'` / `'suspended'` / `'succeeded'` 各 1 处,**都落在新件里**;多的 1 处是
361
+ `'./run-outcome.js'` 这条 import)⇒ **1:1**;新件**零模块级状态**;依赖单向(新件不 import `async.ts`,
362
+ `RunSettled` 是**结构镜像**,真类型漂移时调用点直接报错)。
363
+
364
+ ### 依赖
365
+
366
+ - 开发依赖:`@anthropic-ai/sdk` → 0.132.0、`@types/node` → 26.6.4 —— **不影响运行时面**:
367
+ 两个都在 devDependencies(根 `dependencies` 是 `{}`)。SDK 只在
368
+ `tests/types/message-compat.types.ts` 做「自有公共消息类型族 ↔ SDK 类型」的结构兼容门禁,
369
+ 另在 `tests/engine/errors.test.ts` / `tests/engine/loop.test.ts` 里真 `new` 出 SDK 的错误类
370
+ 断言引擎映射 ⇒ 两道都在 `verify-all` 射程内(第 4 步 / 第 5 步),本版是绿的。
371
+ - 官网(不随包发布):`astro` → 7.3.7。构建与产物形状由 `verify-all` 第 8 步
372
+ (`build:website` + 按产物形状核 404 / robots / sitemap / llms.txt)在 PR 阶段验证,
373
+ 真实部署只在 main 上跑 —— 本版已随 main 的 CI 部署通过。
374
+ ## [0.10.1] - 2026-10-05
375
+
376
+ > 本版主题(窗口 `0.10.0 → 0.10.1`):**从「看得见一次 run」到「留得下、比得出」**,外加给两块
377
+ > 长期没有闸门的地方补上闸门。三块 —— ① dev 面板:每次 run 收尾把 trace 落盘,成为
378
+ > `report` / `diff` / `harvest` / `export` 四个命令的现成输入,并支持**并排比两条 run**
379
+ > (含「改一句 prompt 立刻重跑对照」的自动补对);② `packages/trace-view`:补规模棘轮 +
380
+ > 三条最该有的行为断言(树形状 / 错误行 / usage 汇总);③ `docs/usage-guide.md` §7 补登记
381
+ > 两条**有病例、可复现**的长跑边界。
382
+ > **无破坏性变更**;`@migor/agentia` 框架代码**零改动**(本轮发布面里唯一碰到 `src/` 的是
383
+ > 版本常量那一行)⇒ 既有使用者**不需要任何动作**。
384
+ > ⚠️ **一处新增的用户可见文件**:跑 `agentia dev` 会往工程根的 `.agentia/` 追加
385
+ > `traces.jsonl`,**只增不减**(`.agentia/` 已 gitignore;要清是用户自己 `rm`)。
386
+
387
+ ### dev 调试工作台:trace 落盘 + 并排 A/B(2026-10-05)
388
+
389
+ - **是什么**:`agentia dev` 的本地面板从「看一条 run 的调用树」扩成「**比两条 run 差在哪**」。
390
+ 两件事:① 每次 run 收尾把 trace 追加落盘到 `.agentia/traces.jsonl`;② run 列表支持
391
+ **⌘/Ctrl + 单击**选两条,右栏切**双栏**并排两棵调用树,顶部一条差异摘要
392
+ (run 级 summary + 逐 span 字段级差异)。设计稿 `docs/plans/2026-10-05-dev-workbench.md`。
393
+ - **落盘换来什么**:那个文件的格式**恰好就是** `agentia report` / `diff` / `harvest` /
394
+ `export` 四个命令的输入(`jsonlTraceSink` 的契约)⇒ 面板里跑过的每一次 run,
395
+ 立刻变成「哪个能力慢/贵/爱失败」「改 prompt 后轨迹漂没漂」的现成输入。
396
+ 开发期最贵的一件事是「拿不到刚才那次 run 的数据」,这一行就解决它。
397
+ - **并排 A/B 的判据(三档,显式不静默)**:能力选择与工作目录是 **app 级**的
398
+ (换它们要重启进程)⇒ 两条跨了能力选择的 run,`runConfigSnapshot` 整个变了,
399
+ diff 出来的差异**多半与「我改的那句话」无关**。所以跨能力 / 跨目录 / 元信息缺失
400
+ 都给**显式降级提示**(含可执行的出路),而不是静默给一份没意义的 diff。
401
+ 可比时**不显示**提示条 —— 每次都飘噪声等于没有提示。
402
+ - **三条如实标注的边界**:① `traces.jsonl` **只增不减**(`.agentia/` 已 gitignore;
403
+ 要清是用户自己 `rm`);② 落盘是**同步写**(`appendFileSync`),大 trace 会占 runner
404
+ 事件循环 —— dev 场景单条以百 KB 计,且 `traceLimits.maxEvents` 本来兜上限;
405
+ ③ **并排是「事后回看」,不是「同时跑」** —— 单飞闸没解除(解除会连带改变
406
+ 「文件变更延后重启」的语义,而那条判据是刻意单源化的),真并发另议。
407
+ - **守卫**:compare 路由三态(顺序 / 可比性 / 归一化)与判据六条都有单测,且做过
408
+ **变异反向验证**(路由顺序反了、判据写死、`rootSpanId` 缺失 —— 三个变异都被咬住)。
409
+ 另给 e2e 加了落盘断言(逐行可解析 + **traceId 不重复**:重复行 = sink 被注册两次)。
410
+ - **一处自认的债**:`normalizeForDiff` 存在只因 `diffTraces` 的 `pushAttrDiffs` /
411
+ `pushEventDiffs` 不接受 `undefined`,而 `packages/cli/src/diff.ts` 是框架
412
+ `src/engine/trace-diff.ts` 的**逐字同形移植副本** ⇒ 根治要框架侧一起改
413
+ (`?? {}` / `?? []`),属 `src/` 语义变更。本次取「消费侧归一化」的零核心改动解。
414
+ **零 `src/` 改动** ⇒ 按 `CONTRIBUTING.md` 的判据不写 spec §10。
415
+ - **顺带补的面板测试债**:上面这批面板代码里**能单测的判断已从 HTML 搬进
416
+ `panel-logic`**(`compareView` 三态视图 / `diffSideLabel` 缺侧标签 /
417
+ `diffValueText` 取值 / `diffFetchErrorText` 失败文案)—— 搬**不是**新写,
418
+ 是把已存在的行为从「无测试区」挪进 `panel-logic.test.mjs` 的射程。
419
+ ⚠️ **剩下的 DOM 接线补不了**(本仓无 jsdom,硬加会引入 DOM 依赖)⇒ 只有
420
+ `scripts/e2e-dev.ts` 间接兜。如实标注,不假装已覆盖。
421
+ - **入库前评审修掉三处**:① **并排态下普通单击切 run 时不复位 DOM** —— 只清了选择,
422
+ 右栏与差异摘要**留在屏幕上**(内容还是上一对的)而选中标记已经消失,用户按看到的读
423
+ 会张冠李戴。这是本档**唯一用户可见**的缺陷;修法 = 清空必须走 `exitCompare()`,
424
+ 并抽出 `resetCompareDom()` 消掉三处逐字重复的复位序列;**另补一条文本层守卫 W4**
425
+ (`state.compare = []` 只许出现在 `exitCompare` 里)—— 面板无 jsdom 单测,
426
+ 这是唯一能防它回归的机制。② `refreshCompare` 读不到某条 run 时**静默留白**
427
+ (看起来像「这条没有调用树」,真相是它已被内存 ring 淘汰)⇒ 改为显式说出来。
428
+ ③ 删掉只写不读的 `state.diff` 字段 —— 它的注释还承诺了一个**并不存在**的「读取中…」
429
+ 渲染。另修:`comparability` 的长 JSDoc 错挂到 `capsKey` 头上(它自己反而没有注释)、
430
+ 一条与上一条**同一对 run 同一断言**的死断言(`bothFull`)。
431
+ - **A/B 闭环补最后一跳**:上面那条「改一句 prompt 就能**立刻重跑对照**」此前要手动
432
+ ⌘ 点两条才进并排(三步)。现在 —— **已选一条、还没选第二条**时,新 run 一收尾
433
+ 就自动补上第二条并进双栏,**并播一条 notice** 说清屏幕为什么变了。判定
434
+ `autoPairTarget` 在 `panel-logic`(6 个断言 + 变异验证),三种情况**刻意不自动**:
435
+ 空选择(替用户决定)、已选两条(会挤掉刚选的)、同一条。⚠️ 这是「事后回看」的
436
+ 自动化,**单飞闸仍未解除**(真并发要改「文件变更延后重启」那条单源化判据,
437
+ 见设计稿 §8)。DOM 接线本身**无自动化覆盖**(无 jsdom),只有 e2e 间接兜。
438
+
439
+ ### `packages/trace-view`:补规模守卫 + 补三条最该有的行为断言(2026-10-05)
440
+
441
+ - **规模棘轮**(新增 `test/structure.test.js`):逐文件 `LINE_BUDGET` + **反向**检查
442
+ (表里有、盘上没了 ⇒ 基线该调低,否则登记项失效后守卫「看起来在管」而实际不管)+
443
+ 一条「基线表不许有重复键」的自检。**这道守卫买的不是「今天的健康」**(实测
444
+ `src/` 846 行、测试 904 行、体量完全健康),**是「它开始变大时有人被叫醒」** ——
445
+ 2026-09-23 刚在 CLI 上治过一模一样的病(`dev.ts` 长到 1148 行、长出隐式状态机,
446
+ 靠人工回看才发现)。
447
+ - **三条行为断言**(新增 `test/tree.test.js`,9 个用例):补的是 `renderTrace`
448
+ 里**注释最密、却一直没有断言**的三块 ——
449
+ ① **树形状**:三层嵌套的树形前缀(`└─` / `├─` / 竖线缩进)与「事件与子 span 按发生
450
+ 顺序混排」(拍平成「先全部事件、再全部子 span」会让用户看到因果倒置);
451
+ ② **错误行**:失败 span 的类名 / `meta` 带 `error.type` / **原文单独起一行**(只塞
452
+ `title` 等于没显示 —— 首次运行最常见的「没配 API key」就靠这一行定位)、run 根失败
453
+ 时根行也标红(已修过的缺陷,防回退)、**正常 span 不加 error 类**(防「三态塌两态」
454
+ 导致满屏红);
455
+ ③ **usage 汇总**:**只累加 `llm.turn`**(capability span 的 usage 是子 span 聚合,
456
+ 重复计入会双算,而页面上看不出异常 —— 最坏的静默错),且**成对**钉「两个 turn 累加成
457
+ 两倍」(否则「压根不累加」也能绿);另钉「每行显示自己那份 usage」与「根未结束不收尾」。
458
+ - ⚠️ **新写的断言里三条是我自己写错的**(都被现有代码正确地打红、查证后是断言错不是代码错):
459
+ ① 以为 `m1` 是根的独子(实际它还有兄弟 `sibling` ⇒ 只能是 `├─`);
460
+ ② 以为「混排」是事件行插到**别的** span 行之间(实际混排发生在**同一个父节点的孩子
461
+ 列表**里,事件是所属 span 的子节点);③ 根失败的 status 写在了 trace 顶层
462
+ (渲染器读的是**根 span 自己**的 `status`)。⇒ 三条都已写进注释,说明读的是哪个字段。
463
+ - **两处变异反向验证**(都被咬住):usage 去掉 `llm.turn` 判据 ⇒ 双算 200 被抓到;
464
+ 树形缩进恒空 ⇒ 平铺被抓到。**零 `src/` 改动。**
465
+ - **同批复核又修掉两处**:① 结构守卫里那条关于重复键的注释**事实错误** ——
466
+ 原文说「本文件是 `.mjs`(ESM 严格模式)⇒ 重复键是 SyntaxError、import 就炸」,
467
+ **实测证伪**:`{ a: 1, a: 2 }` 写进 `.mjs` 是 exit 0、输出 `{ a: 2 }`(ES6 起对象
468
+ 字面量的重复键是合法的)。⇒ 那条自检**不是备份,是唯一防线** —— 变异实测:把重复键
469
+ 补在**表尾**时「单文件不超基线」那条**照样绿**(后写的值覆盖前者,反而更宽松),
470
+ 只有它红。② `tree.test.js` 里一条断言消息夹着**两个 U+FFFD 替换字符** ——
471
+ assert 失败时唯一给线索的那句话是乱码。
472
+
473
+ ### 文档 · `§7 已知边界` 补登记两条长跑边界(2026-10-02)
474
+
475
+ - **是什么**:`docs/usage-guide.md` §7 补两行 —— ① 缺省**不裁剪**上下文:不注入 `contextPolicy`
476
+ 就一个回合都不裁,长跑成本随**回合数平方增长**(框架给了缝与参考实现 `createBudgetPolicy`,
477
+ 但**不替你决定**,因为裁剪有损);② 回给模型的 `tool_result` **不截断、也没有大小闸**
478
+ (`maxEventChars` / `traceContent` 只管**记账侧**),长跑需要一个**宿主侧的输出上限**,
479
+ 否则下一回合以 api 错误收尾。
480
+ - **为什么该补**:§7 是「使用者判断『我能不能用这个框架』的唯一依据」,这两条**有病例、
481
+ 可复现、可量化**却没登记。更糟的是第 ② 条**已登记了一半**(「`contextPolicy` 不进子循环 ⇒
482
+ 撞上限以 api 错误收尾」)—— **一半的边界比没有更危险**:读表的人会推出「那主循环是安全的」。
483
+ - **病例(TB 4.0 实跑,286 个 trial)**:① 上下文**一次都没被裁剪过**(发生过回落的
484
+ trial = **0 / 286**);单次请求的输入 token 从 ~1000 涨到 **619,119**,且
485
+ `sum(steps.prompt_tokens) == final_metrics.total_prompt_tokens` ⇒ 每回合把整段历史**重发**一遍。
486
+ ② 有一条 PTY 回显洪水在 trace 里落了 **1.69 亿字符**(单条 `tool_result` 的 content;
487
+ JSON 序列化后 1.84 亿、整个 `trajectory.json` **184 MB**)—— 该 trial 共 **9 步**、
488
+ 总输入仅 **10252 token**,洪水恰好在收尾前,**下一步没有真实请求**才侥幸过关。
489
+ - ⚠️ **这两个数不是一回事**(本次差点读错):**trace 里记了多少 ≠ 模型看到了多少**。
490
+ `maxEventChars` / `traceContent` 都在**记账侧**。拿 trace 体积推断上下文之前,先与 usage 对账。
491
+ - **登记形态是 1 pin + 1 gap**:`tool_result` 那条有真门禁 —— `tests/engine/eventChars.test.ts`
492
+ 的用例「回给模型的 tool_result 不受截断影响(截断只在记账侧)」正是为它立的 ⇒ `pin`;
493
+ 缺省不裁剪那条只有**配置读数**(`run-config.test.ts` 钉 `config.contextPolicy === false`),
494
+ 把 `turn.ts` 里的判据改掉它**照旧绿** ⇒ `gap`(「变假时没有用例会红」就是 gap 的定义)。
495
+ - **刻意不做两件事**:① **不写**独立的「工具输出截断配方」—— 目前只有一个使用方(TB),
496
+ 且它的诉求恰恰是「**不要**截断」(评测要原始输出);此刻写配方必然是两边折中的空话
497
+ (本仓判据:先有病例,再有设备)。② **不改**「缺省不裁剪」的行为 —— 改缺省 = 替宿主做
498
+ **有损**决策 + 逼核心变厚,与「只给缝、不给策略」冲突。框架这里是对的,欠的只是说明书。
499
+ - **顺手订正一处长期没人发现的读数**:`docs/guards.md` §1.4 的登记行写着「§7 的 **88 行**」,
500
+ 而它当时就是错的(实际 90)⇒ 改为 **92**。`guards-registry.test.ts` 只查「清单里的路径
501
+ 真实存在」+ 条目密度下限,**不查数字** —— 这类读数没有守卫盯。
502
+ - **去掉一个会腐烂的数**:`examples/terminal-bench/README.md` 原写「`cat` 大文件会让 trace 到
503
+ **几 MB**」,实测上界 **184 MB**(差两个数量级)⇒ 改成**定性 + 现算路径**,
504
+ 而不是换一个新的会腐烂的数。
505
+ - **零语义变更 ⇒ 不新增 `docs/spec.md` §10 节**:按 `CONTRIBUTING.md`「**语义变更**要同步 §10」,
506
+ 本轮只补登记**既存事实**、未改任何行为(`src/` 零改动)⇒ 不写决策条目。
507
+ - 影响面:`docs/usage-guide.md` §7、`tests/docs/boundary-table.test.ts`(`REGISTRY` +2 条 +
508
+ 5 处写死读数)、`docs/guards.md` §1.4、`examples/terminal-bench/README.md`、本节。
509
+ **框架代码零改动。**
510
+
511
+ ### 依赖
512
+
513
+ - 开发依赖:`@anthropic-ai/sdk` → 0.131.0 —— **不影响运行时面**:`src/` 不 import 它,
514
+ 它只在 devDependencies 里做「core 消息类型族 ↔ SDK 结构兼容」那道门禁(`src/index.ts`
515
+ 有说明),而那道门禁在本版是绿的。
516
+ - 官网(不随包发布):`astro` → 7.3.5。构建与产物形状由 `verify-all` 第 8 步
517
+ (`build:website` + 按产物形状核 404 / robots / sitemap / llms.txt)在 PR 阶段验证,
518
+ 真实部署只在 main 上跑 —— 本版已随 main 的 CI 部署通过。
519
+
8
520
  ## [0.10.0] - 2026-09-29
9
521
 
10
522
  > 本版主题(窗口 `0.9.5 → 0.10.0`):**从「跑得起来」到「敢上线、也找得到」**。
@@ -2733,7 +3245,9 @@ toolkit 142 / runtime 55 / engine 403 / container 10 / store 55 —— 全绿。
2733
3245
  首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
2734
3246
  框架本体单包;CLI 独立成包(workspaces)。
2735
3247
 
2736
- [Unreleased]: https://github.com/retrychx/agentia/compare/v0.10.0...HEAD
3248
+ [Unreleased]: https://github.com/retrychx/agentia/compare/v0.10.2...HEAD
3249
+ [0.10.2]: https://github.com/retrychx/agentia/releases/tag/v0.10.2
3250
+ [0.10.1]: https://github.com/retrychx/agentia/releases/tag/v0.10.1
2737
3251
  [0.10.0]: https://github.com/retrychx/agentia/releases/tag/v0.10.0
2738
3252
  [0.9.5]: https://github.com/retrychx/agentia/releases/tag/v0.9.5
2739
3253
  [0.9.4]: https://github.com/retrychx/agentia/releases/tag/v0.9.4
package/README.en.md CHANGED
@@ -13,9 +13,11 @@ choosing a library (how big is the install?): `npm i @migor/agentia` puts exactl
13
13
  `node_modules` and pulls in no vendor SDK. These numbers are recomputed by
14
14
  `tests/docs/scoreboard.test.ts` on every run; hand-written figures rot.
15
15
 
16
- > This is a **summary**. The authoritative and always-current documentation is Chinese:
17
- > [`docs/usage-guide.md`](./docs/usage-guide.md) (API reference, type wiring, known limits) and the
18
- > [website](https://agentia-web.pages.dev). We keep this file short on purpose — a hand-maintained
16
+ > This is a **summary**. The [website](https://agentia-web.pages.dev/en) is **bilingual** — the English
17
+ > pages mirror the Chinese ones section by section — but the authoritative and always-current
18
+ > **long-form** documentation is Chinese:
19
+ > [`docs/usage-guide.md`](./docs/usage-guide.md) (API reference, type wiring, known limits; the same
20
+ > text is served verbatim at `/llms-full.txt`). We keep this file short on purpose — a hand-maintained
19
21
  > full translation drifts.
20
22
 
21
23
  ## Requirements
@@ -148,7 +150,7 @@ One run == one trace (`traceId === runId`), **built in from turn 0** — not a b
148
150
  | Production observability recipes | [`docs/observability.md`](./docs/observability.md) |
149
151
  | Examples (complete app / minimal deploy / observability) | [`examples/`](./examples/) |
150
152
  | Design spec & roadmap | [`docs/spec.md`](./docs/spec.md) · [`docs/roadmap.md`](./docs/roadmap.md) |
151
- | Website · Playground | [agentia-web.pages.dev](https://agentia-web.pages.dev) · [playground](https://agentia-web.pages.dev/playground) |
153
+ | Website · Playground | [agentia-web.pages.dev/en](https://agentia-web.pages.dev/en) · [playground](https://agentia-web.pages.dev/en/playground) |
152
154
 
153
155
  ## Stability & versioning
154
156
 
package/README.md CHANGED
@@ -38,11 +38,11 @@ npm run dev # = agentia dev:本地 inspector 面板
38
38
  ```
39
39
 
40
40
  > 框架**不自动**读 `.env`:脚手架 `src/app.ts` 的 `loadEnvFile()` 负责把它读进 `process.env`。
41
- > 放在**装配模块**(app.ts)而不是启动入口(main.ts),是因为 `agentia dev` 只 import app.ts、
42
- > 从不执行 main.ts —— 写错一侧会让 `npm run dev` 静默读不到 `.env` 而 `npm start` 读得到。
41
+ > 放在**装配模块**(app.ts)而不是任一个入口(`main.ts` / `batch.ts`),是因为 `agentia dev`
42
+ > 只 import app.ts、从不执行入口 —— 写错一侧会让 `npm run dev` 静默读不到 `.env` 而 `npm start` 读得到。
43
43
  > 真实环境变量优先(CI / docker / 命令行永远赢过文件),`loadEnvFile({ override: true })` 才反过来。
44
44
 
45
- > **版本**:`0.10.0`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
45
+ > **版本**:`0.10.2`(`@migor/agentia` 与 `@migor/cli` 均已发布到 npm)。`examples/` **刻意**用
46
46
  > `file:../..` 指向本仓库而不是 npm 版本 —— 跑的是**工作区代码**,理由见
47
47
  > [`examples/README.md`](examples/README.md) 的「依赖说明」。
48
48
 
@@ -444,3 +444,4 @@ npm run e2e # 端到端:CLI 脚手架 → 装配 mock run;再真跑
444
444
  - 完整示例(四类能力 + 三种触发 + 全观测栈):[`examples/complete/`](examples/complete/)
445
445
  - 最小部署示例:[`examples/deploy/`](examples/deploy/)
446
446
  - 官网:[agentia-web.pages.dev](https://agentia-web.pages.dev)([在线 Playground](https://agentia-web.pages.dev/playground) · [文档](https://agentia-web.pages.dev/docs) · [取舍对照:不做什么 / 什么场景该选别人](https://agentia-web.pages.dev/tradeoffs))
447
+ · **英文版** [agentia-web.pages.dev/en](https://agentia-web.pages.dev/en)(导航右侧可切换;首次访问会按浏览器语言自动落到中文或英文)