@fieldwangai/agentflow 0.1.153 → 0.1.154

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 (192) 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 +276 -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 +268 -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 +1087 -18122
  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-routes.mjs +3088 -0
  57. package/bin/lib/workspace-server.mjs +6199 -0
  58. package/bin/lib/workspace-state.mjs +331 -0
  59. package/bin/lib/workspace.mjs +2 -1
  60. package/builtin/nodes/agent_subAgent.md +1 -0
  61. package/builtin/nodes/control_agent_toBool.md +2 -0
  62. package/builtin/nodes/control_anyOne.md +2 -0
  63. package/builtin/nodes/control_cancelled.md +2 -0
  64. package/builtin/nodes/control_cd_workspace.md +5 -11
  65. package/builtin/nodes/control_delay.md +2 -0
  66. package/builtin/nodes/control_end.md +2 -0
  67. package/builtin/nodes/control_if.md +1 -0
  68. package/builtin/nodes/control_interval_loop.md +2 -0
  69. package/builtin/nodes/control_load_mcp.md +24 -0
  70. package/builtin/nodes/control_load_skills.md +8 -23
  71. package/builtin/nodes/control_start.md +2 -0
  72. package/builtin/nodes/control_toBool.md +2 -0
  73. package/builtin/nodes/control_user_workspace.md +2 -0
  74. package/builtin/nodes/control_wait_until.md +2 -0
  75. package/builtin/nodes/display_ascii.md +1 -0
  76. package/builtin/nodes/display_chart.md +1 -0
  77. package/builtin/nodes/display_html.md +1 -0
  78. package/builtin/nodes/display_image.md +1 -0
  79. package/builtin/nodes/display_markdown.md +1 -0
  80. package/builtin/nodes/display_mermaid.md +1 -0
  81. package/builtin/nodes/display_react_app.md +1 -0
  82. package/builtin/nodes/display_table.md +1 -0
  83. package/builtin/nodes/provide_bool.md +1 -0
  84. package/builtin/nodes/provide_file.md +1 -0
  85. package/builtin/nodes/provide_password.md +1 -0
  86. package/builtin/nodes/provide_str.md +1 -0
  87. package/builtin/nodes/tool_display_share_link.md +1 -0
  88. package/builtin/nodes/tool_get_env.md +2 -0
  89. package/builtin/nodes/tool_git_checkout.md +1 -0
  90. package/builtin/nodes/tool_git_worktree_load.md +1 -0
  91. package/builtin/nodes/tool_git_worktree_unload.md +1 -0
  92. package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
  93. package/builtin/nodes/tool_jenkins_build.md +2 -0
  94. package/builtin/nodes/tool_load_key.md +2 -0
  95. package/builtin/nodes/tool_nodejs.md +17 -19
  96. package/builtin/nodes/tool_print.md +2 -0
  97. package/builtin/nodes/tool_save_key.md +2 -0
  98. package/builtin/nodes/tool_set_run_env.md +1 -0
  99. package/builtin/nodes/tool_user_ask.md +2 -0
  100. package/builtin/nodes/tool_user_check.md +2 -0
  101. package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
  102. package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
  103. package/builtin/nodes/workspace_one_click_task.md +44 -0
  104. package/builtin/nodes/workspace_run.md +16 -0
  105. package/builtin/nodes/workspace_scheduled_run.md +16 -0
  106. package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
  107. package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
  108. package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
  109. package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
  110. package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
  111. package/builtin/pipelines/new/workspace.flow.js +91 -0
  112. package/builtin/pipelines/new/workspace.layout.json +70 -0
  113. package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-CKClwj96.js} +1 -1
  114. package/builtin/web-ui/dist/assets/index-BZ5KqLur.js +870 -0
  115. package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
  116. package/builtin/web-ui/dist/index.html +2 -2
  117. package/package.json +2 -1
  118. package/reference/flow-control-capabilities.md +77 -158
  119. package/reference/flow-layout.md +1 -1
  120. package/reference/flow-prompt-handler-check.md +2 -2
  121. package/skills/agentflow-author-flow/SKILL.md +1 -1
  122. package/skills/agentflow-cli/SKILL.md +139 -24
  123. package/skills/agentflow-cli/agents/openai.yaml +2 -2
  124. package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
  125. package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
  126. package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
  127. package/skills/agentflow-flow-dsl/SKILL.md +206 -0
  128. package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
  129. package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
  130. package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
  131. package/skills/agentflow-flow-recipes/SKILL.md +7 -4
  132. package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
  133. package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
  134. package/skills/agentflow-node-dsl/SKILL.md +212 -0
  135. package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
  136. package/skills/agentflow-node-reference/SKILL.md +2 -2
  137. package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
  138. package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
  139. package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
  140. package/skills/agentflow-workspace-graph/SKILL.md +62 -48
  141. package/skills/agentflow-workspace-html/SKILL.md +7 -2
  142. package/skills/agentflow-workspace-image/SKILL.md +6 -2
  143. package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
  144. package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
  145. package/bin/lib/api-runner.mjs +0 -387
  146. package/bin/lib/apply.mjs +0 -903
  147. package/bin/lib/composer-flow-instances.mjs +0 -68
  148. package/bin/lib/composer-flow-skeleton.mjs +0 -334
  149. package/bin/lib/composer-flow-validate.mjs +0 -47
  150. package/bin/lib/composer-model-router.mjs +0 -185
  151. package/bin/lib/composer-node-schema.mjs +0 -303
  152. package/bin/lib/composer-planner.mjs +0 -751
  153. package/bin/lib/composer-script-ops.mjs +0 -233
  154. package/bin/lib/flow-static-preview.mjs +0 -104
  155. package/bin/lib/hub-login.mjs +0 -54
  156. package/bin/lib/hub-publish.mjs +0 -159
  157. package/bin/lib/hub-remote.mjs +0 -189
  158. package/bin/lib/hub.mjs +0 -299
  159. package/bin/lib/jenkins.mjs +0 -380
  160. package/bin/lib/node-execute.mjs +0 -539
  161. package/bin/lib/normalize-node-tool-command.mjs +0 -97
  162. package/bin/lib/runtime-context.mjs +0 -243
  163. package/bin/lib/scheduler.mjs +0 -601
  164. package/bin/lib/ui-print.mjs +0 -94
  165. package/bin/pipeline/build-node-prompt.mjs +0 -271
  166. package/bin/pipeline/check-cache.mjs +0 -191
  167. package/bin/pipeline/check-flow.mjs +0 -543
  168. package/bin/pipeline/collect-nodes.mjs +0 -212
  169. package/bin/pipeline/compute-cache-md5.mjs +0 -177
  170. package/bin/pipeline/ensure-run-dir.mjs +0 -71
  171. package/bin/pipeline/gc.mjs +0 -129
  172. package/bin/pipeline/get-env.mjs +0 -59
  173. package/bin/pipeline/get-resolved-values.mjs +0 -344
  174. package/bin/pipeline/load-key.mjs +0 -62
  175. package/bin/pipeline/parse-flow.mjs +0 -708
  176. package/bin/pipeline/post-process-control-if.mjs +0 -23
  177. package/bin/pipeline/post-process-node.mjs +0 -490
  178. package/bin/pipeline/pre-process-node.mjs +0 -1430
  179. package/bin/pipeline/resolve-inputs.mjs +0 -201
  180. package/bin/pipeline/run-tool-nodejs.mjs +0 -167
  181. package/bin/pipeline/save-key.mjs +0 -93
  182. package/bin/pipeline/snapshot-prior-round.mjs +0 -70
  183. package/bin/pipeline/validate-for-ui.mjs +0 -234
  184. package/bin/pipeline/validate-script-output.mjs +0 -130
  185. package/bin/pipeline/write-result.mjs +0 -182
  186. package/builtin/pipelines/module-migrate/flow.yaml +0 -819
  187. package/builtin/pipelines/new/flow.yaml +0 -545
  188. package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
  189. package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
  190. package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
  191. package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
  192. package/skills/agentflow-node-authoring/SKILL.md +0 -57
