agentic-api 2.0.646 → 2.0.684

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,6 +59,127 @@ class AgentSimulator {
59
59
  });
60
60
  }
61
61
  // ============================================================================
62
+ // WORKER API : runJob(input) — exécution de mission autonome
63
+ // ============================================================================
64
+ /**
65
+ * Exécuter un WorkerJob — boucle Worker LLM ↔ Agent LLM
66
+ *
67
+ * Parallèle à testCase() mais pour des missions de production :
68
+ * - Le Worker LLM formule des questions pour l'Agent
69
+ * - L'Agent répond avec ses tools (RAG, DB, emails, calendar...)
70
+ * - Le Worker LLM évalue et continue ou termine avec [WORKER_COMPLETE]
71
+ *
72
+ * @param input - Paramètres du job (query, maxIterations, agentContext)
73
+ * @returns WorkerResult
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const worker = new AgentSimulator({
78
+ * agents: [...], start: 'PR-knowledge', verbose: false,
79
+ * mode: 'worker',
80
+ * instructionEx: 'Consulter tous les mails du jour et résumer les actions'
81
+ * });
82
+ *
83
+ * const result = await worker.runJob({
84
+ * query: 'Commence par lister les mails du jour',
85
+ * maxIterations: 10,
86
+ * agentContext: realContext
87
+ * });
88
+ * ```
89
+ */
90
+ async runJob(input) {
91
+ const startTime = Date.now();
92
+ const maxIterations = input.maxIterations ?? 10;
93
+ //
94
+ // Initialiser les contextes avec le contexte réel de l'agent
95
+ const context = await this.executor.initializeContexts({ goals: '', persona: '', result: '' }, input.agentContext);
96
+ const allMessages = [];
97
+ let exchangeCounter = 0;
98
+ try {
99
+ let currentUserQuery = input.query;
100
+ //
101
+ // Stocker le message initial
102
+ const initialMsg = { content: currentUserQuery, role: 'user' };
103
+ allMessages.push(initialMsg);
104
+ if (input.onMessage) {
105
+ input.onMessage(initialMsg);
106
+ }
107
+ //
108
+ // Boucle Worker — maxIterations échanges (parallèle à executeSimulation)
109
+ while (exchangeCounter < maxIterations) {
110
+ //
111
+ // Agent répond à la question
112
+ const agentResponse = await this.executor.executeAgent(context, currentUserQuery);
113
+ const agentMsg = { content: agentResponse, role: 'assistant' };
114
+ allMessages.push(agentMsg);
115
+ if (input.onMessage) {
116
+ input.onMessage(agentMsg);
117
+ }
118
+ exchangeCounter++;
119
+ //
120
+ // Worker LLM évalue la réponse de l'Agent
121
+ const workerResponse = await this.executor.executeSimulator(context, agentResponse);
122
+ //
123
+ // Vérifier [WORKER_COMPLETE]
124
+ if (this.isWorkerComplete(workerResponse)) {
125
+ const parsed = this.parseWorkerResult(workerResponse);
126
+ this.lastExecution = context.lastExecution;
127
+ return {
128
+ success: true,
129
+ deliverable: parsed.deliverable,
130
+ summary: parsed.summary,
131
+ confidence: parsed.confidence,
132
+ iterations: exchangeCounter,
133
+ maxIterations,
134
+ duration: Date.now() - startTime,
135
+ execution: context.lastExecution,
136
+ messages: allMessages
137
+ };
138
+ }
139
+ //
140
+ // Pas terminé — extraire la prochaine question pour l'Agent
141
+ currentUserQuery = this.extractConversationalPart(workerResponse);
142
+ if (currentUserQuery) {
143
+ const workerMsg = { content: currentUserQuery, role: 'user' };
144
+ allMessages.push(workerMsg);
145
+ if (input.onMessage) {
146
+ input.onMessage(workerMsg);
147
+ }
148
+ }
149
+ }
150
+ //
151
+ // Timeout — maxIterations atteint sans [WORKER_COMPLETE]
152
+ this.lastExecution = context.lastExecution;
153
+ return {
154
+ success: false,
155
+ deliverable: '',
156
+ summary: `Max iterations reached (${maxIterations})`,
157
+ confidence: 0,
158
+ iterations: exchangeCounter,
159
+ maxIterations,
160
+ duration: Date.now() - startTime,
161
+ execution: context.lastExecution,
162
+ messages: allMessages,
163
+ error: 'Max iterations reached without [WORKER_COMPLETE]'
164
+ };
165
+ }
166
+ catch (error) {
167
+ this.lastExecution = context.lastExecution;
168
+ return {
169
+ success: false,
170
+ deliverable: '',
171
+ summary: '',
172
+ confidence: 0,
173
+ iterations: exchangeCounter,
174
+ maxIterations,
175
+ duration: Date.now() - startTime,
176
+ execution: context.lastExecution,
177
+ messages: allMessages,
178
+ error: `Erreur d'exécution: ${error.message || error}`
179
+ };
180
+ }
181
+ }
182
+ // ============================================================================
62
183
  // ANCIENNE API : executeSimulation(options) - deprecated
