📄 runner.ts  •  6435 bytes
/**
 * 执行计划 - 计划执行器
 * Phase 2: 分步执行 + 状态追踪
 */
import { type ExecutionPlan, type PlanStep, type StepStatus } from './types'
import { executeTool as realExecuteTool, type ToolCall, type ToolResult } from '../tools.js'

/** 步骤执行结果 */
export interface StepResult {
  stepId: number
  success: boolean
  output: string
  error?: string
  duration: number
}

/** 执行器选项 */
export interface RunnerOptions {
  stopOnError: boolean       // 遇错停止
  confirmEachStep: boolean   // 每步确认
  verbose: boolean          // 详细输出
}

/** 默认选项 */
const DEFAULT_OPTIONS: RunnerOptions = {
  stopOnError: true,
  confirmEachStep: false,
  verbose: true,
}

/** 工具执行计数器(用于生成唯一ID) */
let toolCallCounter = 0

/**
 * 真实工具执行 — 将 PlanStep 的工具调用桥接到 tools.ts 的真实执行器
 * PlanStep.params (object) → ToolCall.arguments (JSON string)
 */
async function executeTool(
  tool: string,
  params: Record<string, any>,
  verbose: boolean
): Promise<{ success: boolean; output: string; error?: string }> {
  if (verbose) {
    console.log(`  \x1b[38;5;240m🔧 执行: ${tool} ${JSON.stringify(params).slice(0, 80)}...\x1b[0m`)
  }

  // 构造 ToolCall 格式(与 tools.ts 的 ToolCall 接口对齐)
  const toolCall: ToolCall = {
    id: `pavr_${++toolCallCounter}_${Date.now()}`,
    name: tool,
    arguments: JSON.stringify(params),  // ToolCall.arguments 是 JSON 字符串
  }

  try {
    const result: ToolResult = await realExecuteTool(toolCall)

    // 判断结果是否为错误
    const isError = result.content.startsWith('Error:')
    return {
      success: !isError,
      output: result.content,
      error: isError ? result.content : undefined,
    }
  } catch (err: any) {
    return {
      success: false,
      output: '',
      error: err.message || String(err),
    }
  }
}

/**
 * 执行单个步骤
 */
async function executeStep(
  step: PlanStep,
  options: RunnerOptions
): Promise<StepResult> {
  const startTime = Date.now()
  
  try {
    // 更新状态为执行中
    step.status = 'running'
    
    // 执行工具
    const result = await executeTool(step.tool, step.params, options.verbose)
    
    const duration = Date.now() - startTime
    
    if (result.success) {
      step.status = 'completed'
      step.result = result.output
      
      return {
        stepId: step.id,
        success: true,
        output: result.output,
        duration,
      }
    } else {
      step.status = 'failed'
      step.error = result.error
      
      return {
        stepId: step.id,
        success: false,
        output: '',
        error: result.error,
        duration,
      }
    }
  } catch (err: any) {
    step.status = 'failed'
    step.error = err.message
    
    return {
      stepId: step.id,
      success: false,
      output: '',
      error: err.message,
      duration: Date.now() - startTime,
    }
  }
}

/**
 * 执行计划
 * @param plan 执行计划
 * @param options 执行选项
 * @param onStepComplete 每步完成回调
 * @returns 执行结果
 */
export async function executePlan(
  plan: ExecutionPlan,
  options: Partial<RunnerOptions> = {},
  onStepComplete?: (step: PlanStep, result: StepResult) => void
): Promise<{
  success: boolean
  completedSteps: number
  failedSteps: number
  totalDuration: number
}> {
  const opts = { ...DEFAULT_OPTIONS, ...options }
  const startTime = Date.now()
  
  plan.status = 'executing'
  
  let completedSteps = 0
  let failedSteps = 0
  
  console.log('')
  console.log(`  \x1b[38;5;220m▶️ 开始执行计划\x1b[0m`)
  console.log(`  \x1b[38;5;240m步骤总数: ${plan.steps.length}\x1b[0m`)
  console.log('')
  
  for (let i = 0; i < plan.steps.length; i++) {
    const step = plan.steps[i]
    plan.currentStep = i + 1
    
    if (opts.verbose) {
      console.log(`  \x1b[38;5;220m[${step.id}/${plan.steps.length}]\x1b[0m ${step.action}`)
    }
    
    // 执行步骤
    const result = await executeStep(step, opts)
    
    if (result.success) {
      completedSteps++
      if (opts.verbose) {
        console.log(`    \x1b[38;5;208m✅ 完成 (${result.duration}ms)\x1b[0m`)
      }
    } else {
      failedSteps++
      if (opts.verbose) {
        console.log(`    \x1b[38;5;196m❌ 失败: ${result.error}\x1b[0m`)
      }
      
      // 如果配置了遇错停止
      if (opts.stopOnError && step.retryCount === 0) {
        // 尝试重试
        step.retryCount++
        step.status = 'pending'
        console.log(`    \x1b[38;5;208m🔄 重试中...\x1b[0m`)
        
        const retryResult = await executeStep(step, opts)
        if (retryResult.success) {
          completedSteps++
          failedSteps--
          if (opts.verbose) {
            console.log(`    \x1b[38;5;208m✅ 重试成功\x1b[0m`)
          }
        } else if (opts.stopOnError) {
          console.log(`    \x1b[38;5;196m⛔ 停止执行\x1b[0m`)
          break
        }
      } else if (opts.stopOnError) {
        break
      }
    }
    
    // 回调
    if (onStepComplete) {
      onStepComplete(step, result)
    }
    
    console.log('')
  }
  
  // 更新计划状态
  if (failedSteps === 0) {
    plan.status = 'completed'
  } else if (completedSteps > 0) {
    plan.status = 'executing' // 部分完成
  } else {
    plan.status = 'cancelled'
  }
  
  const totalDuration = Date.now() - startTime
  
  console.log('')
  console.log(`  \x1b[38;5;220m🏁 执行完成\x1b[0m`)
  console.log(`  \x1b[38;5;208m✅ 成功: ${completedSteps}\x1b[0m`)
  if (failedSteps > 0) {
    console.log(`  \x1b[38;5;196m❌ 失败: ${failedSteps}\x1b[0m`)
  }
  console.log(`  \x1b[38;5;240m⏱️ 总耗时: ${totalDuration}ms\x1b[0m`)
  console.log('')
  
  return {
    success: failedSteps === 0,
    completedSteps,
    failedSteps,
    totalDuration,
  }
}

/**
 * 获取计划执行摘要
 */
export function getPlanSummary(plan: ExecutionPlan): string {
  const completed = plan.steps.filter(s => s.status === 'completed').length
  const failed = plan.steps.filter(s => s.status === 'failed').length
  const pending = plan.steps.filter(s => s.status === 'pending').length
  
  return `
📊 **计划执行摘要**
- 总步骤: ${plan.steps.length}
- ✅ 完成: ${completed}
- ❌ 失败: ${failed}
- ⏳ 待执行: ${pending}
- 当前: 第 ${plan.currentStep} 步
`
}