@fieldwangai/agentflow 0.1.89 → 0.1.91

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.
@@ -1617,10 +1617,48 @@ const DISPLAY_SHARE_ALLOWED_EXPIRY_DAYS = new Set([1, 7, 30, 90, 365]);
1617
1617
  const NODE_STUDIO_DRAFTS_DIRNAME = "node-studio/drafts";
1618
1618
  const USER_WORKSPACES_FILENAME = "workspaces.json";
1619
1619
 
1620
- function userWorkspacesPath(userCtx = {}) {
1620
+ function workspacesPath() {
1621
+ return path.join(getAgentflowDataRoot(), USER_WORKSPACES_FILENAME);
1622
+ }
1623
+
1624
+ function legacyUserWorkspacesPath(userCtx = {}) {
1621
1625
  return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), USER_WORKSPACES_FILENAME);
1622
1626
  }
1623
1627
 
1628
+ function defaultWorkspaceGitPath(id) {
1629
+ return path.join(getAgentflowDataRoot(), "workspaces", "repos", id);
1630
+ }
1631
+
1632
+ function legacyDefaultWorkspaceGitPath(userCtx = {}, id = "") {
1633
+ return path.join(getAgentflowUserDataRoot(userCtx.userId || ""), "workspaces", "repos", id);
1634
+ }
1635
+
1636
+ function isLegacyDefaultWorkspaceGitPath(rawPath = "", id = "", userCtx = {}) {
1637
+ const raw = String(rawPath || "").trim();
1638
+ if (!raw || !id) return false;
1639
+ try {
1640
+ return path.resolve(raw.replace(/^~(?=$|\/|\\)/, os.homedir())) === path.resolve(legacyDefaultWorkspaceGitPath(userCtx, id));
1641
+ } catch {
1642
+ return false;
1643
+ }
1644
+ }
1645
+
1646
+ function workspaceRepoNameFromUrl(repoUrl = "") {
1647
+ const raw = String(repoUrl || "").trim();
1648
+ if (!raw) return "";
1649
+ let pathname = raw;
1650
+ try {
1651
+ pathname = new URL(raw).pathname;
1652
+ } catch {
1653
+ pathname = raw.split("?")[0].split("#")[0];
1654
+ }
1655
+ const name = pathname.replace(/\/+$/, "").split("/").filter(Boolean).pop() || "";
1656
+ return name
1657
+ .replace(/\.git$/i, "")
1658
+ .replace(/[^A-Za-z0-9._-]+/g, "_")
1659
+ .replace(/^_+|_+$/g, "");
1660
+ }
1661
+
1624
1662
  function normalizeWorkspaceEntry(entry = {}, index = 0, userCtx = {}) {
1625
1663
  const label = String(entry?.label || entry?.name || "").trim();
1626
1664
  const kindRaw = String(entry?.kind || entry?.source || "").trim().toLowerCase();
@@ -1632,9 +1670,11 @@ function normalizeWorkspaceEntry(entry = {}, index = 0, userCtx = {}) {
1632
1670
  if (!rawPath && kind !== "git") return null;
1633
1671
  const idRaw = String(entry?.id || label || mountPathRaw || repoUrl || rawPath || `workspace_${index + 1}`).trim().toLowerCase();
1634
1672
  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()));
1673
+ const suggestedMountPath = kind === "git" ? workspaceRepoNameFromUrl(repoUrl) : "";
1674
+ const mountPath = (mountPathRaw || suggestedMountPath || id).replace(/^\/+/, "").replace(/\.\.(\/|\\|$)/g, "").trim() || id;
1675
+ const defaultGitPath = defaultWorkspaceGitPath(id);
1676
+ const explicitPath = kind === "git" && isLegacyDefaultWorkspaceGitPath(rawPath, id, userCtx) ? "" : rawPath;
1677
+ const absPath = path.resolve((explicitPath || defaultGitPath).replace(/^~(?=$|\/|\\)/, os.homedir()));
1638
1678
  const exists = fs.existsSync(absPath) && fs.statSync(absPath).isDirectory();
1639
1679
  return {
1640
1680
  id,
@@ -1653,8 +1693,7 @@ function normalizeWorkspaceEntry(entry = {}, index = 0, userCtx = {}) {
1653
1693
  };
1654
1694
  }
1655
1695
 
1656
- function readUserWorkspaces(userCtx = {}) {
1657
- const p = userWorkspacesPath(userCtx);
1696
+ function readWorkspacesFromPath(p, userCtx = {}) {
1658
1697
  if (!fs.existsSync(p)) return [];
1659
1698
  try {
1660
1699
  const data = JSON.parse(fs.readFileSync(p, "utf-8"));
@@ -1665,6 +1704,49 @@ function readUserWorkspaces(userCtx = {}) {
1665
1704
  }
1666
1705
  }
1667
1706
 
1707
+ function readLegacyAdminWorkspaces(userCtx = {}) {
1708
+ const users = readAuthUsers();
1709
+ const candidates = [];
1710
+ for (const [userId, user] of Object.entries(users || {})) {
1711
+ if (user?.isAdmin) candidates.push(String(userId || ""));
1712
+ }
1713
+ if (userCtx?.isAdmin && userCtx.userId) candidates.unshift(String(userCtx.userId));
1714
+ const seenPaths = new Set();
1715
+ const seenEntries = new Set();
1716
+ const out = [];
1717
+ for (const userId of candidates) {
1718
+ const p = legacyUserWorkspacesPath({ userId });
1719
+ const resolved = path.resolve(p);
1720
+ if (seenPaths.has(resolved) || resolved === path.resolve(workspacesPath())) continue;
1721
+ seenPaths.add(resolved);
1722
+ for (const entry of readWorkspacesFromPath(p, { userId })) {
1723
+ const key = entry.id || entry.path || entry.repoUrl;
1724
+ if (seenEntries.has(key)) continue;
1725
+ seenEntries.add(key);
1726
+ out.push(entry);
1727
+ }
1728
+ }
1729
+ return out;
1730
+ }
1731
+
1732
+ function readUserWorkspaces(userCtx = {}) {
1733
+ const globalPath = workspacesPath();
1734
+ const globalWorkspaces = fs.existsSync(globalPath) ? readWorkspacesFromPath(globalPath, userCtx) : [];
1735
+ const adminLegacy = readLegacyAdminWorkspaces(userCtx);
1736
+ if (globalWorkspaces.length || adminLegacy.length) {
1737
+ const seen = new Set();
1738
+ const out = [];
1739
+ for (const entry of [...globalWorkspaces, ...adminLegacy]) {
1740
+ const key = entry.id || entry.path || entry.repoUrl;
1741
+ if (seen.has(key)) continue;
1742
+ seen.add(key);
1743
+ out.push(entry);
1744
+ }
1745
+ return out;
1746
+ }
1747
+ return readWorkspacesFromPath(legacyUserWorkspacesPath(userCtx), userCtx);
1748
+ }
1749
+
1668
1750
  function writeUserWorkspaces(userCtx = {}, entries = []) {
1669
1751
  const seen = new Set();
1670
1752
  const workspaces = (Array.isArray(entries) ? entries : [])
@@ -1676,7 +1758,7 @@ function writeUserWorkspaces(userCtx = {}, entries = []) {
1676
1758
  seen.add(key);
1677
1759
  return true;
1678
1760
  });
1679
- const p = userWorkspacesPath(userCtx);
1761
+ const p = workspacesPath();
1680
1762
  fs.mkdirSync(path.dirname(p), { recursive: true });
1681
1763
  fs.writeFileSync(p, JSON.stringify({ version: 1, workspaces }, null, 2) + "\n", "utf-8");
1682
1764
  return workspaces;
@@ -2898,6 +2980,114 @@ function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {}) {
2898
2980
  return { root: path.resolve(result.path), flowId, flowSource, archived };
2899
2981
  }
2900
2982
 
2983
+ function workspaceConversationsPath(scopedRoot) {
2984
+ return path.join(path.resolve(scopedRoot), ".workspace", "agentflow", "conversations.json");
2985
+ }
2986
+
2987
+ function workspaceConversationText(value, max = 4000) {
2988
+ const text = String(value ?? "").trim();
2989
+ if (!text) return "";
2990
+ return text.length > max ? `${text.slice(0, max)}\n...[truncated ${text.length - max} chars]` : text;
2991
+ }
2992
+
2993
+ function normalizeWorkspaceConversationMessage(message = {}) {
2994
+ const text = workspaceConversationText(message?.text, 4000);
2995
+ if (!text) return null;
2996
+ const role = String(message?.role || "assistant").trim() === "user" ? "user" : "assistant";
2997
+ const kind = String(message?.kind || "").trim();
2998
+ return {
2999
+ role,
3000
+ ...(kind ? { kind } : {}),
3001
+ text,
3002
+ ...(message?.error ? { error: true } : {}),
3003
+ at: Number.isFinite(Number(message?.at)) ? Number(message.at) : Date.now(),
3004
+ };
3005
+ }
3006
+
3007
+ function normalizeWorkspaceConversationMessages(messages, limit = 80) {
3008
+ return (Array.isArray(messages) ? messages : [])
3009
+ .map(normalizeWorkspaceConversationMessage)
3010
+ .filter(Boolean)
3011
+ .slice(-limit);
3012
+ }
3013
+
3014
+ function normalizeWorkspaceNodeChatSessions(nodeChats = {}) {
3015
+ const source = nodeChats && typeof nodeChats === "object" && !Array.isArray(nodeChats) ? nodeChats : {};
3016
+ const entries = Object.entries(source).slice(-80);
3017
+ const next = {};
3018
+ for (const [nodeId, session] of entries) {
3019
+ if (!session || typeof session !== "object" || Array.isArray(session)) continue;
3020
+ const id = String(nodeId || "").trim();
3021
+ if (!id) continue;
3022
+ const messages = normalizeWorkspaceConversationMessages(session.messages, 40);
3023
+ const draft = workspaceConversationText(session.draft || "", 2000);
3024
+ if (!messages.length && !draft) continue;
3025
+ next[id] = {
3026
+ sessionId: String(session.sessionId || `nodechat_${id}`).trim(),
3027
+ messages,
3028
+ ...(draft ? { draft } : {}),
3029
+ candidateContent: "",
3030
+ running: false,
3031
+ error: "",
3032
+ };
3033
+ }
3034
+ return next;
3035
+ }
3036
+
3037
+ function normalizeWorkspaceComposerRunSessions(sessions = []) {
3038
+ return (Array.isArray(sessions) ? sessions : [])
3039
+ .map((session) => {
3040
+ if (!session || typeof session !== "object" || Array.isArray(session)) return null;
3041
+ const id = String(session.id || "").trim();
3042
+ if (!id) return null;
3043
+ const messages = normalizeWorkspaceConversationMessages(session.messages, 80);
3044
+ if (!messages.length) return null;
3045
+ const status = String(session.status || "done").trim();
3046
+ return {
3047
+ id,
3048
+ label: workspaceConversationText(session.label || id, 120),
3049
+ status: status === "failed" ? "failed" : "done",
3050
+ messages,
3051
+ };
3052
+ })
3053
+ .filter(Boolean)
3054
+ .slice(-20);
3055
+ }
3056
+
3057
+ function normalizeWorkspaceConversations(raw = {}) {
3058
+ const data = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
3059
+ const composer = data.composer && typeof data.composer === "object" && !Array.isArray(data.composer) ? data.composer : {};
3060
+ const activeSessionId = String(composer.activeSessionId || "workspace").trim() || "workspace";
3061
+ return {
3062
+ version: 1,
3063
+ updatedAt: new Date().toISOString(),
3064
+ composer: {
3065
+ activeSessionId,
3066
+ messages: normalizeWorkspaceConversationMessages(composer.messages, 100),
3067
+ runSessions: normalizeWorkspaceComposerRunSessions(composer.runSessions),
3068
+ },
3069
+ nodeChats: normalizeWorkspaceNodeChatSessions(data.nodeChats),
3070
+ };
3071
+ }
3072
+
3073
+ function readWorkspaceConversations(scopedRoot) {
3074
+ const filePath = workspaceConversationsPath(scopedRoot);
3075
+ if (!fs.existsSync(filePath)) return normalizeWorkspaceConversations({});
3076
+ try {
3077
+ return normalizeWorkspaceConversations(JSON.parse(fs.readFileSync(filePath, "utf-8")));
3078
+ } catch {
3079
+ return normalizeWorkspaceConversations({});
3080
+ }
3081
+ }
3082
+
3083
+ function writeWorkspaceConversations(scopedRoot, raw) {
3084
+ const filePath = workspaceConversationsPath(scopedRoot);
3085
+ const conversations = normalizeWorkspaceConversations(raw);
3086
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
3087
+ fs.writeFileSync(filePath, JSON.stringify(conversations, null, 2) + "\n", "utf-8");
3088
+ return conversations;
3089
+ }
3090
+
2901
3091
  function workspaceSearchGuardrailsBlock() {
2902
3092
  return [
2903
3093
  "## 检索约束",
@@ -4071,7 +4261,7 @@ function workspaceTargetSlotForEdge(graph, edge) {
4071
4261
  function isWorkspaceSemanticInputSlot(slot) {
4072
4262
  const name = String(slot?.name || "");
4073
4263
  const type = String(slot?.type || "");
4074
- return type === "node" || name === "prev" || name === "next" || name === "skillsContext" || name === "mcpContext" || name === "workspaceContext" || name === "gitContext";
4264
+ return type === "node" || name === "prev" || name === "next" || name === "skillsContext" || name === "mcpContext" || name === "knowledgeContext" || name === "workspaceContext" || name === "gitContext";
4075
4265
  }
4076
4266
 
4077
4267
  function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
@@ -4904,11 +5094,86 @@ function workspaceContextObjectFromText(text, baseCwd, scopedRoot) {
4904
5094
  };
4905
5095
  }
4906
5096
 
5097
+ function workspaceKnowledgeSourceFromObject(source = {}, baseCwd = "", scopedRoot = "") {
5098
+ if (!source || typeof source !== "object" || Array.isArray(source)) return null;
5099
+ const rawPath = String(source.path || source.repoPath || source.cwd || source.workspaceRoot || "").trim();
5100
+ if (!rawPath) return null;
5101
+ const resolvedPath = workspaceResolvePath(baseCwd || scopedRoot, rawPath) || rawPath;
5102
+ return {
5103
+ id: String(source.id || source.mountPath || source.label || path.basename(resolvedPath) || "").trim(),
5104
+ label: String(source.label || source.id || source.mountPath || path.basename(resolvedPath) || "知识库").trim(),
5105
+ kind: String(source.kind || (source.repoUrl ? "git" : "local")).trim() || "local",
5106
+ type: String(source.type || "").trim(),
5107
+ path: resolvedPath,
5108
+ repoPath: resolvedPath,
5109
+ mountPath: String(source.mountPath || "").trim(),
5110
+ repoUrl: String(source.repoUrl || "").trim(),
5111
+ branch: String(source.branch || "").trim(),
5112
+ readonly: source.readonly !== false,
5113
+ };
5114
+ }
5115
+
5116
+ function workspaceKnowledgeSourcesFromText(text, baseCwd = "", scopedRoot = "") {
5117
+ const raw = String(text || "").trim();
5118
+ if (!raw) return [];
5119
+ const parsed = parseJsonText(raw, null);
5120
+ const candidates = Array.isArray(parsed)
5121
+ ? parsed
5122
+ : (parsed && typeof parsed === "object" && Array.isArray(parsed.sources)
5123
+ ? parsed.sources
5124
+ : (parsed && typeof parsed === "object" && (parsed.path || parsed.repoPath || parsed.cwd || parsed.workspaceRoot) ? [parsed] : []));
5125
+ if (candidates.length) {
5126
+ return candidates
5127
+ .map((source) => workspaceKnowledgeSourceFromObject(source, baseCwd, scopedRoot))
5128
+ .filter(Boolean);
5129
+ }
5130
+ const resolved = workspaceResolvePath(baseCwd || scopedRoot, raw) || raw;
5131
+ return [{
5132
+ id: path.basename(resolved) || "knowledge",
5133
+ label: path.basename(resolved) || "知识库",
5134
+ kind: "local",
5135
+ type: "",
5136
+ path: resolved,
5137
+ repoPath: resolved,
5138
+ mountPath: "",
5139
+ repoUrl: "",
5140
+ branch: "",
5141
+ readonly: true,
5142
+ }];
5143
+ }
5144
+
5145
+ function workspaceKnowledgeContextBlockFromSources(sources = []) {
5146
+ const valid = Array.isArray(sources) ? sources.filter((source) => source?.path || source?.repoPath) : [];
5147
+ if (!valid.length) return "";
5148
+ const lines = [
5149
+ "## 知识库上下文",
5150
+ "",
5151
+ "这些路径是只读知识库/上下文源,用于检索、阅读和分析;它们不代表当前执行 cwd。需要修改代码时,请先创建或使用可写工作区。",
5152
+ "",
5153
+ ];
5154
+ valid.forEach((source, index) => {
5155
+ const label = String(source.label || source.id || source.mountPath || `知识库 ${index + 1}`).trim();
5156
+ const sourcePath = String(source.path || source.repoPath || "").trim();
5157
+ lines.push(`${index + 1}. ${label}`);
5158
+ if (source.kind) lines.push(` - 类型:${source.kind}${source.type ? `/${source.type}` : ""}`);
5159
+ if (sourcePath) lines.push(` - 路径:\`${sourcePath}\``);
5160
+ if (source.mountPath) lines.push(` - 挂载目录:${source.mountPath}`);
5161
+ if (source.repoUrl) lines.push(` - Git URL:${source.repoUrl}`);
5162
+ if (source.branch) lines.push(` - 分支:${source.branch}`);
5163
+ });
5164
+ return lines.join("\n");
5165
+ }
5166
+
4907
5167
  function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "") {
4908
5168
  const root = scopedRoot ? path.resolve(scopedRoot) : "";
4909
5169
  const cwd = logicalCwd ? path.resolve(logicalCwd) : root;
5170
+ const knowledgeText = workspaceSemanticInputText(graph, nodeId, outputs, "knowledgeContext", scopedRoot);
5171
+ let knowledgeSources = workspaceKnowledgeSourcesFromText(knowledgeText, cwd || root, scopedRoot);
4910
5172
  const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot);
4911
5173
  let workspaceContext = workspaceContextObjectFromText(workspaceText, cwd || root, scopedRoot);
5174
+ if (!knowledgeSources.length && workspaceContext?.cwd) {
5175
+ knowledgeSources = workspaceKnowledgeSourcesFromText(JSON.stringify([workspaceContext]), cwd || root, scopedRoot);
5176
+ }
4912
5177
  if (!workspaceContext && cwd && root && cwd !== root) {
4913
5178
  workspaceContext = {
4914
5179
  version: 1,
@@ -4920,7 +5185,8 @@ function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot =
4920
5185
  };
4921
5186
  }
4922
5187
  const gitContext = normalizeGitContext(workspaceSemanticInputText(graph, nodeId, outputs, "gitContext", scopedRoot));
4923
- if (!workspaceContext && !gitContext) return "";
5188
+ const knowledgeBlock = workspaceKnowledgeContextBlockFromSources(knowledgeSources);
5189
+ if (!workspaceContext && !gitContext) return knowledgeBlock;
4924
5190
 
4925
5191
  const contextCwd = workspaceContext?.cwd ? path.resolve(String(workspaceContext.cwd)) : "";
4926
5192
  const workspaceRoot = workspaceContext?.workspaceRoot ? path.resolve(String(workspaceContext.workspaceRoot)) : contextCwd;
@@ -4943,7 +5209,7 @@ function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot =
4943
5209
  "- 读取、搜索、分析当前项目或资料时,优先从“当前工作目录上下文”开始;不要把节点的“当前执行目录”误认为项目根目录。",
4944
5210
  "- 临时文件和正式产物仍必须按“文件边界”写入本节点的 `tmp/` 与 `outputs/`。",
4945
5211
  ].filter((line) => line !== "");
4946
- return lines.join("\n");
5212
+ return [knowledgeBlock, lines.join("\n")].filter(Boolean).join("\n\n");
4947
5213
  }
4948
5214
 
4949
5215
  function workspaceDefaultWorkspaceContextBlock(scopedRoot = "", logicalCwd = "") {
@@ -5731,28 +5997,51 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
5731
5997
  const pathSlot = inputSlots.find((slot) => String(slot?.name || "") === "path") ||
5732
5998
  inputSlots.find((slot) => String(slot?.name || "") === "target");
5733
5999
  const labelSlot = inputSlots.find((slot) => String(slot?.name || "") === "label");
6000
+ const knowledgeSlot = inputSlots.find((slot) => String(slot?.name || "") === "knowledgeContext");
5734
6001
  const contextSlot = inputSlots.find((slot) => String(slot?.name || "") === "workspaceContext");
6002
+ const knowledgeText = workspaceSlotValue(knowledgeSlot);
6003
+ let knowledgeSources = workspaceKnowledgeSourcesFromText(knowledgeText, cwd || scopedRoot, scopedRoot);
5735
6004
  const candidate = workspaceSlotValue(pathSlot) || workspaceInstanceText(instance) || inputText;
5736
- const abs = candidate ? path.resolve(scopedRoot, candidate) : scopedRoot;
5737
- if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
5738
- throw new Error(`Load Workspace path does not exist or is not a directory: ${abs}`);
6005
+ if (!knowledgeSources.length && candidate) {
6006
+ const abs = path.resolve(scopedRoot, candidate);
6007
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
6008
+ throw new Error(`Load Knowledge path does not exist or is not a directory: ${abs}`);
6009
+ }
6010
+ knowledgeSources = [{
6011
+ id: path.basename(abs) || "knowledge",
6012
+ label: workspaceSlotValue(labelSlot) || path.basename(abs) || "知识库",
6013
+ kind: "local",
6014
+ type: "",
6015
+ path: abs,
6016
+ repoPath: abs,
6017
+ mountPath: "",
6018
+ repoUrl: "",
6019
+ branch: "",
6020
+ readonly: true,
6021
+ }];
5739
6022
  }
