feat(routing): route replies to originating channel
Implement reply routing based on OriginatingChannel/OriginatingTo fields. This ensures replies go back to the provider where the message originated instead of using the session's lastChannel. Changes: - Add OriginatingChannel/OriginatingTo fields to MsgContext (templating.ts) - Add originatingChannel/originatingTo fields to FollowupRun (queue.ts) - Create route-reply.ts with provider-agnostic router - Update all providers (Telegram, Slack, Discord, Signal, iMessage) to pass originating channel info - Update reply.ts to pass originating channel to followupRun - Update followup-runner.ts to use route-reply for originating channels This addresses the issue where messages from one provider (e.g., Slack) would receive replies on a different provider (e.g., Telegram) because the queue used the last active dispatcher instead of the originating one.
This commit is contained in:
committed by
Peter Steinberger
parent
514fcfe77e
commit
9d50ebad7d
@@ -13,6 +13,7 @@ import { SILENT_REPLY_TOKEN } from "../tokens.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||
import type { FollowupRun } from "./queue.js";
|
||||
import { extractReplyToTag } from "./reply-tags.js";
|
||||
import { isRoutableChannel, routeReply } from "./route-reply.js";
|
||||
import { incrementCompactionCount } from "./session-updates.js";
|
||||
import type { TypingController } from "./typing.js";
|
||||
|
||||
@@ -37,11 +38,28 @@ export function createFollowupRunner(params: {
|
||||
agentCfgContextTokens,
|
||||
} = params;
|
||||
|
||||
const sendFollowupPayloads = async (payloads: ReplyPayload[]) => {
|
||||
if (!opts?.onBlockReply) {
|
||||
/**
|
||||
* Sends followup payloads, routing to the originating channel if set.
|
||||
*
|
||||
* When originatingChannel/originatingTo are set on the queued run,
|
||||
* replies are routed directly to that provider instead of using the
|
||||
* session's current dispatcher. This ensures replies go back to
|
||||
* where the message originated.
|
||||
*/
|
||||
const sendFollowupPayloads = async (
|
||||
payloads: ReplyPayload[],
|
||||
queued: FollowupRun,
|
||||
) => {
|
||||
// Check if we should route to originating channel.
|
||||
const { originatingChannel, originatingTo } = queued;
|
||||
const shouldRouteToOriginating =
|
||||
isRoutableChannel(originatingChannel) && originatingTo;
|
||||
|
||||
if (!shouldRouteToOriginating && !opts?.onBlockReply) {
|
||||
logVerbose("followup queue: no onBlockReply handler; dropping payloads");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const payload of payloads) {
|
||||
if (!payload?.text && !payload?.mediaUrl && !payload?.mediaUrls?.length) {
|
||||
continue;
|
||||
@@ -54,7 +72,23 @@ export function createFollowupRunner(params: {
|
||||
continue;
|
||||
}
|
||||
await typing.startTypingOnText(payload.text);
|
||||
await opts.onBlockReply(payload);
|
||||
|
||||
// Route to originating channel if set, otherwise fall back to dispatcher.
|
||||
if (shouldRouteToOriginating) {
|
||||
const result = await routeReply({
|
||||
payload,
|
||||
channel: originatingChannel,
|
||||
to: originatingTo,
|
||||
cfg: queued.run.config,
|
||||
});
|
||||
if (!result.ok) {
|
||||
logVerbose(
|
||||
`followup queue: route-reply failed: ${result.error ?? "unknown error"}`,
|
||||
);
|
||||
}
|
||||
} else if (opts?.onBlockReply) {
|
||||
await opts.onBlockReply(payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -210,7 +244,7 @@ export function createFollowupRunner(params: {
|
||||
}
|
||||
}
|
||||
|
||||
await sendFollowupPayloads(replyTaggedPayloads);
|
||||
await sendFollowupPayloads(replyTaggedPayloads, queued);
|
||||
} finally {
|
||||
typing.markRunComplete();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user