fix(domain,application,infrastructure,app-tauri,frontend): conversation_id redevient un pivot logique IdeA/log/handoff, reprise provider réservée à la sélection explicite — lifecycle: LaunchAgentInput::conversation_id documenté comme sélection explicite (Some = reprise native si disponible sinon handoff portable, None = conversation provider neuve), plus de copie implicite du pivot logique de la leaf au reboot ; fallback silencieux: provider_resume_fallback résolu via resolve_handoff et câblé jusqu'aux adaptateurs Claude/Codex/OpenCode via AgentSessionFactory::create resume_fallback_preamble + with_resume_fallback_preamble — le préambule reste dormant si la session native répond et n'est injecté que si la session provider s'avère introuvable/périmée (fake stale-then-fresh en test, relance fraîche sans erreur utilisateur) ; app-tauri launch_agent: contrat documenté, l'activation ordinaire d'une cellule doit envoyer None même si la leaf conserve un pivot logique d'avant reboot ; frontend CustomAgentChatView: freshOnOpen réinitialise l'état conversation complet (scrollback, view state, conversationId publié, bannière reprise, pagination historique) quand le runtime structuré n'existe plus au remontage — pas de fausse « Conversation reprise » après reboot ; tests: structured_launch_d3 étendu, structured_session_d0, agent_lifecycle structured, CustomAgentChatView freshOnOpen — #372 (QA verte: cargo check --tests -p domain -p application -p infrastructure, cargo test -p application --test structured_launch_d3, cargo test -p domain --test structured_session_d0, cargo test -p application --test agent_lifecycle structured, npm test -- CustomAgentChatView, npm run typecheck, git diff --check ; fichiers .ideai/* et scripts/ hors périmètre ; aucun push)
This commit is contained in:
@ -22,7 +22,10 @@ use domain::ports::{
|
||||
use domain::sandbox::{SandboxEnforcer, SandboxPlan};
|
||||
use domain::SessionId;
|
||||
|
||||
use super::process::{run_turn_cancellable, SpawnLine, TurnCancel};
|
||||
use super::process::{
|
||||
is_missing_provider_session, resume_fallback_prompt, run_turn_cancellable, SpawnLine,
|
||||
TurnCancel,
|
||||
};
|
||||
|
||||
/// Résultat du parsing d'une ligne Codex : **zéro ou plusieurs** événements et/ou un
|
||||
/// id de conversation Codex capté. Miroir de `claude::ParsedLine` (vecteur d'events
|
||||
@ -401,6 +404,8 @@ pub struct CodexExecSession {
|
||||
env: Vec<(String, String)>,
|
||||
/// Id de conversation **du moteur** Codex, capté au premier tour, `None` avant.
|
||||
conversation_id: Mutex<Option<String>>,
|
||||
/// Portable handoff injected only if the seeded provider session is stale.
|
||||
resume_fallback_preamble: Option<String>,
|
||||
/// Plan de sandbox OS **par lancement** (lot LP4-4), porté dans chaque
|
||||
/// [`SpawnLine`]. `None` ⇒ aucun sandboxing (drain async natif).
|
||||
sandbox: Option<SandboxPlan>,
|
||||
@ -503,12 +508,20 @@ impl CodexExecSession {
|
||||
model_reasoning_effort,
|
||||
env,
|
||||
conversation_id: Mutex::new(seed_conversation_id),
|
||||
resume_fallback_preamble: None,
|
||||
sandbox,
|
||||
sandbox_enforcer,
|
||||
turn_cancel: TurnCancel::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds the portable context used by the one-shot stale-resume fallback.
|
||||
#[must_use]
|
||||
pub fn with_resume_fallback_preamble(mut self, preamble: Option<&str>) -> Self {
|
||||
self.resume_fallback_preamble = preamble.map(str::to_owned);
|
||||
self
|
||||
}
|
||||
|
||||
/// Compose la ligne de commande d'un tour.
|
||||
///
|
||||
/// Format RÉEL vérifié 2026-06-10 (codex 0.137.0) :
|
||||
@ -641,6 +654,7 @@ impl CodexExecSession {
|
||||
prompt: &str,
|
||||
tap: Option<std::sync::mpsc::Sender<ReplyEvent>>,
|
||||
) -> Result<ReplyStream, AgentSessionError> {
|
||||
let had_native_resume = self.conversation_id().is_some();
|
||||
let spec = self.build_spawn_line(prompt);
|
||||
let has_tap = tap.is_some();
|
||||
let (line_tap, parser_thread) = tap.map_or((None, None), |event_tap| {
|
||||
@ -670,14 +684,27 @@ impl CodexExecSession {
|
||||
});
|
||||
(Some(line_tx), Some(parser))
|
||||
});
|
||||
let raw_result = run_turn_cancellable(
|
||||
let mut raw_result = run_turn_cancellable(
|
||||
&spec,
|
||||
None,
|
||||
self.sandbox_enforcer.as_ref(),
|
||||
line_tap,
|
||||
line_tap.clone(),
|
||||
&self.turn_cancel,
|
||||
)
|
||||
.await;
|
||||
if had_native_resume && raw_result.as_ref().is_err_and(is_missing_provider_session) {
|
||||
*self.conversation_id.lock().expect("mutex sain") = None;
|
||||
let fallback_prompt =
|
||||
resume_fallback_prompt(self.resume_fallback_preamble.as_deref(), prompt);
|
||||
raw_result = run_turn_cancellable(
|
||||
&self.build_spawn_line(&fallback_prompt),
|
||||
None,
|
||||
self.sandbox_enforcer.as_ref(),
|
||||
line_tap,
|
||||
&self.turn_cancel,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if let Some(parser) = parser_thread {
|
||||
let _ = parser.join();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user