Files
openclaw/extensions/discord/src/monitor/message-handler.retry.ts
T
Peter Steinberger 0e792b6de3 refactor(channels): centralize inbound orchestration and remove internal compat (#109716)
* refactor(channels): centralize inbound turn orchestration

* refactor(runtime): remove stale compatibility paths

* chore(guards): reject internal deprecated API use

* refactor(channels): simplify core turn planning

* chore(guards): keep deprecated checks boundary-focused

* refactor(memory): keep modern config off compat barrel

* fix(msteams): preserve feedback learning

* test(channels): align modern inbound fixtures

* refactor(channels): finish modern inbound migration

* refactor(channels): tighten core inbound kernel

* fix(channels): preserve turn assembly narrowing

* test(sdk): keep runtime mock binding immutable

* test(matrix): isolate read policy runtime

* test(msteams): mock canonical reply factory

* test(slack): mock core inbound turn dispatch

* test(telegram): inject core session recorder

* test(signal): inject core session recorder

* test(googlechat): assert canonical inbound routing

* test(synology-chat): align core turn fixture

* fix(sdk): preserve direct DM runtime compat

* refactor(channels): own inbound envelope compat in core

* refactor(channels): trim inbound dispatch seams

* refactor(channels): remove redundant async wrappers

* test(synology-chat): type canonical dispatcher mock

* refactor(channels): remove remaining dead compat seams

* chore(sdk): refresh API baseline after rebase

* fix(channels): preserve direct DM identity metadata
2026-07-17 00:56:46 -07:00

59 lines
2.0 KiB
TypeScript

import { DiscordRetryableInboundError } from "./inbound-dedupe.js";
const REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE = /^reply session initialization conflicted for \S+$/u;
const DISCORD_SESSION_CONFLICT_FAILURE_TEXT =
"⚠️ Couldn't process this message because the session stayed busy. Please try again in a moment.";
type TerminalFailureDelivery = (
payload: { text: string; isError: true },
info: { kind: "final" },
) => Promise<unknown>;
type DeliveryErrorHandler = (error: unknown, info: { kind: string }) => void;
function isReplySessionInitConflictError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return REPLY_SESSION_INIT_CONFLICT_MESSAGE_RE.test(message);
}
export async function completeDiscordSessionConflict(
error: unknown,
deliver: TerminalFailureDelivery,
onDeliveryError: DeliveryErrorHandler,
): Promise<boolean> {
if (!isReplySessionInitConflictError(error)) {
return false;
}
try {
await deliver(
{ text: DISCORD_SESSION_CONFLICT_FAILURE_TEXT, isError: true },
{ kind: "final" },
);
return true;
} catch (deliveryError) {
// Keep the conflict retryable when its visible terminal notice cannot land.
onDeliveryError(deliveryError, { kind: "final" });
throw new DiscordRetryableInboundError(
`discord: reply session init conflict exhausted and terminal notice failed: ${String(deliveryError)}`,
{ cause: error },
);
}
}
export function removeDiscordReplayHistoryEntry<T extends { messageId?: string }>(
historyMap: Map<string, T[]>,
historyKey: string,
messageId: string,
): void {
const history = historyMap.get(historyKey);
if (!history) {
return;
}
// An exhausted dispatch can release its replay claim after pending history
// was recorded. Remove that copy before rebuilding the same inbound turn.
for (let index = history.length - 1; index >= 0; index -= 1) {
if (history[index]?.messageId === messageId) {
history.splice(index, 1);
}
}
}