@fieldwangai/agentflow 0.1.164 → 0.1.165
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/marketplace-usage.mjs +4 -3
- package/bin/lib/workspace-routes.mjs +210 -14
- package/bin/lib/workspace-server.mjs +29 -14
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CffIx5BY.js → WorkflowAssistantThread-pVdrZ-Rl.js} +1 -1
- package/builtin/web-ui/dist/assets/{index-CDItaRfX.css → index-BQeq5tdj.css} +1 -1
- package/builtin/web-ui/dist/assets/{index-CWIcfWHO.js → index-Czutb6ai.js} +139 -139
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -155,8 +155,9 @@ export function marketplaceFlowOriginPath(flowDir) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
export function writeMarketplaceFlowOrigin(flowDir, origin = {}) {
|
|
158
|
+
const kind = safeText(origin.kind, 32) === "project-flow" ? "project-flow" : "flow";
|
|
158
159
|
const value = {
|
|
159
|
-
kind
|
|
160
|
+
kind,
|
|
160
161
|
id: safeText(origin.id),
|
|
161
162
|
version: safeText(origin.version, 80),
|
|
162
163
|
installedAt: safeText(origin.installedAt || new Date().toISOString(), 80),
|
|
@@ -169,8 +170,8 @@ export function writeMarketplaceFlowOrigin(flowDir, origin = {}) {
|
|
|
169
170
|
export function readMarketplaceFlowOrigin(flowDir) {
|
|
170
171
|
try {
|
|
171
172
|
const parsed = JSON.parse(fs.readFileSync(marketplaceFlowOriginPath(flowDir), "utf-8"));
|
|
172
|
-
if (
|
|
173
|
-
return { kind:
|
|
173
|
+
if (!["flow", "project-flow"].includes(parsed?.kind) || !parsed.id || !parsed.version) return null;
|
|
174
|
+
return { kind: parsed.kind, id: String(parsed.id), version: String(parsed.version) };
|
|
174
175
|
} catch {
|
|
175
176
|
return null;
|
|
176
177
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* 是请求回调的闭包,解构之后路由体里的写法完全不变。
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { listNodesJson, readNodeDetailJson, readNodeFilePreview } from "./catalog-flows.mjs";
|
|
14
|
+
import { listFlowsJson, listNodesJson, readNodeDetailJson, readNodeFilePreview } from "./catalog-flows.mjs";
|
|
15
15
|
import { startComposerAgent } from "./composer-agent.mjs";
|
|
16
16
|
import { buildSkillCompactInjectionBlock, loadResourcesForSkillKeys } from "./composer-skill-router.mjs";
|
|
17
17
|
import { execFileBuffered } from "./exec-buffered.mjs";
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
} from "./marketplace.mjs";
|
|
31
31
|
import {
|
|
32
32
|
appendMarketplaceUsageEvent,
|
|
33
|
+
marketplaceStatsFor,
|
|
34
|
+
marketplaceUsageStats,
|
|
33
35
|
marketplaceResourcesForRun,
|
|
34
36
|
readMarketplaceFlowOrigin,
|
|
35
37
|
writeMarketplaceFlowOrigin,
|
|
@@ -38,18 +40,18 @@ import { NODE_PACKAGE_ENTRY, nodePackageExportsRun, readNodePackageManifest } fr
|
|
|
38
40
|
import { inspectNodePackageDirectory } from "./node-package-archive.mjs";
|
|
39
41
|
import { json, readBody } from "./http-util.mjs";
|
|
40
42
|
import { log } from "./log.mjs";
|
|
41
|
-
import { PACKAGE_ROOT, PIPELINES_DIR, getAgentflowUserDataRoot, getUserPipelinesRoot } from "./paths.mjs";
|
|
43
|
+
import { PACKAGE_ROOT, PIPELINES_DIR, getAgentflowUserDataRoot, getUserPipelinesRoot, listAgentflowUserIds } from "./paths.mjs";
|
|
42
44
|
import { validateUserPipelineId } from "./flow-write.mjs";
|
|
43
45
|
import { runLedgerId } from "./run-ledger.mjs";
|
|
44
46
|
import { getTeamById, getTeamForUser } from "./teams.mjs";
|
|
45
47
|
import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
|
|
46
|
-
import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, deleteWorkspaceCollaborationForFlow, ensureWorkspaceCollaboration, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
|
|
48
|
+
import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, deleteWorkspaceCollaborationForFlow, ensureWorkspaceCollaboration, getWorkspaceCollaborationByFlow, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
|
|
47
49
|
import { WorkspaceFlowParseError } from "./workspace-flow-store.mjs";
|
|
48
50
|
import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision } from "./workspace-graph-merge.mjs";
|
|
49
51
|
import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
|
|
50
52
|
import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
|
|
51
53
|
import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
|
|
52
|
-
import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, cleanupWorkspaceRunResources, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, publishWorkspaceRelease, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, readWorkspaceReleaseStatus, 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, 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";
|
|
53
55
|
import { splitWorkspaceGraph, WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
|
|
54
56
|
import { getWorkspaceTree } from "./workspace-tree.mjs";
|
|
55
57
|
import busboy from "busboy";
|
|
@@ -926,6 +928,124 @@ function installedMarketplaceFlowCopies(userId) {
|
|
|
926
928
|
return copies;
|
|
927
929
|
}
|
|
928
930
|
|
|
931
|
+
const PROJECT_FLOW_MARKETPLACE_FILENAME = path.join(".workspace", "agentflow", "marketplace.json");
|
|
932
|
+
|
|
933
|
+
function projectFlowMarketplaceId(ownerUserId, flowSource, flowId) {
|
|
934
|
+
return `project-flow:${String(ownerUserId || "").trim()}:${String(flowSource || "user").trim()}:${String(flowId || "").trim()}`;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function projectFlowMarketplaceMetadataPath(flowRoot) {
|
|
938
|
+
return path.join(path.resolve(flowRoot), PROJECT_FLOW_MARKETPLACE_FILENAME);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
function readProjectFlowMarketplaceMetadata(flowRoot) {
|
|
942
|
+
try {
|
|
943
|
+
const parsed = JSON.parse(fs.readFileSync(projectFlowMarketplaceMetadataPath(flowRoot), "utf-8"));
|
|
944
|
+
return {
|
|
945
|
+
visibility: String(parsed?.visibility || "").trim() === "private" ? "private" : "public",
|
|
946
|
+
updatedAt: String(parsed?.updatedAt || "").trim(),
|
|
947
|
+
};
|
|
948
|
+
} catch {
|
|
949
|
+
return { visibility: "public", updatedAt: "" };
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function writeProjectFlowMarketplaceMetadata(flowRoot, visibility) {
|
|
954
|
+
const filePath = projectFlowMarketplaceMetadataPath(flowRoot);
|
|
955
|
+
const value = {
|
|
956
|
+
version: 1,
|
|
957
|
+
visibility: String(visibility || "").trim() === "private" ? "private" : "public",
|
|
958
|
+
updatedAt: new Date().toISOString(),
|
|
959
|
+
};
|
|
960
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
961
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
962
|
+
fs.writeFileSync(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
963
|
+
fs.renameSync(tempPath, filePath);
|
|
964
|
+
return value;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function graphHasRunnableEntry(graph) {
|
|
968
|
+
return Object.values(graph?.instances || {}).some((instance) => (
|
|
969
|
+
instance?.definitionId === "workspace_run"
|
|
970
|
+
|| instance?.definitionId === "workspace_scheduled_run"
|
|
971
|
+
));
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function projectFlowUpdatedAt(flowRoot, metadata = {}) {
|
|
975
|
+
if (metadata.updatedAt) return metadata.updatedAt;
|
|
976
|
+
try {
|
|
977
|
+
return fs.statSync(path.resolve(flowRoot)).mtime.toISOString();
|
|
978
|
+
} catch {
|
|
979
|
+
return "";
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function listRunnableProjectMarketplaceFlows(workspaceRoot, userCtx = {}, scope = "all") {
|
|
984
|
+
const requestedUserId = String(userCtx.userId || "").trim();
|
|
985
|
+
const stats = marketplaceUsageStats(workspaceRoot);
|
|
986
|
+
const resources = [];
|
|
987
|
+
const appendFlow = (ownerId, flow, flowSource = "user", workspaceId = "") => {
|
|
988
|
+
if (!ownerId || (scope === "owned" && ownerId !== requestedUserId)) return;
|
|
989
|
+
if (flow.archived || !flow.path) return;
|
|
990
|
+
let stable;
|
|
991
|
+
let graph;
|
|
992
|
+
try {
|
|
993
|
+
stable = readWorkspaceStableRelease(flow.path, workspaceRoot);
|
|
994
|
+
graph = stable?.graph || readWorkspaceGraph(flow.path, workspaceRoot).graph;
|
|
995
|
+
} catch {
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (!graphHasRunnableEntry(graph)) return;
|
|
999
|
+
const metadata = readProjectFlowMarketplaceMetadata(flow.path);
|
|
1000
|
+
const owned = ownerId === requestedUserId;
|
|
1001
|
+
if (scope !== "owned" && metadata.visibility === "private" && !owned && userCtx.isAdmin !== true) return;
|
|
1002
|
+
const id = projectFlowMarketplaceId(ownerId, flowSource, flow.id);
|
|
1003
|
+
const version = stable?.release?.id || `current-${workspaceDesignRevision(graph).slice(0, 12)}`;
|
|
1004
|
+
resources.push({
|
|
1005
|
+
resourceType: "flow",
|
|
1006
|
+
projectFlow: true,
|
|
1007
|
+
id,
|
|
1008
|
+
definitionId: flow.id,
|
|
1009
|
+
displayName: flow.id,
|
|
1010
|
+
description: flow.description || "",
|
|
1011
|
+
version,
|
|
1012
|
+
versionLabel: stable?.release?.id ? `Stable ${stable.release.id}` : "当前版本",
|
|
1013
|
+
ownerUserId: ownerId,
|
|
1014
|
+
liveOwnerUserId: ownerId,
|
|
1015
|
+
liveFlowId: flow.id,
|
|
1016
|
+
liveFlowSource: flowSource,
|
|
1017
|
+
liveWorkspaceId: workspaceId,
|
|
1018
|
+
visibility: metadata.visibility,
|
|
1019
|
+
nodeCount: Object.keys(graph?.instances || {}).length,
|
|
1020
|
+
edgeCount: Array.isArray(graph?.edges) ? graph.edges.length : 0,
|
|
1021
|
+
updatedAt: projectFlowUpdatedAt(flow.path, metadata),
|
|
1022
|
+
...marketplaceStatsFor(stats, "project-flow", id, version, ownerId),
|
|
1023
|
+
_graph: graph,
|
|
1024
|
+
_flowRoot: flow.path,
|
|
1025
|
+
});
|
|
1026
|
+
};
|
|
1027
|
+
for (const ownerUserId of listAgentflowUserIds()) {
|
|
1028
|
+
const ownerId = String(ownerUserId || "").trim();
|
|
1029
|
+
for (const flow of listFlowsJson(workspaceRoot, { userId: ownerId })) {
|
|
1030
|
+
if ((flow.source || "user") !== "user" || flow.archived || !flow.path) continue;
|
|
1031
|
+
appendFlow(ownerId, flow, "user");
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
for (const flow of listFlowsJson(workspaceRoot, { userId: "", includeWorkspaceFlows: true })) {
|
|
1035
|
+
if ((flow.source || "") !== "workspace" || flow.archived || !flow.path) continue;
|
|
1036
|
+
const collaboration = getWorkspaceCollaborationByFlow(flow.id, false);
|
|
1037
|
+
const ownerId = String(collaboration?.ownerId || "").trim();
|
|
1038
|
+
if (!ownerId) continue;
|
|
1039
|
+
appendFlow(ownerId, flow, "workspace", collaboration.id);
|
|
1040
|
+
}
|
|
1041
|
+
return resources;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function publicProjectFlowMarketplaceResource(resource) {
|
|
1045
|
+
const { _graph, _flowRoot, ...publicResource } = resource;
|
|
1046
|
+
return publicResource;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
929
1049
|
function marketplaceResourceMatches(item, queryText) {
|
|
930
1050
|
if (!queryText) return true;
|
|
931
1051
|
return [
|
|
@@ -1114,10 +1234,18 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1114
1234
|
installedFlowIds,
|
|
1115
1235
|
};
|
|
1116
1236
|
});
|
|
1237
|
+
const projectFlows = listRunnableProjectMarketplaceFlows(root, userCtx, scope).map((flow) => {
|
|
1238
|
+
const installedFlowIds = installedCopies.get(`${flow.id}@${flow.version}`) || [];
|
|
1239
|
+
return publicProjectFlowMarketplaceResource({
|
|
1240
|
+
...flow,
|
|
1241
|
+
installed: installedFlowIds.length > 0,
|
|
1242
|
+
installedFlowIds,
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1117
1245
|
const snippets = scope === "installed" ? [] : listMarketplaceFlowSnippets(root, { ...userCtx, marketplaceScope }).snippets
|
|
1118
1246
|
.map((snippet) => ({ ...snippet, resourceType: "flow-snippet", installed: false }));
|
|
1119
1247
|
const items = sortMarketplaceResources(
|
|
1120
|
-
[...flows, ...snippets]
|
|
1248
|
+
[...projectFlows, ...flows, ...snippets]
|
|
1121
1249
|
.filter((item) => scope !== "installed" || item.installed)
|
|
1122
1250
|
.filter((item) => marketplaceResourceMatches(item, queryText)),
|
|
1123
1251
|
);
|
|
@@ -1128,6 +1256,43 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1128
1256
|
return;
|
|
1129
1257
|
}
|
|
1130
1258
|
|
|
1259
|
+
if (req.method === "POST" && url.pathname === "/api/marketplace/flow-snippets/use") {
|
|
1260
|
+
let payload;
|
|
1261
|
+
try {
|
|
1262
|
+
payload = JSON.parse(await readBody(req));
|
|
1263
|
+
} catch {
|
|
1264
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
const id = String(payload?.id || "").trim();
|
|
1268
|
+
const version = String(payload?.version || "").trim();
|
|
1269
|
+
if (!id || !version) {
|
|
1270
|
+
json(res, 400, { error: "Missing flow snippet id or version" });
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
try {
|
|
1274
|
+
const accessible = listMarketplaceFlowSnippets(root, { ...userCtx, marketplaceScope: "all" }).snippets
|
|
1275
|
+
.some((snippet) => String(snippet.id) === id && String(snippet.version) === version);
|
|
1276
|
+
if (!accessible) {
|
|
1277
|
+
json(res, 404, { error: "Flow snippet not found" });
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
const eventToken = String(payload?.eventId || crypto.randomUUID()).trim().slice(0, 180);
|
|
1281
|
+
appendMarketplaceUsageEvent(root, {
|
|
1282
|
+
kind: "flow-snippet",
|
|
1283
|
+
id,
|
|
1284
|
+
version,
|
|
1285
|
+
action: "use",
|
|
1286
|
+
actorUserId: userCtx.userId || "",
|
|
1287
|
+
eventId: `use:flow-snippet:${id}@${version}:${userCtx.userId || "anonymous"}:${eventToken}`,
|
|
1288
|
+
});
|
|
1289
|
+
json(res, 200, { ok: true, id, version });
|
|
1290
|
+
} catch (e) {
|
|
1291
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
1292
|
+
}
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1131
1296
|
if (req.method === "POST" && url.pathname === "/api/marketplace/flows/publish") {
|
|
1132
1297
|
let payload;
|
|
1133
1298
|
try {
|
|
@@ -1181,7 +1346,8 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1181
1346
|
}
|
|
1182
1347
|
const id = String(payload?.id || "").trim();
|
|
1183
1348
|
const version = String(payload?.version || "").trim();
|
|
1184
|
-
const
|
|
1349
|
+
const requestedProjectFlow = payload?.projectFlow === true;
|
|
1350
|
+
const idCheck = validateUserPipelineId(String(payload?.flowId || (requestedProjectFlow ? "" : id)));
|
|
1185
1351
|
if (!id || !version || !idCheck.ok) {
|
|
1186
1352
|
json(res, 400, { error: idCheck.error || "Missing marketplace flow id or version" });
|
|
1187
1353
|
return;
|
|
@@ -1193,22 +1359,40 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1193
1359
|
return;
|
|
1194
1360
|
}
|
|
1195
1361
|
try {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1362
|
+
let sourceGraph;
|
|
1363
|
+
let originKind = "flow";
|
|
1364
|
+
if (requestedProjectFlow) {
|
|
1365
|
+
const source = listRunnableProjectMarketplaceFlows(root, userCtx, "all")
|
|
1366
|
+
.find((flow) => flow.id === id);
|
|
1367
|
+
if (!source) {
|
|
1368
|
+
json(res, 404, { error: "Project flow not found or is private" });
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
if (source.version !== version) {
|
|
1372
|
+
json(res, 409, { error: "该流程已有新版本,请刷新流程仓库后重试" });
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
sourceGraph = source._graph;
|
|
1376
|
+
originKind = "project-flow";
|
|
1377
|
+
} else {
|
|
1378
|
+
const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
|
|
1379
|
+
if (!source.ok) {
|
|
1380
|
+
json(res, 404, { error: source.error || "Marketplace flow not found" });
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
sourceGraph = source.graph;
|
|
1200
1384
|
}
|
|
1201
1385
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
1202
|
-
const graph = workspaceGraphWithScheduleMode(
|
|
1386
|
+
const graph = workspaceGraphWithScheduleMode(sourceGraph, "disabled");
|
|
1203
1387
|
writeWorkspaceGraph(targetDir, graph, root);
|
|
1204
|
-
writeMarketplaceFlowOrigin(targetDir, { id, version });
|
|
1388
|
+
writeMarketplaceFlowOrigin(targetDir, { kind: originKind, id, version });
|
|
1205
1389
|
appendMarketplaceUsageEvent(root, {
|
|
1206
|
-
kind:
|
|
1390
|
+
kind: originKind,
|
|
1207
1391
|
id,
|
|
1208
1392
|
version,
|
|
1209
1393
|
action: "install",
|
|
1210
1394
|
actorUserId: userCtx.userId || "",
|
|
1211
|
-
eventId: `install
|
|
1395
|
+
eventId: `install:${originKind}:${id}@${version}:${userCtx.userId || "anonymous"}`,
|
|
1212
1396
|
});
|
|
1213
1397
|
json(res, 201, {
|
|
1214
1398
|
ok: true,
|
|
@@ -1236,6 +1420,18 @@ async function workspaceRoutes(req, res, ctx) {
|
|
|
1236
1420
|
return;
|
|
1237
1421
|
}
|
|
1238
1422
|
try {
|
|
1423
|
+
if (String(payload?.kind || "") === "project-flow") {
|
|
1424
|
+
const id = String(payload?.id || "").trim();
|
|
1425
|
+
const source = listRunnableProjectMarketplaceFlows(root, { ...userCtx, isAdmin: false }, "owned")
|
|
1426
|
+
.find((flow) => flow.id === id);
|
|
1427
|
+
if (!source) {
|
|
1428
|
+
json(res, 403, { error: "Only the project owner can change its visibility" });
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
const metadata = writeProjectFlowMarketplaceMetadata(source._flowRoot, payload?.visibility);
|
|
1432
|
+
json(res, 200, { ok: true, kind: "project-flow", id, version: source.version, ...metadata });
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1239
1435
|
const result = setMarketplaceVisibility(
|
|
1240
1436
|
root,
|
|
1241
1437
|
String(payload?.kind || ""),
|
|
@@ -1751,30 +1751,45 @@ export function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {})
|
|
|
1751
1751
|
if (opts.isAdmin !== true) {
|
|
1752
1752
|
return { root: "", error: "Admin permission required", status: 403 };
|
|
1753
1753
|
}
|
|
1754
|
-
if (
|
|
1755
|
-
return { root: "", error: "Admin read-only review only supports user
|
|
1754
|
+
if (!["user", "workspace"].includes(flowSource)) {
|
|
1755
|
+
return { root: "", error: "Admin read-only review only supports user or shared Workspaces", status: 400 };
|
|
1756
1756
|
}
|
|
1757
|
-
const
|
|
1757
|
+
const collaboration = flowSource === "workspace"
|
|
1758
|
+
? getWorkspaceCollaborationByFlow(flowId, archived)
|
|
1759
|
+
: null;
|
|
1760
|
+
const owner = adminWorkspaceOwnerSummary(collaboration?.ownerId || adminOwnerId);
|
|
1758
1761
|
if (!owner) {
|
|
1759
1762
|
return { root: "", error: "Workspace owner not found", status: 404 };
|
|
1760
1763
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1764
|
+
let targetPath = "";
|
|
1765
|
+
let physicalFlowSource = flowSource;
|
|
1766
|
+
if (flowSource === "user") {
|
|
1767
|
+
const targetFlow = listFlowsJson(workspaceRoot, { userId: owner.userId })
|
|
1768
|
+
.find((flow) => (
|
|
1769
|
+
flow.id === flowId
|
|
1770
|
+
&& (flow.source || "user") === "user"
|
|
1771
|
+
&& Boolean(flow.archived) === archived
|
|
1772
|
+
));
|
|
1773
|
+
targetPath = targetFlow?.path || "";
|
|
1774
|
+
} else {
|
|
1775
|
+
physicalFlowSource = collaboration?.projectSource || collaboration?.flowSource || "workspace";
|
|
1776
|
+
const result = getPipelineFiles(workspaceRoot, flowId, physicalFlowSource, archived, {
|
|
1777
|
+
...opts,
|
|
1778
|
+
userId: owner.userId,
|
|
1779
|
+
});
|
|
1780
|
+
targetPath = result?.path || "";
|
|
1781
|
+
}
|
|
1782
|
+
if (!targetPath) {
|
|
1768
1783
|
return { root: "", error: "Pipeline workspace not found", status: 404 };
|
|
1769
1784
|
}
|
|
1770
1785
|
return {
|
|
1771
|
-
root: path.resolve(
|
|
1786
|
+
root: path.resolve(targetPath),
|
|
1772
1787
|
flowId,
|
|
1773
|
-
flowSource:
|
|
1788
|
+
flowSource: physicalFlowSource,
|
|
1774
1789
|
requestedFlowSource: flowSource,
|
|
1775
|
-
workspaceId: "",
|
|
1790
|
+
workspaceId: collaboration?.id || "",
|
|
1776
1791
|
archived,
|
|
1777
|
-
collaboration
|
|
1792
|
+
collaboration,
|
|
1778
1793
|
collaborationAccess: {
|
|
1779
1794
|
allowed: true,
|
|
1780
1795
|
writable: false,
|