@fieldwangai/agentflow 0.1.138 → 0.1.141

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.
@@ -51,6 +51,13 @@ import {
51
51
  import { buildGitContext, inferGitRepoRootFromWorktree, loadGitWorktree, normalizeGitContext, unloadGitWorktree } from "../lib/git-worktree.mjs";
52
52
  import { createGitLabMergeRequest } from "../lib/gitlab-mr.mjs";
53
53
  import { sendWecomAppMarkdown, sendWecomGroupMarkdown } from "../lib/wecom.mjs";
54
+ import {
55
+ advanceJenkinsBuild,
56
+ createJenkinsSkillInvoker,
57
+ normalizeJenkinsBuildConfig,
58
+ readJenkinsBuildState,
59
+ writeJenkinsBuildState,
60
+ } from "../lib/jenkins.mjs";
54
61
 
55
62
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
56
63
 
@@ -500,6 +507,106 @@ async function emitWecomMarkdownNode(workspaceRoot, flowName, uuid, instanceId,
500
507
  return emitLocalNoopPrompt(workspaceRoot, runDir, instanceId, "wecom-markdown", `${result.message}\n`);
501
508
  }
502
509
 
510
+ function emitJenkinsBuildNode(workspaceRoot, flowName, uuid, instanceId, execId) {
511
+ const runDir = getRunDir(workspaceRoot, flowName, uuid);
512
+ const data = getResolvedValues(workspaceRoot, flowName, uuid, instanceId);
513
+ if (!data.ok) throw new Error("getResolvedValues failed");
514
+ const inputs = data.resolvedInputs || {};
515
+ const config = normalizeJenkinsBuildConfig(inputs);
516
+ const statePath = path.join(runDir, "state", `${instanceId}.jenkins.json`);
517
+ const priorState = readJenkinsBuildState(statePath);
518
+ if (priorState?.job && priorState.job !== config.job) {
519
+ throw new Error(`checkpoint job mismatch: ${priorState.job} != ${config.job}`);
520
+ }
521
+ let invoker = null;
522
+ const invoke = (operation, args) => {
523
+ if (!invoker) {
524
+ invoker = createJenkinsSkillInvoker({
525
+ workspaceRoot,
526
+ credentialRef: config.credentialRef,
527
+ env: process.env,
528
+ });
529
+ }
530
+ return invoker(operation, args);
531
+ };
532
+ const result = advanceJenkinsBuild({
533
+ state: priorState,
534
+ config,
535
+ invoke,
536
+ persistState: (state) => writeJenkinsBuildState(statePath, state),
537
+ nowMs: Date.now(),
538
+ cancelled: readCancelFlag(runDir),
539
+ });
540
+ writeJenkinsBuildState(statePath, result.state);
541
+ writeOutputSlot(runDir, instanceId, execId, "status", result.outputs?.status || result.state?.status || "");
542
+ writeOutputSlot(runDir, instanceId, execId, "url", result.outputs?.url || result.state?.url || result.state?.buildUrl || "");
543
+ writeOutputSlot(runDir, instanceId, execId, "qrUrl", result.outputs?.qrUrl || result.state?.qrUrl || "");
544
+
545
+ const message = result.message || result.state?.message || "Jenkins Build";
546
+ if (result.kind === "failed") {
547
+ writeResult(
548
+ workspaceRoot,
549
+ flowName,
550
+ uuid,
551
+ instanceId,
552
+ { status: "failed", message },
553
+ { execId, preserveBody: false, body: JSON.stringify({ status: "ERROR", message }, null, 2) },
554
+ );
555
+ throw new Error(message);
556
+ }
557
+
558
+ const promptPath = emitLocalNoopPrompt(
559
+ workspaceRoot,
560
+ runDir,
561
+ instanceId,
562
+ result.kind === "waiting" ? "jenkins-waiting" : "jenkins-complete",
563
+ `${message}\n`,
564
+ );
565
+ if (result.kind === "waiting") {
566
+ const waitId = `${uuid}:${instanceId}:jenkins-build`;
567
+ writeWaitState(runDir, {
568
+ waitId,
569
+ status: "waiting",
570
+ reason: "tool_jenkins_build",
571
+ resumeMode: "rerun",
572
+ flowName,
573
+ uuid,
574
+ instanceId,
575
+ execId,
576
+ phase: result.state?.phase || "queued",
577
+ buildNumber: result.state?.buildNumber || "",
578
+ wakeAt: result.wakeAt,
579
+ createdAt: priorState?.createdAt || new Date().toISOString(),
580
+ });
581
+ writeResult(
582
+ workspaceRoot,
583
+ flowName,
584
+ uuid,
585
+ instanceId,
586
+ { status: "pending", message },
587
+ { execId, preserveBody: false, body: JSON.stringify({ phase: result.state?.phase, wakeAt: result.wakeAt }, null, 2) },
588
+ );
589
+ } else {
590
+ writeResult(
591
+ workspaceRoot,
592
+ flowName,
593
+ uuid,
594
+ instanceId,
595
+ { status: "success", message },
596
+ {
597
+ execId,
598
+ preserveBody: false,
599
+ body: JSON.stringify({
600
+ status: result.outputs?.status || result.state?.status || "",
601
+ url: result.outputs?.url || result.state?.url || "",
602
+ qrUrl: result.outputs?.qrUrl || result.state?.qrUrl || "",
603
+ }, null, 2),
604
+ },
605
+ );
606
+ }
607
+ return { promptPath, lifecycle: result.kind };
608
+ }
609
+
503
610
  function emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) {
504
611
  const runDir = getRunDir(workspaceRoot, flowName, uuid);
505
612
  const { inputs, workspaceContext } = resolveNodeRuntimeContexts(workspaceRoot, flowName, uuid, instanceId);
@@ -1203,26 +1310,29 @@ async function main() {
1203
1310
  return;
1204
1311
  }
1205
1312
 
1206
- if (definitionId === "tool_git_checkout" || definitionId === "tool_git_worktree_load" || definitionId === "tool_git_worktree_unload" || definitionId === "tool_gitlab_create_mr" || definitionId === "tool_wecom_send_group_markdown" || definitionId === "tool_wecom_send_app_markdown" || definitionId === "control_cd_workspace" || definitionId === "control_user_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
1313
+ if (definitionId === "tool_git_checkout" || definitionId === "tool_git_worktree_load" || definitionId === "tool_git_worktree_unload" || definitionId === "tool_gitlab_create_mr" || definitionId === "tool_jenkins_build" || definitionId === "tool_wecom_send_group_markdown" || definitionId === "tool_wecom_send_app_markdown" || definitionId === "control_cd_workspace" || definitionId === "control_user_workspace" || definitionId === "control_load_skills" || definitionId === "tool_print") {
1207
1314
  try {
1208
- const promptPath =
1315
+ const localResult =
1209
1316
  definitionId === "tool_git_checkout"
1210
- ? emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel)
1317
+ ? { promptPath: emitGitCheckoutNode(workspaceRoot, flowName, uuid, instanceId, execId, resultPathRel) }
1211
1318
  : definitionId === "tool_git_worktree_load"
1212
- ? emitGitWorktreeLoadNode(workspaceRoot, flowName, uuid, instanceId, execId)
1319
+ ? { promptPath: emitGitWorktreeLoadNode(workspaceRoot, flowName, uuid, instanceId, execId) }
1213
1320
  : definitionId === "tool_git_worktree_unload"
1214
- ? emitGitWorktreeUnloadNode(workspaceRoot, flowName, uuid, instanceId, execId)
1321
+ ? { promptPath: emitGitWorktreeUnloadNode(workspaceRoot, flowName, uuid, instanceId, execId) }
1215
1322
  : definitionId === "tool_gitlab_create_mr"
1216
- ? await emitGitLabCreateMrNode(workspaceRoot, flowName, uuid, instanceId, execId)
1323
+ ? { promptPath: await emitGitLabCreateMrNode(workspaceRoot, flowName, uuid, instanceId, execId) }
1324
+ : definitionId === "tool_jenkins_build"
1325
+ ? emitJenkinsBuildNode(workspaceRoot, flowName, uuid, instanceId, execId)
1217
1326
  : definitionId === "tool_wecom_send_group_markdown" || definitionId === "tool_wecom_send_app_markdown"
1218
- ? await emitWecomMarkdownNode(workspaceRoot, flowName, uuid, instanceId, execId, definitionId)
1327
+ ? { promptPath: await emitWecomMarkdownNode(workspaceRoot, flowName, uuid, instanceId, execId, definitionId) }
1219
1328
  : definitionId === "control_cd_workspace"
1220
- ? emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1329
+ ? { promptPath: emitCdWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) }
1221
1330
  : definitionId === "control_user_workspace"
1222
- ? emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId)
1331
+ ? { promptPath: emitUserWorkspaceNode(workspaceRoot, flowName, uuid, instanceId, execId) }
1223
1332
  : definitionId === "control_load_skills"
1224
- ? emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId)
1225
- : emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId);
1333
+ ? { promptPath: emitLoadSkillsNode(workspaceRoot, flowName, uuid, instanceId, execId) }
1334
+ : { promptPath: emitToolPrintNode(workspaceRoot, flowName, uuid, instanceId, execId) };
1335
+ const promptPath = localResult.promptPath;
1226
1336
  writeCacheJsonForNode(workspaceRoot, flowName, uuid, instanceId, execId);
