micro-models-agent 0.50.4 → 0.51.2

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 (218) hide show
  1. package/README.md +358 -358
  2. package/dist/main.js +923 -590
  3. package/dist/modules/browser/bridge-server.mjs +202 -202
  4. package/dist/skills/builtin/git.md +36 -36
  5. package/dist/skills/builtin/typescript.md +35 -35
  6. package/package.json +48 -48
  7. package/dist/cli/commands.js +0 -323
  8. package/dist/cli/completer.js +0 -167
  9. package/dist/cli/index.js +0 -2
  10. package/dist/cli/main.js +0 -165
  11. package/dist/cli/plugin-commands.js +0 -36
  12. package/dist/cli/repl-commands.js +0 -661
  13. package/dist/cli/repl.js +0 -616
  14. package/dist/cli/run-result.js +0 -22
  15. package/dist/cli/security-commands.js +0 -164
  16. package/dist/cli/setup.js +0 -231
  17. package/dist/config/config.js +0 -249
  18. package/dist/config/defaults.js +0 -124
  19. package/dist/config/experts.js +0 -15
  20. package/dist/config/index.js +0 -3
  21. package/dist/config/security.js +0 -193
  22. package/dist/config/types.js +0 -1
  23. package/dist/core/agent-moe.js +0 -102
  24. package/dist/core/agent.js +0 -886
  25. package/dist/core/bootstrap.js +0 -404
  26. package/dist/core/index.js +0 -2
  27. package/dist/core/prompt-builder.js +0 -76
  28. package/dist/core/session-logger.js +0 -197
  29. package/dist/core/types.js +0 -1
  30. package/dist/core/version.js +0 -24
  31. package/dist/core/workspace.js +0 -76
  32. package/dist/i18n/en.json +0 -598
  33. package/dist/i18n/index.js +0 -46
  34. package/dist/i18n/ru.json +0 -598
  35. package/dist/index.js +0 -22
  36. package/dist/llm/image-utils.js +0 -143
  37. package/dist/llm/index.js +0 -4
  38. package/dist/llm/model-loader.js +0 -78
  39. package/dist/llm/openai-compat.js +0 -359
  40. package/dist/llm/orchestrator.js +0 -198
  41. package/dist/llm/provider.js +0 -10
  42. package/dist/llm/response.js +0 -39
  43. package/dist/llm/token-counter.js +0 -39
  44. package/dist/llm/types.js +0 -1
  45. package/dist/logger/app-logger.js +0 -143
  46. package/dist/logger/file-log.js +0 -151
  47. package/dist/logger/index.js +0 -1
  48. package/dist/migration/backup.js +0 -45
  49. package/dist/migration/detect.js +0 -50
  50. package/dist/migration/index.js +0 -2
  51. package/dist/modules/artifacts/store.js +0 -61
  52. package/dist/modules/browser/actions.js +0 -76
  53. package/dist/modules/browser/bridge-client.js +0 -199
  54. package/dist/modules/browser/bridge-path.js +0 -10
  55. package/dist/modules/browser/cookie-store.js +0 -24
  56. package/dist/modules/browser/driver.js +0 -136
  57. package/dist/modules/browser/index.js +0 -7
  58. package/dist/modules/browser/module.js +0 -29
  59. package/dist/modules/browser/session.js +0 -338
  60. package/dist/modules/browser/snapshot.js +0 -148
  61. package/dist/modules/browser/types.js +0 -12
  62. package/dist/modules/certification/cli.js +0 -174
  63. package/dist/modules/certification/fact-checker.js +0 -82
  64. package/dist/modules/certification/loader.js +0 -105
  65. package/dist/modules/certification/manifest.js +0 -50
  66. package/dist/modules/certification/runner.js +0 -159
  67. package/dist/modules/certification/scenarios.js +0 -124
  68. package/dist/modules/certification/types.js +0 -1
  69. package/dist/modules/context/chunk-query.js +0 -100
  70. package/dist/modules/context/fact-extractor.js +0 -162
  71. package/dist/modules/context/history.js +0 -15
  72. package/dist/modules/context/index.js +0 -1
  73. package/dist/modules/context/manager.js +0 -423
  74. package/dist/modules/execution/audit-runners.js +0 -152
  75. package/dist/modules/execution/auditor.js +0 -218
  76. package/dist/modules/execution/execution-plugin.js +0 -272
  77. package/dist/modules/execution/index.js +0 -8
  78. package/dist/modules/execution/module.js +0 -436
  79. package/dist/modules/execution/moe-executor.js +0 -291
  80. package/dist/modules/execution/plan-coverage.js +0 -68
  81. package/dist/modules/execution/plan-persister.js +0 -46
  82. package/dist/modules/execution/plan-store.js +0 -157
  83. package/dist/modules/execution/plan-tool.js +0 -508
  84. package/dist/modules/execution/plan-validator.js +0 -153
  85. package/dist/modules/execution/planner.js +0 -90
  86. package/dist/modules/execution/stuck-detector.js +0 -510
  87. package/dist/modules/execution/tracker.js +0 -67
  88. package/dist/modules/execution/types.js +0 -1
  89. package/dist/modules/execution/verifier.js +0 -222
  90. package/dist/modules/execution/windows-commands.js +0 -41
  91. package/dist/modules/hallucination/confidence.js +0 -66
  92. package/dist/modules/hallucination/consistency.js +0 -26
  93. package/dist/modules/hallucination/detector.js +0 -43
  94. package/dist/modules/hallucination/factual.js +0 -129
  95. package/dist/modules/hallucination/index.js +0 -5
  96. package/dist/modules/hallucination/js-identifiers.js +0 -262
  97. package/dist/modules/hallucination/llm-judge.js +0 -101
  98. package/dist/modules/index.js +0 -5
  99. package/dist/modules/indexer/cache.js +0 -40
  100. package/dist/modules/indexer/index.js +0 -3
  101. package/dist/modules/indexer/module.js +0 -245
  102. package/dist/modules/indexer/project-profile.js +0 -183
  103. package/dist/modules/indexer/walker.js +0 -101
  104. package/dist/modules/lsp/check-tool.js +0 -58
  105. package/dist/modules/lsp/client.js +0 -278
  106. package/dist/modules/lsp/command.js +0 -60
  107. package/dist/modules/lsp/config.js +0 -135
  108. package/dist/modules/lsp/index.js +0 -3
  109. package/dist/modules/lsp/module.js +0 -232
  110. package/dist/modules/lsp/probe.js +0 -76
  111. package/dist/modules/lsp/project-root.js +0 -32
  112. package/dist/modules/lsp/startup-check.js +0 -141
  113. package/dist/modules/lsp/types.js +0 -1
  114. package/dist/modules/mcp/client.js +0 -399
  115. package/dist/modules/mcp/index.js +0 -3
  116. package/dist/modules/mcp/module.js +0 -142
  117. package/dist/modules/mcp/registry.js +0 -15
  118. package/dist/modules/memory/index.js +0 -1
  119. package/dist/modules/memory/module.js +0 -96
  120. package/dist/modules/memory/search.js +0 -42
  121. package/dist/modules/memory/store.js +0 -69
  122. package/dist/modules/pipelines/engine.js +0 -60
  123. package/dist/modules/pipelines/index.js +0 -3
  124. package/dist/modules/pipelines/parser.js +0 -56
  125. package/dist/modules/pipelines/template.js +0 -14
  126. package/dist/modules/plugins/builtin/lint-on-write.js +0 -231
  127. package/dist/modules/plugins/builtin/notify.js +0 -9
  128. package/dist/modules/plugins/index.js +0 -1
  129. package/dist/modules/plugins/loader.js +0 -70
  130. package/dist/modules/plugins/manager.js +0 -217
  131. package/dist/modules/plugins/types.js +0 -1
  132. package/dist/modules/processes/detect.js +0 -34
  133. package/dist/modules/processes/index.js +0 -2
  134. package/dist/modules/processes/registry.js +0 -327
  135. package/dist/modules/processes/runner.js +0 -23
  136. package/dist/modules/registry.js +0 -47
  137. package/dist/modules/security/audit-log.js +0 -136
  138. package/dist/modules/security/audit-notifier.js +0 -292
  139. package/dist/modules/security/command-validator.js +0 -205
  140. package/dist/modules/security/content-scanner.js +0 -53
  141. package/dist/modules/security/data-sanitizer.js +0 -89
  142. package/dist/modules/security/encryption.js +0 -242
  143. package/dist/modules/security/index.js +0 -14
  144. package/dist/modules/security/network-validator.js +0 -71
  145. package/dist/modules/security/path-validator.js +0 -207
  146. package/dist/modules/security/rate-limiter.js +0 -119
  147. package/dist/modules/security/security-policies.js +0 -531
  148. package/dist/modules/security/session-encryption.js +0 -210
  149. package/dist/modules/security/session-isolation.js +0 -95
  150. package/dist/modules/session/index.js +0 -3
  151. package/dist/modules/session/manager.js +0 -172
  152. package/dist/modules/session/module.js +0 -24
  153. package/dist/modules/session/store.js +0 -222
  154. package/dist/modules/session/types.js +0 -1
  155. package/dist/modules/skills/index.js +0 -2
  156. package/dist/modules/skills/loader.js +0 -72
  157. package/dist/modules/skills/matcher.js +0 -27
  158. package/dist/modules/skills/module.js +0 -129
  159. package/dist/modules/types.js +0 -1
  160. package/dist/modules/updater/checker.js +0 -96
  161. package/dist/modules/updater/index.js +0 -2
  162. package/dist/modules/updater/module.js +0 -116
  163. package/dist/modules/user-profile/compressor.js +0 -16
  164. package/dist/modules/user-profile/index.js +0 -1
  165. package/dist/modules/user-profile/profile.js +0 -68
  166. package/dist/tools/approve.js +0 -32
  167. package/dist/tools/attach-image.js +0 -89
  168. package/dist/tools/bash.js +0 -496
  169. package/dist/tools/browser.js +0 -114
  170. package/dist/tools/chunk-query.js +0 -99
  171. package/dist/tools/create-dir.js +0 -55
  172. package/dist/tools/delete-file.js +0 -62
  173. package/dist/tools/download-file.js +0 -116
  174. package/dist/tools/edit-file.js +0 -79
  175. package/dist/tools/enable-tools.js +0 -58
  176. package/dist/tools/executor.js +0 -144
  177. package/dist/tools/file-info.js +0 -46
  178. package/dist/tools/filter-tools.js +0 -17
  179. package/dist/tools/glob-tool.js +0 -26
  180. package/dist/tools/grep-tool.js +0 -84
  181. package/dist/tools/hidden-tools-block.js +0 -37
  182. package/dist/tools/index.js +0 -78
  183. package/dist/tools/list-dir.js +0 -48
  184. package/dist/tools/load-skill.js +0 -42
  185. package/dist/tools/mcp-call.js +0 -68
  186. package/dist/tools/move-file.js +0 -85
  187. package/dist/tools/path-utils.js +0 -51
  188. package/dist/tools/pipeline-run.js +0 -144
  189. package/dist/tools/preview.js +0 -2
  190. package/dist/tools/process-kill.js +0 -29
  191. package/dist/tools/process-list.js +0 -36
  192. package/dist/tools/process-log.js +0 -45
  193. package/dist/tools/question.js +0 -140
  194. package/dist/tools/read-file.js +0 -91
  195. package/dist/tools/recall.js +0 -117
  196. package/dist/tools/registry.js +0 -47
  197. package/dist/tools/remember.js +0 -67
  198. package/dist/tools/scope-check.js +0 -30
  199. package/dist/tools/search-history.js +0 -84
  200. package/dist/tools/subagent.js +0 -196
  201. package/dist/tools/types.js +0 -1
  202. package/dist/tools/user-input.js +0 -123
  203. package/dist/tools/web-browse.js +0 -86
  204. package/dist/tools/web-fetch.js +0 -98
  205. package/dist/tools/web-search.js +0 -78
  206. package/dist/tools/write-file.js +0 -81
  207. package/dist/ui/box.js +0 -77
  208. package/dist/ui/colors.js +0 -4
  209. package/dist/ui/diff.js +0 -178
  210. package/dist/ui/index.js +0 -6
  211. package/dist/ui/line-editor.js +0 -703
  212. package/dist/ui/line-math.js +0 -69
  213. package/dist/ui/md-formatter.js +0 -212
  214. package/dist/ui/output.js +0 -13
  215. package/dist/ui/plan-view.js +0 -103
  216. package/dist/ui/renderer.js +0 -209
  217. package/dist/ui/spinner.js +0 -70
  218. package/dist/ui/table.js +0 -144
