feat(session): add cross-session references
This commit is contained in:
@@ -512,6 +512,26 @@ describe("MessageBubble", () => {
|
||||
expect(screen.getByTestId("message-mcp-mention-logo-browserbase")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders persisted session mentions inside sent user messages", () => {
|
||||
const message: UIMessage = {
|
||||
id: "u-session",
|
||||
role: "user",
|
||||
content: "Use @收费设计 as context",
|
||||
createdAt: Date.now(),
|
||||
sessionMentions: [{
|
||||
name: "收费设计",
|
||||
session_key: "websocket:pricing",
|
||||
title: "收费设计",
|
||||
}],
|
||||
};
|
||||
|
||||
render(<MessageBubble message={message} />);
|
||||
|
||||
const token = screen.getByTestId("message-session-mention-收费设计");
|
||||
expect(token).toHaveTextContent("@收费设计");
|
||||
expect(token).toHaveAttribute("title", "Session: 收费设计");
|
||||
});
|
||||
|
||||
it("copies completed assistant replies from the action row", async () => {
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(navigator, "clipboard", {
|
||||
|
||||
@@ -1619,6 +1619,36 @@ describe("NanobotClient", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("includes session mentions in outbound messages", () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
reconnect: false,
|
||||
socketFactory: (url) => new FakeSocket(url) as unknown as WebSocket,
|
||||
});
|
||||
client.connect();
|
||||
lastSocket().fakeOpen();
|
||||
|
||||
client.sendMessage("chat-current", "Use @pricing", undefined, {
|
||||
sessionMentions: [{
|
||||
name: "pricing",
|
||||
session_key: "websocket:pricing",
|
||||
title: "Pricing",
|
||||
}],
|
||||
});
|
||||
|
||||
expect(lastSocket().sent).toContain(JSON.stringify({
|
||||
type: "message",
|
||||
chat_id: "chat-current",
|
||||
content: "Use @pricing",
|
||||
session_mentions: [{
|
||||
name: "pricing",
|
||||
session_key: "websocket:pricing",
|
||||
title: "Pricing",
|
||||
}],
|
||||
webui: true,
|
||||
}));
|
||||
});
|
||||
|
||||
it("re-attaches known chats after a reconnect", async () => {
|
||||
const client = new NanobotClient({
|
||||
url: "ws://test",
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -3611,7 +3611,7 @@ describe("ThreadShell", () => {
|
||||
));
|
||||
|
||||
const input = await screen.findByLabelText("Message input");
|
||||
expect(screen.queryByRole("listbox", { name: "Apps" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("listbox", { name: "Mentions" })).not.toBeInTheDocument();
|
||||
|
||||
const payload: CliAppsPayload = {
|
||||
apps: [{
|
||||
@@ -3639,7 +3639,7 @@ describe("ThreadShell", () => {
|
||||
});
|
||||
fireEvent.change(input, { target: { value: "@", selectionStart: 1 } });
|
||||
|
||||
expect(screen.getByRole("listbox", { name: "Apps" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("listbox", { name: "Mentions" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("option", { name: /@gimp/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user