feat(session-limits): LS6 — câblage des événements de limite vers le front
Relaie les 5 nouvelles variantes DomainEvent (AgentRateLimited, ResumeScheduled, ResumeCancelled, Resumed, RateLimitSuspected) via leurs DTO miroirs et bras From dans events.rs, et propage ReplyEvent::RateLimited en chunk côté chat.rs pour que le front soit informé des suspensions/reprises de session. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -205,6 +205,52 @@ pub enum DomainEventDto {
|
||||
/// Whether the in-process ONNX capability is compiled in.
|
||||
vector_onnx_enabled: bool,
|
||||
},
|
||||
/// An agent entered a **session/rate limit** (ARCHITECTURE §21). Low-frequency,
|
||||
/// model-agnostic badge: carries only the neutral "limited, maybe resets at T"
|
||||
/// fact. The frontend badges "limité jusqu'à HH:MM".
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentRateLimited {
|
||||
/// Agent id (UUID string).
|
||||
agent_id: String,
|
||||
/// Reset instant in **epoch-milliseconds**; `null` ⇒ unknown (no auto-resume).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resets_at_ms: Option<i64>,
|
||||
},
|
||||
/// An **auto-resume** was armed for a rate-limited agent (ARCHITECTURE §21). The
|
||||
/// frontend shows the countdown + the "Annuler la reprise" button (cancellable
|
||||
/// window).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentResumeScheduled {
|
||||
/// Agent id (UUID string).
|
||||
agent_id: String,
|
||||
/// Wake-up deadline in **epoch-milliseconds**.
|
||||
fire_at_ms: i64,
|
||||
},
|
||||
/// An agent's **auto-resume** was **cancelled** (ARCHITECTURE §21): the user
|
||||
/// clicked "Annuler la reprise". The frontend removes the countdown.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentResumeCancelled {
|
||||
/// Agent id (UUID string).
|
||||
agent_id: String,
|
||||
},
|
||||
/// An agent was effectively **resumed** after a limit (ARCHITECTURE §21): the
|
||||
/// wake-up fired (or immediate resume). The frontend clears the "limited" state.
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentResumed {
|
||||
/// Agent id (UUID string).
|
||||
agent_id: String,
|
||||
},
|
||||
/// **Human net (level 3)**: a session limit is **suspected** without any reliable
|
||||
/// reset time (ARCHITECTURE §21.1). IdeA never resumes blind: this asks the front
|
||||
/// to **prompt the user** ("limit detected but time unknown — resume at?").
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AgentRateLimitSuspected {
|
||||
/// Agent id (UUID string).
|
||||
agent_id: String,
|
||||
/// Reset instant in **epoch-milliseconds** if an estimate exists, else `null`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resets_at_ms: Option<i64>,
|
||||
},
|
||||
/// Raw PTY output (normally routed to a per-session channel, not here).
|
||||
#[serde(rename_all = "camelCase")]
|
||||
PtyOutput {
|
||||
@ -377,6 +423,33 @@ impl From<&DomainEvent> for DomainEventDto {
|
||||
vector_http_enabled: *vector_http_enabled,
|
||||
vector_onnx_enabled: *vector_onnx_enabled,
|
||||
},
|
||||
DomainEvent::AgentRateLimited {
|
||||
agent_id,
|
||||
resets_at_ms,
|
||||
} => Self::AgentRateLimited {
|
||||
agent_id: agent_id.to_string(),
|
||||
resets_at_ms: *resets_at_ms,
|
||||
},
|
||||
DomainEvent::AgentResumeScheduled {
|
||||
agent_id,
|
||||
fire_at_ms,
|
||||
} => Self::AgentResumeScheduled {
|
||||
agent_id: agent_id.to_string(),
|
||||
fire_at_ms: *fire_at_ms,
|
||||
},
|
||||
DomainEvent::AgentResumeCancelled { agent_id } => Self::AgentResumeCancelled {
|
||||
agent_id: agent_id.to_string(),
|
||||
},
|
||||
DomainEvent::AgentResumed { agent_id } => Self::AgentResumed {
|
||||
agent_id: agent_id.to_string(),
|
||||
},
|
||||
DomainEvent::AgentRateLimitSuspected {
|
||||
agent_id,
|
||||
resets_at_ms,
|
||||
} => Self::AgentRateLimitSuspected {
|
||||
agent_id: agent_id.to_string(),
|
||||
resets_at_ms: *resets_at_ms,
|
||||
},
|
||||
DomainEvent::PtyOutput { session_id, bytes } => Self::PtyOutput {
|
||||
session_id: session_id.to_string(),
|
||||
bytes: bytes.clone(),
|
||||
@ -450,4 +523,19 @@ mod tests {
|
||||
assert_eq!(json["type"], "agentLivenessChanged");
|
||||
assert_eq!(json["liveness"], "alive");
|
||||
}
|
||||
|
||||
/// LS6 : un `AgentRateLimited` du domaine se relaie en DTO portant le même agent
|
||||
/// et l'heure de reset (époche-ms), et se sérialise en `"agentRateLimited"` avec
|
||||
/// `resetsAtMs` — le fait neutre que le front badge « limité jusqu'à HH:MM ».
|
||||
#[test]
|
||||
fn rate_limited_relays_to_dto_and_wire() {
|
||||
let dto = DomainEventDto::from(&DomainEvent::AgentRateLimited {
|
||||
agent_id: agent(11),
|
||||
resets_at_ms: Some(1_700_000_000_000),
|
||||
});
|
||||
let json = serde_json::to_value(&dto).expect("serialisable");
|
||||
assert_eq!(json["type"], "agentRateLimited");
|
||||
assert_eq!(json["agentId"], agent(11).to_string());
|
||||
assert_eq!(json["resetsAtMs"], 1_700_000_000_000_i64);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user