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