@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.
Files changed (83) hide show
  1. package/package.json +11 -1
  2. package/src/cli/analyze-task.js +51 -0
  3. package/src/cli/index.js +8 -0
  4. package/src/components/AddPipelineSidebar.jsx +144 -0
  5. package/src/components/AnalysisProgressTray.jsx +87 -0
  6. package/src/components/DAGGrid.jsx +157 -47
  7. package/src/components/JobTable.jsx +4 -3
  8. package/src/components/Layout.jsx +142 -139
  9. package/src/components/MarkdownRenderer.jsx +149 -0
  10. package/src/components/PipelineDAGGrid.jsx +404 -0
  11. package/src/components/PipelineTypeTaskSidebar.jsx +96 -0
  12. package/src/components/SchemaPreviewPanel.jsx +97 -0
  13. package/src/components/StageTimeline.jsx +36 -0
  14. package/src/components/TaskAnalysisDisplay.jsx +227 -0
  15. package/src/components/TaskCreationSidebar.jsx +447 -0
  16. package/src/components/TaskDetailSidebar.jsx +119 -117
  17. package/src/components/TaskFilePane.jsx +94 -39
  18. package/src/components/ui/RestartJobModal.jsx +26 -6
  19. package/src/components/ui/StopJobModal.jsx +183 -0
  20. package/src/components/ui/button.jsx +59 -27
  21. package/src/components/ui/sidebar.jsx +118 -0
  22. package/src/config/models.js +99 -67
  23. package/src/core/config.js +11 -4
  24. package/src/core/lifecycle-policy.js +62 -0
  25. package/src/core/pipeline-runner.js +312 -217
  26. package/src/core/status-writer.js +84 -0
  27. package/src/llm/index.js +129 -9
  28. package/src/pages/Code.jsx +8 -1
  29. package/src/pages/PipelineDetail.jsx +84 -2
  30. package/src/pages/PipelineList.jsx +214 -0
  31. package/src/pages/PipelineTypeDetail.jsx +234 -0
  32. package/src/pages/PromptPipelineDashboard.jsx +10 -11
  33. package/src/providers/deepseek.js +76 -16
  34. package/src/providers/openai.js +61 -34
  35. package/src/task-analysis/enrichers/analysis-writer.js +62 -0
  36. package/src/task-analysis/enrichers/schema-deducer.js +145 -0
  37. package/src/task-analysis/enrichers/schema-writer.js +74 -0
  38. package/src/task-analysis/extractors/artifacts.js +137 -0
  39. package/src/task-analysis/extractors/llm-calls.js +176 -0
  40. package/src/task-analysis/extractors/stages.js +51 -0
  41. package/src/task-analysis/index.js +103 -0
  42. package/src/task-analysis/parser.js +28 -0
  43. package/src/task-analysis/utils/ast.js +43 -0
  44. package/src/ui/client/adapters/job-adapter.js +60 -0
  45. package/src/ui/client/api.js +233 -8
  46. package/src/ui/client/hooks/useAnalysisProgress.js +145 -0
  47. package/src/ui/client/hooks/useJobList.js +14 -1
  48. package/src/ui/client/index.css +64 -0
  49. package/src/ui/client/main.jsx +4 -0
  50. package/src/ui/client/sse-fetch.js +120 -0
  51. package/src/ui/dist/app.js +262 -0
  52. package/src/ui/dist/assets/index-cjHV9mYW.js +82578 -0
  53. package/src/ui/dist/assets/index-cjHV9mYW.js.map +1 -0
  54. package/src/ui/dist/assets/style-CoM9SoQF.css +180 -0
  55. package/src/ui/dist/favicon.svg +12 -0
  56. package/src/ui/dist/index.html +2 -2
  57. package/src/ui/endpoints/create-pipeline-endpoint.js +194 -0
  58. package/src/ui/endpoints/file-endpoints.js +330 -0
  59. package/src/ui/endpoints/job-control-endpoints.js +1001 -0
  60. package/src/ui/endpoints/job-endpoints.js +62 -0
  61. package/src/ui/endpoints/pipeline-analysis-endpoint.js +246 -0
  62. package/src/ui/endpoints/pipeline-type-detail-endpoint.js +181 -0
  63. package/src/ui/endpoints/pipelines-endpoint.js +133 -0
  64. package/src/ui/endpoints/schema-file-endpoint.js +105 -0
  65. package/src/ui/endpoints/sse-endpoints.js +223 -0
  66. package/src/ui/endpoints/state-endpoint.js +85 -0
  67. package/src/ui/endpoints/task-analysis-endpoint.js +104 -0
  68. package/src/ui/endpoints/task-creation-endpoint.js +114 -0
  69. package/src/ui/endpoints/task-save-endpoint.js +101 -0
  70. package/src/ui/endpoints/upload-endpoints.js +406 -0
  71. package/src/ui/express-app.js +227 -0
  72. package/src/ui/lib/analysis-lock.js +67 -0
  73. package/src/ui/lib/sse.js +30 -0
  74. package/src/ui/server.js +42 -1880
  75. package/src/ui/sse-broadcast.js +93 -0
  76. package/src/ui/utils/http-utils.js +139 -0
  77. package/src/ui/utils/mime-types.js +196 -0
  78. package/src/ui/utils/slug.js +31 -0
  79. package/src/ui/vite.config.js +22 -0
  80. package/src/ui/watcher.js +28 -2
  81. package/src/utils/jobs.js +39 -0
  82. package/src/ui/dist/assets/index-DeDzq-Kk.js +0 -23863
  83. 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
+ }