@fieldwangai/agentflow 0.1.156 → 0.1.159

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.
@@ -121,6 +121,7 @@ import { json, readBody } from "./http-util.mjs";
121
121
  // 从 ui-server 拆出去的 Workspace 子系统;路由仍在下面的 startUiServer 里
122
122
  import {
123
123
  USER_WORKSPACES_FILENAME,
124
+ WORKSPACE_DEFERRED_RUN_POLL_MS,
124
125
  WORKSPACE_SCHEDULE_POLL_MS,
125
126
  activeWorkspaceRunUsageRecords,
126
127
  adminWorkspaceOwnerSummary,
@@ -136,6 +137,7 @@ import {
136
137
  normalizeMcpServerConfig,
137
138
  normalizeWorkspaceScheduledRunConfig,
138
139
  parseJsonText,
140
+ pollWorkspaceDeferredRuns,
139
141
  readCursorMcpConfig,
140
142
  readCursorMcpServers,
141
143
  readDisplayShares,
@@ -1498,10 +1500,60 @@ function publicDisplayPayloadFromShare(root, share) {
1498
1500
  body,
1499
1501
  inputs: Array.isArray(instance.input) ? instance.input : [],
1500
1502
  outputs: Array.isArray(instance.output) ? instance.output : [],
1503
+ hasConnections: (Array.isArray(graph.edges) ? graph.edges : []).some((edge) => edge?.source === id || edge?.target === id),
1501
1504
  size: displayPageSizes[id] || workspaceSizes[id] || null,
1502
1505
  position: displayPagePositions[id] || workspacePositions[id] || null,
1503
1506
  };
1504
1507
  });
1508
+ const groups = (Array.isArray(graph.ui?.groups) ? graph.ui.groups : [])
1509
+ .map((group, index) => {
1510
+ const declaredMemberIds = Array.from(new Set((Array.isArray(group?.nodeIds) ? group.nodeIds : [])
1511
+ .map((id) => String(id || "").trim())
1512
+ .filter((id) => nodeIds.includes(id))));
1513
+ const groupX = Number(group?.x);
1514
+ const groupY = Number(group?.y);
1515
+ const groupWidth = Number(group?.width);
1516
+ const groupHeight = Number(group?.height);
1517
+ const inferredMemberIds = declaredMemberIds.length > 0 || ![groupX, groupY, groupWidth, groupHeight].every(Number.isFinite)
1518
+ ? []
1519
+ : nodeIds.filter((id) => {
1520
+ const position = workspacePositions[id];
1521
+ if (!position) return false;
1522
+ const size = workspaceSizes[id] || { width: 320, height: 96 };
1523
+ const centerX = Number(position.x || 0) + Math.max(1, Number(size.width) || 320) / 2;
1524
+ const centerY = Number(position.y || 0) + Math.max(1, Number(size.height) || 96) / 2;
1525
+ return centerX >= groupX && centerX <= groupX + groupWidth && centerY >= groupY && centerY <= groupY + groupHeight;
1526
+ });
1527
+ const memberIds = declaredMemberIds.length > 0 ? declaredMemberIds : inferredMemberIds;
1528
+ if (memberIds.length === 0) return null;
1529
+ const bounds = memberIds.reduce((acc, id) => {
1530
+ const position = displayPagePositions[id] || workspacePositions[id] || { x: 0, y: 0 };
1531
+ const size = displayPageSizes[id] || workspaceSizes[id] || { width: 520, height: 320 };
1532
+ const x = Number(position.x) || 0;
1533
+ const y = Number(position.y) || 0;
1534
+ const width = Math.max(1, Number(size.width) || 520);
1535
+ const height = Math.max(1, Number(size.height) || 320);
1536
+ return {
1537
+ minX: Math.min(acc.minX, x),
1538
+ minY: Math.min(acc.minY, y),
1539
+ maxX: Math.max(acc.maxX, x + width),
1540
+ maxY: Math.max(acc.maxY, y + height),
1541
+ };
1542
+ }, { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity });
1543
+ const padding = 52;
1544
+ return {
1545
+ id: String(group?.id || `group_${index + 1}`),
1546
+ title: String(group?.title || `Group ${index + 1}`),
1547
+ color: String(group?.color || "purple"),
1548
+ nodeIds: memberIds,
1549
+ position: { x: bounds.minX - padding, y: bounds.minY - padding },
1550
+ size: {
1551
+ width: Math.max(240, bounds.maxX - bounds.minX + padding * 2),
1552
+ height: Math.max(160, bounds.maxY - bounds.minY + padding * 2),
1553
+ },
1554
+ };
1555
+ })
1556
+ .filter(Boolean);
1505
1557
  return {
1506
1558
  ok: true,
1507
1559
  share: {
@@ -1525,6 +1577,7 @@ function publicDisplayPayloadFromShare(root, share) {
1525
1577
  expiresInDays: share.expiresInDays == null ? null : Number(share.expiresInDays),
1526
1578
  },
1527
1579
  nodes,
1580
+ groups,
1528
1581
  };
1529
1582
  }
