micro-models-agent 0.41.2 → 0.43.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 +648 -183
  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
@@ -1,8 +1,8 @@
1
- import { isCommandAllowed, sanitizeCommandForLog } from "../modules/security/command-validator";
2
- import { logBashCommand, logSecurityBlock } from "../modules/security/audit-log";
1
+ import { isCommandAllowed, sanitizeCommandForLog, } from "../modules/security/command-validator";
2
+ import { logBashCommand, logSecurityBlock, } from "../modules/security/audit-log";
3
3
  import { getSessionSecurityConfig } from "../modules/security/session-isolation";
4
4
  import { DEFAULT_SECURITY_CONFIG } from "../config/security";
5
- import { processRegistry, registerKillable, unregisterKillable } from "../modules/processes";
5
+ import { processRegistry, registerKillable, unregisterKillable, } from "../modules/processes";
6
6
  import { t } from "../i18n/index";
7
7
  import { platform } from "os";
8
8
  import { MAX_PREVIEW_LINES } from "./preview";
@@ -16,26 +16,11 @@ import { MAX_PREVIEW_LINES } from "./preview";
16
16
  export const BASH_GRACE_MS = 5000;
17
17
  /** Short window for explicit `background: true` — surfaces immediate spawn failures (bad cwd, missing shell). */
18
18
  const SPAWN_SETTLE_MS = 100;
19
- /** Number of already-emitted lines shown in the background start message. */
20
- const BG_OUTPUT_PREVIEW_LINES = 15;
21
19
  let bashGraceMs = BASH_GRACE_MS;
22
20
  /** Test hook: override the auto-background grace window. */
23
21
  export function setBashGraceMs(ms) {
24
22
  bashGraceMs = ms;
25
23
  }
26
- /**
27
- * Consecutive failures per first command word (resets on success). A model
28
- * that keeps calling a forbidden Unix command in cmd.exe (head/cat/grep/...)
29
- * gets a hint the first couple of times; after HARD_BLOCK_THRESHOLD consecutive
30
- * failures the tool hard-stops with a STOP directive instead of repeating the
31
- * same hint forever (observed: `head` called 5×, `Select-String` 3× in one run).
32
- */
33
- const FAILING_FIRST_WORDS = new Map();
34
- const HARD_BLOCK_THRESHOLD = 3;
35
- /** Test hook: clear the consecutive-failure tracking state. */
36
- export function resetBashFailureTracking() {
37
- FAILING_FIRST_WORDS.clear();
38
- }
39
24
  /**
40
25
  * Detect a file write via `echo/printf ... > file` — common model habit that
41
26
  * breaks in cmd.exe: single quotes are not grouping quotes, `>` only applies
@@ -123,12 +108,6 @@ const UNIX_TO_WIN_HINTS = {
123
108
  echo: "echo works on Windows, but avoid pipes (|).",
124
109
  "Get-Content": "Use the read_file tool instead.",
125
110
  "Select-Object": "Use the read_file tool with offset/limit instead.",
126
- "Write-Host": "PowerShell cmdlet — this shell is cmd.exe. Print with plain echo instead.",
127
- "Select-String": "Use the grep tool instead.",
128
- "Out-File": "Write files with the write_file tool instead.",
129
- "Set-Content": "Write files with the write_file tool instead.",
130
- "Get-ChildItem": "Use the list_dir tool instead.",
131
- "Remove-Item": "Use the delete_file tool instead.",
132
111
  };
133
112
  /** Unix commands that can be transparently translated to cmd.exe equivalents. */
134
113
  const UNIX_TO_WIN_TRANSLATE = {
@@ -219,94 +198,9 @@ export function parseToolArgs(raw) {
219
198
  }
220
199
  return args;
221
200
  }
