@fieldwangai/agentflow 0.1.72 → 0.1.74
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/ui-server.mjs +486 -48
- package/builtin/web-ui/dist/assets/index-Z19bIbFa.js +297 -0
- package/builtin/web-ui/dist/assets/index-fQlSZjeW.css +1 -0
- package/builtin/web-ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/builtin/web-ui/dist/assets/index-NH_rVAzF.css +0 -1
- package/builtin/web-ui/dist/assets/index-t_zkIiXD.js +0 -296
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
readComposerSessionMeta,
|
|
82
82
|
} from "./composer-log.mjs";
|
|
83
83
|
import { runNodeScript } from "./pipeline-scripts.mjs";
|
|
84
|
-
import { readFlowSchedule, writeFlowSchedule } from "./schedule-config.mjs";
|
|
84
|
+
import { computeNextRunAt, readFlowSchedule, writeFlowSchedule } from "./schedule-config.mjs";
|
|
85
85
|
import { listScheduleStatuses } from "./scheduler.mjs";
|
|
86
86
|
import {
|
|
87
87
|
deleteMarketplaceFlowSnippetPackage,
|
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
listMarketplacePackages,
|
|
92
92
|
publishFlowSnippet,
|
|
93
93
|
publishNodeFromInstance,
|
|
94
|
+
resolveMarketplaceNodePackage,
|
|
94
95
|
} from "./marketplace.mjs";
|
|
95
96
|
import { buildGitContext, inferGitRepoRootFromWorktree, loadGitWorktree, normalizeGitContext, runGit, sanitizeWorktreeName, unloadGitWorktree } from "./git-worktree.mjs";
|
|
96
97
|
import { createGitLabMergeRequest } from "./gitlab-mr.mjs";
|
|
@@ -2173,9 +2174,76 @@ function hydrateWorkspaceSlotMetaFromDefinitions(workspaceRoot, scoped = {}, gra
|
|
|
2173
2174
|
return changed ? { ...next, instances } : next;
|
|
2174
2175
|
}
|
|
2175
2176
|
|
|
2177
|
+
function workspaceRuntimeInterpreterForMarketplaceEntry(runtime, entry) {
|
|
2178
|
+
const language = String(runtime?.language || "").trim().toLowerCase();
|
|
2179
|
+
const entryLower = String(entry || "").trim().toLowerCase();
|
|
2180
|
+
if (language.includes("python") || entryLower.endsWith(".py")) return "python3";
|
|
2181
|
+
if (language.includes("shell") || language === "bash" || entryLower.endsWith(".sh") || entryLower.endsWith(".bash")) return "bash";
|
|
2182
|
+
return "node";
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
function workspaceRuntimeArgForMarketplace(arg) {
|
|
2186
|
+
const text = String(arg ?? "").trim();
|
|
2187
|
+
if (!text) return "";
|
|
2188
|
+
if (text.includes("${")) return text;
|
|
2189
|
+
return workspaceShellQuote(text);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
function workspaceMarketplaceRuntimeCommand(resolved) {
|
|
2193
|
+
const runtime = resolved?.runtime && typeof resolved.runtime === "object" ? resolved.runtime : {};
|
|
2194
|
+
const entry = String(runtime.entry || "").trim().replace(/^\/+/, "");
|
|
2195
|
+
if (entry && resolved?.packageDir) {
|
|
2196
|
+
const entryAbs = path.resolve(resolved.packageDir, ...entry.split(/[\\/]+/).filter(Boolean));
|
|
2197
|
+
const packageRoot = path.resolve(resolved.packageDir);
|
|
2198
|
+
const packageRootWithSep = packageRoot.endsWith(path.sep) ? packageRoot : `${packageRoot}${path.sep}`;
|
|
2199
|
+
if (entryAbs === packageRoot || !entryAbs.startsWith(packageRootWithSep)) return "";
|
|
2200
|
+
const args = Array.isArray(runtime.args) ? runtime.args.map(workspaceRuntimeArgForMarketplace).filter(Boolean) : [];
|
|
2201
|
+
return [workspaceRuntimeInterpreterForMarketplaceEntry(runtime, entry), workspaceShellQuote(entryAbs), ...args].join(" ");
|
|
2202
|
+
}
|
|
2203
|
+
return String(runtime.command || "").trim();
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function hydrateWorkspaceMarketplaceToolNodejsRuntime(workspaceRoot, scoped = {}, graph = {}, userCtx = {}) {
|
|
2207
|
+
const next = normalizeWorkspaceGraphPayload(graph || {});
|
|
2208
|
+
const instances = { ...(next.instances || {}) };
|
|
2209
|
+
let changed = false;
|
|
2210
|
+
for (const [nodeId, instance] of Object.entries(instances)) {
|
|
2211
|
+
if (!instance || typeof instance !== "object") continue;
|
|
2212
|
+
const marketplaceDefId = String(instance.marketplaceRef || instance.definitionId || "").trim();
|
|
2213
|
+
if (!marketplaceDefId.startsWith("marketplace:")) continue;
|
|
2214
|
+
let resolved = null;
|
|
2215
|
+
try {
|
|
2216
|
+
resolved = resolveMarketplaceNodePackage(
|
|
2217
|
+
workspaceRoot,
|
|
2218
|
+
scoped.root || scoped.scopedRoot || workspaceRoot,
|
|
2219
|
+
marketplaceDefId,
|
|
2220
|
+
next,
|
|
2221
|
+
{ userId: userCtx?.userId || "" },
|
|
2222
|
+
);
|
|
2223
|
+
} catch {
|
|
2224
|
+
resolved = null;
|
|
2225
|
+
}
|
|
2226
|
+
if (!resolved || String(resolved.baseDefinitionId || "").trim() !== "tool_nodejs") continue;
|
|
2227
|
+
const script = String(instance.script || "").trim();
|
|
2228
|
+
const scriptRef = String(instance.scriptRef || "").trim();
|
|
2229
|
+
const runtimeScript = script || scriptRef ? "" : workspaceMarketplaceRuntimeCommand(resolved);
|
|
2230
|
+
instances[nodeId] = {
|
|
2231
|
+
...instance,
|
|
2232
|
+
definitionId: "tool_nodejs",
|
|
2233
|
+
marketplaceRef: resolved.resolvedDefinitionId || marketplaceDefId,
|
|
2234
|
+
marketplacePackageId: resolved.id,
|
|
2235
|
+
marketplaceVersion: resolved.version,
|
|
2236
|
+
...(runtimeScript ? { script: runtimeScript } : {}),
|
|
2237
|
+
};
|
|
2238
|
+
changed = true;
|
|
2239
|
+
}
|
|
2240
|
+
return changed ? { ...next, instances } : next;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2176
2243
|
function hydrateWorkspaceGraphForRuntime(workspaceRoot, scoped = {}, graph = {}, userCtx = {}) {
|
|
2177
2244
|
const withRefs = hydrateWorkspaceNodeRefsFromFiles(scoped.root || scoped.scopedRoot || workspaceRoot, graph);
|
|
2178
|
-
|
|
2245
|
+
const withMarketplaceRuntime = hydrateWorkspaceMarketplaceToolNodejsRuntime(workspaceRoot, scoped, withRefs, userCtx);
|
|
2246
|
+
return hydrateWorkspaceSlotMetaFromDefinitions(workspaceRoot, scoped, withMarketplaceRuntime, userCtx);
|
|
2179
2247
|
}
|
|
2180
2248
|
|
|
2181
2249
|
function resolveWorkspaceScopeRoot(workspaceRoot, params = {}, opts = {}) {
|
|
@@ -3451,6 +3519,7 @@ function workspaceMaterializeImplementationReference(instance, scopedRoot, runPa
|
|
|
3451
3519
|
}
|
|
3452
3520
|
|
|
3453
3521
|
function workspaceImplementationBlock(instance, scopedRoot, runPackage = {}) {
|
|
3522
|
+
if (!WORKSPACE_IMPLEMENTATION_REFERENCE_ENABLED) return "";
|
|
3454
3523
|
const ref = workspaceMaterializeImplementationReference(instance, scopedRoot, runPackage);
|
|
3455
3524
|
const inline = workspaceImplementationInlineText(instance);
|
|
3456
3525
|
if (!ref && !inline) return "";
|
|
@@ -3478,6 +3547,46 @@ function workspaceDefaultImplementationRef(nodeId) {
|
|
|
3478
3547
|
return `nodes/${workspaceSafeNodeFileName(nodeId)}/implementation.md`;
|
|
3479
3548
|
}
|
|
3480
3549
|
|
|
3550
|
+
function workspaceDefaultHistoryRef(nodeId) {
|
|
3551
|
+
return `nodes/${workspaceSafeNodeFileName(nodeId)}/history.md`;
|
|
3552
|
+
}
|
|
3553
|
+
|
|
3554
|
+
function workspaceMaterializeNodeHistoryReference(nodeId, scopedRoot, runPackage = {}) {
|
|
3555
|
+
const historyRef = workspaceDefaultHistoryRef(nodeId);
|
|
3556
|
+
const abs = workspaceResolveFlowFile(scopedRoot, historyRef, "historyRef");
|
|
3557
|
+
const exists = fs.existsSync(abs) && fs.statSync(abs).isFile();
|
|
3558
|
+
const nodeRunDir = String(runPackage?.nodeRunDir || "").trim();
|
|
3559
|
+
let mounted = "";
|
|
3560
|
+
if (exists && nodeRunDir) {
|
|
3561
|
+
try {
|
|
3562
|
+
const mountedRel = path.join("references", "history.md");
|
|
3563
|
+
const dest = path.resolve(nodeRunDir, mountedRel);
|
|
3564
|
+
const nodeRunWithSep = nodeRunDir.endsWith(path.sep) ? nodeRunDir : `${nodeRunDir}${path.sep}`;
|
|
3565
|
+
if (dest === nodeRunDir || dest.startsWith(nodeRunWithSep)) {
|
|
3566
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
3567
|
+
fs.copyFileSync(abs, dest);
|
|
3568
|
+
mounted = mountedRel.split(path.sep).join(path.posix.sep);
|
|
3569
|
+
}
|
|
3570
|
+
} catch {
|
|
3571
|
+
mounted = "";
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
return { historyRef, exists, mounted };
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
function workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage = {}) {
|
|
3578
|
+
const ref = workspaceMaterializeNodeHistoryReference(nodeId, scopedRoot, runPackage);
|
|
3579
|
+
if (!ref?.exists) return "";
|
|
3580
|
+
return [
|
|
3581
|
+
"## 历史参考",
|
|
3582
|
+
"",
|
|
3583
|
+
`- 历史记录文件:\`${ref.mounted || ref.historyRef}\``,
|
|
3584
|
+
ref.mounted ? `- 原始流水线路径:\`${ref.historyRef}\`` : "",
|
|
3585
|
+
"",
|
|
3586
|
+
"该文件只记录之前运行时的 thinking 摘要与最终结论,用作轻量参考;不要把历史当成硬约束。若历史与当前任务、输入或输出要求冲突,以当前任务为准。",
|
|
3587
|
+
].filter((line) => line !== "").join("\n");
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3481
3590
|
function workspaceImplementationModeForInstance(instance) {
|
|
3482
3591
|
const explicit = String(instance?.implementationMode || "").trim();
|
|
3483
3592
|
if (explicit) return explicit;
|
|
@@ -3669,11 +3778,96 @@ async function workspaceGenerateImplementationMarkdown({
|
|
|
3669
3778
|
return markdown.replace(/^```(?:markdown|md)?\s*/i, "").replace(/```\s*$/i, "").trim();
|
|
3670
3779
|
}
|
|
3671
3780
|
|
|
3781
|
+
function workspaceHistoryTextFromEvents(events = [], kind, maxItems = 24, maxChars = 12000) {
|
|
3782
|
+
const parts = [];
|
|
3783
|
+
for (const ev of Array.isArray(events) ? events : []) {
|
|
3784
|
+
if (!ev || ev.kind !== kind) continue;
|
|
3785
|
+
const text = String(ev.text || "").trim();
|
|
3786
|
+
if (!text) continue;
|
|
3787
|
+
parts.push(text);
|
|
3788
|
+
if (parts.length >= maxItems) break;
|
|
3789
|
+
}
|
|
3790
|
+
return workspaceClipImplementationText(parts.join("\n"), maxChars);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
function workspaceBuildNodeHistoryEntry(instance, nodeId, opts = {}) {
|
|
3794
|
+
const inputValues = opts.inputValues || {};
|
|
3795
|
+
const structured = opts.structured && typeof opts.structured === "object" ? opts.structured : {};
|
|
3796
|
+
const result = String(opts.resultContent || structured.result || "").trim();
|
|
3797
|
+
const task = workspaceResolveBodyPlaceholders(instance?.body || "", inputValues).trim();
|
|
3798
|
+
const thinking = workspaceHistoryTextFromEvents(opts.historyEvents || [], "thinking", 40, 16000);
|
|
3799
|
+
const assistant = workspaceHistoryTextFromEvents(opts.historyEvents || [], "assistant", 8, 8000);
|
|
3800
|
+
const resultEvent = workspaceHistoryTextFromEvents(opts.historyEvents || [], "result", 4, 12000);
|
|
3801
|
+
const conclusion = workspaceClipImplementationText(resultEvent || result || assistant, 20000);
|
|
3802
|
+
return [
|
|
3803
|
+
`## ${new Date().toISOString()} · ${nodeId}`,
|
|
3804
|
+
"",
|
|
3805
|
+
`label: ${String(instance?.label || nodeId || "node")}`,
|
|
3806
|
+
`definitionId: ${String(instance?.definitionId || "(unknown)")}`,
|
|
3807
|
+
"",
|
|
3808
|
+
"### 任务",
|
|
3809
|
+
"",
|
|
3810
|
+
workspaceClipImplementationText(task || instance?.body || instance?.scriptRef || instance?.script || "(无显式任务)", 6000),
|
|
3811
|
+
"",
|
|
3812
|
+
Object.keys(inputValues || {}).length ? "### 输入摘要" : "",
|
|
3813
|
+
Object.keys(inputValues || {}).length ? "" : "",
|
|
3814
|
+
Object.keys(inputValues || {}).length ? workspaceClipImplementationText(JSON.stringify(inputValues, null, 2), 8000) : "",
|
|
3815
|
+
Object.keys(inputValues || {}).length ? "" : "",
|
|
3816
|
+
thinking ? "### Thinking 摘要" : "",
|
|
3817
|
+
thinking ? "" : "",
|
|
3818
|
+
thinking || "",
|
|
3819
|
+
thinking ? "" : "",
|
|
3820
|
+
"### 结论",
|
|
3821
|
+
"",
|
|
3822
|
+
conclusion || "(无结论内容)",
|
|
3823
|
+
"",
|
|
3824
|
+
"### 输出协议",
|
|
3825
|
+
"",
|
|
3826
|
+
JSON.stringify({
|
|
3827
|
+
result: workspaceClipImplementationText(structured.result || result, 4000),
|
|
3828
|
+
resultFile: structured.resultFile || "",
|
|
3829
|
+
outParams: structured.outParams || {},
|
|
3830
|
+
}, null, 2),
|
|
3831
|
+
].filter((line) => line !== "").join("\n");
|
|
3832
|
+
}
|
|
3833
|
+
|
|
3834
|
+
function workspaceClipNodeHistory(value, maxChars = WORKSPACE_NODE_HISTORY_MAX_CHARS) {
|
|
3835
|
+
const text = String(value || "").trim();
|
|
3836
|
+
if (text.length <= maxChars) return text;
|
|
3837
|
+
return [
|
|
3838
|
+
"# Workspace Node History",
|
|
3839
|
+
"",
|
|
3840
|
+
"> Older history was truncated to keep this reference lightweight.",
|
|
3841
|
+
"",
|
|
3842
|
+
text.slice(-maxChars),
|
|
3843
|
+
].join("\n").trim();
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
function workspacePersistNodeHistory(scopedRoot, graph, nodeId, opts = {}) {
|
|
3847
|
+
const current = graph?.instances?.[nodeId];
|
|
3848
|
+
if (!current || String(current.definitionId || "") === "workspace_run" || String(current.definitionId || "") === "workspace_scheduled_run") {
|
|
3849
|
+
return { changed: false, wrote: false, instance: current };
|
|
3850
|
+
}
|
|
3851
|
+
const historyRef = workspaceDefaultHistoryRef(nodeId);
|
|
3852
|
+
const abs = workspaceResolveFlowFile(scopedRoot, historyRef, "historyRef");
|
|
3853
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
3854
|
+
const previous = fs.existsSync(abs) && fs.statSync(abs).isFile()
|
|
3855
|
+
? workspaceReadTextFileIfExists(abs, WORKSPACE_NODE_HISTORY_MAX_CHARS + 20000)
|
|
3856
|
+
: "# Workspace Node History\n";
|
|
3857
|
+
const entry = workspaceBuildNodeHistoryEntry(current, nodeId, opts);
|
|
3858
|
+
const nextText = workspaceClipNodeHistory(`${previous.trimEnd()}\n\n${entry}\n`);
|
|
3859
|
+
fs.writeFileSync(abs, `${nextText.trimEnd()}\n`, "utf-8");
|
|
3860
|
+
return { changed: false, wrote: true, instance: current, historyRef };
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3672
3863
|
async function workspacePersistNodeImplementation(scopedRoot, graph, nodeId, opts = {}) {
|
|
3673
3864
|
const current = graph?.instances?.[nodeId];
|
|
3674
3865
|
if (!current || String(current.definitionId || "") === "workspace_run" || String(current.definitionId || "") === "workspace_scheduled_run") {
|
|
3675
3866
|
return { changed: false, wrote: false, instance: current };
|
|
3676
3867
|
}
|
|
3868
|
+
if (!WORKSPACE_IMPLEMENTATION_SUMMARY_ENABLED) {
|
|
3869
|
+
return workspacePersistNodeHistory(scopedRoot, graph, nodeId, opts);
|
|
3870
|
+
}
|
|
3677
3871
|
const existingRef = String(current.implementationRef || "").trim();
|
|
3678
3872
|
const implementationRef = existingRef || workspaceDefaultImplementationRef(nodeId);
|
|
3679
3873
|
const abs = workspaceResolveFlowFile(scopedRoot, implementationRef, "implementationRef");
|
|
@@ -3715,7 +3909,9 @@ async function workspaceTryPersistNodeImplementation(scopedRoot, graph, nodeId,
|
|
|
3715
3909
|
opts.emit?.({
|
|
3716
3910
|
type: "natural",
|
|
3717
3911
|
kind: "warning",
|
|
3718
|
-
text:
|
|
3912
|
+
text: WORKSPACE_IMPLEMENTATION_SUMMARY_ENABLED
|
|
3913
|
+
? `实现方案未更新:${e?.message || String(e)}`
|
|
3914
|
+
: `历史记录未更新:${e?.message || String(e)}`,
|
|
3719
3915
|
});
|
|
3720
3916
|
return { changed: false, wrote: false, instance: graph?.instances?.[nodeId] };
|
|
3721
3917
|
}
|
|
@@ -4747,8 +4943,8 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4747
4943
|
} catch {
|
|
4748
4944
|
// Best-effort debug artifact only.
|
|
4749
4945
|
}
|
|
4750
|
-
const
|
|
4751
|
-
const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage,
|
|
4946
|
+
const historyBlock = workspaceNodeHistoryBlock(nodeId, scopedRoot, runPackage);
|
|
4947
|
+
const prompt = workspaceNodePrompt(graph, nodeId, promptUpstreamText, promptSkillsBlock, promptMcpBlock, runtimeInputValues, runPackage, historyBlock);
|
|
4752
4948
|
try {
|
|
4753
4949
|
fs.writeFileSync(path.join(runPackage.nodeRunDir, "prompt.md"), prompt.trimEnd() + "\n", "utf-8");
|
|
4754
4950
|
} catch {
|
|
@@ -4763,6 +4959,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4763
4959
|
});
|
|
4764
4960
|
emit({ type: "natural", kind: "prompt", nodeId, text: prompt });
|
|
4765
4961
|
let content = "";
|
|
4962
|
+
const runHistoryEvents = [];
|
|
4766
4963
|
const maxAttempts = 3;
|
|
4767
4964
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
4768
4965
|
let attemptContent = "";
|
|
@@ -4792,6 +4989,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4792
4989
|
? { ...ev, text: workspaceCanonicalAgentOutput(ev.text), nodeId }
|
|
4793
4990
|
: { ...ev, nodeId };
|
|
4794
4991
|
emit(eventToEmit);
|
|
4992
|
+
if (ev?.type === "natural" && typeof ev.text === "string") {
|
|
4993
|
+
const kind = String(ev.kind || "");
|
|
4994
|
+
if (kind === "thinking" || kind === "assistant" || kind === "result") {
|
|
4995
|
+
runHistoryEvents.push({ kind, text: ev.text });
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4795
4998
|
if (ev?.type === "natural" && ev.kind === "assistant" && typeof ev.text === "string") {
|
|
4796
4999
|
attemptLastAssistantContent = ev.text;
|
|
4797
5000
|
attemptContent += (attemptContent ? "\n" : "") + ev.text;
|
|
@@ -4841,6 +5044,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
4841
5044
|
runPackage,
|
|
4842
5045
|
modelKey,
|
|
4843
5046
|
userCtx,
|
|
5047
|
+
historyEvents: runHistoryEvents,
|
|
4844
5048
|
emit: (event) => emit({ ...event, nodeId }),
|
|
4845
5049
|
onActiveChild: opts.onActiveChild,
|
|
4846
5050
|
});
|
|
@@ -5040,15 +5244,61 @@ function broadcastFlowEditorSync(flowId, flowSource, flowArchived = false, userI
|
|
|
5040
5244
|
|
|
5041
5245
|
/** 正在执行的 flow run(flowId → { child, runUuid });同一 flow 只允许一个 run */
|
|
5042
5246
|
const activeFlowRuns = new Map();
|
|
5043
|
-
/** 正在执行的 Workspace 临时 run(
|
|
5247
|
+
/** 正在执行的 Workspace 临时 run(runId/sessionId → { controller, child, runNodeId, startedAt, plannedNodeIds }) */
|
|
5044
5248
|
const activeWorkspaceRuns = new Map();
|
|
5045
5249
|
const WORKSPACE_SCHEDULES_FILENAME = "workspace-schedules.json";
|
|
5046
5250
|
const WORKSPACE_SCHEDULE_POLL_MS = 30_000;
|
|
5251
|
+
const WORKSPACE_IMPLEMENTATION_REFERENCE_ENABLED = false;
|
|
5252
|
+
const WORKSPACE_IMPLEMENTATION_SUMMARY_ENABLED = false;
|
|
5253
|
+
const WORKSPACE_NODE_HISTORY_MAX_CHARS = 80000;
|
|
5254
|
+
|
|
5255
|
+
function workspaceIntervalMinutesToCron(intervalMinutes) {
|
|
5256
|
+
const n = Number(intervalMinutes);
|
|
5257
|
+
if (!Number.isFinite(n) || n <= 0) return "0 9 * * *";
|
|
5258
|
+
const minutes = Math.max(1, Math.min(1440, Math.round(n)));
|
|
5259
|
+
if (minutes < 60) return `*/${minutes} * * * *`;
|
|
5260
|
+
if (minutes === 60) return "0 * * * *";
|
|
5261
|
+
if (minutes < 1440 && minutes % 60 === 0) return `0 */${minutes / 60} * * *`;
|
|
5262
|
+
return "0 9 * * *";
|
|
5263
|
+
}
|
|
5047
5264
|
|
|
5048
5265
|
function workspaceRunKey(userCtx, flowSource, flowId) {
|
|
5049
5266
|
return `${userCtx?.userId || ""}:${flowSource || "user"}:${flowId}`;
|
|
5050
5267
|
}
|
|
5051
5268
|
|
|
5269
|
+
function workspaceRunEntryKey(scopeKey, runId) {
|
|
5270
|
+
return `${scopeKey}:${String(runId || "").trim() || runLedgerId("workspace")}`;
|
|
5271
|
+
}
|
|
5272
|
+
|
|
5273
|
+
function workspaceActiveRunsForScope(scopeKey) {
|
|
5274
|
+
const key = String(scopeKey || "");
|
|
5275
|
+
return Array.from(activeWorkspaceRuns.entries())
|
|
5276
|
+
.filter(([, entry]) => String(entry?.scopeKey || "") === key);
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
function workspaceRunPlanNodeIds(runNodeId, plan) {
|
|
5280
|
+
return Array.from(new Set([
|
|
5281
|
+
String(runNodeId || "").trim(),
|
|
5282
|
+
...(Array.isArray(plan?.order) ? plan.order : []),
|
|
5283
|
+
...(Array.isArray(plan?.pauseNodeIds) ? plan.pauseNodeIds : []),
|
|
5284
|
+
].map((id) => String(id || "").trim()).filter(Boolean)));
|
|
5285
|
+
}
|
|
5286
|
+
|
|
5287
|
+
function workspaceFindActiveRunConflict(scopeKey, plannedNodeIds) {
|
|
5288
|
+
const planned = new Set((plannedNodeIds || []).map((id) => String(id || "").trim()).filter(Boolean));
|
|
5289
|
+
for (const [key, entry] of workspaceActiveRunsForScope(scopeKey)) {
|
|
5290
|
+
const activeIds = Array.isArray(entry?.plannedNodeIds) ? entry.plannedNodeIds : [];
|
|
5291
|
+
if (!activeIds.length) {
|
|
5292
|
+
return { key, entry, conflictNodeIds: [] };
|
|
5293
|
+
}
|
|
5294
|
+
const conflictNodeIds = activeIds
|
|
5295
|
+
.map((id) => String(id || "").trim())
|
|
5296
|
+
.filter((id) => id && planned.has(id));
|
|
5297
|
+
if (conflictNodeIds.length) return { key, entry, conflictNodeIds };
|
|
5298
|
+
}
|
|
5299
|
+
return null;
|
|
5300
|
+
}
|
|
5301
|
+
|
|
5052
5302
|
function normalizeWorkspaceScheduledRunConfig(raw) {
|
|
5053
5303
|
let parsed = {};
|
|
5054
5304
|
const text = String(raw || "").trim();
|
|
@@ -5060,14 +5310,29 @@ function normalizeWorkspaceScheduledRunConfig(raw) {
|
|
|
5060
5310
|
}
|
|
5061
5311
|
}
|
|
5062
5312
|
const intervalMinutes = Number(parsed.intervalMinutes);
|
|
5313
|
+
const migratedCron = workspaceIntervalMinutesToCron(intervalMinutes);
|
|
5314
|
+
const cron = typeof parsed.cron === "string" && parsed.cron.trim()
|
|
5315
|
+
? parsed.cron.trim()
|
|
5316
|
+
: migratedCron;
|
|
5317
|
+
const timezone = typeof parsed.timezone === "string" && parsed.timezone.trim()
|
|
5318
|
+
? parsed.timezone.trim()
|
|
5319
|
+
: "Asia/Shanghai";
|
|
5320
|
+
const targetRunNodeId = typeof parsed.targetRunNodeId === "string" ? parsed.targetRunNodeId.trim() : "";
|
|
5321
|
+
const overlapPolicy = parsed.overlapPolicy === "skip" ? "skip" : "skip";
|
|
5063
5322
|
return {
|
|
5064
5323
|
enabled: parsed.enabled === true,
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5324
|
+
cron,
|
|
5325
|
+
timezone,
|
|
5326
|
+
targetRunNodeId,
|
|
5327
|
+
overlapPolicy,
|
|
5068
5328
|
};
|
|
5069
5329
|
}
|
|
5070
5330
|
|
|
5331
|
+
function workspaceScheduleNextRunAt(config, fromDate = new Date()) {
|
|
5332
|
+
if (!config?.enabled || !config?.cron) return null;
|
|
5333
|
+
return Date.parse(computeNextRunAt(config.cron, config.timezone || "Asia/Shanghai", fromDate));
|
|
5334
|
+
}
|
|
5335
|
+
|
|
5071
5336
|
function workspaceSchedulesPath() {
|
|
5072
5337
|
return path.join(getAgentflowDataRoot(), WORKSPACE_SCHEDULES_FILENAME);
|
|
5073
5338
|
}
|
|
@@ -5098,12 +5363,12 @@ function writeWorkspaceScheduleRegistry(registry) {
|
|
|
5098
5363
|
}, null, 2) + "\n", "utf-8");
|
|
5099
5364
|
}
|
|
5100
5365
|
|
|
5101
|
-
function workspaceScheduleKey(userId, flowSource, flowId,
|
|
5366
|
+
function workspaceScheduleKey(userId, flowSource, flowId, scheduleNodeId) {
|
|
5102
5367
|
return [
|
|
5103
5368
|
String(userId || ""),
|
|
5104
5369
|
String(flowSource || "user"),
|
|
5105
5370
|
String(flowId || ""),
|
|
5106
|
-
String(
|
|
5371
|
+
String(scheduleNodeId || ""),
|
|
5107
5372
|
].join(":");
|
|
5108
5373
|
}
|
|
5109
5374
|
|
|
@@ -5116,7 +5381,14 @@ function listWorkspaceScheduleStatusesForFlow(userCtx = {}, flowSource = "user",
|
|
|
5116
5381
|
String(entry?.flowSource || "user") === String(flowSource || "user") &&
|
|
5117
5382
|
String(entry?.flowId || "") === String(flowId || "")
|
|
5118
5383
|
))
|
|
5119
|
-
.sort((a, b) => String(a.runNodeId || "").localeCompare(String(b.runNodeId || "")));
|
|
5384
|
+
.sort((a, b) => String(a.scheduleNodeId || a.runNodeId || "").localeCompare(String(b.scheduleNodeId || b.runNodeId || "")));
|
|
5385
|
+
}
|
|
5386
|
+
|
|
5387
|
+
function workspaceScheduleInferTargetRunNodeId(graph, scheduleNodeId, config = {}) {
|
|
5388
|
+
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
5389
|
+
return String(instances[scheduleNodeId]?.definitionId || "") === "workspace_scheduled_run"
|
|
5390
|
+
? String(scheduleNodeId || "")
|
|
5391
|
+
: "";
|
|
5120
5392
|
}
|
|
5121
5393
|
|
|
5122
5394
|
function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx = {}) {
|
|
@@ -5133,14 +5405,35 @@ function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx =
|
|
|
5133
5405
|
if (key.startsWith(prefix)) delete schedules[key];
|
|
5134
5406
|
}
|
|
5135
5407
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
5136
|
-
for (const [
|
|
5408
|
+
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
5137
5409
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
5138
5410
|
const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
5139
5411
|
if (!config.enabled) continue;
|
|
5140
|
-
const
|
|
5141
|
-
const
|
|
5412
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(graph, scheduleNodeId, config);
|
|
5413
|
+
const key = workspaceScheduleKey(userId, flowSource, flowId, scheduleNodeId);
|
|
5142
5414
|
const previous = registry.schedules?.[key] && typeof registry.schedules[key] === "object" ? registry.schedules[key] : {};
|
|
5143
5415
|
const previousNext = Number(previous.nextRunAt || 0);
|
|
5416
|
+
const previousMatches = (
|
|
5417
|
+
String(previous.cron || "") === config.cron &&
|
|
5418
|
+
String(previous.timezone || "") === config.timezone &&
|
|
5419
|
+
String(previous.targetRunNodeId || previous.runNodeId || "") === targetRunNodeId
|
|
5420
|
+
);
|
|
5421
|
+
let nextRunAt = null;
|
|
5422
|
+
let lastStatus = previous.lastStatus || "armed";
|
|
5423
|
+
let lastError = previous.lastError || "";
|
|
5424
|
+
try {
|
|
5425
|
+
nextRunAt = previousMatches && Number.isFinite(previousNext) && previousNext > now
|
|
5426
|
+
? previousNext
|
|
5427
|
+
: workspaceScheduleNextRunAt(config, new Date(now));
|
|
5428
|
+
if (!targetRunNodeId) {
|
|
5429
|
+
lastStatus = "invalid";
|
|
5430
|
+
lastError = "Scheduled Run node is missing";
|
|
5431
|
+
}
|
|
5432
|
+
} catch (e) {
|
|
5433
|
+
lastStatus = "invalid";
|
|
5434
|
+
lastError = (e && e.message) || String(e);
|
|
5435
|
+
nextRunAt = null;
|
|
5436
|
+
}
|
|
5144
5437
|
schedules[key] = {
|
|
5145
5438
|
...previous,
|
|
5146
5439
|
key,
|
|
@@ -5149,11 +5442,16 @@ function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx =
|
|
|
5149
5442
|
username: String(authUser?.username || previous.username || userId),
|
|
5150
5443
|
flowId,
|
|
5151
5444
|
flowSource,
|
|
5152
|
-
|
|
5445
|
+
scheduleNodeId,
|
|
5446
|
+
runNodeId: targetRunNodeId,
|
|
5447
|
+
targetRunNodeId,
|
|
5153
5448
|
label: String(instance.label || "Scheduled Run"),
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5449
|
+
cron: config.cron,
|
|
5450
|
+
timezone: config.timezone,
|
|
5451
|
+
overlapPolicy: config.overlapPolicy,
|
|
5452
|
+
nextRunAt,
|
|
5453
|
+
lastStatus,
|
|
5454
|
+
lastError,
|
|
5157
5455
|
updatedAt: nowIso,
|
|
5158
5456
|
};
|
|
5159
5457
|
}
|
|
@@ -5178,17 +5476,22 @@ function updateWorkspaceScheduleEntry(key, patch) {
|
|
|
5178
5476
|
|
|
5179
5477
|
async function runWorkspaceScheduledEntry(root, entry) {
|
|
5180
5478
|
const userCtx = { userId: String(entry.userId || "") };
|
|
5181
|
-
const
|
|
5182
|
-
const
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5479
|
+
const scopeKey = workspaceRunKey(userCtx, entry.flowSource || "user", entry.flowId || "");
|
|
5480
|
+
const fallbackConfig = {
|
|
5481
|
+
enabled: true,
|
|
5482
|
+
cron: String(entry.cron || "0 9 * * *"),
|
|
5483
|
+
timezone: String(entry.timezone || "Asia/Shanghai"),
|
|
5484
|
+
targetRunNodeId: String(entry.targetRunNodeId || entry.runNodeId || ""),
|
|
5485
|
+
overlapPolicy: "skip",
|
|
5486
|
+
};
|
|
5487
|
+
const computeNext = (config = fallbackConfig) => {
|
|
5488
|
+
try {
|
|
5489
|
+
return workspaceScheduleNextRunAt(config, new Date());
|
|
5490
|
+
} catch {
|
|
5491
|
+
return null;
|
|
5492
|
+
}
|
|
5493
|
+
};
|
|
5494
|
+
let nextRunAt = computeNext(fallbackConfig);
|
|
5192
5495
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
5193
5496
|
flowId: entry.flowId || "",
|
|
5194
5497
|
flowSource: entry.flowSource || "user",
|
|
@@ -5204,8 +5507,10 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5204
5507
|
}
|
|
5205
5508
|
const graphPath = workspaceGraphPath(scoped.root);
|
|
5206
5509
|
const graph = hydrateWorkspaceGraphForRuntime(root, scoped, readWorkspaceGraph(scoped.root).graph, userCtx);
|
|
5207
|
-
const
|
|
5510
|
+
const scheduleNodeId = String(entry.scheduleNodeId || entry.key?.split(":").pop() || "");
|
|
5511
|
+
const instance = graph.instances?.[scheduleNodeId];
|
|
5208
5512
|
const config = normalizeWorkspaceScheduledRunConfig(instance?.body || "");
|
|
5513
|
+
nextRunAt = computeNext(config);
|
|
5209
5514
|
if (!instance || String(instance.definitionId || "") !== "workspace_scheduled_run" || !config.enabled) {
|
|
5210
5515
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5211
5516
|
enabled: false,
|
|
@@ -5214,19 +5519,56 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5214
5519
|
});
|
|
5215
5520
|
return;
|
|
5216
5521
|
}
|
|
5522
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(graph, scheduleNodeId, config);
|
|
5523
|
+
if (!targetRunNodeId) {
|
|
5524
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5525
|
+
nextRunAt,
|
|
5526
|
+
lastStatus: "invalid",
|
|
5527
|
+
lastError: "Scheduled Run node is missing",
|
|
5528
|
+
lastErrorAt: Date.now(),
|
|
5529
|
+
});
|
|
5530
|
+
return;
|
|
5531
|
+
}
|
|
5532
|
+
let plan;
|
|
5533
|
+
try {
|
|
5534
|
+
plan = workspaceRunPlan(graph, targetRunNodeId, scoped.root);
|
|
5535
|
+
} catch (e) {
|
|
5536
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5537
|
+
nextRunAt,
|
|
5538
|
+
lastStatus: "failed",
|
|
5539
|
+
lastError: (e && e.message) || String(e),
|
|
5540
|
+
lastErrorAt: Date.now(),
|
|
5541
|
+
});
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
const plannedNodeIds = workspaceRunPlanNodeIds(targetRunNodeId, plan);
|
|
5545
|
+
const conflict = workspaceFindActiveRunConflict(scopeKey, plannedNodeIds);
|
|
5546
|
+
if (conflict) {
|
|
5547
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5548
|
+
nextRunAt,
|
|
5549
|
+
lastSkippedAt: Date.now(),
|
|
5550
|
+
lastStatus: "skipped: busy",
|
|
5551
|
+
lastError: "",
|
|
5552
|
+
});
|
|
5553
|
+
return;
|
|
5554
|
+
}
|
|
5217
5555
|
|
|
5218
5556
|
const controller = new AbortController();
|
|
5219
5557
|
const authUsers = readAuthUsers();
|
|
5220
5558
|
const authUser = authUsers[userCtx.userId] || {};
|
|
5559
|
+
const runId = runLedgerId("workspace");
|
|
5560
|
+
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
5221
5561
|
const runEntry = {
|
|
5562
|
+
scopeKey,
|
|
5222
5563
|
controller,
|
|
5223
5564
|
child: null,
|
|
5224
|
-
runId
|
|
5565
|
+
runId,
|
|
5225
5566
|
userId: userCtx.userId,
|
|
5226
5567
|
username: String(authUser.username || entry.username || userCtx.userId),
|
|
5227
|
-
runNodeId:
|
|
5568
|
+
runNodeId: targetRunNodeId,
|
|
5228
5569
|
flowId: String(entry.flowId || ""),
|
|
5229
5570
|
flowSource: String(entry.flowSource || "user"),
|
|
5571
|
+
plannedNodeIds,
|
|
5230
5572
|
startedAt: Date.now(),
|
|
5231
5573
|
scheduled: true,
|
|
5232
5574
|
stopChild() {
|
|
@@ -5241,6 +5583,10 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5241
5583
|
lastStatus: "running",
|
|
5242
5584
|
lastTriggeredAt: runEntry.startedAt,
|
|
5243
5585
|
lastRunId: runEntry.runId,
|
|
5586
|
+
runNodeId: targetRunNodeId,
|
|
5587
|
+
targetRunNodeId,
|
|
5588
|
+
cron: config.cron,
|
|
5589
|
+
timezone: config.timezone,
|
|
5244
5590
|
lastError: "",
|
|
5245
5591
|
});
|
|
5246
5592
|
const setActiveChild = (child) => {
|
|
@@ -5251,7 +5597,7 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5251
5597
|
const result = await runWorkspaceGraph(root, scoped.root, {
|
|
5252
5598
|
flowId: entry.flowId,
|
|
5253
5599
|
flowSource: entry.flowSource || "user",
|
|
5254
|
-
runNodeId:
|
|
5600
|
+
runNodeId: targetRunNodeId,
|
|
5255
5601
|
graph,
|
|
5256
5602
|
}, userCtx, {
|
|
5257
5603
|
signal: controller.signal,
|
|
@@ -5264,7 +5610,7 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5264
5610
|
const endedAt = Date.now();
|
|
5265
5611
|
appendWorkspaceRunFinished({ ...runEntry, endedAt, durationMs: endedAt - runEntry.startedAt }, "success");
|
|
5266
5612
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5267
|
-
nextRunAt:
|
|
5613
|
+
nextRunAt: computeNext(config),
|
|
5268
5614
|
lastFinishedAt: endedAt,
|
|
5269
5615
|
lastStatus: "success",
|
|
5270
5616
|
lastError: "",
|
|
@@ -5273,13 +5619,13 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5273
5619
|
const endedAt = Date.now();
|
|
5274
5620
|
appendWorkspaceRunFinished({ ...runEntry, endedAt, durationMs: endedAt - runEntry.startedAt }, "failed");
|
|
5275
5621
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5276
|
-
nextRunAt:
|
|
5622
|
+
nextRunAt: computeNext(config),
|
|
5277
5623
|
lastFinishedAt: endedAt,
|
|
5278
5624
|
lastStatus: "failed",
|
|
5279
5625
|
lastError: (e && e.message) || String(e),
|
|
5280
5626
|
lastErrorAt: endedAt,
|
|
5281
5627
|
});
|
|
5282
|
-
log.info(`[workspace-scheduler] failed ${entry.flowId}/${
|
|
5628
|
+
log.info(`[workspace-scheduler] failed ${entry.flowId}/${targetRunNodeId}: ${(e && e.message) || String(e)}`);
|
|
5283
5629
|
} finally {
|
|
5284
5630
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
5285
5631
|
}
|
|
@@ -5292,8 +5638,25 @@ function pollWorkspaceSchedules(root) {
|
|
|
5292
5638
|
if (!entry || entry.enabled !== true) continue;
|
|
5293
5639
|
const nextRunAt = Number(entry.nextRunAt || 0);
|
|
5294
5640
|
if (!Number.isFinite(nextRunAt) || nextRunAt <= 0) {
|
|
5641
|
+
const config = {
|
|
5642
|
+
enabled: true,
|
|
5643
|
+
cron: String(entry.cron || "0 9 * * *"),
|
|
5644
|
+
timezone: String(entry.timezone || "Asia/Shanghai"),
|
|
5645
|
+
};
|
|
5646
|
+
let computedNext = null;
|
|
5647
|
+
try {
|
|
5648
|
+
computedNext = workspaceScheduleNextRunAt(config, new Date(now));
|
|
5649
|
+
} catch (e) {
|
|
5650
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5651
|
+
nextRunAt: null,
|
|
5652
|
+
lastStatus: "invalid",
|
|
5653
|
+
lastError: (e && e.message) || String(e),
|
|
5654
|
+
lastErrorAt: now,
|
|
5655
|
+
});
|
|
5656
|
+
continue;
|
|
5657
|
+
}
|
|
5295
5658
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5296
|
-
nextRunAt:
|
|
5659
|
+
nextRunAt: computedNext,
|
|
5297
5660
|
lastStatus: entry.lastStatus || "armed",
|
|
5298
5661
|
});
|
|
5299
5662
|
continue;
|
|
@@ -6079,6 +6442,53 @@ export function startUiServer({
|
|
|
6079
6442
|
return;
|
|
6080
6443
|
}
|
|
6081
6444
|
|
|
6445
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/run/plan") {
|
|
6446
|
+
let payload;
|
|
6447
|
+
try {
|
|
6448
|
+
payload = JSON.parse(await readBody(req));
|
|
6449
|
+
} catch {
|
|
6450
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
6451
|
+
return;
|
|
6452
|
+
}
|
|
6453
|
+
try {
|
|
6454
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
6455
|
+
flowId: payload.flowId || "",
|
|
6456
|
+
flowSource: payload.flowSource || "user",
|
|
6457
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
6458
|
+
}, userCtx);
|
|
6459
|
+
if (scoped.error) {
|
|
6460
|
+
json(res, 400, { error: scoped.error });
|
|
6461
|
+
return;
|
|
6462
|
+
}
|
|
6463
|
+
const flowId = String(payload.flowId || "").trim();
|
|
6464
|
+
if (!flowId) {
|
|
6465
|
+
json(res, 400, { error: "Missing flowId" });
|
|
6466
|
+
return;
|
|
6467
|
+
}
|
|
6468
|
+
const graph = hydrateWorkspaceGraphForRuntime(root, scoped, payload.graph || {}, userCtx);
|
|
6469
|
+
const runNodeId = String(payload.runNodeId || "").trim();
|
|
6470
|
+
const plan = workspaceRunPlan(graph, runNodeId, scoped.root);
|
|
6471
|
+
const plannedNodeIds = workspaceRunPlanNodeIds(runNodeId, plan);
|
|
6472
|
+
const scopeKey = workspaceRunKey(userCtx, scoped.flowSource || payload.flowSource || "user", flowId);
|
|
6473
|
+
const conflict = workspaceFindActiveRunConflict(scopeKey, plannedNodeIds);
|
|
6474
|
+
json(res, 200, {
|
|
6475
|
+
ok: true,
|
|
6476
|
+
runNodeId,
|
|
6477
|
+
order: plan.order,
|
|
6478
|
+
pauseNodeIds: plan.pauseNodeIds,
|
|
6479
|
+
plannedNodeIds,
|
|
6480
|
+
conflict: conflict ? {
|
|
6481
|
+
runId: conflict.entry?.runId || "",
|
|
6482
|
+
runNodeId: conflict.entry?.runNodeId || "",
|
|
6483
|
+
conflictNodeIds: conflict.conflictNodeIds,
|
|
6484
|
+
} : null,
|
|
6485
|
+
});
|
|
6486
|
+
} catch (e) {
|
|
6487
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
6488
|
+
}
|
|
6489
|
+
return;
|
|
6490
|
+
}
|
|
6491
|
+
|
|
6082
6492
|
if (req.method === "POST" && url.pathname === "/api/workspace/run") {
|
|
6083
6493
|
let payload;
|
|
6084
6494
|
try {
|
|
@@ -6107,21 +6517,35 @@ export function startUiServer({
|
|
|
6107
6517
|
json(res, 400, { error: "Missing flowId" });
|
|
6108
6518
|
return;
|
|
6109
6519
|
}
|
|
6110
|
-
const
|
|
6111
|
-
|
|
6112
|
-
|
|
6520
|
+
const runtimeGraph = hydrateWorkspaceGraphForRuntime(root, scoped, payload.graph || {}, userCtx);
|
|
6521
|
+
const runNodeId = String(payload.runNodeId || "").trim();
|
|
6522
|
+
const plan = workspaceRunPlan(runtimeGraph, runNodeId, scoped.root);
|
|
6523
|
+
const plannedNodeIds = workspaceRunPlanNodeIds(runNodeId, plan);
|
|
6524
|
+
const scopeKey = workspaceRunKey(userCtx, scoped.flowSource || payload.flowSource || "user", flowId);
|
|
6525
|
+
const conflict = workspaceFindActiveRunConflict(scopeKey, plannedNodeIds);
|
|
6526
|
+
if (conflict) {
|
|
6527
|
+
json(res, 409, {
|
|
6528
|
+
error: "该 Run 与正在执行的 Run 共享节点",
|
|
6529
|
+
runNodeId: conflict.entry?.runNodeId || "",
|
|
6530
|
+
runId: conflict.entry?.runId || "",
|
|
6531
|
+
conflictNodeIds: conflict.conflictNodeIds,
|
|
6532
|
+
});
|
|
6113
6533
|
return;
|
|
6114
6534
|
}
|
|
6115
6535
|
const controller = new AbortController();
|
|
6536
|
+
const runId = String(payload.runSessionId || payload.runId || "").trim() || runLedgerId("workspace");
|
|
6537
|
+
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
6116
6538
|
const runEntry = {
|
|
6539
|
+
scopeKey,
|
|
6117
6540
|
controller,
|
|
6118
6541
|
child: null,
|
|
6119
|
-
runId
|
|
6542
|
+
runId,
|
|
6120
6543
|
userId: String(userCtx.userId || ""),
|
|
6121
6544
|
username: String(authUser?.username || userCtx.userId || ""),
|
|
6122
|
-
runNodeId
|
|
6545
|
+
runNodeId,
|
|
6123
6546
|
flowId,
|
|
6124
6547
|
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
6548
|
+
plannedNodeIds,
|
|
6125
6549
|
startedAt: Date.now(),
|
|
6126
6550
|
stopChild() {
|
|
6127
6551
|
if (this.child && !this.child.killed) {
|
|
@@ -6235,14 +6659,22 @@ export function startUiServer({
|
|
|
6235
6659
|
return;
|
|
6236
6660
|
}
|
|
6237
6661
|
const flowSource = url.searchParams.get("flowSource") || "user";
|
|
6238
|
-
const
|
|
6239
|
-
const
|
|
6662
|
+
const scopeKey = workspaceRunKey(userCtx, flowSource, flowId);
|
|
6663
|
+
const entries = workspaceActiveRunsForScope(scopeKey).map(([, entry]) => entry);
|
|
6664
|
+
const entry = entries[0] || null;
|
|
6240
6665
|
json(res, 200, {
|
|
6241
|
-
running:
|
|
6666
|
+
running: entries.length > 0,
|
|
6242
6667
|
flowId,
|
|
6243
6668
|
flowSource,
|
|
6244
6669
|
runNodeId: entry?.runNodeId || "",
|
|
6245
6670
|
startedAt: entry?.startedAt || null,
|
|
6671
|
+
runs: entries.map((item) => ({
|
|
6672
|
+
runId: item?.runId || "",
|
|
6673
|
+
runNodeId: item?.runNodeId || "",
|
|
6674
|
+
startedAt: item?.startedAt || null,
|
|
6675
|
+
plannedNodeIds: Array.isArray(item?.plannedNodeIds) ? item.plannedNodeIds : [],
|
|
6676
|
+
scheduled: item?.scheduled === true,
|
|
6677
|
+
})),
|
|
6246
6678
|
});
|
|
6247
6679
|
return;
|
|
6248
6680
|
}
|
|
@@ -6260,8 +6692,14 @@ export function startUiServer({
|
|
|
6260
6692
|
json(res, 400, { error: "Missing flowId" });
|
|
6261
6693
|
return;
|
|
6262
6694
|
}
|
|
6263
|
-
const
|
|
6264
|
-
const
|
|
6695
|
+
const scopeKey = workspaceRunKey(userCtx, payload.flowSource || "user", flowId);
|
|
6696
|
+
const runId = String(payload.runId || payload.runSessionId || "").trim();
|
|
6697
|
+
const runNodeId = String(payload.runNodeId || "").trim();
|
|
6698
|
+
const entries = workspaceActiveRunsForScope(scopeKey);
|
|
6699
|
+
const match = entries.find(([, item]) => runId && String(item?.runId || "") === runId)
|
|
6700
|
+
|| entries.find(([, item]) => runNodeId && String(item?.runNodeId || "") === runNodeId)
|
|
6701
|
+
|| (!runId && !runNodeId && entries.length === 1 ? entries[0] : null);
|
|
6702
|
+
const entry = match?.[1] || null;
|
|
6265
6703
|
if (!entry) {
|
|
6266
6704
|
json(res, 404, { error: "该 Workspace 未在运行" });
|
|
6267
6705
|
return;
|