From c83c54053fe3e556bfbb985aefbc90b9d1cefeb3 Mon Sep 17 00:00:00 2001 From: kris Date: Sun, 29 Mar 2026 19:08:30 +0800 Subject: [PATCH] feat: route main agent results back into workbench --- collector-service/app/oneliner_features.py | 54 +++++++++++++++++++ tests/test_main_agent_governance.py | 2 + web/storyforge-web-v4/assets/app.js | 19 +++++++ .../tests/workbench-pages.test.mjs | 10 ++++ 4 files changed, 85 insertions(+) diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index fdf932a..7b7dce6 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -2682,6 +2682,58 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: ) return bool(row) + def _build_agent_run_recommended_action(row: dict[str, Any], plan: dict[str, Any]) -> dict[str, Any]: + source_screen = str(plan.get("source_screen") or row.get("source_screen") or "").strip().lower() + source_action_key = str(plan.get("source_action_key") or row.get("source_action_key") or "").strip().lower() + intent_key = str(plan.get("intent_key") or row.get("intent_key") or "custom").strip().lower() or "custom" + + def route(action: str, screen: str, label: str, summary: str) -> dict[str, Any]: + return { + "action": action, + "screen": screen, + "label": label, + "summary": summary, + } + + source_routes = { + "strategy": route("goto-strategy", "strategy", "回到我的策略", "继续查看当前用户策略与覆盖状态。"), + "automation": route("goto-automation", "automation", "回到自动流程", "继续检查自动流程和依赖状态。"), + "playbook": route("goto-playbook", "playbook", "回到 Agent", "继续调整 Agent 与平台能力。"), + "agent": route("goto-playbook", "playbook", "回到 Agent", "继续调整 Agent 与平台能力。"), + "production": route("goto-production", "production", "回到生产中心", "继续推进生产任务与恢复动作。"), + "tracking": route("goto-tracking", "tracking", "回到跟踪账号", "继续查看跟踪摘要和更新提醒。"), + "review": route("goto-review", "review", "回到发布与复盘", "继续沉淀复盘结论和发布结果。"), + "discovery": route("goto-discovery", "discovery", "回到找对标", "继续查看账号拆解和高分样本。"), + "intake": route("goto-intake", "projects", "回到我的项目", "继续切换项目或补齐项目基础信息。"), + "projects": route("goto-intake", "projects", "回到我的项目", "继续切换项目或补齐项目基础信息。"), + } + if source_screen == "dashboard" and source_action_key == "homepage-primary-action": + return route("goto-discovery", "discovery", "回到找对标", "继续查看首页当前最优先的对标与高分样本动作。") + if source_screen == "dashboard" and source_action_key.startswith("homepage-secondary-action-"): + if intent_key == "track_account": + return route("goto-tracking", "tracking", "回到跟踪账号", "继续跟进首页建议的重点账号跟踪。") + return route("goto-production", "production", "回到生产中心", "继续处理首页建议的生产推进动作。") + if source_screen in source_routes: + return source_routes[source_screen] + + intent_routes = { + "analyze_account": route("goto-discovery", "discovery", "回到找对标", "继续拆解当前账号和对标对象。"), + "analyze_top_videos": route("goto-discovery", "discovery", "回到找对标", "继续查看高分作品分析结论。"), + "track_account": route("goto-tracking", "tracking", "回到跟踪账号", "继续更新账号跟踪与日报。"), + "ai_video": route("goto-production", "production", "回到生产中心", "继续推进 AI 视频生产任务。"), + "real_cut": route("goto-production", "production", "回到生产中心", "继续推进实拍剪辑任务。"), + "live_recorder": route("goto-production", "production", "回到生产中心", "继续查看录制维护与产物。"), + "review": route("goto-review", "review", "回到发布与复盘", "继续补齐复盘与发布总结。"), + "create_assistant": route("goto-playbook", "playbook", "回到 Agent", "继续创建或调整项目 Agent。"), + "create_project": route("goto-intake", "projects", "回到我的项目", "继续创建或切换当前项目。"), + "import_homepage": route("goto-discovery", "discovery", "回到找对标", "继续处理主页导入后的账号分析。"), + "ops_admin": route("goto-automation", "automation", "回到自动流程", "继续查看系统依赖和治理状态。"), + "storage_status": route("goto-automation", "automation", "回到自动流程", "继续查看存储与依赖健康状态。"), + } + if intent_key in intent_routes: + return intent_routes[intent_key] + return route("goto-production", "production", "回到生产中心", "继续推进当前主 Agent 任务的执行结果。") + def _complete_agent_run_for_read(row: dict[str, Any]) -> dict[str, Any]: current_status = str(row.get("run_status") or "") run_id = str(row.get("id") or "") @@ -2724,6 +2776,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: steps = ["读取当前项目上下文", "结合治理层生成执行计划", "收口为可执行建议"] summary_text = str(plan.get("summary") or row.get("summary") or "").strip() or "主 Agent 已根据当前计划完成第一版执行收口。" execution_summary = f"已完成「{str(plan.get('goal') or row.get('title') or '主 Agent 任务').strip() or '主 Agent 任务'}」的首轮执行建议。" + recommended_action = _build_agent_run_recommended_action(row, plan) result_payload = { "result_kind": "main_agent_plan", "goal": str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务", @@ -2733,6 +2786,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "intent_key": str(plan.get("intent_key") or row.get("intent_key") or "custom").strip() or "custom", "platform": str(plan.get("platform") or row.get("platform") or "").strip(), "platform_scope": str(plan.get("platform_scope") or row.get("platform_scope") or "single_platform").strip() or "single_platform", + "recommended_action": recommended_action, "active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}), } _log_agent_run_event( diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index d9f6d2f..32ff2c3 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -286,6 +286,8 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertEqual(payload["run_status"], "done") self.assertTrue(payload["finished_at"]) self.assertEqual(payload["result"]["result_kind"], "main_agent_plan") + self.assertEqual(payload["result"]["recommended_action"]["action"], "goto-discovery") + self.assertEqual(payload["result"]["recommended_action"]["screen"], "discovery") event_types = [item["event_type"] for item in payload["events"]] self.assertIn("run.progress", event_types) self.assertIn("run.done", event_types) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index ecdd53c..c97b04b 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -977,6 +977,7 @@ 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 recommendedAction = resultPayload?.recommended_action || null; const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length); const runStatusLabel = { needs_confirmation: "待确认", @@ -1043,7 +1044,9 @@ function renderOneLinerRunsHtml() { ${escapeHtml(currentRun.status_summary || "主 Agent 正在推进中")} `} ${hasResultPayload ? `查看结果` : ""} + ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""} + ${recommendedAction?.summary ? `
${escapeHtml(recommendedAction.summary)}
` : ""} ${hasResultPayload ? `

执行结果

@@ -1737,8 +1740,15 @@ 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?.summary ? ` +
+

建议回跳

+

${escapeHtml(payload.recommended_action.summary)}

+
+ ` : ""} ${safeArray(payload.next_steps).length ? `
${safeArray(payload.next_steps).slice(0, 4).map((step, index) => ` @@ -6174,6 +6184,9 @@ function extractGeneratedCopy(payload) { function renderLastActionCard() { if (!appState.lastAction) return ""; + const payload = appState.lastAction.payload || {}; + const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null; + const runId = payload?.id || payload?.run_id || ""; return `
@@ -6186,6 +6199,12 @@ function renderLastActionCard() {

${escapeHtml(appState.lastAction.title)}

${escapeHtml(appState.lastAction.summary)}

+ ${(runId || recommendedAction?.action) ? ` +
+ ${runId ? `查看结果` : ""} + ${recommendedAction?.action ? `${escapeHtml(recommendedAction.label || "回到对应页面")}` : ""} +
+ ` : ""}
`; diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 9212656..b8c40ee 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -136,6 +136,7 @@ test("oneliner panel includes a dedicated runtime header for agent runs", () => assert.match(runtime, /当前计划/); assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/); assert.match(runtime, /open-oneliner-run-result/); + assert.match(runtime, /recommended_action/); }); test("oneliner meta and action handlers expose governance entry points", () => { @@ -229,3 +230,12 @@ test("user governance UI exposes personal history and rollback entrypoints", () assert.match(actions, /name === "open-user-global-policy-history"/); assert.match(actions, /name === "open-user-platform-policy-history"/); }); + +test("main agent result rendering offers a direct route back into the recommended screen", () => { + const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)"); + const lastAction = extractBetween(APP, "function renderLastActionCard()", "function getJobRecoveryCategory(job)"); + assert.match(execution, /recommended_action/); + assert.match(execution, /data-action="\$\{escapeHtml\(payload\.recommended_action\.action\)\}"/); + assert.match(lastAction, /open-oneliner-run-result/); + assert.match(lastAction, /recommended_action/); +});