Skip to content

Tasks API

The Tasks API provides functionality for defining, executing, and managing tasks within the development environment.

Overview

The Tasks API enables you to:

  • Define custom tasks for build, test, and deployment
  • Execute tasks programmatically
  • Monitor task execution and output
  • Integrate with external tools and scripts
  • Provide task templates and configurations
  • Handle task dependencies and scheduling
  • Support different task types (shell, process, custom)

Basic Usage

Task Manager

typescript
import { TraeAPI } from '@trae/api';

// Task manager
class TaskManager {
  private tasks: Map<string, TaskDefinition> = new Map();
  private runningTasks: Map<string, RunningTask> = new Map();
  private taskHistory: TaskHistoryEntry[] = [];
  private eventEmitter = new TraeAPI.EventEmitter<TaskEvent>();
  private taskProviders: TaskProvider[] = [];
  private taskTemplates: Map<string, TaskTemplate> = new Map();

  constructor() {
    this.initializeBuiltInTasks();
    this.setupTaskProviders();
    this.loadTaskTemplates();
  }

  // Initialize built-in tasks
  private initializeBuiltInTasks(): void {
    // Build tasks
    this.registerTask({
      id: 'build',
      label: 'Build Project',
      type: 'shell',
      command: 'npm',
      args: ['run', 'build'],
      group: 'build',
      presentation: {
        echo: true,
        reveal: 'always',
        focus: false,
        panel: 'shared'
      },
      problemMatcher: ['$tsc']
    });

    // Test tasks
    this.registerTask({
      id: 'test',
      label: 'Run Tests',
      type: 'shell',
      command: 'npm',
      args: ['test'],
      group: 'test',
      presentation: {
        echo: true,
        reveal: 'always',
        focus: true,
        panel: 'dedicated'
      },
      problemMatcher: ['$jest']
    });

    // Lint tasks
    this.registerTask({
      id: 'lint',
      label: 'Lint Code',
      type: 'shell',
      command: 'npm',
      args: ['run', 'lint'],
      group: 'build',
      presentation: {
        echo: true,
        reveal: 'silent',
        focus: false,
        panel: 'shared'
      },
      problemMatcher: ['$eslint-stylish']
    });

    // Development server
    this.registerTask({
      id: 'dev',
      label: 'Start Development Server',
      type: 'shell',
      command: 'npm',
      args: ['run', 'dev'],
      group: 'build',
      isBackground: true,
      presentation: {
        echo: true,
        reveal: 'always',
        focus: false,
        panel: 'dedicated'
      },
      problemMatcher: {
        pattern: {
          regexp: '^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$',
          file: 1,
          line: 2,
          column: 3,
          severity: 4,
          message: 5
        },
        background: {
          activeOnStart: true,
          beginsPattern: '^.*compilation starting.*$',
          endsPattern: '^.*compilation complete.*$'
        }
      }
    });
  }

  // Register a task
  registerTask(definition: TaskDefinition): void {
    this.tasks.set(definition.id, definition);
    this.eventEmitter.fire({ type: 'taskRegistered', task: definition });
  }

  // Unregister a task
  unregisterTask(taskId: string): boolean {
    const task = this.tasks.get(taskId);
    if (task) {
      this.tasks.delete(taskId);
      this.eventEmitter.fire({ type: 'taskUnregistered', taskId });
      return true;
    }
    return false;
  }

  // Get all tasks
  getTasks(): TaskDefinition[] {
    return Array.from(this.tasks.values());
  }

  // Get task by ID
  getTask(taskId: string): TaskDefinition | undefined {
    return this.tasks.get(taskId);
  }

  // Get tasks by group
  getTasksByGroup(group: string): TaskDefinition[] {
    return this.getTasks().filter(task => task.group === group);
  }

