feat: direct-execute review and playbook quick actions
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-05 07:36:01 +08:00
parent de36ce7fe9
commit ad05a4dfbc
5 changed files with 52 additions and 12 deletions

View File

@@ -457,3 +457,4 @@
- 主 Agent 落到 `找对标 / Agent / 生产中心 / 发布与复盘` 后,快捷动作里原先的 `高分分析 / 新建 Agent / 写复盘 / 做 AI 视频 / 做实拍剪辑` 已优先改成 direct-execute。
- 这些动作现在直接调用 `OneLiner` 执行器并按真实结果继续落到对象详情、Agent 编辑页、复盘页或任务详情,而不是先打开旧表单。
- `review-draft` 现在支持显式 `source_job_id`,所以从任务详情、复盘页和最近完成任务入口点“写复盘”,会围绕指定任务直接生成草稿,不再总是退回“最近一条任务”。

View File

@@ -5734,7 +5734,18 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
}
async def _run_review_draft() -> dict[str, Any]:
latest_job = _latest_project_job(account, project_id=project["id"])
requested_job_id = str(
requested_payload.get("source_job_id")
or requested_payload.get("sourceJobId")
or requested_payload.get("job_id")
or requested_payload.get("jobId")
or ""
).strip()
latest_job = _load_owned_job(account, requested_job_id) if requested_job_id else None
if latest_job and str(latest_job.get("project_id") or "") != str(project["id"]):
latest_job = None
if not latest_job:
latest_job = _latest_project_job(account, project_id=project["id"])
if not latest_job:
raise HTTPException(status_code=404, detail="No completed job available for review draft")
existing = legacy.db.fetch_one(

View File

@@ -969,6 +969,23 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertEqual(review_payload["recommended_action"]["job_id"], "job_review_source")
self.assertTrue(review_payload["recommended_action"]["review_id"])
explicit_review_response = self.client.post(
"/v2/oneliner/actions/execute",
headers=self.ctx["member_headers"],
json={
"action_key": "review-draft",
"project_id": self.ctx["project_id"],
"platform": "douyin",
"payload": {
"source_job_id": "job_review_source",
},
},
)
self.assertEqual(explicit_review_response.status_code, 200, explicit_review_response.text)
explicit_review_payload = explicit_review_response.json()
self.assertEqual(explicit_review_payload["recommended_action"]["action"], "open-review-edit")
self.assertEqual(explicit_review_payload["recommended_action"]["job_id"], "job_review_source")
self_check_response = self.client.post(
"/v2/oneliner/actions/execute",
headers=self.ctx["member_headers"],

View File

@@ -5693,7 +5693,7 @@ function renderDashboardScreen() {
return screenShell(
"项目总台",
"先做最能推进当前项目的一步,再按需看概览。",
`${button("新建项目", "create-project")} ${button("导入主页", "open-import-homepage")} ${button("创建 Agent", "open-create-assistant", "primary")}`,
`${button("新建项目", "create-project")} ${button("导入主页", "open-import-homepage")} ${button("创建 Agent", "direct-create-assistant", "primary")}`,
dashboardHomeRenderer?.renderDashboardHome
? `<div id="dashboard-workspace-anchor">${dashboardHomeRenderer.renderDashboardHome(homeModel, { escapeHtml })}</div>`
: renderEmptyState("首页模块未加载", "请刷新页面后重试。")
@@ -6698,7 +6698,7 @@ function renderPlaybookScreen() {
return screenShell(
"Agent",
"这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。",
`${button("配置 OneLiner", "open-oneliner-profile")} ${button("看配置历史", "open-oneliner-profile-history", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "open-create-assistant")} ${button("去生产", "goto-production", "primary")}`,
`${button("配置 OneLiner", "open-oneliner-profile")} ${button("看配置历史", "open-oneliner-profile-history", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "direct-create-assistant")} ${button("去生产", "goto-production", "primary")}`,
`
${renderMainAgentLandingNotice("playbook")}
<div class="hero-card mobile-secondary-card">
@@ -6736,7 +6736,7 @@ function renderPlaybookScreen() {
? `${actionTag("看平台 Agent", "select-page-tab", `data-page-tab-key="playbookDetailTab" data-page-tab-value="platform_agents"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}`
: activeTab === "models"
? `${actionTag("设主模型", "open-preferred-model")} ${actionTag("回工作区", "select-page-tab", `data-page-tab-key="playbookDetailTab" data-page-tab-value="workspace"`)}`
: `${actionTag("配置 OneLiner", "open-oneliner-profile")} ${actionTag("看配置历史", "open-oneliner-profile-history")} ${actionTag(currentAssistant ? "去生产" : "新建 Agent", currentAssistant ? "goto-production" : "open-create-assistant")} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}`
: `${actionTag("配置 OneLiner", "open-oneliner-profile")} ${actionTag("看配置历史", "open-oneliner-profile-history")} ${actionTag(currentAssistant ? "去生产" : "新建 Agent", currentAssistant ? "goto-production" : "direct-create-assistant")} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}`
}
</div>
</div>
@@ -7296,7 +7296,7 @@ function renderReviewScreen() {
return screenShell(
"发布与复盘",
"先看已保存复盘,再把完成任务转成结构化复盘。",
`${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`,
`${button("写复盘", "direct-review-draft")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`,
`
${renderMainAgentLandingNotice("review")}
<div class="hero-card mobile-secondary-card">
@@ -7313,7 +7313,7 @@ function renderReviewScreen() {
<h4>${escapeHtml(reviewTaskTitle)}</h4>
<p>${escapeHtml(reviewTaskSummary)}</p>
<div class="task-meta">
${actionTag(completed.length ? "写复盘" : reviews.length ? "看复盘" : "去生产", completed.length ? "open-review-from-job" : reviews.length ? "goto-review" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")}
${actionTag(completed.length ? "写复盘" : reviews.length ? "看复盘" : "去生产", completed.length ? "direct-review-draft" : reviews.length ? "goto-review" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")}
${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)}
<span class="tag blue">${escapeHtml(`已保存 ${formatNumber(reviews.length)}`)}</span>
<span class="tag">${escapeHtml(`已发布 ${formatNumber(publishedReviewCount)}`)}</span>
@@ -7333,7 +7333,7 @@ function renderReviewScreen() {
: "当前还没有可用复盘,先回到生产中心跑出一条完成链路。"
)}</p>
<div class="task-meta">
${actionTag(completed.length ? "写复盘" : "去生产", completed.length ? "open-review-from-job" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")}
${actionTag(completed.length ? "写复盘" : "去生产", completed.length ? "direct-review-draft" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")}
${actionTag("刷新", "refresh-data")}
${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)}
</div>
@@ -7375,7 +7375,7 @@ function renderReviewScreen() {
<div class="task-meta">
<span class="tag green">已完成</span>
<span class="tag">${escapeHtml(job.line_type || "analysis")}</span>
${actionTag("写复盘", "open-review-from-job", `data-job-id="${escapeHtml(job.id)}"`)}
${actionTag("写复盘", "direct-review-draft", `data-job-id="${escapeHtml(job.id)}"`)}
${canDeriveAiVideo(job) ? renderPipelineJobTag("aiVideo", job, "做 AI 视频") : ""}
${canDeriveRealCut(job) ? renderPipelineJobTag("realCut", job, "做实拍剪辑") : ""}
${actionTag("看详情", "open-job-detail", `data-job-id="${escapeHtml(job.id)}"`)}
@@ -8968,7 +8968,7 @@ function renderLastJobDetailCard() {
<div class="task-meta">
<span class="tag">${escapeHtml(detail.job.line_type || "-")}</span>
${detail.job.status === "failed" ? `<span class="tag ${recovery.recoverable ? "green" : "orange"}">${escapeHtml(recovery.reason)}</span>` : ""}
${detail.job.status === "completed" ? actionTag("写复盘", "open-review-from-job", `data-job-id="${escapeHtml(detail.job.id)}"`) : ""}
${detail.job.status === "completed" ? actionTag("写复盘", "direct-review-draft", `data-job-id="${escapeHtml(detail.job.id)}"`) : ""}
${detail.job.status === "failed" ? actionTag(
recovery.actionLabel,
recovery.recoverable ? "recover-job" : recovery.actionKey,
@@ -12361,7 +12361,8 @@ document.addEventListener("click", async (event) => {
if (name === "direct-create-ai-video") {
await runDirectWorkbenchAction("create-ai-video", {
busyLabel: "正在创建 AI 视频任务...",
errorTitle: "创建 AI 视频任务失败"
errorTitle: "创建 AI 视频任务失败",
payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {}
});
return;
}
@@ -12372,7 +12373,8 @@ document.addEventListener("click", async (event) => {
if (name === "direct-create-real-cut") {
await runDirectWorkbenchAction("create-real-cut", {
busyLabel: "正在创建实拍剪辑任务...",
errorTitle: "创建实拍剪辑任务失败"
errorTitle: "创建实拍剪辑任务失败",
payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {}
});
return;
}
@@ -12383,7 +12385,8 @@ document.addEventListener("click", async (event) => {
if (name === "direct-review-draft") {
await runDirectWorkbenchAction("review-draft", {
busyLabel: "正在生成复盘草稿...",
errorTitle: "生成复盘草稿失败"
errorTitle: "生成复盘草稿失败",
payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {}
});
return;
}

View File

@@ -998,6 +998,14 @@ test("main agent landing quick actions prefer direct execute flows where executo
assert.match(APP, /async function runDirectWorkbenchAction\(executorKey, options = \{\}\)/);
});
test("playbook and review high-frequency actions now reuse direct execute handlers", () => {
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
assert.match(playbook, /direct-create-assistant/);
assert.match(review, /direct-review-draft/);
assert.match(APP, /payload: action\.dataset\.jobId \? \{ source_job_id: action\.dataset\.jobId \} : \{\}/);
});
test("main agent landing notices expose a compact mobile follow-up strip", () => {
const landing = extractBetween(APP, "function renderMainAgentLandingNotice(screenKey)", "function renderEmptyState(title, description)");
assert.match(landing, /mobile-only compact-summary-row/);