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

@ -205,7 +205,10 @@ async fn scan_once(
/// Whether `path` is a request file to process: a `.json` that is not a
/// `.response.json` sibling we wrote ourselves.
fn is_request_file(path: &Path) -> bool {
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or_default();
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default();
name.ends_with(".json") && !name.ends_with(".response.json")
}
@ -242,14 +245,21 @@ async fn dispatch_file(
Ok(r) => r,
Err(e) => return OrchestratorResponse::failure(None, format!("invalid json: {e}")),
};
let action = request.action.clone();
let action = request
.request_type
.as_ref()
.or(request.action.as_ref())
.cloned();
let command = match request.validate() {
Ok(c) => c,
Err(e) => return OrchestratorResponse::failure(Some(action), e.to_string()),
Err(e) => return OrchestratorResponse::failure(action, e.to_string()),
};
match service.dispatch(project, command).await {
Ok(out) => OrchestratorResponse::success(action, out.detail),
Err(e) => OrchestratorResponse::failure(Some(action), e.to_string()),
Ok(out) => OrchestratorResponse::success(
action.unwrap_or_else(|| "unknown".to_owned()),
out.detail,
),
Err(e) => OrchestratorResponse::failure(action, e.to_string()),
}
}