fix(discord): harden slash command routing

This commit is contained in:
Shadow
2026-03-03 11:22:32 -06:00
parent 0eef7a367d
commit b8b1eeb052
8 changed files with 290 additions and 4 deletions

View File

@@ -11,6 +11,7 @@ import { GatewayCloseCodes, type GatewayPlugin } from "@buape/carbon/gateway";
import { VoicePlugin } from "@buape/carbon/voice";
import { Routes } from "discord-api-types/v10";
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
import type { NativeCommandSpec } from "../../auto-reply/commands-registry.js";
import { listNativeCommandSpecsForConfig } from "../../auto-reply/commands-registry.js";
import type { HistoryEntry } from "../../auto-reply/reply/history.js";
import { listSkillCommandsForAgents } from "../../auto-reply/skill-commands.js";
@@ -37,6 +38,7 @@ import { danger, logVerbose, shouldLogVerbose, warn } from "../../globals.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { createDiscordRetryRunner } from "../../infra/retry-policy.js";
import { createSubsystemLogger } from "../../logging/subsystem.js";
import { getPluginCommandSpecs } from "../../plugins/commands.js";
import { createNonExitingRuntime, type RuntimeEnv } from "../../runtime.js";
import { resolveDiscordAccount } from "../accounts.js";
import { fetchDiscordApplicationId } from "../probe.js";
@@ -141,6 +143,37 @@ function dedupeSkillCommandsForDiscord(
return deduped;
}
function appendPluginCommandSpecs(params: {
commandSpecs: NativeCommandSpec[];
runtime: RuntimeEnv;
}): NativeCommandSpec[] {
const merged = [...params.commandSpecs];
const existingNames = new Set(
merged.map((spec) => spec.name.trim().toLowerCase()).filter(Boolean),
);
for (const pluginCommand of getPluginCommandSpecs()) {
const normalizedName = pluginCommand.name.trim().toLowerCase();
if (!normalizedName) {
continue;
}
if (existingNames.has(normalizedName)) {
params.runtime.error?.(
danger(
`discord: plugin command "/${normalizedName}" duplicates an existing native command. Skipping.`,
),
);
continue;
}
existingNames.add(normalizedName);
merged.push({
name: pluginCommand.name,
description: pluginCommand.description,
acceptsArgs: pluginCommand.acceptsArgs,
});
}
return merged;
}
async function deployDiscordCommands(params: {
client: Client;
runtime: RuntimeEnv;
@@ -317,10 +350,14 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
let commandSpecs = nativeEnabled
? listNativeCommandSpecsForConfig(cfg, { skillCommands, provider: "discord" })
: [];
if (nativeEnabled) {
commandSpecs = appendPluginCommandSpecs({ commandSpecs, runtime });
}
const initialCommandCount = commandSpecs.length;
if (nativeEnabled && nativeSkillsEnabled && commandSpecs.length > maxDiscordCommands) {
skillCommands = [];
commandSpecs = listNativeCommandSpecsForConfig(cfg, { skillCommands: [], provider: "discord" });
commandSpecs = appendPluginCommandSpecs({ commandSpecs, runtime });
runtime.log?.(
warn(
`discord: ${initialCommandCount} commands exceeds limit; removing per-skill commands and keeping /skill.`,