feat(03-01): add agent registration controller, config, lifecycle monitor

- AgentRegistryConfig: heartbeat, stale, dead, ping, command expiry settings
- AgentRegistryBeanConfig: wires AgentRegistryService as Spring bean
- AgentLifecycleMonitor: @Scheduled lifecycle check + command expiry sweep
- AgentRegistrationController: POST /register, POST /{id}/heartbeat, GET /agents
- Updated Cameleer3ServerApplication with AgentRegistryConfig
- Updated application.yml with agent-registry section and async timeout
- 7 integration tests: register, re-register, heartbeat, list, filter, invalid status

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-11 18:40:57 +01:00
parent 61f39021b3
commit 0372be2334
8 changed files with 452 additions and 1 deletions

View File

@@ -0,0 +1,23 @@
package com.cameleer3.server.app.config;
import com.cameleer3.server.core.agent.AgentRegistryService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Creates the {@link AgentRegistryService} bean.
* <p>
* Follows the established pattern: core module plain class, app module bean config.
*/
@Configuration
public class AgentRegistryBeanConfig {
@Bean
public AgentRegistryService agentRegistryService(AgentRegistryConfig config) {
return new AgentRegistryService(
config.getStaleThresholdMs(),
config.getDeadThresholdMs(),
config.getCommandExpiryMs()
);
}
}

View File

@@ -0,0 +1,68 @@
package com.cameleer3.server.app.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties for the agent registry.
* Bound from the {@code agent-registry.*} namespace in application.yml.
* <p>
* Registered via {@code @EnableConfigurationProperties} on the application class.
*/
@ConfigurationProperties(prefix = "agent-registry")
public class AgentRegistryConfig {
private long heartbeatIntervalMs = 30_000;
private long staleThresholdMs = 90_000;
private long deadThresholdMs = 300_000;
private long pingIntervalMs = 15_000;
private long commandExpiryMs = 60_000;
private long lifecycleCheckIntervalMs = 10_000;
public long getHeartbeatIntervalMs() {
return heartbeatIntervalMs;
}
public void setHeartbeatIntervalMs(long heartbeatIntervalMs) {
this.heartbeatIntervalMs = heartbeatIntervalMs;
}
public long getStaleThresholdMs() {
return staleThresholdMs;
}
public void setStaleThresholdMs(long staleThresholdMs) {
this.staleThresholdMs = staleThresholdMs;
}
public long getDeadThresholdMs() {
return deadThresholdMs;
}
public void setDeadThresholdMs(long deadThresholdMs) {
this.deadThresholdMs = deadThresholdMs;
}
public long getPingIntervalMs() {
return pingIntervalMs;
}
public void setPingIntervalMs(long pingIntervalMs) {
this.pingIntervalMs = pingIntervalMs;
}
public long getCommandExpiryMs() {
return commandExpiryMs;
}
public void setCommandExpiryMs(long commandExpiryMs) {
this.commandExpiryMs = commandExpiryMs;
}
public long getLifecycleCheckIntervalMs() {
return lifecycleCheckIntervalMs;
}
public void setLifecycleCheckIntervalMs(long lifecycleCheckIntervalMs) {
this.lifecycleCheckIntervalMs = lifecycleCheckIntervalMs;
}
}