micro-models-agent 0.39.1 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +116 -3
- package/dist/cli/main.js +35 -8
- package/dist/cli/repl-commands.js +633 -0
- package/dist/cli/repl.js +110 -611
- package/dist/cli/setup.js +32 -12
- package/dist/config/config.js +46 -30
- package/dist/config/defaults.js +10 -1
- package/dist/config/security.js +15 -8
- package/dist/core/agent-moe.js +24 -12
- package/dist/core/agent.js +281 -47
- package/dist/core/bootstrap.js +52 -36
- package/dist/core/session-logger.js +35 -2
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +79 -15
- package/dist/i18n/index.js +12 -9
- package/dist/i18n/ru.json +79 -15
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +39 -10
- package/dist/logger/app-logger.js +83 -16
- package/dist/logger/file-log.js +151 -0
- package/dist/main.js +492 -283
- package/dist/modules/browser/bridge-server.mjs +113 -105
- package/dist/modules/browser/session.js +108 -60
- package/dist/modules/certification/cli.js +176 -0
- package/dist/modules/certification/fact-checker.js +84 -0
- package/dist/modules/certification/loader.js +111 -0
- package/dist/modules/certification/manifest.js +50 -0
- package/dist/modules/certification/runner.js +162 -0
- package/dist/modules/certification/scenarios.js +124 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/manager.js +119 -10
- package/dist/modules/execution/auditor.js +33 -39
- package/dist/modules/execution/index.js +8 -6
- package/dist/modules/execution/module.js +474 -32
- package/dist/modules/execution/moe-executor.js +97 -40
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +159 -0
- package/dist/modules/execution/planner.js +63 -13
- package/dist/modules/execution/stuck-detector.js +252 -39
- package/dist/modules/execution/tracker.js +21 -7
- package/dist/modules/execution/verifier.js +46 -17
- package/dist/modules/hallucination/confidence.js +7 -2
- package/dist/modules/hallucination/consistency.js +8 -42
- package/dist/modules/hallucination/detector.js +26 -21
- package/dist/modules/hallucination/factual.js +170 -150
- package/dist/modules/hallucination/index.js +5 -4
- package/dist/modules/hallucination/js-identifiers.js +72 -0
- package/dist/modules/hallucination/llm-judge.js +103 -0
- package/dist/modules/index.js +5 -5
- package/dist/modules/lsp/client.js +235 -0
- package/dist/modules/lsp/config.js +81 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +68 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +8 -2
- package/dist/modules/memory/store.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
- package/dist/modules/processes/index.js +1 -2
- package/dist/modules/processes/registry.js +125 -35
- package/dist/modules/processes/runner.js +9 -110
- package/dist/modules/security/audit-log.js +30 -10
- package/dist/modules/security/command-validator.js +42 -16
- package/dist/modules/security/content-scanner.js +9 -8
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +64 -10
- package/dist/modules/security/security-policies.js +221 -67
- package/dist/modules/security/session-encryption.js +42 -25
- package/dist/modules/session/manager.js +15 -10
- package/dist/modules/session/store.js +62 -8
- package/dist/modules/skills/index.js +2 -3
- package/dist/modules/skills/module.js +10 -23
- package/dist/tools/bash.js +287 -90
- package/dist/tools/create-dir.js +0 -1
- package/dist/tools/delete-file.js +0 -1
- package/dist/tools/edit-file.js +10 -8
- package/dist/tools/executor.js +57 -7
- package/dist/tools/grep-tool.js +51 -29
- package/dist/tools/index.js +55 -40
- package/dist/tools/load-skill.js +14 -18
- package/dist/tools/move-file.js +3 -2
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +15 -5
- package/dist/tools/search-history.js +42 -22
- package/dist/tools/subagent.js +21 -12
- package/dist/tools/web-browse.js +54 -25
- package/dist/tools/web-fetch.js +60 -34
- package/dist/tools/web-search.js +39 -20
- package/dist/tools/write-file.js +13 -10
- package/dist/ui/diff.js +9 -16
- package/dist/ui/renderer.js +69 -6
- package/package.json +48 -45
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/skills/matcher.js +0 -27
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 } from "path";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
6
7
|
import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, } from "./completer";
|
|
7
8
|
import { Renderer } from "../ui/renderer";
|
|
8
9
|
import { box } from "../ui/box";
|
|
9
|
-
import { renderTable } from "../ui/table";
|
|
10
10
|
import { t } from "../i18n/index";
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
};
|
|
35
|
-
function formatContextBar(used, limit) {
|
|
11
|
+
import { registerAllCommands } from "./repl-commands";
|
|
12
|
+
function readVersion() {
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const candidates = [
|
|
15
|
+
join(here, "..", "..", "package.json"),
|
|
16
|
+
join(here, "..", "package.json"),
|
|
17
|
+
];
|
|
18
|
+
for (const p of candidates) {
|
|
19
|
+
if (existsSync(p)) {
|
|
20
|
+
try {
|
|
21
|
+
const raw = JSON.parse(readFileSync(p, "utf8"));
|
|
22
|
+
if (raw.version)
|
|
23
|
+
return raw.version;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Broken package.json — fall through to the next candidate
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return "0.0.0";
|
|
31
|
+
}
|
|
32
|
+
const version = readVersion();
|
|
33
|
+
function formatContextBar(used, limit, compactions, quality) {
|
|
36
34
|
const pct = Math.min(100, Math.round((used / limit) * 100));
|
|
37
35
|
const barLen = 20;
|
|
38
36
|
const filled = Math.round((pct / 100) * barLen);
|
|
39
37
|
const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
|
|
40
38
|
const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
|
|
41
|
-
|
|
39
|
+
let line = ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
|
|
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;
|
|
42
48
|
}
|
|
43
49
|
export class Repl {
|
|
44
|
-
rl;
|
|
45
|
-
commands = new Map();
|
|
46
50
|
completer = new Completer();
|
|
47
51
|
running = false;
|
|
48
52
|
agentRunning = false;
|
|
49
|
-
|
|
50
|
-
config;
|
|
51
|
-
sessionManager;
|
|
52
|
-
skillsModule;
|
|
53
|
-
pluginManager;
|
|
53
|
+
inputLocked = false;
|
|
54
54
|
historyPath;
|
|
55
55
|
history = [];
|
|
56
56
|
maxHistory = 1000;
|
|
@@ -60,22 +60,25 @@ export class Repl {
|
|
|
60
60
|
baseDir;
|
|
61
61
|
noAgentsMd;
|
|
62
62
|
pendingClipboardImage = null;
|
|
63
|
-
|
|
63
|
+
rl;
|
|
64
|
+
agent;
|
|
65
|
+
config;
|
|
66
|
+
sessionManager;
|
|
67
|
+
skillsModule;
|
|
68
|
+
pluginManager;
|
|
69
|
+
logger;
|
|
70
|
+
constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger) {
|
|
64
71
|
this.agent = agent;
|
|
65
72
|
this.config = config;
|
|
66
73
|
this.sessionManager = sessionManager;
|
|
67
74
|
this.skillsModule = skillsModule;
|
|
68
75
|
this.pluginManager = pluginManager;
|
|
76
|
+
this.logger = logger;
|
|
69
77
|
this.configDir = configDir || join(homedir(), ".mma");
|
|
70
78
|
this.baseDir = baseDir || process.cwd();
|
|
71
79
|
this.noAgentsMd = noAgentsMd === true;
|
|
72
80
|
this.historyPath = join(homedir(), ".mma", "repl-history");
|
|
73
81
|
this.loadHistory();
|
|
74
|
-
this.registerBuiltinCommands();
|
|
75
|
-
this.registerMmaCommands();
|
|
76
|
-
this.registerSessionCommands();
|
|
77
|
-
this.registerSkillCommands();
|
|
78
|
-
this.setupCompleter();
|
|
79
82
|
this.rl = readline.createInterface({
|
|
80
83
|
input: process.stdin,
|
|
81
84
|
output: process.stdout,
|
|
@@ -90,6 +93,8 @@ export class Repl {
|
|
|
90
93
|
return [[], line];
|
|
91
94
|
},
|
|
92
95
|
});
|
|
96
|
+
registerAllCommands(this);
|
|
97
|
+
this.setupCompleter();
|
|
93
98
|
this.setupListeners();
|
|
94
99
|
}
|
|
95
100
|
loadHistory() {
|
|
@@ -107,551 +112,6 @@ export class Repl {
|
|
|
107
112
|
const allHistory = this.history.slice(-this.maxHistory);
|
|
108
113
|
writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
|
|
109
114
|
}
|
|
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
|
-
}
|
|
655
115
|
setupCompleter() {
|
|
656
116
|
const slashCommands = Array.from(this.commands.keys());
|
|
657
117
|
this.completer.registerProvider(new SlashCommandProvider(slashCommands));
|
|
@@ -674,7 +134,7 @@ export class Repl {
|
|
|
674
134
|
let inMultiLine = false;
|
|
675
135
|
this.rl.on("line", async (line) => {
|
|
676
136
|
const trimmed = line.trim();
|
|
677
|
-
if (this.agentRunning) {
|
|
137
|
+
if (this.agentRunning || this.inputLocked) {
|
|
678
138
|
if (trimmed) {
|
|
679
139
|
this.history.push(trimmed);
|
|
680
140
|
if (this.history.length > this.maxHistory) {
|
|
@@ -742,18 +202,25 @@ export class Repl {
|
|
|
742
202
|
readline.emitKeypressEvents(process.stdin);
|
|
743
203
|
process.stdin.on("keypress", async (str, key) => {
|
|
744
204
|
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;
|
|
745
212
|
const now = Date.now();
|
|
746
|
-
|
|
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);
|
|
753
|
-
}
|
|
213
|
+
const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
|
|
754
214
|
this.lastEscTime = now;
|
|
215
|
+
if (escBytes >= 2 || withinWindow) {
|
|
216
|
+
this.lastEscTime = 0;
|
|
217
|
+
if (this.agentRunning) {
|
|
218
|
+
process.stdout.write(pc.yellow(`\n${t("repl.interrupt")}\n`));
|
|
219
|
+
this.agent.shutdown();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
755
223
|
}
|
|
756
|
-
// Ctrl+V: try to paste image from clipboard
|
|
757
224
|
if (key.ctrl && key.name === "v" && !this.agentRunning) {
|
|
758
225
|
try {
|
|
759
226
|
const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
|
|
@@ -765,6 +232,10 @@ export class Repl {
|
|
|
765
232
|
console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
|
|
766
233
|
this.rl.prompt();
|
|
767
234
|
}
|
|
235
|
+
else {
|
|
236
|
+
console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
|
|
237
|
+
this.rl.prompt();
|
|
238
|
+
}
|
|
768
239
|
}
|
|
769
240
|
catch {
|
|
770
241
|
// clipboard read failed — ignore, let terminal paste text normally
|
|
@@ -772,14 +243,15 @@ export class Repl {
|
|
|
772
243
|
}
|
|
773
244
|
});
|
|
774
245
|
}
|
|
775
|
-
|
|
246
|
+
let forceExitTimer = null;
|
|
776
247
|
process.on("SIGINT", () => {
|
|
777
248
|
if (this.agentRunning) {
|
|
778
249
|
console.log(pc.yellow("\n[Ctrl+C] Остановка агента... (ещё раз — принудительно)"));
|
|
779
250
|
this.agent.shutdown();
|
|
780
251
|
this.agentRunning = false;
|
|
781
|
-
|
|
782
|
-
|
|
252
|
+
if (forceExitTimer)
|
|
253
|
+
clearTimeout(forceExitTimer);
|
|
254
|
+
forceExitTimer = setTimeout(() => process.exit(1), 2000).unref();
|
|
783
255
|
}
|
|
784
256
|
else {
|
|
785
257
|
process.exit(0);
|
|
@@ -799,9 +271,7 @@ export class Repl {
|
|
|
799
271
|
if (this.agentRunning)
|
|
800
272
|
return;
|
|
801
273
|
this.agentRunning = true;
|
|
802
|
-
this.rl.pause();
|
|
803
274
|
try {
|
|
804
|
-
// Attach pending clipboard image if Ctrl+V was pressed
|
|
805
275
|
if (this.pendingClipboardImage) {
|
|
806
276
|
const contextManager = this.agent.contextManager;
|
|
807
277
|
if (contextManager) {
|
|
@@ -812,14 +282,18 @@ export class Repl {
|
|
|
812
282
|
}
|
|
813
283
|
this.pendingClipboardImage = null;
|
|
814
284
|
}
|
|
285
|
+
this.logger?.logREPL("user", input);
|
|
815
286
|
process.stdout.write("\n" + pc.green(t("repl.agent")));
|
|
816
|
-
const renderer = new Renderer({
|
|
287
|
+
const renderer = new Renderer({
|
|
288
|
+
spinner: this.config.ui?.spinner ?? true,
|
|
289
|
+
toolStyle: this.config.ui?.toolStyle ?? "inline",
|
|
290
|
+
});
|
|
817
291
|
const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
|
|
818
292
|
if (ev.type === "start") {
|
|
819
293
|
renderer.toolStart(ev.tool, ev.args);
|
|
820
294
|
}
|
|
821
295
|
else {
|
|
822
|
-
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
|
|
296
|
+
renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
|
|
823
297
|
}
|
|
824
298
|
}, (phase) => {
|
|
825
299
|
if (phase === "thinking") {
|
|
@@ -831,6 +305,7 @@ export class Repl {
|
|
|
831
305
|
});
|
|
832
306
|
renderer.flush();
|
|
833
307
|
process.stdout.write("\n");
|
|
308
|
+
this.logger?.logREPL(result.success ? "assistant" : "system", result.text?.slice(0, 400) || result.error || "");
|
|
834
309
|
if (!result.success) {
|
|
835
310
|
console.error(pc.red(`${t("error.prefix")}${result.error}`));
|
|
836
311
|
}
|
|
@@ -838,11 +313,9 @@ export class Repl {
|
|
|
838
313
|
}
|
|
839
314
|
finally {
|
|
840
315
|
this.agentRunning = false;
|
|
841
|
-
if (this.running) {
|
|
842
|
-
this.rl.resume();
|
|
843
|
-
}
|
|
844
316
|
}
|
|
845
317
|
}
|
|
318
|
+
commands = new Map();
|
|
846
319
|
registerCommand(cmd) {
|
|
847
320
|
this.commands.set(cmd.name, cmd);
|
|
848
321
|
if (cmd.aliases) {
|
|
@@ -851,6 +324,15 @@ export class Repl {
|
|
|
851
324
|
}
|
|
852
325
|
}
|
|
853
326
|
}
|
|
327
|
+
async withExclusiveInput(fn) {
|
|
328
|
+
this.inputLocked = true;
|
|
329
|
+
try {
|
|
330
|
+
await fn();
|
|
331
|
+
}
|
|
332
|
+
finally {
|
|
333
|
+
this.inputLocked = false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
854
336
|
async executeCommand(input) {
|
|
855
337
|
const parts = input.split(/\s+/);
|
|
856
338
|
const name = parts[0].slice(1);
|
|
@@ -870,6 +352,7 @@ export class Repl {
|
|
|
870
352
|
}
|
|
871
353
|
}
|
|
872
354
|
showHelp() {
|
|
355
|
+
const { COMMAND_GROUPS } = require("./repl-commands");
|
|
873
356
|
const order = ["general", "agent", "session", "skill"];
|
|
874
357
|
const seen = new Set();
|
|
875
358
|
for (const groupKey of order) {
|
|
@@ -900,18 +383,29 @@ export class Repl {
|
|
|
900
383
|
console.log();
|
|
901
384
|
}
|
|
902
385
|
}
|
|
386
|
+
lastCompactionShown = 0;
|
|
903
387
|
showContextBar(result) {
|
|
904
|
-
if (result.contextUsed
|
|
905
|
-
result.contextLimit
|
|
906
|
-
result.contextLimit
|
|
388
|
+
if (result.contextUsed === undefined ||
|
|
389
|
+
result.contextLimit === undefined ||
|
|
390
|
+
result.contextLimit <= 0) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
const ui = this.config.ui;
|
|
394
|
+
if (ui?.showContextStats) {
|
|
907
395
|
console.log();
|
|
908
|
-
const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
|
|
396
|
+
const ctxLine = formatContextBar(result.contextUsed, result.contextLimit, result.compactionCount, result.contextQuality);
|
|
909
397
|
console.log(ctxLine);
|
|
910
398
|
if (result.totalTokens !== undefined && result.totalTokens > 0) {
|
|
911
399
|
const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
|
|
912
400
|
console.log(apiLine);
|
|
913
401
|
}
|
|
914
402
|
}
|
|
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
|
+
}
|
|
915
409
|
}
|
|
916
410
|
start() {
|
|
917
411
|
this.running = true;
|
|
@@ -926,6 +420,8 @@ export class Repl {
|
|
|
926
420
|
row(t("repl.model"), pc.white(this.config.model));
|
|
927
421
|
row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
|
|
928
422
|
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 })));
|
|
929
425
|
if (this.skillsModule) {
|
|
930
426
|
const budget = this.skillsModule.getBudget();
|
|
931
427
|
row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
|
|
@@ -972,7 +468,10 @@ export class Repl {
|
|
|
972
468
|
row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
|
|
973
469
|
}
|
|
974
470
|
const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
|
|
975
|
-
for (const line of box(info, {
|
|
471
|
+
for (const line of box(info, {
|
|
472
|
+
title: t("repl.title", { version }),
|
|
473
|
+
width: headerWidth,
|
|
474
|
+
})) {
|
|
976
475
|
console.log(line);
|
|
977
476
|
}
|
|
978
477
|
console.log();
|