|
|
|
|
@ -20,9 +20,9 @@ use domain::ports::{
|
|
|
|
|
};
|
|
|
|
|
use domain::{
|
|
|
|
|
Agent, AgentId, AgentManifest, AgentOrigin, AgentProfile, ContextInjection, ConversationId,
|
|
|
|
|
DomainEvent, Handoff, HandoffStore, ManifestEntry, MarkdownDoc, MemoryIndexEntry, MemoryType,
|
|
|
|
|
NodeId, ProfileId, Project, ProjectPath, PtySize, SessionId, SessionKind, SessionStatus, Skill,
|
|
|
|
|
TerminalSession,
|
|
|
|
|
ConversationParty, DomainEvent, Handoff, HandoffStore, ManifestEntry, MarkdownDoc,
|
|
|
|
|
MemoryIndexEntry, MemoryType, NodeId, ProfileId, Project, ProjectPath, PtySize, SessionId,
|
|
|
|
|
SessionKind, SessionStatus, Skill, TerminalSession,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use crate::error::AppError;
|
|
|
|
|
@ -737,13 +737,22 @@ pub struct LaunchAgentOutput {
|
|
|
|
|
/// structurée (cf. [`Self::structured`]), conformément à §17.4. Garde la sortie
|
|
|
|
|
/// **non cassante** pour A/B et le câblage existant.
|
|
|
|
|
pub session: TerminalSession,
|
|
|
|
|
/// The conversation id **assigned** by this launch, when the profile supports
|
|
|
|
|
/// session assignment and the cell had none yet. The caller persists it on the
|
|
|
|
|
/// hosting leaf (via the layout flow, e.g. `set_cell_conversation`) so the next
|
|
|
|
|
/// open resumes instead of re-assigning. `None` when nothing new was assigned
|
|
|
|
|
/// (resume of an existing id, degraded mode, or a profile without a session
|
|
|
|
|
/// block) — the caller has nothing to persist.
|
|
|
|
|
/// L'**id de paire IdeA** (pivot **logique** de la conversation, ARCHITECTURE
|
|
|
|
|
/// §19.7) assigné par ce lancement, quand la cellule n'en portait pas encore. Le
|
|
|
|
|
/// caller le persiste sur la feuille hôte via `set_cell_conversation` : c'est la
|
|
|
|
|
/// clé qui retrouve **log + handoff** au (re)lancement (P7) et survit au swap de
|
|
|
|
|
/// profil. **Ce n'est plus l'id de session moteur** (qui part désormais dans
|
|
|
|
|
/// [`Self::engine_session_id`]). `None` quand rien de neuf n'a été assigné (reprise
|
|
|
|
|
/// d'un id existant, mode dégradé, ou profil sans session) — rien à persister.
|
|
|
|
|
pub assigned_conversation_id: Option<String>,
|
|
|
|
|
/// L'**id de session moteur** (resumable du provider : id Claude `--resume`, ou
|
|
|
|
|
/// l'UUID minté pour `--session-id`) exposé/attribué par ce lancement, **distinct**
|
|
|
|
|
/// de l'id de paire (ARCHITECTURE §19.7, lot P8a). Le caller le range dans le
|
|
|
|
|
/// **cache** `LeafCell::engine_session_id` (via `set_cell_engine_session`) — jamais
|
|
|
|
|
/// sur `conversation_id`. La **source de vérité** du resumable reste `providers.json`
|
|
|
|
|
/// (lot P8b, hors P8a). `None` pour le chemin PTY/legacy ou quand le moteur n'expose
|
|
|
|
|
/// aucun id : rien à cacher.
|
|
|
|
|
pub engine_session_id: Option<String>,
|
|
|
|
|
/// Descripteur de la session **structurée**, présent **uniquement** quand ce
|
|
|
|
|
/// lancement a routé vers un agent IA structuré (`profile.structured_adapter =
|
|
|
|
|
/// Some(_)`, ARCHITECTURE §17.4). `None` pour le chemin PTY/terminal brut
|
|
|
|
|
@ -1013,6 +1022,7 @@ impl LaunchAgent {
|
|
|
|
|
return Ok(LaunchAgentOutput {
|
|
|
|
|
session,
|
|
|
|
|
assigned_conversation_id: None,
|
|
|
|
|
engine_session_id: None,
|
|
|
|
|
structured: None,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -1031,6 +1041,7 @@ impl LaunchAgent {
|
|
|
|
|
return Ok(LaunchAgentOutput {
|
|
|
|
|
session,
|
|
|
|
|
assigned_conversation_id: None,
|
|
|
|
|
engine_session_id: None,
|
|
|
|
|
structured: None,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@ -1062,6 +1073,8 @@ impl LaunchAgent {
|
|
|
|
|
return Ok(LaunchAgentOutput {
|
|
|
|
|
session: structured_snapshot(&existing, input.agent_id, node_id, size),
|
|
|
|
|
assigned_conversation_id: None,
|
|
|
|
|
// Rebind de vue : aucune (ré)assignation ⇒ rien de neuf à cacher.
|
|
|
|
|
engine_session_id: None,
|
|
|
|
|
structured: Some(StructuredSessionDescriptor {
|
|
|
|
|
session_id: existing.id(),
|
|
|
|
|
agent_id: input.agent_id,
|
|
|
|
|
@ -1183,6 +1196,24 @@ impl LaunchAgent {
|
|
|
|
|
self.structured.as_ref(),
|
|
|
|
|
profile.structured_adapter.is_some(),
|
|
|
|
|
) {
|
|
|
|
|
// ── Clé **logique** de la cellule = id de paire IdeA (ARCHITECTURE §19.7,
|
|
|
|
|
// lot P8a) ──
|
|
|
|
|
// - cellule porteuse d'un `conversation_id` (resume, ou lancement délégué
|
|
|
|
|
// qui a déjà injecté l'id de paire A↔B) ⇒ c'est **déjà** l'id de paire,
|
|
|
|
|
// on le conserve tel quel ;
|
|
|
|
|
// - cellule structurée **neuve** (lancement direct utilisateur, aucun
|
|
|
|
|
// requester) ⇒ on **dérive** `pair(User, agent)` via la fonction domaine
|
|
|
|
|
// pure partagée avec `resolve_conversation` (aucun couplage à
|
|
|
|
|
// l'orchestrateur). C'est cet id — et **non** l'id de session moteur —
|
|
|
|
|
// qui retrouve log + handoff au (re)lancement (P7) et survit au swap.
|
|
|
|
|
let pair_conversation_id =
|
|
|
|
|
input.conversation_id.clone().unwrap_or_else(|| {
|
|
|
|
|
ConversationId::for_pair(
|
|
|
|
|
ConversationParty::User,
|
|
|
|
|
ConversationParty::agent(agent.id),
|
|
|
|
|
)
|
|
|
|
|
.to_string()
|
|
|
|
|
});
|
|
|
|
|
return self
|
|
|
|
|
.launch_structured(
|
|
|
|
|
factory.as_ref(),
|
|
|
|
|
@ -1192,7 +1223,7 @@ impl LaunchAgent {
|
|
|
|
|
&prepared,
|
|
|
|
|
&run_dir,
|
|
|
|
|
&session_plan,
|
|
|
|
|
assigned_conversation_id,
|
|
|
|
|
pair_conversation_id,
|
|
|
|
|
input.node_id,
|
|
|
|
|
size,
|
|
|
|
|
)
|
|
|
|
|
@ -1227,6 +1258,8 @@ impl LaunchAgent {
|
|
|
|
|
Ok(LaunchAgentOutput {
|
|
|
|
|
session,
|
|
|
|
|
assigned_conversation_id,
|
|
|
|
|
// Chemin PTY/terminal brut : pas de session moteur structurée à cacher.
|
|
|
|
|
engine_session_id: None,
|
|
|
|
|
structured: None,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
@ -1250,7 +1283,7 @@ impl LaunchAgent {
|
|
|
|
|
prepared: &PreparedContext,
|
|
|
|
|
run_dir: &ProjectPath,
|
|
|
|
|
session_plan: &SessionPlan,
|
|
|
|
|
assigned_conversation_id: Option<String>,
|
|
|
|
|
pair_conversation_id: String,
|
|
|
|
|
node_id: Option<NodeId>,
|
|
|
|
|
size: PtySize,
|
|
|
|
|
) -> Result<LaunchAgentOutput, AppError> {
|
|
|
|
|
@ -1266,10 +1299,15 @@ impl LaunchAgent {
|
|
|
|
|
// amont sur les deux registres).
|
|
|
|
|
structured.insert(Arc::clone(&session), agent.id, node_id);
|
|
|
|
|
|
|
|
|
|
// L'id de conversation à persister : celui que le moteur expose s'il en a un,
|
|
|
|
|
// sinon l'id pré-attribué par le plan de session (§15).
|
|
|
|
|
let engine_conversation_id = session.conversation_id();
|
|
|
|
|
let conversation_id = engine_conversation_id.clone().or(assigned_conversation_id);
|
|
|
|
|
// ── SÉPARATION DES DEUX CLÉS (ARCHITECTURE §19.7, lot P8a) ──
|
|
|
|
|
// - **id de paire** (`pair_conversation_id`) : clé **logique** persistée sur
|
|
|
|
|
// `LeafCell.conversation_id`. C'est lui qui retrouve log + handoff (P7) et
|
|
|
|
|
// survit au swap. Stable, indépendant du provider.
|
|
|
|
|
// - **id de session moteur** (`session.conversation_id()`) : resumable du
|
|
|
|
|
// provider (Claude/Codex). Rangé **séparément** dans le cache
|
|
|
|
|
// `LeafCell.engine_session_id` (via `set_cell_engine_session`) — **jamais**
|
|
|
|
|
// sur `conversation_id`. `None` si le moteur n'expose encore aucun id.
|
|
|
|
|
let engine_session_id = session.conversation_id();
|
|
|
|
|
|
|
|
|
|
let snapshot = structured_snapshot(&session, agent.id, node_id, size);
|
|
|
|
|
|
|
|
|
|
@ -1280,13 +1318,15 @@ impl LaunchAgent {
|
|
|
|
|
|
|
|
|
|
Ok(LaunchAgentOutput {
|
|
|
|
|
session: snapshot,
|
|
|
|
|
// Le caller persiste cet id sur la cellule (pivot de reprise §15.2).
|
|
|
|
|
assigned_conversation_id: conversation_id.clone(),
|
|
|
|
|
// Cellule ⇐ id de **paire** (pivot logique de reprise §15.2 / §19.7).
|
|
|
|
|
assigned_conversation_id: Some(pair_conversation_id),
|
|
|
|
|
// Cache moteur ⇐ id resumable du provider (distinct de la paire).
|
|
|
|
|
engine_session_id: engine_session_id.clone(),
|
|
|
|
|
structured: Some(StructuredSessionDescriptor {
|
|
|
|
|
session_id,
|
|
|
|
|
agent_id: agent.id,
|
|
|
|
|
node_id,
|
|
|
|
|
conversation_id: engine_conversation_id,
|
|
|
|
|
conversation_id: engine_session_id,
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|