@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
@@ -0,0 +1,113 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ function emptyStore() {
7
+ return { version: 1, profiles: {}, pending: {} };
8
+ }
9
+
10
+ export function agentFlowAuthFile() {
11
+ const configured = String(process.env.AGENTFLOW_AUTH_FILE || "").trim();
12
+ return path.resolve(configured || path.join(os.homedir(), ".agentflow", "auth.json"));
13
+ }
14
+
15
+ export function readAgentFlowAuthStore() {
16
+ const file = agentFlowAuthFile();
17
+ try {
18
+ const data = JSON.parse(fs.readFileSync(file, "utf-8"));
19
+ if (!data || typeof data !== "object" || Array.isArray(data)) return emptyStore();
20
+ return {
21
+ version: 1,
22
+ profiles: data.profiles && typeof data.profiles === "object" && !Array.isArray(data.profiles) ? data.profiles : {},
23
+ pending: data.pending && typeof data.pending === "object" && !Array.isArray(data.pending) ? data.pending : {},
24
+ };
25
+ } catch {
26
+ return emptyStore();
27
+ }
28
+ }
29
+
30
+ export function writeAgentFlowAuthStore(store) {
31
+ const file = agentFlowAuthFile();
32
+ const dir = path.dirname(file);
33
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
34
+ try { fs.chmodSync(dir, 0o700); } catch {}
35
+ const temp = `${file}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`;
36
+ fs.writeFileSync(temp, `${JSON.stringify(store, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
37
+ try { fs.chmodSync(temp, 0o600); } catch {}
38
+ fs.renameSync(temp, file);
39
+ try { fs.chmodSync(file, 0o600); } catch {}
40
+ return file;
41
+ }
42
+
43
+ function profileKey(baseUrl) {
44
+ return String(baseUrl || "").replace(/\/+$/, "");
45
+ }
46
+
47
+ export function savedAgentFlowProfile(baseUrl) {
48
+ const key = profileKey(baseUrl);
49
+ const profile = readAgentFlowAuthStore().profiles[key];
50
+ if (!profile || typeof profile !== "object") return null;
51
+ if (Number(profile.expiresAt) > 0 && Number(profile.expiresAt) <= Date.now()) return null;
52
+ const token = String(profile.token || "").trim();
53
+ return token ? { ...profile, token } : null;
54
+ }
55
+
56
+ export function savedAgentFlowPendingAuthorization(baseUrl) {
57
+ const key = profileKey(baseUrl);
58
+ const pending = readAgentFlowAuthStore().pending[key];
59
+ if (!pending || typeof pending !== "object") return null;
60
+ if (Number(pending.expiresAt) > 0 && Number(pending.expiresAt) <= Date.now()) return null;
61
+ return { ...pending };
62
+ }
63
+
64
+ export function saveAgentFlowPendingAuthorization(baseUrl, pending) {
65
+ const key = profileKey(baseUrl);
66
+ const store = readAgentFlowAuthStore();
67
+ store.pending[key] = {
68
+ requestId: String(pending.requestId || ""),
69
+ deviceCode: String(pending.deviceCode || ""),
70
+ userCode: String(pending.userCode || ""),
71
+ verificationUrl: String(pending.verificationUrl || ""),
72
+ expiresAt: Number(pending.expiresAt) || 0,
73
+ pollInterval: Number(pending.pollInterval) || 3,
74
+ createdAt: Date.now(),
75
+ };
76
+ writeAgentFlowAuthStore(store);
77
+ return { ...store.pending[key], deviceCode: undefined };
78
+ }
79
+
80
+ export function saveAgentFlowProfile(baseUrl, profile) {
81
+ const key = profileKey(baseUrl);
82
+ const store = readAgentFlowAuthStore();
83
+ store.profiles[key] = {
84
+ token: String(profile.token || ""),
85
+ tokenType: String(profile.tokenType || "Bearer"),
86
+ expiresAt: Number(profile.expiresAt) || 0,
87
+ user: profile.user && typeof profile.user === "object" ? profile.user : null,
88
+ scopes: Array.isArray(profile.scopes) ? profile.scopes.map(String) : [],
89
+ updatedAt: Date.now(),
90
+ };
91
+ delete store.pending[key];
92
+ const file = writeAgentFlowAuthStore(store);
93
+ return { file, profile: { ...store.profiles[key], token: undefined } };
94
+ }
95
+
96
+ export function clearAgentFlowPendingAuthorization(baseUrl) {
97
+ const key = profileKey(baseUrl);
98
+ const store = readAgentFlowAuthStore();
99
+ if (!store.pending[key]) return false;
100
+ delete store.pending[key];
101
+ writeAgentFlowAuthStore(store);
102
+ return true;
103
+ }
104
+
105
+ export function clearAgentFlowProfile(baseUrl) {
106
+ const key = profileKey(baseUrl);
107
+ const store = readAgentFlowAuthStore();
108
+ const existed = Boolean(store.profiles[key] || store.pending[key]);
109
+ delete store.profiles[key];
110
+ delete store.pending[key];
111
+ if (existed) writeAgentFlowAuthStore(store);
112
+ return existed;
113
+ }
@@ -7,6 +7,15 @@ import {
7
7
  loadAgentFlowRuntime,
8
8
  resolveAgentFlowPackageRoot,
9
9
  } from "./agentflow-runtime.mjs";
10
+ import {
11
+ agentFlowAuthFile,
12
+ clearAgentFlowPendingAuthorization,
13
+ clearAgentFlowProfile,
14
+ saveAgentFlowPendingAuthorization,
15
+ saveAgentFlowProfile,
16
+ savedAgentFlowPendingAuthorization,
17
+ savedAgentFlowProfile,
18
+ } from "./agentflow-auth-store.mjs";
10
19
  import { createWorkflowReportClient } from "./workflow-report-client.mjs";
11
20
 
12
21
  const DEFAULT_BASE_URL = "http://ai.mengma.bigo.inner/";
@@ -48,13 +57,20 @@ Usage:
48
57
  Config:
49
58
  --base-url <url> Override AGENTFLOW_BASE_URL
50
59
  --token <token> Override AGENTFLOW_TOKEN
51
- --agentflow-package-root <dir> Override the local @fieldwangai/agentflow runtime
60
+ --agentflow-package-root <dir> Development override for the bundled Skill runtime
52
61
  AGENTFLOW_BASE_URL Defaults to ${DEFAULT_BASE_URL}
53
- AGENTFLOW_TOKEN Required unless AGENTFLOW_SESSION_TOKEN is set
62
+ AGENTFLOW_TOKEN Overrides saved browser authorization
54
63
  AGENTFLOW_ENV_FILE Optional dotenv file path
64
+ AGENTFLOW_AUTH_FILE Optional saved authorization path
55
65
 
56
66
  Commands:
57
67
  config
68
+ auth start | auth login
69
+ auth complete
70
+ auth status
71
+ auth logout
72
+ dsl-lint --file <flowDir|workspace.flow.js> [--workspace-root <dir>]
73
+ dsl-layout --file <flowDir|workspace.flow.js> [--workspace-root <dir>] [--all]
58
74
  list-workspace | list-workspaces
59
75
  list-flows
60
76
  node-package-list
@@ -63,17 +79,27 @@ Commands:
63
79
  node-package-install --node <id>@<version> [--workspace-root <dir>]
64
80
  node-package-sync --flow <flowDir|workspace.flow.js> [--workspace-root <dir>]
65
81
  pull-flow --flow-id <id> [--flow-source user] [--output <flowDir>] [--workspace-root <dir>] [--replace]
66
- publish-flow --flow-id <id> --file <flowDir|workspace.flow.js|flow.yaml> [--target-space personal|workspace|team] [--with-dependencies] [--replace]
82
+ publish-flow --flow-id <id> --file <flowDir|workspace.flow.js|flow.yaml> [--target-space personal|workspace|team] [--schedule enabled|disabled|preserve] [--with-dependencies] [--replace]
67
83
  get-graph --flow-id <id> [--flow-source user]
68
84
  migrate-flow --flow-id <id> [--flow-source user] [--archived] [--allow-loss]
69
85
  migrate-all [--include-archived] [--allow-loss] [--dry-run]
70
86
  workspace-preview --file <flowDir|workspace.flow.js|workspace.graph.json> [--preview-id <id>] [--ttl-seconds <n>]
87
+ draft-create --file <flowDir|workspace.flow.js|workspace.graph.json> [--draft-id <id>] [--ttl-seconds <n>] [--with-dependencies]
88
+ draft-pull --draft-id <id> [--output <flowDir>] [--workspace-root <dir>] [--replace]
89
+ draft-update --draft-id <id> --base-revision <revision> --file <flowDir|workspace.flow.js|workspace.graph.json> [--ttl-seconds <n>] [--with-dependencies]
90
+ draft-run --draft-id <id> [--run-node-id <id>] [--input k=v]
91
+ draft-publish --draft-id <id> --flow-id <id> [--target-space personal|workspace|team] [--schedule enabled|disabled|preserve]
71
92
  run --flow-id <id> [--flow-source user] [--run-node-id <id>] [--input k=v]
72
93
  status --flow-id <id> [--flow-source user]
73
94
  list-run-by-workspace | list-runs-by-workspace --workspace <flowId> [--limit 20]
74
95
  list-runs [--flow-id <id>] [--flow-source user] [--limit 20]
75
96
  logs --run-id <id>
76
97
  display-outputs --flow-id <id> [--flow-source user]
98
+ schedule-list [--flow-id <id>] [--flow-source user]
99
+ schedule-set --flow-id <id> --schedule-node-id <id> [--enabled true|false] [--cron <expr>] [--timezone <tz>] [--overlap-policy skip]
100
+ schedule-enable --flow-id <id> --schedule-node-id <id>
101
+ schedule-disable --flow-id <id> --schedule-node-id <id>
102
+ schedule-run-now --flow-id <id> --schedule-node-id <id> [--input k=v]
77
103
  sync-workspace --workspace <id>
78
104
  workflow-get --workflow tapd:<id> [--flow-id <id>] [--runtime-only] [--admin-operation repair-version-membership]
79
105
  workflow-access-sync --workflow tapd:<id> --file <access.json>
@@ -158,11 +184,23 @@ function normalizedBaseUrl(args) {
158
184
  return String(raw).replace(/\/+$/, "");
159
185
  }
160
186
 
187
+ function resolvedAuth(args) {
188
+ const fromFlag = option(args, "token");
189
+ if (fromFlag) return { token: fromFlag, source: "flag", profile: null };
190
+ const fromTokenEnv = String(process.env.AGENTFLOW_TOKEN || "").trim();
191
+ if (fromTokenEnv) return { token: fromTokenEnv, source: "AGENTFLOW_TOKEN", profile: null };
192
+ const fromSessionEnv = String(process.env.AGENTFLOW_SESSION_TOKEN || "").trim();
193
+ if (fromSessionEnv) return { token: fromSessionEnv, source: "AGENTFLOW_SESSION_TOKEN", profile: null };
194
+ const profile = savedAgentFlowProfile(normalizedBaseUrl(args));
195
+ return profile?.token
196
+ ? { token: profile.token, source: "saved-auth", profile }
197
+ : { token: "", source: "", profile: null };
198
+ }
199
+
161
200
  function authToken(args, required = true) {
162
- const token = option(args, "token") || process.env.AGENTFLOW_TOKEN || process.env.AGENTFLOW_SESSION_TOKEN || "";
163
- const trimmed = String(token).trim();
201
+ const trimmed = String(resolvedAuth(args).token || "").trim();
164
202
  if (required && !trimmed) {
165
- throw new Error("Missing AGENTFLOW_TOKEN. Set it in env, .env, .agentflow.env, or pass --token.");
203
+ throw new Error("AgentFlow authorization is missing. Run `auth start`, open the returned URL, then run `auth complete`; or set AGENTFLOW_TOKEN.");
166
204
  }
167
205
  return trimmed;
168
206
  }
@@ -488,6 +526,7 @@ async function pullFlow(args) {
488
526
  ok: true,
489
527
  flowId,
490
528
  flowSource,
529
+ draft: current.draft === true,
491
530
  outputDir,
492
531
  revision: current.revision || "",
493
532
  format: written.format,
@@ -529,6 +568,60 @@ function parseInputs(args) {
529
568
  return inputs;
530
569
  }
531
570
 
571
+ function parseBooleanOption(value, name) {
572
+ if (value === true || value === "true" || value === "1") return true;
573
+ if (value === false || value === "false" || value === "0") return false;
574
+ throw new Error(`${name} must be true or false.`);
575
+ }
576
+
577
+ async function runRemoteWorkspace(args, { flowId, flowSource = "user", runNodeId = "" }) {
578
+ const graphPayload = await httpJson(args, `/api/workspace/graph${query({ flowId, flowSource })}`);
579
+ const result = await httpJson(args, "/api/workspace/run", {
580
+ method: "POST",
581
+ body: {
582
+ flowId,
583
+ flowSource,
584
+ runNodeId,
585
+ runAlias: option(args, "run-alias") || "",
586
+ graph: graphPayload.graph,
587
+ inputs: parseInputs(args),
588
+ },
589
+ });
590
+ return {
591
+ ...result,
592
+ displayOutputs: extractDisplayOutputs(result?.graph),
593
+ };
594
+ }
595
+
596
+ async function prepareDraftGraph(args) {
597
+ const requestedFile = option(args, "file");
598
+ const withDependencies = args["with-dependencies"] === true;
599
+ let publishedNodePackages = [];
600
+ let rewrittenImports = [];
601
+ if (withDependencies) {
602
+ const rawSource = readFlowSourceFile(requestedFile, { allowPackageDependencies: true });
603
+ const source = await prepareFlowWithDependencies(args, requestedFile, rawSource);
604
+ publishedNodePackages = source.publishedNodePackages || [];
605
+ rewrittenImports = source.rewrittenImports || [];
606
+ }
607
+ const graph = await readWorkspaceGraphArg(requestedFile, path.resolve(option(args, "workspace-root") || process.cwd()));
608
+ const nodeDependencies = await graphNodePackageDependencies(graph);
609
+ if (nodeDependencies.length) {
610
+ const remoteByKey = await remoteNodePackageCatalog(args);
611
+ const missing = nodeDependencies.filter((item) => !remoteByKey.has(`${item.id}@${item.version}`));
612
+ if (missing.length) {
613
+ throw new Error(`Cannot create draft: server is missing node packages ${missing.map((item) => item.specifier).join(", ")}.`);
614
+ }
615
+ }
616
+ return {
617
+ graph,
618
+ file: path.resolve(String(requestedFile || "")),
619
+ nodeDependencies: nodeDependencies.map((item) => item.specifier),
620
+ publishedNodePackages,
621
+ rewrittenImports,
622
+ };
623
+ }
624
+
532
625
  function slotText(slots, names = []) {
533
626
  const wanted = new Set(names.map(String));
534
627
  for (const slot of Array.isArray(slots) ? slots : []) {
@@ -566,6 +659,125 @@ function printJson(data) {
566
659
  process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
567
660
  }
568
661
 
662
+ function authAction(command, args) {
663
+ if (command === "auth") return String(args._[1] || "status").trim().toLowerCase();
664
+ if (command.startsWith("auth-")) return command.slice("auth-".length).toLowerCase();
665
+ return "";
666
+ }
667
+
668
+ async function runAuthCommand(action, args) {
669
+ const baseUrl = normalizedBaseUrl(args);
670
+ if (action === "start" || action === "login") {
671
+ const authorization = await httpJson(args, "/api/auth/cli/device", {
672
+ method: "POST",
673
+ tokenRequired: false,
674
+ body: {
675
+ clientName: option(args, "client-name") || "AgentFlow CLI",
676
+ },
677
+ });
678
+ const saved = saveAgentFlowPendingAuthorization(baseUrl, authorization);
679
+ printJson({
680
+ status: "authorization_required",
681
+ baseUrl,
682
+ requestId: saved.requestId,
683
+ userCode: saved.userCode,
684
+ verificationUrl: saved.verificationUrl,
685
+ expiresAt: saved.expiresAt,
686
+ next: "Open verificationUrl, approve access, then run `auth complete`.",
687
+ });
688
+ return;
689
+ }
690
+
691
+ if (action === "complete") {
692
+ const pending = savedAgentFlowPendingAuthorization(baseUrl);
693
+ if (!pending?.deviceCode) {
694
+ throw new Error("No pending AgentFlow authorization. Run `auth start` first.");
695
+ }
696
+ let result;
697
+ try {
698
+ result = await httpJson(args, "/api/auth/cli/token", {
699
+ method: "POST",
700
+ tokenRequired: false,
701
+ body: { deviceCode: pending.deviceCode },
702
+ });
703
+ } catch (error) {
704
+ const code = String(error?.response?.code || "");
705
+ if (["access_denied", "expired_token", "invalid_grant"].includes(code)) {
706
+ clearAgentFlowPendingAuthorization(baseUrl);
707
+ }
708
+ throw error;
709
+ }
710
+ if (result?.code === "authorization_pending") {
711
+ printJson({
712
+ status: "authorization_pending",
713
+ baseUrl,
714
+ requestId: pending.requestId,
715
+ verificationUrl: pending.verificationUrl,
716
+ expiresAt: pending.expiresAt,
717
+ });
718
+ process.exitCode = 2;
719
+ return;
720
+ }
721
+ if (!result?.token) throw new Error("AgentFlow authorization exchange did not return a token.");
722
+ const saved = saveAgentFlowProfile(baseUrl, result);
723
+ printJson({
724
+ status: "authenticated",
725
+ baseUrl,
726
+ user: result.user || null,
727
+ scopes: Array.isArray(result.scopes) ? result.scopes : [],
728
+ expiresAt: result.expiresAt || 0,
729
+ credentialFile: saved.file,
730
+ });
731
+ return;
732
+ }
733
+
734
+ if (action === "status") {
735
+ const auth = resolvedAuth(args);
736
+ if (!auth.token) {
737
+ printJson({ authenticated: false, baseUrl, tokenSource: "", credentialFile: agentFlowAuthFile() });
738
+ return;
739
+ }
740
+ const me = await httpJson(args, "/api/auth/me", { tokenRequired: false });
741
+ printJson({
742
+ authenticated: Boolean(me?.authenticated),
743
+ baseUrl,
744
+ tokenSource: auth.source,
745
+ user: me?.user || null,
746
+ expiresAt: auth.profile?.expiresAt || me?.user?.sessionExpiresAt || 0,
747
+ credentialFile: auth.source === "saved-auth" ? agentFlowAuthFile() : "",
748
+ });
749
+ return;
750
+ }
751
+
752
+ if (action === "logout") {
753
+ const auth = resolvedAuth(args);
754
+ let revoked = false;
755
+ let remoteError = "";
756
+ if (auth.token) {
757
+ try {
758
+ const response = await httpJson(args, "/api/auth/cli/revoke", { method: "POST", body: {} });
759
+ revoked = Boolean(response?.ok);
760
+ } catch (error) {
761
+ remoteError = error?.message || String(error);
762
+ }
763
+ }
764
+ const localCleared = clearAgentFlowProfile(baseUrl);
765
+ printJson({
766
+ authenticated: false,
767
+ baseUrl,
768
+ revoked,
769
+ localCleared,
770
+ tokenSource: auth.source,
771
+ warning: auth.source && auth.source !== "saved-auth"
772
+ ? `${auth.source} is still configured outside the CLI credential store.`
773
+ : remoteError || "",
774
+ });
775
+ return;
776
+ }
777
+
778
+ throw new Error(`Unknown auth command: ${action}. Use auth start, complete, status, or logout.`);
779
+ }
780
+
569
781
  function requireFlowId(args) {
570
782
  const flowId = option(args, "flow-id") || option(args, "flow");
571
783
  if (!flowId) throw new Error("Missing --flow-id.");
@@ -755,16 +967,48 @@ function isMissingPublishedFlowError(error) {
755
967
  return error?.status === 400 && /Pipeline directory not found/i.test(String(error?.message || ""));
756
968
  }
757
969
 
758
- async function importFlow(args, { flowId, targetSpace, resolved, flowYaml }) {
970
+ async function importFlow(args, { flowId, targetSpace, scheduleMode = "disabled", resolved, flowYaml }) {
759
971
  const form = new FormData();
760
972
  form.set("flowId", flowId);
761
973
  form.set("targetSpace", targetSpace);
974
+ form.set("scheduleMode", scheduleMode);
762
975
  const name = path.basename(resolved);
763
976
  const mime = /\.m?js$/i.test(name) ? "application/javascript" : "application/yaml";
764
977
  form.set("file", new Blob([flowYaml], { type: mime }), name);
765
978
  return httpMultipart(args, "/api/flows/import", form);
766
979
  }
767
980
 
981
+ function normalizeScheduleMode(args, fallback = "disabled") {
982
+ const scheduleMode = String(option(args, "schedule") || fallback).trim().toLowerCase();
983
+ if (!["enabled", "disabled", "preserve"].includes(scheduleMode)) {
984
+ throw new Error("--schedule must be enabled, disabled, or preserve.");
985
+ }
986
+ return scheduleMode;
987
+ }
988
+
989
+ function graphWithScheduleMode(graph, scheduleMode = "disabled") {
990
+ if (scheduleMode === "preserve") return graph;
991
+ const instances = { ...(graph?.instances || {}) };
992
+ let scheduleCount = 0;
993
+ for (const [nodeId, instance] of Object.entries(instances)) {
994
+ if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
995
+ scheduleCount += 1;
996
+ let config = {};
997
+ try {
998
+ const parsed = JSON.parse(String(instance.body || "{}"));
999
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) config = parsed;
1000
+ } catch {}
1001
+ instances[nodeId] = {
1002
+ ...instance,
1003
+ body: JSON.stringify({ ...config, enabled: scheduleMode === "enabled" }),
1004
+ };
1005
+ }
1006
+ if (scheduleMode === "enabled" && scheduleCount === 0) {
1007
+ throw new Error("Cannot enable scheduling: the Flow has no Scheduled Run node.");
1008
+ }
1009
+ return { ...graph, instances };
1010
+ }
1011
+
768
1012
  async function resolvePublishTeam(args, shareWithTeam) {
769
1013
  if (!shareWithTeam) return null;
770
1014
  const result = await httpJson(args, "/api/teams/me");
@@ -793,6 +1037,12 @@ async function main() {
793
1037
  return;
794
1038
  }
795
1039
 
1040
+ const selectedAuthAction = authAction(command, args);
1041
+ if (selectedAuthAction) {
1042
+ await runAuthCommand(selectedAuthAction, args);
1043
+ return;
1044
+ }
1045
+
796
1046
  if (command === "config") {
797
1047
  let localRuntime = { available: false, root: "", version: "" };
798
1048
  try {
@@ -802,15 +1052,39 @@ async function main() {
802
1052
  } catch (error) {
803
1053
  localRuntime.error = error?.message || String(error);
804
1054
  }
1055
+ const auth = resolvedAuth(args);
805
1056
  printJson({
806
1057
  baseUrl: normalizedBaseUrl(args),
807
- hasToken: Boolean(authToken(args, false)),
808
- tokenSource: option(args, "token") ? "flag" : process.env.AGENTFLOW_TOKEN ? "AGENTFLOW_TOKEN" : process.env.AGENTFLOW_SESSION_TOKEN ? "AGENTFLOW_SESSION_TOKEN" : "",
1058
+ hasToken: Boolean(auth.token),
1059
+ tokenSource: auth.source,
1060
+ credentialFile: auth.source === "saved-auth" ? agentFlowAuthFile() : "",
809
1061
  localRuntime,
810
1062
  });
811
1063
  return;
812
1064
  }
813
1065
 
1066
+ if (command === "dsl-lint" || command === "dsl-layout") {
1067
+ const target = option(args, "file");
1068
+ if (!target) throw new Error(`${command} requires --file <flowDir|workspace.flow.js>.`);
1069
+ const resolved = path.resolve(target);
1070
+ const stat = fs.statSync(resolved);
1071
+ const flowDir = stat.isDirectory() ? resolved : path.dirname(resolved);
1072
+ const workspaceRoot = path.resolve(option(args, "workspace-root") || process.cwd());
1073
+ const runtime = await loadAgentFlowRuntime();
1074
+ if (command === "dsl-lint") {
1075
+ const result = runtime.lintWorkspaceFlowDir(flowDir, { workspaceRoot });
1076
+ printJson(result);
1077
+ if (result.errors.length) process.exitCode = 1;
1078
+ return;
1079
+ }
1080
+ const result = runtime.layoutWorkspaceFlowDir(flowDir, {
1081
+ all: args.all === true,
1082
+ workspaceRoot,
1083
+ });
1084
+ printJson(result);
1085
+ return;
1086
+ }
1087
+
814
1088
  if (command === "list-workspace" || command === "list-workspaces") {
815
1089
  printJson(await httpJson(args, "/api/workspaces"));
816
1090
  return;
@@ -885,10 +1159,23 @@ async function main() {
885
1159
  return;
886
1160
  }
887
1161
 
1162
+ if (command === "draft-pull") {
1163
+ const draftId = option(args, "draft-id");
1164
+ if (!draftId) throw new Error("draft-pull requires --draft-id <id>.");
1165
+ args["flow-id"] = draftId;
1166
+ args["flow-source"] = "user";
1167
+ const result = await pullFlow(args);
1168
+ if (result.ok && result.draft !== true) throw new Error(`${draftId} is not a Workspace Draft.`);
1169
+ printJson({ ...result, draftId });
1170
+ if (!result.ok) process.exitCode = 2;
1171
+ return;
1172
+ }
1173
+
888
1174
  if (command === "publish-flow") {
889
1175
  const flowId = requireFlowId(args);
890
1176
  const destination = targetDestinationFromArgs(args);
891
1177
  const targetSpace = destination.flowSource;
1178
+ const scheduleMode = normalizeScheduleMode(args);
892
1179
  const withDependencies = args["with-dependencies"] === true;
893
1180
  const requestedFile = option(args, "file");
894
1181
  const rawSource = readFlowSourceFile(requestedFile, { allowPackageDependencies: withDependencies });
@@ -915,7 +1202,7 @@ async function main() {
915
1202
  const dependencyPreflight = await preflightRemoteFlowDependencies(args, source);
916
1203
 
917
1204
  if (!replace) {
918
- const result = await importFlow(args, { flowId, targetSpace, ...source });
1205
+ const result = await importFlow(args, { flowId, targetSpace, scheduleMode, ...source });
919
1206
  const sharedTeam = await sharePublishedFlowWithTeam(args, { flowId, flowSource: targetSpace, team });
920
1207
  printJson({ ...result, action: "created", targetSpace: team ? "team" : targetSpace, team: sharedTeam, file: source.resolved, nodeDependencies: dependencyPreflight.map((item) => item.specifier), rewrittenImports: source.rewrittenImports || [], publishedNodePackages: source.publishedNodePackages || [] });
921
1208
  return;
@@ -924,16 +1211,16 @@ async function main() {
924
1211
  // 更新走哪条路取决于存储格式:yaml 流程改 /api/flow,代码化流程改 Workspace 图。
925
1212
  // /api/flow 只认 flowYaml 字符串,代码化的流程发过去等于把图退回成 yaml。
926
1213
  if (!current) {
927
- const result = await importFlow(args, { flowId, targetSpace, ...source });
1214
+ const result = await importFlow(args, { flowId, targetSpace, scheduleMode, ...source });
928
1215
  const sharedTeam = await sharePublishedFlowWithTeam(args, { flowId, flowSource: targetSpace, team });
929
1216
  printJson({ ...result, action: "created", targetSpace: team ? "team" : targetSpace, team: sharedTeam, file: source.resolved, nodeDependencies: dependencyPreflight.map((item) => item.specifier), rewrittenImports: source.rewrittenImports || [], publishedNodePackages: source.publishedNodePackages || [] });
930
1217
  return;
931
1218
  }
932
1219
  if (source.isCode) {
933
- const graph = await readWorkspaceGraphArg(
1220
+ const graph = graphWithScheduleMode(await readWorkspaceGraphArg(
934
1221
  option(args, "file"),
935
1222
  path.resolve(option(args, "workspace-root") || process.cwd()),
936
- );
1223
+ ), scheduleMode);
937
1224
  const updated = await httpJson(args, "/api/workspace/graph", {
938
1225
  method: "POST",
939
1226
  body: { flowId, flowSource: targetSpace, graph, baseRevision: current.revision },
@@ -1088,26 +1375,66 @@ async function main() {
1088
1375
  return;
1089
1376
  }
1090
1377
 
1091
- if (command === "run") {
1092
- const flowId = requireFlowId(args);
1093
- const flowSource = option(args, "flow-source") || "user";
1094
- const runNodeId = option(args, "run-node-id") || "";
1095
- const graphPayload = await httpJson(args, `/api/workspace/graph${query({ flowId, flowSource })}`);
1096
- const result = await httpJson(args, "/api/workspace/run", {
1378
+ if (command === "draft-create" || command === "draft-update" || command === "workspace-draft") {
1379
+ const draftId = option(args, "draft-id") || "";
1380
+ if (command === "draft-update" && !draftId) throw new Error("draft-update requires --draft-id <id>.");
1381
+ const baseRevision = option(args, "base-revision") || "";
1382
+ if (command === "draft-update" && !baseRevision) {
1383
+ throw new Error("draft-update requires --base-revision <revision>. Pull the Draft first if the revision is unknown.");
1384
+ }
1385
+ const prepared = await prepareDraftGraph(args);
1386
+ const result = await httpJson(args, "/api/workspace/draft", {
1097
1387
  method: "POST",
1098
1388
  body: {
1099
- flowId,
1100
- flowSource,
1101
- runNodeId,
1102
- runAlias: option(args, "run-alias") || "",
1103
- graph: graphPayload.graph,
1104
- inputs: parseInputs(args),
1389
+ graph: prepared.graph,
1390
+ draftId,
1391
+ baseRevision,
1392
+ title: option(args, "title") || "Workspace Draft",
1393
+ ttlSeconds: option(args, "ttl-seconds") ? Number(option(args, "ttl-seconds")) : undefined,
1394
+ resetRuntime: args["keep-runtime"] !== true,
1105
1395
  },
1106
1396
  });
1107
- printJson({
1108
- ...result,
1109
- displayOutputs: extractDisplayOutputs(result?.graph),
1397
+ printJson({ ...result, ...prepared });
1398
+ return;
1399
+ }
1400
+
1401
+ if (command === "draft-run") {
1402
+ const draftId = option(args, "draft-id");
1403
+ if (!draftId) throw new Error("draft-run requires --draft-id <id>.");
1404
+ printJson(await runRemoteWorkspace(args, {
1405
+ flowId: draftId,
1406
+ flowSource: "user",
1407
+ runNodeId: option(args, "run-node-id") || "",
1408
+ }));
1409
+ return;
1410
+ }
1411
+
1412
+ if (command === "draft-publish" || command === "draft-promote") {
1413
+ const draftId = option(args, "draft-id");
1414
+ if (!draftId) throw new Error("draft-publish requires --draft-id <id>.");
1415
+ const flowId = requireFlowId(args);
1416
+ const destination = targetDestinationFromArgs(args);
1417
+ const targetSpace = destination.shareWithTeam ? "team" : destination.flowSource === "user" ? "personal" : "workspace";
1418
+ const scheduleMode = normalizeScheduleMode(args);
1419
+ const team = await resolvePublishTeam(args, destination.shareWithTeam);
1420
+ const result = await httpJson(args, "/api/workspace/draft/publish", {
1421
+ method: "POST",
1422
+ body: { draftId, flowId, targetSpace, scheduleMode },
1423
+ });
1424
+ const sharedTeam = await sharePublishedFlowWithTeam(args, {
1425
+ flowId,
1426
+ flowSource: destination.flowSource,
1427
+ team,
1110
1428
  });
1429
+ printJson({ ...result, team: sharedTeam, targetSpace });
1430
+ return;
1431
+ }
1432
+
1433
+ if (command === "run") {
1434
+ const flowId = requireFlowId(args);
1435
+ const flowSource = option(args, "flow-source") || "user";
1436
+ const runNodeId = option(args, "run-node-id") || "";
1437
+ printJson(await runRemoteWorkspace(args, { flowId, flowSource, runNodeId }));
1111
1438
  return;
1112
1439
  }
1113
1440
 
@@ -1151,6 +1478,44 @@ async function main() {
1151
1478
  return;
1152
1479
  }
1153
1480
 
1481
+ if (command === "schedule-list") {
1482
+ const flowId = option(args, "flow-id") || "";
1483
+ const flowSource = option(args, "flow-source") || "user";
1484
+ if (flowId) {
1485
+ printJson(await httpJson(args, `/api/workspace/schedules${query({ flowId, flowSource })}`));
1486
+ } else {
1487
+ printJson(await httpJson(args, "/api/schedules"));
1488
+ }
1489
+ return;
1490
+ }
1491
+
1492
+ if (["schedule-set", "schedule-enable", "schedule-disable"].includes(command)) {
1493
+ const flowId = requireFlowId(args);
1494
+ const flowSource = option(args, "flow-source") || "user";
1495
+ const scheduleNodeId = option(args, "schedule-node-id");
1496
+ if (!scheduleNodeId) throw new Error(`${command} requires --schedule-node-id <id>.`);
1497
+ const body = { flowId, flowSource, scheduleNodeId };
1498
+ if (command === "schedule-enable") body.enabled = true;
1499
+ if (command === "schedule-disable") body.enabled = false;
1500
+ if (command === "schedule-set" && args.enabled !== undefined) {
1501
+ body.enabled = parseBooleanOption(args.enabled, "--enabled");
1502
+ }
1503
+ if (args.cron !== undefined) body.cron = option(args, "cron");
1504
+ if (args.timezone !== undefined) body.timezone = option(args, "timezone");
1505
+ if (args["overlap-policy"] !== undefined) body.overlapPolicy = option(args, "overlap-policy");
1506
+ printJson(await httpJson(args, "/api/workspace/schedule/config", { method: "POST", body }));
1507
+ return;
1508
+ }
1509
+
1510
+ if (command === "schedule-run-now") {
1511
+ const flowId = requireFlowId(args);
1512
+ const flowSource = option(args, "flow-source") || "user";
1513
+ const scheduleNodeId = option(args, "schedule-node-id");
1514
+ if (!scheduleNodeId) throw new Error("schedule-run-now requires --schedule-node-id <id>.");
1515
+ printJson(await runRemoteWorkspace(args, { flowId, flowSource, runNodeId: scheduleNodeId }));
1516
+ return;
1517
+ }
1518
+
1154
1519
  if (command === "workflow-get") {
1155
1520
  const workflow = workflowReferenceFromArgs(args);
1156
1521
  if (workflow.namespace !== "tapd") {