@alexeiled/pi-fusion 0.6.2 → 0.8.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 +10 -7
- package/agents/fusion-composer.md +3 -0
- package/agents/fusion-judge.md +3 -0
- package/agents/fusion-panelist.md +3 -0
- package/docs/user-guide.md +32 -11
- package/package.json +1 -1
- package/skills/fusion-review/SKILL.md +2 -1
- package/src/caller-contract.ts +80 -0
- package/src/config.ts +58 -2
- package/src/fusion-args.ts +29 -2
- package/src/fusion-rpc.ts +88 -13
- package/src/index.ts +33 -5
- package/src/lifecycle-reconcile.ts +445 -0
- package/src/orchestrator.ts +562 -129
- package/src/panel-completion.ts +105 -7
- package/src/panel-quorum.ts +22 -0
- package/src/report.ts +105 -3
- package/src/result-extract.ts +84 -7
- package/src/run-builder.ts +141 -15
- package/src/run-store.ts +377 -11
- package/src/status.ts +1 -1
- package/src/types.ts +89 -0
package/src/run-store.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type {
|
|
3
3
|
FusionPhase,
|
|
4
|
+
FusionProfileSnapshot,
|
|
5
|
+
FusionRecoveryState,
|
|
4
6
|
FusionRun,
|
|
5
7
|
ModelAttempt,
|
|
6
8
|
PanelDecision,
|
|
@@ -24,6 +26,10 @@ export type FusionRunSummary = Omit<
|
|
|
24
26
|
| "prompt"
|
|
25
27
|
| "profileName"
|
|
26
28
|
| "operationId"
|
|
29
|
+
| "outputContract"
|
|
30
|
+
| "completionQuality"
|
|
31
|
+
| "effectiveTimeouts"
|
|
32
|
+
| "recovery"
|
|
27
33
|
| "phase"
|
|
28
34
|
| "createdAt"
|
|
29
35
|
| "updatedAt"
|
|
@@ -43,6 +49,11 @@ export interface FusionRunStartInput {
|
|
|
43
49
|
inlinePanel?: string[];
|
|
44
50
|
baseProfileName?: string;
|
|
45
51
|
operationId?: string;
|
|
52
|
+
outputContract?: FusionRun["outputContract"];
|
|
53
|
+
minimumSuccessfulPanelists?: FusionRun["minimumSuccessfulPanelists"];
|
|
54
|
+
profileSnapshot?: FusionRun["profileSnapshot"];
|
|
55
|
+
timeoutOverrides?: FusionRun["timeoutOverrides"];
|
|
56
|
+
effectiveTimeouts?: FusionRun["effectiveTimeouts"];
|
|
46
57
|
phase?: Exclude<FusionPhase, FusionTerminalPhase>;
|
|
47
58
|
createdAt?: number;
|
|
48
59
|
}
|
|
@@ -58,8 +69,12 @@ export interface FusionRunPatch {
|
|
|
58
69
|
judgeRunId?: string;
|
|
59
70
|
judgeAsyncDir?: string;
|
|
60
71
|
judgeObservation?: FusionRun["judgeObservation"];
|
|
72
|
+
completionQuality?: FusionRun["completionQuality"];
|
|
61
73
|
panelOutputs?: FusionRun["panelOutputs"];
|
|
62
74
|
panelFailures?: FusionRun["panelFailures"];
|
|
75
|
+
recovery?: FusionRun["recovery"];
|
|
76
|
+
/** `null` clears an intent once the RPC returned its durable remote ID. */
|
|
77
|
+
spawnIntent?: FusionRun["spawnIntent"] | null;
|
|
63
78
|
report?: string;
|
|
64
79
|
error?: string;
|
|
65
80
|
updatedAt?: number;
|
|
@@ -69,6 +84,8 @@ export interface FusionRunTransitionPatch {
|
|
|
69
84
|
chainRunId?: string;
|
|
70
85
|
panelRunId?: string;
|
|
71
86
|
judgeRunId?: string;
|
|
87
|
+
recovery?: FusionRun["recovery"];
|
|
88
|
+
spawnIntent?: FusionRun["spawnIntent"];
|
|
72
89
|
report?: string;
|
|
73
90
|
error?: string;
|
|
74
91
|
updatedAt?: number;
|
|
@@ -105,6 +122,7 @@ export class FusionRunStore {
|
|
|
105
122
|
private readonly now: () => number;
|
|
106
123
|
private readonly idFactory: () => string;
|
|
107
124
|
private readonly persistence: FusionRunStorePersistence | undefined;
|
|
125
|
+
private restoreError: string | undefined;
|
|
108
126
|
|
|
109
127
|
constructor(options: FusionRunStoreOptions = {}) {
|
|
110
128
|
this.now = options.now ?? Date.now;
|
|
@@ -132,6 +150,11 @@ export class FusionRunStore {
|
|
|
132
150
|
return runId ? this.getRunById(runId) : undefined;
|
|
133
151
|
}
|
|
134
152
|
|
|
153
|
+
/** A corrupt newest snapshot must never revive an older active run. */
|
|
154
|
+
getRestoreError(): string | undefined {
|
|
155
|
+
return this.restoreError;
|
|
156
|
+
}
|
|
157
|
+
|
|
135
158
|
startRun(input: FusionRunStartInput): FusionRun {
|
|
136
159
|
if (this.activeRun) {
|
|
137
160
|
throw new FusionRunStoreError(
|
|
@@ -162,13 +185,28 @@ export class FusionRunStore {
|
|
|
162
185
|
...(input.operationId !== undefined
|
|
163
186
|
? { operationId: input.operationId }
|
|
164
187
|
: {}),
|
|
188
|
+
...(input.outputContract !== undefined
|
|
189
|
+
? { outputContract: input.outputContract }
|
|
190
|
+
: {}),
|
|
191
|
+
...(input.minimumSuccessfulPanelists !== undefined
|
|
192
|
+
? { minimumSuccessfulPanelists: input.minimumSuccessfulPanelists }
|
|
193
|
+
: {}),
|
|
194
|
+
...(input.profileSnapshot !== undefined
|
|
195
|
+
? { profileSnapshot: cloneProfileSnapshot(input.profileSnapshot) }
|
|
196
|
+
: {}),
|
|
197
|
+
...(input.timeoutOverrides !== undefined
|
|
198
|
+
? { timeoutOverrides: { ...input.timeoutOverrides } }
|
|
199
|
+
: {}),
|
|
200
|
+
...(input.effectiveTimeouts !== undefined
|
|
201
|
+
? { effectiveTimeouts: { ...input.effectiveTimeouts } }
|
|
202
|
+
: {}),
|
|
165
203
|
phase: input.phase ?? "chain",
|
|
166
204
|
createdAt,
|
|
167
205
|
updatedAt: createdAt,
|
|
168
206
|
};
|
|
207
|
+
this.persistRun(run);
|
|
169
208
|
this.activeRun = run;
|
|
170
209
|
this.rememberRun(run);
|
|
171
|
-
this.persistRun(run);
|
|
172
210
|
return cloneRun(run);
|
|
173
211
|
}
|
|
174
212
|
|
|
@@ -216,11 +254,26 @@ export class FusionRunStore {
|
|
|
216
254
|
restoreFromEntries(
|
|
217
255
|
entries: readonly unknown[],
|
|
218
256
|
): FusionRunSummary | undefined {
|
|
219
|
-
|
|
257
|
+
this.restoreError = undefined;
|
|
220
258
|
this.runsById.clear();
|
|
221
259
|
this.runIdsByOperationId.clear();
|
|
222
|
-
for (const state of states) this.rememberRun(state);
|
|
223
260
|
|
|
261
|
+
const latestPersisted = lastFusionRunEnvelope(entries);
|
|
262
|
+
if (
|
|
263
|
+
latestPersisted &&
|
|
264
|
+
(!isFusionRunEntry(latestPersisted) || !isFusionRunState(latestPersisted.data))
|
|
265
|
+
) {
|
|
266
|
+
// History readers may skip malformed entries, but restore must not adopt
|
|
267
|
+
// an older active state after a newer snapshot was corrupted.
|
|
268
|
+
this.activeRun = undefined;
|
|
269
|
+
this.lastRunSummary = undefined;
|
|
270
|
+
this.restoreError =
|
|
271
|
+
"Latest persisted fusion run snapshot is invalid; refusing stale active-run recovery.";
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const states = readFusionRunStates(entries);
|
|
276
|
+
for (const state of states) this.rememberRun(state);
|
|
224
277
|
const latestState = states.at(-1);
|
|
225
278
|
const summary = readLastFusionRunSummary(entries);
|
|
226
279
|
this.activeRun =
|
|
@@ -341,12 +394,20 @@ function applyPatch(
|
|
|
341
394
|
if (patch.judgeObservation !== undefined) {
|
|
342
395
|
updated.judgeObservation = cloneObservation(patch.judgeObservation);
|
|
343
396
|
}
|
|
397
|
+
if (patch.completionQuality !== undefined) {
|
|
398
|
+
updated.completionQuality = patch.completionQuality;
|
|
399
|
+
}
|
|
344
400
|
if (patch.panelOutputs !== undefined) {
|
|
345
401
|
updated.panelOutputs = clonePanelOutputs(patch.panelOutputs);
|
|
346
402
|
}
|
|
347
403
|
if (patch.panelFailures !== undefined) {
|
|
348
404
|
updated.panelFailures = clonePanelFailures(patch.panelFailures);
|
|
349
405
|
}
|
|
406
|
+
if (patch.recovery !== undefined) updated.recovery = cloneRecovery(patch.recovery);
|
|
407
|
+
if (patch.spawnIntent === null) delete updated.spawnIntent;
|
|
408
|
+
else if (patch.spawnIntent !== undefined) {
|
|
409
|
+
updated.spawnIntent = cloneSpawnIntent(patch.spawnIntent);
|
|
410
|
+
}
|
|
350
411
|
if (patch.report !== undefined) updated.report = patch.report;
|
|
351
412
|
if (patch.error !== undefined) updated.error = patch.error;
|
|
352
413
|
return updated;
|
|
@@ -366,6 +427,10 @@ function applyTransitionPatch(
|
|
|
366
427
|
if (patch.chainRunId !== undefined) updated.chainRunId = patch.chainRunId;
|
|
367
428
|
if (patch.panelRunId !== undefined) updated.panelRunId = patch.panelRunId;
|
|
368
429
|
if (patch.judgeRunId !== undefined) updated.judgeRunId = patch.judgeRunId;
|
|
430
|
+
if (patch.recovery !== undefined) updated.recovery = cloneRecovery(patch.recovery);
|
|
431
|
+
if (patch.spawnIntent !== undefined) {
|
|
432
|
+
updated.spawnIntent = cloneSpawnIntent(patch.spawnIntent);
|
|
433
|
+
}
|
|
369
434
|
if (patch.report !== undefined) updated.report = patch.report;
|
|
370
435
|
if (patch.error !== undefined) updated.error = patch.error;
|
|
371
436
|
return updated;
|
|
@@ -379,6 +444,16 @@ function toRunSummary(
|
|
|
379
444
|
prompt: run.prompt,
|
|
380
445
|
profileName: run.profileName,
|
|
381
446
|
...(run.operationId !== undefined ? { operationId: run.operationId } : {}),
|
|
447
|
+
...(run.outputContract !== undefined
|
|
448
|
+
? { outputContract: run.outputContract }
|
|
449
|
+
: {}),
|
|
450
|
+
...(run.completionQuality !== undefined
|
|
451
|
+
? { completionQuality: run.completionQuality }
|
|
452
|
+
: {}),
|
|
453
|
+
...(run.effectiveTimeouts !== undefined
|
|
454
|
+
? { effectiveTimeouts: { ...run.effectiveTimeouts } }
|
|
455
|
+
: {}),
|
|
456
|
+
...(run.recovery !== undefined ? { recovery: cloneRecovery(run.recovery) } : {}),
|
|
382
457
|
phase: run.phase,
|
|
383
458
|
createdAt: run.createdAt,
|
|
384
459
|
updatedAt: run.updatedAt,
|
|
@@ -404,6 +479,24 @@ function cloneRun(run: FusionRun): FusionRun {
|
|
|
404
479
|
? { baseProfileName: run.baseProfileName }
|
|
405
480
|
: {}),
|
|
406
481
|
...(run.operationId !== undefined ? { operationId: run.operationId } : {}),
|
|
482
|
+
...(run.outputContract !== undefined
|
|
483
|
+
? { outputContract: run.outputContract }
|
|
484
|
+
: {}),
|
|
485
|
+
...(run.minimumSuccessfulPanelists !== undefined
|
|
486
|
+
? { minimumSuccessfulPanelists: run.minimumSuccessfulPanelists }
|
|
487
|
+
: {}),
|
|
488
|
+
...(run.profileSnapshot !== undefined
|
|
489
|
+
? { profileSnapshot: cloneProfileSnapshot(run.profileSnapshot) }
|
|
490
|
+
: {}),
|
|
491
|
+
...(run.timeoutOverrides !== undefined
|
|
492
|
+
? { timeoutOverrides: { ...run.timeoutOverrides } }
|
|
493
|
+
: {}),
|
|
494
|
+
...(run.effectiveTimeouts !== undefined
|
|
495
|
+
? { effectiveTimeouts: { ...run.effectiveTimeouts } }
|
|
496
|
+
: {}),
|
|
497
|
+
...(run.completionQuality !== undefined
|
|
498
|
+
? { completionQuality: run.completionQuality }
|
|
499
|
+
: {}),
|
|
407
500
|
phase: run.phase,
|
|
408
501
|
createdAt: run.createdAt,
|
|
409
502
|
updatedAt: run.updatedAt,
|
|
@@ -434,6 +527,10 @@ function cloneRun(run: FusionRun): FusionRun {
|
|
|
434
527
|
...(run.panelFailures !== undefined
|
|
435
528
|
? { panelFailures: clonePanelFailures(run.panelFailures) }
|
|
436
529
|
: {}),
|
|
530
|
+
...(run.recovery !== undefined ? { recovery: cloneRecovery(run.recovery) } : {}),
|
|
531
|
+
...(run.spawnIntent !== undefined
|
|
532
|
+
? { spawnIntent: cloneSpawnIntent(run.spawnIntent) }
|
|
533
|
+
: {}),
|
|
437
534
|
...(run.report !== undefined ? { report: run.report } : {}),
|
|
438
535
|
...(run.error !== undefined ? { error: run.error } : {}),
|
|
439
536
|
};
|
|
@@ -443,17 +540,30 @@ function cloneRunSummary(summary: FusionRunSummary): FusionRunSummary {
|
|
|
443
540
|
return toRunSummary(summary);
|
|
444
541
|
}
|
|
445
542
|
|
|
446
|
-
function
|
|
447
|
-
|
|
448
|
-
|
|
543
|
+
function lastFusionRunEnvelope(entries: readonly unknown[]): unknown {
|
|
544
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
545
|
+
const entry = entries[index];
|
|
546
|
+
// Locate the newest entry by its envelope before inspecting data. A
|
|
547
|
+
// malformed data field (or its absence) must not revive an older run.
|
|
548
|
+
if (isFusionRunEnvelope(entry)) return entry;
|
|
549
|
+
}
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isFusionRunEnvelope(value: unknown): value is Record<string, unknown> {
|
|
449
554
|
return (
|
|
450
555
|
isRecord(value) &&
|
|
451
556
|
value.type === "custom" &&
|
|
452
|
-
value.customType === FUSION_RUN_ENTRY_TYPE
|
|
453
|
-
"data" in value
|
|
557
|
+
value.customType === FUSION_RUN_ENTRY_TYPE
|
|
454
558
|
);
|
|
455
559
|
}
|
|
456
560
|
|
|
561
|
+
function isFusionRunEntry(
|
|
562
|
+
value: unknown,
|
|
563
|
+
): value is { type: "custom"; customType: string; data: unknown } {
|
|
564
|
+
return isFusionRunEnvelope(value) && "data" in value;
|
|
565
|
+
}
|
|
566
|
+
|
|
457
567
|
function isFusionRunState(value: unknown): value is FusionRun {
|
|
458
568
|
if (!isRecord(value)) return false;
|
|
459
569
|
if (!isNonEmptyString(value.id)) return false;
|
|
@@ -462,6 +572,53 @@ function isFusionRunState(value: unknown): value is FusionRun {
|
|
|
462
572
|
if (value.operationId !== undefined && !isNonEmptyString(value.operationId)) {
|
|
463
573
|
return false;
|
|
464
574
|
}
|
|
575
|
+
if (
|
|
576
|
+
value.outputContract !== undefined &&
|
|
577
|
+
value.outputContract !== "plan-review-v1"
|
|
578
|
+
) {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
if (
|
|
582
|
+
value.minimumSuccessfulPanelists !== undefined &&
|
|
583
|
+
value.minimumSuccessfulPanelists !== "majority" &&
|
|
584
|
+
value.minimumSuccessfulPanelists !== "all" &&
|
|
585
|
+
(!isFiniteNumber(value.minimumSuccessfulPanelists) ||
|
|
586
|
+
!Number.isInteger(value.minimumSuccessfulPanelists) ||
|
|
587
|
+
value.minimumSuccessfulPanelists < 1)
|
|
588
|
+
) {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
if (
|
|
592
|
+
value.profileSnapshot !== undefined &&
|
|
593
|
+
!isProfileSnapshot(value.profileSnapshot)
|
|
594
|
+
) {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
// Snapshots are the canonical quorum record for new runs. Older records may
|
|
598
|
+
// also carry the run-level policy, but it must resolve to the same quorum.
|
|
599
|
+
if (
|
|
600
|
+
value.profileSnapshot !== undefined &&
|
|
601
|
+
value.minimumSuccessfulPanelists !== undefined &&
|
|
602
|
+
resolvePersistedQuorum(
|
|
603
|
+
value.minimumSuccessfulPanelists,
|
|
604
|
+
value.profileSnapshot.panel.length,
|
|
605
|
+
) !== value.profileSnapshot.minimumSuccessfulPanelists
|
|
606
|
+
) {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
if (value.timeoutOverrides !== undefined && !isTimeoutOverrides(value.timeoutOverrides)) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
if (value.effectiveTimeouts !== undefined && !isEffectiveTimeouts(value.effectiveTimeouts)) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
if (
|
|
616
|
+
value.completionQuality !== undefined &&
|
|
617
|
+
value.completionQuality !== "complete" &&
|
|
618
|
+
value.completionQuality !== "partial"
|
|
619
|
+
) {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
465
622
|
if (
|
|
466
623
|
value.inlinePanel !== undefined &&
|
|
467
624
|
(!Array.isArray(value.inlinePanel) ||
|
|
@@ -539,19 +696,51 @@ function isFusionRunState(value: unknown): value is FusionRun {
|
|
|
539
696
|
) {
|
|
540
697
|
return false;
|
|
541
698
|
}
|
|
699
|
+
if (value.recovery !== undefined && !isRecoveryState(value.recovery)) {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
if (value.spawnIntent !== undefined && !isSpawnIntent(value.spawnIntent)) {
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
542
705
|
if (value.report !== undefined && typeof value.report !== "string") {
|
|
543
706
|
return false;
|
|
544
707
|
}
|
|
545
708
|
if (value.error !== undefined && typeof value.error !== "string") {
|
|
546
709
|
return false;
|
|
547
710
|
}
|
|
548
|
-
return
|
|
711
|
+
return (
|
|
712
|
+
value.profileSnapshot === undefined ||
|
|
713
|
+
validateFusionRunPanelSlots(value, value.profileSnapshot.panel.length) ===
|
|
714
|
+
undefined
|
|
715
|
+
);
|
|
549
716
|
}
|
|
550
717
|
|
|
551
718
|
function isFusionRunSummary(value: unknown): value is FusionRunSummary {
|
|
552
719
|
return isFusionRunState(value) && isTerminalPhase(value.phase);
|
|
553
720
|
}
|
|
554
721
|
|
|
722
|
+
function isTimeoutOverrides(value: unknown): boolean {
|
|
723
|
+
if (!isRecord(value)) return false;
|
|
724
|
+
return [
|
|
725
|
+
value.panelistTimeoutMs,
|
|
726
|
+
value.panelTimeoutMs,
|
|
727
|
+
value.panelGraceMs,
|
|
728
|
+
value.judgeTimeoutMs,
|
|
729
|
+
].every((timeout) => timeout === undefined || (isFiniteNumber(timeout) && Number.isInteger(timeout) && timeout > 0));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function isEffectiveTimeouts(value: unknown): boolean {
|
|
733
|
+
return (
|
|
734
|
+
isTimeoutOverrides(value) &&
|
|
735
|
+
isRecord(value) &&
|
|
736
|
+
isFiniteNumber(value.panelistTimeoutMs) &&
|
|
737
|
+
isFiniteNumber(value.panelTimeoutMs) &&
|
|
738
|
+
isFiniteNumber(value.panelGraceMs) &&
|
|
739
|
+
isFiniteNumber(value.judgeTimeoutMs) &&
|
|
740
|
+
typeof value.usesLegacyTimeout === "boolean"
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
555
744
|
function isFusionPhase(value: unknown): value is FusionPhase {
|
|
556
745
|
return (
|
|
557
746
|
value === "panel" ||
|
|
@@ -567,6 +756,45 @@ function isTerminalPhase(value: unknown): value is FusionTerminalPhase {
|
|
|
567
756
|
return value === "done" || value === "failed" || value === "cancelled";
|
|
568
757
|
}
|
|
569
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Checks persisted slot references before they are restored or merged. Legacy
|
|
761
|
+
* runs omit a profile snapshot, so their caller supplies the safely resolved
|
|
762
|
+
* profile length during restore.
|
|
763
|
+
*/
|
|
764
|
+
export function validateFusionRunPanelSlots(
|
|
765
|
+
run: Pick<
|
|
766
|
+
FusionRun,
|
|
767
|
+
"panelOutputs" | "panelFailures" | "panelStoppedIndices" | "recovery"
|
|
768
|
+
>,
|
|
769
|
+
panelLength: number,
|
|
770
|
+
): string | undefined {
|
|
771
|
+
const slotGroups: ReadonlyArray<readonly number[] | undefined> = [
|
|
772
|
+
run.panelOutputs?.map((output) => output.index),
|
|
773
|
+
run.panelFailures?.map((failure) => failure.index),
|
|
774
|
+
run.panelStoppedIndices,
|
|
775
|
+
run.recovery?.failedPanelIndices,
|
|
776
|
+
];
|
|
777
|
+
for (const slots of slotGroups) {
|
|
778
|
+
for (const slot of slots ?? []) {
|
|
779
|
+
if (!isPanelSlotIndex(slot) || slot >= panelLength) {
|
|
780
|
+
return `Persisted panel slot ${String(slot)} is outside the configured panel.`;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return undefined;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function resolvePersistedQuorum(
|
|
788
|
+
policy: NonNullable<FusionRun["minimumSuccessfulPanelists"]>,
|
|
789
|
+
panelLength: number,
|
|
790
|
+
): number {
|
|
791
|
+
if (policy === "all") return panelLength;
|
|
792
|
+
if (typeof policy === "number") {
|
|
793
|
+
return panelLength > 1 ? Math.max(2, Math.min(policy, panelLength)) : 1;
|
|
794
|
+
}
|
|
795
|
+
return Math.ceil(panelLength / 2);
|
|
796
|
+
}
|
|
797
|
+
|
|
570
798
|
function isPanelOutputArray(
|
|
571
799
|
value: unknown,
|
|
572
800
|
): value is NonNullable<FusionRun["panelOutputs"]> {
|
|
@@ -578,7 +806,7 @@ function isPanelOutput(
|
|
|
578
806
|
): value is NonNullable<FusionRun["panelOutputs"]>[number] {
|
|
579
807
|
return (
|
|
580
808
|
isRecord(value) &&
|
|
581
|
-
|
|
809
|
+
isPanelSlotIndex(value.index) &&
|
|
582
810
|
isNonEmptyString(value.agent) &&
|
|
583
811
|
typeof value.output === "string" &&
|
|
584
812
|
(value.id === undefined || typeof value.id === "string") &&
|
|
@@ -605,7 +833,7 @@ function isPanelFailure(
|
|
|
605
833
|
): value is NonNullable<FusionRun["panelFailures"]>[number] {
|
|
606
834
|
return (
|
|
607
835
|
isRecord(value) &&
|
|
608
|
-
|
|
836
|
+
isPanelSlotIndex(value.index) &&
|
|
609
837
|
isNonEmptyString(value.agent) &&
|
|
610
838
|
typeof value.summary === "string" &&
|
|
611
839
|
(value.id === undefined || typeof value.id === "string") &&
|
|
@@ -621,6 +849,144 @@ function isPanelFailure(
|
|
|
621
849
|
);
|
|
622
850
|
}
|
|
623
851
|
|
|
852
|
+
function isProfileSnapshot(value: unknown): value is FusionProfileSnapshot {
|
|
853
|
+
if (!isRecord(value) || !Array.isArray(value.panel) || value.panel.length === 0) {
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
if (!value.panel.every(isSnapshotPanelMember) || !isSnapshotJudge(value.judge)) {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
if (
|
|
860
|
+
!isFiniteNumber(value.minimumSuccessfulPanelists) ||
|
|
861
|
+
!Number.isInteger(value.minimumSuccessfulPanelists) ||
|
|
862
|
+
value.minimumSuccessfulPanelists < 1 ||
|
|
863
|
+
value.minimumSuccessfulPanelists > value.panel.length
|
|
864
|
+
) {
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
if (value.context !== undefined && value.context !== "fresh" && value.context !== "fork") {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
if (value.stopWhenPanelAgrees !== undefined && typeof value.stopWhenPanelAgrees !== "boolean") {
|
|
871
|
+
return false;
|
|
872
|
+
}
|
|
873
|
+
if (value.blindPanelLabels !== undefined && typeof value.blindPanelLabels !== "boolean") {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
if (value.synthesis !== undefined && value.synthesis !== "select" && value.synthesis !== "merge") {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
return value.judgeToolBudget === undefined || isSnapshotToolBudget(value.judgeToolBudget);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
function isSnapshotPanelMember(value: unknown): boolean {
|
|
883
|
+
if (!isRecord(value) || !isNonEmptyString(value.id) || !isSnapshotAgent(value.agent)) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
return (
|
|
887
|
+
(value.label === undefined || isNonEmptyString(value.label)) &&
|
|
888
|
+
(value.model === undefined || isNonEmptyString(value.model)) &&
|
|
889
|
+
(value.thinking === undefined || isThinkingLevel(value.thinking)) &&
|
|
890
|
+
(value.role === undefined || typeof value.role === "string") &&
|
|
891
|
+
(value.question === undefined || isNonEmptyString(value.question))
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function isSnapshotJudge(value: unknown): boolean {
|
|
896
|
+
return (
|
|
897
|
+
isRecord(value) &&
|
|
898
|
+
isSnapshotAgent(value.agent) &&
|
|
899
|
+
(value.model === undefined || isNonEmptyString(value.model)) &&
|
|
900
|
+
(value.thinking === undefined || isThinkingLevel(value.thinking))
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function isSnapshotAgent(value: unknown): boolean {
|
|
905
|
+
return (
|
|
906
|
+
isNonEmptyString(value) &&
|
|
907
|
+
/^[^\s.]+(?:\.[^\s.]+)*$/.test(value.trim())
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function isThinkingLevel(value: unknown): boolean {
|
|
912
|
+
return value === "off" || value === "minimal" || value === "low" ||
|
|
913
|
+
value === "medium" || value === "high" || value === "xhigh";
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
function isSnapshotToolBudget(value: unknown): boolean {
|
|
917
|
+
if (!isRecord(value) || (value.soft === undefined && value.hard === undefined)) {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
if (value.soft !== undefined && (!isFiniteNumber(value.soft) || !Number.isInteger(value.soft) || value.soft < 1)) {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
if (value.hard !== undefined && (!isFiniteNumber(value.hard) || !Number.isInteger(value.hard) || value.hard < 1)) {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
if (typeof value.soft === "number" && typeof value.hard === "number" && value.soft > value.hard) {
|
|
927
|
+
return false;
|
|
928
|
+
}
|
|
929
|
+
return value.block === undefined || value.block === "*" ||
|
|
930
|
+
(Array.isArray(value.block) && value.block.length > 0 && value.block.every(isNonEmptyString));
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function cloneProfileSnapshot(snapshot: FusionProfileSnapshot): FusionProfileSnapshot {
|
|
934
|
+
return {
|
|
935
|
+
panel: snapshot.panel.map((member) => ({ ...member })),
|
|
936
|
+
judge: { ...snapshot.judge },
|
|
937
|
+
minimumSuccessfulPanelists: snapshot.minimumSuccessfulPanelists,
|
|
938
|
+
...(snapshot.context !== undefined ? { context: snapshot.context } : {}),
|
|
939
|
+
...(snapshot.stopWhenPanelAgrees !== undefined
|
|
940
|
+
? { stopWhenPanelAgrees: snapshot.stopWhenPanelAgrees }
|
|
941
|
+
: {}),
|
|
942
|
+
...(snapshot.blindPanelLabels !== undefined
|
|
943
|
+
? { blindPanelLabels: snapshot.blindPanelLabels }
|
|
944
|
+
: {}),
|
|
945
|
+
...(snapshot.judgeToolBudget !== undefined
|
|
946
|
+
? {
|
|
947
|
+
judgeToolBudget: {
|
|
948
|
+
...snapshot.judgeToolBudget,
|
|
949
|
+
...(Array.isArray(snapshot.judgeToolBudget.block)
|
|
950
|
+
? { block: [...snapshot.judgeToolBudget.block] }
|
|
951
|
+
: {}),
|
|
952
|
+
},
|
|
953
|
+
}
|
|
954
|
+
: {}),
|
|
955
|
+
...(snapshot.synthesis !== undefined ? { synthesis: snapshot.synthesis } : {}),
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function isRecoveryState(value: unknown): value is FusionRecoveryState {
|
|
960
|
+
return (
|
|
961
|
+
isRecord(value) &&
|
|
962
|
+
value.retryDeferred === true &&
|
|
963
|
+
Array.isArray(value.failedPanelIndices) &&
|
|
964
|
+
value.failedPanelIndices.every(isPanelSlotIndex)
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function isPanelSlotIndex(value: unknown): value is number {
|
|
969
|
+
return isFiniteNumber(value) && Number.isInteger(value) && value >= 0;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function cloneRecovery(recovery: FusionRecoveryState): FusionRecoveryState {
|
|
973
|
+
return { ...recovery, failedPanelIndices: [...recovery.failedPanelIndices] };
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function isSpawnIntent(value: unknown): value is FusionRun["spawnIntent"] {
|
|
977
|
+
return (
|
|
978
|
+
isRecord(value) &&
|
|
979
|
+
(value.stage === "panel" || value.stage === "judge") &&
|
|
980
|
+
isFiniteNumber(value.requestedAt)
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function cloneSpawnIntent(
|
|
985
|
+
intent: NonNullable<FusionRun["spawnIntent"]>,
|
|
986
|
+
): NonNullable<FusionRun["spawnIntent"]> {
|
|
987
|
+
return { ...intent };
|
|
988
|
+
}
|
|
989
|
+
|
|
624
990
|
function clonePanelOutputs(
|
|
625
991
|
outputs: NonNullable<FusionRun["panelOutputs"]>,
|
|
626
992
|
): NonNullable<FusionRun["panelOutputs"]> {
|
package/src/status.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function formatFusionStatusText(
|
|
|
52
52
|
run.phase === "judge" ? run.judgeRunId : (run.chainRunId ?? run.panelRunId);
|
|
53
53
|
const phase = phaseLabel ?? run.phase;
|
|
54
54
|
if (progress) {
|
|
55
|
-
return `fusion:
|
|
55
|
+
return `fusion: ${phase} · ${formatProgressCounts(progress)} · ${run.profileName}`;
|
|
56
56
|
}
|
|
57
57
|
if (activeRunId) {
|
|
58
58
|
return `fusion: ${phase} · ${run.profileName} · ${activeRunId}`;
|