micro-models-agent 0.28.0 → 0.28.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.
Files changed (167) hide show
  1. package/dist/main.js +314 -208
  2. package/package.json +1 -1
  3. package/dist/cli/commands.js +0 -220
  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 -987
  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 -461
  18. package/dist/core/bootstrap.js +0 -321
  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/history.js +0 -15
  49. package/dist/modules/context/index.js +0 -1
  50. package/dist/modules/context/manager.js +0 -240
  51. package/dist/modules/execution/auditor.js +0 -72
  52. package/dist/modules/execution/index.js +0 -6
  53. package/dist/modules/execution/module.js +0 -337
  54. package/dist/modules/execution/moe-executor.js +0 -209
  55. package/dist/modules/execution/plan-validator.js +0 -153
  56. package/dist/modules/execution/planner.js +0 -35
  57. package/dist/modules/execution/stuck-detector.js +0 -134
  58. package/dist/modules/execution/tracker.js +0 -53
  59. package/dist/modules/execution/types.js +0 -1
  60. package/dist/modules/execution/verifier.js +0 -149
  61. package/dist/modules/hallucination/confidence.js +0 -54
  62. package/dist/modules/hallucination/consistency.js +0 -60
  63. package/dist/modules/hallucination/detector.js +0 -41
  64. package/dist/modules/hallucination/factual.js +0 -170
  65. package/dist/modules/hallucination/index.js +0 -4
  66. package/dist/modules/index.js +0 -5
  67. package/dist/modules/indexer/cache.js +0 -38
  68. package/dist/modules/indexer/index.js +0 -3
  69. package/dist/modules/indexer/module.js +0 -192
  70. package/dist/modules/indexer/walker.js +0 -101
  71. package/dist/modules/mcp/client.js +0 -393
  72. package/dist/modules/mcp/index.js +0 -3
  73. package/dist/modules/mcp/module.js +0 -146
  74. package/dist/modules/mcp/registry.js +0 -15
  75. package/dist/modules/memory/index.js +0 -1
  76. package/dist/modules/memory/module.js +0 -48
  77. package/dist/modules/memory/search.js +0 -40
  78. package/dist/modules/memory/store.js +0 -65
  79. package/dist/modules/pipelines/engine.js +0 -60
  80. package/dist/modules/pipelines/index.js +0 -3
  81. package/dist/modules/pipelines/parser.js +0 -53
  82. package/dist/modules/pipelines/template.js +0 -14
  83. package/dist/modules/plugins/builtin/lint-on-write.js +0 -121
  84. package/dist/modules/plugins/builtin/notify.js +0 -8
  85. package/dist/modules/plugins/index.js +0 -1
  86. package/dist/modules/plugins/loader.js +0 -28
  87. package/dist/modules/plugins/manager.js +0 -161
  88. package/dist/modules/plugins/types.js +0 -1
  89. package/dist/modules/processes/detect.js +0 -34
  90. package/dist/modules/processes/index.js +0 -3
  91. package/dist/modules/processes/registry.js +0 -148
  92. package/dist/modules/processes/runner.js +0 -124
  93. package/dist/modules/registry.js +0 -45
  94. package/dist/modules/security/audit-log.js +0 -116
  95. package/dist/modules/security/audit-notifier.js +0 -292
  96. package/dist/modules/security/command-validator.js +0 -185
  97. package/dist/modules/security/content-scanner.js +0 -52
  98. package/dist/modules/security/data-sanitizer.js +0 -97
  99. package/dist/modules/security/encryption.js +0 -240
  100. package/dist/modules/security/index.js +0 -14
  101. package/dist/modules/security/network-validator.js +0 -79
  102. package/dist/modules/security/path-validator.js +0 -155
  103. package/dist/modules/security/rate-limiter.js +0 -119
  104. package/dist/modules/security/security-policies.js +0 -393
  105. package/dist/modules/security/session-encryption.js +0 -193
  106. package/dist/modules/security/session-isolation.js +0 -95
  107. package/dist/modules/session/index.js +0 -3
  108. package/dist/modules/session/manager.js +0 -167
  109. package/dist/modules/session/module.js +0 -24
  110. package/dist/modules/session/store.js +0 -174
  111. package/dist/modules/session/types.js +0 -1
  112. package/dist/modules/skills/index.js +0 -3
  113. package/dist/modules/skills/loader.js +0 -72
  114. package/dist/modules/skills/matcher.js +0 -27
  115. package/dist/modules/skills/module.js +0 -143
  116. package/dist/modules/types.js +0 -1
  117. package/dist/modules/updater/checker.js +0 -32
  118. package/dist/modules/updater/index.js +0 -1
  119. package/dist/modules/user-profile/compressor.js +0 -16
  120. package/dist/modules/user-profile/index.js +0 -1
  121. package/dist/modules/user-profile/profile.js +0 -68
  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 -56
  127. package/dist/tools/delete-file.js +0 -63
  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 -73
  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 -80
  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,987 +0,0 @@
