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.
- package/README.md +176 -176
- package/README.zh-CN.md +176 -176
- package/dist/src/app/app-context-env.js +1 -1
- package/dist/src/app/create-app-context.js +3 -3
- package/dist/src/app/data-dir.js +13 -3
- package/dist/src/app/pipeline-config.js +4 -4
- package/dist/src/app/pipeline-registry.js +11 -11
- package/dist/src/app/pipeline-runtime.js +6 -9
- package/dist/src/app/runtime-store.js +3 -3
- package/dist/src/artifacts/artifact-cleanup.js +17 -17
- package/dist/src/artifacts/artifact-index.js +14 -14
- package/dist/src/artifacts/artifact-rebuilder.js +3 -3
- package/dist/src/artifacts/storage-service.js +18 -18
- package/dist/src/cli/bootstrap.js +7 -7
- package/dist/src/cli/commands/agent.js +12 -11
- package/dist/src/cli/commands/artifact.js +31 -30
- package/dist/src/cli/commands/init.js +49 -47
- package/dist/src/cli/commands/pipeline/result.js +9 -8
- package/dist/src/cli/commands/pipeline/selector.js +1 -1
- package/dist/src/cli/commands/pipeline/watch.js +2 -2
- package/dist/src/cli/commands/pipeline.js +54 -53
- package/dist/src/cli/commands/scheduler.js +9 -8
- package/dist/src/cli/commands/server.js +12 -11
- package/dist/src/cli/commands/system.js +4 -3
- package/dist/src/cli/errors.js +2 -2
- package/dist/src/cli/help.js +18 -17
- package/dist/src/cli/i18n.js +46 -0
- package/dist/src/cli/locales/en.json +244 -0
- package/dist/src/cli/locales/zh.json +244 -0
- package/dist/src/cli/output.js +3 -3
- package/dist/src/cli/renderers/engine/markdown.js +1 -1
- package/dist/src/cli/renderers/specs/index.js +1 -1
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/server-runtime-client.js +54 -95
- package/dist/src/cli/ui-prompts.js +96 -0
- package/dist/src/cli/ws-runtime-client.js +51 -0
- package/dist/src/gateway/gateway-client.js +4 -4
- package/dist/src/index.js +28 -2
- package/dist/src/logs/run-log-reader.js +1 -1
- package/dist/src/pipeline/agent-activity.js +2 -2
- package/dist/src/pipeline/artifact-storage.js +11 -11
- package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +11 -11
- package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +2 -2
- package/dist/src/pipeline/execution/group-item-executor.js +1 -1
- package/dist/src/pipeline/execution/node-item-executor.js +3 -3
- package/dist/src/pipeline/execution/node-runner.js +7 -7
- package/dist/src/pipeline/execution/readiness-state.js +1 -1
- package/dist/src/pipeline/execution/reject-handler.js +5 -5
- package/dist/src/pipeline/execution/rejected-artifact-archiver.js +1 -1
- package/dist/src/pipeline/execution/route-item-manager.js +4 -4
- package/dist/src/pipeline/execution/run-abort-controller.js +5 -5
- package/dist/src/pipeline/execution/run-state-helpers.js +2 -2
- package/dist/src/pipeline/execution/service.js +4 -4
- package/dist/src/pipeline/execution/structured-node-runner.js +24 -24
- package/dist/src/pipeline/execution-timeout.js +3 -3
- package/dist/src/pipeline/identity/index.js +3 -3
- package/dist/src/pipeline/item-batch-controller.js +6 -6
- package/dist/src/pipeline/scheduler/dependency-state.js +5 -5
- package/dist/src/pipeline/scheduler-service.js +24 -24
- package/dist/src/pipeline/state-machine.js +2 -2
- package/dist/src/pipeline/structured-output/contract.js +4 -4
- package/dist/src/pipeline/structured-output/index.js +2 -2
- package/dist/src/pipeline/structured-output/parser.js +5 -5
- package/dist/src/pipeline/structured-output/prompt.js +38 -38
- package/dist/src/pipeline/structured-output/waiter.js +6 -6
- package/dist/src/pipeline/template.js +5 -5
- package/dist/src/pipeline/timeline-log-store.js +5 -5
- package/dist/src/pipeline/tool-activity.js +3 -3
- package/dist/src/pipeline/types/pipeline-output.js +1 -1
- package/dist/src/pipeline/workflow/branch-rules.js +19 -19
- package/dist/src/pipeline/workflow/io.js +1 -1
- package/dist/src/pipeline/workflow/normalize.js +18 -18
- package/dist/src/pipeline/workflow/template-mapper.js +3 -3
- package/dist/src/pipeline/workflow/validate.js +39 -39
- package/dist/src/pipeline/workflow-graph.js +10 -10
- package/dist/src/server/http-handler.js +74 -0
- package/dist/src/services/agent-service.js +2 -2
- package/dist/src/services/gateway-read-helpers.js +1 -1
- package/dist/src/services/pipeline-service.js +19 -19
- package/dist/src/services/pipeline-status.js +4 -4
- package/dist/src/services/read-services.js +1 -1
- package/dist/src/services/session-service.js +6 -6
- package/dist/src/services/system-service.js +1 -1
- package/dist/src/transport/ws-broker.js +12 -1
- package/dist/src/transport/ws-handler.js +60 -0
- package/dist/src/transport/ws-methods/agents.js +144 -0
- package/dist/src/transport/ws-methods/artifacts.js +171 -0
- package/dist/src/transport/ws-methods/gateway.js +16 -0
- package/dist/src/transport/ws-methods/logs.js +43 -0
- package/dist/src/transport/ws-methods/pipeline-batch.js +68 -0
- package/dist/src/transport/ws-methods/pipeline-links.js +100 -0
- package/dist/src/transport/ws-methods/pipeline-queue.js +51 -0
- package/dist/src/transport/ws-methods/pipeline-runtime.js +151 -0
- package/dist/src/transport/ws-methods/pipeline-scheduler.js +48 -0
- package/dist/src/transport/ws-methods/pipeline-workflow.js +127 -0
- package/dist/src/transport/ws-methods/pipelines.js +56 -0
- package/dist/src/transport/ws-methods/register-all.js +32 -0
- package/dist/src/transport/ws-methods/sessions.js +154 -0
- package/dist/src/transport/ws-methods/timeline.js +10 -0
- package/dist/src/{server/routes/pipeline-identity.js → transport/ws-methods/utils.js} +14 -9
- package/dist/src/version.js +1 -1
- package/package.json +15 -7
- package/web/dist/assets/agent-DP6TMcLj.js +1 -0
- package/web/dist/assets/agent-DmJHzLyj.js +1 -0
- package/web/dist/assets/artifact-BqnoZy2M.js +1 -0
- package/web/dist/assets/artifact-DfDkgkno.js +1 -0
- package/web/dist/assets/common-DRMTVwE9.js +1 -0
- package/web/dist/assets/common-DeXccbr2.js +1 -0
- package/web/dist/assets/dispatch-CBskGCQI.js +1 -0
- package/web/dist/assets/dispatch-sk4Wp30e.js +1 -0
- package/web/dist/assets/index-C8wTjZvH.css +1 -0
- package/web/dist/assets/index-DYDQZRLk.js +58 -0
- package/web/dist/assets/log-DN8cjb0w.js +1 -0
- package/web/dist/assets/log-HSeA_dYy.js +1 -0
- package/web/dist/assets/modal-BdNai9jf.js +1 -0
- package/web/dist/assets/modal-D9_KDpFD.js +1 -0
- package/web/dist/assets/nav-BmF7oAKg.js +1 -0
- package/web/dist/assets/nav-IjC2xqXQ.js +1 -0
- package/web/dist/assets/node-detail-CENRXcrh.js +1 -0
- package/web/dist/assets/node-detail-bndPr0IM.js +1 -0
- package/web/dist/assets/overview-B87zWAxq.js +1 -0
- package/web/dist/assets/overview-gQvk-NOK.js +1 -0
- package/web/dist/assets/pipeline-D4dSJRDz.js +1 -0
- package/web/dist/assets/pipeline-DZzyOqQa.js +1 -0
- package/web/dist/assets/session-CUWvU14v.js +5 -0
- package/web/dist/assets/session-DQ6UuCaJ.js +5 -0
- package/web/dist/assets/timeline-8y_2_0Em.js +1 -0
- package/web/dist/assets/timeline-CAPsXUTC.js +1 -0
- package/web/dist/index.html +3 -3
- package/dist/src/app/pipeline-plugin-config.js +0 -2
- package/dist/src/server/api-handler.js +0 -163
- package/dist/src/server/http-utils.js +0 -34
- package/dist/src/server/middleware.js +0 -61
- package/dist/src/server/router.js +0 -105
- package/dist/src/server/routes/agents.js +0 -189
- package/dist/src/server/routes/artifacts.js +0 -163
- package/dist/src/server/routes/gateway.js +0 -18
- package/dist/src/server/routes/health.js +0 -16
- package/dist/src/server/routes/logs.js +0 -73
- package/dist/src/server/routes/pipeline-batch.js +0 -163
- package/dist/src/server/routes/pipeline-diagnostics.js +0 -33
- package/dist/src/server/routes/pipeline-links.js +0 -117
- package/dist/src/server/routes/pipeline-outputs.js +0 -27
- package/dist/src/server/routes/pipeline-queue.js +0 -62
- package/dist/src/server/routes/pipeline-runtime.js +0 -162
- package/dist/src/server/routes/pipeline-scheduler.js +0 -69
- package/dist/src/server/routes/pipeline-workflow.js +0 -180
- package/dist/src/server/routes/pipelines.js +0 -96
- package/dist/src/server/routes/sessions.js +0 -244
- package/dist/src/server/routes/timeline.js +0 -14
- package/dist/src/server/serve-static.js +0 -42
- package/web/dist/assets/index-CWnfhkn-.js +0 -65
- package/web/dist/assets/index-gZ0xOfSO.css +0 -1
- /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.
|
|
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
|
|
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
|
|
108
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
|
368
|
-
//
|
|
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
|
-
//
|
|
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
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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:
|
|
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
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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}
|
|
70
|
+
deps.pushTimeline(`Agent ${agentId} started (${runLabel(runId)}, ${source})`, "info", detail);
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
|
-
deps.pushTimeline(`Agent ${agentId}
|
|
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
|
-
//
|
|
12
|
-
//
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
//
|
|
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
|
-
//
|
|
47
|
-
//
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
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 }));
|