feat: preserve requested context in main agent results
This commit is contained in:
@@ -4,6 +4,13 @@
|
||||
|
||||
## 2026-04-04
|
||||
|
||||
### 主 Agent 完成态保留精确对象上下文
|
||||
|
||||
- 主 Agent run 在创建时会把 `target_account_id / tracked_account_id / job_id / review_id / source_id / assistant_id` 这类对象上下文固化进执行计划,不再只记一个泛化的来源页面。
|
||||
- 完成态推荐动作现在会优先直接回到具体对象:可以直接打开当前账号、刷新当前跟踪对象、进入任务详情、打开复盘、继续录制维护,或回到刚才编辑的 Agent。
|
||||
- 前端推荐动作属性映射补齐了 `account_id / tracked_account_id / assistant_id`,当前运行卡、结果卡、最近动作卡和后续落点入口都能保住真实对象上下文。
|
||||
- 治理回归新增了“围绕当前账号继续分析”这条链路,锁住主 Agent 完成态结果必须返回 `select-account` 和真实 `account_id`。
|
||||
|
||||
### OneLiner 直接执行结果补齐精确落点
|
||||
|
||||
- OneLiner 直接执行动作现在统一返回结构化 `recommended_action`,不再只有“执行完成”说明块。
|
||||
|
||||
@@ -3375,12 +3375,43 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
platform_scope: str,
|
||||
) -> dict[str, Any]:
|
||||
requested_plan = dict(request.plan_request or {})
|
||||
requested_payload = dict(request.payload or {})
|
||||
raw_steps = requested_plan.get("steps") or []
|
||||
if not isinstance(raw_steps, list):
|
||||
raw_steps = [raw_steps]
|
||||
steps = [str(item).strip() for item in raw_steps if str(item).strip()]
|
||||
if not steps:
|
||||
steps = ["读取当前项目上下文", "结合治理层生成执行计划", "等待用户确认后执行"]
|
||||
requested_context: dict[str, Any] = {}
|
||||
for key in (
|
||||
"target_account_id",
|
||||
"targetAccountId",
|
||||
"tracked_account_id",
|
||||
"trackedAccountId",
|
||||
"job_id",
|
||||
"source_job_id",
|
||||
"sourceJobId",
|
||||
"review_id",
|
||||
"reviewId",
|
||||
"source_id",
|
||||
"sourceId",
|
||||
"assistant_id",
|
||||
"assistantId",
|
||||
"platform",
|
||||
):
|
||||
value = requested_payload.get(key)
|
||||
text = str(value or "").strip() if value is not None else ""
|
||||
if not text:
|
||||
continue
|
||||
normalized_key = {
|
||||
"targetAccountId": "target_account_id",
|
||||
"trackedAccountId": "tracked_account_id",
|
||||
"sourceJobId": "source_job_id",
|
||||
"reviewId": "review_id",
|
||||
"sourceId": "source_id",
|
||||
"assistantId": "assistant_id",
|
||||
}.get(key, key)
|
||||
requested_context[normalized_key] = text
|
||||
return {
|
||||
**requested_plan,
|
||||
"goal": str(requested_plan.get("goal") or request.title or "主 Agent 任务").strip() or "主 Agent 任务",
|
||||
@@ -3392,6 +3423,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
"source_action_key": str(request.source_action_key or "").strip(),
|
||||
"summary": str(request.summary or requested_plan.get("summary") or "").strip(),
|
||||
"requested_delivery_mode": _normalize_delivery_mode(request.delivery_mode),
|
||||
"requested_context": requested_context,
|
||||
"active_admin_override_notice": governance.get("active_admin_override_notice") or {},
|
||||
}
|
||||
|
||||
@@ -3412,13 +3444,43 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
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 {
|
||||
requested_context = plan.get("requested_context") if isinstance(plan.get("requested_context"), dict) else {}
|
||||
|
||||
def route(action: str, screen: str, label: str, summary: str, **extra: Any) -> dict[str, Any]:
|
||||
payload = {
|
||||
"action": action,
|
||||
"screen": screen,
|
||||
"label": label,
|
||||
"summary": summary,
|
||||
}
|
||||
for key, value in extra.items():
|
||||
text = str(value or "").strip() if value is not None else ""
|
||||
if not text:
|
||||
continue
|
||||
payload[key] = value
|
||||
return payload
|
||||
|
||||
target_account_id = str(requested_context.get("target_account_id") or "").strip()
|
||||
tracked_account_id = str(requested_context.get("tracked_account_id") or "").strip()
|
||||
job_id = str(requested_context.get("job_id") or requested_context.get("source_job_id") or "").strip()
|
||||
review_id = str(requested_context.get("review_id") or "").strip()
|
||||
source_id = str(requested_context.get("source_id") or "").strip()
|
||||
assistant_id = str(requested_context.get("assistant_id") or "").strip()
|
||||
|
||||
if review_id:
|
||||
return route("open-review-edit", "review", "打开复盘", "继续回到当前复盘对象完善结论和动作。", review_id=review_id, job_id=job_id)
|
||||
if source_screen == "review" and job_id:
|
||||
return route("open-review-from-job", "review", "继续写复盘", "继续围绕这条任务生成或完善复盘。", job_id=job_id)
|
||||
if source_screen in {"discovery", "tracking"} and target_account_id:
|
||||
return route("select-account", "discovery", "打开当前对象", "继续围绕当前账号查看详情、对标和分析结果。", account_id=target_account_id)
|
||||
if source_screen == "tracking" and tracked_account_id:
|
||||
return route("refresh-tracked-account", "tracking", "继续同步当前账号", "继续同步这条已跟踪账号,并回看最近更新。", tracked_account_id=tracked_account_id)
|
||||
if source_screen == "production" and job_id:
|
||||
return route("open-job-detail", "production", "看任务详情", "继续回到当前任务,查看执行状态和后续动作。", job_id=job_id)
|
||||
if source_screen == "production" and source_id:
|
||||
return route("edit-live-recorder-source", "production", "继续录制维护", "继续查看当前录制源的启停和录制文件。", source_id=source_id)
|
||||
if source_screen in {"playbook", "agent"} and assistant_id:
|
||||
return route("open-edit-assistant", "playbook", "继续编辑 Agent", "继续围绕当前 Agent 调整目标、说明和承接能力。", assistant_id=assistant_id)
|
||||
|
||||
source_routes = {
|
||||
"strategy": route("goto-strategy", "strategy", "回到我的策略", "继续查看当前用户策略与覆盖状态。"),
|
||||
@@ -3442,16 +3504,16 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
|
||||
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。"),
|
||||
"analyze_account": route(target_account_id and "select-account" or "goto-discovery", "discovery", target_account_id and "打开当前对象" or "回到找对标", "继续拆解当前账号和对标对象。", account_id=target_account_id),
|
||||
"analyze_top_videos": route(target_account_id and "select-account" or "goto-discovery", "discovery", target_account_id and "打开当前对象" or "回到找对标", "继续查看高分作品分析结论。", account_id=target_account_id),
|
||||
"track_account": route(tracked_account_id and "refresh-tracked-account" or "goto-tracking", "tracking", tracked_account_id and "继续同步当前账号" or "回到跟踪账号", "继续更新账号跟踪与日报。", tracked_account_id=tracked_account_id),
|
||||
"ai_video": route(job_id and "open-job-detail" or "goto-production", "production", job_id and "看任务详情" or "回到生产中心", "继续推进 AI 视频生产任务。", job_id=job_id),
|
||||
"real_cut": route(job_id and "open-job-detail" or "goto-production", "production", job_id and "看任务详情" or "回到生产中心", "继续推进实拍剪辑任务。", job_id=job_id),
|
||||
"live_recorder": route(source_id and "edit-live-recorder-source" or "goto-production", "production", source_id and "继续录制维护" or "回到生产中心", "继续查看录制维护与产物。", source_id=source_id),
|
||||
"review": route(review_id and "open-review-edit" or job_id and "open-review-from-job" or "goto-review", "review", review_id and "打开复盘" or job_id and "继续写复盘" or "回到发布与复盘", "继续补齐复盘与发布总结。", review_id=review_id, job_id=job_id),
|
||||
"create_assistant": route(assistant_id and "open-edit-assistant" or "goto-playbook", "playbook", assistant_id and "继续编辑 Agent" or "回到 Agent", "继续创建或调整项目 Agent。", assistant_id=assistant_id),
|
||||
"create_project": route("goto-intake", "projects", "回到我的项目", "继续创建或切换当前项目。"),
|
||||
"import_homepage": route("goto-discovery", "discovery", "回到找对标", "继续处理主页导入后的账号分析。"),
|
||||
"import_homepage": route(target_account_id and "select-account" or "goto-discovery", "discovery", target_account_id and "打开当前对象" or "回到找对标", "继续处理主页导入后的账号分析。", account_id=target_account_id),
|
||||
"ops_admin": route("goto-automation", "automation", "回到自动流程", "继续查看系统依赖和治理状态。"),
|
||||
"storage_status": route("goto-automation", "automation", "回到自动流程", "继续查看存储与依赖健康状态。"),
|
||||
}
|
||||
|
||||
@@ -389,6 +389,49 @@ class MainAgentGovernanceTests(unittest.TestCase):
|
||||
self.assertIn("run.progress", event_types)
|
||||
self.assertIn("run.done", event_types)
|
||||
|
||||
def test_agent_run_result_recommended_action_preserves_requested_object_context(self) -> None:
|
||||
run_response = self.client.post(
|
||||
"/v2/oneliner/runs",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={
|
||||
"project_id": self.ctx["project_id"],
|
||||
"source_screen": "discovery",
|
||||
"source_action_key": "selected-account-handoff",
|
||||
"title": "继续分析当前对象",
|
||||
"summary": "让主 Agent 沿着当前账号继续推进。",
|
||||
"intent_key": "analyze_account",
|
||||
"platform": "douyin",
|
||||
"platform_scope": "single_platform",
|
||||
"plan_request": {
|
||||
"goal": "继续分析当前对象",
|
||||
"steps": ["读取当前账号上下文", "结合当前策略生成下一步建议"],
|
||||
},
|
||||
"payload": {
|
||||
"target_account_id": "acct_focus_target",
|
||||
},
|
||||
},
|
||||
)
|
||||
self.assertEqual(run_response.status_code, 200, run_response.text)
|
||||
run_id = run_response.json()["id"]
|
||||
|
||||
confirm = self.client.post(
|
||||
f"/v2/oneliner/runs/{run_id}/confirm",
|
||||
headers=self.ctx["member_headers"],
|
||||
json={"reason": "继续围绕当前对象推进"},
|
||||
)
|
||||
self.assertEqual(confirm.status_code, 200, confirm.text)
|
||||
|
||||
detail = self.client.get(
|
||||
f"/v2/oneliner/runs/{run_id}",
|
||||
headers=self.ctx["member_headers"],
|
||||
)
|
||||
self.assertEqual(detail.status_code, 200, detail.text)
|
||||
payload = detail.json()
|
||||
self.assertEqual(payload["run_status"], "done")
|
||||
self.assertEqual(payload["result"]["recommended_action"]["action"], "select-account")
|
||||
self.assertEqual(payload["result"]["recommended_action"]["screen"], "discovery")
|
||||
self.assertEqual(payload["result"]["recommended_action"]["account_id"], "acct_focus_target")
|
||||
|
||||
def test_cancelled_run_can_be_retried_as_a_new_pending_run(self) -> None:
|
||||
create = self.client.post(
|
||||
"/v2/oneliner/runs",
|
||||
|
||||
@@ -7903,6 +7903,9 @@ function buildRecommendedActionAttrs(recommendedAction, landing = {}) {
|
||||
job_id: "data-job-id",
|
||||
review_id: "data-review-id",
|
||||
platform: "data-platform",
|
||||
account_id: "data-account-id",
|
||||
tracked_account_id: "data-tracked-account-id",
|
||||
assistant_id: "data-assistant-id",
|
||||
source_id: "data-source-id",
|
||||
file_id: "data-file-id",
|
||||
incident_id: "data-incident-id",
|
||||
|
||||
@@ -762,6 +762,9 @@ test("direct oneliner execution results preserve structured follow-up attrs", ()
|
||||
assert.match(helpers, /job_id: "data-job-id"/);
|
||||
assert.match(helpers, /review_id: "data-review-id"/);
|
||||
assert.match(helpers, /platform: "data-platform"/);
|
||||
assert.match(helpers, /account_id: "data-account-id"/);
|
||||
assert.match(helpers, /tracked_account_id: "data-tracked-account-id"/);
|
||||
assert.match(helpers, /assistant_id: "data-assistant-id"/);
|
||||
assert.match(helpers, /source_id: "data-source-id"/);
|
||||
assert.match(execution, /const recommendedAction = payload\.recommended_action/);
|
||||
assert.match(execution, /buildRecommendedActionAttrs\(recommendedAction/);
|
||||
|
||||
Reference in New Issue
Block a user