@fieldwangai/agentflow 0.1.159 → 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 (118) hide show
  1. package/bin/lib/auth.mjs +231 -14
  2. package/bin/lib/catalog-flows.mjs +12 -1
  3. package/bin/lib/cli-auth-page.mjs +103 -0
  4. package/bin/lib/control-while.mjs +336 -0
  5. package/bin/lib/flow-dsl/codegen.mjs +74 -11
  6. package/bin/lib/flow-dsl/ir.mjs +33 -2
  7. package/bin/lib/flow-dsl/lint.mjs +128 -3
  8. package/bin/lib/flow-dsl/parser.mjs +168 -4
  9. package/bin/lib/marketplace.mjs +10 -2
  10. package/bin/lib/node-package-manifest.mjs +7 -2
  11. package/bin/lib/node-ui-kit.mjs +156 -0
  12. package/bin/lib/paths.mjs +2 -0
  13. package/bin/lib/skill-runtime.mjs +6 -0
  14. package/bin/lib/table.mjs +2 -3
  15. package/bin/lib/ui-server.mjs +220 -17
  16. package/bin/lib/workspace-draft.mjs +74 -0
  17. package/bin/lib/workspace-flow-store.mjs +11 -2
  18. package/bin/lib/workspace-graph-merge.mjs +3 -0
  19. package/bin/lib/workspace-routes.mjs +307 -5
  20. package/bin/lib/workspace-server.mjs +598 -11
  21. package/bin/pipeline/validate-flow.mjs +3 -3
  22. package/builtin/nodes/control_parse_json.md +25 -0
  23. package/builtin/nodes/control_subflow_call.md +27 -0
  24. package/builtin/nodes/control_while.md +124 -0
  25. package/builtin/nodes/provide_json.md +14 -0
  26. package/builtin/nodes/workspace_subflow_input.md +14 -0
  27. package/builtin/pipelines/new/workspace.flow.js +1 -1
  28. package/builtin/pipelines/subflow-preview/workspace.flow.js +29 -0
  29. package/builtin/pipelines/subflow-preview/workspace.layout.json +38 -0
  30. package/builtin/pipelines/subflow-preview/workspace.nodes.json +19 -0
  31. package/builtin/pipelines/subflow-preview/workspace.state.json +93 -0
  32. package/builtin/pipelines/while-subflow-preview/workspace.flow.js +55 -0
  33. package/builtin/pipelines/while-subflow-preview/workspace.layout.json +66 -0
  34. package/builtin/pipelines/while-subflow-preview/workspace.nodes.json +49 -0
  35. package/builtin/pipelines/while-subflow-preview/workspace.state.json +194 -0
  36. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-CY19DsYq.js → WorkflowAssistantThread-CTrXOZ00.js} +1 -1
  37. package/builtin/web-ui/dist/assets/index-5uJFccdX.css +1 -0
  38. package/builtin/web-ui/dist/assets/index-BeUfNQRL.js +873 -0
  39. package/builtin/web-ui/dist/index.html +2 -2
  40. package/package.json +6 -1
  41. package/reference/flow-control-capabilities.md +41 -16
  42. package/shared/slot-types.js +58 -0
  43. package/skills/agentflow-author-flow/SKILL.md +81 -8
  44. package/skills/agentflow-author-flow/agents/openai.yaml +3 -3
  45. package/skills/agentflow-cli/SKILL.md +130 -25
  46. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  47. package/skills/agentflow-cli/runtime/bin/lib/skill-runtime.mjs +14765 -0
  48. package/skills/agentflow-cli/runtime/builtin/nodes/agent_subAgent.md +34 -0
  49. package/skills/agentflow-cli/runtime/builtin/nodes/control_agent_toBool.md +26 -0
  50. package/skills/agentflow-cli/runtime/builtin/nodes/control_anyOne.md +19 -0
  51. package/skills/agentflow-cli/runtime/builtin/nodes/control_cancelled.md +26 -0
  52. package/skills/agentflow-cli/runtime/builtin/nodes/control_cd_workspace.md +50 -0
  53. package/skills/agentflow-cli/runtime/builtin/nodes/control_delay.md +31 -0
  54. package/skills/agentflow-cli/runtime/builtin/nodes/control_end.md +13 -0
  55. package/skills/agentflow-cli/runtime/builtin/nodes/control_if.md +21 -0
  56. package/skills/agentflow-cli/runtime/builtin/nodes/control_interval_loop.md +55 -0
  57. package/skills/agentflow-cli/runtime/builtin/nodes/control_load_mcp.md +24 -0
  58. package/skills/agentflow-cli/runtime/builtin/nodes/control_load_skills.md +35 -0
  59. package/skills/agentflow-cli/runtime/builtin/nodes/control_parse_json.md +25 -0
  60. package/skills/agentflow-cli/runtime/builtin/nodes/control_start.md +13 -0
  61. package/skills/agentflow-cli/runtime/builtin/nodes/control_subflow_call.md +27 -0
  62. package/skills/agentflow-cli/runtime/builtin/nodes/control_toBool.md +27 -0
  63. package/skills/agentflow-cli/runtime/builtin/nodes/control_user_workspace.md +24 -0
  64. package/skills/agentflow-cli/runtime/builtin/nodes/control_wait_until.md +34 -0
  65. package/skills/agentflow-cli/runtime/builtin/nodes/control_while.md +124 -0
  66. package/skills/agentflow-cli/runtime/builtin/nodes/display_ascii.md +24 -0
  67. package/skills/agentflow-cli/runtime/builtin/nodes/display_chart.md +32 -0
  68. package/skills/agentflow-cli/runtime/builtin/nodes/display_code.md +39 -0
  69. package/skills/agentflow-cli/runtime/builtin/nodes/display_html.md +32 -0
  70. package/skills/agentflow-cli/runtime/builtin/nodes/display_image.md +36 -0
  71. package/skills/agentflow-cli/runtime/builtin/nodes/display_markdown.md +24 -0
  72. package/skills/agentflow-cli/runtime/builtin/nodes/display_mermaid.md +24 -0
  73. package/skills/agentflow-cli/runtime/builtin/nodes/display_react_app.md +53 -0
  74. package/skills/agentflow-cli/runtime/builtin/nodes/display_table.md +32 -0
  75. package/skills/agentflow-cli/runtime/builtin/nodes/provide_bool.md +14 -0
  76. package/skills/agentflow-cli/runtime/builtin/nodes/provide_file.md +14 -0
  77. package/skills/agentflow-cli/runtime/builtin/nodes/provide_json.md +14 -0
  78. package/skills/agentflow-cli/runtime/builtin/nodes/provide_password.md +14 -0
  79. package/skills/agentflow-cli/runtime/builtin/nodes/provide_str.md +14 -0
  80. package/skills/agentflow-cli/runtime/builtin/nodes/tool_display_share_link.md +47 -0
  81. package/skills/agentflow-cli/runtime/builtin/nodes/tool_get_env.md +20 -0
  82. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_checkout.md +70 -0
  83. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_load.md +73 -0
  84. package/skills/agentflow-cli/runtime/builtin/nodes/tool_git_worktree_unload.md +52 -0
  85. package/skills/agentflow-cli/runtime/builtin/nodes/tool_gitlab_create_mr.md +114 -0
  86. package/skills/agentflow-cli/runtime/builtin/nodes/tool_jenkins_build.md +67 -0
  87. package/skills/agentflow-cli/runtime/builtin/nodes/tool_load_key.md +26 -0
  88. package/skills/agentflow-cli/runtime/builtin/nodes/tool_nodejs.md +52 -0
  89. package/skills/agentflow-cli/runtime/builtin/nodes/tool_print.md +21 -0
  90. package/skills/agentflow-cli/runtime/builtin/nodes/tool_save_key.md +26 -0
  91. package/skills/agentflow-cli/runtime/builtin/nodes/tool_set_run_env.md +38 -0
  92. package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_ask.md +27 -0
  93. package/skills/agentflow-cli/runtime/builtin/nodes/tool_user_check.md +26 -0
  94. package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_app_markdown.md +57 -0
  95. package/skills/agentflow-cli/runtime/builtin/nodes/tool_wecom_send_group_markdown.md +45 -0
  96. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_one_click_task.md +44 -0
  97. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_run.md +16 -0
  98. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_scheduled_run.md +16 -0
  99. package/skills/agentflow-cli/runtime/builtin/nodes/workspace_subflow_input.md +14 -0
  100. package/skills/agentflow-cli/runtime/package.json +6 -0
  101. package/skills/agentflow-cli/scripts/agentflow-auth-store.mjs +113 -0
  102. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +393 -28
  103. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +11 -4
  104. package/skills/agentflow-flow-add-instances/SKILL.md +2 -2
  105. package/skills/agentflow-flow-dsl/SKILL.md +83 -10
  106. package/skills/agentflow-flow-dsl/agents/openai.yaml +2 -2
  107. package/skills/agentflow-flow-dsl/references/node-calls.md +3 -0
  108. package/skills/agentflow-flow-dsl/references/subflow-authoring.md +230 -0
  109. package/skills/agentflow-flow-edit-node-fields/SKILL.md +3 -3
  110. package/skills/agentflow-flow-recipes/SKILL.md +1 -1
  111. package/skills/agentflow-flow-recipes/references/recipes.md +1 -2
  112. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  113. package/skills/agentflow-node-dsl/SKILL.md +44 -13
  114. package/skills/agentflow-node-reference/references/builtin-nodes.md +25 -0
  115. package/skills/agentflow-workspace-markdown/SKILL.md +1 -1
  116. package/builtin/web-ui/dist/assets/index-B9ppXv7e.css +0 -1
  117. package/builtin/web-ui/dist/assets/index-CdEQWRrp.js +0 -872
  118. 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 || {})) {
@@ -538,6 +564,13 @@ function buildNodeStudioPrompt({ requirement, currentSource, parseError, history
538
564
  " 真实运行时位于会被清理的临时目录里。",
539
565
  "3. 槽位类型只能是 `text` `file` `bool` `node` `image` `json`。声明顺序 = 画布上的引脚顺序。",
540
566
  "",
567
+ "## 可选 Node UI Kit",
568
+ "",
569
+ "需要让画布直接解释节点时,在 export default 里声明 `ui.card`。只能组合安全组件:",
570
+ "`binding`、`code`、`decision`、`metrics`、`summary`、`history`、`subflow`;不能写 React、HTML 或事件处理器。",
571
+ "组件里的 input/output 必须引用已声明槽位。template 可用 details / state-machine,tone 可用",
572
+ "neutral / blue / purple / green / amber / red。没有可解释内容时可以不声明 UI。",
573
+ "",
541
574
  "失败用抛异常或非零退出表示,不要把 stdout 包成 JSON。",
542
575
  currentSource ? `\n## 当前 ${NODE_PACKAGE_ENTRY}\n\n\`\`\`js\n${currentSource}\n\`\`\`` : "",
543
576
  parseError ? `\n## 上一版解析失败,必须修掉\n\n${parseError}` : "",
@@ -568,7 +601,7 @@ function emptyNodeStudioDraft(userCtx = {}, draftId = "") {
568
601
  inputs: [],
569
602
  outputs: [],
570
603
  configSchema: { fields: [] },
571
- ui: { card: { icon: "extension", variant: "default", actions: [] } },
604
+ ui: { card: { template: "details", icon: "extension", tone: "neutral", sections: [] } },
572
605
  },
573
606
  config: {},
574
607
  test: { inputs: {}, log: [], status: "not run" },
@@ -1302,6 +1335,185 @@ async function workspaceRoutes(req, res, ctx) {
1302
1335
  return;
1303
1336
  }
1304
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
+
1305
1517
  if (req.method === "POST" && url.pathname === "/api/workspace/preview") {
1306
1518
  if (!authUser?.userId) {
1307
1519
  json(res, 401, { error: "Authentication required" });
@@ -1468,6 +1680,7 @@ async function workspaceRoutes(req, res, ctx) {
1468
1680
  flowId: scoped.flowId,
1469
1681
  flowSource: scoped.flowSource,
1470
1682
  archived: scoped.archived,
1683
+ draft: scoped.draft === true,
1471
1684
  writable: !(scoped.archived || isReadonlyBuiltinFlowSource(scoped.flowSource))
1472
1685
  && scoped.adminReadonly !== true
1473
1686
  && collaborationAccess.writable !== false,
@@ -1589,7 +1802,20 @@ async function workspaceRoutes(req, res, ctx) {
1589
1802
  const graph = mergeWorkspacePersistentNodeRefs(nextGraph, currentGraph);
1590
1803
  const committed = commitWorkspaceGraph(root, scoped, graph, userCtx);
1591
1804
  const { path: graphPath, revision, runtimeRevision } = committed;
1592
- 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
+ }
1593
1819
  broadcastWorkspaceCollaborationEvent(
1594
1820
  userCtx,
1595
1821
  scoped.flowSource,
@@ -1648,6 +1874,80 @@ async function workspaceRoutes(req, res, ctx) {
1648
1874
  return;
1649
1875
  }
1650
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
+
1651
1951
  if (req.method === "POST" && url.pathname === "/api/workspace/run/plan") {
1652
1952
  let payload;
1653
1953
  try {
@@ -2917,6 +3217,7 @@ async function workspaceRoutes(req, res, ctx) {
2917
3217
  const flowSource = url.searchParams.get("flowSource") || "user";
2918
3218
  const lang = url.searchParams.get("lang") || "en";
2919
3219
  const marketplaceScope = url.searchParams.get("scope") === "owned" ? "owned" : "all";
3220
+ const includeHidden = url.searchParams.get("includeHidden") === "1";
2920
3221
  if (flowId && !isValidFlowSourceRead(flowSource)) {
2921
3222
  json(res, 400, { error: "Invalid flowSource" });
2922
3223
  return;
@@ -2934,6 +3235,7 @@ async function workspaceRoutes(req, res, ctx) {
2934
3235
  archived: nodesArchived,
2935
3236
  ...requestedContext.userCtx,
2936
3237
  marketplaceScope,
3238
+ includeHidden,
2937
3239
  }));
2938
3240
  } catch (e) {
2939
3241
  json(res, 500, { error: (e && e.message) || String(e) });