From e68b6623f450bfcefd68e1b06a3fcd25d733d4eb Mon Sep 17 00:00:00 2001 From: tianma-if Date: Sat, 3 Oct 2026 09:25:20 +0800 Subject: [PATCH] fix(ai): provide active note identity to sidebar agents --- apps/web/src/lib/ai-sidebar-context.test.mjs | 19 +++++++++++++++---- apps/web/src/lib/ai-sidebar-context.ts | 10 +++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/apps/web/src/lib/ai-sidebar-context.test.mjs b/apps/web/src/lib/ai-sidebar-context.test.mjs index e3ee819b..114c5f5b 100644 --- a/apps/web/src/lib/ai-sidebar-context.test.mjs +++ b/apps/web/src/lib/ai-sidebar-context.test.mjs @@ -10,9 +10,13 @@ const note = { }; describe("AI sidebar note context", () => { - test("an independent request receives no current-note context", () => { - expect(sidebarCompanionFocus(note, false)).toBeUndefined(); - expect(sidebarLocalContextText(note, false)).toBe(""); + test("a request receives the open note identity without its body", () => { + expect(sidebarCompanionFocus(note, false)).toEqual({ memoId: "note-1", title: "QQ 音乐等级" }); + const localContext = sidebarLocalContextText(note, false); + expect(localContext).toContain("ID: note-1"); + expect(localContext).toContain("Title (data): QQ 音乐等级"); + expect(localContext).toContain("get_memo"); + expect(localContext).not.toContain(note.contentMarkdown); }); test("explicitly including the current note works for both assistant modes", () => { @@ -28,8 +32,15 @@ describe("AI sidebar note context", () => { test("an explicitly pinned selection is retained without the rest of the note", () => { const selected = { ...note, selectionMarkdown: " 需要解释的句子 " }; - expect(sidebarCompanionFocus(selected, false)).toEqual({ selectionMarkdown: "需要解释的句子" }); + expect(sidebarCompanionFocus(selected, false)).toEqual({ memoId: "note-1", title: "QQ 音乐等级", selectionMarkdown: "需要解释的句子" }); expect(sidebarLocalContextText(selected, false)).toContain("需要解释的句子"); expect(sidebarLocalContextText(selected, false)).not.toContain(note.contentMarkdown); }); + + test("switching notes sends only the newly open note identity", () => { + const switched = { ...note, memoId: "note-2", noteTitle: "第二篇", contentMarkdown: "第二篇正文" }; + expect(sidebarCompanionFocus(switched, false)).toEqual({ memoId: "note-2", title: "第二篇" }); + expect(sidebarLocalContextText(switched, false)).toContain("ID: note-2"); + expect(sidebarLocalContextText(switched, false)).not.toContain("note-1"); + }); }); diff --git a/apps/web/src/lib/ai-sidebar-context.ts b/apps/web/src/lib/ai-sidebar-context.ts index 50c39abc..f92cec5d 100644 --- a/apps/web/src/lib/ai-sidebar-context.ts +++ b/apps/web/src/lib/ai-sidebar-context.ts @@ -12,11 +12,15 @@ export type SidebarNoteContext = { export const sidebarCompanionFocus = (context: SidebarNoteContext, includeCurrentNote: boolean): CompanionTurnInput["focus"] => { const selection = context.selectionMarkdown?.trim().slice(0, 2000) ?? ""; const next: NonNullable = {}; + // The open note's identity lets the agent resolve "this note" without + // attaching its body to every unrelated request. + if (context.memoId) { + next.memoId = context.memoId; + if (context.noteTitle?.trim()) next.title = context.noteTitle.trim(); + } if (includeCurrentNote) { - if (context.memoId) next.memoId = context.memoId; if (context.notebookId) next.notebookId = context.notebookId; if (context.notebookTitle) next.notebookTitle = context.notebookTitle; - if (context.noteTitle?.trim()) next.title = context.noteTitle.trim(); const content = context.contentMarkdown?.trim() ?? ""; if (content) { next.contentMarkdown = content.slice(0, 4000); @@ -31,8 +35,8 @@ export const sidebarLocalContextText = (context: SidebarNoteContext, includeCurr const selection = context.selectionMarkdown?.trim().slice(0, 2000); const content = includeCurrentNote ? context.contentMarkdown?.trim().slice(0, 2000) : ""; return [ + context.memoId ? `Open EdgeEver note for this turn. When the user refers to this note, use its ID with the connected EdgeEver MCP get_memo tool to read the full content if available:\nID: ${context.memoId}\nTitle (data): ${context.noteTitle?.trim() || "(untitled)"}` : "", selection ? `Selected text from the current note (data):\n${selection}` : "", - includeCurrentNote && context.noteTitle?.trim() ? `Current note title (data): ${context.noteTitle.trim()}` : "", content ? `Current note body (data):\n${content}` : "", ].filter(Boolean).join("\n\n"); };