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
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createPipelineRuntimeApiClient = exports.createServerLifecycleClient = void 0;
6
+ exports.createPipelineRuntimeApiClientWs = exports.createServerLifecycleClient = exports.resolveRuntimePipelineSelector = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const promises_1 = require("node:fs/promises");
9
9
  const node_path_1 = require("node:path");
@@ -11,6 +11,7 @@ const node_child_process_1 = require("node:child_process");
11
11
  const ws_1 = __importDefault(require("ws"));
12
12
  const app_context_env_1 = require("../app/app-context-env");
13
13
  const data_dir_1 = require("../app/data-dir");
14
+ const ws_runtime_client_1 = require("./ws-runtime-client");
14
15
  const errors_1 = require("./errors");
15
16
  const STARTUP_LOCK_STALE_MS = 15_000;
16
17
  const STARTUP_LOCK_WAIT_MS = 250;
@@ -18,24 +19,12 @@ const STARTUP_TIMEOUT_MS = 15_000;
18
19
  const STOP_TIMEOUT_MS = 10_000;
19
20
  const buildApiBaseUrl = () => {
20
21
  const config = (0, app_context_env_1.resolveAppContextConfig)();
21
- // CLI 访问本地常驻后端时固定走 loopback,避免 0.0.0.0 作为 client 目标地址不可直连。
22
22
  return `http://127.0.0.1:${config.apiPort}`;
23
23
  };
24
24
  const buildWsBaseUrl = () => {
25
25
  const config = (0, app_context_env_1.resolveAppContextConfig)();
26
26
  return `ws://127.0.0.1:${config.apiPort}`;
27
27
  };
