palabre 0.3.0 → 0.6.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/adapters/cli-pty.js +183 -0
- package/dist/adapters/cli.js +6 -6
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/terminal.js +13 -0
- package/dist/config.js +55 -8
- package/dist/configWizard.js +45 -40
- package/dist/context.js +16 -14
- package/dist/discovery.js +3 -1
- package/dist/doctor.js +147 -137
- package/dist/errors.js +4 -31
- package/dist/i18n.js +30 -0
- package/dist/index.js +275 -258
- package/dist/limits.js +11 -10
- package/dist/messages/adapter-errors.js +36 -0
- package/dist/messages/agents.js +38 -0
- package/dist/messages/common.js +28 -0
- package/dist/messages/config.js +88 -0
- package/dist/messages/context.js +24 -0
- package/dist/messages/doctor.js +126 -0
- package/dist/messages/help.js +280 -0
- package/dist/messages/index.js +38 -0
- package/dist/messages/init.js +30 -0
- package/dist/messages/limits.js +12 -0
- package/dist/messages/new.js +66 -0
- package/dist/messages/orchestrator.js +14 -0
- package/dist/messages/output.js +64 -0
- package/dist/messages/presets.js +26 -0
- package/dist/messages/preview.js +22 -0
- package/dist/messages/prompt.js +102 -0
- package/dist/messages/renderers.js +38 -0
- package/dist/messages/update.js +40 -0
- package/dist/new.js +46 -42
- package/dist/orchestrator.js +23 -18
- package/dist/output.js +34 -33
- package/dist/presets.js +122 -2
- package/dist/prompt.js +43 -58
- package/dist/renderers/console.js +33 -27
- package/dist/update.js +10 -21
- package/package.json +4 -1
- package/palabre.config.example.json +1 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const rendererMessages = {
|
|
2
|
+
fr: {
|
|
3
|
+
subject: (topic) => `Sujet: ${topic}`,
|
|
4
|
+
agents: (pair) => `Agents: ${pair}`,
|
|
5
|
+
responsesSummaryContext: (turns, summary, context) => `Réponses: ${turns} | Synthèse: ${summary} | Contexte: ${context}`,
|
|
6
|
+
responsesSummary: (turns, summary) => `Réponses: ${turns} | Synthèse: ${summary}`,
|
|
7
|
+
context: (context) => `Contexte: ${context}`,
|
|
8
|
+
options: (earlyStop, pullModels) => `Options: arrêt anticipé ${earlyStop ? "activé" : "désactivé"}, auto-pull Ollama ${pullModels ? "activé" : "désactivé"}`,
|
|
9
|
+
enabled: "activé",
|
|
10
|
+
disabled: "désactivée",
|
|
11
|
+
warningPrefix: "Warning:",
|
|
12
|
+
infoPrefix: "Info:",
|
|
13
|
+
turn: (turn, totalTurns) => `tour ${turn}/${totalTurns}`,
|
|
14
|
+
thinking: (agent, role) => `${agent} (${role}) reflechit`,
|
|
15
|
+
summaryTitle: "Synthese",
|
|
16
|
+
exported: (path) => `Debat exporte: ${path}`,
|
|
17
|
+
noInjectedFiles: "aucun fichier injecté",
|
|
18
|
+
injectedFiles: (count) => `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""}`
|
|
19
|
+
},
|
|
20
|
+
en: {
|
|
21
|
+
subject: (topic) => `Subject: ${topic}`,
|
|
22
|
+
agents: (pair) => `Agents: ${pair}`,
|
|
23
|
+
responsesSummaryContext: (turns, summary, context) => `Responses: ${turns} | Summary: ${summary} | Context: ${context}`,
|
|
24
|
+
responsesSummary: (turns, summary) => `Responses: ${turns} | Summary: ${summary}`,
|
|
25
|
+
context: (context) => `Context: ${context}`,
|
|
26
|
+
options: (earlyStop, pullModels) => `Options: early stop ${earlyStop ? "enabled" : "disabled"}, Ollama auto-pull ${pullModels ? "enabled" : "disabled"}`,
|
|
27
|
+
enabled: "enabled",
|
|
28
|
+
disabled: "disabled",
|
|
29
|
+
warningPrefix: "Warning:",
|
|
30
|
+
infoPrefix: "Info:",
|
|
31
|
+
turn: (turn, totalTurns) => `turn ${turn}/${totalTurns}`,
|
|
32
|
+
thinking: (agent, role) => `${agent} (${role}) is thinking`,
|
|
33
|
+
summaryTitle: "Summary",
|
|
34
|
+
exported: (path) => `Debate exported: ${path}`,
|
|
35
|
+
noInjectedFiles: "no injected files",
|
|
36
|
+
injectedFiles: (count) => `${count} injected file${count > 1 ? "s" : ""}`
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const updateMessages = {
|
|
2
|
+
fr: {
|
|
3
|
+
upToDate: "PALABRE est a jour.",
|
|
4
|
+
automaticSourceOnly: "Mise a jour automatique disponible seulement depuis un checkout git. Utilise pnpm add --global palabre@latest.",
|
|
5
|
+
stepFailed: (command, args, exitCode) => `${command} ${args} a echoue avec le code ${exitCode}.`,
|
|
6
|
+
instructions: (options) => {
|
|
7
|
+
const lines = [
|
|
8
|
+
`PALABRE ${options.version}`,
|
|
9
|
+
"",
|
|
10
|
+
"Mise a jour recommandee:"
|
|
11
|
+
];
|
|
12
|
+
if (options.sourceCheckout) {
|
|
13
|
+
lines.push("", "Installation depuis le repo source detectee.", "", ` cd "${options.projectRoot}"`, " git pull --ff-only", " pnpm install", " pnpm build", " pnpm link --global", "", "Pour executer ces etapes automatiquement:", "", " palabre update --apply");
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
lines.push("", "Installation package detectee.", "", " pnpm add --global palabre@latest", "", "Si tu utilises npm:", "", " npm install --global palabre@latest");
|
|
17
|
+
}
|
|
18
|
+
return lines.join("\n");
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
en: {
|
|
22
|
+
upToDate: "PALABRE is up to date.",
|
|
23
|
+
automaticSourceOnly: "Automatic update is only available from a git checkout. Use pnpm add --global palabre@latest.",
|
|
24
|
+
stepFailed: (command, args, exitCode) => `${command} ${args} failed with exit code ${exitCode}.`,
|
|
25
|
+
instructions: (options) => {
|
|
26
|
+
const lines = [
|
|
27
|
+
`PALABRE ${options.version}`,
|
|
28
|
+
"",
|
|
29
|
+
"Recommended update:"
|
|
30
|
+
];
|
|
31
|
+
if (options.sourceCheckout) {
|
|
32
|
+
lines.push("", "Source repository installation detected.", "", ` cd "${options.projectRoot}"`, " git pull --ff-only", " pnpm install", " pnpm build", " pnpm link --global", "", "To run these steps automatically:", "", " palabre update --apply");
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push("", "Package installation detected.", "", " pnpm add --global palabre@latest", "", "If you use npm:", "", " npm install --global palabre@latest");
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
package/dist/new.js
CHANGED
|
@@ -8,30 +8,30 @@ import { MAX_TURNS, turnsOrDefault, validateTurns } from "./limits.js";
|
|
|
8
8
|
* Détecte les outils locaux, liste les agents de la config et guide la composition du débat.
|
|
9
9
|
* Retourne `undefined` si l'utilisateur annule (q/quit/exit ou Ctrl+C).
|
|
10
10
|
*/
|
|
11
|
-
export async function runNewWizard(config) {
|
|
11
|
+
export async function runNewWizard(config, messages) {
|
|
12
12
|
const discovery = await discoverLocalTools();
|
|
13
|
-
const choices = buildAgentChoices(config, discovery);
|
|
13
|
+
const choices = buildAgentChoices(config, discovery, messages);
|
|
14
14
|
if (choices.length < 2) {
|
|
15
|
-
throw new Error(
|
|
15
|
+
throw new Error(messages.new.needsTwoAgents);
|
|
16
16
|
}
|
|
17
17
|
const rl = await createQuestioner();
|
|
18
18
|
try {
|
|
19
|
-
console.log(
|
|
20
|
-
console.log(
|
|
21
|
-
console.log(
|
|
19
|
+
console.log(messages.new.title);
|
|
20
|
+
console.log(messages.new.quitHint);
|
|
21
|
+
console.log(messages.new.defaultHint);
|
|
22
22
|
console.log("");
|
|
23
|
-
const agentA = await askAgent(rl, choices,
|
|
23
|
+
const agentA = await askAgent(rl, choices, messages.new.agentA, config.defaults?.agentA, messages);
|
|
24
24
|
if (!agentA)
|
|
25
25
|
return undefined;
|
|
26
|
-
const agentB = await askAgent(rl, choices.filter((choice) => choice.name !== agentA),
|
|
26
|
+
const agentB = await askAgent(rl, choices.filter((choice) => choice.name !== agentA), messages.new.agentB, config.defaults?.agentB === agentA ? undefined : config.defaults?.agentB, messages);
|
|
27
27
|
if (!agentB)
|
|
28
28
|
return undefined;
|
|
29
|
-
const topic = await askRequiredText(rl,
|
|
29
|
+
const topic = await askRequiredText(rl, messages.new.topic, messages);
|
|
30
30
|
if (!topic)
|
|
31
31
|
return undefined;
|
|
32
|
-
printCommandPreview({ agentA, agentB, topic, turns: turnsOrDefault(config.defaults?.turns) });
|
|
33
|
-
console.log(
|
|
34
|
-
const launchMinimal = await askYesNo(rl,
|
|
32
|
+
printCommandPreview({ agentA, agentB, topic, turns: turnsOrDefault(config.defaults?.turns) }, messages);
|
|
33
|
+
console.log(messages.new.advancedHint);
|
|
34
|
+
const launchMinimal = await askYesNo(rl, messages.new.launchMinimal, true, messages);
|
|
35
35
|
if (launchMinimal === undefined)
|
|
36
36
|
return undefined;
|
|
37
37
|
if (launchMinimal) {
|
|
@@ -45,34 +45,34 @@ export async function runNewWizard(config) {
|
|
|
45
45
|
plainOutput: false
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
const turns = await askNumber(rl,
|
|
48
|
+
const turns = await askNumber(rl, messages.new.turns, turnsOrDefault(config.defaults?.turns), messages);
|
|
49
49
|
if (turns === undefined)
|
|
50
50
|
return undefined;
|
|
51
|
-
const modelA = await askOptionalText(rl,
|
|
51
|
+
const modelA = await askOptionalText(rl, messages.new.modelFor(agentA));
|
|
52
52
|
if (modelA === undefined)
|
|
53
53
|
return undefined;
|
|
54
|
-
const modelB = await askOptionalText(rl,
|
|
54
|
+
const modelB = await askOptionalText(rl, messages.new.modelFor(agentB));
|
|
55
55
|
if (modelB === undefined)
|
|
56
56
|
return undefined;
|
|
57
|
-
const summaryEnabled = await askYesNo(rl,
|
|
57
|
+
const summaryEnabled = await askYesNo(rl, messages.new.summaryEnabled, true, messages);
|
|
58
58
|
if (summaryEnabled === undefined)
|
|
59
59
|
return undefined;
|
|
60
60
|
let summaryAgent;
|
|
61
61
|
let summaryModel;
|
|
62
62
|
if (summaryEnabled) {
|
|
63
|
-
summaryAgent = await askAgent(rl, choices,
|
|
63
|
+
summaryAgent = await askAgent(rl, choices, messages.new.summaryAgent, config.defaults?.summaryAgent ?? agentB, messages);
|
|
64
64
|
if (!summaryAgent)
|
|
65
65
|
return undefined;
|
|
66
|
-
summaryModel = await askOptionalText(rl,
|
|
66
|
+
summaryModel = await askOptionalText(rl, messages.new.summaryModelFor(summaryAgent));
|
|
67
67
|
if (summaryModel === undefined)
|
|
68
68
|
return undefined;
|
|
69
69
|
}
|
|
70
|
-
const context = splitPaths(await askOptionalText(rl,
|
|
71
|
-
const files = splitPaths(await askOptionalText(rl,
|
|
72
|
-
const showPrompt = await askYesNo(rl,
|
|
70
|
+
const context = splitPaths(await askOptionalText(rl, messages.new.contextPaths));
|
|
71
|
+
const files = splitPaths(await askOptionalText(rl, messages.new.filesPaths));
|
|
72
|
+
const showPrompt = await askYesNo(rl, messages.new.showPrompt, false, messages);
|
|
73
73
|
if (showPrompt === undefined)
|
|
74
74
|
return undefined;
|
|
75
|
-
const plainOutput = await askYesNo(rl,
|
|
75
|
+
const plainOutput = await askYesNo(rl, messages.new.plainOutput, false, messages);
|
|
76
76
|
if (plainOutput === undefined)
|
|
77
77
|
return undefined;
|
|
78
78
|
const selection = {
|
|
@@ -90,7 +90,7 @@ export async function runNewWizard(config) {
|
|
|
90
90
|
showPrompt,
|
|
91
91
|
plainOutput
|
|
92
92
|
};
|
|
93
|
-
printCommandPreview(selection);
|
|
93
|
+
printCommandPreview(selection, messages);
|
|
94
94
|
return selection;
|
|
95
95
|
}
|
|
96
96
|
finally {
|
|
@@ -123,7 +123,7 @@ async function readPipedLines() {
|
|
|
123
123
|
}
|
|
124
124
|
return raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
125
125
|
}
|
|
126
|
-
function buildAgentChoices(config, discovery) {
|
|
126
|
+
function buildAgentChoices(config, discovery, messages) {
|
|
127
127
|
return Object.entries(config.agents)
|
|
128
128
|
.map(([name, agentConfig]) => {
|
|
129
129
|
const detected = isAgentDetected(name, agentConfig, discovery);
|
|
@@ -131,7 +131,7 @@ function buildAgentChoices(config, discovery) {
|
|
|
131
131
|
name,
|
|
132
132
|
config: agentConfig,
|
|
133
133
|
detected,
|
|
134
|
-
status: agentStatus(name, agentConfig, discovery, detected)
|
|
134
|
+
status: agentStatus(name, agentConfig, discovery, detected, messages)
|
|
135
135
|
};
|
|
136
136
|
})
|
|
137
137
|
.sort((left, right) => Number(right.detected) - Number(left.detected) || left.name.localeCompare(right.name));
|
|
@@ -147,21 +147,25 @@ function isAgentDetected(name, config, discovery) {
|
|
|
147
147
|
return discovery.claude.available;
|
|
148
148
|
if (normalized === "gemini")
|
|
149
149
|
return discovery.gemini.available;
|
|
150
|
+
if (normalized === "agy")
|
|
151
|
+
return discovery.antigravity.available;
|
|
152
|
+
if (normalized === "antigravity")
|
|
153
|
+
return discovery.antigravity.available;
|
|
150
154
|
if (normalized === "opencode")
|
|
151
155
|
return discovery.opencode.available;
|
|
152
156
|
return true;
|
|
153
157
|
}
|
|
154
|
-
function agentStatus(_name, config, discovery, detected) {
|
|
158
|
+
function agentStatus(_name, config, discovery, detected, messages) {
|
|
155
159
|
if (config.type === "ollama") {
|
|
156
160
|
return detected
|
|
157
|
-
?
|
|
158
|
-
:
|
|
161
|
+
? messages.new.detectedOllama(config.role, discovery.ollama.models.length)
|
|
162
|
+
: messages.new.ollamaUnreachable(config.role);
|
|
159
163
|
}
|
|
160
164
|
return detected
|
|
161
|
-
?
|
|
162
|
-
:
|
|
165
|
+
? messages.new.detectedCli(config.role)
|
|
166
|
+
: messages.new.missingCli(config.role);
|
|
163
167
|
}
|
|
164
|
-
async function askAgent(rl, choices, label, defaultName) {
|
|
168
|
+
async function askAgent(rl, choices, label, defaultName, messages) {
|
|
165
169
|
const fallback = choices.find((choice) => choice.name === defaultName)?.name ?? choices[0]?.name;
|
|
166
170
|
console.log(label);
|
|
167
171
|
choices.forEach((choice, index) => {
|
|
@@ -182,10 +186,10 @@ async function askAgent(rl, choices, label, defaultName) {
|
|
|
182
186
|
if (choices.some((choice) => choice.name === value)) {
|
|
183
187
|
return value;
|
|
184
188
|
}
|
|
185
|
-
console.log(
|
|
189
|
+
console.log(messages.new.invalidAgentChoice);
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
|
-
async function askRequiredText(rl, label) {
|
|
192
|
+
async function askRequiredText(rl, label, messages) {
|
|
189
193
|
while (true) {
|
|
190
194
|
const answer = await rl.question(`${label}: `);
|
|
191
195
|
const value = answer.trim();
|
|
@@ -193,7 +197,7 @@ async function askRequiredText(rl, label) {
|
|
|
193
197
|
return undefined;
|
|
194
198
|
if (value)
|
|
195
199
|
return value;
|
|
196
|
-
console.log(
|
|
200
|
+
console.log(messages.new.requiredField);
|
|
197
201
|
}
|
|
198
202
|
}
|
|
199
203
|
async function askOptionalText(rl, label) {
|
|
@@ -201,7 +205,7 @@ async function askOptionalText(rl, label) {
|
|
|
201
205
|
const value = answer.trim();
|
|
202
206
|
return isQuit(value) ? undefined : value;
|
|
203
207
|
}
|
|
204
|
-
async function askNumber(rl, label, defaultValue) {
|
|
208
|
+
async function askNumber(rl, label, defaultValue, messages) {
|
|
205
209
|
while (true) {
|
|
206
210
|
const answer = await rl.question(`${label} [${defaultValue}]: `);
|
|
207
211
|
const value = answer.trim();
|
|
@@ -212,18 +216,18 @@ async function askNumber(rl, label, defaultValue) {
|
|
|
212
216
|
const parsed = Number(value);
|
|
213
217
|
if (Number.isInteger(parsed)) {
|
|
214
218
|
try {
|
|
215
|
-
validateTurns(parsed,
|
|
219
|
+
validateTurns(parsed, messages.new.turnsValidationLabel, messages);
|
|
216
220
|
return parsed;
|
|
217
221
|
}
|
|
218
222
|
catch {
|
|
219
223
|
// Show the user-facing wizard hint below.
|
|
220
224
|
}
|
|
221
225
|
}
|
|
222
|
-
console.log(
|
|
226
|
+
console.log(messages.new.invalidTurns(MAX_TURNS));
|
|
223
227
|
}
|
|
224
228
|
}
|
|
225
|
-
async function askYesNo(rl, label, defaultValue) {
|
|
226
|
-
const suffix = defaultValue
|
|
229
|
+
async function askYesNo(rl, label, defaultValue, messages) {
|
|
230
|
+
const suffix = messages.new.yesNoSuffix(defaultValue);
|
|
227
231
|
while (true) {
|
|
228
232
|
const answer = await rl.question(`${label} [${suffix}]: `);
|
|
229
233
|
const value = answer.trim().toLowerCase();
|
|
@@ -235,7 +239,7 @@ async function askYesNo(rl, label, defaultValue) {
|
|
|
235
239
|
return true;
|
|
236
240
|
if (["n", "no", "non"].includes(value))
|
|
237
241
|
return false;
|
|
238
|
-
console.log(
|
|
242
|
+
console.log(messages.new.invalidYesNo);
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
245
|
function splitPaths(value) {
|
|
@@ -254,11 +258,11 @@ function normalizeCommandName(command) {
|
|
|
254
258
|
function isQuit(value) {
|
|
255
259
|
return ["q", "quit", "exit"].includes(value.toLowerCase());
|
|
256
260
|
}
|
|
257
|
-
function printCommandPreview(selection) {
|
|
261
|
+
function printCommandPreview(selection, messages) {
|
|
258
262
|
const explicitCommand = buildExplicitCommand(selection);
|
|
259
263
|
const shortCommand = buildShortCommand(selection);
|
|
260
264
|
console.log("");
|
|
261
|
-
console.log(
|
|
265
|
+
console.log(messages.new.equivalentCommands);
|
|
262
266
|
console.log(` ${explicitCommand}`);
|
|
263
267
|
if (shortCommand) {
|
|
264
268
|
console.log(` ${shortCommand}`);
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createAgent } from "./adapters/index.js";
|
|
2
|
+
import { createTranslator } from "./i18n.js";
|
|
2
3
|
/**
|
|
3
4
|
* Point d'entrée de l'orchestration.
|
|
4
5
|
* Lance le ping-pong entre `agentA` et `agentB` pendant `options.turns` tours,
|
|
@@ -6,19 +7,19 @@ import { createAgent } from "./adapters/index.js";
|
|
|
6
7
|
*
|
|
7
8
|
* @throws {Error} si un agent référencé dans `options` est absent de `config.agents`.
|
|
8
9
|
*/
|
|
9
|
-
export async function runDebate(config, options, renderer) {
|
|
10
|
+
export async function runDebate(config, options, renderer, messages = createTranslator("fr")) {
|
|
10
11
|
const agentAConfig = withRuntimeOverrides(config.agents[options.agentA], options.modelA, options.pullModels);
|
|
11
12
|
const agentBConfig = withRuntimeOverrides(config.agents[options.agentB], options.modelB, options.pullModels);
|
|
12
13
|
if (!agentAConfig) {
|
|
13
|
-
throw new Error(
|
|
14
|
+
throw new Error(messages.common.unknownAgent(options.agentA));
|
|
14
15
|
}
|
|
15
16
|
if (!agentBConfig) {
|
|
16
|
-
throw new Error(
|
|
17
|
+
throw new Error(messages.common.unknownAgent(options.agentB));
|
|
17
18
|
}
|
|
18
19
|
warnIfOllamaHasNoContext(options, [
|
|
19
20
|
[options.agentA, agentAConfig],
|
|
20
21
|
[options.agentB, agentBConfig]
|
|
21
|
-
], renderer);
|
|
22
|
+
], renderer, messages);
|
|
22
23
|
renderer?.start(options, [
|
|
23
24
|
{ name: options.agentA, role: agentAConfig.role, type: agentAConfig.type },
|
|
24
25
|
{ name: options.agentB, role: agentBConfig.role, type: agentBConfig.type }
|
|
@@ -27,7 +28,7 @@ export async function runDebate(config, options, renderer) {
|
|
|
27
28
|
createAgent(options.agentA, agentAConfig),
|
|
28
29
|
createAgent(options.agentB, agentBConfig)
|
|
29
30
|
];
|
|
30
|
-
const
|
|
31
|
+
const transcript = [];
|
|
31
32
|
let stopReason;
|
|
32
33
|
for (let index = 0; index < options.turns; index += 1) {
|
|
33
34
|
const current = agents[index % agents.length];
|
|
@@ -38,12 +39,14 @@ export async function runDebate(config, options, renderer) {
|
|
|
38
39
|
const response = await current.generate({
|
|
39
40
|
topic: options.topic,
|
|
40
41
|
turn,
|
|
42
|
+
totalTurns: options.turns,
|
|
41
43
|
selfName: current.name,
|
|
42
44
|
peerName: peer.name,
|
|
43
45
|
selfRole: current.role,
|
|
46
|
+
language: options.language,
|
|
44
47
|
session: options.session,
|
|
45
48
|
files: options.files,
|
|
46
|
-
transcript
|
|
49
|
+
transcript
|
|
47
50
|
}).finally(() => renderer?.thinkingEnd());
|
|
48
51
|
const message = {
|
|
49
52
|
agent: current.name,
|
|
@@ -51,20 +54,20 @@ export async function runDebate(config, options, renderer) {
|
|
|
51
54
|
content: response.content,
|
|
52
55
|
createdAt: new Date().toISOString()
|
|
53
56
|
};
|
|
54
|
-
|
|
57
|
+
transcript.push(message);
|
|
55
58
|
renderer?.message(message.content);
|
|
56
|
-
if (shouldStopOnAgreement(options,
|
|
57
|
-
stopReason =
|
|
58
|
-
renderer?.notice(
|
|
59
|
+
if (shouldStopOnAgreement(options, transcript)) {
|
|
60
|
+
stopReason = messages.orchestrator.agreementStopReason;
|
|
61
|
+
renderer?.notice(messages.orchestrator.earlyStop(stopReason));
|
|
59
62
|
break;
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
const summary = options.summaryEnabled
|
|
63
|
-
? await generateSummary(config, options,
|
|
66
|
+
? await generateSummary(config, options, transcript, renderer, messages)
|
|
64
67
|
: undefined;
|
|
65
68
|
return {
|
|
66
69
|
options,
|
|
67
|
-
messages,
|
|
70
|
+
messages: transcript,
|
|
68
71
|
summary,
|
|
69
72
|
stopReason
|
|
70
73
|
};
|
|
@@ -111,7 +114,7 @@ function normalizeForAgreement(value) {
|
|
|
111
114
|
* Émet un avertissement si un agent Ollama participe sans contexte fichier.
|
|
112
115
|
* L'adapter Ollama ne lit pas le filesystem : sans `--files` ou `--context`, il ne voit pas le projet.
|
|
113
116
|
*/
|
|
114
|
-
function warnIfOllamaHasNoContext(options, agents, renderer) {
|
|
117
|
+
function warnIfOllamaHasNoContext(options, agents, renderer, messages = createTranslator("fr")) {
|
|
115
118
|
if (options.files.length > 0) {
|
|
116
119
|
return;
|
|
117
120
|
}
|
|
@@ -122,33 +125,35 @@ function warnIfOllamaHasNoContext(options, agents, renderer) {
|
|
|
122
125
|
if (ollamaAgents.length === 0) {
|
|
123
126
|
return;
|
|
124
127
|
}
|
|
125
|
-
renderer?.warning(
|
|
128
|
+
renderer?.warning(messages.orchestrator.ollamaNoContext(ollamaAgents.join(", ")));
|
|
126
129
|
}
|
|
127
130
|
/**
|
|
128
131
|
* Phase de synthèse post-débat. Utilise `options.summaryAgent` quand il est défini, sinon `agentB`.
|
|
129
132
|
*
|
|
130
133
|
* @throws {Error} si l'agent de synthèse est absent de `config.agents`.
|
|
131
134
|
*/
|
|
132
|
-
async function generateSummary(config, options,
|
|
135
|
+
async function generateSummary(config, options, transcript, renderer, messages = createTranslator("fr")) {
|
|
133
136
|
const summaryAgentName = options.summaryAgent ?? options.agentB;
|
|
134
137
|
const summaryModel = options.summaryModel ?? modelForAgent(options, summaryAgentName);
|
|
135
138
|
const summaryConfig = withRuntimeOverrides(config.agents[summaryAgentName], summaryModel, options.pullModels);
|
|
136
139
|
if (!summaryConfig) {
|
|
137
|
-
throw new Error(
|
|
140
|
+
throw new Error(messages.orchestrator.unknownSummaryAgent(summaryAgentName));
|
|
138
141
|
}
|
|
139
142
|
const summaryAgent = createAgent(summaryAgentName, summaryConfig);
|
|
140
143
|
renderer?.summaryStart(summaryAgent.name, summaryAgent.role);
|
|
141
144
|
renderer?.thinkingStart(summaryAgent.name, summaryAgent.role);
|
|
142
145
|
const response = await summaryAgent.generate({
|
|
143
146
|
topic: options.topic,
|
|
144
|
-
turn:
|
|
147
|
+
turn: transcript.length + 1,
|
|
148
|
+
totalTurns: options.turns,
|
|
145
149
|
selfName: summaryAgent.name,
|
|
146
150
|
peerName: "transcript",
|
|
147
151
|
selfRole: summaryAgent.role,
|
|
148
152
|
mode: "summary",
|
|
153
|
+
language: options.language,
|
|
149
154
|
session: options.session,
|
|
150
155
|
files: options.files,
|
|
151
|
-
transcript
|
|
156
|
+
transcript
|
|
152
157
|
}).finally(() => renderer?.thinkingEnd());
|
|
153
158
|
const summary = {
|
|
154
159
|
agent: summaryAgent.name,
|
package/dist/output.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createTranslator } from "./i18n.js";
|
|
3
4
|
/**
|
|
4
5
|
* Écrit le débat au format Markdown dans `outputDir`.
|
|
5
6
|
* Crée le répertoire si absent. Retourne le chemin absolu du fichier créé.
|
|
6
7
|
*/
|
|
7
|
-
export async function writeDebateMarkdown(outputDir, options,
|
|
8
|
+
export async function writeDebateMarkdown(outputDir, options, debateMessages, summary, stopReason, messages = createTranslator("fr")) {
|
|
8
9
|
const safeDate = new Date().toISOString().replace(/[:.]/g, "-");
|
|
9
10
|
const fileName = `palabre-${slugifyTopic(options.topic)}-${safeDate}.debate.md`;
|
|
10
11
|
const filePath = path.resolve(outputDir, fileName);
|
|
11
12
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
12
|
-
await writeFile(filePath, renderDebateMarkdown(options,
|
|
13
|
+
await writeFile(filePath, renderDebateMarkdown(options, debateMessages, summary, stopReason, messages), "utf8");
|
|
13
14
|
return filePath;
|
|
14
15
|
}
|
|
15
16
|
function slugifyTopic(topic) {
|
|
@@ -27,33 +28,33 @@ function slugifyTopic(topic) {
|
|
|
27
28
|
* Produit la représentation Markdown complète du débat.
|
|
28
29
|
* Fonction pure : aucun effet de bord sur le filesystem.
|
|
29
30
|
*/
|
|
30
|
-
export function renderDebateMarkdown(options,
|
|
31
|
+
export function renderDebateMarkdown(options, debateMessages, summary, stopReason, messages = createTranslator("fr")) {
|
|
31
32
|
const lines = [
|
|
32
|
-
|
|
33
|
+
messages.output.title,
|
|
33
34
|
"",
|
|
34
|
-
...renderSessionHeader(options,
|
|
35
|
+
...renderSessionHeader(options, debateMessages, stopReason, messages),
|
|
35
36
|
"",
|
|
36
|
-
|
|
37
|
+
messages.output.contextTitle,
|
|
37
38
|
"",
|
|
38
|
-
...renderFileList(options.files),
|
|
39
|
+
...renderFileList(options.files, messages),
|
|
39
40
|
"",
|
|
40
|
-
|
|
41
|
+
messages.output.exchangesTitle,
|
|
41
42
|
""
|
|
42
43
|
];
|
|
43
|
-
for (const message of
|
|
44
|
+
for (const message of debateMessages) {
|
|
44
45
|
lines.push(`### ${message.agent} (${message.role})`, "", normalizeMarkdownForWindowsPreview(message.content.trim()), "");
|
|
45
46
|
}
|
|
46
|
-
lines.push("---", "",
|
|
47
|
+
lines.push("---", "", messages.output.finalSummaryTitle, "", ...renderSummaryBlock(options, summary, messages));
|
|
47
48
|
return `${lines.join("\n")}\n`;
|
|
48
49
|
}
|
|
49
|
-
function renderSummaryBlock(options, summary) {
|
|
50
|
+
function renderSummaryBlock(options, summary, messages) {
|
|
50
51
|
if (summary) {
|
|
51
52
|
return [
|
|
52
|
-
|
|
53
|
+
`| ${messages.output.tableField} | ${messages.output.tableValue} |`,
|
|
53
54
|
"| --- | --- |",
|
|
54
|
-
`|
|
|
55
|
-
`|
|
|
56
|
-
`|
|
|
55
|
+
`| ${messages.output.fields.agent} | ${escapeTableCell(summary.agent)} |`,
|
|
56
|
+
`| ${messages.output.fields.role} | ${escapeTableCell(summary.role)} |`,
|
|
57
|
+
`| ${messages.output.fields.date} | ${escapeTableCell(summary.createdAt)} |`,
|
|
57
58
|
"",
|
|
58
59
|
normalizeMarkdownForWindowsPreview(summary.content.trim()),
|
|
59
60
|
""
|
|
@@ -61,30 +62,30 @@ function renderSummaryBlock(options, summary) {
|
|
|
61
62
|
}
|
|
62
63
|
return [
|
|
63
64
|
options.summaryEnabled
|
|
64
|
-
?
|
|
65
|
-
:
|
|
65
|
+
? messages.output.summaryMissing
|
|
66
|
+
: messages.output.summaryDisabled,
|
|
66
67
|
""
|
|
67
68
|
];
|
|
68
69
|
}
|
|
69
70
|
function normalizeMarkdownForWindowsPreview(content) {
|
|
70
71
|
return content.replace(/:\*\*/g, ":**");
|
|
71
72
|
}
|
|
72
|
-
function renderSessionHeader(options,
|
|
73
|
+
function renderSessionHeader(options, debateMessages, stopReason, messages) {
|
|
73
74
|
const rows = [
|
|
74
|
-
[
|
|
75
|
-
[
|
|
76
|
-
[
|
|
77
|
-
[
|
|
78
|
-
[
|
|
79
|
-
[
|
|
80
|
-
[
|
|
81
|
-
[
|
|
82
|
-
[
|
|
83
|
-
[
|
|
84
|
-
[
|
|
75
|
+
[messages.output.fields.subject, options.topic],
|
|
76
|
+
[messages.output.fields.agents, `${options.agentA} <-> ${options.agentB}`],
|
|
77
|
+
[messages.output.fields.autoPullOllama, options.pullModels ? messages.output.yes : messages.output.no],
|
|
78
|
+
[messages.output.fields.summary, options.summaryEnabled ? options.summaryAgent ?? options.agentB : messages.output.disabled],
|
|
79
|
+
[messages.output.fields.requestedTurns, String(options.turns)],
|
|
80
|
+
[messages.output.fields.playedTurns, String(debateMessages.length)],
|
|
81
|
+
[messages.output.fields.earlyStop, stopReason ?? messages.output.no],
|
|
82
|
+
[messages.output.fields.localDate, options.session.localDate],
|
|
83
|
+
[messages.output.fields.timeZone, options.session.timeZone],
|
|
84
|
+
[messages.output.fields.cwd, options.session.cwd],
|
|
85
|
+
[messages.output.fields.sessionStartedAt, options.session.startedAt]
|
|
85
86
|
];
|
|
86
87
|
return [
|
|
87
|
-
|
|
88
|
+
`| ${messages.output.tableField} | ${messages.output.tableValue} |`,
|
|
88
89
|
"| --- | --- |",
|
|
89
90
|
...rows.map(([label, value]) => `| ${escapeTableCell(label)} | ${escapeTableCell(value)} |`)
|
|
90
91
|
];
|
|
@@ -92,9 +93,9 @@ function renderSessionHeader(options, messages, stopReason) {
|
|
|
92
93
|
function escapeTableCell(value) {
|
|
93
94
|
return value.replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
94
95
|
}
|
|
95
|
-
function renderFileList(files) {
|
|
96
|
+
function renderFileList(files, messages) {
|
|
96
97
|
if (files.length === 0) {
|
|
97
|
-
return [
|
|
98
|
+
return [messages.output.noFileContext];
|
|
98
99
|
}
|
|
99
|
-
return files.map((file) => `- \`${file.path}\` (${file.sizeBytes}
|
|
100
|
+
return files.map((file) => `- \`${file.path}\` (${file.sizeBytes} ${messages.output.fileSizeUnit})`);
|
|
100
101
|
}
|