claude-mycelium 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent-meta/_inhibitors.ndjson +1287 -0
- package/.agent-meta/_quarantine.json +45 -0
- package/.agent-meta/config.json +9 -0
- package/.agent-meta/tasks/_active.json +4 -0
- package/.agent-meta/tasks/task_0657b028-05a0-4b0c-b0b9-a4eae3d66cd9.json +168 -0
- package/.claude/memory.db +0 -0
- package/.claude/settings.local.json +4 -1
- package/README.md +85 -233
- package/SECURITY.md +145 -0
- package/dist/agent/task-worker.d.ts +11 -0
- package/dist/agent/task-worker.d.ts.map +1 -0
- package/dist/agent/task-worker.js +173 -0
- package/dist/agent/task-worker.js.map +1 -0
- package/dist/agent/worker.d.ts +8 -0
- package/dist/agent/worker.d.ts.map +1 -0
- package/dist/agent/worker.js +97 -0
- package/dist/agent/worker.js.map +1 -0
- package/dist/bin.d.ts +7 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/cost.d.ts +10 -0
- package/dist/cli/cost.d.ts.map +1 -0
- package/dist/cli/cost.js +163 -0
- package/dist/cli/cost.js.map +1 -0
- package/dist/cli/gc.d.ts +10 -0
- package/dist/cli/gc.d.ts.map +1 -0
- package/dist/cli/gc.js +108 -0
- package/dist/cli/gc.js.map +1 -0
- package/dist/cli/gradients.d.ts +10 -0
- package/dist/cli/gradients.d.ts.map +1 -0
- package/dist/cli/gradients.js +70 -0
- package/dist/cli/gradients.js.map +1 -0
- package/dist/cli/grow.d.ts +17 -0
- package/dist/cli/grow.d.ts.map +1 -0
- package/dist/cli/grow.js +373 -0
- package/dist/cli/grow.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +74 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +11 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +97 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/status.d.ts +10 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +191 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/coordination/file-locks.d.ts +42 -0
- package/dist/coordination/file-locks.d.ts.map +1 -0
- package/dist/coordination/file-locks.js +269 -0
- package/dist/coordination/file-locks.js.map +1 -0
- package/dist/coordination/index.d.ts +4 -0
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +4 -0
- package/dist/coordination/index.js.map +1 -1
- package/dist/coordination/inhibitors.d.ts +84 -0
- package/dist/coordination/inhibitors.d.ts.map +1 -0
- package/dist/coordination/inhibitors.js +290 -0
- package/dist/coordination/inhibitors.js.map +1 -0
- package/dist/coordination/process-manager.d.ts +73 -0
- package/dist/coordination/process-manager.d.ts.map +1 -0
- package/dist/coordination/process-manager.js +144 -0
- package/dist/coordination/process-manager.js.map +1 -0
- package/dist/core/agent-executor.d.ts +4 -1
- package/dist/core/agent-executor.d.ts.map +1 -1
- package/dist/core/agent-executor.js +38 -12
- package/dist/core/agent-executor.js.map +1 -1
- package/dist/core/change-applier.d.ts +29 -5
- package/dist/core/change-applier.d.ts.map +1 -1
- package/dist/core/change-applier.js +254 -24
- package/dist/core/change-applier.js.map +1 -1
- package/dist/core/signals/churn.d.ts.map +1 -1
- package/dist/core/signals/churn.js +6 -4
- package/dist/core/signals/churn.js.map +1 -1
- package/dist/core/signals/debt.d.ts.map +1 -1
- package/dist/core/signals/debt.js +4 -3
- package/dist/core/signals/debt.js.map +1 -1
- package/dist/cost/cost-tracker.d.ts.map +1 -1
- package/dist/cost/cost-tracker.js +2 -0
- package/dist/cost/cost-tracker.js.map +1 -1
- package/dist/gc/index.d.ts +17 -0
- package/dist/gc/index.d.ts.map +1 -0
- package/dist/gc/index.js +17 -0
- package/dist/gc/index.js.map +1 -0
- package/dist/gc/runner.d.ts +39 -0
- package/dist/gc/runner.d.ts.map +1 -0
- package/dist/gc/runner.js +277 -0
- package/dist/gc/runner.js.map +1 -0
- package/dist/gc/trace-compactor.d.ts +31 -0
- package/dist/gc/trace-compactor.d.ts.map +1 -0
- package/dist/gc/trace-compactor.js +162 -0
- package/dist/gc/trace-compactor.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/prompts/index.d.ts +2 -1
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js.map +1 -1
- package/dist/quarantine/explorer.d.ts +65 -0
- package/dist/quarantine/explorer.d.ts.map +1 -0
- package/dist/quarantine/explorer.js +175 -0
- package/dist/quarantine/explorer.js.map +1 -0
- package/dist/quarantine/index.d.ts +7 -0
- package/dist/quarantine/index.d.ts.map +1 -0
- package/dist/quarantine/index.js +7 -0
- package/dist/quarantine/index.js.map +1 -0
- package/dist/quarantine/manager.d.ts +75 -0
- package/dist/quarantine/manager.d.ts.map +1 -0
- package/dist/quarantine/manager.js +275 -0
- package/dist/quarantine/manager.js.map +1 -0
- package/dist/task/acceptance.d.ts +29 -0
- package/dist/task/acceptance.d.ts.map +1 -0
- package/dist/task/acceptance.js +228 -0
- package/dist/task/acceptance.js.map +1 -0
- package/dist/task/agent-coordinator.d.ts +40 -0
- package/dist/task/agent-coordinator.d.ts.map +1 -0
- package/dist/task/agent-coordinator.js +168 -0
- package/dist/task/agent-coordinator.js.map +1 -0
- package/dist/task/executor.d.ts +37 -0
- package/dist/task/executor.d.ts.map +1 -0
- package/dist/task/executor.js +462 -0
- package/dist/task/executor.js.map +1 -0
- package/dist/task/index.d.ts +12 -0
- package/dist/task/index.d.ts.map +1 -0
- package/dist/task/index.js +12 -0
- package/dist/task/index.js.map +1 -0
- package/dist/task/planner.d.ts +21 -0
- package/dist/task/planner.d.ts.map +1 -0
- package/dist/task/planner.js +253 -0
- package/dist/task/planner.js.map +1 -0
- package/dist/task/storage.d.ts +46 -0
- package/dist/task/storage.d.ts.map +1 -0
- package/dist/task/storage.js +266 -0
- package/dist/task/storage.js.map +1 -0
- package/dist/trace/trace-event.d.ts +2 -18
- package/dist/trace/trace-event.d.ts.map +1 -1
- package/dist/trace/trace-event.js +6 -6
- package/dist/trace/trace-event.js.map +1 -1
- package/dist/utils/file-utils.d.ts.map +1 -1
- package/dist/utils/file-utils.js +54 -15
- package/dist/utils/file-utils.js.map +1 -1
- package/docs/PHASE5_IMPLEMENTATION.md +237 -0
- package/docs/PHASES-3-7-COMPLETE.md +177 -0
- package/docs/PHASE_4_COMPLETE.md +135 -0
- package/docs/PHASE_7_DELIVERABLES.md +295 -0
- package/docs/PHASE_7_IMPLEMENTATION.md +306 -0
- package/docs/PHASE_7_SUMMARY.txt +195 -0
- package/docs/RELEASE-NOTES-v2.1.md +213 -0
- package/docs/ROADMAP.md +194 -107
- package/docs/SECURITY-AUDIT.md +387 -0
- package/docs/SNAPSHOT.md +59 -32
- package/docs/implementation/phase3-summary.md +220 -0
- package/package.json +27 -11
- package/src/agent/task-worker.ts +196 -0
- package/src/agent/worker.ts +111 -0
- package/src/bin.ts +13 -0
- package/src/cli/cost.ts +210 -0
- package/src/cli/gc.ts +138 -0
- package/src/cli/gradients.ts +97 -0
- package/src/cli/grow.ts +416 -0
- package/src/cli/index.ts +81 -0
- package/src/cli/init.ts +139 -0
- package/src/cli/status.ts +218 -0
- package/src/coordination/file-locks.ts +300 -0
- package/src/coordination/index.ts +4 -0
- package/src/coordination/inhibitors.ts +345 -0
- package/src/coordination/process-manager.ts +199 -0
- package/src/core/agent-executor.ts +37 -8
- package/src/core/signals/churn.ts +8 -5
- package/src/core/signals/debt.ts +4 -3
- package/src/cost/cost-tracker.ts +2 -0
- package/src/gc/index.ts +17 -0
- package/src/gc/runner.ts +314 -0
- package/src/gc/trace-compactor.ts +187 -0
- package/src/index.ts +7 -1
- package/src/prompts/index.ts +2 -1
- package/src/quarantine/explorer.ts +234 -0
- package/src/quarantine/index.ts +7 -0
- package/src/quarantine/manager.ts +336 -0
- package/src/task/acceptance.ts +267 -0
- package/src/task/agent-coordinator.ts +220 -0
- package/src/task/executor.ts +543 -0
- package/src/task/index.ts +38 -0
- package/src/task/planner.ts +294 -0
- package/src/task/storage.ts +332 -0
- package/src/trace/trace-event.ts +7 -26
- package/src/utils/file-utils.ts +61 -15
- package/tests/cli/gc.test.ts +206 -0
- package/tests/cli/init.test.ts +181 -0
- package/tests/cli/status.test.ts +282 -0
- package/tests/coordination/file-locks.test.ts +196 -0
- package/tests/coordination/inhibitors.test.ts +459 -0
- package/tests/coordination/integration.test.ts +195 -0
- package/tests/coordination/process-manager.test.ts +165 -0
- package/tests/gc/trace-compactor.test.ts +245 -0
- package/tests/integration/phase-7.test.ts +145 -0
- package/tests/quarantine/explorer.test.ts +381 -0
- package/tests/quarantine/manager.test.ts +399 -0
- package/tests/security/command-injection.test.ts +88 -0
- package/tests/security/path-traversal.test.ts +103 -0
- package/tests/task/acceptance.test.ts +411 -0
- package/tests/task/executor.test.ts +421 -0
- package/tests/task/planner.test.ts +359 -0
- package/tests/trace/trace-event.test.ts +62 -20
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acceptance Criteria Validation
|
|
3
|
+
* Per ROADMAP §Phase 5 Week 6 Day 5-6: Acceptance Criteria
|
|
4
|
+
*
|
|
5
|
+
* Validates task completion against acceptance criteria including
|
|
6
|
+
* file existence, test execution, lint checks, and custom validation.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Task, AcceptanceCriterion } from '../types/index.js';
|
|
10
|
+
import { fileExists } from '../utils/file-utils.js';
|
|
11
|
+
import { logDebug, logError } from '../utils/logger.js';
|
|
12
|
+
import { exec } from 'child_process';
|
|
13
|
+
import { promisify } from 'util';
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate all acceptance criteria for a task
|
|
19
|
+
*/
|
|
20
|
+
export async function validateAcceptance(task: Task): Promise<boolean> {
|
|
21
|
+
logDebug('Validating acceptance criteria', {
|
|
22
|
+
taskId: task.id,
|
|
23
|
+
criteriaCount: task.acceptance_criteria.length,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
let allMet = true;
|
|
27
|
+
|
|
28
|
+
for (const criterion of task.acceptance_criteria) {
|
|
29
|
+
const met = await validateCriterion(criterion);
|
|
30
|
+
criterion.met = met;
|
|
31
|
+
|
|
32
|
+
if (!met) {
|
|
33
|
+
allMet = false;
|
|
34
|
+
logDebug('Acceptance criterion not met', {
|
|
35
|
+
taskId: task.id,
|
|
36
|
+
type: criterion.type,
|
|
37
|
+
description: criterion.description,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
logDebug('Acceptance validation complete', {
|
|
43
|
+
taskId: task.id,
|
|
44
|
+
allMet,
|
|
45
|
+
metCount: task.acceptance_criteria.filter(c => c.met).length,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return allMet;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validate a single acceptance criterion
|
|
53
|
+
*/
|
|
54
|
+
async function validateCriterion(criterion: AcceptanceCriterion): Promise<boolean> {
|
|
55
|
+
try {
|
|
56
|
+
switch (criterion.type) {
|
|
57
|
+
case 'file_exists':
|
|
58
|
+
return await checkFileExists(criterion.check);
|
|
59
|
+
|
|
60
|
+
case 'test_passes':
|
|
61
|
+
return await runTests(criterion.check);
|
|
62
|
+
|
|
63
|
+
case 'no_lint_errors':
|
|
64
|
+
return await checkLintErrors(criterion.check);
|
|
65
|
+
|
|
66
|
+
case 'custom':
|
|
67
|
+
return await runCustomValidation(criterion.check);
|
|
68
|
+
|
|
69
|
+
default:
|
|
70
|
+
logError('Unknown criterion type', new Error('Invalid type'), {
|
|
71
|
+
type: criterion.type,
|
|
72
|
+
});
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
77
|
+
logError('Criterion validation failed', errorObj, {
|
|
78
|
+
type: criterion.type,
|
|
79
|
+
check: criterion.check,
|
|
80
|
+
});
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a file exists
|
|
87
|
+
*/
|
|
88
|
+
export async function checkFileExists(filePath: string): Promise<boolean> {
|
|
89
|
+
const exists = fileExists(filePath);
|
|
90
|
+
logDebug('File existence check', { filePath, exists });
|
|
91
|
+
return exists;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Run specific tests and check if they pass
|
|
96
|
+
*/
|
|
97
|
+
export async function runTests(testPattern: string): Promise<boolean> {
|
|
98
|
+
logDebug('Running tests', { pattern: testPattern });
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Run vitest with specific pattern
|
|
102
|
+
const command = `npm test -- --run ${testPattern}`;
|
|
103
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
104
|
+
timeout: 60000, // 60 second timeout
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Check for test failures in output
|
|
108
|
+
const output = stdout + stderr;
|
|
109
|
+
const hasFailed = output.includes('FAILED') ||
|
|
110
|
+
output.includes('failed') ||
|
|
111
|
+
output.includes('Error:');
|
|
112
|
+
|
|
113
|
+
logDebug('Test execution result', {
|
|
114
|
+
pattern: testPattern,
|
|
115
|
+
passed: !hasFailed,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
return !hasFailed;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// exec throws on non-zero exit code
|
|
121
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
122
|
+
logError('Test execution failed', errorObj, { pattern: testPattern });
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if file has lint errors
|
|
129
|
+
*/
|
|
130
|
+
async function checkLintErrors(filePath: string): Promise<boolean> {
|
|
131
|
+
logDebug('Checking lint errors', { filePath });
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
// Run eslint on specific file
|
|
135
|
+
const command = `npx eslint ${filePath} --format json`;
|
|
136
|
+
const { stdout } = await execAsync(command, {
|
|
137
|
+
timeout: 30000, // 30 second timeout
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Parse eslint JSON output
|
|
141
|
+
const results = JSON.parse(stdout);
|
|
142
|
+
|
|
143
|
+
// Check if there are any errors
|
|
144
|
+
const totalErrors = results.reduce(
|
|
145
|
+
(sum: number, result: any) => sum + result.errorCount,
|
|
146
|
+
0
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const noErrors = totalErrors === 0;
|
|
150
|
+
logDebug('Lint check result', {
|
|
151
|
+
filePath,
|
|
152
|
+
errors: totalErrors,
|
|
153
|
+
passed: noErrors,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
return noErrors;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
// eslint returns non-zero exit code when errors found
|
|
159
|
+
// Try to parse the output to get error count
|
|
160
|
+
if (error instanceof Error && 'stdout' in error) {
|
|
161
|
+
try {
|
|
162
|
+
const results = JSON.parse((error as any).stdout);
|
|
163
|
+
const totalErrors = results.reduce(
|
|
164
|
+
(sum: number, result: any) => sum + result.errorCount,
|
|
165
|
+
0
|
|
166
|
+
);
|
|
167
|
+
return totalErrors === 0;
|
|
168
|
+
} catch {
|
|
169
|
+
// Failed to parse, assume errors
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
175
|
+
logError('Lint check failed', errorObj, { filePath });
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Run custom validation command
|
|
182
|
+
*/
|
|
183
|
+
async function runCustomValidation(command: string): Promise<boolean> {
|
|
184
|
+
logDebug('Running custom validation', { command });
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
await execAsync(command, {
|
|
188
|
+
timeout: 60000, // 60 second timeout
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
logDebug('Custom validation passed', { command });
|
|
192
|
+
return true;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
195
|
+
logError('Custom validation failed', errorObj, { command });
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Generate default acceptance criteria for a task
|
|
202
|
+
*/
|
|
203
|
+
export function generateDefaultCriteria(task: Task): AcceptanceCriterion[] {
|
|
204
|
+
const criteria: AcceptanceCriterion[] = [];
|
|
205
|
+
|
|
206
|
+
// If task plan exists, generate criteria based on files
|
|
207
|
+
if (task.plan) {
|
|
208
|
+
// Check that all created files exist
|
|
209
|
+
const createSteps = task.plan.steps.filter(s => s.mode === 'create');
|
|
210
|
+
for (const step of createSteps) {
|
|
211
|
+
criteria.push({
|
|
212
|
+
description: `File ${step.target_file} exists`,
|
|
213
|
+
type: 'file_exists',
|
|
214
|
+
check: step.target_file,
|
|
215
|
+
met: false,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// If it's a test file, add a test_passes criterion
|
|
219
|
+
if (step.target_file.includes('.test.')) {
|
|
220
|
+
criteria.push({
|
|
221
|
+
description: `Tests in ${step.target_file} pass`,
|
|
222
|
+
type: 'test_passes',
|
|
223
|
+
check: step.target_file,
|
|
224
|
+
met: false,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Add lint check for all touched files
|
|
230
|
+
const allFiles = new Set([
|
|
231
|
+
...createSteps.map(s => s.target_file),
|
|
232
|
+
...task.plan.steps.filter(s => s.mode !== 'create').map(s => s.target_file),
|
|
233
|
+
]);
|
|
234
|
+
|
|
235
|
+
for (const file of allFiles) {
|
|
236
|
+
// Only check non-test files for lint
|
|
237
|
+
if (!file.includes('.test.')) {
|
|
238
|
+
criteria.push({
|
|
239
|
+
description: `No lint errors in ${file}`,
|
|
240
|
+
type: 'no_lint_errors',
|
|
241
|
+
check: file,
|
|
242
|
+
met: false,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return criteria;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Check if task can be marked as completed
|
|
253
|
+
*/
|
|
254
|
+
export function canCompleteTask(task: Task): boolean {
|
|
255
|
+
// All steps must be completed
|
|
256
|
+
if (task.steps_completed < task.steps_total) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// All acceptance criteria must be met
|
|
261
|
+
if (task.acceptance_criteria.length > 0) {
|
|
262
|
+
return task.acceptance_criteria.every(c => c.met);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// If no criteria defined, task is complete when all steps done
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Agent Coordinator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates multi-agent execution of task steps using the mycelium
|
|
5
|
+
* process spawning system (Phase 3) for true RALPH-style parallelization.
|
|
6
|
+
*
|
|
7
|
+
* This replaces the simple Promise.all() approach with real agent processes
|
|
8
|
+
* that use file locks, inhibitors, and the full executeAgent() cycle.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { fork, ChildProcess } from 'child_process';
|
|
12
|
+
import { Task, TaskStep, Mode } from '../types/index.js';
|
|
13
|
+
import { logDebug, logError, logInfo } from '../utils/logger.js';
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { dirname } from 'path';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = dirname(__filename);
|
|
21
|
+
|
|
22
|
+
export interface StepAssignment {
|
|
23
|
+
stepOrder: number;
|
|
24
|
+
targetFile: string;
|
|
25
|
+
mode: Mode;
|
|
26
|
+
description: string;
|
|
27
|
+
taskId: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AgentResult {
|
|
31
|
+
stepOrder: number;
|
|
32
|
+
success: boolean;
|
|
33
|
+
traceId?: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
exitCode: number | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Spawn an agent for a specific task step
|
|
40
|
+
* Each agent runs as an independent process via child_process.fork()
|
|
41
|
+
*/
|
|
42
|
+
export function spawnAgentForStep(
|
|
43
|
+
task: Task,
|
|
44
|
+
step: TaskStep
|
|
45
|
+
): Promise<AgentResult> {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const agentId = `agent-${randomUUID().substring(0, 8)}`;
|
|
48
|
+
|
|
49
|
+
logDebug('Spawning agent for task step', {
|
|
50
|
+
agentId,
|
|
51
|
+
taskId: task.id,
|
|
52
|
+
stepOrder: step.order,
|
|
53
|
+
targetFile: step.target_file,
|
|
54
|
+
mode: step.mode,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Path to the compiled task worker file
|
|
58
|
+
const workerPath = path.join(__dirname, '../agent/task-worker.js');
|
|
59
|
+
|
|
60
|
+
// Spawn agent process with task context
|
|
61
|
+
const child = fork(workerPath, [], {
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
AGENT_ID: agentId,
|
|
65
|
+
TASK_ID: task.id,
|
|
66
|
+
STEP_ORDER: String(step.order),
|
|
67
|
+
TARGET_FILE: step.target_file,
|
|
68
|
+
MODE: step.mode,
|
|
69
|
+
STEP_DESCRIPTION: step.description,
|
|
70
|
+
},
|
|
71
|
+
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
let result: AgentResult = {
|
|
75
|
+
stepOrder: step.order,
|
|
76
|
+
success: false,
|
|
77
|
+
exitCode: null,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Set timeout (5 minutes)
|
|
81
|
+
const timeout = setTimeout(() => {
|
|
82
|
+
logError('Agent timeout', new Error('Agent timed out'), {
|
|
83
|
+
agentId,
|
|
84
|
+
stepOrder: step.order,
|
|
85
|
+
});
|
|
86
|
+
child.kill('SIGTERM');
|
|
87
|
+
result.error = 'Agent timed out after 5 minutes';
|
|
88
|
+
result.exitCode = -1;
|
|
89
|
+
resolve(result);
|
|
90
|
+
}, 300_000);
|
|
91
|
+
|
|
92
|
+
// Handle IPC messages from agent
|
|
93
|
+
child.on('message', (message: any) => {
|
|
94
|
+
if (message.type === 'result') {
|
|
95
|
+
logDebug('Agent result received', {
|
|
96
|
+
agentId,
|
|
97
|
+
stepOrder: step.order,
|
|
98
|
+
success: message.success,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
result = {
|
|
102
|
+
stepOrder: step.order,
|
|
103
|
+
success: message.success,
|
|
104
|
+
traceId: message.traceId,
|
|
105
|
+
error: message.error,
|
|
106
|
+
exitCode: 0,
|
|
107
|
+
};
|
|
108
|
+
} else if (message.type === 'progress') {
|
|
109
|
+
logInfo('Agent progress', {
|
|
110
|
+
agentId,
|
|
111
|
+
stepOrder: step.order,
|
|
112
|
+
message: message.message,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Handle agent exit
|
|
118
|
+
child.on('exit', (code, signal) => {
|
|
119
|
+
clearTimeout(timeout);
|
|
120
|
+
|
|
121
|
+
result.exitCode = code;
|
|
122
|
+
|
|
123
|
+
logInfo('Agent exited', {
|
|
124
|
+
agentId,
|
|
125
|
+
stepOrder: step.order,
|
|
126
|
+
code,
|
|
127
|
+
signal,
|
|
128
|
+
success: result.success,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// If we didn't receive a result message, mark as failure
|
|
132
|
+
if (!result.success && !result.error) {
|
|
133
|
+
result.error = `Agent exited with code ${code}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
resolve(result);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Handle errors
|
|
140
|
+
child.on('error', (error) => {
|
|
141
|
+
clearTimeout(timeout);
|
|
142
|
+
logError('Agent error', error, {
|
|
143
|
+
agentId,
|
|
144
|
+
stepOrder: step.order,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
result.error = error.message;
|
|
148
|
+
result.exitCode = -1;
|
|
149
|
+
resolve(result);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Forward stderr for debugging
|
|
153
|
+
if (child.stderr) {
|
|
154
|
+
child.stderr.on('data', (data) => {
|
|
155
|
+
logDebug('Agent stderr', {
|
|
156
|
+
agentId,
|
|
157
|
+
stepOrder: step.order,
|
|
158
|
+
output: data.toString(),
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Execute a wave of task steps in parallel using agent processes
|
|
167
|
+
* This is the true mycelium multi-agent coordination
|
|
168
|
+
*/
|
|
169
|
+
export async function executeWaveWithAgents(
|
|
170
|
+
task: Task,
|
|
171
|
+
wave: TaskStep[]
|
|
172
|
+
): Promise<AgentResult[]> {
|
|
173
|
+
logInfo('Executing wave with agent processes', {
|
|
174
|
+
taskId: task.id,
|
|
175
|
+
waveSize: wave.length,
|
|
176
|
+
steps: wave.map(s => s.order),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Spawn all agents in parallel
|
|
180
|
+
const agentPromises = wave.map(step => spawnAgentForStep(task, step));
|
|
181
|
+
|
|
182
|
+
// Wait for all agents to complete
|
|
183
|
+
const results = await Promise.all(agentPromises);
|
|
184
|
+
|
|
185
|
+
logInfo('Wave execution complete', {
|
|
186
|
+
taskId: task.id,
|
|
187
|
+
total: results.length,
|
|
188
|
+
successful: results.filter(r => r.success).length,
|
|
189
|
+
failed: results.filter(r => !r.success).length,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get parallelizable steps grouped by dependency waves
|
|
197
|
+
* Same logic as before, but now we spawn agents for each wave
|
|
198
|
+
*/
|
|
199
|
+
export function getParallelizableSteps(steps: TaskStep[]): TaskStep[][] {
|
|
200
|
+
const waves: TaskStep[][] = [];
|
|
201
|
+
const completed = new Set<number>();
|
|
202
|
+
|
|
203
|
+
while (completed.size < steps.length) {
|
|
204
|
+
// Find steps whose dependencies are all completed
|
|
205
|
+
const ready = steps.filter(
|
|
206
|
+
step =>
|
|
207
|
+
!completed.has(step.order) &&
|
|
208
|
+
step.depends_on.every(dep => completed.has(dep))
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (ready.length === 0) {
|
|
212
|
+
throw new Error('Circular dependency detected in task steps');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
waves.push(ready);
|
|
216
|
+
ready.forEach(step => completed.add(step.order));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return waves;
|
|
220
|
+
}
|