@alexeiled/pi-fusion 0.5.1 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexeiled/pi-fusion",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Stronger answers for hard Pi questions via a parallel model panel + judge, built on pi-subagents",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -21,6 +21,7 @@ import {
21
21
  clearFusionUi,
22
22
  extractFusionProgressCounts,
23
23
  formatProgressCounts,
24
+ isTerminalFusionProgress,
24
25
  publishFusionStatus,
25
26
  type FusionProgressCounts,
26
27
  type FusionUi,
@@ -165,7 +166,9 @@ export class FusionOrchestrator {
165
166
  run = this.runStore.startRun({
166
167
  prompt: args.prompt,
167
168
  profileName: resolved.name,
168
- ...(args.operationId !== undefined ? { operationId: args.operationId } : {}),
169
+ ...(args.operationId !== undefined
170
+ ? { operationId: args.operationId }
171
+ : {}),
169
172
  phase: "panel",
170
173
  });
171
174
  } catch (error: unknown) {
@@ -188,6 +191,8 @@ export class FusionOrchestrator {
188
191
  const spawnResult = await this.rpc.spawn(
189
192
  buildPanelSpawnParams(resolved.profile, args.prompt),
190
193
  );
194
+ const spawnError = extractSubagentFailure(spawnResult);
195
+ if (spawnError) throw new FusionArgsError(spawnError);
191
196
  const panelRunId = extractSubagentRunId(spawnResult);
192
197
  if (!panelRunId) {
193
198
  throw new FusionArgsError(
@@ -517,13 +522,17 @@ export class FusionOrchestrator {
517
522
  return { status: "ignored" };
518
523
  }
519
524
 
520
- const extracted = extractPanelResults(
521
- snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
522
- {
523
- panel: profile.panel,
524
- limit: profile.panel.length,
525
- },
526
- );
525
+ const lifecyclePayload =
526
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
527
+ const lifecycleError = extractSubagentFailure(lifecyclePayload);
528
+ if (!hasLifecycleResults(lifecyclePayload) && lifecycleError) {
529
+ return this.failActiveRun(lifecycleError);
530
+ }
531
+
532
+ const extracted = extractPanelResults(lifecyclePayload, {
533
+ panel: profile.panel,
534
+ limit: profile.panel.length,
535
+ });
527
536
  if (!extracted.ok) {
528
537
  return this.failActiveRun(
529
538
  `${extracted.error.message} (${extracted.error.path})`,
@@ -628,16 +637,20 @@ export class FusionOrchestrator {
628
637
  return { status: "ignored" };
629
638
  }
630
639
 
631
- const extracted = extractPanelResults(
632
- snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
633
- {
634
- panel: profile.panel,
635
- limit: profile.panel.length,
636
- ...(active.panelStoppedIndices
637
- ? { stoppedPanelIndices: active.panelStoppedIndices }
638
- : {}),
639
- },
640
- );
640
+ const lifecyclePayload =
641
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
642
+ const lifecycleError = extractSubagentFailure(lifecyclePayload);
643
+ if (!hasLifecycleResults(lifecyclePayload) && lifecycleError) {
644
+ return this.failActiveRun(lifecycleError);
645
+ }
646
+
647
+ const extracted = extractPanelResults(lifecyclePayload, {
648
+ panel: profile.panel,
649
+ limit: profile.panel.length,
650
+ ...(active.panelStoppedIndices
651
+ ? { stoppedPanelIndices: active.panelStoppedIndices }
652
+ : {}),
653
+ });
641
654
  if (!extracted.ok) {
642
655
  return this.failActiveRun(
643
656
  `${extracted.error.message} (${extracted.error.path})`,
@@ -742,6 +755,8 @@ export class FusionOrchestrator {
742
755
 
743
756
  try {
744
757
  const spawnResult = await this.rpc.spawn(decision.params);
758
+ const spawnError = extractSubagentFailure(spawnResult);
759
+ if (spawnError) throw new FusionArgsError(spawnError);
745
760
  const judgeRunId = extractSubagentRunId(spawnResult);
746
761
  if (!judgeRunId) {
747
762
  throw new FusionArgsError(decision.missingRunIdError);
@@ -789,9 +804,14 @@ export class FusionOrchestrator {
789
804
  return { status: "ignored" };
790
805
  }
791
806
 
792
- const output = extractJudgeOutput(
793
- snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
794
- );
807
+ const lifecyclePayload =
808
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
809
+ const lifecycleError = extractSubagentFailure(lifecyclePayload);
810
+ if (!hasLifecycleResults(lifecyclePayload) && lifecycleError) {
811
+ return this.failActiveRun(lifecycleError);
812
+ }
813
+
814
+ const output = extractJudgeOutput(lifecyclePayload);
795
815
  if (!output.ok) return this.failActiveRun(output.error);
796
816
 
797
817
  const judgeModel = this.activeProfile
@@ -850,12 +870,18 @@ export class FusionOrchestrator {
850
870
  );
851
871
  }
852
872
 
873
+ const statusIsTerminal =
874
+ isTerminalSubagentState(extractSubagentState(statusPayload)) ||
875
+ isTerminalFusionProgress(statusPayload);
876
+ const eventIsTerminal =
877
+ isTerminalSubagentState(extractSubagentState(input.eventPayload)) ||
878
+ isTerminalFusionProgress(input.eventPayload);
853
879
  const eventHasResults = hasResultsArray(input.eventPayload);
854
880
  if (eventPayloadMatches && eventHasResults) {
855
881
  return {
856
882
  statusPayload,
857
883
  resultPayload: input.eventPayload,
858
- resultIsTerminal: true,
884
+ resultIsTerminal: eventIsTerminal || statusIsTerminal,
859
885
  };
860
886
  }
861
887
 
@@ -872,17 +898,14 @@ export class FusionOrchestrator {
872
898
  }
873
899
 
874
900
  if (hasResultsArray(statusPayload)) {
875
- const resultIsTerminal =
876
- eventPayloadMatches ||
877
- isTerminalSubagentState(extractSubagentState(statusPayload));
878
901
  return {
879
902
  statusPayload,
880
903
  resultPayload: statusPayload,
881
- resultIsTerminal,
904
+ resultIsTerminal: statusIsTerminal,
882
905
  };
883
906
  }
884
907
 
885
- if (isTerminalSubagentState(extractSubagentState(statusPayload))) {
908
+ if (statusIsTerminal) {
886
909
  return {
887
910
  statusPayload,
888
911
  resultPayload: statusPayload,
@@ -894,9 +917,7 @@ export class FusionOrchestrator {
894
917
  return {
895
918
  statusPayload,
896
919
  resultPayload: input.eventPayload,
897
- resultIsTerminal: isTerminalSubagentState(
898
- extractSubagentState(input.eventPayload),
899
- ),
920
+ resultIsTerminal: eventIsTerminal,
900
921
  };
901
922
  }
902
923
 
@@ -1526,6 +1547,38 @@ function isTerminalSubagentState(state: string | undefined): boolean {
1526
1547
  );
1527
1548
  }
1528
1549
 
1550
+ function hasLifecycleResults(payload: unknown): boolean {
1551
+ return hasResultsArray(payload) || findStepsArray(payload).length > 0;
1552
+ }
1553
+
1554
+ function extractSubagentFailure(payload: unknown): string | undefined {
1555
+ if (!isRecord(payload)) return undefined;
1556
+ const direct = firstNonBlankString(payload.error, payload.errorMessage);
1557
+ if (direct) return direct;
1558
+ if (isRecord(payload.details)) {
1559
+ const detailsError = firstNonBlankString(
1560
+ payload.details.error,
1561
+ payload.details.errorMessage,
1562
+ );
1563
+ if (detailsError) return detailsError;
1564
+ }
1565
+ if (payload.isError === true && Array.isArray(payload.content)) {
1566
+ for (const item of payload.content) {
1567
+ const contentText = isRecord(item)
1568
+ ? firstNonBlankString(item.text)
1569
+ : undefined;
1570
+ if (contentText) return contentText;
1571
+ }
1572
+ }
1573
+ const text = firstNonBlankString(
1574
+ payload.text,
1575
+ isRecord(payload.details) ? payload.details.text : undefined,
1576
+ );
1577
+ if (text && /^error(?:\s|:)/i.test(text)) return text;
1578
+ if (isRecord(payload.data)) return extractSubagentFailure(payload.data);
1579
+ return undefined;
1580
+ }
1581
+
1529
1582
  function firstNonBlankStringFromPayload(payload: unknown): string | undefined {
1530
1583
  if (!isRecord(payload)) return undefined;
1531
1584
  const direct = firstNonBlankString(
package/src/status.ts CHANGED
@@ -92,6 +92,20 @@ export function formatProgressCounts(progress: FusionProgressCounts): string {
92
92
  return `${progress.completed}/${total} done, ${progress.running} running, ${progress.failed} failed`;
93
93
  }
94
94
 
95
+ export function isTerminalFusionProgress(payload: unknown): boolean {
96
+ const progressPayload = findTerminalProgressPayload(payload);
97
+ const progress = progressPayload
98
+ ? extractFusionProgressCounts(progressPayload)
99
+ : undefined;
100
+ return Boolean(
101
+ progress &&
102
+ progress.total !== undefined &&
103
+ progress.total > 0 &&
104
+ progress.pending === 0 &&
105
+ progress.running === 0,
106
+ );
107
+ }
108
+
95
109
  type ProgressStatus = "pending" | "running" | "completed" | "failed";
96
110
 
97
111
  function findProgressContainer(
@@ -116,6 +130,23 @@ function findProgressContainer(
116
130
  return undefined;
117
131
  }
118
132
 
133
+ function findTerminalProgressPayload(payload: unknown): unknown {
134
+ if (!isRecord(payload)) return undefined;
135
+ if (Array.isArray(payload.progress)) return { progress: payload.progress };
136
+ if (Array.isArray(payload.steps)) return { steps: payload.steps };
137
+ if (isRecord(payload.details)) {
138
+ if (Array.isArray(payload.details.progress)) {
139
+ return { progress: payload.details.progress };
140
+ }
141
+ if (Array.isArray(payload.details.steps)) {
142
+ return { steps: payload.details.steps };
143
+ }
144
+ }
145
+ return isRecord(payload.data)
146
+ ? findTerminalProgressPayload(payload.data)
147
+ : undefined;
148
+ }
149
+
119
150
  function classifyProgressItem(value: unknown): ProgressStatus {
120
151
  if (!isRecord(value)) return "failed";
121
152
  if (value.success === true) return "completed";