1530
1583
 
@@ -4290,6 +4343,25 @@ finishedAt: "${new Date().toISOString()}"
4290
4343
  log.debug(`[workspace-scheduler] initial poll failed: ${(e && e.message) || String(e)}`);
4291
4344
  }
4292
4345
  }, 1000).unref?.();
4346
+
4347
+ const workspaceDeferredRunTimer = setInterval(() => {
4348
+ try {
4349
+ pollWorkspaceDeferredRuns(root);
4350
+ } catch (e) {
4351
+ log.debug(`[workspace-deferred] poll failed: ${(e && e.message) || String(e)}`);
4352
+ }
4353
+ }, WORKSPACE_DEFERRED_RUN_POLL_MS);
4354
+ try {
4355
+ workspaceDeferredRunTimer.unref?.();
4356
+ } catch (_) {}
4357
+ server.on("close", () => clearInterval(workspaceDeferredRunTimer));
4358
+ setTimeout(() => {
4359
+ try {
4360
+ pollWorkspaceDeferredRuns(root);
4361
+ } catch (e) {
4362
+ log.debug(`[workspace-deferred] initial poll failed: ${(e && e.message) || String(e)}`);
4363
+ }
4364
+ }, 500).unref?.();
4293
4365
  }
4294
4366
 
4295
4367
  const workspacePreviewCleanupTimer = setInterval(() => {
@@ -36,7 +36,7 @@ import { WorkspaceFlowParseError } from "./workspace-flow-store.mjs";
36
36
  import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision } from "./workspace-graph-merge.mjs";
37
37
  import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
38
38
  import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession, listWorkspaceRunLogs, readWorkspaceRunLogEvents } from "./workspace-run-logs.mjs";
39
- import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
39
+ import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, readWorkspaceConversations, readWorkspaceFiles, readWorkspaceGraph, removeWorkspaceDeferredRun, resolveWorkspaceFilePath, resolveWorkspaceScopeRoot, runWorkspaceGraph, sleepMs, syncWorkspaceSchedulesForGraph, upsertWorkspaceDeferredRun, workspaceActiveRunsForScope, workspaceCollaborationEventKey, workspaceCollaborationSequences, workspaceCollaborationSubscribers, workspaceCollaborationSummaryWithUsers, workspaceDeferredRunsForScope, workspaceDesignPath, workspaceDownloadContentDisposition, workspaceFindActiveRunConflict, workspaceGraphAsSource, workspaceOptimizeRunImplementations, workspaceRepoUrlWithCredential, workspaceRunControl, workspaceRunEntryKey, workspaceRunKey, workspaceRunPlan, workspaceRunPlanNodeIds, workspaceRunTouchedNodeIds, workspaceRuntimeNodeLabel, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
40
40
  import { getWorkspaceTree } from "./workspace-tree.mjs";
41
41
  import busboy from "busboy";
42
42
  import crypto from "crypto";
@@ -1885,11 +1885,12 @@ async function workspaceRoutes(req, res, ctx) {
1885
1885
  const setActiveChild = (child, childOptions = {}) => {
1886
1886
  runControl.setChild(child, childOptions);
1887
1887
  };
1888
+ let runDeferred = false;
1888
1889
  const clearActiveRun = (status = "finished") => {
1889
1890
  runControl.finish(status);
1890
1891
  if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
1891
1892
  broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
1892
- type: "run.finished",
1893
+ type: status === "waiting" ? "run.waiting" : "run.finished",
1893
1894
  status,
1894
1895
  runId,
1895
1896
  runNodeId,
@@ -1912,6 +1913,7 @@ async function workspaceRoutes(req, res, ctx) {
1912
1913
  onEvent: writeEvent,
1913
1914
  signal: controller.signal,
1914
1915
  onActiveChild: setActiveChild,
1916
+ runId,
1915
1917
  });
1916
1918
  const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
1917
1919
  const touchedIds = workspaceRunTouchedNodeIds(result);
@@ -1921,6 +1923,33 @@ async function workspaceRoutes(req, res, ctx) {
1921
1923
  const collaborationEventType = revision === workspaceDesignRevision(currentGraph)
1922
1924
  ? "runtime.committed"
1923
1925
  : "graph.committed";
1926
+ if (result.deferred) {
1927
+ runDeferred = true;
1928
+ const waiting = upsertWorkspaceDeferredRun(runEntry, result.deferred);
1929
+ broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
1930
+ type: collaborationEventType,
1931
+ revision,
1932
+ runtimeRevision,
1933
+ actorId: userCtx.userId || "",
1934
+ source: "run",
1935
+ });
1936
+ writeEvent({
1937
+ type: "waiting",
1938
+ ok: true,
1939
+ deferred: true,
1940
+ path: graphPath,
1941
+ graph: committed.graph,
1942
+ revision,
1943
+ runtimeRevision,
1944
+ runId,
1945
+ runNodeId,
1946
+ plannedNodeIds,
1947
+ touchedNodeIds: Array.from(touchedIds),
1948
+ ...waiting,
1949
+ });
1950
+ res.end();
1951
+ return;
1952
+ }
1924
1953
  const endedAt = Date.now();
