@fieldwangai/agentflow 0.1.160 → 0.1.162

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.
Files changed (87) hide show
  1. package/bin/lib/auth.mjs +231 -14
  2. package/bin/lib/catalog-flows.mjs +2 -0
  3. package/bin/lib/cli-auth-page.mjs +103 -0
  4. package/bin/lib/skill-runtime.mjs +6 -0
  5. package/bin/lib/table.mjs +2 -3
  6. package/bin/lib/ui-server.mjs +220 -17
  7. package/bin/lib/workspace-draft.mjs +74 -0
  8. package/bin/lib/workspace-routes.mjs +297 -4
  9. package/bin/lib/workspace-server.mjs +6 -0
  10. package/builtin/nodes/control_while.md +5 -5
  11. package/builtin/pipelines/new/workspace.flow.js +1 -1
  12. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-Bfo9Ythw.js → WorkflowAssistantThread-CTrXOZ00.js} +1 -1
  13. package/builtin/web-ui/dist/assets/{index-e1omCEau.css → index-5uJFccdX.css} +1 -1
  14. package/builtin/web-ui/dist/assets/{index-XbeI5foV.js → index-BeUfNQRL.js} +4 -3
  15. package/builtin/web-ui/dist/index.html +2 -2
  16. package/package.json +5 -1
  17. package/skills/agentflow-author-flow/SKILL.md +81 -8
  18. package/skills/agentflow-author-flow/agents/openai.yaml +3 -3
  19. package/skills/agentflow-cli/SKILL.md +130 -25
  20. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  21. package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14765 -0
  22. package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +34 -0
  23. package/skills/agentflow-cli/runtime/builtin/nodes/control_agent_toBool.md +26 -0
  24. package/skills/agentflow-cli/runtime/builtin/nodes/control_anyOne.md +19 -0
  25. package/skills/agentflow-cli/runtime/builtin/nodes/control_cancelled.md +26 -0
  26. package/skills/agentflow-cli/runtime/builtin/nodes/control_cd_workspace.md +50 -0
  27. package/skills/agentflow-cli/runtime/builtin/nodes/control_delay.md +31 -0
  28. package/skills/agentflow-cli/runtime/builtin/nodes/control_end.md +13 -0
  29. package/skills/agentflow-cli/runtime/builtin/nodes/control_if.md +21 -0
  30. package/skills/agentflow-cli/runtime/builtin/nodes/control_interval_loop.md +55 -0
  31. package/skills/agentflow-cli/runtime/builtin/nodes/control_load_mcp.md +24 -0
  32. package/skills/agentflow-cli/runtime/builtin/nodes/control_load_skills.md +35 -0
  33. package/skills/agentflow-cli/runtime/builtin/nodes/control_parse_json.md +25 -0
  34. package/skills/agentflow-cli/runtime/builtin/nodes/control_start.md +13 -0
  35. package/skills/agentflow-cli/runtime/builtin/nodes/control_subflow_call.md +27 -0
  36. package/skills/agentflow-cli/runtime/builtin/nodes/control_toBool.md +27 -0
  37. package/skills/agentflow-cli/runtime/builtin/nodes/control_user_workspace.md +24 -0
  38. package/skills/agentflow-cli/runtime/builtin/nodes/control_wait_until.md +34 -0
  39. package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +124 -0
  40. package/skills/agentflow-cli/runtime/builtin/nodes/display_ascii.md +24 -0
  41. package/skills/agentflow-cli/runtime/builtin/nodes/display_chart.md +32 -0
  42. package/skills/agentflow-cli/runtime/builtin/nodes/display_code.md +39 -0
  43. package/skills/agentflow-cli/runtime/builtin/nodes/display_html.md +32 -0
  44. package/skills/agentflow-cli/runtime/builtin/nodes/display_image.md +36 -0
  45. package/skills/agentflow-cli/runtime/builtin/nodes/display_markdown.md +24 -0
  46. package/skills/agentflow-cli/runtime/builtin/nodes/display_mermaid.md +24 -0
  47. package/skills/agentflow-cli/runtime/builtin/nodes/display_react_app.md +53 -0
  48. package/skills/agentflow-cli/runtime/builtin/nodes/display_table.md +32 -0
  49. package/skills/agentflow-cli/runtime/builtin/nodes/provide_bool.md +14 -0
  50. package/skills/agentflow-cli/runtime/builtin/nodes/provide_file.md +14 -0
  51. package/skills/agentflow-cli/runtime/builtin/nodes/provide_json.md +14 -0
  52. package/skills/agentflow-cli/runtime/builtin/nodes/provide_password.md +14 -0
  53. package/skills/agentflow-cli/runtime/builtin/nodes/provide_str.md +14 -0
  54. package/skills/agentflow-cli/runtime/builtin/nodes/tool_display_share_link.md +47 -0
  55. package/skills/agentflow-cli/runtime/builtin/nodes/tool_get_env.md +20 -0
  56. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_checkout.md +70 -0
  57. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +73 -0
  58. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_unload.md +52 -0
  59. package/skills/agentflow-cli/runtime/builtin/nodes/tool_gitlab_create_mr.md +114 -0
  60. package/skills/agentflow-cli/runtime/builtin/nodes/tool_jenkins_build.md +67 -0
  61. package/skills/agentflow-cli/runtime/builtin/nodes/tool_load_key.md +26 -0
  62. package/skills/agentflow-cli/runtime/builtin/nodes/tool_nodejs.md +52 -0
  63. package/skills/agentflow-cli/runtime/builtin/nodes/tool_print.md +21 -0
  64. package/skills/agentflow-cli/runtime/builtin/nodes/tool_save_key.md +26 -0
  65. package/skills/agentflow-cli/runtime/builtin/nodes/tool_set_run_env.md +38 -0
  66. package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_ask.md +27 -0
  67. package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_check.md +26 -0
  68. package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_app_markdown.md +57 -0
  69. package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_group_markdown.md +45 -0
  70. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_one_click_task.md +44 -0
  71. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_run.md +16 -0
  72. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_scheduled_run.md +16 -0
  73. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_subflow_input.md +14 -0
  74. package/skills/agentflow-cli/runtime/package.json +6 -0
  75. package/skills/agentflow-cli/scripts/agentflow-auth-store.mjs +113 -0
  76. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +393 -28
  77. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +11 -4
  78. package/skills/agentflow-flow-add-instances/SKILL.md +2 -2
  79. package/skills/agentflow-flow-dsl/SKILL.md +25 -6
  80. package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -2
  81. package/skills/agentflow-flow-edit-node-fields/SKILL.md +3 -3
  82. package/skills/agentflow-flow-recipes/SKILL.md +1 -1
  83. package/skills/agentflow-flow-recipes/references/recipes.md +1 -2
  84. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  85. package/skills/agentflow-node-dsl/SKILL.md +5 -13
  86. package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
  87. package/skills/agentflow-workspace-graph/SKILL.md +0 -85
