feat: deepen main agent handoff workflow

This commit is contained in:
kris
2026-03-29 19:31:28 +08:00
parent c83c54053f
commit 6c0f40c908
4 changed files with 303 additions and 19 deletions

View File

@@ -1043,6 +1043,11 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
return [_agent_run_event_payload(row) for row in rows] return [_agent_run_event_payload(row) for row in rows]
def _agent_run_payload(row: dict[str, Any], *, include_events: bool = True) -> dict[str, Any]: def _agent_run_payload(row: dict[str, Any], *, include_events: bool = True) -> dict[str, Any]:
plan = _parse_json(row.get("plan_json"), {})
result = _parse_json(row.get("result_json"), {})
recommended_preview_action = result.get("recommended_action") if isinstance(result, dict) else {}
if not recommended_preview_action:
recommended_preview_action = _build_agent_run_recommended_action(row, plan)
payload = { payload = {
"id": row["id"], "id": row["id"],
"user_id": row.get("user_id", ""), "user_id": row.get("user_id", ""),
@@ -1060,9 +1065,10 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
"run_status": row.get("run_status", "needs_confirmation"), "run_status": row.get("run_status", "needs_confirmation"),
"scheduling_mode": row.get("scheduling_mode", "queued"), "scheduling_mode": row.get("scheduling_mode", "queued"),
"active_executor_key": row.get("active_executor_key", "main_agent"), "active_executor_key": row.get("active_executor_key", "main_agent"),
"plan": _parse_json(row.get("plan_json"), {}), "plan": plan,
"governance": _parse_json(row.get("governance_json"), {}), "governance": _parse_json(row.get("governance_json"), {}),
"result": _parse_json(row.get("result_json"), {}), "result": result,
"recommended_preview_action": recommended_preview_action,
"status_summary": row.get("status_summary", ""), "status_summary": row.get("status_summary", ""),
"needs_user_input": bool(row.get("needs_user_input")), "needs_user_input": bool(row.get("needs_user_input")),
"blocked_reason": row.get("blocked_reason", ""), "blocked_reason": row.get("blocked_reason", ""),
@@ -2779,6 +2785,7 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
recommended_action = _build_agent_run_recommended_action(row, plan) recommended_action = _build_agent_run_recommended_action(row, plan)
result_payload = { result_payload = {
"result_kind": "main_agent_plan", "result_kind": "main_agent_plan",
"run_id": run_id,
"goal": str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务", "goal": str(plan.get("goal") or row.get("title") or "主 Agent 任务").strip() or "主 Agent 任务",
"summary_text": summary_text, "summary_text": summary_text,
"execution_summary": execution_summary, "execution_summary": execution_summary,

View File

@@ -209,6 +209,8 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertEqual(payload["platform_scope"], "single_platform") self.assertEqual(payload["platform_scope"], "single_platform")
self.assertEqual(payload["session_id"][:5], "oline") self.assertEqual(payload["session_id"][:5], "oline")
self.assertEqual(payload["plan"]["goal"], "跟进重点账号") self.assertEqual(payload["plan"]["goal"], "跟进重点账号")
self.assertEqual(payload["recommended_preview_action"]["action"], "goto-discovery")
self.assertEqual(payload["recommended_preview_action"]["screen"], "discovery")
self.assertEqual(payload["governance"]["project_id"], self.ctx["project_id"]) self.assertEqual(payload["governance"]["project_id"], self.ctx["project_id"])
self.assertIn("layers", payload["governance"]) self.assertIn("layers", payload["governance"])
self.assertEqual(payload["events"][0]["event_type"], "run.created") self.assertEqual(payload["events"][0]["event_type"], "run.created")
@@ -243,6 +245,8 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertEqual(confirm.status_code, 200, confirm.text) self.assertEqual(confirm.status_code, 200, confirm.text)
payload = confirm.json() payload = confirm.json()
self.assertIn(payload["run_status"], {"queued", "running"}) self.assertIn(payload["run_status"], {"queued", "running"})
self.assertEqual(payload["recommended_preview_action"]["action"], "goto-strategy")
self.assertEqual(payload["recommended_preview_action"]["screen"], "strategy")
event_types = [item["event_type"] for item in payload["events"]] event_types = [item["event_type"] for item in payload["events"]]
self.assertIn("run.created", event_types) self.assertIn("run.created", event_types)
self.assertIn("run.confirmed", event_types) self.assertIn("run.confirmed", event_types)

View File

@@ -80,6 +80,7 @@ const appState = {
busy: false, busy: false,
message: "", message: "",
lastAction: null, lastAction: null,
mainAgentLanding: null,
lastGeneratedCopy: null, lastGeneratedCopy: null,
lastSimilaritySearch: null, lastSimilaritySearch: null,
lastJobDetail: null lastJobDetail: null
@@ -977,7 +978,23 @@ function renderOneLinerRunsHtml() {
const runEvents = safeArray(currentRun.events).slice(-3); const runEvents = safeArray(currentRun.events).slice(-3);
const planSteps = safeArray(currentRun.plan?.steps).slice(0, 4); const planSteps = safeArray(currentRun.plan?.steps).slice(0, 4);
const resultPayload = currentRun.result && typeof currentRun.result === "object" ? currentRun.result : null; const resultPayload = currentRun.result && typeof currentRun.result === "object" ? currentRun.result : null;
const previewAction = currentRun.recommended_preview_action || null;
const recommendedAction = resultPayload?.recommended_action || null; const recommendedAction = resultPayload?.recommended_action || null;
const pendingRunCount = safeArray(runs).filter((item) => item.run_status === "needs_confirmation").length;
const activeRunCount = safeArray(runs).filter((item) => ["queued", "running", "blocked"].includes(item.run_status)).length;
const completedRunCount = safeArray(runs).filter((item) => item.run_status === "done").length;
const previewLandingAttrs = buildMainAgentLandingAttrs({
runId: currentRun.id || "",
screen: previewAction?.screen || "",
title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务",
summary: previewAction?.summary || currentRun.summary || ""
});
const resultLandingAttrs = buildMainAgentLandingAttrs({
runId: currentRun.id || "",
screen: recommendedAction?.screen || "",
title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务",
summary: recommendedAction?.summary || currentRun.status_summary || ""
});
const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length); const hasResultPayload = Boolean(resultPayload && Object.keys(resultPayload).length);
const runStatusLabel = { const runStatusLabel = {
needs_confirmation: "待确认", needs_confirmation: "待确认",
@@ -998,6 +1015,32 @@ function renderOneLinerRunsHtml() {
? "orange" ? "orange"
: ""; : "";
return ` return `
<div class="task-item compact" style="margin-bottom:10px;">
<h4>近期运行概况</h4>
<p>先看待确认和执行中的任务,再切回当前 run 继续推进。</p>
<div class="task-meta">
<span class="tag blue">待确认 ${escapeHtml(formatNumber(pendingRunCount))}</span>
<span class="tag green">执行中 ${escapeHtml(formatNumber(activeRunCount))}</span>
<span class="tag">已完成 ${escapeHtml(formatNumber(completedRunCount))}</span>
</div>
${runs.length > 1 ? `
<div class="chip-row" style="margin-top:10px;">
${runs.slice(0, 6).map((item) => `
<span class="chip clickable-tag ${item.id === currentRun.id ? "active" : ""}" data-action="select-oneliner-run" data-run-id="${escapeHtml(item.id)}">
${escapeHtml(brief(item.title || item.plan?.goal || "主 Agent 任务", 14))} · ${escapeHtml({
needs_confirmation: "待确认",
queued: "排队中",
running: "执行中",
blocked: "阻塞",
done: "已完成",
failed: "失败",
cancelled: "已取消"
}[item.run_status] || item.run_status || "运行中")}
</span>
`).join("")}
</div>
` : ""}
</div>
<div class="task-item compact oneliner-run-card"> <div class="task-item compact oneliner-run-card">
<div class="panel-head"> <div class="panel-head">
<div> <div>
@@ -1024,8 +1067,10 @@ function renderOneLinerRunsHtml() {
<span class="tag blue">${escapeHtml(currentRun.source_action_key || "manual-handoff")}</span> <span class="tag blue">${escapeHtml(currentRun.source_action_key || "manual-handoff")}</span>
<span class="tag">${escapeHtml(currentRun.platform_scope === "all_platforms" ? "全平台" : "单平台")}</span> <span class="tag">${escapeHtml(currentRun.platform_scope === "all_platforms" ? "全平台" : "单平台")}</span>
${currentRun.delivery_mode ? `<span class="tag">${escapeHtml(currentRun.delivery_mode)}</span>` : ""} ${currentRun.delivery_mode ? `<span class="tag">${escapeHtml(currentRun.delivery_mode)}</span>` : ""}
${previewAction?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(previewAction.action)}" ${previewLandingAttrs}>${escapeHtml(`预计落点 · ${previewAction.label || "对应页面"}`)}</span>` : ""}
</div> </div>
</div> </div>
${!hasResultPayload && previewAction?.summary ? `<div class="panel-subtitle" style="margin-top:8px;">${escapeHtml(previewAction.summary)}</div>` : ""}
${planSteps.length ? ` ${planSteps.length ? `
<div class="list" style="margin-top:10px;"> <div class="list" style="margin-top:10px;">
${planSteps.map((step, index) => ` ${planSteps.map((step, index) => `
@@ -1044,7 +1089,7 @@ function renderOneLinerRunsHtml() {
<span class="tag ${statusTone}">${escapeHtml(currentRun.status_summary || "主 Agent 正在推进中")}</span> <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>` : ""} ${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>` : ""} ${recommendedAction?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(recommendedAction.action)}" ${resultLandingAttrs}>${escapeHtml(recommendedAction.label || "回到对应页面")}</span>` : ""}
</div> </div>
${recommendedAction?.summary ? `<div class="panel-subtitle" style="margin-top:8px;">${escapeHtml(recommendedAction.summary)}</div>` : ""} ${recommendedAction?.summary ? `<div class="panel-subtitle" style="margin-top:8px;">${escapeHtml(recommendedAction.summary)}</div>` : ""}
${hasResultPayload ? ` ${hasResultPayload ? `
@@ -1064,15 +1109,6 @@ function renderOneLinerRunsHtml() {
</div> </div>
` : ""} ` : ""}
</div> </div>
${runs.length > 1 ? `
<div class="chip-row">
${runs.slice(0, 6).map((item) => `
<span class="chip clickable-tag ${item.id === currentRun.id ? "active" : ""}" data-action="select-oneliner-run" data-run-id="${escapeHtml(item.id)}">
${escapeHtml(brief(item.title || item.plan?.goal || "主 Agent 任务", 14))}
</span>
`).join("")}
</div>
` : ""}
`; `;
} }
@@ -1732,6 +1768,18 @@ function renderOneLinerExecutionPayloadHtml(payload) {
return `<div class="task-item compact"><h4>没有返回执行结果</h4><p>当前执行器没有附带额外数据。</p></div>`; return `<div class="task-item compact"><h4>没有返回执行结果</h4><p>当前执行器没有附带额外数据。</p></div>`;
} }
if (payload.result_kind === "main_agent_plan") { if (payload.result_kind === "main_agent_plan") {
const landingRunId = String(payload.run_id || "").trim();
const landingScreen = String(payload.recommended_action?.screen || "").trim();
const landingTitle = String(payload.goal || "主 Agent 执行建议").trim();
const landingSummary = String(
payload.recommended_action?.summary || payload.execution_summary || payload.summary_text || ""
).trim();
const landingAttrs = buildMainAgentLandingAttrs({
runId: landingRunId,
screen: landingScreen,
title: landingTitle,
summary: landingSummary
});
return ` return `
<div class="task-item compact"> <div class="task-item compact">
<h4>${escapeHtml(payload.goal || "主 Agent 执行建议")}</h4> <h4>${escapeHtml(payload.goal || "主 Agent 执行建议")}</h4>
@@ -1740,7 +1788,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
${payload.platform ? `<span class="tag blue">${escapeHtml(platformLabel(payload.platform))}</span>` : ""} ${payload.platform ? `<span class="tag blue">${escapeHtml(platformLabel(payload.platform))}</span>` : ""}
<span class="tag">${escapeHtml(payload.platform_scope === "all_platforms" ? "全平台" : "单平台")}</span> <span class="tag">${escapeHtml(payload.platform_scope === "all_platforms" ? "全平台" : "单平台")}</span>
<span class="tag green">已收口</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>` : ""} ${payload.recommended_action?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(payload.recommended_action.action)}" data-main-agent-run-id="${escapeHtml(landingRunId)}" data-main-agent-screen="${escapeHtml(landingScreen)}" data-main-agent-title="${escapeHtml(landingTitle)}" data-main-agent-summary="${escapeHtml(landingSummary)}" ${landingAttrs}>${escapeHtml(payload.recommended_action.label || "回到对应页面")}</span>` : ""}
</div> </div>
</div> </div>
${payload.recommended_action?.summary ? ` ${payload.recommended_action?.summary ? `
@@ -4212,6 +4260,70 @@ function buildMainAgentHandoffAttrs({
return attrs.join(" "); return attrs.join(" ");
} }
function buildMainAgentLandingAttrs({
runId = "",
screen = "",
title = "",
summary = ""
} = {}) {
const attrs = [];
if (runId) attrs.push(`data-main-agent-run-id="${escapeHtml(runId)}"`);
if (screen) attrs.push(`data-main-agent-screen="${escapeHtml(screen)}"`);
if (title) attrs.push(`data-main-agent-title="${escapeHtml(title)}"`);
if (summary) attrs.push(`data-main-agent-summary="${escapeHtml(summary)}"`);
return attrs.join(" ");
}
function resolveMainAgentLandingScreen(target) {
const value = String(target || "").trim();
if (!value) return "";
if (!value.startsWith("goto-")) return value;
const routeMap = {
"goto-discovery": "discovery",
"goto-intake": "intake",
"goto-automation": "automation",
"goto-playbook": "playbook",
"goto-tracking": "tracking",
"goto-production": "production",
"goto-strategy": "strategy",
"goto-review": "review"
};
return routeMap[value] || value.replace(/^goto-/, "");
}
function captureMainAgentLandingContext(action, targetScreen) {
const runId = String(action?.dataset?.mainAgentRunId || action?.dataset?.runId || "").trim();
const screen = resolveMainAgentLandingScreen(action?.dataset?.mainAgentScreen || targetScreen || "");
const title = String(action?.dataset?.mainAgentTitle || "").trim();
const summary = String(action?.dataset?.mainAgentSummary || "").trim();
if (!screen || (!runId && !title && !summary)) {
return;
}
appState.mainAgentLanding = {
screen,
runId,
title,
summary,
createdAt: new Date().toISOString()
};
}
function renderMainAgentLandingNotice(screenKey) {
const landing = appState.mainAgentLanding;
if (!landing || landing.screen !== screenKey) return "";
return `
<div class="task-item compact" style="margin-bottom:18px; border-color:rgba(59, 130, 246, 0.24); background:linear-gradient(180deg, rgba(239, 246, 255, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);">
<h4>你正在处理主 Agent 的结果</h4>
<p>${escapeHtml(landing.summary || landing.title || "这是主 Agent 刚刚给出的下一步落点。")}</p>
<div class="task-meta">
${landing.title ? `<span class="tag blue">${escapeHtml(landing.title)}</span>` : ""}
${landing.runId ? `<span class="tag clickable-tag" data-action="open-oneliner-run-result" data-run-id="${escapeHtml(landing.runId)}">查看结果</span>` : ""}
<span class="tag clickable-tag" data-action="dismiss-main-agent-landing">收起提示</span>
</div>
</div>
`;
}
function renderEmptyState(title, description) { function renderEmptyState(title, description) {
return `<div class="panel pad"><div class="empty-state"><strong>${escapeHtml(title)}</strong><p>${escapeHtml(description)}</p></div></div>`; return `<div class="panel pad"><div class="empty-state"><strong>${escapeHtml(title)}</strong><p>${escapeHtml(description)}</p></div></div>`;
} }
@@ -4641,11 +4753,31 @@ function renderProjectsScreen() {
} }
const projects = safeArray(appState.dashboard.projects); const projects = safeArray(appState.dashboard.projects);
const selectedProject = getSelectedProject(); const selectedProject = getSelectedProject();
const intakeHandoffAttrs = buildMainAgentHandoffAttrs({
sourceScreen: "intake",
sourceActionKey: "project-intake-handoff",
intentKey: "project_intake",
title: selectedProject ? `继续推进项目 ${selectedProject.name}` : "继续梳理项目工作区",
goal: selectedProject
? `围绕项目 ${selectedProject.name} 生成下一步推进计划`
: "根据当前项目列表和导入情况,生成下一步项目推进计划",
summary: selectedProject
? "主 Agent 会结合当前项目状态、账号和任务规模,整理下一步动作。"
: "主 Agent 会先看项目列表和内容导入情况,再整理下一步动作。",
platform: getPreferredPlatform(),
platformScope: "single_platform",
planSteps: [
"读取当前项目、账号和任务状态",
"检查是否需要补导入或切换项目",
"生成下一步项目推进计划"
]
});
return screenShell( return screenShell(
"我的项目", "我的项目",
"先建项目,再决定是否绑定自己的账号。", "先建项目,再决定是否绑定自己的账号。",
`${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")}`, `${button("新建项目", "create-project", "primary")} ${button("导入作品", "open-import-video-link")} ${button("导入文本", "open-import-text")} ${button("上传视频", "open-upload-video")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: intakeHandoffAttrs })}`,
` `
${renderMainAgentLandingNotice("intake")}
<div class="hero-card"> <div class="hero-card">
<h3>当前项目</h3> <h3>当前项目</h3>
<p>${escapeHtml(selectedProject?.name || "还没有项目")} · ${escapeHtml(selectedProject?.description || "创建后即可承接对标、Agent 和生产任务。")}</p> <p>${escapeHtml(selectedProject?.name || "还没有项目")} · ${escapeHtml(selectedProject?.description || "创建后即可承接对标、Agent 和生产任务。")}</p>
@@ -4869,6 +5001,25 @@ function renderDiscoveryScreen() {
const selectedProject = getSelectedProject(); const selectedProject = getSelectedProject();
const importedSources = getCurrentProjectSourcesForAccount(selected, selectedProject?.id || ""); const importedSources = getCurrentProjectSourcesForAccount(selected, selectedProject?.id || "");
const tracked = selected?.id ? isTrackedAccount(selected.id) : false; const tracked = selected?.id ? isTrackedAccount(selected.id) : false;
const discoveryHandoffAttrs = buildMainAgentHandoffAttrs({
sourceScreen: "discovery",
sourceActionKey: "discovery-handoff",
intentKey: "benchmark_discovery",
title: selected ? `继续处理对标 ${getAccountName(selected)}` : "继续推进对标工作",
goal: selected
? `围绕 ${getAccountName(selected)} 输出一版下一步对标计划`
: `根据当前${currentPlatformLabel}账号列表生成下一步对标推进计划`,
summary: selected
? "结合当前选中的对标账号、分析报告和相似关系,生成下一步动作。"
: "结合当前账号池和已导入内容,整理一版可执行的对标计划。",
platform: effectivePlatform || currentPlatform,
platformScope: "single_platform",
planSteps: [
"读取当前对标账号与已导入内容",
"检查分析报告与相似关系",
"生成下一步对标推进计划"
]
});
const detailTabs = [ const detailTabs = [
{ value: "overview", label: "账号概览" }, { value: "overview", label: "账号概览" },
{ value: "snapshots", label: "快照 / 字段 / 报告" }, { value: "snapshots", label: "快照 / 字段 / 报告" },
@@ -4915,8 +5066,9 @@ function renderDiscoveryScreen() {
isWorkbenchPlatform(currentPlatform) isWorkbenchPlatform(currentPlatform)
? `这里已经接入真实${currentPlatformLabel}账号列表和单账号详情` ? `这里已经接入真实${currentPlatformLabel}账号列表和单账号详情`
: `${workbenchReason}当前仍可导入内容源绑定 Agent 和沉淀复盘`, : `${workbenchReason}当前仍可导入内容源绑定 Agent 和沉淀复盘`,
`${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button(tracked ? "已在跟踪" : "加入跟踪", "open-track-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("账号分析", "analyze-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("高分分析", "analyze-top-videos", "secondary", { disabledReason: workbenchReason || "" })} ${button("查相似", "open-similar-search", "secondary", { disabledReason: workbenchReason || "" })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}`, `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button(tracked ? "已在跟踪" : "加入跟踪", "open-track-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("账号分析", "analyze-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("高分分析", "analyze-top-videos", "secondary", { disabledReason: workbenchReason || "" })} ${button("查相似", "open-similar-search", "secondary", { disabledReason: workbenchReason || "" })} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}`,
` `
${renderMainAgentLandingNotice("discovery")}
<div class="panel"> <div class="panel">
<div class="toolbar"> <div class="toolbar">
<div class="toolbar-stack"> <div class="toolbar-stack">
@@ -5034,11 +5186,27 @@ function renderTrackingScreen() {
const digestItems = getTrackingDigestItems(12, { platform: currentPlatform }); const digestItems = getTrackingDigestItems(12, { platform: currentPlatform });
const platformCursor = getTrackingCursorForPlatform(currentPlatform) || appState.lastSeenAt; const platformCursor = getTrackingCursorForPlatform(currentPlatform) || appState.lastSeenAt;
const cursorLabel = platformCursor ? formatDateTime(platformCursor) : "尚未记录"; const cursorLabel = platformCursor ? formatDateTime(platformCursor) : "尚未记录";
const trackingHandoffAttrs = buildMainAgentHandoffAttrs({
sourceScreen: "tracking",
sourceActionKey: "tracking-handoff",
intentKey: "tracking_digest",
title: `继续处理${getPlatformShortLabel(currentPlatform)}跟踪日报`,
goal: `基于当前${getPlatformShortLabel(currentPlatform)}跟踪账号和日报生成下一步跟进计划`,
summary: "主 Agent 会结合已跟踪账号、日报窗口和更新摘要,给出下一步动作。",
platform: currentPlatform,
platformScope: "single_platform",
planSteps: [
"读取当前平台跟踪账号和最新日报",
"识别值得继续跟进的账号或内容",
"生成一版跟踪推进计划"
]
});
return screenShell( return screenShell(
"跟踪账号", "跟踪账号",
`这里已经接上真实${getPlatformShortLabel(currentPlatform)}跟踪对象和按上次打开后的更新日报`, `这里已经接上真实${getPlatformShortLabel(currentPlatform)}跟踪对象和按上次打开后的更新日报`,
`${button("同步全部", "refresh-tracking")} ${button("标记已读", "mark-tracking-read")} ${button("跳到找对标", "goto-discovery", "primary")}`, `${button("同步全部", "refresh-tracking")} ${button("标记已读", "mark-tracking-read")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: trackingHandoffAttrs })} ${button("跳到找对标", "goto-discovery", "primary")}`,
` `
${renderMainAgentLandingNotice("tracking")}
<div class="hero-card"> <div class="hero-card">
<h3>日报逻辑</h3> <h3>日报逻辑</h3>
<p>按上次打开后汇总上次打开距今 ${escapeHtml(daysSince(platformCursor))} 本次优先展示有更新且值得借鉴的内容</p> <p>按上次打开后汇总上次打开距今 ${escapeHtml(daysSince(platformCursor))} 本次优先展示有更新且值得借鉴的内容</p>
@@ -5127,6 +5295,7 @@ function renderAutomationScreen() {
"自动同步、日报生成和失败补跑先统一看这里。", "自动同步、日报生成和失败补跑先统一看这里。",
`${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: automationHandoffAttrs })} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`, `${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: automationHandoffAttrs })} ${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去生产", "goto-production", "primary")}`,
` `
${renderMainAgentLandingNotice("automation")}
<div class="hero-card"> <div class="hero-card">
<h3>自动流程</h3> <h3>自动流程</h3>
<p>当前按真实任务量和依赖健康状态给出看板自动流程受阻时会直接在这里拦住动作</p> <p>当前按真实任务量和依赖健康状态给出看板自动流程受阻时会直接在这里拦住动作</p>
@@ -5289,6 +5458,7 @@ function renderPlaybookScreen() {
"这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。", "这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。",
`${button("配置 OneLiner", "open-oneliner-profile")} ${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("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "open-create-assistant")} ${button("去生产", "goto-production", "primary")}`,
` `
${renderMainAgentLandingNotice("playbook")}
<div class="hero-card"> <div class="hero-card">
<h3>Agent 概览</h3> <h3>Agent 概览</h3>
<p>先定项目平台和主模型再导入内容让 Agent 学习</p> <p>先定项目平台和主模型再导入内容让 Agent 学习</p>
@@ -5490,6 +5660,21 @@ function renderProductionScreen() {
const recoverableCount = failedJobs.filter((item) => item.recovery.recoverable).length; const recoverableCount = failedJobs.filter((item) => item.recovery.recoverable).length;
const recentDocs = appState.documents.slice(0, 3); const recentDocs = appState.documents.slice(0, 3);
const works = getProductionWorks(6); const works = getProductionWorks(6);
const productionHandoffAttrs = buildMainAgentHandoffAttrs({
sourceScreen: "production",
sourceActionKey: "production-handoff",
intentKey: "production_coordination",
title: "继续推进生产中心",
goal: "基于当前生产队列、失败任务和产物,生成下一步生产推进计划",
summary: "主 Agent 会结合生产队列、失败恢复和产物状态,给出下一步动作。",
platform: getPreferredPlatform(),
platformScope: "single_platform",
planSteps: [
"读取当前生产队列和失败任务",
"识别最该优先推进或恢复的项",
"生成一版生产推进计划"
]
});
const tabs = [ const tabs = [
{ value: "queue", label: "生产队列" }, { value: "queue", label: "生产队列" },
{ value: "recovery", label: "失败恢复" }, { value: "recovery", label: "失败恢复" },
@@ -5500,8 +5685,9 @@ function renderProductionScreen() {
return screenShell( return screenShell(
"生产中心", "生产中心",
"这里已经接上真实任务和知识库文档,后续再继续补任务创建动作。", "这里已经接上真实任务和知识库文档,后续再继续补任务创建动作。",
`${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`, `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`,
` `
${renderMainAgentLandingNotice("production")}
<div class="panel pad"> <div class="panel pad">
<div class="panel-head"><div><h3>生产队列</h3><div class="panel-subtitle"></div></div></div> <div class="panel-head"><div><h3>生产队列</h3><div class="panel-subtitle"></div></div></div>
<div class="layout-grid grid-4" style="margin-top:16px;"> <div class="layout-grid grid-4" style="margin-top:16px;">
@@ -5639,11 +5825,27 @@ function renderReviewScreen() {
const project = getSelectedProject(); const project = getSelectedProject();
const completed = safeArray(appState.dashboard.recent_jobs).filter((item) => item.status === "completed").slice(0, 4); const completed = safeArray(appState.dashboard.recent_jobs).filter((item) => item.status === "completed").slice(0, 4);
const reviews = getProjectReviews(project?.id || "").slice(0, 8); const reviews = getProjectReviews(project?.id || "").slice(0, 8);
const reviewHandoffAttrs = buildMainAgentHandoffAttrs({
sourceScreen: "review",
sourceActionKey: "review-handoff",
intentKey: "review_followup",
title: project ? `继续沉淀项目复盘 · ${project.name}` : "继续沉淀复盘",
goal: "基于最近完成任务和已保存复盘,生成下一步复盘与发布计划",
summary: "主 Agent 会结合最近完成任务和现有复盘,整理下一步复盘动作。",
platform: getPreferredPlatform(),
platformScope: "single_platform",
planSteps: [
"读取最近完成任务和现有复盘",
"识别还缺的复盘或发布动作",
"生成下一步复盘推进计划"
]
});
return screenShell( return screenShell(
"发布与复盘", "发布与复盘",
"先看已保存复盘,再把完成任务转成结构化复盘。", "先看已保存复盘,再把完成任务转成结构化复盘。",
`${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("去生产", "goto-production", "primary")}`, `${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`,
` `
${renderMainAgentLandingNotice("review")}
<div class="layout-grid grid-main"> <div class="layout-grid grid-main">
<div class="side-stack"> <div class="side-stack">
<div class="panel pad"> <div class="panel pad">
@@ -5723,6 +5925,7 @@ function renderStrategyScreen() {
"把你和主 Agent 的对话沉淀成可查看、可回滚、可追溯的个人策略层。", "把你和主 Agent 的对话沉淀成可查看、可回滚、可追溯的个人策略层。",
`${button("编辑全局策略", "open-user-global-policy")} ${button("编辑当前平台策略", "open-user-platform-policy", "primary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: strategyHandoffAttrs })}`, `${button("编辑全局策略", "open-user-global-policy")} ${button("编辑当前平台策略", "open-user-platform-policy", "primary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: strategyHandoffAttrs })}`,
` `
${renderMainAgentLandingNotice("strategy")}
<div class="hero-card"> <div class="hero-card">
<h3>当前策略工作区</h3> <h3>当前策略工作区</h3>
<p>${escapeHtml(project?.name || "当前项目")} · ${escapeHtml(platformLabel(platform))}。这里展示系统默认、你的个性化策略和管理员覆盖是如何叠加生效的。</p> <p>${escapeHtml(project?.name || "当前项目")} · ${escapeHtml(platformLabel(platform))}。这里展示系统默认、你的个性化策略和管理员覆盖是如何叠加生效的。</p>
@@ -6187,6 +6390,12 @@ function renderLastActionCard() {
const payload = appState.lastAction.payload || {}; const payload = appState.lastAction.payload || {};
const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null; const recommendedAction = payload?.result?.recommended_action || payload?.recommended_action || null;
const runId = payload?.id || payload?.run_id || ""; const runId = payload?.id || payload?.run_id || "";
const landingAttrs = buildMainAgentLandingAttrs({
runId,
screen: recommendedAction?.screen || "",
title: appState.lastAction.title || "",
summary: recommendedAction?.summary || appState.lastAction.summary || ""
});
return ` return `
<div class="panel pad"> <div class="panel pad">
<div class="panel-head"> <div class="panel-head">
@@ -6202,7 +6411,7 @@ function renderLastActionCard() {
${(runId || recommendedAction?.action) ? ` ${(runId || recommendedAction?.action) ? `
<div class="task-meta" style="margin-top:10px;"> <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>` : ""} ${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>` : ""} ${recommendedAction?.action ? `<span class="tag clickable-tag" data-action="${escapeHtml(recommendedAction.action)}" ${landingAttrs}>${escapeHtml(recommendedAction.label || "回到对应页面")}</span>` : ""}
</div> </div>
` : ""} ` : ""}
</div> </div>
@@ -9349,19 +9558,28 @@ document.addEventListener("click", async (event) => {
await logoutSession(); await logoutSession();
return; return;
} }
if (name === "dismiss-main-agent-landing") {
appState.mainAgentLanding = null;
renderAll();
return;
}
if (name === "goto-discovery") { if (name === "goto-discovery") {
captureMainAgentLandingContext(action, "goto-discovery");
setScreen("discovery"); setScreen("discovery");
return; return;
} }
if (name === "goto-intake") { if (name === "goto-intake") {
captureMainAgentLandingContext(action, "goto-intake");
setScreen("intake"); setScreen("intake");
return; return;
} }
if (name === "goto-automation") { if (name === "goto-automation") {
captureMainAgentLandingContext(action, "goto-automation");
setScreen("automation"); setScreen("automation");
return; return;
} }
if (name === "goto-playbook") { if (name === "goto-playbook") {
captureMainAgentLandingContext(action, "goto-playbook");
setScreen("playbook"); setScreen("playbook");
return; return;
} }
@@ -9370,18 +9588,22 @@ document.addEventListener("click", async (event) => {
return; return;
} }
if (name === "goto-tracking") { if (name === "goto-tracking") {
captureMainAgentLandingContext(action, "goto-tracking");
setScreen("tracking"); setScreen("tracking");
return; return;
} }
if (name === "goto-production") { if (name === "goto-production") {
captureMainAgentLandingContext(action, "goto-production");
setScreen("production"); setScreen("production");
return; return;
} }
if (name === "goto-strategy") { if (name === "goto-strategy") {
captureMainAgentLandingContext(action, "goto-strategy");
setScreen("strategy"); setScreen("strategy");
return; return;
} }
if (name === "goto-review") { if (name === "goto-review") {
captureMainAgentLandingContext(action, "goto-review");
setScreen("review"); setScreen("review");
return; return;
} }

View File

@@ -134,6 +134,7 @@ test("oneliner panel includes a dedicated runtime header for agent runs", () =>
assert.match(runtime, /confirm-oneliner-run/); assert.match(runtime, /confirm-oneliner-run/);
assert.match(runtime, /cancel-oneliner-run/); assert.match(runtime, /cancel-oneliner-run/);
assert.match(runtime, /当前计划/); assert.match(runtime, /当前计划/);
assert.match(runtime, /recommended_preview_action/);
assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/); assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/);
assert.match(runtime, /open-oneliner-run-result/); assert.match(runtime, /open-oneliner-run-result/);
assert.match(runtime, /recommended_action/); assert.match(runtime, /recommended_action/);
@@ -239,3 +240,53 @@ test("main agent result rendering offers a direct route back into the recommende
assert.match(lastAction, /open-oneliner-run-result/); assert.match(lastAction, /open-oneliner-run-result/);
assert.match(lastAction, /recommended_action/); assert.match(lastAction, /recommended_action/);
}); });
test("main agent route actions keep landing context and destination screens render a notice", () => {
const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)");
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
assert.match(execution, /data-main-agent-run-id/);
assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-production"/);
assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-strategy"/);
assert.match(actions, /name === "dismiss-main-agent-landing"/);
assert.match(strategy, /renderMainAgentLandingNotice\("strategy"\)/);
assert.match(production, /renderMainAgentLandingNotice\("production"\)/);
});
test("key workbench screens expose contextual handoff-to-main-agent actions", () => {
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
assert.match(discovery, /buildMainAgentHandoffAttrs\(\{/);
assert.match(discovery, /button\("交给主 Agent", "handoff-to-main-agent"/);
assert.match(discovery, /sourceScreen: "discovery"/);
assert.match(tracking, /buildMainAgentHandoffAttrs\(\{/);
assert.match(tracking, /button\("交给主 Agent", "handoff-to-main-agent"/);
assert.match(tracking, /sourceScreen: "tracking"/);
assert.match(projects, /buildMainAgentHandoffAttrs\(\{/);
assert.match(projects, /button\("交给主 Agent", "handoff-to-main-agent"/);
assert.match(projects, /sourceScreen: "intake"/);
assert.match(production, /buildMainAgentHandoffAttrs\(\{/);
assert.match(production, /button\("交给主 Agent", "handoff-to-main-agent"/);
assert.match(production, /sourceScreen: "production"/);
assert.match(review, /buildMainAgentHandoffAttrs\(\{/);
assert.match(review, /button\("交给主 Agent", "handoff-to-main-agent"/);
assert.match(review, /sourceScreen: "review"/);
});
test("oneliner runtime shows grouped run health summary above the current run card", () => {
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
assert.match(runtime, /近期运行概况/);
assert.match(runtime, /待确认/);
assert.match(runtime, /执行中/);
assert.match(runtime, /已完成/);
assert.match(runtime, /safeArray\(runs\)\.filter\(\(item\) => item\.run_status === "needs_confirmation"\)\.length/);
});