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

- 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:
iven
2026-04-04 09:41:24 +08:00
parent f4ed1b33e0
commit 9af7b0dd46
4 changed files with 189 additions and 2 deletions

View File

@@ -448,6 +448,9 @@ Respond with ONLY the number (1-{}) of the agent who should speak next. No expla
temperature: Some(0.3),
stop: vec![],
stream: false,
thinking_enabled: false,
reasoning_effort: None,
plan_mode: false,
};
match driver.complete(request).await {

View File

@@ -1,6 +1,6 @@
//! Agent CRUD operations
use zclaw_types::{AgentConfig, AgentId, AgentInfo, AgentState, Event, Result};
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
#[cfg(feature = "multi-agent")]
use std::sync::Arc;

View File

@@ -22,7 +22,11 @@ pub use events::*;
pub use config::*;
pub use trigger_manager::{TriggerManager, TriggerEntry, TriggerUpdateRequest, TriggerManagerConfig};
#[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")]
pub use zclaw_protocols::{
A2aRouter, A2aAgentProfile, A2aCapability, A2aEnvelope, A2aMessageType, A2aRecipient,

View File

@@ -754,4 +754,184 @@ mod tests {
let cap_index = router.capability_index.read().await;
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());
}
}