@fieldwangai/agentflow 0.1.72 → 0.1.73
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 +495 -48
- package/builtin/web-ui/dist/assets/index-DKw0bt1j.js +297 -0
- package/builtin/web-ui/dist/assets/index-t1Xrjvhr.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,23 @@ 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
|
+
const explicit = String(config.targetRunNodeId || "").trim();
|
|
5390
|
+
if (explicit && String(instances[explicit]?.definitionId || "") === "workspace_run") return explicit;
|
|
5391
|
+
const edges = Array.isArray(graph?.edges) ? graph.edges : [];
|
|
5392
|
+
for (const edge of edges) {
|
|
5393
|
+
const source = String(edge?.source || "");
|
|
5394
|
+
const target = String(edge?.target || "");
|
|
5395
|
+
if (source !== String(scheduleNodeId || "") || !target) continue;
|
|
5396
|
+
if (!workspaceIsControlEdge(graph, edge)) continue;
|
|
5397
|
+
if (String(instances[target]?.definitionId || "") === "workspace_run") return target;
|
|
5398
|
+
}
|
|
5399
|
+
const firstRun = Object.entries(instances).find(([, instance]) => String(instance?.definitionId || "") === "workspace_run");
|
|
5400
|
+
return firstRun ? firstRun[0] : "";
|
|
5120
5401
|
}
|
|
5121
5402
|
|
|
5122
5403
|
function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx = {}) {
|
|
@@ -5133,14 +5414,35 @@ function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx =
|
|
|
5133
5414
|
if (key.startsWith(prefix)) delete schedules[key];
|
|
5134
5415
|
}
|
|
5135
5416
|
const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
|
|
5136
|
-
for (const [
|
|
5417
|
+
for (const [scheduleNodeId, instance] of Object.entries(instances)) {
|
|
5137
5418
|
if (String(instance?.definitionId || "") !== "workspace_scheduled_run") continue;
|
|
5138
5419
|
const config = normalizeWorkspaceScheduledRunConfig(instance.body || "");
|
|
5139
5420
|
if (!config.enabled) continue;
|
|
5140
|
-
const
|
|
5141
|
-
const
|
|
5421
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(graph, scheduleNodeId, config);
|
|
5422
|
+
const key = workspaceScheduleKey(userId, flowSource, flowId, scheduleNodeId);
|
|
5142
5423
|
const previous = registry.schedules?.[key] && typeof registry.schedules[key] === "object" ? registry.schedules[key] : {};
|
|
5143
5424
|
const previousNext = Number(previous.nextRunAt || 0);
|
|
5425
|
+
const previousMatches = (
|
|
5426
|
+
String(previous.cron || "") === config.cron &&
|
|
5427
|
+
String(previous.timezone || "") === config.timezone &&
|
|
5428
|
+
String(previous.targetRunNodeId || previous.runNodeId || "") === targetRunNodeId
|
|
5429
|
+
);
|
|
5430
|
+
let nextRunAt = null;
|
|
5431
|
+
let lastStatus = previous.lastStatus || "armed";
|
|
5432
|
+
let lastError = previous.lastError || "";
|
|
5433
|
+
try {
|
|
5434
|
+
nextRunAt = previousMatches && Number.isFinite(previousNext) && previousNext > now
|
|
5435
|
+
? previousNext
|
|
5436
|
+
: workspaceScheduleNextRunAt(config, new Date(now));
|
|
5437
|
+
if (!targetRunNodeId) {
|
|
5438
|
+
lastStatus = "invalid";
|
|
5439
|
+
lastError = "No target Run node selected or connected";
|
|
5440
|
+
}
|
|
5441
|
+
} catch (e) {
|
|
5442
|
+
lastStatus = "invalid";
|
|
5443
|
+
lastError = (e && e.message) || String(e);
|
|
5444
|
+
nextRunAt = null;
|
|
5445
|
+
}
|
|
5144
5446
|
schedules[key] = {
|
|
5145
5447
|
...previous,
|
|
5146
5448
|
key,
|
|
@@ -5149,11 +5451,16 @@ function syncWorkspaceSchedulesForGraph(root, scoped, graph, authUser, userCtx =
|
|
|
5149
5451
|
username: String(authUser?.username || previous.username || userId),
|
|
5150
5452
|
flowId,
|
|
5151
5453
|
flowSource,
|
|
5152
|
-
|
|
5454
|
+
scheduleNodeId,
|
|
5455
|
+
runNodeId: targetRunNodeId,
|
|
5456
|
+
targetRunNodeId,
|
|
5153
5457
|
label: String(instance.label || "Scheduled Run"),
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5458
|
+
cron: config.cron,
|
|
5459
|
+
timezone: config.timezone,
|
|
5460
|
+
overlapPolicy: config.overlapPolicy,
|
|
5461
|
+
nextRunAt,
|
|
5462
|
+
lastStatus,
|
|
5463
|
+
lastError,
|
|
5157
5464
|
updatedAt: nowIso,
|
|
5158
5465
|
};
|
|
5159
5466
|
}
|
|
@@ -5178,17 +5485,22 @@ function updateWorkspaceScheduleEntry(key, patch) {
|
|
|
5178
5485
|
|
|
5179
5486
|
async function runWorkspaceScheduledEntry(root, entry) {
|
|
5180
5487
|
const userCtx = { userId: String(entry.userId || "") };
|
|
5181
|
-
const
|
|
5182
|
-
const
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5488
|
+
const scopeKey = workspaceRunKey(userCtx, entry.flowSource || "user", entry.flowId || "");
|
|
5489
|
+
const fallbackConfig = {
|
|
5490
|
+
enabled: true,
|
|
5491
|
+
cron: String(entry.cron || "0 9 * * *"),
|
|
5492
|
+
timezone: String(entry.timezone || "Asia/Shanghai"),
|
|
5493
|
+
targetRunNodeId: String(entry.targetRunNodeId || entry.runNodeId || ""),
|
|
5494
|
+
overlapPolicy: "skip",
|
|
5495
|
+
};
|
|
5496
|
+
const computeNext = (config = fallbackConfig) => {
|
|
5497
|
+
try {
|
|
5498
|
+
return workspaceScheduleNextRunAt(config, new Date());
|
|
5499
|
+
} catch {
|
|
5500
|
+
return null;
|
|
5501
|
+
}
|
|
5502
|
+
};
|
|
5503
|
+
let nextRunAt = computeNext(fallbackConfig);
|
|
5192
5504
|
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
5193
5505
|
flowId: entry.flowId || "",
|
|
5194
5506
|
flowSource: entry.flowSource || "user",
|
|
@@ -5204,8 +5516,10 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5204
5516
|
}
|
|
5205
5517
|
const graphPath = workspaceGraphPath(scoped.root);
|
|
5206
5518
|
const graph = hydrateWorkspaceGraphForRuntime(root, scoped, readWorkspaceGraph(scoped.root).graph, userCtx);
|
|
5207
|
-
const
|
|
5519
|
+
const scheduleNodeId = String(entry.scheduleNodeId || entry.key?.split(":").pop() || "");
|
|
5520
|
+
const instance = graph.instances?.[scheduleNodeId];
|
|
5208
5521
|
const config = normalizeWorkspaceScheduledRunConfig(instance?.body || "");
|
|
5522
|
+
nextRunAt = computeNext(config);
|
|
5209
5523
|
if (!instance || String(instance.definitionId || "") !== "workspace_scheduled_run" || !config.enabled) {
|
|
5210
5524
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5211
5525
|
enabled: false,
|
|
@@ -5214,19 +5528,56 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5214
5528
|
});
|
|
5215
5529
|
return;
|
|
5216
5530
|
}
|
|
5531
|
+
const targetRunNodeId = workspaceScheduleInferTargetRunNodeId(graph, scheduleNodeId, config);
|
|
5532
|
+
if (!targetRunNodeId) {
|
|
5533
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5534
|
+
nextRunAt,
|
|
5535
|
+
lastStatus: "invalid",
|
|
5536
|
+
lastError: "No target Run node selected or connected",
|
|
5537
|
+
lastErrorAt: Date.now(),
|
|
5538
|
+
});
|
|
5539
|
+
return;
|
|
5540
|
+
}
|
|
5541
|
+
let plan;
|
|
5542
|
+
try {
|
|
5543
|
+
plan = workspaceRunPlan(graph, targetRunNodeId, scoped.root);
|
|
5544
|
+
} catch (e) {
|
|
5545
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5546
|
+
nextRunAt,
|
|
5547
|
+
lastStatus: "failed",
|
|
5548
|
+
lastError: (e && e.message) || String(e),
|
|
5549
|
+
lastErrorAt: Date.now(),
|
|
5550
|
+
});
|
|
5551
|
+
return;
|
|
5552
|
+
}
|
|
5553
|
+
const plannedNodeIds = workspaceRunPlanNodeIds(targetRunNodeId, plan);
|
|
5554
|
+
const conflict = workspaceFindActiveRunConflict(scopeKey, plannedNodeIds);
|
|
5555
|
+
if (conflict) {
|
|
5556
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5557
|
+
nextRunAt,
|
|
5558
|
+
lastSkippedAt: Date.now(),
|
|
5559
|
+
lastStatus: "skipped: busy",
|
|
5560
|
+
lastError: "",
|
|
5561
|
+
});
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5217
5564
|
|
|
5218
5565
|
const controller = new AbortController();
|
|
5219
5566
|
const authUsers = readAuthUsers();
|
|
5220
5567
|
const authUser = authUsers[userCtx.userId] || {};
|
|
5568
|
+
const runId = runLedgerId("workspace");
|
|
5569
|
+
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
5221
5570
|
const runEntry = {
|
|
5571
|
+
scopeKey,
|
|
5222
5572
|
controller,
|
|
5223
5573
|
child: null,
|
|
5224
|
-
runId
|
|
5574
|
+
runId,
|
|
5225
5575
|
userId: userCtx.userId,
|
|
5226
5576
|
username: String(authUser.username || entry.username || userCtx.userId),
|
|
5227
|
-
runNodeId:
|
|
5577
|
+
runNodeId: targetRunNodeId,
|
|
5228
5578
|
flowId: String(entry.flowId || ""),
|
|
5229
5579
|
flowSource: String(entry.flowSource || "user"),
|
|
5580
|
+
plannedNodeIds,
|
|
5230
5581
|
startedAt: Date.now(),
|
|
5231
5582
|
scheduled: true,
|
|
5232
5583
|
stopChild() {
|
|
@@ -5241,6 +5592,10 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5241
5592
|
lastStatus: "running",
|
|
5242
5593
|
lastTriggeredAt: runEntry.startedAt,
|
|
5243
5594
|
lastRunId: runEntry.runId,
|
|
5595
|
+
runNodeId: targetRunNodeId,
|
|
5596
|
+
targetRunNodeId,
|
|
5597
|
+
cron: config.cron,
|
|
5598
|
+
timezone: config.timezone,
|
|
5244
5599
|
lastError: "",
|
|
5245
5600
|
});
|
|
5246
5601
|
const setActiveChild = (child) => {
|
|
@@ -5251,7 +5606,7 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5251
5606
|
const result = await runWorkspaceGraph(root, scoped.root, {
|
|
5252
5607
|
flowId: entry.flowId,
|
|
5253
5608
|
flowSource: entry.flowSource || "user",
|
|
5254
|
-
runNodeId:
|
|
5609
|
+
runNodeId: targetRunNodeId,
|
|
5255
5610
|
graph,
|
|
5256
5611
|
}, userCtx, {
|
|
5257
5612
|
signal: controller.signal,
|
|
@@ -5264,7 +5619,7 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5264
5619
|
const endedAt = Date.now();
|
|
5265
5620
|
appendWorkspaceRunFinished({ ...runEntry, endedAt, durationMs: endedAt - runEntry.startedAt }, "success");
|
|
5266
5621
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5267
|
-
nextRunAt:
|
|
5622
|
+
nextRunAt: computeNext(config),
|
|
5268
5623
|
lastFinishedAt: endedAt,
|
|
5269
5624
|
lastStatus: "success",
|
|
5270
5625
|
lastError: "",
|
|
@@ -5273,13 +5628,13 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
5273
5628
|
const endedAt = Date.now();
|
|
5274
5629
|
appendWorkspaceRunFinished({ ...runEntry, endedAt, durationMs: endedAt - runEntry.startedAt }, "failed");
|
|
5275
5630
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5276
|
-
nextRunAt:
|
|
5631
|
+
nextRunAt: computeNext(config),
|
|
5277
5632
|
lastFinishedAt: endedAt,
|
|
5278
5633
|
lastStatus: "failed",
|
|
5279
5634
|
lastError: (e && e.message) || String(e),
|
|
5280
5635
|
lastErrorAt: endedAt,
|
|
5281
5636
|
});
|
|
5282
|
-
log.info(`[workspace-scheduler] failed ${entry.flowId}/${
|
|
5637
|
+
log.info(`[workspace-scheduler] failed ${entry.flowId}/${targetRunNodeId}: ${(e && e.message) || String(e)}`);
|
|
5283
5638
|
} finally {
|
|
5284
5639
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
5285
5640
|
}
|
|
@@ -5292,8 +5647,25 @@ function pollWorkspaceSchedules(root) {
|
|
|
5292
5647
|
if (!entry || entry.enabled !== true) continue;
|
|
5293
5648
|
const nextRunAt = Number(entry.nextRunAt || 0);
|
|
5294
5649
|
if (!Number.isFinite(nextRunAt) || nextRunAt <= 0) {
|
|
5650
|
+
const config = {
|
|
5651
|
+
enabled: true,
|
|
5652
|
+
cron: String(entry.cron || "0 9 * * *"),
|
|
5653
|
+
timezone: String(entry.timezone || "Asia/Shanghai"),
|
|
5654
|
+
};
|
|
5655
|
+
let computedNext = null;
|
|
5656
|
+
try {
|
|
5657
|
+
computedNext = workspaceScheduleNextRunAt(config, new Date(now));
|
|
5658
|
+
} catch (e) {
|
|
5659
|
+
updateWorkspaceScheduleEntry(entry.key, {
|
|
5660
|
+
nextRunAt: null,
|
|
5661
|
+
lastStatus: "invalid",
|
|
5662
|
+
lastError: (e && e.message) || String(e),
|
|
5663
|
+
lastErrorAt: now,
|
|
5664
|
+
});
|
|
5665
|
+
continue;
|
|
5666
|
+
}
|
|
5295
5667
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
5296
|
-
nextRunAt:
|
|
5668
|
+
nextRunAt: computedNext,
|
|
5297
5669
|
lastStatus: entry.lastStatus || "armed",
|
|
5298
5670
|
});
|
|
5299
5671
|
continue;
|
|
@@ -6079,6 +6451,53 @@ export function startUiServer({
|
|
|
6079
6451
|
return;
|
|
6080
6452
|
}
|
|
6081
6453
|
|
|
6454
|
+
if (req.method === "POST" && url.pathname === "/api/workspace/run/plan") {
|
|
6455
|
+
let payload;
|
|
6456
|
+
try {
|
|
6457
|
+
payload = JSON.parse(await readBody(req));
|
|
6458
|
+
} catch {
|
|
6459
|
+
json(res, 400, { error: "Invalid JSON body" });
|
|
6460
|
+
return;
|
|
6461
|
+
}
|
|
6462
|
+
try {
|
|
6463
|
+
const scoped = resolveWorkspaceScopeRoot(root, {
|
|
6464
|
+
flowId: payload.flowId || "",
|
|
6465
|
+
flowSource: payload.flowSource || "user",
|
|
6466
|
+
archived: payload.archived === true || payload.flowArchived === true,
|
|
6467
|
+
}, userCtx);
|
|
6468
|
+
if (scoped.error) {
|
|
6469
|
+
json(res, 400, { error: scoped.error });
|
|
6470
|
+
return;
|
|
6471
|
+
}
|
|
6472
|
+
const flowId = String(payload.flowId || "").trim();
|
|
6473
|
+
if (!flowId) {
|
|
6474
|
+
json(res, 400, { error: "Missing flowId" });
|
|
6475
|
+
return;
|
|
6476
|
+
}
|
|
6477
|
+
const graph = hydrateWorkspaceGraphForRuntime(root, scoped, payload.graph || {}, userCtx);
|
|
6478
|
+
const runNodeId = String(payload.runNodeId || "").trim();
|
|
6479
|
+
const plan = workspaceRunPlan(graph, runNodeId, scoped.root);
|
|
6480
|
+
const plannedNodeIds = workspaceRunPlanNodeIds(runNodeId, plan);
|
|
6481
|
+
const scopeKey = workspaceRunKey(userCtx, scoped.flowSource || payload.flowSource || "user", flowId);
|
|
6482
|
+
const conflict = workspaceFindActiveRunConflict(scopeKey, plannedNodeIds);
|
|
6483
|
+
json(res, 200, {
|
|
6484
|
+
ok: true,
|
|
6485
|
+
runNodeId,
|
|
6486
|
+
order: plan.order,
|
|
6487
|
+
pauseNodeIds: plan.pauseNodeIds,
|
|
6488
|
+
plannedNodeIds,
|
|
6489
|
+
conflict: conflict ? {
|
|
6490
|
+
runId: conflict.entry?.runId || "",
|
|
6491
|
+
runNodeId: conflict.entry?.runNodeId || "",
|
|
6492
|
+
conflictNodeIds: conflict.conflictNodeIds,
|
|
6493
|
+
} : null,
|
|
6494
|
+
});
|
|
6495
|
+
} catch (e) {
|
|
6496
|
+
json(res, 500, { error: (e && e.message) || String(e) });
|
|
6497
|
+
}
|
|
6498
|
+
return;
|
|
6499
|
+
}
|
|
6500
|
+
|
|
6082
6501
|
if (req.method === "POST" && url.pathname === "/api/workspace/run") {
|
|
6083
6502
|
let payload;
|
|
6084
6503
|
try {
|
|
@@ -6107,21 +6526,35 @@ export function startUiServer({
|
|
|
6107
6526
|
json(res, 400, { error: "Missing flowId" });
|
|
6108
6527
|
return;
|
|
6109
6528
|
}
|
|
6110
|
-
const
|
|
6111
|
-
|
|
6112
|
-
|
|
6529
|
+
const runtimeGraph = hydrateWorkspaceGraphForRuntime(root, scoped, payload.graph || {}, userCtx);
|
|
6530
|
+
const runNodeId = String(payload.runNodeId || "").trim();
|
|
6531
|
+
const plan = workspaceRunPlan(runtimeGraph, runNodeId, scoped.root);
|
|
6532
|
+
const plannedNodeIds = workspaceRunPlanNodeIds(runNodeId, plan);
|
|
6533
|
+
const scopeKey = workspaceRunKey(userCtx, scoped.flowSource || payload.flowSource || "user", flowId);
|
|
6534
|
+
const conflict = workspaceFindActiveRunConflict(scopeKey, plannedNodeIds);
|
|
6535
|
+
if (conflict) {
|
|
6536
|
+
json(res, 409, {
|
|
6537
|
+
error: "该 Run 与正在执行的 Run 共享节点",
|
|
6538
|
+
runNodeId: conflict.entry?.runNodeId || "",
|
|
6539
|
+
runId: conflict.entry?.runId || "",
|
|
6540
|
+
conflictNodeIds: conflict.conflictNodeIds,
|
|
6541
|
+
});
|
|
6113
6542
|
return;
|
|
6114
6543
|
}
|
|
6115
6544
|
const controller = new AbortController();
|
|
6545
|
+
const runId = String(payload.runSessionId || payload.runId || "").trim() || runLedgerId("workspace");
|
|
6546
|
+
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
6116
6547
|
const runEntry = {
|
|
6548
|
+
scopeKey,
|
|
6117
6549
|
controller,
|
|
6118
6550
|
child: null,
|
|
6119
|
-
runId
|
|
6551
|
+
runId,
|
|
6120
6552
|
userId: String(userCtx.userId || ""),
|
|
6121
6553
|
username: String(authUser?.username || userCtx.userId || ""),
|
|
6122
|
-
runNodeId
|
|
6554
|
+
runNodeId,
|
|
6123
6555
|
flowId,
|
|
6124
6556
|
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
6557
|
+
plannedNodeIds,
|
|
6125
6558
|
startedAt: Date.now(),
|
|
6126
6559
|
stopChild() {
|
|
6127
6560
|
if (this.child && !this.child.killed) {
|
|
@@ -6235,14 +6668,22 @@ export function startUiServer({
|
|
|
6235
6668
|
return;
|
|
6236
6669
|
}
|
|
6237
6670
|
const flowSource = url.searchParams.get("flowSource") || "user";
|
|
6238
|
-
const
|
|
6239
|
-
const
|
|
6671
|
+
const scopeKey = workspaceRunKey(userCtx, flowSource, flowId);
|
|
6672
|
+
const entries = workspaceActiveRunsForScope(scopeKey).map(([, entry]) => entry);
|
|
6673
|
+
const entry = entries[0] || null;
|
|
6240
6674
|
json(res, 200, {
|
|
6241
|
-
running:
|
|
6675
|
+
running: entries.length > 0,
|
|
6242
6676
|
flowId,
|
|
6243
6677
|
flowSource,
|
|
6244
6678
|
runNodeId: entry?.runNodeId || "",
|
|
6245
6679
|
startedAt: entry?.startedAt || null,
|
|
6680
|
+
runs: entries.map((item) => ({
|
|
6681
|
+
runId: item?.runId || "",
|
|
6682
|
+
runNodeId: item?.runNodeId || "",
|
|
6683
|
+
startedAt: item?.startedAt || null,
|
|
6684
|
+
plannedNodeIds: Array.isArray(item?.plannedNodeIds) ? item.plannedNodeIds : [],
|
|
6685
|
+
scheduled: item?.scheduled === true,
|
|
6686
|
+
})),
|
|
6246
6687
|
});
|
|
6247
6688
|
return;
|
|
6248
6689
|
}
|
|
@@ -6260,8 +6701,14 @@ export function startUiServer({
|
|
|
6260
6701
|
json(res, 400, { error: "Missing flowId" });
|
|
6261
6702
|
return;
|
|
6262
6703
|
}
|
|
6263
|
-
const
|
|
6264
|
-
const
|
|
6704
|
+
const scopeKey = workspaceRunKey(userCtx, payload.flowSource || "user", flowId);
|
|
6705
|
+
const runId = String(payload.runId || payload.runSessionId || "").trim();
|
|
6706
|
+
const runNodeId = String(payload.runNodeId || "").trim();
|
|
6707
|
+
const entries = workspaceActiveRunsForScope(scopeKey);
|
|
6708
|
+
const match = entries.find(([, item]) => runId && String(item?.runId || "") === runId)
|
|
6709
|
+
|| entries.find(([, item]) => runNodeId && String(item?.runNodeId || "") === runNodeId)
|
|
6710
|
+
|| (!runId && !runNodeId && entries.length === 1 ? entries[0] : null);
|
|
6711
|
+
const entry = match?.[1] || null;
|
|
6265
6712
|
if (!entry) {
|
|
6266
6713
|
json(res, 404, { error: "该 Workspace 未在运行" });
|
|
6267
6714
|
return;
|