micro-models-agent 0.54.1 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +358 -358
  2. package/dist/certification/certifications.json +493 -493
  3. package/dist/main.js +489 -82
  4. package/dist/modules/browser/bridge-server.mjs +202 -202
  5. package/dist/skills/builtin/git.md +36 -36
  6. package/dist/skills/builtin/typescript.md +35 -35
  7. package/package.json +1 -1
  8. package/dist/cli/commands.js +0 -447
  9. package/dist/cli/completer.js +0 -167
  10. package/dist/cli/index.js +0 -2
  11. package/dist/cli/main.js +0 -153
  12. package/dist/cli/plugin-commands.js +0 -36
  13. package/dist/cli/repl-commands.js +0 -761
  14. package/dist/cli/repl.js +0 -702
  15. package/dist/cli/run-result.js +0 -33
  16. package/dist/cli/security-commands.js +0 -164
  17. package/dist/cli/setup.js +0 -237
  18. package/dist/config/config.js +0 -276
  19. package/dist/config/defaults.js +0 -141
  20. package/dist/config/domains.js +0 -179
  21. package/dist/config/experts.js +0 -15
  22. package/dist/config/index.js +0 -4
  23. package/dist/config/security.js +0 -213
  24. package/dist/config/types.js +0 -1
  25. package/dist/core/agent-moe.js +0 -102
  26. package/dist/core/agent.js +0 -1018
  27. package/dist/core/bootstrap.js +0 -481
  28. package/dist/core/crash-handler.js +0 -51
  29. package/dist/core/environment.js +0 -199
  30. package/dist/core/index.js +0 -2
  31. package/dist/core/prompt-builder.js +0 -76
  32. package/dist/core/session-logger.js +0 -251
  33. package/dist/core/types.js +0 -1
  34. package/dist/core/version.js +0 -26
  35. package/dist/core/workspace.js +0 -76
  36. package/dist/i18n/en.json +0 -679
  37. package/dist/i18n/index.js +0 -46
  38. package/dist/i18n/ru.json +0 -679
  39. package/dist/index.js +0 -22
  40. package/dist/llm/image-utils.js +0 -143
  41. package/dist/llm/index.js +0 -4
  42. package/dist/llm/model-loader.js +0 -78
  43. package/dist/llm/openai-compat.js +0 -497
  44. package/dist/llm/orchestrator.js +0 -200
  45. package/dist/llm/provider.js +0 -10
  46. package/dist/llm/response.js +0 -39
  47. package/dist/llm/token-counter.js +0 -39
  48. package/dist/llm/types.js +0 -1
  49. package/dist/logger/app-logger.js +0 -189
  50. package/dist/logger/file-log.js +0 -151
  51. package/dist/logger/index.js +0 -1
  52. package/dist/migration/backup.js +0 -45
  53. package/dist/migration/detect.js +0 -50
  54. package/dist/migration/index.js +0 -2
  55. package/dist/modules/artifacts/store.js +0 -61
  56. package/dist/modules/browser/actions.js +0 -76
  57. package/dist/modules/browser/bridge-client.js +0 -199
  58. package/dist/modules/browser/bridge-path.js +0 -10
  59. package/dist/modules/browser/cookie-store.js +0 -24
  60. package/dist/modules/browser/driver.js +0 -136
  61. package/dist/modules/browser/index.js +0 -7
  62. package/dist/modules/browser/module.js +0 -29
  63. package/dist/modules/browser/session.js +0 -342
  64. package/dist/modules/browser/snapshot.js +0 -148
  65. package/dist/modules/browser/types.js +0 -12
  66. package/dist/modules/certification/cli.js +0 -213
  67. package/dist/modules/certification/fact-checker.js +0 -82
  68. package/dist/modules/certification/loader.js +0 -106
  69. package/dist/modules/certification/manifest.js +0 -58
  70. package/dist/modules/certification/runner.js +0 -245
  71. package/dist/modules/certification/scenarios.js +0 -407
  72. package/dist/modules/certification/types.js +0 -1
  73. package/dist/modules/context/chunk-query.js +0 -100
  74. package/dist/modules/context/fact-extractor.js +0 -168
  75. package/dist/modules/context/history.js +0 -15
  76. package/dist/modules/context/index.js +0 -1
  77. package/dist/modules/context/manager.js +0 -440
  78. package/dist/modules/execution/audit-runners.js +0 -206
  79. package/dist/modules/execution/auditor.js +0 -218
  80. package/dist/modules/execution/execution-plugin.js +0 -431
  81. package/dist/modules/execution/index.js +0 -8
  82. package/dist/modules/execution/module.js +0 -625
  83. package/dist/modules/execution/moe-executor.js +0 -304
  84. package/dist/modules/execution/plan-coverage.js +0 -68
  85. package/dist/modules/execution/plan-persister.js +0 -46
  86. package/dist/modules/execution/plan-store.js +0 -196
  87. package/dist/modules/execution/plan-tool.js +0 -677
  88. package/dist/modules/execution/plan-validator.js +0 -153
  89. package/dist/modules/execution/planner.js +0 -94
  90. package/dist/modules/execution/stuck-detector.js +0 -746
  91. package/dist/modules/execution/tracker.js +0 -69
  92. package/dist/modules/execution/types.js +0 -1
  93. package/dist/modules/execution/verifier.js +0 -235
  94. package/dist/modules/execution/windows-commands.js +0 -41
  95. package/dist/modules/hallucination/confidence.js +0 -66
  96. package/dist/modules/hallucination/consistency.js +0 -26
  97. package/dist/modules/hallucination/detector.js +0 -47
  98. package/dist/modules/hallucination/factual.js +0 -169
  99. package/dist/modules/hallucination/index.js +0 -5
  100. package/dist/modules/hallucination/js-identifiers.js +0 -262
  101. package/dist/modules/hallucination/llm-judge.js +0 -101
  102. package/dist/modules/index.js +0 -5
  103. package/dist/modules/indexer/cache.js +0 -40
  104. package/dist/modules/indexer/index.js +0 -3
  105. package/dist/modules/indexer/module.js +0 -246
  106. package/dist/modules/indexer/project-profile.js +0 -183
  107. package/dist/modules/indexer/walker.js +0 -101
  108. package/dist/modules/lsp/check-tool.js +0 -58
  109. package/dist/modules/lsp/client.js +0 -389
  110. package/dist/modules/lsp/command.js +0 -60
  111. package/dist/modules/lsp/config.js +0 -135
  112. package/dist/modules/lsp/index.js +0 -3
  113. package/dist/modules/lsp/module.js +0 -260
  114. package/dist/modules/lsp/probe.js +0 -86
  115. package/dist/modules/lsp/project-root.js +0 -32
  116. package/dist/modules/lsp/startup-check.js +0 -144
  117. package/dist/modules/lsp/types.js +0 -1
  118. package/dist/modules/mcp/client.js +0 -399
  119. package/dist/modules/mcp/index.js +0 -3
  120. package/dist/modules/mcp/module.js +0 -142
  121. package/dist/modules/mcp/registry.js +0 -15
  122. package/dist/modules/memory/index.js +0 -1
  123. package/dist/modules/memory/module.js +0 -96
  124. package/dist/modules/memory/search.js +0 -42
  125. package/dist/modules/memory/store.js +0 -69
  126. package/dist/modules/pipelines/engine.js +0 -60
  127. package/dist/modules/pipelines/index.js +0 -3
  128. package/dist/modules/pipelines/parser.js +0 -56
  129. package/dist/modules/pipelines/template.js +0 -14
  130. package/dist/modules/plugins/builtin/lint-on-write.js +0 -334
  131. package/dist/modules/plugins/builtin/notify.js +0 -9
  132. package/dist/modules/plugins/index.js +0 -1
  133. package/dist/modules/plugins/loader.js +0 -70
  134. package/dist/modules/plugins/manager.js +0 -261
  135. package/dist/modules/plugins/types.js +0 -1
  136. package/dist/modules/pricing/index.js +0 -61
  137. package/dist/modules/pricing/prices.js +0 -129
  138. package/dist/modules/processes/detect.js +0 -34
  139. package/dist/modules/processes/index.js +0 -2
  140. package/dist/modules/processes/registry.js +0 -327
  141. package/dist/modules/processes/runner.js +0 -23
  142. package/dist/modules/providers/create.js +0 -22
  143. package/dist/modules/providers/fallback.js +0 -79
  144. package/dist/modules/providers/health.js +0 -46
  145. package/dist/modules/providers/index.js +0 -5
  146. package/dist/modules/providers/manager.js +0 -161
  147. package/dist/modules/providers/presets.js +0 -128
  148. package/dist/modules/providers/registry.js +0 -22
  149. package/dist/modules/providers/types.js +0 -1
  150. package/dist/modules/registry.js +0 -48
  151. package/dist/modules/security/audit-log.js +0 -136
  152. package/dist/modules/security/audit-notifier.js +0 -292
  153. package/dist/modules/security/command-validator.js +0 -219
  154. package/dist/modules/security/content-scanner.js +0 -53
  155. package/dist/modules/security/data-sanitizer.js +0 -89
  156. package/dist/modules/security/encryption.js +0 -242
  157. package/dist/modules/security/index.js +0 -14
  158. package/dist/modules/security/network-validator.js +0 -88
  159. package/dist/modules/security/path-validator.js +0 -203
  160. package/dist/modules/security/rate-limiter.js +0 -119
  161. package/dist/modules/security/security-policies.js +0 -531
  162. package/dist/modules/security/session-encryption.js +0 -210
  163. package/dist/modules/security/session-isolation.js +0 -95
  164. package/dist/modules/session/index.js +0 -3
  165. package/dist/modules/session/manager.js +0 -172
  166. package/dist/modules/session/module.js +0 -24
  167. package/dist/modules/session/store.js +0 -222
  168. package/dist/modules/session/types.js +0 -1
  169. package/dist/modules/skills/index.js +0 -2
  170. package/dist/modules/skills/loader.js +0 -72
  171. package/dist/modules/skills/matcher.js +0 -27
  172. package/dist/modules/skills/module.js +0 -129
  173. package/dist/modules/types.js +0 -1
  174. package/dist/modules/updater/checker.js +0 -96
  175. package/dist/modules/updater/index.js +0 -2
  176. package/dist/modules/updater/module.js +0 -116
  177. package/dist/modules/user-profile/compressor.js +0 -16
  178. package/dist/modules/user-profile/index.js +0 -1
  179. package/dist/modules/user-profile/profile.js +0 -68
  180. package/dist/tools/approve.js +0 -33
  181. package/dist/tools/attach-image.js +0 -101
  182. package/dist/tools/bash.js +0 -519
  183. package/dist/tools/browser.js +0 -115
  184. package/dist/tools/chunk-query.js +0 -100
  185. package/dist/tools/create-dir.js +0 -56
  186. package/dist/tools/delete-file.js +0 -63
  187. package/dist/tools/download-file.js +0 -117
  188. package/dist/tools/edit-file.js +0 -80
  189. package/dist/tools/enable-tools.js +0 -59
  190. package/dist/tools/executor.js +0 -154
  191. package/dist/tools/file-info.js +0 -47
  192. package/dist/tools/filter-tools.js +0 -17
  193. package/dist/tools/glob-tool.js +0 -27
  194. package/dist/tools/grep-tool.js +0 -125
  195. package/dist/tools/hidden-tools-block.js +0 -37
  196. package/dist/tools/index.js +0 -78
  197. package/dist/tools/list-dir.js +0 -49
  198. package/dist/tools/load-skill.js +0 -43
  199. package/dist/tools/mcp-call.js +0 -69
  200. package/dist/tools/move-file.js +0 -86
  201. package/dist/tools/path-utils.js +0 -101
  202. package/dist/tools/pipeline-run.js +0 -145
  203. package/dist/tools/preview.js +0 -2
  204. package/dist/tools/process-kill.js +0 -40
  205. package/dist/tools/process-list.js +0 -37
  206. package/dist/tools/process-log.js +0 -54
  207. package/dist/tools/question.js +0 -141
  208. package/dist/tools/read-file.js +0 -179
  209. package/dist/tools/recall.js +0 -118
  210. package/dist/tools/registry.js +0 -47
  211. package/dist/tools/remember.js +0 -68
  212. package/dist/tools/scope-check.js +0 -32
  213. package/dist/tools/search-history.js +0 -85
  214. package/dist/tools/subagent.js +0 -196
  215. package/dist/tools/types.js +0 -1
  216. package/dist/tools/user-input.js +0 -123
  217. package/dist/tools/web-browse.js +0 -87
  218. package/dist/tools/web-fetch.js +0 -119
  219. package/dist/tools/web-search.js +0 -105
  220. package/dist/tools/write-file.js +0 -82
  221. package/dist/ui/box.js +0 -77
  222. package/dist/ui/colors.js +0 -4
  223. package/dist/ui/diff.js +0 -178
  224. package/dist/ui/index.js +0 -6
  225. package/dist/ui/line-editor.js +0 -822
  226. package/dist/ui/line-math.js +0 -73
  227. package/dist/ui/md-formatter.js +0 -212
  228. package/dist/ui/output.js +0 -13
  229. package/dist/ui/plan-view.js +0 -103
  230. package/dist/ui/renderer.js +0 -259
  231. package/dist/ui/spinner.js +0 -70
  232. package/dist/ui/table.js +0 -144
