fix(agent): 抽屉开着时路由变化,新对话的上下文跟着页面换

浏览器前进后退或会话里的链接换了页面,抽屉里新对话仍挂着上一页的对象。
按路由推断的上下文随页面更新;页面按钮带进来的上下文、正在看的会话不动。

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-10-06 18:08:19 +08:00
1 parent a87c2c19c4
commit bbd7459935
3 files changed
+57 -1

No files matched your search

@@ -28,6 +28,7 @@ import {
setAgentDrawerOpen,
shortGoalTitle,
showAgentRunInDrawer,
syncAgentContextToPath,
takeReturnFocus,
toggleAgentDrawer,
useAgentDrawer,
@@ -104,6 +105,7 @@ export function AgentDrawer() {
const content = useRef<HTMLDivElement>(null);
// 记下最近的非智能体页面:会话页「在侧边打开」回到这里
useEffect(() => noteAgentReturnPath(pathname, search), [pathname, search]);
useEffect(() => syncAgentContextToPath(pathname), [pathname]);
const name = useContextName(context);
const mock = agentMockEnabled();
// 目标前缀带名称和 ID:名称给人看,ID 给模型用
@@ -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<typeof useAgentDrawer> | null = null;
const Probe = () => {
out = useAgentDrawer();
return null;
};
renderToStaticMarkup(createElement(Probe));
return out as unknown as ReturnType<typeof useAgentDrawer>;
};
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);
});
});
@@ -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 };