Ask via PendingExpectation for weak-write verbs, mid confidence, or stage-mismatched doc tasks instead of running blindly. Co-authored-by: Cursor <cursoragent@cursor.com>
92 lines
3.2 KiB
Go
92 lines
3.2 KiB
Go
package coordinator
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/intent"
|
|
"chat-agent-system/internal/maturity"
|
|
)
|
|
|
|
func TestConfirmGate_WeakVerbNeedsConfirmation(t *testing.T) {
|
|
eng := NewCoordinatorEngine(nil)
|
|
userCtx := &UserProjectContext{
|
|
User: UserInfo{ID: "1", Name: "u"},
|
|
Project: ProjectInfo{ID: "9", Name: "P", CurrentStage: maturity.StageV01_Triage, ActiveWorkflowKey: "main"},
|
|
}
|
|
cand := &intent.CandidateIntent{
|
|
Intent: intent.IntentTriggerTask,
|
|
Confidence: 0.78,
|
|
Branch: intent.BranchNewIntentPreempt,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetTask: "intake",
|
|
intent.SlotNeedsConfirmation: "true",
|
|
},
|
|
}
|
|
res, err := eng.handleTriggerTask(context.Background(), cand, userCtx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Status != StatusClarificationRequired {
|
|
t.Fatalf("status=%s want CLARIFICATION_REQUIRED", res.Status)
|
|
}
|
|
if res.NextExpectation == nil || len(res.NextExpectation.Options) < 2 {
|
|
t.Fatalf("missing confirm expectation: %+v", res.NextExpectation)
|
|
}
|
|
if res.NextExpectation.Options[0].Slots[intent.SlotTargetTask] != "intake" {
|
|
t.Fatalf("default option task=%v", res.NextExpectation.Options[0].Slots)
|
|
}
|
|
if !strings.Contains(res.NaturalReply, "用户初始需求收集表") {
|
|
t.Fatalf("reply missing intake title: %s", res.NaturalReply)
|
|
}
|
|
if userCtx.PendingExpectation == nil {
|
|
t.Fatal("pending expectation must be applied")
|
|
}
|
|
}
|
|
|
|
func TestConfirmGate_StrongHighConfidenceDirect(t *testing.T) {
|
|
eng := NewCoordinatorEngine(nil)
|
|
userCtx := &UserProjectContext{
|
|
User: UserInfo{ID: "1", Name: "u"},
|
|
Project: ProjectInfo{ID: "9", Name: "P", CurrentStage: maturity.StageV01_Triage, ActiveWorkflowKey: "main"},
|
|
}
|
|
cand := &intent.CandidateIntent{
|
|
Intent: intent.IntentTriggerTask,
|
|
Confidence: 0.86,
|
|
Branch: intent.BranchNewIntentPreempt,
|
|
FilledSlots: map[string]string{
|
|
intent.SlotTargetTask: "intake",
|
|
},
|
|
}
|
|
res, err := eng.handleTriggerTask(context.Background(), cand, userCtx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// nil tasks runner → accepted stub reply, not clarification
|
|
if res.Status != StatusAccepted {
|
|
t.Fatalf("status=%s want ACCEPTED (no confirm for strong high-conf)", res.Status)
|
|
}
|
|
if strings.Contains(res.NaturalReply, "请确认") {
|
|
t.Fatalf("must not ask confirm: %s", res.NaturalReply)
|
|
}
|
|
}
|
|
|
|
func TestConfirmGate_StageMismatchRemapsDefaultToIntake(t *testing.T) {
|
|
// Force stage-mismatch path without instSvc by calling confirmGate helper with a
|
|
// synthetic candidate; exercise buildTriggerConfirmExpectation via public path
|
|
// using mid-confidence + manual expectation builder check.
|
|
exp := buildTriggerConfirmExpectation("user_requirements", "用户需求表", &UserProjectContext{
|
|
Project: ProjectInfo{CurrentStage: maturity.StageV01_Triage},
|
|
}, true)
|
|
if exp.Options[0].Slots[intent.SlotTargetTask] != "intake" {
|
|
t.Fatalf("stage mismatch default must remap to intake, got %v", exp.Options[0].Slots)
|
|
}
|
|
if len(exp.Options) < 3 {
|
|
t.Fatalf("want advance-stage option, got %d options", len(exp.Options))
|
|
}
|
|
if exp.Options[2].Intent != intent.IntentAdvanceStage {
|
|
t.Fatalf("option 3 want ADVANCE_STAGE, got %s", exp.Options[2].Intent)
|
|
}
|
|
}
|