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.
72 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|