taskmeld 0.1.2 → 0.1.41

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 (154) hide show
  1. package/README.md +176 -176
  2. package/README.zh-CN.md +176 -176
  3. package/dist/src/app/app-context-env.js +1 -1
  4. package/dist/src/app/create-app-context.js +3 -3
  5. package/dist/src/app/data-dir.js +13 -3
  6. package/dist/src/app/pipeline-config.js +4 -4
  7. package/dist/src/app/pipeline-registry.js +11 -11
  8. package/dist/src/app/pipeline-runtime.js +6 -9
  9. package/dist/src/app/runtime-store.js +3 -3
  10. package/dist/src/artifacts/artifact-cleanup.js +17 -17
  11. package/dist/src/artifacts/artifact-index.js +14 -14
  12. package/dist/src/artifacts/artifact-rebuilder.js +3 -3
  13. package/dist/src/artifacts/storage-service.js +18 -18
  14. package/dist/src/cli/bootstrap.js +7 -7
  15. package/dist/src/cli/commands/agent.js +12 -11
  16. package/dist/src/cli/commands/artifact.js +31 -30
  17. package/dist/src/cli/commands/init.js +49 -47
  18. package/dist/src/cli/commands/pipeline/result.js +9 -8
  19. package/dist/src/cli/commands/pipeline/selector.js +1 -1
  20. package/dist/src/cli/commands/pipeline/watch.js +2 -2
  21. package/dist/src/cli/commands/pipeline.js +54 -53
  22. package/dist/src/cli/commands/scheduler.js +9 -8
  23. package/dist/src/cli/commands/server.js +12 -11
  24. package/dist/src/cli/commands/system.js +4 -3
  25. package/dist/src/cli/errors.js +2 -2
  26. package/dist/src/cli/help.js +18 -17
  27. package/dist/src/cli/i18n.js +46 -0
  28. package/dist/src/cli/locales/en.json +244 -0
  29. package/dist/src/cli/locales/zh.json +244 -0
  30. package/dist/src/cli/output.js +3 -3
  31. package/dist/src/cli/renderers/engine/markdown.js +1 -1
  32. package/dist/src/cli/renderers/specs/index.js +1 -1
  33. package/dist/src/cli/router.js +1 -1
  34. package/dist/src/cli/server-runtime-client.js +54 -95
  35. package/dist/src/cli/ui-prompts.js +96 -0
  36. package/dist/src/cli/ws-runtime-client.js +51 -0
  37. package/dist/src/gateway/gateway-client.js +4 -4
  38. package/dist/src/index.js +28 -2
  39. package/dist/src/logs/run-log-reader.js +1 -1
  40. package/dist/src/pipeline/agent-activity.js +2 -2
  41. package/dist/src/pipeline/artifact-storage.js +11 -11
  42. package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +11 -11
  43. package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +2 -2
  44. package/dist/src/pipeline/execution/group-item-executor.js +1 -1
  45. package/dist/src/pipeline/execution/node-item-executor.js +3 -3
  46. package/dist/src/pipeline/execution/node-runner.js +7 -7
  47. package/dist/src/pipeline/execution/readiness-state.js +1 -1
  48. package/dist/src/pipeline/execution/reject-handler.js +5 -5
  49. package/dist/src/pipeline/execution/rejected-artifact-archiver.js +1 -1
  50. package/dist/src/pipeline/execution/route-item-manager.js +4 -4
  51. package/dist/src/pipeline/execution/run-abort-controller.js +5 -5
  52. package/dist/src/pipeline/execution/run-state-helpers.js +2 -2
  53. package/dist/src/pipeline/execution/service.js +4 -4
  54. package/dist/src/pipeline/execution/structured-node-runner.js +24 -24
  55. package/dist/src/pipeline/execution-timeout.js +3 -3
  56. package/dist/src/pipeline/identity/index.js +3 -3
  57. package/dist/src/pipeline/item-batch-controller.js +6 -6
  58. package/dist/src/pipeline/scheduler/dependency-state.js +5 -5
  59. package/dist/src/pipeline/scheduler-service.js +24 -24
  60. package/dist/src/pipeline/state-machine.js +2 -2
  61. package/dist/src/pipeline/structured-output/contract.js +4 -4
  62. package/dist/src/pipeline/structured-output/index.js +2 -2
  63. package/dist/src/pipeline/structured-output/parser.js +5 -5
  64. package/dist/src/pipeline/structured-output/prompt.js +38 -38
  65. package/dist/src/pipeline/structured-output/waiter.js +6 -6
  66. package/dist/src/pipeline/template.js +5 -5
  67. package/dist/src/pipeline/timeline-log-store.js +5 -5
  68. package/dist/src/pipeline/tool-activity.js +3 -3
  69. package/dist/src/pipeline/types/pipeline-output.js +1 -1
  70. package/dist/src/pipeline/workflow/branch-rules.js +19 -19
  71. package/dist/src/pipeline/workflow/io.js +1 -1
  72. package/dist/src/pipeline/workflow/normalize.js +18 -18
  73. package/dist/src/pipeline/workflow/template-mapper.js +3 -3
  74. package/dist/src/pipeline/workflow/validate.js +39 -39
  75. package/dist/src/pipeline/workflow-graph.js +10 -10
  76. package/dist/src/server/http-handler.js +74 -0
  77. package/dist/src/services/agent-service.js +2 -2
  78. package/dist/src/services/gateway-read-helpers.js +1 -1
  79. package/dist/src/services/pipeline-service.js +19 -19
  80. package/dist/src/services/pipeline-status.js +4 -4
  81. package/dist/src/services/read-services.js +1 -1
  82. package/dist/src/services/session-service.js +6 -6
  83. package/dist/src/services/system-service.js +1 -1
  84. package/dist/src/transport/ws-broker.js +12 -1
  85. package/dist/src/transport/ws-handler.js +60 -0
  86. package/dist/src/transport/ws-methods/agents.js +144 -0
  87. package/dist/src/transport/ws-methods/artifacts.js +171 -0
  88. package/dist/src/transport/ws-methods/gateway.js +16 -0
  89. package/dist/src/transport/ws-methods/logs.js +43 -0
  90. package/dist/src/transport/ws-methods/pipeline-batch.js +68 -0
  91. package/dist/src/transport/ws-methods/pipeline-links.js +100 -0
  92. package/dist/src/transport/ws-methods/pipeline-queue.js +51 -0
  93. package/dist/src/transport/ws-methods/pipeline-runtime.js +151 -0
  94. package/dist/src/transport/ws-methods/pipeline-scheduler.js +48 -0
  95. package/dist/src/transport/ws-methods/pipeline-workflow.js +127 -0
  96. package/dist/src/transport/ws-methods/pipelines.js +56 -0
  97. package/dist/src/transport/ws-methods/register-all.js +32 -0
  98. package/dist/src/transport/ws-methods/sessions.js +154 -0
  99. package/dist/src/transport/ws-methods/timeline.js +10 -0
  100. package/dist/src/{server/routes/pipeline-identity.js → transport/ws-methods/utils.js} +14 -9
  101. package/dist/src/version.js +1 -1
  102. package/package.json +15 -7
  103. package/web/dist/assets/agent-DP6TMcLj.js +1 -0
  104. package/web/dist/assets/agent-DmJHzLyj.js +1 -0
  105. package/web/dist/assets/artifact-BqnoZy2M.js +1 -0
  106. package/web/dist/assets/artifact-DfDkgkno.js +1 -0
  107. package/web/dist/assets/common-DRMTVwE9.js +1 -0
  108. package/web/dist/assets/common-DeXccbr2.js +1 -0
  109. package/web/dist/assets/dispatch-CBskGCQI.js +1 -0
  110. package/web/dist/assets/dispatch-sk4Wp30e.js +1 -0
  111. package/web/dist/assets/index-C8wTjZvH.css +1 -0
  112. package/web/dist/assets/index-DYDQZRLk.js +58 -0
  113. package/web/dist/assets/log-DN8cjb0w.js +1 -0
  114. package/web/dist/assets/log-HSeA_dYy.js +1 -0
  115. package/web/dist/assets/modal-BdNai9jf.js +1 -0
  116. package/web/dist/assets/modal-D9_KDpFD.js +1 -0
  117. package/web/dist/assets/nav-BmF7oAKg.js +1 -0
  118. package/web/dist/assets/nav-IjC2xqXQ.js +1 -0
  119. package/web/dist/assets/node-detail-CENRXcrh.js +1 -0
  120. package/web/dist/assets/node-detail-bndPr0IM.js +1 -0
  121. package/web/dist/assets/overview-B87zWAxq.js +1 -0
  122. package/web/dist/assets/overview-gQvk-NOK.js +1 -0
  123. package/web/dist/assets/pipeline-D4dSJRDz.js +1 -0
  124. package/web/dist/assets/pipeline-DZzyOqQa.js +1 -0
  125. package/web/dist/assets/session-CUWvU14v.js +5 -0
  126. package/web/dist/assets/session-DQ6UuCaJ.js +5 -0
  127. package/web/dist/assets/timeline-8y_2_0Em.js +1 -0
  128. package/web/dist/assets/timeline-CAPsXUTC.js +1 -0
  129. package/web/dist/index.html +3 -3
  130. package/dist/src/app/pipeline-plugin-config.js +0 -2
  131. package/dist/src/server/api-handler.js +0 -163
  132. package/dist/src/server/http-utils.js +0 -34
  133. package/dist/src/server/middleware.js +0 -61
  134. package/dist/src/server/router.js +0 -105
  135. package/dist/src/server/routes/agents.js +0 -189
  136. package/dist/src/server/routes/artifacts.js +0 -163
  137. package/dist/src/server/routes/gateway.js +0 -18
  138. package/dist/src/server/routes/health.js +0 -16
  139. package/dist/src/server/routes/logs.js +0 -73
  140. package/dist/src/server/routes/pipeline-batch.js +0 -163
  141. package/dist/src/server/routes/pipeline-diagnostics.js +0 -33
  142. package/dist/src/server/routes/pipeline-links.js +0 -117
  143. package/dist/src/server/routes/pipeline-outputs.js +0 -27
  144. package/dist/src/server/routes/pipeline-queue.js +0 -62
  145. package/dist/src/server/routes/pipeline-runtime.js +0 -162
  146. package/dist/src/server/routes/pipeline-scheduler.js +0 -69
  147. package/dist/src/server/routes/pipeline-workflow.js +0 -180
  148. package/dist/src/server/routes/pipelines.js +0 -96
  149. package/dist/src/server/routes/sessions.js +0 -244
  150. package/dist/src/server/routes/timeline.js +0 -14
  151. package/dist/src/server/serve-static.js +0 -42
  152. package/web/dist/assets/index-CWnfhkn-.js +0 -65
  153. package/web/dist/assets/index-gZ0xOfSO.css +0 -1
  154. /package/dist/src/{server → transport/ws-methods}/types.js +0 -0
