taskmeld 0.1.1
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/LICENSE +18 -0
- package/README.md +172 -0
- package/README.zh-CN.md +172 -0
- package/dist/src/app/app-context-env.js +51 -0
- package/dist/src/app/create-app-context.js +127 -0
- package/dist/src/app/data-dir.js +29 -0
- package/dist/src/app/pipeline-config.js +105 -0
- package/dist/src/app/pipeline-plugin-config.js +2 -0
- package/dist/src/app/pipeline-registry.js +502 -0
- package/dist/src/app/pipeline-runtime.js +202 -0
- package/dist/src/app/runtime-store.js +151 -0
- package/dist/src/app/user-config.js +37 -0
- package/dist/src/artifacts/artifact-cleanup.js +192 -0
- package/dist/src/artifacts/artifact-index.js +262 -0
- package/dist/src/artifacts/artifact-rebuilder.js +120 -0
- package/dist/src/artifacts/storage-service.js +371 -0
- package/dist/src/cli/bootstrap.js +226 -0
- package/dist/src/cli/commands/agent.js +126 -0
- package/dist/src/cli/commands/artifact.js +175 -0
- package/dist/src/cli/commands/init.js +150 -0
- package/dist/src/cli/commands/pipeline/errors.js +37 -0
- package/dist/src/cli/commands/pipeline/result.js +179 -0
- package/dist/src/cli/commands/pipeline/selector.js +51 -0
- package/dist/src/cli/commands/pipeline/types.js +2 -0
- package/dist/src/cli/commands/pipeline/watch.js +67 -0
- package/dist/src/cli/commands/pipeline.js +339 -0
- package/dist/src/cli/commands/scheduler.js +81 -0
- package/dist/src/cli/commands/server.js +70 -0
- package/dist/src/cli/commands/system.js +21 -0
- package/dist/src/cli/errors.js +71 -0
- package/dist/src/cli/help.js +184 -0
- package/dist/src/cli/index.js +65 -0
- package/dist/src/cli/output.js +19 -0
- package/dist/src/cli/renderers/engine/json.js +67 -0
- package/dist/src/cli/renderers/engine/markdown.js +95 -0
- package/dist/src/cli/renderers/engine/types.js +2 -0
- package/dist/src/cli/renderers/engine/utils.js +32 -0
- package/dist/src/cli/renderers/index.js +27 -0
- package/dist/src/cli/renderers/specs/agent.js +78 -0
- package/dist/src/cli/renderers/specs/artifact.js +32 -0
- package/dist/src/cli/renderers/specs/index.js +36 -0
- package/dist/src/cli/renderers/specs/init.js +25 -0
- package/dist/src/cli/renderers/specs/pipeline.js +561 -0
- package/dist/src/cli/renderers/specs/scheduler.js +46 -0
- package/dist/src/cli/renderers/specs/server.js +38 -0
- package/dist/src/cli/renderers/specs/system.js +36 -0
- package/dist/src/cli/router.js +199 -0
- package/dist/src/cli/server-runtime-client.js +780 -0
- package/dist/src/cli/types.js +2 -0
- package/dist/src/gateway/frame-sanitizer.js +78 -0
- package/dist/src/gateway/gateway-client.js +462 -0
- package/dist/src/gateway/index.js +18 -0
- package/dist/src/gateway/types.js +2 -0
- package/dist/src/index.js +123 -0
- package/dist/src/logs/run-log-reader.js +141 -0
- package/dist/src/logs/run-log-service.js +42 -0
- package/dist/src/logs/run-log-types.js +2 -0
- package/dist/src/pipeline/agent-activity.js +191 -0
- package/dist/src/pipeline/artifact-storage.js +208 -0
- package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +105 -0
- package/dist/src/pipeline/diagnostics/index.js +6 -0
- package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +215 -0
- package/dist/src/pipeline/dispatch/pipeline-link-dispatcher.js +66 -0
- package/dist/src/pipeline/dispatch/pipeline-link-store.js +94 -0
- package/dist/src/pipeline/dispatch/pipeline-queue-drainer.js +71 -0
- package/dist/src/pipeline/execution/dependency-check.js +52 -0
- package/dist/src/pipeline/execution/execution-result.js +2 -0
- package/dist/src/pipeline/execution/group-item-executor.js +128 -0
- package/dist/src/pipeline/execution/index.js +5 -0
- package/dist/src/pipeline/execution/node-item-executor.js +58 -0
- package/dist/src/pipeline/execution/node-runner.js +159 -0
- package/dist/src/pipeline/execution/readiness-state.js +10 -0
- package/dist/src/pipeline/execution/reject-handler.js +94 -0
- package/dist/src/pipeline/execution/rejected-artifact-archiver.js +45 -0
- package/dist/src/pipeline/execution/route-item-manager.js +253 -0
- package/dist/src/pipeline/execution/run-abort-controller.js +66 -0
- package/dist/src/pipeline/execution/run-state-helpers.js +257 -0
- package/dist/src/pipeline/execution/service.js +165 -0
- package/dist/src/pipeline/execution/session-registry.js +96 -0
- package/dist/src/pipeline/execution/structured-node-runner.js +411 -0
- package/dist/src/pipeline/execution-status.js +96 -0
- package/dist/src/pipeline/execution-timeout.js +21 -0
- package/dist/src/pipeline/identity/index.js +32 -0
- package/dist/src/pipeline/identity/types.js +2 -0
- package/dist/src/pipeline/item-batch-controller.js +227 -0
- package/dist/src/pipeline/output/pipeline-output-resolver.js +91 -0
- package/dist/src/pipeline/output/pipeline-output-store.js +60 -0
- package/dist/src/pipeline/runtime-model.js +173 -0
- package/dist/src/pipeline/scheduler/dependency-state.js +144 -0
- package/dist/src/pipeline/scheduler-service.js +314 -0
- package/dist/src/pipeline/state/group-item-state.js +50 -0
- package/dist/src/pipeline/state/group-run-state.js +41 -0
- package/dist/src/pipeline/state/index.js +20 -0
- package/dist/src/pipeline/state/node-item-state.js +67 -0
- package/dist/src/pipeline/state/node-run-state.js +51 -0
- package/dist/src/pipeline/state/types.js +2 -0
- package/dist/src/pipeline/state-machine.js +101 -0
- package/dist/src/pipeline/structured-output/contract.js +133 -0
- package/dist/src/pipeline/structured-output/index.js +22 -0
- package/dist/src/pipeline/structured-output/parser.js +214 -0
- package/dist/src/pipeline/structured-output/prompt.js +290 -0
- package/dist/src/pipeline/structured-output/waiter.js +139 -0
- package/dist/src/pipeline/template.js +135 -0
- package/dist/src/pipeline/timeline-log-store.js +57 -0
- package/dist/src/pipeline/tool-activity.js +94 -0
- package/dist/src/pipeline/types/pipeline-link.js +7 -0
- package/dist/src/pipeline/types/pipeline-output.js +11 -0
- package/dist/src/pipeline/types/workflow.js +2 -0
- package/dist/src/pipeline/workflow/branch-rules.js +74 -0
- package/dist/src/pipeline/workflow/defaults.js +48 -0
- package/dist/src/pipeline/workflow/io.js +89 -0
- package/dist/src/pipeline/workflow/normalize.js +347 -0
- package/dist/src/pipeline/workflow/routes.js +16 -0
- package/dist/src/pipeline/workflow/template-mapper.js +113 -0
- package/dist/src/pipeline/workflow/validate.js +312 -0
- package/dist/src/pipeline/workflow-graph.js +165 -0
- package/dist/src/server/api-handler.js +163 -0
- package/dist/src/server/http-utils.js +34 -0
- package/dist/src/server/middleware.js +61 -0
- package/dist/src/server/router.js +105 -0
- package/dist/src/server/routes/agents.js +189 -0
- package/dist/src/server/routes/artifacts.js +163 -0
- package/dist/src/server/routes/gateway.js +18 -0
- package/dist/src/server/routes/health.js +16 -0
- package/dist/src/server/routes/logs.js +73 -0
- package/dist/src/server/routes/pipeline-batch.js +163 -0
- package/dist/src/server/routes/pipeline-diagnostics.js +33 -0
- package/dist/src/server/routes/pipeline-identity.js +24 -0
- package/dist/src/server/routes/pipeline-links.js +117 -0
- package/dist/src/server/routes/pipeline-outputs.js +27 -0
- package/dist/src/server/routes/pipeline-queue.js +62 -0
- package/dist/src/server/routes/pipeline-runtime.js +162 -0
- package/dist/src/server/routes/pipeline-scheduler.js +69 -0
- package/dist/src/server/routes/pipeline-workflow.js +180 -0
- package/dist/src/server/routes/pipelines.js +96 -0
- package/dist/src/server/routes/sessions.js +244 -0
- package/dist/src/server/routes/timeline.js +14 -0
- package/dist/src/server/serve-static.js +42 -0
- package/dist/src/server/types.js +2 -0
- package/dist/src/services/agent-service.js +79 -0
- package/dist/src/services/artifact-service.js +74 -0
- package/dist/src/services/gateway-read-helpers.js +10 -0
- package/dist/src/services/index.js +23 -0
- package/dist/src/services/pipeline-service.js +529 -0
- package/dist/src/services/pipeline-status.js +93 -0
- package/dist/src/services/read-services.js +60 -0
- package/dist/src/services/scheduler-service.js +37 -0
- package/dist/src/services/session-service.js +227 -0
- package/dist/src/services/system-service.js +26 -0
- package/dist/src/transport/ws-broker.js +48 -0
- package/dist/src/utils/array.js +17 -0
- package/dist/src/utils/guards.js +5 -0
- package/dist/src/utils/session.js +60 -0
- package/dist/src/version.js +5 -0
- package/package.json +61 -0
- package/web/dist/assets/index-CWnfhkn-.js +65 -0
- package/web/dist/assets/index-gZ0xOfSO.css +1 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
- package/web/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/web/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/web/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
- package/web/dist/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
- package/web/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/web/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/web/dist/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/web/dist/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/web/dist/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/web/dist/favicon.svg +10 -0
- package/web/dist/index.html +14 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createExecutionService = void 0;
|
|
4
|
+
const run_state_helpers_1 = require("./run-state-helpers");
|
|
5
|
+
const session_registry_1 = require("./session-registry");
|
|
6
|
+
const route_item_manager_1 = require("./route-item-manager");
|
|
7
|
+
const structured_node_runner_1 = require("./structured-node-runner");
|
|
8
|
+
const state_1 = require("../state");
|
|
9
|
+
const reject_handler_1 = require("./reject-handler");
|
|
10
|
+
const run_abort_controller_1 = require("./run-abort-controller");
|
|
11
|
+
const node_runner_1 = require("./node-runner");
|
|
12
|
+
const group_item_executor_1 = require("./group-item-executor");
|
|
13
|
+
const node_item_executor_1 = require("./node-item-executor");
|
|
14
|
+
const ctx = (reason, extra) => ({ reason, ...extra });
|
|
15
|
+
/**
|
|
16
|
+
* 流水线执行器。
|
|
17
|
+
* 负责:节点的具体执行逻辑、状态转换、产物管理。
|
|
18
|
+
* 不负责:调度决策、并发控制。
|
|
19
|
+
*/
|
|
20
|
+
const createExecutionService = (deps) => {
|
|
21
|
+
let activeBatchKeywordItems = null;
|
|
22
|
+
const runAbortController = (0, run_abort_controller_1.createRunAbortController)();
|
|
23
|
+
const state = (0, run_state_helpers_1.createRunStateHelpers)({
|
|
24
|
+
runtimeStore: deps.runtimeStore,
|
|
25
|
+
graph: deps.graph,
|
|
26
|
+
defaultItemKeys: deps.defaultItemKeys,
|
|
27
|
+
});
|
|
28
|
+
const { getRun, getNodeById, getGroupById, getItemRun, getGroupItemRun, computeInitialItemStatus, computeInitialGroupItemStatus, ensureGroupItemKeyInitialized, ensureItemRuns, ensureItemKeyInitialized, getEffectiveDependencyIdsForNodeItem, collectDownstreamSubgraph, resetNodeForReplay, } = state;
|
|
29
|
+
const routeItemManager = (0, route_item_manager_1.createRouteItemManager)({
|
|
30
|
+
runtimeStore: deps.runtimeStore,
|
|
31
|
+
graph: deps.graph,
|
|
32
|
+
state,
|
|
33
|
+
});
|
|
34
|
+
const structuredNodeRunner = (0, structured_node_runner_1.createStructuredNodeRunner)({
|
|
35
|
+
client: deps.client,
|
|
36
|
+
runtimeStore: deps.runtimeStore,
|
|
37
|
+
graph: deps.graph,
|
|
38
|
+
artifactDir: deps.artifactDir,
|
|
39
|
+
pipelineId: deps.pipelineId,
|
|
40
|
+
pipelineNodeExecutionTimeoutMs: deps.pipelineNodeExecutionTimeoutMs,
|
|
41
|
+
getNodeById,
|
|
42
|
+
getRun,
|
|
43
|
+
getActiveBatchKeywordItems: () => activeBatchKeywordItems,
|
|
44
|
+
getBatchRunId: deps.getBatchRunId,
|
|
45
|
+
});
|
|
46
|
+
const sessionRegistry = (0, session_registry_1.createSessionRegistry)({
|
|
47
|
+
client: deps.client,
|
|
48
|
+
pushTimeline: deps.runtimeStore.pushTimeline,
|
|
49
|
+
getKnownExecutorIds: () => {
|
|
50
|
+
const ids = new Set();
|
|
51
|
+
for (const node of getRun().nodes) {
|
|
52
|
+
ids.add(node.executor.agentId);
|
|
53
|
+
if (node.executor.fallbackAgentId)
|
|
54
|
+
ids.add(node.executor.fallbackAgentId);
|
|
55
|
+
}
|
|
56
|
+
return ids;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
// ====== Helper: 重置受影响的 downstream ======
|
|
60
|
+
const resetAffectedDownstreamNodes = (params) => {
|
|
61
|
+
const run = getRun();
|
|
62
|
+
const affected = collectDownstreamSubgraph(params.targetNodeId);
|
|
63
|
+
const skipSet = new Set(params.skipNodeIds ?? []);
|
|
64
|
+
for (const affectedNode of run.nodes) {
|
|
65
|
+
if (!affected.nodeIds.has(affectedNode.id))
|
|
66
|
+
continue;
|
|
67
|
+
if (skipSet.has(affectedNode.id))
|
|
68
|
+
continue;
|
|
69
|
+
resetNodeForReplay(affectedNode, { clearRejectFeedbacks: affectedNode.id !== params.targetNodeId });
|
|
70
|
+
}
|
|
71
|
+
for (const affectedGroup of run.groups ?? []) {
|
|
72
|
+
if (!affected.groupIds.has(affectedGroup.id))
|
|
73
|
+
continue;
|
|
74
|
+
(0, state_1.markGroupReset)(affectedGroup, computeInitialGroupItemStatus(affectedGroup.id), ctx("reset_downstream"));
|
|
75
|
+
affectedGroup.artifacts = [];
|
|
76
|
+
}
|
|
77
|
+
for (const affectedItem of run.itemRuns ?? []) {
|
|
78
|
+
if (!affected.nodeIds.has(affectedItem.nodeId))
|
|
79
|
+
continue;
|
|
80
|
+
if (skipSet.has(affectedItem.nodeId))
|
|
81
|
+
continue;
|
|
82
|
+
if (params.itemKey && affectedItem.itemKey !== params.itemKey)
|
|
83
|
+
continue;
|
|
84
|
+
(0, state_1.markItemReset)(affectedItem, computeInitialItemStatus(affectedItem.nodeId), ctx("reset_downstream"));
|
|
85
|
+
affectedItem.route = null;
|
|
86
|
+
affectedItem.artifacts = [];
|
|
87
|
+
}
|
|
88
|
+
for (const affectedGroupItem of run.groupItemRuns ?? []) {
|
|
89
|
+
if (!affected.groupIds.has(affectedGroupItem.groupId))
|
|
90
|
+
continue;
|
|
91
|
+
if (params.itemKey && affectedGroupItem.itemKey !== params.itemKey)
|
|
92
|
+
continue;
|
|
93
|
+
(0, state_1.markGroupItemReset)(affectedGroupItem, computeInitialGroupItemStatus(affectedGroupItem.groupId), ctx("reset_downstream"));
|
|
94
|
+
affectedGroupItem.artifacts = [];
|
|
95
|
+
}
|
|
96
|
+
return { affectedNodeCount: affected.nodeIds.size, affectedGroupCount: affected.groupIds.size };
|
|
97
|
+
};
|
|
98
|
+
// ====== Node runner ======
|
|
99
|
+
const nodeRunner = (0, node_runner_1.createNodeRunner)({
|
|
100
|
+
runtimeStore: deps.runtimeStore,
|
|
101
|
+
getRun,
|
|
102
|
+
structuredNodeRunner,
|
|
103
|
+
sessionRegistry,
|
|
104
|
+
runAbortController,
|
|
105
|
+
handleNodeReject: reject_handler_1.handleNodeReject,
|
|
106
|
+
resetAffectedDownstreamNodes,
|
|
107
|
+
artifactDir: deps.artifactDir,
|
|
108
|
+
pipelineId: deps.pipelineId,
|
|
109
|
+
getBatchRunId: deps.getBatchRunId,
|
|
110
|
+
});
|
|
111
|
+
// ====== Node item executor ======
|
|
112
|
+
const nodeItemExecutor = (0, node_item_executor_1.createNodeItemExecutor)({
|
|
113
|
+
runtimeStore: deps.runtimeStore,
|
|
114
|
+
graph: deps.graph,
|
|
115
|
+
nodeRunner,
|
|
116
|
+
routeItemManager,
|
|
117
|
+
getRun,
|
|
118
|
+
getNodeById,
|
|
119
|
+
getEffectiveDependencyIdsForNodeItem,
|
|
120
|
+
});
|
|
121
|
+
const executeNodeItem = nodeItemExecutor.executeNodeItem;
|
|
122
|
+
// ====== Group item executor ======
|
|
123
|
+
const groupItemExecutor = (0, group_item_executor_1.createGroupItemExecutor)({
|
|
124
|
+
runtimeStore: deps.runtimeStore,
|
|
125
|
+
graph: deps.graph,
|
|
126
|
+
artifactDir: deps.artifactDir,
|
|
127
|
+
pipelineId: deps.pipelineId,
|
|
128
|
+
getBatchRunId: deps.getBatchRunId,
|
|
129
|
+
getRun,
|
|
130
|
+
getNodeById,
|
|
131
|
+
getGroupById,
|
|
132
|
+
getItemRun,
|
|
133
|
+
ensureItemKeyInitialized,
|
|
134
|
+
getEffectiveDependencyIdsForNodeItem,
|
|
135
|
+
executeNodeItem,
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
getExecutorSessionByAgentId: sessionRegistry.getExecutorSessionByAgentId,
|
|
139
|
+
getSessionCache: sessionRegistry.getSessionCache,
|
|
140
|
+
refreshSessionsFromGateway: sessionRegistry.refreshSessionsFromGateway,
|
|
141
|
+
setActiveBatchKeywordItems: (items) => {
|
|
142
|
+
activeBatchKeywordItems = items;
|
|
143
|
+
},
|
|
144
|
+
onGatewayFrame: (frame) => {
|
|
145
|
+
structuredNodeRunner.rememberGatewayFrame(frame);
|
|
146
|
+
sessionRegistry.onGatewayFrame(frame);
|
|
147
|
+
},
|
|
148
|
+
hasActiveSession: (sessionKey) => {
|
|
149
|
+
return structuredNodeRunner.hasActiveSession?.(sessionKey) ?? false;
|
|
150
|
+
},
|
|
151
|
+
dispose: () => {
|
|
152
|
+
sessionRegistry.dispose();
|
|
153
|
+
},
|
|
154
|
+
ensureItemKeyInitialized,
|
|
155
|
+
ensureGroupItemKeyInitialized,
|
|
156
|
+
getEffectiveDependencyIdsForNodeItem,
|
|
157
|
+
resetAffectedDownstreamNodes,
|
|
158
|
+
executeNode: nodeRunner.executeNode,
|
|
159
|
+
executeNodeItem,
|
|
160
|
+
executeGroupItem: groupItemExecutor.executeGroupItem,
|
|
161
|
+
abortRunControllers: (runId) => runAbortController.abortRunControllers(runId, deps.client),
|
|
162
|
+
getOrCreateDrainSignal: (runId) => runAbortController.getOrCreateDrainSignal(runId),
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
exports.createExecutionService = createExecutionService;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSessionRegistry = void 0;
|
|
4
|
+
const session_1 = require("../../utils/session");
|
|
5
|
+
const array_1 = require("../../utils/array");
|
|
6
|
+
const agent_activity_1 = require("../agent-activity");
|
|
7
|
+
const tool_activity_1 = require("../tool-activity");
|
|
8
|
+
const createSessionRegistry = (options) => {
|
|
9
|
+
const executorSessionByAgentId = new Map();
|
|
10
|
+
let sessionCache = [];
|
|
11
|
+
const envSessionMap = (0, session_1.parseAgentSessionMapFromEnv)();
|
|
12
|
+
for (const [agentId, sessionId] of Object.entries(envSessionMap)) {
|
|
13
|
+
executorSessionByAgentId.set(agentId, sessionId);
|
|
14
|
+
}
|
|
15
|
+
const inferAgentIdFromSessionId = (sessionId) => {
|
|
16
|
+
for (const [agentId, mappedSessionId] of executorSessionByAgentId.entries()) {
|
|
17
|
+
if (mappedSessionId === sessionId)
|
|
18
|
+
return agentId;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
const agentActivityTracker = (0, agent_activity_1.createAgentActivityTracker)({
|
|
23
|
+
pushTimeline: options.pushTimeline,
|
|
24
|
+
resolveAgentBySessionId: inferAgentIdFromSessionId,
|
|
25
|
+
});
|
|
26
|
+
const toolActivityLogger = (0, tool_activity_1.createToolActivityLogger)({
|
|
27
|
+
pushTimeline: options.pushTimeline,
|
|
28
|
+
resolveAgentBySessionId: inferAgentIdFromSessionId,
|
|
29
|
+
});
|
|
30
|
+
const applySessionBindings = (items) => {
|
|
31
|
+
const known = options.getKnownExecutorIds();
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
const agentIds = (0, session_1.inferSessionAgentIds)(item, known);
|
|
34
|
+
for (const agentId of agentIds) {
|
|
35
|
+
if (!executorSessionByAgentId.has(agentId)) {
|
|
36
|
+
executorSessionByAgentId.set(agentId, item.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const [agentId, sessionId] of Object.entries(envSessionMap)) {
|
|
41
|
+
executorSessionByAgentId.set(agentId, sessionId);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const refreshSessionsFromGateway = async () => {
|
|
45
|
+
const payload = await options.client.sendReq("sessions.list");
|
|
46
|
+
const items = (0, array_1.pickArray)(payload)
|
|
47
|
+
.map((item, index) => (0, session_1.normalizeSession)(item, index))
|
|
48
|
+
.filter((item) => Boolean(item));
|
|
49
|
+
sessionCache = items;
|
|
50
|
+
applySessionBindings(items);
|
|
51
|
+
return { payload, items };
|
|
52
|
+
};
|
|
53
|
+
const resolveExecutorSession = async (node) => {
|
|
54
|
+
const pinnedSessionId = node.executor.sessionId?.trim();
|
|
55
|
+
if (pinnedSessionId) {
|
|
56
|
+
return { sessionId: pinnedSessionId, agentId: node.executor.agentId };
|
|
57
|
+
}
|
|
58
|
+
const primary = executorSessionByAgentId.get(node.executor.agentId);
|
|
59
|
+
if (primary) {
|
|
60
|
+
return { sessionId: primary, agentId: node.executor.agentId };
|
|
61
|
+
}
|
|
62
|
+
if (node.executor.fallbackAgentId) {
|
|
63
|
+
const fallback = executorSessionByAgentId.get(node.executor.fallbackAgentId);
|
|
64
|
+
if (fallback) {
|
|
65
|
+
return { sessionId: fallback, agentId: node.executor.fallbackAgentId };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await refreshSessionsFromGateway();
|
|
69
|
+
const refreshedPrimary = executorSessionByAgentId.get(node.executor.agentId);
|
|
70
|
+
if (refreshedPrimary) {
|
|
71
|
+
return { sessionId: refreshedPrimary, agentId: node.executor.agentId };
|
|
72
|
+
}
|
|
73
|
+
if (node.executor.fallbackAgentId) {
|
|
74
|
+
const refreshedFallback = executorSessionByAgentId.get(node.executor.fallbackAgentId);
|
|
75
|
+
if (refreshedFallback) {
|
|
76
|
+
return { sessionId: refreshedFallback, agentId: node.executor.fallbackAgentId };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
const onGatewayFrame = (frame) => {
|
|
82
|
+
agentActivityTracker.handleFrame(frame);
|
|
83
|
+
toolActivityLogger.handleFrame(frame);
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
getExecutorSessionByAgentId: () => executorSessionByAgentId,
|
|
87
|
+
getSessionCache: () => sessionCache,
|
|
88
|
+
resolveExecutorSession,
|
|
89
|
+
refreshSessionsFromGateway,
|
|
90
|
+
onGatewayFrame,
|
|
91
|
+
dispose: () => {
|
|
92
|
+
agentActivityTracker.dispose();
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
exports.createSessionRegistry = createSessionRegistry;
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStructuredNodeRunner = exports.resolveActiveBatchKeywordsForInstruction = void 0;
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const artifact_storage_1 = require("../artifact-storage");
|
|
7
|
+
const structured_output_1 = require("../structured-output");
|
|
8
|
+
const routes_1 = require("../workflow/routes");
|
|
9
|
+
const identity_1 = require("../identity");
|
|
10
|
+
const RUNTIME_KEYWORDS_PLACEHOLDER = "{{RUNTIME_KEYWORDS_JSON}}";
|
|
11
|
+
const injectRuntimeKeywordsToInstruction = (instruction, keywords) => {
|
|
12
|
+
if (keywords.length === 0)
|
|
13
|
+
return instruction;
|
|
14
|
+
const keywordJson = JSON.stringify(keywords, null, 2);
|
|
15
|
+
if (instruction.includes(RUNTIME_KEYWORDS_PLACEHOLDER)) {
|
|
16
|
+
return instruction.replaceAll(RUNTIME_KEYWORDS_PLACEHOLDER, keywordJson);
|
|
17
|
+
}
|
|
18
|
+
const arrayPattern = /\[\s*(?:"[^"\n]{1,400}"\s*,\s*){10,}"[^"\n]{1,400}"\s*\]/gs;
|
|
19
|
+
const matchedArray = instruction.match(arrayPattern);
|
|
20
|
+
if (matchedArray && matchedArray.length > 0) {
|
|
21
|
+
return instruction.replace(arrayPattern, keywordJson);
|
|
22
|
+
}
|
|
23
|
+
return `${instruction}\n\n本批次关键词(JSON数组):\n${keywordJson}`;
|
|
24
|
+
};
|
|
25
|
+
const resolveActiveBatchKeywordsForInstruction = (activeBatchKeywordItems, isSourceEntryNode) => {
|
|
26
|
+
// 只有真正处于批跑中的入口节点,才允许把关键词池注入到提示词。
|
|
27
|
+
// 普通单运行虽然默认 itemKey 可能是 global,但它只是运行项标识,不应被当成批次关键词展示。
|
|
28
|
+
if (!isSourceEntryNode || !activeBatchKeywordItems || activeBatchKeywordItems.length === 0) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
return Array.from(new Set(activeBatchKeywordItems.map((item) => item.trim()).filter(Boolean)));
|
|
32
|
+
};
|
|
33
|
+
exports.resolveActiveBatchKeywordsForInstruction = resolveActiveBatchKeywordsForInstruction;
|
|
34
|
+
const shouldParseFrameForEnvelope = (frame) => {
|
|
35
|
+
if (frame.type === "res")
|
|
36
|
+
return true;
|
|
37
|
+
if (frame.type === "req")
|
|
38
|
+
return false;
|
|
39
|
+
if (frame.type !== "event")
|
|
40
|
+
return false;
|
|
41
|
+
if (frame.event === "chat") {
|
|
42
|
+
const p = frame.payload;
|
|
43
|
+
const state = typeof p?.state === "string" ? p.state.toLowerCase() : "";
|
|
44
|
+
if (state === "final" || state === "done" || state === "end" || state === "completed")
|
|
45
|
+
return true;
|
|
46
|
+
if (typeof p?.data === "object" && p.data !== null)
|
|
47
|
+
return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
if (frame.event === "agent") {
|
|
51
|
+
const p = frame.payload;
|
|
52
|
+
const stream = typeof p?.stream === "string" ? p.stream : "";
|
|
53
|
+
if (stream === "lifecycle")
|
|
54
|
+
return false;
|
|
55
|
+
if (stream === "tool" || stream === "item" || stream === "command_output") {
|
|
56
|
+
if (p && typeof p === "object") {
|
|
57
|
+
if (p.version === "2.0" && Array.isArray(p.artifacts))
|
|
58
|
+
return true;
|
|
59
|
+
const data = p.data;
|
|
60
|
+
if (data && typeof data === "object") {
|
|
61
|
+
const d = data;
|
|
62
|
+
if (d.version === "2.0" && Array.isArray(d.artifacts))
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
};
|
|
72
|
+
const createStructuredNodeRunner = (options) => {
|
|
73
|
+
const emitter = new node_events_1.EventEmitter();
|
|
74
|
+
emitter.setMaxListeners(32);
|
|
75
|
+
// 同一个 agent session 会复用多轮对话。
|
|
76
|
+
// 这里按 sessionId + agent runId 记录生命周期,避免上一轮尾部的 end/error 事件误伤下一轮新请求。
|
|
77
|
+
const sessionRuns = new Map();
|
|
78
|
+
const findStringByKeys = (value, keys, depth = 0) => {
|
|
79
|
+
if (depth > 5 || value === null || value === undefined)
|
|
80
|
+
return null;
|
|
81
|
+
if (typeof value === "string")
|
|
82
|
+
return value.trim() || null;
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
for (const item of value) {
|
|
85
|
+
const found = findStringByKeys(item, keys, depth + 1);
|
|
86
|
+
if (found)
|
|
87
|
+
return found;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (typeof value !== "object")
|
|
92
|
+
return null;
|
|
93
|
+
const record = value;
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
const raw = record[key];
|
|
96
|
+
if (typeof raw === "string" && raw.trim())
|
|
97
|
+
return raw.trim();
|
|
98
|
+
}
|
|
99
|
+
for (const nested of Object.values(record)) {
|
|
100
|
+
const found = findStringByKeys(nested, keys, depth + 1);
|
|
101
|
+
if (found)
|
|
102
|
+
return found;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
};
|
|
106
|
+
const inferLifecycle = (payload) => {
|
|
107
|
+
const marker = (findStringByKeys(payload, ["status", "state", "phase", "event", "type", "kind"]) ?? "").toLowerCase();
|
|
108
|
+
if (!marker)
|
|
109
|
+
return "unknown";
|
|
110
|
+
if (marker.includes("start") ||
|
|
111
|
+
marker.includes("running") ||
|
|
112
|
+
marker.includes("in_progress") ||
|
|
113
|
+
marker.includes("processing") ||
|
|
114
|
+
marker.includes("stream")) {
|
|
115
|
+
return "start";
|
|
116
|
+
}
|
|
117
|
+
if (marker.includes("done") ||
|
|
118
|
+
marker.includes("finish") ||
|
|
119
|
+
marker.includes("complete") ||
|
|
120
|
+
marker.includes("success") ||
|
|
121
|
+
marker.includes("failed") ||
|
|
122
|
+
marker.includes("error") ||
|
|
123
|
+
marker.includes("idle") ||
|
|
124
|
+
marker.includes("stop")) {
|
|
125
|
+
return "end";
|
|
126
|
+
}
|
|
127
|
+
return "unknown";
|
|
128
|
+
};
|
|
129
|
+
const rememberSessionRunEvent = (sessionId, runId, lifecycle) => {
|
|
130
|
+
if (!sessionId || !runId)
|
|
131
|
+
return;
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
const runMap = sessionRuns.get(sessionId) ?? new Map();
|
|
134
|
+
const current = runMap.get(runId) ?? {
|
|
135
|
+
firstSeenAt: now,
|
|
136
|
+
lastSeenAt: now,
|
|
137
|
+
completedAt: null,
|
|
138
|
+
};
|
|
139
|
+
current.firstSeenAt = Math.min(current.firstSeenAt, now);
|
|
140
|
+
current.lastSeenAt = now;
|
|
141
|
+
if (lifecycle === "end") {
|
|
142
|
+
current.completedAt = now;
|
|
143
|
+
}
|
|
144
|
+
if (lifecycle === "start") {
|
|
145
|
+
current.completedAt = null;
|
|
146
|
+
}
|
|
147
|
+
runMap.set(runId, current);
|
|
148
|
+
// 每个 session 只保留最近少量 run 记录,避免长跑时无界增长。
|
|
149
|
+
// 驱逐前跳过仍在等待确认的活跃 run (completedAt 为 null),
|
|
150
|
+
// 防止 resolveCompletedAtForRequest 锁定的 runId 被误删导致超时。
|
|
151
|
+
const MAX_RUNS_PER_SESSION = 48;
|
|
152
|
+
if (runMap.size > MAX_RUNS_PER_SESSION) {
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const STALE_GRACE_MS = 5 * 60 * 1000;
|
|
155
|
+
const staleEntries = [...runMap.entries()]
|
|
156
|
+
.filter(([, snapshot]) => {
|
|
157
|
+
if (snapshot.completedAt === null)
|
|
158
|
+
return false;
|
|
159
|
+
return now - snapshot.completedAt > STALE_GRACE_MS;
|
|
160
|
+
})
|
|
161
|
+
.sort((a, b) => a[1].lastSeenAt - b[1].lastSeenAt);
|
|
162
|
+
const overflow = runMap.size - MAX_RUNS_PER_SESSION;
|
|
163
|
+
for (let i = 0; i < Math.min(overflow, staleEntries.length); i += 1) {
|
|
164
|
+
runMap.delete(staleEntries[i][0]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
sessionRuns.set(sessionId, runMap);
|
|
168
|
+
};
|
|
169
|
+
const resolveCompletedAtForRequest = (sessionId, requestStartedAt, watch) => {
|
|
170
|
+
const runMap = sessionRuns.get(sessionId);
|
|
171
|
+
if (!runMap || runMap.size === 0)
|
|
172
|
+
return null;
|
|
173
|
+
if (!watch.lockedRunId) {
|
|
174
|
+
// 优先锁定"首次出现时间晚于本次请求发出时间"的 runId。
|
|
175
|
+
// 这样可以避免上一轮请求的尾部 end/error 在新请求刚开始时被错认成当前轮次结束。
|
|
176
|
+
const currentRun = [...runMap.entries()]
|
|
177
|
+
.filter(([, snapshot]) => snapshot.firstSeenAt >= requestStartedAt)
|
|
178
|
+
.sort((a, b) => a[1].firstSeenAt - b[1].firstSeenAt)[0];
|
|
179
|
+
if (currentRun) {
|
|
180
|
+
watch.lockedRunId = currentRun[0];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!watch.lockedRunId)
|
|
184
|
+
return null;
|
|
185
|
+
const snapshot = runMap.get(watch.lockedRunId);
|
|
186
|
+
if (!snapshot)
|
|
187
|
+
return null;
|
|
188
|
+
if (snapshot.completedAt === null || snapshot.completedAt < requestStartedAt)
|
|
189
|
+
return null;
|
|
190
|
+
return snapshot.completedAt;
|
|
191
|
+
};
|
|
192
|
+
const rememberSessionCompletion = (frame) => {
|
|
193
|
+
if (frame.type !== "event")
|
|
194
|
+
return;
|
|
195
|
+
const payload = frame.payload;
|
|
196
|
+
if (!payload)
|
|
197
|
+
return;
|
|
198
|
+
const sessionId = findStringByKeys(payload, ["sessionKey", "sessionId", "key", "session"]);
|
|
199
|
+
const runId = findStringByKeys(payload, ["runId"]);
|
|
200
|
+
if (!sessionId)
|
|
201
|
+
return;
|
|
202
|
+
if (frame.event === "agent") {
|
|
203
|
+
const stream = typeof payload.stream === "string" ? payload.stream : "";
|
|
204
|
+
if (stream === "tool" || stream === "item" || stream === "command_output")
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (frame.event === "agent") {
|
|
208
|
+
const stream = typeof payload.stream === "string" ? payload.stream : "";
|
|
209
|
+
if (stream === "lifecycle") {
|
|
210
|
+
rememberSessionRunEvent(sessionId, runId, inferLifecycle(payload.data));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
// assistant/tool/item 等流虽然不代表完成,但能提供当前轮次的 runId。
|
|
214
|
+
// 这里只做"看见过该 run"的记录,完成仍以 lifecycle/chat.final 为准。
|
|
215
|
+
rememberSessionRunEvent(sessionId, runId, "unknown");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (frame.event === "chat") {
|
|
219
|
+
rememberSessionRunEvent(sessionId, runId, "unknown");
|
|
220
|
+
const state = (findStringByKeys(payload, ["state"]) ?? "").toLowerCase();
|
|
221
|
+
if (state === "final" || state === "done" || state === "end" || state === "completed") {
|
|
222
|
+
rememberSessionRunEvent(sessionId, runId, "end");
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const buildNodePrompt = async (node, sessionId, itemKey, dependencyIds) => {
|
|
227
|
+
const run = options.getRun();
|
|
228
|
+
const requestId = (0, identity_1.buildRequestId)(node.id);
|
|
229
|
+
const effectiveDependencyIds = dependencyIds?.length
|
|
230
|
+
? Array.from(new Set(dependencyIds.map((id) => id.trim()).filter(Boolean)))
|
|
231
|
+
: node.dependsOn;
|
|
232
|
+
const workflowNode = options.graph.getWorkflowNodeById(node.id);
|
|
233
|
+
const retryBackoffMs = workflowNode?.retryPolicy.backoffMs ?? 0;
|
|
234
|
+
const parallelGroup = options.graph.getParallelGroupByMemberNodeId(node.id);
|
|
235
|
+
// 批处理关键词只应注入真正的入口节点。并行组成员虽然通常没有 direct incoming edge,
|
|
236
|
+
// 但它们的依赖挂在 group 上,只看 node 自身入边会把 group 成员误判为 source entry。
|
|
237
|
+
const isSourceEntryNode = options.graph.getIncomingEdges(node.id).length === 0 &&
|
|
238
|
+
(!parallelGroup || options.graph.getIncomingEdges(parallelGroup.id).length === 0);
|
|
239
|
+
const effectiveRuntimeKeywordItems = (0, exports.resolveActiveBatchKeywordsForInstruction)(options.getActiveBatchKeywordItems(), isSourceEntryNode);
|
|
240
|
+
const effectiveInstruction = effectiveRuntimeKeywordItems.length > 0
|
|
241
|
+
? injectRuntimeKeywordsToInstruction(node.instruction, effectiveRuntimeKeywordItems)
|
|
242
|
+
: node.instruction;
|
|
243
|
+
const dependencyArtifacts = await (0, structured_output_1.buildDependencyArtifactInputs)(run, node, itemKey, effectiveDependencyIds);
|
|
244
|
+
// Load external pipeline artifact for pipeline_link triggered runs (entry nodes only)
|
|
245
|
+
let externalPipelineArtifact = null;
|
|
246
|
+
if (run.input?.trigger === "pipeline_link" && isSourceEntryNode) {
|
|
247
|
+
externalPipelineArtifact = await (0, structured_output_1.buildExternalPipelineArtifactInput)(run.input.upstreamOutput);
|
|
248
|
+
if (!externalPipelineArtifact) {
|
|
249
|
+
throw new Error("external_pipeline_artifact_read_failed");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (effectiveDependencyIds.length > 0) {
|
|
253
|
+
options.runtimeStore.pushTimeline(`节点 ${node.id} 已加载上游产物 ${dependencyArtifacts.length} 条(来自: ${effectiveDependencyIds.join(",")})`);
|
|
254
|
+
}
|
|
255
|
+
const allowedRoutes = workflowNode?.routePolicy?.allowed ?? [];
|
|
256
|
+
const routeTargets = options.graph.getOutgoingEdges(node.id)
|
|
257
|
+
.filter((edge) => edge.when && edge.when !== routes_1.MAINLINE_ROUTE_VALUE)
|
|
258
|
+
.map((edge) => {
|
|
259
|
+
const targetWorkflowNode = options.graph.getWorkflowNodeById(edge.to);
|
|
260
|
+
const targetWorkflowGroup = options.graph.getWorkflowGroupById(edge.to);
|
|
261
|
+
const targetNode = options.getNodeById(edge.to);
|
|
262
|
+
return {
|
|
263
|
+
route: edge.when ?? "",
|
|
264
|
+
targetNodeId: edge.to,
|
|
265
|
+
targetNodeTitle: targetWorkflowGroup
|
|
266
|
+
? `并行组 ${targetWorkflowGroup.id}`
|
|
267
|
+
: targetNode?.title ?? targetWorkflowNode?.name ?? edge.to,
|
|
268
|
+
targetAgentId: targetWorkflowGroup
|
|
269
|
+
? targetWorkflowGroup.id
|
|
270
|
+
: targetNode?.executor.agentId ?? targetWorkflowNode?.executor.agentId ?? "-",
|
|
271
|
+
lane: targetWorkflowGroup ? "group" : targetWorkflowNode?.lane ?? "main",
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
const ctx = {
|
|
275
|
+
runId: run.id, nodeId: node.id, nodeTitle: node.title,
|
|
276
|
+
requestId, sessionId,
|
|
277
|
+
dependencies: effectiveDependencyIds,
|
|
278
|
+
dependencyArtifacts,
|
|
279
|
+
externalPipelineArtifact,
|
|
280
|
+
outputSpec: node.outputSpec,
|
|
281
|
+
instruction: effectiveInstruction,
|
|
282
|
+
allowReject: node.allowReject,
|
|
283
|
+
maxRejectCount: node.maxRejectCount,
|
|
284
|
+
rejectFeedbacks: node.rejectFeedbacks ?? [],
|
|
285
|
+
allowedRoutes, routeTargets,
|
|
286
|
+
};
|
|
287
|
+
return { requestId, ctx, effectiveDependencyIds, dependencyArtifacts, allowedRoutes, routeTargets, retryBackoffMs };
|
|
288
|
+
};
|
|
289
|
+
const sendAndWaitForEnvelope = async (prompt, sessionId, nodeId, validationCtx, lastViolation, requestStartedAt, requestRunWatch, signal) => {
|
|
290
|
+
// DAG 节点发送只保留 chat.send 单一路径,避免不同发送 API 的兼容补发把同一 requestId 投递两次。
|
|
291
|
+
const idempotencyKey = (0, node_crypto_1.randomUUID)();
|
|
292
|
+
let payload;
|
|
293
|
+
try {
|
|
294
|
+
payload = await options.client.sendReq("chat.send", { sessionKey: sessionId, message: prompt }, {
|
|
295
|
+
sideEffect: true,
|
|
296
|
+
timeoutMs: options.pipelineNodeExecutionTimeoutMs,
|
|
297
|
+
idempotencyKey,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
options.runtimeStore.pushTimeline(`节点 ${nodeId} 发送失败(chat.send): ${String(error)}`, "error");
|
|
302
|
+
throw new Error(`openclaw_send_failed:chat.send:${String(error)}`);
|
|
303
|
+
}
|
|
304
|
+
options.runtimeStore.pushTimeline(`节点 ${nodeId} 已发送请求,等待结构化回执...`);
|
|
305
|
+
const envelope = await (0, structured_output_1.waitForStructuredEnvelope)(emitter, validationCtx, lastViolation, () => resolveCompletedAtForRequest(sessionId, requestStartedAt, requestRunWatch), signal);
|
|
306
|
+
return envelope;
|
|
307
|
+
};
|
|
308
|
+
const validateAndRetryEnvelope = async (ctx, sessionId, node, allowedRoutes, retryBackoffMs = 0, signal) => {
|
|
309
|
+
const validationCtx = {
|
|
310
|
+
runId: ctx.runId,
|
|
311
|
+
nodeId: node.id,
|
|
312
|
+
requestId: ctx.requestId,
|
|
313
|
+
sessionId,
|
|
314
|
+
outputSpec: node.outputSpec,
|
|
315
|
+
allowedRoutes,
|
|
316
|
+
requireRouteContent: allowedRoutes.length > 0,
|
|
317
|
+
};
|
|
318
|
+
let lastViolation = null;
|
|
319
|
+
// 纠错重试属于同一次节点请求的补正,不应更换 requestId。
|
|
320
|
+
// 否则模型即使按上一轮上下文修正成功,也会因为顶层 requestId 变化被误判为 mismatch。
|
|
321
|
+
for (let attemptIndex = 0; attemptIndex < 2; attemptIndex += 1) {
|
|
322
|
+
if (attemptIndex > 0 && retryBackoffMs > 0) {
|
|
323
|
+
await new Promise(resolve => setTimeout(resolve, retryBackoffMs));
|
|
324
|
+
}
|
|
325
|
+
try {
|
|
326
|
+
const requestStartedAt = Date.now();
|
|
327
|
+
const requestRunWatch = { lockedRunId: null };
|
|
328
|
+
const prompt = attemptIndex === 0 || !lastViolation
|
|
329
|
+
? (0, structured_output_1.createNodeExecutionPrompt)(ctx)
|
|
330
|
+
: (0, structured_output_1.createNodeCorrectionPrompt)(ctx, lastViolation);
|
|
331
|
+
const envelope = await sendAndWaitForEnvelope(prompt, sessionId, node.id, validationCtx, lastViolation, requestStartedAt, requestRunWatch, signal);
|
|
332
|
+
return envelope;
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
const violation = (0, structured_output_1.extractViolationCode)(error);
|
|
336
|
+
if (violation) {
|
|
337
|
+
lastViolation = violation;
|
|
338
|
+
if (attemptIndex === 0) {
|
|
339
|
+
options.runtimeStore.pushTimeline(`节点 ${node.id} 回执校验失败(${violation}),自动纠正重试中...`, "warn");
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
options.runtimeStore.pushTimeline(`节点 ${node.id} 回执校验失败(${violation}),纠正后仍未通过,标记失败`, "error");
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
throw new Error(`contract_violation:${lastViolation ?? "result_envelope_missing"}`);
|
|
348
|
+
};
|
|
349
|
+
const persistEnvelopeArtifacts = async (envelope, runId, nodeId, requestId, sessionId) => {
|
|
350
|
+
const savedAt = new Date();
|
|
351
|
+
const status = envelope.status === "failed" ? "failed" : "success";
|
|
352
|
+
const batchRunId = options.getBatchRunId?.();
|
|
353
|
+
// envelope 文件通过 persistEnvelopeFile 写入并追加索引
|
|
354
|
+
await (0, artifact_storage_1.persistEnvelopeFile)(options.artifactDir, status, { runId, batchRunId, nodeId, requestId, pipelineId: options.pipelineId }, envelope, { savedAt });
|
|
355
|
+
const normalizedArtifacts = [];
|
|
356
|
+
for (let i = 0; i < envelope.artifacts.length; i += 1) {
|
|
357
|
+
const artifact = envelope.artifacts[i];
|
|
358
|
+
const safeType = artifact.type.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
359
|
+
const manifest = await (0, artifact_storage_1.persistArtifactFile)(options.artifactDir, status, {
|
|
360
|
+
runId,
|
|
361
|
+
pipelineId: options.pipelineId,
|
|
362
|
+
batchRunId,
|
|
363
|
+
nodeId,
|
|
364
|
+
requestId,
|
|
365
|
+
kind: "artifact",
|
|
366
|
+
}, {
|
|
367
|
+
type: artifact.type,
|
|
368
|
+
schemaVersion: artifact.schemaVersion,
|
|
369
|
+
name: artifact.name ?? `artifact-${i + 1}`,
|
|
370
|
+
content: artifact.content,
|
|
371
|
+
meta: artifact.meta,
|
|
372
|
+
}, { savedAt, fileNameSuffix: `${i + 1}-${safeType}` });
|
|
373
|
+
normalizedArtifacts.push(manifest);
|
|
374
|
+
}
|
|
375
|
+
return normalizedArtifacts;
|
|
376
|
+
};
|
|
377
|
+
const runNodeViaStructuredOutput = async (node, sessionId, itemKey, dependencyIds, signal) => {
|
|
378
|
+
const { requestId, ctx, allowedRoutes, retryBackoffMs } = await buildNodePrompt(node, sessionId, itemKey, dependencyIds);
|
|
379
|
+
const envelope = await validateAndRetryEnvelope(ctx, sessionId, node, allowedRoutes, retryBackoffMs, signal);
|
|
380
|
+
const artifacts = await persistEnvelopeArtifacts(envelope, ctx.runId, node.id, requestId, sessionId);
|
|
381
|
+
return { envelope, artifacts };
|
|
382
|
+
};
|
|
383
|
+
const rememberGatewayFrame = (frame) => {
|
|
384
|
+
if (frame.type === "req")
|
|
385
|
+
return;
|
|
386
|
+
if (shouldParseFrameForEnvelope(frame)) {
|
|
387
|
+
const source = frame.type === "event" ? `event:${frame.event}` : "res";
|
|
388
|
+
const candidates = (0, structured_output_1.collectEnvelopeCandidates)(frame.payload);
|
|
389
|
+
const now = Date.now();
|
|
390
|
+
for (const envelope of candidates) {
|
|
391
|
+
emitter.emit("candidate", { envelope, observedAt: now, source });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
rememberSessionCompletion(frame);
|
|
395
|
+
};
|
|
396
|
+
return {
|
|
397
|
+
runNodeViaStructuredOutput,
|
|
398
|
+
rememberGatewayFrame,
|
|
399
|
+
hasActiveSession: (sessionKey) => {
|
|
400
|
+
const runMap = sessionRuns.get(sessionKey);
|
|
401
|
+
if (!runMap || runMap.size === 0)
|
|
402
|
+
return false;
|
|
403
|
+
for (const snapshot of runMap.values()) {
|
|
404
|
+
if (snapshot.completedAt === null)
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
return false;
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
exports.createStructuredNodeRunner = createStructuredNodeRunner;
|