msteams: prefer freshest personal conversation reference

This commit is contained in:
gumclaw 2026-03-25 17:08:18 -04:00 committed by Peter Steinberger
parent 2c15960ac2
commit 28eb5ece14
3 changed files with 68 additions and 6 deletions

View File

@ -123,6 +123,48 @@ describe("msteams conversation store (fs)", () => {
expect(retrieved).not.toBeNull();
expect(retrieved!.timezone).toBe("Europe/London");
});
it("prefers the freshest personal conversation when a user has multiple references", async () => {
const stateDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "openclaw-msteams-store-"));
const store = createMSTeamsConversationStoreFs({
env: { ...process.env, OPENCLAW_STATE_DIR: stateDir },
ttlMs: 60_000,
});
await store.upsert("a:old-personal", {
conversation: { id: "a:old-personal", conversationType: "personal" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "old-user", aadObjectId: "shared-aad" },
});
await store.upsert("19:group-chat", {
conversation: { id: "19:group-chat", conversationType: "groupChat" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "group-user", aadObjectId: "shared-aad" },
});
await new Promise((resolve) => setTimeout(resolve, 10));
await store.upsert("a:new-personal", {
conversation: { id: "a:new-personal", conversationType: "personal" },
channelId: "msteams",
serviceUrl: "https://service.example.com",
user: { id: "new-user", aadObjectId: "shared-aad" },
});
await expect(store.findByUserId("shared-aad")).resolves.toEqual({
conversationId: "a:new-personal",
reference: expect.objectContaining({
conversation: expect.objectContaining({
id: "a:new-personal",
conversationType: "personal",
}),
user: expect.objectContaining({ id: "new-user", aadObjectId: "shared-aad" }),
}),
});
});
});
describe("msteams conversation store (memory)", () => {

View File

@ -118,16 +118,34 @@ export function createMSTeamsConversationStoreFs(params?: {
if (!target) {
return null;
}
const matches: MSTeamsConversationStoreEntry[] = [];
for (const entry of await list()) {
const { conversationId, reference } = entry;
if (reference.user?.aadObjectId === target) {
return { conversationId, reference };
}
if (reference.user?.id === target) {
return { conversationId, reference };
if (reference.user?.aadObjectId === target || reference.user?.id === target) {
matches.push({ conversationId, reference });
}
}
return null;
if (matches.length === 0) {
return null;
}
matches.sort((a, b) => {
const aType = a.reference.conversation?.conversationType?.toLowerCase() ?? "";
const bType = b.reference.conversation?.conversationType?.toLowerCase() ?? "";
const aPersonal = aType === "personal" ? 1 : 0;
const bPersonal = bType === "personal" ? 1 : 0;
if (aPersonal !== bPersonal) {
return bPersonal - aPersonal;
}
return (
(parseTimestamp(b.reference.lastSeenAt) ?? 0) -
(parseTimestamp(a.reference.lastSeenAt) ?? 0)
);
});
return matches[0] ?? null;
};
const upsert = async (

View File

@ -7,6 +7,8 @@
/** Minimal ConversationReference shape for proactive messaging */
export type StoredConversationReference = {
/** Timestamp when this reference was last seen/updated. */
lastSeenAt?: string;
/** Activity ID from the last message */
activityId?: string;
/** User who sent the message */