palabre 0.9.0 → 0.10.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 +2 -0
- package/dist/adapters/cli-pty.js +30 -10
- package/dist/adapters/cli-shared.js +73 -0
- package/dist/adapters/cli.js +40 -77
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/ollama.js +32 -32
- package/dist/adapters/terminal.js +1 -0
- package/dist/args.js +1 -0
- package/dist/commands/agents.js +91 -0
- package/dist/commands/context.js +31 -0
- package/dist/commands/history.js +33 -0
- package/dist/commands/init.js +64 -0
- package/dist/commands/presets.js +39 -0
- package/dist/commands/shared.js +8 -0
- package/dist/commands/update.js +26 -0
- package/dist/config.js +17 -1
- package/dist/configWizard.js +5 -4
- package/dist/context.js +1 -0
- package/dist/contextScan.js +4 -3
- package/dist/discovery.js +9 -1
- package/dist/doctor.js +1 -0
- package/dist/errors.js +4 -0
- package/dist/exec.js +1 -0
- package/dist/history.js +10 -0
- package/dist/i18n.js +2 -0
- package/dist/index.js +174 -879
- package/dist/limits.js +5 -0
- package/dist/messages/adapter-errors.js +26 -2
- package/dist/messages/config.js +6 -0
- package/dist/messages/index.js +1 -0
- package/dist/messages/renderers.js +10 -2
- package/dist/messages/tui.js +8 -2
- package/dist/new.js +1 -0
- package/dist/ollamaUrl.js +20 -0
- package/dist/orchestrator.js +106 -161
- package/dist/output.js +2 -10
- package/dist/presets.js +1 -0
- package/dist/prompt.js +1 -0
- package/dist/renderers/console.js +2 -8
- package/dist/renderers/ndjson.js +1 -10
- package/dist/renderers/tui-prompts.js +339 -0
- package/dist/renderers/tui-renderer.js +224 -0
- package/dist/renderers/tui-screens.js +352 -0
- package/dist/renderers/tui-theme.js +356 -0
- package/dist/renderers/tui.js +7 -1095
- package/dist/runOptions.js +97 -0
- package/dist/session.js +1 -0
- package/dist/tuiController.js +445 -0
- package/dist/tuiState.js +4 -0
- package/dist/types.js +1 -0
- package/dist/update.js +1 -0
- package/dist/version.js +1 -0
- package/package.json +3 -2
package/dist/limits.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
/** @file Limites produit sur le nombre de tours et d'agents `ask`, et parsing/validation de `--turns`. */
|
|
1
2
|
import { createTranslator } from "./i18n.js";
|
|
3
|
+
/** Nombre de tours par défaut quand `--turns` n'est pas fourni. */
|
|
2
4
|
export const DEFAULT_TURNS = 4;
|
|
5
|
+
/** Nombre maximal d'agents acceptés par `--agents` en mode `ask`. */
|
|
6
|
+
export const MAX_ASK_AGENTS = 4;
|
|
7
|
+
/** Borne haute produit pour `--turns`, au-delà considérée comme une erreur d'utilisation. */
|
|
3
8
|
export const MAX_TURNS = 20;
|
|
4
9
|
/** Convertit `value` en nombre et valide la plage [1, `MAX_TURNS`]. Lève une erreur si invalide. */
|
|
5
10
|
export function parseTurns(value, label = "--turns", messages = createTranslator("fr")) {
|
|
@@ -29,10 +29,34 @@ const enHints = {
|
|
|
29
29
|
export const adapterErrorMessages = {
|
|
30
30
|
fr: {
|
|
31
31
|
suggestionPrefix: "Suggestion",
|
|
32
|
-
hint: (kind) => frHints[kind]
|
|
32
|
+
hint: (kind) => frHints[kind],
|
|
33
|
+
usageLimit: (adapterName, detail) => `${adapterName} a atteint une limite d'utilisation: ${detail}`,
|
|
34
|
+
unsupportedModel: (adapterName, detail) => `${adapterName} ne peut pas utiliser ce modèle: ${detail}`,
|
|
35
|
+
noStderrCaptured: "aucun stderr capturé.",
|
|
36
|
+
noPtyOutputCaptured: "aucune sortie PTY capturée.",
|
|
37
|
+
ollamaModelUnavailable: (model, installedModels) => `Modèle Ollama indisponible: ${model}. Modèles détectés: ${installedModels.join(", ") || "aucun"}. ` +
|
|
38
|
+
"Utilise --pull-models ou autoPullModel: true pour autoriser le téléchargement.",
|
|
39
|
+
ollamaModelStillUnavailable: (model) => `Le modèle Ollama ${model} reste indisponible après téléchargement.`,
|
|
40
|
+
ollamaPullProgress: (model) => `[ollama] Modèle absent, téléchargement: ${model}`,
|
|
41
|
+
ollamaPullFailed: (model, detail) => `Échec du téléchargement Ollama ${model}: ${detail}`,
|
|
42
|
+
ollamaTagsHttpError: (status) => `Ollama HTTP ${status} pendant la détection des modèles`,
|
|
43
|
+
ollamaPsHttpError: (status) => `Ollama HTTP ${status} pendant la détection des modèles chargés`,
|
|
44
|
+
ollamaUnloadFailed: (model, status) => `Impossible de décharger le modèle Ollama ${model}: HTTP ${status}`
|
|
33
45
|
},
|
|
34
46
|
en: {
|
|
35
47
|
suggestionPrefix: "Suggestion",
|
|
36
|
-
hint: (kind) => enHints[kind]
|
|
48
|
+
hint: (kind) => enHints[kind],
|
|
49
|
+
usageLimit: (adapterName, detail) => `${adapterName} hit a usage limit: ${detail}`,
|
|
50
|
+
unsupportedModel: (adapterName, detail) => `${adapterName} cannot use this model: ${detail}`,
|
|
51
|
+
noStderrCaptured: "no stderr captured.",
|
|
52
|
+
noPtyOutputCaptured: "no PTY output captured.",
|
|
53
|
+
ollamaModelUnavailable: (model, installedModels) => `Ollama model unavailable: ${model}. Detected models: ${installedModels.join(", ") || "none"}. ` +
|
|
54
|
+
"Use --pull-models or autoPullModel: true to allow downloading.",
|
|
55
|
+
ollamaModelStillUnavailable: (model) => `Ollama model ${model} is still unavailable after downloading.`,
|
|
56
|
+
ollamaPullProgress: (model) => `[ollama] Model missing, downloading: ${model}`,
|
|
57
|
+
ollamaPullFailed: (model, detail) => `Ollama download failed for ${model}: ${detail}`,
|
|
58
|
+
ollamaTagsHttpError: (status) => `Ollama HTTP ${status} while detecting models`,
|
|
59
|
+
ollamaPsHttpError: (status) => `Ollama HTTP ${status} while detecting loaded models`,
|
|
60
|
+
ollamaUnloadFailed: (model, status) => `Failed to unload Ollama model ${model}: HTTP ${status}`
|
|
37
61
|
}
|
|
38
62
|
};
|
package/dist/messages/config.js
CHANGED
|
@@ -9,6 +9,9 @@ export const configMessages = {
|
|
|
9
9
|
ollamaModelUnavailable: (model) => `Modèle Ollama non installé: ${model}. Action: choisis un modèle installé ou lance \`ollama pull ${model}\`.`,
|
|
10
10
|
ollamaModelNoAgent: "Agent ollama-local absent ou invalide dans la config.",
|
|
11
11
|
ollamaModelNoInstalledModels: "Aucun modèle Ollama installé détecté. Action: lance `ollama pull <modèle>`.",
|
|
12
|
+
ollamaModelsCurrent: (model) => `ollama-local: ${model ?? "(non configuré)"}`,
|
|
13
|
+
ollamaModelsApi: (available, baseUrl) => `Ollama API: ${available ? "joignable" : "indisponible"} (${baseUrl})`,
|
|
14
|
+
ollamaModelsInstalled: (models) => `Modèles installés: ${models.length > 0 ? models.join(", ") : "(aucun)"}`,
|
|
12
15
|
updated: (path, defaults, language) => `Configuration mise à jour dans ${path}: ${defaults}, langue: ${language}.`,
|
|
13
16
|
cleared: (path) => `Paramètres par défaut supprimés dans ${path}. Utilise maintenant un preset ou --agent-a/--agent-b pour lancer un débat.`,
|
|
14
17
|
defaultsSummary: (agentA, agentB, turns, summaryAgent, askSummaryAgent, mode, askAgents, interfaceName) => {
|
|
@@ -63,6 +66,9 @@ export const configMessages = {
|
|
|
63
66
|
ollamaModelUnavailable: (model) => `Ollama model is not installed: ${model}. Action: choose an installed model or run \`ollama pull ${model}\`.`,
|
|
64
67
|
ollamaModelNoAgent: "ollama-local agent is missing or invalid in the config.",
|
|
65
68
|
ollamaModelNoInstalledModels: "No installed Ollama model detected. Action: run `ollama pull <model>`.",
|
|
69
|
+
ollamaModelsCurrent: (model) => `ollama-local: ${model ?? "(not configured)"}`,
|
|
70
|
+
ollamaModelsApi: (available, baseUrl) => `Ollama API: ${available ? "reachable" : "unavailable"} (${baseUrl})`,
|
|
71
|
+
ollamaModelsInstalled: (models) => `Installed models: ${models.length > 0 ? models.join(", ") : "(none)"}`,
|
|
66
72
|
updated: (path, defaults, language) => `Configuration updated in ${path}: ${defaults}, language: ${language}.`,
|
|
67
73
|
cleared: (path) => `Default settings cleared in ${path}. Use a preset or --agent-a/--agent-b to start a debate now.`,
|
|
68
74
|
defaultsSummary: (agentA, agentB, turns, summaryAgent, askSummaryAgent, mode, askAgents, interfaceName) => {
|
package/dist/messages/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { previewMessages } from "./preview.js";
|
|
|
16
16
|
import { rendererMessages } from "./renderers.js";
|
|
17
17
|
import { tuiMessages } from "./tui.js";
|
|
18
18
|
import { updateMessages } from "./update.js";
|
|
19
|
+
/** Construit le dictionnaire `Messages` pour `language`, en assemblant chaque domaine indépendamment. */
|
|
19
20
|
export function createTranslator(language) {
|
|
20
21
|
return {
|
|
21
22
|
adapterErrors: adapterErrorMessages[language],
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/** Tronque une liste de chemins pour l'affichage : 3 premiers + compteur du reste. */
|
|
2
|
+
function summarizePaths(paths) {
|
|
3
|
+
const shown = paths.slice(0, 3);
|
|
4
|
+
const extra = paths.length - shown.length;
|
|
5
|
+
return `${shown.join(", ")}${extra > 0 ? ` (+${extra})` : ""}`;
|
|
6
|
+
}
|
|
1
7
|
export const rendererMessages = {
|
|
2
8
|
fr: {
|
|
3
9
|
subject: (topic) => `Sujet: ${topic}`,
|
|
@@ -5,6 +11,7 @@ export const rendererMessages = {
|
|
|
5
11
|
responsesSummaryContext: (turns, summary, context) => `Réponses: ${turns} | Synthèse: ${summary} | Contexte: ${context}`,
|
|
6
12
|
responsesSummary: (turns, summary) => `Réponses: ${turns} | Synthèse: ${summary}`,
|
|
7
13
|
context: (context) => `Contexte: ${context}`,
|
|
14
|
+
workingFolder: (path) => `Dossier: ${path}`,
|
|
8
15
|
options: (earlyStop, pullModels) => `Options: arrêt anticipé ${earlyStop ? "activé" : "désactivé"}, auto-pull Ollama ${pullModels ? "activé" : "désactivé"}`,
|
|
9
16
|
enabled: "activé",
|
|
10
17
|
disabled: "désactivée",
|
|
@@ -15,7 +22,7 @@ export const rendererMessages = {
|
|
|
15
22
|
summaryTitle: "Synthese",
|
|
16
23
|
exported: (path) => `Palabre exporte: ${path}`,
|
|
17
24
|
noInjectedFiles: "aucun fichier injecté",
|
|
18
|
-
injectedFiles: (count) => `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""}`
|
|
25
|
+
injectedFiles: (count, paths) => `${count} fichier${count > 1 ? "s" : ""} injecté${count > 1 ? "s" : ""} : ${summarizePaths(paths)}`
|
|
19
26
|
},
|
|
20
27
|
en: {
|
|
21
28
|
subject: (topic) => `Subject: ${topic}`,
|
|
@@ -23,6 +30,7 @@ export const rendererMessages = {
|
|
|
23
30
|
responsesSummaryContext: (turns, summary, context) => `Responses: ${turns} | Summary: ${summary} | Context: ${context}`,
|
|
24
31
|
responsesSummary: (turns, summary) => `Responses: ${turns} | Summary: ${summary}`,
|
|
25
32
|
context: (context) => `Context: ${context}`,
|
|
33
|
+
workingFolder: (path) => `Folder: ${path}`,
|
|
26
34
|
options: (earlyStop, pullModels) => `Options: early stop ${earlyStop ? "enabled" : "disabled"}, Ollama auto-pull ${pullModels ? "enabled" : "disabled"}`,
|
|
27
35
|
enabled: "enabled",
|
|
28
36
|
disabled: "disabled",
|
|
@@ -33,6 +41,6 @@ export const rendererMessages = {
|
|
|
33
41
|
summaryTitle: "Summary",
|
|
34
42
|
exported: (path) => `Palabre exported: ${path}`,
|
|
35
43
|
noInjectedFiles: "no injected files",
|
|
36
|
-
injectedFiles: (count) => `${count} injected file${count > 1 ? "s" : ""}`
|
|
44
|
+
injectedFiles: (count, paths) => `${count} injected file${count > 1 ? "s" : ""}: ${summarizePaths(paths)}`
|
|
37
45
|
}
|
|
38
46
|
};
|
package/dist/messages/tui.js
CHANGED
|
@@ -9,7 +9,7 @@ export const tuiMessages = {
|
|
|
9
9
|
roles: "Roles",
|
|
10
10
|
summary: "Synthese",
|
|
11
11
|
ollamaModel: "Modele Ollama",
|
|
12
|
-
ollamaUrl: "Adresse Ollama
|
|
12
|
+
ollamaUrl: "Adresse Ollama",
|
|
13
13
|
ollamaUrlEffective: "Adresse Ollama effective",
|
|
14
14
|
responses: "Tours",
|
|
15
15
|
folder: "Dossier",
|
|
@@ -53,6 +53,9 @@ export const tuiMessages = {
|
|
|
53
53
|
roleReviewer: "cherche risques et tests manquants",
|
|
54
54
|
roleSummarizer: "synthetise fidelement",
|
|
55
55
|
configTitle: "Configuration Palabre",
|
|
56
|
+
configSectionGeneral: "Général",
|
|
57
|
+
exportedFile: "Fichier exporté",
|
|
58
|
+
exportedFolder: "Dossier d'export",
|
|
56
59
|
configFile: "Config",
|
|
57
60
|
interface: "Interface",
|
|
58
61
|
language: "Langue",
|
|
@@ -129,7 +132,7 @@ export const tuiMessages = {
|
|
|
129
132
|
roles: "Roles",
|
|
130
133
|
summary: "Summary",
|
|
131
134
|
ollamaModel: "Ollama model",
|
|
132
|
-
ollamaUrl: "
|
|
135
|
+
ollamaUrl: "Ollama address",
|
|
133
136
|
ollamaUrlEffective: "Effective Ollama address",
|
|
134
137
|
responses: "Turns",
|
|
135
138
|
folder: "Folder",
|
|
@@ -173,6 +176,9 @@ export const tuiMessages = {
|
|
|
173
176
|
roleReviewer: "looks for risks and missing tests",
|
|
174
177
|
roleSummarizer: "summarizes faithfully",
|
|
175
178
|
configTitle: "Palabre Configuration",
|
|
179
|
+
configSectionGeneral: "General",
|
|
180
|
+
exportedFile: "Exported file",
|
|
181
|
+
exportedFolder: "Export folder",
|
|
176
182
|
configFile: "Config",
|
|
177
183
|
interface: "Interface",
|
|
178
184
|
language: "Language",
|
package/dist/new.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @file Assistant interactif `palabre new` : compose les mêmes flags qu'un lancement direct, sans second chemin d'exécution. */
|
|
1
2
|
import { createInterface } from "node:readline/promises";
|
|
2
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
4
|
import { isAgentDetected } from "./agentRegistry.js";
|
package/dist/ollamaUrl.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
/** Serveur Ollama utilisé quand aucune autre source (flag, env, config) n'est fournie. */
|
|
1
2
|
export const DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
|
|
3
|
+
/**
|
|
4
|
+
* Erreur levée par `normalizeOllamaBaseUrl` quand la valeur fournie n'est pas une URL HTTP(S) exploitable.
|
|
5
|
+
* Le message du constructeur est un texte technique de repli non localisé ; les appelants CLI/TUI
|
|
6
|
+
* doivent reformater `kind`/`value`/`protocol` via les messages traduits plutôt que d'afficher ce message brut.
|
|
7
|
+
*/
|
|
2
8
|
export class OllamaUrlError extends Error {
|
|
3
9
|
kind;
|
|
4
10
|
value;
|
|
@@ -15,6 +21,20 @@ export class OllamaUrlError extends Error {
|
|
|
15
21
|
this.name = "OllamaUrlError";
|
|
16
22
|
}
|
|
17
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Formate une `OllamaUrlError` en message localisé actionnable.
|
|
26
|
+
* Source unique du mapping `kind` → message, partagée entre le gestionnaire
|
|
27
|
+
* d'erreur du point d'entrée CLI et la conversion en `DebateFailure` de l'orchestrateur.
|
|
28
|
+
*/
|
|
29
|
+
export function formatOllamaUrlError(error, messages) {
|
|
30
|
+
if (error.kind === "empty") {
|
|
31
|
+
return messages.common.ollamaUrlEmpty;
|
|
32
|
+
}
|
|
33
|
+
if (error.kind === "protocol") {
|
|
34
|
+
return messages.common.ollamaUrlProtocol(error.protocol ?? "");
|
|
35
|
+
}
|
|
36
|
+
return messages.common.ollamaUrlInvalid(error.value);
|
|
37
|
+
}
|
|
18
38
|
/**
|
|
19
39
|
* Résout l'adresse client Ollama selon la priorité produit :
|
|
20
40
|
* flag CLI > OLLAMA_HOST > config agent > serveur local par défaut.
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
/** @file Boucle d'orchestration des modes `debate` et `ask` : tours, arrêt anticipé et synthèse finale. */
|
|
1
2
|
import { createAgent } from "./adapters/index.js";
|
|
2
3
|
import { AdapterError } from "./errors.js";
|
|
3
4
|
import { createTranslator } from "./i18n.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
5
|
+
import { MAX_ASK_AGENTS } from "./limits.js";
|
|
6
|
+
import { formatOllamaUrlError, OllamaUrlError } from "./ollamaUrl.js";
|
|
7
|
+
export { MAX_ASK_AGENTS } from "./limits.js";
|
|
8
|
+
/** Rôle imposé à l'agent de synthèse, indépendant de son rôle configuré. */
|
|
9
|
+
const SUMMARY_ROLE = "summarizer";
|
|
6
10
|
/**
|
|
7
11
|
* Point d'entrée de l'orchestration.
|
|
8
12
|
* Lance le ping-pong entre `agentA` et `agentB` pendant `options.turns` tours,
|
|
@@ -51,86 +55,36 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
51
55
|
const peer = agents[(index + 1) % agents.length];
|
|
52
56
|
const turn = index + 1;
|
|
53
57
|
renderer?.turnStart(turn, options.turns, current.name, current.role);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
signal: options.signal
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
const failure = toDebateFailure(error, {
|
|
73
|
-
phase: "debate",
|
|
74
|
-
agent: current.name,
|
|
75
|
-
role: current.role,
|
|
76
|
-
turn
|
|
77
|
-
}, messages);
|
|
78
|
-
renderer?.error(failure);
|
|
58
|
+
const outcome = await generateTurnMessage(current, {
|
|
59
|
+
topic: options.topic,
|
|
60
|
+
turn,
|
|
61
|
+
totalTurns: options.turns,
|
|
62
|
+
selfName: current.name,
|
|
63
|
+
peerName: peer.name,
|
|
64
|
+
selfRole: current.role,
|
|
65
|
+
language: options.language,
|
|
66
|
+
session: options.session,
|
|
67
|
+
files: options.files,
|
|
68
|
+
transcript,
|
|
69
|
+
signal: options.signal
|
|
70
|
+
}, { phase: "debate", agent: current.name, role: current.role, turn }, renderer, messages);
|
|
71
|
+
if (outcome.failure) {
|
|
79
72
|
return {
|
|
80
73
|
options,
|
|
81
74
|
messages: transcript,
|
|
82
75
|
stopReason,
|
|
83
|
-
failure
|
|
76
|
+
failure: outcome.failure
|
|
84
77
|
};
|
|
85
78
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
const message = {
|
|
90
|
-
agent: current.name,
|
|
91
|
-
role: current.role,
|
|
92
|
-
content: response.content,
|
|
93
|
-
createdAt: new Date().toISOString()
|
|
94
|
-
};
|
|
95
|
-
transcript.push(message);
|
|
96
|
-
renderer?.message(message.content);
|
|
79
|
+
transcript.push(outcome.message);
|
|
80
|
+
renderer?.message(outcome.message.content);
|
|
97
81
|
if (shouldStopOnAgreement(options, transcript, messages)) {
|
|
98
82
|
stopReason = messages.orchestrator.agreementStopReason;
|
|
99
83
|
renderer?.notice(messages.orchestrator.earlyStop(stopReason));
|
|
100
84
|
break;
|
|
101
85
|
}
|
|
102
86
|
}
|
|
103
|
-
|
|
104
|
-
let failure;
|
|
105
|
-
if (options.summaryEnabled) {
|
|
106
|
-
try {
|
|
107
|
-
const cancellation = cancellationFailureIfAborted(options, messages, {
|
|
108
|
-
phase: "summary",
|
|
109
|
-
agent: resolveSummaryAgentName(options),
|
|
110
|
-
role: summaryRole(),
|
|
111
|
-
turn: transcript.length + 1
|
|
112
|
-
});
|
|
113
|
-
if (cancellation) {
|
|
114
|
-
renderer?.error(cancellation);
|
|
115
|
-
return {
|
|
116
|
-
options,
|
|
117
|
-
messages: transcript,
|
|
118
|
-
stopReason,
|
|
119
|
-
failure: cancellation
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
summary = await generateSummary(config, options, transcript, renderer, messages);
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
failure = toDebateFailure(error, {
|
|
126
|
-
phase: "summary",
|
|
127
|
-
agent: resolveSummaryAgentName(options),
|
|
128
|
-
role: summaryRole(),
|
|
129
|
-
turn: transcript.length + 1
|
|
130
|
-
}, messages);
|
|
131
|
-
renderer?.error(failure);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
87
|
+
const { summary, failure } = await runSummaryPhase(config, options, transcript, renderer, messages);
|
|
134
88
|
return {
|
|
135
89
|
options,
|
|
136
90
|
messages: transcript,
|
|
@@ -144,6 +98,8 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
144
98
|
* puis un agent de synthèse résume fidèlement chaque réponse et les compare.
|
|
145
99
|
*/
|
|
146
100
|
export async function runAsk(config, options, renderer, messages = createTranslator("fr")) {
|
|
101
|
+
// `runAsk` est appelable directement, hors du chemin CLI : on revalide la liste
|
|
102
|
+
// ask même si `resolveRunOptions` l'a déjà normalisée pour `palabre run`.
|
|
147
103
|
const askAgentNames = resolveAskAgentNames(options);
|
|
148
104
|
if (askAgentNames.length === 0) {
|
|
149
105
|
throw new Error(messages.common.noAgentDefined("ask agent"));
|
|
@@ -158,7 +114,7 @@ export async function runAsk(config, options, renderer, messages = createTransla
|
|
|
158
114
|
}
|
|
159
115
|
return [name, agentConfig];
|
|
160
116
|
});
|
|
161
|
-
warnIfOllamaHasNoContext(options, agentEntries
|
|
117
|
+
warnIfOllamaHasNoContext(options, agentEntries, renderer, messages);
|
|
162
118
|
renderer?.start(options, agentEntries.map(([name, agentConfig]) => ({
|
|
163
119
|
name,
|
|
164
120
|
role: agentConfig.role,
|
|
@@ -189,86 +145,36 @@ export async function runAsk(config, options, renderer, messages = createTransla
|
|
|
189
145
|
else {
|
|
190
146
|
renderer?.turnStart(response, agents.length, current.name, current.role);
|
|
191
147
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
signal: options.signal
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
const failure = toDebateFailure(error, {
|
|
212
|
-
phase: "ask",
|
|
213
|
-
agent: current.name,
|
|
214
|
-
role: current.role,
|
|
215
|
-
turn: response
|
|
216
|
-
}, messages);
|
|
217
|
-
renderer?.error(failure);
|
|
148
|
+
const outcome = await generateTurnMessage(current, {
|
|
149
|
+
topic: options.topic,
|
|
150
|
+
turn: response,
|
|
151
|
+
totalTurns: agents.length,
|
|
152
|
+
selfName: current.name,
|
|
153
|
+
peerName: "independent-agents",
|
|
154
|
+
selfRole: current.role,
|
|
155
|
+
mode: "ask",
|
|
156
|
+
language: options.language,
|
|
157
|
+
session: options.session,
|
|
158
|
+
files: options.files,
|
|
159
|
+
transcript: [],
|
|
160
|
+
signal: options.signal
|
|
161
|
+
}, { phase: "ask", agent: current.name, role: current.role, turn: response }, renderer, messages);
|
|
162
|
+
if (outcome.failure) {
|
|
218
163
|
return {
|
|
219
164
|
options,
|
|
220
165
|
messages: transcript,
|
|
221
|
-
failure
|
|
166
|
+
failure: outcome.failure
|
|
222
167
|
};
|
|
223
168
|
}
|
|
224
|
-
|
|
225
|
-
renderer?.thinkingEnd();
|
|
226
|
-
}
|
|
227
|
-
const message = {
|
|
228
|
-
agent: current.name,
|
|
229
|
-
role: current.role,
|
|
230
|
-
content: agentResponse.content,
|
|
231
|
-
createdAt: new Date().toISOString()
|
|
232
|
-
};
|
|
233
|
-
transcript.push(message);
|
|
169
|
+
transcript.push(outcome.message);
|
|
234
170
|
if (renderer?.askResponseMessage) {
|
|
235
|
-
renderer.askResponseMessage(message.content);
|
|
171
|
+
renderer.askResponseMessage(outcome.message.content);
|
|
236
172
|
}
|
|
237
173
|
else {
|
|
238
|
-
renderer?.message(message.content);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
let summary;
|
|
242
|
-
let failure;
|
|
243
|
-
if (options.summaryEnabled) {
|
|
244
|
-
try {
|
|
245
|
-
const summaryAgentName = resolveSummaryAgentName(options);
|
|
246
|
-
const cancellation = cancellationFailureIfAborted(options, messages, {
|
|
247
|
-
phase: "summary",
|
|
248
|
-
agent: summaryAgentName,
|
|
249
|
-
role: summaryRole(),
|
|
250
|
-
turn: transcript.length + 1
|
|
251
|
-
});
|
|
252
|
-
if (cancellation) {
|
|
253
|
-
renderer?.error(cancellation);
|
|
254
|
-
return {
|
|
255
|
-
options,
|
|
256
|
-
messages: transcript,
|
|
257
|
-
failure: cancellation
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
summary = await generateSummary(config, options, transcript, renderer, messages);
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
failure = toDebateFailure(error, {
|
|
264
|
-
phase: "summary",
|
|
265
|
-
agent: resolveSummaryAgentName(options),
|
|
266
|
-
role: summaryRole(),
|
|
267
|
-
turn: transcript.length + 1
|
|
268
|
-
}, messages);
|
|
269
|
-
renderer?.error(failure);
|
|
174
|
+
renderer?.message(outcome.message.content);
|
|
270
175
|
}
|
|
271
176
|
}
|
|
177
|
+
const { summary, failure } = await runSummaryPhase(config, options, transcript, renderer, messages);
|
|
272
178
|
return {
|
|
273
179
|
options,
|
|
274
180
|
messages: transcript,
|
|
@@ -276,6 +182,62 @@ export async function runAsk(config, options, renderer, messages = createTransla
|
|
|
276
182
|
failure
|
|
277
183
|
};
|
|
278
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Appelle `agent.generate` en encadrant l'état "thinking" du renderer, puis
|
|
187
|
+
* construit le `DebateMessage` horodaté ou convertit l'erreur en `DebateFailure`
|
|
188
|
+
* (notifiée au renderer). Mutualisé entre les tours `debate` et les réponses `ask`.
|
|
189
|
+
*/
|
|
190
|
+
async function generateTurnMessage(agent, prompt, context, renderer, messages) {
|
|
191
|
+
renderer?.thinkingStart(agent.name, agent.role);
|
|
192
|
+
try {
|
|
193
|
+
const response = await agent.generate(prompt);
|
|
194
|
+
return {
|
|
195
|
+
message: {
|
|
196
|
+
agent: agent.name,
|
|
197
|
+
role: agent.role,
|
|
198
|
+
content: response.content,
|
|
199
|
+
createdAt: new Date().toISOString()
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
const failure = toDebateFailure(error, context, messages);
|
|
205
|
+
renderer?.error(failure);
|
|
206
|
+
return { failure };
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
renderer?.thinkingEnd();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Phase de synthèse commune aux modes `debate` et `ask` : vérifie l'annulation,
|
|
214
|
+
* génère la synthèse et convertit tout échec en `DebateFailure` déjà notifiée au
|
|
215
|
+
* renderer. Retourne un objet vide si la synthèse est désactivée.
|
|
216
|
+
*/
|
|
217
|
+
async function runSummaryPhase(config, options, transcript, renderer, messages) {
|
|
218
|
+
if (!options.summaryEnabled) {
|
|
219
|
+
return {};
|
|
220
|
+
}
|
|
221
|
+
const context = {
|
|
222
|
+
phase: "summary",
|
|
223
|
+
agent: options.summaryAgent,
|
|
224
|
+
role: SUMMARY_ROLE,
|
|
225
|
+
turn: transcript.length + 1
|
|
226
|
+
};
|
|
227
|
+
const cancellation = cancellationFailureIfAborted(options, messages, context);
|
|
228
|
+
if (cancellation) {
|
|
229
|
+
renderer?.error(cancellation);
|
|
230
|
+
return { failure: cancellation };
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
return { summary: await generateSummary(config, options, transcript, renderer, messages) };
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
const failure = toDebateFailure(error, context, messages);
|
|
237
|
+
renderer?.error(failure);
|
|
238
|
+
return { failure };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
279
241
|
/**
|
|
280
242
|
* Heuristique d'arrêt sur accord explicite.
|
|
281
243
|
* Ne s'active qu'après un tour complet (nombre pair de messages) pour éviter les faux positifs.
|
|
@@ -330,14 +292,14 @@ function warnIfOllamaHasNoContext(options, agents, renderer, messages = createTr
|
|
|
330
292
|
* @throws {Error} si l'agent de synthèse est absent de `config.agents`.
|
|
331
293
|
*/
|
|
332
294
|
async function generateSummary(config, options, transcript, renderer, messages = createTranslator("fr")) {
|
|
333
|
-
const summaryAgentName =
|
|
295
|
+
const summaryAgentName = options.summaryAgent;
|
|
334
296
|
const summaryModel = options.summaryModel ?? modelForAgent(options, summaryAgentName);
|
|
335
297
|
const summaryConfig = withRuntimeOverrides(config.agents[summaryAgentName], summaryModel, options.pullModels);
|
|
336
298
|
if (!summaryConfig) {
|
|
337
299
|
throw new Error(messages.orchestrator.unknownSummaryAgent(summaryAgentName));
|
|
338
300
|
}
|
|
339
301
|
const summaryAgent = createAgent(summaryAgentName, summaryConfig, { ollamaUrl: options.ollamaUrl });
|
|
340
|
-
const role =
|
|
302
|
+
const role = SUMMARY_ROLE;
|
|
341
303
|
renderer?.summaryStart(summaryAgent.name, role);
|
|
342
304
|
renderer?.thinkingStart(summaryAgent.name, role);
|
|
343
305
|
const response = await summaryAgent.generate({
|
|
@@ -363,9 +325,6 @@ async function generateSummary(config, options, transcript, renderer, messages =
|
|
|
363
325
|
renderer?.message(summary.content);
|
|
364
326
|
return summary;
|
|
365
327
|
}
|
|
366
|
-
function summaryRole() {
|
|
367
|
-
return "summarizer";
|
|
368
|
-
}
|
|
369
328
|
function cancellationFailureIfAborted(options, messages, context) {
|
|
370
329
|
if (!options.signal?.aborted) {
|
|
371
330
|
return undefined;
|
|
@@ -385,15 +344,6 @@ function resolveAskAgentNames(options) {
|
|
|
385
344
|
: [options.agentA, options.agentB];
|
|
386
345
|
return agents.filter((agent, index) => Boolean(agent) && agents.indexOf(agent) === index);
|
|
387
346
|
}
|
|
388
|
-
function resolveSummaryAgentName(options) {
|
|
389
|
-
if (options.summaryAgent) {
|
|
390
|
-
return options.summaryAgent;
|
|
391
|
-
}
|
|
392
|
-
if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
|
|
393
|
-
return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
|
|
394
|
-
}
|
|
395
|
-
return options.agentB;
|
|
396
|
-
}
|
|
397
347
|
function toDebateFailure(error, context, messages) {
|
|
398
348
|
if (error instanceof AdapterError) {
|
|
399
349
|
return {
|
|
@@ -407,15 +357,10 @@ function toDebateFailure(error, context, messages) {
|
|
|
407
357
|
};
|
|
408
358
|
}
|
|
409
359
|
if (error instanceof OllamaUrlError) {
|
|
410
|
-
const message = error.kind === "empty"
|
|
411
|
-
? messages.common.ollamaUrlEmpty
|
|
412
|
-
: error.kind === "protocol"
|
|
413
|
-
? messages.common.ollamaUrlProtocol(error.protocol ?? "")
|
|
414
|
-
: messages.common.ollamaUrlInvalid(error.value);
|
|
415
360
|
return {
|
|
416
361
|
...context,
|
|
417
362
|
kind: "unknown",
|
|
418
|
-
message
|
|
363
|
+
message: formatOllamaUrlError(error, messages)
|
|
419
364
|
};
|
|
420
365
|
}
|
|
421
366
|
return {
|
package/dist/output.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @file Export Markdown `.debate.md`/`.ask.md` : transcript, table de métadonnées et synthèse finale. */
|
|
1
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { createTranslator } from "./i18n.js";
|
|
@@ -94,7 +95,7 @@ function renderSessionHeader(options, debateMessages, stopReason, messages) {
|
|
|
94
95
|
[messages.output.fields.mode, options.mode],
|
|
95
96
|
[messages.output.fields.agents, formatAgentsForHeader(options)],
|
|
96
97
|
[messages.output.fields.autoPullOllama, options.pullModels ? messages.output.yes : messages.output.no],
|
|
97
|
-
[messages.output.fields.summary, options.summaryEnabled ?
|
|
98
|
+
[messages.output.fields.summary, options.summaryEnabled ? options.summaryAgent : messages.output.disabled],
|
|
98
99
|
[
|
|
99
100
|
options.mode === "ask" ? messages.output.fields.requestedResponses : messages.output.fields.requestedTurns,
|
|
100
101
|
String(options.mode === "ask" ? options.askAgents?.length ?? debateMessages.length : options.turns)
|
|
@@ -124,15 +125,6 @@ function renderFileList(files, messages) {
|
|
|
124
125
|
}
|
|
125
126
|
return files.map((file) => `- \`${file.path}\` (${file.sizeBytes} ${messages.output.fileSizeUnit})`);
|
|
126
127
|
}
|
|
127
|
-
function formatSummaryAgent(options) {
|
|
128
|
-
if (options.summaryAgent) {
|
|
129
|
-
return options.summaryAgent;
|
|
130
|
-
}
|
|
131
|
-
if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
|
|
132
|
-
return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
|
|
133
|
-
}
|
|
134
|
-
return options.agentB;
|
|
135
|
-
}
|
|
136
128
|
function formatAgentsForHeader(options) {
|
|
137
129
|
if (options.mode === "ask") {
|
|
138
130
|
return (options.askAgents && options.askAgents.length > 0 ? options.askAgents : [options.agentA, options.agentB]).join(", ");
|
package/dist/presets.js
CHANGED
package/dist/prompt.js
CHANGED
|
@@ -241,13 +241,7 @@ function formatSummary(options, messages) {
|
|
|
241
241
|
if (!options.summaryEnabled) {
|
|
242
242
|
return messages.renderers.disabled;
|
|
243
243
|
}
|
|
244
|
-
|
|
245
|
-
return options.summaryAgent;
|
|
246
|
-
}
|
|
247
|
-
if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
|
|
248
|
-
return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
|
|
249
|
-
}
|
|
250
|
-
return options.agentB;
|
|
244
|
+
return options.summaryAgent;
|
|
251
245
|
}
|
|
252
246
|
function formatResponseCount(options) {
|
|
253
247
|
return options.mode === "ask" ? options.askAgents?.length ?? 2 : options.turns;
|
|
@@ -261,7 +255,7 @@ function formatContext(options, messages) {
|
|
|
261
255
|
if (count === 0) {
|
|
262
256
|
return messages.renderers.noInjectedFiles;
|
|
263
257
|
}
|
|
264
|
-
return messages.renderers.injectedFiles(count);
|
|
258
|
+
return messages.renderers.injectedFiles(count, options.files.map((file) => file.path));
|
|
265
259
|
}
|
|
266
260
|
function formatFailureLocation(failure, messages) {
|
|
267
261
|
if (failure.phase === "summary") {
|
package/dist/renderers/ndjson.js
CHANGED
|
@@ -31,7 +31,7 @@ export class NdjsonRenderer {
|
|
|
31
31
|
agents: agents.map((a) => ({ name: a.name, role: a.role, type: a.type })),
|
|
32
32
|
summaryEnabled: options.summaryEnabled,
|
|
33
33
|
summaryAgent: options.summaryEnabled
|
|
34
|
-
?
|
|
34
|
+
? options.summaryAgent
|
|
35
35
|
: null,
|
|
36
36
|
earlyStop: options.earlyStopOnAgreement,
|
|
37
37
|
filesCount: options.files.length,
|
|
@@ -134,15 +134,6 @@ export class NdjsonRenderer {
|
|
|
134
134
|
process.stdout.write(JSON.stringify({ v: this.schemaVersion, ...event }) + "\n");
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
|
-
function resolveSummaryAgent(options) {
|
|
138
|
-
if (options.summaryAgent) {
|
|
139
|
-
return options.summaryAgent;
|
|
140
|
-
}
|
|
141
|
-
if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
|
|
142
|
-
return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
|
|
143
|
-
}
|
|
144
|
-
return options.agentB;
|
|
145
|
-
}
|
|
146
137
|
/** Factory pratique pour conserver la symétrie avec `createConsoleRenderer`. */
|
|
147
138
|
export function createNdjsonRenderer() {
|
|
148
139
|
return new NdjsonRenderer();
|