@fieldwangai/agentflow 0.1.90 → 0.1.92

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.
@@ -2980,6 +2980,114 @@ function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {}) {
2980
2980
  return { root: path.resolve(result.path), flowId, flowSource, archived };
2981
2981
  }
2982
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
+
2983
3091
  function workspaceSearchGuardrailsBlock() {
2984
3092
  return [
2985
3093
  "## 检索约束",
@@ -4153,7 +4261,7 @@ function workspaceTargetSlotForEdge(graph, edge) {
4153
4261
  function isWorkspaceSemanticInputSlot(slot) {
4154
4262
  const name = String(slot?.name || "");
4155
4263
  const type = String(slot?.type || "");
4156
- 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";
4157
4265
  }
4158
4266
 
4159
4267
  function workspaceAgentInputBlock(inputValues = {}, inputMounts = {}) {
@@ -4986,11 +5094,86 @@ function workspaceContextObjectFromText(text, baseCwd, scopedRoot) {
4986
5094
  };
4987
5095
  }
4988
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
+
4989
5167
  function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot = "", logicalCwd = "") {
4990
5168
  const root = scopedRoot ? path.resolve(scopedRoot) : "";
4991
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);
4992
5172
  const workspaceText = workspaceSemanticInputText(graph, nodeId, outputs, "workspaceContext", scopedRoot);
4993
5173
  let workspaceContext = workspaceContextObjectFromText(workspaceText, cwd || root, scopedRoot);
5174
+ if (!knowledgeSources.length && workspaceContext?.cwd) {
5175
+ knowledgeSources = workspaceKnowledgeSourcesFromText(JSON.stringify([workspaceContext]), cwd || root, scopedRoot);
5176
+ }
4994
5177
  if (!workspaceContext && cwd && root && cwd !== root) {
4995
5178
  workspaceContext = {
4996
5179
  version: 1,
@@ -5002,7 +5185,8 @@ function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot =
5002
5185
  };
5003
5186
  }
5004
5187
  const gitContext = normalizeGitContext(workspaceSemanticInputText(graph, nodeId, outputs, "gitContext", scopedRoot));
5005
- if (!workspaceContext && !gitContext) return "";
5188
+ const knowledgeBlock = workspaceKnowledgeContextBlockFromSources(knowledgeSources);
5189
+ if (!workspaceContext && !gitContext) return knowledgeBlock;
5006
5190
 
5007
5191
  const contextCwd = workspaceContext?.cwd ? path.resolve(String(workspaceContext.cwd)) : "";
5008
5192
  const workspaceRoot = workspaceContext?.workspaceRoot ? path.resolve(String(workspaceContext.workspaceRoot)) : contextCwd;
@@ -5025,7 +5209,7 @@ function workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot =
5025
5209
  "- 读取、搜索、分析当前项目或资料时,优先从“当前工作目录上下文”开始;不要把节点的“当前执行目录”误认为项目根目录。",
5026
5210
  "- 临时文件和正式产物仍必须按“文件边界”写入本节点的 `tmp/` 与 `outputs/`。",
5027
5211
  ].filter((line) => line !== "");
5028
- return lines.join("\n");
5212
+ return [knowledgeBlock, lines.join("\n")].filter(Boolean).join("\n\n");
5029
5213
  }
5030
5214
 
5031
5215
  function workspaceDefaultWorkspaceContextBlock(scopedRoot = "", logicalCwd = "") {
@@ -5813,28 +5997,51 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
5813
5997
  const pathSlot = inputSlots.find((slot) => String(slot?.name || "") === "path") ||
5814
5998
  inputSlots.find((slot) => String(slot?.name || "") === "target");
5815
5999
  const labelSlot = inputSlots.find((slot) => String(slot?.name || "") === "label");
6000
+ const knowledgeSlot = inputSlots.find((slot) => String(slot?.name || "") === "knowledgeContext");
5816
6001
  const contextSlot = inputSlots.find((slot) => String(slot?.name || "") === "workspaceContext");
6002
+ const knowledgeText = workspaceSlotValue(knowledgeSlot);
6003
+ let knowledgeSources = workspaceKnowledgeSourcesFromText(knowledgeText, cwd || scopedRoot, scopedRoot);
5817
6004
  const candidate = workspaceSlotValue(pathSlot) || workspaceInstanceText(instance) || inputText;
5818
- const abs = candidate ? path.resolve(scopedRoot, candidate) : scopedRoot;
5819
- if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
5820
- 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
+ }];
5821
6022
  }
