micro-models-agent 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +312 -312
  2. package/bin/mma.mjs +41 -41
  3. package/dist/main.js +241 -56
  4. package/dist/skills/builtin/git.md +36 -36
  5. package/dist/skills/builtin/typescript.md +35 -35
  6. package/package.json +1 -1
  7. package/dist/cli/commands.js +0 -333
  8. package/dist/cli/completer.js +0 -168
  9. package/dist/cli/index.js +0 -2
  10. package/dist/cli/main.js +0 -140
  11. package/dist/cli/repl-commands.js +0 -633
  12. package/dist/cli/repl.js +0 -486
  13. package/dist/cli/security-commands.js +0 -166
  14. package/dist/cli/setup.js +0 -249
  15. package/dist/config/config.js +0 -202
  16. package/dist/config/defaults.js +0 -100
  17. package/dist/config/experts.js +0 -15
  18. package/dist/config/index.js +0 -3
  19. package/dist/config/security.js +0 -200
  20. package/dist/config/types.js +0 -1
  21. package/dist/core/agent-moe.js +0 -110
  22. package/dist/core/agent.js +0 -695
  23. package/dist/core/bootstrap.js +0 -337
  24. package/dist/core/index.js +0 -2
  25. package/dist/core/prompt-builder.js +0 -55
  26. package/dist/core/session-logger.js +0 -155
  27. package/dist/core/types.js +0 -1
  28. package/dist/core/workspace.js +0 -76
  29. package/dist/i18n/en.json +0 -525
  30. package/dist/i18n/index.js +0 -46
  31. package/dist/i18n/ru.json +0 -525
  32. package/dist/index.js +0 -22
  33. package/dist/llm/image-utils.js +0 -144
  34. package/dist/llm/index.js +0 -4
  35. package/dist/llm/model-loader.js +0 -78
  36. package/dist/llm/openai-compat.js +0 -353
  37. package/dist/llm/orchestrator.js +0 -194
  38. package/dist/llm/provider.js +0 -10
  39. package/dist/llm/response.js +0 -39
  40. package/dist/llm/token-counter.js +0 -39
  41. package/dist/llm/types.js +0 -1
  42. package/dist/logger/app-logger.js +0 -143
  43. package/dist/logger/file-log.js +0 -151
  44. package/dist/logger/index.js +0 -1
  45. package/dist/migration/backup.js +0 -45
  46. package/dist/migration/detect.js +0 -50
  47. package/dist/migration/index.js +0 -2
  48. package/dist/modules/browser/actions.js +0 -46
  49. package/dist/modules/browser/cookie-store.js +0 -24
  50. package/dist/modules/browser/index.js +0 -5
  51. package/dist/modules/browser/module.js +0 -28
  52. package/dist/modules/browser/session.js +0 -335
  53. package/dist/modules/browser/snapshot.js +0 -114
  54. package/dist/modules/browser/types.js +0 -9
  55. package/dist/modules/certification/cli.js +0 -176
  56. package/dist/modules/certification/fact-checker.js +0 -84
  57. package/dist/modules/certification/loader.js +0 -111
  58. package/dist/modules/certification/manifest.js +0 -50
  59. package/dist/modules/certification/runner.js +0 -162
  60. package/dist/modules/certification/scenarios.js +0 -124
  61. package/dist/modules/certification/types.js +0 -1
  62. package/dist/modules/context/index.js +0 -1
  63. package/dist/modules/context/manager.js +0 -349
  64. package/dist/modules/execution/auditor.js +0 -66
  65. package/dist/modules/execution/index.js +0 -8
  66. package/dist/modules/execution/module.js +0 -779
  67. package/dist/modules/execution/moe-executor.js +0 -266
  68. package/dist/modules/execution/plan-coverage.js +0 -68
  69. package/dist/modules/execution/plan-persister.js +0 -46
  70. package/dist/modules/execution/plan-store.js +0 -159
  71. package/dist/modules/execution/plan-validator.js +0 -153
  72. package/dist/modules/execution/planner.js +0 -85
  73. package/dist/modules/execution/stuck-detector.js +0 -347
  74. package/dist/modules/execution/tracker.js +0 -67
  75. package/dist/modules/execution/types.js +0 -1
  76. package/dist/modules/execution/verifier.js +0 -178
  77. package/dist/modules/hallucination/confidence.js +0 -59
  78. package/dist/modules/hallucination/consistency.js +0 -26
  79. package/dist/modules/hallucination/detector.js +0 -46
  80. package/dist/modules/hallucination/factual.js +0 -190
  81. package/dist/modules/hallucination/index.js +0 -5
  82. package/dist/modules/hallucination/js-identifiers.js +0 -72
  83. package/dist/modules/hallucination/llm-judge.js +0 -103
  84. package/dist/modules/index.js +0 -5
  85. package/dist/modules/indexer/cache.js +0 -38
  86. package/dist/modules/indexer/index.js +0 -3
  87. package/dist/modules/indexer/module.js +0 -192
  88. package/dist/modules/indexer/walker.js +0 -101
  89. package/dist/modules/lsp/client.js +0 -235
  90. package/dist/modules/lsp/config.js +0 -81
  91. package/dist/modules/lsp/index.js +0 -3
  92. package/dist/modules/lsp/module.js +0 -68
  93. package/dist/modules/lsp/types.js +0 -1
  94. package/dist/modules/mcp/client.js +0 -399
  95. package/dist/modules/mcp/index.js +0 -3
  96. package/dist/modules/mcp/module.js +0 -146
  97. package/dist/modules/mcp/registry.js +0 -15
  98. package/dist/modules/memory/index.js +0 -1
  99. package/dist/modules/memory/module.js +0 -48
  100. package/dist/modules/memory/search.js +0 -40
  101. package/dist/modules/memory/store.js +0 -69
  102. package/dist/modules/pipelines/engine.js +0 -60
  103. package/dist/modules/pipelines/index.js +0 -3
  104. package/dist/modules/pipelines/parser.js +0 -53
  105. package/dist/modules/pipelines/template.js +0 -14
  106. package/dist/modules/plugins/builtin/lint-on-write.js +0 -226
  107. package/dist/modules/plugins/builtin/notify.js +0 -8
  108. package/dist/modules/plugins/index.js +0 -1
  109. package/dist/modules/plugins/loader.js +0 -28
  110. package/dist/modules/plugins/manager.js +0 -161
  111. package/dist/modules/plugins/types.js +0 -1
  112. package/dist/modules/processes/index.js +0 -2
  113. package/dist/modules/processes/registry.js +0 -238
  114. package/dist/modules/processes/runner.js +0 -23
  115. package/dist/modules/registry.js +0 -45
  116. package/dist/modules/security/audit-log.js +0 -136
  117. package/dist/modules/security/audit-notifier.js +0 -292
  118. package/dist/modules/security/command-validator.js +0 -211
  119. package/dist/modules/security/content-scanner.js +0 -53
  120. package/dist/modules/security/data-sanitizer.js +0 -97
  121. package/dist/modules/security/encryption.js +0 -240
  122. package/dist/modules/security/index.js +0 -14
  123. package/dist/modules/security/network-validator.js +0 -79
  124. package/dist/modules/security/path-validator.js +0 -209
  125. package/dist/modules/security/rate-limiter.js +0 -119
  126. package/dist/modules/security/security-policies.js +0 -547
  127. package/dist/modules/security/session-encryption.js +0 -210
  128. package/dist/modules/security/session-isolation.js +0 -95
  129. package/dist/modules/session/index.js +0 -3
  130. package/dist/modules/session/manager.js +0 -172
  131. package/dist/modules/session/module.js +0 -24
  132. package/dist/modules/session/store.js +0 -228
  133. package/dist/modules/session/types.js +0 -1
  134. package/dist/modules/skills/index.js +0 -2
  135. package/dist/modules/skills/loader.js +0 -72
  136. package/dist/modules/skills/module.js +0 -130
  137. package/dist/modules/types.js +0 -1
  138. package/dist/modules/updater/checker.js +0 -32
  139. package/dist/modules/updater/index.js +0 -1
  140. package/dist/modules/user-profile/compressor.js +0 -16
  141. package/dist/modules/user-profile/index.js +0 -1
  142. package/dist/modules/user-profile/profile.js +0 -68
  143. package/dist/tools/approve.js +0 -32
  144. package/dist/tools/attach-image.js +0 -89
  145. package/dist/tools/bash.js +0 -337
  146. package/dist/tools/browser.js +0 -97
  147. package/dist/tools/create-dir.js +0 -55
  148. package/dist/tools/delete-file.js +0 -62
  149. package/dist/tools/edit-file.js +0 -79
  150. package/dist/tools/executor.js +0 -145
  151. package/dist/tools/file-info.js +0 -45
  152. package/dist/tools/filter-tools.js +0 -10
  153. package/dist/tools/glob-tool.js +0 -26
  154. package/dist/tools/grep-tool.js +0 -86
  155. package/dist/tools/index.js +0 -67
  156. package/dist/tools/list-dir.js +0 -47
  157. package/dist/tools/load-skill.js +0 -44
  158. package/dist/tools/mcp-call.js +0 -68
  159. package/dist/tools/move-file.js +0 -85
  160. package/dist/tools/path-utils.js +0 -51
  161. package/dist/tools/pipeline-run.js +0 -144
  162. package/dist/tools/preview.js +0 -2
  163. package/dist/tools/process-kill.js +0 -29
  164. package/dist/tools/process-list.js +0 -38
  165. package/dist/tools/process-log.js +0 -41
  166. package/dist/tools/question.js +0 -142
  167. package/dist/tools/read-file.js +0 -83
  168. package/dist/tools/recall.js +0 -110
  169. package/dist/tools/registry.js +0 -36
  170. package/dist/tools/remember.js +0 -67
  171. package/dist/tools/scope-check.js +0 -30
  172. package/dist/tools/search-history.js +0 -84
  173. package/dist/tools/subagent.js +0 -151
  174. package/dist/tools/types.js +0 -1
  175. package/dist/tools/user-input.js +0 -123
  176. package/dist/tools/web-browse.js +0 -86
  177. package/dist/tools/web-fetch.js +0 -98
  178. package/dist/tools/web-search.js +0 -78
  179. package/dist/tools/write-file.js +0 -83
  180. package/dist/ui/box.js +0 -81
  181. package/dist/ui/colors.js +0 -4
  182. package/dist/ui/diff.js +0 -178
  183. package/dist/ui/index.js +0 -6
  184. package/dist/ui/md-formatter.js +0 -212
  185. package/dist/ui/output.js +0 -13
  186. package/dist/ui/renderer.js +0 -204
  187. package/dist/ui/spinner.js +0 -70
  188. package/dist/ui/table.js +0 -144
