* Gateway: resolve agent.wait for chat.send runs * Discord: harden ACP thread binding + listener timeout * ACPX: handle already-exited child wait * Gateway/Discord: address PR review findings * Discord: keep ACP error-state thread bindings on startup * gateway: make agent.wait dedupe bridge event-driven * discord: harden ACP probe classification and cap startup fan-out * discord: add cooperative timeout cancellation * discord: fix startup probe concurrency helper typing * plugin-sdk: avoid Windows root-alias shard timeout * plugin-sdk: keep root alias reflection path non-blocking * discord+gateway: resolve remaining PR review findings * gateway+discord: fix codex review regressions * Discord/Gateway: address Codex review findings * Gateway: keep agent.wait lifecycle active with shared run IDs * Discord: clean up status reactions on aborted runs * fix: add changelog note for ACP/Discord startup hardening (#33699) (thanks @dutifulbob) --------- Co-authored-by: Onur <2453968+osolmaz@users.noreply.github.com>
89 lines
2.5 KiB
TypeScript
89 lines
2.5 KiB
TypeScript
import type { OpenClawConfig } from "../../config/config.js";
|
|
import { logVerbose } from "../../globals.js";
|
|
|
|
type DiscordAudioAttachment = {
|
|
content_type?: string;
|
|
url?: string;
|
|
};
|
|
|
|
function collectAudioAttachments(
|
|
attachments: DiscordAudioAttachment[] | undefined,
|
|
): DiscordAudioAttachment[] {
|
|
if (!Array.isArray(attachments)) {
|
|
return [];
|
|
}
|
|
return attachments.filter((att) => att.content_type?.startsWith("audio/"));
|
|
}
|
|
|
|
export async function resolveDiscordPreflightAudioMentionContext(params: {
|
|
message: {
|
|
attachments?: DiscordAudioAttachment[];
|
|
content?: string;
|
|
};
|
|
isDirectMessage: boolean;
|
|
shouldRequireMention: boolean;
|
|
mentionRegexes: RegExp[];
|
|
cfg: OpenClawConfig;
|
|
abortSignal?: AbortSignal;
|
|
}): Promise<{
|
|
hasAudioAttachment: boolean;
|
|
hasTypedText: boolean;
|
|
transcript?: string;
|
|
}> {
|
|
const audioAttachments = collectAudioAttachments(params.message.attachments);
|
|
const hasAudioAttachment = audioAttachments.length > 0;
|
|
const hasTypedText = Boolean(params.message.content?.trim());
|
|
const needsPreflightTranscription =
|
|
!params.isDirectMessage &&
|
|
params.shouldRequireMention &&
|
|
hasAudioAttachment &&
|
|
// `baseText` includes media placeholders; gate on typed text only.
|
|
!hasTypedText &&
|
|
params.mentionRegexes.length > 0;
|
|
|
|
let transcript: string | undefined;
|
|
if (needsPreflightTranscription) {
|
|
if (params.abortSignal?.aborted) {
|
|
return {
|
|
hasAudioAttachment,
|
|
hasTypedText,
|
|
};
|
|
}
|
|
try {
|
|
const { transcribeFirstAudio } = await import("../../media-understanding/audio-preflight.js");
|
|
if (params.abortSignal?.aborted) {
|
|
return {
|
|
hasAudioAttachment,
|
|
hasTypedText,
|
|
};
|
|
}
|
|
const audioUrls = audioAttachments
|
|
.map((att) => att.url)
|
|
.filter((url): url is string => typeof url === "string" && url.length > 0);
|
|
if (audioUrls.length > 0) {
|
|
transcript = await transcribeFirstAudio({
|
|
ctx: {
|
|
MediaUrls: audioUrls,
|
|
MediaTypes: audioAttachments
|
|
.map((att) => att.content_type)
|
|
.filter((contentType): contentType is string => Boolean(contentType)),
|
|
},
|
|
cfg: params.cfg,
|
|
agentDir: undefined,
|
|
});
|
|
if (params.abortSignal?.aborted) {
|
|
transcript = undefined;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
logVerbose(`discord: audio preflight transcription failed: ${String(err)}`);
|
|
}
|
|
}
|
|
|
|
return {
|
|
hasAudioAttachment,
|
|
hasTypedText,
|
|
transcript,
|
|
};
|
|
}
|