From 4d48abaa8c4ac71dfde1a7f3a68655f8183c1386 Mon Sep 17 00:00:00 2001 From: weichen Date: Mon, 12 Oct 2026 07:43:31 +0000 Subject: [PATCH] Map background card failures to CardFailed with L1 body Why: Stop showing raw task text as progress and stop lighting the done lamp on failed background runs (U1 whitelist). Evidence: go test ./internal/runmgr/ -count=1 Impact: create/promote/finish card bodies; material queue status text. Refs: fast-slow-channel-display-u1-plan W2; Core ba289e7 Co-authored-by: Cursor --- deps/agent-system-core | 2 +- internal/runmgr/background_card_test.go | 57 +++++++++++++++++++++++-- internal/runmgr/material_test.go | 2 +- internal/runmgr/runmgr.go | 33 +++++++------- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/deps/agent-system-core b/deps/agent-system-core index 89f3906..ba289e7 160000 --- a/deps/agent-system-core +++ b/deps/agent-system-core @@ -1 +1 @@ -Subproject commit 89f3906c390027512ef47b5333088c3d3a019e84 +Subproject commit ba289e7b1780a29855aef8477bf1ac49bd563eb2 diff --git a/internal/runmgr/background_card_test.go b/internal/runmgr/background_card_test.go index 8583834..3f8f0d1 100644 --- a/internal/runmgr/background_card_test.go +++ b/internal/runmgr/background_card_test.go @@ -120,18 +120,67 @@ func TestBackgroundCardLifecycleOnFailure(t *testing.T) { t.Fatalf("cards = %d, want 1", len(cards)) } card := cards[0] - if card.Status != core.CardDone || card.Tone != "danger" { - t.Fatalf("status = %s tone = %s, want done/danger", card.Status, card.Tone) + if card.Status != core.CardFailed || card.Tone != "danger" { + t.Fatalf("status = %s tone = %s, want failed/danger", card.Status, card.Tone) } var md string + statusLabel := "" + for _, n := range card.Body { + if n.Type == core.UIMarkdown { + md = n.Markdown + } + if n.Type == core.UIKV { + for _, kv := range n.KV { + if kv[0] == core.TaskSummaryKVStatus { + statusLabel = kv[1] + } + } + } + } + if !strings.Contains(md, "执行失败") { + t.Fatalf("failure body = %q", md) + } + if statusLabel != core.TaskSummaryStatusFailed { + t.Fatalf("status kv = %q, want %q", statusLabel, core.TaskSummaryStatusFailed) + } +} + +func TestBackgroundCardCreateUsesL1SkeletonNotRawTask(t *testing.T) { + runner := &holdRunner{release: make(chan struct{})} + c, _ := newCardSpawnEnv(t, runner) + c.mgr.runner = noopRunner{} + raw := "逐页解析上传材料并对照验收条件" + strings.Repeat("LONGTASK", 40) + if _, err := c.SpawnBackground("agent.sub.general", "深度解析员", raw, ""); err != nil { + t.Fatal(err) + } + waitUntil(t, func() bool { + cards, err := c.mgr.cards.List(c.run.Owner, c.run.ConversationID) + return err == nil && len(cards) == 1 + }) + cards, err := c.mgr.cards.List(c.run.Owner, c.run.ConversationID) + if err != nil { + t.Fatal(err) + } + card := cards[0] + var md string for _, n := range card.Body { if n.Type == core.UIMarkdown { md = n.Markdown } } - if !strings.Contains(md, "执行失败") { - t.Fatalf("failure body = %q", md) + if strings.Contains(md, "LONGTASK") || strings.Contains(md, raw[:32]) { + t.Fatalf("create body still embeds raw task: %q", md) } + if md != core.TaskSummaryNoFindingsYet { + t.Fatalf("create markdown = %q, want honest placeholder", md) + } + close(runner.release) + waitUntil(t, func() bool { + c.mgr.convMu.Lock() + busy := c.mgr.convActive[c.run.ConversationID] + c.mgr.convMu.Unlock() + return !busy + }) } // TestBackgroundCardSkippedWithoutInbox:收件箱未启用(cards==nil)时后台派发 diff --git a/internal/runmgr/material_test.go b/internal/runmgr/material_test.go index ad175b1..8f6efed 100644 --- a/internal/runmgr/material_test.go +++ b/internal/runmgr/material_test.go @@ -275,7 +275,7 @@ func TestMaterialQueuedWhenFull(t *testing.T) { return false } for _, card := range cards { - if card.Title == materialCardTitle && card.SourceRunID != "" && strings.Contains(cardBodyText(card), "执行中") { + if card.Title == materialCardTitle && card.SourceRunID != "" && strings.Contains(cardBodyText(card), core.TaskSummaryStatusRunning) { return true } } diff --git a/internal/runmgr/runmgr.go b/internal/runmgr/runmgr.go index 51ae71a..1086b73 100644 --- a/internal/runmgr/runmgr.go +++ b/internal/runmgr/runmgr.go @@ -1947,13 +1947,14 @@ func (m *Manager) createBackgroundCard(parent core.Run, childID core.ID, name, t if title == "" { title = "后台任务" } - if status == "" { - status = "执行中,完成后自动回投结果" - } - body := []core.UINode{ - {Type: core.UIMarkdown, Markdown: truncate(strings.TrimSpace(task), 400)}, - {Type: core.UIKV, KV: append(append([][2]string(nil), kv...), [2]string{"通道", "慢通道 · 后台执行"}, [2]string{"状态", status})}, + _ = task // 派发说明保留入参兼容;用户可见正文用 L1 骨架,不回填原始指令。 + statusLabel := core.TaskSummaryStatusRunning + if status != "" { + statusLabel = status } + focus := "正在执行:" + title + extra := append(append([][2]string(nil), kv...), [2]string{"通道", "慢通道 · 后台执行"}) + body := (core.TaskSummaryView{Focus: focus}).RunningBody(statusLabel, extra) card, err := m.cards.Create(parent.Owner, core.ActivityCard{ Kind: core.CardKindProgress, Title: title, Status: core.CardActive, Tone: "info", ConversationID: parent.ConversationID, ProjectID: parent.ProjectID, SourceRunID: childID, @@ -1976,9 +1977,9 @@ func (m *Manager) promoteQueuedCard(owner string, card core.ActivityCard, childI if node.Type != core.UIKV { continue } - for j, kv := range node.KV { - if kv[0] == "状态" { - card.Body[i].KV[j][1] = "执行中,完成后自动回投结果" + for j, pair := range node.KV { + if pair[0] == core.TaskSummaryKVStatus || pair[0] == "状态" { + card.Body[i].KV[j][1] = core.TaskSummaryStatusRunning } } } @@ -1988,15 +1989,19 @@ func (m *Manager) promoteQueuedCard(owner string, card core.ActivityCard, childI } // finishBackgroundCard 把后台子代理终态写回进度卡:成功=结果摘要(含工件行), -// 失败=错误说明;状态统一置 done,tone 区分成败。kv 原样保留(材料键等持久凭据)。 +// 失败=错误说明并置 CardFailed(U1:失败不得显示完成灯)。kv 原样保留。 func (m *Manager) finishBackgroundCard(owner string, card core.ActivityCard, payload core.AgentResultPayload, err error, kv [][2]string) { if m.cards == nil || card.ID == "" { return } summary := strings.TrimSpace(payload.Summary) tone := "ok" + status := core.CardDone + statusLabel := core.TaskSummaryStatusDone if err != nil { tone = "danger" + status = core.CardFailed + statusLabel = core.TaskSummaryStatusFailed if summary == "" { summary = err.Error() } @@ -2005,14 +2010,12 @@ func (m *Manager) finishBackgroundCard(owner string, card core.ActivityCard, pay if summary == "" { summary = "(无结果摘要)" } - card.Body = []core.UINode{ - {Type: core.UIMarkdown, Markdown: summary}, - {Type: core.UIKV, KV: append(append([][2]string(nil), kv...), [2]string{"通道", "慢通道 · 后台执行"}, [2]string{"状态", "已完成"})}, - } + extra := append(append([][2]string(nil), kv...), [2]string{"通道", "慢通道 · 后台执行"}) + card.Body = (core.TaskSummaryView{OutcomeLine: summary}).FinishedBody(statusLabel, extra) if uerr := m.cards.Update(owner, card); uerr != nil { log.Printf("[runmgr] background card update: %v", uerr) } - if _, serr := m.cards.SetStatus(owner, card.ID, core.CardDone, tone, ""); serr != nil { + if _, serr := m.cards.SetStatus(owner, card.ID, status, tone, ""); serr != nil { log.Printf("[runmgr] background card status: %v", serr) } }