63
184
  // ============================================================================
64
185
  /**
@@ -118,12 +239,18 @@ class AgentSimulator {
118
239
  const parsed = this.parseSimulationResult(simulatorResult, expectedFormat, context);
119
240
  this.lastExecution = context.lastExecution;
120
241
  // Validation des tools si expectedTool est fourni
121
- if (options.expectedTool) {
242
+ if (options.expectedTool && Object.keys(options.expectedTool).length > 0) {
122
243
  const validation = this.validateExpectedTools(options.expectedTool);
244
+ //
245
+ // Toujours définir toolValidation pour inspection par les tests
246
+ parsed.toolValidation = validation;
123
247
  if (!validation.passed) {
124
248
  parsed.success = false;
125
- parsed.error = validation.errors.join('; ');
126
- parsed.toolValidation = validation;
249
+ //
250
+ // Préserver l'erreur LLM si présente, ajouter l'erreur de validation
251
+ const llmError = parsed.error || '';
252
+ const toolError = validation.errors.join('; ');
253
+ parsed.error = llmError ? `${llmError} | Tool validation: ${toolError}` : toolError;
127
254
  }
128
255
  }
129
256
  // Ajouter l'historique des messages au résultat
@@ -149,12 +276,16 @@ class AgentSimulator {
149
276
  const timeout = await this.generateTimeoutReport(context, expectedFormat);
150
277
  this.lastExecution = context.lastExecution;
151
278
  // Validation des tools même en cas de timeout
152
- if (options.expectedTool) {
279
+ if (options.expectedTool && Object.keys(options.expectedTool).length > 0) {
153
280
  const validation = this.validateExpectedTools(options.expectedTool);
281
+ //
282
+ // Toujours définir toolValidation pour inspection par les tests
283
+ timeout.toolValidation = validation;
154
284
  if (!validation.passed) {
155
285
  timeout.success = false;
156
- timeout.error = validation.errors.join('; ');
157
- timeout.toolValidation = validation;
286
+ const llmError = timeout.error || '';
287
+ const toolError = validation.errors.join('; ');
288
+ timeout.error = llmError ? `${llmError} | Tool validation: ${toolError}` : toolError;
158
289
  }
159
290
  }
160
291
  return { ...timeout, exchangeCount: exchangeCounter, messages: allMessages }; // Utiliser notre compteur
@@ -222,6 +353,29 @@ class AgentSimulator {
222
353
  isSimulationComplete(response) {
223
354
  return response.includes('[DONE]') || response.includes('[SIMULATION_COMPLETE]') || response.includes('[TERMINE]');
224
355
  }
356
+ isWorkerComplete(response) {
357
+ return response.includes('[WORKER_COMPLETE]');
358
+ }
359
+ parseWorkerResult(response) {
360
+ const match = response.match(/\[WORKER_COMPLETE\]\s*\n?([\s\S]*)/);
361
+ if (match) {
362
+ const content = match[1].trim();
363
+ try {
364
+ const parsed = JSON.parse(content);
365
+ return {
366
+ deliverable: parsed.deliverable || content,
367
+ summary: parsed.summary || '',
368
+ confidence: parsed.confidence ?? 0.5
369
+ };
370
+ }
371
+ catch {
372
+ //
373
+ // Fallback : contenu brut comme deliverable
374
+ return { deliverable: content, summary: '', confidence: 0.5 };
375
+ }
376
+ }
377
+ return { deliverable: '', summary: '', confidence: 0 };
378
+ }
225
379
  parseSimulationResult(response, expectedFormat, context) {
226
380
  // Le simulateur produit directement le résultat et rapport
227
381
  // Parsing pour détecter la fin et extraire le contenu JSON
@@ -286,7 +440,7 @@ class AgentSimulator {
286
440
  extractConversationalPart(response) {
287
441
  // Extraire la partie conversationnelle avant les tags d'évaluation ou d'observation
288
442
  // Filtrer tous les tags système : [DONE], [OBSERVATEUR SILENCIEUX], [À NOTER], etc.
289
- const tagIndex = response.search(/\[(DONE|SIMULATION_COMPLETE|TERMINE|BUG_|OBSERVATEUR|À NOTER|NOTE|ANALYSE)/i);
443
+ const tagIndex = response.search(/\[(DONE|SIMULATION_COMPLETE|TERMINE|WORKER_COMPLETE|BUG_|OBSERVATEUR|À NOTER|NOTE|ANALYSE)/i);
290
444
  if (tagIndex !== -1) {
291
445
  return response.substring(0, tagIndex).trim();
292
446
  }
@@ -16,3 +16,21 @@ export declare function GENERIC_SIMULATOR_PROMPT(scenario: SimulationScenario, i
16
16
  export declare const PERSONA_PATIENT = "Utilisateur patient et poli qui prend le temps d'expliquer sa situation";
17
17
  export declare const PERSONA_PRESSE = "Utilisateur press\u00E9 qui veut une solution rapide, r\u00E9pond bri\u00E8vement";
18
18
  export declare const PERSONA_ENERVE = "Utilisateur \u00E9nerv\u00E9 et frustr\u00E9, c'est son 3\u00E8me appel pour le m\u00EAme probl\u00E8me, ton direct et impatient";
19
+ /**
20
+ * Persona collaborateur professionnel et méthodique
21
+ */
22
+ export declare const PERSONA_EMPLOYEE = "Collaborateur professionnel et m\u00E9thodique, tu formules des demandes claires et pr\u00E9cises, tu analyses les r\u00E9ponses de mani\u00E8re critique, et tu identifies les lacunes pour poser des questions de suivi.";
23
+ /**
24
+ * Prompt interne fixe du WorkerJob — system prompt pour le Worker LLM
25
+ *
26
+ * Parallèle à GENERIC_SIMULATOR_PROMPT : même pattern, différent rôle.
27
+ * - GENERIC_SIMULATOR_PROMPT → testeur automatisé, détecte [DONE]
28
+ * - WORKER_INTERNAL_PROMPT → collaborateur, détecte [WORKER_COMPLETE]
29
+ *
30
+ * Le brief est injecté une fois dans les instructions système.
31
+ * La mémoire est gérée par l'historique de conversation (comme le simulateur).
32
+ *
33
+ * @param brief - Le cahier des charges du worker (= instructionEx de SimulatorConfig)
34
+ * @param instructionEx - Instructions additionnelles (optionnel)
35
+ */
36
+ export declare function WORKER_INTERNAL_PROMPT(brief: string, instructionEx?: string): string;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PERSONA_ENERVE = exports.PERSONA_PRESSE = exports.PERSONA_PATIENT = void 0;
3
+ exports.PERSONA_EMPLOYEE = exports.PERSONA_ENERVE = exports.PERSONA_PRESSE = exports.PERSONA_PATIENT = void 0;
4
4
  exports.GENERIC_SIMULATOR_PROMPT = GENERIC_SIMULATOR_PROMPT;
