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 继续推进。
+ + ${runs.length > 1 ? ` +当前执行器没有附带额外数据。
${escapeHtml(landing.summary || landing.title || "这是主 Agent 刚刚给出的下一步落点。")}
+ +${escapeHtml(description)}
${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")}