diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97a8cea..0593494 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
- 前端新增统一的 `buildRecommendedActionAttrs(...)`,把 `job_id / review_id / platform / source_id` 这类上下文一起带进最近动作卡和执行结果卡,后续新增直接动作时不用再重复拼接跳转参数。
- 后端回归新增了 `review-draft / platform-self-check / generate-copy` 三类真实动作的推荐落点断言;前端回归则锁住了结果卡和最近动作卡必须使用统一的推荐动作属性映射。
- 这轮还顺手修掉了一个真实 bug:保存录制源时,usage 记账错误地读取了 `binding["id"]`,现在已改成兼容 `binding_id / id`,不会再因为键名差异导致录制源创建链路直接报错。
+- 当前运行卡、最近完成、主 Agent 结果卡、平台 Agent 最近执行这几处“回到业务页”入口,现在也全部切到同一套结构化属性映射,不再只带 `run_id / screen`,从这些入口继续跳转时也能保留 `job_id / review_id / source_id` 这类精确上下文。
### 主 Agent 消息卡补齐配置追溯与主动作执行上下文
diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js
index 3c6da4b..3e198e1 100644
--- a/web/storyforge-web-v4/assets/app.js
+++ b/web/storyforge-web-v4/assets/app.js
@@ -1108,7 +1108,7 @@ function renderOneLinerRunsHtml() {
title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务",
summary: previewAction?.summary || currentRun.summary || ""
});
- const resultLandingAttrs = buildMainAgentLandingAttrs({
+ const resultLandingAttrs = buildRecommendedActionAttrs(recommendedAction, {
runId: currentRun.id || "",
screen: recommendedAction?.screen || "",
title: currentRun.title || currentRun.plan?.goal || "主 Agent 任务",
@@ -1304,7 +1304,7 @@ function renderOneLinerRunsHtml() {
${recentCompletedRuns.map((item) => {
const doneAction = item.result?.recommended_action || null;
- const doneLandingAttrs = buildMainAgentLandingAttrs({
+ const doneLandingAttrs = buildRecommendedActionAttrs(doneAction, {
runId: item.id || "",
screen: doneAction?.screen || "",
title: item.title || item.plan?.goal || "主 Agent 任务",
@@ -2019,7 +2019,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
platformAgentProfile,
latestPlatformAgentProfile.current_version || {}
);
- const landingAttrs = buildMainAgentLandingAttrs({
+ const landingAttrs = buildRecommendedActionAttrs(payload.recommended_action, {
runId: landingRunId,
screen: landingScreen,
title: landingTitle,
@@ -2036,7 +2036,7 @@ function renderOneLinerExecutionPayloadHtml(payload) {
${currentRunOnelinerConfigStale ? `主配置已更新` : ""}
${currentRunPlatformAgentConfigStale ? `平台 Agent 已更新` : ""}
已收口
- ${payload.recommended_action?.action ? `${escapeHtml(payload.recommended_action.label || "回到对应页面")}` : ""}
+ ${payload.recommended_action?.action ? `${escapeHtml(payload.recommended_action.label || "回到对应页面")}` : ""}
${configVersion.version_no ? `
@@ -4586,7 +4586,7 @@ function renderPlatformAgentPanel() {
${item.recent_execution.source_screen ? `${escapeHtml(screenLabel(item.recent_execution.source_screen) || item.recent_execution.source_screen)}` : ""}
- ${item.recent_execution.recommended_action?.action ? `${escapeHtml(item.recent_execution.recommended_action.label || "回到业务页")}` : ""}
+ ${item.recent_execution.recommended_action?.action ? `${escapeHtml(item.recent_execution.recommended_action.label || "回到业务页")}` : ""}
${item.recent_execution.oneliner_profile_version_no ? `主配置历史` : ""}
${item.recent_execution.platform_agent_profile_version_no ? `平台配置历史` : ""}
查看执行结果
@@ -9755,7 +9755,7 @@ async function openPlatformAgentDetailAction(platform) {
${profile.recent_execution.source_screen ? `${escapeHtml(screenLabel(profile.recent_execution.source_screen) || profile.recent_execution.source_screen)}` : ""}
- ${profile.recent_execution.recommended_action?.action ? `${escapeHtml(profile.recent_execution.recommended_action.label || "回到业务页")}` : ""}
+ ${profile.recent_execution.recommended_action?.action ? `${escapeHtml(profile.recent_execution.recommended_action.label || "回到业务页")}` : ""}
${profile.recent_execution.oneliner_profile_version_no ? `主配置历史` : ""}
${profile.recent_execution.platform_agent_profile_version_no ? `平台配置历史` : ""}
查看执行结果
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
index 7f92ca5..1a782af 100644
--- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs
+++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs
@@ -773,6 +773,18 @@ test("direct oneliner execution results preserve structured follow-up attrs", ()
assert.match(lastAction, /actionTag\(recommendedAction\.label \|\| "回到对应页面"/);
});
+test("main agent runtime and platform recent execution preserve structured follow-up attrs", () => {
+ const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
+ const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)");
+ const playbook = extractBetween(APP, "function renderPlatformAgentPanel()", "function renderAdminOpsPanel()");
+ const detail = extractBetween(APP, "async function openPlatformAgentDetailAction(platform)", "function openPlatformSkillReviewAction(platform, skillId, accepted)");
+ assert.match(runtime, /const resultLandingAttrs = buildRecommendedActionAttrs\(recommendedAction/);
+ assert.match(runtime, /const doneLandingAttrs = buildRecommendedActionAttrs\(doneAction/);
+ assert.match(execution, /const landingAttrs = buildRecommendedActionAttrs\(payload\.recommended_action/);
+ assert.match(playbook, /buildRecommendedActionAttrs\(item\.recent_execution\.recommended_action/);
+ assert.match(detail, /buildRecommendedActionAttrs\(profile\.recent_execution\.recommended_action/);
+});
+
test("platform agent profiles expose history, rollback, and execution version context", () => {
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
const profileEditor = extractBetween(APP, "function openPlatformAgentProfileAction(platform)", "async function openPlatformAgentProfileHistoryAction(platform, preferredVersionId = \"\")");
@@ -835,7 +847,7 @@ test("main agent route actions keep landing context and destination screens rend
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
const landing = extractBetween(APP, "function renderMainAgentLandingNotice(screenKey)", "function renderEmptyState(title, description)");
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
- assert.match(execution, /data-main-agent-run-id/);
+ assert.match(execution, /buildRecommendedActionAttrs\(payload\.recommended_action/);
assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-production"/);
assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-strategy"/);
assert.match(actions, /name === "dismiss-main-agent-landing"/);