From f890a0ace7d5eec031e9c7320c6a90110ec918b0 Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 3 Apr 2026 15:38:01 +0800 Subject: [PATCH] feat: carry platform agent config into main agent runs --- CHANGELOG.md | 7 +++ collector-service/app/oneliner_features.py | 60 +++++++++++++++++++ tests/test_main_agent_governance.py | 3 + web/storyforge-web-v4/assets/app.js | 26 ++++++++ .../tests/workbench-pages.test.mjs | 2 + 5 files changed, 98 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8464dcb..bd1802c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,13 @@ - run 的治理快照也收窄成“当前主配置 + 当前版本”最小运行态,不再把完整版本历史和审计链塞进每次执行记录,避免 `agent_runs.governance_json` 无限制膨胀。 - Web 回归测试修正了 OneLiner 运行区函数边界,并新增了对执行链配置版本显示的断言;后端治理测试也补上了 run 完成态必须带配置版本的检查。 +### 平台 Agent 配置进入执行链 + +- 主 Agent 在创建 run、重试 run 时,会把当前平台 Agent 的最小运行快照一起固化进治理快照,包括平台、Agent 名称、承接使命、assistant 名称和 readiness 状态。 +- 完成态结果现在会带上 `execution_card.platform_agent_profile`,前端浮窗、当前运行卡和结果卡都能直接看到“本轮平台 Agent”,执行链不会再丢失平台侧配置来源。 +- run 的平台 Agent 快照只保留运行时最小必要字段,不把完整平台 Agent 配置、技能列表和记忆列表塞进执行结果,避免执行记录继续膨胀。 +- Web 回归测试新增了对“本轮平台 Agent”结果渲染的断言;后端治理测试也补上了 run 创建态与完成态必须带平台 Agent 快照的检查。 + ### NAS 联调发布 - 最新 Web 已重新发布到 fnOS NAS: diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index 8e3dbfe..0afab6a 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -1248,6 +1248,35 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "updated_at": (row or {}).get("updated_at", ""), } + def _platform_agent_runtime_snapshot( + account: dict[str, Any], + row: dict[str, Any] | None, + *, + platform: str, + project_id: str = "", + ) -> dict[str, Any]: + payload = _platform_agent_payload(account, row, platform=platform, project_id=project_id) + assistant = payload.get("assistant") or {} + recent_memory = payload.get("recent_memory") or {} + recent_skill = payload.get("recent_skill") or {} + return { + "id": str(payload.get("id") or "").strip(), + "platform": str(payload.get("platform") or platform or "").strip(), + "platform_label": str(payload.get("platform_label") or legacy.platform_label(platform) or "").strip(), + "assistant_id": str(payload.get("assistant_id") or "").strip(), + "assistant_name": str((assistant or {}).get("name") or "").strip(), + "name": str(payload.get("name") or "").strip(), + "mission": str(payload.get("mission") or "").strip(), + "notes": str(payload.get("notes") or "").strip(), + "status": str(payload.get("status") or "").strip(), + "memory_count": int(payload.get("memory_count") or 0), + "skill_count": int(payload.get("skill_count") or 0), + "readiness_score": int(payload.get("readiness_score") or 0), + "readiness_label": str(payload.get("readiness_label") or "").strip(), + "recent_memory_title": str(recent_memory.get("title") or "").strip(), + "recent_skill_title": str(recent_skill.get("title") or "").strip(), + } + def _memory_payload(row: dict[str, Any]) -> dict[str, Any]: return { "id": row["id"], @@ -3190,6 +3219,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: or (governance.get("oneliner_profile") or {}).get("current_version") or {} ) + platform_agent_profile = governance.get("platform_agent_profile") or {} steps = [str(item).strip() for item in list(plan.get("steps") or []) if str(item).strip()] if not steps: steps = ["读取当前项目上下文", "结合治理层生成执行计划", "收口为可执行建议"] @@ -3222,6 +3252,16 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: "title": str(oneliner_profile_version.get("title") or "").strip(), "summary": str(oneliner_profile_version.get("summary") or "").strip(), }, + "platform_agent_profile": { + "platform": str(platform_agent_profile.get("platform") or "").strip(), + "platform_label": str(platform_agent_profile.get("platform_label") or "").strip(), + "name": str(platform_agent_profile.get("name") or "").strip(), + "assistant_name": str(platform_agent_profile.get("assistant_name") or "").strip(), + "mission": str(platform_agent_profile.get("mission") or "").strip(), + "status": str(platform_agent_profile.get("status") or "").strip(), + "readiness_label": str(platform_agent_profile.get("readiness_label") or "").strip(), + "readiness_score": int(platform_agent_profile.get("readiness_score") or 0), + }, "next_steps": steps, }, } @@ -5087,11 +5127,21 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: platform=normalized_platform, ) profile_row = _fetch_profile_row(account, project["id"]) or _ensure_oneliner_profile(account, project["id"]) + platform_profile_row = legacy.db.fetch_one( + "SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?", + (account["id"], project["id"], normalized_platform), + ) if normalized_platform else None oneliner_profile_bundle = _oneliner_profile_runtime_snapshot(profile_row, account=account) governance_snapshot = { **governance, "oneliner_profile": oneliner_profile_bundle, "oneliner_profile_version": oneliner_profile_bundle.get("current_version") or {}, + "platform_agent_profile": _platform_agent_runtime_snapshot( + account, + platform_profile_row, + platform=normalized_platform, + project_id=project["id"], + ) if normalized_platform else {}, } plan = _agent_run_plan_payload( request, @@ -5178,11 +5228,21 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: platform=platform, ) profile_row = _fetch_profile_row(account, project_id) or _ensure_oneliner_profile(account, project_id) + platform_profile_row = legacy.db.fetch_one( + "SELECT * FROM platform_agent_profiles WHERE user_id = ? AND project_id = ? AND platform = ?", + (account["id"], project_id, platform), + ) if platform else None oneliner_profile_bundle = _oneliner_profile_runtime_snapshot(profile_row, account=account) governance_snapshot = { **governance, "oneliner_profile": oneliner_profile_bundle, "oneliner_profile_version": oneliner_profile_bundle.get("current_version") or {}, + "platform_agent_profile": _platform_agent_runtime_snapshot( + account, + platform_profile_row, + platform=platform, + project_id=project_id, + ) if platform else {}, } plan = _parse_json(row.get("plan_json"), {}) session_id = str(row.get("session_id") or "").strip() diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index b150392..1b3f901 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -227,6 +227,8 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertIn("oneliner_profile", payload["governance"]) self.assertIn("oneliner_profile_version", payload["governance"]) self.assertGreaterEqual(payload["governance"]["oneliner_profile_version"]["version_no"], 1) + self.assertIn("platform_agent_profile", payload["governance"]) + self.assertEqual(payload["governance"]["platform_agent_profile"]["platform"], "douyin") self.assertEqual(payload["events"][0]["event_type"], "run.created") def test_agent_run_confirm_transitions_to_queue_or_running_and_logs_events(self) -> None: @@ -310,6 +312,7 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertGreaterEqual(len(payload["result"]["result_sections"]["cards"]), 2) self.assertEqual(payload["result"]["result_sections"]["cards"][0]["title"], "当前焦点") self.assertGreaterEqual(payload["result"]["execution_card"]["oneliner_profile_version"]["version_no"], 1) + self.assertEqual(payload["result"]["execution_card"]["platform_agent_profile"]["platform"], "douyin") 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 7fdf0e6..7797d35 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -1131,6 +1131,7 @@ function renderOneLinerRunsHtml() { : ""; const canRetryCurrentRun = ["blocked", "failed", "cancelled"].includes(currentRun.run_status); const currentRunConfigVersion = currentRun.governance?.oneliner_profile_version || currentRun.governance?.oneliner_profile?.current_version || {}; + const currentRunPlatformAgentProfile = currentRun.result?.execution_card?.platform_agent_profile || currentRun.governance?.platform_agent_profile || {}; return `

