micro-models-agent 0.19.1 → 0.20.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.
Files changed (167) hide show
  1. package/dist/main.js +18112 -0
  2. package/package.json +1 -1
  3. package/dist/cli/commands.js +0 -237
  4. package/dist/cli/completer.js +0 -168
  5. package/dist/cli/index.js +0 -2
  6. package/dist/cli/main.js +0 -113
  7. package/dist/cli/repl.js +0 -1010
  8. package/dist/cli/security-commands.js +0 -166
  9. package/dist/cli/setup.js +0 -229
  10. package/dist/config/config.js +0 -186
  11. package/dist/config/defaults.js +0 -91
  12. package/dist/config/experts.js +0 -15
  13. package/dist/config/index.js +0 -3
  14. package/dist/config/security.js +0 -193
  15. package/dist/config/types.js +0 -1
  16. package/dist/core/agent-moe.js +0 -98
  17. package/dist/core/agent.js +0 -471
  18. package/dist/core/bootstrap.js +0 -314
  19. package/dist/core/index.js +0 -2
  20. package/dist/core/prompt-builder.js +0 -55
  21. package/dist/core/session-logger.js +0 -122
  22. package/dist/core/types.js +0 -1
  23. package/dist/i18n/en.json +0 -461
  24. package/dist/i18n/index.js +0 -43
  25. package/dist/i18n/ru.json +0 -461
  26. package/dist/index.js +0 -22
  27. package/dist/llm/image-utils.js +0 -144
  28. package/dist/llm/index.js +0 -4
  29. package/dist/llm/model-loader.js +0 -78
  30. package/dist/llm/openai-compat.js +0 -324
  31. package/dist/llm/orchestrator.js +0 -194
  32. package/dist/llm/provider.js +0 -10
  33. package/dist/llm/response.js +0 -39
  34. package/dist/llm/token-counter.js +0 -39
  35. package/dist/llm/types.js +0 -1
  36. package/dist/logger/app-logger.js +0 -76
  37. package/dist/logger/index.js +0 -1
  38. package/dist/migration/backup.js +0 -45
  39. package/dist/migration/detect.js +0 -50
  40. package/dist/migration/index.js +0 -2
  41. package/dist/modules/browser/actions.js +0 -46
  42. package/dist/modules/browser/cookie-store.js +0 -24
  43. package/dist/modules/browser/index.js +0 -5
  44. package/dist/modules/browser/module.js +0 -28
  45. package/dist/modules/browser/session.js +0 -287
  46. package/dist/modules/browser/snapshot.js +0 -114
  47. package/dist/modules/browser/types.js +0 -9
  48. package/dist/modules/context/index.js +0 -1
  49. package/dist/modules/context/manager.js +0 -240
  50. package/dist/modules/execution/auditor.js +0 -72
  51. package/dist/modules/execution/index.js +0 -6
  52. package/dist/modules/execution/module.js +0 -337
  53. package/dist/modules/execution/moe-executor.js +0 -209
  54. package/dist/modules/execution/plan-validator.js +0 -153
  55. package/dist/modules/execution/planner.js +0 -35
  56. package/dist/modules/execution/stuck-detector.js +0 -134
  57. package/dist/modules/execution/tracker.js +0 -53
  58. package/dist/modules/execution/types.js +0 -1
  59. package/dist/modules/execution/verifier.js +0 -149
  60. package/dist/modules/hallucination/confidence.js +0 -59
  61. package/dist/modules/hallucination/consistency.js +0 -66
  62. package/dist/modules/hallucination/detector.js +0 -33
  63. package/dist/modules/hallucination/index.js +0 -3
  64. package/dist/modules/index.js +0 -5
  65. package/dist/modules/indexer/cache.js +0 -38
  66. package/dist/modules/indexer/index.js +0 -3
  67. package/dist/modules/indexer/module.js +0 -192
  68. package/dist/modules/indexer/walker.js +0 -101
  69. package/dist/modules/mcp/client.js +0 -393
  70. package/dist/modules/mcp/index.js +0 -3
  71. package/dist/modules/mcp/module.js +0 -146
  72. package/dist/modules/mcp/registry.js +0 -15
  73. package/dist/modules/memory/index.js +0 -1
  74. package/dist/modules/memory/module.js +0 -48
  75. package/dist/modules/memory/search.js +0 -40
  76. package/dist/modules/memory/store.js +0 -65
  77. package/dist/modules/pipelines/engine.js +0 -60
  78. package/dist/modules/pipelines/index.js +0 -3
  79. package/dist/modules/pipelines/parser.js +0 -53
  80. package/dist/modules/pipelines/template.js +0 -14
  81. package/dist/modules/plugins/builtin/lint-on-write.js +0 -121
  82. package/dist/modules/plugins/builtin/notify.js +0 -8
  83. package/dist/modules/plugins/index.js +0 -1
  84. package/dist/modules/plugins/loader.js +0 -28
  85. package/dist/modules/plugins/manager.js +0 -161
  86. package/dist/modules/plugins/types.js +0 -1
  87. package/dist/modules/processes/detect.js +0 -34
  88. package/dist/modules/processes/index.js +0 -3
  89. package/dist/modules/processes/registry.js +0 -148
  90. package/dist/modules/processes/runner.js +0 -124
  91. package/dist/modules/registry.js +0 -45
  92. package/dist/modules/security/audit-log.js +0 -116
  93. package/dist/modules/security/audit-notifier.js +0 -292
  94. package/dist/modules/security/command-validator.js +0 -185
  95. package/dist/modules/security/content-scanner.js +0 -52
  96. package/dist/modules/security/data-sanitizer.js +0 -97
  97. package/dist/modules/security/encryption.js +0 -240
  98. package/dist/modules/security/index.js +0 -14
  99. package/dist/modules/security/network-validator.js +0 -79
  100. package/dist/modules/security/path-validator.js +0 -155
  101. package/dist/modules/security/rate-limiter.js +0 -119
  102. package/dist/modules/security/security-policies.js +0 -393
  103. package/dist/modules/security/session-encryption.js +0 -193
  104. package/dist/modules/security/session-isolation.js +0 -95
  105. package/dist/modules/session/index.js +0 -3
  106. package/dist/modules/session/manager.js +0 -167
  107. package/dist/modules/session/module.js +0 -24
  108. package/dist/modules/session/store.js +0 -174
  109. package/dist/modules/session/types.js +0 -1
  110. package/dist/modules/skills/index.js +0 -3
  111. package/dist/modules/skills/loader.js +0 -72
  112. package/dist/modules/skills/matcher.js +0 -27
  113. package/dist/modules/skills/module.js +0 -143
  114. package/dist/modules/types.js +0 -1
  115. package/dist/modules/updater/checker.js +0 -32
  116. package/dist/modules/updater/index.js +0 -1
  117. package/dist/modules/user-profile/compressor.js +0 -16
  118. package/dist/modules/user-profile/index.js +0 -1
  119. package/dist/modules/user-profile/profile.js +0 -68
  120. package/dist/skills/builtin/builtin/git.md +0 -36
  121. package/dist/skills/builtin/builtin/typescript.md +0 -35
  122. package/dist/tools/approve.js +0 -32
  123. package/dist/tools/attach-image.js +0 -89
  124. package/dist/tools/bash.js +0 -140
  125. package/dist/tools/browser.js +0 -97
  126. package/dist/tools/create-dir.js +0 -55
  127. package/dist/tools/delete-file.js +0 -62
  128. package/dist/tools/edit-file.js +0 -77
  129. package/dist/tools/executor.js +0 -95
  130. package/dist/tools/file-info.js +0 -45
  131. package/dist/tools/filter-tools.js +0 -10
  132. package/dist/tools/glob-tool.js +0 -26
  133. package/dist/tools/grep-tool.js +0 -64
  134. package/dist/tools/index.js +0 -52
  135. package/dist/tools/list-dir.js +0 -47
  136. package/dist/tools/load-skill.js +0 -48
  137. package/dist/tools/mcp-call.js +0 -68
  138. package/dist/tools/move-file.js +0 -84
  139. package/dist/tools/path-utils.js +0 -51
  140. package/dist/tools/pipeline-run.js +0 -144
  141. package/dist/tools/preview.js +0 -2
  142. package/dist/tools/process-kill.js +0 -29
  143. package/dist/tools/process-list.js +0 -38
  144. package/dist/tools/process-log.js +0 -41
  145. package/dist/tools/question.js +0 -142
  146. package/dist/tools/read-file.js +0 -70
  147. package/dist/tools/recall.js +0 -110
  148. package/dist/tools/registry.js +0 -36
  149. package/dist/tools/remember.js +0 -67
  150. package/dist/tools/scope-check.js +0 -30
  151. package/dist/tools/search-history.js +0 -64
  152. package/dist/tools/subagent.js +0 -142
  153. package/dist/tools/types.js +0 -1
  154. package/dist/tools/user-input.js +0 -123
  155. package/dist/tools/web-browse.js +0 -57
  156. package/dist/tools/web-fetch.js +0 -72
  157. package/dist/tools/web-search.js +0 -59
  158. package/dist/tools/write-file.js +0 -79
  159. package/dist/ui/box.js +0 -81
  160. package/dist/ui/colors.js +0 -4
  161. package/dist/ui/diff.js +0 -185
  162. package/dist/ui/index.js +0 -6
  163. package/dist/ui/md-formatter.js +0 -212
  164. package/dist/ui/output.js +0 -13
  165. package/dist/ui/renderer.js +0 -141
  166. package/dist/ui/spinner.js +0 -70
  167. package/dist/ui/table.js +0 -144
