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

@ -0,0 +1,43 @@
//! Durable catalogue of user-facing custom CLI conversations.
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::ports::StoreError;
use crate::{AgentId, ConversationId};
/// One non-empty custom CLI conversation owned by an agent.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CustomConversationEntry {
/// Stable logical conversation id used by log, handoff and provider-session stores.
pub conversation_id: ConversationId,
/// Agent that owns the conversation.
pub agent_id: AgentId,
/// Title derived from the first accepted user prompt.
pub title: String,
/// Creation timestamp in epoch milliseconds.
pub created_at_ms: u64,
/// Most recent persisted human/final activity in epoch milliseconds.
pub updated_at_ms: u64,
}
/// Project-scoped durable index for non-empty custom CLI conversations.
#[async_trait]
pub trait CustomConversationIndex: Send + Sync {
/// Lists conversations owned by `agent_id`, newest first and capped by `limit`.
async fn list(
&self,
agent_id: AgentId,
limit: usize,
) -> Result<Vec<CustomConversationEntry>, StoreError>;
/// Finds an indexed conversation by logical id.
async fn get(
&self,
conversation_id: ConversationId,
) -> Result<Option<CustomConversationEntry>, StoreError>;
/// Inserts the first non-empty prompt or updates the last-activity timestamp.
async fn upsert(&self, entry: CustomConversationEntry) -> Result<(), StoreError>;
}

View File

@ -37,6 +37,7 @@ pub mod chat_attachment;
pub mod chat_draft;
pub mod conversation;
pub mod conversation_log;
pub mod custom_conversation;
pub mod device;
pub mod error;
pub mod events;
@ -177,6 +178,7 @@ pub use conversation_log::{
TurnSlice, HANDOFF_SUMMARY_MAX_CHARS, MAX_ARCHIVE_SEGMENTS, PAGE_DEFAULT_LIMIT, PAGE_MAX_LIMIT,
ROTATE_AFTER_BYTES, ROTATE_AFTER_TURNS,
};
pub use custom_conversation::{CustomConversationEntry, CustomConversationIndex};
pub use device::{
AuthenticatedDevice, DeviceError, DeviceId, DeviceName, PairedDevice, SessionTokenHash,