1925
1954
  appendWorkspaceRunFinished({
1926
1955
  ...runEntry,
@@ -1972,7 +2001,7 @@ async function workspaceRoutes(req, res, ctx) {
1972
2001
  }
1973
2002
  res.end();
1974
2003
  } finally {
1975
- clearActiveRun(controller.signal.aborted ? "stopped" : "finished");
2004
+ clearActiveRun(controller.signal.aborted ? "stopped" : runDeferred ? "waiting" : "finished");
1976
2005
  }
1977
2006
  return;
1978
2007
  }
@@ -1981,6 +2010,7 @@ async function workspaceRoutes(req, res, ctx) {
1981
2010
  signal: controller.signal,
1982
2011
  onActiveChild: setActiveChild,
1983
2012
  onEvent: (event) => appendWorkspaceRunLogEvent(runLog.runId, event),
2013
+ runId,
1984
2014
  });
1985
2015
  const currentGraph = readWorkspaceGraph(scoped.root, root).graph;
1986
2016
  const touchedIds = workspaceRunTouchedNodeIds(result);
@@ -1990,6 +2020,31 @@ async function workspaceRoutes(req, res, ctx) {
1990
2020
  const collaborationEventType = revision === workspaceDesignRevision(currentGraph)
1991
2021
  ? "runtime.committed"
1992
2022
  : "graph.committed";
2023
+ if (result.deferred) {
2024
+ runDeferred = true;
2025
+ const waiting = upsertWorkspaceDeferredRun(runEntry, result.deferred);
2026
+ broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
2027
+ type: collaborationEventType,
2028
+ revision,
2029
+ runtimeRevision,
2030
+ actorId: userCtx.userId || "",
2031
+ source: "run",
2032
+ });
2033
+ json(res, 200, {
2034
+ ok: true,
2035
+ path: graphPath,
2036
+ ...result,
2037
+ deferred: true,
2038
+ graph: committed.graph,
2039
+ revision,
2040
+ runtimeRevision,
2041
+ runId,
2042
+ plannedNodeIds,
2043
+ touchedNodeIds: Array.from(touchedIds),
2044
+ waiting,
2045
+ });
2046
+ return;
2047
+ }
1993
2048
  const endedAt = Date.now();
1994
2049
  appendWorkspaceRunFinished({
1995
2050
  ...runEntry,
@@ -2040,7 +2095,7 @@ async function workspaceRoutes(req, res, ctx) {
2040
2095
  throw e;
2041
2096
  }
2042
2097
  } finally {
2043
- clearActiveRun(controller.signal.aborted ? "stopped" : "finished");
2098
+ clearActiveRun(controller.signal.aborted ? "stopped" : runDeferred ? "waiting" : "finished");
2044
2099
  }
2045
2100
  } catch (e) {
2046
2101
  json(res, 500, { error: (e && e.message) || String(e) });
@@ -2138,11 +2193,15 @@ async function workspaceRoutes(req, res, ctx) {
2138
2193
  return;
2139
2194
  }
2140
2195
  const scopeKey = workspaceRunKey(workspaceScopedUserContext(scoped, userCtx), flowSource, flowId);
2141
- const entries = workspaceActiveRunsForScope(scopeKey).map(([, entry]) => entry);
2196
+ const activeEntries = workspaceActiveRunsForScope(scopeKey).map(([, entry]) => entry);
2197
+ const activeRunIds = new Set(activeEntries.map((entry) => String(entry?.runId || "")));
2198
+ const deferredEntries = workspaceDeferredRunsForScope(scopeKey)
2199
+ .filter((entry) => !activeRunIds.has(String(entry?.runId || "")));
2200
+ const entries = [...activeEntries, ...deferredEntries];
2142
2201
  const entry = entries[0] || null;
2143
2202
  json(res, 200, {
2144
2203
  running: entries.length > 0,
2145
- state: entry?.runControl?.state || (entries.length > 0 ? "running" : "idle"),
2204
+ state: entry?.runControl?.state || entry?.status || (entries.length > 0 ? "running" : "idle"),
2146
2205
  flowId,
2147
2206
  flowSource,
2148
2207
  runNodeId: entry?.runNodeId || "",
@@ -2155,7 +2214,15 @@ async function workspaceRoutes(req, res, ctx) {
2155
2214
  startedAt: item?.startedAt || null,
2156
2215
  plannedNodeIds: Array.isArray(item?.plannedNodeIds) ? item.plannedNodeIds : [],
2157
2216
  scheduled: item?.scheduled === true,
2158
- state: item?.runControl?.state || "running",
2217
+ state: item?.runControl?.state || item?.status || "running",
2218
+ waitingNodeId: item?.nodeId || "",
2219
+ phase: item?.phase || "",
2220
+ jenkinsStatus: item?.jenkinsStatus || "",
2221
+ message: item?.message || "",
2222
+ buildNumber: item?.buildNumber || "",
2223
+ url: item?.url || "",
2224
+ qrUrl: item?.qrUrl || "",
2225
+ wakeAt: item?.wakeAt || "",
2159
2226
  })),
2160
2227
  });
