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>
This commit is contained in:
Bob
2026-03-04 10:52:28 +01:00
committed by GitHub
parent bd25182d5a
commit 61f7cea48b
30 changed files with 2568 additions and 180 deletions

View File

@@ -68,6 +68,10 @@ export type {
const DISCORD_BOUND_THREAD_SYSTEM_PREFIXES = ["⚙️", "🤖", "🧰"];
function isPreflightAborted(abortSignal?: AbortSignal): boolean {
return Boolean(abortSignal?.aborted);
}
function isBoundThreadBotSystemMessage(params: {
isBoundThreadSession: boolean;
isBotAuthor: boolean;
@@ -124,6 +128,9 @@ export function shouldIgnoreBoundThreadWebhookMessage(params: {
export async function preflightDiscordMessage(
params: DiscordMessagePreflightParams,
): Promise<DiscordMessagePreflightContext | null> {
if (isPreflightAborted(params.abortSignal)) {
return null;
}
const logger = getChildLogger({ module: "discord-auto-reply" });
const message = params.data.message;
const author = params.data.author;
@@ -157,6 +164,9 @@ export async function preflightDiscordMessage(
messageId: message.id,
config: pluralkitConfig,
});
if (isPreflightAborted(params.abortSignal)) {
return null;
}
} catch (err) {
logVerbose(`discord: pluralkit lookup failed for ${message.id}: ${String(err)}`);
}
@@ -176,6 +186,9 @@ export async function preflightDiscordMessage(
const isGuildMessage = Boolean(params.data.guild_id);
const channelInfo = await resolveDiscordChannelInfo(params.client, messageChannelId);
if (isPreflightAborted(params.abortSignal)) {
return null;
}
const isDirectMessage = channelInfo?.type === ChannelType.DM;
const isGroupDm = channelInfo?.type === ChannelType.GroupDM;
logDebug(
@@ -213,6 +226,9 @@ export async function preflightDiscordMessage(
allowNameMatching,
useAccessGroups,
});
if (isPreflightAborted(params.abortSignal)) {
return null;
}
commandAuthorized = dmAccess.commandAuthorized;
if (dmAccess.decision !== "allow") {
const allowMatchMeta = formatAllowlistMatchMeta(
@@ -300,6 +316,9 @@ export async function preflightDiscordMessage(
threadChannel: earlyThreadChannel,
channelInfo,
});
if (isPreflightAborted(params.abortSignal)) {
return null;
}
earlyThreadParentId = parentInfo.id;
earlyThreadParentName = parentInfo.name;
earlyThreadParentType = parentInfo.type;
@@ -548,7 +567,11 @@ export async function preflightDiscordMessage(
shouldRequireMention,
mentionRegexes,
cfg: params.cfg,
abortSignal: params.abortSignal,
});
if (isPreflightAborted(params.abortSignal)) {
return null;
}
const mentionText = hasTypedText ? baseText : "";
const wasMentioned =
@@ -727,6 +750,7 @@ export async function preflightDiscordMessage(
token: params.token,
runtime: params.runtime,
botUserId: params.botUserId,
abortSignal: params.abortSignal,
guildHistories: params.guildHistories,
historyLimit: params.historyLimit,
mediaMaxBytes: params.mediaMaxBytes,