@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.
@@ -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: "flow",
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 (parsed?.kind !== "flow" || !parsed.id || !parsed.version) return null;
173
- return { kind: "flow", id: String(parsed.id), version: String(parsed.version) };
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 idCheck = validateUserPipelineId(String(payload?.flowId || id));
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
- const source = readMarketplaceFlow(root, id, version, { ...userCtx, marketplaceScope: "all" });
1197
- if (!source.ok) {
1198
- json(res, 404, { error: source.error || "Marketplace flow not found" });
1199
- return;
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(source.graph, "disabled");
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: "flow",
1390
+ kind: originKind,
1207
1391
  id,
1208
1392
  version,
1209
1393
  action: "install",
1210
1394
  actorUserId: userCtx.userId || "",
1211
- eventId: `install:flow:${id}@${version}:${userCtx.userId || "anonymous"}`,
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 (flowSource !== "user") {
1755
- return { root: "", error: "Admin read-only review only supports user projects", status: 400 };
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 owner = adminWorkspaceOwnerSummary(adminOwnerId);
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
- const targetFlow = listFlowsJson(workspaceRoot, { userId: owner.userId })
1762
- .find((flow) => (
1763
- flow.id === flowId
1764
- && (flow.source || "user") === "user"
1765
- && Boolean(flow.archived) === archived
1766
- ));
1767
- if (!targetFlow?.path) {
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(targetFlow.path),
1786
+ root: path.resolve(targetPath),
1772
1787
  flowId,
1773
- flowSource: "user",
1788
+ flowSource: physicalFlowSource,
1774
1789
  requestedFlowSource: flowSource,
1775
- workspaceId: "",
1790
+ workspaceId: collaboration?.id || "",
1776
1791
  archived,
1777
- collaboration: null,
1792
+ collaboration,
1778
1793
  collaborationAccess: {
1779
1794
  allowed: true,
1780
1795
  writable: false,