@oxgeneral/orch 0.3.4 → 1.0.1
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/{App-RKAPZNZO.js → App-GJVTVGRU.js} +55 -20
- package/dist/{agent-KBTLGGCT.js → agent-7ZJ3ZDJ7.js} +1 -1
- package/dist/{chunk-D6RFF3KN.js → chunk-4IFIOMCW.js} +4 -3
- package/dist/{chunk-S3QYSBW4.js → chunk-C6XZ3FJT.js} +6 -3
- package/dist/chunk-C6XZ3FJT.js.map +1 -0
- package/dist/{chunk-W6RSVMXR.js → chunk-MGGSRXWJ.js} +5 -2
- package/dist/{chunk-VMDQVRBR.js → chunk-O2OQCSBL.js} +40 -16
- package/dist/chunk-O2OQCSBL.js.map +1 -0
- package/dist/{chunk-B4JQM4NU.js → chunk-VG4465AG.js} +119 -45
- package/dist/chunk-VG4465AG.js.map +1 -0
- package/dist/{chunk-A36WAF2S.js → chunk-VXS2CJFH.js} +117 -43
- package/dist/{chunk-52BFUGDD.js → chunk-XJTJ2TJV.js} +3 -2
- package/dist/{claude-INM52PTH.js → claude-WUJU5KIE.js} +6 -5
- package/dist/claude-WUJU5KIE.js.map +1 -0
- package/dist/claude-ZUEKJJ4X.js +5 -0
- package/dist/cli.js +10 -10
- package/dist/{codex-DIXT44JR.js → codex-7IXXXG5U.js} +3 -3
- package/dist/{codex-QGH2GRV6.js → codex-NYJWEPRQ.js} +4 -4
- package/dist/codex-NYJWEPRQ.js.map +1 -0
- package/dist/{container-LJU4QNDH.js → container-RY54L3XC.js} +12 -10
- package/dist/{cursor-KQJTQ73D.js → cursor-3YHVD4NP.js} +4 -4
- package/dist/cursor-3YHVD4NP.js.map +1 -0
- package/dist/{cursor-C3TR2IJC.js → cursor-622RBRHH.js} +3 -3
- package/dist/{doctor-V2FPS236.js → doctor-XSGQSD57.js} +5 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.js +15 -13
- package/dist/index.js.map +1 -1
- package/dist/{init-U7MCIOB2.js → init-45BEMVL6.js} +38 -4
- package/dist/opencode-FAMPSA6X.js +100 -0
- package/dist/opencode-FAMPSA6X.js.map +1 -0
- package/dist/opencode-WOR53TSC.js +98 -0
- package/dist/{orchestrator-E3FQ4SOE.js → orchestrator-O6MFMATT.js} +38 -14
- package/dist/orchestrator-X2CWGFCL.js +5 -0
- package/dist/{orchestrator-ADO66XZ3.js.map → orchestrator-X2CWGFCL.js.map} +1 -1
- package/dist/shell-DVFHHYAZ.js +5 -0
- package/dist/{shell-JXOPKDXH.js → shell-NJNW3O6K.js} +5 -3
- package/dist/shell-NJNW3O6K.js.map +1 -0
- package/dist/{task-2TJW6Z7O.js → task-5EL2RNGW.js} +1 -1
- package/dist/template-engine-5ZKVJMYA.js +3 -0
- package/dist/{template-engine-MFL5B677.js.map → template-engine-5ZKVJMYA.js.map} +1 -1
- package/dist/template-engine-AWIS56BL.js +3 -0
- package/dist/{tui-IM3YUUVD.js → tui-LN5XHSQY.js} +1 -1
- package/package.json +14 -10
- package/readme.md +208 -152
- package/scripts/load-test.ts +478 -0
- package/dist/chunk-B4JQM4NU.js.map +0 -1
- package/dist/chunk-S3QYSBW4.js.map +0 -1
- package/dist/chunk-VMDQVRBR.js.map +0 -1
- package/dist/claude-INM52PTH.js.map +0 -1
- package/dist/claude-NHUNA5RZ.js +0 -5
- package/dist/codex-QGH2GRV6.js.map +0 -1
- package/dist/cursor-KQJTQ73D.js.map +0 -1
- package/dist/orchestrator-ADO66XZ3.js +0 -5
- package/dist/shell-3S4VLYEG.js +0 -4
- package/dist/shell-JXOPKDXH.js.map +0 -1
- package/dist/template-engine-4IZKRRHG.js +0 -3
- package/dist/template-engine-MFL5B677.js +0 -3
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Load test script for AgentsOrchestryCLI.
|
|
4
|
+
*
|
|
5
|
+
* Simulates 10+ hours of continuous orchestrator operation with shell-adapter tasks.
|
|
6
|
+
* Monitors heap, directory sizes, file counts, and event listener counts every 5 minutes.
|
|
7
|
+
* Logs all metrics to a CSV file for post-analysis.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npm run load-test # 10-hour run
|
|
11
|
+
* npm run load-test -- --duration 600 # 10-minute smoke test
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mkdtemp, mkdir, writeFile, readdir, stat, rm } from 'node:fs/promises';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { execSync } from 'node:child_process';
|
|
18
|
+
import { createWriteStream, WriteStream } from 'node:fs';
|
|
19
|
+
|
|
20
|
+
// ── Configuration ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/** ms between task creation batches */
|
|
23
|
+
const TASK_INTERVAL_MS = 45_000;
|
|
24
|
+
/** ms between metric snapshots */
|
|
25
|
+
const MONITOR_INTERVAL_MS = 5 * 60_000;
|
|
26
|
+
/** ms until burst phase */
|
|
27
|
+
const BURST_AFTER_MS = 60 * 60_000;
|
|
28
|
+
/** number of tasks in burst */
|
|
29
|
+
const BURST_TASK_COUNT = 50;
|
|
30
|
+
/** default run duration: 10 hours */
|
|
31
|
+
const DEFAULT_DURATION_S = 10 * 3600;
|
|
32
|
+
|
|
33
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
interface MetricsSnapshot {
|
|
36
|
+
ts: string;
|
|
37
|
+
elapsed_min: number;
|
|
38
|
+
heap_mb: number;
|
|
39
|
+
rss_mb: number;
|
|
40
|
+
external_mb: number;
|
|
41
|
+
orchestry_kb: number;
|
|
42
|
+
runs_count: number;
|
|
43
|
+
state_json_kb: number;
|
|
44
|
+
largest_jsonl_kb: number;
|
|
45
|
+
event_listeners: number;
|
|
46
|
+
tasks_total: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function sleep(ms: number): Promise<void> {
|
|
52
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function round1(n: number): number {
|
|
56
|
+
return Math.round(n * 10) / 10;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function dirSizeKb(dir: string): Promise<number> {
|
|
60
|
+
try {
|
|
61
|
+
const out = execSync(`du -sk "${dir}"`, { stdio: 'pipe' }).toString();
|
|
62
|
+
return parseInt(out.split('\t')[0] ?? '0', 10);
|
|
63
|
+
} catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function countJsonlFiles(dir: string): Promise<number> {
|
|
69
|
+
try {
|
|
70
|
+
const files = await readdir(dir);
|
|
71
|
+
return files.filter((f) => f.endsWith('.jsonl')).length;
|
|
72
|
+
} catch {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function fileSizeKb(path: string): Promise<number> {
|
|
78
|
+
try {
|
|
79
|
+
const s = await stat(path);
|
|
80
|
+
return round1(s.size / 1024);
|
|
81
|
+
} catch {
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function largestJsonlKb(dir: string): Promise<number> {
|
|
87
|
+
try {
|
|
88
|
+
const files = await readdir(dir);
|
|
89
|
+
const jsonlFiles = files.filter((f) => f.endsWith('.jsonl'));
|
|
90
|
+
let max = 0;
|
|
91
|
+
await Promise.all(
|
|
92
|
+
jsonlFiles.map(async (f) => {
|
|
93
|
+
const kb = await fileSizeKb(join(dir, f));
|
|
94
|
+
if (kb > max) max = kb;
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
return max;
|
|
98
|
+
} catch {
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function csvRow(snap: MetricsSnapshot): string {
|
|
104
|
+
return [
|
|
105
|
+
snap.ts,
|
|
106
|
+
snap.elapsed_min,
|
|
107
|
+
snap.heap_mb,
|
|
108
|
+
snap.rss_mb,
|
|
109
|
+
snap.external_mb,
|
|
110
|
+
snap.orchestry_kb,
|
|
111
|
+
snap.runs_count,
|
|
112
|
+
snap.state_json_kb,
|
|
113
|
+
snap.largest_jsonl_kb,
|
|
114
|
+
snap.event_listeners,
|
|
115
|
+
snap.tasks_total,
|
|
116
|
+
].join(',') + '\n';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const CSV_HEADER =
|
|
120
|
+
'ts,elapsed_min,heap_mb,rss_mb,external_mb,orchestry_kb,runs_count,state_json_kb,largest_jsonl_kb,event_listeners,tasks_total\n';
|
|
121
|
+
|
|
122
|
+
// ── Setup ─────────────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
async function initOrchestry(projectRoot: string): Promise<void> {
|
|
125
|
+
const orchestryDir = join(projectRoot, '.orchestry');
|
|
126
|
+
|
|
127
|
+
await Promise.all([
|
|
128
|
+
mkdir(join(orchestryDir, 'tasks'), { recursive: true }),
|
|
129
|
+
mkdir(join(orchestryDir, 'agents'), { recursive: true }),
|
|
130
|
+
mkdir(join(orchestryDir, 'runs'), { recursive: true }),
|
|
131
|
+
mkdir(join(orchestryDir, 'goals'), { recursive: true }),
|
|
132
|
+
mkdir(join(orchestryDir, 'templates'), { recursive: true }),
|
|
133
|
+
mkdir(join(orchestryDir, 'logs'), { recursive: true }),
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
// Config — 3 concurrent agents, 10s poll
|
|
137
|
+
const config = {
|
|
138
|
+
project: { name: 'load-test' },
|
|
139
|
+
defaults: {
|
|
140
|
+
agent: {
|
|
141
|
+
adapter: 'shell',
|
|
142
|
+
approval_policy: 'auto',
|
|
143
|
+
max_turns: 5,
|
|
144
|
+
timeout_ms: 120_000,
|
|
145
|
+
stall_timeout_ms: 60_000,
|
|
146
|
+
workspace_mode: 'shared',
|
|
147
|
+
},
|
|
148
|
+
task: {
|
|
149
|
+
max_attempts: 3,
|
|
150
|
+
priority: 3,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
scheduling: {
|
|
154
|
+
poll_interval_ms: 10_000,
|
|
155
|
+
max_concurrent_agents: 3,
|
|
156
|
+
retry_base_delay_ms: 5_000,
|
|
157
|
+
retry_max_delay_ms: 30_000,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const { dump } = await import('js-yaml');
|
|
162
|
+
await writeFile(join(orchestryDir, 'config.yml'), dump(config, { lineWidth: -1 }));
|
|
163
|
+
|
|
164
|
+
// Minimal prompt template
|
|
165
|
+
await writeFile(
|
|
166
|
+
join(orchestryDir, 'templates', 'default.md'),
|
|
167
|
+
'# Task: {{ task.title }}\n\n{{ task.description }}\n',
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
async function main(): Promise<void> {
|
|
174
|
+
// Parse args
|
|
175
|
+
const args = process.argv.slice(2);
|
|
176
|
+
const dIdx = args.indexOf('--duration');
|
|
177
|
+
const durationSec = dIdx >= 0 ? parseInt(args[dIdx + 1] ?? String(DEFAULT_DURATION_S), 10) : DEFAULT_DURATION_S;
|
|
178
|
+
const DURATION_MS = durationSec * 1000;
|
|
179
|
+
|
|
180
|
+
console.log('='.repeat(65));
|
|
181
|
+
console.log('AgentsOrchestryCLI — Load Test');
|
|
182
|
+
console.log(`Duration: ${round1(durationSec / 3600)}h (${durationSec}s)`);
|
|
183
|
+
console.log(`Task interval: ${TASK_INTERVAL_MS / 1000}s`);
|
|
184
|
+
console.log(`Monitor interval: ${MONITOR_INTERVAL_MS / 60000}min`);
|
|
185
|
+
console.log(`Burst: ${BURST_TASK_COUNT} tasks after ${BURST_AFTER_MS / 3600000}h`);
|
|
186
|
+
console.log('='.repeat(65));
|
|
187
|
+
|
|
188
|
+
// ── 1. Create temp project ─────────────────────────────────────────────────
|
|
189
|
+
const projectRoot = await mkdtemp(join(tmpdir(), 'orch-load-'));
|
|
190
|
+
console.log(`\n[SETUP] Project root: ${projectRoot}`);
|
|
191
|
+
await initOrchestry(projectRoot);
|
|
192
|
+
|
|
193
|
+
// ── 2. Build container ─────────────────────────────────────────────────────
|
|
194
|
+
const { buildFullContainer } = await import('../src/container.js');
|
|
195
|
+
const context = {
|
|
196
|
+
projectRoot,
|
|
197
|
+
json: false,
|
|
198
|
+
quiet: true,
|
|
199
|
+
noColor: true,
|
|
200
|
+
ascii: true,
|
|
201
|
+
};
|
|
202
|
+
const c = await buildFullContainer(context);
|
|
203
|
+
const orchestryDir = join(projectRoot, '.orchestry');
|
|
204
|
+
console.log('[SETUP] Container built.');
|
|
205
|
+
|
|
206
|
+
// ── 3. Create agents ───────────────────────────────────────────────────────
|
|
207
|
+
const [agentSuccess, agentFail, agentLong] = await Promise.all([
|
|
208
|
+
c.agentService.create({
|
|
209
|
+
name: 'Worker Success',
|
|
210
|
+
adapter: 'shell',
|
|
211
|
+
command: 'sleep 5 && echo "task completed successfully"',
|
|
212
|
+
approval_policy: 'auto',
|
|
213
|
+
max_turns: 5,
|
|
214
|
+
timeout_ms: 30_000,
|
|
215
|
+
stall_timeout_ms: 20_000,
|
|
216
|
+
workspace_mode: 'shared',
|
|
217
|
+
}),
|
|
218
|
+
c.agentService.create({
|
|
219
|
+
name: 'Worker Fail',
|
|
220
|
+
adapter: 'shell',
|
|
221
|
+
command: 'sleep 2 && exit 1',
|
|
222
|
+
approval_policy: 'auto',
|
|
223
|
+
max_turns: 5,
|
|
224
|
+
timeout_ms: 30_000,
|
|
225
|
+
stall_timeout_ms: 20_000,
|
|
226
|
+
workspace_mode: 'shared',
|
|
227
|
+
}),
|
|
228
|
+
c.agentService.create({
|
|
229
|
+
name: 'Worker Long',
|
|
230
|
+
adapter: 'shell',
|
|
231
|
+
command: 'sleep 30 && echo "long task completed"',
|
|
232
|
+
approval_policy: 'auto',
|
|
233
|
+
max_turns: 5,
|
|
234
|
+
timeout_ms: 90_000,
|
|
235
|
+
stall_timeout_ms: 60_000,
|
|
236
|
+
workspace_mode: 'shared',
|
|
237
|
+
}),
|
|
238
|
+
]);
|
|
239
|
+
console.log(`[SETUP] Agents: ${agentSuccess.id}, ${agentFail.id}, ${agentLong.id}`);
|
|
240
|
+
|
|
241
|
+
// ── 4. CSV output ──────────────────────────────────────────────────────────
|
|
242
|
+
const csvPath = join(process.cwd(), `load-test-metrics-${Date.now()}.csv`);
|
|
243
|
+
const csvFile: WriteStream = createWriteStream(csvPath);
|
|
244
|
+
csvFile.write(CSV_HEADER);
|
|
245
|
+
console.log(`[SETUP] Metrics CSV: ${csvPath}`);
|
|
246
|
+
|
|
247
|
+
// ── 5. Start orchestrator ──────────────────────────────────────────────────
|
|
248
|
+
await c.orchestrator.startWatch();
|
|
249
|
+
console.log('[SETUP] Orchestrator watch started.\n');
|
|
250
|
+
|
|
251
|
+
// ── State ──────────────────────────────────────────────────────────────────
|
|
252
|
+
let taskCounter = 0;
|
|
253
|
+
let burstFired = false;
|
|
254
|
+
let isShuttingDown = false;
|
|
255
|
+
const metricsHistory: MetricsSnapshot[] = [];
|
|
256
|
+
const startTime = Date.now();
|
|
257
|
+
const agents = [agentSuccess.id, agentFail.id, agentLong.id] as const;
|
|
258
|
+
|
|
259
|
+
// ── Collect metrics ────────────────────────────────────────────────────────
|
|
260
|
+
async function collectMetrics(elapsedMin: number): Promise<MetricsSnapshot> {
|
|
261
|
+
const mem = process.memoryUsage();
|
|
262
|
+
const runsDir = join(orchestryDir, 'runs');
|
|
263
|
+
|
|
264
|
+
const [orchestryKb, runsCount, stateKb, largestKb] = await Promise.all([
|
|
265
|
+
dirSizeKb(orchestryDir),
|
|
266
|
+
countJsonlFiles(runsDir),
|
|
267
|
+
fileSizeKb(join(orchestryDir, 'state.json')),
|
|
268
|
+
largestJsonlKb(runsDir),
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
// Sum known event type listener counts
|
|
272
|
+
let eventListeners = 0;
|
|
273
|
+
const eb = c.eventBus as unknown as { listenerCount?: (ev: string) => number };
|
|
274
|
+
if (typeof eb.listenerCount === 'function') {
|
|
275
|
+
const knownEvents = [
|
|
276
|
+
'task:created',
|
|
277
|
+
'task:status_changed',
|
|
278
|
+
'agent:completed',
|
|
279
|
+
'agent:error',
|
|
280
|
+
'orchestrator:error',
|
|
281
|
+
'orchestrator:shutdown',
|
|
282
|
+
];
|
|
283
|
+
for (const ev of knownEvents) {
|
|
284
|
+
eventListeners += eb.listenerCount(ev);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
ts: new Date().toISOString(),
|
|
290
|
+
elapsed_min: elapsedMin,
|
|
291
|
+
heap_mb: round1(mem.heapUsed / 1024 / 1024),
|
|
292
|
+
rss_mb: round1(mem.rss / 1024 / 1024),
|
|
293
|
+
external_mb: round1(mem.external / 1024 / 1024),
|
|
294
|
+
orchestry_kb: orchestryKb,
|
|
295
|
+
runs_count: runsCount,
|
|
296
|
+
state_json_kb: stateKb,
|
|
297
|
+
largest_jsonl_kb: largestKb,
|
|
298
|
+
event_listeners: eventListeners,
|
|
299
|
+
tasks_total: taskCounter,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function printMetrics(snap: MetricsSnapshot): void {
|
|
304
|
+
console.log(`\n[METRICS T+${snap.elapsed_min}min @ ${snap.ts}]`);
|
|
305
|
+
console.log(` Heap: ${snap.heap_mb} MB`);
|
|
306
|
+
console.log(` RSS: ${snap.rss_mb} MB`);
|
|
307
|
+
console.log(` .orchestry/: ${snap.orchestry_kb} KB`);
|
|
308
|
+
console.log(` runs/ files: ${snap.runs_count}`);
|
|
309
|
+
console.log(` state.json: ${snap.state_json_kb} KB`);
|
|
310
|
+
console.log(` Largest JSONL: ${snap.largest_jsonl_kb} KB`);
|
|
311
|
+
console.log(` Event listeners: ${snap.event_listeners}`);
|
|
312
|
+
console.log(` Tasks created: ${snap.tasks_total}`);
|
|
313
|
+
|
|
314
|
+
if (snap.heap_mb > 300) {
|
|
315
|
+
console.warn(` [ALERT] ⚠ Heap ${snap.heap_mb}MB exceeds 300MB threshold!`);
|
|
316
|
+
}
|
|
317
|
+
if (snap.state_json_kb > 1024) {
|
|
318
|
+
console.warn(` [ALERT] ⚠ state.json ${snap.state_json_kb}KB exceeds 1MB threshold!`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Create task batch ──────────────────────────────────────────────────────
|
|
323
|
+
async function createTasks(count: number): Promise<void> {
|
|
324
|
+
const creations = Array.from({ length: count }, async (_, i) => {
|
|
325
|
+
const idx = (taskCounter + i) % 3;
|
|
326
|
+
const types = ['success', 'fail', 'long'] as const;
|
|
327
|
+
const type = types[idx]!;
|
|
328
|
+
const assignee = agents[idx]!;
|
|
329
|
+
|
|
330
|
+
try {
|
|
331
|
+
await c.taskService.create({
|
|
332
|
+
title: `Load test #${taskCounter + i + 1} (${type})`,
|
|
333
|
+
description: `Automated load test task, type: ${type}, seq: ${taskCounter + i + 1}`,
|
|
334
|
+
priority: 3,
|
|
335
|
+
assignee,
|
|
336
|
+
labels: [type],
|
|
337
|
+
max_attempts: 3,
|
|
338
|
+
});
|
|
339
|
+
} catch (err) {
|
|
340
|
+
console.error(`[WARN] Failed to create task: ${err}`);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
await Promise.all(creations);
|
|
345
|
+
taskCounter += count;
|
|
346
|
+
const elapsed = Math.round((Date.now() - startTime) / 60000);
|
|
347
|
+
console.log(`[T+${elapsed}min] Created ${count} tasks (total: ${taskCounter})`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ── Graceful shutdown ──────────────────────────────────────────────────────
|
|
351
|
+
async function shutdown(): Promise<void> {
|
|
352
|
+
if (isShuttingDown) return;
|
|
353
|
+
isShuttingDown = true;
|
|
354
|
+
console.log('\n[SHUTDOWN] Stopping orchestrator...');
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
await c.orchestrator.stop();
|
|
358
|
+
console.log('[SHUTDOWN] Orchestrator stopped.');
|
|
359
|
+
} catch (err) {
|
|
360
|
+
console.error(`[SHUTDOWN] Stop error: ${err}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Final metrics
|
|
364
|
+
const elapsed_min = Math.round((Date.now() - startTime) / 60000);
|
|
365
|
+
const finalSnap = await collectMetrics(elapsed_min);
|
|
366
|
+
metricsHistory.push(finalSnap);
|
|
367
|
+
printMetrics(finalSnap);
|
|
368
|
+
csvFile.write(csvRow(finalSnap));
|
|
369
|
+
csvFile.end();
|
|
370
|
+
|
|
371
|
+
// ── Final report ─────────────────────────────────────────────────────────
|
|
372
|
+
console.log('\n' + '='.repeat(65));
|
|
373
|
+
console.log('FINAL REPORT');
|
|
374
|
+
console.log('='.repeat(65));
|
|
375
|
+
console.log(`Total run time: ${elapsed_min} min (${round1(elapsed_min / 60)}h)`);
|
|
376
|
+
console.log(`Tasks created: ${taskCounter}`);
|
|
377
|
+
console.log(`Final heap: ${finalSnap.heap_mb} MB`);
|
|
378
|
+
console.log(`Final RSS: ${finalSnap.rss_mb} MB`);
|
|
379
|
+
console.log(`Runs dir files: ${finalSnap.runs_count}`);
|
|
380
|
+
console.log(`state.json: ${finalSnap.state_json_kb} KB`);
|
|
381
|
+
console.log(`Metrics CSV: ${csvPath}`);
|
|
382
|
+
|
|
383
|
+
if (metricsHistory.length > 0) {
|
|
384
|
+
const peakHeap = Math.max(...metricsHistory.map((s) => s.heap_mb));
|
|
385
|
+
const peakRss = Math.max(...metricsHistory.map((s) => s.rss_mb));
|
|
386
|
+
console.log(`\nPeak heap: ${peakHeap} MB`);
|
|
387
|
+
console.log(`Peak RSS: ${peakRss} MB`);
|
|
388
|
+
|
|
389
|
+
// SLO checks
|
|
390
|
+
const slos: Array<{ name: string; pass: boolean }> = [
|
|
391
|
+
{ name: `Heap < 300MB (peak: ${peakHeap}MB)`, pass: peakHeap < 300 },
|
|
392
|
+
{
|
|
393
|
+
name: `state.json < 1MB after 500 tasks (${finalSnap.state_json_kb}KB, ${taskCounter} tasks)`,
|
|
394
|
+
pass: taskCounter < 500 || finalSnap.state_json_kb < 1024,
|
|
395
|
+
},
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
console.log('\nSLO Results:');
|
|
399
|
+
let allPass = true;
|
|
400
|
+
for (const slo of slos) {
|
|
401
|
+
const icon = slo.pass ? '✓' : '✗';
|
|
402
|
+
console.log(` ${icon} ${slo.name}`);
|
|
403
|
+
if (!slo.pass) allPass = false;
|
|
404
|
+
}
|
|
405
|
+
console.log('\n' + (allPass ? 'All SLOs PASS' : 'Some SLOs FAIL'));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Cleanup temp dir
|
|
409
|
+
try {
|
|
410
|
+
await rm(projectRoot, { recursive: true, force: true });
|
|
411
|
+
console.log(`\n[CLEANUP] Removed ${projectRoot}`);
|
|
412
|
+
} catch {
|
|
413
|
+
console.warn(`[CLEANUP] Could not remove ${projectRoot}`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
process.once('SIGINT', () => { void shutdown(); });
|
|
420
|
+
process.once('SIGTERM', () => { void shutdown(); });
|
|
421
|
+
|
|
422
|
+
// ── Main loop ──────────────────────────────────────────────────────────────
|
|
423
|
+
let lastTaskTime = Date.now();
|
|
424
|
+
let lastMonitorTime = Date.now();
|
|
425
|
+
|
|
426
|
+
// Initial batch
|
|
427
|
+
await createTasks(3);
|
|
428
|
+
|
|
429
|
+
while (!isShuttingDown && Date.now() - startTime < DURATION_MS) {
|
|
430
|
+
const now = Date.now();
|
|
431
|
+
const elapsed = now - startTime;
|
|
432
|
+
|
|
433
|
+
// Create tasks on interval
|
|
434
|
+
if (now - lastTaskTime >= TASK_INTERVAL_MS) {
|
|
435
|
+
const count = 2 + (Math.random() < 0.33 ? 1 : 0); // 2 or 3
|
|
436
|
+
await createTasks(count);
|
|
437
|
+
lastTaskTime = now;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Burst at 1 hour
|
|
441
|
+
if (!burstFired && elapsed >= BURST_AFTER_MS) {
|
|
442
|
+
burstFired = true;
|
|
443
|
+
console.log(`\n[BURST] T+1h reached — creating ${BURST_TASK_COUNT} tasks in one batch...`);
|
|
444
|
+
await createTasks(BURST_TASK_COUNT);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Monitor on interval
|
|
448
|
+
if (now - lastMonitorTime >= MONITOR_INTERVAL_MS) {
|
|
449
|
+
const elapsedMin = Math.round(elapsed / 60000);
|
|
450
|
+
const snap = await collectMetrics(elapsedMin);
|
|
451
|
+
metricsHistory.push(snap);
|
|
452
|
+
printMetrics(snap);
|
|
453
|
+
csvFile.write(csvRow(snap));
|
|
454
|
+
lastMonitorTime = now;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Lightweight idle sleep (5s checks)
|
|
458
|
+
await sleep(5_000);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (!isShuttingDown) {
|
|
462
|
+
await shutdown();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// AbortError is expected when the orchestrator kills running child processes on shutdown.
|
|
467
|
+
// Without this handler Node.js would crash with an unhandled 'error' event on ChildProcess.
|
|
468
|
+
process.on('uncaughtException', (err: Error) => {
|
|
469
|
+
const code = (err as NodeJS.ErrnoException).code;
|
|
470
|
+
if (code === 'ABORT_ERR' || err.name === 'AbortError') return;
|
|
471
|
+
console.error('[UNCAUGHT ERROR]', err);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
main().catch((err: unknown) => {
|
|
476
|
+
console.error('[FATAL]', err);
|
|
477
|
+
process.exit(1);
|
|
478
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/domain/task.ts","../src/infrastructure/template/template-engine.ts"],"names":[],"mappings":"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","file":"chunk-B4JQM4NU.js","sourcesContent":["/**\n * Task domain model.\n *\n * A Task is the unit of work in the orchestrator.\n * It moves through a state machine: todo → in_progress → review → done.\n */\n\nexport type TaskStatus =\n | 'todo'\n | 'in_progress'\n | 'retrying'\n | 'review'\n | 'done'\n | 'failed'\n | 'cancelled';\n\n/** Label applied to tasks auto-generated by the orchestrator for autonomous agents. */\nexport const AUTONOMOUS_LABEL = 'autonomous' as const;\n\nexport type WorkspaceMode = 'shared' | 'worktree' | 'isolated';\n\nexport type ReviewCriterion = 'test_pass' | 'typecheck' | 'lint';\n\nexport interface ReviewResult {\n criterion: ReviewCriterion;\n passed: boolean;\n output: string;\n}\n\nexport interface TaskProof {\n branch?: string;\n pr_url?: string;\n files_changed: string[];\n test_results?: string;\n agent_summary?: string;\n}\n\nexport interface Task {\n id: string;\n title: string;\n description: string;\n status: TaskStatus;\n priority: number;\n assignee?: string;\n labels: string[];\n depends_on: string[];\n created_at: string;\n updated_at: string;\n attempts: number;\n max_attempts: number;\n workspace_mode?: WorkspaceMode;\n workspace?: string;\n proof?: TaskProof;\n review_criteria?: ReviewCriterion[];\n review_results?: ReviewResult[];\n scope?: string[];\n feedback?: string;\n goalId?: string;\n attachments?: string[];\n}\n\nexport interface CreateTaskInput {\n title: string;\n description?: string;\n priority?: number;\n assignee?: string;\n labels?: string[];\n depends_on?: string[];\n max_attempts?: number;\n workspace_mode?: WorkspaceMode;\n review_criteria?: ReviewCriterion[];\n scope?: string[];\n goalId?: string;\n attachments?: string[];\n}\n","/**\n * Template engine for prompt construction.\n *\n * Uses LiquidJS for Liquid-compatible templating with\n * task, agent, project, and run context variables.\n */\n\nimport type { Liquid } from 'liquidjs';\nimport type { Agent } from '../../domain/agent.js';\nimport type { GoalStatus } from '../../domain/goal.js';\nimport type { OrchestratorConfig } from '../../domain/config.js';\nimport { AUTONOMOUS_LABEL, type Task } from '../../domain/task.js';\n\nexport interface ITemplateEngine {\n render(template: string, context: PromptContext): Promise<string>;\n}\n\nexport interface AgentInfo {\n id: string;\n name: string;\n role?: string;\n adapter: string;\n}\n\nexport interface RetryContext {\n previous_error: string;\n previous_output: string;\n}\n\nexport interface GoalContext {\n id: string;\n title: string;\n description: string;\n status: GoalStatus;\n task_names: string[];\n progress?: string;\n}\n\nexport interface PromptContext {\n project: {\n name: string;\n description?: string;\n };\n task: {\n id: string;\n title: string;\n description: string;\n priority: number;\n labels: string[];\n scope?: string[];\n is_autonomous: boolean;\n goal_id?: string;\n };\n agent: {\n id: string;\n name: string;\n role?: string;\n };\n agents: AgentInfo[];\n attempt: number | null;\n workspace_path: string;\n retry?: RetryContext;\n feedback?: string;\n shared_context?: Record<string, string>;\n messages?: Array<{\n id: string;\n from: string;\n subject: string;\n body: string;\n sent_at: string;\n reply_to?: string;\n }>;\n goal?: GoalContext;\n}\n\nexport class LiquidTemplateEngine implements ITemplateEngine {\n private engine: Liquid | undefined;\n private readonly renderTimeoutMs: number;\n\n constructor(options?: { renderTimeoutMs?: number }) {\n this.renderTimeoutMs = options?.renderTimeoutMs ?? 5_000;\n }\n\n private async getEngine(): Promise<Liquid> {\n if (!this.engine) {\n const { Liquid } = await import('liquidjs');\n this.engine = new Liquid({\n strictFilters: false,\n strictVariables: false,\n });\n }\n return this.engine;\n }\n\n async render(template: string, context: PromptContext): Promise<string> {\n const engine = await this.getEngine();\n const renderPromise = engine.parseAndRender(template, context);\n\n if (this.renderTimeoutMs <= 0) {\n return renderPromise;\n }\n\n let timer: ReturnType<typeof setTimeout>;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(`Template render timed out after ${this.renderTimeoutMs}ms`)),\n this.renderTimeoutMs,\n );\n });\n\n try {\n return await Promise.race([renderPromise, timeoutPromise]);\n } finally {\n clearTimeout(timer!);\n }\n }\n}\n\n/**\n * Build prompt context from domain objects.\n */\nexport interface BuildPromptOptions {\n allAgents?: Agent[];\n retryContext?: RetryContext;\n sharedContext?: Record<string, string>;\n feedback?: string;\n messages?: import('../../domain/message.js').Message[];\n goal?: GoalContext;\n}\n\nexport function buildPromptContext(\n task: Task,\n agent: Agent,\n attempt: number,\n workspacePath: string,\n config: OrchestratorConfig,\n options?: BuildPromptOptions,\n): PromptContext {\n const { allAgents, retryContext, sharedContext, feedback, messages: rawMessages, goal } = options ?? {};\n\n // Map messages to prompt-friendly shape\n const agentById = new Map((allAgents ?? []).map((a) => [a.id, a]));\n const messages = rawMessages?.length\n ? rawMessages.map((m) => ({\n id: m.id,\n from: agentById.get(m.from_agent_id)?.name ?? m.from_agent_id,\n subject: m.subject,\n body: m.body,\n sent_at: m.created_at,\n reply_to: m.reply_to,\n }))\n : undefined;\n\n return {\n project: {\n name: config.project.name,\n description: config.project.description,\n },\n task: {\n id: task.id,\n title: task.title,\n description: task.description,\n priority: task.priority,\n labels: task.labels,\n scope: task.scope,\n is_autonomous: task.labels?.includes(AUTONOMOUS_LABEL) ?? false,\n goal_id: task.goalId,\n },\n agent: {\n id: agent.id,\n name: agent.name,\n role: agent.role,\n },\n agents: (allAgents ?? []).map((a) => ({\n id: a.id,\n name: a.name,\n role: a.role,\n adapter: a.adapter,\n })),\n attempt: attempt > 1 ? attempt : null,\n workspace_path: workspacePath,\n retry: attempt > 1 ? retryContext : undefined,\n feedback,\n shared_context: sharedContext && Object.keys(sharedContext).length > 0 ? sharedContext : undefined,\n messages,\n goal,\n };\n}\n\n/** Default prompt template */\nexport const DEFAULT_PROMPT_TEMPLATE = `You are {{ agent.name }}{% if agent.role %} ({{ agent.role }}){% endif %}.\n\n## Task: {{ task.title }}\n{{ task.description }}\n\nPriority: {{ task.priority }}\n{% if attempt %}Attempt: {{ attempt }}{% endif %}\n{% if retry %}\n## Previous attempt failed\n**Error:** {{ retry.previous_error }}\n{% if retry.previous_output != \"\" %}\n**Last output:**\n\\`\\`\\`\n{{ retry.previous_output }}\n\\`\\`\\`\n{% endif %}\n**Important:** The previous approach failed. Analyze the error above and try a different strategy. Do NOT repeat the same steps that led to the failure.\n{% endif %}\n\n## Context\nProject: {{ project.name }}\nWorking directory: {{ workspace_path }}\n\n## Team\nYou are part of a multi-agent team. Available agents:\n{% for a in agents %}- **{{ a.name }}** ({{ a.adapter }}){% if a.role %} — {{ a.role }}{% endif %} · ID: \\`{{ a.id }}\\`\n{% endfor %}\nUse \\`orch agent list\\` to check current agent statuses. Find teammates by name/role — do NOT hardcode agent IDs.\n\n{% if feedback %}\n## Review Feedback\nThis task was previously completed but **rejected** during review with the following feedback:\n> {{ feedback }}\n\n**Important:** Address the feedback above. Focus on what the reviewer asked to change. Do NOT redo work that was already accepted.\n{% endif %}\n\n{% if shared_context %}\n## Shared Context\nOther agents have shared the following information:\n{% for entry in shared_context %}- **{{ entry[0] }}**: {{ entry[1] }}\n{% endfor %}\n{% endif %}\n\n{% if messages %}\n## Inbox ({{ messages.size }} message{% if messages.size != 1 %}s{% endif %})\n{% for msg in messages %}\n---\n**From:** {{ msg.from }}{% if msg.subject != \"\" %} · **Subject:** {{ msg.subject }}{% endif %}\n{{ msg.body }}\n{% if msg.reply_to %}*(Reply to: {{ msg.reply_to }})*{% endif %}\n---\n{% endfor %}\n{% endif %}\n\n## Orchestrator CLI\nManage tasks and coordinate with other agents using \\`orch\\`:\n\n**Tasks:**\n- \\`orch task add \"<title>\" -d \"<description>\" -p <1-4> --assignee <agent-id>\\` — create and assign a task\n- \\`orch task add \"<title>\" -d \"<description>\" --scope \"src/path/**\" --depends-on <task-id>\\` — scoped task with dependency\n- \\`orch task list [--status todo|in_progress|done|failed]\\` — list tasks\n\n**Messaging:**\n- \\`orch msg send <agent-id> \"<body>\" -s \"<subject>\"\\` — direct message\n- \\`orch msg broadcast \"<body>\" -s \"<subject>\"\\` — broadcast to all\n- \\`orch msg inbox {{ agent.id }}\\` — your pending messages\n\n**Shared context:**\n- \\`orch context set <key> <value>\\` / \\`orch context get <key>\\` / \\`orch context list\\`\n\n{% if goal %}\n## Goal: {{ goal.title }}\n**Status:** {{ goal.status }} · **ID:** \\`{{ goal.id }}\\`\n{% if goal.description != \"\" %}\n{{ goal.description }}\n{% endif %}\n{% if goal.task_names.size > 0 %}\n**Linked tasks ({{ goal.task_names.size }}):**\n{% for name in goal.task_names %}- {{ name }}\n{% endfor %}\nUse \\`orch task list --goal-id {{ goal.id }}\\` and \\`orch task show <id>\\` to inspect details.\n{% endif %}\n{% if goal.progress %}\n**Latest progress report:**\n{{ goal.progress }}\n{% endif %}\n{% endif %}\n\n{% if task.is_autonomous %}\n## Autonomous Goal Mode\nThis is an autonomous task driven by a goal. Work in a continuous loop until the goal is achieved:\n\n1. **Understand the goal** — read the Goal section above.\n2. **Decompose** — break the goal into concrete subtasks via \\`orch task add\\`. {% if task.goal_id %}Pass \\`--goal-id {{ task.goal_id }}\\` so subtasks are linked to this goal. {% endif %}Assign yourself for your specialty, delegate other work to appropriate teammates by role.\n3. **Execute** — follow your standard workflow for each subtask.\n4. **Track progress** — after each iteration: \\`orch context set {{ task.goal_id | default: \"<goal>\" }}-progress \"<summary of what's done and what remains>\"\\`.\n5. **Be proactive** — do NOT wait for tasks from others. Create your own subtasks and keep working.\n6. **Do NOT finish** the [auto] task until the goal is achieved — keep creating subtasks.\n7. **When done** — mark the goal as achieved: \\`orch goal status {{ task.goal_id | default: \"<goal-id>\" }} achieved\\`.\n\n**Deep inspection:** Use \\`orch goal show {{ task.goal_id | default: \"<goal-id>\" }}\\` to see full goal details at any time.\n{% endif %}\n\n## Rules\n- Do NOT ask clarifying questions. You are running autonomously without human input.\n- Make reasonable assumptions and proceed with the best approach.\n- If critical information is missing, document your assumptions and continue.\n- When a task is too large or spans multiple domains, break it into subtasks using \\`orch task add\\`.\n- When creating subtasks, use \\`--scope\\` to declare which files each task will touch, and \\`--depends-on\\` to order dependent work.\n`;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/infrastructure/adapters/utils.ts"],"names":[],"mappings":"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","file":"chunk-S3QYSBW4.js","sourcesContent":["/**\n * Shared utilities for agent adapters.\n *\n * Deduplicates extractTokens and streaming event generation logic\n * common to claude, codex, and cursor adapters.\n */\n\nimport type { ChildProcess } from 'node:child_process';\nimport type { AgentEvent } from './interface.js';\nimport { readLines } from '../process/process-manager.js';\nimport { createTokenUsage } from '../../domain/run.js';\nimport { classifyAdapterError } from '../../domain/errors.js';\n\nexport type TokenInfo = { input: number; output: number; total: number };\n\n/**\n * Extract token usage from a parsed JSON event.\n *\n * @param parsed - The parsed JSON object from an adapter event line.\n * @param opts.statsFallback - If true, also checks `parsed.stats?.usage` (Claude-specific).\n */\nexport function extractTokens(\n parsed: Record<string, unknown>,\n opts?: { statsFallback?: boolean },\n): TokenInfo | undefined {\n let usage = parsed.usage as Record<string, unknown> | undefined;\n\n if (!usage && opts?.statsFallback) {\n const stats = parsed.stats as Record<string, unknown> | undefined;\n usage = stats?.usage as Record<string, unknown> | undefined;\n }\n\n if (usage && typeof usage.input_tokens === 'number') {\n const input = usage.input_tokens;\n const output = typeof usage.output_tokens === 'number' ? usage.output_tokens : 0;\n return createTokenUsage(input, output);\n }\n return undefined;\n}\n\n/**\n * Create an async generator that streams AgentEvents from a child process.\n *\n * Handles: exit promise setup, line-by-line reading, abort signal, exit code checking.\n *\n * @param proc - The spawned child process.\n * @param parseEvent - Adapter-specific function to parse a line into an AgentEvent.\n * @param adapterName - Name used in error messages (e.g. \"Claude\", \"Codex\").\n * @param signal - Optional abort signal.\n */\nexport function createStreamingEvents(\n proc: ChildProcess,\n parseEvent: (line: string) => AgentEvent | null,\n adapterName: string,\n signal?: AbortSignal,\n): AsyncGenerator<AgentEvent> {\n async function* generate(): AsyncGenerator<AgentEvent> {\n let gotDoneEvent = false;\n\n let exitCode: number | null = null;\n let exitError: Error | null = null;\n const exitPromise = new Promise<void>((resolve) => {\n proc.on('close', (code) => { exitCode = code; resolve(); });\n proc.on('error', (err) => { exitError = err; resolve(); });\n });\n\n if (proc.stdout) {\n for await (const line of readLines(proc.stdout)) {\n if (signal?.aborted) break;\n const event = parseEvent(line);\n if (event) {\n if (event.type === 'done') gotDoneEvent = true;\n yield event;\n }\n }\n }\n\n await exitPromise;\n\n if (exitError && !signal?.aborted && !gotDoneEvent) {\n const spawnErr = exitError as Error;\n const classified = classifyAdapterError(spawnErr.message, exitCode ?? undefined);\n const err = Object.assign(new Error(spawnErr.message), { errorKind: classified });\n throw err;\n }\n if (exitCode !== 0 && exitCode !== null && !signal?.aborted && !gotDoneEvent) {\n const msg = `${adapterName} process exited with code ${exitCode}`;\n const classified = classifyAdapterError(msg, exitCode);\n const err = Object.assign(new Error(msg), { errorKind: classified });\n throw err;\n }\n }\n\n return generate();\n}\n"]}
|