execute method

  1. @override
Future<ToolResult> execute(
  1. Map<String, dynamic> input
)
override

Execute the tool with the given input.

Implementation

@override
Future<ToolResult> execute(Map<String, dynamic> input) async {
  final parsed = TaskUpdateToolInput.fromJson(input);
  final taskListId = _taskStore.getTaskListId();

  // Auto-expand task list.
  onExpandTaskView?.call();

  // Check if task exists.
  final existingTask = await _taskStore.getTask(taskListId, parsed.taskId);
  if (existingTask == null) {
    return _formatResult(
      TaskUpdateToolOutput(
        success: false,
        taskId: parsed.taskId,
        updatedFields: const [],
        error: 'Task not found',
      ),
    );
  }

  final updatedFields = <String>[];
  final updates = <String, dynamic>{};

  // Update basic fields if provided and different.
  if (parsed.subject != null && parsed.subject != existingTask.subject) {
    updates['subject'] = parsed.subject;
    updatedFields.add('subject');
  }
  if (parsed.description != null &&
      parsed.description != existingTask.description) {
    updates['description'] = parsed.description;
    updatedFields.add('description');
  }
  if (parsed.activeForm != null &&
      parsed.activeForm != existingTask.activeForm) {
    updates['activeForm'] = parsed.activeForm;
    updatedFields.add('activeForm');
  }
  if (parsed.owner != null && parsed.owner != existingTask.owner) {
    updates['owner'] = parsed.owner;
    updatedFields.add('owner');
  }

  // Auto-set owner when a teammate marks a task as in_progress.
  if (_agentSwarmsEnabled &&
      parsed.status == 'in_progress' &&
      parsed.owner == null &&
      existingTask.owner == null &&
      _agentName != null) {
    updates['owner'] = _agentName;
    updatedFields.add('owner');
  }

  // Merge metadata.
  if (parsed.metadata != null) {
    final merged = Map<String, dynamic>.from(existingTask.metadata ?? {});
    for (final entry in parsed.metadata!.entries) {
      if (entry.value == null) {
        merged.remove(entry.key);
      } else {
        merged[entry.key] = entry.value;
      }
    }
    updates['metadata'] = merged;
    updatedFields.add('metadata');
  }

  // Handle status changes.
  if (parsed.status != null) {
    final updateStatus = TaskUpdateStatus.fromString(parsed.status!);

    // Handle deletion.
    if (updateStatus != null && updateStatus.isDeleted) {
      final deleted = await _taskStore.deleteTask(taskListId, parsed.taskId);
      return _formatResult(
        TaskUpdateToolOutput(
          success: deleted,
          taskId: parsed.taskId,
          updatedFields: deleted ? const ['deleted'] : const [],
          error: deleted ? null : 'Failed to delete task',
          statusChange: deleted
              ? (from: existingTask.status.toJsonString(), to: 'deleted')
              : null,
        ),
      );
    }

    // Regular status update.
    if (updateStatus?.status != null &&
        updateStatus!.status != existingTask.status) {
      // Run TaskCompleted hooks.
      if (updateStatus.status == TaskStatus.completed &&
          _executeHooks != null) {
        final blockingErrors = <String>[];
        await for (final result in _executeHooks(
          taskId: parsed.taskId,
          subject: existingTask.subject,
          description: existingTask.description,
          agentName: _agentName,
          teamName: _teamName,
        )) {
          if (result.blockingError != null) {
            blockingErrors.add(result.blockingError!);
          }
        }

        if (blockingErrors.isNotEmpty) {
          return _formatResult(
            TaskUpdateToolOutput(
              success: false,
              taskId: parsed.taskId,
              updatedFields: const [],
              error: blockingErrors.join('\n'),
            ),
          );
        }
      }

      updates['status'] = updateStatus.status;
      updatedFields.add('status');
    }
  }

  // Apply updates.
  if (updates.isNotEmpty) {
    await _taskStore.updateTask(taskListId, parsed.taskId, updates);
  }

  // Notify new owner via mailbox.
  if (updates.containsKey('owner') &&
      _agentSwarmsEnabled &&
      _writeToMailbox != null) {
    final senderName = _agentName ?? 'team-lead';
    final assignmentMessage = jsonEncode({
      'type': 'task_assignment',
      'taskId': parsed.taskId,
      'subject': existingTask.subject,
      'description': existingTask.description,
      'assignedBy': senderName,
      'timestamp': DateTime.now().toIso8601String(),
    });
    await _writeToMailbox(
      updates['owner'] as String,
      MailboxMessage(
        from: senderName,
        text: assignmentMessage,
        timestamp: DateTime.now().toIso8601String(),
        color: _agentColor,
      ),
      taskListId,
    );
  }

  // Add blocks.
  if (parsed.addBlocks != null && parsed.addBlocks!.isNotEmpty) {
    final newBlocks = parsed.addBlocks!
        .where((id) => !existingTask.blocks.contains(id))
        .toList();
    for (final blockId in newBlocks) {
      await _taskStore.blockTask(taskListId, parsed.taskId, blockId);
    }
    if (newBlocks.isNotEmpty) updatedFields.add('blocks');
  }

  // Add blockedBy (reverse relationship).
  if (parsed.addBlockedBy != null && parsed.addBlockedBy!.isNotEmpty) {
    final newBlockedBy = parsed.addBlockedBy!
        .where((id) => !existingTask.blockedBy.contains(id))
        .toList();
    for (final blockerId in newBlockedBy) {
      await _taskStore.blockTask(taskListId, blockerId, parsed.taskId);
    }
    if (newBlockedBy.isNotEmpty) updatedFields.add('blockedBy');
  }

  // Verification nudge check.
  var verificationNudgeNeeded = false;
  if (_verificationEnabled &&
      _agentId == null &&
      updates['status'] is TaskStatus &&
      (updates['status'] as TaskStatus) == TaskStatus.completed) {
    final allTasks = await _taskStore.listTasks(taskListId);
    final allDone = allTasks.every((t) => t.status == TaskStatus.completed);
    if (allDone &&
        allTasks.length >= 3 &&
        !allTasks.any(
          (t) => RegExp(r'verif', caseSensitive: false).hasMatch(t.subject),
        )) {
      verificationNudgeNeeded = true;
    }
  }

  return _formatResult(
    TaskUpdateToolOutput(
      success: true,
      taskId: parsed.taskId,
      updatedFields: updatedFields,
      statusChange: updates.containsKey('status')
          ? (
              from: existingTask.status.toJsonString(),
              to: (updates['status'] as TaskStatus).toJsonString(),
            )
          : null,
      verificationNudgeNeeded: verificationNudgeNeeded,
    ),
  );
}