diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js
index 7884547..1e0ee02 100644
--- a/web/storyforge-web-v4/assets/app.js
+++ b/web/storyforge-web-v4/assets/app.js
@@ -2075,6 +2075,78 @@ function openCurrentOneLinerRunResultAction(runId = "") {
});
}
+function openConfirmOneLinerRunAction(runId = "") {
+ const run = safeArray(appState.onelinerRuns).find((item) => item.id === runId) || getCurrentOneLinerRun();
+ if (!run?.id) {
+ rememberAction("还没有可确认的任务", "当前没有主 Agent 待确认任务。", "orange");
+ renderAll();
+ return;
+ }
+ const planSteps = safeArray(run.plan?.steps).slice(0, 5);
+ const previewAction = run.recommended_preview_action || null;
+ const tags = [
+ run.platform_label ? `${escapeHtml(run.platform_label)}` : "",
+ `${escapeHtml(run.platform_scope === "all_platforms" ? "全平台" : "单平台")}`,
+ `${escapeHtml(onelinerIntentLabel(run.intent_key))}`,
+ run.source_screen ? `${escapeHtml(run.source_screen)}` : ""
+ ].filter(Boolean).join("");
+ const planHtml = `
+
+
当前计划
+
${escapeHtml(run.plan?.summary || run.summary || "主 Agent 会先按这张确认卡理解目标,再继续执行。")}
+
${tags}
+ ${planSteps.length ? `
+
+ ${planSteps.map((step, index) => `
+
+
步骤 ${escapeHtml(formatNumber(index + 1))}
+
${escapeHtml(step)}
+
+ `).join("")}
+
+ ` : ""}
+ ${previewAction ? `
+
+
预计落点
+
${escapeHtml(previewAction.summary || "执行后会回到更合适的业务页面继续推进。")}
+
+ ${escapeHtml(previewAction.label || "回到对应页面")}
+ ${previewAction.screen ? `${escapeHtml(previewAction.screen)}` : ""}
+
+
+ ` : ""}
+
+ `;
+ openActionModal({
+ title: "确认主 Agent 执行计划",
+ description: "确认后,主 Agent 会按当前计划进入执行流;你也可以先补一句执行说明。",
+ submitLabel: "确认执行",
+ fields: [
+ {
+ name: "plan",
+ type: "html",
+ label: "执行计划",
+ html: `${planHtml}
`
+ },
+ {
+ name: "reason",
+ type: "textarea",
+ label: "补充说明",
+ rows: 3,
+ placeholder: "可选:比如优先做抖音,或者先给我一版更保守的执行建议。",
+ value: ""
+ }
+ ],
+ onSubmit: async (values) => {
+ const payload = await confirmOneLinerRun(run.id, values.reason || "user confirmed");
+ return {
+ keepOpen: false,
+ payload
+ };
+ }
+ });
+}
+
async function loadPlatformAccount(platform, accountId, requestToken = 0) {
if (!accountId) return;
const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform());
@@ -9784,14 +9856,7 @@ document.addEventListener("click", async (event) => {
return;
}
if (name === "confirm-oneliner-run") {
- try {
- setBusy(true, "正在确认执行计划...");
- await confirmOneLinerRun(action.dataset.runId || "", "user confirmed");
- } catch (error) {
- presentActionFailure(error, "主 Agent 确认失败");
- } finally {
- setBusy(false, "");
- }
+ openConfirmOneLinerRunAction(action.dataset.runId || "");
return;
}
if (name === "cancel-oneliner-run") {
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
index 7804866..f22c423 100644
--- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs
+++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
@@ -140,6 +140,19 @@ test("oneliner panel includes a dedicated runtime header for agent runs", () =>
assert.match(runtime, /recommended_action/);
});
+test("confirm-oneliner-run opens a dedicated confirmation sheet before execution", () => {
+ const confirmSheet = extractBetween(APP, "function openConfirmOneLinerRunAction(runId = \"\")", "async function loadPlatformAccount(");
+ const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
+
+ assert.match(confirmSheet, /确认主 Agent 执行计划/);
+ assert.match(confirmSheet, /当前计划/);
+ assert.match(confirmSheet, /预计落点/);
+ assert.match(confirmSheet, /submitLabel: "确认执行"/);
+ assert.match(confirmSheet, /reason/);
+ assert.match(confirmSheet, /confirmOneLinerRun\(run\.id,\s*values\.reason/);
+ assert.match(actions, /openConfirmOneLinerRunAction\(action\.dataset\.runId \|\| ""\)/);
+});
+
test("oneliner meta and action handlers expose governance entry points", () => {
const meta = extractBetween(APP, "function renderOneLinerUi()", "function openOneLinerPanel()");
const messages = extractBetween(APP, "function renderOneLinerMessagesHtml()", "function renderOneLinerUi()");