@@ -1,746 +0,0 @@
1
- import { t } from "../../i18n/index";
2
- const TRANSIENT_PATTERNS = [
3
- /timeout/i,
4
- /timed out/i,
5
- /ECONNREFUSED/i,
6
- /ETIMEDOUT/i,
7
- /ECONNRESET/i,
8
- /killed/i,
9
- /SIGTERM/i,
10
- /SIGKILL/i,
11
- /rate limit/i,
12
- /429/i,
13
- /503/i,
14
- /502/i,
15
- ];
16
- const BLOCKING_PATTERNS = [
17
- /EACCES/i,
18
- /permission denied/i,
19
- /EPERM/i,
20
- /ENOENT.*node_modules/i,
21
- /ENOTDIR/i,
22
- /invalid.*token/i,
23
- /unauthorized/i,
24
- /401/i,
25
- /403/i,
26
- ];
27
- export function classifyError(output) {
28
- for (const p of TRANSIENT_PATTERNS) {
29
- if (p.test(output))
30
- return "transient";
31
- }
32
- for (const p of BLOCKING_PATTERNS) {
33
- if (p.test(output))
34
- return "blocking";
35
- }
36
- return "systematic";
37
- }
38
- /**
39
- * Tools that only observe state and never change it. A long run of only these
40
- * with no write/exec in between means the model is reading/exploring without
41
- * producing anything — a read-only loop that the stuck detector must surface
42
- * (observed: 50+ read_file iterations with zero writes after a false-positive
43
- * audit rejected a completed task).
44
- */
45
- const READ_ONLY_TOOLS = new Set([
46
- "read_file",
47
- "glob",
48
- "grep",
49
- "list_dir",
50
- "file_info",
51
- "browser",
52
- "web_search",
53
- "web_fetch",
54
- "web_browse",
55
- "search_history",
56
- "recall",
57
- "project_map",
58
- "chunk_query",
59
- "process_list",
60
- "process_log",
61
- "plan",
62
- "todo",
63
- "verify",
64
- "load_skill",
65
- ]);
66
- /** Consecutive read-only tool calls that constitute a loop. */
67
- const READ_ONLY_LOOP_THRESHOLD = 10;
68
- /**
69
- * Bash flailing: ≥BASH_FLAIL_THRESHOLD failures within the last
70
- * BASH_FLAIL_WINDOW bash attempts, regardless of interleaved successes.
71
- * The per-word hard-stop (bash.ts) is evaded by rotating commands
72
- * (wc → cat → Get-Content → …); this window catches the rotation itself
73
- * (observed in ses_mt4x3zbo: 8 failed shell tricks across ~12 iterations
74
- * trying to count/merge files on Windows).
75
- */
76
- const BASH_FLAIL_WINDOW = 6;
77
- const BASH_FLAIL_THRESHOLD = 4;
78
- const ANSI_RE = /\x1b\[[0-9;]*m/g;
79
- /** Пути (абсолютные/относительные) + опционально :line:col. */
80
- const ERROR_LOC_RE = /(?:[A-Za-z]:[\\/])?(?:[\w.@+-]+[\\/])+[\w.@+-]+\.[a-zA-Z0-9]{1,6}(?::\d+(?::\d+)?)?/g;
81
- const SIGNATURE_MAX_CHARS = 120;
82
- const SEARCHABLE_PATTERNS = [
83
- /error TS\d+/i,
84
- /ERR_[A-Z_]+/,
85
- /ENOENT|EACCES|EPERM|ECONNREFUSED|ETIMEDOUT|ECONNRESET|ENOTFOUND|EADDRINUSE/i,
86
- /Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i,
87
- /TypeError|ReferenceError|SyntaxError|RangeError/i,
88
- /is not a function|is not defined|is not a constructor/i,
89
- /Cannot read properties of/i,
90
- /failed to (compile|build|resolve|parse)/i,
91
- /fatal error|panic/i,
92
- ];
93
- const RUNTIME_WORDS = [
94
- "bun",
95
- "node",
96
- "deno",
97
- "npm",
98
- "yarn",
99
- "pnpm",
100
- "pip",
101
- "pip3",
102
- "python",
103
- "python3",
104
- "tsc",
105
- "tsx",
106
- "npx",
107
- "go",
108
- "cargo",
109
- "gradle",
110
- "maven",
111
- "make",
112
- ];
113
- const FRAMEWORK_WORDS = [
114
- "react",
115
- "vue",
116
- "angular",
117
- "svelte",
118
- "vite",
119
- "next",
120
- "nest",
121
- "express",
122
- "fastify",
123
- "flask",
124
- "django",
125
- "spring",
126
- "webpack",
127
- "tailwind",
128
- ];
129
- /**
130
- * Stable identity for an error: tool + normalized first error lines (ANSI
131
- * stripped, paths/locations replaced, whitespace collapsed, capped at 120
132
- * chars). Two runs failing with the same root cause produce the same
133
- * signature — that is what triggers the automatic web search.
134
- */
135
- export function normalizeErrorSignature(toolName, output) {
136
- if (!output)
137
- return null;
138
- const cleaned = output
139
- .replace(ANSI_RE, "")
140
- .replace(ERROR_LOC_RE, " <path>")
141
- .split("\n")
142
- .map((l) => l.trim())
143
- .filter(Boolean)
144
- .join(" ")
145
- .replace(/\s+/g, " ")
146
- .trim();
147
- if (!cleaned)
148
- return null;
149
- return `${toolName}::${cleaned.slice(0, SIGNATURE_MAX_CHARS)}`;
150
- }
151
- /**
152
- * Gate for the automatic web search: only real, meaningful error text is
153
- * worth searching for. "Command failed with exit code 1" without details is
154
- * noise — searching it would just burn a query.
155
- */
156
- export function isSearchableError(output) {
157
- if (!output || !output.trim())
158
- return false;
159
- const text = output.trim();
160
- return SEARCHABLE_PATTERNS.some((p) => p.test(text));
161
- }
162
- /**
163
- * Build the search query for a failing error: up to 3 cleaned error lines
164
- * (paths stripped), prefixed with the runtime and framework detected from the
165
- * step/bash context, suffixed with "how to fix". Falls back to `tool step`
166
- * when the error text is empty. Capped at maxQueryChars.
167
- */
168
- export function buildSearchQuery(stepDescription, lastBashCommand, lastErrorOutput, toolName, maxQueryChars) {
169
- const errorText = (lastErrorOutput || "")
170
- .replace(ANSI_RE, "")
171
- .replace(ERROR_LOC_RE, " <path>")
172
- .split("\n")
173
- .map((l) => l.trim())
174
- .filter(Boolean)
175
- .slice(0, 3)
176
- .join(" ")
177
- .replace(/\s+/g, " ")
178
- .trim();
179
- const context = `${stepDescription} ${lastBashCommand}`.toLowerCase();
180
- const runtime = RUNTIME_WORDS.find((w) => context.includes(w)) || "";
181
- const framework = FRAMEWORK_WORDS.find((w) => context.includes(w)) || "";
182
- let query;
183
- if (errorText) {
184
- query =
185
- runtime && !errorText.toLowerCase().includes(runtime) ? `${runtime} ${errorText}` : errorText;
186
- if (framework && !query.toLowerCase().includes(framework))
187
- query = `${query} ${framework}`;
188
- query = `${query} how to fix`;
189
- }
190
- else if (runtime || framework) {
191
- query = `${[runtime, framework, "error", "how to fix"].filter(Boolean).join(" ")}`;
192
- }
193
- else {
194
- query = `${toolName} ${stepDescription} how to fix`;
195
- }
196
- return query.slice(0, maxQueryChars);
197
- }
198
- export class StuckDetector {
199
- threshold;
200
- errorThreshold;
201
- iterationsOnCurrentStep = 0;
202
- currentStepId = null;
203
- toolErrors = new Map();
204
- currentStepDescription = "";
205
- recentToolCalls = [];
206
- maxRecentCalls = 10;
207
- repetitionThreshold = 3;
208
- consecutiveFailures = 0;
209
- lastFailedTool = "";
210
- lastErrorOutput = "";
211
- escalationCount = 0;
212
- escalationThreshold = 3;
213
- fileRewriteCount = new Map();
214
- fileRewriteThreshold = 3;
215
- lastBashCommand = "";
216
- lastBashOutput = "";
217
- emptyBashRunCount = 0;
218
- readOnlyStreak = 0;
219
- bashRecent = [];
220
- errorSignatureCounts = new Map();
221
- searchedErrorSignatures = new Set();
222
- webSearchThreshold;
223
- constructor(threshold = 6, errorThreshold = 3, webSearchThreshold = 5) {
224
- this.threshold = threshold;
225
- this.errorThreshold = errorThreshold;
226
- this.webSearchThreshold = webSearchThreshold;
227
- }
228
- recordIteration(stepId) {
229
- if (stepId === this.currentStepId) {
230
- this.iterationsOnCurrentStep++;
231
- }
232
- else {
233
- this.resetStepState(stepId);
234
- }
235
- }
236
- recordToolCall(name, args) {
237
- const argsKey = JSON.stringify(args);
238
- this.recentToolCalls.push({ name, argsKey });
239
- if (this.recentToolCalls.length > this.maxRecentCalls) {
240
- this.recentToolCalls.shift();
241
- }
242
- // Track the consecutive read-only streak. A write/exec tool resets it;
243
- // plan/todo bookkeeping counts as read-only (it is not real progress).
244
- if (READ_ONLY_TOOLS.has(name)) {
245
- this.readOnlyStreak++;
246
- }
247
- else {
248
- this.readOnlyStreak = 0;
249
- }
250
- }
251
- /**
252
- * True after a long run of read-only tool calls with no write in between.
253
- * The model is exploring/looping without producing anything — inject a
254
- * recovery hint so it stops re-reading and actually edits files.
255
- */
256
- hasReadOnlyLoop() {
257
- return this.readOnlyStreak >= READ_ONLY_LOOP_THRESHOLD;
258
- }
259
- recordToolError(toolName, output) {
260
- this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
261
- this.consecutiveFailures++;
262
- this.lastFailedTool = toolName;
263
- if (output)
264
- this.lastErrorOutput = output;
265
- const sig = normalizeErrorSignature(toolName, output);
266
- if (sig) {
267
- this.errorSignatureCounts.set(sig, (this.errorSignatureCounts.get(sig) || 0) + 1);
268
- }
269
- }
270
- getLastErrorOutput() {
271
- return this.lastErrorOutput;
272
- }
273
- /**
274
- * Record every bash invocation (successful or not) so stuck detection can
275
- * reason about repeated runs with empty output, entry-point issues, and
276
- * platform mistakes (PowerShell cmdlets in cmd.exe, bash heredocs, ...).
277
- */
278
- recordBashOutput(command, output) {
279
- const trimmed = output.trim();
280
- if (command === this.lastBashCommand && !trimmed) {
281
- this.emptyBashRunCount++;
282
- }
283
- else {
284
- this.emptyBashRunCount = 0;
285
- }
286
- this.lastBashCommand = command;
287
- this.lastBashOutput = output;
288
- }
289
- /** Outcome of one bash attempt (success or failure), for flailing detection. */
290
- recordBashAttempt(success) {
291
- this.bashRecent.push(success);
292
- if (this.bashRecent.length > BASH_FLAIL_WINDOW)
293
- this.bashRecent.shift();
294
- }
295
- /** True when most recent bash attempts failed — the model is flailing. */
296
- hasBashFlailing() {
297
- if (this.bashRecent.length < BASH_FLAIL_THRESHOLD)
298
- return false;
299
- let failures = 0;
300
- for (const ok of this.bashRecent)
301
- if (!ok)
302
- failures++;
303
- return failures >= BASH_FLAIL_THRESHOLD;
304
- }
305
- getLastBashCommand() {
306
- return this.lastBashCommand;
307
- }
308
- getLastBashOutput() {
309
- return this.lastBashOutput;
310
- }
311
- getLastFailedTool() {
312
- return this.lastFailedTool;
313
- }
314
- /**
315
- * First error signature whose repetition count reached the web-search
316
- * threshold and that has NOT been searched yet (or was unmarked for a
317
- * retry). This is what triggers the automatic web search — not iterations
318
- * and not the per-tool error count.
319
- */
320
- getRepeatedErrorSignature() {
321
- for (const [sig, count] of this.errorSignatureCounts) {
322
- if (count >= this.webSearchThreshold && !this.searchedErrorSignatures.has(sig)) {
323
- return sig;
324
- }
325
- }
326
- return null;
327
- }
328
- getErrorSignatureCount(sig) {
329
- return this.errorSignatureCounts.get(sig) || 0;
330
- }
331
- markErrorSearched(sig) {
332
- this.searchedErrorSignatures.add(sig);
333
- }
334
- unmarkErrorSearched(sig) {
335
- this.searchedErrorSignatures.delete(sig);
336
- }
337
- hasErrorSearched(sig) {
338
- return this.searchedErrorSignatures.has(sig);
339
- }
340
- getIterationsOnCurrentStep() {
341
- return this.iterationsOnCurrentStep;
342
- }
343
- recordToolSuccess() {
344
- this.consecutiveFailures = 0;
345
- this.lastFailedTool = "";
346
- this.escalationCount = 0;
347
- // Decay per-tool error counters on productive calls: a tool that failed
348
- // early must not keep the "Tool X failed N times" warning alive forever
349
- // (ses_mt4fn58c — bash failed 3x, warning re-logged after 30 successful
350
- // writes). Fresh failures rebuild the count.
351
- for (const [tool, count] of this.toolErrors) {
352
- if (count <= 1)
353
- this.toolErrors.delete(tool);
354
- else
355
- this.toolErrors.set(tool, count - 1);
356
- }
357
- // Decay iteration counter on productive tool calls — reading new files,
358
- // writing, editing all count as progress. Prevents false-positive stuck
359
- // warnings during legitimate file exploration (e.g. reading 12 files in
360
- // an analysis step).
361
- if (this.iterationsOnCurrentStep > 0) {
362
- this.iterationsOnCurrentStep = Math.max(0, this.iterationsOnCurrentStep - 1);
363
- }
364
- }
365
- setCurrentStep(stepId, description) {
366
- if (stepId !== this.currentStepId) {
367
- this.resetStepState(stepId);
368
- }
369
- this.currentStepDescription = description;
370
- }
371
- isStuck() {
372
- return this.iterationsOnCurrentStep > this.threshold;
373
- }
374
- hasRepetitiveErrors(toolName) {
375
- if (toolName) {
376
- return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
377
- }
378
- return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
379
- }
380
- hasRepetitiveToolCalls() {
381
- if (this.recentToolCalls.length < this.repetitionThreshold)
382
- return false;
383
- const last = this.recentToolCalls[this.recentToolCalls.length - 1];
384
- // Check consecutive identical calls (original logic)
385
- let consecutiveCount = 1;
386
- for (let i = this.recentToolCalls.length - 2; i >= 0; i--) {
387
- if (this.recentToolCalls[i].name === last.name &&
388
- this.recentToolCalls[i].argsKey === last.argsKey) {
389
- consecutiveCount++;
390
- }
391
- else {
392
- break;
393
- }
394
- }
395
- if (consecutiveCount >= this.repetitionThreshold)
396
- return true;
397
- // Also detect interleaved patterns (A, B, A, B, A) — count any single
398
- // tool+args combo in the window and flag if it appears >= threshold times
399
- const counts = new Map();
400
- for (const call of this.recentToolCalls) {
401
- const key = `${call.name}::${call.argsKey}`;
402
- counts.set(key, (counts.get(key) || 0) + 1);
403
- }
404
- for (const count of counts.values()) {
405
- if (count >= this.repetitionThreshold)
406
- return true;
407
- }
408
- return false;
409
- }
410
- hasConsecutiveFailures() {
411
- return this.consecutiveFailures >= this.errorThreshold;
412
- }
413
- getConsecutiveFailuresCount() {
414
- return this.consecutiveFailures;
415
- }
416
- recordEscalation() {
417
- this.escalationCount++;
418
- }
419
- shouldEscalate() {
420
- return this.escalationCount >= this.escalationThreshold;
421
- }
422
- resetEscalation() {
423
- this.escalationCount = 0;
424
- }
425
- getEscalationCount() {
426
- return this.escalationCount;
427
- }
428
- getHints() {
429
- const hints = [];
430
- const desc = this.currentStepDescription.toLowerCase();
431
- if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
432
- hints.push(t("exec.hint_deps"));
433
- }
434
- if (desc.includes("test") || desc.includes("spec")) {
435
- hints.push(t("exec.hint_test"));
436
- }
437
- if (desc.includes("build") || desc.includes("compile")) {
438
- hints.push(t("exec.hint_build"));
439
- }
440
- if (desc.includes("deploy") || desc.includes("publish")) {
441
- hints.push(t("exec.hint_deploy"));
442
- }
443
- // State-based bullets. The recovery message (getRecoveryMessage) already
444
- // reports the PRIMARY reason (priority: stuck > tool-errors > consecutive
445
- // > repetitive > read-only). Repeating that same state here as a hint
446
- // would duplicate the guidance in the same cooldown block. Only add the
447
- // bullet when the recovery message is NOT already covering that state.
448
- const primary = this.getPrimaryReason();
449
- if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
450
- hints.push(t("exec.hint_repetitive"));
451
- }
452
- if (this.hasConsecutiveFailures() && primary !== "consecutive") {
453
- hints.push(t("exec.hint_consecutive"));
454
- }
455
- if (this.hasReadOnlyLoop() && primary !== "read-only") {
456
- hints.push(t("exec.hint_read_only"));
457
- }
458
- return hints;
459
- }
460
- getActionableHints() {
461
- const hints = [];
462
- const error = this.lastErrorOutput;
463
- // Bash-specific hints do not depend on a tool failure — they fire even
464
- // when the command "succeeded" with empty output (missing entry point).
465
- const isScriptRun = /(^|[\s&])(bun|node|tsx|ts-node|deno|python|python3)\S*\s+[^|&]+\s+[^\s]+$/.test(this.lastBashCommand);
466
- if (isScriptRun && !this.lastBashOutput.trim() && this.emptyBashRunCount >= 2) {
467
- hints.push(`The command "${this.lastBashCommand}" ran ${this.emptyBashRunCount} times with EMPTY output. The program likely has no entry point — read the file with read_file and check that it actually calls its main function with process.argv / CLI arguments and prints results (console.log). Then run it again.`);
468
- }
469
- if (!error)
470
- return hints;
471
- // Runtime incompatibility — tool/module crashes on this Node version
472
- if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
473
- if (/node_modules/.test(error)) {
474
- hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
475
- }
476
- }
477
- // TypeScript syntax errors — model generated invalid code
478
- if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
479
- hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
480
- }
481
- // TypeScript type errors — model doesn't understand the types
482
- if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
483
- hints.push("TypeScript type mismatch. Check the type signature of the function/API you are using. If a package lacks type declarations, install @types/<package> or use skipLibCheck.");
484
- }
485
- // Module not found — missing dependency or wrong import
486
- if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
487
- hints.push("Module not found. Run npm install or check if the import path is correct.");
488
- }
489
- // Permission / access errors
490
- if (/EACCES|EPERM|permission denied/i.test(error)) {
491
- hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
492
- }
493
- // Network errors
494
- if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
495
- hints.push("Network error. Check if the server is running and accessible.");
496
- }
497
- // Command not found
498
- if (/is not recognized|command not found|not found in path/i.test(error)) {
499
- hints.push("Command not found. Check if the tool is installed and in PATH.");
500
- }
501
- // File rewrite loop — same file keeps failing
502
- if (this.hasExcessiveRewrites()) {
503
- const file = this.getExcessiveRewriteFile();
504
- hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
505
- }
506
- // Repeated identical bash runs with empty output — likely a missing
507
- // entry point (the program never calls its main function).
508
- // PowerShell cmdlets used inside cmd.exe (Windows shell mismatch).
509
- if (/Write-Host|Get-Content|Select-String|Out-File|Set-Content/i.test(error)) {
510
- hints.push("That looks like a PowerShell cmdlet — the shell here is cmd.exe. Use echo/type for output or the read_file/write_file tools instead.");
511
- }
512
- // Model invented a Bun API that does not exist.
513
- if (/Bun\.\w+ is not a function|Bun\.\w+ is not defined|Bun\.\w+ is not a constructor/i.test(error)) {
514
- hints.push("That Bun API does not exist. Verify the API name in the Bun docs — common ones are Bun.file, Bun.write, Bun.spawn, Bun.serve. For file checks use fs.existsSync from node:fs.");
515
- }
516
- // The model keeps writing a file whose output shows a type/syntax
517
- // error — read the actual error and fix it, don't rewrite blindly.
518
- if (/error TS\d+|typecheck failed|syntax check failed/i.test(error)) {
519
- hints.push("The file still has a type/syntax error (see the error line in the output). Read the file with read_file around the reported line, fix the actual error, then re-run — rewriting the whole file blindly usually makes it worse.");
520
- }
521
- // Bash heredoc in cmd.exe.
522
- if (/unexpected.*<<|Непредвиденное появление/i.test(error)) {
523
- hints.push("Heredoc (<< EOF) is a bash feature — this shell is cmd.exe and does not support it. Write the file with write_file instead.");
524
- }
525
- return hints;
526
- }
527
- getToolAlternative() {
528
- const tool = this.lastFailedTool;
529
- const error = this.lastErrorOutput;
530
- if (!tool)
531
- return null;
532
- // If a tool fails with a runtime crash, suggest alternatives
533
- if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
534
- // Map of tools to their common alternatives
535
- const alternatives = {
536
- "ts-node": "tsx",
537
- jest: "vitest",
538
- mocha: "vitest",
539
- webpack: "vite",
540
- rollup: "vite",
541
- parcel: "vite",
542
- babel: "tsc",
543
- eslint: "biome",
544
- prettier: "biome",
545
- };
546
- for (const [failed, alt] of Object.entries(alternatives)) {
547
- if (tool.includes(failed) || error.includes(failed)) {
548
- return alt;
549
- }
550
- }
551
- }
552
- return null;
553
- }
554
- recordFileRewrite(filePath) {
555
- const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
556
- this.fileRewriteCount.set(filePath, count);
557
- }
558
- getFileRewriteCount(filePath) {
559
- return this.fileRewriteCount.get(filePath) || 0;
560
- }
561
- hasExcessiveRewrites() {
562
- return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
563
- }
564
- getExcessiveRewriteFile() {
565
- for (const [file, count] of this.fileRewriteCount) {
566
- if (count >= this.fileRewriteThreshold)
567
- return file;
568
- }
569
- return null;
570
- }
571
- getRepetitiveToolMessage() {
572
- if (!this.hasRepetitiveToolCalls())
573
- return "";
574
- const last = this.recentToolCalls[this.recentToolCalls.length - 1];
575
- return t("exec.repetitive_tool", {
576
- tool: last.name,
577
- count: this.repetitionThreshold,
578
- });
579
- }
580
- /**
581
- * Stable warning identity for anti-spam dedup: the same key re-logged every
582
- * iteration is spam, a NEW key (or a long gap) deserves a re-warn.
583
- */
584
- getWarningKey() {
585
- if (this.isStuck())
586
- return "stuck";
587
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
588
- if (errorTool)
589
- return `tool-errors:${errorTool[0]}`;
590
- if (this.hasBashFlailing())
591
- return "bash-flailing";
592
- if (this.hasConsecutiveFailures())
593
- return "consecutive";
594
- if (this.hasRepetitiveToolCalls())
595
- return "repetitive";
596
- if (this.hasReadOnlyLoop())
597
- return "read-only";
598
- return "";
599
- }
600
- getStuckReason() {
601
- if (this.isStuck()) {
602
- return t("exec.stuck", {
603
- stepId: String(this.currentStepId ?? "?"),
604
- description: this.currentStepDescription,
605
- iterations: this.iterationsOnCurrentStep,
606
- });
607
- }
608
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
609
- if (errorTool) {
610
- return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
611
- }
612
- if (this.hasReadOnlyLoop()) {
613
- return t("exec.read_only_loop", {
614
- count: String(this.readOnlyStreak),
615
- });
616
- }
617
- return "";
618
- }
619
- getRecoveryMessage() {
620
- const reason = this.getPrimaryReason();
621
- if (reason === "stuck") {
622
- return t("exec.stuck_recovery", {
623
- iterations: this.iterationsOnCurrentStep,
624
- stepId: String(this.currentStepId ?? "?"),
625
- description: this.currentStepDescription,
626
- });
627
- }
628
- // Per-tool error recovery (more specific — e.g., "bash failed 3 times")
629
- if (reason === "tool-errors") {
630
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
631
- return t("exec.tool_errors_recovery", {
632
- tool: errorTool[0],
633
- count: errorTool[1],
634
- });
635
- }
636
- // Consecutive failures from different tools (generic — e.g., "5 consecutive failures")
637
- if (reason === "consecutive") {
638
- return t("exec.consecutive_failures_recovery", {
639
- count: this.consecutiveFailures,
640
- });
641
- }
642
- if (reason === "bash-flailing") {
643
- const fails = this.bashRecent.filter((ok) => !ok).length;
644
- return t("exec.bash_flailing_recovery", {
645
- fails: String(fails),
646
- window: String(this.bashRecent.length),
647
- });
648
- }
649
- if (reason === "repetitive") {
650
- return this.getRepetitiveToolMessage();
651
- }
652
- if (reason === "read-only") {
653
- return t("exec.read_only_loop_recovery", {
654
- count: String(this.readOnlyStreak),
655
- });
656
- }
657
- return "";
658
- }
659
- /**
660
- * Single source of truth for the recovery reason priority. Used by BOTH
661
- * getRecoveryMessage() and getHints() so the two cannot drift apart: the
662
- * state bullet that getRecoveryMessage already reports is NOT repeated as a
663
- * hint (priority: stuck > tool-errors > consecutive > repetitive >
664
- * read-only).
665
- */
666
- getPrimaryReason() {
667
- if (this.isStuck())
668
- return "stuck";
669
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
670
- if (errorTool)
671
- return "tool-errors";
672
- if (this.hasBashFlailing())
673
- return "bash-flailing";
674
- if (this.hasConsecutiveFailures())
675
- return "consecutive";
676
- if (this.hasRepetitiveToolCalls())
677
- return "repetitive";
678
- if (this.hasReadOnlyLoop())
679
- return "read-only";
680
- return null;
681
- }
682
- reset() {
683
- this.currentStepId = null;
684
- this.iterationsOnCurrentStep = 0;
685
- this.toolErrors.clear();
686
- this.consecutiveFailures = 0;
687
- this.lastFailedTool = "";
688
- this.lastErrorOutput = "";
689
- this.recentToolCalls = [];
690
- this.fileRewriteCount.clear();
691
- this.escalationCount = 0;
692
- this.lastBashCommand = "";
693
- this.lastBashOutput = "";
694
- this.emptyBashRunCount = 0;
695
- this.bashRecent = [];
696
- this.readOnlyStreak = 0;
697
- this.errorSignatureCounts.clear();
698
- this.searchedErrorSignatures.clear();
699
- }
700
- /**
701
- * Reset only step-progress state, keeping the tool-call history and the
702
- * read-only streak. Used when the plan is complete but the loop continues
703
- * (e.g. the audit gate rejected a finished task): step-stuck warnings are
704
- * silenced, but a read-without-write loop is still detected.
705
- */
706
- resetStepProgress() {
707
- this.currentStepId = null;
708
- this.iterationsOnCurrentStep = 0;
709
- this.toolErrors.clear();
710
- this.consecutiveFailures = 0;
711
- this.lastFailedTool = "";
712
- this.lastErrorOutput = "";
713
- this.fileRewriteCount.clear();
714
- this.escalationCount = 0;
715
- this.lastBashCommand = "";
716
- this.lastBashOutput = "";
717
- this.emptyBashRunCount = 0;
718
- this.bashRecent = [];
719
- this.errorSignatureCounts.clear();
720
- this.searchedErrorSignatures.clear();
721
- }
722
- /**
723
- * Reset all per-step state when moving to a new step. Prevents errors and
724
- * tool repetitions from one step leaking into the next (false-positive
725
- * stuck/recovery on the first iteration of the new step).
726
- */
727
- resetStepState(stepId) {
728
- this.currentStepId = stepId;
729
- this.iterationsOnCurrentStep = 1;
730
- this.toolErrors.clear();
731
- this.consecutiveFailures = 0;
732
- this.lastFailedTool = "";
733
- this.lastErrorOutput = "";
734
- this.recentToolCalls = [];
735
- this.fileRewriteCount.clear();
736
- this.lastBashCommand = "";
737
- this.lastBashOutput = "";
738
- this.emptyBashRunCount = 0;
739
- this.bashRecent = [];
740
- this.readOnlyStreak = 0;
741
- this.errorSignatureCounts.clear();
742
- this.searchedErrorSignatures.clear();
743
- // Preserve escalationCount — prevents agent from "gaming" stuck detection
744
- // by marking a step done and moving to the next one
745
- }
746
- }