@fieldwangai/agentflow 0.1.29 → 0.1.31

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.
Files changed (69) hide show
  1. package/agents/agentflow-node-executor-code.md +3 -2
  2. package/agents/agentflow-node-executor-planning.md +3 -2
  3. package/agents/agentflow-node-executor-requirement.md +3 -2
  4. package/agents/agentflow-node-executor-test.md +3 -2
  5. package/agents/agentflow-node-executor-ui.md +3 -2
  6. package/agents/agentflow-node-executor.md +3 -2
  7. package/agents/en/agentflow-node-executor.md +3 -2
  8. package/agents/zh/agentflow-node-executor.md +3 -2
  9. package/bin/lib/agent-runners.mjs +63 -14
  10. package/bin/lib/api-runner.mjs +30 -4
  11. package/bin/lib/apply.mjs +6 -5
  12. package/bin/lib/auth.mjs +240 -0
  13. package/bin/lib/catalog-agents.mjs +2 -2
  14. package/bin/lib/catalog-flows.mjs +196 -17
  15. package/bin/lib/composer-agent.mjs +22 -1
  16. package/bin/lib/composer-skill-router.mjs +10 -78
  17. package/bin/lib/flow-import.mjs +2 -2
  18. package/bin/lib/flow-write.mjs +20 -20
  19. package/bin/lib/help.mjs +2 -2
  20. package/bin/lib/locales/en.json +29 -1
  21. package/bin/lib/locales/zh.json +31 -3
  22. package/bin/lib/main.mjs +6 -1
  23. package/bin/lib/node-exec-context.mjs +5 -5
  24. package/bin/lib/node-execute.mjs +15 -10
  25. package/bin/lib/paths.mjs +69 -13
  26. package/bin/lib/recent-runs.mjs +2 -2
  27. package/bin/lib/run-node-statuses-from-disk.mjs +3 -3
  28. package/bin/lib/runtime-context.mjs +225 -0
  29. package/bin/lib/scheduler.mjs +42 -38
  30. package/bin/lib/skill-registry.mjs +145 -0
  31. package/bin/lib/ui-server.mjs +1517 -57
  32. package/bin/lib/user-env.mjs +83 -0
  33. package/bin/lib/workspace-tree.mjs +4 -3
  34. package/bin/lib/workspace.mjs +9 -11
  35. package/bin/pipeline/build-node-prompt.mjs +29 -4
  36. package/bin/pipeline/get-env.mjs +5 -29
  37. package/bin/pipeline/get-exec-id.mjs +2 -2
  38. package/bin/pipeline/get-resolved-values.mjs +1 -0
  39. package/bin/pipeline/pre-process-node.mjs +328 -6
  40. package/bin/pipeline/run-tool-nodejs.mjs +7 -0
  41. package/bin/pipeline/validate-flow.mjs +2 -0
  42. package/builtin/nodes/agent_subAgent.md +12 -3
  43. package/builtin/nodes/control_cd_workspace.md +45 -0
  44. package/builtin/nodes/control_load_skills.md +50 -0
  45. package/builtin/nodes/control_user_workspace.md +20 -0
  46. package/builtin/nodes/display_ascii.md +22 -0
  47. package/builtin/nodes/display_markdown.md +22 -0
  48. package/builtin/nodes/display_mermaid.md +22 -0
  49. package/builtin/nodes/tool_git_checkout.md +57 -0
  50. package/builtin/nodes/tool_nodejs.md +8 -1
  51. package/builtin/nodes/tool_print.md +4 -1
  52. package/builtin/web-ui/dist/assets/index-BVWwQpvg.css +1 -0
  53. package/builtin/web-ui/dist/assets/index-CvNy1n3f.js +197 -0
  54. package/builtin/web-ui/dist/index.html +2 -2
  55. package/package.json +1 -1
  56. package/skills/agentflow-flow-recipes/SKILL.md +24 -0
  57. package/skills/agentflow-flow-recipes/references/recipes.md +63 -0
  58. package/skills/agentflow-node-reference/SKILL.md +25 -0
  59. package/skills/agentflow-node-reference/references/builtin-nodes.md +210 -0
  60. package/skills/agentflow-placeholder-reference/SKILL.md +24 -0
  61. package/skills/agentflow-placeholder-reference/references/placeholders.md +20 -0
  62. package/skills/agentflow-runtime-reference/SKILL.md +25 -0
  63. package/skills/agentflow-runtime-reference/references/runtime.md +64 -0
  64. package/skills/agentflow-workspace-ascii/SKILL.md +42 -0
  65. package/skills/agentflow-workspace-graph/SKILL.md +67 -0
  66. package/skills/agentflow-workspace-markdown/SKILL.md +44 -0
  67. package/skills/agentflow-workspace-mermaid/SKILL.md +43 -0
  68. package/builtin/web-ui/dist/assets/index-0vJxkTJz.css +0 -1
  69. package/builtin/web-ui/dist/assets/index-h69bpxLI.js +0 -190
