@fieldwangai/agentflow 0.1.30 → 0.1.32

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 (34) hide show
  1. package/bin/lib/agent-runners.mjs +26 -2
  2. package/bin/lib/api-runner.mjs +26 -3
  3. package/bin/lib/apply.mjs +6 -5
  4. package/bin/lib/catalog-flows.mjs +30 -5
  5. package/bin/lib/composer-agent.mjs +2 -1
  6. package/bin/lib/locales/en.json +4 -0
  7. package/bin/lib/locales/zh.json +6 -2
  8. package/bin/lib/marketplace.mjs +124 -2
  9. package/bin/lib/node-execute.mjs +1 -1
  10. package/bin/lib/paths.mjs +5 -0
  11. package/bin/lib/scheduler.mjs +3 -2
  12. package/bin/lib/ui-server.mjs +639 -8
  13. package/bin/lib/user-env.mjs +83 -0
  14. package/bin/pipeline/get-env.mjs +5 -29
  15. package/bin/pipeline/pre-process-node.mjs +28 -6
  16. package/bin/pipeline/run-tool-nodejs.mjs +7 -0
  17. package/builtin/nodes/agent_subAgent.md +6 -3
  18. package/builtin/nodes/control_cd_workspace.md +8 -6
  19. package/builtin/nodes/control_load_skills.md +2 -0
  20. package/builtin/nodes/control_user_workspace.md +20 -0
  21. package/builtin/nodes/display_ascii.md +5 -0
  22. package/builtin/nodes/display_markdown.md +5 -0
  23. package/builtin/nodes/display_mermaid.md +5 -0
  24. package/builtin/nodes/tool_git_checkout.md +3 -0
  25. package/builtin/web-ui/dist/assets/index-D0Tkhqr6.css +1 -0
  26. package/builtin/web-ui/dist/assets/index-DyhW5chp.js +197 -0
  27. package/builtin/web-ui/dist/index.html +2 -2
  28. package/package.json +1 -1
  29. package/skills/agentflow-workspace-ascii/SKILL.md +42 -0
  30. package/skills/agentflow-workspace-graph/SKILL.md +67 -0
  31. package/skills/agentflow-workspace-markdown/SKILL.md +44 -0
  32. package/skills/agentflow-workspace-mermaid/SKILL.md +43 -0
  33. package/builtin/web-ui/dist/assets/index-NdVOJLL9.js +0 -196
  34. package/builtin/web-ui/dist/assets/index-naVI6LZj.css +0 -1
