From 01ce085f6ac6758a83b6258ffdc15fd7fecf3944 Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 4 Apr 2026 04:35:22 +0800 Subject: [PATCH] feat: continue platform agent executions from recent runs --- CHANGELOG.md | 7 ++ web/storyforge-web-v4/assets/app.js | 95 ++++++++++++++----- .../tests/workbench-pages.test.mjs | 6 ++ 3 files changed, 84 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 86657a0..573c76a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -144,3 +144,10 @@ - `GET /v2/platform-agents` 能返回 `recent_execution` - 最近执行会带上 run id、intent 和 `oneliner_profile_version_no` - 前端工作台测试新增平台 Agent 最近执行渲染断言,锁住总览卡和详情弹层里的“最近执行”展示。 + +### 平台 Agent 最近执行继续处理 + +- 平台 Agent 总览卡和详情弹层里的“最近执行”现在都带上了直接动作,不再只是只读摘要。 +- 新增“查看执行结果”,会直接打开对应主 Agent run 的结果卡。 +- 新增“回到主 Agent 查看”,会切到对应 run 的上下文并打开主 Agent 悬浮窗口,方便顺着同一轮执行继续处理。 +- 前端回归也补上了这两个动作入口和事件处理器,避免后续又退回成只能展示、不能继续操作。 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 55e3ef8..228c7cf 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -2268,31 +2268,59 @@ async function executeOneLinerAction(executorKey, options = {}) { } function openCurrentOneLinerRunResultAction(runId = "") { - const currentRun = safeArray(appState.onelinerRuns).find((item) => item.id === runId) || getCurrentOneLinerRun(); - if (!currentRun?.id) { - rememberAction("还没有可查看的结果", "当前主 Agent 任务还没有返回可展示的执行结果。", "orange"); - renderAll(); - return; - } - if (!currentRun.result || !Object.keys(currentRun.result || {}).length) { - rememberAction("结果还在生成中", currentRun.status_summary || "当前主 Agent 任务还没有返回执行结果。", "orange", currentRun); - renderAll(); - return; - } - appState.selectedOnelinerRunId = currentRun.id; - appState.onelinerRunFilter = currentRun.run_status === "done" ? "done" : appState.onelinerRunFilter; - openActionModal({ - title: currentRun.title || currentRun.plan?.goal || "主 Agent 执行结果", - description: currentRun.result?.execution_summary || currentRun.status_summary || "这是当前主 Agent 任务的执行结果。", - hideSubmit: true, - fields: [ - { - type: "html", - label: "执行结果", - html: `
${renderOneLinerExecutionPayloadHtml(currentRun.result)}
` + openOneLinerRunContextAction(runId) + .then((currentRun) => { + if (!currentRun?.id) { + rememberAction("还没有可查看的结果", "当前主 Agent 任务还没有返回可展示的执行结果。", "orange"); + renderAll(); + return; } - ] - }); + if (!currentRun.result || !Object.keys(currentRun.result || {}).length) { + rememberAction("结果还在生成中", currentRun.status_summary || "当前主 Agent 任务还没有返回执行结果。", "orange", currentRun); + renderAll(); + return; + } + appState.selectedOnelinerRunId = currentRun.id; + appState.onelinerRunFilter = currentRun.run_status === "done" ? "done" : appState.onelinerRunFilter; + openActionModal({ + title: currentRun.title || currentRun.plan?.goal || "主 Agent 执行结果", + description: currentRun.result?.execution_summary || currentRun.status_summary || "这是当前主 Agent 任务的执行结果。", + hideSubmit: true, + fields: [ + { + type: "html", + label: "执行结果", + html: `
${renderOneLinerExecutionPayloadHtml(currentRun.result)}
` + } + ] + }); + }) + .catch(() => { + rememberAction("还没有可查看的结果", "当前主 Agent 任务还没有返回可展示的执行结果。", "orange"); + renderAll(); + }); +} + +async function openOneLinerRunContextAction(runId = "") { + const targetRunId = runId || appState.selectedOnelinerRunId || ""; + if (!targetRunId) { + rememberAction("还没有可查看的运行", "当前还没有主 Agent 运行可继续查看。", "orange"); + renderAll(); + return null; + } + const currentProjectId = appState.selectedProjectId || getOneLinerProjectId(); + if (!safeArray(appState.onelinerRuns).some((item) => item.id === targetRunId) && currentProjectId) { + await loadAgentControlSurfaces(currentProjectId); + } + appState.selectedOnelinerRunId = targetRunId; + const hydrated = await hydrateSelectedOneLinerRun(); + const currentRun = hydrated || safeArray(appState.onelinerRuns).find((item) => item.id === targetRunId) || null; + if (currentRun?.run_status === "done") { + appState.onelinerRunFilter = "done"; + } + openOneLinerPanel(); + renderAll(); + return currentRun; } function openConfirmOneLinerRunAction(runId = "") { @@ -4432,6 +4460,10 @@ function renderPlatformAgentPanel() { ${item.recent_execution.oneliner_profile_version_no ? `配置 v${escapeHtml(formatNumber(item.recent_execution.oneliner_profile_version_no))}` : ""} ${item.recent_execution.source_screen ? `${escapeHtml(screenLabel(item.recent_execution.source_screen) || item.recent_execution.source_screen)}` : ""} +
+ 查看执行结果 + 回到主 Agent 查看 +
` : ""}
@@ -9364,6 +9396,10 @@ async function openPlatformAgentDetailAction(platform) { ${profile.recent_execution.oneliner_profile_version_no ? `配置 v${escapeHtml(formatNumber(profile.recent_execution.oneliner_profile_version_no))}` : ""} ${profile.recent_execution.source_screen ? `${escapeHtml(screenLabel(profile.recent_execution.source_screen) || profile.recent_execution.source_screen)}` : ""}
+
+ 查看执行结果 + 回到主 Agent 查看 +
` : ""}
@@ -11068,6 +11104,17 @@ document.addEventListener("click", async (event) => { openCurrentOneLinerRunResultAction(action.dataset.runId || ""); return; } + if (name === "open-oneliner-run-context") { + try { + setBusy(true, "正在切到主 Agent 运行上下文..."); + await openOneLinerRunContextAction(action.dataset.runId || ""); + } catch (error) { + presentActionFailure(error, "打开主 Agent 运行失败"); + } finally { + setBusy(false, ""); + } + return; + } if (name === "confirm-oneliner-run") { openConfirmOneLinerRunAction(action.dataset.runId || ""); return; diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index f446d51..34c46e1 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -274,12 +274,18 @@ test("governance and quota panels use real empty-state language instead of backe test("platform agent surfaces recent execution feedback from main agent runs", () => { const platformAgents = extractBetween(APP, "function renderPlatformAgentPanel()", "function renderAdminOpsPanel()"); const detail = extractBetween(APP, "async function openPlatformAgentDetailAction(platform)", "function openPlatformSkillReviewAction(platform, skillId, accepted)"); + const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); assert.match(platformAgents, /recent_execution/); assert.match(platformAgents, /最近执行/); assert.match(platformAgents, /配置 v/); + assert.match(platformAgents, /open-oneliner-run-result/); + assert.match(platformAgents, /open-oneliner-run-context/); assert.match(detail, /最近执行/); assert.match(detail, /recent_execution/); + assert.match(detail, /open-oneliner-run-result/); + assert.match(detail, /open-oneliner-run-context/); + assert.match(actions, /name === "open-oneliner-run-context"[\s\S]*openOneLinerRunContextAction/); }); test("quota and review screens foreground live next-step guidance", () => {