5822
- cwd = abs;
5823
6023
  const parsedContext = parseJsonText(workspaceSlotValue(contextSlot), {});
5824
6024
  const configuredContext = parsedContext && typeof parsedContext === "object" && !Array.isArray(parsedContext) ? parsedContext : {};
5825
- 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 ? {
5826
6032
  ...configuredContext,
5827
6033
  version: 1,
5828
- label: workspaceSlotValue(labelSlot) || path.basename(cwd) || "workspace",
5829
- cwd,
5830
- workspaceRoot: cwd,
6034
+ label: primarySource.label || workspaceSlotValue(labelSlot) || path.basename(primaryPath) || "知识库",
6035
+ cwd: primaryPath,
6036
+ workspaceRoot: primaryPath,
5831
6037
  pipelineWorkspace: path.resolve(scopedRoot),
5832
6038
  previous: null,
5833
- };
5834
- let nextInstance = workspaceSetOutputSlot(instance, "workspaceContext", JSON.stringify(workspaceContext));
5835
- 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);
5836
6043
  graph.instances[nodeId] = nextInstance;
5837
- publishNodeOutput(nodeId, JSON.stringify(workspaceContext), { emitGraph: true });
6044
+ publishNodeOutput(nodeId, JSON.stringify(knowledgeContext), { emitGraph: true });
5838
6045
  emit({ type: "graph", nodeId, graph });
5839
6046
  emit({ type: "node-done", nodeId, definitionId: defId });
5840
6047
  continue;
@@ -6207,7 +6414,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
6207
6414
  }
6208
6415
  const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
6209
6416
  let workspaceContextBlock = workspaceNodeWorkspaceContextBlock(graph, nodeId, outputs, scopedRoot, cwd);
6210
- if (isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true) && !workspaceContextBlock) {
6417
+ if (!isContextRunNode && workspaceBoolSlot(instance, "includeWorkspaceContext", true) && !workspaceContextBlock) {
6211
6418
  workspaceContextBlock = workspaceDefaultWorkspaceContextBlock(scopedRoot, cwd);
6212
6419
  }
6213
6420
  const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage, historyBlock, workspaceContextBlock);
@@ -8710,6 +8917,47 @@ export function startUiServer({
8710
8917
  return;
8711
8918
  }
8712
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
+
8713
8961
  if (req.method === "POST" && url.pathname === "/api/workspace/generate") {
8714
8962
  let payload;
8715
8963
  try {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  # 内置节点:子 Agent
3
- description: 利用子 Agent 执行任务;可接收 workspaceContext 切换执行工作区,并接收 skillsContext / mcpContext 注入已加载 skills 与 MCP 工具清单。
3
+ description: 利用子 Agent 执行任务;可接收 knowledgeContext 读取知识库,可接收 workspaceContext 切换执行工作区,并接收 skillsContext / mcpContext 注入已加载 skills 与 MCP 工具清单。
4
4
  displayName: 子 Agent
5
5
  input:
6
6
  - type: node
@@ -12,9 +12,14 @@ input:
12
12
  - type: text
13
13
  name: skillsContext
14
14
  default: ""
15
+ showOnNode: true
15
16
  - type: text
16
17
  name: mcpContext
17
18
  default: ""
19
+ - type: text
20
+ name: knowledgeContext
21
+ default: ""
22
+ showOnNode: true
18
23
  output:
19
24
  - type: node
20
25
  name: next
@@ -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.