

























@@ -148,17 +148,107 @@ describe("resolveBlueBubblesMessageId chat-scoped short-id guard", () => {
148148).toThrow(/different chat/);
149149});
150150151-it("accepts a full uuid input unchanged regardless of chat context", () => {
152-// Non-numeric input is treated as a full GUID already; the guard does
153-// not apply. Callers supplying the full GUID have presumably resolved
154-// the chat themselves.
151+it("passes a full uuid through unchanged when not in the reply cache", () => {
152+// Cache miss falls through. Callers supplying a GUID that the cache
153+// hasn't observed get the input back so fresh-from-the-wire GUIDs
154+// (e.g. from a `find` API call) still work.
155155const resolved = resolveBlueBubblesMessageId("1E7E6B6A-0000-4C6C-BCA7-000000000001", {
156156requireKnownShortId: true,
157157chatContext: { chatGuid: "iMessage;+;anything" },
158158});
159159expect(resolved).toBe("1E7E6B6A-0000-4C6C-BCA7-000000000001");
160160});
161161162+it("passes a full uuid through unchanged when caller supplies no chat context", () => {
163+// Belt-and-braces: even when the cache knows the GUID, callers that
164+// can't supply any chat hint at all (legacy tool invocations) fall
165+// through to preserve prior behavior.
166+seedMessage({
167+accountId: "default",
168+messageId: "uuid-known",
169+chatGuid: "iMessage;+;chat240698944142298252",
170+});
171+expect(resolveBlueBubblesMessageId("uuid-known")).toBe("uuid-known");
172+expect(resolveBlueBubblesMessageId("uuid-known", { chatContext: {} })).toBe("uuid-known");
173+});
174+175+it("accepts a full uuid that points at a same-chat cached entry", () => {
176+seedMessage({
177+accountId: "default",
178+messageId: "uuid-in-group",
179+chatGuid: "iMessage;+;chat240698944142298252",
180+});
181+182+const resolved = resolveBlueBubblesMessageId("uuid-in-group", {
183+chatContext: { chatGuid: "iMessage;+;chat240698944142298252" },
184+});
185+expect(resolved).toBe("uuid-in-group");
186+});
187+188+it("REJECTS a full uuid that points at a different chat in the cache", () => {
189+// Candidate-1 regression: the previous implementation only ran the
190+// cross-chat guard on numeric short ids. After the short-id guard
191+// landed, agents that retried with a full GUID (because the short id
192+// got rejected) silently bypassed the check. Group GUIDs reused in
193+// DM tool calls again leaked group reactions into DMs.
194+seedMessage({
195+accountId: "default",
196+messageId: "uuid-in-group",
197+chatGuid: "iMessage;+;chat240698944142298252",
198+});
199+200+expect(() =>
201+resolveBlueBubblesMessageId("uuid-in-group", {
202+chatContext: { chatGuid: "iMessage;-;+8618621181874" },
203+}),
204+).toThrow(/different chat/);
205+});
206+207+it("uuid-path error message hints at fixing the chat target, not the id format", () => {
208+// The short-id error tells the agent to retry with the full GUID.
209+// For UUID input that's already failed, advising "use the full GUID"
210+// would be wrong — the agent already supplied one. Make the
211+// remediation hint differ so a retrying agent is steered toward
212+// fixing the chat target.
213+seedMessage({
214+accountId: "default",
215+messageId: "uuid-in-group",
216+chatGuid: "iMessage;+;chat240698944142298252",
217+});
218+219+try {
220+resolveBlueBubblesMessageId("uuid-in-group", {
221+chatContext: { chatGuid: "iMessage;-;+8618621181874" },
222+});
223+expect.fail("expected cross-chat guard to throw");
224+} catch (err) {
225+const message = err instanceof Error ? err.message : String(err);
226+expect(message).toContain("iMessage;+;chat240698944142298252");
227+expect(message).toContain("iMessage;-;+8618621181874");
228+expect(message).toContain("correct chat target");
229+expect(message).not.toContain("Retry with the full message GUID");
230+}
231+});
232+233+it("applies the chatIdentifier fallback to full uuid input as well", () => {
234+// Same handle-only-caller scenario as the short-id case: a tool
235+// invocation might only resolve the chatIdentifier (the bare handle).
236+// The guard must catch GUID reuse across mismatched chatIdentifiers
237+// even when the caller has no chatGuid hint.
238+seedMessage({
239+accountId: "default",
240+messageId: "uuid-in-group",
241+chatGuid: "iMessage;+;chat240698944142298252",
242+chatIdentifier: "chat240698944142298252",
243+});
244+245+expect(() =>
246+resolveBlueBubblesMessageId("uuid-in-group", {
247+chatContext: { chatIdentifier: "+8618621181874" },
248+}),
249+).toThrow(/different chat/);
250+});
251+162252it("reports the conflicting chats in the error message for debugability", () => {
163253const entry = seedMessage({
164254accountId: "default",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。