chore(wip): checkpoint P8/C avant chantier Codex inter-agents
Sauvegarde de l'arbre de travail en cours (persistance P8, conversations C-series, write-portal frontend, médiation d'entrée) avant d'attaquer le support de la délégation inter-agents pour les profils Codex. Le round-trip inter-agent question/réponse est couvert sans tokens par les tests loopback existants (state::mcp_e2e_loopback_tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -570,10 +570,10 @@ impl FakeProviderSessionStore {
|
||||
/// so a P8c launch reading via [`ProviderSessionStore::get`] resolves the engine
|
||||
/// resumable for that pair (mirrors what a previous P8b launch would have stored).
|
||||
fn seed_sync(&self, conversation: ConversationId, provider: &str, resumable_id: &str) {
|
||||
self.entries.lock().unwrap().insert(
|
||||
(conversation, provider.to_owned()),
|
||||
resumable_id.to_owned(),
|
||||
);
|
||||
self.entries
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert((conversation, provider.to_owned()), resumable_id.to_owned());
|
||||
}
|
||||
/// Observed resumable id for a `(conversation, provider)` couple, if any.
|
||||
fn get_sync(&self, conversation: ConversationId, provider: &str) -> Option<String> {
|
||||
@ -606,10 +606,10 @@ impl ProviderSessionStore for FakeProviderSessionStore {
|
||||
if self.fail_set {
|
||||
return Err(StoreError::Io("forced set failure".to_owned()));
|
||||
}
|
||||
self.entries
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert((conversation, provider_id.to_owned()), resumable_id.to_owned());
|
||||
self.entries.lock().unwrap().insert(
|
||||
(conversation, provider_id.to_owned()),
|
||||
resumable_id.to_owned(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -862,14 +862,21 @@ async fn non_structured_profile_takes_pty_path_unchanged() {
|
||||
|
||||
// PTY spawn appelé, factory jamais sollicitée.
|
||||
assert_eq!(f.pty.spawn_count(), 1, "pty path spawns");
|
||||
assert_eq!(f.factory.start_count(), 0, "factory not called for pty profile");
|
||||
assert_eq!(
|
||||
f.factory.start_count(),
|
||||
0,
|
||||
"factory not called for pty profile"
|
||||
);
|
||||
|
||||
// Session côté registre PTY, rien côté structuré.
|
||||
assert_eq!(f.sessions.session_for_agent(&f.agent.id), Some(sid(777)));
|
||||
assert!(f.structured.session_for_agent(&f.agent.id).is_none());
|
||||
|
||||
// output.structured = None ; session PTY classique.
|
||||
assert!(out.structured.is_none(), "no structured descriptor on pty path");
|
||||
assert!(
|
||||
out.structured.is_none(),
|
||||
"no structured descriptor on pty path"
|
||||
);
|
||||
assert_eq!(out.session.id, sid(777));
|
||||
}
|
||||
|
||||
@ -911,7 +918,11 @@ async fn structured_launch_new_in_other_cell_refuses_when_live_elsewhere() {
|
||||
"no second factory.start on a refused launch"
|
||||
);
|
||||
assert_eq!(f.pty.spawn_count(), 0, "still no pty spawn");
|
||||
assert_eq!(f.structured.len(), 1, "still a single live structured session");
|
||||
assert_eq!(
|
||||
f.structured.len(),
|
||||
1,
|
||||
"still a single live structured session"
|
||||
);
|
||||
assert_eq!(
|
||||
f.structured.node_for_agent(&f.agent.id),
|
||||
Some(host),
|
||||
@ -973,7 +984,11 @@ async fn structured_relaunch_other_cell_with_conversation_id_rebinds() {
|
||||
"no second factory.start on explicit reattach"
|
||||
);
|
||||
assert_eq!(f.pty.spawn_count(), 0, "still no pty spawn");
|
||||
assert_eq!(f.structured.len(), 1, "still a single live structured session");
|
||||
assert_eq!(
|
||||
f.structured.len(),
|
||||
1,
|
||||
"still a single live structured session"
|
||||
);
|
||||
assert_eq!(f.structured.node_for_agent(&f.agent.id), Some(target));
|
||||
let desc = out.structured.expect("descriptor on rebind");
|
||||
assert_eq!(desc.session_id, sid(500), "same live session id");
|
||||
@ -1125,7 +1140,11 @@ async fn swap_structured_live_session_shuts_down_then_relaunches() {
|
||||
f.pty.kills().is_empty(),
|
||||
"no PTY kill for a structured live session"
|
||||
);
|
||||
assert_eq!(f.pty.spawn_count(), 0, "no PTY spawn (target is structured)");
|
||||
assert_eq!(
|
||||
f.pty.spawn_count(),
|
||||
0,
|
||||
"no PTY spawn (target is structured)"
|
||||
);
|
||||
|
||||
// Relance via la factory : un nouveau start (donc total 2).
|
||||
assert_eq!(
|
||||
@ -1137,12 +1156,25 @@ async fn swap_structured_live_session_shuts_down_then_relaunches() {
|
||||
// `ChangeAgentProfileOutput` ne surface qu'un snapshot `relaunched` (TerminalSession)
|
||||
// — on vérifie donc le registre structuré + le snapshot.
|
||||
// Seed consumed id 600; the relaunch's session is the factory's next id (601).
|
||||
let relaunched = out.relaunched.expect("a live structured agent is relaunched");
|
||||
assert_eq!(relaunched.id, sid(601), "relaunch adopts the new structured id");
|
||||
assert_eq!(relaunched.node_id, host, "relaunch reopens in the same cell");
|
||||
let relaunched = out
|
||||
.relaunched
|
||||
.expect("a live structured agent is relaunched");
|
||||
assert_eq!(
|
||||
relaunched.id,
|
||||
sid(601),
|
||||
"relaunch adopts the new structured id"
|
||||
);
|
||||
assert_eq!(
|
||||
relaunched.node_id, host,
|
||||
"relaunch reopens in the same cell"
|
||||
);
|
||||
assert_eq!(f.structured.session_id_for_agent(&agent.id), Some(sid(601)));
|
||||
assert_eq!(f.structured.node_for_agent(&agent.id), Some(host));
|
||||
assert_eq!(f.structured.len(), 1, "single live structured session after swap");
|
||||
assert_eq!(
|
||||
f.structured.len(),
|
||||
1,
|
||||
"single live structured session after swap"
|
||||
);
|
||||
// Manifeste muté vers le nouveau profil.
|
||||
assert_eq!(f.contexts.profile_of(&agent.id), Some(pid(2)));
|
||||
}
|
||||
@ -1169,12 +1201,19 @@ async fn swap_pty_live_session_keeps_a1_kill_behaviour() {
|
||||
// A1 d'origine : le PTY vivant est tué.
|
||||
assert_eq!(f.pty.kills(), vec![sid(42)], "the live PTY must be killed");
|
||||
// Aucune session structurée n'a été créée ni shutdown.
|
||||
assert_eq!(f.factory.start_count(), 0, "no structured start on pty swap");
|
||||
assert_eq!(
|
||||
f.factory.start_count(),
|
||||
0,
|
||||
"no structured start on pty swap"
|
||||
);
|
||||
assert_eq!(f.factory.shutdown_count(), 0, "no structured shutdown");
|
||||
// Relance PTY : un spawn, même cellule.
|
||||
assert_eq!(f.pty.spawn_count(), 1, "the new engine spawns once");
|
||||
let relaunched = out.relaunched.expect("a live agent is relaunched");
|
||||
assert_eq!(relaunched.node_id, host, "relaunch reopens in the same cell");
|
||||
assert_eq!(
|
||||
relaunched.node_id, host,
|
||||
"relaunch reopens in the same cell"
|
||||
);
|
||||
assert_eq!(relaunched.id, sid(777));
|
||||
// The relaunched session lives in the PTY registry, not the structured one.
|
||||
assert_eq!(f.sessions.session_for_agent(&agent.id), Some(sid(777)));
|
||||
@ -1321,7 +1360,8 @@ fn launch_fixture_p8b(
|
||||
)
|
||||
.with_structured(Arc::new(factory), Arc::clone(&structured));
|
||||
if let Some(store) = store {
|
||||
let provider = Arc::new(FakeProviderSessionProvider(store)) as Arc<dyn ProviderSessionProvider>;
|
||||
let provider =
|
||||
Arc::new(FakeProviderSessionProvider(store)) as Arc<dyn ProviderSessionProvider>;
|
||||
launch = launch.with_provider_session_provider(provider);
|
||||
}
|
||||
(Arc::new(launch), agent)
|
||||
@ -1335,8 +1375,11 @@ fn launch_fixture_p8b(
|
||||
async fn p8b_nominal_claude_writes_engine_id_under_pair_and_claude_key() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
let factory = FakeFactory::new(500, Some("engine-xyz"));
|
||||
let (launch, agent) =
|
||||
launch_fixture_p8b(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent) = launch_fixture_p8b(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
// La cellule porte un id de paire UUID valide : c'est lui qui sert de clé.
|
||||
let pair = ConversationId::from_uuid(Uuid::from_u128(123));
|
||||
@ -1406,7 +1449,10 @@ async fn p8b_no_provider_wired_writes_nothing_launch_ok() {
|
||||
let mut input = launch_input(agent.id);
|
||||
input.conversation_id = Some(pair.to_string());
|
||||
|
||||
launch.execute(input).await.expect("launch ok without provider");
|
||||
launch
|
||||
.execute(input)
|
||||
.await
|
||||
.expect("launch ok without provider");
|
||||
|
||||
assert_eq!(store.len(), 0, "no provider wired ⇒ nothing written");
|
||||
}
|
||||
@ -1418,14 +1464,20 @@ async fn p8b_no_engine_id_writes_nothing_launch_ok() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
// Factory avec conversation_id None ⇒ session.conversation_id() == None.
|
||||
let factory = FakeFactory::new(500, None);
|
||||
let (launch, agent) =
|
||||
launch_fixture_p8b(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent) = launch_fixture_p8b(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
let pair = ConversationId::from_uuid(Uuid::from_u128(123));
|
||||
let mut input = launch_input(agent.id);
|
||||
input.conversation_id = Some(pair.to_string());
|
||||
|
||||
launch.execute(input).await.expect("launch ok without engine id");
|
||||
launch
|
||||
.execute(input)
|
||||
.await
|
||||
.expect("launch ok without engine id");
|
||||
|
||||
assert_eq!(
|
||||
store.len(),
|
||||
@ -1441,8 +1493,11 @@ async fn p8b_no_engine_id_writes_nothing_launch_ok() {
|
||||
async fn p8b_store_set_error_does_not_fail_launch() {
|
||||
let store = Arc::new(FakeProviderSessionStore::failing());
|
||||
let factory = FakeFactory::new(500, Some("engine-xyz"));
|
||||
let (launch, agent) =
|
||||
launch_fixture_p8b(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent) = launch_fixture_p8b(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
let pair = ConversationId::from_uuid(Uuid::from_u128(123));
|
||||
let mut input = launch_input(agent.id);
|
||||
@ -1498,8 +1553,11 @@ async fn p8c_structured_claude_resumes_engine_id_from_store_not_pair() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
// Le moteur n'attribue pas d'id neuf (None) : le resume vient du store, pas du run.
|
||||
let factory = FakeFactory::new(500, None);
|
||||
let (launch, agent, observed) =
|
||||
launch_fixture_p8c(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent, observed) = launch_fixture_p8c(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
// Pré-remplit le store : (pair, "claude") → "engine-x".
|
||||
let pair = ConversationId::from_uuid(Uuid::from_u128(123));
|
||||
@ -1509,7 +1567,10 @@ async fn p8c_structured_claude_resumes_engine_id_from_store_not_pair() {
|
||||
let mut input = launch_input(agent.id);
|
||||
input.conversation_id = Some(pair.to_string());
|
||||
|
||||
launch.execute(input).await.expect("structured launch resumes");
|
||||
launch
|
||||
.execute(input)
|
||||
.await
|
||||
.expect("structured launch resumes");
|
||||
|
||||
// Le SessionPlan transmis à factory.start est Resume avec l'**engine id du store**.
|
||||
let starts = observed.starts();
|
||||
@ -1552,7 +1613,10 @@ async fn p8c_structured_codex_resumes_engine_id_from_store_codex_key() {
|
||||
let mut input = launch_input(agent.id);
|
||||
input.conversation_id = Some(pair.to_string());
|
||||
|
||||
launch.execute(input).await.expect("structured codex launch");
|
||||
launch
|
||||
.execute(input)
|
||||
.await
|
||||
.expect("structured codex launch");
|
||||
|
||||
let starts = observed.starts();
|
||||
assert_eq!(starts.len(), 1);
|
||||
@ -1572,8 +1636,11 @@ async fn p8c_structured_codex_resumes_engine_id_from_store_codex_key() {
|
||||
async fn p8c_provider_key_mismatch_falls_back_to_none() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
let factory = FakeFactory::new(500, None);
|
||||
let (launch, agent, observed) =
|
||||
launch_fixture_p8c(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent, observed) = launch_fixture_p8c(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
let pair = ConversationId::from_uuid(Uuid::from_u128(123));
|
||||
// Engine id rangé sous une AUTRE clé provider.
|
||||
@ -1600,8 +1667,11 @@ async fn p8c_provider_key_mismatch_falls_back_to_none() {
|
||||
async fn p8c_structured_store_empty_resolves_to_none() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
let factory = FakeFactory::new(500, None);
|
||||
let (launch, agent, observed) =
|
||||
launch_fixture_p8c(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent, observed) = launch_fixture_p8c(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
let pair = ConversationId::from_uuid(Uuid::from_u128(123));
|
||||
let mut input = launch_input(agent.id);
|
||||
@ -1624,8 +1694,11 @@ async fn p8c_structured_store_empty_resolves_to_none() {
|
||||
async fn p8c_structured_store_wired_fresh_cell_resolves_to_none() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
let factory = FakeFactory::new(500, None);
|
||||
let (launch, agent, observed) =
|
||||
launch_fixture_p8c(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent, observed) = launch_fixture_p8c(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
// Cellule neuve : conversation_id = None.
|
||||
launch
|
||||
@ -1649,8 +1722,11 @@ async fn p8c_structured_store_wired_fresh_cell_resolves_to_none() {
|
||||
async fn p8c_non_uuid_pair_id_with_store_resolves_to_none() {
|
||||
let store = Arc::new(FakeProviderSessionStore::new());
|
||||
let factory = FakeFactory::new(500, None);
|
||||
let (launch, agent, observed) =
|
||||
launch_fixture_p8c(structured_profile(pid(9)), factory, Some(Arc::clone(&store)));
|
||||
let (launch, agent, observed) = launch_fixture_p8c(
|
||||
structured_profile(pid(9)),
|
||||
factory,
|
||||
Some(Arc::clone(&store)),
|
||||
);
|
||||
|
||||
let mut input = launch_input(agent.id);
|
||||
input.conversation_id = Some("not-a-uuid".to_owned());
|
||||
|
||||
Reference in New Issue
Block a user