micro-models-agent 0.46.1 → 0.46.3

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 +312 -312
  2. package/dist/main.js +724 -530
  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,90 +0,0 @@
1
- function generatePlanId() {
2
- const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
3
- let id = "";
4
- for (let i = 0; i < 6; i++) {
5
- id += chars[Math.floor(Math.random() * chars.length)];
6
- }
7
- return `plan_${id}`;
8
- }
9
- export class PlanCreator {
10
- static isMultiStep(task) {
11
- const fileCount = (task.match(/\b[\w./-]+\.[a-z]+\b/gi) || []).length;
12
- if (fileCount > 1)
13
- return true;
14
- const actionWords = [
15
- "implement",
16
- "create",
17
- "add",
18
- "build",
19
- "setup",
20
- "configure",
21
- "write",
22
- "make",
23
- "develop",
24
- ];
25
- const words = task.split(/\s+/);
26
- const hasActionWord = actionWords.some((w) => task.toLowerCase().includes(w));
27
- return hasActionWord && words.length > 8;
28
- }
29
- static createPlan(title, stepDescriptions, baseDir, kinds) {
30
- const stepCount = stepDescriptions.length;
31
- return {
32
- id: generatePlanId(),
33
- title: `[${stepCount}] ${title}`,
34
- steps: stepDescriptions.map((desc, i) => ({
35
- id: i + 1,
36
- description: desc,
37
- status: "pending",
38
- kind: kinds?.[i] ?? "create",
39
- })),
40
- createdAt: new Date().toISOString(),
41
- baseDir,
42
- };
43
- }
44
- static replan(plan, newSteps, title) {
45
- const kept = plan.steps.filter((s) => s.status === "done" || s.status === "skipped");
46
- const baseTitle = plan.title.replace(/^\[\d+[^]]*\]\s*/, "");
47
- const newTitle = title || baseTitle;
48
- const totalSteps = kept.length + newSteps.length;
49
- const added = newSteps.map((desc, i) => ({
50
- id: kept.length + i + 1,
51
- description: desc,
52
- status: "pending",
53
- kind: "create",
54
- }));
55
- return {
56
- id: plan.id,
57
- title: `[${totalSteps}] ${newTitle}`,
58
- steps: [...kept, ...added],
59
- createdAt: plan.createdAt,
60
- baseDir: plan.baseDir,
61
- name: plan.name,
62
- };
63
- }
64
- static toPromptBlock(plan, currentStepIndex) {
65
- const date = plan.createdAt.slice(0, 10);
66
- const lines = [
67
- `[${plan.id}] ${plan.title}`,
68
- `Dir: ${plan.baseDir}`,
69
- `Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
70
- ``,
71
- ];
72
- for (const step of plan.steps) {
73
- const icon = step.status === "done"
74
- ? "[x]"
75
- : step.status === "in_progress"
76
- ? "[*]"
77
- : step.status === "failed"
78
- ? "[!]"
79
- : step.status === "skipped"
80
- ? "[-]"
81
- : "[ ]";
82
- const note = step.note ? ` — ${step.note}` : "";
83
- lines.push(`${icon} ${step.id}. ${step.description}${note}`);
84
- for (const sub of step.subtasks ?? []) {
85
- lines.push(` ${sub.done ? "[x]" : "[ ]"} ${sub.text}`);
86
- }
87
- }
88
- return lines.join("\n");
89
- }
90
- }
@@ -1,510 +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
- export class StuckDetector {
69
- threshold;
70
- errorThreshold;
71
- iterationsOnCurrentStep = 0;
72
- currentStepId = null;
73
- toolErrors = new Map();
74
- currentStepDescription = "";
75
- recentToolCalls = [];
76
- maxRecentCalls = 10;
77
- repetitionThreshold = 3;
78
- consecutiveFailures = 0;
79
- lastFailedTool = "";
80
- lastErrorOutput = "";
81
- escalationCount = 0;
82
- escalationThreshold = 3;
83
- fileRewriteCount = new Map();
84
- fileRewriteThreshold = 3;
85
- lastBashCommand = "";
86
- lastBashOutput = "";
87
- emptyBashRunCount = 0;
88
- readOnlyStreak = 0;
89
- constructor(threshold = 6, errorThreshold = 3) {
90
- this.threshold = threshold;
91
- this.errorThreshold = errorThreshold;
92
- }
93
- recordIteration(stepId) {
94
- if (stepId === this.currentStepId) {
95
- this.iterationsOnCurrentStep++;
96
- }
97
- else {
98
- this.resetStepState(stepId);
99
- }
100
- }
101
- recordToolCall(name, args) {
102
- const argsKey = JSON.stringify(args);
103
- this.recentToolCalls.push({ name, argsKey });
104
- if (this.recentToolCalls.length > this.maxRecentCalls) {
105
- this.recentToolCalls.shift();
106
- }
107
- // Track the consecutive read-only streak. A write/exec tool resets it;
108
- // plan/todo bookkeeping counts as read-only (it is not real progress).
109
- if (READ_ONLY_TOOLS.has(name)) {
110
- this.readOnlyStreak++;
111
- }
112
- else {
113
- this.readOnlyStreak = 0;
114
- }
115
- }
116
- /**
117
- * True after a long run of read-only tool calls with no write in between.
118
- * The model is exploring/looping without producing anything — inject a
119
- * recovery hint so it stops re-reading and actually edits files.
120
- */
121
- hasReadOnlyLoop() {
122
- return this.readOnlyStreak >= READ_ONLY_LOOP_THRESHOLD;
123
- }
124
- recordToolError(toolName, output) {
125
- this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
126
- this.consecutiveFailures++;
127
- this.lastFailedTool = toolName;
128
- if (output)
129
- this.lastErrorOutput = output;
130
- }
131
- getLastErrorOutput() {
132
- return this.lastErrorOutput;
133
- }
134
- /**
135
- * Record every bash invocation (successful or not) so stuck detection can
136
- * reason about repeated runs with empty output, entry-point issues, and
137
- * platform mistakes (PowerShell cmdlets in cmd.exe, bash heredocs, ...).
138
- */
139
- recordBashOutput(command, output) {
140
- const trimmed = output.trim();
141
- if (command === this.lastBashCommand && !trimmed) {
142
- this.emptyBashRunCount++;
143
- }
144
- else {
145
- this.emptyBashRunCount = 0;
146
- }
147
- this.lastBashCommand = command;
148
- this.lastBashOutput = output;
149
- }
150
- getLastBashCommand() {
151
- return this.lastBashCommand;
152
- }
153
- getLastBashOutput() {
154
- return this.lastBashOutput;
155
- }
156
- getLastFailedTool() {
157
- return this.lastFailedTool;
158
- }
159
- getIterationsOnCurrentStep() {
160
- return this.iterationsOnCurrentStep;
161
- }
162
- recordToolSuccess() {
163
- this.consecutiveFailures = 0;
164
- this.lastFailedTool = "";
165
- this.escalationCount = 0;
166
- }
167
- setCurrentStep(stepId, description) {
168
- if (stepId !== this.currentStepId) {
169
- this.resetStepState(stepId);
170
- }
171
- this.currentStepDescription = description;
172
- }
173
- isStuck() {
174
- return this.iterationsOnCurrentStep > this.threshold;
175
- }
176
- hasRepetitiveErrors(toolName) {
177
- if (toolName) {
178
- return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
179
- }
180
- return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
181
- }
182
- hasRepetitiveToolCalls() {
183
- if (this.recentToolCalls.length < this.repetitionThreshold)
184
- return false;
185
- const last = this.recentToolCalls[this.recentToolCalls.length - 1];
186
- // Check consecutive identical calls (original logic)
187
- let consecutiveCount = 1;
188
- for (let i = this.recentToolCalls.length - 2; i >= 0; i--) {
189
- if (this.recentToolCalls[i].name === last.name &&
190
- this.recentToolCalls[i].argsKey === last.argsKey) {
191
- consecutiveCount++;
192
- }
193
- else {
194
- break;
195
- }
196
- }
197
- if (consecutiveCount >= this.repetitionThreshold)
198
- return true;
199
- // Also detect interleaved patterns (A, B, A, B, A) — count any single
200
- // tool+args combo in the window and flag if it appears >= threshold times
201
- const counts = new Map();
202
- for (const call of this.recentToolCalls) {
203
- const key = `${call.name}::${call.argsKey}`;
204
- counts.set(key, (counts.get(key) || 0) + 1);
205
- }
206
- for (const count of counts.values()) {
207
- if (count >= this.repetitionThreshold)
208
- return true;
209
- }
210
- return false;
211
- }
212
- hasConsecutiveFailures() {
213
- return this.consecutiveFailures >= this.errorThreshold;
214
- }
215
- getConsecutiveFailuresCount() {
216
- return this.consecutiveFailures;
217
- }
218
- recordEscalation() {
219
- this.escalationCount++;
220
- }
221
- shouldEscalate() {
222
- return this.escalationCount >= this.escalationThreshold;
223
- }
224
- resetEscalation() {
225
- this.escalationCount = 0;
226
- }
227
- getEscalationCount() {
228
- return this.escalationCount;
229
- }
230
- getHints() {
231
- const hints = [];
232
- const desc = this.currentStepDescription.toLowerCase();
233
- if (desc.includes("install") || desc.includes("npm") || desc.includes("pip")) {
234
- hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
235
- }
236
- if (desc.includes("test") || desc.includes("spec")) {
237
- hints.push("Make sure the source files exist and have real code before running tests.");
238
- }
239
- if (desc.includes("build") || desc.includes("compile")) {
240
- hints.push("Check that all dependencies are installed and source files are not empty.");
241
- }
242
- if (desc.includes("deploy") || desc.includes("publish")) {
243
- hints.push("Verify credentials and network access before deploying.");
244
- }
245
- // State-based bullets. The recovery message (getRecoveryMessage) already
246
- // reports the PRIMARY reason (priority: stuck > tool-errors > consecutive
247
- // > repetitive > read-only). Repeating that same state here as a hint
248
- // would duplicate the guidance in the same cooldown block. Only add the
249
- // bullet when the recovery message is NOT already covering that state.
250
- const primary = this.getPrimaryReason();
251
- if (this.hasRepetitiveToolCalls() && primary !== "repetitive") {
252
- hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
253
- }
254
- if (this.hasConsecutiveFailures() && primary !== "consecutive") {
255
- hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
256
- }
257
- if (this.hasReadOnlyLoop() && primary !== "read-only") {
258
- hints.push("You have made many read-only tool calls (read_file/glob/grep/browser) without writing anything. Stop exploring — make the edit/write the task needs, or use the plan tool to decide next steps.");
259
- }
260
- return hints;
261
- }
262
- getActionableHints() {
263
- const hints = [];
264
- const error = this.lastErrorOutput;
265
- // Bash-specific hints do not depend on a tool failure — they fire even
266
- // when the command "succeeded" with empty output (missing entry point).
267
- const isScriptRun = /(^|[\s&])(bun|node|tsx|ts-node|deno|python|python3)\S*\s+[^|&]+\s+[^\s]+$/.test(this.lastBashCommand);
268
- if (isScriptRun && !this.lastBashOutput.trim() && this.emptyBashRunCount >= 2) {
269
- 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.`);
270
- }
271
- if (!error)
272
- return hints;
273
- // Runtime incompatibility — tool/module crashes on this Node version
274
- if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
275
- if (/node_modules/.test(error)) {
276
- hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
277
- }
278
- }
279
- // TypeScript syntax errors — model generated invalid code
280
- if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
281
- hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
282
- }
283
- // TypeScript type errors — model doesn't understand the types
284
- if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
285
- 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.");
286
- }
287
- // Module not found — missing dependency or wrong import
288
- if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
289
- hints.push("Module not found. Run npm install or check if the import path is correct.");
290
- }
291
- // Permission / access errors
292
- if (/EACCES|EPERM|permission denied/i.test(error)) {
293
- hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
294
- }
295
- // Network errors
296
- if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
297
- hints.push("Network error. Check if the server is running and accessible.");
298
- }
299
- // Command not found
300
- if (/is not recognized|command not found|not found in path/i.test(error)) {
301
- hints.push("Command not found. Check if the tool is installed and in PATH.");
302
- }
303
- // File rewrite loop — same file keeps failing
304
- if (this.hasExcessiveRewrites()) {
305
- const file = this.getExcessiveRewriteFile();
306
- hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
307
- }
308
- // Repeated identical bash runs with empty output — likely a missing
309
- // entry point (the program never calls its main function).
310
- // PowerShell cmdlets used inside cmd.exe (Windows shell mismatch).
311
- if (/Write-Host|Get-Content|Select-String|Out-File|Set-Content/i.test(error)) {
312
- 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.");
313
- }
314
- // Model invented a Bun API that does not exist.
315
- if (/Bun\.\w+ is not a function|Bun\.\w+ is not defined|Bun\.\w+ is not a constructor/i.test(error)) {
316
- 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.");
317
- }
318
- // The model keeps writing a file whose output shows a type/syntax
319
- // error — read the actual error and fix it, don't rewrite blindly.
320
- if (/error TS\d+|typecheck failed|syntax check failed/i.test(error)) {
321
- 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.");
322
- }
323
- // Bash heredoc in cmd.exe.
324
- if (/unexpected.*<<|Непредвиденное появление/i.test(error)) {
325
- 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.");
326
- }
327
- return hints;
328
- }
329
- getToolAlternative() {
330
- const tool = this.lastFailedTool;
331
- const error = this.lastErrorOutput;
332
- if (!tool)
333
- return null;
334
- // If a tool fails with a runtime crash, suggest alternatives
335
- if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
336
- // Map of tools to their common alternatives
337
- const alternatives = {
338
- "ts-node": "tsx",
339
- jest: "vitest",
340
- mocha: "vitest",
341
- webpack: "vite",
342
- rollup: "vite",
343
- parcel: "vite",
344
- babel: "tsc",
345
- eslint: "biome",
346
- prettier: "biome",
347
- };
348
- for (const [failed, alt] of Object.entries(alternatives)) {
349
- if (tool.includes(failed) || error.includes(failed)) {
350
- return alt;
351
- }
352
- }
353
- }
354
- return null;
355
- }
356
- recordFileRewrite(filePath) {
357
- const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
358
- this.fileRewriteCount.set(filePath, count);
359
- }
360
- getFileRewriteCount(filePath) {
361
- return this.fileRewriteCount.get(filePath) || 0;
362
- }
363
- hasExcessiveRewrites() {
364
- return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
365
- }
366
- getExcessiveRewriteFile() {
367
- for (const [file, count] of this.fileRewriteCount) {
368
- if (count >= this.fileRewriteThreshold)
369
- return file;
370
- }
371
- return null;
372
- }
373
- getRepetitiveToolMessage() {
374
- if (!this.hasRepetitiveToolCalls())
375
- return "";
376
- const last = this.recentToolCalls[this.recentToolCalls.length - 1];
377
- return t("exec.repetitive_tool", {
378
- tool: last.name,
379
- count: this.repetitionThreshold,
380
- });
381
- }
382
- getStuckReason() {
383
- if (this.isStuck()) {
384
- return t("exec.stuck", {
385
- stepId: String(this.currentStepId ?? "?"),
386
- description: this.currentStepDescription,
387
- iterations: this.iterationsOnCurrentStep,
388
- });
389
- }
390
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
391
- if (errorTool) {
392
- return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
393
- }
394
- if (this.hasReadOnlyLoop()) {
395
- return t("exec.read_only_loop", {
396
- count: String(this.readOnlyStreak),
397
- });
398
- }
399
- return "";
400
- }
401
- getRecoveryMessage() {
402
- const reason = this.getPrimaryReason();
403
- if (reason === "stuck") {
404
- return t("exec.stuck_recovery", {
405
- iterations: this.iterationsOnCurrentStep,
406
- stepId: String(this.currentStepId ?? "?"),
407
- description: this.currentStepDescription,
408
- });
409
- }
410
- // Per-tool error recovery (more specific — e.g., "bash failed 3 times")
411
- if (reason === "tool-errors") {
412
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
413
- return t("exec.tool_errors_recovery", {
414
- tool: errorTool[0],
415
- count: errorTool[1],
416
- });
417
- }
418
- // Consecutive failures from different tools (generic — e.g., "5 consecutive failures")
419
- if (reason === "consecutive") {
420
- return t("exec.consecutive_failures_recovery", {
421
- count: this.consecutiveFailures,
422
- });
423
- }
424
- if (reason === "repetitive") {
425
- return this.getRepetitiveToolMessage();
426
- }
427
- if (reason === "read-only") {
428
- return t("exec.read_only_loop_recovery", {
429
- count: String(this.readOnlyStreak),
430
- });
431
- }
432
- return "";
433
- }
434
- /**
435
- * Single source of truth for the recovery reason priority. Used by BOTH
436
- * getRecoveryMessage() and getHints() so the two cannot drift apart: the
437
- * state bullet that getRecoveryMessage already reports is NOT repeated as a
438
- * hint (priority: stuck > tool-errors > consecutive > repetitive >
439
- * read-only).
440
- */
441
- getPrimaryReason() {
442
- if (this.isStuck())
443
- return "stuck";
444
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
445
- if (errorTool)
446
- return "tool-errors";
447
- if (this.hasConsecutiveFailures())
448
- return "consecutive";
449
- if (this.hasRepetitiveToolCalls())
450
- return "repetitive";
451
- if (this.hasReadOnlyLoop())
452
- return "read-only";
453
- return null;
454
- }
455
- reset() {
456
- this.currentStepId = null;
457
- this.iterationsOnCurrentStep = 0;
458
- this.toolErrors.clear();
459
- this.consecutiveFailures = 0;
460
- this.lastFailedTool = "";
461
- this.lastErrorOutput = "";
462
- this.recentToolCalls = [];
463
- this.fileRewriteCount.clear();
464
- this.escalationCount = 0;
465
- this.lastBashCommand = "";
466
- this.lastBashOutput = "";
467
- this.emptyBashRunCount = 0;
468
- this.readOnlyStreak = 0;
469
- }
470
- /**
471
- * Reset only step-progress state, keeping the tool-call history and the
472
- * read-only streak. Used when the plan is complete but the loop continues
473
- * (e.g. the audit gate rejected a finished task): step-stuck warnings are
474
- * silenced, but a read-without-write loop is still detected.
475
- */
476
- resetStepProgress() {
477
- this.currentStepId = null;
478
- this.iterationsOnCurrentStep = 0;
479
- this.toolErrors.clear();
480
- this.consecutiveFailures = 0;
481
- this.lastFailedTool = "";
482
- this.lastErrorOutput = "";
483
- this.fileRewriteCount.clear();
484
- this.escalationCount = 0;
485
- this.lastBashCommand = "";
486
- this.lastBashOutput = "";
487
- this.emptyBashRunCount = 0;
488
- }
489
- /**
490
- * Reset all per-step state when moving to a new step. Prevents errors and
491
- * tool repetitions from one step leaking into the next (false-positive
492
- * stuck/recovery on the first iteration of the new step).
493
- */
494
- resetStepState(stepId) {
495
- this.currentStepId = stepId;
496
- this.iterationsOnCurrentStep = 1;
497
- this.toolErrors.clear();
498
- this.consecutiveFailures = 0;
499
- this.lastFailedTool = "";
500
- this.lastErrorOutput = "";
501
- this.recentToolCalls = [];
502
- this.fileRewriteCount.clear();
503
- this.lastBashCommand = "";
504
- this.lastBashOutput = "";
505
- this.emptyBashRunCount = 0;
506
- this.readOnlyStreak = 0;
507
- // Preserve escalationCount — prevents agent from "gaming" stuck detection
508
- // by marking a step done and moving to the next one
509
- }
510
- }
@@ -1,67 +0,0 @@
1
- import { PlanCreator } from "./planner";
2
- export class PlanTracker {
3
- plan;
4
- currentStepIndex = 0;
5
- constructor(plan) {
6
- this.plan = plan;
7
- }
8
- getPlan() {
9
- return this.plan;
10
- }
11
- getCurrentStepIndex() {
12
- return this.currentStepIndex;
13
- }
14
- getCurrentStep() {
15
- return this.plan.steps[this.currentStepIndex];
16
- }
17
- getStep(id) {
18
- return this.plan.steps.find((s) => s.id === id);
19
- }
20
- updateStepStatus(id, status) {
21
- const step = this.getStep(id);
22
- if (step)
23
- step.status = status;
24
- }
25
- addNote(id, note) {
26
- const step = this.getStep(id);
27
- if (step)
28
- step.note = note;
29
- }
30
- advance() {
31
- if (this.currentStepIndex < this.plan.steps.length - 1) {
32
- this.currentStepIndex++;
33
- this.plan.steps[this.currentStepIndex].status = "in_progress";
34
- return true;
35
- }
36
- return false;
37
- }
38
- /**
39
- * Advance the pointer past any done/skipped steps without mutating their
40
- * status. Keeps currentStepIndex in sync when the model marks steps done via
41
- * the `plan update` tool (which never calls advance()). Without this,
42
- * getCurrentStep() stays on the first step forever and the stuck detector
43
- * accumulates false-positive "no progress" warnings.
44
- */
45
- syncCurrentStep() {
46
- while (this.currentStepIndex < this.plan.steps.length - 1 &&
47
- (this.plan.steps[this.currentStepIndex].status === "done" ||
48
- this.plan.steps[this.currentStepIndex].status === "skipped")) {
49
- this.currentStepIndex++;
50
- }
51
- }
52
- isComplete() {
53
- return this.plan.steps.every((s) => s.status === "done" || s.status === "skipped");
54
- }
55
- getProgressString() {
56
- const done = this.plan.steps.filter((s) => s.status === "done").length;
57
- const total = this.plan.steps.length;
58
- const pct = total > 0 ? Math.round((done / total) * 100) : 0;
59
- const barWidth = 10;
60
- const filled = Math.round((done / total) * barWidth);
61
- const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
62
- return `[${this.plan.id}] ${this.plan.title} ${done}/${total} ${bar} ${pct}%`;
63
- }
64
- toPromptBlock() {
65
- return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
66
- }
67
- }
@@ -1 +0,0 @@
1
- export {};