1
- import * as readline from "readline";
2
- import { pc } from "../ui/colors";
3
- import { existsSync, readFileSync, writeFileSync } from "fs";
4
- import { join } from "path";
5
- import { homedir } from "os";
6
- import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, } from "./completer";
7
- import { Renderer } from "../ui/renderer";
8
- import { box } from "../ui/box";
9
- import { renderTable } from "../ui/table";
10
- import { t } from "../i18n/index";
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) {
36
- const pct = Math.min(100, Math.round((used / limit) * 100));
37
- const barLen = 20;
38
- const filled = Math.round((pct / 100) * barLen);
39
- const bar = pc.green("█".repeat(filled)) + pc.dim("░".repeat(barLen - filled));
40
- const pctStr = pct >= 75 ? pc.yellow(`${pct}%`) : pc.dim(`${pct}%`);
41
- return ` ${bar} ${pctStr} ${pc.dim(`(${used} / ${limit} tokens)`)}`;
42
- }
43
- export class Repl {
44
- rl;
45
- commands = new Map();
46
- completer = new Completer();
47
- running = false;
48
- agentRunning = false;
49
- agent;
50
- config;
51
- sessionManager;
52
- skillsModule;
53
- pluginManager;
54
- historyPath;
55
- history = [];
56
- maxHistory = 1000;
57
- lastEscTime = 0;
58
- doubleEscDelay = 500;
59
- configDir;
60
- baseDir;
61
- noAgentsMd;
62
- pendingClipboardImage = null;
63
- constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd) {
64
- this.agent = agent;
65
- this.config = config;
66
- this.sessionManager = sessionManager;
67
- this.skillsModule = skillsModule;
68
- this.pluginManager = pluginManager;
69
- this.configDir = configDir || join(homedir(), ".mma");
70
- this.baseDir = baseDir || process.cwd();
71
- this.noAgentsMd = noAgentsMd === true;
72
- this.historyPath = join(homedir(), ".mma", "repl-history");
73
- this.loadHistory();
74
- this.registerBuiltinCommands();
75
- this.registerMmaCommands();
76
- this.registerSessionCommands();
77
- this.registerSkillCommands();
78
- this.setupCompleter();
79
- this.rl = readline.createInterface({
80
- input: process.stdin,
81
- output: process.stdout,
82
- prompt: pc.cyan("> "),
83
- history: this.history,
84
- historySize: this.maxHistory,
85
- tabSize: 2,
86
- completer: (line) => {
87
- const [matches, partial] = this.completer.complete(line);
88
- if (matches.length > 0)
89
- return [matches, partial];
90
- return [[], line];
91
- },
92
- });
93
- this.setupListeners();
94
- }
95
- loadHistory() {
96
- if (existsSync(this.historyPath)) {
97
- try {
98
- const raw = readFileSync(this.historyPath, "utf-8");
99
- this.history = raw.split("\n").filter(Boolean).slice(-this.maxHistory);
100
- }
101
- catch {
102
- this.history = [];
103
- }
104
- }
105
- }
106
- saveHistory() {
107
- const allHistory = this.history.slice(-this.maxHistory);
108
- writeFileSync(this.historyPath, allHistory.join("\n"), "utf-8");
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
- }
655
- setupCompleter() {
656
- const slashCommands = Array.from(this.commands.keys());
657
- this.completer.registerProvider(new SlashCommandProvider(slashCommands));
658
- if (this.sessionManager) {
659
- this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
660
- }
661
- if (this.skillsModule) {
662
- this.completer.registerProvider(new SubcommandProvider("skill", [
663
- "list",
664
- "loaded",
665
- "load",
666
- "unload",
667
- "search",
668
- ]));
669
- this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
670
- }
671
- }
672
- setupListeners() {
673
- let multiLineBuffer = "";
674
- let inMultiLine = false;
675
- this.rl.on("line", async (line) => {
676
- const trimmed = line.trim();
677
- if (this.agentRunning) {
678
- if (trimmed) {
679
- this.history.push(trimmed);
680
- if (this.history.length > this.maxHistory) {
681
- this.history = this.history.slice(-this.maxHistory);
682
- }
683
- }
684
- return;
685
- }
686
- if (trimmed) {
687
- this.history.push(trimmed);
688
- if (this.history.length > this.maxHistory) {
689
- this.history = this.history.slice(-this.maxHistory);
690
- }
691
- }
692
- if (inMultiLine) {
693
- multiLineBuffer += "\n" + line;
694
- if (!this.isMultiLineInput(multiLineBuffer)) {
695
- inMultiLine = false;
696
- const fullInput = multiLineBuffer.trim();
697
- multiLineBuffer = "";
698
- if (fullInput) {
699
- if (fullInput.startsWith("/")) {
700
- await this.executeCommand(fullInput);
701
- }
702
- else {
703
- await this.runAgent(fullInput);
704
- }
705
- }
706
- if (this.running) {
707
- this.rl.setPrompt(pc.cyan("> "));
708
- this.rl.prompt();
709
- }
710
- }
711
- else {
712
- this.rl.setPrompt(pc.cyan("... "));
713
- this.rl.prompt();
714
- }
715
- return;
716
- }
717
- if (this.isMultiLineInput(trimmed)) {
718
- inMultiLine = true;
719
- multiLineBuffer = trimmed;
720
- this.rl.setPrompt(pc.cyan("... "));
721
- this.rl.prompt();
722
- return;
723
- }
724
- if (!trimmed) {
725
- this.rl.prompt();
726
- return;
727
- }
728
- if (trimmed.startsWith("/")) {
729
- await this.executeCommand(trimmed);
730
- }
731
- else {
732
- await this.runAgent(trimmed);
733
- }
734
- if (this.running) {
735
- this.rl.prompt();
736
- }
737
- });
738
- this.rl.on("close", () => {
739
- this.running = false;
740
- });
741
- if (process.stdin.isTTY) {
742
- readline.emitKeypressEvents(process.stdin);
743
- process.stdin.on("keypress", async (str, key) => {
744
- if (key.name === "escape") {
745
- const now = Date.now();
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);
753
- }
754
- this.lastEscTime = now;
755
- }
756
- // Ctrl+V: try to paste image from clipboard
757
- if (key.ctrl && key.name === "v" && !this.agentRunning) {
758
- try {
759
- const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
760
- const clipBuf = await readClipboardImage();
761
- if (clipBuf) {
762
- const { dataUrl } = await bufferToDataUrl(clipBuf);
763
- this.pendingClipboardImage = dataUrl;
764
- const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
765
- console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
766
- this.rl.prompt();
767
- }
768
- }
769
- catch {
770
- // clipboard read failed — ignore, let terminal paste text normally
771
- }
772
- }
773
- });
774
- }
775
- // SIGINT: first Ctrl+C sends graceful shutdown, second forces exit
776
- process.on("SIGINT", () => {
777
- if (this.agentRunning) {
778
- console.log(pc.yellow("\n[Ctrl+C] Остановка агента... (ещё раз — принудительно)"));
779
- this.agent.shutdown();
780
- this.agentRunning = false;
781
- // Force exit after 2s if graceful shutdown hangs
782
- setTimeout(() => process.exit(1), 2000).unref();
783
- }
784
- else {
785
- process.exit(0);
786
- }
787
- });
788
- }
789
- isMultiLineInput(line) {
790
- if (line.endsWith("\\"))
791
- return true;
792
- const openBraces = (line.match(/\{/g) || []).length;
793
- const closeBraces = (line.match(/\}/g) || []).length;
794
- if (openBraces > closeBraces)
795
- return true;
796
- return false;
797
- }
798
- async runAgent(input) {
799
- if (this.agentRunning)
800
- return;
801
- this.agentRunning = true;
802
- this.rl.pause();
803
- try {
804
- // Attach pending clipboard image if Ctrl+V was pressed
805
- if (this.pendingClipboardImage) {
806
- const contextManager = this.agent.contextManager;
807
- if (contextManager) {
808
- contextManager.addPendingImage({
809
- type: "image_url",
810
- image_url: { url: this.pendingClipboardImage },
811
- });
812
- }
813
- this.pendingClipboardImage = null;
814
- }
815
- process.stdout.write("\n" + pc.green(t("repl.agent")));
816
- const renderer = new Renderer({ spinner: this.config.ui?.spinner ?? true });
817
- const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
818
- if (ev.type === "start") {
819
- renderer.toolStart(ev.tool, ev.args);
820
- }
821
- else {
822
- renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error);
823
- }
824
- }, (phase) => {
825
- if (phase === "thinking") {
826
- renderer.thinkingStart();
827
- }
828
- else {
829
- renderer.thinkingEnd();
830
- }
831
- });
832
- renderer.flush();
833
- process.stdout.write("\n");
834
- if (!result.success) {
835
- console.error(pc.red(`${t("error.prefix")}${result.error}`));
836
- }
837
- this.showContextBar(result);
838
- }
839
- finally {
840
- this.agentRunning = false;
841
- if (this.running) {
842
- this.rl.resume();
843
- }
844
- }
845
- }
846
- registerCommand(cmd) {
847
- this.commands.set(cmd.name, cmd);
848
- if (cmd.aliases) {
849
- for (const alias of cmd.aliases) {
850
- this.commands.set(alias, cmd);
851
- }
852
- }
853
- }
854
- async executeCommand(input) {
855
- const parts = input.split(/\s+/);
856
- const name = parts[0].slice(1);
857
- const args = parts.slice(1);
858
- const cmd = this.commands.get(name);
859
- if (!cmd) {
860
- console.log(pc.red(t("cli.unknown_cmd", { name })), t("cli.help_hint"));
861
- return;
862
- }
863
- try {
864
- await cmd.action(args);
865
- }
866
- catch (err) {
867
- console.error(pc.red(t("error.command_error", {
868
- message: err instanceof Error ? err.message : String(err),
869
- })));
870
- }
871
- }
872
- showHelp() {
873
- const order = ["general", "agent", "session", "skill"];
874
- const seen = new Set();
875
- for (const groupKey of order) {
876
- const cmds = [];
877
- for (const [name, cmd] of this.commands) {
878
- if (cmd.aliases?.includes(name))
879
- continue;
880
- if (seen.has(cmd.name))
881
- continue;
882
- const group = COMMAND_GROUPS[cmd.name] ?? "general";
883
- if (group !== groupKey)
884
- continue;
885
- seen.add(cmd.name);
886
- cmds.push(cmd);
887
- }
888
- if (cmds.length === 0)
889
- continue;
890
- console.log(pc.bold(t(`repl.group.${groupKey}`)));
891
- for (const cmd of cmds) {
892
- const aliases = cmd.aliases?.length
893
- ? ` (${pc.dim(cmd.aliases.join(", "))})`
894
- : "";
895
- console.log(` ${pc.cyan("/" + cmd.name)}${aliases} ${pc.dim(cmd.description)}`);
896
- if (cmd.usage) {
897
- console.log(` ${pc.dim(cmd.usage)}`);
898
- }
899
- }
900
- console.log();
901
- }
902
- }
903
- showContextBar(result) {
904
- if (result.contextUsed !== undefined &&
905
- result.contextLimit !== undefined &&
906
- result.contextLimit > 0) {
907
- console.log();
908
- const ctxLine = formatContextBar(result.contextUsed, result.contextLimit);
909
- console.log(ctxLine);
910
- if (result.totalTokens !== undefined && result.totalTokens > 0) {
911
- const apiLine = pc.dim(` API: ${result.promptTokens} prompt + ${result.completionTokens} completion = ${result.totalTokens} total`);
912
- console.log(apiLine);
913
- }
914
- }
915
- }
916
- start() {
917
- this.running = true;
918
- const info = [];
919
- const row = (label, value) => {
920
- info.push(` ${pc.yellow(label)} ${value}`);
921
- };
922
- const ctx = this.config.contextWindow;
923
- const sysBudget = Math.floor(ctx * this.config.contextBudget.systemPrompt);
924
- const resBudget = Math.floor(ctx * this.config.contextBudget.responseReserve);
925
- const histBudget = ctx - sysBudget - resBudget;
926
- row(t("repl.model"), pc.white(this.config.model));
927
- row(t("repl.provider"), `${this.config.provider.type} → ${pc.dim(this.config.provider.baseUrl)}`);
928
- row(t("repl.context"), `${pc.white(String(ctx))} ${pc.dim(`(sys:${sysBudget} res:${resBudget} hist:${histBudget})`)}`);
929
- if (this.skillsModule) {
930
- const budget = this.skillsModule.getBudget();
931
- row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
932
- }
933
- if (this.pluginManager) {
934
- const plugins = this.pluginManager
935
- .getAllPlugins()
936
- .filter((p) => !p.isBuiltin);
937
- if (plugins.length > 0) {
938
- const pluginNames = plugins.map((p) => p.name).join(", ");
939
- row(t("repl.plugins_label"), `${pc.white(String(plugins.length))} active ${pc.dim(`(${pluginNames})`)}`);
940
- }
941
- }
942
- const mcpServers = this.config.mcpServers || {};
943
- const enabledServers = Object.entries(mcpServers).filter(([, s]) => s.enabled !== false);
944
- if (enabledServers.length > 0) {
945
- const names = enabledServers.map(([name]) => name).join(", ");
946
- row(t("repl.mcp_label"), `${pc.white(String(enabledServers.length))} ${pc.dim(`(${names})`)}`);
947
- }
948
- const cwd = process.cwd();
949
- row(t("repl.work_dir"), pc.dim(cwd));
950
- if (this.noAgentsMd) {
951
- row(t("repl.agents_label"), pc.red(t("repl.disabled")));
952
- }
953
- else {
954
- const agentsMdCandidates = [
955
- join(this.baseDir, "AGENTS.md"),
956
- join(this.baseDir, ".mma", "AGENTS.md"),
957
- join(this.configDir, "AGENTS.md"),
958
- ];
959
- const foundAgents = agentsMdCandidates.filter((p) => existsSync(p));
960
- if (foundAgents.length > 0) {
961
- for (const p of foundAgents) {
962
- row(t("repl.agents_label"), pc.dim(p));
963
- }
964
- }
965
- else {
966
- row(t("repl.agents_label"), pc.dim(t("repl.not_found")));
967
- }
968
- }
969
- const meta = this.sessionManager?.getActiveMeta();
970
- if (meta) {
971
- const sessionPath = join(this.configDir, "sessions", meta.id);
972
- row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
973
- }
974
- const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
975
- for (const line of box(info, { title: t("repl.title"), width: headerWidth })) {
976
- console.log(line);
977
- }
978
- console.log();
979
- this.rl.prompt();
980
- }
981
- stop() {
982
- this.running = false;
983
- this.saveHistory();
984
- this.agent.shutdown();
985
- this.rl.close();
986
- }
987
- }