  // Execute a task
  async executeTask(taskId: string, options: TaskExecutionOptions = {}): Promise<TaskExecution> {
    const task = this.tasks.get(taskId);
    if (!task) {
      throw new Error(`Task '${taskId}' not found`);
    }

    // Check if task is already running and not allowed to run multiple instances
    if (this.runningTasks.has(taskId) && !task.allowMultiple) {
      throw new Error(`Task '${taskId}' is already running`);
    }

    const executionId = this.generateExecutionId();
    const execution = new TaskExecution(executionId, task, options);

    try {
      // Resolve task variables
      const resolvedTask = await this.resolveTaskVariables(task, options);
      
      // Create running task
      const runningTask: RunningTask = {
        id: executionId,
        task: resolvedTask,
        execution,
        startTime: Date.now(),
        status: 'running'
      };

      this.runningTasks.set(executionId, runningTask);
      this.eventEmitter.fire({ type: 'taskStarted', execution });

      // Execute based on task type
      let result: TaskResult;
      switch (resolvedTask.type) {
        case 'shell':
          result = await this.executeShellTask(resolvedTask, execution);
          break;
        case 'process':
          result = await this.executeProcessTask(resolvedTask, execution);
          break;
        case 'custom':
          result = await this.executeCustomTask(resolvedTask, execution);
          break;
        default:
          throw new Error(`Unsupported task type: ${resolvedTask.type}`);
      }

      // Update running task
      runningTask.status = result.success ? 'completed' : 'failed';
      runningTask.endTime = Date.now();
      runningTask.result = result;

      // Add to history
      this.addToHistory({
        taskId,
        executionId,
        startTime: runningTask.startTime,
        endTime: runningTask.endTime!,
        duration: runningTask.endTime! - runningTask.startTime,
        success: result.success,
        exitCode: result.exitCode
      });

      // Fire completion event
      this.eventEmitter.fire({ 
        type: result.success ? 'taskCompleted' : 'taskFailed', 
        execution,
        result 
      });

      // Remove from running tasks
      this.runningTasks.delete(executionId);

      return execution;
    } catch (error) {
      // Handle execution error
      const runningTask = this.runningTasks.get(executionId);
      if (runningTask) {
        runningTask.status = 'failed';
        runningTask.endTime = Date.now();
        runningTask.error = error as Error;
      }

      this.eventEmitter.fire({ 
        type: 'taskFailed', 
        execution,
        error: error as Error 
      });

      this.runningTasks.delete(executionId);
      throw error;
    }
  }

  // Execute shell task
  private async executeShellTask(task: ResolvedTaskDefinition, execution: TaskExecution): Promise<TaskResult> {
    const options: TraeAPI.ProcessExecutionOptions = {
      cwd: task.options?.cwd || TraeAPI.workspace.rootPath,
      env: { ...process.env, ...task.options?.env },
      shell: task.options?.shell !== false
    };

    const args = task.args || [];
    const fullCommand = `${task.command} ${args.join(' ')}`;

    execution.appendOutput(`> Executing task: ${task.label}\n`);
    execution.appendOutput(`> ${fullCommand}\n\n`);

    return new Promise((resolve) => {
      const process = TraeAPI.tasks.executeProcess(task.command, args, options);
      
      let output = '';
      let errorOutput = '';

      process.onDidWrite(data => {
        output += data;
        execution.appendOutput(data);
      });

      process.onDidWriteErr(data => {
        errorOutput += data;
        execution.appendOutput(data);
      });

      process.onDidClose(exitCode => {
        const success = exitCode === 0;
        
        execution.appendOutput(`\n> Task ${success ? 'completed' : 'failed'} with exit code ${exitCode}\n`);
        
        resolve({
          success,
          exitCode,
          output,
          errorOutput
        });
      });

      // Store process reference for potential termination
      execution.setProcess(process);
    });
  }

  // Execute process task
  private async executeProcessTask(task: ResolvedTaskDefinition, execution: TaskExecution): Promise<TaskResult> {
    // Similar to shell task but without shell interpretation
    const options: TraeAPI.ProcessExecutionOptions = {
      cwd: task.options?.cwd || TraeAPI.workspace.rootPath,
      env: { ...process.env, ...task.options?.env },
      shell: false
    };

    const args = task.args || [];
    
    execution.appendOutput(`> Executing process: ${task.label}\n`);
    execution.appendOutput(`> ${task.command} ${args.join(' ')}\n\n`);

    return new Promise((resolve) => {
      const process = TraeAPI.tasks.executeProcess(task.command, args, options);
      
      let output = '';
      let errorOutput = '';

      process.onDidWrite(data => {
        output += data;
        execution.appendOutput(data);
      });

      process.onDidWriteErr(data => {
        errorOutput += data;
        execution.appendOutput(data);
      });

      process.onDidClose(exitCode => {
        const success = exitCode === 0;
        
        execution.appendOutput(`\n> Process ${success ? 'completed' : 'failed'} with exit code ${exitCode}\n`);
        
        resolve({
          success,
          exitCode,
          output,
          errorOutput
        });
      });

      execution.setProcess(process);
    });
  }

  // Execute custom task
  private async executeCustomTask(task: ResolvedTaskDefinition, execution: TaskExecution): Promise<TaskResult> {
    if (!task.customExecution) {
      throw new Error('Custom task must have customExecution function');
    }

    execution.appendOutput(`> Executing custom task: ${task.label}\n\n`);

    try {
      const result = await task.customExecution(execution);
      
      execution.appendOutput(`\n> Custom task ${result.success ? 'completed' : 'failed'}\n`);
      
      return result;
    } catch (error) {
      execution.appendOutput(`\n> Custom task failed: ${error}\n`);
      
      return {
        success: false,
        exitCode: 1,
        output: '',
        errorOutput: error instanceof Error ? error.message : String(error)
      };
    }
  }

