micro-models-agent 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/bin/mma.mjs +41 -41
  2. package/dist/cli/commands.js +116 -3
  3. package/dist/cli/main.js +35 -8
  4. package/dist/cli/repl-commands.js +633 -0
  5. package/dist/cli/repl.js +110 -611
  6. package/dist/cli/setup.js +32 -12
  7. package/dist/config/config.js +46 -30
  8. package/dist/config/defaults.js +10 -1
  9. package/dist/config/security.js +15 -8
  10. package/dist/core/agent-moe.js +24 -12
  11. package/dist/core/agent.js +281 -47
  12. package/dist/core/bootstrap.js +52 -36
  13. package/dist/core/session-logger.js +35 -2
  14. package/dist/core/workspace.js +76 -0
  15. package/dist/i18n/en.json +79 -15
  16. package/dist/i18n/index.js +12 -9
  17. package/dist/i18n/ru.json +79 -15
  18. package/dist/index.js +13 -13
  19. package/dist/llm/openai-compat.js +39 -10
  20. package/dist/logger/app-logger.js +83 -16
  21. package/dist/logger/file-log.js +151 -0
  22. package/dist/main.js +537 -284
  23. package/dist/modules/browser/bridge-server.mjs +113 -105
  24. package/dist/modules/browser/session.js +108 -60
  25. package/dist/modules/certification/cli.js +176 -0
  26. package/dist/modules/certification/fact-checker.js +84 -0
  27. package/dist/modules/certification/loader.js +111 -0
  28. package/dist/modules/certification/manifest.js +50 -0
  29. package/dist/modules/certification/runner.js +162 -0
  30. package/dist/modules/certification/scenarios.js +124 -0
  31. package/dist/modules/certification/types.js +1 -0
  32. package/dist/modules/context/manager.js +119 -10
  33. package/dist/modules/execution/auditor.js +33 -39
  34. package/dist/modules/execution/index.js +8 -6
  35. package/dist/modules/execution/module.js +474 -32
  36. package/dist/modules/execution/moe-executor.js +97 -40
  37. package/dist/modules/execution/plan-coverage.js +68 -0
  38. package/dist/modules/execution/plan-persister.js +46 -0
  39. package/dist/modules/execution/plan-store.js +159 -0
  40. package/dist/modules/execution/planner.js +63 -13
  41. package/dist/modules/execution/stuck-detector.js +252 -39
  42. package/dist/modules/execution/tracker.js +21 -7
  43. package/dist/modules/execution/verifier.js +46 -17
  44. package/dist/modules/hallucination/confidence.js +7 -2
  45. package/dist/modules/hallucination/consistency.js +8 -42
  46. package/dist/modules/hallucination/detector.js +26 -21
  47. package/dist/modules/hallucination/factual.js +170 -150
  48. package/dist/modules/hallucination/index.js +5 -4
  49. package/dist/modules/hallucination/js-identifiers.js +72 -0
  50. package/dist/modules/hallucination/llm-judge.js +103 -0
  51. package/dist/modules/index.js +5 -5
  52. package/dist/modules/lsp/client.js +235 -0
  53. package/dist/modules/lsp/config.js +81 -0
  54. package/dist/modules/lsp/index.js +3 -0
  55. package/dist/modules/lsp/module.js +68 -0
  56. package/dist/modules/lsp/types.js +1 -0
  57. package/dist/modules/mcp/client.js +8 -2
  58. package/dist/modules/memory/store.js +4 -0
  59. package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
  60. package/dist/modules/processes/index.js +1 -2
  61. package/dist/modules/processes/registry.js +125 -35
  62. package/dist/modules/processes/runner.js +9 -110
  63. package/dist/modules/security/audit-log.js +30 -10
  64. package/dist/modules/security/command-validator.js +42 -16
  65. package/dist/modules/security/content-scanner.js +9 -8
  66. package/dist/modules/security/network-validator.js +2 -2
  67. package/dist/modules/security/path-validator.js +64 -10
  68. package/dist/modules/security/security-policies.js +221 -67
  69. package/dist/modules/security/session-encryption.js +42 -25
  70. package/dist/modules/session/manager.js +15 -10
  71. package/dist/modules/session/store.js +62 -8
  72. package/dist/modules/skills/index.js +2 -3
  73. package/dist/modules/skills/module.js +10 -23
  74. package/dist/tools/bash.js +287 -90
  75. package/dist/tools/create-dir.js +0 -1
  76. package/dist/tools/delete-file.js +0 -1
  77. package/dist/tools/edit-file.js +10 -8
  78. package/dist/tools/executor.js +57 -7
  79. package/dist/tools/grep-tool.js +51 -29
  80. package/dist/tools/index.js +55 -40
  81. package/dist/tools/load-skill.js +14 -18
  82. package/dist/tools/move-file.js +3 -2
  83. package/dist/tools/pipeline-run.js +1 -1
  84. package/dist/tools/read-file.js +15 -5
  85. package/dist/tools/search-history.js +42 -22
  86. package/dist/tools/subagent.js +21 -12
  87. package/dist/tools/web-browse.js +54 -25
  88. package/dist/tools/web-fetch.js +60 -34
  89. package/dist/tools/web-search.js +39 -20
  90. package/dist/tools/write-file.js +13 -10
  91. package/dist/ui/diff.js +9 -16
  92. package/dist/ui/renderer.js +69 -6
  93. package/package.json +48 -45
  94. package/dist/modules/context/history.js +0 -15
  95. package/dist/modules/processes/detect.js +0 -34
  96. package/dist/modules/skills/matcher.js +0 -27
