@fieldwangai/agentflow 0.1.66 → 0.1.68
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/ui-server.mjs +213 -24
- package/builtin/web-ui/dist/assets/{index-BFZ6_IPB.css → index-B7EYZmUN.css} +1 -1
- package/builtin/web-ui/dist/assets/index-D3NG6W8U.js +264 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-Ct8AjXGS.js +0 -264
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -97,6 +97,7 @@ import {
|
|
|
97
97
|
isAuthUserAllowed,
|
|
98
98
|
loginOrCreateUser,
|
|
99
99
|
logoutRequest,
|
|
100
|
+
readAuthUsers,
|
|
100
101
|
readUserAllowlist,
|
|
101
102
|
writeUserAllowlist,
|
|
102
103
|
} from "./auth.mjs";
|
|
@@ -257,8 +258,38 @@ function createFeedbackItem(payload, user) {
|
|
|
257
258
|
};
|
|
258
259
|
}
|
|
259
260
|
|
|
260
|
-
function skillCollectionsAbs(
|
|
261
|
-
return path.join(
|
|
261
|
+
function skillCollectionsAbs() {
|
|
262
|
+
return path.join(getAgentflowDataRoot(), "admin", SKILL_COLLECTIONS_FILENAME);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function legacyAdminSkillCollectionPaths() {
|
|
266
|
+
const users = readAuthUsers();
|
|
267
|
+
return Object.entries(users || {})
|
|
268
|
+
.filter(([, user]) => user?.isAdmin)
|
|
269
|
+
.map(([userId, user]) => path.join(getAgentflowUserDataRoot(user.userId || userId), SKILL_COLLECTIONS_FILENAME));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function readSkillCollectionFile(filePath) {
|
|
273
|
+
try {
|
|
274
|
+
if (!fs.existsSync(filePath)) return null;
|
|
275
|
+
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
276
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : null;
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function mergeSkillCollectionConfigs(configs = []) {
|
|
283
|
+
const merged = [];
|
|
284
|
+
const seen = new Set();
|
|
285
|
+
for (const config of configs) {
|
|
286
|
+
for (const collection of normalizeSkillCollectionConfig(config).collections) {
|
|
287
|
+
if (!collection.id || seen.has(collection.id)) continue;
|
|
288
|
+
seen.add(collection.id);
|
|
289
|
+
merged.push(collection);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { version: 1, collections: merged };
|
|
262
293
|
}
|
|
263
294
|
|
|
264
295
|
function slugifySkillCollectionId(name, fallback = "collection") {
|
|
@@ -382,8 +413,12 @@ function withBuiltinSkillCollections(config, availableSkills = []) {
|
|
|
382
413
|
function readSkillCollectionConfig(userCtx = {}, availableSkills = []) {
|
|
383
414
|
const p = skillCollectionsAbs(userCtx);
|
|
384
415
|
try {
|
|
385
|
-
|
|
386
|
-
return withBuiltinSkillCollections(
|
|
416
|
+
const globalConfig = readSkillCollectionFile(p);
|
|
417
|
+
if (globalConfig) return withBuiltinSkillCollections(globalConfig, availableSkills);
|
|
418
|
+
const legacyConfigs = legacyAdminSkillCollectionPaths()
|
|
419
|
+
.map((legacyPath) => readSkillCollectionFile(legacyPath))
|
|
420
|
+
.filter(Boolean);
|
|
421
|
+
return withBuiltinSkillCollections(mergeSkillCollectionConfigs(legacyConfigs), availableSkills);
|
|
387
422
|
} catch {
|
|
388
423
|
return withBuiltinSkillCollections({}, availableSkills);
|
|
389
424
|
}
|
|
@@ -1408,6 +1443,7 @@ function readWorkspaceGraph(workspaceRoot) {
|
|
|
1408
1443
|
}
|
|
1409
1444
|
|
|
1410
1445
|
const DISPLAY_SHARE_FILENAME = "display-shares.json";
|
|
1446
|
+
const DISPLAY_SHARE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
1411
1447
|
|
|
1412
1448
|
function displaySharesPath() {
|
|
1413
1449
|
return path.join(getAgentflowDataRoot(), DISPLAY_SHARE_FILENAME);
|
|
@@ -1432,6 +1468,26 @@ function createDisplayShareId() {
|
|
|
1432
1468
|
return crypto.randomBytes(12).toString("base64url");
|
|
1433
1469
|
}
|
|
1434
1470
|
|
|
1471
|
+
function displayShareExpiresAt(now = new Date()) {
|
|
1472
|
+
const time = now instanceof Date ? now.getTime() : Date.now();
|
|
1473
|
+
return new Date(time + DISPLAY_SHARE_TTL_MS).toISOString();
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function isDisplayShareExpired(share) {
|
|
1477
|
+
const expiresAt = Date.parse(String(share?.expiresAt || ""));
|
|
1478
|
+
return Number.isFinite(expiresAt) && expiresAt <= Date.now();
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function getDisplayShareOrExpired(id) {
|
|
1482
|
+
const shares = readDisplayShares();
|
|
1483
|
+
const share = shares[id];
|
|
1484
|
+
if (!share) return { shares, share: null, expired: false };
|
|
1485
|
+
if (!isDisplayShareExpired(share)) return { shares, share, expired: false };
|
|
1486
|
+
delete shares[id];
|
|
1487
|
+
writeDisplayShares(shares);
|
|
1488
|
+
return { shares, share: null, expired: true };
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1435
1491
|
function normalizeDisplayShareNodeIds(ids, graph) {
|
|
1436
1492
|
const out = [];
|
|
1437
1493
|
const seen = new Set();
|
|
@@ -1519,6 +1575,7 @@ function publicDisplayPayloadFromShare(root, share) {
|
|
|
1519
1575
|
: null,
|
|
1520
1576
|
createdAt: share.createdAt || "",
|
|
1521
1577
|
updatedAt: share.updatedAt || "",
|
|
1578
|
+
expiresAt: share.expiresAt || "",
|
|
1522
1579
|
},
|
|
1523
1580
|
nodes,
|
|
1524
1581
|
};
|
|
@@ -2416,7 +2473,7 @@ function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
|
2416
2473
|
].join("\n");
|
|
2417
2474
|
}
|
|
2418
2475
|
|
|
2419
|
-
function workspaceRunPlan(graph, runNodeId) {
|
|
2476
|
+
function workspaceRunPlan(graph, runNodeId, scopedRoot = "") {
|
|
2420
2477
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
2421
2478
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
2422
2479
|
const target = String(runNodeId || "").trim();
|
|
@@ -2464,6 +2521,7 @@ function workspaceRunPlan(graph, runNodeId) {
|
|
|
2464
2521
|
for (const edge of incoming.get(id) || []) {
|
|
2465
2522
|
const source = String(edge?.source || "");
|
|
2466
2523
|
if (!source || source === target || needed.has(source)) continue;
|
|
2524
|
+
if (!workspaceNeedsUpstreamExecutionForEdge(graph, edge, scopedRoot)) continue;
|
|
2467
2525
|
addNeeded(source);
|
|
2468
2526
|
if (needed.has(source)) dependencyQueue.push(source);
|
|
2469
2527
|
}
|
|
@@ -2511,6 +2569,42 @@ function workspaceIsControlEdge(graph, edge) {
|
|
|
2511
2569
|
workspaceIsControlInputSlot(workspaceTargetSlotForEdge(graph, edge));
|
|
2512
2570
|
}
|
|
2513
2571
|
|
|
2572
|
+
function workspaceNeedsUpstreamExecutionForEdge(graph, edge, scopedRoot = "") {
|
|
2573
|
+
if (workspaceIsControlEdge(graph, edge)) return true;
|
|
2574
|
+
return !workspaceEdgeHasCachedOutput(graph, edge, scopedRoot);
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
function workspaceEdgeHasCachedOutput(graph, edge, scopedRoot = "") {
|
|
2578
|
+
const sourceId = String(edge?.source || "");
|
|
2579
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
2580
|
+
const source = instances[sourceId];
|
|
2581
|
+
if (!source) return false;
|
|
2582
|
+
const slot = workspaceSourceSlotForEdge(graph, edge);
|
|
2583
|
+
if (!isWorkspaceSemanticOutputSlot(slot) && slot && String(slot?.type || "") !== "node") {
|
|
2584
|
+
const value = workspaceSlotValue(slot);
|
|
2585
|
+
if (workspaceCachedOutputValueExists(value, scopedRoot)) return true;
|
|
2586
|
+
}
|
|
2587
|
+
const defId = String(source.definitionId || "");
|
|
2588
|
+
if (workspaceDisplayKind(defId) && String(source.body || "").trim()) return true;
|
|
2589
|
+
if (defId === "provide_str" || defId === "provide_bool" || defId === "provide_file") {
|
|
2590
|
+
return Boolean(String(workspaceInstanceText(source) || "").trim());
|
|
2591
|
+
}
|
|
2592
|
+
return false;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
function workspaceCachedOutputValueExists(value, scopedRoot = "") {
|
|
2596
|
+
const text = String(value || "").trim();
|
|
2597
|
+
if (!text) return false;
|
|
2598
|
+
const outputRel = workspaceSafeNodeOutputRelPath(text);
|
|
2599
|
+
if (!outputRel) return true;
|
|
2600
|
+
const root = path.resolve(scopedRoot || "");
|
|
2601
|
+
if (!root) return false;
|
|
2602
|
+
const abs = path.resolve(root, outputRel);
|
|
2603
|
+
const rootWithSep = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
2604
|
+
if (abs !== root && !abs.startsWith(rootWithSep)) return false;
|
|
2605
|
+
return fs.existsSync(abs) && fs.statSync(abs).isFile();
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2514
2608
|
function workspaceUpstreamText(graph, nodeId, outputs) {
|
|
2515
2609
|
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
2516
2610
|
const incoming = edges
|
|
@@ -2539,13 +2633,17 @@ function isWorkspaceSemanticInputSlot(slot) {
|
|
|
2539
2633
|
return type === "node" || name === "prev" || name === "next" || name === "skillsContext" || name === "mcpContext" || name === "workspaceContext" || name === "gitContext";
|
|
2540
2634
|
}
|
|
2541
2635
|
|
|
2542
|
-
function workspaceAgentInputBlock(inputValues = {}) {
|
|
2636
|
+
function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
|
|
2543
2637
|
const entries = Object.entries(inputValues || {}).filter(([name, value]) => String(name || "").trim() && String(value || "").trim());
|
|
2544
2638
|
if (!entries.length) return "## 输入\n\n无。";
|
|
2545
2639
|
const lines = entries.map(([name, value]) => {
|
|
2546
2640
|
const text = String(value || "");
|
|
2547
2641
|
const clipped = text.length > 6000 ? `${text.slice(0, 6000)}\n...[已截断 ${text.length - 6000} 字]` : text;
|
|
2548
|
-
|
|
2642
|
+
const mount = inputMounts?.[name];
|
|
2643
|
+
const mountNote = mount?.mounted
|
|
2644
|
+
? `\n\n> 源文件:\`${mount.source}\`\n> 已挂载为当前任务可读文件:\`${mount.mounted}\`。请读取挂载路径,不要修改源文件。`
|
|
2645
|
+
: "";
|
|
2646
|
+
return `### ${name}\n\n${clipped}${mountNote}`;
|
|
2549
2647
|
});
|
|
2550
2648
|
return ["## 输入", "", ...lines].join("\n");
|
|
2551
2649
|
}
|
|
@@ -2560,6 +2658,7 @@ function workspaceNodeFileBoundaryBlock(runPackage = {}) {
|
|
|
2560
2658
|
"",
|
|
2561
2659
|
nodeRunDir ? `- 当前执行目录:\`${nodeRunDir}\`。` : "",
|
|
2562
2660
|
nodeTmpDir ? `- 临时文件只能写入:\`${nodeTmpDir}\`,也可通过环境变量 \`AGENTFLOW_NODE_TMP_DIR\` 获取。` : "",
|
|
2661
|
+
Object.keys(runPackage?.inputMounts || {}).length ? "- 已挂载的输入文件位于本任务 `inputs/`;`inputs/` 只用于读取,正式产物仍写入 `outputs/`。" : "",
|
|
2563
2662
|
`- 正式产物写入本任务 \`${outputsRel}/\`,例如 \`${outputsRel}/result.html\`、\`${outputsRel}/result.md\`;返回时仍使用 \`${outputsRel}/...\` 相对路径。`,
|
|
2564
2663
|
"- 不要在执行目录根部创建 `temp_*`、`_out.json`、`tmp.html` 等临时产物。",
|
|
2565
2664
|
"- 不要自行删除 run package;AgentFlow 会在运行结束后统一清理。",
|
|
@@ -2615,6 +2714,18 @@ function workspaceResolveBodyPlaceholders(body, inputValues = {}) {
|
|
|
2615
2714
|
});
|
|
2616
2715
|
}
|
|
2617
2716
|
|
|
2717
|
+
function workspacePromptUpstreamText(upstreamText, runPackage = {}) {
|
|
2718
|
+
const raw = String(upstreamText || "").trim();
|
|
2719
|
+
if (!raw) return "";
|
|
2720
|
+
for (const [name, mount] of Object.entries(runPackage?.inputMounts || {})) {
|
|
2721
|
+
if (!mount?.mounted) continue;
|
|
2722
|
+
if (raw === String(mount.source || "").trim() || raw === String(mount.mounted || "").trim()) {
|
|
2723
|
+
return `输入 \`${name}\` 已挂载为 \`${mount.mounted}\`。源文件:\`${mount.source}\`。`;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return upstreamText;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2618
2729
|
function parseWorkspaceSkillKeys(raw) {
|
|
2619
2730
|
const text = String(raw || "").trim();
|
|
2620
2731
|
if (!text) return [];
|
|
@@ -2792,7 +2903,7 @@ function workspaceNodePrompt(graph, nodeId, upstreamText, skillsBlock, mcpBlock
|
|
|
2792
2903
|
const body = workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim();
|
|
2793
2904
|
const { values: relevantInputValues, placeholders } = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
2794
2905
|
const runPackage = typeof nodeTmpDir === "object" && nodeTmpDir ? nodeTmpDir : { nodeTmpDir: String(nodeTmpDir || "") };
|
|
2795
|
-
const inputBlock = workspaceAgentInputBlock(relevantInputValues);
|
|
2906
|
+
const inputBlock = workspaceAgentInputBlock(relevantInputValues, runPackage.inputMounts || {});
|
|
2796
2907
|
const fileBoundary = workspaceNodeFileBoundaryBlock(runPackage);
|
|
2797
2908
|
const outputProtocolRequirements = workspaceOutputProtocolRequirements(graph, nodeId);
|
|
2798
2909
|
return [
|
|
@@ -2900,7 +3011,8 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, task =
|
|
|
2900
3011
|
const nodeRunDir = workspaceCreateNodeTmpDir(runTmpRoot, nodeId);
|
|
2901
3012
|
const nodeTmpDir = path.join(nodeRunDir, "tmp");
|
|
2902
3013
|
const outputsDir = path.join(nodeRunDir, "outputs");
|
|
2903
|
-
const
|
|
3014
|
+
const workspaceRoot = path.resolve(scopedRoot);
|
|
3015
|
+
const workspaceOutputsDir = path.join(workspaceRoot, "outputs");
|
|
2904
3016
|
fs.mkdirSync(nodeTmpDir, { recursive: true });
|
|
2905
3017
|
fs.mkdirSync(outputsDir, { recursive: true });
|
|
2906
3018
|
fs.mkdirSync(workspaceOutputsDir, { recursive: true });
|
|
@@ -2910,13 +3022,19 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, task =
|
|
|
2910
3022
|
nodeRunDir,
|
|
2911
3023
|
nodeTmpDir,
|
|
2912
3024
|
outputsDir,
|
|
3025
|
+
workspaceRoot,
|
|
2913
3026
|
createdAt: new Date().toISOString(),
|
|
2914
3027
|
};
|
|
3028
|
+
const materializedInputs = workspaceMaterializeNodeInputFiles(nodeRunDir, workspaceRoot, inputValues);
|
|
3029
|
+
const runtimeInputValues = { ...(inputValues || {}), ...(materializedInputs.values || {}) };
|
|
2915
3030
|
try {
|
|
2916
3031
|
fs.writeFileSync(path.join(nodeRunDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
2917
3032
|
fs.writeFileSync(path.join(nodeRunDir, "task.md"), String(task || "").trimEnd() + "\n", "utf-8");
|
|
2918
|
-
if (Object.keys(
|
|
2919
|
-
fs.writeFileSync(path.join(nodeRunDir, "inputs.json"), JSON.stringify(
|
|
3033
|
+
if (Object.keys(runtimeInputValues || {}).length) {
|
|
3034
|
+
fs.writeFileSync(path.join(nodeRunDir, "inputs.json"), JSON.stringify(runtimeInputValues, null, 2) + "\n", "utf-8");
|
|
3035
|
+
}
|
|
3036
|
+
if (Object.keys(materializedInputs.mounts || {}).length) {
|
|
3037
|
+
fs.writeFileSync(path.join(nodeRunDir, "inputs.manifest.json"), JSON.stringify(materializedInputs.mounts, null, 2) + "\n", "utf-8");
|
|
2920
3038
|
}
|
|
2921
3039
|
if (skillsBlock) fs.writeFileSync(path.join(nodeRunDir, "skills.md"), String(skillsBlock).trimEnd() + "\n", "utf-8");
|
|
2922
3040
|
if (mcpBlock) fs.writeFileSync(path.join(nodeRunDir, "mcp.md"), String(mcpBlock).trimEnd() + "\n", "utf-8");
|
|
@@ -2927,9 +3045,59 @@ function workspaceCreateNodeRunPackage(runTmpRoot, nodeId, { scopedRoot, task =
|
|
|
2927
3045
|
...manifest,
|
|
2928
3046
|
workspaceOutputsDir,
|
|
2929
3047
|
outputsRel: "outputs",
|
|
3048
|
+
inputValues: runtimeInputValues,
|
|
3049
|
+
inputMounts: materializedInputs.mounts,
|
|
2930
3050
|
};
|
|
2931
3051
|
}
|
|
2932
3052
|
|
|
3053
|
+
function workspaceMaterializeNodeInputFiles(nodeRunDir, workspaceRoot, inputValues = {}) {
|
|
3054
|
+
const values = {};
|
|
3055
|
+
const mounts = {};
|
|
3056
|
+
for (const [name, value] of Object.entries(inputValues || {})) {
|
|
3057
|
+
const slotName = String(name || "").trim();
|
|
3058
|
+
if (!slotName) continue;
|
|
3059
|
+
const rel = workspaceInputFileRelPath(value);
|
|
3060
|
+
if (!rel) continue;
|
|
3061
|
+
const src = path.resolve(workspaceRoot, rel);
|
|
3062
|
+
const rootWithSep = workspaceRoot.endsWith(path.sep) ? workspaceRoot : `${workspaceRoot}${path.sep}`;
|
|
3063
|
+
if (src !== workspaceRoot && !src.startsWith(rootWithSep)) continue;
|
|
3064
|
+
if (!fs.existsSync(src) || !fs.statSync(src).isFile()) continue;
|
|
3065
|
+
const stat = fs.statSync(src);
|
|
3066
|
+
const mountedRel = path.join("inputs", workspaceSanitizeTmpSegment(slotName, "input"), path.basename(rel));
|
|
3067
|
+
const dest = path.resolve(nodeRunDir, mountedRel);
|
|
3068
|
+
const nodeRunWithSep = nodeRunDir.endsWith(path.sep) ? nodeRunDir : `${nodeRunDir}${path.sep}`;
|
|
3069
|
+
if (dest !== nodeRunDir && !dest.startsWith(nodeRunWithSep)) continue;
|
|
3070
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
3071
|
+
workspaceCopyInputFile(src, dest);
|
|
3072
|
+
const mounted = mountedRel.split(path.sep).join(path.posix.sep);
|
|
3073
|
+
values[slotName] = mounted;
|
|
3074
|
+
mounts[slotName] = {
|
|
3075
|
+
source: rel,
|
|
3076
|
+
mounted,
|
|
3077
|
+
bytes: stat.size,
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3080
|
+
return { values, mounts };
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
function workspaceCopyInputFile(src, dest) {
|
|
3084
|
+
try {
|
|
3085
|
+
fs.copyFileSync(src, dest, fs.constants.COPYFILE_FICLONE);
|
|
3086
|
+
} catch {
|
|
3087
|
+
fs.copyFileSync(src, dest);
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
function workspaceInputFileRelPath(value) {
|
|
3092
|
+
const text = String(value || "").trim().replace(/^["']|["']$/g, "");
|
|
3093
|
+
if (!text || text.length > 260) return "";
|
|
3094
|
+
if (/[\r\n<>]/.test(text)) return "";
|
|
3095
|
+
if (/^(?:https?:|data:|blob:|file:|javascript:|mailto:|tel:)/i.test(text)) return "";
|
|
3096
|
+
const clean = text.replace(/^\/+/, "");
|
|
3097
|
+
if (clean.includes("..") || clean.startsWith(".") || path.isAbsolute(clean)) return "";
|
|
3098
|
+
return clean;
|
|
3099
|
+
}
|
|
3100
|
+
|
|
2933
3101
|
function workspaceShouldKeepTmp(userCtx = {}) {
|
|
2934
3102
|
const env = { ...process.env, ...readMergedEnvObject(userCtx.userId) };
|
|
2935
3103
|
const value = String(env.AGENTFLOW_KEEP_TMP || env.AGENTFLOW_KEEP_WORKSPACE_TMP || "").trim().toLowerCase();
|
|
@@ -2953,7 +3121,7 @@ function workspaceCleanupTmpRoot(runTmpRoot, userCtx = {}, emit = () => {}) {
|
|
|
2953
3121
|
async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts = {}) {
|
|
2954
3122
|
const graph = normalizeWorkspaceGraphPayload(payload.graph || {});
|
|
2955
3123
|
const runNodeId = String(payload?.runNodeId || "").trim();
|
|
2956
|
-
const { order, pauseNodeIds } = workspaceRunPlan(graph, runNodeId);
|
|
3124
|
+
const { order, pauseNodeIds } = workspaceRunPlan(graph, runNodeId, scopedRoot);
|
|
2957
3125
|
const signal = opts.signal || null;
|
|
2958
3126
|
const throwIfAborted = () => {
|
|
2959
3127
|
if (signal?.aborted) {
|
|
@@ -3316,22 +3484,29 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
3316
3484
|
const inputValues = workspaceInputValues(graph, nodeId, outputs);
|
|
3317
3485
|
const relevantInputs = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
3318
3486
|
const upstreamText = workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputs.placeholders);
|
|
3319
|
-
const body = workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim();
|
|
3320
|
-
if (defId === "agent_subAgent" && !body && !String(upstreamText || "").trim()) {
|
|
3321
|
-
throw new Error(`Workspace node ${nodeId} has no task. Fill the node body or connect upstream text.`);
|
|
3322
|
-
}
|
|
3323
3487
|
const upstreamSkillBlocks = workspaceUpstreamSkillBlocks(graph, nodeId, outputs);
|
|
3324
3488
|
const promptSkillsBlock = mergeWorkspaceSkillBlocks(upstreamSkillBlocks);
|
|
3325
3489
|
const promptMcpBlock = workspaceUpstreamMcpBlocks(graph, nodeId, outputs);
|
|
3326
3490
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
3327
3491
|
scopedRoot,
|
|
3328
3492
|
cwd,
|
|
3329
|
-
task: body || upstreamText,
|
|
3493
|
+
task: workspaceResolveBodyPlaceholders(instance.body || "", inputValues).trim() || upstreamText,
|
|
3330
3494
|
inputValues: relevantInputs.values,
|
|
3331
3495
|
skillsBlock: promptSkillsBlock,
|
|
3332
3496
|
mcpBlock: promptMcpBlock,
|
|
3333
3497
|
});
|
|
3334
|
-
const
|
|
3498
|
+
const runtimeInputValues = { ...inputValues, ...(runPackage.inputValues || {}) };
|
|
3499
|
+
const body = workspaceResolveBodyPlaceholders(instance.body || "", runtimeInputValues).trim();
|
|
3500
|
+
const promptUpstreamText = workspacePromptUpstreamText(upstreamText, runPackage);
|
|
3501
|
+
if (defId === "agent_subAgent" && !body && !String(promptUpstreamText || "").trim()) {
|
|
3502
|
+
throw new Error(`Workspace node ${nodeId} has no task. Fill the node body or connect upstream text.`);
|
|
3503
|
+
}
|
|
3504
|
+
try {
|
|
3505
|
+
fs.writeFileSync(path.join(runPackage.nodeRunDir, "task.md"), String(body || promptUpstreamText || "").trimEnd() + "\n", "utf-8");
|
|
3506
|
+
} catch {
|
|
3507
|
+
// Best-effort debug artifact only.
|
|
3508
|
+
}
|
|
3509
|
+
const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage);
|
|
3335
3510
|
try {
|
|
3336
3511
|
fs.writeFileSync(path.join(runPackage.nodeRunDir, "prompt.md"), prompt.trimEnd() + "\n", "utf-8");
|
|
3337
3512
|
} catch {
|
|
@@ -3809,9 +3984,9 @@ export function startUiServer({
|
|
|
3809
3984
|
json(res, 400, { error: "Missing display share id" });
|
|
3810
3985
|
return;
|
|
3811
3986
|
}
|
|
3812
|
-
const share =
|
|
3987
|
+
const { share, expired } = getDisplayShareOrExpired(id);
|
|
3813
3988
|
if (!share) {
|
|
3814
|
-
json(res, 404, { error: "Display share not found" });
|
|
3989
|
+
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
3815
3990
|
return;
|
|
3816
3991
|
}
|
|
3817
3992
|
const payload = publicDisplayPayloadFromShare(root, share);
|
|
@@ -3833,9 +4008,9 @@ export function startUiServer({
|
|
|
3833
4008
|
json(res, 400, { error: "Missing display share id" });
|
|
3834
4009
|
return;
|
|
3835
4010
|
}
|
|
3836
|
-
const share =
|
|
4011
|
+
const { share, expired } = getDisplayShareOrExpired(id);
|
|
3837
4012
|
if (!share) {
|
|
3838
|
-
json(res, 404, { error: "Display share not found" });
|
|
4013
|
+
json(res, expired ? 410 : 404, { error: expired ? "Display share has expired" : "Display share not found" });
|
|
3839
4014
|
return;
|
|
3840
4015
|
}
|
|
3841
4016
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
@@ -4276,7 +4451,8 @@ export function startUiServer({
|
|
|
4276
4451
|
const shares = readDisplayShares();
|
|
4277
4452
|
let id = createDisplayShareId();
|
|
4278
4453
|
while (shares[id]) id = createDisplayShareId();
|
|
4279
|
-
const
|
|
4454
|
+
const nowDate = new Date();
|
|
4455
|
+
const now = nowDate.toISOString();
|
|
4280
4456
|
const share = {
|
|
4281
4457
|
id,
|
|
4282
4458
|
userId: authUser.userId,
|
|
@@ -4284,10 +4460,11 @@ export function startUiServer({
|
|
|
4284
4460
|
flowSource: scoped.flowSource || "user",
|
|
4285
4461
|
archived: scoped.archived === true,
|
|
4286
4462
|
title: String(payload.title || "").trim() || "AgentFlow Display",
|
|
4287
|
-
layout: ["canvas", "gallery", "slides", "document"].includes(String(payload.layout || "")) ? String(payload.layout) : "canvas",
|
|
4463
|
+
layout: ["canvas", "gallery", "slides", "document", "single"].includes(String(payload.layout || "")) ? String(payload.layout) : "canvas",
|
|
4288
4464
|
nodeIds,
|
|
4289
4465
|
createdAt: now,
|
|
4290
4466
|
updatedAt: now,
|
|
4467
|
+
expiresAt: displayShareExpiresAt(nowDate),
|
|
4291
4468
|
};
|
|
4292
4469
|
shares[id] = share;
|
|
4293
4470
|
writeDisplayShares(shares);
|
|
@@ -5363,6 +5540,10 @@ export function startUiServer({
|
|
|
5363
5540
|
json(res, 400, { error: "Missing skill slug or collection" });
|
|
5364
5541
|
return;
|
|
5365
5542
|
}
|
|
5543
|
+
if (payload?.collection && !authUser?.isAdmin) {
|
|
5544
|
+
json(res, 403, { error: "Admin required" });
|
|
5545
|
+
return;
|
|
5546
|
+
}
|
|
5366
5547
|
const beforeSkills = payload?.collection ? listComposerSkills(PACKAGE_ROOT, root) : [];
|
|
5367
5548
|
const result = await runSkillhub(args, { cwd: root, timeoutMs: 180_000, maxBuffer: 4 * 1024 * 1024 });
|
|
5368
5549
|
if (!result.ok) {
|
|
@@ -5392,6 +5573,10 @@ export function startUiServer({
|
|
|
5392
5573
|
json(res, 400, { error: "Missing skill slug or collection" });
|
|
5393
5574
|
return;
|
|
5394
5575
|
}
|
|
5576
|
+
if (payload?.collection && !authUser?.isAdmin) {
|
|
5577
|
+
json(res, 403, { error: "Admin required" });
|
|
5578
|
+
return;
|
|
5579
|
+
}
|
|
5395
5580
|
const result = await runSkillhub(args, { cwd: root, timeoutMs: 120_000, maxBuffer: 4 * 1024 * 1024 });
|
|
5396
5581
|
if (!result.ok) {
|
|
5397
5582
|
json(res, 500, { error: result.error, stdout: result.stdout });
|
|
@@ -6381,6 +6566,10 @@ finishedAt: "${new Date().toISOString()}"
|
|
|
6381
6566
|
}
|
|
6382
6567
|
|
|
6383
6568
|
if (req.method === "POST" && url.pathname === "/api/skill-collections") {
|
|
6569
|
+
if (!authUser?.isAdmin) {
|
|
6570
|
+
json(res, 403, { error: "Admin required" });
|
|
6571
|
+
return;
|
|
6572
|
+
}
|
|
6384
6573
|
let payload;
|
|
6385
6574
|
try {
|
|
6386
6575
|
payload = JSON.parse(await readBody(req));
|