feat(attachments): fondation pipeline durable d'attachments agent/chat + sandbox-safe (#154, QA verte)

Socle #154 : entité attachment, store, contrat DTO et routage session. Modules : domain/chat_attachment, application/chat_attachments, infrastructure/chat_attachments, app-tauri (commands/lib), backend dto.
This commit is contained in:
2026-08-06 10:44:15 +02:00
parent fb19ee48dc
commit 1c80d08f92
16 changed files with 1210 additions and 158 deletions

View File

@ -2966,6 +2966,79 @@ impl From<TerminalSession> for TerminalSessionDto {
// Structured chat sessions (§17 — D4)
// ---------------------------------------------------------------------------
/// Structured attachment intent sent by the chat frontend.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatAttachmentInputDto {
/// Local source path selected or staged by the driving adapter.
pub path: String,
/// Optional MIME type known by the frontend/OS picker.
#[serde(default)]
pub mime: Option<String>,
/// Origin kind (`localFile`, `clipboard`, `dragDrop`, `other`).
#[serde(default)]
pub source_kind: Option<domain::ChatAttachmentSourceKind>,
}
/// Request for importing durable chat attachments before or during a structured turn.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportChatAttachmentsRequestDto {
/// Live structured/chat session id.
pub session_id: String,
/// Legacy local source paths, kept for transition compatibility.
#[serde(default)]
pub attachment_paths: Vec<String>,
/// Structured attachment intents.
#[serde(default)]
pub attachments: Vec<ChatAttachmentInputDto>,
}
/// Response returned after durable chat attachment import.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportChatAttachmentsResponseDto {
/// Imported durable attachments.
pub attachments: Vec<ChatAttachmentDto>,
}
/// Durable chat attachment metadata returned by the backend/application layer.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChatAttachmentDto {
/// Stable attachment id.
pub id: String,
/// Original display filename.
pub filename: String,
/// MIME type.
pub mime: String,
/// Size in bytes.
pub size_bytes: u64,
/// Origin kind.
pub source_kind: domain::ChatAttachmentSourceKind,
/// Stored relative path under `.ideai/attachments`.
pub storage_path: String,
/// Absolute managed path readable by the target agent sandbox.
pub readable_path: String,
/// Creation time, epoch milliseconds.
pub created_at: u64,
}
impl From<domain::ChatAttachment> for ChatAttachmentDto {
fn from(value: domain::ChatAttachment) -> Self {
Self {
id: value.id.as_str().to_owned(),
filename: value.filename,
mime: value.mime,
size_bytes: value.size_bytes,
source_kind: value.source_kind,
storage_path: value.storage_path,
readable_path: value.readable_path,
created_at: value.created_at,
}
}
}
/// One incremental chunk of a structured agent reply, streamed over the chat
/// session's adapter-owned channel. The serialised wire twin of a
/// [`domain::ports::ReplyEvent`]: the `agent_send` pump maps each turn event to