MCPcopy Create free account
hub / github.com/QodeXcli/QodeX / runFanout

Function runFanout

src/orchestration/fanout.ts:60–134  ·  view source on GitHub ↗
(jobs: FanoutJob[], opts: FanoutOptions)

Source from the content-addressed store, hash-verified

58 * should have checked).
59 */
60export async function runFanout(jobs: FanoutJob[], opts: FanoutOptions): Promise<FanoutJobResult[]> {
61 const runner = getSubAgentRunner();
62 if (!runner) {
63 throw new Error('fanout requires sub-agents to be enabled (getSubAgentRunner() returned null)');
64 }
65 const now = opts.now ?? Date.now;
66 const cap = Math.max(1, Math.floor(opts.maxConcurrency));
67 const results = new Array<FanoutJobResult>(jobs.length);
68
69 const runOne = async (job: FanoutJob, index: number): Promise<void> => {
70 const start = now();
71 opts.onEvent?.({ type: 'job-start', label: job.label, index, total: jobs.length });
72 try {
73 const r = await runner(job.prompt, {
74 maxIterations: job.maxIterations ?? 8,
75 signal: opts.signal,
76 sessionId: job.sessionId,
77 modelOverride: job.model,
78 role: job.role,
79 });
80 results[index] = {
81 label: job.label,
82 ok: r.ok,
83 finalText: r.finalText ?? '',
84 toolCallsRun: r.toolCallsRun ?? 0,
85 error: r.error,
86 modelUsed: r.modelUsed,
87 elapsedMs: now() - start,
88 };
89 } catch (err: any) {
90 // A runner that throws (rather than returning ok:false) must not kill the pool.
91 logger.warn('fanout job threw', { label: job.label, err: err?.message ?? String(err) });
92 results[index] = {
93 label: job.label,
94 ok: false,
95 finalText: '',
96 toolCallsRun: 0,
97 error: err?.message ?? String(err),
98 elapsedMs: now() - start,
99 };
100 } finally {
101 const res = results[index]!;
102 opts.onEvent?.({ type: 'job-done', label: job.label, index, ok: res.ok, elapsedMs: res.elapsedMs, toolCallsRun: res.toolCallsRun });
103 }
104 };
105
106 let next = 0;
107 const inFlight = new Set<Promise<void>>();
108 while (next < jobs.length || inFlight.size > 0) {
109 // Fill open slots (unless aborted — then drain what's running and stop).
110 while (next < jobs.length && inFlight.size < cap && !opts.signal?.aborted) {
111 const index = next++;
112 const p = runOne(jobs[index]!, index).finally(() => inFlight.delete(p));
113 inFlight.add(p);
114 }
115 if (inFlight.size === 0) break; // aborted with nothing left to drain
116 await Promise.race(inFlight);
117 }

Callers 2

executeMethod · 0.85

Calls 4

getSubAgentRunnerFunction · 0.85
deleteMethod · 0.80
addMethod · 0.80
runOneFunction · 0.70

Tested by

no test coverage detected