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
|
@@ -41,7 +41,7 @@ const createNodeRunner = (deps) => {
|
|
|
41
41
|
node.rejectCount = 0;
|
|
42
42
|
node.rejectFeedbacks = [];
|
|
43
43
|
succeeded = true;
|
|
44
|
-
deps.runtimeStore.pushTimeline(
|
|
44
|
+
deps.runtimeStore.pushTimeline(`Node executed (structured): ${node.id} <- ${usedAgentId}`);
|
|
45
45
|
}
|
|
46
46
|
else if (envelopeErrorCode === "upstream_reject" && node.allowReject) {
|
|
47
47
|
node.artifacts = [];
|
|
@@ -72,7 +72,7 @@ const createNodeRunner = (deps) => {
|
|
|
72
72
|
finalStatus = "failed";
|
|
73
73
|
node.artifacts = result.artifacts;
|
|
74
74
|
execError = "upstream_reject_not_allowed: node_allowReject_false";
|
|
75
|
-
deps.runtimeStore.pushTimeline(
|
|
75
|
+
deps.runtimeStore.pushTimeline(`Node execution failed (structured): ${node.id} <- ${usedAgentId} upstream_reject_not_allowed: node_allowReject_false`, "error");
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
78
|
const structuredFailError = JSON.stringify(result.envelope.error ?? "node_failed");
|
|
@@ -84,14 +84,14 @@ const createNodeRunner = (deps) => {
|
|
|
84
84
|
const detailText = envelopeError.message
|
|
85
85
|
? `${envelopeError.code ? `${envelopeError.code}: ` : ""}${envelopeError.message}`
|
|
86
86
|
: envelopeError.code || "node_failed";
|
|
87
|
-
deps.runtimeStore.pushTimeline(
|
|
87
|
+
deps.runtimeStore.pushTimeline(`Node execution failed (structured): ${node.id} <- ${usedAgentId} ${detailText}`, "error");
|
|
88
88
|
}
|
|
89
89
|
return { succeeded, finalStatus, execError, haltPipeline };
|
|
90
90
|
};
|
|
91
91
|
const executeNode = async (node, opts) => {
|
|
92
92
|
const resolved = await deps.sessionRegistry.resolveExecutorSession(node);
|
|
93
93
|
if (!resolved) {
|
|
94
|
-
deps.runtimeStore.pushTimeline(
|
|
94
|
+
deps.runtimeStore.pushTimeline(`Node ${node.id} execution failed: executor session not found (${node.executor.agentId})`, "error");
|
|
95
95
|
(0, state_1.markNodeFailed)(node, ctx("executor_session_not_found", { error: "executor_session_not_found" }));
|
|
96
96
|
deps.runtimeStore.emitPipeline();
|
|
97
97
|
return {
|
|
@@ -105,7 +105,7 @@ const createNodeRunner = (deps) => {
|
|
|
105
105
|
const sessionId = resolved.sessionId;
|
|
106
106
|
const usedAgentId = resolved.agentId;
|
|
107
107
|
(0, state_1.markNodeRunning)(node, ctx("exec_start"));
|
|
108
|
-
deps.runtimeStore.pushTimeline(
|
|
108
|
+
deps.runtimeStore.pushTimeline(`Node execution triggered: ${node.id} -> ${usedAgentId}`);
|
|
109
109
|
deps.runtimeStore.emitPipeline();
|
|
110
110
|
const effectiveDependencyIds = opts?.dependencyIds?.length
|
|
111
111
|
? Array.from(new Set(opts.dependencyIds.map((id) => id.trim()).filter(Boolean)))
|
|
@@ -134,11 +134,11 @@ const createNodeRunner = (deps) => {
|
|
|
134
134
|
const classification = isAborted
|
|
135
135
|
? { reason: "pipeline_aborted", haltPipeline: true }
|
|
136
136
|
: classifyNodeFailure(error);
|
|
137
|
-
//
|
|
137
|
+
// When the user aborts, the node is already marked as stopped — should not be overwritten to failed
|
|
138
138
|
if (!isAborted) {
|
|
139
139
|
(0, state_1.markNodeFailed)(node, ctx(classification.reason, { error: String(error) }));
|
|
140
140
|
}
|
|
141
|
-
deps.runtimeStore.pushTimeline(
|
|
141
|
+
deps.runtimeStore.pushTimeline(`Node execution interrupted (structured): ${node.id} <- ${usedAgentId} ${String(error)}`, "warn");
|
|
142
142
|
deps.runtimeStore.emitPipeline();
|
|
143
143
|
return {
|
|
144
144
|
ok: false,
|
|
@@ -5,6 +5,6 @@ const isSleepWaitingState = (candidate) => candidate.status === "waiting" && typ
|
|
|
5
5
|
exports.isSleepWaitingState = isSleepWaitingState;
|
|
6
6
|
const canPromoteToQueuedByDependency = (candidate) => candidate.status === "blocked" ||
|
|
7
7
|
candidate.status === "skipped" ||
|
|
8
|
-
//
|
|
8
|
+
// Only "waiting" caused by unmet dependencies is allowed to be re-enqueued; sleep waiting must still wait until wakeAt expires.
|
|
9
9
|
(candidate.status === "waiting" && !(0, exports.isSleepWaitingState)(candidate));
|
|
10
10
|
exports.canPromoteToQueuedByDependency = canPromoteToQueuedByDependency;
|
|
@@ -50,7 +50,7 @@ const handleNodeReject = async (params) => {
|
|
|
50
50
|
node.rejectCount = (node.rejectCount ?? 0) + 1;
|
|
51
51
|
if (node.rejectCount > limit) {
|
|
52
52
|
(0, state_1.markNodeFailed)(node, ctx("reject_limit_exceeded", { error: JSON.stringify(envelope.error ?? "reject_limit_exceeded") }));
|
|
53
|
-
pushTimeline(
|
|
53
|
+
pushTimeline(`Node ${node.id} rejected ${limit} times, marked as failed`, "error");
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
const effectiveDependencyIds = dependencyIds?.length ? dependencyIds : node.dependsOn;
|
|
@@ -61,10 +61,10 @@ const handleNodeReject = async (params) => {
|
|
|
61
61
|
: defaultTarget
|
|
62
62
|
? [defaultTarget]
|
|
63
63
|
: [];
|
|
64
|
-
const rejectMessage = (0, exports.extractEnvelopeErrorMessage)(envelope.error) || "
|
|
64
|
+
const rejectMessage = (0, exports.extractEnvelopeErrorMessage)(envelope.error) || "Downstream validation failed, please correct and resubmit.";
|
|
65
65
|
if (rejectTargetIds.length === 0) {
|
|
66
66
|
(0, state_1.markNodeFailed)(node, ctx("reject_target_missing", { error: JSON.stringify(envelope.error ?? "reject_target_missing") }));
|
|
67
|
-
pushTimeline(
|
|
67
|
+
pushTimeline(`Node ${node.id} requested reject but no available upstream node found, marked as failed`, "error");
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
(0, state_1.markNodeRejected)(node, ctx("upstream_reject", { error: JSON.stringify(envelope.error ?? "upstream_reject") }));
|
|
@@ -72,7 +72,7 @@ const handleNodeReject = async (params) => {
|
|
|
72
72
|
const targetNode = nodes.find((current) => current.id === targetId);
|
|
73
73
|
if (!targetNode)
|
|
74
74
|
continue;
|
|
75
|
-
const feedback = `${node.id}(${node.title})
|
|
75
|
+
const feedback = `${node.id}(${node.title}) reject reason: ${rejectMessage}`;
|
|
76
76
|
targetNode.rejectFeedbacks = [...(targetNode.rejectFeedbacks ?? []), feedback].slice(-5);
|
|
77
77
|
const movedCount = await (0, rejected_artifact_archiver_1.archiveRejectedArtifacts)({
|
|
78
78
|
node: targetNode,
|
|
@@ -88,7 +88,7 @@ const handleNodeReject = async (params) => {
|
|
|
88
88
|
itemKey,
|
|
89
89
|
skipNodeIds: [node.id],
|
|
90
90
|
});
|
|
91
|
-
pushTimeline(
|
|
91
|
+
pushTimeline(`Node ${node.id} rejected ${targetNode.id}, reason: ${rejectMessage}; reset ${affectedNodeCount} nodes/${affectedGroupCount} parallel groups, archived ${movedCount} artifacts`, "warn");
|
|
92
92
|
}
|
|
93
93
|
};
|
|
94
94
|
exports.handleNodeReject = handleNodeReject;
|
|
@@ -37,7 +37,7 @@ const archiveRejectedArtifacts = async (params) => {
|
|
|
37
37
|
moved += 1;
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
|
-
pushTimeline(
|
|
40
|
+
pushTimeline(`Failed to archive rejected artifact (node ${node.id}): ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
return moved;
|
|
@@ -20,7 +20,7 @@ const normalizeAllowedRoute = (rawRoute, allowedRoutes) => {
|
|
|
20
20
|
if (direct)
|
|
21
21
|
return direct;
|
|
22
22
|
const lower = trimmed.toLowerCase();
|
|
23
|
-
//
|
|
23
|
+
// Consistent with structured validation: when case differs, use the workflow-declared value to avoid missed matches.
|
|
24
24
|
return allowedRoutes.find((route) => route.toLowerCase() === lower) ?? null;
|
|
25
25
|
};
|
|
26
26
|
const collectRouteBuckets = (content, allowedRoutes) => {
|
|
@@ -86,7 +86,7 @@ const createRouteItemManager = (options) => {
|
|
|
86
86
|
const outgoingEdges = options.graph.getOutgoingEdges(sourceItem.nodeId);
|
|
87
87
|
const isRouteNode = (options.graph.getWorkflowNodeById(sourceItem.nodeId)?.routePolicy?.allowed.length ?? 0) > 0;
|
|
88
88
|
const startTargets = outgoingEdges
|
|
89
|
-
//
|
|
89
|
+
// In routing nodes, yes is mainline semantics: only follow unconditional dependency edges; no/custom values only follow route edges.
|
|
90
90
|
.filter((edge) => (isRouteNode ? (route === routes_1.MAINLINE_ROUTE_VALUE ? edge.when === null : edge.when === route) : !edge.when))
|
|
91
91
|
.map((edge) => edge.to);
|
|
92
92
|
const reachable = options.state.collectReachableEntities(startTargets);
|
|
@@ -192,7 +192,7 @@ const createRouteItemManager = (options) => {
|
|
|
192
192
|
for (const edge of options.graph.getOutgoingEdges(item.nodeId)) {
|
|
193
193
|
if (bucket.route === routes_1.MAINLINE_ROUTE_VALUE)
|
|
194
194
|
continue;
|
|
195
|
-
//
|
|
195
|
+
// Routing nodes only advance along the matched route edge; unconditional edges are unconditionally ignored here to prevent implicit mainline passthrough.
|
|
196
196
|
if (edge.when !== bucket.route)
|
|
197
197
|
continue;
|
|
198
198
|
if (options.graph.isGroupId(edge.to)) {
|
|
@@ -211,7 +211,7 @@ const createRouteItemManager = (options) => {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
-
options.runtimeStore.pushTimeline(
|
|
214
|
+
options.runtimeStore.pushTimeline(`Route hit: ${item.nodeId}#${item.itemKey} -> ${bucket.route} (${bucket.count} items)`);
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
const sleepUntil = envelope.control?.sleepUntil;
|
|
@@ -25,9 +25,9 @@ const createRunAbortController = () => {
|
|
|
25
25
|
entry.ac.abort();
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* 1.
|
|
30
|
-
* 2.
|
|
28
|
+
* Abort all node executions for a given pipeline run.
|
|
29
|
+
* 1. Send "/stop" to each active node's remote agent session (fire-and-forget)
|
|
30
|
+
* 2. Trigger local AbortController to interrupt polling/drain loop
|
|
31
31
|
*/
|
|
32
32
|
const abortRunControllers = (runId, client) => {
|
|
33
33
|
const controllers = nodeExecutionControllers.get(runId);
|
|
@@ -50,8 +50,8 @@ const createRunAbortController = () => {
|
|
|
50
50
|
}
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
53
|
+
* Get or create the abort signal for drainPipeline.
|
|
54
|
+
* Each new run creates a new AbortController so stop/retry only interrupt the current run.
|
|
55
55
|
*/
|
|
56
56
|
const getOrCreateDrainSignal = (runId) => {
|
|
57
57
|
let dc = drainControllers.get(runId);
|
|
@@ -134,8 +134,8 @@ const createRunStateHelpers = (options) => {
|
|
|
134
134
|
if (options.graph.isGroupId(current)) {
|
|
135
135
|
groupIds.add(current);
|
|
136
136
|
const group = options.graph.getWorkflowGroupById(current);
|
|
137
|
-
//
|
|
138
|
-
//
|
|
137
|
+
// A parallel group is not an execution node itself, but it controls the state of its member nodes and subsequent join branches.
|
|
138
|
+
// Replay/reject must penetrate the group to include members and nodes downstream of the group in the reset scope.
|
|
139
139
|
for (const memberId of group?.members ?? []) {
|
|
140
140
|
queue.push(memberId);
|
|
141
141
|
}
|
|
@@ -13,9 +13,9 @@ const group_item_executor_1 = require("./group-item-executor");
|
|
|
13
13
|
const node_item_executor_1 = require("./node-item-executor");
|
|
14
14
|
const ctx = (reason, extra) => ({ reason, ...extra });
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Pipeline executor.
|
|
17
|
+
* Responsible for: specific execution logic of nodes, state transitions, artifact management.
|
|
18
|
+
* Not responsible for: scheduling decisions, concurrency control.
|
|
19
19
|
*/
|
|
20
20
|
const createExecutionService = (deps) => {
|
|
21
21
|
let activeBatchKeywordItems = null;
|
|
@@ -56,7 +56,7 @@ const createExecutionService = (deps) => {
|
|
|
56
56
|
return ids;
|
|
57
57
|
},
|
|
58
58
|
});
|
|
59
|
-
// ====== Helper:
|
|
59
|
+
// ====== Helper: Reset affected downstream ======
|
|
60
60
|
const resetAffectedDownstreamNodes = (params) => {
|
|
61
61
|
const run = getRun();
|
|
62
62
|
const affected = collectDownstreamSubgraph(params.targetNodeId);
|
|
@@ -20,11 +20,11 @@ const injectRuntimeKeywordsToInstruction = (instruction, keywords) => {
|
|
|
20
20
|
if (matchedArray && matchedArray.length > 0) {
|
|
21
21
|
return instruction.replace(arrayPattern, keywordJson);
|
|
22
22
|
}
|
|
23
|
-
return `${instruction}\n\
|
|
23
|
+
return `${instruction}\n\nKeywords for this batch (JSON array):\n${keywordJson}`;
|
|
24
24
|
};
|
|
25
25
|
const resolveActiveBatchKeywordsForInstruction = (activeBatchKeywordItems, isSourceEntryNode) => {
|
|
26
|
-
//
|
|
27
|
-
//
|
|
26
|
+
// Only the entry node that is actually in a batch run may inject the keyword pool into the prompt.
|
|
27
|
+
// In a regular single run the default itemKey may be "global", but that's just a run-item identifier — it should not be shown as a batch keyword.
|
|
28
28
|
if (!isSourceEntryNode || !activeBatchKeywordItems || activeBatchKeywordItems.length === 0) {
|
|
29
29
|
return [];
|
|
30
30
|
}
|
|
@@ -72,8 +72,8 @@ const shouldParseFrameForEnvelope = (frame) => {
|
|
|
72
72
|
const createStructuredNodeRunner = (options) => {
|
|
73
73
|
const emitter = new node_events_1.EventEmitter();
|
|
74
74
|
emitter.setMaxListeners(32);
|
|
75
|
-
//
|
|
76
|
-
//
|
|
75
|
+
// The same agent session reuses multiple rounds of conversation.
|
|
76
|
+
// Track lifecycle by sessionId + agent runId to prevent stray end/error events from the previous round's tail from hitting the next round's new request.
|
|
77
77
|
const sessionRuns = new Map();
|
|
78
78
|
const findStringByKeys = (value, keys, depth = 0) => {
|
|
79
79
|
if (depth > 5 || value === null || value === undefined)
|
|
@@ -145,9 +145,9 @@ const createStructuredNodeRunner = (options) => {
|
|
|
145
145
|
current.completedAt = null;
|
|
146
146
|
}
|
|
147
147
|
runMap.set(runId, current);
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
148
|
+
// Keep only the most recent few run records per session to avoid unbounded growth during long runs.
|
|
149
|
+
// Skip active runs still awaiting confirmation (completedAt is null) before eviction,
|
|
150
|
+
// to prevent runId locked by resolveCompletedAtForRequest from being mistakenly deleted and causing a timeout.
|
|
151
151
|
const MAX_RUNS_PER_SESSION = 48;
|
|
152
152
|
if (runMap.size > MAX_RUNS_PER_SESSION) {
|
|
153
153
|
const now = Date.now();
|
|
@@ -171,8 +171,8 @@ const createStructuredNodeRunner = (options) => {
|
|
|
171
171
|
if (!runMap || runMap.size === 0)
|
|
172
172
|
return null;
|
|
173
173
|
if (!watch.lockedRunId) {
|
|
174
|
-
//
|
|
175
|
-
//
|
|
174
|
+
// Prefer locking the runId whose "first seen time" is later than this request's start time.
|
|
175
|
+
// This avoids the previous request's tail end/error being misidentified as the current round's completion when a new request just starts.
|
|
176
176
|
const currentRun = [...runMap.entries()]
|
|
177
177
|
.filter(([, snapshot]) => snapshot.firstSeenAt >= requestStartedAt)
|
|
178
178
|
.sort((a, b) => a[1].firstSeenAt - b[1].firstSeenAt)[0];
|
|
@@ -210,8 +210,8 @@ const createStructuredNodeRunner = (options) => {
|
|
|
210
210
|
rememberSessionRunEvent(sessionId, runId, inferLifecycle(payload.data));
|
|
211
211
|
return;
|
|
212
212
|
}
|
|
213
|
-
// assistant/tool/item
|
|
214
|
-
//
|
|
213
|
+
// assistant/tool/item streams don't represent completion, but they provide the current round's runId.
|
|
214
|
+
// This only records "this run was seen"; completion is still determined by lifecycle/chat.final.
|
|
215
215
|
rememberSessionRunEvent(sessionId, runId, "unknown");
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
@@ -232,8 +232,8 @@ const createStructuredNodeRunner = (options) => {
|
|
|
232
232
|
const workflowNode = options.graph.getWorkflowNodeById(node.id);
|
|
233
233
|
const retryBackoffMs = workflowNode?.retryPolicy.backoffMs ?? 0;
|
|
234
234
|
const parallelGroup = options.graph.getParallelGroupByMemberNodeId(node.id);
|
|
235
|
-
//
|
|
236
|
-
//
|
|
235
|
+
// Batch keywords should only be injected into the true entry node. Parallel-group members typically have no direct incoming edge,
|
|
236
|
+
// but their dependencies are attached to the group; looking only at the node's own incoming edges would misclassify group members as source entries.
|
|
237
237
|
const isSourceEntryNode = options.graph.getIncomingEdges(node.id).length === 0 &&
|
|
238
238
|
(!parallelGroup || options.graph.getIncomingEdges(parallelGroup.id).length === 0);
|
|
239
239
|
const effectiveRuntimeKeywordItems = (0, exports.resolveActiveBatchKeywordsForInstruction)(options.getActiveBatchKeywordItems(), isSourceEntryNode);
|
|
@@ -250,7 +250,7 @@ const createStructuredNodeRunner = (options) => {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
if (effectiveDependencyIds.length > 0) {
|
|
253
|
-
options.runtimeStore.pushTimeline(
|
|
253
|
+
options.runtimeStore.pushTimeline(`Node ${node.id} loaded ${dependencyArtifacts.length} upstream artifacts (from: ${effectiveDependencyIds.join(",")})`);
|
|
254
254
|
}
|
|
255
255
|
const allowedRoutes = workflowNode?.routePolicy?.allowed ?? [];
|
|
256
256
|
const routeTargets = options.graph.getOutgoingEdges(node.id)
|
|
@@ -263,7 +263,7 @@ const createStructuredNodeRunner = (options) => {
|
|
|
263
263
|
route: edge.when ?? "",
|
|
264
264
|
targetNodeId: edge.to,
|
|
265
265
|
targetNodeTitle: targetWorkflowGroup
|
|
266
|
-
?
|
|
266
|
+
? `Parallel group ${targetWorkflowGroup.id}`
|
|
267
267
|
: targetNode?.title ?? targetWorkflowNode?.name ?? edge.to,
|
|
268
268
|
targetAgentId: targetWorkflowGroup
|
|
269
269
|
? targetWorkflowGroup.id
|
|
@@ -287,7 +287,7 @@ const createStructuredNodeRunner = (options) => {
|
|
|
287
287
|
return { requestId, ctx, effectiveDependencyIds, dependencyArtifacts, allowedRoutes, routeTargets, retryBackoffMs };
|
|
288
288
|
};
|
|
289
289
|
const sendAndWaitForEnvelope = async (prompt, sessionId, nodeId, validationCtx, lastViolation, requestStartedAt, requestRunWatch, signal) => {
|
|
290
|
-
// DAG
|
|
290
|
+
// DAG node send only uses the chat.send single path to avoid compatibility fallback sends from different APIs delivering the same requestId twice.
|
|
291
291
|
const idempotencyKey = (0, node_crypto_1.randomUUID)();
|
|
292
292
|
let payload;
|
|
293
293
|
try {
|
|
@@ -298,10 +298,10 @@ const createStructuredNodeRunner = (options) => {
|
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
300
|
catch (error) {
|
|
301
|
-
options.runtimeStore.pushTimeline(
|
|
301
|
+
options.runtimeStore.pushTimeline(`Node ${nodeId} send failed (chat.send): ${String(error)}`, "error");
|
|
302
302
|
throw new Error(`openclaw_send_failed:chat.send:${String(error)}`);
|
|
303
303
|
}
|
|
304
|
-
options.runtimeStore.pushTimeline(
|
|
304
|
+
options.runtimeStore.pushTimeline(`Node ${nodeId} request sent, waiting for structured receipt...`);
|
|
305
305
|
const envelope = await (0, structured_output_1.waitForStructuredEnvelope)(emitter, validationCtx, lastViolation, () => resolveCompletedAtForRequest(sessionId, requestStartedAt, requestRunWatch), signal);
|
|
306
306
|
return envelope;
|
|
307
307
|
};
|
|
@@ -316,8 +316,8 @@ const createStructuredNodeRunner = (options) => {
|
|
|
316
316
|
requireRouteContent: allowedRoutes.length > 0,
|
|
317
317
|
};
|
|
318
318
|
let lastViolation = null;
|
|
319
|
-
//
|
|
320
|
-
//
|
|
319
|
+
// Error-correction retries are corrections of the same node request; the requestId should not be changed.
|
|
320
|
+
// Otherwise the model would be wrongly flagged as a mismatch due to a changed top-level requestId, even after a successful context-based correction.
|
|
321
321
|
for (let attemptIndex = 0; attemptIndex < 2; attemptIndex += 1) {
|
|
322
322
|
if (attemptIndex > 0 && retryBackoffMs > 0) {
|
|
323
323
|
await new Promise(resolve => setTimeout(resolve, retryBackoffMs));
|
|
@@ -336,10 +336,10 @@ const createStructuredNodeRunner = (options) => {
|
|
|
336
336
|
if (violation) {
|
|
337
337
|
lastViolation = violation;
|
|
338
338
|
if (attemptIndex === 0) {
|
|
339
|
-
options.runtimeStore.pushTimeline(
|
|
339
|
+
options.runtimeStore.pushTimeline(`Node ${node.id} receipt validation failed (${violation}), auto-correcting...`, "warn");
|
|
340
340
|
continue;
|
|
341
341
|
}
|
|
342
|
-
options.runtimeStore.pushTimeline(
|
|
342
|
+
options.runtimeStore.pushTimeline(`Node ${node.id} receipt validation failed (${violation}), correction still failed, marked as failed`, "error");
|
|
343
343
|
}
|
|
344
344
|
throw error;
|
|
345
345
|
}
|
|
@@ -350,7 +350,7 @@ const createStructuredNodeRunner = (options) => {
|
|
|
350
350
|
const savedAt = new Date();
|
|
351
351
|
const status = envelope.status === "failed" ? "failed" : "success";
|
|
352
352
|
const batchRunId = options.getBatchRunId?.();
|
|
353
|
-
//
|
|
353
|
+
// Envelope files are written and indexed via persistEnvelopeFile
|
|
354
354
|
await (0, artifact_storage_1.persistEnvelopeFile)(options.artifactDir, status, { runId, batchRunId, nodeId, requestId, pipelineId: options.pipelineId }, envelope, { savedAt });
|
|
355
355
|
const normalizedArtifacts = [];
|
|
356
356
|
for (let i = 0; i < envelope.artifacts.length; i += 1) {
|
|
@@ -10,12 +10,12 @@ const normalizeTimeout = (value, fallback, min) => {
|
|
|
10
10
|
return fallback;
|
|
11
11
|
return Math.max(min, Math.trunc(parsed));
|
|
12
12
|
};
|
|
13
|
-
//
|
|
13
|
+
// Centrally maintain DAG node execution timeout so send-request and wait-receipt don't use different defaults.
|
|
14
14
|
const getPipelineNodeExecutionTimeoutMs = () => normalizeTimeout(process.env.PIPELINE_NODE_EXECUTION_TIMEOUT_MS, DEFAULT_PIPELINE_NODE_TIMEOUT_MS, MIN_TIMEOUT_MS);
|
|
15
15
|
exports.getPipelineNodeExecutionTimeoutMs = getPipelineNodeExecutionTimeoutMs;
|
|
16
|
-
//
|
|
16
|
+
// Compatible with legacy env var name; fall back to the old var when the new one is not set.
|
|
17
17
|
const getStructuredResultTimeoutMs = () => normalizeTimeout(process.env.STRUCTURED_RESULT_TIMEOUT_MS ?? process.env.PIPELINE_NODE_EXECUTION_TIMEOUT_MS, DEFAULT_PIPELINE_NODE_TIMEOUT_MS, MIN_TIMEOUT_MS);
|
|
18
18
|
exports.getStructuredResultTimeoutMs = getStructuredResultTimeoutMs;
|
|
19
|
-
//
|
|
19
|
+
// Polling interval stays short; it only observes receipts and does not participate in failure judgment.
|
|
20
20
|
const getStructuredResultPollMs = () => normalizeTimeout(process.env.STRUCTURED_RESULT_POLL_MS, DEFAULT_STRUCTURED_RESULT_POLL_MS, 100);
|
|
21
21
|
exports.getStructuredResultPollMs = getStructuredResultPollMs;
|
|
@@ -15,17 +15,17 @@ const buildRequestId = (nodeId) => `node-${nodeId}-${(0, node_crypto_1.randomUUI
|
|
|
15
15
|
exports.buildRequestId = buildRequestId;
|
|
16
16
|
const buildItemKeyFromKeywords = (keywords) => [...new Set(keywords.map((item) => item.trim()).filter(Boolean))];
|
|
17
17
|
exports.buildItemKeyFromKeywords = buildItemKeyFromKeywords;
|
|
18
|
-
/**
|
|
18
|
+
/** Build an identity snapshot from a Run and pipelineId */
|
|
19
19
|
const toRunIdentity = (pipelineId, run) => ({
|
|
20
20
|
pipelineId,
|
|
21
21
|
runId: run.id,
|
|
22
22
|
batchRunId: null,
|
|
23
23
|
});
|
|
24
24
|
exports.toRunIdentity = toRunIdentity;
|
|
25
|
-
/**
|
|
25
|
+
/** Format an identity into a human-readable string */
|
|
26
26
|
const formatIdentity = (id) => `pipeline=${id.pipelineId} run=${id.runId}${id.batchRunId ? ` batch=${id.batchRunId}` : ""}`;
|
|
27
27
|
exports.formatIdentity = formatIdentity;
|
|
28
|
-
/**
|
|
28
|
+
/** Verify identity match: pipelineId must be equal, runId must be equal, allow when either batchRunId is null */
|
|
29
29
|
const matchIdentity = (target, candidate) => target.pipelineId === candidate.pipelineId &&
|
|
30
30
|
target.runId === candidate.runId &&
|
|
31
31
|
(target.batchRunId === null || candidate.batchRunId === null || target.batchRunId === candidate.batchRunId);
|
|
@@ -44,7 +44,7 @@ const normalizePoolItems = (value) => {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
else if (typeof value === "string") {
|
|
47
|
-
//
|
|
47
|
+
// Support mixed comma and newline input for easy pasting of keyword pools.
|
|
48
48
|
rawList.push(...value.split(/[\n,]/g));
|
|
49
49
|
}
|
|
50
50
|
const unique = new Set();
|
|
@@ -93,8 +93,8 @@ const createItemBatchController = (deps) => {
|
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
catch (error) {
|
|
96
|
-
//
|
|
97
|
-
//
|
|
96
|
+
// The batch-run controller runs outside HTTP requests; if an exception escapes here, the snapshot would permanently stay at running,
|
|
97
|
+
// and subsequent start() calls would falsely detect an existing batch run in progress. Must guard the state inside the controller.
|
|
98
98
|
if (token !== runToken)
|
|
99
99
|
return;
|
|
100
100
|
hasBatchErrors = true;
|
|
@@ -184,7 +184,7 @@ const createItemBatchController = (deps) => {
|
|
|
184
184
|
stopRequested: false,
|
|
185
185
|
batchRunId,
|
|
186
186
|
};
|
|
187
|
-
//
|
|
187
|
+
// Run asynchronously in the background to avoid blocking the HTTP request.
|
|
188
188
|
void runLoop(runToken, [...queuedItems]);
|
|
189
189
|
return { ok: true, snapshot: cloneSnapshot(snapshot) };
|
|
190
190
|
};
|
|
@@ -192,7 +192,7 @@ const createItemBatchController = (deps) => {
|
|
|
192
192
|
if (snapshot.status !== "running") {
|
|
193
193
|
return { ok: false, error: "batch_run_not_running", snapshot: cloneSnapshot(snapshot) };
|
|
194
194
|
}
|
|
195
|
-
//
|
|
195
|
+
// Only request stop; the current batch will exit safely after completion.
|
|
196
196
|
snapshot = {
|
|
197
197
|
...snapshot,
|
|
198
198
|
stopRequested: true,
|
|
@@ -203,7 +203,7 @@ const createItemBatchController = (deps) => {
|
|
|
203
203
|
if (snapshot.status !== "running") {
|
|
204
204
|
return { ok: false, error: "batch_run_not_running", snapshot: cloneSnapshot(snapshot) };
|
|
205
205
|
}
|
|
206
|
-
//
|
|
206
|
+
// When the plugin is disabled, the running batch controller should not be retained; switch runToken to invalidate old loop results.
|
|
207
207
|
runToken += 1;
|
|
208
208
|
snapshot = {
|
|
209
209
|
...snapshot,
|
|
@@ -23,9 +23,9 @@ const createDependencyState = (options) => {
|
|
|
23
23
|
impossibleCount += 1;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
// all/any
|
|
27
|
-
// - all:
|
|
28
|
-
// - any:
|
|
26
|
+
// all/any execution difference:
|
|
27
|
+
// - all: only enqueue when all dependencies are satisfied; skip when remaining dependencies are all impossible.
|
|
28
|
+
// - any: enqueue when any single dependency is satisfied; only skip when all dependencies are impossible.
|
|
29
29
|
if (policy === "any") {
|
|
30
30
|
if (satisfiedCount > 0)
|
|
31
31
|
return "queued";
|
|
@@ -56,7 +56,7 @@ const createDependencyState = (options) => {
|
|
|
56
56
|
if (!Number.isFinite(wakeTs) || wakeTs > now)
|
|
57
57
|
continue;
|
|
58
58
|
(0, state_1.markItemWakeSuccess)(item, { reason: "sleep_expired" });
|
|
59
|
-
options.runtimeStore.pushTimeline(
|
|
59
|
+
options.runtimeStore.pushTimeline(`Waiting node woken: ${item.nodeId}#${item.itemKey}`);
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
62
|
const markReadyItemsFromDependencies = () => {
|
|
@@ -115,7 +115,7 @@ const createDependencyState = (options) => {
|
|
|
115
115
|
const related = (getRun().groupItemRuns ?? []).filter((item) => item.groupId === group.id);
|
|
116
116
|
if (related.length === 0)
|
|
117
117
|
continue;
|
|
118
|
-
// joinPolicy
|
|
118
|
+
// joinPolicy only supports "all": mark group success only when all group items succeed, fail if any fails
|
|
119
119
|
if (related.some((item) => item.status === "failed")) {
|
|
120
120
|
(0, state_1.markGroupFailed)(group, { reason: "member_failed", command: "group_aggregate", error: related.find((item) => item.status === "failed")?.lastError });
|
|
121
121
|
}
|