Files
Agent-System/internal/server/task_conflict_cards_test.go
T
weichen 76ddf4dd35 feat(ctx): surface dialogue conflict confirmation cards to triggering user
Capture trigger_user HITL expectations from dialogue adjudication (previously
dropped at the bizdev call site), project them as chat confirmation cards via
taskrun OnTerminal (exactly-once), arm the session PendingExpectation, and
handle confirm_context_fact hits through the supplement-info blackboard path.
Decline (纠正) guides the user to state the correct fact.
2026-09-25 21:01:37 +00:00

72 lines
2.7 KiB
Go

package server
import (
"strings"
"testing"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/taskrun"
)
func TestProjectContextConflictCards_ArmsSessionExpectation(t *testing.T) {
s := &Server{sessions: map[string]*chat.Session{}}
sess := chat.NewSession("sess-cc", &coordinator.UserProjectContext{
Project: coordinator.ProjectInfo{Name: "便携式掌上超声项目"},
BlackboardFacts: map[string]string{},
}, nil, nil)
s.sessions[sess.ID] = sess
record := &taskrun.RunRecord{RunID: "run_1", SessionID: sess.ID, ProjectID: 42}
result := &bizdev.TaskRunResult{
Success: true,
PendingContextConflicts: []bizdev.ContextConflictCard{{
ExpectationID: "ctx_conflict_run_1_device_type",
FactKey: "device_type",
WinningValue: "一次性使用采血管",
Prompt: "对话与现有事实对「device_type」说法不一致。",
Snippet: "同级时间相近且无明确证据",
}},
}
s.projectContextConflictCards(t.Context(), record, result)
if sess.UserCtx.PendingExpectation == nil {
t.Fatal("expected session PendingExpectation armed")
}
exp := sess.UserCtx.PendingExpectation
if exp.TargetAction != "confirm_context_fact" || exp.Payload["fact_key"] != "device_type" {
t.Fatalf("unexpected armed expectation: %+v", exp)
}
msgs := sess.SnapshotMessages()
if len(msgs) == 0 || !strings.Contains(msgs[len(msgs)-1].Content, "确认") {
t.Fatalf("expected card message appended, got %+v", msgs)
}
}
func TestProjectContextConflictCards_NoSession_SkipsSilently(t *testing.T) {
s := &Server{sessions: map[string]*chat.Session{}}
record := &taskrun.RunRecord{RunID: "run_2", SessionID: "sess-missing", ProjectID: 7}
result := &bizdev.TaskRunResult{PendingContextConflicts: []bizdev.ContextConflictCard{{
ExpectationID: "e", FactKey: "budget", WinningValue: "15万", Prompt: "p", Snippet: "s",
}}}
s.projectContextConflictCards(t.Context(), record, result) // must not panic
}
func TestProjectContextConflictCards_EmptySkips(t *testing.T) {
s := &Server{sessions: map[string]*chat.Session{}}
s.projectContextConflictCards(t.Context(), &taskrun.RunRecord{SessionID: "x"}, &bizdev.TaskRunResult{})
}
func TestRenderContextConflictCardMessage(t *testing.T) {
msg := renderContextConflictCardMessage([]bizdev.ContextConflictCard{{
FactKey: "device_type", WinningValue: "一次性使用采血管", Prompt: "冲突提示", Snippet: "同级相近",
}})
for _, want := range []string{"冲突", "device_type", "一次性使用采血管", "确认", "纠正"} {
if !strings.Contains(msg, want) {
t.Fatalf("card message missing %q: %s", want, msg)
}
}
}