feat: surface recent platform agent execution feedback
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled

This commit is contained in:
kris
2026-04-04 04:29:54 +08:00
parent f890a0ace7
commit a76bdb432f
6 changed files with 207 additions and 0 deletions

View File

@@ -120,3 +120,27 @@
- 后端单测 `36/36`
- `bash scripts/check_repo_baseline.sh`
- `bash scripts/smoke_fnos_storyforge_lan.sh`
## 2026-04-04
### 平台 Agent 执行回写闭环
- 平台 Agent 配置现在不只是“被主 Agent 带进执行链”,还会在主 Agent 完成态后反向记录最近一次执行信息。
- `platform_agent_profiles` 新增最近执行回写字段,保存:
- 最近 run id
- run 状态
- 最近使用时间
- 意图 key
- 使用的 OneLiner 配置版本号
- 执行摘要
- 来源页面
- `GET /v2/platform-agents` 现在会返回 `recent_execution`,平台 Agent 总览卡和详情弹层都会直接显示“最近执行”和“配置 vN”方便追溯平台配置最近是怎么被主 Agent 用起来的。
- 这条回写链已经覆盖到主 Agent 完成态读取路径,避免只在治理层能看到版本,执行面却看不到最近一次真实使用记录。
### 回归护栏
- 后端新增平台 Agent live 路由回写测试,确认:
- 创建并确认一条主 Agent run 之后
- `GET /v2/platform-agents` 能返回 `recent_execution`
- 最近执行会带上 run id、intent 和 `oneliner_profile_version_no`
- 前端工作台测试新增平台 Agent 最近执行渲染断言,锁住总览卡和详情弹层里的“最近执行”展示。

View File

@@ -364,6 +364,15 @@ class Database:
"provider_task_id": "TEXT NOT NULL DEFAULT ''",
"result_json": "TEXT NOT NULL DEFAULT '{}'",
},
"platform_agent_profiles": {
"last_run_id": "TEXT NOT NULL DEFAULT ''",
"last_run_status": "TEXT NOT NULL DEFAULT ''",
"last_used_at": "TEXT NOT NULL DEFAULT ''",
"last_intent_key": "TEXT NOT NULL DEFAULT ''",
"last_oneliner_profile_version_no": "INTEGER NOT NULL DEFAULT 0",
"last_execution_summary": "TEXT NOT NULL DEFAULT ''",
"last_source_screen": "TEXT NOT NULL DEFAULT ''",
},
}
for table, columns in table_columns.items():

View File