@@ -1,496 +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
- /** Number of already-emitted lines shown in the background start message. */
20
- const BG_OUTPUT_PREVIEW_LINES = 15;
21
- let bashGraceMs = BASH_GRACE_MS;
22
- /** Test hook: override the auto-background grace window. */
23
- export function setBashGraceMs(ms) {
24
- bashGraceMs = ms;
25
- }
26
- /**
27
- * Consecutive failures per first command word (resets on success). A model
28
- * that keeps calling a forbidden Unix command in cmd.exe (head/cat/grep/...)
29
- * gets a hint the first couple of times; after HARD_BLOCK_THRESHOLD consecutive
30
- * failures the tool hard-stops with a STOP directive instead of repeating the
31
- * same hint forever (observed: `head` called 5×, `Select-String` 3× in one run).
32
- */
33
- const FAILING_FIRST_WORDS = new Map();
34
- const HARD_BLOCK_THRESHOLD = 3;
35
- /** Test hook: clear the consecutive-failure tracking state. */
36
- export function resetBashFailureTracking() {
37
- FAILING_FIRST_WORDS.clear();
38
- }
39
- /**
40
- * Detect a file write via `echo/printf ... > file` — common model habit that
41
- * breaks in cmd.exe: single quotes are not grouping quotes, `>` only applies
42
- * to the LAST line of a multi-line command, and double quotes inside the text
43
- * split the command. Returns the target filename or null.
44
- */
45
- export function extractEchoFileWrite(command) {
46
- if (!/^\s*(?:echo|printf)\b/i.test(command))
47
- return null;
48
- const m = command.match(/[>»]{1,2}\s*"?([^"'\s&|]+)"?/i);
49
- if (!m)
50
- return null;
51
- return m[1].replace(/["'']$/g, "");
52
- }
53
- /**
54
- * cmd.exe uses `&` as the command separator, not `;` (bash). The model
55
- * regularly chains commands with `;` — without translation cmd passes the
56
- * `;` to the first command as an argument (e.g. `node x.ts;` → ENOENT for
57
- * "x.ts;"). Replace `;` with `&` only OUTSIDE double-quoted strings so
58
- * `echo "a;b"` stays intact. (Single quotes are not special in cmd.)
59
- */
60
- export function translateSemicolonsForCmd(command) {
61
- let out = "";
62
- let inQuotes = false;
63
- for (let i = 0; i < command.length; i++) {
64
- const ch = command[i];
65
- if (ch === '"') {
66
- inQuotes = !inQuotes;
67
- out += ch;
68
- continue;
69
- }
70
- out += ch === ";" && !inQuotes ? "&" : ch;
71
- }
72
- return out;
73
- }
74
- function adaptCommandForWindows(command) {
75
- if (platform() !== "win32")
76
- return command;
77
- // The model sometimes appends `|| true` (bash error-suppression idiom)
78
- // which PowerShell doesn't understand. Replace with `; exit 0` which
79
- // forces a successful exit code regardless of the previous command's result.
80
- if (/\|\|\s*true\b/.test(command)) {
81
- command = command.replace(/\s*\|\|\s*true\b/g, "; exit 0");
82
- }
83
- // Windows mkdir does not support -p flag, but creates intermediate dirs by default
84
- const trimmed = command.trim();
85
- if (trimmed.startsWith("mkdir -p ")) {
86
- return trimmed.replace(/^mkdir -p /, "mkdir ");
87
- }
88
- if (trimmed === "mkdir -p" || trimmed.startsWith("mkdir -p ")) {
89
- return trimmed.replace(/mkdir -p/g, "mkdir");
90
- }
91
- // Translate simple Unix commands to their cmd.exe equivalents. Only the
92
- // leading word is rewritten; flags are passed through (ls -la → dir -la,
93
- // which cmd tolerates). Pipe-using forms are left alone — they would break.
94
- const firstWord = trimmed.split(/\s+/)[0]?.split(/[\\/]/).pop();
95
- const translated = firstWord ? UNIX_TO_WIN_TRANSLATE[firstWord] : undefined;
96
- if (translated &&
97
- !trimmed.includes("|") &&
98
- !trimmed.includes(">") &&
99
- !trimmed.includes("&&") &&
100
- !trimmed.includes(";")) {
101
- return trimmed.replace(firstWord, translated);
102
- }
103
- // No encoding adaptation needed — registry.ts handles UTF-8/OEM decoding
104
- return translateSemicolonsForCmd(command);
105
- }
106
- /** Common Unix → Windows command mapping for error hints. */
107
- const UNIX_TO_WIN_HINTS = {
108
- ls: "Use the list_dir tool instead.",
109
- pwd: "Use the file_info tool instead.",
110
- cat: "Use the read_file tool instead.",
111
- cp: "Use the move_file tool instead.",
112
- mv: "Use the move_file tool instead.",
113
- rm: "Use the delete_file tool instead.",
114
- grep: "Use the grep tool instead.",
115
- chmod: "Use the chmod tool instead.",
116
- touch: "Use the write_file tool instead.",
117
- find: "Use the glob tool instead.",
118
- head: "Use the read_file tool with offset/limit instead.",
119
- tail: "Use the read_file tool instead.",
120
- wc: "Use the read_file tool instead.",
121
- diff: "Use the diff tool instead.",
122
- which: 'Use "where" instead.',
123
- echo: "echo works on Windows, but avoid pipes (|).",
124
- "Get-Content": "Use the read_file tool instead.",
125
- "Select-Object": "Use the read_file tool with offset/limit instead.",
126
- "Write-Host": "PowerShell cmdlet — this shell is cmd.exe. Print with plain echo instead.",
127
- "Select-String": "Use the grep tool instead.",
128
- "Out-File": "Write files with the write_file tool instead.",
129
- "Set-Content": "Write files with the write_file tool instead.",
130
- "Get-ChildItem": "Use the list_dir tool instead.",
131
- "Remove-Item": "Use the delete_file tool instead.",
132
- };
133
- /** Unix commands that can be transparently translated to cmd.exe equivalents. */
134
- const UNIX_TO_WIN_TRANSLATE = {
135
- ls: "dir",
136
- pwd: "cd",
137
- cat: "type",
138
- };
139
- /**
140
- * Detect when the model mistakes a tool call for a shell command — e.g.
141
- * `bash` with command "create_dir path=C:\...\src" or "read_file file=x".
142
- * These are tool invocations, not commands; running them through the shell
143
- * fails. The matched tool name and raw args are returned so the caller can
144
- * redirect into the real tool.
145
- */
146
- /**
147
- * Shell commands that must NEVER be treated as mistaken tool calls. The
148
- * model often writes files via `echo '<code with = signs>'` or reads them
149
- * via `cat` — those are shell commands, not tool invocations. Without this
150
- * guard the redirect heuristic fires on any `<word> <text containing =>`
151
- * and fails with "Unknown tool: echo".
152
- */
153
- const NEVER_TOOL_CALLS = new Set([
154
- "echo",
155
- "cat",
156
- "type",
157
- "printf",
158
- "touch",
159
- "mkdir",
160
- "cp",
161
- "mv",
162
- "rm",
163
- "ls",
164
- "dir",
165
- "cd",
166
- "pwd",
167
- "grep",
168
- "find",
169
- "head",
170
- "tail",
171
- "wc",
172
- "chmod",
173
- "sed",
174
- "awk",
175
- ]);
176
- export function detectToolCallInBash(command) {
177
- const match = command.trim().match(/^([\w-]+)\s+(.+)$/s);
178
- if (!match)
179
- return null;
180
- const tool = match[1];
181
- // Real shell commands (echo, cat, ...) are never mistaken tool calls.
182
- if (NEVER_TOOL_CALLS.has(tool))
183
- return null;
184
- const rest = match[2].trim();
185
- // Only treat as a tool call when the first word looks like a snake_case
186
- // tool name and the rest has at least one '=' or a JSON object shape.
187
- if (!/^[a-z][a-z0-9_]+$/.test(tool))
188
- return null;
189
- if (!rest.includes("=") && !rest.startsWith("{"))
190
- return null;
191
- return { tool, args: rest };
192
- }
193
- /**
194
- * Parse the raw argument string of a mistaken tool call captured in a bash
195
- * command into a Record. Supports JSON objects ("{"path": "..."}") and
196
- * key=value pairs ("path=C:\...\src"). Values keep their literal text.
197
- */
198
- export function parseToolArgs(raw) {
199
- const trimmed = raw.trim();
200
- if (trimmed.startsWith("{")) {
201
- try {
202
- return JSON.parse(trimmed);
203
- }
204
- catch {
205
- /* fall through to key=value parsing */
206
- }
207
- }
208
- const args = {};
209
- // Tokenize respecting double/single-quoted values.
210
- const tokens = trimmed.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
211
- for (const token of tokens) {
212
- const eq = token.indexOf("=");
213
- if (eq > 0) {
214
- const key = token.slice(0, eq);
215
- const value = token.slice(eq + 1);
216
- // Strip surrounding quotes from values.
217
- args[key] = value.replace(/^["']|["']$/g, "");
218
- }
219
- }
220
- return args;
221
- }
222
- /**
223
- * Detect a test-runner report embedded in command output (bun test / vitest /
224
- * jest / mocha / `go test` / pytest). Returns null when the output does not
225
- * look like a test run. Used to inject a warning when tests fail so the model
226
- * cannot close a "run the tests" step as done.
227
- */
228
- export function detectTestResults(output) {
229
- if (!output)
230
- return null;
231
- // Framework-specific quantifiers first (unambiguous).
232
- const jest = output.match(/Tests:\s+(\d+)\s+passed,\s*(\d+)\s+failed/i);
233
- if (jest) {
234
- return { framework: "jest", passed: +jest[1], failed: +jest[2] };
235
- }
236
- const mochaPass = output.match(/(\d+)\s+passing/i);
237
- const mochaFail = output.match(/(\d+)\s+failing/i);
238
- if (mochaPass || mochaFail) {
239
- return {
240
- framework: "mocha",
241
- passed: mochaPass ? +mochaPass[1] : 0,
242
- failed: mochaFail ? +mochaFail[1] : 0,
243
- };
244
- }
245
- const pytest = output.match(/(\d+)\s+passed[^\n]*?(?:,\s*(\d+)\s+failed)?/i);
246
- if (pytest &&
247
- /pytest|passed|failed/i.test(output) &&
248
- /(=====|short test summary|tests\s+ok)/i.test(output) === false &&
249
- /pytest|collect/i.test(output)) {
250
- return {
251
- framework: "pytest",
252
- passed: +pytest[1],
253
- failed: pytest[2] ? +pytest[2] : 0,
254
- };
255
- }
256
- // bun test / vitest: per-case markers + trailing "N pass / M fail".
257
- const failMarkers = output.match(/\(fail\)/g)?.length ?? 0;
258
- const passMarkers = output.match(/\(pass\)/g)?.length ?? 0;
259
- const xMarkers = output.match(/\s×\s/g)?.length ?? 0;
260
- const summaryMatch = output.match(/^\s*(\d+)\s+pass[^\n]*$/m);
261
- const failSummary = output.match(/^\s*(\d+)\s+fail[^\n]*$/m);
262
- const ran = output.match(/(?:Ran|ran)\s+\d+\s+tests/i);
263
- if (failMarkers > 0 || passMarkers > 0 || xMarkers > 0 || ran) {
264
- const failed = Math.max(failMarkers + xMarkers, failSummary ? +failSummary[1] : 0);
265
- const passed = Math.max(passMarkers, summaryMatch ? +summaryMatch[1] : 0);
266
- return {
267
- framework: ran ? "bun/vitest" : "vitest",
268
- passed,
269
- failed,
270
- summary: summaryMatch?.[0] ?? failSummary?.[0],
271
- };
272
- }
273
- return null;
274
- }
275
- /**
276
- * R5: a command that runs a script file (bun/node/python/...) exited 0 but
277
- * printed nothing. The most common cause in agent sessions: the program has
278
- * no entry point — nobody calls its main() with the CLI args. Surface a
279
- * concrete hint instead of letting the model stare at an empty output.
280
- */
281
- const CLI_FILE_RUN_RE = /\b(bun|node|deno|python|python3|tsx|ts-node|php|ruby|go\s+run)\S*\s+(run\s+)?["']?[\w./\\-]+\.(ts|js|tsx|jsx|mjs|cjs|py)\b/;
282
- export function emptyCliRunHint(command, output, code) {
283
- if (code !== 0 || output.trim())
284
- return null;
285
- if (/[>|]/.test(command))
286
- return null; // redirection/pipes — output went elsewhere
287
- if (/\b(bun test|vitest|pytest|jest|mocha|--test)\b/i.test(command))
288
- return null;
289
- if (!CLI_FILE_RUN_RE.test(command))
290
- return null;
291
- return "the command exited 0 but printed NOTHING to stdout. If this should run a CLI program, the file probably has no entry point: read it with read_file and check the code actually calls its main function with command-line arguments (e.g. main(process.argv[2])) and prints results with console.log.";
292
- }
293
- /**
294
- * Detect npm/npx/bunx failing with "could not determine executable to run"
295
- * — the package declares no `bin` (or `npm run <script>` named a missing
296
- * script). Append a concrete hint instead of letting the model re-try blindly
297
- * (observed in two agent sessions: the model burned many iterations re-running
298
- * the same failing `npx`/`bunx`/`npm run` command).
299
- */
300
- const NPM_EXEC_RE = /could not determine executable to run/i;
301
- export function npmExecHint(output) {
302
- if (NPM_EXEC_RE.test(output)) {
303
- return `${output}\n\nHint: ${t("exec.npm_exec_hint")}`;
304
- }
305
- return output;
306
- }
307
- export const bashTool = {
308
- name: "bash",
309
- description: "Execute a shell command and return its output. Use for running tests, build, git, and shell operations. Commands that are still running after a few seconds are automatically moved to the background and return a process id — manage them with process_list, process_log, process_kill. Set background=true to return a process id immediately for commands you know are long-running (dev servers, watchers).\n\nWindows notes: the shell is cmd.exe — PowerShell cmdlets (Write-Host, Get-Content, Select-String, Out-File) and bash heredocs (cat << EOF) do NOT work there. Use the read_file/write_file tools instead of cat/echo redirection. Sequential commands: use && (a leading ; is auto-converted to &).",
310
- tags: ["shell", "code"],
311
- parameters: {
312
- type: "object",
313
- properties: {
314
- command: { type: "string", description: "Shell command to execute" },
315
- workdir: {
316
- type: "string",
317
- description: "Working directory (default: baseDir)",
318
- },
319
- background: {
320
- type: "boolean",
321
- description: "Return a process id immediately without waiting (default: commands still running after a few seconds are auto-promoted to the background)",
322
- },
323
- },
324
- required: ["command"],
325
- },
326
- handler: async (ctx, args) => {
327
- const originalCommand = String(args.command);
328
- // The model sometimes sends a tool invocation (e.g. "create_dir path=...")
329
- // as a bash command instead of calling the tool directly. Redirect into
330
- // the real tool so the intent succeeds instead of failing in the shell.
331
- const toolCall = detectToolCallInBash(originalCommand);
332
- if (toolCall &&
333
- toolCall.tool !== "bash" &&
334
- ctx.toolExecutor &&
335
- ctx.toolExecutor.hasTool(toolCall.tool)) {
336
- const redirected = await ctx.toolExecutor.executeByName(toolCall.tool, parseToolArgs(toolCall.args), ctx);
337
- return {
338
- success: redirected.success,
339
- output: `[redirected to tool "${toolCall.tool}"]\n${redirected.output}`,
340
- };
341
- }
342
- const command = adaptCommandForWindows(originalCommand);
343
- // echo/printf redirection to a file is unreliable in cmd.exe (single
344
- // quotes don't group, multi-line commands split, embedded double quotes
345
- // break the command). Steer the model to write_file instead — it
346
- // produces correct files every time.
347
- if (platform() === "win32") {
348
- const echoWrite = extractEchoFileWrite(originalCommand);
349
- if (echoWrite) {
350
- return {
351
- success: false,
352
- output: t("bash.echo_write_blocked", { path: echoWrite }),
353
- };
354
- }
355
- }
356
- const workdir = args.workdir ? String(args.workdir) : ctx.baseDir;
357
- // Get session-specific security config with defaults
358
- const appConfig = ctx.config || {};
359
- const fullSecurityConfig = ctx.sessionContext
360
- ? getSessionSecurityConfig(appConfig, ctx.sessionContext)
361
- : appConfig.security || DEFAULT_SECURITY_CONFIG;
362
- const securityConfig = fullSecurityConfig.bash || DEFAULT_SECURITY_CONFIG.bash;
363
- const validation = isCommandAllowed(command, securityConfig);
364
- if (!validation.allowed) {
365
- // Log security block
366
- logSecurityBlock(ctx.sessionId, "bash_command", validation.reason || "Command blocked by security policy", sanitizeCommandForLog(originalCommand));
367
- return {
368
- success: false,
369
- 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.`,
370
- };
371
- }
372
- // Log command execution if enabled
373
- if (securityConfig?.logCommands) {
374
- logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), false, // Will be updated after execution
375
- `Working directory: ${workdir}`);
376
- }
377
- const entry = processRegistry.start(command, workdir, ctx.sessionId);
378
- if (ctx.activeCallId) {
379
- // Let the executor abort/kill the child during the grace window.
380
- registerKillable(ctx.activeCallId, () => processRegistry.kill(entry.id));
381
- }
382
- try {
383
- const settleMs = args.background === true ? SPAWN_SETTLE_MS : bashGraceMs;
384
- const exited = await processRegistry.waitForExit(entry.id, settleMs);
385
- if (exited) {
386
- // Command finished (or failed to spawn) within the window.
387
- if (entry.spawnError) {
388
- return {
389
- success: false,
390
- output: `[process error] ${entry.spawnError}\nHint: check the "workdir" path exists and the command is valid for this OS.`,
391
- };
392
- }
393
- const code = entry.exitCode;
394
- let output = entry.log.join("\n");
395
- processRegistry.remove(entry.id);
396
- // R5: script file ran with exit 0 but empty output — likely a
397
- // missing entry point (main is never called with argv).
398
- const cliHint = emptyCliRunHint(command, output, code);
399
- if (cliHint) {
400
- output = `(exit code 0, no output)\n\nHint: ${cliHint}`;
401
- }
402
- // Auto-verify test runs: a failing suite must not be reported as a
403
- // clean success. Inject a prominent marker the model cannot miss
404
- // (the tpical failure: tests fail but the CLI exit code is 0).
405
- const testRun = detectTestResults(output);
406
- if (testRun && testRun.failed > 0) {
407
- output =
408
- `[test-runner] ${testRun.framework}: ${testRun.failed} test(s) FAILING, ${testRun.passed} passing — do NOT mark verification steps as done while tests fail. Investigate the failures, fix the code, then re-run the tests.\n\n` +
409
- output;
410
- }
411
- else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
412
- output = `[test-runner] ${testRun.framework}: all ${testRun.passed} test(s) passing.\n\n${output}`;
413
- }
414
- if (!output && code !== 0) {
415
- output = `(exit code ${code})`;
416
- }
417
- // npm/npx/bunx: "could not determine executable to run" — the
418
- // package has no `bin` entry (or the script does not exist).
419
- // This is cross-platform, so it runs before the win32 hint block.
420
- output = npmExecHint(output);
421
- // On Windows, hint about Unix commands that don't work, and
422
- // hard-stop a command that keeps failing the same way. The
423
- // hint keys on the ORIGINAL command word (before
424
- // adaptCommandForWindows translated cat→type): the translation
425
- // is only for execution, but the model wrote `cat`, and that is
426
- // what UNIX_TO_WIN_HINTS knows about. Keying on the translated
427
- // word left `cat` invisible to the hint/block (observed: model
428
- // ran cat 4+ times in one session with no guidance).
429
- if (platform() === "win32") {
430
- const originalFirstWord = originalCommand.trim().split(/\s+/)[0]?.split(/[\\/]/).pop();
431
- if (originalFirstWord && originalFirstWord in UNIX_TO_WIN_HINTS) {
432
- if (code === 0) {
433
- FAILING_FIRST_WORDS.delete(originalFirstWord);
434
- }
435
- else {
436
- const failures = (FAILING_FIRST_WORDS.get(originalFirstWord) || 0) + 1;
437
- FAILING_FIRST_WORDS.set(originalFirstWord, failures);
438
- if (failures >= HARD_BLOCK_THRESHOLD) {
439
- output = `STOP using "${originalFirstWord}" — it does not work in this cmd.exe shell and has failed ${failures} times in a row. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
440
- }
441
- else {
442
- output = `${output}\n\nHint: "${originalFirstWord}" may not work on Windows. ${UNIX_TO_WIN_HINTS[originalFirstWord]}`;
443
- }
444
- }
445
- }
446
- }
447
- // Update audit log with result
448
- if (securityConfig?.logCommands) {
449
- logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), code === 0, `Working directory: ${workdir}, Output length: ${output.length}`);
450
- }
451
- const lines = output.split("\n");
452
- if (testRun && testRun.failed > 0) {
453
- // Keep the failure details: test runners print passing markers
454
- // first and the errors + summary at the very end. A short head
455
- // preview hides exactly what the model needs to fix the failures.
456
- const TEST_TAIL_LINES = 400;
457
- const kept = lines.slice(-TEST_TAIL_LINES);
458
- const skipped = lines.length - kept.length;
459
- output =
460
- (skipped > 0 ? `[... ${skipped} earlier lines omitted — failing tests below]\n` : "") +
461
- kept.join("\n");
462
- }
463
- else if (lines.length > MAX_PREVIEW_LINES) {
464
- output =
465
- lines.slice(0, MAX_PREVIEW_LINES).join("\n") +
466
- `\n... (${lines.length - MAX_PREVIEW_LINES} more lines)`;
467
- }
468
- return { success: code === 0, output };
469
- }
470
- // Still running — promote to a background process.
471
- const explicit = args.background === true;
472
- const preview = entry.log
473
- .filter((l) => l.trim() !== "")
474
- .slice(0, BG_OUTPUT_PREVIEW_LINES)
475
- .join("\n");
476
- let output = `${t("proc.started", {
477
- id: entry.id,
478
- pid: entry.pid,
479
- command,
480
- })}${explicit ? "" : `\n${t("proc.promoted_hint", { ms: settleMs })}`}`;
481
- if (preview) {
482
- output += `\n\n${t("proc.output_preview", { lines: preview })}`;
483
- }
484
- output += `\n${t("proc.manage_hint", { id: entry.id })}`;
485
- if (securityConfig?.logCommands) {
486
- logBashCommand(ctx.sessionId, sanitizeCommandForLog(command), true, `Started in background: ${entry.id} (PID ${entry.pid})`);
487
- }
488
- return { success: true, output };
489
- }
490
- finally {
491
- if (ctx.activeCallId) {
492
- unregisterKillable(ctx.activeCallId);
493
- }
494
- }
495
- },
496
- };
@@ -1,114 +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
- maxContentChars: ctx.config.browser?.maxContentChars ?? 2500,
14
- maxConsoleEntries: ctx.config.browser?.maxConsoleEntries ?? 40,
15
- maxConsoleLineChars: ctx.config.browser?.maxConsoleLineChars ?? 400,
16
- navigationTimeout: ctx.config.browser?.navigationTimeout ?? 15000,
17
- viewportWidth: ctx.config.browser?.viewportWidth ?? 1280,
18
- viewportHeight: ctx.config.browser?.viewportHeight ?? 720,
19
- cookieDir,
20
- });
21
- }
22
- return session;
23
- }
24
- export function formatScreenshotForTextModel(snapshot) {
25
- return `${t("tool.screenshot_unavailable")}\n\n${snapshot}`;
26
- }
27
- export function formatScreenshotResult(snapshot, screenshot, supportsVision) {
28
- if (!supportsVision) {
29
- return formatScreenshotForTextModel(snapshot);
30
- }
31
- const base64 = screenshot.toString("base64");
32
- return `${snapshot}\n\n[Screenshot: data:image/png;base64,${base64}]`;
33
- }
34
- export function createBrowserTool() {
35
- return {
36
- name: "browser",
37
- tags: ["browser", "vision"],
38
- description: [
39
- "Control a headless browser. Navigate pages, click elements, type text, scroll, take screenshots.",
40
- "Runs a real browser with JavaScript enabled — use it for JS-rendered sites (weather, dashboards, SPAs) where web_fetch/web_browse return empty content.",
41
- "Do NOT use it for search queries: search engines (Google, Yandex, Bing) block headless browsers with CAPTCHA/429/redirect pages — use the web_search tool for searching.",
42
- "Each snapshot returns: a numbered list of interactive elements, the visible page text (Content section), browser console messages, and network errors.",
43
- "Use the Content section to understand what the page says; use element numbers as targets for click/type.",
44
- "Actions: open (url), click (target), type (target, text), scroll (direction), back, forward, screenshot, snapshot, close, wait (ms).",
45
- ].join(" "),
46
- parameters: {
47
- type: "object",
48
- properties: {
49
- action: {
50
- type: "string",
51
- enum: [
52
- "open",
53
- "click",
54
- "type",
55
- "scroll",
56
- "back",
57
- "forward",
58
- "screenshot",
59
- "snapshot",
60
- "close",
61
- "wait",
62
- ],
63
- description: "Browser action to perform",
64
- },
65
- url: {
66
- type: "string",
67
- description: 'URL to open (for action "open")',
68
- },
69
- target: {
70
- type: "number",
71
- description: 'Element number from snapshot (for action "click" or "type")',
72
- },
73
- text: {
74
- type: "string",
75
- description: 'Text to type (for action "type")',
76
- },
77
- direction: {
78
- type: "string",
79
- enum: ["up", "down", "top", "bottom"],
80
- description: 'Scroll direction (for action "scroll", default "down")',
81
- },
82
- ms: {
83
- type: "number",
84
- description: 'Milliseconds to wait (for action "wait", default 1000)',
85
- },
86
- },
87
- required: ["action"],
88
- },
89
- handler: async (ctx, args) => {
90
- const action = String(args.action || "");
91
- if (!action) {
92
- return { success: false, output: t("tool.action_required") };
93
- }
94
- const s = getSession(ctx);
95
- const result = await s.execute(action, args);
96
- const supportsVision = ctx.config.model.includes("vision") ||
97
- ctx.config.model.includes("gpt-4o") ||
98
- ctx.config.model.includes("claude");
99
- if (result.screenshot) {
100
- return {
101
- success: result.success,
102
- output: formatScreenshotResult(result.output, result.screenshot, supportsVision),
103
- };
104
- }
105
- return { success: result.success, output: result.output };
106
- },
107
- };
108
- }
109
- export function closeBrowserSession() {
110
- if (session) {
111
- session.close().catch(() => { });
112
- session = null;
113
- }
114
- }