222
- /**
223
- * Detect a test-runner report embedded in command output (bun test / vitest /
224
- * jest / mocha / `go test` / pytest). Returns null when the output does not
225
- * look like a test run. Used to inject a warning when tests fail so the model
226
- * cannot close a "run the tests" step as done.
227
- */
228
- export function detectTestResults(output) {
229
- if (!output)
230
- return null;
231
- // Framework-specific quantifiers first (unambiguous).
232
- const jest = output.match(/Tests:\s+(\d+)\s+passed,\s*(\d+)\s+failed/i);
233
- if (jest) {
234
- return { framework: "jest", passed: +jest[1], failed: +jest[2] };
235
- }
236
- const mochaPass = output.match(/(\d+)\s+passing/i);
237
- const mochaFail = output.match(/(\d+)\s+failing/i);
238
- if (mochaPass || mochaFail) {
239
- return {
240
- framework: "mocha",
241
- passed: mochaPass ? +mochaPass[1] : 0,
242
- failed: mochaFail ? +mochaFail[1] : 0,
243
- };
244
- }
245
- const pytest = output.match(/(\d+)\s+passed[^\n]*?(?:,\s*(\d+)\s+failed)?/i);
246
- if (pytest &&
247
- /pytest|passed|failed/i.test(output) &&
248
- /(=====|short test summary|tests\s+ok)/i.test(output) === false &&
249
- /pytest|collect/i.test(output)) {
250
- return {
251
- framework: "pytest",
252
- passed: +pytest[1],
253
- failed: pytest[2] ? +pytest[2] : 0,
254
- };
255
- }
256
- // bun test / vitest: per-case markers + trailing "N pass / M fail".
257
- const failMarkers = output.match(/\(fail\)/g)?.length ?? 0;
258
- const passMarkers = output.match(/\(pass\)/g)?.length ?? 0;
259
- const xMarkers = output.match(/\s×\s/g)?.length ?? 0;
260
- const summaryMatch = output.match(/^\s*(\d+)\s+pass[^\n]*$/m);
261
- const failSummary = output.match(/^\s*(\d+)\s+fail[^\n]*$/m);
262
- const ran = output.match(/(?:Ran|ran)\s+\d+\s+tests/i);
263
- if (failMarkers > 0 || passMarkers > 0 || xMarkers > 0 || ran) {
264
- const failed = Math.max(failMarkers + xMarkers, failSummary ? +failSummary[1] : 0);
265
- const passed = Math.max(passMarkers, summaryMatch ? +summaryMatch[1] : 0);
266
- return {
267
- framework: ran ? "bun/vitest" : "vitest",
268
- passed,
269
- failed,
270
- summary: summaryMatch?.[0] ?? failSummary?.[0],
271
- };
272
- }
273
- return null;
274
- }
275
- /**
276
- * R5: a command that runs a script file (bun/node/python/...) exited 0 but
277
- * printed nothing. The most common cause in agent sessions: the program has
278
- * no entry point — nobody calls its main() with the CLI args. Surface a
279
- * concrete hint instead of letting the model stare at an empty output.
280
- */
281
- const CLI_FILE_RUN_RE = /\b(bun|node|deno|python|python3|tsx|ts-node|php|ruby|go\s+run)\S*\s+(run\s+)?["']?[\w./\\-]+\.(ts|js|tsx|jsx|mjs|cjs|py)\b/;
282
- export function emptyCliRunHint(command, output, code) {
283
- if (code !== 0 || output.trim())
284
- return null;
285
- if (/[>|]/.test(command))
286
- return null; // redirection/pipes — output went elsewhere
287
- if (/\b(bun test|vitest|pytest|jest|mocha|--test)\b/i.test(command))
288
- return null;
289
- if (!CLI_FILE_RUN_RE.test(command))
290
- return null;
291
- return "the command exited 0 but printed NOTHING to stdout. If this should run a CLI program, the file probably has no entry point: read it with read_file and check the code actually calls its main function with command-line arguments (e.g. main(process.argv[2])) and prints results with console.log.";
292
- }
293
- /**
294
- * Detect npm/npx/bunx failing with "could not determine executable to run"
295
- * — the package declares no `bin` (or `npm run <script>` named a missing
296
- * script). Append a concrete hint instead of letting the model re-try blindly
297
- * (observed in two agent sessions: the model burned many iterations re-running
298
- * the same failing `npx`/`bunx`/`npm run` command).
299
- */
300
- const NPM_EXEC_RE = /could not determine executable to run/i;
301
- export function npmExecHint(output) {
302
- if (NPM_EXEC_RE.test(output)) {
303
- return `${output}\n\nHint: ${t("exec.npm_exec_hint")}`;
304
- }
305
- return output;
306
- }
307
201
  export const bashTool = {
308
202
  name: "bash",
309
- description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).\n\nWindows notes: the shell is cmd.exe — PowerShell cmdlets (Write-Host, Get-Content, Select-String, Out-File) and bash heredocs (cat << EOF) do NOT work there. Use the read_file/write_file tools instead of cat/echo redirection. Sequential commands: use && (a leading ; is auto-converted to &).",
203
+ description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).",
310
204
  tags: ["shell", "code"],
