@fieldwangai/agentflow 0.1.165 → 0.1.166
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 +105 -13
- package/bin/lib/cursor-api-key-pool.mjs +140 -31
- package/bin/lib/cursor-model-catalog.mjs +152 -0
- package/bin/lib/workspace-routes.mjs +501 -41
- package/bin/lib/workspace-server.mjs +1 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-pVdrZ-Rl.js → WorkflowAssistantThread-DKdBGu3q.js} +1 -1
- package/builtin/web-ui/dist/assets/{index-BQeq5tdj.css → index-B7YuvFR2.css} +1 -1
- package/builtin/web-ui/dist/assets/index-BUljbvrW.js +877 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +2 -2
- package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +242 -137
- package/skills/agentflow-cli/runtime/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-Czutb6ai.js +0 -873
|
@@ -51,7 +51,7 @@ import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision
|
|
|
51
51
|
import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
|
|
52
52
|
import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
|
|
53
53
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
|
|
54
|
-
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, publishWorkspaceRelease, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, readWorkspaceReleaseStatus, readWorkspaceStableRelease, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, rollbackWorkspaceRelease, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, upsertWorkspaceDeferredRun, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDeferredRunsForScope, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScheduleNextRunAt, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
|
|
54
|
+
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, publishWorkspaceRelease, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, readWorkspaceReleaseStatus, readWorkspaceRunUsageRecords, readWorkspaceStableRelease, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, rollbackWorkspaceRelease, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, upsertWorkspaceDeferredRun, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDeferredRunsForScope, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScheduleNextRunAt, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
|
|
55
55
|
import { splitWorkspaceGraph, WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
56
56
|
import { getWorkspaceTree } from "./workspace-tree.mjs";
|
|
57
57
|
import busboy from "busboy";
|
|
@@ -248,6 +248,170 @@ function missingWorkspaceGraphNodePackages(workspaceRoot, scoped, graph, userCtx
|
|
|
248
248
|
return [...missing].sort();
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
const NODE_REVIEW_MAX_FILES = 100;
|
|
252
|
+
const NODE_REVIEW_MAX_FILE_BYTES = 256 * 1024;
|
|
253
|
+
const NODE_REVIEW_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
254
|
+
const NODE_REVIEW_TEXT_EXTENSIONS = new Set([
|
|
255
|
+
".cjs", ".css", ".env", ".go", ".html", ".ini", ".java", ".js", ".json", ".jsx", ".kt",
|
|
256
|
+
".md", ".mdx", ".mjs", ".py", ".rs", ".scss", ".sh", ".sql", ".toml", ".ts", ".tsx",
|
|
257
|
+
".txt", ".xml", ".yaml", ".yml",
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
function workspaceNodeReviewLanguage(filePath = "") {
|
|
261
|
+
const extension = path.extname(String(filePath || "")).toLowerCase().replace(/^\./, "");
|
|
262
|
+
const aliases = { cjs: "javascript", js: "javascript", jsx: "javascript", mjs: "javascript", py: "python", sh: "shell", ts: "typescript", tsx: "typescript", yml: "yaml" };
|
|
263
|
+
return aliases[extension] || extension || "text";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function workspaceNodeReviewSource({ sourcePath = "", title = "", kind = "file", content = "" } = {}) {
|
|
267
|
+
const text = String(content || "").replace(/\r\n/g, "\n");
|
|
268
|
+
const reviewPath = String(sourcePath || title || "source.txt").replace(/\\/g, "/");
|
|
269
|
+
return {
|
|
270
|
+
path: reviewPath,
|
|
271
|
+
title: String(title || path.basename(reviewPath) || reviewPath),
|
|
272
|
+
kind,
|
|
273
|
+
language: workspaceNodeReviewLanguage(reviewPath),
|
|
274
|
+
size: Buffer.byteLength(text),
|
|
275
|
+
sha256: crypto.createHash("sha256").update(text).digest("hex"),
|
|
276
|
+
content: text,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function workspaceNodeReviewFlowFile(flowRoot, fileRef, kind) {
|
|
281
|
+
const ref = String(fileRef || "").trim();
|
|
282
|
+
if (!ref) return null;
|
|
283
|
+
try {
|
|
284
|
+
const { abs, rel } = resolveWorkspaceFilePath(flowRoot, ref);
|
|
285
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
|
|
286
|
+
return { error: `找不到 ${ref}` };
|
|
287
|
+
}
|
|
288
|
+
const stat = fs.statSync(abs);
|
|
289
|
+
if (stat.size > NODE_REVIEW_MAX_FILE_BYTES) {
|
|
290
|
+
return { error: `${ref} 超过可预览大小限制` };
|
|
291
|
+
}
|
|
292
|
+
return workspaceNodeReviewSource({ sourcePath: rel, title: rel, kind, content: fs.readFileSync(abs, "utf-8") });
|
|
293
|
+
} catch (error) {
|
|
294
|
+
return { error: `${ref}: ${(error && error.message) || String(error)}` };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function workspaceNodeReviewPackageFiles(packageDir) {
|
|
299
|
+
const root = path.resolve(packageDir);
|
|
300
|
+
const candidates = [];
|
|
301
|
+
const walk = (dir) => {
|
|
302
|
+
if (candidates.length >= NODE_REVIEW_MAX_FILES) return;
|
|
303
|
+
let entries = [];
|
|
304
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
305
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
306
|
+
for (const entry of entries) {
|
|
307
|
+
if (candidates.length >= NODE_REVIEW_MAX_FILES) break;
|
|
308
|
+
if ([".git", "node_modules"].includes(entry.name)) continue;
|
|
309
|
+
const abs = path.join(dir, entry.name);
|
|
310
|
+
if (entry.isDirectory()) walk(abs);
|
|
311
|
+
else if (entry.isFile() && NODE_REVIEW_TEXT_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) candidates.push(abs);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
walk(root);
|
|
315
|
+
candidates.sort((left, right) => {
|
|
316
|
+
const leftRel = path.relative(root, left).replace(/\\/g, "/");
|
|
317
|
+
const rightRel = path.relative(root, right).replace(/\\/g, "/");
|
|
318
|
+
if (leftRel === NODE_PACKAGE_ENTRY) return -1;
|
|
319
|
+
if (rightRel === NODE_PACKAGE_ENTRY) return 1;
|
|
320
|
+
return leftRel.localeCompare(rightRel);
|
|
321
|
+
});
|
|
322
|
+
const sources = [];
|
|
323
|
+
let totalBytes = 0;
|
|
324
|
+
for (const abs of candidates) {
|
|
325
|
+
let stat;
|
|
326
|
+
try { stat = fs.statSync(abs); } catch { continue; }
|
|
327
|
+
if (!stat.isFile() || stat.size > NODE_REVIEW_MAX_FILE_BYTES || totalBytes + stat.size > NODE_REVIEW_MAX_TOTAL_BYTES) continue;
|
|
328
|
+
const rel = path.relative(root, abs).replace(/\\/g, "/");
|
|
329
|
+
sources.push(workspaceNodeReviewSource({ sourcePath: rel, title: rel, kind: "package", content: fs.readFileSync(abs, "utf-8") }));
|
|
330
|
+
totalBytes += stat.size;
|
|
331
|
+
}
|
|
332
|
+
return sources;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function workspaceNodeReviewSnapshot(workspaceRoot, flowRoot, graph, nodeId, userCtx = {}, label = "Draft") {
|
|
336
|
+
const instance = graph?.instances?.[nodeId];
|
|
337
|
+
if (!instance) return null;
|
|
338
|
+
const definitionId = String(instance.definitionId || nodeId);
|
|
339
|
+
const marketplaceRef = String(instance.marketplaceRef || definitionId).trim();
|
|
340
|
+
const sources = [];
|
|
341
|
+
const errors = [];
|
|
342
|
+
let packageInfo = null;
|
|
343
|
+
|
|
344
|
+
if (marketplaceRef.startsWith("marketplace:")) {
|
|
345
|
+
const resolved = resolveMarketplaceNodePackage(
|
|
346
|
+
workspaceRoot,
|
|
347
|
+
flowRoot,
|
|
348
|
+
marketplaceRef,
|
|
349
|
+
graph,
|
|
350
|
+
{ ...userCtx, marketplaceScope: "all" },
|
|
351
|
+
);
|
|
352
|
+
if (!resolved) {
|
|
353
|
+
errors.push(`无法解析节点包 ${marketplaceRef}`);
|
|
354
|
+
} else {
|
|
355
|
+
const inspected = inspectNodePackageDirectory(resolved.packageDir, { allowLegacyManifest: true });
|
|
356
|
+
sources.push(...workspaceNodeReviewPackageFiles(resolved.packageDir));
|
|
357
|
+
packageInfo = {
|
|
358
|
+
id: resolved.id,
|
|
359
|
+
version: resolved.version,
|
|
360
|
+
definitionId: resolved.resolvedDefinitionId || marketplaceRef,
|
|
361
|
+
source: resolved.source || "marketplace",
|
|
362
|
+
contentSha256: inspected.ok ? inspected.contentSha256 : String(resolved.contentSha256 || ""),
|
|
363
|
+
};
|
|
364
|
+
if (!sources.length) errors.push(`节点包 ${marketplaceRef} 没有可预览的文本文件`);
|
|
365
|
+
}
|
|
366
|
+
} else if (String(instance.script || "").trim()) {
|
|
367
|
+
sources.push(workspaceNodeReviewSource({
|
|
368
|
+
sourcePath: `nodes/${nodeId}/inline-script.mjs`,
|
|
369
|
+
title: "内联脚本",
|
|
370
|
+
kind: "inline",
|
|
371
|
+
content: instance.script,
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
for (const [ref, kind] of [[instance.scriptRef, "script"], [instance.implementationRef, "implementation"]]) {
|
|
376
|
+
const source = workspaceNodeReviewFlowFile(flowRoot, ref, kind);
|
|
377
|
+
if (source?.error) errors.push(source.error);
|
|
378
|
+
else if (source) sources.push(source);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const body = String(instance.body || "");
|
|
382
|
+
if (body.trim()) {
|
|
383
|
+
const looksJson = /^[\[{]/.test(body.trim());
|
|
384
|
+
sources.push(workspaceNodeReviewSource({
|
|
385
|
+
sourcePath: `nodes/${nodeId}/${looksJson ? "configuration.json" : "prompt.md"}`,
|
|
386
|
+
title: looksJson ? "节点配置" : "Prompt / 指令",
|
|
387
|
+
kind: looksJson ? "configuration" : "prompt",
|
|
388
|
+
content: body,
|
|
389
|
+
}));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const uniqueSources = [];
|
|
393
|
+
const seen = new Set();
|
|
394
|
+
for (const source of sources) {
|
|
395
|
+
const key = `${source.kind}\u0000${source.path}\u0000${source.sha256}`;
|
|
396
|
+
if (seen.has(key)) continue;
|
|
397
|
+
seen.add(key);
|
|
398
|
+
uniqueSources.push(source);
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
label,
|
|
402
|
+
nodeId,
|
|
403
|
+
definitionId,
|
|
404
|
+
model: String(instance.model || ""),
|
|
405
|
+
role: String(instance.role || "normal"),
|
|
406
|
+
implementationMode: String(instance.implementationMode || ""),
|
|
407
|
+
marketplaceRef: marketplaceRef.startsWith("marketplace:") ? marketplaceRef : "",
|
|
408
|
+
package: packageInfo,
|
|
409
|
+
sources: uniqueSources,
|
|
410
|
+
errors,
|
|
411
|
+
reviewable: uniqueSources.length > 0,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
251
415
|
const NODE_STUDIO_DRAFTS_DIRNAME = "node-studio/drafts";
|
|
252
416
|
|
|
253
417
|
function writeUserWorkspaces(userCtx = {}, entries = []) {
|
|
@@ -964,11 +1128,47 @@ function writeProjectFlowMarketplaceMetadata(flowRoot, visibility) {
|
|
|
964
1128
|
return value;
|
|
965
1129
|
}
|
|
966
1130
|
|
|
967
|
-
function
|
|
968
|
-
|
|
1131
|
+
function runnableProjectFlowEntries(graph, scopedRoot = "") {
|
|
1132
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
1133
|
+
const entries = Object.entries(instances).filter(([, instance]) => (
|
|
969
1134
|
instance?.definitionId === "workspace_run"
|
|
970
1135
|
|| instance?.definitionId === "workspace_scheduled_run"
|
|
971
1136
|
));
|
|
1137
|
+
return entries.flatMap(([entryId, entry]) => {
|
|
1138
|
+
let plan;
|
|
1139
|
+
try {
|
|
1140
|
+
plan = workspaceRunPlan(graph, entryId, scopedRoot, { ignoreCache: true });
|
|
1141
|
+
} catch {
|
|
1142
|
+
return [];
|
|
1143
|
+
}
|
|
1144
|
+
const executedNodeIds = Array.from(new Set((plan.order || []).map((id) => String(id || "").trim()).filter(Boolean)));
|
|
1145
|
+
if (executedNodeIds.length === 0) return [];
|
|
1146
|
+
const includedNodeIds = new Set([entryId, ...executedNodeIds]);
|
|
1147
|
+
const positions = graph?.ui?.nodePositions && typeof graph.ui.nodePositions === "object"
|
|
1148
|
+
? Object.fromEntries(Object.entries(graph.ui.nodePositions).filter(([id]) => includedNodeIds.has(id)))
|
|
1149
|
+
: {};
|
|
1150
|
+
const sizes = graph?.ui?.nodeSizes && typeof graph.ui.nodeSizes === "object"
|
|
1151
|
+
? Object.fromEntries(Object.entries(graph.ui.nodeSizes).filter(([id]) => includedNodeIds.has(id)))
|
|
1152
|
+
: {};
|
|
1153
|
+
return [{
|
|
1154
|
+
entryId,
|
|
1155
|
+
entry,
|
|
1156
|
+
runMode: entry.definitionId === "workspace_scheduled_run" ? "scheduled" : "manual",
|
|
1157
|
+
graph: {
|
|
1158
|
+
...graph,
|
|
1159
|
+
instances: Object.fromEntries(Object.entries(instances).filter(([id]) => includedNodeIds.has(id))),
|
|
1160
|
+
edges: (Array.isArray(graph?.edges) ? graph.edges : []).filter((edge) => (
|
|
1161
|
+
includedNodeIds.has(String(edge?.source || ""))
|
|
1162
|
+
&& includedNodeIds.has(String(edge?.target || ""))
|
|
1163
|
+
)),
|
|
1164
|
+
ui: {
|
|
1165
|
+
...(graph?.ui || {}),
|
|
1166
|
+
nodePositions: positions,
|
|
1167
|
+
nodeSizes: sizes,
|
|
1168
|
+
},
|
|
1169
|
+
},
|
|
1170
|
+
}];
|
|
1171
|
+
});
|
|
972
1172
|
}
|
|
973
1173
|
|
|
974
1174
|
function projectFlowUpdatedAt(flowRoot, metadata = {}) {
|
|
@@ -983,6 +1183,7 @@ function projectFlowUpdatedAt(flowRoot, metadata = {}) {
|
|
|
983
1183
|
function listRunnableProjectMarketplaceFlows(workspaceRoot, userCtx = {}, scope = "all") {
|
|
984
1184
|
const requestedUserId = String(userCtx.userId || "").trim();
|
|
985
1185
|
const stats = marketplaceUsageStats(workspaceRoot);
|
|
1186
|
+
const runUsage = readWorkspaceRunUsageRecords();
|
|
986
1187
|
const resources = [];
|
|
987
1188
|
const appendFlow = (ownerId, flow, flowSource = "user", workspaceId = "") => {
|
|
988
1189
|
if (!ownerId || (scope === "owned" && ownerId !== requestedUserId)) return;
|
|
@@ -995,34 +1196,60 @@ function listRunnableProjectMarketplaceFlows(workspaceRoot, userCtx = {}, scope
|
|
|
995
1196
|
} catch {
|
|
996
1197
|
return;
|
|
997
1198
|
}
|
|
998
|
-
|
|
1199
|
+
const runnableEntries = runnableProjectFlowEntries(graph, flow.path);
|
|
1200
|
+
if (runnableEntries.length === 0) return;
|
|
999
1201
|
const metadata = readProjectFlowMarketplaceMetadata(flow.path);
|
|
1000
1202
|
const owned = ownerId === requestedUserId;
|
|
1001
1203
|
if (scope !== "owned" && metadata.visibility === "private" && !owned && userCtx.isAdmin !== true) return;
|
|
1002
|
-
const
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1204
|
+
for (const runnable of runnableEntries) {
|
|
1205
|
+
const baseId = projectFlowMarketplaceId(ownerId, flowSource, flow.id);
|
|
1206
|
+
const id = runnableEntries.length === 1 ? baseId : `${baseId}:${runnable.entryId}`;
|
|
1207
|
+
const version = stable?.release?.id || `current-${workspaceDesignRevision(graph).slice(0, 12)}`;
|
|
1208
|
+
const rawEntryLabel = String(runnable.entry?.label || "").trim();
|
|
1209
|
+
const genericLabel = ["", "Run", "Scheduled Run", "运行", "定时运行"].includes(rawEntryLabel);
|
|
1210
|
+
const directRuns = runUsage.filter((run) => (
|
|
1211
|
+
run.status === "success"
|
|
1212
|
+
&& run.flowId === flow.id
|
|
1213
|
+
&& (run.flowSource || "user") === flowSource
|
|
1214
|
+
&& (flowSource !== "user" || run.userId === ownerId)
|
|
1215
|
+
&& (run.runNodeId ? run.runNodeId === runnable.entryId : runnableEntries.length === 1)
|
|
1216
|
+
));
|
|
1217
|
+
const telemetry = marketplaceStatsFor(stats, "project-flow", id, version);
|
|
1218
|
+
const directLastUsedAt = directRuns.reduce((latest, run) => {
|
|
1219
|
+
const value = new Date(Number(run.endedAt || run.at || 0)).toISOString();
|
|
1220
|
+
return !latest || value > latest ? value : latest;
|
|
1221
|
+
}, "");
|
|
1222
|
+
resources.push({
|
|
1223
|
+
resourceType: "flow",
|
|
1224
|
+
projectFlow: true,
|
|
1225
|
+
id,
|
|
1226
|
+
definitionId: `${flow.id}/${runnable.entryId}`,
|
|
1227
|
+
displayName: runnableEntries.length === 1
|
|
1228
|
+
? flow.id
|
|
1229
|
+
: `${flow.id} · ${genericLabel ? runnable.entryId : rawEntryLabel}`,
|
|
1230
|
+
description: flow.description || "",
|
|
1231
|
+
version,
|
|
1232
|
+
versionLabel: stable?.release?.id ? `Stable ${stable.release.id}` : "当前版本",
|
|
1233
|
+
runMode: runnable.runMode,
|
|
1234
|
+
runModeLabel: runnable.runMode === "scheduled" ? "定时运行" : "手动运行",
|
|
1235
|
+
ownerUserId: ownerId,
|
|
1236
|
+
liveOwnerUserId: ownerId,
|
|
1237
|
+
liveFlowId: flow.id,
|
|
1238
|
+
liveFlowSource: flowSource,
|
|
1239
|
+
liveWorkspaceId: workspaceId,
|
|
1240
|
+
liveEntryId: runnable.entryId,
|
|
1241
|
+
installFlowId: runnableEntries.length === 1 ? flow.id : `${flow.id}-${runnable.entryId}`,
|
|
1242
|
+
visibility: metadata.visibility,
|
|
1243
|
+
nodeCount: Object.keys(runnable.graph?.instances || {}).length,
|
|
1244
|
+
edgeCount: Array.isArray(runnable.graph?.edges) ? runnable.graph.edges.length : 0,
|
|
1245
|
+
updatedAt: projectFlowUpdatedAt(flow.path, metadata),
|
|
1246
|
+
...telemetry,
|
|
1247
|
+
useCount: telemetry.useCount + directRuns.length,
|
|
1248
|
+
lastUsedAt: [telemetry.lastUsedAt, directLastUsedAt].filter(Boolean).sort().at(-1) || "",
|
|
1249
|
+
_graph: runnable.graph,
|
|
1250
|
+
_flowRoot: flow.path,
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1026
1253
|
};
|
|
1027
1254
|
for (const ownerUserId of listAgentflowUserIds()) {
|
|
1028
1255
|
const ownerId = String(ownerUserId || "").trim();
|
|
@@ -1062,7 +1289,6 @@ function marketplaceResourceMatches(item, queryText) {
|
|
|
1062
1289
|
function sortMarketplaceResources(items) {
|
|
1063
1290
|
return items.sort((a, b) => (
|
|
1064
1291
|
Number(b.useCount || 0) - Number(a.useCount || 0)
|
|
1065
|
-
|| Number(b.installCount || 0) - Number(a.installCount || 0)
|
|
1066
1292
|
|| String(b.updatedAt || b.createdAt || "").localeCompare(String(a.updatedAt || a.createdAt || ""))
|
|
1067
1293
|
|| String(a.displayName || a.id).localeCompare(String(b.displayName || b.id))
|
|
1068
1294
|
|| String(b.version || "").localeCompare(String(a.version || ""), undefined, { numeric: true, sensitivity: "base" })
|
|
@@ -1225,15 +1451,6 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1225
1451
|
return;
|
|
1226
1452
|
}
|
|
1227
1453
|
const installedCopies = installedMarketplaceFlowCopies(userCtx.userId);
|
|
1228
|
-
const flows = listMarketplaceFlows(root, { ...userCtx, marketplaceScope }).flows.map((flow) => {
|
|
1229
|
-
const installedFlowIds = installedCopies.get(`${flow.id}@${flow.version}`) || [];
|
|
1230
|
-
return {
|
|
1231
|
-
...flow,
|
|
1232
|
-
resourceType: "flow",
|
|
1233
|
-
installed: installedFlowIds.length > 0,
|
|
1234
|
-
installedFlowIds,
|
|
1235
|
-
};
|
|
1236
|
-
});
|
|
1237
1454
|
const projectFlows = listRunnableProjectMarketplaceFlows(root, userCtx, scope).map((flow) => {
|
|
1238
1455
|
const installedFlowIds = installedCopies.get(`${flow.id}@${flow.version}`) || [];
|
|
1239
1456
|
return publicProjectFlowMarketplaceResource({
|
|
@@ -1242,10 +1459,8 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1242
1459
|
installedFlowIds,
|
|
1243
1460
|
});
|
|
1244
1461
|
});
|
|
1245
|
-
const snippets = scope === "installed" ? [] : listMarketplaceFlowSnippets(root, { ...userCtx, marketplaceScope }).snippets
|
|
1246
|
-
.map((snippet) => ({ ...snippet, resourceType: "flow-snippet", installed: false }));
|
|
1247
1462
|
const items = sortMarketplaceResources(
|
|
1248
|
-
|
|
1463
|
+
projectFlows
|
|
1249
1464
|
.filter((item) => scope !== "installed" || item.installed)
|
|
1250
1465
|
.filter((item) => marketplaceResourceMatches(item, queryText)),
|
|
1251
1466
|
);
|
|
@@ -1293,6 +1508,208 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1293
1508
|
return;
|
|
1294
1509
|
}
|
|
1295
1510
|
|
|
1511
|
+
if (req.method === "GET" && url.pathname === "/api/marketplace/flows/preview") {
|
|
1512
|
+
const id = String(url.searchParams.get("id") || "").trim();
|
|
1513
|
+
const version = String(url.searchParams.get("version") || "").trim();
|
|
1514
|
+
const projectFlow = url.searchParams.get("projectFlow") === "1";
|
|
1515
|
+
if (!id || !version) {
|
|
1516
|
+
json(res, 400, { error: "Missing marketplace flow id or version" });
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
const installedCopies = installedMarketplaceFlowCopies(userCtx.userId);
|
|
1521
|
+
if (projectFlow) {
|
|
1522
|
+
const source = listRunnableProjectMarketplaceFlows(root, userCtx, "all")
|
|
1523
|
+
.find((flow) => flow.id === id);
|
|
1524
|
+
if (!source) {
|
|
1525
|
+
json(res, 404, { error: "Project flow not found or is private" });
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
if (source.version !== version) {
|
|
1529
|
+
json(res, 409, { error: "该流程已有新版本,请刷新流程仓库后重试" });
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
json(res, 200, {
|
|
1533
|
+
flow: {
|
|
1534
|
+
...publicProjectFlowMarketplaceResource(source),
|
|
1535
|
+
owned: source.ownerUserId === userCtx.userId,
|
|
1536
|
+
installedFlowIds: installedCopies.get(`${id}@${version}`) || [],
|
|
1537
|
+
},
|
|
1538
|
+
graph: source._graph,
|
|
1539
|
+
});
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
|
|
1543
|
+
if (!source.ok) {
|
|
1544
|
+
json(res, 404, { error: source.error || "Marketplace flow not found" });
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
const metadata = listMarketplaceFlows(root, { ...userCtx, marketplaceScope: "all" }).flows
|
|
1548
|
+
.find((flow) => flow.id === id && flow.version === version) || {};
|
|
1549
|
+
json(res, 200, {
|
|
1550
|
+
flow: {
|
|
1551
|
+
...metadata,
|
|
1552
|
+
id,
|
|
1553
|
+
version,
|
|
1554
|
+
resourceType: "flow",
|
|
1555
|
+
owned: metadata.ownerUserId === userCtx.userId,
|
|
1556
|
+
installedFlowIds: installedCopies.get(`${id}@${version}`) || [],
|
|
1557
|
+
},
|
|
1558
|
+
graph: source.graph,
|
|
1559
|
+
});
|
|
1560
|
+
} catch (e) {
|
|
1561
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1562
|
+
}
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/workspace-preview") {
|
|
1567
|
+
let payload;
|
|
1568
|
+
try {
|
|
1569
|
+
payload = JSON.parse(await readBody(req));
|
|
1570
|
+
} catch {
|
|
1571
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
const id = String(payload?.id || "").trim();
|
|
1575
|
+
const version = String(payload?.version || "").trim();
|
|
1576
|
+
const previewKind = ["snippet", "node"].includes(payload?.kind) ? payload.kind : "flow";
|
|
1577
|
+
const projectFlow = payload?.projectFlow === true;
|
|
1578
|
+
if (!id || !version) {
|
|
1579
|
+
json(res, 400, { error: "Missing marketplace flow id or version" });
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
try {
|
|
1583
|
+
const installedCopies = installedMarketplaceFlowCopies(userCtx.userId);
|
|
1584
|
+
let graph;
|
|
1585
|
+
let resource;
|
|
1586
|
+
if (previewKind === "node") {
|
|
1587
|
+
const node = listMarketplacePackages(root, { ...userCtx, marketplaceScope: "all" }).nodes
|
|
1588
|
+
.find((item) => item.id === id && item.version === version);
|
|
1589
|
+
if (!node) {
|
|
1590
|
+
json(res, 404, { error: "Node package not found or is private" });
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
graph = {
|
|
1594
|
+
version: 1,
|
|
1595
|
+
instances: {
|
|
1596
|
+
node_preview: {
|
|
1597
|
+
definitionId: node.definitionId,
|
|
1598
|
+
marketplaceRef: node.definitionId,
|
|
1599
|
+
marketplacePackageId: node.id,
|
|
1600
|
+
marketplaceVersion: node.version,
|
|
1601
|
+
label: node.displayName || node.id,
|
|
1602
|
+
role: "normal",
|
|
1603
|
+
body: "",
|
|
1604
|
+
input: Array.isArray(node.inputs) ? node.inputs : [],
|
|
1605
|
+
output: Array.isArray(node.outputs) ? node.outputs : [],
|
|
1606
|
+
},
|
|
1607
|
+
},
|
|
1608
|
+
edges: [],
|
|
1609
|
+
ui: { nodePositions: { node_preview: { x: 320, y: 220 } } },
|
|
1610
|
+
};
|
|
1611
|
+
resource = { ...node, resourceType: "node", owned: node.ownerUserId === userCtx.userId };
|
|
1612
|
+
} else if (previewKind === "snippet") {
|
|
1613
|
+
const snippet = listMarketplaceFlowSnippets(root, { ...userCtx, marketplaceScope: "all" }).snippets
|
|
1614
|
+
.find((item) => item.id === id && item.version === version);
|
|
1615
|
+
if (!snippet) {
|
|
1616
|
+
json(res, 404, { error: "Flow snippet not found or is private" });
|
|
1617
|
+
return;
|
|
1618
|
+
}
|
|
1619
|
+
graph = snippet.snippet;
|
|
1620
|
+
resource = { ...snippet, resourceType: "flow-snippet" };
|
|
1621
|
+
} else if (projectFlow) {
|
|
1622
|
+
const source = listRunnableProjectMarketplaceFlows(root, userCtx, "all")
|
|
1623
|
+
.find((flow) => flow.id === id);
|
|
1624
|
+
if (!source) {
|
|
1625
|
+
json(res, 404, { error: "Project flow not found or is private" });
|
|
1626
|
+
return;
|
|
1627
|
+
}
|
|
1628
|
+
if (source.version !== version) {
|
|
1629
|
+
json(res, 409, { error: "该流程已有新版本,请刷新流程仓库后重试" });
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
graph = source._graph;
|
|
1633
|
+
resource = {
|
|
1634
|
+
...publicProjectFlowMarketplaceResource(source),
|
|
1635
|
+
owned: source.ownerUserId === userCtx.userId,
|
|
1636
|
+
installedFlowIds: installedCopies.get(`${id}@${version}`) || [],
|
|
1637
|
+
};
|
|
1638
|
+
} else {
|
|
1639
|
+
const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
|
|
1640
|
+
if (!source.ok) {
|
|
1641
|
+
json(res, 404, { error: source.error || "Marketplace flow not found" });
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const metadata = listMarketplaceFlows(root, { ...userCtx, marketplaceScope: "all" }).flows
|
|
1645
|
+
.find((flow) => flow.id === id && flow.version === version) || {};
|
|
1646
|
+
graph = source.graph;
|
|
1647
|
+
resource = {
|
|
1648
|
+
...metadata,
|
|
1649
|
+
id,
|
|
1650
|
+
version,
|
|
1651
|
+
resourceType: "flow",
|
|
1652
|
+
owned: metadata.ownerUserId === userCtx.userId,
|
|
1653
|
+
installedFlowIds: installedCopies.get(`${id}@${version}`) || [],
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
const flowId = createWorkspacePreviewId();
|
|
1657
|
+
const flowDir = workspaceSharedPreviewFlowDir(root, flowId);
|
|
1658
|
+
const now = Date.now();
|
|
1659
|
+
const metadata = {
|
|
1660
|
+
version: 1,
|
|
1661
|
+
flowId,
|
|
1662
|
+
ownerId: authUser.userId,
|
|
1663
|
+
title: String(resource.displayName || resource.definitionId || id).trim().slice(0, 200),
|
|
1664
|
+
createdAt: new Date(now).toISOString(),
|
|
1665
|
+
updatedAt: new Date(now).toISOString(),
|
|
1666
|
+
expiresAt: new Date(now + DEFAULT_WORKSPACE_PREVIEW_TTL_MS).toISOString(),
|
|
1667
|
+
marketplace: { id, version, projectFlow },
|
|
1668
|
+
};
|
|
1669
|
+
fs.mkdirSync(flowDir, { recursive: true });
|
|
1670
|
+
writeWorkspaceGraph(flowDir, graph, root);
|
|
1671
|
+
writeWorkspacePreviewMetadata(flowDir, metadata);
|
|
1672
|
+
const installedFlowId = resource.installedFlowIds?.[0] || "";
|
|
1673
|
+
const action = previewKind === "node"
|
|
1674
|
+
? "add-node"
|
|
1675
|
+
: previewKind === "snippet" ? "add-snippet"
|
|
1676
|
+
: resource.projectFlow && resource.owned
|
|
1677
|
+
? "open-source"
|
|
1678
|
+
: installedFlowId ? "open-installed" : "install";
|
|
1679
|
+
const previewParams = new URLSearchParams({
|
|
1680
|
+
flowId,
|
|
1681
|
+
flowSource: "workspace",
|
|
1682
|
+
archived: "1",
|
|
1683
|
+
marketplacePreview: "1",
|
|
1684
|
+
marketplaceKind: previewKind,
|
|
1685
|
+
marketplaceResourceId: id,
|
|
1686
|
+
marketplaceVersion: version,
|
|
1687
|
+
marketplaceProjectFlow: projectFlow ? "1" : "0",
|
|
1688
|
+
marketplaceTitle: resource.displayName || resource.definitionId || id,
|
|
1689
|
+
marketplaceAction: action,
|
|
1690
|
+
marketplaceInstallFlowId: resource.installFlowId || resource.liveFlowId || resource.definitionId || id,
|
|
1691
|
+
});
|
|
1692
|
+
if (previewKind === "node") previewParams.set("focusNodeId", "node_preview");
|
|
1693
|
+
if (action === "open-source") {
|
|
1694
|
+
previewParams.set("marketplaceTargetFlowId", resource.liveFlowId || resource.definitionId || "");
|
|
1695
|
+
previewParams.set("marketplaceTargetFlowSource", resource.liveFlowSource || "user");
|
|
1696
|
+
if (resource.liveWorkspaceId) previewParams.set("marketplaceTargetWorkspaceId", resource.liveWorkspaceId);
|
|
1697
|
+
} else if (action === "open-installed") {
|
|
1698
|
+
previewParams.set("marketplaceTargetFlowId", installedFlowId);
|
|
1699
|
+
previewParams.set("marketplaceTargetFlowSource", "user");
|
|
1700
|
+
}
|
|
1701
|
+
json(res, 200, {
|
|
1702
|
+
ok: true,
|
|
1703
|
+
preview: true,
|
|
1704
|
+
expiresAt: metadata.expiresAt,
|
|
1705
|
+
url: `/workspace?${previewParams}`,
|
|
1706
|
+
});
|
|
1707
|
+
} catch (e) {
|
|
1708
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1709
|
+
}
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1296
1713
|
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/publish") {
|
|
1297
1714
|
let payload;
|
|
1298
1715
|
try {
|
|
@@ -1729,6 +2146,49 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1729
2146
|
return;
|
|
1730
2147
|
}
|
|
1731
2148
|
|
|
2149
|
+
if (req.method === "GET" && url.pathname === "/api/workspace/node-review") {
|
|
2150
|
+
try {
|
|
2151
|
+
const nodeId = String(url.searchParams.get("nodeId") || "").trim();
|
|
2152
|
+
if (!nodeId) {
|
|
2153
|
+
json(res, 400, { error: "Missing nodeId" });
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
2157
|
+
flowId: url.searchParams.get("flowId") || "",
|
|
2158
|
+
flowSource: url.searchParams.get("flowSource") || "user",
|
|
2159
|
+
workspaceId: url.searchParams.get("workspaceId") || "",
|
|
2160
|
+
adminOwnerId: url.searchParams.get("adminOwnerId") || "",
|
|
2161
|
+
archived: url.searchParams.get("archived") === "1",
|
|
2162
|
+
}, userCtx);
|
|
2163
|
+
if (scoped.error) {
|
|
2164
|
+
json(res, scoped.status || 400, { error: scoped.error });
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
const scopedUserCtx = workspaceScopedUserContext(scoped, userCtx);
|
|
2168
|
+
const draftGraph = readWorkspaceGraph(scoped.root, root).graph;
|
|
2169
|
+
const stableRelease = readWorkspaceStableRelease(scoped.root, root);
|
|
2170
|
+
const draft = workspaceNodeReviewSnapshot(root, scoped.root, draftGraph, nodeId, scopedUserCtx, "Draft");
|
|
2171
|
+
const stable = stableRelease
|
|
2172
|
+
? workspaceNodeReviewSnapshot(root, stableRelease.root, stableRelease.graph, nodeId, scopedUserCtx, `Stable ${stableRelease.release.id}`)
|
|
2173
|
+
: null;
|
|
2174
|
+
if (!draft && !stable) {
|
|
2175
|
+
json(res, 404, { error: "Node not found" });
|
|
2176
|
+
return;
|
|
2177
|
+
}
|
|
2178
|
+
json(res, 200, {
|
|
2179
|
+
nodeId,
|
|
2180
|
+
draft,
|
|
2181
|
+
stable,
|
|
2182
|
+
stableReleaseId: stableRelease?.release?.id || "",
|
|
2183
|
+
stableRevision: stableRelease?.release?.designRevision || "",
|
|
2184
|
+
draftRevision: workspaceDesignRevision(draftGraph),
|
|
2185
|
+
});
|
|
2186
|
+
} catch (e) {
|
|
2187
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
2188
|
+
}
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
1732
2192
|
if (req.method === "GET" && url.pathname === "/api/workspaces") {
|
|
1733
2193
|
try {
|
|
1734
2194
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
@@ -1182,6 +1182,7 @@ function normalizeWorkspaceUsageRecord(parsed, source = "workspace-run") {
|
|
|
1182
1182
|
username: String(parsed?.username || userId),
|
|
1183
1183
|
flowId,
|
|
1184
1184
|
flowSource: String(parsed?.flowSource || "user"),
|
|
1185
|
+
runNodeId: String(parsed?.runNodeId || ""),
|
|
1185
1186
|
runId: String(parsed?.runId || ""),
|
|
1186
1187
|
at,
|
|
1187
1188
|
endedAt: parsed?.endedAt == null ? null : Number(parsed.endedAt),
|