@@ -1225,6 +1225,46 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
readiness_label = "可用"
else:
readiness_label = "待补全"
recent_execution = None
if row and str(row.get("last_run_id") or "").strip():
recent_execution = {
"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(),
"intent_key": str(row.get("last_intent_key") or "").strip(),
"intent_label": INTENT_LABELS.get(str(row.get("last_intent_key") or "").strip() or "custom", "主 Agent 任务"),
"oneliner_profile_version_no": int(row.get("last_oneliner_profile_version_no") or 0),
"summary": str(row.get("last_execution_summary") or "").strip(),
"source_screen": str(row.get("last_source_screen") 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(
"""
SELECT * FROM agent_runs
WHERE user_id = ? AND project_id = ? AND platform = ?
ORDER BY updated_at DESC, created_at DESC
LIMIT 1
""",
(account["id"], project_id, platform),
)
if latest_run_row:
latest_result = _parse_json(latest_run_row.get("result_json"), {})
latest_governance = _parse_json(latest_run_row.get("governance_json"), {})
latest_profile_version = (
((latest_result.get("execution_card") or {}).get("oneliner_profile_version"))
or latest_governance.get("oneliner_profile_version")
or {}
)
recent_execution = {
"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(),
"intent_key": str(latest_run_row.get("intent_key") or "").strip(),
"intent_label": INTENT_LABELS.get(str(latest_run_row.get("intent_key") or "").strip() or "custom", "主 Agent 任务"),
"oneliner_profile_version_no": int(latest_profile_version.get("version_no") or 0),
"summary": str(latest_run_row.get("status_summary") or "").strip(),
"source_screen": str(latest_run_row.get("source_screen") or "").strip(),
}
return {
"id": row["id"] if row else "",
"user_id": account["id"],
@@ -1241,6 +1281,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
"skill_count": skill_count,
"recent_memory": _memory_payload(recent_memory_row) if recent_memory_row else None,
"recent_skill": _skill_payload(recent_skill_row) if recent_skill_row else None,
"recent_execution": recent_execution,
"readiness_score": readiness_score,
"readiness_label": readiness_label,
"assistant": assistant,
@@ -1275,8 +1316,46 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
"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(),
"recent_execution": payload.get("recent_execution") or {},
}
def _record_platform_agent_execution_feedback(
account_id: str,
*,
project_id: str,
platform: str,
run_id: str,
run_status: str,
intent_key: str,
source_screen: str,
oneliner_profile_version_no: int,
execution_summary: str,
) -> None:
normalized_platform = _safe_platform(platform, fallback="") if str(platform or "").strip() else ""
if not normalized_platform or not str(project_id or "").strip() or not str(run_id or "").strip():
return
legacy.db.execute(
"""
UPDATE platform_agent_profiles
SET last_run_id = ?, last_run_status = ?, last_used_at = ?, last_intent_key = ?,
last_oneliner_profile_version_no = ?, last_execution_summary = ?, last_source_screen = ?, updated_at = ?
WHERE user_id = ? AND project_id = ? AND platform = ?
""",
(
str(run_id).strip(),
str(run_status or "").strip(),
now(),
str(intent_key or "custom").strip() or "custom",
int(oneliner_profile_version_no or 0),
str(execution_summary or "").strip(),
str(source_screen or "").strip(),
now(),
account_id,
project_id,
normalized_platform,
),
)
def _memory_payload(row: dict[str, Any]) -> dict[str, Any]:
return {
"id": row["id"],
@@ -3285,6 +3364,17 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
""",
(execution_summary, _dump(result_payload), timestamp, timestamp, run_id),
)
_record_platform_agent_execution_feedback(
str(row.get("user_id") or "").strip(),
project_id=str(row.get("project_id") or "").strip(),
platform=str(row.get("platform") or "").strip(),
run_id=run_id,
run_status="done",
intent_key=str(plan.get("intent_key") or row.get("intent_key") or "custom").strip() or "custom",
source_screen=str(row.get("source_screen") or "").strip(),
oneliner_profile_version_no=int(oneliner_profile_version.get("version_no") or 0),
execution_summary=execution_summary,
)
_log_agent_run_event(
run_id,
event_type="run.done",

View File

@@ -821,6 +821,55 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertIn("route_checks", self_check_payload)
self.assertIn("score", self_check_payload)
run_response = self.client.post(
"/v2/oneliner/runs",
headers=self.ctx["member_headers"],
json={
"project_id": self.ctx["project_id"],
"source_screen": "playbook",
"source_action_key": "platform-agent-handoff",
"title": "验证平台 Agent 执行回写",
"summary": "确认主 Agent 完成态会回写最近平台执行信息。",
"intent_key": "governance_review",
"platform": "douyin",
"delivery_mode": "confirm_first",
"plan": {
"goal": "验证平台 Agent 执行回写",
"summary": "检查主 Agent 完成态后平台 Agent 是否记录最近执行。",
"steps": ["读取当前主配置", "读取当前平台 Agent", "生成执行结果"],
},
},
)
self.assertEqual(run_response.status_code, 200, run_response.text)
run_payload = run_response.json()
confirm_response = self.client.post(
f"/v2/oneliner/runs/{run_payload['id']}/confirm",
headers=self.ctx["member_headers"],
json={"note": "执行平台 Agent 回写验证"},
)
self.assertEqual(confirm_response.status_code, 200, confirm_response.text)
detail_response = self.client.get(
f"/v2/oneliner/runs/{run_payload['id']}",
headers=self.ctx["member_headers"],
)
self.assertEqual(detail_response.status_code, 200, detail_response.text)
detail_payload = detail_response.json()
self.assertEqual(detail_payload["run_status"], "done")
refreshed_agents = self.client.get(
"/v2/platform-agents",
headers=self.ctx["member_headers"],
params={"project_id": self.ctx["project_id"]},
)
self.assertEqual(refreshed_agents.status_code, 200, refreshed_agents.text)
refreshed_douyin = next(item for item in refreshed_agents.json()["items"] if item["platform"] == "douyin")
self.assertIn("recent_execution", refreshed_douyin)
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)
def test_admin_ops_routes_are_live(self) -> None:
now = self.db_module.utc_now()
job_id = "job_failed_admin_ops"

View File

@@ -4422,6 +4422,18 @@ function renderPlatformAgentPanel() {
` : ""}
</div>
` : ""}
${item.recent_execution?.run_id ? `
<div class="task-item compact" style="margin-top:10px;">
<h4>最近执行</h4>
<p>${escapeHtml(item.recent_execution.summary || "最近一次主 Agent 执行已回写到当前平台 Agent。")}</p>
<div class="task-meta">
<span class="tag blue">${escapeHtml(item.recent_execution.intent_label || "主 Agent 任务")}</span>
<span class="tag">${escapeHtml(item.recent_execution.run_status || "done")}</span>
${item.recent_execution.oneliner_profile_version_no ? `<span class="tag">配置 v${escapeHtml(formatNumber(item.recent_execution.oneliner_profile_version_no))}</span>` : ""}
${item.recent_execution.source_screen ? `<span class="tag">${escapeHtml(screenLabel(item.recent_execution.source_screen) || item.recent_execution.source_screen)}</span>` : ""}
</div>
</div>
` : ""}
<div class="task-meta" style="margin-top:10px;">
<span class="tag clickable-tag" data-action="open-platform-agent-detail" data-platform="${escapeHtml(item.platform)}">查看详情</span>
<span class="tag clickable-tag" data-action="open-platform-agent-profile" data-platform="${escapeHtml(item.platform)}">配置</span>
@@ -9342,6 +9354,18 @@ async function openPlatformAgentDetailAction(platform) {
<span class="tag">${escapeHtml(profile.assistant?.name || "未绑执行 Agent")}</span>
</div>
</div>
${profile.recent_execution?.run_id ? `
<div class="task-item compact" style="margin-top:12px;">
<h4>最近执行</h4>
<p>${escapeHtml(profile.recent_execution.summary || "最近一次主 Agent 执行已回写到当前平台 Agent。")}</p>
<div class="task-meta">
<span class="tag blue">${escapeHtml(profile.recent_execution.intent_label || "主 Agent 任务")}</span>
<span class="tag">${escapeHtml(profile.recent_execution.run_status || "done")}</span>
${profile.recent_execution.oneliner_profile_version_no ? `<span class="tag">配置 v${escapeHtml(formatNumber(profile.recent_execution.oneliner_profile_version_no))}</span>` : ""}
${profile.recent_execution.source_screen ? `<span class="tag">${escapeHtml(screenLabel(profile.recent_execution.source_screen) || profile.recent_execution.source_screen)}</span>` : ""}
</div>
</div>
` : ""}
<div class="two-col" style="margin-top:12px;">
<div>
<div class="panel-subtitle">最近记忆</div>

View File

@@ -271,6 +271,17 @@ test("governance and quota panels use real empty-state language instead of backe
assert.match(platformAgents, /open-platform-agent-profile/);
});
test("platform agent surfaces recent execution feedback from main agent runs", () => {
const platformAgents = extractBetween(APP, "function renderPlatformAgentPanel()", "function renderAdminOpsPanel()");
const detail = extractBetween(APP, "async function openPlatformAgentDetailAction(platform)", "function openPlatformSkillReviewAction(platform, skillId, accepted)");
assert.match(platformAgents, /recent_execution/);
assert.match(platformAgents, /最近执行/);
assert.match(platformAgents, /配置 v/);
assert.match(detail, /最近执行/);
assert.match(detail, /recent_execution/);
});
test("quota and review screens foreground live next-step guidance", () => {
const tenantQuota = extractBetween(APP, "function renderTenantQuotaPanel()", "function policyScopeTagLabel(");
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");