micro-models-agent 0.28.17 → 0.29.1

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