feat(session): add cross-session references

This commit is contained in:
Xubin Ren
2026-08-04 12:14:51 +08:00
parent 44b7e1bf41
commit 9b25da7b92
31 changed files with 1196 additions and 110 deletions
+87 -2
View File
@@ -1395,7 +1395,7 @@ describe("ThreadComposer", () => {
const input = screen.getByLabelText("Message input");
fireEvent.change(input, { target: { value: "@", selectionStart: 1 } });
const palette = screen.getByRole("listbox", { name: "Apps" });
const palette = screen.getByRole("listbox", { name: "Mentions" });
expect(palette).toBeInTheDocument();
expect(screen.getByRole("option", { name: /@gimp/i })).toHaveAttribute(
"aria-selected",
@@ -1414,7 +1414,7 @@ describe("ThreadComposer", () => {
expect(screen.getByTestId("composer-cli-mention-blender")).toHaveTextContent("@blender");
expect(screen.queryByTestId("composer-cli-app-tray")).not.toBeInTheDocument();
expect(onSend).not.toHaveBeenCalled();
expect(screen.queryByRole("listbox", { name: "Apps" })).not.toBeInTheDocument();
expect(screen.queryByRole("listbox", { name: "Mentions" })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Send message" }));
@@ -1536,6 +1536,91 @@ describe("ThreadComposer", () => {
});
});
it("reuses the mention palette for persisted sessions", () => {
const onSend = vi.fn();
render(
<ThreadComposer
onSend={onSend}
placeholder="Type your message..."
sessions={[{
key: "websocket:pricing",
channel: "websocket",
chatId: "pricing",
createdAt: null,
updatedAt: null,
title: "收费设计",
preview: "讨论云存储",
}]}
/>,
);
const input = screen.getByLabelText("Message input");
fireEvent.change(input, {
target: { value: "参考 @收费", selectionStart: 6 },
});
expect(screen.getByRole("group", { name: "Nanobot conversations" })).toBeInTheDocument();
expect(screen.getByRole("option", { name: /@收费设计/i })).toBeInTheDocument();
fireEvent.keyDown(input, { key: "Tab" });
expect(input).toHaveValue("参考 @收费设计 ");
expect(screen.getByTestId("composer-session-mention-收费设计")).toHaveTextContent(
"@收费设计",
);
fireEvent.click(screen.getByRole("button", { name: "Send message" }));
expect(onSend).toHaveBeenCalledWith("参考 @收费设计", undefined, {
sessionMentions: [{
name: "收费设计",
session_key: "websocket:pricing",
title: "收费设计",
}],
});
});
it("disambiguates a session mention that shares a capability name", () => {
render(
<ThreadComposer
onSend={vi.fn()}
placeholder="Type your message..."
cliApps={CLI_APPS}
sessions={[
{
key: "websocket:blender-chat",
channel: "websocket",
chatId: "blender-chat",
createdAt: null,
updatedAt: null,
title: "Blender",
preview: "3D notes",
},
...Array.from({ length: 8 }, (_, index) => ({
key: `websocket:chat-${index}`,
channel: "websocket",
chatId: `chat-${index}`,
createdAt: null,
updatedAt: null,
title: `Chat ${index}`,
preview: "",
})),
]}
/>,
);
const input = screen.getByLabelText("Message input");
fireEvent.change(input, { target: { value: "@", selectionStart: 1 } });
expect(screen.getByRole("group", { name: "Nanobot conversations" })).toBeInTheDocument();
expect(screen.getByRole("group", { name: "CLI apps" })).toBeInTheDocument();
expect(screen.getByRole("option", {
name: /Blender @Blender-chat Reference/i,
})).toBeInTheDocument();
expect(screen.getByRole("option", {
name: /Blender @blender Use/i,
})).toBeInTheDocument();
});
it("opens skills only from a $ reference and prioritizes the skill name", () => {
const skillName = "arxiv-intelligence-filter";
render(