@fieldwangai/agentflow 0.1.99 → 0.1.101
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/agent-runners.mjs +19 -0
- package/bin/lib/composer-agent.mjs +2 -0
- package/bin/lib/ui-server.mjs +333 -88
- package/bin/lib/workspace-run-controller.mjs +125 -0
- package/builtin/web-ui/dist/assets/{index-DR6YAyoA.js → index-DBiy5eMk.js} +75 -75
- package/builtin/web-ui/dist/index.html +1 -1
- package/package.json +1 -1
package/bin/lib/ui-server.mjs
CHANGED
|
@@ -128,6 +128,7 @@ import {
|
|
|
128
128
|
listWorkspaceRunLogs,
|
|
129
129
|
readWorkspaceRunLogEvents,
|
|
130
130
|
} from "./workspace-run-logs.mjs";
|
|
131
|
+
import { createWorkspaceRunController } from "./workspace-run-controller.mjs";
|
|
131
132
|
|
|
132
133
|
const MIME = {
|
|
133
134
|
".html": "text/html; charset=utf-8",
|
|
@@ -4947,6 +4948,8 @@ async function workspaceGeneratePlannedImplementationMarkdown({
|
|
|
4947
4948
|
prompt,
|
|
4948
4949
|
modelKey,
|
|
4949
4950
|
agentflowUserId: userCtx?.userId || "",
|
|
4951
|
+
detached: process.platform !== "win32",
|
|
4952
|
+
onChild: onActiveChild,
|
|
4950
4953
|
extraEnv: runtimeEnvForUser(userCtx, {
|
|
4951
4954
|
AGENTFLOW_IMPLEMENTATION_REF: implementationPath || "",
|
|
4952
4955
|
AGENTFLOW_NODE_RUN_DIR: runPackage?.nodeRunDir || "",
|
|
@@ -4967,7 +4970,6 @@ async function workspaceGeneratePlannedImplementationMarkdown({
|
|
|
4967
4970
|
emit?.({ type: "status", nodeId, line: `优化工具 ${tool || "thinking"}${sub ? ` (${sub})` : ""}` });
|
|
4968
4971
|
},
|
|
4969
4972
|
});
|
|
4970
|
-
if (typeof onActiveChild === "function") onActiveChild(handle.child || null);
|
|
4971
4973
|
try {
|
|
4972
4974
|
await handle.finished;
|
|
4973
4975
|
} finally {
|
|
@@ -5009,6 +5011,8 @@ async function workspaceGenerateImplementationMarkdown({
|
|
|
5009
5011
|
prompt,
|
|
5010
5012
|
modelKey,
|
|
5011
5013
|
agentflowUserId: userCtx?.userId || "",
|
|
5014
|
+
detached: process.platform !== "win32",
|
|
5015
|
+
onChild: onActiveChild,
|
|
5012
5016
|
extraEnv: runtimeEnvForUser(userCtx, {
|
|
5013
5017
|
AGENTFLOW_IMPLEMENTATION_REF: implementationPath || "",
|
|
5014
5018
|
AGENTFLOW_NODE_RUN_DIR: runPackage?.nodeRunDir || "",
|
|
@@ -5029,7 +5033,6 @@ async function workspaceGenerateImplementationMarkdown({
|
|
|
5029
5033
|
emit?.({ type: "status", line: `总结方案工具 ${tool || "thinking"}${sub ? ` (${sub})` : ""}` });
|
|
5030
5034
|
},
|
|
5031
5035
|
});
|
|
5032
|
-
if (typeof onActiveChild === "function") onActiveChild(handle.child || null);
|
|
5033
5036
|
try {
|
|
5034
5037
|
await handle.finished;
|
|
5035
5038
|
} finally {
|
|
@@ -5994,6 +5997,8 @@ async function workspaceRunToolNodejsScript({
|
|
|
5994
5997
|
userCtx,
|
|
5995
5998
|
envOverlay = {},
|
|
5996
5999
|
emit,
|
|
6000
|
+
signal,
|
|
6001
|
+
onActiveChild,
|
|
5997
6002
|
}) {
|
|
5998
6003
|
const scriptRef = String(instance?.scriptRef || "").trim();
|
|
5999
6004
|
const scriptAbs = scriptRef ? workspaceResolveFlowFile(scopedRoot, scriptRef, "scriptRef") : "";
|
|
@@ -6041,14 +6046,24 @@ async function workspaceRunToolNodejsScript({
|
|
|
6041
6046
|
|
|
6042
6047
|
const started = Date.now();
|
|
6043
6048
|
return await new Promise((resolve, reject) => {
|
|
6049
|
+
const processGroup = process.platform !== "win32";
|
|
6044
6050
|
const child = spawn(command, [], {
|
|
6045
6051
|
cwd: runPackage.nodeRunDir,
|
|
6046
6052
|
shell: true,
|
|
6047
6053
|
stdio: ["ignore", "pipe", "pipe"],
|
|
6048
6054
|
env,
|
|
6055
|
+
detached: processGroup,
|
|
6049
6056
|
});
|
|
6057
|
+
if (typeof onActiveChild === "function") onActiveChild(child, { processGroup });
|
|
6050
6058
|
let stdout = "";
|
|
6051
6059
|
let stderr = "";
|
|
6060
|
+
let settled = false;
|
|
6061
|
+
const finish = (callback) => {
|
|
6062
|
+
if (settled) return;
|
|
6063
|
+
settled = true;
|
|
6064
|
+
if (typeof onActiveChild === "function") onActiveChild(null);
|
|
6065
|
+
callback();
|
|
6066
|
+
};
|
|
6052
6067
|
child.stdout.setEncoding("utf-8");
|
|
6053
6068
|
child.stderr.setEncoding("utf-8");
|
|
6054
6069
|
child.stdout.on("data", (chunk) => {
|
|
@@ -6057,19 +6072,27 @@ async function workspaceRunToolNodejsScript({
|
|
|
6057
6072
|
child.stderr.on("data", (chunk) => {
|
|
6058
6073
|
stderr += String(chunk);
|
|
6059
6074
|
});
|
|
6060
|
-
child.on("error", reject);
|
|
6075
|
+
child.on("error", (error) => finish(() => reject(error)));
|
|
6061
6076
|
child.on("close", (code) => {
|
|
6077
|
+
if (signal?.aborted) {
|
|
6078
|
+
finish(() => {
|
|
6079
|
+
const error = new Error("Workspace run stopped");
|
|
6080
|
+
error.code = "WORKSPACE_RUN_ABORTED";
|
|
6081
|
+
reject(error);
|
|
6082
|
+
});
|
|
6083
|
+
return;
|
|
6084
|
+
}
|
|
6062
6085
|
if (stderr.trim()) {
|
|
6063
6086
|
emit?.({ type: "natural", kind: "warning", text: `[script stderr]\n${stderr.trim().slice(-4000)}` });
|
|
6064
6087
|
}
|
|
6065
6088
|
if (code !== 0) {
|
|
6066
|
-
reject(new Error(`tool_nodejs script exited ${code}${stderr.trim() ? `: ${stderr.trim().slice(-800)}` : ""}`));
|
|
6089
|
+
finish(() => reject(new Error(`tool_nodejs script exited ${code}${stderr.trim() ? `: ${stderr.trim().slice(-800)}` : ""}`)));
|
|
6067
6090
|
return;
|
|
6068
6091
|
}
|
|
6069
6092
|
const elapsedMs = Math.max(0, Date.now() - started);
|
|
6070
6093
|
emit?.({ type: "status", line: `Timing script: ${elapsedMs}ms`, timing: { label: "script", elapsedMs } });
|
|
6071
6094
|
const content = stdout.trim() || workspaceEnvelopeFromOutputFiles(outputRefs, runPackage.nodeRunDir);
|
|
6072
|
-
resolve(content);
|
|
6095
|
+
finish(() => resolve(content));
|
|
6073
6096
|
});
|
|
6074
6097
|
});
|
|
6075
6098
|
}
|
|
@@ -6691,6 +6714,8 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6691
6714
|
userCtx,
|
|
6692
6715
|
envOverlay: runEnv,
|
|
6693
6716
|
emit: (event) => emit({ ...event, nodeId }),
|
|
6717
|
+
signal,
|
|
6718
|
+
onActiveChild: opts.onActiveChild,
|
|
6694
6719
|
});
|
|
6695
6720
|
const normalizedAgentOutput = workspacePublishAgentOutputFiles(workspaceStructuredAgentOutput(content), runPackage);
|
|
6696
6721
|
const resultContent = normalizedAgentOutput.result || content;
|
|
@@ -6787,6 +6812,8 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6787
6812
|
prompt,
|
|
6788
6813
|
modelKey: nodeModelKey,
|
|
6789
6814
|
agentflowUserId: userCtx.userId || "",
|
|
6815
|
+
detached: process.platform !== "win32",
|
|
6816
|
+
onChild: opts.onActiveChild,
|
|
6790
6817
|
extraEnv: runtimeEnv({
|
|
6791
6818
|
AGENTFLOW_WORKSPACE_TMP_ROOT: runTmpRoot,
|
|
6792
6819
|
AGENTFLOW_NODE_RUN_DIR: runPackage.nodeRunDir,
|
|
@@ -6823,7 +6850,6 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
|
|
|
6823
6850
|
emit({ type: "status", nodeId, line: `工具 ${tool || "thinking"}${sub ? ` (${sub})` : ""}` });
|
|
6824
6851
|
},
|
|
6825
6852
|
});
|
|
6826
|
-
if (typeof opts.onActiveChild === "function") opts.onActiveChild(handle.child || null);
|
|
6827
6853
|
emitTiming(nodeId, "spawn-agent", spawnStartedAt, { attempt });
|
|
6828
6854
|
try {
|
|
6829
6855
|
await handle.finished;
|
|
@@ -7395,9 +7421,7 @@ function prdWorkflowReviewRenderTable(lines) {
|
|
|
7395
7421
|
].join("");
|
|
7396
7422
|
}
|
|
7397
7423
|
|
|
7398
|
-
function
|
|
7399
|
-
const { frontmatter, body } = prdWorkflowReviewSplitFrontmatter(markdown);
|
|
7400
|
-
const lines = String(body || "").replace(/\r\n/g, "\n").split("\n");
|
|
7424
|
+
function prdWorkflowReviewMarkdownLinesToHtml(lines) {
|
|
7401
7425
|
const html = [];
|
|
7402
7426
|
let paragraph = [];
|
|
7403
7427
|
let list = [];
|
|
@@ -7474,11 +7498,147 @@ function prdWorkflowReviewMarkdownToHtml(markdown) {
|
|
|
7474
7498
|
}
|
|
7475
7499
|
if (code) html.push(`<pre><code>${htmlEscapeAttribute(prdWorkflowReviewNormalizeText(code.lines.join("\n")))}</code></pre>`);
|
|
7476
7500
|
flushBlocks();
|
|
7477
|
-
return
|
|
7501
|
+
return html.join("\n");
|
|
7502
|
+
}
|
|
7503
|
+
|
|
7504
|
+
function prdWorkflowReviewActionLine(line) {
|
|
7505
|
+
const match = String(line || "").match(/^\s*[-*]\s+(?:\[( |x|X)\]\s+)?(A\d+|Action\s*\d+)(?=\s|[((::.-]|$)(.*)$/i);
|
|
7506
|
+
if (!match) return null;
|
|
7507
|
+
return {
|
|
7508
|
+
checked: match[1] ? match[1].toLowerCase() === "x" : null,
|
|
7509
|
+
label: match[2].replace(/\s+/g, " ").toUpperCase(),
|
|
7510
|
+
title: String(match[3] || "").replace(/^\s*[-::]\s*/, "").trim(),
|
|
7511
|
+
};
|
|
7512
|
+
}
|
|
7513
|
+
|
|
7514
|
+
function prdWorkflowReviewIsActionsHeading(line) {
|
|
7515
|
+
const heading = String(line || "").trim().match(/^(#{1,6})\s+(.+)$/);
|
|
7516
|
+
if (!heading) return null;
|
|
7517
|
+
const title = heading[2].replace(/[*_`]/g, "").trim();
|
|
7518
|
+
if (!/(?:\bTODO\s+Actions?\b|\bActions?\b|待办(?:事项|行动)?|行动项)/i.test(title)) return null;
|
|
7519
|
+
return { level: heading[1].length };
|
|
7520
|
+
}
|
|
7521
|
+
|
|
7522
|
+
function prdWorkflowReviewRenderActionSection(lines, sectionIndex) {
|
|
7523
|
+
const actionStarts = [];
|
|
7524
|
+
lines.forEach((line, index) => {
|
|
7525
|
+
const action = prdWorkflowReviewActionLine(line);
|
|
7526
|
+
if (action) actionStarts.push({ index, action });
|
|
7527
|
+
});
|
|
7528
|
+
if (!actionStarts.length) return prdWorkflowReviewMarkdownLinesToHtml(lines);
|
|
7529
|
+
|
|
7530
|
+
const ids = new Map();
|
|
7531
|
+
const actions = actionStarts.map(({ index, action }, actionIndex) => {
|
|
7532
|
+
const nextStart = actionStarts[actionIndex + 1]?.index ?? lines.length;
|
|
7533
|
+
let bodyStart = index + 1;
|
|
7534
|
+
const titleParts = [action.title].filter(Boolean);
|
|
7535
|
+
while (bodyStart < nextStart) {
|
|
7536
|
+
const continuation = String(lines[bodyStart] || "");
|
|
7537
|
+
if (!/^\s{2,}\S/.test(continuation) || /^\s*[-*]\s+/.test(continuation) || /^\s*```/.test(continuation)) break;
|
|
7538
|
+
titleParts.push(continuation.trim());
|
|
7539
|
+
bodyStart += 1;
|
|
7540
|
+
}
|
|
7541
|
+
const labelId = action.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || actionIndex + 1;
|
|
7542
|
+
const baseId = `action-${labelId}${sectionIndex ? `-${sectionIndex + 1}` : ""}`;
|
|
7543
|
+
const duplicate = ids.get(baseId) || 0;
|
|
7544
|
+
ids.set(baseId, duplicate + 1);
|
|
7545
|
+
return {
|
|
7546
|
+
...action,
|
|
7547
|
+
id: duplicate ? `${baseId}-${sectionIndex + 1}-${duplicate + 1}` : baseId,
|
|
7548
|
+
title: titleParts.join(" ") || action.label,
|
|
7549
|
+
body: lines.slice(bodyStart, nextStart),
|
|
7550
|
+
};
|
|
7551
|
+
});
|
|
7552
|
+
|
|
7553
|
+
const intro = prdWorkflowReviewMarkdownLinesToHtml(lines.slice(0, actionStarts[0].index));
|
|
7554
|
+
const navigation = actions.length > 1
|
|
7555
|
+
? `<nav class="action-index" aria-label="Action 快速跳转"><span class="action-index__label">快速跳转</span>${actions.map((action) => `<a href="#${htmlEscapeAttribute(action.id)}">${htmlEscapeAttribute(action.label)}</a>`).join("")}</nav>`
|
|
7556
|
+
: "";
|
|
7557
|
+
const cards = actions.map((action) => {
|
|
7558
|
+
const status = action.checked === null
|
|
7559
|
+
? ""
|
|
7560
|
+
: `<span class="action-card__status${action.checked ? " is-complete" : ""}">${action.checked ? "已完成" : "待完成"}</span>`;
|
|
7561
|
+
const body = prdWorkflowReviewMarkdownLinesToHtml(action.body);
|
|
7562
|
+
return `<section class="action-card${action.checked ? " is-complete" : ""}" id="${htmlEscapeAttribute(action.id)}">
|
|
7563
|
+
<div class="action-card__header">
|
|
7564
|
+
<span class="action-card__index">${htmlEscapeAttribute(action.label)}</span>
|
|
7565
|
+
<h3 class="action-card__title">${prdWorkflowReviewInlineMarkdown(action.title)}</h3>
|
|
7566
|
+
${status}
|
|
7567
|
+
</div>
|
|
7568
|
+
<div class="action-card__body">${body}</div>
|
|
7569
|
+
</section>`;
|
|
7570
|
+
}).join("\n");
|
|
7571
|
+
return [intro, navigation, cards].filter(Boolean).join("\n");
|
|
7572
|
+
}
|
|
7573
|
+
|
|
7574
|
+
function prdWorkflowReviewBodyToHtml(body) {
|
|
7575
|
+
const lines = String(body || "").replace(/\r\n/g, "\n").split("\n");
|
|
7576
|
+
const html = [];
|
|
7577
|
+
let cursor = 0;
|
|
7578
|
+
let sectionIndex = 0;
|
|
7579
|
+
while (cursor < lines.length) {
|
|
7580
|
+
const actionsHeading = prdWorkflowReviewIsActionsHeading(lines[cursor]);
|
|
7581
|
+
if (!actionsHeading) {
|
|
7582
|
+
const nextHeading = lines.findIndex((line, index) => index > cursor && prdWorkflowReviewIsActionsHeading(line));
|
|
7583
|
+
const end = nextHeading >= 0 ? nextHeading : lines.length;
|
|
7584
|
+
html.push(prdWorkflowReviewMarkdownLinesToHtml(lines.slice(cursor, end)));
|
|
7585
|
+
cursor = end;
|
|
7586
|
+
continue;
|
|
7587
|
+
}
|
|
7588
|
+
let end = cursor + 1;
|
|
7589
|
+
while (end < lines.length) {
|
|
7590
|
+
const heading = String(lines[end] || "").trim().match(/^(#{1,6})\s+/);
|
|
7591
|
+
if (heading && heading[1].length <= actionsHeading.level) break;
|
|
7592
|
+
end += 1;
|
|
7593
|
+
}
|
|
7594
|
+
html.push(prdWorkflowReviewMarkdownLinesToHtml([lines[cursor]]));
|
|
7595
|
+
html.push(prdWorkflowReviewRenderActionSection(lines.slice(cursor + 1, end), sectionIndex));
|
|
7596
|
+
sectionIndex += 1;
|
|
7597
|
+
cursor = end;
|
|
7598
|
+
}
|
|
7599
|
+
return html.filter(Boolean).join("\n");
|
|
7600
|
+
}
|
|
7601
|
+
|
|
7602
|
+
export function prdWorkflowReviewMarkdownToHtml(markdown) {
|
|
7603
|
+
const { frontmatter, body } = prdWorkflowReviewSplitFrontmatter(markdown);
|
|
7604
|
+
return [
|
|
7605
|
+
prdWorkflowReviewRenderFrontmatter(frontmatter),
|
|
7606
|
+
prdWorkflowReviewBodyToHtml(body),
|
|
7607
|
+
].filter(Boolean).join("\n");
|
|
7608
|
+
}
|
|
7609
|
+
|
|
7610
|
+
function prdWorkflowReviewExtractPageTitle(markdown, fallbackTitle) {
|
|
7611
|
+
const { frontmatter, body } = prdWorkflowReviewSplitFrontmatter(markdown);
|
|
7612
|
+
const lines = String(body || "").replace(/\r\n/g, "\n").split("\n");
|
|
7613
|
+
const firstContentIndex = lines.findIndex((line) => String(line || "").trim());
|
|
7614
|
+
const heading = firstContentIndex >= 0
|
|
7615
|
+
? String(lines[firstContentIndex] || "").trim().match(/^#\s+(.+)$/)
|
|
7616
|
+
: null;
|
|
7617
|
+
if (!heading) {
|
|
7618
|
+
return {
|
|
7619
|
+
markdown: String(markdown || ""),
|
|
7620
|
+
title: String(fallbackTitle || "PRD Workflow Review"),
|
|
7621
|
+
};
|
|
7622
|
+
}
|
|
7623
|
+
|
|
7624
|
+
lines.splice(firstContentIndex, 1);
|
|
7625
|
+
const markdownTitle = prdWorkflowReviewNormalizeText(heading[1])
|
|
7626
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
7627
|
+
.replace(/[`*_~]/g, "")
|
|
7628
|
+
.trim();
|
|
7629
|
+
const bodyWithoutTitle = lines.join("\n").replace(/^\n+/, "");
|
|
7630
|
+
return {
|
|
7631
|
+
markdown: [
|
|
7632
|
+
frontmatter ? `---\n${frontmatter}\n---` : "",
|
|
7633
|
+
bodyWithoutTitle,
|
|
7634
|
+
].filter(Boolean).join("\n\n"),
|
|
7635
|
+
title: markdownTitle || String(fallbackTitle || "PRD Workflow Review"),
|
|
7636
|
+
};
|
|
7478
7637
|
}
|
|
7479
7638
|
|
|
7480
|
-
function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
7481
|
-
const
|
|
7639
|
+
export function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
7640
|
+
const page = prdWorkflowReviewExtractPageTitle(markdown, title);
|
|
7641
|
+
const escapedTitle = htmlEscapeAttribute(page.title);
|
|
7482
7642
|
const escapedMeta = htmlEscapeAttribute([
|
|
7483
7643
|
meta.tapdId ? `TAPD ${meta.tapdId}` : "",
|
|
7484
7644
|
meta.stage ? `stage ${meta.stage}` : "",
|
|
@@ -7492,7 +7652,7 @@ function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
7492
7652
|
meta.persistence ? `persistence ${meta.persistence}` : "",
|
|
7493
7653
|
].filter(Boolean).join(" · ");
|
|
7494
7654
|
const escapedLifecycle = htmlEscapeAttribute(lifecycle);
|
|
7495
|
-
const renderedMarkdown = prdWorkflowReviewMarkdownToHtml(markdown
|
|
7655
|
+
const renderedMarkdown = prdWorkflowReviewMarkdownToHtml(page.markdown);
|
|
7496
7656
|
const rawHref = htmlEscapeAttribute(meta.rawHref || "?raw=1");
|
|
7497
7657
|
return `<!doctype html>
|
|
7498
7658
|
<html lang="zh-CN" data-theme="dark">
|
|
@@ -7504,57 +7664,80 @@ function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
7504
7664
|
:root {
|
|
7505
7665
|
color-scheme: dark;
|
|
7506
7666
|
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
7507
|
-
--bg: #
|
|
7508
|
-
--
|
|
7509
|
-
--panel:
|
|
7510
|
-
--panel-
|
|
7511
|
-
--
|
|
7512
|
-
--border:
|
|
7513
|
-
--border-soft: rgba(148,163,184,.14);
|
|
7667
|
+
--bg: #1a1b26;
|
|
7668
|
+
--panel: #1f2335;
|
|
7669
|
+
--panel-strong: #24283b;
|
|
7670
|
+
--panel-soft: #292e42;
|
|
7671
|
+
--border: #3b4261;
|
|
7672
|
+
--border-soft: #30364f;
|
|
7514
7673
|
--text: #e5e7eb;
|
|
7515
|
-
--heading: #
|
|
7516
|
-
--muted: #
|
|
7517
|
-
--body: #
|
|
7518
|
-
--link: #
|
|
7519
|
-
--
|
|
7520
|
-
--
|
|
7521
|
-
--
|
|
7522
|
-
--
|
|
7523
|
-
--
|
|
7674
|
+
--heading: #f4f4f5;
|
|
7675
|
+
--muted: #8b90a0;
|
|
7676
|
+
--body: #d4d4d8;
|
|
7677
|
+
--link: #7dcfff;
|
|
7678
|
+
--interactive: #7aa2f7;
|
|
7679
|
+
--purple: #bb9af7;
|
|
7680
|
+
--button: #24283b;
|
|
7681
|
+
--button-text: #cbd0da;
|
|
7682
|
+
--code-bg: #292e42;
|
|
7683
|
+
--code-inline: #7dcfff;
|
|
7684
|
+
--code-block: #16161e;
|
|
7685
|
+
--code-block-text: #e5e7eb;
|
|
7686
|
+
--action-bg: #1f2335;
|
|
7687
|
+
--action-header: #24283b;
|
|
7688
|
+
--pending-bg: rgba(224,175,104,.10);
|
|
7689
|
+
--pending-border: rgba(224,175,104,.34);
|
|
7690
|
+
--pending-text: #e0af68;
|
|
7691
|
+
--complete-bg: rgba(158,206,106,.10);
|
|
7692
|
+
--complete-border: rgba(158,206,106,.34);
|
|
7693
|
+
--complete-text: #9ece6a;
|
|
7694
|
+
--shadow: rgba(9,10,15,.28);
|
|
7524
7695
|
background: var(--bg);
|
|
7525
7696
|
}
|
|
7526
7697
|
:root[data-theme="light"] {
|
|
7527
7698
|
color-scheme: light;
|
|
7528
|
-
--bg: #
|
|
7529
|
-
--
|
|
7530
|
-
--panel:
|
|
7531
|
-
--panel-
|
|
7532
|
-
--
|
|
7533
|
-
--border:
|
|
7534
|
-
--
|
|
7535
|
-
--
|
|
7536
|
-
--
|
|
7537
|
-
--
|
|
7538
|
-
--
|
|
7539
|
-
--
|
|
7540
|
-
--
|
|
7541
|
-
--button
|
|
7542
|
-
--
|
|
7543
|
-
--code-
|
|
7544
|
-
--
|
|
7699
|
+
--bg: #e1e2e7;
|
|
7700
|
+
--panel: #f3f3f5;
|
|
7701
|
+
--panel-strong: #e9e9ed;
|
|
7702
|
+
--panel-soft: #dcdfe7;
|
|
7703
|
+
--border: #c8cad4;
|
|
7704
|
+
--border-soft: #d5d7df;
|
|
7705
|
+
--text: #4c505e;
|
|
7706
|
+
--heading: #343b58;
|
|
7707
|
+
--muted: #7f849c;
|
|
7708
|
+
--body: #4c505e;
|
|
7709
|
+
--link: #007197;
|
|
7710
|
+
--interactive: #2e7de9;
|
|
7711
|
+
--purple: #7847bd;
|
|
7712
|
+
--button: #e7e8ed;
|
|
7713
|
+
--button-text: #4c505e;
|
|
7714
|
+
--code-bg: #dcdfe7;
|
|
7715
|
+
--code-inline: #007197;
|
|
7716
|
+
--code-block: #d5d8e1;
|
|
7717
|
+
--code-block-text: #343b58;
|
|
7718
|
+
--action-bg: #f3f3f5;
|
|
7719
|
+
--action-header: #e9e9ed;
|
|
7720
|
+
--pending-bg: rgba(177,92,0,.08);
|
|
7721
|
+
--pending-border: rgba(177,92,0,.28);
|
|
7722
|
+
--pending-text: #9a5200;
|
|
7723
|
+
--complete-bg: rgba(88,117,57,.10);
|
|
7724
|
+
--complete-border: rgba(88,117,57,.28);
|
|
7725
|
+
--complete-text: #587539;
|
|
7726
|
+
--shadow: rgba(52,59,88,.10);
|
|
7545
7727
|
}
|
|
7546
7728
|
*, *::before, *::after { box-sizing: border-box; }
|
|
7547
7729
|
html, body { overflow-x: hidden; }
|
|
7548
|
-
body { margin: 0; min-height: 100vh; background:
|
|
7549
|
-
main { width: min(100%,
|
|
7730
|
+
body { margin: 0; min-height: 100vh; background: var(--bg); color: var(--text); }
|
|
7731
|
+
main { width: min(100%, 1180px); margin: 0 auto; padding: 40px 24px 72px; min-width: 0; }
|
|
7550
7732
|
header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 22px; }
|
|
7551
|
-
h1 { margin: 0 0 10px; font-size: clamp(28px,
|
|
7733
|
+
h1 { margin: 0 0 10px; font-size: clamp(28px, 4vw, 44px); line-height: 1.12; letter-spacing: -.015em; }
|
|
7552
7734
|
.meta { margin: 0; color: var(--muted); font-size: 14px; }
|
|
7553
7735
|
.toolbar { flex: 0 0 auto; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
|
|
7554
|
-
.raw, .theme-toggle { border: 1px solid
|
|
7736
|
+
.raw, .theme-toggle { border: 1px solid var(--border); border-radius: 999px; color: var(--button-text); background: var(--button); padding: 9px 14px; text-decoration: none; font-size: 13px; font-weight: 800; line-height: 1.2; }
|
|
7737
|
+
.raw:hover, .theme-toggle:hover { border-color: var(--interactive); color: var(--link); }
|
|
7555
7738
|
.theme-toggle { cursor: pointer; font-family: inherit; }
|
|
7556
|
-
.lifecycle { margin-top: 10px; display: inline-flex; max-width: 100%; border: 1px solid
|
|
7557
|
-
article { min-width: 0; border: 1px solid var(--border); border-radius: 14px; background: var(--panel); box-shadow: 0
|
|
7739
|
+
.lifecycle { margin-top: 10px; display: inline-flex; max-width: 100%; border: 1px solid var(--border); border-radius: 999px; background: var(--button); color: var(--muted); padding: 6px 10px; font-size: 12px; font-weight: 800; line-height: 1.35; overflow-wrap: anywhere; }
|
|
7740
|
+
article { min-width: 0; border: 1px solid var(--border); border-radius: 14px; background: var(--panel); box-shadow: 0 18px 50px var(--shadow); padding: clamp(20px, 4vw, 34px); }
|
|
7558
7741
|
article > *:first-child { margin-top: 0; }
|
|
7559
7742
|
article > *:last-child { margin-bottom: 0; }
|
|
7560
7743
|
h2, h3, h4, h5, h6 { margin: 1.7em 0 .65em; line-height: 1.25; letter-spacing: 0; color: var(--heading); overflow-wrap: anywhere; }
|
|
@@ -7565,10 +7748,10 @@ function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
7565
7748
|
ul { margin: .65rem 0 1rem; padding-left: 1.35rem; }
|
|
7566
7749
|
li { margin: .28rem 0; color: var(--body); }
|
|
7567
7750
|
li input { margin-right: .38rem; transform: translateY(1px); }
|
|
7568
|
-
code { display: inline; max-width: 100%; border: 1px solid var(--border-soft); border-radius: 6px; background: var(--code-bg); color: var(--
|
|
7751
|
+
code { display: inline; max-width: 100%; border: 1px solid var(--border-soft); border-radius: 6px; background: var(--code-bg); color: var(--code-inline); padding: .1rem .34rem; font-family: "SFMono-Regular", Consolas, monospace; font-size: .92em; white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
|
|
7569
7752
|
pre { max-width: 100%; overflow: auto; border: 1px solid var(--border); border-radius: 10px; background: var(--code-block); padding: 16px; line-height: 1.65; }
|
|
7570
|
-
pre code { border: 0; background: transparent; padding: 0; white-space: pre; overflow-wrap: normal; word-break: normal; }
|
|
7571
|
-
blockquote { margin: 1rem 0; border-left: 3px solid
|
|
7753
|
+
pre code { border: 0; background: transparent; color: var(--code-block-text); padding: 0; white-space: pre; overflow-wrap: normal; word-break: normal; }
|
|
7754
|
+
blockquote { margin: 1rem 0; border-left: 3px solid var(--purple); background: var(--panel-soft); padding: .75rem 1rem; color: var(--body); }
|
|
7572
7755
|
a { color: var(--link); text-decoration-thickness: .08em; text-underline-offset: .16em; overflow-wrap: anywhere; }
|
|
7573
7756
|
.table-wrap { max-width: 100%; overflow-x: auto; margin: 1rem 0 1.25rem; border: 1px solid var(--border-soft); border-radius: 10px; background: var(--panel-strong); }
|
|
7574
7757
|
table { width: 100%; max-width: 100%; border-collapse: collapse; table-layout: fixed; }
|
|
@@ -7580,7 +7763,34 @@ function prdWorkflowReviewHtml(title, markdown, meta = {}) {
|
|
|
7580
7763
|
.frontmatter table { min-width: 0; margin-top: .7rem; }
|
|
7581
7764
|
.frontmatter th { width: min(34%, 12rem); background: var(--panel-soft); color: var(--body); }
|
|
7582
7765
|
.frontmatter-list { margin: 0; padding-left: 1.1rem; }
|
|
7583
|
-
|
|
7766
|
+
.action-index { position: sticky; top: 10px; z-index: 4; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 1rem 0 1.25rem; border: 1px solid var(--border); border-radius: 12px; background: var(--panel-strong); box-shadow: 0 8px 22px var(--shadow); padding: 10px 12px; }
|
|
7767
|
+
.action-index__label { margin-right: 2px; color: var(--muted); font-size: 12px; font-weight: 800; }
|
|
7768
|
+
.action-index a { min-width: 38px; border: 1px solid color-mix(in srgb, var(--interactive) 38%, var(--border)); border-radius: 999px; background: color-mix(in srgb, var(--interactive) 10%, var(--button)); color: var(--interactive); padding: 5px 10px; text-align: center; text-decoration: none; font-size: 12px; font-weight: 900; }
|
|
7769
|
+
.action-index a:hover { border-color: var(--link); background: color-mix(in srgb, var(--interactive) 18%, var(--button)); color: var(--link); }
|
|
7770
|
+
.action-card { scroll-margin-top: 78px; margin: 0 0 20px; overflow: hidden; border: 1px solid var(--border); border-radius: 12px; background: var(--action-bg); box-shadow: 0 8px 24px var(--shadow); }
|
|
7771
|
+
.action-card:target { border-color: var(--interactive); box-shadow: 0 0 0 2px color-mix(in srgb, var(--interactive) 18%, transparent), 0 8px 24px var(--shadow); }
|
|
7772
|
+
.action-card__header { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: start; gap: 12px; border-bottom: 1px solid var(--border-soft); background: var(--action-header); padding: 16px 18px; }
|
|
7773
|
+
.action-card__index { display: inline-grid; place-items: center; min-width: 42px; min-height: 30px; border: 1px solid color-mix(in srgb, var(--interactive) 34%, var(--border)); border-radius: 8px; background: color-mix(in srgb, var(--interactive) 10%, var(--panel-soft)); color: var(--interactive); font: 900 13px/1 "SFMono-Regular", Consolas, monospace; }
|
|
7774
|
+
.action-card__title { margin: 3px 0 0; font-size: 16px; line-height: 1.55; letter-spacing: 0; color: var(--heading); }
|
|
7775
|
+
.action-card__status { margin-top: 2px; border: 1px solid var(--pending-border); border-radius: 999px; background: var(--pending-bg); color: var(--pending-text); padding: 5px 9px; font-size: 11px; font-weight: 900; white-space: nowrap; }
|
|
7776
|
+
.action-card__status.is-complete { border-color: var(--complete-border); background: var(--complete-bg); color: var(--complete-text); }
|
|
7777
|
+
.action-card__body { padding: 15px 20px 20px; }
|
|
7778
|
+
.action-card__body > *:first-child { margin-top: 0; }
|
|
7779
|
+
.action-card__body > *:last-child { margin-bottom: 0; }
|
|
7780
|
+
.action-card__body > ul { margin: 0 0 1rem; padding-left: 1.3rem; }
|
|
7781
|
+
.action-card__body > ul > li { margin: .55rem 0; padding-left: .15rem; }
|
|
7782
|
+
.action-card__body pre { margin: .9rem 0 1.1rem; }
|
|
7783
|
+
@media (max-width: 720px) {
|
|
7784
|
+
main { padding: 28px 14px 48px; }
|
|
7785
|
+
header { display: block; }
|
|
7786
|
+
.toolbar { justify-content: flex-start; margin-top: 14px; }
|
|
7787
|
+
article { padding: 18px; }
|
|
7788
|
+
th, td { padding: .58rem .65rem; }
|
|
7789
|
+
.action-index { top: 6px; }
|
|
7790
|
+
.action-card__header { grid-template-columns: auto minmax(0, 1fr); padding: 14px; }
|
|
7791
|
+
.action-card__status { grid-column: 2; justify-self: start; }
|
|
7792
|
+
.action-card__body { padding: 14px 16px 18px; }
|
|
7793
|
+
}
|
|
7584
7794
|
</style>
|
|
7585
7795
|
</head>
|
|
7586
7796
|
<body>
|
|
@@ -9026,6 +9236,14 @@ function workspaceRunEntryKey(scopeKey, runId) {
|
|
|
9026
9236
|
return `${scopeKey}:${String(runId || "").trim() || runLedgerId("workspace")}`;
|
|
9027
9237
|
}
|
|
9028
9238
|
|
|
9239
|
+
function workspaceRunControl(abortController) {
|
|
9240
|
+
return createWorkspaceRunController({
|
|
9241
|
+
abortController,
|
|
9242
|
+
gracefulTimeoutMs: 3_000,
|
|
9243
|
+
forceTimeoutMs: 1_500,
|
|
9244
|
+
});
|
|
9245
|
+
}
|
|
9246
|
+
|
|
9029
9247
|
function workspaceRuntimeNodeLabel(graph, nodeId, fallback = "Workspace Run") {
|
|
9030
9248
|
const id = String(nodeId || "").trim();
|
|
9031
9249
|
const instance = graph?.instances && typeof graph.instances === "object" ? graph.instances[id] : null;
|
|
@@ -9463,11 +9681,12 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
9463
9681
|
}
|
|
9464
9682
|
|
|
9465
9683
|
const controller = new AbortController();
|
|
9684
|
+
const runControl = workspaceRunControl(controller);
|
|
9466
9685
|
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
9467
9686
|
const runEntry = {
|
|
9468
9687
|
scopeKey,
|
|
9469
9688
|
controller,
|
|
9470
|
-
|
|
9689
|
+
runControl,
|
|
9471
9690
|
runId,
|
|
9472
9691
|
userId: userCtx.userId,
|
|
9473
9692
|
username: String(authUser.username || entry.username || userCtx.userId),
|
|
@@ -9478,11 +9697,6 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
9478
9697
|
plannedNodeIds,
|
|
9479
9698
|
startedAt: Date.now(),
|
|
9480
9699
|
scheduled: true,
|
|
9481
|
-
stopChild() {
|
|
9482
|
-
if (this.child && !this.child.killed) {
|
|
9483
|
-
try { this.child.kill("SIGTERM"); } catch (_) {}
|
|
9484
|
-
}
|
|
9485
|
-
},
|
|
9486
9700
|
};
|
|
9487
9701
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
9488
9702
|
appendWorkspaceRunStarted(runEntry);
|
|
@@ -9496,9 +9710,8 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
9496
9710
|
timezone: config.timezone,
|
|
9497
9711
|
lastError: "",
|
|
9498
9712
|
});
|
|
9499
|
-
const setActiveChild = (child) => {
|
|
9500
|
-
|
|
9501
|
-
if (controller.signal.aborted) runEntry.stopChild();
|
|
9713
|
+
const setActiveChild = (child, childOptions = {}) => {
|
|
9714
|
+
runControl.setChild(child, childOptions);
|
|
9502
9715
|
};
|
|
9503
9716
|
try {
|
|
9504
9717
|
const result = await runWorkspaceGraph(root, scoped.root, {
|
|
@@ -9531,23 +9744,28 @@ async function runWorkspaceScheduledEntry(root, entry) {
|
|
|
9531
9744
|
} catch (e) {
|
|
9532
9745
|
const endedAt = Date.now();
|
|
9533
9746
|
const error = (e && e.message) || String(e);
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9747
|
+
const stopped = isWorkspaceRunAbortError(e) || controller.signal.aborted;
|
|
9748
|
+
const finalStatus = stopped ? "stopped" : "failed";
|
|
9749
|
+
appendWorkspaceRunFinished({ ...runEntry, endedAt, durationMs: endedAt - runEntry.startedAt }, finalStatus);
|
|
9750
|
+
appendWorkspaceRunLogEvent(runLog.runId, stopped
|
|
9751
|
+
? { type: "stopped", message: "Workspace run stopped", ts: endedAt }
|
|
9752
|
+
: { type: "error", error, ts: endedAt });
|
|
9753
|
+
finishWorkspaceRunLogSession(runLog.runId, finalStatus, {
|
|
9537
9754
|
endedAt,
|
|
9538
9755
|
durationMs: endedAt - runEntry.startedAt,
|
|
9539
9756
|
runNodeId: targetRunNodeId,
|
|
9540
|
-
error,
|
|
9757
|
+
error: stopped ? "" : error,
|
|
9541
9758
|
});
|
|
9542
9759
|
updateWorkspaceScheduleEntry(entry.key, {
|
|
9543
9760
|
nextRunAt: computeNext(config),
|
|
9544
9761
|
lastFinishedAt: endedAt,
|
|
9545
|
-
lastStatus:
|
|
9546
|
-
lastError: error,
|
|
9547
|
-
lastErrorAt: endedAt,
|
|
9762
|
+
lastStatus: finalStatus,
|
|
9763
|
+
lastError: stopped ? "" : error,
|
|
9764
|
+
...(stopped ? {} : { lastErrorAt: endedAt }),
|
|
9548
9765
|
});
|
|
9549
|
-
log.info(`[workspace-scheduler] failed ${entry.flowId}/${targetRunNodeId}: ${error}`);
|
|
9766
|
+
if (!stopped) log.info(`[workspace-scheduler] failed ${entry.flowId}/${targetRunNodeId}: ${error}`);
|
|
9550
9767
|
} finally {
|
|
9768
|
+
runControl.finish(controller.signal.aborted ? "stopped" : "finished");
|
|
9551
9769
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
9552
9770
|
}
|
|
9553
9771
|
}
|
|
@@ -11516,13 +11734,14 @@ export function startUiServer({
|
|
|
11516
11734
|
return;
|
|
11517
11735
|
}
|
|
11518
11736
|
const controller = new AbortController();
|
|
11737
|
+
const runControl = workspaceRunControl(controller);
|
|
11519
11738
|
const runId = String(payload.runSessionId || payload.runId || "").trim() || runLedgerId("workspace");
|
|
11520
11739
|
const runKey = workspaceRunEntryKey(scopeKey, runId);
|
|
11521
11740
|
const runAlias = String(payload.runAlias || "").trim() || workspaceRuntimeNodeLabel(runtimeGraph, runNodeId, "Workspace Run");
|
|
11522
11741
|
const runEntry = {
|
|
11523
11742
|
scopeKey,
|
|
11524
11743
|
controller,
|
|
11525
|
-
|
|
11744
|
+
runControl,
|
|
11526
11745
|
runId,
|
|
11527
11746
|
userId: String(userCtx.userId || ""),
|
|
11528
11747
|
username: String(authUser?.username || userCtx.userId || ""),
|
|
@@ -11532,11 +11751,6 @@ export function startUiServer({
|
|
|
11532
11751
|
flowSource: scoped.flowSource || payload.flowSource || "user",
|
|
11533
11752
|
plannedNodeIds,
|
|
11534
11753
|
startedAt: Date.now(),
|
|
11535
|
-
stopChild() {
|
|
11536
|
-
if (this.child && !this.child.killed) {
|
|
11537
|
-
try { this.child.kill("SIGTERM"); } catch (_) {}
|
|
11538
|
-
}
|
|
11539
|
-
},
|
|
11540
11754
|
};
|
|
11541
11755
|
const runLog = createWorkspaceRunLogSession({
|
|
11542
11756
|
runId,
|
|
@@ -11553,11 +11767,11 @@ export function startUiServer({
|
|
|
11553
11767
|
});
|
|
11554
11768
|
activeWorkspaceRuns.set(runKey, runEntry);
|
|
11555
11769
|
appendWorkspaceRunStarted(runEntry);
|
|
11556
|
-
const setActiveChild = (child) => {
|
|
11557
|
-
|
|
11558
|
-
if (controller.signal.aborted) runEntry.stopChild();
|
|
11770
|
+
const setActiveChild = (child, childOptions = {}) => {
|
|
11771
|
+
runControl.setChild(child, childOptions);
|
|
11559
11772
|
};
|
|
11560
|
-
const clearActiveRun = () => {
|
|
11773
|
+
const clearActiveRun = (status = "finished") => {
|
|
11774
|
+
runControl.finish(status);
|
|
11561
11775
|
if (activeWorkspaceRuns.get(runKey) === runEntry) activeWorkspaceRuns.delete(runKey);
|
|
11562
11776
|
};
|
|
11563
11777
|
if (wantsStream) {
|
|
@@ -11626,7 +11840,7 @@ export function startUiServer({
|
|
|
11626
11840
|
}
|
|
11627
11841
|
res.end();
|
|
11628
11842
|
} finally {
|
|
11629
|
-
clearActiveRun();
|
|
11843
|
+
clearActiveRun(controller.signal.aborted ? "stopped" : "finished");
|
|
11630
11844
|
}
|
|
11631
11845
|
return;
|
|
11632
11846
|
}
|
|
@@ -11684,7 +11898,7 @@ export function startUiServer({
|
|
|
11684
11898
|
throw e;
|
|
11685
11899
|
}
|
|
11686
11900
|
} finally {
|
|
11687
|
-
clearActiveRun();
|
|
11901
|
+
clearActiveRun(controller.signal.aborted ? "stopped" : "finished");
|
|
11688
11902
|
}
|
|
11689
11903
|
} catch (e) {
|
|
11690
11904
|
json(res, 500, { error: (e && e.message) || String(e) });
|
|
@@ -11750,6 +11964,7 @@ export function startUiServer({
|
|
|
11750
11964
|
const entry = entries[0] || null;
|
|
11751
11965
|
json(res, 200, {
|
|
11752
11966
|
running: entries.length > 0,
|
|
11967
|
+
state: entry?.runControl?.state || (entries.length > 0 ? "running" : "idle"),
|
|
11753
11968
|
flowId,
|
|
11754
11969
|
flowSource,
|
|
11755
11970
|
runNodeId: entry?.runNodeId || "",
|
|
@@ -11762,6 +11977,7 @@ export function startUiServer({
|
|
|
11762
11977
|
startedAt: item?.startedAt || null,
|
|
11763
11978
|
plannedNodeIds: Array.isArray(item?.plannedNodeIds) ? item.plannedNodeIds : [],
|
|
11764
11979
|
scheduled: item?.scheduled === true,
|
|
11980
|
+
state: item?.runControl?.state || "running",
|
|
11765
11981
|
})),
|
|
11766
11982
|
});
|
|
11767
11983
|
return;
|
|
@@ -11792,9 +12008,38 @@ export function startUiServer({
|
|
|
11792
12008
|
json(res, 404, { error: "该 Workspace 未在运行" });
|
|
11793
12009
|
return;
|
|
11794
12010
|
}
|
|
11795
|
-
|
|
11796
|
-
|
|
11797
|
-
|
|
12011
|
+
appendWorkspaceRunLogEvent(entry.runId, {
|
|
12012
|
+
type: "stop-requested",
|
|
12013
|
+
runNodeId: entry.runNodeId || "",
|
|
12014
|
+
ts: Date.now(),
|
|
12015
|
+
});
|
|
12016
|
+
const result = await entry.runControl.stop();
|
|
12017
|
+
if (!result.stopped) {
|
|
12018
|
+
appendWorkspaceRunLogEvent(entry.runId, {
|
|
12019
|
+
type: "stop-failed",
|
|
12020
|
+
runNodeId: entry.runNodeId || "",
|
|
12021
|
+
reason: result.timedOut ? "timeout" : "unknown",
|
|
12022
|
+
ts: Date.now(),
|
|
12023
|
+
});
|
|
12024
|
+
json(res, 409, {
|
|
12025
|
+
error: "停止请求已发送,但运行进程未能退出",
|
|
12026
|
+
ok: false,
|
|
12027
|
+
stopped: false,
|
|
12028
|
+
state: entry.runControl.state,
|
|
12029
|
+
});
|
|
12030
|
+
return;
|
|
12031
|
+
}
|
|
12032
|
+
appendWorkspaceRunLogEvent(entry.runId, {
|
|
12033
|
+
type: "stop-completed",
|
|
12034
|
+
runNodeId: entry.runNodeId || "",
|
|
12035
|
+
forced: result.forced === true,
|
|
12036
|
+
ts: Date.now(),
|
|
12037
|
+
});
|
|
12038
|
+
json(res, 200, {
|
|
12039
|
+
ok: true,
|
|
12040
|
+
stopped: true,
|
|
12041
|
+
forced: result.forced === true,
|
|
12042
|
+
});
|
|
11798
12043
|
return;
|
|
11799
12044
|
}
|
|
11800
12045
|
|