refactor(channels): reuse route context helpers

This commit is contained in:
Peter Steinberger
2026-04-27 22:30:05 +01:00
parent 3876682635
commit 3eec9e4642
3 changed files with 52 additions and 29 deletions

View File

@@ -2,6 +2,7 @@ import { getChannelPlugin, normalizeChannelId } from "../../channels/plugins/ind
import type { CallGatewayOptions } from "../../gateway/call.js";
import { parseThreadSessionSuffix } from "../../sessions/session-key-utils.js";
import { normalizeOptionalStringifiedId } from "../../shared/string-coerce.js";
import { deliveryContextFromSession } from "../../utils/delivery-context.shared.js";
import type { SessionListRow } from "./sessions-helpers.js";
import type { AnnounceTarget } from "./sessions-send-helpers.js";
import { resolveAnnounceTargetFromKey } from "./sessions-send-helpers.js";
@@ -45,30 +46,10 @@ export async function resolveAnnounceTarget(params: {
sessions.find((entry) => entry?.key === params.sessionKey) ??
sessions.find((entry) => entry?.key === params.displayKey);
const deliveryContext =
match?.deliveryContext && typeof match.deliveryContext === "object"
? (match.deliveryContext as Record<string, unknown>)
: undefined;
const origin =
match?.origin && typeof match.origin === "object"
? (match.origin as Record<string, unknown>)
: undefined;
const channel =
(typeof deliveryContext?.channel === "string" ? deliveryContext.channel : undefined) ??
(typeof match?.lastChannel === "string" ? match.lastChannel : undefined) ??
(typeof origin?.provider === "string" ? origin.provider : undefined);
const to =
(typeof deliveryContext?.to === "string" ? deliveryContext.to : undefined) ??
(typeof match?.lastTo === "string" ? match.lastTo : undefined);
const accountId =
(typeof deliveryContext?.accountId === "string" ? deliveryContext.accountId : undefined) ??
(typeof match?.lastAccountId === "string" ? match.lastAccountId : undefined) ??
(typeof origin?.accountId === "string" ? origin.accountId : undefined);
const threadId = normalizeOptionalStringifiedId(
deliveryContext?.threadId ?? match?.lastThreadId ?? origin?.threadId ?? fallbackThreadId,
);
if (channel && to) {
return { channel, to, accountId, threadId };
const context = deliveryContextFromSession(match);
const threadId = normalizeOptionalStringifiedId(context?.threadId ?? fallbackThreadId);
if (context?.channel && context.to) {
return { channel: context.channel, to: context.to, accountId: context.accountId, threadId };
}
} catch {
// ignore

View File

@@ -622,6 +622,50 @@ describe("followup queue collect routing", () => {
expect(calls[0]?.originatingThreadId).toBe("1706000000.000001");
});
it("collects messages when numeric and string thread ids share the route key", async () => {
const key = `test-collect-thread-normalized-${Date.now()}`;
const calls: FollowupRun[] = [];
const done = createDeferred<void>();
const runFollowup = async (run: FollowupRun) => {
calls.push(run);
done.resolve();
};
const settings: QueueSettings = {
mode: "collect",
debounceMs: 0,
cap: 50,
dropPolicy: "summarize",
};
enqueueFollowupRun(
key,
createRun({
prompt: "one",
originatingChannel: "telegram",
originatingTo: "-100123",
originatingThreadId: 42.9,
}),
settings,
);
enqueueFollowupRun(
key,
createRun({
prompt: "two",
originatingChannel: "telegram",
originatingTo: "-100123",
originatingThreadId: "42",
}),
settings,
);
scheduleFollowupDrain(key, runFollowup);
await done.promise;
expect(calls).toHaveLength(1);
expect(calls[0]?.prompt).toContain("[Queued messages while agent was busy]");
expect(calls[0]?.prompt).toContain("one");
expect(calls[0]?.prompt).toContain("two");
});
it("does not collect Slack messages when thread ids differ", async () => {
const key = `test-collect-slack-thread-diff-${Date.now()}`;
const calls: FollowupRun[] = [];

View File

@@ -1,3 +1,4 @@
import { channelRouteKey, normalizeChannelRouteRef } from "../../../channels/route/ref.js";
import { defaultRuntime } from "../../../runtime.js";
import { resolveGlobalMap } from "../../../shared/global-singleton.js";
import {
@@ -134,11 +135,8 @@ function resolveCrossChannelKey(item: FollowupRun): { cross?: true; key?: string
if (!isRoutableChannel(channel) || !to) {
return { cross: true };
}
// Support both number (Telegram topic IDs) and string (Slack thread_ts) thread IDs.
const threadKey = threadId != null && threadId !== "" ? String(threadId) : "";
return {
key: [channel, to, accountId || "", threadKey].join("|"),
};
const key = channelRouteKey(normalizeChannelRouteRef({ channel, to, accountId, threadId }));
return key ? { key } : { cross: true };
}
export function scheduleFollowupDrain(