taskmeld 0.1.1
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/LICENSE +18 -0
- package/README.md +172 -0
- package/README.zh-CN.md +172 -0
- package/dist/src/app/app-context-env.js +51 -0
- package/dist/src/app/create-app-context.js +127 -0
- package/dist/src/app/data-dir.js +29 -0
- package/dist/src/app/pipeline-config.js +105 -0
- package/dist/src/app/pipeline-plugin-config.js +2 -0
- package/dist/src/app/pipeline-registry.js +502 -0
- package/dist/src/app/pipeline-runtime.js +202 -0
- package/dist/src/app/runtime-store.js +151 -0
- package/dist/src/app/user-config.js +37 -0
- package/dist/src/artifacts/artifact-cleanup.js +192 -0
- package/dist/src/artifacts/artifact-index.js +262 -0
- package/dist/src/artifacts/artifact-rebuilder.js +120 -0
- package/dist/src/artifacts/storage-service.js +371 -0
- package/dist/src/cli/bootstrap.js +226 -0
- package/dist/src/cli/commands/agent.js +126 -0
- package/dist/src/cli/commands/artifact.js +175 -0
- package/dist/src/cli/commands/init.js +150 -0
- package/dist/src/cli/commands/pipeline/errors.js +37 -0
- package/dist/src/cli/commands/pipeline/result.js +179 -0
- package/dist/src/cli/commands/pipeline/selector.js +51 -0
- package/dist/src/cli/commands/pipeline/types.js +2 -0
- package/dist/src/cli/commands/pipeline/watch.js +67 -0
- package/dist/src/cli/commands/pipeline.js +339 -0
- package/dist/src/cli/commands/scheduler.js +81 -0
- package/dist/src/cli/commands/server.js +70 -0
- package/dist/src/cli/commands/system.js +21 -0
- package/dist/src/cli/errors.js +71 -0
- package/dist/src/cli/help.js +184 -0
- package/dist/src/cli/index.js +65 -0
- package/dist/src/cli/output.js +19 -0
- package/dist/src/cli/renderers/engine/json.js +67 -0
- package/dist/src/cli/renderers/engine/markdown.js +95 -0
- package/dist/src/cli/renderers/engine/types.js +2 -0
- package/dist/src/cli/renderers/engine/utils.js +32 -0
- package/dist/src/cli/renderers/index.js +27 -0
- package/dist/src/cli/renderers/specs/agent.js +78 -0
- package/dist/src/cli/renderers/specs/artifact.js +32 -0
- package/dist/src/cli/renderers/specs/index.js +36 -0
- package/dist/src/cli/renderers/specs/init.js +25 -0
- package/dist/src/cli/renderers/specs/pipeline.js +561 -0
- package/dist/src/cli/renderers/specs/scheduler.js +46 -0
- package/dist/src/cli/renderers/specs/server.js +38 -0
- package/dist/src/cli/renderers/specs/system.js +36 -0
- package/dist/src/cli/router.js +199 -0
- package/dist/src/cli/server-runtime-client.js +780 -0
- package/dist/src/cli/types.js +2 -0
- package/dist/src/gateway/frame-sanitizer.js +78 -0
- package/dist/src/gateway/gateway-client.js +462 -0
- package/dist/src/gateway/index.js +18 -0
- package/dist/src/gateway/types.js +2 -0
- package/dist/src/index.js +123 -0
- package/dist/src/logs/run-log-reader.js +141 -0
- package/dist/src/logs/run-log-service.js +42 -0
- package/dist/src/logs/run-log-types.js +2 -0
- package/dist/src/pipeline/agent-activity.js +191 -0
- package/dist/src/pipeline/artifact-storage.js +208 -0
- package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +105 -0
- package/dist/src/pipeline/diagnostics/index.js +6 -0
- package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +215 -0
- package/dist/src/pipeline/dispatch/pipeline-link-dispatcher.js +66 -0
- package/dist/src/pipeline/dispatch/pipeline-link-store.js +94 -0
- package/dist/src/pipeline/dispatch/pipeline-queue-drainer.js +71 -0
- package/dist/src/pipeline/execution/dependency-check.js +52 -0
- package/dist/src/pipeline/execution/execution-result.js +2 -0
- package/dist/src/pipeline/execution/group-item-executor.js +128 -0
- package/dist/src/pipeline/execution/index.js +5 -0
- package/dist/src/pipeline/execution/node-item-executor.js +58 -0
- package/dist/src/pipeline/execution/node-runner.js +159 -0
- package/dist/src/pipeline/execution/readiness-state.js +10 -0
- package/dist/src/pipeline/execution/reject-handler.js +94 -0
- package/dist/src/pipeline/execution/rejected-artifact-archiver.js +45 -0
- package/dist/src/pipeline/execution/route-item-manager.js +253 -0
- package/dist/src/pipeline/execution/run-abort-controller.js +66 -0
- package/dist/src/pipeline/execution/run-state-helpers.js +257 -0
- package/dist/src/pipeline/execution/service.js +165 -0
- package/dist/src/pipeline/execution/session-registry.js +96 -0
- package/dist/src/pipeline/execution/structured-node-runner.js +411 -0
- package/dist/src/pipeline/execution-status.js +96 -0
- package/dist/src/pipeline/execution-timeout.js +21 -0
- package/dist/src/pipeline/identity/index.js +32 -0
- package/dist/src/pipeline/identity/types.js +2 -0
- package/dist/src/pipeline/item-batch-controller.js +227 -0
- package/dist/src/pipeline/output/pipeline-output-resolver.js +91 -0
- package/dist/src/pipeline/output/pipeline-output-store.js +60 -0
- package/dist/src/pipeline/runtime-model.js +173 -0
- package/dist/src/pipeline/scheduler/dependency-state.js +144 -0
- package/dist/src/pipeline/scheduler-service.js +314 -0
- package/dist/src/pipeline/state/group-item-state.js +50 -0
- package/dist/src/pipeline/state/group-run-state.js +41 -0
- package/dist/src/pipeline/state/index.js +20 -0
- package/dist/src/pipeline/state/node-item-state.js +67 -0
- package/dist/src/pipeline/state/node-run-state.js +51 -0
- package/dist/src/pipeline/state/types.js +2 -0
- package/dist/src/pipeline/state-machine.js +101 -0
- package/dist/src/pipeline/structured-output/contract.js +133 -0
- package/dist/src/pipeline/structured-output/index.js +22 -0
- package/dist/src/pipeline/structured-output/parser.js +214 -0
- package/dist/src/pipeline/structured-output/prompt.js +290 -0
- package/dist/src/pipeline/structured-output/waiter.js +139 -0
- package/dist/src/pipeline/template.js +135 -0
- package/dist/src/pipeline/timeline-log-store.js +57 -0
- package/dist/src/pipeline/tool-activity.js +94 -0
- package/dist/src/pipeline/types/pipeline-link.js +7 -0
- package/dist/src/pipeline/types/pipeline-output.js +11 -0
- package/dist/src/pipeline/types/workflow.js +2 -0
- package/dist/src/pipeline/workflow/branch-rules.js +74 -0
- package/dist/src/pipeline/workflow/defaults.js +48 -0
- package/dist/src/pipeline/workflow/io.js +89 -0
- package/dist/src/pipeline/workflow/normalize.js +347 -0
- package/dist/src/pipeline/workflow/routes.js +16 -0
- package/dist/src/pipeline/workflow/template-mapper.js +113 -0
- package/dist/src/pipeline/workflow/validate.js +312 -0
- package/dist/src/pipeline/workflow-graph.js +165 -0
- package/dist/src/server/api-handler.js +163 -0
- package/dist/src/server/http-utils.js +34 -0
- package/dist/src/server/middleware.js +61 -0
- package/dist/src/server/router.js +105 -0
- package/dist/src/server/routes/agents.js +189 -0
- package/dist/src/server/routes/artifacts.js +163 -0
- package/dist/src/server/routes/gateway.js +18 -0
- package/dist/src/server/routes/health.js +16 -0
- package/dist/src/server/routes/logs.js +73 -0
- package/dist/src/server/routes/pipeline-batch.js +163 -0
- package/dist/src/server/routes/pipeline-diagnostics.js +33 -0
- package/dist/src/server/routes/pipeline-identity.js +24 -0
- package/dist/src/server/routes/pipeline-links.js +117 -0
- package/dist/src/server/routes/pipeline-outputs.js +27 -0
- package/dist/src/server/routes/pipeline-queue.js +62 -0
- package/dist/src/server/routes/pipeline-runtime.js +162 -0
- package/dist/src/server/routes/pipeline-scheduler.js +69 -0
- package/dist/src/server/routes/pipeline-workflow.js +180 -0
- package/dist/src/server/routes/pipelines.js +96 -0
- package/dist/src/server/routes/sessions.js +244 -0
- package/dist/src/server/routes/timeline.js +14 -0
- package/dist/src/server/serve-static.js +42 -0
- package/dist/src/server/types.js +2 -0
- package/dist/src/services/agent-service.js +79 -0
- package/dist/src/services/artifact-service.js +74 -0
- package/dist/src/services/gateway-read-helpers.js +10 -0
- package/dist/src/services/index.js +23 -0
- package/dist/src/services/pipeline-service.js +529 -0
- package/dist/src/services/pipeline-status.js +93 -0
- package/dist/src/services/read-services.js +60 -0
- package/dist/src/services/scheduler-service.js +37 -0
- package/dist/src/services/session-service.js +227 -0
- package/dist/src/services/system-service.js +26 -0
- package/dist/src/transport/ws-broker.js +48 -0
- package/dist/src/utils/array.js +17 -0
- package/dist/src/utils/guards.js +5 -0
- package/dist/src/utils/session.js +60 -0
- package/dist/src/version.js +5 -0
- package/package.json +61 -0
- package/web/dist/assets/index-CWnfhkn-.js +65 -0
- package/web/dist/assets/index-gZ0xOfSO.css +1 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
- package/web/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/web/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/web/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
- package/web/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/web/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/web/dist/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/web/dist/favicon.svg +10 -0
- package/web/dist/index.html +14 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPipelineRegistry = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const template_1 = require("../pipeline/template");
|
|
7
|
+
const pipeline_config_1 = require("./pipeline-config");
|
|
8
|
+
const pipeline_runtime_1 = require("./pipeline-runtime");
|
|
9
|
+
const pipeline_output_store_1 = require("../pipeline/output/pipeline-output-store");
|
|
10
|
+
const pipeline_output_resolver_1 = require("../pipeline/output/pipeline-output-resolver");
|
|
11
|
+
const pipeline_link_store_1 = require("../pipeline/dispatch/pipeline-link-store");
|
|
12
|
+
const pipeline_inbound_queue_1 = require("../pipeline/dispatch/pipeline-inbound-queue");
|
|
13
|
+
const pipeline_link_dispatcher_1 = require("../pipeline/dispatch/pipeline-link-dispatcher");
|
|
14
|
+
const pipeline_queue_drainer_1 = require("../pipeline/dispatch/pipeline-queue-drainer");
|
|
15
|
+
const sortCombinedTimeline = (items) => [...items].sort((a, b) => {
|
|
16
|
+
const aTs = Date.parse(a.ts);
|
|
17
|
+
const bTs = Date.parse(b.ts);
|
|
18
|
+
return Number.isFinite(aTs) && Number.isFinite(bTs) ? aTs - bTs : 0;
|
|
19
|
+
});
|
|
20
|
+
const isPipelineRuntimeBusy = (runtime) => {
|
|
21
|
+
const run = runtime.runtime.getRun();
|
|
22
|
+
const batchRunState = runtime.pipeline.getBatchRunState();
|
|
23
|
+
return (batchRunState.status === "running" ||
|
|
24
|
+
run.nodes.some((node) => node.status === "running" || (Boolean(node.startedAt) && !node.finishedAt)) ||
|
|
25
|
+
(run.groups ?? []).some((group) => group.status === "running" || (Boolean(group.startedAt) && !group.finishedAt)));
|
|
26
|
+
};
|
|
27
|
+
const createArchivedPipelineDirName = (pipelineId) => `${pipelineId}-${new Date().toISOString().replace(/[:.]/g, "-")}`;
|
|
28
|
+
const buildArchivedPipelineDirPath = (pipelineId) => {
|
|
29
|
+
const deletedRootDir = (0, pipeline_config_1.getDeletedPipelineRootDir)();
|
|
30
|
+
(0, node_fs_1.mkdirSync)(deletedRootDir, { recursive: true });
|
|
31
|
+
let archiveDirPath = (0, node_path_1.join)(deletedRootDir, createArchivedPipelineDirName(pipelineId));
|
|
32
|
+
let suffix = 1;
|
|
33
|
+
// 归档目录需要保证唯一,避免同秒内重复删除时互相覆盖。
|
|
34
|
+
while ((0, node_fs_1.existsSync)(archiveDirPath)) {
|
|
35
|
+
archiveDirPath = (0, node_path_1.join)(deletedRootDir, `${createArchivedPipelineDirName(pipelineId)}-${suffix}`);
|
|
36
|
+
suffix += 1;
|
|
37
|
+
}
|
|
38
|
+
return archiveDirPath;
|
|
39
|
+
};
|
|
40
|
+
const createPipelineRegistry = (options) => {
|
|
41
|
+
const pipelineDefinitions = (0, pipeline_config_1.loadPipelineDefinitions)();
|
|
42
|
+
const pipelineDefinitionById = new Map(pipelineDefinitions.map((definition) => [definition.id, definition]));
|
|
43
|
+
// Create output stores per pipeline
|
|
44
|
+
const outputStoreById = new Map(pipelineDefinitions.map((definition) => [
|
|
45
|
+
definition.id,
|
|
46
|
+
(0, pipeline_output_store_1.createPipelineOutputStore)(definition.id),
|
|
47
|
+
]));
|
|
48
|
+
// Create global dispatch infrastructure
|
|
49
|
+
const linkStore = (0, pipeline_link_store_1.createPipelineLinkStore)();
|
|
50
|
+
const inboundQueue = (0, pipeline_inbound_queue_1.createPipelineInboundQueue)();
|
|
51
|
+
let drainer = null;
|
|
52
|
+
const pipelineExists = (id) => pipelineDefinitionById.has(id);
|
|
53
|
+
const dispatcher = (0, pipeline_link_dispatcher_1.createPipelineLinkDispatcher)({
|
|
54
|
+
linkStore,
|
|
55
|
+
inboundQueue,
|
|
56
|
+
pipelineExists,
|
|
57
|
+
});
|
|
58
|
+
const onRunCompleted = async (run) => {
|
|
59
|
+
if (run.status !== "success")
|
|
60
|
+
return;
|
|
61
|
+
// Find the source pipeline by run id
|
|
62
|
+
let sourcePipelineId = null;
|
|
63
|
+
let sourceRuntime = null;
|
|
64
|
+
for (const [id, rt] of runtimeById) {
|
|
65
|
+
if (rt.runtime.getRun().id === run.id) {
|
|
66
|
+
sourcePipelineId = id;
|
|
67
|
+
sourceRuntime = rt;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!sourcePipelineId || !sourceRuntime)
|
|
72
|
+
return;
|
|
73
|
+
const workflow = sourceRuntime.workflow.getWorkflow();
|
|
74
|
+
const definition = pipelineDefinitionById.get(sourcePipelineId);
|
|
75
|
+
const artifactDir = definition?.artifactDir ?? "";
|
|
76
|
+
const batchRunState = sourceRuntime.pipeline.getBatchRunState();
|
|
77
|
+
const output = await (0, pipeline_output_resolver_1.resolvePipelineOutput)(workflow, run, artifactDir, sourcePipelineId, batchRunState.batchRunId);
|
|
78
|
+
if (!output)
|
|
79
|
+
return;
|
|
80
|
+
const outputStore = outputStoreById.get(sourcePipelineId);
|
|
81
|
+
if (!outputStore)
|
|
82
|
+
return;
|
|
83
|
+
const appended = await outputStore.append(output);
|
|
84
|
+
if (!appended)
|
|
85
|
+
return; // Duplicate
|
|
86
|
+
// Update run output
|
|
87
|
+
run.output = output;
|
|
88
|
+
// Dispatch to downstream pipelines
|
|
89
|
+
const dispatchResult = await dispatcher.dispatch(output);
|
|
90
|
+
// Request drain for each downstream pipeline
|
|
91
|
+
const links = await linkStore.list();
|
|
92
|
+
const downstreamIds = new Set(links
|
|
93
|
+
.filter((l) => l.enabled && l.fromPipelineId === sourcePipelineId)
|
|
94
|
+
.map((l) => l.toPipelineId));
|
|
95
|
+
for (const downstreamId of downstreamIds) {
|
|
96
|
+
drainer?.requestDrainInboundQueue(downstreamId);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
// Create runtimes with onRunCompleted
|
|
100
|
+
const runtimeById = new Map(pipelineDefinitions.map((definition) => [
|
|
101
|
+
definition.id,
|
|
102
|
+
(0, pipeline_runtime_1.createPipelineRuntime)({
|
|
103
|
+
pipelineId: definition.id,
|
|
104
|
+
client: options.client,
|
|
105
|
+
webOrigin: options.webOrigin,
|
|
106
|
+
defaultItemKeys: options.defaultItemKeys,
|
|
107
|
+
workflowFilePath: definition.workflowFilePath,
|
|
108
|
+
runStateFile: definition.runStateFile,
|
|
109
|
+
artifactDir: definition.artifactDir,
|
|
110
|
+
outputStore: outputStoreById.get(definition.id),
|
|
111
|
+
onRunCompleted,
|
|
112
|
+
}),
|
|
113
|
+
]));
|
|
114
|
+
// Create drainer (needs runtime access)
|
|
115
|
+
drainer = (0, pipeline_queue_drainer_1.createPipelineQueueDrainer)({
|
|
116
|
+
inboundQueue,
|
|
117
|
+
linkStore,
|
|
118
|
+
isPipelineBusy: (pipelineId) => {
|
|
119
|
+
const runtime = runtimeById.get(pipelineId);
|
|
120
|
+
if (!runtime)
|
|
121
|
+
return false;
|
|
122
|
+
return isPipelineRuntimeBusy(runtime);
|
|
123
|
+
},
|
|
124
|
+
executeInboundJob: async ({ jobId, linkId, upstreamOutput }) => {
|
|
125
|
+
const job = inboundQueue.getJobById(jobId);
|
|
126
|
+
if (!job)
|
|
127
|
+
return { ok: false, runId: null, error: "job_not_found" };
|
|
128
|
+
const toPipelineId = job.toPipelineId;
|
|
129
|
+
const runtime = runtimeById.get(toPipelineId);
|
|
130
|
+
if (!runtime)
|
|
131
|
+
return { ok: false, runId: null, error: "pipeline_not_found" };
|
|
132
|
+
if (isPipelineRuntimeBusy(runtime))
|
|
133
|
+
return { ok: false, runId: null, error: "pipeline_busy" };
|
|
134
|
+
const now = new Date().toISOString();
|
|
135
|
+
const nextRun = runtime.runtime.seedRun(runtime.workflow.getTemplateNodes(), runtime.pipeline.getItemRuns().length > 0 ? undefined : options.defaultItemKeys);
|
|
136
|
+
// Set pipeline_link input on the new run
|
|
137
|
+
nextRun.input = {
|
|
138
|
+
trigger: "pipeline_link",
|
|
139
|
+
inboundJobId: jobId,
|
|
140
|
+
linkId,
|
|
141
|
+
upstreamOutput,
|
|
142
|
+
};
|
|
143
|
+
runtime.runtime.setRun(nextRun);
|
|
144
|
+
runtime.workflow.getWorkflow(); // sync
|
|
145
|
+
runtime.runtime.emitPipeline();
|
|
146
|
+
// Mark job as running
|
|
147
|
+
await inboundQueue.appendEvent({
|
|
148
|
+
type: "job.running",
|
|
149
|
+
at: now,
|
|
150
|
+
jobId,
|
|
151
|
+
targetRunId: nextRun.id,
|
|
152
|
+
});
|
|
153
|
+
// Start draining the pipeline
|
|
154
|
+
const drainSignal = runtime.pipeline.getOrCreateDrainSignal(nextRun.id);
|
|
155
|
+
const drainResult = await runtime.pipeline.drainPipeline(`pipeline_link:${jobId}`, drainSignal);
|
|
156
|
+
return {
|
|
157
|
+
ok: !drainResult.hardFailed,
|
|
158
|
+
runId: nextRun.id,
|
|
159
|
+
error: drainResult.hardFailed ? "pipeline_execution_failed" : undefined,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const defaultPipelineId = (0, pipeline_config_1.getDefaultPipelineId)();
|
|
164
|
+
let broadcast = () => { };
|
|
165
|
+
const bindRuntimeBroadcast = (definition, runtime) => {
|
|
166
|
+
runtime.runtime.setBroadcast((payload) => {
|
|
167
|
+
const event = payload;
|
|
168
|
+
if (!event?.type)
|
|
169
|
+
return;
|
|
170
|
+
if (event.type === "pipeline.updated") {
|
|
171
|
+
broadcast({
|
|
172
|
+
type: "pipeline.updated",
|
|
173
|
+
payload: {
|
|
174
|
+
...event.payload,
|
|
175
|
+
pipelineId: definition.id,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (event.type === "timeline.updated") {
|
|
181
|
+
broadcast({
|
|
182
|
+
type: "timeline.updated",
|
|
183
|
+
payload: { item: event.payload.item, pipelineId: definition.id },
|
|
184
|
+
});
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// 网关状态/握手是全局共享事件,只透传主流水线的一份,避免前端收到重复广播。
|
|
188
|
+
if ((event.type === "gateway.status" || event.type === "gateway.ready" || event.type === "gateway.frame") &&
|
|
189
|
+
definition.id !== getPrimaryPipelineId()) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
broadcast(payload);
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
const getPipelineRuntime = (pipelineId) => runtimeById.get(pipelineId) ?? null;
|
|
196
|
+
const getPrimaryRuntime = () => {
|
|
197
|
+
const preferredRuntime = runtimeById.get(defaultPipelineId);
|
|
198
|
+
if (preferredRuntime)
|
|
199
|
+
return preferredRuntime;
|
|
200
|
+
const fallbackRuntime = runtimeById.values().next().value;
|
|
201
|
+
if (fallbackRuntime)
|
|
202
|
+
return fallbackRuntime;
|
|
203
|
+
throw new Error("pipeline_registry_empty");
|
|
204
|
+
};
|
|
205
|
+
const getPrimaryPipelineId = () => {
|
|
206
|
+
if (runtimeById.has(defaultPipelineId))
|
|
207
|
+
return defaultPipelineId;
|
|
208
|
+
const fallbackDefinition = pipelineDefinitions[0];
|
|
209
|
+
if (fallbackDefinition)
|
|
210
|
+
return fallbackDefinition.id;
|
|
211
|
+
throw new Error("pipeline_registry_empty");
|
|
212
|
+
};
|
|
213
|
+
const getCombinedTimeline = () => sortCombinedTimeline(pipelineDefinitions.flatMap((definition) => {
|
|
214
|
+
const runtime = runtimeById.get(definition.id);
|
|
215
|
+
return runtime ? runtime.runtime.getTimeline() : [];
|
|
216
|
+
}));
|
|
217
|
+
const getBootstrapPayload = () => {
|
|
218
|
+
const pipelines = {};
|
|
219
|
+
for (const definition of pipelineDefinitions) {
|
|
220
|
+
const runtime = runtimeById.get(definition.id);
|
|
221
|
+
if (!runtime)
|
|
222
|
+
continue;
|
|
223
|
+
const run = runtime.runtime.getRun();
|
|
224
|
+
pipelines[definition.id] = {
|
|
225
|
+
pipelineId: definition.id,
|
|
226
|
+
title: definition.title,
|
|
227
|
+
run: { ...run, nodes: run.nodes },
|
|
228
|
+
pipeline: run.nodes,
|
|
229
|
+
runId: run.id,
|
|
230
|
+
scheduler: runtime.pipeline.getSchedulerState(),
|
|
231
|
+
batchRunState: runtime.pipeline.getBatchRunState(),
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const primaryPipelineId = getPrimaryPipelineId();
|
|
235
|
+
const primary = pipelines[primaryPipelineId];
|
|
236
|
+
const MAX_BOOTSTRAP_TIMELINE = 50;
|
|
237
|
+
const combined = getCombinedTimeline();
|
|
238
|
+
return {
|
|
239
|
+
pipelines,
|
|
240
|
+
timeline: combined.slice(0, MAX_BOOTSTRAP_TIMELINE),
|
|
241
|
+
timelineHasMore: combined.length > MAX_BOOTSTRAP_TIMELINE,
|
|
242
|
+
status: getPrimaryRuntime().gateway.getLatestStatus() ?? options.client.getStatus(),
|
|
243
|
+
hello: getPrimaryRuntime().gateway.getLatestHello(),
|
|
244
|
+
// 保留旧字段兜底,避免前端分阶段改造时直接失效。
|
|
245
|
+
run: primary?.run,
|
|
246
|
+
pipeline: primary?.pipeline,
|
|
247
|
+
runId: primary?.runId,
|
|
248
|
+
scheduler: primary?.scheduler,
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
const broadcastBootstrapPayload = () => {
|
|
252
|
+
// 流水线资源发生新增、删除、重命名时,直接广播完整 bootstrap;
|
|
253
|
+
// 这样已连接前端可以一次性拿到最新 definitions + 运行态快照,避免自己拼补丁遗漏字段。
|
|
254
|
+
broadcast({
|
|
255
|
+
type: "bootstrap",
|
|
256
|
+
payload: getBootstrapPayload(),
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
for (const definition of pipelineDefinitions) {
|
|
260
|
+
const runtime = runtimeById.get(definition.id);
|
|
261
|
+
if (!runtime)
|
|
262
|
+
continue;
|
|
263
|
+
bindRuntimeBroadcast(definition, runtime);
|
|
264
|
+
}
|
|
265
|
+
const createRuntimeForDefinition = (definition) => (0, pipeline_runtime_1.createPipelineRuntime)({
|
|
266
|
+
pipelineId: definition.id,
|
|
267
|
+
client: options.client,
|
|
268
|
+
webOrigin: options.webOrigin,
|
|
269
|
+
defaultItemKeys: options.defaultItemKeys,
|
|
270
|
+
workflowFilePath: definition.workflowFilePath,
|
|
271
|
+
runStateFile: definition.runStateFile,
|
|
272
|
+
artifactDir: definition.artifactDir,
|
|
273
|
+
outputStore: outputStoreById.get(definition.id) ?? (0, pipeline_output_store_1.createPipelineOutputStore)(definition.id),
|
|
274
|
+
onRunCompleted,
|
|
275
|
+
});
|
|
276
|
+
const persistDefinitions = (items, defaultPipelineId) => {
|
|
277
|
+
const currentDocument = (0, pipeline_config_1.loadPipelineDefinitionsDocument)();
|
|
278
|
+
return (0, pipeline_config_1.savePipelineDefinitions)({
|
|
279
|
+
...currentDocument,
|
|
280
|
+
defaultPipelineId: defaultPipelineId && items.some((item) => item.id === defaultPipelineId)
|
|
281
|
+
? defaultPipelineId
|
|
282
|
+
: items[0]?.id ?? currentDocument.defaultPipelineId,
|
|
283
|
+
items,
|
|
284
|
+
});
|
|
285
|
+
};
|
|
286
|
+
const initializePipelineWorkflowFile = (definition, cloneFrom) => {
|
|
287
|
+
if (cloneFrom) {
|
|
288
|
+
// 克隆只复制 workflow 定义,运行态和产物目录仍由新流水线独立初始化。
|
|
289
|
+
const sourceWorkflow = (0, template_1.loadWorkflowDefinitionWithStorage)({ workflowFilePath: cloneFrom.workflowFilePath });
|
|
290
|
+
(0, template_1.saveWorkflowDefinitionWithStorage)(sourceWorkflow, { workflowFilePath: definition.workflowFilePath });
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const defaultWorkflow = (0, template_1.loadWorkflowDefinitionWithStorage)({ workflowFilePath: definition.workflowFilePath });
|
|
294
|
+
(0, template_1.saveWorkflowDefinitionWithStorage)(defaultWorkflow, { workflowFilePath: definition.workflowFilePath });
|
|
295
|
+
};
|
|
296
|
+
const archivePipelineDirectory = (definition) => {
|
|
297
|
+
const pipelineDir = (0, node_path_1.dirname)(definition.workflowFilePath);
|
|
298
|
+
if (!(0, node_fs_1.existsSync)(pipelineDir))
|
|
299
|
+
return;
|
|
300
|
+
const archivedDirPath = buildArchivedPipelineDirPath(definition.id);
|
|
301
|
+
(0, node_fs_1.renameSync)(pipelineDir, archivedDirPath);
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
initialize: async () => {
|
|
305
|
+
await inboundQueue.initialize();
|
|
306
|
+
for (const definition of pipelineDefinitions) {
|
|
307
|
+
const runtime = runtimeById.get(definition.id);
|
|
308
|
+
if (!runtime)
|
|
309
|
+
continue;
|
|
310
|
+
await runtime.initialize();
|
|
311
|
+
}
|
|
312
|
+
// 启动时扫描所有流水线,对已有 pending job 的队列触发 drain
|
|
313
|
+
for (const definition of pipelineDefinitions) {
|
|
314
|
+
const pendingCount = inboundQueue.getPendingCount(definition.id);
|
|
315
|
+
if (pendingCount > 0) {
|
|
316
|
+
drainer?.requestDrainInboundQueue(definition.id);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
dispose: () => {
|
|
321
|
+
for (const runtime of runtimeById.values()) {
|
|
322
|
+
runtime.dispose();
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
runtime: {
|
|
326
|
+
setBroadcast: (nextBroadcast) => {
|
|
327
|
+
broadcast = nextBroadcast;
|
|
328
|
+
},
|
|
329
|
+
getCombinedTimeline,
|
|
330
|
+
},
|
|
331
|
+
gateway: {
|
|
332
|
+
client: options.client,
|
|
333
|
+
getLatestStatus: () => getPrimaryRuntime().gateway.getLatestStatus(),
|
|
334
|
+
getLatestHello: () => getPrimaryRuntime().gateway.getLatestHello(),
|
|
335
|
+
getLastFrame: () => getPrimaryRuntime().gateway.getLastFrame(),
|
|
336
|
+
refreshSessionsFromGateway: () => getPrimaryRuntime().gateway.refreshSessionsFromGateway(),
|
|
337
|
+
getSessionCache: () => getPrimaryRuntime().gateway.getSessionCache(),
|
|
338
|
+
pickArray: getPrimaryRuntime().gateway.pickArray,
|
|
339
|
+
},
|
|
340
|
+
getBootstrapPayload,
|
|
341
|
+
listPipelines: () => [...pipelineDefinitions],
|
|
342
|
+
createPipeline: async (input) => {
|
|
343
|
+
const nextPipelineId = input.id.trim();
|
|
344
|
+
if (!(0, pipeline_config_1.isValidPipelineId)(nextPipelineId)) {
|
|
345
|
+
throw new Error("pipeline_id_invalid");
|
|
346
|
+
}
|
|
347
|
+
if (pipelineDefinitionById.has(nextPipelineId)) {
|
|
348
|
+
throw new Error("pipeline_already_exists");
|
|
349
|
+
}
|
|
350
|
+
const cloneSourceDefinition = input.cloneFrom ? pipelineDefinitionById.get(input.cloneFrom) ?? null : null;
|
|
351
|
+
if (input.cloneFrom && !cloneSourceDefinition) {
|
|
352
|
+
throw new Error("pipeline_clone_source_not_found");
|
|
353
|
+
}
|
|
354
|
+
const definition = (0, pipeline_config_1.createPipelineDefinition)(nextPipelineId, input.title);
|
|
355
|
+
const currentDocument = (0, pipeline_config_1.loadPipelineDefinitionsDocument)();
|
|
356
|
+
persistDefinitions([...currentDocument.items, { id: definition.id, title: definition.title }], currentDocument.defaultPipelineId);
|
|
357
|
+
let runtime = null;
|
|
358
|
+
try {
|
|
359
|
+
// 先把目标流水线的 workflow 文件初始化到位,再创建 runtime;
|
|
360
|
+
// 否则 runtime 构造阶段会先读到默认 workflow,导致“磁盘已克隆、内存仍是默认”的假克隆问题。
|
|
361
|
+
initializePipelineWorkflowFile(definition, cloneSourceDefinition ?? undefined);
|
|
362
|
+
runtime = createRuntimeForDefinition(definition);
|
|
363
|
+
bindRuntimeBroadcast(definition, runtime);
|
|
364
|
+
await runtime.initialize();
|
|
365
|
+
pipelineDefinitions.push(definition);
|
|
366
|
+
pipelineDefinitionById.set(definition.id, definition);
|
|
367
|
+
runtimeById.set(definition.id, runtime);
|
|
368
|
+
outputStoreById.set(definition.id, (0, pipeline_output_store_1.createPipelineOutputStore)(definition.id));
|
|
369
|
+
broadcastBootstrapPayload();
|
|
370
|
+
return definition;
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
runtime?.dispose();
|
|
374
|
+
(0, pipeline_config_1.savePipelineDefinitions)(currentDocument);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
renamePipeline: (pipelineId, title) => {
|
|
379
|
+
const definition = pipelineDefinitionById.get(pipelineId);
|
|
380
|
+
if (!definition) {
|
|
381
|
+
throw new Error("pipeline_not_found");
|
|
382
|
+
}
|
|
383
|
+
const normalizedTitle = title.trim();
|
|
384
|
+
if (!normalizedTitle) {
|
|
385
|
+
throw new Error("pipeline_title_invalid");
|
|
386
|
+
}
|
|
387
|
+
const currentDocument = (0, pipeline_config_1.loadPipelineDefinitionsDocument)();
|
|
388
|
+
const nextItems = currentDocument.items.map((item) => item.id === pipelineId
|
|
389
|
+
? {
|
|
390
|
+
...item,
|
|
391
|
+
title: normalizedTitle,
|
|
392
|
+
}
|
|
393
|
+
: item);
|
|
394
|
+
persistDefinitions(nextItems, currentDocument.defaultPipelineId);
|
|
395
|
+
definition.title = normalizedTitle;
|
|
396
|
+
broadcastBootstrapPayload();
|
|
397
|
+
return { ...definition };
|
|
398
|
+
},
|
|
399
|
+
deletePipeline: (pipelineId) => {
|
|
400
|
+
const definitionIndex = pipelineDefinitions.findIndex((definition) => definition.id === pipelineId);
|
|
401
|
+
if (definitionIndex < 0) {
|
|
402
|
+
throw new Error("pipeline_not_found");
|
|
403
|
+
}
|
|
404
|
+
if (pipelineDefinitions.length <= 1) {
|
|
405
|
+
throw new Error("pipeline_delete_last_forbidden");
|
|
406
|
+
}
|
|
407
|
+
const runtime = runtimeById.get(pipelineId);
|
|
408
|
+
if (!runtime) {
|
|
409
|
+
throw new Error("pipeline_not_found");
|
|
410
|
+
}
|
|
411
|
+
if (isPipelineRuntimeBusy(runtime)) {
|
|
412
|
+
throw new Error("pipeline_delete_running_forbidden");
|
|
413
|
+
}
|
|
414
|
+
const currentDocument = (0, pipeline_config_1.loadPipelineDefinitionsDocument)();
|
|
415
|
+
const nextItems = currentDocument.items.filter((item) => item.id !== pipelineId);
|
|
416
|
+
const fallbackPipelineId = nextItems[0]?.id ?? currentDocument.defaultPipelineId;
|
|
417
|
+
persistDefinitions(nextItems, currentDocument.defaultPipelineId === pipelineId ? fallbackPipelineId : currentDocument.defaultPipelineId);
|
|
418
|
+
try {
|
|
419
|
+
archivePipelineDirectory(pipelineDefinitions[definitionIndex]);
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
// 归档失败时立即回滚 definitions,避免页面列表先把流水线删掉但目录仍保持原位。
|
|
423
|
+
(0, pipeline_config_1.savePipelineDefinitions)(currentDocument);
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
runtime.dispose();
|
|
427
|
+
runtimeById.delete(pipelineId);
|
|
428
|
+
pipelineDefinitionById.delete(pipelineId);
|
|
429
|
+
pipelineDefinitions.splice(definitionIndex, 1);
|
|
430
|
+
broadcastBootstrapPayload();
|
|
431
|
+
return { pipelineId };
|
|
432
|
+
},
|
|
433
|
+
getPipelineRuntime,
|
|
434
|
+
getPrimaryRuntime,
|
|
435
|
+
getPipelineDefinition: (pipelineId) => pipelineDefinitionById.get(pipelineId) ?? null,
|
|
436
|
+
dispatch: {
|
|
437
|
+
listLinks: linkStore.list.bind(linkStore),
|
|
438
|
+
getLinkById: linkStore.getById.bind(linkStore),
|
|
439
|
+
createLink: linkStore.create.bind(linkStore),
|
|
440
|
+
updateLink: linkStore.update.bind(linkStore),
|
|
441
|
+
deleteLink: linkStore.remove.bind(linkStore),
|
|
442
|
+
getQueue: (pipelineId) => inboundQueue.getJobs(pipelineId),
|
|
443
|
+
getPendingJobs: (pipelineId) => inboundQueue.getPendingJobs(pipelineId),
|
|
444
|
+
getRunningJob: (pipelineId) => inboundQueue.getRunningJob(pipelineId),
|
|
445
|
+
cancelJob: inboundQueue.cancelJob.bind(inboundQueue),
|
|
446
|
+
retryJob: async (jobId) => {
|
|
447
|
+
const result = await inboundQueue.retryJob(jobId);
|
|
448
|
+
if (result.ok) {
|
|
449
|
+
drainer?.requestDrainInboundQueue(result.job.toPipelineId);
|
|
450
|
+
}
|
|
451
|
+
return result;
|
|
452
|
+
},
|
|
453
|
+
drainQueue: (pipelineId) => {
|
|
454
|
+
drainer?.requestDrainInboundQueue(pipelineId);
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
onGatewayStatus: (status) => {
|
|
458
|
+
for (const runtime of runtimeById.values())
|
|
459
|
+
runtime.onGatewayStatus(status);
|
|
460
|
+
},
|
|
461
|
+
onGatewayFrame: (frame) => {
|
|
462
|
+
for (const runtime of runtimeById.values())
|
|
463
|
+
runtime.onGatewayFrame(frame);
|
|
464
|
+
},
|
|
465
|
+
onGatewayRawFrame: (rawFrame) => {
|
|
466
|
+
if (rawFrame.type !== "event" && rawFrame.type !== "res")
|
|
467
|
+
return;
|
|
468
|
+
const payload = rawFrame.payload;
|
|
469
|
+
const sessionKey = payload
|
|
470
|
+
? (typeof payload.sessionKey === "string" && payload.sessionKey) ||
|
|
471
|
+
(typeof payload.sessionId === "string" && payload.sessionId) ||
|
|
472
|
+
(typeof payload.key === "string" && payload.key) ||
|
|
473
|
+
(typeof payload.session === "string" && payload.session) ||
|
|
474
|
+
null
|
|
475
|
+
: null;
|
|
476
|
+
if (sessionKey) {
|
|
477
|
+
let routed = false;
|
|
478
|
+
for (const runtime of runtimeById.values()) {
|
|
479
|
+
if (runtime.hasActiveSession?.(sessionKey)) {
|
|
480
|
+
runtime.onGatewayRawFrame(rawFrame);
|
|
481
|
+
routed = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (routed)
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
// 无法判定 sessionKey 或无匹配 runtime 时,投递全部
|
|
488
|
+
for (const runtime of runtimeById.values()) {
|
|
489
|
+
runtime.onGatewayRawFrame(rawFrame);
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
onGatewayError: (error) => {
|
|
493
|
+
for (const runtime of runtimeById.values())
|
|
494
|
+
runtime.onGatewayError(error);
|
|
495
|
+
},
|
|
496
|
+
onGatewayReady: (hello) => {
|
|
497
|
+
for (const runtime of runtimeById.values())
|
|
498
|
+
runtime.onGatewayReady(hello);
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
exports.createPipelineRegistry = createPipelineRegistry;
|