fix(hitl): 审批复盘轨迹补全 —— 中断不关 exec 流,resume 事件续录

现象:已完成的审批任务,复盘轨迹停在「已中断等待审批」、审批节点永远转圈,看着
像卡住(实际任务已 done、有输出)。

根因:任务中断时 Handle 的 defer tr.done() 给 exec 流发了 CompleteExec → 网关 exec
录制器关闭。resume 是另一次独立调用,其 exec 事件(审批通过/拒绝、续跑节点)发到
同一主题时录制器已关 → 没录进去。(token 流中断时特意没关,所以输出录到了;exec
流却被关了,不对称。)

- exec.go: execTracer 加 suspended 标志,done() 挂起时不关流。
- orchestrator.go: 中断分支置 tr.suspended=true(与 token 流一致)。
- ExecTrace.tsx: 前端兜底——运行已完成(phase done)时把悬挂的 waiting/running 节点
  收敛为 done,修旧任务(已 done、轨迹缺 resume 事件)的转圈假象。

live 验证:审批任务批准后复盘轨迹完整呈现 approval await→end「批准:放行」→
agent start→推理→end,审批节点不再转圈。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-29 16:57:11 +08:00
parent fcefecd10d
commit 158fe094ab
3 changed files with 16 additions and 6 deletions
@@ -43,7 +43,13 @@ function StatusDot({ status }: { status: NodeTrace["status"] }) {
// ExecTrace 把执行事件流渲染为竖向轨道:每个节点一颗灯,实时点亮 + 耗时 + 入参/产出。
export function ExecTrace({ events, phase, compact }: { events: ExecEvent[]; phase?: RunPhase; compact?: boolean }) {
const nodes = deriveNodes(events);
const raw = deriveNodes(events);
// 运行整体已完成,但轨迹可能缺了中断后(resume)续录的事件(如审批通过、续跑节点),
// 导致审批节点停在 waiting 转圈。既然已完成,把悬挂的 waiting/running 收敛为 done,不再假装进行中。
const nodes =
phase === "done"
? raw.map((n) => (n.status === "waiting" || n.status === "running" ? { ...n, status: "done" as const } : n))
: raw;
if (nodes.length === 0) {
return (
<div className="flex h-full flex-col items-center justify-center gap-1 px-4 py-8 text-center text-xs text-slate-600">