  // Resolve task variables
  private async resolveTaskVariables(task: TaskDefinition, options: TaskExecutionOptions): Promise<ResolvedTaskDefinition> {
    const variables = {
      workspaceFolder: TraeAPI.workspace.rootPath || '',
      workspaceFolderBasename: TraeAPI.path.basename(TraeAPI.workspace.rootPath || ''),
      file: TraeAPI.window.activeTextEditor?.document.uri.fsPath || '',
      fileBasename: TraeAPI.path.basename(TraeAPI.window.activeTextEditor?.document.uri.fsPath || ''),
      fileBasenameNoExtension: TraeAPI.path.parse(TraeAPI.window.activeTextEditor?.document.uri.fsPath || '').name,
      fileDirname: TraeAPI.path.dirname(TraeAPI.window.activeTextEditor?.document.uri.fsPath || ''),
      fileExtname: TraeAPI.path.extname(TraeAPI.window.activeTextEditor?.document.uri.fsPath || ''),
      cwd: process.cwd(),
      ...options.variables
    };

    const resolveString = (str: string): string => {
      return str.replace(/\$\{([^}]+)\}/g, (match, varName) => {
        return variables[varName] || match;
      });
    };

    const resolveArray = (arr: string[]): string[] => {
      return arr.map(resolveString);
    };

    return {
      ...task,
      command: resolveString(task.command),
      args: task.args ? resolveArray(task.args) : undefined,
      options: task.options ? {
        ...task.options,
        cwd: task.options.cwd ? resolveString(task.options.cwd) : undefined,
        env: task.options.env ? Object.fromEntries(
          Object.entries(task.options.env).map(([key, value]) => [key, resolveString(value)])
        ) : undefined
      } : undefined
    };
  }

  // Terminate a running task
  async terminateTask(executionId: string): Promise<boolean> {
    const runningTask = this.runningTasks.get(executionId);
    if (!runningTask) {
      return false;
    }

    try {
      await runningTask.execution.terminate();
      
      runningTask.status = 'terminated';
      runningTask.endTime = Date.now();
      
      this.eventEmitter.fire({ 
        type: 'taskTerminated', 
        execution: runningTask.execution 
      });
      
      this.runningTasks.delete(executionId);
      return true;
    } catch (error) {
      console.error('Failed to terminate task:', error);
      return false;
    }
  }

  // Get running tasks
  getRunningTasks(): RunningTask[] {
    return Array.from(this.runningTasks.values());
  }

  // Get task history
  getTaskHistory(limit?: number): TaskHistoryEntry[] {
    const history = [...this.taskHistory].reverse(); // Most recent first
    return limit ? history.slice(0, limit) : history;
  }

  // Clear task history
  clearTaskHistory(): void {
    this.taskHistory = [];
  }

  // Add to history
  private addToHistory(entry: TaskHistoryEntry): void {
    this.taskHistory.push(entry);
    
    // Limit history size
    if (this.taskHistory.length > 1000) {
      this.taskHistory = this.taskHistory.slice(-500); // Keep last 500 entries
    }
  }

  // Generate execution ID
  private generateExecutionId(): string {
    return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  // Task providers
  registerTaskProvider(provider: TaskProvider): TraeAPI.Disposable {
    this.taskProviders.push(provider);
    
    return {
      dispose: () => {
        const index = this.taskProviders.indexOf(provider);
        if (index !== -1) {
          this.taskProviders.splice(index, 1);
        }
      }
    };
  }

  private setupTaskProviders(): void {
    // Built-in npm task provider
    this.registerTaskProvider(new NpmTaskProvider());
    
    // Built-in script task provider
    this.registerTaskProvider(new ScriptTaskProvider());
  }

  // Get tasks from providers
  async getProvidedTasks(): Promise<TaskDefinition[]> {
    const allTasks: TaskDefinition[] = [];
    
    for (const provider of this.taskProviders) {
      try {
        const tasks = await provider.provideTasks();
        allTasks.push(...tasks);
      } catch (error) {
        console.error('Task provider failed:', error);
      }
    }
    
    return allTasks;
  }

  // Task templates
  private loadTaskTemplates(): void {
    // Build templates
    this.taskTemplates.set('npm-build', {
      id: 'npm-build',
      name: 'NPM Build',
      description: 'Build project using npm',
      template: {
        type: 'shell',
        command: 'npm',
        args: ['run', 'build'],
        group: 'build',
        presentation: {
          echo: true,
          reveal: 'always',
          focus: false,
          panel: 'shared'
        }
      }
    });

    this.taskTemplates.set('npm-test', {
      id: 'npm-test',
      name: 'NPM Test',
      description: 'Run tests using npm',
      template: {
        type: 'shell',
        command: 'npm',
        args: ['test'],
        group: 'test',
        presentation: {
          echo: true,
          reveal: 'always',
          focus: true,
          panel: 'dedicated'
        }
      }
    });

    this.taskTemplates.set('docker-build', {
      id: 'docker-build',
      name: 'Docker Build',
      description: 'Build Docker image',
      template: {
        type: 'shell',
        command: 'docker',
        args: ['build', '-t', '${workspaceFolderBasename}', '.'],
        group: 'build',
        presentation: {
          echo: true,
          reveal: 'always',
          focus: false,
          panel: 'shared'
        }
      }
    });

    this.taskTemplates.set('python-run', {
      id: 'python-run',
      name: 'Python Run',
      description: 'Run Python script',
      template: {
        type: 'shell',
        command: 'python',
        args: ['${file}'],
        group: 'build',
        presentation: {
          echo: true,
          reveal: 'always',
          focus: true,
          panel: 'dedicated'
        }
      }
    });
  }

  // Get task templates
  getTaskTemplates(): TaskTemplate[] {
    return Array.from(this.taskTemplates.values());
  }

  // Create task from template
  createTaskFromTemplate(templateId: string, customization: Partial<TaskDefinition>): TaskDefinition {
    const template = this.taskTemplates.get(templateId);
    if (!template) {
      throw new Error(`Task template '${templateId}' not found`);
    }

    const taskId = customization.id || `${templateId}_${Date.now()}`;
    const label = customization.label || template.name;

    return {
      id: taskId,
      label,
      ...template.template,
      ...customization
    };
  }

  // Event handling
  onDidStartTask(listener: (execution: TaskExecution) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(event => {
      if (event.type === 'taskStarted') {
        listener(event.execution);
      }
    });
  }

  onDidEndTask(listener: (execution: TaskExecution, result?: TaskResult) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(event => {
      if (event.type === 'taskCompleted' || event.type === 'taskFailed' || event.type === 'taskTerminated') {
        listener(event.execution, event.result);
      }
    });
  }

  onDidChangeTask(listener: (event: TaskEvent) => void): TraeAPI.Disposable {
    return this.eventEmitter.event(listener);
  }

  // Dispose
  dispose(): void {
    // Terminate all running tasks
    for (const [executionId] of this.runningTasks) {
      this.terminateTask(executionId);
    }
    
    this.tasks.clear();
    this.runningTasks.clear();
    this.taskHistory = [];
    this.taskProviders = [];
    this.taskTemplates.clear();
    
    this.eventEmitter.dispose();
  }
}

