fix(agent): 插话 - 独立运行时路径的送达与退回 (#673)

复现:Agent 运行中发送插话,独立 Agent 运行时(Pi)下既不送达模型也不退回用户,插话一直停在待送队列里。

原因与修法:插话送达点原本只有旧 Go 执行循环(advanceCloudAgent 开步前出队);Pi 运行时接手执行后调度循环不再推进。现把 Pi 路径的送达搬到 /model 模型步:出队、规范历史追加与 delivered 事件同在入队的一次 CAS 写入里,响应携带 steeringMessages 交给运行时 session.steer。完成、失败、取消、审批拒绝与步数耗尽统一退回;最终回答之后的插话在预算富余时续跑一步。完成闸一次带走多条插话时,合并后的续跑提示词也记入指纹,避免被恢复闸当成遗留提示词清空。
This commit is contained in:
jiabx
2026-10-05 13:39:57 +08:00
committed by GitHub
parent dd563d3373
commit 5bf838ebbe
6 changed files with 730 additions and 6 deletions

View File

@@ -172,6 +172,8 @@ func (s *Service) runCloudAgentModelStep(ctx context.Context, userID, runID stri
// 这些事件也会推进运行 revision。调度模型步骤是 CAS 写入:冲突时重新读取
// 最新状态再调度,而不是把整轮判失败。冲突时事务整体回滚,不会产生任务或扣费。
var state cloudAgentRuntime
// 本步送达的插话正文,随响应交给运行时 session.steer(见 cloud_agent_interjection_pi.go)。
var steeringMessages []string
for attempt := 0; attempt < 8; attempt++ {
run, err := s.repo.CloudAgent(userID, runID)
if err != nil {
@@ -185,6 +187,9 @@ func (s *Service) runCloudAgentModelStep(ctx context.Context, userID, runID stri
return nil, false, fmt.Errorf("run already terminated")
}
if cloudAgentStepBudgetExhausted(&state) {
// 最终回答之后的插话续步会在这里撞上限;若确实停在最终回答边界,
// 按完成收尾而不是把整轮判失败(见 settleCloudAgentPiFinalAnswer)。
s.settleCloudAgentPiFinalAnswer(userID, runID)
return nil, false, fmt.Errorf("step budget exhausted")
}
if state.StepLimits, err = s.cloudAgentStepLimits(); err != nil {
@@ -222,8 +227,14 @@ func (s *Service) runCloudAgentModelStep(ctx context.Context, userID, runID stri
if len(canonical.Messages) == 0 {
return nil, false, fmt.Errorf("no canonical messages")
}
// 运行时投影回写会洗掉插话来源标记,先补回再送达新插话;送达必须发生在
// 入队这次 CAS 写入里(出队、canonical 追加与 delivered 事件同批落库),
// 冲突重试时从最新状态重新出队,不会重复送达。压缩摘要请求不参与。
if !compactionSummary {
state.Canonical = canonical
cloudAgentRestoreInterjectionSources(&state)
steeringMessages = cloudAgentPiDeliverInterjections(runID, &state)
canonical = state.Canonical
}
if len(correction) > 0 {
// 纠偏上下文只用于这一次请求,不写回运行状态,避免下一步重复出现。
@@ -328,7 +339,11 @@ func (s *Service) runCloudAgentModelStep(ctx context.Context, userID, runID stri
} else if err := s.finishCloudAgentPiModelStep(userID, runID, task.ID, finishText, finishReasoning, result.ToolCalls); err != nil {
return nil, false, err
}
return map[string]any{"text": result.Text, "reasoning": result.Reasoning, "toolCalls": runtimeToolCalls(result.ToolCalls)}, false, nil
delivered := map[string]any{"text": result.Text, "reasoning": result.Reasoning, "toolCalls": runtimeToolCalls(result.ToolCalls)}
if len(steeringMessages) > 0 {
delivered["steeringMessages"] = steeringMessages
}
return delivered, false, nil
}
// adoptCloudAgentPiModelStep 判断挂着的活动任务能否作为本步结果直接接手。