feat(domain,application,infrastructure,backend,web-server,app-tauri,frontend): persistance et reprise des conversations de CLI custom par agent — index durable <projet>/.ideai/conversations/custom-index.json borné aux conversations non vides par agent (conversation_id logique, titre dérivé de la première invite, created/updated_at_ms), port CustomConversationIndex + adaptateur FsCustomConversationIndex, persistence des messages utilisateur et finals agent dans le log de conversation existant, DTO/commandes app-tauri et routes web-server pour lister/rouvrir, CustomAgentChatView: ouverture par défaut sur une nouvelle conversation, menu reprise d'une conversation passée ou nouvelle conversation, replay de l'historique sauvegardé (user+finals) à la réouverture, affichage normal des nouveaux messages après reprise, /clean ouvre une nouvelle conversation, aucune entrée créée si la conversation courante est vide, injection de l'historique dans le contexte agent différée au premier nouveau message après reprise — #363 (QA verte: git diff --check, cargo fmt --all -- --check, cargo tests ciblés infrastructure/application/web-server/app-tauri/backend, npm run typecheck, tests frontend ciblés et CustomAgentChatView, npm run build)

This commit is contained in:
2026-09-16 23:20:31 +02:00
parent 83b78f438f
commit 943c4525d7
30 changed files with 1794 additions and 109 deletions

View File

@ -74,6 +74,40 @@ fn id<T>(value: u128, make: impl FnOnce(Uuid) -> T) -> T {
make(Uuid::from_u128(value))
}
#[test]
fn custom_conversation_binding_keeps_logical_id_and_consumes_resume_once() {
let session_id = id(1, SessionId::from_uuid);
let project_id = id(2, ProjectId::from_uuid);
let agent_id = id(3, AgentId::from_uuid);
let node_id = id(4, NodeId::from_uuid);
let conversation_id = domain::ConversationId::from_uuid(Uuid::from_u128(5));
let sessions = StructuredSessions::new();
sessions.insert_in_project(
project_id,
Arc::new(FakeSession::new(session_id, [])),
agent_id,
node_id,
);
sessions.bind_custom_conversation(
session_id,
conversation_id,
Some("bounded handoff".to_owned()),
);
assert_eq!(
sessions.custom_conversation_id(&session_id),
Some(conversation_id)
);
assert_eq!(
sessions
.take_pending_resume_preamble(&session_id)
.as_deref(),
Some("bounded handoff")
);
assert_eq!(sessions.take_pending_resume_preamble(&session_id), None);
}
struct TurnAccounting {
state: Mutex<AgentBusyState>,
cancelled: Mutex<Vec<(RuntimeAgentKey, TicketId)>>,