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

@ -116,6 +116,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
* set by the hook once launchAgent resolves.
*/
const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
const [panelNodeIds, setPanelNodeIds] = useState<Record<string, string>>({});
/**
* Live PTY session id of the running agent terminal, keyed by agent id. Lets
@ -149,11 +150,15 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
}
function handleLaunch(agentId: string) {
setPanelNodeIds((prev) => ({
...prev,
[agentId]: prev[agentId] ?? crypto.randomUUID(),
}));
setActiveAgentId(agentId);
}
function handleStop() {
vm.stopAgent();
function handleStop(agentId?: string) {
void vm.stopAgent(agentId);
setActiveAgentId(null);
}
@ -288,6 +293,7 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
{vm.agents.map((a) => {
const isSelected = a.id === vm.selectedAgentId;
const isRunning = a.id === activeAgentId;
const live = vm.liveAgents.find((candidate) => candidate.agentId === a.id);
const profileName =
vm.profiles.find((p) => p.id === a.profileId)?.name ??
a.profileId;
@ -318,6 +324,11 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
)}
</span>
<span className="text-xs text-muted">{profileName}</span>
{live && (
<span className="text-xs text-primary">
running in IdeA · {live.sessionId ?? live.nodeId}
</span>
)}
</button>
<div className="flex items-center gap-1.5 shrink-0">
@ -334,12 +345,12 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
Sync
</Button>
)}
{isRunning ? (
{isRunning || live ? (
<Button
size="sm"
variant="ghost"
aria-label={`stop ${a.name}`}
onClick={handleStop}
onClick={() => handleStop(a.id)}
className="text-danger hover:text-danger"
>
Stop
@ -381,7 +392,11 @@ export function AgentsPanel({ projectId, projectRoot = "" }: AgentsPanelProps) {
key={activeAgentId}
cwd={projectRoot}
open={(opts, onData) =>
vm.launchAgent(activeAgentId, opts, onData)
vm.launchAgent(
activeAgentId,
{ ...opts, nodeId: panelNodeIds[activeAgentId] },
onData,
)
}
reattach={(sessionId, onData) =>
gateways.agent.reattach(sessionId, onData)