@ryanfw/prompt-orchestration-pipeline 1.2.5 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/status-writer.ts +0 -3
- package/src/core/task-runner.ts +10 -0
- package/src/ui/client/__tests__/job-adapter.test.ts +26 -0
- package/src/ui/client/__tests__/useJobDetailWithUpdates.test.ts +43 -0
- package/src/ui/client/adapters/job-adapter.ts +5 -3
- package/src/ui/client/hooks/useJobDetailWithUpdates.ts +8 -2
- package/src/ui/dist/assets/{index-Dgzc8e-G.js → index-CeAgP91B.js} +8 -3
- package/src/ui/dist/assets/{index-Dgzc8e-G.js.map → index-CeAgP91B.js.map} +1 -1
- package/src/ui/dist/index.html +1 -1
- package/src/ui/embedded-assets.js +6 -6
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +1 -1
- package/src/ui/server/endpoints/job-control-endpoints.ts +1 -1
- package/src/ui/state/transformers/__tests__/status-transformer.test.ts +33 -0
- package/src/ui/state/transformers/status-transformer.ts +4 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryanfw/prompt-orchestration-pipeline",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "A Prompt-orchestration pipeline (POP) is a framework for building, running, and experimenting with complex chains of LLM tasks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/ui/server/index.ts",
|
|
@@ -212,13 +212,10 @@ export function resetJobFromTask(jobDir: string, fromTask: string, options?: Res
|
|
|
212
212
|
|
|
213
213
|
return writeJobStatus(jobDir, (snapshot) => {
|
|
214
214
|
const taskKeys = Object.keys(snapshot.tasks);
|
|
215
|
-
const totalCount = taskKeys.length;
|
|
216
|
-
const doneCount = taskKeys.filter((k) => snapshot.tasks[k]!.state === "done").length;
|
|
217
215
|
|
|
218
216
|
snapshot.state = "pending";
|
|
219
217
|
snapshot.current = null;
|
|
220
218
|
snapshot.currentStage = null;
|
|
221
|
-
snapshot.progress = totalCount > 0 ? (doneCount / totalCount) * 100 : 0;
|
|
222
219
|
|
|
223
220
|
const fromIndex = taskKeys.indexOf(fromTask);
|
|
224
221
|
const resetKeys = fromIndex === -1 ? [] : taskKeys.slice(fromIndex);
|
package/src/core/task-runner.ts
CHANGED
|
@@ -795,7 +795,17 @@ export async function runPipeline(
|
|
|
795
795
|
|
|
796
796
|
// Write done status (best-effort)
|
|
797
797
|
try {
|
|
798
|
+
const lastStage = KNOWN_STAGES[KNOWN_STAGES.length - 1];
|
|
799
|
+
const doneProgress = computeDeterministicProgress(
|
|
800
|
+
pipelineTasks ?? [taskName],
|
|
801
|
+
taskName,
|
|
802
|
+
lastStage,
|
|
803
|
+
);
|
|
798
804
|
await writeJobStatus(jobDir, (snapshot: StatusSnapshot) => {
|
|
805
|
+
snapshot.state = TaskState.DONE;
|
|
806
|
+
snapshot.progress = doneProgress;
|
|
807
|
+
snapshot.current = null;
|
|
808
|
+
snapshot.currentStage = null;
|
|
799
809
|
if (!snapshot.tasks[taskName]) snapshot.tasks[taskName] = {};
|
|
800
810
|
snapshot.tasks[taskName]!.state = TaskState.DONE;
|
|
801
811
|
snapshot.tasks[taskName]!.currentStage = null;
|
|
@@ -102,6 +102,32 @@ describe("job adapter", () => {
|
|
|
102
102
|
expect(job.doneCount).toBe(1);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
+
it("computes progress from pipelineConfig taskCount, ignoring api progress", () => {
|
|
106
|
+
const job = adaptJobSummary({
|
|
107
|
+
jobId: "job-1",
|
|
108
|
+
progress: 100,
|
|
109
|
+
tasks: {
|
|
110
|
+
build: { state: "done" },
|
|
111
|
+
test: { state: "done" },
|
|
112
|
+
lint: { state: "done" },
|
|
113
|
+
},
|
|
114
|
+
pipelineConfig: {
|
|
115
|
+
tasks: [
|
|
116
|
+
{ name: "build" },
|
|
117
|
+
{ name: "test" },
|
|
118
|
+
{ name: "lint" },
|
|
119
|
+
{ name: "deploy" },
|
|
120
|
+
{ name: "verify" },
|
|
121
|
+
{ name: "publish" },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(job.taskCount).toBe(6);
|
|
127
|
+
expect(job.doneCount).toBe(3);
|
|
128
|
+
expect(job.progress).toBe(50);
|
|
129
|
+
});
|
|
130
|
+
|
|
105
131
|
it("falls back to taskList length when pipelineConfig is absent", () => {
|
|
106
132
|
const job = adaptJobSummary({
|
|
107
133
|
jobId: "job-1",
|
|
@@ -68,6 +68,49 @@ describe("useJobDetailWithUpdates helpers", () => {
|
|
|
68
68
|
expect(extractJobDetail({ ok: true, data: { jobId: "job-1" } })).toEqual({ jobId: "job-1" });
|
|
69
69
|
});
|
|
70
70
|
|
|
71
|
+
it("uses pipelineConfig.tasks.length as authoritative denominator", () => {
|
|
72
|
+
const detail: NormalizedJobDetail = {
|
|
73
|
+
...makeDetail("job-1"),
|
|
74
|
+
pipelineConfig: {
|
|
75
|
+
tasks: ["a", "b", "c", "d", "e", "f"],
|
|
76
|
+
},
|
|
77
|
+
taskCount: 6,
|
|
78
|
+
tasks: {
|
|
79
|
+
a: { name: "a", state: "done", startedAt: null, endedAt: null, files: { artifacts: [], logs: [], tmp: [] } },
|
|
80
|
+
b: { name: "b", state: "done", startedAt: null, endedAt: null, files: { artifacts: [], logs: [], tmp: [] } },
|
|
81
|
+
c: { name: "c", state: "done", startedAt: null, endedAt: null, files: { artifacts: [], logs: [], tmp: [] } },
|
|
82
|
+
d: { name: "d", state: "running", startedAt: null, endedAt: null, files: { artifacts: [], logs: [], tmp: [] } },
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const next = applyDetailEvent(detail, {
|
|
87
|
+
type: "task:updated",
|
|
88
|
+
data: { jobId: "job-1", taskName: "d", task: { state: "running" } },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
expect(next.taskCount).toBe(6);
|
|
92
|
+
expect(next.doneCount).toBe(3);
|
|
93
|
+
expect(next.progress).toBe(50);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("falls back to local task list length without pipelineConfig", () => {
|
|
97
|
+
const detail: NormalizedJobDetail = {
|
|
98
|
+
...makeDetail("job-1"),
|
|
99
|
+
pipelineConfig: undefined,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const next = applyDetailEvent(detail, {
|
|
103
|
+
type: "task:updated",
|
|
104
|
+
data: { jobId: "job-1", taskName: "build", task: { state: "done" } },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(next.taskCount).toBe(2);
|
|
108
|
+
expect(next.doneCount).toBe(1);
|
|
109
|
+
expect(next.progress).toBe(50);
|
|
110
|
+
expect(next.progress).toBeGreaterThanOrEqual(0);
|
|
111
|
+
expect(next.progress).toBeLessThanOrEqual(100);
|
|
112
|
+
});
|
|
113
|
+
|
|
71
114
|
it("exports the detail debounce constant", () => {
|
|
72
115
|
expect(REFRESH_DEBOUNCE_MS).toBe(200);
|
|
73
116
|
});
|
|
@@ -139,9 +139,11 @@ function adaptBaseJob(apiJob: Record<string, unknown>): NormalizedJobSummary {
|
|
|
139
139
|
const taskCount = pipelineTaskArray ? pipelineTaskArray.length : taskList.length;
|
|
140
140
|
const inferredStatus = deriveJobStatusFromTasks(taskList);
|
|
141
141
|
const status = normalizeJobStatus(apiJob["status"] ?? inferredStatus);
|
|
142
|
-
const progress =
|
|
143
|
-
?
|
|
144
|
-
:
|
|
142
|
+
const progress = pipelineTaskArray
|
|
143
|
+
? (taskCount === 0 ? 0 : Math.min(100, Math.floor((doneCount / taskCount) * 100)))
|
|
144
|
+
: typeof apiJob["progress"] === "number"
|
|
145
|
+
? apiJob["progress"]
|
|
146
|
+
: taskCount === 0 ? 0 : Math.min(100, Math.floor((doneCount / taskCount) * 100));
|
|
145
147
|
|
|
146
148
|
return {
|
|
147
149
|
id: typeof apiJob["id"] === "string" ? apiJob["id"] : String(apiJob["jobId"] ?? ""),
|
|
@@ -22,15 +22,21 @@ export function extractJobDetail(payload: unknown): Record<string, unknown> | nu
|
|
|
22
22
|
return null;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function getPipelineTaskCount(detail: NormalizedJobDetail): number | null {
|
|
26
|
+
const config = detail.pipelineConfig;
|
|
27
|
+
if (config && Array.isArray(config["tasks"])) return config["tasks"].length;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
function recomputeProgress(detail: NormalizedJobDetail): NormalizedJobDetail {
|
|
26
32
|
const tasks = Object.values(detail.tasks);
|
|
27
33
|
const doneCount = tasks.filter((task) => task.state === "done").length;
|
|
28
|
-
const taskCount = tasks.length;
|
|
34
|
+
const taskCount = getPipelineTaskCount(detail) ?? tasks.length;
|
|
29
35
|
return {
|
|
30
36
|
...detail,
|
|
31
37
|
doneCount,
|
|
32
38
|
taskCount,
|
|
33
|
-
progress: taskCount === 0 ? 0 : Math.floor((doneCount / taskCount) * 100),
|
|
39
|
+
progress: taskCount === 0 ? 0 : Math.min(100, Math.floor((doneCount / taskCount) * 100)),
|
|
34
40
|
updatedAt: new Date().toISOString(),
|
|
35
41
|
};
|
|
36
42
|
}
|
|
@@ -21910,7 +21910,7 @@ function adaptBaseJob(apiJob) {
|
|
|
21910
21910
|
const taskCount = pipelineTaskArray ? pipelineTaskArray.length : taskList.length;
|
|
21911
21911
|
const inferredStatus = deriveJobStatusFromTasks(taskList);
|
|
21912
21912
|
const status = normalizeJobStatus(apiJob["status"] ?? inferredStatus);
|
|
21913
|
-
const progress = typeof apiJob["progress"] === "number" ? apiJob["progress"] : taskCount === 0 ? 0 : Math.floor(doneCount / taskCount * 100);
|
|
21913
|
+
const progress = pipelineTaskArray ? taskCount === 0 ? 0 : Math.min(100, Math.floor(doneCount / taskCount * 100)) : typeof apiJob["progress"] === "number" ? apiJob["progress"] : taskCount === 0 ? 0 : Math.min(100, Math.floor(doneCount / taskCount * 100));
|
|
21914
21914
|
return {
|
|
21915
21915
|
id: typeof apiJob["id"] === "string" ? apiJob["id"] : String(apiJob["jobId"] ?? ""),
|
|
21916
21916
|
jobId: typeof apiJob["jobId"] === "string" ? apiJob["jobId"] : String(apiJob["id"] ?? ""),
|
|
@@ -21968,15 +21968,20 @@ function extractJobDetail(payload) {
|
|
|
21968
21968
|
if (isRecord$2(payload)) return payload;
|
|
21969
21969
|
return null;
|
|
21970
21970
|
}
|
|
21971
|
+
function getPipelineTaskCount(detail) {
|
|
21972
|
+
const config = detail.pipelineConfig;
|
|
21973
|
+
if (config && Array.isArray(config["tasks"])) return config["tasks"].length;
|
|
21974
|
+
return null;
|
|
21975
|
+
}
|
|
21971
21976
|
function recomputeProgress(detail) {
|
|
21972
21977
|
const tasks = Object.values(detail.tasks);
|
|
21973
21978
|
const doneCount = tasks.filter((task) => task.state === "done").length;
|
|
21974
|
-
const taskCount = tasks.length;
|
|
21979
|
+
const taskCount = getPipelineTaskCount(detail) ?? tasks.length;
|
|
21975
21980
|
return {
|
|
21976
21981
|
...detail,
|
|
21977
21982
|
doneCount,
|
|
21978
21983
|
taskCount,
|
|
21979
|
-
progress: taskCount === 0 ? 0 : Math.floor(doneCount / taskCount * 100),
|
|
21984
|
+
progress: taskCount === 0 ? 0 : Math.min(100, Math.floor(doneCount / taskCount * 100)),
|
|
21980
21985
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
21981
21986
|
};
|
|
21982
21987
|
}
|