@migor/agentia 0.9.5 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +740 -1
- package/README.en.md +61 -3
- package/README.md +66 -4
- package/dist/AGENTS.md +54 -13
- package/dist/container/container.js +17 -11
- package/dist/container/container.js.map +1 -1
- package/dist/core/limits.d.ts +12 -5
- package/dist/core/limits.js +4 -4
- package/dist/core/limits.js.map +1 -1
- package/dist/engine/budget.js +14 -0
- package/dist/engine/budget.js.map +1 -1
- 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/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/run.js +14 -2
- package/dist/runtime/run.js.map +1 -1
- package/dist/store/redisStore.d.ts +21 -0
- package/dist/store/redisStore.js +21 -0
- package/dist/store/redisStore.js.map +1 -1
- package/dist/store/store.d.ts +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 +29 -105
- package/dist/transport/async.js +85 -513
- package/dist/transport/async.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/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/task-events.js +8 -1
- package/dist/transport/task-events.js.map +1 -1
- package/package.json +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,743 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.10.1] - 2026-10-05
|
|
9
|
+
|
|
10
|
+
> 本版主题(窗口 `0.10.0 → 0.10.1`):**从「看得见一次 run」到「留得下、比得出」**,外加给两块
|
|
11
|
+
> 长期没有闸门的地方补上闸门。三块 —— ① dev 面板:每次 run 收尾把 trace 落盘,成为
|
|
12
|
+
> `report` / `diff` / `harvest` / `export` 四个命令的现成输入,并支持**并排比两条 run**
|
|
13
|
+
> (含「改一句 prompt 立刻重跑对照」的自动补对);② `packages/trace-view`:补规模棘轮 +
|
|
14
|
+
> 三条最该有的行为断言(树形状 / 错误行 / usage 汇总);③ `docs/usage-guide.md` §7 补登记
|
|
15
|
+
> 两条**有病例、可复现**的长跑边界。
|
|
16
|
+
> **无破坏性变更**;`@migor/agentia` 框架代码**零改动**(本轮发布面里唯一碰到 `src/` 的是
|
|
17
|
+
> 版本常量那一行)⇒ 既有使用者**不需要任何动作**。
|
|
18
|
+
> ⚠️ **一处新增的用户可见文件**:跑 `agentia dev` 会往工程根的 `.agentia/` 追加
|
|
19
|
+
> `traces.jsonl`,**只增不减**(`.agentia/` 已 gitignore;要清是用户自己 `rm`)。
|
|
20
|
+
|
|
21
|
+
### dev 调试工作台:trace 落盘 + 并排 A/B(2026-10-05)
|
|
22
|
+
|
|
23
|
+
- **是什么**:`agentia dev` 的本地面板从「看一条 run 的调用树」扩成「**比两条 run 差在哪**」。
|
|
24
|
+
两件事:① 每次 run 收尾把 trace 追加落盘到 `.agentia/traces.jsonl`;② run 列表支持
|
|
25
|
+
**⌘/Ctrl + 单击**选两条,右栏切**双栏**并排两棵调用树,顶部一条差异摘要
|
|
26
|
+
(run 级 summary + 逐 span 字段级差异)。设计稿 `docs/plans/2026-10-05-dev-workbench.md`。
|
|
27
|
+
- **落盘换来什么**:那个文件的格式**恰好就是** `agentia report` / `diff` / `harvest` /
|
|
28
|
+
`export` 四个命令的输入(`jsonlTraceSink` 的契约)⇒ 面板里跑过的每一次 run,
|
|
29
|
+
立刻变成「哪个能力慢/贵/爱失败」「改 prompt 后轨迹漂没漂」的现成输入。
|
|
30
|
+
开发期最贵的一件事是「拿不到刚才那次 run 的数据」,这一行就解决它。
|
|
31
|
+
- **并排 A/B 的判据(三档,显式不静默)**:能力选择与工作目录是 **app 级**的
|
|
32
|
+
(换它们要重启进程)⇒ 两条跨了能力选择的 run,`runConfigSnapshot` 整个变了,
|
|
33
|
+
diff 出来的差异**多半与「我改的那句话」无关**。所以跨能力 / 跨目录 / 元信息缺失
|
|
34
|
+
都给**显式降级提示**(含可执行的出路),而不是静默给一份没意义的 diff。
|
|
35
|
+
可比时**不显示**提示条 —— 每次都飘噪声等于没有提示。
|
|
36
|
+
- **三条如实标注的边界**:① `traces.jsonl` **只增不减**(`.agentia/` 已 gitignore;
|
|
37
|
+
要清是用户自己 `rm`);② 落盘是**同步写**(`appendFileSync`),大 trace 会占 runner
|
|
38
|
+
事件循环 —— dev 场景单条以百 KB 计,且 `traceLimits.maxEvents` 本来兜上限;
|
|
39
|
+
③ **并排是「事后回看」,不是「同时跑」** —— 单飞闸没解除(解除会连带改变
|
|
40
|
+
「文件变更延后重启」的语义,而那条判据是刻意单源化的),真并发另议。
|
|
41
|
+
- **守卫**:compare 路由三态(顺序 / 可比性 / 归一化)与判据六条都有单测,且做过
|
|
42
|
+
**变异反向验证**(路由顺序反了、判据写死、`rootSpanId` 缺失 —— 三个变异都被咬住)。
|
|
43
|
+
另给 e2e 加了落盘断言(逐行可解析 + **traceId 不重复**:重复行 = sink 被注册两次)。
|
|
44
|
+
- **一处自认的债**:`normalizeForDiff` 存在只因 `diffTraces` 的 `pushAttrDiffs` /
|
|
45
|
+
`pushEventDiffs` 不接受 `undefined`,而 `packages/cli/src/diff.ts` 是框架
|
|
46
|
+
`src/engine/trace-diff.ts` 的**逐字同形移植副本** ⇒ 根治要框架侧一起改
|
|
47
|
+
(`?? {}` / `?? []`),属 `src/` 语义变更。本次取「消费侧归一化」的零核心改动解。
|
|
48
|
+
**零 `src/` 改动** ⇒ 按 `CONTRIBUTING.md` 的判据不写 spec §10。
|
|
49
|
+
- **顺带补的面板测试债**:上面这批面板代码里**能单测的判断已从 HTML 搬进
|
|
50
|
+
`panel-logic`**(`compareView` 三态视图 / `diffSideLabel` 缺侧标签 /
|
|
51
|
+
`diffValueText` 取值 / `diffFetchErrorText` 失败文案)—— 搬**不是**新写,
|
|
52
|
+
是把已存在的行为从「无测试区」挪进 `panel-logic.test.mjs` 的射程。
|
|
53
|
+
⚠️ **剩下的 DOM 接线补不了**(本仓无 jsdom,硬加会引入 DOM 依赖)⇒ 只有
|
|
54
|
+
`scripts/e2e-dev.ts` 间接兜。如实标注,不假装已覆盖。
|
|
55
|
+
- **入库前评审修掉三处**:① **并排态下普通单击切 run 时不复位 DOM** —— 只清了选择,
|
|
56
|
+
右栏与差异摘要**留在屏幕上**(内容还是上一对的)而选中标记已经消失,用户按看到的读
|
|
57
|
+
会张冠李戴。这是本档**唯一用户可见**的缺陷;修法 = 清空必须走 `exitCompare()`,
|
|
58
|
+
并抽出 `resetCompareDom()` 消掉三处逐字重复的复位序列;**另补一条文本层守卫 W4**
|
|
59
|
+
(`state.compare = []` 只许出现在 `exitCompare` 里)—— 面板无 jsdom 单测,
|
|
60
|
+
这是唯一能防它回归的机制。② `refreshCompare` 读不到某条 run 时**静默留白**
|
|
61
|
+
(看起来像「这条没有调用树」,真相是它已被内存 ring 淘汰)⇒ 改为显式说出来。
|
|
62
|
+
③ 删掉只写不读的 `state.diff` 字段 —— 它的注释还承诺了一个**并不存在**的「读取中…」
|
|
63
|
+
渲染。另修:`comparability` 的长 JSDoc 错挂到 `capsKey` 头上(它自己反而没有注释)、
|
|
64
|
+
一条与上一条**同一对 run 同一断言**的死断言(`bothFull`)。
|
|
65
|
+
- **A/B 闭环补最后一跳**:上面那条「改一句 prompt 就能**立刻重跑对照**」此前要手动
|
|
66
|
+
⌘ 点两条才进并排(三步)。现在 —— **已选一条、还没选第二条**时,新 run 一收尾
|
|
67
|
+
就自动补上第二条并进双栏,**并播一条 notice** 说清屏幕为什么变了。判定
|
|
68
|
+
`autoPairTarget` 在 `panel-logic`(6 个断言 + 变异验证),三种情况**刻意不自动**:
|
|
69
|
+
空选择(替用户决定)、已选两条(会挤掉刚选的)、同一条。⚠️ 这是「事后回看」的
|
|
70
|
+
自动化,**单飞闸仍未解除**(真并发要改「文件变更延后重启」那条单源化判据,
|
|
71
|
+
见设计稿 §8)。DOM 接线本身**无自动化覆盖**(无 jsdom),只有 e2e 间接兜。
|
|
72
|
+
|
|
73
|
+
### `packages/trace-view`:补规模守卫 + 补三条最该有的行为断言(2026-10-05)
|
|
74
|
+
|
|
75
|
+
- **规模棘轮**(新增 `test/structure.test.js`):逐文件 `LINE_BUDGET` + **反向**检查
|
|
76
|
+
(表里有、盘上没了 ⇒ 基线该调低,否则登记项失效后守卫「看起来在管」而实际不管)+
|
|
77
|
+
一条「基线表不许有重复键」的自检。**这道守卫买的不是「今天的健康」**(实测
|
|
78
|
+
`src/` 846 行、测试 904 行、体量完全健康),**是「它开始变大时有人被叫醒」** ——
|
|
79
|
+
2026-09-23 刚在 CLI 上治过一模一样的病(`dev.ts` 长到 1148 行、长出隐式状态机,
|
|
80
|
+
靠人工回看才发现)。
|
|
81
|
+
- **三条行为断言**(新增 `test/tree.test.js`,9 个用例):补的是 `renderTrace`
|
|
82
|
+
里**注释最密、却一直没有断言**的三块 ——
|
|
83
|
+
① **树形状**:三层嵌套的树形前缀(`└─` / `├─` / 竖线缩进)与「事件与子 span 按发生
|
|
84
|
+
顺序混排」(拍平成「先全部事件、再全部子 span」会让用户看到因果倒置);
|
|
85
|
+
② **错误行**:失败 span 的类名 / `meta` 带 `error.type` / **原文单独起一行**(只塞
|
|
86
|
+
`title` 等于没显示 —— 首次运行最常见的「没配 API key」就靠这一行定位)、run 根失败
|
|
87
|
+
时根行也标红(已修过的缺陷,防回退)、**正常 span 不加 error 类**(防「三态塌两态」
|
|
88
|
+
导致满屏红);
|
|
89
|
+
③ **usage 汇总**:**只累加 `llm.turn`**(capability span 的 usage 是子 span 聚合,
|
|
90
|
+
重复计入会双算,而页面上看不出异常 —— 最坏的静默错),且**成对**钉「两个 turn 累加成
|
|
91
|
+
两倍」(否则「压根不累加」也能绿);另钉「每行显示自己那份 usage」与「根未结束不收尾」。
|
|
92
|
+
- ⚠️ **新写的断言里三条是我自己写错的**(都被现有代码正确地打红、查证后是断言错不是代码错):
|
|
93
|
+
① 以为 `m1` 是根的独子(实际它还有兄弟 `sibling` ⇒ 只能是 `├─`);
|
|
94
|
+
② 以为「混排」是事件行插到**别的** span 行之间(实际混排发生在**同一个父节点的孩子
|
|
95
|
+
列表**里,事件是所属 span 的子节点);③ 根失败的 status 写在了 trace 顶层
|
|
96
|
+
(渲染器读的是**根 span 自己**的 `status`)。⇒ 三条都已写进注释,说明读的是哪个字段。
|
|
97
|
+
- **两处变异反向验证**(都被咬住):usage 去掉 `llm.turn` 判据 ⇒ 双算 200 被抓到;
|
|
98
|
+
树形缩进恒空 ⇒ 平铺被抓到。**零 `src/` 改动。**
|
|
99
|
+
- **同批复核又修掉两处**:① 结构守卫里那条关于重复键的注释**事实错误** ——
|
|
100
|
+
原文说「本文件是 `.mjs`(ESM 严格模式)⇒ 重复键是 SyntaxError、import 就炸」,
|
|
101
|
+
**实测证伪**:`{ a: 1, a: 2 }` 写进 `.mjs` 是 exit 0、输出 `{ a: 2 }`(ES6 起对象
|
|
102
|
+
字面量的重复键是合法的)。⇒ 那条自检**不是备份,是唯一防线** —— 变异实测:把重复键
|
|
103
|
+
补在**表尾**时「单文件不超基线」那条**照样绿**(后写的值覆盖前者,反而更宽松),
|
|
104
|
+
只有它红。② `tree.test.js` 里一条断言消息夹着**两个 U+FFFD 替换字符** ——
|
|
105
|
+
assert 失败时唯一给线索的那句话是乱码。
|
|
106
|
+
|
|
107
|
+
### 文档 · `§7 已知边界` 补登记两条长跑边界(2026-10-02)
|
|
108
|
+
|
|
109
|
+
- **是什么**:`docs/usage-guide.md` §7 补两行 —— ① 缺省**不裁剪**上下文:不注入 `contextPolicy`
|
|
110
|
+
就一个回合都不裁,长跑成本随**回合数平方增长**(框架给了缝与参考实现 `createBudgetPolicy`,
|
|
111
|
+
但**不替你决定**,因为裁剪有损);② 回给模型的 `tool_result` **不截断、也没有大小闸**
|
|
112
|
+
(`maxEventChars` / `traceContent` 只管**记账侧**),长跑需要一个**宿主侧的输出上限**,
|
|
113
|
+
否则下一回合以 api 错误收尾。
|
|
114
|
+
- **为什么该补**:§7 是「使用者判断『我能不能用这个框架』的唯一依据」,这两条**有病例、
|
|
115
|
+
可复现、可量化**却没登记。更糟的是第 ② 条**已登记了一半**(「`contextPolicy` 不进子循环 ⇒
|
|
116
|
+
撞上限以 api 错误收尾」)—— **一半的边界比没有更危险**:读表的人会推出「那主循环是安全的」。
|
|
117
|
+
- **病例(TB 4.0 实跑,286 个 trial)**:① 上下文**一次都没被裁剪过**(发生过回落的
|
|
118
|
+
trial = **0 / 286**);单次请求的输入 token 从 ~1000 涨到 **619,119**,且
|
|
119
|
+
`sum(steps.prompt_tokens) == final_metrics.total_prompt_tokens` ⇒ 每回合把整段历史**重发**一遍。
|
|
120
|
+
② 有一条 PTY 回显洪水在 trace 里落了 **1.69 亿字符**(单条 `tool_result` 的 content;
|
|
121
|
+
JSON 序列化后 1.84 亿、整个 `trajectory.json` **184 MB**)—— 该 trial 共 **9 步**、
|
|
122
|
+
总输入仅 **10252 token**,洪水恰好在收尾前,**下一步没有真实请求**才侥幸过关。
|
|
123
|
+
- ⚠️ **这两个数不是一回事**(本次差点读错):**trace 里记了多少 ≠ 模型看到了多少**。
|
|
124
|
+
`maxEventChars` / `traceContent` 都在**记账侧**。拿 trace 体积推断上下文之前,先与 usage 对账。
|
|
125
|
+
- **登记形态是 1 pin + 1 gap**:`tool_result` 那条有真门禁 —— `tests/engine/eventChars.test.ts`
|
|
126
|
+
的用例「回给模型的 tool_result 不受截断影响(截断只在记账侧)」正是为它立的 ⇒ `pin`;
|
|
127
|
+
缺省不裁剪那条只有**配置读数**(`run-config.test.ts` 钉 `config.contextPolicy === false`),
|
|
128
|
+
把 `turn.ts` 里的判据改掉它**照旧绿** ⇒ `gap`(「变假时没有用例会红」就是 gap 的定义)。
|
|
129
|
+
- **刻意不做两件事**:① **不写**独立的「工具输出截断配方」—— 目前只有一个使用方(TB),
|
|
130
|
+
且它的诉求恰恰是「**不要**截断」(评测要原始输出);此刻写配方必然是两边折中的空话
|
|
131
|
+
(本仓判据:先有病例,再有设备)。② **不改**「缺省不裁剪」的行为 —— 改缺省 = 替宿主做
|
|
132
|
+
**有损**决策 + 逼核心变厚,与「只给缝、不给策略」冲突。框架这里是对的,欠的只是说明书。
|
|
133
|
+
- **顺手订正一处长期没人发现的读数**:`docs/guards.md` §1.4 的登记行写着「§7 的 **88 行**」,
|
|
134
|
+
而它当时就是错的(实际 90)⇒ 改为 **92**。`guards-registry.test.ts` 只查「清单里的路径
|
|
135
|
+
真实存在」+ 条目密度下限,**不查数字** —— 这类读数没有守卫盯。
|
|
136
|
+
- **去掉一个会腐烂的数**:`examples/terminal-bench/README.md` 原写「`cat` 大文件会让 trace 到
|
|
137
|
+
**几 MB**」,实测上界 **184 MB**(差两个数量级)⇒ 改成**定性 + 现算路径**,
|
|
138
|
+
而不是换一个新的会腐烂的数。
|
|
139
|
+
- **零语义变更 ⇒ 不新增 `docs/spec.md` §10 节**:按 `CONTRIBUTING.md`「**语义变更**要同步 §10」,
|
|
140
|
+
本轮只补登记**既存事实**、未改任何行为(`src/` 零改动)⇒ 不写决策条目。
|
|
141
|
+
- 影响面:`docs/usage-guide.md` §7、`tests/docs/boundary-table.test.ts`(`REGISTRY` +2 条 +
|
|
142
|
+
5 处写死读数)、`docs/guards.md` §1.4、`examples/terminal-bench/README.md`、本节。
|
|
143
|
+
**框架代码零改动。**
|
|
144
|
+
|
|
145
|
+
### 依赖
|
|
146
|
+
|
|
147
|
+
- 开发依赖:`@anthropic-ai/sdk` → 0.131.0 —— **不影响运行时面**:`src/` 不 import 它,
|
|
148
|
+
它只在 devDependencies 里做「core 消息类型族 ↔ SDK 结构兼容」那道门禁(`src/index.ts`
|
|
149
|
+
有说明),而那道门禁在本版是绿的。
|
|
150
|
+
- 官网(不随包发布):`astro` → 7.3.5。构建与产物形状由 `verify-all` 第 8 步
|
|
151
|
+
(`build:website` + 按产物形状核 404 / robots / sitemap / llms.txt)在 PR 阶段验证,
|
|
152
|
+
真实部署只在 main 上跑 —— 本版已随 main 的 CI 部署通过。
|
|
153
|
+
|
|
154
|
+
## [0.10.0] - 2026-09-29
|
|
155
|
+
|
|
156
|
+
> 本版主题(窗口 `0.9.5 → 0.10.0`):**从「跑得起来」到「敢上线、也找得到」**。
|
|
157
|
+
> 两条主线 —— ① **把对外承诺从「文档上的一句话」改成「断言」**:稳定性策略 / 三个不发布的包 /
|
|
158
|
+
> CI 运行环境全都写代码核实,README 顶部每一个数字由脚本现算,公开 API 边界由 `exports` 字段
|
|
159
|
+
> (而不是一句承诺)封住;② **补两处对外空白**:「从 demo 到生产」的 `docs/deployment.md`、
|
|
160
|
+
> 官网第一次写出「什么场景该选别人」的 `/tradeoffs` 页。
|
|
161
|
+
> ⚠️ **本版有破坏性变更**:`exports` 收窄后深路径导入会被拒 —— 只有 import 过
|
|
162
|
+
> `@migor/agentia/dist/**` 的使用者需要动手,动作写在下面 ⑭ 那条的「**迁移**」小节。
|
|
163
|
+
> 其余 15 条条目**都不改变既有使用者的行为**。
|
|
164
|
+
|
|
165
|
+
### 工程 · 发版闸门「骨架未填」的判据改成精确锚(2026-09-29 ⑳)
|
|
166
|
+
|
|
167
|
+
- **是什么**:`scripts/release-surface.mjs` 判断「本版 CHANGELOG 是不是还停在 bump 插的骨架」时,
|
|
168
|
+
用的是粗判据 `sec.body.includes('TODO')`。**本次发版当场被它拦下 —— 而且是误伤**:本版 ⑫ 那条的
|
|
169
|
+
正文里合法地写着「发版流程文档里的两个必填 TODO、四步顺序」(它描述的正是 `docs/CONTRIBUTING.md`
|
|
170
|
+
里那两个必填标记)⇒ 一个**已经写完**的版本节被判成「没写」,`check-release` 直接 `exit 1`。
|
|
171
|
+
- **改法**:锚改成 bump **真正插入的那串**标记(`TODO` 紧跟 `(发版)`)—— 它由 `release.mjs` 的 `INSERT_OPS`
|
|
172
|
+
集中生成,永远带 `(发版)`。同文件里 `--allow-pending` 的降级判据一并改:否则 bump 后的自检会把
|
|
173
|
+
「正文里正常提到 TODO」当成 pending 警告。
|
|
174
|
+
- 这是本仓反复出现的那个形态:**守卫测的东西 ≠ 它想测的东西**。粗判据在「正文恰好提到 TODO」时红,
|
|
175
|
+
而它想拦的只有一种情况 —— bump 插的骨架还没被人填。
|
|
176
|
+
- **反向验证 2 条**:① 往 `[0.10.0]` 一节插回那段骨架标记 ⇒ 严格闸门**恰好点名**这一条(`exit 1`);
|
|
177
|
+
② 同一状态下加 `--allow-pending` ⇒ 降级为 ⚠ 警告(`exit 0`,bump 后的预期行为)。
|
|
178
|
+
还原后 `sha256` 逐字节一致。
|
|
179
|
+
- 影响面:`scripts/release-surface.mjs`(两处判据 + 为什么不能用裸 `TODO` 的注释)、本节。
|
|
180
|
+
`tests/scripts/release-scripts.test.ts` **10/10 仍绿** —— 它的两条断言锚的本就是那段骨架标记
|
|
181
|
+
与「输出含 TODO」,精确化之后照样成立。
|
|
182
|
+
|
|
183
|
+
### 文档 · 推广稿重写成「可直接发出去的定稿」(2026-09-29 ⑲)
|
|
184
|
+
|
|
185
|
+
- **是什么**:`docs/promotion.md` 此前是**素材清单**(Product Hunt / X / 短版 / awesome 条目 /
|
|
186
|
+
视频旁白,各一段),且口径是「配合 30 秒演示视频」的草稿 ⇒ 真要发的人还得自己组织成文。
|
|
187
|
+
重写为**八节定稿**:发之前自检 / 中文主稿(长文,可整段粘贴)/ 短版 / X 英文线程 /
|
|
188
|
+
Product Hunt / awesome PR 条目 / 视频旁白 / **评论区预设应答(7 条)**。
|
|
189
|
+
- 补上此前**缺的两块**:① 中文长稿 —— 旧稿只有英文 PH + 中文短版,中文社区**没有可直接发的长文**;
|
|
190
|
+
② 评论区预设应答 —— 发出去后必然被问的七个问题(零依赖怎么调模型 / 与 LangGraph·Mastra·
|
|
191
|
+
Vercel AI SDK 的区别 / `0.x` 敢不敢用 / RAG 怎么办 / 为什么不做日志与存储 / 附属包为什么 404 /
|
|
192
|
+
性能),每条都**指回仓内真源**,不替框架总结立场。
|
|
193
|
+
- **诚实段落进正文**:主稿第六节就是「我劝退你的部分」,并指向官网 `/tradeoffs` —— 与本节
|
|
194
|
+
⑱ 那条同源:选型时最有用的不是优点清单,而是作者愿意把劝退条件写在哪、写多细。
|
|
195
|
+
- **数字纪律**:稿里**只写**与记分牌同源的下限(三处)与结构性事实(4 个装饰器 / Node 18+);
|
|
196
|
+
**体积刻意不写** —— 解包体积每次发布都会小幅变,抄进稿里就是第二个会腐烂的副本
|
|
197
|
+
(需要时报 npm 页上的值,或抄 README 顶部那行 —— 那行由守卫现算)。
|
|
198
|
+
- **代码块真编译过**:`promotion.md` 不在 `code-fences` 守卫的取样面里(取样面是「对外阅读面」的
|
|
199
|
+
固定清单),但它是**被照抄概率最高**的文档,复制出去后没有任何东西盯着它 ⇒ 用一次性探针
|
|
200
|
+
`.workbuddy/probes/verify-promotion-blocks.mjs` 按同一套 tsconfig(`paths` 指向 `src/index.ts`,
|
|
201
|
+
`strict` + `exactOptionalPropertyTypes`)把两个 `ts` 块(装配 / HTTP 宿主)真编译一遍,全过。
|
|
202
|
+
- **反向验证 2 条**:稿里中文、英文各改一处守卫条数 ⇒ 各**恰好点名** `scoreboard` 那条
|
|
203
|
+
(另外两条 README 断言不受影响),还原后 `sha256` 逐字节一致。
|
|
204
|
+
- 影响面:`docs/promotion.md`(重写)、本节。
|
|
205
|
+
|
|
206
|
+
### 官网 · 加一页「取舍对照」(2026-09-29 ⑱,评审 P1-1)
|
|
207
|
+
|
|
208
|
+
- **是什么**:新页面 `/tradeoffs`(`packages/website/src/pages/tradeoffs.astro` + 片段
|
|
209
|
+
`src/fragments/tradeoffs.html`)。评审要的是「**取舍对照**」而不是「**优势对照**」——
|
|
210
|
+
明写**不做什么**、代价是什么、以及**什么场景该选别人**。
|
|
211
|
+
- 六节:定位(刻意只占「运行时 + 可观测」两层)/ **不适用场景** / 刻意不做的事(空白由谁填)/
|
|
212
|
+
代价清单 / 什么时候该选它 / 怎么自己核实。
|
|
213
|
+
- **此前对外表达面从来没有「不适用场景」**(实测 `grep -rn "不适用" packages/website/src
|
|
214
|
+
README.md` ⇒ 0 命中)⇒ 读者只能从「它有什么」反推边界,而**反推出来的边界必然比真实边界宽松**。
|
|
215
|
+
- 页面里每一条「不做」都**指回仓内真源**(`spec.md` §9.3「已锁定」/ `usage-guide.md` 的判据 /
|
|
216
|
+
`no-runtime-deps.test.ts` / `examples/grpc-host/`),不替框架总结立场 —— 与 `deployment.md`
|
|
217
|
+
同一条口径:**决定清单 + 指回真源**,每多抄一份正文就多一处会分叉的口径。
|
|
218
|
+
- **守卫** `tests/docs/tradeoffs-page.test.ts`:页面与片段在 / 含「不适用场景」节(评审验收点)/
|
|
219
|
+
导航 + sitemap + llms.txt **三处都登记**(Nav 要求 ≥2 —— 首页与非首页分支各一个)/
|
|
220
|
+
正文站内锚点真实存在 + 自证。**反向验证 4 条,各恰好点名**,还原后 `sha256` 逐字节一致。
|
|
221
|
+
- 影响面:新增 2 个页面文件、`Nav.astro`、`sitemap.xml.ts`、`llms.txt.ts`、
|
|
222
|
+
`tests/docs/tradeoffs-page.test.ts`(新)、`docs/guards.md` §1.4、本节。
|
|
223
|
+
|
|
224
|
+
### 文档 · 推广稿的「50+ 条守卫」改成现算值并纳入守卫(2026-09-29 ⑰,评审 P1-2)
|
|
225
|
+
|
|
226
|
+
- **是什么**:`docs/promotion.md` 三处写着「50+ invariants / 50+ registered invariants /
|
|
227
|
+
50+ 条守卫」,而 `docs/guards.md` §1 实际是 **99 条** ⇒ **自贬**,而且是那种**没人会发现**
|
|
228
|
+
的自贬(推广稿不是测试、没有门禁)。改成与 README 记分牌同源的下限 `90+`。
|
|
229
|
+
- **光改一次不够** ⇒ 一并纳入 `tests/docs/scoreboard.test.ts`(推广稿是最容易「抄一次就
|
|
230
|
+
再也不更新」的文本:发完就没人回头核)。只钉「守卫条数」这一个数 —— 推广稿里其余数字
|
|
231
|
+
(4 个装饰器 / Node 18+)是**结构性事实**,改了就是产品变了、不是腐烂。
|
|
232
|
+
- 文件头加了一句「这个数别手改 + 发之前跑 `npm test`」,且**刻意不写那个数**(多写一处就
|
|
233
|
+
多一处要同步,而它恰恰是会被守卫点名的字面量 —— 这条是被变异验证逼出来的,见 guards.md)。
|
|
234
|
+
- **反向验证 7 条**(原 4 条 + 推广稿三处各改一处,各恰好点名),还原后 `sha256` 逐字节一致。
|
|
235
|
+
- ⚠️ 评审 P1-2 的另一半是「**执行** promotion.md(发到 Product Hunt / X / V2EX 等)」——
|
|
236
|
+
那一步要账号与发布时机,**不是代码能做的**,留给仓库所有者。
|
|
237
|
+
- 影响面:`docs/promotion.md`、`tests/docs/scoreboard.test.ts`、`docs/guards.md` §1.4、本节。
|
|
238
|
+
|
|
239
|
+
### 文档 · 五个 bench 的数字对外 + 三面守卫(2026-09-29 ⑯)
|
|
240
|
+
|
|
241
|
+
- **是什么**:`README.md` / `README.en.md` / 官网 docs 页各加一节「性能量级」,把仓里五个
|
|
242
|
+
基准(`scripts/bench-*.ts`)的读数搬出来 —— 此前对外文档面**一个性能数字都没有**。
|
|
243
|
+
每个数都配了**形状结论**(差多少倍 / 往返几次 / 哪一笔跟什么成正比)与**复跑命令**。
|
|
244
|
+
- ⚠️ **这一节的射程要如实说清**:五个基准是**计时类**的,本仓早就定了「不进 verify-all / CI,
|
|
245
|
+
要看时跑」(`docs/plans/2026-09-22-dev-debug-loop.md`)⇒ **毫秒数守不住**(机器一换就变、
|
|
246
|
+
且零信号),守卫也**刻意不去跑基准**。所以表格里明确写着「**毫秒不是承诺**」。
|
|
247
|
+
真正恒定的是**形状**(常驻 runner 换能力选择只付 ≈8 ms 而非 ≈4 s ⇒ ~500×;MCP 冷 280 ms /
|
|
248
|
+
热 0.3 ms ⇒ ~900×;sqlite 到期索引 0.1 ms vs 全表 62 ms ⇒ ~600×;trace 不截断是大出参档的
|
|
249
|
+
13.6×、小出参档只差 1.0× ⇒ 代价**按出参字节**计)。
|
|
250
|
+
- **守卫守的是「读者能不能自己复算出那一列」**:五个基准每个都有可跑的复跑命令且脚本真实
|
|
251
|
+
存在 + 三面都写了「不是承诺」+ 三面都写了「刻意不进 CI」+ `BENCH_SCRIPTS` 与 `scripts/` 目录
|
|
252
|
+
双向相等。**反向验证过 5 条,各恰好点名**,还原后 `sha256` 逐字节一致。
|
|
253
|
+
- 影响面:`README.md`、`README.en.md`、`packages/website/src/fragments/docs.html`(+ 侧栏锚点)、
|
|
254
|
+
`tests/docs/bench-numbers.test.ts`(新)、`docs/guards.md` §1.4。**框架代码零改动。**
|
|
255
|
+
|
|
256
|
+
### 文档 · `docs/deployment.md` 上线清单 + 守卫(2026-09-29 ⑮)
|
|
257
|
+
|
|
258
|
+
- **是什么**:从「跑通 demo」到「推上生产」之间那篇文档 —— store 选型(含「单宿主写者」前提)、
|
|
259
|
+
鉴权边界(`/healthz` 与 `/metrics` 不鉴权)、优雅停机的四步顺序(宽限 > drain 超时 > store 后关)、
|
|
260
|
+
预算旋钮、多副本写路径协调、回滚(含「旧代码读新数据不保证」的如实口径)、§10 上线自检复选框。
|
|
261
|
+
- **三条口径**:只做「决定清单 + 指回真源」,不重复 usage-guide §7 与 `examples/deploy`;
|
|
262
|
+
每条来自已核实的仓内事实(`redisStore.ts` 头注 / `record.ts` 读时归一层),不是通用模板;
|
|
263
|
+
完备性由 `tests/docs/deployment.test.ts` 守(节骨架 / 路径真实 / 五个事故级机制词 /
|
|
264
|
+
「不鉴权」如实锚 / 复选框下限;5 条变异反向验证)。
|
|
265
|
+
|
|
266
|
+
### 变更 · 加 `exports` 字段:公开 API 边界从「文档承诺」变成「机制」(2026-09-29 ⑭)
|
|
267
|
+
|
|
268
|
+
**可观测面变更**(`0.x` 期间 minor 可含破坏性变更 —— 你要做的动作写在下面「迁移」一节)。
|
|
269
|
+
|
|
270
|
+
**起因**:「上生产 × 推广」评审的 `P2-2`。`README.md` / 官网的「稳定性与版本策略」节早就写明
|
|
271
|
+
「**不**承诺深路径导入(`dist/**`)」—— 但两个**已发布**包的 `package.json` 里**没有 `exports`
|
|
272
|
+
字段**,于是那句话是一句**没有机制的话**:2026-09-29 实测 `import('@migor/agentia/dist/index.js')`
|
|
273
|
+
**真的能进**(`require('@migor/agentia')` 也照常拿到 81 个导出)。任何一个这么写的使用者都会把
|
|
274
|
+
内部模块路径当成 API —— 一次重构就把他打碎,而且**没有任何信号**。这正是本仓反复记的那个形态:
|
|
275
|
+
**承诺写在文档里、没有门禁**(`docs/guards.md` 头注)。
|
|
276
|
+
|
|
277
|
+
**改动**:
|
|
278
|
+
|
|
279
|
+
- `@migor/agentia` 新增 `exports`:根入口 `{"types" → ./dist/index.d.ts, "default" → ./dist/index.js}`
|
|
280
|
+
(`types` **排第一** —— TS 按书写顺序匹配条件)+ `"./package.json"`;
|
|
281
|
+
- `@migor/cli` 新增 `exports`,**只放开 `"./package.json"`**。
|
|
282
|
+
|
|
283
|
+
**为什么用 `default` 而不是 `import` / `require` 两条**:本包**只有 ESM 一种产物** —— 写两条会暗示
|
|
284
|
+
存在 CJS 构建。`default` 同时接住 `import` 与 `require`(后者靠 Node 的 `require(esm)`),
|
|
285
|
+
**行为与改动前逐字一致**(Node 18 上照样是 `ERR_REQUIRE_ESM`)。
|
|
286
|
+
|
|
287
|
+
**为什么 `@migor/cli` 的 `exports` 只留 manifest**:它的公共面是**可执行文件**(`bin.agentia`),
|
|
288
|
+
不是模块 —— 入口 `packages/cli/src/cli.ts` 末尾是 `process.exitCode = main(process.argv.slice(2))`
|
|
289
|
+
(**顶层副作用,没有 main 守卫**)⇒ 给 `"."` 等于承诺「import 这个包会直接跑 CLI」。只留 manifest
|
|
290
|
+
(工具读清单的常规请求),其余封死。
|
|
291
|
+
|
|
292
|
+
**守卫** `tests/architecture/package-exports.test.ts`(登记 `docs/guards.md` §1.4):四类判据 ——
|
|
293
|
+
① 两个已发布包都声明 `exports` 且放开 `./package.json`;② 根入口 `types` 排第一、目标与顶层
|
|
294
|
+
`main`/`types` 指向同一文件(`./` 前缀归一后比较 —— 两侧写法天然不同);③ 除根入口与 manifest 外
|
|
295
|
+
**不开放任何子路径**,且目标都在 `./dist/**`(源码不进 tarball)+ `files` 含 `dist` 的自证;
|
|
296
|
+
④ **真解析**:自建临时夹具(真 manifest 的逐字拷贝 + 占位文件)用 `require.resolve` 跑一遍 Node
|
|
297
|
+
的解析算法 —— 根入口/manifest 必须解析成功、深路径必须报 `ERR_PACKAGE_PATH_NOT_EXPORTED`。
|
|
298
|
+
⚠️ 刻意**不用符号链接指向仓库**(那会让夹具依赖 `dist/` 已构建,且批量删会被环境的安全删除垫片拦下)。
|
|
299
|
+
|
|
300
|
+
**反向验证 5 条(各恰好点名,还原后 `sha256` 逐字节一致)**:摘掉根包 `exports` ⇒ 四条全红;
|
|
301
|
+
`types` 挪到 `default` 之后 ⇒ 只红「types 排第一」那条;加一条 `"./dist/*"` ⇒ 红「不开放子路径」+
|
|
302
|
+
「真解析」;CLI 摘掉 `./package.json` ⇒ 红 ①③④;把 `default` 指到别的文件 ⇒ 红 ②④。
|
|
303
|
+
(①②③ 是**文本**判据、④ 是**行为**判据 —— 后者是前者的兜底:`exports` 的错法有一半是形态对、
|
|
304
|
+
指向错。)
|
|
305
|
+
|
|
306
|
+
**实测(真发布形态:真 pack → 真装进空项目 → 真跑)**:`import`/`require` 根入口各 **81 个导出**
|
|
307
|
+
(与改动前**同数** ⇒ 导出面零变化);`require.resolve('@migor/agentia')` → `dist/index.js`;
|
|
308
|
+
`@migor/agentia/package.json` 与 `@migor/cli/package.json` 可解析;`@migor/agentia/dist/index.js`、
|
|
309
|
+
`@migor/agentia/dist/engine/loop.js`、`@migor/cli`、`@migor/cli/dist/cli.js` **四条全被拒**
|
|
310
|
+
(`ERR_PACKAGE_PATH_NOT_EXPORTED`);装出来的 CLI `cli.js --version` 与 PATH 上的 `agentia --version`
|
|
311
|
+
都是 `0.9.5`(bin 解析不经过 `exports`)。CI 的 `scripts/e2e-cli.ts` 第 9 步本来就在跑这条链。
|
|
312
|
+
|
|
313
|
+
**迁移**
|
|
314
|
+
|
|
315
|
+
只有一种情况需要你动手:**你 import 过 `@migor/agentia` 的深路径**(例如
|
|
316
|
+
`import … from '@migor/agentia/dist/engine/loop.js'`)。改成从根入口导入 ——
|
|
317
|
+
公开面一直是 `src/index.ts` 的具名导出,深路径**从来不在承诺范围内**,只是此前没被封。
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
// 改前(现在会抛 ERR_PACKAGE_PATH_NOT_EXPORTED)
|
|
321
|
+
import { runAgent } from '@migor/agentia/dist/engine/loop.js';
|
|
322
|
+
// 改后
|
|
323
|
+
import { runAgent } from '@migor/agentia';
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`@migor/cli` 是**命令行工具**,不受影响。读清单的 `…/@migor/*/package.json` 也照旧可用。
|
|
327
|
+
|
|
328
|
+
**登记**:`docs/guards.md` §1.4、`docs/spec.md` §10 ⑭。
|
|
329
|
+
|
|
330
|
+
### 文档 · README 顶部加「实测记分牌」——数字一律脚本现算(2026-09-29 ⑬)
|
|
331
|
+
|
|
332
|
+
**使用者可见行为零变更**(README 顶部一行 + 一条新守卫):框架代码**零改动**。
|
|
333
|
+
|
|
334
|
+
**起因**:「上生产 × 推广」评审的 `P0-2` 与它引用的那条外部判据 —— 当前最大的选型陷阱是
|
|
335
|
+
**GitHub star bias**(「为了 50 行的问题上一个重型多层框架」)。本仓的答案正是
|
|
336
|
+
「**1 个包 / 0 运行时依赖 / ≈2 MB**」,而这个数字 `grep -nE "2\.7|1 个包" README.md` ⇒ **0 命中**:
|
|
337
|
+
README 里只有形容词「零依赖」。**形容词不参与比较,数字才参与。**
|
|
338
|
+
|
|
339
|
+
**改动**:`README.md` 与 `README.en.md` 的品牌句之后各加一行记分牌
|
|
340
|
+
(`**1 个包 · ≈2 MB · 0 运行时依赖 · 90+ 条守卫**`),并新增 `tests/docs/scoreboard.test.ts`
|
|
341
|
+
(登记在 `docs/guards.md` §1.4)把它钉住 —— 评审原文要求「**必须脚本现算,不许手写**」。
|
|
342
|
+
|
|
343
|
+
**三条口径(都不是随手定的)**:
|
|
344
|
+
|
|
345
|
+
1. **「会涨的数」用下限**(`90+ 条守卫`,断言 `count >= 90`)—— 精确值会让「每加一条守卫都要改
|
|
346
|
+
README」成为绕开守卫的理由(本仓已知的失效模式);下限是可机械复算的,且只在**掉下来**时咬人。
|
|
347
|
+
2. **体积按「四舍五入到整数 MB」判定**,让数字**自带精度**:`≈2 MB` 覆盖 1.5–2.5 MB;
|
|
348
|
+
写 `2.1 MB` 会因几十 KB 的正常增长而红 —— 那不是信号,是噪声。
|
|
349
|
+
3. **不把 `usage-guide §7` 的已知边界条数搬上来**:那个数已有专属守卫(`boundary-table.test.ts`
|
|
350
|
+
的行集合对拍 + 三处读数断言),再放一份 = 造**第二个会腐烂的副本**,且两处口径必然分叉
|
|
351
|
+
(§7 首列是散文,归一化规则只为那张表定义)。
|
|
352
|
+
|
|
353
|
+
⚠️ **先定口径再写数**:评审里那个「2.7 MB」量的是 `du -sh node_modules`(含文件系统块分配),
|
|
354
|
+
而 npm 页上显示的是 `dist.unpackedSize`(`npm pack --dry-run --json` 现算 = **2.10 MB**)。
|
|
355
|
+
两者差 ~30% ⇒ 数字落纸之前**必须先选口径**,否则记分牌自己就是一句不可比的假话。
|
|
356
|
+
|
|
357
|
+
**代价如实标注**:`npm pack` 在本机约 **11s**(I/O 为主,`--offline` / 关 notifier 都压不动)
|
|
358
|
+
⇒ 守卫里**记忆化**,整轮只真跑一次。**不用自己遍历 `dist` 加字节**:实测差 ~1.5%
|
|
359
|
+
(自己数 2.08 MB vs npm 2.10 MB),而复刻 npm 的选文件规则(`files` 与 `.npmignore` 的交互)
|
|
360
|
+
是一件会静默失准的事。
|
|
361
|
+
|
|
362
|
+
**登记**:`docs/guards.md` §1.4、`docs/spec.md` §10 ⑬。
|
|
363
|
+
### 工程 · CI 的 Node 上沿跟到当前 LTS + 补上发版流程文档(2026-09-29 ⑫)
|
|
364
|
+
|
|
365
|
+
**使用者可见行为零变更**(CI 配置 + 文档):框架代码**零改动**。
|
|
366
|
+
|
|
367
|
+
**起因**:「上生产 × 推广」评审里那条 `A2` —— **CI 的上沿停在 Node 22,而当前 LTS 已经是 24**。
|
|
368
|
+
当时的 README / README.en / CONTRIBUTING / `usage-guide.md` 四处都写着「CI 在 18/20/22 上守」:
|
|
369
|
+
**那句话是真的**,问题是①上沿没跟 LTS 走;②**没有任何东西盯着这四处散文**(矩阵一改就静默腐烂)。
|
|
370
|
+
|
|
371
|
+
**改动**:`.github/workflows/ci.yml` 里四个主 job(`verify` / `lint` / `e2e-mcp` / `deploy-website`)
|
|
372
|
+
的 `node-version` 由 `'22'` 改成 **`'24'`**,并把**覆盖规则**写进 ci.yml 顶部:
|
|
373
|
+
`import-floor` 的 matrix 只覆盖 `engines` 声明的**下限**,其余 job 跟**当前 LTS** 走
|
|
374
|
+
⇒ 覆盖集合 = **18 / 20 / 24**;四处散文同步改成这句(`docs/usage-guide.md` 的已知边界表也复述了一处)。
|
|
375
|
+
|
|
376
|
+
> ⚠️ **为什么不顺手把 24 加进 `import-floor` 的 matrix**:**matrix 取值会进必需检查名**
|
|
377
|
+
> (分支保护里现在钉的是「导入下限(Node 18)」与「(Node 20)」)。增删取值 = 改仓库的分支保护配置,
|
|
378
|
+
> 否则新取值那条不是必需检查、旧取值那条永远等不到。所以「跑下限」与「跟上沿」**刻意分成两件事**,
|
|
379
|
+
> 并把这条坑写在 ci.yml 顶部(此前只写在 `AGENTS.md` 的会话记录里)。
|
|
380
|
+
|
|
381
|
+
另:`publish` 目前仍是**本机手动**两条命令(顺序「发布 → 合并 → 打 tag」,理由见 `release.mjs` 头注)。
|
|
382
|
+
`npm publish --provenance` 只支持在 CI 里生成 ⇒ 本轮**没有动它**,作为独立议题留下(要动就要在
|
|
383
|
+
GitHub Actions 里加发布工作流 + 一个仓库 secret,那是仓库所有者的运维决定)。
|
|
384
|
+
|
|
385
|
+
**补文档**:`CONTRIBUTING.md` 新增「**发版流程**」一节(`release.mjs bump/tag` 两条命令、
|
|
386
|
+
两个必填 TODO、四步顺序与**为什么不能换**、以及几条纪律)。此前 CONTRIBUTING 里
|
|
387
|
+
`grep -niE "release|发布流程|version"` ⇒ **0 命中** —— 一条对外可见的流程却没在任何面向人的文档里。
|
|
388
|
+
|
|
389
|
+
**登记**:`docs/spec.md` §10 ⑫。
|
|
390
|
+
### 文档与守卫 · 把「对外承诺」变成可执行的断言(稳定性策略 / 三个不发布的包 / CI 运行环境)(2026-09-29 ⑪)
|
|
391
|
+
|
|
392
|
+
**使用者可见行为零变更**(纯文档 + 一条新守卫):框架代码**零改动、零新依赖**。
|
|
393
|
+
|
|
394
|
+
**先说结论(三次误报的定性收口)**:`@migor/trace-view`、`@migor/agentia-observability`、
|
|
395
|
+
`@migor/agentia-eval-gate` **三个都不发布到 registry** —— 三个 `package.json` 都是
|
|
396
|
+
`"private": true`,**这是机制、是刻意的**(`roadmap.md` 早已写明「不进 npm,不是待修项」;
|
|
397
|
+
`eval-gate/README.md` 写明「这是宿主的发布流程,框架不内建」)。⇒ 正确动作不是「发布它们」,
|
|
398
|
+
而是**把文档口径补齐、再用守卫钉住**。
|
|
399
|
+
|
|
400
|
+
**新增 `README.md`「稳定性与版本策略」节**(+ `README.en.md` 对应节 + 官网 docs 页 `id="versioning"` 一节):
|
|
401
|
+
|
|
402
|
+
- **`0.x` 的变更策略**:minor 可以含破坏性变更,但**必须留痕** —— `CHANGELOG.md` 对应版本里
|
|
403
|
+
至少要有一段专门写它(小节标题含「迁移」或「破坏性变更」),写清「你要改什么」。
|
|
404
|
+
⚠️ **如实标注**:这条纪律是**逐步收紧**的 —— 同一个意思历史上有三种形态(`### 迁移` /
|
|
405
|
+
`### 破坏性变更 · …`(`0.9.0` 脚手架模板)/ 行内 `**迁移**:无`(`0.8.1`~`0.8.3`)),
|
|
406
|
+
所以文档给的枚举命令是**下限**而非全集。
|
|
407
|
+
- **承诺范围的四行表**:公共 API(`src/index.ts` 的具名导出 / CLI 命令行面)· 行为(以「迁移」小节为界)·
|
|
408
|
+
运行环境(`engines.node` + CI 覆盖的每个版本)· 依赖(运行时零第三方依赖,有守卫)——
|
|
409
|
+
各自对应「**不**承诺什么」(深路径导入 / `examples/*` / Deno·Bun·edge / devDependencies)。
|
|
410
|
+
- **1.0 的三条门槛**:`spec.md` §11 开放项逐条落地或明确划掉 · `guards.md` §2「待守」清空 ·
|
|
411
|
+
公共导出面连续 3 个 minor 无破坏性变更。
|
|
412
|
+
- **刻意不写死数字**:不用「N 个版本里只有 M 个带迁移小节」这种会腐烂的读数,改为给
|
|
413
|
+
**现数命令**;而**那条命令本身也由守卫自证**(必须真在 `CHANGELOG.md` 上跑得出结果 ——
|
|
414
|
+
文档给一条零命中的命令,效果与写错数字一样)。
|
|
415
|
+
|
|
416
|
+
⚠️ **落地前回源核对抓到的真错**(写下来是因为它就是「为什么会有那条命令自证」的由来):初稿写的是
|
|
417
|
+
「**没有「迁移」小节的版本,就是不需要你改代码的版本**」,配 `grep -nE '^#{3,4} .*迁移'` ——
|
|
418
|
+
**这句话是假的**:同一个意思历史上有三种形态(`### 迁移` / `### 破坏性变更 · …`(`0.9.0`)/
|
|
419
|
+
行内 `**迁移**:无`(`0.8.1`~`0.8.3`)),只认第一种会漏掉 `0.9.0`(**它真的有破坏性变更**)
|
|
420
|
+
与 `0.8.2` 那句「唯一需要动作的是 `maxRetries`」。⇒ 改成「至少要有一段专门写它」+ 如实标注
|
|
421
|
+
「这条纪律是逐步收紧的、那条命令是下限」,并顺手把**命令的非空**也钉成断言。
|
|
422
|
+
|
|
423
|
+
**补齐「怎么拿到它」(此前只有 observability 一支写全)**:`examples/eval-gate/README.md` 与
|
|
424
|
+
`docs/eval-gate.md` 现在都明写「**没发布到 registry,`npm i` 会 404**;把目录拷进工程、以
|
|
425
|
+
`"file:…"` 引入,或直接照 `src/gate.ts` 抄走」;`examples/observability/README.md` 也在自己的
|
|
426
|
+
文档里复述了 404(此前只说「沿用 `trace-view` 的同一套办法」)。
|
|
427
|
+
|
|
428
|
+
**新增守卫** `tests/docs/stability.test.ts`(登记在 `docs/guards.md` §1.4):
|
|
429
|
+
|
|
430
|
+
1. **不发布的包不得漏写口径** —— 扫 `根 + packages/* + examples/*` 的 `@migor/*` 包,
|
|
431
|
+
`private: true` 的集合与文件内的登记表**双向相等**(幽灵与漏登都报;`@migor/website` 走
|
|
432
|
+
带理由的豁免),再逐包断言 `private === true` + 入口文档写了「不发 npm」+ 写了「怎么拿到」;
|
|
433
|
+
2. **稳定性承诺三面互为镜像** —— `README.md` / `README.en.md` / 官网 docs 页各抽节,断言同一组
|
|
434
|
+
承诺锚点与 1.0 的三条门槛都在(三面**刻意各写一份**,代价由这条守卫兜底);
|
|
435
|
+
3. **CI 的 Node 集合 == 四份文档写的集合** —— 从 `ci.yml` 抽实际值(matrix + 各 job 的
|
|
436
|
+
`node-version`),与 `README.md` / `README.en.md` / `CONTRIBUTING.md` / `docs/usage-guide.md`
|
|
437
|
+
里复述的那串版本号**集合相等**,并断言 `engines.node` 的下限**真在 CI 里被跑到**;
|
|
438
|
+
`usage-guide.md` 是权威文档,它的 §7 已知边界表复述了同一句话 ⇒ 射程覆盖它才算数;
|
|
439
|
+
4. **文档里那条「数一数」命令必须真跑得出结果** —— 给一条零命中的命令,效果与写错数字一样。
|
|
440
|
+
|
|
441
|
+
**反向验证 6 条**(各恰好点名):版本句改 `18/20` ⇒ 只剩集合相等那条红;eval-gate 摘
|
|
442
|
+
`private` ⇒ 集合相等 + 机读 private 两条红;官网节改名 ⇒ 三面镜像三条红;eval-gate README
|
|
443
|
+
**整段**摘掉「先看这条」⇒ 只红「入口文档说了不发 npm」那条;那条「数一数」命令改成零命中
|
|
444
|
+
⇒ 只红命令自证那条;`usage-guide.md` 的复述处改成 `18/20/21` ⇒ 红「四份文档 == ci.yml」
|
|
445
|
+
那条(把射程从三处扩到四处时补做的 —— 扩射程必须配一次变异)。
|
|
446
|
+
|
|
447
|
+
⚠️ **一条教训**:④ 的**第一版变异只删掉一句、结果全绿** —— 同一件事在那一节里出现不止一次
|
|
448
|
+
(「不发布的包」/「别 `npm i`」)。**变异要摘干净**,否则测的是「这个词还在不在」,
|
|
449
|
+
不是「这件事还说没说」。
|
|
450
|
+
|
|
451
|
+
**登记**:`docs/guards.md` §1.4、`docs/spec.md` §10 ⑪。
|
|
452
|
+
|
|
453
|
+
### 测试与工具 · 补上「完整装配 × 时间维度」这一格 + examples 清单守卫(2026-09-29 ⑩)
|
|
454
|
+
|
|
455
|
+
**使用者可见行为零变更**(新增一个手跑脚本 + 一条新守卫 + 一处文档订正):框架代码**零改动**。
|
|
456
|
+
|
|
457
|
+
**起因**:竞争力评审里那句「没有一个基于框架的复杂 demo 长期运行验证」,查下来**一半对** ——
|
|
458
|
+
长期运行验证**有**(`scripts/e2e-soak.ts`,2 小时档实测 811 万请求、内存有界与否皆有断言),
|
|
459
|
+
完整装配的 demo 也**有**(`examples/complete`),但**两者的射程不重叠**:`e2e-soak` 的 import 面
|
|
460
|
+
只有 `createOpenAIClient` / `metricsSink` / `runAgent` / `AgentTool`(**无** createApp / 容器 /
|
|
461
|
+
store / scheduler / AsyncRunner / 恢复扫描)⇒ 压的是**无状态单点**;而跑完整装配的
|
|
462
|
+
`e2e-examples` 是**线性脚本、只跑一轮**。⇒ **「完整装配 × 时间维度」这一格是空的。**
|
|
463
|
+
|
|
464
|
+
**新增 `scripts/e2e-soak-app.ts`**(`npm run e2e:soak:app`;黑盒——只走 HTTP 与子进程,
|
|
465
|
+
不 import 框架):起 `examples/complete` 的 dist + 本地假 OpenAI 端点(五类故障注入),
|
|
466
|
+
中途用假端点**闸门**把任务精确停在 running 再 SIGKILL。七条断言:不丢(全到终态)/ 不重
|
|
467
|
+
(幂等簿记)/ 猝死续跑(同库重启真把活干完)/ 故障按比例落地 / 定时触发真在派 / 干净退出 /
|
|
468
|
+
DB 每任务字节数有界。
|
|
469
|
+
|
|
470
|
+
**读数**(30s × 6 并发):4452 个任务**全部终态**(succeeded=4286 / failed=166)、**零 stuck**、
|
|
471
|
+
幂等簿记准、猝死续跑 5 个且都跑完、故障对账 **335 vs 336**、DB **39.1MB**(≈ 9.0KB/任务)。
|
|
472
|
+
8s 短档**重复跑 3 次**同样全绿 —— 参见下面那条关于「抖动」的说明。
|
|
473
|
+
|
|
474
|
+
⚠️ **一条比判据更值得记的事:变异验证全过,也不等于断言可靠。**
|
|
475
|
+
初次落盘时 5 条变异全部「各恰好点名那条」,看着已经足够扎实;但其中 ③(闸门任务续跑后
|
|
476
|
+
必须 `succeeded`)是**概率性**的 —— 续跑那次模型调用仍走故障注入,3% 的不可重试 400 抽中就
|
|
477
|
+
假红(30s 首跑绿是运气)。**8s 档重复跑**时才抓到。⇒ 修法:给假端点加 `pauseFaults()` /
|
|
478
|
+
`resumeFaults()`,**只**在「等闸门任务跑完」那一小段关掉注入(判的是恢复,不是运气),并补一条
|
|
479
|
+
**自证**:窗口之后注入数必须高于窗口前读数,否则「忘了重开」会让阶段 B 静默变成无故障负载。
|
|
480
|
+
阶段 A / B 的几千个请求照旧吃满注入,④ 在那里对账 —— **注入机制的存活性没有因此放松**。
|
|
481
|
+
|
|
482
|
+
⚠️ **两条判据上的新认识**(都写进脚本头注;依据不同,别混写):
|
|
483
|
+
① 装配层里「一个不可重试故障**恰好**杀死一个 run」**不成立**——依据是**代码语义**(子能力的失败
|
|
484
|
+
由能力层包成 `is_error` 回主循环、**不杀 run**,见 `src/engine/loop.ts` / `src/engine/spec.ts`;
|
|
485
|
+
本例装配里真有子 agent 与 skill,而假端点服务的**所有**模型调用都可能是 400),再加上猝死窗口内
|
|
486
|
+
被续跑成功的任务会把那次失败洗掉 ⇒ 本脚本用量级判据,**不是** `e2e-soak` 那种逐笔对账
|
|
487
|
+
(实测 335 vs 336 已经很接近,但**接近不等于恒等**,不该断言恒等式);
|
|
488
|
+
② 故障注入是**按请求**的,而请求同时来自同步 `/run` 与异步任务 ⇒ **两边都要统计**——这条是
|
|
489
|
+
**实测**出来的:只对账异步那一半会得出「注入数 > 失败数」的假结论(首跑 129 vs 267 就是这么来的)。
|
|
490
|
+
|
|
491
|
+
**顺带修掉一处腐烂读数**:`examples/README.md` 那张表**此前零守卫**,已烂成三个互不相同的
|
|
492
|
+
读数——文案写「四个示例」、表 5 行、目录 6 个(`eval-gate` 整行缺席);依赖说明那段的
|
|
493
|
+
「三个应用示例」也对不上实况(六个**全部**在用 `file:../..`)。已补齐,并**不再写死数字**。
|
|
494
|
+
|
|
495
|
+
**新增守卫** `tests/docs/examples-table.test.ts`:表 ↔ 目录**双向相等** + 每行链接指向真实目录
|
|
496
|
+
+ 条目密度下限(解析器退化成 0 行时红)。反向验证 2 条(删掉 `eval-gate` 那一行 / 加一行幽灵
|
|
497
|
+
目录),各恰好点名那条。
|
|
498
|
+
|
|
499
|
+
**登记**:`docs/guards.md` §1.4 两行(含反向验证读数)、`docs/spec.md` §10 ⑩。
|
|
500
|
+
|
|
501
|
+
### 测试与文档 · 文档围栏代码块真编译(竞评收官:换尺子)+ 三处「照抄会坏」的片段修正(2026-09-29 ⑨)
|
|
502
|
+
|
|
503
|
+
**使用者可见行为零变更**(纯文档 + 一条新守卫):框架代码**零改动**。
|
|
504
|
+
|
|
505
|
+
**发现方式**:产品竞争力评审走到「说不出还有什么缺口」时换的尺子 —— 不再问「缺什么」,
|
|
506
|
+
改问「**已经写下来的东西是不是真的**」,取样对象取**代码围栏块**(读者会逐字照抄),
|
|
507
|
+
因为它**恰好不在既有守卫面上**(`usage-guide.test.ts` 只收表格行、`run-output-shape.test.ts`
|
|
508
|
+
只钉 `.finalText` 一种形状)。实测:88 个围栏块 / 22 个含具名导入的 ts 块 ⇒ **3 个照抄坏掉**,
|
|
509
|
+
且**没有任何一条是新引入的 —— 是从未被检查过**:
|
|
510
|
+
|
|
511
|
+
1. `README.md` 的 `scheduler.every(60_000, '巡检一次', { idempotencyKey: 'patrol' })` ——
|
|
512
|
+
`ScheduleEveryOptions` 里没这个键,真名是 **`idempotencyPrefix`**。TS 报 `TS2353`;
|
|
513
|
+
**JS 用户静默无幂等**,而示例上方那句注释正写着「幂等键去重」。⚠️ 同一块上一行的
|
|
514
|
+
`runner.submit(…, { idempotencyKey })` 是**对的** —— 两个同名旋钮,一个在一个不在。
|
|
515
|
+
2. `docs/usage-guide.md` 的 `ctx!.deferUntil(due)` —— `ToolRunContext.deferUntil` 是**可选成员**,
|
|
516
|
+
`!` 只消掉了 `ctx` 的 null ⇒ `TS2722`;改为 `ctx?.deferUntil?.(due)`。
|
|
517
|
+
3. `docs/usage-guide.md` 的出站传播片段是**裸方法**(`@Tool` 挂在函数声明上 + 漏必填 `schema`)
|
|
518
|
+
⇒ **不是合法 TS、无法被任何编译器检查**;补成完整的 `class OrderTools { … }`。
|
|
519
|
+
|
|
520
|
+
**新增守卫** `tests/docs/code-fences.test.ts`:把「含 `@migor/agentia` 具名导入」的 ts 块落成文件、
|
|
521
|
+
**一次性交给 `tsc`**(`paths` 指向 `src/`,不依赖先 build,约 0.3s)。三条反假阳性机制缺一不可 ——
|
|
522
|
+
① **语法错会连坐**:实测 `tsc 7`(tsgo)只要程序里存在语法错误就**整体跳过语义阶段**(22 个块只报
|
|
523
|
+
3 条语法错、其余全被吞 ⇒ 守卫会变成静默全绿),故迭代摘出语法错的块并**断言摘掉数为 0**;
|
|
524
|
+
② **省略垫片**(吃掉「名字不存在」、吃不掉「类型形状不对」);③ **省略记号归一成展开 any**
|
|
525
|
+
而不是空容器(后者会造出守卫自己的假阳性)。另有**金丝雀**(故意写错的块必须被报出来)与
|
|
526
|
+
**取样下限 20** 两条自证。
|
|
527
|
+
|
|
528
|
+
**验证**:4 条变异各恰好点名那条(两条真错各 1 条 + 裸方法 1 条 + 金丝雀自证 1 条),
|
|
529
|
+
还原后 `sha256` 逐字节一致。`docs/guards.md` §1.4 登记一行、`docs/spec.md` §10 ⑨ 记决策。
|
|
530
|
+
|
|
531
|
+
**顺带订正一处腐烂读数**:`docs/observability.md` 的日志层那格原写「2026-09-15 实测 9 处
|
|
532
|
+
`console.error/warn`」,现测 **22 处(散在 12 个文件)** ⇒ 不再写死数字,改为给现数命令
|
|
533
|
+
`grep -rn "console\." src/`。
|
|
534
|
+
|
|
535
|
+
**如实记**:`usage-guide.md` 里 `from '@migor/agentia-observability'` 的块首次对拍曾报 `TS2307`,
|
|
536
|
+
但它是**按设计的配方**(`docs/observability.md` 已明写「`npm i` 会 404,这是本地小包」,
|
|
537
|
+
`examples/observability` 真有那些导出)⇒ **不计为缺口**,守卫按 `tsconfig.tests.json` 的
|
|
538
|
+
`paths` 映射过去,不再误报。
|
|
539
|
+
|
|
540
|
+
### 测试与文档 · 时钟回拨下三条时间判据的退化方向(`DEEP-AUDIT` §3.4 的 L0 补栏)(2026-09-29 ④)
|
|
541
|
+
|
|
542
|
+
**使用者可见行为零变更**(纯测试 + 文档):`tests/transport/{approval-policy,wake-policy,resume-policy}.test.ts`
|
|
543
|
+
各补一组「NTP 回拨」用例(共 6 条),钉住三条时间判据在回拨下的**退化方向**;`docs/guards.md` §1.2
|
|
544
|
+
登记一行;`docs/spec.md` §10 ④ 记决策。同轮把 `docs/reviews/2026-09-28/README.md` §4 原来那句
|
|
545
|
+
「它是本仓**质量门禁**的下一步来源」**订正为「测试策略」**(那两节盘点的是测试覆盖缺口,不是闸门待办),
|
|
546
|
+
并把四栏逐栏判过:L0 补用例(本轮)、L1 已由抽件红利覆盖、**L2 定时器注入缝记为「有意不做」**、
|
|
547
|
+
L3 是「运维化」而不是「门禁化」。
|
|
548
|
+
|
|
549
|
+
- **为什么值得记**:三条判据里 `resumeSkipReason` 的**新鲜度档**是回拨下**唯一一条让系统更不安全的
|
|
550
|
+
方向** —— `too-fresh` 变长 ⇒ 判不了主人(异主机 / 升级前旧格式 ownerId)的崩溃孤儿**饿死窗口
|
|
551
|
+
≈ 回拨幅度**(扫描是惰性的,扫不到就再没人扫);而 `approvalExpired` / `timerDue` 只是**推迟**
|
|
552
|
+
(等更久 / 睡更久)。另一条纪律被钉住:`fillTimeoutDenials` 的 `decidedAt` **可以早于**
|
|
553
|
+
`requestedAt` —— 判据层**不许**把负差值钳掉,钳掉回拨就从 trace 上消失了。
|
|
554
|
+
- **验证**:6 条用例 + **5 条变异**逐条亲跑点名(`git checkout --` 还原后 `sha256` 逐字节一致)。
|
|
555
|
+
⚠️ 其中两条新用例**如实记为不独立**(会同时打红既有用例),已写进用例头注与 `guards.md`。
|
|
556
|
+
- **有意不做**:`AsyncRunnerOptions` 上的 `now()` / `timers` 注入缝 —— ① 半假时钟比不注入更坏;
|
|
557
|
+
② 整链可注入是一次跨编排层的时钟抽象(全仓 `Date.now()` 39 处),不是加个可选参数;
|
|
558
|
+
③ 判据是「先有病例,再有设备」。
|
|
559
|
+
|
|
560
|
+
### 测试与工具 · soak 的错误分类判据改成穷尽表(2026-09-29 ⑤)
|
|
561
|
+
|
|
562
|
+
**使用者可见行为零变更**(判据 + 新导出常量):`src/engine/errors.ts` 新增
|
|
563
|
+
`ERROR_TYPES` / `ErrorType`(`classifyError` 的返回类型收窄成 `ClassifiedError`,**不改运行期
|
|
564
|
+
行为**;`SpanError.type` 仍是公共类型上的 `string` —— 收紧它才是破坏性变更,没做)。
|
|
565
|
+
|
|
566
|
+
**这一条是「把 2 小时 soak 真跑起来」拿到的第一份结果,而且那轮是红的** —— 红的是**判据**:
|
|
567
|
+
|
|
568
|
+
```
|
|
569
|
+
AssertionError: 存在未知错误分类:connection
|
|
570
|
+
run:total=7189536 failed=325235(4.52%)thrown=0,吞吐 998.5 run/s
|
|
571
|
+
端点:requests=8114503 … 内存:heap 61.6MB → 69.6MB,rss 峰值 204.7MB(27 个采样)
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
`e2e-soak.ts` 的断言里手写着一份三名字白名单(`api` / `server` / `rate_limit`),漏了框架
|
|
575
|
+
自己的合法类 `connection` —— 719 万 run 里它出现 **1 次**,默认 60 秒的跑法永远撞不到。
|
|
576
|
+
现在判据单源化:`scripts/soak-error-posture.ts` 的 `SOAK_ERROR_POSTURE` 是
|
|
577
|
+
`Record<ErrorType, 规则>` 穷尽表(加一类不表态 ⇒ `TS2741`,每条必须写 `why`),
|
|
578
|
+
`soakErrorVerdict` 对**表外一切**判 `hard-red`;`unknown` / `aborted` / `timeout` 判红,
|
|
579
|
+
注入的四类允许但**分类计数打印出来**(允许 ≠ 不看)。
|
|
580
|
+
|
|
581
|
+
- **验证**:9 条用例(`tests/scripts/soak-error-posture.test.ts`,**不跑 soak 本体**)+ **6 条
|
|
582
|
+
变异**逐条亲跑点名,还原后 `sha256` 逐字节一致;短档 20s 实测全过且新打印行在场。
|
|
583
|
+
- ⚠️ **一条命令教训**:处置表在 `scripts/`,而根 `tsconfig.json` 只 include `src` ⇒ 守它的是
|
|
584
|
+
`npm run typecheck:tests`。第一次跑变异拿 `typecheck` 验,结果是**假绿** —— 已写进头注并补
|
|
585
|
+
承重条件用例。
|
|
586
|
+
|
|
587
|
+
### 修复 · 外部深评施工收口(7 条 + 施工中新发现的第 8 条)+ §7 边界表回填 + 官网注入已知边界表(2026-09-29 ⑥)
|
|
588
|
+
|
|
589
|
+
**来源与性质**:`FIX-READINESS-2026-09-29.md` 给出的施工顺序 1–7(A1 / D1 / B1 / C1 / F1 / F2 / F3
|
|
590
|
+
+ 结构建议① + 官网建议①③),外加施工中**实测发现的第 8 条 C5b**。本轮的共同点仍是前几轮那条:
|
|
591
|
+
**把静默换成有声,把判据换成真能咬人的那种**。结构建议②(`async.ts` 抽审批监督簇 + 行数上限闸)
|
|
592
|
+
按该报告 §8「各自独立 PR」另起一轮,**未随本批**。
|
|
593
|
+
|
|
594
|
+
**使用者可见行为变更(4 处,均为「静默 → 有声」或判据更准)**:
|
|
595
|
+
|
|
596
|
+
- **构造期拒 `NaN` 上限(施工中新发现,C5b)**:`src/core/limits.ts` 的 `BadValuePolicy` 新增第 5 类
|
|
597
|
+
`'rejects-nan'`,`BudgetGuardOptions.maxTotalTokens` / `maxCostUsd` 由 `'none'` 改用它;
|
|
598
|
+
`src/engine/budget.ts` 加 `assertNotNaN`(抛 `RangeError`)。**为什么必须**:闸的判据是 `used > max`,
|
|
599
|
+
而 `x > NaN` 恒为 `false` ⇒ 传了 `NaN` 的闸**静默失效**,方向与「设了个上限」正好相反。
|
|
600
|
+
0 / 负数 / `±Infinity` 仍照旧放行(各有语义),**只拒 `NaN`**。
|
|
601
|
+
- **容器注入判据从「有 `then`」改为「`instanceof Promise`」(D1)**:`src/container/container.ts`。
|
|
602
|
+
原判据会**误伤** knex / mongoose 的 query builder —— 实证两者的 builder **同时**带 `then` 与 `catch`
|
|
603
|
+
(knex `builder-interface-augmenter.js`;mongoose 官方文档明列 `Query.prototype.catch()`),
|
|
604
|
+
所以「`then` 且 `catch`」这个加固方向**无效**;而「忘了 `await`」的产物恒为原生 Promise ⇒
|
|
605
|
+
`instanceof` 精确命中且不误伤。**如实边界**:跨 realm 的 Promise(vm 产物)会漏,已写进注释;
|
|
606
|
+
逃逸口 `useValue: { promise }` 保留。
|
|
607
|
+
- **`RedisTaskStore.close()`(C1)**:`src/store/redisStore.ts` 的 `RedisLike` 加 `quit?(): Promise<unknown>`,
|
|
608
|
+
`RedisTaskStore` 新增**幂等** `close()`(无 `quit` 则 no-op;重复关闭 / 已断连时的 reject 都吞掉)。
|
|
609
|
+
取 `quit` 的理由:它是 ioredis / node-redis **唯一共名**的关闭出口(node-redis v5 起标 deprecated、
|
|
610
|
+
后继 `close()`,已写进接口注释)。**框架仍不主动替你调**(store 可能共享,drain 不关 store)。
|
|
611
|
+
- **记忆水合失败的两条 warn(A1)**:`src/runtime/run.ts`。① 水合 `load` 抛错不再无声吞
|
|
612
|
+
(`catch {}` → `console.warn`);② `flushMemory` 的 CAS 分支开头判 `rev === undefined` ⇒
|
|
613
|
+
**跳过 CAS 回写**并说真话(「这一次读失败了」≠「并发抢写」)。**为什么**:能走到该分支的 store 必有
|
|
614
|
+
`loadWithRev`(成对性由 `assertMemoryStoreShape` 入口保证),所以此处 `rev === undefined` ⟺
|
|
615
|
+
水合失败;旧代码把它夹带到几十行之后的 CAS 回写、归因成并发 —— **查错会一开始就查错方向**。
|
|
616
|
+
|
|
617
|
+
**测试与文档(使用者可见行为零变更)**:
|
|
618
|
+
|
|
619
|
+
- **F3**:删 `tests/toolkit/capability-slice.test.ts` 的恒真断言(源码里 `CAPABILITY_KINDS = Object.keys(KIND_SPEC)`,
|
|
620
|
+
那句是 `x === x`);保留精确键序断言与 #184 的 `TS2741` 穷尽护栏 —— 删了不降低任何防线强度。
|
|
621
|
+
- **F2**:`AGENTS.md` 的 toolkit 段补 `capability-cycles.ts`(`841f2ac` / #170 引入、地图漏了);
|
|
622
|
+
新建 `tests/architecture/toolkit-map.test.ts` —— 判据是**「露名或显式豁免」**(实证「全量穷尽」不可行:
|
|
623
|
+
`src/toolkit/` 13 个文件里 9 个用**概念词**覆盖而非文件名词,属正常);两条用例(未登记集合必须为空 +
|
|
624
|
+
F2 当事人必须被点名),已登记 `docs/guards.md` §1.1。
|
|
625
|
+
- **`AGENTS.md` 结构数字**:`:66` 区域「910 行的类」→ **1554 行**,并注「计划写于 09-20(当时 895 行),
|
|
626
|
+
后续三步一步没走,类反长 115%」—— 这类**写死的读数会腐烂且无守卫**,改到结构时顺手改真值。
|
|
627
|
+
- **B1**:`src/transport/task-events.ts` 两处注释订正(零行为变化)—— `:204` 那句「**不会**回收刚 push
|
|
628
|
+
的这条」是**假的**(单热流场景下若真跳过它,候选集恒空 ⇒ 配额完全失效 ⇒ 无界增长,比现状更坏;
|
|
629
|
+
故取「改注释」而非「改行为」);`#recycleNonTerminal` 补第 3 条代价(退订不触发回收 ⇒
|
|
630
|
+
配额真实上界 = `nonTerminalBuffers + 曾有订阅者的流数`)。
|
|
631
|
+
- **F1**:`tests/docs/boundary-table.test.ts` 的 31 条 `gap` 逐条回源核对 ⇒ **7 条回填为 `pin`**、
|
|
632
|
+
1 条订正理由(读数 **pin 49→56 / gap 31→24**,§7 仍 **90 行**);三处写死的行数读数一并同步
|
|
633
|
+
(文件头注 88→90、A4 断言文案 89→90、登记表注释);`docs/guards.md §2 待守` 登记一行
|
|
634
|
+
「`gap` 表需随每轮盘点复核」—— `gap` 表一旦写下就再没人复算,09-29 抽样 3 条**3 条全过期**。
|
|
635
|
+
- **官网(建议①③)**:`packages/website/src/pages/docs.astro` 在**构建期**从单源
|
|
636
|
+
`docs/usage-guide.md?raw` 抠出 §7 表,注入 `docs.html` 新增的 `#limits` 段(占位符找不到 / 行数 < 70
|
|
637
|
+
都**硬失败**,仿 `llms-full.txt.ts` 的 `?raw` 手法,单源不漂移);`docs.html` 两处「去看
|
|
638
|
+
`/llms-full.txt`」的指路改成 `#limits` 锚点。产物实测 **90 行表 + `href="#limits"`×2**。
|
|
639
|
+
|
|
640
|
+
**验证**:本轮累计 **5 条变异**逐条亲跑点名(F2 守卫改名 ⇒ 两条同时红;C5b `assertNotNaN` 判据失效 ⇒
|
|
641
|
+
恰好那条红;D1 判据退回 `typeof then` ⇒ 恰好新用例红;C1 `quit` 未被调 ⇒ 恰好那条红;A1 跳过分支失效 ⇒
|
|
642
|
+
恰好那条红),`git checkout --` 还原后全绿。门禁三件套全绿(`typecheck` / `typecheck:tests` /
|
|
643
|
+
`lint` 443 文件零告警);相关套件:runtime 56 · engine 404 · container 11 · store 56 · transport 352 ·
|
|
644
|
+
toolkit 142 · architecture 57 · limits 32 · boundary-table 6 · 各 docs/官网守卫 —— 全绿。
|
|
645
|
+
|
|
646
|
+
**有意不做(留独立一轮)**:`async.ts` 抽「审批监督」簇 + 行数上限闸。回源实测其真实跨度**大于**报告描述
|
|
647
|
+
(`#suspended` Map 还被恢复簇的 `#resyncSuspended` 重建、6 处 `#unmarkSuspended` 调用点、对外 getter
|
|
648
|
+
`suspendedSummary`、`#dispatch` 是私有方法需回调注入)—— 这是**零行为变化的纯结构重构**,与前面
|
|
649
|
+
「修缺陷」性质不同,报告 §8 自己也标「各自独立 PR」;仓促混进本批会让「零行为变化」难以严格保证。
|
|
650
|
+
|
|
651
|
+
### 重构 · `AsyncRunner` 的审批监督簇抽成独立件(结构体检建议②)(2026-09-29 ⑦)
|
|
652
|
+
|
|
653
|
+
**使用者可见行为零变更**(纯结构重构:逐字搬迁 + 依赖改构造注入)。
|
|
654
|
+
|
|
655
|
+
`src/transport/async.ts` 里 `AsyncRunner` 的**审批监督簇** —— 挂起登记簿(`#suspended`,即
|
|
656
|
+
`/healthz` 的 `suspended` 读数)+ 在飞审批闸(`#inflightApprovals`)+ `approve` / `#approveInner`
|
|
657
|
+
+ 审批超时恢复(`#expireAndResume` / `#expireAndResumeInner`)—— 抽到新件
|
|
658
|
+
`src/transport/approval-supervisor.ts`(`ApprovalSupervisor`)。`async.ts` **1929 → 1756 行**。
|
|
659
|
+
|
|
660
|
+
- **为什么是这一簇**:结构体检(`STRUCTURE-INVENTORY-2026-09-29.md` 建议②)指出它的方向与
|
|
661
|
+
「认领 / 槽位 / 派发」**正交** —— 它管的是「谁挂着、谁在等审批、超时了没」。抽之前它被私字段
|
|
662
|
+
与私有方法缝在主类里,横跨 **5 处外部调用点**(`cancel` / `signalTask` / 到期唤醒 /
|
|
663
|
+
`resumePending` 扫描 / `#executeInner` 的挂起与终态出口)+ 对外 getter。
|
|
664
|
+
- **依赖怎么解**:构造注入 `store` / `ownerId` / `approvalTimeoutMs` / **`dispatch` 回调**。
|
|
665
|
+
`#dispatch` 仍归 `AsyncRunner`(停机闸 + 唯一派发口 + 一次性告警),审批簇只**消费**它 ⇒
|
|
666
|
+
依赖单向,也不会出现两模块互相 `import`。`TaskApproveError` / `ApprovalDecisions` 随簇迁走,
|
|
667
|
+
`async.ts` 顶部 **re-export** ⇒ 一切 `from './async.js'` 的既有 import 路径不变。
|
|
668
|
+
- **一处有意的等价简化**:`#redispatch` 的超时扫描原写作
|
|
669
|
+
`rec.status !== 'suspended' || !approvalExpired(...)`,而 `approvalExpired` 内部**已经**判
|
|
670
|
+
`status === 'suspended'` 与 `reason === 'approval'`(见 `approval-policy.ts`)⇒ 那句短路是
|
|
671
|
+
**冗余的**,合并进 `expireIfExpired` 不改变任何可观测行为(字面量多重集对拍已证:唯一
|
|
672
|
+
「消失」的字面量就是它)。
|
|
673
|
+
- **一处随之而变的守卫**:`tests/transport/dispatch-guard.test.ts` 的「`#dispatch(` 调用点下限」
|
|
674
|
+
射程**扩到两个文件**(两条恢复路径的派发点形如 `this.#deps.dispatch(`)—— 否则这次搬迁会把
|
|
675
|
+
计数从 7 掉到 6 而无谓判红。⚠️ **该守卫在改动中真的先红了**(先见红、再改守卫,不是反过来)。
|
|
676
|
+
- **验证**:3 条变异逐条亲跑 —— ① `expireIfExpired` 恒不触发 ⇒ 恰好 3 条红;② `approve` 的 409
|
|
677
|
+
判据退回只看 `status` ⇒ 恰好 1 条红;③ `rebuild` 的 reason 判据摘掉 ⇒ **全绿**(如实记为
|
|
678
|
+
**既存**覆盖缺口,已登记 `guards.md §2`)。三条还原后 `sha256` 逐字节一致。另跑 B.3「纯结构
|
|
679
|
+
拆分复核清单」第 2 条要求的**字符串字面量多重集对拍**(无丢失)。门禁三件套 + transport 352 /
|
|
680
|
+
architecture 57 / toolkit 142 / runtime 56 / engine 404 / container 11 / store 56 —— 全绿。
|
|
681
|
+
- **顺带登记**(`guards.md §2`):上面那条覆盖缺口,以及**行数上限闸为何暂不做**(口径未定、
|
|
682
|
+
易误报,与 §3「宁可窄,不要误报」有张力;替代形状见那一行)。
|
|
683
|
+
|
|
684
|
+
### 重构 · `AsyncRunner` 再抽两簇(信号投递 / 恢复扫描)+ **源码体量闸**(结构体检建议③)(2026-09-29 ⑧)
|
|
685
|
+
|
|
686
|
+
**使用者可见行为零变更**(纯结构重构:逐字搬迁 + 依赖改构造注入)。
|
|
687
|
+
|
|
688
|
+
⑦ 抽走审批簇后 `async.ts` 仍有 1756 行。本轮把剩下两个方向正交的簇也抽走:
|
|
689
|
+
|
|
690
|
+
- **信号投递**(`SignalSupervisor`,`src/transport/signal-supervisor.ts`,182 行 / **74 含代码行**):
|
|
691
|
+
`signalTask` / `#signalInner` / 在飞信号闸 + `TaskEventError` + 两个上限常量
|
|
692
|
+
(`MAX_DELIVERED_EVENT_IDS` 256 / `MAX_PENDING_EVENTS` 64)。它与 `ApprovalSupervisor.approve`
|
|
693
|
+
是**同一族**(挂起任务的外部唤醒入口;形状逐条对应:读记录 → 校验挂起 → 改状态 → 先落库再
|
|
694
|
+
派发 → `unmarkSuspended` → `dispatch`),两处真实差异:① 并发闸不同款(`approve` 共享在飞那次;
|
|
695
|
+
事件**串行成链**);② 多一道容量闸(到上限 409,不静默丢)。
|
|
696
|
+
- **恢复扫描与接管**(`ResumeScanner`,`src/transport/resume-scanner.ts`,261 行 / **126 含代码行**):
|
|
697
|
+
`resumePending` / `#redispatch` / `#ownerLiveness` / `wakeDue` / `#wakeDueInner` + 两把在飞闸。
|
|
698
|
+
一次扫描**按序**干四件事(顺序有语义):① `approvals.rebuild` ② `approvals.expireIfExpired`
|
|
699
|
+
③ `wakeDue` ④ `#redispatch` 认领重投(`wakeDue` 由私有改公开,两个调用点)。
|
|
700
|
+
|
|
701
|
+
`src/transport/async.ts` **1756 → 1460 行**(**664 含代码行**)。依赖仍走**构造注入**:两个监督件都只
|
|
702
|
+
**消费** `AsyncRunner.#dispatch`(停机闸 + 唯一派发口 + 一次性告警)⇒ 依赖单向、无模块环。
|
|
703
|
+
`TaskEventError` / `ResumePendingOptions` 随簇迁走 + `async.ts` 顶部 **re-export** ⇒ 一切
|
|
704
|
+
`from './async.js'` 的既有 import 路径不变。
|
|
705
|
+
|
|
706
|
+
**新增防回涨的闸**:`tests/architecture/source-size.test.ts` —— `src/**` 单文件**含代码行** ≤ 750。
|
|
707
|
+
|
|
708
|
+
- ⚠️ **口径与报告建议刻意不一致**:报告写「`async.ts ≤ 1600` **行**」,但本仓注释占 29~55%
|
|
709
|
+
(`async.ts` **55%**)、注释是证据载体(记着「判据只挡终态,别写成 `!== 'queued'`」这类踩坑)⇒
|
|
710
|
+
按**行数**设闸的第一个作用是**逼人删注释**(静默、且删的是最贵的资产);且按行数 `1460 < 1600`
|
|
711
|
+
**当下根本不触发**(事后不疼)。⇒ 与注释解耦:整行注释与空行一律不计入。
|
|
712
|
+
- **上限 750 的由来**:`async.ts` 664 与第二名 `turn.ts` 521 之间隔着一大截(143 行)—— 750 落在这段
|
|
713
|
+
空档里,给 `async.ts` 留约 13% 正常迭代余量,又远离合法长文件那一档(报告 §2 那批「不该拆」的
|
|
714
|
+
最长只有 260 行代码)⇒ 不误伤。
|
|
715
|
+
- **超了只有两条路、都必须显式**:① 抽件(本仓既有做法);② 改 `MAX_CODE_LINES`(改动落在这个
|
|
716
|
+
文件里 ⇒ review 时一定看得见,而不是悄悄长过去)。
|
|
717
|
+
- ⚠️ **刻意不用「剥掉注释再数非空行」**:本仓多处 URL 字面量与含 `//` 的字符串,朴素剥离会**吃掉
|
|
718
|
+
真实的代码行** ⇒ 读数偏低 ⇒ 闸静默失效。逐行只问「这一行是不是从头到尾都是注释」。
|
|
719
|
+
- **变异验证过**:上限压到 500 ⇒ 精确点名 `transport/async.ts: 664` / `engine/turn.ts: 521`
|
|
720
|
+
(当下越线的确实只有这两个);还原后复绿。
|
|
721
|
+
|
|
722
|
+
**两条随之而变 / 补上的守卫**:
|
|
723
|
+
|
|
724
|
+
- `tests/transport/dispatch-guard.test.ts` 的「`#dispatch(` 调用点」射程从**两个文件扩到四个**
|
|
725
|
+
(三条恢复路径的派发点形如 `this.#deps.dispatch(`),并**钉住每个监督件的精确数**
|
|
726
|
+
(approval 2 / signal 1 / scanner 3)+ 真空护栏。⚠️ 与 ⑦ 同款:**这条守卫每次搬件都会先掉一格
|
|
727
|
+
判红** —— 修法是**扩射程**、不是放宽下限(「路径总数」这一事实没变)。
|
|
728
|
+
- **补一条既存覆盖缺口**:变异「把 `signalTask` 的串行闸摘掉」⇒ **全绿**,说明这条闸**此前没有
|
|
729
|
+
任何用例钉住**(搬迁前内联在 `async.ts` 里时就没有)⇒ 补 `tests/transport/task-events-input.test.ts`
|
|
730
|
+
的「两条并发事件串行成链 ⇒ 只唤醒一次」(用**有往返的异步 store** 让并发窗口真实存在)。
|
|
731
|
+
|
|
732
|
+
**验证**:5 条变异逐条亲跑 —— ① `wakeDue` 的 drain 闸失效 ⇒ 恰 2 条红(`drain-race` + `durable-timer`);
|
|
733
|
+
② 认领后异步派发摘掉 ⇒ 恰 3 条红(`async.test`);③ 串行闸摘掉 ⇒ **全绿(既存缺口,已补用例)**;
|
|
734
|
+
④ 容量闸失效 ⇒ 恰 3 条红(`event-buffer-cap`);⑤ 补用例后重跑串行闸 ⇒ **恰好那条新用例红**。
|
|
735
|
+
全部 `cp` 还原后 `sha256` 逐字节一致。另跑 B.3「纯结构拆分复核清单」第 2 条的**字符串字面量多重集
|
|
736
|
+
对拍**(可复跑探针 `.workbuddy/probes/literal-multiset-diff.mjs`,逐字符状态机剥注释):旧(`async.ts`
|
|
737
|
+
+ `approval-supervisor.ts`)**81 种 / 142 个** vs 新(四文件)**83 种 / 154 个** —— **丢失 0 项**,
|
|
738
|
+
新增 6 项**全是 import 路径** ⇒ 纯搬迁。门禁三件套 + 套件:transport 353 / architecture 57 /
|
|
739
|
+
toolkit 142 / runtime 55 / engine 403 / container 10 / store 55 —— 全绿。
|
|
740
|
+
|
|
741
|
+
**顺带**:`guards.md §2` 那条「`async.ts` 没有行数上限闸」**闭环**(划掉、移入 §1.1);
|
|
742
|
+
`AGENTS.md` 的 `transport/` 段补登记三个监督件,并订正那句已过时的「910 行的类 / 后续抽块三步一步
|
|
743
|
+
没走」(抽块其实已走了三步)。
|
|
744
|
+
|
|
8
745
|
## [Unreleased]
|
|
9
746
|
|
|
10
747
|
## [0.9.5] - 2026-09-29
|
|
@@ -2142,7 +2879,9 @@
|
|
|
2142
2879
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
2143
2880
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
2144
2881
|
|
|
2145
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.
|
|
2882
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.10.1...HEAD
|
|
2883
|
+
[0.10.1]: https://github.com/retrychx/agentia/releases/tag/v0.10.1
|
|
2884
|
+
[0.10.0]: https://github.com/retrychx/agentia/releases/tag/v0.10.0
|
|
2146
2885
|
[0.9.5]: https://github.com/retrychx/agentia/releases/tag/v0.9.5
|
|
2147
2886
|
[0.9.4]: https://github.com/retrychx/agentia/releases/tag/v0.9.4
|
|
2148
2887
|
[0.9.3]: https://github.com/retrychx/agentia/releases/tag/v0.9.3
|