feat: advance main agent runs from homepage handoff

This commit is contained in:
kris
2026-03-29 18:37:57 +08:00
parent 30e37e5ce1
commit d4be3a2ce1
4 changed files with 229 additions and 9 deletions

View File

@@ -28,6 +28,36 @@
`;
}
function createHandoffMetadata(config = {}) {
const steps = safeArray(config.planSteps).map((item) => String(item || "").trim()).filter(Boolean);
return {
sourceScreen: config.sourceScreen || "dashboard",
sourceActionKey: config.sourceActionKey || "homepage-action",
intentKey: config.intentKey || "custom",
title: config.title || "",
goal: config.goal || config.title || "",
summary: config.summary || "",
platform: config.platform || "",
platformScope: config.platformScope || "single_platform",
planSteps: steps.length ? steps : ["读取当前项目上下文", "结合当前动作生成执行计划", "等待确认后执行"]
};
}
function buildHandoffAttrs(config, escapeHtml) {
const metadata = createHandoffMetadata(config);
return [
`data-source-screen="${escapeHtml(metadata.sourceScreen)}"`,
`data-source-action-key="${escapeHtml(metadata.sourceActionKey)}"`,
`data-intent-key="${escapeHtml(metadata.intentKey)}"`,
`data-title="${escapeHtml(metadata.title)}"`,
`data-goal="${escapeHtml(metadata.goal)}"`,
`data-summary="${escapeHtml(metadata.summary)}"`,
`data-platform-scope="${escapeHtml(metadata.platformScope)}"`,
`data-plan-steps="${escapeHtml(JSON.stringify(metadata.planSteps))}"`,
metadata.platform ? `data-platform="${escapeHtml(metadata.platform)}"` : ""
].filter(Boolean);
}
function createDashboardHomeModel(raw) {
const trackedAccountsCount = Number(raw?.trackedAccountsCount || 0);
const assistantCount = Number(raw?.assistantCount || 0);
@@ -44,7 +74,11 @@
badges: ["最优先", "项目入口"],
goAction: "goto-intake",
goLabel: "去项目",
agentLabel: "交给主 Agent"
agentLabel: "交给主 Agent",
sourceActionKey: "homepage-primary-action",
intentKey: "create_project",
platformScope: "all_platforms",
planSteps: ["读取当前工作区项目列表", "确认需要切换还是新建项目", "给出主流程推进建议"]
});
} else if (trackedAccountsCount > 0) {
actions.push({
@@ -53,7 +87,11 @@
badges: ["最优先", "预计 10 分钟判断", "关联:重点账号"],
goAction: "goto-discovery",
goLabel: "去找对标",
agentLabel: "交给主 Agent"
agentLabel: "交给主 Agent",
sourceActionKey: "homepage-primary-action",
intentKey: "analyze_top_videos",
platform: "douyin",
planSteps: ["读取当前项目上下文", "检查重点账号最近变化", "补充高分作品分析结论"]
});
} else if (assistantCount <= 0) {
actions.push({
@@ -62,7 +100,10 @@
badges: ["最优先", "Agent 缺失"],
goAction: "goto-playbook",
goLabel: "去创建",
agentLabel: "交给主 Agent"
agentLabel: "交给主 Agent",
sourceActionKey: "homepage-primary-action",
intentKey: "create_assistant",
planSteps: ["读取当前项目职责", "确认首个 Agent 角色", "给出创建与配置建议"]
});
} else {
actions.push({
@@ -71,7 +112,10 @@
badges: ["默认推进", "主流程"],
goAction: "goto-production",
goLabel: "去处理",
agentLabel: "交给主 Agent"
agentLabel: "交给主 Agent",
sourceActionKey: "homepage-primary-action",
intentKey: "custom",
planSteps: ["读取当前项目上下文", "检查对标和生产链路状态", "生成下一步执行计划"]
});
}
@@ -80,7 +124,11 @@
title: "确认一个待执行的生产计划",
reason: "素材和结论都在,只差最后确认。",
goAction: "goto-production",
goLabel: "去处理"
goLabel: "去处理",
agentLabel: "交给主 Agent",
sourceActionKey: "homepage-secondary-action-1",
intentKey: "custom",
planSteps: ["读取当前待执行生产任务", "确认素材和结论完整性", "给出执行确认建议"]
});
}
@@ -88,7 +136,11 @@
title: "更新重点账号的跟踪摘要",
reason: "有新动态,但不值得占据大块首页空间。",
goAction: "goto-tracking",
goLabel: "去处理"
goLabel: "去处理",
agentLabel: "交给主 Agent",
sourceActionKey: `homepage-secondary-action-${Math.max(actions.length, 1)}`,
intentKey: "track_account",
planSteps: ["读取重点账号近 7 天动态", "更新跟踪摘要", "给出是否继续跟进建议"]
});
while (actions.length < 3) {
@@ -96,7 +148,11 @@
title: "继续补高分对标并安排生产",
reason: "当前项目没有更多高优先动作时,保持主流程推进。",
goAction: "goto-production",
goLabel: "去处理"
goLabel: "去处理",
agentLabel: "交给主 Agent",
sourceActionKey: `homepage-secondary-action-${actions.length}`,
intentKey: "custom",
planSteps: ["读取当前项目上下文", "检查主流程空缺", "给出下一步执行建议"]
});
}
@@ -122,6 +178,17 @@
}
function renderSecondaryAction(item, index, escapeHtml) {
const handoffAttrs = buildHandoffAttrs({
sourceScreen: "dashboard",
sourceActionKey: item.sourceActionKey || `homepage-secondary-action-${index + 1}`,
intentKey: item.intentKey || "custom",
title: item.title,
goal: item.title,
summary: item.reason,
platform: item.platform,
platformScope: item.platformScope || "single_platform",
planSteps: item.planSteps
}, escapeHtml);
return `
<div class="dashboard-action-secondary">
<div class="dashboard-action-index">${index + 2}</div>
@@ -141,6 +208,12 @@
action: item.goAction || "goto-production",
tone: "secondary"
}, escapeHtml)}
${renderActionButton({
label: item.agentLabel || "交给主 Agent",
action: "handoff-to-main-agent",
tone: "secondary",
attrs: handoffAttrs
}, escapeHtml)}
</div>
</div>
`;
@@ -152,6 +225,17 @@
const contextLinks = safeArray(model?.contextLinks);
const primaryAction = model?.primaryAction || {};
const secondaryActions = safeArray(model?.secondaryActions);
const primaryHandoffAttrs = buildHandoffAttrs({
sourceScreen: "dashboard",
sourceActionKey: primaryAction.sourceActionKey || "homepage-primary-action",
intentKey: primaryAction.intentKey || "custom",
title: primaryAction.title,
goal: primaryAction.title,
summary: primaryAction.reason,
platform: primaryAction.platform,
platformScope: primaryAction.platformScope || "single_platform",
planSteps: primaryAction.planSteps
}, escapeHtml);
return `
<div class="dashboard-home">
@@ -214,8 +298,9 @@
}, escapeHtml)}
${renderActionButton({
label: primaryAction.agentLabel || "交给主 Agent",
action: "open-oneliner",
tone: "primary"
action: "handoff-to-main-agent",
tone: "primary",
attrs: primaryHandoffAttrs
}, escapeHtml)}
</div>
</div>

View File

@@ -57,6 +57,7 @@ test("homepage v6 puts actions before overview and uses 1-primary-2-secondary st
assert.match(html, /先补抖音重点对标的高分作品分析/);
assert.match(html, /确认一个待执行的生产计划/);
assert.match(html, /更新重点账号的跟踪摘要/);
assert.match(html, /data-action="handoff-to-main-agent"/);
});
test("homepage model builds one primary action, two secondary actions, and a rule fallback label", () => {
@@ -98,3 +99,11 @@ test("homepage overview uses tab buttons and does not render legacy repeated sec
assert.ok(!html.includes("当前项目推进详情"));
assert.ok(!html.includes("重点账号 / 对标</h3><div class=\"panel-subtitle\">右栏保留"));
});
test("homepage handoff buttons carry runtime plan metadata instead of only opening chat", () => {
const source = fs.readFileSync(path.join(ROOT, "assets/storyforge-dashboard-home.js"), "utf8");
assert.match(source, /handoff-to-main-agent/);
assert.match(source, /data-source-screen=/);
assert.match(source, /data-source-action-key=/);
assert.match(source, /data-plan-steps=/);
});