diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 7779efa..ecdd53c 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -54,6 +54,7 @@ const appState = { selectedOnelinerSessionId: "", onelinerRuns: [], selectedOnelinerRunId: "", + lastCompletedOnelinerRunId: "", onelinerMessages: [], onelinerActionRegistry: [], platformAgents: [], @@ -975,6 +976,8 @@ function renderOneLinerRunsHtml() { } const runEvents = safeArray(currentRun.events).slice(-3); const planSteps = safeArray(currentRun.plan?.steps).slice(0, 4); + const resultPayload = currentRun.result && typeof currentRun.result === "object" ? currentRun.result : null; + const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length); const runStatusLabel = { needs_confirmation: "待确认", queued: "排队中", @@ -1004,6 +1007,7 @@ function renderOneLinerRunsHtml() { ${escapeHtml(runStatusLabel)} ${currentRun.platform_label ? `${escapeHtml(currentRun.platform_label)}` : ""} ${escapeHtml(onelinerIntentLabel(currentRun.intent_key))} + ${currentRun.source_screen ? `${escapeHtml(currentRun.source_screen)}` : ""} ${currentRun.active_admin_override_notice?.title ? ` @@ -1012,6 +1016,15 @@ function renderOneLinerRunsHtml() {

${escapeHtml(currentRun.active_admin_override_notice.summary || "当前运行会优先遵循管理员覆盖层。")}

` : ""} +
+

当前计划

+

${escapeHtml(currentRun.plan?.summary || currentRun.summary || "主 Agent 会先按这张确认卡理解目标,再继续执行。")}

+
+ ${escapeHtml(currentRun.source_action_key || "manual-handoff")} + ${escapeHtml(currentRun.platform_scope === "all_platforms" ? "全平台" : "单平台")} + ${currentRun.delivery_mode ? `${escapeHtml(currentRun.delivery_mode)}` : ""} +
+
${planSteps.length ? `
${planSteps.map((step, index) => ` @@ -1029,7 +1042,14 @@ function renderOneLinerRunsHtml() { ` : ` ${escapeHtml(currentRun.status_summary || "主 Agent 正在推进中")} `} + ${hasResultPayload ? `查看结果` : ""}
+ ${hasResultPayload ? ` +
+

执行结果

+
${renderOneLinerExecutionPayloadHtml(currentRun.result)}
+
+ ` : ""} ${runEvents.length ? `
${runEvents.map((item) => ` @@ -1401,6 +1421,7 @@ async function logoutSession() { appState.selectedOnelinerSessionId = ""; appState.onelinerRuns = []; appState.selectedOnelinerRunId = ""; + appState.lastCompletedOnelinerRunId = ""; appState.onelinerMessages = []; appState.onelinerActionRegistry = []; appState.platformAgents = []; @@ -1465,6 +1486,10 @@ async function hydrateSelectedOneLinerRun() { ? runs.map((item) => (item.id === detail.id ? detail : item)) : [detail, ...runs]; appState.onelinerRuns = nextRuns; + if (detail.run_status === "done" && detail.id && appState.lastCompletedOnelinerRunId !== detail.id) { + rememberAction("主 Agent 已完成本轮", detail.result?.execution_summary || detail.status_summary || detail.summary || "当前运行已经完成,可以继续执行下一步。", "green", detail); + appState.lastCompletedOnelinerRunId = detail.id; + } return detail; } @@ -1703,6 +1728,29 @@ function renderOneLinerExecutionPayloadHtml(payload) { if (!payload || typeof payload !== "object") { return `

没有返回执行结果

当前执行器没有附带额外数据。

`; } + if (payload.result_kind === "main_agent_plan") { + return ` +
+

${escapeHtml(payload.goal || "主 Agent 执行建议")}

+

${escapeHtml(payload.execution_summary || payload.summary_text || "已形成一版可继续执行的主 Agent 建议。")}

+
+ ${payload.platform ? `${escapeHtml(platformLabel(payload.platform))}` : ""} + ${escapeHtml(payload.platform_scope === "all_platforms" ? "全平台" : "单平台")} + 已收口 +
+
+ ${safeArray(payload.next_steps).length ? ` +
+ ${safeArray(payload.next_steps).slice(0, 4).map((step, index) => ` +
+

下一步 ${escapeHtml(formatNumber(index + 1))}

+

${escapeHtml(step)}

+
+ `).join("")} +
+ ` : ""} + `; + } if (payload.job) { const job = payload.job || {}; const sourceJob = payload.source_job || {}; @@ -1909,6 +1957,32 @@ async function executeOneLinerAction(executorKey, options = {}) { return payload; } +function openCurrentOneLinerRunResultAction(runId = "") { + const currentRun = safeArray(appState.onelinerRuns).find((item) => item.id === runId) || getCurrentOneLinerRun(); + if (!currentRun?.id) { + rememberAction("还没有可查看的结果", "当前主 Agent 任务还没有返回可展示的执行结果。", "orange"); + renderAll(); + return; + } + if (!currentRun.result || !Object.keys(currentRun.result || {}).length) { + rememberAction("结果还在生成中", currentRun.status_summary || "当前主 Agent 任务还没有返回执行结果。", "orange", currentRun); + renderAll(); + return; + } + openActionModal({ + title: currentRun.title || currentRun.plan?.goal || "主 Agent 执行结果", + description: currentRun.result?.execution_summary || currentRun.status_summary || "这是当前主 Agent 任务的执行结果。", + hideSubmit: true, + fields: [ + { + type: "html", + label: "执行结果", + html: `
${renderOneLinerExecutionPayloadHtml(currentRun.result)}
` + } + ] + }); +} + async function loadPlatformAccount(platform, accountId, requestToken = 0) { if (!accountId) return; const normalizedPlatform = normalizePlatformValue(platform, getPreferredPlatform()); @@ -3992,6 +4066,7 @@ function button(label, action, tone = "secondary", options = {}) { if (options.disabledReason) classes.push("is-disabled"); const targetAction = options.disabledReason ? "show-disabled-reason" : action; const title = options.disabledReason || options.title || ""; + const attrs = options.attrs || ""; return ` `.replace(/\s+/g, " ").trim(); } @@ -4097,6 +4173,35 @@ function renderIntegrationOverviewPanel(options = {}) { `; } +function buildMainAgentHandoffAttrs({ + sourceScreen = "", + sourceActionKey = "", + intentKey = "custom", + title = "", + goal = "", + summary = "", + platform = "", + platformScope = "single_platform", + planSteps = [] +} = {}) { + const attrs = [ + `data-source-screen="${escapeHtml(sourceScreen || appState.screen || "dashboard")}"`, + `data-source-action-key="${escapeHtml(sourceActionKey || "main-agent-handoff")}"`, + `data-intent-key="${escapeHtml(intentKey || "custom")}"`, + `data-title="${escapeHtml(title || goal || "交给主 Agent 处理")}"`, + `data-goal="${escapeHtml(goal || title || "交给主 Agent 处理")}"`, + `data-summary="${escapeHtml(summary || "")}"`, + `data-platform-scope="${escapeHtml(platformScope || "single_platform")}"` + ]; + if (platform) { + attrs.push(`data-platform="${escapeHtml(platform)}"`); + } + if (safeArray(planSteps).length) { + attrs.push(`data-plan-steps="${escapeHtml(JSON.stringify(safeArray(planSteps)))}"`); + } + return attrs.join(" "); +} + function renderEmptyState(title, description) { return `
${escapeHtml(title)}

${escapeHtml(description)}

`; } @@ -4997,10 +5102,20 @@ function renderAutomationScreen() { { value: "guards", label: "动作防呆" } ]; const activeTab = getActiveDetailTab("automationDetailTab", tabs); + const automationHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "automation", + sourceActionKey: "automation-main-agent-handoff", + intentKey: "ops_admin", + title: "继续检查自动流程", + goal: "继续检查自动流程", + summary: "让主 Agent 结合依赖健康和动作防呆状态,给出下一步处理建议。", + platform: getPreferredPlatform(), + planSteps: ["读取当前依赖健康", "检查动作防呆和拦截状态", "生成下一步处理建议"] + }); return screenShell( "自动流程", "自动同步、日报生成和失败补跑先统一看这里。", - `${button("刷新", "refresh-data")} ${button("OneLiner", "open-oneliner")} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`, + `${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: automationHandoffAttrs })} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`, `

自动流程

@@ -5143,6 +5258,16 @@ function renderPlaybookScreen() { const localCatalog = appState.localModelCatalog || {}; const activeAdminOverrideNotice = appState.onelinerGovernanceEffective?.active_admin_override_notice || null; const gatewayModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean); + const playbookHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "playbook", + sourceActionKey: "playbook-main-agent-handoff", + intentKey: "custom", + title: "继续梳理当前 Agent 工作区", + goal: "继续梳理当前 Agent 工作区", + summary: "让主 Agent 结合当前 Agent、模型和策略状态,给出下一步执行建议。", + platform: appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform(), + planSteps: ["读取当前 Agent 与模型配置", "检查当前策略与平台 Agent 缺口", "生成下一步执行建议"] + }); const tabs = [ { value: "workspace", label: "当前 Agent 工作台" }, { value: "platform_agents", label: "平台 Agent" }, @@ -5152,7 +5277,7 @@ function renderPlaybookScreen() { return screenShell( "Agent", "这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。", - `${button("配置 OneLiner", "open-oneliner-profile")} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "open-create-assistant")} ${button("生成文案", "open-generate-copy")} ${button("去生产", "goto-production", "primary")}`, + `${button("配置 OneLiner", "open-oneliner-profile")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "open-create-assistant")} ${button("去生产", "goto-production", "primary")}`, `

Agent 概览

@@ -5181,7 +5306,7 @@ function renderPlaybookScreen() {
${escapeHtml(appState.onelinerProfile?.display_name || "OneLiner")} ${escapeHtml(appState.onelinerProfile?.default_platform ? platformLabel(appState.onelinerProfile.default_platform) : "未设默认平台")} - 打开对话 + ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}
@@ -5573,10 +5698,20 @@ function renderStrategyScreen() { const project = getSelectedProject(); const platform = appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform(); const activeAdminOverrideNotice = appState.onelinerGovernanceEffective?.active_admin_override_notice || null; + const strategyHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "strategy", + sourceActionKey: "strategy-main-agent-handoff", + intentKey: "custom", + title: "继续调整我的策略", + goal: "继续调整我的策略", + summary: "让主 Agent 结合当前生效层、个人策略和管理员覆盖,给出下一步治理建议。", + platform, + planSteps: ["读取当前生效策略", "检查用户层与管理员覆盖差异", "生成下一步治理建议"] + }); return screenShell( "我的策略", "把你和主 Agent 的对话沉淀成可查看、可回滚、可追溯的个人策略层。", - `${button("编辑全局策略", "open-user-global-policy")} ${button("编辑当前平台策略", "open-user-platform-policy", "primary")} ${button("打开 OneLiner", "open-oneliner")}`, + `${button("编辑全局策略", "open-user-global-policy")} ${button("编辑当前平台策略", "open-user-platform-policy", "primary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: strategyHandoffAttrs })}`, `

当前策略工作区

@@ -9369,6 +9504,10 @@ document.addEventListener("click", async (event) => { } return; } + if (name === "open-oneliner-run-result") { + openCurrentOneLinerRunResultAction(action.dataset.runId || ""); + return; + } if (name === "confirm-oneliner-run") { try { setBusy(true, "正在确认执行计划..."); diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 54f56e6..9212656 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -133,6 +133,9 @@ test("oneliner panel includes a dedicated runtime header for agent runs", () => assert.match(source, /data-role="oneliner-runs"/); assert.match(runtime, /confirm-oneliner-run/); assert.match(runtime, /cancel-oneliner-run/); + assert.match(runtime, /当前计划/); + assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/); + assert.match(runtime, /open-oneliner-run-result/); }); test("oneliner meta and action handlers expose governance entry points", () => { @@ -147,6 +150,16 @@ test("oneliner meta and action handlers expose governance entry points", () => { assert.match(actions, /name === "open-system-main-policy"/); assert.match(actions, /name === "handoff-to-main-agent"/); assert.match(actions, /name === "confirm-oneliner-run"/); + assert.match(actions, /name === "open-oneliner-run-result"/); +}); + +test("oneliner runtime remembers completed runs exactly once after hydration", () => { + const hydrate = extractBetween(APP, "async function hydrateSelectedOneLinerRun()", "async function loadAgentControlSurfaces(projectId = \"\")"); + const state = extractBetween(APP, "const appState = {", "};\n\nlet PLATFORM_RUNTIME"); + assert.match(state, /lastCompletedOnelinerRunId/); + assert.match(hydrate, /detail\.run_status === "done"/); + assert.match(hydrate, /appState\.lastCompletedOnelinerRunId !== detail\.id/); + assert.match(hydrate, /rememberAction\("主 Agent 已完成本轮"/); }); test("system governance saves refresh control surfaces after persisting", () => { @@ -199,12 +212,19 @@ test("governance UI exposes admin override target picker and history rollback en test("user governance UI exposes personal history and rollback entrypoints", () => { const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()"); const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()"); + const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()"); const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); assert.match(playbook, /open-user-global-policy-history/); assert.match(playbook, /open-user-platform-policy-history/); + assert.match(playbook, /handoff-to-main-agent/); + assert.match(playbook, /playbook-main-agent-handoff/); assert.match(strategy, /active_admin_override_notice/); assert.match(strategy, /管理员覆盖生效中/); + assert.match(strategy, /handoff-to-main-agent/); + assert.match(strategy, /strategy-main-agent-handoff/); + assert.match(automation, /handoff-to-main-agent/); + assert.match(automation, /automation-main-agent-handoff/); assert.match(actions, /name === "open-user-global-policy-history"/); assert.match(actions, /name === "open-user-platform-policy-history"/);