5
+ exports.WORKER_INTERNAL_PROMPT = WORKER_INTERNAL_PROMPT;
5
6
  /**
6
7
  * Génère le prompt du simulateur générique avec le scenario intégré
7
8
  *
@@ -56,9 +57,16 @@ Exemple de conversation:
56
57
  Tu retournes le trigger de fin: \`[DONE] {"success": true, "explain": "..."}\` ou \`[DONE] {"success": false, "error": "..."}\`.
57
58
 
58
59
  ### CONTEXT (\`<agent-context>\` invisible pour le CLIENT)
59
- - Le tag \`<agent-context>\` contient des données techniques de l'agent testé (outils appelés, nombre d'échanges, etc).
60
- - Ces informations sont **strictement réservées à toi l'observateur.
61
- **AUTORISÉ** : les exploiter pour valider les objectifs ou décider de la fin du test.
60
+ - Le tag \`<agent-context>\` contient les **APPELS RÉELS** des outils effectués par l'agent testé.
61
+ - Le champ \`tools\` liste les **noms exacts des outils appelés** pendant cet échange.
62
+ - **CRITIQUE**: Si un outil apparaît dans \`<agent-context>.tools\`, c'est qu'il a **réellement été exécuté** par l'agent.
63
+ - Ces informations sont **strictement réservées à toi l'observateur**.
64
+ - **AUTORISÉ** : les exploiter pour valider les objectifs ou décider de la fin du test.
65
+
66
+ Exemple avec validation d'outil:
67
+ > Agent répond avec une information
68
+ > \`<agent-context>{"tools": ["lookupKnowledge"], "exchangeCount": 1}</agent-context>\`
69
+ > → L'outil \`lookupKnowledge\` a été **réellement appelé** par l'agent ✅
62
70
 
63
71
  Exemple avec sortie pour <simulation_goals>CONDITION DE FIN: L'agent demande si l'utilisateur souhaite chercher sur internet</simulation_goals>:
64
72
  > Toi : "Quelle est la température du lac à Genève ?"
@@ -86,3 +94,72 @@ ${instructionEx ? `\n\n${instructionEx}` : ''}`;
86
94
  exports.PERSONA_PATIENT = 'Utilisateur patient et poli qui prend le temps d\'expliquer sa situation';
87
95
  exports.PERSONA_PRESSE = 'Utilisateur pressé qui veut une solution rapide, répond brièvement';
88
96
  exports.PERSONA_ENERVE = 'Utilisateur énervé et frustré, c\'est son 3ème appel pour le même problème, ton direct et impatient';
97
+ // ============================================================================
98
+ // WORKER PROMPT — Prompt interne fixe pour WorkerJob
99
+ // ============================================================================
100
+ /**
101
+ * Persona collaborateur professionnel et méthodique
102
+ */
103
+ exports.PERSONA_EMPLOYEE = 'Collaborateur professionnel et méthodique, tu formules des demandes claires et précises, tu analyses les réponses de manière critique, et tu identifies les lacunes pour poser des questions de suivi.';
104
+ /**
105
+ * Prompt interne fixe du WorkerJob — system prompt pour le Worker LLM
106
+ *
107
+ * Parallèle à GENERIC_SIMULATOR_PROMPT : même pattern, différent rôle.
108
+ * - GENERIC_SIMULATOR_PROMPT → testeur automatisé, détecte [DONE]
109
+ * - WORKER_INTERNAL_PROMPT → collaborateur, détecte [WORKER_COMPLETE]
110
+ *
111
+ * Le brief est injecté une fois dans les instructions système.
112
+ * La mémoire est gérée par l'historique de conversation (comme le simulateur).
113
+ *
114
+ * @param brief - Le cahier des charges du worker (= instructionEx de SimulatorConfig)
115
+ * @param instructionEx - Instructions additionnelles (optionnel)
116
+ */
117
+ function WORKER_INTERNAL_PROMPT(brief, instructionEx) {
118
+ return `# IDENTITÉ
119
+ Tu es un **COLLABORATEUR** chargé d'une mission. Tu travailles de manière autonome en formulant des demandes précises à un Agent qui dispose de tous les outils nécessaires (recherche, bases de données, emails, calendrier, documents...).
120
+
121
+ # MISSION
122
+ Tu dois accomplir le cahier des charges décrit dans <worker_brief> en utilisant l'Agent comme exécutant.
123
+ À chaque échange, tu formules UNE question ou instruction précise pour l'Agent.
124
+ Tu analyses sa réponse, accumules les informations utiles, et décides de ta prochaine action.
125
+
126
+ # MÉTHODE
127
+ 1. **Analyser** le brief et les réponses précédentes
128
+ 2. **Identifier** la prochaine information nécessaire ou action à réaliser
129
+ 3. **Formuler** une question/instruction claire et précise pour l'Agent
130
+ 4. **Évaluer** la réponse : est-ce suffisant pour atteindre l'objectif ?
131
+
132
+ # RÈGLES
133
+ - Formule UNE seule question/instruction par échange
134
+ - Sois précis et contextuel dans tes demandes
135
+ - N'invente pas d'information — base-toi uniquement sur les réponses de l'Agent
136
+ - Si l'Agent ne peut pas répondre, reformule ou change d'approche
137
+ - Accumule les faits clés de chaque réponse pour les réutiliser
138
+
139
+ # TRIGGERS DE SORTIE
140
+ Quand tu estimes que l'objectif du brief est atteint, ou que tu as accumulé suffisamment d'information pour produire le livrable, termine avec :
141
+ \`[WORKER_COMPLETE]\`
142
+ Suivi immédiatement du JSON de résultat (voir format ci-dessous).
143
+
144
+ Si tu n'as pas terminé, formule simplement ta prochaine question/instruction pour l'Agent.
145
+
146
+ # FORMAT DE SORTIE (uniquement quand terminé)
147
+ \`\`\`
148
+ [WORKER_COMPLETE]
149
+ {"deliverable": "...", "summary": "...", "confidence": 0.95}
150
+ \`\`\`
151
+
152
+ Où :
153
+ - \`deliverable\` : le résultat complet de la mission (texte, données, rapport...)
154
+ - \`summary\` : résumé court de ce qui a été accompli
155
+ - \`confidence\` : niveau de confiance (0.0 à 1.0) que l'objectif est atteint
156
+
157
+ ---
158
+
159
+ <worker_brief>
160
+ ${brief}
161
+ </worker_brief>
162
+
163
+ **CRITICAL**: Si tu termines, le contenu après [WORKER_COMPLETE] doit être du JSON valide uniquement.
164
+ ${instructionEx ? `\n\n${instructionEx}` : ''}`;
165
+ }
@@ -1,10 +1,15 @@
1
1
  import { AgentMessage } from "../stategraph";