@@ -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
+
@@ -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() {
@@ -24,6 +24,7 @@
24
24
 
25
25
  import { spawnSync } from "child_process";
26
26
  import fs from "fs";
27
+ import os from "os";
27
28
  import path from "path";
28
29
  import { fileURLToPath } from "url";
29
30
 
@@ -337,9 +338,9 @@ function emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
337
338
  if (mode === "pop") {
338
339
  next = normalizeWorkspaceContext(workspaceContext.previous, workspaceRoot, flowName);
339
340
  } else {
340
- const target = resolveWorkspaceTarget(inputs.target || inputs.path || inputs.repoPath || "", workspaceContext);
341
+ const target = resolveWorkspaceTarget(inputs.path || inputs.target || inputs.repoPath || "", workspaceContext);
341
342
  if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {
342
- throw new Error(`control_cd_workspace: target directory not found: ${target}`);
343
+ throw new Error(`control_cd_workspace: path directory not found: ${target}`);
343
344
  }
344
345
  next = {
345
346
  version: 1,
@@ -358,6 +359,25 @@ function emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
358
359
  return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "cd-workspace", `Workspace context switched to: ${next.cwd}\n`);
359
360
  }
360
361
 
362
+ function emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) {
363
+ const runDir = getRunDir(workspaceRoot, flowName, uuid);
364
+ const { workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
365
+ const homeDir = path.resolve(os.homedir());
366
+ const next = {
367
+ version: 1,
368
+ label: "home",
369
+ cwd: homeDir,
370
+ workspaceRoot: homeDir,
371
+ pipelineWorkspace: workspaceContext.pipelineWorkspace || path.resolve(workspaceRoot),
372
+ flowDir: workspaceContext.flowDir,
373
+ previous: workspaceContext,
374
+ };
375
+ writeOutputSlot(runDir, instanceId, execId, "workspaceContext", JSON.stringify(next));
376
+ writeOutputSlot(runDir, instanceId, execId, "cwd", next.cwd);
377
+ writeResult(workspaceRoot, flowName, uuid, instanceId, { status: "success", message: `user workspace: ${homeDir}` }, { execId });
378
+ return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "user-workspace", `Workspace context switched to user home: ${homeDir}\n`);
379
+ }
380
+
361
381
  function emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId) {
362
382
  const runDir = getRunDir(workspaceRoot, flowName, uuid);
363
383
  const { inputs, workspaceContext, skillsContext: existingSkills } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
@@ -981,16 +1001,18 @@ function main() {
981
1001
  return;
982
1002
  }
983
1003
 
984
- if (definitionId === "tool_git_checkout" || definitionId === "control_cd_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
1004
+ if (definitionId === "tool_git_checkout" || definitionId === "control_cd_workspace" || definitionId === "control_user_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
985
1005
  try {
986
1006
  const promptPath =
987
1007
  definitionId === "tool_git_checkout"
988
1008
  ? emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel)
989
1009
  : definitionId === "control_cd_workspace"
990
1010
  ? emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
991
- : definitionId === "control_load_skills"
992
- ? emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId)
993
- : emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId);
1011
+ : definitionId === "control_user_workspace"
1012
+ ? emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1013
+ : definitionId === "control_load_skills"
1014
+ ? emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId)
1015
+ : emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId);
994
1016
  writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
995
1017
  logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "runtime-context-node", instanceId, definitionId });
