@alexeiled/pi-fusion 0.1.1 → 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 +148 -28
- 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/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> {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const STATUS_FILE = "status.json";
|
|
5
|
+
const ASYNC_RESULTS_DIR = "async-subagent-results";
|
|
6
|
+
const ASYNC_RUNS_DIR = "async-subagent-runs";
|
|
7
|
+
|
|
8
|
+
export function deriveSubagentResultPath(
|
|
9
|
+
asyncDir: string,
|
|
10
|
+
runId: string,
|
|
11
|
+
): string | undefined {
|
|
12
|
+
const runsDir = dirname(asyncDir);
|
|
13
|
+
if (!runsDir.endsWith(ASYNC_RUNS_DIR)) return undefined;
|
|
14
|
+
return join(dirname(runsDir), ASYNC_RESULTS_DIR, `${runId}.json`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readSubagentStatusArtifact(
|
|
18
|
+
asyncDir: string | undefined,
|
|
19
|
+
): unknown {
|
|
20
|
+
if (!asyncDir) return undefined;
|
|
21
|
+
return readJsonArtifact(join(asyncDir, STATUS_FILE));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readSubagentResultArtifact(input: {
|
|
25
|
+
runId?: string;
|
|
26
|
+
asyncDir?: string;
|
|
27
|
+
}): unknown {
|
|
28
|
+
if (!input.runId || !input.asyncDir) return undefined;
|
|
29
|
+
const resultPath = deriveSubagentResultPath(input.asyncDir, input.runId);
|
|
30
|
+
return resultPath ? readJsonArtifact(resultPath) : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readJsonArtifact(path: string): unknown {
|
|
34
|
+
if (!existsSync(path)) return undefined;
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(readFileSync(path, "utf8")) as unknown;
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -38,7 +38,32 @@ export interface FusionConfig {
|
|
|
38
38
|
profiles: Record<string, FusionProfile>;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export
|
|
41
|
+
export interface PanelOutput {
|
|
42
|
+
index: number;
|
|
43
|
+
agent: string;
|
|
44
|
+
output: string;
|
|
45
|
+
id?: string;
|
|
46
|
+
label?: string;
|
|
47
|
+
role?: string;
|
|
48
|
+
model?: string;
|
|
49
|
+
artifactPath?: string;
|
|
50
|
+
sessionPath?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FailedPanelSummary {
|
|
54
|
+
index: number;
|
|
55
|
+
agent: string;
|
|
56
|
+
summary: string;
|
|
57
|
+
id?: string;
|
|
58
|
+
label?: string;
|
|
59
|
+
role?: string;
|
|
60
|
+
model?: string;
|
|
61
|
+
artifactPath?: string;
|
|
62
|
+
sessionPath?: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type FusionPhase =
|
|
66
|
+
"panel" | "chain" | "judge" | "done" | "failed" | "cancelled";
|
|
42
67
|
|
|
43
68
|
export interface FusionRun {
|
|
44
69
|
id: string;
|
|
@@ -47,8 +72,13 @@ export interface FusionRun {
|
|
|
47
72
|
phase: FusionPhase;
|
|
48
73
|
createdAt: number;
|
|
49
74
|
updatedAt: number;
|
|
75
|
+
chainRunId?: string;
|
|
76
|
+
chainAsyncDir?: string;
|
|
50
77
|
panelRunId?: string;
|
|
51
78
|
judgeRunId?: string;
|
|
79
|
+
judgeAsyncDir?: string;
|
|
80
|
+
panelOutputs?: PanelOutput[];
|
|
81
|
+
panelFailures?: FailedPanelSummary[];
|
|
52
82
|
report?: string;
|
|
53
83
|
error?: string;
|
|
54
84
|
}
|