Skip to content

fix: 失败的 render_ui 不再算作已交付;同回合合成上下文不再清掉回合状态(#236 review 确认的两处漏补救) - #262

Merged
taekchef merged 1 commit into
mainfrom
fix/fence-feedback-delivery-and-turn-boundary
Oct 5, 2026
Merged

taekchef merged 1 commit into
mainfrom
fix/fence-feedback-delivery-and-turn-boundary

Conversation

@taekchef

@taekchef taekchef commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

修复 #236 review 中确认、合入 main 后仍保留的两处漏补救(CI 绿、相关 issue 关闭不代表这两条场景已修复)。

漏补救 ①:失败的 render_ui 调用被算作已交付

deliveredSomething 在 assistant/message 里见到 tool-call(name=render_ui) 就把 deliveredThisTurn 置真——只看调用、不看结果。渲染卡失败后用户什么都没有,回合边界却认为已交付,nothing_delivered 提醒永远不会发出。

修复:交付只由 tool/result 决定(事件携带 message.content[0] 的 toolCallId/isError 与事件级 error):

  • tool/call(render_ui) 记入 pendingRenders;
  • tool/result 无事件级 error 且结果块非 isError ⇒ 交付成立;否则不算;
  • 结果未到达时回合边界保持静默(pendingRenders 非空不纠正),避免与晚到结果赛跑(review 第 3 条「晚到结果不重复纠正」);turn/start 清空残留。

漏补救 ②:同轮上下文消息清掉验证状态

user/message 分支对任何非插件来源的消息执行回合状态重置。但宿主的 user/message 明确包含三类:真实用户提示、agent.inject() 合成上下文(文件变更通知、AGENTS.md、skill 内容等)、成员消息——后两类到达于回合中途,把 validatedThisTurn/deliveredThisTurn 清空,回合边界检查时信号已丢失。

修复:重置只发生在——

  1. turn/start(SessionEventMap 的正式回合边界,0.1.2-rc.1 起可用);
  2. 直接用户提示(source.kind === 'user',作为老宿主的兜底)。

agent.inject() 通知与成员消息(source.kind !== 'user')不再清状态。

测试(tests/fence-feedback.spec.ts 43 项,+4)

  • render_ui 结果成功 ⇒ 静默(原用例改为完整事件链);
  • render_ui 结果失败(事件级 error / 结果块 isError)⇒ 提醒照发;
  • 结果未决 ⇒ 边界静默,结果落定(失败)后下一边界提醒;
  • 合成上下文消息(tool/agent/member 三种 kind)不清状态 ⇒ 提醒照发;
  • turn/start 开启干净回合 ⇒ 新回合可再次提醒。

验证

  • npx vitest run 全量 994 passed / 99 skipped;npx tsc --noEmit 无错误。

…no longer wipes turn state (#236 review)

Two gaps confirmed on #236 review that survived the merge:

1. A render_ui tool CALL marked the turn delivered. A failed render left
   the user with nothing while the boundary stayed silent forever.
   Delivery is now decided by the tool/result: success = no internal
   error identity and a non-isError result block. A call whose result has
   not arrived blocks steering at the boundary instead of racing the
   late result.

2. The user/message handler reset per-turn state for ANY non-plugin user
   message, so same-turn synthetic context (agent.inject() notices, team
   member messages) wiped validatedThisTurn/deliveredThisTurn before the
   boundary could use them. Reset now happens on the formal turn/start
   boundary and on direct human prompts (source.kind === 'user') only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant