@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.
- package/README.md +16 -34
- package/README.zh-CN.md +14 -34
- package/agents/agentflow-node-executor-code.md +13 -3
- package/agents/agentflow-node-executor-planning.md +13 -3
- package/agents/agentflow-node-executor-requirement.md +13 -3
- package/agents/agentflow-node-executor-test.md +13 -3
- package/agents/agentflow-node-executor-ui.md +13 -3
- package/agents/agentflow-node-executor.md +13 -3
- package/agents/en/agentflow-node-executor.md +13 -3
- package/agents/zh/agentflow-node-executor.md +13 -3
- package/bin/agentflow.mjs +3 -3
- package/bin/lib/admin-builtin-pipelines.mjs +3 -2
- package/bin/lib/agent-runners.mjs +0 -929
- package/bin/lib/auth.mjs +2 -1
- package/bin/lib/catalog-agents.mjs +2 -1
- package/bin/lib/catalog-flows.mjs +165 -90
- package/bin/lib/composer-agent.mjs +3 -793
- package/bin/lib/composer-skill-router.mjs +3 -323
- package/bin/lib/exec-buffered.mjs +32 -0
- package/bin/lib/flow-dsl/cli.mjs +277 -0
- package/bin/lib/flow-dsl/codegen.mjs +358 -0
- package/bin/lib/flow-dsl/defs.mjs +103 -0
- package/bin/lib/flow-dsl/index.mjs +77 -0
- package/bin/lib/flow-dsl/ir.mjs +305 -0
- package/bin/lib/flow-dsl/layout.mjs +115 -0
- package/bin/lib/flow-dsl/legacy-yaml.mjs +303 -0
- package/bin/lib/flow-dsl/lint.mjs +241 -0
- package/bin/lib/flow-dsl/packages.mjs +251 -0
- package/bin/lib/flow-dsl/parser.mjs +444 -0
- package/bin/lib/flow-import.mjs +96 -26
- package/bin/lib/flow-write.mjs +48 -12
- package/bin/lib/help.mjs +8 -28
- package/bin/lib/html-escape.mjs +19 -0
- package/bin/lib/http-util.mjs +49 -0
- package/bin/lib/legacy-flow-execution.mjs +37 -0
- package/bin/lib/locales/en.json +1 -1
- package/bin/lib/locales/zh.json +1 -1
- package/bin/lib/main.mjs +152 -286
- package/bin/lib/marketplace.mjs +206 -61
- package/bin/lib/model-config.mjs +10 -0
- package/bin/lib/node-package-archive.mjs +240 -0
- package/bin/lib/node-package-bootstrap.mjs +72 -0
- package/bin/lib/node-package-manifest.mjs +174 -0
- package/bin/lib/paths.mjs +42 -19
- package/bin/lib/prd-workflow-routes.mjs +2949 -0
- package/bin/lib/prd-workflow-server.mjs +4884 -0
- package/bin/lib/run-events.mjs +0 -66
- package/bin/lib/schedule-config.mjs +4 -4
- package/bin/lib/skill-runtime.mjs +35 -0
- package/bin/lib/startup-storage-migrations.mjs +274 -0
- package/bin/lib/ui-server.mjs +1092 -18123
- package/bin/lib/user-env.mjs +16 -0
- package/bin/lib/workspace-auto-layout.mjs +234 -0
- package/bin/lib/workspace-flow-store.mjs +478 -0
- package/bin/lib/workspace-graph-merge.mjs +41 -34
- package/bin/lib/workspace-preview.mjs +12 -2
- package/bin/lib/workspace-routes.mjs +3088 -0
- package/bin/lib/workspace-server.mjs +6199 -0
- package/bin/lib/workspace-state.mjs +331 -0
- package/bin/lib/workspace.mjs +2 -1
- package/builtin/nodes/agent_subAgent.md +1 -0
- package/builtin/nodes/control_agent_toBool.md +2 -0
- package/builtin/nodes/control_anyOne.md +2 -0
- package/builtin/nodes/control_cancelled.md +2 -0
- package/builtin/nodes/control_cd_workspace.md +5 -11
- package/builtin/nodes/control_delay.md +2 -0
- package/builtin/nodes/control_end.md +2 -0
- package/builtin/nodes/control_if.md +1 -0
- package/builtin/nodes/control_interval_loop.md +2 -0
- package/builtin/nodes/control_load_mcp.md +24 -0
- package/builtin/nodes/control_load_skills.md +8 -23
- package/builtin/nodes/control_start.md +2 -0
- package/builtin/nodes/control_toBool.md +2 -0
- package/builtin/nodes/control_user_workspace.md +2 -0
- package/builtin/nodes/control_wait_until.md +2 -0
- package/builtin/nodes/display_ascii.md +1 -0
- package/builtin/nodes/display_chart.md +1 -0
- package/builtin/nodes/display_html.md +1 -0
- package/builtin/nodes/display_image.md +1 -0
- package/builtin/nodes/display_markdown.md +1 -0
- package/builtin/nodes/display_mermaid.md +1 -0
- package/builtin/nodes/display_react_app.md +1 -0
- package/builtin/nodes/display_table.md +1 -0
- package/builtin/nodes/provide_bool.md +1 -0
- package/builtin/nodes/provide_file.md +1 -0
- package/builtin/nodes/provide_password.md +1 -0
- package/builtin/nodes/provide_str.md +1 -0
- package/builtin/nodes/tool_display_share_link.md +1 -0
- package/builtin/nodes/tool_get_env.md +2 -0
- package/builtin/nodes/tool_git_checkout.md +1 -0
- package/builtin/nodes/tool_git_worktree_load.md +1 -0
- package/builtin/nodes/tool_git_worktree_unload.md +1 -0
- package/builtin/nodes/tool_gitlab_create_mr.md +1 -0
- package/builtin/nodes/tool_jenkins_build.md +2 -0
- package/builtin/nodes/tool_load_key.md +2 -0
- package/builtin/nodes/tool_nodejs.md +17 -19
- package/builtin/nodes/tool_print.md +2 -0
- package/builtin/nodes/tool_save_key.md +2 -0
- package/builtin/nodes/tool_set_run_env.md +1 -0
- package/builtin/nodes/tool_user_ask.md +2 -0
- package/builtin/nodes/tool_user_check.md +2 -0
- package/builtin/nodes/tool_wecom_send_app_markdown.md +1 -0
- package/builtin/nodes/tool_wecom_send_group_markdown.md +1 -0
- package/builtin/nodes/workspace_one_click_task.md +44 -0
- package/builtin/nodes/workspace_run.md +16 -0
- package/builtin/nodes/workspace_scheduled_run.md +16 -0
- package/builtin/pipelines/module-migrate/scripts/gate.mjs +37 -0
- package/builtin/pipelines/module-migrate/scripts/static-check.mjs +82 -0
- package/builtin/pipelines/module-migrate/workspace.flow.js +172 -0
- package/builtin/pipelines/module-migrate/workspace.layout.json +134 -0
- package/builtin/pipelines/new/scripts/lint-flow.mjs +38 -0
- package/builtin/pipelines/new/workspace.flow.js +91 -0
- package/builtin/pipelines/new/workspace.layout.json +70 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B9xslwI0.js → WorkflowAssistantThread-ClNxY2Wu.js} +1 -1
- package/builtin/web-ui/dist/assets/index-BJzMYRK3.js +870 -0
- package/builtin/web-ui/dist/assets/index-CEXmmwM2.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +3 -1
- package/reference/flow-control-capabilities.md +77 -158
- package/reference/flow-layout.md +1 -1
- package/reference/flow-prompt-handler-check.md +2 -2
- package/skills/agentflow-author-flow/SKILL.md +1 -1
- package/skills/agentflow-cli/SKILL.md +139 -24
- package/skills/agentflow-cli/agents/openai.yaml +2 -2
- package/skills/agentflow-cli/scripts/agentflow-cli.mjs +654 -20
- package/skills/agentflow-cli/scripts/agentflow-runtime.mjs +97 -0
- package/skills/agentflow-flow-add-instances/SKILL.md +1 -1
- package/skills/agentflow-flow-dsl/SKILL.md +206 -0
- package/skills/agentflow-flow-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-flow-dsl/references/node-calls.md +39 -0
- package/skills/agentflow-flow-edit-node-fields/SKILL.md +1 -1
- package/skills/agentflow-flow-recipes/SKILL.md +7 -4
- package/skills/agentflow-flow-recipes/references/recipes.md +97 -43
- package/skills/agentflow-flow-sync-ui/SKILL.md +1 -1
- package/skills/agentflow-node-dsl/SKILL.md +212 -0
- package/skills/agentflow-node-dsl/agents/openai.yaml +4 -0
- package/skills/agentflow-node-reference/SKILL.md +2 -2
- package/skills/agentflow-node-reference/references/builtin-nodes.md +166 -115
- package/skills/agentflow-runtime-reference/references/runtime.md +1 -1
- package/skills/agentflow-workspace-ascii/SKILL.md +9 -16
- package/skills/agentflow-workspace-graph/SKILL.md +62 -48
- package/skills/agentflow-workspace-html/SKILL.md +7 -2
- package/skills/agentflow-workspace-image/SKILL.md +6 -2
- package/skills/agentflow-workspace-markdown/SKILL.md +14 -21
- package/skills/agentflow-workspace-mermaid/SKILL.md +8 -16
- package/bin/lib/api-runner.mjs +0 -387
- package/bin/lib/apply.mjs +0 -903
- package/bin/lib/composer-flow-instances.mjs +0 -68
- package/bin/lib/composer-flow-skeleton.mjs +0 -334
- package/bin/lib/composer-flow-validate.mjs +0 -47
- package/bin/lib/composer-model-router.mjs +0 -185
- package/bin/lib/composer-node-schema.mjs +0 -303
- package/bin/lib/composer-planner.mjs +0 -751
- package/bin/lib/composer-script-ops.mjs +0 -233
- package/bin/lib/flow-static-preview.mjs +0 -104
- package/bin/lib/hub-login.mjs +0 -54
- package/bin/lib/hub-publish.mjs +0 -159
- package/bin/lib/hub-remote.mjs +0 -189
- package/bin/lib/hub.mjs +0 -299
- package/bin/lib/jenkins.mjs +0 -380
- package/bin/lib/node-execute.mjs +0 -539
- package/bin/lib/normalize-node-tool-command.mjs +0 -97
- package/bin/lib/runtime-context.mjs +0 -243
- package/bin/lib/scheduler.mjs +0 -601
- package/bin/lib/ui-print.mjs +0 -94
- package/bin/pipeline/build-node-prompt.mjs +0 -271
- package/bin/pipeline/check-cache.mjs +0 -191
- package/bin/pipeline/check-flow.mjs +0 -543
- package/bin/pipeline/collect-nodes.mjs +0 -212
- package/bin/pipeline/compute-cache-md5.mjs +0 -177
- package/bin/pipeline/ensure-run-dir.mjs +0 -71
- package/bin/pipeline/gc.mjs +0 -129
- package/bin/pipeline/get-env.mjs +0 -59
- package/bin/pipeline/get-resolved-values.mjs +0 -344
- package/bin/pipeline/load-key.mjs +0 -62
- package/bin/pipeline/parse-flow.mjs +0 -708
- package/bin/pipeline/post-process-control-if.mjs +0 -23
- package/bin/pipeline/post-process-node.mjs +0 -490
- package/bin/pipeline/pre-process-node.mjs +0 -1430
- package/bin/pipeline/resolve-inputs.mjs +0 -201
- package/bin/pipeline/run-tool-nodejs.mjs +0 -167
- package/bin/pipeline/save-key.mjs +0 -93
- package/bin/pipeline/snapshot-prior-round.mjs +0 -70
- package/bin/pipeline/validate-for-ui.mjs +0 -234
- package/bin/pipeline/validate-script-output.mjs +0 -130
- package/bin/pipeline/write-result.mjs +0 -182
- package/builtin/pipelines/module-migrate/flow.yaml +0 -819
- package/builtin/pipelines/new/flow.yaml +0 -545
- package/builtin/pipelines/new/scripts/check-flow.mjs +0 -9
- package/builtin/pipelines/new/scripts/collect-nodes.mjs +0 -211
- package/builtin/web-ui/dist/assets/index-DZ328oSo.css +0 -1
- package/builtin/web-ui/dist/assets/index-DmUV7ZCL.js +0 -888
- package/skills/agentflow-node-authoring/SKILL.md +0 -57
package/bin/lib/jenkins.mjs
DELETED
|
@@ -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
|
-
}
|