feat: route main agent results back into workbench

This commit is contained in:
kris
2026-03-29 19:08:30 +08:00
parent 1c5108dcc1
commit c83c54053f
4 changed files with 85 additions and 0 deletions

View File

@@ -2682,6 +2682,58 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
)
return bool(row)
def _build_agent_run_recommended_action(row: dict[str, Any], plan: dict[str, Any]) -> dict[str, Any]:
source_screen = str(plan.get("source_screen") or row.get("source_screen") or "").strip().lower()
source_action_key = str(plan.get("source_action_key") or row.get("source_action_key") or "").strip().lower()
intent_key = str(plan.get("intent_key") or row.get("intent_key") or "custom").strip().lower() or "custom"
def route(action: str, screen: str, label: str, summary: str) -> dict[str, Any]:
return {
"action": action,
"screen": screen,
"label": label,
"summary": summary,
}
source_routes = {
"strategy": route("goto-strategy", "strategy", "回到我的策略", "继续查看当前用户策略与覆盖状态。"),
"automation": route("goto-automation", "automation", "回到自动流程", "继续检查自动流程和依赖状态。"),
"playbook": route("goto-playbook", "playbook", "回到 Agent", "继续调整 Agent 与平台能力。"),
"agent": route("goto-playbook", "playbook", "回到 Agent", "继续调整 Agent 与平台能力。"),
"production": route("goto-production", "production", "回到生产中心", "继续推进生产任务与恢复动作。"),
"tracking": route("goto-tracking", "tracking", "回到跟踪账号", "继续查看跟踪摘要和更新提醒。"),
"review": route("goto-review", "review", "回到发布与复盘", "继续沉淀复盘结论和发布结果。"),
"discovery": route("goto-discovery", "discovery", "回到找对标", "继续查看账号拆解和高分样本。"),
"intake": route("goto-intake", "projects", "回到我的项目", "继续切换项目或补齐项目基础信息。"),
"projects": route("goto-intake", "projects", "回到我的项目", "继续切换项目或补齐项目基础信息。"),
}
if source_screen == "dashboard" and source_action_key == "homepage-primary-action":
return route("goto-discovery", "discovery", "回到找对标", "继续查看首页当前最优先的对标与高分样本动作。")
if source_screen == "dashboard" and source_action_key.startswith("homepage-secondary-action-"):
if intent_key == "track_account":
return route("goto-tracking", "tracking", "回到跟踪账号", "继续跟进首页建议的重点账号跟踪。")
return route("goto-production", "production", "回到生产中心", "继续处理首页建议的生产推进动作。")
if source_screen in source_routes:
return source_routes[source_screen]
intent_routes = {
"analyze_account": route("goto-discovery", "discovery", "回到找对标", "继续拆解当前账号和对标对象。"),
"analyze_top_videos": route("goto-discovery", "discovery", "回到找对标", "继续查看高分作品分析结论。"),
"track_account": route("goto-tracking", "tracking", "回到跟踪账号", "继续更新账号跟踪与日报。"),
"ai_video": route("goto-production", "production", "回到生产中心", "继续推进 AI 视频生产任务。"),
"real_cut": route("goto-production", "production", "回到生产中心", "继续推进实拍剪辑任务。"),
"live_recorder": route("goto-production", "production", "回到生产中心", "继续查看录制维护与产物。"),
"review": route("goto-review", "review", "回到发布与复盘", "继续补齐复盘与发布总结。"),
"create_assistant": route("goto-playbook", "playbook", "回到 Agent", "继续创建或调整项目 Agent。"),
"create_project": route("goto-intake", "projects", "回到我的项目", "继续创建或切换当前项目。"),
"import_homepage": route("goto-discovery", "discovery", "回到找对标", "继续处理主页导入后的账号分析。"),
"ops_admin": route("goto-automation", "automation", "回到自动流程", "继续查看系统依赖和治理状态。"),
"storage_status": route("goto-automation", "automation", "回到自动流程", "继续查看存储与依赖健康状态。"),
}
if intent_key in intent_routes:
return intent_routes[intent_key]
return route("goto-production", "production", "回到生产中心", "继续推进当前主 Agent 任务的执行结果。")
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 "")
@@ -2724,6 +2776,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
steps = ["读取当前项目上下文", "结合治理层生成执行计划", "收口为可执行建议"]
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_payload = {
"result_kind": "main_agent_plan",
"goal": str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务",
@@ -2733,6 +2786,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
"intent_key": str(plan.get("intent_key") or row.get("intent_key") or "custom").strip() or "custom",
"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,
"active_admin_override_notice": _parse_json(row.get("active_admin_override_notice_json"), {}),
}
_log_agent_run_event(

View File

@@ -286,6 +286,8 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertEqual(payload["run_status"], "done")
self.assertTrue(payload["finished_at"])
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")
event_types = [item["event_type"] for item in payload["events"]]
self.assertIn("run.progress", event_types)
self.assertIn("run.done", event_types)

View File

@@ -977,6 +977,7 @@ 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 recommendedAction = resultPayload?.recommended_action || null;
const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length);
const runStatusLabel = {
needs_confirmation: "待确认",
@@ -1043,7 +1044,9 @@ function renderOneLinerRunsHtml() {
<span class="tag ${statusTone}">${escapeHtml(currentRun.status_summary || "主 Agent 正在推进中")}</span>
`}
${hasResultPayload ? `<span class="tag clickable-tag" data-action="open-oneliner-run-result" data-run-id="${escapeHtml(currentRun.id)}">查看结果</span>` : ""}
${recommendedAction?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(recommendedAction.action)}">${escapeHtml(recommendedAction.label || "回到对应页面")}</span>` : ""}
</div>
${recommendedAction?.summary ? `<div class="panel-subtitle" style="margin-top:8px;">${escapeHtml(recommendedAction.summary)}</div>` : ""}
${hasResultPayload ? `
<div class="task-item compact" style="margin-top:10px;">
<h4>执行结果</h4>
@@ -1737,8 +1740,15 @@ function renderOneLinerExecutionPayloadHtml(payload) {
${payload.platform ? `<span class="tag blue">${escapeHtml(platformLabel(payload.platform))}</span>` : ""}
<span class="tag">${escapeHtml(payload.platform_scope === "all_platforms" ? "全平台" : "单平台")}</span>
<span class="tag green">已收口</span>
${payload.recommended_action?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(payload.recommended_action.action)}">${escapeHtml(payload.recommended_action.label || "回到对应页面")}</span>` : ""}
</div>
</div>
${payload.recommended_action?.summary ? `
<div class="task-item compact" style="margin-top:12px;">
<h4>建议回跳</h4>
<p>${escapeHtml(payload.recommended_action.summary)}</p>
</div>
` : ""}
${safeArray(payload.next_steps).length ? `
<div class="list" style="margin-top:12px;">
${safeArray(payload.next_steps).slice(0, 4).map((step, index) => `
@@ -6174,6 +6184,9 @@ function extractGeneratedCopy(payload) {
function renderLastActionCard() {
if (!appState.lastAction) return "";
const payload = appState.lastAction.payload || {};
const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null;
const runId = payload?.id || payload?.run_id || "";
return `
<div class="panel pad">
<div class="panel-head">
@@ -6186,6 +6199,12 @@ function renderLastActionCard() {
<div class="task-item">
<h4>${escapeHtml(appState.lastAction.title)}</h4>
<p>${escapeHtml(appState.lastAction.summary)}</p>
${(runId || recommendedAction?.action) ? `
<div class="task-meta" style="margin-top:10px;">
${runId ? `<span class="tag clickable-tag" data-action="open-oneliner-run-result" data-run-id="${escapeHtml(runId)}">查看结果</span>` : ""}
${recommendedAction?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(recommendedAction.action)}">${escapeHtml(recommendedAction.label || "回到对应页面")}</span>` : ""}
</div>
` : ""}
</div>
</div>
`;

View File

@@ -136,6 +136,7 @@ test("oneliner panel includes a dedicated runtime header for agent runs", () =>
assert.match(runtime, /当前计划/);
assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/);
assert.match(runtime, /open-oneliner-run-result/);
assert.match(runtime, /recommended_action/);
});
test("oneliner meta and action handlers expose governance entry points", () => {
@@ -229,3 +230,12 @@ test("user governance UI exposes personal history and rollback entrypoints", ()
assert.match(actions, /name === "open-user-global-policy-history"/);
assert.match(actions, /name === "open-user-platform-policy-history"/);
});
test("main agent result rendering offers a direct route back into the recommended screen", () => {
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, /data-action="\$\{escapeHtml\(payload\.recommended_action\.action\)\}"/);
assert.match(lastAction, /open-oneliner-run-result/);
assert.match(lastAction, /recommended_action/);
});