From bbd7459935effda078e7ab10189e2a21f9683dc6 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Oct 2026 18:08:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(agent):=20=E6=8A=BD=E5=B1=89=E5=BC=80?= =?UTF-8?q?=E7=9D=80=E6=97=B6=E8=B7=AF=E7=94=B1=E5=8F=98=E5=8C=96,?= =?UTF-8?q?=E6=96=B0=E5=AF=B9=E8=AF=9D=E7=9A=84=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E8=B7=9F=E7=9D=80=E9=A1=B5=E9=9D=A2=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 浏览器前进后退或会话里的链接换了页面,抽屉里新对话仍挂着上一页的对象。 按路由推断的上下文随页面更新;页面按钮带进来的上下文、正在看的会话不动。 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei --- .../web/app/components/agent/agent-drawer.tsx | 2 + .../app/components/agent/agent-nav.test.ts | 44 ++++++++++++++++++- apps/web/app/components/agent/agent-store.ts | 12 +++++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/apps/web/app/components/agent/agent-drawer.tsx b/apps/web/app/components/agent/agent-drawer.tsx index ea124aac..6dee2a57 100644 --- a/apps/web/app/components/agent/agent-drawer.tsx +++ b/apps/web/app/components/agent/agent-drawer.tsx @@ -28,6 +28,7 @@ import { setAgentDrawerOpen, shortGoalTitle, showAgentRunInDrawer, + syncAgentContextToPath, takeReturnFocus, toggleAgentDrawer, useAgentDrawer, @@ -104,6 +105,7 @@ export function AgentDrawer() { const content = useRef(null); // 记下最近的非智能体页面:会话页「在侧边打开」回到这里 useEffect(() => noteAgentReturnPath(pathname, search), [pathname, search]); + useEffect(() => syncAgentContextToPath(pathname), [pathname]); const name = useContextName(context); const mock = agentMockEnabled(); // 目标前缀带名称和 ID:名称给人看,ID 给模型用 diff --git a/apps/web/app/components/agent/agent-nav.test.ts b/apps/web/app/components/agent/agent-nav.test.ts index 3cdc6db0..11e862fd 100644 --- a/apps/web/app/components/agent/agent-nav.test.ts +++ b/apps/web/app/components/agent/agent-nav.test.ts @@ -17,7 +17,19 @@ import { writeDraft, } from "./agent-nav.ts"; import { CompactPager } from "./agent-session-list.tsx"; -import { agentReturnPath, isAgentPage, noteAgentReturnPath, outletKey, toggleAction } from "./agent-store.ts"; +import { + agentReturnPath, + clearAgentContext, + isAgentPage, + noteAgentReturnPath, + openAgentDrawer, + outletKey, + setAgentDrawerOpen, + showAgentRunInDrawer, + syncAgentContextToPath, + toggleAction, + useAgentDrawer, +} from "./agent-store.ts"; import { cachedRunEvents, cacheRunEvents } from "./use-agent-stream.ts"; // 智能体页面切换的状态逻辑:整页重挂载 key、分栏与 URL、草稿、滚动策略、快捷键在智能体页面的行为。 @@ -176,3 +188,33 @@ describe("会话页「在侧边打开」回到之前的页面", () => { expect(agentReturnPath()).toBe("/console/sites"); }); }); + +describe("抽屉开着时路由变化", () => { + // useAgentDrawer 是 useSyncExternalStore,测试里直接读快照 + const snapshot = () => { + let out: ReturnType | null = null; + const Probe = () => { + out = useAgentDrawer(); + return null; + }; + renderToStaticMarkup(createElement(Probe)); + return out as unknown as ReturnType; + }; + it("新对话的路由上下文跟着页面换", () => { + openAgentDrawer({ label: "作品 aaaa", entityId: "aaaa", fromPath: true }); + syncAgentContextToPath("/console/sites/hdh"); + expect(snapshot().context).toMatchObject({ siteId: "hdh", fromPath: true }); + syncAgentContextToPath("/console/home"); + expect(snapshot().context).toBeNull(); + }); + it("页面按钮带的上下文、正在看的会话、抽屉关着时不动", () => { + openAgentDrawer({ label: "作品:沙丘", entityId: "e1" }); + syncAgentContextToPath("/console/sites/hdh"); + expect(snapshot().context).toMatchObject({ entityId: "e1" }); + clearAgentContext(); + showAgentRunInDrawer("r1"); + syncAgentContextToPath("/console/sites/hdh"); + expect(snapshot().context).toBeNull(); + setAgentDrawerOpen(false); + }); +}); diff --git a/apps/web/app/components/agent/agent-store.ts b/apps/web/app/components/agent/agent-store.ts index 7d499987..3a21c58d 100644 --- a/apps/web/app/components/agent/agent-store.ts +++ b/apps/web/app/components/agent/agent-store.ts @@ -84,6 +84,18 @@ export function newAgentConversation() { emit(); } +/** + * 抽屉开着时路由变了(浏览器前进后退、会话里的链接):还在新对话、上下文是按路由推断的,就换成新页面的对象; + * 页面按钮带进来的上下文、已经在看的会话不动。 + */ +export function syncAgentContextToPath(pathname: string) { + if (!state.open || state.runId || (state.context && !state.context.fromPath)) return; + const next = contextFromPath(pathname); + if (JSON.stringify(next) === JSON.stringify(state.context)) return; + state = { ...state, context: next }; + emit(); +} + /** 去掉上下文(输入框上的名称标签 ×) */ export function clearAgentContext() { state = { ...state, context: null };