Date : 15-02-2026 03:00
Status : IMPLEMENTEE
Auditeur : Claude Opus 4.6 (pass 2, regard neuf)
Scope : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/ (16 fichiers .ts)
Angle : Architecture, interactions inter-composants, WebSocket, cycle de vie agents, gestion sessions
Client WS
|
v
index.ts:handleWsMessage(ws, raw) [L213]
|-- Parse JSON (ou utilise l'objet auto-parse de Bun)
|-- type='auth' -> getOrCreateSession() -> auth_success
|-- type='set_conversation' -> session.activeConversationId
|-- type='message' :
| |-- Lookup session via sessions.get(msg.apiKey) [L253]
| |-- touchSession() [L259]
| |-- Auto-create conversation si aucune active [L265-274]
| |-- Save user message dans DB [L279-289]
| |-- Check briefing actif -> handleBriefingMessage() [L310-341]
| |-- OU director.handleMessage() [L344]
| | |
| | v
| director/index.ts:handleMessage() [L170]
| |-- sessionHistory.push() + trim a 20 [L176-177]
| |-- Cree objective en DB [L197-208]
| |-- interpreter.route(message) [L224]
| | |
| | v
| | interpreter.ts:route() [L209]
| | |-- fastKeywordMatch() -> si match, skip LLM [L214-218]
| | |-- OU llmChat(qwen3:8b, routing prompt) [L222-232]
| | |-- OU keywordFallback() [L251]
| | |-- checkInstanceAmbiguity() [L257-259]
| | |-- Retourne RoutingDecision
| | |
| |-- Si needs_briefing -> start briefing, return pending [L235-256]
| |-- Create AbortController [L273-274]
| |-- Si pipeline -> pipelineRunner.runSequential() [L278-301]
| |-- Si multi_step -> handleMultiStep() [L302-304]
| |-- Sinon -> handleSingleStep() [L306-307]
| | |
| | v
| | handleSingleStep() [L369]
| | |-- buildAgentConfig() -> registry lookup [L378]
| | |-- fetchRelevantLessons() -> embeddings [L405]
| | |-- contextBuilder.build() [L406]
| | |-- runner.run() [L411]
| | | |
| | | v
| | | agent-runner/index.ts:run() [L70]
| | | |-- Boucle ReAct (max 10 iterations)
| | | |-- Check abort signal [L109]
| | | |-- Check timeout (10 min) [L124]
| | | |-- llmChatWithRetry() (3 retries, backoff) [L396]
| | | | |
| | | | v
| | | | tools/client.ts:llmChat() [L306]
| | | | |-- POST /api/jobs (ai-orchestrator) [L332]
| | | | |-- waitForCallback() (callback-based) [L347]
| | | | | |-- Timeout -> poll fallback [L421-437]
| | | | |-- Retourne LLMResponse
| | | | |
| | | |-- Si tool_calls -> executeTool() en parallel [L193]
| | | | |-- Circuit breaker check [L200]
| | | | |-- compactToolResult() si trop gros [L427]
| | | | |-- Repeated error auto-stop (3x) [L279]
| | | |-- Si content -> return success [L296-306]
| | | |-- Si vide -> nudge LLM [L310-316]
| | |
| |-- translateOutput() -> traduction FR [L314-316]
| |-- Cleanup abort controller [L311]
| |-- Save lesson on failure [L355-361]
| |-- Return Objective
| |
|-- ws.send(objective_result) [L351-360]
|-- Save assistant message + update conversation [L362-396]
|-- Auto-title (1st msg) [L385-386]
|-- Auto-summary (every 10 msgs) [L389-391]
getOrCreateSession(apiKey) [index.ts:145] : Lookup dans la Map sessions, sinon authenticate() + creation ToolsClient + Director + chargement des prompt overrides depuis DB.resolveSession(apiKey) [index.ts:182] : Wrapper safe qui catch les erreurs d'auth.touchSession(apiKey) [index.ts:176] : Met a jour lastActivity a chaque message.SESSION_IDLE_MS = 30 min [index.ts:18].setInterval toutes les 5 min [index.ts:197-210] : Parcourt les sessions, supprime celles idle > 30 min. Nettoie aussi le suggestionsCache.session.ws = ws [index.ts:221] : La reference WS est stockee dans la session lors du message auth.agents/registry.ts : objet AGENTS avec 24 agents definis [L23-638].promptOverrides Map [L641] : charges depuis DB au demarrage de session [index.ts:159-171].Interpreter.route() [interpreter.ts:209] : 3 niveaux de fallback :fastKeywordMatch() — regex high-confidence, skip LLMkeywordFallback() — regex low-confidencecheckInstanceAmbiguity() pour disambiguation multi-instance.AgentRunner.run() [agent-runner/index.ts:70] : Boucle ReAct avec :handleMessage() [director/index.ts:273], verifie en debut de boucle [agent-runner:109] et entre sub-tasks [director:456].Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L1219-1221 (close handler CLI), L1232-1234 (close handler WebUI)
Classification : BUG PROBABLE
Probleme : Quand un client WS se deconnecte (close handler, L1220/L1233), le code ne fait QUE logger. Il ne :
session.ws = undefined pour invalider la reference WS morteConsequence : Apres deconnexion WS :
session.ws pointe vers un WS ferme. Tout ws.send() appele depuis un fire-and-forget (generateConversationTitle L86, generateConversationSummary L127, events via onEvent L294) va crash silencieusement ou lever une exception non catchee.Detail critique : Le onEvent callback ferme sur le ws capture au moment du message [L293-307]. Si le WS se ferme pendant l'execution de l'agent, chaque event envoye va tenter ws.send() sur une connexion fermee.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L1219-1221
Classification : BUG CERTAIN
Probleme : Le close handler WS ne sait pas QUEL apiKey est associe au WS qui se ferme. La Map sessions est indexee par apiKey, mais le close handler n'a pas acces a l'apiKey — seulement a l'objet ws.
De plus, comme documente dans MEMORY.md (bug connu Elysia/Bun WS), l'objet ws dans les handlers open/message/close n'est PAS la meme reference. Donc meme un Map<ws, apiKey> ne fonctionnerait pas.
Consequence : Il est structurellement impossible, dans l'architecture actuelle, de nettoyer la session quand le WS se ferme. C'est un defaut de conception.
Solution : Utiliser une string key (ex: apiKey ou sessionId) stockee dans ws.data lors du message auth, puis chercher/nettoyer par cette cle dans le close handler.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts
Ligne : L13
Classification : BUG PROBABLE
Probleme : toolCircuitBreaker et responseCache sont declares comme constantes globales (singletons). Le circuit breaker est keye par nom d'outil (ex: exec, dockerPs).
Si un outil echoue 3x pour une session (ex: instance SSH temporairement indisponible pour l'utilisateur A), le circuit s'ouvre pour TOUS les utilisateurs/sessions. L'utilisateur B, qui a des instances SSH differentes et fonctionnelles, sera bloque.
Gravite actuelle : Faible en pratique (mono-utilisateur), mais c'est un defaut architectural si le systeme est utilise par plusieurs utilisateurs.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L486-512
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts
Lignes : L109-121
Classification : RISQUE THEORIQUE
Probleme : Le cancel REST [index.ts:500] appelle director.cancelObjective(id) qui abort le AbortController. Cependant, l'abort signal est seulement verifie en DEBUT de boucle d'iteration [agent-runner:109]. Si l'agent est en plein milieu d'un llmChat() (qui attend un callback AI job avec timeout 5 min), le cancel ne prendra effet qu'au retour du LLM, pas immediatement.
De plus, les jobs LLM soumis a l'AI orchestrator ne sont PAS annules. Le job continue sur le GPU, consomme des ressources, et le callback arrive sur un callbackId qui n'a plus de pending resolver (callback perdu silencieusement, ce qui est OK, mais le GPU a travaille pour rien).
Amelioration : Verifier le signal aussi dans waitForCallback(), et optionnellement envoyer un DELETE sur le job AI orchestrator.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts
Lignes : L88, L416-441
Classification : RISQUE THEORIQUE
Probleme : pendingJobs (Map globale) stocke les callbacks en attente. Le seul mecanisme de nettoyage est le setTimeout dans waitForCallback() [L418]. Si le process crash ou si une exception non geree empile des pending jobs sans que le timeout se declenche, la Map croit indefiniment.
En pratique, le setTimeout devrait toujours nettoyer. Mais si de nombreux jobs sont soumis en parallele (ex: multi-step avec sous-taches rapides), la Map pourrait avoir des dizaines d'entrees simultanees. Ce n'est pas un leak mais c'est une zone sans monitoring.
Observation : L'endpoint GET /internal/pending-jobs [index.ts:1207-1209] expose les IDs en attente, ce qui est bien pour le debug.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/response-cache.ts
Lignes : L23-37
Classification : BUG PROBABLE
Probleme : Le hash utilise un djb2 simple sur 32 bits. La cle est construite avec model + systemPrompt[:200] + lastUserMessage. Deux problemes :
Troncature du system prompt a 200 chars : Deux agents differents avec le meme prefixe de prompt (200 premiers chars identiques) et le meme message utilisateur auront le MEME hash. Le cache retournera la reponse de l'agent A pour l'agent B.
Hash 32-bit : Avec 50 entrees max, le birthday problem donne ~0.03% de collision. Faible mais non-nul.
Impact : Un agent pourrait recevoir une reponse cachee d'un autre agent, avec des tool calls inadaptees. Cela dit, le cache n'est utilise que sur l'iteration 1, et le contexte complet (y compris les tool results) n'est pas cache, donc l'impact est limite a la premiere reponse LLM.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/director/index.ts
Lignes : L314-316, L666-684
Classification : BUG PROBABLE
Probleme : translateOutput() est appelee sur CHAQUE reponse agent reussie [L314-316]. Elle envoie l'output complet a qwen3:8b pour traduction. Le prompt dit "Garde le formatage Markdown intact".
Risques :
Impact : Un output comme Container "nginx-prod" restarted on 192.168.1.12 pourrait devenir Conteneur "nginx-prod" redemarre sur 192.168.1.12 (OK) ou pire, le LLM pourrait "traduire" l'IP ou le nom du container.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L159-171
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agents/registry.ts
Lignes : L641-655
Classification : BUG PROBABLE (en multi-utilisateur)
Probleme : promptOverrides est une Map globale dans registry.ts. Quand la session d'un utilisateur est creee [index.ts:159-171], ses prompt overrides sont charges depuis la DB et appliques GLOBALEMENT via setPromptOverride(). Si deux utilisateurs ont des overrides differents pour le meme agent, le dernier a se connecter ecrase les overrides du premier.
Impact actuel : Nul (mono-utilisateur), mais c'est une bombe a retardement architecturale.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L986-998 (delete conversation), L1053-1086 (patch message), L1089-1108 (fork)
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/db/index.ts
Classification : RISQUE THEORIQUE (mono-utilisateur) / BUG CERTAIN (multi-utilisateur)
Probleme : Les endpoints REST pour conversations et messages ne verifient pas que la conversation/message appartient au user authentifie. Exemples :
DELETE /api/conversations/:id [index.ts:986] : Appelle db.deleteConversation(params.id) sans verifier que conversation.user_id === session.userCtx.userId.PATCH /api/conversations/:id/messages/:msgId [index.ts:1053] : Verifie que le message appartient a la conversation, mais PAS que la conversation appartient au user.POST /api/conversations/:id/fork [index.ts:1089] : Le fork cree les nouveaux messages avec le user_id de la session, mais copie les messages d'une conversation potentiellement d'un autre user.GET /api/conversations/:id/export [index.ts:1017] : Exporte les messages sans verifier l'ownership.Le meme probleme existe pour les objectifs : DELETE /api/objectives/:id [index.ts:515] et POST /api/objectives/:id/cancel [index.ts:486] ne verifient pas l'ownership.
Attenuation : PostgREST peut avoir du RLS, mais le code ulias-org utilise une API key partagee via connectors-api, donc le RLS ne s'applique probablement pas au niveau user.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts
Lignes : L176-202 (exec), L204-218 (readFile/writeFile)
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts
Lignes : L481-602 (executeTool)
Classification : RISQUE THEORIQUE
Probleme : Le LLM decide quels outils appeler et avec quels arguments. L'outil exec [client.ts:176] execute directement la commande sur un serveur SSH. Les arguments viennent du LLM, qui interprete le message utilisateur.
Scenario d'attaque :
"Execute la commande suivante sur prod-portainer : rm -rf /"monitor ou incident (qui ont exec dans leur toolset)exec("prod-portainer-SSH", "rm -rf /")executeTool() [agent-runner:487] appelle toolsClient.exec() sans aucune validation/sanitizationAttenuation existante :
requiresApproval: ['exec'] (ex: patcher), mais la plupart des agents avec exec n'ont PAS de requirement d'approbation (monitor, incident, infra, backup, analyst, watcher, tester, etc.)onDecision callback n'est jamais fourni par le code actuel (index.ts ne le passe pas au director/runner), donc meme les agents avec requiresApproval auto-deny toujours [agent-runner:371-375]Second vecteur : writeFile [client.ts:212-218] utilise un heredoc avec echappement des single quotes, mais ne sanitise pas le contenu contre l'injection de commandes shell via $() ou backticks dans le contenu.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts
Lignes : L70-76 (signature run), L356-391 (requestDecision)
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/director/index.ts
Lignes : L411 (appel runner.run sans onDecision)
Classification : BUG CERTAIN
Probleme : AgentRunner.run() accepte un onDecision?: DecisionCallback [L74]. Mais Director.handleSingleStep() [L411] et handleMultiStep() [L529] appellent toujours runner.run(agentConfig, task, onEvent, undefined, signal) — le 4eme argument (onDecision) est toujours undefined.
Consequence : Tous les agents avec requiresApproval (deployer, incident, infra, patcher, strategist, gitlab-dev) ont leurs tool calls systematiquement REFUSES [agent-runner:371-375 "No decision handler, auto-denying"]. Les outils dockerRestart, gitPush, exec (pour patcher), gitlabCreateProject, gitlabTriggerPipeline ne peuvent JAMAIS etre executes par ces agents.
Cela signifie que :
dockerRestart ou gitPushdockerRestartGravite : Ces agents sont fondamentalement casses pour leurs operations critiques.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L251-397 (handler message)
Classification : BUG PROBABLE
Probleme : Si le client envoie 2 messages WS rapidement, handleWsMessage sera appele 2 fois en parallele (async). Les 2 messages vont :
director.handleMessage() en parallelesessionHistory en paralleledirector.activeBriefing peut etre lu/ecrit par les 2 handlers simultanementCas specifique briefing : Si un message est en cours de traitement (briefing actif) et un second message arrive, le check hasBriefing() [L310] peut etre vu comme true par les 2, mais un seul devrait traiter le briefing.
Solution : Ajouter un semaphore/lock par session, ou une file d'attente FIFO pour serialiser les messages d'une meme session.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/context-builder/index.ts
Lignes : L23-25, L79-119
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts
Lignes : L81-84
Classification : RISQUE THEORIQUE
Probleme : Le context builder a un budget de ~21000 chars (~6000 tokens). Mais ce budget ne concerne que le system prompt. Le messages array dans le runner [agent-runner:81] croit a chaque iteration : system prompt + user prompt + tool_calls + tool_results + assistant messages.
Avec 10 iterations et des tool results de 6000 chars chacun (apres compaction), l'array total peut atteindre 60000+ chars. Le num_ctx est fixe a 16384 tokens dans llmChat() [client.ts:324]. Si le contexte depasse cette limite, le LLM recevra un contexte tronque cote serveur (Ollama tronque silencieusement).
Le context builder gere bien la partie system prompt, mais ne controle pas la taille totale de la conversation du runner.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/auth.ts
Lignes : L74-78 (login), L98 (log avec userId)
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts
Lignes : L148 (API key dans header), L327 (log de chaque LLM call)
Classification : RISQUE THEORIQUE
Probleme :
msg.apiKey) en clair dans le JSON. Si le transport WS est compromis (meme en WSS, les logs serveur le capturent), la cle est exposee.ceo_profile.preferences.connector_api_key en clair dans Supabase [auth.ts:150, 185].Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts
Lignes : L220-233
Classification : RISQUE THEORIQUE
Probleme : editFile() utilise content.replace(oldString, newString) [L231] qui ne remplace que la PREMIERE occurrence. Si le LLM veut remplacer une string qui apparait plusieurs fois, seule la premiere sera modifiee, ce qui peut laisser le fichier dans un etat incoherent.
Ce n'est pas un bug de securite mais un comportement surprenant qui peut mener des agents a croire qu'un edit a ete fait alors qu'il est incomplet.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/pipelines/index.ts
Lignes : L123-223 (runSequential)
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/director/index.ts
Lignes : L280-289 (appel runSequential)
Classification : BUG CERTAIN
Probleme : Director.handleMessage() cree un AbortController [L273] et le passe a handleSingleStep() et handleMultiStep(). Mais quand un pipeline est utilise [L280-289], pipelineRunner.runSequential() est appele SANS signal. Et PipelineRunner.runSequential() [pipelines:181] appelle runner.run() SANS signal.
Consequence : Si l'utilisateur annule un objectif qui utilise un pipeline (create_frontend, verification_chain, etc.), le cancel REST [index.ts:500] va abort le controller, mais le pipeline runner ne recevra jamais le signal. L'agent continue a tourner jusqu'a completion naturelle ou timeout.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/briefing/index.ts
Lignes : L143-161
Classification : BUG PROBABLE
Probleme : Le code a L149 :
if (!currentQ.required || !skipWords.includes(answer.trim().toLowerCase())) {
session.answers.set(currentQ.id, answer);
}
La logique est inversee. Le || signifie : "enregistre la reponse SI la question n'est PAS required OU si le mot n'est PAS un skip word". Cela veut dire :
!true || !true -> false || false -> false -> la reponse N'EST PAS enregistree. C'est correct, la question required est skippee.MAIS le probleme est que le currentQuestionIndex est incrementee quand meme [L153]. Donc la question required est sautee sans reponse, et le briefing continue avec la question suivante. Une question required sans reponse ne bloque pas le briefing.
Impact : Un utilisateur peut repondre "passer" a toutes les questions, y compris les required, et le briefing generera un summary avec des champs manquants. L'enriched prompt sera incomplet.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/db/index.ts
Lignes : L516-547
Classification : RISQUE THEORIQUE
Probleme : searchConversations() construit un filtre PostgREST avec ilike.*${query}* [L519, L527]. Le parametre query vient directement du query string HTTP [index.ts:935] avec seulement un check de longueur minimale (2 chars).
Si query contient des caracteres speciaux PostgREST (%, _, *), ils seront interpretes comme des wildcards. Ce n'est pas de l'injection SQL a proprement parler (PostgREST parametrise les queries), mais un utilisateur pourrait forcer des patterns tres larges (% seul = retourne tout).
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/definitions.ts
Ligne : L683
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts
Ligne : L146
Classification : FAUX POSITIF
Observation : L'agent simplifier a un toolSet vide [] [definitions.ts:683]. Dans le runner [agent-runner:146], config.tools.length > 0 ? config.tools : undefined passe undefined au LLM, ce qui est correct (pas de tool_choice). L'agent simplifier est concu pour ne faire que du text processing.
pendingDecisions n'est jamais nettoyeeFichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L143, L401-408
Classification : RISQUE THEORIQUE
Probleme : pendingDecisions [L143] est une Map globale qui stocke les resolver functions pour les decisions CEO. Les entries sont ajoutees... quelque part (le code qui les ajoute n'est pas visible dans le codebase — le pendingDecisions n'est jamais set()). Les entries sont supprimees quand le CEO repond [L406].
Mais comme le code ne contient aucun pendingDecisions.set() dans les 16 fichiers audites, cette Map est en fait JAMAIS utilisee. C'est du dead code.
Le decision_response message type [L401] est gere mais ne peut jamais recevoir de decision valide car aucun decisionId n'est enregistre.
Cela confirme le FINDING-P2-11 : le systeme de decisions/approbations CEO est completement non-fonctionnel.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Lignes : L374-379
Classification : RISQUE THEORIQUE
Probleme : Le code fait GET + calcul + UPDATE pour incrementer message_count :
const conv = await db.getConversation(convId); // GET
const prevCount = conv?.message_count || 0;
await db.updateConversation(convId, {
message_count: prevCount + 2, // UPDATE
});
C'est un pattern read-modify-write sans transaction. En cas de messages concurrents (FINDING-P2-12), deux handlers pourraient lire le meme count et chacun ecrire +2, resultant en un count de +2 au lieu de +4.
Impact : Le message_count serait incorrect, ce qui affecterait l'auto-summary (trigger tous les 10 messages).
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts
Ligne : L16
Fichier : /stock_8to/33800-stack/projects/ulias-org/conf.prod.gouroubleu.yml
Ligne : L8
Classification : FAUX POSITIF (en contexte)
Observation : Le fallback http://192.168.1.12:5400 [index.ts:16] est different de la valeur en prod http://192.168.1.12:5403 [conf.yml:8]. Mais comme la variable d'env est toujours definie en prod, le fallback n'est jamais utilise. Cependant, si quelqu'un lance le serveur en dev sans definir la variable, il pointerait vers le mauvais port.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts
Ligne : L112
Classification : RISQUE THEORIQUE
Probleme : CALLBACK_BASE = process.env.CALLBACK_BASE_URL || 'http://192.168.1.12:5515'. Le callback URL est envoyee a l'AI orchestrator qui doit pouvoir atteindre ulias-org. Si ulias-org tourne sur un reseau different ou derriere un reverse proxy, le fallback hardcode pourrait ne pas etre accessible depuis l'orchestrator.
En pratique, les deux services sont sur le meme reseau Docker (prod-portainer), donc ca fonctionne.
Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/notifications/index.ts
Classification : FAUX POSITIF (design intent)
Observation : NotificationService est definie avec des methodes completes (objectiveCompleted, objectiveFailed, decisionNeeded, proactiveAlert), mais n'est importee ou utilisee nulle part dans index.ts ou director/index.ts. Le director envoie les notifications directement via les events WS et les fire-and-forget DB saves.
Ce fichier semble etre du code preparatoire pour une integration future, pas du dead code problematique.
| # | Finding | Impact | Complexite fix |
|---|---|---|---|
| P2-02 | WS close handler ne peut pas identifier la session | Resources orphelines, ws.send() sur connexion fermee | Moyenne |
| P2-11 | onDecision jamais branche | Agents avec requiresApproval completement casses | Faible |
| P2-16 | Pipeline runner ne passe pas AbortSignal | Cancel d'objectif pipeline impossible | Faible |
| # | Finding | Impact | Complexite fix |
|---|---|---|---|
| P2-01 | WS deconnecte laisse sessions orphelines | Jobs LLM inutiles, ws.send() crash | Moyenne |
| P2-03 | Circuit breaker global (cross-session) | Blocage inter-utilisateur | Faible |
| P2-06 | Hash cache fragile, collision possible | Reponse LLM incorrecte cachee | Faible |
| P2-07 | Traduction automatique peut corrompre les donnees | Output altere pour l'utilisateur | Faible |
| P2-08 | Prompt overrides globaux (cross-session) | Ecrasement overrides entre utilisateurs | Faible |
| P2-12 | Pas de lock sur messages concurrents | Race condition briefing/conversation | Moyenne |
| P2-17 | Briefing skip questions required | Briefing incomplet sans erreur | Faible |
| # | Finding | Impact |
|---|---|---|
| P2-04 | Cancel n'annule pas les jobs GPU | Ressources GPU gaspillees |
| P2-05 | Pending jobs pas nettoyes si crash | Memory leak theorique |
| P2-09 | Pas de validation ownership REST | Acces cross-user en multi-tenant |
| P2-10 | Injection commande SSH via LLM | Execution commandes arbitraires |
| P2-13 | Context overflow dans le runner | Troncature silencieuse par Ollama |
| P2-14 | Credentials en clair dans logs/DB | Exposition API key |
| P2-18 | Injection pattern dans search | Requetes DB trop larges |
| P2-21 | Race condition message_count | Counter incorrect |
P2-11 (onDecision) : Fix immediat. Sans ce fix, 6 agents sont fondamentalement casses. Il suffit de brancher le mecanisme de decision via WS (envoyer un event decision_request et attendre une reponse decision_response via pendingDecisions).
P2-02 + P2-01 (WS lifecycle) : Utiliser ws.data dans Elysia pour stocker l'apiKey lors du message auth, puis dans le close handler, nettoyer la session, invalider session.ws, et optionnellement cancel les objectifs en cours.
P2-16 (Pipeline abort) : Passer le signal au PipelineRunner.runSequential() et le propager a runner.run().
P2-12 (Concurrence) : Ajouter un flag processing ou une file par session pour serialiser les messages.
P2-07 (Traduction) : Ajouter un check heuristique (detecter si le texte est deja en francais avant d'appeler le LLM, ou exclure les outputs qui contiennent du JSON/code).
Fin de l'audit pass 2. 16 fichiers lus, 24 findings classes.