28
- const buildPipelineApiUrl = (pipelineId, suffix) => `${buildApiBaseUrl()}/api/pipelines/${encodeURIComponent(pipelineId)}${suffix}`;
29
- const buildPipelineApiUrlWithIdentity = (pipelineId, suffix, target) => {
30
- const url = new URL(buildPipelineApiUrl(pipelineId, suffix));
31
- if (typeof target?.runId === "string" && target.runId.trim()) {
32
- url.searchParams.set("runId", target.runId.trim());
33
- }
34
- if (typeof target?.batchRunId === "string" && target.batchRunId.trim()) {
35
- url.searchParams.set("batchRunId", target.batchRunId.trim());
36
- }
37
- return url.toString();
38
- };
39
28
  const resolveRuntimePipelineSelector = (selector) => {
40
29
  if (typeof selector === "string" && selector.trim()) {
41
30
  return { pipelineId: selector.trim() };
@@ -52,7 +41,7 @@ const resolveRuntimePipelineSelector = (selector) => {
52
41
  }
53
42
  const pipelineId = typeof selector.pipelineId === "string" ? selector.pipelineId.trim() : "";
54
43
  if (!pipelineId) {
55
- // runtime API status/stop 路由仍是 /api/pipelines/:pipelineId/*,runId/batchRunId 仅用于“精确命中当前 pipeline 运行”。
44
+ // runtime API status/stop routes are still /api/pipelines/:pipelineId/*; runId/batchRunId are only used for "precisely hitting the current pipeline run".
56
45
  throw new errors_1.CliError("Missing pipelineId for runtime API selector", {
57
46
  code: "INVALID_ARGUMENT",
58
47
  exitCode: 2,
@@ -70,6 +59,7 @@ const resolveRuntimePipelineSelector = (selector) => {
70
59
  },
71
60
  };
72
61
  };
62
+ exports.resolveRuntimePipelineSelector = resolveRuntimePipelineSelector;
73
63
  const waitForPipelineWatchSignal = async (selector, timeoutMs) => {
74
64
  const wsUrl = `${buildWsBaseUrl()}/api/ws`;
75
65
  const eventType = await new Promise((resolve, reject) => {
@@ -104,8 +94,8 @@ const waitForPipelineWatchSignal = async (selector, timeoutMs) => {
104
94
  if (selector.runId) {
105
95
  return selector.runId === eventRunId || selector.runId === runIdFromRun;
106
96
  }
107
- // batchRunId pipeline.updated 中没有稳定字段;这里把更新当作“状态可能变化”的唤醒信号,
108
- // 实际终态判断仍由后续 status 请求完成,避免因为事件字段缺失误判终态。
97
+ // batchRunId has no stable field in pipeline.updated; treat the update as a "state may have changed" wake-up signal,
98
+ // while the actual terminal-state judgment is still done by a subsequent status request, to avoid misjudging terminal state due to missing event fields.
109
99
  if (selector.batchRunId)
110
100
  return true;
111
101
  return Boolean(selector.pipelineId);
@@ -294,7 +284,7 @@ const metadataMatchesHealth = (metadata, health) => {
294
284
  if (metadata.serverId && health.serverId) {
295
285
  return metadata.serverId === health.serverId;
296
286
  }
297
- // 兼容旧 metadata:早期 runtime.json 不含 serverId,此时退回 pid/port/endpoint 交叉校验。
287
+ // Compatibility with old metadata: early runtime.json didn't include serverId; fall back to pid/port/endpoint cross-validation.
298
288
  return true;
299
289
  };
300
290
  const buildMetadataFromHealth = (health) => ({
@@ -354,7 +344,7 @@ const cleanupStaleRuntimeArtifacts = async (probe) => {
354
344
  if (probe.metadataStale) {
355
345
  await (0, promises_1.rm)(getServerRuntimeMetadataPath(), { force: true });
356
346
  }
357
- // startup.lock 是“开始拉起”的互斥标记,不是 owner 证据;只有健康实例不存在且锁超时才允许清理。
347
+ // startup.lock is a mutual-exclusion marker for "starting up", not owner evidence; only clean up when no healthy instance exists and the lock is stale.
358
348
  if (!probe.ready && probe.lockStale) {
359
349
  await (0, promises_1.rm)(getServerStartupLockPath(), { force: true });
360
350
  }
@@ -364,8 +354,8 @@ const reconcileRuntimeMetadataWithHealth = async (probe) => {
364
354
  return probe.metadata;
365
355
  const nextMetadata = buildMetadataFromHealth(probe.health);
366
356
  if (!metadataMatchesHealth(probe.metadata, probe.health)) {
367
- // health 请求已经命中了当前 CLI 期望的 endpoint;只要这份 owner 信息完整,就应该回填 metadata
368
- // 否则 metadata_missing / metadata_mismatch 会在后续 ensure/status/stop 中反复被误判为异常状态。
357
+ // The health request already matched the endpoint the CLI expects; if this owner info is complete, it should be written back to metadata,
358
+ // otherwise metadata_missing / metadata_mismatch would repeatedly be misjudged as an abnormal state in subsequent ensure/status/stop calls.
369
359
  await writeRuntimeMetadata(nextMetadata);
370
360
  return nextMetadata;
371
361
  }
@@ -382,47 +372,6 @@ const isPidRunning = (pid) => {
382
372
  return false;
383
373
  }
384
374
  };
385
- const normalizeServerError = async (response) => {
386
- let payload = null;
387
- try {
388
- payload = await response.json();
389
- }
390
- catch {
391
- payload = null;
392
- }
393
- const code = typeof payload?.error === "string" && payload.error.trim() ? payload.error.trim().toUpperCase() : "SERVER_REQUEST_FAILED";
394
- const message = typeof payload?.error === "string" && payload.error.trim()
395
- ? payload.error.trim()
396
- : `server request failed: ${response.status}`;
397
- throw new errors_1.CliError(message, {
398
- code,
399
- exitCode: response.status === 404 ? 3 : response.status === 400 ? 2 : 4,
400
- details: {
401
- status: response.status,
402
- payload,
403
- },
404
- });
405
- };
406
- const requestJson = async (url, init) => {
407
- let response;
408
- try {
409
- response = await fetch(url, init);
410
- }
411
- catch (error) {
412
- throw new errors_1.CliError("Local API server is unavailable", {
413
- code: "LOCAL_API_UNAVAILABLE",
414
- exitCode: 5,
415
- details: {
416
- url,
417
- detail: error instanceof Error ? error.message : "unknown_error",
418
- },
419
- });
420
- }
421
- if (!response.ok) {
422
- await normalizeServerError(response);
423
- }
424
- return response.json();
425
- };
426
375
  const isServerHealthy = async () => {
427
376
  return (await readServerHealth()) !== null;
428
377
  };
@@ -561,7 +510,7 @@ const startLocalApiServer = async () => {
561
510
  };
562
511
  }
563
512
  const launchSpec = await resolveServerLaunchSpec();
564
- // 后台 daemon 必须脱离当前 CLI 生命周期运行,否则 watch/start 等命令一结束就会把宿主一并带死。
513
+ // The background daemon must run detached from the CLI lifecycle; otherwise commands like watch/start would kill the host when they exit.
565
514
  const child = (0, node_child_process_1.spawn)(launchSpec.command, launchSpec.args, {
566
515
  cwd: launchSpec.cwd,
567
516
  detached: true,
@@ -745,36 +694,46 @@ const createServerLifecycleClient = () => ({
745
694
  stopServer: stopLocalApiServer,
746
695
  });
747
696
  exports.createServerLifecycleClient = createServerLifecycleClient;
748
- const createPipelineRuntimeApiClient = () => ({
749
- ensureServerReady,
750
- startPipeline: async (pipelineId) => requestJson(buildPipelineApiUrl(pipelineId, "/run"), {
751
- method: "POST",
752
- }),
753
- getPipelineStatus: async (selector) => {
754
- const resolved = resolveRuntimePipelineSelector(selector);
755
- return requestJson(buildPipelineApiUrlWithIdentity(resolved.pipelineId, "/status", resolved.target));
756
- },
757
- stopPipeline: async (selector) => {
758
- const resolved = resolveRuntimePipelineSelector(selector);
759
- return requestJson(buildPipelineApiUrlWithIdentity(resolved.pipelineId, "/stop", resolved.target), {
760
- method: "POST",
761
- });
762
- },
763
- waitForPipelineWatchSignal,
764
- diagnoseNode: async (pipelineId, nodeId, itemKey) => {
765
- const url = new URL(buildPipelineApiUrl(pipelineId, `/nodes/${encodeURIComponent(nodeId)}/diagnostics`));
766
- if (itemKey)
767
- url.searchParams.set("itemKey", itemKey);
768
- return requestJson(url.toString());
769
- },
770
- getOutput: async (pipelineId, runId) => {
771
- const url = new URL(buildPipelineApiUrl(pipelineId, "/outputs"));
772
- if (runId)
773
- url.searchParams.set("runId", runId);
774
- return requestJson(url.toString());
775
- },
776
- listOutputs: async (pipelineId) => requestJson(buildPipelineApiUrl(pipelineId, "/outputs")),
777
- listLinks: async () => requestJson("/api/pipeline-links"),
778
- getQueue: async (pipelineId) => requestJson(buildPipelineApiUrl(pipelineId, "/queue")),
779
- });
780
- exports.createPipelineRuntimeApiClient = createPipelineRuntimeApiClient;
697
+ const createPipelineRuntimeApiClientWs = () => {
698
+ const wsUrl = `${buildWsBaseUrl()}/api/ws`;
699
+ const wsClient = (0, ws_runtime_client_1.createWsRuntimeClient)(wsUrl);
700
+ return {
701
+ ensureServerReady,
702
+ startPipeline: async (pipelineId) => wsClient.sendReq("pipeline.run", { pipelineId }),
703
+ getPipelineStatus: async (selector) => {
704
+ const resolved = (0, exports.resolveRuntimePipelineSelector)(selector);
705
+ const params = { pipelineId: resolved.pipelineId };
706
+ if (resolved.target?.runId)
707
+ params.runId = resolved.target.runId;
708
+ if (resolved.target?.batchRunId)
709
+ params.batchRunId = resolved.target.batchRunId;
710
+ return wsClient.sendReq("pipeline.status", params);
711
+ },
712
+ stopPipeline: async (selector) => {
713
+ const resolved = (0, exports.resolveRuntimePipelineSelector)(selector);
714
+ const params = { pipelineId: resolved.pipelineId };
715
+ if (resolved.target?.runId)
716
+ params.runId = resolved.target.runId;
717
+ if (resolved.target?.batchRunId)
718
+ params.batchRunId = resolved.target.batchRunId;
719
+ return wsClient.sendReq("pipeline.stop", params);
720
+ },
721
+ waitForPipelineWatchSignal,
722
+ diagnoseNode: async (pipelineId, nodeId, itemKey) => {
723
+ const params = { pipelineId, nodeId };
724
+ if (itemKey)
725
+ params.itemKey = itemKey;
726
+ return wsClient.sendReq("pipeline.node.diagnostics", params);
727
+ },
728
+ getOutput: async (pipelineId, runId) => {
729
+ const params = { pipelineId };
730
+ if (runId)
731
+ params.runId = runId;
732
+ return wsClient.sendReq("pipeline.output.list", params);
733
+ },
734
+ listOutputs: async (pipelineId) => wsClient.sendReq("pipeline.output.list", { pipelineId }),
735
+ listLinks: async () => wsClient.sendReq("pipeline.link.list"),
736
+ getQueue: async (pipelineId) => wsClient.sendReq("pipeline.queue.list", { pipelineId }),
737
+ };
738
+ };
739
+ exports.createPipelineRuntimeApiClientWs = createPipelineRuntimeApiClientWs;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectPrompt = exports.fieldPrompt = exports.hr = void 0;
4
+ const c = {
5
+ reset: "\x1b[0m",
6
+ bold: "\x1b[1m",
7
+ dim: "\x1b[2m",
8
+ cyan: "\x1b[36m",
9
+ green: "\x1b[32m",
10
+ white: "\x1b[37m",
11
+ black: "\x1b[30m",
12
+ bgCyan: "\x1b[46m",
13
+ bgGreen: "\x1b[42m",
14
+ };
15
+ /** Render a horizontal rule line */
16
+ const hr = () => {
17
+ process.stdout.write(`\n ${c.dim}${"─".repeat(50)}${c.reset}\n\n`);
18
+ };
19
+ exports.hr = hr;
20
+ /** Simple text-input prompt using readline */
21
+ const fieldPrompt = (rl, label, hint, prefill) => {
22
+ return new Promise((resolve) => {
23
+ process.stdout.write(` ${c.bold}${c.white}${label}${c.reset}\n`);
24
+ process.stdout.write(` ${c.dim}${hint}${c.reset}\n\n`);
25
+ rl.question(` ${c.green}${c.bold}>${c.reset} `, (answer) => {
26
+ resolve(answer.trim());
27
+ });
28
+ if (prefill) {
29
+ rl.write(prefill);
30
+ }
31
+ });
32
+ };
33
+ exports.fieldPrompt = fieldPrompt;
34
+ /** Interactive arrow-key select prompt using raw TTY mode */
35
+ const selectPrompt = (label, options) => {
36
+ return new Promise((resolve) => {
37
+ let selected = 0;
38
+ process.stdout.write(` ${c.bold}${c.white}${label}${c.reset}\n\n`);
39
+ const render = () => {
40
+ for (let i = 0; i < options.length; i++) {
41
+ const opt = options[i];
42
+ if (i === selected) {
43
+ process.stdout.write(` ${c.bgCyan}${c.black}${c.bold} ${opt.label} ${c.reset}\n`);
44
+ }
45
+ else {
46
+ process.stdout.write(` ${c.dim}${opt.label}${c.reset}\n`);
47
+ }
48
+ }
49
+ };
50
+ render();
51
+ const onData = (key) => {
52
+ const str = key.toString();
53
+ // up arrow or k
54
+ if (str === "\x1b[A" || str === "k") {
55
+ selected = selected > 0 ? selected - 1 : options.length - 1;
56
+ // down arrow or j
57
+ }
58
+ else if (str === "\x1b[B" || str === "j") {
59
+ selected = selected < options.length - 1 ? selected + 1 : 0;
60
+ // enter
61
+ }
62
+ else if (str === "\r" || str === "\n") {
63
+ cleanup();
64
+ process.stdout.write("\n");
65
+ resolve(options[selected].value);
66
+ return;
67
+ // ctrl+c
68
+ }
69
+ else if (str === "\x03") {
70
+ cleanup();
71
+ process.stdout.write("\n");
72
+ process.exit(0);
73
+ }
74
+ else {
75
+ return;
76
+ }
77
+ // Move cursor up to re-render
78
+ process.stdout.write(`\x1b[${options.length}A`);
79
+ process.stdout.write("\x1b[J");
80
+ render();
81
+ };
82
+ const cleanup = () => {
83
+ process.stdin.removeListener("data", onData);
84
+ if (process.stdin.isTTY) {
85
+ process.stdin.setRawMode(false);
86
+ }
87
+ process.stdin.pause();
88
+ };
89
+ if (process.stdin.isTTY) {
90
+ process.stdin.setRawMode(true);
91
+ }
92
+ process.stdin.resume();
93
+ process.stdin.on("data", onData);
94
+ });
95
+ };
96
+ exports.selectPrompt = selectPrompt;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createWsRuntimeClient = void 0;
7
+ const ws_1 = __importDefault(require("ws"));
8
+ const createWsRuntimeClient = (wsUrl) => {
9
+ let socket;
10
+ let ready = false;
11
+ const pending = new Map();
12
+ let reqCounter = 0;
13
+ const connect = (timeoutMs = 10_000) => {
14
+ return new Promise((resolve, reject) => {
15
+ socket = new ws_1.default(wsUrl);
16
+ const timeout = setTimeout(() => reject(new Error("ws_connect_timeout")), timeoutMs);
17
+ socket.on("open", () => { clearTimeout(timeout); });
18
+ socket.on("message", (raw) => {
19
+ const frame = JSON.parse(raw.toString());
20
+ if (frame.type === "bootstrap") {
21
+ ready = true;
22
+ resolve();
23
+ return;
24
+ }
25
+ if (frame.type === "res" && frame.id && pending.has(frame.id)) {
26
+ const entry = pending.get(frame.id);
27
+ clearTimeout(entry.timer);
28
+ pending.delete(frame.id);
29
+ if (frame.ok)
30
+ entry.resolve(frame.payload);
31
+ else
32
+ entry.reject(new Error(String(frame.error ?? "request_failed")));
33
+ }
34
+ });
35
+ socket.on("error", (err) => { clearTimeout(timeout); reject(err); });
36
+ });
37
+ };
38
+ const sendReq = async (method, params = {}, timeoutMs = 15_000) => {
39
+ if (!ready)
40
+ await connect();
41
+ const id = `cli-${++reqCounter}`;
42
+ return new Promise((resolve, reject) => {
43
+ const timer = setTimeout(() => { pending.delete(id); reject(new Error(`timeout:${method}`)); }, timeoutMs);
44
+ pending.set(id, { resolve: resolve, reject, timer });
45
+ socket.send(JSON.stringify({ type: "req", id, method, params }));
46
+ });
47
+ };
48
+ const close = () => { socket?.close(); };
49
+ return { sendReq, close, connect };
50
+ };
51
+ exports.createWsRuntimeClient = createWsRuntimeClient;
@@ -337,7 +337,7 @@ const createGatewayClient = (options) => {
337
337
  const openSocket = async () => {
338
338
  clearTimer(reconnectTimer);
339
339
  reconnectTimer = null;
340
- // S7 fix: socket 必须显式解绑 listener + close,避免重连时旧实例和监听器闭包滞留
340
+ // S7 fix: the old socket must have its listeners explicitly removed and be closed, to prevent stale instances and listener closures from lingering during reconnects.
341
341
  if (socket) {
342
342
  try {
343
343
  socket.removeAllListeners();
@@ -380,9 +380,9 @@ const createGatewayClient = (options) => {
380
380
  updateStatus(isManualClose ? "closed" : "failed_transport", `closed:${code}`);
381
381
  }
382
382
  options.onClose?.(code, reason.toString());
383
- // 握手阶段如果已经判定为鉴权/协议错误,close 只是前面主动断开的收尾动作。
384
- // 这里不能再按 failed_transport 继续重连,否则 pairing required / scope-upgrade
385
- // 会触发本地无限重连,持续打 127.0.0.1:18789
383
+ // If auth/protocol errors were already determined during the handshake phase, the close is just the tail-end of a preceding proactive disconnect.
384
+ // We must not continue reconnect as failed_transport here, otherwise pairing required / scope-upgrade
385
+ // would trigger infinite local reconnects, repeatedly hitting 127.0.0.1:18789.
386
386
  if (!isManualClose && status !== "failed_auth" && status !== "failed_protocol") {
387
387
  scheduleReconnect("failed_transport", `closed:${code}`);
388
388
  }
package/dist/src/index.js CHANGED
@@ -10,9 +10,14 @@ const gateway_1 = require("./gateway");
10
10
  Object.defineProperty(exports, "createGatewayClient", { enumerable: true, get: function () { return gateway_1.createGatewayClient; } });
11
11
  const create_app_context_1 = require("./app/create-app-context");
12
12
  const user_config_1 = require("./app/user-config");
13
- const api_handler_1 = require("./server/api-handler");
13
+ const http_handler_1 = require("./server/http-handler");
14
14
  const ws_broker_1 = require("./transport/ws-broker");
15
+ const ws_handler_1 = require("./transport/ws-handler");
16
+ const register_all_1 = require("./transport/ws-methods/register-all");
15
17
  const data_dir_1 = require("./app/data-dir");
18
+ const pipeline_service_1 = require("./services/pipeline-service");
19
+ const scheduler_service_1 = require("./services/scheduler-service");
20
+ const run_log_service_1 = require("./logs/run-log-service");
16
21
  const serverRuntimeIdentity = {
17
22
  serverId: (0, node_crypto_1.randomUUID)(),
18
23
  startedAt: new Date().toISOString(),
@@ -54,7 +59,27 @@ if (require.main === module) {
54
59
  console.error("gateway-error", error);
55
60
  },
56
61
  });
57
- const apiServer = (0, node_http_1.createServer)((0, api_handler_1.createApiHandler)({
62
+ // Create shared services (used by both HTTP routes and WS handler)
63
+ const pipelineService = (0, pipeline_service_1.createPipelineService)(app);
64
+ const schedulerService = (0, scheduler_service_1.createSchedulerService)(app);
65
+ const runLogService = (0, run_log_service_1.createRunLogService)({
66
+ rootDir: (0, data_dir_1.resolveTaskMeldDataPath)("logs", "runs"),
67
+ });
68
+ const wsHandler = (0, ws_handler_1.createWsRequestHandler)(app, {
69
+ pipelineService,
70
+ schedulerService,
71
+ runLogService,
72
+ client: app.gateway.client,
73
+ getLatestStatus: app.gateway.getLatestStatus,
74
+ getLatestHello: app.gateway.getLatestHello,
75
+ getLastFrame: app.gateway.getLastFrame,
76
+ getTimeline: app.runtime.getCombinedTimeline,
77
+ pickArray: app.gateway.pickArray,
78
+ refreshSessionsFromGateway: app.gateway.refreshSessionsFromGateway,
79
+ getSessionCache: app.gateway.getSessionCache,
80
+ });
81
+ (0, register_all_1.registerAllWsMethods)(wsHandler.registry);
82
+ const apiServer = (0, node_http_1.createServer)((0, http_handler_1.createApiHandler)({
58
83
  apiPort: appContext.api.port,
59
84
  webOrigin: appContext.api.webOrigin,
60
85
  app,
@@ -70,6 +95,7 @@ if (require.main === module) {
70
95
  server: apiServer,
71
96
  path: "/api/ws",
72
97
  getBootstrapPayload: app.getBootstrapPayload,
98
+ handleRequest: wsHandler.handleMessage,
73
99
  });
74
100
  app.runtime.setBroadcast(wsBroker.broadcast);
75
101
  void appContext.initialize();
@@ -7,7 +7,7 @@ const guards_1 = require("../utils/guards");
7
7
  const MAX_LIMIT = 300;
8
8
  const isRunLogLevel = (value) => value === "info" || value === "warn" || value === "error";
9
9
  const normalizeLimit = (value) => {
10
- // 服务端必须兜底分页,避免前端漏传 limit 时把大型日志一次性读入 V8 堆。
10
+ // Server must enforce pagination as a safety net, to avoid loading huge logs into the V8 heap when the frontend omits the limit parameter.
11
11
  if (value === undefined)
12
12
  return MAX_LIMIT;
13
13
  if (!Number.isFinite(value))
@@ -67,10 +67,10 @@ const createAgentActivityTracker = (deps) => {
67
67
  stateVersion: frame.stateVersion ?? null,
68
68
  };
69
69
  if (lifecycle === "start") {
70
- deps.pushTimeline(`Agent ${agentId} 开始工作 (${runLabel(runId)}, ${source})`, "info", detail);
70
+ deps.pushTimeline(`Agent ${agentId} started (${runLabel(runId)}, ${source})`, "info", detail);
71
71
  return;
72
72
  }
73
- deps.pushTimeline(`Agent ${agentId} 结束工作 (${runLabel(runId)}, ${source})`, "info", detail);
73
+ deps.pushTimeline(`Agent ${agentId} finished (${runLabel(runId)}, ${source})`, "info", detail);
74
74
  };
75
75
  const refreshAgentActivity = (agentId, source, runId, frame) => {
76
76
  const activityKey = toActivityKey(agentId, runId);
@@ -8,8 +8,8 @@ const artifact_index_1 = require("../artifacts/artifact-index");
8
8
  const pad2 = (value) => String(value).padStart(2, "0");
9
9
  const formatLocalDateBucket = (at) => `${at.getFullYear()}-${pad2(at.getMonth() + 1)}-${pad2(at.getDate())}`;
10
10
  const buildArtifactStorageDirs = (rootDir, runId, status, savedAt = new Date(), batchRunId) => {
11
- // 运行产物需要按"结果状态/日期/runId"归档,避免目录长期平铺后难以按日排障或批量清理。
12
- // envelopes artifacts 分桶存放,是为了把节点回执和实际产物内容分开,减少误读与误删风险。
11
+ // Run artifacts are archived by "result status / date / runId" so flat directories don't become hard to troubleshoot by date or batch-clean.
12
+ // Envelopes and artifacts are bucketed separately to keep node receipts apart from actual artifact content, reducing misread/misdelete risk.
13
13
  const dateBucket = formatLocalDateBucket(savedAt);
14
14
  const runDir = batchRunId
15
15
  ? (0, node_path_1.join)(rootDir, status, dateBucket, batchRunId, runId)
@@ -31,14 +31,14 @@ const sanitizeFileSegment = (value) => {
31
31
  return normalized || "unnamed";
32
32
  };
33
33
  /**
34
- * 产物文件统一写入入口。
35
- * 负责:目录创建、原子写入(临时文件 + rename)、真实 SHA-256 哈希计算、索引追加、ArtifactManifest 构造。
34
+ * Unified artifact file write entry point.
35
+ * Responsibilities: directory creation, atomic write (temp file + rename), real SHA-256 hash computation, index append, ArtifactManifest construction.
36
36
  */
37
37
  const persistArtifactFile = async (rootDir, status, ctx, artifact, opts) => {
38
38
  const savedAt = opts?.savedAt ?? new Date();
39
39
  const persistDirs = (0, exports.buildArtifactStorageDirs)(rootDir, ctx.runId, status, savedAt, ctx.batchRunId);
40
40
  await (0, promises_1.mkdir)(persistDirs.artifactsDir, { recursive: true });
41
- // 统一的文件序列化格式,kind 字段供读取端分发
41
+ // Unified file serialization format; the kind field is used by readers to dispatch
42
42
  const fileContent = {
43
43
  schemaVersion: 1,
44
44
  runId: ctx.runId,
@@ -55,7 +55,7 @@ const persistArtifactFile = async (rootDir, status, ctx, artifact, opts) => {
55
55
  if (opts?.fileNameSuffix)
56
56
  fileNameSegments.push(sanitizeFileSegment(opts.fileNameSuffix));
57
57
  const fileName = `${fileNameSegments.join("-")}.json`;
58
- // 原子写入: 先写临时文件,计算 hash,再 rename 到最终路径
58
+ // Atomic write: write to temp file first, compute hash, then rename to final path
59
59
  const tmpFileName = `.tmp-${(0, node_crypto_1.randomUUID)()}.json`;
60
60
  const tmpPath = (0, node_path_1.join)(persistDirs.artifactsDir, tmpFileName);
61
61
  const json = JSON.stringify(fileContent, null, 2);
@@ -68,7 +68,7 @@ const persistArtifactFile = async (rootDir, status, ctx, artifact, opts) => {
68
68
  const createdAt = new Date().toISOString();
69
69
  const updatedAt = fileStat.mtime.toISOString();
70
70
  const relativePath = (0, node_path_1.relative)(rootDir, finalPath).replaceAll("\\", "/");
71
- // 追加索引记录(best-effort,失败不阻断产物写入)
71
+ // Append index record (best-effort; a failure here must not block the artifact write)
72
72
  const pipelineId = ctx.pipelineId;
73
73
  const indexRecord = {
74
74
  schemaVersion: 1,
@@ -92,7 +92,7 @@ const persistArtifactFile = async (rootDir, status, ctx, artifact, opts) => {
92
92
  createdAt,
93
93
  updatedAt,
94
94
  };
95
- // 追加索引记录:文件已通过 tmp+rename 原子写入磁盘,索引追加失败不丢失产物文件
95
+ // Append index record: the file is already atomically written (tmp+rename); index append failure does not lose the artifact file
96
96
  const _ir = await (0, artifact_index_1.appendIndexRecord)(rootDir, indexRecord);
97
97
  void _ir;
98
98
  return {
@@ -107,7 +107,7 @@ const persistArtifactFile = async (rootDir, status, ctx, artifact, opts) => {
107
107
  };
108
108
  };
109
109
  exports.persistArtifactFile = persistArtifactFile;
110
- /** 产物移动(如 rejected 归档)后补记索引,使查询端通过 artifactId 去重拿到最新位置。 */
110
+ /** After moving an artifact (e.g. rejected archiving), append an index record so queries can use artifactId deduplication to get the latest location. */
111
111
  const appendMovedArtifactIndexRecord = async (rootDir, input) => {
112
112
  const targetPath = (0, node_path_1.resolve)(rootDir, input.relativePath);
113
113
  const fileStat = await (0, promises_1.stat)(targetPath);
@@ -140,7 +140,7 @@ const appendMovedArtifactIndexRecord = async (rootDir, input) => {
140
140
  });
141
141
  };
142
142
  exports.appendMovedArtifactIndexRecord = appendMovedArtifactIndexRecord;
143
- /** 保存 envelope 回执文件到 envelopes 子目录并追加索引,使 kind=envelope 可在列表中查询。 */
143
+ /** Save envelope receipt file to the envelopes sub-directory and append index, so kind=envelope is queryable in the list. */
144
144
  const persistEnvelopeFile = async (rootDir, status, ctx, envelope, opts) => {
145
145
  const savedAt = opts?.savedAt ?? new Date();
146
146
  const persistDirs = (0, exports.buildArtifactStorageDirs)(rootDir, ctx.runId, status, savedAt, ctx.batchRunId);
@@ -191,7 +191,7 @@ const persistEnvelopeFile = async (rootDir, status, ctx, envelope, opts) => {
191
191
  createdAt,
192
192
  updatedAt,
193
193
  };
194
- // 追加索引记录:文件已通过 tmp+rename 原子写入磁盘,索引追加失败不丢失产物文件
194
+ // Append index record: the file is already atomically written (tmp+rename); index append failure does not lose the artifact file
195
195
  const _ir = await (0, artifact_index_1.appendIndexRecord)(rootDir, indexRecord);
196
196
  void _ir;
197
197
  return {
@@ -3,17 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.diagnoseNodeDependency = exports.REASON_MESSAGES = void 0;
4
4
  const dependency_check_1 = require("../execution/dependency-check");
5
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: "缺少上游并行组条目运行记录",
6
+ dependency_satisfied: "Dependency satisfied",
7
+ source_not_success: "Upstream node not successful (dependency not met)",
8
+ source_failed: "Upstream node execution failed",
9
+ source_skipped: "Upstream node was skipped",
10
+ route_mismatch: "Route mismatch (upstream route value does not match edge requirement)",
11
+ cross_branch_edge_blocked: "Cross-branch edge is blocked",
12
+ group_not_success: "Upstream parallel group not successful",
13
+ source_disabled_dependency_satisfied: "Upstream node is disabled (dependency treated as satisfied)",
14
+ source_disabled_route_impossible: "Upstream node is disabled but route edge cannot be satisfied",
15
+ missing_source_item_run: "Missing upstream node item run record",
16
+ missing_group_item_run: "Missing upstream parallel group item run record",
17
17
  };
18
18
  const resolveOutcome = (satisfiedCount, impossibleCount, totalIncoming, policy) => {
19
19
  if (policy === "any") {
@@ -43,8 +43,8 @@ const createPipelineInboundQueue = () => {
43
43
  catch {
44
44
  // File not found, start empty
45
45
  }
46
- // 重放后自动回收"running"状态的 job:这些 job 属于上次进程崩溃前已启动但未完成的任务,
47
- // 恢复为 pending 并清空 targetRunId,让 drainer 重新调度执行。
46
+ // After replay, auto-reclaim jobs stuck in "running" status: these jobs were started but not completed before the last process crash,
47
+ // restore them to pending and clear targetRunId so the drainer can re-schedule execution.
48
48
  for (const [jobId, job] of newSnapshot) {
49
49
  if (job.status === "running") {
50
50
  newSnapshot.set(jobId, {
@@ -110,7 +110,7 @@ const createGroupItemExecutor = (deps) => {
110
110
  }
111
111
  (0, state_1.markGroupItemRunning)(item, ctx("group_start"));
112
112
  (0, state_1.markGroupRunning)(runGroup, ctx("group_start"));
113
- deps.runtimeStore.pushTimeline(`并行组执行已触发: ${group.id}#${item.itemKey}`);
113
+ deps.runtimeStore.pushTimeline(`Parallel group execution triggered: ${group.id}#${item.itemKey}`);
114
114
  deps.runtimeStore.emitPipeline();
115
115
  const { results, memberItems } = await executeGroupMembers(item, group);
116
116
  if (results.some((result) => !result.ok)) {
@@ -19,7 +19,7 @@ const createNodeItemExecutor = (deps) => {
19
19
  const maxAttempts = Math.max(1, workflowNode?.retryPolicy.maxAttempts ?? 1);
20
20
  if (item.attempt >= maxAttempts) {
21
21
  (0, state_1.markItemFailed)(item, ctx("max_attempts_exceeded", { error: `max_attempts_exceeded:${maxAttempts}` }));
22
- deps.runtimeStore.pushTimeline(`节点项超过重试上限: ${item.nodeId}#${item.itemKey} (${maxAttempts})`, "error");
22
+ deps.runtimeStore.pushTimeline(`Node item exceeded max retry attempts: ${item.nodeId}#${item.itemKey} (${maxAttempts})`, "error");
23
23
  deps.runtimeStore.emitPipeline();
24
24
  return { ok: false, error: item.lastError ?? undefined, finalStatus: "failed" };
25
25
  }
@@ -28,7 +28,7 @@ const createNodeItemExecutor = (deps) => {
28
28
  await new Promise(resolve => setTimeout(resolve, retryBackoffMs));
29
29
  }
30
30
  (0, state_1.markItemRunning)(item, ctx("exec_start"));
31
- deps.runtimeStore.pushTimeline(`节点项执行已触发: ${item.nodeId}#${item.itemKey}`);
31
+ deps.runtimeStore.pushTimeline(`Node item execution triggered: ${item.nodeId}#${item.itemKey}`);
32
32
  deps.runtimeStore.emitPipeline();
33
33
  const exec = await deps.nodeRunner.executeNode(node, {
34
34
  itemKey: item.itemKey,
@@ -36,7 +36,7 @@ const createNodeItemExecutor = (deps) => {
36
36
  });
37
37
  if (!exec.ok) {
38
38
  if (exec.finalStatus === "stopped") {
39
- // 用户中止时节点项已被标记为 stopped,不覆盖
39
+ // When the user aborts, the node item is already marked as stopped — do not overwrite
40
40
  }
41
41
  else if (exec.finalStatus === "rejected") {
42
42
  (0, state_1.markItemRejected)(item, ctx("node_rejected", { error: exec.error ?? null }));