fix(memory-b): W9 —— adapter 的 embedText 丢掉 caller signal(真 bug)
复现(真 adapter + 本地真 http server,3s 回包,50ms abort)
signal forwarded? outcome=RESOLVED vector=[0.1,0.2,0.3] elapsed=3012ms
★ caller abort ignored — signal dropped by embedText signature
根因:lib/index.js:173 调 embedText(text, options.signal),而
lib/embed/http.js:152 声明的是 embedText(text) —— 第二实参被 JS 静默丢弃。
生产语义:用户已取消,工具继续阻塞到 adapter 自己的 30s 超时,
而且最后还把向量写进库;isConcurrencySafe 的兄弟调用一起被堵。
为什么 99 条全绿
W5/W5† 用的是手写慢 embedder,它自己 addEventListener('abort') 了 ——
判据测的是「我希望的行为」,没测真实 adapter。与上一轮 V3b 自造
2 段 batch 是同一形态:替身站到了唯一可能出错的那段代码前面。
修复
- embedText(text, signal):用 AbortSignal.any([signal, controller.signal])
融合两个取消源。两个语义都保留:
· 用户取消要立刻停(实测 50ms -> 53ms)
· provider 卡死也要停(实测无 signal 时 151ms 超时;
有未取消的 caller signal 时仍 151ms 超时)
Node >= 22.5 已有 AbortSignal.any,不手写 fuse、不轮询。
- 同步修 Embedder 的 JSDoc 与 interface 类型,把第二个参数写进契约
W9 判据(真链路,不用假 embedder)
W9 caller abort 打断真实请求:耗时 < 500ms 且确实抛出
W9b adapter 自身 timeout 在有 caller signal 时仍然生效
W9c 端到端(真 server + 真 adapter + ctx.tools.execute):
块仍写入、vector 为空、error.info.code === 'ABORTED'
W9† 变异(贴红绿原文)
★ 第一次变异不干净:只改签名,函数体仍引用 signal ⇒ 抛 ReferenceError
⇒ W9 看到「很快抛错」判为通过。这是假绿。
干净的变异(签名单参 + 去掉 fuse):
not ok 41 - W9: a caller abort interrupts a REAL http embedding call promptly
an aborted call must reject, not resolve with a vector
not ok 43 - W9c: end-to-end — aborted tool call still writes the block
恢复后 # pass 102 / # fail 0
f. 同形问题全仓扫描结论(grep + 实参/形参比对,非「我觉得」)
lib/ 里所有传到 Embedder 的调用点:
lib/index.js:173 embedText(text, options.signal) -> 形参 (text, signal) ✔
lib/recall.js:434 embedText(String(query ?? '')) -> 单参,合法 ✔
自动比对脚本报了一处 withDiagnosis 4>3 的告警,手工核对为误报:
6 处调用实参分别为 2/2/3/3/3/2,正则被 Object.defineProperty 干扰。
结论:无其它「传了实参、对面签名不收」的调用点。
README:376 修正
原文写「exec.signal is forwarded into embedText」——参数确实传了但对面对掉,
比没传更糟(读文档的人会以为验过)。改为与代码一致的表述,
并注明「W5 只覆盖管道,适配器侧由 W9 覆盖」。
另记下变异必须干净这条:失败原因不对的变异不算证据。
验证
- npm test 102/102(99 + W9/W9b/W9c)
- npm run check 10/10
- 提交前连跑 3 次 test + 3 次 check,6 次全绿