@ryanfw/prompt-orchestration-pipeline 0.11.0 → 0.13.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/package.json +11 -1
- package/src/cli/analyze-task.js +51 -0
- package/src/cli/index.js +8 -0
- package/src/components/AddPipelineSidebar.jsx +144 -0
- package/src/components/AnalysisProgressTray.jsx +87 -0
- package/src/components/DAGGrid.jsx +157 -47
- package/src/components/JobTable.jsx +4 -3
- package/src/components/Layout.jsx +142 -139
- package/src/components/MarkdownRenderer.jsx +149 -0
- package/src/components/PipelineDAGGrid.jsx +404 -0
- package/src/components/PipelineTypeTaskSidebar.jsx +96 -0
- package/src/components/SchemaPreviewPanel.jsx +97 -0
- package/src/components/StageTimeline.jsx +36 -0
- package/src/components/TaskAnalysisDisplay.jsx +227 -0
- package/src/components/TaskCreationSidebar.jsx +447 -0
- package/src/components/TaskDetailSidebar.jsx +119 -117
- package/src/components/TaskFilePane.jsx +94 -39
- package/src/components/ui/RestartJobModal.jsx +26 -6
- package/src/components/ui/StopJobModal.jsx +183 -0
- package/src/components/ui/button.jsx +59 -27
- package/src/components/ui/sidebar.jsx +118 -0
- package/src/config/models.js +99 -67
- package/src/core/config.js +11 -4
- package/src/core/lifecycle-policy.js +62 -0
- package/src/core/pipeline-runner.js +312 -217
- package/src/core/status-writer.js +84 -0
- package/src/llm/index.js +129 -9
- package/src/pages/Code.jsx +8 -1
- package/src/pages/PipelineDetail.jsx +84 -2
- package/src/pages/PipelineList.jsx +214 -0
- package/src/pages/PipelineTypeDetail.jsx +234 -0
- package/src/pages/PromptPipelineDashboard.jsx +10 -11
- package/src/providers/deepseek.js +76 -16
- package/src/providers/openai.js +61 -34
- package/src/task-analysis/enrichers/analysis-writer.js +62 -0
- package/src/task-analysis/enrichers/schema-deducer.js +145 -0
- package/src/task-analysis/enrichers/schema-writer.js +74 -0
- package/src/task-analysis/extractors/artifacts.js +137 -0
- package/src/task-analysis/extractors/llm-calls.js +176 -0
- package/src/task-analysis/extractors/stages.js +51 -0
- package/src/task-analysis/index.js +103 -0
- package/src/task-analysis/parser.js +28 -0
- package/src/task-analysis/utils/ast.js +43 -0
- package/src/ui/client/adapters/job-adapter.js +60 -0
- package/src/ui/client/api.js +233 -8
- package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
- package/src/ui/client/hooks/useJobList.js +14 -1
- package/src/ui/client/index.css +64 -0
- package/src/ui/client/main.jsx +4 -0
- package/src/ui/client/sse-fetch.js +120 -0
- package/src/ui/dist/app.js +262 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js +82578 -0
- package/src/ui/dist/assets/index-cjHV9mYW.js.map +1 -0
- package/src/ui/dist/assets/style-CoM9SoQF.css +180 -0
- package/src/ui/dist/favicon.svg +12 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
- package/src/ui/endpoints/file-endpoints.js +330 -0
- package/src/ui/endpoints/job-control-endpoints.js +1001 -0
- package/src/ui/endpoints/job-endpoints.js +62 -0
- package/src/ui/endpoints/pipeline-analysis-endpoint.js +246 -0
- package/src/ui/endpoints/pipeline-type-detail-endpoint.js +181 -0
- package/src/ui/endpoints/pipelines-endpoint.js +133 -0
- package/src/ui/endpoints/schema-file-endpoint.js +105 -0
- package/src/ui/endpoints/sse-endpoints.js +223 -0
- package/src/ui/endpoints/state-endpoint.js +85 -0
- package/src/ui/endpoints/task-analysis-endpoint.js +104 -0
- package/src/ui/endpoints/task-creation-endpoint.js +114 -0
- package/src/ui/endpoints/task-save-endpoint.js +101 -0
- package/src/ui/endpoints/upload-endpoints.js +406 -0
- package/src/ui/express-app.js +227 -0
- package/src/ui/lib/analysis-lock.js +67 -0
- package/src/ui/lib/sse.js +30 -0
- package/src/ui/server.js +42 -1880
- package/src/ui/sse-broadcast.js +93 -0
- package/src/ui/utils/http-utils.js +139 -0
- package/src/ui/utils/mime-types.js +196 -0
- package/src/ui/utils/slug.js +31 -0
- package/src/ui/vite.config.js +22 -0
- package/src/ui/watcher.js +28 -2
- package/src/utils/jobs.js +39 -0
- package/src/ui/dist/assets/index-DeDzq-Kk.js +0 -23863
- package/src/ui/dist/assets/style-aBtD_Yrs.css +0 -62
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { sseRegistry } from "./sse.js";
|
|
5
|
+
import { handleApiState } from "./endpoints/state-endpoint.js";
|
|
6
|
+
import { handleSeedUpload } from "./endpoints/upload-endpoints.js";
|
|
7
|
+
import {
|
|
8
|
+
handleJobListRequest,
|
|
9
|
+
handleJobDetailRequest,
|
|
10
|
+
} from "./endpoints/job-endpoints.js";
|
|
11
|
+
import {
|
|
12
|
+
handleJobRescan,
|
|
13
|
+
handleJobRestart,
|
|
14
|
+
handleJobStop,
|
|
15
|
+
handleTaskStart,
|
|
16
|
+
} from "./endpoints/job-control-endpoints.js";
|
|
17
|
+
import {
|
|
18
|
+
handleTaskFileListRequest,
|
|
19
|
+
handleTaskFileRequest,
|
|
20
|
+
} from "./endpoints/file-endpoints.js";
|
|
21
|
+
import { handlePipelinesHttpRequest } from "./endpoints/pipelines-endpoint.js";
|
|
22
|
+
import { handleCreatePipeline } from "./endpoints/create-pipeline-endpoint.js";
|
|
23
|
+
import { handlePipelineTypeDetailRequest } from "./endpoints/pipeline-type-detail-endpoint.js";
|
|
24
|
+
import { handlePipelineAnalysis } from "./endpoints/pipeline-analysis-endpoint.js";
|
|
25
|
+
import { handleTaskAnalysisRequest } from "./endpoints/task-analysis-endpoint.js";
|
|
26
|
+
import { handleSchemaFileRequest } from "./endpoints/schema-file-endpoint.js";
|
|
27
|
+
import { handleTaskPlan } from "./endpoints/task-creation-endpoint.js";
|
|
28
|
+
import { handleTaskSave } from "./endpoints/task-save-endpoint.js";
|
|
29
|
+
import { sendJson } from "./utils/http-utils.js";
|
|
30
|
+
import { PROVIDER_FUNCTIONS } from "../config/models.js";
|
|
31
|
+
|
|
32
|
+
// Get __dirname equivalent in ES modules
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = path.dirname(__filename);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build Express application with API routes, SSE, and static serving
|
|
38
|
+
* @param {Object} params - Configuration parameters
|
|
39
|
+
* @param {string} params.dataDir - Base data directory
|
|
40
|
+
* @param {Object} [params.viteServer] - Vite dev server instance (optional)
|
|
41
|
+
* @returns {express.Application} Configured Express app
|
|
42
|
+
*/
|
|
43
|
+
export function buildExpressApp({ dataDir, viteServer }) {
|
|
44
|
+
const app = express();
|
|
45
|
+
|
|
46
|
+
// Parse JSON request bodies
|
|
47
|
+
app.use(express.json());
|
|
48
|
+
|
|
49
|
+
// Parse URL-encoded request bodies (for form submissions)
|
|
50
|
+
app.use(express.urlencoded({ extended: true }));
|
|
51
|
+
|
|
52
|
+
// API guard middleware mounted on /api
|
|
53
|
+
app.use("/api", (req, res, next) => {
|
|
54
|
+
// Set CORS headers
|
|
55
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
56
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
57
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
58
|
+
|
|
59
|
+
// Handle OPTIONS preflight
|
|
60
|
+
if (req.method === "OPTIONS") {
|
|
61
|
+
return res.status(204).end();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Set Connection: close for non-SSE requests
|
|
65
|
+
const isSSE = req.path === "/events" || req.path === "/sse";
|
|
66
|
+
if (!isSSE) {
|
|
67
|
+
res.setHeader("Connection", "close");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
next();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// SSE routes
|
|
74
|
+
app.get(["/api/events", "/api/sse"], (req, res) => {
|
|
75
|
+
// Set SSE headers
|
|
76
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
77
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
78
|
+
res.setHeader("Connection", "keep-alive");
|
|
79
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
80
|
+
|
|
81
|
+
// Flush headers if available
|
|
82
|
+
if (typeof res.flushHeaders === "function") {
|
|
83
|
+
res.flushHeaders();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const jobId = req.query.jobId;
|
|
87
|
+
sseRegistry.addClient(res, { jobId });
|
|
88
|
+
|
|
89
|
+
req.on("close", () => {
|
|
90
|
+
sseRegistry.removeClient(res);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// REST routes
|
|
95
|
+
|
|
96
|
+
// GET /api/state
|
|
97
|
+
app.get("/api/state", async (req, res) => {
|
|
98
|
+
await handleApiState(req, res);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// POST /api/upload/seed
|
|
102
|
+
app.post("/api/upload/seed", async (req, res) => {
|
|
103
|
+
await handleSeedUpload(req, res);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// GET /api/llm/functions
|
|
107
|
+
app.get("/api/llm/functions", (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
sendJson(res, 200, { ok: true, data: PROVIDER_FUNCTIONS });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error("Error serving LLM functions:", error);
|
|
112
|
+
sendJson(res, 500, {
|
|
113
|
+
ok: false,
|
|
114
|
+
error: "internal_error",
|
|
115
|
+
message: "Failed to load LLM functions",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// GET /api/pipelines
|
|
121
|
+
app.get("/api/pipelines", async (req, res) => {
|
|
122
|
+
await handlePipelinesHttpRequest(req, res);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// POST /api/pipelines
|
|
126
|
+
app.post("/api/pipelines", handleCreatePipeline);
|
|
127
|
+
|
|
128
|
+
// GET /api/pipelines/:slug
|
|
129
|
+
app.get("/api/pipelines/:slug", async (req, res) => {
|
|
130
|
+
await handlePipelineTypeDetailRequest(req, res);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// POST /api/pipelines/:slug/analyze
|
|
134
|
+
app.post("/api/pipelines/:slug/analyze", handlePipelineAnalysis);
|
|
135
|
+
|
|
136
|
+
// GET /api/pipelines/:slug/tasks/:taskId/analysis
|
|
137
|
+
app.get(
|
|
138
|
+
"/api/pipelines/:slug/tasks/:taskId/analysis",
|
|
139
|
+
handleTaskAnalysisRequest
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
// GET /api/pipelines/:slug/schemas/:fileName
|
|
143
|
+
app.get("/api/pipelines/:slug/schemas/:fileName", handleSchemaFileRequest);
|
|
144
|
+
|
|
145
|
+
// GET /api/jobs
|
|
146
|
+
app.get("/api/jobs", async (req, res) => {
|
|
147
|
+
await handleJobListRequest(req, res);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// GET /api/jobs/:jobId
|
|
151
|
+
app.get("/api/jobs/:jobId", async (req, res) => {
|
|
152
|
+
const { jobId } = req.params;
|
|
153
|
+
await handleJobDetailRequest(req, res, jobId);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// POST /api/jobs/:jobId/rescan
|
|
157
|
+
app.post("/api/jobs/:jobId/rescan", async (req, res) => {
|
|
158
|
+
const { jobId } = req.params;
|
|
159
|
+
await handleJobRescan(req, res, jobId, dataDir, sendJson);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// POST /api/jobs/:jobId/restart
|
|
163
|
+
app.post("/api/jobs/:jobId/restart", async (req, res) => {
|
|
164
|
+
const { jobId } = req.params;
|
|
165
|
+
await handleJobRestart(req, res, jobId, dataDir, sendJson);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// POST /api/jobs/:jobId/stop
|
|
169
|
+
app.post("/api/jobs/:jobId/stop", async (req, res) => {
|
|
170
|
+
const { jobId } = req.params;
|
|
171
|
+
await handleJobStop(req, res, jobId, dataDir, sendJson);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// POST /api/jobs/:jobId/tasks/:taskId/start
|
|
175
|
+
app.post("/api/jobs/:jobId/tasks/:taskId/start", async (req, res) => {
|
|
176
|
+
const { jobId, taskId } = req.params;
|
|
177
|
+
await handleTaskStart(req, res, jobId, taskId, dataDir, sendJson);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// GET /api/jobs/:jobId/tasks/:taskId/files
|
|
181
|
+
app.get("/api/jobs/:jobId/tasks/:taskId/files", async (req, res) => {
|
|
182
|
+
const { jobId, taskId } = req.params;
|
|
183
|
+
const { type } = req.query;
|
|
184
|
+
await handleTaskFileListRequest(req, res, {
|
|
185
|
+
jobId,
|
|
186
|
+
taskId,
|
|
187
|
+
type,
|
|
188
|
+
dataDir,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// GET /api/jobs/:jobId/tasks/:taskId/file
|
|
193
|
+
app.get("/api/jobs/:jobId/tasks/:taskId/file", async (req, res) => {
|
|
194
|
+
const { jobId, taskId } = req.params;
|
|
195
|
+
const { type, filename } = req.query;
|
|
196
|
+
await handleTaskFileRequest(req, res, {
|
|
197
|
+
jobId,
|
|
198
|
+
taskId,
|
|
199
|
+
type,
|
|
200
|
+
filename,
|
|
201
|
+
dataDir,
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// POST /api/ai/task-plan
|
|
206
|
+
app.post("/api/ai/task-plan", handleTaskPlan);
|
|
207
|
+
|
|
208
|
+
// POST /api/tasks/create
|
|
209
|
+
app.post("/api/tasks/create", handleTaskSave);
|
|
210
|
+
|
|
211
|
+
// Dev middleware (mount after all API routes)
|
|
212
|
+
if (viteServer && viteServer.middlewares) {
|
|
213
|
+
app.use(viteServer.middlewares);
|
|
214
|
+
} else {
|
|
215
|
+
// Production static serving
|
|
216
|
+
app.use("/public", express.static(path.join(__dirname, "public")));
|
|
217
|
+
app.use("/assets", express.static(path.join(__dirname, "dist", "assets")));
|
|
218
|
+
app.use(express.static(path.join(__dirname, "dist")));
|
|
219
|
+
|
|
220
|
+
// SPA fallback
|
|
221
|
+
app.get("*", (_, res) => {
|
|
222
|
+
res.sendFile(path.join(__dirname, "dist", "index.html"));
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return app;
|
|
227
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory lock for pipeline analysis operations.
|
|
3
|
+
* Ensures only one pipeline can be analyzed at a time.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
let currentLock = null;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Attempt to acquire the analysis lock for a pipeline.
|
|
10
|
+
* @param {string} pipelineSlug - The pipeline identifier
|
|
11
|
+
* @returns {{ acquired: true } | { acquired: false, heldBy: string }}
|
|
12
|
+
*/
|
|
13
|
+
export function acquireLock(pipelineSlug) {
|
|
14
|
+
if (!pipelineSlug || typeof pipelineSlug !== "string") {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Invalid pipelineSlug: expected non-empty string, got ${typeof pipelineSlug}`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (currentLock === null) {
|
|
21
|
+
currentLock = {
|
|
22
|
+
pipelineSlug,
|
|
23
|
+
startedAt: new Date(),
|
|
24
|
+
};
|
|
25
|
+
return { acquired: true };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
acquired: false,
|
|
30
|
+
heldBy: currentLock.pipelineSlug,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Release the analysis lock for a pipeline.
|
|
36
|
+
* @param {string} pipelineSlug - The pipeline identifier that holds the lock
|
|
37
|
+
* @throws {Error} If the lock is not held by this pipeline
|
|
38
|
+
*/
|
|
39
|
+
export function releaseLock(pipelineSlug) {
|
|
40
|
+
if (!pipelineSlug || typeof pipelineSlug !== "string") {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Invalid pipelineSlug: expected non-empty string, got ${typeof pipelineSlug}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (currentLock === null) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Cannot release lock for '${pipelineSlug}': no lock is currently held`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (currentLock.pipelineSlug !== pipelineSlug) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Cannot release lock for '${pipelineSlug}': lock is held by '${currentLock.pipelineSlug}'`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
currentLock = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the current lock status.
|
|
63
|
+
* @returns {{ pipelineSlug: string, startedAt: Date } | null}
|
|
64
|
+
*/
|
|
65
|
+
export function getLockStatus() {
|
|
66
|
+
return currentLock;
|
|
67
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function streamSSE(res) {
|
|
2
|
+
console.log("[sse] Creating new SSE stream");
|
|
3
|
+
|
|
4
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
5
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
6
|
+
res.setHeader("Connection", "keep-alive");
|
|
7
|
+
res.flushHeaders();
|
|
8
|
+
|
|
9
|
+
console.log("[sse] SSE headers set and flushed");
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
send(event, data) {
|
|
13
|
+
console.log("[sse] Sending event:", {
|
|
14
|
+
eventType: event,
|
|
15
|
+
hasData: !!data,
|
|
16
|
+
dataKeys: data ? Object.keys(data) : [],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const eventData = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
20
|
+
res.write(eventData);
|
|
21
|
+
|
|
22
|
+
console.log("[sse] Event sent successfully");
|
|
23
|
+
},
|
|
24
|
+
end() {
|
|
25
|
+
console.log("[sse] Ending SSE stream");
|
|
26
|
+
res.end();
|
|
27
|
+
console.log("[sse] SSE stream ended");
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|