近期运行概况

@@ -1232,6 +1233,18 @@ function renderOneLinerRunsHtml() {
` : ""} + ${currentRunPlatformAgentProfile.platform ? ` +
+

本轮平台 Agent

+

${escapeHtml(currentRunPlatformAgentProfile.mission || currentRunPlatformAgentProfile.name || `${currentRunPlatformAgentProfile.platform_label || platformLabel(currentRunPlatformAgentProfile.platform)} Agent 正在承接这轮执行。`)}

+
+ ${escapeHtml(currentRunPlatformAgentProfile.platform_label || platformLabel(currentRunPlatformAgentProfile.platform))} + ${currentRunPlatformAgentProfile.name ? `${escapeHtml(currentRunPlatformAgentProfile.name)}` : ""} + ${currentRunPlatformAgentProfile.assistant_name ? `${escapeHtml(currentRunPlatformAgentProfile.assistant_name)}` : ""} + ${currentRunPlatformAgentProfile.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(currentRunPlatformAgentProfile.readiness_label)} ${escapeHtml(formatNumber(currentRunPlatformAgentProfile.readiness_score || 0))}` : ""} +
+
+ ` : ""} ${!hasResultPayload && previewAction?.summary ? `
${escapeHtml(previewAction.summary)}
` : ""} ${planSteps.length ? `
@@ -1969,6 +1982,7 @@ function renderOneLinerExecutionPayloadHtml(payload) { : {}; const resultCards = safeArray(resultSections.cards).slice(0, 4); const configVersion = payload.execution_card?.oneliner_profile_version || payload.context?.oneliner_profile?.current_version || {}; + const platformAgentProfile = payload.execution_card?.platform_agent_profile || {}; const landingAttrs = buildMainAgentLandingAttrs({ runId: landingRunId, screen: landingScreen, @@ -1997,6 +2011,18 @@ function renderOneLinerExecutionPayloadHtml(payload) {
` : ""} + ${platformAgentProfile.platform ? ` +
+

本轮平台 Agent

+

${escapeHtml(platformAgentProfile.mission || platformAgentProfile.name || `${platformAgentProfile.platform_label || platformLabel(platformAgentProfile.platform)} Agent 正在承接这轮执行。`)}

+
+ ${escapeHtml(platformAgentProfile.platform_label || platformLabel(platformAgentProfile.platform))} + ${platformAgentProfile.name ? `${escapeHtml(platformAgentProfile.name)}` : ""} + ${platformAgentProfile.assistant_name ? `${escapeHtml(platformAgentProfile.assistant_name)}` : ""} + ${platformAgentProfile.readiness_label ? `= 50 ? "blue" : "orange"}">${escapeHtml(platformAgentProfile.readiness_label)} ${escapeHtml(formatNumber(platformAgentProfile.readiness_score || 0))}` : ""} +
+
+ ` : ""} ${resultCards.length ? `

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

diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index d607509..344ae2d 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -725,6 +725,8 @@ test("main agent result rendering offers a direct route back into the recommende assert.match(execution, /result_sections/); assert.match(execution, /当前焦点/); assert.match(execution, /detail-grid/); + assert.match(execution, /本轮平台 Agent/); + assert.match(execution, /platform_agent_profile/); assert.match(execution, /data-action="\$\{escapeHtml\(payload\.recommended_action\.action\)\}"/); assert.match(lastAction, /open-oneliner-run-result/); assert.match(lastAction, /recommended_action/);