@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,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
- }