@@ -0,0 +1,83 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+
5
+ import { getAgentflowUserEnvAbs, sanitizeAgentflowUserId } from "./paths.mjs";
6
+
7
+ function normalizeEnvKey(key) {
8
+ return String(key || "").trim();
9
+ }
10
+
11
+ function isValidEnvKey(key) {
12
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key);
13
+ }
14
+
15
+ function readJsonObject(filePath) {
16
+ try {
17
+ if (!fs.existsSync(filePath)) return {};
18
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
19
+ return data && typeof data === "object" && !Array.isArray(data) ? data : {};
20
+ } catch {
21
+ return {};
22
+ }
23
+ }
24
+
25
+ function getFromConfig(config, keyStr) {
26
+ if (!config || typeof config !== "object" || !keyStr) return undefined;
27
+ const parts = String(keyStr).trim().split(".");
28
+ let cur = config;
29
+ for (const p of parts) {
30
+ if (cur == null || typeof cur !== "object") return undefined;
31
+ cur = cur[p];
32
+ }
33
+ return cur != null ? String(cur) : undefined;
34
+ }
35
+
36
+ export function normalizeUserEnvRows(rawRows) {
37
+ const rows = Array.isArray(rawRows) ? rawRows : [];
38
+ const byKey = new Map();
39
+ for (const item of rows) {
40
+ if (!item || typeof item !== "object") continue;
41
+ const key = normalizeEnvKey(item.key);
42
+ if (!key || !isValidEnvKey(key)) continue;
43
+ byKey.set(key, String(item.value ?? ""));
44
+ }
45
+ return Array.from(byKey.entries())
46
+ .sort(([a], [b]) => a.localeCompare(b))
47
+ .map(([key, value]) => ({ key, value }));
48
+ }
49
+
50
+ export function readUserEnvRows(userId) {
51
+ const data = readJsonObject(getAgentflowUserEnvAbs(userId));
52
+ return normalizeUserEnvRows(Array.isArray(data.env) ? data.env : []);
53
+ }
54
+
55
+ export function readUserEnvObject(userId) {
56
+ const out = {};
57
+ for (const row of readUserEnvRows(userId)) {
58
+ out[row.key] = row.value;
59
+ }
60
+ return out;
61
+ }
62
+
63
+ export function writeUserEnvRows(userId, rows) {
64
+ const normalized = normalizeUserEnvRows(rows);
65
+ const safeUserId = sanitizeAgentflowUserId(userId);
66
+ const filePath = getAgentflowUserEnvAbs(safeUserId);
67
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
68
+ fs.writeFileSync(filePath, JSON.stringify({ version: 1, env: normalized }, null, 2) + "\n", "utf-8");
69
+ return normalized;
70
+ }
71
+
72
+ export function resolveUserEnvValue(key, userId) {
73
+ const keyStr = normalizeEnvKey(key);
74
+ if (!keyStr) return "";
75
+ const userEnv = readUserEnvObject(userId);
76
+ if (Object.prototype.hasOwnProperty.call(userEnv, keyStr)) return String(userEnv[keyStr] ?? "");
77
+ const processValue = process.env[keyStr];
78
+ if (processValue != null && processValue !== "") return String(processValue);
79
+ const configPath = path.join(os.homedir(), ".cursor", "config.json");
80
+ const fromConfig = getFromConfig(readJsonObject(configPath), keyStr);
81
+ return fromConfig !== undefined ? fromConfig : "";
82
+ }
83
+
@@ -221,13 +221,14 @@ function readFilesRecursive(dir, baseDir, maxDepth = 2, currentDepth = 0) {
221
221
  }
222
222
  }
223
223
 
