diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index 7b7dce6..891def0 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -1043,6 +1043,11 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: return [_agent_run_event_payload(row) for row in rows] def _agent_run_payload(row: dict[str, Any], *, include_events: bool = True) -> dict[str, Any]: + plan = _parse_json(row.get("plan_json"), {}) + result = _parse_json(row.get("result_json"), {}) + recommended_preview_action = result.get("recommended_action") if isinstance(result, dict) else {} + if not recommended_preview_action: + recommended_preview_action = _build_agent_run_recommended_action(row, plan) payload = { "id": row["id"], "user_id": row.get("user_id", ""), @@ -1060,9 +1065,10 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "run_status": row.get("run_status", "needs_confirmation"), "scheduling_mode": row.get("scheduling_mode", "queued"), "active_executor_key": row.get("active_executor_key", "main_agent"), - "plan": _parse_json(row.get("plan_json"), {}), + "plan": plan, "governance": _parse_json(row.get("governance_json"), {}), - "result": _parse_json(row.get("result_json"), {}), + "result": result, + "recommended_preview_action": recommended_preview_action, "status_summary": row.get("status_summary", ""), "needs_user_input": bool(row.get("needs_user_input")), "blocked_reason": row.get("blocked_reason", ""), @@ -2779,6 +2785,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: recommended_action = _build_agent_run_recommended_action(row, plan) result_payload = { "result_kind": "main_agent_plan", + "run_id": run_id, "goal": str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务", "summary_text": summary_text, "execution_summary": execution_summary, diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index 32ff2c3..fbf8d93 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -209,6 +209,8 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertEqual(payload["platform_scope"], "single_platform") self.assertEqual(payload["session_id"][:5], "oline") self.assertEqual(payload["plan"]["goal"], "跟进重点账号") + self.assertEqual(payload["recommended_preview_action"]["action"], "goto-discovery") + self.assertEqual(payload["recommended_preview_action"]["screen"], "discovery") self.assertEqual(payload["governance"]["project_id"], self.ctx["project_id"]) self.assertIn("layers", payload["governance"]) self.assertEqual(payload["events"][0]["event_type"], "run.created") @@ -243,6 +245,8 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertEqual(confirm.status_code, 200, confirm.text) payload = confirm.json() self.assertIn(payload["run_status"], {"queued", "running"}) + self.assertEqual(payload["recommended_preview_action"]["action"], "goto-strategy") + self.assertEqual(payload["recommended_preview_action"]["screen"], "strategy") event_types = [item["event_type"] for item in payload["events"]] self.assertIn("run.created", event_types) self.assertIn("run.confirmed", event_types) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index c97b04b..4ac0060 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -80,6 +80,7 @@ const appState = { busy: false, message: "", lastAction: null, + mainAgentLanding: null, lastGeneratedCopy: null, lastSimilaritySearch: null, lastJobDetail: null @@ -977,7 +978,23 @@ 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 previewAction = currentRun.recommended_preview_action || null; const recommendedAction = resultPayload?.recommended_action || null; + const pendingRunCount = safeArray(runs).filter((item) => item.run_status === "needs_confirmation").length; + const activeRunCount = safeArray(runs).filter((item) => ["queued", "running", "blocked"].includes(item.run_status)).length; + const completedRunCount = safeArray(runs).filter((item) => item.run_status === "done").length; + const previewLandingAttrs = buildMainAgentLandingAttrs({ + runId: currentRun.id || "", + screen: previewAction?.screen || "", + title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务", + summary: previewAction?.summary || currentRun.summary || "" + }); + const resultLandingAttrs = buildMainAgentLandingAttrs({ + runId: currentRun.id || "", + screen: recommendedAction?.screen || "", + title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务", + summary: recommendedAction?.summary || currentRun.status_summary || "" + }); const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length); const runStatusLabel = { needs_confirmation: "待确认", @@ -998,6 +1015,32 @@ function renderOneLinerRunsHtml() { ? "orange" : ""; return ` +
+

近期运行概况

+

先看待确认和执行中的任务,再切回当前 run 继续推进。

+
+ 待确认 ${escapeHtml(formatNumber(pendingRunCount))} + 执行中 ${escapeHtml(formatNumber(activeRunCount))} + 已完成 ${escapeHtml(formatNumber(completedRunCount))} +
+ ${runs.length > 1 ? ` +
+ ${runs.slice(0, 6).map((item) => ` + + ${escapeHtml(brief(item.title || item.plan?.goal || "主 Agent 任务", 14))} · ${escapeHtml({ + needs_confirmation: "待确认", + queued: "排队中", + running: "执行中", + blocked: "阻塞", + done: "已完成", + failed: "失败", + cancelled: "已取消" + }[item.run_status] || item.run_status || "运行中")} + + `).join("")} +
+ ` : ""} +
@@ -1024,8 +1067,10 @@ function renderOneLinerRunsHtml() { ${escapeHtml(currentRun.source_action_key || "manual-handoff")} ${escapeHtml(currentRun.platform_scope === "all_platforms" ? "全平台" : "单平台")} ${currentRun.delivery_mode ? `${escapeHtml(currentRun.delivery_mode)}` : ""} + ${previewAction?.action ? `${escapeHtml(`预计落点 · ${previewAction.label || "对应页面"}`)}` : ""}
+ ${!hasResultPayload && previewAction?.summary ? `
${escapeHtml(previewAction.summary)}
` : ""} ${planSteps.length ? `
${planSteps.map((step, index) => ` @@ -1044,7 +1089,7 @@ function renderOneLinerRunsHtml() { ${escapeHtml(currentRun.status_summary || "主 Agent 正在推进中")} `} ${hasResultPayload ? `查看结果` : ""} - ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""} + ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""}
${recommendedAction?.summary ? `
${escapeHtml(recommendedAction.summary)}
` : ""} ${hasResultPayload ? ` @@ -1064,15 +1109,6 @@ function renderOneLinerRunsHtml() {
` : ""} - ${runs.length > 1 ? ` -
- ${runs.slice(0, 6).map((item) => ` - - ${escapeHtml(brief(item.title || item.plan?.goal || "主 Agent 任务", 14))} - - `).join("")} -
- ` : ""} `; } @@ -1732,6 +1768,18 @@ function renderOneLinerExecutionPayloadHtml(payload) { return `

没有返回执行结果

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

`; } if (payload.result_kind === "main_agent_plan") { + const landingRunId = String(payload.run_id || "").trim(); + const landingScreen = String(payload.recommended_action?.screen || "").trim(); + const landingTitle = String(payload.goal || "主 Agent 执行建议").trim(); + const landingSummary = String( + payload.recommended_action?.summary || payload.execution_summary || payload.summary_text || "" + ).trim(); + const landingAttrs = buildMainAgentLandingAttrs({ + runId: landingRunId, + screen: landingScreen, + title: landingTitle, + summary: landingSummary + }); return `

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

@@ -1740,7 +1788,7 @@ function renderOneLinerExecutionPayloadHtml(payload) { ${payload.platform ? `${escapeHtml(platformLabel(payload.platform))}` : ""} ${escapeHtml(payload.platform_scope === "all_platforms" ? "全平台" : "单平台")} 已收口 - ${payload.recommended_action?.action ? `${escapeHtml(payload.recommended_action.label || "回到对应页面")}` : ""} + ${payload.recommended_action?.action ? `${escapeHtml(payload.recommended_action.label || "回到对应页面")}` : ""}
${payload.recommended_action?.summary ? ` @@ -4212,6 +4260,70 @@ function buildMainAgentHandoffAttrs({ return attrs.join(" "); } +function buildMainAgentLandingAttrs({ + runId = "", + screen = "", + title = "", + summary = "" +} = {}) { + const attrs = []; + if (runId) attrs.push(`data-main-agent-run-id="${escapeHtml(runId)}"`); + if (screen) attrs.push(`data-main-agent-screen="${escapeHtml(screen)}"`); + if (title) attrs.push(`data-main-agent-title="${escapeHtml(title)}"`); + if (summary) attrs.push(`data-main-agent-summary="${escapeHtml(summary)}"`); + return attrs.join(" "); +} + +function resolveMainAgentLandingScreen(target) { + const value = String(target || "").trim(); + if (!value) return ""; + if (!value.startsWith("goto-")) return value; + const routeMap = { + "goto-discovery": "discovery", + "goto-intake": "intake", + "goto-automation": "automation", + "goto-playbook": "playbook", + "goto-tracking": "tracking", + "goto-production": "production", + "goto-strategy": "strategy", + "goto-review": "review" + }; + return routeMap[value] || value.replace(/^goto-/, ""); +} + +function captureMainAgentLandingContext(action, targetScreen) { + const runId = String(action?.dataset?.mainAgentRunId || action?.dataset?.runId || "").trim(); + const screen = resolveMainAgentLandingScreen(action?.dataset?.mainAgentScreen || targetScreen || ""); + const title = String(action?.dataset?.mainAgentTitle || "").trim(); + const summary = String(action?.dataset?.mainAgentSummary || "").trim(); + if (!screen || (!runId && !title && !summary)) { + return; + } + appState.mainAgentLanding = { + screen, + runId, + title, + summary, + createdAt: new Date().toISOString() + }; +} + +function renderMainAgentLandingNotice(screenKey) { + const landing = appState.mainAgentLanding; + if (!landing || landing.screen !== screenKey) return ""; + return ` +
+

你正在处理主 Agent 的结果

+

${escapeHtml(landing.summary || landing.title || "这是主 Agent 刚刚给出的下一步落点。")}

+
+ ${landing.title ? `${escapeHtml(landing.title)}` : ""} + ${landing.runId ? `查看结果` : ""} + 收起提示 +
+
+ `; +} + function renderEmptyState(title, description) { return `
${escapeHtml(title)}

${escapeHtml(description)}

`; } @@ -4641,11 +4753,31 @@ function renderProjectsScreen() { } const projects = safeArray(appState.dashboard.projects); const selectedProject = getSelectedProject(); + const intakeHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "intake", + sourceActionKey: "project-intake-handoff", + intentKey: "project_intake", + title: selectedProject ? `继续推进项目 ${selectedProject.name}` : "继续梳理项目工作区", + goal: selectedProject + ? `围绕项目 ${selectedProject.name} 生成下一步推进计划` + : "根据当前项目列表和导入情况,生成下一步项目推进计划", + summary: selectedProject + ? "主 Agent 会结合当前项目状态、账号和任务规模,整理下一步动作。" + : "主 Agent 会先看项目列表和内容导入情况,再整理下一步动作。", + platform: getPreferredPlatform(), + platformScope: "single_platform", + planSteps: [ + "读取当前项目、账号和任务状态", + "检查是否需要补导入或切换项目", + "生成下一步项目推进计划" + ] + }); return screenShell( "我的项目", "先建项目,再决定是否绑定自己的账号。", - `${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")}`, + `${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: intakeHandoffAttrs })}`, ` + ${renderMainAgentLandingNotice("intake")}

当前项目

${escapeHtml(selectedProject?.name || "还没有项目")} · ${escapeHtml(selectedProject?.description || "创建后即可承接对标、Agent 和生产任务。")}

@@ -4869,6 +5001,25 @@ function renderDiscoveryScreen() { const selectedProject = getSelectedProject(); const importedSources = getCurrentProjectSourcesForAccount(selected, selectedProject?.id || ""); const tracked = selected?.id ? isTrackedAccount(selected.id) : false; + const discoveryHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "discovery", + sourceActionKey: "discovery-handoff", + intentKey: "benchmark_discovery", + title: selected ? `继续处理对标 ${getAccountName(selected)}` : "继续推进对标工作", + goal: selected + ? `围绕 ${getAccountName(selected)} 输出一版下一步对标计划` + : `根据当前${currentPlatformLabel}账号列表,生成下一步对标推进计划`, + summary: selected + ? "结合当前选中的对标账号、分析报告和相似关系,生成下一步动作。" + : "结合当前账号池和已导入内容,整理一版可执行的对标计划。", + platform: effectivePlatform || currentPlatform, + platformScope: "single_platform", + planSteps: [ + "读取当前对标账号与已导入内容", + "检查分析报告与相似关系", + "生成下一步对标推进计划" + ] + }); const detailTabs = [ { value: "overview", label: "账号概览" }, { value: "snapshots", label: "快照 / 字段 / 报告" }, @@ -4915,8 +5066,9 @@ function renderDiscoveryScreen() { isWorkbenchPlatform(currentPlatform) ? `这里已经接入真实${currentPlatformLabel}账号列表和单账号详情。` : `${workbenchReason}。当前仍可导入内容源、绑定 Agent 和沉淀复盘。`, - `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button(tracked ? "已在跟踪" : "加入跟踪", "open-track-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("账号分析", "analyze-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("高分分析", "analyze-top-videos", "secondary", { disabledReason: workbenchReason || "" })} ${button("查相似", "open-similar-search", "secondary", { disabledReason: workbenchReason || "" })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}`, + `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button(tracked ? "已在跟踪" : "加入跟踪", "open-track-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("账号分析", "analyze-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("高分分析", "analyze-top-videos", "secondary", { disabledReason: workbenchReason || "" })} ${button("查相似", "open-similar-search", "secondary", { disabledReason: workbenchReason || "" })} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}`, ` + ${renderMainAgentLandingNotice("discovery")}
@@ -5034,11 +5186,27 @@ function renderTrackingScreen() { const digestItems = getTrackingDigestItems(12, { platform: currentPlatform }); const platformCursor = getTrackingCursorForPlatform(currentPlatform) || appState.lastSeenAt; const cursorLabel = platformCursor ? formatDateTime(platformCursor) : "尚未记录"; + const trackingHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "tracking", + sourceActionKey: "tracking-handoff", + intentKey: "tracking_digest", + title: `继续处理${getPlatformShortLabel(currentPlatform)}跟踪日报`, + goal: `基于当前${getPlatformShortLabel(currentPlatform)}跟踪账号和日报,生成下一步跟进计划`, + summary: "主 Agent 会结合已跟踪账号、日报窗口和更新摘要,给出下一步动作。", + platform: currentPlatform, + platformScope: "single_platform", + planSteps: [ + "读取当前平台跟踪账号和最新日报", + "识别值得继续跟进的账号或内容", + "生成一版跟踪推进计划" + ] + }); return screenShell( "跟踪账号", `这里已经接上真实${getPlatformShortLabel(currentPlatform)}跟踪对象和按上次打开后的更新日报。`, - `${button("同步全部", "refresh-tracking")} ${button("标记已读", "mark-tracking-read")} ${button("跳到找对标", "goto-discovery", "primary")}`, + `${button("同步全部", "refresh-tracking")} ${button("标记已读", "mark-tracking-read")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: trackingHandoffAttrs })} ${button("跳到找对标", "goto-discovery", "primary")}`, ` + ${renderMainAgentLandingNotice("tracking")}

日报逻辑

按上次打开后汇总。上次打开距今 ${escapeHtml(daysSince(platformCursor))} 天,本次优先展示有更新且值得借鉴的内容。

@@ -5127,6 +5295,7 @@ function renderAutomationScreen() { "自动同步、日报生成和失败补跑先统一看这里。", `${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: automationHandoffAttrs })} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`, ` + ${renderMainAgentLandingNotice("automation")}

自动流程

当前按真实任务量和依赖健康状态给出看板,自动流程受阻时会直接在这里拦住动作。

@@ -5289,6 +5458,7 @@ function renderPlaybookScreen() { "这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。", `${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")}`, ` + ${renderMainAgentLandingNotice("playbook")}

Agent 概览

先定项目、平台和主模型,再导入内容让 Agent 学习。

@@ -5490,6 +5660,21 @@ function renderProductionScreen() { const recoverableCount = failedJobs.filter((item) => item.recovery.recoverable).length; const recentDocs = appState.documents.slice(0, 3); const works = getProductionWorks(6); + const productionHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "production", + sourceActionKey: "production-handoff", + intentKey: "production_coordination", + title: "继续推进生产中心", + goal: "基于当前生产队列、失败任务和产物,生成下一步生产推进计划", + summary: "主 Agent 会结合生产队列、失败恢复和产物状态,给出下一步动作。", + platform: getPreferredPlatform(), + platformScope: "single_platform", + planSteps: [ + "读取当前生产队列和失败任务", + "识别最该优先推进或恢复的项", + "生成一版生产推进计划" + ] + }); const tabs = [ { value: "queue", label: "生产队列" }, { value: "recovery", label: "失败恢复" }, @@ -5500,8 +5685,9 @@ function renderProductionScreen() { return screenShell( "生产中心", "这里已经接上真实任务和知识库文档,后续再继续补任务创建动作。", - `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`, + `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`, ` + ${renderMainAgentLandingNotice("production")}

生产队列

最近任务的真实状态
@@ -5639,11 +5825,27 @@ function renderReviewScreen() { const project = getSelectedProject(); const completed = safeArray(appState.dashboard.recent_jobs).filter((item) => item.status === "completed").slice(0, 4); const reviews = getProjectReviews(project?.id || "").slice(0, 8); + const reviewHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "review", + sourceActionKey: "review-handoff", + intentKey: "review_followup", + title: project ? `继续沉淀项目复盘 · ${project.name}` : "继续沉淀复盘", + goal: "基于最近完成任务和已保存复盘,生成下一步复盘与发布计划", + summary: "主 Agent 会结合最近完成任务和现有复盘,整理下一步复盘动作。", + platform: getPreferredPlatform(), + platformScope: "single_platform", + planSteps: [ + "读取最近完成任务和现有复盘", + "识别还缺的复盘或发布动作", + "生成下一步复盘推进计划" + ] + }); return screenShell( "发布与复盘", "先看已保存复盘,再把完成任务转成结构化复盘。", - `${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("去生产", "goto-production", "primary")}`, + `${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`, ` + ${renderMainAgentLandingNotice("review")}
@@ -5723,6 +5925,7 @@ function renderStrategyScreen() { "把你和主 Agent 的对话沉淀成可查看、可回滚、可追溯的个人策略层。", `${button("编辑全局策略", "open-user-global-policy")} ${button("编辑当前平台策略", "open-user-platform-policy", "primary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: strategyHandoffAttrs })}`, ` + ${renderMainAgentLandingNotice("strategy")}

当前策略工作区

${escapeHtml(project?.name || "当前项目")} · ${escapeHtml(platformLabel(platform))}。这里展示系统默认、你的个性化策略和管理员覆盖是如何叠加生效的。

@@ -6187,6 +6390,12 @@ function renderLastActionCard() { const payload = appState.lastAction.payload || {}; const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null; const runId = payload?.id || payload?.run_id || ""; + const landingAttrs = buildMainAgentLandingAttrs({ + runId, + screen: recommendedAction?.screen || "", + title: appState.lastAction.title || "", + summary: recommendedAction?.summary || appState.lastAction.summary || "" + }); return `
@@ -6202,7 +6411,7 @@ function renderLastActionCard() { ${(runId || recommendedAction?.action) ? `
${runId ? `查看结果` : ""} - ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""} + ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""}
` : ""}
@@ -9349,19 +9558,28 @@ document.addEventListener("click", async (event) => { await logoutSession(); return; } + if (name === "dismiss-main-agent-landing") { + appState.mainAgentLanding = null; + renderAll(); + return; + } if (name === "goto-discovery") { + captureMainAgentLandingContext(action, "goto-discovery"); setScreen("discovery"); return; } if (name === "goto-intake") { + captureMainAgentLandingContext(action, "goto-intake"); setScreen("intake"); return; } if (name === "goto-automation") { + captureMainAgentLandingContext(action, "goto-automation"); setScreen("automation"); return; } if (name === "goto-playbook") { + captureMainAgentLandingContext(action, "goto-playbook"); setScreen("playbook"); return; } @@ -9370,18 +9588,22 @@ document.addEventListener("click", async (event) => { return; } if (name === "goto-tracking") { + captureMainAgentLandingContext(action, "goto-tracking"); setScreen("tracking"); return; } if (name === "goto-production") { + captureMainAgentLandingContext(action, "goto-production"); setScreen("production"); return; } if (name === "goto-strategy") { + captureMainAgentLandingContext(action, "goto-strategy"); setScreen("strategy"); return; } if (name === "goto-review") { + captureMainAgentLandingContext(action, "goto-review"); setScreen("review"); return; } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index b8c40ee..88e4efe 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -134,6 +134,7 @@ test("oneliner panel includes a dedicated runtime header for agent runs", () => assert.match(runtime, /confirm-oneliner-run/); assert.match(runtime, /cancel-oneliner-run/); assert.match(runtime, /当前计划/); + assert.match(runtime, /recommended_preview_action/); assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/); assert.match(runtime, /open-oneliner-run-result/); assert.match(runtime, /recommended_action/); @@ -239,3 +240,53 @@ test("main agent result rendering offers a direct route back into the recommende assert.match(lastAction, /open-oneliner-run-result/); assert.match(lastAction, /recommended_action/); }); + +test("main agent route actions keep landing context and destination screens render a notice", () => { + const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)"); + const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); + const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()"); + const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()"); + assert.match(execution, /data-main-agent-run-id/); + assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-production"/); + assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-strategy"/); + assert.match(actions, /name === "dismiss-main-agent-landing"/); + assert.match(strategy, /renderMainAgentLandingNotice\("strategy"\)/); + assert.match(production, /renderMainAgentLandingNotice\("production"\)/); +}); + +test("key workbench screens expose contextual handoff-to-main-agent actions", () => { + const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()"); + const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()"); + const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab("); + const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()"); + const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()"); + + assert.match(discovery, /buildMainAgentHandoffAttrs\(\{/); + assert.match(discovery, /button\("交给主 Agent", "handoff-to-main-agent"/); + assert.match(discovery, /sourceScreen: "discovery"/); + + assert.match(tracking, /buildMainAgentHandoffAttrs\(\{/); + assert.match(tracking, /button\("交给主 Agent", "handoff-to-main-agent"/); + assert.match(tracking, /sourceScreen: "tracking"/); + + assert.match(projects, /buildMainAgentHandoffAttrs\(\{/); + assert.match(projects, /button\("交给主 Agent", "handoff-to-main-agent"/); + assert.match(projects, /sourceScreen: "intake"/); + + assert.match(production, /buildMainAgentHandoffAttrs\(\{/); + assert.match(production, /button\("交给主 Agent", "handoff-to-main-agent"/); + assert.match(production, /sourceScreen: "production"/); + + assert.match(review, /buildMainAgentHandoffAttrs\(\{/); + assert.match(review, /button\("交给主 Agent", "handoff-to-main-agent"/); + assert.match(review, /sourceScreen: "review"/); +}); + +test("oneliner runtime shows grouped run health summary above the current run card", () => { + const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()"); + assert.match(runtime, /近期运行概况/); + assert.match(runtime, /待确认/); + assert.match(runtime, /执行中/); + assert.match(runtime, /已完成/); + assert.match(runtime, /safeArray\(runs\)\.filter\(\(item\) => item\.run_status === "needs_confirmation"\)\.length/); +});