@fieldwangai/agentflow 0.1.38 → 0.1.40
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/agent-runners.mjs +2 -2
- package/bin/lib/apply.mjs +2 -2
- package/bin/lib/composer-agent.mjs +63 -3
- package/bin/lib/marketplace.mjs +18 -0
- package/bin/lib/scheduler.mjs +3 -3
- package/bin/lib/ui-server.mjs +209 -31
- package/bin/lib/user-env.mjs +32 -2
- package/bin/pipeline/run-tool-nodejs.mjs +2 -2
- package/builtin/web-ui/dist/assets/index-COHXnLDo.js +218 -0
- package/builtin/web-ui/dist/assets/index-YXZNOj6z.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-BftRvss5.js +0 -218
- package/builtin/web-ui/dist/assets/index-Dzo7k7P-.css +0 -1
|
@@ -9,7 +9,7 @@ import { normalizeCursorModelForCli } from "./model-config.mjs";
|
|
|
9
9
|
import { appendRunLogLine } from "./run-events.mjs";
|
|
10
10
|
import { writeWithPrefix } from "./terminal.mjs";
|
|
11
11
|
import { t } from "./i18n.mjs";
|
|
12
|
-
import {
|
|
12
|
+
import { readMergedEnvObject } from "./user-env.mjs";
|
|
13
13
|
import { outputNodeBasename } from "../pipeline/get-exec-id.mjs";
|
|
14
14
|
|
|
15
15
|
function shouldPassCursorModelArg(model) {
|
|
@@ -20,7 +20,7 @@ function shouldPassCursorModelArg(model) {
|
|
|
20
20
|
function childEnv(options = {}, extra = {}) {
|
|
21
21
|
const optEnv = options && options.env && typeof options.env === "object" ? options.env : {};
|
|
22
22
|
const userId = optEnv.AGENTFLOW_USER_ID || process.env.AGENTFLOW_USER_ID || "";
|
|
23
|
-
return { ...process.env, ...
|
|
23
|
+
return { ...process.env, ...readMergedEnvObject(userId), ...optEnv, ...extra };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function writeAgentTextArtifacts(absResultPath, absRunDir, instanceId, text) {
|
package/bin/lib/apply.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import { formatDuration } from "./terminal.mjs";
|
|
|
23
23
|
import { printEntryAndFlowFiles, printNodeStatusTable, runValidateFlowAndExitIfInvalid } from "./ui-print.mjs";
|
|
24
24
|
import { clearApplyActiveLock, writeApplyActiveLock } from "./run-apply-active-lock.mjs";
|
|
25
25
|
import { ensureReference, findFlowNameByUuid, getFlowDir, getRunDir } from "./workspace.mjs";
|
|
26
|
-
import {
|
|
26
|
+
import { readMergedEnvObject } from "./user-env.mjs";
|
|
27
27
|
|
|
28
28
|
const PARALLEL_PREFIX_COLORS = [
|
|
29
29
|
(s) => chalk.cyan(s),
|
|
@@ -347,7 +347,7 @@ ${currentContent}
|
|
|
347
347
|
|
|
348
348
|
const result = spawnSync(opencodeCmd, ["--prompt-file", tmpPromptFile, "--print"], {
|
|
349
349
|
cwd: workspaceRoot,
|
|
350
|
-
env: { ...process.env, ...
|
|
350
|
+
env: { ...process.env, ...readMergedEnvObject(process.env.AGENTFLOW_USER_ID || ""), OPENCODE_NON_INTERACTIVE: "1" },
|
|
351
351
|
stdio: ["ignore", "pipe", "pipe"],
|
|
352
352
|
});
|
|
353
353
|
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* 用户 prompt → planner 分解 → [script 直执 | agent 子调用(按复杂度选模型)] → sync UI
|
|
6
6
|
*/
|
|
7
7
|
import fs from "fs";
|
|
8
|
+
import os from "os";
|
|
8
9
|
import path from "path";
|
|
9
|
-
import { getAgentflowDataRoot, sanitizeAgentflowUserId } from "./paths.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { getAgentflowDataRoot, getAgentflowUserDataRoot, sanitizeAgentflowUserId } from "./paths.mjs";
|
|
11
|
+
import { readMergedEnvObject } from "./user-env.mjs";
|
|
11
12
|
import { resolveCliAndModel } from "./model-config.mjs";
|
|
12
13
|
import { runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
|
|
13
14
|
import { planComposerTasks, hasPlannerApiAvailable, shouldUsePhased, classifyComplexity, classifyTaskComplexity, PHASED_DEFINITIONS } from "./composer-planner.mjs";
|
|
@@ -24,9 +25,68 @@ const MAX_PROMPT_CHARS = 500_000;
|
|
|
24
25
|
const MAX_COMPOSER_VALIDATION_REPAIR = 5;
|
|
25
26
|
const MAX_SCRIPT_INJECT_BYTES = 30_000;
|
|
26
27
|
|
|
28
|
+
function readJsonObject(filePath) {
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(filePath)) return {};
|
|
31
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
32
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
33
|
+
} catch {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readUserMcpPrivateEnvObject(userId) {
|
|
39
|
+
const safe = sanitizeAgentflowUserId(userId);
|
|
40
|
+
const data = readJsonObject(path.join(getAgentflowUserDataRoot(safe), "mcp-private.json"));
|
|
41
|
+
const servers = data?.servers && typeof data.servers === "object" && !Array.isArray(data.servers) ? data.servers : {};
|
|
42
|
+
const env = {};
|
|
43
|
+
for (const server of Object.values(servers)) {
|
|
44
|
+
const serverEnv = server?.env && typeof server.env === "object" && !Array.isArray(server.env) ? server.env : {};
|
|
45
|
+
for (const [key, value] of Object.entries(serverEnv)) {
|
|
46
|
+
const envKey = String(key || "").trim();
|
|
47
|
+
if (envKey) env[envKey] = String(value ?? "");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return env;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pruneCursorMcpPrivateEnvPlaceholders() {
|
|
54
|
+
const filePath = path.join(os.homedir(), ".cursor", "mcp.json");
|
|
55
|
+
const config = readJsonObject(filePath);
|
|
56
|
+
const servers = config?.mcpServers && typeof config.mcpServers === "object" && !Array.isArray(config.mcpServers)
|
|
57
|
+
? config.mcpServers
|
|
58
|
+
: null;
|
|
59
|
+
if (!servers) return;
|
|
60
|
+
let changed = false;
|
|
61
|
+
const nextServers = {};
|
|
62
|
+
for (const [name, raw] of Object.entries(servers)) {
|
|
63
|
+
const server = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...raw } : raw;
|
|
64
|
+
const privateEnvKeys = Array.isArray(server?.__agentflowPrivateKeys?.env)
|
|
65
|
+
? server.__agentflowPrivateKeys.env.map((key) => String(key || "").trim()).filter(Boolean)
|
|
66
|
+
: [];
|
|
67
|
+
if (!privateEnvKeys.length || !server?.env || typeof server.env !== "object" || Array.isArray(server.env)) {
|
|
68
|
+
nextServers[name] = server;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const nextEnv = { ...server.env };
|
|
72
|
+
for (const key of privateEnvKeys) {
|
|
73
|
+
if (Object.prototype.hasOwnProperty.call(nextEnv, key) && String(nextEnv[key] ?? "") === "") {
|
|
74
|
+
delete nextEnv[key];
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
nextServers[name] = { ...server, env: nextEnv };
|
|
79
|
+
if (Object.keys(nextEnv).length === 0) delete nextServers[name].env;
|
|
80
|
+
}
|
|
81
|
+
if (!changed) return;
|
|
82
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
83
|
+
fs.writeFileSync(filePath, JSON.stringify({ ...config, mcpServers: nextServers }, null, 2) + "\n", "utf-8");
|
|
84
|
+
}
|
|
85
|
+
|
|
27
86
|
function agentflowUserEnv(userId) {
|
|
28
87
|
const safe = sanitizeAgentflowUserId(userId);
|
|
29
|
-
|
|
88
|
+
pruneCursorMcpPrivateEnvPlaceholders();
|
|
89
|
+
return { ...readMergedEnvObject(safe), ...(safe ? readUserMcpPrivateEnvObject(safe) : {}), AGENTFLOW_USER_ID: safe };
|
|
30
90
|
}
|
|
31
91
|
|
|
32
92
|
// ─── script 内容注入辅助 ─────────────────────────────────────────────────
|
package/bin/lib/marketplace.mjs
CHANGED
|
@@ -449,6 +449,24 @@ export function deleteMarketplaceNodePackage(workspaceRoot, id, version, opts =
|
|
|
449
449
|
return { ok: true, id, version, packageDir };
|
|
450
450
|
}
|
|
451
451
|
|
|
452
|
+
export function deleteMarketplaceFlowSnippetPackage(workspaceRoot, id, version) {
|
|
453
|
+
const packageDir = resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version);
|
|
454
|
+
if (!packageDir) return { ok: false, error: "Invalid flow snippet id or version" };
|
|
455
|
+
if (!fs.existsSync(path.join(packageDir, FLOW_SNIPPET_MANIFEST))) {
|
|
456
|
+
return { ok: false, error: `Flow snippet package not found: ${id}@${version}` };
|
|
457
|
+
}
|
|
458
|
+
fs.rmSync(packageDir, { recursive: true, force: true });
|
|
459
|
+
const versionRoot = path.dirname(packageDir);
|
|
460
|
+
try {
|
|
461
|
+
if (fs.existsSync(versionRoot) && fs.readdirSync(versionRoot).length === 0) {
|
|
462
|
+
fs.rmdirSync(versionRoot);
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
/* keep non-empty or unreadable parent */
|
|
466
|
+
}
|
|
467
|
+
return { ok: true, id, version, packageDir };
|
|
468
|
+
}
|
|
469
|
+
|
|
452
470
|
export function writeFlowMarketplaceLock(workspaceRoot, flowDir, flowData) {
|
|
453
471
|
if (!flowData || !flowData.instances || typeof flowData.instances !== "object") return null;
|
|
454
472
|
const nodes = {};
|
package/bin/lib/scheduler.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { getAgentflowUserContexts, getRunDir, PACKAGE_ROOT } from "./paths.mjs";
|
|
13
13
|
import { isApplyProcessAlive } from "./run-apply-active-lock.mjs";
|
|
14
14
|
import { log } from "./log.mjs";
|
|
15
|
-
import {
|
|
15
|
+
import { readMergedEnvObject } from "./user-env.mjs";
|
|
16
16
|
import { writeResult } from "../pipeline/write-result.mjs";
|
|
17
17
|
|
|
18
18
|
const DEFAULT_POLL_MS = 30_000;
|
|
@@ -258,7 +258,7 @@ function startScheduledRun(workspaceRoot, flow, schedule, state, opts = {}) {
|
|
|
258
258
|
const child = spawn(process.execPath, args, {
|
|
259
259
|
cwd: path.resolve(workspaceRoot),
|
|
260
260
|
stdio: ["ignore", "pipe", "pipe"],
|
|
261
|
-
env: { ...process.env, ...
|
|
261
|
+
env: { ...process.env, ...readMergedEnvObject(opts.userId), FORCE_COLOR: "0", AGENTFLOW_USER_ID: opts.userId || "" },
|
|
262
262
|
detached: true,
|
|
263
263
|
});
|
|
264
264
|
|
|
@@ -331,7 +331,7 @@ function startWaitingRunResume(workspaceRoot, flow, waitState, opts = {}) {
|
|
|
331
331
|
const child = spawn(process.execPath, args, {
|
|
332
332
|
cwd: path.resolve(workspaceRoot),
|
|
333
333
|
stdio: ["ignore", "pipe", "pipe"],
|
|
334
|
-
env: { ...process.env, ...
|
|
334
|
+
env: { ...process.env, ...readMergedEnvObject(opts.userId), FORCE_COLOR: "0", AGENTFLOW_USER_ID: opts.userId || "" },
|
|
335
335
|
detached: true,
|
|
336
336
|
});
|
|
337
337
|
child.stdout.on("data", () => {});
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -77,6 +77,7 @@ import { runNodeScript } from "./pipeline-scripts.mjs";
|
|
|
77
77
|
import { readFlowSchedule, writeFlowSchedule } from "./schedule-config.mjs";
|
|
78
78
|
import { listScheduleStatuses } from "./scheduler.mjs";
|
|
79
79
|
import {
|
|
80
|
+
deleteMarketplaceFlowSnippetPackage,
|
|
80
81
|
deleteMarketplaceNodePackage,
|
|
81
82
|
installFlowDependency,
|
|
82
83
|
listMarketplaceFlowSnippets,
|
|
@@ -96,7 +97,7 @@ import {
|
|
|
96
97
|
logoutRequest,
|
|
97
98
|
readUserAllowlist,
|
|
98
99
|
} from "./auth.mjs";
|
|
99
|
-
import {
|
|
100
|
+
import { readGlobalEnvRows, readMergedEnvObject, readUserEnvRows, writeGlobalEnvRows, writeUserEnvRows } from "./user-env.mjs";
|
|
100
101
|
import {
|
|
101
102
|
readAdminBuiltinPipelineConfig,
|
|
102
103
|
updateAdminBuiltinPipelineConfig,
|
|
@@ -189,7 +190,6 @@ function writeFeedbackItems(items) {
|
|
|
189
190
|
function createFeedbackItem(payload, user) {
|
|
190
191
|
const title = String(payload?.title || "").trim().slice(0, 120);
|
|
191
192
|
const content = String(payload?.content || "").trim().slice(0, 5000);
|
|
192
|
-
const contact = String(payload?.contact || "").trim().slice(0, 160);
|
|
193
193
|
const pageUrl = String(payload?.pageUrl || "").trim().slice(0, 500);
|
|
194
194
|
if (!title) return { error: "Missing feedback title" };
|
|
195
195
|
if (!content) return { error: "Missing feedback content" };
|
|
@@ -198,7 +198,6 @@ function createFeedbackItem(payload, user) {
|
|
|
198
198
|
id: `fb_${Date.now().toString(36)}_${crypto.randomBytes(5).toString("hex")}`,
|
|
199
199
|
title,
|
|
200
200
|
content,
|
|
201
|
-
contact,
|
|
202
201
|
pageUrl,
|
|
203
202
|
userId: String(user?.userId || ""),
|
|
204
203
|
username: String(user?.username || user?.userId || ""),
|
|
@@ -389,7 +388,7 @@ function removeSkillhubCollectionGroup(userCtx = {}, collectionId = "", root = p
|
|
|
389
388
|
function runtimeEnvForUser(userCtx = {}, extra = {}) {
|
|
390
389
|
return {
|
|
391
390
|
...process.env,
|
|
392
|
-
...
|
|
391
|
+
...readMergedEnvObject(userCtx.userId),
|
|
393
392
|
...extra,
|
|
394
393
|
AGENTFLOW_USER_ID: userCtx.userId || "",
|
|
395
394
|
};
|
|
@@ -564,8 +563,6 @@ function writeCursorMcpServer(payload = {}, userCtx = {}) {
|
|
|
564
563
|
env: omitObjectKeys(server.env || {}, privateEnvKeys),
|
|
565
564
|
headers: omitObjectKeys(server.headers || {}, privateHeaderKeys),
|
|
566
565
|
};
|
|
567
|
-
publicServer.env = withPrivatePlaceholders(publicServer.env, privateEnvKeys);
|
|
568
|
-
publicServer.headers = withPrivatePlaceholders(publicServer.headers, privateHeaderKeys);
|
|
569
566
|
if (privateEnvKeys.size || privateHeaderKeys.size) {
|
|
570
567
|
publicServer.__agentflowPrivateKeys = {
|
|
571
568
|
...(privateEnvKeys.size ? { env: Array.from(privateEnvKeys) } : {}),
|
|
@@ -1368,6 +1365,125 @@ function workspaceSetOutputSlot(instance, name, value) {
|
|
|
1368
1365
|
};
|
|
1369
1366
|
}
|
|
1370
1367
|
|
|
1368
|
+
function workspaceSourceSlotForEdge(graph, edge) {
|
|
1369
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1370
|
+
const source = instances[String(edge?.source || "")];
|
|
1371
|
+
const output = Array.isArray(source?.output) ? source.output : [];
|
|
1372
|
+
return output[workspaceHandleIndex(edge?.sourceHandle, "output")] || null;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function workspaceOutputSlotValueForEdge(graph, outputs, edge) {
|
|
1376
|
+
const sourceId = String(edge?.source || "");
|
|
1377
|
+
const slot = workspaceSourceSlotForEdge(graph, edge);
|
|
1378
|
+
if (slot && String(slot?.type || "") !== "node") {
|
|
1379
|
+
const value = workspaceSlotValue(slot);
|
|
1380
|
+
if (value.trim()) return value;
|
|
1381
|
+
}
|
|
1382
|
+
const out = outputs.get(sourceId);
|
|
1383
|
+
if (out != null && String(out).trim()) return String(out);
|
|
1384
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1385
|
+
return workspaceInstanceText(instances[sourceId]);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function workspaceParseJsonObjectFromText(text) {
|
|
1389
|
+
const raw = String(text || "").trim();
|
|
1390
|
+
if (!raw) return null;
|
|
1391
|
+
const candidates = [raw];
|
|
1392
|
+
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
1393
|
+
if (fenced?.[1]) candidates.unshift(fenced[1].trim());
|
|
1394
|
+
const first = raw.indexOf("{");
|
|
1395
|
+
const last = raw.lastIndexOf("}");
|
|
1396
|
+
if (first >= 0 && last > first) candidates.unshift(raw.slice(first, last + 1));
|
|
1397
|
+
for (const candidate of candidates) {
|
|
1398
|
+
try {
|
|
1399
|
+
const parsed = JSON.parse(candidate);
|
|
1400
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
1401
|
+
} catch {
|
|
1402
|
+
/* try next */
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function workspaceStringifyOutputValue(value) {
|
|
1409
|
+
if (value == null) return "";
|
|
1410
|
+
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
function workspaceStructuredAgentOutput(content) {
|
|
1414
|
+
const raw = String(content || "").trim();
|
|
1415
|
+
const parsed = workspaceParseJsonObjectFromText(raw);
|
|
1416
|
+
if (!parsed) return { result: raw, outParams: {}, structured: false, parsed: null };
|
|
1417
|
+
const hasEnvelope = Object.prototype.hasOwnProperty.call(parsed, "result") ||
|
|
1418
|
+
Object.prototype.hasOwnProperty.call(parsed, "outParams");
|
|
1419
|
+
if (!hasEnvelope) return { result: raw, outParams: {}, structured: false, parsed };
|
|
1420
|
+
const outParamsRaw = parsed.outParams && typeof parsed.outParams === "object" && !Array.isArray(parsed.outParams)
|
|
1421
|
+
? parsed.outParams
|
|
1422
|
+
: {};
|
|
1423
|
+
const outParams = {};
|
|
1424
|
+
for (const [key, value] of Object.entries(outParamsRaw)) {
|
|
1425
|
+
const name = String(key || "").trim();
|
|
1426
|
+
if (name) outParams[name] = workspaceStringifyOutputValue(value);
|
|
1427
|
+
}
|
|
1428
|
+
return {
|
|
1429
|
+
result: workspaceStringifyOutputValue(parsed.result ?? ""),
|
|
1430
|
+
outParams,
|
|
1431
|
+
structured: true,
|
|
1432
|
+
parsed,
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function workspaceExtractNamedOutputValue(content, slotName) {
|
|
1437
|
+
const name = String(slotName || "").trim();
|
|
1438
|
+
if (!name) return "";
|
|
1439
|
+
const structured = workspaceStructuredAgentOutput(content);
|
|
1440
|
+
if (Object.prototype.hasOwnProperty.call(structured.outParams, name)) {
|
|
1441
|
+
return String(structured.outParams[name] ?? "");
|
|
1442
|
+
}
|
|
1443
|
+
const parsed = structured.parsed || workspaceParseJsonObjectFromText(content);
|
|
1444
|
+
if (parsed && Object.prototype.hasOwnProperty.call(parsed, name)) {
|
|
1445
|
+
const value = parsed[name];
|
|
1446
|
+
return workspaceStringifyOutputValue(value);
|
|
1447
|
+
}
|
|
1448
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1449
|
+
const patterns = [
|
|
1450
|
+
new RegExp(`(?:\\$\\{${escaped}\\}|\\$${escaped})\\s*[=::]\\s*([^\\n\\r]+)`, "i"),
|
|
1451
|
+
new RegExp(`(?:^|[\\n\\r])\\s*${escaped}\\s*[=::]\\s*([^\\n\\r]+)`, "i"),
|
|
1452
|
+
];
|
|
1453
|
+
for (const pattern of patterns) {
|
|
1454
|
+
const match = pattern.exec(String(content || ""));
|
|
1455
|
+
if (!match?.[1]) continue;
|
|
1456
|
+
return match[1].replace(/^["'`]|["'`]$/g, "").trim();
|
|
1457
|
+
}
|
|
1458
|
+
return "";
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function workspaceApplyAgentOutputSlots(instance, content) {
|
|
1462
|
+
const structured = workspaceStructuredAgentOutput(content);
|
|
1463
|
+
const text = String(structured.result || "").trim();
|
|
1464
|
+
let changed = false;
|
|
1465
|
+
const next = {
|
|
1466
|
+
...(instance || {}),
|
|
1467
|
+
output: (Array.isArray(instance?.output) ? instance.output : []).map((slot, index) => {
|
|
1468
|
+
const name = String(slot?.name || "").trim();
|
|
1469
|
+
const type = String(slot?.type || "");
|
|
1470
|
+
if (type === "node" || name === "next" || !name) return slot;
|
|
1471
|
+
let value = "";
|
|
1472
|
+
if (name === "result" || name === "content" || index === 0) {
|
|
1473
|
+
value = text;
|
|
1474
|
+
} else if (Object.prototype.hasOwnProperty.call(structured.outParams, name)) {
|
|
1475
|
+
value = structured.outParams[name];
|
|
1476
|
+
} else {
|
|
1477
|
+
value = workspaceExtractNamedOutputValue(text, name);
|
|
1478
|
+
}
|
|
1479
|
+
if (!value) return slot;
|
|
1480
|
+
changed = true;
|
|
1481
|
+
return { ...slot, default: value, value };
|
|
1482
|
+
}),
|
|
1483
|
+
};
|
|
1484
|
+
return { instance: changed ? next : instance, changed };
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1371
1487
|
function workspaceResolvePath(baseCwd, raw) {
|
|
1372
1488
|
const text = String(raw || "").trim();
|
|
1373
1489
|
if (!text) return "";
|
|
@@ -1452,25 +1568,25 @@ function workspaceDownstreamDisplayRequirements(graph, nodeId) {
|
|
|
1452
1568
|
if (kinds.size === 0) return "";
|
|
1453
1569
|
const rules = [];
|
|
1454
1570
|
if (kinds.has("html")) {
|
|
1455
|
-
rules.push("- 下游连接了 HTML
|
|
1571
|
+
rules.push("- 下游连接了 HTML 展示节点:将可直接放入 iframe 渲染的 HTML 放在输出协议的 `result` 字段中。可以是完整 HTML 文档或 HTML fragment;不要使用 Markdown 代码围栏。");
|
|
1456
1572
|
}
|
|
1457
1573
|
if (kinds.has("markdown")) {
|
|
1458
|
-
rules.push("- 下游连接了 Markdown
|
|
1574
|
+
rules.push("- 下游连接了 Markdown 展示节点:将 Markdown 正文放在输出协议的 `result` 字段中;除非正文确实需要代码块,否则不要额外包裹代码围栏。");
|
|
1459
1575
|
}
|
|
1460
1576
|
if (kinds.has("mermaid")) {
|
|
1461
|
-
rules.push("- 下游连接了 Mermaid
|
|
1577
|
+
rules.push("- 下游连接了 Mermaid 展示节点:将 Mermaid 图表代码放在输出协议的 `result` 字段中,例如 flowchart/sequenceDiagram;不要使用 Markdown 代码围栏。");
|
|
1462
1578
|
}
|
|
1463
1579
|
if (kinds.has("ascii")) {
|
|
1464
|
-
rules.push("- 下游连接了 ASCII
|
|
1580
|
+
rules.push("- 下游连接了 ASCII 展示节点:将纯文本/ASCII 图或表格放在输出协议的 `result` 字段中;不要输出 HTML 或 Markdown 装饰。");
|
|
1465
1581
|
}
|
|
1466
1582
|
if (kinds.has("image")) {
|
|
1467
|
-
rules.push("-
|
|
1583
|
+
rules.push("- 下游连接了图片展示节点:将可作为 img src 使用的图片地址、data URL 或 base64 data URL 放在输出协议的 `result` 字段中;不要输出 Markdown 图片语法。");
|
|
1468
1584
|
}
|
|
1469
1585
|
if (kinds.has("chart")) {
|
|
1470
|
-
rules.push('- 下游连接了 Chart
|
|
1586
|
+
rules.push('- 下游连接了 Chart 展示节点:将 ChartSpec JSON 对象放在输出协议的 `result` 字段中。ChartSpec 必须包含 `"type":"chart"`、`"version":"1.0"`、`"renderer":"echarts"`、`"option"`;`option.series[].type` 只使用 line/bar/pie/scatter/radar/heatmap/tree/treemap/sunburst/sankey/graph/gauge/funnel;不要输出 HTML、script、iframe 或 JS 函数。');
|
|
1471
1587
|
}
|
|
1472
1588
|
if (kinds.has("table")) {
|
|
1473
|
-
rules.push('-
|
|
1589
|
+
rules.push('- 下游连接了表格展示节点:将表格数据放在输出协议的 `result` 字段中。推荐格式:`{"columns":["列名1","列名2"],"rows":[["值1","值2"]]}`;也可使用对象数组、Markdown 表格、CSV 或 TSV。不要输出 HTML。');
|
|
1474
1590
|
}
|
|
1475
1591
|
return [
|
|
1476
1592
|
"## 下游输出要求",
|
|
@@ -1481,6 +1597,35 @@ function workspaceDownstreamDisplayRequirements(graph, nodeId) {
|
|
|
1481
1597
|
].join("\n");
|
|
1482
1598
|
}
|
|
1483
1599
|
|
|
1600
|
+
function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
1601
|
+
const instance = graph?.instances?.[nodeId] || {};
|
|
1602
|
+
const slots = (Array.isArray(instance.output) ? instance.output : [])
|
|
1603
|
+
.filter((slot) => {
|
|
1604
|
+
const name = String(slot?.name || "").trim();
|
|
1605
|
+
const type = String(slot?.type || "");
|
|
1606
|
+
return name && type !== "node" && name !== "next" && name !== "result" && name !== "content";
|
|
1607
|
+
})
|
|
1608
|
+
.map((slot) => String(slot.name).trim());
|
|
1609
|
+
const outParamsExample = slots.length
|
|
1610
|
+
? Object.fromEntries(slots.map((name) => [name, `<${name} 的值>`]))
|
|
1611
|
+
: {};
|
|
1612
|
+
return [
|
|
1613
|
+
"## Workspace 输出协议",
|
|
1614
|
+
"",
|
|
1615
|
+
"最终回复必须是一个 JSON 对象,不要使用 Markdown 代码围栏,不要在 JSON 外追加解释文字。",
|
|
1616
|
+
"固定格式:",
|
|
1617
|
+
"",
|
|
1618
|
+
JSON.stringify({ result: "<给用户看的完整正文>", outParams: outParamsExample }, null, 2),
|
|
1619
|
+
"",
|
|
1620
|
+
"- `result`:完整正文,写入 `result` / `content` 输出口,直连默认展示节点时展示它。",
|
|
1621
|
+
"- `outParams`:具名输出参数,只写入同名输出引脚。",
|
|
1622
|
+
...(slots.length
|
|
1623
|
+
? [`- 当前节点具名输出槽:${slots.map((name) => `\`${name}\``).join("、")}。例如任务要求写入 \`${slots[0]}\` 时,放到 \`outParams.${slots[0]}\`。`]
|
|
1624
|
+
: ["- 当前节点没有额外具名输出槽,`outParams` 返回空对象即可。"]),
|
|
1625
|
+
"- 如果 `result` 需要承载表格、ChartSpec、HTML 等结构化内容,可把对象或字符串放入 `result`;系统会把它转换给下游展示节点。",
|
|
1626
|
+
].join("\n");
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1484
1629
|
function workspaceRunPlan(graph, runNodeId) {
|
|
1485
1630
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1486
1631
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
@@ -1541,10 +1686,7 @@ function workspaceUpstreamText(graph, nodeId, outputs) {
|
|
|
1541
1686
|
const incoming = edges.filter((edge) => String(edge?.target || "") === String(nodeId));
|
|
1542
1687
|
const contentEdge = incoming.find((edge) => String(edge?.targetHandle || "") === "input-1") || incoming[0];
|
|
1543
1688
|
if (!contentEdge) return "";
|
|
1544
|
-
|
|
1545
|
-
const out = outputs.get(sourceId);
|
|
1546
|
-
if (out != null && String(out).trim()) return String(out);
|
|
1547
|
-
return workspaceInstanceText(instances[sourceId]);
|
|
1689
|
+
return workspaceOutputSlotValueForEdge(graph, outputs, contentEdge);
|
|
1548
1690
|
}
|
|
1549
1691
|
|
|
1550
1692
|
function workspaceHandleIndex(handle, prefix) {
|
|
@@ -1572,10 +1714,7 @@ function workspaceTaskUpstreamText(graph, nodeId, outputs) {
|
|
|
1572
1714
|
const contentEdges = incoming.filter((edge) => !isWorkspaceSemanticInputSlot(workspaceTargetSlotForEdge(graph, edge)));
|
|
1573
1715
|
const contentEdge = contentEdges.find((edge) => String(edge?.targetHandle || "") === "input-1") || contentEdges[0];
|
|
1574
1716
|
if (!contentEdge) return "";
|
|
1575
|
-
|
|
1576
|
-
const out = outputs.get(sourceId);
|
|
1577
|
-
if (out != null && String(out).trim()) return String(out);
|
|
1578
|
-
return workspaceInstanceText(instances[sourceId]);
|
|
1717
|
+
return workspaceOutputSlotValueForEdge(graph, outputs, contentEdge);
|
|
1579
1718
|
}
|
|
1580
1719
|
|
|
1581
1720
|
function parseWorkspaceSkillKeys(raw) {
|
|
@@ -1726,7 +1865,7 @@ function workspaceWriteDisplayContent(instance, content) {
|
|
|
1726
1865
|
return next;
|
|
1727
1866
|
}
|
|
1728
1867
|
|
|
1729
|
-
function workspaceUpdateDirectDisplays(graph, sourceId, content) {
|
|
1868
|
+
function workspaceUpdateDirectDisplays(graph, sourceId, content, outputs = null) {
|
|
1730
1869
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1731
1870
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
1732
1871
|
const updated = [];
|
|
@@ -1735,7 +1874,8 @@ function workspaceUpdateDirectDisplays(graph, sourceId, content) {
|
|
|
1735
1874
|
const targetId = String(edge?.target || "");
|
|
1736
1875
|
const target = instances[targetId];
|
|
1737
1876
|
if (!target || !workspaceDisplayKind(target.definitionId)) continue;
|
|
1738
|
-
|
|
1877
|
+
const value = outputs ? workspaceOutputSlotValueForEdge(graph, outputs, edge) : String(content || "");
|
|
1878
|
+
instances[targetId] = workspaceWriteDisplayContent(target, value || content);
|
|
1739
1879
|
updated.push(targetId);
|
|
1740
1880
|
}
|
|
1741
1881
|
return updated;
|
|
@@ -1746,14 +1886,16 @@ function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock
|
|
|
1746
1886
|
const body = String(instance.body || "").trim();
|
|
1747
1887
|
const label = String(instance.label || nodeId).trim();
|
|
1748
1888
|
const downstreamRequirements = workspaceDownstreamDisplayRequirements(graph, nodeId);
|
|
1889
|
+
const outputProtocolRequirements = workspaceOutputProtocolRequirements(graph, nodeId);
|
|
1749
1890
|
return [
|
|
1750
1891
|
"你正在执行 AgentFlow Workspace 画布中的一个临时节点。",
|
|
1751
|
-
"
|
|
1892
|
+
"按 Workspace 输出协议返回该节点要传给下游展示/后续节点的数据。",
|
|
1752
1893
|
workspaceSearchGuardrailsBlock(),
|
|
1753
1894
|
skillsBlock ? `\n## Available Skills\n\n${skillsBlock}` : "",
|
|
1754
1895
|
mcpBlock ? `\n## Available MCP\n\n${mcpBlock}` : "",
|
|
1755
1896
|
upstreamText ? `\n## 上游上下文\n\n${upstreamText}` : "",
|
|
1756
1897
|
downstreamRequirements ? `\n${downstreamRequirements}` : "",
|
|
1898
|
+
outputProtocolRequirements ? `\n${outputProtocolRequirements}` : "",
|
|
1757
1899
|
`\n## 当前节点\n\n- id: ${nodeId}\n- label: ${label}\n- definitionId: ${instance.definitionId || ""}`,
|
|
1758
1900
|
`\n## 节点任务\n\n${body || upstreamText}`,
|
|
1759
1901
|
].filter(Boolean).join("\n");
|
|
@@ -1843,7 +1985,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
1843
1985
|
)),
|
|
1844
1986
|
};
|
|
1845
1987
|
outputs.set(nodeId, skillsBlock);
|
|
1846
|
-
workspaceUpdateDirectDisplays(graph, nodeId, skillsBlock);
|
|
1988
|
+
workspaceUpdateDirectDisplays(graph, nodeId, skillsBlock, outputs);
|
|
1847
1989
|
emit({ type: "graph", nodeId, graph });
|
|
1848
1990
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
1849
1991
|
continue;
|
|
@@ -1863,7 +2005,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
1863
2005
|
)),
|
|
1864
2006
|
};
|
|
1865
2007
|
outputs.set(nodeId, mcpBlock);
|
|
1866
|
-
workspaceUpdateDirectDisplays(graph, nodeId, mcpBlock);
|
|
2008
|
+
workspaceUpdateDirectDisplays(graph, nodeId, mcpBlock, outputs);
|
|
1867
2009
|
emit({ type: "graph", nodeId, graph });
|
|
1868
2010
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
1869
2011
|
continue;
|
|
@@ -2165,9 +2307,13 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
2165
2307
|
throw e;
|
|
2166
2308
|
}
|
|
2167
2309
|
}
|
|
2168
|
-
|
|
2169
|
-
const
|
|
2170
|
-
|
|
2310
|
+
const normalizedAgentOutput = workspaceStructuredAgentOutput(content);
|
|
2311
|
+
const resultContent = normalizedAgentOutput.result || content;
|
|
2312
|
+
outputs.set(nodeId, resultContent);
|
|
2313
|
+
const slotUpdate = workspaceApplyAgentOutputSlots(instance, content);
|
|
2314
|
+
if (slotUpdate.changed) graph.instances[nodeId] = slotUpdate.instance;
|
|
2315
|
+
const updatedDisplays = workspaceUpdateDirectDisplays(graph, nodeId, resultContent, outputs);
|
|
2316
|
+
if (slotUpdate.changed || updatedDisplays.length) emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
2171
2317
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
2172
2318
|
}
|
|
2173
2319
|
if (pauseNodeIds.length > 0) {
|
|
@@ -3568,7 +3714,11 @@ export function startUiServer({
|
|
|
3568
3714
|
|
|
3569
3715
|
if (req.method === "GET" && url.pathname === "/api/user-env") {
|
|
3570
3716
|
try {
|
|
3571
|
-
json(res, 200, {
|
|
3717
|
+
json(res, 200, {
|
|
3718
|
+
env: readUserEnvRows(userCtx.userId),
|
|
3719
|
+
globalEnv: authUser?.isAdmin ? readGlobalEnvRows() : [],
|
|
3720
|
+
canEditGlobalEnv: Boolean(authUser?.isAdmin),
|
|
3721
|
+
});
|
|
3572
3722
|
} catch (e) {
|
|
3573
3723
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
3574
3724
|
}
|
|
@@ -3584,8 +3734,20 @@ export function startUiServer({
|
|
|
3584
3734
|
return;
|
|
3585
3735
|
}
|
|
3586
3736
|
try {
|
|
3737
|
+
if (Object.prototype.hasOwnProperty.call(payload || {}, "globalEnv") && !authUser?.isAdmin) {
|
|
3738
|
+
json(res, 403, { error: "Admin permission required" });
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3587
3741
|
const envRows = writeUserEnvRows(userCtx.userId, payload?.env || []);
|
|
3588
|
-
|
|
3742
|
+
const globalEnvRows = authUser?.isAdmin && Object.prototype.hasOwnProperty.call(payload || {}, "globalEnv")
|
|
3743
|
+
? writeGlobalEnvRows(payload?.globalEnv || [])
|
|
3744
|
+
: readGlobalEnvRows();
|
|
3745
|
+
json(res, 200, {
|
|
3746
|
+
success: true,
|
|
3747
|
+
env: envRows,
|
|
3748
|
+
globalEnv: authUser?.isAdmin ? globalEnvRows : [],
|
|
3749
|
+
canEditGlobalEnv: Boolean(authUser?.isAdmin),
|
|
3750
|
+
});
|
|
3589
3751
|
} catch (e) {
|
|
3590
3752
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
3591
3753
|
}
|
|
@@ -3849,6 +4011,22 @@ export function startUiServer({
|
|
|
3849
4011
|
return;
|
|
3850
4012
|
}
|
|
3851
4013
|
|
|
4014
|
+
if (req.method === "DELETE" && url.pathname === "/api/marketplace/flow-snippet") {
|
|
4015
|
+
const id = url.searchParams.get("id") || "";
|
|
4016
|
+
const version = url.searchParams.get("version") || "";
|
|
4017
|
+
if (!id || !version) {
|
|
4018
|
+
json(res, 400, { ok: false, error: "Missing flow snippet id or version" });
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
try {
|
|
4022
|
+
const result = deleteMarketplaceFlowSnippetPackage(root, id, version);
|
|
4023
|
+
json(res, result.ok ? 200 : 400, result);
|
|
4024
|
+
} catch (e) {
|
|
4025
|
+
json(res, 500, { ok: false, error: (e && e.message) || String(e) });
|
|
4026
|
+
}
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
4029
|
+
|
|
3852
4030
|
if (req.method === "POST" && url.pathname === "/api/marketplace/install-node") {
|
|
3853
4031
|
let payload;
|
|
3854
4032
|
try {
|
package/bin/lib/user-env.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import os from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
|
-
import { getAgentflowUserEnvAbs, sanitizeAgentflowUserId } from "./paths.mjs";
|
|
5
|
+
import { getAgentflowDataRoot, getAgentflowUserEnvAbs, sanitizeAgentflowUserId } from "./paths.mjs";
|
|
6
6
|
|
|
7
7
|
function normalizeEnvKey(key) {
|
|
8
8
|
return String(key || "").trim();
|
|
@@ -60,6 +60,35 @@ export function readUserEnvObject(userId) {
|
|
|
60
60
|
return out;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function getGlobalEnvAbs() {
|
|
64
|
+
return path.join(getAgentflowDataRoot(), "admin", "env.json");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readGlobalEnvRows() {
|
|
68
|
+
const data = readJsonObject(getGlobalEnvAbs());
|
|
69
|
+
return normalizeUserEnvRows(Array.isArray(data.env) ? data.env : []);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function readGlobalEnvObject() {
|
|
73
|
+
const out = {};
|
|
74
|
+
for (const row of readGlobalEnvRows()) {
|
|
75
|
+
out[row.key] = row.value;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function writeGlobalEnvRows(rows) {
|
|
81
|
+
const normalized = normalizeUserEnvRows(rows);
|
|
82
|
+
const filePath = getGlobalEnvAbs();
|
|
83
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
84
|
+
fs.writeFileSync(filePath, JSON.stringify({ version: 1, env: normalized }, null, 2) + "\n", "utf-8");
|
|
85
|
+
return normalized;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function readMergedEnvObject(userId) {
|
|
89
|
+
return { ...readGlobalEnvObject(), ...readUserEnvObject(userId) };
|
|
90
|
+
}
|
|
91
|
+
|
|
63
92
|
export function writeUserEnvRows(userId, rows) {
|
|
64
93
|
const normalized = normalizeUserEnvRows(rows);
|
|
65
94
|
const safeUserId = sanitizeAgentflowUserId(userId);
|
|
@@ -74,10 +103,11 @@ export function resolveUserEnvValue(key, userId) {
|
|
|
74
103
|
if (!keyStr) return "";
|
|
75
104
|
const userEnv = readUserEnvObject(userId);
|
|
76
105
|
if (Object.prototype.hasOwnProperty.call(userEnv, keyStr)) return String(userEnv[keyStr] ?? "");
|
|
106
|
+
const globalEnv = readGlobalEnvObject();
|
|
107
|
+
if (Object.prototype.hasOwnProperty.call(globalEnv, keyStr)) return String(globalEnv[keyStr] ?? "");
|
|
77
108
|
const processValue = process.env[keyStr];
|
|
78
109
|
if (processValue != null && processValue !== "") return String(processValue);
|
|
79
110
|
const configPath = path.join(os.homedir(), ".cursor", "config.json");
|
|
80
111
|
const fromConfig = getFromConfig(readJsonObject(configPath), keyStr);
|
|
81
112
|
return fromConfig !== undefined ? fromConfig : "";
|
|
82
113
|
}
|
|
83
|
-
|
|
@@ -19,7 +19,7 @@ import path from "path";
|
|
|
19
19
|
import { fileURLToPath } from "url";
|
|
20
20
|
|
|
21
21
|
import { getRunDir } from "../lib/paths.mjs";
|
|
22
|
-
import {
|
|
22
|
+
import { readMergedEnvObject } from "../lib/user-env.mjs";
|
|
23
23
|
import { validateAndParse } from "./validate-script-output.mjs";
|
|
24
24
|
import { writeResult } from "./write-result.mjs";
|
|
25
25
|
import { loadExecId, outputNodeBasename, outputDirForNode } from "./get-exec-id.mjs";
|
|
@@ -31,7 +31,7 @@ const MAX_RETRIES = 3;
|
|
|
31
31
|
const RETRY_DELAY_MS = 1000;
|
|
32
32
|
|
|
33
33
|
function runtimeEnv() {
|
|
34
|
-
return { ...process.env, ...
|
|
34
|
+
return { ...process.env, ...readMergedEnvObject(process.env.AGENTFLOW_USER_ID || "") };
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function runOnce(workspaceRoot, flowName, uuid, instanceId, execId, scriptArgs) {
|