5740
- cwd = abs;
5741
6023
  const parsedContext = parseJsonText(workspaceSlotValue(contextSlot), {});
5742
6024
  const configuredContext = parsedContext && typeof parsedContext === "object" && !Array.isArray(parsedContext) ? parsedContext : {};
5743
- const workspaceContext = {
6025
+ const primarySource = knowledgeSources[0] || null;
6026
+ const primaryPath = primarySource?.path ? path.resolve(String(primarySource.path)) : "";
6027
+ const knowledgeContext = {
6028
+ version: 1,
6029
+ sources: knowledgeSources,
6030
+ };
6031
+ const workspaceContext = primarySource ? {
5744
6032
  ...configuredContext,
5745
6033
  version: 1,
5746
- label: workspaceSlotValue(labelSlot) || path.basename(cwd) || "workspace",
5747
- cwd,
5748
- workspaceRoot: cwd,
6034
+ label: primarySource.label || workspaceSlotValue(labelSlot) || path.basename(primaryPath) || "知识库",
6035
+ cwd: primaryPath,
6036
+ workspaceRoot: primaryPath,
5749
6037
  pipelineWorkspace: path.resolve(scopedRoot),
5750
6038
  previous: null,
5751
- };
5752
- let nextInstance = workspaceSetOutputSlot(instance, "workspaceContext", JSON.stringify(workspaceContext));
5753
- nextInstance = workspaceSetOutputSlot(nextInstance, "cwd", cwd);
6039
+ } : null;
6040
+ let nextInstance = workspaceSetOutputSlot(instance, "knowledgeContext", JSON.stringify(knowledgeContext));
6041
+ nextInstance = workspaceSetOutputSlot(nextInstance, "workspaceContext", workspaceContext ? JSON.stringify(workspaceContext) : "");
6042
+ nextInstance = workspaceSetOutputSlot(nextInstance, "cwd", primaryPath);
5754
6043
  graph.instances[nodeId] = nextInstance;
5755
- publishNodeOutput(nodeId, JSON.stringify(workspaceContext), { emitGraph: true });
6044
+ publishNodeOutput(nodeId, JSON.stringify(knowledgeContext), { emitGraph: true });
5756
6045
  emit({ type: "graph", nodeId, graph });
5757
6046
  emit({ type: "node-done", nodeId, definitionId: defId });
5758
6047
  continue;
@@ -6125,7 +6414,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
6125
6414
  }
6126
6415
  const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
6127
6416
  let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
6128
- if (isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true) && !workspaceContextBlock) {
6417
+ if (!isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true) && !workspaceContextBlock) {
6129
6418
  workspaceContextBlock = workspaceDefaultWorkspaceContextBlock(scopedRoot, cwd);
6130
6419
  }
6131
6420
  const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage, historyBlock, workspaceContextBlock);
@@ -7752,7 +8041,7 @@ export function startUiServer({
7752
8041
  }, userCtx);
7753
8042
  const scopedRoot = scoped.error ? root : scoped.root;
7754
8043
  json(res, 200, {
7755
- path: userWorkspacesPath(userCtx),
8044
+ path: workspacesPath(),
7756
8045
  workspaces: listConfiguredWorkspaces(root, scopedRoot, userCtx),
7757
8046
  customWorkspaces: readUserWorkspaces(userCtx),
7758
8047
  });
