Files
Agent-Squad/squad/r2_regression_test.go
T
weichenandCursor 9537321782 Close the squad P1 gaps the audit listed for this batch
Why: parallel groups dropped a hold, HITL timeouts were unused, the planner failed closed on any model error, and event sequence numbers restarted at 1.
Evidence: GOWORK=off go test -count=1 -race ./squad/ ./squadintent/ ./store/ ./platform/ passed, including TestR2ExpiredDecisionUsesCancel, TestR2IntentStepSkipsRoleRegistry, TestR2PlannerFallsBackAndCapsSteps, TestR2TaskFileUsesLatestBlackboard, TestR2FanoutCancelDoesNotPanic, TestR2EventSeqContinuesAfterRestart, TestR2JournalModeWAL, and TestR2GateReloadSeesNewIntent.
Impact: a failed or held parallel sibling cancels the rest; expired requests synthesize cancel or fail; the intent gate follows Registry.Reload.
Refs: Q6, Q10, Q11, Q12, Q13, Q17, Q18, Q19, I9
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-03 13:10:39 +00:00

93 lines
3.0 KiB
Go

package squad
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestR2ExpiredDecisionUsesCancel(t *testing.T) {
created := time.Date(2026, 10, 3, 0, 0, 0, 0, time.UTC)
decision, due := expiredDecision(HumanRequest{ID: "hr_1", CreatedAt: created, Status: "pending"},
HitlPolicy{TimeoutAfter: time.Minute, ExpireAction: "cancel"}, created.Add(2*time.Minute))
if !due || decision.Choice != "cancel" || decision.RequestID != "hr_1" {
t.Fatalf("decision %+v due %v", decision, due)
}
if _, early := expiredDecision(HumanRequest{ID: "hr_1", CreatedAt: created},
HitlPolicy{TimeoutAfter: time.Minute}, created.Add(30*time.Second)); early {
t.Fatal("not due yet")
}
}
func TestR2IntentStepSkipsRoleRegistry(t *testing.T) {
p := &LLMPlanner{MaxSteps: 4}
out, err := p.materialize(PlanInput{Goal: Goal{Text: "查进度"}, Registry: NewRoleRegistry()}, &llmPlanSchema{
Steps: []llmStep{{IntentID: "demo.run.status_query", Title: "查"}},
})
if err != nil {
t.Fatal(err)
}
if out.Plan.Workflow.Steps[0].IntentID != "demo.run.status_query" || out.Plan.Workflow.Steps[0].Role != "intent" {
t.Fatalf("step %+v", out.Plan.Workflow.Steps[0])
}
}
func TestR2PlannerFallsBackAndCapsSteps(t *testing.T) {
fallback := NewRulePlanner()
p := &LLMPlanner{Fallback: fallback}
out, err := p.Plan(context.Background(), PlanInput{Goal: Goal{Text: "写一份说明"}, Registry: NewRoleRegistry()})
if err != nil || out == nil || (out.Plan == nil && out.Clarification == nil) {
t.Fatalf("fallback: %+v %v", out, err)
}
tooMany := &llmPlanSchema{}
for i := 0; i < 3; i++ {
tooMany.Steps = append(tooMany.Steps, llmStep{IntentID: "demo.x", Title: "t"})
}
if _, err := (&LLMPlanner{MaxSteps: 2}).materialize(PlanInput{Goal: Goal{Text: "x"}, Registry: NewRoleRegistry()}, tooMany); err == nil || !strings.Contains(err.Error(), "max 2") {
t.Fatalf("cap: %v", err)
}
}
func TestR2GoalIsSegmented(t *testing.T) {
text := strings.Repeat("标", goalSegmentRunes+1)
got := segmentGoal(text)
if !strings.Contains(got, "目标第 2 段") {
t.Fatalf("segments missing: %s", got[:80])
}
}
func TestR2TaskFileUsesLatestBlackboard(t *testing.T) {
dir := t.TempDir()
ws := &WorkspaceEngine{}
step := &StepSpec{ID: "s", Role: "writer", Title: "写", Targets: []string{"output.md"}}
role := RoleSpec{Role: "writer", Name: "写手"}
bb := &BlackboardState{Fields: map[string]string{"fact": "old"}}
if err := ws.writeTaskFile(dir, step, role, bb); err != nil {
t.Fatal(err)
}
bb.Fields["fact"] = "new"
if err := ws.writeTaskFile(dir, step, role, bb); err != nil {
t.Fatal(err)
}
b, err := os.ReadFile(filepath.Join(dir, "TASK.md"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(b), "fact = new") || strings.Contains(string(b), "fact = old") {
t.Fatalf("task file: %s", b)
}
}
func TestR2FanoutCancelDoesNotPanic(t *testing.T) {
sink := NewFanoutSink(8)
ch, _, cancel := sink.Subscribe(0)
cancel()
sink.Emit(Event{Type: "phase", Text: "later"})
if _, ok := <-ch; ok {
t.Fatal("channel should be closed")
}
}