refactor: prune legacy group targets
This commit is contained in:
@@ -40,43 +40,43 @@ describe("history helpers", () => {
|
|||||||
|
|
||||||
appendHistoryEntry({
|
appendHistoryEntry({
|
||||||
historyMap,
|
historyMap,
|
||||||
historyKey: "room",
|
historyKey: "group",
|
||||||
limit: 2,
|
limit: 2,
|
||||||
entry: { sender: "A", body: "one" },
|
entry: { sender: "A", body: "one" },
|
||||||
});
|
});
|
||||||
appendHistoryEntry({
|
appendHistoryEntry({
|
||||||
historyMap,
|
historyMap,
|
||||||
historyKey: "room",
|
historyKey: "group",
|
||||||
limit: 2,
|
limit: 2,
|
||||||
entry: { sender: "B", body: "two" },
|
entry: { sender: "B", body: "two" },
|
||||||
});
|
});
|
||||||
appendHistoryEntry({
|
appendHistoryEntry({
|
||||||
historyMap,
|
historyMap,
|
||||||
historyKey: "room",
|
historyKey: "group",
|
||||||
limit: 2,
|
limit: 2,
|
||||||
entry: { sender: "C", body: "three" },
|
entry: { sender: "C", body: "three" },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(historyMap.get("room")?.map((entry) => entry.body)).toEqual(["two", "three"]);
|
expect(historyMap.get("group")?.map((entry) => entry.body)).toEqual(["two", "three"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("builds context from map and appends entry", () => {
|
it("builds context from map and appends entry", () => {
|
||||||
const historyMap = new Map<string, { sender: string; body: string }[]>();
|
const historyMap = new Map<string, { sender: string; body: string }[]>();
|
||||||
historyMap.set("room", [
|
historyMap.set("group", [
|
||||||
{ sender: "A", body: "one" },
|
{ sender: "A", body: "one" },
|
||||||
{ sender: "B", body: "two" },
|
{ sender: "B", body: "two" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = buildHistoryContextFromMap({
|
const result = buildHistoryContextFromMap({
|
||||||
historyMap,
|
historyMap,
|
||||||
historyKey: "room",
|
historyKey: "group",
|
||||||
limit: 3,
|
limit: 3,
|
||||||
entry: { sender: "C", body: "three" },
|
entry: { sender: "C", body: "three" },
|
||||||
currentMessage: "current",
|
currentMessage: "current",
|
||||||
formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
|
formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(historyMap.get("room")?.map((entry) => entry.body)).toEqual(["one", "two", "three"]);
|
expect(historyMap.get("group")?.map((entry) => entry.body)).toEqual(["one", "two", "three"]);
|
||||||
expect(result).toContain(HISTORY_CONTEXT_MARKER);
|
expect(result).toContain(HISTORY_CONTEXT_MARKER);
|
||||||
expect(result).toContain("A: one");
|
expect(result).toContain("A: one");
|
||||||
expect(result).toContain("B: two");
|
expect(result).toContain("B: two");
|
||||||
@@ -85,20 +85,20 @@ describe("history helpers", () => {
|
|||||||
|
|
||||||
it("builds context from pending map without appending", () => {
|
it("builds context from pending map without appending", () => {
|
||||||
const historyMap = new Map<string, { sender: string; body: string }[]>();
|
const historyMap = new Map<string, { sender: string; body: string }[]>();
|
||||||
historyMap.set("room", [
|
historyMap.set("group", [
|
||||||
{ sender: "A", body: "one" },
|
{ sender: "A", body: "one" },
|
||||||
{ sender: "B", body: "two" },
|
{ sender: "B", body: "two" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const result = buildPendingHistoryContextFromMap({
|
const result = buildPendingHistoryContextFromMap({
|
||||||
historyMap,
|
historyMap,
|
||||||
historyKey: "room",
|
historyKey: "group",
|
||||||
limit: 3,
|
limit: 3,
|
||||||
currentMessage: "current",
|
currentMessage: "current",
|
||||||
formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
|
formatEntry: (entry) => `${entry.sender}: ${entry.body}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(historyMap.get("room")?.map((entry) => entry.body)).toEqual(["one", "two"]);
|
expect(historyMap.get("group")?.map((entry) => entry.body)).toEqual(["one", "two"]);
|
||||||
expect(result).toContain(HISTORY_CONTEXT_MARKER);
|
expect(result).toContain(HISTORY_CONTEXT_MARKER);
|
||||||
expect(result).toContain("A: one");
|
expect(result).toContain("A: one");
|
||||||
expect(result).toContain("B: two");
|
expect(result).toContain("B: two");
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export async function initSessionState(params: {
|
|||||||
channel: baseEntry?.channel,
|
channel: baseEntry?.channel,
|
||||||
groupId: baseEntry?.groupId,
|
groupId: baseEntry?.groupId,
|
||||||
subject: baseEntry?.subject,
|
subject: baseEntry?.subject,
|
||||||
room: baseEntry?.room,
|
groupChannel: baseEntry?.groupChannel,
|
||||||
space: baseEntry?.space,
|
space: baseEntry?.space,
|
||||||
deliveryContext: deliveryFields.deliveryContext,
|
deliveryContext: deliveryFields.deliveryContext,
|
||||||
// Track originating channel for subagent announce routing.
|
// Track originating channel for subagent announce routing.
|
||||||
@@ -247,24 +247,24 @@ export async function initSessionState(params: {
|
|||||||
normalizedChannel &&
|
normalizedChannel &&
|
||||||
getChannelDock(normalizedChannel)?.capabilities.chatTypes.includes("channel"),
|
getChannelDock(normalizedChannel)?.capabilities.chatTypes.includes("channel"),
|
||||||
);
|
);
|
||||||
const nextRoom =
|
const nextGroupChannel =
|
||||||
explicitChannel ??
|
explicitChannel ??
|
||||||
((groupResolution.chatType === "channel" || isChannelProvider) &&
|
((groupResolution.chatType === "channel" || isChannelProvider) &&
|
||||||
subject &&
|
subject &&
|
||||||
subject.startsWith("#")
|
subject.startsWith("#")
|
||||||
? subject
|
? subject
|
||||||
: undefined);
|
: undefined);
|
||||||
const nextSubject = nextRoom ? undefined : subject;
|
const nextSubject = nextGroupChannel ? undefined : subject;
|
||||||
sessionEntry.chatType = groupResolution.chatType ?? "group";
|
sessionEntry.chatType = groupResolution.chatType ?? "group";
|
||||||
sessionEntry.channel = channel;
|
sessionEntry.channel = channel;
|
||||||
sessionEntry.groupId = groupResolution.id;
|
sessionEntry.groupId = groupResolution.id;
|
||||||
if (nextSubject) sessionEntry.subject = nextSubject;
|
if (nextSubject) sessionEntry.subject = nextSubject;
|
||||||
if (nextRoom) sessionEntry.room = nextRoom;
|
if (nextGroupChannel) sessionEntry.groupChannel = nextGroupChannel;
|
||||||
if (space) sessionEntry.space = space;
|
if (space) sessionEntry.space = space;
|
||||||
sessionEntry.displayName = buildGroupDisplayName({
|
sessionEntry.displayName = buildGroupDisplayName({
|
||||||
provider: sessionEntry.channel,
|
provider: sessionEntry.channel,
|
||||||
subject: sessionEntry.subject,
|
subject: sessionEntry.subject,
|
||||||
room: sessionEntry.room,
|
groupChannel: sessionEntry.groupChannel,
|
||||||
space: sessionEntry.space,
|
space: sessionEntry.space,
|
||||||
id: groupResolution.id,
|
id: groupResolution.id,
|
||||||
key: sessionKey,
|
key: sessionKey,
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ describe("sessions", () => {
|
|||||||
expect(
|
expect(
|
||||||
buildGroupDisplayName({
|
buildGroupDisplayName({
|
||||||
provider: "discord",
|
provider: "discord",
|
||||||
room: "#general",
|
groupChannel: "#general",
|
||||||
space: "friends-of-clawd",
|
space: "friends-of-clawd",
|
||||||
id: "123",
|
id: "123",
|
||||||
key: "discord:group:123",
|
key: "discord:group:123",
|
||||||
|
|||||||
@@ -22,26 +22,26 @@ function shortenGroupId(value?: string) {
|
|||||||
export function buildGroupDisplayName(params: {
|
export function buildGroupDisplayName(params: {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
room?: string;
|
groupChannel?: string;
|
||||||
space?: string;
|
space?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
key: string;
|
key: string;
|
||||||
}) {
|
}) {
|
||||||
const providerKey = (params.provider?.trim().toLowerCase() || "group").trim();
|
const providerKey = (params.provider?.trim().toLowerCase() || "group").trim();
|
||||||
const room = params.room?.trim();
|
const groupChannel = params.groupChannel?.trim();
|
||||||
const space = params.space?.trim();
|
const space = params.space?.trim();
|
||||||
const subject = params.subject?.trim();
|
const subject = params.subject?.trim();
|
||||||
const detail =
|
const detail =
|
||||||
(room && space
|
(groupChannel && space
|
||||||
? `${space}${room.startsWith("#") ? "" : "#"}${room}`
|
? `${space}${groupChannel.startsWith("#") ? "" : "#"}${groupChannel}`
|
||||||
: room || subject || space || "") || "";
|
: groupChannel || subject || space || "") || "";
|
||||||
const fallbackId = params.id?.trim() || params.key;
|
const fallbackId = params.id?.trim() || params.key;
|
||||||
const rawLabel = detail || fallbackId;
|
const rawLabel = detail || fallbackId;
|
||||||
let token = normalizeGroupLabel(rawLabel);
|
let token = normalizeGroupLabel(rawLabel);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
token = normalizeGroupLabel(shortenGroupId(rawLabel));
|
token = normalizeGroupLabel(shortenGroupId(rawLabel));
|
||||||
}
|
}
|
||||||
if (!params.room && token.startsWith("#")) {
|
if (!params.groupChannel && token.startsWith("#")) {
|
||||||
token = token.replace(/^#+/, "");
|
token = token.replace(/^#+/, "");
|
||||||
}
|
}
|
||||||
if (token && !/^[@#]/.test(token) && !token.startsWith("g-") && !token.includes("#")) {
|
if (token && !/^[@#]/.test(token) && !token.startsWith("g-") && !token.includes("#")) {
|
||||||
|
|||||||
@@ -130,6 +130,14 @@ export function loadSessionStore(
|
|||||||
rec.lastChannel = rec.lastProvider;
|
rec.lastChannel = rec.lastProvider;
|
||||||
delete rec.lastProvider;
|
delete rec.lastProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Best-effort migration: legacy `room` field → `groupChannel` (keep value, prune old key).
|
||||||
|
if (typeof rec.groupChannel !== "string" && typeof rec.room === "string") {
|
||||||
|
rec.groupChannel = rec.room;
|
||||||
|
delete rec.room;
|
||||||
|
} else if ("room" in rec) {
|
||||||
|
delete rec.room;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the result if caching is enabled
|
// Cache the result if caching is enabled
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export type SessionEntry = {
|
|||||||
channel?: string;
|
channel?: string;
|
||||||
groupId?: string;
|
groupId?: string;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
room?: string;
|
groupChannel?: string;
|
||||||
space?: string;
|
space?: string;
|
||||||
deliveryContext?: DeliveryContext;
|
deliveryContext?: DeliveryContext;
|
||||||
lastChannel?: SessionChannelId;
|
lastChannel?: SessionChannelId;
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ export const handleSessionsBridgeMethods: BridgeMethodHandler = async (
|
|||||||
chatType: entry?.chatType,
|
chatType: entry?.chatType,
|
||||||
channel: entry?.channel,
|
channel: entry?.channel,
|
||||||
subject: entry?.subject,
|
subject: entry?.subject,
|
||||||
room: entry?.room,
|
groupChannel: entry?.groupChannel,
|
||||||
space: entry?.space,
|
space: entry?.space,
|
||||||
lastChannel: entry?.lastChannel,
|
lastChannel: entry?.lastChannel,
|
||||||
lastTo: entry?.lastTo,
|
lastTo: entry?.lastTo,
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ export function listSessionsFromStore(params: {
|
|||||||
const parsed = parseGroupKey(key);
|
const parsed = parseGroupKey(key);
|
||||||
const channel = entry?.channel ?? parsed?.channel;
|
const channel = entry?.channel ?? parsed?.channel;
|
||||||
const subject = entry?.subject;
|
const subject = entry?.subject;
|
||||||
const room = entry?.room;
|
const groupChannel = entry?.groupChannel;
|
||||||
const space = entry?.space;
|
const space = entry?.space;
|
||||||
const id = parsed?.id;
|
const id = parsed?.id;
|
||||||
const displayName =
|
const displayName =
|
||||||
@@ -387,7 +387,7 @@ export function listSessionsFromStore(params: {
|
|||||||
? buildGroupDisplayName({
|
? buildGroupDisplayName({
|
||||||
provider: channel,
|
provider: channel,
|
||||||
subject,
|
subject,
|
||||||
room,
|
groupChannel,
|
||||||
space,
|
space,
|
||||||
id,
|
id,
|
||||||
key,
|
key,
|
||||||
@@ -401,7 +401,7 @@ export function listSessionsFromStore(params: {
|
|||||||
displayName,
|
displayName,
|
||||||
channel,
|
channel,
|
||||||
subject,
|
subject,
|
||||||
room,
|
groupChannel,
|
||||||
space,
|
space,
|
||||||
chatType: entry?.chatType,
|
chatType: entry?.chatType,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export type GatewaySessionRow = {
|
|||||||
displayName?: string;
|
displayName?: string;
|
||||||
channel?: string;
|
channel?: string;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
room?: string;
|
groupChannel?: string;
|
||||||
space?: string;
|
space?: string;
|
||||||
chatType?: NormalizedChatType;
|
chatType?: NormalizedChatType;
|
||||||
updatedAt: number | null;
|
updatedAt: number | null;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ function normalizeQuery(value: string): string {
|
|||||||
|
|
||||||
function stripTargetPrefixes(value: string): string {
|
function stripTargetPrefixes(value: string): string {
|
||||||
return value
|
return value
|
||||||
.replace(/^(channel|group|user):/i, "")
|
.replace(/^(channel|user):/i, "")
|
||||||
.replace(/^[@#]/, "")
|
.replace(/^[@#]/, "")
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ export function formatTargetDisplay(params: {
|
|||||||
params.kind ??
|
params.kind ??
|
||||||
(lowered.startsWith("user:")
|
(lowered.startsWith("user:")
|
||||||
? "user"
|
? "user"
|
||||||
: lowered.startsWith("channel:") || lowered.startsWith("group:")
|
: lowered.startsWith("channel:")
|
||||||
? "group"
|
? "group"
|
||||||
: undefined);
|
: undefined);
|
||||||
|
|
||||||
@@ -103,8 +103,8 @@ export function formatTargetDisplay(params: {
|
|||||||
if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) return trimmedTarget;
|
if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) return trimmedTarget;
|
||||||
|
|
||||||
const withoutPrefix = trimmedTarget.replace(/^telegram:/i, "");
|
const withoutPrefix = trimmedTarget.replace(/^telegram:/i, "");
|
||||||
if (/^(channel|group):/i.test(withoutPrefix)) {
|
if (/^channel:/i.test(withoutPrefix)) {
|
||||||
return `#${withoutPrefix.replace(/^(channel|group):/i, "")}`;
|
return `#${withoutPrefix.replace(/^channel:/i, "")}`;
|
||||||
}
|
}
|
||||||
if (/^user:/i.test(withoutPrefix)) {
|
if (/^user:/i.test(withoutPrefix)) {
|
||||||
return `@${withoutPrefix.replace(/^user:/i, "")}`;
|
return `@${withoutPrefix.replace(/^user:/i, "")}`;
|
||||||
@@ -126,7 +126,7 @@ function detectTargetKind(raw: string, preferred?: TargetResolveKind): TargetRes
|
|||||||
const trimmed = raw.trim();
|
const trimmed = raw.trim();
|
||||||
if (!trimmed) return "group";
|
if (!trimmed) return "group";
|
||||||
if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) return "user";
|
if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) return "user";
|
||||||
if (trimmed.startsWith("#") || /^channel:/i.test(trimmed) || /^group:/i.test(trimmed)) {
|
if (trimmed.startsWith("#") || /^channel:/i.test(trimmed)) {
|
||||||
return "group";
|
return "group";
|
||||||
}
|
}
|
||||||
return "group";
|
return "group";
|
||||||
|
|||||||
@@ -131,7 +131,13 @@ function normalizeSessionEntry(entry: SessionEntryLike): SessionEntry | null {
|
|||||||
typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt)
|
typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt)
|
||||||
? entry.updatedAt
|
? entry.updatedAt
|
||||||
: Date.now();
|
: Date.now();
|
||||||
return { ...(entry as unknown as SessionEntry), sessionId, updatedAt };
|
const normalized = { ...(entry as unknown as SessionEntry), sessionId, updatedAt };
|
||||||
|
const rec = normalized as unknown as Record<string, unknown>;
|
||||||
|
if (typeof rec.groupChannel !== "string" && typeof rec.room === "string") {
|
||||||
|
rec.groupChannel = rec.room;
|
||||||
|
}
|
||||||
|
delete rec.room;
|
||||||
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptyDirOrMissing(dir: string): boolean {
|
function emptyDirOrMissing(dir: string): boolean {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ describe("resolveTelegramTargetChatType", () => {
|
|||||||
|
|
||||||
it("handles tg/group prefixes and topic suffixes", () => {
|
it("handles tg/group prefixes and topic suffixes", () => {
|
||||||
expect(resolveTelegramTargetChatType("tg:5232990709")).toBe("direct");
|
expect(resolveTelegramTargetChatType("tg:5232990709")).toBe("direct");
|
||||||
expect(resolveTelegramTargetChatType("group:-123456789")).toBe("group");
|
|
||||||
expect(resolveTelegramTargetChatType("telegram:group:-1001234567890")).toBe("group");
|
expect(resolveTelegramTargetChatType("telegram:group:-1001234567890")).toBe("group");
|
||||||
expect(resolveTelegramTargetChatType("telegram:group:-1001234567890:topic:456")).toBe("group");
|
expect(resolveTelegramTargetChatType("telegram:group:-1001234567890:topic:456")).toBe("group");
|
||||||
expect(resolveTelegramTargetChatType("-1001234567890:456")).toBe("group");
|
expect(resolveTelegramTargetChatType("-1001234567890:456")).toBe("group");
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ describe("stripTelegramInternalPrefixes", () => {
|
|||||||
expect(stripTelegramInternalPrefixes("telegram:group:-100123")).toBe("-100123");
|
expect(stripTelegramInternalPrefixes("telegram:group:-100123")).toBe("-100123");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not strip group prefix without telegram prefix", () => {
|
||||||
|
expect(stripTelegramInternalPrefixes("group:-100123")).toBe("group:-100123");
|
||||||
|
});
|
||||||
|
|
||||||
it("is idempotent", () => {
|
it("is idempotent", () => {
|
||||||
expect(stripTelegramInternalPrefixes("@mychannel")).toBe("@mychannel");
|
expect(stripTelegramInternalPrefixes("@mychannel")).toBe("@mychannel");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,8 +5,19 @@ export type TelegramTarget = {
|
|||||||
|
|
||||||
export function stripTelegramInternalPrefixes(to: string): string {
|
export function stripTelegramInternalPrefixes(to: string): string {
|
||||||
let trimmed = to.trim();
|
let trimmed = to.trim();
|
||||||
|
let strippedTelegramPrefix = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
const next = trimmed.replace(/^(telegram|tg|group):/i, "").trim();
|
const next = (() => {
|
||||||
|
if (/^(telegram|tg):/i.test(trimmed)) {
|
||||||
|
strippedTelegramPrefix = true;
|
||||||
|
return trimmed.replace(/^(telegram|tg):/i, "").trim();
|
||||||
|
}
|
||||||
|
// Legacy internal form: `telegram:group:<id>` (still emitted by session keys).
|
||||||
|
if (strippedTelegramPrefix && /^group:/i.test(trimmed)) {
|
||||||
|
return trimmed.replace(/^group:/i, "").trim();
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
})();
|
||||||
if (next === trimmed) return trimmed;
|
if (next === trimmed) return trimmed;
|
||||||
trimmed = next;
|
trimmed = next;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export type GatewaySessionList = {
|
|||||||
label?: string;
|
label?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
provider?: string;
|
provider?: string;
|
||||||
room?: string;
|
groupChannel?: string;
|
||||||
space?: string;
|
space?: string;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
chatType?: string;
|
chatType?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user