palabre 0.6.0 → 0.6.1
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 +27 -1
- package/dist/adapters/cli-pty.js +11 -0
- package/dist/adapters/cli.js +21 -0
- package/dist/contextScan.js +48 -0
- package/dist/index.js +36 -3
- package/dist/messages/adapter-errors.js +2 -0
- package/dist/messages/help.js +22 -0
- package/dist/messages/output.js +14 -2
- package/dist/orchestrator.js +72 -16
- package/dist/output.js +20 -3
- package/dist/renderers/console.js +15 -0
- package/dist/renderers/ndjson.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ palabre -s "Compare ces deux approches" -t 2
|
|
|
50
50
|
palabre codex-claude "Relis cette architecture" --context src docs
|
|
51
51
|
palabre claude-ollama "Critique ce fichier" --files README.md
|
|
52
52
|
palabre codex-claude "Preview" --context src --show-prompt
|
|
53
|
+
palabre context scan src docs --json
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
### Agents supportés
|
|
@@ -63,10 +64,22 @@ palabre codex-claude "Preview" --context src --show-prompt
|
|
|
63
64
|
|
|
64
65
|
PALABRE ne liste pas les modèles : ils changent souvent et dépendent de chaque CLI ou compte utilisateur. `--model-a`, `--model-b` et `--summary-model` transmettent simplement la valeur brute à l'agent concerné.
|
|
65
66
|
|
|
67
|
+
### Intégrations
|
|
68
|
+
|
|
69
|
+
PALABRE expose des sorties JSON versionnées pour les clients externes :
|
|
70
|
+
|
|
71
|
+
- `palabre presets --json` pour lire les paires d'agents disponibles ;
|
|
72
|
+
- `palabre context scan --json` pour prévisualiser le contexte que `--context` retiendrait ;
|
|
73
|
+
- `--renderer ndjson` ou `--json` pour suivre un débat événement par événement.
|
|
74
|
+
|
|
75
|
+
Le flux NDJSON v1 est traité comme une API publique d'intégration. Les ajouts compatibles se font sans casser v1 ; les changements cassants doivent changer le champ `v`.
|
|
76
|
+
|
|
66
77
|
### Confidentialité
|
|
67
78
|
|
|
68
79
|
PALABRE tourne localement et n'envoie aucune donnée à un serveur appartenant à PALABRE. Les données envoyées aux agents dépendent des outils que vous utilisez : vérifiez les politiques de confidentialité de Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, OpenCode, Ollama ou de tout autre agent configuré.
|
|
69
80
|
|
|
81
|
+
Si un agent échoue pendant le débat ou la synthèse, PALABRE conserve l'export Markdown partiel avec une section d'interruption quand c'est possible.
|
|
82
|
+
|
|
70
83
|
### Développement local
|
|
71
84
|
|
|
72
85
|
```bash
|
|
@@ -97,7 +110,7 @@ It does not replace your tools: it drives them. You keep your subscriptions, def
|
|
|
97
110
|
- https://palab.re
|
|
98
111
|
- https://palabre.netlify.app
|
|
99
112
|
|
|
100
|
-
Useful pages: [Installation](https://palab.re/
|
|
113
|
+
Useful pages: [Installation](https://palab.re/en/get-started/installation), [Configuration](https://palab.re/en/get-started/configuration), [First debate](https://palab.re/en/get-started/first-debate), [CLI reference](https://palab.re/en/reference/cli), [Troubleshooting](https://palab.re/en/troubleshooting), [Roadmap](https://palab.re/en/roadmap).
|
|
101
114
|
|
|
102
115
|
### Installation
|
|
103
116
|
|
|
@@ -125,6 +138,7 @@ palabre -s "Compare these two approaches" -t 2
|
|
|
125
138
|
palabre codex-claude "Review this architecture" --context src docs
|
|
126
139
|
palabre claude-ollama "Review this file" --files README.md
|
|
127
140
|
palabre codex-claude "Preview" --context src --show-prompt
|
|
141
|
+
palabre context scan src docs --json
|
|
128
142
|
```
|
|
129
143
|
|
|
130
144
|
### Supported Agents
|
|
@@ -138,10 +152,22 @@ palabre codex-claude "Preview" --context src --show-prompt
|
|
|
138
152
|
|
|
139
153
|
PALABRE does not list models: they change often and depend on each CLI or user account. `--model-a`, `--model-b`, and `--summary-model` simply pass the raw value to the selected agent.
|
|
140
154
|
|
|
155
|
+
### Integrations
|
|
156
|
+
|
|
157
|
+
PALABRE exposes versioned JSON outputs for external clients:
|
|
158
|
+
|
|
159
|
+
- `palabre presets --json` to read available agent pairs;
|
|
160
|
+
- `palabre context scan --json` to preview the context `--context` would retain;
|
|
161
|
+
- `--renderer ndjson` or `--json` to follow a debate event by event.
|
|
162
|
+
|
|
163
|
+
The NDJSON v1 stream is treated as a public integration API. Compatible additions do not break v1; breaking changes must change the `v` field.
|
|
164
|
+
|
|
141
165
|
### Privacy
|
|
142
166
|
|
|
143
167
|
PALABRE runs locally and does not send data to a PALABRE-owned server. Data sent to agents depends on the tools you use: check the privacy policies of Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, OpenCode, Ollama, or any custom agent you configure.
|
|
144
168
|
|
|
169
|
+
If an agent fails during the debate or final summary, PALABRE keeps the partial Markdown export with an interruption section whenever possible.
|
|
170
|
+
|
|
145
171
|
### Local Development
|
|
146
172
|
|
|
147
173
|
```bash
|
package/dist/adapters/cli-pty.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { AdapterError } from "../errors.js";
|
|
5
5
|
import { formatAgentPrompt } from "../prompt.js";
|
|
6
6
|
import { cleanTerminalOutput } from "./terminal.js";
|
|
7
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
|
|
7
8
|
/**
|
|
8
9
|
* Adapter pour les CLIs qui exigent un vrai terminal.
|
|
9
10
|
* Contrairement à `CliAdapter`, stdout/stderr sont fusionnés dans le flux PTY.
|
|
@@ -45,11 +46,13 @@ export class CliPtyAdapter {
|
|
|
45
46
|
: baseArgs;
|
|
46
47
|
return new Promise((resolve, reject) => {
|
|
47
48
|
let output = "";
|
|
49
|
+
let outputBytes = 0;
|
|
48
50
|
let settled = false;
|
|
49
51
|
let hardTimer;
|
|
50
52
|
let term;
|
|
51
53
|
let dataSubscription;
|
|
52
54
|
let exitSubscription;
|
|
55
|
+
const maxOutputBytes = this.config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
53
56
|
const finish = (error, exitCode, kill = true) => {
|
|
54
57
|
if (settled)
|
|
55
58
|
return;
|
|
@@ -109,6 +112,14 @@ export class CliPtyAdapter {
|
|
|
109
112
|
}));
|
|
110
113
|
}, this.config.timeoutMs ?? 180_000);
|
|
111
114
|
dataSubscription = term.onData((chunk) => {
|
|
115
|
+
outputBytes += Buffer.byteLength(chunk, "utf8");
|
|
116
|
+
if (outputBytes > maxOutputBytes) {
|
|
117
|
+
finish(new AdapterError("output-too-large", this.name, `${this.name} produced more than ${maxOutputBytes} bytes of PTY output`, {
|
|
118
|
+
maxOutputBytes,
|
|
119
|
+
outputBytes
|
|
120
|
+
}));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
112
123
|
output += chunk;
|
|
113
124
|
});
|
|
114
125
|
exitSubscription = term.onExit(({ exitCode }) => {
|
package/dist/adapters/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { AdapterError } from "../errors.js";
|
|
3
3
|
import { formatAgentPrompt } from "../prompt.js";
|
|
4
4
|
import { cleanTerminalOutput } from "./terminal.js";
|
|
5
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 50 * 1024 * 1024;
|
|
5
6
|
/**
|
|
6
7
|
* Adapter pour les CLIs batch (Codex, Claude, Gemini…).
|
|
7
8
|
* Lance un sous-processus, injecte le prompt via stdin ou argument, capture stdout.
|
|
@@ -50,8 +51,10 @@ export class CliAdapter {
|
|
|
50
51
|
let stdout = "";
|
|
51
52
|
let stderr = "";
|
|
52
53
|
let settled = false;
|
|
54
|
+
let outputBytes = 0;
|
|
53
55
|
let hardTimer;
|
|
54
56
|
let idleTimer;
|
|
57
|
+
const maxOutputBytes = this.config.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
55
58
|
const finish = (error) => {
|
|
56
59
|
if (settled)
|
|
57
60
|
return;
|
|
@@ -94,10 +97,28 @@ export class CliAdapter {
|
|
|
94
97
|
};
|
|
95
98
|
bumpIdleTimer();
|
|
96
99
|
child.stdout.on("data", (chunk) => {
|
|
100
|
+
outputBytes += chunk.length;
|
|
101
|
+
if (outputBytes > maxOutputBytes) {
|
|
102
|
+
child.kill();
|
|
103
|
+
finish(new AdapterError("output-too-large", this.name, `${this.name} produced more than ${maxOutputBytes} bytes of output`, {
|
|
104
|
+
maxOutputBytes,
|
|
105
|
+
outputBytes
|
|
106
|
+
}));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
97
109
|
stdout += chunk.toString("utf8");
|
|
98
110
|
bumpIdleTimer();
|
|
99
111
|
});
|
|
100
112
|
child.stderr.on("data", (chunk) => {
|
|
113
|
+
outputBytes += chunk.length;
|
|
114
|
+
if (outputBytes > maxOutputBytes) {
|
|
115
|
+
child.kill();
|
|
116
|
+
finish(new AdapterError("output-too-large", this.name, `${this.name} produced more than ${maxOutputBytes} bytes of output`, {
|
|
117
|
+
maxOutputBytes,
|
|
118
|
+
outputBytes
|
|
119
|
+
}));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
101
122
|
stderr += chunk.toString("utf8");
|
|
102
123
|
bumpIdleTimer();
|
|
103
124
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { loadProjectInputs } from "./context.js";
|
|
3
|
+
import { createTranslator } from "./i18n.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builds the machine-readable context preview used by integrations.
|
|
6
|
+
*
|
|
7
|
+
* The scan intentionally reuses the same tolerant loader as `--context`, so
|
|
8
|
+
* the returned files are the files Palabre would actually inject into a debate.
|
|
9
|
+
*/
|
|
10
|
+
export async function buildContextScan(scanPaths, cwd = process.cwd(), messages = createTranslator("fr")) {
|
|
11
|
+
const effectiveScanPaths = scanPaths.length > 0 ? scanPaths : ["."];
|
|
12
|
+
const result = await loadProjectInputs([], effectiveScanPaths, cwd, messages);
|
|
13
|
+
const files = result.files.map((file) => ({
|
|
14
|
+
kind: "file",
|
|
15
|
+
path: file.path,
|
|
16
|
+
absolutePath: file.absolutePath,
|
|
17
|
+
sizeBytes: file.sizeBytes
|
|
18
|
+
}));
|
|
19
|
+
const folders = collectContextFolders(files.map((file) => file.path), cwd);
|
|
20
|
+
return {
|
|
21
|
+
v: 1,
|
|
22
|
+
root: cwd,
|
|
23
|
+
scanned: effectiveScanPaths,
|
|
24
|
+
items: [...folders, ...files],
|
|
25
|
+
warnings: result.warnings
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function collectContextFolders(filePaths, cwd) {
|
|
29
|
+
const counts = new Map();
|
|
30
|
+
if (filePaths.length > 0) {
|
|
31
|
+
counts.set(".", filePaths.length);
|
|
32
|
+
}
|
|
33
|
+
for (const filePath of filePaths) {
|
|
34
|
+
const parts = filePath.split("/").filter(Boolean);
|
|
35
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
36
|
+
const folder = parts.slice(0, index).join("/");
|
|
37
|
+
counts.set(folder, (counts.get(folder) ?? 0) + 1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return [...counts.entries()]
|
|
41
|
+
.sort(([left], [right]) => left === "." ? -1 : right === "." ? 1 : left.localeCompare(right))
|
|
42
|
+
.map(([folder, filesCount]) => ({
|
|
43
|
+
kind: "folder",
|
|
44
|
+
path: folder,
|
|
45
|
+
absolutePath: path.resolve(cwd, folder),
|
|
46
|
+
filesCount
|
|
47
|
+
}));
|
|
48
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, writeExampleConfig } from "./config.js";
|
|
6
6
|
import { loadProjectInputs } from "./context.js";
|
|
7
|
+
import { buildContextScan } from "./contextScan.js";
|
|
7
8
|
import { discoverLocalTools } from "./discovery.js";
|
|
8
9
|
import { runDoctor } from "./doctor.js";
|
|
9
10
|
import { AdapterError, formatAdapterError } from "./errors.js";
|
|
@@ -51,6 +52,10 @@ async function main() {
|
|
|
51
52
|
await runPresetsCommand(parsed.flags);
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
55
|
+
if (parsed.command === "context") {
|
|
56
|
+
await runContextCommand(parsed.flags, parsed.positionals);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
54
59
|
if (parsed.command === "update") {
|
|
55
60
|
const info = await getUpdateInfo(await getPackageVersion());
|
|
56
61
|
const updateConfigPath = optionalString(parsed.flags.config) ?? await resolveDefaultConfigPath();
|
|
@@ -168,8 +173,11 @@ async function main() {
|
|
|
168
173
|
const renderer = createRendererFromFlags(parsed.flags, options.plainOutput, messages);
|
|
169
174
|
context.warnings.forEach((warning) => renderer.warning(warning));
|
|
170
175
|
const result = await runDebate(config, options, renderer, messages);
|
|
171
|
-
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages);
|
|
176
|
+
const outputPath = await writeDebateMarkdown(resolveOutputDir(config.outputDir), result.options, result.messages, result.summary, result.stopReason, messages, result.failure);
|
|
172
177
|
renderer.done(outputPath);
|
|
178
|
+
if (result.failure) {
|
|
179
|
+
process.exitCode = 1;
|
|
180
|
+
}
|
|
173
181
|
}
|
|
174
182
|
/**
|
|
175
183
|
* Exécute la commande `agents` : charge la config et affiche les agents déclarés avec leur état de détection.
|
|
@@ -436,6 +444,31 @@ async function runPresetsCommand(flags) {
|
|
|
436
444
|
console.log("");
|
|
437
445
|
console.log(messages.presets.total(presets.length));
|
|
438
446
|
}
|
|
447
|
+
async function runContextCommand(flags, positionals) {
|
|
448
|
+
const language = resolveLanguage({ explicitLanguage: optionalString(flags.language) });
|
|
449
|
+
const messages = createTranslator(language);
|
|
450
|
+
const subcommand = positionals[0] ?? "scan";
|
|
451
|
+
if (subcommand !== "scan") {
|
|
452
|
+
throw new Error(messages.common.unknownCommand(`context ${subcommand}`, "context scan"));
|
|
453
|
+
}
|
|
454
|
+
const paths = positionals.slice(1);
|
|
455
|
+
const result = await buildContextScan(paths, process.cwd(), messages);
|
|
456
|
+
const folders = result.items.filter((item) => item.kind === "folder");
|
|
457
|
+
const files = result.items.filter((item) => item.kind === "file");
|
|
458
|
+
if (flags.json) {
|
|
459
|
+
console.log(JSON.stringify(result, null, 2));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
for (const folder of folders) {
|
|
463
|
+
console.log(`[folder] ${folder.path}`);
|
|
464
|
+
}
|
|
465
|
+
for (const file of files) {
|
|
466
|
+
console.log(`[file] ${file.path} (${file.sizeBytes} bytes)`);
|
|
467
|
+
}
|
|
468
|
+
for (const warning of result.warnings) {
|
|
469
|
+
console.error(`${messages.renderers.warningPrefix} ${warning}`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
439
472
|
/**
|
|
440
473
|
* Parse `process.argv` en une structure typée `ParsedArgs`.
|
|
441
474
|
* Gère les flags courts (-h, -v, -s, -t, -a), les flags longs (--topic, --agent-a…),
|
|
@@ -448,7 +481,7 @@ function parseArgs(args, messages) {
|
|
|
448
481
|
let command = "run";
|
|
449
482
|
let commandExplicit = false;
|
|
450
483
|
const positionals = [];
|
|
451
|
-
const commands = new Set(["run", "new", "init", "setup", "help", "version", "update", "doctor", "config", "agent", "agents", "preset", "presets"]);
|
|
484
|
+
const commands = new Set(["run", "new", "init", "setup", "help", "version", "update", "doctor", "config", "agent", "agents", "preset", "presets", "context"]);
|
|
452
485
|
const presets = new Set(listPresetNames());
|
|
453
486
|
for (let index = 0; index < args.length; index += 1) {
|
|
454
487
|
const value = args[index];
|
|
@@ -545,7 +578,7 @@ function parseArgs(args, messages) {
|
|
|
545
578
|
if (command === "run") {
|
|
546
579
|
applyRunPositionals(positionals, flags, presets, commandExplicit, commands, messages);
|
|
547
580
|
}
|
|
548
|
-
return { command, commandExplicit, flags };
|
|
581
|
+
return { command, commandExplicit, positionals, flags };
|
|
549
582
|
}
|
|
550
583
|
/**
|
|
551
584
|
* Détecte si une valeur ressemble à une faute de frappe d'une commande connue
|
|
@@ -3,6 +3,7 @@ const frHints = {
|
|
|
3
3
|
"spawn-failed": "Sur Windows, essaye le wrapper .cmd ou active \"shell\": true dans la config agent.",
|
|
4
4
|
timeout: "Augmente timeoutMs ou teste la commande directement dans le terminal.",
|
|
5
5
|
"idle-timeout": "Desactive idleTimeoutMs pour les CLIs IA qui restent silencieuses pendant la generation.",
|
|
6
|
+
"output-too-large": "Reduis le contexte, le nombre de tours ou configure maxOutputBytes pour cet agent si ce volume est attendu.",
|
|
6
7
|
"empty-output": "Teste la commande en dehors de Palabre et verifie que le prompt est bien lu via stdin ou argument.",
|
|
7
8
|
"usage-limit": "Attends la fenetre indiquee par la CLI, change de modele ou relance avec un autre agent/preset disponible.",
|
|
8
9
|
"non-zero-exit": "Teste la commande directement, puis ajuste args, permissions, modele ou authentification de la CLI.",
|
|
@@ -16,6 +17,7 @@ const enHints = {
|
|
|
16
17
|
"spawn-failed": "On Windows, try the .cmd wrapper or enable \"shell\": true in the agent config.",
|
|
17
18
|
timeout: "Increase timeoutMs or test the command directly in the terminal.",
|
|
18
19
|
"idle-timeout": "Disable idleTimeoutMs for AI CLIs that stay silent while generating.",
|
|
20
|
+
"output-too-large": "Reduce context, turn count, or configure maxOutputBytes for this agent if this volume is expected.",
|
|
19
21
|
"empty-output": "Test the command outside Palabre and check that the prompt is read through stdin or an argument.",
|
|
20
22
|
"usage-limit": "Wait for the window indicated by the CLI, change model, or run again with another available agent/preset.",
|
|
21
23
|
"non-zero-exit": "Test the command directly, then adjust args, permissions, model, or CLI authentication.",
|
package/dist/messages/help.js
CHANGED
|
@@ -29,6 +29,16 @@ Usage:
|
|
|
29
29
|
Flags:
|
|
30
30
|
--json sortie structuree pour integrations
|
|
31
31
|
--config <path> chemin de config explicite
|
|
32
|
+
`,
|
|
33
|
+
context: `
|
|
34
|
+
Scanne le contexte projet avec les memes regles que --context.
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
palabre context scan [paths...] [flags]
|
|
38
|
+
|
|
39
|
+
Flags:
|
|
40
|
+
--json sortie structuree pour integrations
|
|
41
|
+
--language <fr|en> force la langue des avertissements
|
|
32
42
|
`,
|
|
33
43
|
config: `
|
|
34
44
|
Configure les agents par defaut, la synthese, le nombre de reponses et la langue.
|
|
@@ -124,6 +134,16 @@ Usage:
|
|
|
124
134
|
Flags:
|
|
125
135
|
--json structured output for integrations
|
|
126
136
|
--config <path> explicit config path
|
|
137
|
+
`,
|
|
138
|
+
context: `
|
|
139
|
+
Scans project context with the same rules as --context.
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
palabre context scan [paths...] [flags]
|
|
143
|
+
|
|
144
|
+
Flags:
|
|
145
|
+
--json structured output for integrations
|
|
146
|
+
--language <fr|en> forces warning language
|
|
127
147
|
`,
|
|
128
148
|
config: `
|
|
129
149
|
Configures default agents, summary, response count, and language.
|
|
@@ -212,6 +232,7 @@ Commandes:
|
|
|
212
232
|
new Assistant interactif de debat
|
|
213
233
|
agents Lister les agents configures
|
|
214
234
|
presets Lister les presets disponibles
|
|
235
|
+
context Scanner le contexte projet
|
|
215
236
|
config Modifier les parametres par defaut
|
|
216
237
|
doctor Verifier la config et les outils locaux
|
|
217
238
|
update Afficher ou appliquer les etapes de mise a jour
|
|
@@ -256,6 +277,7 @@ Commands:
|
|
|
256
277
|
new Interactive debate assistant
|
|
257
278
|
agents List configured agents
|
|
258
279
|
presets List available presets
|
|
280
|
+
context Scan project context
|
|
259
281
|
config Edit default settings
|
|
260
282
|
doctor Check config and local tools
|
|
261
283
|
update Show or apply update steps
|
package/dist/messages/output.js
CHANGED
|
@@ -3,6 +3,7 @@ export const outputMessages = {
|
|
|
3
3
|
title: "# PALABRE Debate",
|
|
4
4
|
contextTitle: "## Contexte",
|
|
5
5
|
exchangesTitle: "## Echanges",
|
|
6
|
+
failureTitle: "## Interruption",
|
|
6
7
|
finalSummaryTitle: "## Synthese finale",
|
|
7
8
|
tableField: "Champ",
|
|
8
9
|
tableValue: "Valeur",
|
|
@@ -27,13 +28,19 @@ export const outputMessages = {
|
|
|
27
28
|
sessionStartedAt: "Session demarree a",
|
|
28
29
|
agent: "Agent",
|
|
29
30
|
role: "Role",
|
|
30
|
-
date: "Date"
|
|
31
|
+
date: "Date",
|
|
32
|
+
failurePhase: "Phase",
|
|
33
|
+
failureAgent: "Agent",
|
|
34
|
+
failureTurn: "Tour",
|
|
35
|
+
failureKind: "Type d'erreur",
|
|
36
|
+
failureMessage: "Message"
|
|
31
37
|
}
|
|
32
38
|
},
|
|
33
39
|
en: {
|
|
34
40
|
title: "# PALABRE Debate",
|
|
35
41
|
contextTitle: "## Context",
|
|
36
42
|
exchangesTitle: "## Exchanges",
|
|
43
|
+
failureTitle: "## Interruption",
|
|
37
44
|
finalSummaryTitle: "## Final summary",
|
|
38
45
|
tableField: "Field",
|
|
39
46
|
tableValue: "Value",
|
|
@@ -58,7 +65,12 @@ export const outputMessages = {
|
|
|
58
65
|
sessionStartedAt: "Session started at",
|
|
59
66
|
agent: "Agent",
|
|
60
67
|
role: "Role",
|
|
61
|
-
date: "Date"
|
|
68
|
+
date: "Date",
|
|
69
|
+
failurePhase: "Phase",
|
|
70
|
+
failureAgent: "Agent",
|
|
71
|
+
failureTurn: "Turn",
|
|
72
|
+
failureKind: "Error kind",
|
|
73
|
+
failureMessage: "Message"
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
};
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createAgent } from "./adapters/index.js";
|
|
2
|
+
import { AdapterError } from "./errors.js";
|
|
2
3
|
import { createTranslator } from "./i18n.js";
|
|
3
4
|
/**
|
|
4
5
|
* Point d'entrée de l'orchestration.
|
|
@@ -36,18 +37,39 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
36
37
|
const turn = index + 1;
|
|
37
38
|
renderer?.turnStart(turn, options.turns, current.name, current.role);
|
|
38
39
|
renderer?.thinkingStart(current.name, current.role);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await current.generate({
|
|
43
|
+
topic: options.topic,
|
|
44
|
+
turn,
|
|
45
|
+
totalTurns: options.turns,
|
|
46
|
+
selfName: current.name,
|
|
47
|
+
peerName: peer.name,
|
|
48
|
+
selfRole: current.role,
|
|
49
|
+
language: options.language,
|
|
50
|
+
session: options.session,
|
|
51
|
+
files: options.files,
|
|
52
|
+
transcript
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const failure = toDebateFailure(error, {
|
|
57
|
+
phase: "debate",
|
|
58
|
+
agent: current.name,
|
|
59
|
+
role: current.role,
|
|
60
|
+
turn
|
|
61
|
+
});
|
|
62
|
+
renderer?.error(failure);
|
|
63
|
+
return {
|
|
64
|
+
options,
|
|
65
|
+
messages: transcript,
|
|
66
|
+
stopReason,
|
|
67
|
+
failure
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
renderer?.thinkingEnd();
|
|
72
|
+
}
|
|
51
73
|
const message = {
|
|
52
74
|
agent: current.name,
|
|
53
75
|
role: current.role,
|
|
@@ -62,14 +84,27 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
62
84
|
break;
|
|
63
85
|
}
|
|
64
86
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
let summary;
|
|
88
|
+
let failure;
|
|
89
|
+
if (options.summaryEnabled) {
|
|
90
|
+
try {
|
|
91
|
+
summary = await generateSummary(config, options, transcript, renderer, messages);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
failure = toDebateFailure(error, {
|
|
95
|
+
phase: "summary",
|
|
96
|
+
agent: options.summaryAgent ?? options.agentB,
|
|
97
|
+
turn: transcript.length + 1
|
|
98
|
+
});
|
|
99
|
+
renderer?.error(failure);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
68
102
|
return {
|
|
69
103
|
options,
|
|
70
104
|
messages: transcript,
|
|
71
105
|
summary,
|
|
72
|
-
stopReason
|
|
106
|
+
stopReason,
|
|
107
|
+
failure
|
|
73
108
|
};
|
|
74
109
|
}
|
|
75
110
|
/**
|
|
@@ -164,6 +199,27 @@ async function generateSummary(config, options, transcript, renderer, messages =
|
|
|
164
199
|
renderer?.message(summary.content);
|
|
165
200
|
return summary;
|
|
166
201
|
}
|
|
202
|
+
function toDebateFailure(error, context) {
|
|
203
|
+
if (error instanceof AdapterError) {
|
|
204
|
+
return {
|
|
205
|
+
phase: context.phase,
|
|
206
|
+
agent: context.agent ?? error.adapterName,
|
|
207
|
+
role: context.role,
|
|
208
|
+
turn: context.turn,
|
|
209
|
+
kind: error.kind,
|
|
210
|
+
message: error.message,
|
|
211
|
+
details: error.details
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
phase: context.phase,
|
|
216
|
+
agent: context.agent,
|
|
217
|
+
role: context.role,
|
|
218
|
+
turn: context.turn,
|
|
219
|
+
kind: "unknown",
|
|
220
|
+
message: error instanceof Error ? error.message : String(error)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
167
223
|
/** Résout le model override pour un agent donné. Retourne `undefined` si l'agent n'est ni A ni B. */
|
|
168
224
|
function modelForAgent(options, agent) {
|
|
169
225
|
if (agent === options.agentA) {
|
package/dist/output.js
CHANGED
|
@@ -5,12 +5,12 @@ import { createTranslator } from "./i18n.js";
|
|
|
5
5
|
* Écrit le débat au format Markdown dans `outputDir`.
|
|
6
6
|
* Crée le répertoire si absent. Retourne le chemin absolu du fichier créé.
|
|
7
7
|
*/
|
|
8
|
-
export async function writeDebateMarkdown(outputDir, options, debateMessages, summary, stopReason, messages = createTranslator("fr")) {
|
|
8
|
+
export async function writeDebateMarkdown(outputDir, options, debateMessages, summary, stopReason, messages = createTranslator("fr"), failure) {
|
|
9
9
|
const safeDate = new Date().toISOString().replace(/[:.]/g, "-");
|
|
10
10
|
const fileName = `palabre-${slugifyTopic(options.topic)}-${safeDate}.debate.md`;
|
|
11
11
|
const filePath = path.resolve(outputDir, fileName);
|
|
12
12
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
13
|
-
await writeFile(filePath, renderDebateMarkdown(options, debateMessages, summary, stopReason, messages), "utf8");
|
|
13
|
+
await writeFile(filePath, renderDebateMarkdown(options, debateMessages, summary, stopReason, messages, failure), "utf8");
|
|
14
14
|
return filePath;
|
|
15
15
|
}
|
|
16
16
|
function slugifyTopic(topic) {
|
|
@@ -28,7 +28,7 @@ function slugifyTopic(topic) {
|
|
|
28
28
|
* Produit la représentation Markdown complète du débat.
|
|
29
29
|
* Fonction pure : aucun effet de bord sur le filesystem.
|
|
30
30
|
*/
|
|
31
|
-
export function renderDebateMarkdown(options, debateMessages, summary, stopReason, messages = createTranslator("fr")) {
|
|
31
|
+
export function renderDebateMarkdown(options, debateMessages, summary, stopReason, messages = createTranslator("fr"), failure) {
|
|
32
32
|
const lines = [
|
|
33
33
|
messages.output.title,
|
|
34
34
|
"",
|
|
@@ -44,6 +44,9 @@ export function renderDebateMarkdown(options, debateMessages, summary, stopReaso
|
|
|
44
44
|
for (const message of debateMessages) {
|
|
45
45
|
lines.push(`### ${message.agent} (${message.role})`, "", normalizeMarkdownForWindowsPreview(message.content.trim()), "");
|
|
46
46
|
}
|
|
47
|
+
if (failure) {
|
|
48
|
+
lines.push("---", "", messages.output.failureTitle, "", ...renderFailureBlock(failure, messages), "");
|
|
49
|
+
}
|
|
47
50
|
lines.push("---", "", messages.output.finalSummaryTitle, "", ...renderSummaryBlock(options, summary, messages));
|
|
48
51
|
return `${lines.join("\n")}\n`;
|
|
49
52
|
}
|
|
@@ -67,6 +70,20 @@ function renderSummaryBlock(options, summary, messages) {
|
|
|
67
70
|
""
|
|
68
71
|
];
|
|
69
72
|
}
|
|
73
|
+
function renderFailureBlock(failure, messages) {
|
|
74
|
+
const rows = [
|
|
75
|
+
[messages.output.fields.failurePhase, failure.phase],
|
|
76
|
+
[messages.output.fields.failureAgent, failure.agent ?? messages.output.no],
|
|
77
|
+
[messages.output.fields.failureTurn, failure.turn === undefined ? messages.output.no : String(failure.turn)],
|
|
78
|
+
[messages.output.fields.failureKind, failure.kind],
|
|
79
|
+
[messages.output.fields.failureMessage, failure.message]
|
|
80
|
+
];
|
|
81
|
+
return [
|
|
82
|
+
`| ${messages.output.tableField} | ${messages.output.tableValue} |`,
|
|
83
|
+
"| --- | --- |",
|
|
84
|
+
...rows.map(([label, value]) => `| ${escapeTableCell(label)} | ${escapeTableCell(value)} |`)
|
|
85
|
+
];
|
|
86
|
+
}
|
|
70
87
|
function normalizeMarkdownForWindowsPreview(content) {
|
|
71
88
|
return content.replace(/:\*\*/g, ":**");
|
|
72
89
|
}
|
|
@@ -99,6 +99,10 @@ class PrettyConsoleRenderer {
|
|
|
99
99
|
""
|
|
100
100
|
].join("\n"));
|
|
101
101
|
}
|
|
102
|
+
error(failure) {
|
|
103
|
+
this.thinkingEnd();
|
|
104
|
+
process.stderr.write(`\n${this.c("red", this.messages.common.errorPrefix)} ${formatFailureLocation(failure, this.messages)}: ${failure.message}\n`);
|
|
105
|
+
}
|
|
102
106
|
/** Affiche le chemin du fichier de sortie en vert à la fin du débat. */
|
|
103
107
|
done(outputPath) {
|
|
104
108
|
process.stdout.write(`\n\n${this.c("green", this.messages.renderers.exported(outputPath))}\n\n`);
|
|
@@ -175,6 +179,9 @@ class PlainConsoleRenderer {
|
|
|
175
179
|
summaryStart(agent, role) {
|
|
176
180
|
process.stdout.write(`\n[${this.messages.renderers.summaryTitle}] ${agent} (${role})...\n`);
|
|
177
181
|
}
|
|
182
|
+
error(failure) {
|
|
183
|
+
process.stderr.write(`\n${this.messages.common.errorPrefix}: ${formatFailureLocation(failure, this.messages)}: ${failure.message}\n`);
|
|
184
|
+
}
|
|
178
185
|
/** Affiche le chemin du fichier de sortie à la fin du débat. */
|
|
179
186
|
done(outputPath) {
|
|
180
187
|
process.stdout.write(`\n${this.messages.renderers.exported(outputPath)}\n`);
|
|
@@ -220,6 +227,13 @@ function formatContext(options, messages) {
|
|
|
220
227
|
}
|
|
221
228
|
return messages.renderers.injectedFiles(count);
|
|
222
229
|
}
|
|
230
|
+
function formatFailureLocation(failure, messages) {
|
|
231
|
+
if (failure.phase === "summary") {
|
|
232
|
+
return messages.renderers.summaryTitle;
|
|
233
|
+
}
|
|
234
|
+
const turn = failure.turn === undefined ? "" : `, turn ${failure.turn}`;
|
|
235
|
+
return `${failure.agent ?? "?"} (${failure.role ?? "?"}${turn})`;
|
|
236
|
+
}
|
|
223
237
|
/** Codes d'échappement ANSI utilisés par `PrettyConsoleRenderer`. */
|
|
224
238
|
const codes = {
|
|
225
239
|
reset: "\u001b[0m",
|
|
@@ -228,6 +242,7 @@ const codes = {
|
|
|
228
242
|
cyan: "\u001b[36m",
|
|
229
243
|
green: "\u001b[32m",
|
|
230
244
|
magenta: "\u001b[35m",
|
|
245
|
+
red: "\u001b[31m",
|
|
231
246
|
yellow: "\u001b[33m",
|
|
232
247
|
orange: "\u001b[38;5;208m",
|
|
233
248
|
pink: "\u001b[38;5;205m"
|
package/dist/renderers/ndjson.js
CHANGED
|
@@ -101,6 +101,10 @@ export class NdjsonRenderer {
|
|
|
101
101
|
this.currentRole = role;
|
|
102
102
|
this.emit({ type: "summary-start", agent, role });
|
|
103
103
|
}
|
|
104
|
+
/** Émet une erreur runtime structurée. */
|
|
105
|
+
error(failure) {
|
|
106
|
+
this.emit({ type: "error", ...failure });
|
|
107
|
+
}
|
|
104
108
|
/** Émet `done` avec le chemin du `.debate.md` écrit. */
|
|
105
109
|
done(outputPath) {
|
|
106
110
|
this.emit({ type: "done", outputPath });
|