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

@ -186,8 +186,14 @@ fn agent_leaf(node: NodeId, agent: Option<AgentId>) -> LeafCell {
}
async fn register_project(store: &FakeStore, id: ProjectId) {
let project =
Project::new(id, "Demo", ProjectPath::new(ROOT).unwrap(), RemoteRef::Local, 0).unwrap();
let project = Project::new(
id,
"Demo",
ProjectPath::new(ROOT).unwrap(),
RemoteRef::Local,
0,
)
.unwrap();
store.save_project(&project).await.unwrap();
}
@ -222,11 +228,7 @@ fn was_running(fs: &FakeFs, node: NodeId) -> Option<bool> {
find(&tree.root, node)
}
fn make_use_case(
store: &FakeStore,
fs: &FakeFs,
live: &FakeLive,
) -> SnapshotRunningAgents {
fn make_use_case(store: &FakeStore, fs: &FakeFs, live: &FakeLive) -> SnapshotRunningAgents {
SnapshotRunningAgents::new(
Arc::new(store.clone()) as Arc<dyn ProjectStore>,
Arc::new(fs.clone()) as Arc<dyn FileSystem>,
@ -247,7 +249,11 @@ async fn live_agent_is_marked_running() {
let leaf = nid(10);
let agent = aid(100);
seed_layouts(&fs, lid(1), &LayoutTree::single(agent_leaf(leaf, Some(agent))));
seed_layouts(
&fs,
lid(1),
&LayoutTree::single(agent_leaf(leaf, Some(agent))),
);
let live = FakeLive::with_nodes(&[leaf]);
let uc = make_use_case(&store, &fs, &live);
@ -271,7 +277,11 @@ async fn stopped_agent_is_marked_not_running() {
let leaf = nid(10);
let agent = aid(100);
seed_layouts(&fs, lid(1), &LayoutTree::single(agent_leaf(leaf, Some(agent))));
seed_layouts(
&fs,
lid(1),
&LayoutTree::single(agent_leaf(leaf, Some(agent))),
);
// Registry is empty: the agent has no live session.
let live = FakeLive::default();
@ -347,7 +357,11 @@ async fn snapshot_reads_liveness_before_kill() {
let leaf = nid(10);
let agent = aid(100);
seed_layouts(&fs, lid(1), &LayoutTree::single(agent_leaf(leaf, Some(agent))));
seed_layouts(
&fs,
lid(1),
&LayoutTree::single(agent_leaf(leaf, Some(agent))),
);
let live = FakeLive::with_nodes(&[leaf]);
let uc = make_use_case(&store, &fs, &live);
@ -365,7 +379,11 @@ async fn snapshot_reads_liveness_before_kill() {
// Re-seed and demonstrate the opposite order yields `false` — proving the
// flag is sensitive to registry state at call time (hence order matters).
seed_layouts(&fs, lid(1), &LayoutTree::single(agent_leaf(leaf, Some(agent))));
seed_layouts(
&fs,
lid(1),
&LayoutTree::single(agent_leaf(leaf, Some(agent))),
);
uc.execute(SnapshotRunningAgentsInput { project_id: pid(1) })
.await
.unwrap();
@ -443,7 +461,10 @@ async fn duplicate_leaves_same_agent_only_live_node_is_running() {
.unwrap();
assert_eq!(out.running, 1, "only the live cell counts as running");
assert_eq!(out.stopped, 1, "the duplicate (dead) cell counts as stopped");
assert_eq!(
out.stopped, 1,
"the duplicate (dead) cell counts as stopped"
);
assert_eq!(was_running(&fs, leaf_a), Some(true));
assert_eq!(
was_running(&fs, leaf_b),