taskmeld 0.1.1 → 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 (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +176 -172
  3. package/README.zh-CN.md +176 -172
  4. package/dist/src/app/app-context-env.js +1 -1
  5. package/dist/src/app/create-app-context.js +3 -3
  6. package/dist/src/app/data-dir.js +13 -3
  7. package/dist/src/app/pipeline-config.js +4 -4
  8. package/dist/src/app/pipeline-registry.js +11 -11
  9. package/dist/src/app/pipeline-runtime.js +6 -9
  10. package/dist/src/app/runtime-store.js +3 -3
  11. package/dist/src/artifacts/artifact-cleanup.js +17 -17
  12. package/dist/src/artifacts/artifact-index.js +14 -14
  13. package/dist/src/artifacts/artifact-rebuilder.js +3 -3
  14. package/dist/src/artifacts/storage-service.js +18 -18
  15. package/dist/src/cli/bootstrap.js +7 -7
  16. package/dist/src/cli/commands/agent.js +12 -11
  17. package/dist/src/cli/commands/artifact.js +31 -30
  18. package/dist/src/cli/commands/init.js +49 -47
  19. package/dist/src/cli/commands/pipeline/result.js +9 -8
  20. package/dist/src/cli/commands/pipeline/selector.js +1 -1
  21. package/dist/src/cli/commands/pipeline/watch.js +2 -2
  22. package/dist/src/cli/commands/pipeline.js +54 -53
  23. package/dist/src/cli/commands/scheduler.js +9 -8
  24. package/dist/src/cli/commands/server.js +12 -11
  25. package/dist/src/cli/commands/system.js +4 -3
  26. package/dist/src/cli/errors.js +2 -2
  27. package/dist/src/cli/help.js +18 -17
  28. package/dist/src/cli/i18n.js +46 -0
  29. package/dist/src/cli/locales/en.json +244 -0
  30. package/dist/src/cli/locales/zh.json +244 -0
  31. package/dist/src/cli/output.js +3 -3
  32. package/dist/src/cli/renderers/engine/markdown.js +1 -1
  33. package/dist/src/cli/renderers/specs/index.js +1 -1
  34. package/dist/src/cli/router.js +1 -1
  35. package/dist/src/cli/server-runtime-client.js +54 -95
  36. package/dist/src/cli/ui-prompts.js +96 -0
  37. package/dist/src/cli/ws-runtime-client.js +51 -0
  38. package/dist/src/gateway/gateway-client.js +4 -4
  39. package/dist/src/index.js +28 -2
  40. package/dist/src/logs/run-log-reader.js +1 -1
  41. package/dist/src/pipeline/agent-activity.js +2 -2
  42. package/dist/src/pipeline/artifact-storage.js +11 -11
  43. package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +11 -11
  44. package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +2 -2
  45. package/dist/src/pipeline/execution/group-item-executor.js +1 -1
  46. package/dist/src/pipeline/execution/node-item-executor.js +3 -3
  47. package/dist/src/pipeline/execution/node-runner.js +7 -7
  48. package/dist/src/pipeline/execution/readiness-state.js +1 -1
  49. package/dist/src/pipeline/execution/reject-handler.js +5 -5
  50. package/dist/src/pipeline/execution/rejected-artifact-archiver.js +1 -1
  51. package/dist/src/pipeline/execution/route-item-manager.js +4 -4
  52. package/dist/src/pipeline/execution/run-abort-controller.js +5 -5
  53. package/dist/src/pipeline/execution/run-state-helpers.js +2 -2
  54. package/dist/src/pipeline/execution/service.js +4 -4
  55. package/dist/src/pipeline/execution/structured-node-runner.js +24 -24
  56. package/dist/src/pipeline/execution-timeout.js +3 -3
  57. package/dist/src/pipeline/identity/index.js +3 -3
  58. package/dist/src/pipeline/item-batch-controller.js +6 -6
  59. package/dist/src/pipeline/scheduler/dependency-state.js +5 -5
  60. package/dist/src/pipeline/scheduler-service.js +24 -24
  61. package/dist/src/pipeline/state-machine.js +2 -2
  62. package/dist/src/pipeline/structured-output/contract.js +4 -4
  63. package/dist/src/pipeline/structured-output/index.js +2 -2
  64. package/dist/src/pipeline/structured-output/parser.js +5 -5
  65. package/dist/src/pipeline/structured-output/prompt.js +38 -38
  66. package/dist/src/pipeline/structured-output/waiter.js +6 -6
  67. package/dist/src/pipeline/template.js +5 -5
  68. package/dist/src/pipeline/timeline-log-store.js +5 -5
  69. package/dist/src/pipeline/tool-activity.js +3 -3
  70. package/dist/src/pipeline/types/pipeline-output.js +1 -1
  71. package/dist/src/pipeline/workflow/branch-rules.js +19 -19
  72. package/dist/src/pipeline/workflow/io.js +1 -1
  73. package/dist/src/pipeline/workflow/normalize.js +18 -18
  74. package/dist/src/pipeline/workflow/template-mapper.js +3 -3
  75. package/dist/src/pipeline/workflow/validate.js +39 -39
  76. package/dist/src/pipeline/workflow-graph.js +10 -10
  77. package/dist/src/server/http-handler.js +74 -0
  78. package/dist/src/services/agent-service.js +2 -2
  79. package/dist/src/services/gateway-read-helpers.js +1 -1
  80. package/dist/src/services/pipeline-service.js +19 -19
  81. package/dist/src/services/pipeline-status.js +4 -4
  82. package/dist/src/services/read-services.js +1 -1
  83. package/dist/src/services/session-service.js +6 -6
  84. package/dist/src/services/system-service.js +1 -1
  85. package/dist/src/transport/ws-broker.js +12 -1
  86. package/dist/src/transport/ws-handler.js +60 -0
  87. package/dist/src/transport/ws-methods/agents.js +144 -0
  88. package/dist/src/transport/ws-methods/artifacts.js +171 -0
  89. package/dist/src/transport/ws-methods/gateway.js +16 -0
  90. package/dist/src/transport/ws-methods/logs.js +43 -0
  91. package/dist/src/transport/ws-methods/pipeline-batch.js +68 -0
  92. package/dist/src/transport/ws-methods/pipeline-links.js +100 -0
  93. package/dist/src/transport/ws-methods/pipeline-queue.js +51 -0
  94. package/dist/src/transport/ws-methods/pipeline-runtime.js +151 -0
  95. package/dist/src/transport/ws-methods/pipeline-scheduler.js +48 -0
  96. package/dist/src/transport/ws-methods/pipeline-workflow.js +127 -0
  97. package/dist/src/transport/ws-methods/pipelines.js +56 -0
  98. package/dist/src/transport/ws-methods/register-all.js +32 -0
  99. package/dist/src/transport/ws-methods/sessions.js +154 -0
  100. package/dist/src/transport/ws-methods/timeline.js +10 -0
  101. package/dist/src/{server/routes/pipeline-identity.js → transport/ws-methods/utils.js} +14 -9
  102. package/dist/src/version.js +1 -1
  103. package/package.json +16 -7
  104. package/web/dist/assets/agent-DP6TMcLj.js +1 -0
  105. package/web/dist/assets/agent-DmJHzLyj.js +1 -0
  106. package/web/dist/assets/artifact-BqnoZy2M.js +1 -0
  107. package/web/dist/assets/artifact-DfDkgkno.js +1 -0
  108. package/web/dist/assets/common-DRMTVwE9.js +1 -0
  109. package/web/dist/assets/common-DeXccbr2.js +1 -0
  110. package/web/dist/assets/dispatch-CBskGCQI.js +1 -0
  111. package/web/dist/assets/dispatch-sk4Wp30e.js +1 -0
  112. package/web/dist/assets/index-C8wTjZvH.css +1 -0
  113. package/web/dist/assets/index-DYDQZRLk.js +58 -0
  114. package/web/dist/assets/log-DN8cjb0w.js +1 -0
  115. package/web/dist/assets/log-HSeA_dYy.js +1 -0
  116. package/web/dist/assets/modal-BdNai9jf.js +1 -0
  117. package/web/dist/assets/modal-D9_KDpFD.js +1 -0
  118. package/web/dist/assets/nav-BmF7oAKg.js +1 -0
  119. package/web/dist/assets/nav-IjC2xqXQ.js +1 -0
  120. package/web/dist/assets/node-detail-CENRXcrh.js +1 -0
  121. package/web/dist/assets/node-detail-bndPr0IM.js +1 -0
  122. package/web/dist/assets/overview-B87zWAxq.js +1 -0
  123. package/web/dist/assets/overview-gQvk-NOK.js +1 -0
  124. package/web/dist/assets/pipeline-D4dSJRDz.js +1 -0
  125. package/web/dist/assets/pipeline-DZzyOqQa.js +1 -0
  126. package/web/dist/assets/session-CUWvU14v.js +5 -0
  127. package/web/dist/assets/session-DQ6UuCaJ.js +5 -0
  128. package/web/dist/assets/timeline-8y_2_0Em.js +1 -0
  129. package/web/dist/assets/timeline-CAPsXUTC.js +1 -0
  130. package/web/dist/index.html +3 -3
  131. package/dist/src/app/pipeline-plugin-config.js +0 -2
  132. package/dist/src/server/api-handler.js +0 -163
  133. package/dist/src/server/http-utils.js +0 -34
  134. package/dist/src/server/middleware.js +0 -61
  135. package/dist/src/server/router.js +0 -105
  136. package/dist/src/server/routes/agents.js +0 -189
  137. package/dist/src/server/routes/artifacts.js +0 -163
  138. package/dist/src/server/routes/gateway.js +0 -18
  139. package/dist/src/server/routes/health.js +0 -16
  140. package/dist/src/server/routes/logs.js +0 -73
  141. package/dist/src/server/routes/pipeline-batch.js +0 -163
  142. package/dist/src/server/routes/pipeline-diagnostics.js +0 -33
  143. package/dist/src/server/routes/pipeline-links.js +0 -117
  144. package/dist/src/server/routes/pipeline-outputs.js +0 -27
  145. package/dist/src/server/routes/pipeline-queue.js +0 -62
  146. package/dist/src/server/routes/pipeline-runtime.js +0 -162
  147. package/dist/src/server/routes/pipeline-scheduler.js +0 -69
  148. package/dist/src/server/routes/pipeline-workflow.js +0 -180
  149. package/dist/src/server/routes/pipelines.js +0 -96
  150. package/dist/src/server/routes/sessions.js +0 -244
  151. package/dist/src/server/routes/timeline.js +0 -14
  152. package/dist/src/server/serve-static.js +0 -42
  153. package/web/dist/assets/index-CWnfhkn-.js +0 -65
  154. package/web/dist/assets/index-gZ0xOfSO.css +0 -1
  155. /package/dist/src/{server → transport/ws-methods}/types.js +0 -0
@@ -1,23 +1,23 @@
1
1
  "use strict";
2
2
  /**
3
- * 支线判定纯规则函数。
4
- * 无外部依赖,只接受数据参数,供 workflow/validate execution/dependency-check 共同使用。
3
+ * Pure-functional branch determination rules.
4
+ * No external dependencies, accepts only data parameters. Shared by workflow/validate and execution/dependency-check.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.getBranchScope = exports.isCrossBranchEdgeByScope = exports.computeNodeScopes = void 0;
8
- // ====== Phase 2: 基于显式 branchScopeId 的支线规则 ======
8
+ // ====== Phase 2: Branch rules based on explicit branchScopeId ======
9
9
  /**
10
- * workflow route 边推导每个节点的 branch scope。
11
- * scope 标识 = routerNodeId:routeValue,如 "router:a"
12
- * 主线节点 scope null
10
+ * Derive each node's branch scope from the workflow's route edges.
11
+ * Scope identifier = routerNodeId:routeValue, e.g. "router:a".
12
+ * Mainline nodes have null scope.
13
13
  */
14
14
  const computeNodeScopes = (nodes, edges, explicitScopes) => {
15
15
  const scopes = new Map();
16
- // 初始化:有显式 scope 的用显式,否则为 null
16
+ // Initialize: use explicit scope if provided, otherwise null
17
17
  for (const node of nodes) {
18
18
  scopes.set(node.id, explicitScopes?.get(node.id) ?? null);
19
19
  }
20
- // route 边推导 scope:路由边 from scope 已知时,to scope = fromScope != null ? fromScope : "from:when"
20
+ // Derive scope from route edges: when from's scope is known, to's scope = fromScope != null ? fromScope : "from:when"
21
21
  for (const edge of edges) {
22
22
  if (edge.when === null)
23
23
  continue;
@@ -28,17 +28,17 @@ const computeNodeScopes = (nodes, edges, explicitScopes) => {
28
28
  scopes.set(edge.to, targetScope);
29
29
  }
30
30
  }
31
- // 传播 scope 沿普通依赖边:BFS 确保同一分支内的下游节点继承上游 scope
32
- // 多条路径到达节点时,若 scope 一致则合并;若冲突则保留首次设置的 scope
31
+ // Propagate scope along unconditional dependency edges: BFS ensures downstream nodes within the same branch inherit the upstream scope.
32
+ // When multiple paths reach a node, merge if scopes agree; on conflict preserve the first-set scope.
33
33
  let changed = true;
34
34
  while (changed) {
35
35
  changed = false;
36
36
  for (const edge of edges) {
37
37
  if (edge.when !== null)
38
- continue; // 只传播普通依赖边
38
+ continue; // Only propagate unconditional dependency edges
39
39
  const fromScope = scopes.get(edge.from);
40
40
  if (fromScope == null)
41
- continue; // 上游无 scope 则不传播
41
+ continue; // Don't propagate if upstream has no scope
42
42
  const toScope = scopes.get(edge.to);
43
43
  if (toScope == null) {
44
44
  scopes.set(edge.to, fromScope);
@@ -50,25 +50,25 @@ const computeNodeScopes = (nodes, edges, explicitScopes) => {
50
50
  };
51
51
  exports.computeNodeScopes = computeNodeScopes;
52
52
  /**
53
- * 基于 scope 判断边是否为跨支线普通边。
54
- * 跨支线:when null from to 在不同的 scope 中。
53
+ * Determine whether an edge is a cross-branch unconditional edge based on scope.
54
+ * Cross-branch: when is null and from and to are in different scopes.
55
55
  */
56
56
  const isCrossBranchEdgeByScope = (edge, nodeScopes) => {
57
57
  if (edge.when !== null)
58
58
  return false;
59
59
  const fromScope = nodeScopes.get(edge.from) ?? null;
60
60
  const toScope = nodeScopes.get(edge.to) ?? null;
61
- // scope → 同一分支内,允许
61
+ // Same scope → within the same branch, allowed
62
62
  if (fromScope === toScope)
63
63
  return false;
64
- // from scopeto scope → 支线结点向主线传播,不阻断(scope 沿依赖边继承)
64
+ // from has scope, to has no scope → branch node propagating to mainline, don't block (scope inherits along dependency edges)
65
65
  if (fromScope != null && toScope == null)
66
66
  return false;
67
- // from scope(主线),to scope(支线)→ 禁止:主线不能无条件依赖支线内部节点
68
- // from to 都有 scope 但不同禁止:不同支线之间的跨分支依赖
67
+ // from has no scope (mainline), to has scope (branch) → forbidden: mainline cannot unconditionally depend on branch-internal nodes
68
+ // from and to both have scope but different forbidden: cross-branch dependency between different branches
69
69
  return fromScope !== toScope;
70
70
  };
71
71
  exports.isCrossBranchEdgeByScope = isCrossBranchEdgeByScope;
72
- /** 基于 scope 获取节点的支线身份。null scope = 主线。 */
72
+ /** Get a node's branch identity based on scope. null scope = mainline. */
73
73
  const getBranchScope = (nodeId, nodeScopes) => nodeScopes.get(nodeId) ?? null;
74
74
  exports.getBranchScope = getBranchScope;
@@ -33,7 +33,7 @@ const loadWorkflowDefinitionWithStorage = (options) => {
33
33
  throw error;
34
34
  }
35
35
  const wrapped = new Error("invalid_persisted_workflow_definition");
36
- wrapped.detail = "workflow 文件解析失败,请检查 JSON 格式";
36
+ wrapped.detail = "Failed to parse workflow file, please check JSON format";
37
37
  throw wrapped;
38
38
  }
39
39
  };
@@ -57,7 +57,7 @@ const normalizeRemoteBatchPlugin = (value) => {
57
57
  enabled: record.enabled === true,
58
58
  url: normalizeNonEmptyString(record.url) ?? DEFAULT_REMOTE_BATCH_URL,
59
59
  startBatch: normalizeIntegerInRange(record.startBatch, 1, 1, 1_000_000),
60
- // 批大小与取数来源都属于低频配置,统一并入插件配置,避免主面板堆叠过多参数。
60
+ // Batch size and data source are low-frequency configuration; consolidate into plugin config to avoid too many params piling up on the main panel.
61
61
  batchSize: normalizeIntegerInRange(record.batchSize, 5, 1, 1_000),
62
62
  sourceField: normalizeNonEmptyString(record.sourceField) ?? "list30",
63
63
  };
@@ -65,14 +65,14 @@ const normalizeRemoteBatchPlugin = (value) => {
65
65
  const normalizeSchedulerPlugin = (value) => {
66
66
  const record = (0, guards_1.isRecord)(value) ? value : {};
67
67
  return {
68
- // 调度器历史上默认对所有流水线开启;插件化后继续保持默认开启,避免升级后界面和行为突然消失。
68
+ // The scheduler was historically enabled for all pipelines by default; after plugin-ification, keep it enabled by default so the UI and behavior don't suddenly disappear after upgrade.
69
69
  enabled: record.enabled !== false,
70
70
  };
71
71
  };
72
72
  const normalizeWorkflowPlugins = (value) => {
73
73
  const record = (0, guards_1.isRecord)(value) ? value : {};
74
74
  return {
75
- // 插件能力对所有流水线一致,这里只记录每条流水线自己的启用与参数配置。
75
+ // Plugin capabilities are consistent across all pipelines; only record each pipeline's own enabled + parameter configuration here.
76
76
  remoteBatch: normalizeRemoteBatchPlugin(record.remoteBatch),
77
77
  scheduler: normalizeSchedulerPlugin(record.scheduler),
78
78
  };
@@ -193,7 +193,7 @@ const normalizeWorkflowNode = (value) => {
193
193
  allowReject: value.allowReject === true,
194
194
  maxRejectCount: normalizeIntegerInRange(value.maxRejectCount, 3, 0, 10),
195
195
  };
196
- // 仅在显式提供时写入 branch scope 字段,避免 null 值污染 JSON 输出
196
+ // Only write branch scope fields when explicitly provided to avoid null values polluting JSON output
197
197
  if (branchScopeId)
198
198
  node.branchScopeId = branchScopeId;
199
199
  if (routeSourceNodeId)
@@ -224,8 +224,8 @@ const normalizeWorkflowEdgeV3 = (value) => {
224
224
  const to = normalizeNonEmptyString(value.to);
225
225
  if (!from || !to)
226
226
  return null;
227
- // v3 API 闭环要求:读取接口返回运行时 when 形状时,写回 /workflow 也必须可直接接受。
228
- // 这里仅在 version=3.0 契约下做"同版本双形状"归一化,不允许 version=2.0 旁路写入。
227
+ // v3 API closed-loop requirement: when the read API returns runtime when shapes, writing back to /workflow must also be directly accepted.
228
+ // Here, only do "same-version dual-shape" normalization under the version=3.0 contract; do not allow version=2.0 bypass writes.
229
229
  if ("when" in value) {
230
230
  return {
231
231
  from,
@@ -258,10 +258,10 @@ const normalizeWorkflowGroup = (value) => {
258
258
  if (!type)
259
259
  return null;
260
260
  const joinPolicy = "all";
261
- // 历史 any/quorum 降级:运行时仅支持 all,读取历史数据时静默降级为 all
262
- // 保存新 workflow validate 会显式拒绝 any/quorum
261
+ // Historical any/quorum downgrade: runtime only supports all; silently downgrade to all when reading historical data.
262
+ // When saving a new workflow, validate will explicitly reject any/quorum.
263
263
  if (value.joinPolicy === "any" || value.joinPolicy === "quorum") {
264
- // 静默降级历史数据兼容,新保存会被 validate 拦截
264
+ // Silently downgrade historical data compatibility; new saves will be intercepted by validate
265
265
  }
266
266
  return {
267
267
  id,
@@ -286,48 +286,48 @@ const readWorkflowDefinitionFromRaw = (value) => {
286
286
  exports.readWorkflowDefinitionFromRaw = readWorkflowDefinitionFromRaw;
287
287
  const readWorkflowDefinitionFromRawDetailed = (value) => {
288
288
  if (!(0, guards_1.isRecord)(value)) {
289
- return { ok: false, error: "invalid_workflow_definition", detail: "workflow 根对象格式非法" };
289
+ return { ok: false, error: "invalid_workflow_definition", detail: "Workflow root object has an invalid format" };
290
290
  }
291
291
  if (value.version === "2.0") {
292
292
  return {
293
293
  ok: false,
294
294
  error: "workflow_migration_required",
295
- detail: "检测到 workflow v2.0,请先执行迁移脚本再写入",
295
+ detail: "Workflow v2.0 detected, please run the migration script before writing",
296
296
  };
297
297
  }
298
298
  if (value.version !== "3.0") {
299
- return { ok: false, error: "invalid_workflow_definition", detail: `workflow.version 非法: ${String(value.version ?? "")}` };
299
+ return { ok: false, error: "invalid_workflow_definition", detail: `Invalid workflow.version: ${String(value.version ?? "")}` };
300
300
  }
301
301
  if (!Array.isArray(value.nodes) || !Array.isArray(value.edges) || !Array.isArray(value.groups)) {
302
- return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes/edges/groups 必须为数组" };
302
+ return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes/edges/groups must be arrays" };
303
303
  }
304
304
  const nodes = [];
305
305
  for (const item of value.nodes) {
306
306
  const normalized = (0, exports.normalizeWorkflowNode)(item);
307
307
  if (!normalized)
308
- return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes 存在非法节点结构" };
308
+ return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes contains an invalid node structure" };
309
309
  nodes.push(normalized);
310
310
  }
311
311
  const edges = [];
312
312
  for (const item of value.edges) {
313
313
  const normalized = normalizeWorkflowEdgeV3(item);
314
314
  if (!normalized)
315
- return { ok: false, error: "invalid_workflow_definition", detail: "workflow.edges 存在非法边结构" };
315
+ return { ok: false, error: "invalid_workflow_definition", detail: "workflow.edges contains an invalid edge structure" };
316
316
  edges.push(normalized);
317
317
  }
318
318
  const groups = [];
319
319
  for (const item of value.groups) {
320
- // 预检:在 normalize 静默降级之前显式拒绝不支持的 joinPolicy
320
+ // Pre-check: explicitly reject unsupported joinPolicy before normalize silently downgrades it
321
321
  if ((0, guards_1.isRecord)(item) && (item.joinPolicy === "any" || item.joinPolicy === "quorum")) {
322
322
  return {
323
323
  ok: false,
324
324
  error: "join_policy_not_supported",
325
- detail: `并行组 "${String(item.id ?? "?")}" joinPolicy "${String(item.joinPolicy)}" 未支持,当前仅支持 "all"`,
325
+ detail: `Parallel group "${String(item.id ?? "?")}" has unsupported joinPolicy "${String(item.joinPolicy)}", only "all" is currently supported`,
326
326
  };
327
327
  }
328
328
  const normalized = (0, exports.normalizeWorkflowGroup)(item);
329
329
  if (!normalized)
330
- return { ok: false, error: "invalid_workflow_definition", detail: "workflow.groups 存在非法并行组结构" };
330
+ return { ok: false, error: "invalid_workflow_definition", detail: "workflow.groups contains an invalid parallel group structure" };
331
331
  groups.push(normalized);
332
332
  }
333
333
  const workflow = {
@@ -5,13 +5,13 @@ const validate_1 = require("./validate");
5
5
  const toUniqueList = (items) => [...new Set(items)];
6
6
  // ====== Workflow → Template nodes (unified, with dedup) ======
7
7
  /**
8
- * WorkflowDefinitionRuntime 提取模板节点(仅 dependency 类型的出边)。
9
- * 这是 workflow → template 映射的唯一权威实现。
8
+ * Extract template nodes from WorkflowDefinitionRuntime (dependency-type outgoing edges only).
9
+ * This is the single authoritative implementation of the workflow → template mapping.
10
10
  */
11
11
  const workflowToTemplateNodes = (workflow) => {
12
12
  const incomingByNodeId = new Map();
13
13
  for (const edge of workflow.edges) {
14
- // template.dependsOn 只表达依赖边;路由边属于分流语义,不能回写成普通依赖。
14
+ // template.dependsOn only expresses dependency edges; route edges belong to routing semantics and cannot be written back as ordinary dependencies.
15
15
  if (edge.when !== null)
16
16
  continue;
17
17
  const prev = incomingByNodeId.get(edge.to) ?? [];
@@ -8,11 +8,11 @@ const validateWorkflowGraph = (workflow) => {
8
8
  if (workflow.nodes.length === 0) {
9
9
  return workflow.edges.length === 0 && workflow.groups.length === 0
10
10
  ? { ok: true }
11
- : { ok: false, error: "invalid_workflow_definition", detail: " workflow 不能包含 edges groups" };
11
+ : { ok: false, error: "invalid_workflow_definition", detail: "Empty workflow cannot contain edges or groups" };
12
12
  }
13
13
  const nodeIds = new Set(workflow.nodes.map((node) => node.id));
14
14
  if (nodeIds.size !== workflow.nodes.length) {
15
- return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes 存在重复 id" };
15
+ return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes contains duplicate IDs" };
16
16
  }
17
17
  const groupIds = new Set(workflow.groups.map((group) => group.id));
18
18
  const entityIds = new Set([...nodeIds, ...groupIds]);
@@ -29,14 +29,14 @@ const validateWorkflowGraph = (workflow) => {
29
29
  const edgeDedupe = new Set();
30
30
  for (const edge of workflow.edges) {
31
31
  if (!entityIds.has(edge.from) || !entityIds.has(edge.to)) {
32
- return { ok: false, error: "invalid_workflow_definition", detail: `边引用了不存在实体: ${edge.from} -> ${edge.to}` };
32
+ return { ok: false, error: "invalid_workflow_definition", detail: `Edge references non-existent entity: ${edge.from} -> ${edge.to}` };
33
33
  }
34
34
  if (edge.from === edge.to) {
35
- return { ok: false, error: "invalid_workflow_definition", detail: `检测到自环边: ${edge.from} -> ${edge.to}` };
35
+ return { ok: false, error: "invalid_workflow_definition", detail: `Self-loop edge detected: ${edge.from} -> ${edge.to}` };
36
36
  }
37
37
  const key = `${edge.from}|${edge.when ?? ""}|${edge.to}`;
38
38
  if (edgeDedupe.has(key)) {
39
- return { ok: false, error: "invalid_workflow_definition", detail: `检测到重复边: ${edge.from} -> ${edge.to}` };
39
+ return { ok: false, error: "invalid_workflow_definition", detail: `Duplicate edge detected: ${edge.from} -> ${edge.to}` };
40
40
  }
41
41
  edgeDedupe.add(key);
42
42
  outgoing.set(edge.from, [...(outgoing.get(edge.from) ?? []), edge.to]);
@@ -57,15 +57,15 @@ const validateWorkflowGraph = (workflow) => {
57
57
  const sourceNode = workflow.nodes.find((node) => node.id === sourceId);
58
58
  if (sourceNode?.routePolicy)
59
59
  continue;
60
- // 非分流节点仍禁止同一节点混合依赖边和路由边,避免无条件放行导致重复执行。
60
+ // Non-routing nodes are still forbidden from mixing dependency and route edges on the same node, to prevent unconditional passthrough leading to double execution.
61
61
  return {
62
62
  ok: false,
63
63
  error: "mixed_outgoing_edge_kinds_forbidden",
64
- detail: `节点 ${sourceId} 同时存在 dependency route 出边,已禁止保存`,
64
+ detail: `Node ${sourceId} has both dependency and route outgoing edges, which is not allowed`,
65
65
  };
66
66
  }
67
- // Phase 2: 基于显式 scope 的跨支线边检测。
68
- // computeNodeScopes + isCrossBranchEdgeByScope 使用显式 branchScopeId(缺失时从 route 边推导)。
67
+ // Phase 2: Cross-branch edge detection based on explicit scope.
68
+ // computeNodeScopes + isCrossBranchEdgeByScope use explicit branchScopeId (derived from route edges when missing).
69
69
  {
70
70
  const explicitScopes = new Map();
71
71
  const mergeNodeIds = new Set();
@@ -73,13 +73,13 @@ const validateWorkflowGraph = (workflow) => {
73
73
  if (node.branchScopeId != null) {
74
74
  explicitScopes.set(node.id, node.branchScopeId);
75
75
  }
76
- // merge 节点(dependencyPolicy !== "all")是显式分支汇聚点,接受来自不同 scope 的依赖边
76
+ // merge nodes (dependencyPolicy !== "all") are explicit branch convergence points, accepting dependency edges from different scopes
77
77
  if (node.dependencyPolicy && node.dependencyPolicy !== "all") {
78
78
  mergeNodeIds.add(node.id);
79
79
  }
80
80
  }
81
81
  const nodeScopes = (0, branch_rules_1.computeNodeScopes)(workflow.nodes, workflow.edges, explicitScopes);
82
- // merge 节点清除 scope,避免其被误判为跨支线(与 workflow-graph.ts 的 buildIndices 保持一致)
82
+ // Clear scope for merge nodes to avoid them being misjudged as cross-branch (consistent with buildIndices in workflow-graph.ts)
83
83
  for (const nodeId of mergeNodeIds) {
84
84
  nodeScopes.set(nodeId, null);
85
85
  }
@@ -88,7 +88,7 @@ const validateWorkflowGraph = (workflow) => {
88
88
  return {
89
89
  ok: false,
90
90
  error: "cross_branch_edge_forbidden",
91
- detail: `禁止跨支线无条件边: ${scopeCrossEdges[0].from} -> ${scopeCrossEdges[0].to}from 分支 ${nodeScopes.get(scopeCrossEdges[0].from) ?? "main"} -> to 分支 ${nodeScopes.get(scopeCrossEdges[0].to) ?? "main"},跨支线依赖边需要显式 merge 节点)`,
91
+ detail: `Cross-branch unconditional edge is not allowed: ${scopeCrossEdges[0].from} -> ${scopeCrossEdges[0].to} (from branch ${nodeScopes.get(scopeCrossEdges[0].from) ?? "main"} -> to branch ${nodeScopes.get(scopeCrossEdges[0].to) ?? "main"}, cross-branch dependency edges require an explicit merge node)`,
92
92
  };
93
93
  }
94
94
  }
@@ -96,12 +96,12 @@ const validateWorkflowGraph = (workflow) => {
96
96
  const explicitGroupById = new Map(workflow.groups.map((group) => [group.id, group]));
97
97
  for (const group of workflow.groups) {
98
98
  if (uniqueGroupIds.has(group.id)) {
99
- return { ok: false, error: "invalid_workflow_definition", detail: `并行组 id 重复: ${group.id}` };
99
+ return { ok: false, error: "invalid_workflow_definition", detail: `Duplicate parallel group ID: ${group.id}` };
100
100
  }
101
101
  uniqueGroupIds.add(group.id);
102
102
  for (const member of group.members) {
103
103
  if (!nodeIds.has(member)) {
104
- return { ok: false, error: "invalid_workflow_definition", detail: `并行组 ${group.id} 引用了不存在成员 ${member}` };
104
+ return { ok: false, error: "invalid_workflow_definition", detail: `Parallel group ${group.id} references non-existent member ${member}` };
105
105
  }
106
106
  }
107
107
  }
@@ -111,9 +111,9 @@ const validateWorkflowGraph = (workflow) => {
111
111
  continue;
112
112
  const group = explicitGroupById.get(groupId);
113
113
  if (!group)
114
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 引用了不存在并行组 ${groupId}` };
114
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} references non-existent parallel group ${groupId}` };
115
115
  if (!group.members.includes(node.id)) {
116
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 未加入其声明的并行组 ${groupId}` };
116
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} is not a member of its declared parallel group ${groupId}` };
117
117
  }
118
118
  }
119
119
  for (const group of workflow.groups) {
@@ -129,20 +129,20 @@ const validateWorkflowGraph = (workflow) => {
129
129
  if (edge.to === group.id)
130
130
  continue;
131
131
  if (edge.from === group.id)
132
- return { ok: false, error: "invalid_workflow_definition", detail: `并行组 ${group.id} 不能直接连入成员节点` };
132
+ return { ok: false, error: "invalid_workflow_definition", detail: `Parallel group ${group.id} cannot directly connect to member nodes` };
133
133
  if (memberSet.has(edge.from))
134
- return { ok: false, error: "invalid_workflow_definition", detail: `并行组 ${group.id} 成员之间禁止直接依赖` };
134
+ return { ok: false, error: "invalid_workflow_definition", detail: `Direct dependencies between members of parallel group ${group.id} are not allowed` };
135
135
  if (groupIncoming.has(edge.from))
136
- return { ok: false, error: "invalid_workflow_definition", detail: `并行组 ${group.id} 的入口节点不能直连成员` };
136
+ return { ok: false, error: "invalid_workflow_definition", detail: `The entry node of parallel group ${group.id} cannot directly connect to members` };
137
137
  }
138
138
  }
139
139
  for (const group of workflow.groups) {
140
- // joinPolicy 仅支持 "all"any/quorum 运行时未实现,保存时显式拒绝
140
+ // joinPolicy only supports "all"; any/quorum are not implemented at runtime, explicitly reject on save
141
141
  if (group.joinPolicy !== "all") {
142
142
  return {
143
143
  ok: false,
144
144
  error: "join_policy_not_supported",
145
- detail: `并行组 ${group.id} joinPolicy "${group.joinPolicy}" 未支持,当前仅支持 "all"`,
145
+ detail: `Parallel group ${group.id} has unsupported joinPolicy "${group.joinPolicy}", only "all" is currently supported`,
146
146
  };
147
147
  }
148
148
  }
@@ -150,24 +150,24 @@ const validateWorkflowGraph = (workflow) => {
150
150
  if (node.routePolicy) {
151
151
  const { allowed } = node.routePolicy;
152
152
  if (allowed.length < 2 || allowed.length > 5) {
153
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 的路由集合长度非法` };
153
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} has an invalid route set size` };
154
154
  }
155
155
  if (!allowed.includes(routes_1.MAINLINE_ROUTE_VALUE) || !allowed.includes(routes_1.DEFAULT_BRANCH_ROUTE_VALUE)) {
156
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 开启分流后必须包含 yes no` };
156
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} must include "yes" and "no" routes when routing is enabled` };
157
157
  }
158
158
  const outgoingEdges = edgesBySource.get(node.id) ?? [];
159
159
  const dependencyEdges = outgoingEdges.filter((edge) => edge.when === null);
160
160
  if (dependencyEdges.length > 1) {
161
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} yes 主线依赖边最多只能有 1 条` };
161
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} can have at most 1 "yes" mainline dependency edge` };
162
162
  }
163
163
  const routeEdgeCounts = new Map();
164
164
  for (const edge of outgoingEdges.filter((item) => item.when !== null)) {
165
165
  routeEdgeCounts.set(edge.when ?? "", (routeEdgeCounts.get(edge.when ?? "") ?? 0) + 1);
166
166
  if (edge.when === routes_1.MAINLINE_ROUTE_VALUE) {
167
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} yes 不能保存为路由边` };
167
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} cannot save "yes" as a route edge` };
168
168
  }
169
169
  if (!allowed.includes(edge.when ?? "")) {
170
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 存在未声明的路由边: ${edge.when}` };
170
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} has an undeclared route edge: ${edge.when}` };
171
171
  }
172
172
  const targetNode = workflow.nodes.find((candidate) => candidate.id === edge.to);
173
173
  const targetGroup = workflow.groups.find((group) => group.id === edge.to);
@@ -176,17 +176,17 @@ const validateWorkflowGraph = (workflow) => {
176
176
  : [];
177
177
  const isBranchTarget = targetNode?.lane === "branch" || (targetGroupMembers.length > 0 && targetGroupMembers.every((member) => member?.lane === "branch"));
178
178
  if (!isBranchTarget) {
179
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 的路由 ${edge.when} 只能指向支线节点或支线并行组` };
179
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} route "${edge.when}" can only target branch nodes or branch parallel groups` };
180
180
  }
181
181
  }
182
182
  for (const route of allowed.filter((item) => item !== routes_1.MAINLINE_ROUTE_VALUE)) {
183
183
  if ((routeEdgeCounts.get(route) ?? 0) !== 1) {
184
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} 的路由 ${route} 必须配置且只能配置 1 个支线目标` };
184
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} route "${route}" must have exactly 1 branch target` };
185
185
  }
186
186
  }
187
187
  }
188
188
  if (node.dependencyPolicy !== undefined && node.dependencyPolicy !== "all" && node.dependencyPolicy !== "any") {
189
- return { ok: false, error: "invalid_workflow_definition", detail: `节点 ${node.id} dependencyPolicy 非法` };
189
+ return { ok: false, error: "invalid_workflow_definition", detail: `Node ${node.id} has an invalid dependencyPolicy` };
190
190
  }
191
191
  }
192
192
  const queue = [...[...entityIds].filter((id) => (indegree.get(id) ?? 0) === 0)];
@@ -203,7 +203,7 @@ const validateWorkflowGraph = (workflow) => {
203
203
  }
204
204
  }
205
205
  if (visited !== entityIds.size) {
206
- return { ok: false, error: "invalid_workflow_definition", detail: "工作流存在环路,无法拓扑排序" };
206
+ return { ok: false, error: "invalid_workflow_definition", detail: "Workflow contains a cycle, cannot perform topological sort" };
207
207
  }
208
208
  return { ok: true };
209
209
  };
@@ -212,25 +212,25 @@ const validateWorkflowOutputConfig = (workflow) => {
212
212
  const output = workflow.output ?? { mode: "mainline_last", nodeId: null };
213
213
  if (workflow.nodes.length === 0) {
214
214
  return output.mode === "explicit" && output.nodeId
215
- ? { ok: false, error: "invalid_workflow_output_config", detail: " workflow 不能指定输出节点" }
215
+ ? { ok: false, error: "invalid_workflow_output_config", detail: "Empty workflow cannot specify an output node" }
216
216
  : { ok: true };
217
217
  }
218
218
  if (output.mode === "explicit") {
219
219
  if (!output.nodeId) {
220
- return { ok: false, error: "invalid_workflow_output_config", detail: "mode=explicit 时 nodeId 必填" };
220
+ return { ok: false, error: "invalid_workflow_output_config", detail: "nodeId is required when mode=explicit" };
221
221
  }
222
222
  const node = workflow.nodes.find((n) => n.id === output.nodeId);
223
223
  if (!node) {
224
- return { ok: false, error: "invalid_workflow_output_config", detail: `输出节点 ${output.nodeId} 不存在` };
224
+ return { ok: false, error: "invalid_workflow_output_config", detail: `Output node ${output.nodeId} does not exist` };
225
225
  }
226
226
  if (!node.enabled) {
227
- return { ok: false, error: "invalid_workflow_output_config", detail: `输出节点 ${output.nodeId} 必须 enabled` };
227
+ return { ok: false, error: "invalid_workflow_output_config", detail: `Output node ${output.nodeId} must be enabled` };
228
228
  }
229
229
  if (node.lane !== "main") {
230
- return { ok: false, error: "invalid_workflow_output_config", detail: `输出节点 ${output.nodeId} 必须是主线节点` };
230
+ return { ok: false, error: "invalid_workflow_output_config", detail: `Output node ${output.nodeId} must be a mainline node` };
231
231
  }
232
232
  if (node.branchScopeId) {
233
- return { ok: false, error: "invalid_workflow_output_config", detail: `输出节点 ${output.nodeId} 不能属于支线 scope` };
233
+ return { ok: false, error: "invalid_workflow_output_config", detail: `Output node ${output.nodeId} cannot belong to a branch scope` };
234
234
  }
235
235
  return { ok: true };
236
236
  }
@@ -239,7 +239,7 @@ const validateWorkflowOutputConfig = (workflow) => {
239
239
  .filter((n) => n.enabled && n.lane === "main" && !n.branchScopeId && !n.routeSourceNodeId && !n.routeValue)
240
240
  .map((n) => n.id));
241
241
  if (mainlineNodeIds.size === 0) {
242
- return { ok: false, error: "invalid_workflow_output_config", detail: "没有可用的主线节点" };
242
+ return { ok: false, error: "invalid_workflow_output_config", detail: "No available mainline nodes" };
243
243
  }
244
244
  // Build full adjacency (all nodes, all edges) for reachability DFS
245
245
  const allNodeIds = new Set(workflow.nodes.map((n) => n.id));
@@ -296,13 +296,13 @@ const validateWorkflowOutputConfig = (workflow) => {
296
296
  ? sinkNodes // all candidates are orphans, keep as-is
297
297
  : sinkNodes.filter((id) => !orphanIds.has(id)); // exclude orphans
298
298
  if (effectiveSinks.length === 0) {
299
- return { ok: false, error: "invalid_workflow_output_config", detail: "无法推导唯一主线 sink 节点" };
299
+ return { ok: false, error: "invalid_workflow_output_config", detail: "Cannot derive a unique mainline sink node" };
300
300
  }
301
301
  if (effectiveSinks.length > 1) {
302
302
  return {
303
303
  ok: false,
304
304
  error: "invalid_workflow_output_config",
305
- detail: `存在多个主线 sink 节点: ${effectiveSinks.join(", ")},请切换到 mode=explicit 并指定 nodeId`,
305
+ detail: `Multiple mainline sink nodes found: ${effectiveSinks.join(", ")}, switch to mode=explicit and specify nodeId`,
306
306
  };
307
307
  }
308
308
  return { ok: true };
@@ -21,8 +21,8 @@ const buildIndices = (workflow) => {
21
21
  outgoing.push(edge);
22
22
  outgoingEdgesBySource.set(edge.from, outgoing);
23
23
  }
24
- // 计算节点 branch scope:优先使用显式 branchScopeId,缺失时从 route 边推导。
25
- // nodes groups 都纳入 scope 计算,确保 group scope 被正确传播。
24
+ // Compute node branch scope: prefer explicit branchScopeId, derive from route edges when missing.
25
+ // Include both nodes and groups in scope computation so group scopes are correctly propagated.
26
26
  const explicitScopes = new Map();
27
27
  for (const node of workflow.nodes) {
28
28
  if (node.branchScopeId != null) {
@@ -34,9 +34,9 @@ const buildIndices = (workflow) => {
34
34
  ...workflow.groups.map((g) => ({ id: g.id })),
35
35
  ];
36
36
  const nodeScopes = (0, branch_rules_1.computeNodeScopes)(allEntities, workflow.edges, explicitScopes);
37
- // explicitScopeIds: 显式声明了非默认 merge 策略的节点(dependencyPolicy != "all"),
38
- // 是多个分支的显式汇聚点,应接受来自不同 scope 的依赖边。
39
- // 将其 scope 重置为 null,避免跨支线误判阻断合法的分支合并。
37
+ // explicitScopeIds: nodes that explicitly declare a non-default merge strategy (dependencyPolicy != "all"),
38
+ // they are explicit convergence points for multiple branches and should accept dependency edges from different scopes.
39
+ // Reset their scope to null to avoid cross-branch false positives blocking valid branch merges.
40
40
  for (const node of workflow.nodes) {
41
41
  if (node.dependencyPolicy && node.dependencyPolicy !== "all") {
42
42
  nodeScopes.set(node.id, null);
@@ -103,14 +103,14 @@ const createWorkflowGraph = (initialWorkflow, initialTemplateNodes) => {
103
103
  const getParallelGroupByMemberNodeId = (nodeId) => indices.parallelGroupByMemberNodeId.get(nodeId) ?? null;
104
104
  const isWorkflowNodeEnabled = (nodeId) => getWorkflowNodeById(nodeId)?.enabled !== false;
105
105
  const isGroupId = (id) => indices.groupById.has(id);
106
- // Phase 2: 基于 scope 判断支线身份,替代入边形状推断。
107
- // scope null → 主线节点;scope null → 支线节点。
106
+ // Phase 2: determine branch identity based on scope, replacing incoming-edge shape inference.
107
+ // scope is null → mainline node; scope is non-null → branch node.
108
108
  const isBranchNode = (nodeId) => {
109
109
  const scope = indices.nodeScopes.get(nodeId);
110
110
  return scope != null;
111
111
  };
112
- // Phase 2: 基于 scope 判断跨支线边,替代入边形状推断。
113
- // 旧版在存在 B1→B2 普通边时会因 B2 不再"纯支线"而漏判,新版不受此影响。
112
+ // Phase 2: determine cross-branch edges based on scope, replacing incoming-edge shape inference.
113
+ // The old version would miss detection when B1→B2 unconditional edges exist because B2 was no longer "purely branch"; the new version is unaffected.
114
114
  const isCrossBranchEdge = (edge) => (0, branch_rules_1.isCrossBranchEdgeByScope)(edge, indices.nodeScopes);
115
115
  const getNodeScope = (nodeId) => indices.nodeScopes.get(nodeId) ?? null;
116
116
  const getNodesWithWorkflowMeta = (nodes) => nodes.map((node) => {
@@ -126,7 +126,7 @@ const createWorkflowGraph = (initialWorkflow, initialTemplateNodes) => {
126
126
  const current = new Map((run.groups ?? []).map((group) => [group.id, group]));
127
127
  run.groups = indices.groups.map((group) => ({
128
128
  id: group.id,
129
- title: `并行组 ${group.id}`,
129
+ title: `Group ${group.id}`,
130
130
  status: current.get(group.id)?.status ?? "blocked",
131
131
  members: group.members,
132
132
  joinPolicy: group.joinPolicy,
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createApiHandler = void 0;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ // Static file MIME type map
7
+ const MIME = {
8
+ ".html": "text/html; charset=utf-8",
9
+ ".css": "text/css; charset=utf-8",
10
+ ".js": "application/javascript; charset=utf-8",
11
+ ".json": "application/json; charset=utf-8",
12
+ ".svg": "image/svg+xml",
13
+ ".png": "image/png",
14
+ ".ico": "image/x-icon",
15
+ ".woff": "font/woff",
16
+ ".woff2": "font/woff2",
17
+ };
18
+ // Resolve the web static assets directory
19
+ const resolveWebDist = () => (0, node_path_1.join)(__dirname, "..", "..", "..", "web", "dist");
20
+ const webDistRoot = resolveWebDist();
21
+ // Static file serving (SPA falls back to index.html)
22
+ const serveStatic = (req, res) => {
23
+ if (!(0, node_fs_1.existsSync)(webDistRoot))
24
+ return false;
25
+ const url = new URL(req.url ?? "/", "http://localhost");
26
+ let filePath = (0, node_path_1.join)(webDistRoot, url.pathname === "/" ? "index.html" : url.pathname);
27
+ if (!(0, node_fs_1.existsSync)(filePath) || !filePath.startsWith(webDistRoot)) {
28
+ filePath = (0, node_path_1.join)(webDistRoot, "index.html");
29
+ }
30
+ if (!(0, node_fs_1.existsSync)(filePath))
31
+ return false;
32
+ const ext = (0, node_path_1.extname)(filePath);
33
+ const contentType = MIME[ext] ?? "application/octet-stream";
34
+ res.writeHead(200, {
35
+ "Content-Type": contentType,
36
+ "Cache-Control": ext === ".html" ? "no-cache" : "public, max-age=3600",
37
+ });
38
+ (0, node_fs_1.createReadStream)(filePath).pipe(res);
39
+ return true;
40
+ };
41
+ // Create HTTP request handler (health check + CORS + static files)
42
+ const createApiHandler = (options) => {
43
+ return (req, res) => {
44
+ const url = req.url ?? "/";
45
+ // /api/health — CLI server lifecycle check
46
+ if (req.method === "GET" && url === "/api/health") {
47
+ res.writeHead(200, {
48
+ "Content-Type": "application/json; charset=utf-8",
49
+ "Access-Control-Allow-Origin": options.webOrigin,
50
+ });
51
+ res.end(JSON.stringify({ ok: true, ...options.serverRuntimeIdentity }));
52
+ return;
53
+ }
54
+ // OPTIONS — CORS preflight
55
+ if (req.method === "OPTIONS") {
56
+ res.writeHead(204, {
57
+ "Access-Control-Allow-Origin": options.webOrigin,
58
+ "Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
59
+ "Access-Control-Allow-Headers": "Content-Type",
60
+ });
61
+ res.end();
62
+ return;
63
+ }
64
+ // Static SPA files (falls back to index.html)
65
+ if (serveStatic(req, res))
66
+ return;
67
+ res.writeHead(404, {
68
+ "Content-Type": "application/json; charset=utf-8",
69
+ "Access-Control-Allow-Origin": options.webOrigin,
70
+ });
71
+ res.end(JSON.stringify({ error: "not_found" }));
72
+ };
73
+ };
74
+ exports.createApiHandler = createApiHandler;
@@ -38,14 +38,14 @@ const createAgentService = (app) => {
38
38
  await (0, gateway_read_helpers_1.ensureGatewayReadyForReadonly)(app);
39
39
  const payload = await app.gateway.client.sendReq("agents.list");
40
40
  const rawItems = app.gateway.pickArray(payload);
41
- // 会话活动时间优先尝试刷新;刷新失败时回退缓存,保证只读查询可用性。
41
+ // Try refreshing session activity first; fall back to cache on failure to keep read-only queries available.
42
42
  let sessionItems = app.gateway.getSessionCache();
43
43
  try {
44
44
  const refreshed = await app.gateway.refreshSessionsFromGateway();
45
45
  sessionItems = refreshed.items;
46
46
  }
47
47
  catch {
48
- // 只读查询容错:会话刷新失败不应阻断 agents.list 输出。
48
+ // Read-only query resilience: session refresh failure must not block agents.list output.
49
49
  }
50
50
  const lastActiveByAgentId = new Map();
51
51
  for (const session of sessionItems) {