feat(backend,frontend): lot utilisateur #310 #311 #312 #313 — horodatage conversation, stop agent robuste, préférence terminal par défaut, paramètres projet (QA verte)

- #310: horodatage de tous les messages et événements de conversation visibles — composant partagé EntryTimestamp, timestamps propagés dans le transcript chat, les annonces inter-agent et le viewer de conversation, normalisation côté domain/adapters (conversation_log, paginate).
- #311: stop d'agent robuste pour PTY non monté, sessions headless et CLI custom — lifecycle/orchestrator durcis, ponts chat app-tauri/web-server alignés, arrêt fiable sans session orpheline.
- #312: préférence globale d'interface terminal par défaut — nouveau store settings (domain/application/infrastructure + tests), DTO et commandes backend, adapter frontend userSettings, panneau TerminalSettings dans les paramètres globaux.
- #313: Paramètres du projet — contexte de projet éditable et couleur de thème par projet (ProjectSettingsPanel + projectTheme), teinte des onglets projet, persistance via DTO projet.

QA verte (GO QA sur feature/batch-310-311-312-313-chat-stop-prefs-theme) — vérifications Git: git diff --cached --check OK; staging borné à crates/ et frontend/ (74 fichiers, +2105/-531), commit unique de lot conforme à la convention des lots (précédents batch-151/158/159, batch-210/211/212) car les fichiers partagés (dto.rs, commands.rs, CustomAgentChatView) entrelacent les 4 tickets.

fichiers .ideai/* (agents/test.md, chat-drafts.json, system-permissions.json, tickets/counter.json, tickets/index.json, tickets/308-313/), attachments/ et scripts/ hors périmètre et préservés en worktree ; aucun push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 21:26:16 +02:00
parent fc45594d9a
commit 402a60f213
74 changed files with 2105 additions and 531 deletions

View File

@ -91,8 +91,10 @@ pub struct ConversationTurn {
pub id: TurnId,
/// La conversation (paire) à laquelle ce tour appartient.
pub conversation: ConversationId,
/// Horodatage (epoch millisecondes) du tour.
pub at_ms: u64,
/// Horodatage (epoch millisecondes) du tour. Absent uniquement pour les
/// lignes legacy écrites avant que l'horodatage ne devienne universel.
#[serde(default)]
pub at_ms: Option<u64>,
/// L'origine de l'entrée (Humain ou Agent délégant).
pub source: InputSource,
/// La nature du tour (invite, réponse, activité outillée).
@ -115,7 +117,7 @@ impl ConversationTurn {
Self {
id,
conversation,
at_ms,
at_ms: Some(at_ms),
source,
role,
text: text.into(),

View File

@ -67,6 +67,7 @@ pub mod readiness;
pub mod remote;
pub mod sandbox;
pub mod session_limit;
pub mod settings;
pub mod skill;
pub mod slash_command;
pub mod sprint;
@ -258,6 +259,8 @@ pub use sandbox::{
SandboxKind, SandboxPlan, SandboxStatus,
};
pub use settings::{DefaultAgentTerminalInterface, IdeaUserSettings, ProjectSettings, ThemeColor};
pub use orchestrator::{
OrchestratorCommand, OrchestratorError, OrchestratorRequest, OrchestratorVisibility,
};
@ -277,9 +280,10 @@ pub use ports::{
PluginManifestError, PluginManifestValidator, PluginMcpError, PluginMcpSupervisor,
PluginPackageStore, PluginRegistryError, PluginRegistryStore, PluginStorageError,
PluginStorageStore, PluginStoreError, PreparedContext, ProcessError, ProcessSpawner,
ProfileStore, ProjectPluginSettingsStore, ProjectStore, ProviderModelCatalogue, PtyError,
PtyHandle, PtyPort, RemoteError, RemoteHost, RemotePath, RuntimeError, RuntimePermissionProbe,
ScheduledTask, Scheduler, SpawnSpec, SprintStore, SprintStoreError, StoreError,
StructuredSessionEnvironment, StructuredSessionEnvironmentPreparer, SystemPermissionStore,
TemplateStore, WindowStateStore,
ProfileStore, ProjectPluginSettingsStore, ProjectSettingsStore, ProjectStore,
ProviderModelCatalogue, PtyError, PtyHandle, PtyPort, RemoteError, RemoteHost, RemotePath,
RuntimeError, RuntimePermissionProbe, ScheduledTask, Scheduler, SpawnSpec, SprintStore,
SprintStoreError, StoreError, StructuredSessionEnvironment,
StructuredSessionEnvironmentPreparer, SystemPermissionStore, TemplateStore, UserSettingsStore,
WindowStateStore,
};

View File

@ -539,6 +539,31 @@ pub trait ProjectPluginSettingsStore: Send + Sync {
) -> Result<(), StoreError>;
}
/// Persistence for global, machine-local user settings.
#[async_trait]
pub trait UserSettingsStore: Send + Sync {
/// Loads settings, returning an empty document when missing.
async fn load(&self) -> Result<crate::IdeaUserSettings, StoreError>;
/// Persists the complete document.
async fn save(&self, settings: &crate::IdeaUserSettings) -> Result<(), StoreError>;
}
/// Persistence for settings owned by one project.
#[async_trait]
pub trait ProjectSettingsStore: Send + Sync {
/// Loads settings, returning an empty document when missing.
async fn load(
&self,
project_root: &crate::ProjectPath,
) -> Result<crate::ProjectSettings, StoreError>;
/// Persists the complete document.
async fn save(
&self,
project_root: &crate::ProjectPath,
settings: &crate::ProjectSettings,
) -> Result<(), StoreError>;
}
/// Manifest parser and validator.
pub trait PluginManifestValidator: Send + Sync {
/// Validates raw manifest bytes.

View File

@ -0,0 +1,69 @@
//! Machine-local user preferences and project-owned presentation settings.
use serde::{Deserialize, Serialize};
/// Preferred interface when a new human-facing agent terminal is opened.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum DefaultAgentTerminalInterface {
/// Provider's native interactive terminal UI.
NativeTui,
/// IdeA's structured/custom CLI chat surface.
IdeaCli,
}
/// Global, machine-local IdeA preferences.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IdeaUserSettings {
/// Default for newly launched human-facing agent terminals.
#[serde(default)]
pub default_agent_terminal_interface: Option<DefaultAgentTerminalInterface>,
}
/// Strict project theme colour (`#RRGGBB`).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct ThemeColor(String);
impl ThemeColor {
/// Validates and builds a project theme colour.
pub fn new(value: impl Into<String>) -> Result<Self, String> {
let value = value.into();
let valid = value.len() == 7
&& value.starts_with('#')
&& value.as_bytes()[1..].iter().all(u8::is_ascii_hexdigit);
valid
.then_some(Self(value))
.ok_or_else(|| "themeColor must match #RRGGBB".to_owned())
}
/// Returns the validated wire representation.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl TryFrom<String> for ThemeColor {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<ThemeColor> for String {
fn from(value: ThemeColor) -> Self {
value.0
}
}
/// Settings owned by one project and stored below its `.ideai` directory.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProjectSettings {
/// Optional visual theme colour; absence keeps the product fallback.
#[serde(default)]
pub theme_color: Option<ThemeColor>,
}