palabre 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/agents.js +85 -0
- package/dist/commands/context.js +25 -0
- package/dist/commands/history.js +28 -0
- package/dist/commands/init.js +59 -0
- package/dist/commands/presets.js +34 -0
- package/dist/commands/shared.js +4 -0
- package/dist/commands/update.js +21 -0
- package/dist/discovery.js +8 -1
- package/dist/index.js +26 -770
- package/dist/limits.js +1 -0
- package/dist/orchestrator.js +7 -15
- package/dist/output.js +1 -10
- package/dist/renderers/console.js +1 -7
- package/dist/renderers/ndjson.js +1 -10
- package/dist/renderers/tui.js +1 -10
- package/dist/runOptions.js +66 -0
- package/dist/tuiController.js +400 -0
- package/package.json +3 -2
package/dist/limits.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTranslator } from "./i18n.js";
|
|
2
2
|
export const DEFAULT_TURNS = 4;
|
|
3
|
+
export const MAX_ASK_AGENTS = 4;
|
|
3
4
|
export const MAX_TURNS = 20;
|
|
4
5
|
/** Convertit `value` en nombre et valide la plage [1, `MAX_TURNS`]. Lève une erreur si invalide. */
|
|
5
6
|
export function parseTurns(value, label = "--turns", messages = createTranslator("fr")) {
|
package/dist/orchestrator.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createAgent } from "./adapters/index.js";
|
|
2
2
|
import { AdapterError } from "./errors.js";
|
|
3
3
|
import { createTranslator } from "./i18n.js";
|
|
4
|
+
import { MAX_ASK_AGENTS } from "./limits.js";
|
|
4
5
|
import { OllamaUrlError } from "./ollamaUrl.js";
|
|
5
|
-
export
|
|
6
|
+
export { MAX_ASK_AGENTS } from "./limits.js";
|
|
6
7
|
/**
|
|
7
8
|
* Point d'entrée de l'orchestration.
|
|
8
9
|
* Lance le ping-pong entre `agentA` et `agentB` pendant `options.turns` tours,
|
|
@@ -106,7 +107,7 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
106
107
|
try {
|
|
107
108
|
const cancellation = cancellationFailureIfAborted(options, messages, {
|
|
108
109
|
phase: "summary",
|
|
109
|
-
agent:
|
|
110
|
+
agent: options.summaryAgent,
|
|
110
111
|
role: summaryRole(),
|
|
111
112
|
turn: transcript.length + 1
|
|
112
113
|
});
|
|
@@ -124,7 +125,7 @@ export async function runDebate(config, options, renderer, messages = createTran
|
|
|
124
125
|
catch (error) {
|
|
125
126
|
failure = toDebateFailure(error, {
|
|
126
127
|
phase: "summary",
|
|
127
|
-
agent:
|
|
128
|
+
agent: options.summaryAgent,
|
|
128
129
|
role: summaryRole(),
|
|
129
130
|
turn: transcript.length + 1
|
|
130
131
|
}, messages);
|
|
@@ -242,7 +243,7 @@ export async function runAsk(config, options, renderer, messages = createTransla
|
|
|
242
243
|
let failure;
|
|
243
244
|
if (options.summaryEnabled) {
|
|
244
245
|
try {
|
|
245
|
-
const summaryAgentName =
|
|
246
|
+
const summaryAgentName = options.summaryAgent;
|
|
246
247
|
const cancellation = cancellationFailureIfAborted(options, messages, {
|
|
247
248
|
phase: "summary",
|
|
248
249
|
agent: summaryAgentName,
|
|
@@ -262,7 +263,7 @@ export async function runAsk(config, options, renderer, messages = createTransla
|
|
|
262
263
|
catch (error) {
|
|
263
264
|
failure = toDebateFailure(error, {
|
|
264
265
|
phase: "summary",
|
|
265
|
-
agent:
|
|
266
|
+
agent: options.summaryAgent,
|
|
266
267
|
role: summaryRole(),
|
|
267
268
|
turn: transcript.length + 1
|
|
268
269
|
}, messages);
|
|
@@ -330,7 +331,7 @@ function warnIfOllamaHasNoContext(options, agents, renderer, messages = createTr
|
|
|
330
331
|
* @throws {Error} si l'agent de synthèse est absent de `config.agents`.
|
|
331
332
|
*/
|
|
332
333
|
async function generateSummary(config, options, transcript, renderer, messages = createTranslator("fr")) {
|
|
333
|
-
const summaryAgentName =
|
|
334
|
+
const summaryAgentName = options.summaryAgent;
|
|
334
335
|
const summaryModel = options.summaryModel ?? modelForAgent(options, summaryAgentName);
|
|
335
336
|
const summaryConfig = withRuntimeOverrides(config.agents[summaryAgentName], summaryModel, options.pullModels);
|
|
336
337
|
if (!summaryConfig) {
|
|
@@ -385,15 +386,6 @@ function resolveAskAgentNames(options) {
|
|
|
385
386
|
: [options.agentA, options.agentB];
|
|
386
387
|
return agents.filter((agent, index) => Boolean(agent) && agents.indexOf(agent) === index);
|
|
387
388
|
}
|
|
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
389
|
function toDebateFailure(error, context, messages) {
|
|
398
390
|
if (error instanceof AdapterError) {
|
|
399
391
|
return {
|
package/dist/output.js
CHANGED
|
@@ -94,7 +94,7 @@ function renderSessionHeader(options, debateMessages, stopReason, messages) {
|
|
|
94
94
|
[messages.output.fields.mode, options.mode],
|
|
95
95
|
[messages.output.fields.agents, formatAgentsForHeader(options)],
|
|
96
96
|
[messages.output.fields.autoPullOllama, options.pullModels ? messages.output.yes : messages.output.no],
|
|
97
|
-
[messages.output.fields.summary, options.summaryEnabled ?
|
|
97
|
+
[messages.output.fields.summary, options.summaryEnabled ? options.summaryAgent : messages.output.disabled],
|
|
98
98
|
[
|
|
99
99
|
options.mode === "ask" ? messages.output.fields.requestedResponses : messages.output.fields.requestedTurns,
|
|
100
100
|
String(options.mode === "ask" ? options.askAgents?.length ?? debateMessages.length : options.turns)
|
|
@@ -124,15 +124,6 @@ function renderFileList(files, messages) {
|
|
|
124
124
|
}
|
|
125
125
|
return files.map((file) => `- \`${file.path}\` (${file.sizeBytes} ${messages.output.fileSizeUnit})`);
|
|
126
126
|
}
|
|
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
127
|
function formatAgentsForHeader(options) {
|
|
137
128
|
if (options.mode === "ask") {
|
|
138
129
|
return (options.askAgents && options.askAgents.length > 0 ? options.askAgents : [options.agentA, options.agentB]).join(", ");
|
|
@@ -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;
|
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();
|
package/dist/renderers/tui.js
CHANGED
|
@@ -747,13 +747,7 @@ function formatSummary(options, messages) {
|
|
|
747
747
|
if (!options.summaryEnabled) {
|
|
748
748
|
return messages.renderers.disabled;
|
|
749
749
|
}
|
|
750
|
-
|
|
751
|
-
return options.summaryAgent;
|
|
752
|
-
}
|
|
753
|
-
if (options.mode === "ask" && options.askAgents && options.askAgents.length > 0) {
|
|
754
|
-
return options.askAgents[options.askAgents.length - 1] ?? options.agentB;
|
|
755
|
-
}
|
|
756
|
-
return options.agentB;
|
|
750
|
+
return options.summaryAgent;
|
|
757
751
|
}
|
|
758
752
|
function formatResponseCount(options) {
|
|
759
753
|
return options.mode === "ask" ? options.askAgents?.length ?? 2 : options.turns;
|
|
@@ -1032,9 +1026,6 @@ function accent(value) {
|
|
|
1032
1026
|
function violet(value) {
|
|
1033
1027
|
return supportsColor ? `${codes.violet}${value}${codes.reset}` : value;
|
|
1034
1028
|
}
|
|
1035
|
-
function muted(value) {
|
|
1036
|
-
return supportsColor ? `${codes.gray}${value}${codes.reset}` : value;
|
|
1037
|
-
}
|
|
1038
1029
|
function agentLabel(agent) {
|
|
1039
1030
|
return supportsColor ? `${agentAnsi(agent)}${agent}${codes.reset}` : agent;
|
|
1040
1031
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getStringListFlag } from "./args.js";
|
|
2
|
+
import { DEFAULT_TURNS, MAX_ASK_AGENTS, parseTurnsFlag } from "./limits.js";
|
|
3
|
+
import { normalizeOllamaBaseUrl } from "./ollamaUrl.js";
|
|
4
|
+
import { createSessionContext } from "./session.js";
|
|
5
|
+
import { askAgentSeedsForMode } from "./tuiState.js";
|
|
6
|
+
import { optionalString } from "./commands/shared.js";
|
|
7
|
+
/** Resolves flags and defaults into complete orchestrator options. */
|
|
8
|
+
export function resolveRunOptions(input, messages) {
|
|
9
|
+
const { flags, config, language, topic, files, preset, signal } = input;
|
|
10
|
+
const mode = parseModeFlag(optionalString(flags.mode) ?? config.defaults?.mode, messages);
|
|
11
|
+
const explicitAskAgents = getStringListFlag(flags.agents);
|
|
12
|
+
const askAgentSeeds = askAgentSeedsForMode(mode, explicitAskAgents, config.defaults?.askAgents);
|
|
13
|
+
const agentA = resolveAgentName("agent A", flags["agent-a"], preset?.agentA, askAgentSeeds[0] ?? config.defaults?.agentA, messages);
|
|
14
|
+
const agentB = resolveAgentName("agent B", flags["agent-b"], preset?.agentB, askAgentSeeds[1] ?? askAgentSeeds[0] ?? config.defaults?.agentB, messages);
|
|
15
|
+
const askAgents = mode === "ask" ? resolveAskAgents(explicitAskAgents, config.defaults?.askAgents, [agentA, agentB], messages) : undefined;
|
|
16
|
+
const ollamaUrl = optionalString(flags["ollama-url"]);
|
|
17
|
+
return {
|
|
18
|
+
mode,
|
|
19
|
+
language,
|
|
20
|
+
topic,
|
|
21
|
+
agentA,
|
|
22
|
+
agentB,
|
|
23
|
+
askAgents,
|
|
24
|
+
turns: parseTurnsFlag(flags.turns, config.defaults?.turns ?? DEFAULT_TURNS, "--turns", messages),
|
|
25
|
+
session: createSessionContext(),
|
|
26
|
+
files,
|
|
27
|
+
modelA: optionalString(flags["model-a"]),
|
|
28
|
+
modelB: optionalString(flags["model-b"]),
|
|
29
|
+
ollamaUrl: ollamaUrl ? normalizeOllamaBaseUrl(ollamaUrl) : undefined,
|
|
30
|
+
pullModels: Boolean(flags["pull-models"]),
|
|
31
|
+
summaryAgent: resolveSummaryAgent(flags["summary-agent"], config.defaults, mode, askAgents, agentB),
|
|
32
|
+
summaryModel: optionalString(flags["summary-model"]),
|
|
33
|
+
summaryEnabled: !flags["no-summary"],
|
|
34
|
+
earlyStopOnAgreement: !flags["no-early-stop"],
|
|
35
|
+
plainOutput: Boolean(flags.plain || flags.terminal),
|
|
36
|
+
signal
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function resolveAgentName(label, explicitValue, presetValue, defaultValue, messages) {
|
|
40
|
+
const resolved = optionalString(explicitValue) ?? presetValue ?? defaultValue;
|
|
41
|
+
if (!resolved)
|
|
42
|
+
throw new Error(messages.common.noAgentDefined(label));
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function resolveSummaryAgent(explicitValue, defaults, mode, askAgents, agentB) {
|
|
46
|
+
const explicit = optionalString(explicitValue);
|
|
47
|
+
if (explicit)
|
|
48
|
+
return explicit;
|
|
49
|
+
if (mode === "ask")
|
|
50
|
+
return defaults?.askSummaryAgent ?? defaults?.summaryAgent ?? askAgents?.at(-1) ?? agentB;
|
|
51
|
+
return defaults?.summaryAgent ?? agentB;
|
|
52
|
+
}
|
|
53
|
+
function parseModeFlag(value, messages) {
|
|
54
|
+
if (!value)
|
|
55
|
+
return "debate";
|
|
56
|
+
if (value === "debate" || value === "ask")
|
|
57
|
+
return value;
|
|
58
|
+
throw new Error(messages.common.unknownMode(value, "debate, ask"));
|
|
59
|
+
}
|
|
60
|
+
function resolveAskAgents(explicitAgents, defaultAgents, fallbackAgents, messages) {
|
|
61
|
+
const selected = explicitAgents.length > 0 ? explicitAgents : defaultAgents && defaultAgents.length > 0 ? defaultAgents : fallbackAgents;
|
|
62
|
+
const unique = selected.filter((agent, index) => agent.trim() && selected.indexOf(agent) === index);
|
|
63
|
+
if (unique.length > MAX_ASK_AGENTS)
|
|
64
|
+
throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
|
|
65
|
+
return unique;
|
|
66
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { setOllamaBaseUrl, setOllamaModel, syncDetectedAgentsDetailed, syncOllamaModel, writeExampleConfig } from "./config.js";
|
|
2
|
+
import { discoverLocalToolsForConfig } from "./discovery.js";
|
|
3
|
+
import { AdapterError, formatAdapterError } from "./errors.js";
|
|
4
|
+
import { createTranslator, DEFAULT_LANGUAGE, parseLanguage } from "./i18n.js";
|
|
5
|
+
import { MAX_ASK_AGENTS, validateTurns } from "./limits.js";
|
|
6
|
+
import { DEFAULT_OLLAMA_BASE_URL, normalizeOllamaBaseUrl, OllamaUrlError, resolveOllamaBaseUrl } from "./ollamaUrl.js";
|
|
7
|
+
import { promptTuiAgentsWizard, promptTuiConfigCommand, promptTuiRolesWizard, renderTuiConfig } from "./renderers/tui.js";
|
|
8
|
+
export async function runTuiConfigLoop(configPath, config, messages, initialMode) {
|
|
9
|
+
let mode = initialMode;
|
|
10
|
+
let notice;
|
|
11
|
+
let currentMessages = messages;
|
|
12
|
+
let changedRunDefaults = false;
|
|
13
|
+
for (;;) {
|
|
14
|
+
renderTuiConfig(config, configPath, mode, currentMessages, { message: notice });
|
|
15
|
+
notice = undefined;
|
|
16
|
+
const input = await promptTuiConfigCommand(mode, currentMessages);
|
|
17
|
+
if (input.kind === "quit") {
|
|
18
|
+
return { mode, quit: true, changedRunDefaults };
|
|
19
|
+
}
|
|
20
|
+
if (input.kind === "back") {
|
|
21
|
+
return { mode, quit: false, changedRunDefaults };
|
|
22
|
+
}
|
|
23
|
+
if (input.kind === "unknown") {
|
|
24
|
+
notice = input.message;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (input.kind === "mode") {
|
|
28
|
+
mode = mode === "ask" ? "debate" : "ask";
|
|
29
|
+
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
30
|
+
await writeExampleConfig(configPath, config);
|
|
31
|
+
changedRunDefaults = true;
|
|
32
|
+
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (input.kind === "default-mode") {
|
|
36
|
+
config.defaults = { ...(config.defaults ?? {}), mode };
|
|
37
|
+
await writeExampleConfig(configPath, config);
|
|
38
|
+
changedRunDefaults = true;
|
|
39
|
+
notice = mode === "ask" ? currentMessages.tui.askDefaultMode : currentMessages.tui.debateDefaultMode;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (input.kind === "interface") {
|
|
43
|
+
config.defaults = { ...(config.defaults ?? {}), interface: input.interfaceName };
|
|
44
|
+
await writeExampleConfig(configPath, config);
|
|
45
|
+
notice = currentMessages.tui.interfaceDefault(input.interfaceName);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (input.kind === "language") {
|
|
49
|
+
config.language = parseLanguage(input.language, "--language");
|
|
50
|
+
await writeExampleConfig(configPath, config);
|
|
51
|
+
currentMessages = createTranslator(config.language ?? DEFAULT_LANGUAGE);
|
|
52
|
+
notice = currentMessages.tui.languageUpdated(input.language);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (input.kind === "agents") {
|
|
56
|
+
try {
|
|
57
|
+
const agentsInput = input.agents.length > 0
|
|
58
|
+
? { kind: "agents", agents: input.agents }
|
|
59
|
+
: await promptTuiAgentsWizard(config, mode, currentMessages);
|
|
60
|
+
if (agentsInput.kind === "quit") {
|
|
61
|
+
return { mode, quit: true, changedRunDefaults };
|
|
62
|
+
}
|
|
63
|
+
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
64
|
+
notice = currentMessages.tui.agentsUnchanged;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (mode === "ask") {
|
|
68
|
+
const agents = normalizeTuiAskAgents(config, agentsInput.agents, currentMessages);
|
|
69
|
+
config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
|
|
70
|
+
await writeExampleConfig(configPath, config);
|
|
71
|
+
changedRunDefaults = true;
|
|
72
|
+
notice = currentMessages.tui.askAgentsUpdated(agents.join(", "));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const [agentA, agentB] = normalizeTuiDebateAgents(config, agentsInput.agents, currentMessages);
|
|
76
|
+
config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
|
|
77
|
+
await writeExampleConfig(configPath, config);
|
|
78
|
+
changedRunDefaults = true;
|
|
79
|
+
notice = currentMessages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (input.kind === "roles") {
|
|
88
|
+
try {
|
|
89
|
+
const rolesInput = input.roles.length > 0
|
|
90
|
+
? { kind: "roles", roles: input.roles }
|
|
91
|
+
: await promptTuiRolesWizard(config, mode, currentMessages);
|
|
92
|
+
if (rolesInput.kind === "quit") {
|
|
93
|
+
return { mode, quit: true, changedRunDefaults };
|
|
94
|
+
}
|
|
95
|
+
if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
|
|
96
|
+
notice = currentMessages.tui.rolesUnchanged;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
notice = applyTuiRoles(config, mode, rolesInput.roles, currentMessages);
|
|
100
|
+
await writeExampleConfig(configPath, config);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (input.kind === "turns") {
|
|
108
|
+
if (mode === "ask") {
|
|
109
|
+
notice = currentMessages.tui.askTurnsNotice;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
validateTurns(input.turns, "--turns", currentMessages);
|
|
114
|
+
config.defaults = { ...(config.defaults ?? {}), turns: input.turns };
|
|
115
|
+
await writeExampleConfig(configPath, config);
|
|
116
|
+
changedRunDefaults = true;
|
|
117
|
+
notice = currentMessages.tui.turnsUpdated(input.turns);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (input.kind === "summary") {
|
|
125
|
+
try {
|
|
126
|
+
const nextDefaults = { ...(config.defaults ?? {}) };
|
|
127
|
+
if (input.agent !== undefined) {
|
|
128
|
+
assertKnownAgent(config, input.agent, mode === "ask" ? "defaults.askSummaryAgent" : "defaults.summaryAgent", currentMessages);
|
|
129
|
+
}
|
|
130
|
+
if (mode === "ask") {
|
|
131
|
+
if (input.agent === undefined) {
|
|
132
|
+
delete nextDefaults.askSummaryAgent;
|
|
133
|
+
notice = currentMessages.tui.askSummaryFallback;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
nextDefaults.askSummaryAgent = input.agent;
|
|
137
|
+
notice = currentMessages.tui.askSummaryAgent(input.agent);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
else if (input.agent === undefined) {
|
|
141
|
+
delete nextDefaults.summaryAgent;
|
|
142
|
+
notice = currentMessages.tui.debateSummaryFallback;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
nextDefaults.summaryAgent = input.agent;
|
|
146
|
+
notice = currentMessages.tui.debateSummaryAgent(input.agent);
|
|
147
|
+
}
|
|
148
|
+
config.defaults = nextDefaults;
|
|
149
|
+
await writeExampleConfig(configPath, config);
|
|
150
|
+
changedRunDefaults = true;
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (input.kind === "ollama-info") {
|
|
158
|
+
try {
|
|
159
|
+
notice = await formatTuiOllamaInfo(config, currentMessages);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (input.kind === "ollama-url") {
|
|
167
|
+
try {
|
|
168
|
+
notice = await setTuiOllamaUrl(configPath, config, input.url, currentMessages);
|
|
169
|
+
changedRunDefaults = true;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
notice = formatTuiRuntimeError(error, currentMessages);
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (input.kind === "ollama-model") {
|
|
177
|
+
try {
|
|
178
|
+
notice = await setTuiOllamaModel(configPath, config, input.model, currentMessages);
|
|
179
|
+
changedRunDefaults = true;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (input.kind === "ollama-sync") {
|
|
187
|
+
try {
|
|
188
|
+
notice = await syncTuiOllamaModel(configPath, config, currentMessages);
|
|
189
|
+
changedRunDefaults = true;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
notice = error instanceof Error ? error.message : String(error);
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function setTuiOllamaUrl(configPath, config, value, messages) {
|
|
199
|
+
if (!Object.values(config.agents).some((agent) => agent.type === "ollama")) {
|
|
200
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
201
|
+
}
|
|
202
|
+
const normalized = isDefaultOllamaUrl(value)
|
|
203
|
+
? DEFAULT_OLLAMA_BASE_URL
|
|
204
|
+
: normalizeOllamaBaseUrl(value);
|
|
205
|
+
const effective = resolveOllamaBaseUrl({ configUrl: normalized });
|
|
206
|
+
setOllamaBaseUrl(config, normalized);
|
|
207
|
+
await writeExampleConfig(configPath, config);
|
|
208
|
+
return messages.tui.ollamaUrlUpdated(normalized, effective);
|
|
209
|
+
}
|
|
210
|
+
function isDefaultOllamaUrl(value) {
|
|
211
|
+
return ["default", "defaut", "défaut", "local", "localhost"].includes(value.trim().toLowerCase());
|
|
212
|
+
}
|
|
213
|
+
async function formatTuiOllamaInfo(config, messages) {
|
|
214
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
215
|
+
const agent = config.agents["ollama-local"];
|
|
216
|
+
if (agent?.type !== "ollama") {
|
|
217
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
218
|
+
}
|
|
219
|
+
if (!discovery.ollama.available) {
|
|
220
|
+
return messages.tui.ollamaUnavailable(discovery.ollama.baseUrl);
|
|
221
|
+
}
|
|
222
|
+
const installed = discovery.ollama.models.length > 0
|
|
223
|
+
? discovery.ollama.models.join(", ")
|
|
224
|
+
: messages.config.ollamaModelNoInstalledModels;
|
|
225
|
+
const api = `${discovery.ollama.baseUrl}`;
|
|
226
|
+
return messages.tui.ollamaInfo(agent.model, installed, api);
|
|
227
|
+
}
|
|
228
|
+
async function setTuiOllamaModel(configPath, config, model, messages) {
|
|
229
|
+
const trimmed = model.trim();
|
|
230
|
+
if (!trimmed) {
|
|
231
|
+
throw new Error(messages.tui.ollamaModelUsage);
|
|
232
|
+
}
|
|
233
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
234
|
+
const agent = config.agents["ollama-local"];
|
|
235
|
+
if (agent?.type !== "ollama") {
|
|
236
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
237
|
+
}
|
|
238
|
+
if (!discovery.ollama.models.includes(trimmed)) {
|
|
239
|
+
throw new Error(messages.config.ollamaModelUnavailable(trimmed));
|
|
240
|
+
}
|
|
241
|
+
const result = setOllamaModel(config, trimmed);
|
|
242
|
+
await writeExampleConfig(configPath, config);
|
|
243
|
+
return result
|
|
244
|
+
? messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel)
|
|
245
|
+
: messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
246
|
+
}
|
|
247
|
+
async function syncTuiOllamaModel(configPath, config, messages) {
|
|
248
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
249
|
+
const agent = config.agents["ollama-local"];
|
|
250
|
+
if (agent?.type !== "ollama") {
|
|
251
|
+
throw new Error(messages.config.ollamaModelNoAgent);
|
|
252
|
+
}
|
|
253
|
+
if (discovery.ollama.models.length === 0) {
|
|
254
|
+
throw new Error(messages.config.ollamaModelNoInstalledModels);
|
|
255
|
+
}
|
|
256
|
+
const result = syncOllamaModel(config, discovery);
|
|
257
|
+
if (!result) {
|
|
258
|
+
return messages.config.ollamaModelNoChange(configPath, agent.model);
|
|
259
|
+
}
|
|
260
|
+
await writeExampleConfig(configPath, config);
|
|
261
|
+
return messages.config.ollamaModelUpdated(configPath, result.previousModel, result.nextModel);
|
|
262
|
+
}
|
|
263
|
+
export async function syncInteractiveDetectedAgents(configPath, config) {
|
|
264
|
+
const discovery = await discoverLocalToolsForConfig(config);
|
|
265
|
+
const result = syncDetectedAgentsDetailed(config, discovery);
|
|
266
|
+
if (result.changed) {
|
|
267
|
+
await writeExampleConfig(configPath, config);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
addedAgents: result.addedAgents
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function normalizeTuiDebateAgents(config, agents, messages) {
|
|
274
|
+
const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
|
|
275
|
+
if (unique.length !== 2) {
|
|
276
|
+
throw new Error(messages.tui.debateAgentsUsage);
|
|
277
|
+
}
|
|
278
|
+
assertKnownAgent(config, unique[0], "defaults.agentA", messages);
|
|
279
|
+
assertKnownAgent(config, unique[1], "defaults.agentB", messages);
|
|
280
|
+
return [unique[0], unique[1]];
|
|
281
|
+
}
|
|
282
|
+
function normalizeTuiAskAgents(config, agents, messages) {
|
|
283
|
+
const unique = agents.map((agent) => agent.trim()).filter((agent, index, list) => agent && list.indexOf(agent) === index);
|
|
284
|
+
if (unique.length === 0) {
|
|
285
|
+
throw new Error(messages.tui.askAgentsUsage);
|
|
286
|
+
}
|
|
287
|
+
if (unique.length > MAX_ASK_AGENTS) {
|
|
288
|
+
throw new Error(messages.common.tooManyAskAgents(MAX_ASK_AGENTS));
|
|
289
|
+
}
|
|
290
|
+
unique.forEach((agent) => assertKnownAgent(config, agent, "defaults.askAgents", messages));
|
|
291
|
+
return unique;
|
|
292
|
+
}
|
|
293
|
+
export async function runTuiAgentsWizard(configPath, config, messages, mode, inlineAgents = []) {
|
|
294
|
+
try {
|
|
295
|
+
const agentsInput = inlineAgents.length > 0
|
|
296
|
+
? { kind: "agents", agents: inlineAgents }
|
|
297
|
+
: await promptTuiAgentsWizard(config, mode, messages);
|
|
298
|
+
if (agentsInput.kind === "quit") {
|
|
299
|
+
return { quit: true, changedRunDefaults: false };
|
|
300
|
+
}
|
|
301
|
+
if (agentsInput.kind === "back" || agentsInput.agents.length === 0) {
|
|
302
|
+
return { quit: false, changedRunDefaults: false };
|
|
303
|
+
}
|
|
304
|
+
const notice = applyTuiAgents(config, mode, agentsInput.agents, messages);
|
|
305
|
+
await writeExampleConfig(configPath, config);
|
|
306
|
+
return { notice, quit: false, changedRunDefaults: true };
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
return { notice: messages.tui.agentsError(error instanceof Error ? error.message : String(error)), quit: false, changedRunDefaults: false };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
export async function runTuiRolesWizard(configPath, config, messages, mode, inlineRoles = []) {
|
|
313
|
+
try {
|
|
314
|
+
const rolesInput = inlineRoles.length > 0
|
|
315
|
+
? { kind: "roles", roles: inlineRoles }
|
|
316
|
+
: await promptTuiRolesWizard(config, mode, messages);
|
|
317
|
+
if (rolesInput.kind === "quit") {
|
|
318
|
+
return { quit: true };
|
|
319
|
+
}
|
|
320
|
+
if (rolesInput.kind === "back" || rolesInput.roles.length === 0) {
|
|
321
|
+
return { quit: false };
|
|
322
|
+
}
|
|
323
|
+
const notice = applyTuiRoles(config, mode, rolesInput.roles, messages);
|
|
324
|
+
await writeExampleConfig(configPath, config);
|
|
325
|
+
return { notice, quit: false };
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
return { notice: messages.tui.rolesError(error instanceof Error ? error.message : String(error)), quit: false };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function applyTuiAgents(config, mode, agentNames, messages) {
|
|
332
|
+
if (mode === "ask") {
|
|
333
|
+
const agents = normalizeTuiAskAgents(config, agentNames, messages);
|
|
334
|
+
config.defaults = { ...(config.defaults ?? {}), askAgents: agents };
|
|
335
|
+
return messages.tui.askAgentsUpdated(agents.join(", "));
|
|
336
|
+
}
|
|
337
|
+
const [agentA, agentB] = normalizeTuiDebateAgents(config, agentNames, messages);
|
|
338
|
+
config.defaults = { ...(config.defaults ?? {}), agentA, agentB };
|
|
339
|
+
return messages.tui.debateAgentsUpdated(`${agentA} <-> ${agentB}`);
|
|
340
|
+
}
|
|
341
|
+
function applyTuiRoles(config, mode, roleNames, messages) {
|
|
342
|
+
const agents = activeAgentsForMode(config, mode);
|
|
343
|
+
if (agents.length === 0) {
|
|
344
|
+
throw new Error(mode === "ask" ? messages.tui.noAskAgentsConfigured : messages.tui.noDebateAgentsConfigured);
|
|
345
|
+
}
|
|
346
|
+
const roles = normalizeTuiRoles(roleNames, agents, mode, messages);
|
|
347
|
+
agents.forEach((agent, index) => {
|
|
348
|
+
config.agents[agent].role = roles[index];
|
|
349
|
+
});
|
|
350
|
+
return mode === "ask"
|
|
351
|
+
? messages.tui.askRolesUpdated(roles.join(", "))
|
|
352
|
+
: messages.tui.debateRolesUpdated(roles.join(" <-> "));
|
|
353
|
+
}
|
|
354
|
+
function activeAgentsForMode(config, mode) {
|
|
355
|
+
const defaults = config.defaults ?? {};
|
|
356
|
+
if (mode === "ask") {
|
|
357
|
+
if (defaults.askAgents && defaults.askAgents.length > 0) {
|
|
358
|
+
return defaults.askAgents.filter((agent) => Boolean(config.agents[agent]));
|
|
359
|
+
}
|
|
360
|
+
return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
|
|
361
|
+
}
|
|
362
|
+
return [defaults.agentA, defaults.agentB].filter((agent) => Boolean(agent && config.agents[agent]));
|
|
363
|
+
}
|
|
364
|
+
function normalizeTuiRoles(roleNames, agents, mode, messages) {
|
|
365
|
+
const roles = roleNames.map((role) => role.trim().toLowerCase()).filter(Boolean);
|
|
366
|
+
const expectedCount = agents.length;
|
|
367
|
+
if (roles.length < expectedCount) {
|
|
368
|
+
const agentLabel = mode === "ask"
|
|
369
|
+
? agents.join(", ")
|
|
370
|
+
: agents.join(" <-> ");
|
|
371
|
+
throw new Error(messages.tui.rolesCountError(roles.length, expectedCount, agentLabel));
|
|
372
|
+
}
|
|
373
|
+
return roles.slice(0, expectedCount).map((role) => {
|
|
374
|
+
if (isAgentRole(role)) {
|
|
375
|
+
return role;
|
|
376
|
+
}
|
|
377
|
+
throw new Error(messages.tui.unknownRole(role, VALID_AGENT_ROLES.join(", ")));
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function isAgentRole(value) {
|
|
381
|
+
return VALID_AGENT_ROLES.includes(value);
|
|
382
|
+
}
|
|
383
|
+
const VALID_AGENT_ROLES = ["implementer", "reviewer", "architect", "scout", "critic", "summarizer"];
|
|
384
|
+
function assertKnownAgent(config, agentName, fieldName, messages) {
|
|
385
|
+
if (!config.agents[agentName]) {
|
|
386
|
+
throw new Error(messages.common.unknownAgentForField(fieldName, agentName, Object.keys(config.agents).join(", ")));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function formatTuiRuntimeError(error, messages) {
|
|
390
|
+
if (error instanceof AdapterError)
|
|
391
|
+
return formatAdapterError(error, messages);
|
|
392
|
+
if (error instanceof OllamaUrlError) {
|
|
393
|
+
if (error.kind === "empty")
|
|
394
|
+
return messages.common.ollamaUrlEmpty;
|
|
395
|
+
if (error.kind === "protocol")
|
|
396
|
+
return messages.common.ollamaUrlProtocol(error.protocol ?? "");
|
|
397
|
+
return messages.common.ollamaUrlInvalid(error.value);
|
|
398
|
+
}
|
|
399
|
+
return error instanceof Error ? error.message : String(error);
|
|
400
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palabre",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Orchestrateur de debat entre agents IA locaux, CLIs et Ollama.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
39
39
|
"prepack": "pnpm build",
|
|
40
40
|
"start": "node ./dist/index.js",
|
|
41
|
-
"test": "pnpm build:test && node --test .tmp/test-dist/tests/*.test.js",
|
|
41
|
+
"test": "pnpm build:test && node --test .tmp/test-dist/tests/*.test.js && pnpm test:release-social",
|
|
42
|
+
"test:release-social": "node --test scripts/post_release_bluesky.test.mjs",
|
|
42
43
|
"build:test": "node -e \"fs.rmSync('.tmp/test-dist',{recursive:true,force:true})\" && tsc -p tsconfig.test.json",
|
|
43
44
|
"smoke:real-presets": "node -e \"fs.rmSync('.tmp/smoke-real',{recursive:true,force:true})\" && tsc --target ES2022 --module NodeNext --moduleResolution NodeNext --types node --outDir .tmp/smoke-real scripts/smoke_real_presets.ts && node .tmp/smoke-real/smoke_real_presets.js"
|
|
44
45
|
},
|