micro-models-agent 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/main.js +179 -358
  2. package/package.json +1 -1
  3. package/dist/cli/commands.js +0 -237
  4. package/dist/cli/completer.js +0 -168
  5. package/dist/cli/index.js +0 -2
  6. package/dist/cli/main.js +0 -113
  7. package/dist/cli/repl.js +0 -1010
  8. package/dist/cli/security-commands.js +0 -166
  9. package/dist/cli/setup.js +0 -229
  10. package/dist/config/config.js +0 -186
  11. package/dist/config/defaults.js +0 -91
  12. package/dist/config/experts.js +0 -15
  13. package/dist/config/index.js +0 -3
  14. package/dist/config/security.js +0 -193
  15. package/dist/config/types.js +0 -1
  16. package/dist/core/agent-moe.js +0 -98
  17. package/dist/core/agent.js +0 -471
  18. package/dist/core/bootstrap.js +0 -314
  19. package/dist/core/index.js +0 -2
  20. package/dist/core/prompt-builder.js +0 -55
  21. package/dist/core/session-logger.js +0 -122
  22. package/dist/core/types.js +0 -1
  23. package/dist/i18n/en.json +0 -461
  24. package/dist/i18n/index.js +0 -43
  25. package/dist/i18n/ru.json +0 -461
  26. package/dist/index.js +0 -22
  27. package/dist/llm/image-utils.js +0 -144
  28. package/dist/llm/index.js +0 -4
  29. package/dist/llm/model-loader.js +0 -78
  30. package/dist/llm/openai-compat.js +0 -324
  31. package/dist/llm/orchestrator.js +0 -194
  32. package/dist/llm/provider.js +0 -10
  33. package/dist/llm/response.js +0 -39
  34. package/dist/llm/token-counter.js +0 -39
  35. package/dist/llm/types.js +0 -1
  36. package/dist/logger/app-logger.js +0 -76
  37. package/dist/logger/index.js +0 -1
  38. package/dist/migration/backup.js +0 -45
  39. package/dist/migration/detect.js +0 -50
  40. package/dist/migration/index.js +0 -2
  41. package/dist/modules/browser/actions.js +0 -46
  42. package/dist/modules/browser/cookie-store.js +0 -24
  43. package/dist/modules/browser/index.js +0 -5
  44. package/dist/modules/browser/module.js +0 -28
  45. package/dist/modules/browser/session.js +0 -287
  46. package/dist/modules/browser/snapshot.js +0 -114
  47. package/dist/modules/browser/types.js +0 -9
  48. package/dist/modules/context/index.js +0 -1
  49. package/dist/modules/context/manager.js +0 -240
  50. package/dist/modules/execution/auditor.js +0 -72
  51. package/dist/modules/execution/index.js +0 -6
  52. package/dist/modules/execution/module.js +0 -337
  53. package/dist/modules/execution/moe-executor.js +0 -209
  54. package/dist/modules/execution/plan-validator.js +0 -153
  55. package/dist/modules/execution/planner.js +0 -35
  56. package/dist/modules/execution/stuck-detector.js +0 -134
  57. package/dist/modules/execution/tracker.js +0 -53
  58. package/dist/modules/execution/types.js +0 -1
  59. package/dist/modules/execution/verifier.js +0 -149
  60. package/dist/modules/hallucination/confidence.js +0 -59
  61. package/dist/modules/hallucination/consistency.js +0 -66
  62. package/dist/modules/hallucination/detector.js +0 -33
  63. package/dist/modules/hallucination/factual.js +0 -561
  64. package/dist/modules/hallucination/index.js +0 -3
  65. package/dist/modules/index.js +0 -5
  66. package/dist/modules/indexer/cache.js +0 -38
  67. package/dist/modules/indexer/index.js +0 -3
  68. package/dist/modules/indexer/module.js +0 -192
  69. package/dist/modules/indexer/walker.js +0 -101
  70. package/dist/modules/mcp/client.js +0 -393
  71. package/dist/modules/mcp/index.js +0 -3
  72. package/dist/modules/mcp/module.js +0 -146
  73. package/dist/modules/mcp/registry.js +0 -15
  74. package/dist/modules/memory/index.js +0 -1
  75. package/dist/modules/memory/module.js +0 -48
  76. package/dist/modules/memory/search.js +0 -40
  77. package/dist/modules/memory/store.js +0 -65
  78. package/dist/modules/pipelines/engine.js +0 -60
  79. package/dist/modules/pipelines/index.js +0 -3
  80. package/dist/modules/pipelines/parser.js +0 -53
  81. package/dist/modules/pipelines/template.js +0 -14
  82. package/dist/modules/plugins/builtin/lint-on-write.js +0 -121
  83. package/dist/modules/plugins/builtin/notify.js +0 -8
  84. package/dist/modules/plugins/index.js +0 -1
  85. package/dist/modules/plugins/loader.js +0 -28
  86. package/dist/modules/plugins/manager.js +0 -161
  87. package/dist/modules/plugins/types.js +0 -1
  88. package/dist/modules/processes/detect.js +0 -34
  89. package/dist/modules/processes/index.js +0 -3
  90. package/dist/modules/processes/registry.js +0 -148
  91. package/dist/modules/processes/runner.js +0 -124
  92. package/dist/modules/registry.js +0 -45
  93. package/dist/modules/security/audit-log.js +0 -116
  94. package/dist/modules/security/audit-notifier.js +0 -292
  95. package/dist/modules/security/command-validator.js +0 -185
  96. package/dist/modules/security/content-scanner.js +0 -52
  97. package/dist/modules/security/data-sanitizer.js +0 -97
  98. package/dist/modules/security/encryption.js +0 -240
  99. package/dist/modules/security/index.js +0 -14
  100. package/dist/modules/security/network-validator.js +0 -79
  101. package/dist/modules/security/path-validator.js +0 -155
  102. package/dist/modules/security/rate-limiter.js +0 -119
  103. package/dist/modules/security/security-policies.js +0 -393
  104. package/dist/modules/security/session-encryption.js +0 -193
  105. package/dist/modules/security/session-isolation.js +0 -95
  106. package/dist/modules/session/index.js +0 -3
  107. package/dist/modules/session/manager.js +0 -167
  108. package/dist/modules/session/module.js +0 -24
  109. package/dist/modules/session/store.js +0 -174
  110. package/dist/modules/session/types.js +0 -1
  111. package/dist/modules/skills/index.js +0 -3
  112. package/dist/modules/skills/loader.js +0 -72
  113. package/dist/modules/skills/matcher.js +0 -27
  114. package/dist/modules/skills/module.js +0 -143
  115. package/dist/modules/types.js +0 -1
  116. package/dist/modules/updater/checker.js +0 -32
  117. package/dist/modules/updater/index.js +0 -1
  118. package/dist/modules/user-profile/compressor.js +0 -16
  119. package/dist/modules/user-profile/index.js +0 -1
  120. package/dist/modules/user-profile/profile.js +0 -68
  121. package/dist/skills/builtin/builtin/git.md +0 -36
  122. package/dist/skills/builtin/builtin/typescript.md +0 -35
  123. package/dist/tools/approve.js +0 -32
  124. package/dist/tools/attach-image.js +0 -89
  125. package/dist/tools/bash.js +0 -140
  126. package/dist/tools/browser.js +0 -97
  127. package/dist/tools/create-dir.js +0 -55
  128. package/dist/tools/delete-file.js +0 -62
  129. package/dist/tools/edit-file.js +0 -77
  130. package/dist/tools/executor.js +0 -95
  131. package/dist/tools/file-info.js +0 -45
  132. package/dist/tools/filter-tools.js +0 -10
  133. package/dist/tools/glob-tool.js +0 -26
  134. package/dist/tools/grep-tool.js +0 -64
  135. package/dist/tools/index.js +0 -52
  136. package/dist/tools/list-dir.js +0 -47
  137. package/dist/tools/load-skill.js +0 -48
  138. package/dist/tools/mcp-call.js +0 -68
  139. package/dist/tools/move-file.js +0 -84
  140. package/dist/tools/path-utils.js +0 -51
  141. package/dist/tools/pipeline-run.js +0 -144
  142. package/dist/tools/preview.js +0 -2
  143. package/dist/tools/process-kill.js +0 -29
  144. package/dist/tools/process-list.js +0 -38
  145. package/dist/tools/process-log.js +0 -41
  146. package/dist/tools/question.js +0 -142
  147. package/dist/tools/read-file.js +0 -70
  148. package/dist/tools/recall.js +0 -110
  149. package/dist/tools/registry.js +0 -36
  150. package/dist/tools/remember.js +0 -67
  151. package/dist/tools/scope-check.js +0 -30
  152. package/dist/tools/search-history.js +0 -64
  153. package/dist/tools/subagent.js +0 -142
  154. package/dist/tools/types.js +0 -1
  155. package/dist/tools/user-input.js +0 -123
  156. package/dist/tools/web-browse.js +0 -57
  157. package/dist/tools/web-fetch.js +0 -72
  158. package/dist/tools/web-search.js +0 -59
  159. package/dist/tools/write-file.js +0 -79
  160. package/dist/ui/box.js +0 -81
  161. package/dist/ui/colors.js +0 -4
  162. package/dist/ui/diff.js +0 -185
  163. package/dist/ui/index.js +0 -6
  164. package/dist/ui/md-formatter.js +0 -212
  165. package/dist/ui/output.js +0 -13
  166. package/dist/ui/renderer.js +0 -141
  167. package/dist/ui/spinner.js +0 -70
  168. 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
- }