@fieldwangai/agentflow 0.1.59 → 0.1.61
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/bin/lib/catalog-flows.mjs +36 -33
- package/bin/lib/composer-agent.mjs +3 -1
- package/bin/lib/ui-server.mjs +167 -20
- package/bin/lib/workspace.mjs +5 -3
- package/builtin/web-ui/dist/assets/index-Db5Z0V8r.css +1 -0
- package/builtin/web-ui/dist/assets/index-DdSsdIza.js +238 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-B7_s-Wnl.css +0 -1
- package/builtin/web-ui/dist/assets/index-Cp-MNcxx.js +0 -238
|
@@ -57,6 +57,7 @@ export function readPipelineListDescription(flowDir) {
|
|
|
57
57
|
export function listFlowsJson(workspaceRoot, opts = {}) {
|
|
58
58
|
const root = path.resolve(workspaceRoot);
|
|
59
59
|
const out = [];
|
|
60
|
+
const includeWorkspaceFlows = opts.includeWorkspaceFlows === true || !opts.userId;
|
|
60
61
|
const adminBuiltinConfig = readAdminBuiltinPipelineConfig();
|
|
61
62
|
const hiddenBuiltins = new Set(adminBuiltinConfig.hiddenBuiltins);
|
|
62
63
|
const fromBuiltin = collectPipelineNamesFromDir(PACKAGE_BUILTIN_PIPELINES_DIR);
|
|
@@ -93,39 +94,41 @@ export function listFlowsJson(workspaceRoot, opts = {}) {
|
|
|
93
94
|
const description = readPipelineListDescription(dir);
|
|
94
95
|
out.push({ id: name, path: dir, source: "user", archived: true, ...(description ? { description } : {}) });
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
97
|
+
if (includeWorkspaceFlows) {
|
|
98
|
+
const wsPrimary = path.join(root, PIPELINES_DIR);
|
|
99
|
+
const fromWorkspace = collectPipelineNamesFromDir(wsPrimary);
|
|
100
|
+
const workspaceIds = new Set(fromWorkspace);
|
|
101
|
+
for (const name of fromWorkspace) {
|
|
102
|
+
if (name === ARCHIVED_PIPELINES_DIR_NAME) continue;
|
|
103
|
+
const dir = path.join(wsPrimary, name);
|
|
104
|
+
const description = readPipelineListDescription(dir);
|
|
105
|
+
out.push({ id: name, path: dir, source: "workspace", ...(description ? { description } : {}) });
|
|
106
|
+
}
|
|
107
|
+
const wsArchivedPrimary = path.join(wsPrimary, ARCHIVED_PIPELINES_DIR_NAME);
|
|
108
|
+
const fromWsArchived = collectPipelineNamesFromDir(wsArchivedPrimary);
|
|
109
|
+
const workspaceArchivedIds = new Set(fromWsArchived);
|
|
110
|
+
for (const name of fromWsArchived) {
|
|
111
|
+
const dir = path.join(wsArchivedPrimary, name);
|
|
112
|
+
const description = readPipelineListDescription(dir);
|
|
113
|
+
out.push({ id: name, path: dir, source: "workspace", archived: true, ...(description ? { description } : {}) });
|
|
114
|
+
}
|
|
115
|
+
const fromLegacyWs = collectPipelineNamesFromDir(path.join(root, LEGACY_PIPELINES_DIR));
|
|
116
|
+
for (const name of fromLegacyWs) {
|
|
117
|
+
if (name === ARCHIVED_PIPELINES_DIR_NAME) continue;
|
|
118
|
+
if (workspaceIds.has(name)) continue;
|
|
119
|
+
const legDir = path.join(root, LEGACY_PIPELINES_DIR, name);
|
|
120
|
+
const description = readPipelineListDescription(legDir);
|
|
121
|
+
out.push({ id: name, path: legDir, source: "workspace", ...(description ? { description } : {}) });
|
|
122
|
+
}
|
|
123
|
+
const legArchivedRoot = path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME);
|
|
124
|
+
const fromLegArchived = collectPipelineNamesFromDir(legArchivedRoot);
|
|
125
|
+
for (const name of fromLegArchived) {
|
|
126
|
+
if (workspaceArchivedIds.has(name)) continue;
|
|
127
|
+
const dir = path.join(legArchivedRoot, name);
|
|
128
|
+
const description = readPipelineListDescription(dir);
|
|
129
|
+
out.push({ id: name, path: dir, source: "workspace", archived: true, ...(description ? { description } : {}) });
|
|
130
|
+
workspaceArchivedIds.add(name);
|
|
131
|
+
}
|
|
129
132
|
}
|
|
130
133
|
const sourceRank = (s) => (s === "builtin" ? 0 : s === "admin" ? 1 : s === "user" ? 2 : 3);
|
|
131
134
|
const archRank = (a) => (a.archived ? 1 : 0);
|
|
@@ -257,6 +257,7 @@ export function buildScriptContentBlockForInstances(flowYamlAbs, instanceIds) {
|
|
|
257
257
|
* @param {string} [opts.cliWorkspace]
|
|
258
258
|
* @param {string} opts.prompt
|
|
259
259
|
* @param {string} [opts.modelKey]
|
|
260
|
+
* @param {Record<string, string>} [opts.extraEnv]
|
|
260
261
|
* @param {boolean} [opts.force]
|
|
261
262
|
* @param {(ev: object) => void} [opts.onStreamEvent]
|
|
262
263
|
* @param {(subtype: string, toolName: string) => void} [opts.onToolCall]
|
|
@@ -273,7 +274,8 @@ export function startComposerAgent(opts) {
|
|
|
273
274
|
const cliWs = opts.cliWorkspace ? String(opts.cliWorkspace) : getAgentflowDataRoot();
|
|
274
275
|
const modelKey = opts.modelKey != null ? String(opts.modelKey).trim() : "";
|
|
275
276
|
const { cli, model } = resolveCliAndModel(uiRoot, modelKey || null, null);
|
|
276
|
-
const
|
|
277
|
+
const extraEnv = opts.extraEnv && typeof opts.extraEnv === "object" && !Array.isArray(opts.extraEnv) ? opts.extraEnv : {};
|
|
278
|
+
const env = { ...agentflowUserEnv(opts.agentflowUserId), ...extraEnv };
|
|
277
279
|
|
|
278
280
|
const common = {
|
|
279
281
|
onStreamEvent: opts.onStreamEvent,
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -2072,6 +2072,7 @@ function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
|
2072
2072
|
"## Workspace 输出协议",
|
|
2073
2073
|
"",
|
|
2074
2074
|
"最终回复必须是一个 JSON 对象,必须直接以 `{` 开头并以 `}` 结尾;不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字、进度说明或自然语言前后缀。",
|
|
2075
|
+
"执行过程中不要发送 assistant 进度说明,例如“正在读取/正在生成/准备输出”;需要思考时使用内部 thinking,最终只发送一个 JSON 对象。",
|
|
2075
2076
|
"JSON 必须可被 `JSON.parse` 解析;如果 `result` 是多行 Markdown,必须在 JSON 字符串里使用 `\\n` 转义换行,不能把裸 Markdown 直接塞进未转义的字符串。",
|
|
2076
2077
|
"固定格式:",
|
|
2077
2078
|
"",
|
|
@@ -2168,6 +2169,74 @@ function isWorkspaceSemanticInputSlot(slot) {
|
|
|
2168
2169
|
return type === "node" || name === "prev" || name === "next" || name === "skillsContext" || name === "mcpContext" || name === "workspaceContext" || name === "gitContext";
|
|
2169
2170
|
}
|
|
2170
2171
|
|
|
2172
|
+
function workspaceNodeBrief(graph, nodeId) {
|
|
2173
|
+
const instance = graph?.instances?.[String(nodeId || "")] || {};
|
|
2174
|
+
const label = String(instance?.label || nodeId || "").trim();
|
|
2175
|
+
const defId = String(instance?.definitionId || "").trim();
|
|
2176
|
+
return defId && defId !== label ? `${label || nodeId} (${defId})` : (label || nodeId);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
function workspaceSlotBrief(slot, fallback) {
|
|
2180
|
+
const name = String(slot?.name || "").trim();
|
|
2181
|
+
const type = String(slot?.type || "").trim();
|
|
2182
|
+
return `${name || fallback}${type ? `:${type}` : ""}`;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function workspaceNodeConnectionContextBlock(graph, nodeId) {
|
|
2186
|
+
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
2187
|
+
const incoming = edges.filter((edge) => String(edge?.target || "") === String(nodeId));
|
|
2188
|
+
const outgoing = edges.filter((edge) => String(edge?.source || "") === String(nodeId));
|
|
2189
|
+
const inputLines = incoming.map((edge) => {
|
|
2190
|
+
const sourceSlot = workspaceSourceSlotForEdge(graph, edge);
|
|
2191
|
+
const targetSlot = workspaceTargetSlotForEdge(graph, edge);
|
|
2192
|
+
const targetName = String(targetSlot?.name || "").trim();
|
|
2193
|
+
const semantic = isWorkspaceSemanticInputSlot(targetSlot) ? "上下文输入" : "业务输入";
|
|
2194
|
+
return `- ${workspaceNodeBrief(graph, edge.source)} \`${workspaceSlotBrief(sourceSlot, edge.sourceHandle || "output")}\` -> 当前 \`${workspaceSlotBrief(targetSlot, edge.targetHandle || "input")}\`(${semantic}${targetName ? `:${targetName}` : ""})`;
|
|
2195
|
+
});
|
|
2196
|
+
const outputLines = outgoing.map((edge) => {
|
|
2197
|
+
const sourceSlot = workspaceSourceSlotForEdge(graph, edge);
|
|
2198
|
+
const targetSlot = workspaceTargetSlotForEdge(graph, edge);
|
|
2199
|
+
const targetKind = workspaceDisplayKind(graph?.instances?.[String(edge?.target || "")]?.definitionId);
|
|
2200
|
+
return `- 当前 \`${workspaceSlotBrief(sourceSlot, edge.sourceHandle || "output")}\` -> ${workspaceNodeBrief(graph, edge.target)} \`${workspaceSlotBrief(targetSlot, edge.targetHandle || "input")}\`${targetKind ? `(${targetKind} 展示)` : ""}`;
|
|
2201
|
+
});
|
|
2202
|
+
if (!inputLines.length && !outputLines.length) return "";
|
|
2203
|
+
return [
|
|
2204
|
+
"## 当前节点连线",
|
|
2205
|
+
"",
|
|
2206
|
+
inputLines.length ? "### 直接上游" : "",
|
|
2207
|
+
...inputLines,
|
|
2208
|
+
outputLines.length ? "\n### 直接下游" : "",
|
|
2209
|
+
...outputLines,
|
|
2210
|
+
"",
|
|
2211
|
+
"只把直接上游的业务输入和上下文输入当作本节点依据;直接下游只用于确定输出字段和格式。",
|
|
2212
|
+
].filter(Boolean).join("\n");
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
function workspaceResolvedInputValuesBlock(inputValues = {}) {
|
|
2216
|
+
const entries = Object.entries(inputValues || {}).filter(([name, value]) => String(name || "").trim() && String(value || "").trim());
|
|
2217
|
+
if (!entries.length) return "";
|
|
2218
|
+
const lines = entries.map(([name, value]) => {
|
|
2219
|
+
const text = String(value || "");
|
|
2220
|
+
const clipped = text.length > 6000 ? `${text.slice(0, 6000)}\n...[已截断 ${text.length - 6000} 字]` : text;
|
|
2221
|
+
return `### ${name}\n\n${clipped}`;
|
|
2222
|
+
});
|
|
2223
|
+
return ["## 已解析业务输入槽", "", ...lines].join("\n");
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
function workspaceNodeTmpDirectoryBlock(nodeTmpDir) {
|
|
2227
|
+
const dir = String(nodeTmpDir || "").trim();
|
|
2228
|
+
if (!dir) return "";
|
|
2229
|
+
return [
|
|
2230
|
+
"## 临时文件目录",
|
|
2231
|
+
"",
|
|
2232
|
+
`- 本节点专用临时目录:\`${dir}\``,
|
|
2233
|
+
"- 如果确实需要创建中间文件,只能写入该目录,路径也可通过环境变量 `AGENTFLOW_NODE_TMP_DIR` 获取。",
|
|
2234
|
+
"- 不要在 workspace 根目录、业务仓库根目录或当前 cwd 下创建 `temp_*`、`_out.json`、`tmp.html` 等临时产物。",
|
|
2235
|
+
"- 不要自行删除该目录或其中的最终待读文件;AgentFlow 会在节点运行结束后统一清理。",
|
|
2236
|
+
"- 纯生成类任务应直接在最终 JSON 中返回结果;不要为了输出而写文件、cat 文件再粘贴。",
|
|
2237
|
+
].join("\n");
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2171
2240
|
function workspaceTaskUpstreamText(graph, nodeId, outputs) {
|
|
2172
2241
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
2173
2242
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
@@ -2247,8 +2316,7 @@ function workspaceUpstreamSkillBlocks(graph, nodeId, outputs) {
|
|
|
2247
2316
|
const slot = workspaceTargetSlotForEdge(graph, edge);
|
|
2248
2317
|
return String(slot?.name || "") === "skillsContext";
|
|
2249
2318
|
})
|
|
2250
|
-
.map((edge) =>
|
|
2251
|
-
.filter((text) => text.includes("Skill") || text.includes("skill"))
|
|
2319
|
+
.map((edge) => workspaceOutputSlotValueForEdge(graph, outputs, edge))
|
|
2252
2320
|
.flatMap((text) => text.split(/\n\s*---\s*\n/g))
|
|
2253
2321
|
.map((text) => text.trim())
|
|
2254
2322
|
.filter(Boolean);
|
|
@@ -2263,8 +2331,7 @@ function workspaceUpstreamMcpBlocks(graph, nodeId, outputs) {
|
|
|
2263
2331
|
const slot = workspaceTargetSlotForEdge(graph, edge);
|
|
2264
2332
|
return String(slot?.name || "") === "mcpContext";
|
|
2265
2333
|
})
|
|
2266
|
-
.map((edge) =>
|
|
2267
|
-
.filter((text) => text.includes("MCP") || text.includes("mcp"))
|
|
2334
|
+
.map((edge) => workspaceOutputSlotValueForEdge(graph, outputs, edge))
|
|
2268
2335
|
.flatMap((text) => text.split(/\n\s*---\s*\n/g))
|
|
2269
2336
|
.map((text) => text.trim())
|
|
2270
2337
|
.filter(Boolean);
|
|
@@ -2291,9 +2358,9 @@ function buildWorkspaceSkillManifestBlock(skills, selectedKeys = []) {
|
|
|
2291
2358
|
}).filter(Boolean);
|
|
2292
2359
|
if (!rows.length && !normalizedKeys.length) return "";
|
|
2293
2360
|
return [
|
|
2294
|
-
"###
|
|
2361
|
+
"### 已加载 Skills",
|
|
2295
2362
|
"",
|
|
2296
|
-
"这些 skills
|
|
2363
|
+
"这些 skills 来自当前 Workspace 中已连接的 Load Skills 节点。只有节点任务需要对应能力时,才按路径 Read 对应 SKILL.md;不要展开未连接或未加载的 skills。",
|
|
2297
2364
|
"",
|
|
2298
2365
|
...(
|
|
2299
2366
|
rows.length
|
|
@@ -2375,20 +2442,26 @@ function workspaceUpdateDirectDisplays(graph, sourceId, content, outputs = null)
|
|
|
2375
2442
|
return updated;
|
|
2376
2443
|
}
|
|
2377
2444
|
|
|
2378
|
-
function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock = "", inputValues = {}) {
|
|
2445
|
+
function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock = "", inputValues = {}, nodeTmpDir = "") {
|
|
2379
2446
|
const instance = graph.instances[nodeId] || {};
|
|
2380
2447
|
const body = workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim();
|
|
2381
2448
|
const label = String(instance.label || nodeId).trim();
|
|
2382
2449
|
const scopeGuardrails = workspaceNodeScopeGuardrails(graph, nodeId, inputValues);
|
|
2450
|
+
const connectionContext = workspaceNodeConnectionContextBlock(graph, nodeId);
|
|
2451
|
+
const resolvedInputs = workspaceResolvedInputValuesBlock(inputValues);
|
|
2452
|
+
const tmpDirectory = workspaceNodeTmpDirectoryBlock(nodeTmpDir);
|
|
2383
2453
|
const downstreamRequirements = workspaceDownstreamDisplayRequirements(graph, nodeId);
|
|
2384
2454
|
const outputProtocolRequirements = workspaceOutputProtocolRequirements(graph, nodeId);
|
|
2385
2455
|
return [
|
|
2386
2456
|
"你正在执行 AgentFlow Workspace 画布中的一个临时节点。",
|
|
2387
2457
|
"按 Workspace 输出协议返回该节点要传给下游展示/后续节点的数据。",
|
|
2388
2458
|
scopeGuardrails,
|
|
2459
|
+
connectionContext ? `\n${connectionContext}` : "",
|
|
2460
|
+
tmpDirectory ? `\n${tmpDirectory}` : "",
|
|
2389
2461
|
workspaceSearchGuardrailsBlock(),
|
|
2390
|
-
|
|
2391
|
-
|
|
2462
|
+
resolvedInputs ? `\n${resolvedInputs}` : "",
|
|
2463
|
+
skillsBlock ? `\n## 上游已加载 Skills\n\n${skillsBlock}` : "",
|
|
2464
|
+
mcpBlock ? `\n## 上游已加载 MCP\n\n${mcpBlock}` : "",
|
|
2392
2465
|
upstreamText ? `\n## 上游上下文\n\n${upstreamText}` : "",
|
|
2393
2466
|
downstreamRequirements ? `\n${downstreamRequirements}` : "",
|
|
2394
2467
|
outputProtocolRequirements ? `\n${outputProtocolRequirements}` : "",
|
|
@@ -2463,6 +2536,48 @@ function workspaceCleanupAutoWorktrees(list, graph, emit) {
|
|
|
2463
2536
|
list.splice(0, list.length);
|
|
2464
2537
|
}
|
|
2465
2538
|
|
|
2539
|
+
function workspaceSanitizeTmpSegment(value, fallback = "node") {
|
|
2540
|
+
return String(value || fallback)
|
|
2541
|
+
.trim()
|
|
2542
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "_")
|
|
2543
|
+
.replace(/^_+|_+$/g, "")
|
|
2544
|
+
.slice(0, 120) || fallback;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function workspaceCreateRunTmpRoot(scopedRoot, runNodeId) {
|
|
2548
|
+
const runPart = workspaceSanitizeTmpSegment(runNodeId || "run", "run");
|
|
2549
|
+
const id = typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
2550
|
+
const dir = path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "tmp", `workspace-run-${Date.now()}-${runPart}-${id}`);
|
|
2551
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2552
|
+
return dir;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
function workspaceCreateNodeTmpDir(runTmpRoot, nodeId) {
|
|
2556
|
+
const dir = path.join(path.resolve(runTmpRoot), workspaceSanitizeTmpSegment(nodeId, "node"));
|
|
2557
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2558
|
+
return dir;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
function workspaceShouldKeepTmp(userCtx = {}) {
|
|
2562
|
+
const env = { ...process.env, ...readMergedEnvObject(userCtx.userId) };
|
|
2563
|
+
const value = String(env.AGENTFLOW_KEEP_TMP || env.AGENTFLOW_KEEP_WORKSPACE_TMP || "").trim().toLowerCase();
|
|
2564
|
+
return ["1", "true", "yes", "on"].includes(value);
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
function workspaceCleanupTmpRoot(runTmpRoot, userCtx = {}, emit = () => {}) {
|
|
2568
|
+
const dir = String(runTmpRoot || "").trim();
|
|
2569
|
+
if (!dir) return;
|
|
2570
|
+
if (workspaceShouldKeepTmp(userCtx)) {
|
|
2571
|
+
emit({ type: "status", line: `Workspace tmp kept: ${dir}` });
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
try {
|
|
2575
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
2576
|
+
} catch (e) {
|
|
2577
|
+
emit({ type: "natural", kind: "warning", text: `Workspace tmp cleanup failed: ${dir}\n原因:${e?.message || String(e)}` });
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2466
2581
|
async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts = {}) {
|
|
2467
2582
|
const graph = normalizeWorkspaceGraphPayload(payload.graph || {});
|
|
2468
2583
|
const runNodeId = String(payload?.runNodeId || "").trim();
|
|
@@ -2475,9 +2590,6 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2475
2590
|
throw err;
|
|
2476
2591
|
}
|
|
2477
2592
|
};
|
|
2478
|
-
const fallbackSelectedSkillKeys = Array.isArray(payload?.selectedSkills)
|
|
2479
|
-
? payload.selectedSkills.map((x) => String(x || "").trim()).filter(Boolean)
|
|
2480
|
-
: [];
|
|
2481
2593
|
const skillsBlockCache = new Map();
|
|
2482
2594
|
const loadSkillsBlockForKeys = (keys) => {
|
|
2483
2595
|
const normalized = Array.from(new Set((keys || []).map((x) => String(x || "").trim()).filter(Boolean)));
|
|
@@ -2521,6 +2633,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2521
2633
|
let cwd = scopedRoot;
|
|
2522
2634
|
const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
|
|
2523
2635
|
const autoCleanupWorktrees = [];
|
|
2636
|
+
const runTmpRoot = workspaceCreateRunTmpRoot(scopedRoot, runNodeId);
|
|
2524
2637
|
|
|
2525
2638
|
try {
|
|
2526
2639
|
for (const nodeId of order) {
|
|
@@ -2537,9 +2650,8 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2537
2650
|
if (defId === "control_load_skills") {
|
|
2538
2651
|
const skillStartedAt = Date.now();
|
|
2539
2652
|
const nodeSkillKeys = selectedSkillKeysFromInstance(instance);
|
|
2540
|
-
const
|
|
2541
|
-
|
|
2542
|
-
emitTiming(nodeId, "load-skills", skillStartedAt, { skillCount: activeSkillKeys.length, charCount: skillsBlock.length });
|
|
2653
|
+
const skillsBlock = loadSkillsBlockForKeys(nodeSkillKeys);
|
|
2654
|
+
emitTiming(nodeId, "load-skills", skillStartedAt, { skillCount: nodeSkillKeys.length, charCount: skillsBlock.length });
|
|
2543
2655
|
graph.instances[nodeId] = {
|
|
2544
2656
|
...instance,
|
|
2545
2657
|
output: (Array.isArray(instance.output) ? instance.output : []).map((slot) => (
|
|
@@ -2829,9 +2941,10 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2829
2941
|
throw new Error(`Workspace node ${nodeId} has no task. Fill the node body or connect upstream text.`);
|
|
2830
2942
|
}
|
|
2831
2943
|
const upstreamSkillBlocks = workspaceUpstreamSkillBlocks(graph, nodeId, outputs);
|
|
2832
|
-
const promptSkillsBlock = mergeWorkspaceSkillBlocks(upstreamSkillBlocks
|
|
2944
|
+
const promptSkillsBlock = mergeWorkspaceSkillBlocks(upstreamSkillBlocks);
|
|
2833
2945
|
const promptMcpBlock = workspaceUpstreamMcpBlocks(graph, nodeId, outputs);
|
|
2834
|
-
const
|
|
2946
|
+
const nodeTmpDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
2947
|
+
const prompt = workspaceNodePrompt(graph, nodeId, upstreamText, promptSkillsBlock, promptMcpBlock, inputValues, nodeTmpDir);
|
|
2835
2948
|
emitTiming(nodeId, "prepare-agent-prompt", prepareStartedAt, { promptChars: prompt.length, upstreamChars: String(upstreamText || "").length, skillsChars: promptSkillsBlock.length, mcpChars: promptMcpBlock.length });
|
|
2836
2949
|
emit({ type: "natural", kind: "prompt", nodeId, text: prompt });
|
|
2837
2950
|
let content = "";
|
|
@@ -2847,6 +2960,10 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2847
2960
|
prompt,
|
|
2848
2961
|
modelKey,
|
|
2849
2962
|
agentflowUserId: userCtx.userId || "",
|
|
2963
|
+
extraEnv: {
|
|
2964
|
+
AGENTFLOW_WORKSPACE_TMP_ROOT: runTmpRoot,
|
|
2965
|
+
AGENTFLOW_NODE_TMP_DIR: nodeTmpDir,
|
|
2966
|
+
},
|
|
2850
2967
|
onStreamEvent: (ev) => {
|
|
2851
2968
|
if (!firstAgentEventSeen) {
|
|
2852
2969
|
firstAgentEventSeen = true;
|
|
@@ -2894,6 +3011,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2894
3011
|
}
|
|
2895
3012
|
} finally {
|
|
2896
3013
|
workspaceCleanupAutoWorktrees(autoCleanupWorktrees, graph, emit);
|
|
3014
|
+
workspaceCleanupTmpRoot(runTmpRoot, userCtx, emit);
|
|
2897
3015
|
}
|
|
2898
3016
|
if (pauseNodeIds.length > 0) {
|
|
2899
3017
|
emit({ type: "paused", nodeIds: pauseNodeIds, message: `Workspace run paused at ${pauseNodeIds.join(", ")}` });
|
|
@@ -3082,7 +3200,7 @@ function broadcastFlowEditorSync(flowId, flowSource, flowArchived = false, userI
|
|
|
3082
3200
|
|
|
3083
3201
|
/** 正在执行的 flow run(flowId → { child, runUuid });同一 flow 只允许一个 run */
|
|
3084
3202
|
const activeFlowRuns = new Map();
|
|
3085
|
-
/** 正在执行的 Workspace 临时 run(flowId → { controller, child });同一 flow 只允许一个 run */
|
|
3203
|
+
/** 正在执行的 Workspace 临时 run(flowId → { controller, child, runNodeId, startedAt });同一 flow 只允许一个 run */
|
|
3086
3204
|
const activeWorkspaceRuns = new Map();
|
|
3087
3205
|
|
|
3088
3206
|
function workspaceRunKey(userCtx, flowSource, flowId) {
|
|
@@ -3471,7 +3589,10 @@ export function startUiServer({
|
|
|
3471
3589
|
if (ts === "workspace" || ts === "user") {
|
|
3472
3590
|
targetSpace = ts;
|
|
3473
3591
|
}
|
|
3474
|
-
const existing = listFlowsJson(root,
|
|
3592
|
+
const existing = listFlowsJson(root, {
|
|
3593
|
+
...userCtx,
|
|
3594
|
+
includeWorkspaceFlows: targetSpace === "workspace",
|
|
3595
|
+
});
|
|
3475
3596
|
if (
|
|
3476
3597
|
existing.some(
|
|
3477
3598
|
(f) => f.id === flowId && (f.source ?? "user") === targetSpace && !f.archived,
|
|
@@ -3527,7 +3648,10 @@ export function startUiServer({
|
|
|
3527
3648
|
}
|
|
3528
3649
|
const flowId = idCheck.flowId;
|
|
3529
3650
|
const targetSpace = parsed.targetSpace === "workspace" ? "workspace" : "user";
|
|
3530
|
-
const existing = listFlowsJson(root,
|
|
3651
|
+
const existing = listFlowsJson(root, {
|
|
3652
|
+
...userCtx,
|
|
3653
|
+
includeWorkspaceFlows: targetSpace === "workspace",
|
|
3654
|
+
});
|
|
3531
3655
|
if (
|
|
3532
3656
|
existing.some(
|
|
3533
3657
|
(f) => f.id === flowId && (f.source ?? "user") === targetSpace && !f.archived,
|
|
@@ -3831,6 +3955,10 @@ export function startUiServer({
|
|
|
3831
3955
|
const runEntry = {
|
|
3832
3956
|
controller,
|
|
3833
3957
|
child: null,
|
|
3958
|
+
runNodeId: String(payload.runNodeId || "").trim(),
|
|
3959
|
+
flowId,
|
|
3960
|
+
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
3961
|
+
startedAt: Date.now(),
|
|
3834
3962
|
stopChild() {
|
|
3835
3963
|
if (this.child && !this.child.killed) {
|
|
3836
3964
|
try { this.child.kill("SIGTERM"); } catch (_) {}
|
|
@@ -3905,6 +4033,25 @@ export function startUiServer({
|
|
|
3905
4033
|
return;
|
|
3906
4034
|
}
|
|
3907
4035
|
|
|
4036
|
+
if (req.method === "GET" && url.pathname === "/api/workspace/run/status") {
|
|
4037
|
+
const flowId = typeof url.searchParams.get("flowId") === "string" ? url.searchParams.get("flowId").trim() : "";
|
|
4038
|
+
if (!flowId) {
|
|
4039
|
+
json(res, 400, { error: "Missing flowId" });
|
|
4040
|
+
return;
|
|
4041
|
+
}
|
|
4042
|
+
const flowSource = url.searchParams.get("flowSource") || "user";
|
|
4043
|
+
const runKey = workspaceRunKey(userCtx, flowSource, flowId);
|
|
4044
|
+
const entry = activeWorkspaceRuns.get(runKey);
|
|
4045
|
+
json(res, 200, {
|
|
4046
|
+
running: Boolean(entry),
|
|
4047
|
+
flowId,
|
|
4048
|
+
flowSource,
|
|
4049
|
+
runNodeId: entry?.runNodeId || "",
|
|
4050
|
+
startedAt: entry?.startedAt || null,
|
|
4051
|
+
});
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
4054
|
+
|
|
3908
4055
|
if (req.method === "POST" && url.pathname === "/api/workspace/run/stop") {
|
|
3909
4056
|
let payload;
|
|
3910
4057
|
try {
|
package/bin/lib/workspace.mjs
CHANGED
|
@@ -28,6 +28,8 @@ export function listAllRunDirs(workspaceRoot, opts = {}) {
|
|
|
28
28
|
const root = path.resolve(workspaceRoot);
|
|
29
29
|
const out = [];
|
|
30
30
|
const seen = new Set();
|
|
31
|
+
const includeWorkspaceRuns = opts.includeWorkspaceRuns === true || !opts.userId;
|
|
32
|
+
const includeLegacyUserRuns = opts.includeLegacyUserRuns === true || !opts.userId;
|
|
31
33
|
const add = (flowName, uuid, runDir, source) => {
|
|
32
34
|
const key = `${flowName}\t${uuid}`;
|
|
33
35
|
if (seen.has(key)) return;
|
|
@@ -64,7 +66,7 @@ export function listAllRunDirs(workspaceRoot, opts = {}) {
|
|
|
64
66
|
|
|
65
67
|
// 新位置(优先)
|
|
66
68
|
scanPipelinesDir(getUserPipelinesRoot(opts.userId), "user");
|
|
67
|
-
scanPipelinesDir(path.join(root, PIPELINES_DIR), "workspace");
|
|
69
|
+
if (includeWorkspaceRuns) scanPipelinesDir(path.join(root, PIPELINES_DIR), "workspace");
|
|
68
70
|
|
|
69
71
|
// 旧位置(兼容读)
|
|
70
72
|
const scanLegacyRoot = (runBuildDir, source) => {
|
|
@@ -90,8 +92,8 @@ export function listAllRunDirs(workspaceRoot, opts = {}) {
|
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
};
|
|
93
|
-
scanLegacyRoot(getWorkspaceRunBuildRoot(root), "legacyWorkspaceRoot");
|
|
94
|
-
scanLegacyRoot(getLegacyUserRunBuildRoot(), "legacyUserRoot");
|
|
95
|
+
if (includeWorkspaceRuns) scanLegacyRoot(getWorkspaceRunBuildRoot(root), "legacyWorkspaceRoot");
|
|
96
|
+
if (includeLegacyUserRuns) scanLegacyRoot(getLegacyUserRunBuildRoot(), "legacyUserRoot");
|
|
95
97
|
|
|
96
98
|
return out;
|
|
97
99
|
}
|