micro-models-agent 0.23.0 → 0.24.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 (169) hide show
  1. package/README.md +312 -312
  2. package/bin/mma.mjs +8 -8
  3. package/dist/main.js +8777 -864
  4. package/package.json +3 -2
  5. package/dist/cli/commands.js +0 -220
  6. package/dist/cli/completer.js +0 -168
  7. package/dist/cli/index.js +0 -2
  8. package/dist/cli/main.js +0 -113
  9. package/dist/cli/repl.js +0 -987
  10. package/dist/cli/security-commands.js +0 -166
  11. package/dist/cli/setup.js +0 -229
  12. package/dist/config/config.js +0 -186
  13. package/dist/config/defaults.js +0 -91
  14. package/dist/config/experts.js +0 -15
  15. package/dist/config/index.js +0 -3
  16. package/dist/config/security.js +0 -193
  17. package/dist/config/types.js +0 -1
  18. package/dist/core/agent-moe.js +0 -98
  19. package/dist/core/agent.js +0 -461
  20. package/dist/core/bootstrap.js +0 -321
  21. package/dist/core/index.js +0 -2
  22. package/dist/core/prompt-builder.js +0 -55
  23. package/dist/core/session-logger.js +0 -122
  24. package/dist/core/types.js +0 -1
  25. package/dist/i18n/en.json +0 -461
  26. package/dist/i18n/index.js +0 -43
  27. package/dist/i18n/ru.json +0 -461
  28. package/dist/index.js +0 -22
  29. package/dist/llm/image-utils.js +0 -144
  30. package/dist/llm/index.js +0 -4
  31. package/dist/llm/model-loader.js +0 -78
  32. package/dist/llm/openai-compat.js +0 -324
  33. package/dist/llm/orchestrator.js +0 -194
  34. package/dist/llm/provider.js +0 -10
  35. package/dist/llm/response.js +0 -39
  36. package/dist/llm/token-counter.js +0 -39
  37. package/dist/llm/types.js +0 -1
  38. package/dist/logger/app-logger.js +0 -76
  39. package/dist/logger/index.js +0 -1
  40. package/dist/migration/backup.js +0 -45
  41. package/dist/migration/detect.js +0 -50
  42. package/dist/migration/index.js +0 -2
  43. package/dist/modules/browser/actions.js +0 -46
  44. package/dist/modules/browser/cookie-store.js +0 -24
  45. package/dist/modules/browser/index.js +0 -5
  46. package/dist/modules/browser/module.js +0 -28
  47. package/dist/modules/browser/session.js +0 -287
  48. package/dist/modules/browser/snapshot.js +0 -114
  49. package/dist/modules/browser/types.js +0 -9
  50. package/dist/modules/context/history.js +0 -15
  51. package/dist/modules/context/index.js +0 -1
  52. package/dist/modules/context/manager.js +0 -240
  53. package/dist/modules/execution/auditor.js +0 -72
  54. package/dist/modules/execution/index.js +0 -6
  55. package/dist/modules/execution/module.js +0 -337
  56. package/dist/modules/execution/moe-executor.js +0 -209
  57. package/dist/modules/execution/plan-validator.js +0 -153
  58. package/dist/modules/execution/planner.js +0 -35
  59. package/dist/modules/execution/stuck-detector.js +0 -134
  60. package/dist/modules/execution/tracker.js +0 -53
  61. package/dist/modules/execution/types.js +0 -1
  62. package/dist/modules/execution/verifier.js +0 -149
  63. package/dist/modules/hallucination/confidence.js +0 -54
  64. package/dist/modules/hallucination/consistency.js +0 -60
  65. package/dist/modules/hallucination/detector.js +0 -41
  66. package/dist/modules/hallucination/factual.js +0 -170
  67. package/dist/modules/hallucination/index.js +0 -4
  68. package/dist/modules/index.js +0 -5
  69. package/dist/modules/indexer/cache.js +0 -38
  70. package/dist/modules/indexer/index.js +0 -3
  71. package/dist/modules/indexer/module.js +0 -192
  72. package/dist/modules/indexer/walker.js +0 -101
  73. package/dist/modules/mcp/client.js +0 -393
  74. package/dist/modules/mcp/index.js +0 -3
  75. package/dist/modules/mcp/module.js +0 -146
  76. package/dist/modules/mcp/registry.js +0 -15
  77. package/dist/modules/memory/index.js +0 -1
  78. package/dist/modules/memory/module.js +0 -48
  79. package/dist/modules/memory/search.js +0 -40
  80. package/dist/modules/memory/store.js +0 -65
  81. package/dist/modules/pipelines/engine.js +0 -60
  82. package/dist/modules/pipelines/index.js +0 -3
  83. package/dist/modules/pipelines/parser.js +0 -53
  84. package/dist/modules/pipelines/template.js +0 -14
  85. package/dist/modules/plugins/builtin/lint-on-write.js +0 -121
  86. package/dist/modules/plugins/builtin/notify.js +0 -8
  87. package/dist/modules/plugins/index.js +0 -1
  88. package/dist/modules/plugins/loader.js +0 -28
  89. package/dist/modules/plugins/manager.js +0 -161
  90. package/dist/modules/plugins/types.js +0 -1
  91. package/dist/modules/processes/detect.js +0 -34
  92. package/dist/modules/processes/index.js +0 -3
  93. package/dist/modules/processes/registry.js +0 -148
  94. package/dist/modules/processes/runner.js +0 -124
  95. package/dist/modules/registry.js +0 -45
  96. package/dist/modules/security/audit-log.js +0 -116
  97. package/dist/modules/security/audit-notifier.js +0 -292
  98. package/dist/modules/security/command-validator.js +0 -185
  99. package/dist/modules/security/content-scanner.js +0 -52
  100. package/dist/modules/security/data-sanitizer.js +0 -97
  101. package/dist/modules/security/encryption.js +0 -240
  102. package/dist/modules/security/index.js +0 -14
  103. package/dist/modules/security/network-validator.js +0 -79
  104. package/dist/modules/security/path-validator.js +0 -155
  105. package/dist/modules/security/rate-limiter.js +0 -119
  106. package/dist/modules/security/security-policies.js +0 -393
  107. package/dist/modules/security/session-encryption.js +0 -193
  108. package/dist/modules/security/session-isolation.js +0 -95
  109. package/dist/modules/session/index.js +0 -3
  110. package/dist/modules/session/manager.js +0 -167
  111. package/dist/modules/session/module.js +0 -24
  112. package/dist/modules/session/store.js +0 -174
  113. package/dist/modules/session/types.js +0 -1
  114. package/dist/modules/skills/index.js +0 -3
  115. package/dist/modules/skills/loader.js +0 -72
  116. package/dist/modules/skills/matcher.js +0 -27
  117. package/dist/modules/skills/module.js +0 -143
  118. package/dist/modules/types.js +0 -1
  119. package/dist/modules/updater/checker.js +0 -32
  120. package/dist/modules/updater/index.js +0 -1
  121. package/dist/modules/user-profile/compressor.js +0 -16
  122. package/dist/modules/user-profile/index.js +0 -1
  123. package/dist/modules/user-profile/profile.js +0 -68
  124. package/dist/tools/approve.js +0 -32
  125. package/dist/tools/attach-image.js +0 -89
  126. package/dist/tools/bash.js +0 -140
  127. package/dist/tools/browser.js +0 -97
  128. package/dist/tools/create-dir.js +0 -56
  129. package/dist/tools/delete-file.js +0 -63
  130. package/dist/tools/edit-file.js +0 -77
  131. package/dist/tools/executor.js +0 -95
  132. package/dist/tools/file-info.js +0 -45
  133. package/dist/tools/filter-tools.js +0 -10
  134. package/dist/tools/glob-tool.js +0 -26
  135. package/dist/tools/grep-tool.js +0 -64
  136. package/dist/tools/index.js +0 -52
  137. package/dist/tools/list-dir.js +0 -47
  138. package/dist/tools/load-skill.js +0 -48
  139. package/dist/tools/mcp-call.js +0 -68
  140. package/dist/tools/move-file.js +0 -84
  141. package/dist/tools/path-utils.js +0 -51
  142. package/dist/tools/pipeline-run.js +0 -144
  143. package/dist/tools/preview.js +0 -2
  144. package/dist/tools/process-kill.js +0 -29
  145. package/dist/tools/process-list.js +0 -38
  146. package/dist/tools/process-log.js +0 -41
  147. package/dist/tools/question.js +0 -142
  148. package/dist/tools/read-file.js +0 -73
  149. package/dist/tools/recall.js +0 -110
  150. package/dist/tools/registry.js +0 -36
  151. package/dist/tools/remember.js +0 -67
  152. package/dist/tools/scope-check.js +0 -30
  153. package/dist/tools/search-history.js +0 -64
  154. package/dist/tools/subagent.js +0 -142
  155. package/dist/tools/types.js +0 -1
  156. package/dist/tools/user-input.js +0 -123
  157. package/dist/tools/web-browse.js +0 -57
  158. package/dist/tools/web-fetch.js +0 -72
  159. package/dist/tools/web-search.js +0 -59
  160. package/dist/tools/write-file.js +0 -80
  161. package/dist/ui/box.js +0 -81
  162. package/dist/ui/colors.js +0 -4
  163. package/dist/ui/diff.js +0 -185
  164. package/dist/ui/index.js +0 -6
  165. package/dist/ui/md-formatter.js +0 -212
  166. package/dist/ui/output.js +0 -13
  167. package/dist/ui/renderer.js +0 -141
  168. package/dist/ui/spinner.js +0 -70
  169. package/dist/ui/table.js +0 -144
