diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java index 9d34cb7d..6fd1a449 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/controller/AgentCommandController.java @@ -1,10 +1,12 @@ package com.cameleer3.server.app.controller; import com.cameleer3.server.app.agent.SseConnectionManager; +import com.cameleer3.server.app.dto.CommandAckRequest; import com.cameleer3.server.app.dto.CommandBroadcastResponse; import com.cameleer3.server.app.dto.CommandRequest; import com.cameleer3.server.app.dto.CommandSingleResponse; import com.cameleer3.server.core.agent.AgentCommand; +import com.cameleer3.server.core.agent.AgentEventService; import com.cameleer3.server.core.agent.AgentInfo; import com.cameleer3.server.core.agent.AgentRegistryService; import com.cameleer3.server.core.agent.AgentState; @@ -48,18 +50,21 @@ public class AgentCommandController { private final AgentRegistryService registryService; private final SseConnectionManager connectionManager; private final ObjectMapper objectMapper; + private final AgentEventService agentEventService; public AgentCommandController(AgentRegistryService registryService, SseConnectionManager connectionManager, - ObjectMapper objectMapper) { + ObjectMapper objectMapper, + AgentEventService agentEventService) { this.registryService = registryService; this.connectionManager = connectionManager; this.objectMapper = objectMapper; + this.agentEventService = agentEventService; } @PostMapping("/{id}/commands") @Operation(summary = "Send command to a specific agent", - description = "Sends a config-update, deep-trace, or replay command to the specified agent") + description = "Sends a command to the specified agent via SSE") @ApiResponse(responseCode = "202", description = "Command accepted") @ApiResponse(responseCode = "400", description = "Invalid command payload") @ApiResponse(responseCode = "404", description = "Agent not registered") @@ -128,15 +133,26 @@ public class AgentCommandController { @PostMapping("/{id}/commands/{commandId}/ack") @Operation(summary = "Acknowledge command receipt", - description = "Agent acknowledges that it has received and processed a command") + description = "Agent acknowledges that it has received and processed a command, with result status and message") @ApiResponse(responseCode = "200", description = "Command acknowledged") @ApiResponse(responseCode = "404", description = "Command not found") public ResponseEntity acknowledgeCommand(@PathVariable String id, - @PathVariable String commandId) { + @PathVariable String commandId, + @RequestBody(required = false) CommandAckRequest body) { boolean acknowledged = registryService.acknowledgeCommand(id, commandId); if (!acknowledged) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Command not found: " + commandId); } + + // Record command result in agent event log + if (body != null && body.status() != null) { + AgentInfo agent = registryService.findById(id); + String application = agent != null ? agent.application() : "unknown"; + agentEventService.recordEvent(id, application, "COMMAND_" + body.status(), + "Command " + commandId + ": " + body.message()); + log.debug("Command {} ack from agent {}: {} - {}", commandId, id, body.status(), body.message()); + } + return ResponseEntity.ok().build(); } @@ -145,8 +161,9 @@ public class AgentCommandController { case "config-update" -> CommandType.CONFIG_UPDATE; case "deep-trace" -> CommandType.DEEP_TRACE; case "replay" -> CommandType.REPLAY; + case "set-traced-processors" -> CommandType.SET_TRACED_PROCESSORS; default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "Invalid command type: " + typeStr + ". Valid: config-update, deep-trace, replay"); + "Invalid command type: " + typeStr + ". Valid: config-update, deep-trace, replay, set-traced-processors"); }; } }