@fieldwangai/agentflow 0.1.138 → 0.1.142
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/bin/lib/catalog-flows.mjs +17 -3
- package/bin/lib/composer-node-schema.mjs +4 -0
- package/bin/lib/i18n.mjs +2 -2
- package/bin/lib/jenkins.mjs +380 -0
- package/bin/lib/locales/en.json +22 -0
- package/bin/lib/locales/zh.json +22 -0
- package/bin/lib/paths.mjs +1 -0
- package/bin/lib/prd-workflow-collaboration.mjs +93 -1
- package/bin/lib/recent-runs.mjs +16 -0
- package/bin/lib/run-node-statuses-from-disk.mjs +41 -3
- package/bin/lib/scheduler.mjs +59 -25
- package/bin/lib/ui-server.mjs +996 -73
- package/bin/lib/workflow-report.mjs +127 -1
- package/bin/pipeline/pre-process-node.mjs +122 -11
- package/bin/pipeline/run-log.mjs +2 -2
- package/bin/pipeline/write-result.mjs +4 -4
- package/builtin/nodes/tool_jenkins_build.md +64 -0
- package/builtin/web-ui/dist/assets/{WorkflowAssistantThread-B3thaqH2.js → WorkflowAssistantThread-DGaXikJ9.js} +1 -1
- package/builtin/web-ui/dist/assets/index-CtG65J3e.css +1 -0
- package/builtin/web-ui/dist/assets/index-pLsH0Uyj.js +835 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/agentflow-workflow-report/SKILL.md +9 -1
- package/skills/agentflow-workflow-report/references/protocol.md +137 -12
- package/builtin/web-ui/dist/assets/index-COX1zMwq.css +0 -1
- package/builtin/web-ui/dist/assets/index-YS4XOpXF.js +0 -590
|
@@ -11,6 +11,8 @@ const WORKFLOW_ACTION_STATUSES = new Set([
|
|
|
11
11
|
"cancelled",
|
|
12
12
|
"observed",
|
|
13
13
|
]);
|
|
14
|
+
const WORKFLOW_CHECKLIST_COMPLETION_POLICIES = new Set(["all_required", "any_required", "manual"]);
|
|
15
|
+
const WORKFLOW_CHECKLIST_ITEM_STATUSES = new Set(["pending", "passed", "failed", "blocked", "skipped"]);
|
|
14
16
|
const UNSAFE_OBJECT_KEYS = new Set(["__proto__", "prototype", "constructor"]);
|
|
15
17
|
|
|
16
18
|
function plainObject(value) {
|
|
@@ -109,6 +111,98 @@ function normalizeStringList(value, maxItems = 100) {
|
|
|
109
111
|
return uniqueValues(list.map((item) => cleanString(item, 240)).filter(Boolean)).slice(0, maxItems);
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
function normalizeChecklistSection(value, index = 0) {
|
|
115
|
+
const raw = plainObject(value);
|
|
116
|
+
const rawContent = raw.content ?? raw.value ?? raw.text ?? "";
|
|
117
|
+
const content = Array.isArray(rawContent)
|
|
118
|
+
? rawContent.map((item) => cleanString(item, 4000)).filter(Boolean).slice(0, 100)
|
|
119
|
+
: cleanString(rawContent, 12000);
|
|
120
|
+
return {
|
|
121
|
+
key: cleanString(raw.key || raw.id || `section-${index + 1}`, 120),
|
|
122
|
+
title: cleanString(raw.title || raw.label || `Section ${index + 1}`, 500),
|
|
123
|
+
content,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeWorkflowChecklist(value) {
|
|
128
|
+
const raw = plainObject(value);
|
|
129
|
+
const rawDocument = plainObject(raw.document);
|
|
130
|
+
const items = (Array.isArray(raw.items) ? raw.items : []).map((value, index) => {
|
|
131
|
+
const item = plainObject(value);
|
|
132
|
+
const rawDetail = item.detail;
|
|
133
|
+
const detailObject = plainObject(rawDetail);
|
|
134
|
+
const sections = Array.isArray(detailObject.sections)
|
|
135
|
+
? detailObject.sections.map((section, sectionIndex) => normalizeChecklistSection(section, sectionIndex))
|
|
136
|
+
: [];
|
|
137
|
+
const summary = typeof rawDetail === "string"
|
|
138
|
+
? cleanString(rawDetail, 4000)
|
|
139
|
+
: cleanString(detailObject.summary || detailObject.description, 4000);
|
|
140
|
+
return {
|
|
141
|
+
key: cleanString(item.key || item.id, 240),
|
|
142
|
+
title: cleanString(item.title || item.label || item.key || item.id || `Item ${index + 1}`, 500),
|
|
143
|
+
required: item.required !== false,
|
|
144
|
+
...(summary || sections.length ? { detail: { ...(summary ? { summary } : {}), ...(sections.length ? { sections } : {}) } } : {}),
|
|
145
|
+
...(item.evidenceRequired === true || item.evidence_required === true ? { evidenceRequired: true } : {}),
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
const completionPolicy = cleanString(raw.completionPolicy || raw.completion_policy || "all_required", 40).toLowerCase();
|
|
149
|
+
const documentUrl = cleanString(rawDocument.url || rawDocument.href, 4000);
|
|
150
|
+
return {
|
|
151
|
+
schemaVersion: 1,
|
|
152
|
+
completionPolicy,
|
|
153
|
+
document: {
|
|
154
|
+
title: cleanString(rawDocument.title || rawDocument.label || "Checklist 详情", 500),
|
|
155
|
+
...(rawDocument.artifactKey || rawDocument.artifact_key ? { artifactKey: cleanString(rawDocument.artifactKey || rawDocument.artifact_key, 500) } : {}),
|
|
156
|
+
...(documentUrl ? { url: documentUrl } : {}),
|
|
157
|
+
},
|
|
158
|
+
items,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function validateWorkflowChecklist(value) {
|
|
163
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return "action.checklist must be an object";
|
|
164
|
+
const raw = plainObject(value);
|
|
165
|
+
const schemaVersion = Number(raw.schemaVersion ?? raw.schema_version ?? 1);
|
|
166
|
+
if (schemaVersion !== 1) return `Unsupported action.checklist schemaVersion: ${schemaVersion}`;
|
|
167
|
+
const completionPolicy = rawString(raw.completionPolicy || raw.completion_policy || "all_required").toLowerCase();
|
|
168
|
+
if (!WORKFLOW_CHECKLIST_COMPLETION_POLICIES.has(completionPolicy)) {
|
|
169
|
+
return `Invalid action.checklist completionPolicy: ${completionPolicy}`;
|
|
170
|
+
}
|
|
171
|
+
if (!Array.isArray(raw.items) || raw.items.length === 0) return "action.checklist requires at least one item";
|
|
172
|
+
if (raw.items.length > 100) return "action.checklist supports at most 100 items";
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
for (let index = 0; index < raw.items.length; index += 1) {
|
|
175
|
+
const item = raw.items[index];
|
|
176
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return `action.checklist.items[${index}] must be an object`;
|
|
177
|
+
const key = rawString(item.key || item.id);
|
|
178
|
+
if (!key || key.length > 240 || /[\0\r\n]/.test(key)) return `action.checklist.items[${index}].key is invalid`;
|
|
179
|
+
if (seen.has(key)) return `action.checklist.items[${index}].key must be unique`;
|
|
180
|
+
seen.add(key);
|
|
181
|
+
if (stringExceeds(item.title || item.label || key, 500)) return `action.checklist.items[${index}].title exceeds 500 characters`;
|
|
182
|
+
if (item.detail != null && typeof item.detail !== "string" && (!item.detail || typeof item.detail !== "object" || Array.isArray(item.detail))) {
|
|
183
|
+
return `action.checklist.items[${index}].detail must be a string or object`;
|
|
184
|
+
}
|
|
185
|
+
const detail = plainObject(item.detail);
|
|
186
|
+
if (typeof item.detail === "string" && stringExceeds(item.detail, 4000)) return `action.checklist.items[${index}].detail exceeds 4000 characters`;
|
|
187
|
+
if (stringExceeds(detail.summary || detail.description, 4000)) return `action.checklist.items[${index}].detail.summary exceeds 4000 characters`;
|
|
188
|
+
if (detail.sections != null && !Array.isArray(detail.sections)) return `action.checklist.items[${index}].detail.sections must be an array`;
|
|
189
|
+
if (Array.isArray(detail.sections) && detail.sections.length > 50) return `action.checklist.items[${index}].detail.sections supports at most 50 entries`;
|
|
190
|
+
}
|
|
191
|
+
const document = plainObject(raw.document);
|
|
192
|
+
if (stringExceeds(document.title || document.label, 500)) return "action.checklist.document.title exceeds 500 characters";
|
|
193
|
+
if (stringExceeds(document.artifactKey || document.artifact_key, 500)) return "action.checklist.document.artifactKey exceeds 500 characters";
|
|
194
|
+
const documentUrl = rawString(document.url || document.href);
|
|
195
|
+
if (documentUrl && !isSafeWorkflowUrl(documentUrl)) return "action.checklist.document.url must use http, https, or an absolute application path";
|
|
196
|
+
return "";
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function normalizeWorkflowChecklistItemStatus(value) {
|
|
200
|
+
const raw = cleanString(value, 40).toLowerCase();
|
|
201
|
+
const aliases = { done: "passed", complete: "passed", completed: "passed", success: "passed", error: "failed", cancelled: "skipped", canceled: "skipped" };
|
|
202
|
+
const normalized = aliases[raw] || raw || "pending";
|
|
203
|
+
return WORKFLOW_CHECKLIST_ITEM_STATUSES.has(normalized) ? normalized : "pending";
|
|
204
|
+
}
|
|
205
|
+
|
|
112
206
|
function normalizeWorkflowArtifact(value, index = 0, defaultScope = "action") {
|
|
113
207
|
const raw = plainObject(value);
|
|
114
208
|
const url = cleanString(raw.url || raw.href, 4000);
|
|
@@ -347,6 +441,11 @@ export function normalizeWorkflowReport(payload = {}) {
|
|
|
347
441
|
if (stringExceeds(rawAction.issueKey || rawAction.issue_key, 240)) return { error: "Workflow action issueKey exceeds 240 characters" };
|
|
348
442
|
const rawTags = Array.isArray(rawAction.tags) ? rawAction.tags : rawAction.tags == null ? [] : [rawAction.tags];
|
|
349
443
|
if (rawTags.length > 100 || rawTags.some((tag) => stringExceeds(tag, 240))) return { error: "Workflow action tags exceed supported limits" };
|
|
444
|
+
const hasChecklist = hasOwn(rawAction, "checklist");
|
|
445
|
+
if (hasChecklist) {
|
|
446
|
+
const checklistError = validateWorkflowChecklist(rawAction.checklist);
|
|
447
|
+
if (checklistError) return { error: checklistError };
|
|
448
|
+
}
|
|
350
449
|
if (hasAction && !isKnownActionStatus(rawAction.status)) return { error: `Invalid workflow action status: ${rawAction.status}` };
|
|
351
450
|
const rawOccurredAt = rawString(rawAction.occurredAt || rawAction.occurred_at || rawAction.completedAt || rawAction.startedAt);
|
|
352
451
|
if (rawOccurredAt && !Number.isFinite(Date.parse(rawOccurredAt))) return { error: "action.occurredAt must be an ISO-compatible date" };
|
|
@@ -361,6 +460,7 @@ export function normalizeWorkflowReport(payload = {}) {
|
|
|
361
460
|
...(rawAction.platform ? { platform: cleanString(rawAction.platform, 80) } : {}),
|
|
362
461
|
...(rawAction.issueKey || rawAction.issue_key ? { issueKey: cleanString(rawAction.issueKey || rawAction.issue_key, 240) } : {}),
|
|
363
462
|
...(rawAction.tags != null ? { tags: normalizeStringList(rawAction.tags) } : {}),
|
|
463
|
+
...(hasChecklist ? { checklist: normalizeWorkflowChecklist(rawAction.checklist) } : {}),
|
|
364
464
|
occurredAt: cleanString(
|
|
365
465
|
rawAction.occurredAt ||
|
|
366
466
|
rawAction.occurred_at ||
|
|
@@ -539,6 +639,7 @@ export function normalizeWorkflowReport(payload = {}) {
|
|
|
539
639
|
...(action.platform ? { platform: action.platform } : {}),
|
|
540
640
|
...(action.issueKey ? { issueKey: action.issueKey } : {}),
|
|
541
641
|
...(action.tags ? { tags: action.tags } : {}),
|
|
642
|
+
...(action.checklist ? { checklist: action.checklist } : {}),
|
|
542
643
|
...(action.occurredAt ? { occurredAt: action.occurredAt } : {}),
|
|
543
644
|
} : {
|
|
544
645
|
title: cleanString(payload.title || "Workflow 全局状态更新", 500),
|
|
@@ -600,6 +701,24 @@ function resourceVersion(value) {
|
|
|
600
701
|
return `rv:${semanticHash(value)}`;
|
|
601
702
|
}
|
|
602
703
|
|
|
704
|
+
function actionDefinitionForVersion(event = {}) {
|
|
705
|
+
const action = plainObject(event.actionModel);
|
|
706
|
+
if (!Object.keys(action).length) return event;
|
|
707
|
+
const checklist = plainObject(action.checklist);
|
|
708
|
+
if (!Object.keys(checklist).length) return action;
|
|
709
|
+
const items = Array.isArray(checklist.items)
|
|
710
|
+
? checklist.items.map((item) => {
|
|
711
|
+
const clean = { ...plainObject(item) };
|
|
712
|
+
delete clean.state;
|
|
713
|
+
return clean;
|
|
714
|
+
})
|
|
715
|
+
: [];
|
|
716
|
+
const cleanChecklist = { ...checklist, items };
|
|
717
|
+
delete cleanChecklist.progress;
|
|
718
|
+
delete cleanChecklist.source;
|
|
719
|
+
return { ...action, checklist: cleanChecklist };
|
|
720
|
+
}
|
|
721
|
+
|
|
603
722
|
function addObjectResourceVersions(out, prefix, value, path = []) {
|
|
604
723
|
if (value === undefined) return;
|
|
605
724
|
if (path.length) out[`${prefix}:${path.join(".")}`] = resourceVersion(value);
|
|
@@ -617,8 +736,15 @@ export function workflowSnapshotResourceVersions(snapshot = {}) {
|
|
|
617
736
|
: [];
|
|
618
737
|
for (const event of runtimeEvents) {
|
|
619
738
|
const source = cleanString(event?.source || event?.producer || "agentflow", 120).toLowerCase() || "agentflow";
|
|
739
|
+
const checklistState = plainObject(event?.checklistState || event?.checklist_state);
|
|
740
|
+
const checklistSource = cleanString(checklistState.producer || checklistState.source, 120).toLowerCase();
|
|
741
|
+
const checklistActionKey = cleanString(checklistState.actionKey || checklistState.action_key, 240);
|
|
742
|
+
const checklistItemKey = cleanString(checklistState.itemKey || checklistState.item_key, 240);
|
|
743
|
+
if (checklistSource && checklistActionKey && checklistItemKey) {
|
|
744
|
+
out[`checklist:${checklistSource}:${checklistActionKey}:${checklistItemKey}`] = resourceVersion(checklistState);
|
|
745
|
+
}
|
|
620
746
|
const actionKey = cleanString(event?.actionModel?.key || event?.action || event?.actionId || event?.stageKey, 240);
|
|
621
|
-
if (actionKey && event?.auxiliary !== true) out[`action:${source}:${actionKey}`] = resourceVersion(event);
|
|
747
|
+
if (actionKey && event?.auxiliary !== true) out[`action:${source}:${actionKey}`] = resourceVersion(actionDefinitionForVersion(event));
|
|
622
748
|
for (const artifact of Array.isArray(event?.artifacts) ? event.artifacts : []) {
|
|
623
749
|
const producer = cleanString(artifact?.producer || source, 120).toLowerCase() || source;
|
|
624
750
|
const key = cleanString(artifact?.key || artifact?.artifactKey || artifact?.artifact_key, 500);
|
|
@@ -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
|
|
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) {
|
package/bin/pipeline/run-log.mjs
CHANGED
|
@@ -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}`.
|