Make stopped work actionable and retain detailed execution errors
Why: A budget-stopped task retained a promise to generate a final result, while readable card errors need the full diagnosis in the execution record. Evidence: Continuation-promise and missing-error-record regressions failed on the prior implementation; the full Backend suite now passes. Impact: Terminal next steps require user action, stopped work has clear labels, and precise subagent errors persist outside the default task summary. Refs: Independent A04 model assessment; approved R08 and R10
This commit is contained in:
@@ -160,6 +160,10 @@ func TestBackgroundCardLifecycleOnFailure(t *testing.T) {
|
||||
if statusLabel != core.TaskSummaryStatusFailed {
|
||||
t.Fatalf("status kv = %q, want %q", statusLabel, core.TaskSummaryStatusFailed)
|
||||
}
|
||||
failed, err := c.mgr.st.GetRunByID(card.SourceRunID)
|
||||
if err != nil || failed.Error == nil || failed.Error.Message != runner.err.Error() {
|
||||
t.Fatalf("failure details were lost from the execution record: %+v %v", failed.Error, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackgroundCardCreateUsesL1SkeletonNotRawTask(t *testing.T) {
|
||||
|
||||
@@ -1800,6 +1800,9 @@ func (m *Manager) finishChild(rc *runCtx, runner runtime.Runner, emitResult bool
|
||||
child.Status = status
|
||||
child.Phase = core.PhaseDone
|
||||
child.Usage = &usage
|
||||
if execErr != nil {
|
||||
child.Error = &core.ErrorPart{Code: "run.subagent_failed", Message: execErr.Error()}
|
||||
}
|
||||
m.st.UpdateRun(&child)
|
||||
if emitResult {
|
||||
m.emit(child.ConversationID, child.ID, core.EvAgentResult, payload)
|
||||
|
||||
@@ -169,6 +169,7 @@ func (m *Manager) finishTaskProjection(b taskBinding, payload core.AgentResultPa
|
||||
}
|
||||
s.DeliveryState = "available"
|
||||
s.Conclusion = taskResultConclusion(payload)
|
||||
s.NextStep = "核验已有结果和未决事项,再选择后续工作"
|
||||
s.CheckpointKeys = nil
|
||||
if runErr != nil {
|
||||
s.ExecutionState = "failed"
|
||||
@@ -179,7 +180,11 @@ func (m *Manager) finishTaskProjection(b taskBinding, payload core.AgentResultPa
|
||||
if len(s.Findings) > 0 {
|
||||
s.OutcomeCompleteness = "partial"
|
||||
}
|
||||
s.Conclusion = "执行失败:" + runErr.Error()
|
||||
s.NextStep = "核对已有发现和材料;需要继续处理时重新提出请求"
|
||||
s.Conclusion = "执行失败,任务已停止;请核验已有发现和材料限制"
|
||||
if strings.Contains(runErr.Error(), "budget_exceeded") {
|
||||
s.Conclusion = "达到本次处理额度,执行已停止;已有发现已保留"
|
||||
}
|
||||
}
|
||||
if s.Conclusion == "" {
|
||||
s.Conclusion = "没有可用结果摘要"
|
||||
@@ -212,8 +217,15 @@ func (m *Manager) finishTaskProjection(b taskBinding, payload core.AgentResultPa
|
||||
}
|
||||
}
|
||||
}
|
||||
s.Phase = core.TaskPhase{ID: s.ExecutionState, Label: "处理结束", ObservedAt: time.Now().UTC()}
|
||||
s.Focus = "处理结束"
|
||||
label := "处理结束"
|
||||
if s.ExecutionState == "failed" {
|
||||
label = "执行已停止"
|
||||
}
|
||||
if s.ExecutionState == "interrupted" {
|
||||
label = "任务已中断"
|
||||
}
|
||||
s.Phase = core.TaskPhase{ID: s.ExecutionState, Label: label, ObservedAt: time.Now().UTC()}
|
||||
s.Focus = label
|
||||
s.LastActivityAt = time.Now().UTC()
|
||||
s.LastMeaningfulUpdateAt = s.LastActivityAt
|
||||
if e := s.Validate(); e != nil {
|
||||
|
||||
@@ -3,12 +3,33 @@ package runmgr
|
||||
import (
|
||||
"agent-system-backend/internal/cardsvc"
|
||||
core "agent-system-core"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFailedTaskDoesNotPromiseAutomaticContinuation(t *testing.T) {
|
||||
c := checkpointEnvironment(t)
|
||||
if err := c.mgr.mutateTask(c.task, func(s *core.TaskSnapshot) error {
|
||||
s.NextStep = "输出最终结构化整理结果"
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.mgr.finishTaskProjection(c.task, core.AgentResultPayload{}, errors.New("budget_exceeded: run token budget exhausted")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
card, err := c.mgr.st.GetCard(c.run.Owner, c.task.cardID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if card.Task.Phase.ID != "failed" || card.Task.Phase.Label != "执行已停止" || strings.Contains(card.Task.NextStep, "输出最终") || strings.Contains(card.Task.Conclusion, "budget_exceeded") {
|
||||
t.Fatalf("stopped task retained a process promise: %+v", card.Task)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTaskConclusionSkipsProcessText(t *testing.T) {
|
||||
c, _ := newCardSpawnEnv(t, &gateRunner{parts: []core.Part{{Type: core.PartText, Process: true, Text: "我先检查知识库。"}, {Type: core.PartText, Text: "日期仍待裁定,预算审批尚未完成。"}}})
|
||||
c.mgr.runner = noopRunner{}
|
||||
|
||||
Reference in New Issue
Block a user