// Task execution class
class TaskExecution {
  private output = '';
  private process: TraeAPI.ProcessExecution | null = null;
  private outputChannel: TraeAPI.OutputChannel;

  constructor(
    public readonly id: string,
    public readonly task: TaskDefinition,
    public readonly options: TaskExecutionOptions
  ) {
    this.outputChannel = TraeAPI.window.createOutputChannel(`Task: ${task.label}`);
    
    if (task.presentation?.reveal === 'always' || 
        (task.presentation?.reveal === 'silent' && task.presentation?.focus)) {
      this.outputChannel.show(task.presentation?.focus);
    }
  }

  appendOutput(data: string): void {
    this.output += data;
    this.outputChannel.append(data);
  }

  getOutput(): string {
    return this.output;
  }

  setProcess(process: TraeAPI.ProcessExecution): void {
    this.process = process;
  }

  async terminate(): Promise<void> {
    if (this.process) {
      await this.process.terminate();
    }
  }

  dispose(): void {
    this.outputChannel.dispose();
  }
}

// NPM task provider
class NpmTaskProvider implements TaskProvider {
  async provideTasks(): Promise<TaskDefinition[]> {
    const tasks: TaskDefinition[] = [];
    
    try {
      const packageJsonPath = TraeAPI.path.join(TraeAPI.workspace.rootPath || '', 'package.json');
      const packageJsonUri = TraeAPI.Uri.file(packageJsonPath);
      
      const packageJsonContent = await TraeAPI.workspace.fs.readFile(packageJsonUri);
      const packageJson = JSON.parse(packageJsonContent.toString());
      
      if (packageJson.scripts) {
        for (const [scriptName, scriptCommand] of Object.entries(packageJson.scripts)) {
          tasks.push({
            id: `npm:${scriptName}`,
            label: `npm: ${scriptName}`,
            type: 'shell',
            command: 'npm',
            args: ['run', scriptName],
            group: this.getScriptGroup(scriptName),
            detail: scriptCommand as string,
            presentation: {
              echo: true,
              reveal: 'always',
              focus: false,
              panel: 'shared'
            }
          });
        }
      }
    } catch (error) {
      // No package.json or invalid JSON
    }
    
    return tasks;
  }

  private getScriptGroup(scriptName: string): string {
    if (scriptName.includes('build')) return 'build';
    if (scriptName.includes('test')) return 'test';
    if (scriptName.includes('lint')) return 'build';
    if (scriptName.includes('dev') || scriptName.includes('start')) return 'build';
    return 'build';
  }
}

