From f13c83a583d667e96ddf67b7dc6e6092552e39c9 Mon Sep 17 00:00:00 2001 From: kris Date: Sun, 29 Mar 2026 19:51:06 +0800 Subject: [PATCH] feat: structure main agent result cards --- collector-service/app/oneliner_features.py | 111 ++++++++++++++++++ tests/test_main_agent_governance.py | 3 + web/storyforge-web-v4/assets/app.js | 27 +++++ .../tests/workbench-pages.test.mjs | 3 + 4 files changed, 144 insertions(+) diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index 891def0..9e4968a 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -2740,6 +2740,115 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: return intent_routes[intent_key] return route("goto-production", "production", "回到生产中心", "继续推进当前主 Agent 任务的执行结果。") + def _build_agent_run_result_sections( + row: dict[str, Any], + plan: dict[str, Any], + recommended_action: dict[str, Any], + ) -> dict[str, Any]: + source_screen = str(plan.get("source_screen") or row.get("source_screen") or "").strip().lower() + recommended_screen = str(recommended_action.get("screen") or "").strip().lower() + screen_key = recommended_screen or source_screen + intent_key = str(plan.get("intent_key") or row.get("intent_key") or "custom").strip().lower() or "custom" + platform = str(plan.get("platform") or row.get("platform") or "").strip() + platform_label = legacy.platform_label(platform) if platform else "" + plan_summary = str(plan.get("summary") or row.get("summary") or "").strip() or "主 Agent 已按当前上下文完成首轮收口。" + goal = str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务" + override_notice = _parse_json(row.get("active_admin_override_notice_json"), {}) + + workstream_map = { + "dashboard": ("dashboard", "首页动作"), + "discovery": ("discovery", "对标推进"), + "tracking": ("tracking", "跟踪推进"), + "production": ("production", "生产推进"), + "review": ("review", "复盘推进"), + "strategy": ("strategy", "策略治理"), + "playbook": ("playbook", "Agent 治理"), + "agent": ("playbook", "Agent 治理"), + "automation": ("automation", "自动流程"), + "intake": ("intake", "项目推进"), + "projects": ("intake", "项目推进"), + } + workstream_key, workstream_label = workstream_map.get(screen_key, ("production", "主 Agent 执行结果")) + + if intent_key in {"analyze_account", "analyze_top_videos", "import_homepage"}: + workstream_key, workstream_label = "discovery", "对标推进" + elif intent_key == "track_account": + workstream_key, workstream_label = "tracking", "跟踪推进" + elif intent_key in {"ai_video", "real_cut", "live_recorder"}: + workstream_key, workstream_label = "production", "生产推进" + elif intent_key == "review": + workstream_key, workstream_label = "review", "复盘推进" + elif intent_key in {"create_assistant"}: + workstream_key, workstream_label = "playbook", "Agent 治理" + elif intent_key in {"create_project"}: + workstream_key, workstream_label = "intake", "项目推进" + elif intent_key in {"storage_status", "ops_admin"}: + workstream_key, workstream_label = "automation", "自动流程" + + def card(title: str, body: str, *, tone: str = "blue", tags: list[str] | None = None) -> dict[str, Any]: + return { + "title": title, + "body": body, + "tone": tone, + "tags": [item for item in list(tags or []) if str(item).strip()], + } + + cards = [ + card( + "当前焦点", + f"围绕「{goal}」先完成一版可执行收口,避免你先在多个页面之间来回切换。", + tone="blue", + tags=[platform_label or "当前平台", workstream_label], + ) + ] + + focus_body_map = { + "discovery": "优先回到找对标,继续看账号、相似关系和高分样本,再决定是否导入或加入跟踪。", + "tracking": "优先回到跟踪账号,看最近日报窗口和值得继续跟进的对象,再决定同步或加深跟踪。", + "production": "优先回到生产中心,看队列、失败恢复和产物,再决定下一步是推进还是补救。", + "review": "优先回到发布与复盘,把最近完成任务沉淀成结构化复盘,再决定是否继续发布。", + "strategy": "优先回到我的策略,先看当前生效层和管理员覆盖,再决定是否继续调整用户策略。", + "playbook": "优先回到 Agent 工作区,结合当前平台 Agent、模型和技能,继续完善执行能力。", + "automation": "优先回到自动流程,先看依赖健康和动作防呆,再决定是否恢复或放行动作。", + "intake": "优先回到我的项目,先补项目、账号和导入基础,再决定往哪个工作页继续推进。", + "dashboard": "优先回到首页当前推荐动作,把这一轮的重点从概览转成真实执行。", + } + cards.append( + card( + workstream_label, + focus_body_map.get(workstream_key, plan_summary), + tone="green", + tags=[recommended_action.get("label") or "回到业务页"], + ) + ) + cards.append( + card( + "建议落点", + str(recommended_action.get("summary") or plan_summary).strip() or "回到对应业务页面继续推进。", + tone="orange" if override_notice.get("title") else "blue", + tags=[ + recommended_action.get("screen") or "", + "管理员覆盖生效中" if override_notice.get("title") else "", + "全平台" if str(plan.get("platform_scope") or row.get("platform_scope") or "") == "all_platforms" else "单平台", + ], + ) + ) + if override_notice.get("title"): + cards.append( + card( + "管理员覆盖提醒", + str(override_notice.get("summary") or "当前执行会优先遵循管理员覆盖层。").strip(), + tone="orange", + tags=[str(override_notice.get("title") or "管理员覆盖").strip()], + ) + ) + + return { + "workstream_key": workstream_key, + "workstream_label": workstream_label, + "cards": cards, + } + 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 "") @@ -2783,6 +2892,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: 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_sections = _build_agent_run_result_sections(row, plan, recommended_action) result_payload = { "result_kind": "main_agent_plan", "run_id": run_id, @@ -2794,6 +2904,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "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, + "result_sections": result_sections, "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 fbf8d93..4b17013 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -292,6 +292,9 @@ class MainAgentGovernanceTests(unittest.TestCase): 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") + self.assertEqual(payload["result"]["result_sections"]["workstream_key"], "discovery") + self.assertGreaterEqual(len(payload["result"]["result_sections"]["cards"]), 2) + self.assertEqual(payload["result"]["result_sections"]["cards"][0]["title"], "当前焦点") 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 1e0ee02..142c1c3 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -1808,6 +1808,10 @@ function renderOneLinerExecutionPayloadHtml(payload) { const landingSummary = String( payload.recommended_action?.summary || payload.execution_summary || payload.summary_text || "" ).trim(); + const resultSections = payload.result_sections && typeof payload.result_sections === "object" + ? payload.result_sections + : {}; + const resultCards = safeArray(resultSections.cards).slice(0, 4); const landingAttrs = buildMainAgentLandingAttrs({ runId: landingRunId, screen: landingScreen, @@ -1825,6 +1829,29 @@ function renderOneLinerExecutionPayloadHtml(payload) { ${payload.recommended_action?.action ? `${escapeHtml(payload.recommended_action.label || "回到对应页面")}` : ""} + ${resultCards.length ? ` +
+

${escapeHtml(resultSections.workstream_label || "执行结果分组")}

+

${escapeHtml(payload.summary_text || "主 Agent 已将当前执行结果整理为可继续推进的结构化建议。")}

+
+ ${resultCards.map((card, index) => { + const tags = safeArray(card?.tags).slice(0, 3); + const tone = String(card?.tone || "").trim(); + const toneClass = tone === "orange" ? "orange" : tone === "green" ? "green" : "blue"; + return ` +
+

${escapeHtml(card?.title || (index === 0 ? "当前焦点" : "执行结果"))}

+

${escapeHtml(card?.body || "主 Agent 已生成一条可继续执行的结果说明。")}

+
+ ${escapeHtml(card?.title || (index === 0 ? "当前焦点" : "结果卡片"))} + ${tags.map((tag) => `${escapeHtml(tag)}`).join("")} +
+
+ `; + }).join("")} +
+
+ ` : ""} ${payload.recommended_action?.summary ? `

建议回跳

diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index f22c423..65a4575 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -249,6 +249,9 @@ test("main agent result rendering offers a direct route back into the recommende 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, /result_sections/); + assert.match(execution, /当前焦点/); + assert.match(execution, /detail-grid/); assert.match(execution, /data-action="\$\{escapeHtml\(payload\.recommended_action\.action\)\}"/); assert.match(lastAction, /open-oneliner-run-result/); assert.match(lastAction, /recommended_action/);