@alexeiled/pi-fusion 0.1.2 → 0.2.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.
@@ -13,10 +13,9 @@ import {
13
13
  } from "./report.js";
14
14
  import { extractPanelResults } from "./result-extract.js";
15
15
  import {
16
+ appendThinkingSuffix,
17
+ buildFusionChainSpawnParams,
16
18
  buildJudgeSpawnParams,
17
- buildPanelSpawnParams,
18
- type FailedPanelSummary,
19
- type PanelOutput,
20
19
  } from "./run-builder.js";
21
20
  import { FusionRunStore, FusionRunStoreError } from "./run-store.js";
22
21
  import {
@@ -27,12 +26,23 @@ import {
27
26
  type FusionProgressCounts,
28
27
  type FusionUi,
29
28
  } from "./status.js";
30
- import type { FusionProfile, FusionRun } from "./types.js";
29
+ import {
30
+ readSubagentResultArtifact,
31
+ readSubagentStatusArtifact,
32
+ } from "./subagent-artifacts.js";
33
+ import type {
34
+ FailedPanelSummary,
35
+ FusionProfile,
36
+ FusionRun,
37
+ PanelOutput,
38
+ } from "./types.js";
31
39
  import { parseFusionArgs, type ParsedFusionArgs } from "./commands.js";
32
40
  import type { SubagentsTargetParams } from "./subagents-rpc.js";
33
41
 
34
42
  export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
35
43
 
44
+ const RECONCILE_INTERVAL_MS = 2_000;
45
+
36
46
  export type FusionNotifyType = "info" | "warning" | "error";
37
47
 
38
48
  export interface FusionCommandUi extends FusionUi {
@@ -82,6 +92,11 @@ export type FusionCommandResult =
82
92
  | { status: "cancelled"; run: FusionRun; report: string }
83
93
  | { status: "ignored" };
84
94
 
95
+ interface RunLifecycleSnapshot {
96
+ statusPayload?: unknown;
97
+ resultPayload?: unknown;
98
+ }
99
+
85
100
  export class FusionOrchestrator {
86
101
  private readonly rpc: FusionRpcClientLike;
87
102
  private readonly runStore: FusionRunStore;
@@ -90,10 +105,10 @@ export class FusionOrchestrator {
90
105
  private readonly resolveProfile: typeof resolveFusionProfile;
91
106
  private context: FusionCommandContext | undefined;
92
107
  private activeProfile: FusionProfile | undefined;
93
- private activePanelOutputs: PanelOutput[] = [];
94
- private activePanelFailures: FailedPanelSummary[] = [];
95
108
  private installWarning: string | undefined;
96
109
  private configWarning: string | undefined;
110
+ private reconcileTimer: NodeJS.Timeout | undefined;
111
+ private reconciling = false;
97
112
 
98
113
  constructor(deps: FusionOrchestratorDeps) {
99
114
  this.rpc = deps.rpc;
@@ -150,25 +165,33 @@ export class FusionOrchestrator {
150
165
  const run = this.runStore.startRun({
151
166
  prompt: args.prompt,
152
167
  profileName: resolved.name,
168
+ phase: "chain",
153
169
  });
154
170
  this.activeProfile = resolved.profile;
155
- this.activePanelOutputs = [];
156
- this.activePanelFailures = [];
157
171
  publishFusionStatus(ctx, run);
158
172
 
159
173
  try {
160
174
  const spawnResult = await this.rpc.spawn(
161
- buildPanelSpawnParams(resolved.profile, args.prompt),
175
+ buildFusionChainSpawnParams(resolved.profile, args.prompt),
162
176
  );
163
- const panelRunId = extractSubagentRunId(spawnResult);
164
- if (!panelRunId) {
177
+ const chainRunId = extractSubagentRunId(spawnResult);
178
+ if (!chainRunId) {
165
179
  throw new FusionArgsError(
166
- "pi-subagents spawn did not return a panel run ID.",
180
+ "pi-subagents spawn did not return a fusion chain run ID.",
167
181
  );
168
182
  }
169
- const updated = this.runStore.updateRun(run.id, { panelRunId });
183
+ const chainAsyncDir = extractSubagentAsyncDir(spawnResult);
184
+ const updated = this.runStore.updateRun(run.id, {
185
+ chainRunId,
186
+ ...(chainAsyncDir ? { chainAsyncDir } : {}),
187
+ });
170
188
  publishFusionStatus(ctx, updated);
171
- this.notify(ctx, `Fusion panel started: ${panelRunId}`, "info");
189
+ this.ensureReconcileLoop();
190
+ this.notify(
191
+ ctx,
192
+ `Fusion ${resolved.name} started (${resolved.profile.panel.length} panelists): "${promptPreview(args.prompt)}" — ${chainRunId}`,
193
+ "info",
194
+ );
172
195
  return { status: "started", run: updated };
173
196
  } catch (error: unknown) {
174
197
  return this.failActiveRun(errorMessage(error));
@@ -180,32 +203,43 @@ export class FusionOrchestrator {
180
203
  if (!active) return { status: "ignored" };
181
204
 
182
205
  const completedRunId = extractSubagentRunId(payload);
183
- if (active.phase === "panel") {
184
- if (!active.panelRunId || completedRunId !== active.panelRunId) {
185
- return { status: "ignored" };
186
- }
187
- return this.handlePanelComplete(active, payload);
188
- }
206
+ if (!completedRunId) return { status: "ignored" };
189
207
 
190
208
  if (active.phase === "judge") {
191
209
  if (!active.judgeRunId || completedRunId !== active.judgeRunId) {
192
210
  return { status: "ignored" };
193
211
  }
194
- return this.handleJudgeComplete(active, payload);
212
+ return this.reconcileActiveRun(payload);
195
213
  }
196
214
 
197
- return { status: "ignored" };
215
+ const rootRunId = active.chainRunId ?? active.panelRunId;
216
+ if (!rootRunId || completedRunId !== rootRunId) {
217
+ return { status: "ignored" };
218
+ }
219
+ return this.reconcileActiveRun(payload);
198
220
  }
199
221
 
200
- async refreshStatus(targetRunId?: string): Promise<unknown> {
222
+ async refreshStatus(
223
+ targetRunId?: string,
224
+ asyncDir?: string,
225
+ ): Promise<unknown> {
201
226
  const active = this.runStore.getActiveRun();
202
227
  const runId = targetRunId ?? activeRunId(active);
203
- if (!runId) return undefined;
228
+ const resolvedAsyncDir = asyncDir ?? activeAsyncDir(active);
204
229
 
205
- const payload = await this.rpc.status({ id: runId });
206
- const progress = extractFusionProgressCounts(payload);
207
- if (active) publishFusionStatus(this.context, active, progress);
208
- return payload;
230
+ const statusPayload =
231
+ readSubagentStatusArtifact(resolvedAsyncDir) ??
232
+ (runId ? await this.rpc.status({ id: runId }) : undefined);
233
+ const progress = extractFusionProgressCounts(statusPayload);
234
+ if (active) {
235
+ publishFusionStatus(
236
+ this.context,
237
+ active,
238
+ progress,
239
+ deriveFusionStatusPhase(active, statusPayload),
240
+ );
241
+ }
242
+ return statusPayload;
209
243
  }
210
244
 
211
245
  async cancelActiveRun(
@@ -241,10 +275,18 @@ export class FusionOrchestrator {
241
275
  run: active,
242
276
  method,
243
277
  ...(targetRunId ? { targetRunId } : {}),
244
- panelOutputs: this.activePanelOutputs,
245
- failures: this.activePanelFailures,
278
+ panelOutputs: storedPanelOutputs(active),
279
+ failures: storedPanelFailures(active),
280
+ ...withJudgeModel(
281
+ this.activeProfile
282
+ ? configuredJudgeModel(this.activeProfile)
283
+ : undefined,
284
+ ),
246
285
  });
247
286
  const cancelled = this.runStore.cancelRun(active.id, {
287
+ ...(active.chainRunId ? { chainRunId: active.chainRunId } : {}),
288
+ ...(active.panelRunId ? { panelRunId: active.panelRunId } : {}),
289
+ ...(active.judgeRunId ? { judgeRunId: active.judgeRunId } : {}),
248
290
  report,
249
291
  error: `Cancellation requested with ${method}.`,
250
292
  });
@@ -264,6 +306,7 @@ export class FusionOrchestrator {
264
306
 
265
307
  const active = this.runStore.getActiveRun();
266
308
  if (!active) {
309
+ this.stopReconcileLoop();
267
310
  clearFusionUi(ctx);
268
311
  return summary;
269
312
  }
@@ -281,6 +324,8 @@ export class FusionOrchestrator {
281
324
  this.notify(ctx, message, "warning");
282
325
  }
283
326
  publishFusionStatus(ctx, active);
327
+ this.ensureReconcileLoop();
328
+ await this.reconcileActiveRun();
284
329
  return summary;
285
330
  }
286
331
 
@@ -288,6 +333,10 @@ export class FusionOrchestrator {
288
333
  clearFusionUi(ctx);
289
334
  }
290
335
 
336
+ dispose(): void {
337
+ this.stopReconcileLoop();
338
+ }
339
+
291
340
  async showStatus(ctx: FusionCommandContext): Promise<string> {
292
341
  this.context = ctx;
293
342
  const report = await this.getStatusReport();
@@ -296,16 +345,29 @@ export class FusionOrchestrator {
296
345
  }
297
346
 
298
347
  async getStatusReport(): Promise<string> {
348
+ if (this.runStore.getActiveRun()) {
349
+ await this.reconcileActiveRun();
350
+ }
351
+
299
352
  const active = this.runStore.getActiveRun();
300
353
  let progress: FusionProgressCounts | undefined;
301
354
  let statusWarning: string | undefined;
355
+ let statusDetails: FusionStatusDetails | undefined;
302
356
 
303
357
  if (active) {
304
358
  const targetRunId = activeRunId(active);
305
359
  if (targetRunId) {
306
360
  try {
307
- const payload = await this.refreshStatus(targetRunId);
361
+ const payload = await this.refreshStatus(
362
+ targetRunId,
363
+ activeAsyncDir(active),
364
+ );
308
365
  progress = extractFusionProgressCounts(payload);
366
+ statusDetails = buildFusionStatusDetails(
367
+ active,
368
+ this.activeProfile,
369
+ payload,
370
+ );
309
371
  } catch (error: unknown) {
310
372
  statusWarning = `Could not refresh ${targetRunId}: ${errorMessage(error)}`;
311
373
  }
@@ -313,6 +375,7 @@ export class FusionOrchestrator {
313
375
  return formatFusionStatusReport({
314
376
  active,
315
377
  ...(progress ? { progress } : {}),
378
+ ...(statusDetails ? { details: statusDetails } : {}),
316
379
  warnings: this.warnings(statusWarning),
317
380
  });
318
381
  }
@@ -327,37 +390,197 @@ export class FusionOrchestrator {
327
390
  return this.runStore.getActiveRun();
328
391
  }
329
392
 
330
- private async handlePanelComplete(
393
+ private async reconcileActiveRun(
394
+ eventPayload?: unknown,
395
+ ): Promise<FusionCommandResult> {
396
+ if (this.reconciling) return { status: "ignored" };
397
+ const active = this.runStore.getActiveRun();
398
+ if (!active) return { status: "ignored" };
399
+
400
+ this.reconciling = true;
401
+ try {
402
+ if (active.phase === "panel") {
403
+ return this.handleLegacyPanelComplete(active, eventPayload);
404
+ }
405
+ if (active.phase === "chain") {
406
+ return this.handleChainComplete(active, eventPayload);
407
+ }
408
+ if (active.phase === "judge") {
409
+ return this.handleJudgeComplete(active, eventPayload);
410
+ }
411
+ return { status: "ignored" };
412
+ } finally {
413
+ this.reconciling = false;
414
+ }
415
+ }
416
+
417
+ private async handleChainComplete(
331
418
  active: FusionRun,
332
419
  payload: unknown,
333
420
  ): Promise<FusionCommandResult> {
334
421
  const profile = this.activeProfile;
335
422
  if (!profile) {
336
423
  return this.failActiveRun(
337
- "Fusion panel completed, but the active profile was not available.",
424
+ "Fusion chain is active, but the profile could not be restored.",
425
+ );
426
+ }
427
+
428
+ const snapshot = await this.loadRunLifecycle({
429
+ run: active,
430
+ ...(active.chainRunId ? { runId: active.chainRunId } : {}),
431
+ ...(active.chainAsyncDir ? { asyncDir: active.chainAsyncDir } : {}),
432
+ eventPayload: payload,
433
+ });
434
+ const terminalPayload =
435
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
436
+ if (
437
+ !hasResultsArray(snapshot.resultPayload) &&
438
+ !isTerminalSubagentState(extractSubagentState(terminalPayload))
439
+ ) {
440
+ return { status: "ignored" };
441
+ }
442
+
443
+ const extracted = extractPanelResults(
444
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
445
+ {
446
+ panel: profile.panel,
447
+ limit: profile.panel.length,
448
+ },
449
+ );
450
+ if (!extracted.ok) {
451
+ return this.failActiveRun(
452
+ `${extracted.error.message} (${extracted.error.path})`,
338
453
  );
339
454
  }
340
455
 
341
- const statusPayload = await this.refreshStatusOrPayload(
342
- active.panelRunId,
343
- payload,
456
+ const updated = this.storePanelResults(
457
+ active.id,
458
+ extracted.outputs,
459
+ extracted.failures,
344
460
  );
345
- const extracted = extractPanelResults(statusPayload, {
346
- panel: profile.panel,
461
+
462
+ if (hasJudgeResult(snapshot.resultPayload, profile.panel.length)) {
463
+ const output = extractJudgeOutput(snapshot.resultPayload, {
464
+ resultIndex: profile.panel.length,
465
+ });
466
+ if (!output.ok) return this.failActiveRun(output.error);
467
+
468
+ const report = renderJudgeReport({
469
+ run: updated,
470
+ judgeOutput: output.output,
471
+ panelOutputs: storedPanelOutputs(updated),
472
+ failures: storedPanelFailures(updated),
473
+ ...withJudgeModel(configuredJudgeModel(profile)),
474
+ });
475
+ return this.completeActiveRun(report);
476
+ }
477
+
478
+ if (extracted.outputs.length === 0) {
479
+ const report = renderPanelFailureReport({
480
+ run: updated,
481
+ failures: extracted.failures,
482
+ ...withJudgeModel(configuredJudgeModel(profile)),
483
+ });
484
+ return this.failActiveRun(
485
+ "No fusion panelists completed successfully.",
486
+ report,
487
+ );
488
+ }
489
+
490
+ if (extracted.outputs.length === 1) {
491
+ const report = renderSinglePanelReport({
492
+ run: updated,
493
+ output: extracted.outputs[0]!,
494
+ failures: extracted.failures,
495
+ ...withJudgeModel(configuredJudgeModel(profile)),
496
+ });
497
+ return this.completeActiveRun(report);
498
+ }
499
+
500
+ try {
501
+ const spawnResult = await this.rpc.spawn(
502
+ buildJudgeSpawnParams({
503
+ profile,
504
+ prompt: active.prompt,
505
+ panelOutputs: extracted.outputs,
506
+ failedPanelists: extracted.failures,
507
+ }),
508
+ );
509
+ const judgeRunId = extractSubagentRunId(spawnResult);
510
+ if (!judgeRunId) {
511
+ throw new FusionArgsError(
512
+ "pi-subagents spawn did not return a fallback judge run ID.",
513
+ );
514
+ }
515
+ const judgeAsyncDir = extractSubagentAsyncDir(spawnResult);
516
+ const nextRun = this.runStore.updateRun(active.id, {
517
+ phase: "judge",
518
+ judgeRunId,
519
+ ...(judgeAsyncDir ? { judgeAsyncDir } : {}),
520
+ panelOutputs: extracted.outputs,
521
+ panelFailures: extracted.failures,
522
+ });
523
+ publishFusionStatus(this.context, nextRun);
524
+ this.notify(
525
+ this.context,
526
+ `Fusion fallback judge started: ${judgeRunId}`,
527
+ "info",
528
+ );
529
+ return { status: "started", run: nextRun };
530
+ } catch (error: unknown) {
531
+ return this.failActiveRun(errorMessage(error));
532
+ }
533
+ }
534
+
535
+ private async handleLegacyPanelComplete(
536
+ active: FusionRun,
537
+ payload: unknown,
538
+ ): Promise<FusionCommandResult> {
539
+ const profile = this.activeProfile;
540
+ if (!profile) {
541
+ return this.failActiveRun(
542
+ "Fusion panel completed, but the active profile was not available.",
543
+ );
544
+ }
545
+
546
+ const snapshot = await this.loadRunLifecycle({
547
+ run: active,
548
+ ...(active.panelRunId ? { runId: active.panelRunId } : {}),
549
+ ...(active.chainAsyncDir ? { asyncDir: active.chainAsyncDir } : {}),
550
+ eventPayload: payload,
347
551
  });
552
+ const terminalPayload =
553
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
554
+ if (
555
+ !hasResultsArray(snapshot.resultPayload) &&
556
+ !isTerminalSubagentState(extractSubagentState(terminalPayload))
557
+ ) {
558
+ return { status: "ignored" };
559
+ }
560
+
561
+ const extracted = extractPanelResults(
562
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
563
+ {
564
+ panel: profile.panel,
565
+ },
566
+ );
348
567
  if (!extracted.ok) {
349
568
  return this.failActiveRun(
350
569
  `${extracted.error.message} (${extracted.error.path})`,
351
570
  );
352
571
  }
353
572
 
354
- this.activePanelOutputs = extracted.outputs;
355
- this.activePanelFailures = extracted.failures;
573
+ const updated = this.storePanelResults(
574
+ active.id,
575
+ extracted.outputs,
576
+ extracted.failures,
577
+ );
356
578
 
357
579
  if (extracted.outputs.length === 0) {
358
580
  const report = renderPanelFailureReport({
359
- run: active,
581
+ run: updated,
360
582
  failures: extracted.failures,
583
+ ...withJudgeModel(configuredJudgeModel(profile)),
361
584
  });
362
585
  return this.failActiveRun(
363
586
  "No fusion panelists completed successfully.",
@@ -367,9 +590,10 @@ export class FusionOrchestrator {
367
590
 
368
591
  if (extracted.outputs.length === 1) {
369
592
  const report = renderSinglePanelReport({
370
- run: active,
593
+ run: updated,
371
594
  output: extracted.outputs[0]!,
372
595
  failures: extracted.failures,
596
+ ...withJudgeModel(configuredJudgeModel(profile)),
373
597
  });
374
598
  return this.completeActiveRun(report);
375
599
  }
@@ -389,13 +613,17 @@ export class FusionOrchestrator {
389
613
  "pi-subagents spawn did not return a judge run ID.",
390
614
  );
391
615
  }
392
- const updated = this.runStore.updateRun(active.id, {
616
+ const judgeAsyncDir = extractSubagentAsyncDir(spawnResult);
617
+ const nextRun = this.runStore.updateRun(active.id, {
393
618
  phase: "judge",
394
619
  judgeRunId,
620
+ ...(judgeAsyncDir ? { judgeAsyncDir } : {}),
621
+ panelOutputs: extracted.outputs,
622
+ panelFailures: extracted.failures,
395
623
  });
396
- publishFusionStatus(this.context, updated);
624
+ publishFusionStatus(this.context, nextRun);
397
625
  this.notify(this.context, `Fusion judge started: ${judgeRunId}`, "info");
398
- return { status: "started", run: updated };
626
+ return { status: "started", run: nextRun };
399
627
  } catch (error: unknown) {
400
628
  return this.failActiveRun(errorMessage(error));
401
629
  }
@@ -405,44 +633,117 @@ export class FusionOrchestrator {
405
633
  active: FusionRun,
406
634
  payload: unknown,
407
635
  ): Promise<FusionCommandResult> {
408
- const statusPayload = await this.refreshStatusOrPayload(
409
- active.judgeRunId,
410
- payload,
636
+ const snapshot = await this.loadRunLifecycle({
637
+ run: active,
638
+ ...(active.judgeRunId ? { runId: active.judgeRunId } : {}),
639
+ ...(active.judgeAsyncDir ? { asyncDir: active.judgeAsyncDir } : {}),
640
+ eventPayload: payload,
641
+ });
642
+ const terminalPayload =
643
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload;
644
+ if (
645
+ !hasResultsArray(snapshot.resultPayload) &&
646
+ !isTerminalSubagentState(extractSubagentState(terminalPayload))
647
+ ) {
648
+ return { status: "ignored" };
649
+ }
650
+
651
+ const output = extractJudgeOutput(
652
+ snapshot.resultPayload ?? snapshot.statusPayload ?? payload,
411
653
  );
412
- const output = extractJudgeOutput(statusPayload);
413
654
  if (!output.ok) return this.failActiveRun(output.error);
414
655
 
415
656
  const report = renderJudgeReport({
416
657
  run: active,
417
658
  judgeOutput: output.output,
418
- panelOutputs: this.activePanelOutputs,
419
- failures: this.activePanelFailures,
659
+ panelOutputs: storedPanelOutputs(active),
660
+ failures: storedPanelFailures(active),
661
+ ...withJudgeModel(
662
+ this.activeProfile
663
+ ? configuredJudgeModel(this.activeProfile)
664
+ : undefined,
665
+ ),
420
666
  });
421
667
  return this.completeActiveRun(report);
422
668
  }
423
669
 
424
- private async refreshStatusOrPayload(
425
- runId: string | undefined,
426
- payload: unknown,
427
- ): Promise<unknown> {
428
- if (!runId) return payload;
429
- const payloadResults = findResultsArray(payload);
430
- try {
431
- const statusPayload = await this.refreshStatus(runId);
432
- const statusResults = findResultsArray(statusPayload);
433
- if (payloadResults && payloadResults.length > 0) return payload;
434
- if (statusResults && statusResults.length > 0) return statusPayload;
435
- return payloadResults ? payload : (statusPayload ?? payload);
436
- } catch (error: unknown) {
437
- this.installWarning = `Could not refresh subagent run ${runId}: ${errorMessage(error)}`;
438
- return payload;
670
+ private async loadRunLifecycle(input: {
671
+ run: FusionRun;
672
+ runId?: string;
673
+ asyncDir?: string;
674
+ eventPayload?: unknown;
675
+ }): Promise<RunLifecycleSnapshot> {
676
+ const eventPayloadMatches =
677
+ input.eventPayload !== undefined &&
678
+ extractSubagentRunId(input.eventPayload) === input.runId;
679
+
680
+ let statusPayload = readSubagentStatusArtifact(input.asyncDir);
681
+ if (statusPayload === undefined && input.runId) {
682
+ try {
683
+ statusPayload = await this.rpc.status({ id: input.runId });
684
+ } catch (error: unknown) {
685
+ this.installWarning = `Could not refresh subagent run ${input.runId}: ${errorMessage(error)}`;
686
+ }
687
+ }
688
+
689
+ const progress = extractFusionProgressCounts(statusPayload);
690
+ if (progress) {
691
+ publishFusionStatus(
692
+ this.context,
693
+ input.run,
694
+ progress,
695
+ deriveFusionStatusPhase(input.run, statusPayload),
696
+ );
697
+ }
698
+
699
+ const eventHasResults = hasResultsArray(input.eventPayload);
700
+ if (eventPayloadMatches && eventHasResults) {
701
+ return { statusPayload, resultPayload: input.eventPayload };
702
+ }
703
+
704
+ const artifactResult = readSubagentResultArtifact({
705
+ ...(input.runId ? { runId: input.runId } : {}),
706
+ ...(input.asyncDir ? { asyncDir: input.asyncDir } : {}),
707
+ });
708
+ if (hasResultsArray(artifactResult)) {
709
+ return { statusPayload, resultPayload: artifactResult };
439
710
  }
711
+
712
+ if (hasResultsArray(statusPayload)) {
713
+ return { statusPayload, resultPayload: statusPayload };
714
+ }
715
+
716
+ if (isTerminalSubagentState(extractSubagentState(statusPayload))) {
717
+ return { statusPayload, resultPayload: statusPayload };
718
+ }
719
+
720
+ if (eventPayloadMatches) {
721
+ return { statusPayload, resultPayload: input.eventPayload };
722
+ }
723
+
724
+ return { statusPayload };
725
+ }
726
+
727
+ private storePanelResults(
728
+ runId: string,
729
+ panelOutputs: readonly PanelOutput[],
730
+ panelFailures: readonly FailedPanelSummary[],
731
+ ): FusionRun {
732
+ return this.runStore.updateRun(runId, {
733
+ panelOutputs: [...panelOutputs],
734
+ panelFailures: [...panelFailures],
735
+ });
440
736
  }
441
737
 
442
738
  private completeActiveRun(report: string): FusionCommandResult {
443
739
  const active = this.runStore.getActiveRun();
444
740
  if (!active) return { status: "failed", error: "No active fusion run." };
445
- const done = this.runStore.completeRun(active.id, { report });
741
+ const done = this.runStore.completeRun(active.id, {
742
+ ...(active.chainRunId ? { chainRunId: active.chainRunId } : {}),
743
+ ...(active.panelRunId ? { panelRunId: active.panelRunId } : {}),
744
+ ...(active.judgeRunId ? { judgeRunId: active.judgeRunId } : {}),
745
+ report,
746
+ });
446
747
  this.postMessage("fusion-report", report, { runId: done.id });
447
748
  this.clearActiveRuntime();
448
749
  this.clearUi();
@@ -457,7 +758,13 @@ export class FusionOrchestrator {
457
758
  if (!active) return { status: "failed", error };
458
759
  let failed: FusionRun;
459
760
  try {
460
- failed = this.runStore.failRun(active.id, { error, report });
761
+ failed = this.runStore.failRun(active.id, {
762
+ ...(active.chainRunId ? { chainRunId: active.chainRunId } : {}),
763
+ ...(active.panelRunId ? { panelRunId: active.panelRunId } : {}),
764
+ ...(active.judgeRunId ? { judgeRunId: active.judgeRunId } : {}),
765
+ report,
766
+ error,
767
+ });
461
768
  } catch (storeError: unknown) {
462
769
  if (!(storeError instanceof FusionRunStoreError)) throw storeError;
463
770
  return { status: "failed", error: errorMessage(storeError), report };
@@ -475,15 +782,33 @@ export class FusionOrchestrator {
475
782
  return renderFailureReport({
476
783
  run: active,
477
784
  error,
478
- panelOutputs: this.activePanelOutputs,
479
- failures: this.activePanelFailures,
785
+ panelOutputs: storedPanelOutputs(active),
786
+ failures: storedPanelFailures(active),
787
+ ...withJudgeModel(
788
+ this.activeProfile
789
+ ? configuredJudgeModel(this.activeProfile)
790
+ : undefined,
791
+ ),
480
792
  });
481
793
  }
482
794
 
483
795
  private clearActiveRuntime(): void {
484
796
  this.activeProfile = undefined;
485
- this.activePanelOutputs = [];
486
- this.activePanelFailures = [];
797
+ this.stopReconcileLoop();
798
+ }
799
+
800
+ private ensureReconcileLoop(): void {
801
+ if (this.reconcileTimer) return;
802
+ this.reconcileTimer = setInterval(() => {
803
+ void this.reconcileActiveRun();
804
+ }, RECONCILE_INTERVAL_MS);
805
+ this.reconcileTimer.unref?.();
806
+ }
807
+
808
+ private stopReconcileLoop(): void {
809
+ if (!this.reconcileTimer) return;
810
+ clearInterval(this.reconcileTimer);
811
+ this.reconcileTimer = undefined;
487
812
  }
488
813
 
489
814
  private warnings(extra?: string): string[] {
@@ -534,10 +859,23 @@ export function extractSubagentRunId(payload: unknown): string | undefined {
534
859
  return undefined;
535
860
  }
536
861
 
862
+ export function extractSubagentAsyncDir(payload: unknown): string | undefined {
863
+ if (!isRecord(payload)) return undefined;
864
+ const direct = firstNonBlankString(payload.asyncDir);
865
+ if (direct) return direct;
866
+ if (isRecord(payload.details)) {
867
+ const details = firstNonBlankString(payload.details.asyncDir);
868
+ if (details) return details;
869
+ }
870
+ if (isRecord(payload.data)) return extractSubagentAsyncDir(payload.data);
871
+ return undefined;
872
+ }
873
+
537
874
  export function extractJudgeOutput(
538
875
  payload: unknown,
876
+ options: { resultIndex?: number } = {},
539
877
  ): { ok: true; output: string } | { ok: false; error: string } {
540
- const result = findFirstResult(payload);
878
+ const result = findResult(payload, options.resultIndex);
541
879
  if (result) {
542
880
  const failed = resultFailed(result);
543
881
  const output = firstNonBlankString(
@@ -556,8 +894,9 @@ export function extractJudgeOutput(
556
894
  }
557
895
  if (output) return { ok: true, output };
558
896
  const artifactPath = extractArtifactPath(result);
559
- if (artifactPath)
897
+ if (artifactPath) {
560
898
  return { ok: true, output: `Output artifact: ${artifactPath}` };
899
+ }
561
900
  }
562
901
 
563
902
  const output = firstNonBlankStringFromPayload(payload);
@@ -565,48 +904,332 @@ export function extractJudgeOutput(
565
904
  return { ok: false, error: "Fusion judge completed without output." };
566
905
  }
567
906
 
907
+ interface FusionStatusPanelLine {
908
+ label: string;
909
+ role?: string;
910
+ model?: string;
911
+ status: string;
912
+ activity?: string;
913
+ }
914
+
915
+ interface FusionStatusJudgeLine {
916
+ label: string;
917
+ model?: string;
918
+ status: string;
919
+ activity?: string;
920
+ }
921
+
922
+ interface FusionStatusDetails {
923
+ prompt: string;
924
+ phaseLabel?: string;
925
+ panelists?: readonly FusionStatusPanelLine[];
926
+ judge?: FusionStatusJudgeLine;
927
+ fallbackJudge?: FusionStatusJudgeLine;
928
+ }
929
+
568
930
  function formatFusionStatusReport(input: {
569
931
  active?: FusionRun;
570
932
  last?: ReturnType<FusionRunStore["getLastRunSummary"]>;
571
933
  progress?: FusionProgressCounts;
934
+ details?: FusionStatusDetails;
572
935
  warnings: readonly string[];
573
936
  }): string {
574
937
  const lines = ["Fusion status"];
575
938
  if (input.active) {
576
939
  lines.push("State: active");
577
940
  lines.push(`Run: ${input.active.id}`);
941
+ lines.push(
942
+ `Prompt: ${firstLine(input.details?.prompt ?? input.active.prompt)}`,
943
+ );
578
944
  lines.push(`Profile: ${input.active.profileName}`);
579
- lines.push(`Phase: ${input.active.phase}`);
580
- if (input.active.panelRunId)
945
+ lines.push(`Phase: ${input.details?.phaseLabel ?? input.active.phase}`);
946
+ if (input.active.chainRunId)
947
+ lines.push(`Chain run: ${input.active.chainRunId}`);
948
+ else if (input.active.panelRunId)
581
949
  lines.push(`Panel run: ${input.active.panelRunId}`);
582
- if (input.active.judgeRunId)
583
- lines.push(`Judge run: ${input.active.judgeRunId}`);
950
+ if (input.active.judgeRunId) {
951
+ lines.push(`Fallback judge run: ${input.active.judgeRunId}`);
952
+ }
584
953
  lines.push(
585
954
  `Progress: ${input.progress ? formatProgressCounts(input.progress) : "unknown"}`,
586
955
  );
956
+ appendStatusDetails(lines, input.details);
587
957
  } else if (input.last) {
588
958
  lines.push("State: idle");
589
959
  lines.push(`Last run: ${input.last.id}`);
960
+ lines.push(`Prompt: ${firstLine(input.last.prompt)}`);
590
961
  lines.push(`Profile: ${input.last.profileName}`);
591
962
  lines.push(`Phase: ${input.last.phase}`);
592
- if (input.last.panelRunId)
963
+ if (input.last.chainRunId)
964
+ lines.push(`Chain run: ${input.last.chainRunId}`);
965
+ else if (input.last.panelRunId)
593
966
  lines.push(`Panel run: ${input.last.panelRunId}`);
594
- if (input.last.judgeRunId)
595
- lines.push(`Judge run: ${input.last.judgeRunId}`);
967
+ if (input.last.judgeRunId) {
968
+ lines.push(`Fallback judge run: ${input.last.judgeRunId}`);
969
+ }
596
970
  } else {
597
971
  lines.push("State: idle");
598
972
  lines.push("Last run: none");
599
973
  }
600
974
 
601
975
  if (input.warnings.length === 0) lines.push("Warnings: none");
602
- else
976
+ else {
603
977
  lines.push("Warnings:", ...input.warnings.map((warning) => `- ${warning}`));
978
+ }
604
979
  return lines.join("\n");
605
980
  }
606
981
 
982
+ function appendStatusDetails(
983
+ lines: string[],
984
+ details: FusionStatusDetails | undefined,
985
+ ): void {
986
+ if (!details) return;
987
+ if (details.panelists && details.panelists.length > 0) {
988
+ lines.push("", "Panelists");
989
+ for (const panelist of details.panelists) {
990
+ lines.push(`- ${panelist.label}: ${panelist.status}`);
991
+ if (panelist.role) lines.push(` Role: ${panelist.role}`);
992
+ if (panelist.model) lines.push(` Model: ${panelist.model}`);
993
+ if (panelist.activity) lines.push(` Activity: ${panelist.activity}`);
994
+ }
995
+ }
996
+ if (details.judge) {
997
+ lines.push("", details.judge.label);
998
+ lines.push(`- Status: ${details.judge.status}`);
999
+ if (details.judge.model) lines.push(` Model: ${details.judge.model}`);
1000
+ if (details.judge.activity)
1001
+ lines.push(` Activity: ${details.judge.activity}`);
1002
+ }
1003
+ if (details.fallbackJudge) {
1004
+ lines.push("", details.fallbackJudge.label);
1005
+ lines.push(`- Status: ${details.fallbackJudge.status}`);
1006
+ if (details.fallbackJudge.model) {
1007
+ lines.push(` Model: ${details.fallbackJudge.model}`);
1008
+ }
1009
+ if (details.fallbackJudge.activity) {
1010
+ lines.push(` Activity: ${details.fallbackJudge.activity}`);
1011
+ }
1012
+ }
1013
+ }
1014
+
607
1015
  function activeRunId(run: FusionRun | undefined): string | undefined {
608
1016
  if (!run) return undefined;
609
- return run.phase === "judge" ? run.judgeRunId : run.panelRunId;
1017
+ if (run.phase === "judge") return run.judgeRunId;
1018
+ return run.chainRunId ?? run.panelRunId;
1019
+ }
1020
+
1021
+ function activeAsyncDir(run: FusionRun | undefined): string | undefined {
1022
+ if (!run) return undefined;
1023
+ return run.phase === "judge" ? run.judgeAsyncDir : run.chainAsyncDir;
1024
+ }
1025
+
1026
+ function buildFusionStatusDetails(
1027
+ run: FusionRun,
1028
+ profile: FusionProfile | undefined,
1029
+ payload: unknown,
1030
+ ): FusionStatusDetails {
1031
+ const details: FusionStatusDetails = {
1032
+ prompt: run.prompt,
1033
+ phaseLabel: deriveFusionStatusPhase(run, payload),
1034
+ };
1035
+ if (!profile) return details;
1036
+
1037
+ const judgeModel = configuredJudgeModel(profile);
1038
+
1039
+ if (run.phase === "judge") {
1040
+ details.panelists = buildCompletedPanelStatusLines(
1041
+ profile.panel,
1042
+ storedPanelOutputs(run),
1043
+ storedPanelFailures(run),
1044
+ );
1045
+ details.fallbackJudge = {
1046
+ label: run.chainRunId ? "Fallback judge" : "Judge",
1047
+ ...(judgeModel ? { model: judgeModel } : {}),
1048
+ status: describeStandaloneRunStatus(payload),
1049
+ };
1050
+ return details;
1051
+ }
1052
+
1053
+ const steps = findStepsArray(payload);
1054
+ const panelSteps = steps.slice(0, profile.panel.length);
1055
+ const panelists = profile.panel.map((member, index) => {
1056
+ const step = panelSteps[index];
1057
+ const model = configuredPanelModel(member);
1058
+ const activity = describeStepActivity(step);
1059
+ return {
1060
+ label: member.label,
1061
+ ...(member.role ? { role: member.role } : {}),
1062
+ ...(model ? { model } : {}),
1063
+ status: describePanelStatus(step),
1064
+ ...(activity ? { activity } : {}),
1065
+ };
1066
+ });
1067
+ details.panelists = panelists;
1068
+
1069
+ const judgeActivity = describeStepActivity(steps[profile.panel.length]);
1070
+ details.judge = {
1071
+ label: "Judge",
1072
+ ...(judgeModel ? { model: judgeModel } : {}),
1073
+ status: describeChainJudgeStatus(steps[profile.panel.length], panelists),
1074
+ ...(judgeActivity ? { activity: judgeActivity } : {}),
1075
+ };
1076
+ return details;
1077
+ }
1078
+
1079
+ function deriveFusionStatusPhase(
1080
+ run: Pick<FusionRun, "phase" | "chainRunId">,
1081
+ payload: unknown,
1082
+ ): string {
1083
+ if (run.phase === "judge") {
1084
+ return run.chainRunId ? "fallback judge" : "judge";
1085
+ }
1086
+ if (run.phase !== "chain") return run.phase;
1087
+
1088
+ const steps = findStepsArray(payload);
1089
+ if (steps.length === 0) return "chain";
1090
+ const judgeStatus = normalizeStatusLabel(steps.at(-1));
1091
+ if (judgeStatus === "running" || judgeStatus === "completed") {
1092
+ return "judge";
1093
+ }
1094
+ const panelSteps = steps.slice(0, -1);
1095
+ if (panelSteps.some((step) => normalizeStatusLabel(step) === "running")) {
1096
+ return "panel";
1097
+ }
1098
+ if (judgeStatus === "pending") {
1099
+ const allPanelsFinished = panelSteps.every((step) => {
1100
+ const status = normalizeStatusLabel(step);
1101
+ return status === "completed" || status === "failed";
1102
+ });
1103
+ return allPanelsFinished ? "judge" : "panel";
1104
+ }
1105
+ return "panel";
1106
+ }
1107
+
1108
+ function buildCompletedPanelStatusLines(
1109
+ panel: FusionProfile["panel"],
1110
+ outputs: readonly PanelOutput[],
1111
+ failures: readonly FailedPanelSummary[],
1112
+ ): FusionStatusPanelLine[] {
1113
+ return panel.map((member, index) => {
1114
+ const model = configuredPanelModel(member);
1115
+ const output = outputs.find(
1116
+ (item) => item.id === member.id || item.index === index,
1117
+ );
1118
+ if (output) {
1119
+ return {
1120
+ label: member.label,
1121
+ ...(member.role ? { role: member.role } : {}),
1122
+ ...(model ? { model } : {}),
1123
+ status: "completed",
1124
+ };
1125
+ }
1126
+ const failure = failures.find(
1127
+ (item) => item.id === member.id || item.index === index,
1128
+ );
1129
+ return {
1130
+ label: member.label,
1131
+ ...(member.role ? { role: member.role } : {}),
1132
+ ...(model ? { model } : {}),
1133
+ status: failure ? "failed" : "unknown",
1134
+ ...(failure ? { activity: firstLine(failure.summary) } : {}),
1135
+ };
1136
+ });
1137
+ }
1138
+
1139
+ function describePanelStatus(step: unknown): string {
1140
+ return normalizeStatusLabel(step);
1141
+ }
1142
+
1143
+ function describeChainJudgeStatus(
1144
+ step: unknown,
1145
+ panelists: readonly FusionStatusPanelLine[],
1146
+ ): string {
1147
+ if (step === undefined) return "waiting for panel results";
1148
+ const normalized = normalizeStatusLabel(step);
1149
+ if (normalized === "pending") {
1150
+ const waitingOnPanel = panelists.some(
1151
+ (panelist) =>
1152
+ panelist.status !== "completed" && panelist.status !== "failed",
1153
+ );
1154
+ return waitingOnPanel ? "waiting for panel results" : "pending";
1155
+ }
1156
+ return normalized;
1157
+ }
1158
+
1159
+ function describeStandaloneRunStatus(payload: unknown): string {
1160
+ const state = extractSubagentState(payload);
1161
+ if (state) {
1162
+ if (state === "complete" || state === "completed" || state === "done") {
1163
+ return "completed";
1164
+ }
1165
+ if (state === "running" || state === "active") return "running";
1166
+ if (state === "pending" || state === "queued") return "pending";
1167
+ if (state === "failed" || state === "paused" || state === "detached") {
1168
+ return "failed";
1169
+ }
1170
+ return state;
1171
+ }
1172
+ if (hasResultsArray(payload)) return "completed";
1173
+ return "running";
1174
+ }
1175
+
1176
+ function normalizeStatusLabel(step: unknown): string {
1177
+ if (!isRecord(step)) return "pending";
1178
+ if (step.success === true) return "completed";
1179
+ if (step.success === false) return "failed";
1180
+ if (step.timedOut === true || step.interrupted === true) return "failed";
1181
+ if (typeof step.exitCode === "number") {
1182
+ return step.exitCode === 0 ? "completed" : "failed";
1183
+ }
1184
+ const status = firstString(step.status, step.state);
1185
+ if (status === "complete" || status === "completed" || status === "done") {
1186
+ return "completed";
1187
+ }
1188
+ if (status === "running" || status === "active") return "running";
1189
+ if (status === "pending" || status === "queued") return "pending";
1190
+ if (status === "failed" || status === "paused" || status === "detached") {
1191
+ return "failed";
1192
+ }
1193
+ return "pending";
1194
+ }
1195
+
1196
+ function findStepsArray(payload: unknown): readonly unknown[] {
1197
+ if (!isRecord(payload)) return [];
1198
+ const direct = unknownArray(payload.steps);
1199
+ if (direct) return direct;
1200
+ if (isRecord(payload.details)) {
1201
+ const detailsSteps = unknownArray(payload.details.steps);
1202
+ if (detailsSteps) return detailsSteps;
1203
+ }
1204
+ if (isRecord(payload.data)) return findStepsArray(payload.data);
1205
+ return [];
1206
+ }
1207
+
1208
+ function describeStepActivity(step: unknown): string | undefined {
1209
+ if (!isRecord(step)) return undefined;
1210
+ const tools = unknownArray(step.recentTools);
1211
+ const lastTool = tools?.at(-1);
1212
+ if (isRecord(lastTool)) {
1213
+ const tool = firstString(lastTool.tool) ?? "tool";
1214
+ const args = firstString(lastTool.args);
1215
+ return args ? `${tool} ${summarizeActivityArg(args)}` : tool;
1216
+ }
1217
+ const recentOutput = unknownArray(step.recentOutput)
1218
+ ?.map((item) => (typeof item === "string" ? item.trim() : ""))
1219
+ .find(Boolean);
1220
+ return recentOutput || undefined;
1221
+ }
1222
+
1223
+ function summarizeActivityArg(value: string): string {
1224
+ const parts = value.split("/").filter(Boolean);
1225
+ if (parts.length >= 3) return parts.slice(-3).join("/");
1226
+ return value;
1227
+ }
1228
+
1229
+ function configuredPanelModel(
1230
+ member: FusionProfile["panel"][number],
1231
+ ): string | undefined {
1232
+ return appendThinkingSuffix(member.model, member.thinking);
610
1233
  }
611
1234
 
612
1235
  function findResultsArray(payload: unknown): readonly unknown[] | undefined {
@@ -626,10 +1249,25 @@ function findResultsArray(payload: unknown): readonly unknown[] | undefined {
626
1249
  return undefined;
627
1250
  }
628
1251
 
629
- function findFirstResult(
1252
+ function hasResultsArray(payload: unknown): boolean {
1253
+ return (findResultsArray(payload)?.length ?? 0) > 0;
1254
+ }
1255
+
1256
+ function hasJudgeResult(payload: unknown, panelCount: number): boolean {
1257
+ return (findResultsArray(payload)?.length ?? 0) > panelCount;
1258
+ }
1259
+
1260
+ function findResult(
630
1261
  payload: unknown,
1262
+ resultIndex?: number,
631
1263
  ): Record<string, unknown> | undefined {
632
- const first = findResultsArray(payload)?.[0];
1264
+ const results = findResultsArray(payload);
1265
+ if (!results || results.length === 0) return undefined;
1266
+ if (resultIndex !== undefined) {
1267
+ const indexed = results[resultIndex];
1268
+ return isRecord(indexed) ? indexed : undefined;
1269
+ }
1270
+ const first = results[0];
633
1271
  return isRecord(first) ? first : undefined;
634
1272
  }
635
1273
 
@@ -642,41 +1280,115 @@ function resultFailed(result: Record<string, unknown>): boolean {
642
1280
  return status === "failed" || status === "paused" || status === "detached";
643
1281
  }
644
1282
 
1283
+ function extractSubagentState(payload: unknown): string | undefined {
1284
+ if (!isRecord(payload)) return undefined;
1285
+ const direct = firstString(payload.state, payload.status);
1286
+ if (direct) return direct;
1287
+ const text = firstString(
1288
+ payload.text,
1289
+ isRecord(payload.details) ? payload.details.text : undefined,
1290
+ );
1291
+ const parsedText = text ? extractStateFromText(text) : undefined;
1292
+ if (parsedText) return parsedText;
1293
+ if (isRecord(payload.details)) {
1294
+ const fromDetails = firstString(
1295
+ payload.details.state,
1296
+ payload.details.status,
1297
+ );
1298
+ if (fromDetails) return fromDetails;
1299
+ }
1300
+ if (isRecord(payload.data)) return extractSubagentState(payload.data);
1301
+ return undefined;
1302
+ }
1303
+
1304
+ function extractStateFromText(text: string): string | undefined {
1305
+ const match = text.match(/(?:^|\n)(?:State|Status):\s*([^\n\r]+)/i);
1306
+ return match?.[1]?.trim() || undefined;
1307
+ }
1308
+
1309
+ function isTerminalSubagentState(state: string | undefined): boolean {
1310
+ return (
1311
+ state === "complete" ||
1312
+ state === "completed" ||
1313
+ state === "done" ||
1314
+ state === "failed" ||
1315
+ state === "paused" ||
1316
+ state === "detached"
1317
+ );
1318
+ }
1319
+
645
1320
  function firstNonBlankStringFromPayload(payload: unknown): string | undefined {
646
1321
  if (!isRecord(payload)) return undefined;
647
1322
  const direct = firstNonBlankString(
648
1323
  payload.output,
649
1324
  payload.finalOutput,
650
1325
  payload.summary,
651
- payload.text,
652
1326
  );
653
1327
  if (direct) return direct;
1328
+ const text = firstNonBlankString(payload.text);
1329
+ if (text && !isStatusEnvelopeText(text)) return text;
654
1330
  if (isRecord(payload.details)) {
655
1331
  const fromDetails = firstNonBlankString(
656
1332
  payload.details.output,
657
1333
  payload.details.finalOutput,
658
1334
  payload.details.summary,
659
- payload.details.text,
660
1335
  );
661
1336
  if (fromDetails) return fromDetails;
1337
+ const detailsText = firstNonBlankString(payload.details.text);
1338
+ if (detailsText && !isStatusEnvelopeText(detailsText)) return detailsText;
662
1339
  }
663
1340
  if (isRecord(payload.data))
664
1341
  return firstNonBlankStringFromPayload(payload.data);
665
1342
  return undefined;
666
1343
  }
667
1344
 
1345
+ function isStatusEnvelopeText(value: string): boolean {
1346
+ return (
1347
+ /(?:^|\n)Run:\s*[^\n\r]+/i.test(value) &&
1348
+ /(?:^|\n)(?:State|Status):\s*[^\n\r]+/i.test(value)
1349
+ );
1350
+ }
1351
+
668
1352
  function extractArtifactPath(
669
1353
  result: Record<string, unknown>,
670
1354
  ): string | undefined {
671
1355
  const direct = firstString(result.artifactPath, result.savedOutputPath);
672
1356
  if (direct) return direct;
673
- if (isRecord(result.artifactPaths))
1357
+ if (isRecord(result.artifactPaths)) {
674
1358
  return firstString(result.artifactPaths.outputPath);
675
- if (isRecord(result.outputReference))
1359
+ }
1360
+ if (isRecord(result.outputReference)) {
676
1361
  return firstString(result.outputReference.path);
1362
+ }
677
1363
  return undefined;
678
1364
  }
679
1365
 
1366
+ function storedPanelOutputs(run: FusionRun): readonly PanelOutput[] {
1367
+ return run.panelOutputs ?? [];
1368
+ }
1369
+
1370
+ function storedPanelFailures(run: FusionRun): readonly FailedPanelSummary[] {
1371
+ return run.panelFailures ?? [];
1372
+ }
1373
+
1374
+ function configuredJudgeModel(profile: FusionProfile): string | undefined {
1375
+ return appendThinkingSuffix(profile.judge.model, profile.judge.thinking);
1376
+ }
1377
+
1378
+ function withJudgeModel(
1379
+ judgeModel: string | undefined,
1380
+ ): { judgeModel: string } | Record<string, never> {
1381
+ return judgeModel ? { judgeModel } : {};
1382
+ }
1383
+
1384
+ function promptPreview(prompt: string): string {
1385
+ return firstLine(prompt).slice(0, 80);
1386
+ }
1387
+
1388
+ function firstLine(value: string): string {
1389
+ return value.split(/\r?\n/, 1)[0]?.trim() ?? "";
1390
+ }
1391
+
680
1392
  function firstString(...values: readonly unknown[]): string | undefined {
681
1393
  for (const value of values) {
682
1394
  if (typeof value === "string") return value;