// Script task provider
class ScriptTaskProvider implements TaskProvider {
  async provideTasks(): Promise<TaskDefinition[]> {
    const tasks: TaskDefinition[] = [];
    
    try {
      const workspaceFolder = TraeAPI.workspace.workspaceFolders?.[0];
      if (!workspaceFolder) return tasks;
      
      // Look for common script files
      const scriptPatterns = ['**/*.sh', '**/*.bat', '**/*.cmd', '**/*.ps1'];
      
      for (const pattern of scriptPatterns) {
        const files = await TraeAPI.workspace.findFiles(pattern, '**/node_modules/**');
        
        for (const file of files) {
          const relativePath = TraeAPI.workspace.asRelativePath(file);
          const fileName = TraeAPI.path.basename(file.fsPath);
          const extension = TraeAPI.path.extname(fileName);
          
          let command: string;
          let args: string[] = [];
          
          switch (extension) {
            case '.sh':
              command = 'bash';
              args = [file.fsPath];
              break;
            case '.bat':
            case '.cmd':
              command = file.fsPath;
              break;
            case '.ps1':
              command = 'powershell';
              args = ['-File', file.fsPath];
              break;
            default:
              continue;
          }
          
          tasks.push({
            id: `script:${relativePath}`,
            label: `Script: ${fileName}`,
            type: 'shell',
            command,
            args,
            group: 'build',
            detail: relativePath,
            presentation: {
              echo: true,
              reveal: 'always',
              focus: false,
              panel: 'shared'
            }
          });
        }
      }
    } catch (error) {
      console.error('Failed to provide script tasks:', error);
    }
    
    return tasks;
  }
}

// Initialize task manager
const taskManager = new TaskManager();

Task UI Integration

typescript
// Task UI provider
class TaskUIProvider {
  private taskPanel: TraeAPI.WebviewPanel | null = null;
  private statusBarItem: TraeAPI.StatusBarItem;

  constructor(private taskManager: TaskManager) {
    this.setupCommands();
    this.setupStatusBar();
    this.setupEventListeners();
  }

  private setupCommands(): void {
    // Register task commands
    TraeAPI.commands.registerCommand('tasks.showPanel', () => {
      this.showTaskPanel();
    });
    
    TraeAPI.commands.registerCommand('tasks.runTask', async () => {
      await this.showTaskPicker();
    });
    
    TraeAPI.commands.registerCommand('tasks.runBuildTask', async () => {
      await this.runTaskByGroup('build');
    });
    
    TraeAPI.commands.registerCommand('tasks.runTestTask', async () => {
      await this.runTaskByGroup('test');
    });
    
    TraeAPI.commands.registerCommand('tasks.terminateTask', async () => {
      await this.showRunningTaskPicker();
    });
    
    TraeAPI.commands.registerCommand('tasks.restartTask', async () => {
      await this.showRestartTaskPicker();
    });
  }

  private setupStatusBar(): void {
    this.statusBarItem = TraeAPI.window.createStatusBarItem(
      TraeAPI.StatusBarAlignment.Left,
      10
    );
    
    this.statusBarItem.text = '$(play) Tasks';
    this.statusBarItem.tooltip = 'Run Task';
    this.statusBarItem.command = 'tasks.runTask';
    this.statusBarItem.show();
  }

  private async showTaskPicker(): Promise<void> {
    const allTasks = this.taskManager.getTasks();
    const providedTasks = await this.taskManager.getProvidedTasks();
    const tasks = [...allTasks, ...providedTasks];
    
    if (tasks.length === 0) {
      TraeAPI.window.showInformationMessage('No tasks available');
      return;
    }
    
    const items = tasks.map(task => ({
      label: task.label,
      description: task.detail || task.command,
      detail: `Group: ${task.group || 'none'}`,
      task
    }));
    
    const selected = await TraeAPI.window.showQuickPick(items, {
      placeHolder: 'Select a task to run',
      matchOnDescription: true,
      matchOnDetail: true
    });
    
    if (selected) {
      try {
        await this.taskManager.executeTask(selected.task.id);
      } catch (error) {
        TraeAPI.window.showErrorMessage(`Failed to run task: ${error}`);
      }
    }
  }

  private async runTaskByGroup(group: string): Promise<void> {
    const tasks = this.taskManager.getTasksByGroup(group);
    const providedTasks = await this.taskManager.getProvidedTasks();
    const groupTasks = [...tasks, ...providedTasks.filter(t => t.group === group)];
    
    if (groupTasks.length === 0) {
      TraeAPI.window.showInformationMessage(`No ${group} tasks available`);
      return;
    }
    
    if (groupTasks.length === 1) {
      try {
        await this.taskManager.executeTask(groupTasks[0].id);
      } catch (error) {
        TraeAPI.window.showErrorMessage(`Failed to run task: ${error}`);
      }
      return;
    }
    
    const items = groupTasks.map(task => ({
      label: task.label,
      description: task.detail || task.command,
      task
    }));
    
    const selected = await TraeAPI.window.showQuickPick(items, {
      placeHolder: `Select a ${group} task to run`,
      matchOnDescription: true
    });
    
    if (selected) {
      try {
        await this.taskManager.executeTask(selected.task.id);
      } catch (error) {
        TraeAPI.window.showErrorMessage(`Failed to run task: ${error}`);
      }
    }
  }