224
- export function getPipelineFiles(workspaceRoot, flowId, flowSource, archived = false) {
224
+ export function getPipelineFiles(workspaceRoot, flowId, flowSource, archived = false, opts = {}) {
225
225
  const root = path.resolve(workspaceRoot);
226
226
  let pipelineDir = null;
227
+ const userPipelinesRoot = getUserPipelinesRoot(opts.userId);
227
228
 
228
229
  if (archived) {
229
230
  if (flowSource === "user") {
230
- pipelineDir = path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId);
231
+ pipelineDir = path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId);
231
232
  } else if (flowSource === "workspace") {
232
233
  pipelineDir = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId);
233
234
  if (!fs.existsSync(pipelineDir)) {
@@ -239,7 +240,7 @@ export function getPipelineFiles(workspaceRoot, flowId, flowSource, archived = f
239
240
  if (flowSource === "builtin") {
240
241
  pipelineDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
241
242
  } else if (flowSource === "user") {
242
- pipelineDir = path.join(getUserPipelinesRoot(), flowId);
243
+ pipelineDir = path.join(userPipelinesRoot, flowId);
243
244
  if (!fs.existsSync(pipelineDir)) {
244
245
  const alt = path.join(root, PIPELINES_DIR, flowId);
245
246
  if (fs.existsSync(alt)) pipelineDir = alt;
@@ -9,6 +9,7 @@ import {
9
9
  getReferenceRootAbs,
10
10
  getWorkspaceRunBuildRoot,
11
11
  getUserPipelinesRoot,
12
+ resolveUniqueUserPipelineDir,
12
13
  ARCHIVED_PIPELINES_DIR_NAME,
13
14
  } from "./paths.mjs";
14
15
 
@@ -23,7 +24,7 @@ export { getRunDir } from "./paths.mjs";
23
24
  * - legacyUserRoot: ~/agentflow/runBuild/<name>/<uuid>
24
25
  * 后两者仅作为向下兼容读取;新写入只走 user/workspace。
25
26
  */
26
- export function listAllRunDirs(workspaceRoot) {
27
+ export function listAllRunDirs(workspaceRoot, opts = {}) {
27
28
  const root = path.resolve(workspaceRoot);
28
29
  const out = [];
29
30
  const seen = new Set();
@@ -62,7 +63,7 @@ export function listAllRunDirs(workspaceRoot) {
62
63
  };
63
64
 
64
65
  // 新位置(优先)
65
- scanPipelinesDir(getUserPipelinesRoot(), "user");
66
+ scanPipelinesDir(getUserPipelinesRoot(opts.userId), "user");
66
67
  scanPipelinesDir(path.join(root, PIPELINES_DIR), "workspace");
67
68
 
68
69
  // 旧位置(兼容读)
@@ -138,25 +139,22 @@ export function listRunsWithLogs(workspaceRoot) {
138
139
  return list;
139
140
  }
140
141
 
141
- /** 解析 flow 目录:~/agentflow/pipelines.workspace/agentflow/pipelines.cursor/agentflow/pipelines(旧)→ builtin/pipelines */
142
- export function getFlowDir(workspaceRoot, flowName) {
142
+ /** 解析活跃 flow 目录:user → workspace → legacy workspace → builtin。归档 flow 必须走显式 archived resolver。 */
143
+ export function getFlowDir(workspaceRoot, flowName, opts = {}) {
143
144
  const root = path.resolve(workspaceRoot);
144
145
  const hasFlow = (dir) => fs.existsSync(dir) && fs.existsSync(path.join(dir, "flow.yaml"));
145
146
 
146
147
  // user pipelines
147
- const userRoot = getUserPipelinesRoot();
148
+ const userRoot = getUserPipelinesRoot(opts.userId);
148
149
  const userFlowDir = path.join(userRoot, flowName);
149
150
  if (hasFlow(userFlowDir)) return userFlowDir;
150
- // user archived
151
- const userArchivedDir = path.join(userRoot, ARCHIVED_PIPELINES_DIR_NAME, flowName);
152
- if (hasFlow(userArchivedDir)) return userArchivedDir;
151
+
152
+ const inferredUserFlowDir = resolveUniqueUserPipelineDir(flowName);
153
+ if (inferredUserFlowDir) return inferredUserFlowDir;
153
154
 
154
155
  // workspace pipelines
155
156
  const wsFlowDir = path.join(root, PIPELINES_DIR, flowName);
156
157
  if (hasFlow(wsFlowDir)) return wsFlowDir;
157
- // workspace archived
158
- const wsArchivedDir = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowName);
159
- if (hasFlow(wsArchivedDir)) return wsArchivedDir;
160
158
 
161
159
  // legacy
162
160
  const legacyFlowDir = path.join(root, LEGACY_PIPELINES_DIR, flowName);
@@ -16,6 +16,7 @@ import { loadFlowDefinition } from "./parse-flow.mjs";
16
16
  import { getResolvedValues, getOutputPathForSlot } from "./get-resolved-values.mjs";
17
17
  import { loadExecId } from "./get-exec-id.mjs";
18
18
  import { intermediatePromptBasename, intermediateDirForNode } from "./get-exec-id.mjs";
19
+ import { normalizeSkillsContext, normalizeWorkspaceContext, renderSkillsContextForPrompt } from "../lib/runtime-context.mjs";
19
20
 
20
21
  function shellQuote(s) {
21
22
  if (s == null) return "''";
@@ -116,7 +117,7 @@ function marketplaceRuntimeCommand(marketplaceNode, resolvedInputs, resolvedOutp
116
117
  * @param {number} [execId] - 本轮 execId,缺省则从 memory 读取
117
118
  * @returns {{ ok: boolean, promptPath?: string, nodeContext?: string, taskBody?: string, error?: string }}
118
119
  */
119
- export function buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execId) {
120
+ export function buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execId, opts = {}) {
120
121
  const runDir = getRunDir(workspaceRoot, flowName, uuid);
121
122
  const flowJsonPath = path.join(runDir, "intermediate", "flow.json");
122
123
  let flowDir = getFlowDir(workspaceRoot, flowName) || path.join(workspaceRoot, PIPELINES_DIR, flowName);
@@ -154,7 +155,26 @@ export function buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execI
154
155
  : "";
155
156
 
156
157
  const { resolvedInputs = {}, resolvedOutputs = {}, systemPrompt = "" } = data;
157
- const resolveOpts = { instanceId, currentExecId: e, runDir, workspaceRoot };
158
+ const workspaceContext = normalizeWorkspaceContext(opts.workspaceContext || resolvedInputs.workspaceContext, workspaceRoot, flowName, { flowDir });
159
+ const skillsContext = normalizeSkillsContext(opts.skillsContext || resolvedInputs.skillsContext);
160
+ if (workspaceContext?.workspaceRoot) {
161
+ resolvedInputs.workspaceRoot = workspaceContext.workspaceRoot;
162
+ resolvedInputs.cwd = workspaceContext.cwd || workspaceContext.workspaceRoot;
163
+ resolvedInputs.pipelineWorkspace = workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot);
164
+ }
165
+ const resolveOpts = {
166
+ instanceId,
167
+ currentExecId: e,
168
+ runDir,
169
+ workspaceRoot: workspaceContext?.workspaceRoot || workspaceRoot,
170
+ extra: {
171
+ workspaceRoot: workspaceContext?.workspaceRoot || path.resolve(workspaceRoot),
172
+ cwd: workspaceContext?.cwd || workspaceContext?.workspaceRoot || path.resolve(workspaceRoot),
173
+ pipelineWorkspace: workspaceContext?.pipelineWorkspace || path.resolve(workspaceRoot),
174
+ flowDir: path.resolve(flowDir),
175
+ runDir,
176
+ },
177
+ };
158
178
  const taskBody = resolvePlaceholdersInText(
159
179
  instanceBody,
160
180
  resolvedInputs,
@@ -168,9 +188,12 @@ export function buildNodePrompt(workspaceRoot, flowName, uuid, instanceId, execI
168
188
  ? marketplaceRuntimeCommand(marketplaceNode, resolvedInputs, resolvedOutputs, resolveOpts)
169
189
  : "";
170
190
 
191
+ const skillsPrompt = renderSkillsContextForPrompt(skillsContext);
192
+ const contextBlocks = [systemPrompt || "(无)", skillsPrompt].filter((x) => x && String(x).trim());
193
+
171
194
  const content = `## 节点上下文
172
195
 
173
- ${systemPrompt || "(无)"}
196
+ ${contextBlocks.join("\n\n")}
174
197
 
175
198
  ## 执行任务
176
199
 
@@ -191,9 +214,11 @@ ${taskBody || "(无)"}
191
214
  return {
192
215
  ok: true,
193
216
  promptPath: relativePath.replace(/\\/g, "/"),
194
- nodeContext: systemPrompt || "",
217
+ nodeContext: contextBlocks.join("\n\n") || "",
195
218
  taskBody: taskBody || "",
196
219
  script: resolvedScript || "",
220
+ workspaceContext,
221
+ skillsContext,
197
222
  };
198
223
  }
199
224
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * apply -ai get-env:按 key 从系统环境变量与 ~/.cursor/config.json 读取 value。
4
- * 优先级:先查 process.env[key],若无则查 ~/.cursor/config.json(支持点号路径如 openai.apiKey)。
3
+ * apply -ai get-env:按 key 从用户私有 env、系统环境变量与 ~/.cursor/config.json 读取 value。
4
+ * 优先级:先查当前 AGENTFLOW_USER_ID 的私有 env,再查 process.env[key],最后查 ~/.cursor/config.json(支持点号路径如 openai.apiKey)。
5
5
  *
6
6
  * 用法(apply 步骤,由 CLI 调用):
7
7
  * agentflow apply -ai get-env <workspaceRoot> <flowName> <uuid> <instanceId> <execId> <key>
@@ -15,39 +15,15 @@
15
15
 
16
16
  import fs from "fs";
17
17
  import path from "path";
18
- import os from "os";
19
18
 
20
19
  import { getRunDir } from "../lib/paths.mjs";
20
+ import { resolveUserEnvValue } from "../lib/user-env.mjs";
21
21
  import { writeResult } from "./write-result.mjs";
22
22
  import { outputDirForNode, outputNodeBasename } from "./get-exec-id.mjs";
23
23
 
24
- function getFromConfig(config, keyStr) {
25
- if (!config || typeof config !== "object" || !keyStr) return undefined;
26
- const parts = String(keyStr).trim().split(".");
27
- let cur = config;
28
- for (const p of parts) {
29
- if (cur == null || typeof cur !== "object") return undefined;
30
- cur = cur[p];
31
- }
32
- return cur != null ? String(cur) : undefined;
33
- }
34
-
35
24
  function resolveValue(keyStr) {
36
- let value = "";
37
- if (!keyStr) return value;
38
- value = process.env[keyStr] ?? "";
39
- if (value === "") {
40
- const configPath = path.join(os.homedir(), ".cursor", "config.json");
41
- if (fs.existsSync(configPath)) {
42
- try {
43
- const raw = fs.readFileSync(configPath, "utf-8");
44
- const config = JSON.parse(raw);
45
- const fromConfig = getFromConfig(config, keyStr);
46
- if (fromConfig !== undefined) value = fromConfig;
47
- } catch (_) {}
48
- }
49
- }
50
- return value;
25
+ if (!keyStr) return "";
26
+ return resolveUserEnvValue(keyStr, process.env.AGENTFLOW_USER_ID || "");
51
27
  }
52
28
 
53
29
  function main() {
@@ -53,8 +53,8 @@ export function loadExecId(workspaceRoot, flowName, uuid, instanceId) {
53
53
  /**
54
54
  * 从 memory 读取所有 order 中节点的 execId。
55
55
  */
56
- export function loadAllExecIds(workspaceRoot, flowName, uuid, order) {
57
- const runDir = getRunDir(workspaceRoot, flowName, uuid);
56
+ export function loadAllExecIds(workspaceRoot, flowName, uuid, order, opts = {}) {
57
+ const runDir = getRunDir(workspaceRoot, flowName, uuid, opts);
58
58
  const memoryPath = path.join(runDir, MEMORY_FILENAME);
59
59
  const map = fs.existsSync(memoryPath)
60
60
  ? parseMemory(fs.readFileSync(memoryPath, "utf-8"))
@@ -233,6 +233,7 @@ export function getResolvedValues(workspaceRoot, flowName, uuid, instanceId) {
233
233
  // 运行时常量放在后面,确保不会被 input 槽位的空值覆盖
234
234
  const runtimeConstants = {
235
235
  workspaceRoot: path.resolve(workspaceRoot),
236
+ pipelineWorkspace: path.resolve(workspaceRoot),
236
237
  flowName,
237
238
  runDir: runDirRel,
238
239
  flowDir: path.resolve(flowDir),