micro-models-agent 0.28.0 → 0.28.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.
- package/dist/main.js +826 -650
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -220
- package/dist/cli/completer.js +0 -168
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -113
- package/dist/cli/repl.js +0 -987
- package/dist/cli/security-commands.js +0 -166
- package/dist/cli/setup.js +0 -229
- package/dist/config/config.js +0 -186
- package/dist/config/defaults.js +0 -91
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -193
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -98
- package/dist/core/agent.js +0 -461
- package/dist/core/bootstrap.js +0 -321
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -55
- package/dist/core/session-logger.js +0 -122
- package/dist/core/types.js +0 -1
- package/dist/i18n/en.json +0 -461
- package/dist/i18n/index.js +0 -43
- package/dist/i18n/ru.json +0 -461
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -144
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -324
- package/dist/llm/orchestrator.js +0 -194
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -76
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/browser/actions.js +0 -46
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/index.js +0 -5
- package/dist/modules/browser/module.js +0 -28
- package/dist/modules/browser/session.js +0 -287
- package/dist/modules/browser/snapshot.js +0 -114
- package/dist/modules/browser/types.js +0 -9
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -240
- package/dist/modules/execution/auditor.js +0 -72
- package/dist/modules/execution/index.js +0 -6
- package/dist/modules/execution/module.js +0 -337
- package/dist/modules/execution/moe-executor.js +0 -209
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -35
- package/dist/modules/execution/stuck-detector.js +0 -134
- package/dist/modules/execution/tracker.js +0 -53
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -149
- package/dist/modules/hallucination/confidence.js +0 -54
- package/dist/modules/hallucination/consistency.js +0 -60
- package/dist/modules/hallucination/detector.js +0 -41
- package/dist/modules/hallucination/factual.js +0 -170
- package/dist/modules/hallucination/index.js +0 -4
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -38
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -192
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/mcp/client.js +0 -393
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -146
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -48
- package/dist/modules/memory/search.js +0 -40
- package/dist/modules/memory/store.js +0 -65
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -53
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -121
- package/dist/modules/plugins/builtin/notify.js +0 -8
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -28
- package/dist/modules/plugins/manager.js +0 -161
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -3
- package/dist/modules/processes/registry.js +0 -148
- package/dist/modules/processes/runner.js +0 -124
- package/dist/modules/registry.js +0 -45
- package/dist/modules/security/audit-log.js +0 -116
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -185
- package/dist/modules/security/content-scanner.js +0 -52
- package/dist/modules/security/data-sanitizer.js +0 -97
- package/dist/modules/security/encryption.js +0 -240
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -79
- package/dist/modules/security/path-validator.js +0 -155
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -393
- package/dist/modules/security/session-encryption.js +0 -193
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -167
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -174
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -3
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -143
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -32
- package/dist/modules/updater/index.js +0 -1
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -32
- package/dist/tools/attach-image.js +0 -89
- package/dist/tools/bash.js +0 -140
- package/dist/tools/browser.js +0 -97
- package/dist/tools/create-dir.js +0 -56
- package/dist/tools/delete-file.js +0 -63
- package/dist/tools/edit-file.js +0 -77
- package/dist/tools/executor.js +0 -95
- package/dist/tools/file-info.js +0 -45
- package/dist/tools/filter-tools.js +0 -10
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -64
- package/dist/tools/index.js +0 -52
- package/dist/tools/list-dir.js +0 -47
- package/dist/tools/load-skill.js +0 -48
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -84
- package/dist/tools/path-utils.js +0 -51
- package/dist/tools/pipeline-run.js +0 -144
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -29
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -41
- package/dist/tools/question.js +0 -142
- package/dist/tools/read-file.js +0 -73
- package/dist/tools/recall.js +0 -110
- package/dist/tools/registry.js +0 -36
- package/dist/tools/remember.js +0 -67
- package/dist/tools/scope-check.js +0 -30
- package/dist/tools/search-history.js +0 -64
- package/dist/tools/subagent.js +0 -142
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -57
- package/dist/tools/web-fetch.js +0 -72
- package/dist/tools/web-search.js +0 -59
- package/dist/tools/write-file.js +0 -80
- package/dist/ui/box.js +0 -81
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -185
- package/dist/ui/index.js +0 -6
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/renderer.js +0 -141
- package/dist/ui/spinner.js +0 -70
- 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,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
|
-
}
|