@alexeiled/pi-fusion 0.1.2 → 0.2.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 +68 -31
- package/docs/user-guide.md +10 -2
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/orchestrator.ts +801 -89
- package/src/report.ts +21 -3
- package/src/result-extract.ts +21 -2
- package/src/run-builder.ts +131 -19
- package/src/run-store.ts +121 -1
- package/src/status.ts +34 -14
- package/src/subagent-artifacts.ts +40 -0
- package/src/types.ts +31 -1
package/src/report.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { FusionRun } from "./types.js";
|
|
|
3
3
|
|
|
4
4
|
type ReportRun = Pick<
|
|
5
5
|
FusionRun,
|
|
6
|
-
"id" | "prompt" | "profileName" | "panelRunId" | "judgeRunId"
|
|
6
|
+
"id" | "prompt" | "profileName" | "chainRunId" | "panelRunId" | "judgeRunId"
|
|
7
7
|
> &
|
|
8
8
|
Partial<Pick<FusionRun, "phase" | "createdAt" | "updatedAt">>;
|
|
9
9
|
|
|
@@ -11,12 +11,14 @@ export interface RenderPanelFailureReportInput {
|
|
|
11
11
|
run: ReportRun;
|
|
12
12
|
failures: readonly FailedPanelSummary[];
|
|
13
13
|
error?: string;
|
|
14
|
+
judgeModel?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface RenderSinglePanelReportInput {
|
|
17
18
|
run: ReportRun;
|
|
18
19
|
output: PanelOutput;
|
|
19
20
|
failures: readonly FailedPanelSummary[];
|
|
21
|
+
judgeModel?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface RenderJudgeReportInput {
|
|
@@ -24,6 +26,7 @@ export interface RenderJudgeReportInput {
|
|
|
24
26
|
judgeOutput: string;
|
|
25
27
|
panelOutputs?: readonly PanelOutput[];
|
|
26
28
|
failures?: readonly FailedPanelSummary[];
|
|
29
|
+
judgeModel?: string;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
export interface RenderFailureReportInput {
|
|
@@ -31,6 +34,7 @@ export interface RenderFailureReportInput {
|
|
|
31
34
|
error: string;
|
|
32
35
|
panelOutputs?: readonly PanelOutput[];
|
|
33
36
|
failures?: readonly FailedPanelSummary[];
|
|
37
|
+
judgeModel?: string;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export interface RenderCancelledReportInput {
|
|
@@ -39,6 +43,7 @@ export interface RenderCancelledReportInput {
|
|
|
39
43
|
targetRunId?: string;
|
|
40
44
|
panelOutputs?: readonly PanelOutput[];
|
|
41
45
|
failures?: readonly FailedPanelSummary[];
|
|
46
|
+
judgeModel?: string;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
type ReportSectionTitle =
|
|
@@ -62,6 +67,7 @@ interface AgentStatusOptions {
|
|
|
62
67
|
panelOutputs?: readonly PanelOutput[];
|
|
63
68
|
failures?: readonly FailedPanelSummary[];
|
|
64
69
|
judgeStatus: string;
|
|
70
|
+
judgeModel?: string;
|
|
65
71
|
extra?: readonly string[];
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -80,6 +86,7 @@ export function renderPanelFailureReport(
|
|
|
80
86
|
panelOutputs: [],
|
|
81
87
|
failures: input.failures,
|
|
82
88
|
judgeStatus: "not run - no successful panelists",
|
|
89
|
+
...(input.judgeModel ? { judgeModel: input.judgeModel } : {}),
|
|
83
90
|
}),
|
|
84
91
|
},
|
|
85
92
|
{
|
|
@@ -132,6 +139,7 @@ export function renderSinglePanelReport(
|
|
|
132
139
|
panelOutputs: [input.output],
|
|
133
140
|
failures: input.failures,
|
|
134
141
|
judgeStatus: "skipped - one successful panelist",
|
|
142
|
+
...(input.judgeModel ? { judgeModel: input.judgeModel } : {}),
|
|
135
143
|
}),
|
|
136
144
|
},
|
|
137
145
|
{
|
|
@@ -193,6 +201,7 @@ export function renderJudgeReport(input: RenderJudgeReportInput): string {
|
|
|
193
201
|
panelOutputs,
|
|
194
202
|
failures,
|
|
195
203
|
judgeStatus: "succeeded",
|
|
204
|
+
...(input.judgeModel ? { judgeModel: input.judgeModel } : {}),
|
|
196
205
|
}),
|
|
197
206
|
},
|
|
198
207
|
{
|
|
@@ -244,6 +253,7 @@ export function renderFailureReport(input: RenderFailureReportInput): string {
|
|
|
244
253
|
: {}),
|
|
245
254
|
...(input.failures !== undefined ? { failures: input.failures } : {}),
|
|
246
255
|
judgeStatus: `failed - ${firstLine(input.error)}`,
|
|
256
|
+
...(input.judgeModel ? { judgeModel: input.judgeModel } : {}),
|
|
247
257
|
extra: [`- Phase: ${phase}`],
|
|
248
258
|
}),
|
|
249
259
|
},
|
|
@@ -291,6 +301,7 @@ export function renderCancelledReport(
|
|
|
291
301
|
: {}),
|
|
292
302
|
...(input.failures !== undefined ? { failures: input.failures } : {}),
|
|
293
303
|
judgeStatus: "cancelled or not completed",
|
|
304
|
+
...(input.judgeModel ? { judgeModel: input.judgeModel } : {}),
|
|
294
305
|
extra: [
|
|
295
306
|
`- Phase: ${input.run.phase ?? "unknown"}`,
|
|
296
307
|
`- Cancellation method: ${input.method}`,
|
|
@@ -371,15 +382,21 @@ function formatAgentStatus(options: AgentStatusOptions): string[] {
|
|
|
371
382
|
}
|
|
372
383
|
|
|
373
384
|
lines.push(`- Judge: ${options.judgeStatus}`);
|
|
385
|
+
if (options.judgeModel) lines.push(` Model: ${options.judgeModel}`);
|
|
374
386
|
if (options.extra) lines.push(...options.extra);
|
|
375
387
|
return lines;
|
|
376
388
|
}
|
|
377
389
|
|
|
378
390
|
function formatPanelDetails(
|
|
379
|
-
item: Pick<
|
|
391
|
+
item: Pick<
|
|
392
|
+
PanelOutput,
|
|
393
|
+
"agent" | "role" | "model" | "artifactPath" | "sessionPath"
|
|
394
|
+
>,
|
|
380
395
|
): string[] {
|
|
381
396
|
return [
|
|
382
397
|
` Agent: ${item.agent}`,
|
|
398
|
+
...(item.role ? [` Role: ${item.role}`] : []),
|
|
399
|
+
...(item.model ? [` Model: ${item.model}`] : []),
|
|
383
400
|
...(item.artifactPath ? [` Artifact: ${item.artifactPath}`] : []),
|
|
384
401
|
...(item.sessionPath ? [` Session: ${item.sessionPath}`] : []),
|
|
385
402
|
];
|
|
@@ -391,8 +408,9 @@ function formatRunMetadata(run: ReportRun): string[] {
|
|
|
391
408
|
`- Profile: ${run.profileName}`,
|
|
392
409
|
...(run.phase ? [`- Phase: ${run.phase}`] : []),
|
|
393
410
|
`- Prompt: ${firstLine(run.prompt)}`,
|
|
411
|
+
...(run.chainRunId ? [`- Chain run: ${run.chainRunId}`] : []),
|
|
394
412
|
...(run.panelRunId ? [`- Panel run: ${run.panelRunId}`] : []),
|
|
395
|
-
...(run.judgeRunId ? [`-
|
|
413
|
+
...(run.judgeRunId ? [`- Fallback judge run: ${run.judgeRunId}`] : []),
|
|
396
414
|
...(typeof run.createdAt === "number"
|
|
397
415
|
? [`- Created: ${formatTimestamp(run.createdAt)}`]
|
|
398
416
|
: []),
|
package/src/result-extract.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
appendThinkingSuffix,
|
|
3
|
+
type FailedPanelSummary,
|
|
4
|
+
type PanelOutput,
|
|
5
|
+
} from "./run-builder.js";
|
|
2
6
|
import type { PanelMemberConfig } from "./types.js";
|
|
3
7
|
|
|
4
8
|
export type ResultExtractErrorCode =
|
|
@@ -12,6 +16,7 @@ export interface ResultExtractError {
|
|
|
12
16
|
|
|
13
17
|
export interface ExtractPanelResultsOptions {
|
|
14
18
|
panel?: readonly PanelMemberConfig[];
|
|
19
|
+
limit?: number;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
export type ExtractPanelResultsResult =
|
|
@@ -40,7 +45,11 @@ export function extractPanelResults(
|
|
|
40
45
|
|
|
41
46
|
const outputs: PanelOutput[] = [];
|
|
42
47
|
const failures: FailedPanelSummary[] = [];
|
|
43
|
-
|
|
48
|
+
const results =
|
|
49
|
+
options.limit === undefined
|
|
50
|
+
? container.results
|
|
51
|
+
: container.results.slice(0, options.limit);
|
|
52
|
+
for (const [index, rawResult] of results.entries()) {
|
|
44
53
|
const child = normalizeChildResult(rawResult, index, options);
|
|
45
54
|
if (!child.ok) return child;
|
|
46
55
|
if (child.status === "success") outputs.push(child.output);
|
|
@@ -232,12 +241,17 @@ function buildPanelOutput(input: {
|
|
|
232
241
|
artifactPath: string | undefined;
|
|
233
242
|
sessionPath: string | undefined;
|
|
234
243
|
}): PanelOutput {
|
|
244
|
+
const model = input.member
|
|
245
|
+
? appendThinkingSuffix(input.member.model, input.member.thinking)
|
|
246
|
+
: undefined;
|
|
235
247
|
return {
|
|
236
248
|
index: input.index,
|
|
237
249
|
agent: input.agent,
|
|
238
250
|
output: input.output,
|
|
239
251
|
...(input.member?.id ? { id: input.member.id } : {}),
|
|
240
252
|
...(input.member?.label ? { label: input.member.label } : {}),
|
|
253
|
+
...(input.member?.role ? { role: input.member.role } : {}),
|
|
254
|
+
...(model ? { model } : {}),
|
|
241
255
|
...(input.artifactPath ? { artifactPath: input.artifactPath } : {}),
|
|
242
256
|
...(input.sessionPath ? { sessionPath: input.sessionPath } : {}),
|
|
243
257
|
};
|
|
@@ -251,12 +265,17 @@ function buildFailedPanelSummary(input: {
|
|
|
251
265
|
artifactPath: string | undefined;
|
|
252
266
|
sessionPath: string | undefined;
|
|
253
267
|
}): FailedPanelSummary {
|
|
268
|
+
const model = input.member
|
|
269
|
+
? appendThinkingSuffix(input.member.model, input.member.thinking)
|
|
270
|
+
: undefined;
|
|
254
271
|
return {
|
|
255
272
|
index: input.index,
|
|
256
273
|
agent: input.agent,
|
|
257
274
|
summary: input.summary,
|
|
258
275
|
...(input.member?.id ? { id: input.member.id } : {}),
|
|
259
276
|
...(input.member?.label ? { label: input.member.label } : {}),
|
|
277
|
+
...(input.member?.role ? { role: input.member.role } : {}),
|
|
278
|
+
...(model ? { model } : {}),
|
|
260
279
|
...(input.artifactPath ? { artifactPath: input.artifactPath } : {}),
|
|
261
280
|
...(input.sessionPath ? { sessionPath: input.sessionPath } : {}),
|
|
262
281
|
};
|
package/src/run-builder.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
THINKING_LEVELS,
|
|
3
|
+
type FailedPanelSummary,
|
|
3
4
|
type FusionProfile,
|
|
4
5
|
type PanelMemberConfig,
|
|
6
|
+
type PanelOutput,
|
|
5
7
|
type ThinkingLevel,
|
|
6
8
|
} from "./types.js";
|
|
7
9
|
|
|
@@ -24,6 +26,12 @@ export interface PanelSubagentTaskParams {
|
|
|
24
26
|
model?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
export interface PanelChainTaskParams extends PanelSubagentTaskParams {
|
|
30
|
+
as: string;
|
|
31
|
+
label: string;
|
|
32
|
+
phase: "Panel";
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
export interface PanelSpawnParams {
|
|
28
36
|
tasks: PanelSubagentTaskParams[];
|
|
29
37
|
async: true;
|
|
@@ -36,39 +44,51 @@ export interface PanelSpawnParams {
|
|
|
36
44
|
timeoutMs?: number;
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
export interface
|
|
47
|
+
export interface FusionChainParallelStepParams {
|
|
48
|
+
parallel: PanelChainTaskParams[];
|
|
49
|
+
concurrency: number;
|
|
50
|
+
failFast: false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FusionChainJudgeStepParams {
|
|
40
54
|
agent: string;
|
|
41
55
|
task: string;
|
|
56
|
+
label: "Judge";
|
|
57
|
+
phase: "Judge";
|
|
58
|
+
output: true;
|
|
59
|
+
outputMode: "inline";
|
|
60
|
+
skill: false;
|
|
61
|
+
acceptance: FusionAcceptanceDisabled;
|
|
62
|
+
model?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface FusionChainSpawnParams {
|
|
66
|
+
chain: [FusionChainParallelStepParams, FusionChainJudgeStepParams];
|
|
67
|
+
task: string;
|
|
42
68
|
async: true;
|
|
43
69
|
clarify: false;
|
|
44
70
|
context: "fresh" | "fork";
|
|
45
71
|
output: true;
|
|
46
72
|
outputMode: "inline";
|
|
47
|
-
skill: false;
|
|
48
73
|
acceptance: FusionAcceptanceDisabled;
|
|
49
|
-
model?: string;
|
|
50
74
|
timeoutMs?: number;
|
|
51
75
|
}
|
|
52
76
|
|
|
53
|
-
export interface
|
|
54
|
-
index: number;
|
|
77
|
+
export interface JudgeSpawnParams {
|
|
55
78
|
agent: string;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
task: string;
|
|
80
|
+
async: true;
|
|
81
|
+
clarify: false;
|
|
82
|
+
context: "fresh" | "fork";
|
|
83
|
+
output: true;
|
|
84
|
+
outputMode: "inline";
|
|
85
|
+
skill: false;
|
|
86
|
+
acceptance: FusionAcceptanceDisabled;
|
|
87
|
+
model?: string;
|
|
88
|
+
timeoutMs?: number;
|
|
61
89
|
}
|
|
62
90
|
|
|
63
|
-
export
|
|
64
|
-
index: number;
|
|
65
|
-
agent: string;
|
|
66
|
-
summary: string;
|
|
67
|
-
id?: string;
|
|
68
|
-
label?: string;
|
|
69
|
-
artifactPath?: string;
|
|
70
|
-
sessionPath?: string;
|
|
71
|
-
}
|
|
91
|
+
export type { FailedPanelSummary, PanelOutput } from "./types.js";
|
|
72
92
|
|
|
73
93
|
export interface BuildJudgeSpawnParamsInput {
|
|
74
94
|
profile: FusionProfile;
|
|
@@ -127,6 +147,48 @@ export function buildPanelSpawnParams(
|
|
|
127
147
|
};
|
|
128
148
|
}
|
|
129
149
|
|
|
150
|
+
export function buildFusionChainSpawnParams(
|
|
151
|
+
profile: FusionProfile,
|
|
152
|
+
prompt: string,
|
|
153
|
+
): FusionChainSpawnParams {
|
|
154
|
+
const model = appendThinkingSuffix(
|
|
155
|
+
profile.judge.model,
|
|
156
|
+
profile.judge.thinking,
|
|
157
|
+
);
|
|
158
|
+
return {
|
|
159
|
+
chain: [
|
|
160
|
+
{
|
|
161
|
+
parallel: profile.panel.map((member, index) =>
|
|
162
|
+
buildPanelChainTaskParams(member, index),
|
|
163
|
+
),
|
|
164
|
+
concurrency: profile.concurrency ?? profile.panel.length,
|
|
165
|
+
failFast: false,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
agent: profile.judge.agent,
|
|
169
|
+
task: buildChainJudgeTask(profile.panel),
|
|
170
|
+
label: "Judge",
|
|
171
|
+
phase: "Judge",
|
|
172
|
+
output: true,
|
|
173
|
+
outputMode: "inline",
|
|
174
|
+
skill: false,
|
|
175
|
+
acceptance: FUSION_ACCEPTANCE_DISABLED,
|
|
176
|
+
...(model ? { model } : {}),
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
task: prompt.trim(),
|
|
180
|
+
async: true,
|
|
181
|
+
clarify: false,
|
|
182
|
+
context: profile.context ?? "fresh",
|
|
183
|
+
output: true,
|
|
184
|
+
outputMode: "inline",
|
|
185
|
+
acceptance: FUSION_ACCEPTANCE_DISABLED,
|
|
186
|
+
...(profile.timeoutMs !== undefined
|
|
187
|
+
? { timeoutMs: profile.timeoutMs }
|
|
188
|
+
: {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
130
192
|
export function buildJudgeSpawnParams(
|
|
131
193
|
input: BuildJudgeSpawnParamsInput,
|
|
132
194
|
): JudgeSpawnParams {
|
|
@@ -168,6 +230,26 @@ function buildPanelTaskParams(
|
|
|
168
230
|
};
|
|
169
231
|
}
|
|
170
232
|
|
|
233
|
+
function buildPanelChainTaskParams(
|
|
234
|
+
member: PanelMemberConfig,
|
|
235
|
+
index: number,
|
|
236
|
+
): PanelChainTaskParams {
|
|
237
|
+
const model = appendThinkingSuffix(member.model, member.thinking);
|
|
238
|
+
return {
|
|
239
|
+
agent: member.agent,
|
|
240
|
+
task: buildPanelTask(member, "{task}"),
|
|
241
|
+
as: chainOutputName(member, index),
|
|
242
|
+
label: member.label,
|
|
243
|
+
phase: "Panel",
|
|
244
|
+
output: true,
|
|
245
|
+
outputMode: "inline",
|
|
246
|
+
progress: true,
|
|
247
|
+
skill: false,
|
|
248
|
+
acceptance: FUSION_ACCEPTANCE_DISABLED,
|
|
249
|
+
...(model ? { model } : {}),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
171
253
|
function buildPanelTask(member: PanelMemberConfig, prompt: string): string {
|
|
172
254
|
const role = member.role?.trim() || "independent analysis and critique";
|
|
173
255
|
return [
|
|
@@ -215,6 +297,30 @@ function buildJudgeTask(input: BuildJudgeSpawnParamsInput): string {
|
|
|
215
297
|
].join("\n");
|
|
216
298
|
}
|
|
217
299
|
|
|
300
|
+
function buildChainJudgeTask(panel: readonly PanelMemberConfig[]): string {
|
|
301
|
+
return [
|
|
302
|
+
"You are the fusion judge.",
|
|
303
|
+
"Read-only synthesis only. Leave files, git state, and the workspace untouched. Do not ask other agents. Do not run subagents.",
|
|
304
|
+
"Synthesize the panel results. Preserve disagreement instead of forcing consensus.",
|
|
305
|
+
"",
|
|
306
|
+
"Original task:",
|
|
307
|
+
"{task}",
|
|
308
|
+
"",
|
|
309
|
+
"All listed panelists completed successfully.",
|
|
310
|
+
"",
|
|
311
|
+
"Panel outputs:",
|
|
312
|
+
...panel.flatMap((member, index) => [
|
|
313
|
+
`## ${member.label} (${member.id})`,
|
|
314
|
+
`Agent: ${member.agent}`,
|
|
315
|
+
"",
|
|
316
|
+
`{outputs.${chainOutputName(member, index)}}`,
|
|
317
|
+
"",
|
|
318
|
+
]),
|
|
319
|
+
"Output contract:",
|
|
320
|
+
...JUDGE_OUTPUT_CONTRACT,
|
|
321
|
+
].join("\n");
|
|
322
|
+
}
|
|
323
|
+
|
|
218
324
|
function formatPanelStatus(
|
|
219
325
|
outputs: readonly PanelOutput[],
|
|
220
326
|
failures: readonly FailedPanelSummary[],
|
|
@@ -275,6 +381,12 @@ function firstLine(value: string): string {
|
|
|
275
381
|
return value.split(/\r?\n/, 1)[0]?.trim() || "unknown failure";
|
|
276
382
|
}
|
|
277
383
|
|
|
384
|
+
function chainOutputName(member: PanelMemberConfig, index: number): string {
|
|
385
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(member.id)
|
|
386
|
+
? member.id
|
|
387
|
+
: `panel_${index + 1}`;
|
|
388
|
+
}
|
|
389
|
+
|
|
278
390
|
function hasThinkingSuffix(model: string): boolean {
|
|
279
391
|
const colonIndex = model.lastIndexOf(":");
|
|
280
392
|
if (colonIndex === -1) return false;
|
package/src/run-store.ts
CHANGED
|
@@ -17,6 +17,7 @@ export type FusionRunSummary = Omit<
|
|
|
17
17
|
| "phase"
|
|
18
18
|
| "createdAt"
|
|
19
19
|
| "updatedAt"
|
|
20
|
+
| "chainRunId"
|
|
20
21
|
| "panelRunId"
|
|
21
22
|
| "judgeRunId"
|
|
22
23
|
| "report"
|
|
@@ -29,19 +30,26 @@ export interface FusionRunStartInput {
|
|
|
29
30
|
id?: string;
|
|
30
31
|
prompt: string;
|
|
31
32
|
profileName: string;
|
|
33
|
+
phase?: Exclude<FusionPhase, FusionTerminalPhase>;
|
|
32
34
|
createdAt?: number;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface FusionRunPatch {
|
|
36
38
|
phase?: Exclude<FusionPhase, FusionTerminalPhase>;
|
|
39
|
+
chainRunId?: string;
|
|
40
|
+
chainAsyncDir?: string;
|
|
37
41
|
panelRunId?: string;
|
|
38
42
|
judgeRunId?: string;
|
|
43
|
+
judgeAsyncDir?: string;
|
|
44
|
+
panelOutputs?: FusionRun["panelOutputs"];
|
|
45
|
+
panelFailures?: FusionRun["panelFailures"];
|
|
39
46
|
report?: string;
|
|
40
47
|
error?: string;
|
|
41
48
|
updatedAt?: number;
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
export interface FusionRunTransitionPatch {
|
|
52
|
+
chainRunId?: string;
|
|
45
53
|
panelRunId?: string;
|
|
46
54
|
judgeRunId?: string;
|
|
47
55
|
report?: string;
|
|
@@ -106,7 +114,7 @@ export class FusionRunStore {
|
|
|
106
114
|
id: input.id ?? this.idFactory(),
|
|
107
115
|
prompt: input.prompt,
|
|
108
116
|
profileName: input.profileName,
|
|
109
|
-
phase: "
|
|
117
|
+
phase: input.phase ?? "chain",
|
|
110
118
|
createdAt,
|
|
111
119
|
updatedAt: createdAt,
|
|
112
120
|
};
|
|
@@ -246,8 +254,21 @@ function applyPatch(
|
|
|
246
254
|
const updated = cloneRun(run);
|
|
247
255
|
updated.updatedAt = updatedAt;
|
|
248
256
|
if (patch.phase !== undefined) updated.phase = patch.phase;
|
|
257
|
+
if (patch.chainRunId !== undefined) updated.chainRunId = patch.chainRunId;
|
|
258
|
+
if (patch.chainAsyncDir !== undefined) {
|
|
259
|
+
updated.chainAsyncDir = patch.chainAsyncDir;
|
|
260
|
+
}
|
|
249
261
|
if (patch.panelRunId !== undefined) updated.panelRunId = patch.panelRunId;
|
|
250
262
|
if (patch.judgeRunId !== undefined) updated.judgeRunId = patch.judgeRunId;
|
|
263
|
+
if (patch.judgeAsyncDir !== undefined) {
|
|
264
|
+
updated.judgeAsyncDir = patch.judgeAsyncDir;
|
|
265
|
+
}
|
|
266
|
+
if (patch.panelOutputs !== undefined) {
|
|
267
|
+
updated.panelOutputs = clonePanelOutputs(patch.panelOutputs);
|
|
268
|
+
}
|
|
269
|
+
if (patch.panelFailures !== undefined) {
|
|
270
|
+
updated.panelFailures = clonePanelFailures(patch.panelFailures);
|
|
271
|
+
}
|
|
251
272
|
if (patch.report !== undefined) updated.report = patch.report;
|
|
252
273
|
if (patch.error !== undefined) updated.error = patch.error;
|
|
253
274
|
return updated;
|
|
@@ -264,6 +285,7 @@ function applyTransitionPatch(
|
|
|
264
285
|
phase,
|
|
265
286
|
};
|
|
266
287
|
updated.updatedAt = updatedAt;
|
|
288
|
+
if (patch.chainRunId !== undefined) updated.chainRunId = patch.chainRunId;
|
|
267
289
|
if (patch.panelRunId !== undefined) updated.panelRunId = patch.panelRunId;
|
|
268
290
|
if (patch.judgeRunId !== undefined) updated.judgeRunId = patch.judgeRunId;
|
|
269
291
|
if (patch.report !== undefined) updated.report = patch.report;
|
|
@@ -281,6 +303,7 @@ function toRunSummary(
|
|
|
281
303
|
phase: run.phase,
|
|
282
304
|
createdAt: run.createdAt,
|
|
283
305
|
updatedAt: run.updatedAt,
|
|
306
|
+
...(run.chainRunId !== undefined ? { chainRunId: run.chainRunId } : {}),
|
|
284
307
|
...(run.panelRunId !== undefined ? { panelRunId: run.panelRunId } : {}),
|
|
285
308
|
...(run.judgeRunId !== undefined ? { judgeRunId: run.judgeRunId } : {}),
|
|
286
309
|
...(run.report !== undefined ? { report: run.report } : {}),
|
|
@@ -296,8 +319,21 @@ function cloneRun(run: FusionRun): FusionRun {
|
|
|
296
319
|
phase: run.phase,
|
|
297
320
|
createdAt: run.createdAt,
|
|
298
321
|
updatedAt: run.updatedAt,
|
|
322
|
+
...(run.chainRunId !== undefined ? { chainRunId: run.chainRunId } : {}),
|
|
323
|
+
...(run.chainAsyncDir !== undefined
|
|
324
|
+
? { chainAsyncDir: run.chainAsyncDir }
|
|
325
|
+
: {}),
|
|
299
326
|
...(run.panelRunId !== undefined ? { panelRunId: run.panelRunId } : {}),
|
|
300
327
|
...(run.judgeRunId !== undefined ? { judgeRunId: run.judgeRunId } : {}),
|
|
328
|
+
...(run.judgeAsyncDir !== undefined
|
|
329
|
+
? { judgeAsyncDir: run.judgeAsyncDir }
|
|
330
|
+
: {}),
|
|
331
|
+
...(run.panelOutputs !== undefined
|
|
332
|
+
? { panelOutputs: clonePanelOutputs(run.panelOutputs) }
|
|
333
|
+
: {}),
|
|
334
|
+
...(run.panelFailures !== undefined
|
|
335
|
+
? { panelFailures: clonePanelFailures(run.panelFailures) }
|
|
336
|
+
: {}),
|
|
301
337
|
...(run.report !== undefined ? { report: run.report } : {}),
|
|
302
338
|
...(run.error !== undefined ? { error: run.error } : {}),
|
|
303
339
|
};
|
|
@@ -326,12 +362,39 @@ function isFusionRunState(value: unknown): value is FusionRun {
|
|
|
326
362
|
if (!isFusionPhase(value.phase)) return false;
|
|
327
363
|
if (!isFiniteNumber(value.createdAt)) return false;
|
|
328
364
|
if (!isFiniteNumber(value.updatedAt)) return false;
|
|
365
|
+
if (value.chainRunId !== undefined && typeof value.chainRunId !== "string") {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
if (
|
|
369
|
+
value.chainAsyncDir !== undefined &&
|
|
370
|
+
typeof value.chainAsyncDir !== "string"
|
|
371
|
+
) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
329
374
|
if (value.panelRunId !== undefined && typeof value.panelRunId !== "string") {
|
|
330
375
|
return false;
|
|
331
376
|
}
|
|
332
377
|
if (value.judgeRunId !== undefined && typeof value.judgeRunId !== "string") {
|
|
333
378
|
return false;
|
|
334
379
|
}
|
|
380
|
+
if (
|
|
381
|
+
value.judgeAsyncDir !== undefined &&
|
|
382
|
+
typeof value.judgeAsyncDir !== "string"
|
|
383
|
+
) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
if (
|
|
387
|
+
value.panelOutputs !== undefined &&
|
|
388
|
+
!isPanelOutputArray(value.panelOutputs)
|
|
389
|
+
) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
if (
|
|
393
|
+
value.panelFailures !== undefined &&
|
|
394
|
+
!isPanelFailureArray(value.panelFailures)
|
|
395
|
+
) {
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
335
398
|
if (value.report !== undefined && typeof value.report !== "string") {
|
|
336
399
|
return false;
|
|
337
400
|
}
|
|
@@ -348,6 +411,7 @@ function isFusionRunSummary(value: unknown): value is FusionRunSummary {
|
|
|
348
411
|
function isFusionPhase(value: unknown): value is FusionPhase {
|
|
349
412
|
return (
|
|
350
413
|
value === "panel" ||
|
|
414
|
+
value === "chain" ||
|
|
351
415
|
value === "judge" ||
|
|
352
416
|
value === "done" ||
|
|
353
417
|
value === "failed" ||
|
|
@@ -370,3 +434,59 @@ function isNonEmptyString(value: unknown): value is string {
|
|
|
370
434
|
function isFiniteNumber(value: unknown): value is number {
|
|
371
435
|
return typeof value === "number" && Number.isFinite(value);
|
|
372
436
|
}
|
|
437
|
+
|
|
438
|
+
function isPanelOutputArray(
|
|
439
|
+
value: unknown,
|
|
440
|
+
): value is NonNullable<FusionRun["panelOutputs"]> {
|
|
441
|
+
return Array.isArray(value) && value.every(isPanelOutput);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function isPanelOutput(
|
|
445
|
+
value: unknown,
|
|
446
|
+
): value is NonNullable<FusionRun["panelOutputs"]>[number] {
|
|
447
|
+
return (
|
|
448
|
+
isRecord(value) &&
|
|
449
|
+
isFiniteNumber(value.index) &&
|
|
450
|
+
isNonEmptyString(value.agent) &&
|
|
451
|
+
typeof value.output === "string" &&
|
|
452
|
+
(value.id === undefined || typeof value.id === "string") &&
|
|
453
|
+
(value.label === undefined || typeof value.label === "string") &&
|
|
454
|
+
(value.artifactPath === undefined ||
|
|
455
|
+
typeof value.artifactPath === "string") &&
|
|
456
|
+
(value.sessionPath === undefined || typeof value.sessionPath === "string")
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function isPanelFailureArray(
|
|
461
|
+
value: unknown,
|
|
462
|
+
): value is NonNullable<FusionRun["panelFailures"]> {
|
|
463
|
+
return Array.isArray(value) && value.every(isPanelFailure);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isPanelFailure(
|
|
467
|
+
value: unknown,
|
|
468
|
+
): value is NonNullable<FusionRun["panelFailures"]>[number] {
|
|
469
|
+
return (
|
|
470
|
+
isRecord(value) &&
|
|
471
|
+
isFiniteNumber(value.index) &&
|
|
472
|
+
isNonEmptyString(value.agent) &&
|
|
473
|
+
typeof value.summary === "string" &&
|
|
474
|
+
(value.id === undefined || typeof value.id === "string") &&
|
|
475
|
+
(value.label === undefined || typeof value.label === "string") &&
|
|
476
|
+
(value.artifactPath === undefined ||
|
|
477
|
+
typeof value.artifactPath === "string") &&
|
|
478
|
+
(value.sessionPath === undefined || typeof value.sessionPath === "string")
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function clonePanelOutputs(
|
|
483
|
+
outputs: NonNullable<FusionRun["panelOutputs"]>,
|
|
484
|
+
): NonNullable<FusionRun["panelOutputs"]> {
|
|
485
|
+
return outputs.map((output) => ({ ...output }));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function clonePanelFailures(
|
|
489
|
+
failures: NonNullable<FusionRun["panelFailures"]>,
|
|
490
|
+
): NonNullable<FusionRun["panelFailures"]> {
|
|
491
|
+
return failures.map((failure) => ({ ...failure }));
|
|
492
|
+
}
|
package/src/status.ts
CHANGED
|
@@ -23,12 +23,16 @@ export function publishFusionStatus(
|
|
|
23
23
|
ctx: FusionUiContext | undefined,
|
|
24
24
|
run: Pick<
|
|
25
25
|
FusionRun,
|
|
26
|
-
"id" | "phase" | "profileName" | "panelRunId" | "judgeRunId"
|
|
26
|
+
"id" | "phase" | "profileName" | "chainRunId" | "panelRunId" | "judgeRunId"
|
|
27
27
|
>,
|
|
28
28
|
progress?: FusionProgressCounts,
|
|
29
|
+
phaseLabel?: string,
|
|
29
30
|
): void {
|
|
30
31
|
if (!ctx?.hasUI) return;
|
|
31
|
-
ctx.ui.setStatus(
|
|
32
|
+
ctx.ui.setStatus(
|
|
33
|
+
FUSION_STATUS_KEY,
|
|
34
|
+
formatFusionStatusText(run, progress, phaseLabel),
|
|
35
|
+
);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
export function clearFusionUi(ctx: FusionUiContext | undefined): void {
|
|
@@ -37,20 +41,30 @@ export function clearFusionUi(ctx: FusionUiContext | undefined): void {
|
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
export function formatFusionStatusText(
|
|
40
|
-
run: Pick<
|
|
44
|
+
run: Pick<
|
|
45
|
+
FusionRun,
|
|
46
|
+
"phase" | "profileName" | "chainRunId" | "panelRunId" | "judgeRunId"
|
|
47
|
+
>,
|
|
41
48
|
progress?: FusionProgressCounts,
|
|
49
|
+
phaseLabel?: string,
|
|
42
50
|
): string {
|
|
43
|
-
const activeRunId =
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
51
|
+
const activeRunId =
|
|
52
|
+
run.phase === "judge" ? run.judgeRunId : (run.chainRunId ?? run.panelRunId);
|
|
53
|
+
const phase = phaseLabel ?? run.phase;
|
|
54
|
+
if (progress) {
|
|
55
|
+
return `fusion: panel · ${formatProgressCounts(progress)} · ${run.profileName}`;
|
|
56
|
+
}
|
|
57
|
+
if (activeRunId) {
|
|
58
|
+
return `fusion: ${phase} · ${run.profileName} · ${activeRunId}`;
|
|
59
|
+
}
|
|
60
|
+
return `fusion: ${phase} · starting · ${run.profileName}`;
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
export function extractFusionProgressCounts(
|
|
50
64
|
payload: unknown,
|
|
51
65
|
): FusionProgressCounts | undefined {
|
|
52
66
|
const container = findProgressContainer(payload);
|
|
53
|
-
if (!container) return undefined;
|
|
67
|
+
if (!container || container.length === 0) return undefined;
|
|
54
68
|
|
|
55
69
|
const counts: FusionProgressCounts = {
|
|
56
70
|
total: container.length,
|
|
@@ -84,15 +98,19 @@ function findProgressContainer(
|
|
|
84
98
|
payload: unknown,
|
|
85
99
|
): readonly unknown[] | undefined {
|
|
86
100
|
if (!isRecord(payload)) return undefined;
|
|
87
|
-
const progress =
|
|
101
|
+
const progress = nonEmptyArray(payload.progress);
|
|
88
102
|
if (progress) return progress;
|
|
89
|
-
const results =
|
|
103
|
+
const results = nonEmptyArray(payload.results);
|
|
90
104
|
if (results) return results;
|
|
105
|
+
const steps = nonEmptyArray(payload.steps);
|
|
106
|
+
if (steps) return steps;
|
|
91
107
|
if (isRecord(payload.details)) {
|
|
92
|
-
const detailsProgress =
|
|
108
|
+
const detailsProgress = nonEmptyArray(payload.details.progress);
|
|
93
109
|
if (detailsProgress) return detailsProgress;
|
|
94
|
-
const detailsResults =
|
|
110
|
+
const detailsResults = nonEmptyArray(payload.details.results);
|
|
95
111
|
if (detailsResults) return detailsResults;
|
|
112
|
+
const detailsSteps = nonEmptyArray(payload.details.steps);
|
|
113
|
+
if (detailsSteps) return detailsSteps;
|
|
96
114
|
}
|
|
97
115
|
if (isRecord(payload.data)) return findProgressContainer(payload.data);
|
|
98
116
|
return undefined;
|
|
@@ -127,8 +145,10 @@ function firstString(...values: readonly unknown[]): string | undefined {
|
|
|
127
145
|
return undefined;
|
|
128
146
|
}
|
|
129
147
|
|
|
130
|
-
function
|
|
131
|
-
return Array.isArray(value)
|
|
148
|
+
function nonEmptyArray(value: unknown): readonly unknown[] | undefined {
|
|
149
|
+
return Array.isArray(value) && value.length > 0
|
|
150
|
+
? (value as readonly unknown[])
|
|
151
|
+
: undefined;
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
function isRecord(value: unknown): value is Record<string, unknown> {
|