feat: support enriched command ack with status/message + set-traced-processors command type
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / deploy-feature (push) Has been cancelled
CI / cleanup-branch (push) Has been cancelled
CI / build (push) Has been cancelled

- Add @RequestBody(required=false) CommandAckRequest to ack endpoint for
  receiving agent command results (backward compat with old agents)
- Record command results in agent event log via AgentEventService
- Add set-traced-processors to mapCommandType switch
- Inject AgentEventService dependency
This commit is contained in:
2026-03-24 16:11:04 +01:00
parent 4d9a9ff851
commit 4a99e6cf6b

View File

@@ -1,10 +1,12 @@
package com.cameleer3.server.app.controller; package com.cameleer3.server.app.controller;
import com.cameleer3.server.app.agent.SseConnectionManager; 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.CommandBroadcastResponse;
import com.cameleer3.server.app.dto.CommandRequest; import com.cameleer3.server.app.dto.CommandRequest;
import com.cameleer3.server.app.dto.CommandSingleResponse; import com.cameleer3.server.app.dto.CommandSingleResponse;
import com.cameleer3.server.core.agent.AgentCommand; 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.AgentInfo;
import com.cameleer3.server.core.agent.AgentRegistryService; import com.cameleer3.server.core.agent.AgentRegistryService;
import com.cameleer3.server.core.agent.AgentState; import com.cameleer3.server.core.agent.AgentState;
@@ -48,18 +50,21 @@ public class AgentCommandController {
private final AgentRegistryService registryService; private final AgentRegistryService registryService;
private final SseConnectionManager connectionManager; private final SseConnectionManager connectionManager;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final AgentEventService agentEventService;
public AgentCommandController(AgentRegistryService registryService, public AgentCommandController(AgentRegistryService registryService,
SseConnectionManager connectionManager, SseConnectionManager connectionManager,
ObjectMapper objectMapper) { ObjectMapper objectMapper,
AgentEventService agentEventService) {
this.registryService = registryService; this.registryService = registryService;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.objectMapper = objectMapper; this.objectMapper = objectMapper;
this.agentEventService = agentEventService;
} }
@PostMapping("/{id}/commands") @PostMapping("/{id}/commands")
@Operation(summary = "Send command to a specific agent", @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 = "202", description = "Command accepted")
@ApiResponse(responseCode = "400", description = "Invalid command payload") @ApiResponse(responseCode = "400", description = "Invalid command payload")
@ApiResponse(responseCode = "404", description = "Agent not registered") @ApiResponse(responseCode = "404", description = "Agent not registered")
@@ -128,15 +133,26 @@ public class AgentCommandController {
@PostMapping("/{id}/commands/{commandId}/ack") @PostMapping("/{id}/commands/{commandId}/ack")
@Operation(summary = "Acknowledge command receipt", @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 = "200", description = "Command acknowledged")
@ApiResponse(responseCode = "404", description = "Command not found") @ApiResponse(responseCode = "404", description = "Command not found")
public ResponseEntity<Void> acknowledgeCommand(@PathVariable String id, public ResponseEntity<Void> acknowledgeCommand(@PathVariable String id,
@PathVariable String commandId) { @PathVariable String commandId,
@RequestBody(required = false) CommandAckRequest body) {
boolean acknowledged = registryService.acknowledgeCommand(id, commandId); boolean acknowledged = registryService.acknowledgeCommand(id, commandId);
if (!acknowledged) { if (!acknowledged) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Command not found: " + commandId); 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(); return ResponseEntity.ok().build();
} }
@@ -145,8 +161,9 @@ public class AgentCommandController {
case "config-update" -> CommandType.CONFIG_UPDATE; case "config-update" -> CommandType.CONFIG_UPDATE;
case "deep-trace" -> CommandType.DEEP_TRACE; case "deep-trace" -> CommandType.DEEP_TRACE;
case "replay" -> CommandType.REPLAY; case "replay" -> CommandType.REPLAY;
case "set-traced-processors" -> CommandType.SET_TRACED_PROCESSORS;
default -> throw new ResponseStatusException(HttpStatus.BAD_REQUEST, 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");
}; };
} }
} }