@fieldwangai/agentflow 0.1.30 → 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.
- package/bin/lib/agent-runners.mjs +26 -2
- package/bin/lib/api-runner.mjs +26 -3
- package/bin/lib/apply.mjs +6 -5
- package/bin/lib/catalog-flows.mjs +4 -1
- package/bin/lib/composer-agent.mjs +2 -1
- package/bin/lib/locales/en.json +4 -0
- package/bin/lib/locales/zh.json +6 -2
- package/bin/lib/node-execute.mjs +1 -1
- package/bin/lib/paths.mjs +5 -0
- package/bin/lib/scheduler.mjs +3 -2
- package/bin/lib/ui-server.mjs +621 -6
- package/bin/lib/user-env.mjs +83 -0
- package/bin/pipeline/get-env.mjs +5 -29
- package/bin/pipeline/pre-process-node.mjs +28 -6
- package/bin/pipeline/run-tool-nodejs.mjs +7 -0
- package/builtin/nodes/agent_subAgent.md +6 -3
- package/builtin/nodes/control_cd_workspace.md +8 -6
- package/builtin/nodes/control_load_skills.md +2 -0
- package/builtin/nodes/control_user_workspace.md +20 -0
- package/builtin/nodes/display_ascii.md +5 -0
- package/builtin/nodes/display_markdown.md +5 -0
- package/builtin/nodes/display_mermaid.md +5 -0
- package/builtin/nodes/tool_git_checkout.md +3 -0
- package/builtin/web-ui/dist/assets/index-BVWwQpvg.css +1 -0
- package/builtin/web-ui/dist/assets/index-CvNy1n3f.js +197 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +42 -0
- package/skills/agentflow-workspace-graph/SKILL.md +67 -0
- package/skills/agentflow-workspace-markdown/SKILL.md +44 -0
- package/skills/agentflow-workspace-mermaid/SKILL.md +43 -0
- package/builtin/web-ui/dist/assets/index-NdVOJLL9.js +0 -196
- 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
|
+
|
package/bin/pipeline/get-env.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* apply -ai get-env:按 key
|
|
4
|
-
*
|
|
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
|
-
|
|
37
|
-
|
|
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.
|
|
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:
|
|
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 === "
|
|
992
|
-
?
|
|
993
|
-
:
|
|
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
|
-
#
|
|
3
|
-
description:
|
|
4
|
-
displayName:
|
|
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
|
|
8
|
-
- `push`: switch to
|
|
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
|
-
`
|
|
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:
|
|
19
|
-
default: "
|
|
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 `${
|
|
45
|
+
Switch downstream execution to `${path}` using mode `${mode}`.
|
|
@@ -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.
|