2
2
  import { AgentConfig, AgenticContext, ExecutionResult } from "../types";
3
3
  import { RAGManagerConfig } from "../rag";
4
+ export declare const SIMULATOR_AGENT_NAME = "simple-simulator";
5
+ export declare const SIMULATOR_AGENT_DESCRIPTION = "Simulateur simple pour tests - TOUJOURS retourner JSON valide";
6
+ export declare const WORKER_AGENT_NAME = "worker-agent";
7
+ export declare const WORKER_AGENT_DESCRIPTION = "Worker collaborateur pour missions autonomes";
4
8
  export interface SimulatorConfig {
5
9
  agents: AgentConfig[];
6
10
  start: string;
7
11
  verbose: boolean;
12
+ mode?: 'simulator' | 'worker';
8
13
  instructionEx?: string;
9
14
  mockCacheInitializer?: (sessionId: string) => Promise<void>;
10
15
  ragConfig?: RAGManagerConfig;
@@ -38,6 +43,30 @@ export interface TestCaseInput {
38
43
  expectedTools?: Record<string, ToolConstraint>;
39
44
  onMessage?: (message: AgentMessage) => void;
40
45
  }
46
+ /**
47
+ * Input pour worker.runJob() — parallèle à TestCaseInput
48
+ */
49
+ export interface WorkerJobInput {
50
+ query: string;
51
+ maxIterations?: number;
52
+ agentContext: AgenticContext;
53
+ onMessage?: (message: AgentMessage) => void;
54
+ }
55
+ /**
56
+ * Résultat d'un WorkerJob — parallèle à SimulationResult
57
+ */
58
+ export interface WorkerResult {
59
+ success: boolean;
60
+ deliverable: string;
61
+ summary: string;
62
+ confidence: number;
63
+ iterations: number;
64
+ maxIterations: number;
65
+ duration: number;
66
+ execution: ExecutionResult;
67
+ messages: AgentMessage[];
68
+ error?: string;
69
+ }
41
70
  /**
42
71
  * @deprecated Utiliser TestScenario à la place
43
72
  */
@@ -1,2 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WORKER_AGENT_DESCRIPTION = exports.WORKER_AGENT_NAME = exports.SIMULATOR_AGENT_DESCRIPTION = exports.SIMULATOR_AGENT_NAME = void 0;
4
+ exports.SIMULATOR_AGENT_NAME = 'simple-simulator';
5
+ exports.SIMULATOR_AGENT_DESCRIPTION = 'Simulateur simple pour tests - TOUJOURS retourner JSON valide';
6
+ exports.WORKER_AGENT_NAME = 'worker-agent';
7
+ exports.WORKER_AGENT_DESCRIPTION = 'Worker collaborateur pour missions autonomes';
@@ -45,7 +45,7 @@ function accumulateUsageTokens(stateGraph, discussion, agentName, model, usage)
45
45
  function stepsToActions(stateGraph, agentName) {
46
46
  return stateGraph.steps(agentName).map(step => ({
47
47
  action: step.tool,
48
- content: step.context,
48
+ content: step.reason,
49
49
  feedback: step.reason
50
50
  }));
51
51
  }
@@ -196,7 +196,7 @@ async function readCompletionsStream(params) {
196
196
  const transferredAgent = agents.find(a => a.name === transferAgentName) || agentConfig;
197
197
  const instructions = transferredAgent.instructions;
198
198
  const enrichedInstructions = (await params.enrichWithMemory?.("system", transferredAgent, context)) || '';
199
- // ✅ Set préserve le trail existant via updateSystemMessage()
199
+ // ✅ Set préserve le trail existant via updateSystemContextTrail()
200
200
  stateGraph.set(discussionRootAgent, instructions + '\n' + enrichedInstructions);
201
201
  }
202
202
  //
@@ -236,12 +236,11 @@ async function readCompletionsStream(params) {
236
236
  for (let i = 0; i < results.length; i++) {
237
237
  const result = results[i];
238
238
  const toolCall = parsedCalls[i];
239
+ const reason = result.context ? `${result.context} (${toolCall.args.justification})` : toolCall.args.justification;
239
240
  if (result.name) {
240
241
  stateGraph.addStep(discussionRootAgent, {
241
242
  tool: result.name,
242
- context: result.context || '',
243
- reason: toolCall.args.justification || '',
244
- id: result.id
243
+ reason
245
244
  });
246
245
  }
247
246
  }
@@ -355,7 +354,7 @@ async function executeAgentSet(agentSet, context, params) {
355
354
  else if (enrichedInstructions) {
356
355
  //
357
356
  // Existing discussion: only update system message if enrichedInstructions exists
358
- // stateGraph.set() preserves context-trail via updateSystemMessage()
357
+ // stateGraph.set() preserves context-trail via updateSystemContextTrail()
359
358
  const instructions = currentAgentConfig.instructions + '\n' + enrichedInstructions;
360
359
  stateGraph.set(discussionRootAgent, instructions);
361
360
  }
@@ -34,6 +34,9 @@ class RAGManager {
34
34
  if (!config.baseDir || !(0, fs_1.existsSync)(config.baseDir)) {
35
35
  throw new Error(`Le dossier du registre RAG n'existe pas: ${config.baseDir}`);
36
36
  }
37
+ if (!config.defaultName || config.defaultName.trim() === '') {
38
+ throw new Error('RAGManagerConfig.defaultName est obligatoire et ne peut pas être vide');
39
+ }
37
40
  this.config = {
38
41
  tempDir: path_1.default.join(config.baseDir, 'temp'),
39
42
  archiveDir: path_1.default.join(config.baseDir, 'archives'),
@@ -49,7 +52,10 @@ class RAGManager {
49
52
  // store instance in memory
50
53
  DEFAULT_RAG_INSTANCE[this.config.baseDir] = this;
51
54
  const registry = this.getRegistry();
52
- const defaultName = registry.defaultName || 'RAGRegistry';
55
+ const defaultName = registry.defaultName;
56
+ if (!defaultName || defaultName.trim() === '') {
57
+ throw new Error('registry.json invalide: defaultName manquant');
58
+ }
53
59
  DEFAULT_RAG_INSTANCE[defaultName] = this;
54
60
  }
55
61
  static get(config) {
@@ -101,7 +107,7 @@ class RAGManager {
101
107
  registry = {
102
108
  version: '1.0',
103
109
  registries: {},
104
- defaultName: 'RAGRegistry'
110
+ defaultName: this.config.defaultName
105
111
  };
106
112
  }
107
113
  }
@@ -109,12 +115,12 @@ class RAGManager {
109
115
  registry = {
110
116
  version: '1.0',
111
117
  registries: {},
112
- defaultName: 'RAGRegistry'
118
+ defaultName: this.config.defaultName
113
119
  };
114
120
  }
115
- // Définir defaultName par défaut s'il n'est pas défini
121
+ // Fail-fast: defaultName doit toujours être défini explicitement
116
122
  if (!registry.defaultName) {
117
- registry.defaultName = 'RAGRegistry';
123
+ throw new Error('registry.json invalide: defaultName manquant. Fournir defaultName dans la config RAG.');
118
124
  }
119
125
  // Scanner les dossiers pour découvrir les RAG existants
120
126
  this.scanDirectoriesForRAGs(registry);
@@ -732,8 +738,15 @@ class RAGManager {
732
738
  }
733
739
  //
734
740
  // Charger le metadata pour obtenir la liste des documents
741
+ // Ignorer les dossiers temporaires (temp/) sans metadata
735
742
  const metadataFile = path_1.default.join(entry.configPath, types_1.RAG_FILES.METADATA);
736
743
  if (!(0, fs_1.existsSync)(metadataFile)) {
744
+ //
745
+ // Si c'est un dossier temporaire (temp/rule-validation-*), retourner vide au lieu d'erreur
746
+ if (entry.configPath.includes('/temp/') || entry.status === 'building') {
747
+ console.warn(`⚠️ Dossier temporaire sans metadata ignoré: ${entry.configPath}`);
748
+ return { documents: {}, totalQueries: 0 };
749
+ }
737
750
  throw new Error(`Fichier metadata manquant: ${metadataFile}`);
738
751
  }
739
752
  let metadata;
@@ -990,7 +1003,7 @@ class RAGManager {
990
1003
  // Changer le défaut si nécessaire
991
1004
  if (registry.defaultName === name) {
992
1005
  const remainingRAGs = Object.keys(registry.registries);
993
- registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : 'RAGRegistry';
1006
+ registry.defaultName = remainingRAGs.length > 0 ? remainingRAGs[0] : this.config.defaultName;
994
1007
  }
995
1008
  this.saveRegistry(registry);
996
1009
  console.log(`✅ RAG supprimé: ${name}`);
@@ -1420,7 +1433,10 @@ class RAGManager {
1420
1433
  */
1421
1434
  getDefault() {
1422
1435
  const registry = this.getRegistry();
1423
- return registry.defaultName || 'RAGRegistry';
1436
+ if (!registry.defaultName || registry.defaultName.trim() === '') {
1437
+ throw new Error('registry.json invalide: defaultName manquant');
1438
+ }
1439
+ return registry.defaultName;
1424
1440
  }
1425
1441
  /**
1426
1442
  * Obtient les statistiques de tous les RAG
@@ -278,13 +278,15 @@ export interface RAGRegistry {
278
278
  version: string;
279
279
  /** Liste des RAG disponibles */
280
280
  registries: Record<string, RAGRegistryEntry>;
281
- /** RAG par défaut (par défaut: 'RAGRegistry') */
281
+ /** RAG par défaut (obligatoire) */
282
282
  defaultName?: string;
283
283
  }
284
284
  /** Configuration du gestionnaire RAG */
285
285
  export interface RAGManagerConfig {
286
286
  /** Répertoire de base du registre */
287
287
  baseDir: string;
288
+ /** Nom du RAG par défaut (obligatoire) */
289
+ defaultName?: string;
288
290
  /** Répertoire temporaire pour les constructions */
289
291
  tempDir?: string;
290
292
  /** Répertoire d'archives */
@@ -156,14 +156,14 @@ Temporary repository for E2E testing.`;
156
156
  return (0, index_1.gitCheckConfiguration)(this.git);
157
157
  }
158
158
  async isPRClosed(branch) {
159
- return (0, index_1.gitIsPRClosed)(this.git, branch);
159
+ return (0, index_1.gitIsPRClosedRobust)(this.git, branch, this.config);
160
160
  }
161
161
  async isPRClosedRobust(branch, config) {
162
162
  return (0, index_1.gitIsPRClosedRobust)(this.git, branch, config || this.config);
163
163
  }
164
164
  async closePR(branch, author, message) {
165
165
  console.log(`[DEBUG][E2EHelper.closePR] Closing PR: ${branch} by ${author.name}`);
166
- return (0, index_1.gitClosePR)(this.git, branch, author, message);
166
+ return (0, index_1.gitClosePRRobust)(this.git, branch, author, message || `Closed by ${author.name}`, this.config);
167
167
  }
168
168
  async closePRRobust(branch, closedBy, message, config) {
169
169
  return (0, index_1.gitClosePRRobust)(this.git, branch, closedBy, message, config || this.config);
@@ -174,7 +174,47 @@ Temporary repository for E2E testing.`;
174
174
  return (0, index_1.gitGetAllPR)(this.git, options);
175
175
  }
176
176
  async getNextPRNumber(validationPrefix) {
177
- return (0, index_1.gitGetNextPRNumber)(this.git, validationPrefix);
177
+ const initialPRCounter = 60;
178
+ const registryPath = (0, path_1.join)(this.config.repoPath, '.git', 'with-ids.json');
179
+ const now = new Date().toISOString();
180
+ const allBranches = await this.getAllBranches();
181
+ const numbers = allBranches
182
+ .filter((branch) => branch.startsWith(validationPrefix))
183
+ .map((branch) => parseInt(branch.substring(validationPrefix.length).split('-')[0], 10))
184
+ .filter((num) => !isNaN(num));
185
+ const maxBranchNumber = numbers.length > 0 ? Math.max(...numbers) : initialPRCounter;
186
+ let registry = {
187
+ last: 980,
188
+ lastPR: initialPRCounter,
189
+ used: [],
190
+ updated: now,
191
+ matters: {}
192
+ };
193
+ if ((0, fs_1.existsSync)(registryPath)) {
194
+ try {
195
+ registry = JSON.parse(await fs_1.promises.readFile(registryPath, 'utf8'));
196
+ }
197
+ catch {
198
+ // Rebuild minimal registry fallback
199
+ }
200
+ }
201
+ if (!Number.isInteger(registry.lastPR)) {
202
+ registry.lastPR = initialPRCounter;
203
+ }
204
+ if (!Number.isInteger(registry.last)) {
205
+ registry.last = 980;
206
+ }
207
+ if (!Array.isArray(registry.used)) {
208
+ registry.used = [];
209
+ }
210
+ if (!registry.matters || typeof registry.matters !== 'object') {
211
+ registry.matters = {};
212
+ }
213
+ const nextPR = Math.max(registry.lastPR, maxBranchNumber, initialPRCounter) + 1;
214
+ registry.lastPR = nextPR;
215
+ registry.updated = now;
216
+ await fs_1.promises.writeFile(registryPath, JSON.stringify(registry, null, 2), 'utf8');
217
+ return nextPR;
178
218
  }
179
219
  async newPR(files, description, author, options) {
180
220
  //console.log(`[DEBUG][E2EHelper.newPR] Creating new PR by ${author.name} for files: ${files.join(', ')}`);
@@ -220,7 +260,11 @@ Temporary repository for E2E testing.`;
220
260
  async editFile(filePath, content, branch) {
221
261
  const user = { name: 'E2E Test', email: 'e2e@test.com' };
222
262
  const targetBranch = branch || this.config.draftBranch;
223
- return (0, index_1.gitEditFile)(this.git, filePath, targetBranch, content, user, this.config);
263
+ const exists = await (0, index_1.gitFileExistsInBranch)(this.git, filePath, targetBranch);
264
+ if (!exists) {
265
+ throw new Error(`File "${filePath}" not found in branch "${targetBranch}"`);
266
+ }
267
+ return (0, index_1.gitCreateOrEditFile)(this.git, filePath, targetBranch, content, user, this.config);
224
268
  }
225
269
  async validateRepositoryState() {
226
270
  const errors = [];
@@ -608,10 +608,12 @@ class GitHealthManager {
608
608
  // Parser le matter complet
609
609
  const parsed = (0, utils_matter_1.matterParse)(fileData.content);
610
610
  const { matter: fullMatter, content } = parsed;
611
- // ✅ CORRECTION: Utiliser gitEnsureMatterID() pour réutiliser l'ID existant du fichier
612
- // Si le fichier existe déjà dans une autre branche, son ID sera réutilisé
613
- // Sinon, un nouvel ID sera généré automatiquement
614
- const matterWithID = (0, git_1.gitEnsureMatterID)(fullMatter, this.config, branchName, file);
611
+ // ✅ CORRECTION: En cas de doublon détecté, éviter la réutilisation via cache fichier
612
+ // pour forcer une réallocation d'ID. Sinon, garder la logique d'identité stable.
613
+ const isDuplicateIssue = report.duplicateID.some((entry) => entry.startsWith(fileRef));
614
+ const matterWithID = isDuplicateIssue
615
+ ? (0, git_1.gitEnsureMatterID)(fullMatter, this.config)
616
+ : (0, git_1.gitEnsureMatterID)(fullMatter, this.config, branchName, file);
615
617
  if (matterWithID.id !== fullMatter.id) {
616
618
  if (fullMatter.id && fullMatter.id > 999) {
617
619
  console.log(` 🔧 ${file}: ID ${fullMatter.id} remplacé par ID existant ${matterWithID.id}`);
@@ -1,6 +1,6 @@
1
1
  export { lock, unlock, gitLoad, isValidInt, gitIsFileMerged, gitLastCommit, gitListFilesInBranch, gitListFilesOutsideRepo, gitFileExistsInBranch, gitGetFilesSummary, gitGetFileContent, gitGetFilePreview, gitGetFileHistory, gitReadFileOutsideRepo, gitGetUnmergedBranchesForFile, gitGetAllBranches, gitGetDiffFiles, gitReadNote, gitWriteNote, gitDeleteNote, } from './repo.tools';
2
- export { gitInit, gitEnsureRepositoryConfiguration, gitEnsureRemoteConfiguration, gitSetupRepository, gitShowConfiguration, gitCheckConfiguration, gitGetBranchHealth, gitGetValidationBranchHealth, gitCreateOrEditFile, gitEditFile, gitRenameFile, gitDeleteFile, gitGenerateNextID, gitReloadIDRegistry, gitRegisterExistingID, gitEnsureMatterID, gitFileStrictMatter, gitIDRegistryExists, gitIDRegistryRename, } from './repo';
3
- export { gitSyncPR, gitIsPRClosed, gitIsPRClosedRobust, gitGetPRMetadata, gitGetAllPR, gitGetClosedPRs, gitLoadPR, gitPRUpdateComments, gitClosePR, gitClosePRRobust, gitGetNextPRNumber, gitNewValidationRequest, gitNewPR, } from './repo.pr';
2
+ export { gitInit, gitEnsureRepositoryConfiguration, gitEnsureRemoteConfiguration, gitSetupRepository, gitShowConfiguration, gitCheckConfiguration, gitGetBranchHealth, gitGetValidationBranchHealth, gitCreateOrEditFile, gitEditFile, gitRenameFile, gitDeleteFile, gitGenerateNextID, gitAllocateNextPRNumber, gitReloadIDRegistry, gitRegisterExistingID, gitEnsureMatterID, gitFileStrictMatter, gitIDRegistryExists, gitIDRegistryRename, } from './repo';
3
+ export { gitSyncPR, gitIsPRClosed, gitIsPRClosedRobust, gitGetPRMetadata, gitGetAllPR, gitGetClosedPRs, gitLoadPR, gitPRUpdateComments, gitClosePR, gitClosePRRobust, gitNewValidationRequest, gitNewPR, } from './repo.pr';
4
4
  export * from './git.e2e.helper';
5
5
  export * from './git.health';
6
6
  export * from './git.helper';