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.
Files changed (204) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +172 -0
  3. package/README.zh-CN.md +172 -0
  4. package/dist/src/app/app-context-env.js +51 -0
  5. package/dist/src/app/create-app-context.js +127 -0
  6. package/dist/src/app/data-dir.js +29 -0
  7. package/dist/src/app/pipeline-config.js +105 -0
  8. package/dist/src/app/pipeline-plugin-config.js +2 -0
  9. package/dist/src/app/pipeline-registry.js +502 -0
  10. package/dist/src/app/pipeline-runtime.js +202 -0
  11. package/dist/src/app/runtime-store.js +151 -0
  12. package/dist/src/app/user-config.js +37 -0
  13. package/dist/src/artifacts/artifact-cleanup.js +192 -0
  14. package/dist/src/artifacts/artifact-index.js +262 -0
  15. package/dist/src/artifacts/artifact-rebuilder.js +120 -0
  16. package/dist/src/artifacts/storage-service.js +371 -0
  17. package/dist/src/cli/bootstrap.js +226 -0
  18. package/dist/src/cli/commands/agent.js +126 -0
  19. package/dist/src/cli/commands/artifact.js +175 -0
  20. package/dist/src/cli/commands/init.js +150 -0
  21. package/dist/src/cli/commands/pipeline/errors.js +37 -0
  22. package/dist/src/cli/commands/pipeline/result.js +179 -0
  23. package/dist/src/cli/commands/pipeline/selector.js +51 -0
  24. package/dist/src/cli/commands/pipeline/types.js +2 -0
  25. package/dist/src/cli/commands/pipeline/watch.js +67 -0
  26. package/dist/src/cli/commands/pipeline.js +339 -0
  27. package/dist/src/cli/commands/scheduler.js +81 -0
  28. package/dist/src/cli/commands/server.js +70 -0
  29. package/dist/src/cli/commands/system.js +21 -0
  30. package/dist/src/cli/errors.js +71 -0
  31. package/dist/src/cli/help.js +184 -0
  32. package/dist/src/cli/index.js +65 -0
  33. package/dist/src/cli/output.js +19 -0
  34. package/dist/src/cli/renderers/engine/json.js +67 -0
  35. package/dist/src/cli/renderers/engine/markdown.js +95 -0
  36. package/dist/src/cli/renderers/engine/types.js +2 -0
  37. package/dist/src/cli/renderers/engine/utils.js +32 -0
  38. package/dist/src/cli/renderers/index.js +27 -0
  39. package/dist/src/cli/renderers/specs/agent.js +78 -0
  40. package/dist/src/cli/renderers/specs/artifact.js +32 -0
  41. package/dist/src/cli/renderers/specs/index.js +36 -0
  42. package/dist/src/cli/renderers/specs/init.js +25 -0
  43. package/dist/src/cli/renderers/specs/pipeline.js +561 -0
  44. package/dist/src/cli/renderers/specs/scheduler.js +46 -0
  45. package/dist/src/cli/renderers/specs/server.js +38 -0
  46. package/dist/src/cli/renderers/specs/system.js +36 -0
  47. package/dist/src/cli/router.js +199 -0
  48. package/dist/src/cli/server-runtime-client.js +780 -0
  49. package/dist/src/cli/types.js +2 -0
  50. package/dist/src/gateway/frame-sanitizer.js +78 -0
  51. package/dist/src/gateway/gateway-client.js +462 -0
  52. package/dist/src/gateway/index.js +18 -0
  53. package/dist/src/gateway/types.js +2 -0
  54. package/dist/src/index.js +123 -0
  55. package/dist/src/logs/run-log-reader.js +141 -0
  56. package/dist/src/logs/run-log-service.js +42 -0
  57. package/dist/src/logs/run-log-types.js +2 -0
  58. package/dist/src/pipeline/agent-activity.js +191 -0
  59. package/dist/src/pipeline/artifact-storage.js +208 -0
  60. package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +105 -0
  61. package/dist/src/pipeline/diagnostics/index.js +6 -0
  62. package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +215 -0
  63. package/dist/src/pipeline/dispatch/pipeline-link-dispatcher.js +66 -0
  64. package/dist/src/pipeline/dispatch/pipeline-link-store.js +94 -0
  65. package/dist/src/pipeline/dispatch/pipeline-queue-drainer.js +71 -0
  66. package/dist/src/pipeline/execution/dependency-check.js +52 -0
  67. package/dist/src/pipeline/execution/execution-result.js +2 -0
  68. package/dist/src/pipeline/execution/group-item-executor.js +128 -0
  69. package/dist/src/pipeline/execution/index.js +5 -0
  70. package/dist/src/pipeline/execution/node-item-executor.js +58 -0
  71. package/dist/src/pipeline/execution/node-runner.js +159 -0
  72. package/dist/src/pipeline/execution/readiness-state.js +10 -0
  73. package/dist/src/pipeline/execution/reject-handler.js +94 -0
  74. package/dist/src/pipeline/execution/rejected-artifact-archiver.js +45 -0
  75. package/dist/src/pipeline/execution/route-item-manager.js +253 -0
  76. package/dist/src/pipeline/execution/run-abort-controller.js +66 -0
  77. package/dist/src/pipeline/execution/run-state-helpers.js +257 -0
  78. package/dist/src/pipeline/execution/service.js +165 -0
  79. package/dist/src/pipeline/execution/session-registry.js +96 -0
  80. package/dist/src/pipeline/execution/structured-node-runner.js +411 -0
  81. package/dist/src/pipeline/execution-status.js +96 -0
  82. package/dist/src/pipeline/execution-timeout.js +21 -0
  83. package/dist/src/pipeline/identity/index.js +32 -0
  84. package/dist/src/pipeline/identity/types.js +2 -0
  85. package/dist/src/pipeline/item-batch-controller.js +227 -0
  86. package/dist/src/pipeline/output/pipeline-output-resolver.js +91 -0
  87. package/dist/src/pipeline/output/pipeline-output-store.js +60 -0
  88. package/dist/src/pipeline/runtime-model.js +173 -0
  89. package/dist/src/pipeline/scheduler/dependency-state.js +144 -0
  90. package/dist/src/pipeline/scheduler-service.js +314 -0
  91. package/dist/src/pipeline/state/group-item-state.js +50 -0
  92. package/dist/src/pipeline/state/group-run-state.js +41 -0
  93. package/dist/src/pipeline/state/index.js +20 -0
  94. package/dist/src/pipeline/state/node-item-state.js +67 -0
  95. package/dist/src/pipeline/state/node-run-state.js +51 -0
  96. package/dist/src/pipeline/state/types.js +2 -0
  97. package/dist/src/pipeline/state-machine.js +101 -0
  98. package/dist/src/pipeline/structured-output/contract.js +133 -0
  99. package/dist/src/pipeline/structured-output/index.js +22 -0
  100. package/dist/src/pipeline/structured-output/parser.js +214 -0
  101. package/dist/src/pipeline/structured-output/prompt.js +290 -0
  102. package/dist/src/pipeline/structured-output/waiter.js +139 -0
  103. package/dist/src/pipeline/template.js +135 -0
  104. package/dist/src/pipeline/timeline-log-store.js +57 -0
  105. package/dist/src/pipeline/tool-activity.js +94 -0
  106. package/dist/src/pipeline/types/pipeline-link.js +7 -0
  107. package/dist/src/pipeline/types/pipeline-output.js +11 -0
  108. package/dist/src/pipeline/types/workflow.js +2 -0
  109. package/dist/src/pipeline/workflow/branch-rules.js +74 -0
  110. package/dist/src/pipeline/workflow/defaults.js +48 -0
  111. package/dist/src/pipeline/workflow/io.js +89 -0
  112. package/dist/src/pipeline/workflow/normalize.js +347 -0
  113. package/dist/src/pipeline/workflow/routes.js +16 -0
  114. package/dist/src/pipeline/workflow/template-mapper.js +113 -0
  115. package/dist/src/pipeline/workflow/validate.js +312 -0
  116. package/dist/src/pipeline/workflow-graph.js +165 -0
  117. package/dist/src/server/api-handler.js +163 -0
  118. package/dist/src/server/http-utils.js +34 -0
  119. package/dist/src/server/middleware.js +61 -0
  120. package/dist/src/server/router.js +105 -0
  121. package/dist/src/server/routes/agents.js +189 -0
  122. package/dist/src/server/routes/artifacts.js +163 -0
  123. package/dist/src/server/routes/gateway.js +18 -0
  124. package/dist/src/server/routes/health.js +16 -0
  125. package/dist/src/server/routes/logs.js +73 -0
  126. package/dist/src/server/routes/pipeline-batch.js +163 -0
  127. package/dist/src/server/routes/pipeline-diagnostics.js +33 -0
  128. package/dist/src/server/routes/pipeline-identity.js +24 -0
  129. package/dist/src/server/routes/pipeline-links.js +117 -0
  130. package/dist/src/server/routes/pipeline-outputs.js +27 -0
  131. package/dist/src/server/routes/pipeline-queue.js +62 -0
  132. package/dist/src/server/routes/pipeline-runtime.js +162 -0
  133. package/dist/src/server/routes/pipeline-scheduler.js +69 -0
  134. package/dist/src/server/routes/pipeline-workflow.js +180 -0
  135. package/dist/src/server/routes/pipelines.js +96 -0
  136. package/dist/src/server/routes/sessions.js +244 -0
  137. package/dist/src/server/routes/timeline.js +14 -0
  138. package/dist/src/server/serve-static.js +42 -0
  139. package/dist/src/server/types.js +2 -0
  140. package/dist/src/services/agent-service.js +79 -0
  141. package/dist/src/services/artifact-service.js +74 -0
  142. package/dist/src/services/gateway-read-helpers.js +10 -0
  143. package/dist/src/services/index.js +23 -0
  144. package/dist/src/services/pipeline-service.js +529 -0
  145. package/dist/src/services/pipeline-status.js +93 -0
  146. package/dist/src/services/read-services.js +60 -0
  147. package/dist/src/services/scheduler-service.js +37 -0
  148. package/dist/src/services/session-service.js +227 -0
  149. package/dist/src/services/system-service.js +26 -0
  150. package/dist/src/transport/ws-broker.js +48 -0
  151. package/dist/src/utils/array.js +17 -0
  152. package/dist/src/utils/guards.js +5 -0
  153. package/dist/src/utils/session.js +60 -0
  154. package/dist/src/version.js +5 -0
  155. package/package.json +61 -0
  156. package/web/dist/assets/index-CWnfhkn-.js +65 -0
  157. package/web/dist/assets/index-gZ0xOfSO.css +1 -0
  158. package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  159. package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  160. package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  161. package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  162. package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
  163. package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
  164. package/web/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  165. package/web/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  166. package/web/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  167. package/web/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  168. package/web/dist/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
  169. package/web/dist/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
  170. package/web/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  171. package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  172. package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  173. package/web/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  174. package/web/dist/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
  175. package/web/dist/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
  176. package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  177. package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  178. package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  179. package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  180. package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
  181. package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
  182. package/web/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  183. package/web/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  184. package/web/dist/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
  185. package/web/dist/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
  186. package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  187. package/web/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
  188. package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
  189. package/web/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  190. package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  191. package/web/dist/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
  192. package/web/dist/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
  193. package/web/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
  194. package/web/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
  195. package/web/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  196. package/web/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  197. package/web/dist/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
  198. package/web/dist/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
  199. package/web/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
  200. package/web/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
  201. package/web/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  202. package/web/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  203. package/web/dist/favicon.svg +10 -0
  204. package/web/dist/index.html +14 -0
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.diagnoseNodeDependency = exports.REASON_MESSAGES = void 0;
4
+ const dependency_check_1 = require("../execution/dependency-check");
5
+ exports.REASON_MESSAGES = {
6
+ dependency_satisfied: "依赖已满足",
7
+ source_not_success: "上游节点未成功(当前状态不满足依赖)",
8
+ source_failed: "上游节点执行失败",
9
+ source_skipped: "上游节点已跳过",
10
+ route_mismatch: "路由不匹配(上游路由值与边要求不一致)",
11
+ cross_branch_edge_blocked: "跨支线边已被阻断",
12
+ group_not_success: "上游并行组未成功",
13
+ source_disabled_dependency_satisfied: "上游节点已禁用(视为依赖满足)",
14
+ source_disabled_route_impossible: "上游节点已禁用但路由边无法满足",
15
+ missing_source_item_run: "缺少上游节点条目运行记录",
16
+ missing_group_item_run: "缺少上游并行组条目运行记录",
17
+ };
18
+ const resolveOutcome = (satisfiedCount, impossibleCount, totalIncoming, policy) => {
19
+ if (policy === "any") {
20
+ if (satisfiedCount > 0)
21
+ return "queued";
22
+ if (impossibleCount === totalIncoming)
23
+ return "skipped";
24
+ return "waiting";
25
+ }
26
+ if (satisfiedCount === totalIncoming)
27
+ return "queued";
28
+ if (satisfiedCount + impossibleCount === totalIncoming && impossibleCount > 0)
29
+ return "skipped";
30
+ return "waiting";
31
+ };
32
+ const resolveReason = (satisfied, impossible, ctx, edge, itemKey) => {
33
+ if (satisfied) {
34
+ if (ctx.isGroupId(edge.from))
35
+ return "dependency_satisfied";
36
+ if (!ctx.isWorkflowNodeEnabled(edge.from))
37
+ return "source_disabled_dependency_satisfied";
38
+ return "dependency_satisfied";
39
+ }
40
+ if (ctx.isCrossBranchEdge(edge))
41
+ return "cross_branch_edge_blocked";
42
+ if (ctx.isGroupId(edge.from)) {
43
+ const sourceGroup = ctx.getGroupItemRun(edge.from, itemKey);
44
+ if (!sourceGroup)
45
+ return "missing_group_item_run";
46
+ if (sourceGroup.status === "failed")
47
+ return "group_not_success";
48
+ if (sourceGroup.status === "skipped")
49
+ return "group_not_success";
50
+ return "group_not_success";
51
+ }
52
+ if (!ctx.isWorkflowNodeEnabled(edge.from)) {
53
+ if (edge.when !== null)
54
+ return "source_disabled_route_impossible";
55
+ return "source_disabled_dependency_satisfied";
56
+ }
57
+ const source = ctx.getItemRun(edge.from, itemKey);
58
+ if (!source)
59
+ return "missing_source_item_run";
60
+ if (edge.when && source.status === "success" && source.route !== edge.when) {
61
+ return "route_mismatch";
62
+ }
63
+ if (source.status === "failed")
64
+ return "source_failed";
65
+ if (source.status === "skipped")
66
+ return "source_skipped";
67
+ return "source_not_success";
68
+ };
69
+ const diagnoseNodeDependency = (run, graph, nodeId, itemKey) => {
70
+ const workflowNode = graph.getWorkflowNodeById(nodeId);
71
+ const policy = workflowNode?.dependencyPolicy === "any" ? "any" : "all";
72
+ const incoming = graph.getIncomingEdges(nodeId);
73
+ const itemRuns = (run.itemRuns ?? [])
74
+ .filter((item) => item.nodeId === nodeId)
75
+ .filter((item) => !itemKey || item.itemKey === itemKey);
76
+ if (itemRuns.length === 0)
77
+ return [];
78
+ const ctx = {
79
+ isCrossBranchEdge: (edge) => graph.isCrossBranchEdge(edge),
80
+ isGroupId: (id) => graph.isGroupId(id),
81
+ isWorkflowNodeEnabled: (id) => graph.isWorkflowNodeEnabled(id),
82
+ isRoutePolicyNode: (id) => (graph.getWorkflowNodeById(id)?.routePolicy?.allowed.length ?? 0) > 0,
83
+ getGroupItemRun: (groupId, key) => (run.groupItemRuns ?? []).find((item) => item.groupId === groupId && item.itemKey === key) ?? null,
84
+ getItemRun: (node, key) => (run.itemRuns ?? []).find((item) => item.nodeId === node && item.itemKey === key) ?? null,
85
+ };
86
+ return itemRuns.map((item) => {
87
+ const incomingDiagnostics = incoming.map((edge) => {
88
+ const satisfied = (0, dependency_check_1.isDependencySatisfied)(item.itemKey, edge, ctx);
89
+ const impossible = (0, dependency_check_1.canNeverSatisfy)(item.itemKey, edge, ctx);
90
+ const reason = resolveReason(satisfied, impossible, ctx, edge, item.itemKey);
91
+ return { from: edge.from, when: edge.when, satisfied, impossible, reason };
92
+ });
93
+ const satisfiedCount = incomingDiagnostics.filter((d) => d.satisfied).length;
94
+ const impossibleCount = incomingDiagnostics.filter((d) => d.impossible).length;
95
+ const outcome = resolveOutcome(satisfiedCount, impossibleCount, incoming.length, policy);
96
+ return {
97
+ itemKey: item.itemKey,
98
+ nodeId: item.nodeId,
99
+ incoming: incomingDiagnostics,
100
+ policy,
101
+ outcome,
102
+ };
103
+ });
104
+ };
105
+ exports.diagnoseNodeDependency = diagnoseNodeDependency;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REASON_MESSAGES = exports.diagnoseNodeDependency = void 0;
4
+ var dependency_diagnostic_1 = require("./dependency-diagnostic");
5
+ Object.defineProperty(exports, "diagnoseNodeDependency", { enumerable: true, get: function () { return dependency_diagnostic_1.diagnoseNodeDependency; } });
6
+ Object.defineProperty(exports, "REASON_MESSAGES", { enumerable: true, get: function () { return dependency_diagnostic_1.REASON_MESSAGES; } });
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildJobId = exports.createPipelineInboundQueue = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const pipeline_link_1 = require("../types/pipeline-link");
6
+ Object.defineProperty(exports, "buildJobId", { enumerable: true, get: function () { return pipeline_link_1.buildJobId; } });
7
+ const data_dir_1 = require("../../app/data-dir");
8
+ const QUEUE_FILE = (0, data_dir_1.resolveTaskMeldDataPath)("pipeline-inbound-queue.jsonl");
9
+ const MAX_COMPLETED_JOBS = 500;
10
+ const COMPACT_TRIGGER_EVENT_COUNT = 2000;
11
+ const pruneCompletedJobs = (snapshot) => {
12
+ const completed = [];
13
+ for (const job of snapshot.values()) {
14
+ if (job.status === "success" || job.status === "failed" || job.status === "canceled") {
15
+ completed.push(job);
16
+ }
17
+ }
18
+ if (completed.length <= MAX_COMPLETED_JOBS)
19
+ return;
20
+ completed.sort((a, b) => (a.finishedAt ?? "").localeCompare(b.finishedAt ?? ""));
21
+ const toDelete = completed.length - MAX_COMPLETED_JOBS;
22
+ for (let i = 0; i < toDelete; i++) {
23
+ snapshot.delete(completed[i].jobId);
24
+ }
25
+ };
26
+ const createPipelineInboundQueue = () => {
27
+ let jobSnapshot = new Map();
28
+ const replay = async () => {
29
+ const newSnapshot = new Map();
30
+ try {
31
+ const raw = await (0, promises_1.readFile)(QUEUE_FILE, "utf8");
32
+ const lines = raw.trim().split("\n").filter(Boolean);
33
+ for (const line of lines) {
34
+ try {
35
+ const event = JSON.parse(line);
36
+ applyEvent(event, newSnapshot);
37
+ }
38
+ catch {
39
+ // Skip malformed lines
40
+ }
41
+ }
42
+ }
43
+ catch {
44
+ // File not found, start empty
45
+ }
46
+ // 重放后自动回收"running"状态的 job:这些 job 属于上次进程崩溃前已启动但未完成的任务,
47
+ // 恢复为 pending 并清空 targetRunId,让 drainer 重新调度执行。
48
+ for (const [jobId, job] of newSnapshot) {
49
+ if (job.status === "running") {
50
+ newSnapshot.set(jobId, {
51
+ ...job,
52
+ status: "pending",
53
+ targetRunId: null,
54
+ startedAt: null,
55
+ error: "recovered_after_crash",
56
+ });
57
+ }
58
+ }
59
+ jobSnapshot = newSnapshot;
60
+ };
61
+ const applyEvent = (event, snapshot) => {
62
+ const now = new Date().toISOString();
63
+ switch (event.type) {
64
+ case "job.created": {
65
+ snapshot.set(event.job.jobId, { ...event.job });
66
+ break;
67
+ }
68
+ case "job.running": {
69
+ const job = snapshot.get(event.jobId);
70
+ if (job) {
71
+ snapshot.set(event.jobId, {
72
+ ...job,
73
+ status: "running",
74
+ targetRunId: event.targetRunId,
75
+ startedAt: event.at,
76
+ attempts: job.attempts + 1,
77
+ });
78
+ }
79
+ break;
80
+ }
81
+ case "job.success": {
82
+ const job = snapshot.get(event.jobId);
83
+ if (job) {
84
+ snapshot.set(event.jobId, {
85
+ ...job,
86
+ status: "success",
87
+ finishedAt: event.at,
88
+ });
89
+ }
90
+ pruneCompletedJobs(snapshot);
91
+ break;
92
+ }
93
+ case "job.failed": {
94
+ const job = snapshot.get(event.jobId);
95
+ if (job) {
96
+ snapshot.set(event.jobId, {
97
+ ...job,
98
+ status: "failed",
99
+ targetRunId: event.targetRunId ?? job.targetRunId,
100
+ finishedAt: event.at,
101
+ error: event.error,
102
+ });
103
+ }
104
+ pruneCompletedJobs(snapshot);
105
+ break;
106
+ }
107
+ case "job.canceled": {
108
+ const job = snapshot.get(event.jobId);
109
+ if (job) {
110
+ snapshot.set(event.jobId, {
111
+ ...job,
112
+ status: "canceled",
113
+ finishedAt: event.at,
114
+ });
115
+ }
116
+ pruneCompletedJobs(snapshot);
117
+ break;
118
+ }
119
+ case "job.retry_requested": {
120
+ const job = snapshot.get(event.jobId);
121
+ if (job) {
122
+ snapshot.set(event.jobId, {
123
+ ...job,
124
+ status: "pending",
125
+ targetRunId: null,
126
+ startedAt: null,
127
+ finishedAt: null,
128
+ error: null,
129
+ });
130
+ }
131
+ break;
132
+ }
133
+ }
134
+ };
135
+ const persistEvent = async (event) => {
136
+ await (0, promises_1.mkdir)((0, data_dir_1.resolveTaskMeldDataPath)(), { recursive: true });
137
+ const line = JSON.stringify(event) + "\n";
138
+ await (0, promises_1.appendFile)(QUEUE_FILE, line, "utf8");
139
+ };
140
+ let writeChain = Promise.resolve();
141
+ let eventsSinceCompact = 0;
142
+ const enqueueWrite = (op) => {
143
+ const next = writeChain.catch(() => { }).then(op);
144
+ writeChain = next.then(() => { }, () => { });
145
+ return next;
146
+ };
147
+ const compactJsonl = async (snapshot) => {
148
+ const tmpFile = `${QUEUE_FILE}.compact`;
149
+ await (0, promises_1.mkdir)((0, data_dir_1.resolveTaskMeldDataPath)(), { recursive: true });
150
+ const lines = [];
151
+ const now = new Date().toISOString();
152
+ for (const job of snapshot.values()) {
153
+ lines.push(JSON.stringify({ type: "job.created", at: now, job }));
154
+ }
155
+ await (0, promises_1.writeFile)(tmpFile, lines.length > 0 ? lines.join("\n") + "\n" : "", "utf8");
156
+ await (0, promises_1.rename)(tmpFile, QUEUE_FILE);
157
+ eventsSinceCompact = 0;
158
+ };
159
+ const filterByPipeline = (toPipelineId, status) => [...jobSnapshot.values()]
160
+ .filter((j) => j.toPipelineId === toPipelineId && (!status || j.status === status))
161
+ .sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.jobId.localeCompare(b.jobId));
162
+ const queue = {
163
+ initialize: async () => {
164
+ await enqueueWrite(async () => {
165
+ await replay();
166
+ pruneCompletedJobs(jobSnapshot);
167
+ await compactJsonl(jobSnapshot);
168
+ });
169
+ },
170
+ getJobs: (toPipelineId) => filterByPipeline(toPipelineId),
171
+ getPendingJobs: (toPipelineId) => filterByPipeline(toPipelineId, "pending"),
172
+ getRunningJob: (toPipelineId) => {
173
+ const running = filterByPipeline(toPipelineId, "running");
174
+ return running.length > 0 ? running[0] : null;
175
+ },
176
+ getJobById: (jobId) => jobSnapshot.get(jobId) ?? null,
177
+ getJobCount: (toPipelineId) => filterByPipeline(toPipelineId).length,
178
+ getPendingCount: (toPipelineId) => filterByPipeline(toPipelineId, "pending").length,
179
+ appendEvent: async (event) => {
180
+ await enqueueWrite(async () => {
181
+ await persistEvent(event);
182
+ applyEvent(event, jobSnapshot);
183
+ eventsSinceCompact += 1;
184
+ if (eventsSinceCompact >= COMPACT_TRIGGER_EVENT_COUNT) {
185
+ await compactJsonl(jobSnapshot);
186
+ }
187
+ });
188
+ },
189
+ cancelJob: async (jobId, reason) => {
190
+ const job = jobSnapshot.get(jobId);
191
+ if (!job)
192
+ return { ok: false, error: "pipeline_queue_job_not_found" };
193
+ if (job.status !== "pending")
194
+ return { ok: false, error: "pipeline_queue_job_not_cancelable" };
195
+ await queue.appendEvent({ type: "job.canceled", at: new Date().toISOString(), jobId, reason });
196
+ return { ok: true };
197
+ },
198
+ retryJob: async (jobId) => {
199
+ const job = jobSnapshot.get(jobId);
200
+ if (!job)
201
+ return { ok: false, error: "pipeline_queue_job_not_found" };
202
+ if (job.status !== "failed" && job.status !== "canceled") {
203
+ return { ok: false, error: "pipeline_queue_job_not_retryable" };
204
+ }
205
+ await queue.appendEvent({ type: "job.retry_requested", at: new Date().toISOString(), jobId });
206
+ const updated = jobSnapshot.get(jobId);
207
+ if (!updated)
208
+ return { ok: false, error: "pipeline_queue_job_not_found" };
209
+ return { ok: true, job: updated };
210
+ },
211
+ getJobSnapshot: () => jobSnapshot,
212
+ };
213
+ return queue;
214
+ };
215
+ exports.createPipelineInboundQueue = createPipelineInboundQueue;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPipelineLinkDispatcher = void 0;
4
+ const pipeline_link_1 = require("../types/pipeline-link");
5
+ const createPipelineLinkDispatcher = (deps) => {
6
+ const { linkStore, inboundQueue, pipelineExists } = deps;
7
+ const validateContract = (link, output) => {
8
+ if (!link.inputContract)
9
+ return true;
10
+ const { requireType, requireSchemaVersion } = link.inputContract;
11
+ if (requireType && output.artifactRef.type !== requireType)
12
+ return false;
13
+ if (requireSchemaVersion !== undefined && output.artifactRef.schemaVersion !== requireSchemaVersion)
14
+ return false;
15
+ return true;
16
+ };
17
+ return {
18
+ dispatch: async (output) => {
19
+ const links = await linkStore.list();
20
+ const matchingLinks = links.filter((l) => l.enabled && l.fromPipelineId === output.pipelineId);
21
+ let dispatched = 0;
22
+ const errors = [];
23
+ for (const link of matchingLinks) {
24
+ if (!pipelineExists(link.toPipelineId)) {
25
+ errors.push(`link ${link.id}: downstream pipeline ${link.toPipelineId} not found`);
26
+ continue;
27
+ }
28
+ if (!validateContract(link, output)) {
29
+ errors.push(`link ${link.id}: input contract mismatch`);
30
+ continue;
31
+ }
32
+ const pendingCount = inboundQueue.getPendingCount(link.toPipelineId);
33
+ if (pendingCount >= link.maxPendingJobs) {
34
+ errors.push(`link ${link.id}: queue full (${pendingCount}/${link.maxPendingJobs})`);
35
+ continue;
36
+ }
37
+ // Dedup: check if job already exists for this link+output
38
+ const jobId = (0, pipeline_link_1.buildJobId)(link.id, output.outputId);
39
+ const existing = inboundQueue.getJobById(jobId);
40
+ if (existing) {
41
+ continue; // Already dispatched, skip
42
+ }
43
+ const now = new Date().toISOString();
44
+ const job = {
45
+ schemaVersion: 1,
46
+ jobId,
47
+ linkId: link.id,
48
+ fromPipelineId: output.pipelineId,
49
+ toPipelineId: link.toPipelineId,
50
+ status: "pending",
51
+ upstreamOutput: output,
52
+ targetRunId: null,
53
+ attempts: 0,
54
+ createdAt: now,
55
+ startedAt: null,
56
+ finishedAt: null,
57
+ error: null,
58
+ };
59
+ await inboundQueue.appendEvent({ type: "job.created", at: now, job });
60
+ dispatched += 1;
61
+ }
62
+ return { dispatched, errors };
63
+ },
64
+ };
65
+ };
66
+ exports.createPipelineLinkDispatcher = createPipelineLinkDispatcher;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPipelineLinkStore = void 0;
4
+ const promises_1 = require("node:fs/promises");
5
+ const pipeline_link_1 = require("../types/pipeline-link");
6
+ const data_dir_1 = require("../../app/data-dir");
7
+ const LINKS_FILE = (0, data_dir_1.resolveTaskMeldDataPath)("pipeline-links.json");
8
+ const loadLinksDocument = async () => {
9
+ try {
10
+ const raw = await (0, promises_1.readFile)(LINKS_FILE, "utf8");
11
+ const parsed = JSON.parse(raw);
12
+ if (parsed && typeof parsed === "object" && parsed.version === 1) {
13
+ return parsed;
14
+ }
15
+ }
16
+ catch {
17
+ // File not found or invalid, return default
18
+ }
19
+ return { version: 1, items: [] };
20
+ };
21
+ const saveLinksDocument = async (doc) => {
22
+ await (0, promises_1.mkdir)((0, data_dir_1.resolveTaskMeldDataPath)(), { recursive: true });
23
+ await (0, promises_1.writeFile)(LINKS_FILE, JSON.stringify(doc, null, 2), "utf8");
24
+ };
25
+ const createPipelineLinkStore = () => {
26
+ const store = {
27
+ list: async () => {
28
+ const doc = await loadLinksDocument();
29
+ return doc.items;
30
+ },
31
+ getById: async (id) => {
32
+ const doc = await loadLinksDocument();
33
+ return doc.items.find((link) => link.id === id) ?? null;
34
+ },
35
+ create: async (link) => {
36
+ if (!(0, pipeline_link_1.isValidLinkId)(link.id)) {
37
+ return { ok: false, error: "pipeline_link_invalid_id" };
38
+ }
39
+ if (link.fromPipelineId === link.toPipelineId) {
40
+ return { ok: false, error: "pipeline_link_self_loop" };
41
+ }
42
+ const doc = await loadLinksDocument();
43
+ if (doc.items.some((l) => l.id === link.id)) {
44
+ return { ok: false, error: "pipeline_link_already_exists" };
45
+ }
46
+ // Check for duplicate same from/to without contract differentiation
47
+ const existingSamePair = doc.items.filter((l) => l.fromPipelineId === link.fromPipelineId && l.toPipelineId === link.toPipelineId);
48
+ if (existingSamePair.length > 0) {
49
+ const contractMatch = existingSamePair.every((l) => {
50
+ const a = l.inputContract;
51
+ const b = link.inputContract;
52
+ if (!a && !b)
53
+ return true;
54
+ if (!a || !b)
55
+ return false;
56
+ return a.requireType === b.requireType && a.requireSchemaVersion === b.requireSchemaVersion;
57
+ });
58
+ if (contractMatch) {
59
+ return { ok: false, error: "pipeline_link_duplicate" };
60
+ }
61
+ }
62
+ doc.items.push(link);
63
+ await saveLinksDocument(doc);
64
+ return { ok: true, link };
65
+ },
66
+ update: async (id, patch) => {
67
+ const doc = await loadLinksDocument();
68
+ const index = doc.items.findIndex((l) => l.id === id);
69
+ if (index < 0) {
70
+ return { ok: false, error: "pipeline_link_not_found" };
71
+ }
72
+ const now = new Date().toISOString();
73
+ doc.items[index] = {
74
+ ...doc.items[index],
75
+ ...patch,
76
+ updatedAt: now,
77
+ };
78
+ await saveLinksDocument(doc);
79
+ return { ok: true, link: doc.items[index] };
80
+ },
81
+ remove: async (id) => {
82
+ const doc = await loadLinksDocument();
83
+ const index = doc.items.findIndex((l) => l.id === id);
84
+ if (index < 0) {
85
+ return { ok: false, error: "pipeline_link_not_found" };
86
+ }
87
+ doc.items.splice(index, 1);
88
+ await saveLinksDocument(doc);
89
+ return { ok: true };
90
+ },
91
+ };
92
+ return store;
93
+ };
94
+ exports.createPipelineLinkStore = createPipelineLinkStore;
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createPipelineQueueDrainer = void 0;
4
+ const createPipelineQueueDrainer = (deps) => {
5
+ const drainInFlightByPipelineId = new Map();
6
+ const { inboundQueue, linkStore, isPipelineBusy, executeInboundJob } = deps;
7
+ const drainOne = async (toPipelineId) => {
8
+ if (isPipelineBusy(toPipelineId))
9
+ return;
10
+ const pendingJobs = inboundQueue.getPendingJobs(toPipelineId);
11
+ if (pendingJobs.length === 0)
12
+ return;
13
+ const job = pendingJobs[0];
14
+ const now = new Date().toISOString();
15
+ // Check if there's already a running job for this pipeline
16
+ const runningJob = inboundQueue.getRunningJob(toPipelineId);
17
+ if (runningJob)
18
+ return;
19
+ const result = await executeInboundJob({
20
+ jobId: job.jobId,
21
+ linkId: job.linkId,
22
+ upstreamOutput: job.upstreamOutput,
23
+ });
24
+ if (result.ok && result.runId) {
25
+ await inboundQueue.appendEvent({
26
+ type: "job.success",
27
+ at: new Date().toISOString(),
28
+ jobId: job.jobId,
29
+ targetRunId: result.runId,
30
+ });
31
+ }
32
+ else {
33
+ const link = await linkStore.getById(job.linkId);
34
+ const onJobFailed = link?.onJobFailed ?? "continue";
35
+ await inboundQueue.appendEvent({
36
+ type: "job.failed",
37
+ at: new Date().toISOString(),
38
+ jobId: job.jobId,
39
+ targetRunId: result.runId,
40
+ error: result.error ?? "unknown_error",
41
+ });
42
+ if (onJobFailed === "pause") {
43
+ return; // Stop draining this queue
44
+ }
45
+ }
46
+ // Continue draining if more pending jobs
47
+ if (inboundQueue.getPendingJobs(toPipelineId).length > 0) {
48
+ setImmediate(() => drainer.requestDrainInboundQueue(toPipelineId));
49
+ }
50
+ };
51
+ const drainer = {
52
+ requestDrainInboundQueue: (toPipelineId) => {
53
+ // If already draining, reuse existing promise
54
+ if (drainInFlightByPipelineId.has(toPipelineId))
55
+ return;
56
+ const drainPromise = drainOne(toPipelineId).finally(() => {
57
+ drainInFlightByPipelineId.delete(toPipelineId);
58
+ });
59
+ drainInFlightByPipelineId.set(toPipelineId, drainPromise);
60
+ },
61
+ onPipelineRunCompleted: (pipelineId) => {
62
+ // When a pipeline run completes, check if there are pending jobs to drain
63
+ const pendingJobs = inboundQueue.getPendingJobs(pipelineId);
64
+ if (pendingJobs.length > 0) {
65
+ setImmediate(() => drainer.requestDrainInboundQueue(pipelineId));
66
+ }
67
+ },
68
+ };
69
+ return drainer;
70
+ };
71
+ exports.createPipelineQueueDrainer = createPipelineQueueDrainer;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canNeverSatisfy = exports.isDependencySatisfied = void 0;
4
+ const routes_1 = require("../workflow/routes");
5
+ const isDependencySatisfied = (itemKey, edge, ctx) => {
6
+ if (ctx.isCrossBranchEdge(edge))
7
+ return false;
8
+ if (ctx.isGroupId(edge.from)) {
9
+ const sourceGroup = ctx.getGroupItemRun(edge.from, itemKey);
10
+ if (!sourceGroup)
11
+ return false;
12
+ return sourceGroup.status === "success";
13
+ }
14
+ if (!ctx.isWorkflowNodeEnabled(edge.from)) {
15
+ return edge.when === null;
16
+ }
17
+ const source = ctx.getItemRun(edge.from, itemKey);
18
+ if (!source)
19
+ return false;
20
+ if (source.status !== "success")
21
+ return false;
22
+ if (!edge.when) {
23
+ return ctx.isRoutePolicyNode(edge.from) ? source.route === routes_1.MAINLINE_ROUTE_VALUE : true;
24
+ }
25
+ return source.route === edge.when;
26
+ };
27
+ exports.isDependencySatisfied = isDependencySatisfied;
28
+ const canNeverSatisfy = (itemKey, edge, ctx) => {
29
+ if (ctx.isCrossBranchEdge(edge))
30
+ return true;
31
+ if (ctx.isGroupId(edge.from)) {
32
+ const sourceGroup = ctx.getGroupItemRun(edge.from, itemKey);
33
+ if (!sourceGroup)
34
+ return false;
35
+ return sourceGroup.status === "failed" || sourceGroup.status === "skipped";
36
+ }
37
+ if (!ctx.isWorkflowNodeEnabled(edge.from)) {
38
+ return edge.when !== null;
39
+ }
40
+ const source = ctx.getItemRun(edge.from, itemKey);
41
+ if (!source)
42
+ return false;
43
+ if (source.status === "failed" || source.status === "skipped" || source.status === "stopped")
44
+ return true;
45
+ if (source.status === "success" && edge.when === null && ctx.isRoutePolicyNode(edge.from)) {
46
+ return source.route !== routes_1.MAINLINE_ROUTE_VALUE;
47
+ }
48
+ if (source.status === "success" && edge.when && source.route !== edge.when)
49
+ return true;
50
+ return false;
51
+ };
52
+ exports.canNeverSatisfy = canNeverSatisfy;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });