@ryanfw/prompt-orchestration-pipeline 1.3.2 → 1.3.4
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/docs/http-api.md +80 -4
- package/package.json +1 -4
- package/src/api/__tests__/index.test.ts +443 -32
- package/src/api/index.ts +108 -127
- package/src/cli/__tests__/analyze-task.test.ts +40 -7
- package/src/cli/__tests__/index.test.ts +337 -17
- package/src/cli/__tests__/types.test.ts +0 -18
- package/src/cli/__tests__/update-pipeline-json.test.ts +19 -9
- package/src/cli/analyze-task.ts +3 -14
- package/src/cli/index.ts +73 -61
- package/src/cli/types.ts +0 -13
- package/src/cli/update-pipeline-json.ts +3 -14
- package/src/config/__tests__/paths.test.ts +46 -1
- package/src/config/__tests__/pipeline-registry.test.ts +767 -0
- package/src/config/__tests__/sse-events.test.ts +470 -0
- package/src/config/__tests__/statuses.test.ts +41 -0
- package/src/config/paths.ts +11 -2
- package/src/config/pipeline-registry.ts +258 -0
- package/src/config/sse-events.ts +122 -0
- package/src/config/statuses.ts +20 -1
- package/src/core/__tests__/agent-step.test.ts +115 -0
- package/src/core/__tests__/config.test.ts +545 -105
- package/src/core/__tests__/core-boot-resolution.test.ts +181 -0
- package/src/core/__tests__/job-concurrency.test.ts +260 -0
- package/src/core/__tests__/job-submission.test.ts +396 -0
- package/src/core/__tests__/job-view.test.ts +1341 -0
- package/src/core/__tests__/json-file.test.ts +111 -0
- package/src/core/__tests__/lifecycle-policy.test.ts +81 -7
- package/src/core/__tests__/logger.test.ts +22 -0
- package/src/core/__tests__/orchestrator-no-deprecated-exports.test.ts +41 -0
- package/src/core/__tests__/orchestrator.test.ts +615 -2
- package/src/core/__tests__/pipeline-runner.test.ts +946 -9
- package/src/core/__tests__/redact.test.ts +153 -0
- package/src/core/__tests__/runner-liveness.test.ts +910 -0
- package/src/core/__tests__/seed-naming.test.ts +57 -0
- package/src/core/__tests__/single-derivation.test.ts +159 -0
- package/src/core/__tests__/task-runner.test.ts +594 -3
- package/src/core/__tests__/task-telemetry.test.ts +241 -0
- package/src/core/__tests__/workspace-root-resolution-guard.test.ts +62 -0
- package/src/core/agent-step.ts +4 -1
- package/src/core/agent-types.ts +5 -4
- package/src/core/config.ts +134 -222
- package/src/core/control.ts +3 -0
- package/src/core/job-concurrency.ts +56 -20
- package/src/core/job-submission.ts +133 -0
- package/src/core/job-view.ts +473 -0
- package/src/core/json-file.ts +45 -0
- package/src/core/lifecycle-policy.ts +23 -8
- package/src/core/logger.ts +0 -29
- package/src/core/orchestrator.ts +200 -74
- package/src/core/pipeline-runner.ts +85 -53
- package/src/core/redact.ts +40 -0
- package/src/core/runner-liveness.ts +280 -0
- package/src/core/seed-naming.ts +9 -0
- package/src/core/status-writer.ts +27 -33
- package/src/core/task-runner.ts +356 -319
- package/src/core/task-telemetry.ts +107 -0
- package/src/harness/__tests__/subprocess.test.ts +112 -1
- package/src/harness/subprocess.ts +55 -16
- package/src/llm/__tests__/index.test.ts +684 -33
- package/src/llm/index.ts +310 -67
- package/src/providers/__tests__/alibaba.test.ts +67 -6
- package/src/providers/__tests__/anthropic.test.ts +35 -14
- package/src/providers/__tests__/base.test.ts +62 -0
- package/src/providers/__tests__/claude-code.test.ts +99 -14
- package/src/providers/__tests__/deepseek.test.ts +16 -6
- package/src/providers/__tests__/gemini.test.ts +47 -25
- package/src/providers/__tests__/moonshot.test.ts +27 -0
- package/src/providers/__tests__/openai.test.ts +262 -74
- package/src/providers/__tests__/opencode.test.ts +77 -0
- package/src/providers/__tests__/request-timeout-contract.test.ts +226 -0
- package/src/providers/__tests__/stream-accumulator.test.ts +52 -0
- package/src/providers/__tests__/types.test.ts +85 -0
- package/src/providers/__tests__/zero-fill-guard.test.ts +62 -0
- package/src/providers/__tests__/zhipu.test.ts +27 -14
- package/src/providers/alibaba.ts +20 -39
- package/src/providers/anthropic.ts +23 -11
- package/src/providers/base.ts +19 -0
- package/src/providers/claude-code.ts +27 -18
- package/src/providers/deepseek.ts +9 -28
- package/src/providers/gemini.ts +20 -58
- package/src/providers/moonshot.ts +15 -11
- package/src/providers/openai.ts +79 -61
- package/src/providers/opencode.ts +16 -33
- package/src/providers/stream-accumulator.ts +27 -21
- package/src/providers/types.ts +29 -4
- package/src/providers/zhipu.ts +15 -44
- package/src/task-analysis/__tests__/analyzer.test.ts +0 -0
- package/src/task-analysis/__tests__/enrichers-schema-deducer.test.ts +34 -2
- package/src/task-analysis/__tests__/no-ast-imports.test.ts +52 -0
- package/src/task-analysis/__tests__/repository.test.ts +65 -0
- package/src/task-analysis/__tests__/types.test.ts +63 -130
- package/src/task-analysis/analyzer.ts +91 -0
- package/src/task-analysis/enrichers/schema-deducer.ts +13 -2
- package/src/task-analysis/index.ts +2 -36
- package/src/task-analysis/repository.ts +45 -0
- package/src/task-analysis/types.ts +42 -58
- package/src/ui/client/__tests__/api.test.ts +143 -1
- package/src/ui/client/__tests__/bootstrap.test.ts +178 -62
- package/src/ui/client/__tests__/job-adapter.test.ts +203 -2
- package/src/ui/client/__tests__/load-state.test.ts +70 -0
- package/src/ui/client/__tests__/types.test.ts +66 -3
- package/src/ui/client/__tests__/useJobDetailWithUpdates.test.ts +390 -77
- package/src/ui/client/__tests__/useJobList.test.ts +198 -23
- package/src/ui/client/__tests__/useJobListWithUpdates.test.ts +218 -7
- package/src/ui/client/adapters/__tests__/job-adapter.test.ts +186 -0
- package/src/ui/client/adapters/job-adapter.ts +41 -16
- package/src/ui/client/api.ts +38 -15
- package/src/ui/client/bootstrap.ts +19 -14
- package/src/ui/client/hooks/useJobDetailWithUpdates.ts +73 -97
- package/src/ui/client/hooks/useJobList.ts +26 -31
- package/src/ui/client/hooks/useJobListWithUpdates.ts +42 -76
- package/src/ui/client/load-state.ts +20 -0
- package/src/ui/client/reducers/__tests__/job-events.test.ts +568 -0
- package/src/ui/client/reducers/job-events.ts +137 -0
- package/src/ui/client/types.ts +16 -20
- package/src/ui/components/DAGGrid.tsx +6 -1
- package/src/ui/components/JobDetail.tsx +12 -4
- package/src/ui/components/JobTable.tsx +41 -13
- package/src/ui/components/StageTimeline.tsx +8 -20
- package/src/ui/components/TaskAnalysisDisplay.tsx +48 -6
- package/src/ui/components/__tests__/DAGGrid.test.tsx +36 -0
- package/src/ui/components/__tests__/JobDetail.test.tsx +112 -0
- package/src/ui/components/__tests__/JobTable.test.tsx +137 -1
- package/src/ui/components/__tests__/StageTimeline.test.tsx +5 -6
- package/src/ui/components/__tests__/TaskAnalysisDisplay.test.tsx +64 -0
- package/src/ui/components/types.ts +35 -15
- package/src/ui/dist/assets/{index--RH3sAt3.js → index-DN3-zvtP.js} +4324 -263
- package/src/ui/dist/assets/index-DN3-zvtP.js.map +1 -0
- package/src/ui/dist/assets/style-CtZBnjlR.css +2 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/pages/PipelineDetail.tsx +60 -4
- package/src/ui/pages/PromptPipelineDashboard.tsx +59 -7
- package/src/ui/pages/__tests__/PromptPipelineDashboard.test.tsx +114 -32
- package/src/ui/pages/__tests__/pages.test.tsx +236 -42
- package/src/ui/server/__tests__/concurrency-endpoint.test.ts +54 -0
- package/src/ui/server/__tests__/config-bridge-node.test.ts +34 -1
- package/src/ui/server/__tests__/config-bridge.test.ts +9 -1
- package/src/ui/server/__tests__/embedded-assets.test.ts +66 -0
- package/src/ui/server/__tests__/file-endpoints.test.ts +183 -5
- package/src/ui/server/__tests__/gate-endpoints.test.ts +55 -0
- package/src/ui/server/__tests__/index.test.ts +63 -0
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +512 -2
- package/src/ui/server/__tests__/job-endpoints.test.ts +158 -2
- package/src/ui/server/__tests__/read-static-path-guard.test.ts +94 -0
- package/src/ui/server/__tests__/router-root-isolation.test.ts +204 -0
- package/src/ui/server/__tests__/router-threading.test.ts +148 -0
- package/src/ui/server/__tests__/router.test.ts +104 -0
- package/src/ui/server/__tests__/sse-broadcast.test.ts +44 -0
- package/src/ui/server/__tests__/sse-enhancer.test.ts +70 -0
- package/src/ui/server/config-bridge-node.ts +8 -26
- package/src/ui/server/config-bridge.ts +3 -4
- package/src/ui/server/embedded-assets-imports.d.ts +44 -0
- package/src/ui/server/embedded-assets.ts +13 -2
- package/src/ui/server/endpoints/__tests__/meta-endpoint.test.ts +1 -0
- package/src/ui/server/endpoints/__tests__/pipeline-analysis-endpoint.test.ts +167 -0
- package/src/ui/server/endpoints/__tests__/schema-file-endpoint.test.ts +104 -0
- package/src/ui/server/endpoints/__tests__/task-analysis-endpoint.test.ts +103 -0
- package/src/ui/server/endpoints/__tests__/upload-endpoints.test.ts +162 -96
- package/src/ui/server/endpoints/concurrency-endpoint.ts +2 -1
- package/src/ui/server/endpoints/create-pipeline-endpoint.ts +14 -29
- package/src/ui/server/endpoints/file-endpoints.ts +15 -10
- package/src/ui/server/endpoints/gate-endpoints.ts +2 -6
- package/src/ui/server/endpoints/job-control-endpoints.ts +129 -141
- package/src/ui/server/endpoints/job-endpoints.ts +7 -0
- package/src/ui/server/endpoints/meta-endpoint.ts +1 -1
- package/src/ui/server/endpoints/pipeline-analysis-endpoint.ts +99 -11
- package/src/ui/server/endpoints/schema-file-endpoint.ts +11 -3
- package/src/ui/server/endpoints/task-analysis-endpoint.ts +21 -5
- package/src/ui/server/endpoints/task-save-endpoint.ts +1 -2
- package/src/ui/server/endpoints/upload-endpoints.ts +5 -40
- package/src/ui/server/index.ts +19 -14
- package/src/ui/server/job-reader.ts +14 -2
- package/src/ui/server/router.ts +33 -10
- package/src/ui/server/sse-broadcast.ts +14 -3
- package/src/ui/server/sse-enhancer.ts +12 -2
- package/src/ui/state/__tests__/schema-loader.test.ts +11 -1
- package/src/ui/state/__tests__/snapshot.test.ts +120 -14
- package/src/ui/state/__tests__/types.test.ts +104 -5
- package/src/ui/state/snapshot.ts +2 -2
- package/src/ui/state/transformers/__tests__/list-transformer.test.ts +85 -2
- package/src/ui/state/transformers/__tests__/status-transformer.test.ts +250 -22
- package/src/ui/state/transformers/list-transformer.ts +13 -3
- package/src/ui/state/transformers/status-transformer.ts +36 -170
- package/src/ui/state/types.ts +15 -48
- package/src/utils/__tests__/path-containment.test.ts +160 -0
- package/src/{ui/server/utils → utils}/path-containment.ts +21 -0
- package/src/task-analysis/__tests__/enrichers-analysis-writer.test.ts +0 -86
- package/src/task-analysis/__tests__/enrichers-artifact-resolver.test.ts +0 -124
- package/src/task-analysis/__tests__/extractors-artifacts.test.ts +0 -133
- package/src/task-analysis/__tests__/extractors-llm-calls.test.ts +0 -46
- package/src/task-analysis/__tests__/extractors-stages.test.ts +0 -52
- package/src/task-analysis/__tests__/index.test.ts +0 -143
- package/src/task-analysis/__tests__/parser.test.ts +0 -41
- package/src/task-analysis/__tests__/utils-ast.test.ts +0 -82
- package/src/task-analysis/enrichers/analysis-writer.ts +0 -75
- package/src/task-analysis/enrichers/artifact-resolver.ts +0 -89
- package/src/task-analysis/extractors/artifacts.ts +0 -143
- package/src/task-analysis/extractors/llm-calls.ts +0 -117
- package/src/task-analysis/extractors/stages.ts +0 -45
- package/src/task-analysis/parser.ts +0 -20
- package/src/task-analysis/utils/ast.ts +0 -45
- package/src/ui/dist/assets/index--RH3sAt3.js.map +0 -1
- package/src/ui/dist/assets/style-CSSKuMOe.css +0 -2
- package/src/ui/embedded-assets.js +0 -12
- package/src/ui/server/__tests__/path-containment.test.ts +0 -54
package/src/api/index.ts
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import { resolvePipelinePaths,
|
|
2
|
+
import { readdir, stat } from "node:fs/promises";
|
|
3
|
+
import { resolvePipelinePaths, resolveWorkspaceRoot } from "../config/paths";
|
|
4
4
|
import type { PipelinePaths } from "../config/paths";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
5
|
+
import { submitSeed } from "../core/job-submission.ts";
|
|
6
|
+
import type { SubmitResult } from "../core/job-submission.ts";
|
|
7
|
+
import { normalizeJobView } from "../core/job-view";
|
|
8
|
+
import type { JobView } from "../core/job-view";
|
|
9
|
+
import { parseSeedFilename } from "../core/seed-naming.ts";
|
|
10
|
+
import { readJobStatusResult, STATUS_FILENAME, type StatusReadResult } from "../core/status-writer";
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
export interface SubmitSuccessResult {
|
|
11
|
-
success: true;
|
|
12
|
-
jobId: string;
|
|
13
|
-
jobName: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Result of a failed job submission. */
|
|
17
|
-
export interface SubmitFailureResult {
|
|
18
|
-
success: false;
|
|
19
|
-
message: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type SubmitResult = SubmitSuccessResult | SubmitFailureResult;
|
|
12
|
+
export type { SubmitResult, SubmitSuccessResult, SubmitFailureResult } from "../core/job-submission.ts";
|
|
23
13
|
|
|
24
14
|
/** Options for submitJobWithValidation. */
|
|
25
15
|
export interface SubmitJobOptions {
|
|
26
|
-
|
|
16
|
+
root?: string;
|
|
27
17
|
seedObject: unknown;
|
|
28
18
|
}
|
|
29
19
|
|
|
30
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Job status record returned by getStatus.
|
|
22
|
+
*
|
|
23
|
+
* Additive optional fields (present only when the on-disk status is unreadable):
|
|
24
|
+
* `readable: false` and `error: { code, message, path }`. Absent ⇒ normal/readable.
|
|
25
|
+
*/
|
|
31
26
|
export interface JobStatusRecord {
|
|
32
27
|
jobId: string;
|
|
33
28
|
jobName: string;
|
|
@@ -40,28 +35,11 @@ export interface JobStatusRecord {
|
|
|
40
35
|
/** Orchestrator construction options. */
|
|
41
36
|
export interface OrchestratorOptions {
|
|
42
37
|
autoStart: boolean;
|
|
38
|
+
root?: string;
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
// ─── Private Helpers ──────────────────────────────────────────────────────────
|
|
46
42
|
|
|
47
|
-
async function atomicWriteJson(filePath: string, data: unknown): Promise<void> {
|
|
48
|
-
const tmp = `${filePath}.${Date.now()}.tmp`;
|
|
49
|
-
await Bun.write(tmp, JSON.stringify(data, null, 2));
|
|
50
|
-
await rename(tmp, filePath);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function assertSeedObject(value: unknown): asserts value is Record<string, unknown> {
|
|
54
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
55
|
-
throw new Error("seed must be a JSON object");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function assertPipelineSlug(value: unknown): asserts value is string {
|
|
60
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
61
|
-
throw new Error("seed.pipeline must be a non-empty string");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
43
|
async function safeReaddir(dirPath: string): Promise<string[]> {
|
|
66
44
|
try {
|
|
67
45
|
return await readdir(dirPath);
|
|
@@ -71,66 +49,77 @@ async function safeReaddir(dirPath: string): Promise<string[]> {
|
|
|
71
49
|
}
|
|
72
50
|
}
|
|
73
51
|
|
|
52
|
+
function locationForPath(paths: PipelinePaths, jobDir: string): "current" | "complete" {
|
|
53
|
+
if (jobDir.startsWith(paths.current + path.sep) || jobDir === paths.current) return "current";
|
|
54
|
+
return "complete";
|
|
55
|
+
}
|
|
56
|
+
|
|
74
57
|
async function readJsonFile<T = unknown>(filePath: string): Promise<T> {
|
|
75
58
|
const text = await Bun.file(filePath).text();
|
|
76
59
|
return JSON.parse(text) as T;
|
|
77
60
|
}
|
|
78
61
|
|
|
79
|
-
function
|
|
80
|
-
const {
|
|
81
|
-
|
|
82
|
-
jobId:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
62
|
+
function toJobStatusRecord(view: JobView): JobStatusRecord {
|
|
63
|
+
const {
|
|
64
|
+
id: _id,
|
|
65
|
+
jobId: _jobId,
|
|
66
|
+
name,
|
|
67
|
+
title: _title,
|
|
68
|
+
status,
|
|
69
|
+
pipeline,
|
|
70
|
+
createdAt,
|
|
71
|
+
readable,
|
|
72
|
+
error,
|
|
73
|
+
...rest
|
|
74
|
+
} = view;
|
|
75
|
+
const record: JobStatusRecord = {
|
|
76
|
+
jobId: view.jobId,
|
|
77
|
+
jobName: name,
|
|
78
|
+
pipeline: pipeline ?? "",
|
|
79
|
+
state: status,
|
|
80
|
+
createdAt: createdAt ?? "",
|
|
87
81
|
...rest,
|
|
88
82
|
};
|
|
83
|
+
if (readable !== undefined) record["readable"] = readable;
|
|
84
|
+
if (error !== undefined) record["error"] = error;
|
|
85
|
+
return record;
|
|
89
86
|
}
|
|
90
87
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return { success: false, message: "seed.name must be a non-empty string if provided" };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
88
|
+
function buildCorruptPlaceholder(jobDir: string, jobId: string): JobStatusRecord {
|
|
89
|
+
const statusPath = path.join(jobDir, STATUS_FILENAME);
|
|
90
|
+
return {
|
|
91
|
+
jobId,
|
|
92
|
+
jobName: jobId,
|
|
93
|
+
pipeline: "",
|
|
94
|
+
state: "failed",
|
|
95
|
+
createdAt: "",
|
|
96
|
+
readable: false,
|
|
97
|
+
error: {
|
|
98
|
+
code: "STATUS_CORRUPT",
|
|
99
|
+
message:
|
|
100
|
+
`Status file is unreadable (corrupt). ` +
|
|
101
|
+
`Repair or delete ${statusPath} for job "${jobId}" to recover.`,
|
|
102
|
+
path: statusPath,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
112
106
|
|
|
107
|
+
async function readStatusForApi(jobDir: string, label: string): Promise<StatusReadResult | null> {
|
|
113
108
|
try {
|
|
114
|
-
|
|
109
|
+
return await readJobStatusResult(jobDir);
|
|
115
110
|
} catch (err) {
|
|
116
|
-
|
|
111
|
+
console.warn(`[api] skipping unreadable status ${label}: ${(err as Error).message}`);
|
|
112
|
+
return null;
|
|
117
113
|
}
|
|
114
|
+
}
|
|
118
115
|
|
|
119
|
-
|
|
120
|
-
const jobName = (typeof seed["name"] === "string" && seed["name"].length > 0)
|
|
121
|
-
? seed["name"]
|
|
122
|
-
: jobId;
|
|
123
|
-
const pendingPath = getPendingSeedPath(rootDir, jobId);
|
|
124
|
-
|
|
125
|
-
await mkdir(path.dirname(pendingPath), { recursive: true });
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
await atomicWriteJson(pendingPath, opts.seedObject);
|
|
129
|
-
} catch (err) {
|
|
130
|
-
throw new Error(`failed to write seed file for job ${jobId}: ${(err as Error).message}`);
|
|
131
|
-
}
|
|
116
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
132
117
|
|
|
133
|
-
|
|
118
|
+
export async function submitJobWithValidation(
|
|
119
|
+
opts: SubmitJobOptions,
|
|
120
|
+
): Promise<SubmitResult> {
|
|
121
|
+
const rootDir = resolveWorkspaceRoot(opts.root);
|
|
122
|
+
return submitSeed({ root: rootDir, seedObject: opts.seedObject });
|
|
134
123
|
}
|
|
135
124
|
|
|
136
125
|
export class PipelineOrchestrator {
|
|
@@ -140,20 +129,22 @@ export class PipelineOrchestrator {
|
|
|
140
129
|
|
|
141
130
|
constructor(opts: OrchestratorOptions) {
|
|
142
131
|
this.autoStart = opts.autoStart;
|
|
143
|
-
this.root =
|
|
132
|
+
this.root = resolveWorkspaceRoot(opts.root);
|
|
144
133
|
this.paths = resolvePipelinePaths(this.root);
|
|
145
134
|
}
|
|
146
135
|
|
|
147
136
|
async getStatus(jobName: string): Promise<JobStatusRecord> {
|
|
148
137
|
// 1. Direct jobId lookup in current, then complete
|
|
149
138
|
for (const dir of [this.paths.current, this.paths.complete]) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
139
|
+
const jobDir = path.join(dir, jobName);
|
|
140
|
+
const result = await readStatusForApi(jobDir, jobName);
|
|
141
|
+
if (!result) continue;
|
|
142
|
+
if (result.ok) {
|
|
143
|
+
return toJobStatusRecord(
|
|
144
|
+
normalizeJobView(result.value, jobName, locationForPath(this.paths, jobDir)),
|
|
145
|
+
);
|
|
156
146
|
}
|
|
147
|
+
if (result.reason === "corrupt") return buildCorruptPlaceholder(jobDir, jobName);
|
|
157
148
|
}
|
|
158
149
|
|
|
159
150
|
// 2. Name scan fallback
|
|
@@ -161,14 +152,14 @@ export class PipelineOrchestrator {
|
|
|
161
152
|
const entries = await safeReaddir(dir);
|
|
162
153
|
for (const entry of entries) {
|
|
163
154
|
if (entry === ".gitkeep") continue;
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
155
|
+
const jobDir = path.join(dir, entry);
|
|
156
|
+
const result = await readStatusForApi(jobDir, entry);
|
|
157
|
+
if (!result) continue;
|
|
158
|
+
if (!result.ok) continue;
|
|
159
|
+
if (result.value["name"] === jobName) {
|
|
160
|
+
return toJobStatusRecord(
|
|
161
|
+
normalizeJobView(result.value, entry, locationForPath(this.paths, jobDir)),
|
|
162
|
+
);
|
|
172
163
|
}
|
|
173
164
|
}
|
|
174
165
|
}
|
|
@@ -176,10 +167,8 @@ export class PipelineOrchestrator {
|
|
|
176
167
|
// 3. Pending fallback
|
|
177
168
|
const pendingFiles = await safeReaddir(this.paths.pending);
|
|
178
169
|
for (const file of pendingFiles) {
|
|
179
|
-
const
|
|
180
|
-
if (
|
|
181
|
-
const id = match[1]!;
|
|
182
|
-
if (id !== jobName) continue;
|
|
170
|
+
const id = parseSeedFilename(file);
|
|
171
|
+
if (id === null || id !== jobName) continue;
|
|
183
172
|
|
|
184
173
|
const filePath = path.join(this.paths.pending, file);
|
|
185
174
|
const seed = await readJsonFile<Record<string, unknown>>(filePath);
|
|
@@ -202,9 +191,8 @@ export class PipelineOrchestrator {
|
|
|
202
191
|
// Pending jobs
|
|
203
192
|
const pendingFiles = await safeReaddir(this.paths.pending);
|
|
204
193
|
for (const file of pendingFiles) {
|
|
205
|
-
const
|
|
206
|
-
if (
|
|
207
|
-
const id = match[1]!;
|
|
194
|
+
const id = parseSeedFilename(file);
|
|
195
|
+
if (id === null) continue;
|
|
208
196
|
const filePath = path.join(this.paths.pending, file);
|
|
209
197
|
try {
|
|
210
198
|
const seed = await readJsonFile<Record<string, unknown>>(filePath);
|
|
@@ -225,35 +213,28 @@ export class PipelineOrchestrator {
|
|
|
225
213
|
const currentEntries = await safeReaddir(this.paths.current);
|
|
226
214
|
for (const entry of currentEntries) {
|
|
227
215
|
if (entry === ".gitkeep") continue;
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
: [];
|
|
235
|
-
const state = deriveJobStatusFromTasks(taskArray);
|
|
236
|
-
const record = mapStatusToRecord(data);
|
|
237
|
-
record.state = state;
|
|
238
|
-
results.push(record);
|
|
239
|
-
} catch (err) {
|
|
240
|
-
console.warn(`[api] skipping unreadable current job ${entry}: ${(err as Error).message}`);
|
|
216
|
+
const jobDir = path.join(this.paths.current, entry);
|
|
217
|
+
const result = await readStatusForApi(jobDir, entry);
|
|
218
|
+
if (!result) continue;
|
|
219
|
+
if (!result.ok) {
|
|
220
|
+
if (result.reason === "corrupt") results.push(buildCorruptPlaceholder(jobDir, entry));
|
|
221
|
+
continue;
|
|
241
222
|
}
|
|
223
|
+
results.push(toJobStatusRecord(normalizeJobView(result.value, entry, "current")));
|
|
242
224
|
}
|
|
243
225
|
|
|
244
226
|
// Complete jobs
|
|
245
227
|
const completeEntries = await safeReaddir(this.paths.complete);
|
|
246
228
|
for (const entry of completeEntries) {
|
|
247
229
|
if (entry === ".gitkeep") continue;
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
} catch (err) {
|
|
255
|
-
console.warn(`[api] skipping unreadable complete job ${entry}: ${(err as Error).message}`);
|
|
230
|
+
const jobDir = path.join(this.paths.complete, entry);
|
|
231
|
+
const result = await readStatusForApi(jobDir, entry);
|
|
232
|
+
if (!result) continue;
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
if (result.reason === "corrupt") results.push(buildCorruptPlaceholder(jobDir, entry));
|
|
235
|
+
continue;
|
|
256
236
|
}
|
|
237
|
+
results.push(toJobStatusRecord(normalizeJobView(result.value, entry, "complete")));
|
|
257
238
|
}
|
|
258
239
|
|
|
259
240
|
return results;
|
|
@@ -1,7 +1,43 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach, spyOn } from "bun:test";
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, spyOn, mock } from "bun:test";
|
|
2
2
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import type { ChatResponse, ProviderAvailability } from "../../providers/types.ts";
|
|
6
|
+
|
|
7
|
+
// Mock the gateway boundary so the analyzer produces a deterministic, valid
|
|
8
|
+
// result without a real LLM call. (See task-analysis/__tests__/analyzer.test.ts.)
|
|
9
|
+
const availability: ProviderAvailability = {
|
|
10
|
+
openai: false,
|
|
11
|
+
deepseek: true,
|
|
12
|
+
anthropic: false,
|
|
13
|
+
gemini: false,
|
|
14
|
+
zai: false,
|
|
15
|
+
zhipu: false,
|
|
16
|
+
claudecode: false,
|
|
17
|
+
moonshot: false,
|
|
18
|
+
alibaba: false,
|
|
19
|
+
opencode: false,
|
|
20
|
+
mock: false,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const VALID_ANALYSIS = {
|
|
24
|
+
summary: "Ingests raw data and writes a processed artifact.",
|
|
25
|
+
stages: [{ name: "ingestion", purpose: "Reads raw input and normalizes it." }],
|
|
26
|
+
artifacts: {
|
|
27
|
+
reads: [{ fileName: "seed.json", role: "raw input" }],
|
|
28
|
+
writes: [{ fileName: "processed.json", role: "normalized output" }],
|
|
29
|
+
},
|
|
30
|
+
models: [{ provider: "deepseek", method: "chat", stage: "ingestion" }],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
mock.module("../../llm/index.ts", () => ({
|
|
34
|
+
chat: (): Promise<ChatResponse> =>
|
|
35
|
+
Promise.resolve({
|
|
36
|
+
content: VALID_ANALYSIS,
|
|
37
|
+
usage: { promptTokens: 1, completionTokens: 1, totalTokens: 2, source: "reported" },
|
|
38
|
+
}),
|
|
39
|
+
getAvailableProviders: () => availability,
|
|
40
|
+
}));
|
|
5
41
|
|
|
6
42
|
describe("analyzeTaskFile", () => {
|
|
7
43
|
let tmpDir: string;
|
|
@@ -22,11 +58,11 @@ describe("analyzeTaskFile", () => {
|
|
|
22
58
|
stdoutSpy.mockRestore();
|
|
23
59
|
});
|
|
24
60
|
|
|
25
|
-
it("
|
|
61
|
+
it("prints a status-tagged analysis as JSON for a sample file", async () => {
|
|
26
62
|
const taskFile = join(tmpDir, "task.ts");
|
|
27
63
|
await writeFile(
|
|
28
64
|
taskFile,
|
|
29
|
-
'export async function ingestion({ io, llm }) { await io.readArtifact("seed.json");
|
|
65
|
+
'export async function ingestion({ io, llm }) { await io.readArtifact("seed.json"); }',
|
|
30
66
|
);
|
|
31
67
|
|
|
32
68
|
const { analyzeTaskFile } = await import("../analyze-task.ts");
|
|
@@ -35,10 +71,7 @@ describe("analyzeTaskFile", () => {
|
|
|
35
71
|
expect(stdoutSpy).toHaveBeenCalledTimes(1);
|
|
36
72
|
const written = (stdoutSpy.mock.calls[0] as [string])[0];
|
|
37
73
|
const parsed = JSON.parse(written);
|
|
38
|
-
expect(parsed.
|
|
39
|
-
expect(parsed.stages).toEqual([{ name: "ingestion", order: 1, isAsync: true }]);
|
|
40
|
-
expect(parsed.artifacts.reads).toEqual([{ fileName: "seed.json", stage: "ingestion", required: true }]);
|
|
41
|
-
expect(parsed.models).toEqual([{ provider: "openai", method: "complete", stage: "ingestion" }]);
|
|
74
|
+
expect(parsed.analysisStatus).toBe("ok");
|
|
42
75
|
});
|
|
43
76
|
|
|
44
77
|
it("calls process.exit(1) for a non-existent file", async () => {
|