311
205
  parameters: {
312
206
  type: "object",
@@ -393,55 +287,19 @@ export const bashTool = {
393
287
  const code = entry.exitCode;
394
288
  let output = entry.log.join("\n");
395
289
  processRegistry.remove(entry.id);
396
- // R5: script file ran with exit 0 but empty output — likely a
397
- // missing entry point (main is never called with argv).
398
- const cliHint = emptyCliRunHint(command, output, code);
399
- if (cliHint) {
400
- output = `(exit code 0, no output)\n\nHint: ${cliHint}`;
401
- }
402
- // Auto-verify test runs: a failing suite must not be reported as a
403
- // clean success. Inject a prominent marker the model cannot miss
404
- // (the tpical failure: tests fail but the CLI exit code is 0).
405
- const testRun = detectTestResults(output);
406
- if (testRun && testRun.failed > 0) {
407
- output =
408
- `[test-runner] ${testRun.framework}: ${testRun.failed} test(s) FAILING, ${testRun.passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.\n\n` +
409
- output;
410
- }
411
- else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
412
- output = `[test-runner] ${testRun.framework}: all ${testRun.passed} test(s) passing.\n\n${output}`;
413
- }
414
290
  if (!output && code !== 0) {
415
291
  output = `(exit code ${code})`;
416
292
  }
417
- // npm/npx/bunx: "could not determine executable to run" the
418
- // package has no `bin` entry (or the script does not exist).
419
- // This is cross-platform, so it runs before the win32 hint block.
420
- output = npmExecHint(output);
421
- // On Windows, hint about Unix commands that don't work, and
422
- // hard-stop a command that keeps failing the same way. The
423
- // hint keys on the ORIGINAL command word (before
424
- // adaptCommandForWindows translated cat→type): the translation
425
- // is only for execution, but the model wrote `cat`, and that is
426
- // what UNIX_TO_WIN_HINTS knows about. Keying on the translated
427
- // word left `cat` invisible to the hint/block (observed: model
428
- // ran cat 4+ times in one session with no guidance).
429
- if (platform() === "win32") {
430
- const originalFirstWord = originalCommand.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
431
- if (originalFirstWord && originalFirstWord in UNIX_TO_WIN_HINTS) {
432
- if (code === 0) {
433
- FAILING_FIRST_WORDS.delete(originalFirstWord);
434
- }
435
- else {
436
- const failures = (FAILING_FIRST_WORDS.get(originalFirstWord) || 0) + 1;
437
- FAILING_FIRST_WORDS.set(originalFirstWord, failures);
438
- if (failures >= HARD_BLOCK_THRESHOLD) {
439
- output = `STOP using "${originalFirstWord}" — it does not work in this cmd.exe shell and has failed ${failures} times in a row. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
440
- }
441
- else {
442
- output = `${output}\n\nHint: "${originalFirstWord}" may not work on Windows. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
443
- }
444
- }
293
+ // On Windows, hint about Unix commands that don't work
294
+ if (platform() === "win32" && code !== 0) {
295
+ const firstWord = command
296
+ .trim()
297
+ .split(/\s+/)[0]
298
+ ?.split(/[\\/]/)
299
+ .pop();
300
+ const hint = firstWord ? UNIX_TO_WIN_HINTS[firstWord] : undefined;
301
+ if (hint) {
302
+ output = `${output}\n\nHint: "${firstWord}" may not work on Windows. ${hint}`;
445
303
  }
446
304
  }
447
305
  // Update audit log with result
@@ -449,18 +307,7 @@ export const bashTool = {
449
307
  logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), code === 0, `Working directory: ${workdir}, Output length: ${output.length}`);
450
308
  }
451
309
  const lines = output.split("\n");
452
- if (testRun && testRun.failed > 0) {
453
- // Keep the failure details: test runners print passing markers
454
- // first and the errors + summary at the very end. A short head
455
- // preview hides exactly what the model needs to fix the failures.
456
- const TEST_TAIL_LINES = 400;
457
- const kept = lines.slice(-TEST_TAIL_LINES);
458
- const skipped = lines.length - kept.length;
459
- output =
460
- (skipped > 0 ? `[... ${skipped} earlier lines omitted — failing tests below]\n` : "") +
461
- kept.join("\n");
462
- }
463
- else if (lines.length > MAX_PREVIEW_LINES) {
310
+ if (lines.length > MAX_PREVIEW_LINES) {
464
311
  output =
465
312
  lines.slice(0, MAX_PREVIEW_LINES).join("\n") +
466
313
  `\n... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
@@ -469,19 +316,13 @@ export const bashTool = {
469
316
  }
470
317
  // Still running — promote to a background process.
471
318
  const explicit = args.background === true;
472
- const preview = entry.log
473
- .filter((l) => l.trim() !== "")
474
- .slice(0, BG_OUTPUT_PREVIEW_LINES)
475
- .join("\n");
476
- let output = `${t("proc.started", {
319
+ const output = `${t("proc.started", {
477
320
  id: entry.id,
478
321
  pid: entry.pid,
479
322
  command,
480
- })}${explicit ? "" : `\n${t("proc.promoted_hint", { ms: settleMs })}`}`;
481
- if (preview) {
482
- output += `\n\n${t("proc.output_preview", { lines: preview })}`;
483
- }
484
- output += `\n${t("proc.manage_hint", { id: entry.id })}`;
323
+ })}${explicit ? "" : `\n${t("proc.promoted_hint", { ms: settleMs })}`}\n${t("proc.manage_hint", {
324
+ id: entry.id,
325
+ })}`;
485
326
  if (securityConfig?.logCommands) {
486
327
  logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), true, `Started in background: ${entry.id} (PID ${entry.pid})`);
487
328
  }
@@ -1,18 +1,15 @@
1
- import { BrowserSession } from "../modules/browser/session";
2
- import { DEFAULT_BROWSER_CONFIG } from "../modules/browser/types";
3
- import { t } from "../i18n/index";
4
- import { join } from "path";
1
+ import { BrowserSession } from '../modules/browser/session';
2
+ import { DEFAULT_BROWSER_CONFIG } from '../modules/browser/types';
3
+ import { t } from '../i18n/index';
4
+ import { join } from 'path';
5
5
  let session = null;
6
6
  function getSession(ctx) {
7
7
  if (!session) {
8
- const cookieDir = join(ctx.baseDir, ".mma", "browser");
8
+ const cookieDir = join(ctx.baseDir, '.mma', 'browser');
9
9
  session = new BrowserSession({
10
10
  ...DEFAULT_BROWSER_CONFIG,
11
11
  headless: ctx.config.browser?.headless ?? true,
12
12
  maxElements: ctx.config.browser?.maxElements ?? 30,
13
- maxContentChars: ctx.config.browser?.maxContentChars ?? 2500,
14
- maxConsoleEntries: ctx.config.browser?.maxConsoleEntries ?? 40,
15
- maxConsoleLineChars: ctx.config.browser?.maxConsoleLineChars ?? 400,
16
13
  navigationTimeout: ctx.config.browser?.navigationTimeout ?? 15000,
17
14
  viewportWidth: ctx.config.browser?.viewportWidth ?? 1280,
18
15
  viewportHeight: ctx.config.browser?.viewportHeight ?? 720,
@@ -22,80 +19,66 @@ function getSession(ctx) {
22
19
  return session;
23
20
  }
24
21
  export function formatScreenshotForTextModel(snapshot) {
25
- return `${t("tool.screenshot_unavailable")}\n\n${snapshot}`;
22
+ return `${t('tool.screenshot_unavailable')}\n\n${snapshot}`;
26
23
  }
27
24
  export function formatScreenshotResult(snapshot, screenshot, supportsVision) {
28
25
  if (!supportsVision) {
29
26
  return formatScreenshotForTextModel(snapshot);
30
27
  }
31
- const base64 = screenshot.toString("base64");
28
+ const base64 = screenshot.toString('base64');
32
29
  return `${snapshot}\n\n[Screenshot: data:image/png;base64,${base64}]`;
33
30
  }
34
31
  export function createBrowserTool() {
35
32
  return {
36
- name: "browser",
37
- tags: ["browser", "vision"],
33
+ name: 'browser',
34
+ tags: ['browser', 'vision'],
38
35
  description: [
39
- "Control a headless browser. Navigate pages, click elements, type text, scroll, take screenshots.",
40
- "Runs a real browser with JavaScript enabled — use it for JS-rendered sites (weather, dashboards, SPAs) where web_fetch/web_browse return empty content.",
41
- "Do NOT use it for search queries: search engines (Google, Yandex, Bing) block headless browsers with CAPTCHA/429/redirect pages use the web_search tool for searching.",
42
- "Each snapshot returns: a numbered list of interactive elements, the visible page text (Content section), browser console messages, and network errors.",
43
- "Use the Content section to understand what the page says; use element numbers as targets for click/type.",
44
- "Actions: open (url), click (target), type (target, text), scroll (direction), back, forward, screenshot, snapshot, close, wait (ms).",
45
- ].join(" "),
36
+ 'Control a headless browser. Navigate pages, click elements, type text, scroll, take screenshots.',
37
+ 'Returns a numbered list of interactive elements — use element numbers as targets for click/type.',
38
+ 'Actions: open (url), click (target), type (target, text), scroll (direction), back, forward, screenshot, snapshot, close, wait (ms).',
39
+ ].join(' '),
46
40
  parameters: {
47
- type: "object",
41
+ type: 'object',
48
42
  properties: {
49
43
  action: {
50
- type: "string",
51
- enum: [
52
- "open",
53
- "click",
54
- "type",
55
- "scroll",
56
- "back",
57
- "forward",
58
- "screenshot",
59
- "snapshot",
60
- "close",
61
- "wait",
62
- ],
63
- description: "Browser action to perform",
44
+ type: 'string',
45
+ enum: ['open', 'click', 'type', 'scroll', 'back', 'forward', 'screenshot', 'snapshot', 'close', 'wait'],
46
+ description: 'Browser action to perform',
64
47
  },
65
48
  url: {
66
- type: "string",
49
+ type: 'string',
67
50
  description: 'URL to open (for action "open")',
68
51
  },
69
52
  target: {
70
- type: "number",
53
+ type: 'number',
71
54
  description: 'Element number from snapshot (for action "click" or "type")',
72
55
  },
73
56
  text: {
74
- type: "string",
57
+ type: 'string',
75
58
  description: 'Text to type (for action "type")',
76
59
  },
77
60
  direction: {
78
- type: "string",
79
- enum: ["up", "down", "top", "bottom"],
61
+ type: 'string',
62
+ enum: ['up', 'down', 'top', 'bottom'],
80
63
  description: 'Scroll direction (for action "scroll", default "down")',
81
64
  },
82
65
  ms: {
83
- type: "number",
66
+ type: 'number',
84
67
  description: 'Milliseconds to wait (for action "wait", default 1000)',
85
68
  },
86
69
  },
87
- required: ["action"],
70
+ required: ['action'],
88
71
  },
89
72
  handler: async (ctx, args) => {
90
- const action = String(args.action || "");
73
+ const action = String(args.action || '');
91
74
  if (!action) {
92
- return { success: false, output: t("tool.action_required") };
75
+ return { success: false, output: t('tool.action_required') };
93
76
  }
94
77
  const s = getSession(ctx);
95
78
  const result = await s.execute(action, args);
96
- const supportsVision = ctx.config.model.includes("vision") ||
97
- ctx.config.model.includes("gpt-4o") ||
98
- ctx.config.model.includes("claude");
79
+ const supportsVision = ctx.config.model.includes('vision') ||
80
+ ctx.config.model.includes('gpt-4o') ||
81
+ ctx.config.model.includes('claude');
99
82
  if (result.screenshot) {
100
83
  return {
101
84
  success: result.success,
@@ -49,7 +49,9 @@ export class ToolExecutor {
49
49
  try {
50
50
  const proceed = await plugin.onBeforeTool(this.ctx, call);
51
51
  if (proceed === false || typeof proceed === "string") {
52
- const pluginName = plugin.name || plugin.constructor?.name || "unknown";
52
+ const pluginName = plugin.name ||
53
+ plugin.constructor?.name ||
54
+ "unknown";
53
55
  const reason = typeof proceed === "string" ? proceed : undefined;
54
56
  return {
55
57
  success: false,
@@ -73,16 +75,15 @@ export class ToolExecutor {
73
75
  result = await tool.handler(this.ctx, call.arguments);
74
76
  }
75
77
  else {
76
- const timeoutMs = tool.timeoutMs ?? TOOL_EXECUTION_TIMEOUT_MS;
77
78
  const timeoutPromise = new Promise((_, reject) => {
78
79
  setTimeout(() => {
79
80
  // Kill any long-running child process registered for this call.
80
81
  killByCallId(call.id);
81
82
  reject(new Error(t("tool.timeout", {
82
83
  name: call.name,
83
- seconds: Math.round(timeoutMs / 1000),
84
+ seconds: TOOL_EXECUTION_TIMEOUT_MS / 1000,
84
85
  })));
85
- }, timeoutMs);
86
+ }, TOOL_EXECUTION_TIMEOUT_MS);
86
87
  });
87
88
  const racePromises = [
88
89
  tool.handler(this.ctx, call.arguments),
@@ -1,18 +1,17 @@
1
- import { statSync, existsSync } from "fs";
2
- import { t } from "../i18n/index";
3
- import { isPathInScope } from "../modules/security/path-validator";
4
- import { safeResolvePath } from "./path-utils";
1
+ import { statSync, existsSync } from 'fs';
2
+ import { t } from '../i18n/index';
3
+ import { isPathInScope } from '../modules/security/path-validator';
4
+ import { safeResolvePath } from './path-utils';
5
5
  export const fileInfoTool = {
6
- name: "file_info",
7
- description: "Get metadata about a file or directory (size, creation date, modification date).",
8
- tags: ["file"],
9
- boundedOutput: true,
6
+ name: 'file_info',
7
+ description: 'Get metadata about a file or directory (size, creation date, modification date).',
8
+ tags: ['file'],
10
9
  parameters: {
11
- type: "object",
10
+ type: 'object',
12
11
  properties: {
13
- path: { type: "string", description: "File path" },
12
+ path: { type: 'string', description: 'File path' },
14
13
  },
15
- required: ["path"],
14
+ required: ['path'],
16
15
  },
17
16
  handler: async (ctx, args) => {
18
17
  const path = String(args.path);
@@ -22,13 +21,13 @@ export const fileInfoTool = {
22
21
  if (!scopeCheck.allowed) {
23
22
  return {
24
23
  success: false,
25
- output: t("file.path_not_allowed", {
24
+ output: t('file.path_not_allowed', {
26
25
  path: `${path} — ${scopeCheck.reason}`,
27
26
  }),
28
27
  };
29
28
  }
30
29
  if (!existsSync(resolved)) {
31
- return { success: false, output: t("file.not_found_short", { path }) };
30
+ return { success: false, output: t('file.not_found_short', { path }) };
32
31
  }
33
32
  const stat = statSync(resolved);
34
33
  return {
@@ -1,17 +1,10 @@
1
- /**
2
- * Filter tools by a tag set, always keeping `alwaysOn` tools. Used by MoE
3
- * expert routing and subagent tool_tags so structural tools (plan/todo/verify,
4
- * project_map, lsp_check, enable_tools) survive narrowing.
5
- */
6
1
  export function filterToolsByTags(tools, toolTags) {
7
2
  if (!toolTags || toolTags.length === 0)
8
3
  return tools;
9
4
  const tagSet = new Set(toolTags);
10
- return tools.filter((t) => {
11
- if (t.alwaysOn)
12
- return true;
5
+ return tools.filter(t => {
13
6
  if (!t.tags || t.tags.length === 0)
14
7
  return false;
15
- return t.tags.some((tag) => tagSet.has(tag));
8
+ return t.tags.some(tag => tagSet.has(tag));
16
9
  });
17
10
  }
@@ -1,26 +1,26 @@
1
- import { globSync } from "fs";
2
- import { t } from "../i18n/index";
3
- import { MAX_PREVIEW_LINES } from "./preview";
1
+ import { globSync } from 'fs';
2
+ import { t } from '../i18n/index';
3
+ import { MAX_PREVIEW_LINES } from './preview';
4
4
  export const globTool = {
5
- name: "glob",
5
+ name: 'glob',
6
6
  description: `Search for files matching a glob pattern. Shows up to ${MAX_PREVIEW_LINES} results by default. Uses standard glob syntax (e.g., **/*.ts, src/**/*.test.ts).`,
7
- tags: ["file", "code", "research"],
7
+ tags: ['file', 'code', 'research'],
8
8
  parameters: {
9
- type: "object",
9
+ type: 'object',
10
10
  properties: {
11
- pattern: { type: "string", description: "Glob pattern" },
11
+ pattern: { type: 'string', description: 'Glob pattern' },
12
12
  },
13
- required: ["pattern"],
13
+ required: ['pattern'],
14
14
  },
15
15
  handler: async (ctx, args) => {
16
16
  const pattern = String(args.pattern);
17
17
  const results = globSync(pattern, { cwd: ctx.baseDir });
18
18
  if (results.length === 0) {
19
- return { success: true, output: t("file.no_matches") };
19
+ return { success: true, output: t('file.no_matches') };
20
20
  }
21
21
  const truncated = results.length > MAX_PREVIEW_LINES
22
22
  ? `\n... (${results.length - MAX_PREVIEW_LINES} more files)`
23
- : "";
24
- return { success: true, output: results.slice(0, MAX_PREVIEW_LINES).join("\n") + truncated };
23
+ : '';
24
+ return { success: true, output: results.slice(0, MAX_PREVIEW_LINES).join('\n') + truncated };
25
25
  },
26
26
  };
@@ -31,7 +31,9 @@ export const grepTool = {
31
31
  },
32
32
  handler: async (ctx, args) => {
33
33
  const pattern = String(args.pattern);
34
- const searchPath = args.path ? resolve(ctx.baseDir, String(args.path)) : ctx.baseDir;
34
+ const searchPath = args.path
35
+ ? resolve(ctx.baseDir, String(args.path))
36
+ : ctx.baseDir;
35
37
  // Build rg arguments as an array to avoid shell interpretation of regex
36
38
  // metacharacters like |, (, ) — these are regex patterns, not shell operators.
37
39
  const rgArgs = ["-n", "--with-filename", pattern, searchPath];
@@ -15,11 +15,9 @@ import { processListTool } from "./process-list";
15
15
  import { processLogTool } from "./process-log";
16
16
  import { processKillTool } from "./process-kill";
17
17
  import { subagentTool } from "./subagent";
18
- import { chunkQueryTool } from "./chunk-query";
19
18
  import { webSearchTool } from "./web-search";
20
19
  import { webFetchTool } from "./web-fetch";
21
20
  import { webBrowseTool } from "./web-browse";
22
- import { downloadFileTool } from "./download-file";
23
21
  // questionTool and approveTool removed — interactive menus are too disruptive for small models.
24
22
  // The model should ask questions as plain text in its response instead.
25
23
  // import { questionTool } from './question'
@@ -32,11 +30,10 @@ import { rememberTool } from "./remember";
32
30
  import { recallTool } from "./recall";
33
31
  import { createBrowserTool } from "./browser";
34
32
  import { attachImageTool } from "./attach-image";
35
- import { enableToolsTool } from "./enable-tools";
36
33
  export { ToolRegistry, ToolExecutor };
37
34
  export { filterToolsByTags } from "./filter-tools";
38
- export { readFileTool, writeFileTool, editFileTool, globTool, grepTool, listDirTool, createDirTool, deleteFileTool, moveFileTool, fileInfoTool, bashTool, subagentTool, chunkQueryTool, processListTool, processLogTool, processKillTool, webSearchTool, webFetchTool, webBrowseTool, downloadFileTool, createLoadSkillTool, pipelineRunTool, mcpCallTool, searchHistoryTool, rememberTool, recallTool, createBrowserTool, attachImageTool, enableToolsTool, };
39
- export function registerAllTools(registry, skillsModule, opts) {
35
+ export { readFileTool, writeFileTool, editFileTool, globTool, grepTool, listDirTool, createDirTool, deleteFileTool, moveFileTool, fileInfoTool, bashTool, subagentTool, processListTool, processLogTool, processKillTool, webSearchTool, webFetchTool, webBrowseTool, createLoadSkillTool, pipelineRunTool, mcpCallTool, searchHistoryTool, rememberTool, recallTool, createBrowserTool, attachImageTool, };
36
+ export function registerAllTools(registry, skillsModule) {
40
37
  const tools = [
41
38
  readFileTool,
42
39
  writeFileTool,
@@ -50,25 +47,17 @@ export function registerAllTools(registry, skillsModule, opts) {
50
47
  fileInfoTool,
51
48
  bashTool,
52
49
  subagentTool,
53
- chunkQueryTool,
54
50
  processListTool,
55
51
  processLogTool,
56
52
  processKillTool,
57
53
  webSearchTool,
58
54
  webFetchTool,
59
55
  webBrowseTool,
60
- downloadFileTool,
61
56
  pipelineRunTool,
62
57
  mcpCallTool,
63
58
  searchHistoryTool,
64
59
  attachImageTool,
65
60
  ];
66
- // On-demand tool activation is the default. When explicitly disabled
67
- // (enableOnDemand: false), the tool is not registered at all — combined
68
- // with an empty defaultTags this restores the legacy all-tools behavior.
69
- if (opts?.enableOnDemand !== false) {
70
- tools.push(enableToolsTool);
71
- }
72
61
  if (skillsModule) {
73
62
  tools.push(createLoadSkillTool(skillsModule));
74
63
  }