fix(kernel): enable multi-agent compilation + A2A routing tests
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- director.rs: add missing CompletionRequest fields (thinking_enabled, reasoning_effort, plan_mode) for multi-agent feature gate - agents.rs: remove unused AgentState import behind multi-agent feature - lib.rs: replace ambiguous glob re-export with explicit director types, resolving AgentRole conflict between director and generation modules - a2a.rs: add 5 integration tests covering direct message delivery, broadcast routing, group messaging, agent unregistration, and expired message rejection (10 total A2A tests, all passing) - Verified: 537 workspace tests pass with multi-agent feature enabled Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -448,6 +448,9 @@ Respond with ONLY the number (1-{}) of the agent who should speak next. No expla
|
|||||||
temperature: Some(0.3),
|
temperature: Some(0.3),
|
||||||
stop: vec![],
|
stop: vec![],
|
||||||
stream: false,
|
stream: false,
|
||||||
|
thinking_enabled: false,
|
||||||
|
reasoning_effort: None,
|
||||||
|
plan_mode: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
match driver.complete(request).await {
|
match driver.complete(request).await {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! Agent CRUD operations
|
//! Agent CRUD operations
|
||||||
|
|
||||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, AgentState, Event, Result};
|
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
|
||||||
|
|
||||||
#[cfg(feature = "multi-agent")]
|
#[cfg(feature = "multi-agent")]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ pub use events::*;
|
|||||||
pub use config::*;
|
pub use config::*;
|
||||||
pub use trigger_manager::{TriggerManager, TriggerEntry, TriggerUpdateRequest, TriggerManagerConfig};
|
pub use trigger_manager::{TriggerManager, TriggerEntry, TriggerUpdateRequest, TriggerManagerConfig};
|
||||||
#[cfg(feature = "multi-agent")]
|
#[cfg(feature = "multi-agent")]
|
||||||
pub use director::*;
|
pub use director::{
|
||||||
|
Director, DirectorConfig, DirectorBuilder, DirectorAgent,
|
||||||
|
ConversationState, ScheduleStrategy,
|
||||||
|
// Note: AgentRole is intentionally NOT re-exported here — use generation::AgentRole instead
|
||||||
|
};
|
||||||
#[cfg(feature = "multi-agent")]
|
#[cfg(feature = "multi-agent")]
|
||||||
pub use zclaw_protocols::{
|
pub use zclaw_protocols::{
|
||||||
A2aRouter, A2aAgentProfile, A2aCapability, A2aEnvelope, A2aMessageType, A2aRecipient,
|
A2aRouter, A2aAgentProfile, A2aCapability, A2aEnvelope, A2aMessageType, A2aRecipient,
|
||||||
|
|||||||
@@ -754,4 +754,184 @@ mod tests {
|
|||||||
let cap_index = router.capability_index.read().await;
|
let cap_index = router.capability_index.read().await;
|
||||||
assert!(cap_index.contains_key("code-generation"));
|
assert!(cap_index.contains_key("code-generation"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_direct_message_delivery() {
|
||||||
|
let router = A2aRouter::new(AgentId::new());
|
||||||
|
|
||||||
|
// Register two agents
|
||||||
|
let alice_id = AgentId::new();
|
||||||
|
let bob_id = AgentId::new();
|
||||||
|
|
||||||
|
let alice_profile = A2aAgentProfile {
|
||||||
|
id: alice_id, name: "Alice".into(), description: String::new(),
|
||||||
|
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||||
|
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||||
|
};
|
||||||
|
let bob_profile = A2aAgentProfile {
|
||||||
|
id: bob_id, name: "Bob".into(), description: String::new(),
|
||||||
|
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||||
|
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut alice_rx = router.register_agent(alice_profile).await;
|
||||||
|
let mut bob_rx = router.register_agent(bob_profile).await;
|
||||||
|
|
||||||
|
// Alice sends direct message to Bob
|
||||||
|
let envelope = A2aEnvelope::new(
|
||||||
|
alice_id,
|
||||||
|
A2aRecipient::Direct { agent_id: bob_id },
|
||||||
|
A2aMessageType::Notification,
|
||||||
|
serde_json::json!({"msg": "hello bob"}),
|
||||||
|
);
|
||||||
|
router.route(envelope).await.unwrap();
|
||||||
|
|
||||||
|
// Bob should receive it
|
||||||
|
let received = bob_rx.recv().await.unwrap();
|
||||||
|
assert_eq!(received.from, alice_id);
|
||||||
|
assert_eq!(received.payload["msg"], "hello bob");
|
||||||
|
|
||||||
|
// Alice should NOT receive it
|
||||||
|
assert!(alice_rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_broadcast_delivery() {
|
||||||
|
let router = A2aRouter::new(AgentId::new());
|
||||||
|
|
||||||
|
let alice_id = AgentId::new();
|
||||||
|
let bob_id = AgentId::new();
|
||||||
|
let carol_id = AgentId::new();
|
||||||
|
|
||||||
|
let make_profile = |id: AgentId, name: &str| A2aAgentProfile {
|
||||||
|
id, name: name.into(), description: String::new(),
|
||||||
|
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||||
|
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut alice_rx = router.register_agent(make_profile(alice_id, "Alice")).await;
|
||||||
|
let mut bob_rx = router.register_agent(make_profile(bob_id, "Bob")).await;
|
||||||
|
let mut carol_rx = router.register_agent(make_profile(carol_id, "Carol")).await;
|
||||||
|
|
||||||
|
// Alice broadcasts
|
||||||
|
let envelope = A2aEnvelope::new(
|
||||||
|
alice_id,
|
||||||
|
A2aRecipient::Broadcast,
|
||||||
|
A2aMessageType::Notification,
|
||||||
|
serde_json::json!({"announcement": "standup in 5"}),
|
||||||
|
);
|
||||||
|
router.route(envelope).await.unwrap();
|
||||||
|
|
||||||
|
// Bob and Carol should receive, Alice should NOT (sender excluded)
|
||||||
|
let bob_msg = bob_rx.recv().await.unwrap();
|
||||||
|
assert_eq!(bob_msg.payload["announcement"], "standup in 5");
|
||||||
|
|
||||||
|
let carol_msg = carol_rx.recv().await.unwrap();
|
||||||
|
assert_eq!(carol_msg.payload["announcement"], "standup in 5");
|
||||||
|
|
||||||
|
assert!(alice_rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_group_message_delivery() {
|
||||||
|
let router = A2aRouter::new(AgentId::new());
|
||||||
|
|
||||||
|
let alice_id = AgentId::new();
|
||||||
|
let bob_id = AgentId::new();
|
||||||
|
let carol_id = AgentId::new();
|
||||||
|
|
||||||
|
let make_profile = |id: AgentId, name: &str| A2aAgentProfile {
|
||||||
|
id, name: name.into(), description: String::new(),
|
||||||
|
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||||
|
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut alice_rx = router.register_agent(make_profile(alice_id, "Alice")).await;
|
||||||
|
let mut bob_rx = router.register_agent(make_profile(bob_id, "Bob")).await;
|
||||||
|
let mut carol_rx = router.register_agent(make_profile(carol_id, "Carol")).await;
|
||||||
|
|
||||||
|
// Add Bob and Carol to "dev-team" group
|
||||||
|
router.add_to_group("dev-team", bob_id).await;
|
||||||
|
router.add_to_group("dev-team", carol_id).await;
|
||||||
|
|
||||||
|
// Alice sends to group
|
||||||
|
let envelope = A2aEnvelope::new(
|
||||||
|
alice_id,
|
||||||
|
A2aRecipient::Group { group_id: "dev-team".into() },
|
||||||
|
A2aMessageType::Notification,
|
||||||
|
serde_json::json!({"sprint": "review"}),
|
||||||
|
);
|
||||||
|
router.route(envelope).await.unwrap();
|
||||||
|
|
||||||
|
// Bob and Carol should receive
|
||||||
|
let bob_msg = bob_rx.recv().await.unwrap();
|
||||||
|
assert_eq!(bob_msg.payload["sprint"], "review");
|
||||||
|
let carol_msg = carol_rx.recv().await.unwrap();
|
||||||
|
assert_eq!(carol_msg.payload["sprint"], "review");
|
||||||
|
|
||||||
|
// Alice not in group, should NOT receive
|
||||||
|
assert!(alice_rx.try_recv().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_unregister_agent() {
|
||||||
|
let router = A2aRouter::new(AgentId::new());
|
||||||
|
|
||||||
|
let agent_id = AgentId::new();
|
||||||
|
let profile = A2aAgentProfile {
|
||||||
|
id: agent_id, name: "Temp".into(), description: String::new(),
|
||||||
|
capabilities: vec![A2aCapability {
|
||||||
|
name: "temp-work".into(), description: "temp".into(),
|
||||||
|
input_schema: None, output_schema: None, requires_approval: false,
|
||||||
|
version: "1.0.0".into(), tags: vec![],
|
||||||
|
}],
|
||||||
|
protocols: vec!["a2a".into()], role: "worker".into(),
|
||||||
|
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
router.register_agent(profile).await;
|
||||||
|
assert_eq!(router.list_profiles().await.len(), 1);
|
||||||
|
|
||||||
|
// Discover should find it
|
||||||
|
let found = router.discover("temp-work").await.unwrap();
|
||||||
|
assert_eq!(found.len(), 1);
|
||||||
|
|
||||||
|
// Unregister
|
||||||
|
router.unregister_agent(&agent_id).await;
|
||||||
|
assert_eq!(router.list_profiles().await.len(), 0);
|
||||||
|
|
||||||
|
// Capability index should be cleaned
|
||||||
|
let found_after = router.discover("temp-work").await.unwrap();
|
||||||
|
assert!(found_after.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_expired_message_rejected() {
|
||||||
|
let router = A2aRouter::new(AgentId::new());
|
||||||
|
|
||||||
|
let alice_id = AgentId::new();
|
||||||
|
let bob_id = AgentId::new();
|
||||||
|
|
||||||
|
let profile = |id: AgentId| A2aAgentProfile {
|
||||||
|
id, name: "Agent".into(), description: String::new(),
|
||||||
|
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||||
|
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let _rx = router.register_agent(profile(alice_id)).await;
|
||||||
|
let _rx = router.register_agent(profile(bob_id)).await;
|
||||||
|
|
||||||
|
// Create envelope with already-expired TTL
|
||||||
|
let mut envelope = A2aEnvelope::new(
|
||||||
|
alice_id,
|
||||||
|
A2aRecipient::Direct { agent_id: bob_id },
|
||||||
|
A2aMessageType::Notification,
|
||||||
|
serde_json::json!({}),
|
||||||
|
);
|
||||||
|
envelope.ttl = 1; // 1 second
|
||||||
|
envelope.timestamp = 0; // Far in the past — definitely expired
|
||||||
|
|
||||||
|
let result = router.route(envelope).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user