@@ -1,11 +1,25 @@
1
1
  import { DEFAULT_SECURITY_CONFIG } from "../../config/security";
2
2
  // Fallback in case DEFAULT_SECURITY_CONFIG is not available
3
3
  const FALLBACK_BASH_CONFIG = {
4
- blacklist: ['rm', 'dd', 'chmod', 'wget', 'curl', 'scp', 'ssh', 'nc', 'netcat', 'powershell', 'pwsh', 'cmd'],
4
+ enabled: false,
5
+ blacklist: [
6
+ "rm",
7
+ "dd",
8
+ "chmod",
9
+ "wget",
10
+ "curl",
11
+ "scp",
12
+ "ssh",
13
+ "nc",
14
+ "netcat",
15
+ "powershell",
16
+ "pwsh",
17
+ "cmd",
18
+ ],
5
19
  whitelist: [],
6
20
  blockDangerousFlags: false,
7
- dangerousFlags: ['--force', '-rf', '--no-preserve-root'],
8
- dangerousOperators: ['>', '>>', '2>', '2>>', '|', '&&', '||', ';', '`'],
21
+ dangerousFlags: ["--force", "-rf", "--no-preserve-root"],
22
+ dangerousOperators: [">", ">>", "2>", "2>>", "|", "&&", "||", ";", "`"],
9
23
  logCommands: true,
10
24
  };
11
25
  export const DEFAULT_BASH_CONFIG = DEFAULT_SECURITY_CONFIG?.bash || FALLBACK_BASH_CONFIG;
@@ -32,7 +46,11 @@ function extractBaseCommand(trimmed) {
32
46
  i++;
33
47
  continue;
34
48
  }