@@ -7767,7 +8056,7 @@ export function startUiServer({
7767
8056
  const payload = JSON.parse(await readBody(req));
7768
8057
  const customWorkspaces = writeUserWorkspaces(userCtx, payload?.workspaces || payload?.customWorkspaces || []);
7769
8058
  json(res, 200, {
7770
- path: userWorkspacesPath(userCtx),
8059
+ path: workspacesPath(),
7771
8060
  workspaces: listConfiguredWorkspaces(root, root, userCtx),
7772
8061
  customWorkspaces,
7773
8062
  });
@@ -8628,6 +8917,47 @@ export function startUiServer({
8628
8917
  return;
8629
8918
  }
8630
8919
 
8920
+ if (url.pathname === "/api/workspace/conversations") {
8921
+ let payload = {};
8922
+ if (req.method === "POST") {
8923
+ try {
8924
+ payload = JSON.parse(await readBody(req));
8925
+ } catch {
8926
+ json(res, 400, { error: "Invalid JSON body" });
8927
+ return;
8928
+ }
8929
+ } else if (req.method !== "GET") {
8930
+ json(res, 405, { error: "Method not allowed" });
8931
+ return;
8932
+ }
8933
+ try {
8934
+ const scoped = resolveWorkspaceScopeRoot(root, {
8935
+ flowId: req.method === "POST" ? (payload.flowId || "") : (url.searchParams.get("flowId") || ""),
8936
+ flowSource: req.method === "POST" ? (payload.flowSource || "user") : (url.searchParams.get("flowSource") || "user"),
8937
+ archived: req.method === "POST"
8938
+ ? (payload.archived === true || payload.flowArchived === true)
8939
+ : (url.searchParams.get("archived") === "1" || url.searchParams.get("flowArchived") === "1"),
8940
+ }, userCtx);
8941
+ if (scoped.error) {
8942
+ json(res, 400, { error: scoped.error });
8943
+ return;
8944
+ }
8945
+ if (req.method === "GET") {
8946
+ json(res, 200, { ok: true, conversations: readWorkspaceConversations(scoped.root) });
8947
+ return;
8948
+ }
8949
+ if (scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource)) {
8950
+ json(res, 400, { error: "Cannot write conversations for builtin or archived pipeline workspace" });
8951
+ return;
8952
+ }
8953
+ const conversations = writeWorkspaceConversations(scoped.root, payload.conversations || payload);
8954
+ json(res, 200, { ok: true, conversations });
8955
+ } catch (e) {
8956
+ json(res, 500, { error: (e && e.message) || String(e) });
8957
+ }
8958
+ return;
8959
+ }
8960
+
8631
8961
  if (req.method === "POST" && url.pathname === "/api/workspace/generate") {
8632
8962
  let payload;
8633
8963
  try {
@@ -1,47 +1,56 @@
1
1
  ---
2
- # 内置节点:CD Workspace
2
+ # 内置节点:加载知识库
3
3
  description: |
4
- Switch the runtime workspace context for downstream nodes without changing the AgentFlow pipeline workspace.
4
+ Load one or more read-only knowledge sources for downstream Agent nodes.
5
5
 
6
- Modes:
7
- - `set`: switch to path, keep previous stack unchanged.
8
- - `push`: switch to path and save the incoming context as previous.
9
- - `pop`: restore the previous context.
10
-
11
- `path` supports `${workspaceRoot}`, `${pipelineWorkspace}`, `${flowDir}`, absolute paths, and paths relative to the input workspace context.
12
- displayName: CD Workspace
6
+ This node does not change the runtime cwd. It publishes `knowledgeContext`
7
+ for reading/searching referenced repos or folders. `workspaceContext` and
8
+ `cwd` are retained as legacy compatibility outputs for the first source.
9
+ displayName: 加载知识库
13
10
  input:
14
11
  - type: node
15
12
  name: prev
16
13
  default: ""
14
+ - type: text
15
+ name: knowledgeContext
16
+ default: ""
17
+ showOnNode: false
17
18
  - type: text
18
19
  name: path
19
20
  default: ""
20
- required: true
21
- showOnNode: true
21
+ showOnNode: false
22
22
  - type: text
23
23
  name: mode
24
24
  default: "set"
25
+ showOnNode: false
25
26
  - type: text
26
27
  name: label
27
28
  default: ""
29
+ showOnNode: false
28
30
  - type: text
29
31
  name: workspaceContext
30
32
  default: ""
33
+ showOnNode: false
31
34
  output:
32
35
  - type: node
33
36
  name: next
34
37
  default: ""
35
38
  - type: text
36
- name: workspaceContext
39
+ name: knowledgeContext
37
40
  default: ""
38
41
  required: true
39
42
  showOnNode: true
43
+ - type: text
44
+ name: workspaceContext
45
+ default: ""
46
+ showOnNode: false
40
47
  - type: file
41
48
  name: cwd
42
49
  default: ""
50
+ showOnNode: false
43
51
  - type: text
44
52
  name: previous
45
53
  default: ""
54
+ showOnNode: false
46
55
  ---
47
- Switch downstream execution to `${path}` using mode `${mode}`.
56
+ Load selected knowledge sources for downstream agents.