execute method
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,
),
);
}