fix(runtime): isolate agent state by project (#101)
This commit is contained in:
@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use application::{AgentResumer, AppError, SessionLimitService, RESUME_PROMPT};
|
||||
use domain::ids::{AgentId, NodeId, ScheduleId};
|
||||
use domain::ids::{AgentId, NodeId, ProjectId, ScheduleId};
|
||||
use domain::ports::{Clock, EventBus, EventStream, ScheduledTask, Scheduler};
|
||||
use domain::DomainEvent;
|
||||
use uuid::Uuid;
|
||||
@ -24,6 +24,9 @@ fn aid(n: u128) -> AgentId {
|
||||
fn nid(n: u128) -> NodeId {
|
||||
NodeId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
fn pid(n: u128) -> ProjectId {
|
||||
ProjectId::from_uuid(Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fakes des ports
|
||||
@ -128,6 +131,7 @@ impl FakeResumer {
|
||||
impl AgentResumer for FakeResumer {
|
||||
async fn resume(
|
||||
&self,
|
||||
_project_id: ProjectId,
|
||||
agent_id: AgentId,
|
||||
node_id: NodeId,
|
||||
conversation_id: Option<String>,
|
||||
@ -185,8 +189,13 @@ const NOW: i64 = 1_700_000_000_000;
|
||||
fn on_rate_limited_future_arms_and_emits_in_order() {
|
||||
let env = env_at(NOW);
|
||||
let reset = NOW + 60_000;
|
||||
env.service
|
||||
.on_rate_limited(aid(1), nid(2), Some("conv-1".to_owned()), Some(reset));
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
Some(reset),
|
||||
);
|
||||
|
||||
// Exactement un arm, avec la bonne échéance et la bonne tâche.
|
||||
let armed = env.scheduler.armed();
|
||||
@ -195,6 +204,7 @@ fn on_rate_limited_future_arms_and_emits_in_order() {
|
||||
assert_eq!(
|
||||
armed[0].1,
|
||||
ScheduledTask::ResumeAgent {
|
||||
project_id: pid(1),
|
||||
agent_id: aid(1),
|
||||
node_id: nid(2),
|
||||
conversation_id: Some("conv-1".to_owned()),
|
||||
@ -225,7 +235,7 @@ fn on_rate_limited_past_reset_clamps_fire_at_to_now() {
|
||||
let env = env_at(NOW);
|
||||
let past = NOW - 60_000;
|
||||
env.service
|
||||
.on_rate_limited(aid(1), nid(2), None, Some(past));
|
||||
.on_rate_limited(pid(1), aid(1), nid(2), None, Some(past));
|
||||
|
||||
let armed = env.scheduler.armed();
|
||||
assert_eq!(armed.len(), 1);
|
||||
@ -252,7 +262,7 @@ fn on_rate_limited_past_reset_clamps_fire_at_to_now() {
|
||||
fn on_rate_limited_without_reset_is_human_fallback_no_arm() {
|
||||
let env = env_at(NOW);
|
||||
env.service
|
||||
.on_rate_limited(aid(1), nid(2), Some("conv-1".to_owned()), None);
|
||||
.on_rate_limited(pid(1), aid(1), nid(2), Some("conv-1".to_owned()), None);
|
||||
|
||||
assert!(
|
||||
env.scheduler.armed().is_empty(),
|
||||
@ -282,10 +292,20 @@ fn on_rate_limited_twice_same_agent_dedups_cancelling_previous() {
|
||||
let env = env_at(NOW);
|
||||
let reset1 = NOW + 60_000;
|
||||
let reset2 = NOW + 120_000;
|
||||
env.service
|
||||
.on_rate_limited(aid(1), nid(2), Some("conv-1".to_owned()), Some(reset1));
|
||||
env.service
|
||||
.on_rate_limited(aid(1), nid(2), Some("conv-1".to_owned()), Some(reset2));
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
Some(reset1),
|
||||
);
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
Some(reset2),
|
||||
);
|
||||
|
||||
// Deux arms (un par signal), ids distincts.
|
||||
let issued = env.scheduler.issued();
|
||||
@ -326,6 +346,7 @@ async fn execute_resume_calls_resumer_with_prompt_and_emits_resumed() {
|
||||
let env = env_at(NOW);
|
||||
// Arme d'abord (pour prouver que l'entrée est ensuite retirée).
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
@ -333,6 +354,7 @@ async fn execute_resume_calls_resumer_with_prompt_and_emits_resumed() {
|
||||
);
|
||||
|
||||
let task = ScheduledTask::ResumeAgent {
|
||||
project_id: pid(1),
|
||||
agent_id: aid(1),
|
||||
node_id: nid(2),
|
||||
conversation_id: Some("conv-1".to_owned()),
|
||||
@ -373,6 +395,7 @@ async fn execute_resume_propagates_error_without_emitting_resumed() {
|
||||
env.resumer.set_fail(true);
|
||||
|
||||
let task = ScheduledTask::ResumeAgent {
|
||||
project_id: pid(1),
|
||||
agent_id: aid(1),
|
||||
node_id: nid(2),
|
||||
conversation_id: None,
|
||||
@ -406,6 +429,7 @@ async fn execute_resume_propagates_error_without_emitting_resumed() {
|
||||
fn cancel_resume_after_arm_returns_true_and_emits_cancelled() {
|
||||
let env = env_at(NOW);
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
@ -448,6 +472,7 @@ fn cancel_resume_without_arm_is_false_no_event() {
|
||||
fn cancel_resume_when_scheduler_already_fired_is_false_no_event() {
|
||||
let env = env_at(NOW);
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
@ -484,7 +509,7 @@ fn confirm_human_resume_future_arms_and_emits_in_order() {
|
||||
let env = env_at(NOW);
|
||||
let reset = NOW + 90_000;
|
||||
env.service
|
||||
.confirm_human_resume(aid(1), nid(2), Some("conv-1".to_owned()), reset);
|
||||
.confirm_human_resume(pid(1), aid(1), nid(2), Some("conv-1".to_owned()), reset);
|
||||
|
||||
// Exactement un arm, bonne échéance, bonne tâche.
|
||||
let armed = env.scheduler.armed();
|
||||
@ -493,6 +518,7 @@ fn confirm_human_resume_future_arms_and_emits_in_order() {
|
||||
assert_eq!(
|
||||
armed[0].1,
|
||||
ScheduledTask::ResumeAgent {
|
||||
project_id: pid(1),
|
||||
agent_id: aid(1),
|
||||
node_id: nid(2),
|
||||
conversation_id: Some("conv-1".to_owned()),
|
||||
@ -524,7 +550,8 @@ fn confirm_human_resume_future_arms_and_emits_in_order() {
|
||||
fn confirm_human_resume_past_reset_clamps_fire_at_to_now() {
|
||||
let env = env_at(NOW);
|
||||
let past = NOW - 30_000;
|
||||
env.service.confirm_human_resume(aid(1), nid(2), None, past);
|
||||
env.service
|
||||
.confirm_human_resume(pid(1), aid(1), nid(2), None, past);
|
||||
|
||||
let armed = env.scheduler.armed();
|
||||
assert_eq!(armed.len(), 1);
|
||||
@ -554,13 +581,19 @@ fn confirm_human_resume_past_reset_clamps_fire_at_to_now() {
|
||||
fn confirm_human_resume_after_auto_dedups_single_active_arm() {
|
||||
let env = env_at(NOW);
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
Some(NOW + 60_000),
|
||||
);
|
||||
env.service
|
||||
.confirm_human_resume(aid(1), nid(2), Some("conv-1".to_owned()), NOW + 120_000);
|
||||
env.service.confirm_human_resume(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
NOW + 120_000,
|
||||
);
|
||||
|
||||
let issued = env.scheduler.issued();
|
||||
assert_eq!(
|
||||
@ -600,9 +633,15 @@ fn confirm_human_resume_after_auto_dedups_single_active_arm() {
|
||||
#[test]
|
||||
fn auto_after_confirm_human_resume_dedups_single_active_arm() {
|
||||
let env = env_at(NOW);
|
||||
env.service
|
||||
.confirm_human_resume(aid(1), nid(2), Some("conv-1".to_owned()), NOW + 60_000);
|
||||
env.service.confirm_human_resume(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
NOW + 60_000,
|
||||
);
|
||||
env.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
@ -644,8 +683,13 @@ fn auto_after_confirm_human_resume_dedups_single_active_arm() {
|
||||
#[test]
|
||||
fn cancel_resume_after_confirm_human_resume_returns_true_and_emits_cancelled() {
|
||||
let env = env_at(NOW);
|
||||
env.service
|
||||
.confirm_human_resume(aid(1), nid(2), Some("conv-1".to_owned()), NOW + 60_000);
|
||||
env.service.confirm_human_resume(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
NOW + 60_000,
|
||||
);
|
||||
let issued = env.scheduler.issued();
|
||||
|
||||
assert!(
|
||||
@ -670,13 +714,18 @@ fn confirm_human_resume_is_event_for_event_identical_to_auto_scheduled() {
|
||||
let reset = NOW + 60_000;
|
||||
|
||||
let auto = env_at(NOW);
|
||||
auto.service
|
||||
.on_rate_limited(aid(1), nid(2), Some("conv-1".to_owned()), Some(reset));
|
||||
auto.service.on_rate_limited(
|
||||
pid(1),
|
||||
aid(1),
|
||||
nid(2),
|
||||
Some("conv-1".to_owned()),
|
||||
Some(reset),
|
||||
);
|
||||
|
||||
let human = env_at(NOW);
|
||||
human
|
||||
.service
|
||||
.confirm_human_resume(aid(1), nid(2), Some("conv-1".to_owned()), reset);
|
||||
.confirm_human_resume(pid(1), aid(1), nid(2), Some("conv-1".to_owned()), reset);
|
||||
|
||||
// Même séquence d'events.
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user