micro-models-agent 0.41.2 → 0.42.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 (171) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +19 -9
  3. package/dist/cli/completer.js +37 -36
  4. package/dist/cli/index.js +2 -2
  5. package/dist/cli/main.js +23 -48
  6. package/dist/cli/repl-commands.js +12 -40
  7. package/dist/cli/repl.js +87 -217
  8. package/dist/cli/security-commands.js +7 -5
  9. package/dist/cli/setup.js +26 -8
  10. package/dist/config/config.js +5 -52
  11. package/dist/config/defaults.js +5 -29
  12. package/dist/config/experts.js +1 -1
  13. package/dist/config/index.js +3 -3
  14. package/dist/config/security.js +10 -3
  15. package/dist/core/agent-moe.js +10 -2
  16. package/dist/core/agent.js +82 -273
  17. package/dist/core/bootstrap.js +13 -80
  18. package/dist/core/index.js +2 -2
  19. package/dist/core/prompt-builder.js +2 -23
  20. package/dist/core/session-logger.js +4 -46
  21. package/dist/i18n/en.json +2 -75
  22. package/dist/i18n/ru.json +1 -74
  23. package/dist/index.js +1 -1
  24. package/dist/llm/image-utils.js +5 -4
  25. package/dist/llm/index.js +4 -4
  26. package/dist/llm/model-loader.js +6 -6
  27. package/dist/llm/openai-compat.js +34 -40
  28. package/dist/llm/orchestrator.js +29 -33
  29. package/dist/llm/response.js +9 -9
  30. package/dist/logger/app-logger.js +1 -1
  31. package/dist/logger/index.js +1 -1
  32. package/dist/main.js +299 -55
  33. package/dist/migration/backup.js +13 -13
  34. package/dist/migration/detect.js +11 -11
  35. package/dist/migration/index.js +2 -2
  36. package/dist/modules/browser/actions.js +4 -34
  37. package/dist/modules/browser/bridge-server.mjs +202 -202
  38. package/dist/modules/browser/cookie-store.js +6 -6
  39. package/dist/modules/browser/index.js +5 -7
  40. package/dist/modules/browser/module.js +7 -8
  41. package/dist/modules/browser/session.js +84 -87
  42. package/dist/modules/browser/snapshot.js +58 -92
  43. package/dist/modules/browser/types.js +1 -4
  44. package/dist/modules/certification/cli.js +4 -2
  45. package/dist/modules/certification/fact-checker.js +3 -1
  46. package/dist/modules/certification/loader.js +9 -3
  47. package/dist/modules/certification/runner.js +4 -1
  48. package/dist/modules/context/index.js +1 -1
  49. package/dist/modules/context/manager.js +86 -160
  50. package/dist/modules/execution/auditor.js +25 -177
  51. package/dist/modules/execution/module.js +544 -201
  52. package/dist/modules/execution/moe-executor.js +0 -25
  53. package/dist/modules/execution/plan-store.js +3 -1
  54. package/dist/modules/execution/plan-validator.js +10 -10
  55. package/dist/modules/execution/planner.js +1 -6
  56. package/dist/modules/execution/stuck-detector.js +10 -173
  57. package/dist/modules/execution/verifier.js +42 -86
  58. package/dist/modules/hallucination/confidence.js +1 -8
  59. package/dist/modules/hallucination/detector.js +5 -2
  60. package/dist/modules/hallucination/factual.js +64 -3
  61. package/dist/modules/hallucination/index.js +1 -1
  62. package/dist/modules/hallucination/js-identifiers.js +0 -190
  63. package/dist/modules/hallucination/llm-judge.js +3 -1
  64. package/dist/modules/indexer/cache.js +7 -9
  65. package/dist/modules/indexer/index.js +3 -3
  66. package/dist/modules/indexer/module.js +42 -95
  67. package/dist/modules/indexer/walker.js +17 -17
  68. package/dist/modules/lsp/client.js +31 -74
  69. package/dist/modules/lsp/config.js +33 -87
  70. package/dist/modules/lsp/index.js +3 -3
  71. package/dist/modules/lsp/module.js +21 -185
  72. package/dist/modules/mcp/module.js +6 -2
  73. package/dist/modules/memory/index.js +1 -1
  74. package/dist/modules/memory/module.js +23 -71
  75. package/dist/modules/memory/search.js +9 -11
  76. package/dist/modules/memory/store.js +13 -13
  77. package/dist/modules/pipelines/engine.js +10 -10
  78. package/dist/modules/pipelines/index.js +3 -3
  79. package/dist/modules/pipelines/parser.js +14 -17
  80. package/dist/modules/pipelines/template.js +1 -1
  81. package/dist/modules/plugins/builtin/lint-on-write.js +16 -21
  82. package/dist/modules/plugins/builtin/notify.js +2 -3
  83. package/dist/modules/plugins/index.js +1 -1
  84. package/dist/modules/plugins/loader.js +17 -59
  85. package/dist/modules/plugins/manager.js +17 -73
  86. package/dist/modules/processes/index.js +1 -1
  87. package/dist/modules/processes/registry.js +46 -135
  88. package/dist/modules/registry.js +2 -4
  89. package/dist/modules/security/audit-notifier.js +39 -39
  90. package/dist/modules/security/command-validator.js +8 -2
  91. package/dist/modules/security/data-sanitizer.js +9 -1
  92. package/dist/modules/security/encryption.js +56 -58
  93. package/dist/modules/security/network-validator.js +9 -1
  94. package/dist/modules/security/path-validator.js +3 -1
  95. package/dist/modules/security/security-policies.js +19 -3
  96. package/dist/modules/security/session-encryption.js +1 -1
  97. package/dist/modules/security/session-isolation.js +8 -8
  98. package/dist/modules/session/index.js +3 -3
  99. package/dist/modules/session/module.js +5 -5
  100. package/dist/modules/session/store.js +9 -3
  101. package/dist/modules/skills/module.js +2 -1
  102. package/dist/modules/updater/checker.js +6 -70
  103. package/dist/modules/updater/index.js +1 -2
  104. package/dist/modules/user-profile/compressor.js +2 -2
  105. package/dist/modules/user-profile/index.js +1 -1
  106. package/dist/modules/user-profile/profile.js +9 -9
  107. package/dist/tools/attach-image.js +1 -1
  108. package/dist/tools/bash.js +19 -178
  109. package/dist/tools/browser.js +29 -46
  110. package/dist/tools/executor.js +5 -4
  111. package/dist/tools/file-info.js +12 -13
  112. package/dist/tools/filter-tools.js +2 -9
  113. package/dist/tools/glob-tool.js +11 -11
  114. package/dist/tools/grep-tool.js +3 -1
  115. package/dist/tools/index.js +2 -13
  116. package/dist/tools/list-dir.js +17 -18
  117. package/dist/tools/load-skill.js +3 -1
  118. package/dist/tools/path-utils.js +4 -4
  119. package/dist/tools/pipeline-run.js +25 -25
  120. package/dist/tools/process-kill.js +11 -11
  121. package/dist/tools/process-list.js +22 -20
  122. package/dist/tools/process-log.js +18 -22
  123. package/dist/tools/question.js +3 -1
  124. package/dist/tools/read-file.js +2 -10
  125. package/dist/tools/recall.js +37 -44
  126. package/dist/tools/registry.js +4 -15
  127. package/dist/tools/remember.js +29 -29
  128. package/dist/tools/scope-check.js +9 -9
  129. package/dist/tools/subagent.js +9 -54
  130. package/dist/tools/user-input.js +1 -1
  131. package/dist/tools/web-browse.js +3 -3
  132. package/dist/tools/web-fetch.js +3 -3
  133. package/dist/tools/web-search.js +3 -3
  134. package/dist/tools/write-file.js +3 -1
  135. package/dist/ui/box.js +5 -1
  136. package/dist/ui/index.js +6 -6
  137. package/dist/ui/md-formatter.js +33 -33
  138. package/dist/ui/output.js +5 -5
  139. package/dist/ui/renderer.js +10 -15
  140. package/dist/ui/table.js +1 -1
  141. package/package.json +48 -48
  142. package/dist/cli/plugin-commands.js +0 -36
  143. package/dist/cli/run-result.js +0 -22
  144. package/dist/core/version.js +0 -24
  145. package/dist/modules/artifacts/store.js +0 -61
  146. package/dist/modules/browser/bridge-client.js +0 -199
  147. package/dist/modules/browser/bridge-path.js +0 -10
  148. package/dist/modules/browser/driver.js +0 -136
  149. package/dist/modules/context/chunk-query.js +0 -100
  150. package/dist/modules/context/fact-extractor.js +0 -162
  151. package/dist/modules/context/history.js +0 -15
  152. package/dist/modules/execution/audit-runners.js +0 -152
  153. package/dist/modules/execution/execution-plugin.js +0 -272
  154. package/dist/modules/execution/plan-tool.js +0 -508
  155. package/dist/modules/execution/windows-commands.js +0 -41
  156. package/dist/modules/indexer/project-profile.js +0 -183
  157. package/dist/modules/lsp/check-tool.js +0 -58
  158. package/dist/modules/lsp/command.js +0 -60
  159. package/dist/modules/lsp/probe.js +0 -76
  160. package/dist/modules/lsp/project-root.js +0 -32
  161. package/dist/modules/lsp/startup-check.js +0 -141
  162. package/dist/modules/processes/detect.js +0 -34
  163. package/dist/modules/skills/matcher.js +0 -27
  164. package/dist/modules/updater/module.js +0 -116
  165. package/dist/tools/chunk-query.js +0 -99
  166. package/dist/tools/download-file.js +0 -116
  167. package/dist/tools/enable-tools.js +0 -58
  168. package/dist/tools/hidden-tools-block.js +0 -37
  169. package/dist/ui/line-editor.js +0 -703
  170. package/dist/ui/line-math.js +0 -69
  171. package/dist/ui/plan-view.js +0 -103