@@ -1,72 +0,0 @@
1
- import { execSync } from 'child_process';
2
- import { existsSync } from 'fs';
3
- import { resolve, join } from 'path';
4
- import { t } from '../../i18n/index';
5
- export class Auditor {
6
- baseDir;
7
- constructor(baseDir) {
8
- this.baseDir = baseDir;
9
- }
10
- async audit(plan) {
11
- const allStepText = plan.steps.map(s => s.description).join(' ');
12
- const fileMatches = allStepText.match(/\b[\w./-]+\.[a-z]+/gi) || [];
13
- const uniqueFiles = [...new Set(fileMatches)];
14
- const missingFiles = [];
15
- const existingFiles = [];
16
- for (const filePath of uniqueFiles) {
17
- const resolved = resolve(this.baseDir, filePath);
18
- if (existsSync(resolved)) {
19
- existingFiles.push(filePath);
20
- }
21
- else {
22
- missingFiles.push(filePath);
23
- }
24
- }
25
- const doneSteps = plan.steps.filter(s => s.status === 'done').length;
26
- const totalSteps = plan.steps.length;
27
- const typeCheckError = await this.runProjectTypeCheck();
28
- const passed = missingFiles.length === 0 && !typeCheckError;
29
- let summary;
30
- if (passed) {
31
- summary = t('exec.audit_pass', { done: doneSteps, total: totalSteps, files: existingFiles.length });
32
- }
33
- else {
34
- const missingCount = missingFiles.length;
35
- if (typeCheckError) {
36
- summary = t('exec.audit_fail_typecheck', { done: doneSteps, total: totalSteps, missing: missingCount, typeError: typeCheckError });
37
- }
38
- else {
39
- summary = t('exec.audit_fail', { done: doneSteps, total: totalSteps, files: missingCount });
40
- }
41
- }
42
- return {
43
- passed,
44
- missingFiles,
45
- createdFiles: existingFiles,
46
- modifiedFiles: [],
47
- summary,
48
- typeCheckError,
49
- };
50
- }
51
- async runProjectTypeCheck() {
52
- const tsconfigPath = join(this.baseDir, 'tsconfig.json');
53
- if (!existsSync(tsconfigPath)) {
54
- return null;
55
- }
56
- try {
57
- execSync(`npx tsc --noEmit --skipLibCheck`, { cwd: this.baseDir, stdio: 'pipe', timeout: 30000 });
58
- return null;
59
- }
60
- catch (err) {
61
- if (err.status === 127 || err.message.includes('not found') || err.message.includes('ENOENT')) {
62
- return null;
63
- }
64
- const stderr = err.stderr?.toString() || err.stdout?.toString() || '';
65
- if (stderr.includes('error TS')) {
66
- const firstError = stderr.split('\n').find((line) => line.includes('error TS')) || 'TypeScript type error';
67
- return firstError.trim();
68
- }
69
- return null;
70
- }
71
- }
72
- }
@@ -1,6 +0,0 @@
1
- export { ExecutionModule } from './module';
2
- export { PlanCreator } from './planner';
3
- export { PlanTracker } from './tracker';
4
- export { StepVerifier } from './verifier';
5
- export { StuckDetector } from './stuck-detector';
6
- export { Auditor } from './auditor';
@@ -1,337 +0,0 @@
1
- import { t } from "../../i18n/index";
2
- import { PlanCreator } from "./planner";
3
- import { PlanTracker } from "./tracker";
4
- import { StepVerifier } from "./verifier";
5
- import { StuckDetector } from "./stuck-detector";
6
- import { Auditor } from "./auditor";
7
- import { existsSync } from "fs";
8
- import { resolve } from "path";
9
- const STUCK_RECOVERY_COOLDOWN = 5;
10
- export class ExecutionModule {
11
- name = "execution";
12
- tracker = null;
13
- verifier;
14
- stuckDetector;
15
- auditor;
16
- baseDir;
17
- lastRecoveryIteration = -STUCK_RECOVERY_COOLDOWN;
18
- constructor(baseDir, stuckThreshold = 8) {
19
- this.baseDir = baseDir;
20
- this.verifier = new StepVerifier(baseDir);
21
- this.stuckDetector = new StuckDetector(stuckThreshold);
22
- this.auditor = new Auditor(baseDir);
23
- }
24
- setPlan(plan) {
25
- this.tracker = new PlanTracker(plan);
26
- }
27
- getTracker() {
28
- return this.tracker;
29
- }
30
- getStuckDetector() {
31
- return this.stuckDetector;
32
- }
33
- /**
34
- * Final audit before the agent may declare the task done. Returns null when
35
- * no plan is active; otherwise checks plan completion + artifact existence.
36
- */
37
- async runFinalAudit() {
38
- if (!this.tracker)
39
- return null;
40
- const plan = this.tracker.getPlan();
41
- const audit = await this.auditor.audit(plan);
42
- const pendingSteps = plan.steps
43
- .filter((s) => s.status !== "done" && s.status !== "skipped")
44
- .map((s) => `${s.id}. ${s.description}`);
45
- const done = plan.steps.filter((s) => s.status === "done").length;
46
- const passed = audit.passed && pendingSteps.length === 0;
47
- return {
48
- passed,
49
- done,
50
- total: plan.steps.length,
51
- pendingSteps,
52
- missingFiles: audit.missingFiles,
53
- summary: audit.summary,
54
- };
55
- }
56
- getSystemPromptBlock() {
57
- if (!this.tracker)
58
- return null;
59
- return {
60
- content: this.tracker.toPromptBlock(),
61
- priority: "critical",
62
- essential: true,
63
- estimatedTokens: 200,
64
- };
65
- }
66
- getToolDefinitions() {
67
- return [
68
- {
69
- name: "plan",
70
- description: 'Create, update, show, or abort a multi-step plan. Use "create" at the start of complex tasks. Use "update" after completing each step to track progress. Use "show" to re-print the current plan checklist.',
71
- parameters: {
72
- type: "object",
73
- properties: {
74
- action: {
75
- type: "string",
76
- enum: ["create", "update", "show", "abort"],
77
- },
78
- title: { type: "string" },
79
- steps: { type: "array", items: { type: "string" } },
80
- step: { type: "number" },
81
- status: { type: "string", enum: ["done", "failed", "skipped"] },
82
- note: { type: "string" },
83
- },
84
- required: ["action"],
85
- },
86
- handler: async (_ctx, args) => {
87
- const action = String(args.action);
88
- if (action === "create") {
89
- const title = String(args.title || "Task Plan");
90
- const steps = Array.isArray(args.steps)
91
- ? args.steps.map(String)
92
- : [];
93
- if (steps.length === 0) {
94
- return { success: false, output: t("plan.no_steps") };
95
- }
96
- const plan = PlanCreator.createPlan(title, steps);
97
- this.setPlan(plan);
98
- const display = PlanCreator.toPromptBlock(plan, 0);
99
- return {
100
- success: true,
101
- output: t("plan.created", { title, steps: String(steps.length) }),
102
- display,
103
- };
104
- }
105
- if (action === "show") {
106
- if (!this.tracker) {
107
- return { success: false, output: t("plan.no_active") };
108
- }
109
- const display = this.tracker.toPromptBlock();
110
- return {
111
- success: true,
112
- output: `${t("plan.show_header")}\n${display}`,
113
- display,
114
- };
115
- }
116
- if (action === "update" && args.step && this.tracker) {
117
- this.tracker.updateStepStatus(Number(args.step), args.status || "done");
118
- if (args.note)
119
- this.tracker.addNote(Number(args.step), String(args.note));
120
- const progress = this.tracker.getProgressString();
121
- const display = this.tracker.toPromptBlock();
122
- return {
123
- success: true,
124
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}`,
125
- display,
126
- };
127
- }
128
- if (action === "abort") {
129
- this.tracker = null;
130
- return { success: true, output: t("plan.aborted") };
131
- }
132
- if (!this.tracker) {
133
- return { success: false, output: t("plan.no_active") };
134
- }
135
- return {
136
- success: false,
137
- output: t("plan.unknown_action", { action }),
138
- };
139
- },
140
- },
141
- {
142
- name: "todo",
143
- description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
144
- parameters: {
145
- type: "object",
146
- properties: {
147
- action: { type: "string", enum: ["add", "done", "list"] },
148
- items: { type: "array", items: { type: "string" } },
149
- },
150
- required: ["action"],
151
- },
152
- handler: async (_ctx, args) => {
153
- if (!this.tracker) {
154
- return { success: false, output: t("plan.no_active") };
155
- }
156
- const action = String(args.action);
157
- const currentStep = this.tracker.getCurrentStep();
158
- if (!currentStep) {
159
- return { success: false, output: t("plan.step_not_found") };
160
- }
161
- if (action === "add" && Array.isArray(args.items)) {
162
- const items = args.items.map(String);
163
- currentStep.note = currentStep.note
164
- ? `${currentStep.note}; ${items.join(", ")}`
165
- : items.join(", ");
166
- return {
167
- success: true,
168
- output: t("todo.added", {
169
- count: String(items.length),
170
- items: items.join(", "),
171
- }),
172
- };
173
- }
174
- if (action === "done") {
175
- return {
176
- success: true,
177
- output: t("todo.marked_done", { count: "1" }),
178
- };
179
- }
180
- if (action === "list") {
181
- const note = currentStep.note || "(no sub-tasks)";
182
- return {
183
- success: true,
184
- output: `Step ${currentStep.id}: ${currentStep.description}\nSub-tasks: ${note}`,
185
- };
186
- }
187
- return {
188
- success: false,
189
- output: t("todo.unknown_action", { action }),
190
- };
191
- },
192
- },
193
- {
194
- name: "verify",
195
- description: "Run verification for the current step. Checks files mentioned in the step description.",
196
- parameters: {
197
- type: "object",
198
- properties: {
199
- step: { type: "number", description: "Step number to verify" },
200
- },
201
- },
202
- handler: async (_ctx, args) => {
203
- if (!this.tracker)
204
- return { success: false, output: t("plan.no_active") };
205
- const step = args.step
206
- ? this.tracker.getStep(Number(args.step))
207
- : this.tracker.getCurrentStep();
208
- if (!step)
209
- return { success: false, output: t("plan.step_not_found") };
210
- const result = await this.verifier.verifyStep(step.description);
211
- return {
212
- success: result.passed,
213
- output: result.passed
214
- ? t("verify.passed")
215
- : t("verify.failed", {
216
- details: result.failed.map((f) => f.message).join("; "),
217
- }),
218
- };
219
- },
220
- },
221
- ];
222
- }
223
- getPlugin() {
224
- return {
225
- name: "execution",
226
- onBeforeThink: (ctx) => {
227
- const step = this.tracker?.getCurrentStep();
228
- if (this.tracker && step) {
229
- this.stuckDetector.setCurrentStep(step.id, step.description);
230
- this.stuckDetector.recordIteration(step.id);
231
- }
232
- else {
233
- this.stuckDetector.reset();
234
- }
235
- const stuckReason = this.stuckDetector.getStuckReason();
236
- if (stuckReason) {
237
- ctx.logger?.warn(stuckReason);
238
- }
239
- if (this.stuckDetector.isStuck() ||
240
- this.stuckDetector.hasRepetitiveToolCalls()) {
241
- const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
242
- if (currentIter - this.lastRecoveryIteration >=
243
- STUCK_RECOVERY_COOLDOWN) {
244
- const recovery = this.stuckDetector.getRecoveryMessage();
245
- if (recovery && ctx.contextManager) {
246
- ctx.contextManager.addMessage({
247
- role: "user",
248
- content: `<system-summary>${recovery}</system-summary>`,
249
- });
250
- }
251
- this.lastRecoveryIteration = currentIter;
252
- }
253
- }
254
- },
255
- onBeforeTool: (ctx, call) => {
256
- const warning = this.checkPlanAlignment(call);
257
- if (warning && ctx.contextManager) {
258
- ctx.contextManager.addMessage({
259
- role: "user",
260
- content: `<system-summary>${warning}</system-summary>`,
261
- });
262
- }
263
- return true;
264
- },
265
- onToolCall: (ctx) => {
266
- const toolName = ctx?.toolName;
267
- const args = ctx?.args;
268
- if (toolName && args) {
269
- this.stuckDetector.recordToolCall(toolName, args);
270
- }
271
- },
272
- onAfterTool: (ctx, call, result) => {
273
- if (!result.success) {
274
- this.stuckDetector.recordToolError(call.name);
275
- }
276
- else {
277
- this.stuckDetector.recordToolSuccess();
278
- }
279
- if (this.tracker &&
280
- result.success &&
281
- (call.name === "write_file" || call.name === "edit_file")) {
282
- this.advancePlanIfStepComplete();
283
- }
284
- },
285
- };
286
- }
287
- checkPlanAlignment(call) {
288
- if (!this.tracker)
289
- return null;
290
- const step = this.tracker.getCurrentStep();
291
- if (!step)
292
- return null;
293
- const allowedAlways = [
294
- "plan",
295
- "todo",
296
- "verify",
297
- "list_dir",
298
- "read_file",
299
- "glob",
300
- "grep",
301
- "file_info",
302
- "load_skill",
303
- "question",
304
- "approve",
305
- ];
306
- if (allowedAlways.includes(call.name))
307
- return null;
308
- const stepPaths = (step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
309
- if (stepPaths.length === 0)
310
- return null;
311
- const argStr = JSON.stringify(call.arguments).toLowerCase();
312
- const callPaths = (argStr.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
313
- if (callPaths.length === 0)
314
- return null;
315
- const offPath = callPaths.some((p) => !stepPaths.some((s) => p.includes(s) || s.includes(p)));
316
- if (!offPath)
317
- return null;
318
- return t("exec.plan_warning", {
319
- step: String(step.id),
320
- description: step.description,
321
- tool: call.name,
322
- });
323
- }
324
- advancePlanIfStepComplete() {
325
- const step = this.tracker?.getCurrentStep();
326
- if (!step)
327
- return;
328
- const stepPaths = step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || [];
329
- if (stepPaths.length === 0)
330
- return;
331
- const allExist = stepPaths.every((p) => existsSync(resolve(this.baseDir, p)));
332
- if (allExist) {
333
- this.tracker?.updateStepStatus(step.id, "done");
334
- this.tracker?.advance();
335
- }
336
- }
337
- }
@@ -1,209 +0,0 @@
1
- import { getExpertConfig } from '../../config/experts';
2
- import { filterToolsByTags } from '../../tools/filter-tools';
3
- const TRANSIENT_ERROR_PATTERNS = [
4
- 'timeout', 'Timeout', 'TIMEOUT',
5
- '5xx', '500', '502', '503',
6
- 'network', 'Network',
7
- 'ECONNREFUSED', 'ECONNRESET',
8
- 'fetch failed',
9
- 'abort', 'Abort',
10
- ];
11
- function isTransientError(error) {
12
- return TRANSIENT_ERROR_PATTERNS.some(p => error.includes(p));
13
- }
14
- function sleep(ms) {
15
- return new Promise(r => setTimeout(r, ms));
16
- }
17
- export function topologicalSort(subtasks) {
18
- const sorted = [];
19
- const remaining = new Set(subtasks.map(s => s.id));
20
- const subtaskMap = new Map(subtasks.map(s => [s.id, s]));
21
- while (remaining.size > 0) {
22
- const ready = [];
23
- for (const id of remaining) {
24
- const sub = subtaskMap.get(id);
25
- const depsSatisfied = (sub.depends_on || []).every(d => !remaining.has(d));
26
- if (depsSatisfied) {
27
- ready.push(sub);
28
- }
29
- }
30
- if (ready.length === 0) {
31
- break;
32
- }
33
- sorted.push(ready);
34
- for (const r of ready) {
35
- remaining.delete(r.id);
36
- }
37
- }
38
- return sorted;
39
- }
40
- async function executeSubtask(subtask, deps, _sharedContext) {
41
- const startTime = Date.now();
42
- const expertConfig = getExpertConfig(deps.config, subtask.expert_tag);
43
- if (!expertConfig) {
44
- return {
45
- subtaskId: subtask.id,
46
- success: false,
47
- summary: `Unknown expert_tag: ${subtask.expert_tag}`,
48
- result: '',
49
- error: `No expert config found for tag "${subtask.expert_tag}"`,
50
- durationMs: Date.now() - startTime,
51
- };
52
- }
53
- const subagentTool = deps.toolRegistry.get('subagent');
54
- if (!subagentTool) {
55
- return {
56
- subtaskId: subtask.id,
57
- success: false,
58
- summary: 'Subagent tool not registered',
59
- result: '',
60
- error: 'Subagent tool not found in registry',
61
- durationMs: Date.now() - startTime,
62
- };
63
- }
64
- const allTools = deps.toolRegistry.getAll();
65
- const filteredTools = filterToolsByTags(allTools, expertConfig.tool_tags);
66
- if (filteredTools.length === 0) {
67
- return {
68
- subtaskId: subtask.id,
69
- success: false,
70
- summary: `No tools available for expert "${subtask.expert_tag}" (tags: ${expertConfig.tool_tags.join(', ')})`,
71
- result: '',
72
- error: 'No matching tools',
73
- durationMs: Date.now() - startTime,
74
- };
75
- }
76
- const maxAttempts = expertConfig.max_attempts || 3;
77
- let lastError = '';
78
- let lastResult = null;
79
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
80
- try {
81
- const allowedFiles = subtask.allowed_files || [];
82
- const readOnlyFiles = subtask.read_only_files || [];
83
- const scopePrompt = allowedFiles.length > 0 || readOnlyFiles.length > 0
84
- ? `\n\nAllowed files to write: ${allowedFiles.join(', ') || '(none)'}\nRead-only files: ${readOnlyFiles.join(', ') || '(none)'}`
85
- : '';
86
- const taskPrompt = `${subtask.description}\n\nExpected output: ${subtask.expected_output}\nSuccess criteria:\n${(subtask.success_criteria || []).map((c) => `- ${c}`).join('\n')}${scopePrompt}`;
87
- const subagentArgs = {
88
- task: taskPrompt,
89
- allowed_files: allowedFiles,
90
- read_only_files: readOnlyFiles,
91
- tool_tags: expertConfig.tool_tags,
92
- };
93
- const result = await subagentTool.handler({
94
- config: deps.config,
95
- baseDir: deps.baseDir,
96
- logger: deps.logger,
97
- llmProvider: deps.llmProvider,
98
- toolExecutor: deps.toolExecutor,
99
- }, subagentArgs);
100
- if (result.success) {
101
- return {
102
- subtaskId: subtask.id,
103
- success: true,
104
- summary: `Completed: ${subtask.id}`,
105
- result: result.output,
106
- durationMs: Date.now() - startTime,
107
- };
108
- }
109
- lastError = result.output;
110
- lastResult = result;
111
- if (isTransientError(lastError) && attempt < maxAttempts) {
112
- const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
113
- deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: transient error`);
114
- await sleep(delay);
115
- continue;
116
- }
117
- return {
118
- subtaskId: subtask.id,
119
- success: false,
120
- summary: `Failed: ${subtask.id}`,
121
- result: result.output,
122
- error: result.output,
123
- durationMs: Date.now() - startTime,
124
- };
125
- }
126
- catch (e) {
127
- lastError = e.message;
128
- if (isTransientError(lastError) && attempt < maxAttempts) {
129
- const delay = Math.min(1000 * Math.pow(2, attempt - 1), 8000);
130
- deps.logger.debug(`Retrying subtask "${subtask.id}" (attempt ${attempt}/${maxAttempts}) after ${delay}ms: ${lastError}`);
131
- await sleep(delay);
132
- continue;
133
- }
134
- return {
135
- subtaskId: subtask.id,
136
- success: false,
137
- summary: `Error: ${subtask.id}`,
138
- result: '',
139
- error: e.message,
140
- durationMs: Date.now() - startTime,
141
- };
142
- }
143
- }
144
- return {
145
- subtaskId: subtask.id,
146
- success: false,
147
- summary: `Failed after ${maxAttempts} attempts: ${subtask.id}`,
148
- result: '',
149
- error: lastError,
150
- durationMs: Date.now() - startTime,
151
- };
152
- }
153
- export class MoEExecutor {
154
- deps;
155
- constructor(deps) {
156
- this.deps = deps;
157
- }
158
- async executePlan(plan) {
159
- const results = [];
160
- const errors = [];
161
- const warnings = [];
162
- const waves = topologicalSort(plan.subtasks);
163
- if (waves.length === 0) {
164
- return { success: false, results: [], errors: ['Failed to topologically sort subtasks (possible cycle)'], warnings: [] };
165
- }
166
- // Verify all subtasks are included — catch silent drops from unresolved dependencies
167
- const sortedCount = waves.flat().length;
168
- if (sortedCount < plan.subtasks.length) {
169
- const missing = plan.subtasks
170
- .filter(s => !waves.flat().some(w => w.id === s.id))
171
- .map(s => s.id);
172
- return {
173
- success: false,
174
- results: [],
175
- errors: [`Missing subtasks after topological sort: ${missing.join(', ')} (dangling or invalid depends_on)`],
176
- warnings: [],
177
- };
178
- }
179
- for (let waveIdx = 0; waveIdx < waves.length; waveIdx++) {
180
- const wave = waves[waveIdx];
181
- const wavePromises = wave.map(subtask => executeSubtask(subtask, this.deps, plan.shared_context)
182
- .then(result => {
183
- results.push(result);
184
- return result;
185
- })
186
- .catch(e => {
187
- const errResult = {
188
- subtaskId: subtask.id,
189
- success: false,
190
- summary: `Unhandled error: ${subtask.id}`,
191
- result: '',
192
- error: e.message,
193
- durationMs: 0,
194
- };
195
- results.push(errResult);
196
- return errResult;
197
- }));
198
- await Promise.all(wavePromises);
199
- }
200
- const failed = results.filter(r => !r.success);
201
- if (failed.length > 0) {
202
- errors.push(...failed.map(f => `[${f.subtaskId}] ${f.error || f.summary}`));
203
- }
204
- return { success: failed.length === 0, results, errors, warnings };
205
- }
206
- getChainMaxAttempts() {
207
- return 3;
208
- }
209
- }