fix: preserve subagent thread routing (#1241)
Thanks @gnarco. Co-authored-by: gnarco <gnarco@users.noreply.github.com>
This commit is contained in:
@@ -210,6 +210,8 @@ export async function runAgentTurnWithFallback(params: {
|
||||
sessionKey: params.sessionKey,
|
||||
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
|
||||
agentAccountId: params.sessionCtx.AccountId,
|
||||
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
|
||||
messageThreadId: params.sessionCtx.MessageThreadId ?? undefined,
|
||||
// Provider threading context for tool auto-injection
|
||||
...buildThreadingToolContext({
|
||||
sessionCtx: params.sessionCtx,
|
||||
|
||||
@@ -106,6 +106,8 @@ export async function runMemoryFlushIfNeeded(params: {
|
||||
sessionKey: params.sessionKey,
|
||||
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
|
||||
agentAccountId: params.sessionCtx.AccountId,
|
||||
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
|
||||
messageThreadId: params.sessionCtx.MessageThreadId ?? undefined,
|
||||
// Provider threading context for tool auto-injection
|
||||
...buildThreadingToolContext({
|
||||
sessionCtx: params.sessionCtx,
|
||||
|
||||
@@ -145,6 +145,8 @@ export function createFollowupRunner(params: {
|
||||
sessionKey: queued.run.sessionKey,
|
||||
messageProvider: queued.run.messageProvider,
|
||||
agentAccountId: queued.run.agentAccountId,
|
||||
messageTo: queued.originatingTo,
|
||||
messageThreadId: queued.originatingThreadId,
|
||||
sessionFile: queued.run.sessionFile,
|
||||
workspaceDir: queued.run.workspaceDir,
|
||||
config: queued.run.config,
|
||||
|
||||
@@ -168,6 +168,8 @@ export async function handleInlineActions(params: {
|
||||
agentSessionKey: sessionKey,
|
||||
agentChannel: channel,
|
||||
agentAccountId: (ctx as { AccountId?: string }).AccountId,
|
||||
agentTo: ctx.OriginatingTo ?? ctx.To,
|
||||
agentThreadId: ctx.MessageThreadId ?? undefined,
|
||||
agentDir,
|
||||
workspaceDir,
|
||||
config: cfg,
|
||||
|
||||
@@ -255,6 +255,22 @@ describe("routeReply", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("uses threadId as threadTs for Slack when replyToId is missing", async () => {
|
||||
mocks.sendMessageSlack.mockClear();
|
||||
await routeReply({
|
||||
payload: { text: "hi" },
|
||||
channel: "slack",
|
||||
to: "channel:C123",
|
||||
threadId: "1710000000.9999",
|
||||
cfg: {} as never,
|
||||
});
|
||||
expect(mocks.sendMessageSlack).toHaveBeenCalledWith(
|
||||
"channel:C123",
|
||||
"hi",
|
||||
expect.objectContaining({ threadTs: "1710000000.9999" }),
|
||||
);
|
||||
});
|
||||
|
||||
it("sends multiple mediaUrls (caption only on first)", async () => {
|
||||
mocks.sendMessageSlack.mockClear();
|
||||
await routeReply({
|
||||
|
||||
@@ -100,6 +100,11 @@ export async function routeReply(params: RouteReplyParams): Promise<RouteReplyRe
|
||||
return { ok: false, error: "Reply routing aborted" };
|
||||
}
|
||||
|
||||
const resolvedReplyToId =
|
||||
replyToId ??
|
||||
(channelId === "slack" && threadId != null && threadId !== "" ? String(threadId) : undefined);
|
||||
const resolvedThreadId = channelId === "slack" ? null : (threadId ?? null);
|
||||
|
||||
try {
|
||||
// Provider docking: this is an execution boundary (we're about to send).
|
||||
// Keep the module cheap to import by loading outbound plumbing lazily.
|
||||
@@ -110,8 +115,8 @@ export async function routeReply(params: RouteReplyParams): Promise<RouteReplyRe
|
||||
to,
|
||||
accountId: accountId ?? undefined,
|
||||
payloads: [normalized],
|
||||
replyToId: replyToId ?? null,
|
||||
threadId: threadId ?? null,
|
||||
replyToId: resolvedReplyToId ?? null,
|
||||
threadId: resolvedThreadId,
|
||||
abortSignal,
|
||||
mirror: params.sessionKey
|
||||
? {
|
||||
|
||||
@@ -221,16 +221,20 @@ export async function initSessionState(params: {
|
||||
const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
|
||||
const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo;
|
||||
const lastAccountIdRaw = (ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
|
||||
const lastThreadIdRaw =
|
||||
(ctx.MessageThreadId as string | number | undefined) || baseEntry?.lastThreadId;
|
||||
const deliveryFields = normalizeSessionDeliveryFields({
|
||||
deliveryContext: {
|
||||
channel: lastChannelRaw,
|
||||
to: lastToRaw,
|
||||
accountId: lastAccountIdRaw,
|
||||
threadId: lastThreadIdRaw,
|
||||
},
|
||||
});
|
||||
const lastChannel = deliveryFields.lastChannel ?? lastChannelRaw;
|
||||
const lastTo = deliveryFields.lastTo ?? lastToRaw;
|
||||
const lastAccountId = deliveryFields.lastAccountId ?? lastAccountIdRaw;
|
||||
const lastThreadId = deliveryFields.lastThreadId ?? lastThreadIdRaw;
|
||||
sessionEntry = {
|
||||
...baseEntry,
|
||||
sessionId,
|
||||
@@ -261,6 +265,7 @@ export async function initSessionState(params: {
|
||||
lastChannel,
|
||||
lastTo,
|
||||
lastAccountId,
|
||||
lastThreadId,
|
||||
};
|
||||
const metaPatch = deriveSessionMetaPatch({
|
||||
ctx: sessionCtxForState,
|
||||
|
||||
Reference in New Issue
Block a user