Skip to content

🐛 fix(sdk): engine 执行循环九项修复——运行锁 TOCTOU、缓存计价、压缩边界 uuid 回填 - #435

Merged
CavinHuang merged 8 commits into
mainfrom
fix/sdk-engine-loop
Aug 22, 2026
Merged

🐛 fix(sdk): engine 执行循环九项修复——运行锁 TOCTOU、缓存计价、压缩边界 uuid 回填#435
CavinHuang merged 8 commits into
mainfrom
fix/sdk-engine-loop

Conversation

@CavinHuang

Copy link
Copy Markdown
Owner

概述

修复 sdk 全量 review 提出的 9 个 issue(engine 执行循环域),每个 commit 独立可跑绿。

Issue 修复
#358 repeat guard 熔断:跨签名连续计数 + 结构化 errorCode(rebase 时采用 main 先行落地的更严实现 208c818,本分支保留其交替击穿用例)
#352 estimateCost 补 cache read(0.1x)/write(1.25x) 计价 + recordProviderUsage 透传——maxBudgetUsd 预算熔断不再失真
#353 prompt-too-long 守卫改 consecutiveFailures < 3:压缩成功后可再次触发,失败自增自带熔断收口
#357 (P2) 运行锁同步置位/finally 复位闭合 TOCTOU 窗口,并发 query 不再双引擎写坏同一会话
#363 compact 边界后 uuid 队尾对齐回填;合成摘要排除出配对强制 fresh uuid(返工修复:否则摘要偷走被摘要消息的 uuid,rewindFiles 会恢复错误轮次文件快照)
#365 protectedUserMessage 按引用剔除出摘要区间,插回逻辑不动——双重包含消除
#359 pendingLspDiagnostics 改非破坏性读取+成功后清空,压缩重试不再永久吞诊断
#360 非流式 api_retry 经 onAsyncEvent 即时投递,与流式路径一致(数组仅无宿主回调兜底)
#361 max_tokens 续写耗尽的纯文本回复终态映射 error_max_output_tokens,不再以 success 掩盖截断

注:#304(max_tokens 占位 tool_result)已由 main 侧 515d6ad 上游修复,本分支在其上叠加 #361 耗尽标志。

验证

Closes #358, closes #352, closes #353, closes #357, closes #359, closes #360, closes #361, closes #363, closes #365

🤖 Generated with Claude Code

@CavinHuang

Copy link
Copy Markdown
Owner Author

Code Review

九项逐项核对完毕,全部真实落地、方向正确、无 P0/P1/P2,CI 6/6 绿,可合

亮点核验

非阻塞 P3 记录

BEHIND,merge main 过 gate 后即可合。

TaTaLiao and others added 6 commits August 22, 2026 20:43
protectedUserMessage 的命中条件(boundaryStart ≤ idx < firstKeptIndex)
决定它必然同时落在 messagesToSummarize ∪ turnPrefixMessages 的序列化
区间内,摘要生成后再原样插回,同一请求文本出现两份。

prepareCompaction 在返回前按引用把该消息从两个序列化区间剔除;
插回逻辑与 checkpoint 语义保持原样,split-turn 与非 split-turn 一并覆盖。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
estimateCost 只计 input/output,cache read(约 0.1x 输入价)与
cache write(约 1.25x 输入价)无计价路径,totalCost 长期低估,
maxBudgetUsd 预算熔断因此失真。

签名扩展两个可选 cache 字段:read 按 0.1x、write 按 1.25x 折算;
未传字段行为不变。引擎侧接线随引擎批次提交。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
启动让并发 query 双双穿过守卫、fork 出双引擎写坏同一会话。入口改
同步置位 runLocked 布尔并在外层 finally 复位(拆出 runSinglePromptLocked),
单标志闭合窗口;锁随首个 next() 的同步前缀即生效。

按旧 uuid 键查 fileCheckpointState 必 miss。重建时按同角色消息自队尾
对齐回填原 uuid(toSessionMessage 增加可选 uuid 参数):compaction 在
队首插入合成摘要消息,只有尾部消息与前列表一一对应,checkpoint 键不动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#361 续写预算耗尽后再遇 max_tokens 且无 tool_use,落入
completedNaturally 以 success 收尾掩盖截断。置 maxTokensExhausted
标志,终态映射 error_max_output_tokens(subtype 联合新增字面量,
含 string 向后兼容),errors 附说明;#304 占位结构沿用 main 既有实现。

#353 prompt-too-long 守卫要求 !compacted,而 compacted 成功后整个
run 不复位,tool 循环二次超窗直接终止 run。守卫改
consecutiveFailures < 3:成功复位 0 允许再触发,失败自增自带熔断。

#359 pendingLspDiagnostics.splice(0) 破坏性取走后仅注入瞬态
apiMessages,prompt-too-long 压缩重建请求时诊断永久丢失。改非破坏
读取,请求成功后才清空,压缩重试下一轮自然重新注入。

#360 非流式路径 api_retry 事件缓冲至 withRetry 整体返回后才补发,
最长约 30s×3 假死。onRetry 改经既有 onAsyncEvent 通道即时投递,
缓冲数组仅作无宿主回调时的兜底。

另接 #352:recordProviderUsage 将归一化 cache 读写字段透传
estimateCost,totalCost/modelUsage/billingUsage 反映缓存计价。

rebase 注记:#358(repeat guard 交替击穿)与 #304 占位已由 main
208c818/515d6ad10 先行落地且语义更严,本次保留 main 实现;
原 #358 放行用例与 main 的 stall-guard 取舍相悖,随本提交移除。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
review 返工:队尾对齐未区分合成消息。当 previous 同角色条目数多于
重建后历史(长会话压缩的常态),队尾配对会把被摘要吞掉的最近真实
用户消息的 uuid 偷给合成摘要条目——fileCheckpointState 不随压缩
清理时,宿主对摘要条目 rewindFiles 会静默恢复无关旧轮次的文件快照。

配对前剔除 content 含 _meta.contextBlock === 'compaction' 的消息,
强制其拿 fresh uuid;补 previous > new 方向的回归测试(原测试只覆盖
了反方向)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
上一轮 rebase 解决 agent.test.ts 双方新增冲突时遗留了一行
>>>>>>> 标记未删,随本提交移除,无其它改动。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CavinHuang

Copy link
Copy Markdown
Owner Author

Code Review

九项逐项核对完毕,全部真实落地且各有行为钉死测试,可合。merge-tree 实测与 main 零冲突(仅 BEHIND,main 唯一触碰点 c0cc273 新增 stop_reason:'aborted' 与本分支正交,自动合并干净);无 .tmp 残留。

亮点验证

P3(不阻塞)

  1. estimateCost 对 PRICING_ENTRIES 全模型统一套缓存系数,非 Anthropic 系口径可能不同——方向保守(高估成本→预算熔断更早),可接受。
  2. [P3][sdk/agent] compact 边界后全量重建 sessionMessages 换发新 uuid,checkpoint 键与 UI 引用脱钩 #363 配对是纯 role 尾对齐启发式,history 与 previous 同角色条目数错位时会静默串号(checkpoint 键错位不崩溃);主路径已钉死。

结论

可合,走 PR 正常合并即可。

@CavinHuang
CavinHuang force-pushed the fix/sdk-engine-loop branch from 9be2dcc to 56ebfea Compare August 22, 2026 12:47
@CavinHuang
CavinHuang merged commit 9ddf6d3 into main Aug 22, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment