palabre 0.6.1 → 0.6.4
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 -2
- package/dist/adapters/cli-pty.js +6 -29
- package/dist/adapters/cli-shared.js +24 -0
- package/dist/adapters/cli.js +56 -30
- package/dist/adapters/ollama.js +2 -1
- package/dist/agentRegistry.js +76 -0
- package/dist/args.js +265 -0
- package/dist/config.js +26 -20
- package/dist/discovery.js +3 -13
- package/dist/doctor.js +2 -27
- package/dist/exec.js +17 -0
- package/dist/index.js +9 -262
- package/dist/messages/adapter-errors.js +2 -2
- package/dist/messages/common.js +6 -0
- package/dist/messages/orchestrator.js +19 -0
- package/dist/messages/prompt.js +6 -2
- package/dist/new.js +1 -26
- package/dist/orchestrator.js +7 -14
- package/dist/presets.js +2 -21
- package/dist/prompt.js +4 -0
- package/package.json +3 -2
package/dist/discovery.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { executableExtensions } from "./exec.js";
|
|
3
4
|
/**
|
|
4
5
|
* Détecte en parallèle toutes les CLIs supportées et le serveur Ollama local.
|
|
5
6
|
* Sur Windows, tente `claude.exe` avant `claude`.
|
|
7
|
+
* Antigravity est exposé selon les installations sous `agy` ou `antigravity`.
|
|
6
8
|
*/
|
|
7
9
|
export async function discoverLocalTools() {
|
|
8
10
|
const [codex, claude, gemini, antigravity, opencode, ollamaCommand] = await Promise.all([
|
|
9
11
|
detectCommand("codex"),
|
|
10
12
|
detectFirstCommand(process.platform === "win32" ? ["claude.exe", "claude"] : ["claude"]),
|
|
11
13
|
detectCommand("gemini"),
|
|
12
|
-
|
|
14
|
+
detectFirstCommand(["agy", "antigravity"]),
|
|
13
15
|
detectCommand("opencode"),
|
|
14
16
|
detectCommand("ollama")
|
|
15
17
|
]);
|
|
@@ -100,18 +102,6 @@ async function findExecutable(command) {
|
|
|
100
102
|
}
|
|
101
103
|
return undefined;
|
|
102
104
|
}
|
|
103
|
-
function executableExtensions(command) {
|
|
104
|
-
if (path.extname(command)) {
|
|
105
|
-
return [""];
|
|
106
|
-
}
|
|
107
|
-
if (process.platform !== "win32") {
|
|
108
|
-
return [""];
|
|
109
|
-
}
|
|
110
|
-
return (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
111
|
-
.split(";")
|
|
112
|
-
.map((extension) => extension.toLowerCase())
|
|
113
|
-
.concat(".ps1", "");
|
|
114
|
-
}
|
|
115
105
|
async function isAccessible(filePath) {
|
|
116
106
|
try {
|
|
117
107
|
await access(filePath);
|
package/dist/doctor.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { stat } from "node:fs/promises";
|
|
3
3
|
import { configExists, loadConfig, resolveDefaultConfigPath, resolveOutputDir } from "./config.js";
|
|
4
|
+
import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
|
|
4
5
|
import { discoverLocalTools } from "./discovery.js";
|
|
5
6
|
import { createTranslator, resolveLanguage } from "./i18n.js";
|
|
6
7
|
import { DEFAULT_TURNS, MAX_TURNS } from "./limits.js";
|
|
@@ -194,7 +195,7 @@ function inspectAgentShape(name, agent, lines, t) {
|
|
|
194
195
|
}
|
|
195
196
|
}
|
|
196
197
|
function inspectCliAgent(name, agent, discovery, lines, t) {
|
|
197
|
-
const known =
|
|
198
|
+
const known = detectionForCommand(agent.command, discovery);
|
|
198
199
|
const prefix = `${name} [cli:${agent.role}] command=${agent.command}`;
|
|
199
200
|
if (!known) {
|
|
200
201
|
lines.push(info(t.doctor.customCommand(prefix)));
|
|
@@ -219,37 +220,11 @@ function inspectOllamaAgent(name, agent, discovery, lines, t) {
|
|
|
219
220
|
? ok(t.doctor.ollamaInstalled(prefix))
|
|
220
221
|
: warn(t.doctor.ollamaMissing(prefix, agent.model)));
|
|
221
222
|
}
|
|
222
|
-
function detectedAgentNames(discovery) {
|
|
223
|
-
return [
|
|
224
|
-
discovery.codex.available ? "codex" : undefined,
|
|
225
|
-
discovery.claude.available ? "claude" : undefined,
|
|
226
|
-
discovery.gemini.available ? "gemini" : undefined,
|
|
227
|
-
discovery.antigravity.available ? "antigravity" : undefined,
|
|
228
|
-
discovery.opencode.available ? "opencode" : undefined,
|
|
229
|
-
discovery.ollama.available ? "ollama-local" : undefined
|
|
230
|
-
].filter((name) => Boolean(name));
|
|
231
|
-
}
|
|
232
223
|
function formatCommand(label, available, command, resolvedPath, t) {
|
|
233
224
|
return available
|
|
234
225
|
? ok(t.doctor.commandDetected(label, resolvedPath ?? command))
|
|
235
226
|
: warn(t.doctor.commandMissing(label));
|
|
236
227
|
}
|
|
237
|
-
function knownCliDetection(command, discovery) {
|
|
238
|
-
const normalized = path.basename(command).toLowerCase().replace(/\.(exe|cmd|bat)$/i, "");
|
|
239
|
-
if (normalized === "codex")
|
|
240
|
-
return discovery.codex;
|
|
241
|
-
if (normalized === "claude")
|
|
242
|
-
return discovery.claude;
|
|
243
|
-
if (normalized === "gemini")
|
|
244
|
-
return discovery.gemini;
|
|
245
|
-
if (normalized === "agy")
|
|
246
|
-
return discovery.antigravity;
|
|
247
|
-
if (normalized === "antigravity")
|
|
248
|
-
return discovery.antigravity;
|
|
249
|
-
if (normalized === "opencode")
|
|
250
|
-
return discovery.opencode;
|
|
251
|
-
return undefined;
|
|
252
|
-
}
|
|
253
228
|
function render(lines, plain, t) {
|
|
254
229
|
const hasErrors = lines.some((line) => line.level === "error");
|
|
255
230
|
return {
|
package/dist/exec.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/**
|
|
3
|
+
* Extensions exécutables candidates pour résoudre une commande dans le PATH.
|
|
4
|
+
*
|
|
5
|
+
* Retourne `[""]` quand la commande porte déjà une extension ou hors Windows.
|
|
6
|
+
* Sur Windows sans extension, dérive la liste de `PATHEXT` et ajoute `.ps1`
|
|
7
|
+
* ainsi que la candidate vide (binaire sans extension).
|
|
8
|
+
*/
|
|
9
|
+
export function executableExtensions(command) {
|
|
10
|
+
if (path.extname(command) || process.platform !== "win32") {
|
|
11
|
+
return [""];
|
|
12
|
+
}
|
|
13
|
+
return (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD")
|
|
14
|
+
.split(";")
|
|
15
|
+
.map((extension) => extension.toLowerCase())
|
|
16
|
+
.concat(".ps1", "");
|
|
17
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, writeExampleConfig } from "./config.js";
|
|
5
|
+
import { assertRunnableConfig, configExists, createConfigFromDiscovery, DEFAULT_CONFIG_PATH, GLOBAL_CONFIG_PATH, loadConfig, resolveDefaultConfigPath, resolveOutputDir, writeExampleConfig } from "./config.js";
|
|
6
6
|
import { loadProjectInputs } from "./context.js";
|
|
7
7
|
import { buildContextScan } from "./contextScan.js";
|
|
8
8
|
import { discoverLocalTools } from "./discovery.js";
|
|
@@ -20,6 +20,8 @@ import { runDebate } from "./orchestrator.js";
|
|
|
20
20
|
import { writeDebateMarkdown } from "./output.js";
|
|
21
21
|
import { applySourceUpdate, formatUpdateInstructions, getUpdateInfo } from "./update.js";
|
|
22
22
|
import { createSessionContext } from "./session.js";
|
|
23
|
+
import { getStringListFlag, parseArgs } from "./args.js";
|
|
24
|
+
import { detectedAgentNames, detectionForCommand } from "./agentRegistry.js";
|
|
23
25
|
/** Point d'entrée principal du CLI Palabre. Dispatche vers la commande appropriée selon les arguments. */
|
|
24
26
|
async function main() {
|
|
25
27
|
const rawArgs = process.argv.slice(2);
|
|
@@ -111,6 +113,7 @@ async function main() {
|
|
|
111
113
|
configLanguage: config.language
|
|
112
114
|
});
|
|
113
115
|
const messages = createTranslator(language);
|
|
116
|
+
assertRunnableConfig(config, messages, configPath);
|
|
114
117
|
if (parsed.command === "new") {
|
|
115
118
|
const selection = await runNewWizard(config, messages);
|
|
116
119
|
if (!selection) {
|
|
@@ -469,214 +472,6 @@ async function runContextCommand(flags, positionals) {
|
|
|
469
472
|
console.error(`${messages.renderers.warningPrefix} ${warning}`);
|
|
470
473
|
}
|
|
471
474
|
}
|
|
472
|
-
/**
|
|
473
|
-
* Parse `process.argv` en une structure typée `ParsedArgs`.
|
|
474
|
-
* Gère les flags courts (-h, -v, -s, -t, -a), les flags longs (--topic, --agent-a…),
|
|
475
|
-
* les flags multi-valeurs (--files, --context, --set-defaults) et les positionnels.
|
|
476
|
-
* @param args - Tableau d'arguments (généralement `process.argv.slice(2)`).
|
|
477
|
-
* @returns Commande détectée, indicateur d'explicitation et map de flags.
|
|
478
|
-
*/
|
|
479
|
-
function parseArgs(args, messages) {
|
|
480
|
-
const flags = {};
|
|
481
|
-
let command = "run";
|
|
482
|
-
let commandExplicit = false;
|
|
483
|
-
const positionals = [];
|
|
484
|
-
const commands = new Set(["run", "new", "init", "setup", "help", "version", "update", "doctor", "config", "agent", "agents", "preset", "presets", "context"]);
|
|
485
|
-
const presets = new Set(listPresetNames());
|
|
486
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
487
|
-
const value = args[index];
|
|
488
|
-
if (!value.startsWith("-") && !commandExplicit && positionals.length === 0 && commands.has(value)) {
|
|
489
|
-
command = value;
|
|
490
|
-
commandExplicit = true;
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
if (!value.startsWith("-") && index === 0) {
|
|
494
|
-
if (commands.has(value)) {
|
|
495
|
-
command = value;
|
|
496
|
-
commandExplicit = true;
|
|
497
|
-
}
|
|
498
|
-
else if (isLikelyCommandTypo(value, commands)) {
|
|
499
|
-
throw new Error(messages.common.unknownCommand(value, Array.from(commands).join(", ")));
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
positionals.push(value);
|
|
503
|
-
}
|
|
504
|
-
continue;
|
|
505
|
-
}
|
|
506
|
-
if (!value.startsWith("-")) {
|
|
507
|
-
positionals.push(value);
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (value === "-h") {
|
|
511
|
-
flags.help = true;
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
if (value === "-v") {
|
|
515
|
-
flags.version = true;
|
|
516
|
-
continue;
|
|
517
|
-
}
|
|
518
|
-
if (value === "-a") {
|
|
519
|
-
command = "agents";
|
|
520
|
-
commandExplicit = true;
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
if (value === "-s") {
|
|
524
|
-
const next = args[index + 1];
|
|
525
|
-
if (!next || next.startsWith("-")) {
|
|
526
|
-
throw new Error(messages.common.optionRequiresValue("-s"));
|
|
527
|
-
}
|
|
528
|
-
flags.topic = next;
|
|
529
|
-
index += 1;
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
if (value === "-t") {
|
|
533
|
-
const next = args[index + 1];
|
|
534
|
-
if (!next || next.startsWith("-")) {
|
|
535
|
-
throw new Error(messages.common.optionRequiresValue("-t"));
|
|
536
|
-
}
|
|
537
|
-
flags.turns = next;
|
|
538
|
-
index += 1;
|
|
539
|
-
continue;
|
|
540
|
-
}
|
|
541
|
-
if (value.startsWith("--")) {
|
|
542
|
-
const rawKey = value.slice(2);
|
|
543
|
-
const key = normalizeFlagName(rawKey);
|
|
544
|
-
if (key === "set-defaults") {
|
|
545
|
-
const values = [];
|
|
546
|
-
while (args[index + 1] && !args[index + 1].startsWith("-") && values.length < 2) {
|
|
547
|
-
values.push(args[index + 1]);
|
|
548
|
-
index += 1;
|
|
549
|
-
}
|
|
550
|
-
if (values.length !== 2) {
|
|
551
|
-
throw new Error(messages.common.setDefaultsRequiresTwo);
|
|
552
|
-
}
|
|
553
|
-
flags[key] = values;
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
if (key === "files" || key === "context") {
|
|
557
|
-
const values = [];
|
|
558
|
-
while (args[index + 1] && !args[index + 1].startsWith("-")) {
|
|
559
|
-
values.push(args[index + 1]);
|
|
560
|
-
index += 1;
|
|
561
|
-
}
|
|
562
|
-
flags[key] = [...getStringListFlag(flags[key]), ...values];
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
const next = args[index + 1];
|
|
566
|
-
if (!next || next.startsWith("-")) {
|
|
567
|
-
if (requiresFlagValue(key)) {
|
|
568
|
-
throw new Error(messages.common.optionRequiresValue(`--${rawKey}`));
|
|
569
|
-
}
|
|
570
|
-
flags[key] = true;
|
|
571
|
-
}
|
|
572
|
-
else {
|
|
573
|
-
flags[key] = next;
|
|
574
|
-
index += 1;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (command === "run") {
|
|
579
|
-
applyRunPositionals(positionals, flags, presets, commandExplicit, commands, messages);
|
|
580
|
-
}
|
|
581
|
-
return { command, commandExplicit, positionals, flags };
|
|
582
|
-
}
|
|
583
|
-
/**
|
|
584
|
-
* Détecte si une valeur ressemble à une faute de frappe d'une commande connue
|
|
585
|
-
* (même première lettre et distance de Levenshtein ≤ 2).
|
|
586
|
-
* @param value - Token saisi par l'utilisateur.
|
|
587
|
-
* @param commands - Ensemble des commandes valides.
|
|
588
|
-
*/
|
|
589
|
-
function isLikelyCommandTypo(value, commands) {
|
|
590
|
-
const normalized = value.toLowerCase();
|
|
591
|
-
for (const command of commands) {
|
|
592
|
-
if (normalized[0] === command[0] && levenshteinDistance(normalized, command) <= 2) {
|
|
593
|
-
return true;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return false;
|
|
597
|
-
}
|
|
598
|
-
/**
|
|
599
|
-
* Calcule la distance de Levenshtein entre deux chaînes (insertions, suppressions, substitutions).
|
|
600
|
-
* @param left - Première chaîne.
|
|
601
|
-
* @param right - Deuxième chaîne.
|
|
602
|
-
* @returns Distance entière ≥ 0.
|
|
603
|
-
*/
|
|
604
|
-
function levenshteinDistance(left, right) {
|
|
605
|
-
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
606
|
-
for (let leftIndex = 0; leftIndex < left.length; leftIndex += 1) {
|
|
607
|
-
let diagonal = previous[0];
|
|
608
|
-
previous[0] = leftIndex + 1;
|
|
609
|
-
for (let rightIndex = 0; rightIndex < right.length; rightIndex += 1) {
|
|
610
|
-
const insertCost = previous[rightIndex + 1] + 1;
|
|
611
|
-
const deleteCost = previous[rightIndex] + 1;
|
|
612
|
-
const replaceCost = diagonal + (left[leftIndex] === right[rightIndex] ? 0 : 1);
|
|
613
|
-
diagonal = previous[rightIndex + 1];
|
|
614
|
-
previous[rightIndex + 1] = Math.min(insertCost, deleteCost, replaceCost);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return previous[right.length] ?? 0;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Interprète les arguments positionnels pour la commande `run` :
|
|
621
|
-
* premier positionnel = preset si connu, sinon sujet complet concaténé.
|
|
622
|
-
* @param positionals - Arguments positionnels extraits du parseur.
|
|
623
|
-
* @param flags - Map de flags à muter si un preset ou un sujet est détecté.
|
|
624
|
-
* @param presets - Ensemble des noms de presets valides.
|
|
625
|
-
* @param commandExplicit - `true` si l'utilisateur a tapé `palabre run` explicitement.
|
|
626
|
-
*/
|
|
627
|
-
function applyRunPositionals(positionals, flags, presets, commandExplicit, commands, messages) {
|
|
628
|
-
if (positionals.length === 0) {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
const [first, ...rest] = positionals;
|
|
632
|
-
if (presets.has(first)) {
|
|
633
|
-
flags.preset ??= first;
|
|
634
|
-
if (rest.length > 0) {
|
|
635
|
-
flags.topic ??= rest.join(" ");
|
|
636
|
-
}
|
|
637
|
-
return;
|
|
638
|
-
}
|
|
639
|
-
if (!commandExplicit && positionals.length === 1 && !positionals[0]?.includes(" ")) {
|
|
640
|
-
if (isLikelyCommandTypo(positionals[0], commands)) {
|
|
641
|
-
throw new Error(messages.common.unknownCommand(positionals[0], Array.from(commands).join(", ")));
|
|
642
|
-
}
|
|
643
|
-
throw new Error(messages.common.ambiguousSubject(positionals[0]));
|
|
644
|
-
}
|
|
645
|
-
flags.topic ??= positionals.join(" ");
|
|
646
|
-
}
|
|
647
|
-
/**
|
|
648
|
-
* Normalise un nom de flag long en son alias canonique (ex. `subject` → `topic`).
|
|
649
|
-
* @param value - Nom brut extrait après `--`.
|
|
650
|
-
*/
|
|
651
|
-
function normalizeFlagName(value) {
|
|
652
|
-
const aliases = {
|
|
653
|
-
lang: "language",
|
|
654
|
-
s: "topic",
|
|
655
|
-
subject: "topic",
|
|
656
|
-
t: "turns"
|
|
657
|
-
};
|
|
658
|
-
return aliases[value] ?? value;
|
|
659
|
-
}
|
|
660
|
-
/**
|
|
661
|
-
* Indique si un flag long nécessite une valeur suivante (lève une erreur si absente).
|
|
662
|
-
* @param value - Nom canonique du flag (sans `--`).
|
|
663
|
-
*/
|
|
664
|
-
function requiresFlagValue(value) {
|
|
665
|
-
return new Set([
|
|
666
|
-
"agent-a",
|
|
667
|
-
"agent-b",
|
|
668
|
-
"config",
|
|
669
|
-
"language",
|
|
670
|
-
"model-a",
|
|
671
|
-
"model-b",
|
|
672
|
-
"preset",
|
|
673
|
-
"summary-agent",
|
|
674
|
-
"summary-model",
|
|
675
|
-
"set-defaults",
|
|
676
|
-
"topic",
|
|
677
|
-
"turns"
|
|
678
|
-
]).has(value);
|
|
679
|
-
}
|
|
680
475
|
/** Lit la version depuis `package.json` adjacent au bundle compilé. */
|
|
681
476
|
async function getPackageVersion() {
|
|
682
477
|
const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
@@ -684,20 +479,6 @@ async function getPackageVersion() {
|
|
|
684
479
|
const packageJson = JSON.parse(raw);
|
|
685
480
|
return packageJson.version ?? "0.0.0";
|
|
686
481
|
}
|
|
687
|
-
/**
|
|
688
|
-
* Normalise une valeur de flag multi-valeur en tableau de chaînes.
|
|
689
|
-
* @param value - Valeur brute (tableau, chaîne unique ou absent).
|
|
690
|
-
* @returns Tableau de chaînes, vide si la valeur n'est pas applicable.
|
|
691
|
-
*/
|
|
692
|
-
function getStringListFlag(value) {
|
|
693
|
-
if (Array.isArray(value)) {
|
|
694
|
-
return value;
|
|
695
|
-
}
|
|
696
|
-
if (typeof value === "string") {
|
|
697
|
-
return [value];
|
|
698
|
-
}
|
|
699
|
-
return [];
|
|
700
|
-
}
|
|
701
482
|
/**
|
|
702
483
|
* Écrit les avertissements de contexte sur `stderr`.
|
|
703
484
|
* @param warnings - Messages d'avertissement issus du chargement des fichiers de contexte.
|
|
@@ -728,15 +509,7 @@ function syncDetectedAgents(config, discovery) {
|
|
|
728
509
|
* @param discovery - Résultat de la découverte locale des outils.
|
|
729
510
|
*/
|
|
730
511
|
function findDetectedMissingAgents(config, discovery) {
|
|
731
|
-
|
|
732
|
-
discovery.codex.available ? "codex" : undefined,
|
|
733
|
-
discovery.claude.available ? "claude" : undefined,
|
|
734
|
-
discovery.gemini.available ? "gemini" : undefined,
|
|
735
|
-
discovery.antigravity.available ? "antigravity" : undefined,
|
|
736
|
-
discovery.opencode.available ? "opencode" : undefined,
|
|
737
|
-
discovery.ollama.available ? "ollama-local" : undefined
|
|
738
|
-
].filter((agent) => Boolean(agent));
|
|
739
|
-
return detectedAgents.filter((agentName) => !config.agents[agentName]);
|
|
512
|
+
return detectedAgentNames(discovery).filter((agentName) => !config.agents[agentName]);
|
|
740
513
|
}
|
|
741
514
|
/**
|
|
742
515
|
* Affiche la liste des agents déclarés avec leur type, rôle, état de détection et défauts.
|
|
@@ -807,34 +580,15 @@ function formatAgentDetection(name, agentConfig, discovery, messages) {
|
|
|
807
580
|
return detection.available ? messages.agents.detected(detection.command) : messages.agents.notDetected;
|
|
808
581
|
}
|
|
809
582
|
/**
|
|
810
|
-
* Résout l'entrée de détection correspondant à un agent CLI
|
|
583
|
+
* Résout l'entrée de détection correspondant à un agent CLI.
|
|
811
584
|
* Renvoie un objet `{ available: true }` pour les agents CLI non reconnus (considérés disponibles).
|
|
812
585
|
* @param name - Nom de l'agent dans la config.
|
|
813
586
|
* @param agentConfig - Configuration de l'agent.
|
|
814
587
|
* @param discovery - Résultat de la découverte locale des outils.
|
|
815
588
|
*/
|
|
816
589
|
function cliDetectionForAgent(name, agentConfig, discovery) {
|
|
817
|
-
const command =
|
|
818
|
-
|
|
819
|
-
return discovery.codex;
|
|
820
|
-
if (command === "claude")
|
|
821
|
-
return discovery.claude;
|
|
822
|
-
if (command === "gemini")
|
|
823
|
-
return discovery.gemini;
|
|
824
|
-
if (command === "agy")
|
|
825
|
-
return discovery.antigravity;
|
|
826
|
-
if (command === "antigravity")
|
|
827
|
-
return discovery.antigravity;
|
|
828
|
-
if (command === "opencode")
|
|
829
|
-
return discovery.opencode;
|
|
830
|
-
return { available: true, command: agentConfig.type === "cli" || agentConfig.type === "cli-pty" ? agentConfig.command : name };
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Extrait le nom de base d'une commande en supprimant le chemin et l'extension Windows éventuelle.
|
|
834
|
-
* @param command - Chemin ou nom de commande brut (ex. `C:\bin\claude.cmd`).
|
|
835
|
-
*/
|
|
836
|
-
function normalizeCommandName(command) {
|
|
837
|
-
return path.basename(command).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
|
|
590
|
+
const command = agentConfig.type === "cli" || agentConfig.type === "cli-pty" ? agentConfig.command : name;
|
|
591
|
+
return detectionForCommand(command, discovery) ?? { available: true, command };
|
|
838
592
|
}
|
|
839
593
|
/**
|
|
840
594
|
* Affiche le récapitulatif de détection locale après `palabre init`.
|
|
@@ -857,14 +611,7 @@ function printInitDiscovery(discovery, config, messages) {
|
|
|
857
611
|
console.log(messages.init.languageHint(config.language ?? DEFAULT_LANGUAGE));
|
|
858
612
|
}
|
|
859
613
|
function formatDetectedAgentSummary(discovery, language) {
|
|
860
|
-
const names =
|
|
861
|
-
discovery.codex.available ? "codex" : undefined,
|
|
862
|
-
discovery.claude.available ? "claude" : undefined,
|
|
863
|
-
discovery.gemini.available ? "gemini" : undefined,
|
|
864
|
-
discovery.antigravity.available ? "antigravity" : undefined,
|
|
865
|
-
discovery.opencode.available ? "opencode" : undefined,
|
|
866
|
-
discovery.ollama.available ? "ollama-local" : undefined
|
|
867
|
-
].filter((name) => Boolean(name));
|
|
614
|
+
const names = detectedAgentNames(discovery);
|
|
868
615
|
if (names.length === 0) {
|
|
869
616
|
return language === "en" ? "no agent detected" : "aucun agent détecté";
|
|
870
617
|
}
|
|
@@ -8,7 +8,7 @@ const frHints = {
|
|
|
8
8
|
"usage-limit": "Attends la fenetre indiquee par la CLI, change de modele ou relance avec un autre agent/preset disponible.",
|
|
9
9
|
"non-zero-exit": "Teste la commande directement, puis ajuste args, permissions, modele ou authentification de la CLI.",
|
|
10
10
|
"model-unavailable": "Installe le modele Ollama ou relance avec --pull-models pour autoriser le telechargement.",
|
|
11
|
-
"unsupported-model": "
|
|
11
|
+
"unsupported-model": "Mets a jour la CLI de l'agent, verifie le nom du modele et ton abonnement, ou retire --model-a/--model-b/--summary-model pour laisser la CLI utiliser son modele par defaut.",
|
|
12
12
|
"model-pull-failed": "Verifie le nom du modele, ta connexion et l'espace disque disponible.",
|
|
13
13
|
"http-error": "Verifie que le service local est lance et que baseUrl est correct."
|
|
14
14
|
};
|
|
@@ -22,7 +22,7 @@ const enHints = {
|
|
|
22
22
|
"usage-limit": "Wait for the window indicated by the CLI, change model, or run again with another available agent/preset.",
|
|
23
23
|
"non-zero-exit": "Test the command directly, then adjust args, permissions, model, or CLI authentication.",
|
|
24
24
|
"model-unavailable": "Install the Ollama model or run again with --pull-models to allow downloading.",
|
|
25
|
-
"unsupported-model": "
|
|
25
|
+
"unsupported-model": "Update the agent CLI, check the model name and your subscription, or remove --model-a/--model-b/--summary-model so the CLI can use its default model.",
|
|
26
26
|
"model-pull-failed": "Check the model name, your connection, and available disk space.",
|
|
27
27
|
"http-error": "Check that the local service is running and baseUrl is correct."
|
|
28
28
|
};
|
package/dist/messages/common.js
CHANGED
|
@@ -10,6 +10,9 @@ export const commonMessages = {
|
|
|
10
10
|
unknownAgentForField: (field, agent, available) => `Agent inconnu pour ${field}: ${agent}. Agents disponibles: ${available}.`,
|
|
11
11
|
unknownAgent: (agent) => `Agent inconnu: ${agent}`,
|
|
12
12
|
unknownRenderer: (value, supported) => `Renderer inconnu: ${value}. Valeurs supportées: ${supported}.`,
|
|
13
|
+
configInvalidShape: (configPath) => `Config invalide: ${configPath} ne contient pas un objet JSON. Relance palabre init ou corrige le fichier.`,
|
|
14
|
+
configMissingAgents: (configPath) => `Config invalide: ${configPath} ne déclare pas de bloc "agents". Relance palabre init ou ajoute au moins un agent.`,
|
|
15
|
+
configEmptyAgents: (configPath) => `Config invalide: ${configPath} ne déclare aucun agent. Ajoute au moins un agent ou relance palabre init.`,
|
|
13
16
|
errorPrefix: "Erreur"
|
|
14
17
|
},
|
|
15
18
|
en: {
|
|
@@ -23,6 +26,9 @@ export const commonMessages = {
|
|
|
23
26
|
unknownAgentForField: (field, agent, available) => `Unknown agent for ${field}: ${agent}. Available agents: ${available}.`,
|
|
24
27
|
unknownAgent: (agent) => `Unknown agent: ${agent}`,
|
|
25
28
|
unknownRenderer: (value, supported) => `Unknown renderer: ${value}. Supported values: ${supported}.`,
|
|
29
|
+
configInvalidShape: (configPath) => `Invalid config: ${configPath} does not contain a JSON object. Run palabre init or fix the file.`,
|
|
30
|
+
configMissingAgents: (configPath) => `Invalid config: ${configPath} has no "agents" block. Run palabre init or add at least one agent.`,
|
|
31
|
+
configEmptyAgents: (configPath) => `Invalid config: ${configPath} declares no agent. Add at least one agent or run palabre init.`,
|
|
26
32
|
errorPrefix: "Error"
|
|
27
33
|
}
|
|
28
34
|
};
|
|
@@ -2,12 +2,31 @@ export const orchestratorMessages = {
|
|
|
2
2
|
fr: {
|
|
3
3
|
agreementStopReason: "Accord clair detecte apres un tour complet.",
|
|
4
4
|
earlyStop: (reason) => `Arret anticipe: ${reason}`,
|
|
5
|
+
agreementPatterns: [
|
|
6
|
+
"accord complet",
|
|
7
|
+
"accord total",
|
|
8
|
+
"aucun desaccord",
|
|
9
|
+
"aucune incertitude",
|
|
10
|
+
"rien a trancher",
|
|
11
|
+
"rien a ajouter",
|
|
12
|
+
"question factuelle resolue"
|
|
13
|
+
],
|
|
5
14
|
ollamaNoContext: (agentNames) => `${agentNames} ne lit pas le filesystem. Ajoute --files ou --context pour fournir un contexte projet.`,
|
|
6
15
|
unknownSummaryAgent: (agentName) => `Agent de synthese inconnu: ${agentName}`
|
|
7
16
|
},
|
|
8
17
|
en: {
|
|
9
18
|
agreementStopReason: "Clear agreement detected after a complete round.",
|
|
10
19
|
earlyStop: (reason) => `Early stop: ${reason}`,
|
|
20
|
+
agreementPatterns: [
|
|
21
|
+
"full agreement",
|
|
22
|
+
"complete agreement",
|
|
23
|
+
"total agreement",
|
|
24
|
+
"no disagreement",
|
|
25
|
+
"no remaining uncertainty",
|
|
26
|
+
"nothing to settle",
|
|
27
|
+
"nothing to add",
|
|
28
|
+
"factual question resolved"
|
|
29
|
+
],
|
|
11
30
|
ollamaNoContext: (agentNames) => `${agentNames} cannot read the filesystem. Add --files or --context to provide project context.`,
|
|
12
31
|
unknownSummaryAgent: (agentName) => `Unknown summary agent: ${agentName}`
|
|
13
32
|
}
|
package/dist/messages/prompt.js
CHANGED
|
@@ -29,6 +29,7 @@ export const promptMessages = {
|
|
|
29
29
|
cwd: (value) => `- Dossier courant: ${value}`,
|
|
30
30
|
sessionStartedAt: (value) => `- Session demarree a: ${value}`,
|
|
31
31
|
turnProgress: (turn, totalTurns) => `- Tour courant: ${turn}/${totalTurns}`,
|
|
32
|
+
responseLanguageInstruction: "Langue de reponse obligatoire: francais. Reponds uniquement en francais, meme si le sujet ou le transcript contient une autre langue.",
|
|
32
33
|
objectiveTitle: "Objectif:",
|
|
33
34
|
debateObjectives: [
|
|
34
35
|
"- Apporte une reponse utile, concrete et courte.",
|
|
@@ -55,7 +56,8 @@ export const promptMessages = {
|
|
|
55
56
|
actionsHeading: "### Actions proposees",
|
|
56
57
|
conclusionHeading: "### Conclusion",
|
|
57
58
|
finalProseInstruction: "Un court paragraphe de synthese en prose, sans liste, qui resume le sens general du debat et la decision ou direction la plus raisonnable.",
|
|
58
|
-
summaryAnswerTitle: "Synthese:"
|
|
59
|
+
summaryAnswerTitle: "Synthese:",
|
|
60
|
+
ollamaSystemPrompt: "Tu participes a un debat technique orchestre. Reste precis, utile et honnete sur tes limites."
|
|
59
61
|
},
|
|
60
62
|
en: {
|
|
61
63
|
subject: (topic) => `Subject: ${topic}`,
|
|
@@ -71,6 +73,7 @@ export const promptMessages = {
|
|
|
71
73
|
cwd: (value) => `- Current directory: ${value}`,
|
|
72
74
|
sessionStartedAt: (value) => `- Session started at: ${value}`,
|
|
73
75
|
turnProgress: (turn, totalTurns) => `- Current turn: ${turn}/${totalTurns}`,
|
|
76
|
+
responseLanguageInstruction: "Required response language: English. Answer only in English, even if the subject or transcript contains another language.",
|
|
74
77
|
objectiveTitle: "Objective:",
|
|
75
78
|
debateObjectives: [
|
|
76
79
|
"- Provide a useful, concrete, and concise answer.",
|
|
@@ -97,6 +100,7 @@ export const promptMessages = {
|
|
|
97
100
|
actionsHeading: "### Proposed actions",
|
|
98
101
|
conclusionHeading: "### Conclusion",
|
|
99
102
|
finalProseInstruction: "A short prose summary paragraph, without a list, that captures the general meaning of the debate and the most reasonable decision or direction.",
|
|
100
|
-
summaryAnswerTitle: "Summary:"
|
|
103
|
+
summaryAnswerTitle: "Summary:",
|
|
104
|
+
ollamaSystemPrompt: "You are taking part in an orchestrated technical debate. Stay precise, useful, and honest about your limits."
|
|
101
105
|
}
|
|
102
106
|
};
|
package/dist/new.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { isAgentDetected } from "./agentRegistry.js";
|
|
3
4
|
import { discoverLocalTools } from "./discovery.js";
|
|
4
5
|
import { findPresetNameForPair } from "./presets.js";
|
|
5
6
|
import { MAX_TURNS, turnsOrDefault, validateTurns } from "./limits.js";
|
|
@@ -136,25 +137,6 @@ function buildAgentChoices(config, discovery, messages) {
|
|
|
136
137
|
})
|
|
137
138
|
.sort((left, right) => Number(right.detected) - Number(left.detected) || left.name.localeCompare(right.name));
|
|
138
139
|
}
|
|
139
|
-
function isAgentDetected(name, config, discovery) {
|
|
140
|
-
if (config.type === "ollama") {
|
|
141
|
-
return discovery.ollama.available;
|
|
142
|
-
}
|
|
143
|
-
const normalized = normalizeCommandName(config.command || name);
|
|
144
|
-
if (normalized === "codex")
|
|
145
|
-
return discovery.codex.available;
|
|
146
|
-
if (normalized === "claude")
|
|
147
|
-
return discovery.claude.available;
|
|
148
|
-
if (normalized === "gemini")
|
|
149
|
-
return discovery.gemini.available;
|
|
150
|
-
if (normalized === "agy")
|
|
151
|
-
return discovery.antigravity.available;
|
|
152
|
-
if (normalized === "antigravity")
|
|
153
|
-
return discovery.antigravity.available;
|
|
154
|
-
if (normalized === "opencode")
|
|
155
|
-
return discovery.opencode.available;
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
140
|
function agentStatus(_name, config, discovery, detected, messages) {
|
|
159
141
|
if (config.type === "ollama") {
|
|
160
142
|
return detected
|
|
@@ -248,13 +230,6 @@ function splitPaths(value) {
|
|
|
248
230
|
.map((entry) => entry.trim())
|
|
249
231
|
.filter(Boolean) ?? [];
|
|
250
232
|
}
|
|
251
|
-
function normalizeCommandName(command) {
|
|
252
|
-
return command
|
|
253
|
-
.split(/[\\/]/)
|
|
254
|
-
.pop()
|
|
255
|
-
?.toLowerCase()
|
|
256
|
-
.replace(/\.(exe|cmd|bat|ps1)$/i, "") ?? command.toLowerCase();
|
|
257
|
-
}
|
|
258
233
|
function isQuit(value) {
|
|
259
234
|
return ["q", "quit", "exit"].includes(value.toLowerCase());
|
|
260
235
|
}
|
package/dist/orchestrator.js
CHANGED
|
@@ -78,7 +78,7 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
78
78
|
};
|
|
79
79
|
transcript.push(message);
|
|
80
80
|
renderer?.message(message.content);
|
|
81
|
-
if (shouldStopOnAgreement(options, transcript)) {
|
|
81
|
+
if (shouldStopOnAgreement(options, transcript, messages)) {
|
|
82
82
|
stopReason = messages.orchestrator.agreementStopReason;
|
|
83
83
|
renderer?.notice(messages.orchestrator.earlyStop(stopReason));
|
|
84
84
|
break;
|
|
@@ -110,28 +110,21 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
110
110
|
/**
|
|
111
111
|
* Heuristique d'arrêt sur accord explicite.
|
|
112
112
|
* Ne s'active qu'après un tour complet (nombre pair de messages) pour éviter les faux positifs.
|
|
113
|
+
* Les phrases d'accord proviennent du dictionnaire i18n pour suivre la langue d'interface.
|
|
113
114
|
* Intentionnellement prudente : ne remplace pas une évaluation sémantique réelle.
|
|
114
115
|
*/
|
|
115
|
-
function shouldStopOnAgreement(options, messages) {
|
|
116
|
-
if (!options.earlyStopOnAgreement ||
|
|
116
|
+
function shouldStopOnAgreement(options, transcript, messages) {
|
|
117
|
+
if (!options.earlyStopOnAgreement || transcript.length < 2 || transcript.length % 2 !== 0) {
|
|
117
118
|
return false;
|
|
118
119
|
}
|
|
119
|
-
const latest = normalizeForAgreement(
|
|
120
|
+
const latest = normalizeForAgreement(transcript[transcript.length - 1]?.content ?? "");
|
|
120
121
|
if (!latest) {
|
|
121
122
|
return false;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
"accord complet",
|
|
125
|
-
"accord total",
|
|
126
|
-
"aucun desaccord",
|
|
127
|
-
"aucune incertitude",
|
|
128
|
-
"rien a trancher",
|
|
129
|
-
"rien a ajouter",
|
|
130
|
-
"question factuelle resolue"
|
|
131
|
-
];
|
|
132
|
-
if (positivePatterns.some((pattern) => latest.includes(pattern))) {
|
|
124
|
+
if (messages.orchestrator.agreementPatterns.some((pattern) => latest.includes(pattern))) {
|
|
133
125
|
return true;
|
|
134
126
|
}
|
|
127
|
+
// Combinaison française historique : confirmation explicite + absence de point ouvert.
|
|
135
128
|
return (latest.includes("confirme") || latest.includes("acte")) &&
|
|
136
129
|
(latest.includes("aucun") || latest.includes("rien a trancher") || latest.includes("rien a ajouter"));
|
|
137
130
|
}
|