micro-models-agent 0.28.17 → 0.29.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/dist/cli/commands.js +3 -116
- package/dist/cli/main.js +8 -35
- package/dist/cli/repl.js +611 -110
- package/dist/cli/setup.js +12 -32
- package/dist/config/config.js +30 -46
- package/dist/config/defaults.js +1 -10
- package/dist/config/security.js +8 -15
- package/dist/core/agent-moe.js +12 -24
- package/dist/core/agent.js +47 -281
- package/dist/core/bootstrap.js +36 -52
- package/dist/core/session-logger.js +2 -35
- package/dist/i18n/en.json +15 -79
- package/dist/i18n/index.js +9 -12
- package/dist/i18n/ru.json +15 -79
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +10 -39
- package/dist/logger/app-logger.js +16 -83
- package/dist/main.js +625 -243
- package/dist/modules/browser/session.js +60 -108
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/manager.js +10 -119
- package/dist/modules/execution/auditor.js +39 -33
- package/dist/modules/execution/index.js +6 -8
- package/dist/modules/execution/module.js +32 -474
- package/dist/modules/execution/moe-executor.js +40 -97
- package/dist/modules/execution/planner.js +13 -63
- package/dist/modules/execution/stuck-detector.js +39 -252
- package/dist/modules/execution/tracker.js +7 -21
- package/dist/modules/execution/verifier.js +17 -46
- package/dist/modules/hallucination/confidence.js +2 -7
- package/dist/modules/hallucination/consistency.js +42 -8
- package/dist/modules/hallucination/detector.js +21 -26
- package/dist/modules/hallucination/factual.js +150 -170
- package/dist/modules/hallucination/index.js +4 -5
- package/dist/modules/index.js +5 -5
- package/dist/modules/mcp/client.js +2 -8
- package/dist/modules/memory/store.js +0 -4
- package/dist/modules/plugins/builtin/lint-on-write.js +38 -143
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +2 -1
- package/dist/modules/processes/registry.js +35 -125
- package/dist/modules/processes/runner.js +110 -9
- package/dist/modules/security/audit-log.js +10 -30
- package/dist/modules/security/command-validator.js +16 -42
- package/dist/modules/security/content-scanner.js +8 -9
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +10 -64
- package/dist/modules/security/security-policies.js +67 -221
- package/dist/modules/security/session-encryption.js +25 -42
- package/dist/modules/session/manager.js +10 -15
- package/dist/modules/session/store.js +8 -62
- package/dist/modules/skills/index.js +3 -2
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +23 -10
- package/dist/tools/bash.js +90 -287
- package/dist/tools/create-dir.js +1 -0
- package/dist/tools/delete-file.js +1 -0
- package/dist/tools/edit-file.js +8 -10
- package/dist/tools/executor.js +7 -57
- package/dist/tools/grep-tool.js +29 -51
- package/dist/tools/index.js +40 -55
- package/dist/tools/load-skill.js +18 -14
- package/dist/tools/move-file.js +2 -3
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +5 -15
- package/dist/tools/search-history.js +22 -42
- package/dist/tools/subagent.js +12 -21
- package/dist/tools/web-browse.js +25 -54
- package/dist/tools/web-fetch.js +34 -60
- package/dist/tools/web-search.js +20 -39
- package/dist/tools/write-file.js +10 -13
- package/dist/ui/diff.js +16 -9
- package/dist/ui/renderer.js +6 -69
- package/package.json +1 -1
- package/dist/cli/repl-commands.js +0 -633
- package/dist/core/workspace.js +0 -76
- package/dist/logger/file-log.js +0 -151
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -159
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- package/dist/modules/lsp/types.js +0 -1
package/dist/cli/repl.js
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
1
|
import * as readline from "readline";
|
|
2
2
|
import { pc } from "../ui/colors";
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
-
import { join
|
|
4
|
+
import { join } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
6
|
import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, } from "./completer";
|
|
8
7
|
import { Renderer } from "../ui/renderer";
|
|
9
8
|
import { box } from "../ui/box";
|
|
9
|
+
import { renderTable } from "../ui/table";
|
|
10
10
|
import { t } from "../i18n/index";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
11
|
+
import { runSetup } from "./setup";
|
|
12
|
+
import { saveConfig } from "../config/config";
|
|
13
|
+
import { getMessageText } from "../llm/provider";
|
|
14
|
+
const COMMAND_GROUPS = {
|
|
15
|
+
help: "general",
|
|
16
|
+
exit: "general",
|
|
17
|
+
clear: "general",
|
|
18
|
+
run: "general",
|
|
19
|
+
image: "general",
|
|
20
|
+
config: "agent",
|
|
21
|
+
status: "agent",
|
|
22
|
+
reasoning: "agent",
|
|
23
|
+
provider: "agent",
|
|
24
|
+
model: "agent",
|
|
25
|
+
context: "agent",
|
|
26
|
+
reload: "agent",
|
|
27
|
+
wizard: "agent",
|
|
28
|
+
sessions: "session",
|
|
29
|
+
new: "session",
|
|
30
|
+
resume: "session",
|
|
31
|
+
rename: "session",
|
|
32
|
+
delete: "session",
|
|
33
|
+
skill: "skill",
|
|
34
|
+
};
|
|
35
|
+
function formatContextBar(used, limit) {
|
|
34
36
|
const pct = Math.min(100, Math.round((used / limit) * 100));
|
|
35
37
|
const barLen = 20;
|
|
36
38
|
const filled = Math.round((pct / 100) * barLen);
|
|
37
39
|
const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
|
|
38
40
|
const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
|
|
39
|
-
|
|
40
|
-
if (compactions !== undefined) {
|
|
41
|
-
line += pc.dim(` compactions: ${compactions}`);
|
|
42
|
-
}
|
|
43
|
-
if (quality !== undefined) {
|
|
44
|
-
const qColor = quality >= 70 ? pc.green : quality >= 40 ? pc.yellow : pc.red;
|
|
45
|
-
line += ` ${qColor(`quality: ${quality}%`)}`;
|
|
46
|
-
}
|
|
47
|
-
return line;
|
|
41
|
+
return ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
|
|
48
42
|
}
|
|
49
43
|
export class Repl {
|
|
44
|
+
rl;
|
|
45
|
+
commands = new Map();
|
|
50
46
|
completer = new Completer();
|
|
51
47
|
running = false;
|
|
52
48
|
agentRunning = false;
|
|
53
|
-
|
|
49
|
+
agent;
|
|
50
|
+
config;
|
|
51
|
+
sessionManager;
|
|
52
|
+
skillsModule;
|
|
53
|
+
pluginManager;
|
|
54
54
|
historyPath;
|
|
55
55
|
history = [];
|
|
56
56
|
maxHistory = 1000;
|
|
@@ -60,25 +60,22 @@ export class Repl {
|
|
|
60
60
|
baseDir;
|
|
61
61
|
noAgentsMd;
|
|
62
62
|
pendingClipboardImage = null;
|
|
63
|
-
|
|
64
|
-
agent;
|
|
65
|
-
config;
|
|
66
|
-
sessionManager;
|
|
67
|
-
skillsModule;
|
|
68
|
-
pluginManager;
|
|
69
|
-
logger;
|
|
70
|
-
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger) {
|
|
63
|
+
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd) {
|
|
71
64
|
this.agent = agent;
|
|
72
65
|
this.config = config;
|
|
73
66
|
this.sessionManager = sessionManager;
|
|
74
67
|
this.skillsModule = skillsModule;
|
|
75
68
|
this.pluginManager = pluginManager;
|
|
76
|
-
this.logger = logger;
|
|
77
69
|
this.configDir = configDir || join(homedir(), ".mma");
|
|
78
70
|
this.baseDir = baseDir || process.cwd();
|
|
79
71
|
this.noAgentsMd = noAgentsMd === true;
|
|
80
72
|
this.historyPath = join(homedir(), ".mma", "repl-history");
|
|
81
73
|
this.loadHistory();
|
|
74
|
+
this.registerBuiltinCommands();
|
|
75
|
+
this.registerMmaCommands();
|
|
76
|
+
this.registerSessionCommands();
|
|
77
|
+
this.registerSkillCommands();
|
|
78
|
+
this.setupCompleter();
|
|
82
79
|
this.rl = readline.createInterface({
|
|
83
80
|
input: process.stdin,
|
|
84
81
|
output: process.stdout,
|
|
@@ -93,8 +90,6 @@ export class Repl {
|
|
|
93
90
|
return [[], line];
|
|
94
91
|
},
|
|
95
92
|
});
|
|
96
|
-
registerAllCommands(this);
|
|
97
|
-
this.setupCompleter();
|
|
98
93
|
this.setupListeners();
|
|
99
94
|
}
|
|
100
95
|
loadHistory() {
|
|
@@ -112,6 +107,551 @@ export class Repl {
|
|
|
112
107
|
const allHistory = this.history.slice(-this.maxHistory);
|
|
113
108
|
writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
|
|
114
109
|
}
|
|
110
|
+
registerBuiltinCommands() {
|
|
111
|
+
this.registerCommand({
|
|
112
|
+
name: "help",
|
|
113
|
+
description: t("repl.help"),
|
|
114
|
+
usage: t("repl.help_usage"),
|
|
115
|
+
action: () => this.showHelp(),
|
|
116
|
+
});
|
|
117
|
+
this.registerCommand({
|
|
118
|
+
name: "exit",
|
|
119
|
+
description: t("repl.exit"),
|
|
120
|
+
aliases: ["quit", "q"],
|
|
121
|
+
usage: t("repl.exit_usage"),
|
|
122
|
+
action: () => this.stop(),
|
|
123
|
+
});
|
|
124
|
+
this.registerCommand({
|
|
125
|
+
name: "clear",
|
|
126
|
+
description: t("repl.clear"),
|
|
127
|
+
usage: t("repl.clear_usage"),
|
|
128
|
+
action: () => {
|
|
129
|
+
console.clear();
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
registerMmaCommands() {
|
|
134
|
+
this.registerCommand({
|
|
135
|
+
name: "run",
|
|
136
|
+
description: t("repl.run"),
|
|
137
|
+
usage: t("repl.run_usage"),
|
|
138
|
+
action: async (args) => {
|
|
139
|
+
if (args.length === 0) {
|
|
140
|
+
console.log(t("repl.run_usage"));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const prompt = args.join(" ");
|
|
144
|
+
await this.runAgent(prompt);
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
this.registerCommand({
|
|
148
|
+
name: "image",
|
|
149
|
+
description: t("repl.image"),
|
|
150
|
+
aliases: ["img"],
|
|
151
|
+
usage: t("repl.image_usage"),
|
|
152
|
+
action: async (args) => {
|
|
153
|
+
const source = args.join(" ");
|
|
154
|
+
if (!source) {
|
|
155
|
+
console.log(t("repl.image_usage"));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const { loadFileAsDataUrl, loadUrlAsDataUrl, readClipboardImage } = await import("../llm/image-utils");
|
|
160
|
+
const { existsSync } = await import("fs");
|
|
161
|
+
const { resolve } = await import("path");
|
|
162
|
+
let dataUrl;
|
|
163
|
+
let label;
|
|
164
|
+
if (source.toLowerCase() === "clipboard") {
|
|
165
|
+
const clipBuf = await readClipboardImage();
|
|
166
|
+
if (!clipBuf) {
|
|
167
|
+
console.log(pc.yellow(t("image.clipboard_empty")));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const { bufferToDataUrl } = await import("../llm/image-utils");
|
|
171
|
+
const result = await bufferToDataUrl(clipBuf);
|
|
172
|
+
dataUrl = result.dataUrl;
|
|
173
|
+
label = "clipboard";
|
|
174
|
+
}
|
|
175
|
+
else if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
176
|
+
const result = await loadUrlAsDataUrl(source);
|
|
177
|
+
dataUrl = result.dataUrl;
|
|
178
|
+
label = source;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const absPath = resolve(process.cwd(), source);
|
|
182
|
+
if (!existsSync(absPath)) {
|
|
183
|
+
console.log(pc.red(t("image.not_found", { path: source })));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const result = await loadFileAsDataUrl(absPath);
|
|
187
|
+
dataUrl = result.dataUrl;
|
|
188
|
+
label = source;
|
|
189
|
+
}
|
|
190
|
+
// Store the image data on the agent's context manager for the next message
|
|
191
|
+
const contextManager = this.agent.contextManager;
|
|
192
|
+
if (!contextManager) {
|
|
193
|
+
console.log(pc.red(t("image.no_context")));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
contextManager.addPendingImage({
|
|
197
|
+
type: "image_url",
|
|
198
|
+
image_url: { url: dataUrl },
|
|
199
|
+
});
|
|
200
|
+
const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
|
|
201
|
+
console.log(pc.green(t("image.attached", { source: label, size: `${sizeKb} KB` })));
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
console.log(pc.red(t("image.error", { message: err.message })));
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
this.registerCommand({
|
|
209
|
+
name: "config",
|
|
210
|
+
description: t("repl.config"),
|
|
211
|
+
usage: t("repl.config_usage"),
|
|
212
|
+
action: () => {
|
|
213
|
+
const ctx = this.config.contextWindow;
|
|
214
|
+
const sys = Math.floor(ctx * this.config.contextBudget.systemPrompt);
|
|
215
|
+
const res = Math.floor(ctx * this.config.contextBudget.responseReserve);
|
|
216
|
+
console.log(`${t("repl.model")} ${this.config.model}`);
|
|
217
|
+
console.log(`${t("repl.provider")} ${this.config.provider.type} → ${this.config.provider.baseUrl}`);
|
|
218
|
+
console.log(`${t("repl.context")} ${ctx} (sys:${sys} res:${res} hist:${ctx - sys - res})`);
|
|
219
|
+
console.log(`${t("repl.max_iters")} ${this.config.maxToolIterations}`);
|
|
220
|
+
console.log(`${t("repl.stuck_thresh")} ${this.config.stuckThreshold}`);
|
|
221
|
+
console.log(`${t("repl.reasoning_label")} ${this.config.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"))}`);
|
|
222
|
+
console.log(`${t("repl.log_level")} ${this.config.logLevel}`);
|
|
223
|
+
console.log(`${t("repl.locale")} ${this.config.locale}`);
|
|
224
|
+
const meta = this.sessionManager?.getActiveMeta();
|
|
225
|
+
if (meta) {
|
|
226
|
+
console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} msgs`);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
this.registerCommand({
|
|
231
|
+
name: "reasoning",
|
|
232
|
+
description: t("repl.reasoning"),
|
|
233
|
+
usage: t("repl.reasoning_usage"),
|
|
234
|
+
action: () => {
|
|
235
|
+
this.config.showReasoning = !this.config.showReasoning;
|
|
236
|
+
const status = this.config.showReasoning
|
|
237
|
+
? pc.green(t("repl.show"))
|
|
238
|
+
: pc.dim(t("repl.hide"));
|
|
239
|
+
console.log(t("repl.reasoning_status", { status }));
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
this.registerCommand({
|
|
243
|
+
name: "status",
|
|
244
|
+
description: t("repl.status"),
|
|
245
|
+
usage: t("repl.status_usage"),
|
|
246
|
+
action: () => {
|
|
247
|
+
console.log(`${t("repl.model")} ${this.config.model}`);
|
|
248
|
+
console.log(`${t("repl.provider")} ${this.config.provider.type} @ ${this.config.provider.baseUrl}`);
|
|
249
|
+
const meta = this.sessionManager?.getActiveMeta();
|
|
250
|
+
if (meta) {
|
|
251
|
+
console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
this.registerCommand({
|
|
256
|
+
name: "wizard",
|
|
257
|
+
description: t("repl.wizard"),
|
|
258
|
+
aliases: ["setup"],
|
|
259
|
+
usage: t("repl.wizard_usage"),
|
|
260
|
+
action: async () => {
|
|
261
|
+
console.log(pc.yellow(t("repl.wizard_running")));
|
|
262
|
+
const answers = await runSetup();
|
|
263
|
+
const configPath = join(homedir(), ".mma", "config.json");
|
|
264
|
+
this.config.provider.type = answers.provider;
|
|
265
|
+
this.config.provider.baseUrl = answers.apiBase;
|
|
266
|
+
this.config.provider.apiKey = answers.apiKey;
|
|
267
|
+
this.config.model = answers.model;
|
|
268
|
+
this.config.contextWindow = answers.contextWindow;
|
|
269
|
+
this.config.maxToolIterations = answers.maxToolIterations;
|
|
270
|
+
this.config.locale = answers.locale;
|
|
271
|
+
saveConfig(this.config, configPath);
|
|
272
|
+
console.log(pc.green(t("cli.config_saved")));
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
this.registerCommand({
|
|
276
|
+
name: "provider",
|
|
277
|
+
description: t("repl.provider_list"),
|
|
278
|
+
usage: t("repl.provider_usage"),
|
|
279
|
+
action: (args) => {
|
|
280
|
+
const subcmd = args[0];
|
|
281
|
+
if (!subcmd || subcmd === "list") {
|
|
282
|
+
console.log(`${t("repl.provider_current")} ${this.config.provider.type}`);
|
|
283
|
+
console.log(` ${this.config.provider.baseUrl}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (subcmd === "use") {
|
|
287
|
+
const name = args[1];
|
|
288
|
+
if (!name) {
|
|
289
|
+
console.log(t("repl.provider_usage"));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.config.provider.type = name;
|
|
293
|
+
const configPath = join(homedir(), ".mma", "config.json");
|
|
294
|
+
saveConfig(this.config, configPath);
|
|
295
|
+
console.log(pc.green(t("repl.provider_set", { name })));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
console.log(t("repl.provider_usage"));
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
this.registerCommand({
|
|
302
|
+
name: "model",
|
|
303
|
+
description: t("repl.model_list"),
|
|
304
|
+
usage: t("repl.model_usage"),
|
|
305
|
+
action: async (args) => {
|
|
306
|
+
const subcmd = args[0];
|
|
307
|
+
if (!subcmd || subcmd === "list") {
|
|
308
|
+
console.log(`${t("repl.model_current")} ${this.config.model}`);
|
|
309
|
+
// Fetch available models from provider
|
|
310
|
+
const { OpenAICompatProvider } = await import("../llm/openai-compat");
|
|
311
|
+
const provider = new OpenAICompatProvider({
|
|
312
|
+
model: this.config.model,
|
|
313
|
+
baseUrl: this.config.provider.baseUrl,
|
|
314
|
+
apiKey: this.config.provider.apiKey,
|
|
315
|
+
contextWindow: this.config.contextWindow,
|
|
316
|
+
});
|
|
317
|
+
const { Spinner } = await import("../ui/spinner");
|
|
318
|
+
const s = new Spinner();
|
|
319
|
+
s.start(t("cli.fetching_models"));
|
|
320
|
+
try {
|
|
321
|
+
const models = await provider.listModels();
|
|
322
|
+
s.stop();
|
|
323
|
+
if (models.length > 0) {
|
|
324
|
+
console.log(t("cli.available_models"));
|
|
325
|
+
for (const m of models) {
|
|
326
|
+
const marker = m === this.config.model ? pc.green("* ") : " ";
|
|
327
|
+
console.log(` ${marker}${m}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
console.log(t("cli.no_models_found"));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
s.stop();
|
|
336
|
+
console.log(t("cli.model_fetch_failed", { error: String(err) }));
|
|
337
|
+
}
|
|
338
|
+
console.log(t("cli.model_hint"));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (subcmd === "use") {
|
|
342
|
+
const name = args[1];
|
|
343
|
+
if (!name) {
|
|
344
|
+
console.log(t("repl.model_usage"));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this.config.model = name;
|
|
348
|
+
const configPath = join(homedir(), ".mma", "config.json");
|
|
349
|
+
saveConfig(this.config, configPath);
|
|
350
|
+
console.log(pc.green(t("repl.model_set", { name })));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
console.log(t("repl.model_usage"));
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
this.registerCommand({
|
|
357
|
+
name: "context",
|
|
358
|
+
description: t("cli.manage_context"),
|
|
359
|
+
usage: "/context <size>",
|
|
360
|
+
action: (args) => {
|
|
361
|
+
if (args.length === 0) {
|
|
362
|
+
console.log(`/context ${t("repl.context")} ${this.config.contextWindow}`);
|
|
363
|
+
console.log(`Usage: /context <size> (min 1024)`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const size = parseInt(args[0], 10);
|
|
367
|
+
if (isNaN(size) || size < 1024) {
|
|
368
|
+
console.log(t("cli.invalid_context_size"));
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
this.config.contextWindow = size;
|
|
372
|
+
const configPath = join(homedir(), ".mma", "config.json");
|
|
373
|
+
saveConfig(this.config, configPath);
|
|
374
|
+
console.log(pc.green(t("cli.context_set", { size })));
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
this.registerCommand({
|
|
378
|
+
name: "reload",
|
|
379
|
+
description: t("repl.reload"),
|
|
380
|
+
usage: t("repl.reload_usage"),
|
|
381
|
+
action: async () => {
|
|
382
|
+
console.log(pc.yellow(t("repl.reloading")));
|
|
383
|
+
// Save current session if auto-save enabled
|
|
384
|
+
if (this.sessionManager && this.config.session.autoSave) {
|
|
385
|
+
const active = this.sessionManager.getActiveMeta();
|
|
386
|
+
if (active) {
|
|
387
|
+
// Session is already auto-saved on each message
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// Shutdown current agent
|
|
391
|
+
this.agent.shutdown();
|
|
392
|
+
// Reload config from disk
|
|
393
|
+
const { loadConfig } = await import("../config/config");
|
|
394
|
+
const { homedir } = await import("os");
|
|
395
|
+
const { join } = await import("path");
|
|
396
|
+
const configDir = join(homedir(), ".mma");
|
|
397
|
+
const projectConfigPath = join(process.cwd(), ".mmrc");
|
|
398
|
+
const freshConfig = loadConfig({ configDir, projectConfigPath });
|
|
399
|
+
// Update config reference
|
|
400
|
+
Object.assign(this.config, freshConfig);
|
|
401
|
+
// Recreate agent with new config (re-bootstrap)
|
|
402
|
+
const { bootstrap } = await import("../core/bootstrap");
|
|
403
|
+
const result = await bootstrap(configDir, process.cwd(), this.noAgentsMd, false);
|
|
404
|
+
// Replace agent and related components
|
|
405
|
+
this.agent = result.agent;
|
|
406
|
+
this.sessionManager = result.sessionManager;
|
|
407
|
+
this.skillsModule = result.skillsModule;
|
|
408
|
+
this.pluginManager = result.pluginManager;
|
|
409
|
+
// Update completer with new session/skill data
|
|
410
|
+
this.setupCompleter();
|
|
411
|
+
console.log(pc.green(t("repl.reloaded")));
|
|
412
|
+
console.log(`${t("repl.model")} ${this.config.model}`);
|
|
413
|
+
console.log(`${t("repl.context")} ${this.config.contextWindow}`);
|
|
414
|
+
console.log(`${t("repl.provider")} ${this.config.provider.type} @ ${this.config.provider.baseUrl}`);
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
registerSessionCommands() {
|
|
419
|
+
if (!this.sessionManager)
|
|
420
|
+
return;
|
|
421
|
+
this.registerCommand({
|
|
422
|
+
name: "sessions",
|
|
423
|
+
description: t("repl.sessions"),
|
|
424
|
+
aliases: ["ls"],
|
|
425
|
+
usage: t("repl.sessions_usage"),
|
|
426
|
+
action: () => {
|
|
427
|
+
const sessions = this.sessionManager.list();
|
|
428
|
+
const active = this.sessionManager.getActive();
|
|
429
|
+
if (sessions.length === 0) {
|
|
430
|
+
console.log(t("session.no_sessions_hint"));
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const rows = sessions.map((s) => [
|
|
434
|
+
s.id === active ? pc.green("●") : "",
|
|
435
|
+
s.id.slice(0, 12),
|
|
436
|
+
s.name,
|
|
437
|
+
s.updatedAt.slice(0, 19).replace("T", " "),
|
|
438
|
+
String(s.messageCount),
|
|
439
|
+
]);
|
|
440
|
+
for (const line of renderTable([
|
|
441
|
+
t("session.col_active"),
|
|
442
|
+
t("session.col_id"),
|
|
443
|
+
t("session.col_name"),
|
|
444
|
+
t("session.col_updated"),
|
|
445
|
+
t("session.col_msgs"),
|
|
446
|
+
], rows)) {
|
|
447
|
+
console.log(line);
|
|
448
|
+
}
|
|
449
|
+
console.log(pc.dim(`\n ${t("repl.resume_hint")}`));
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
this.registerCommand({
|
|
453
|
+
name: "new",
|
|
454
|
+
description: t("repl.new"),
|
|
455
|
+
aliases: ["create"],
|
|
456
|
+
usage: t("repl.new_usage"),
|
|
457
|
+
action: (args) => {
|
|
458
|
+
const name = args.join(" ") || undefined;
|
|
459
|
+
const meta = this.sessionManager.create(name);
|
|
460
|
+
this.agent.clearContext();
|
|
461
|
+
console.clear();
|
|
462
|
+
console.log(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
|
|
463
|
+
console.log(pc.dim(` ${t("session.chat_cleared")}\n`));
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
this.registerCommand({
|
|
467
|
+
name: "resume",
|
|
468
|
+
description: t("repl.resume"),
|
|
469
|
+
aliases: ["switch", "use"],
|
|
470
|
+
usage: t("repl.resume_usage"),
|
|
471
|
+
action: (args) => {
|
|
472
|
+
const query = args.join(" ");
|
|
473
|
+
const sessions = this.sessionManager.list();
|
|
474
|
+
if (!query) {
|
|
475
|
+
console.log(pc.dim(t("session.available")));
|
|
476
|
+
const active = this.sessionManager.getActive();
|
|
477
|
+
for (const s of sessions) {
|
|
478
|
+
const marker = s.id === active ? pc.green(" *") : " ";
|
|
479
|
+
console.log(pc.dim(` ${marker} ${s.id.slice(0, 12)} ${s.name}`));
|
|
480
|
+
}
|
|
481
|
+
console.log(pc.dim(`\n ${t("repl.resume_usage")}`));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const match = sessions.find((s) => s.id === query ||
|
|
485
|
+
s.id.startsWith(query) ||
|
|
486
|
+
s.name.toLowerCase().includes(query.toLowerCase()));
|
|
487
|
+
if (!match) {
|
|
488
|
+
console.log(t("session.no_match", { query }));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
this.sessionManager.setActive(match.id);
|
|
492
|
+
const history = this.sessionManager.loadHistory();
|
|
493
|
+
this.agent.setContext(history);
|
|
494
|
+
console.clear();
|
|
495
|
+
console.log(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
|
|
496
|
+
" " +
|
|
497
|
+
pc.dim(`(${match.id.slice(0, 12)})`) +
|
|
498
|
+
" — " +
|
|
499
|
+
match.messageCount +
|
|
500
|
+
" msgs");
|
|
501
|
+
console.log(pc.dim("─".repeat(50)));
|
|
502
|
+
if (history.length === 0) {
|
|
503
|
+
console.log(pc.dim(t("session.no_history")));
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
console.log(pc.dim(t("session.chat_history")));
|
|
507
|
+
console.log();
|
|
508
|
+
for (const msg of history) {
|
|
509
|
+
if (msg.role === "user") {
|
|
510
|
+
console.log(pc.cyan(t("session.user_label") + ":"));
|
|
511
|
+
console.log(getMessageText(msg.content));
|
|
512
|
+
console.log();
|
|
513
|
+
}
|
|
514
|
+
else if (msg.role === "assistant") {
|
|
515
|
+
console.log(pc.green(t("session.assistant_label") + ":"));
|
|
516
|
+
console.log(getMessageText(msg.content));
|
|
517
|
+
console.log();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
console.log(pc.dim("─".repeat(50)));
|
|
522
|
+
console.log(pc.dim(` ${t("session.chat_loaded")}`));
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
this.registerCommand({
|
|
526
|
+
name: "rename",
|
|
527
|
+
description: t("repl.rename"),
|
|
528
|
+
usage: t("repl.rename_usage"),
|
|
529
|
+
action: (args) => {
|
|
530
|
+
const name = args.join(" ");
|
|
531
|
+
if (!name) {
|
|
532
|
+
console.log(t("repl.rename_usage"));
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const active = this.sessionManager.getActive();
|
|
536
|
+
if (!active) {
|
|
537
|
+
console.log(t("session.no_active"));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.sessionManager.rename(active, name);
|
|
541
|
+
console.log(t("session.renamed", { name }));
|
|
542
|
+
},
|
|
543
|
+
});
|
|
544
|
+
this.registerCommand({
|
|
545
|
+
name: "delete",
|
|
546
|
+
description: t("repl.delete"),
|
|
547
|
+
aliases: ["rm"],
|
|
548
|
+
usage: t("repl.delete_usage"),
|
|
549
|
+
action: (args) => {
|
|
550
|
+
const query = args[0];
|
|
551
|
+
if (!query) {
|
|
552
|
+
console.log(t("repl.delete_usage"));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const sessions = this.sessionManager.list();
|
|
556
|
+
const match = sessions.find((s) => s.id === query || s.id.startsWith(query));
|
|
557
|
+
if (!match) {
|
|
558
|
+
console.log(t("session.no_match", { query }));
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this.sessionManager.delete(match.id);
|
|
562
|
+
console.log(`${t("session.deleted", { id: match.id })}: ${match.name}`);
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
registerSkillCommands() {
|
|
567
|
+
if (!this.skillsModule)
|
|
568
|
+
return;
|
|
569
|
+
this.registerCommand({
|
|
570
|
+
name: "skill",
|
|
571
|
+
description: t("repl.skill"),
|
|
572
|
+
usage: t("repl.skill_usage"),
|
|
573
|
+
action: (args) => {
|
|
574
|
+
const subcmd = args[0];
|
|
575
|
+
const arg = args.slice(1).join(" ");
|
|
576
|
+
if (!subcmd || subcmd === "list") {
|
|
577
|
+
const available = this.skillsModule.getAvailable();
|
|
578
|
+
if (available.length === 0) {
|
|
579
|
+
console.log(t("repl.no_skills"));
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
console.log(pc.bold(t("repl.available_skills")));
|
|
583
|
+
for (const skill of available) {
|
|
584
|
+
const tokens = Math.ceil(skill.content.length / 4);
|
|
585
|
+
const loaded = this.skillsModule.getLoaded().some((s) => s.name === skill.name);
|
|
586
|
+
const marker = loaded ? pc.green(" [loaded]") : "";
|
|
587
|
+
console.log(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (subcmd === "loaded") {
|
|
592
|
+
const loaded = this.skillsModule.getLoaded();
|
|
593
|
+
const budget = this.skillsModule.getBudget();
|
|
594
|
+
if (loaded.length === 0) {
|
|
595
|
+
console.log(t("repl.no_loaded"));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
console.log(pc.bold(t("repl.loaded_skills")));
|
|
599
|
+
for (const skill of loaded) {
|
|
600
|
+
const tokens = Math.ceil(skill.content.length / 4);
|
|
601
|
+
console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
602
|
+
}
|
|
603
|
+
console.log(pc.dim(`\n ${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (subcmd === "load") {
|
|
607
|
+
if (!arg) {
|
|
608
|
+
console.log(t("repl.skill_load_usage"));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const result = this.skillsModule.loadByName(arg);
|
|
612
|
+
if (result.success) {
|
|
613
|
+
console.log(pc.green(result.message));
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
console.log(pc.red(result.message));
|
|
617
|
+
}
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (subcmd === "unload") {
|
|
621
|
+
if (!arg) {
|
|
622
|
+
console.log(t("repl.skill_unload_usage"));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
if (this.skillsModule.unload(arg)) {
|
|
626
|
+
console.log(pc.green(t("repl.skill_unloaded", { name: arg })));
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
console.log(pc.red(t("repl.skill_not_loaded", { name: arg })));
|
|
630
|
+
}
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (subcmd === "search") {
|
|
634
|
+
if (!arg) {
|
|
635
|
+
console.log(t("repl.skill_search_usage"));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const results = this.skillsModule.search(arg);
|
|
639
|
+
if (results.length === 0) {
|
|
640
|
+
console.log(t("repl.no_skill_match", { query: arg }));
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
console.log(pc.bold(t("repl.skills_matching", { query: arg })));
|
|
644
|
+
for (const skill of results) {
|
|
645
|
+
const tokens = Math.ceil(skill.content.length / 4);
|
|
646
|
+
console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
console.log(pc.red(t("repl.skill_unknown_sub", { subcmd })));
|
|
651
|
+
console.log(t("repl.skill_usage"));
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
}
|
|
115
655
|
setupCompleter() {
|
|
116
656
|
const slashCommands = Array.from(this.commands.keys());
|
|
117
657
|
this.completer.registerProvider(new SlashCommandProvider(slashCommands));
|
|
@@ -134,7 +674,7 @@ export class Repl {
|
|
|
134
674
|
let inMultiLine = false;
|
|
135
675
|
this.rl.on("line", async (line) => {
|
|
136
676
|
const trimmed = line.trim();
|
|
137
|
-
if (this.agentRunning
|
|
677
|
+
if (this.agentRunning) {
|
|
138
678
|
if (trimmed) {
|
|
139
679
|
this.history.push(trimmed);
|
|
140
680
|
if (this.history.length > this.maxHistory) {
|
|
@@ -202,25 +742,18 @@ export class Repl {
|
|
|
202
742
|
readline.emitKeypressEvents(process.stdin);
|
|
203
743
|
process.stdin.on("keypress", async (str, key) => {
|
|
204
744
|
if (key.name === "escape") {
|
|
205
|
-
// Bun/Node's readline collapses a fast double-Esc into a single
|
|
206
|
-
// keypress whose `sequence` contains two ESC bytes ("\x1b\x1b").
|
|
207
|
-
// Counting bytes (not events) catches both the collapsed case and
|
|
208
|
-
// the case where two separate escape keypresses land in the window.
|
|
209
|
-
const escBytes = key.sequence
|
|
210
|
-
? (key.sequence.match(/\x1b/g) || []).length
|
|
211
|
-
: 1;
|
|
212
745
|
const now = Date.now();
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
this.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
746
|
+
if (now - this.lastEscTime < this.doubleEscDelay) {
|
|
747
|
+
console.log(pc.yellow("\n\n[Ctrl+C] Остановка агента..."));
|
|
748
|
+
this.agent.shutdown();
|
|
749
|
+
this.running = false;
|
|
750
|
+
this.saveHistory();
|
|
751
|
+
this.rl.close();
|
|
752
|
+
process.exit(0);
|
|
221
753
|
}
|
|
222
|
-
|
|
754
|
+
this.lastEscTime = now;
|
|
223
755
|
}
|
|
756
|
+
// Ctrl+V: try to paste image from clipboard
|
|
224
757
|
if (key.ctrl && key.name === "v" && !this.agentRunning) {
|
|
225
758
|
try {
|
|
226
759
|
const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
|
|
@@ -232,10 +765,6 @@ export class Repl {
|
|
|
232
765
|
console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
233
766
|
this.rl.prompt();
|
|
234
767
|
}
|
|
235
|
-
else {
|
|
236
|
-
console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
|
|
237
|
-
this.rl.prompt();
|
|
238
|
-
}
|
|
239
768
|
}
|
|
240
769
|
catch {
|
|
241
770
|
// clipboard read failed — ignore, let terminal paste text normally
|
|
@@ -243,15 +772,14 @@ export class Repl {
|
|
|
243
772
|
}
|
|
244
773
|
});
|
|
245
774
|
}
|
|
246
|
-
|
|
775
|
+
// SIGINT: first Ctrl+C sends graceful shutdown, second forces exit
|
|
247
776
|
process.on("SIGINT", () => {
|
|
248
777
|
if (this.agentRunning) {
|
|
249
778
|
console.log(pc.yellow("\n[Ctrl+C] Остановка агента... (ещё раз — принудительно)"));
|
|
250
779
|
this.agent.shutdown();
|
|
251
780
|
this.agentRunning = false;
|
|
252
|
-
if
|
|
253
|
-
|
|
254
|
-
forceExitTimer = setTimeout(() => process.exit(1), 2000).unref();
|
|
781
|
+
// Force exit after 2s if graceful shutdown hangs
|
|
782
|
+
setTimeout(() => process.exit(1), 2000).unref();
|
|
255
783
|
}
|
|
256
784
|
else {
|
|
257
785
|
process.exit(0);
|
|
@@ -271,7 +799,9 @@ export class Repl {
|
|
|
271
799
|
if (this.agentRunning)
|
|
272
800
|
return;
|
|
273
801
|
this.agentRunning = true;
|
|
802
|
+
this.rl.pause();
|
|
274
803
|
try {
|
|
804
|
+
// Attach pending clipboard image if Ctrl+V was pressed
|
|
275
805
|
if (this.pendingClipboardImage) {
|
|
276
806
|
const contextManager = this.agent.contextManager;
|
|
277
807
|
if (contextManager) {
|
|
@@ -282,18 +812,14 @@ export class Repl {
|
|
|
282
812
|
}
|
|
283
813
|
this.pendingClipboardImage = null;
|
|
284
814
|
}
|
|
285
|
-
this.logger?.logREPL("user", input);
|
|
286
815
|
process.stdout.write("\n" + pc.green(t("repl.agent")));
|
|
287
|
-
const renderer = new Renderer({
|
|
288
|
-
spinner: this.config.ui?.spinner ?? true,
|
|
289
|
-
toolStyle: this.config.ui?.toolStyle ?? "inline",
|
|
290
|
-
});
|
|
816
|
+
const renderer = new Renderer({ spinner: this.config.ui?.spinner ?? true });
|
|
291
817
|
const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
|
|
292
818
|
if (ev.type === "start") {
|
|
293
819
|
renderer.toolStart(ev.tool, ev.args);
|
|
294
820
|
}
|
|
295
821
|
else {
|
|
296
|
-
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error
|
|
822
|
+
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
|
|
297
823
|
}
|
|
298
824
|
}, (phase) => {
|
|
299
825
|
if (phase === "thinking") {
|
|
@@ -305,7 +831,6 @@ export class Repl {
|
|
|
305
831
|
});
|
|
306
832
|
renderer.flush();
|
|
307
833
|
process.stdout.write("\n");
|
|
308
|
-
this.logger?.logREPL(result.success ? "assistant" : "system", result.text?.slice(0, 400) || result.error || "");
|
|
309
834
|
if (!result.success) {
|
|
310
835
|
console.error(pc.red(`${t("error.prefix")}${result.error}`));
|
|
311
836
|
}
|
|
@@ -313,9 +838,11 @@ export class Repl {
|
|
|
313
838
|
}
|
|
314
839
|
finally {
|
|
315
840
|
this.agentRunning = false;
|
|
841
|
+
if (this.running) {
|
|
842
|
+
this.rl.resume();
|
|
843
|
+
}
|
|
316
844
|
}
|
|
317
845
|
}
|
|
318
|
-
commands = new Map();
|
|
319
846
|
registerCommand(cmd) {
|
|
320
847
|
this.commands.set(cmd.name, cmd);
|
|
321
848
|
if (cmd.aliases) {
|
|
@@ -324,15 +851,6 @@ export class Repl {
|
|
|
324
851
|
}
|
|
325
852
|
}
|
|
326
853
|
}
|
|
327
|
-
async withExclusiveInput(fn) {
|
|
328
|
-
this.inputLocked = true;
|
|
329
|
-
try {
|
|
330
|
-
await fn();
|
|
331
|
-
}
|
|
332
|
-
finally {
|
|
333
|
-
this.inputLocked = false;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
854
|
async executeCommand(input) {
|
|
337
855
|
const parts = input.split(/\s+/);
|
|
338
856
|
const name = parts[0].slice(1);
|
|
@@ -352,7 +870,6 @@ export class Repl {
|
|
|
352
870
|
}
|
|
353
871
|
}
|
|
354
872
|
showHelp() {
|
|
355
|
-
const { COMMAND_GROUPS } = require("./repl-commands");
|
|
356
873
|
const order = ["general", "agent", "session", "skill"];
|
|
357
874
|
const seen = new Set();
|
|
358
875
|
for (const groupKey of order) {
|
|
@@ -383,29 +900,18 @@ export class Repl {
|
|
|
383
900
|
console.log();
|
|
384
901
|
}
|
|
385
902
|
}
|
|
386
|
-
lastCompactionShown = 0;
|
|
387
903
|
showContextBar(result) {
|
|
388
|
-
if (result.contextUsed
|
|
389
|
-
result.contextLimit
|
|
390
|
-
result.contextLimit
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
const ui = this.config.ui;
|
|
394
|
-
if (ui?.showContextStats) {
|
|
904
|
+
if (result.contextUsed !== undefined &&
|
|
905
|
+
result.contextLimit !== undefined &&
|
|
906
|
+
result.contextLimit > 0) {
|
|
395
907
|
console.log();
|
|
396
|
-
const ctxLine = formatContextBar(result.contextUsed, result.contextLimit
|
|
908
|
+
const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
|
|
397
909
|
console.log(ctxLine);
|
|
398
910
|
if (result.totalTokens !== undefined && result.totalTokens > 0) {
|
|
399
911
|
const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
|
|
400
912
|
console.log(apiLine);
|
|
401
913
|
}
|
|
402
914
|
}
|
|
403
|
-
else if (ui?.showCompaction && result.compactionCount !== undefined) {
|
|
404
|
-
if (result.compactionCount > this.lastCompactionShown) {
|
|
405
|
-
this.lastCompactionShown = result.compactionCount;
|
|
406
|
-
console.log(pc.dim(`\n ⟳ Context compacted (${result.compactionCount})`));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
915
|
}
|
|
410
916
|
start() {
|
|
411
917
|
this.running = true;
|
|
@@ -420,8 +926,6 @@ export class Repl {
|
|
|
420
926
|
row(t("repl.model"), pc.white(this.config.model));
|
|
421
927
|
row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
|
|
422
928
|
row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
|
|
423
|
-
const si = this.agent.getSystemPromptInfo();
|
|
424
|
-
row(t("repl.sysprompt_label"), pc.dim(t("repl.sysprompt_size", { used: si.tokenCount, budget: sysBudget })));
|
|
425
929
|
if (this.skillsModule) {
|
|
426
930
|
const budget = this.skillsModule.getBudget();
|
|
427
931
|
row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
|
|
@@ -468,10 +972,7 @@ export class Repl {
|
|
|
468
972
|
row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
|
|
469
973
|
}
|
|
470
974
|
const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
|
|
471
|
-
for (const line of box(info, {
|
|
472
|
-
title: t("repl.title", { version }),
|
|
473
|
-
width: headerWidth,
|
|
474
|
-
})) {
|
|
975
|
+
for (const line of box(info, { title: t("repl.title"), width: headerWidth })) {
|
|
475
976
|
console.log(line);
|
|
476
977
|
}
|
|
477
978
|
console.log();
|