mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:10:45 +00:00
fix(auto-reply): route exec-event replies via persisted delivery context
This commit is contained in:
committed by
Peter Steinberger
parent
aad31817ef
commit
87a08dd4c2
@@ -713,6 +713,43 @@ describe("dispatchReplyFromConfig", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("routes exec-event replies using persisted session delivery context when current turn has no originating route", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
sessionStoreMocks.currentEntry = {
|
||||
deliveryContext: {
|
||||
channel: "telegram",
|
||||
to: "telegram:999",
|
||||
accountId: "acc-1",
|
||||
},
|
||||
lastChannel: "telegram",
|
||||
lastTo: "telegram:999",
|
||||
lastAccountId: "acc-1",
|
||||
};
|
||||
const cfg = emptyConfig;
|
||||
const dispatcher = createDispatcher();
|
||||
const ctx = buildTestCtx({
|
||||
Provider: "exec-event",
|
||||
Surface: "exec-event",
|
||||
SessionKey: "agent:main:main",
|
||||
AccountId: undefined,
|
||||
OriginatingChannel: undefined,
|
||||
OriginatingTo: undefined,
|
||||
});
|
||||
|
||||
const replyResolver = async () => ({ text: "hi" }) satisfies ReplyPayload;
|
||||
await dispatchReplyFromConfig({ ctx, cfg, dispatcher, replyResolver });
|
||||
|
||||
expect(dispatcher.sendFinalReply).not.toHaveBeenCalled();
|
||||
expect(mocks.routeReply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
channel: "telegram",
|
||||
to: "telegram:999",
|
||||
accountId: "acc-1",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to thread-scoped session key when current ctx has no MessageThreadId", async () => {
|
||||
setNoAbort();
|
||||
mocks.routeReply.mockClear();
|
||||
|
||||
@@ -287,6 +287,18 @@ export async function dispatchReplyFromConfig(
|
||||
"",
|
||||
) ?? "off",
|
||||
});
|
||||
const isSystemEventTurn =
|
||||
ctx.Provider === "heartbeat" || ctx.Provider === "cron-event" || ctx.Provider === "exec-event";
|
||||
const persistedDeliveryContext = sessionStoreEntry.entry?.deliveryContext;
|
||||
const fallbackOriginatingChannel = isSystemEventTurn
|
||||
? (persistedDeliveryContext?.channel ?? sessionStoreEntry.entry?.lastChannel)
|
||||
: undefined;
|
||||
const fallbackOriginatingTo = isSystemEventTurn
|
||||
? (persistedDeliveryContext?.to ?? sessionStoreEntry.entry?.lastTo)
|
||||
: undefined;
|
||||
const fallbackOriginatingAccountId = isSystemEventTurn
|
||||
? (persistedDeliveryContext?.accountId ?? sessionStoreEntry.entry?.lastAccountId)
|
||||
: undefined;
|
||||
// Restore route thread context only from the active turn or the thread-scoped session key.
|
||||
// Do not read thread ids from the normalised session store here: `origin.threadId` can be
|
||||
// folded back into lastThreadId/deliveryContext during store normalisation and resurrect a
|
||||
@@ -319,7 +331,10 @@ export async function dispatchReplyFromConfig(
|
||||
//
|
||||
// Debug: `pnpm test src/auto-reply/reply/dispatch-from-config.test.ts`
|
||||
const suppressAcpChildUserDelivery = isParentOwnedBackgroundAcpSession(sessionStoreEntry.entry);
|
||||
const normalizedOriginatingChannel = normalizeMessageChannel(ctx.OriginatingChannel);
|
||||
const effectiveOriginatingChannel = ctx.OriginatingChannel ?? fallbackOriginatingChannel;
|
||||
const effectiveOriginatingTo = ctx.OriginatingTo ?? fallbackOriginatingTo;
|
||||
const routeAccountId = ctx.AccountId ?? fallbackOriginatingAccountId;
|
||||
const normalizedOriginatingChannel = normalizeMessageChannel(effectiveOriginatingChannel);
|
||||
const normalizedProviderChannel = normalizeMessageChannel(ctx.Provider);
|
||||
const normalizedSurfaceChannel = normalizeMessageChannel(ctx.Surface);
|
||||
const normalizedCurrentSurface = normalizedProviderChannel ?? normalizedSurfaceChannel;
|
||||
@@ -331,7 +346,7 @@ export async function dispatchReplyFromConfig(
|
||||
!suppressAcpChildUserDelivery &&
|
||||
!isInternalWebchatTurn &&
|
||||
normalizedOriginatingChannel &&
|
||||
ctx.OriginatingTo &&
|
||||
effectiveOriginatingTo &&
|
||||
normalizedOriginatingChannel !== normalizedCurrentSurface,
|
||||
);
|
||||
const routeReplyRuntime = hasRouteReplyCandidate ? await loadRouteReplyRuntime() : undefined;
|
||||
@@ -340,12 +355,12 @@ export async function dispatchReplyFromConfig(
|
||||
provider: ctx.Provider,
|
||||
surface: ctx.Surface,
|
||||
explicitDeliverRoute: ctx.ExplicitDeliverRoute,
|
||||
originatingChannel: ctx.OriginatingChannel,
|
||||
originatingTo: ctx.OriginatingTo,
|
||||
originatingChannel: effectiveOriginatingChannel,
|
||||
originatingTo: effectiveOriginatingTo,
|
||||
suppressDirectUserDelivery: suppressAcpChildUserDelivery,
|
||||
isRoutableChannel: routeReplyRuntime?.isRoutableChannel ?? (() => false),
|
||||
});
|
||||
const originatingTo = ctx.OriginatingTo;
|
||||
const originatingTo = effectiveOriginatingTo;
|
||||
const ttsChannel = shouldRouteToOriginating ? originatingChannel : currentSurface;
|
||||
const { createReplyMediaPathNormalizer } = await loadReplyMediaPathsRuntime();
|
||||
const normalizeReplyMediaPaths = createReplyMediaPathNormalizer({
|
||||
@@ -353,7 +368,7 @@ export async function dispatchReplyFromConfig(
|
||||
sessionKey: acpDispatchSessionKey,
|
||||
workspaceDir: resolveAgentWorkspaceDir(cfg, sessionAgentId),
|
||||
messageProvider: ttsChannel,
|
||||
accountId: ctx.AccountId,
|
||||
accountId: routeAccountId,
|
||||
groupId,
|
||||
groupChannel: ctx.GroupChannel,
|
||||
groupSpace: ctx.GroupSpace,
|
||||
@@ -385,7 +400,7 @@ export async function dispatchReplyFromConfig(
|
||||
ctx.CommandSource === "native"
|
||||
? (ctx.CommandTargetSessionKey ?? ctx.SessionKey)
|
||||
: ctx.SessionKey,
|
||||
accountId: ctx.AccountId,
|
||||
accountId: routeAccountId,
|
||||
requesterSenderId: ctx.SenderId,
|
||||
requesterSenderName: ctx.SenderName,
|
||||
requesterSenderUsername: ctx.SenderUsername,
|
||||
|
||||
Reference in New Issue
Block a user