palabre 0.1.7 → 0.3.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 +271 -6
- 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) {
|
|
@@ -41,6 +43,10 @@ async function main() {
|
|
|
41
43
|
await runAgentsCommand(parsed.flags);
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
46
|
+
if (parsed.command === "presets" || parsed.command === "preset") {
|
|
47
|
+
runPresetsCommand(parsed.flags);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
44
50
|
if (parsed.command === "update") {
|
|
45
51
|
const info = await getUpdateInfo(await getPackageVersion());
|
|
46
52
|
if (parsed.flags.apply) {
|
|
@@ -129,12 +135,16 @@ async function main() {
|
|
|
129
135
|
printPromptPreview(config, options);
|
|
130
136
|
return;
|
|
131
137
|
}
|
|
132
|
-
const renderer =
|
|
138
|
+
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput);
|
|
133
139
|
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
134
140
|
const result = await runDebate(config, options, renderer);
|
|
135
141
|
const outputPath = await writeDebateMarkdown(config.outputDir ?? ".", result.options, result.messages, result.summary, result.stopReason);
|
|
136
142
|
renderer.done(outputPath);
|
|
137
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Exécute la commande `agents` : charge la config et affiche les agents déclarés avec leur état de détection.
|
|
146
|
+
* @param flags - Flags parsés depuis la ligne de commande.
|
|
147
|
+
*/
|
|
138
148
|
async function runAgentsCommand(flags) {
|
|
139
149
|
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
140
150
|
if (!(await configExists(configPath))) {
|
|
@@ -144,6 +154,10 @@ async function runAgentsCommand(flags) {
|
|
|
144
154
|
const discovery = await discoverLocalTools();
|
|
145
155
|
printAgents(configPath, config, discovery);
|
|
146
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Exécute la commande `config` : wizard interactif ou mise à jour directe des paramètres par défaut.
|
|
159
|
+
* @param flags - Flags parsés depuis la ligne de commande.
|
|
160
|
+
*/
|
|
147
161
|
async function runConfigCommand(flags) {
|
|
148
162
|
const configPath = optionalString(flags.config) ?? await resolveDefaultConfigPath();
|
|
149
163
|
if (!(await configExists(configPath))) {
|
|
@@ -203,9 +217,18 @@ async function runConfigCommand(flags) {
|
|
|
203
217
|
}
|
|
204
218
|
await runConfigWizard(configPath, config);
|
|
205
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Renvoie `true` si la valeur représente une désactivation explicite (ex. "none", "0", "disabled").
|
|
222
|
+
* @param value - Chaîne saisie par l'utilisateur.
|
|
223
|
+
*/
|
|
206
224
|
function isNoneValue(value) {
|
|
207
225
|
return ["0", "none", "aucun", "disabled", "désactivé", "desactive"].includes(value.trim().toLowerCase());
|
|
208
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Formate les paramètres par défaut en une ligne lisible pour les messages console.
|
|
229
|
+
* @param defaults - Objet `defaults` de la config Palabre.
|
|
230
|
+
* @returns Chaîne résumant la paire d'agents, le nombre de réponses et l'agent de synthèse.
|
|
231
|
+
*/
|
|
209
232
|
function formatDefaultsForMessage(defaults) {
|
|
210
233
|
const pair = defaults.agentA && defaults.agentB
|
|
211
234
|
? `agents: ${defaults.agentA} <-> ${defaults.agentB}`
|
|
@@ -213,11 +236,26 @@ function formatDefaultsForMessage(defaults) {
|
|
|
213
236
|
const summary = defaults.summaryAgent ? `synthèse: ${defaults.summaryAgent}` : "synthèse: agent B";
|
|
214
237
|
return `${pair}, réponses: ${turnsOrDefault(defaults.turns)}, ${summary}`;
|
|
215
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* Lève une erreur si `agentName` n'est pas déclaré dans la config.
|
|
241
|
+
* @param config - Config chargée.
|
|
242
|
+
* @param agentName - Nom de l'agent à vérifier.
|
|
243
|
+
* @param fieldName - Nom du champ (utilisé dans le message d'erreur).
|
|
244
|
+
*/
|
|
216
245
|
function assertKnownAgent(config, agentName, fieldName) {
|
|
217
246
|
if (!config.agents[agentName]) {
|
|
218
247
|
throw new Error(`Agent inconnu pour ${fieldName}: ${agentName}. Agents disponibles: ${Object.keys(config.agents).join(", ")}.`);
|
|
219
248
|
}
|
|
220
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Résout le nom d'un agent selon la priorité : flag CLI > preset > défaut config.
|
|
252
|
+
* Lève une erreur si aucune source ne fournit de valeur.
|
|
253
|
+
* @param label - Libellé humain utilisé dans le message d'erreur (ex. "agent A").
|
|
254
|
+
* @param explicitValue - Valeur passée via flag CLI.
|
|
255
|
+
* @param presetValue - Valeur issue du preset sélectionné.
|
|
256
|
+
* @param defaultValue - Valeur issue des défauts de la config.
|
|
257
|
+
* @returns Nom de l'agent résolu.
|
|
258
|
+
*/
|
|
221
259
|
function resolveAgentName(label, explicitValue, presetValue, defaultValue) {
|
|
222
260
|
const resolved = optionalString(explicitValue) ?? presetValue ?? defaultValue;
|
|
223
261
|
if (!resolved) {
|
|
@@ -225,6 +263,11 @@ function resolveAgentName(label, explicitValue, presetValue, defaultValue) {
|
|
|
225
263
|
}
|
|
226
264
|
return resolved;
|
|
227
265
|
}
|
|
266
|
+
/**
|
|
267
|
+
* Affiche un aperçu du prompt du premier tour sans appeler aucun agent (flag `--show-prompt`).
|
|
268
|
+
* @param config - Config chargée.
|
|
269
|
+
* @param options - Options du débat résolues.
|
|
270
|
+
*/
|
|
228
271
|
function printPromptPreview(config, options) {
|
|
229
272
|
const agentConfig = config.agents[options.agentA];
|
|
230
273
|
if (!agentConfig) {
|
|
@@ -250,15 +293,89 @@ function printPromptPreview(config, options) {
|
|
|
250
293
|
console.log("");
|
|
251
294
|
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
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Extrait une chaîne non vide depuis une valeur de flag, ou renvoie `undefined`.
|
|
298
|
+
* @param value - Valeur brute issue du parseur de flags.
|
|
299
|
+
*/
|
|
253
300
|
function optionalString(value) {
|
|
254
301
|
return typeof value === "string" && value.trim() ? value : undefined;
|
|
255
302
|
}
|
|
303
|
+
/** Liste des kinds de renderer acceptés par `--renderer`. */
|
|
304
|
+
const SUPPORTED_RENDERERS = ["auto", "pretty", "plain", "ndjson"];
|
|
305
|
+
/**
|
|
306
|
+
* Instancie le renderer en fonction des flags CLI.
|
|
307
|
+
*
|
|
308
|
+
* Précédence :
|
|
309
|
+
* 1. `--renderer <kind>` (canonique).
|
|
310
|
+
* 2. `--json` (alias pour `--renderer ndjson`).
|
|
311
|
+
* 3. `--plain` (rétro-compatible, équivalent `--renderer plain`).
|
|
312
|
+
* 4. par défaut : `auto` (pretty si TTY, plain sinon, hérité de `createConsoleRenderer`).
|
|
313
|
+
*
|
|
314
|
+
* Lève si la valeur de `--renderer` n'est pas dans `SUPPORTED_RENDERERS`.
|
|
315
|
+
*/
|
|
316
|
+
function createRendererFromFlags(flags, plainOutputFallback) {
|
|
317
|
+
const explicit = optionalString(flags.renderer);
|
|
318
|
+
if (explicit) {
|
|
319
|
+
if (!SUPPORTED_RENDERERS.includes(explicit)) {
|
|
320
|
+
throw new Error(`Renderer inconnu: ${explicit}. Valeurs supportées: ${SUPPORTED_RENDERERS.join(", ")}.`);
|
|
321
|
+
}
|
|
322
|
+
const kind = explicit;
|
|
323
|
+
switch (kind) {
|
|
324
|
+
case "ndjson":
|
|
325
|
+
return createNdjsonRenderer();
|
|
326
|
+
case "plain":
|
|
327
|
+
return createConsoleRenderer(true);
|
|
328
|
+
case "pretty":
|
|
329
|
+
return createConsoleRenderer(false);
|
|
330
|
+
case "auto":
|
|
331
|
+
return createConsoleRenderer(plainOutputFallback);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (flags.json) {
|
|
335
|
+
return createNdjsonRenderer();
|
|
336
|
+
}
|
|
337
|
+
return createConsoleRenderer(plainOutputFallback);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Exécute la commande `palabre presets`.
|
|
341
|
+
*
|
|
342
|
+
* Sortie humaine par défaut (liste alignée), ou JSON avec `--json` pour les
|
|
343
|
+
* intégrations (extension VS Code, scripts shell). Le schéma JSON est versionné
|
|
344
|
+
* via le champ `v` au cas où on enrichirait plus tard (ex : description par
|
|
345
|
+
* preset, tags premium/local).
|
|
346
|
+
*
|
|
347
|
+
* @param flags - Flags parsés depuis la ligne de commande.
|
|
348
|
+
*/
|
|
349
|
+
function runPresetsCommand(flags) {
|
|
350
|
+
const presets = listPresetNames().map((name) => {
|
|
351
|
+
const pair = resolvePreset(name);
|
|
352
|
+
return { name, agentA: pair.agentA, agentB: pair.agentB };
|
|
353
|
+
});
|
|
354
|
+
if (flags.json) {
|
|
355
|
+
process.stdout.write(JSON.stringify({ v: 1, presets }) + "\n");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
console.log("Presets disponibles:");
|
|
359
|
+
console.log("");
|
|
360
|
+
for (const preset of presets) {
|
|
361
|
+
console.log(` ${preset.name.padEnd(20)} ${preset.agentA} <-> ${preset.agentB}`);
|
|
362
|
+
}
|
|
363
|
+
console.log("");
|
|
364
|
+
console.log(`Total : ${presets.length} preset(s). Utilise --json pour une sortie machine-readable.`);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Parse `process.argv` en une structure typée `ParsedArgs`.
|
|
368
|
+
* Gère les flags courts (-h, -v, -s, -t, -a), les flags longs (--topic, --agent-a…),
|
|
369
|
+
* les flags multi-valeurs (--files, --context, --set-defaults) et les positionnels.
|
|
370
|
+
* @param args - Tableau d'arguments (généralement `process.argv.slice(2)`).
|
|
371
|
+
* @returns Commande détectée, indicateur d'explicitation et map de flags.
|
|
372
|
+
*/
|
|
256
373
|
function parseArgs(args) {
|
|
257
374
|
const flags = {};
|
|
258
375
|
let command = "run";
|
|
259
376
|
let commandExplicit = false;
|
|
260
377
|
const positionals = [];
|
|
261
|
-
const commands = new Set(["run", "new", "init", "setup", "help", "version", "update", "doctor", "config", "agent", "agents"]);
|
|
378
|
+
const commands = new Set(["run", "new", "init", "setup", "help", "version", "update", "doctor", "config", "agent", "agents", "preset", "presets"]);
|
|
262
379
|
const presets = new Set(listPresetNames());
|
|
263
380
|
for (let index = 0; index < args.length; index += 1) {
|
|
264
381
|
const value = args[index];
|
|
@@ -352,6 +469,12 @@ function parseArgs(args) {
|
|
|
352
469
|
}
|
|
353
470
|
return { command, commandExplicit, flags };
|
|
354
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Détecte si une valeur ressemble à une faute de frappe d'une commande connue
|
|
474
|
+
* (même première lettre et distance de Levenshtein ≤ 2).
|
|
475
|
+
* @param value - Token saisi par l'utilisateur.
|
|
476
|
+
* @param commands - Ensemble des commandes valides.
|
|
477
|
+
*/
|
|
355
478
|
function isLikelyCommandTypo(value, commands) {
|
|
356
479
|
const normalized = value.toLowerCase();
|
|
357
480
|
for (const command of commands) {
|
|
@@ -361,6 +484,12 @@ function isLikelyCommandTypo(value, commands) {
|
|
|
361
484
|
}
|
|
362
485
|
return false;
|
|
363
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* Calcule la distance de Levenshtein entre deux chaînes (insertions, suppressions, substitutions).
|
|
489
|
+
* @param left - Première chaîne.
|
|
490
|
+
* @param right - Deuxième chaîne.
|
|
491
|
+
* @returns Distance entière ≥ 0.
|
|
492
|
+
*/
|
|
364
493
|
function levenshteinDistance(left, right) {
|
|
365
494
|
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
366
495
|
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
@@ -376,6 +505,14 @@ function levenshteinDistance(left, right) {
|
|
|
376
505
|
}
|
|
377
506
|
return previous[right.length] ?? 0;
|
|
378
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Interprète les arguments positionnels pour la commande `run` :
|
|
510
|
+
* premier positionnel = preset si connu, sinon sujet complet concaténé.
|
|
511
|
+
* @param positionals - Arguments positionnels extraits du parseur.
|
|
512
|
+
* @param flags - Map de flags à muter si un preset ou un sujet est détecté.
|
|
513
|
+
* @param presets - Ensemble des noms de presets valides.
|
|
514
|
+
* @param commandExplicit - `true` si l'utilisateur a tapé `palabre run` explicitement.
|
|
515
|
+
*/
|
|
379
516
|
function applyRunPositionals(positionals, flags, presets, commandExplicit) {
|
|
380
517
|
if (positionals.length === 0) {
|
|
381
518
|
return;
|
|
@@ -393,6 +530,10 @@ function applyRunPositionals(positionals, flags, presets, commandExplicit) {
|
|
|
393
530
|
}
|
|
394
531
|
flags.topic ??= positionals.join(" ");
|
|
395
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* Normalise un nom de flag long en son alias canonique (ex. `subject` → `topic`).
|
|
535
|
+
* @param value - Nom brut extrait après `--`.
|
|
536
|
+
*/
|
|
396
537
|
function normalizeFlagName(value) {
|
|
397
538
|
const aliases = {
|
|
398
539
|
s: "topic",
|
|
@@ -401,6 +542,10 @@ function normalizeFlagName(value) {
|
|
|
401
542
|
};
|
|
402
543
|
return aliases[value] ?? value;
|
|
403
544
|
}
|
|
545
|
+
/**
|
|
546
|
+
* Indique si un flag long nécessite une valeur suivante (lève une erreur si absente).
|
|
547
|
+
* @param value - Nom canonique du flag (sans `--`).
|
|
548
|
+
*/
|
|
404
549
|
function requiresFlagValue(value) {
|
|
405
550
|
return new Set([
|
|
406
551
|
"agent-a",
|
|
@@ -416,12 +561,18 @@ function requiresFlagValue(value) {
|
|
|
416
561
|
"turns"
|
|
417
562
|
]).has(value);
|
|
418
563
|
}
|
|
564
|
+
/** Lit la version depuis `package.json` adjacent au bundle compilé. */
|
|
419
565
|
async function getPackageVersion() {
|
|
420
566
|
const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
421
567
|
const raw = await readFile(packageJsonPath, "utf8");
|
|
422
568
|
const packageJson = JSON.parse(raw);
|
|
423
569
|
return packageJson.version ?? "0.0.0";
|
|
424
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Normalise une valeur de flag multi-valeur en tableau de chaînes.
|
|
573
|
+
* @param value - Valeur brute (tableau, chaîne unique ou absent).
|
|
574
|
+
* @returns Tableau de chaînes, vide si la valeur n'est pas applicable.
|
|
575
|
+
*/
|
|
425
576
|
function getStringListFlag(value) {
|
|
426
577
|
if (Array.isArray(value)) {
|
|
427
578
|
return value;
|
|
@@ -431,11 +582,22 @@ function getStringListFlag(value) {
|
|
|
431
582
|
}
|
|
432
583
|
return [];
|
|
433
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Écrit les avertissements de contexte sur `stderr`.
|
|
587
|
+
* @param warnings - Messages d'avertissement issus du chargement des fichiers de contexte.
|
|
588
|
+
*/
|
|
434
589
|
function printContextWarnings(warnings) {
|
|
435
590
|
for (const warning of warnings) {
|
|
436
591
|
process.stderr.write(`Warning: ${warning}\n`);
|
|
437
592
|
}
|
|
438
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Ajoute dans `config.agents` les agents détectés localement mais absents de la config.
|
|
596
|
+
* Mute `config` directement ; l'appelant est responsable de persister la config.
|
|
597
|
+
* @param config - Config Palabre à compléter.
|
|
598
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
599
|
+
* @returns Noms des agents nouvellement ajoutés.
|
|
600
|
+
*/
|
|
439
601
|
function syncDetectedAgents(config, discovery) {
|
|
440
602
|
const discoveredConfig = createConfigFromDiscovery(discovery);
|
|
441
603
|
const missingAgents = findDetectedMissingAgents(config, discovery);
|
|
@@ -444,6 +606,11 @@ function syncDetectedAgents(config, discovery) {
|
|
|
444
606
|
}
|
|
445
607
|
return missingAgents;
|
|
446
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Renvoie les noms des agents détectés localement qui ne sont pas encore dans `config.agents`.
|
|
611
|
+
* @param config - Config Palabre existante.
|
|
612
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
613
|
+
*/
|
|
447
614
|
function findDetectedMissingAgents(config, discovery) {
|
|
448
615
|
const detectedAgents = [
|
|
449
616
|
discovery.codex.available ? "codex" : undefined,
|
|
@@ -454,6 +621,12 @@ function findDetectedMissingAgents(config, discovery) {
|
|
|
454
621
|
].filter((agent) => Boolean(agent));
|
|
455
622
|
return detectedAgents.filter((agentName) => !config.agents[agentName]);
|
|
456
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* Affiche la liste des agents déclarés avec leur type, rôle, état de détection et défauts.
|
|
626
|
+
* @param configPath - Chemin du fichier de config (affiché en en-tête).
|
|
627
|
+
* @param config - Config Palabre chargée.
|
|
628
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
629
|
+
*/
|
|
457
630
|
function printAgents(configPath, config, discovery) {
|
|
458
631
|
const entries = Object.entries(config.agents).sort(([left], [right]) => left.localeCompare(right));
|
|
459
632
|
console.log(`Config: ${configPath}`);
|
|
@@ -472,6 +645,11 @@ function printAgents(configPath, config, discovery) {
|
|
|
472
645
|
console.log("");
|
|
473
646
|
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
647
|
}
|
|
648
|
+
/**
|
|
649
|
+
* Renvoie un libellé indiquant si l'agent est agent A, agent B ou agent de synthèse par défaut.
|
|
650
|
+
* @param name - Nom de l'agent.
|
|
651
|
+
* @param config - Config Palabre contenant les défauts.
|
|
652
|
+
*/
|
|
475
653
|
function formatAgentDefaults(name, config) {
|
|
476
654
|
const labels = [];
|
|
477
655
|
if (config.defaults?.agentA === name)
|
|
@@ -482,12 +660,23 @@ function formatAgentDefaults(name, config) {
|
|
|
482
660
|
labels.push("synthèse par défaut");
|
|
483
661
|
return labels.join(", ");
|
|
484
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Renvoie une ligne de détails pour un agent : commande CLI ou modèle Ollama.
|
|
665
|
+
* @param agentConfig - Configuration de l'agent.
|
|
666
|
+
*/
|
|
485
667
|
function formatAgentDetails(agentConfig) {
|
|
486
668
|
if (agentConfig.type === "ollama") {
|
|
487
669
|
return `modèle: ${agentConfig.model}`;
|
|
488
670
|
}
|
|
489
671
|
return `commande: ${agentConfig.command}${agentConfig.model ? ` | modèle: ${agentConfig.model}` : ""}`;
|
|
490
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Renvoie le statut de détection d'un agent sous forme de chaîne lisible.
|
|
675
|
+
* Pour Ollama, vérifie la disponibilité du serveur et la présence du modèle.
|
|
676
|
+
* @param name - Nom de l'agent dans la config.
|
|
677
|
+
* @param agentConfig - Configuration de l'agent.
|
|
678
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
679
|
+
*/
|
|
491
680
|
function formatAgentDetection(name, agentConfig, discovery) {
|
|
492
681
|
if (agentConfig.type === "ollama") {
|
|
493
682
|
if (!discovery.ollama.available) {
|
|
@@ -500,6 +689,13 @@ function formatAgentDetection(name, agentConfig, discovery) {
|
|
|
500
689
|
const detection = cliDetectionForAgent(name, agentConfig, discovery);
|
|
501
690
|
return detection.available ? `détecté (${detection.command})` : "non détecté";
|
|
502
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* Résout l'entrée de détection correspondant à un agent CLI dans le résultat de découverte.
|
|
694
|
+
* Renvoie un objet `{ available: true }` pour les agents CLI non reconnus (considérés disponibles).
|
|
695
|
+
* @param name - Nom de l'agent dans la config.
|
|
696
|
+
* @param agentConfig - Configuration de l'agent.
|
|
697
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
698
|
+
*/
|
|
503
699
|
function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
504
700
|
const command = normalizeCommandName(agentConfig.type === "cli" ? agentConfig.command : name);
|
|
505
701
|
if (command === "codex")
|
|
@@ -512,9 +708,18 @@ function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
|
512
708
|
return discovery.opencode;
|
|
513
709
|
return { available: true, command: agentConfig.type === "cli" ? agentConfig.command : name };
|
|
514
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* Extrait le nom de base d'une commande en supprimant le chemin et l'extension Windows éventuelle.
|
|
713
|
+
* @param command - Chemin ou nom de commande brut (ex. `C:\bin\claude.cmd`).
|
|
714
|
+
*/
|
|
515
715
|
function normalizeCommandName(command) {
|
|
516
716
|
return path.basename(command).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
|
|
517
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Affiche le récapitulatif de détection locale après `palabre init`.
|
|
720
|
+
* @param discovery - Résultat de la découverte locale des outils.
|
|
721
|
+
* @param config - Config générée à partir de la découverte.
|
|
722
|
+
*/
|
|
518
723
|
function printInitDiscovery(discovery, config) {
|
|
519
724
|
console.log("");
|
|
520
725
|
console.log("Détection locale:");
|
|
@@ -526,11 +731,19 @@ function printInitDiscovery(discovery, config) {
|
|
|
526
731
|
console.log("");
|
|
527
732
|
console.log(`Défauts: ${config.defaults?.agentA ?? "codex"} <-> ${config.defaults?.agentB ?? "ollama-local"}`);
|
|
528
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* Formate le statut de détection d'un outil CLI (disponible ou non).
|
|
736
|
+
* @param detection - Résultat de détection d'un outil CLI.
|
|
737
|
+
*/
|
|
529
738
|
function formatCommandDetection(detection) {
|
|
530
739
|
return detection.available
|
|
531
740
|
? `détecté (${detection.command})`
|
|
532
741
|
: "non détecté";
|
|
533
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Formate le statut de détection d'Ollama : commande absente, serveur injoignable ou modèles disponibles.
|
|
745
|
+
* @param detection - Résultat de détection d'Ollama.
|
|
746
|
+
*/
|
|
534
747
|
function formatOllamaDetection(detection) {
|
|
535
748
|
if (!detection.available) {
|
|
536
749
|
return detection.commandAvailable
|
|
@@ -540,54 +753,94 @@ function formatOllamaDetection(detection) {
|
|
|
540
753
|
const modelCount = detection.models.length;
|
|
541
754
|
return `détectée (${modelCount} modèle${modelCount > 1 ? "s" : ""})`;
|
|
542
755
|
}
|
|
756
|
+
/** Affiche le texte d'aide complet sur `stdout`. */
|
|
543
757
|
function printHelp() {
|
|
544
758
|
console.log(`
|
|
545
759
|
PALABRE
|
|
760
|
+
_____________________________________________
|
|
546
761
|
|
|
547
762
|
Usage rapide:
|
|
763
|
+
|
|
764
|
+
palabre init
|
|
765
|
+
Crée une config globale et détecte les agents AI disponibles sur la machine.
|
|
766
|
+
|
|
767
|
+
palabre agents
|
|
768
|
+
Affiche les agents déclarés dans la config.
|
|
769
|
+
|
|
770
|
+
palabre config
|
|
771
|
+
Assistant pour définir ou supprimer les paramètres par défaut.
|
|
772
|
+
|
|
548
773
|
palabre new
|
|
549
774
|
Assistant interactif pour choisir les agents, le sujet et les options.
|
|
550
|
-
|
|
551
|
-
Lance avec les agents par défaut de la config.
|
|
775
|
+
|
|
552
776
|
palabre claude-gemini "Sujet" -t 4
|
|
553
777
|
Lance avec un preset et un sujet positionnel.
|
|
554
778
|
|
|
779
|
+
palabre "Sujet"
|
|
780
|
+
Lance le débat avec paramètres par défaut de la config.
|
|
781
|
+
|
|
782
|
+
_____________________________________________
|
|
783
|
+
|
|
784
|
+
|
|
555
785
|
Commandes:
|
|
786
|
+
|
|
556
787
|
palabre init [--local]
|
|
557
|
-
Crée une config et détecte Codex, Claude, Gemini, OpenCode et Ollama.
|
|
788
|
+
Crée une config locale et détecte Codex, Claude, Gemini, OpenCode et Ollama.
|
|
789
|
+
|
|
558
790
|
palabre agents [--config <path>]
|
|
559
791
|
Liste les agents déclarés dans la config et leur détection locale.
|
|
792
|
+
|
|
793
|
+
palabre presets [--json]
|
|
794
|
+
Liste les presets de paires d'agents. \`--json\` émet la liste structurée
|
|
795
|
+
pour les intégrations (extension VS Code, scripts).
|
|
796
|
+
|
|
560
797
|
palabre config
|
|
561
798
|
Assistant pour définir ou supprimer les paramètres par défaut.
|
|
799
|
+
|
|
562
800
|
palabre config --set-defaults <agentA> <agentB> [-t <n>] [--summary-agent <name>]
|
|
563
801
|
Définit les agents par défaut, et optionnellement les réponses et la synthèse.
|
|
802
|
+
|
|
564
803
|
palabre config -t <n>
|
|
565
804
|
Définit seulement le nombre de réponses par défaut.
|
|
805
|
+
|
|
566
806
|
palabre config --summary-agent <name|none>
|
|
567
807
|
Définit ou retire seulement l'agent de synthèse par défaut.
|
|
808
|
+
|
|
568
809
|
palabre config --clear-defaults
|
|
569
810
|
Supprime les paramètres par défaut.
|
|
811
|
+
|
|
570
812
|
palabre doctor [--config <path>]
|
|
571
813
|
Vérifie la config et les outils locaux.
|
|
814
|
+
|
|
572
815
|
palabre update [--apply]
|
|
573
816
|
Affiche ou exécute les étapes de mise à jour d'un checkout git.
|
|
817
|
+
|
|
574
818
|
palabre help
|
|
575
819
|
Affiche cette aide. Identique à -h ou --help.
|
|
820
|
+
|
|
576
821
|
palabre version
|
|
577
822
|
Affiche la version. Identique à -v ou --version.
|
|
578
823
|
|
|
824
|
+
_____________________________________________
|
|
825
|
+
|
|
826
|
+
|
|
579
827
|
Notation:
|
|
828
|
+
|
|
580
829
|
[option] signifie facultatif. Ne tape pas les crochets.
|
|
581
830
|
<valeur> signifie qu'il faut remplacer ce texte par ta valeur.
|
|
582
831
|
|
|
583
832
|
Options générales:
|
|
833
|
+
|
|
584
834
|
-h, --help Affiche cette aide
|
|
585
835
|
-v, --version Affiche la version
|
|
586
|
-
-a
|
|
836
|
+
-a, --agents Liste les agents. Identique à palabre agents
|
|
587
837
|
--config <path> Chemin vers un fichier de config explicite
|
|
588
838
|
--plain Utilise le rendu console simple sans habillage TUI
|
|
839
|
+
--json Émet un événement NDJSON par ligne sur stdout (alias de --renderer ndjson)
|
|
840
|
+
--renderer <kind> Force le renderer : auto | pretty | plain | ndjson
|
|
589
841
|
|
|
590
842
|
Sujet et lancement:
|
|
843
|
+
|
|
591
844
|
-s, --subject <text> Sujet du débat, option recommandée
|
|
592
845
|
--topic <text> Alias compatible de --subject
|
|
593
846
|
--agent-a <name> Premier agent
|
|
@@ -597,21 +850,25 @@ Sujet et lancement:
|
|
|
597
850
|
--no-early-stop Désactive l'arrêt anticipé si les agents sont clairement d'accord
|
|
598
851
|
|
|
599
852
|
Modèles:
|
|
853
|
+
|
|
600
854
|
--model-a <model> Modèle brut transmis à l'agent A
|
|
601
855
|
--model-b <model> Modèle brut transmis à l'agent B
|
|
602
856
|
--pull-models Autorise Ollama à télécharger un modèle manquant
|
|
603
857
|
|
|
604
858
|
Synthèse:
|
|
859
|
+
|
|
605
860
|
--summary-agent <name> Agent utilisé pour produire la synthèse finale
|
|
606
861
|
--summary-model <model> Modèle brut transmis à l'agent de synthèse
|
|
607
862
|
--no-summary Désactive la synthèse finale
|
|
608
863
|
|
|
609
864
|
Contexte:
|
|
865
|
+
|
|
610
866
|
--files <paths...> Fichiers texte à injecter explicitement dans le contexte
|
|
611
867
|
--context <paths...> Scanne fichiers/dossiers texte en respectant les limites de contexte
|
|
612
868
|
--show-prompt Affiche le prompt du premier tour sans appeler d'agent
|
|
613
869
|
|
|
614
870
|
Configuration:
|
|
871
|
+
|
|
615
872
|
--local Avec init/setup, crée ./palabre.config.json
|
|
616
873
|
--set-defaults <a b> Avec config, définit les agents par défaut
|
|
617
874
|
--summary-agent <name> Avec config, définit l'agent de synthèse par défaut
|
|
@@ -620,10 +877,18 @@ Configuration:
|
|
|
620
877
|
--sync-agents Avec config, ajoute les agents détectés manquants
|
|
621
878
|
|
|
622
879
|
Mise à jour:
|
|
880
|
+
|
|
623
881
|
--apply Avec update, exécute les étapes de mise à jour
|
|
624
882
|
|
|
883
|
+
_____________________________________________
|
|
884
|
+
|
|
885
|
+
|
|
625
886
|
Presets disponibles:
|
|
887
|
+
|
|
626
888
|
${listPresetNames().join(", ")}
|
|
889
|
+
|
|
890
|
+
_____________________________________________
|
|
891
|
+
|
|
627
892
|
`);
|
|
628
893
|
}
|
|
629
894
|
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
|
+
}
|