package/dist/cli/repl.js DELETED
@@ -1,1010 +0,0 @@
1
- import * as readline from "readline";
2
- import { pc } from "../ui/colors";
3
- import { existsSync, readFileSync, writeFileSync } from "fs";
4
- import { join, dirname } from "path";
5
- import { homedir } from "os";
6
- import { fileURLToPath } from "url";
7
- import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, } from "./completer";
8
- import { Renderer } from "../ui/renderer";
9
- import { box } from "../ui/box";
10
- import { renderTable } from "../ui/table";
11
- import { t } from "../i18n/index";
12
- import { runSetup } from "./setup";
13
- import { saveConfig } from "../config/config";
14
- import { getMessageText } from "../llm/provider";
15
- function readVersion() {
16
- const here = dirname(fileURLToPath(import.meta.url));
17
- const candidates = [
18
- join(here, "..", "..", "package.json"),
19
- join(here, "..", "package.json"),
20
- ];
21
- for (const p of candidates) {
22
- if (existsSync(p)) {
23
- const raw = JSON.parse(readFileSync(p, "utf8"));
24
- if (raw.version)
25
- return raw.version;
26
- }
27
- }
28
- return "0.0.0";
29
- }
30
- const version = readVersion();
31
- const COMMAND_GROUPS = {
32
- help: "general",
33
- exit: "general",
34
- clear: "general",
35
- run: "general",
36
- image: "general",
37
- config: "agent",
38
- status: "agent",
39
- reasoning: "agent",
40
- provider: "agent",
41
- model: "agent",
42
- context: "agent",
43
- reload: "agent",
44
- wizard: "agent",
45
- sessions: "session",
46
- new: "session",
47
- resume: "session",
48
- rename: "session",
49
- delete: "session",
50
- skill: "skill",
51
- };
52
- function formatContextBar(used, limit) {
53
- const pct = Math.min(100, Math.round((used / limit) * 100));
54
- const barLen = 20;
55
- const filled = Math.round((pct / 100) * barLen);
56
- const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
57
- const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
58
- return ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
59
- }
60
- export class Repl {
61
- rl;
62
- commands = new Map();
63
- completer = new Completer();
64
- running = false;
65
- agentRunning = false;
66
- agent;
67
- config;
68
- sessionManager;
69
- skillsModule;
70
- pluginManager;
71
- historyPath;
72
- history = [];
73
- maxHistory = 1000;
74
- lastEscTime = 0;
75
- doubleEscDelay = 500;
76
- configDir;
77
- baseDir;
78
- noAgentsMd;
79
- pendingClipboardImage = null;
80
- constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd) {
81
- this.agent = agent;
82
- this.config = config;
83
- this.sessionManager = sessionManager;
84
- this.skillsModule = skillsModule;
85
- this.pluginManager = pluginManager;
86
- this.configDir = configDir || join(homedir(), ".mma");
87
- this.baseDir = baseDir || process.cwd();
88
- this.noAgentsMd = noAgentsMd === true;
89
- this.historyPath = join(homedir(), ".mma", "repl-history");
90
- this.loadHistory();
91
- this.registerBuiltinCommands();
92
- this.registerMmaCommands();
93
- this.registerSessionCommands();
94
- this.registerSkillCommands();
95
- this.setupCompleter();
96
- this.rl = readline.createInterface({
97
- input: process.stdin,
98
- output: process.stdout,
99
- prompt: pc.cyan("> "),
100
- history: this.history,
101
- historySize: this.maxHistory,
102
- tabSize: 2,
103
- completer: (line) => {
104
- const [matches, partial] = this.completer.complete(line);
105
- if (matches.length > 0)
106
- return [matches, partial];
107
- return [[], line];
108
- },
109
- });
110
- this.setupListeners();
111
- }
112
- loadHistory() {
113
- if (existsSync(this.historyPath)) {
114
- try {
115
- const raw = readFileSync(this.historyPath, "utf-8");
116
- this.history = raw.split("\n").filter(Boolean).slice(-this.maxHistory);
117
- }
118
- catch {
119
- this.history = [];
120
- }
121
- }
122
- }
123
- saveHistory() {
124
- const allHistory = this.history.slice(-this.maxHistory);
125
- writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
126
- }
127
- registerBuiltinCommands() {
128
- this.registerCommand({
129
- name: "help",
130
- description: t("repl.help"),
131
- usage: t("repl.help_usage"),
132
- action: () => this.showHelp(),
133
- });
134
- this.registerCommand({
135
- name: "exit",
136
- description: t("repl.exit"),
137
- aliases: ["quit", "q"],
138
- usage: t("repl.exit_usage"),
139
- action: () => this.stop(),
140
- });
141
- this.registerCommand({
142
- name: "clear",
143
- description: t("repl.clear"),
144
- usage: t("repl.clear_usage"),
145
- action: () => {
146
- console.clear();
147
- },
148
- });
149
- }
150
- registerMmaCommands() {
151
- this.registerCommand({
152
- name: "run",
153
- description: t("repl.run"),
154
- usage: t("repl.run_usage"),
155
- action: async (args) => {
156
- if (args.length === 0) {
157
- console.log(t("repl.run_usage"));
158
- return;
159
- }
160
- const prompt = args.join(" ");
161
- await this.runAgent(prompt);
162
- },
163
- });
164
- this.registerCommand({
165
- name: "image",
166
- description: t("repl.image"),
167
- aliases: ["img"],
168
- usage: t("repl.image_usage"),
169
- action: async (args) => {
170
- const source = args.join(" ");
171
- if (!source) {
172
- console.log(t("repl.image_usage"));
173
- return;
174
- }
175
- try {
176
- const { loadFileAsDataUrl, loadUrlAsDataUrl, readClipboardImage } = await import("../llm/image-utils");
177
- const { existsSync } = await import("fs");
178
- const { resolve } = await import("path");
179
- let dataUrl;
180
- let label;
181
- if (source.toLowerCase() === "clipboard") {
182
- const clipBuf = await readClipboardImage();
183
- if (!clipBuf) {
184
- console.log(pc.yellow(t("image.clipboard_empty")));
185
- return;
186
- }
187
- const { bufferToDataUrl } = await import("../llm/image-utils");
188
- const result = await bufferToDataUrl(clipBuf);
189
- dataUrl = result.dataUrl;
190
- label = "clipboard";
191
- }
192
- else if (source.startsWith("http://") ||
193
- source.startsWith("https://")) {
194
- const result = await loadUrlAsDataUrl(source);
195
- dataUrl = result.dataUrl;
196
- label = source;
197
- }
198
- else {
199
- const absPath = resolve(process.cwd(), source);
200
- if (!existsSync(absPath)) {
201
- console.log(pc.red(t("image.not_found", { path: source })));
202
- return;
203
- }
204
- const result = await loadFileAsDataUrl(absPath);
205
- dataUrl = result.dataUrl;
206
- label = source;
207
- }
208
- // Store the image data on the agent's context manager for the next message
209
- const contextManager = this.agent.contextManager;
210
- if (!contextManager) {
211
- console.log(pc.red(t("image.no_context")));
212
- return;
213
- }
214
- contextManager.addPendingImage({
215
- type: "image_url",
216
- image_url: { url: dataUrl },
217
- });
218
- const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
219
- console.log(pc.green(t("image.attached", { source: label, size: `${sizeKb} KB` })));
220
- }
221
- catch (err) {
222
- console.log(pc.red(t("image.error", { message: err.message })));
223
- }
224
- },
225
- });
226
- this.registerCommand({
227
- name: "config",
228
- description: t("repl.config"),
229
- usage: t("repl.config_usage"),
230
- action: () => {
231
- const ctx = this.config.contextWindow;
232
- const sys = Math.floor(ctx * this.config.contextBudget.systemPrompt);
233
- const res = Math.floor(ctx * this.config.contextBudget.responseReserve);
234
- console.log(`${t("repl.model")} ${this.config.model}`);
235
- console.log(`${t("repl.provider")} ${this.config.provider.type} → ${this.config.provider.baseUrl}`);
236
- console.log(`${t("repl.context")} ${ctx} (sys:${sys} res:${res} hist:${ctx - sys - res})`);
237
- console.log(`${t("repl.max_iters")} ${this.config.maxToolIterations}`);
238
- console.log(`${t("repl.stuck_thresh")} ${this.config.stuckThreshold}`);
239
- console.log(`${t("repl.reasoning_label")} ${this.config.showReasoning ? pc.green(t("repl.show")) : pc.dim(t("repl.hide"))}`);
240
- console.log(`${t("repl.log_level")} ${this.config.logLevel}`);
241
- console.log(`${t("repl.locale")} ${this.config.locale}`);
242
- const meta = this.sessionManager?.getActiveMeta();
243
- if (meta) {
244
- console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} msgs`);
245
- }
246
- },
247
- });
248
- this.registerCommand({
249
- name: "reasoning",
250
- description: t("repl.reasoning"),
251
- usage: t("repl.reasoning_usage"),
252
- action: () => {
253
- this.config.showReasoning = !this.config.showReasoning;
254
- const status = this.config.showReasoning
255
- ? pc.green(t("repl.show"))
256
- : pc.dim(t("repl.hide"));
257
- console.log(t("repl.reasoning_status", { status }));
258
- },
259
- });
260
- this.registerCommand({
261
- name: "status",
262
- description: t("repl.status"),
263
- usage: t("repl.status_usage"),
264
- action: () => {
265
- console.log(`${t("repl.model")} ${this.config.model}`);
266
- console.log(`${t("repl.provider")} ${this.config.provider.type} @ ${this.config.provider.baseUrl}`);
267
- const meta = this.sessionManager?.getActiveMeta();
268
- if (meta) {
269
- console.log(`${t("repl.session_label")} ${meta.name} (${meta.id}) — ${meta.messageCount} messages`);
270
- }
271
- },
272
- });
273
- this.registerCommand({
274
- name: "wizard",
275
- description: t("repl.wizard"),
276
- aliases: ["setup"],
277
- usage: t("repl.wizard_usage"),
278
- action: async () => {
279
- console.log(pc.yellow(t("repl.wizard_running")));
280
- const answers = await runSetup();
281
- const configPath = join(homedir(), ".mma", "config.json");
282
- this.config.provider.type = answers.provider;
283
- this.config.provider.baseUrl = answers.apiBase;
284
- this.config.provider.apiKey = answers.apiKey;
285
- this.config.model = answers.model;
286
- this.config.contextWindow = answers.contextWindow;
287
- this.config.maxToolIterations = answers.maxToolIterations;
288
- this.config.locale = answers.locale;
289
- saveConfig(this.config, configPath);
290
- console.log(pc.green(t("cli.config_saved")));
291
- },
292
- });
293
- this.registerCommand({
294
- name: "provider",
295
- description: t("repl.provider_list"),
296
- usage: t("repl.provider_usage"),
297
- action: (args) => {
298
- const subcmd = args[0];
299
- if (!subcmd || subcmd === "list") {
300
- console.log(`${t("repl.provider_current")} ${this.config.provider.type}`);
301
- console.log(` ${this.config.provider.baseUrl}`);
302
- return;
303
- }
304
- if (subcmd === "use") {
305
- const name = args[1];
306
- if (!name) {
307
- console.log(t("repl.provider_usage"));
308
- return;
309
- }
310
- this.config.provider.type = name;
311
- const configPath = join(homedir(), ".mma", "config.json");
312
- saveConfig(this.config, configPath);
313
- console.log(pc.green(t("repl.provider_set", { name })));
314
- return;
315
- }
316
- console.log(t("repl.provider_usage"));
317
- },
318
- });
319
- this.registerCommand({
320
- name: "model",
321
- description: t("repl.model_list"),
322
- usage: t("repl.model_usage"),
323
- action: async (args) => {
324
- const subcmd = args[0];
325
- if (!subcmd || subcmd === "list") {
326
- console.log(`${t("repl.model_current")} ${this.config.model}`);
327
- // Fetch available models from provider
328
- const { OpenAICompatProvider } = await import("../llm/openai-compat");
329
- const provider = new OpenAICompatProvider({
330
- model: this.config.model,
331
- baseUrl: this.config.provider.baseUrl,
332
- apiKey: this.config.provider.apiKey,
333
- contextWindow: this.config.contextWindow,
334
- });
335
- const { Spinner } = await import("../ui/spinner");
336
- const s = new Spinner();
337
- s.start(t("cli.fetching_models"));
338
- try {
339
- const models = await provider.listModels();
340
- s.stop();
341
- if (models.length > 0) {
342
- console.log(t("cli.available_models"));
343
- for (const m of models) {
344
- const marker = m === this.config.model ? pc.green("* ") : " ";
345
- console.log(` ${marker}${m}`);
346
- }
347
- }
348
- else {
349
- console.log(t("cli.no_models_found"));
350
- }
351
- }
352
- catch (err) {
353
- s.stop();
354
- console.log(t("cli.model_fetch_failed", { error: String(err) }));
355
- }
356
- console.log(t("cli.model_hint"));
357
- return;
358
- }
359
- if (subcmd === "use") {
360
- const name = args[1];
361
- if (!name) {
362
- console.log(t("repl.model_usage"));
363
- return;
364
- }
365
- this.config.model = name;
366
- const configPath = join(homedir(), ".mma", "config.json");
367
- saveConfig(this.config, configPath);
368
- console.log(pc.green(t("repl.model_set", { name })));
369
- return;
370
- }
371
- console.log(t("repl.model_usage"));
372
- },
373
- });
374
- this.registerCommand({
375
- name: "context",
376
- description: t("cli.manage_context"),
377
- usage: "/context <size>",
378
- action: (args) => {
379
- if (args.length === 0) {
380
- console.log(`/context ${t("repl.context")} ${this.config.contextWindow}`);
381
- console.log(`Usage: /context <size> (min 1024)`);
382
- return;
383
- }
384
- const size = parseInt(args[0], 10);
385
- if (isNaN(size) || size < 1024) {
386
- console.log(t("cli.invalid_context_size"));
387
- return;
388
- }
389
- this.config.contextWindow = size;
390
- const configPath = join(homedir(), ".mma", "config.json");
391
- saveConfig(this.config, configPath);
392
- console.log(pc.green(t("cli.context_set", { size })));
393
- },
394
- });
395
- this.registerCommand({
396
- name: "reload",
397
- description: t("repl.reload"),
398
- usage: t("repl.reload_usage"),
399
- action: async () => {
400
- console.log(pc.yellow(t("repl.reloading")));
401
- // Save current session if auto-save enabled
402
- if (this.sessionManager && this.config.session.autoSave) {
403
- const active = this.sessionManager.getActiveMeta();
404
- if (active) {
405
- // Session is already auto-saved on each message
406
- }
407
- }
408
- // Shutdown current agent
409
- this.agent.shutdown();
410
- // Reload config from disk
411
- const { loadConfig } = await import("../config/config");
412
- const { homedir } = await import("os");
413
- const { join } = await import("path");
414
- const configDir = join(homedir(), ".mma");
415
- const projectConfigPath = join(process.cwd(), ".mmrc");
416
- const freshConfig = loadConfig({ configDir, projectConfigPath });
417
- // Update config reference
418
- Object.assign(this.config, freshConfig);
419
- // Recreate agent with new config (re-bootstrap)
420
- const { bootstrap } = await import("../core/bootstrap");
421
- const result = await bootstrap(configDir, process.cwd(), this.noAgentsMd, false);
422
- // Replace agent and related components
423
- this.agent = result.agent;
424
- this.sessionManager = result.sessionManager;
425
- this.skillsModule = result.skillsModule;
426
- this.pluginManager = result.pluginManager;
427
- // Update completer with new session/skill data
428
- this.setupCompleter();
429
- console.log(pc.green(t("repl.reloaded")));
430
- console.log(`${t("repl.model")} ${this.config.model}`);
431
- console.log(`${t("repl.context")} ${this.config.contextWindow}`);
432
- console.log(`${t("repl.provider")} ${this.config.provider.type} @ ${this.config.provider.baseUrl}`);
433
- },
434
- });
435
- }
436
- registerSessionCommands() {
437
- if (!this.sessionManager)
438
- return;
439
- this.registerCommand({
440
- name: "sessions",
441
- description: t("repl.sessions"),
442
- aliases: ["ls"],
443
- usage: t("repl.sessions_usage"),
444
- action: () => {
445
- const sessions = this.sessionManager.list();
446
- const active = this.sessionManager.getActive();
447
- if (sessions.length === 0) {
448
- console.log(t("session.no_sessions_hint"));
449
- return;
450
- }
451
- const rows = sessions.map((s) => [
452
- s.id === active ? pc.green("●") : "",
453
- s.id.slice(0, 12),
454
- s.name,
455
- s.updatedAt.slice(0, 19).replace("T", " "),
456
- String(s.messageCount),
457
- ]);
458
- for (const line of renderTable([
459
- t("session.col_active"),
460
- t("session.col_id"),
461
- t("session.col_name"),
462
- t("session.col_updated"),
463
- t("session.col_msgs"),
464
- ], rows)) {
465
- console.log(line);
466
- }
467
- console.log(pc.dim(`\n ${t("repl.resume_hint")}`));
468
- },
469
- });
470
- this.registerCommand({
471
- name: "new",
472
- description: t("repl.new"),
473
- aliases: ["create"],
474
- usage: t("repl.new_usage"),
475
- action: (args) => {
476
- const name = args.join(" ") || undefined;
477
- const meta = this.sessionManager.create(name);
478
- this.agent.clearContext();
479
- console.clear();
480
- console.log(`${t("session.created", { name: meta.name })} (${pc.dim(meta.id.slice(0, 12))})`);
481
- console.log(pc.dim(` ${t("session.chat_cleared")}\n`));
482
- },
483
- });
484
- this.registerCommand({
485
- name: "resume",
486
- description: t("repl.resume"),
487
- aliases: ["switch", "use"],
488
- usage: t("repl.resume_usage"),
489
- action: (args) => {
490
- const query = args.join(" ");
491
- const sessions = this.sessionManager.list();
492
- if (!query) {
493
- console.log(pc.dim(t("session.available")));
494
- const active = this.sessionManager.getActive();
495
- for (const s of sessions) {
496
- const marker = s.id === active ? pc.green(" *") : " ";
497
- console.log(pc.dim(` ${marker} ${s.id.slice(0, 12)} ${s.name}`));
498
- }
499
- console.log(pc.dim(`\n ${t("repl.resume_usage")}`));
500
- return;
501
- }
502
- const match = sessions.find((s) => s.id === query ||
503
- s.id.startsWith(query) ||
504
- s.name.toLowerCase().includes(query.toLowerCase()));
505
- if (!match) {
506
- console.log(t("session.no_match", { query }));
507
- return;
508
- }
509
- this.sessionManager.setActive(match.id);
510
- const history = this.sessionManager.loadHistory();
511
- this.agent.setContext(history);
512
- console.clear();
513
- console.log(pc.bold(pc.green(t("session.resumed", { name: match.name }))) +
514
- " " +
515
- pc.dim(`(${match.id.slice(0, 12)})`) +
516
- " — " +
517
- match.messageCount +
518
- " msgs");
519
- console.log(pc.dim("─".repeat(50)));
520
- if (history.length === 0) {
521
- console.log(pc.dim(t("session.no_history")));
522
- }
523
- else {
524
- console.log(pc.dim(t("session.chat_history")));
525
- console.log();
526
- for (const msg of history) {
527
- if (msg.role === "user") {
528
- console.log(pc.cyan(t("session.user_label") + ":"));
529
- console.log(getMessageText(msg.content));
530
- console.log();
531
- }
532
- else if (msg.role === "assistant") {
533
- console.log(pc.green(t("session.assistant_label") + ":"));
534
- console.log(getMessageText(msg.content));
535
- console.log();
536
- }
537
- }
538
- }
539
- console.log(pc.dim("─".repeat(50)));
540
- console.log(pc.dim(` ${t("session.chat_loaded")}`));
541
- },
542
- });
543
- this.registerCommand({
544
- name: "rename",
545
- description: t("repl.rename"),
546
- usage: t("repl.rename_usage"),
547
- action: (args) => {
548
- const name = args.join(" ");
549
- if (!name) {
550
- console.log(t("repl.rename_usage"));
551
- return;
552
- }
553
- const active = this.sessionManager.getActive();
554
- if (!active) {
555
- console.log(t("session.no_active"));
556
- return;
557
- }
558
- this.sessionManager.rename(active, name);
559
- console.log(t("session.renamed", { name }));
560
- },
561
- });
562
- this.registerCommand({
563
- name: "delete",
564
- description: t("repl.delete"),
565
- aliases: ["rm"],
566
- usage: t("repl.delete_usage"),
567
- action: (args) => {
568
- const query = args[0];
569
- if (!query) {
570
- console.log(t("repl.delete_usage"));
571
- return;
572
- }
573
- const sessions = this.sessionManager.list();
574
- const match = sessions.find((s) => s.id === query || s.id.startsWith(query));
575
- if (!match) {
576
- console.log(t("session.no_match", { query }));
577
- return;
578
- }
579
- this.sessionManager.delete(match.id);
580
- console.log(`${t("session.deleted", { id: match.id })}: ${match.name}`);
581
- },
582
- });
583
- }
584
- registerSkillCommands() {
585
- if (!this.skillsModule)
586
- return;
587
- this.registerCommand({
588
- name: "skill",
589
- description: t("repl.skill"),
590
- usage: t("repl.skill_usage"),
591
- action: (args) => {
592
- const subcmd = args[0];
593
- const arg = args.slice(1).join(" ");
594
- if (!subcmd || subcmd === "list") {
595
- const available = this.skillsModule.getAvailable();
596
- if (available.length === 0) {
597
- console.log(t("repl.no_skills"));
598
- return;
599
- }
600
- console.log(pc.bold(t("repl.available_skills")));
601
- for (const skill of available) {
602
- const tokens = Math.ceil(skill.content.length / 4);
603
- const loaded = this.skillsModule.getLoaded().some((s) => s.name === skill.name);
604
- const marker = loaded ? pc.green(" [loaded]") : "";
605
- console.log(` ${pc.cyan(skill.name)}${marker} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
606
- }
607
- return;
608
- }
609
- if (subcmd === "loaded") {
610
- const loaded = this.skillsModule.getLoaded();
611
- const budget = this.skillsModule.getBudget();
612
- if (loaded.length === 0) {
613
- console.log(t("repl.no_loaded"));
614
- return;
615
- }
616
- console.log(pc.bold(t("repl.loaded_skills")));
617
- for (const skill of loaded) {
618
- const tokens = Math.ceil(skill.content.length / 4);
619
- console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
620
- }
621
- console.log(pc.dim(`\n ${t("repl.budget", { used: budget.used, total: budget.total, remaining: budget.remaining })}`));
622
- return;
623
- }
624
- if (subcmd === "load") {
625
- if (!arg) {
626
- console.log(t("repl.skill_load_usage"));
627
- return;
628
- }
629
- const result = this.skillsModule.loadByName(arg);
630
- if (result.success) {
631
- console.log(pc.green(result.message));
632
- }
633
- else {
634
- console.log(pc.red(result.message));
635
- }
636
- return;
637
- }
638
- if (subcmd === "unload") {
639
- if (!arg) {
640
- console.log(t("repl.skill_unload_usage"));
641
- return;
642
- }
643
- if (this.skillsModule.unload(arg)) {
644
- console.log(pc.green(t("repl.skill_unloaded", { name: arg })));
645
- }
646
- else {
647
- console.log(pc.red(t("repl.skill_not_loaded", { name: arg })));
648
- }
649
- return;
650
- }
651
- if (subcmd === "search") {
652
- if (!arg) {
653
- console.log(t("repl.skill_search_usage"));
654
- return;
655
- }
656
- const results = this.skillsModule.search(arg);
657
- if (results.length === 0) {
658
- console.log(t("repl.no_skill_match", { query: arg }));
659
- return;
660
- }
661
- console.log(pc.bold(t("repl.skills_matching", { query: arg })));
662
- for (const skill of results) {
663
- const tokens = Math.ceil(skill.content.length / 4);
664
- console.log(` ${pc.cyan(skill.name)} ${pc.dim(`(${tokens} tokens)`)} ${skill.description.slice(0, 60)}`);
665
- }
666
- return;
667
- }
668
- console.log(pc.red(t("repl.skill_unknown_sub", { subcmd })));
669
- console.log(t("repl.skill_usage"));
670
- },
671
- });
672
- }
673
- setupCompleter() {
674
- const slashCommands = Array.from(this.commands.keys());
675
- this.completer.registerProvider(new SlashCommandProvider(slashCommands));
676
- if (this.sessionManager) {
677
- this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
678
- }
679
- if (this.skillsModule) {
680
- this.completer.registerProvider(new SubcommandProvider("skill", [
681
- "list",
682
- "loaded",
683
- "load",
684
- "unload",
685
- "search",
686
- ]));
687
- this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
688
- }
689
- }
690
- setupListeners() {
691
- let multiLineBuffer = "";
692
- let inMultiLine = false;
693
- this.rl.on("line", async (line) => {
694
- const trimmed = line.trim();
695
- if (this.agentRunning) {
696
- if (trimmed) {
697
- this.history.push(trimmed);
698
- if (this.history.length > this.maxHistory) {
699
- this.history = this.history.slice(-this.maxHistory);
700
- }
701
- }
702
- return;
703
- }
704
- if (trimmed) {
705
- this.history.push(trimmed);
706
- if (this.history.length > this.maxHistory) {
707
- this.history = this.history.slice(-this.maxHistory);
708
- }
709
- }
710
- if (inMultiLine) {
711
- multiLineBuffer += "\n" + line;
712
- if (!this.isMultiLineInput(multiLineBuffer)) {
713
- inMultiLine = false;
714
- const fullInput = multiLineBuffer.trim();
715
- multiLineBuffer = "";
716
- if (fullInput) {
717
- if (fullInput.startsWith("/")) {
718
- await this.executeCommand(fullInput);
719
- }
720
- else {
721
- await this.runAgent(fullInput);
722
- }
723
- }
724
- if (this.running) {
725
- this.rl.setPrompt(pc.cyan("> "));
726
- this.rl.prompt();
727
- }
728
- }
729
- else {
730
- this.rl.setPrompt(pc.cyan("... "));
731
- this.rl.prompt();
732
- }
733
- return;
734
- }
735
- if (this.isMultiLineInput(trimmed)) {
736
- inMultiLine = true;
737
- multiLineBuffer = trimmed;
738
- this.rl.setPrompt(pc.cyan("... "));
739
- this.rl.prompt();
740
- return;
741
- }
742
- if (!trimmed) {
743
- this.rl.prompt();
744
- return;
745
- }
746
- if (trimmed.startsWith("/")) {
747
- await this.executeCommand(trimmed);
748
- }
749
- else {
750
- await this.runAgent(trimmed);
751
- }
752
- if (this.running) {
753
- this.rl.prompt();
754
- }
755
- });
756
- this.rl.on("close", () => {
757
- this.running = false;
758
- });
759
- if (process.stdin.isTTY) {
760
- readline.emitKeypressEvents(process.stdin);
761
- process.stdin.on("keypress", async (str, key) => {
762
- if (key.name === "escape") {
763
- const now = Date.now();
764
- if (now - this.lastEscTime < this.doubleEscDelay) {
765
- console.log(pc.yellow("\n\n[Ctrl+C] Остановка агента..."));
766
- this.agent.shutdown();
767
- this.running = false;
768
- this.saveHistory();
769
- this.rl.close();
770
- process.exit(0);
771
- }
772
- this.lastEscTime = now;
773
- }
774
- // Ctrl+V: try to paste image from clipboard
775
- if (key.ctrl && key.name === "v" && !this.agentRunning) {
776
- try {
777
- const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
778
- const clipBuf = await readClipboardImage();
779
- if (clipBuf) {
780
- const { dataUrl } = await bufferToDataUrl(clipBuf);
781
- this.pendingClipboardImage = dataUrl;
782
- const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
783
- console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
784
- this.rl.prompt();
785
- }
786
- }
787
- catch {
788
- // clipboard read failed — ignore, let terminal paste text normally
789
- }
790
- }
791
- });
792
- }
793
- // SIGINT: first Ctrl+C sends graceful shutdown, second forces exit
794
- process.on("SIGINT", () => {
795
- if (this.agentRunning) {
796
- console.log(pc.yellow("\n[Ctrl+C] Остановка агента... (ещё раз — принудительно)"));
797
- this.agent.shutdown();
798
- this.agentRunning = false;
799
- // Force exit after 2s if graceful shutdown hangs
800
- setTimeout(() => process.exit(1), 2000).unref();
801
- }
802
- else {
803
- process.exit(0);
804
- }
805
- });
806
- }
807
- isMultiLineInput(line) {
808
- if (line.endsWith("\\"))
809
- return true;
810
- const openBraces = (line.match(/\{/g) || []).length;
811
- const closeBraces = (line.match(/\}/g) || []).length;
812
- if (openBraces > closeBraces)
813
- return true;
814
- return false;
815
- }
816
- async runAgent(input) {
817
- if (this.agentRunning)
818
- return;
819
- this.agentRunning = true;
820
- this.rl.pause();
821
- try {
822
- // Attach pending clipboard image if Ctrl+V was pressed
823
- if (this.pendingClipboardImage) {
824
- const contextManager = this.agent.contextManager;
825
- if (contextManager) {
826
- contextManager.addPendingImage({
827
- type: "image_url",
828
- image_url: { url: this.pendingClipboardImage },
829
- });
830
- }
831
- this.pendingClipboardImage = null;
832
- }
833
- process.stdout.write("\n" + pc.green(t("repl.agent")));
834
- const renderer = new Renderer({
835
- spinner: this.config.ui?.spinner ?? true,
836
- });
837
- const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
838
- if (ev.type === "start") {
839
- renderer.toolStart(ev.tool, ev.args);
840
- }
841
- else {
842
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
843
- }
844
- }, (phase) => {
845
- if (phase === "thinking") {
846
- renderer.thinkingStart();
847
- }
848
- else {
849
- renderer.thinkingEnd();
850
- }
851
- });
852
- renderer.flush();
853
- process.stdout.write("\n");
854
- if (!result.success) {
855
- console.error(pc.red(`${t("error.prefix")}${result.error}`));
856
- }
857
- this.showContextBar(result);
858
- }
859
- finally {
860
- this.agentRunning = false;
861
- if (this.running) {
862
- this.rl.resume();
863
- }
864
- }
865
- }
866
- registerCommand(cmd) {
867
- this.commands.set(cmd.name, cmd);
868
- if (cmd.aliases) {
869
- for (const alias of cmd.aliases) {
870
- this.commands.set(alias, cmd);
871
- }
872
- }
873
- }
874
- async executeCommand(input) {
875
- const parts = input.split(/\s+/);
876
- const name = parts[0].slice(1);
877
- const args = parts.slice(1);
878
- const cmd = this.commands.get(name);
879
- if (!cmd) {
880
- console.log(pc.red(t("cli.unknown_cmd", { name })), t("cli.help_hint"));
881
- return;
882
- }
883
- try {
884
- await cmd.action(args);
885
- }
886
- catch (err) {
887
- console.error(pc.red(t("error.command_error", {
888
- message: err instanceof Error ? err.message : String(err),
889
- })));
890
- }
891
- }
892
- showHelp() {
893
- const order = ["general", "agent", "session", "skill"];
894
- const seen = new Set();
895
- for (const groupKey of order) {
896
- const cmds = [];
897
- for (const [name, cmd] of this.commands) {
898
- if (cmd.aliases?.includes(name))
899
- continue;
900
- if (seen.has(cmd.name))
901
- continue;
902
- const group = COMMAND_GROUPS[cmd.name] ?? "general";
903
- if (group !== groupKey)
904
- continue;
905
- seen.add(cmd.name);
906
- cmds.push(cmd);
907
- }
908
- if (cmds.length === 0)
909
- continue;
910
- console.log(pc.bold(t(`repl.group.${groupKey}`)));
911
- for (const cmd of cmds) {
912
- const aliases = cmd.aliases?.length
913
- ? ` (${pc.dim(cmd.aliases.join(", "))})`
914
- : "";
915
- console.log(` ${pc.cyan("/" + cmd.name)}${aliases} ${pc.dim(cmd.description)}`);
916
- if (cmd.usage) {
917
- console.log(` ${pc.dim(cmd.usage)}`);
918
- }
919
- }
920
- console.log();
921
- }
922
- }
923
- showContextBar(result) {
924
- if (result.contextUsed !== undefined &&
925
- result.contextLimit !== undefined &&
926
- result.contextLimit > 0) {
927
- console.log();
928
- const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
929
- console.log(ctxLine);
930
- if (result.totalTokens !== undefined && result.totalTokens > 0) {
931
- const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
932
- console.log(apiLine);
933
- }
934
- }
935
- }
936
- start() {
937
- this.running = true;
938
- const info = [];
939
- const row = (label, value) => {
940
- info.push(` ${pc.yellow(label)} ${value}`);
941
- };
942
- const ctx = this.config.contextWindow;
943
- const sysBudget = Math.floor(ctx * this.config.contextBudget.systemPrompt);
944
- const resBudget = Math.floor(ctx * this.config.contextBudget.responseReserve);
945
- const histBudget = ctx - sysBudget - resBudget;
946
- row(t("repl.model"), pc.white(this.config.model));
947
- row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
948
- row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
949
- if (this.skillsModule) {
950
- const budget = this.skillsModule.getBudget();
951
- row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
952
- }
953
- if (this.pluginManager) {
954
- const plugins = this.pluginManager
955
- .getAllPlugins()
956
- .filter((p) => !p.isBuiltin);
957
- if (plugins.length > 0) {
958
- const pluginNames = plugins.map((p) => p.name).join(", ");
959
- row(t("repl.plugins_label"), `${pc.white(String(plugins.length))} active ${pc.dim(`(${pluginNames})`)}`);
960
- }
961
- }
962
- const mcpServers = this.config.mcpServers || {};
963
- const enabledServers = Object.entries(mcpServers).filter(([, s]) => s.enabled !== false);
964
- if (enabledServers.length > 0) {
965
- const names = enabledServers.map(([name]) => name).join(", ");
966
- row(t("repl.mcp_label"), `${pc.white(String(enabledServers.length))} ${pc.dim(`(${names})`)}`);
967
- }
968
- const cwd = process.cwd();
969
- row(t("repl.work_dir"), pc.dim(cwd));
970
- if (this.noAgentsMd) {
971
- row(t("repl.agents_label"), pc.red(t("repl.disabled")));
972
- }
973
- else {
974
- const agentsMdCandidates = [
975
- join(this.baseDir, "AGENTS.md"),
976
- join(this.baseDir, ".mma", "AGENTS.md"),
977
- join(this.configDir, "AGENTS.md"),
978
- ];
979
- const foundAgents = agentsMdCandidates.filter((p) => existsSync(p));
980
- if (foundAgents.length > 0) {
981
- for (const p of foundAgents) {
982
- row(t("repl.agents_label"), pc.dim(p));
983
- }
984
- }
985
- else {
986
- row(t("repl.agents_label"), pc.dim(t("repl.not_found")));
987
- }
988
- }
989
- const meta = this.sessionManager?.getActiveMeta();
990
- if (meta) {
991
- const sessionPath = join(this.configDir, "sessions", meta.id);
992
- row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
993
- }
994
- const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
995
- for (const line of box(info, {
996
- title: t("repl.title", { version }),
997
- width: headerWidth,
998
- })) {
999
- console.log(line);
1000
- }
1001
- console.log();
1002
- this.rl.prompt();
1003
- }
1004
- stop() {
1005
- this.running = false;
1006
- this.saveHistory();
1007
- this.agent.shutdown();
1008
- this.rl.close();
1009
- }
1010
- }