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,153 +0,0 @@
|
|
|
1
|
-
export function validatePlan(plan, config) {
|
|
2
|
-
const errors = [];
|
|
3
|
-
const warnings = [];
|
|
4
|
-
const autoFixes = [];
|
|
5
|
-
if (!plan.subtasks || plan.subtasks.length === 0) {
|
|
6
|
-
return { valid: false, errors: ['Plan has no subtasks'], warnings: [], autoFixes: [] };
|
|
7
|
-
}
|
|
8
|
-
errors.push(...expertTagsExist(plan.subtasks, config));
|
|
9
|
-
errors.push(...hasCycle(plan.subtasks));
|
|
10
|
-
const overlapErrors = hasAllowedFilesOverlap(plan.subtasks);
|
|
11
|
-
errors.push(...overlapErrors);
|
|
12
|
-
const readOverlaps = deleteReadOverlap(plan.subtasks);
|
|
13
|
-
for (const overlap of readOverlaps) {
|
|
14
|
-
if (overlap.canAutoFix) {
|
|
15
|
-
autoFixes.push({ subtaskId: overlap.subtaskId, fix: `Add depends_on: ${overlap.dependsOn}` });
|
|
16
|
-
}
|
|
17
|
-
else if (overlap.error) {
|
|
18
|
-
errors.push(overlap.error);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
valid: errors.length === 0,
|
|
23
|
-
errors,
|
|
24
|
-
warnings,
|
|
25
|
-
autoFixes,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
export function expertTagsExist(subtasks, config) {
|
|
29
|
-
const errors = [];
|
|
30
|
-
const knownExperts = new Set(Object.keys(config.experts || {}));
|
|
31
|
-
for (const subtask of subtasks) {
|
|
32
|
-
if (!knownExperts.has(subtask.expert_tag)) {
|
|
33
|
-
errors.push(`Subtask "${subtask.id}" references unknown expert_tag "${subtask.expert_tag}". Known experts: ${Array.from(knownExperts).join(', ')}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return errors;
|
|
37
|
-
}
|
|
38
|
-
export function hasCycle(subtasks) {
|
|
39
|
-
const edges = new Map();
|
|
40
|
-
for (const s of subtasks) {
|
|
41
|
-
edges.set(s.id, s.depends_on || []);
|
|
42
|
-
}
|
|
43
|
-
const visited = new Set();
|
|
44
|
-
const inStack = new Set();
|
|
45
|
-
function dfs(node) {
|
|
46
|
-
if (inStack.has(node))
|
|
47
|
-
return true;
|
|
48
|
-
if (visited.has(node))
|
|
49
|
-
return false;
|
|
50
|
-
visited.add(node);
|
|
51
|
-
inStack.add(node);
|
|
52
|
-
const deps = edges.get(node) || [];
|
|
53
|
-
for (const dep of deps) {
|
|
54
|
-
if (edges.has(dep) && dfs(dep))
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
inStack.delete(node);
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
const errors = [];
|
|
61
|
-
for (const s of subtasks) {
|
|
62
|
-
visited.clear();
|
|
63
|
-
inStack.clear();
|
|
64
|
-
if (dfs(s.id)) {
|
|
65
|
-
errors.push(`Cycle detected involving subtask "${s.id}"`);
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return errors;
|
|
70
|
-
}
|
|
71
|
-
export function hasAllowedFilesOverlap(subtasks) {
|
|
72
|
-
const errors = [];
|
|
73
|
-
const writeFiles = new Map();
|
|
74
|
-
for (const s of subtasks) {
|
|
75
|
-
for (const f of s.allowed_files || []) {
|
|
76
|
-
if (!writeFiles.has(f)) {
|
|
77
|
-
writeFiles.set(f, []);
|
|
78
|
-
}
|
|
79
|
-
writeFiles.get(f).push(s.id);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
for (const [file, writers] of writeFiles) {
|
|
83
|
-
if (writers.length > 1) {
|
|
84
|
-
const noDep = writers.filter(w => {
|
|
85
|
-
const sub = subtasks.find(s => s.id === w);
|
|
86
|
-
return sub && !sub.depends_on?.some(d => writers.includes(d));
|
|
87
|
-
});
|
|
88
|
-
if (noDep.length > 1) {
|
|
89
|
-
errors.push(`Write-write overlap on "${file}" between subtasks: ${writers.join(', ')} without dependency chain`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return errors;
|
|
94
|
-
}
|
|
95
|
-
export function deleteReadOverlap(subtasks) {
|
|
96
|
-
const results = [];
|
|
97
|
-
for (let i = 0; i < subtasks.length; i++) {
|
|
98
|
-
for (let j = 0; j < subtasks.length; j++) {
|
|
99
|
-
if (i === j)
|
|
100
|
-
continue;
|
|
101
|
-
const writer = subtasks[i];
|
|
102
|
-
const reader = subtasks[j];
|
|
103
|
-
if (!writer.allowed_files || !reader.read_only_files)
|
|
104
|
-
continue;
|
|
105
|
-
const readFiles = new Set(reader.read_only_files);
|
|
106
|
-
const writeFiles = writer.allowed_files;
|
|
107
|
-
const overlapping = writeFiles.filter(f => {
|
|
108
|
-
for (const rf of readFiles) {
|
|
109
|
-
if (f.startsWith(rf) || rf.startsWith(f))
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
return false;
|
|
113
|
-
});
|
|
114
|
-
if (overlapping.length > 0 && !reader.depends_on?.includes(writer.id)) {
|
|
115
|
-
const canAutoFix = !reader.depends_on || reader.depends_on.length === 0;
|
|
116
|
-
if (canAutoFix) {
|
|
117
|
-
results.push({
|
|
118
|
-
subtaskId: reader.id,
|
|
119
|
-
dependsOn: writer.id,
|
|
120
|
-
canAutoFix: true,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
results.push({
|
|
125
|
-
subtaskId: reader.id,
|
|
126
|
-
dependsOn: writer.id,
|
|
127
|
-
canAutoFix: false,
|
|
128
|
-
error: `Read-after-write overlap between "${writer.id}" (writer) and "${reader.id}" (reader) on "${overlapping.join(', ')}" — add depends_on: [${writer.id}]`,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return results;
|
|
135
|
-
}
|
|
136
|
-
export function applyAutoFixes(plan, autoFixes) {
|
|
137
|
-
const fixed = { ...plan, subtasks: plan.subtasks.map(s => ({ ...s })) };
|
|
138
|
-
for (const fix of autoFixes) {
|
|
139
|
-
const sub = fixed.subtasks.find(s => s.id === fix.subtaskId);
|
|
140
|
-
if (!sub)
|
|
141
|
-
continue;
|
|
142
|
-
const dependsOnMatch = fix.fix.match(/depends_on:\s*(\S+)/);
|
|
143
|
-
if (dependsOnMatch) {
|
|
144
|
-
const dep = dependsOnMatch[1];
|
|
145
|
-
if (!sub.depends_on)
|
|
146
|
-
sub.depends_on = [];
|
|
147
|
-
if (!sub.depends_on.includes(dep)) {
|
|
148
|
-
sub.depends_on.push(dep);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return fixed;
|
|
153
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export class PlanCreator {
|
|
2
|
-
static isMultiStep(task) {
|
|
3
|
-
const fileCount = (task.match(/\b[\w./-]+\.[a-z]+\b/gi) || []).length;
|
|
4
|
-
if (fileCount > 1)
|
|
5
|
-
return true;
|
|
6
|
-
const actionWords = ['implement', 'create', 'add', 'build', 'setup', 'configure', 'write', 'make', 'develop'];
|
|
7
|
-
const words = task.split(/\s+/);
|
|
8
|
-
const hasActionWord = actionWords.some(w => task.toLowerCase().includes(w));
|
|
9
|
-
return hasActionWord && words.length > 8;
|
|
10
|
-
}
|
|
11
|
-
static createPlan(title, stepDescriptions) {
|
|
12
|
-
return {
|
|
13
|
-
title,
|
|
14
|
-
steps: stepDescriptions.map((desc, i) => ({
|
|
15
|
-
id: i + 1,
|
|
16
|
-
description: desc,
|
|
17
|
-
status: 'pending',
|
|
18
|
-
})),
|
|
19
|
-
createdAt: new Date().toISOString(),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
static toPromptBlock(plan, currentStepIndex) {
|
|
23
|
-
const lines = [`[Plan: ${plan.title}] (steps: ${plan.steps.filter(s => s.status === 'done').length}/${plan.steps.length} done, current: step ${currentStepIndex + 1})`];
|
|
24
|
-
for (const step of plan.steps) {
|
|
25
|
-
const icon = step.status === 'done' ? '[x]' :
|
|
26
|
-
step.status === 'in_progress' ? '[*]' :
|
|
27
|
-
step.status === 'failed' ? '[!]' :
|
|
28
|
-
step.status === 'skipped' ? '[-]' :
|
|
29
|
-
'[ ]';
|
|
30
|
-
const note = step.note ? ` — ${step.note}` : '';
|
|
31
|
-
lines.push(`${icon} ${step.id}. ${step.description}${note}`);
|
|
32
|
-
}
|
|
33
|
-
return lines.join('\n');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { t } from '../../i18n/index';
|
|
2
|
-
export class StuckDetector {
|
|
3
|
-
threshold;
|
|
4
|
-
errorThreshold;
|
|
5
|
-
iterationsOnCurrentStep = 0;
|
|
6
|
-
currentStepId = null;
|
|
7
|
-
toolErrors = new Map();
|
|
8
|
-
currentStepDescription = '';
|
|
9
|
-
recentToolCalls = [];
|
|
10
|
-
maxRecentCalls = 10;
|
|
11
|
-
repetitionThreshold = 3;
|
|
12
|
-
consecutiveFailures = 0;
|
|
13
|
-
lastFailedTool = '';
|
|
14
|
-
constructor(threshold = 8, errorThreshold = 3) {
|
|
15
|
-
this.threshold = threshold;
|
|
16
|
-
this.errorThreshold = errorThreshold;
|
|
17
|
-
}
|
|
18
|
-
recordIteration(stepId) {
|
|
19
|
-
if (stepId === this.currentStepId) {
|
|
20
|
-
this.iterationsOnCurrentStep++;
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
this.currentStepId = stepId;
|
|
24
|
-
this.iterationsOnCurrentStep = 1;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
recordToolCall(name, args) {
|
|
28
|
-
const argsKey = JSON.stringify(args);
|
|
29
|
-
this.recentToolCalls.push({ name, argsKey });
|
|
30
|
-
if (this.recentToolCalls.length > this.maxRecentCalls) {
|
|
31
|
-
this.recentToolCalls.shift();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
recordToolError(toolName) {
|
|
35
|
-
this.toolErrors.set(toolName, (this.toolErrors.get(toolName) || 0) + 1);
|
|
36
|
-
this.consecutiveFailures++;
|
|
37
|
-
this.lastFailedTool = toolName;
|
|
38
|
-
}
|
|
39
|
-
recordToolSuccess() {
|
|
40
|
-
this.consecutiveFailures = 0;
|
|
41
|
-
this.lastFailedTool = '';
|
|
42
|
-
}
|
|
43
|
-
setCurrentStep(stepId, description) {
|
|
44
|
-
if (stepId !== this.currentStepId) {
|
|
45
|
-
this.currentStepId = stepId;
|
|
46
|
-
this.iterationsOnCurrentStep = 1;
|
|
47
|
-
}
|
|
48
|
-
this.currentStepDescription = description;
|
|
49
|
-
}
|
|
50
|
-
isStuck() {
|
|
51
|
-
return this.iterationsOnCurrentStep > this.threshold;
|
|
52
|
-
}
|
|
53
|
-
hasRepetitiveErrors(toolName) {
|
|
54
|
-
if (toolName) {
|
|
55
|
-
return (this.toolErrors.get(toolName) || 0) >= this.errorThreshold;
|
|
56
|
-
}
|
|
57
|
-
return Array.from(this.toolErrors.values()).some(c => c >= this.errorThreshold);
|
|
58
|
-
}
|
|
59
|
-
hasRepetitiveToolCalls() {
|
|
60
|
-
if (this.recentToolCalls.length < this.repetitionThreshold)
|
|
61
|
-
return false;
|
|
62
|
-
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
63
|
-
let count = 1;
|
|
64
|
-
for (let i = this.recentToolCalls.length - 2; i >= 0; i--) {
|
|
65
|
-
if (this.recentToolCalls[i].name === last.name && this.recentToolCalls[i].argsKey === last.argsKey) {
|
|
66
|
-
count++;
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return count >= this.repetitionThreshold;
|
|
73
|
-
}
|
|
74
|
-
hasConsecutiveFailures() {
|
|
75
|
-
return this.consecutiveFailures >= this.errorThreshold;
|
|
76
|
-
}
|
|
77
|
-
getConsecutiveFailuresCount() {
|
|
78
|
-
return this.consecutiveFailures;
|
|
79
|
-
}
|
|
80
|
-
getRepetitiveToolMessage() {
|
|
81
|
-
if (!this.hasRepetitiveToolCalls())
|
|
82
|
-
return '';
|
|
83
|
-
const last = this.recentToolCalls[this.recentToolCalls.length - 1];
|
|
84
|
-
return t('exec.repetitive_tool', { tool: last.name, count: this.repetitionThreshold });
|
|
85
|
-
}
|
|
86
|
-
getStuckReason() {
|
|
87
|
-
if (this.isStuck()) {
|
|
88
|
-
return t('exec.stuck', { stepId: String(this.currentStepId), description: this.currentStepDescription, iterations: this.iterationsOnCurrentStep });
|
|
89
|
-
}
|
|
90
|
-
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
91
|
-
if (errorTool) {
|
|
92
|
-
return t('exec.tool_errors', { tool: errorTool[0], count: errorTool[1] });
|
|
93
|
-
}
|
|
94
|
-
return '';
|
|
95
|
-
}
|
|
96
|
-
getRecoveryMessage() {
|
|
97
|
-
if (this.isStuck()) {
|
|
98
|
-
return t('exec.stuck_recovery', { iterations: this.iterationsOnCurrentStep });
|
|
99
|
-
}
|
|
100
|
-
// Per-tool error recovery (more specific — e.g., "bash failed 3 times")
|
|
101
|
-
const errorTool = Array.from(this.toolErrors.entries()).find(([_, c]) => c >= this.errorThreshold);
|
|
102
|
-
if (errorTool) {
|
|
103
|
-
return t('exec.tool_errors_recovery', { tool: errorTool[0], count: errorTool[1] });
|
|
104
|
-
}
|
|
105
|
-
// Consecutive failures from different tools (generic — e.g., "5 consecutive failures")
|
|
106
|
-
if (this.hasConsecutiveFailures()) {
|
|
107
|
-
return t('exec.consecutive_failures_recovery', { count: this.consecutiveFailures });
|
|
108
|
-
}
|
|
109
|
-
if (this.hasRepetitiveToolCalls()) {
|
|
110
|
-
return this.getRepetitiveToolMessage();
|
|
111
|
-
}
|
|
112
|
-
return '';
|
|
113
|
-
}
|
|
114
|
-
checkOffTrack(toolName, call) {
|
|
115
|
-
if (!this.currentStepDescription)
|
|
116
|
-
return null;
|
|
117
|
-
const toolArgStr = JSON.stringify(call.arguments).toLowerCase();
|
|
118
|
-
const pathInStep = (this.currentStepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || [])
|
|
119
|
-
.map(p => p.toLowerCase());
|
|
120
|
-
const pathInCall = (toolArgStr.match(/\b[\w./-]+\.[a-z]+/gi) || [])
|
|
121
|
-
.map(p => p.toLowerCase());
|
|
122
|
-
const overlaps = pathInCall.some(p => pathInStep.some(s => p.includes(s) || s.includes(p)));
|
|
123
|
-
if (!overlaps && pathInStep.length > 0 && pathInCall.length > 0) {
|
|
124
|
-
return t('exec.off_track', { stepId: String(this.currentStepId), description: this.currentStepDescription, tool: toolName });
|
|
125
|
-
}
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
reset() {
|
|
129
|
-
this.iterationsOnCurrentStep = 0;
|
|
130
|
-
this.toolErrors.clear();
|
|
131
|
-
this.consecutiveFailures = 0;
|
|
132
|
-
this.lastFailedTool = '';
|
|
133
|
-
}
|
|
134
|
-
}
|
|
@@ -1,53 +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
|
-
isComplete() {
|
|
39
|
-
return this.plan.steps.every(s => s.status === 'done' || s.status === 'skipped');
|
|
40
|
-
}
|
|
41
|
-
getProgressString() {
|
|
42
|
-
const done = this.plan.steps.filter(s => s.status === 'done').length;
|
|
43
|
-
const total = this.plan.steps.length;
|
|
44
|
-
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
45
|
-
const barWidth = 10;
|
|
46
|
-
const filled = Math.round((done / total) * barWidth);
|
|
47
|
-
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
48
|
-
return `[Plan: ${this.plan.title}] ${done}/${total} ${bar} ${pct}%`;
|
|
49
|
-
}
|
|
50
|
-
toPromptBlock() {
|
|
51
|
-
return PlanCreator.toPromptBlock(this.plan, this.currentStepIndex);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
2
|
-
import { resolve, extname } from 'path';
|
|
3
|
-
import { execSync } 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
|
-
execSync(`bun run ${scriptName}`, {
|
|
22
|
-
cwd: this.baseDir,
|
|
23
|
-
encoding: 'utf-8',
|
|
24
|
-
timeout: 60_000,
|
|
25
|
-
stdio: 'pipe',
|
|
26
|
-
});
|
|
27
|
-
return { passed: true, message: t('verify.script_passed', { script: scriptName }) };
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
return { passed: false, message: t('verify.script_failed', { script: scriptName, message: e.message }) };
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async runTypeCheck() {
|
|
34
|
-
const tsconfigPath = resolve(this.baseDir, 'tsconfig.json');
|
|
35
|
-
if (!existsSync(tsconfigPath)) {
|
|
36
|
-
return { passed: true, message: 'No tsconfig.json found — skipping type check' };
|
|
37
|
-
}
|
|
38
|
-
try {
|
|
39
|
-
execSync('npx tsc --noEmit', {
|
|
40
|
-
cwd: this.baseDir,
|
|
41
|
-
encoding: 'utf-8',
|
|
42
|
-
timeout: 60_000,
|
|
43
|
-
stdio: 'pipe',
|
|
44
|
-
});
|
|
45
|
-
return { passed: true, message: 'TypeScript type check passed' };
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
const stderr = e.stderr?.toString() || e.stdout?.toString() || e.message;
|
|
49
|
-
return { passed: false, message: `TypeScript type check failed: ${stderr.slice(0, 500)}` };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
async runTests() {
|
|
53
|
-
const pkgPath = resolve(this.baseDir, 'package.json');
|
|
54
|
-
if (!existsSync(pkgPath)) {
|
|
55
|
-
return { passed: true, message: 'No package.json found — skipping tests' };
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const pkg = JSON.parse(require('fs').readFileSync(pkgPath, 'utf-8'));
|
|
59
|
-
if (!pkg.scripts?.test) {
|
|
60
|
-
return { passed: true, message: 'No test script defined — skipping tests' };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch { /* fall through — attempt to run */ }
|
|
64
|
-
return this.runScript('test');
|
|
65
|
-
}
|
|
66
|
-
async verifyArtifactFiles(files) {
|
|
67
|
-
return Promise.all(files.map(f => this.checkFileExists(f)));
|
|
68
|
-
}
|
|
69
|
-
async verifyMoEManifest(plan, config, allToolTags) {
|
|
70
|
-
const errors = [];
|
|
71
|
-
const warnings = [];
|
|
72
|
-
const details = [];
|
|
73
|
-
const expertErrors = validateExpertConfig(config, allToolTags);
|
|
74
|
-
for (const err of expertErrors) {
|
|
75
|
-
errors.push(err);
|
|
76
|
-
details.push({ passed: false, message: err });
|
|
77
|
-
}
|
|
78
|
-
const tscResult = await this.runTypeCheck();
|
|
79
|
-
details.push(tscResult);
|
|
80
|
-
if (!tscResult.passed) {
|
|
81
|
-
errors.push(tscResult.message);
|
|
82
|
-
}
|
|
83
|
-
for (const sub of plan.subtasks) {
|
|
84
|
-
const fileChecks = await this.verifyArtifactFiles(sub.allowed_files || []);
|
|
85
|
-
for (const check of fileChecks) {
|
|
86
|
-
details.push(check);
|
|
87
|
-
if (!check.passed) {
|
|
88
|
-
errors.push(check.message);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
success: errors.length === 0,
|
|
94
|
-
errors,
|
|
95
|
-
warnings,
|
|
96
|
-
details,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
async verifyStep(stepDescription) {
|
|
100
|
-
const fileMatches = stepDescription.match(/\b[\w./-]+\.[a-z]+/gi) || [];
|
|
101
|
-
const results = [];
|
|
102
|
-
let syntaxValid = true;
|
|
103
|
-
for (const filePath of fileMatches) {
|
|
104
|
-
const result = await this.checkFileExists(filePath);
|
|
105
|
-
results.push(result);
|
|
106
|
-
if (result.passed) {
|
|
107
|
-
const fullPath = resolve(this.baseDir, filePath);
|
|
108
|
-
if (!this.validateSyntax(fullPath)) {
|
|
109
|
-
syntaxValid = false;
|
|
110
|
-
results.push({ passed: false, message: t('verify.syntax_error', { path: filePath }) });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return {
|
|
115
|
-
passed: results.every(r => r.passed),
|
|
116
|
-
syntaxValid,
|
|
117
|
-
failed: results.filter(r => !r.passed),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
validateSyntax(filePath) {
|
|
121
|
-
const ext = extname(filePath);
|
|
122
|
-
if (ext === '.ts' || ext === '.tsx') {
|
|
123
|
-
try {
|
|
124
|
-
execSync(`npx tsc --noEmit --skipLibCheck ${filePath}`, { stdio: 'pipe', timeout: 10000 });
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
catch (err) {
|
|
128
|
-
if (err.status === 127 || err.message.includes('not found') || err.message.includes('ENOENT')) {
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
const stderr = err.stderr?.toString() || '';
|
|
132
|
-
if (stderr.includes('error TS') && !stderr.includes('Cannot find module')) {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (ext === '.js' || ext === '.jsx') {
|
|
139
|
-
try {
|
|
140
|
-
execSync(`node --check ${filePath}`, { stdio: 'pipe', timeout: 5000 });
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
catch {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
const MIN_CHARS = 1;
|
|
3
|
-
const MIN_WORDS = 5;
|
|
4
|
-
export class ConfidenceCheck {
|
|
5
|
-
previousResponse = "";
|
|
6
|
-
setPreviousResponse(response) {
|
|
7
|
-
this.previousResponse = response;
|
|
8
|
-
}
|
|
9
|
-
validate(response) {
|
|
10
|
-
if (!response || response.length < MIN_CHARS) {
|
|
11
|
-
return { status: "retry", reason: t("hall.short_response") };
|
|
12
|
-
}
|
|
13
|
-
// Language-agnostic: very short response with no structured content
|
|
14
|
-
const wordCount = response.split(/\s+/).filter(Boolean).length;
|
|
15
|
-
const hasStructure = /```|^\s*[-*]\s|^\s*\d+\.\s|<[^>]+>/m.test(response);
|
|
16
|
-
if (wordCount < MIN_WORDS && !hasStructure) {
|
|
17
|
-
return {
|
|
18
|
-
status: "warn",
|
|
19
|
-
reason: t("hall.short_response"),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
// Language-agnostic: repetition detection via word overlap
|
|
23
|
-
if (this.previousResponse) {
|
|
24
|
-
const overlap = this.calculateOverlap(response, this.previousResponse);
|
|
25
|
-
if (overlap > 0.5) {
|
|
26
|
-
return {
|
|
27
|
-
status: "retry",
|
|
28
|
-
reason: t("hall.repetitive", { pct: Math.round(overlap * 100) }),
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// Language-agnostic: very low word diversity (same words repeated)
|
|
33
|
-
const words = response.toLowerCase().split(/\s+/).filter(w => w.length > 2);
|
|
34
|
-
if (words.length >= 10) {
|
|
35
|
-
const unique = new Set(words);
|
|
36
|
-
const diversity = unique.size / words.length;
|
|
37
|
-
if (diversity < 0.25) {
|
|
38
|
-
return {
|
|
39
|
-
status: "warn",
|
|
40
|
-
reason: t("hall.repetitive", { pct: Math.round((1 - diversity) * 100) }),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return { status: "pass" };
|
|
45
|
-
}
|
|
46
|
-
calculateOverlap(a, b) {
|
|
47
|
-
const wordsA = new Set(a.toLowerCase().split(/\s+/));
|
|
48
|
-
const wordsB = b.toLowerCase().split(/\s+/);
|
|
49
|
-
if (wordsB.length === 0)
|
|
50
|
-
return 0;
|
|
51
|
-
const matches = wordsB.filter((w) => wordsA.has(w));
|
|
52
|
-
return matches.length / wordsB.length;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { t } from '../../i18n/index';
|
|
2
|
-
export class ConsistencyCheck {
|
|
3
|
-
decisions = [];
|
|
4
|
-
createdFiles = new Set();
|
|
5
|
-
deletedFiles = new Set();
|
|
6
|
-
trackDecision(decision, location) {
|
|
7
|
-
this.decisions.push({ decision, location });
|
|
8
|
-
}
|
|
9
|
-
trackCreatedFile(path) {
|
|
10
|
-
this.createdFiles.add(path);
|
|
11
|
-
}
|
|
12
|
-
trackDeletedFile(path) {
|
|
13
|
-
this.deletedFiles.add(path);
|
|
14
|
-
}
|
|
15
|
-
getCreatedFiles() {
|
|
16
|
-
return Array.from(this.createdFiles);
|
|
17
|
-
}
|
|
18
|
-
validate(response) {
|
|
19
|
-
const lower = response.toLowerCase();
|
|
20
|
-
for (const d of this.decisions) {
|
|
21
|
-
const decisionWords = d.decision.toLowerCase().split(/\s+/).filter(w => w.length > 3);
|
|
22
|
-
const contradicts = decisionWords.some(word => {
|
|
23
|
-
// English patterns
|
|
24
|
-
if (lower.includes(`instead of ${word}`))
|
|
25
|
-
return true;
|
|
26
|
-
if (lower.includes(`not ${word}`))
|
|
27
|
-
return true;
|
|
28
|
-
if (lower.includes(`replacing ${word} with`))
|
|
29
|
-
return true;
|
|
30
|
-
if (lower.includes(`switching to`))
|
|
31
|
-
return true;
|
|
32
|
-
if (lower.includes(`changing from ${word}`))
|
|
33
|
-
return true;
|
|
34
|
-
if (lower.includes(`abandoning ${word}`))
|
|
35
|
-
return true;
|
|
36
|
-
// Russian patterns
|
|
37
|
-
if (lower.includes(`вместо ${word}`))
|
|
38
|
-
return true;
|
|
39
|
-
if (lower.includes(`заменяя ${word}`))
|
|
40
|
-
return true;
|
|
41
|
-
if (lower.includes(`заменяем ${word}`))
|
|
42
|
-
return true;
|
|
43
|
-
if (lower.includes(`переключаемся на`))
|
|
44
|
-
return true;
|
|
45
|
-
if (lower.includes(`от ${word} к`))
|
|
46
|
-
return true;
|
|
47
|
-
if (lower.includes(`отказываемся от ${word}`))
|
|
48
|
-
return true;
|
|
49
|
-
return false;
|
|
50
|
-
});
|
|
51
|
-
if (contradicts) {
|
|
52
|
-
return {
|
|
53
|
-
status: 'warn',
|
|
54
|
-
reason: t('hall.contradiction', { decision: d.decision, location: d.location }),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return { status: 'pass' };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { FactualCheck } from './factual';
|
|
2
|
-
import { ConsistencyCheck } from './consistency';
|
|
3
|
-
import { ConfidenceCheck } from './confidence';
|
|
4
|
-
export class HallucinationDetector {
|
|
5
|
-
factual;
|
|
6
|
-
consistency;
|
|
7
|
-
confidence;
|
|
8
|
-
constructor() {
|
|
9
|
-
this.factual = new FactualCheck();
|
|
10
|
-
this.consistency = new ConsistencyCheck();
|
|
11
|
-
this.confidence = new ConfidenceCheck();
|
|
12
|
-
}
|
|
13
|
-
getFactualCheck() {
|
|
14
|
-
return this.factual;
|
|
15
|
-
}
|
|
16
|
-
getConsistencyCheck() {
|
|
17
|
-
return this.consistency;
|
|
18
|
-
}
|
|
19
|
-
getConfidenceCheck() {
|
|
20
|
-
return this.confidence;
|
|
21
|
-
}
|
|
22
|
-
validate(response) {
|
|
23
|
-
const confidenceResult = this.confidence.validate(response);
|
|
24
|
-
if (confidenceResult.status === 'retry' || confidenceResult.status === 'block') {
|
|
25
|
-
return confidenceResult;
|
|
26
|
-
}
|
|
27
|
-
const factualResult = this.factual.validate(response);
|
|
28
|
-
const consistencyResult = this.consistency.validate(response);
|
|
29
|
-
const warnings = [];
|
|
30
|
-
if (factualResult.status === 'warn')
|
|
31
|
-
warnings.push(factualResult.reason || '');
|
|
32
|
-
if (consistencyResult.status === 'warn')
|
|
33
|
-
warnings.push(consistencyResult.reason || '');
|
|
34
|
-
if (confidenceResult.status === 'warn')
|
|
35
|
-
warnings.push(confidenceResult.reason || '');
|
|
36
|
-
if (warnings.length > 0) {
|
|
37
|
-
return { status: 'warn', reason: warnings.join('; ') };
|
|
38
|
-
}
|
|
39
|
-
return { status: 'pass' };
|
|
40
|
-
}
|
|
41
|
-
}
|