cipher-security 5.0.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/bin/cipher.js +465 -0
- package/lib/api/billing.js +321 -0
- package/lib/api/compliance.js +693 -0
- package/lib/api/controls.js +1401 -0
- package/lib/api/index.js +49 -0
- package/lib/api/marketplace.js +467 -0
- package/lib/api/openai-proxy.js +383 -0
- package/lib/api/server.js +685 -0
- package/lib/autonomous/feedback-loop.js +554 -0
- package/lib/autonomous/framework.js +512 -0
- package/lib/autonomous/index.js +97 -0
- package/lib/autonomous/leaderboard.js +594 -0
- package/lib/autonomous/modes/architect.js +412 -0
- package/lib/autonomous/modes/blue.js +386 -0
- package/lib/autonomous/modes/incident.js +684 -0
- package/lib/autonomous/modes/privacy.js +369 -0
- package/lib/autonomous/modes/purple.js +294 -0
- package/lib/autonomous/modes/recon.js +250 -0
- package/lib/autonomous/parallel.js +587 -0
- package/lib/autonomous/researcher.js +583 -0
- package/lib/autonomous/runner.js +955 -0
- package/lib/autonomous/scheduler.js +615 -0
- package/lib/autonomous/task-parser.js +127 -0
- package/lib/autonomous/validators/forensic.js +266 -0
- package/lib/autonomous/validators/osint.js +216 -0
- package/lib/autonomous/validators/privacy.js +296 -0
- package/lib/autonomous/validators/purple.js +298 -0
- package/lib/autonomous/validators/sigma.js +248 -0
- package/lib/autonomous/validators/threat-model.js +363 -0
- package/lib/benchmark/agent.js +119 -0
- package/lib/benchmark/baselines.js +43 -0
- package/lib/benchmark/builder.js +143 -0
- package/lib/benchmark/config.js +35 -0
- package/lib/benchmark/coordinator.js +91 -0
- package/lib/benchmark/index.js +20 -0
- package/lib/benchmark/llm.js +58 -0
- package/lib/benchmark/models.js +137 -0
- package/lib/benchmark/reporter.js +103 -0
- package/lib/benchmark/runner.js +103 -0
- package/lib/benchmark/sandbox.js +96 -0
- package/lib/benchmark/scorer.js +32 -0
- package/lib/benchmark/solver.js +166 -0
- package/lib/benchmark/tools.js +62 -0
- package/lib/bot/bot.js +130 -0
- package/lib/commands.js +99 -0
- package/lib/complexity.js +377 -0
- package/lib/config.js +213 -0
- package/lib/gateway/client.js +309 -0
- package/lib/gateway/commands.js +830 -0
- package/lib/gateway/config-validate.js +109 -0
- package/lib/gateway/gateway.js +367 -0
- package/lib/gateway/index.js +62 -0
- package/lib/gateway/mode.js +309 -0
- package/lib/gateway/plugins.js +222 -0
- package/lib/gateway/prompt.js +214 -0
- package/lib/mcp/server.js +262 -0
- package/lib/memory/compressor.js +425 -0
- package/lib/memory/engine.js +763 -0
- package/lib/memory/evolution.js +668 -0
- package/lib/memory/index.js +58 -0
- package/lib/memory/orchestrator.js +506 -0
- package/lib/memory/retriever.js +515 -0
- package/lib/memory/synthesizer.js +333 -0
- package/lib/pipeline/async-scanner.js +510 -0
- package/lib/pipeline/binary-analysis.js +1043 -0
- package/lib/pipeline/dom-xss-scanner.js +435 -0
- package/lib/pipeline/github-actions.js +792 -0
- package/lib/pipeline/index.js +124 -0
- package/lib/pipeline/osint.js +498 -0
- package/lib/pipeline/sarif.js +373 -0
- package/lib/pipeline/scanner.js +880 -0
- package/lib/pipeline/template-manager.js +525 -0
- package/lib/pipeline/xss-scanner.js +353 -0
- package/lib/setup-wizard.js +229 -0
- package/package.json +30 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
// Copyright (c) 2026 defconxt. All rights reserved.
|
|
2
|
+
// Licensed under AGPL-3.0 — see LICENSE file for details.
|
|
3
|
+
// CIPHER is a trademark of defconxt.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parallel agent execution engine for CIPHER autonomous operations.
|
|
7
|
+
*
|
|
8
|
+
* Provides Promise-based parallel execution of agent tasks with
|
|
9
|
+
* fan-out/fan-in, map-reduce, pipeline, and callback patterns.
|
|
10
|
+
*
|
|
11
|
+
* Python threading.Thread → Promise.all + concurrency limiter.
|
|
12
|
+
*
|
|
13
|
+
* @module autonomous/parallel
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { basename, dirname } from 'node:path';
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Data classes
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
export class AgentTask {
|
|
25
|
+
constructor({
|
|
26
|
+
taskId = randomUUID(),
|
|
27
|
+
name = '',
|
|
28
|
+
skillPath = '',
|
|
29
|
+
inputData = {},
|
|
30
|
+
status = 'pending',
|
|
31
|
+
result = null,
|
|
32
|
+
error = null,
|
|
33
|
+
startTime = 0,
|
|
34
|
+
endTime = 0,
|
|
35
|
+
workerId = 0,
|
|
36
|
+
} = {}) {
|
|
37
|
+
this.taskId = taskId;
|
|
38
|
+
this.name = name;
|
|
39
|
+
this.skillPath = skillPath;
|
|
40
|
+
this.inputData = inputData;
|
|
41
|
+
this.status = status;
|
|
42
|
+
this.result = result;
|
|
43
|
+
this.error = error;
|
|
44
|
+
this.startTime = startTime;
|
|
45
|
+
this.endTime = endTime;
|
|
46
|
+
this.workerId = workerId;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class AgentResult {
|
|
51
|
+
constructor({
|
|
52
|
+
taskId = '',
|
|
53
|
+
success = false,
|
|
54
|
+
output = {},
|
|
55
|
+
duration = 0,
|
|
56
|
+
workerId = 0,
|
|
57
|
+
error = null,
|
|
58
|
+
} = {}) {
|
|
59
|
+
this.taskId = taskId;
|
|
60
|
+
this.success = success;
|
|
61
|
+
this.output = output;
|
|
62
|
+
this.duration = duration;
|
|
63
|
+
this.workerId = workerId;
|
|
64
|
+
this.error = error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// WorkerPool — Promise-based concurrency limiter
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export class WorkerPool {
|
|
73
|
+
/**
|
|
74
|
+
* @param {number} [maxWorkers=4]
|
|
75
|
+
*/
|
|
76
|
+
constructor(maxWorkers = 4) {
|
|
77
|
+
this.maxWorkers = maxWorkers;
|
|
78
|
+
/** @type {Map<string, AgentTask>} */
|
|
79
|
+
this._tasks = new Map();
|
|
80
|
+
/** @type {Map<string, AgentResult>} */
|
|
81
|
+
this._results = new Map();
|
|
82
|
+
this._pendingCount = 0;
|
|
83
|
+
this._activeCount = 0;
|
|
84
|
+
this._completedCount = 0;
|
|
85
|
+
this._shutdown = false;
|
|
86
|
+
/** @type {AgentTask[]} */
|
|
87
|
+
this._queue = [];
|
|
88
|
+
/** @type {Array<{resolve: Function}>} */
|
|
89
|
+
this._waiters = [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Submit a single task. Returns its taskId.
|
|
94
|
+
* @param {AgentTask} task
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
submit(task) {
|
|
98
|
+
this._tasks.set(task.taskId, task);
|
|
99
|
+
this._pendingCount += 1;
|
|
100
|
+
this._queue.push(task);
|
|
101
|
+
return task.taskId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Submit several tasks at once.
|
|
106
|
+
* @param {AgentTask[]} tasks
|
|
107
|
+
* @returns {string[]}
|
|
108
|
+
*/
|
|
109
|
+
submitBatch(tasks) {
|
|
110
|
+
return tasks.map(t => this.submit(t));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Return result for taskId, or null if not yet done.
|
|
115
|
+
* @param {string} taskId
|
|
116
|
+
* @returns {AgentResult|null}
|
|
117
|
+
*/
|
|
118
|
+
getResult(taskId) {
|
|
119
|
+
return this._results.get(taskId) || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Return all results collected so far. */
|
|
123
|
+
getAllResults() {
|
|
124
|
+
return [...this._results.values()];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Execute all queued tasks with concurrency limit, then return results.
|
|
129
|
+
* @param {number} [timeout=300000] - ms
|
|
130
|
+
* @returns {Promise<AgentResult[]>}
|
|
131
|
+
*/
|
|
132
|
+
async waitAll(timeout = 300000) {
|
|
133
|
+
const tasks = [...this._queue];
|
|
134
|
+
this._queue = [];
|
|
135
|
+
|
|
136
|
+
// Process tasks with concurrency limit
|
|
137
|
+
const inFlight = new Set();
|
|
138
|
+
let workerCounter = 0;
|
|
139
|
+
|
|
140
|
+
for (const task of tasks) {
|
|
141
|
+
if (this._shutdown) break;
|
|
142
|
+
if (task.status === 'cancelled') continue;
|
|
143
|
+
|
|
144
|
+
const workerId = workerCounter++ % this.maxWorkers;
|
|
145
|
+
|
|
146
|
+
const promise = this._executeTask(task, workerId).then(result => {
|
|
147
|
+
inFlight.delete(promise);
|
|
148
|
+
this._results.set(task.taskId, result);
|
|
149
|
+
task.status = result.success ? 'completed' : 'failed';
|
|
150
|
+
task.result = result.output;
|
|
151
|
+
task.error = result.error;
|
|
152
|
+
this._activeCount -= 1;
|
|
153
|
+
this._completedCount += 1;
|
|
154
|
+
this._pendingCount -= 1;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
this._activeCount += 1;
|
|
158
|
+
inFlight.add(promise);
|
|
159
|
+
|
|
160
|
+
if (inFlight.size >= this.maxWorkers) {
|
|
161
|
+
await Promise.race(inFlight);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Wait for remaining in-flight tasks
|
|
166
|
+
if (inFlight.size > 0) {
|
|
167
|
+
await Promise.race([
|
|
168
|
+
Promise.all(inFlight),
|
|
169
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('waitAll timeout')), timeout)),
|
|
170
|
+
]).catch(() => { /* timeout — return what we have */ });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Notify any waiters
|
|
174
|
+
for (const w of this._waiters) w.resolve();
|
|
175
|
+
this._waiters = [];
|
|
176
|
+
|
|
177
|
+
return this.getAllResults();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Cancel a pending task.
|
|
182
|
+
* @param {string} taskId
|
|
183
|
+
* @returns {boolean}
|
|
184
|
+
*/
|
|
185
|
+
cancel(taskId) {
|
|
186
|
+
const task = this._tasks.get(taskId);
|
|
187
|
+
if (task && task.status === 'pending') {
|
|
188
|
+
task.status = 'cancelled';
|
|
189
|
+
this._pendingCount -= 1;
|
|
190
|
+
this._results.set(taskId, new AgentResult({
|
|
191
|
+
taskId,
|
|
192
|
+
success: false,
|
|
193
|
+
output: {},
|
|
194
|
+
duration: 0,
|
|
195
|
+
workerId: 0,
|
|
196
|
+
error: 'cancelled',
|
|
197
|
+
}));
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Cancel every pending task in the queue. */
|
|
204
|
+
cancelAll() {
|
|
205
|
+
for (const task of this._tasks.values()) {
|
|
206
|
+
if (task.status === 'pending') {
|
|
207
|
+
task.status = 'cancelled';
|
|
208
|
+
this._pendingCount -= 1;
|
|
209
|
+
this._results.set(task.taskId, new AgentResult({
|
|
210
|
+
taskId: task.taskId,
|
|
211
|
+
success: false,
|
|
212
|
+
output: {},
|
|
213
|
+
duration: 0,
|
|
214
|
+
workerId: 0,
|
|
215
|
+
error: 'cancelled',
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Return a snapshot of pool state. */
|
|
222
|
+
getStatus() {
|
|
223
|
+
return {
|
|
224
|
+
active: this._activeCount,
|
|
225
|
+
pending: this._pendingCount,
|
|
226
|
+
completed: this._completedCount,
|
|
227
|
+
totalTasks: this._tasks.size,
|
|
228
|
+
maxWorkers: this.maxWorkers,
|
|
229
|
+
shutdown: this._shutdown,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Signal workers to stop. */
|
|
234
|
+
shutdown() {
|
|
235
|
+
this._shutdown = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// -- internal -----------------------------------------------------------
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {AgentTask} task
|
|
242
|
+
* @param {number} workerId
|
|
243
|
+
* @returns {Promise<AgentResult>}
|
|
244
|
+
*/
|
|
245
|
+
async _executeTask(task, workerId = 0) {
|
|
246
|
+
const start = performance.now() / 1000;
|
|
247
|
+
try {
|
|
248
|
+
const output = {};
|
|
249
|
+
|
|
250
|
+
// Check skill path
|
|
251
|
+
if (task.skillPath && existsSync(task.skillPath)) {
|
|
252
|
+
output.skillLoaded = task.skillPath;
|
|
253
|
+
output.skillName = basename(dirname(task.skillPath));
|
|
254
|
+
} else {
|
|
255
|
+
output.skillLoaded = null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Run callable if present
|
|
259
|
+
const fn = task.inputData.fn;
|
|
260
|
+
if (typeof fn === 'function') {
|
|
261
|
+
const args = task.inputData.args || [];
|
|
262
|
+
const kwargs = task.inputData.kwargs || {};
|
|
263
|
+
const fnResult = await fn(...args, kwargs);
|
|
264
|
+
output.fnResult = fnResult;
|
|
265
|
+
} else {
|
|
266
|
+
output.inputEcho = task.inputData;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const duration = performance.now() / 1000 - start;
|
|
270
|
+
task.startTime = start;
|
|
271
|
+
task.endTime = start + duration;
|
|
272
|
+
task.workerId = workerId;
|
|
273
|
+
|
|
274
|
+
return new AgentResult({
|
|
275
|
+
taskId: task.taskId,
|
|
276
|
+
success: true,
|
|
277
|
+
output,
|
|
278
|
+
duration,
|
|
279
|
+
workerId,
|
|
280
|
+
});
|
|
281
|
+
} catch (exc) {
|
|
282
|
+
const duration = performance.now() / 1000 - start;
|
|
283
|
+
task.startTime = start;
|
|
284
|
+
task.endTime = start + duration;
|
|
285
|
+
task.workerId = workerId;
|
|
286
|
+
|
|
287
|
+
return new AgentResult({
|
|
288
|
+
taskId: task.taskId,
|
|
289
|
+
success: false,
|
|
290
|
+
output: {},
|
|
291
|
+
duration,
|
|
292
|
+
workerId,
|
|
293
|
+
error: `${exc.constructor.name}: ${exc.message}`,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// ParallelScanner
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
|
|
303
|
+
export class ParallelScanner {
|
|
304
|
+
/**
|
|
305
|
+
* @param {WorkerPool} [pool]
|
|
306
|
+
*/
|
|
307
|
+
constructor(pool) {
|
|
308
|
+
this.pool = pool || new WorkerPool();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Create one task per target, fan-out, collect results.
|
|
313
|
+
* @param {string[]} targets
|
|
314
|
+
* @param {string} [profile='pentest']
|
|
315
|
+
*/
|
|
316
|
+
async scanTargets(targets, profile = 'pentest') {
|
|
317
|
+
const tasks = targets.map(target => new AgentTask({
|
|
318
|
+
name: `scan-${target}`,
|
|
319
|
+
inputData: {
|
|
320
|
+
fn: ParallelScanner._runScan,
|
|
321
|
+
args: [target, profile],
|
|
322
|
+
},
|
|
323
|
+
}));
|
|
324
|
+
const ids = this.pool.submitBatch(tasks);
|
|
325
|
+
const idSet = new Set(ids);
|
|
326
|
+
await this.pool.waitAll();
|
|
327
|
+
const allResults = this.pool.getAllResults();
|
|
328
|
+
const results = allResults.filter(r => idSet.has(r.taskId));
|
|
329
|
+
const idToTarget = Object.fromEntries(ids.map((id, i) => [id, targets[i]]));
|
|
330
|
+
return results.map(r => ({
|
|
331
|
+
taskId: r.taskId,
|
|
332
|
+
target: idToTarget[r.taskId] || 'unknown',
|
|
333
|
+
success: r.success,
|
|
334
|
+
output: r.output,
|
|
335
|
+
duration: r.duration,
|
|
336
|
+
error: r.error,
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Run domain-specific recon scans in parallel.
|
|
342
|
+
* @param {string[]} domains
|
|
343
|
+
*/
|
|
344
|
+
async scanDomains(domains) {
|
|
345
|
+
const tasks = domains.map(domain => new AgentTask({
|
|
346
|
+
name: `domain-scan-${domain}`,
|
|
347
|
+
inputData: {
|
|
348
|
+
fn: ParallelScanner._runDomainScan,
|
|
349
|
+
args: [domain],
|
|
350
|
+
},
|
|
351
|
+
}));
|
|
352
|
+
const ids = this.pool.submitBatch(tasks);
|
|
353
|
+
const idSet = new Set(ids);
|
|
354
|
+
await this.pool.waitAll();
|
|
355
|
+
const allResults = this.pool.getAllResults();
|
|
356
|
+
const results = allResults.filter(r => idSet.has(r.taskId));
|
|
357
|
+
return {
|
|
358
|
+
domainsScanned: domains.length,
|
|
359
|
+
successful: results.filter(r => r.success).length,
|
|
360
|
+
failed: results.filter(r => !r.success).length,
|
|
361
|
+
results: results.map(r => ({
|
|
362
|
+
taskId: r.taskId,
|
|
363
|
+
success: r.success,
|
|
364
|
+
output: r.output,
|
|
365
|
+
error: r.error,
|
|
366
|
+
})),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
static _runScan(target, profile) {
|
|
371
|
+
return {
|
|
372
|
+
target,
|
|
373
|
+
profile,
|
|
374
|
+
findings: [],
|
|
375
|
+
status: 'complete',
|
|
376
|
+
timestamp: Date.now() / 1000,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
static _runDomainScan(domain) {
|
|
381
|
+
return {
|
|
382
|
+
domain,
|
|
383
|
+
dnsRecords: [],
|
|
384
|
+
subdomains: [],
|
|
385
|
+
ports: [],
|
|
386
|
+
status: 'complete',
|
|
387
|
+
timestamp: Date.now() / 1000,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
// ParallelAnalyzer
|
|
394
|
+
// ---------------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
export class ParallelAnalyzer {
|
|
397
|
+
/**
|
|
398
|
+
* @param {WorkerPool} [pool]
|
|
399
|
+
*/
|
|
400
|
+
constructor(pool) {
|
|
401
|
+
this.pool = pool || new WorkerPool();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Analyze multiple diffs concurrently for security issues.
|
|
406
|
+
* @param {string[]} diffs
|
|
407
|
+
*/
|
|
408
|
+
async analyzeDiffs(diffs) {
|
|
409
|
+
const tasks = diffs.map((diff, idx) => new AgentTask({
|
|
410
|
+
name: `diff-analysis-${idx}`,
|
|
411
|
+
inputData: {
|
|
412
|
+
fn: ParallelAnalyzer._analyzeSingleDiff,
|
|
413
|
+
args: [diff],
|
|
414
|
+
},
|
|
415
|
+
}));
|
|
416
|
+
const ids = this.pool.submitBatch(tasks);
|
|
417
|
+
const idSet = new Set(ids);
|
|
418
|
+
await this.pool.waitAll();
|
|
419
|
+
const allResults = this.pool.getAllResults();
|
|
420
|
+
const results = allResults.filter(r => idSet.has(r.taskId));
|
|
421
|
+
return results.map(r => ({
|
|
422
|
+
taskId: r.taskId,
|
|
423
|
+
success: r.success,
|
|
424
|
+
analysis: r.output?.fnResult || {},
|
|
425
|
+
error: r.error,
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Score multiple responses in parallel.
|
|
431
|
+
* @param {object[]} responses
|
|
432
|
+
*/
|
|
433
|
+
async bulkScore(responses) {
|
|
434
|
+
const tasks = responses.map((resp, idx) => new AgentTask({
|
|
435
|
+
name: `score-${idx}`,
|
|
436
|
+
inputData: {
|
|
437
|
+
fn: ParallelAnalyzer._scoreResponse,
|
|
438
|
+
args: [resp],
|
|
439
|
+
},
|
|
440
|
+
}));
|
|
441
|
+
const ids = this.pool.submitBatch(tasks);
|
|
442
|
+
const idSet = new Set(ids);
|
|
443
|
+
await this.pool.waitAll();
|
|
444
|
+
const allResults = this.pool.getAllResults();
|
|
445
|
+
const results = allResults.filter(r => idSet.has(r.taskId));
|
|
446
|
+
return results.map(r => ({
|
|
447
|
+
taskId: r.taskId,
|
|
448
|
+
success: r.success,
|
|
449
|
+
score: r.output?.fnResult || {},
|
|
450
|
+
error: r.error,
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
static _analyzeSingleDiff(diff) {
|
|
455
|
+
const lines = diff ? diff.trim().split('\n') : [];
|
|
456
|
+
const additions = lines.filter(l => l.startsWith('+')).length;
|
|
457
|
+
const deletions = lines.filter(l => l.startsWith('-')).length;
|
|
458
|
+
return {
|
|
459
|
+
totalLines: lines.length,
|
|
460
|
+
additions,
|
|
461
|
+
deletions,
|
|
462
|
+
riskIndicators: [],
|
|
463
|
+
severity: 'info',
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
static _scoreResponse(response) {
|
|
468
|
+
let score = 0.5;
|
|
469
|
+
const flags = [];
|
|
470
|
+
if ('error' in response) {
|
|
471
|
+
score -= 0.3;
|
|
472
|
+
flags.push('contains_error');
|
|
473
|
+
}
|
|
474
|
+
if (response.status === 'complete') {
|
|
475
|
+
score += 0.2;
|
|
476
|
+
flags.push('complete');
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
score: Math.max(0.0, Math.min(1.0, score)),
|
|
480
|
+
flags,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
// TaskOrchestrator
|
|
487
|
+
// ---------------------------------------------------------------------------
|
|
488
|
+
|
|
489
|
+
export class TaskOrchestrator {
|
|
490
|
+
/**
|
|
491
|
+
* @param {number} [maxWorkers=4]
|
|
492
|
+
*/
|
|
493
|
+
constructor(maxWorkers = 4) {
|
|
494
|
+
this.maxWorkers = maxWorkers;
|
|
495
|
+
this._totalTasks = 0;
|
|
496
|
+
this._totalDuration = 0;
|
|
497
|
+
this._totalFailures = 0;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Submit all tasks, wait for completion, return results.
|
|
502
|
+
* @param {AgentTask[]} tasks
|
|
503
|
+
* @returns {Promise<AgentResult[]>}
|
|
504
|
+
*/
|
|
505
|
+
async fanOut(tasks) {
|
|
506
|
+
const pool = new WorkerPool(this.maxWorkers);
|
|
507
|
+
pool.submitBatch(tasks);
|
|
508
|
+
const results = await pool.waitAll();
|
|
509
|
+
pool.shutdown();
|
|
510
|
+
this._recordMetrics(results);
|
|
511
|
+
return results;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Fan-out tasks; invoke callback as each completes.
|
|
516
|
+
* @param {AgentTask[]} tasks
|
|
517
|
+
* @param {(result: AgentResult) => void} callback
|
|
518
|
+
*/
|
|
519
|
+
async fanOutWithCallback(tasks, callback) {
|
|
520
|
+
const pool = new WorkerPool(this.maxWorkers);
|
|
521
|
+
pool.submitBatch(tasks);
|
|
522
|
+
const results = await pool.waitAll();
|
|
523
|
+
for (const r of results) {
|
|
524
|
+
callback(r);
|
|
525
|
+
}
|
|
526
|
+
pool.shutdown();
|
|
527
|
+
this._recordMetrics(results);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Fan-out tasks then apply reduceFn to collected results.
|
|
532
|
+
* @param {AgentTask[]} tasks
|
|
533
|
+
* @param {(results: AgentResult[]) => *} reduceFn
|
|
534
|
+
*/
|
|
535
|
+
async mapReduce(tasks, reduceFn) {
|
|
536
|
+
const results = await this.fanOut(tasks);
|
|
537
|
+
return reduceFn(results);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Execute stages sequentially; within each stage tasks run in parallel.
|
|
542
|
+
* @param {AgentTask[][]} stages
|
|
543
|
+
* @returns {Promise<AgentResult[]>}
|
|
544
|
+
*/
|
|
545
|
+
async pipeline(stages) {
|
|
546
|
+
const allResults = [];
|
|
547
|
+
let priorResults = [];
|
|
548
|
+
|
|
549
|
+
for (const stageTasks of stages) {
|
|
550
|
+
for (const task of stageTasks) {
|
|
551
|
+
task.inputData.priorResults = priorResults.map(r => ({
|
|
552
|
+
taskId: r.taskId,
|
|
553
|
+
success: r.success,
|
|
554
|
+
output: r.output,
|
|
555
|
+
error: r.error,
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
priorResults = await this.fanOut(stageTasks);
|
|
560
|
+
allResults.push(...priorResults);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return allResults;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/** Return throughput, average duration, and failure rate. */
|
|
567
|
+
getMetrics() {
|
|
568
|
+
const avg = this._totalTasks > 0 ? this._totalDuration / this._totalTasks : 0;
|
|
569
|
+
const failureRate = this._totalTasks > 0 ? this._totalFailures / this._totalTasks : 0;
|
|
570
|
+
return {
|
|
571
|
+
totalTasks: this._totalTasks,
|
|
572
|
+
totalDuration: Math.round(this._totalDuration * 10000) / 10000,
|
|
573
|
+
avgDuration: Math.round(avg * 10000) / 10000,
|
|
574
|
+
failureRate: Math.round(failureRate * 10000) / 10000,
|
|
575
|
+
maxWorkers: this.maxWorkers,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** @param {AgentResult[]} results */
|
|
580
|
+
_recordMetrics(results) {
|
|
581
|
+
for (const r of results) {
|
|
582
|
+
this._totalTasks += 1;
|
|
583
|
+
this._totalDuration += r.duration;
|
|
584
|
+
if (!r.success) this._totalFailures += 1;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|