feat(memory): config embedders (LOT C2) + suggestion contextuelle (LOT C3) + contexte projet partagé

- LOT C2 (§14.5.3) : use cases de configuration des embedders déclaratifs
  (List/Save/Delete + DescribeEmbedderEngines : modèles ONNX recommandés,
  environnement local détecté, stratégies compilées). UI EmbedderSettings.
- LOT C3 (§14.5.5) : suggestion contextuelle best-effort à l'activation quand la
  mémoire dépasse le budget de recall sans embedder configuré (event
  EmbedderSuggested, anti-spam 1×/session, « ne plus demander »).
- Contexte projet partagé .ideai/CONTEXT.md (model-agnostic) injecté à tous les
  agents/profils au lancement, avant la persona. UI ProjectContextPanel.

Tests : backend workspace vert (0 échec) ; frontend 306/306.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-09 09:24:51 +02:00
parent 32398827fb
commit 785e9935fd
118 changed files with 5793 additions and 866 deletions

View File

@ -10,32 +10,37 @@ use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use application::{
CloseProject, CloseTab, CloseTerminal, ConfigureProfiles, CreateAgentFromScratch,
CreateAgentFromTemplate, CreateLayout, CreateProject, CreateTemplate, DeleteAgent,
DeleteLayout, DeleteProfile, DeleteTemplate, DetectAgentDrift, DetectProfiles, FirstRunState,
GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus, GitUnstage,
HealthUseCase, InspectConversation, LaunchAgent, ListAgents, ListLayouts, ListProfiles, ListProjects, ListSkills,
ListTemplates, LiveAgentRegistry, LoadLayout, MoveTabToNewWindow, MutateLayout, OpenProject,
OpenTerminal, OrchestratorService, ReadAgentContext, ReferenceProfiles, RenameLayout,
ResizeTerminal, SaveProfile, SetActiveLayout, SnapshotRunningAgents,
AssignSkillToAgent, CreateSkill, DeleteSkill, UnassignSkillFromAgent, UpdateSkill,
CreateMemory, DeleteMemory, GetMemory, ListMemories, ReadMemoryIndex, RecallMemory,
ResolveMemoryLinks, UpdateMemory,
SyncAgentWithTemplate, TerminalSessions, UpdateAgentContext, UpdateTemplate, WriteToTerminal,
AssignSkillToAgent, CheckEmbedderSuggestion, CloseProject, CloseTab, CloseTerminal,
ConfigureProfiles, CreateAgentFromScratch, CreateAgentFromTemplate, CreateLayout, CreateMemory,
CreateProject, CreateSkill, CreateTemplate, DeleteAgent, DeleteEmbedderProfile, DeleteLayout,
DeleteMemory, DeleteProfile, DeleteSkill, DeleteTemplate, DescribeEmbedderEngines,
DetectAgentDrift, DetectProfiles, DismissEmbedderSuggestion, FirstRunState, GetMemory,
GitBranches, GitCheckout, GitCommit, GitGraph, GitInit, GitLog, GitStage, GitStatus,
GitUnstage, HealthUseCase, InspectConversation, LaunchAgent, ListAgents, ListEmbedderProfiles,
ListLayouts, ListMemories, ListProfiles, ListProjects, ListSkills, ListTemplates,
LiveAgentRegistry, LoadLayout, MoveTabToNewWindow, MutateLayout, OnnxModelView, OpenProject,
OpenTerminal, OrchestratorService, ReadAgentContext, ReadMemoryIndex, ReadProjectContext,
RecallMemory, ReferenceProfiles, RenameLayout, ResizeTerminal, ResolveMemoryLinks,
SaveEmbedderProfile, SaveProfile, SetActiveLayout, SnapshotRunningAgents, SuggestedThisSession,
SyncAgentWithTemplate, TerminalSessions, UnassignSkillFromAgent, UpdateAgentContext,
UpdateMemory, UpdateProjectContext, UpdateSkill, UpdateTemplate, WriteToTerminal,
AGENT_MEMORY_RECALL_BUDGET,
};
use domain::ports::{
AgentContextStore, AgentRuntime, Clock, Embedder, EventBus, FileSystem, GitPort, IdGenerator,
MemoryRecall, MemoryStore, ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore,
TemplateStore,
AgentContextStore, AgentRuntime, Clock, Embedder, EmbedderEnvInspector, EmbedderProfileStore,
EmbedderPromptStore, EventBus, FileSystem, GitPort, IdGenerator, MemoryRecall, MemoryStore,
ProcessSpawner, ProfileStore, ProjectStore, PtyPort, SkillStore, TemplateStore,
};
use domain::{DomainEvent, EmbedderProfile, Project, ProjectId};
use infrastructure::{
embedder_from_profile, AdaptiveMemoryRecall, ClaudeTranscriptInspector, CliAgentRuntime,
FsEmbedderProfileStore, FsMemoryStore, FsOrchestratorWatcher, FsProfileStore, FsProjectStore,
FsSkillStore, FsTemplateStore, Git2Repository, IdeaiContextStore, LocalFileSystem,
LocalProcessSpawner, NaiveMemoryRecall, OrchestratorWatchHandle, PortablePtyAdapter,
SystemClock, TokioBroadcastEventBus, UuidGenerator, VectorMemoryRecall, ONNX_CACHE_SUBDIR,
EmbedderEnvProbe, FsEmbedderProfileStore, FsEmbedderPromptStore, FsMemoryStore,
FsOrchestratorWatcher, FsProfileStore, FsProjectStore, FsSkillStore, FsTemplateStore,
Git2Repository, IdeaiContextStore, LocalFileSystem, LocalProcessSpawner, NaiveMemoryRecall,
OrchestratorWatchHandle, PortablePtyAdapter, SystemClock, TokioBroadcastEventBus,
UuidGenerator, VectorMemoryRecall, DEFAULT_OLLAMA_BASE_URL, ONNX_CACHE_SUBDIR,
RECOMMENDED_ONNX_MODELS, VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
};
use crate::pty::PtyBridge;
@ -59,6 +64,10 @@ pub struct AppState {
pub close_tab: Arc<CloseTab>,
/// List known projects.
pub list_projects: Arc<ListProjects>,
/// Read `.ideai/CONTEXT.md`.
pub read_project_context: Arc<ReadProjectContext>,
/// Overwrite `.ideai/CONTEXT.md`.
pub update_project_context: Arc<UpdateProjectContext>,
/// Open a terminal (spawn PTY, register session).
pub open_terminal: Arc<OpenTerminal>,
/// Write keystrokes to a terminal.
@ -195,6 +204,19 @@ pub struct AppState {
/// Recall the most relevant memory entries for a query within a budget
/// (LOT B — §14.5.2).
pub recall_memory: Arc<RecallMemory>,
// --- Embedder config (LOT C2 — §14.5.3) ---
/// List the configured embedder profiles (`embedder.json`).
pub list_embedder_profiles: Arc<ListEmbedderProfiles>,
/// Save (upsert, validating) an embedder profile.
pub save_embedder_profile: Arc<SaveEmbedderProfile>,
/// Delete an embedder profile by id.
pub delete_embedder_profile: Arc<DeleteEmbedderProfile>,
/// Describe the embedding engines available to the configuration UI (catalogue
/// + detected local environment + compiled-in capabilities).
pub describe_embedder_engines: Arc<DescribeEmbedderEngines>,
// --- Embedder suggestion (LOT C3 — §14.5.5) ---
/// Persist the user's response to the embedder suggestion (`later`/`never`).
pub dismiss_embedder_suggestion: Arc<DismissEmbedderSuggestion>,
// --- Orchestrator (§14.3) ---
/// Dispatches validated orchestrator requests to the agent/skill use cases.
/// Shared by every per-project filesystem watcher.
@ -254,6 +276,8 @@ impl AppState {
let close_project = Arc::new(CloseProject::new(Arc::clone(&store_port)));
let close_tab = Arc::new(CloseTab::new(Arc::clone(&store_port)));
let list_projects = Arc::new(ListProjects::new(Arc::clone(&store_port)));
let read_project_context = Arc::new(ReadProjectContext::new(Arc::clone(&fs_port)));
let update_project_context = Arc::new(UpdateProjectContext::new(Arc::clone(&fs_port)));
// --- PTY adapter + terminal use cases (L3) ---
let pty = Arc::new(PortablePtyAdapter::new());
@ -372,10 +396,11 @@ impl AppState {
// Load the configured embedder profile (mono-profile for now: take the last
// listed, fall back to `none`). A multi-profile selector is a follow-up; the
// `none` default keeps recall strictly naïve and dependency-free.
let embedder_store = FsEmbedderProfileStore::new(
let embedder_store = Arc::new(FsEmbedderProfileStore::new(
Arc::clone(&fs_port),
app_data_dir.to_string_lossy().into_owned(),
);
));
let embedder_store_port = Arc::clone(&embedder_store) as Arc<dyn EmbedderProfileStore>;
// `build` may run inside an ambient async runtime (Tauri's `setup`, or
// `#[tokio::test]`), so blocking the current thread on a future panics.
// Drive the one-shot load on a dedicated thread with its own runtime.
@ -401,6 +426,66 @@ impl AppState {
&onnx_cache_dir,
);
// --- Embedder configuration use cases (LOT C2 — §14.5.3) ---
// CRUD over `embedder.json` (port-typed store, built above) + a read-only
// description of the available engines. The environment probe shares the SAME
// `onnx_cache_dir` the recall uses, so "is this model cached?" stays coherent.
// The static ONNX catalogue and the compiled-capability flags are owned by
// infrastructure and injected here (the application stays infra-free, DIP).
let env_inspector = Arc::new(EmbedderEnvProbe::new(
onnx_cache_dir.clone(),
DEFAULT_OLLAMA_BASE_URL,
)) as Arc<dyn EmbedderEnvInspector>;
// Cloned for the suggestion check below (the original is moved into
// `describe_embedder_engines`). Both share the same probe behaviour.
let env_inspector_for_suggestion = Arc::clone(&env_inspector);
let recommended_onnx: Vec<OnnxModelView> = RECOMMENDED_ONNX_MODELS
.iter()
.map(|m| OnnxModelView {
id: m.id.to_owned(),
display_name: m.display_name.to_owned(),
dimension: m.dimension,
approx_size_mb: m.approx_size_mb,
recommended: m.recommended,
})
.collect();
let list_embedder_profiles =
Arc::new(ListEmbedderProfiles::new(Arc::clone(&embedder_store_port)));
let save_embedder_profile =
Arc::new(SaveEmbedderProfile::new(Arc::clone(&embedder_store_port)));
let delete_embedder_profile =
Arc::new(DeleteEmbedderProfile::new(Arc::clone(&embedder_store_port)));
let describe_embedder_engines = Arc::new(DescribeEmbedderEngines::new(
env_inspector,
recommended_onnx,
VECTOR_HTTP_ENABLED,
VECTOR_ONNX_ENABLED,
));
// --- Embedder suggestion (LOT C3 — §14.5.5) ---
// Per-project dismissal state (`.ideai/memory/.embedder-prompt.json`) +
// the in-memory "already suggested this session" guard, shared with the
// launcher's best-effort check. The check publishes EmbedderSuggested at
// most once per session per project, only while strategy is `none` and the
// memory has outgrown the recall budget.
let prompt_store = Arc::new(FsEmbedderPromptStore::new(Arc::clone(&fs_port)));
let prompt_store_port = Arc::clone(&prompt_store) as Arc<dyn EmbedderPromptStore>;
let suggested_this_session: SuggestedThisSession = SuggestedThisSession::default();
let check_embedder_suggestion = Arc::new(CheckEmbedderSuggestion::new(
Arc::clone(&embedder_store_port),
Arc::clone(&memory_store_port),
Arc::clone(&prompt_store_port),
env_inspector_for_suggestion,
Arc::clone(&events_port),
Arc::clone(&suggested_this_session),
AGENT_MEMORY_RECALL_BUDGET,
VECTOR_HTTP_ENABLED,
VECTOR_ONNX_ENABLED,
));
let dismiss_embedder_suggestion = Arc::new(DismissEmbedderSuggestion::new(Arc::clone(
&prompt_store_port,
)));
let create_agent = Arc::new(CreateAgentFromScratch::new(
Arc::clone(&contexts_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
@ -426,6 +511,7 @@ impl AppState {
Arc::clone(&events_port),
Arc::clone(&ids) as Arc<dyn IdGenerator>,
Arc::clone(&memory_recall_port),
Some(Arc::clone(&check_embedder_suggestion)),
));
// --- Conversation inspection (T7) ---
@ -437,11 +523,9 @@ impl AppState {
let home_dir = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_default();
let inspectors: Vec<Arc<dyn domain::ports::SessionInspector>> =
vec![Arc::new(ClaudeTranscriptInspector::new(
Arc::clone(&fs_port),
home_dir,
))];
let inspectors: Vec<Arc<dyn domain::ports::SessionInspector>> = vec![Arc::new(
ClaudeTranscriptInspector::new(Arc::clone(&fs_port), home_dir),
)];
let inspect_conversation = Arc::new(InspectConversation::new(
Arc::clone(&contexts_port),
Arc::clone(&profile_store_port),
@ -491,14 +575,20 @@ impl AppState {
let git_status = Arc::new(GitStatus::new(Arc::clone(&git_port)));
let git_stage = Arc::new(GitStage::new(Arc::clone(&git_port)));
let git_unstage = Arc::new(GitUnstage::new(Arc::clone(&git_port)));
let git_commit = Arc::new(GitCommit::new(Arc::clone(&git_port), Arc::clone(&events_port)));
let git_commit = Arc::new(GitCommit::new(
Arc::clone(&git_port),
Arc::clone(&events_port),
));
let git_branches = Arc::new(GitBranches::new(Arc::clone(&git_port)));
let git_checkout = Arc::new(GitCheckout::new(
Arc::clone(&git_port),
Arc::clone(&events_port),
));
let git_log = Arc::new(GitLog::new(Arc::clone(&git_port)));
let git_init = Arc::new(GitInit::new(Arc::clone(&git_port), Arc::clone(&events_port)));
let git_init = Arc::new(GitInit::new(
Arc::clone(&git_port),
Arc::clone(&events_port),
));
let git_graph = Arc::new(GitGraph::new(Arc::clone(&git_port)));
// --- Skill use cases (L12) ---
@ -578,6 +668,8 @@ impl AppState {
close_project,
close_tab,
list_projects,
read_project_context,
update_project_context,
open_terminal,
write_terminal,
resize_terminal,
@ -639,6 +731,11 @@ impl AppState {
read_memory_index,
resolve_memory_links,
recall_memory,
list_embedder_profiles,
save_embedder_profile,
delete_embedder_profile,
describe_embedder_engines,
dismiss_embedder_suggestion,
orchestrator_service,
orchestrator_watchers: Mutex::new(HashMap::new()),
move_tab,