micro-models-agent 0.43.3 → 0.45.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/README.md +312 -312
- package/bin/mma.mjs +41 -41
- package/dist/main.js +333 -66
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -333
- package/dist/cli/completer.js +0 -168
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -140
- package/dist/cli/repl-commands.js +0 -633
- package/dist/cli/repl.js +0 -486
- package/dist/cli/security-commands.js +0 -166
- package/dist/cli/setup.js +0 -249
- package/dist/config/config.js +0 -202
- package/dist/config/defaults.js +0 -100
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -200
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -110
- package/dist/core/agent.js +0 -695
- package/dist/core/bootstrap.js +0 -337
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -55
- package/dist/core/session-logger.js +0 -155
- package/dist/core/types.js +0 -1
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -525
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -525
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -144
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -353
- package/dist/llm/orchestrator.js +0 -194
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -143
- package/dist/logger/file-log.js +0 -151
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/browser/actions.js +0 -46
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/index.js +0 -5
- package/dist/modules/browser/module.js +0 -28
- package/dist/modules/browser/session.js +0 -335
- package/dist/modules/browser/snapshot.js +0 -114
- package/dist/modules/browser/types.js +0 -9
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -349
- package/dist/modules/execution/auditor.js +0 -66
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -779
- package/dist/modules/execution/moe-executor.js +0 -266
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -159
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -85
- package/dist/modules/execution/stuck-detector.js +0 -347
- package/dist/modules/execution/tracker.js +0 -67
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -178
- package/dist/modules/hallucination/confidence.js +0 -59
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -46
- package/dist/modules/hallucination/factual.js +0 -190
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -38
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -192
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -399
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -146
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -48
- package/dist/modules/memory/search.js +0 -40
- package/dist/modules/memory/store.js +0 -69
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -53
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -226
- package/dist/modules/plugins/builtin/notify.js +0 -8
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -28
- package/dist/modules/plugins/manager.js +0 -161
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -238
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/registry.js +0 -45
- package/dist/modules/security/audit-log.js +0 -136
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -211
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -97
- package/dist/modules/security/encryption.js +0 -240
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -79
- package/dist/modules/security/path-validator.js +0 -209
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -547
- package/dist/modules/security/session-encryption.js +0 -210
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -172
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -228
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/module.js +0 -130
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -32
- package/dist/modules/updater/index.js +0 -1
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -32
- package/dist/tools/attach-image.js +0 -89
- package/dist/tools/bash.js +0 -337
- package/dist/tools/browser.js +0 -97
- package/dist/tools/create-dir.js +0 -55
- package/dist/tools/delete-file.js +0 -62
- package/dist/tools/edit-file.js +0 -79
- package/dist/tools/executor.js +0 -145
- package/dist/tools/file-info.js +0 -45
- package/dist/tools/filter-tools.js +0 -10
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -86
- package/dist/tools/index.js +0 -67
- package/dist/tools/list-dir.js +0 -47
- package/dist/tools/load-skill.js +0 -44
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -85
- package/dist/tools/path-utils.js +0 -51
- package/dist/tools/pipeline-run.js +0 -144
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -29
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -41
- package/dist/tools/question.js +0 -142
- package/dist/tools/read-file.js +0 -83
- package/dist/tools/recall.js +0 -110
- package/dist/tools/registry.js +0 -36
- package/dist/tools/remember.js +0 -67
- package/dist/tools/scope-check.js +0 -30
- package/dist/tools/search-history.js +0 -84
- package/dist/tools/subagent.js +0 -151
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -86
- package/dist/tools/web-fetch.js +0 -98
- package/dist/tools/web-search.js +0 -78
- package/dist/tools/write-file.js +0 -83
- package/dist/ui/box.js +0 -81
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/renderer.js +0 -204
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
package/dist/tools/bash.js
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import { isCommandAllowed, sanitizeCommandForLog, } from "../modules/security/command-validator";
|
|
2
|
-
import { logBashCommand, logSecurityBlock, } from "../modules/security/audit-log";
|
|
3
|
-
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
4
|
-
import { DEFAULT_SECURITY_CONFIG } from "../config/security";
|
|
5
|
-
import { processRegistry, registerKillable, unregisterKillable, } from "../modules/processes";
|
|
6
|
-
import { t } from "../i18n/index";
|
|
7
|
-
import { platform } from "os";
|
|
8
|
-
import { MAX_PREVIEW_LINES } from "./preview";
|
|
9
|
-
/**
|
|
10
|
-
* A command still running after this window is promoted to the background.
|
|
11
|
-
* The decision is based on process *behavior* (still alive), not on matching
|
|
12
|
-
* words in the command text — a command that happens to mention "vite",
|
|
13
|
-
* "server", etc. runs normally, and any genuinely long-running command is
|
|
14
|
-
* caught regardless of how it is written.
|
|
15
|
-
*/
|
|
16
|
-
export const BASH_GRACE_MS = 5000;
|
|
17
|
-
/** Short window for explicit `background: true` — surfaces immediate spawn failures (bad cwd, missing shell). */
|
|
18
|
-
const SPAWN_SETTLE_MS = 100;
|
|
19
|
-
let bashGraceMs = BASH_GRACE_MS;
|
|
20
|
-
/** Test hook: override the auto-background grace window. */
|
|
21
|
-
export function setBashGraceMs(ms) {
|
|
22
|
-
bashGraceMs = ms;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Detect a file write via `echo/printf ... > file` — common model habit that
|
|
26
|
-
* breaks in cmd.exe: single quotes are not grouping quotes, `>` only applies
|
|
27
|
-
* to the LAST line of a multi-line command, and double quotes inside the text
|
|
28
|
-
* split the command. Returns the target filename or null.
|
|
29
|
-
*/
|
|
30
|
-
export function extractEchoFileWrite(command) {
|
|
31
|
-
if (!/^\s*(?:echo|printf)\b/i.test(command))
|
|
32
|
-
return null;
|
|
33
|
-
const m = command.match(/[>»]{1,2}\s*"?([^"'\s&|]+)"?/i);
|
|
34
|
-
if (!m)
|
|
35
|
-
return null;
|
|
36
|
-
return m[1].replace(/["'']$/g, "");
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* cmd.exe uses `&` as the command separator, not `;` (bash). The model
|
|
40
|
-
* regularly chains commands with `;` — without translation cmd passes the
|
|
41
|
-
* `;` to the first command as an argument (e.g. `node x.ts;` → ENOENT for
|
|
42
|
-
* "x.ts;"). Replace `;` with `&` only OUTSIDE double-quoted strings so
|
|
43
|
-
* `echo "a;b"` stays intact. (Single quotes are not special in cmd.)
|
|
44
|
-
*/
|
|
45
|
-
export function translateSemicolonsForCmd(command) {
|
|
46
|
-
let out = "";
|
|
47
|
-
let inQuotes = false;
|
|
48
|
-
for (let i = 0; i < command.length; i++) {
|
|
49
|
-
const ch = command[i];
|
|
50
|
-
if (ch === '"') {
|
|
51
|
-
inQuotes = !inQuotes;
|
|
52
|
-
out += ch;
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
out += ch === ";" && !inQuotes ? "&" : ch;
|
|
56
|
-
}
|
|
57
|
-
return out;
|
|
58
|
-
}
|
|
59
|
-
function adaptCommandForWindows(command) {
|
|
60
|
-
if (platform() !== "win32")
|
|
61
|
-
return command;
|
|
62
|
-
// The model sometimes appends `|| true` (bash error-suppression idiom)
|
|
63
|
-
// which PowerShell doesn't understand. Replace with `; exit 0` which
|
|
64
|
-
// forces a successful exit code regardless of the previous command's result.
|
|
65
|
-
if (/\|\|\s*true\b/.test(command)) {
|
|
66
|
-
command = command.replace(/\s*\|\|\s*true\b/g, "; exit 0");
|
|
67
|
-
}
|
|
68
|
-
// Windows mkdir does not support -p flag, but creates intermediate dirs by default
|
|
69
|
-
const trimmed = command.trim();
|
|
70
|
-
if (trimmed.startsWith("mkdir -p ")) {
|
|
71
|
-
return trimmed.replace(/^mkdir -p /, "mkdir ");
|
|
72
|
-
}
|
|
73
|
-
if (trimmed === "mkdir -p" || trimmed.startsWith("mkdir -p ")) {
|
|
74
|
-
return trimmed.replace(/mkdir -p/g, "mkdir");
|
|
75
|
-
}
|
|
76
|
-
// Translate simple Unix commands to their cmd.exe equivalents. Only the
|
|
77
|
-
// leading word is rewritten; flags are passed through (ls -la → dir -la,
|
|
78
|
-
// which cmd tolerates). Pipe-using forms are left alone — they would break.
|
|
79
|
-
const firstWord = trimmed.split(/\s+/)[0]?.split(/[\\/]/).pop();
|
|
80
|
-
const translated = firstWord ? UNIX_TO_WIN_TRANSLATE[firstWord] : undefined;
|
|
81
|
-
if (translated &&
|
|
82
|
-
!trimmed.includes("|") &&
|
|
83
|
-
!trimmed.includes(">") &&
|
|
84
|
-
!trimmed.includes("&&") &&
|
|
85
|
-
!trimmed.includes(";")) {
|
|
86
|
-
return trimmed.replace(firstWord, translated);
|
|
87
|
-
}
|
|
88
|
-
// No encoding adaptation needed — registry.ts handles UTF-8/OEM decoding
|
|
89
|
-
return translateSemicolonsForCmd(command);
|
|
90
|
-
}
|
|
91
|
-
/** Common Unix → Windows command mapping for error hints. */
|
|
92
|
-
const UNIX_TO_WIN_HINTS = {
|
|
93
|
-
ls: "Use the list_dir tool instead.",
|
|
94
|
-
pwd: "Use the file_info tool instead.",
|
|
95
|
-
cat: "Use the read_file tool instead.",
|
|
96
|
-
cp: "Use the move_file tool instead.",
|
|
97
|
-
mv: "Use the move_file tool instead.",
|
|
98
|
-
rm: "Use the delete_file tool instead.",
|
|
99
|
-
grep: "Use the grep tool instead.",
|
|
100
|
-
chmod: "Use the chmod tool instead.",
|
|
101
|
-
touch: "Use the write_file tool instead.",
|
|
102
|
-
find: "Use the glob tool instead.",
|
|
103
|
-
head: "Use the read_file tool with offset/limit instead.",
|
|
104
|
-
tail: "Use the read_file tool instead.",
|
|
105
|
-
wc: "Use the read_file tool instead.",
|
|
106
|
-
diff: "Use the diff tool instead.",
|
|
107
|
-
which: 'Use "where" instead.',
|
|
108
|
-
echo: "echo works on Windows, but avoid pipes (|).",
|
|
109
|
-
"Get-Content": "Use the read_file tool instead.",
|
|
110
|
-
"Select-Object": "Use the read_file tool with offset/limit instead.",
|
|
111
|
-
};
|
|
112
|
-
/** Unix commands that can be transparently translated to cmd.exe equivalents. */
|
|
113
|
-
const UNIX_TO_WIN_TRANSLATE = {
|
|
114
|
-
ls: "dir",
|
|
115
|
-
pwd: "cd",
|
|
116
|
-
cat: "type",
|
|
117
|
-
};
|
|
118
|
-
/**
|
|
119
|
-
* Detect when the model mistakes a tool call for a shell command — e.g.
|
|
120
|
-
* `bash` with command "create_dir path=C:\...\src" or "read_file file=x".
|
|
121
|
-
* These are tool invocations, not commands; running them through the shell
|
|
122
|
-
* fails. The matched tool name and raw args are returned so the caller can
|
|
123
|
-
* redirect into the real tool.
|
|
124
|
-
*/
|
|
125
|
-
/**
|
|
126
|
-
* Shell commands that must NEVER be treated as mistaken tool calls. The
|
|
127
|
-
* model often writes files via `echo '<code with = signs>'` or reads them
|
|
128
|
-
* via `cat` — those are shell commands, not tool invocations. Without this
|
|
129
|
-
* guard the redirect heuristic fires on any `<word> <text containing =>`
|
|
130
|
-
* and fails with "Unknown tool: echo".
|
|
131
|
-
*/
|
|
132
|
-
const NEVER_TOOL_CALLS = new Set([
|
|
133
|
-
"echo",
|
|
134
|
-
"cat",
|
|
135
|
-
"type",
|
|
136
|
-
"printf",
|
|
137
|
-
"touch",
|
|
138
|
-
"mkdir",
|
|
139
|
-
"cp",
|
|
140
|
-
"mv",
|
|
141
|
-
"rm",
|
|
142
|
-
"ls",
|
|
143
|
-
"dir",
|
|
144
|
-
"cd",
|
|
145
|
-
"pwd",
|
|
146
|
-
"grep",
|
|
147
|
-
"find",
|
|
148
|
-
"head",
|
|
149
|
-
"tail",
|
|
150
|
-
"wc",
|
|
151
|
-
"chmod",
|
|
152
|
-
"sed",
|
|
153
|
-
"awk",
|
|
154
|
-
]);
|
|
155
|
-
export function detectToolCallInBash(command) {
|
|
156
|
-
const match = command.trim().match(/^([\w-]+)\s+(.+)$/s);
|
|
157
|
-
if (!match)
|
|
158
|
-
return null;
|
|
159
|
-
const tool = match[1];
|
|
160
|
-
// Real shell commands (echo, cat, ...) are never mistaken tool calls.
|
|
161
|
-
if (NEVER_TOOL_CALLS.has(tool))
|
|
162
|
-
return null;
|
|
163
|
-
const rest = match[2].trim();
|
|
164
|
-
// Only treat as a tool call when the first word looks like a snake_case
|
|
165
|
-
// tool name and the rest has at least one '=' or a JSON object shape.
|
|
166
|
-
if (!/^[a-z][a-z0-9_]+$/.test(tool))
|
|
167
|
-
return null;
|
|
168
|
-
if (!rest.includes("=") && !rest.startsWith("{"))
|
|
169
|
-
return null;
|
|
170
|
-
return { tool, args: rest };
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Parse the raw argument string of a mistaken tool call captured in a bash
|
|
174
|
-
* command into a Record. Supports JSON objects ("{"path": "..."}") and
|
|
175
|
-
* key=value pairs ("path=C:\...\src"). Values keep their literal text.
|
|
176
|
-
*/
|
|
177
|
-
export function parseToolArgs(raw) {
|
|
178
|
-
const trimmed = raw.trim();
|
|
179
|
-
if (trimmed.startsWith("{")) {
|
|
180
|
-
try {
|
|
181
|
-
return JSON.parse(trimmed);
|
|
182
|
-
}
|
|
183
|
-
catch {
|
|
184
|
-
/* fall through to key=value parsing */
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
const args = {};
|
|
188
|
-
// Tokenize respecting double/single-quoted values.
|
|
189
|
-
const tokens = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
|
|
190
|
-
for (const token of tokens) {
|
|
191
|
-
const eq = token.indexOf("=");
|
|
192
|
-
if (eq > 0) {
|
|
193
|
-
const key = token.slice(0, eq);
|
|
194
|
-
const value = token.slice(eq + 1);
|
|
195
|
-
// Strip surrounding quotes from values.
|
|
196
|
-
args[key] = value.replace(/^["']|["']$/g, "");
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return args;
|
|
200
|
-
}
|
|
201
|
-
export const bashTool = {
|
|
202
|
-
name: "bash",
|
|
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).",
|
|
204
|
-
tags: ["shell", "code"],
|
|
205
|
-
parameters: {
|
|
206
|
-
type: "object",
|
|
207
|
-
properties: {
|
|
208
|
-
command: { type: "string", description: "Shell command to execute" },
|
|
209
|
-
workdir: {
|
|
210
|
-
type: "string",
|
|
211
|
-
description: "Working directory (default: baseDir)",
|
|
212
|
-
},
|
|
213
|
-
background: {
|
|
214
|
-
type: "boolean",
|
|
215
|
-
description: "Return a process id immediately without waiting (default: commands still running after a few seconds are auto-promoted to the background)",
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
|
-
required: ["command"],
|
|
219
|
-
},
|
|
220
|
-
handler: async (ctx, args) => {
|
|
221
|
-
const originalCommand = String(args.command);
|
|
222
|
-
// The model sometimes sends a tool invocation (e.g. "create_dir path=...")
|
|
223
|
-
// as a bash command instead of calling the tool directly. Redirect into
|
|
224
|
-
// the real tool so the intent succeeds instead of failing in the shell.
|
|
225
|
-
const toolCall = detectToolCallInBash(originalCommand);
|
|
226
|
-
if (toolCall &&
|
|
227
|
-
toolCall.tool !== "bash" &&
|
|
228
|
-
ctx.toolExecutor &&
|
|
229
|
-
ctx.toolExecutor.hasTool(toolCall.tool)) {
|
|
230
|
-
const redirected = await ctx.toolExecutor.executeByName(toolCall.tool, parseToolArgs(toolCall.args), ctx);
|
|
231
|
-
return {
|
|
232
|
-
success: redirected.success,
|
|
233
|
-
output: `[redirected to tool "${toolCall.tool}"]\n${redirected.output}`,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
const command = adaptCommandForWindows(originalCommand);
|
|
237
|
-
// echo/printf redirection to a file is unreliable in cmd.exe (single
|
|
238
|
-
// quotes don't group, multi-line commands split, embedded double quotes
|
|
239
|
-
// break the command). Steer the model to write_file instead — it
|
|
240
|
-
// produces correct files every time.
|
|
241
|
-
if (platform() === "win32") {
|
|
242
|
-
const echoWrite = extractEchoFileWrite(originalCommand);
|
|
243
|
-
if (echoWrite) {
|
|
244
|
-
return {
|
|
245
|
-
success: false,
|
|
246
|
-
output: t("bash.echo_write_blocked", { path: echoWrite }),
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const workdir = args.workdir ? String(args.workdir) : ctx.baseDir;
|
|
251
|
-
// Get session-specific security config with defaults
|
|
252
|
-
const appConfig = ctx.config || {};
|
|
253
|
-
const fullSecurityConfig = ctx.sessionContext
|
|
254
|
-
? getSessionSecurityConfig(appConfig, ctx.sessionContext)
|
|
255
|
-
: appConfig.security || DEFAULT_SECURITY_CONFIG;
|
|
256
|
-
const securityConfig = fullSecurityConfig.bash || DEFAULT_SECURITY_CONFIG.bash;
|
|
257
|
-
const validation = isCommandAllowed(command, securityConfig);
|
|
258
|
-
if (!validation.allowed) {
|
|
259
|
-
// Log security block
|
|
260
|
-
logSecurityBlock(ctx.sessionId, "bash_command", validation.reason || "Command blocked by security policy", sanitizeCommandForLog(originalCommand));
|
|
261
|
-
return {
|
|
262
|
-
success: false,
|
|
263
|
-
output: `[SECURITY BLOCKED] Command is not allowed: ${validation.reason}\nHint: Use the "workdir" parameter to run commands in a specific directory instead of "cd dir && cmd". Run one command per tool call.`,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
// Log command execution if enabled
|
|
267
|
-
if (securityConfig?.logCommands) {
|
|
268
|
-
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), false, // Will be updated after execution
|
|
269
|
-
`Working directory: ${workdir}`);
|
|
270
|
-
}
|
|
271
|
-
const entry = processRegistry.start(command, workdir, ctx.sessionId);
|
|
272
|
-
if (ctx.activeCallId) {
|
|
273
|
-
// Let the executor abort/kill the child during the grace window.
|
|
274
|
-
registerKillable(ctx.activeCallId, () => processRegistry.kill(entry.id));
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
const settleMs = args.background === true ? SPAWN_SETTLE_MS : bashGraceMs;
|
|
278
|
-
const exited = await processRegistry.waitForExit(entry.id, settleMs);
|
|
279
|
-
if (exited) {
|
|
280
|
-
// Command finished (or failed to spawn) within the window.
|
|
281
|
-
if (entry.spawnError) {
|
|
282
|
-
return {
|
|
283
|
-
success: false,
|
|
284
|
-
output: `[process error] ${entry.spawnError}\nHint: check the "workdir" path exists and the command is valid for this OS.`,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
const code = entry.exitCode;
|
|
288
|
-
let output = entry.log.join("\n");
|
|
289
|
-
processRegistry.remove(entry.id);
|
|
290
|
-
if (!output && code !== 0) {
|
|
291
|
-
output = `(exit code ${code})`;
|
|
292
|
-
}
|
|
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}`;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
// Update audit log with result
|
|
306
|
-
if (securityConfig?.logCommands) {
|
|
307
|
-
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), code === 0, `Working directory: ${workdir}, Output length: ${output.length}`);
|
|
308
|
-
}
|
|
309
|
-
const lines = output.split("\n");
|
|
310
|
-
if (lines.length > MAX_PREVIEW_LINES) {
|
|
311
|
-
output =
|
|
312
|
-
lines.slice(0, MAX_PREVIEW_LINES).join("\n") +
|
|
313
|
-
`\n... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
|
|
314
|
-
}
|
|
315
|
-
return { success: code === 0, output };
|
|
316
|
-
}
|
|
317
|
-
// Still running — promote to a background process.
|
|
318
|
-
const explicit = args.background === true;
|
|
319
|
-
const output = `${t("proc.started", {
|
|
320
|
-
id: entry.id,
|
|
321
|
-
pid: entry.pid,
|
|
322
|
-
command,
|
|
323
|
-
})}${explicit ? "" : `\n${t("proc.promoted_hint", { ms: settleMs })}`}\n${t("proc.manage_hint", {
|
|
324
|
-
id: entry.id,
|
|
325
|
-
})}`;
|
|
326
|
-
if (securityConfig?.logCommands) {
|
|
327
|
-
logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), true, `Started in background: ${entry.id} (PID ${entry.pid})`);
|
|
328
|
-
}
|
|
329
|
-
return { success: true, output };
|
|
330
|
-
}
|
|
331
|
-
finally {
|
|
332
|
-
if (ctx.activeCallId) {
|
|
333
|
-
unregisterKillable(ctx.activeCallId);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
},
|
|
337
|
-
};
|
package/dist/tools/browser.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
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
|
-
let session = null;
|
|
6
|
-
function getSession(ctx) {
|
|
7
|
-
if (!session) {
|
|
8
|
-
const cookieDir = join(ctx.baseDir, '.mma', 'browser');
|
|
9
|
-
session = new BrowserSession({
|
|
10
|
-
...DEFAULT_BROWSER_CONFIG,
|
|
11
|
-
headless: ctx.config.browser?.headless ?? true,
|
|
12
|
-
maxElements: ctx.config.browser?.maxElements ?? 30,
|
|
13
|
-
navigationTimeout: ctx.config.browser?.navigationTimeout ?? 15000,
|
|
14
|
-
viewportWidth: ctx.config.browser?.viewportWidth ?? 1280,
|
|
15
|
-
viewportHeight: ctx.config.browser?.viewportHeight ?? 720,
|
|
16
|
-
cookieDir,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
return session;
|
|
20
|
-
}
|
|
21
|
-
export function formatScreenshotForTextModel(snapshot) {
|
|
22
|
-
return `${t('tool.screenshot_unavailable')}\n\n${snapshot}`;
|
|
23
|
-
}
|
|
24
|
-
export function formatScreenshotResult(snapshot, screenshot, supportsVision) {
|
|
25
|
-
if (!supportsVision) {
|
|
26
|
-
return formatScreenshotForTextModel(snapshot);
|
|
27
|
-
}
|
|
28
|
-
const base64 = screenshot.toString('base64');
|
|
29
|
-
return `${snapshot}\n\n[Screenshot: data:image/png;base64,${base64}]`;
|
|
30
|
-
}
|
|
31
|
-
export function createBrowserTool() {
|
|
32
|
-
return {
|
|
33
|
-
name: 'browser',
|
|
34
|
-
tags: ['browser', 'vision'],
|
|
35
|
-
description: [
|
|
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(' '),
|
|
40
|
-
parameters: {
|
|
41
|
-
type: 'object',
|
|
42
|
-
properties: {
|
|
43
|
-
action: {
|
|
44
|
-
type: 'string',
|
|
45
|
-
enum: ['open', 'click', 'type', 'scroll', 'back', 'forward', 'screenshot', 'snapshot', 'close', 'wait'],
|
|
46
|
-
description: 'Browser action to perform',
|
|
47
|
-
},
|
|
48
|
-
url: {
|
|
49
|
-
type: 'string',
|
|
50
|
-
description: 'URL to open (for action "open")',
|
|
51
|
-
},
|
|
52
|
-
target: {
|
|
53
|
-
type: 'number',
|
|
54
|
-
description: 'Element number from snapshot (for action "click" or "type")',
|
|
55
|
-
},
|
|
56
|
-
text: {
|
|
57
|
-
type: 'string',
|
|
58
|
-
description: 'Text to type (for action "type")',
|
|
59
|
-
},
|
|
60
|
-
direction: {
|
|
61
|
-
type: 'string',
|
|
62
|
-
enum: ['up', 'down', 'top', 'bottom'],
|
|
63
|
-
description: 'Scroll direction (for action "scroll", default "down")',
|
|
64
|
-
},
|
|
65
|
-
ms: {
|
|
66
|
-
type: 'number',
|
|
67
|
-
description: 'Milliseconds to wait (for action "wait", default 1000)',
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
required: ['action'],
|
|
71
|
-
},
|
|
72
|
-
handler: async (ctx, args) => {
|
|
73
|
-
const action = String(args.action || '');
|
|
74
|
-
if (!action) {
|
|
75
|
-
return { success: false, output: t('tool.action_required') };
|
|
76
|
-
}
|
|
77
|
-
const s = getSession(ctx);
|
|
78
|
-
const result = await s.execute(action, args);
|
|
79
|
-
const supportsVision = ctx.config.model.includes('vision') ||
|
|
80
|
-
ctx.config.model.includes('gpt-4o') ||
|
|
81
|
-
ctx.config.model.includes('claude');
|
|
82
|
-
if (result.screenshot) {
|
|
83
|
-
return {
|
|
84
|
-
success: result.success,
|
|
85
|
-
output: formatScreenshotResult(result.output, result.screenshot, supportsVision),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
return { success: result.success, output: result.output };
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
export function closeBrowserSession() {
|
|
93
|
-
if (session) {
|
|
94
|
-
session.close().catch(() => { });
|
|
95
|
-
session = null;
|
|
96
|
-
}
|
|
97
|
-
}
|
package/dist/tools/create-dir.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, existsSync } from "fs";
|
|
2
|
-
import { t } from "../i18n/index";
|
|
3
|
-
import { isPathWritable } from "../modules/security/path-validator";
|
|
4
|
-
import { logFileWrite, logSecurityBlock } from "../modules/security/audit-log";
|
|
5
|
-
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
6
|
-
import { safeResolvePath } from "./path-utils";
|
|
7
|
-
export const createDirTool = {
|
|
8
|
-
name: "create_dir",
|
|
9
|
-
description: "Create a directory (and any intermediate directories).",
|
|
10
|
-
tags: ["file"],
|
|
11
|
-
parameters: {
|
|
12
|
-
type: "object",
|
|
13
|
-
properties: {
|
|
14
|
-
path: { type: "string", description: "Directory path" },
|
|
15
|
-
},
|
|
16
|
-
required: ["path"],
|
|
17
|
-
},
|
|
18
|
-
handler: async (ctx, args) => {
|
|
19
|
-
const path = String(args.path);
|
|
20
|
-
const resolved = safeResolvePath(ctx.baseDir, path);
|
|
21
|
-
// Get session-specific security config
|
|
22
|
-
const securityConfig = ctx.sessionContext
|
|
23
|
-
? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
|
|
24
|
-
: ctx.config.security;
|
|
25
|
-
// Check file operations limit
|
|
26
|
-
const maxFileOps = securityConfig?.maxFileOperations ?? 100;
|
|
27
|
-
const currentCount = ctx.fileOperationsCount ?? 0;
|
|
28
|
-
if (currentCount >= maxFileOps) {
|
|
29
|
-
logSecurityBlock(ctx.sessionId, "file_write", `Maximum file operations (${maxFileOps}) exceeded`, path);
|
|
30
|
-
return {
|
|
31
|
-
success: false,
|
|
32
|
-
output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
// Check path permissions
|
|
36
|
-
const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
|
|
37
|
-
if (!scopeCheck.allowed) {
|
|
38
|
-
logSecurityBlock(ctx.sessionId, "file_write", scopeCheck.reason || "Path not allowed", path);
|
|
39
|
-
return {
|
|
40
|
-
success: false,
|
|
41
|
-
output: t("file.path_not_allowed", {
|
|
42
|
-
path: `${path} — ${scopeCheck.reason}`,
|
|
43
|
-
}),
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (!existsSync(resolved)) {
|
|
47
|
-
mkdirSync(resolved, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
// Increment file operations counter
|
|
50
|
-
ctx.fileOperationsCount = currentCount + 1;
|
|
51
|
-
// Log directory creation
|
|
52
|
-
logFileWrite(ctx.sessionId, path, true, "Directory created");
|
|
53
|
-
return { success: true, output: t("file.created", { path }) };
|
|
54
|
-
},
|
|
55
|
-
};
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { unlinkSync, existsSync, statSync, readFileSync } from "fs";
|
|
2
|
-
import { t } from "../i18n/index";
|
|
3
|
-
import { isPathWritable } from "../modules/security/path-validator";
|
|
4
|
-
import { logFileDelete, logSecurityBlock } from "../modules/security/audit-log";
|
|
5
|
-
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
6
|
-
import { generateDeleteDiff } from "../ui/diff";
|
|
7
|
-
import { safeResolvePath } from "./path-utils";
|
|
8
|
-
export const deleteFileTool = {
|
|
9
|
-
name: "delete_file",
|
|
10
|
-
description: "Delete a file from the filesystem.",
|
|
11
|
-
tags: ["file"],
|
|
12
|
-
parameters: {
|
|
13
|
-
type: "object",
|
|
14
|
-
properties: {
|
|
15
|
-
path: { type: "string", description: "File path" },
|
|
16
|
-
},
|
|
17
|
-
required: ["path"],
|
|
18
|
-
},
|
|
19
|
-
handler: async (ctx, args) => {
|
|
20
|
-
const path = String(args.path);
|
|
21
|
-
const resolved = safeResolvePath(ctx.baseDir, path);
|
|
22
|
-
// Get session-specific security config
|
|
23
|
-
const securityConfig = ctx.sessionContext
|
|
24
|
-
? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
|
|
25
|
-
: ctx.config.security;
|
|
26
|
-
// Check file operations limit
|
|
27
|
-
const maxFileOps = securityConfig?.maxFileOperations ?? 100;
|
|
28
|
-
const currentCount = ctx.fileOperationsCount ?? 0;
|
|
29
|
-
if (currentCount >= maxFileOps) {
|
|
30
|
-
logSecurityBlock(ctx.sessionId, "file_delete", `Maximum file operations (${maxFileOps}) exceeded`, path);
|
|
31
|
-
return {
|
|
32
|
-
success: false,
|
|
33
|
-
output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
// Check path permissions
|
|
37
|
-
const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
|
|
38
|
-
if (!scopeCheck.allowed) {
|
|
39
|
-
logSecurityBlock(ctx.sessionId, "file_delete", scopeCheck.reason || "Path not allowed", path);
|
|
40
|
-
return {
|
|
41
|
-
success: false,
|
|
42
|
-
output: t("file.path_not_allowed", {
|
|
43
|
-
path: `${path} — ${scopeCheck.reason}`,
|
|
44
|
-
}),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (!existsSync(resolved)) {
|
|
48
|
-
return { success: false, output: t("file.notfound", { path }) };
|
|
49
|
-
}
|
|
50
|
-
if (statSync(resolved).isDirectory()) {
|
|
51
|
-
return { success: false, output: t("file.is_directory", { path }) };
|
|
52
|
-
}
|
|
53
|
-
const content = readFileSync(resolved, "utf-8");
|
|
54
|
-
unlinkSync(resolved);
|
|
55
|
-
const diff = generateDeleteDiff(content);
|
|
56
|
-
// Increment file operations counter
|
|
57
|
-
ctx.fileOperationsCount = currentCount + 1;
|
|
58
|
-
// Log successful file deletion
|
|
59
|
-
logFileDelete(ctx.sessionId, path, true);
|
|
60
|
-
return { success: true, output: t("file.deleted", { path }), diff };
|
|
61
|
-
},
|
|
62
|
-
};
|
package/dist/tools/edit-file.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { t } from "../i18n/index";
|
|
3
|
-
import { isPathWritable } from "../modules/security/path-validator";
|
|
4
|
-
import { scanContent } from "../modules/security/content-scanner";
|
|
5
|
-
import { logFileWrite, logSecurityBlock } from "../modules/security/audit-log";
|
|
6
|
-
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
7
|
-
import { generateDiff } from "../ui/diff";
|
|
8
|
-
import { safeResolvePath } from "./path-utils";
|
|
9
|
-
export const editFileTool = {
|
|
10
|
-
name: "edit_file",
|
|
11
|
-
description: "Find and replace text in an existing file. Uses exact string match (not regex).",
|
|
12
|
-
tags: ["file", "code"],
|
|
13
|
-
parameters: {
|
|
14
|
-
type: "object",
|
|
15
|
-
properties: {
|
|
16
|
-
path: { type: "string", description: "File path" },
|
|
17
|
-
old: { type: "string", description: "Text to replace" },
|
|
18
|
-
new: { type: "string", description: "Replacement text" },
|
|
19
|
-
},
|
|
20
|
-
required: ["path", "old", "new"],
|
|
21
|
-
},
|
|
22
|
-
handler: async (ctx, args) => {
|
|
23
|
-
const path = String(args.path);
|
|
24
|
-
const resolved = safeResolvePath(ctx.baseDir, path);
|
|
25
|
-
// Get session-specific security config
|
|
26
|
-
const securityConfig = ctx.sessionContext
|
|
27
|
-
? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
|
|
28
|
-
: ctx.config.security;
|
|
29
|
-
// Check file operations limit
|
|
30
|
-
const maxFileOps = securityConfig?.maxFileOperations ?? 100;
|
|
31
|
-
const currentCount = ctx.fileOperationsCount ?? 0;
|
|
32
|
-
if (currentCount >= maxFileOps) {
|
|
33
|
-
logSecurityBlock(ctx.sessionId, "file_write", `Maximum file operations (${maxFileOps}) exceeded`, path);
|
|
34
|
-
return {
|
|
35
|
-
success: false,
|
|
36
|
-
output: `[SECURITY BLOCKED] Maximum file operations (${maxFileOps}) exceeded`,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
// Check path permissions
|
|
40
|
-
const scopeCheck = isPathWritable(ctx.baseDir, resolved, ctx.scope, ctx.config.security?.paths);
|
|
41
|
-
if (!scopeCheck.allowed) {
|
|
42
|
-
logSecurityBlock(ctx.sessionId, "file_write", scopeCheck.reason || "Path not allowed", path);
|
|
43
|
-
return {
|
|
44
|
-
success: false,
|
|
45
|
-
output: t("file.path_not_allowed", {
|
|
46
|
-
path: `${path} — ${scopeCheck.reason}`,
|
|
47
|
-
}),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const content = readFileSync(resolved, "utf-8");
|
|
51
|
-
const oldStr = String(args.old);
|
|
52
|
-
const newStr = String(args.new);
|
|
53
|
-
if (!content.includes(oldStr)) {
|
|
54
|
-
return {
|
|
55
|
-
success: false,
|
|
56
|
-
output: t("file.string_not_found", { str: oldStr }),
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const updated = content.replace(oldStr, newStr);
|
|
60
|
-
// Check new content for dangerous patterns — only when security is enabled
|
|
61
|
-
if (securityConfig?.enabled && securityConfig?.contentScan?.enabled) {
|
|
62
|
-
const scanResult = scanContent(updated, path, securityConfig.contentScan);
|
|
63
|
-
if (!scanResult.allowed) {
|
|
64
|
-
logSecurityBlock(ctx.sessionId, "file_write", scanResult.reason || "Content contains dangerous patterns", path);
|
|
65
|
-
return {
|
|
66
|
-
success: false,
|
|
67
|
-
output: `[SECURITY BLOCKED] ${scanResult.reason}`,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
writeFileSync(resolved, updated, "utf-8");
|
|
72
|
-
const diff = generateDiff(content, updated);
|
|
73
|
-
// Increment file operations counter
|
|
74
|
-
ctx.fileOperationsCount = currentCount + 1;
|
|
75
|
-
// Log successful file edit
|
|
76
|
-
logFileWrite(ctx.sessionId, path, true, "File edited");
|
|
77
|
-
return { success: true, output: t("file.replaced_in", { path }), diff };
|
|
78
|
-
},
|
|
79
|
-
};
|