Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

319 lines
12 KiB
Go

package tuttimodeplan
import (
"context"
"errors"
"testing"
"time"
workspaceissues "github.com/tutti-os/tutti/packages/workspace/issues"
executionbiz "github.com/tutti-os/tutti/services/tuttid/biz/tuttimodeexecution"
workspaceservice "github.com/tutti-os/tutti/services/tuttid/service/workspace"
)
type recordingWorkspaceIssueTarget struct {
workspaceID string
input workspaceservice.CreateIssueManagerIssueFromPlanInput
createErr error
existing workspaceissues.IssueDetail
execution executionbiz.Aggregate
getErr error
}
func (target *recordingWorkspaceIssueTarget) GetTuttiModeExecutionByIssue(
_ context.Context,
_ string,
_ string,
) (executionbiz.Aggregate, error) {
if target.getErr != nil {
return executionbiz.Aggregate{}, target.getErr
}
if target.execution.Execution.ID == "" {
return executionbiz.Aggregate{}, executionbiz.ErrExecutionNotFound
}
return target.execution, nil
}
func (target *recordingWorkspaceIssueTarget) CreateIssueFromPlan(
_ context.Context,
workspaceID string,
input workspaceservice.CreateIssueManagerIssueFromPlanInput,
) (workspaceissues.IssueDetail, error) {
target.workspaceID = workspaceID
target.input = input
if target.createErr != nil {
return workspaceissues.IssueDetail{}, target.createErr
}
return workspaceissues.IssueDetail{Issue: workspaceissues.Issue{IssueID: input.Issue.IssueID}}, nil
}
func (target *recordingWorkspaceIssueTarget) GetIssueDetail(
_ context.Context,
_ string,
issueID string,
) (workspaceissues.IssueDetail, error) {
if target.getErr != nil {
return workspaceissues.IssueDetail{}, target.getErr
}
if target.existing.Issue.IssueID != "" {
return target.existing, nil
}
return workspaceissues.IssueDetail{Issue: workspaceissues.Issue{IssueID: issueID}}, nil
}
func TestWorkspaceIssueMaterializerMapsAcceptedProjectionIntoIssueDomain(t *testing.T) {
t.Parallel()
target := &recordingWorkspaceIssueTarget{}
materializer := WorkspaceIssueMaterializer{Issues: target}
issueID, err := materializer.MaterializeIssue(context.Background(), MaterializeIssueInput{
WorkspaceID: "workspace-1",
WorkflowID: "workflow-1",
RevisionID: "revision-1",
SourceSessionID: "session-1",
Title: "Ship workflow",
Content: "Plan body",
TopicID: "topic-1",
Execution: PlanExecution{
Mode: "parallel",
ReasoningIntensity: 70,
OrchestrationIntensity: 80,
},
Budget: PlanBudget{Mode: "fixed", TokenLimit: 120_000, QuotaWaterlinePercent: 10},
ActionableItems: []ActionableItem{{
Ordinal: 1,
Task: PlanTask{
ID: "task-1",
Title: "Implement",
Content: "Build it",
Priority: "high",
AgentTargetID: "local:codex",
ModelPlanID: "plan-1",
Model: "gpt-5.4",
ExecutionDirectory: "/workspace/task-1",
DependsOn: []string{"task-0"},
},
}},
})
if err != nil {
t.Fatalf("MaterializeIssue() error = %v", err)
}
if issueID != "tutti-mode-plan-workflow-1" || target.workspaceID != "workspace-1" {
t.Fatalf("MaterializeIssue() issueID=%q workspaceID=%q", issueID, target.workspaceID)
}
issue := target.input.Issue
if issue.PlanningSource != string(workspaceissues.PlanningSourceTuttiModePlan) || !issue.ParallelExecution || issue.SequentialExecution {
t.Fatalf("materialized issue execution/provenance = %#v", issue)
}
if !issue.TuttiModeWorkflowOwned {
t.Fatal("materialized issue did not carry internal workflow authority")
}
if issue.TuttiModeWorkflowID != "workflow-1" {
t.Fatalf("materialized workflow id = %q, want workflow-1", issue.TuttiModeWorkflowID)
}
if issue.SourceSessionID != "session-1" || issue.ExecutionProfile.ReasoningIntensity != 70 || issue.Budget.TokenLimit != 120_000 {
t.Fatalf("materialized issue settings = %#v", issue)
}
if len(target.input.Tasks) != 1 || target.input.Tasks[0].TaskID != "task-1" || len(target.input.Tasks[0].DependencyTaskIDs) != 1 {
t.Fatalf("materialized tasks = %#v", target.input.Tasks)
}
}
func TestWorkspaceIssueMaterializerRejectsReservedIDPreemption(t *testing.T) {
t.Parallel()
input := materializeIssueFixture()
target := &recordingWorkspaceIssueTarget{
createErr: workspaceissues.ErrIssueAlreadyExists,
existing: workspaceissues.IssueDetail{Issue: workspaceissues.Issue{
IssueID: "tutti-mode-plan-workflow-1",
TopicID: input.TopicID,
Title: input.Title,
Content: input.Content,
PlanningSource: workspaceissues.PlanningSourceManual,
SourceSessionID: input.SourceSessionID,
}},
}
if _, err := (WorkspaceIssueMaterializer{Issues: target}).MaterializeIssue(context.Background(), input); !errors.Is(err, ErrIssueMaterializationConflict) {
t.Fatalf("MaterializeIssue() error = %v, want ErrIssueMaterializationConflict", err)
}
}
func TestWorkspaceIssueMaterializerAcceptsOnlyStrictlyMatchingRetry(t *testing.T) {
t.Parallel()
input := materializeIssueFixture()
existing := materializedIssueDetailFixture(input)
target := &recordingWorkspaceIssueTarget{
createErr: workspaceissues.ErrIssueAlreadyExists,
existing: existing,
execution: materializedExecutionFixture(t, input),
}
issueID, err := (WorkspaceIssueMaterializer{Issues: target}).MaterializeIssue(context.Background(), input)
if err != nil || issueID != "tutti-mode-plan-workflow-1" {
t.Fatalf("MaterializeIssue() issueID=%q error=%v", issueID, err)
}
target.execution.Execution.Status = executionbiz.StatusRunning
target.execution.Execution.GraphRevision = 2
target.execution.Checkpoints[0].Status = executionbiz.CheckpointStatusResolved
target.execution.Checkpoints = append(target.execution.Checkpoints, executionbiz.Checkpoint{
ID: target.execution.Execution.ID + ":checkpoint:watchdog:2",
ExecutionID: target.execution.Execution.ID,
Kind: executionbiz.CheckpointKindWatchdog,
Status: executionbiz.CheckpointStatusActive,
Sequence: 2,
GraphRevision: 2,
})
target.execution.Execution.ActiveCheckpointID = target.execution.Checkpoints[1].ID
if advancedIssueID, advancedErr := (WorkspaceIssueMaterializer{Issues: target}).MaterializeIssue(context.Background(), input); advancedErr != nil || advancedIssueID != issueID {
t.Fatalf("MaterializeIssue(advanced replay) issueID=%q error=%v", advancedIssueID, advancedErr)
}
target.existing.Tasks[0].Content = "preempted content"
if _, err := (WorkspaceIssueMaterializer{Issues: target}).MaterializeIssue(context.Background(), input); !errors.Is(err, ErrIssueMaterializationConflict) {
t.Fatalf("MaterializeIssue() mismatched retry error = %v, want ErrIssueMaterializationConflict", err)
}
}
func TestWorkspaceIssueMaterializerRejectsRetryWithDifferentTaskOrder(t *testing.T) {
t.Parallel()
input := materializeIssueFixture()
input.ActionableItems = append(input.ActionableItems, ActionableItem{
Ordinal: 2,
Task: PlanTask{
ID: "task-2",
Title: "Verify",
Content: "Test it",
Priority: "medium",
DependsOn: []string{"task-1"},
},
})
existing := materializedIssueDetailFixture(input)
existing.Tasks[0].SortIndex = 2
existing.Tasks = append(existing.Tasks, workspaceissues.Task{
TaskID: "task-2",
Title: "Verify",
Content: "Test it",
Priority: workspaceissues.PriorityMedium,
DependencyTaskIDs: []string{"task-1"},
SortIndex: 1,
})
target := &recordingWorkspaceIssueTarget{
createErr: workspaceissues.ErrIssueAlreadyExists,
existing: existing,
execution: materializedExecutionFixture(t, input),
}
if _, err := (WorkspaceIssueMaterializer{Issues: target}).MaterializeIssue(context.Background(), input); !errors.Is(err, ErrIssueMaterializationConflict) {
t.Fatalf("MaterializeIssue() reordered retry error = %v, want ErrIssueMaterializationConflict", err)
}
}
func TestWorkspaceIssueMaterializerRejectsRetryWithConflictingExecution(t *testing.T) {
t.Parallel()
input := materializeIssueFixture()
execution := materializedExecutionFixture(t, input)
execution.Execution.SourceSessionID = "different-session"
target := &recordingWorkspaceIssueTarget{
createErr: workspaceissues.ErrIssueAlreadyExists,
existing: materializedIssueDetailFixture(input),
execution: execution,
}
if _, err := (WorkspaceIssueMaterializer{Issues: target}).MaterializeIssue(context.Background(), input); !errors.Is(err, ErrIssueMaterializationConflict) {
t.Fatalf("MaterializeIssue() conflicting execution error = %v, want ErrIssueMaterializationConflict", err)
}
}
func TestWorkspaceIssueMaterializerRejectsNonCanonicalActionableOrdinals(t *testing.T) {
t.Parallel()
input := materializeIssueFixture()
input.ActionableItems[0].Ordinal = 0
if _, err := (WorkspaceIssueMaterializer{Issues: &recordingWorkspaceIssueTarget{}}).MaterializeIssue(context.Background(), input); !errors.Is(err, ErrInvalidInput) {
t.Fatalf("MaterializeIssue() invalid ordinal error = %v, want ErrInvalidInput", err)
}
}
func materializeIssueFixture() MaterializeIssueInput {
return MaterializeIssueInput{
WorkspaceID: "workspace-1",
WorkflowID: "workflow-1",
RevisionID: "revision-1",
SourceSessionID: "session-1",
Title: "Ship workflow",
Content: "Plan body",
TopicID: "topic-1",
Execution: PlanExecution{
Mode: "parallel",
ReasoningIntensity: 70,
OrchestrationIntensity: 80,
},
Budget: PlanBudget{Mode: "fixed", TokenLimit: 120_000, QuotaWaterlinePercent: 10},
ActionableItems: []ActionableItem{{Ordinal: 1, Task: PlanTask{
ID: "task-1",
Title: "Implement",
Content: "Build it",
Priority: "high",
AgentTargetID: "local:codex",
ModelPlanID: "plan-1",
Model: "gpt-5.4",
ExecutionDirectory: "/workspace/task-1",
DependsOn: []string{"task-0"},
}}},
}
}
func materializedIssueDetailFixture(input MaterializeIssueInput) workspaceissues.IssueDetail {
return workspaceissues.IssueDetail{
Issue: workspaceissues.Issue{
IssueID: "tutti-mode-plan-" + input.WorkflowID,
TopicID: input.TopicID,
Title: input.Title,
Content: input.Content,
PlanningSource: workspaceissues.PlanningSourceTuttiModePlan,
SourceSessionID: input.SourceSessionID,
SequentialExecution: input.Execution.Mode == "sequential",
ParallelExecution: input.Execution.Mode == "parallel",
ExecutionProfile: workspaceissues.ExecutionProfile{
ReasoningIntensity: input.Execution.ReasoningIntensity,
OrchestrationIntensity: input.Execution.OrchestrationIntensity,
},
Budget: workspaceissues.Budget{
Mode: workspaceissues.BudgetMode(input.Budget.Mode),
TokenLimit: input.Budget.TokenLimit,
QuotaWaterlinePercent: input.Budget.QuotaWaterlinePercent,
Status: workspaceissues.BudgetStatusActive,
},
},
Tasks: []workspaceissues.Task{{
TaskID: input.ActionableItems[0].Task.ID,
Title: input.ActionableItems[0].Task.Title,
Content: input.ActionableItems[0].Task.Content,
Priority: workspaceissues.Priority(input.ActionableItems[0].Task.Priority),
AgentTargetID: input.ActionableItems[0].Task.AgentTargetID,
ModelPlanID: input.ActionableItems[0].Task.ModelPlanID,
Model: input.ActionableItems[0].Task.Model,
ExecutionDirectory: input.ActionableItems[0].Task.ExecutionDirectory,
DependencyTaskIDs: append([]string(nil), input.ActionableItems[0].Task.DependsOn...),
SortIndex: input.ActionableItems[0].Ordinal,
}},
}
}
func materializedExecutionFixture(t *testing.T, input MaterializeIssueInput) executionbiz.Aggregate {
t.Helper()
aggregate, err := executionbiz.NewInitialAggregate(
input.WorkspaceID,
"tutti-mode-plan-"+input.WorkflowID,
input.WorkflowID,
input.SourceSessionID,
time.UnixMilli(1_700_000_000_000).UTC(),
)
if err != nil {
t.Fatalf("NewInitialAggregate() error = %v", err)
}
return aggregate
}