ci / vet-and-test (pull_request) Successful in 1m1s
Why: real run run_b520bf2e3037 showed the user-facing reply being displaced by internal bookkeeping - the fact-check correction round's closing text plus the same-source verifier skip notice became the visible answer while the actual summary was folded into the process timeline (frontend splits at the last process part). Same tail pollution reproduced on every same-model run since. Evidence: go test ./... -count=1 green (32 pkgs); new regression tests cover the demotion of the pre-check summary and the skip notice becoming a check part; three old-contract tests updated to assert notices land as process checks instead of final text. Impact: stop() fact-check branch now demotes the standing summary (aligned with the gate and cap branches) and asks the model for a fresh full reply; verifier errors record as process checks (pass-state for same-source skip, fail-state for real errors) instead of StopDecision.Notice text; the cap notice likewise becomes a failed check line. Refs: Dev task feature/final-reply-pollution-fix; diagnosis in .task/feature-dual-speed-tasks.md lineage and session analysis 2026-10-10.
98 lines
3.2 KiB
Go
98 lines
3.2 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
// 问题一回归(run_b520bf2e3037):事实核查修正轮触发时,现场那段正式总结必须
|
|
// 降级进过程时间线;修正轮的收尾才是最终答复——否则前端按「最后一个过程部件」
|
|
// 切分后,用户只看到修正流水账,看不到原总结。
|
|
func TestFactCheckDemotesPriorSummary(t *testing.T) {
|
|
rc := &fakeRunCtx{
|
|
ctx: context.Background(),
|
|
prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model", Lightweight: "lite-model"}},
|
|
verify: true,
|
|
execArtifact: &core.Artifact{ID: "art_0123456789ab", Name: "报告.md"},
|
|
execArtifactContent: []byte("# 报告\n\n准确率 95%。"),
|
|
}
|
|
turns := padTurns(
|
|
textDeltaTurn("", toolCall("make_report", `{"content":"准确率 95%"}`)),
|
|
textDeltaTurn("已完成。## 产出物\n\n结构完整的用户总结。"),
|
|
textDeltaTurn("已修正并重新登记。"),
|
|
)
|
|
r := &LLMRunner{Stream: scriptStream(turns, nil), VerifyStream: passedStream()}
|
|
if err := r.Execute(rc); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
var summary *core.Part
|
|
visible := []string{}
|
|
sawFactCheck := false
|
|
for i := range rc.assistant {
|
|
p := rc.assistant[i]
|
|
if p.Type == core.PartText && strings.Contains(p.Text, "结构完整的用户总结") {
|
|
summary = &rc.assistant[i]
|
|
}
|
|
if p.Type == core.PartText && !p.Process {
|
|
visible = append(visible, p.Text)
|
|
}
|
|
if p.Check != nil && !p.Check.Passed && strings.Contains(p.Check.Reason, "事实核查") {
|
|
sawFactCheck = true
|
|
}
|
|
}
|
|
if summary == nil {
|
|
t.Fatal("prior summary text missing")
|
|
}
|
|
if !summary.Process {
|
|
t.Fatalf("prior summary not demoted to process: %+v", *summary)
|
|
}
|
|
if !sawFactCheck {
|
|
t.Fatal("fact check note missing")
|
|
}
|
|
if len(visible) != 1 || visible[0] != "已修正并重新登记。" {
|
|
t.Fatalf("visible final texts = %v, want only the continuation closing", visible)
|
|
}
|
|
}
|
|
|
|
// 同源跳过提示不再作为用户可见正文追加(原先经 StopDecision.Notice 顶掉正式总结),
|
|
// 改记为通过态检查行留在过程时间线。
|
|
func TestVerifierSkipIsCheckNotFinalText(t *testing.T) {
|
|
rc := &fakeRunCtx{
|
|
ctx: context.Background(),
|
|
prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}, // 轻量回落=主模型 → 同源跳过
|
|
verify: true,
|
|
}
|
|
turns := padTurns(textDeltaTurn("总结完成。"))
|
|
r := &LLMRunner{Stream: scriptStream(turns, nil)} // 无 VerifyStream
|
|
if err := r.Execute(rc); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
skipCheck, noticeText := false, false
|
|
visible := []string{}
|
|
for _, p := range rc.assistant {
|
|
if p.Check != nil && p.Check.Passed && strings.Contains(p.Check.Reason, "同源模型验证") {
|
|
skipCheck = true
|
|
}
|
|
if p.Type == core.PartText {
|
|
if strings.Contains(p.Text, verifierUnavailableNotice) {
|
|
noticeText = true
|
|
}
|
|
if !p.Process {
|
|
visible = append(visible, p.Text)
|
|
}
|
|
}
|
|
}
|
|
if !skipCheck {
|
|
t.Fatal("same-source skip not recorded as a passing check")
|
|
}
|
|
if noticeText {
|
|
t.Fatal("skip notice leaked into message text parts")
|
|
}
|
|
if len(visible) != 1 || visible[0] != "总结完成。" {
|
|
t.Fatalf("visible final texts = %v", visible)
|
|
}
|
|
}
|