  private async showRunningTaskPicker(): Promise<void> {
    const runningTasks = this.taskManager.getRunningTasks();
    
    if (runningTasks.length === 0) {
      TraeAPI.window.showInformationMessage('No tasks are currently running');
      return;
    }
    
    const items = runningTasks.map(runningTask => ({
      label: runningTask.task.label,
      description: `Running for ${Math.floor((Date.now() - runningTask.startTime) / 1000)}s`,
      detail: runningTask.task.command,
      runningTask
    }));
    
    const selected = await TraeAPI.window.showQuickPick(items, {
      placeHolder: 'Select a task to terminate',
      matchOnDescription: true
    });
    
    if (selected) {
      const confirmed = await TraeAPI.window.showWarningMessage(
        `Are you sure you want to terminate '${selected.runningTask.task.label}'?`,
        'Terminate',
        'Cancel'
      );
      
      if (confirmed === 'Terminate') {
        const success = await this.taskManager.terminateTask(selected.runningTask.id);
        if (success) {
          TraeAPI.window.showInformationMessage('Task terminated');
        } else {
          TraeAPI.window.showErrorMessage('Failed to terminate task');
        }
      }
    }
  }

  private async showRestartTaskPicker(): Promise<void> {
    const history = this.taskManager.getTaskHistory(10);
    
    if (history.length === 0) {
      TraeAPI.window.showInformationMessage('No recent tasks to restart');
      return;
    }
    
    const items = history.map(entry => {
      const task = this.taskManager.getTask(entry.taskId);
      if (!task) return null;
      
      return {
        label: task.label,
        description: `Last run: ${new Date(entry.endTime).toLocaleTimeString()}`,
        detail: `Duration: ${entry.duration}ms, ${entry.success ? 'Success' : 'Failed'}`,
        task
      };
    }).filter(Boolean) as any[];
    
    if (items.length === 0) {
      TraeAPI.window.showInformationMessage('No tasks available to restart');
      return;
    }
    
    const selected = await TraeAPI.window.showQuickPick(items, {
      placeHolder: 'Select a task to restart',
      matchOnDescription: true
    });
    
    if (selected) {
      try {
        await this.taskManager.executeTask(selected.task.id);
      } catch (error) {
        TraeAPI.window.showErrorMessage(`Failed to restart task: ${error}`);
      }
    }
  }

  private async showTaskPanel(): Promise<void> {
    if (this.taskPanel) {
      this.taskPanel.reveal();
      return;
    }

    this.taskPanel = TraeAPI.window.createWebviewPanel(
      'tasks',
      'Tasks',
      TraeAPI.ViewColumn.One,
      {
        enableScripts: true,
        retainContextWhenHidden: true
      }
    );

    this.taskPanel.webview.html = this.getTaskHTML();
    this.setupWebviewMessageHandling();

    this.taskPanel.onDidDispose(() => {
      this.taskPanel = null;
    });

    // Send initial data
    this.updateTaskPanel();
  }