@@ -41,7 +41,7 @@ const createNodeRunner = (deps) => {
41
41
  node.rejectCount = 0;
42
42
  node.rejectFeedbacks = [];
43
43
  succeeded = true;
44
- deps.runtimeStore.pushTimeline(`节点执行完成(结构化): ${node.id} <- ${usedAgentId}`);
44
+ deps.runtimeStore.pushTimeline(`Node executed (structured): ${node.id} <- ${usedAgentId}`);
45
45
  }
46
46
  else if (envelopeErrorCode === "upstream_reject" && node.allowReject) {
47
47
  node.artifacts = [];
@@ -72,7 +72,7 @@ const createNodeRunner = (deps) => {
72
72
  finalStatus = "failed";
73
73
  node.artifacts = result.artifacts;
74
74
  execError = "upstream_reject_not_allowed: node_allowReject_false";
75
- deps.runtimeStore.pushTimeline(`节点执行失败(结构化): ${node.id} <- ${usedAgentId} upstream_reject_not_allowed: node_allowReject_false`, "error");
75
+ deps.runtimeStore.pushTimeline(`Node execution failed (structured): ${node.id} <- ${usedAgentId} upstream_reject_not_allowed: node_allowReject_false`, "error");
76
76
  }
77
77
  else {
78
78
  const structuredFailError = JSON.stringify(result.envelope.error ?? "node_failed");
@@ -84,14 +84,14 @@ const createNodeRunner = (deps) => {
84
84
  const detailText = envelopeError.message
85
85
  ? `${envelopeError.code ? `${envelopeError.code}: ` : ""}${envelopeError.message}`
86
86
  : envelopeError.code || "node_failed";
87
- deps.runtimeStore.pushTimeline(`节点执行失败(结构化): ${node.id} <- ${usedAgentId} ${detailText}`, "error");
87
+ deps.runtimeStore.pushTimeline(`Node execution failed (structured): ${node.id} <- ${usedAgentId} ${detailText}`, "error");
88
88
  }
89
89
  return { succeeded, finalStatus, execError, haltPipeline };
90
90
  };
91
91
  const executeNode = async (node, opts) => {
92
92
  const resolved = await deps.sessionRegistry.resolveExecutorSession(node);
93
93
  if (!resolved) {
94
- deps.runtimeStore.pushTimeline(`节点 ${node.id} 执行失败: 找不到执行者会话(${node.executor.agentId})`, "error");
94
+ deps.runtimeStore.pushTimeline(`Node ${node.id} execution failed: executor session not found (${node.executor.agentId})`, "error");
95
95
  (0, state_1.markNodeFailed)(node, ctx("executor_session_not_found", { error: "executor_session_not_found" }));
96
96
  deps.runtimeStore.emitPipeline();
97
97
  return {
@@ -105,7 +105,7 @@ const createNodeRunner = (deps) => {
105
105
  const sessionId = resolved.sessionId;
106
106
  const usedAgentId = resolved.agentId;
107
107
  (0, state_1.markNodeRunning)(node, ctx("exec_start"));
108
- deps.runtimeStore.pushTimeline(`节点执行已触发: ${node.id} -> ${usedAgentId}`);
108
+ deps.runtimeStore.pushTimeline(`Node execution triggered: ${node.id} -> ${usedAgentId}`);
109
109
  deps.runtimeStore.emitPipeline();
110
110
  const effectiveDependencyIds = opts?.dependencyIds?.length
111
111
  ? Array.from(new Set(opts.dependencyIds.map((id) => id.trim()).filter(Boolean)))
@@ -134,11 +134,11 @@ const createNodeRunner = (deps) => {
134
134
  const classification = isAborted
135
135
  ? { reason: "pipeline_aborted", haltPipeline: true }
136
136
  : classifyNodeFailure(error);
137
- // 用户中止时节点已被标记为 stopped,不应再覆盖为 failed
137
+ // When the user aborts, the node is already marked as stopped — should not be overwritten to failed
138
138
  if (!isAborted) {
139
139
  (0, state_1.markNodeFailed)(node, ctx(classification.reason, { error: String(error) }));
140
140
  }
141
- deps.runtimeStore.pushTimeline(`节点执行中断(结构化): ${node.id} <- ${usedAgentId} ${String(error)}`, "warn");
141
+ deps.runtimeStore.pushTimeline(`Node execution interrupted (structured): ${node.id} <- ${usedAgentId} ${String(error)}`, "warn");
142
142
  deps.runtimeStore.emitPipeline();
143
143
  return {
144
144
  ok: false,
@@ -5,6 +5,6 @@ const isSleepWaitingState = (candidate) => candidate.status === "waiting" && typ
5
5
  exports.isSleepWaitingState = isSleepWaitingState;
6
6
  const canPromoteToQueuedByDependency = (candidate) => candidate.status === "blocked" ||
7
7
  candidate.status === "skipped" ||
8
- // 仅允许“依赖未满足”造成的 waiting 被重新入队;sleep waiting 仍需等到 wakeAt 到期。
8
+ // Only "waiting" caused by unmet dependencies is allowed to be re-enqueued; sleep waiting must still wait until wakeAt expires.
9
9
  (candidate.status === "waiting" && !(0, exports.isSleepWaitingState)(candidate));
10
10
  exports.canPromoteToQueuedByDependency = canPromoteToQueuedByDependency;
@@ -50,7 +50,7 @@ const handleNodeReject = async (params) => {
50
50
  node.rejectCount = (node.rejectCount ?? 0) + 1;
51
51
  if (node.rejectCount > limit) {
52
52
  (0, state_1.markNodeFailed)(node, ctx("reject_limit_exceeded", { error: JSON.stringify(envelope.error ?? "reject_limit_exceeded") }));
53
- pushTimeline(`节点 ${node.id} 打回超过 ${limit} 次,标记失败`, "error");
53
+ pushTimeline(`Node ${node.id} rejected ${limit} times, marked as failed`, "error");
54
54
  return;
55
55
  }
56
56
  const effectiveDependencyIds = dependencyIds?.length ? dependencyIds : node.dependsOn;
@@ -61,10 +61,10 @@ const handleNodeReject = async (params) => {
61
61
  : defaultTarget
62
62
  ? [defaultTarget]
63
63
  : [];
64
- const rejectMessage = (0, exports.extractEnvelopeErrorMessage)(envelope.error) || "下游校验不通过,请修正后重新提交。";
64
+ const rejectMessage = (0, exports.extractEnvelopeErrorMessage)(envelope.error) || "Downstream validation failed, please correct and resubmit.";
65
65
  if (rejectTargetIds.length === 0) {
66
66
  (0, state_1.markNodeFailed)(node, ctx("reject_target_missing", { error: JSON.stringify(envelope.error ?? "reject_target_missing") }));
67
- pushTimeline(`节点 ${node.id} 请求打回但未找到可用上游节点,标记失败`, "error");
67
+ pushTimeline(`Node ${node.id} requested reject but no available upstream node found, marked as failed`, "error");
68
68
  return;
69
69
  }
70
70
  (0, state_1.markNodeRejected)(node, ctx("upstream_reject", { error: JSON.stringify(envelope.error ?? "upstream_reject") }));
@@ -72,7 +72,7 @@ const handleNodeReject = async (params) => {
72
72
  const targetNode = nodes.find((current) => current.id === targetId);
73
73
  if (!targetNode)
74
74
  continue;
75
- const feedback = `${node.id}(${node.title})打回原因: ${rejectMessage}`;
75
+ const feedback = `${node.id}(${node.title}) reject reason: ${rejectMessage}`;
76
76
  targetNode.rejectFeedbacks = [...(targetNode.rejectFeedbacks ?? []), feedback].slice(-5);
77
77
  const movedCount = await (0, rejected_artifact_archiver_1.archiveRejectedArtifacts)({
78
78
  node: targetNode,
@@ -88,7 +88,7 @@ const handleNodeReject = async (params) => {
88
88
  itemKey,
89
89
  skipNodeIds: [node.id],
90
90
  });
91
- pushTimeline(`节点 ${node.id} 打回 ${targetNode.id},原因: ${rejectMessage};重置 ${affectedNodeCount} 个节点/${affectedGroupCount} 个并行组,归档产物 ${movedCount} 条`, "warn");
91
+ pushTimeline(`Node ${node.id} rejected ${targetNode.id}, reason: ${rejectMessage}; reset ${affectedNodeCount} nodes/${affectedGroupCount} parallel groups, archived ${movedCount} artifacts`, "warn");
92
92
  }
93
93
  };
94
94
  exports.handleNodeReject = handleNodeReject;
@@ -37,7 +37,7 @@ const archiveRejectedArtifacts = async (params) => {
37
37
  moved += 1;
38
38
  }
39
39
  catch (error) {
40
- pushTimeline(`归档被驳回产物失败 (节点 ${node.id}): ${error instanceof Error ? error.message : String(error)}`, "warn");
40
+ pushTimeline(`Failed to archive rejected artifact (node ${node.id}): ${error instanceof Error ? error.message : String(error)}`, "warn");
41
41
  }
42
42
  }
43
43
  return moved;
@@ -20,7 +20,7 @@ const normalizeAllowedRoute = (rawRoute, allowedRoutes) => {
20
20
  if (direct)
21
21
  return direct;
22
22
  const lower = trimmed.toLowerCase();
23
- // 与结构化校验保持一致:大小写不一致时使用工作流声明值,避免命中丢失。
23
+ // Consistent with structured validation: when case differs, use the workflow-declared value to avoid missed matches.
24
24
  return allowedRoutes.find((route) => route.toLowerCase() === lower) ?? null;
25
25
  };
26
26
  const collectRouteBuckets = (content, allowedRoutes) => {
@@ -86,7 +86,7 @@ const createRouteItemManager = (options) => {
86
86
  const outgoingEdges = options.graph.getOutgoingEdges(sourceItem.nodeId);
87
87
  const isRouteNode = (options.graph.getWorkflowNodeById(sourceItem.nodeId)?.routePolicy?.allowed.length ?? 0) > 0;
88
88
  const startTargets = outgoingEdges
89
- // 分流节点中 yes 是主线语义:只沿普通依赖边初始化;no/自定义值只沿路由边初始化。
89
+ // In routing nodes, yes is mainline semantics: only follow unconditional dependency edges; no/custom values only follow route edges.
90
90
  .filter((edge) => (isRouteNode ? (route === routes_1.MAINLINE_ROUTE_VALUE ? edge.when === null : edge.when === route) : !edge.when))
91
91
  .map((edge) => edge.to);
92
92
  const reachable = options.state.collectReachableEntities(startTargets);
@@ -192,7 +192,7 @@ const createRouteItemManager = (options) => {
192
192
  for (const edge of options.graph.getOutgoingEdges(item.nodeId)) {
193
193
  if (bucket.route === routes_1.MAINLINE_ROUTE_VALUE)
194
194
  continue;
195
- // 路由节点只按命中的 route 边推进,普通边在此一律忽略,避免主线被隐式直通。
195
+ // Routing nodes only advance along the matched route edge; unconditional edges are unconditionally ignored here to prevent implicit mainline passthrough.
196
196
  if (edge.when !== bucket.route)
197
197
  continue;
198
198
  if (options.graph.isGroupId(edge.to)) {
@@ -211,7 +211,7 @@ const createRouteItemManager = (options) => {
211
211
  }
212
212
  }
213
213
  }
214
- options.runtimeStore.pushTimeline(`分流命中: ${item.nodeId}#${item.itemKey} -> ${bucket.route} (${bucket.count} )`);
214
+ options.runtimeStore.pushTimeline(`Route hit: ${item.nodeId}#${item.itemKey} -> ${bucket.route} (${bucket.count} items)`);
215
215
  }
216
216
  }
217
217
  const sleepUntil = envelope.control?.sleepUntil;
@@ -25,9 +25,9 @@ const createRunAbortController = () => {
25
25
  entry.ac.abort();
26
26
  };
27
27
  /**
28
- * 中止指定流水线运行的所有节点执行。
29
- * 1. 向每个活跃节点的远端 agent 会话发送 "/stop" 命令(fire-and-forget
30
- * 2. 触发本地 AbortController 中断轮询/排水循环
28
+ * Abort all node executions for a given pipeline run.
29
+ * 1. Send "/stop" to each active node's remote agent session (fire-and-forget)
30
+ * 2. Trigger local AbortController to interrupt polling/drain loop
31
31
  */
32
32
  const abortRunControllers = (runId, client) => {
33
33
  const controllers = nodeExecutionControllers.get(runId);
@@ -50,8 +50,8 @@ const createRunAbortController = () => {
50
50
  }
51
51
  };
52
52
  /**
53
- * 获取或创建用于 drainPipeline 的中止信号。
54
- * 每次新 run 会创建新的 AbortController,确保 stop/retry 只中断当前运行。
53
+ * Get or create the abort signal for drainPipeline.
54
+ * Each new run creates a new AbortController so stop/retry only interrupt the current run.
55
55
  */
56
56
  const getOrCreateDrainSignal = (runId) => {
57
57
  let dc = drainControllers.get(runId);
@@ -134,8 +134,8 @@ const createRunStateHelpers = (options) => {
134
134
  if (options.graph.isGroupId(current)) {
135
135
  groupIds.add(current);
136
136
  const group = options.graph.getWorkflowGroupById(current);
137
- // 并行组本身不是执行节点,但它控制成员节点与后续 join 分支的状态。
138
- // 回放/打回时必须穿透 group,把成员和 group 后面的节点一起纳入重置范围。
137
+ // A parallel group is not an execution node itself, but it controls the state of its member nodes and subsequent join branches.
138
+ // Replay/reject must penetrate the group to include members and nodes downstream of the group in the reset scope.
139
139
  for (const memberId of group?.members ?? []) {
140
140
  queue.push(memberId);
141
141
  }
@@ -13,9 +13,9 @@ const group_item_executor_1 = require("./group-item-executor");
13
13
  const node_item_executor_1 = require("./node-item-executor");
14
14
  const ctx = (reason, extra) => ({ reason, ...extra });
15
15
  /**
16
- * 流水线执行器。
17
- * 负责:节点的具体执行逻辑、状态转换、产物管理。
18
- * 不负责:调度决策、并发控制。
16
+ * Pipeline executor.
17
+ * Responsible for: specific execution logic of nodes, state transitions, artifact management.
18
+ * Not responsible for: scheduling decisions, concurrency control.
19
19
  */
20
20
  const createExecutionService = (deps) => {
21
21
  let activeBatchKeywordItems = null;
@@ -56,7 +56,7 @@ const createExecutionService = (deps) => {
56
56
  return ids;
57
57
  },
58
58
  });
59
- // ====== Helper: 重置受影响的 downstream ======
59
+ // ====== Helper: Reset affected downstream ======
60
60
  const resetAffectedDownstreamNodes = (params) => {
61
61
  const run = getRun();
62
62
  const affected = collectDownstreamSubgraph(params.targetNodeId);
@@ -20,11 +20,11 @@ const injectRuntimeKeywordsToInstruction = (instruction, keywords) => {
20
20
  if (matchedArray && matchedArray.length > 0) {
21
21
  return instruction.replace(arrayPattern, keywordJson);
22
22
  }
23
- return `${instruction}\n\n本批次关键词(JSON数组):\n${keywordJson}`;
23
+ return `${instruction}\n\nKeywords for this batch (JSON array):\n${keywordJson}`;
24
24
  };
25
25
  const resolveActiveBatchKeywordsForInstruction = (activeBatchKeywordItems, isSourceEntryNode) => {
26
- // 只有真正处于批跑中的入口节点,才允许把关键词池注入到提示词。
27
- // 普通单运行虽然默认 itemKey 可能是 global,但它只是运行项标识,不应被当成批次关键词展示。
26
+ // Only the entry node that is actually in a batch run may inject the keyword pool into the prompt.
27
+ // In a regular single run the default itemKey may be "global", but that's just a run-item identifier — it should not be shown as a batch keyword.
28
28
  if (!isSourceEntryNode || !activeBatchKeywordItems || activeBatchKeywordItems.length === 0) {
29
29
  return [];
30
30
  }
@@ -72,8 +72,8 @@ const shouldParseFrameForEnvelope = (frame) => {
72
72
  const createStructuredNodeRunner = (options) => {
73
73
  const emitter = new node_events_1.EventEmitter();
74
74
  emitter.setMaxListeners(32);
75
- // 同一个 agent session 会复用多轮对话。
76
- // 这里按 sessionId + agent runId 记录生命周期,避免上一轮尾部的 end/error 事件误伤下一轮新请求。
75
+ // The same agent session reuses multiple rounds of conversation.
76
+ // Track lifecycle by sessionId + agent runId to prevent stray end/error events from the previous round's tail from hitting the next round's new request.
77
77
  const sessionRuns = new Map();
78
78
  const findStringByKeys = (value, keys, depth = 0) => {
79
79
  if (depth > 5 || value === null || value === undefined)
@@ -145,9 +145,9 @@ const createStructuredNodeRunner = (options) => {
145
145
  current.completedAt = null;
146
146
  }
147
147
  runMap.set(runId, current);
148
- // 每个 session 只保留最近少量 run 记录,避免长跑时无界增长。
149
- // 驱逐前跳过仍在等待确认的活跃 run (completedAt null)
150
- // 防止 resolveCompletedAtForRequest 锁定的 runId 被误删导致超时。
148
+ // Keep only the most recent few run records per session to avoid unbounded growth during long runs.
149
+ // Skip active runs still awaiting confirmation (completedAt is null) before eviction,
150
+ // to prevent runId locked by resolveCompletedAtForRequest from being mistakenly deleted and causing a timeout.
151
151
  const MAX_RUNS_PER_SESSION = 48;
152
152
  if (runMap.size > MAX_RUNS_PER_SESSION) {
153
153
  const now = Date.now();
@@ -171,8 +171,8 @@ const createStructuredNodeRunner = (options) => {
171
171
  if (!runMap || runMap.size === 0)
172
172
  return null;
173
173
  if (!watch.lockedRunId) {
174
- // 优先锁定"首次出现时间晚于本次请求发出时间" runId。
175
- // 这样可以避免上一轮请求的尾部 end/error 在新请求刚开始时被错认成当前轮次结束。
174
+ // Prefer locking the runId whose "first seen time" is later than this request's start time.
175
+ // This avoids the previous request's tail end/error being misidentified as the current round's completion when a new request just starts.
176
176
  const currentRun = [...runMap.entries()]
177
177
  .filter(([, snapshot]) => snapshot.firstSeenAt >= requestStartedAt)
178
178
  .sort((a, b) => a[1].firstSeenAt - b[1].firstSeenAt)[0];
@@ -210,8 +210,8 @@ const createStructuredNodeRunner = (options) => {
210
210
  rememberSessionRunEvent(sessionId, runId, inferLifecycle(payload.data));
211
211
  return;
212
212
  }
213
- // assistant/tool/item 等流虽然不代表完成,但能提供当前轮次的 runId
214
- // 这里只做"看见过该 run"的记录,完成仍以 lifecycle/chat.final 为准。
213
+ // assistant/tool/item streams don't represent completion, but they provide the current round's runId.
214
+ // This only records "this run was seen"; completion is still determined by lifecycle/chat.final.
215
215
  rememberSessionRunEvent(sessionId, runId, "unknown");
216
216
  return;
217
217
  }
@@ -232,8 +232,8 @@ const createStructuredNodeRunner = (options) => {
232
232
  const workflowNode = options.graph.getWorkflowNodeById(node.id);
233
233
  const retryBackoffMs = workflowNode?.retryPolicy.backoffMs ?? 0;
234
234
  const parallelGroup = options.graph.getParallelGroupByMemberNodeId(node.id);
235
- // 批处理关键词只应注入真正的入口节点。并行组成员虽然通常没有 direct incoming edge
236
- // 但它们的依赖挂在 group 上,只看 node 自身入边会把 group 成员误判为 source entry。
235
+ // Batch keywords should only be injected into the true entry node. Parallel-group members typically have no direct incoming edge,
236
+ // but their dependencies are attached to the group; looking only at the node's own incoming edges would misclassify group members as source entries.
237
237
  const isSourceEntryNode = options.graph.getIncomingEdges(node.id).length === 0 &&
238
238
  (!parallelGroup || options.graph.getIncomingEdges(parallelGroup.id).length === 0);
239
239
  const effectiveRuntimeKeywordItems = (0, exports.resolveActiveBatchKeywordsForInstruction)(options.getActiveBatchKeywordItems(), isSourceEntryNode);
@@ -250,7 +250,7 @@ const createStructuredNodeRunner = (options) => {
250
250
  }
251
251
  }
252
252
  if (effectiveDependencyIds.length > 0) {
253
- options.runtimeStore.pushTimeline(`节点 ${node.id} 已加载上游产物 ${dependencyArtifacts.length} 条(来自: ${effectiveDependencyIds.join(",")})`);
253
+ options.runtimeStore.pushTimeline(`Node ${node.id} loaded ${dependencyArtifacts.length} upstream artifacts (from: ${effectiveDependencyIds.join(",")})`);
254
254
  }
255
255
  const allowedRoutes = workflowNode?.routePolicy?.allowed ?? [];
256
256
  const routeTargets = options.graph.getOutgoingEdges(node.id)
@@ -263,7 +263,7 @@ const createStructuredNodeRunner = (options) => {
263
263
  route: edge.when ?? "",
264
264
  targetNodeId: edge.to,
265
265
  targetNodeTitle: targetWorkflowGroup
266
- ? `并行组 ${targetWorkflowGroup.id}`
266
+ ? `Parallel group ${targetWorkflowGroup.id}`
267
267
  : targetNode?.title ?? targetWorkflowNode?.name ?? edge.to,
268
268
  targetAgentId: targetWorkflowGroup
269
269
  ? targetWorkflowGroup.id
@@ -287,7 +287,7 @@ const createStructuredNodeRunner = (options) => {
287
287
  return { requestId, ctx, effectiveDependencyIds, dependencyArtifacts, allowedRoutes, routeTargets, retryBackoffMs };
288
288
  };
289
289
  const sendAndWaitForEnvelope = async (prompt, sessionId, nodeId, validationCtx, lastViolation, requestStartedAt, requestRunWatch, signal) => {
290
- // DAG 节点发送只保留 chat.send 单一路径,避免不同发送 API 的兼容补发把同一 requestId 投递两次。
290
+ // DAG node send only uses the chat.send single path to avoid compatibility fallback sends from different APIs delivering the same requestId twice.
291
291
  const idempotencyKey = (0, node_crypto_1.randomUUID)();
292
292
  let payload;
293
293
  try {
@@ -298,10 +298,10 @@ const createStructuredNodeRunner = (options) => {
298
298
  });
299
299
  }
300
300
  catch (error) {
301
- options.runtimeStore.pushTimeline(`节点 ${nodeId} 发送失败(chat.send): ${String(error)}`, "error");
301
+ options.runtimeStore.pushTimeline(`Node ${nodeId} send failed (chat.send): ${String(error)}`, "error");
302
302
  throw new Error(`openclaw_send_failed:chat.send:${String(error)}`);
303
303
  }
304
- options.runtimeStore.pushTimeline(`节点 ${nodeId} 已发送请求,等待结构化回执...`);
304
+ options.runtimeStore.pushTimeline(`Node ${nodeId} request sent, waiting for structured receipt...`);
305
305
  const envelope = await (0, structured_output_1.waitForStructuredEnvelope)(emitter, validationCtx, lastViolation, () => resolveCompletedAtForRequest(sessionId, requestStartedAt, requestRunWatch), signal);
306
306
  return envelope;
307
307
  };
@@ -316,8 +316,8 @@ const createStructuredNodeRunner = (options) => {
316
316
  requireRouteContent: allowedRoutes.length > 0,
317
317
  };
318
318
  let lastViolation = null;
319
- // 纠错重试属于同一次节点请求的补正,不应更换 requestId
320
- // 否则模型即使按上一轮上下文修正成功,也会因为顶层 requestId 变化被误判为 mismatch。
319
+ // Error-correction retries are corrections of the same node request; the requestId should not be changed.
320
+ // Otherwise the model would be wrongly flagged as a mismatch due to a changed top-level requestId, even after a successful context-based correction.
321
321
  for (let attemptIndex = 0; attemptIndex < 2; attemptIndex += 1) {
322
322
  if (attemptIndex > 0 && retryBackoffMs > 0) {
323
323
  await new Promise(resolve => setTimeout(resolve, retryBackoffMs));
@@ -336,10 +336,10 @@ const createStructuredNodeRunner = (options) => {
336
336
  if (violation) {
337
337
  lastViolation = violation;
338
338
  if (attemptIndex === 0) {
339
- options.runtimeStore.pushTimeline(`节点 ${node.id} 回执校验失败(${violation}),自动纠正重试中...`, "warn");
339
+ options.runtimeStore.pushTimeline(`Node ${node.id} receipt validation failed (${violation}), auto-correcting...`, "warn");
340
340
  continue;
341
341
  }
342
- options.runtimeStore.pushTimeline(`节点 ${node.id} 回执校验失败(${violation}),纠正后仍未通过,标记失败`, "error");
342
+ options.runtimeStore.pushTimeline(`Node ${node.id} receipt validation failed (${violation}), correction still failed, marked as failed`, "error");
343
343
  }
344
344
  throw error;
345
345
  }
@@ -350,7 +350,7 @@ const createStructuredNodeRunner = (options) => {
350
350
  const savedAt = new Date();
351
351
  const status = envelope.status === "failed" ? "failed" : "success";
352
352
  const batchRunId = options.getBatchRunId?.();
353
- // envelope 文件通过 persistEnvelopeFile 写入并追加索引
353
+ // Envelope files are written and indexed via persistEnvelopeFile
354
354
  await (0, artifact_storage_1.persistEnvelopeFile)(options.artifactDir, status, { runId, batchRunId, nodeId, requestId, pipelineId: options.pipelineId }, envelope, { savedAt });
355
355
  const normalizedArtifacts = [];
356
356
  for (let i = 0; i < envelope.artifacts.length; i += 1) {
@@ -10,12 +10,12 @@ const normalizeTimeout = (value, fallback, min) => {
10
10
  return fallback;
11
11
  return Math.max(min, Math.trunc(parsed));
12
12
  };
13
- // 统一维护 DAG 节点执行超时,避免发送请求与等待回执使用不同默认值。
13
+ // Centrally maintain DAG node execution timeout so send-request and wait-receipt don't use different defaults.
14
14
  const getPipelineNodeExecutionTimeoutMs = () => normalizeTimeout(process.env.PIPELINE_NODE_EXECUTION_TIMEOUT_MS, DEFAULT_PIPELINE_NODE_TIMEOUT_MS, MIN_TIMEOUT_MS);
15
15
  exports.getPipelineNodeExecutionTimeoutMs = getPipelineNodeExecutionTimeoutMs;
16
- // 兼容历史环境变量名,未设置新变量时继续读取旧变量。
16
+ // Compatible with legacy env var name; fall back to the old var when the new one is not set.
17
17
  const getStructuredResultTimeoutMs = () => normalizeTimeout(process.env.STRUCTURED_RESULT_TIMEOUT_MS ?? process.env.PIPELINE_NODE_EXECUTION_TIMEOUT_MS, DEFAULT_PIPELINE_NODE_TIMEOUT_MS, MIN_TIMEOUT_MS);
18
18
  exports.getStructuredResultTimeoutMs = getStructuredResultTimeoutMs;
19
- // 轮询间隔保持短周期,仅负责观察回执,不参与失败判定。
19
+ // Polling interval stays short; it only observes receipts and does not participate in failure judgment.
20
20
  const getStructuredResultPollMs = () => normalizeTimeout(process.env.STRUCTURED_RESULT_POLL_MS, DEFAULT_STRUCTURED_RESULT_POLL_MS, 100);
21
21
  exports.getStructuredResultPollMs = getStructuredResultPollMs;
@@ -15,17 +15,17 @@ const buildRequestId = (nodeId) => `node-${nodeId}-${(0, node_crypto_1.randomUUI
15
15
  exports.buildRequestId = buildRequestId;
16
16
  const buildItemKeyFromKeywords = (keywords) => [...new Set(keywords.map((item) => item.trim()).filter(Boolean))];
17
17
  exports.buildItemKeyFromKeywords = buildItemKeyFromKeywords;
18
- /** Run pipelineId 构造身份快照 */
18
+ /** Build an identity snapshot from a Run and pipelineId */
19
19
  const toRunIdentity = (pipelineId, run) => ({
20
20
  pipelineId,
21
21
  runId: run.id,
22
22
  batchRunId: null,
23
23
  });
24
24
  exports.toRunIdentity = toRunIdentity;
25
- /** 格式化身份为可读字符串 */
25
+ /** Format an identity into a human-readable string */
26
26
  const formatIdentity = (id) => `pipeline=${id.pipelineId} run=${id.runId}${id.batchRunId ? ` batch=${id.batchRunId}` : ""}`;
27
27
  exports.formatIdentity = formatIdentity;
28
- /** 校验身份匹配:pipelineId 必须相等,runId 必须相等,batchRunId 任一方为 null 时放行 */
28
+ /** Verify identity match: pipelineId must be equal, runId must be equal, allow when either batchRunId is null */
29
29
  const matchIdentity = (target, candidate) => target.pipelineId === candidate.pipelineId &&
30
30
  target.runId === candidate.runId &&
31
31
  (target.batchRunId === null || candidate.batchRunId === null || target.batchRunId === candidate.batchRunId);
@@ -44,7 +44,7 @@ const normalizePoolItems = (value) => {
44
44
  }
45
45
  }
46
46
  else if (typeof value === "string") {
47
- // 支持逗号与换行混输,方便直接粘贴关键词池。
47
+ // Support mixed comma and newline input for easy pasting of keyword pools.
48
48
  rawList.push(...value.split(/[\n,]/g));
49
49
  }
50
50
  const unique = new Set();
@@ -93,8 +93,8 @@ const createItemBatchController = (deps) => {
93
93
  });
94
94
  }
95
95
  catch (error) {
96
- // 批跑控制器运行在 HTTP 请求之外;这里若让异常逃逸,快照会永久停留在 running
97
- // 后续 start() 都会误判为已有批跑进行中。必须在控制器内兜底收口状态。
96
+ // The batch-run controller runs outside HTTP requests; if an exception escapes here, the snapshot would permanently stay at running,
97
+ // and subsequent start() calls would falsely detect an existing batch run in progress. Must guard the state inside the controller.
98
98
  if (token !== runToken)
99
99
  return;
100
100
  hasBatchErrors = true;
@@ -184,7 +184,7 @@ const createItemBatchController = (deps) => {
184
184
  stopRequested: false,
185
185
  batchRunId,
186
186
  };
187
- // 异步后台运行,避免占住 HTTP 请求。
187
+ // Run asynchronously in the background to avoid blocking the HTTP request.
188
188
  void runLoop(runToken, [...queuedItems]);
189
189
  return { ok: true, snapshot: cloneSnapshot(snapshot) };
190
190
  };
@@ -192,7 +192,7 @@ const createItemBatchController = (deps) => {
192
192
  if (snapshot.status !== "running") {
193
193
  return { ok: false, error: "batch_run_not_running", snapshot: cloneSnapshot(snapshot) };
194
194
  }
195
- // 仅请求停止,当前批次跑完后会安全退出。
195
+ // Only request stop; the current batch will exit safely after completion.
196
196
  snapshot = {
197
197
  ...snapshot,
198
198
  stopRequested: true,
@@ -203,7 +203,7 @@ const createItemBatchController = (deps) => {
203
203
  if (snapshot.status !== "running") {
204
204
  return { ok: false, error: "batch_run_not_running", snapshot: cloneSnapshot(snapshot) };
205
205
  }
206
- // 插件关闭后不应继续保留运行中的批跑控制器;切换 runToken 让旧循环结果失效。
206
+ // When the plugin is disabled, the running batch controller should not be retained; switch runToken to invalidate old loop results.
207
207
  runToken += 1;
208
208
  snapshot = {
209
209
  ...snapshot,
@@ -23,9 +23,9 @@ const createDependencyState = (options) => {
23
23
  impossibleCount += 1;
24
24
  }
25
25
  }
26
- // all/any 执行差异:
27
- // - all: 只有全部依赖满足才入队;若剩余依赖都已不可能满足则跳过。
28
- // - any: 任一依赖满足即可入队;仅当所有依赖都不可能满足时才跳过。
26
+ // all/any execution difference:
27
+ // - all: only enqueue when all dependencies are satisfied; skip when remaining dependencies are all impossible.
28
+ // - any: enqueue when any single dependency is satisfied; only skip when all dependencies are impossible.
29
29
  if (policy === "any") {
30
30
  if (satisfiedCount > 0)
31
31
  return "queued";
@@ -56,7 +56,7 @@ const createDependencyState = (options) => {
56
56
  if (!Number.isFinite(wakeTs) || wakeTs > now)
57
57
  continue;
58
58
  (0, state_1.markItemWakeSuccess)(item, { reason: "sleep_expired" });
59
- options.runtimeStore.pushTimeline(`等待节点已唤醒: ${item.nodeId}#${item.itemKey}`);
59
+ options.runtimeStore.pushTimeline(`Waiting node woken: ${item.nodeId}#${item.itemKey}`);
60
60
  }
61
61
  };
62
62
  const markReadyItemsFromDependencies = () => {
@@ -115,7 +115,7 @@ const createDependencyState = (options) => {
115
115
  const related = (getRun().groupItemRuns ?? []).filter((item) => item.groupId === group.id);
116
116
  if (related.length === 0)
117
117
  continue;
118
- // joinPolicy 仅支持 "all":全部 group item 成功才标记 group 成功,任一失败即失败
118
+ // joinPolicy only supports "all": mark group success only when all group items succeed, fail if any fails
119
119
  if (related.some((item) => item.status === "failed")) {
120
120
  (0, state_1.markGroupFailed)(group, { reason: "member_failed", command: "group_aggregate", error: related.find((item) => item.status === "failed")?.lastError });
121
121
  }