35
- if (tok === "sudo" || tok === "env" || tok === "command" || tok === "exec" || tok === "nohup") {
49
+ if (tok === "sudo" ||
50
+ tok === "env" ||
51
+ tok === "command" ||
52
+ tok === "exec" ||
53
+ tok === "nohup") {
36
54
  i++;
37
55
  continue;
38
56
  }
@@ -45,7 +63,7 @@ function extractBaseCommand(trimmed) {
45
63
  const parts = raw.split(/[\\/]/);
46
64
  const basename = parts[parts.length - 1] || raw;
47
65
  // Strip .exe/.cmd/.bat extensions for blacklist matching (e.g. powershell.exe → powershell)
48
- return basename.replace(/\.(exe|cmd|bat)$/i, '') || basename;
66
+ return basename.replace(/\.(exe|cmd|bat)$/i, "") || basename;
49
67
  }
50
68
  /**
51
69
  * Check if a shell operator appears as a standalone token in the command.
@@ -56,16 +74,16 @@ function extractBaseCommand(trimmed) {
56
74
  * "2>" does NOT match "2>>" (append stderr)
57
75
  */
58
76
  function containsOperator(command, op) {
59
- const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
77
+ const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
60
78
  // For single-char operators that are prefixes of multi-char ones,
61
79
  // ensure they don't match when part of a longer sequence.
62
80
  // Note: "&" is no longer in dangerousOperators (&& is legitimate),
63
81
  // but keep the special case for defense-in-depth if manually added.
64
- if (op === '&')
82
+ if (op === "&")
65
83
  return /(?<!&)&(?!&)/.test(command);
66
- if (op === '|')
84
+ if (op === "|")
67
85
  return /(?<!\|)\|(?!\|)/.test(command);
68
- if (op === '>')
86
+ if (op === ">")
69
87
  return /(?<!>)>(?!>)/.test(command);
70
88
  // For everything else (including multi-char like ">>", "2>", "2>>", ";", "`"),
71
89
  // match literally.
@@ -75,11 +93,17 @@ function containsOperator(command, op) {
75
93
  * Check if a command is allowed based on security configuration
76
94
  */
77
95
  export function isCommandAllowed(command, securityConfig) {
78
- // If no security config, allow everything (backward compatibility)
79
- let config = securityConfig || DEFAULT_BASH_CONFIG;
96
+ // If security is disabled, allow everything
97
+ if (!securityConfig?.enabled) {
98
+ return { allowed: true, sanitizedCommand: command };
99
+ }
80
100
  // Ensure config has all required fields
101
+ let config = securityConfig;
81
102
  if (!config.blacklist || !Array.isArray(config.blacklist)) {
82
- config = FALLBACK_BASH_CONFIG;
103
+ config = {
104
+ ...FALLBACK_BASH_CONFIG,
105
+ enabled: config.enabled ?? FALLBACK_BASH_CONFIG.enabled,
106
+ };
83
107
  }
84
108
  const trimmedCommand = command.trim();
85
109
  if (!trimmedCommand) {
@@ -87,7 +111,7 @@ export function isCommandAllowed(command, securityConfig) {
87
111
  }
88
112
  // Shell wrappers are always blocked — they bypass all command validation.
89
113
  // The agent should use the bash tool directly, not shell interpreters.
90
- const ALWAYS_BLOCKED = ['powershell', 'pwsh', 'cmd'];
114
+ const ALWAYS_BLOCKED = ["powershell", "pwsh", "cmd"];
91
115
  const baseForShellCheck = extractBaseCommand(trimmedCommand);
92
116
  if (ALWAYS_BLOCKED.includes(baseForShellCheck)) {
93
117
  return {
@@ -126,7 +150,9 @@ export function isCommandAllowed(command, securityConfig) {
126
150
  };
127
151
  }
128
152
  // Also check the raw first token in case it's a simple name
129
- const rawFirst = trimmedCommand.split(/\s+/)[0].replace(/\.(exe|cmd|bat)$/i, '');
153
+ const rawFirst = trimmedCommand
154
+ .split(/\s+/)[0]
155
+ .replace(/\.(exe|cmd|bat)$/i, "");
130
156
  if (rawFirst !== baseCommand && config.blacklist.includes(rawFirst)) {
131
157
  return {
132
158
  allowed: false,
@@ -170,7 +196,7 @@ export function sanitizeCommandForLog(command) {
170
196
  ];
171
197
  let sanitized = command;
172
198
  for (const pattern of patterns) {
173
- if (pattern.toString().includes('($1')) {
199
+ if (pattern.toString().includes("($1")) {
174
200
  // For patterns with capture groups, use the group in replacement
175
201
  sanitized = sanitized.replace(pattern, (match, p1) => {
176
202
  return `${p1}=[REDACTED]`;
@@ -178,7 +204,7 @@ export function sanitizeCommandForLog(command) {
178
204
  }
179
205
  else {
180
206
  // For simple patterns, just replace with [REDACTED]
181
- sanitized = sanitized.replace(pattern, '[REDACTED]');
207
+ sanitized = sanitized.replace(pattern, "[REDACTED]");
182
208
  }
183
209
  }
184
210
  return sanitized;
@@ -1,5 +1,9 @@
1
1
  /**
2
- * Default dangerous patterns to scan for in file content
2
+ * Default dangerous patterns to scan for in file content.
3
+ * NOTE: only genuinely dangerous constructs are listed. Legitimate runtime
4
+ * APIs (process.exit, setTimeout/setInterval, template literals `${}`,
5
+ * backtick strings) are NOT scanned — they are valid JS/TS and blocking
6
+ * them breaks ordinary CLI code (false positives).
3
7
  */
4
8
  const DEFAULT_DANGEROUS_PATTERNS = [
5
9
  /eval\(/gi,
@@ -11,9 +15,6 @@ const DEFAULT_DANGEROUS_PATTERNS = [
11
15
  /require\('net'\).createServer/gi,
12
16
  /require\('fs'\).writeFileSync/gi,
13
17
  /require\('fs'\).unlinkSync/gi,
14
- /process\.exit\(/gi,
15
- /setInterval\(/gi,
16
- /setTimeout\(/gi,
17
18
  /while\(true\)/gi,
18
19
  /for\(;;\)/gi,
19
20
  /rm -rf/gi,
@@ -21,8 +22,6 @@ const DEFAULT_DANGEROUS_PATTERNS = [
21
22
  /chmod 777/gi,
22
23
  /wget.*\|.*sh/gi,
23
24
  /curl.*\|.*sh/gi,
24
- /\$\{/gi,
25
- /`[^`]+`/gi,
26
25
  ];
27
26
  /**
28
27
  * Scan file content for dangerous patterns
@@ -33,7 +32,9 @@ export function scanContent(content, filePath, config) {
33
32
  return { allowed: true };
34
33
  }
35
34
  // Use default patterns if none provided
36
- const patterns = config.dangerousPatterns?.length ? config.dangerousPatterns : DEFAULT_DANGEROUS_PATTERNS;
35
+ const patterns = config.dangerousPatterns?.length
36
+ ? config.dangerousPatterns
37
+ : DEFAULT_DANGEROUS_PATTERNS;
37
38
  const matches = [];
38
39
  for (const pattern of patterns) {
39
40
  const match = content.match(pattern);
@@ -44,7 +45,7 @@ export function scanContent(content, filePath, config) {
44
45
  if (matches.length > 0) {
45
46
  return {
46
47
  allowed: false,
47
- reason: `Content contains dangerous patterns: ${matches.slice(0, 3).join(", ")}`,
48
+ reason: `Content contains dangerous patterns in ${filePath}: ${matches.slice(0, 3).join(", ")}`,
48
49
  matches,
49
50
  };
50
51
  }
@@ -3,8 +3,8 @@ import { sanitizeLogMessage } from "./data-sanitizer";
3
3
  * Check if a URL is allowed based on security configuration
4
4
  */
5
5
  export function isUrlAllowed(url, securityConfig) {
6
- // If no security config, allow everything (backward compatibility)
7
- if (!securityConfig) {
6
+ // If security is disabled, allow everything
7
+ if (!securityConfig?.enabled) {
8
8
  return { allowed: true };
9
9
  }
10
10
  try {
@@ -8,13 +8,36 @@ export function isPathInScope(baseDir, targetPath, scope, securityConfig) {
8
8
  const targetResolved = resolve(baseDir, normalize(targetPath));
9
9
  // First, check that path is within baseDir
10
10
  if (!targetResolved.startsWith(baseResolved)) {
11
- return { allowed: false, reason: "Path is outside the base working directory" };
11
+ return {
12
+ allowed: false,
13
+ reason: "Path is outside the base working directory",
14
+ };
15
+ }
16
+ // If security is disabled, skip pattern validation
17
+ if (!securityConfig?.enabled) {
18
+ // Still enforce subagent scope if present
19
+ if (scope) {
20
+ const isReadScope = scope.read_only_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
21
+ const isWriteScope = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
22
+ if (isWriteScope)
23
+ return { allowed: true };
24
+ if (isReadScope)
25
+ return { allowed: true, reason: "Read-only file" };
26
+ return {
27
+ allowed: false,
28
+ reason: "Path is not within the allowed scope for this sub-agent",
29
+ };
30
+ }
31
+ return { allowed: true };
12
32
  }
13
33
  // Check global denied patterns
14
34
  if (securityConfig?.denied?.length) {
15
35
  for (const pattern of securityConfig.denied) {
16
36
  if (matchesGlobPattern(targetResolved, baseDir, pattern)) {
17
- return { allowed: false, reason: `Path matches denied pattern: ${pattern}` };
37
+ return {
38
+ allowed: false,
39
+ reason: `Path matches denied pattern: ${pattern}`,
40
+ };
18
41
  }
19
42
  }
20
43
  }
@@ -28,7 +51,10 @@ export function isPathInScope(baseDir, targetPath, scope, securityConfig) {
28
51
  }
29
52
  }
30
53
  if (!isAllowed) {
31
- return { allowed: false, reason: "Path does not match any allowed pattern" };
54
+ return {
55
+ allowed: false,
56
+ reason: "Path does not match any allowed pattern",
57
+ };
32
58
  }
33
59
  }
34
60
  // Check tool-specific scope (for subagents)
@@ -54,13 +80,33 @@ export function isPathWritable(baseDir, targetPath, scope, securityConfig) {
54
80
  const targetResolved = resolve(baseDir, normalize(targetPath));
55
81
  // First, check that path is within baseDir
56
82
  if (!targetResolved.startsWith(baseResolved)) {
57
- return { allowed: false, reason: "Path is outside the base working directory" };
83
+ return {
84
+ allowed: false,
85
+ reason: "Path is outside the base working directory",
86
+ };
87
+ }
88
+ // If security is disabled, skip pattern validation
89
+ if (!securityConfig?.enabled) {
90
+ // Still enforce subagent scope if present
91
+ if (scope) {
92
+ const isWriteScope = scope.allowed_files.some((f) => targetResolved.startsWith(resolve(baseDir, f)));
93
+ if (isWriteScope)
94
+ return { allowed: true };
95
+ return {
96
+ allowed: false,
97
+ reason: "Path is not within the allowed scope for this sub-agent",
98
+ };
99
+ }
100
+ return { allowed: true };
58
101
  }
59
102
  // Check global denied patterns
60
103
  if (securityConfig?.denied?.length) {
61
104
  for (const pattern of securityConfig.denied) {
62
105
  if (matchesGlobPattern(targetResolved, baseDir, pattern)) {
63
- return { allowed: false, reason: `Path matches denied pattern: ${pattern}` };
106
+ return {
107
+ allowed: false,
108
+ reason: `Path matches denied pattern: ${pattern}`,
109
+ };
64
110
  }
65
111
  }
66
112
  }
@@ -74,7 +120,10 @@ export function isPathWritable(baseDir, targetPath, scope, securityConfig) {
74
120
  }
75
121
  }
76
122
  if (!isAllowed) {
77
- return { allowed: false, reason: "Path does not match any allowed pattern" };
123
+ return {
124
+ allowed: false,
125
+ reason: "Path does not match any allowed pattern",
126
+ };
78
127
  }
79
128
  }
80
129
  // Check tool-specific scope (for subagents)
@@ -105,7 +154,8 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
105
154
  return true;
106
155
  }
107
156
  // Check if target is inside the directory
108
- if (resolvedTargetPath.startsWith(resolvedDirPattern + "/") || resolvedTargetPath.startsWith(resolvedDirPattern + "\\")) {
157
+ if (resolvedTargetPath.startsWith(resolvedDirPattern + "/") ||
158
+ resolvedTargetPath.startsWith(resolvedDirPattern + "\\")) {
109
159
  return true;
110
160
  }
111
161
  return false;
@@ -116,7 +166,8 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
116
166
  const prefix = parts[0];
117
167
  const suffix = parts[1];
118
168
  const resolvedPrefix = resolve(baseDir, prefix);
119
- if (!resolvedTargetPath.startsWith(resolvedPrefix + "/") && !resolvedTargetPath.startsWith(resolvedPrefix + "\\")) {
169
+ if (!resolvedTargetPath.startsWith(resolvedPrefix + "/") &&
170
+ !resolvedTargetPath.startsWith(resolvedPrefix + "\\")) {
120
171
  return false;
121
172
  }
122
173
  const remainingPath = resolvedTargetPath.slice(resolvedPrefix.length);
@@ -132,7 +183,8 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
132
183
  const prefix = parts[0];
133
184
  const suffix = parts[1];
134
185
  const resolvedPrefix = resolve(baseDir, prefix);
135
- if (!resolvedTargetPath.startsWith(resolvedPrefix + "/") && !resolvedTargetPath.startsWith(resolvedPrefix + "\\")) {
186
+ if (!resolvedTargetPath.startsWith(resolvedPrefix + "/") &&
187
+ !resolvedTargetPath.startsWith(resolvedPrefix + "\\")) {
136
188
  return false;
137
189
  }
138
190
  if (!suffix) {
@@ -147,7 +199,9 @@ function matchesGlobPattern(targetPath, baseDir, pattern) {
147
199
  .replace(/\?/g, ".")
148
200
  .replace(/\./g, "\\.");
149
201
  const regex = new RegExp(`^${regexPattern}$`);
150
- const relativePath = resolvedTargetPath.slice(resolvedBaseDir.length + 1).replace(/\\/g, "/");
202
+ const relativePath = resolvedTargetPath
203
+ .slice(resolvedBaseDir.length + 1)
204
+ .replace(/\\/g, "/");
151
205
  return regex.test(relativePath);
152
206
  }
153
207
  // Exact match