@fieldwangai/agentflow 0.1.36 → 0.1.38
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/admin-builtin-pipelines.mjs +110 -0
- package/bin/lib/catalog-flows.mjs +35 -2
- package/bin/lib/locales/en.json +4 -0
- package/bin/lib/locales/zh.json +4 -0
- package/bin/lib/ui-server.mjs +171 -27
- package/bin/lib/workspace-tree.mjs +3 -0
- package/builtin/nodes/display_table.md +31 -0
- package/builtin/web-ui/dist/assets/index-BftRvss5.js +218 -0
- package/builtin/web-ui/dist/assets/{index-CPsrRISH.css → index-Dzo7k7P-.css} +1 -1
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-7-343AUn.js +0 -214
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import {
|
|
4
|
+
getAgentflowDataRoot,
|
|
5
|
+
getUserPipelinesRoot,
|
|
6
|
+
} from "./paths.mjs";
|
|
7
|
+
|
|
8
|
+
const CONFIG_FILENAME = "admin-builtin-pipelines.json";
|
|
9
|
+
|
|
10
|
+
function configPath() {
|
|
11
|
+
return path.join(getAgentflowDataRoot(), "admin", CONFIG_FILENAME);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeConfig(raw) {
|
|
15
|
+
const hiddenBuiltins = Array.isArray(raw?.hiddenBuiltins)
|
|
16
|
+
? raw.hiddenBuiltins.map((id) => String(id || "").trim()).filter(Boolean)
|
|
17
|
+
: [];
|
|
18
|
+
const promoted = Array.isArray(raw?.promoted)
|
|
19
|
+
? raw.promoted.map((item) => ({
|
|
20
|
+
id: String(item?.id || item?.flowId || "").trim(),
|
|
21
|
+
ownerUserId: String(item?.ownerUserId || "").trim(),
|
|
22
|
+
source: "user",
|
|
23
|
+
createdAt: String(item?.createdAt || ""),
|
|
24
|
+
createdBy: String(item?.createdBy || ""),
|
|
25
|
+
})).filter((item) => item.id && item.ownerUserId)
|
|
26
|
+
: [];
|
|
27
|
+
return {
|
|
28
|
+
hiddenBuiltins: Array.from(new Set(hiddenBuiltins)),
|
|
29
|
+
promoted: promoted.filter((item, index, list) => (
|
|
30
|
+
list.findIndex((other) => other.id === item.id) === index
|
|
31
|
+
)),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function readAdminBuiltinPipelineConfig() {
|
|
36
|
+
try {
|
|
37
|
+
const p = configPath();
|
|
38
|
+
if (!fs.existsSync(p)) return normalizeConfig({});
|
|
39
|
+
return normalizeConfig(JSON.parse(fs.readFileSync(p, "utf-8")));
|
|
40
|
+
} catch {
|
|
41
|
+
return normalizeConfig({});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function writeAdminBuiltinPipelineConfig(config) {
|
|
46
|
+
const next = normalizeConfig(config);
|
|
47
|
+
const p = configPath();
|
|
48
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
49
|
+
fs.writeFileSync(p, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function resolveAdminBuiltinPipelineDir(flowId) {
|
|
54
|
+
const id = String(flowId || "").trim();
|
|
55
|
+
if (!id) return "";
|
|
56
|
+
const config = readAdminBuiltinPipelineConfig();
|
|
57
|
+
const entry = config.promoted.find((item) => item.id === id);
|
|
58
|
+
if (!entry) return "";
|
|
59
|
+
const dir = path.join(getUserPipelinesRoot(entry.ownerUserId), entry.id);
|
|
60
|
+
return fs.existsSync(path.join(dir, "flow.yaml")) ? dir : "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function updateAdminBuiltinPipelineConfig(action, payload = {}, actor = {}) {
|
|
64
|
+
const config = readAdminBuiltinPipelineConfig();
|
|
65
|
+
const flowId = String(payload.flowId || "").trim();
|
|
66
|
+
if (!flowId) return { ok: false, error: "Missing flowId" };
|
|
67
|
+
if (action === "promote") {
|
|
68
|
+
const ownerUserId = String(payload.ownerUserId || actor.userId || "").trim();
|
|
69
|
+
if (!ownerUserId) return { ok: false, error: "Missing ownerUserId" };
|
|
70
|
+
const dir = path.join(getUserPipelinesRoot(ownerUserId), flowId);
|
|
71
|
+
if (!fs.existsSync(path.join(dir, "flow.yaml"))) return { ok: false, error: "Pipeline not found" };
|
|
72
|
+
const promoted = config.promoted.filter((item) => item.id !== flowId);
|
|
73
|
+
promoted.unshift({
|
|
74
|
+
id: flowId,
|
|
75
|
+
ownerUserId,
|
|
76
|
+
source: "user",
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
createdBy: String(actor.userId || ""),
|
|
79
|
+
});
|
|
80
|
+
return { ok: true, config: writeAdminBuiltinPipelineConfig({ ...config, promoted }) };
|
|
81
|
+
}
|
|
82
|
+
if (action === "unpromote") {
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
config: writeAdminBuiltinPipelineConfig({
|
|
86
|
+
...config,
|
|
87
|
+
promoted: config.promoted.filter((item) => item.id !== flowId),
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (action === "hide-builtin") {
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
config: writeAdminBuiltinPipelineConfig({
|
|
95
|
+
...config,
|
|
96
|
+
hiddenBuiltins: Array.from(new Set([...config.hiddenBuiltins, flowId])),
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (action === "show-builtin") {
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
config: writeAdminBuiltinPipelineConfig({
|
|
104
|
+
...config,
|
|
105
|
+
hiddenBuiltins: config.hiddenBuiltins.filter((id) => id !== flowId),
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return { ok: false, error: "Invalid action" };
|
|
110
|
+
}
|
|
@@ -15,6 +15,10 @@ import {
|
|
|
15
15
|
USER_AGENTFLOW_PIPELINES_LABEL,
|
|
16
16
|
getUserPipelinesRoot,
|
|
17
17
|
} from "./paths.mjs";
|
|
18
|
+
import {
|
|
19
|
+
readAdminBuiltinPipelineConfig,
|
|
20
|
+
resolveAdminBuiltinPipelineDir,
|
|
21
|
+
} from "./admin-builtin-pipelines.mjs";
|
|
18
22
|
import { Table } from "./table.mjs";
|
|
19
23
|
import { listMarketplaceNodes, parseMarketplaceDefinitionId, resolveMarketplaceNodePackage } from "./marketplace.mjs";
|
|
20
24
|
|
|
@@ -53,12 +57,27 @@ export function readPipelineListDescription(flowDir) {
|
|
|
53
57
|
export function listFlowsJson(workspaceRoot, opts = {}) {
|
|
54
58
|
const root = path.resolve(workspaceRoot);
|
|
55
59
|
const out = [];
|
|
60
|
+
const adminBuiltinConfig = readAdminBuiltinPipelineConfig();
|
|
61
|
+
const hiddenBuiltins = new Set(adminBuiltinConfig.hiddenBuiltins);
|
|
56
62
|
const fromBuiltin = collectPipelineNamesFromDir(PACKAGE_BUILTIN_PIPELINES_DIR);
|
|
57
63
|
for (const name of fromBuiltin) {
|
|
64
|
+
if (hiddenBuiltins.has(name)) continue;
|
|
58
65
|
const dir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, name);
|
|
59
66
|
const description = readPipelineListDescription(dir);
|
|
60
67
|
out.push({ id: name, path: dir, source: "builtin", ...(description ? { description } : {}) });
|
|
61
68
|
}
|
|
69
|
+
for (const item of adminBuiltinConfig.promoted) {
|
|
70
|
+
const dir = resolveAdminBuiltinPipelineDir(item.id);
|
|
71
|
+
if (!dir) continue;
|
|
72
|
+
const description = readPipelineListDescription(dir);
|
|
73
|
+
out.push({
|
|
74
|
+
id: item.id,
|
|
75
|
+
path: dir,
|
|
76
|
+
source: "admin",
|
|
77
|
+
ownerUserId: item.ownerUserId,
|
|
78
|
+
...(description ? { description } : {}),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
62
81
|
const userPipelinesRoot = getUserPipelinesRoot(opts.userId);
|
|
63
82
|
const fromUserData = collectPipelineNamesFromDir(userPipelinesRoot);
|
|
64
83
|
for (const name of fromUserData) {
|
|
@@ -108,7 +127,7 @@ export function listFlowsJson(workspaceRoot, opts = {}) {
|
|
|
108
127
|
out.push({ id: name, path: dir, source: "workspace", archived: true, ...(description ? { description } : {}) });
|
|
109
128
|
workspaceArchivedIds.add(name);
|
|
110
129
|
}
|
|
111
|
-
const sourceRank = (s) => (s === "builtin" ? 0 : s === "
|
|
130
|
+
const sourceRank = (s) => (s === "builtin" ? 0 : s === "admin" ? 1 : s === "user" ? 2 : 3);
|
|
112
131
|
const archRank = (a) => (a.archived ? 1 : 0);
|
|
113
132
|
out.sort(
|
|
114
133
|
(a, b) =>
|
|
@@ -302,6 +321,9 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
|
302
321
|
}
|
|
303
322
|
}
|
|
304
323
|
} catch (_) {}
|
|
324
|
+
} else if (flowSource === "admin") {
|
|
325
|
+
const flowDir = resolveAdminBuiltinPipelineDir(flowId);
|
|
326
|
+
if (flowDir) addFromDir(path.join(flowDir, "nodes"), "flow", flowId);
|
|
305
327
|
} else if (flowSource === "user") {
|
|
306
328
|
if (archived) {
|
|
307
329
|
addFromDir(path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes"), "flow", flowId);
|
|
@@ -384,6 +406,8 @@ export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
|
|
|
384
406
|
|
|
385
407
|
if (flowSource === "builtin") {
|
|
386
408
|
flowDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
|
|
409
|
+
} else if (flowSource === "admin") {
|
|
410
|
+
flowDir = resolveAdminBuiltinPipelineDir(flowId);
|
|
387
411
|
} else if (flowSource === "user") {
|
|
388
412
|
flowDir = path.join(userPipelinesRoot, flowId);
|
|
389
413
|
} else if (flowSource === "workspace") {
|
|
@@ -455,6 +479,9 @@ export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {})
|
|
|
455
479
|
|
|
456
480
|
if (flowSource === "builtin") {
|
|
457
481
|
yamlPath = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "flow.yaml");
|
|
482
|
+
} else if (flowSource === "admin") {
|
|
483
|
+
const flowDir = resolveAdminBuiltinPipelineDir(flowId);
|
|
484
|
+
yamlPath = flowDir ? path.join(flowDir, "flow.yaml") : "";
|
|
458
485
|
} else if (flowSource === "user") {
|
|
459
486
|
yamlPath = path.join(userPipelinesRoot, flowId, "flow.yaml");
|
|
460
487
|
if (!fs.existsSync(yamlPath)) {
|
|
@@ -523,6 +550,9 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
|
|
|
523
550
|
if (flowId && flowSource) {
|
|
524
551
|
if (flowSource === "builtin") {
|
|
525
552
|
pathsToTry.push(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
553
|
+
} else if (flowSource === "admin") {
|
|
554
|
+
const flowDir = resolveAdminBuiltinPipelineDir(flowId);
|
|
555
|
+
if (flowDir) pathsToTry.push(path.join(flowDir, "nodes", fileName));
|
|
526
556
|
} else if (flowSource === "user") {
|
|
527
557
|
if (archived) {
|
|
528
558
|
pathsToTry.push(path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
|
|
@@ -637,6 +667,9 @@ function resolveMarkdownNodeFile(workspaceRoot, nodeId, flowId, flowSource, opts
|
|
|
637
667
|
if (flowId && flowSource) {
|
|
638
668
|
if (flowSource === "builtin") {
|
|
639
669
|
pathsToTry.push(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
670
|
+
} else if (flowSource === "admin") {
|
|
671
|
+
const flowDir = resolveAdminBuiltinPipelineDir(flowId);
|
|
672
|
+
if (flowDir) pathsToTry.push(path.join(flowDir, "nodes", fileName));
|
|
640
673
|
} else if (flowSource === "user") {
|
|
641
674
|
if (archived) {
|
|
642
675
|
pathsToTry.push(path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
|
|
@@ -797,7 +830,7 @@ export function listPipelines(workspaceRoot) {
|
|
|
797
830
|
style: { head: [], border: ["grey"] },
|
|
798
831
|
});
|
|
799
832
|
for (const row of rows) {
|
|
800
|
-
const sourceLabel = row.source === "builtin" ? "builtin" : row.source === "workspace" ? "workspace" : "user";
|
|
833
|
+
const sourceLabel = row.source === "builtin" || row.source === "admin" ? "builtin" : row.source === "workspace" ? "workspace" : "user";
|
|
801
834
|
table.push([row.id, sourceLabel, `agentflow apply ${row.id}`]);
|
|
802
835
|
}
|
|
803
836
|
log.info("\n" + chalk.bold("Pipelines"));
|
package/bin/lib/locales/en.json
CHANGED
|
@@ -333,6 +333,10 @@
|
|
|
333
333
|
"displayName": "Chart Display",
|
|
334
334
|
"description": "Render ChartSpec/ECharts JSON on the Workspace canvas and pass the JSON downstream"
|
|
335
335
|
},
|
|
336
|
+
"display_table": {
|
|
337
|
+
"displayName": "Table Display",
|
|
338
|
+
"description": "Render JSON, Markdown, CSV, or TSV table data on the Workspace canvas and pass the text downstream"
|
|
339
|
+
},
|
|
336
340
|
"provide_str": {
|
|
337
341
|
"displayName": "Text",
|
|
338
342
|
"description": "Provide a text value directly, value will be passed to downstream as-is"
|
package/bin/lib/locales/zh.json
CHANGED
|
@@ -333,6 +333,10 @@
|
|
|
333
333
|
"displayName": "图表展示",
|
|
334
334
|
"description": "在 Workspace 画布中渲染 ChartSpec/ECharts JSON,并将 JSON 继续传给下游"
|
|
335
335
|
},
|
|
336
|
+
"display_table": {
|
|
337
|
+
"displayName": "表格展示",
|
|
338
|
+
"description": "在 Workspace 画布中渲染 JSON、Markdown、CSV 或 TSV 表格,并将文本继续传给下游"
|
|
339
|
+
},
|
|
336
340
|
"provide_str": {
|
|
337
341
|
"displayName": "文本",
|
|
338
342
|
"description": "直接提供一段文本,value 会原样供下游引用"
|
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import fs from "fs";
|
|
|
9
9
|
import http from "http";
|
|
10
10
|
import os from "os";
|
|
11
11
|
import path from "path";
|
|
12
|
+
import crypto from "crypto";
|
|
12
13
|
import { execFile, spawn } from "child_process";
|
|
13
14
|
import busboy from "busboy";
|
|
14
15
|
import { log } from "./log.mjs";
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
import { t } from "./i18n.mjs";
|
|
41
42
|
import {
|
|
42
43
|
PACKAGE_ROOT,
|
|
44
|
+
getAgentflowDataRoot,
|
|
43
45
|
getAgentflowUserConfigAbs,
|
|
44
46
|
getAgentflowUserDataRoot,
|
|
45
47
|
getModelListsAbs,
|
|
@@ -95,6 +97,10 @@ import {
|
|
|
95
97
|
readUserAllowlist,
|
|
96
98
|
} from "./auth.mjs";
|
|
97
99
|
import { readUserEnvObject, readUserEnvRows, writeUserEnvRows } from "./user-env.mjs";
|
|
100
|
+
import {
|
|
101
|
+
readAdminBuiltinPipelineConfig,
|
|
102
|
+
updateAdminBuiltinPipelineConfig,
|
|
103
|
+
} from "./admin-builtin-pipelines.mjs";
|
|
98
104
|
|
|
99
105
|
const MIME = {
|
|
100
106
|
".html": "text/html; charset=utf-8",
|
|
@@ -159,6 +165,48 @@ function json(res, status, obj) {
|
|
|
159
165
|
res.end(body);
|
|
160
166
|
}
|
|
161
167
|
|
|
168
|
+
function feedbackStorePath() {
|
|
169
|
+
return path.join(getAgentflowDataRoot(), "feedback", "feedback.json");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readFeedbackItems() {
|
|
173
|
+
try {
|
|
174
|
+
const p = feedbackStorePath();
|
|
175
|
+
if (!fs.existsSync(p)) return [];
|
|
176
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
177
|
+
return Array.isArray(data) ? data.filter((item) => item && typeof item === "object") : [];
|
|
178
|
+
} catch {
|
|
179
|
+
return [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function writeFeedbackItems(items) {
|
|
184
|
+
const p = feedbackStorePath();
|
|
185
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
186
|
+
fs.writeFileSync(p, JSON.stringify(Array.isArray(items) ? items : [], null, 2) + "\n", "utf-8");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function createFeedbackItem(payload, user) {
|
|
190
|
+
const title = String(payload?.title || "").trim().slice(0, 120);
|
|
191
|
+
const content = String(payload?.content || "").trim().slice(0, 5000);
|
|
192
|
+
const contact = String(payload?.contact || "").trim().slice(0, 160);
|
|
193
|
+
const pageUrl = String(payload?.pageUrl || "").trim().slice(0, 500);
|
|
194
|
+
if (!title) return { error: "Missing feedback title" };
|
|
195
|
+
if (!content) return { error: "Missing feedback content" };
|
|
196
|
+
return {
|
|
197
|
+
item: {
|
|
198
|
+
id: `fb_${Date.now().toString(36)}_${crypto.randomBytes(5).toString("hex")}`,
|
|
199
|
+
title,
|
|
200
|
+
content,
|
|
201
|
+
contact,
|
|
202
|
+
pageUrl,
|
|
203
|
+
userId: String(user?.userId || ""),
|
|
204
|
+
username: String(user?.username || user?.userId || ""),
|
|
205
|
+
createdAt: new Date().toISOString(),
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
162
210
|
function skillCollectionsAbs(userCtx = {}) {
|
|
163
211
|
return path.join(getAgentflowUserDataRoot(userCtx.userId), SKILL_COLLECTIONS_FILENAME);
|
|
164
212
|
}
|
|
@@ -959,13 +1007,38 @@ function normalizeSkillhubSearchPayload(raw) {
|
|
|
959
1007
|
|
|
960
1008
|
function normalizeSkillhubListPayload(raw) {
|
|
961
1009
|
const arr = Array.isArray(raw) ? raw : [];
|
|
962
|
-
return arr.map((x) =>
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1010
|
+
return arr.map((x) => {
|
|
1011
|
+
const pathValue = String(x?.path ?? "");
|
|
1012
|
+
const targetPath = String(x?.targetPath ?? x?.target ?? "");
|
|
1013
|
+
const metaPaths = [
|
|
1014
|
+
pathValue ? path.join(pathValue, "_meta.json") : "",
|
|
1015
|
+
targetPath ? path.join(targetPath, "_meta.json") : "",
|
|
1016
|
+
];
|
|
1017
|
+
try {
|
|
1018
|
+
if (pathValue) metaPaths.push(path.join(fs.realpathSync(pathValue), "_meta.json"));
|
|
1019
|
+
} catch {}
|
|
1020
|
+
let meta = {};
|
|
1021
|
+
for (const metaPath of metaPaths) {
|
|
1022
|
+
if (!metaPath || !fs.existsSync(metaPath)) continue;
|
|
1023
|
+
try {
|
|
1024
|
+
meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
1025
|
+
break;
|
|
1026
|
+
} catch {}
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
name: String(x?.name ?? meta?.slug ?? ""),
|
|
1030
|
+
displayName: String(meta?.displayName ?? ""),
|
|
1031
|
+
summary: String(meta?.summary ?? ""),
|
|
1032
|
+
version: String(meta?.version ?? ""),
|
|
1033
|
+
baseDir: String(x?.baseDir ?? ""),
|
|
1034
|
+
path: pathValue,
|
|
1035
|
+
targetPath,
|
|
1036
|
+
kind: String(x?.kind ?? ""),
|
|
1037
|
+
agent: String(x?.agent ?? ""),
|
|
1038
|
+
userName: String(meta?.userName ?? ""),
|
|
1039
|
+
generatedAt: String(meta?.generatedAt ?? ""),
|
|
1040
|
+
};
|
|
1041
|
+
}).filter((x) => x.name);
|
|
969
1042
|
}
|
|
970
1043
|
|
|
971
1044
|
function skillhubInstallArgs(payload, { uninstall = false } = {}) {
|
|
@@ -1207,16 +1280,17 @@ function buildWorkspaceGeneratePrompt(payload) {
|
|
|
1207
1280
|
].join("\n")
|
|
1208
1281
|
: [
|
|
1209
1282
|
"你是 AgentFlow Workspace Composer。",
|
|
1210
|
-
"
|
|
1211
|
-
"
|
|
1212
|
-
"
|
|
1283
|
+
"默认以用户当前选择的 workspace 节点作为上下文范围;选中节点不是让你重建整张画布的授权。",
|
|
1284
|
+
"默认不要修改 workspace.graph.json,不要新增/删除/重连画布节点;只有当用户明确要求“更新画布、加节点、改连线、展示成节点、生成流程”时,才编辑 workspace.graph.json。",
|
|
1285
|
+
"如果用户请求生成或恢复文档/文件,可以直接在 workspace 文件系统中完成,最终只输出简短结果:改了什么、路径在哪里、是否需要下一步。",
|
|
1286
|
+
"不要在最终回答中列出过程性步骤,例如“先查看结构”“继续检索”“正在生成”;这些属于执行过程,不属于最终结果。",
|
|
1213
1287
|
].join("\n");
|
|
1214
1288
|
return [
|
|
1215
1289
|
"你正在 AgentFlow 的 Workspace 工作画布中执行任务。",
|
|
1216
1290
|
"Workspace 是当前 pipeline 的临时工作区,用于分析、试验、生成中间文件和展示结果。",
|
|
1217
1291
|
"Workspace 与 Pipeline 各自有独立的 Skill collection;此处只使用当前 Workspace Composer 选择的 collections / skills 作为本次行为规则与编辑依据。",
|
|
1218
1292
|
"当 Skills 提到修改 flow.yaml / instances / edges / ui 时,在 Workspace 视图下应映射为修改当前工作区的 workspace.graph.json,除非用户显式勾选并要求修改正式 flow.yaml。",
|
|
1219
|
-
"workspace.graph.json 使用 JSON:{ version, instances, edges, ui: { nodePositions, nodeSizes } }。instances 的结构与 flow.yaml instances 一致;edges 使用 source/target/sourceHandle/targetHandle;ui.nodePositions
|
|
1293
|
+
"workspace.graph.json 使用 JSON:{ version, instances, edges, ui: { nodePositions, nodeSizes } }。instances 的结构与 flow.yaml instances 一致;edges 使用 source/target/sourceHandle/targetHandle;ui.nodePositions 记录节点坐标,ui.nodeSizes 记录用户调整过的节点宽高。",
|
|
1220
1294
|
allowFlowYaml
|
|
1221
1295
|
? "用户已允许你考虑正式 flow.yaml;如需修改仍必须明确说明影响。"
|
|
1222
1296
|
: "默认不要修改正式 flow.yaml;优先在 workspace 文件、workspace.graph.json 或回复内容中完成任务。",
|
|
@@ -1328,6 +1402,7 @@ function workspaceDisplayKind(definitionId) {
|
|
|
1328
1402
|
if (id === "display_html") return "html";
|
|
1329
1403
|
if (id === "display_image") return "image";
|
|
1330
1404
|
if (id === "display_chart") return "chart";
|
|
1405
|
+
if (id === "display_table") return "table";
|
|
1331
1406
|
return "";
|
|
1332
1407
|
}
|
|
1333
1408
|
|
|
@@ -1394,6 +1469,9 @@ function workspaceDownstreamDisplayRequirements(graph, nodeId) {
|
|
|
1394
1469
|
if (kinds.has("chart")) {
|
|
1395
1470
|
rules.push('- 下游连接了 Chart 展示节点:只输出 ChartSpec JSON 对象,不要 Markdown 代码围栏,不要解释文字。格式必须包含 `"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 函数。');
|
|
1396
1471
|
}
|
|
1472
|
+
if (kinds.has("table")) {
|
|
1473
|
+
rules.push('- 下游连接了表格展示节点:优先只输出表格 JSON,不要解释文字。推荐格式:`{"columns":["列名1","列名2"],"rows":[["值1","值2"]]}`;也可输出对象数组、Markdown 表格、CSV 或 TSV。不要输出 HTML。');
|
|
1474
|
+
}
|
|
1397
1475
|
return [
|
|
1398
1476
|
"## 下游输出要求",
|
|
1399
1477
|
"",
|
|
@@ -2198,7 +2276,11 @@ function parseFlowsImportForm(req) {
|
|
|
2198
2276
|
|
|
2199
2277
|
/** GET 读 flow / nodes / SSE 等 */
|
|
2200
2278
|
function isValidFlowSourceRead(s) {
|
|
2201
|
-
return s === "builtin" || s === "user" || s === "workspace";
|
|
2279
|
+
return s === "builtin" || s === "admin" || s === "user" || s === "workspace";
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
function isReadonlyBuiltinFlowSource(s) {
|
|
2283
|
+
return s === "builtin" || s === "admin";
|
|
2202
2284
|
}
|
|
2203
2285
|
|
|
2204
2286
|
/** POST 写 flow */
|
|
@@ -2250,9 +2332,9 @@ function composerCliWorkspaceForFlowDir(workspaceRoot, _flowDir) {
|
|
|
2250
2332
|
* @param {object} p
|
|
2251
2333
|
* @param {string} p.flowYamlAbs
|
|
2252
2334
|
* @param {string} p.flowId
|
|
2253
|
-
* @param {"builtin" | "user" | "workspace"} p.flowSource
|
|
2254
|
-
* @param {string} [p.workspaceWriteDirAbs]
|
|
2255
|
-
* @param {"user" | "workspace"} [p.editorSyncFlowSource] flow-editor-sync 使用的 flowSource
|
|
2335
|
+
* @param {"builtin" | "admin" | "user" | "workspace"} p.flowSource
|
|
2336
|
+
* @param {string} [p.workspaceWriteDirAbs] 内置来源的可写副本根目录(…/pipelines/<flowId>)
|
|
2337
|
+
* @param {"user" | "workspace"} [p.editorSyncFlowSource] flow-editor-sync 使用的 flowSource(内置来源时为 workspace)
|
|
2256
2338
|
* @param {string[]} p.instanceIds
|
|
2257
2339
|
* @param {string} p.userPrompt
|
|
2258
2340
|
* @param {number} p.uiPort 本地 Web UI 端口(用于 flow 保存后通知浏览器刷新)
|
|
@@ -2284,9 +2366,9 @@ function buildComposerPromptWithFlowContext(p) {
|
|
|
2284
2366
|
const idsLine =
|
|
2285
2367
|
p.instanceIds.length > 0 ? p.instanceIds.map(String).join(", ") : "(无,可能为全局修改或新增节点)";
|
|
2286
2368
|
const builtinExtra =
|
|
2287
|
-
p.flowSource
|
|
2369
|
+
isReadonlyBuiltinFlowSource(p.flowSource) && p.workspaceWriteDirAbs
|
|
2288
2370
|
? [
|
|
2289
|
-
`-
|
|
2371
|
+
`- 内置模板为只读;若保存修改请写入工作区副本目录:${p.workspaceWriteDirAbs}(flow.yaml 与同 id)`,
|
|
2290
2372
|
"- 保存后刷新 Web 画布时,flow-editor-sync 的 JSON 须使用 flowSource: workspace(与上方 curl 一致)。",
|
|
2291
2373
|
]
|
|
2292
2374
|
: [];
|
|
@@ -2433,6 +2515,63 @@ export function startUiServer({
|
|
|
2433
2515
|
return;
|
|
2434
2516
|
}
|
|
2435
2517
|
|
|
2518
|
+
if (url.pathname === "/api/feedback") {
|
|
2519
|
+
if (req.method === "POST") {
|
|
2520
|
+
let payload;
|
|
2521
|
+
try {
|
|
2522
|
+
payload = JSON.parse(await readBody(req));
|
|
2523
|
+
} catch {
|
|
2524
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
const created = createFeedbackItem(payload, authUser);
|
|
2528
|
+
if (created.error) {
|
|
2529
|
+
json(res, 400, { error: created.error });
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
const items = readFeedbackItems();
|
|
2533
|
+
items.unshift(created.item);
|
|
2534
|
+
writeFeedbackItems(items.slice(0, 1000));
|
|
2535
|
+
json(res, 200, { ok: true, feedback: created.item });
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
if (req.method === "GET") {
|
|
2539
|
+
if (!authUser?.isAdmin) {
|
|
2540
|
+
json(res, 403, { error: "Admin permission required" });
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
json(res, 200, { feedback: readFeedbackItems() });
|
|
2544
|
+
return;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
if (url.pathname === "/api/admin/builtin-flows") {
|
|
2549
|
+
if (!authUser?.isAdmin) {
|
|
2550
|
+
json(res, 403, { error: "Admin permission required" });
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (req.method === "GET") {
|
|
2554
|
+
json(res, 200, { config: readAdminBuiltinPipelineConfig() });
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if (req.method === "POST") {
|
|
2558
|
+
let payload;
|
|
2559
|
+
try {
|
|
2560
|
+
payload = JSON.parse(await readBody(req));
|
|
2561
|
+
} catch {
|
|
2562
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
const result = updateAdminBuiltinPipelineConfig(payload?.action, payload, authUser);
|
|
2566
|
+
if (!result.ok) {
|
|
2567
|
+
json(res, 400, { error: result.error || "Update failed" });
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
json(res, 200, { ok: true, config: result.config });
|
|
2571
|
+
return;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2436
2575
|
if (url.pathname === "/api/flows") {
|
|
2437
2576
|
if (req.method === "GET") {
|
|
2438
2577
|
try {
|
|
@@ -2704,7 +2843,7 @@ export function startUiServer({
|
|
|
2704
2843
|
flowId: scoped.flowId,
|
|
2705
2844
|
flowSource: scoped.flowSource,
|
|
2706
2845
|
archived: scoped.archived,
|
|
2707
|
-
writable: !(scoped.archived || scoped.flowSource
|
|
2846
|
+
writable: !(scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)),
|
|
2708
2847
|
});
|
|
2709
2848
|
} catch (e) {
|
|
2710
2849
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
@@ -2730,7 +2869,7 @@ export function startUiServer({
|
|
|
2730
2869
|
json(res, 400, { error: scoped.error });
|
|
2731
2870
|
return;
|
|
2732
2871
|
}
|
|
2733
|
-
if (scoped.archived || scoped.flowSource
|
|
2872
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
2734
2873
|
json(res, 400, { error: "Cannot write workspace graph for builtin or archived pipeline" });
|
|
2735
2874
|
return;
|
|
2736
2875
|
}
|
|
@@ -2762,7 +2901,7 @@ export function startUiServer({
|
|
|
2762
2901
|
json(res, 400, { error: scoped.error });
|
|
2763
2902
|
return;
|
|
2764
2903
|
}
|
|
2765
|
-
if (scoped.archived || scoped.flowSource
|
|
2904
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
2766
2905
|
json(res, 400, { error: "Cannot run workspace graph for builtin or archived pipeline" });
|
|
2767
2906
|
return;
|
|
2768
2907
|
}
|
|
@@ -2920,7 +3059,7 @@ export function startUiServer({
|
|
|
2920
3059
|
json(res, 400, { error: scoped.error });
|
|
2921
3060
|
return;
|
|
2922
3061
|
}
|
|
2923
|
-
if (scoped.archived || scoped.flowSource
|
|
3062
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
2924
3063
|
json(res, 400, { error: "Cannot write to builtin or archived pipeline workspace" });
|
|
2925
3064
|
return;
|
|
2926
3065
|
}
|
|
@@ -2956,7 +3095,7 @@ export function startUiServer({
|
|
|
2956
3095
|
json(res, 400, { error: scoped.error });
|
|
2957
3096
|
return;
|
|
2958
3097
|
}
|
|
2959
|
-
if (scoped.archived || scoped.flowSource
|
|
3098
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
2960
3099
|
json(res, 400, { error: "Cannot write to builtin or archived pipeline workspace" });
|
|
2961
3100
|
return;
|
|
2962
3101
|
}
|
|
@@ -2991,7 +3130,7 @@ export function startUiServer({
|
|
|
2991
3130
|
json(res, 400, { error: scoped.error });
|
|
2992
3131
|
return;
|
|
2993
3132
|
}
|
|
2994
|
-
if (scoped.archived || scoped.flowSource
|
|
3133
|
+
if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
|
|
2995
3134
|
json(res, 400, { error: "Cannot write to builtin or archived pipeline workspace" });
|
|
2996
3135
|
return;
|
|
2997
3136
|
}
|
|
@@ -3050,7 +3189,8 @@ export function startUiServer({
|
|
|
3050
3189
|
const promptText = buildWorkspaceGeneratePrompt({ ...payload, skillsBlock });
|
|
3051
3190
|
const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
|
|
3052
3191
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3053
|
-
let
|
|
3192
|
+
let attemptResult = "";
|
|
3193
|
+
const assistantSegments = [];
|
|
3054
3194
|
try {
|
|
3055
3195
|
if (attempt > 1) {
|
|
3056
3196
|
events.push({
|
|
@@ -3068,12 +3208,16 @@ export function startUiServer({
|
|
|
3068
3208
|
onStreamEvent: (ev) => {
|
|
3069
3209
|
events.push(ev);
|
|
3070
3210
|
if (ev?.type === "natural" && ev.kind === "assistant" && typeof ev.text === "string") {
|
|
3071
|
-
|
|
3211
|
+
const text = ev.text.trim();
|
|
3212
|
+
if (text) assistantSegments.push(text);
|
|
3213
|
+
} else if (ev?.type === "natural" && ev.kind === "result" && typeof ev.text === "string") {
|
|
3214
|
+
const text = ev.text.trim();
|
|
3215
|
+
if (text) attemptResult = text;
|
|
3072
3216
|
}
|
|
3073
3217
|
},
|
|
3074
3218
|
});
|
|
3075
3219
|
await handle.finished;
|
|
3076
|
-
content =
|
|
3220
|
+
content = attemptResult || assistantSegments.at(-1) || "";
|
|
3077
3221
|
break;
|
|
3078
3222
|
} catch (e) {
|
|
3079
3223
|
if (attempt < maxAttempts && isTransientAgentNetworkError(e)) {
|
|
@@ -4640,7 +4784,7 @@ finishedAt: "${new Date().toISOString()}"
|
|
|
4640
4784
|
let workspaceWriteDirAbs;
|
|
4641
4785
|
let editorSyncFlowSource = flowSource;
|
|
4642
4786
|
let flowDirForCli = path.dirname(flowYamlAbs);
|
|
4643
|
-
if (flowSource
|
|
4787
|
+
if (isReadonlyBuiltinFlowSource(flowSource)) {
|
|
4644
4788
|
const w = resolveFlowDirForWrite(root, flowId, "workspace", userCtx);
|
|
4645
4789
|
if (w.error || !w.flowDir) {
|
|
4646
4790
|
json(res, 400, { error: w.error || "Could not resolve workspace flow directory" });
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
PACKAGE_BUILTIN_PIPELINES_DIR,
|
|
12
12
|
} from "./paths.mjs";
|
|
13
13
|
import { listAllRunDirs } from "./workspace.mjs";
|
|
14
|
+
import { resolveAdminBuiltinPipelineDir } from "./admin-builtin-pipelines.mjs";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* 获取目录下的子目录列表
|
|
@@ -239,6 +240,8 @@ export function getPipelineFiles(workspaceRoot, flowId, flowSource, archived = f
|
|
|
239
240
|
} else {
|
|
240
241
|
if (flowSource === "builtin") {
|
|
241
242
|
pipelineDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
|
|
243
|
+
} else if (flowSource === "admin") {
|
|
244
|
+
pipelineDir = resolveAdminBuiltinPipelineDir(flowId);
|
|
242
245
|
} else if (flowSource === "user") {
|
|
243
246
|
pipelineDir = path.join(userPipelinesRoot, flowId);
|
|
244
247
|
if (!fs.existsSync(pipelineDir)) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Built-in node: Table Display
|
|
3
|
+
description: Display table data in workspace canvas; accepts JSON, Markdown table, CSV, or TSV and passes the text downstream
|
|
4
|
+
displayName: Table Display
|
|
5
|
+
input:
|
|
6
|
+
- type: node
|
|
7
|
+
name: prev
|
|
8
|
+
default: ""
|
|
9
|
+
- type: text
|
|
10
|
+
name: content
|
|
11
|
+
default: ""
|
|
12
|
+
required: true
|
|
13
|
+
showOnNode: true
|
|
14
|
+
- type: file
|
|
15
|
+
name: filePath
|
|
16
|
+
default: ""
|
|
17
|
+
showOnNode: false
|
|
18
|
+
- type: text
|
|
19
|
+
name: workspaceContext
|
|
20
|
+
default: ""
|
|
21
|
+
showOnNode: false
|
|
22
|
+
output:
|
|
23
|
+
- type: text
|
|
24
|
+
name: content
|
|
25
|
+
default: ""
|
|
26
|
+
showOnNode: false
|
|
27
|
+
- type: node
|
|
28
|
+
name: next
|
|
29
|
+
default: ""
|
|
30
|
+
---
|
|
31
|
+
${content}
|