Skip to content

Commit b8fb236

Browse files
committed
fix(reply): keep inherited routes unthreaded
1 parent a29e730 commit b8fb236

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

src/auto-reply/reply/dispatch-from-config.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,6 +1420,48 @@ describe("dispatchReplyFromConfig", () => {
14201420
expect(replyDispatchCall?.[0]?.originatingTo).toBe("user:ou_123");
14211421
});
14221422

1423+
it("does not borrow the live webchat thread for unthreaded inherited sessions_send routes", async () => {
1424+
setNoAbort();
1425+
mocks.routeReply.mockClear();
1426+
sessionStoreMocks.currentEntry = {
1427+
deliveryContext: {
1428+
channel: "feishu",
1429+
to: "user:ou_123",
1430+
accountId: "work",
1431+
},
1432+
lastChannel: "feishu",
1433+
lastTo: "user:ou_123",
1434+
lastAccountId: "work",
1435+
};
1436+
const cfg = emptyConfig;
1437+
const dispatcher = createDispatcher();
1438+
const ctx = buildTestCtx({
1439+
Provider: "webchat",
1440+
Surface: "webchat",
1441+
SessionKey: "agent:main:feishu:direct:ou_123",
1442+
AccountId: undefined,
1443+
MessageThreadId: "internal-webchat-thread",
1444+
OriginatingChannel: "webchat",
1445+
OriginatingTo: "session:dashboard",
1446+
InputProvenance: {
1447+
kind: "inter_session",
1448+
sourceTool: "sessions_send",
1449+
sourceChannel: "webchat",
1450+
},
1451+
});
1452+
1453+
const replyResolver = async () => ({ text: "hi" }) satisfies ReplyPayload;
1454+
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
1455+
1456+
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
1457+
const routeCall = firstRouteReplyCall() as
1458+
| { channel?: string; threadId?: string | number; to?: string }
1459+
| undefined;
1460+
expect(routeCall?.channel).toBe("feishu");
1461+
expect(routeCall?.to).toBe("user:ou_123");
1462+
expect(routeCall?.threadId).toBeUndefined();
1463+
});
1464+
14231465
it("routes exec-event replies using last route fields when delivery context is missing", async () => {
14241466
setNoAbort();
14251467
mocks.routeReply.mockClear();

src/auto-reply/reply/dispatch-from-config.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,12 +1202,10 @@ export async function dispatchReplyFromConfig(
12021202
ctx,
12031203
sessionKey: acpDispatchSessionKey,
12041204
});
1205-
// sessions_send inherited routes carry the persisted delivery thread; ordinary turns keep
1206-
// using active-turn context so normalized stale origin threads stay suppressed.
1205+
// sessions_send inherited routes use only the persisted delivery thread; ordinary turns
1206+
// keep using active-turn context so normalized stale origin threads stay suppressed.
12071207
const routeReplyThreadId =
1208-
replyRoute.inheritedExternalRoute === true
1209-
? (replyRoute.threadId ?? routeThreadId)
1210-
: routeThreadId;
1208+
replyRoute.inheritedExternalRoute === true ? replyRoute.threadId : routeThreadId;
12111209
const inboundAudio = isInboundAudioContext(ctx);
12121210
const sessionTtsAuto = normalizeTtsAutoMode(sessionStoreEntry.entry?.ttsAuto);
12131211
const workspaceDir = resolveAgentWorkspaceDir(cfg, sessionAgentId);

0 commit comments

Comments
 (0)