fix(session-limit): brancher le handle de limite sur le chemin direct (#30)
Le handle de limite de session ne se déclenchait pas quand un agent directement adressé (dont Main) touchait sa propre limite : le ReplyEvent ::RateLimited du chemin structuré direct n'était pas relayé au service de limite. On tap désormais ReplyEvent::RateLimited dans le registre terminal vers SessionLimitService::on_rate_limited, qui émet AgentRateLimited puis AgentResumeScheduled et arme la reprise auto annulable, exactement comme le chemin délégué. Couverture QA (sortie réelle) : structured_registry_d1 12 passed, session_limit_wiring 6 passed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -1652,11 +1652,10 @@ pub async fn agent_send(
|
||||
// `Final` event (or stream end / superseded channel), then detaches *only its
|
||||
// own* generation so a concurrent re-attach is never torn down.
|
||||
let bridge = std::sync::Arc::clone(&state.chat_bridge);
|
||||
// Level-1 session-limit tap (§21, niveau 1 — structuré). Resolve the agent + host
|
||||
// cell of this session once; a `RateLimited` turn event then feeds the service
|
||||
// (détecter→planifier). DORMANT en composition B-2 (aucune session structurée
|
||||
// vivante) mais câblé pour forward-compat. `conversation_id` non disponible ici ⇒
|
||||
// `None` (acceptable LS7).
|
||||
// Level-1 session-limit tap (§21, niveau 1 — structuré). Resolve the agent, host
|
||||
// cell and engine conversation once from the live registry; a `RateLimited` turn
|
||||
// event then feeds the service (détecter→planifier). Without a conversation id the
|
||||
// service must not pretend an automatic resume is armed.
|
||||
let service = std::sync::Arc::clone(&state.session_limit_service);
|
||||
let meta = state.structured_sessions.meta_for_session(&sid);
|
||||
std::thread::spawn(move || {
|
||||
@ -1665,8 +1664,17 @@ pub async fn agent_send(
|
||||
// (le badge UI vient du bus `AgentRateLimited`, pas du flux chat) puis on
|
||||
// continue à drainer comme pour un battement.
|
||||
if let domain::ports::ReplyEvent::RateLimited { resets_at_ms } = &event {
|
||||
if let Some((agent_id, node_id)) = meta {
|
||||
service.on_rate_limited(agent_id, node_id, None, *resets_at_ms);
|
||||
if let Some((agent_id, node_id, conversation_id)) = &meta {
|
||||
let (conversation_id, resets_at_ms) = match (conversation_id, resets_at_ms) {
|
||||
(Some(conversation_id), resets_at_ms) => {
|
||||
(Some(conversation_id.clone()), *resets_at_ms)
|
||||
}
|
||||
(None, None) => (None, None),
|
||||
// Reset connu mais conversation moteur absente : ne pas armer
|
||||
// une reprise non reprenable ; surfacer le fallback humain.
|
||||
(None, Some(_)) => (None, None),
|
||||
};
|
||||
service.on_rate_limited(*agent_id, *node_id, conversation_id, resets_at_ms);
|
||||
}
|
||||
}
|
||||
// Heartbeats carry no chat content (readiness/heartbeat lot 1) ⇒ no wire
|
||||
|
||||
Reference in New Issue
Block a user