@@ -27,16 +27,19 @@ import { NODE_PACKAGE_ENTRY, nodePackageExportsRun, readNodePackageManifest } fr
27
27
  import { inspectNodePackageDirectory } from "./node-package-archive.mjs";
28
28
  import { json, readBody } from "./http-util.mjs";
29
29
  import { log } from "./log.mjs";
30
- import { PACKAGE_ROOT, getAgentflowUserDataRoot } from "./paths.mjs";
30
+ import { PACKAGE_ROOT, PIPELINES_DIR, getAgentflowUserDataRoot, getUserPipelinesRoot } from "./paths.mjs";
31
+ import { validateUserPipelineId } from "./flow-write.mjs";
31
32
  import { runLedgerId } from "./run-ledger.mjs";
32
33
  import { getTeamById, getTeamForUser } from "./teams.mjs";
33
34
  import { readMergedEnvObject, runtimeEnvForUser } from "./user-env.mjs";
34
- import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, ensureWorkspaceCollaboration, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
35
+ import { acceptWorkspaceCollaborationInvite, addWorkspaceCollaborationMember, deleteWorkspaceCollaborationForFlow, ensureWorkspaceCollaboration, getWorkspaceCollaborationForProject, listWorkspaceCollaborationsForUser, removeWorkspaceCollaborationMember, removeWorkspaceCollaborationTeamShare, setWorkspaceCollaborationTeamShare, workspaceCollaborationAccess } from "./workspace-collaboration.mjs";
35
36
  import { WorkspaceFlowParseError } from "./workspace-flow-store.mjs";
36
37
  import { mergeWorkspaceGraphs, workspaceDesignRevision, workspaceRuntimeRevision } from "./workspace-graph-merge.mjs";
37
38
  import { DEFAULT_WORKSPACE_PREVIEW_TTL_MS, createWorkspacePreviewId, normalizeWorkspacePreviewTtlMs, readWorkspacePreviewMetadata, workspaceSharedPreviewFlowDir, writeWorkspacePreviewMetadata } from "./workspace-preview.mjs";
39
+ import { DEFAULT_WORKSPACE_DRAFT_TTL_MS, createWorkspaceDraftId, normalizeWorkspaceDraftTtlMs, readWorkspaceDraftMetadata, workspaceDraftFlowDir, writeWorkspaceDraftMetadata } from "./workspace-draft.mjs";
38
40
  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, 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";
41
+ import { activeWorkspaceRuns, appendWorkspaceRunFinished, appendWorkspaceRunStarted, hydrateWorkspaceGraphForRuntime, isReadonlyBuiltinFlowSource, isTransientAgentNetworkError, isValidFlowSourceRead, isWorkspaceRunAbortError, listWorkspaceScheduleStatusesForFlow, mergeWorkspacePersistentNodeRefs, mergeWorkspaceRunGraph, normalizeWorkspaceEntry, normalizeWorkspaceScheduledRunConfig, 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, workspaceScheduleNextRunAt, workspaceScopedUserContext, workspaceSearchGuardrailsBlock, workspaceUnwrapOutputEnvelopeForDisplay, workspacesPath, writeWorkspaceConversations, writeWorkspaceGraph } from "./workspace-server.mjs";
42
+ import { WORKSPACE_STATE_FILENAME } from "./workspace-state.mjs";
40
43
  import { getWorkspaceTree } from "./workspace-tree.mjs";
41
44
  import busboy from "busboy";
42
45
  import crypto from "crypto";
@@ -192,6 +195,29 @@ function commitWorkspaceGraph(workspaceRoot, scoped, graph, userCtx) {
192
195
  };
193
196
  }
194
197
 
198
+ export function workspaceGraphWithScheduleMode(graph, mode = "disabled") {
199
+ const normalizedMode = String(mode || "disabled").trim().toLowerCase();
200
+ if (!["enabled", "disabled", "preserve"].includes(normalizedMode)) {
201
+ throw new Error("scheduleMode must be enabled, disabled, or preserve");
202
+ }
203
+ if (normalizedMode === "preserve") return graph;
204
+ const enabled = normalizedMode === "enabled";
205
+ const instances = { ...(graph?.instances || {}) };
206
+ let scheduleCount = 0;
207
+ for (const [nodeId, instance] of Object.entries(instances)) {
208
+ if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
209
+ scheduleCount += 1;
210
+ const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
211
+ const nextConfig = { ...config, enabled };
212
+ if (enabled) workspaceScheduleNextRunAt(nextConfig, new Date());
213
+ instances[nodeId] = { ...instance, body: JSON.stringify(nextConfig) };
214
+ }
215
+ if (enabled && scheduleCount === 0) {
216
+ throw new Error("Cannot enable scheduling: the draft has no Scheduled Run node");
217
+ }
218
+ return { ...graph, instances };
219
+ }
220
+
195
221
  function missingWorkspaceGraphNodePackages(workspaceRoot, scoped, graph, userCtx) {
196
222
  const missing = new Set();
197
223
  for (const instance of Object.values(graph?.instances || {})) {
@@ -1309,6 +1335,185 @@ async function workspaceRoutes(req, res, ctx) {
1309
1335
  return;
1310
1336
  }
1311
1337
 
1338
+ if (req.method === "POST" && url.pathname === "/api/workspace/draft") {
1339
+ if (!authUser?.userId) {
1340
+ json(res, 401, { error: "Authentication required" });
1341
+ return;
1342
+ }
1343
+ let payload;
1344
+ try {
1345
+ payload = JSON.parse(await readBody(req, 4 * 1024 * 1024));
1346
+ } catch {
1347
+ json(res, 400, { error: "Invalid JSON" });
1348
+ return;
1349
+ }
1350
+ const graph = payload?.graph;
1351
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
1352
+ json(res, 400, { error: "graph must be an object" });
1353
+ return;
1354
+ }
1355
+ const rawRequestedId = String(payload.draftId || "").trim();
1356
+ const flowId = rawRequestedId || createWorkspaceDraftId();
1357
+ const flowDir = workspaceDraftFlowDir(flowId, authUser.userId);
1358
+ if (!flowDir) {
1359
+ json(res, 400, { error: "Invalid draftId" });
1360
+ return;
1361
+ }
1362
+ const existing = readWorkspaceDraftMetadata(flowDir);
1363
+ if (existing && existing.ownerId !== authUser.userId) {
1364
+ json(res, 403, { error: "Draft ownership denied" });
1365
+ return;
1366
+ }
1367
+ if (rawRequestedId && !existing && fs.existsSync(flowDir)) {
1368
+ json(res, 409, { error: "Draft project already exists but is not a draft" });
1369
+ return;
1370
+ }
1371
+ if (existing) {
1372
+ const currentGraph = readWorkspaceGraph(flowDir, root).graph;
1373
+ const currentRevision = workspaceDesignRevision(currentGraph);
1374
+ const baseRevision = String(payload.baseRevision || "").trim();
1375
+ if (!baseRevision) {
1376
+ json(res, 428, {
1377
+ error: "Draft update requires baseRevision",
1378
+ conflict: "missing-base-revision",
1379
+ currentRevision,
1380
+ });
1381
+ return;
1382
+ }
1383
+ if (baseRevision !== currentRevision) {
1384
+ json(res, 409, {
1385
+ error: "Draft 已被更新,请先拉取最新版本后再修改",
1386
+ conflict: "revision-mismatch",
1387
+ expectedRevision: baseRevision,
1388
+ currentRevision,
1389
+ });
1390
+ return;
1391
+ }
1392
+ }
1393
+ const now = Date.now();
1394
+ const ttlInput = payload.ttlMs != null
1395
+ ? Number(payload.ttlMs)
1396
+ : payload.ttlSeconds != null
1397
+ ? Number(payload.ttlSeconds) * 1000
1398
+ : DEFAULT_WORKSPACE_DRAFT_TTL_MS;
1399
+ const ttlMs = normalizeWorkspaceDraftTtlMs(ttlInput);
1400
+ const metadata = {
1401
+ version: 1,
1402
+ flowId,
1403
+ ownerId: authUser.userId,
1404
+ title: String(payload.title || "Workspace Draft").trim().slice(0, 200),
1405
+ createdAt: existing?.createdAt || new Date(now).toISOString(),
1406
+ updatedAt: new Date(now).toISOString(),
1407
+ expiresAt: new Date(now + ttlMs).toISOString(),
1408
+ };
1409
+ const created = !fs.existsSync(flowDir);
1410
+ try {
1411
+ fs.mkdirSync(flowDir, { recursive: true });
1412
+ if (payload.resetRuntime !== false) {
1413
+ const statePath = path.join(flowDir, WORKSPACE_STATE_FILENAME);
1414
+ if (fs.existsSync(statePath)) fs.rmSync(statePath);
1415
+ }
1416
+ const written = writeWorkspaceGraph(flowDir, graph, root);
1417
+ writeWorkspaceDraftMetadata(flowDir, metadata);
1418
+ const persisted = readWorkspaceGraph(flowDir, root).graph;
1419
+ const baseUrl = `${url.protocol}//${url.host}`;
1420
+ json(res, 200, {
1421
+ ok: true,
1422
+ flowId,
1423
+ draftId: flowId,
1424
+ flowSource: "user",
1425
+ draft: true,
1426
+ writable: true,
1427
+ runnable: true,
1428
+ schedulesSuppressed: true,
1429
+ revision: workspaceDesignRevision(persisted),
1430
+ format: written.format,
1431
+ expiresAt: metadata.expiresAt,
1432
+ url: `${baseUrl}/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=user`,
1433
+ });
1434
+ } catch (e) {
1435
+ if (created) {
1436
+ try { fs.rmSync(flowDir, { recursive: true, force: true }); } catch (_) {}
1437
+ }
1438
+ json(res, 500, { error: (e && e.message) || String(e) });
1439
+ }
1440
+ return;
1441
+ }
1442
+
1443
+ if (req.method === "POST" && url.pathname === "/api/workspace/draft/publish") {
1444
+ if (!authUser?.userId) {
1445
+ json(res, 401, { error: "Authentication required" });
1446
+ return;
1447
+ }
1448
+ let payload;
1449
+ try {
1450
+ payload = JSON.parse(await readBody(req));
1451
+ } catch {
1452
+ json(res, 400, { error: "Invalid JSON body" });
1453
+ return;
1454
+ }
1455
+ const draftId = String(payload.draftId || "").trim();
1456
+ const draftDir = workspaceDraftFlowDir(draftId, authUser.userId);
1457
+ const draftMetadata = draftDir ? readWorkspaceDraftMetadata(draftDir) : null;
1458
+ if (!draftMetadata || draftMetadata.ownerId !== authUser.userId) {
1459
+ json(res, 404, { error: "Workspace draft not found" });
1460
+ return;
1461
+ }
1462
+ const idCheck = validateUserPipelineId(String(payload.flowId || ""));
1463
+ if (!idCheck.ok) {
1464
+ json(res, 400, { error: idCheck.error });
1465
+ return;
1466
+ }
1467
+ const flowId = idCheck.flowId;
1468
+ const requestedTarget = String(payload.targetSpace || "personal").trim().toLowerCase();
1469
+ const flowSource = requestedTarget === "workspace" || requestedTarget === "team" ? "workspace" : "user";
1470
+ const targetDir = flowSource === "workspace"
1471
+ ? path.join(path.resolve(root), PIPELINES_DIR, flowId)
1472
+ : path.join(getUserPipelinesRoot(authUser.userId), flowId);
1473
+ if (fs.existsSync(targetDir)) {
1474
+ json(res, 409, { error: `已存在同名流水线 ${flowId}` });
1475
+ return;
1476
+ }
1477
+ try {
1478
+ const draftGraph = readWorkspaceGraph(draftDir, root).graph;
1479
+ const graph = workspaceGraphWithScheduleMode(draftGraph, payload.scheduleMode || "disabled");
1480
+ fs.mkdirSync(targetDir, { recursive: true });
1481
+ writeWorkspaceGraph(targetDir, graph, root);
1482
+ let collaborationCreated = false;
1483
+ if (flowSource === "workspace") {
1484
+ ensureWorkspaceCollaboration({ flowId, userId: authUser.userId });
1485
+ collaborationCreated = true;
1486
+ }
1487
+ try {
1488
+ const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource }, userCtx);
1489
+ if (scoped.error) throw new Error(scoped.error);
1490
+ const persisted = readWorkspaceGraph(scoped.root, root).graph;
1491
+ const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, persisted, authUser, userCtx);
1492
+ const baseUrl = `${url.protocol}//${url.host}`;
1493
+ json(res, 200, {
1494
+ ok: true,
1495
+ success: true,
1496
+ action: "created",
1497
+ draftId,
1498
+ flowId,
1499
+ flowSource,
1500
+ targetSpace: requestedTarget === "team" ? "team" : flowSource === "user" ? "personal" : "workspace",
1501
+ scheduleMode: String(payload.scheduleMode || "disabled").toLowerCase(),
1502
+ workspaceSchedules,
1503
+ revision: workspaceDesignRevision(persisted),
1504
+ url: `${baseUrl}/workspace?flowId=${encodeURIComponent(flowId)}&flowSource=${encodeURIComponent(flowSource)}`,
1505
+ });
1506
+ } catch (e) {
1507
+ if (collaborationCreated) deleteWorkspaceCollaborationForFlow(flowId, false);
1508
+ throw e;
1509
+ }
1510
+ } catch (e) {
1511
+ try { fs.rmSync(targetDir, { recursive: true, force: true }); } catch (_) {}
1512
+ json(res, 500, { error: (e && e.message) || String(e) });
1513
+ }
1514
+ return;
1515
+ }
1516
+
1312
1517
  if (req.method === "POST" && url.pathname === "/api/workspace/preview") {
1313
1518
  if (!authUser?.userId) {
1314
1519
  json(res, 401, { error: "Authentication required" });
@@ -1475,6 +1680,7 @@ async function workspaceRoutes(req, res, ctx) {
1475
1680
  flowId: scoped.flowId,
1476
1681
  flowSource: scoped.flowSource,
1477
1682
  archived: scoped.archived,
1683
+ draft: scoped.draft === true,
1478
1684
  writable: !(scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource))
1479
1685
  && scoped.adminReadonly !== true
1480
1686
  && collaborationAccess.writable !== false,
@@ -1596,7 +1802,20 @@ async function workspaceRoutes(req, res, ctx) {
1596
1802
  const graph = mergeWorkspacePersistentNodeRefs(nextGraph, currentGraph);
1597
1803
  const committed = commitWorkspaceGraph(root, scoped, graph, userCtx);
1598
1804
  const { path: graphPath, revision, runtimeRevision } = committed;
1599
- const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, committed.graph, authUser, userCtx);
1805
+ let workspaceSchedules;
1806
+ try {
1807
+ workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, committed.graph, authUser, userCtx);
1808
+ } catch (scheduleError) {
1809
+ try {
1810
+ const rolledBack = commitWorkspaceGraph(root, scoped, currentStoredGraph, userCtx);
1811
+ syncWorkspaceSchedulesForGraph(root, scoped, rolledBack.graph, authUser, userCtx);
1812
+ } catch (rollbackError) {
1813
+ throw new Error(
1814
+ `Workspace schedule sync failed and graph rollback also failed: ${(scheduleError && scheduleError.message) || String(scheduleError)}; rollback: ${(rollbackError && rollbackError.message) || String(rollbackError)}`,
1815
+ );
1816
+ }
1817
+ throw new Error(`Workspace graph save rolled back: ${(scheduleError && scheduleError.message) || String(scheduleError)}`);
1818
+ }
1600
1819
  broadcastWorkspaceCollaborationEvent(
1601
1820
  userCtx,
1602
1821
  scoped.flowSource,
@@ -1655,6 +1874,80 @@ async function workspaceRoutes(req, res, ctx) {
1655
1874
  return;
1656
1875
  }
1657
1876
 
1877
+ if (req.method === "POST" && url.pathname === "/api/workspace/schedule/config") {
1878
+ let payload;
1879
+ try {
1880
+ payload = JSON.parse(await readBody(req));
1881
+ } catch {
1882
+ json(res, 400, { error: "Invalid JSON body" });
1883
+ return;
1884
+ }
1885
+ try {
1886
+ const flowId = String(payload.flowId || "").trim();
1887
+ const flowSource = String(payload.flowSource || "user").trim() || "user";
1888
+ const scheduleNodeId = String(payload.scheduleNodeId || "").trim();
1889
+ if (!flowId || !scheduleNodeId) {
1890
+ json(res, 400, { error: "flowId and scheduleNodeId are required" });
1891
+ return;
1892
+ }
1893
+ const scoped = resolveWorkspaceScopeRoot(root, { flowId, flowSource }, userCtx);
1894
+ if (scoped.error) {
1895
+ json(res, scoped.status || 400, { error: scoped.error });
1896
+ return;
1897
+ }
1898
+ if (
1899
+ scoped.archived
1900
+ || isReadonlyBuiltinFlowSource(scoped.flowSource)
1901
+ || scoped.collaborationAccess?.writable === false
1902
+ ) {
1903
+ json(res, 403, { error: "Workspace schedule edit permission denied" });
1904
+ return;
1905
+ }
1906
+ const graph = readWorkspaceGraph(scoped.root, root).graph;
1907
+ const instance = graph.instances?.[scheduleNodeId];
1908
+ if (!instance || String(instance.definitionId || "") !== "workspace_scheduled_run") {
1909
+ json(res, 404, { error: "Workspace schedule node not found" });
1910
+ return;
1911
+ }
1912
+ const current = normalizeWorkspaceScheduledRunConfig(instance.body || "");
1913
+ const config = {
1914
+ ...current,
1915
+ ...(typeof payload.enabled === "boolean" ? { enabled: payload.enabled } : {}),
1916
+ ...(payload.cron != null ? { cron: String(payload.cron).trim() } : {}),
1917
+ ...(payload.timezone != null ? { timezone: String(payload.timezone).trim() } : {}),
1918
+ ...(payload.overlapPolicy != null ? { overlapPolicy: String(payload.overlapPolicy).trim() } : {}),
1919
+ };
1920
+ if (config.overlapPolicy !== "skip") {
1921
+ json(res, 400, { error: "Only overlapPolicy=skip is supported" });
1922
+ return;
1923
+ }
1924
+ if (config.enabled) workspaceScheduleNextRunAt(config, new Date());
1925
+ graph.instances = { ...(graph.instances || {}) };
1926
+ graph.instances[scheduleNodeId] = { ...instance, body: JSON.stringify(config) };
1927
+ const committed = commitWorkspaceGraph(root, scoped, graph, userCtx);
1928
+ const workspaceSchedules = syncWorkspaceSchedulesForGraph(root, scoped, committed.graph, authUser, userCtx);
1929
+ const schedule = workspaceSchedules.find((item) => item.scheduleNodeId === scheduleNodeId) || {
1930
+ flowId,
1931
+ flowSource,
1932
+ scheduleNodeId,
1933
+ ...config,
1934
+ suppressed: scoped.draft === true,
1935
+ };
1936
+ json(res, 200, {
1937
+ ok: true,
1938
+ flowId,
1939
+ flowSource,
1940
+ draft: scoped.draft === true,
1941
+ revision: committed.revision,
1942
+ schedule,
1943
+ workspaceSchedules,
1944
+ });
1945
+ } catch (e) {
1946
+ json(res, 400, { error: (e && e.message) || String(e) });
1947
+ }
1948
+ return;
1949
+ }
1950
+
1658
1951
  if (req.method === "POST" && url.pathname === "/api/workspace/run/plan") {
1659
1952
  let payload;
1660
1953
  try {
@@ -44,6 +44,7 @@ import { FLOW_SOURCE_FILENAME, WORKSPACE_GRAPH_FILENAME, WorkspaceFlowParseError
44
44
  import { createWorkspaceRunController, terminateWorkspaceChild } from "./workspace-run-controller.mjs";
45
45
  import { appendWorkspaceRunLogEvent, createWorkspaceRunLogSession, finishWorkspaceRunLogSession } from "./workspace-run-logs.mjs";
46
46
  import { splitWorkspaceGraph } from "./workspace-state.mjs";
47
+ import { isWorkspaceDraftDir } from "./workspace-draft.mjs";
47
48
  import { getPipelineFiles } from "./workspace-tree.mjs";
48
49
  import { spawn } from "child_process";
49
50
  import crypto from "crypto";
@@ -1491,6 +1492,7 @@ export function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {})
1491
1492
  requestedFlowSource: flowSource,
1492
1493
  workspaceId: collaboration?.id || workspaceId,
1493
1494
  archived,
1495
+ draft: isWorkspaceDraftDir(result.path),
1494
1496
  collaboration,
1495
1497
  collaborationAccess: workspaceCollaborationAccess(collaboration, opts.userId),
1496
1498
  };
@@ -6726,6 +6728,10 @@ export function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, us
6726
6728
  );
6727
6729
  if (sameSharedFlow || key.startsWith(prefix)) delete schedules[key];
6728
6730
  }
6731
+ if (isWorkspaceDraftDir(scoped?.root || "")) {
6732
+ writeWorkspaceScheduleRegistry({ version: 1, schedules });
6733
+ return [];
6734
+ }
6729
6735
  const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
6730
6736
  for (const [scheduleNodeId, instance] of Object.entries(instances)) {
6731
6737
  if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
@@ -44,24 +44,24 @@ ui:
44
44
  label: Loop execution
45
45
  field: script
46
46
  - type: decision
47
- label: Decision from step
47
+ label: Decision status
48
48
  output: decision
49
49
  source: step.stdout.decision
50
50
  options:
51
51
  - value: continue
52
- label: continue
52
+ label: continue
53
53
  tone: purple
54
54
  description: 保存 state 并立即进入下一轮
55
55
  - value: wait
56
- label: wait
56
+ label: wait
57
57
  tone: amber
58
58
  description: 保存 checkpoint,暂停当前 Run
59
59
  - value: done
60
- label: done
60
+ label: done
61
61
  tone: green
62
62
  description: 结束循环并继续下游
63
63
  - value: fail
64
- label: fail ×
64
+ label: fail
65
65
  tone: red
66
66
  description: 终止并标记节点失败
67
67
  - type: metrics
@@ -10,7 +10,7 @@ const flowId = provide.str("新流程 ID", {
10
10
 
11
11
  // 规划和落地都得照 DSL 语法写,技能直接注入,不再单独跑一个收集节点
12
12
  const skills = control.loadSkills("加载 DSL 技能", {
13
- skillKeys: "agentflow-flow-dsl,agentflow-node-reference,agentflow-workspace-graph",
13
+ skillKeys: "agentflow-flow-dsl,agentflow-node-reference",
14
14
  });
15
15
 
16
16
  // ── ① 规划 ──────────────────────────────────────────────────────────