feat(agents): pont Codex inter-agents + readiness/heartbeat lot 1
Deux chantiers livrés au vert (workspace entier : domain+application+
infrastructure 42 + app-tauri --lib 128, 0 échec).
## Codex inter-agents
- domaine: McpConfigStrategy::TomlConfigHome { target, home_env } +
toml_config_home(...); AgentProfile::materializes_idea_bridge()
(whitelist Claude/ConfigFile + Codex/TomlConfigHome); McpServerWiring
+ encodeur TOML.
- application: lifecycle apply_mcp_config bras TomlConfigHome (écrit
{runDir}/<target>, pousse (home_env, parent) dans spec.env);
guard_mcp_bridge_supported ré-exprimée via materializes_idea_bridge();
catalogue Codex porte toml_config_home(".codex/config.toml","CODEX_HOME").
- app-tauri: is_codex_mcp_profile, migrate_codex_run_dir,
mcp_server_entry_toml.
- tests: matrice domaine TomlConfigHome + round-trip dual Claude/Codex
sur loopback réel (fakes, zéro token).
## Readiness/heartbeat lot 1
- domaine: readiness.rs — ReadinessPolicy::classify (Final => TurnEnded),
variantes ReplyEvent::Heartbeat / ToolActivity.
- application: drain_with_readiness consulte la policy et appelle
mark_idle sur le signal déterministe; branché dans ask_agent.
Corrige la cause racine: une cible qui ne renvoie qu'un Final (sans
idea_reply) débloque désormais sa file Busy.
- infrastructure: adapters de session émettent Heartbeat/ToolActivity.
- tests: drain_with_readiness_lot1 (points QA 5 & 6) verts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -13,6 +13,7 @@ use serde::Serialize;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
use domain::events::{DomainEvent, OrchestrationSource};
|
||||
use domain::input::AgentLiveness;
|
||||
use infrastructure::TokioBroadcastEventBus;
|
||||
|
||||
/// Name of the Tauri event carrying relayed [`DomainEvent`]s.
|
||||
@ -84,6 +85,17 @@ pub enum DomainEventDto {
|
||||
/// Reply length in bytes (metric, not the payload).
|
||||
reply_len: usize,
|
||||
},
|
||||
/// An agent's liveness (alive/stalled) changed (lot 2, readiness/heartbeat). The
|
||||
/// frontend can badge a frozen agent; `"stalled"` while no proof of liveness arrived
|
||||
/// for longer than the profile's `stallAfterMs`, back to `"alive"` on a late
|
||||
/// battement or when the turn ends.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentLivenessChanged {
|
||||
/// Agent id (UUID string).
|
||||
agent_id: String,
|
||||
/// New liveness, as a lowercase string (`"alive"` / `"stalled"`).
|
||||
liveness: AgentLivenessDto,
|
||||
},
|
||||
/// An agent's runtime profile was changed (hot-swap of the AI engine).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentProfileChanged {
|
||||
@ -215,6 +227,26 @@ pub enum OrchestrationSourceDto {
|
||||
Mcp,
|
||||
}
|
||||
|
||||
/// Wire mirror of [`AgentLiveness`]: the alive/stalled liveness of an agent (lot 2),
|
||||
/// serialised as a lowercase string (`"alive"` / `"stalled"`) the frontend badges.
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum AgentLivenessDto {
|
||||
/// The agent shows proof of liveness (or is idle).
|
||||
Alive,
|
||||
/// No proof of liveness past the profile's `stallAfterMs` threshold.
|
||||
Stalled,
|
||||
}
|
||||
|
||||
impl From<AgentLiveness> for AgentLivenessDto {
|
||||
fn from(liveness: AgentLiveness) -> Self {
|
||||
match liveness {
|
||||
AgentLiveness::Alive => Self::Alive,
|
||||
AgentLiveness::Stalled => Self::Stalled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OrchestrationSource> for OrchestrationSourceDto {
|
||||
fn from(source: OrchestrationSource) -> Self {
|
||||
match source {
|
||||
@ -265,6 +297,12 @@ impl From<&DomainEvent> for DomainEventDto {
|
||||
agent_id: agent_id.to_string(),
|
||||
reply_len: *reply_len,
|
||||
},
|
||||
DomainEvent::AgentLivenessChanged { agent_id, liveness } => {
|
||||
Self::AgentLivenessChanged {
|
||||
agent_id: agent_id.to_string(),
|
||||
liveness: (*liveness).into(),
|
||||
}
|
||||
}
|
||||
DomainEvent::AgentProfileChanged {
|
||||
agent_id,
|
||||
profile_id,
|
||||
@ -376,3 +414,40 @@ pub fn spawn_relay(app: AppHandle, bus: &TokioBroadcastEventBus) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use domain::ids::AgentId;
|
||||
|
||||
fn agent(n: u128) -> AgentId {
|
||||
AgentId::from_uuid(uuid::Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
/// Lot 2 : un `AgentLivenessChanged{Stalled}` du domaine se relaie en DTO
|
||||
/// `Stalled` portant le même agent, et se sérialise en `"stalled"` (le mot que
|
||||
/// le front badge). Garantit le câblage présentation de la détection de stall.
|
||||
#[test]
|
||||
fn liveness_changed_stalled_relays_to_dto_and_wire() {
|
||||
let dto = DomainEventDto::from(&DomainEvent::AgentLivenessChanged {
|
||||
agent_id: agent(7),
|
||||
liveness: AgentLiveness::Stalled,
|
||||
});
|
||||
let json = serde_json::to_value(&dto).expect("serialisable");
|
||||
assert_eq!(json["type"], "agentLivenessChanged");
|
||||
assert_eq!(json["agentId"], agent(7).to_string());
|
||||
assert_eq!(json["liveness"], "stalled");
|
||||
}
|
||||
|
||||
/// La reprise `Stalled→Alive` se relaie en DTO `Alive` ⇒ wire `"alive"`.
|
||||
#[test]
|
||||
fn liveness_changed_alive_relays_to_dto_and_wire() {
|
||||
let dto = DomainEventDto::from(&DomainEvent::AgentLivenessChanged {
|
||||
agent_id: agent(3),
|
||||
liveness: AgentLiveness::Alive,
|
||||
});
|
||||
let json = serde_json::to_value(&dto).expect("serialisable");
|
||||
assert_eq!(json["type"], "agentLivenessChanged");
|
||||
assert_eq!(json["liveness"], "alive");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user