package dryrun import ( "fmt" "sync/atomic" "time" "github.com/yaoapp/yao/agent/robot/executor/types" robottypes "github.com/yaoapp/yao/agent/robot/types" ) // Executor implements a dry-run executor that simulates execution // without making real Agent calls. Useful for: // - Testing scheduling and concurrency logic // - Demo and preview modes // - Debugging execution flow // - Performance testing type Executor struct { config types.DryRunConfig execCount atomic.Int32 currentCount atomic.Int32 } // New creates a new dry-run executor with default settings func New() *Executor { return &Executor{} } // NewWithDelay creates a dry-run executor with specified delay func NewWithDelay(delay time.Duration) *Executor { return &Executor{ config: types.DryRunConfig{ Delay: delay, }, } } // NewWithConfig creates a dry-run executor with full configuration func NewWithConfig(config types.DryRunConfig) *Executor { return &Executor{ config: config, } } // Execute simulates robot execution without real Agent calls (auto-generates ID) func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, trigger robottypes.TriggerType, data interface{}) (*robottypes.Execution, error) { return e.ExecuteWithControl(ctx, robot, trigger, data, "", nil) } // ExecuteWithID simulates robot execution with a pre-generated execution ID (no control) func (e *Executor) ExecuteWithID(ctx *robottypes.Context, robot *robottypes.Robot, trigger robottypes.TriggerType, data interface{}, execID string) (*robottypes.Execution, error) { return e.ExecuteWithControl(ctx, robot, trigger, data, execID, nil) } // ExecuteWithControl simulates robot execution with execution control func (e *Executor) ExecuteWithControl(ctx *robottypes.Context, robot *robottypes.Robot, trigger robottypes.TriggerType, data interface{}, execID string, control robottypes.ExecutionControl) (*robottypes.Execution, error) { if robot == nil { return nil, fmt.Errorf("robot cannot be nil") } // Determine starting phase startPhaseIndex := 0 if trigger == robottypes.TriggerHuman || trigger == robottypes.TriggerEvent { startPhaseIndex = 1 // Skip P0 } // Use provided execID or generate new one if execID == "" { execID = fmt.Sprintf("dryrun_%d", time.Now().UnixNano()) } // Create execution record exec := &robottypes.Execution{ ID: execID, MemberID: robot.MemberID, TeamID: robot.TeamID, TriggerType: trigger, StartTime: time.Now(), Status: robottypes.ExecPending, Phase: robottypes.AllPhases[startPhaseIndex], Input: types.BuildTriggerInput(trigger, data), } // Set robot reference exec.SetRobot(robot) // Acquire slot if !robot.TryAcquireSlot(exec) { return nil, robottypes.ErrQuotaExceeded } defer robot.RemoveExecution(exec.ID) // Track counts e.execCount.Add(1) e.currentCount.Add(1) defer e.currentCount.Add(-1) // Start callback if e.config.OnStart != nil { e.config.OnStart() } if e.config.OnEnd != nil { defer e.config.OnEnd() } // Update status exec.Status = robottypes.ExecRunning // Simulate execution delay (once for entire execution, not per-phase) if e.config.Delay > 0 { time.Sleep(e.config.Delay) } // Check for simulated failure if dataStr, ok := data.(string); ok && dataStr == "simulate_failure" { exec.Status = robottypes.ExecFailed exec.Error = "simulated failure" return exec, nil } // Execute phases with mock data phases := robottypes.AllPhases[startPhaseIndex:] for _, phase := range phases { // Check if cancelled select { case <-ctx.Context.Done(): exec.Status = robottypes.ExecCancelled exec.Error = "execution cancelled" return exec, nil default: } // Wait if paused if control != nil { if err := control.WaitIfPaused(); err != nil { exec.Status = robottypes.ExecCancelled exec.Error = "execution cancelled while paused" return exec, nil } } exec.Phase = phase // Phase start callback if e.config.OnPhaseStart != nil { e.config.OnPhaseStart(phase) } // Generate mock output e.mockPhaseOutput(exec, phase) // Phase end callback if e.config.OnPhaseEnd != nil { e.config.OnPhaseEnd(phase) } } // Mark completed exec.Status = robottypes.ExecCompleted now := time.Now() exec.EndTime = &now return exec, nil } // mockPhaseOutput generates mock output for each phase func (e *Executor) mockPhaseOutput(exec *robottypes.Execution, phase robottypes.Phase) { switch phase { case robottypes.PhaseInspiration: exec.Inspiration = &robottypes.InspirationReport{ Clock: robottypes.NewClockContext(time.Now(), ""), Content: "## Dry-Run Inspiration\n\nThis is a simulated inspiration report for testing.", } case robottypes.PhaseGoals: exec.Goals = &robottypes.Goals{ Content: "## Dry-Run Goals\n\n1. [High] Simulated goal for testing", } case robottypes.PhaseTasks: exec.Tasks = []robottypes.Task{ { ID: "dryrun-task-1", GoalRef: "Goal 1", Source: robottypes.TaskSourceAuto, ExecutorType: robottypes.ExecutorAssistant, ExecutorID: "mock-agent", Status: robottypes.TaskPending, }, } case robottypes.PhaseRun: exec.Results = []robottypes.TaskResult{ { TaskID: "dryrun-task-1", Success: true, Output: map[string]interface{}{"mode": "dryrun", "result": "simulated"}, Duration: 100, Validation: &robottypes.ValidationResult{ Passed: true, Score: 1.0, }, }, } case robottypes.PhaseDelivery: exec.Delivery = &robottypes.DeliveryResult{ RequestID: "dryrun-" + exec.ID, Content: &robottypes.DeliveryContent{ Summary: "Dry-run delivery completed", Body: "# Dry-run Delivery\n\nThis is a simulated delivery result.", }, Success: true, } case robottypes.PhaseLearning: exec.Learning = []robottypes.LearningEntry{ { Type: robottypes.LearnExecution, Content: "Dry-run execution completed successfully", }, } } } // ExecCount returns total execution count func (e *Executor) ExecCount() int { return int(e.execCount.Load()) } // CurrentCount returns currently running execution count func (e *Executor) CurrentCount() int { return int(e.currentCount.Load()) } // Reset resets the executor counters func (e *Executor) Reset() { e.execCount.Store(0) e.currentCount.Store(0) } // Resume is not supported in dry-run mode func (e *Executor) Resume(ctx *robottypes.Context, execID string, reply string) error { return fmt.Errorf("resume is not supported in dry-run executor") } // Verify Executor implements types.Executor var _ types.Executor = (*Executor)(nil)