@@ -1,85 +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) {
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
- })),
39
- createdAt: new Date().toISOString(),
40
- baseDir,
41
- };
42
- }
43
- static replan(plan, newSteps, title) {
44
- const kept = plan.steps.filter((s) => s.status === "done" || s.status === "skipped");
45
- const baseTitle = plan.title.replace(/^\[\d+[^]]*\]\s*/, "");
46
- const newTitle = title || baseTitle;
47
- const totalSteps = kept.length + newSteps.length;
48
- const added = newSteps.map((desc, i) => ({
49
- id: kept.length + i + 1,
50
- description: desc,
51
- status: "pending",
52
- }));
53
- return {
54
- id: plan.id,
55
- title: `[${totalSteps}] ${newTitle}`,
56
- steps: [...kept, ...added],
57
- createdAt: plan.createdAt,
58
- baseDir: plan.baseDir,
59
- name: plan.name,
60
- };
61
- }
62
- static toPromptBlock(plan, currentStepIndex) {
63
- const date = plan.createdAt.slice(0, 10);
64
- const lines = [
65
- `[${plan.id}] ${plan.title}`,
66
- `Dir: ${plan.baseDir}`,
67
- `Created: ${date} | Progress: ${plan.steps.filter((s) => s.status === "done").length}/${plan.steps.length} done, current: step ${currentStepIndex + 1}`,
68
- ``,
69
- ];
70
- for (const step of plan.steps) {
71
- const icon = step.status === "done"
72
- ? "[x]"
73
- : step.status === "in_progress"
74
- ? "[*]"
75
- : step.status === "failed"
76
- ? "[!]"
77
- : step.status === "skipped"
78
- ? "[-]"
79
- : "[ ]";
80
- const note = step.note ? ` — ${step.note}` : "";
81
- lines.push(`${icon} ${step.id}. ${step.description}${note}`);
82
- }
83
- return lines.join("\n");
84
- }
85
- }
@@ -1,347 +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
- export class StuckDetector {
39
- threshold;
40
- errorThreshold;
41
- iterationsOnCurrentStep = 0;
42
- currentStepId = null;
43
- toolErrors = new Map();
44
- currentStepDescription = "";
45
- recentToolCalls = [];
46
- maxRecentCalls = 10;
47
- repetitionThreshold = 3;
48
- consecutiveFailures = 0;
49
- lastFailedTool = "";
50
- lastErrorOutput = "";
51
- escalationCount = 0;
52
- escalationThreshold = 3;
53
- fileRewriteCount = new Map();
54
- fileRewriteThreshold = 3;
55
- constructor(threshold = 6, errorThreshold = 3) {
56
- this.threshold = threshold;
57
- this.errorThreshold = errorThreshold;
58
- }
59
- recordIteration(stepId) {
60
- if (stepId === this.currentStepId) {
61
- this.iterationsOnCurrentStep++;
62
- }
63
- else {
64
- this.resetStepState(stepId);
65
- }
66
- }
67
- recordToolCall(name, args) {
68
- const argsKey = JSON.stringify(args);
69
- this.recentToolCalls.push({ name, argsKey });
70
- if (this.recentToolCalls.length > this.maxRecentCalls) {
71
- this.recentToolCalls.shift();
72
- }
73
- }
74
- recordToolError(toolName, output) {
75
- this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
76
- this.consecutiveFailures++;
77
- this.lastFailedTool = toolName;
78
- if (output)
79
- this.lastErrorOutput = output;
80
- }
81
- getLastErrorOutput() {
82
- return this.lastErrorOutput;
83
- }
84
- getLastFailedTool() {
85
- return this.lastFailedTool;
86
- }
87
- getIterationsOnCurrentStep() {
88
- return this.iterationsOnCurrentStep;
89
- }
90
- recordToolSuccess() {
91
- this.consecutiveFailures = 0;
92
- this.lastFailedTool = "";
93
- this.escalationCount = 0;
94
- }
95
- setCurrentStep(stepId, description) {
96
- if (stepId !== this.currentStepId) {
97
- this.resetStepState(stepId);
98
- }
99
- this.currentStepDescription = description;
100
- }
101
- isStuck() {
102
- return this.iterationsOnCurrentStep > this.threshold;
103
- }
104
- hasRepetitiveErrors(toolName) {
105
- if (toolName) {
106
- return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
107
- }
108
- return Array.from(this.toolErrors.values()).some((c) => c >= this.errorThreshold);
109
- }
110
- hasRepetitiveToolCalls() {
111
- if (this.recentToolCalls.length < this.repetitionThreshold)
112
- return false;
113
- const last = this.recentToolCalls[this.recentToolCalls.length - 1];
114
- // Check consecutive identical calls (original logic)
115
- let consecutiveCount = 1;
116
- for (let i = this.recentToolCalls.length - 2; i >= 0; i--) {
117
- if (this.recentToolCalls[i].name === last.name &&
118
- this.recentToolCalls[i].argsKey === last.argsKey) {
119
- consecutiveCount++;
120
- }
121
- else {
122
- break;
123
- }
124
- }
125
- if (consecutiveCount >= this.repetitionThreshold)
126
- return true;
127
- // Also detect interleaved patterns (A, B, A, B, A) — count any single
128
- // tool+args combo in the window and flag if it appears >= threshold times
129
- const counts = new Map();
130
- for (const call of this.recentToolCalls) {
131
- const key = `${call.name}::${call.argsKey}`;
132
- counts.set(key, (counts.get(key) || 0) + 1);
133
- }
134
- for (const count of counts.values()) {
135
- if (count >= this.repetitionThreshold)
136
- return true;
137
- }
138
- return false;
139
- }
140
- hasConsecutiveFailures() {
141
- return this.consecutiveFailures >= this.errorThreshold;
142
- }
143
- getConsecutiveFailuresCount() {
144
- return this.consecutiveFailures;
145
- }
146
- recordEscalation() {
147
- this.escalationCount++;
148
- }
149
- shouldEscalate() {
150
- return this.escalationCount >= this.escalationThreshold;
151
- }
152
- resetEscalation() {
153
- this.escalationCount = 0;
154
- }
155
- getEscalationCount() {
156
- return this.escalationCount;
157
- }
158
- getHints() {
159
- const hints = [];
160
- const desc = this.currentStepDescription.toLowerCase();
161
- if (desc.includes("install") ||
162
- desc.includes("npm") ||
163
- desc.includes("pip")) {
164
- hints.push("Check if a lock file exists (package-lock.json, poetry.lock). If missing, run the install command first.");
165
- }
166
- if (desc.includes("test") || desc.includes("spec")) {
167
- hints.push("Make sure the source files exist and have real code before running tests.");
168
- }
169
- if (desc.includes("build") || desc.includes("compile")) {
170
- hints.push("Check that all dependencies are installed and source files are not empty.");
171
- }
172
- if (desc.includes("deploy") || desc.includes("publish")) {
173
- hints.push("Verify credentials and network access before deploying.");
174
- }
175
- if (this.hasRepetitiveToolCalls()) {
176
- hints.push("You are calling the same tool repeatedly with the same arguments. Try a different approach.");
177
- }
178
- if (this.hasConsecutiveFailures()) {
179
- hints.push("Multiple different tools are failing. Check if the environment is set up correctly.");
180
- }
181
- return hints;
182
- }
183
- getActionableHints() {
184
- const hints = [];
185
- const error = this.lastErrorOutput;
186
- if (!error)
187
- return hints;
188
- // Runtime incompatibility — tool/module crashes on this Node version
189
- if (/Cannot read properties of undefined|is not a function|is not a constructor/i.test(error)) {
190
- if (/node_modules/.test(error)) {
191
- hints.push("A dependency is incompatible with your Node.js version. Check if there is an alternative package or use a different runtime.");
192
- }
193
- }
194
- // TypeScript syntax errors — model generated invalid code
195
- if (/TS1005|TS1003|TS1109|TS1128|TS1434/i.test(error)) {
196
- hints.push("TypeScript syntax error. Read the error message carefully — it tells you the exact line and column. Fix the syntax before retrying.");
197
- }
198
- // TypeScript type errors — model doesn't understand the types
199
- if (/TS2322|TS2345|TS2769|TS7006|TS7016/i.test(error)) {
200
- 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.");
201
- }
202
- // Module not found — missing dependency or wrong import
203
- if (/Cannot find module|Module not found|ERR_MODULE_NOT_FOUND/i.test(error)) {
204
- hints.push("Module not found. Run npm install or check if the import path is correct.");
205
- }
206
- // Permission / access errors
207
- if (/EACCES|EPERM|permission denied/i.test(error)) {
208
- hints.push("Permission denied. Check file permissions or run with appropriate privileges.");
209
- }
210
- // Network errors
211
- if (/ECONNREFUSED|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(error)) {
212
- hints.push("Network error. Check if the server is running and accessible.");
213
- }
214
- // Command not found
215
- if (/is not recognized|command not found|not found in path/i.test(error)) {
216
- hints.push("Command not found. Check if the tool is installed and in PATH.");
217
- }
218
- // File rewrite loop — same file keeps failing
219
- if (this.hasExcessiveRewrites()) {
220
- const file = this.getExcessiveRewriteFile();
221
- hints.push(`File ${file} has been rewritten ${this.getFileRewriteCount(file)} times without success. Stop rewriting and try a fundamentally different approach.`);
222
- }
223
- return hints;
224
- }
225
- getToolAlternative() {
226
- const tool = this.lastFailedTool;
227
- const error = this.lastErrorOutput;
228
- if (!tool)
229
- return null;
230
- // If a tool fails with a runtime crash, suggest alternatives
231
- if (/Cannot read properties of undefined|is not a function|TypeError|ReferenceError/i.test(error)) {
232
- // Map of tools to their common alternatives
233
- const alternatives = {
234
- "ts-node": "tsx",
235
- jest: "vitest",
236
- mocha: "vitest",
237
- webpack: "vite",
238
- rollup: "vite",
239
- parcel: "vite",
240
- babel: "tsc",
241
- eslint: "biome",
242
- prettier: "biome",
243
- };
244
- for (const [failed, alt] of Object.entries(alternatives)) {
245
- if (tool.includes(failed) || error.includes(failed)) {
246
- return alt;
247
- }
248
- }
249
- }
250
- return null;
251
- }
252
- recordFileRewrite(filePath) {
253
- const count = (this.fileRewriteCount.get(filePath) || 0) + 1;
254
- this.fileRewriteCount.set(filePath, count);
255
- }
256
- getFileRewriteCount(filePath) {
257
- return this.fileRewriteCount.get(filePath) || 0;
258
- }
259
- hasExcessiveRewrites() {
260
- return Array.from(this.fileRewriteCount.values()).some((c) => c >= this.fileRewriteThreshold);
261
- }
262
- getExcessiveRewriteFile() {
263
- for (const [file, count] of this.fileRewriteCount) {
264
- if (count >= this.fileRewriteThreshold)
265
- return file;
266
- }
267
- return null;
268
- }
269
- getRepetitiveToolMessage() {
270
- if (!this.hasRepetitiveToolCalls())
271
- return "";
272
- const last = this.recentToolCalls[this.recentToolCalls.length - 1];
273
- return t("exec.repetitive_tool", {
274
- tool: last.name,
275
- count: this.repetitionThreshold,
276
- });
277
- }
278
- getStuckReason() {
279
- if (this.isStuck()) {
280
- return t("exec.stuck", {
281
- stepId: String(this.currentStepId ?? "?"),
282
- description: this.currentStepDescription,
283
- iterations: this.iterationsOnCurrentStep,
284
- });
285
- }
286
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
287
- if (errorTool) {
288
- return t("exec.tool_errors", { tool: errorTool[0], count: errorTool[1] });
289
- }
290
- return "";
291
- }
292
- getRecoveryMessage() {
293
- if (this.isStuck()) {
294
- return t("exec.stuck_recovery", {
295
- iterations: this.iterationsOnCurrentStep,
296
- stepId: String(this.currentStepId ?? "?"),
297
- description: this.currentStepDescription,
298
- });
299
- }
300
- // Per-tool error recovery (more specific — e.g., "bash failed 3 times")
301
- const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
302
- if (errorTool) {
303
- return t("exec.tool_errors_recovery", {
304
- tool: errorTool[0],
305
- count: errorTool[1],
306
- });
307
- }
308
- // Consecutive failures from different tools (generic — e.g., "5 consecutive failures")
309
- if (this.hasConsecutiveFailures()) {
310
- return t("exec.consecutive_failures_recovery", {
311
- count: this.consecutiveFailures,
312
- });
313
- }
314
- if (this.hasRepetitiveToolCalls()) {
315
- return this.getRepetitiveToolMessage();
316
- }
317
- return "";
318
- }
319
- reset() {
320
- this.currentStepId = null;
321
- this.iterationsOnCurrentStep = 0;
322
- this.toolErrors.clear();
323
- this.consecutiveFailures = 0;
324
- this.lastFailedTool = "";
325
- this.lastErrorOutput = "";
326
- this.recentToolCalls = [];
327
- this.fileRewriteCount.clear();
328
- this.escalationCount = 0;
329
- }
330
- /**
331
- * Reset all per-step state when moving to a new step. Prevents errors and
332
- * tool repetitions from one step leaking into the next (false-positive
333
- * stuck/recovery on the first iteration of the new step).
334
- */
335
- resetStepState(stepId) {
336
- this.currentStepId = stepId;
337
- this.iterationsOnCurrentStep = 1;
338
- this.toolErrors.clear();
339
- this.consecutiveFailures = 0;
340
- this.lastFailedTool = "";
341
- this.lastErrorOutput = "";
342
- this.recentToolCalls = [];
343
- this.fileRewriteCount.clear();
344
- // Preserve escalationCount — prevents agent from "gaming" stuck detection
345
- // by marking a step done and moving to the next one
346
- }
347
- }
@@ -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 {};
@@ -1,178 +0,0 @@
1
- import { existsSync } from 'fs';
2
- import { resolve, extname } from 'path';
3
- import { spawn } from 'child_process';
4
- import { t } from '../../i18n/index';
5
- import { validateExpertConfig } from '../../config/experts';
6
- export class StepVerifier {
7
- baseDir;
8
- constructor(baseDir) {
9
- this.baseDir = baseDir;
10
- }
11
- async checkFileExists(path) {
12
- const resolved = resolve(this.baseDir, path);
13
- const exists = existsSync(resolved);
14
- return {
15
- passed: exists,
16
- message: exists ? t('verify.file_exists', { path }) : t('verify.file_not_found', { path }),
17
- };
18
- }
19
- async runScript(scriptName) {
20
- try {
21
- await this.runAsync(`bun run ${scriptName}`, this.baseDir, 60_000);
22
- return { passed: true, message: t('verify.script_passed', { script: scriptName }) };
23
- }
24
- catch (e) {
25
- return { passed: false, message: t('verify.script_failed', { script: scriptName, message: e.message }) };
26
- }
27
- }
28
- async runTypeCheck() {
29
- const tsconfigPath = resolve(this.baseDir, 'tsconfig.json');
30
- if (!existsSync(tsconfigPath)) {
31
- return { passed: true, message: 'No tsconfig.json found — skipping type check' };
32
- }
33
- try {
34
- await this.runAsync('npx tsc --noEmit', this.baseDir, 60_000);
35
- return { passed: true, message: 'TypeScript type check passed' };
36
- }
37
- catch (e) {
38
- const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
39
- return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
40
- }
41
- }
42
- async runTests() {
43
- const pkgPath = resolve(this.baseDir, 'package.json');
44
- if (!existsSync(pkgPath)) {
45
- return { passed: true, message: 'No package.json found — skipping tests' };
46
- }
47
- try {
48
- const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf-8'));
49
- if (!pkg.scripts?.test) {
50
- return { passed: true, message: 'No test script defined — skipping tests' };
51
- }
52
- }
53
- catch { /* fall through — attempt to run */ }
54
- return this.runScript('test');
55
- }
56
- async verifyArtifactFiles(files) {
57
- return Promise.all(files.map(f => this.checkFileExists(f)));
58
- }
59
- async verifyMoEManifest(plan, config, allToolTags) {
60
- const errors = [];
61
- const warnings = [];
62
- const details = [];
63
- const expertErrors = validateExpertConfig(config, allToolTags);
64
- for (const err of expertErrors) {
65
- errors.push(err);
66
- details.push({ passed: false, message: err });
67
- }
68
- const tscResult = await this.runTypeCheck();
69
- details.push(tscResult);
70
- if (!tscResult.passed) {
71
- errors.push(tscResult.message);
72
- }
73
- for (const sub of plan.subtasks) {
74
- const fileChecks = await this.verifyArtifactFiles(sub.allowed_files || []);
75
- for (const check of fileChecks) {
76
- details.push(check);
77
- if (!check.passed) {
78
- errors.push(check.message);
79
- }
80
- }
81
- }
82
- return {
83
- success: errors.length === 0,
84
- errors,
85
- warnings,
86
- details,
87
- };
88
- }
89
- async verifyStep(stepDescription) {
90
- const fileMatches = stepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || [];
91
- const results = [];
92
- let syntaxValid = true;
93
- for (const filePath of fileMatches) {
94
- const result = await this.checkFileExists(filePath);
95
- results.push(result);
96
- if (result.passed) {
97
- const fullPath = resolve(this.baseDir, filePath);
98
- if (!(await this.validateSyntax(fullPath))) {
99
- syntaxValid = false;
100
- results.push({ passed: false, message: t('verify.syntax_error', { path: filePath }) });
101
- }
102
- }
103
- }
104
- return {
105
- passed: results.every(r => r.passed),
106
- syntaxValid,
107
- failed: results.filter(r => !r.passed),
108
- };
109
- }
110
- async validateSyntax(filePath) {
111
- const ext = extname(filePath);
112
- if (ext === '.ts' || ext === '.tsx') {
113
- try {
114
- await this.runAsync(`npx tsc --noEmit --skipLibCheck ${filePath}`, this.baseDir, 10000);
115
- return true;
116
- }
117
- catch (err) {
118
- if (err.status === 127 || err.message.includes('not found') || err.message.includes('ENOENT')) {
119
- return true;
120
- }
121
- const stderr = err.stderr?.toString() || '';
122
- if (stderr.includes('error TS') && !stderr.includes('Cannot find module')) {
123
- return false;
124
- }
125
- return true;
126
- }
127
- }
128
- if (ext === '.js' || ext === '.jsx') {
129
- try {
130
- await this.runAsync(`node --check ${filePath}`, this.baseDir, 5000);
131
- return true;
132
- }
133
- catch {
134
- return false;
135
- }
136
- }
137
- return true;
138
- }
139
- /**
140
- * Run a command asynchronously (non-blocking event loop) and resolve when it
141
- * exits. Rejects with the captured stderr/stdout on non-zero exit or timeout.
142
- */
143
- runAsync(command, cwd, timeoutMs) {
144
- return new Promise((resolve, reject) => {
145
- const child = spawn(command, {
146
- cwd,
147
- shell: true,
148
- windowsHide: true,
149
- stdio: ['ignore', 'pipe', 'pipe'],
150
- });
151
- let stdout = '';
152
- let stderr = '';
153
- child.stdout?.on('data', (d) => { stdout += d.toString(); });
154
- child.stderr?.on('data', (d) => { stderr += d.toString(); });
155
- const timer = setTimeout(() => {
156
- child.kill();
157
- reject(new Error(`Command timed out after ${timeoutMs}ms`));
158
- }, timeoutMs);
159
- child.on('error', (err) => {
160
- clearTimeout(timer);
161
- reject(err);
162
- });
163
- child.on('close', (code) => {
164
- clearTimeout(timer);
165
- if (code === 0) {
166
- resolve({ stdout, stderr });
167
- }
168
- else {
169
- const err = new Error(`Command failed with exit code ${code}`);
170
- err.stdout = stdout;
171
- err.stderr = stderr;
172
- err.status = code;
173
- reject(err);
174
- }
175
- });
176
- });
177
- }
178
- }