@fieldwangai/agentflow 0.1.160 → 0.1.163
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/auth.mjs +231 -14
- package/bin/lib/catalog-flows.mjs +2 -0
- package/bin/lib/cli-auth-page.mjs +103 -0
- package/bin/lib/flow-dsl/codegen.mjs +23 -2
- package/bin/lib/flow-dsl/lint.mjs +44 -0
- package/bin/lib/flow-dsl/parser.mjs +68 -1
- package/bin/lib/marketplace-usage.mjs +218 -0
- package/bin/lib/marketplace.mjs +183 -3
- package/bin/lib/node-package-manifest.mjs +1 -1
- package/bin/lib/skill-runtime.mjs +6 -0
- package/bin/lib/spaces.mjs +200 -0
- package/bin/lib/table.mjs +2 -3
- package/bin/lib/ui-server.mjs +471 -84
- package/bin/lib/workspace-draft.mjs +74 -0
- package/bin/lib/workspace-routes.mjs +484 -4
- package/bin/lib/workspace-server.mjs +373 -30
- package/bin/lib/workspace-state.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +5 -1
- package/builtin/nodes/context_bundle.md +44 -0
- package/builtin/nodes/context_knowledge.md +29 -0
- package/builtin/nodes/context_skills.md +29 -0
- package/builtin/nodes/context_workspace.md +36 -0
- package/builtin/nodes/control_while.md +12 -5
- package/builtin/nodes/tool_git_worktree_load.md +4 -3
- package/builtin/pipelines/new/workspace.flow.js +1 -1
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-Bfo9Ythw.js → WorkflowAssistantThread-DmhIgoD7.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CI9J6Unt.js +873 -0
- package/builtin/web-ui/dist/assets/index-DY5vE7v1.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +5 -1
- package/shared/slot-types.js +1 -0
- package/skills/agentflow-author-flow/SKILL.md +81 -8
- package/skills/agentflow-author-flow/agents/openai.yaml +3 -3
- package/skills/agentflow-cli/SKILL.md +179 -27
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14879 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +38 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_bundle.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_knowledge.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_skills.md +29 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/context_workspace.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_agent_toBool.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_anyOne.md +19 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cancelled.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_cd_workspace.md +50 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_delay.md +31 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_end.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_if.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_interval_loop.md +55 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_mcp.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_load_skills.md +35 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_parse_json.md +25 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_start.md +13 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_subflow_call.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_toBool.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_user_workspace.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_wait_until.md +34 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +131 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_ascii.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_chart.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_code.md +39 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_html.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_image.md +36 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_markdown.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_mermaid.md +24 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_react_app.md +53 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/display_table.md +32 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_bool.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_file.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_json.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_password.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/provide_str.md +14 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_display_share_link.md +47 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_get_env.md +20 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_checkout.md +70 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +74 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_unload.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_gitlab_create_mr.md +114 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_jenkins_build.md +67 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_load_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_nodejs.md +52 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_print.md +21 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_save_key.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_set_run_env.md +38 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_ask.md +27 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_check.md +26 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_app_markdown.md +57 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_group_markdown.md +45 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_one_click_task.md +44 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/skills/agentflow-cli/runtime/builtin/nodes/workspace_subflow_input.md +14 -0
- package/skills/agentflow-cli/runtime/package.json +6 -0
- package/skills/agentflow-cli/scripts/agentflow-auth-store.mjs +113 -0
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +457 -28
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +11 -4
- package/skills/agentflow-flow-add-instances/SKILL.md +2 -2
- package/skills/agentflow-flow-dsl/SKILL.md +73 -8
- package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -2
- package/skills/agentflow-flow-dsl/references/node-calls.md +6 -2
- package/skills/agentflow-flow-dsl/references/subflow-authoring.md +34 -7
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +3 -3
- package/skills/agentflow-flow-recipes/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/references/recipes.md +1 -2
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +5 -13
- package/skills/agentflow-node-reference/references/builtin-nodes.md +37 -5
- package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
- package/builtin/web-ui/dist/assets/index-XbeI5foV.js +0 -872
- package/builtin/web-ui/dist/assets/index-e1omCEau.css +0 -1
- package/skills/agentflow-workspace-graph/SKILL.md +0 -85
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
import { MARKETPLACE_PACKAGES_DIR } from "./paths.mjs";
|
|
5
|
+
|
|
6
|
+
const USAGE_DIRNAME = "usage";
|
|
7
|
+
const FLOW_ORIGIN_FILENAME = ".agentflow-marketplace-origin.json";
|
|
8
|
+
|
|
9
|
+
function usageRoot(workspaceRoot) {
|
|
10
|
+
return path.join(path.resolve(workspaceRoot), path.dirname(MARKETPLACE_PACKAGES_DIR), USAGE_DIRNAME);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function dayKey(timeMs) {
|
|
14
|
+
const date = new Date(Number(timeMs) || Date.now());
|
|
15
|
+
return [
|
|
16
|
+
date.getFullYear(),
|
|
17
|
+
String(date.getMonth() + 1).padStart(2, "0"),
|
|
18
|
+
String(date.getDate()).padStart(2, "0"),
|
|
19
|
+
].join("-");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function safeText(value, max = 240) {
|
|
23
|
+
return String(value || "").trim().slice(0, max);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function normalizeMarketplaceVisibility(value, fallback = "public") {
|
|
27
|
+
const normalized = safeText(value, 20).toLowerCase();
|
|
28
|
+
if (normalized === "private") return "private";
|
|
29
|
+
if (normalized === "public") return "public";
|
|
30
|
+
return fallback === "private" ? "private" : "public";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function marketplaceResourceKey(kind, id, version) {
|
|
34
|
+
return `${safeText(kind, 32)}:${safeText(id)}@${safeText(version, 80)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function appendMarketplaceUsageEvent(workspaceRoot, event = {}) {
|
|
38
|
+
try {
|
|
39
|
+
const kind = safeText(event.kind, 32);
|
|
40
|
+
const id = safeText(event.id);
|
|
41
|
+
const version = safeText(event.version, 80);
|
|
42
|
+
const action = safeText(event.action, 32);
|
|
43
|
+
const actorUserId = safeText(event.actorUserId || event.userId, 160);
|
|
44
|
+
const at = Number(event.at || Date.now());
|
|
45
|
+
const eventId = safeText(
|
|
46
|
+
event.eventId || `${action}:${kind}:${id}@${version}:${actorUserId}:${at}`,
|
|
47
|
+
500,
|
|
48
|
+
);
|
|
49
|
+
if (!kind || !id || !version || !["install", "use"].includes(action) || !eventId) return false;
|
|
50
|
+
const record = {
|
|
51
|
+
version: 1,
|
|
52
|
+
eventId,
|
|
53
|
+
kind,
|
|
54
|
+
id,
|
|
55
|
+
resourceVersion: version,
|
|
56
|
+
action,
|
|
57
|
+
actorUserId,
|
|
58
|
+
runId: safeText(event.runId, 240),
|
|
59
|
+
at: Number.isFinite(at) && at > 0 ? at : Date.now(),
|
|
60
|
+
};
|
|
61
|
+
const filePath = path.join(usageRoot(workspaceRoot), `${dayKey(record.at)}.jsonl`);
|
|
62
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
63
|
+
fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`, "utf-8");
|
|
64
|
+
return true;
|
|
65
|
+
} catch {
|
|
66
|
+
// Marketplace telemetry must never break publishing, installing, or running.
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readUsageEvents(workspaceRoot) {
|
|
72
|
+
const dir = usageRoot(workspaceRoot);
|
|
73
|
+
if (!fs.existsSync(dir)) return [];
|
|
74
|
+
const events = [];
|
|
75
|
+
try {
|
|
76
|
+
const files = fs.readdirSync(dir, { withFileTypes: true })
|
|
77
|
+
.filter((entry) => entry.isFile() && /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(entry.name))
|
|
78
|
+
.map((entry) => path.join(dir, entry.name))
|
|
79
|
+
.sort();
|
|
80
|
+
for (const filePath of files) {
|
|
81
|
+
for (const line of fs.readFileSync(filePath, "utf-8").split(/\r?\n/)) {
|
|
82
|
+
if (!line.trim()) continue;
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(line);
|
|
85
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) events.push(parsed);
|
|
86
|
+
} catch {
|
|
87
|
+
// Ignore individual malformed telemetry records.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
return events;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function marketplaceUsageStats(workspaceRoot) {
|
|
98
|
+
const byResource = new Map();
|
|
99
|
+
const seenEvents = new Set();
|
|
100
|
+
for (const event of readUsageEvents(workspaceRoot)) {
|
|
101
|
+
const eventId = safeText(event.eventId, 500);
|
|
102
|
+
if (!eventId || seenEvents.has(eventId)) continue;
|
|
103
|
+
seenEvents.add(eventId);
|
|
104
|
+
const key = marketplaceResourceKey(event.kind, event.id, event.resourceVersion);
|
|
105
|
+
if (!byResource.has(key)) {
|
|
106
|
+
byResource.set(key, {
|
|
107
|
+
useCount: 0,
|
|
108
|
+
installCount: 0,
|
|
109
|
+
uniqueUserCount: 0,
|
|
110
|
+
lastUsedAt: "",
|
|
111
|
+
_users: new Set(),
|
|
112
|
+
_actors: new Map(),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const stats = byResource.get(key);
|
|
116
|
+
const actorUserId = safeText(event.actorUserId, 160);
|
|
117
|
+
if (actorUserId) stats._users.add(actorUserId);
|
|
118
|
+
if (actorUserId && !stats._actors.has(actorUserId)) stats._actors.set(actorUserId, { useCount: 0, installCount: 0 });
|
|
119
|
+
if (event.action === "use") {
|
|
120
|
+
stats.useCount += 1;
|
|
121
|
+
if (actorUserId) stats._actors.get(actorUserId).useCount += 1;
|
|
122
|
+
const atIso = new Date(Number(event.at) || 0).toISOString();
|
|
123
|
+
if (!stats.lastUsedAt || atIso > stats.lastUsedAt) stats.lastUsedAt = atIso;
|
|
124
|
+
} else if (event.action === "install") {
|
|
125
|
+
stats.installCount += 1;
|
|
126
|
+
if (actorUserId) stats._actors.get(actorUserId).installCount += 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const stats of byResource.values()) {
|
|
130
|
+
stats.uniqueUserCount = stats._users.size;
|
|
131
|
+
}
|
|
132
|
+
return byResource;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function marketplaceStatsFor(statsByResource, kind, id, version, ownerUserId = "") {
|
|
136
|
+
const stats = statsByResource.get(marketplaceResourceKey(kind, id, version));
|
|
137
|
+
if (!stats) return {
|
|
138
|
+
useCount: 0,
|
|
139
|
+
installCount: 0,
|
|
140
|
+
uniqueUserCount: 0,
|
|
141
|
+
lastUsedAt: "",
|
|
142
|
+
};
|
|
143
|
+
const owner = safeText(ownerUserId, 160);
|
|
144
|
+
const ownerStats = owner ? stats._actors.get(owner) : null;
|
|
145
|
+
return {
|
|
146
|
+
useCount: Math.max(0, stats.useCount - Number(ownerStats?.useCount || 0)),
|
|
147
|
+
installCount: Math.max(0, stats.installCount - Number(ownerStats?.installCount || 0)),
|
|
148
|
+
uniqueUserCount: Math.max(0, stats.uniqueUserCount - (owner && stats._users.has(owner) ? 1 : 0)),
|
|
149
|
+
lastUsedAt: stats.lastUsedAt,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function marketplaceFlowOriginPath(flowDir) {
|
|
154
|
+
return path.join(path.resolve(flowDir), FLOW_ORIGIN_FILENAME);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function writeMarketplaceFlowOrigin(flowDir, origin = {}) {
|
|
158
|
+
const value = {
|
|
159
|
+
kind: "flow",
|
|
160
|
+
id: safeText(origin.id),
|
|
161
|
+
version: safeText(origin.version, 80),
|
|
162
|
+
installedAt: safeText(origin.installedAt || new Date().toISOString(), 80),
|
|
163
|
+
};
|
|
164
|
+
if (!value.id || !value.version) throw new Error("Invalid marketplace flow origin");
|
|
165
|
+
fs.writeFileSync(marketplaceFlowOriginPath(flowDir), `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function readMarketplaceFlowOrigin(flowDir) {
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(fs.readFileSync(marketplaceFlowOriginPath(flowDir), "utf-8"));
|
|
172
|
+
if (parsed?.kind !== "flow" || !parsed.id || !parsed.version) return null;
|
|
173
|
+
return { kind: "flow", id: String(parsed.id), version: String(parsed.version) };
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function parseMarketplaceNodeRef(instance = {}) {
|
|
180
|
+
const ref = safeText(instance.marketplaceRef || instance.definitionId, 500);
|
|
181
|
+
if (!ref.startsWith("marketplace:")) return null;
|
|
182
|
+
const spec = ref.slice("marketplace:".length);
|
|
183
|
+
const at = spec.lastIndexOf("@");
|
|
184
|
+
if (at <= 0 || at === spec.length - 1) return null;
|
|
185
|
+
return { kind: "node", id: spec.slice(0, at), version: spec.slice(at + 1) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function marketplaceResourcesForRun(flowDir, graph, executedNodeIds = []) {
|
|
189
|
+
const resources = new Map();
|
|
190
|
+
const flowOrigin = readMarketplaceFlowOrigin(flowDir);
|
|
191
|
+
if (flowOrigin) resources.set(marketplaceResourceKey(flowOrigin.kind, flowOrigin.id, flowOrigin.version), flowOrigin);
|
|
192
|
+
const ids = Array.isArray(executedNodeIds) && executedNodeIds.length
|
|
193
|
+
? executedNodeIds.map((id) => String(id))
|
|
194
|
+
: Object.keys(graph?.instances || {});
|
|
195
|
+
for (const nodeId of ids) {
|
|
196
|
+
const ref = parseMarketplaceNodeRef(graph?.instances?.[nodeId]);
|
|
197
|
+
if (ref) resources.set(marketplaceResourceKey(ref.kind, ref.id, ref.version), ref);
|
|
198
|
+
}
|
|
199
|
+
return [...resources.values()];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function recordMarketplaceRunUsage(workspaceRoot, resources = [], run = {}) {
|
|
203
|
+
if (String(run.status || "") !== "success") return;
|
|
204
|
+
if (!String(workspaceRoot || "").trim() || !Array.isArray(resources) || resources.length === 0) return;
|
|
205
|
+
const runId = safeText(run.runId, 240);
|
|
206
|
+
const actorUserId = safeText(run.userId || run.actorUserId, 160);
|
|
207
|
+
if (!runId) return;
|
|
208
|
+
for (const resource of resources) {
|
|
209
|
+
appendMarketplaceUsageEvent(workspaceRoot, {
|
|
210
|
+
...resource,
|
|
211
|
+
action: "use",
|
|
212
|
+
actorUserId,
|
|
213
|
+
runId,
|
|
214
|
+
at: run.endedAt || Date.now(),
|
|
215
|
+
eventId: `use:${resource.kind}:${resource.id}@${resource.version}:${runId}`,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
package/bin/lib/marketplace.mjs
CHANGED
|
@@ -19,10 +19,17 @@ import {
|
|
|
19
19
|
inspectNodePackageDirectory,
|
|
20
20
|
writeNodePackageFiles,
|
|
21
21
|
} from "./node-package-archive.mjs";
|
|
22
|
+
import {
|
|
23
|
+
marketplaceStatsFor,
|
|
24
|
+
marketplaceUsageStats,
|
|
25
|
+
normalizeMarketplaceVisibility,
|
|
26
|
+
} from "./marketplace-usage.mjs";
|
|
22
27
|
|
|
23
28
|
const NODE_MANIFEST = "node.yaml";
|
|
24
29
|
const COLLECTION_MANIFEST = "collection.yaml";
|
|
25
30
|
const FLOW_SNIPPET_MANIFEST = "flow-snippet.yaml";
|
|
31
|
+
const FLOW_PACKAGE_MANIFEST = "flow-package.yaml";
|
|
32
|
+
const FLOW_PACKAGE_GRAPH = "workspace.graph.json";
|
|
26
33
|
const LOCK_FILENAME = "agentflow.lock.json";
|
|
27
34
|
|
|
28
35
|
function workspacePackageRoot(workspaceRoot) {
|
|
@@ -131,6 +138,7 @@ function normalizeManifest(raw, packageDir, source = "workspace") {
|
|
|
131
138
|
baseDefinitionId,
|
|
132
139
|
displayName: raw.displayName != null ? String(raw.displayName) : raw.name != null ? String(raw.name) : id,
|
|
133
140
|
description: raw.description != null ? String(raw.description) : "",
|
|
141
|
+
visibility: normalizeMarketplaceVisibility(raw.visibility),
|
|
134
142
|
input,
|
|
135
143
|
output,
|
|
136
144
|
runtime,
|
|
@@ -168,7 +176,12 @@ function canManageMarketplaceOwner(ownerUserId, opts = {}) {
|
|
|
168
176
|
|
|
169
177
|
function canAccessMarketplaceNode(manifest, opts = {}) {
|
|
170
178
|
if ((manifest?.source || "marketplace") !== "marketplace") return true;
|
|
171
|
-
|
|
179
|
+
const ownerUserId = manifestOwnerUserId(manifest);
|
|
180
|
+
if (!canAccessMarketplaceOwner(ownerUserId, opts)) return false;
|
|
181
|
+
if (normalizeMarketplaceVisibility(manifest?.visibility) === "public") return true;
|
|
182
|
+
const requestedUserId = String(opts.userId || "").trim();
|
|
183
|
+
if (!requestedUserId) return true;
|
|
184
|
+
return isAdminRequest(opts) || Boolean(ownerUserId) && ownerUserId === requestedUserId;
|
|
172
185
|
}
|
|
173
186
|
|
|
174
187
|
function sortVersionsDesc(versions) {
|
|
@@ -204,6 +217,14 @@ function resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version) {
|
|
|
204
217
|
return target;
|
|
205
218
|
}
|
|
206
219
|
|
|
220
|
+
function resolveWorkspaceFlowPackageDir(workspaceRoot, id, version) {
|
|
221
|
+
if (!isSafePathSegment(id) || !isSafePathSegment(version)) return null;
|
|
222
|
+
const base = path.resolve(workspacePackageRoot(workspaceRoot), "flows");
|
|
223
|
+
const target = path.resolve(base, id, version);
|
|
224
|
+
if (target !== base && !target.startsWith(base + path.sep)) return null;
|
|
225
|
+
return target;
|
|
226
|
+
}
|
|
227
|
+
|
|
207
228
|
function collectFlowDirs(rootDir, source, archived = false) {
|
|
208
229
|
const out = [];
|
|
209
230
|
if (!fs.existsSync(rootDir)) return out;
|
|
@@ -470,6 +491,7 @@ export function listMarketplaceNodes(workspaceRoot, flowData = null, opts = {})
|
|
|
470
491
|
|
|
471
492
|
export function listMarketplacePackages(workspaceRoot, opts = {}) {
|
|
472
493
|
const root = workspacePackageRoot(workspaceRoot);
|
|
494
|
+
const stats = marketplaceUsageStats(workspaceRoot);
|
|
473
495
|
const nodes = listMarketplaceNodes(workspaceRoot, null, opts).map((n) => ({
|
|
474
496
|
id: n.id,
|
|
475
497
|
version: n.version,
|
|
@@ -490,9 +512,16 @@ export function listMarketplacePackages(workspaceRoot, opts = {}) {
|
|
|
490
512
|
installedAt: String(n.installedAt || ""),
|
|
491
513
|
ownerUserId: n.ownerUserId || n.createdBy || "",
|
|
492
514
|
createdBy: n.createdBy || n.ownerUserId || "",
|
|
515
|
+
visibility: normalizeMarketplaceVisibility(n.visibility),
|
|
493
516
|
packageDir: n.packageDir,
|
|
494
517
|
usage: listMarketplaceNodeUsages(workspaceRoot, n.id, n.version, opts),
|
|
495
|
-
|
|
518
|
+
...marketplaceStatsFor(stats, "node", n.id, n.version, n.ownerUserId || n.createdBy || ""),
|
|
519
|
+
})).sort((a, b) => (
|
|
520
|
+
Number(b.useCount || 0) - Number(a.useCount || 0)
|
|
521
|
+
|| Number(b.installCount || 0) - Number(a.installCount || 0)
|
|
522
|
+
|| String(a.id).localeCompare(String(b.id))
|
|
523
|
+
|| String(b.version).localeCompare(String(a.version), undefined, { numeric: true, sensitivity: "base" })
|
|
524
|
+
));
|
|
496
525
|
const collections = [];
|
|
497
526
|
const collectionsRoot = path.join(root, "collections");
|
|
498
527
|
if (fs.existsSync(collectionsRoot)) {
|
|
@@ -519,6 +548,7 @@ export function listMarketplaceFlowSnippets(workspaceRoot, opts = {}) {
|
|
|
519
548
|
const root = workspacePackageRoot(workspaceRoot);
|
|
520
549
|
const snippetsRoot = path.join(root, "flow-snippets");
|
|
521
550
|
const snippets = [];
|
|
551
|
+
const stats = marketplaceUsageStats(workspaceRoot);
|
|
522
552
|
const requestedUserId = String(opts.userId || "").trim();
|
|
523
553
|
if (!fs.existsSync(snippetsRoot)) return { snippets };
|
|
524
554
|
for (const entry of fs.readdirSync(snippetsRoot, { withFileTypes: true })) {
|
|
@@ -531,6 +561,8 @@ export function listMarketplaceFlowSnippets(workspaceRoot, opts = {}) {
|
|
|
531
561
|
const snippet = manifest.snippet && typeof manifest.snippet === "object" ? manifest.snippet : {};
|
|
532
562
|
const ownerUserId = String(manifest.ownerUserId || manifest.createdBy || "").trim();
|
|
533
563
|
if (requestedUserId && !canAccessMarketplaceOwner(ownerUserId, opts)) continue;
|
|
564
|
+
const visibility = normalizeMarketplaceVisibility(manifest.visibility);
|
|
565
|
+
if (visibility === "private" && requestedUserId && !isAdminRequest(opts) && ownerUserId !== requestedUserId) continue;
|
|
534
566
|
snippets.push({
|
|
535
567
|
id: manifest.id || entry.name,
|
|
536
568
|
version: manifest.version || version,
|
|
@@ -542,18 +574,162 @@ export function listMarketplaceFlowSnippets(workspaceRoot, opts = {}) {
|
|
|
542
574
|
createdAt: manifest.createdAt || "",
|
|
543
575
|
updatedAt: manifest.updatedAt || "",
|
|
544
576
|
ownerUserId,
|
|
577
|
+
visibility,
|
|
545
578
|
packageDir: dir,
|
|
546
579
|
snippet,
|
|
580
|
+
...marketplaceStatsFor(stats, "flow-snippet", manifest.id || entry.name, manifest.version || version, ownerUserId),
|
|
547
581
|
});
|
|
548
582
|
}
|
|
549
583
|
}
|
|
550
584
|
snippets.sort((a, b) => {
|
|
585
|
+
const byUsage = Number(b.useCount || 0) - Number(a.useCount || 0);
|
|
586
|
+
const byInstall = Number(b.installCount || 0) - Number(a.installCount || 0);
|
|
551
587
|
const byTime = String(b.updatedAt || b.createdAt || "").localeCompare(String(a.updatedAt || a.createdAt || ""));
|
|
552
|
-
return byTime || a.id.localeCompare(b.id) || a.version.localeCompare(b.version);
|
|
588
|
+
return byUsage || byInstall || byTime || a.id.localeCompare(b.id) || a.version.localeCompare(b.version);
|
|
553
589
|
});
|
|
554
590
|
return { snippets };
|
|
555
591
|
}
|
|
556
592
|
|
|
593
|
+
function marketplaceRecordAccessible(manifest, opts = {}) {
|
|
594
|
+
const ownerUserId = String(manifest?.ownerUserId || manifest?.createdBy || "").trim();
|
|
595
|
+
if (!canAccessMarketplaceOwner(ownerUserId, opts)) return false;
|
|
596
|
+
if (normalizeMarketplaceVisibility(manifest?.visibility) === "public") return true;
|
|
597
|
+
const requestedUserId = String(opts.userId || "").trim();
|
|
598
|
+
if (!requestedUserId) return true;
|
|
599
|
+
return isAdminRequest(opts) || ownerUserId === requestedUserId;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export function listMarketplaceFlows(workspaceRoot, opts = {}) {
|
|
603
|
+
const base = path.join(workspacePackageRoot(workspaceRoot), "flows");
|
|
604
|
+
const stats = marketplaceUsageStats(workspaceRoot);
|
|
605
|
+
const flows = [];
|
|
606
|
+
if (!fs.existsSync(base)) return { flows };
|
|
607
|
+
for (const entry of fs.readdirSync(base, { withFileTypes: true })) {
|
|
608
|
+
if (!entry.isDirectory()) continue;
|
|
609
|
+
for (const version of listVersionDirs(path.join(base, entry.name))) {
|
|
610
|
+
const packageDir = resolveWorkspaceFlowPackageDir(workspaceRoot, entry.name, version);
|
|
611
|
+
const manifest = packageDir ? readYamlObject(path.join(packageDir, FLOW_PACKAGE_MANIFEST)) : null;
|
|
612
|
+
if (!manifest || !marketplaceRecordAccessible(manifest, opts)) continue;
|
|
613
|
+
const id = String(manifest.id || entry.name);
|
|
614
|
+
const resourceVersion = String(manifest.version || version);
|
|
615
|
+
flows.push({
|
|
616
|
+
id,
|
|
617
|
+
version: resourceVersion,
|
|
618
|
+
displayName: String(manifest.displayName || manifest.name || id),
|
|
619
|
+
description: String(manifest.description || ""),
|
|
620
|
+
tags: Array.isArray(manifest.tags) ? manifest.tags.map((tag) => String(tag)) : [],
|
|
621
|
+
ownerUserId: String(manifest.ownerUserId || manifest.createdBy || ""),
|
|
622
|
+
visibility: normalizeMarketplaceVisibility(manifest.visibility),
|
|
623
|
+
nodeCount: Number(manifest.nodeCount || 0),
|
|
624
|
+
edgeCount: Number(manifest.edgeCount || 0),
|
|
625
|
+
createdAt: String(manifest.createdAt || ""),
|
|
626
|
+
updatedAt: String(manifest.updatedAt || manifest.createdAt || ""),
|
|
627
|
+
packageDir,
|
|
628
|
+
...marketplaceStatsFor(stats, "flow", id, resourceVersion, String(manifest.ownerUserId || manifest.createdBy || "")),
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
flows.sort((a, b) => (
|
|
633
|
+
Number(b.useCount || 0) - Number(a.useCount || 0)
|
|
634
|
+
|| Number(b.installCount || 0) - Number(a.installCount || 0)
|
|
635
|
+
|| String(b.updatedAt || "").localeCompare(String(a.updatedAt || ""))
|
|
636
|
+
|| a.id.localeCompare(b.id)
|
|
637
|
+
|| b.version.localeCompare(a.version, undefined, { numeric: true, sensitivity: "base" })
|
|
638
|
+
));
|
|
639
|
+
return { flows };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
export function publishMarketplaceFlow(workspaceRoot, payload = {}, opts = {}) {
|
|
643
|
+
const ownerUserId = String(opts.userId || "").trim();
|
|
644
|
+
if (!ownerUserId) return { ok: false, error: "Authentication required" };
|
|
645
|
+
const label = String(payload.displayName || payload.name || payload.id || "flow").trim();
|
|
646
|
+
const id = safePackageId(payload.id || payload.packageId || label);
|
|
647
|
+
const version = normalizeVersion(payload.version || "1.0.0");
|
|
648
|
+
const graph = payload.graph && typeof payload.graph === "object" && !Array.isArray(payload.graph) ? payload.graph : null;
|
|
649
|
+
if (!id || !graph) return { ok: false, error: "Invalid marketplace flow" };
|
|
650
|
+
const packageDir = resolveWorkspaceFlowPackageDir(workspaceRoot, id, version);
|
|
651
|
+
if (!packageDir) return { ok: false, error: "Invalid marketplace flow id or version" };
|
|
652
|
+
const existing = readYamlObject(path.join(packageDir, FLOW_PACKAGE_MANIFEST));
|
|
653
|
+
if (existing && !canManageMarketplaceOwner(String(existing.ownerUserId || existing.createdBy || ""), opts)) {
|
|
654
|
+
return { ok: false, error: "Marketplace flow permission denied" };
|
|
655
|
+
}
|
|
656
|
+
if (existing) {
|
|
657
|
+
const existingGraph = readJsonObject(path.join(packageDir, FLOW_PACKAGE_GRAPH));
|
|
658
|
+
if (JSON.stringify(existingGraph) !== JSON.stringify(graph)) {
|
|
659
|
+
return { ok: false, conflict: true, error: `${id}@${version} 已存在且内容不同,请提升版本号` };
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
ok: true,
|
|
663
|
+
alreadyExists: true,
|
|
664
|
+
...existing,
|
|
665
|
+
visibility: normalizeMarketplaceVisibility(existing.visibility),
|
|
666
|
+
packageDir,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const now = new Date().toISOString();
|
|
670
|
+
const manifest = {
|
|
671
|
+
id,
|
|
672
|
+
version,
|
|
673
|
+
displayName: label,
|
|
674
|
+
description: String(payload.description || "").trim(),
|
|
675
|
+
tags: Array.isArray(payload.tags) ? payload.tags.map((tag) => String(tag).trim()).filter(Boolean) : [],
|
|
676
|
+
visibility: normalizeMarketplaceVisibility(payload.visibility),
|
|
677
|
+
ownerUserId,
|
|
678
|
+
createdBy: ownerUserId,
|
|
679
|
+
nodeCount: Object.keys(graph.instances || {}).length,
|
|
680
|
+
edgeCount: Array.isArray(graph.edges) ? graph.edges.length : 0,
|
|
681
|
+
createdAt: String(existing?.createdAt || now),
|
|
682
|
+
updatedAt: now,
|
|
683
|
+
};
|
|
684
|
+
fs.mkdirSync(path.dirname(packageDir), { recursive: true });
|
|
685
|
+
fs.rmSync(packageDir, { recursive: true, force: true });
|
|
686
|
+
fs.mkdirSync(packageDir, { recursive: true });
|
|
687
|
+
fs.writeFileSync(path.join(packageDir, FLOW_PACKAGE_MANIFEST), yaml.dump(manifest, { lineWidth: -1 }), "utf-8");
|
|
688
|
+
fs.writeFileSync(path.join(packageDir, FLOW_PACKAGE_GRAPH), `${JSON.stringify(graph, null, 2)}\n`, "utf-8");
|
|
689
|
+
fs.writeFileSync(path.join(packageDir, "README.md"), `# ${label}\n\n${manifest.description || "Published AgentFlow template."}\n`, "utf-8");
|
|
690
|
+
return { ok: true, ...manifest, packageDir };
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
export function readMarketplaceFlow(workspaceRoot, id, version, opts = {}) {
|
|
694
|
+
const packageDir = resolveWorkspaceFlowPackageDir(workspaceRoot, id, version);
|
|
695
|
+
const manifest = packageDir ? readYamlObject(path.join(packageDir, FLOW_PACKAGE_MANIFEST)) : null;
|
|
696
|
+
if (!manifest || !marketplaceRecordAccessible(manifest, opts)) return { ok: false, error: "Marketplace flow not found" };
|
|
697
|
+
const graph = readJsonObject(path.join(packageDir, FLOW_PACKAGE_GRAPH));
|
|
698
|
+
if (!graph) return { ok: false, error: "Marketplace flow graph is missing" };
|
|
699
|
+
return { ok: true, manifest, graph, packageDir };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export function setMarketplaceVisibility(workspaceRoot, kind, id, version, visibility, opts = {}) {
|
|
703
|
+
const normalized = normalizeMarketplaceVisibility(visibility);
|
|
704
|
+
let manifestPath = "";
|
|
705
|
+
if (kind === "flow") {
|
|
706
|
+
const dir = resolveWorkspaceFlowPackageDir(workspaceRoot, id, version);
|
|
707
|
+
manifestPath = dir ? path.join(dir, FLOW_PACKAGE_MANIFEST) : "";
|
|
708
|
+
} else if (kind === "flow-snippet") {
|
|
709
|
+
const dir = resolveWorkspaceFlowSnippetPackageDir(workspaceRoot, id, version);
|
|
710
|
+
manifestPath = dir ? path.join(dir, FLOW_SNIPPET_MANIFEST) : "";
|
|
711
|
+
} else if (kind === "node") {
|
|
712
|
+
const dir = resolveWorkspaceNodePackageDir(workspaceRoot, id, version);
|
|
713
|
+
if (!dir) return { ok: false, error: "Invalid marketplace node" };
|
|
714
|
+
const manifest = normalizeManifest(readNodeManifestRaw(dir), dir, "marketplace");
|
|
715
|
+
if (!manifest || !canManageMarketplaceOwner(manifestOwnerUserId(manifest), opts)) {
|
|
716
|
+
return { ok: false, error: "Marketplace node permission denied" };
|
|
717
|
+
}
|
|
718
|
+
const metadataPath = path.join(dir, NODE_PACKAGE_METADATA_FILENAME);
|
|
719
|
+
const metadata = readJsonObject(metadataPath) || {};
|
|
720
|
+
fs.writeFileSync(metadataPath, `${JSON.stringify({ ...metadata, visibility: normalized }, null, 2)}\n`, "utf-8");
|
|
721
|
+
return { ok: true, kind, id, version, visibility: normalized };
|
|
722
|
+
} else {
|
|
723
|
+
return { ok: false, error: "Unsupported marketplace resource kind" };
|
|
724
|
+
}
|
|
725
|
+
const manifest = manifestPath ? readYamlObject(manifestPath) : null;
|
|
726
|
+
if (!manifest || !canManageMarketplaceOwner(String(manifest.ownerUserId || manifest.createdBy || ""), opts)) {
|
|
727
|
+
return { ok: false, error: "Marketplace resource permission denied" };
|
|
728
|
+
}
|
|
729
|
+
fs.writeFileSync(manifestPath, yaml.dump({ ...manifest, visibility: normalized, updatedAt: new Date().toISOString() }, { lineWidth: -1 }), "utf-8");
|
|
730
|
+
return { ok: true, kind, id, version, visibility: normalized };
|
|
731
|
+
}
|
|
732
|
+
|
|
557
733
|
export function deleteMarketplaceNodePackage(workspaceRoot, id, version, opts = {}) {
|
|
558
734
|
const packageDir = resolveWorkspaceNodePackageDir(workspaceRoot, id, version);
|
|
559
735
|
if (!packageDir) return { ok: false, error: "Invalid marketplace node id or version" };
|
|
@@ -638,6 +814,7 @@ export function publishNodePackage(workspaceRoot, sourceDir, opts = {}) {
|
|
|
638
814
|
const ownerUserId = String(opts.ownerUserId || opts.userId || manifest.ownerUserId || manifest.createdBy || "").trim();
|
|
639
815
|
fs.writeFileSync(path.join(dest, NODE_PACKAGE_METADATA_FILENAME), `${JSON.stringify({
|
|
640
816
|
...(ownerUserId ? { ownerUserId, createdBy: ownerUserId } : {}),
|
|
817
|
+
visibility: normalizeMarketplaceVisibility(opts.visibility),
|
|
641
818
|
contentSha256: inspected.contentSha256,
|
|
642
819
|
fileList: inspected.fileList,
|
|
643
820
|
fileCount: inspected.fileCount,
|
|
@@ -692,6 +869,7 @@ export function publishNodePackageArchive(workspaceRoot, archiveInput, opts = {}
|
|
|
692
869
|
if (!written.ok) return written;
|
|
693
870
|
fs.writeFileSync(path.join(staging, NODE_PACKAGE_METADATA_FILENAME), `${JSON.stringify({
|
|
694
871
|
...(ownerUserId ? { ownerUserId, createdBy: ownerUserId } : {}),
|
|
872
|
+
visibility: normalizeMarketplaceVisibility(opts.visibility),
|
|
695
873
|
contentSha256: inspected.contentSha256,
|
|
696
874
|
archiveSha256: inspected.archiveSha256,
|
|
697
875
|
fileList: inspected.fileList,
|
|
@@ -994,6 +1172,7 @@ export function publishNodeFromInstance(workspaceRoot, payload = {}, options = {
|
|
|
994
1172
|
...(ui ? { ui } : {}),
|
|
995
1173
|
ownerUserId,
|
|
996
1174
|
createdBy: ownerUserId,
|
|
1175
|
+
visibility: normalizeMarketplaceVisibility(payload.visibility),
|
|
997
1176
|
createdAt: existingManifest?.createdAt || now,
|
|
998
1177
|
updatedAt: now,
|
|
999
1178
|
};
|
|
@@ -1056,6 +1235,7 @@ export function publishFlowSnippet(workspaceRoot, payload = {}, opts = {}) {
|
|
|
1056
1235
|
tags: Array.isArray(payload.tags) ? payload.tags.map((x) => String(x).trim()).filter(Boolean) : [],
|
|
1057
1236
|
ownerUserId,
|
|
1058
1237
|
createdBy: ownerUserId,
|
|
1238
|
+
visibility: normalizeMarketplaceVisibility(payload.visibility),
|
|
1059
1239
|
nodeCount,
|
|
1060
1240
|
edgeCount: edges.length,
|
|
1061
1241
|
createdAt: now,
|
|
@@ -29,7 +29,7 @@ import { normalizeNodeUiForSlots } from "./node-ui-kit.mjs";
|
|
|
29
29
|
export const NODE_PACKAGE_ENTRY = "index.mjs";
|
|
30
30
|
|
|
31
31
|
/** 槽位类型;与 builtin/nodes/*.md 的 `type:` 取值一致。 */
|
|
32
|
-
const SLOT_TYPES = new Set(["text", "file", "bool", "node", "image", "json"]);
|
|
32
|
+
const SLOT_TYPES = new Set(["text", "file", "bool", "node", "image", "json", "context"]);
|
|
33
33
|
|
|
34
34
|
function staticEval(node, where) {
|
|
35
35
|
if (!node) throw new Error(`${where}: 空表达式`);
|