refactor: prune legacy group prefixes

This commit is contained in:
Peter Steinberger
2026-01-17 08:46:19 +00:00
parent ab49fe0e92
commit 13b931c006
44 changed files with 160 additions and 179 deletions

View File

@@ -30,7 +30,9 @@ describe("sessions", () => {
});
it("keeps group chats distinct", () => {
expect(deriveSessionKey("per-sender", { From: "12345-678@g.us" })).toBe("group:12345-678@g.us");
expect(deriveSessionKey("per-sender", { From: "12345-678@g.us" })).toBe(
"whatsapp:group:12345-678@g.us",
);
});
it("prefixes group keys with provider when available", () => {
@@ -45,7 +47,7 @@ describe("sessions", () => {
it("keeps explicit provider when provided in group key", () => {
expect(
resolveSessionKey("per-sender", { From: "group:discord:12345", ChatType: "group" }, "main"),
resolveSessionKey("per-sender", { From: "discord:group:12345", ChatType: "group" }, "main"),
).toBe("agent:main:discord:group:12345");
});
@@ -87,7 +89,7 @@ describe("sessions", () => {
it("leaves groups untouched even with main key", () => {
expect(resolveSessionKey("per-sender", { From: "12345-678@g.us" }, "main")).toBe(
"agent:main:group:12345-678@g.us",
"agent:main:whatsapp:group:12345-678@g.us",
);
});

View File

@@ -35,7 +35,7 @@ export function buildGroupDisplayName(params: {
(room && space
? `${space}${room.startsWith("#") ? "" : "#"}${room}`
: room || subject || space || "") || "";
const fallbackId = params.id?.trim() || params.key.replace(/^group:/, "");
const fallbackId = params.id?.trim() || params.key;
const rawLabel = detail || fallbackId;
let token = normalizeGroupLabel(rawLabel);
if (!token) {
@@ -52,84 +52,49 @@ export function buildGroupDisplayName(params: {
export function resolveGroupSessionKey(ctx: MsgContext): GroupKeyResolution | null {
const from = typeof ctx.From === "string" ? ctx.From.trim() : "";
if (!from) return null;
const chatType = ctx.ChatType?.trim().toLowerCase();
const isGroup =
chatType === "group" ||
from.startsWith("group:") ||
from.includes("@g.us") ||
const normalizedChatType =
chatType === "channel" ? "channel" : chatType === "group" ? "group" : undefined;
const isWhatsAppGroupId = from.toLowerCase().endsWith("@g.us");
const looksLikeGroup =
normalizedChatType === "group" ||
normalizedChatType === "channel" ||
from.includes(":group:") ||
from.includes(":channel:");
if (!isGroup) return null;
from.includes(":channel:") ||
isWhatsAppGroupId;
if (!looksLikeGroup) return null;
const providerHint = ctx.Provider?.trim().toLowerCase();
const hasGroupPrefix = from.startsWith("group:");
const raw = (hasGroupPrefix ? from.slice("group:".length) : from).trim();
let provider: string | undefined;
let kind: "group" | "channel" | undefined;
let id = "";
const parts = from.split(":").filter(Boolean);
const head = parts[0]?.trim().toLowerCase() ?? "";
const headIsSurface = head ? getGroupSurfaces().has(head) : false;
const parseKind = (value: string) => {
if (value === "channel") return "channel";
return "group";
};
const provider = headIsSurface
? head
: (providerHint ?? (isWhatsAppGroupId ? "whatsapp" : undefined));
if (!provider) return null;
const parseParts = (parts: string[]) => {
if (parts.length >= 2 && getGroupSurfaces().has(parts[0])) {
provider = parts[0];
if (parts.length >= 3) {
const kindCandidate = parts[1];
if (["group", "channel"].includes(kindCandidate)) {
kind = parseKind(kindCandidate);
id = parts.slice(2).join(":");
} else {
id = parts.slice(1).join(":");
}
} else {
id = parts[1];
}
return;
}
if (parts.length >= 2 && ["group", "channel"].includes(parts[0])) {
kind = parseKind(parts[0]);
id = parts.slice(1).join(":");
}
};
if (hasGroupPrefix) {
const legacyParts = raw.split(":").filter(Boolean);
if (legacyParts.length > 1) {
parseParts(legacyParts);
} else {
id = raw;
}
} else if (from.includes("@g.us") && !from.includes(":")) {
id = from;
} else {
parseParts(from.split(":").filter(Boolean));
if (!id) {
id = raw || from;
}
}
const resolvedProvider = provider ?? providerHint;
if (!resolvedProvider) {
const legacy = hasGroupPrefix ? `group:${raw}` : `group:${from}`;
return {
key: legacy,
id: raw || from,
chatType: "group",
};
}
const resolvedKind = kind === "channel" ? "channel" : "group";
const key = `${resolvedProvider}:${resolvedKind}:${id || raw || from}`;
const second = parts[1]?.trim().toLowerCase();
const secondIsKind = second === "group" || second === "channel";
const kind = secondIsKind
? (second as "group" | "channel")
: from.includes(":channel:") || normalizedChatType === "channel"
? "channel"
: "group";
const id = headIsSurface
? secondIsKind
? parts.slice(2).join(":")
: parts.slice(1).join(":")
: from;
const finalId = id.trim();
if (!finalId) return null;
return {
key,
channel: resolvedProvider,
id: id || raw || from,
chatType: resolvedKind === "channel" ? "channel" : "group",
key: `${provider}:${kind}:${finalId}`,
channel: provider,
id: finalId,
chatType: kind === "channel" ? "channel" : "group",
};
}

View File

@@ -31,7 +31,7 @@ export function resolveSessionKey(scope: SessionScope, ctx: MsgContext, mainKey?
agentId: DEFAULT_AGENT_ID,
mainKey: canonicalMainKey,
});
const isGroup = raw.startsWith("group:") || raw.includes(":group:") || raw.includes(":channel:");
const isGroup = raw.includes(":group:") || raw.includes(":channel:");
if (!isGroup) return canonical;
return `agent:${DEFAULT_AGENT_ID}:${raw}`;
}

View File

@@ -65,6 +65,7 @@ export type SessionEntry = {
label?: string;
displayName?: string;
channel?: string;
groupId?: string;
subject?: string;
room?: string;
space?: string;