package/dist/cli/repl.js CHANGED
@@ -1,22 +1,20 @@
1
1
  import * as readline from "readline";
2
2
  import { pc } from "../ui/colors";
3
- import { LineEditor } from "../ui/line-editor";
4
3
  import { existsSync, readFileSync, writeFileSync } from "fs";
5
4
  import { join, dirname } from "path";
6
5
  import { homedir } from "os";
7
6
  import { fileURLToPath } from "url";
8
7
  import { Completer, SlashCommandProvider, SessionNameProvider, SubcommandProvider, SkillNameProvider, } from "./completer";
9
8
  import { Renderer } from "../ui/renderer";
10
- import { box, divider } from "../ui/box";
11
- import stringWidth from "string-width";
9
+ import { box } from "../ui/box";
12
10
  import { t } from "../i18n/index";
13
11
  import { registerAllCommands } from "./repl-commands";
14
- import { probeLspServers } from "../modules/lsp/probe";
15
- import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
16
- import { readActivePlan, formatPlanChecklist, stepContextForTool } from "../ui/plan-view";
17
12
  function readVersion() {
18
13
  const here = dirname(fileURLToPath(import.meta.url));
19
- const candidates = [join(here, "..", "..", "package.json"), join(here, "..", "package.json")];
14
+ const candidates = [
15
+ join(here, "..", "..", "package.json"),
16
+ join(here, "..", "package.json"),
17
+ ];
20
18
  for (const p of candidates) {
21
19
  if (existsSync(p)) {
22
20
  try {
@@ -62,9 +60,6 @@ export class Repl {
62
60
  baseDir;
63
61
  noAgentsMd;
64
62
  pendingClipboardImage = null;
65
- exitOnClose = false;
66
- activePlan = null;
67
- lastPlanSig = "";
68
63
  rl;
69
64
  agent;
70
65
  config;
@@ -72,10 +67,9 @@ export class Repl {
72
67
  skillsModule;
73
68
  pluginManager;
74
69
  logger;
75
- constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger, exitOnClose) {
70
+ constructor(agent, config, sessionManager, skillsModule, pluginManager, configDir, baseDir, noAgentsMd, logger) {
76
71
  this.agent = agent;
77
72
  this.config = config;
78
- this.exitOnClose = exitOnClose === true;
79
73
  this.sessionManager = sessionManager;
80
74
  this.skillsModule = skillsModule;
81
75
  this.pluginManager = pluginManager;
@@ -85,34 +79,20 @@ export class Repl {
85
79
  this.noAgentsMd = noAgentsMd === true;
86
80
  this.historyPath = join(homedir(), ".mma", "repl-history");
87
81
  this.loadHistory();
88
- this.rl = process.stdin.isTTY
89
- ? new LineEditor({
90
- input: process.stdin,
91
- output: process.stdout,
92
- prompt: pc.cyan(t("repl.you")),
93
- history: this.history,
94
- historySize: this.maxHistory,
95
- completer: (line) => {
96
- const [matches, partial] = this.completer.complete(line);
97
- if (matches.length > 0)
98
- return [matches, partial];
99
- return [[], line];
100
- },
101
- })
102
- : readline.createInterface({
103
- input: process.stdin,
104
- output: process.stdout,
105
- prompt: pc.cyan(t("repl.you")),
106
- history: this.history,
107
- historySize: this.maxHistory,
108
- tabSize: 2,
109
- completer: (line) => {
110
- const [matches, partial] = this.completer.complete(line);
111
- if (matches.length > 0)
112
- return [matches, partial];
113
- return [[], line];
114
- },
115
- });
82
+ this.rl = readline.createInterface({
83
+ input: process.stdin,
84
+ output: process.stdout,
85
+ prompt: pc.cyan("> "),
86
+ history: this.history,
87
+ historySize: this.maxHistory,
88
+ tabSize: 2,
89
+ completer: (line) => {
90
+ const [matches, partial] = this.completer.complete(line);
91
+ if (matches.length > 0)
92
+ return [matches, partial];
93
+ return [[], line];
94
+ },
95
+ });
116
96
  registerAllCommands(this);
117
97
  this.setupCompleter();
118
98
  this.setupListeners();
@@ -139,7 +119,13 @@ export class Repl {
139
119
  this.completer.registerProvider(new SessionNameProvider(() => this.sessionManager.list().map((s) => s.name)));
140
120
  }
141
121
  if (this.skillsModule) {
142
- this.completer.registerProvider(new SubcommandProvider("skill", ["list", "loaded", "load", "unload", "search"]));
122
+ this.completer.registerProvider(new SubcommandProvider("skill", [
123
+ "list",
124
+ "loaded",
125
+ "load",
126
+ "unload",
127
+ "search",
128
+ ]));
143
129
  this.completer.registerProvider(new SkillNameProvider(this.skillsModule));
144
130
  }
145
131
  }
@@ -178,12 +164,12 @@ export class Repl {
178
164
  }
179
165
  }
180
166
  if (this.running) {
181
- this.rl.setPrompt(pc.cyan(t("repl.you")));
167
+ this.rl.setPrompt(pc.cyan("> "));
182
168
  this.rl.prompt();
183
169
  }
184
170
  }
185
171
  else {
186
- this.rl.setPrompt(pc.cyan(t("repl.you") + "… "));
172
+ this.rl.setPrompt(pc.cyan("... "));
187
173
  this.rl.prompt();
188
174
  }
189
175
  return;
@@ -191,7 +177,7 @@ export class Repl {
191
177
  if (this.isMultiLineInput(trimmed)) {
192
178
  inMultiLine = true;
193
179
  multiLineBuffer = trimmed;
194
- this.rl.setPrompt(pc.cyan(t("repl.you") + "… "));
180
+ this.rl.setPrompt(pc.cyan("... "));
195
181
  this.rl.prompt();
196
182
  return;
197
183
  }
@@ -211,22 +197,52 @@ export class Repl {
211
197
  });
212
198
  this.rl.on("close", () => {
213
199
  this.running = false;
214
- this.saveHistory();
215
- this.agent.shutdown();
216
- if (this.exitOnClose)
217
- process.exit(0);
218
200
  });
219
- if (this.exitOnClose && !process.stdin.isTTY) {
220
- process.stdin.on("end", () => {
221
- this.running = false;
222
- this.saveHistory();
223
- this.agent.shutdown();
224
- process.exit(0);
201
+ if (process.stdin.isTTY) {
202
+ readline.emitKeypressEvents(process.stdin);
203
+ process.stdin.on("keypress", async (str, key) => {
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;
212
+ const now = Date.now();
213
+ const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
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;
223
+ }
224
+ if (key.ctrl && key.name === "v" && !this.agentRunning) {
225
+ try {
226
+ const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
227
+ const clipBuf = await readClipboardImage();
228
+ if (clipBuf) {
229
+ const { dataUrl } = await bufferToDataUrl(clipBuf);
230
+ this.pendingClipboardImage = dataUrl;
231
+ const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
232
+ console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
233
+ this.rl.prompt();
234
+ }
235
+ else {
236
+ console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
237
+ this.rl.prompt();
238
+ }
239
+ }
240
+ catch {
241
+ // clipboard read failed — ignore, let terminal paste text normally
242
+ }
243
+ }
225
244
  });
226
245
  }
227
- if (this.rl instanceof LineEditor) {
228
- this.rl.onKeyInput = (str, key) => this.handleSpecialKey(str, key);
229
- }
230
246
  let forceExitTimer = null;
231
247
  process.on("SIGINT", () => {
232
248
  if (this.agentRunning) {
@@ -242,55 +258,6 @@ export class Repl {
242
258
  }
243
259
  });
244
260
  }
245
- handleSpecialKey(str, key) {
246
- if (key.name === "escape") {
247
- // Bun's keypress decoder collapses a fast double-Esc into a single
248
- // keypress whose `sequence` contains two ESC bytes ("\x1b\x1b").
249
- // Counting bytes (not events) catches both the collapsed case and
250
- // the case where two separate escape keypresses land in the window.
251
- const escBytes = key.sequence ? (key.sequence.match(/\x1b/g) || []).length : 1;
252
- const now = Date.now();
253
- const withinWindow = now - this.lastEscTime < this.doubleEscDelay;
254
- this.lastEscTime = now;
255
- if (escBytes >= 2 || withinWindow) {
256
- this.lastEscTime = 0;
257
- if (this.agentRunning) {
258
- process.stdout.write(pc.yellow(`\n${t("repl.interrupt")}\n`));
259
- this.agent.shutdown();
260
- }
261
- }
262
- return true;
263
- }
264
- if (key.ctrl && key.name === "v" && !this.agentRunning) {
265
- this.pasteClipboardImage();
266
- return true;
267
- }
268
- return false;
269
- }
270
- async pasteClipboardImage() {
271
- try {
272
- const { readClipboardImage, bufferToDataUrl } = await import("../llm/image-utils");
273
- const clipBuf = await readClipboardImage();
274
- if (clipBuf) {
275
- const { dataUrl } = await bufferToDataUrl(clipBuf);
276
- this.pendingClipboardImage = dataUrl;
277
- const sizeKb = Math.round((dataUrl.length * 3) / 4 / 1024);
278
- console.log(pc.green(`\n${t("image.attached", { source: "clipboard", size: `${sizeKb} KB` })}`));
279
- if (this.rl instanceof LineEditor)
280
- this.rl.reset();
281
- this.rl.prompt();
282
- }
283
- else {
284
- console.log(pc.yellow(`\n${t("image.clipboard_empty")}`));
285
- if (this.rl instanceof LineEditor)
286
- this.rl.reset();
287
- this.rl.prompt();
288
- }
289
- }
290
- catch {
291
- // clipboard read failed — ignore, let terminal paste text normally
292
- }
293
- }
294
261
  isMultiLineInput(line) {
295
262
  if (line.endsWith("\\"))
296
263
  return true;
@@ -316,26 +283,17 @@ export class Repl {
316
283
  this.pendingClipboardImage = null;
317
284
  }
318
285
  this.logger?.logREPL("user", input);
319
- if (this.rl instanceof LineEditor)
320
- this.rl.reset();
321
- // Overwrite the empty prompt that reset() just drew with a divider,
322
- // then label the agent's reply — separates the user's message from
323
- // the agent's answer.
324
- process.stdout.write("\r" + divider() + "\n" + pc.green(t("repl.agent")));
286
+ process.stdout.write("\n" + pc.green(t("repl.agent")));
325
287
  const renderer = new Renderer({
326
288
  spinner: this.config.ui?.spinner ?? true,
327
289
  toolStyle: this.config.ui?.toolStyle ?? "inline",
328
290
  });
329
- this.refreshActivePlan(renderer);
330
291
  const result = await this.agent.run(input, (c) => renderer.text(c), (m) => renderer.meta(m), (ev) => {
331
292
  if (ev.type === "start") {
332
- renderer.toolStart(ev.tool, ev.args, stepContextForTool(this.activePlan, ev.tool, ev.args));
293
+ renderer.toolStart(ev.tool, ev.args);
333
294
  }
334
295
  else {
335
296
  renderer.toolEnd(ev.tool, ev.duration ?? 0, ev.error, ev.ctxDelta);
336
- if (ev.tool === "plan" || ev.tool === "todo") {
337
- this.refreshActivePlan(renderer);
338
- }
339
297
  }
340
298
  }, (phase) => {
341
299
  if (phase === "thinking") {
@@ -352,29 +310,11 @@ export class Repl {
352
310
  console.error(pc.red(`${t("error.prefix")}${result.error}`));
353
311
  }
354
312
  this.showContextBar(result);
355
- // Close the agent's turn: a divider before the next prompt keeps
356
- // consecutive user/agent messages visually separated.
357
- process.stdout.write("\n" + divider() + "\n");
358
313
  }
359
314
  finally {
360
315
  this.agentRunning = false;
361
316
  }
362
317
  }
363
- /** Re-read the active plan from disk; print a live checklist when it changed. */
364
- refreshActivePlan(renderer) {
365
- const plan = readActivePlan(this.baseDir);
366
- const sig = plan ? JSON.stringify(plan.steps.map((s) => `${s.id}:${s.status}`)) : "";
367
- if (!plan) {
368
- this.activePlan = null;
369
- this.lastPlanSig = "";
370
- return;
371
- }
372
- this.activePlan = plan;
373
- if (sig !== this.lastPlanSig) {
374
- this.lastPlanSig = sig;
375
- renderer.planBlock(formatPlanChecklist(plan));
376
- }
377
- }
378
318
  commands = new Map();
379
319
  registerCommand(cmd) {
380
320
  this.commands.set(cmd.name, cmd);
@@ -432,7 +372,9 @@ export class Repl {
432
372
  continue;
433
373
  console.log(pc.bold(t(`repl.group.${groupKey}`)));
434
374
  for (const cmd of cmds) {
435
- const aliases = cmd.aliases?.length ? ` (${pc.dim(cmd.aliases.join(", "))})` : "";
375
+ const aliases = cmd.aliases?.length
376
+ ? ` (${pc.dim(cmd.aliases.join(", "))})`
377
+ : "";
436
378
  console.log(` ${pc.cyan("/" + cmd.name)}${aliases} ${pc.dim(cmd.description)}`);
437
379
  if (cmd.usage) {
438
380
  console.log(` ${pc.dim(cmd.usage)}`);
@@ -465,7 +407,7 @@ export class Repl {
465
407
  }
466
408
  }
467
409
  }
468
- async start() {
410
+ start() {
469
411
  this.running = true;
470
412
  const info = [];
471
413
  const row = (label, value) => {
@@ -485,16 +427,12 @@ export class Repl {
485
427
  row(t("repl.skills_label"), `${pc.white(String(this.skillsModule.getAvailable().length))} available, ${pc.dim(`budget: ${budget.total} tokens`)}`);
486
428
  }
487
429
  if (this.pluginManager) {
488
- const infos = this.pluginManager.getPluginInfos().filter(({ plugin }) => !plugin.isBuiltin);
489
- if (infos.length > 0) {
490
- const pluginNames = infos
491
- .map(({ plugin, source }) => {
492
- const version = plugin.version ? `${pc.dim(plugin.version)}` : "";
493
- const origin = source ? pc.dim(` [${source}]`) : "";
494
- return `${plugin.name}${version}${origin}`;
495
- })
496
- .join(", ");
497
- row(t("repl.plugins_label"), `${pc.white(String(infos.length))} active ${pc.dim(`(${pluginNames})`)}`);
430
+ const plugins = this.pluginManager
431
+ .getAllPlugins()
432
+ .filter((p) => !p.isBuiltin);
433
+ if (plugins.length > 0) {
434
+ const pluginNames = plugins.map((p) => p.name).join(", ");
435
+ row(t("repl.plugins_label"), `${pc.white(String(plugins.length))} active ${pc.dim(`(${pluginNames})`)}`);
498
436
  }
499
437
  }
500
438
  const mcpServers = this.config.mcpServers || {};
@@ -530,82 +468,14 @@ export class Repl {
530
468
  row(t("repl.session_label"), `${pc.cyan(meta.name)} ${pc.dim(`(${meta.id.slice(0, 12)})`)} — ${meta.messageCount} msgs ${pc.dim(sessionPath)}`);
531
469
  }
532
470
  const headerWidth = Math.max(50, Math.min(96, process.stdout.columns || 96));
533
- // LSP status is probed in the background — it must never block the banner.
534
- const isTty = process.stdout.isTTY === true;
535
- const lspEnabled = isTty && (this.config.lsp ?? DEFAULT_LSP_CONFIG).enabled !== false;
536
- if (lspEnabled) {
537
- row(t("repl.lsp_label"), pc.dim("…"));
538
- }
539
- const boxLines = box(info, {
471
+ for (const line of box(info, {
540
472
  title: t("repl.title", { version }),
541
473
  width: headerWidth,
542
- });
543
- for (const line of boxLines) {
474
+ })) {
544
475
  console.log(line);
545
476
  }
546
477
  console.log();
547
478
  this.rl.prompt();
548
- if (lspEnabled) {
549
- this.probeLspBanner()
550
- .then((lspSummary) => {
551
- if (lspSummary)
552
- this.updateLspRow(boxLines.length, headerWidth, lspSummary);
553
- })
554
- .catch(() => { });
555
- }
556
- }
557
- /**
558
- * Replace the LSP banner row (rendered as "…") in place once the probe
559
- * finishes. The LSP row is the last data row of the box, so it sits exactly
560
- * two terminal rows above the prompt line — that offset is stable no matter
561
- * how earlier rows wrap. Skipped (placeholder stays) when a rewrite would
562
- * corrupt the screen: the agent is already running, the terminal is not a
563
- * TTY, a box line wrapped, or the summary is too wide for a single row.
564
- */
565
- updateLspRow(boxLineCount, headerWidth, summary) {
566
- if (this.agentRunning || !process.stdout.isTTY)
567
- return;
568
- if ((process.stdout.columns ?? 96) < headerWidth)
569
- return;
570
- const inner = headerWidth - 4;
571
- // Match box()'s wrapText normalization (leading space stripped, runs of
572
- // whitespace collapsed) so the rewritten row is byte-identical to what
573
- // box() would render for the same text.
574
- const rowText = `${pc.yellow(t("repl.lsp_label"))} ${summary.replace(/\s+/g, " ").trim()}`;
575
- if (stringWidth(rowText) > inner)
576
- return;
577
- const padded = rowText + " ".repeat(Math.max(0, inner - stringWidth(rowText)));
578
- const line = pc.dim("│") + " " + padded + " " + pc.dim("│");
579
- // From the prompt row (boxLineCount) up 2 to the LSP row (last data row,
580
- // directly above the bottom border), clear it, write the new row, and
581
- // return the cursor to the prompt row. The editor re-positions itself on
582
- // the next keystroke, so any typed input is preserved.
583
- process.stdout.write(`\x1b[2A\r\x1b[2K${line}\x1b[2B\r`);
584
- }
585
- /**
586
- * Probe the LSP servers applicable to the current project and format a
587
- * one-line banner summary, e.g. "✓ typescript (1.2s)" or "✗ css — timeout".
588
- * Returns null when LSP is disabled or no server applies.
589
- */
590
- async probeLspBanner() {
591
- const config = this.config.lsp ?? DEFAULT_LSP_CONFIG;
592
- try {
593
- const summary = await probeLspServers(config, this.baseDir);
594
- if (summary.noneApplicable)
595
- return null;
596
- const parts = [];
597
- for (const r of summary.ok) {
598
- parts.push(pc.green(`✓ ${r.language}${pc.dim(` (${(r.durationMs / 1000).toFixed(1)}s)`)}`));
599
- }
600
- for (const r of summary.failed) {
601
- const reason = r.error?.includes("timeout") ? t("repl.lsp_timeout") : t("repl.lsp_failed");
602
- parts.push(pc.red(`✗ ${r.language}${pc.dim(` — ${reason}`)}`));
603
- }
604
- return parts.join(" ");
605
- }
606
- catch {
607
- return pc.yellow(t("repl.lsp_unknown"));
608
- }
609
479
  }
610
480
  stop() {
611
481
  this.running = false;
@@ -9,7 +9,9 @@ import { t } from "../i18n/index";
9
9
  * Create security subcommand
10
10
  */
11
11
  export function createSecurityCommand(program) {
12
- const securityCmd = program.command("security").description(t("cli.security.description"));
12
+ const securityCmd = program
13
+ .command("security")
14
+ .description(t("cli.security.description"));
13
15
  // security status - show current security configuration
14
16
  securityCmd
15
17
  .command("status")
@@ -43,7 +45,7 @@ export function createSecurityCommand(program) {
43
45
  console.log(t("cli.security.available_policies"));
44
46
  console.log("");
45
47
  for (const [preset, policy] of Object.entries(SECURITY_POLICIES)) {
46
- if (preset === "custom")
48
+ if (preset === 'custom')
47
49
  continue;
48
50
  const marker = " ";
49
51
  console.log(` ${marker}${preset.padEnd(12)} ${policy.name}`);
@@ -60,7 +62,7 @@ export function createSecurityCommand(program) {
60
62
  .action(async (preset) => {
61
63
  const configPath = join(homedir(), ".mma", "config.json");
62
64
  const { config: appConfig } = await bootstrap();
63
- const validPresets = ["strict", "balanced", "permissive"];
65
+ const validPresets = ['strict', 'balanced', 'permissive'];
64
66
  if (!validPresets.includes(preset)) {
65
67
  console.log(t("cli.security.invalid_preset", { presets: validPresets.join(", ") }));
66
68
  return;
@@ -115,8 +117,8 @@ export function createSecurityCommand(program) {
115
117
  appConfig.security = appConfig.security || {};
116
118
  appConfig.security.auditNotifier = {
117
119
  enabled: true,
118
- minSeverity: "medium",
119
- eventTypes: ["security_block", "bash_command", "file_operation", "network_request"],
120
+ minSeverity: 'medium',
121
+ eventTypes: ['security_block', 'bash_command', 'file_operation', 'network_request'],
120
122
  maxRetries: 3,
121
123
  webhookTimeout: 5000,
122
124
  };
package/dist/cli/setup.js CHANGED
@@ -22,7 +22,9 @@ function menuList(items) {
22
22
  }
23
23
  function ask(rl, question, defaultValue) {
24
24
  return new Promise((resolve) => {
25
- const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
25
+ const prompt = defaultValue
26
+ ? `${question} [${defaultValue}]: `
27
+ : `${question}: `;
26
28
  rl.question(prompt, (answer) => {
27
29
  resolve(answer.trim() || defaultValue || "");
28
30
  });
@@ -174,7 +176,7 @@ export async function runSetup(externalRl) {
174
176
  model = models[idx];
175
177
  }
176
178
  else {
177
- model = await ask(rl, t("setup.model_name"), "qwen/qwen3.5-9b");
179
+ model = await ask(rl, t("setup.model_name"), "qwen3.5-9b");
178
180
  }
179
181
  const connected = await withSpinner(t("setup.testing_spinner", { model }), () => testChat(apiBase, apiKey, model));
180
182
  if (connected) {
@@ -193,11 +195,17 @@ export async function runSetup(externalRl) {
193
195
  let securityBashBlock = false;
194
196
  let securityFlagsBlock = false;
195
197
  let securityPathsDeny = true;
196
- if (configureSecurity.toLowerCase() === "y" || configureSecurity.toLowerCase() === "yes") {
197
- securityBashBlock = (await ask(rl, t("setup.security_bash_block"), "n")).toLowerCase() === "y";
198
+ if (configureSecurity.toLowerCase() === "y" ||
199
+ configureSecurity.toLowerCase() === "yes") {
200
+ securityBashBlock =
201
+ (await ask(rl, t("setup.security_bash_block"), "n")).toLowerCase() ===
202
+ "y";
198
203
  securityFlagsBlock =
199
- (await ask(rl, t("setup.security_flags_block"), "n")).toLowerCase() === "y";
200
- securityPathsDeny = (await ask(rl, t("setup.security_paths_deny"), "Y")).toLowerCase() !== "n";
204
+ (await ask(rl, t("setup.security_flags_block"), "n")).toLowerCase() ===
205
+ "y";
206
+ securityPathsDeny =
207
+ (await ask(rl, t("setup.security_paths_deny"), "Y")).toLowerCase() !==
208
+ "n";
201
209
  }
202
210
  if (ownRl)
203
211
  rl.close();
@@ -221,8 +229,18 @@ export async function runSetup(externalRl) {
221
229
  t("setup.summary_value"),
222
230
  ], [
223
231
  [t("setup.provider_type"), provider, t("setup.model_name"), model],
224
- ["API Base URL", apiBase, t("setup.context_window"), String(contextWindow)],
225
- [t("setup.api_key"), apiKey || "not-needed", t("setup.max_iters"), String(maxIterations)],
232
+ [
233
+ "API Base URL",
234
+ apiBase,
235
+ t("setup.context_window"),
236
+ String(contextWindow),
237
+ ],
238
+ [
239
+ t("setup.api_key"),
240
+ apiKey || "not-needed",
241
+ t("setup.max_iters"),
242
+ String(maxIterations),
243
+ ],
226
244
  [t("setup.ui_lang"), locale, "", ""],
227
245
  ], { maxColumns: 4 });
228
246
  for (const l of summary)
@@ -1,13 +1,12 @@
1
- import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from "fs";
1
+ import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync, } from "fs";
2
2
  import { join, dirname } from "path";
3
3
  import { DEFAULTS } from "./defaults";
4
4
  import { DEFAULT_SECURITY_CONFIG } from "./security";
5
- import { DEFAULT_LSP_CONFIG } from "../modules/lsp/config";
6
5
  import { t } from "../i18n/index";
7
6
  import { MigrationDetector } from "../migration/detect";
8
7
  import { BackupManager } from "../migration/backup";
9
8
  import { validateExpertConfig } from "./experts";
10
- import { ConfigEncryptor } from "../modules/security/encryption";
9
+ import { ConfigEncryptor, } from "../modules/security/encryption";
11
10
  /**
12
11
  * Restore RegExp instances in dangerousPatterns that were serialized as {}
13
12
  * (pre-0.8.0 configs) or as {__regex, source, flags} (new format).
@@ -70,7 +69,9 @@ function regexReplacer(_key, value) {
70
69
  * JSON reviver that restores RegExp objects serialized by regexReplacer.
71
70
  */
72
71
  function regexReviver(_key, value) {
73
- if (value && typeof value === "object" && value.__regex === true) {
72
+ if (value &&
73
+ typeof value === "object" &&
74
+ value.__regex === true) {
74
75
  const { source, flags } = value;
75
76
  try {
76
77
  return new RegExp(source, flags);
@@ -81,46 +82,6 @@ function regexReviver(_key, value) {
81
82
  }
82
83
  return value;
83
84
  }
84
- /**
85
- * Legacy npx `args` for LSP servers that predate the fixed invocation
86
- * (`--yes --package <server> --package typescript@5 …`). These exact arrays
87
- * are unsafe: without `--yes`/`--package` the servers fail to bootstrap
88
- * (`Could not find a valid TypeScript installation`) or the package name is
89
- * wrong, so the server hangs until the initialize timeout. When a stored
90
- * server's `args` matches one of these (or is missing) it is replaced with the
91
- * current default args — user-customized arrays that differ are left alone.
92
- */
93
- const LEGACY_LSP_SERVER_ARGS = {
94
- typescript: { args: ["typescript-language-server", "--stdio"], timeout: 30000 },
95
- javascript: { args: ["typescript-language-server", "--stdio"], timeout: 30000 },
96
- json: { args: ["vscode-json-languageserver", "--stdio"], timeout: 30000 },
97
- html: { args: ["vscode-html-languageserver", "--stdio"], timeout: 60000 },
98
- css: { args: ["vscode-css-languageserver", "--stdio"], timeout: 60000 },
99
- };
100
- /**
101
- * Replace stale pre-0.41.1 LSP server invocations (npx bootstrap args) with
102
- * the current defaults, so an old `~/.mma/config.json` that overrides the
103
- * fixed server args can't silently re-break every LSP server. In-memory only —
104
- * mirrors the `restoreDangerousPatterns` precedent; the file keeps the old
105
- * args and is normalized again on every load.
106
- */
107
- function normalizeLspServerArgs(config) {
108
- const servers = config.lsp?.servers;
109
- if (!servers)
110
- return;
111
- for (const [lang, legacy] of Object.entries(LEGACY_LSP_SERVER_ARGS)) {
112
- const server = servers[lang];
113
- const def = DEFAULT_LSP_CONFIG.servers[lang];
114
- if (!server || server.command !== "npx" || !def)
115
- continue;
116
- const storedArgs = Array.isArray(server.args) ? server.args : null;
117
- const isLegacy = storedArgs === null || JSON.stringify(storedArgs) === JSON.stringify(legacy.args);
118
- if (isLegacy) {
119
- server.args = def.args;
120
- server.timeout = def.timeout;
121
- }
122
- }
123
- }
124
85
  function loadJSON(path) {
125
86
  try {
126
87
  if (existsSync(path)) {
@@ -199,8 +160,6 @@ export function loadConfig(options) {
199
160
  if (config.security?.contentScan?.dangerousPatterns) {
200
161
  config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
201
162
  }
202
- // Replace stale pre-0.41.1 LSP server args with the current defaults.
203
- normalizeLspServerArgs(config);
204
163
  config = applyEnvVars(config);
205
164
  // Decrypt sensitive fields in the loaded config
206
165
  try {
@@ -220,12 +179,6 @@ export function loadConfig(options) {
220
179
  }
221
180
  export function validateConfig(config, allToolTags) {
222
181
  const errors = validateExpertConfig(config, allToolTags);
223
- const knownTags = new Set(allToolTags);
224
- for (const tag of config.tools?.defaultTags ?? []) {
225
- if (!knownTags.has(tag)) {
226
- errors.push(`tools.defaultTags references unknown tag "${tag}". Known tags: ${Array.from(knownTags).join(", ")}`);
227
- }
228
- }
229
182
  if (errors.length > 0) {
230
183
  throw new Error(`Config validation failed:\n${errors.join("\n")}`);
231
184
  }