feat: agent registry API + heartbeat checker + core unit tests
Tasks completed: - 2.7: Core unit tests (14 tests: state machine, event store, queue, timeout, retry) - 3.1: POST /api/v1/agents/register (upsert on duplicate) - 3.2: POST /api/v1/agents/heartbeat - 3.3: POST /api/v1/agents/deregister (offline + requeue running tasks) - 3.4: GET /api/v1/agents (filter by capability + status) - 3.5: Background heartbeat checker (marks offline, sets tasks agent_lost) - 3.6: API unit tests (register, duplicate, heartbeat, deregister, checker) All 14 tests pass. cargo check clean (warnings only).
This commit is contained in:
parent
2658a74730
commit
b75546bda6
9 changed files with 1023 additions and 115 deletions
|
|
@ -1,5 +1,6 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// ─── Agent ───────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -15,6 +16,32 @@ pub enum AgentType {
|
|||
Other(String),
|
||||
}
|
||||
|
||||
impl AgentType {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::OpenClaw => "openclaw",
|
||||
Self::ClaudeCode => "claude-code",
|
||||
Self::CodexCli => "codex-cli",
|
||||
Self::Hermes => "hermes",
|
||||
Self::Acp => "acp",
|
||||
Self::Shell => "shell",
|
||||
Self::Other(value) => value.as_str(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Self {
|
||||
match value {
|
||||
"openclaw" => Self::OpenClaw,
|
||||
"claude-code" => Self::ClaudeCode,
|
||||
"codex-cli" => Self::CodexCli,
|
||||
"hermes" => Self::Hermes,
|
||||
"acp" => Self::Acp,
|
||||
"shell" => Self::Shell,
|
||||
other => Self::Other(other.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AgentStatus {
|
||||
|
|
@ -23,7 +50,26 @@ pub enum AgentStatus {
|
|||
Draining,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
impl AgentStatus {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Online => "online",
|
||||
Self::Offline => "offline",
|
||||
Self::Draining => "draining",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_str(value: &str) -> Self {
|
||||
match value {
|
||||
"online" => Self::Online,
|
||||
"offline" => Self::Offline,
|
||||
"draining" => Self::Draining,
|
||||
_ => Self::Offline,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Agent {
|
||||
pub agent_id: String,
|
||||
pub agent_type: AgentType,
|
||||
|
|
@ -34,7 +80,7 @@ pub struct Agent {
|
|||
pub status: AgentStatus,
|
||||
pub last_heartbeat_at: DateTime<Utc>,
|
||||
pub registered_at: DateTime<Utc>,
|
||||
pub metadata: std::collections::HashMap<String, String>,
|
||||
pub metadata: HashMap<String, String>,
|
||||
}
|
||||
|
||||
// ─── Task ────────────────────────────────────────────────────────
|
||||
|
|
@ -75,8 +121,6 @@ pub enum Priority {
|
|||
}
|
||||
|
||||
impl Priority {
|
||||
/// Explicit priority ordering (lower = higher priority).
|
||||
/// Not reliant on variant declaration order.
|
||||
pub fn order(&self) -> u8 {
|
||||
match self {
|
||||
Self::Urgent => 0,
|
||||
|
|
@ -86,7 +130,6 @@ impl Priority {
|
|||
}
|
||||
}
|
||||
|
||||
/// Serialize to the string stored in the DB.
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Low => "low",
|
||||
|
|
@ -97,15 +140,15 @@ impl Priority {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Task {
|
||||
pub task_id: String,
|
||||
pub source: String, // "forgejo:<repo>#<issue>"
|
||||
pub task_type: String, // "code", "review", "test", "deploy", "research"
|
||||
pub source: String,
|
||||
pub task_type: String,
|
||||
pub priority: Priority,
|
||||
pub status: TaskStatus,
|
||||
pub assigned_agent_id: Option<String>,
|
||||
pub requirements: String, // Issue body
|
||||
pub requirements: String,
|
||||
pub labels: Vec<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub assigned_at: Option<DateTime<Utc>>,
|
||||
|
|
@ -136,7 +179,7 @@ pub enum ArtifactType {
|
|||
Url,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Artifact {
|
||||
pub artifact_type: ArtifactType,
|
||||
pub url: Option<String>,
|
||||
|
|
@ -144,7 +187,7 @@ pub struct Artifact {
|
|||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Receipt {
|
||||
pub task_id: String,
|
||||
pub agent_id: String,
|
||||
|
|
@ -157,7 +200,7 @@ pub struct Receipt {
|
|||
|
||||
// ─── TaskEvent (event sourcing) ──────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TaskEvent {
|
||||
pub event_id: String,
|
||||
pub task_id: String,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue