@fieldwangai/agentflow 0.1.153 → 0.1.156

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 (193) hide show
  1. package/README.md +16 -34
  2. package/README.zh-CN.md +14 -34
  3. package/agents/agentflow-node-executor-code.md +13 -3
  4. package/agents/agentflow-node-executor-planning.md +13 -3
  5. package/agents/agentflow-node-executor-requirement.md +13 -3
  6. package/agents/agentflow-node-executor-test.md +13 -3
  7. package/agents/agentflow-node-executor-ui.md +13 -3
  8. package/agents/agentflow-node-executor.md +13 -3
  9. package/agents/en/agentflow-node-executor.md +13 -3
  10. package/agents/zh/agentflow-node-executor.md +13 -3
  11. package/bin/agentflow.mjs +3 -3
  12. package/bin/lib/admin-builtin-pipelines.mjs +3 -2
  13. package/bin/lib/agent-runners.mjs +0 -929
  14. package/bin/lib/auth.mjs +2 -1
  15. package/bin/lib/catalog-agents.mjs +2 -1
  16. package/bin/lib/catalog-flows.mjs +165 -90
  17. package/bin/lib/composer-agent.mjs +3 -793
  18. package/bin/lib/composer-skill-router.mjs +3 -323
  19. package/bin/lib/exec-buffered.mjs +32 -0
  20. package/bin/lib/flow-dsl/cli.mjs +277 -0
  21. package/bin/lib/flow-dsl/codegen.mjs +358 -0
  22. package/bin/lib/flow-dsl/defs.mjs +103 -0
  23. package/bin/lib/flow-dsl/index.mjs +77 -0
  24. package/bin/lib/flow-dsl/ir.mjs +305 -0
  25. package/bin/lib/flow-dsl/layout.mjs +115 -0
  26. package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
  27. package/bin/lib/flow-dsl/lint.mjs +241 -0
  28. package/bin/lib/flow-dsl/packages.mjs +251 -0
  29. package/bin/lib/flow-dsl/parser.mjs +444 -0
  30. package/bin/lib/flow-import.mjs +96 -26
  31. package/bin/lib/flow-write.mjs +48 -12
  32. package/bin/lib/help.mjs +8 -28
  33. package/bin/lib/html-escape.mjs +19 -0
  34. package/bin/lib/http-util.mjs +49 -0
  35. package/bin/lib/legacy-flow-execution.mjs +37 -0
  36. package/bin/lib/locales/en.json +1 -1
  37. package/bin/lib/locales/zh.json +1 -1
  38. package/bin/lib/main.mjs +152 -286
  39. package/bin/lib/marketplace.mjs +206 -61
  40. package/bin/lib/model-config.mjs +10 -0
  41. package/bin/lib/node-package-archive.mjs +240 -0
  42. package/bin/lib/node-package-bootstrap.mjs +72 -0
  43. package/bin/lib/node-package-manifest.mjs +174 -0
  44. package/bin/lib/paths.mjs +42 -19
  45. package/bin/lib/prd-workflow-routes.mjs +2949 -0
  46. package/bin/lib/prd-workflow-server.mjs +4884 -0
  47. package/bin/lib/run-events.mjs +0 -66
  48. package/bin/lib/schedule-config.mjs +4 -4
  49. package/bin/lib/skill-runtime.mjs +35 -0
  50. package/bin/lib/startup-storage-migrations.mjs +274 -0
  51. package/bin/lib/ui-server.mjs +1092 -18123
  52. package/bin/lib/user-env.mjs +16 -0
  53. package/bin/lib/workspace-auto-layout.mjs +234 -0
  54. package/bin/lib/workspace-flow-store.mjs +478 -0
  55. package/bin/lib/workspace-graph-merge.mjs +41 -34
  56. package/bin/lib/workspace-preview.mjs +12 -2
  57. package/bin/lib/workspace-routes.mjs +3088 -0
  58. package/bin/lib/workspace-server.mjs +6199 -0
  59. package/bin/lib/workspace-state.mjs +331 -0
  60. package/bin/lib/workspace.mjs +2 -1
  61. package/builtin/nodes/agent_subAgent.md +1 -0
  62. package/builtin/nodes/control_agent_toBool.md +2 -0
  63. package/builtin/nodes/control_anyOne.md +2 -0
  64. package/builtin/nodes/control_cancelled.md +2 -0
  65. package/builtin/nodes/control_cd_workspace.md +5 -11
  66. package/builtin/nodes/control_delay.md +2 -0
  67. package/builtin/nodes/control_end.md +2 -0
  68. package/builtin/nodes/control_if.md +1 -0
  69. package/builtin/nodes/control_interval_loop.md +2 -0
  70. package/builtin/nodes/control_load_mcp.md +24 -0
  71. package/builtin/nodes/control_load_skills.md +8 -23
  72. package/builtin/nodes/control_start.md +2 -0
  73. package/builtin/nodes/control_toBool.md +2 -0
  74. package/builtin/nodes/control_user_workspace.md +2 -0
  75. package/builtin/nodes/control_wait_until.md +2 -0
  76. package/builtin/nodes/display_ascii.md +1 -0
  77. package/builtin/nodes/display_chart.md +1 -0
  78. package/builtin/nodes/display_html.md +1 -0
  79. package/builtin/nodes/display_image.md +1 -0
  80. package/builtin/nodes/display_markdown.md +1 -0
  81. package/builtin/nodes/display_mermaid.md +1 -0
  82. package/builtin/nodes/display_react_app.md +1 -0
  83. package/builtin/nodes/display_table.md +1 -0
  84. package/builtin/nodes/provide_bool.md +1 -0
  85. package/builtin/nodes/provide_file.md +1 -0
  86. package/builtin/nodes/provide_password.md +1 -0
  87. package/builtin/nodes/provide_str.md +1 -0
  88. package/builtin/nodes/tool_display_share_link.md +1 -0
  89. package/builtin/nodes/tool_get_env.md +2 -0
  90. package/builtin/nodes/tool_git_checkout.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  92. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  93. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  94. package/builtin/nodes/tool_jenkins_build.md +2 -0
  95. package/builtin/nodes/tool_load_key.md +2 -0
  96. package/builtin/nodes/tool_nodejs.md +17 -19
  97. package/builtin/nodes/tool_print.md +2 -0
  98. package/builtin/nodes/tool_save_key.md +2 -0
  99. package/builtin/nodes/tool_set_run_env.md +1 -0
  100. package/builtin/nodes/tool_user_ask.md +2 -0
  101. package/builtin/nodes/tool_user_check.md +2 -0
  102. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  103. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  104. package/builtin/nodes/workspace_one_click_task.md +44 -0
  105. package/builtin/nodes/workspace_run.md +16 -0
  106. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  107. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  108. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  109. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  110. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  111. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  112. package/builtin/pipelines/new/workspace.flow.js +91 -0
  113. package/builtin/pipelines/new/workspace.layout.json +70 -0
  114. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
  115. package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
  116. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  117. package/builtin/web-ui/dist/index.html +2 -2
  118. package/package.json +3 -1
  119. package/reference/flow-control-capabilities.md +77 -158
  120. package/reference/flow-layout.md +1 -1
  121. package/reference/flow-prompt-handler-check.md +2 -2
  122. package/skills/agentflow-author-flow/SKILL.md +1 -1
  123. package/skills/agentflow-cli/SKILL.md +139 -24
  124. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  125. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  126. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  127. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  128. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  129. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  130. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  131. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  132. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  133. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  134. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  135. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  136. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  137. package/skills/agentflow-node-reference/SKILL.md +2 -2
  138. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  139. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  140. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  141. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  142. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  143. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  144. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  145. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  146. package/bin/lib/api-runner.mjs +0 -387
  147. package/bin/lib/apply.mjs +0 -903
  148. package/bin/lib/composer-flow-instances.mjs +0 -68
  149. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  150. package/bin/lib/composer-flow-validate.mjs +0 -47
  151. package/bin/lib/composer-model-router.mjs +0 -185
  152. package/bin/lib/composer-node-schema.mjs +0 -303
  153. package/bin/lib/composer-planner.mjs +0 -751
  154. package/bin/lib/composer-script-ops.mjs +0 -233
  155. package/bin/lib/flow-static-preview.mjs +0 -104
  156. package/bin/lib/hub-login.mjs +0 -54
  157. package/bin/lib/hub-publish.mjs +0 -159
  158. package/bin/lib/hub-remote.mjs +0 -189
  159. package/bin/lib/hub.mjs +0 -299
  160. package/bin/lib/jenkins.mjs +0 -380
  161. package/bin/lib/node-execute.mjs +0 -539
  162. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  163. package/bin/lib/runtime-context.mjs +0 -243
  164. package/bin/lib/scheduler.mjs +0 -601
  165. package/bin/lib/ui-print.mjs +0 -94
  166. package/bin/pipeline/build-node-prompt.mjs +0 -271
  167. package/bin/pipeline/check-cache.mjs +0 -191
  168. package/bin/pipeline/check-flow.mjs +0 -543
  169. package/bin/pipeline/collect-nodes.mjs +0 -212
  170. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  171. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  172. package/bin/pipeline/gc.mjs +0 -129
  173. package/bin/pipeline/get-env.mjs +0 -59
  174. package/bin/pipeline/get-resolved-values.mjs +0 -344
  175. package/bin/pipeline/load-key.mjs +0 -62
  176. package/bin/pipeline/parse-flow.mjs +0 -708
  177. package/bin/pipeline/post-process-control-if.mjs +0 -23
  178. package/bin/pipeline/post-process-node.mjs +0 -490
  179. package/bin/pipeline/pre-process-node.mjs +0 -1430
  180. package/bin/pipeline/resolve-inputs.mjs +0 -201
  181. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  182. package/bin/pipeline/save-key.mjs +0 -93
  183. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  184. package/bin/pipeline/validate-for-ui.mjs +0 -234
  185. package/bin/pipeline/validate-script-output.mjs +0 -130
  186. package/bin/pipeline/write-result.mjs +0 -182
  187. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  188. package/builtin/pipelines/new/flow.yaml +0 -545
  189. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  190. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  191. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  192. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  193. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -1,601 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { spawn } from "child_process";
4
- import yaml from "js-yaml";
5
- import { listFlowsJson } from "./catalog-flows.mjs";
6
- import {
7
- computeNextRunAtFromSchedule,
8
- readFlowSchedule,
9
- readScheduleState,
10
- writeScheduleState,
11
- } from "./schedule-config.mjs";
12
- import { getAgentflowUserContexts, getFlowRuntimeRoot, getRunDir, PACKAGE_ROOT } from "./paths.mjs";
13
- import { isApplyProcessAlive } from "./run-apply-active-lock.mjs";
14
- import { log } from "./log.mjs";
15
- import { readMergedEnvObject } from "./user-env.mjs";
16
- import { writeResult } from "../pipeline/write-result.mjs";
17
-
18
- const DEFAULT_POLL_MS = 30_000;
19
- const RUN_CONFIG_FILENAME = "run-config.json";
20
- const WAIT_STATE_FILENAME = "wait-state.json";
21
- const WAIT_STATES_FILENAME = "wait-states.json";
22
-
23
- function sleep(ms) {
24
- return new Promise((resolve) => setTimeout(resolve, ms));
25
- }
26
-
27
- function scheduleIdentity(schedule) {
28
- return [
29
- schedule.enabled ? "1" : "0",
30
- schedule.cron || "",
31
- schedule.timezone || "",
32
- schedule.preset || "",
33
- schedule.overlapPolicy || "skip",
34
- schedule.misfirePolicy || "skip",
35
- ].join("\t");
36
- }
37
-
38
- function readRunConfig(flowDir) {
39
- const configPath = path.join(flowDir, RUN_CONFIG_FILENAME);
40
- if (!fs.existsSync(configPath)) return { presets: {}, activePreset: null };
41
- try {
42
- const data = JSON.parse(fs.readFileSync(configPath, "utf-8"));
43
- return {
44
- presets: data.presets && typeof data.presets === "object" ? data.presets : {},
45
- activePreset: typeof data.activePreset === "string" ? data.activePreset : null,
46
- };
47
- } catch {
48
- return { presets: {}, activePreset: null };
49
- }
50
- }
51
-
52
- function buildCliInputArgs(flowDir, presetName) {
53
- const cfg = readRunConfig(flowDir);
54
- const name = presetName || cfg.activePreset || "";
55
- const preset = name && cfg.presets && typeof cfg.presets[name] === "object" ? cfg.presets[name] : null;
56
- if (!preset) return [];
57
- let flow;
58
- try {
59
- flow = yaml.load(fs.readFileSync(path.join(flowDir, "flow.yaml"), "utf-8"));
60
- } catch {
61
- flow = null;
62
- }
63
- const instances = flow && typeof flow === "object" && flow.instances && typeof flow.instances === "object" ? flow.instances : {};
64
- const edges = flow && typeof flow === "object" && Array.isArray(flow.edges) ? flow.edges : [];
65
- const provideToInputName = {};
66
- for (const e of edges) {
67
- if (!e || !e.source || !e.target) continue;
68
- const source = String(e.source);
69
- const target = String(e.target);
70
- if (!preset.hasOwnProperty(source)) continue;
71
- const sourceInst = instances[source] || {};
72
- const sourceDef = String(sourceInst.definitionId || "");
73
- if (!sourceDef.startsWith("provide_")) continue;
74
- const m = /^input-(\d+)$/.exec(String(e.targetHandle || ""));
75
- if (!m) continue;
76
- const targetInst = instances[target] || {};
77
- const inputs = Array.isArray(targetInst.input) ? targetInst.input : [];
78
- const slot = inputs[parseInt(m[1], 10)];
79
- if (slot && typeof slot.name === "string" && slot.name.trim()) {
80
- provideToInputName[source] = {
81
- name: slot.name.trim(),
82
- isFile: sourceDef.startsWith("provide_file"),
83
- };
84
- }
85
- }
86
- const args = [];
87
- for (const [inputName, value] of Object.entries(preset)) {
88
- if (!inputName || typeof inputName !== "string") continue;
89
- const mapped = provideToInputName[inputName];
90
- if (!mapped) continue;
91
- args.push("--input", `${mapped.name}=${mapped.isFile ? "file:" : ""}${String(value ?? "")}`);
92
- }
93
- return args;
94
- }
95
-
96
- function hasHigherPriorityDuplicate(workspaceRoot, flow, opts = {}) {
97
- if ((flow.source || "user") !== "workspace") return false;
98
- return listFlowsJson(workspaceRoot, opts).some((f) => f.id === flow.id && !f.archived && (f.source || "user") === "user");
99
- }
100
-
101
- function getLatestRunUuidForFlow(workspaceRoot, flowId, opts = {}) {
102
- const runRoot = path.dirname(getRunDir(workspaceRoot, flowId, "00000000000000", opts));
103
- if (!fs.existsSync(runRoot)) return null;
104
- try {
105
- const dirs = fs.readdirSync(runRoot, { withFileTypes: true })
106
- .filter((e) => e.isDirectory() && /^\d{14}$/.test(e.name))
107
- .map((e) => e.name)
108
- .sort();
109
- return dirs[dirs.length - 1] || null;
110
- } catch {
111
- return null;
112
- }
113
- }
114
-
115
- function listRunDirsForFlow(workspaceRoot, flow, opts = {}) {
116
- const roots = [
117
- flow.path ? path.join(flow.path, "runBuild") : "",
118
- path.join(getFlowRuntimeRoot(workspaceRoot, flow.id, opts), "runBuild"),
119
- ].filter(Boolean);
120
- const seen = new Set();
121
- const runs = [];
122
- for (const runRoot of roots) {
123
- const resolved = path.resolve(runRoot);
124
- if (seen.has(resolved) || !fs.existsSync(resolved)) continue;
125
- seen.add(resolved);
126
- try {
127
- for (const entry of fs.readdirSync(resolved, { withFileTypes: true })) {
128
- if (entry.isDirectory() && /^\d{14}$/.test(entry.name)) {
129
- runs.push({ uuid: entry.name, runDir: path.join(resolved, entry.name) });
130
- }
131
- }
132
- } catch {
133
- /* ignore unreadable run roots */
134
- }
135
- }
136
- return runs.sort((a, b) => b.uuid.localeCompare(a.uuid));
137
- }
138
-
139
- function readJsonObject(filePath) {
140
- if (!fs.existsSync(filePath)) return null;
141
- try {
142
- const state = JSON.parse(fs.readFileSync(filePath, "utf-8"));
143
- return state && typeof state === "object" ? state : null;
144
- } catch {
145
- return null;
146
- }
147
- }
148
-
149
- function waitStateKey(state) {
150
- return String((state && (state.waitId || state.id || state.instanceId)) || "");
151
- }
152
-
153
- function persistedWaitState(state) {
154
- const {
155
- waitPath: _waitPath,
156
- legacyPath: _legacyPath,
157
- registryPath: _registryPath,
158
- runDir: _runDir,
159
- ...persisted
160
- } = state && typeof state === "object" ? state : {};
161
- return persisted;
162
- }
163
-
164
- function readWaitStates(runDir) {
165
- const legacyPath = path.join(runDir, WAIT_STATE_FILENAME);
166
- const registryPath = path.join(runDir, WAIT_STATES_FILENAME);
167
- const states = [];
168
- const seen = new Set();
169
- const registry = readJsonObject(registryPath);
170
- if (registry && Array.isArray(registry.waits)) {
171
- for (const raw of registry.waits) {
172
- if (!raw || typeof raw !== "object") continue;
173
- const state = { ...raw, runDir, legacyPath, registryPath };
174
- const key = waitStateKey(state);
175
- if (!key || seen.has(key)) continue;
176
- seen.add(key);
177
- states.push(state);
178
- }
179
- }
180
- const legacy = readJsonObject(legacyPath);
181
- if (legacy) {
182
- const state = { ...legacy, runDir, waitPath: legacyPath, legacyPath, registryPath: fs.existsSync(registryPath) ? registryPath : null };
183
- const key = waitStateKey(state);
184
- if (key && !seen.has(key)) states.push(state);
185
- }
186
- return states;
187
- }
188
-
189
- function writeWaitState(waitState, patch = {}) {
190
- const next = {
191
- ...(waitState && typeof waitState === "object" ? waitState : {}),
192
- ...(patch && typeof patch === "object" ? patch : {}),
193
- updatedAt: new Date().toISOString(),
194
- };
195
- const key = waitStateKey(next);
196
- if (key) {
197
- next.waitId = String(next.waitId || key);
198
- next.id = String(next.id || next.waitId);
199
- }
200
- const runDir = next.runDir || (next.waitPath ? path.dirname(next.waitPath) : "");
201
- if (!runDir) return;
202
-
203
- const legacyPath = next.legacyPath || next.waitPath || path.join(runDir, WAIT_STATE_FILENAME);
204
- const registryPath = next.registryPath || path.join(runDir, WAIT_STATES_FILENAME);
205
- const persisted = persistedWaitState(next);
206
- const persistedKey = waitStateKey(persisted);
207
-
208
- const registry = readJsonObject(registryPath);
209
- if (registry && Array.isArray(registry.waits)) {
210
- const waits = registry.waits.filter((w) => waitStateKey(w) !== persistedKey);
211
- waits.push(persisted);
212
- fs.writeFileSync(registryPath, JSON.stringify({ ...registry, updatedAt: new Date().toISOString(), waits }, null, 2) + "\n", "utf-8");
213
- }
214
- fs.writeFileSync(legacyPath, JSON.stringify(persisted, null, 2) + "\n", "utf-8");
215
- }
216
-
217
- function readNodeResultStatus(runDir, instanceId) {
218
- const resultPath = path.join(runDir, "intermediate", instanceId, `${instanceId}.result.md`);
219
- if (!fs.existsSync(resultPath)) return null;
220
- try {
221
- const raw = fs.readFileSync(resultPath, "utf-8");
222
- const m = raw.match(/^\s*status:\s*["']?([^"'\s]+)["']?/m);
223
- return m ? m[1] : null;
224
- } catch {
225
- return null;
226
- }
227
- }
228
-
229
- function isFlowCurrentlyRunning(workspaceRoot, flowId, state, opts = {}) {
230
- const candidates = [];
231
- if (state && typeof state.lastRunUuid === "string") candidates.push(state.lastRunUuid);
232
- const latest = getLatestRunUuidForFlow(workspaceRoot, flowId, opts);
233
- if (latest) candidates.push(latest);
234
- for (const uuid of candidates) {
235
- const runDir = getRunDir(workspaceRoot, flowId, uuid, opts);
236
- if (isApplyProcessAlive(runDir)) return true;
237
- }
238
- return false;
239
- }
240
-
241
- function baseState(flow, schedule, previousState) {
242
- return {
243
- ...(previousState && typeof previousState === "object" ? previousState : {}),
244
- flowId: flow.id,
245
- flowSource: flow.source || "user",
246
- scheduleIdentity: scheduleIdentity(schedule),
247
- updatedAt: new Date().toISOString(),
248
- };
249
- }
250
-
251
- function ensureNextRunAt(workspaceRoot, flow, schedule, state, opts = {}) {
252
- const identity = scheduleIdentity(schedule);
253
- if (state.scheduleIdentity === identity && state.nextRunAt) return state;
254
- const nextRunAt = schedule.enabled && schedule.cron ? computeNextRunAtFromSchedule(schedule) : null;
255
- const next = {
256
- ...baseState(flow, schedule, state),
257
- nextRunAt,
258
- lastError: "",
259
- };
260
- writeScheduleState(workspaceRoot, flow.id, flow.source || "user", next, opts);
261
- return next;
262
- }
263
-
264
- function startScheduledRun(workspaceRoot, flow, schedule, state, opts = {}) {
265
- const flowDir = flow.path || "";
266
- const agentflowBin = path.join(PACKAGE_ROOT, "bin", "agentflow.mjs");
267
- const args = [agentflowBin, "apply", flow.id, "--machine-readable", "--workspace-root", path.resolve(workspaceRoot), "--force"];
268
- args.push(...buildCliInputArgs(flowDir, schedule.preset));
269
- const child = spawn(process.execPath, args, {
270
- cwd: path.resolve(workspaceRoot),
271
- stdio: ["ignore", "pipe", "pipe"],
272
- env: { ...process.env, ...readMergedEnvObject(opts.userId), FORCE_COLOR: "0", AGENTFLOW_USER_ID: opts.userId || "" },
273
- detached: true,
274
- });
275
-
276
- const startedAt = new Date().toISOString();
277
- let lastRunUuid = null;
278
- let stdoutBuf = "";
279
- child.stdout.on("data", (chunk) => {
280
- stdoutBuf += chunk.toString("utf8");
281
- const lines = stdoutBuf.split("\n");
282
- stdoutBuf = lines.pop() || "";
283
- for (const line of lines) {
284
- if (!line.trim()) continue;
285
- try {
286
- const evt = JSON.parse(line);
287
- if (evt && evt.event === "apply-start" && typeof evt.uuid === "string") {
288
- lastRunUuid = evt.uuid;
289
- writeScheduleState(workspaceRoot, flow.id, flow.source || "user", {
290
- ...baseState(flow, schedule, state),
291
- nextRunAt: computeNextRunAtFromSchedule(schedule),
292
- lastTriggeredAt: startedAt,
293
- lastRunUuid,
294
- lastPid: child.pid || null,
295
- lastError: "",
296
- }, opts);
297
- }
298
- } catch {
299
- /* ignore non-json lines */
300
- }
301
- }
302
- });
303
-
304
- child.stderr.on("data", (chunk) => {
305
- const text = chunk.toString("utf8").trim();
306
- if (text) log.debug(`[scheduler] ${flow.id}: ${text.slice(0, 1000)}`);
307
- });
308
-
309
- child.on("exit", (code, signal) => {
310
- const prev = readScheduleState(workspaceRoot, flow.id, flow.source || "user", opts).state || state;
311
- writeScheduleState(workspaceRoot, flow.id, flow.source || "user", {
312
- ...baseState(flow, schedule, prev),
313
- nextRunAt: prev.nextRunAt || computeNextRunAtFromSchedule(schedule),
314
- lastTriggeredAt: prev.lastTriggeredAt || startedAt,
315
- lastRunUuid: lastRunUuid || prev.lastRunUuid || null,
316
- lastExitCode: code,
317
- lastExitSignal: signal || "",
318
- lastFinishedAt: new Date().toISOString(),
319
- lastError: code === 0 ? "" : `scheduled run exited with code ${code}${signal ? ` signal ${signal}` : ""}`,
320
- }, opts);
321
- });
322
-
323
- child.unref();
324
- return child;
325
- }
326
-
327
- function startWaitingRunResume(workspaceRoot, flow, waitState, opts = {}) {
328
- const agentflowBin = path.join(PACKAGE_ROOT, "bin", "agentflow.mjs");
329
- const uuid = String(waitState.uuid || "");
330
- const instanceId = String(waitState.instanceId || "");
331
- const rerunCurrentNode = waitState.resumeMode === "rerun";
332
- if (rerunCurrentNode) {
333
- writeResult(
334
- workspaceRoot,
335
- flow.id,
336
- uuid,
337
- instanceId,
338
- { status: "cache_not_met", message: "后台任务到期,重新检查外部状态" },
339
- { execId: Number(waitState.execId) || undefined, preserveBody: true, runDir: waitState.runDir },
340
- );
341
- }
342
- const args = [
343
- agentflowBin,
344
- rerunCurrentNode ? "apply" : "resume",
345
- flow.id,
346
- uuid,
347
- ...(rerunCurrentNode ? [] : [instanceId]),
348
- "--machine-readable",
349
- "--workspace-root",
350
- path.resolve(workspaceRoot),
351
- "--force",
352
- ];
353
- const child = spawn(process.execPath, args, {
354
- cwd: path.resolve(workspaceRoot),
355
- stdio: ["ignore", "pipe", "pipe"],
356
- env: { ...process.env, ...readMergedEnvObject(opts.userId), FORCE_COLOR: "0", AGENTFLOW_USER_ID: opts.userId || "" },
357
- detached: true,
358
- });
359
- child.stdout.on("data", () => {});
360
- child.stderr.on("data", (chunk) => {
361
- const text = chunk.toString("utf8").trim();
362
- if (text) log.debug(`[scheduler] resume ${flow.id}/${uuid}: ${text.slice(0, 1000)}`);
363
- });
364
- child.on("exit", (code, signal) => {
365
- const runDir = waitState.runDir || (waitState.waitPath ? path.dirname(waitState.waitPath) : "");
366
- if (!runDir) return;
367
- const latest = readWaitStates(runDir).find((s) => waitStateKey(s) === waitStateKey(waitState));
368
- if (!latest || latest.wakeAt !== waitState.wakeAt || latest.instanceId !== waitState.instanceId) return;
369
- writeWaitState(latest, {
370
- status: code === 0 ? "resumed" : "waiting",
371
- lastResumeExitCode: code,
372
- lastResumeExitSignal: signal || "",
373
- lastResumeFinishedAt: new Date().toISOString(),
374
- ...(code === 0 ? {} : { lastError: `resume exited with code ${code}${signal ? ` signal ${signal}` : ""}` }),
375
- });
376
- });
377
- child.unref();
378
- return child;
379
- }
380
-
381
- function countActiveWaitsForFlow(workspaceRoot, flow, opts = {}) {
382
- let count = 0;
383
- for (const run of listRunDirsForFlow(workspaceRoot, flow, opts)) {
384
- for (const waitState of readWaitStates(run.runDir)) {
385
- if (waitState && (waitState.status === "waiting" || waitState.status === "resuming")) count += 1;
386
- }
387
- }
388
- return count;
389
- }
390
-
391
- function hasNodeBranchEdge(runDir, instanceId, branchName) {
392
- const flowJsonPath = path.join(runDir, "intermediate", "flow.json");
393
- const flow = readJsonObject(flowJsonPath);
394
- if (!flow || !Array.isArray(flow.edges)) return false;
395
- const outputSlotTypes = flow.outputSlotTypes && flow.outputSlotTypes[instanceId];
396
- if (!outputSlotTypes || typeof outputSlotTypes !== "object") return false;
397
- const idx = Object.keys(outputSlotTypes).indexOf(branchName);
398
- if (idx < 0) return false;
399
- const sourceHandle = `output-${idx}`;
400
- return flow.edges.some((e) => e && e.source === instanceId && (e.sourceHandle || "output-0") === sourceHandle);
401
- }
402
-
403
- export function cancelScheduledRun(workspaceRoot, flowId, uuid, opts = {}) {
404
- const flow = listFlowsJson(workspaceRoot, opts).find((f) => f.id === flowId && !f.archived);
405
- if (!flow) return { ok: false, error: `flow not found: ${flowId}` };
406
- const runDir = getRunDir(workspaceRoot, flow.id, uuid, opts);
407
- if (!fs.existsSync(runDir)) return { ok: false, error: `run not found: ${flowId}/${uuid}` };
408
- const cancelledAt = new Date().toISOString();
409
- fs.writeFileSync(path.join(runDir, "cancelled.json"), JSON.stringify({ cancelled: true, cancelledAt }, null, 2) + "\n", "utf-8");
410
- let updated = 0;
411
- let propagated = 0;
412
- let resumePid = null;
413
- for (const waitState of readWaitStates(runDir)) {
414
- if (waitState.status !== "waiting" && waitState.status !== "resuming") continue;
415
- const instanceId = String(waitState.instanceId || "");
416
- const canPropagate =
417
- waitState.reason === "control_interval_loop" &&
418
- instanceId &&
419
- hasNodeBranchEdge(runDir, instanceId, "cancelled") &&
420
- !resumePid;
421
- const canRerunForCancellation = waitState.resumeMode === "rerun" && instanceId && !resumePid;
422
- if (canRerunForCancellation) {
423
- const child = startWaitingRunResume(workspaceRoot, flow, { ...waitState, uuid, runDir, branch: "cancelled" }, opts);
424
- resumePid = child.pid || null;
425
- writeWaitState(waitState, { status: "resuming", branch: "cancelled", cancelledAt, resumePid, resumeStartedAt: cancelledAt });
426
- propagated += 1;
427
- } else if (canPropagate) {
428
- writeResult(
429
- workspaceRoot,
430
- flow.id,
431
- uuid,
432
- instanceId,
433
- { status: "success", message: "已取消", branch: "cancelled" },
434
- { execId: Number(waitState.execId) || undefined, preserveBody: false, runDir },
435
- );
436
- const child = startWaitingRunResume(workspaceRoot, flow, { ...waitState, uuid, runDir, branch: "cancelled" }, opts);
437
- resumePid = child.pid || null;
438
- writeWaitState(waitState, { status: "resuming", branch: "cancelled", cancelledAt, resumePid, resumeStartedAt: cancelledAt });
439
- propagated += 1;
440
- } else {
441
- writeWaitState(waitState, { status: "cancelled", cancelledAt });
442
- }
443
- updated += 1;
444
- }
445
- return { ok: true, flowId, uuid, cancelledAt, updatedWaits: updated, propagatedWaits: propagated, resumePid };
446
- }
447
-
448
- export function listScheduleStatuses(workspaceRoot, opts = {}) {
449
- const rows = [];
450
- for (const flow of listFlowsJson(workspaceRoot, opts)) {
451
- if (flow.archived || flow.source === "builtin") continue;
452
- const scheduleRes = readFlowSchedule(workspaceRoot, flow.id, flow.source || "user", opts);
453
- if (!scheduleRes.success) {
454
- rows.push({ flowId: flow.id, flowSource: flow.source || "user", enabled: false, error: scheduleRes.error });
455
- continue;
456
- }
457
- const schedule = scheduleRes.schedule;
458
- const stateRes = readScheduleState(workspaceRoot, flow.id, flow.source || "user", opts);
459
- const state = stateRes.success ? stateRes.state : {};
460
- rows.push({
461
- flowId: flow.id,
462
- flowSource: flow.source || "user",
463
- enabled: Boolean(schedule.enabled),
464
- cron: schedule.cron || "",
465
- timezone: schedule.timezone || "",
466
- preset: schedule.preset || "",
467
- nextRunAt: state.nextRunAt || schedule.nextRunAt || null,
468
- lastTriggeredAt: state.lastTriggeredAt || null,
469
- lastRunUuid: state.lastRunUuid || null,
470
- lastError: hasHigherPriorityDuplicate(workspaceRoot, flow, opts)
471
- ? "workspace flow is shadowed by a user flow with the same id"
472
- : state.lastError || "",
473
- running: isFlowCurrentlyRunning(workspaceRoot, flow.id, state, opts),
474
- waiting: countActiveWaitsForFlow(workspaceRoot, flow, opts),
475
- });
476
- }
477
- rows.sort((a, b) => {
478
- const ea = a.enabled ? 0 : 1;
479
- const eb = b.enabled ? 0 : 1;
480
- return ea - eb || String(a.nextRunAt || "").localeCompare(String(b.nextRunAt || "")) || a.flowId.localeCompare(b.flowId);
481
- });
482
- return rows;
483
- }
484
-
485
- export async function startScheduler(workspaceRoot, opts = {}) {
486
- const pollMs = Math.max(1000, Number(opts.pollMs) || DEFAULT_POLL_MS);
487
- const once = Boolean(opts.once);
488
- log.info(`AgentFlow scheduler started. workspace=${path.resolve(workspaceRoot)} poll=${pollMs}ms`);
489
- while (true) {
490
- const now = Date.now();
491
- const contexts = opts.userId ? [{ userId: opts.userId }] : getAgentflowUserContexts();
492
- for (const scheduleCtx of contexts) {
493
- for (const flow of listFlowsJson(workspaceRoot, scheduleCtx)) {
494
- if (flow.archived) continue;
495
- const flowSource = flow.source || "user";
496
- let resumedWaitingRun = false;
497
- for (const run of listRunDirsForFlow(workspaceRoot, flow, scheduleCtx)) {
498
- if (resumedWaitingRun) break;
499
- for (const waitState of readWaitStates(run.runDir)) {
500
- if (!waitState || !waitState.wakeAt || !waitState.instanceId) continue;
501
- if (waitState.status === "resuming" && !isFlowCurrentlyRunning(workspaceRoot, flow.id, { lastRunUuid: run.uuid }, scheduleCtx)) {
502
- const nodeStatus = readNodeResultStatus(run.runDir, String(waitState.instanceId));
503
- writeWaitState(waitState, {
504
- status:
505
- nodeStatus === "pending" ||
506
- (waitState.resumeMode === "rerun" && nodeStatus !== "success" && nodeStatus !== "failed")
507
- ? "waiting"
508
- : "resumed",
509
- reconciledAt: new Date().toISOString(),
510
- });
511
- continue;
512
- }
513
- if (waitState.status !== "waiting") continue;
514
- if (Date.parse(waitState.wakeAt) > now) continue;
515
- if (isFlowCurrentlyRunning(workspaceRoot, flow.id, { lastRunUuid: run.uuid }, scheduleCtx)) continue;
516
- const nextState = {
517
- ...waitState,
518
- status: "resuming",
519
- resumePid: null,
520
- resumeStartedAt: new Date().toISOString(),
521
- };
522
- try {
523
- const child = startWaitingRunResume(workspaceRoot, flow, { ...waitState, uuid: run.uuid, runDir: run.runDir }, scheduleCtx);
524
- nextState.resumePid = child.pid || null;
525
- writeWaitState(waitState, nextState);
526
- resumedWaitingRun = true;
527
- log.info(`[scheduler] resume ${flow.id}/${run.uuid} at ${waitState.instanceId}; pid=${child.pid || "?"}`);
528
- break;
529
- } catch (e) {
530
- writeWaitState(waitState, {
531
- status: "waiting",
532
- lastError: e && e.message ? e.message : String(e),
533
- lastErrorAt: new Date().toISOString(),
534
- });
535
- log.info(`[scheduler] resume failed ${flow.id}/${run.uuid}: ${e && e.message ? e.message : String(e)}`);
536
- }
537
- }
538
- }
539
-
540
- if (flow.source === "builtin") continue;
541
-
542
- const scheduleRes = readFlowSchedule(workspaceRoot, flow.id, flowSource, scheduleCtx);
543
- if (!scheduleRes.success) {
544
- log.debug(`[scheduler] ${flow.id}: ${scheduleRes.error}`);
545
- continue;
546
- }
547
- const schedule = scheduleRes.schedule;
548
- if (!schedule.enabled || !schedule.cron) continue;
549
- if (hasHigherPriorityDuplicate(workspaceRoot, flow, scheduleCtx)) {
550
- const stateRes = readScheduleState(workspaceRoot, flow.id, flowSource, scheduleCtx);
551
- writeScheduleState(workspaceRoot, flow.id, flowSource, {
552
- ...baseState(flow, schedule, stateRes.success ? stateRes.state : {}),
553
- nextRunAt: null,
554
- lastError: "workspace flow is shadowed by a user flow with the same id; scheduled run skipped",
555
- lastErrorAt: new Date().toISOString(),
556
- }, scheduleCtx);
557
- continue;
558
- }
559
- const stateRes = readScheduleState(workspaceRoot, flow.id, flowSource, scheduleCtx);
560
- let state = ensureNextRunAt(workspaceRoot, flow, schedule, stateRes.success ? stateRes.state : {}, scheduleCtx);
561
- if (!state.nextRunAt || Date.parse(state.nextRunAt) > now) continue;
562
-
563
- if (isFlowCurrentlyRunning(workspaceRoot, flow.id, state, scheduleCtx)) {
564
- const nextRunAt = computeNextRunAtFromSchedule(schedule);
565
- writeScheduleState(workspaceRoot, flow.id, flowSource, {
566
- ...baseState(flow, schedule, state),
567
- nextRunAt,
568
- lastSkippedAt: new Date().toISOString(),
569
- lastSkipReason: "running",
570
- }, scheduleCtx);
571
- log.info(`[scheduler] skip ${flow.id}: already running; next=${nextRunAt}`);
572
- continue;
573
- }
574
-
575
- try {
576
- const child = startScheduledRun(workspaceRoot, flow, schedule, state, scheduleCtx);
577
- const nextRunAt = computeNextRunAtFromSchedule(schedule);
578
- writeScheduleState(workspaceRoot, flow.id, flowSource, {
579
- ...baseState(flow, schedule, state),
580
- nextRunAt,
581
- lastTriggeredAt: new Date().toISOString(),
582
- lastPid: child.pid || null,
583
- lastError: "",
584
- }, scheduleCtx);
585
- log.info(`[scheduler] triggered ${flow.id}; pid=${child.pid || "?"}; next=${nextRunAt}`);
586
- } catch (e) {
587
- const nextRunAt = computeNextRunAtFromSchedule(schedule);
588
- writeScheduleState(workspaceRoot, flow.id, flowSource, {
589
- ...baseState(flow, schedule, state),
590
- nextRunAt,
591
- lastError: e && e.message ? e.message : String(e),
592
- lastErrorAt: new Date().toISOString(),
593
- }, scheduleCtx);
594
- log.info(`[scheduler] failed ${flow.id}: ${e && e.message ? e.message : String(e)}`);
595
- }
596
- }
597
- }
598
- if (once) return;
599
- await sleep(pollMs);
600
- }
601
- }
@@ -1,94 +0,0 @@
1
- import chalk from "chalk";
2
- import { log } from "./log.mjs";
3
- import { runNodeScript } from "./pipeline-scripts.mjs";
4
- import { Table } from "./table.mjs";
5
-
6
- /** 开始时:入口信息(仅流程名称与 uuid) */
7
- export function printEntryAndFlowFiles(workspaceRoot, flowName, uuid) {
8
- const entryTable = new Table({
9
- head: [chalk.cyan("项目"), chalk.cyan("值")],
10
- colWidths: [18, 24],
11
- style: { head: [], border: ["grey"] },
12
- });
13
- entryTable.push(["流程名称", flowName], ["本次运行 uuid", uuid]);
14
- log.info("\n" + chalk.bold("入口信息"));
15
- log.info(entryTable.toString());
16
- }
17
-
18
- export function styleStatus(s) {
19
- if (s === "success") return chalk.green("success");
20
- if (s === "pending") return chalk.yellow("pending");
21
- if (s === "running") return chalk.cyan("running");
22
- if (s === "condition_not_met") return chalk.dim("condition_not_met");
23
- return chalk.dim(s || "-");
24
- }
25
-
26
- /** 仅打印全量节点状态表(应用进入时展示一次用)。输出到 stderr。 */
27
- export function printNodeStatusTable(instanceStatus, nodes, execIdMap = {}) {
28
- const idToLabel = new Map();
29
- const idToType = new Map();
30
- if (Array.isArray(nodes)) {
31
- for (const n of nodes) {
32
- idToLabel.set(n.id, n.label || n.id);
33
- idToType.set(n.id, n.type || "-");
34
- }
35
- }
36
- const order = Array.isArray(nodes) ? nodes.map((n) => n.id) : Object.keys(instanceStatus || {});
37
- if (order.length === 0) return;
38
- const statusTable = new Table({
39
- head: [chalk.cyan("标签"), chalk.cyan("类型"), chalk.cyan("状态"), chalk.cyan("execId")],
40
- colWidths: [20, 10, 16, 8],
41
- style: { head: [], border: ["grey"] },
42
- });
43
- for (const id of order) {
44
- const label = idToLabel.get(id) || id;
45
- const type = idToType.get(id) || "-";
46
- const status = (instanceStatus && instanceStatus[id]) || "-";
47
- const execId = execIdMap[id] != null ? String(execIdMap[id]) : "-";
48
- statusTable.push([label, type, styleStatus(status), execId]);
49
- }
50
- process.stderr.write("\n" + chalk.bold("节点状态") + "\n");
51
- process.stderr.write(statusTable.toString() + "\n");
52
- }
53
-
54
- /**
55
- * apply 启动时执行 validate-flow(统一校验);errors 则退出,仅 warnings 则提示后继续。
56
- */
57
- export function runValidateFlowAndExitIfInvalid(workspaceRoot, flowName, flowDir) {
58
- const result = runNodeScript(workspaceRoot, "validate-flow.mjs", [workspaceRoot, flowName, flowDir], {
59
- captureStdout: true,
60
- });
61
- const stdout = (result.stdout || "").trim();
62
- if (!stdout) return;
63
- let data;
64
- try {
65
- data = JSON.parse(stdout);
66
- } catch {
67
- return;
68
- }
69
- const errors = Array.isArray(data.errors) ? data.errors : [];
70
- const warnings = Array.isArray(data.warnings) ? data.warnings : [];
71
-
72
- if (errors.length > 0) {
73
- process.stderr.write("\n" + chalk.bold.red("流程校验未通过,请修正后再执行 apply:") + "\n\n");
74
- for (const err of errors) {
75
- process.stderr.write(" " + chalk.red("• ") + err + "\n");
76
- }
77
- if (warnings.length > 0) {
78
- process.stderr.write("\n" + chalk.yellow("警告:") + "\n");
79
- for (const w of warnings) {
80
- process.stderr.write(" " + chalk.yellow("• ") + w + "\n");
81
- }
82
- }
83
- process.stderr.write("\n" + chalk.dim("校验命令: agentflow validate " + flowName) + "\n");
84
- process.exit(1);
85
- }
86
-
87
- if (warnings.length > 0) {
88
- process.stderr.write("\n" + chalk.yellow("校验警告:") + "\n");
89
- for (const w of warnings) {
90
- process.stderr.write(" " + chalk.yellow("• ") + w + "\n");
91
- }
92
- process.stderr.write("\n");
93
- }
94
- }