2161
2228
  return;
@@ -2189,7 +2256,7 @@ async function workspaceRoutes(req, res, ctx) {
2189
2256
  json(res, 403, { error: "Workspace collaboration run permission denied" });
2190
2257
  return;
2191
2258
  }
2192
- const scopeKey = workspaceRunKey(userCtx, flowSource, flowId);
2259
+ const scopeKey = workspaceRunKey(workspaceScopedUserContext(scoped, userCtx), flowSource, flowId);
2193
2260
  const runId = String(payload.runId || payload.runSessionId || "").trim();
2194
2261
  const runNodeId = String(payload.runNodeId || "").trim();
2195
2262
  const entries = workspaceActiveRunsForScope(scopeKey);
@@ -2198,7 +2265,44 @@ async function workspaceRoutes(req, res, ctx) {
2198
2265
  || (!runId && !runNodeId && entries.length === 1 ? entries[0] : null);
2199
2266
  const entry = match?.[1] || null;
2200
2267
  if (!entry) {
2201
- json(res, 404, { error: "该 Workspace 未在运行" });
2268
+ const deferredEntries = workspaceDeferredRunsForScope(scopeKey);
2269
+ const deferred = deferredEntries.find((item) => runId && String(item?.runId || "") === runId)
2270
+ || deferredEntries.find((item) => runNodeId && String(item?.runNodeId || "") === runNodeId)
2271
+ || (!runId && !runNodeId && deferredEntries.length === 1 ? deferredEntries[0] : null);
2272
+ if (!deferred) {
2273
+ json(res, 404, { error: "该 Workspace 未在运行" });
2274
+ return;
2275
+ }
2276
+ removeWorkspaceDeferredRun(deferred.key);
2277
+ const endedAt = Date.now();
2278
+ appendWorkspaceRunLogEvent(deferred.runId, {
2279
+ type: "stop-completed",
2280
+ runNodeId: deferred.runNodeId || "",
2281
+ monitoringOnly: true,
2282
+ ts: endedAt,
2283
+ });
2284
+ appendWorkspaceRunFinished({
2285
+ ...deferred,
2286
+ endedAt,
2287
+ durationMs: Math.max(0, endedAt - Number(deferred.startedAt || endedAt)),
2288
+ }, "stopped");
2289
+ finishWorkspaceRunLogSession(deferred.runId, "stopped", {
2290
+ endedAt,
2291
+ durationMs: Math.max(0, endedAt - Number(deferred.startedAt || endedAt)),
2292
+ runNodeId: deferred.runNodeId || "",
2293
+ });
2294
+ broadcastWorkspaceCollaborationEvent(userCtx, scoped.flowSource, scoped.flowId, scoped.archived, {
2295
+ type: "run.finished",
2296
+ status: "stopped",
2297
+ runId: deferred.runId,
2298
+ runNodeId: deferred.runNodeId || "",
2299
+ actorId: userCtx.userId || "",
2300
+ });
2301
+ json(res, 200, {
2302
+ ok: true,
2303
+ stopped: true,
2304
+ monitoringOnly: true,
2305
+ });
2202
2306
  return;
2203
2307
  }
2204
2308
  appendWorkspaceRunLogEvent(entry.runId, {