  private getTaskHTML(): string {
    return `
      <!DOCTYPE html>
      <html>
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Tasks</title>
        <style>
          body {
            margin: 0;
            padding: 20px;
            background: #1e1e1e;
            color: #d4d4d4;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            font-size: 13px;
          }
          
          .tasks-container {
            max-width: 1000px;
            margin: 0 auto;
          }
          
          .section {
            margin-bottom: 30px;
          }
          
          .section-title {
            font-size: 16px;
            font-weight: 600;
            margin-bottom: 15px;
            color: #ffffff;
            border-bottom: 1px solid #3e3e42;
            padding-bottom: 8px;
          }
          
          .task-list {
            display: grid;
            gap: 10px;
          }
          
          .task-item {
            background: #2d2d30;
            border: 1px solid #3e3e42;
            border-radius: 6px;
            padding: 15px;
            cursor: pointer;
            transition: all 0.2s ease;
          }
          
          .task-item:hover {
            background: #37373d;
            border-color: #007acc;
          }
          
          .task-item.running {
            border-color: #f9c23c;
            background: #3d3a2a;
          }
          
          .task-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
          }
          
          .task-name {
            font-weight: 500;
            color: #ffffff;
          }
          
          .task-status {
            padding: 2px 8px;
            border-radius: 12px;
            font-size: 11px;
            font-weight: 500;
          }
          
          .task-status.running {
            background: #f9c23c;
            color: #000000;
          }
          
          .task-status.completed {
            background: #4caf50;
            color: #ffffff;
          }
          
          .task-status.failed {
            background: #f44336;
            color: #ffffff;
          }
          
          .task-command {
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            font-size: 12px;
            color: #cccccc;
            margin-bottom: 8px;
          }
          
          .task-details {
            display: flex;
            gap: 15px;
            font-size: 11px;
            color: #999999;
          }
          
          .task-actions {
            display: flex;
            gap: 8px;
            margin-top: 10px;
          }
          
          .task-button {
            padding: 4px 12px;
            background: #0e639c;
            border: none;
            color: #ffffff;
            border-radius: 3px;
            cursor: pointer;
            font-size: 11px;
          }
          
          .task-button:hover {
            background: #1177bb;
          }
          
          .task-button.danger {
            background: #d32f2f;
          }
          
          .task-button.danger:hover {
            background: #f44336;
          }
          
          .no-tasks {
            text-align: center;
            color: #cccccc;
            padding: 40px;
            background: #2d2d30;
            border-radius: 6px;
          }
          
          .history-item {
            background: #2d2d30;
            border: 1px solid #3e3e42;
            border-radius: 6px;
            padding: 12px;
            margin-bottom: 8px;
          }
          
          .history-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 5px;
          }
          
          .history-name {
            font-weight: 500;
          }
          
          .history-time {
            font-size: 11px;
            color: #999999;
          }
          
          .history-details {
            display: flex;
            gap: 15px;
            font-size: 11px;
            color: #cccccc;
          }
          
          .templates-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
            gap: 15px;
          }
          
          .template-item {
            background: #2d2d30;
            border: 1px solid #3e3e42;
            border-radius: 6px;
            padding: 15px;
            cursor: pointer;
            transition: all 0.2s ease;
          }
          
          .template-item:hover {
            background: #37373d;
            border-color: #007acc;
          }
          
          .template-name {
            font-weight: 500;
            color: #ffffff;
            margin-bottom: 8px;
          }
          
          .template-description {
            font-size: 12px;
            color: #cccccc;
            margin-bottom: 10px;
          }
          
          .template-command {
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            font-size: 11px;
            color: #999999;
          }
        </style>
      </head>
      <body>
        <div class="tasks-container">
          <div class="section">
            <div class="section-title">Running Tasks</div>
            <div id="runningTasks" class="task-list">
              <div class="no-tasks">No tasks are currently running</div>
            </div>
          </div>
          
          <div class="section">
            <div class="section-title">Available Tasks</div>
            <div id="availableTasks" class="task-list">
              <div class="no-tasks">Loading tasks...</div>
            </div>
          </div>
          
          <div class="section">
            <div class="section-title">Task Templates</div>
            <div id="taskTemplates" class="templates-grid">
              <div class="no-tasks">Loading templates...</div>
            </div>
          </div>
          
          <div class="section">
            <div class="section-title">Recent Tasks</div>
            <div id="taskHistory">
              <div class="no-tasks">No recent tasks</div>
            </div>
          </div>
        </div>
        
        <script>
          const vscode = acquireVsCodeApi();
          
          function runTask(taskId) {
            vscode.postMessage({
              type: 'runTask',
              taskId
            });
          }
          
          function terminateTask(executionId) {
            vscode.postMessage({
              type: 'terminateTask',
              executionId
            });
          }
          
          function createFromTemplate(templateId) {
            vscode.postMessage({
              type: 'createFromTemplate',
              templateId
            });
          }
          
          function updateRunningTasks(tasks) {
            const container = document.getElementById('runningTasks');
            
            if (tasks.length === 0) {
              container.innerHTML = '<div class="no-tasks">No tasks are currently running</div>';
              return;
            }
            
            container.innerHTML = tasks.map(task => `
              <div class="task-item running">
                <div class="task-header">
                  <div class="task-name">${task.task.label}</div>
                  <div class="task-status running">Running</div>
                </div>
                <div class="task-command">${task.task.command} ${(task.task.args || []).join(' ')}</div>
                <div class="task-details">
                  <span>Started: ${new Date(task.startTime).toLocaleTimeString()}</span>
                  <span>Duration: ${Math.floor((Date.now() - task.startTime) / 1000)}s</span>
                </div>
                <div class="task-actions">
                  <button class="task-button danger" onclick="terminateTask('${task.id}')">Terminate</button>
                </div>
              </div>
            `).join('');
          }
          
          function updateAvailableTasks(tasks) {
            const container = document.getElementById('availableTasks');
            
            if (tasks.length === 0) {
              container.innerHTML = '<div class="no-tasks">No tasks available</div>';
              return;
            }
            
            container.innerHTML = tasks.map(task => `
              <div class="task-item" onclick="runTask('${task.id}')">
                <div class="task-header">
                  <div class="task-name">${task.label}</div>
                </div>
                <div class="task-command">${task.command} ${(task.args || []).join(' ')}</div>
                <div class="task-details">
                  <span>Group: ${task.group || 'none'}</span>
                  <span>Type: ${task.type}</span>
                </div>
              </div>
            `).join('');
          }
          
          function updateTaskTemplates(templates) {
            const container = document.getElementById('taskTemplates');
            
            if (templates.length === 0) {
              container.innerHTML = '<div class="no-tasks">No templates available</div>';
              return;
            }
            
            container.innerHTML = templates.map(template => `
              <div class="template-item" onclick="createFromTemplate('${template.id}')">
                <div class="template-name">${template.name}</div>
                <div class="template-description">${template.description}</div>
                <div class="template-command">${template.template.command} ${(template.template.args || []).join(' ')}</div>
              </div>
            `).join('');
          }
          
          function updateTaskHistory(history) {
            const container = document.getElementById('taskHistory');
            
            if (history.length === 0) {
              container.innerHTML = '<div class="no-tasks">No recent tasks</div>';
              return;
            }
            
            container.innerHTML = history.map(entry => `
              <div class="history-item">
                <div class="history-header">
                  <div class="history-name">${entry.taskId}</div>
                  <div class="history-time">${new Date(entry.endTime).toLocaleString()}</div>
                </div>
                <div class="history-details">
                  <span>Duration: ${entry.duration}ms</span>
                  <span>Status: ${entry.success ? 'Success' : 'Failed'}</span>
                  <span>Exit Code: ${entry.exitCode}</span>
                </div>
              </div>
            `).join('');
          }
          
          // Handle messages from extension
          window.addEventListener('message', event => {
            const message = event.data;
            
            switch (message.type) {
              case 'updateRunningTasks':
                updateRunningTasks(message.tasks);
                break;
              case 'updateAvailableTasks':
                updateAvailableTasks(message.tasks);
                break;
              case 'updateTaskTemplates':
                updateTaskTemplates(message.templates);
                break;
              case 'updateTaskHistory':
                updateTaskHistory(message.history);
                break;
            }
          });
        </script>
      </body>
      </html>
    `;
  }

