Discord: reconcile native commands without restart churn (#46597)

Merged via squash.

Prepared head SHA: 37090daad4b99171a55962101d9998fd452e2739
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
This commit is contained in:
Harold Hunt 2026-03-19 22:23:21 -04:00 committed by GitHub
parent 65594f972c
commit f1ce679929
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 6 deletions

View File

@ -181,6 +181,7 @@ Docs: https://docs.openclaw.ai
- Plugins/message discovery: require `ChannelMessageActionAdapter.describeMessageTool(...)` for shared `message` tool discovery. The legacy `listActions`, `getCapabilities`, and `getToolSchema` adapter methods are removed. Plugin authors should migrate message discovery to `describeMessageTool(...)` and keep channel-specific action runtime code inside the owning plugin package. Thanks @gumadeiras.
- Exec/env sandbox: block build-tool JVM injection (`MAVEN_OPTS`, `SBT_OPTS`, `GRADLE_OPTS`, `ANT_OPTS`), glibc tunable exploitation (`GLIBC_TUNABLES`), and .NET dependency resolution hijack (`DOTNET_ADDITIONAL_DEPS`) from the host exec environment, and restrict Gradle init script redirect (`GRADLE_USER_HOME`) as an override-only block so user-configured Gradle homes still propagate. (#49702)
- Plugins/Matrix: add a new Matrix plugin backed by the official `matrix-js-sdk`. If you are upgrading from the previous public Matrix plugin, follow the migration guide: https://docs.openclaw.ai/install/migrating-matrix Thanks @gumadeiras.
- Discord/commands: switch native command deployment to Carbon reconcile by default so Discord restarts stop churning slash commands through OpenClaws local deploy path. (#46597) Thanks @huntharo and @thewilloftheshadow.
## 2026.3.13

View File

@ -4,7 +4,7 @@
"description": "OpenClaw Discord channel plugin",
"type": "module",
"dependencies": {
"@buape/carbon": "0.0.0-beta-20260216184201",
"@buape/carbon": "0.0.0-beta-20260317045421",
"@discordjs/voice": "^0.19.2",
"discord-api-types": "^0.38.42",
"https-proxy-agent": "^8.0.0",

View File

@ -88,11 +88,25 @@ describe("monitorDiscordProvider", () => {
const getConstructedEventQueue = (): { listenerTimeout?: number } | undefined => {
expect(clientConstructorOptionsMock).toHaveBeenCalledTimes(1);
const opts = clientConstructorOptionsMock.mock.calls[0]?.[0] as {
commandDeploymentMode?: string;
eventQueue?: { listenerTimeout?: number };
};
return opts.eventQueue;
};
const getConstructedClientOptions = (): {
commandDeploymentMode?: string;
eventQueue?: { listenerTimeout?: number };
} => {
expect(clientConstructorOptionsMock).toHaveBeenCalledTimes(1);
return (
(clientConstructorOptionsMock.mock.calls[0]?.[0] as {
commandDeploymentMode?: string;
eventQueue?: { listenerTimeout?: number };
}) ?? {}
);
};
const getHealthProbe = () => {
expect(reconcileAcpThreadBindingsOnStartupMock).toHaveBeenCalledTimes(1);
const firstCall = reconcileAcpThreadBindingsOnStartupMock.mock.calls.at(0) as
@ -539,6 +553,18 @@ describe("monitorDiscordProvider", () => {
);
});
it("configures Carbon reconcile deployment by default", async () => {
const { monitorDiscordProvider } = await import("./provider.js");
await monitorDiscordProvider({
config: baseConfig(),
runtime: baseRuntime(),
});
expect(clientHandleDeployRequestMock).toHaveBeenCalledTimes(1);
expect(getConstructedClientOptions().commandDeploymentMode).toBe("reconcile");
});
it("reports connected status on startup and shutdown", async () => {
const { monitorDiscordProvider } = await import("./provider.js");
const setStatus = vi.fn();

View File

@ -306,6 +306,7 @@ async function deployDiscordCommands(params: {
// errors like Discord 30034 fail fast and don't wedge the provider.
restClient.options.queueRequests = false;
}
params.runtime.log?.("discord: native commands using Carbon reconcile path");
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
await params.client.handleDeployRequest();
@ -762,6 +763,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
baseUrl: "http://localhost",
deploySecret: "a",
clientId: applicationId,
commandDeploymentMode: "reconcile",
publicKey: "a",
token,
autoDeploy: false,
@ -805,7 +807,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
phase: "deploy-commands:start",
startAt: startupStartedAt,
gateway: lifecycleGateway,
details: `native=${nativeEnabled ? "on" : "off"} commandCount=${commands.length}`,
details: `native=${nativeEnabled ? "on" : "off"} reconcile=on commandCount=${commands.length}`,
});
await deployDiscordCommands({
client,

31
pnpm-lock.yaml generated
View File

@ -309,8 +309,8 @@ importers:
extensions/discord:
dependencies:
'@buape/carbon':
specifier: 0.0.0-beta-20260216184201
version: 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.12.8)(opusscript@0.1.1)
specifier: 0.0.0-beta-20260317045421
version: 0.0.0-beta-20260317045421(@discordjs/opus@0.10.0)(hono@4.12.8)(opusscript@0.1.1)
'@discordjs/voice':
specifier: ^0.19.2
version: 0.19.2(@discordjs/opus@0.10.0)(opusscript@0.1.1)
@ -991,6 +991,9 @@ packages:
'@buape/carbon@0.0.0-beta-20260216184201':
resolution: {integrity: sha512-u5mgYcigfPVqT7D9gVTGd+3YSflTreQmrWog7ORbb0z5w9eT8ft4rJOdw9fGwr75zMu9kXpSBaAcY2eZoJFSdA==}
'@buape/carbon@0.0.0-beta-20260317045421':
resolution: {integrity: sha512-yM+r5iSxA/iG8CZ2VhK+EkcBQV+y45WLgF7kuczt2Ul1yixjXSCCcM80GppsklfUv7pqM4Dui+7w1WB3f5p7Kg==}
'@cacheable/memory@2.0.7':
resolution: {integrity: sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==}
@ -7494,7 +7497,27 @@ snapshots:
dependencies:
css-tree: 3.2.1
'@buape/carbon@0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.12.8)(opusscript@0.1.1)':
'@buape/carbon@0.0.0-beta-20260216184201(hono@4.12.8)(opusscript@0.1.1)':
dependencies:
'@types/node': 25.5.0
discord-api-types: 0.38.37
optionalDependencies:
'@cloudflare/workers-types': 4.20260120.0
'@discordjs/voice': 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)
'@hono/node-server': 1.19.10(hono@4.12.8)
'@types/bun': 1.3.9
'@types/ws': 8.18.1
ws: 8.19.0
transitivePeerDependencies:
- '@discordjs/opus'
- bufferutil
- ffmpeg-static
- hono
- node-opus
- opusscript
- utf-8-validate
'@buape/carbon@0.0.0-beta-20260317045421(@discordjs/opus@0.10.0)(hono@4.12.8)(opusscript@0.1.1)':
dependencies:
'@types/node': 25.5.0
discord-api-types: 0.38.37
@ -12415,7 +12438,7 @@ snapshots:
dependencies:
'@agentclientprotocol/sdk': 0.16.1(zod@4.3.6)
'@aws-sdk/client-bedrock': 3.1009.0
'@buape/carbon': 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.12.8)(opusscript@0.1.1)
'@buape/carbon': 0.0.0-beta-20260216184201(hono@4.12.8)(opusscript@0.1.1)
'@clack/prompts': 1.1.0
'@discordjs/voice': 0.19.2(@discordjs/opus@0.10.0)(opusscript@0.1.1)
'@grammyjs/runner': 2.0.3(grammy@1.41.1)