$result * @param array $findings * @param array $changes * @param array $report * * @throws \InvalidArgumentException */ public function handle( int $workspaceId, string $agentId, int $taskId, array $result = [], array $findings = [], array $changes = [], array $report = [] ): FleetTask { return DB::transaction(function () use ( $workspaceId, $agentId, $taskId, $result, $findings, $changes, $report, ): FleetTask { $node = FleetNode::query() ->where('workspace_id', $workspaceId) ->where('agent_id', $agentId) ->lockForUpdate() ->first(); $fleetTask = FleetTask::query() ->where('workspace_id', $workspaceId) ->lockForUpdate() ->find($taskId); if (! $node instanceof FleetNode || ! $fleetTask instanceof FleetTask) { throw new \InvalidArgumentException('Fleet task not found'); } if ($fleetTask->fleet_node_id !== null && $fleetTask->fleet_node_id !== $node->id) { throw new \InvalidArgumentException('Fleet task does not belong to this node'); } $status = ($result['status'] ?? '') === 'failed' ? FleetTask::STATUS_FAILED : FleetTask::STATUS_COMPLETED; $fleetTask->update([ 'status' => $status, 'result' => $result, 'findings' => $findings, 'changes' => $changes, 'report' => $report, 'completed_at' => now(), ]); $creditAmount = max(1, count($findings) + 1); AwardCredits::run( $workspaceId, $agentId, 'fleet-task', $creditAmount, $node->id, 'Fleet task completed', $fleetTask->id, ); $nodeUpdate = [ 'last_heartbeat_at' => now(), ]; if ($node->current_task_id === null || $node->current_task_id === $fleetTask->id) { $nodeUpdate['status'] = FleetNode::STATUS_ONLINE; $nodeUpdate['current_task_id'] = null; } $node->update($nodeUpdate); return $fleetTask->fresh(); }); } }