@fieldwangai/agentflow 0.1.75 → 0.1.77
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/locales/en.json +12 -0
- package/bin/lib/locales/zh.json +12 -0
- package/bin/lib/paths.mjs +4 -0
- package/bin/lib/ui-server.mjs +243 -34
- package/builtin/nodes/provide_password.md +13 -0
- package/builtin/nodes/tool_display_share_link.md +46 -0
- package/builtin/nodes/tool_set_run_env.md +37 -0
- package/builtin/web-ui/dist/assets/index-CnEgfAqq.js +297 -0
- package/builtin/web-ui/dist/assets/index-DjZ70DAY.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-BPlxyeDg.css +0 -1
- package/builtin/web-ui/dist/assets/index-CnTodMb9.js +0 -297
package/bin/lib/locales/en.json
CHANGED
|
@@ -297,6 +297,10 @@
|
|
|
297
297
|
"displayName": "WeCom App Markdown",
|
|
298
298
|
"description": "Send a Markdown message to users through a WeCom enterprise application"
|
|
299
299
|
},
|
|
300
|
+
"tool_display_share_link": {
|
|
301
|
+
"displayName": "Display Share Link",
|
|
302
|
+
"description": "Publish upstream Display nodes as a shareable link and output the URL"
|
|
303
|
+
},
|
|
300
304
|
"tool_nodejs": {
|
|
301
305
|
"displayName": "Node.js Script",
|
|
302
306
|
"description": "Execute Node.js script, success determined by exit code, stdout as result"
|
|
@@ -309,6 +313,10 @@
|
|
|
309
313
|
"displayName": "Get Env",
|
|
310
314
|
"description": "Get environment variable value"
|
|
311
315
|
},
|
|
316
|
+
"tool_set_run_env": {
|
|
317
|
+
"displayName": "Set Run Env",
|
|
318
|
+
"description": "Set temporary environment variables for downstream nodes in the current run"
|
|
319
|
+
},
|
|
312
320
|
"tool_save_key": {
|
|
313
321
|
"displayName": "Save Key",
|
|
314
322
|
"description": "Save key-value pair to global storage"
|
|
@@ -353,6 +361,10 @@
|
|
|
353
361
|
"displayName": "File",
|
|
354
362
|
"description": "Provide file path or content directly, value will be passed to downstream as-is"
|
|
355
363
|
},
|
|
364
|
+
"provide_password": {
|
|
365
|
+
"displayName": "Password",
|
|
366
|
+
"description": "Provide a password or secret text value without showing it on the node card"
|
|
367
|
+
},
|
|
356
368
|
"provide_bool": {
|
|
357
369
|
"displayName": "Boolean",
|
|
358
370
|
"description": "Provide a true/false boolean value directly to downstream bool pins"
|
package/bin/lib/locales/zh.json
CHANGED
|
@@ -297,6 +297,10 @@
|
|
|
297
297
|
"displayName": "企业微信应用 Markdown",
|
|
298
298
|
"description": "通过企业微信应用消息接口发送 Markdown 给用户"
|
|
299
299
|
},
|
|
300
|
+
"tool_display_share_link": {
|
|
301
|
+
"displayName": "展示分享链接",
|
|
302
|
+
"description": "将上游 Display 节点发布为可分享链接,并输出 URL"
|
|
303
|
+
},
|
|
300
304
|
"tool_nodejs": {
|
|
301
305
|
"displayName": "Node.js 脚本",
|
|
302
306
|
"description": "执行 Node.js 脚本,以 exit code 判断成败,stdout 作为结果"
|
|
@@ -309,6 +313,10 @@
|
|
|
309
313
|
"displayName": "获取环境变量",
|
|
310
314
|
"description": "获取环境变量值"
|
|
311
315
|
},
|
|
316
|
+
"tool_set_run_env": {
|
|
317
|
+
"displayName": "设置 Run Env",
|
|
318
|
+
"description": "为当前运行设置临时环境变量,只影响后续节点"
|
|
319
|
+
},
|
|
312
320
|
"tool_save_key": {
|
|
313
321
|
"displayName": "保存键值",
|
|
314
322
|
"description": "保存键值对到全局存储"
|
|
@@ -353,6 +361,10 @@
|
|
|
353
361
|
"displayName": "文件",
|
|
354
362
|
"description": "直接提供文件路径或内容,value 会原样供下游引用"
|
|
355
363
|
},
|
|
364
|
+
"provide_password": {
|
|
365
|
+
"displayName": "密码",
|
|
366
|
+
"description": "直接提供密码或密钥文本,默认不在节点卡片上展示"
|
|
367
|
+
},
|
|
356
368
|
"provide_bool": {
|
|
357
369
|
"displayName": "布尔",
|
|
358
370
|
"description": "直接提供 true/false 布尔值,value 会供下游 bool 引脚引用"
|
package/bin/lib/paths.mjs
CHANGED
|
@@ -332,11 +332,14 @@ export const LOCAL_ONLY_DEFINITION_IDS = new Set([
|
|
|
332
332
|
"tool_gitlab_create_mr",
|
|
333
333
|
"tool_wecom_send_group_markdown",
|
|
334
334
|
"tool_wecom_send_app_markdown",
|
|
335
|
+
"tool_display_share_link",
|
|
336
|
+
"tool_set_run_env",
|
|
335
337
|
"tool_print",
|
|
336
338
|
"tool_user_check",
|
|
337
339
|
"tool_user_ask",
|
|
338
340
|
"provide_str",
|
|
339
341
|
"provide_file",
|
|
342
|
+
"provide_password",
|
|
340
343
|
"provide_bool",
|
|
341
344
|
]);
|
|
342
345
|
|
|
@@ -347,5 +350,6 @@ export const LOCAL_ONLY_TERMINAL_SUCCESS_IDS = new Set([
|
|
|
347
350
|
"tool_print",
|
|
348
351
|
"provide_str",
|
|
349
352
|
"provide_file",
|
|
353
|
+
"provide_password",
|
|
350
354
|
"provide_bool",
|
|
351
355
|
]);
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
import { clearSkillRegistryCache } from "./skill-registry.mjs";
|
|
67
67
|
import { COMPOSER_NODE_SPEC_FILENAME } from "./composer-planner.mjs";
|
|
68
68
|
import { listRecentRunsFromDisk } from "./recent-runs.mjs";
|
|
69
|
+
import { parseBool } from "../pipeline/parse-bool.mjs";
|
|
69
70
|
import {
|
|
70
71
|
unzipAndNormalizePipelineZip,
|
|
71
72
|
validateImportedFlowYaml,
|
|
@@ -1249,8 +1250,11 @@ function sanitizeWorkspaceUploadName(filename) {
|
|
|
1249
1250
|
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
1250
1251
|
.replace(/^-+|-+$/g, "")
|
|
1251
1252
|
.slice(0, 80) || "image";
|
|
1252
|
-
const ext = String(parsed.ext || "")
|
|
1253
|
-
|
|
1253
|
+
const ext = String(parsed.ext || "")
|
|
1254
|
+
.toLowerCase()
|
|
1255
|
+
.replace(/[^a-z0-9.]+/g, "")
|
|
1256
|
+
.slice(0, 24);
|
|
1257
|
+
return `${stem}${ext}`;
|
|
1254
1258
|
}
|
|
1255
1259
|
|
|
1256
1260
|
function uniqueWorkspaceRelPath(workspaceRoot, relPath) {
|
|
@@ -1434,8 +1438,6 @@ function readWorkspaceFilesRecursive(dir, root, depth = 0, maxDepth = 3, budget
|
|
|
1434
1438
|
budget.count++;
|
|
1435
1439
|
} else if (entry.isFile()) {
|
|
1436
1440
|
if (WORKSPACE_FILE_SKIP_FILES.has(entry.name)) continue;
|
|
1437
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
1438
|
-
if (!WORKSPACE_TEXT_EXTS.has(ext) && !WORKSPACE_IMAGE_EXTS.has(ext)) continue;
|
|
1439
1441
|
let size = 0;
|
|
1440
1442
|
try { size = fs.statSync(abs).size; } catch {}
|
|
1441
1443
|
budget.count++;
|
|
@@ -1964,6 +1966,102 @@ function normalizeDisplayShareNodeIds(ids, graph) {
|
|
|
1964
1966
|
return out;
|
|
1965
1967
|
}
|
|
1966
1968
|
|
|
1969
|
+
function normalizeDisplayShareLayout(layout, fallback = "canvas") {
|
|
1970
|
+
const text = String(layout || "").trim();
|
|
1971
|
+
return ["canvas", "gallery", "slides", "document", "single"].includes(text) ? text : fallback;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
function createDisplayShareRecord({ userId, flowId, flowSource, archived, title, layout, nodeIds }) {
|
|
1975
|
+
const shares = readDisplayShares();
|
|
1976
|
+
let id = createDisplayShareId();
|
|
1977
|
+
while (shares[id]) id = createDisplayShareId();
|
|
1978
|
+
const nowDate = new Date();
|
|
1979
|
+
const now = nowDate.toISOString();
|
|
1980
|
+
const share = {
|
|
1981
|
+
id,
|
|
1982
|
+
userId: String(userId || ""),
|
|
1983
|
+
flowId: String(flowId || ""),
|
|
1984
|
+
flowSource: String(flowSource || "user"),
|
|
1985
|
+
archived: archived === true,
|
|
1986
|
+
title: String(title || "").trim() || "AgentFlow Display",
|
|
1987
|
+
layout: normalizeDisplayShareLayout(layout, "canvas"),
|
|
1988
|
+
nodeIds: Array.isArray(nodeIds) ? nodeIds : [],
|
|
1989
|
+
createdAt: now,
|
|
1990
|
+
updatedAt: now,
|
|
1991
|
+
expiresAt: displayShareExpiresAt(nowDate),
|
|
1992
|
+
};
|
|
1993
|
+
shares[id] = share;
|
|
1994
|
+
writeDisplayShares(shares);
|
|
1995
|
+
return share;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
function parseDisplayShareNodeIdInput(value) {
|
|
1999
|
+
return String(value || "")
|
|
2000
|
+
.split(/[\s,,]+/g)
|
|
2001
|
+
.map((id) => id.trim())
|
|
2002
|
+
.filter(Boolean);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
function inferUpstreamDisplayNodeIds(graph, nodeId) {
|
|
2006
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
2007
|
+
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
2008
|
+
const ids = [];
|
|
2009
|
+
const seen = new Set();
|
|
2010
|
+
for (const edge of edges) {
|
|
2011
|
+
if (String(edge?.target || "") !== String(nodeId || "")) continue;
|
|
2012
|
+
const sourceId = String(edge?.source || "").trim();
|
|
2013
|
+
if (!sourceId || seen.has(sourceId)) continue;
|
|
2014
|
+
if (!workspaceDisplayKind(instances[sourceId]?.definitionId)) continue;
|
|
2015
|
+
seen.add(sourceId);
|
|
2016
|
+
ids.push(sourceId);
|
|
2017
|
+
}
|
|
2018
|
+
return ids;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
function displayShareOutputUrl(shareId, baseUrl = "") {
|
|
2022
|
+
const pathPart = `/display/${encodeURIComponent(String(shareId || ""))}`;
|
|
2023
|
+
const base = String(baseUrl || "").trim();
|
|
2024
|
+
if (!base) return pathPart;
|
|
2025
|
+
try {
|
|
2026
|
+
return new URL(pathPart, base.endsWith("/") ? base : `${base}/`).href;
|
|
2027
|
+
} catch {
|
|
2028
|
+
return pathPart;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function normalizeRunEnvKey(key) {
|
|
2033
|
+
const text = String(key || "").trim();
|
|
2034
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(text) ? text : "";
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function parseRunEnvAssignments(raw = "") {
|
|
2038
|
+
const text = String(raw || "").trim();
|
|
2039
|
+
if (!text) return {};
|
|
2040
|
+
if (text.startsWith("{")) {
|
|
2041
|
+
const parsed = JSON.parse(text);
|
|
2042
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Run Env JSON must be an object");
|
|
2043
|
+
const out = {};
|
|
2044
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
2045
|
+
const normalizedKey = normalizeRunEnvKey(key);
|
|
2046
|
+
if (!normalizedKey) throw new Error(`Invalid env key: ${key}`);
|
|
2047
|
+
out[normalizedKey] = String(value ?? "");
|
|
2048
|
+
}
|
|
2049
|
+
return out;
|
|
2050
|
+
}
|
|
2051
|
+
const out = {};
|
|
2052
|
+
for (const line of text.split(/\r?\n/g)) {
|
|
2053
|
+
const trimmed = line.trim();
|
|
2054
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2055
|
+
const normalized = trimmed.replace(/^export\s+/i, "");
|
|
2056
|
+
const eq = normalized.indexOf("=");
|
|
2057
|
+
if (eq <= 0) throw new Error(`Invalid env assignment: ${trimmed}`);
|
|
2058
|
+
const key = normalizeRunEnvKey(normalized.slice(0, eq));
|
|
2059
|
+
if (!key) throw new Error(`Invalid env key: ${normalized.slice(0, eq).trim()}`);
|
|
2060
|
+
out[key] = normalized.slice(eq + 1);
|
|
2061
|
+
}
|
|
2062
|
+
return out;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
1967
2065
|
function publicDisplayPayloadFromShare(root, share) {
|
|
1968
2066
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
1969
2067
|
flowId: share.flowId || "",
|
|
@@ -3324,6 +3422,13 @@ function workspaceIsControlEdge(graph, edge) {
|
|
|
3324
3422
|
workspaceIsControlInputSlot(workspaceTargetSlotForEdge(graph, edge));
|
|
3325
3423
|
}
|
|
3326
3424
|
|
|
3425
|
+
function workspaceControlIfBranchToSourceHandle(branch) {
|
|
3426
|
+
const text = String(branch || "").trim().toLowerCase();
|
|
3427
|
+
if (text === "true" || text === "next1") return "output-0";
|
|
3428
|
+
if (text === "false" || text === "next2") return "output-1";
|
|
3429
|
+
return null;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3327
3432
|
function workspaceNeedsUpstreamExecutionForEdge(graph, edge, scopedRoot = "") {
|
|
3328
3433
|
if (workspaceIsControlEdge(graph, edge)) return true;
|
|
3329
3434
|
return !workspaceEdgeHasCachedOutput(graph, edge, scopedRoot);
|
|
@@ -3341,7 +3446,7 @@ function workspaceEdgeHasCachedOutput(graph, edge, scopedRoot = "") {
|
|
|
3341
3446
|
}
|
|
3342
3447
|
const defId = String(source.definitionId || "");
|
|
3343
3448
|
if (workspaceDisplayKind(defId) && String(source.body || "").trim()) return true;
|
|
3344
|
-
if (defId === "provide_str" || defId === "provide_bool" || defId === "provide_file") {
|
|
3449
|
+
if (defId === "provide_str" || defId === "provide_bool" || defId === "provide_file" || defId === "provide_password") {
|
|
3345
3450
|
return Boolean(String(workspaceInstanceText(source) || "").trim());
|
|
3346
3451
|
}
|
|
3347
3452
|
return false;
|
|
@@ -4396,6 +4501,7 @@ async function workspaceRunToolNodejsScript({
|
|
|
4396
4501
|
inputValues,
|
|
4397
4502
|
runPackage,
|
|
4398
4503
|
userCtx,
|
|
4504
|
+
envOverlay = {},
|
|
4399
4505
|
emit,
|
|
4400
4506
|
}) {
|
|
4401
4507
|
const scriptRef = String(instance?.scriptRef || "").trim();
|
|
@@ -4431,6 +4537,7 @@ async function workspaceRunToolNodejsScript({
|
|
|
4431
4537
|
emit?.({ type: "status", line: `Run script: ${scriptRef || command.slice(0, 120)}` });
|
|
4432
4538
|
|
|
4433
4539
|
const env = runtimeEnvForUser(userCtx, {
|
|
4540
|
+
...envOverlay,
|
|
4434
4541
|
AGENTFLOW_WORKSPACE_ROOT: path.resolve(scopedRoot),
|
|
4435
4542
|
AGENTFLOW_NODE_RUN_DIR: runPackage.nodeRunDir,
|
|
4436
4543
|
AGENTFLOW_NODE_TMP_DIR: runPackage.nodeTmpDir,
|
|
@@ -4542,8 +4649,34 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4542
4649
|
};
|
|
4543
4650
|
let cwd = scopedRoot;
|
|
4544
4651
|
const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
|
|
4652
|
+
const runEnv = {};
|
|
4653
|
+
const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
|
|
4545
4654
|
const autoCleanupWorktrees = [];
|
|
4546
4655
|
const runTmpRoot = workspaceCreateRunTmpRoot(scopedRoot, runNodeId);
|
|
4656
|
+
const controlBranches = new Map();
|
|
4657
|
+
const skippedNodes = new Set();
|
|
4658
|
+
const incomingControlEdgesByTarget = new Map();
|
|
4659
|
+
for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
|
|
4660
|
+
const target = String(edge?.target || "");
|
|
4661
|
+
if (!target || !workspaceIsControlEdge(graph, edge)) continue;
|
|
4662
|
+
if (!incomingControlEdgesByTarget.has(target)) incomingControlEdgesByTarget.set(target, []);
|
|
4663
|
+
incomingControlEdgesByTarget.get(target).push(edge);
|
|
4664
|
+
}
|
|
4665
|
+
const skipReasonForNode = (nodeId) => {
|
|
4666
|
+
for (const edge of incomingControlEdgesByTarget.get(nodeId) || []) {
|
|
4667
|
+
const sourceId = String(edge?.source || "");
|
|
4668
|
+
if (!sourceId) continue;
|
|
4669
|
+
if (skippedNodes.has(sourceId)) return `上游 ${sourceId} 已被分支跳过`;
|
|
4670
|
+
const sourceDefId = String(graph.instances?.[sourceId]?.definitionId || "");
|
|
4671
|
+
if (sourceDefId !== "control_if" || !controlBranches.has(sourceId)) continue;
|
|
4672
|
+
const expectedHandle = workspaceControlIfBranchToSourceHandle(controlBranches.get(sourceId));
|
|
4673
|
+
const actualHandle = String(edge?.sourceHandle || "output-0");
|
|
4674
|
+
if (expectedHandle && actualHandle !== expectedHandle) {
|
|
4675
|
+
return `control_if ${sourceId} 分支为 ${controlBranches.get(sourceId)},跳过 ${actualHandle}`;
|
|
4676
|
+
}
|
|
4677
|
+
}
|
|
4678
|
+
return "";
|
|
4679
|
+
};
|
|
4547
4680
|
|
|
4548
4681
|
try {
|
|
4549
4682
|
for (const nodeId of order) {
|
|
@@ -4551,6 +4684,13 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4551
4684
|
const instance = graph.instances[nodeId];
|
|
4552
4685
|
if (!instance) continue;
|
|
4553
4686
|
const defId = String(instance.definitionId || "");
|
|
4687
|
+
const skipReason = skipReasonForNode(nodeId);
|
|
4688
|
+
if (skipReason) {
|
|
4689
|
+
skippedNodes.add(nodeId);
|
|
4690
|
+
emit({ type: "status", nodeId, line: `Skipped: ${skipReason}` });
|
|
4691
|
+
emit({ type: "node-done", nodeId, definitionId: defId, skipped: true });
|
|
4692
|
+
continue;
|
|
4693
|
+
}
|
|
4554
4694
|
emit({ type: "node-start", nodeId, definitionId: defId });
|
|
4555
4695
|
|
|
4556
4696
|
if (defId === "workspace_run" || defId === "workspace_scheduled_run") {
|
|
@@ -4606,7 +4746,24 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4606
4746
|
continue;
|
|
4607
4747
|
}
|
|
4608
4748
|
|
|
4609
|
-
if (defId === "
|
|
4749
|
+
if (defId === "control_if") {
|
|
4750
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
4751
|
+
const boolSlot = (Array.isArray(instance.input) ? instance.input : [])
|
|
4752
|
+
.find((slot) => String(slot?.type || "").trim().toLowerCase() === "bool");
|
|
4753
|
+
const boolSlotName = String(boolSlot?.name || "").trim();
|
|
4754
|
+
const rawValue = boolSlotName && Object.prototype.hasOwnProperty.call(inputValues, boolSlotName)
|
|
4755
|
+
? inputValues[boolSlotName]
|
|
4756
|
+
: workspaceSlotValue(boolSlot);
|
|
4757
|
+
const boolValue = parseBool(rawValue);
|
|
4758
|
+
const branch = boolValue ? "true" : "false";
|
|
4759
|
+
controlBranches.set(nodeId, branch);
|
|
4760
|
+
outputs.set(nodeId, branch);
|
|
4761
|
+
emit({ type: "status", nodeId, line: `control_if branch: ${branch}` });
|
|
4762
|
+
emit({ type: "node-done", nodeId, definitionId: defId, branch });
|
|
4763
|
+
continue;
|
|
4764
|
+
}
|
|
4765
|
+
|
|
4766
|
+
if (defId === "provide_str" || defId === "provide_password") {
|
|
4610
4767
|
const content = workspaceInstanceText(instance);
|
|
4611
4768
|
outputs.set(nodeId, content);
|
|
4612
4769
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
@@ -4633,6 +4790,26 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4633
4790
|
continue;
|
|
4634
4791
|
}
|
|
4635
4792
|
|
|
4793
|
+
if (defId === "tool_set_run_env") {
|
|
4794
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
4795
|
+
const assignments = {
|
|
4796
|
+
...parseRunEnvAssignments(inputValues.variables || workspaceSlotValue(workspaceSlotByName(instance, "variables"))),
|
|
4797
|
+
};
|
|
4798
|
+
const key = normalizeRunEnvKey(inputValues.key || workspaceSlotValue(workspaceSlotByName(instance, "key")));
|
|
4799
|
+
if (key) assignments[key] = String(inputValues.value ?? workspaceSlotValue(workspaceSlotByName(instance, "value")) ?? "");
|
|
4800
|
+
const keys = Object.keys(assignments);
|
|
4801
|
+
if (!keys.length) throw new Error("Set Run Env requires key/value or variables");
|
|
4802
|
+
Object.assign(runEnv, assignments);
|
|
4803
|
+
let nextInstance = workspaceSetOutputSlot(instance, "keys", keys.join(", "));
|
|
4804
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "count", String(keys.length));
|
|
4805
|
+
graph.instances[nodeId] = nextInstance;
|
|
4806
|
+
outputs.set(nodeId, keys.join(", "));
|
|
4807
|
+
emit({ type: "status", nodeId, line: `Set run env: ${keys.join(", ")}`, envKeys: keys });
|
|
4808
|
+
emit({ type: "graph", nodeId, graph });
|
|
4809
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
4810
|
+
continue;
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4636
4813
|
if (defId === "control_cd_workspace") {
|
|
4637
4814
|
const inputText = workspaceUpstreamText(graph, nodeId, outputs, scopedRoot);
|
|
4638
4815
|
const inputSlots = Array.isArray(instance.input) ? instance.input : [];
|
|
@@ -4829,7 +5006,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4829
5006
|
gitlabApiBase: workspaceSlotValue(workspaceSlotByName(instance, "gitlabApiBase")),
|
|
4830
5007
|
removeSourceBranch: workspaceSlotValue(workspaceSlotByName(instance, "removeSourceBranch")),
|
|
4831
5008
|
squash: workspaceSlotValue(workspaceSlotByName(instance, "squash")),
|
|
4832
|
-
},
|
|
5009
|
+
}, runtimeEnv());
|
|
4833
5010
|
let nextInstance = workspaceSetOutputSlot(instance, "mrUrl", result.mrUrl);
|
|
4834
5011
|
nextInstance = workspaceSetOutputSlot(nextInstance, "created", result.created ? "true" : "false");
|
|
4835
5012
|
nextInstance = workspaceSetOutputSlot(nextInstance, "mrIid", result.mrIid ?? "");
|
|
@@ -4856,12 +5033,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4856
5033
|
corpSecret: inputValues.corpSecret || workspaceSlotValue(workspaceSlotByName(instance, "corpSecret")),
|
|
4857
5034
|
agentId: inputValues.agentId || workspaceSlotValue(workspaceSlotByName(instance, "agentId")),
|
|
4858
5035
|
accessToken: inputValues.accessToken || workspaceSlotValue(workspaceSlotByName(instance, "accessToken")),
|
|
4859
|
-
},
|
|
5036
|
+
}, runtimeEnv())
|
|
4860
5037
|
: await sendWecomGroupMarkdown({
|
|
4861
5038
|
markdown,
|
|
4862
5039
|
webhookUrl: inputValues.webhookUrl || workspaceSlotValue(workspaceSlotByName(instance, "webhookUrl")),
|
|
4863
5040
|
webhookKey: inputValues.webhookKey || workspaceSlotValue(workspaceSlotByName(instance, "webhookKey")),
|
|
4864
|
-
},
|
|
5041
|
+
}, runtimeEnv());
|
|
4865
5042
|
let nextInstance = workspaceSetOutputSlot(instance, "sent", "true");
|
|
4866
5043
|
nextInstance = workspaceSetOutputSlot(nextInstance, "message", result.message);
|
|
4867
5044
|
nextInstance = workspaceSetOutputSlot(nextInstance, "response", JSON.stringify(result.response || {}));
|
|
@@ -4872,6 +5049,52 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4872
5049
|
continue;
|
|
4873
5050
|
}
|
|
4874
5051
|
|
|
5052
|
+
if (defId === "tool_display_share_link") {
|
|
5053
|
+
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5054
|
+
const explicitNodeIds = parseDisplayShareNodeIdInput(inputValues.nodeIds || workspaceSlotValue(workspaceSlotByName(instance, "nodeIds")));
|
|
5055
|
+
const inferredNodeIds = explicitNodeIds.length ? explicitNodeIds : inferUpstreamDisplayNodeIds(graph, nodeId);
|
|
5056
|
+
const nodeIds = normalizeDisplayShareNodeIds(inferredNodeIds, graph);
|
|
5057
|
+
if (nodeIds.length === 0) throw new Error("Display Share Link requires at least one connected display node or nodeIds input");
|
|
5058
|
+
|
|
5059
|
+
const layout = normalizeDisplayShareLayout(inputValues.layout || workspaceSlotValue(workspaceSlotByName(instance, "layout")), "single");
|
|
5060
|
+
const title = inputValues.title || workspaceSlotValue(workspaceSlotByName(instance, "title"));
|
|
5061
|
+
const env = runtimeEnv();
|
|
5062
|
+
const baseUrl = inputValues.baseUrl ||
|
|
5063
|
+
workspaceSlotValue(workspaceSlotByName(instance, "baseUrl")) ||
|
|
5064
|
+
env.AGENTFLOW_PUBLIC_BASE_URL ||
|
|
5065
|
+
env.AGENTFLOW_BASE_URL ||
|
|
5066
|
+
env.PUBLIC_BASE_URL ||
|
|
5067
|
+
"";
|
|
5068
|
+
|
|
5069
|
+
const graphPath = workspaceGraphPath(scopedRoot);
|
|
5070
|
+
try {
|
|
5071
|
+
const currentGraph = readWorkspaceGraph(scopedRoot).graph;
|
|
5072
|
+
const mergedGraph = mergeWorkspaceRunGraph(currentGraph, graph, new Set([nodeId, ...nodeIds]));
|
|
5073
|
+
fs.writeFileSync(graphPath, JSON.stringify(mergedGraph, null, 2) + "\n", "utf-8");
|
|
5074
|
+
} catch (e) {
|
|
5075
|
+
emit({ type: "natural", kind: "warning", text: `保存分享展示内容失败:${(e && e.message) || String(e)}` });
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
const share = createDisplayShareRecord({
|
|
5079
|
+
userId: userCtx.userId,
|
|
5080
|
+
flowId: payload.flowId || "",
|
|
5081
|
+
flowSource: payload.flowSource || "user",
|
|
5082
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
5083
|
+
title,
|
|
5084
|
+
layout,
|
|
5085
|
+
nodeIds,
|
|
5086
|
+
});
|
|
5087
|
+
const url = displayShareOutputUrl(share.id, baseUrl);
|
|
5088
|
+
let nextInstance = workspaceSetOutputSlot(instance, "url", url);
|
|
5089
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "shareId", share.id);
|
|
5090
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "expiresAt", share.expiresAt);
|
|
5091
|
+
graph.instances[nodeId] = nextInstance;
|
|
5092
|
+
outputs.set(nodeId, url);
|
|
5093
|
+
emit({ type: "graph", nodeId, graph, displayNodeIds: nodeIds });
|
|
5094
|
+
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5095
|
+
continue;
|
|
5096
|
+
}
|
|
5097
|
+
|
|
4875
5098
|
if (defId === "tool_nodejs") {
|
|
4876
5099
|
const prepareStartedAt = Date.now();
|
|
4877
5100
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
@@ -4893,6 +5116,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4893
5116
|
inputValues: runtimeInputValues,
|
|
4894
5117
|
runPackage,
|
|
4895
5118
|
userCtx,
|
|
5119
|
+
envOverlay: runEnv,
|
|
4896
5120
|
emit: (event) => emit({ ...event, nodeId }),
|
|
4897
5121
|
});
|
|
4898
5122
|
const normalizedAgentOutput = workspacePublishAgentOutputFiles(workspaceStructuredAgentOutput(content), runPackage);
|
|
@@ -4974,12 +5198,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4974
5198
|
prompt,
|
|
4975
5199
|
modelKey,
|
|
4976
5200
|
agentflowUserId: userCtx.userId || "",
|
|
4977
|
-
extraEnv: {
|
|
5201
|
+
extraEnv: runtimeEnv({
|
|
4978
5202
|
AGENTFLOW_WORKSPACE_TMP_ROOT: runTmpRoot,
|
|
4979
5203
|
AGENTFLOW_NODE_RUN_DIR: runPackage.nodeRunDir,
|
|
4980
5204
|
AGENTFLOW_NODE_TMP_DIR: runPackage.nodeTmpDir,
|
|
4981
5205
|
AGENTFLOW_OUTPUTS_DIR: runPackage.outputsDir,
|
|
4982
|
-
},
|
|
5206
|
+
}),
|
|
4983
5207
|
onStreamEvent: (ev) => {
|
|
4984
5208
|
if (!firstAgentEventSeen) {
|
|
4985
5209
|
firstAgentEventSeen = true;
|
|
@@ -6363,27 +6587,16 @@ export function startUiServer({
|
|
|
6363
6587
|
json(res, 400, { error: "请选择至少一个 display 节点" });
|
|
6364
6588
|
return;
|
|
6365
6589
|
}
|
|
6366
|
-
const
|
|
6367
|
-
let id = createDisplayShareId();
|
|
6368
|
-
while (shares[id]) id = createDisplayShareId();
|
|
6369
|
-
const nowDate = new Date();
|
|
6370
|
-
const now = nowDate.toISOString();
|
|
6371
|
-
const share = {
|
|
6372
|
-
id,
|
|
6590
|
+
const share = createDisplayShareRecord({
|
|
6373
6591
|
userId: authUser.userId,
|
|
6374
6592
|
flowId: scoped.flowId || "",
|
|
6375
6593
|
flowSource: scoped.flowSource || "user",
|
|
6376
6594
|
archived: scoped.archived === true,
|
|
6377
|
-
title:
|
|
6378
|
-
layout:
|
|
6595
|
+
title: payload.title,
|
|
6596
|
+
layout: payload.layout,
|
|
6379
6597
|
nodeIds,
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
expiresAt: displayShareExpiresAt(nowDate),
|
|
6383
|
-
};
|
|
6384
|
-
shares[id] = share;
|
|
6385
|
-
writeDisplayShares(shares);
|
|
6386
|
-
json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(id)}` });
|
|
6598
|
+
});
|
|
6599
|
+
json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(share.id)}` });
|
|
6387
6600
|
} catch (e) {
|
|
6388
6601
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
6389
6602
|
}
|
|
@@ -6897,13 +7110,9 @@ export function startUiServer({
|
|
|
6897
7110
|
return;
|
|
6898
7111
|
}
|
|
6899
7112
|
const safeName = sanitizeWorkspaceUploadName(parsed.filename);
|
|
6900
|
-
const
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
return;
|
|
6904
|
-
}
|
|
6905
|
-
const targetDir = String(parsed.fields.dir || "img").trim().replace(/^[/\\]+/, "") || "img";
|
|
6906
|
-
const target = uniqueWorkspaceRelPath(scoped.root, path.posix.join(targetDir.replace(/\\/g, "/"), safeName));
|
|
7113
|
+
const targetDir = String(parsed.fields.dir ?? "").trim().replace(/^[/\\]+/, "").replace(/\\/g, "/");
|
|
7114
|
+
const targetRel = targetDir ? path.posix.join(targetDir, safeName) : safeName;
|
|
7115
|
+
const target = uniqueWorkspaceRelPath(scoped.root, targetRel);
|
|
6907
7116
|
fs.mkdirSync(path.dirname(target.abs), { recursive: true });
|
|
6908
7117
|
fs.writeFileSync(target.abs, parsed.file);
|
|
6909
7118
|
json(res, 200, {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Built-in node: directly provide a hidden password/secret value
|
|
3
|
+
description: Provide a secret text value without showing it on the node card
|
|
4
|
+
displayName: Password
|
|
5
|
+
input: []
|
|
6
|
+
output:
|
|
7
|
+
- type: text
|
|
8
|
+
name: value
|
|
9
|
+
default: ""
|
|
10
|
+
required: true
|
|
11
|
+
showOnNode: true
|
|
12
|
+
---
|
|
13
|
+
${USER_PROMPT}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Built-in node: create share link for Display nodes
|
|
3
|
+
description: Create a public share link for upstream Display nodes
|
|
4
|
+
displayName: Display Share Link
|
|
5
|
+
input:
|
|
6
|
+
- type: node
|
|
7
|
+
name: prev
|
|
8
|
+
default: ""
|
|
9
|
+
- type: text
|
|
10
|
+
name: title
|
|
11
|
+
default: ""
|
|
12
|
+
description: "分享页标题;为空时使用 AgentFlow Display。"
|
|
13
|
+
showOnNode: true
|
|
14
|
+
- type: text
|
|
15
|
+
name: layout
|
|
16
|
+
default: "single"
|
|
17
|
+
description: "分享布局:single、gallery、slides、document、canvas。"
|
|
18
|
+
showOnNode: true
|
|
19
|
+
- type: text
|
|
20
|
+
name: nodeIds
|
|
21
|
+
default: ""
|
|
22
|
+
description: "可选。逗号或空格分隔的 Display 节点 ID;为空时自动使用连接到本节点的上游 Display 节点。"
|
|
23
|
+
showOnNode: false
|
|
24
|
+
- type: text
|
|
25
|
+
name: baseUrl
|
|
26
|
+
default: ""
|
|
27
|
+
description: "可选。输出绝对链接的站点地址,例如 https://agentflow.example.com;为空时输出 /display/<id>。"
|
|
28
|
+
showOnNode: false
|
|
29
|
+
output:
|
|
30
|
+
- type: node
|
|
31
|
+
name: next
|
|
32
|
+
default: ""
|
|
33
|
+
- type: text
|
|
34
|
+
name: url
|
|
35
|
+
default: ""
|
|
36
|
+
showOnNode: true
|
|
37
|
+
- type: text
|
|
38
|
+
name: shareId
|
|
39
|
+
default: ""
|
|
40
|
+
showOnNode: false
|
|
41
|
+
- type: text
|
|
42
|
+
name: expiresAt
|
|
43
|
+
default: ""
|
|
44
|
+
showOnNode: false
|
|
45
|
+
---
|
|
46
|
+
Create a share link for connected Display nodes.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Built-in node: set environment variables for the current run only
|
|
3
|
+
description: Set environment variables for downstream nodes in the current workspace run
|
|
4
|
+
displayName: Set Run Env
|
|
5
|
+
input:
|
|
6
|
+
- type: node
|
|
7
|
+
name: prev
|
|
8
|
+
default: ""
|
|
9
|
+
- type: text
|
|
10
|
+
name: key
|
|
11
|
+
default: ""
|
|
12
|
+
description: "单个环境变量名,例如 IMAP_USER。"
|
|
13
|
+
showOnNode: true
|
|
14
|
+
- type: text
|
|
15
|
+
name: value
|
|
16
|
+
default: ""
|
|
17
|
+
description: "单个环境变量值。"
|
|
18
|
+
showOnNode: true
|
|
19
|
+
- type: text
|
|
20
|
+
name: variables
|
|
21
|
+
default: ""
|
|
22
|
+
description: "批量设置,支持多行 KEY=VALUE 或 JSON object。"
|
|
23
|
+
showOnNode: false
|
|
24
|
+
output:
|
|
25
|
+
- type: node
|
|
26
|
+
name: next
|
|
27
|
+
default: ""
|
|
28
|
+
- type: text
|
|
29
|
+
name: keys
|
|
30
|
+
default: ""
|
|
31
|
+
showOnNode: false
|
|
32
|
+
- type: text
|
|
33
|
+
name: count
|
|
34
|
+
default: ""
|
|
35
|
+
showOnNode: false
|
|
36
|
+
---
|
|
37
|
+
Set environment variables for downstream nodes in this run only.
|