mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
0e792b6de3
* 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
59 lines
2.0 KiB
TypeScript
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);
|
|
}
|
|
}
|
|
}
|