@fieldwangai/agentflow 0.1.25
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 +21 -0
- package/README.md +201 -0
- package/README.zh-CN.md +201 -0
- package/agents/agentflow-node-executor-code.md +32 -0
- package/agents/agentflow-node-executor-planning.md +32 -0
- package/agents/agentflow-node-executor-requirement.md +32 -0
- package/agents/agentflow-node-executor-test.md +32 -0
- package/agents/agentflow-node-executor-ui.md +32 -0
- package/agents/agentflow-node-executor.md +32 -0
- package/agents/agents.json +8 -0
- package/agents/en/agentflow-node-executor.md +32 -0
- package/agents/zh/agentflow-node-executor.md +32 -0
- package/bin/agentflow.mjs +52 -0
- package/bin/ensure-workspace-reference.mjs +35 -0
- package/bin/lib/agent-runners.mjs +1199 -0
- package/bin/lib/agents-path.mjs +61 -0
- package/bin/lib/api-runner.mjs +361 -0
- package/bin/lib/apply.mjs +852 -0
- package/bin/lib/catalog-agents.mjs +300 -0
- package/bin/lib/catalog-flows.mjs +532 -0
- package/bin/lib/composer-agent.mjs +884 -0
- package/bin/lib/composer-flow-instances.mjs +68 -0
- package/bin/lib/composer-flow-skeleton.mjs +334 -0
- package/bin/lib/composer-flow-validate.mjs +47 -0
- package/bin/lib/composer-log.mjs +197 -0
- package/bin/lib/composer-model-router.mjs +160 -0
- package/bin/lib/composer-node-schema.mjs +299 -0
- package/bin/lib/composer-planner.mjs +749 -0
- package/bin/lib/composer-script-ops.mjs +233 -0
- package/bin/lib/composer-skill-router.mjs +384 -0
- package/bin/lib/flow-import.mjs +305 -0
- package/bin/lib/flow-normalize.mjs +71 -0
- package/bin/lib/flow-write.mjs +395 -0
- package/bin/lib/help.mjs +139 -0
- package/bin/lib/hub-login.mjs +54 -0
- package/bin/lib/hub-publish.mjs +159 -0
- package/bin/lib/hub-remote.mjs +189 -0
- package/bin/lib/hub.mjs +299 -0
- package/bin/lib/i18n.mjs +233 -0
- package/bin/lib/locales/en.json +344 -0
- package/bin/lib/locales/zh.json +344 -0
- package/bin/lib/log.mjs +37 -0
- package/bin/lib/main.mjs +611 -0
- package/bin/lib/model-config.mjs +118 -0
- package/bin/lib/model-lists.mjs +188 -0
- package/bin/lib/node-exec-context.mjs +336 -0
- package/bin/lib/node-execute.mjs +513 -0
- package/bin/lib/normalize-node-tool-command.mjs +97 -0
- package/bin/lib/paths.mjs +216 -0
- package/bin/lib/pipeline-scripts.mjs +41 -0
- package/bin/lib/recent-runs.mjs +173 -0
- package/bin/lib/run-apply-active-lock.mjs +82 -0
- package/bin/lib/run-events.mjs +85 -0
- package/bin/lib/run-node-statuses-from-disk.mjs +85 -0
- package/bin/lib/schedule-config.mjs +227 -0
- package/bin/lib/scheduler.mjs +312 -0
- package/bin/lib/table.mjs +4 -0
- package/bin/lib/terminal.mjs +42 -0
- package/bin/lib/ui-print.mjs +94 -0
- package/bin/lib/ui-server.mjs +2113 -0
- package/bin/lib/workspace-tree.mjs +266 -0
- package/bin/lib/workspace.mjs +180 -0
- package/bin/pipeline/build-node-prompt.mjs +179 -0
- package/bin/pipeline/check-cache.mjs +191 -0
- package/bin/pipeline/check-flow.mjs +543 -0
- package/bin/pipeline/collect-nodes.mjs +212 -0
- package/bin/pipeline/compute-cache-md5.mjs +177 -0
- package/bin/pipeline/ensure-run-dir.mjs +71 -0
- package/bin/pipeline/extract-thinking.mjs +308 -0
- package/bin/pipeline/gc.mjs +129 -0
- package/bin/pipeline/get-env.mjs +83 -0
- package/bin/pipeline/get-exec-id.mjs +145 -0
- package/bin/pipeline/get-ready-nodes.mjs +435 -0
- package/bin/pipeline/get-resolved-values.mjs +337 -0
- package/bin/pipeline/load-key.mjs +62 -0
- package/bin/pipeline/parse-bool.mjs +33 -0
- package/bin/pipeline/parse-flow.mjs +698 -0
- package/bin/pipeline/post-process-control-if.mjs +23 -0
- package/bin/pipeline/post-process-node.mjs +490 -0
- package/bin/pipeline/pre-process-node.mjs +449 -0
- package/bin/pipeline/resolve-inputs.mjs +201 -0
- package/bin/pipeline/run-log.mjs +34 -0
- package/bin/pipeline/run-tool-nodejs.mjs +160 -0
- package/bin/pipeline/save-key.mjs +93 -0
- package/bin/pipeline/snapshot-prior-round.mjs +70 -0
- package/bin/pipeline/validate-flow.mjs +825 -0
- package/bin/pipeline/validate-for-ui.mjs +226 -0
- package/bin/pipeline/validate-script-output.mjs +130 -0
- package/bin/pipeline/write-result.mjs +182 -0
- package/builtin/nodes/agent_subAgent.md +14 -0
- package/builtin/nodes/control_agent_toBool.md +20 -0
- package/builtin/nodes/control_anyOne.md +17 -0
- package/builtin/nodes/control_end.md +11 -0
- package/builtin/nodes/control_if.md +20 -0
- package/builtin/nodes/control_start.md +11 -0
- package/builtin/nodes/control_toBool.md +21 -0
- package/builtin/nodes/provide_file.md +11 -0
- package/builtin/nodes/provide_str.md +11 -0
- package/builtin/nodes/tool_get_env.md +14 -0
- package/builtin/nodes/tool_load_key.md +20 -0
- package/builtin/nodes/tool_nodejs.md +40 -0
- package/builtin/nodes/tool_print.md +14 -0
- package/builtin/nodes/tool_save_key.md +20 -0
- package/builtin/nodes/tool_user_ask.md +23 -0
- package/builtin/nodes/tool_user_check.md +22 -0
- package/builtin/pipelines/module-migrate/flow.yaml +819 -0
- package/builtin/pipelines/module-migrate/scripts/check_imports.mjs +700 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Makefile +362 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o.d +17 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o.d +5 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o.d +8 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/tree_sitter_kotlin_binding.node.d +1 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/tree_sitter_kotlin_binding.node +0 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/binding.Makefile +6 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/gyp-mac-tool +768 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_except.target.mk +126 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_maybe.target.mk +122 -0
- package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/tree_sitter_kotlin_binding.target.mk +203 -0
- package/builtin/pipelines/new/flow.yaml +545 -0
- package/builtin/pipelines/new/scripts/check-flow.mjs +9 -0
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +211 -0
- package/builtin/pipelines/scripts/adjust-node-positions.mjs +113 -0
- package/builtin/web-ui/dist/agentflow-icon.svg +23 -0
- package/builtin/web-ui/dist/assets/index-CZkUPcXE.css +1 -0
- package/builtin/web-ui/dist/assets/index-DkkhNESc.js +190 -0
- package/builtin/web-ui/dist/index.html +24 -0
- package/package.json +67 -0
- package/reference/flow-control-capabilities.md +274 -0
- package/reference/flow-layout.md +84 -0
- package/reference/flow-prompt-handler-check.md +12 -0
- package/reference/flow-result-semantics.md +14 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 从 agentflow 某次运行的 logs/log.txt 中:
|
|
4
|
+
* 1. 提取 [cursor-stdout-raw] 里 type=thinking、subtype=delta 的 text,按 session_id 聚合
|
|
5
|
+
* 2. 每个 session 下附带 node-id(instanceId)以及传给该节点的 prompt(路径 + 完整内容或前 800 字)
|
|
6
|
+
* 3. 保留节点执行情况(node-start / node-done / result status)
|
|
7
|
+
* 输出写入该次 run 的 logs/thinking_by_session_and_nodes.md
|
|
8
|
+
*
|
|
9
|
+
* 用法(apply -ai 步骤):
|
|
10
|
+
* agentflow apply -ai extract-thinking <workspaceRoot> <flowName> <uuid>
|
|
11
|
+
* 用法(顶层命令由 CLI 调用):
|
|
12
|
+
* agentflow extract-thinking <flowName> <uuid>
|
|
13
|
+
* 等价于在工作区根目录执行上述 -ai 步骤。
|
|
14
|
+
*
|
|
15
|
+
* 输入:<runDir>/logs/log.txt
|
|
16
|
+
* 输出:<runDir>/logs/thinking_by_session_and_nodes.md
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
|
|
23
|
+
import { getRunDir } from "../lib/paths.mjs";
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
|
|
27
|
+
const LOG_REL = "logs/log.txt";
|
|
28
|
+
const OUT_REL = "logs/thinking_by_session_and_nodes.md";
|
|
29
|
+
|
|
30
|
+
/** 从 log 行提取 [tag] 后的 message 部分(用于 cli-raw 等非 JSON 行) */
|
|
31
|
+
function getMessageAfterTag(line, tag) {
|
|
32
|
+
const marker = `[${tag}]`;
|
|
33
|
+
const idx = line.indexOf(marker);
|
|
34
|
+
if (idx === -1) return null;
|
|
35
|
+
const after = line.slice(idx + marker.length).replace(/^\s+/, "");
|
|
36
|
+
return after || null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 判断是否为新的一条 log 行(以 [ISO 时间] 开头) */
|
|
40
|
+
function isNewLogLine(line) {
|
|
41
|
+
return /^\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(line.trim());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractThinking(logContent) {
|
|
45
|
+
const lines = logContent.split(/\r?\n/);
|
|
46
|
+
/** session_id -> { texts: string[], instanceId?, label?, promptPath?, promptPreview? } */
|
|
47
|
+
const thinkingBySession = Object.create(null);
|
|
48
|
+
const nodeRuns = [];
|
|
49
|
+
const resultStatus = Object.create(null);
|
|
50
|
+
let cursorRawLines = 0;
|
|
51
|
+
let thinkingDeltaCount = 0;
|
|
52
|
+
|
|
53
|
+
let currentInstanceId = null;
|
|
54
|
+
let currentLabel = null;
|
|
55
|
+
let currentPromptPath = null;
|
|
56
|
+
let currentPromptPreview = null;
|
|
57
|
+
let currentPromptFull = null;
|
|
58
|
+
|
|
59
|
+
const jsonMatch = (line) => {
|
|
60
|
+
const i = line.indexOf("{");
|
|
61
|
+
if (i === -1) return null;
|
|
62
|
+
return line.slice(i);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const line = lines[i];
|
|
67
|
+
|
|
68
|
+
const tag = line.includes("[cursor-stdout-raw]")
|
|
69
|
+
? "cursor-stdout-raw"
|
|
70
|
+
: line.includes("[claude-code-stdout-raw]")
|
|
71
|
+
? "claude-code-stdout-raw"
|
|
72
|
+
: line.includes("[cli]")
|
|
73
|
+
? "cli"
|
|
74
|
+
: line.includes("[result]")
|
|
75
|
+
? "result"
|
|
76
|
+
: line.includes("[cli-raw]")
|
|
77
|
+
? "cli-raw"
|
|
78
|
+
: null;
|
|
79
|
+
|
|
80
|
+
if (tag === "cli-raw") {
|
|
81
|
+
const msg = getMessageAfterTag(line, "cli-raw");
|
|
82
|
+
if (msg && msg.startsWith("Prompt: ")) {
|
|
83
|
+
currentPromptPath = msg.slice(8).trim();
|
|
84
|
+
} else {
|
|
85
|
+
const fullPrefixes = ["Cursor CLI prompt 完整", "Claude Code CLI prompt 完整"];
|
|
86
|
+
const previewPrefixes = ["Cursor CLI prompt 前 800 字", "Claude Code CLI prompt 前 800 字"];
|
|
87
|
+
const fullHit = msg ? fullPrefixes.find((p) => msg.startsWith(p)) : null;
|
|
88
|
+
const previewHit = msg && !fullHit ? previewPrefixes.find((p) => msg.startsWith(p)) : null;
|
|
89
|
+
if (fullHit || previewHit) {
|
|
90
|
+
const prefix = fullHit || previewHit;
|
|
91
|
+
const afterPrefix = msg.slice(prefix.length).replace(/^[:\s]*\n?/, "");
|
|
92
|
+
const rest = [afterPrefix];
|
|
93
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
94
|
+
const next = lines[j];
|
|
95
|
+
if (isNewLogLine(next)) break;
|
|
96
|
+
rest.push(next);
|
|
97
|
+
}
|
|
98
|
+
const content = rest.join("\n").trim();
|
|
99
|
+
if (fullHit) currentPromptFull = content;
|
|
100
|
+
else currentPromptPreview = content;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const raw = jsonMatch(line);
|
|
107
|
+
if (!raw) continue;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
if (tag === "cursor-stdout-raw") {
|
|
111
|
+
cursorRawLines += 1;
|
|
112
|
+
const obj = JSON.parse(raw);
|
|
113
|
+
const isThinkingDelta = obj.type === "thinking" && obj.subtype === "delta" && obj.text != null && obj.session_id;
|
|
114
|
+
const isThinkingOther = obj.type === "thinking" && obj.session_id && (obj.text != null || (obj.content && typeof obj.content === "string"));
|
|
115
|
+
if (isThinkingDelta || isThinkingOther) {
|
|
116
|
+
thinkingDeltaCount += 1;
|
|
117
|
+
const sid = obj.session_id;
|
|
118
|
+
const text = obj.text != null ? obj.text : (obj.content || "");
|
|
119
|
+
if (!text && isThinkingOther) continue;
|
|
120
|
+
if (!thinkingBySession[sid]) {
|
|
121
|
+
thinkingBySession[sid] = {
|
|
122
|
+
texts: [],
|
|
123
|
+
instanceId: currentInstanceId ?? undefined,
|
|
124
|
+
label: currentLabel ?? undefined,
|
|
125
|
+
promptPath: currentPromptPath ?? undefined,
|
|
126
|
+
promptPreview: currentPromptPreview ?? undefined,
|
|
127
|
+
promptFull: currentPromptFull ?? undefined,
|
|
128
|
+
};
|
|
129
|
+
if (currentInstanceId) {
|
|
130
|
+
for (let r = nodeRuns.length - 1; r >= 0; r--) {
|
|
131
|
+
if (nodeRuns[r].instanceId === currentInstanceId && nodeRuns[r].session_id == null) {
|
|
132
|
+
nodeRuns[r].session_id = sid;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
thinkingBySession[sid].texts.push(text);
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (tag === "claude-code-stdout-raw") {
|
|
144
|
+
cursorRawLines += 1;
|
|
145
|
+
const obj = JSON.parse(raw);
|
|
146
|
+
if (obj.type === "assistant" && obj.session_id && obj.message && Array.isArray(obj.message.content)) {
|
|
147
|
+
const sid = obj.session_id;
|
|
148
|
+
const thinkingBlocks = obj.message.content.filter(
|
|
149
|
+
(b) => b && b.type === "thinking" && typeof b.thinking === "string" && b.thinking.length > 0,
|
|
150
|
+
);
|
|
151
|
+
if (thinkingBlocks.length === 0) continue;
|
|
152
|
+
if (!thinkingBySession[sid]) {
|
|
153
|
+
thinkingBySession[sid] = {
|
|
154
|
+
texts: [],
|
|
155
|
+
instanceId: currentInstanceId ?? undefined,
|
|
156
|
+
label: currentLabel ?? undefined,
|
|
157
|
+
promptPath: currentPromptPath ?? undefined,
|
|
158
|
+
promptPreview: currentPromptPreview ?? undefined,
|
|
159
|
+
promptFull: currentPromptFull ?? undefined,
|
|
160
|
+
};
|
|
161
|
+
if (currentInstanceId) {
|
|
162
|
+
for (let r = nodeRuns.length - 1; r >= 0; r--) {
|
|
163
|
+
if (nodeRuns[r].instanceId === currentInstanceId && nodeRuns[r].session_id == null) {
|
|
164
|
+
nodeRuns[r].session_id = sid;
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const block of thinkingBlocks) {
|
|
171
|
+
thinkingDeltaCount += 1;
|
|
172
|
+
thinkingBySession[sid].texts.push(block.thinking);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (tag === "cli") {
|
|
179
|
+
const obj = JSON.parse(raw);
|
|
180
|
+
if (obj.event === "node-start" && obj.instanceId) {
|
|
181
|
+
currentInstanceId = obj.instanceId;
|
|
182
|
+
currentLabel = obj.label || obj.instanceId;
|
|
183
|
+
currentPromptPath = null;
|
|
184
|
+
currentPromptPreview = null;
|
|
185
|
+
currentPromptFull = null;
|
|
186
|
+
const start = {
|
|
187
|
+
instanceId: obj.instanceId,
|
|
188
|
+
label: obj.label || obj.instanceId,
|
|
189
|
+
startTime: line.slice(0, 30),
|
|
190
|
+
elapsed: null,
|
|
191
|
+
total: null,
|
|
192
|
+
status: null,
|
|
193
|
+
message: null,
|
|
194
|
+
session_id: null,
|
|
195
|
+
};
|
|
196
|
+
nodeRuns.push(start);
|
|
197
|
+
} else if (obj.event === "node-done" && obj.instanceId) {
|
|
198
|
+
const last = nodeRuns[nodeRuns.length - 1];
|
|
199
|
+
if (last && last.instanceId === obj.instanceId) {
|
|
200
|
+
last.elapsed = obj.elapsed ?? null;
|
|
201
|
+
last.total = obj.total ?? null;
|
|
202
|
+
}
|
|
203
|
+
const res = resultStatus[obj.instanceId];
|
|
204
|
+
if (res && last && last.instanceId === obj.instanceId) {
|
|
205
|
+
last.status = res.status;
|
|
206
|
+
last.message = res.message;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (tag === "result") {
|
|
213
|
+
const obj = JSON.parse(raw);
|
|
214
|
+
if (obj.instanceId) {
|
|
215
|
+
resultStatus[obj.instanceId] = { status: obj.status, message: obj.message || "" };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (_) {
|
|
219
|
+
// 非 JSON 或解析失败忽略
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const run of nodeRuns) {
|
|
224
|
+
if (run.status == null && resultStatus[run.instanceId]) {
|
|
225
|
+
run.status = resultStatus[run.instanceId].status;
|
|
226
|
+
run.message = resultStatus[run.instanceId].message;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { thinkingBySession, nodeRuns, stats: { totalLogLines: lines.length, cursorRawLines, thinkingDeltaCount } };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function toMarkdown(thinkingBySession, nodeRuns, stats = {}) {
|
|
234
|
+
const parts = [];
|
|
235
|
+
|
|
236
|
+
if (stats.totalLogLines != null || stats.cursorRawLines != null || stats.thinkingDeltaCount != null) {
|
|
237
|
+
parts.push("本文件由 extract-thinking 从 log.txt 提取。\n");
|
|
238
|
+
parts.push(`Log 总行数: ${stats.totalLogLines ?? "-"},[cursor-stdout-raw] 行数: ${stats.cursorRawLines ?? "-"},thinking delta 数: ${stats.thinkingDeltaCount ?? "-"},session 数: ${Object.keys(thinkingBySession).length}。\n`);
|
|
239
|
+
parts.push("(log 中大量为 check-cache / get-ready-nodes / cli 等;Cursor 与 Claude Code 流式输出的 thinking 会按 session 聚合。)\n\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
parts.push("# 节点执行情况\n");
|
|
243
|
+
parts.push("| instanceId | label | session_id | elapsed | total | status | message |\n");
|
|
244
|
+
parts.push("|------------|-------|------------|---------|-------|--------|--------|\n");
|
|
245
|
+
for (const r of nodeRuns) {
|
|
246
|
+
const msg = (r.message || "").replace(/\|/g, "\\|").slice(0, 60);
|
|
247
|
+
const sid = (r.session_id ?? "-").replace(/\|/g, "\\|");
|
|
248
|
+
parts.push(`| ${r.instanceId} | ${r.label || ""} | ${sid} | ${r.elapsed ?? "-"} | ${r.total ?? "-"} | ${r.status ?? "-"} | ${msg} |\n`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
parts.push("\n---\n# Thinking 按 session_id 聚合\n");
|
|
252
|
+
const sessionIds = Object.keys(thinkingBySession).sort();
|
|
253
|
+
for (const sid of sessionIds) {
|
|
254
|
+
const entry = thinkingBySession[sid];
|
|
255
|
+
const texts = entry.texts || [];
|
|
256
|
+
const full = texts.join("");
|
|
257
|
+
parts.push(`## session_id: ${sid}\n`);
|
|
258
|
+
if (entry.instanceId != null) {
|
|
259
|
+
parts.push(`- **node-id**: ${entry.instanceId}${entry.label != null && entry.label !== entry.instanceId ? ` (${entry.label})` : ""}\n`);
|
|
260
|
+
}
|
|
261
|
+
if (entry.promptPath != null) {
|
|
262
|
+
parts.push(`- **prompt 路径**: ${entry.promptPath}\n`);
|
|
263
|
+
}
|
|
264
|
+
if (entry.promptFull != null && entry.promptFull.length > 0) {
|
|
265
|
+
parts.push(`- **prompt 完整内容**:\n\`\`\`\n${entry.promptFull}\n\`\`\`\n`);
|
|
266
|
+
} else if (entry.promptPreview != null && entry.promptPreview.length > 0) {
|
|
267
|
+
parts.push(`- **prompt 前 800 字**:\n\`\`\`\n${entry.promptPreview}\n\`\`\`\n`);
|
|
268
|
+
}
|
|
269
|
+
parts.push(full.trim() || "(无文本)");
|
|
270
|
+
parts.push("\n\n");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return parts.join("");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function main() {
|
|
277
|
+
const args = process.argv.slice(2);
|
|
278
|
+
const workspaceRoot = args[0] ? path.resolve(args[0]) : process.cwd();
|
|
279
|
+
const flowName = args[1];
|
|
280
|
+
const uuid = args[2];
|
|
281
|
+
|
|
282
|
+
if (!flowName || !uuid) {
|
|
283
|
+
process.stderr.write("Usage: agentflow apply -ai extract-thinking <workspaceRoot> <flowName> <uuid>\n");
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
288
|
+
const logPath = path.join(runDir, LOG_REL);
|
|
289
|
+
const outPath = path.join(runDir, OUT_REL);
|
|
290
|
+
|
|
291
|
+
if (!existsSync(logPath)) {
|
|
292
|
+
process.stderr.write(`Log file not found: ${logPath}\n`);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const logContent = readFileSync(logPath, "utf8");
|
|
297
|
+
const { thinkingBySession, nodeRuns, stats } = extractThinking(logContent);
|
|
298
|
+
const md = toMarkdown(thinkingBySession, nodeRuns, stats);
|
|
299
|
+
|
|
300
|
+
const outDir = path.dirname(outPath);
|
|
301
|
+
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
302
|
+
writeFileSync(outPath, md, "utf8");
|
|
303
|
+
|
|
304
|
+
process.stdout.write(`Written: ${outPath}\n`);
|
|
305
|
+
process.stdout.write(`Nodes: ${nodeRuns.length} Sessions: ${Object.keys(thinkingBySession).length}\n`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
main();
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 清理 <workspaceRoot>/.workspace/agentflow/runBuild/ 下各 flowName 的 uuid 临时 run 目录。
|
|
4
|
+
* 用法:
|
|
5
|
+
* agentflow apply -ai gc <workspaceRoot> [--list] 仅列出 flowName/uuid 目录
|
|
6
|
+
* agentflow apply -ai gc <workspaceRoot> --dry-run [--keep N] [--older-than N] 预览将删除的
|
|
7
|
+
* agentflow apply -ai gc <workspaceRoot> --delete [--keep N] [--older-than N] 执行删除
|
|
8
|
+
* 输出(stdout 一行 JSON):{ "err_code": 0|1, "message": { "result": "<文本>" } }
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
import { listAllRunDirs } from "../lib/workspace.mjs";
|
|
15
|
+
|
|
16
|
+
const UUID_DIR_PATTERN = /^\d{14}$/;
|
|
17
|
+
|
|
18
|
+
function parseArgs(args) {
|
|
19
|
+
const workspaceRoot = args[0] ? path.resolve(args[0]) : "";
|
|
20
|
+
const rest = args.slice(1);
|
|
21
|
+
const opts = { list: false, delete: false, dryRun: false, keep: null, olderThan: null };
|
|
22
|
+
for (let i = 0; i < rest.length; i++) {
|
|
23
|
+
if (rest[i] === "--list") opts.list = true;
|
|
24
|
+
else if (rest[i] === "--delete") opts.delete = true;
|
|
25
|
+
else if (rest[i] === "--dry-run") opts.dryRun = true;
|
|
26
|
+
else if (rest[i] === "--keep" && rest[i + 1] != null) {
|
|
27
|
+
opts.keep = Math.max(0, parseInt(rest[++i], 10));
|
|
28
|
+
if (Number.isNaN(opts.keep)) opts.keep = null;
|
|
29
|
+
} else if (rest[i] === "--older-than" && rest[i + 1] != null) {
|
|
30
|
+
opts.olderThan = Math.max(0, parseInt(rest[++i], 10));
|
|
31
|
+
if (Number.isNaN(opts.olderThan)) opts.olderThan = null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { workspaceRoot, opts };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 收集所有 run 目录(新 per-flow + legacy 两种位置),按 mtime 倒序 */
|
|
38
|
+
function getAllRunDirs(workspaceRoot) {
|
|
39
|
+
const dirs = [];
|
|
40
|
+
for (const { flowName, uuid, runDir } of listAllRunDirs(workspaceRoot)) {
|
|
41
|
+
if (!UUID_DIR_PATTERN.test(uuid)) continue;
|
|
42
|
+
try {
|
|
43
|
+
const stat = fs.statSync(runDir);
|
|
44
|
+
dirs.push({ flowName, uuid, path: runDir, mtime: stat.mtimeMs });
|
|
45
|
+
} catch (_) {}
|
|
46
|
+
}
|
|
47
|
+
dirs.sort((a, b) => b.mtime - a.mtime);
|
|
48
|
+
return dirs;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function main() {
|
|
52
|
+
const argv = process.argv.slice(2);
|
|
53
|
+
if (argv.length < 1) {
|
|
54
|
+
const payload = {
|
|
55
|
+
err_code: 1,
|
|
56
|
+
message: {
|
|
57
|
+
result:
|
|
58
|
+
"Usage: agentflow apply -ai gc <workspaceRoot> [--list] | ... --dry-run [--keep N] [--older-than N] | ... --delete [--keep N] [--older-than N]",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
console.log(JSON.stringify(payload));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { workspaceRoot, opts } = parseArgs(argv);
|
|
66
|
+
const allDirs = getAllRunDirs(workspaceRoot);
|
|
67
|
+
|
|
68
|
+
// 默认仅列出
|
|
69
|
+
if (!opts.delete && !opts.dryRun) {
|
|
70
|
+
opts.list = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (opts.list) {
|
|
74
|
+
const lines = [`共 ${allDirs.length} 个 run 目录:`, ""];
|
|
75
|
+
for (const d of allDirs) {
|
|
76
|
+
const mtime = new Date(d.mtime).toISOString();
|
|
77
|
+
lines.push(` ${d.flowName}/${d.uuid} (mtime: ${mtime})`);
|
|
78
|
+
}
|
|
79
|
+
const payload = { err_code: 0, message: { result: lines.join("\n") } };
|
|
80
|
+
console.log(JSON.stringify(payload));
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 计算待删除列表
|
|
85
|
+
let toDelete = [...allDirs];
|
|
86
|
+
if (opts.keep != null && opts.keep > 0) {
|
|
87
|
+
toDelete = allDirs.slice(opts.keep);
|
|
88
|
+
}
|
|
89
|
+
if (opts.olderThan != null && opts.olderThan > 0) {
|
|
90
|
+
const cutoff = Date.now() - opts.olderThan * 24 * 60 * 60 * 1000;
|
|
91
|
+
toDelete = toDelete.filter((d) => d.mtime < cutoff);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (opts.dryRun) {
|
|
95
|
+
const msg =
|
|
96
|
+
toDelete.length === 0
|
|
97
|
+
? "无符合条件的目录需要删除。"
|
|
98
|
+
: `以下 ${toDelete.length} 个目录将被删除(dry-run):\n${toDelete.map((d) => ` ${d.flowName}/${d.uuid}`).join("\n")}`;
|
|
99
|
+
const payload = { err_code: 0, message: { result: msg } };
|
|
100
|
+
console.log(JSON.stringify(payload));
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// --delete:实际删除
|
|
105
|
+
const deleted = [];
|
|
106
|
+
const failed = [];
|
|
107
|
+
for (const d of toDelete) {
|
|
108
|
+
try {
|
|
109
|
+
fs.rmSync(d.path, { recursive: true });
|
|
110
|
+
deleted.push(`${d.flowName}/${d.uuid}`);
|
|
111
|
+
} catch (e) {
|
|
112
|
+
failed.push({ label: `${d.flowName}/${d.uuid}`, error: e.message || String(e) });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parts = [`已删除 ${deleted.length} 个 run 目录:${deleted.join(", ") || "无"}`];
|
|
117
|
+
if (failed.length > 0) {
|
|
118
|
+
parts.push(`删除失败 ${failed.length} 个:`);
|
|
119
|
+
failed.forEach((f) => parts.push(` ${f.label}: ${f.error}`));
|
|
120
|
+
}
|
|
121
|
+
const payload = {
|
|
122
|
+
err_code: failed.length > 0 ? 1 : 0,
|
|
123
|
+
message: { result: parts.join("\n") },
|
|
124
|
+
};
|
|
125
|
+
console.log(JSON.stringify(payload));
|
|
126
|
+
process.exit(failed.length > 0 ? 1 : 0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
main();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* apply -ai get-env:按 key 从系统环境变量与 ~/.cursor/config.json 读取 value。
|
|
4
|
+
* 优先级:先查 process.env[key],若无则查 ~/.cursor/config.json(支持点号路径如 openai.apiKey)。
|
|
5
|
+
*
|
|
6
|
+
* 用法(apply 步骤,由 CLI 调用):
|
|
7
|
+
* agentflow apply -ai get-env <workspaceRoot> <flowName> <uuid> <instanceId> <execId> <key>
|
|
8
|
+
* 将 value 写入当前 run 的 output 并写 result。
|
|
9
|
+
*
|
|
10
|
+
* 用法(仅 key,兼容/测试):
|
|
11
|
+
* agentflow apply -ai get-env <key>
|
|
12
|
+
* 或 node get-env.mjs <key>
|
|
13
|
+
* 仅向 stdout 输出 JSON,不写文件。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
import os from "os";
|
|
19
|
+
|
|
20
|
+
import { getRunDir } from "../lib/paths.mjs";
|
|
21
|
+
import { writeResult } from "./write-result.mjs";
|
|
22
|
+
import { outputDirForNode, outputNodeBasename } from "./get-exec-id.mjs";
|
|
23
|
+
|
|
24
|
+
function getFromConfig(config, keyStr) {
|
|
25
|
+
if (!config || typeof config !== "object" || !keyStr) return undefined;
|
|
26
|
+
const parts = String(keyStr).trim().split(".");
|
|
27
|
+
let cur = config;
|
|
28
|
+
for (const p of parts) {
|
|
29
|
+
if (cur == null || typeof cur !== "object") return undefined;
|
|
30
|
+
cur = cur[p];
|
|
31
|
+
}
|
|
32
|
+
return cur != null ? String(cur) : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveValue(keyStr) {
|
|
36
|
+
let value = "";
|
|
37
|
+
if (!keyStr) return value;
|
|
38
|
+
value = process.env[keyStr] ?? "";
|
|
39
|
+
if (value === "") {
|
|
40
|
+
const configPath = path.join(os.homedir(), ".cursor", "config.json");
|
|
41
|
+
if (fs.existsSync(configPath)) {
|
|
42
|
+
try {
|
|
43
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
44
|
+
const config = JSON.parse(raw);
|
|
45
|
+
const fromConfig = getFromConfig(config, keyStr);
|
|
46
|
+
if (fromConfig !== undefined) value = fromConfig;
|
|
47
|
+
} catch (_) {}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function main() {
|
|
54
|
+
const argv = process.argv.slice(2);
|
|
55
|
+
const keyStr = (argv[argv.length - 1] != null ? String(argv[argv.length - 1]).trim() : "") || "";
|
|
56
|
+
const value = resolveValue(keyStr);
|
|
57
|
+
|
|
58
|
+
if (argv.length >= 6) {
|
|
59
|
+
const [workspaceRoot, flowName, uuid, instanceId, execIdStr] = argv;
|
|
60
|
+
const execId = parseInt(String(execIdStr), 10) || 1;
|
|
61
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
62
|
+
const outputDir = path.join(runDir, outputDirForNode(instanceId));
|
|
63
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
64
|
+
// 备份由 snapshotPriorRoundIfNeeded 在 pre-process 入口统一处理。
|
|
65
|
+
const valueFile = path.join(outputDir, outputNodeBasename(instanceId, execId, "value"));
|
|
66
|
+
const resultFile = path.join(outputDir, outputNodeBasename(instanceId, execId, "result"));
|
|
67
|
+
fs.writeFileSync(valueFile, value, "utf-8");
|
|
68
|
+
fs.writeFileSync(resultFile, value, "utf-8");
|
|
69
|
+
writeResult(workspaceRoot, flowName, uuid, instanceId, {
|
|
70
|
+
status: "success",
|
|
71
|
+
message: "执行完成",
|
|
72
|
+
}, { preserveBody: true, execId });
|
|
73
|
+
} else {
|
|
74
|
+
console.log(
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
err_code: 0,
|
|
77
|
+
message: { result: value, value },
|
|
78
|
+
}),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
main();
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 从 run 目录 memory 读取各节点的 execId(供其它逻辑使用)。result/中间文件使用固定文件名,不再带 _execId。
|
|
4
|
+
* 用法(CLI):node get-exec-id.mjs <workspaceRoot> <uuid> [instanceId]
|
|
5
|
+
* - 仅 instanceId:输出该节点 execId(默认 1)
|
|
6
|
+
* - 无 instanceId:需从 flow.json 读 order,输出 JSON { "execIds": { "<id>": number } }
|
|
7
|
+
* 用法(模块):
|
|
8
|
+
* loadExecId(workspaceRoot, flowName, uuid, instanceId) -> number
|
|
9
|
+
* loadAllExecIds(workspaceRoot, flowName, uuid, order) -> { instanceId: number }
|
|
10
|
+
* latestResultExecId(execId) -> number
|
|
11
|
+
* intermediateResultBasename(instanceId, execId?) -> "<instanceId>.result.md"(固定,execId 忽略)
|
|
12
|
+
* intermediatePromptBasename(instanceId, execId?) -> "<instanceId>.prompt.md"(固定)
|
|
13
|
+
* intermediateCacheBasename(instanceId, execId?) -> "<instanceId>.cache.json"(固定)
|
|
14
|
+
* outputNodeBasename(instanceId, execId?, slot) -> "node_<instanceId>_<slot>.md"(固定,无 execId)
|
|
15
|
+
* intermediateDirForNode(instanceId) -> "intermediate/<instanceId>"(相对 run 目录)
|
|
16
|
+
* outputDirForNode(instanceId) -> "output/<instanceId>"
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import path from "path";
|
|
21
|
+
|
|
22
|
+
import { getRunDir } from "../lib/paths.mjs";
|
|
23
|
+
|
|
24
|
+
const MEMORY_FILENAME = "memory.md";
|
|
25
|
+
const EXEC_ID_KEY_PREFIX = "execId_";
|
|
26
|
+
|
|
27
|
+
function parseMemory(content) {
|
|
28
|
+
const map = new Map();
|
|
29
|
+
for (const line of (content || "").split(/\r?\n/)) {
|
|
30
|
+
const idx = line.indexOf(": ");
|
|
31
|
+
if (idx <= 0) continue;
|
|
32
|
+
const k = line.slice(0, idx).trim();
|
|
33
|
+
const v = line.slice(idx + 2).trim();
|
|
34
|
+
if (k) map.set(k, v);
|
|
35
|
+
}
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 从 memory 读取单个节点的 execId,默认 1。
|
|
41
|
+
*/
|
|
42
|
+
export function loadExecId(workspaceRoot, flowName, uuid, instanceId) {
|
|
43
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
44
|
+
const memoryPath = path.join(runDir, MEMORY_FILENAME);
|
|
45
|
+
if (!fs.existsSync(memoryPath)) return 1;
|
|
46
|
+
const map = parseMemory(fs.readFileSync(memoryPath, "utf-8"));
|
|
47
|
+
const v = map.get(EXEC_ID_KEY_PREFIX + instanceId);
|
|
48
|
+
if (v === undefined || v === "") return 1;
|
|
49
|
+
const n = parseInt(String(v), 10);
|
|
50
|
+
return Number.isFinite(n) && n >= 1 ? n : 1;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 从 memory 读取所有 order 中节点的 execId。
|
|
55
|
+
*/
|
|
56
|
+
export function loadAllExecIds(workspaceRoot, flowName, uuid, order) {
|
|
57
|
+
const runDir = getRunDir(workspaceRoot, flowName, uuid);
|
|
58
|
+
const memoryPath = path.join(runDir, MEMORY_FILENAME);
|
|
59
|
+
const map = fs.existsSync(memoryPath)
|
|
60
|
+
? parseMemory(fs.readFileSync(memoryPath, "utf-8"))
|
|
61
|
+
: new Map();
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const instanceId of order) {
|
|
64
|
+
const v = map.get(EXEC_ID_KEY_PREFIX + instanceId);
|
|
65
|
+
const n = v !== undefined && v !== "" ? parseInt(String(v), 10) : 1;
|
|
66
|
+
out[instanceId] = Number.isFinite(n) && n >= 1 ? n : 1;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 用于读取「当前最新」result 文件对应的 execId。
|
|
73
|
+
* 约定:memory 存的是「上一轮已完成的 execId」,节点以 execId E 执行时写入 <id>_E.result.md。
|
|
74
|
+
* 故最新 result 即为 _<memory 中的 execId>.result.md,直接返回该值;缺省或非法时为 1。
|
|
75
|
+
*/
|
|
76
|
+
export function latestResultExecId(execId) {
|
|
77
|
+
const e = execId != null ? Number(execId) : 1;
|
|
78
|
+
return Number.isFinite(e) && e >= 1 ? e : 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 固定文件名,不含 _execId,第二参数保留兼容调用方但不参与结果 */
|
|
82
|
+
export function intermediateResultBasename(instanceId, execId) {
|
|
83
|
+
return `${instanceId}.result.md`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 固定文件名,不含 _execId */
|
|
87
|
+
export function intermediatePromptBasename(instanceId, execId) {
|
|
88
|
+
return `${instanceId}.prompt.md`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 固定文件名,不含 _execId */
|
|
92
|
+
export function intermediateCacheBasename(instanceId, execId) {
|
|
93
|
+
return `${instanceId}.cache.json`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 固定文件名:node_<instanceId>_<base>.md,不含 execId */
|
|
97
|
+
export function outputNodeBasename(instanceId, execId, slot) {
|
|
98
|
+
const base = (slot || "result").replace(/\.(md|txt|json|html?)$/i, "") || "result";
|
|
99
|
+
return `node_${instanceId}_${base}.md`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 节点在 intermediate 下的一级目录(相对 run 目录),flow.json 仍在 intermediate/ 根下 */
|
|
103
|
+
export function intermediateDirForNode(instanceId) {
|
|
104
|
+
return `intermediate/${instanceId}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 节点在 output 下的一级目录(相对 run 目录) */
|
|
108
|
+
export function outputDirForNode(instanceId) {
|
|
109
|
+
return `output/${instanceId}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function main() {
|
|
113
|
+
const args = process.argv.slice(2);
|
|
114
|
+
if (args.length < 3) {
|
|
115
|
+
console.error("Usage: node get-exec-id.mjs <workspaceRoot> <flowName> <uuid> [instanceId]");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
const [root, flowName, uuid, instanceId] = args;
|
|
119
|
+
const workspaceRoot = path.resolve(root);
|
|
120
|
+
|
|
121
|
+
if (instanceId) {
|
|
122
|
+
const execId = loadExecId(workspaceRoot, flowName, uuid, instanceId);
|
|
123
|
+
console.log(JSON.stringify({ execId }));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const flowJsonPath = path.join(getRunDir(workspaceRoot, flowName, uuid), "intermediate", "flow.json");
|
|
128
|
+
if (!fs.existsSync(flowJsonPath)) {
|
|
129
|
+
console.log(JSON.stringify({ execIds: {} }));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
let order = [];
|
|
133
|
+
try {
|
|
134
|
+
const flow = JSON.parse(fs.readFileSync(flowJsonPath, "utf-8"));
|
|
135
|
+
if (flow.ok && Array.isArray(flow.order)) order = flow.order;
|
|
136
|
+
} catch (_) {}
|
|
137
|
+
const execIds = loadAllExecIds(workspaceRoot, flowName, uuid, order);
|
|
138
|
+
console.log(JSON.stringify({ execIds }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const isMain =
|
|
142
|
+
typeof process !== "undefined" &&
|
|
143
|
+
process.argv[1] &&
|
|
144
|
+
(process.argv[1].endsWith("get-exec-id.mjs") || process.argv[1].endsWith("get-exec-id.js"));
|
|
145
|
+
if (isMain) main();
|