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:
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user