@alexeiled/pi-fusion 0.1.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/AGENTS.md +7 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/agents/fusion-judge.md +38 -0
- package/agents/fusion-panelist.md +30 -0
- package/docs/user-guide.md +252 -0
- package/package.json +74 -0
- package/src/commands.ts +273 -0
- package/src/config.ts +256 -0
- package/src/errors.ts +13 -0
- package/src/index.ts +34 -0
- package/src/orchestrator.ts +708 -0
- package/src/report.ts +478 -0
- package/src/result-extract.ts +311 -0
- package/src/run-builder.ts +273 -0
- package/src/run-store.ts +372 -0
- package/src/status.ts +136 -0
- package/src/subagents-rpc.ts +407 -0
- package/src/types.ts +54 -0
- package/src/utils.ts +0 -0
- package/tsconfig.json +19 -0
package/src/run-store.ts
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { FusionPhase, FusionRun } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const FUSION_RUN_ENTRY_TYPE = "fusion-run";
|
|
5
|
+
|
|
6
|
+
export type FusionTerminalPhase = Extract<
|
|
7
|
+
FusionPhase,
|
|
8
|
+
"done" | "failed" | "cancelled"
|
|
9
|
+
>;
|
|
10
|
+
|
|
11
|
+
export type FusionRunSummary = Omit<
|
|
12
|
+
Pick<
|
|
13
|
+
FusionRun,
|
|
14
|
+
| "id"
|
|
15
|
+
| "prompt"
|
|
16
|
+
| "profileName"
|
|
17
|
+
| "phase"
|
|
18
|
+
| "createdAt"
|
|
19
|
+
| "updatedAt"
|
|
20
|
+
| "panelRunId"
|
|
21
|
+
| "judgeRunId"
|
|
22
|
+
| "report"
|
|
23
|
+
| "error"
|
|
24
|
+
>,
|
|
25
|
+
"phase"
|
|
26
|
+
> & { phase: FusionTerminalPhase };
|
|
27
|
+
|
|
28
|
+
export interface FusionRunStartInput {
|
|
29
|
+
id?: string;
|
|
30
|
+
prompt: string;
|
|
31
|
+
profileName: string;
|
|
32
|
+
createdAt?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface FusionRunPatch {
|
|
36
|
+
phase?: Exclude<FusionPhase, FusionTerminalPhase>;
|
|
37
|
+
panelRunId?: string;
|
|
38
|
+
judgeRunId?: string;
|
|
39
|
+
report?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
updatedAt?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface FusionRunTransitionPatch {
|
|
45
|
+
panelRunId?: string;
|
|
46
|
+
judgeRunId?: string;
|
|
47
|
+
report?: string;
|
|
48
|
+
error?: string;
|
|
49
|
+
updatedAt?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface FusionRunStorePersistence {
|
|
53
|
+
appendEntry(customType: string, data?: unknown): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface FusionRunSessionContext {
|
|
57
|
+
sessionManager: {
|
|
58
|
+
getEntries(): readonly unknown[];
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface FusionRunStoreOptions {
|
|
63
|
+
now?: () => number;
|
|
64
|
+
idFactory?: () => string;
|
|
65
|
+
persistence?: FusionRunStorePersistence;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class FusionRunStoreError extends Error {
|
|
69
|
+
constructor(message: string) {
|
|
70
|
+
super(message);
|
|
71
|
+
this.name = "FusionRunStoreError";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class FusionRunStore {
|
|
76
|
+
private activeRun: FusionRun | undefined;
|
|
77
|
+
private lastRunSummary: FusionRunSummary | undefined;
|
|
78
|
+
private readonly now: () => number;
|
|
79
|
+
private readonly idFactory: () => string;
|
|
80
|
+
private readonly persistence: FusionRunStorePersistence | undefined;
|
|
81
|
+
|
|
82
|
+
constructor(options: FusionRunStoreOptions = {}) {
|
|
83
|
+
this.now = options.now ?? Date.now;
|
|
84
|
+
this.idFactory = options.idFactory ?? randomUUID;
|
|
85
|
+
this.persistence = options.persistence;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getActiveRun(): FusionRun | undefined {
|
|
89
|
+
return this.activeRun ? cloneRun(this.activeRun) : undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getLastRunSummary(): FusionRunSummary | undefined {
|
|
93
|
+
return this.lastRunSummary
|
|
94
|
+
? cloneRunSummary(this.lastRunSummary)
|
|
95
|
+
: undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
startRun(input: FusionRunStartInput): FusionRun {
|
|
99
|
+
if (this.activeRun) {
|
|
100
|
+
throw new FusionRunStoreError(
|
|
101
|
+
`Fusion run ${this.activeRun.id} is already active.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const createdAt = input.createdAt ?? this.now();
|
|
105
|
+
const run: FusionRun = {
|
|
106
|
+
id: input.id ?? this.idFactory(),
|
|
107
|
+
prompt: input.prompt,
|
|
108
|
+
profileName: input.profileName,
|
|
109
|
+
phase: "panel",
|
|
110
|
+
createdAt,
|
|
111
|
+
updatedAt: createdAt,
|
|
112
|
+
};
|
|
113
|
+
this.activeRun = run;
|
|
114
|
+
this.persistRun(run);
|
|
115
|
+
return cloneRun(run);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
updateRun(id: string, patch: FusionRunPatch): FusionRun {
|
|
119
|
+
const active = this.requireActiveRun(id);
|
|
120
|
+
const updated = applyPatch(active, patch, patch.updatedAt ?? this.now());
|
|
121
|
+
this.activeRun = updated;
|
|
122
|
+
this.persistRun(updated);
|
|
123
|
+
return cloneRun(updated);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
completeRun(id: string, patch: FusionRunTransitionPatch = {}): FusionRun {
|
|
127
|
+
return this.transitionRun(id, "done", patch);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
failRun(id: string, patch: FusionRunTransitionPatch = {}): FusionRun {
|
|
131
|
+
return this.transitionRun(id, "failed", patch);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
cancelRun(id: string, patch: FusionRunTransitionPatch = {}): FusionRun {
|
|
135
|
+
return this.transitionRun(id, "cancelled", patch);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
transitionRun(
|
|
139
|
+
id: string,
|
|
140
|
+
phase: FusionTerminalPhase,
|
|
141
|
+
patch: FusionRunTransitionPatch = {},
|
|
142
|
+
): FusionRun {
|
|
143
|
+
const active = this.requireActiveRun(id);
|
|
144
|
+
const finished = applyTransitionPatch(
|
|
145
|
+
active,
|
|
146
|
+
phase,
|
|
147
|
+
patch,
|
|
148
|
+
patch.updatedAt ?? this.now(),
|
|
149
|
+
);
|
|
150
|
+
const summary = toRunSummary(finished);
|
|
151
|
+
this.activeRun = undefined;
|
|
152
|
+
this.lastRunSummary = summary;
|
|
153
|
+
this.persistRun(summary);
|
|
154
|
+
return cloneRun(finished);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
restoreFromEntries(
|
|
158
|
+
entries: readonly unknown[],
|
|
159
|
+
): FusionRunSummary | undefined {
|
|
160
|
+
const latestState = readLastFusionRunState(entries);
|
|
161
|
+
const summary = readLastFusionRunSummary(entries);
|
|
162
|
+
this.activeRun =
|
|
163
|
+
latestState && !isTerminalPhase(latestState.phase)
|
|
164
|
+
? cloneRun(latestState)
|
|
165
|
+
: undefined;
|
|
166
|
+
this.lastRunSummary = summary;
|
|
167
|
+
return summary ? cloneRunSummary(summary) : undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
restoreFromSession(
|
|
171
|
+
ctx: FusionRunSessionContext,
|
|
172
|
+
): FusionRunSummary | undefined {
|
|
173
|
+
return this.restoreFromEntries(ctx.sessionManager.getEntries());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
clearActiveRun(id?: string): void {
|
|
177
|
+
if (!this.activeRun) return;
|
|
178
|
+
if (id && this.activeRun.id !== id) {
|
|
179
|
+
throw new FusionRunStoreError(
|
|
180
|
+
`Fusion run ${id} is not active; active run is ${this.activeRun.id}.`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
this.activeRun = undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private persistRun(run: FusionRun): void {
|
|
187
|
+
this.persistence?.appendEntry(FUSION_RUN_ENTRY_TYPE, cloneRun(run));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private requireActiveRun(id: string): FusionRun {
|
|
191
|
+
if (!this.activeRun) {
|
|
192
|
+
throw new FusionRunStoreError("No active fusion run.");
|
|
193
|
+
}
|
|
194
|
+
if (this.activeRun.id !== id) {
|
|
195
|
+
throw new FusionRunStoreError(
|
|
196
|
+
`Fusion run ${id} is not active; active run is ${this.activeRun.id}.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return this.activeRun;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function readFusionRunStates(entries: readonly unknown[]): FusionRun[] {
|
|
204
|
+
const states: FusionRun[] = [];
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (!isFusionRunEntry(entry)) continue;
|
|
207
|
+
if (isFusionRunState(entry.data)) states.push(cloneRun(entry.data));
|
|
208
|
+
}
|
|
209
|
+
return states;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function readLastFusionRunState(
|
|
213
|
+
entries: readonly unknown[],
|
|
214
|
+
): FusionRun | undefined {
|
|
215
|
+
const states = readFusionRunStates(entries);
|
|
216
|
+
const state = states.at(-1);
|
|
217
|
+
return state ? cloneRun(state) : undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function readFusionRunSummaries(
|
|
221
|
+
entries: readonly unknown[],
|
|
222
|
+
): FusionRunSummary[] {
|
|
223
|
+
const summaries: FusionRunSummary[] = [];
|
|
224
|
+
for (const entry of entries) {
|
|
225
|
+
if (!isFusionRunEntry(entry)) continue;
|
|
226
|
+
if (isFusionRunSummary(entry.data)) {
|
|
227
|
+
summaries.push(cloneRunSummary(entry.data));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return summaries;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function readLastFusionRunSummary(
|
|
234
|
+
entries: readonly unknown[],
|
|
235
|
+
): FusionRunSummary | undefined {
|
|
236
|
+
const summaries = readFusionRunSummaries(entries);
|
|
237
|
+
const summary = summaries.at(-1);
|
|
238
|
+
return summary ? cloneRunSummary(summary) : undefined;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function applyPatch(
|
|
242
|
+
run: FusionRun,
|
|
243
|
+
patch: FusionRunPatch,
|
|
244
|
+
updatedAt: number,
|
|
245
|
+
): FusionRun {
|
|
246
|
+
const updated = cloneRun(run);
|
|
247
|
+
updated.updatedAt = updatedAt;
|
|
248
|
+
if (patch.phase !== undefined) updated.phase = patch.phase;
|
|
249
|
+
if (patch.panelRunId !== undefined) updated.panelRunId = patch.panelRunId;
|
|
250
|
+
if (patch.judgeRunId !== undefined) updated.judgeRunId = patch.judgeRunId;
|
|
251
|
+
if (patch.report !== undefined) updated.report = patch.report;
|
|
252
|
+
if (patch.error !== undefined) updated.error = patch.error;
|
|
253
|
+
return updated;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function applyTransitionPatch(
|
|
257
|
+
run: FusionRun,
|
|
258
|
+
phase: FusionTerminalPhase,
|
|
259
|
+
patch: FusionRunTransitionPatch,
|
|
260
|
+
updatedAt: number,
|
|
261
|
+
): FusionRun & { phase: FusionTerminalPhase } {
|
|
262
|
+
const updated: FusionRun & { phase: FusionTerminalPhase } = {
|
|
263
|
+
...cloneRun(run),
|
|
264
|
+
phase,
|
|
265
|
+
};
|
|
266
|
+
updated.updatedAt = updatedAt;
|
|
267
|
+
if (patch.panelRunId !== undefined) updated.panelRunId = patch.panelRunId;
|
|
268
|
+
if (patch.judgeRunId !== undefined) updated.judgeRunId = patch.judgeRunId;
|
|
269
|
+
if (patch.report !== undefined) updated.report = patch.report;
|
|
270
|
+
if (patch.error !== undefined) updated.error = patch.error;
|
|
271
|
+
return updated;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function toRunSummary(
|
|
275
|
+
run: FusionRun & { phase: FusionTerminalPhase },
|
|
276
|
+
): FusionRunSummary {
|
|
277
|
+
return {
|
|
278
|
+
id: run.id,
|
|
279
|
+
prompt: run.prompt,
|
|
280
|
+
profileName: run.profileName,
|
|
281
|
+
phase: run.phase,
|
|
282
|
+
createdAt: run.createdAt,
|
|
283
|
+
updatedAt: run.updatedAt,
|
|
284
|
+
...(run.panelRunId !== undefined ? { panelRunId: run.panelRunId } : {}),
|
|
285
|
+
...(run.judgeRunId !== undefined ? { judgeRunId: run.judgeRunId } : {}),
|
|
286
|
+
...(run.report !== undefined ? { report: run.report } : {}),
|
|
287
|
+
...(run.error !== undefined ? { error: run.error } : {}),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function cloneRun(run: FusionRun): FusionRun {
|
|
292
|
+
return {
|
|
293
|
+
id: run.id,
|
|
294
|
+
prompt: run.prompt,
|
|
295
|
+
profileName: run.profileName,
|
|
296
|
+
phase: run.phase,
|
|
297
|
+
createdAt: run.createdAt,
|
|
298
|
+
updatedAt: run.updatedAt,
|
|
299
|
+
...(run.panelRunId !== undefined ? { panelRunId: run.panelRunId } : {}),
|
|
300
|
+
...(run.judgeRunId !== undefined ? { judgeRunId: run.judgeRunId } : {}),
|
|
301
|
+
...(run.report !== undefined ? { report: run.report } : {}),
|
|
302
|
+
...(run.error !== undefined ? { error: run.error } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function cloneRunSummary(summary: FusionRunSummary): FusionRunSummary {
|
|
307
|
+
return toRunSummary(summary);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isFusionRunEntry(
|
|
311
|
+
value: unknown,
|
|
312
|
+
): value is { type: "custom"; customType: string; data: unknown } {
|
|
313
|
+
return (
|
|
314
|
+
isRecord(value) &&
|
|
315
|
+
value.type === "custom" &&
|
|
316
|
+
value.customType === FUSION_RUN_ENTRY_TYPE &&
|
|
317
|
+
"data" in value
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isFusionRunState(value: unknown): value is FusionRun {
|
|
322
|
+
if (!isRecord(value)) return false;
|
|
323
|
+
if (!isNonEmptyString(value.id)) return false;
|
|
324
|
+
if (typeof value.prompt !== "string") return false;
|
|
325
|
+
if (!isNonEmptyString(value.profileName)) return false;
|
|
326
|
+
if (!isFusionPhase(value.phase)) return false;
|
|
327
|
+
if (!isFiniteNumber(value.createdAt)) return false;
|
|
328
|
+
if (!isFiniteNumber(value.updatedAt)) return false;
|
|
329
|
+
if (value.panelRunId !== undefined && typeof value.panelRunId !== "string") {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
if (value.judgeRunId !== undefined && typeof value.judgeRunId !== "string") {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
if (value.report !== undefined && typeof value.report !== "string") {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
if (value.error !== undefined && typeof value.error !== "string") {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function isFusionRunSummary(value: unknown): value is FusionRunSummary {
|
|
345
|
+
return isFusionRunState(value) && isTerminalPhase(value.phase);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function isFusionPhase(value: unknown): value is FusionPhase {
|
|
349
|
+
return (
|
|
350
|
+
value === "panel" ||
|
|
351
|
+
value === "judge" ||
|
|
352
|
+
value === "done" ||
|
|
353
|
+
value === "failed" ||
|
|
354
|
+
value === "cancelled"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function isTerminalPhase(value: unknown): value is FusionTerminalPhase {
|
|
359
|
+
return value === "done" || value === "failed" || value === "cancelled";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
363
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
367
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function isFiniteNumber(value: unknown): value is number {
|
|
371
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
372
|
+
}
|
package/src/status.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import type { FusionRun } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export const FUSION_STATUS_KEY = "fusion";
|
|
4
|
+
|
|
5
|
+
export interface FusionProgressCounts {
|
|
6
|
+
total?: number;
|
|
7
|
+
pending: number;
|
|
8
|
+
running: number;
|
|
9
|
+
completed: number;
|
|
10
|
+
failed: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface FusionUi {
|
|
14
|
+
setStatus(key: string, text: string | undefined): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FusionUiContext {
|
|
18
|
+
hasUI: boolean;
|
|
19
|
+
ui: FusionUi;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function publishFusionStatus(
|
|
23
|
+
ctx: FusionUiContext | undefined,
|
|
24
|
+
run: Pick<
|
|
25
|
+
FusionRun,
|
|
26
|
+
"id" | "phase" | "profileName" | "panelRunId" | "judgeRunId"
|
|
27
|
+
>,
|
|
28
|
+
progress?: FusionProgressCounts,
|
|
29
|
+
): void {
|
|
30
|
+
if (!ctx?.hasUI) return;
|
|
31
|
+
ctx.ui.setStatus(FUSION_STATUS_KEY, formatFusionStatusText(run, progress));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearFusionUi(ctx: FusionUiContext | undefined): void {
|
|
35
|
+
if (!ctx?.hasUI) return;
|
|
36
|
+
ctx.ui.setStatus(FUSION_STATUS_KEY, undefined);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatFusionStatusText(
|
|
40
|
+
run: Pick<FusionRun, "phase" | "profileName" | "panelRunId" | "judgeRunId">,
|
|
41
|
+
progress?: FusionProgressCounts,
|
|
42
|
+
): string {
|
|
43
|
+
const activeRunId = run.phase === "judge" ? run.judgeRunId : run.panelRunId;
|
|
44
|
+
const progressText = progress ? ` ${formatProgressCounts(progress)}` : "";
|
|
45
|
+
const runText = activeRunId ? ` ${activeRunId}` : " starting";
|
|
46
|
+
return `fusion: ${run.phase} ${run.profileName}${runText}${progressText}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function extractFusionProgressCounts(
|
|
50
|
+
payload: unknown,
|
|
51
|
+
): FusionProgressCounts | undefined {
|
|
52
|
+
const container = findProgressContainer(payload);
|
|
53
|
+
if (!container) return undefined;
|
|
54
|
+
|
|
55
|
+
const counts: FusionProgressCounts = {
|
|
56
|
+
total: container.length,
|
|
57
|
+
pending: 0,
|
|
58
|
+
running: 0,
|
|
59
|
+
completed: 0,
|
|
60
|
+
failed: 0,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
for (const item of container) {
|
|
64
|
+
const status = classifyProgressItem(item);
|
|
65
|
+
if (status === "pending") counts.pending++;
|
|
66
|
+
else if (status === "running") counts.running++;
|
|
67
|
+
else if (status === "completed") counts.completed++;
|
|
68
|
+
else counts.failed++;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return counts;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatProgressCounts(progress: FusionProgressCounts): string {
|
|
75
|
+
const total =
|
|
76
|
+
progress.total ??
|
|
77
|
+
progress.pending + progress.running + progress.completed + progress.failed;
|
|
78
|
+
return `${progress.completed}/${total} done, ${progress.running} running, ${progress.failed} failed`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type ProgressStatus = "pending" | "running" | "completed" | "failed";
|
|
82
|
+
|
|
83
|
+
function findProgressContainer(
|
|
84
|
+
payload: unknown,
|
|
85
|
+
): readonly unknown[] | undefined {
|
|
86
|
+
if (!isRecord(payload)) return undefined;
|
|
87
|
+
const progress = unknownArray(payload.progress);
|
|
88
|
+
if (progress) return progress;
|
|
89
|
+
const results = unknownArray(payload.results);
|
|
90
|
+
if (results) return results;
|
|
91
|
+
if (isRecord(payload.details)) {
|
|
92
|
+
const detailsProgress = unknownArray(payload.details.progress);
|
|
93
|
+
if (detailsProgress) return detailsProgress;
|
|
94
|
+
const detailsResults = unknownArray(payload.details.results);
|
|
95
|
+
if (detailsResults) return detailsResults;
|
|
96
|
+
}
|
|
97
|
+
if (isRecord(payload.data)) return findProgressContainer(payload.data);
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function classifyProgressItem(value: unknown): ProgressStatus {
|
|
102
|
+
if (!isRecord(value)) return "failed";
|
|
103
|
+
if (value.success === true) return "completed";
|
|
104
|
+
if (value.success === false) return "failed";
|
|
105
|
+
if (value.timedOut === true || value.interrupted === true) return "failed";
|
|
106
|
+
const status = firstString(value.status, value.state);
|
|
107
|
+
if (status === "pending" || status === "queued") return "pending";
|
|
108
|
+
if (status === "running" || status === "active") return "running";
|
|
109
|
+
if (status === "completed" || status === "complete" || status === "done") {
|
|
110
|
+
return "completed";
|
|
111
|
+
}
|
|
112
|
+
if (status === "failed" || status === "paused" || status === "detached") {
|
|
113
|
+
return "failed";
|
|
114
|
+
}
|
|
115
|
+
if (typeof value.exitCode === "number") {
|
|
116
|
+
return value.exitCode === 0 ? "completed" : "failed";
|
|
117
|
+
}
|
|
118
|
+
return firstString(value.output, value.finalOutput, value.summary, value.text)
|
|
119
|
+
? "completed"
|
|
120
|
+
: "pending";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function firstString(...values: readonly unknown[]): string | undefined {
|
|
124
|
+
for (const value of values) {
|
|
125
|
+
if (typeof value === "string") return value;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function unknownArray(value: unknown): readonly unknown[] | undefined {
|
|
131
|
+
return Array.isArray(value) ? (value as readonly unknown[]) : undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
|
+
}
|