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

@ -192,7 +192,15 @@ fn profile(id: ProfileId) -> AgentProfile {
}
fn agent(id: AgentId, profile_id: ProfileId) -> Agent {
Agent::new(id, "Bob", "agents/bob.md", profile_id, AgentOrigin::Scratch, false).unwrap()
Agent::new(
id,
"Bob",
"agents/bob.md",
profile_id,
AgentOrigin::Scratch,
false,
)
.unwrap()
}
fn input(agent_id: AgentId) -> InspectConversationInput {
@ -228,7 +236,10 @@ async fn supporting_inspector_propagates_details() {
);
let out = uc.execute(input(aid)).await.unwrap();
assert_eq!(out.details.last_topic.as_deref(), Some("refactor the parser"));
assert_eq!(
out.details.last_topic.as_deref(),
Some("refactor the parser")
);
assert_eq!(out.details.token_count, Some(4242));
// Routed the conversation id and the agent's isolated run dir (not the root).
@ -270,8 +281,10 @@ async fn read_error_degrades_to_empty_details() {
let aid = AgentId::from_uuid(Uuid::from_u128(42));
let a = agent(aid, pid);
let (inspector, _seen) =
FakeInspector::new(true, InspectResult::Err(InspectError::Read("boom".to_owned())));
let (inspector, _seen) = FakeInspector::new(
true,
InspectResult::Err(InspectError::Read("boom".to_owned())),
);
let uc = InspectConversation::new(
Arc::new(FakeContexts::with_agent(&a)),
@ -280,7 +293,13 @@ async fn read_error_degrades_to_empty_details() {
);
let out = uc.execute(input(aid)).await.unwrap();
assert_eq!(out.details, ConversationDetails { last_topic: None, token_count: None });
assert_eq!(
out.details,
ConversationDetails {
last_topic: None,
token_count: None
}
);
}
// ---------------------------------------------------------------------------
@ -344,7 +363,10 @@ async fn unknown_agent_errors() {
let aid = AgentId::from_uuid(Uuid::from_u128(99));
let (inspector, _seen) = FakeInspector::new(
true,
InspectResult::Ok(ConversationDetails { last_topic: None, token_count: None }),
InspectResult::Ok(ConversationDetails {
last_topic: None,
token_count: None,
}),
);
let uc = InspectConversation::new(