@fieldwangai/agentflow 0.1.29 → 0.1.30
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/agents/agentflow-node-executor-code.md +3 -2
- package/agents/agentflow-node-executor-planning.md +3 -2
- package/agents/agentflow-node-executor-requirement.md +3 -2
- package/agents/agentflow-node-executor-test.md +3 -2
- package/agents/agentflow-node-executor-ui.md +3 -2
- package/agents/agentflow-node-executor.md +3 -2
- package/agents/en/agentflow-node-executor.md +3 -2
- package/agents/zh/agentflow-node-executor.md +3 -2
- package/bin/lib/agent-runners.mjs +38 -13
- package/bin/lib/api-runner.mjs +6 -3
- package/bin/lib/auth.mjs +240 -0
- package/bin/lib/catalog-agents.mjs +2 -2
- package/bin/lib/catalog-flows.mjs +192 -16
- package/bin/lib/composer-agent.mjs +21 -1
- package/bin/lib/composer-skill-router.mjs +10 -78
- package/bin/lib/flow-import.mjs +2 -2
- package/bin/lib/flow-write.mjs +20 -20
- package/bin/lib/help.mjs +2 -2
- package/bin/lib/locales/en.json +25 -1
- package/bin/lib/locales/zh.json +25 -1
- package/bin/lib/main.mjs +6 -1
- package/bin/lib/node-exec-context.mjs +5 -5
- package/bin/lib/node-execute.mjs +14 -9
- package/bin/lib/paths.mjs +64 -13
- package/bin/lib/recent-runs.mjs +2 -2
- package/bin/lib/run-node-statuses-from-disk.mjs +3 -3
- package/bin/lib/runtime-context.mjs +225 -0
- package/bin/lib/scheduler.mjs +41 -38
- package/bin/lib/skill-registry.mjs +145 -0
- package/bin/lib/ui-server.mjs +902 -57
- package/bin/lib/workspace-tree.mjs +4 -3
- package/bin/lib/workspace.mjs +9 -11
- package/bin/pipeline/build-node-prompt.mjs +29 -4
- package/bin/pipeline/get-exec-id.mjs +2 -2
- package/bin/pipeline/get-resolved-values.mjs +1 -0
- package/bin/pipeline/pre-process-node.mjs +306 -6
- package/bin/pipeline/validate-flow.mjs +2 -0
- package/builtin/nodes/agent_subAgent.md +7 -1
- package/builtin/nodes/control_cd_workspace.md +43 -0
- package/builtin/nodes/control_load_skills.md +48 -0
- package/builtin/nodes/display_ascii.md +17 -0
- package/builtin/nodes/display_markdown.md +17 -0
- package/builtin/nodes/display_mermaid.md +17 -0
- package/builtin/nodes/tool_git_checkout.md +54 -0
- package/builtin/nodes/tool_nodejs.md +8 -1
- package/builtin/nodes/tool_print.md +4 -1
- package/builtin/web-ui/dist/assets/index-NdVOJLL9.js +196 -0
- package/builtin/web-ui/dist/assets/index-naVI6LZj.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-flow-recipes/SKILL.md +24 -0
- package/skills/agentflow-flow-recipes/references/recipes.md +63 -0
- package/skills/agentflow-node-reference/SKILL.md +25 -0
- package/skills/agentflow-node-reference/references/builtin-nodes.md +210 -0
- package/skills/agentflow-placeholder-reference/SKILL.md +24 -0
- package/skills/agentflow-placeholder-reference/references/placeholders.md +20 -0
- package/skills/agentflow-runtime-reference/SKILL.md +25 -0
- package/skills/agentflow-runtime-reference/references/runtime.md +64 -0
- package/builtin/web-ui/dist/assets/index-0vJxkTJz.css +0 -1
- package/builtin/web-ui/dist/assets/index-h69bpxLI.js +0 -190
|
@@ -50,7 +50,7 @@ export function readPipelineListDescription(flowDir) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
export function listFlowsJson(workspaceRoot) {
|
|
53
|
+
export function listFlowsJson(workspaceRoot, opts = {}) {
|
|
54
54
|
const root = path.resolve(workspaceRoot);
|
|
55
55
|
const out = [];
|
|
56
56
|
const fromBuiltin = collectPipelineNamesFromDir(PACKAGE_BUILTIN_PIPELINES_DIR);
|
|
@@ -59,7 +59,7 @@ export function listFlowsJson(workspaceRoot) {
|
|
|
59
59
|
const description = readPipelineListDescription(dir);
|
|
60
60
|
out.push({ id: name, path: dir, source: "builtin", ...(description ? { description } : {}) });
|
|
61
61
|
}
|
|
62
|
-
const userPipelinesRoot = getUserPipelinesRoot();
|
|
62
|
+
const userPipelinesRoot = getUserPipelinesRoot(opts.userId);
|
|
63
63
|
const fromUserData = collectPipelineNamesFromDir(userPipelinesRoot);
|
|
64
64
|
for (const name of fromUserData) {
|
|
65
65
|
if (name === ARCHIVED_PIPELINES_DIR_NAME) continue;
|
|
@@ -200,6 +200,7 @@ export function parseNodeFrontmatter(raw) {
|
|
|
200
200
|
export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
201
201
|
const root = path.resolve(workspaceRoot);
|
|
202
202
|
const archived = Boolean(opts.archived);
|
|
203
|
+
const userPipelinesRoot = getUserPipelinesRoot(opts.userId);
|
|
203
204
|
const byId = new Map();
|
|
204
205
|
const pipelineTranslations = {};
|
|
205
206
|
let marketplaceFlowData = null;
|
|
@@ -293,13 +294,9 @@ export function listNodesJson(workspaceRoot, flowId, flowSource, opts = {}) {
|
|
|
293
294
|
} catch (_) {}
|
|
294
295
|
} else if (flowSource === "user") {
|
|
295
296
|
if (archived) {
|
|
296
|
-
addFromDir(
|
|
297
|
-
path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes"),
|
|
298
|
-
"flow",
|
|
299
|
-
flowId,
|
|
300
|
-
);
|
|
297
|
+
addFromDir(path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes"), "flow", flowId);
|
|
301
298
|
} else {
|
|
302
|
-
addFromDir(path.join(
|
|
299
|
+
addFromDir(path.join(userPipelinesRoot, flowId, "nodes"), "flow", flowId);
|
|
303
300
|
addFromDir(path.join(root, PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
|
|
304
301
|
addFromDir(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes"), "flow", flowId);
|
|
305
302
|
}
|
|
@@ -342,13 +339,14 @@ export function printNodesTable(list) {
|
|
|
342
339
|
export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
|
|
343
340
|
const root = path.resolve(workspaceRoot);
|
|
344
341
|
const archived = Boolean(options.archived);
|
|
342
|
+
const userPipelinesRoot = getUserPipelinesRoot(options.userId);
|
|
345
343
|
let flowDir;
|
|
346
344
|
if (archived) {
|
|
347
345
|
if (flowSource === "builtin") {
|
|
348
346
|
return { error: t("catalog.builtin_flow_archive_not_supported") };
|
|
349
347
|
}
|
|
350
348
|
if (flowSource === "user") {
|
|
351
|
-
flowDir = path.join(
|
|
349
|
+
flowDir = path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId);
|
|
352
350
|
} else if (flowSource === "workspace") {
|
|
353
351
|
flowDir = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId);
|
|
354
352
|
} else {
|
|
@@ -377,7 +375,7 @@ export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
|
|
|
377
375
|
if (flowSource === "builtin") {
|
|
378
376
|
flowDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
|
|
379
377
|
} else if (flowSource === "user") {
|
|
380
|
-
flowDir = path.join(
|
|
378
|
+
flowDir = path.join(userPipelinesRoot, flowId);
|
|
381
379
|
} else if (flowSource === "workspace") {
|
|
382
380
|
flowDir = path.join(root, PIPELINES_DIR, flowId);
|
|
383
381
|
} else {
|
|
@@ -422,13 +420,14 @@ export function readFlowJson(workspaceRoot, flowId, flowSource, options = {}) {
|
|
|
422
420
|
export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {}) {
|
|
423
421
|
const root = path.resolve(workspaceRoot);
|
|
424
422
|
const archived = Boolean(options.archived);
|
|
423
|
+
const userPipelinesRoot = getUserPipelinesRoot(options.userId);
|
|
425
424
|
let yamlPath;
|
|
426
425
|
if (archived) {
|
|
427
426
|
if (flowSource === "builtin") {
|
|
428
427
|
return { error: t("catalog.builtin_flow_archive_path_not_supported") };
|
|
429
428
|
}
|
|
430
429
|
if (flowSource === "user") {
|
|
431
|
-
yamlPath = path.join(
|
|
430
|
+
yamlPath = path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
|
|
432
431
|
} else if (flowSource === "workspace") {
|
|
433
432
|
yamlPath = path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "flow.yaml");
|
|
434
433
|
if (!fs.existsSync(yamlPath)) {
|
|
@@ -447,7 +446,7 @@ export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {})
|
|
|
447
446
|
if (flowSource === "builtin") {
|
|
448
447
|
yamlPath = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "flow.yaml");
|
|
449
448
|
} else if (flowSource === "user") {
|
|
450
|
-
yamlPath = path.join(
|
|
449
|
+
yamlPath = path.join(userPipelinesRoot, flowId, "flow.yaml");
|
|
451
450
|
if (!fs.existsSync(yamlPath)) {
|
|
452
451
|
const alt = path.join(root, PIPELINES_DIR, flowId, "flow.yaml");
|
|
453
452
|
if (fs.existsSync(alt)) yamlPath = alt;
|
|
@@ -474,6 +473,7 @@ export function getFlowYamlAbs(workspaceRoot, flowId, flowSource, options = {})
|
|
|
474
473
|
export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {}) {
|
|
475
474
|
const root = path.resolve(workspaceRoot);
|
|
476
475
|
const archived = Boolean(opts.archived);
|
|
476
|
+
const userPipelinesRoot = getUserPipelinesRoot(opts.userId);
|
|
477
477
|
const marketSpec = parseMarketplaceDefinitionId(nodeId);
|
|
478
478
|
if (marketSpec) {
|
|
479
479
|
let flowDir = root;
|
|
@@ -515,11 +515,9 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
|
|
|
515
515
|
pathsToTry.push(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
516
516
|
} else if (flowSource === "user") {
|
|
517
517
|
if (archived) {
|
|
518
|
-
pathsToTry.push(
|
|
519
|
-
path.join(getUserPipelinesRoot(), ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName),
|
|
520
|
-
);
|
|
518
|
+
pathsToTry.push(path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
|
|
521
519
|
} else {
|
|
522
|
-
pathsToTry.push(path.join(
|
|
520
|
+
pathsToTry.push(path.join(userPipelinesRoot, flowId, "nodes", fileName));
|
|
523
521
|
pathsToTry.push(path.join(root, PIPELINES_DIR, flowId, "nodes", fileName));
|
|
524
522
|
pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
525
523
|
}
|
|
@@ -568,6 +566,184 @@ export function readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts = {
|
|
|
568
566
|
return { error: "Node not found: " + nodeId };
|
|
569
567
|
}
|
|
570
568
|
|
|
569
|
+
const NODE_DETAIL_MAX_FILES = 300;
|
|
570
|
+
const NODE_FILE_MAX_BYTES = 256 * 1024;
|
|
571
|
+
|
|
572
|
+
function isTextPreviewPath(filePath) {
|
|
573
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
574
|
+
return [
|
|
575
|
+
".md", ".mdx", ".txt", ".json", ".yaml", ".yml", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx",
|
|
576
|
+
".py", ".sh", ".css", ".html", ".xml", ".toml", ".ini", ".env", ".sql",
|
|
577
|
+
].includes(ext);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function listNodeFiles(baseDir, allowAllFiles, primaryFilePath = "") {
|
|
581
|
+
const root = path.resolve(baseDir);
|
|
582
|
+
const out = [];
|
|
583
|
+
const addFile = (filePath) => {
|
|
584
|
+
if (out.length >= NODE_DETAIL_MAX_FILES) return;
|
|
585
|
+
try {
|
|
586
|
+
const stat = fs.statSync(filePath);
|
|
587
|
+
if (!stat.isFile()) return;
|
|
588
|
+
const rel = path.relative(root, filePath).replace(/\\/g, "/");
|
|
589
|
+
out.push({
|
|
590
|
+
path: rel,
|
|
591
|
+
size: stat.size,
|
|
592
|
+
previewable: isTextPreviewPath(filePath),
|
|
593
|
+
});
|
|
594
|
+
} catch (_) {}
|
|
595
|
+
};
|
|
596
|
+
if (!allowAllFiles) {
|
|
597
|
+
if (primaryFilePath) addFile(primaryFilePath);
|
|
598
|
+
return out;
|
|
599
|
+
}
|
|
600
|
+
const walk = (dir) => {
|
|
601
|
+
if (out.length >= NODE_DETAIL_MAX_FILES) return;
|
|
602
|
+
let entries = [];
|
|
603
|
+
try {
|
|
604
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
605
|
+
} catch {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
609
|
+
for (const entry of entries) {
|
|
610
|
+
if (out.length >= NODE_DETAIL_MAX_FILES) return;
|
|
611
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
612
|
+
const p = path.join(dir, entry.name);
|
|
613
|
+
if (entry.isDirectory()) walk(p);
|
|
614
|
+
else if (entry.isFile()) addFile(p);
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
walk(root);
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function resolveMarkdownNodeFile(workspaceRoot, nodeId, flowId, flowSource, opts = {}) {
|
|
622
|
+
const root = path.resolve(workspaceRoot);
|
|
623
|
+
const archived = Boolean(opts.archived);
|
|
624
|
+
const userPipelinesRoot = getUserPipelinesRoot(opts.userId);
|
|
625
|
+
const fileName = nodeId.endsWith(".md") ? nodeId : `${nodeId}.md`;
|
|
626
|
+
const pathsToTry = [];
|
|
627
|
+
if (flowId && flowSource) {
|
|
628
|
+
if (flowSource === "builtin") {
|
|
629
|
+
pathsToTry.push(path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
630
|
+
} else if (flowSource === "user") {
|
|
631
|
+
if (archived) {
|
|
632
|
+
pathsToTry.push(path.join(userPipelinesRoot, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
|
|
633
|
+
} else {
|
|
634
|
+
pathsToTry.push(path.join(userPipelinesRoot, flowId, "nodes", fileName));
|
|
635
|
+
pathsToTry.push(path.join(root, PIPELINES_DIR, flowId, "nodes", fileName));
|
|
636
|
+
pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
637
|
+
}
|
|
638
|
+
} else if (flowSource === "workspace") {
|
|
639
|
+
if (archived) {
|
|
640
|
+
pathsToTry.push(path.join(root, PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
|
|
641
|
+
pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, ARCHIVED_PIPELINES_DIR_NAME, flowId, "nodes", fileName));
|
|
642
|
+
} else {
|
|
643
|
+
pathsToTry.push(path.join(root, PIPELINES_DIR, flowId, "nodes", fileName));
|
|
644
|
+
pathsToTry.push(path.join(root, LEGACY_PIPELINES_DIR, flowId, "nodes", fileName));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
pathsToTry.push(path.join(root, PROJECT_NODES_DIR, fileName));
|
|
649
|
+
pathsToTry.push(path.join(root, LEGACY_NODES_DIR, fileName));
|
|
650
|
+
pathsToTry.push(path.join(PACKAGE_BUILTIN_NODES_DIR, fileName));
|
|
651
|
+
return pathsToTry.find((p) => fs.existsSync(p) && fs.statSync(p).isFile()) || "";
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function readNodeUsage(workspaceRoot, nodeId, opts = {}) {
|
|
655
|
+
const usage = [];
|
|
656
|
+
for (const flow of listFlowsJson(workspaceRoot, opts)) {
|
|
657
|
+
const flowPath = getFlowYamlAbs(workspaceRoot, flow.id, flow.source || "user", { archived: Boolean(flow.archived), userId: opts.userId });
|
|
658
|
+
if (!flowPath.path) continue;
|
|
659
|
+
try {
|
|
660
|
+
const data = yaml.load(fs.readFileSync(flowPath.path, "utf-8"));
|
|
661
|
+
const instances = data && typeof data === "object" ? data.instances : null;
|
|
662
|
+
if (!instances || typeof instances !== "object") continue;
|
|
663
|
+
const hits = Object.entries(instances)
|
|
664
|
+
.filter(([, inst]) => inst && inst.definitionId === nodeId)
|
|
665
|
+
.map(([instanceId, inst]) => ({ instanceId, label: inst.label || instanceId }));
|
|
666
|
+
if (hits.length > 0) {
|
|
667
|
+
usage.push({ flowId: flow.id, flowSource: flow.source || "user", archived: Boolean(flow.archived), instances: hits });
|
|
668
|
+
}
|
|
669
|
+
} catch (_) {}
|
|
670
|
+
}
|
|
671
|
+
return usage;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function resolveNodeFileScope(workspaceRoot, nodeId, flowId, flowSource, opts = {}) {
|
|
675
|
+
const marketSpec = parseMarketplaceDefinitionId(nodeId);
|
|
676
|
+
if (marketSpec) {
|
|
677
|
+
let flowDir = path.resolve(workspaceRoot);
|
|
678
|
+
let flowData = null;
|
|
679
|
+
if (flowId && flowSource) {
|
|
680
|
+
const flowPath = getFlowYamlAbs(workspaceRoot, flowId, flowSource, opts);
|
|
681
|
+
if (flowPath.path) {
|
|
682
|
+
flowDir = path.dirname(flowPath.path);
|
|
683
|
+
try {
|
|
684
|
+
const parsed = yaml.load(fs.readFileSync(flowPath.path, "utf-8"));
|
|
685
|
+
if (parsed && typeof parsed === "object") flowData = parsed;
|
|
686
|
+
} catch (_) {}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const resolved = resolveMarketplaceNodePackage(workspaceRoot, flowDir, nodeId, flowData);
|
|
690
|
+
if (!resolved) return null;
|
|
691
|
+
return { baseDir: resolved.packageDir, allowAllFiles: true, primaryFilePath: path.join(resolved.packageDir, "node.yaml"), manifest: resolved };
|
|
692
|
+
}
|
|
693
|
+
const filePath = resolveMarkdownNodeFile(workspaceRoot, nodeId, flowId, flowSource, opts);
|
|
694
|
+
if (!filePath) return null;
|
|
695
|
+
return { baseDir: path.dirname(filePath), allowAllFiles: false, primaryFilePath: filePath, manifest: null };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function readNodeDetailJson(workspaceRoot, nodeId, flowId = "", flowSource = "", opts = {}) {
|
|
699
|
+
if (!nodeId) return { error: "Missing node id" };
|
|
700
|
+
const node = readNodeJson(workspaceRoot, nodeId, flowId, flowSource, opts);
|
|
701
|
+
if (node.error) return node;
|
|
702
|
+
const scope = resolveNodeFileScope(workspaceRoot, nodeId, flowId, flowSource, opts);
|
|
703
|
+
const files = scope ? listNodeFiles(scope.baseDir, scope.allowAllFiles, scope.primaryFilePath) : [];
|
|
704
|
+
return {
|
|
705
|
+
node: { id: nodeId, ...node },
|
|
706
|
+
readOnly: true,
|
|
707
|
+
manifest: scope?.manifest || null,
|
|
708
|
+
runtime: node.runtime || scope?.manifest?.runtime || null,
|
|
709
|
+
body: node.executionLogic || "",
|
|
710
|
+
baseDir: scope?.baseDir || "",
|
|
711
|
+
files,
|
|
712
|
+
usage: readNodeUsage(workspaceRoot, nodeId, opts),
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export function readNodeFilePreview(workspaceRoot, nodeId, relPath, flowId = "", flowSource = "", opts = {}) {
|
|
717
|
+
if (!nodeId) return { error: "Missing node id" };
|
|
718
|
+
const rel = String(relPath || "").trim();
|
|
719
|
+
if (!rel || rel.includes("\0") || path.isAbsolute(rel) || rel.split(/[\\/]+/).includes("..")) {
|
|
720
|
+
return { error: "Invalid file path" };
|
|
721
|
+
}
|
|
722
|
+
const scope = resolveNodeFileScope(workspaceRoot, nodeId, flowId, flowSource, opts);
|
|
723
|
+
if (!scope) return { error: "Node files not found" };
|
|
724
|
+
if (!scope.allowAllFiles) {
|
|
725
|
+
const primaryRel = path.relative(scope.baseDir, scope.primaryFilePath).replace(/\\/g, "/");
|
|
726
|
+
if (rel !== primaryRel) return { error: "File is outside node preview scope" };
|
|
727
|
+
}
|
|
728
|
+
const base = path.resolve(scope.baseDir);
|
|
729
|
+
const abs = path.resolve(base, rel);
|
|
730
|
+
if (abs !== base && !abs.startsWith(base + path.sep)) return { error: "File is outside node package" };
|
|
731
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return { error: "File not found" };
|
|
732
|
+
const stat = fs.statSync(abs);
|
|
733
|
+
if (!isTextPreviewPath(abs)) {
|
|
734
|
+
return { path: rel, size: stat.size, binary: true, content: "", truncated: false };
|
|
735
|
+
}
|
|
736
|
+
const fd = fs.openSync(abs, "r");
|
|
737
|
+
try {
|
|
738
|
+
const len = Math.min(stat.size, NODE_FILE_MAX_BYTES);
|
|
739
|
+
const buf = Buffer.alloc(len);
|
|
740
|
+
fs.readSync(fd, buf, 0, len, 0);
|
|
741
|
+
return { path: rel, size: stat.size, binary: false, content: buf.toString("utf-8"), truncated: stat.size > len };
|
|
742
|
+
} finally {
|
|
743
|
+
fs.closeSync(fd);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
571
747
|
/** 列出所有 pipeline(包内 builtin + ~/agentflow/pipelines + 项目内 .workspace/.cursor agentflow/pipelines);nodes 见 PROJECT_NODES_DIR / LEGACY_NODES_DIR */
|
|
572
748
|
export function listPipelines(workspaceRoot) {
|
|
573
749
|
const rows = listFlowsJson(workspaceRoot);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
|
-
import { getAgentflowDataRoot } from "./paths.mjs";
|
|
9
|
+
import { getAgentflowDataRoot, sanitizeAgentflowUserId } from "./paths.mjs";
|
|
10
10
|
import { resolveCliAndModel } from "./model-config.mjs";
|
|
11
11
|
import { runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
|
|
12
12
|
import { planComposerTasks, hasPlannerApiAvailable, shouldUsePhased, classifyComplexity, classifyTaskComplexity, PHASED_DEFINITIONS } from "./composer-planner.mjs";
|
|
@@ -23,6 +23,11 @@ const MAX_PROMPT_CHARS = 500_000;
|
|
|
23
23
|
const MAX_COMPOSER_VALIDATION_REPAIR = 5;
|
|
24
24
|
const MAX_SCRIPT_INJECT_BYTES = 30_000;
|
|
25
25
|
|
|
26
|
+
function agentflowUserEnv(userId) {
|
|
27
|
+
const safe = sanitizeAgentflowUserId(userId);
|
|
28
|
+
return safe ? { AGENTFLOW_USER_ID: safe } : {};
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
// ─── script 内容注入辅助 ─────────────────────────────────────────────────
|
|
27
32
|
|
|
28
33
|
/**
|
|
@@ -113,10 +118,12 @@ export function startComposerAgent(opts) {
|
|
|
113
118
|
const cliWs = opts.cliWorkspace ? String(opts.cliWorkspace) : getAgentflowDataRoot();
|
|
114
119
|
const modelKey = opts.modelKey != null ? String(opts.modelKey).trim() : "";
|
|
115
120
|
const { cli, model } = resolveCliAndModel(uiRoot, modelKey || null, null);
|
|
121
|
+
const env = agentflowUserEnv(opts.agentflowUserId);
|
|
116
122
|
|
|
117
123
|
const common = {
|
|
118
124
|
onStreamEvent: opts.onStreamEvent,
|
|
119
125
|
force: Boolean(opts.force),
|
|
126
|
+
env,
|
|
120
127
|
};
|
|
121
128
|
|
|
122
129
|
if (cli === "opencode") {
|
|
@@ -185,6 +192,10 @@ function buildAgentStepPrompt(step, flowContext) {
|
|
|
185
192
|
parts.push(`- flowId:${flowContext.flowId}`);
|
|
186
193
|
parts.push(`- flowSource:${flowContext.flowSource || "user"}`);
|
|
187
194
|
}
|
|
195
|
+
if (flowContext.userId) {
|
|
196
|
+
parts.push(`- agentflow 用户:${flowContext.userId}`);
|
|
197
|
+
parts.push(`- 执行 agentflow 命令时保留当前环境中的 AGENTFLOW_USER_ID,必要时显式前置 AGENTFLOW_USER_ID='${flowContext.userId}'。`);
|
|
198
|
+
}
|
|
188
199
|
if (flowContext.skillsHint) {
|
|
189
200
|
parts.push(flowContext.skillsHint);
|
|
190
201
|
}
|
|
@@ -344,6 +355,7 @@ export async function runComposerPostFlowValidationAndRepair(opts) {
|
|
|
344
355
|
const flowYamlAbs = String(opts.flowYamlAbs || "").trim();
|
|
345
356
|
const cliWs = opts.cliWorkspace ? String(opts.cliWorkspace) : getAgentflowDataRoot();
|
|
346
357
|
const maxRepair = Math.max(1, Math.min(10, Number(opts.maxRepairAttempts) || MAX_COMPOSER_VALIDATION_REPAIR));
|
|
358
|
+
const env = agentflowUserEnv(opts.agentflowUserId || opts.flowContext?.userId);
|
|
347
359
|
|
|
348
360
|
if (!uiRoot || !flowYamlAbs) {
|
|
349
361
|
return { ok: true, result: { skipped: true } };
|
|
@@ -405,6 +417,7 @@ export async function runComposerPostFlowValidationAndRepair(opts) {
|
|
|
405
417
|
onStreamEvent: stepEmit,
|
|
406
418
|
model: model || undefined,
|
|
407
419
|
force: Boolean(opts.force),
|
|
420
|
+
env,
|
|
408
421
|
});
|
|
409
422
|
setChild(handle.child);
|
|
410
423
|
await handle.finished;
|
|
@@ -413,6 +426,7 @@ export async function runComposerPostFlowValidationAndRepair(opts) {
|
|
|
413
426
|
onStreamEvent: stepEmit,
|
|
414
427
|
model: model || undefined,
|
|
415
428
|
force: Boolean(opts.force),
|
|
429
|
+
env,
|
|
416
430
|
});
|
|
417
431
|
setChild(handle.child);
|
|
418
432
|
await handle.finished;
|
|
@@ -421,6 +435,7 @@ export async function runComposerPostFlowValidationAndRepair(opts) {
|
|
|
421
435
|
onStreamEvent: stepEmit,
|
|
422
436
|
model: model || undefined,
|
|
423
437
|
force: Boolean(opts.force),
|
|
438
|
+
env,
|
|
424
439
|
});
|
|
425
440
|
setChild(handle.child);
|
|
426
441
|
await handle.finished;
|
|
@@ -490,6 +505,7 @@ export function startComposerMultiStep(opts) {
|
|
|
490
505
|
const emit = typeof opts.onStreamEvent === "function" ? opts.onStreamEvent : () => {};
|
|
491
506
|
let aborted = false;
|
|
492
507
|
let currentChild = null;
|
|
508
|
+
const env = agentflowUserEnv(opts.agentflowUserId || opts.flowContext?.userId);
|
|
493
509
|
|
|
494
510
|
const abort = () => {
|
|
495
511
|
aborted = true;
|
|
@@ -674,6 +690,7 @@ export function startComposerMultiStep(opts) {
|
|
|
674
690
|
onStreamEvent: stepEmit,
|
|
675
691
|
model: model || undefined,
|
|
676
692
|
force: Boolean(opts.force),
|
|
693
|
+
env,
|
|
677
694
|
});
|
|
678
695
|
currentChild = handle.child;
|
|
679
696
|
await handle.finished;
|
|
@@ -682,6 +699,7 @@ export function startComposerMultiStep(opts) {
|
|
|
682
699
|
onStreamEvent: stepEmit,
|
|
683
700
|
model: model || undefined,
|
|
684
701
|
force: Boolean(opts.force),
|
|
702
|
+
env,
|
|
685
703
|
});
|
|
686
704
|
currentChild = handle.child;
|
|
687
705
|
await handle.finished;
|
|
@@ -690,6 +708,7 @@ export function startComposerMultiStep(opts) {
|
|
|
690
708
|
onStreamEvent: stepEmit,
|
|
691
709
|
model: model || undefined,
|
|
692
710
|
force: Boolean(opts.force),
|
|
711
|
+
env,
|
|
693
712
|
});
|
|
694
713
|
currentChild = handle.child;
|
|
695
714
|
await handle.finished;
|
|
@@ -757,6 +776,7 @@ export function startComposerMultiStep(opts) {
|
|
|
757
776
|
flowContext: opts.flowContext,
|
|
758
777
|
modelKey: opts.modelKey,
|
|
759
778
|
force: Boolean(opts.force),
|
|
779
|
+
agentflowUserId: opts.agentflowUserId || opts.flowContext?.userId,
|
|
760
780
|
onStreamEvent: emit,
|
|
761
781
|
getAborted: () => aborted,
|
|
762
782
|
setCurrentChild: (c) => {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import fs from "fs";
|
|
15
15
|
import path from "path";
|
|
16
|
-
import
|
|
16
|
+
import { listSkills as registryListSkills, readSkillDetail as registryReadSkillDetail } from "./skill-registry.mjs";
|
|
17
17
|
|
|
18
18
|
// ─── 意图模式定义 ─────────────────────────────────────────────────────────
|
|
19
19
|
|
|
@@ -125,81 +125,12 @@ function readFileCached(absPath) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
if (!content) return null;
|
|
131
|
-
const fmMatch = content.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
132
|
-
let meta = {};
|
|
133
|
-
if (fmMatch) {
|
|
134
|
-
try {
|
|
135
|
-
meta = yaml.load(fmMatch[1]) || {};
|
|
136
|
-
} catch {
|
|
137
|
-
meta = {};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const dirName = path.basename(path.dirname(absPath));
|
|
141
|
-
const name = String(meta.name || dirName).trim();
|
|
142
|
-
if (!name) return null;
|
|
143
|
-
const description = String(meta.description || "").trim();
|
|
144
|
-
return {
|
|
145
|
-
name,
|
|
146
|
-
description,
|
|
147
|
-
content,
|
|
148
|
-
body: stripFrontmatter(content),
|
|
149
|
-
absPath,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function listSkillDirs(rootDir) {
|
|
154
|
-
try {
|
|
155
|
-
return fs.readdirSync(rootDir, { withFileTypes: true })
|
|
156
|
-
.filter((e) => e.isDirectory())
|
|
157
|
-
.map((e) => path.join(rootDir, e.name, "SKILL.md"))
|
|
158
|
-
.filter((p) => fs.existsSync(p));
|
|
159
|
-
} catch {
|
|
160
|
-
return [];
|
|
161
|
-
}
|
|
128
|
+
export function listComposerSkills(packageRoot, workspaceRoot) {
|
|
129
|
+
return registryListSkills(packageRoot, workspaceRoot).map(({ body, content, ...skill }) => skill);
|
|
162
130
|
}
|
|
163
131
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
{ source: "builtin", label: "AgentFlow", dir: path.join(packageRoot, "skills") },
|
|
167
|
-
];
|
|
168
|
-
if (workspaceRoot) {
|
|
169
|
-
sources.push(
|
|
170
|
-
{ source: "workspace-agents", label: ".agents", dir: path.join(workspaceRoot, ".agents", "skills") },
|
|
171
|
-
{ source: "workspace-cursor", label: ".cursor", dir: path.join(workspaceRoot, ".cursor", "skills") },
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
return sources;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export function listComposerSkills(packageRoot, workspaceRoot) {
|
|
178
|
-
const out = [];
|
|
179
|
-
const seenKeys = new Set();
|
|
180
|
-
for (const src of skillSources(packageRoot, workspaceRoot)) {
|
|
181
|
-
for (const skillPath of listSkillDirs(src.dir)) {
|
|
182
|
-
const skill = parseSkillFile(skillPath);
|
|
183
|
-
if (!skill) continue;
|
|
184
|
-
const key = `${src.source}:${skill.name}`;
|
|
185
|
-
if (seenKeys.has(key)) continue;
|
|
186
|
-
seenKeys.add(key);
|
|
187
|
-
out.push({
|
|
188
|
-
key,
|
|
189
|
-
id: skill.name,
|
|
190
|
-
name: skill.name,
|
|
191
|
-
description: skill.description,
|
|
192
|
-
source: src.source,
|
|
193
|
-
sourceLabel: src.label,
|
|
194
|
-
path: skill.absPath,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return out.sort((a, b) => {
|
|
199
|
-
const bySource = a.sourceLabel.localeCompare(b.sourceLabel);
|
|
200
|
-
if (bySource !== 0) return bySource;
|
|
201
|
-
return a.name.localeCompare(b.name);
|
|
202
|
-
});
|
|
132
|
+
export function readComposerSkillDetail(packageRoot, workspaceRoot, keyOrName) {
|
|
133
|
+
return registryReadSkillDetail(packageRoot, workspaceRoot, keyOrName);
|
|
203
134
|
}
|
|
204
135
|
|
|
205
136
|
export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot) {
|
|
@@ -210,13 +141,11 @@ export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot)
|
|
|
210
141
|
if (wanted.size === 0) return { skills: [], references: [], skillsHint: "", hasContext: false };
|
|
211
142
|
|
|
212
143
|
const skills = [];
|
|
213
|
-
for (const item of
|
|
144
|
+
for (const item of registryListSkills(packageRoot, workspaceRoot)) {
|
|
214
145
|
if (!wanted.has(item.key) && !wanted.has(item.name)) continue;
|
|
215
|
-
const parsed = parseSkillFile(item.path);
|
|
216
|
-
if (!parsed) continue;
|
|
217
146
|
skills.push({
|
|
218
147
|
id: item.name,
|
|
219
|
-
content:
|
|
148
|
+
content: item.body,
|
|
220
149
|
absPath: item.path,
|
|
221
150
|
source: item.source,
|
|
222
151
|
sourceLabel: item.sourceLabel,
|
|
@@ -356,6 +285,9 @@ export function buildSkillCompactInjectionBlock(skills, references) {
|
|
|
356
285
|
*/
|
|
357
286
|
export function buildSkillInjectionBlock(skills, references) {
|
|
358
287
|
const parts = [];
|
|
288
|
+
if ((skills?.length || 0) + (references?.length || 0) > 8) {
|
|
289
|
+
return buildSkillCompactInjectionBlock(skills || [], references || []);
|
|
290
|
+
}
|
|
359
291
|
|
|
360
292
|
if (skills.length > 0) {
|
|
361
293
|
parts.push("### 相关编辑技能(请严格遵循)");
|
package/bin/lib/flow-import.mjs
CHANGED
|
@@ -263,8 +263,8 @@ export function unzipAndNormalizePipelineZip(zipBuffer) {
|
|
|
263
263
|
* @param {Map<string, Buffer>} filesRelative 相对流水线根,须含 flow.yaml
|
|
264
264
|
* @returns {{ success: true } | { success: false, error: string }}
|
|
265
265
|
*/
|
|
266
|
-
export function writePipelineTree(workspaceRoot, flowId, flowSource, filesRelative) {
|
|
267
|
-
const { flowDir, error } = resolveFlowDirForWrite(workspaceRoot, flowId, flowSource);
|
|
266
|
+
export function writePipelineTree(workspaceRoot, flowId, flowSource, filesRelative, opts = {}) {
|
|
267
|
+
const { flowDir, error } = resolveFlowDirForWrite(workspaceRoot, flowId, flowSource, opts);
|
|
268
268
|
if (error) return { success: false, error };
|
|
269
269
|
if (fs.existsSync(flowDir)) {
|
|
270
270
|
return { success: false, error: "目标目录已存在" };
|