diff --git a/CHANGELOG.md b/CHANGELOG.md index 318ad12..04b83a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ 这个文件用于给 Gitea 仓库保留阶段性版本更新记录,方便直接查看每一轮里程碑,不用只依赖零散 commit。 +## 2026-04-04 + +### 主 Agent 配置与执行落点继续收口 + +- 发现页里三类关键动作现在会落到更精确的业务区域:账号分析会直接切到快照/字段/报告区域,高分作品分析会直接滚到“最近高分拆解”,相似账号生成会直接滚到“相似对标 / 已绑关系”。 +- 复盘创建/更新完成后,不再只停留在通用成功提示,而是会自动回到“发布与复盘”,并把刚保存的复盘项聚焦出来。 +- 同一类“保存对标关系”动作也统一改成精确落到关系区域,避免成功后仍让用户自己再找结果在哪。 + ## 2026-03-30 ### 主 Agent 治理与运行闭环 @@ -186,3 +194,30 @@ - 新增“查看执行结果”,会直接打开对应主 Agent run 的结果卡。 - 新增“回到主 Agent 查看”,会切到对应 run 的上下文并打开主 Agent 悬浮窗口,方便顺着同一轮执行继续处理。 - 前端回归也补上了这两个动作入口和事件处理器,避免后续又退回成只能展示、不能继续操作。 + +### 真实动作成功后的落点继续收口 + +- `加入跟踪 / 更新跟踪` 成功后,现在会直接切到 `跟踪账号` 工作区,不再只留一条成功提示。 +- `存对标 / 保存对标关系` 成功后,会直接把找对标详情切到 `关系` 视图,便于继续看刚保存的关系和候选。 +- `单任务恢复 / 批量恢复` 成功后,会优先打开新恢复出来的任务详情;如果没有拿到新任务 id,也会回到 `生产中心 -> 失败恢复`。 +- `生成文案` 成功后,会直接回到 `Agent` 工作区的“最近生成”结果区,而不是让用户自己找。 + +### 平台 Agent 最近执行字段补齐 + +- `recent_execution` 现在除了版本号和摘要,还会带: + - `oneliner_profile_version_id` + - `platform_agent_profile_version_id` + - `recommended_action` + - `workstream_key / workstream_label` +- 平台 Agent 总览卡和详情弹层会直接利用这些字段渲染“回到业务页”动作,不需要先打开 run 详情再猜下一步。 + +### 回归护栏继续加固 + +- 前端工作台回归新增了: + - 跟踪/对标成功后的页面落点校验 + - 恢复任务和文案生成的结果落点校验 + - 平台 Agent 最近执行 `recommended_action / workstream` 渲染校验 +- 后端治理回归新增了平台 Agent `recent_execution` 新字段断言,锁住: + - 精确版本 id + - 推荐业务动作 + - 工作流标签 diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index 8d51f2f..dee2547 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -1482,13 +1482,48 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: readiness_label = "可用" else: readiness_label = "待补全" + + def build_recent_execution_payload(base_payload: dict[str, Any], run_row: dict[str, Any] | None) -> dict[str, Any]: + if not run_row: + return base_payload + latest_result = _parse_json(run_row.get("result_json"), {}) + latest_governance = _parse_json(run_row.get("governance_json"), {}) + execution_card = (latest_result.get("execution_card") or {}) if isinstance(latest_result, dict) else {} + result_sections = (latest_result.get("result_sections") or {}) if isinstance(latest_result, dict) else {} + recommended_action = {} + if isinstance(latest_result, dict): + recommended_action = latest_result.get("recommended_action") or latest_result.get("recommended_preview_action") or {} + oneliner_profile_version = ( + execution_card.get("oneliner_profile_version") + or latest_governance.get("oneliner_profile_version") + or (latest_governance.get("oneliner_profile") or {}).get("current_version") + or {} + ) + platform_profile_version = ( + execution_card.get("platform_agent_profile") + or ((latest_governance.get("platform_agent_profile") or {}).get("current_version") or {}) + ) + return { + **base_payload, + "oneliner_profile_version_id": str(oneliner_profile_version.get("version_id") or oneliner_profile_version.get("id") or "").strip(), + "platform_agent_profile_version_id": str(platform_profile_version.get("version_id") or platform_profile_version.get("id") or "").strip(), + "recommended_action": { + "action": str(recommended_action.get("action") or "").strip(), + "screen": str(recommended_action.get("screen") or "").strip(), + "label": str(recommended_action.get("label") or "").strip(), + "summary": str(recommended_action.get("summary") or "").strip(), + }, + "workstream_key": str(result_sections.get("workstream_key") or "").strip(), + "workstream_label": str(result_sections.get("workstream_label") or "").strip(), + } + recent_execution = None current_version = None if row: _ensure_platform_agent_profile_version_seed(row, actor_user_id=account.get("id", "")) current_version = _platform_agent_profile_version_payload(_current_platform_agent_profile_version_row(row)) if row and str(row.get("last_run_id") or "").strip(): - recent_execution = { + recent_execution = build_recent_execution_payload({ "run_id": str(row.get("last_run_id") or "").strip(), "run_status": str(row.get("last_run_status") or "").strip(), "used_at": str(row.get("last_used_at") or "").strip(), @@ -1498,7 +1533,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "platform_agent_profile_version_no": int(row.get("last_platform_profile_version_no") or 0), "summary": str(row.get("last_execution_summary") or "").strip(), "source_screen": str(row.get("last_source_screen") or "").strip(), - } + }, legacy.db.fetch_one("SELECT * FROM agent_runs WHERE id = ?", (str(row.get("last_run_id") or "").strip(),))) if recent_execution is None and str(project_id or "").strip() and str(platform or "").strip(): latest_run_row = legacy.db.fetch_one( """ @@ -1517,7 +1552,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: or latest_governance.get("oneliner_profile_version") or {} ) - recent_execution = { + recent_execution = build_recent_execution_payload({ "run_id": str(latest_run_row.get("id") or "").strip(), "run_status": str(latest_run_row.get("run_status") or "").strip(), "used_at": str(latest_run_row.get("finished_at") or latest_run_row.get("updated_at") or "").strip(), @@ -1531,7 +1566,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: ), "summary": str(latest_run_row.get("status_summary") or "").strip(), "source_screen": str(latest_run_row.get("source_screen") or "").strip(), - } + }, latest_run_row) return { "id": row["id"] if row else "", "user_id": account["id"], diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index d747e80..cdb1e98 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -928,10 +928,16 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertEqual(refreshed_douyin["recent_execution"]["run_id"], run_payload["id"]) self.assertEqual(refreshed_douyin["recent_execution"]["intent_key"], "governance_review") self.assertGreaterEqual(refreshed_douyin["recent_execution"]["oneliner_profile_version_no"], 1) + self.assertTrue(refreshed_douyin["recent_execution"]["oneliner_profile_version_id"]) + self.assertTrue(refreshed_douyin["recent_execution"]["platform_agent_profile_version_id"]) self.assertEqual( refreshed_douyin["recent_execution"]["platform_agent_profile_version_no"], rollback_profile_payload["current_version"]["version_no"], ) + self.assertEqual(refreshed_douyin["recent_execution"]["recommended_action"]["action"], "goto-playbook") + self.assertEqual(refreshed_douyin["recent_execution"]["recommended_action"]["screen"], "playbook") + self.assertEqual(refreshed_douyin["recent_execution"]["workstream_key"], "playbook") + self.assertEqual(refreshed_douyin["recent_execution"]["workstream_label"], "Agent 治理") def test_admin_ops_routes_are_live(self) -> None: now = self.db_module.utc_now() diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 9205985..5620e38 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -61,6 +61,7 @@ const appState = { trackingDigest: null, trackingRefreshNotice: null, reviews: [], + reviewFocusId: "", liveRecorderSources: [], liveRecorderStatus: null, liveRecorderFiles: [], @@ -4467,11 +4468,13 @@ function renderPlatformAgentPanel() {
@@ -4687,7 +4690,7 @@ async function saveCandidateAsBenchmark(candidateIndex, relationType = "benchmar }); markSavedCandidate(candidate, result.links); rememberAction("候选已存对标", `已把「${candidate.candidate_nickname || candidate.candidate_profile_url || "候选账号"}」加入对标关系。`, "green", result); - renderAll(); + focusDiscoveryRelations(); } function screenShell(title, subtitle, actionsHtml, bodyHtml) { @@ -5482,7 +5485,7 @@ function renderProjectsScreen() { `${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 和生产任务。")}
${escapeHtml(item.summary || `发布时间 ${formatDateTime(item.video?.published_at)},建议继续判断借鉴点。`)}