The-Agency/lib/src/tools/task_tool.dart

254 lines
No EOL
7.2 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as path;
import 'base_tool.dart';
/// Tool for managing background tasks
/// Tasks are persisted to ~/.clawd_code/tasks/ directory
class TaskTool extends BaseTool {
@override
final String name = 'Task';
@override
final String description =
'Create, list, get, update, and stop background tasks';
// In-memory cache (loaded from disk)
static final Map<String, Map<String, dynamic>> _tasks = {};
static int _taskCounter = 1;
static bool _initialized = false;
@override
Future<String> execute(Map<String, dynamic> input) async {
// Initialize task storage on first use
if (!_initialized) {
await _loadTasks();
_initialized = true;
}
final action = input['action'] as String? ?? 'list';
final taskId = input['task_id'] as String?;
final taskName = input['name'] as String?;
final command = input['command'] as String?;
switch (action.toLowerCase()) {
case 'create':
return await _createTask(command: command, name: taskName);
case 'list':
return _listTasks();
case 'get':
if (taskId == null) {
return 'Error: task_id is required for get action';
}
return _getTask(taskId);
case 'update':
if (taskId == null) {
return 'Error: task_id is required for update action';
}
final status = input['status'] as String?;
final output = input['output'] as String?;
return await _updateTask(taskId, status: status, output: output);
case 'stop':
if (taskId == null) {
return 'Error: task_id is required for stop action';
}
return await _stopTask(taskId);
case 'output':
if (taskId == null) {
return 'Error: task_id is required for output action';
}
return _getTaskOutput(taskId);
default:
return 'Error: Unknown action "$action". Available actions: create, list, get, update, stop, output';
}
}
Future<String> _createTask({String? command, String? name}) async {
final taskId = 'task_${_taskCounter++}';
final now = DateTime.now().toUtc();
_tasks[taskId] = {
'id': taskId,
'name': name ?? 'Unnamed task',
'command': command ?? '',
'status': 'running',
'created_at': now.toIso8601String(),
'updated_at': now.toIso8601String(),
'output': '',
};
await _saveTasks();
return '''Created task: $taskId
Name: ${name ?? 'Unnamed task'}
Command: ${command ?? '(no command)'}
Status: running
Created: ${now.toLocal()}
Use /tasks to list tasks or Task:get task_id=$taskId to check status.''';
}
String _listTasks() {
if (_tasks.isEmpty) {
return 'No tasks found. Create one with Task:create command="your command"';
}
final buffer = StringBuffer();
buffer.writeln('Tasks (${_tasks.length}):');
buffer.writeln('' * 50);
for (final task in _tasks.values) {
final id = task['id'] as String;
final name = task['name'] as String;
final status = task['status'] as String;
final createdAt = task['created_at'] as String;
final updatedAt = task['updated_at'] as String;
final created = DateTime.parse(createdAt).toLocal();
final updated = DateTime.parse(updatedAt).toLocal();
buffer.writeln('$id: $name');
buffer.writeln(' Status: $status');
buffer.writeln(' Created: ${created.toString().substring(0, 16)}');
buffer.writeln(' Updated: ${updated.toString().substring(0, 16)}');
if (task['command'] != null && (task['command'] as String).isNotEmpty) {
buffer.writeln(' Command: ${task['command']}');
}
buffer.writeln();
}
return buffer.toString();
}
String _getTask(String taskId) {
final task = _tasks[taskId];
if (task == null) {
return 'Error: Task "$taskId" not found';
}
final buffer = StringBuffer();
buffer.writeln('Task: ${task['name']}');
buffer.writeln('ID: $taskId');
buffer.writeln('Status: ${task['status']}');
buffer.writeln('Command: ${task['command']}');
buffer.writeln('Created: ${DateTime.parse(task['created_at'] as String).toLocal()}');
buffer.writeln('Updated: ${DateTime.parse(task['updated_at'] as String).toLocal()}');
final output = task['output'] as String;
if (output.isNotEmpty) {
buffer.writeln();
buffer.writeln('Output:');
buffer.writeln(output);
}
return buffer.toString();
}
Future<String> _updateTask(String taskId, {String? status, String? output}) async {
final task = _tasks[taskId];
if (task == null) {
return 'Error: Task "$taskId" not found';
}
if (status != null) {
task['status'] = status;
}
if (output != null) {
task['output'] = output;
}
task['updated_at'] = DateTime.now().toUtc().toIso8601String();
await _saveTasks();
return 'Updated task $taskId';
}
Future<String> _stopTask(String taskId) async {
final task = _tasks[taskId];
if (task == null) {
return 'Error: Task "$taskId" not found';
}
task['status'] = 'stopped';
task['updated_at'] = DateTime.now().toUtc().toIso8601String();
await _saveTasks();
return 'Stopped task $taskId';
}
String _getTaskOutput(String taskId) {
final task = _tasks[taskId];
if (task == null) {
return 'Error: Task "$taskId" not found';
}
final output = task['output'] as String;
if (output.isEmpty) {
return 'No output recorded for task $taskId';
}
return output;
}
// Persistence: load tasks from disk
Future<void> _loadTasks() async {
try {
final dir = _getTasksDirectory();
if (!await dir.exists()) {
return;
}
_tasks.clear();
_taskCounter = 1;
final files = dir.listSync();
for (final file in files) {
if (file is! File || !file.path.endsWith('.json')) {
continue;
}
try {
final content = await file.readAsString();
final json = jsonDecode(content) as Map<String, dynamic>;
final taskId = json['id'] as String?;
if (taskId != null) {
_tasks[taskId] = json;
// Update counter
final match = RegExp(r'task_(\d+)').firstMatch(taskId);
if (match != null) {
final num = int.tryParse(match.group(1)!);
if (num != null && num >= _taskCounter) {
_taskCounter = num + 1;
}
}
}
} catch (_) {
// Skip malformed files
}
}
} catch (_) {
// If loading fails, continue with empty
}
}
// Persistence: save tasks to disk
Future<void> _saveTasks() async {
try {
final dir = _getTasksDirectory();
await dir.create(recursive: true);
for (final entry in _tasks.entries) {
final file = File(path.join(dir.path, '${entry.key}.json'));
await file.writeAsString(jsonEncode(entry.value));
}
} catch (_) {
// Silently fail - tasks stored in memory anyway
}
}
Directory _getTasksDirectory() {
final home = Platform.environment['HOME'] ?? Platform.environment['USERPROFILE'] ?? '';
return Directory(path.join(home, '.clawd_code', 'tasks'));
}
}