@@ -1,380 +0,0 @@
1
- import { spawnSync } from "child_process";
2
- import crypto from "crypto";
3
- import fs from "fs";
4
- import os from "os";
5
- import path from "path";
6
-
7
- import { getAgentflowSkillsRoot } from "./paths.mjs";
8
-
9
- const PACKAGE_RE = /(?:\.apk|\.aab|\.ipa)(?:[?#]|$)|apk-dir|apk-file/i;
10
- const QR_RE = /(?:^|[^a-z])(?:qr|qrcode)(?:[^a-z]|$)|二维码/i;
11
- const URL_RE = /https?:\/\/[^\s<>"']+/gi;
12
-
13
- export const JENKINS_BUILD_STATE_VERSION = 1;
14
- export const DEFAULT_JENKINS_POLL_INTERVAL_MS = 30_000;
15
- export const DEFAULT_JENKINS_TIMEOUT_MS = 2 * 60 * 60 * 1000;
16
-
17
- function safeText(value, max = 800) {
18
- return String(value ?? "").replace(/[\r\n]+/g, " ").trim().slice(0, max);
19
- }
20
-
21
- function safeCredentialKey(ref) {
22
- return String(ref || "")
23
- .trim()
24
- .toUpperCase()
25
- .replace(/[^A-Z0-9]+/g, "_")
26
- .replace(/^_+|_+$/g, "");
27
- }
28
-
29
- function parseDurationMs(raw, fallback) {
30
- const text = String(raw ?? "").trim().toLowerCase();
31
- if (!text) return fallback;
32
- const match = text.match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/);
33
- if (!match) throw new Error(`invalid duration: ${raw}`);
34
- const value = Number(match[1]);
35
- const unit = match[2] || "s";
36
- const factor = unit === "ms" ? 1 : unit === "s" ? 1000 : unit === "m" ? 60_000 : unit === "h" ? 3_600_000 : 86_400_000;
37
- return Math.round(value * factor);
38
- }
39
-
40
- export function normalizeJenkinsBuildConfig(inputs = {}) {
41
- const job = String(inputs.job || inputs.jobName || "").trim();
42
- if (!job) throw new Error("Jenkins job is required");
43
- let parameters = {};
44
- const rawParameters = inputs.parameters ?? inputs.parametersJson ?? "{}";
45
- if (rawParameters && typeof rawParameters === "object" && !Array.isArray(rawParameters)) {
46
- parameters = { ...rawParameters };
47
- } else if (String(rawParameters || "").trim()) {
48
- const parsed = JSON.parse(String(rawParameters));
49
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Jenkins parameters must be a JSON object");
50
- parameters = parsed;
51
- }
52
- const pollIntervalMs = Math.max(5_000, parseDurationMs(inputs.pollInterval || inputs.pollIntervalSec, DEFAULT_JENKINS_POLL_INTERVAL_MS));
53
- const timeoutMs = Math.max(pollIntervalMs, parseDurationMs(inputs.timeout || inputs.timeoutSec, DEFAULT_JENKINS_TIMEOUT_MS));
54
- return {
55
- job,
56
- parameters,
57
- credentialRef: String(inputs.credentialRef || "").trim(),
58
- pollIntervalMs,
59
- timeoutMs,
60
- };
61
- }
62
-
63
- export function jenkinsCredentialEnv(env = process.env, credentialRef = "") {
64
- const key = safeCredentialKey(credentialRef);
65
- const prefix = key ? `JENKINS_${key}_` : "";
66
- const pick = (name) => (prefix && env[`${prefix}${name}`]) || env[`JENKINS_${name}`] || "";
67
- return {
68
- baseUrl: String(pick("BASE_URL") || "").trim(),
69
- username: String(pick("USERNAME") || "").trim(),
70
- token: String(pick("TOKEN") || "").trim(),
71
- };
72
- }
73
-
74
- function candidateSkillDirs(workspaceRoot, env) {
75
- return [
76
- env.AGENTFLOW_JENKINS_SKILL_DIR,
77
- workspaceRoot ? path.join(workspaceRoot, ".agents", "skills", "jenkins") : "",
78
- workspaceRoot ? path.join(workspaceRoot, ".codex", "skills", "jenkins") : "",
79
- path.join(getAgentflowSkillsRoot(), "jenkins"),
80
- path.join(os.homedir(), ".agents", "skills", "jenkins"),
81
- path.join(os.homedir(), ".codex", "skills", "jenkins"),
82
- path.join(os.homedir(), ".@nova", "skillhub", "jenkins"),
83
- path.join(os.homedir(), ".@nova", "skillhub", "jenkins", "latest", "content"),
84
- ].filter(Boolean);
85
- }
86
-
87
- export function resolveJenkinsSkillRuntime(workspaceRoot, env = process.env) {
88
- for (const dir of candidateSkillDirs(workspaceRoot, env)) {
89
- const skillDir = path.resolve(String(dir));
90
- const cli = ["jenkins_cli.py", "jenkins.py"]
91
- .map((name) => path.join(skillDir, "scripts", name))
92
- .find((candidate) => fs.existsSync(candidate));
93
- if (!cli) continue;
94
- const configuredPython = String(env.AGENTFLOW_JENKINS_PYTHON || "").trim();
95
- const venvPython = path.join(skillDir, ".venv", "bin", "python");
96
- if (configuredPython) return { skillDir, cli, command: configuredPython, prefixArgs: [] };
97
- if (fs.existsSync(venvPython)) return { skillDir, cli, command: venvPython, prefixArgs: [] };
98
- const systemPython = spawnSync("python3", ["-c", "import jenkins"], { encoding: "utf-8", timeout: 5_000 });
99
- if (systemPython.status === 0) return { skillDir, cli, command: "python3", prefixArgs: [] };
100
- const uv = spawnSync("uv", ["--version"], { encoding: "utf-8", timeout: 5_000 });
101
- if (uv.status === 0) {
102
- return {
103
- skillDir,
104
- cli,
105
- command: "uv",
106
- prefixArgs: ["run", "--quiet", "--with", "python-jenkins", "python"],
107
- };
108
- }
109
- return { skillDir, cli, command: "python3", prefixArgs: [] };
110
- }
111
- throw new Error("Jenkins skill is not installed; install the `jenkins` skill or set AGENTFLOW_JENKINS_SKILL_DIR");
112
- }
113
-
114
- function operationArgs(operation, args) {
115
- if (operation === "trigger") {
116
- return ["trigger", "--job-name", String(args.job), "--parameters-json", JSON.stringify(args.parameters || {})];
117
- }
118
- if (operation === "queue") return ["queue", "--queue-id", String(args.queueId)];
119
- if (operation === "build") return ["build", "--job-name", String(args.job), "--build-number", String(args.buildNumber)];
120
- throw new Error(`unsupported Jenkins operation: ${operation}`);
121
- }
122
-
123
- export function createJenkinsSkillInvoker({ workspaceRoot, credentialRef = "", env = process.env, timeoutMs = 30_000 } = {}) {
124
- const runtime = resolveJenkinsSkillRuntime(workspaceRoot, env);
125
- const credentials = jenkinsCredentialEnv(env, credentialRef);
126
- if (!credentials.baseUrl) {
127
- const suffix = credentialRef ? ` for credentialRef ${credentialRef}` : "";
128
- throw new Error(`JENKINS_BASE_URL is required${suffix}`);
129
- }
130
- return (operation, args = {}) => {
131
- const child = spawnSync(runtime.command, [...runtime.prefixArgs, runtime.cli, ...operationArgs(operation, args)], {
132
- cwd: workspaceRoot ? path.resolve(workspaceRoot) : process.cwd(),
133
- env: {
134
- ...env,
135
- JENKINS_BASE_URL: credentials.baseUrl,
136
- JENKINS_USERNAME: credentials.username,
137
- JENKINS_TOKEN: credentials.token,
138
- },
139
- encoding: "utf-8",
140
- timeout: Math.max(1_000, Number(timeoutMs) || 30_000),
141
- maxBuffer: 8 * 1024 * 1024,
142
- });
143
- if (child.error) throw new Error(`Jenkins skill failed: ${safeText(child.error.message)}`);
144
- let payload;
145
- try {
146
- payload = JSON.parse(String(child.stdout || "").trim());
147
- } catch {
148
- throw new Error(`Jenkins skill returned invalid JSON: ${safeText(child.stderr || child.stdout || "empty output")}`);
149
- }
150
- return payload;
151
- };
152
- }
153
-
154
- function hashParameters(parameters) {
155
- return crypto.createHash("sha256").update(JSON.stringify(parameters || {})).digest("hex").slice(0, 16);
156
- }
157
-
158
- function trimUrl(url) {
159
- return String(url || "").trim().replace(/[.,;:!?\])}]+$/, "");
160
- }
161
-
162
- function artifactUrl(buildUrl, artifact) {
163
- const direct = trimUrl(artifact?.url);
164
- if (direct) return direct;
165
- const relative = String(artifact?.relativePath || "").trim();
166
- if (!relative || !buildUrl) return "";
167
- return `${String(buildUrl).replace(/\/+$/, "")}/artifact/${relative.split("/").map(encodeURIComponent).join("/")}`;
168
- }
169
-
170
- function collectBuildLinks(build = {}) {
171
- const buildUrl = trimUrl(build.url);
172
- const packageUrls = [];
173
- const qrUrls = [];
174
- const add = (context, rawUrl) => {
175
- const url = trimUrl(rawUrl);
176
- if (!/^https?:\/\//i.test(url)) return;
177
- const haystack = `${context || ""} ${url}`;
178
- if (QR_RE.test(haystack)) {
179
- if (!qrUrls.includes(url)) qrUrls.push(url);
180
- } else if (PACKAGE_RE.test(haystack)) {
181
- if (!packageUrls.includes(url)) packageUrls.push(url);
182
- }
183
- };
184
- for (const artifact of Array.isArray(build.artifacts) ? build.artifacts : []) {
185
- add(artifact?.relativePath || artifact?.fileName || "", artifactUrl(buildUrl, artifact));
186
- }
187
- const scan = (value, context = "", depth = 0) => {
188
- if (depth > 5 || value == null) return;
189
- if (typeof value === "string") {
190
- for (const url of value.match(URL_RE) || []) add(context, url);
191
- return;
192
- }
193
- if (Array.isArray(value)) {
194
- for (const item of value) scan(item, context, depth + 1);
195
- return;
196
- }
197
- if (typeof value === "object") {
198
- for (const [key, item] of Object.entries(value)) scan(item, `${context} ${key}`, depth + 1);
199
- }
200
- };
201
- scan(build.description || "", "description");
202
- scan(build.actions || [], "actions");
203
- return {
204
- buildUrl,
205
- url: packageUrls[0] || buildUrl,
206
- qrUrl: qrUrls[0] || "",
207
- };
208
- }
209
-
210
- function waitResult(state, nowMs, pollIntervalMs, message) {
211
- const wakeAt = new Date(nowMs + pollIntervalMs).toISOString();
212
- return {
213
- kind: "waiting",
214
- message,
215
- wakeAt,
216
- state: { ...state, wakeAt, message, updatedAt: new Date(nowMs).toISOString() },
217
- };
218
- }
219
-
220
- function completeResult(state, nowMs, status, message, links = {}) {
221
- const completedAt = new Date(nowMs).toISOString();
222
- const next = {
223
- ...state,
224
- phase: "complete",
225
- status,
226
- message,
227
- url: links.url || state.url || state.buildUrl || "",
228
- qrUrl: links.qrUrl || state.qrUrl || "",
229
- buildUrl: links.buildUrl || state.buildUrl || "",
230
- wakeAt: "",
231
- completedAt,
232
- updatedAt: completedAt,
233
- };
234
- return { kind: "complete", message, state: next, outputs: { status, url: next.url, qrUrl: next.qrUrl } };
235
- }
236
-
237
- function failedResult(state, nowMs, message) {
238
- const completed = completeResult(state, nowMs, "ERROR", message);
239
- return { ...completed, kind: "failed" };
240
- }
241
-
242
- function invokeFailure(state, nowMs, config, response, fallback) {
243
- const errors = Number(state.consecutiveErrors || 0) + 1;
244
- const message = safeText(response?.safe_summary || response?.blocked_reason || fallback || "Jenkins request failed");
245
- const next = { ...state, consecutiveErrors: errors, lastError: message };
246
- if (errors >= 3) return failedResult(next, nowMs, message);
247
- return waitResult(next, nowMs, config.pollIntervalMs, `Jenkins 暂时不可用,稍后重试 (${errors}/3)`);
248
- }
249
-
250
- /**
251
- * Advances exactly one Jenkins remote operation. Long waits are represented as
252
- * a wakeAt checkpoint; callers persist the returned state and let Scheduler
253
- * re-enter the same node.
254
- */
255
- export function advanceJenkinsBuild({ state, config, invoke, persistState = () => {}, nowMs = Date.now(), cancelled = false }) {
256
- const nowIso = new Date(nowMs).toISOString();
257
- let current = state && typeof state === "object" ? { ...state } : null;
258
- if (cancelled) return completeResult(current || { version: 1, job: config.job, createdAt: nowIso }, nowMs, "CANCELLED", "Jenkins 构建监控已取消");
259
- if (current?.phase === "complete") return completeResult(current, nowMs, current.status || "ERROR", current.message || "Jenkins 构建已结束", current);
260
-
261
- if (!current) {
262
- current = {
263
- version: JENKINS_BUILD_STATE_VERSION,
264
- job: config.job,
265
- parametersHash: hashParameters(config.parameters),
266
- phase: "triggering",
267
- status: "QUEUED",
268
- createdAt: nowIso,
269
- startedAt: nowIso,
270
- deadlineAt: new Date(nowMs + config.timeoutMs).toISOString(),
271
- pollCount: 0,
272
- consecutiveErrors: 0,
273
- updatedAt: nowIso,
274
- };
275
- // Persist before the non-idempotent trigger. If the process dies after the
276
- // Jenkins request, a later resume reports an unknown outcome instead of
277
- // accidentally triggering a duplicate build.
278
- persistState(current);
279
- let response;
280
- try {
281
- response = invoke("trigger", { job: config.job, parameters: config.parameters });
282
- } catch (error) {
283
- return failedResult(current, nowMs, safeText(error?.message || error));
284
- }
285
- if (!response?.ok) return failedResult(current, nowMs, safeText(response?.safe_summary || response?.blocked_reason || "Jenkins trigger failed"));
286
- const queueId = String(response?.resource?.queue_id || response?.resource?.queueId || "").trim();
287
- if (!queueId) return failedResult(current, nowMs, "Jenkins trigger did not return queueId");
288
- current = { ...current, phase: "queued", status: "QUEUED", queueId, message: `已进入 Jenkins 队列 ${queueId}`, updatedAt: nowIso };
289
- return waitResult(current, nowMs, config.pollIntervalMs, current.message);
290
- }
291
-
292
- if (current.phase === "triggering") {
293
- return failedResult(current, nowMs, "Jenkins 触发结果未知,为避免重复构建未自动重试");
294
- }
295
- if (current.deadlineAt && Date.parse(current.deadlineAt) <= nowMs) {
296
- return completeResult(current, nowMs, "TIMEOUT", "等待 Jenkins 构建超时");
297
- }
298
-
299
- if (current.phase === "queued") {
300
- let response;
301
- try {
302
- response = invoke("queue", { queueId: current.queueId });
303
- } catch (error) {
304
- response = { ok: false, safe_summary: error?.message || String(error) };
305
- }
306
- if (!response?.ok) return invokeFailure(current, nowMs, config, response, "Jenkins queue lookup failed");
307
- const item = response?.resource?.item || {};
308
- const executable = item.executable || {};
309
- if (item.cancelled) return completeResult(current, nowMs, "ABORTED", "Jenkins 队列任务已取消");
310
- if (executable.number != null) {
311
- const buildNumber = String(executable.number);
312
- const buildUrl = trimUrl(executable.url);
313
- const next = {
314
- ...current,
315
- phase: "running",
316
- status: "RUNNING",
317
- buildNumber,
318
- buildUrl,
319
- pollCount: Number(current.pollCount || 0) + 1,
320
- consecutiveErrors: 0,
321
- };
322
- return waitResult(next, nowMs, config.pollIntervalMs, `Jenkins 构建中 · #${buildNumber}`);
323
- }
324
- const next = {
325
- ...current,
326
- pollCount: Number(current.pollCount || 0) + 1,
327
- consecutiveErrors: 0,
328
- queueReason: safeText(item.why || ""),
329
- };
330
- return waitResult(next, nowMs, config.pollIntervalMs, next.queueReason || `Jenkins 排队中 · ${current.queueId}`);
331
- }
332
-
333
- if (current.phase === "running") {
334
- let response;
335
- try {
336
- response = invoke("build", { job: config.job, buildNumber: current.buildNumber });
337
- } catch (error) {
338
- response = { ok: false, safe_summary: error?.message || String(error) };
339
- }
340
- if (!response?.ok) return invokeFailure(current, nowMs, config, response, "Jenkins build lookup failed");
341
- const build = response?.resource?.build || {};
342
- const links = collectBuildLinks(build);
343
- if (build.building || !build.result) {
344
- const next = {
345
- ...current,
346
- status: "RUNNING",
347
- buildUrl: links.buildUrl || current.buildUrl || "",
348
- pollCount: Number(current.pollCount || 0) + 1,
349
- consecutiveErrors: 0,
350
- };
351
- return waitResult(next, nowMs, config.pollIntervalMs, `Jenkins 构建中 · #${current.buildNumber}`);
352
- }
353
- const status = String(build.result || "ERROR").trim().toUpperCase();
354
- return completeResult(
355
- { ...current, buildNumber: String(build.number ?? current.buildNumber ?? "") },
356
- nowMs,
357
- status,
358
- `Jenkins ${status}${current.buildNumber ? ` · #${current.buildNumber}` : ""}`,
359
- links,
360
- );
361
- }
362
-
363
- return failedResult(current, nowMs, `Unknown Jenkins phase: ${safeText(current.phase)}`);
364
- }
365
-
366
- export function readJenkinsBuildState(filePath) {
367
- try {
368
- const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
369
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
370
- } catch {
371
- return null;
372
- }
373
- }
374
-
375
- export function writeJenkinsBuildState(filePath, state) {
376
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
377
- const temp = `${filePath}.tmp-${process.pid}`;
378
- fs.writeFileSync(temp, JSON.stringify(state, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
379
- fs.renameSync(temp, filePath);
380
- }