@fieldwangai/agentflow 0.1.85 → 0.1.87
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/README.md +21 -5
- package/README.zh-CN.md +21 -5
- package/bin/agentflow.mjs +1 -1
- package/bin/lib/agent-runners.mjs +589 -0
- package/bin/lib/auth.mjs +11 -1
- package/bin/lib/catalog-flows.mjs +2 -0
- package/bin/lib/composer-agent.mjs +163 -1
- package/bin/lib/composer-model-router.mjs +28 -3
- package/bin/lib/composer-planner.mjs +3 -1
- package/bin/lib/help.mjs +16 -14
- package/bin/lib/locales/en.json +5 -1
- package/bin/lib/locales/zh.json +5 -1
- package/bin/lib/main.mjs +5 -0
- package/bin/lib/mcp-server.mjs +328 -0
- package/bin/lib/model-config.mjs +32 -3
- package/bin/lib/model-lists.mjs +68 -3
- package/bin/lib/node-execute.mjs +22 -1
- package/bin/lib/ui-server.mjs +579 -17
- package/bin/pipeline/validate-flow.mjs +19 -11
- package/bin/pipeline/validate-for-ui.mjs +19 -11
- package/builtin/nodes/display_react_app.md +52 -0
- package/builtin/web-ui/dist/assets/index-BqOb-W89.css +1 -0
- package/builtin/web-ui/dist/assets/index-Cla8u37K.js +340 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +3 -2
- package/builtin/web-ui/dist/assets/index-Lb9rIMqs.js +0 -250
- package/builtin/web-ui/dist/assets/index-aqphFZlG.css +0 -1
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -102,6 +102,7 @@ import {
|
|
|
102
102
|
buildClearSessionCookie,
|
|
103
103
|
buildSessionCookie,
|
|
104
104
|
getAuthUserFromRequest,
|
|
105
|
+
getSessionTokenFromRequest,
|
|
105
106
|
isAuthUserAllowed,
|
|
106
107
|
loginOrCreateUser,
|
|
107
108
|
logoutRequest,
|
|
@@ -619,6 +620,125 @@ function normalizeMcpServerConfig(value) {
|
|
|
619
620
|
return next;
|
|
620
621
|
}
|
|
621
622
|
|
|
623
|
+
function mcpHeadersInfo(headers = {}) {
|
|
624
|
+
const entries = Object.entries(headers && typeof headers === "object" && !Array.isArray(headers) ? headers : {})
|
|
625
|
+
.filter(([key]) => String(key || "").trim());
|
|
626
|
+
const unsupported = [];
|
|
627
|
+
let bearer = false;
|
|
628
|
+
for (const [key, value] of entries) {
|
|
629
|
+
const name = String(key || "").trim();
|
|
630
|
+
if (name.toLowerCase() !== "authorization") {
|
|
631
|
+
unsupported.push(name);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (/^Bearer\s+.+/i.test(String(value ?? "").trim())) bearer = true;
|
|
635
|
+
else unsupported.push(name);
|
|
636
|
+
}
|
|
637
|
+
return { count: entries.length, bearer, unsupported };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
function codexMcpCompatibility(server) {
|
|
641
|
+
const raw = server?.raw && typeof server.raw === "object" && !Array.isArray(server.raw) ? server.raw : {};
|
|
642
|
+
const reasons = [];
|
|
643
|
+
const supported = [];
|
|
644
|
+
const unsupported = [];
|
|
645
|
+
const type = raw.url || server?.url ? "url" : raw.command || server?.command ? "command" : "";
|
|
646
|
+
if (raw.disabled === true) {
|
|
647
|
+
return {
|
|
648
|
+
status: "unsupported",
|
|
649
|
+
label: "Codex disabled",
|
|
650
|
+
reasons: ["该 MCP 已 disabled,Codex 不会启用。"],
|
|
651
|
+
supported,
|
|
652
|
+
unsupported: ["disabled"],
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
if (!type) {
|
|
656
|
+
return {
|
|
657
|
+
status: "unsupported",
|
|
658
|
+
label: "Codex unsupported",
|
|
659
|
+
reasons: ["缺少 url 或 command。"],
|
|
660
|
+
supported,
|
|
661
|
+
unsupported: ["transport"],
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (type === "command") {
|
|
666
|
+
supported.push("command", "args");
|
|
667
|
+
const envKeys = Object.keys(raw.env && typeof raw.env === "object" && !Array.isArray(raw.env) ? raw.env : {});
|
|
668
|
+
if (envKeys.length) supported.push("env");
|
|
669
|
+
if (raw.cwd) supported.push("cwd");
|
|
670
|
+
const headers = raw.headers && typeof raw.headers === "object" && !Array.isArray(raw.headers) ? raw.headers : {};
|
|
671
|
+
if (Object.keys(headers).length) {
|
|
672
|
+
unsupported.push("headers");
|
|
673
|
+
reasons.push("Codex stdio MCP 不支持 headers。");
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (type === "url") {
|
|
678
|
+
supported.push("url");
|
|
679
|
+
const headers = mcpHeadersInfo(raw.headers);
|
|
680
|
+
if (headers.bearer) supported.push("Authorization Bearer");
|
|
681
|
+
if (headers.unsupported.length) {
|
|
682
|
+
unsupported.push(...headers.unsupported.map((name) => `header:${name}`));
|
|
683
|
+
reasons.push(`Codex URL MCP 仅能等价支持 Authorization: Bearer;不支持自定义 header:${headers.unsupported.join(", ")}。`);
|
|
684
|
+
}
|
|
685
|
+
const envKeys = Object.keys(raw.env && typeof raw.env === "object" && !Array.isArray(raw.env) ? raw.env : {});
|
|
686
|
+
if (envKeys.length) {
|
|
687
|
+
unsupported.push("env");
|
|
688
|
+
reasons.push("Codex URL MCP 不支持 env;如需鉴权请使用 Authorization: Bearer 或 bearer_token_env_var。");
|
|
689
|
+
}
|
|
690
|
+
if (raw.bearer_token_env_var) supported.push("bearer_token_env_var");
|
|
691
|
+
if (raw.oauth_client_id) supported.push("oauth_client_id");
|
|
692
|
+
if (raw.oauth_resource) supported.push("oauth_resource");
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const known = new Set([
|
|
696
|
+
"url",
|
|
697
|
+
"command",
|
|
698
|
+
"args",
|
|
699
|
+
"env",
|
|
700
|
+
"headers",
|
|
701
|
+
"description",
|
|
702
|
+
"cwd",
|
|
703
|
+
"disabled",
|
|
704
|
+
"bearer_token_env_var",
|
|
705
|
+
"oauth_client_id",
|
|
706
|
+
"oauth_resource",
|
|
707
|
+
"__agentflowPrivateKeys",
|
|
708
|
+
]);
|
|
709
|
+
const unknownKeys = Object.keys(raw).filter((key) => !known.has(key));
|
|
710
|
+
if (unknownKeys.length) {
|
|
711
|
+
unsupported.push(...unknownKeys.map((key) => `field:${key}`));
|
|
712
|
+
reasons.push(`存在 Codex 未确认支持的额外字段:${unknownKeys.join(", ")}。`);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const status = unsupported.length ? "partial" : "ok";
|
|
716
|
+
return {
|
|
717
|
+
status,
|
|
718
|
+
label: status === "ok" ? "Codex OK" : "Codex partial",
|
|
719
|
+
reasons,
|
|
720
|
+
supported,
|
|
721
|
+
unsupported,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function cursorMcpCompatibility(server) {
|
|
726
|
+
if (server?.raw?.disabled === true) {
|
|
727
|
+
return { status: "unsupported", label: "Cursor disabled", reasons: ["该 MCP 已 disabled。"] };
|
|
728
|
+
}
|
|
729
|
+
return { status: "ok", label: "Cursor OK", reasons: [] };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function withMcpBackendCompatibility(server) {
|
|
733
|
+
return {
|
|
734
|
+
...server,
|
|
735
|
+
backends: {
|
|
736
|
+
cursor: cursorMcpCompatibility(server),
|
|
737
|
+
codex: codexMcpCompatibility(server),
|
|
738
|
+
},
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
|
|
622
742
|
function readCursorMcpServers(userCtx = {}) {
|
|
623
743
|
const config = readCursorMcpConfig();
|
|
624
744
|
const privateConfig = readUserMcpPrivate(userCtx);
|
|
@@ -651,7 +771,7 @@ function readCursorMcpServers(userCtx = {}) {
|
|
|
651
771
|
privateEnvKeys,
|
|
652
772
|
privateHeaderKeys,
|
|
653
773
|
};
|
|
654
|
-
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
774
|
+
}).map(withMcpBackendCompatibility).sort((a, b) => a.name.localeCompare(b.name));
|
|
655
775
|
return { path: cursorMcpConfigPath(), servers };
|
|
656
776
|
}
|
|
657
777
|
|
|
@@ -958,9 +1078,11 @@ function readModelListsFromDisk(workspaceRoot) {
|
|
|
958
1078
|
cursor: [],
|
|
959
1079
|
opencode: [],
|
|
960
1080
|
claudeCode: [],
|
|
1081
|
+
codex: [],
|
|
961
1082
|
cursorFetchedAt: null,
|
|
962
1083
|
opencodeFetchedAt: null,
|
|
963
1084
|
claudeCodeFetchedAt: null,
|
|
1085
|
+
codexFetchedAt: null,
|
|
964
1086
|
};
|
|
965
1087
|
try {
|
|
966
1088
|
if (!fs.existsSync(p)) return empty;
|
|
@@ -969,9 +1091,11 @@ function readModelListsFromDisk(workspaceRoot) {
|
|
|
969
1091
|
cursor: Array.isArray(data.cursor) ? data.cursor.map(String) : [],
|
|
970
1092
|
opencode: Array.isArray(data.opencode) ? data.opencode.map(String) : [],
|
|
971
1093
|
claudeCode: Array.isArray(data.claudeCode) ? data.claudeCode.map(String) : [],
|
|
1094
|
+
codex: Array.isArray(data.codex) ? data.codex.map(String) : [],
|
|
972
1095
|
cursorFetchedAt: data.cursorFetchedAt ?? null,
|
|
973
1096
|
opencodeFetchedAt: data.opencodeFetchedAt ?? null,
|
|
974
1097
|
claudeCodeFetchedAt: data.claudeCodeFetchedAt ?? null,
|
|
1098
|
+
codexFetchedAt: data.codexFetchedAt ?? null,
|
|
975
1099
|
};
|
|
976
1100
|
} catch {
|
|
977
1101
|
return empty;
|
|
@@ -1490,6 +1614,194 @@ function readWorkspaceGraph(workspaceRoot) {
|
|
|
1490
1614
|
const DISPLAY_SHARE_FILENAME = "display-shares.json";
|
|
1491
1615
|
const DISPLAY_SHARE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
1492
1616
|
const DISPLAY_SHARE_ALLOWED_EXPIRY_DAYS = new Set([1, 7, 30, 90, 365]);
|
|
1617
|
+
const NODE_STUDIO_DRAFTS_DIRNAME = "node-studio/drafts";
|
|
1618
|
+
const USER_WORKSPACES_FILENAME = "workspaces.json";
|
|
1619
|
+
|
|
1620
|
+
function userWorkspacesPath(userCtx = {}) {
|
|
1621
|
+
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
function normalizeWorkspaceEntry(entry = {}, index = 0, userCtx = {}) {
|
|
1625
|
+
const label = String(entry?.label || entry?.name || "").trim();
|
|
1626
|
+
const kindRaw = String(entry?.kind || entry?.source || "").trim().toLowerCase();
|
|
1627
|
+
const repoUrl = String(entry?.repoUrl || entry?.gitUrl || entry?.url || "").trim();
|
|
1628
|
+
const kind = kindRaw === "git" || repoUrl ? "git" : "local";
|
|
1629
|
+
const branch = String(entry?.branch || "master").trim() || "master";
|
|
1630
|
+
const mountPathRaw = String(entry?.mountPath || "").trim();
|
|
1631
|
+
const rawPath = String(entry?.path || entry?.cwd || "").trim();
|
|
1632
|
+
if (!rawPath && kind !== "git") return null;
|
|
1633
|
+
const idRaw = String(entry?.id || label || mountPathRaw || repoUrl || rawPath || `workspace_${index + 1}`).trim().toLowerCase();
|
|
1634
|
+
const id = idRaw.replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64) || `workspace_${index + 1}`;
|
|
1635
|
+
const mountPath = (mountPathRaw || id).replace(/^\/+/, "").replace(/\.\.(\/|\\|$)/g, "").trim() || id;
|
|
1636
|
+
const defaultGitPath = path.join(getAgentflowUserDataRoot(userCtx.userId || ""), "workspaces", "repos", id);
|
|
1637
|
+
const absPath = path.resolve((rawPath || defaultGitPath).replace(/^~(?=$|\/|\\)/, os.homedir()));
|
|
1638
|
+
const exists = fs.existsSync(absPath) && fs.statSync(absPath).isDirectory();
|
|
1639
|
+
return {
|
|
1640
|
+
id,
|
|
1641
|
+
label: label || path.basename(absPath) || id,
|
|
1642
|
+
kind,
|
|
1643
|
+
path: absPath,
|
|
1644
|
+
repoUrl,
|
|
1645
|
+
branch,
|
|
1646
|
+
mountPath,
|
|
1647
|
+
credentialRef: String(entry?.credentialRef || "").trim(),
|
|
1648
|
+
type: String(entry?.type || (kind === "git" ? "code" : "local")).trim() || (kind === "git" ? "code" : "local"),
|
|
1649
|
+
description: String(entry?.description || "").trim(),
|
|
1650
|
+
visibility: String(entry?.visibility || "personal").trim() || "personal",
|
|
1651
|
+
enabled: entry?.enabled !== false,
|
|
1652
|
+
exists,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
function readUserWorkspaces(userCtx = {}) {
|
|
1657
|
+
const p = userWorkspacesPath(userCtx);
|
|
1658
|
+
if (!fs.existsSync(p)) return [];
|
|
1659
|
+
try {
|
|
1660
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
1661
|
+
const list = Array.isArray(data?.workspaces) ? data.workspaces : Array.isArray(data) ? data : [];
|
|
1662
|
+
return list.map((entry, index) => normalizeWorkspaceEntry(entry, index, userCtx)).filter(Boolean);
|
|
1663
|
+
} catch {
|
|
1664
|
+
return [];
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function writeUserWorkspaces(userCtx = {}, entries = []) {
|
|
1669
|
+
const seen = new Set();
|
|
1670
|
+
const workspaces = (Array.isArray(entries) ? entries : [])
|
|
1671
|
+
.map((entry, index) => normalizeWorkspaceEntry(entry, index, userCtx))
|
|
1672
|
+
.filter(Boolean)
|
|
1673
|
+
.filter((entry) => {
|
|
1674
|
+
const key = entry.id || entry.path;
|
|
1675
|
+
if (seen.has(key)) return false;
|
|
1676
|
+
seen.add(key);
|
|
1677
|
+
return true;
|
|
1678
|
+
});
|
|
1679
|
+
const p = userWorkspacesPath(userCtx);
|
|
1680
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
1681
|
+
fs.writeFileSync(p, JSON.stringify({ version: 1, workspaces }, null, 2) + "\n", "utf-8");
|
|
1682
|
+
return workspaces;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
function listConfiguredWorkspaces(root, scopedRoot, userCtx = {}) {
|
|
1686
|
+
const currentRoot = path.resolve(scopedRoot || root);
|
|
1687
|
+
const homeRoot = path.resolve(os.homedir());
|
|
1688
|
+
const builtins = [
|
|
1689
|
+
{ id: "current", label: "当前流程工作区", kind: "local", path: currentRoot, builtin: true, exists: fs.existsSync(currentRoot) && fs.statSync(currentRoot).isDirectory(), type: "flow", enabled: true },
|
|
1690
|
+
{ id: "home", label: "用户 Home", kind: "local", path: homeRoot, builtin: true, exists: fs.existsSync(homeRoot) && fs.statSync(homeRoot).isDirectory(), type: "local", enabled: true },
|
|
1691
|
+
];
|
|
1692
|
+
const custom = readUserWorkspaces(userCtx).filter((entry) => entry.enabled !== false).map((entry) => ({ ...entry, builtin: false }));
|
|
1693
|
+
const seen = new Set();
|
|
1694
|
+
return [...builtins, ...custom].filter((entry) => {
|
|
1695
|
+
const key = path.resolve(entry.path);
|
|
1696
|
+
if (seen.has(key)) return false;
|
|
1697
|
+
seen.add(key);
|
|
1698
|
+
return true;
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
function nodeStudioDraftsRoot(userCtx = {}) {
|
|
1703
|
+
return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), NODE_STUDIO_DRAFTS_DIRNAME);
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function normalizeNodeStudioDraftId(value) {
|
|
1707
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
1708
|
+
const safe = raw.replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 64);
|
|
1709
|
+
return safe || `draft_${Date.now().toString(36)}_${crypto.randomBytes(3).toString("hex")}`;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
function nodeStudioDraftPath(userCtx = {}, draftId = "") {
|
|
1713
|
+
return path.join(nodeStudioDraftsRoot(userCtx), normalizeNodeStudioDraftId(draftId), "draft.json");
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
function emptyNodeStudioDraft(userCtx = {}, draftId = "") {
|
|
1717
|
+
const now = new Date().toISOString();
|
|
1718
|
+
const id = normalizeNodeStudioDraftId(draftId || "untitled_node");
|
|
1719
|
+
return {
|
|
1720
|
+
id,
|
|
1721
|
+
title: "Untitled Node",
|
|
1722
|
+
definitionId: "",
|
|
1723
|
+
createdAt: now,
|
|
1724
|
+
updatedAt: now,
|
|
1725
|
+
ownerUserId: String(userCtx.userId || ""),
|
|
1726
|
+
agentMessages: [],
|
|
1727
|
+
promptDraft: "",
|
|
1728
|
+
manifest: {
|
|
1729
|
+
id,
|
|
1730
|
+
version: "1.0.0",
|
|
1731
|
+
name: "Untitled Node",
|
|
1732
|
+
description: "",
|
|
1733
|
+
baseDefinitionId: "agent_subAgent",
|
|
1734
|
+
runtime: { type: "agent_subAgent" },
|
|
1735
|
+
inputs: [],
|
|
1736
|
+
outputs: [],
|
|
1737
|
+
configSchema: { fields: [] },
|
|
1738
|
+
ui: { card: { icon: "extension", variant: "default", actions: [] } },
|
|
1739
|
+
},
|
|
1740
|
+
config: {},
|
|
1741
|
+
test: { inputs: {}, log: [], status: "not run" },
|
|
1742
|
+
files: {},
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
function isLegacyNodeStudioDemoDraft(draft) {
|
|
1747
|
+
return (
|
|
1748
|
+
String(draft?.id || "") === "daily_report_demo" &&
|
|
1749
|
+
String(draft?.definitionId || "") === "marketplace:daily_report@1.0.0"
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function readNodeStudioDraft(userCtx = {}, draftId = "") {
|
|
1754
|
+
const id = normalizeNodeStudioDraftId(draftId || "");
|
|
1755
|
+
const filePath = nodeStudioDraftPath(userCtx, id);
|
|
1756
|
+
if (!fs.existsSync(filePath)) return null;
|
|
1757
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1758
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
function writeNodeStudioDraft(userCtx = {}, draft = {}) {
|
|
1762
|
+
const id = normalizeNodeStudioDraftId(draft.id || "untitled_node");
|
|
1763
|
+
const filePath = nodeStudioDraftPath(userCtx, id);
|
|
1764
|
+
const previous = fs.existsSync(filePath)
|
|
1765
|
+
? JSON.parse(fs.readFileSync(filePath, "utf-8"))
|
|
1766
|
+
: {};
|
|
1767
|
+
const now = new Date().toISOString();
|
|
1768
|
+
const next = {
|
|
1769
|
+
...previous,
|
|
1770
|
+
...draft,
|
|
1771
|
+
id,
|
|
1772
|
+
createdAt: previous.createdAt || draft.createdAt || now,
|
|
1773
|
+
updatedAt: now,
|
|
1774
|
+
ownerUserId: String(userCtx.userId || draft.ownerUserId || ""),
|
|
1775
|
+
};
|
|
1776
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1777
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2) + "\n", "utf-8");
|
|
1778
|
+
return next;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function listNodeStudioDrafts(userCtx = {}) {
|
|
1782
|
+
const rootDir = nodeStudioDraftsRoot(userCtx);
|
|
1783
|
+
if (!fs.existsSync(rootDir)) return [];
|
|
1784
|
+
const rows = [];
|
|
1785
|
+
for (const entry of fs.readdirSync(rootDir, { withFileTypes: true })) {
|
|
1786
|
+
if (!entry.isDirectory()) continue;
|
|
1787
|
+
const filePath = path.join(rootDir, entry.name, "draft.json");
|
|
1788
|
+
if (!fs.existsSync(filePath)) continue;
|
|
1789
|
+
try {
|
|
1790
|
+
const draft = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1791
|
+
if (isLegacyNodeStudioDemoDraft(draft)) continue;
|
|
1792
|
+
rows.push({
|
|
1793
|
+
id: String(draft.id || entry.name),
|
|
1794
|
+
title: String(draft.title || draft.manifest?.name || entry.name),
|
|
1795
|
+
definitionId: String(draft.definitionId || `marketplace:${draft.manifest?.id || entry.name}@${draft.manifest?.version || "1.0.0"}`),
|
|
1796
|
+
updatedAt: String(draft.updatedAt || ""),
|
|
1797
|
+
});
|
|
1798
|
+
} catch {
|
|
1799
|
+
/* ignore corrupt drafts */
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
rows.sort((a, b) => String(b.updatedAt || "").localeCompare(String(a.updatedAt || "")) || a.id.localeCompare(b.id));
|
|
1803
|
+
return rows;
|
|
1804
|
+
}
|
|
1493
1805
|
|
|
1494
1806
|
function displaySharesPath() {
|
|
1495
1807
|
return path.join(getAgentflowDataRoot(), DISPLAY_SHARE_FILENAME);
|
|
@@ -1988,13 +2300,27 @@ function normalizeDisplayShareNodeIds(ids, graph) {
|
|
|
1988
2300
|
const id = String(rawId || "").trim();
|
|
1989
2301
|
if (!id || seen.has(id)) continue;
|
|
1990
2302
|
const instance = instances[id];
|
|
1991
|
-
if (!
|
|
2303
|
+
if (!workspaceDisplayKindFromInstance(instance)) continue;
|
|
1992
2304
|
seen.add(id);
|
|
1993
2305
|
out.push(id);
|
|
1994
2306
|
}
|
|
1995
2307
|
return out;
|
|
1996
2308
|
}
|
|
1997
2309
|
|
|
2310
|
+
function workspaceDisplayContentFromInstance(instance, kind = "") {
|
|
2311
|
+
const slots = [...(Array.isArray(instance?.input) ? instance.input : []), ...(Array.isArray(instance?.output) ? instance.output : [])];
|
|
2312
|
+
const primaryName = kind === "image" ? "src" : "content";
|
|
2313
|
+
const slotText = (slot) => workspaceSlotValue(slot);
|
|
2314
|
+
const hasSlotText = (slot) => slotText(slot).trim();
|
|
2315
|
+
const contentSlot =
|
|
2316
|
+
slots.find((slot) => String(slot?.name || "") === primaryName && hasSlotText(slot)) ||
|
|
2317
|
+
slots.find((slot) => String(slot?.name || "") === "result" && hasSlotText(slot)) ||
|
|
2318
|
+
slots.find((slot) => String(slot?.name || "") === "filePath" && hasSlotText(slot)) ||
|
|
2319
|
+
slots.find((slot) => String(slot?.type || "") === "text" && hasSlotText(slot));
|
|
2320
|
+
const slotContent = contentSlot ? slotText(contentSlot) : "";
|
|
2321
|
+
return String(isWorkspaceOneClickTaskDefinitionId(instance?.definitionId) ? slotContent : (instance?.body || slotContent));
|
|
2322
|
+
}
|
|
2323
|
+
|
|
1998
2324
|
function normalizeDisplayShareLayout(layout, fallback = "canvas") {
|
|
1999
2325
|
const text = String(layout || "").trim();
|
|
2000
2326
|
return ["canvas", "gallery", "slides", "document", "single"].includes(text) ? text : fallback;
|
|
@@ -2206,8 +2532,8 @@ function publicDisplayPayloadFromShare(root, share) {
|
|
|
2206
2532
|
const nodes = nodeIds.map((id) => {
|
|
2207
2533
|
const instance = instances[id] || {};
|
|
2208
2534
|
const definitionId = String(instance.definitionId || "");
|
|
2209
|
-
const kind =
|
|
2210
|
-
const rawBody =
|
|
2535
|
+
const kind = workspaceDisplayKindFromInstance(instance);
|
|
2536
|
+
const rawBody = workspaceDisplayContentFromInstance(instance, kind);
|
|
2211
2537
|
const filePath = workspaceDisplayTextFilePath(rawBody, kind);
|
|
2212
2538
|
let body = rawBody;
|
|
2213
2539
|
if (filePath) {
|
|
@@ -2499,6 +2825,20 @@ function buildWorkspaceGeneratePrompt(payload) {
|
|
|
2499
2825
|
? payload.selectedNodeIds.map((id) => String(id || "").trim()).filter(Boolean)
|
|
2500
2826
|
: [];
|
|
2501
2827
|
const skillsBlock = typeof payload?.skillsBlock === "string" ? payload.skillsBlock.trim() : "";
|
|
2828
|
+
const history = Array.isArray(payload?.messages) ? payload.messages : [];
|
|
2829
|
+
const historyBlock = history
|
|
2830
|
+
.slice(-16)
|
|
2831
|
+
.map((msg) => {
|
|
2832
|
+
const text = String(msg?.text || "").trim();
|
|
2833
|
+
if (!text) return "";
|
|
2834
|
+
const kind = String(msg?.kind || "").trim();
|
|
2835
|
+
if (kind === "raw" || kind === "prompt" || kind === "thinking") return "";
|
|
2836
|
+
const role = msg?.role === "user" ? "user" : (msg?.error ? "error" : "assistant");
|
|
2837
|
+
if (kind === "run-summary" || kind === "activity") return `context: ${text}`;
|
|
2838
|
+
return `${role}: ${text}`;
|
|
2839
|
+
})
|
|
2840
|
+
.filter(Boolean)
|
|
2841
|
+
.join("\n\n");
|
|
2502
2842
|
const contexts = Array.isArray(payload?.contexts) ? payload.contexts : [];
|
|
2503
2843
|
const contextBlocks = contexts
|
|
2504
2844
|
.map((ctx, idx) => {
|
|
@@ -2525,6 +2865,14 @@ function buildWorkspaceGeneratePrompt(payload) {
|
|
|
2525
2865
|
"只输出 ASCII 图正文,不要解释,不要包裹 Markdown 代码围栏。",
|
|
2526
2866
|
"使用 +-|/\\<> 等字符表达结构,尽量保持对齐。",
|
|
2527
2867
|
].join("\n")
|
|
2868
|
+
: outputKind === "react"
|
|
2869
|
+
? [
|
|
2870
|
+
"你是 workspace React 工程节点的内容生成器。",
|
|
2871
|
+
"请根据用户 prompt 和上游节点/文件上下文生成一个可预览的小型 React 工程 JSON。",
|
|
2872
|
+
"只输出 JSON,不要解释,不要包裹 Markdown 代码围栏。",
|
|
2873
|
+
"JSON 必须包含 title、entry、files;files 至少包含 src/App.jsx,可包含 src/styles.css。",
|
|
2874
|
+
"src/App.jsx 里定义或 export default 一个 App 组件;不要依赖未声明的外部包。",
|
|
2875
|
+
].join("\n")
|
|
2528
2876
|
: [
|
|
2529
2877
|
"你是 AgentFlow Workspace Composer。",
|
|
2530
2878
|
"默认以用户当前选择的 workspace 节点作为上下文范围;选中节点不是让你重建整张画布的授权。",
|
|
@@ -2547,6 +2895,7 @@ function buildWorkspaceGeneratePrompt(payload) {
|
|
|
2547
2895
|
skillsBlock ? `\n## Selected Skills\n\n${skillsBlock}` : "",
|
|
2548
2896
|
kindInstruction,
|
|
2549
2897
|
contextBlocks ? `\n## 上下文\n\n${contextBlocks}` : "",
|
|
2898
|
+
historyBlock ? `\n## 对话历史\n\n${historyBlock}` : "",
|
|
2550
2899
|
`\n## 用户 prompt\n\n${userPrompt}`,
|
|
2551
2900
|
].filter(Boolean).join("\n");
|
|
2552
2901
|
}
|
|
@@ -2578,6 +2927,8 @@ function buildWorkspaceNodeChatPrompt(payload) {
|
|
|
2578
2927
|
].join("\n")
|
|
2579
2928
|
: nodeKind === "html"
|
|
2580
2929
|
? "只输出完整或片段 HTML,不要解释,不要包裹 Markdown 代码围栏。"
|
|
2930
|
+
: nodeKind === "react"
|
|
2931
|
+
? "只输出 React 工程 JSON,不要解释,不要包裹 Markdown 代码围栏。JSON 必须包含 title、entry、files;files 至少包含 src/App.jsx。"
|
|
2581
2932
|
: nodeKind === "image"
|
|
2582
2933
|
? "只输出新的图片 src,可以是 URL、data URL 或文件路径,不要解释。"
|
|
2583
2934
|
: nodeKind === "mermaid"
|
|
@@ -3133,12 +3484,21 @@ function workspaceDisplayKind(definitionId) {
|
|
|
3133
3484
|
if (id === "display_mermaid") return "mermaid";
|
|
3134
3485
|
if (id === "display_ascii") return "ascii";
|
|
3135
3486
|
if (id === "display_html") return "html";
|
|
3487
|
+
if (id === "display_react_app") return "react";
|
|
3136
3488
|
if (id === "display_image") return "image";
|
|
3137
3489
|
if (id === "display_chart") return "chart";
|
|
3138
3490
|
if (id === "display_table") return "table";
|
|
3139
3491
|
return "";
|
|
3140
3492
|
}
|
|
3141
3493
|
|
|
3494
|
+
function workspaceDisplayKindFromInstance(instance) {
|
|
3495
|
+
const direct = workspaceDisplayKind(instance?.definitionId);
|
|
3496
|
+
if (direct) return direct;
|
|
3497
|
+
if (!isWorkspaceOneClickTaskDefinitionId(instance?.definitionId)) return "";
|
|
3498
|
+
if (!workspaceDisplayContentFromInstance(instance, workspaceContextRunDisplayKind(instance)).trim()) return "";
|
|
3499
|
+
return workspaceContextRunDisplayKind(instance);
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3142
3502
|
function workspaceDisplayTextFilePath(value, kind = "") {
|
|
3143
3503
|
const text = String(value || "").trim();
|
|
3144
3504
|
if (!text || text.length > 260) return "";
|
|
@@ -3149,6 +3509,7 @@ function workspaceDisplayTextFilePath(value, kind = "") {
|
|
|
3149
3509
|
const ext = clean.split("?")[0].split("#")[0].toLowerCase().split(".").pop() || "";
|
|
3150
3510
|
const allowedByKind = {
|
|
3151
3511
|
html: new Set(["html", "htm"]),
|
|
3512
|
+
react: new Set(["json", "jsx", "tsx", "js", "txt"]),
|
|
3152
3513
|
markdown: new Set(["md", "markdown", "txt"]),
|
|
3153
3514
|
mermaid: new Set(["mmd", "mermaid", "txt"]),
|
|
3154
3515
|
ascii: new Set(["txt", "log"]),
|
|
@@ -3222,6 +3583,7 @@ function workspaceDisplayKindExample(kind, field) {
|
|
|
3222
3583
|
if (kind === "table") return { columns: ["列名1", "列名2"], rows: [["值1", "值2"]] };
|
|
3223
3584
|
if (kind === "chart") return { type: "chart", version: "1.0", renderer: "echarts", option: { xAxis: { type: "category", data: [] }, yAxis: { type: "value" }, series: [{ type: "bar", data: [] }] } };
|
|
3224
3585
|
if (kind === "html") return "<可直接渲染的 HTML>";
|
|
3586
|
+
if (kind === "react") return { title: "React App", entry: "src/App.jsx", files: { "src/App.jsx": "export default function App() { return <main>...</main>; }", "src/styles.css": "body { margin: 0; }" }, inputs: {} };
|
|
3225
3587
|
if (kind === "mermaid") return "flowchart TD\n A[开始] --> B[结束]";
|
|
3226
3588
|
if (kind === "ascii") return "+---+\n| |\n+---+";
|
|
3227
3589
|
if (kind === "image") return "<图片 URL 或 data URL>";
|
|
@@ -3357,12 +3719,16 @@ function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
|
3357
3719
|
.filter((slot) => {
|
|
3358
3720
|
const name = String(slot?.name || "").trim();
|
|
3359
3721
|
const type = String(slot?.type || "");
|
|
3360
|
-
return name && type !== "node" && name !== "next" && name !== "result" && name !== "content";
|
|
3722
|
+
return name && type !== "node" && name !== "next" && name !== "result" && name !== "content" && name !== "displayType";
|
|
3361
3723
|
})
|
|
3362
3724
|
.map((slot) => String(slot.name).trim());
|
|
3363
|
-
const
|
|
3725
|
+
const configuredResultKind = isWorkspaceOneClickTaskDefinitionId(instance.definitionId)
|
|
3726
|
+
? workspaceContextRunDisplayKind(instance)
|
|
3727
|
+
: "";
|
|
3728
|
+
const resultKind = displayByField.get("result") || workspaceDownstreamOutputKindForField(graph, nodeId, "result") || configuredResultKind || "";
|
|
3364
3729
|
const resultExtByKind = {
|
|
3365
3730
|
html: "html",
|
|
3731
|
+
react: "json",
|
|
3366
3732
|
markdown: "md",
|
|
3367
3733
|
mermaid: "mmd",
|
|
3368
3734
|
ascii: "txt",
|
|
@@ -3374,6 +3740,7 @@ function workspaceOutputProtocolRequirements(graph, nodeId) {
|
|
|
3374
3740
|
const resultKindText = resultKind ? ` ${resultKind}` : "";
|
|
3375
3741
|
const resultGuidance = {
|
|
3376
3742
|
html: "内容必须是可直接放入 iframe 渲染的 HTML;不要使用 Markdown 代码围栏。",
|
|
3743
|
+
react: "内容必须是 React 工程 JSON,包含 title、entry、files;files 至少包含 src/App.jsx,可包含 CSS 文件。",
|
|
3377
3744
|
markdown: "内容必须是 Markdown 正文;除非正文确实需要代码块,否则不要额外包裹代码围栏。",
|
|
3378
3745
|
mermaid: "内容必须是 Mermaid 图表代码,例如 flowchart/sequenceDiagram;不要使用 Markdown 代码围栏。",
|
|
3379
3746
|
ascii: "内容必须是纯文本/ASCII 图或表格;不要输出 HTML 或 Markdown 装饰。",
|
|
@@ -3480,8 +3847,11 @@ function workspaceRunPlan(graph, runNodeId, scopedRoot = "") {
|
|
|
3480
3847
|
if (needed.size !== before) visitControlDownstream(next);
|
|
3481
3848
|
}
|
|
3482
3849
|
};
|
|
3850
|
+
const targetDefId = String(instances[target]?.definitionId || "");
|
|
3851
|
+
const targetIsRunController = targetDefId === "workspace_run" || targetDefId === "workspace_scheduled_run";
|
|
3852
|
+
if (!targetIsRunController) needed.add(target);
|
|
3483
3853
|
visitControlDownstream(target);
|
|
3484
|
-
needed.delete(target);
|
|
3854
|
+
if (targetIsRunController) needed.delete(target);
|
|
3485
3855
|
const dependencyQueue = Array.from(needed);
|
|
3486
3856
|
for (let i = 0; i < dependencyQueue.length; i++) {
|
|
3487
3857
|
const id = dependencyQueue[i];
|
|
@@ -4362,6 +4732,10 @@ function parseWorkspaceSkillKeys(raw) {
|
|
|
4362
4732
|
function selectedSkillKeysFromInstance(instance) {
|
|
4363
4733
|
const bodyKeys = parseWorkspaceSkillKeys(instance?.body || "");
|
|
4364
4734
|
if (bodyKeys.length > 0) return bodyKeys;
|
|
4735
|
+
return selectedSkillKeysFromConfigSlots(instance);
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
function selectedSkillKeysFromConfigSlots(instance) {
|
|
4365
4739
|
const slots = [...(Array.isArray(instance?.input) ? instance.input : []), ...(Array.isArray(instance?.output) ? instance.output : [])];
|
|
4366
4740
|
const slot = slots.find((item) => item?.name === "skillsContext") || slots.find((item) => item?.name === "skillKeys");
|
|
4367
4741
|
return parseWorkspaceSkillKeys(workspaceSlotValue(slot) || "");
|
|
@@ -4475,6 +4849,34 @@ function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot =
|
|
|
4475
4849
|
return lines.join("\n");
|
|
4476
4850
|
}
|
|
4477
4851
|
|
|
4852
|
+
function workspaceDefaultWorkspaceContextBlock(scopedRoot = "", logicalCwd = "") {
|
|
4853
|
+
const root = scopedRoot ? path.resolve(scopedRoot) : "";
|
|
4854
|
+
const cwd = logicalCwd ? path.resolve(logicalCwd) : root;
|
|
4855
|
+
if (!root && !cwd) return "";
|
|
4856
|
+
return [
|
|
4857
|
+
"## Workspace 上下文",
|
|
4858
|
+
"",
|
|
4859
|
+
"当前 Agent 仍在独立节点目录中运行,文件边界以“文件边界”章节为准。",
|
|
4860
|
+
cwd ? `- 当前工作目录上下文:\`${cwd}\`` : "",
|
|
4861
|
+
root ? `- 流程目录:\`${root}\`` : "",
|
|
4862
|
+
"",
|
|
4863
|
+
"使用要求:",
|
|
4864
|
+
"- 读取、搜索、分析当前项目或资料时,优先从“当前工作目录上下文”开始;不要把节点的“当前执行目录”误认为项目根目录。",
|
|
4865
|
+
"- 临时文件和正式产物仍必须按“文件边界”写入本节点的 `tmp/` 与 `outputs/`。",
|
|
4866
|
+
].filter((line) => line !== "").join("\n");
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
function isWorkspaceOneClickTaskDefinitionId(definitionId) {
|
|
4870
|
+
const id = String(definitionId || "");
|
|
4871
|
+
return id === "workspace_one_click_task" || id === "workspace_context_run";
|
|
4872
|
+
}
|
|
4873
|
+
|
|
4874
|
+
function workspaceContextRunDisplayKind(instance) {
|
|
4875
|
+
const raw = workspaceSlotValue(workspaceSlotByName(instance, "displayType")).trim().toLowerCase();
|
|
4876
|
+
if (["markdown", "html", "react", "table", "chart", "ascii", "mermaid"].includes(raw)) return raw;
|
|
4877
|
+
return "markdown";
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4478
4880
|
function mergeWorkspaceSkillBlocks(...values) {
|
|
4479
4881
|
const blocks = values
|
|
4480
4882
|
.map((value) => String(value || ""))
|
|
@@ -4983,6 +5385,12 @@ async function workspaceRunToolNodejsScript({
|
|
|
4983
5385
|
});
|
|
4984
5386
|
}
|
|
4985
5387
|
|
|
5388
|
+
function workspaceNodeModelKey(instance, fallback = "") {
|
|
5389
|
+
const own = String(instance?.model || "").trim();
|
|
5390
|
+
if (own && own !== "default") return own;
|
|
5391
|
+
return String(fallback || "").trim();
|
|
5392
|
+
}
|
|
5393
|
+
|
|
4986
5394
|
async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts = {}) {
|
|
4987
5395
|
const graph = hydrateWorkspaceGraphForRuntime(root, {
|
|
4988
5396
|
root: scopedRoot,
|
|
@@ -5225,17 +5633,49 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5225
5633
|
const inputSlots = Array.isArray(instance.input) ? instance.input : [];
|
|
5226
5634
|
const pathSlot = inputSlots.find((slot) => String(slot?.name || "") === "path") ||
|
|
5227
5635
|
inputSlots.find((slot) => String(slot?.name || "") === "target");
|
|
5636
|
+
const labelSlot = inputSlots.find((slot) => String(slot?.name || "") === "label");
|
|
5637
|
+
const contextSlot = inputSlots.find((slot) => String(slot?.name || "") === "workspaceContext");
|
|
5228
5638
|
const candidate = workspaceSlotValue(pathSlot) || workspaceInstanceText(instance) || inputText;
|
|
5229
5639
|
const abs = candidate ? path.resolve(scopedRoot, candidate) : scopedRoot;
|
|
5230
|
-
if (fs.existsSync(abs)
|
|
5231
|
-
|
|
5640
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
|
|
5641
|
+
throw new Error(`Load Workspace path does not exist or is not a directory: ${abs}`);
|
|
5642
|
+
}
|
|
5643
|
+
cwd = abs;
|
|
5644
|
+
const parsedContext = parseJsonText(workspaceSlotValue(contextSlot), {});
|
|
5645
|
+
const configuredContext = parsedContext && typeof parsedContext === "object" && !Array.isArray(parsedContext) ? parsedContext : {};
|
|
5646
|
+
const workspaceContext = {
|
|
5647
|
+
...configuredContext,
|
|
5648
|
+
version: 1,
|
|
5649
|
+
label: workspaceSlotValue(labelSlot) || path.basename(cwd) || "workspace",
|
|
5650
|
+
cwd,
|
|
5651
|
+
workspaceRoot: cwd,
|
|
5652
|
+
pipelineWorkspace: path.resolve(scopedRoot),
|
|
5653
|
+
previous: null,
|
|
5654
|
+
};
|
|
5655
|
+
let nextInstance = workspaceSetOutputSlot(instance, "workspaceContext", JSON.stringify(workspaceContext));
|
|
5656
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "cwd", cwd);
|
|
5657
|
+
graph.instances[nodeId] = nextInstance;
|
|
5658
|
+
publishNodeOutput(nodeId, JSON.stringify(workspaceContext), { emitGraph: true });
|
|
5659
|
+
emit({ type: "graph", nodeId, graph });
|
|
5232
5660
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5233
5661
|
continue;
|
|
5234
5662
|
}
|
|
5235
5663
|
|
|
5236
5664
|
if (defId === "control_user_workspace") {
|
|
5237
5665
|
cwd = path.resolve(os.homedir());
|
|
5238
|
-
|
|
5666
|
+
const workspaceContext = {
|
|
5667
|
+
version: 1,
|
|
5668
|
+
label: "home",
|
|
5669
|
+
cwd,
|
|
5670
|
+
workspaceRoot: cwd,
|
|
5671
|
+
pipelineWorkspace: path.resolve(scopedRoot),
|
|
5672
|
+
previous: null,
|
|
5673
|
+
};
|
|
5674
|
+
let nextInstance = workspaceSetOutputSlot(instance, "workspaceContext", JSON.stringify(workspaceContext));
|
|
5675
|
+
nextInstance = workspaceSetOutputSlot(nextInstance, "cwd", cwd);
|
|
5676
|
+
graph.instances[nodeId] = nextInstance;
|
|
5677
|
+
publishNodeOutput(nodeId, JSON.stringify(workspaceContext), { emitGraph: true });
|
|
5678
|
+
emit({ type: "graph", nodeId, graph });
|
|
5239
5679
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5240
5680
|
continue;
|
|
5241
5681
|
}
|
|
@@ -5511,6 +5951,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5511
5951
|
}
|
|
5512
5952
|
|
|
5513
5953
|
if (defId === "tool_nodejs") {
|
|
5954
|
+
const nodeModelKey = workspaceNodeModelKey(instance, modelKey);
|
|
5514
5955
|
const prepareStartedAt = Date.now();
|
|
5515
5956
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5516
5957
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
@@ -5544,7 +5985,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5544
5985
|
resultContent,
|
|
5545
5986
|
structured: normalizedAgentOutput,
|
|
5546
5987
|
runPackage,
|
|
5547
|
-
modelKey,
|
|
5988
|
+
modelKey: nodeModelKey,
|
|
5548
5989
|
userCtx,
|
|
5549
5990
|
emit: (event) => emit({ ...event, nodeId }),
|
|
5550
5991
|
onActiveChild: opts.onActiveChild,
|
|
@@ -5556,12 +5997,15 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5556
5997
|
continue;
|
|
5557
5998
|
}
|
|
5558
5999
|
|
|
6000
|
+
const isContextRunNode = isWorkspaceOneClickTaskDefinitionId(defId);
|
|
6001
|
+
const nodeModelKey = workspaceNodeModelKey(instance, modelKey);
|
|
5559
6002
|
const prepareStartedAt = Date.now();
|
|
5560
6003
|
const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
|
|
5561
6004
|
const relevantInputs = workspaceRelevantInputValues(instance.body || "", inputValues);
|
|
5562
6005
|
const upstreamText = workspaceTaskUpstreamText(graph, nodeId, outputs, relevantInputs.placeholders, scopedRoot);
|
|
5563
6006
|
const upstreamSkillBlocks = workspaceUpstreamSkillBlocks(graph, nodeId, outputs);
|
|
5564
|
-
const
|
|
6007
|
+
const ownSkillBlock = isContextRunNode ? loadSkillsBlockForKeys(selectedSkillKeysFromConfigSlots(instance)) : "";
|
|
6008
|
+
const promptSkillsBlock = mergeWorkspaceSkillBlocks(ownSkillBlock, upstreamSkillBlocks);
|
|
5565
6009
|
const promptMcpBlock = workspaceUpstreamMcpBlocks(graph, nodeId, outputs);
|
|
5566
6010
|
const runPackage = workspaceCreateNodeRunPackage(runTmpRoot, nodeId, {
|
|
5567
6011
|
scopedRoot,
|
|
@@ -5583,7 +6027,10 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5583
6027
|
// Best-effort debug artifact only.
|
|
5584
6028
|
}
|
|
5585
6029
|
const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
|
|
5586
|
-
|
|
6030
|
+
let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
|
|
6031
|
+
if (isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true) && !workspaceContextBlock) {
|
|
6032
|
+
workspaceContextBlock = workspaceDefaultWorkspaceContextBlock(scopedRoot, cwd);
|
|
6033
|
+
}
|
|
5587
6034
|
const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage, historyBlock, workspaceContextBlock);
|
|
5588
6035
|
try {
|
|
5589
6036
|
fs.writeFileSync(path.join(runPackage.nodeRunDir, "prompt.md"), prompt.trimEnd() + "\n", "utf-8");
|
|
@@ -5598,6 +6045,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5598
6045
|
nodeRunDir: runPackage.nodeRunDir,
|
|
5599
6046
|
});
|
|
5600
6047
|
emit({ type: "natural", kind: "prompt", nodeId, text: prompt });
|
|
6048
|
+
emit({ type: "status", nodeId, line: `Model: ${nodeModelKey || "default"}` });
|
|
5601
6049
|
let content = "";
|
|
5602
6050
|
const runHistoryEvents = [];
|
|
5603
6051
|
const maxAttempts = 3;
|
|
@@ -5612,7 +6060,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5612
6060
|
uiWorkspaceRoot: scopedRoot,
|
|
5613
6061
|
cliWorkspace: runPackage.nodeRunDir,
|
|
5614
6062
|
prompt,
|
|
5615
|
-
modelKey,
|
|
6063
|
+
modelKey: nodeModelKey,
|
|
5616
6064
|
agentflowUserId: userCtx.userId || "",
|
|
5617
6065
|
extraEnv: runtimeEnv({
|
|
5618
6066
|
AGENTFLOW_WORKSPACE_TMP_ROOT: runTmpRoot,
|
|
@@ -5682,15 +6130,20 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
5682
6130
|
resultContent,
|
|
5683
6131
|
structured: normalizedAgentOutput,
|
|
5684
6132
|
runPackage,
|
|
5685
|
-
modelKey,
|
|
6133
|
+
modelKey: nodeModelKey,
|
|
5686
6134
|
userCtx,
|
|
5687
6135
|
historyEvents: runHistoryEvents,
|
|
5688
6136
|
emit: (event) => emit({ ...event, nodeId }),
|
|
5689
6137
|
onActiveChild: opts.onActiveChild,
|
|
5690
6138
|
});
|
|
5691
6139
|
if (implementationUpdate.changed) graph.instances[nodeId] = implementationUpdate.instance;
|
|
6140
|
+
let contextRunOutputChanged = false;
|
|
6141
|
+
if (isContextRunNode) {
|
|
6142
|
+
graph.instances[nodeId] = workspaceSetOutputSlot(graph.instances[nodeId] || instance, "displayType", workspaceContextRunDisplayKind(instance));
|
|
6143
|
+
contextRunOutputChanged = true;
|
|
6144
|
+
}
|
|
5692
6145
|
const updatedDisplays = propagateNodeOutputDisplays(nodeId, resultContent);
|
|
5693
|
-
if (slotUpdate.changed || implementationUpdate.changed || updatedDisplays.length) emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
6146
|
+
if (slotUpdate.changed || implementationUpdate.changed || contextRunOutputChanged || updatedDisplays.length) emit({ type: "graph", nodeId, displayNodeIds: updatedDisplays, graph });
|
|
5694
6147
|
emit({ type: "node-done", nodeId, definitionId: defId });
|
|
5695
6148
|
}
|
|
5696
6149
|
} finally {
|
|
@@ -5910,6 +6363,13 @@ function workspaceRunEntryKey(scopeKey, runId) {
|
|
|
5910
6363
|
return `${scopeKey}:${String(runId || "").trim() || runLedgerId("workspace")}`;
|
|
5911
6364
|
}
|
|
5912
6365
|
|
|
6366
|
+
function workspaceRuntimeNodeLabel(graph, nodeId, fallback = "Workspace Run") {
|
|
6367
|
+
const id = String(nodeId || "").trim();
|
|
6368
|
+
const instance = graph?.instances && typeof graph.instances === "object" ? graph.instances[id] : null;
|
|
6369
|
+
const label = String(instance?.label || "").trim();
|
|
6370
|
+
return label || id || fallback;
|
|
6371
|
+
}
|
|
6372
|
+
|
|
5913
6373
|
function workspaceActiveRunsForScope(scopeKey) {
|
|
5914
6374
|
const key = String(scopeKey || "");
|
|
5915
6375
|
return Array.from(activeWorkspaceRuns.entries())
|
|
@@ -6287,6 +6747,7 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6287
6747
|
return;
|
|
6288
6748
|
}
|
|
6289
6749
|
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(graph, scheduleNodeId, config);
|
|
6750
|
+
const scheduleAlias = workspaceRuntimeNodeLabel(graph, scheduleNodeId, String(entry.label || "Scheduled Run"));
|
|
6290
6751
|
if (!targetRunNodeId) {
|
|
6291
6752
|
const error = "Scheduled Run node is missing";
|
|
6292
6753
|
appendWorkspaceRunLogEvent(runLog.runId, { type: "invalid", error, scheduleNodeId });
|
|
@@ -6350,6 +6811,7 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
6350
6811
|
runNodeId: targetRunNodeId,
|
|
6351
6812
|
flowId: String(entry.flowId || ""),
|
|
6352
6813
|
flowSource: String(entry.flowSource || "user"),
|
|
6814
|
+
label: scheduleAlias,
|
|
6353
6815
|
plannedNodeIds,
|
|
6354
6816
|
startedAt: Date.now(),
|
|
6355
6817
|
scheduled: true,
|
|
@@ -6645,6 +7107,14 @@ export function startUiServer({
|
|
|
6645
7107
|
|
|
6646
7108
|
const authUser = getAuthUserFromRequest(req);
|
|
6647
7109
|
const userCtx = authUser ? { userId: authUser.userId, isAdmin: Boolean(authUser.isAdmin) } : {};
|
|
7110
|
+
if (req.method === "GET" && url.pathname === "/api/auth/session-token") {
|
|
7111
|
+
if (!authUser?.userId) {
|
|
7112
|
+
json(res, 401, { error: "Unauthorized" });
|
|
7113
|
+
return;
|
|
7114
|
+
}
|
|
7115
|
+
json(res, 200, { token: getSessionTokenFromRequest(req) || "" });
|
|
7116
|
+
return;
|
|
7117
|
+
}
|
|
6648
7118
|
if (req.method === "GET" && url.pathname === "/api/display/shares") {
|
|
6649
7119
|
try {
|
|
6650
7120
|
if (!authUser?.userId) {
|
|
@@ -7176,6 +7646,40 @@ export function startUiServer({
|
|
|
7176
7646
|
return;
|
|
7177
7647
|
}
|
|
7178
7648
|
|
|
7649
|
+
if (req.method === "GET" && url.pathname === "/api/workspaces") {
|
|
7650
|
+
try {
|
|
7651
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
7652
|
+
flowId: url.searchParams.get("flowId") || "",
|
|
7653
|
+
flowSource: url.searchParams.get("flowSource") || "user",
|
|
7654
|
+
archived: url.searchParams.get("archived") === "1",
|
|
7655
|
+
}, userCtx);
|
|
7656
|
+
const scopedRoot = scoped.error ? root : scoped.root;
|
|
7657
|
+
json(res, 200, {
|
|
7658
|
+
path: userWorkspacesPath(userCtx),
|
|
7659
|
+
workspaces: listConfiguredWorkspaces(root, scopedRoot, userCtx),
|
|
7660
|
+
customWorkspaces: readUserWorkspaces(userCtx),
|
|
7661
|
+
});
|
|
7662
|
+
} catch (e) {
|
|
7663
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
7664
|
+
}
|
|
7665
|
+
return;
|
|
7666
|
+
}
|
|
7667
|
+
|
|
7668
|
+
if (req.method === "POST" && url.pathname === "/api/workspaces") {
|
|
7669
|
+
try {
|
|
7670
|
+
const payload = await readJson(req);
|
|
7671
|
+
const customWorkspaces = writeUserWorkspaces(userCtx, payload?.workspaces || payload?.customWorkspaces || []);
|
|
7672
|
+
json(res, 200, {
|
|
7673
|
+
path: userWorkspacesPath(userCtx),
|
|
7674
|
+
workspaces: listConfiguredWorkspaces(root, root, userCtx),
|
|
7675
|
+
customWorkspaces,
|
|
7676
|
+
});
|
|
7677
|
+
} catch (e) {
|
|
7678
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
7679
|
+
}
|
|
7680
|
+
return;
|
|
7681
|
+
}
|
|
7682
|
+
|
|
7179
7683
|
if (req.method === "GET" && url.pathname === "/api/workspace/graph") {
|
|
7180
7684
|
try {
|
|
7181
7685
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
@@ -7446,6 +7950,7 @@ export function startUiServer({
|
|
|
7446
7950
|
const controller = new AbortController();
|
|
7447
7951
|
const runId = String(payload.runSessionId || payload.runId || "").trim() || runLedgerId("workspace");
|
|
7448
7952
|
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
7953
|
+
const runAlias = String(payload.runAlias || "").trim() || workspaceRuntimeNodeLabel(runtimeGraph, runNodeId, "Workspace Run");
|
|
7449
7954
|
const runEntry = {
|
|
7450
7955
|
scopeKey,
|
|
7451
7956
|
controller,
|
|
@@ -7454,6 +7959,7 @@ export function startUiServer({
|
|
|
7454
7959
|
userId: String(userCtx.userId || ""),
|
|
7455
7960
|
username: String(authUser?.username || userCtx.userId || ""),
|
|
7456
7961
|
runNodeId,
|
|
7962
|
+
label: runAlias,
|
|
7457
7963
|
flowId,
|
|
7458
7964
|
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
7459
7965
|
plannedNodeIds,
|
|
@@ -7474,7 +7980,7 @@ export function startUiServer({
|
|
|
7474
7980
|
runNodeId,
|
|
7475
7981
|
scheduled: false,
|
|
7476
7982
|
trigger: "manual",
|
|
7477
|
-
label:
|
|
7983
|
+
label: runAlias,
|
|
7478
7984
|
startedAt: runEntry.startedAt,
|
|
7479
7985
|
});
|
|
7480
7986
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
@@ -7679,10 +8185,12 @@ export function startUiServer({
|
|
|
7679
8185
|
flowId,
|
|
7680
8186
|
flowSource,
|
|
7681
8187
|
runNodeId: entry?.runNodeId || "",
|
|
8188
|
+
label: entry?.label || "",
|
|
7682
8189
|
startedAt: entry?.startedAt || null,
|
|
7683
8190
|
runs: entries.map((item) => ({
|
|
7684
8191
|
runId: item?.runId || "",
|
|
7685
8192
|
runNodeId: item?.runNodeId || "",
|
|
8193
|
+
label: item?.label || "",
|
|
7686
8194
|
startedAt: item?.startedAt || null,
|
|
7687
8195
|
plannedNodeIds: Array.isArray(item?.plannedNodeIds) ? item.plannedNodeIds : [],
|
|
7688
8196
|
scheduled: item?.scheduled === true,
|
|
@@ -8730,6 +9238,60 @@ export function startUiServer({
|
|
|
8730
9238
|
return;
|
|
8731
9239
|
}
|
|
8732
9240
|
|
|
9241
|
+
if (req.method === "GET" && url.pathname === "/api/node-studio/drafts") {
|
|
9242
|
+
try {
|
|
9243
|
+
json(res, 200, { drafts: listNodeStudioDrafts(userCtx) });
|
|
9244
|
+
} catch (e) {
|
|
9245
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
9246
|
+
}
|
|
9247
|
+
return;
|
|
9248
|
+
}
|
|
9249
|
+
|
|
9250
|
+
if (req.method === "GET" && url.pathname === "/api/node-studio/draft") {
|
|
9251
|
+
try {
|
|
9252
|
+
const id = url.searchParams.get("id") || "";
|
|
9253
|
+
if (!id) {
|
|
9254
|
+
json(res, 200, { draft: null });
|
|
9255
|
+
return;
|
|
9256
|
+
}
|
|
9257
|
+
const draft = readNodeStudioDraft(userCtx, id);
|
|
9258
|
+
json(res, 200, { draft: draft && !isLegacyNodeStudioDemoDraft(draft) ? draft : null });
|
|
9259
|
+
} catch (e) {
|
|
9260
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
9261
|
+
}
|
|
9262
|
+
return;
|
|
9263
|
+
}
|
|
9264
|
+
|
|
9265
|
+
if (req.method === "POST" && url.pathname === "/api/node-studio/draft") {
|
|
9266
|
+
let payload;
|
|
9267
|
+
try {
|
|
9268
|
+
payload = JSON.parse(await readBody(req));
|
|
9269
|
+
} catch {
|
|
9270
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
9271
|
+
return;
|
|
9272
|
+
}
|
|
9273
|
+
try {
|
|
9274
|
+
const current = readNodeStudioDraft(userCtx, payload.id || "") || emptyNodeStudioDraft(userCtx, payload.id || "untitled_node");
|
|
9275
|
+
const promptDraft = payload.promptDraft != null ? String(payload.promptDraft) : current.promptDraft || "";
|
|
9276
|
+
const agentMessages = Array.isArray(current.agentMessages) ? [...current.agentMessages] : [];
|
|
9277
|
+
if (payload.appendUserMessage === true && promptDraft.trim()) {
|
|
9278
|
+
const at = new Date().toISOString();
|
|
9279
|
+
agentMessages.push({ role: "user", text: promptDraft.trim(), at });
|
|
9280
|
+
agentMessages.push({ role: "assistant", text: "已记录需求,下一步会由节点 Agent 更新 manifest、脚本和 UI schema。", at });
|
|
9281
|
+
}
|
|
9282
|
+
const draft = writeNodeStudioDraft(userCtx, {
|
|
9283
|
+
...current,
|
|
9284
|
+
...(payload.config && typeof payload.config === "object" ? { config: { ...(current.config || {}), ...payload.config } } : {}),
|
|
9285
|
+
promptDraft,
|
|
9286
|
+
agentMessages,
|
|
9287
|
+
});
|
|
9288
|
+
json(res, 200, { ok: true, draft });
|
|
9289
|
+
} catch (e) {
|
|
9290
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
9291
|
+
}
|
|
9292
|
+
return;
|
|
9293
|
+
}
|
|
9294
|
+
|
|
8733
9295
|
if (req.method === "GET" && url.pathname === "/api/marketplace/nodes") {
|
|
8734
9296
|
try {
|
|
8735
9297
|
const marketplaceScope = url.searchParams.get("scope") === "owned" ? "owned" : "all";
|