  private setupWebviewMessageHandling(): void {
    if (!this.taskPanel) return;

    this.taskPanel.webview.onDidReceiveMessage(async message => {
      switch (message.type) {
        case 'runTask':
          try {
            await this.taskManager.executeTask(message.taskId);
          } catch (error) {
            TraeAPI.window.showErrorMessage(`Failed to run task: ${error}`);
          }
          break;

        case 'terminateTask':
          const success = await this.taskManager.terminateTask(message.executionId);
          if (!success) {
            TraeAPI.window.showErrorMessage('Failed to terminate task');
          }
          break;

        case 'createFromTemplate':
          await this.createTaskFromTemplate(message.templateId);
          break;
      }
    });
  }

  private async createTaskFromTemplate(templateId: string): Promise<void> {
    const label = await TraeAPI.window.showInputBox({
      prompt: 'Enter task label',
      placeHolder: 'My Custom Task'
    });
    
    if (!label) return;
    
    try {
      const task = this.taskManager.createTaskFromTemplate(templateId, { label });
      this.taskManager.registerTask(task);
      
      TraeAPI.window.showInformationMessage(`Task '${label}' created successfully`);
      this.updateTaskPanel();
    } catch (error) {
      TraeAPI.window.showErrorMessage(`Failed to create task: ${error}`);
    }
  }

  private async updateTaskPanel(): Promise<void> {
    if (!this.taskPanel) return;

    // Update running tasks
    const runningTasks = this.taskManager.getRunningTasks();
    this.taskPanel.webview.postMessage({
      type: 'updateRunningTasks',
      tasks: runningTasks
    });

    // Update available tasks
    const allTasks = this.taskManager.getTasks();
    const providedTasks = await this.taskManager.getProvidedTasks();
    this.taskPanel.webview.postMessage({
      type: 'updateAvailableTasks',
      tasks: [...allTasks, ...providedTasks]
    });

    // Update templates
    const templates = this.taskManager.getTaskTemplates();
    this.taskPanel.webview.postMessage({
      type: 'updateTaskTemplates',
      templates
    });

    // Update history
    const history = this.taskManager.getTaskHistory(10);
    this.taskPanel.webview.postMessage({
      type: 'updateTaskHistory',
      history
    });
  }

  private setupEventListeners(): void {
    // Update status bar based on running tasks
    this.taskManager.onDidChangeTask(event => {
      const runningTasks = this.taskManager.getRunningTasks();
      
      if (runningTasks.length > 0) {
        this.statusBarItem.text = `$(sync~spin) ${runningTasks.length} task(s)`;
        this.statusBarItem.tooltip = `${runningTasks.length} task(s) running`;
      } else {
        this.statusBarItem.text = '$(play) Tasks';
        this.statusBarItem.tooltip = 'Run Task';
      }
      
      // Update task panel if open
      this.updateTaskPanel();
    });
  }
}

// Initialize task UI
const taskUIProvider = new TaskUIProvider(taskManager);

Your Ultimate AI-Powered IDE Learning Guide