openclaw/src/discord/monitor/preflight-audio.ts
Bob 61f7cea48b
fix: kill stuck ACP child processes on startup and harden sessions in discord threads (#33699)
* 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>
2026-03-04 10:52:28 +01:00

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,
};
}