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.
- package/bin/mma.mjs +41 -41
- package/dist/cli/commands.js +116 -3
- package/dist/cli/main.js +35 -8
- package/dist/cli/repl-commands.js +633 -0
- package/dist/cli/repl.js +110 -611
- package/dist/cli/setup.js +32 -12
- package/dist/config/config.js +46 -30
- package/dist/config/defaults.js +10 -1
- package/dist/config/security.js +15 -8
- package/dist/core/agent-moe.js +24 -12
- package/dist/core/agent.js +281 -47
- package/dist/core/bootstrap.js +52 -36
- package/dist/core/session-logger.js +35 -2
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +79 -15
- package/dist/i18n/index.js +12 -9
- package/dist/i18n/ru.json +79 -15
- package/dist/index.js +13 -13
- package/dist/llm/openai-compat.js +39 -10
- package/dist/logger/app-logger.js +83 -16
- package/dist/logger/file-log.js +151 -0
- package/dist/main.js +537 -284
- package/dist/modules/browser/bridge-server.mjs +113 -105
- package/dist/modules/browser/session.js +108 -60
- package/dist/modules/certification/cli.js +176 -0
- package/dist/modules/certification/fact-checker.js +84 -0
- package/dist/modules/certification/loader.js +111 -0
- package/dist/modules/certification/manifest.js +50 -0
- package/dist/modules/certification/runner.js +162 -0
- package/dist/modules/certification/scenarios.js +124 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/manager.js +119 -10
- package/dist/modules/execution/auditor.js +33 -39
- package/dist/modules/execution/index.js +8 -6
- package/dist/modules/execution/module.js +474 -32
- package/dist/modules/execution/moe-executor.js +97 -40
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +159 -0
- package/dist/modules/execution/planner.js +63 -13
- package/dist/modules/execution/stuck-detector.js +252 -39
- package/dist/modules/execution/tracker.js +21 -7
- package/dist/modules/execution/verifier.js +46 -17
- package/dist/modules/hallucination/confidence.js +7 -2
- package/dist/modules/hallucination/consistency.js +8 -42
- package/dist/modules/hallucination/detector.js +26 -21
- package/dist/modules/hallucination/factual.js +170 -150
- package/dist/modules/hallucination/index.js +5 -4
- package/dist/modules/hallucination/js-identifiers.js +72 -0
- package/dist/modules/hallucination/llm-judge.js +103 -0
- package/dist/modules/index.js +5 -5
- package/dist/modules/lsp/client.js +235 -0
- package/dist/modules/lsp/config.js +81 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +68 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +8 -2
- package/dist/modules/memory/store.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +143 -38
- package/dist/modules/processes/index.js +1 -2
- package/dist/modules/processes/registry.js +125 -35
- package/dist/modules/processes/runner.js +9 -110
- package/dist/modules/security/audit-log.js +30 -10
- package/dist/modules/security/command-validator.js +42 -16
- package/dist/modules/security/content-scanner.js +9 -8
- package/dist/modules/security/network-validator.js +2 -2
- package/dist/modules/security/path-validator.js +64 -10
- package/dist/modules/security/security-policies.js +221 -67
- package/dist/modules/security/session-encryption.js +42 -25
- package/dist/modules/session/manager.js +15 -10
- package/dist/modules/session/store.js +62 -8
- package/dist/modules/skills/index.js +2 -3
- package/dist/modules/skills/module.js +10 -23
- package/dist/tools/bash.js +287 -90
- package/dist/tools/create-dir.js +0 -1
- package/dist/tools/delete-file.js +0 -1
- package/dist/tools/edit-file.js +10 -8
- package/dist/tools/executor.js +57 -7
- package/dist/tools/grep-tool.js +51 -29
- package/dist/tools/index.js +55 -40
- package/dist/tools/load-skill.js +14 -18
- package/dist/tools/move-file.js +3 -2
- package/dist/tools/pipeline-run.js +1 -1
- package/dist/tools/read-file.js +15 -5
- package/dist/tools/search-history.js +42 -22
- package/dist/tools/subagent.js +21 -12
- package/dist/tools/web-browse.js +54 -25
- package/dist/tools/web-fetch.js +60 -34
- package/dist/tools/web-search.js +39 -20
- package/dist/tools/write-file.js +13 -10
- package/dist/ui/diff.js +9 -16
- package/dist/ui/renderer.js +69 -6
- package/package.json +48 -45
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/processes/detect.js +0 -34
- 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
|
-
|
|
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: [
|
|
8
|
-
dangerousOperators: [
|
|
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" ||
|
|
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,
|
|
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
|
|
79
|
-
|
|
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 =
|
|
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 = [
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 + "/") ||
|
|
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 + "/") &&
|
|
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 + "/") &&
|
|
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
|
|
202
|
+
const relativePath = resolvedTargetPath
|
|
203
|
+
.slice(resolvedBaseDir.length + 1)
|
|
204
|
+
.replace(/\\/g, "/");
|
|
151
205
|
return regex.test(relativePath);
|
|
152
206
|
}
|
|
153
207
|
// Exact match
|