1227
1337
  logToRunTag(workspaceRoot, flowName, uuid, "pre-process", { event: "runtime-context-node", instanceId, definitionId });
1228
1338
  console.log(JSON.stringify({
@@ -1233,6 +1343,7 @@ async function main() {
1233
1343
  subagent: "agentflow-node-executor",
1234
1344
  optionalPromptPath: promptPath,
1235
1345
  definitionId,
1346
+ ...(localResult.lifecycle ? { nodeLifecycle: localResult.lifecycle } : {}),
1236
1347
  }));
1237
1348
  return;
1238
1349
  } catch (e) {
@@ -21,11 +21,11 @@ const LOG_FILE = "logs/log.txt";
21
21
  * @param {string} tag - 来源标识:get-ready-nodes | check-cache | pre-process | post-process | result
22
22
  * @param {string|object} message - 文本或对象(对象会 JSON.stringify)
23
23
  */
24
- export function logToRunTag(workspaceRoot, flowName, uuid, tag, message) {
24
+ export function logToRunTag(workspaceRoot, flowName, uuid, tag, message, opts = {}) {
25
25
  if (!workspaceRoot || !flowName || !uuid) return;
26
26
  const text = typeof message === "string" ? message : JSON.stringify(message);
27
27
  try {
28
- const runDir = getRunDir(workspaceRoot, flowName, uuid);
28
+ const runDir = opts.runDir || getRunDir(workspaceRoot, flowName, uuid);
29
29
  const logPath = path.join(runDir, LOG_FILE);
30
30
  fs.mkdirSync(path.dirname(logPath), { recursive: true });
31
31
  const line = `[${new Date().toISOString()}] [${tag}] ${text}\n`;
@@ -12,7 +12,7 @@
12
12
  * import { writeResult } from "./write-result.mjs";
13
13
  * writeResult(workspaceRoot, flowName, uuid, instanceId, fields, options?)
14
14
  * fields: { status, message, finishedAt?, outputPath?, branch?, cacheNotMetReason?, elapsedMs? }
15
- * options: { preserveBody?: boolean, body?: string, execId?: number }
15
+ * options: { preserveBody?: boolean, body?: string, execId?: number, runDir?: string }
16
16
  */
17
17
 
18
18
  import fs from "fs";
@@ -53,10 +53,10 @@ function getExistingBody(resultPath) {
53
53
  * @param {string} uuid - 本次 run 的 uuid
54
54
  * @param {string} instanceId - 节点 instance id
55
55
  * @param {{ status: string, message: string, finishedAt?: string, outputPath?: string, branch?: string, cacheNotMetReason?: string, elapsedMs?: number }} fields - 必填 status、message;可选其余(elapsedMs 为节点执行耗时毫秒,供 UI 展示)
56
- * @param {{ preserveBody?: boolean, body?: string, execId?: number }} [options] - preserveBody:保留已有正文;body:指定正文内容;execId:本轮 execId,缺省则从 memory 读取
56
+ * @param {{ preserveBody?: boolean, body?: string, execId?: number, runDir?: string }} [options] - preserveBody:保留已有正文;body:指定正文内容;execId:本轮 execId,缺省则从 memory 读取;runDir:多用户调度时使用已解析的精确运行目录
57
57
  */
58
58
  export function writeResult(workspaceRoot, flowName, uuid, instanceId, fields, options = {}) {
59
- const runDir = getRunDir(workspaceRoot, flowName, uuid);
59
+ const runDir = options.runDir || getRunDir(workspaceRoot, flowName, uuid);
60
60
  const execId = options.execId ?? loadExecId(workspaceRoot, flowName, uuid, instanceId);
61
61
  const resultBasename = intermediateResultBasename(instanceId, execId);
62
62
  const resultPath = path.join(runDir, intermediateDirForNode(instanceId), resultBasename);
@@ -114,7 +114,7 @@ export function writeResult(workspaceRoot, flowName, uuid, instanceId, fields, o
114
114
  branch: branch ?? undefined,
115
115
  cacheNotMetReason: cacheNotMetReason ?? undefined,
116
116
  resultPathRel: path.relative(runDir, resultPath),
117
- });
117
+ }, { runDir });
118
118
  }
119
119
 
120
120
  function main() {
@@ -0,0 +1,64 @@
1
+ ---
2
+ # Built-in node: durable Jenkins build
3
+ description: |
4
+ Trigger one Jenkins job and durably monitor it until completion.
5
+
6
+ The node persists queue/build checkpoints and never keeps a process blocked while waiting.
7
+ Scheduler re-enters the same node at `pollInterval`; an existing queueId/buildNumber is reused,
8
+ so a resumed run does not trigger the job again. Jenkins FAILURE/ABORTED/TIMEOUT are business
9
+ outcomes and continue to downstream notification nodes. Authentication, configuration, and
10
+ repeated platform request errors fail the AgentFlow node.
11
+
12
+ Credentials are read from environment configuration. `credentialRef: team-ci` selects
13
+ `JENKINS_TEAM_CI_BASE_URL`, `JENKINS_TEAM_CI_USERNAME`, and `JENKINS_TEAM_CI_TOKEN`, falling
14
+ back to the standard `JENKINS_BASE_URL`, `JENKINS_USERNAME`, and `JENKINS_TOKEN` variables.
15
+ displayName: Jenkins Build
16
+ input:
17
+ - type: node
18
+ name: prev
19
+ default: ""
20
+ - type: text
21
+ name: job
22
+ default: ""
23
+ required: true
24
+ showOnNode: true
25
+ - type: text
26
+ name: parameters
27
+ default: "{}"
28
+ description: "Jenkins Job 参数 JSON object。"
29
+ showOnNode: true
30
+ - type: text
31
+ name: credentialRef
32
+ default: ""
33
+ description: "Project Deployment 中配置的 Jenkins 凭证引用;不在 flow.yaml 中填写 token。"
34
+ showOnNode: false
35
+ - type: text
36
+ name: pollInterval
37
+ default: "30s"
38
+ showOnNode: false
39
+ - type: text
40
+ name: timeout
41
+ default: "2h"
42
+ showOnNode: false
43
+ output:
44
+ - type: node
45
+ name: next
46
+ default: ""
47
+ - type: text
48
+ name: status
49
+ default: ""
50
+ required: true
51
+ showOnNode: true
52
+ - type: text
53
+ name: url
54
+ default: ""
55
+ required: true
56
+ description: "优先返回安装包 URL;没有安装包时返回 Jenkins Build URL。"
57
+ showOnNode: true
58
+ - type: text
59
+ name: qrUrl
60
+ default: ""
61
+ description: "解析到二维码时返回,否则为空。"
62
+ showOnNode: true
63
+ ---
64
+ Trigger Jenkins job `${job}` with `${parameters}`, wait durably, then output `${status}`, `${url}`, and optional `${qrUrl}`.
@@ -0,0 +1,217 @@
1
+ instances:
2
+ start:
3
+ definitionId: control_start
4
+ label: Start
5
+ role: normal
6
+ input: []
7
+ output:
8
+ - type: node
9
+ name: next
10
+ value: ""
11
+ body: ""
12
+ job:
13
+ definitionId: provide_str
14
+ label: Jenkins Job
15
+ role: normal
16
+ input: []
17
+ output:
18
+ - type: text
19
+ name: value
20
+ value: ""
21
+ body: ""
22
+ parameters:
23
+ definitionId: provide_str
24
+ label: Build Parameters
25
+ role: normal
26
+ input: []
27
+ output:
28
+ - type: text
29
+ name: value
30
+ value: "{}"
31
+ body: ""
32
+ credential_ref:
33
+ definitionId: provide_str
34
+ label: Credential Ref
35
+ role: normal
36
+ input: []
37
+ output:
38
+ - type: text
39
+ name: value
40
+ value: ""
41
+ body: ""
42
+ webhook_key:
43
+ definitionId: provide_str
44
+ label: WeCom Webhook Key
45
+ role: normal
46
+ input: []
47
+ output:
48
+ - type: text
49
+ name: value
50
+ value: ""
51
+ body: ""
52
+ jenkins_build:
53
+ definitionId: tool_jenkins_build
54
+ label: Jenkins Build
55
+ role: normal
56
+ input:
57
+ - type: node
58
+ name: prev
59
+ value: ""
60
+ - type: text
61
+ name: job
62
+ value: ""
63
+ - type: text
64
+ name: parameters
65
+ value: "{}"
66
+ - type: text
67
+ name: credentialRef
68
+ value: ""
69
+ - type: text
70
+ name: pollInterval
71
+ value: "30s"
72
+ - type: text
73
+ name: timeout
74
+ value: "2h"
75
+ output:
76
+ - type: node
77
+ name: next
78
+ value: ""
79
+ - type: text
80
+ name: status
81
+ value: ""
82
+ - type: text
83
+ name: url
84
+ value: ""
85
+ - type: text
86
+ name: qrUrl
87
+ value: ""
88
+ body: ""
89
+ format_notification:
90
+ definitionId: tool_nodejs
91
+ label: Format Build Notification
92
+ role: normal
93
+ input:
94
+ - type: node
95
+ name: prev
96
+ value: ""
97
+ - type: text
98
+ name: status
99
+ value: ""
100
+ - type: text
101
+ name: url
102
+ value: ""
103
+ - type: text
104
+ name: qrUrl
105
+ value: ""
106
+ output:
107
+ - type: node
108
+ name: next
109
+ value: ""
110
+ - type: text
111
+ name: markdown
112
+ value: ""
113
+ script: >-
114
+ node -e "const fs=require('fs');const [status,url,qr,out]=process.argv.slice(1);const lines=['## Jenkins 构建结果','> 状态:'+status];if(url)lines.push('[查看构建或产物]('+url+')');if(qr)lines.push('[查看二维码]('+qr+')');fs.writeFileSync(out,lines.join('\n\n'));" ${status} ${url} ${qrUrl} ${markdown}
115
+ body: ""
116
+ notify:
117
+ definitionId: tool_wecom_send_group_markdown
118
+ label: WeCom Notification
119
+ role: normal
120
+ input:
121
+ - type: node
122
+ name: prev
123
+ value: ""
124
+ - type: text
125
+ name: markdown
126
+ value: ""
127
+ - type: text
128
+ name: webhookUrl
129
+ value: ""
130
+ - type: text
131
+ name: webhookKey
132
+ value: ""
133
+ output:
134
+ - type: node
135
+ name: next
136
+ value: ""
137
+ - type: bool
138
+ name: sent
139
+ value: ""
140
+ - type: text
141
+ name: message
142
+ value: ""
143
+ - type: text
144
+ name: response
145
+ value: ""
146
+ body: ""
147
+ end:
148
+ definitionId: control_end
149
+ label: End
150
+ role: normal
151
+ input:
152
+ - type: node
153
+ name: prev
154
+ value: ""
155
+ output: []
156
+ body: ""
157
+ edges:
158
+ - source: start
159
+ target: jenkins_build
160
+ sourceHandle: output-0
161
+ targetHandle: input-0
162
+ - source: job
163
+ target: jenkins_build
164
+ sourceHandle: output-0
165
+ targetHandle: input-1
166
+ - source: parameters
167
+ target: jenkins_build
168
+ sourceHandle: output-0
169
+ targetHandle: input-2
170
+ - source: credential_ref
171
+ target: jenkins_build
172
+ sourceHandle: output-0
173
+ targetHandle: input-3
174
+ - source: jenkins_build
175
+ target: format_notification
176
+ sourceHandle: output-0
177
+ targetHandle: input-0
178
+ - source: jenkins_build
179
+ target: format_notification
180
+ sourceHandle: output-1
181
+ targetHandle: input-1
182
+ - source: jenkins_build
183
+ target: format_notification
184
+ sourceHandle: output-2
185
+ targetHandle: input-2
186
+ - source: jenkins_build
187
+ target: format_notification
188
+ sourceHandle: output-3
189
+ targetHandle: input-3
190
+ - source: format_notification
191
+ target: notify
192
+ sourceHandle: output-0
193
+ targetHandle: input-0
194
+ - source: format_notification
195
+ target: notify
196
+ sourceHandle: output-1
197
+ targetHandle: input-1
198
+ - source: webhook_key
199
+ target: notify
200
+ sourceHandle: output-0
201
+ targetHandle: input-3
202
+ - source: notify
203
+ target: end
204
+ sourceHandle: output-0
205
+ targetHandle: input-0
206
+ ui:
207
+ nodePositions:
208
+ start: { x: -460, y: 60 }
209
+ job: { x: -450, y: 180 }
210
+ parameters: { x: -450, y: 300 }
211
+ credential_ref: { x: -450, y: 420 }
212
+ webhook_key: { x: 350, y: 360 }
213
+ jenkins_build: { x: -120, y: 100 }
214
+ format_notification: { x: 210, y: 100 }
215
+ notify: { x: 530, y: 100 }
216
+ end: { x: 830, y: 100 }
217
+ description: Jenkins 构建完成后发送企业微信通知