palabre 0.1.7 → 0.2.0
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.
- package/README.md +6 -4
- package/dist/index.js +235 -5
- package/dist/renderers/console.js +62 -5
- package/dist/renderers/ndjson.js +116 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
<a href="https://palab.re"><img src="https://img.shields.io/badge/docs-palab.re-18181B?logo=netlify&logoColor=7C3AED" alt="Documentation"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
+

|
|
11
|
+
|
|
10
12
|
[Français](#français) | [English](#english)
|
|
11
13
|
|
|
12
14
|
## Français
|
|
@@ -20,7 +22,7 @@ Il ne remplace pas vos outils : il les pilote. Vous gardez vos abonnements, vos
|
|
|
20
22
|
- https://palab.re
|
|
21
23
|
- https://palabre.netlify.app
|
|
22
24
|
|
|
23
|
-
Pages utiles : [Installation](https://palab.re/get-started/installation), [Configuration](https://palab.re/get-started/configuration), [Premier débat](https://palab.re/get-started/first-debate), [Référence CLI](https://palab.re/reference/cli), [Dépannage](https://palab.re/troubleshooting), [Roadmap](https://palab.re/roadmap).
|
|
25
|
+
Pages utiles : [Installation](https://palab.re/fr/get-started/installation), [Configuration](https://palab.re/fr/get-started/configuration), [Premier débat](https://palab.re/fr/get-started/first-debate), [Référence CLI](https://palab.re/fr/reference/cli), [Dépannage](https://palab.re/fr/troubleshooting), [Roadmap](https://palab.re/fr/roadmap).
|
|
24
26
|
|
|
25
27
|
### Installation
|
|
26
28
|
|
|
@@ -77,7 +79,7 @@ palabre --version
|
|
|
77
79
|
|
|
78
80
|
Commandes utiles : `pnpm check`, `pnpm test`, `pnpm build`.
|
|
79
81
|
|
|
80
|
-
Roadmap
|
|
82
|
+
Roadmap publique : [docs/guide/fr/roadmap.md](./docs/guide/fr/roadmap.md). Guide agents/contributeurs : [AGENTS.md](./AGENTS.md).
|
|
81
83
|
|
|
82
84
|
### Licence
|
|
83
85
|
|
|
@@ -94,7 +96,7 @@ It does not replace your tools: it drives them. You keep your subscriptions, def
|
|
|
94
96
|
- https://palab.re
|
|
95
97
|
- https://palabre.netlify.app
|
|
96
98
|
|
|
97
|
-
Useful pages: [Installation](https://palab.re/get-started/installation), [Configuration](https://palab.re/get-started/configuration), [First debate](https://palab.re/get-started/first-debate), [CLI reference](https://palab.re/reference/cli), [Troubleshooting](https://palab.re/troubleshooting), [Roadmap](https://palab.re/roadmap).
|
|
99
|
+
Useful pages: [Installation](https://palab.re/fr/get-started/installation), [Configuration](https://palab.re/fr/get-started/configuration), [First debate](https://palab.re/fr/get-started/first-debate), [CLI reference](https://palab.re/fr/reference/cli), [Troubleshooting](https://palab.re/fr/troubleshooting), [Roadmap](https://palab.re/fr/roadmap).
|
|
98
100
|
|
|
99
101
|
### Installation
|
|
100
102
|
|
|
@@ -151,7 +153,7 @@ palabre --version
|
|
|
151
153
|
|
|
152
154
|
Useful commands: `pnpm check`, `pnpm test`, `pnpm build`.
|
|
153
155
|
|
|
154
|
-
|
|
156
|
+
Public roadmap: [docs/guide/fr/roadmap.md](./docs/guide/fr/roadmap.md). Agent/contributor guide: [AGENTS.md](./AGENTS.md).
|
|
155
157
|
|
|
156
158
|
### License
|
|
157
159
|
|
package/dist/index.js
CHANGED
|
@@ -13,10 +13,12 @@ import { formatAgentPrompt } from "./prompt.js";
|
|
|
13
13
|
import { runNewWizard } from "./new.js";
|
|
14
14
|
import { listPresetNames, resolvePreset } from "./presets.js";
|
|
15
15
|
import { createConsoleRenderer } from "./renderers/console.js";
|
|
16
|
+
import { createNdjsonRenderer } from "./renderers/ndjson.js";
|
|
16
17
|
import { runDebate } from "./orchestrator.js";
|
|
17
18
|
import { writeDebateMarkdown } from "./output.js";
|
|
18
19
|
import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
|
|
19
20
|
import { createSessionContext } from "./session.js";
|
|
21
|
+
/** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
|
|
20
22
|
async function main() {
|
|
21
23
|
const parsed = parseArgs(process.argv.slice(2));
|
|
22
24
|
if (parsed.command === "version" || parsed.flags.version) {
|
|
@@ -129,12 +131,16 @@ async function main() {
|
|
|
129
131
|
printPromptPreview(config, options);
|
|
130
132
|
return;
|
|
131
133
|
}
|
|
132
|
-
const renderer =
|
|
134
|
+
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput);
|
|
133
135
|
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
134
136
|
const result = await runDebate(config, options, renderer);
|
|
135
137
|
const outputPath = await writeDebateMarkdown(config.outputDir ?? ".", result.options, result.messages, result.summary, result.stopReason);
|
|
136
138
|
renderer.done(outputPath);
|
|
137
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Exécute la commande `agents` : charge la config et affiche les agents déclarés avec leur état de détection.
|
|
142
|
+
* @param flags - Flags parsés depuis la ligne de commande.
|
|
143
|
+
*/
|
|
138
144
|
async function runAgentsCommand(flags) {
|
|
139
145
|
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
140
146
|
if (!(await configExists(configPath))) {
|
|
@@ -144,6 +150,10 @@ async function runAgentsCommand(flags) {
|
|
|
144
150
|
const discovery = await discoverLocalTools();
|
|
145
151
|
printAgents(configPath, config, discovery);
|
|
146
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Exécute la commande `config` : wizard interactif ou mise à jour directe des paramètres par défaut.
|
|
155
|
+
* @param flags - Flags parsés depuis la ligne de commande.
|
|
156
|
+
*/
|
|
147
157
|
async function runConfigCommand(flags) {
|
|
148
158
|
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
149
159
|
if (!(await configExists(configPath))) {
|
|
@@ -203,9 +213,18 @@ async function runConfigCommand(flags) {
|
|
|
203
213
|
}
|
|
204
214
|
await runConfigWizard(configPath, config);
|
|
205
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Renvoie `true` si la valeur représente une désactivation explicite (ex. "none", "0", "disabled").
|
|
218
|
+
* @param value - Chaîne saisie par l'utilisateur.
|
|
219
|
+
*/
|
|
206
220
|
function isNoneValue(value) {
|
|
207
221
|
return ["0", "none", "aucun", "disabled", "désactivé", "desactive"].includes(value.trim().toLowerCase());
|
|
208
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Formate les paramètres par défaut en une ligne lisible pour les messages console.
|
|
225
|
+
* @param defaults - Objet `defaults` de la config Palabre.
|
|
226
|
+
* @returns Chaîne résumant la paire d'agents, le nombre de réponses et l'agent de synthèse.
|
|
227
|
+
*/
|
|
209
228
|
function formatDefaultsForMessage(defaults) {
|
|
210
229
|
const pair = defaults.agentA && defaults.agentB
|
|
211
230
|
? `agents: ${defaults.agentA} <-> ${defaults.agentB}`
|
|
@@ -213,11 +232,26 @@ function formatDefaultsForMessage(defaults) {
|
|
|
213
232
|
const summary = defaults.summaryAgent ? `synthèse: ${defaults.summaryAgent}` : "synthèse: agent B";
|
|
214
233
|
return `${pair}, réponses: ${turnsOrDefault(defaults.turns)}, ${summary}`;
|
|
215
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Lève une erreur si `agentName` n'est pas déclaré dans la config.
|
|
237
|
+
* @param config - Config chargée.
|
|
238
|
+
* @param agentName - Nom de l'agent à vérifier.
|
|
239
|
+
* @param fieldName - Nom du champ (utilisé dans le message d'erreur).
|
|
240
|
+
*/
|
|
216
241
|
function assertKnownAgent(config, agentName, fieldName) {
|
|
217
242
|
if (!config.agents[agentName]) {
|
|
218
243
|
throw new Error(`Agent inconnu pour ${fieldName}: ${agentName}. Agents disponibles: ${Object.keys(config.agents).join(", ")}.`);
|
|
219
244
|
}
|
|
220
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Résout le nom d'un agent selon la priorité : flag CLI > preset > défaut config.
|
|
248
|
+
* Lève une erreur si aucune source ne fournit de valeur.
|
|
249
|
+
* @param label - Libellé humain utilisé dans le message d'erreur (ex. "agent A").
|
|
250
|
+
* @param explicitValue - Valeur passée via flag CLI.
|
|
251
|
+
* @param presetValue - Valeur issue du preset sélectionné.
|
|
252
|
+
* @param defaultValue - Valeur issue des défauts de la config.
|
|
253
|
+
* @returns Nom de l'agent résolu.
|
|
254
|
+
*/
|
|
221
255
|
function resolveAgentName(label, explicitValue, presetValue, defaultValue) {
|
|
222
256
|
const resolved = optionalString(explicitValue) ?? presetValue ?? defaultValue;
|
|
223
257
|
if (!resolved) {
|
|
@@ -225,6 +259,11 @@ function resolveAgentName(label, explicitValue, presetValue, defaultValue) {
|
|
|
225
259
|
}
|
|
226
260
|
return resolved;
|
|
227
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
|
|
264
|
+
* @param config - Config chargée.
|
|
265
|
+
* @param options - Options du débat résolues.
|
|
266
|
+
*/
|
|
228
267
|
function printPromptPreview(config, options) {
|
|
229
268
|
const agentConfig = config.agents[options.agentA];
|
|
230
269
|
if (!agentConfig) {
|
|
@@ -250,9 +289,56 @@ function printPromptPreview(config, options) {
|
|
|
250
289
|
console.log("");
|
|
251
290
|
console.log("Note: seuls les prompts du premier tour sont exacts sans exécuter les agents. Les tours suivants incluent le transcript réel.");
|
|
252
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Extrait une chaîne non vide depuis une valeur de flag, ou renvoie `undefined`.
|
|
294
|
+
* @param value - Valeur brute issue du parseur de flags.
|
|
295
|
+
*/
|
|
253
296
|
function optionalString(value) {
|
|
254
297
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
255
298
|
}
|
|
299
|
+
/** Liste des kinds de renderer acceptés par `--renderer`. */
|
|
300
|
+
const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "ndjson"];
|
|
301
|
+
/**
|
|
302
|
+
* Instancie le renderer en fonction des flags CLI.
|
|
303
|
+
*
|
|
304
|
+
* Précédence :
|
|
305
|
+
* 1. `--renderer <kind>` (canonique).
|
|
306
|
+
* 2. `--json` (alias pour `--renderer ndjson`).
|
|
307
|
+
* 3. `--plain` (rétro-compatible, équivalent `--renderer plain`).
|
|
308
|
+
* 4. par défaut : `auto` (pretty si TTY, plain sinon, hérité de `createConsoleRenderer`).
|
|
309
|
+
*
|
|
310
|
+
* Lève si la valeur de `--renderer` n'est pas dans `SUPPORTED_RENDERERS`.
|
|
311
|
+
*/
|
|
312
|
+
function createRendererFromFlags(flags, plainOutputFallback) {
|
|
313
|
+
const explicit = optionalString(flags.renderer);
|
|
314
|
+
if (explicit) {
|
|
315
|
+
if (!SUPPORTED_RENDERERS.includes(explicit)) {
|
|
316
|
+
throw new Error(`Renderer inconnu: ${explicit}. Valeurs supportées: ${SUPPORTED_RENDERERS.join(", ")}.`);
|
|
317
|
+
}
|
|
318
|
+
const kind = explicit;
|
|
319
|
+
switch (kind) {
|
|
320
|
+
case "ndjson":
|
|
321
|
+
return createNdjsonRenderer();
|
|
322
|
+
case "plain":
|
|
323
|
+
return createConsoleRenderer(true);
|
|
324
|
+
case "pretty":
|
|
325
|
+
return createConsoleRenderer(false);
|
|
326
|
+
case "auto":
|
|
327
|
+
return createConsoleRenderer(plainOutputFallback);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (flags.json) {
|
|
331
|
+
return createNdjsonRenderer();
|
|
332
|
+
}
|
|
333
|
+
return createConsoleRenderer(plainOutputFallback);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Parse `process.argv` en une structure typée `ParsedArgs`.
|
|
337
|
+
* Gère les flags courts (-h, -v, -s, -t, -a), les flags longs (--topic, --agent-a…),
|
|
338
|
+
* les flags multi-valeurs (--files, --context, --set-defaults) et les positionnels.
|
|
339
|
+
* @param args - Tableau d'arguments (généralement `process.argv.slice(2)`).
|
|
340
|
+
* @returns Commande détectée, indicateur d'explicitation et map de flags.
|
|
341
|
+
*/
|
|
256
342
|
function parseArgs(args) {
|
|
257
343
|
const flags = {};
|
|
258
344
|
let command = "run";
|
|
@@ -352,6 +438,12 @@ function parseArgs(args) {
|
|
|
352
438
|
}
|
|
353
439
|
return { command, commandExplicit, flags };
|
|
354
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Détecte si une valeur ressemble à une faute de frappe d'une commande connue
|
|
443
|
+
* (même première lettre et distance de Levenshtein ≤ 2).
|
|
444
|
+
* @param value - Token saisi par l'utilisateur.
|
|
445
|
+
* @param commands - Ensemble des commandes valides.
|
|
446
|
+
*/
|
|
355
447
|
function isLikelyCommandTypo(value, commands) {
|
|
356
448
|
const normalized = value.toLowerCase();
|
|
357
449
|
for (const command of commands) {
|
|
@@ -361,6 +453,12 @@ function isLikelyCommandTypo(value, commands) {
|
|
|
361
453
|
}
|
|
362
454
|
return false;
|
|
363
455
|
}
|
|
456
|
+
/**
|
|
457
|
+
* Calcule la distance de Levenshtein entre deux chaînes (insertions, suppressions, substitutions).
|
|
458
|
+
* @param left - Première chaîne.
|
|
459
|
+
* @param right - Deuxième chaîne.
|
|
460
|
+
* @returns Distance entière ≥ 0.
|
|
461
|
+
*/
|
|
364
462
|
function levenshteinDistance(left, right) {
|
|
365
463
|
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
366
464
|
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
@@ -376,6 +474,14 @@ function levenshteinDistance(left, right) {
|
|
|
376
474
|
}
|
|
377
475
|
return previous[right.length] ?? 0;
|
|
378
476
|
}
|
|
477
|
+
/**
|
|
478
|
+
* Interprète les arguments positionnels pour la commande `run` :
|
|
479
|
+
* premier positionnel = preset si connu, sinon sujet complet concaténé.
|
|
480
|
+
* @param positionals - Arguments positionnels extraits du parseur.
|
|
481
|
+
* @param flags - Map de flags à muter si un preset ou un sujet est détecté.
|
|
482
|
+
* @param presets - Ensemble des noms de presets valides.
|
|
483
|
+
* @param commandExplicit - `true` si l'utilisateur a tapé `palabre run` explicitement.
|
|
484
|
+
*/
|
|
379
485
|
function applyRunPositionals(positionals, flags, presets, commandExplicit) {
|
|
380
486
|
if (positionals.length === 0) {
|
|
381
487
|
return;
|
|
@@ -393,6 +499,10 @@ function applyRunPositionals(positionals, flags, presets, commandExplicit) {
|
|
|
393
499
|
}
|
|
394
500
|
flags.topic ??= positionals.join(" ");
|
|
395
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Normalise un nom de flag long en son alias canonique (ex. `subject` → `topic`).
|
|
504
|
+
* @param value - Nom brut extrait après `--`.
|
|
505
|
+
*/
|
|
396
506
|
function normalizeFlagName(value) {
|
|
397
507
|
const aliases = {
|
|
398
508
|
s: "topic",
|
|
@@ -401,6 +511,10 @@ function normalizeFlagName(value) {
|
|
|
401
511
|
};
|
|
402
512
|
return aliases[value] ?? value;
|
|
403
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Indique si un flag long nécessite une valeur suivante (lève une erreur si absente).
|
|
516
|
+
* @param value - Nom canonique du flag (sans `--`).
|
|
517
|
+
*/
|
|
404
518
|
function requiresFlagValue(value) {
|
|
405
519
|
return new Set([
|
|
406
520
|
"agent-a",
|
|
@@ -416,12 +530,18 @@ function requiresFlagValue(value) {
|
|
|
416
530
|
"turns"
|
|
417
531
|
]).has(value);
|
|
418
532
|
}
|
|
533
|
+
/** Lit la version depuis `package.json` adjacent au bundle compilé. */
|
|
419
534
|
async function getPackageVersion() {
|
|
420
535
|
const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
421
536
|
const raw = await readFile(packageJsonPath, "utf8");
|
|
422
537
|
const packageJson = JSON.parse(raw);
|
|
423
538
|
return packageJson.version ?? "0.0.0";
|
|
424
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* Normalise une valeur de flag multi-valeur en tableau de chaînes.
|
|
542
|
+
* @param value - Valeur brute (tableau, chaîne unique ou absent).
|
|
543
|
+
* @returns Tableau de chaînes, vide si la valeur n'est pas applicable.
|
|
544
|
+
*/
|
|
425
545
|
function getStringListFlag(value) {
|
|
426
546
|
if (Array.isArray(value)) {
|
|
427
547
|
return value;
|
|
@@ -431,11 +551,22 @@ function getStringListFlag(value) {
|
|
|
431
551
|
}
|
|
432
552
|
return [];
|
|
433
553
|
}
|
|
554
|
+
/**
|
|
555
|
+
* Écrit les avertissements de contexte sur `stderr`.
|
|
556
|
+
* @param warnings - Messages d'avertissement issus du chargement des fichiers de contexte.
|
|
557
|
+
*/
|
|
434
558
|
function printContextWarnings(warnings) {
|
|
435
559
|
for (const warning of warnings) {
|
|
436
560
|
process.stderr.write(`Warning: ${warning}\n`);
|
|
437
561
|
}
|
|
438
562
|
}
|
|
563
|
+
/**
|
|
564
|
+
* Ajoute dans `config.agents` les agents détectés localement mais absents de la config.
|
|
565
|
+
* Mute `config` directement ; l'appelant est responsable de persister la config.
|
|
566
|
+
* @param config - Config Palabre à compléter.
|
|
567
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
568
|
+
* @returns Noms des agents nouvellement ajoutés.
|
|
569
|
+
*/
|
|
439
570
|
function syncDetectedAgents(config, discovery) {
|
|
440
571
|
const discoveredConfig = createConfigFromDiscovery(discovery);
|
|
441
572
|
const missingAgents = findDetectedMissingAgents(config, discovery);
|
|
@@ -444,6 +575,11 @@ function syncDetectedAgents(config, discovery) {
|
|
|
444
575
|
}
|
|
445
576
|
return missingAgents;
|
|
446
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Renvoie les noms des agents détectés localement qui ne sont pas encore dans `config.agents`.
|
|
580
|
+
* @param config - Config Palabre existante.
|
|
581
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
582
|
+
*/
|
|
447
583
|
function findDetectedMissingAgents(config, discovery) {
|
|
448
584
|
const detectedAgents = [
|
|
449
585
|
discovery.codex.available ? "codex" : undefined,
|
|
@@ -454,6 +590,12 @@ function findDetectedMissingAgents(config, discovery) {
|
|
|
454
590
|
].filter((agent) => Boolean(agent));
|
|
455
591
|
return detectedAgents.filter((agentName) => !config.agents[agentName]);
|
|
456
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Affiche la liste des agents déclarés avec leur type, rôle, état de détection et défauts.
|
|
595
|
+
* @param configPath - Chemin du fichier de config (affiché en en-tête).
|
|
596
|
+
* @param config - Config Palabre chargée.
|
|
597
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
598
|
+
*/
|
|
457
599
|
function printAgents(configPath, config, discovery) {
|
|
458
600
|
const entries = Object.entries(config.agents).sort(([left], [right]) => left.localeCompare(right));
|
|
459
601
|
console.log(`Config: ${configPath}`);
|
|
@@ -472,6 +614,11 @@ function printAgents(configPath, config, discovery) {
|
|
|
472
614
|
console.log("");
|
|
473
615
|
console.log(`Défauts: ${config.defaults?.agentA ?? "aucun"} <-> ${config.defaults?.agentB ?? "aucun"}, réponses: ${turnsOrDefault(config.defaults?.turns)}, synthèse: ${config.defaults?.summaryAgent ?? "agent B"}`);
|
|
474
616
|
}
|
|
617
|
+
/**
|
|
618
|
+
* Renvoie un libellé indiquant si l'agent est agent A, agent B ou agent de synthèse par défaut.
|
|
619
|
+
* @param name - Nom de l'agent.
|
|
620
|
+
* @param config - Config Palabre contenant les défauts.
|
|
621
|
+
*/
|
|
475
622
|
function formatAgentDefaults(name, config) {
|
|
476
623
|
const labels = [];
|
|
477
624
|
if (config.defaults?.agentA === name)
|
|
@@ -482,12 +629,23 @@ function formatAgentDefaults(name, config) {
|
|
|
482
629
|
labels.push("synthèse par défaut");
|
|
483
630
|
return labels.join(", ");
|
|
484
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Renvoie une ligne de détails pour un agent : commande CLI ou modèle Ollama.
|
|
634
|
+
* @param agentConfig - Configuration de l'agent.
|
|
635
|
+
*/
|
|
485
636
|
function formatAgentDetails(agentConfig) {
|
|
486
637
|
if (agentConfig.type === "ollama") {
|
|
487
638
|
return `modèle: ${agentConfig.model}`;
|
|
488
639
|
}
|
|
489
640
|
return `commande: ${agentConfig.command}${agentConfig.model ? ` | modèle: ${agentConfig.model}` : ""}`;
|
|
490
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* Renvoie le statut de détection d'un agent sous forme de chaîne lisible.
|
|
644
|
+
* Pour Ollama, vérifie la disponibilité du serveur et la présence du modèle.
|
|
645
|
+
* @param name - Nom de l'agent dans la config.
|
|
646
|
+
* @param agentConfig - Configuration de l'agent.
|
|
647
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
648
|
+
*/
|
|
491
649
|
function formatAgentDetection(name, agentConfig, discovery) {
|
|
492
650
|
if (agentConfig.type === "ollama") {
|
|
493
651
|
if (!discovery.ollama.available) {
|
|
@@ -500,6 +658,13 @@ function formatAgentDetection(name, agentConfig, discovery) {
|
|
|
500
658
|
const detection = cliDetectionForAgent(name, agentConfig, discovery);
|
|
501
659
|
return detection.available ? `détecté (${detection.command})` : "non détecté";
|
|
502
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Résout l'entrée de détection correspondant à un agent CLI dans le résultat de découverte.
|
|
663
|
+
* Renvoie un objet `{ available: true }` pour les agents CLI non reconnus (considérés disponibles).
|
|
664
|
+
* @param name - Nom de l'agent dans la config.
|
|
665
|
+
* @param agentConfig - Configuration de l'agent.
|
|
666
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
667
|
+
*/
|
|
503
668
|
function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
504
669
|
const command = normalizeCommandName(agentConfig.type === "cli" ? agentConfig.command : name);
|
|
505
670
|
if (command === "codex")
|
|
@@ -512,9 +677,18 @@ function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
|
512
677
|
return discovery.opencode;
|
|
513
678
|
return { available: true, command: agentConfig.type === "cli" ? agentConfig.command : name };
|
|
514
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Extrait le nom de base d'une commande en supprimant le chemin et l'extension Windows éventuelle.
|
|
682
|
+
* @param command - Chemin ou nom de commande brut (ex. `C:\bin\claude.cmd`).
|
|
683
|
+
*/
|
|
515
684
|
function normalizeCommandName(command) {
|
|
516
685
|
return path.basename(command).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
|
|
517
686
|
}
|
|
687
|
+
/**
|
|
688
|
+
* Affiche le récapitulatif de détection locale après `palabre init`.
|
|
689
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
690
|
+
* @param config - Config générée à partir de la découverte.
|
|
691
|
+
*/
|
|
518
692
|
function printInitDiscovery(discovery, config) {
|
|
519
693
|
console.log("");
|
|
520
694
|
console.log("Détection locale:");
|
|
@@ -526,11 +700,19 @@ function printInitDiscovery(discovery, config) {
|
|
|
526
700
|
console.log("");
|
|
527
701
|
console.log(`Défauts: ${config.defaults?.agentA ?? "codex"} <-> ${config.defaults?.agentB ?? "ollama-local"}`);
|
|
528
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* Formate le statut de détection d'un outil CLI (disponible ou non).
|
|
705
|
+
* @param detection - Résultat de détection d'un outil CLI.
|
|
706
|
+
*/
|
|
529
707
|
function formatCommandDetection(detection) {
|
|
530
708
|
return detection.available
|
|
531
709
|
? `détecté (${detection.command})`
|
|
532
710
|
: "non détecté";
|
|
533
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Formate le statut de détection d'Ollama : commande absente, serveur injoignable ou modèles disponibles.
|
|
714
|
+
* @param detection - Résultat de détection d'Ollama.
|
|
715
|
+
*/
|
|
534
716
|
function formatOllamaDetection(detection) {
|
|
535
717
|
if (!detection.available) {
|
|
536
718
|
return detection.commandAvailable
|
|
@@ -540,54 +722,90 @@ function formatOllamaDetection(detection) {
|
|
|
540
722
|
const modelCount = detection.models.length;
|
|
541
723
|
return `détectée (${modelCount} modèle${modelCount > 1 ? "s" : ""})`;
|
|
542
724
|
}
|
|
725
|
+
/** Affiche le texte d'aide complet sur `stdout`. */
|
|
543
726
|
function printHelp() {
|
|
544
727
|
console.log(`
|
|
545
728
|
PALABRE
|
|
729
|
+
_____________________________________________
|
|
546
730
|
|
|
547
731
|
Usage rapide:
|
|
732
|
+
|
|
733
|
+
palabre init
|
|
734
|
+
Crée une config globale et détecte les agents AI disponibles sur la machine.
|
|
735
|
+
|
|
736
|
+
palabre agents
|
|
737
|
+
Affiche les agents déclarés dans la config.
|
|
738
|
+
|
|
739
|
+
palabre config
|
|
740
|
+
Assistant pour définir ou supprimer les paramètres par défaut.
|
|
741
|
+
|
|
548
742
|
palabre new
|
|
549
743
|
Assistant interactif pour choisir les agents, le sujet et les options.
|
|
550
|
-
|
|
551
|
-
Lance avec les agents par défaut de la config.
|
|
744
|
+
|
|
552
745
|
palabre claude-gemini "Sujet" -t 4
|
|
553
746
|
Lance avec un preset et un sujet positionnel.
|
|
554
747
|
|
|
748
|
+
palabre "Sujet"
|
|
749
|
+
Lance le débat avec paramètres par défaut de la config.
|
|
750
|
+
|
|
751
|
+
_____________________________________________
|
|
752
|
+
|
|
753
|
+
|
|
555
754
|
Commandes:
|
|
755
|
+
|
|
556
756
|
palabre init [--local]
|
|
557
|
-
Crée une config et détecte Codex, Claude, Gemini, OpenCode et Ollama.
|
|
757
|
+
Crée une config locale et détecte Codex, Claude, Gemini, OpenCode et Ollama.
|
|
758
|
+
|
|
558
759
|
palabre agents [--config <path>]
|
|
559
760
|
Liste les agents déclarés dans la config et leur détection locale.
|
|
761
|
+
|
|
560
762
|
palabre config
|
|
561
763
|
Assistant pour définir ou supprimer les paramètres par défaut.
|
|
764
|
+
|
|
562
765
|
palabre config --set-defaults <agentA> <agentB> [-t <n>] [--summary-agent <name>]
|
|
563
766
|
Définit les agents par défaut, et optionnellement les réponses et la synthèse.
|
|
767
|
+
|
|
564
768
|
palabre config -t <n>
|
|
565
769
|
Définit seulement le nombre de réponses par défaut.
|
|
770
|
+
|
|
566
771
|
palabre config --summary-agent <name|none>
|
|
567
772
|
Définit ou retire seulement l'agent de synthèse par défaut.
|
|
773
|
+
|
|
568
774
|
palabre config --clear-defaults
|
|
569
775
|
Supprime les paramètres par défaut.
|
|
776
|
+
|
|
570
777
|
palabre doctor [--config <path>]
|
|
571
778
|
Vérifie la config et les outils locaux.
|
|
779
|
+
|
|
572
780
|
palabre update [--apply]
|
|
573
781
|
Affiche ou exécute les étapes de mise à jour d'un checkout git.
|
|
782
|
+
|
|
574
783
|
palabre help
|
|
575
784
|
Affiche cette aide. Identique à -h ou --help.
|
|
785
|
+
|
|
576
786
|
palabre version
|
|
577
787
|
Affiche la version. Identique à -v ou --version.
|
|
578
788
|
|
|
789
|
+
_____________________________________________
|
|
790
|
+
|
|
791
|
+
|
|
579
792
|
Notation:
|
|
793
|
+
|
|
580
794
|
[option] signifie facultatif. Ne tape pas les crochets.
|
|
581
795
|
<valeur> signifie qu'il faut remplacer ce texte par ta valeur.
|
|
582
796
|
|
|
583
797
|
Options générales:
|
|
798
|
+
|
|
584
799
|
-h, --help Affiche cette aide
|
|
585
800
|
-v, --version Affiche la version
|
|
586
|
-
-a
|
|
801
|
+
-a, --agents Liste les agents. Identique à palabre agents
|
|
587
802
|
--config <path> Chemin vers un fichier de config explicite
|
|
588
803
|
--plain Utilise le rendu console simple sans habillage TUI
|
|
804
|
+
--json Émet un événement NDJSON par ligne sur stdout (alias de --renderer ndjson)
|
|
805
|
+
--renderer <kind> Force le renderer : auto | pretty | plain | ndjson
|
|
589
806
|
|
|
590
807
|
Sujet et lancement:
|
|
808
|
+
|
|
591
809
|
-s, --subject <text> Sujet du débat, option recommandée
|
|
592
810
|
--topic <text> Alias compatible de --subject
|
|
593
811
|
--agent-a <name> Premier agent
|
|
@@ -597,21 +815,25 @@ Sujet et lancement:
|
|
|
597
815
|
--no-early-stop Désactive l'arrêt anticipé si les agents sont clairement d'accord
|
|
598
816
|
|
|
599
817
|
Modèles:
|
|
818
|
+
|
|
600
819
|
--model-a <model> Modèle brut transmis à l'agent A
|
|
601
820
|
--model-b <model> Modèle brut transmis à l'agent B
|
|
602
821
|
--pull-models Autorise Ollama à télécharger un modèle manquant
|
|
603
822
|
|
|
604
823
|
Synthèse:
|
|
824
|
+
|
|
605
825
|
--summary-agent <name> Agent utilisé pour produire la synthèse finale
|
|
606
826
|
--summary-model <model> Modèle brut transmis à l'agent de synthèse
|
|
607
827
|
--no-summary Désactive la synthèse finale
|
|
608
828
|
|
|
609
829
|
Contexte:
|
|
830
|
+
|
|
610
831
|
--files <paths...> Fichiers texte à injecter explicitement dans le contexte
|
|
611
832
|
--context <paths...> Scanne fichiers/dossiers texte en respectant les limites de contexte
|
|
612
833
|
--show-prompt Affiche le prompt du premier tour sans appeler d'agent
|
|
613
834
|
|
|
614
835
|
Configuration:
|
|
836
|
+
|
|
615
837
|
--local Avec init/setup, crée ./palabre.config.json
|
|
616
838
|
--set-defaults <a b> Avec config, définit les agents par défaut
|
|
617
839
|
--summary-agent <name> Avec config, définit l'agent de synthèse par défaut
|
|
@@ -620,10 +842,18 @@ Configuration:
|
|
|
620
842
|
--sync-agents Avec config, ajoute les agents détectés manquants
|
|
621
843
|
|
|
622
844
|
Mise à jour:
|
|
845
|
+
|
|
623
846
|
--apply Avec update, exécute les étapes de mise à jour
|
|
624
847
|
|
|
848
|
+
_____________________________________________
|
|
849
|
+
|
|
850
|
+
|
|
625
851
|
Presets disponibles:
|
|
852
|
+
|
|
626
853
|
${listPresetNames().join(", ")}
|
|
854
|
+
|
|
855
|
+
_____________________________________________
|
|
856
|
+
|
|
627
857
|
`);
|
|
628
858
|
}
|
|
629
859
|
main().catch((error) => {
|
|
@@ -4,6 +4,10 @@ const supportsInteractiveOutput = Boolean(process.stdout.isTTY);
|
|
|
4
4
|
export function createConsoleRenderer(plain) {
|
|
5
5
|
return plain ? new PlainConsoleRenderer() : new PrettyConsoleRenderer(supportsColor, supportsInteractiveOutput);
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Renderer interactif avec spinner, couleurs ANSI et encadrés de section.
|
|
9
|
+
* Utilisé quand `stdout` est un TTY et que `--plain` n'est pas passé.
|
|
10
|
+
*/
|
|
7
11
|
class PrettyConsoleRenderer {
|
|
8
12
|
color;
|
|
9
13
|
interactive;
|
|
@@ -11,10 +15,15 @@ class PrettyConsoleRenderer {
|
|
|
11
15
|
spinnerFrame = 0;
|
|
12
16
|
renderingSummary = false;
|
|
13
17
|
frames = ["-", "\\", "|", "/"];
|
|
18
|
+
/**
|
|
19
|
+
* @param color - Active les codes couleur ANSI.
|
|
20
|
+
* @param interactive - Active le spinner en place (mode TTY interactif).
|
|
21
|
+
*/
|
|
14
22
|
constructor(color, interactive) {
|
|
15
23
|
this.color = color;
|
|
16
24
|
this.interactive = interactive;
|
|
17
25
|
}
|
|
26
|
+
/** Affiche l'en-tête du débat (sujet, agents, options). */
|
|
18
27
|
start(options, agents = []) {
|
|
19
28
|
const title = "PALABRE";
|
|
20
29
|
process.stdout.write([
|
|
@@ -29,21 +38,25 @@ class PrettyConsoleRenderer {
|
|
|
29
38
|
""
|
|
30
39
|
].join("\n"));
|
|
31
40
|
}
|
|
41
|
+
/** Écrit un avertissement sur `stderr` en jaune. */
|
|
32
42
|
warning(message) {
|
|
33
43
|
process.stderr.write(`${this.c("yellow", "Warning:")} ${message}\n`);
|
|
34
44
|
}
|
|
45
|
+
/** Écrit une notice informative sur `stdout` en vert. */
|
|
35
46
|
notice(message) {
|
|
36
47
|
process.stdout.write(`${this.c("green", "Info:")} ${message}\n`);
|
|
37
48
|
}
|
|
49
|
+
/** Affiche l'en-tête d'un nouveau tour (agent, rôle, progression). */
|
|
38
50
|
turnStart(turn, totalTurns, agent, role) {
|
|
39
51
|
this.renderingSummary = false;
|
|
40
52
|
process.stdout.write([
|
|
41
53
|
"",
|
|
42
|
-
this.c("
|
|
54
|
+
this.c("orange", `◆ ${agent}`) + this.dim(` · ${role} · tour ${turn}/${totalTurns}`),
|
|
43
55
|
this.dim("─".repeat(60)),
|
|
44
56
|
""
|
|
45
57
|
].join("\n"));
|
|
46
58
|
}
|
|
59
|
+
/** Démarre le spinner de réflexion (ou affiche une ligne fixe si non interactif). */
|
|
47
60
|
thinkingStart(agent, role) {
|
|
48
61
|
this.thinkingEnd();
|
|
49
62
|
const text = `${agent} (${role}) reflechit`;
|
|
@@ -59,6 +72,7 @@ class PrettyConsoleRenderer {
|
|
|
59
72
|
render();
|
|
60
73
|
this.spinner = setInterval(render, 120);
|
|
61
74
|
}
|
|
75
|
+
/** Arrête le spinner et efface la ligne de réflexion en mode interactif. */
|
|
62
76
|
thinkingEnd() {
|
|
63
77
|
if (this.spinner) {
|
|
64
78
|
clearInterval(this.spinner);
|
|
@@ -68,22 +82,29 @@ class PrettyConsoleRenderer {
|
|
|
68
82
|
process.stdout.write("\r\u001b[2K");
|
|
69
83
|
}
|
|
70
84
|
}
|
|
85
|
+
/** Écrit le contenu d'un message agent, avec formatage de synthèse si applicable. */
|
|
71
86
|
message(content) {
|
|
72
87
|
const trimmed = content.trim();
|
|
73
88
|
process.stdout.write(`${this.renderingSummary ? this.formatSummaryMessage(trimmed) : trimmed}\n`);
|
|
74
89
|
}
|
|
90
|
+
/** Affiche l'en-tête de section synthèse et active le mode formatage de résumé. */
|
|
75
91
|
summaryStart(agent, role) {
|
|
76
92
|
this.renderingSummary = true;
|
|
77
93
|
process.stdout.write([
|
|
78
94
|
"",
|
|
79
|
-
this.c("
|
|
95
|
+
this.c("pink", `◆ Synthese`) + this.dim(` · ${agent} · ${role}`),
|
|
80
96
|
this.dim("─".repeat(60)),
|
|
81
97
|
""
|
|
82
98
|
].join("\n"));
|
|
83
99
|
}
|
|
100
|
+
/** Affiche le chemin du fichier de sortie en vert à la fin du débat. */
|
|
84
101
|
done(outputPath) {
|
|
85
|
-
process.stdout.write(`\n${this.c("green", "Debat exporte:")} ${outputPath}\n`);
|
|
102
|
+
process.stdout.write(`\n\n${this.c("green", "Debat exporte:")} ${outputPath}\n\n`);
|
|
86
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Convertit les titres Markdown `### Heading` en titres colorés avec séparateur.
|
|
106
|
+
* @param content - Contenu texte de la synthèse.
|
|
107
|
+
*/
|
|
87
108
|
formatSummaryMessage(content) {
|
|
88
109
|
return content
|
|
89
110
|
.split(/\r?\n/)
|
|
@@ -93,66 +114,99 @@ class PrettyConsoleRenderer {
|
|
|
93
114
|
return line;
|
|
94
115
|
return [
|
|
95
116
|
"",
|
|
96
|
-
this.c("
|
|
117
|
+
this.c("pink", heading[1] ?? line),
|
|
97
118
|
this.dim("─".repeat(40))
|
|
98
119
|
].join("\n");
|
|
99
120
|
})
|
|
100
121
|
.join("\n")
|
|
101
122
|
.trimStart();
|
|
102
123
|
}
|
|
124
|
+
/** Entoure `value` avec le code couleur ANSI si les couleurs sont activées. */
|
|
103
125
|
c(color, value) {
|
|
104
126
|
if (!this.color)
|
|
105
127
|
return value;
|
|
106
128
|
return `${codes[color]}${value}${codes.reset}`;
|
|
107
129
|
}
|
|
130
|
+
/** Applique le style dim (atténué) si les couleurs sont activées. */
|
|
108
131
|
dim(value) {
|
|
109
132
|
if (!this.color)
|
|
110
133
|
return value;
|
|
111
134
|
return `${codes.dim}${value}${codes.reset}`;
|
|
112
135
|
}
|
|
113
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Renderer minimaliste sans couleurs ni spinner.
|
|
139
|
+
* Utilisé avec `--plain` ou quand `stdout` n'est pas un TTY.
|
|
140
|
+
*/
|
|
114
141
|
class PlainConsoleRenderer {
|
|
142
|
+
/** Affiche les informations de démarrage du débat en texte brut. */
|
|
115
143
|
start(options, agents = []) {
|
|
116
144
|
process.stdout.write(`Sujet: ${options.topic}` + "\n");
|
|
117
145
|
process.stdout.write(`Agents: ${formatAgentPair(options, agents)}` + "\n");
|
|
118
146
|
process.stdout.write(`Réponses: ${options.turns} | Synthèse: ${formatSummary(options)} | Contexte: ${formatContext(options)}` + "\n");
|
|
119
147
|
}
|
|
148
|
+
/** Écrit un avertissement sur `stderr`. */
|
|
120
149
|
warning(message) {
|
|
121
150
|
process.stderr.write(`Warning: ${message}\n`);
|
|
122
151
|
}
|
|
152
|
+
/** Écrit une notice informative sur `stdout`. */
|
|
123
153
|
notice(message) {
|
|
124
154
|
process.stdout.write(`Info: ${message}\n`);
|
|
125
155
|
}
|
|
156
|
+
/** Affiche la progression du tour en texte brut. */
|
|
126
157
|
turnStart(turn, totalTurns, agent, role) {
|
|
127
158
|
process.stdout.write(`\n[${turn}/${totalTurns}] ${agent} (${role})...\n`);
|
|
128
159
|
}
|
|
160
|
+
/** No-op : pas de spinner en mode plain. */
|
|
129
161
|
thinkingStart(_agent, _role) { }
|
|
162
|
+
/** No-op : pas de spinner à arrêter en mode plain. */
|
|
130
163
|
thinkingEnd() { }
|
|
164
|
+
/** Écrit le contenu du message agent trimé. */
|
|
131
165
|
message(content) {
|
|
132
166
|
process.stdout.write(`${content.trim()}\n`);
|
|
133
167
|
}
|
|
168
|
+
/** Affiche l'en-tête de la section synthèse en texte brut. */
|
|
134
169
|
summaryStart(agent, role) {
|
|
135
170
|
process.stdout.write(`\n[Synthese] ${agent} (${role})...\n`);
|
|
136
171
|
}
|
|
172
|
+
/** Affiche le chemin du fichier de sortie à la fin du débat. */
|
|
137
173
|
done(outputPath) {
|
|
138
174
|
process.stdout.write(`\nDebat exporte: ${outputPath}\n`);
|
|
139
175
|
}
|
|
140
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Formate la paire d'agents pour l'en-tête : utilise les infos enrichies si disponibles,
|
|
179
|
+
* sinon les noms bruts des options.
|
|
180
|
+
* @param options - Options du débat.
|
|
181
|
+
* @param agents - Infos de démarrage des agents (type, rôle, nom).
|
|
182
|
+
*/
|
|
141
183
|
function formatAgentPair(options, agents) {
|
|
142
184
|
if (agents.length >= 2) {
|
|
143
185
|
return `${formatAgentLabel(agents[0])} <-> ${formatAgentLabel(agents[1])}`;
|
|
144
186
|
}
|
|
145
187
|
return `${options.agentA} <-> ${options.agentB}`;
|
|
146
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Formate un agent en `nom (rôle, type)` ou `"?"` si absent.
|
|
191
|
+
* @param agent - Info de démarrage de l'agent, ou `undefined`.
|
|
192
|
+
*/
|
|
147
193
|
function formatAgentLabel(agent) {
|
|
148
194
|
if (!agent) {
|
|
149
195
|
return "?";
|
|
150
196
|
}
|
|
151
197
|
return `${agent.name} (${agent.role}, ${agent.type})`;
|
|
152
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Renvoie le nom de l'agent de synthèse ou `"désactivée"` si la synthèse est désactivée.
|
|
201
|
+
* @param options - Options du débat.
|
|
202
|
+
*/
|
|
153
203
|
function formatSummary(options) {
|
|
154
204
|
return options.summaryEnabled ? options.summaryAgent ?? options.agentB : "désactivée";
|
|
155
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Renvoie un résumé du contexte injecté (nombre de fichiers ou mention d'absence).
|
|
208
|
+
* @param options - Options du débat.
|
|
209
|
+
*/
|
|
156
210
|
function formatContext(options) {
|
|
157
211
|
const count = options.files.length;
|
|
158
212
|
if (count === 0) {
|
|
@@ -160,6 +214,7 @@ function formatContext(options) {
|
|
|
160
214
|
}
|
|
161
215
|
return `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""}`;
|
|
162
216
|
}
|
|
217
|
+
/** Codes d'échappement ANSI utilisés par `PrettyConsoleRenderer`. */
|
|
163
218
|
const codes = {
|
|
164
219
|
reset: "\u001b[0m",
|
|
165
220
|
dim: "\u001b[2m",
|
|
@@ -167,5 +222,7 @@ const codes = {
|
|
|
167
222
|
cyan: "\u001b[36m",
|
|
168
223
|
green: "\u001b[32m",
|
|
169
224
|
magenta: "\u001b[35m",
|
|
170
|
-
yellow: "\u001b[33m"
|
|
225
|
+
yellow: "\u001b[33m",
|
|
226
|
+
orange: "\u001b[38;5;208m",
|
|
227
|
+
pink: "\u001b[38;5;205m"
|
|
171
228
|
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer NDJSON : émet une ligne JSON par événement DebateRenderer.
|
|
3
|
+
*
|
|
4
|
+
* Cible : intégrations qui pilotent Palabre en out-of-process (extension
|
|
5
|
+
* VS Code, plugin Obsidian, scripts shell). Le rendu humain reste
|
|
6
|
+
* `PrettyConsoleRenderer` ou `PlainConsoleRenderer`.
|
|
7
|
+
*
|
|
8
|
+
* Contrat :
|
|
9
|
+
* - une ligne = un événement JSON valide, terminé par `\n` ;
|
|
10
|
+
* - tous les événements portent un champ `v` (entier) pour le versioning ;
|
|
11
|
+
* - toute évolution cassante du schéma doit incrémenter `v` et documenter
|
|
12
|
+
* la migration dans AGENTS.md (section "Renderer NDJSON").
|
|
13
|
+
*
|
|
14
|
+
* Sortie : stdout uniquement. stderr reste libre pour les messages bas
|
|
15
|
+
* niveau (Node, shell) que les consommateurs peuvent agréger comme ils
|
|
16
|
+
* veulent.
|
|
17
|
+
*/
|
|
18
|
+
export class NdjsonRenderer {
|
|
19
|
+
schemaVersion = 1;
|
|
20
|
+
currentSection = "debate";
|
|
21
|
+
currentTurn = 0;
|
|
22
|
+
currentAgent = null;
|
|
23
|
+
currentRole = null;
|
|
24
|
+
/** Émet `start` avec le sujet, les agents, les options principales et le contexte de session. */
|
|
25
|
+
start(options, agents = []) {
|
|
26
|
+
this.emit({
|
|
27
|
+
type: "start",
|
|
28
|
+
topic: options.topic,
|
|
29
|
+
turns: options.turns,
|
|
30
|
+
agents: agents.map((a) => ({ name: a.name, role: a.role, type: a.type })),
|
|
31
|
+
summaryEnabled: options.summaryEnabled,
|
|
32
|
+
summaryAgent: options.summaryEnabled
|
|
33
|
+
? options.summaryAgent ?? options.agentB
|
|
34
|
+
: null,
|
|
35
|
+
earlyStop: options.earlyStopOnAgreement,
|
|
36
|
+
filesCount: options.files.length,
|
|
37
|
+
session: {
|
|
38
|
+
startedAt: options.session.startedAt,
|
|
39
|
+
localDate: options.session.localDate,
|
|
40
|
+
timeZone: options.session.timeZone,
|
|
41
|
+
cwd: options.session.cwd,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Émet un événement informatif. */
|
|
46
|
+
notice(message) {
|
|
47
|
+
this.emit({ type: "notice", message });
|
|
48
|
+
}
|
|
49
|
+
/** Émet un avertissement. Reste sur stdout pour conserver l'ordre des événements. */
|
|
50
|
+
warning(message) {
|
|
51
|
+
this.emit({ type: "warning", message });
|
|
52
|
+
}
|
|
53
|
+
/** Émet `turn-start` et bascule la section courante en débat. */
|
|
54
|
+
turnStart(turn, totalTurns, agent, role) {
|
|
55
|
+
this.currentSection = "debate";
|
|
56
|
+
this.currentTurn = turn;
|
|
57
|
+
this.currentAgent = agent;
|
|
58
|
+
this.currentRole = role;
|
|
59
|
+
this.emit({ type: "turn-start", turn, totalTurns, agent, role });
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Émet `thinking-start`. Les consommateurs UI peuvent l'utiliser pour un
|
|
63
|
+
* indicateur "agent en cours" ; les consommateurs purement data peuvent
|
|
64
|
+
* l'ignorer sans perte d'information sémantique.
|
|
65
|
+
*/
|
|
66
|
+
thinkingStart(agent, role) {
|
|
67
|
+
this.emit({ type: "thinking-start", agent, role });
|
|
68
|
+
}
|
|
69
|
+
/** Émet `thinking-end`. */
|
|
70
|
+
thinkingEnd() {
|
|
71
|
+
this.emit({ type: "thinking-end" });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Émet `message` (section débat) ou `summary-message` (section synthèse)
|
|
75
|
+
* selon l'état courant. La discrimination par type permet aux
|
|
76
|
+
* consommateurs de router sans maintenir d'état eux-mêmes.
|
|
77
|
+
*/
|
|
78
|
+
message(content) {
|
|
79
|
+
if (this.currentSection === "summary") {
|
|
80
|
+
this.emit({
|
|
81
|
+
type: "summary-message",
|
|
82
|
+
agent: this.currentAgent,
|
|
83
|
+
role: this.currentRole,
|
|
84
|
+
content,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.emit({
|
|
89
|
+
type: "message",
|
|
90
|
+
turn: this.currentTurn,
|
|
91
|
+
agent: this.currentAgent,
|
|
92
|
+
role: this.currentRole,
|
|
93
|
+
content,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Émet `summary-start` et bascule la section courante en synthèse. */
|
|
98
|
+
summaryStart(agent, role) {
|
|
99
|
+
this.currentSection = "summary";
|
|
100
|
+
this.currentAgent = agent;
|
|
101
|
+
this.currentRole = role;
|
|
102
|
+
this.emit({ type: "summary-start", agent, role });
|
|
103
|
+
}
|
|
104
|
+
/** Émet `done` avec le chemin du `.debate.md` écrit. */
|
|
105
|
+
done(outputPath) {
|
|
106
|
+
this.emit({ type: "done", outputPath });
|
|
107
|
+
}
|
|
108
|
+
/** Sérialise un événement et l'écrit sur stdout, terminé par `\n`. */
|
|
109
|
+
emit(event) {
|
|
110
|
+
process.stdout.write(JSON.stringify({ v: this.schemaVersion, ...event }) + "\n");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Factory pratique pour conserver la symétrie avec `createConsoleRenderer`. */
|
|
114
|
+
export function createNdjsonRenderer() {
|
|
115
|
+
return new NdjsonRenderer();
|
|
116
|
+
}
|