@alexeiled/pi-fusion 0.3.1 → 0.5.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/README.md +34 -2
- package/agents/fusion-panelist.md +7 -1
- package/docs/user-guide.md +56 -20
- package/package.json +1 -1
- package/skills/fusion-review/SKILL.md +4 -0
- package/src/config.ts +7 -0
- package/src/fusion-rpc.ts +619 -0
- package/src/index.ts +16 -3
- package/src/orchestrator.ts +380 -53
- package/src/report.ts +171 -10
- package/src/result-extract.ts +157 -24
- package/src/run-builder.ts +32 -4
- package/src/run-observations.ts +335 -0
- package/src/run-store.ts +237 -6
- package/src/types.ts +52 -0
package/src/orchestrator.ts
CHANGED
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
renderFailureReport,
|
|
12
12
|
renderJudgeReport,
|
|
13
13
|
} from "./report.js";
|
|
14
|
-
import { extractPanelResults } from "./result-extract.js";
|
|
15
14
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
} from "./
|
|
15
|
+
extractPanelResults,
|
|
16
|
+
type ExtractPanelResultsSuccess,
|
|
17
|
+
} from "./result-extract.js";
|
|
18
|
+
import { appendThinkingSuffix, buildPanelSpawnParams } from "./run-builder.js";
|
|
19
19
|
import { FusionRunStore, FusionRunStoreError } from "./run-store.js";
|
|
20
20
|
import {
|
|
21
21
|
clearFusionUi,
|
|
@@ -36,6 +36,11 @@ import type {
|
|
|
36
36
|
PanelOutput,
|
|
37
37
|
ParsedFusionArgs,
|
|
38
38
|
} from "./types.js";
|
|
39
|
+
import {
|
|
40
|
+
extractRunObservation,
|
|
41
|
+
hasStrongPanelAgreement,
|
|
42
|
+
mergeRunObservations,
|
|
43
|
+
} from "./run-observations.js";
|
|
39
44
|
import type { SubagentsTargetParams } from "./subagents-rpc.js";
|
|
40
45
|
|
|
41
46
|
export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
|
|
@@ -94,6 +99,7 @@ export type FusionCommandResult =
|
|
|
94
99
|
interface RunLifecycleSnapshot {
|
|
95
100
|
statusPayload?: unknown;
|
|
96
101
|
resultPayload?: unknown;
|
|
102
|
+
resultIsTerminal: boolean;
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
export class FusionOrchestrator {
|
|
@@ -108,6 +114,7 @@ export class FusionOrchestrator {
|
|
|
108
114
|
private configWarning: string | undefined;
|
|
109
115
|
private reconcileTimer: NodeJS.Timeout | undefined;
|
|
110
116
|
private reconciling = false;
|
|
117
|
+
private pendingCompletionPayload: unknown;
|
|
111
118
|
|
|
112
119
|
constructor(deps: FusionOrchestratorDeps) {
|
|
113
120
|
this.rpc = deps.rpc;
|
|
@@ -153,42 +160,103 @@ export class FusionOrchestrator {
|
|
|
153
160
|
return { status: "failed", error: message };
|
|
154
161
|
}
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
let run: FusionRun;
|
|
164
|
+
try {
|
|
165
|
+
run = this.runStore.startRun({
|
|
166
|
+
prompt: args.prompt,
|
|
167
|
+
profileName: resolved.name,
|
|
168
|
+
...(args.operationId !== undefined ? { operationId: args.operationId } : {}),
|
|
169
|
+
phase: "panel",
|
|
170
|
+
});
|
|
171
|
+
} catch (error: unknown) {
|
|
172
|
+
if (!(error instanceof FusionRunStoreError)) throw error;
|
|
173
|
+
const active = this.runStore.getActiveRun();
|
|
174
|
+
if (active) {
|
|
175
|
+
this.notify(
|
|
176
|
+
ctx,
|
|
177
|
+
`Fusion run ${active.id} is already active.`,
|
|
178
|
+
"warning",
|
|
179
|
+
);
|
|
180
|
+
return { status: "conflict", activeRunId: active.id };
|
|
181
|
+
}
|
|
182
|
+
return { status: "failed", error: errorMessage(error) };
|
|
183
|
+
}
|
|
161
184
|
this.activeProfile = resolved.profile;
|
|
162
185
|
publishFusionStatus(ctx, run);
|
|
163
186
|
|
|
164
187
|
try {
|
|
165
188
|
const spawnResult = await this.rpc.spawn(
|
|
166
|
-
|
|
189
|
+
buildPanelSpawnParams(resolved.profile, args.prompt),
|
|
167
190
|
);
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
191
|
+
const panelRunId = extractSubagentRunId(spawnResult);
|
|
192
|
+
if (!panelRunId) {
|
|
170
193
|
throw new FusionArgsError(
|
|
171
|
-
"pi-subagents spawn did not return a fusion
|
|
194
|
+
"pi-subagents spawn did not return a fusion panel run ID.",
|
|
172
195
|
);
|
|
173
196
|
}
|
|
174
|
-
const
|
|
197
|
+
const panelAsyncDir = extractSubagentAsyncDir(spawnResult);
|
|
198
|
+
const current = this.runStore.getActiveRun();
|
|
199
|
+
if (!current || current.id !== run.id) {
|
|
200
|
+
await this.stopOrphanedRun(panelRunId);
|
|
201
|
+
const cancelled = this.runStore.getLastRunSummary();
|
|
202
|
+
return cancelled?.id === run.id &&
|
|
203
|
+
cancelled.phase === "cancelled" &&
|
|
204
|
+
cancelled.report
|
|
205
|
+
? { status: "cancelled", run: cancelled, report: cancelled.report }
|
|
206
|
+
: { status: "ignored" };
|
|
207
|
+
}
|
|
175
208
|
const updated = this.runStore.updateRun(run.id, {
|
|
176
|
-
|
|
177
|
-
...(
|
|
209
|
+
panelRunId,
|
|
210
|
+
...(panelAsyncDir ? { panelAsyncDir } : {}),
|
|
178
211
|
});
|
|
179
212
|
publishFusionStatus(ctx, updated);
|
|
180
213
|
this.ensureReconcileLoop();
|
|
181
214
|
this.notify(
|
|
182
215
|
ctx,
|
|
183
|
-
`Fusion ${resolved.name} started (${resolved.profile.panel.length} panelists): "${promptPreview(args.prompt)}" — ${
|
|
216
|
+
`Fusion ${resolved.name} started (${resolved.profile.panel.length} panelists): "${promptPreview(args.prompt)}" — ${panelRunId}`,
|
|
184
217
|
"info",
|
|
185
218
|
);
|
|
186
219
|
return { status: "started", run: updated };
|
|
187
220
|
} catch (error: unknown) {
|
|
221
|
+
const cancelled = this.runStore.getLastRunSummary();
|
|
222
|
+
if (
|
|
223
|
+
cancelled?.id === run.id &&
|
|
224
|
+
cancelled.phase === "cancelled" &&
|
|
225
|
+
cancelled.report
|
|
226
|
+
) {
|
|
227
|
+
return {
|
|
228
|
+
status: "cancelled",
|
|
229
|
+
run: cancelled,
|
|
230
|
+
report: cancelled.report,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
188
233
|
return this.failActiveRun(errorMessage(error));
|
|
189
234
|
}
|
|
190
235
|
}
|
|
191
236
|
|
|
237
|
+
private async stopOrphanedRun(
|
|
238
|
+
runId: string,
|
|
239
|
+
kind: "panel" | "judge" = "panel",
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
try {
|
|
242
|
+
await this.rpc.stop({ id: runId });
|
|
243
|
+
return;
|
|
244
|
+
} catch (stopError: unknown) {
|
|
245
|
+
try {
|
|
246
|
+
await this.rpc.interrupt({ id: runId });
|
|
247
|
+
this.installWarning = `Orphaned ${kind} stop fell back to interrupt for ${runId}: ${errorMessage(stopError)}`;
|
|
248
|
+
return;
|
|
249
|
+
} catch (interruptError: unknown) {
|
|
250
|
+
this.installWarning = `Could not stop orphaned ${kind} run ${runId}: ${errorMessage(interruptError)}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
this.notify(
|
|
254
|
+
this.context,
|
|
255
|
+
this.installWarning ?? `Could not stop orphaned ${kind} run.`,
|
|
256
|
+
"warning",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
192
260
|
async handleSubagentComplete(payload: unknown): Promise<FusionCommandResult> {
|
|
193
261
|
const active = this.runStore.getActiveRun();
|
|
194
262
|
if (!active) return { status: "ignored" };
|
|
@@ -262,6 +330,10 @@ export class FusionOrchestrator {
|
|
|
262
330
|
}
|
|
263
331
|
}
|
|
264
332
|
|
|
333
|
+
if (this.runStore.getActiveRun()?.id !== active.id) {
|
|
334
|
+
return { status: "ignored" };
|
|
335
|
+
}
|
|
336
|
+
|
|
265
337
|
const report = renderCancelledReport({
|
|
266
338
|
run: active,
|
|
267
339
|
method,
|
|
@@ -384,7 +456,12 @@ export class FusionOrchestrator {
|
|
|
384
456
|
private async reconcileActiveRun(
|
|
385
457
|
eventPayload?: unknown,
|
|
386
458
|
): Promise<FusionCommandResult> {
|
|
387
|
-
if (this.reconciling)
|
|
459
|
+
if (this.reconciling) {
|
|
460
|
+
if (eventPayload !== undefined) {
|
|
461
|
+
this.pendingCompletionPayload = eventPayload;
|
|
462
|
+
}
|
|
463
|
+
return { status: "ignored" };
|
|
464
|
+
}
|
|
388
465
|
const active = this.runStore.getActiveRun();
|
|
389
466
|
if (!active) return { status: "ignored" };
|
|
390
467
|
|
|
@@ -402,6 +479,15 @@ export class FusionOrchestrator {
|
|
|
402
479
|
return { status: "ignored" };
|
|
403
480
|
} finally {
|
|
404
481
|
this.reconciling = false;
|
|
482
|
+
const pendingPayload = this.pendingCompletionPayload;
|
|
483
|
+
this.pendingCompletionPayload = undefined;
|
|
484
|
+
if (pendingPayload !== undefined) {
|
|
485
|
+
void this.reconcileActiveRun(pendingPayload).catch((error: unknown) => {
|
|
486
|
+
const message = `Could not reconcile completed fusion run: ${errorMessage(error)}`;
|
|
487
|
+
this.installWarning = message;
|
|
488
|
+
this.notify(this.context, message, "warning");
|
|
489
|
+
});
|
|
490
|
+
}
|
|
405
491
|
}
|
|
406
492
|
}
|
|
407
493
|
|
|
@@ -425,7 +511,7 @@ export class FusionOrchestrator {
|
|
|
425
511
|
const terminalPayload =
|
|
426
512
|
snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
|
|
427
513
|
if (
|
|
428
|
-
!
|
|
514
|
+
!snapshot.resultIsTerminal &&
|
|
429
515
|
!isTerminalSubagentState(extractSubagentState(terminalPayload))
|
|
430
516
|
) {
|
|
431
517
|
return { status: "ignored" };
|
|
@@ -444,10 +530,15 @@ export class FusionOrchestrator {
|
|
|
444
530
|
);
|
|
445
531
|
}
|
|
446
532
|
|
|
533
|
+
const observedPanels = mergePanelObservations(
|
|
534
|
+
extracted,
|
|
535
|
+
snapshot.statusPayload,
|
|
536
|
+
profile,
|
|
537
|
+
);
|
|
447
538
|
const updated = this.storePanelResults(
|
|
448
539
|
active.id,
|
|
449
|
-
|
|
450
|
-
|
|
540
|
+
observedPanels.outputs,
|
|
541
|
+
observedPanels.failures,
|
|
451
542
|
);
|
|
452
543
|
|
|
453
544
|
if (hasJudgeResult(snapshot.resultPayload, profile.panel.length)) {
|
|
@@ -456,12 +547,26 @@ export class FusionOrchestrator {
|
|
|
456
547
|
});
|
|
457
548
|
if (!output.ok) return this.failActiveRun(output.error);
|
|
458
549
|
|
|
550
|
+
const judgeObservation = mergeRunObservations(
|
|
551
|
+
extractRunObservation(
|
|
552
|
+
findStepsArray(snapshot.statusPayload)[profile.panel.length] ??
|
|
553
|
+
snapshot.statusPayload,
|
|
554
|
+
),
|
|
555
|
+
extractRunObservation(
|
|
556
|
+
findResult(snapshot.resultPayload, profile.panel.length) ??
|
|
557
|
+
snapshot.resultPayload,
|
|
558
|
+
),
|
|
559
|
+
);
|
|
560
|
+
const observed = this.runStore.updateRun(updated.id, {
|
|
561
|
+
judgeObservation,
|
|
562
|
+
});
|
|
459
563
|
const report = renderJudgeReport({
|
|
460
|
-
run:
|
|
564
|
+
run: observed,
|
|
461
565
|
judgeOutput: output.output,
|
|
462
|
-
panelOutputs: storedPanelOutputs(
|
|
463
|
-
failures: storedPanelFailures(
|
|
566
|
+
panelOutputs: storedPanelOutputs(observed),
|
|
567
|
+
failures: storedPanelFailures(observed),
|
|
464
568
|
...withJudgeModel(configuredJudgeModel(profile)),
|
|
569
|
+
judgeObservation,
|
|
465
570
|
});
|
|
466
571
|
return this.completeActiveRun(report);
|
|
467
572
|
}
|
|
@@ -469,8 +574,8 @@ export class FusionOrchestrator {
|
|
|
469
574
|
return this.finishPanelCompletion(
|
|
470
575
|
updated,
|
|
471
576
|
profile,
|
|
472
|
-
|
|
473
|
-
|
|
577
|
+
observedPanels.outputs,
|
|
578
|
+
observedPanels.failures,
|
|
474
579
|
{ fallbackJudge: true },
|
|
475
580
|
);
|
|
476
581
|
}
|
|
@@ -489,13 +594,35 @@ export class FusionOrchestrator {
|
|
|
489
594
|
const snapshot = await this.loadRunLifecycle({
|
|
490
595
|
run: active,
|
|
491
596
|
...(active.panelRunId ? { runId: active.panelRunId } : {}),
|
|
492
|
-
...(active.
|
|
597
|
+
...(active.panelAsyncDir
|
|
598
|
+
? { asyncDir: active.panelAsyncDir }
|
|
599
|
+
: active.chainAsyncDir
|
|
600
|
+
? { asyncDir: active.chainAsyncDir }
|
|
601
|
+
: {}),
|
|
493
602
|
eventPayload: payload,
|
|
494
603
|
});
|
|
604
|
+
|
|
605
|
+
if (!active.panelStopReason) {
|
|
606
|
+
const partial = extractPanelResults(snapshot.statusPayload, {
|
|
607
|
+
panel: profile.panel,
|
|
608
|
+
completedOnly: true,
|
|
609
|
+
});
|
|
610
|
+
if (
|
|
611
|
+
partial.ok &&
|
|
612
|
+
shouldStopWhenPanelAgrees(profile, partial.outputs, partial.failures)
|
|
613
|
+
) {
|
|
614
|
+
return this.stopPanelAfterAgreement(
|
|
615
|
+
active,
|
|
616
|
+
partial,
|
|
617
|
+
profile.panel.length,
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
495
622
|
const terminalPayload =
|
|
496
623
|
snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
|
|
497
624
|
if (
|
|
498
|
-
!
|
|
625
|
+
!snapshot.resultIsTerminal &&
|
|
499
626
|
!isTerminalSubagentState(extractSubagentState(terminalPayload))
|
|
500
627
|
) {
|
|
501
628
|
return { status: "ignored" };
|
|
@@ -505,6 +632,10 @@ export class FusionOrchestrator {
|
|
|
505
632
|
snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
|
|
506
633
|
{
|
|
507
634
|
panel: profile.panel,
|
|
635
|
+
limit: profile.panel.length,
|
|
636
|
+
...(active.panelStoppedIndices
|
|
637
|
+
? { stoppedPanelIndices: active.panelStoppedIndices }
|
|
638
|
+
: {}),
|
|
508
639
|
},
|
|
509
640
|
);
|
|
510
641
|
if (!extracted.ok) {
|
|
@@ -513,21 +644,80 @@ export class FusionOrchestrator {
|
|
|
513
644
|
);
|
|
514
645
|
}
|
|
515
646
|
|
|
647
|
+
const observedPanels = mergePanelObservations(
|
|
648
|
+
extracted,
|
|
649
|
+
snapshot.statusPayload,
|
|
650
|
+
profile,
|
|
651
|
+
);
|
|
516
652
|
const updated = this.storePanelResults(
|
|
517
653
|
active.id,
|
|
518
|
-
|
|
519
|
-
|
|
654
|
+
observedPanels.outputs,
|
|
655
|
+
observedPanels.failures,
|
|
520
656
|
);
|
|
521
657
|
|
|
522
658
|
return this.finishPanelCompletion(
|
|
523
659
|
updated,
|
|
524
660
|
profile,
|
|
525
|
-
|
|
526
|
-
|
|
661
|
+
observedPanels.outputs,
|
|
662
|
+
observedPanels.failures,
|
|
527
663
|
{ fallbackJudge: false },
|
|
528
664
|
);
|
|
529
665
|
}
|
|
530
666
|
|
|
667
|
+
private async stopPanelAfterAgreement(
|
|
668
|
+
active: FusionRun,
|
|
669
|
+
partial: ExtractPanelResultsSuccess,
|
|
670
|
+
panelSize: number,
|
|
671
|
+
): Promise<FusionCommandResult> {
|
|
672
|
+
if (!active.panelRunId) return { status: "ignored" };
|
|
673
|
+
|
|
674
|
+
let method: "stop" | "interrupt" = "stop";
|
|
675
|
+
try {
|
|
676
|
+
await this.rpc.stop({ id: active.panelRunId });
|
|
677
|
+
} catch (stopError: unknown) {
|
|
678
|
+
try {
|
|
679
|
+
await this.rpc.interrupt({ id: active.panelRunId });
|
|
680
|
+
method = "interrupt";
|
|
681
|
+
} catch (interruptError: unknown) {
|
|
682
|
+
this.installWarning = `Could not stop panel run ${active.panelRunId} after agreement: ${errorMessage(interruptError)}`;
|
|
683
|
+
this.notify(this.context, this.installWarning, "warning");
|
|
684
|
+
return { status: "ignored" };
|
|
685
|
+
}
|
|
686
|
+
this.installWarning = `Panel stop fell back to interrupt for ${active.panelRunId}: ${errorMessage(stopError)}`;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (this.runStore.getActiveRun()?.id !== active.id) {
|
|
690
|
+
return { status: "ignored" };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const completedIndices = new Set([
|
|
694
|
+
...partial.outputs.map((output) => output.index),
|
|
695
|
+
...partial.failures.map((failure) => failure.index),
|
|
696
|
+
]);
|
|
697
|
+
const panelStoppedIndices = Array.from(
|
|
698
|
+
{ length: panelSize },
|
|
699
|
+
(_, index) => index,
|
|
700
|
+
).filter((index) => !completedIndices.has(index));
|
|
701
|
+
const updated = this.runStore.updateRun(active.id, {
|
|
702
|
+
panelStopReason: "agreement",
|
|
703
|
+
panelStoppedIndices,
|
|
704
|
+
panelOutputs: partial.outputs,
|
|
705
|
+
panelFailures: partial.failures,
|
|
706
|
+
});
|
|
707
|
+
publishFusionStatus(
|
|
708
|
+
this.context,
|
|
709
|
+
updated,
|
|
710
|
+
undefined,
|
|
711
|
+
"stopping after panel agreement",
|
|
712
|
+
);
|
|
713
|
+
this.notify(
|
|
714
|
+
this.context,
|
|
715
|
+
`Panel agreement found; stopping remaining panelists (${method}).`,
|
|
716
|
+
"info",
|
|
717
|
+
);
|
|
718
|
+
return { status: "started", run: updated };
|
|
719
|
+
}
|
|
720
|
+
|
|
531
721
|
private async finishPanelCompletion(
|
|
532
722
|
run: FusionRun,
|
|
533
723
|
profile: FusionProfile,
|
|
@@ -557,6 +747,10 @@ export class FusionOrchestrator {
|
|
|
557
747
|
throw new FusionArgsError(decision.missingRunIdError);
|
|
558
748
|
}
|
|
559
749
|
const judgeAsyncDir = extractSubagentAsyncDir(spawnResult);
|
|
750
|
+
if (this.runStore.getActiveRun()?.id !== run.id) {
|
|
751
|
+
await this.stopOrphanedRun(judgeRunId, "judge");
|
|
752
|
+
return { status: "ignored" };
|
|
753
|
+
}
|
|
560
754
|
const nextRun = this.runStore.updateRun(run.id, {
|
|
561
755
|
phase: "judge",
|
|
562
756
|
judgeRunId,
|
|
@@ -589,7 +783,7 @@ export class FusionOrchestrator {
|
|
|
589
783
|
const terminalPayload =
|
|
590
784
|
snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
|
|
591
785
|
if (
|
|
592
|
-
!
|
|
786
|
+
!snapshot.resultIsTerminal &&
|
|
593
787
|
!isTerminalSubagentState(extractSubagentState(terminalPayload))
|
|
594
788
|
) {
|
|
595
789
|
return { status: "ignored" };
|
|
@@ -600,16 +794,29 @@ export class FusionOrchestrator {
|
|
|
600
794
|
);
|
|
601
795
|
if (!output.ok) return this.failActiveRun(output.error);
|
|
602
796
|
|
|
797
|
+
const judgeModel = this.activeProfile
|
|
798
|
+
? configuredJudgeModel(this.activeProfile)
|
|
799
|
+
: undefined;
|
|
800
|
+
const judgeObservation = mergeRunObservations(
|
|
801
|
+
extractRunObservation(
|
|
802
|
+
findStepsArray(snapshot.statusPayload)[0] ?? snapshot.statusPayload,
|
|
803
|
+
),
|
|
804
|
+
extractRunObservation(
|
|
805
|
+
findResult(
|
|
806
|
+
snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
|
|
807
|
+
) ?? snapshot.resultPayload,
|
|
808
|
+
),
|
|
809
|
+
);
|
|
810
|
+
const observed = this.runStore.updateRun(active.id, {
|
|
811
|
+
judgeObservation,
|
|
812
|
+
});
|
|
603
813
|
const report = renderJudgeReport({
|
|
604
|
-
run:
|
|
814
|
+
run: observed,
|
|
605
815
|
judgeOutput: output.output,
|
|
606
|
-
panelOutputs: storedPanelOutputs(
|
|
607
|
-
failures: storedPanelFailures(
|
|
608
|
-
...withJudgeModel(
|
|
609
|
-
|
|
610
|
-
? configuredJudgeModel(this.activeProfile)
|
|
611
|
-
: undefined,
|
|
612
|
-
),
|
|
816
|
+
panelOutputs: storedPanelOutputs(observed),
|
|
817
|
+
failures: storedPanelFailures(observed),
|
|
818
|
+
...withJudgeModel(judgeModel),
|
|
819
|
+
judgeObservation,
|
|
613
820
|
});
|
|
614
821
|
return this.completeActiveRun(report);
|
|
615
822
|
}
|
|
@@ -645,7 +852,11 @@ export class FusionOrchestrator {
|
|
|
645
852
|
|
|
646
853
|
const eventHasResults = hasResultsArray(input.eventPayload);
|
|
647
854
|
if (eventPayloadMatches && eventHasResults) {
|
|
648
|
-
return {
|
|
855
|
+
return {
|
|
856
|
+
statusPayload,
|
|
857
|
+
resultPayload: input.eventPayload,
|
|
858
|
+
resultIsTerminal: true,
|
|
859
|
+
};
|
|
649
860
|
}
|
|
650
861
|
|
|
651
862
|
const artifactResult = readSubagentResultArtifact({
|
|
@@ -653,22 +864,43 @@ export class FusionOrchestrator {
|
|
|
653
864
|
...(input.asyncDir ? { asyncDir: input.asyncDir } : {}),
|
|
654
865
|
});
|
|
655
866
|
if (hasResultsArray(artifactResult)) {
|
|
656
|
-
return {
|
|
867
|
+
return {
|
|
868
|
+
statusPayload,
|
|
869
|
+
resultPayload: artifactResult,
|
|
870
|
+
resultIsTerminal: true,
|
|
871
|
+
};
|
|
657
872
|
}
|
|
658
873
|
|
|
659
874
|
if (hasResultsArray(statusPayload)) {
|
|
660
|
-
|
|
875
|
+
const resultIsTerminal =
|
|
876
|
+
eventPayloadMatches ||
|
|
877
|
+
isTerminalSubagentState(extractSubagentState(statusPayload));
|
|
878
|
+
return {
|
|
879
|
+
statusPayload,
|
|
880
|
+
resultPayload: statusPayload,
|
|
881
|
+
resultIsTerminal,
|
|
882
|
+
};
|
|
661
883
|
}
|
|
662
884
|
|
|
663
885
|
if (isTerminalSubagentState(extractSubagentState(statusPayload))) {
|
|
664
|
-
return {
|
|
886
|
+
return {
|
|
887
|
+
statusPayload,
|
|
888
|
+
resultPayload: statusPayload,
|
|
889
|
+
resultIsTerminal: true,
|
|
890
|
+
};
|
|
665
891
|
}
|
|
666
892
|
|
|
667
893
|
if (eventPayloadMatches) {
|
|
668
|
-
return {
|
|
894
|
+
return {
|
|
895
|
+
statusPayload,
|
|
896
|
+
resultPayload: input.eventPayload,
|
|
897
|
+
resultIsTerminal: isTerminalSubagentState(
|
|
898
|
+
extractSubagentState(input.eventPayload),
|
|
899
|
+
),
|
|
900
|
+
};
|
|
669
901
|
}
|
|
670
902
|
|
|
671
|
-
return { statusPayload };
|
|
903
|
+
return { statusPayload, resultIsTerminal: false };
|
|
672
904
|
}
|
|
673
905
|
|
|
674
906
|
private storePanelResults(
|
|
@@ -747,7 +979,11 @@ export class FusionOrchestrator {
|
|
|
747
979
|
private ensureReconcileLoop(): void {
|
|
748
980
|
if (this.reconcileTimer) return;
|
|
749
981
|
this.reconcileTimer = setInterval(() => {
|
|
750
|
-
void this.reconcileActiveRun()
|
|
982
|
+
void this.reconcileActiveRun().catch((error: unknown) => {
|
|
983
|
+
const message = `Could not reconcile fusion run: ${errorMessage(error)}`;
|
|
984
|
+
this.installWarning = message;
|
|
985
|
+
this.notify(this.context, message, "warning");
|
|
986
|
+
});
|
|
751
987
|
}, RECONCILE_INTERVAL_MS);
|
|
752
988
|
this.reconcileTimer.unref?.();
|
|
753
989
|
}
|
|
@@ -895,7 +1131,9 @@ function formatFusionStatusReport(input: {
|
|
|
895
1131
|
else if (input.active.panelRunId)
|
|
896
1132
|
lines.push(`Panel run: ${input.active.panelRunId}`);
|
|
897
1133
|
if (input.active.judgeRunId) {
|
|
898
|
-
lines.push(
|
|
1134
|
+
lines.push(
|
|
1135
|
+
`${input.active.chainRunId ? "Fallback judge run" : "Judge run"}: ${input.active.judgeRunId}`,
|
|
1136
|
+
);
|
|
899
1137
|
}
|
|
900
1138
|
lines.push(
|
|
901
1139
|
`Progress: ${input.progress ? formatProgressCounts(input.progress) : "unknown"}`,
|
|
@@ -912,7 +1150,9 @@ function formatFusionStatusReport(input: {
|
|
|
912
1150
|
else if (input.last.panelRunId)
|
|
913
1151
|
lines.push(`Panel run: ${input.last.panelRunId}`);
|
|
914
1152
|
if (input.last.judgeRunId) {
|
|
915
|
-
lines.push(
|
|
1153
|
+
lines.push(
|
|
1154
|
+
`${input.last.chainRunId ? "Fallback judge run" : "Judge run"}: ${input.last.judgeRunId}`,
|
|
1155
|
+
);
|
|
916
1156
|
}
|
|
917
1157
|
} else {
|
|
918
1158
|
lines.push("State: idle");
|
|
@@ -967,7 +1207,10 @@ function activeRunId(run: FusionRun | undefined): string | undefined {
|
|
|
967
1207
|
|
|
968
1208
|
function activeAsyncDir(run: FusionRun | undefined): string | undefined {
|
|
969
1209
|
if (!run) return undefined;
|
|
970
|
-
|
|
1210
|
+
if (run.phase === "judge") return run.judgeAsyncDir;
|
|
1211
|
+
return run.phase === "panel"
|
|
1212
|
+
? (run.panelAsyncDir ?? run.chainAsyncDir)
|
|
1213
|
+
: run.chainAsyncDir;
|
|
971
1214
|
}
|
|
972
1215
|
|
|
973
1216
|
function buildFusionStatusDetails(
|
|
@@ -1003,22 +1246,28 @@ function buildFusionStatusDetails(
|
|
|
1003
1246
|
const step = panelSteps[index];
|
|
1004
1247
|
const model = configuredPanelModel(member);
|
|
1005
1248
|
const activity = describeStepActivity(step);
|
|
1249
|
+
const metrics = describeStepMetrics(step);
|
|
1006
1250
|
return {
|
|
1007
1251
|
label: member.label,
|
|
1008
1252
|
...(member.role ? { role: member.role } : {}),
|
|
1009
1253
|
...(model ? { model } : {}),
|
|
1010
1254
|
status: describePanelStatus(step),
|
|
1011
|
-
...(activity
|
|
1255
|
+
...([activity, metrics].filter(Boolean).length > 0
|
|
1256
|
+
? { activity: [activity, metrics].filter(Boolean).join(" · ") }
|
|
1257
|
+
: {}),
|
|
1012
1258
|
};
|
|
1013
1259
|
});
|
|
1014
1260
|
details.panelists = panelists;
|
|
1015
1261
|
|
|
1016
1262
|
const judgeActivity = describeStepActivity(steps[profile.panel.length]);
|
|
1263
|
+
const judgeMetrics = describeStepMetrics(steps[profile.panel.length]);
|
|
1017
1264
|
details.judge = {
|
|
1018
1265
|
label: "Judge",
|
|
1019
1266
|
...(judgeModel ? { model: judgeModel } : {}),
|
|
1020
1267
|
status: describeChainJudgeStatus(steps[profile.panel.length], panelists),
|
|
1021
|
-
...(judgeActivity
|
|
1268
|
+
...([judgeActivity, judgeMetrics].filter(Boolean).length > 0
|
|
1269
|
+
? { activity: [judgeActivity, judgeMetrics].filter(Boolean).join(" · ") }
|
|
1270
|
+
: {}),
|
|
1022
1271
|
};
|
|
1023
1272
|
return details;
|
|
1024
1273
|
}
|
|
@@ -1167,6 +1416,19 @@ function describeStepActivity(step: unknown): string | undefined {
|
|
|
1167
1416
|
return recentOutput || undefined;
|
|
1168
1417
|
}
|
|
1169
1418
|
|
|
1419
|
+
function describeStepMetrics(step: unknown): string | undefined {
|
|
1420
|
+
const observation = extractRunObservation(step);
|
|
1421
|
+
const metrics = [
|
|
1422
|
+
observation.durationMs !== undefined
|
|
1423
|
+
? `${(observation.durationMs / 1000).toFixed(1)}s`
|
|
1424
|
+
: undefined,
|
|
1425
|
+
observation.usage?.costUsd !== undefined
|
|
1426
|
+
? `$${observation.usage.costUsd.toFixed(4)}`
|
|
1427
|
+
: undefined,
|
|
1428
|
+
].filter((value): value is string => Boolean(value));
|
|
1429
|
+
return metrics.length > 0 ? metrics.join(", ") : undefined;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1170
1432
|
function summarizeActivityArg(value: string): string {
|
|
1171
1433
|
const parts = value.split("/").filter(Boolean);
|
|
1172
1434
|
if (parts.length >= 3) return parts.slice(-3).join("/");
|
|
@@ -1310,6 +1572,71 @@ function extractArtifactPath(
|
|
|
1310
1572
|
return undefined;
|
|
1311
1573
|
}
|
|
1312
1574
|
|
|
1575
|
+
function mergePanelObservations(
|
|
1576
|
+
result: ExtractPanelResultsSuccess,
|
|
1577
|
+
statusPayload: unknown,
|
|
1578
|
+
profile: FusionProfile,
|
|
1579
|
+
): ExtractPanelResultsSuccess {
|
|
1580
|
+
const status = extractPanelResults(statusPayload, {
|
|
1581
|
+
panel: profile.panel,
|
|
1582
|
+
completedOnly: true,
|
|
1583
|
+
limit: profile.panel.length,
|
|
1584
|
+
});
|
|
1585
|
+
if (!status.ok) return result;
|
|
1586
|
+
|
|
1587
|
+
const observations = new Map<number, PanelOutput["observation"]>();
|
|
1588
|
+
for (const output of status.outputs) {
|
|
1589
|
+
observations.set(output.index, output.observation);
|
|
1590
|
+
}
|
|
1591
|
+
for (const failure of status.failures) {
|
|
1592
|
+
observations.set(failure.index, failure.observation);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
return {
|
|
1596
|
+
...result,
|
|
1597
|
+
outputs: result.outputs.map((output) =>
|
|
1598
|
+
withMergedObservation(output, observations.get(output.index)),
|
|
1599
|
+
),
|
|
1600
|
+
failures: result.failures.map((failure) =>
|
|
1601
|
+
withMergedObservation(failure, observations.get(failure.index)),
|
|
1602
|
+
),
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function withMergedObservation<T extends PanelOutput | FailedPanelSummary>(
|
|
1607
|
+
item: T,
|
|
1608
|
+
statusObservation: PanelOutput["observation"] | undefined,
|
|
1609
|
+
): T {
|
|
1610
|
+
const observation = mergeRunObservations(statusObservation, item.observation);
|
|
1611
|
+
return hasObservationData(observation) ? { ...item, observation } : item;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
function hasObservationData(observation: PanelOutput["observation"]): boolean {
|
|
1615
|
+
return Boolean(
|
|
1616
|
+
observation &&
|
|
1617
|
+
(observation.model ||
|
|
1618
|
+
observation.durationMs !== undefined ||
|
|
1619
|
+
observation.usage ||
|
|
1620
|
+
observation.attempts ||
|
|
1621
|
+
observation.providerFailures),
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
function shouldStopWhenPanelAgrees(
|
|
1626
|
+
profile: FusionProfile,
|
|
1627
|
+
outputs: readonly PanelOutput[],
|
|
1628
|
+
failures: readonly FailedPanelSummary[],
|
|
1629
|
+
): boolean {
|
|
1630
|
+
return (
|
|
1631
|
+
profile.stopWhenPanelAgrees === true &&
|
|
1632
|
+
hasStrongPanelAgreement(
|
|
1633
|
+
outputs,
|
|
1634
|
+
outputs.length + failures.length,
|
|
1635
|
+
profile.panel.length,
|
|
1636
|
+
)
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1313
1640
|
function storedPanelOutputs(run: FusionRun): readonly PanelOutput[] {
|
|
1314
1641
|
return run.panelOutputs ?? [];
|
|
1315
1642
|
}
|