996
1018
  console.log(JSON.stringify({
@@ -19,6 +19,7 @@ import path from "path";
19
19
  import { fileURLToPath } from "url";
20
20
 
21
21
  import { getRunDir } from "../lib/paths.mjs";
22
+ import { readUserEnvObject } from "../lib/user-env.mjs";
22
23
  import { validateAndParse } from "./validate-script-output.mjs";
23
24
  import { writeResult } from "./write-result.mjs";
24
25
  import { loadExecId, outputNodeBasename, outputDirForNode } from "./get-exec-id.mjs";
@@ -29,6 +30,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
30
  const MAX_RETRIES = 3;
30
31
  const RETRY_DELAY_MS = 1000;
31
32
 
33
+ function runtimeEnv() {
34
+ return { ...process.env, ...readUserEnvObject(process.env.AGENTFLOW_USER_ID || "") };
35
+ }
36
+
32
37
  function runOnce(workspaceRoot, flowName, uuid, instanceId, execId, scriptArgs) {
33
38
  const runDir = getRunDir(workspaceRoot, flowName, uuid);
34
39
  const outputDir = path.join(runDir, outputDirForNode(instanceId));
@@ -41,11 +46,13 @@ function runOnce(workspaceRoot, flowName, uuid, instanceId, execId, scriptArgs)
41
46
  cwd: workspaceRoot,
42
47
  shell: false,
43
48
  stdio: ["inherit", "pipe", "pipe"],
49
+ env: runtimeEnv(),
44
50
  })
45
51
  : spawnSync(normalizedCmd, [], {
46
52
  cwd: workspaceRoot,
47
53
  shell: true,
48
54
  stdio: ["inherit", "pipe", "pipe"],
55
+ env: runtimeEnv(),
49
56
  });
50
57
 
51
58
  const stdout = child.stdout?.toString("utf-8") ?? "";
@@ -1,7 +1,7 @@
1
1
  ---
2
- # 内置节点:SubAgent
3
- description: 利用 SubAgent 执行任务;可接收 workspaceContext 切换执行工作区,并接收 skillsContext 注入已加载 skills。
4
- displayName: SubAgent
2
+ # 内置节点:子 Agent
3
+ description: 利用子 Agent 执行任务;可接收 workspaceContext 切换执行工作区,并接收 skillsContext 注入已加载 skills。
4
+ displayName: 子 Agent
5
5
  input:
6
6
  - type: node
7
7
  name: prev
@@ -16,5 +16,8 @@ output:
16
16
  - type: node
17
17
  name: next
18
18
  default: ""
19
+ - type: text
20
+ name: result
21
+ default: ""
19
22
  ---
20
23
  ${USER_PROMPT}
@@ -4,19 +4,21 @@ description: |
4
4
  Switch the runtime workspace context for downstream nodes without changing the AgentFlow pipeline workspace.
5
5
 
6
6
  Modes:
7
- - `set`: switch to target, keep previous stack unchanged.
8
- - `push`: switch to target and save the incoming context as previous.
7
+ - `set`: switch to path, keep previous stack unchanged.
8
+ - `push`: switch to path and save the incoming context as previous.
9
9
  - `pop`: restore the previous context.
10
10
 
11
- `target` supports `${workspaceRoot}`, `${pipelineWorkspace}`, `${flowDir}`, absolute paths, and paths relative to current workspace context.
11
+ `path` supports `${workspaceRoot}`, `${pipelineWorkspace}`, `${flowDir}`, absolute paths, and paths relative to the input workspace context.
12
12
  displayName: CD Workspace
13
13
  input:
14
14
  - type: node
15
15
  name: prev
16
16
  default: ""
17
17
  - type: text
18
- name: target
19
- default: "${pipelineWorkspace}"
18
+ name: path
19
+ default: ""
20
+ required: true
21
+ showOnNode: true
20
22
  - type: text
21
23
  name: mode
22
24
  default: "set"
@@ -40,4 +42,4 @@ output:
40
42
  name: previous
41
43
  default: ""
42
44
  ---
43
- Switch downstream execution to `${target}` using mode `${mode}`.
45
+ Switch downstream execution to `${path}` using mode `${mode}`.
@@ -22,6 +22,8 @@ input:
22
22
  - type: text
23
23
  name: skillKeys
24
24
  default: ""
25
+ required: true
26
+ showOnNode: true
25
27
  - type: text
26
28
  name: mergeMode
27
29
  default: "replace"
@@ -0,0 +1,20 @@
1
+ ---
2
+ # Built-in node: User Workspace
3
+ description: Output a workspace context pointing to the current user's home directory.
4
+ displayName: User Workspace
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ output:
10
+ - type: node
11
+ name: next
12
+ default: ""
13
+ - type: text
14
+ name: workspaceContext
15
+ default: ""
16
+ - type: file
17
+ name: cwd
18
+ default: ""
19
+ ---
20
+ Use the current user's home directory as downstream workspace context.
@@ -9,9 +9,14 @@ input:
9
9
  - type: text
10
10
  name: content
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: text
14
16
  name: content
15
17
  default: ""
18
+ - type: node
19
+ name: next
20
+ default: ""
16
21
  ---
17
22
  ${content}
@@ -9,9 +9,14 @@ input:
9
9
  - type: text
10
10
  name: content
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: text
14
16
  name: content
15
17
  default: ""
18
+ - type: node
19
+ name: next
20
+ default: ""
16
21
  ---
17
22
  ${content}
@@ -9,9 +9,14 @@ input:
9
9
  - type: text
10
10
  name: content
11
11
  default: ""
12
+ required: true
13
+ showOnNode: true
12
14
  output:
13
15
  - type: text
14
16
  name: content
15
17
  default: ""
18
+ - type: node
19
+ name: next
20
+ default: ""
16
21
  ---
17
22
  ${content}
@@ -16,9 +16,12 @@ input:
16
16
  - type: text
17
17
  name: repoUrl
18
18
  default: ""
19
+ required: true
20
+ showOnNode: true
19
21
  - type: text
20
22
  name: branch
21
23
  default: ""
24
+ showOnNode: true
22
25
  - type: text
23
26
  name: targetDir
24
27
  default: ""