Compare commits

...

6 Commits

Author SHA1 Message Date
Vincent Koc
88de47bb3c fix(discord): bypass monolithic runtime barrel in lazy slices 2026-03-24 15:16:21 -07:00
Vincent Koc
e3ecc5a20a Merge branch 'main' into perf/discord-runtime-lazy-split 2026-03-24 15:07:42 -07:00
Vincent Koc
9e53c5e032 Merge branch 'main' into perf/discord-runtime-lazy-split 2026-03-22 09:31:48 -07:00
Vincent Koc
1253bb4a8f build(plugin-sdk): export discord runtime seams 2026-03-22 09:28:43 -07:00
Vincent Koc
f6a1cfbdf3 fix(plugin-sdk): add discord runtime seams 2026-03-22 09:26:04 -07:00
Vincent Koc
d89e19a9aa perf(discord): split runtime lazy ops 2026-03-22 09:17:36 -07:00
12 changed files with 180 additions and 105 deletions

View File

@@ -37,6 +37,7 @@ Docs: https://docs.openclaw.ai
- Runtime/build: stabilize long-lived lazy `dist` runtime entry paths and harden bundled plugin npm staging so local rebuilds stop breaking on missing hashed chunks or broken shell `npm` shims. (#53855) Thanks @vincentkoc.
- Slack/runtime defaults: trim Slack DM reply overhead, restore Codex auto transport, and tighten Slack/web-search runtime defaults around DM preview threading, cache scoping, warning dedupe, and explicit web-search opt-in. (#53957) Thanks @vincentkoc.
- Discord/timeouts: send a visible timeout reply when the inbound Discord worker times out before a final reply starts, including created auto-thread targets and queued-run ordering. (#53823) Thanks @Kimbo7870.
- Discord/runtime: split the core Discord runtime into provider, send, and directory lazy-load slices so startup and first-use paths stop importing the whole Discord ops surface up front.
## 2026.3.23
@@ -210,6 +211,14 @@ Docs: https://docs.openclaw.ai
- Nostr/security: enforce inbound DM policy before decrypt, route Nostr DMs through the standard reply pipeline, and add pre-crypto rate and size guards so unknown senders cannot bypass pairing or force unbounded crypto work. Thanks @kuranikaran.
- Synology Chat/security: keep reply delivery bound to stable numeric `user_id` by default, and gate mutable username/nickname recipient lookup behind `dangerouslyAllowNameMatching` with new regression coverage. Thanks @nexrin.
- Agents/default timeout: raise the shared default agent timeout from `600s` to `48h` so long-running ACP and agent sessions do not fail unless you configure a shorter limit.
- Gateway/Linux: auto-detect nvm-managed Node TLS CA bundle needs before CLI startup and refresh installed services that are missing `NODE_EXTRA_CA_CERTS`. (#51146) Thanks @GodsBoy.
- Android/pairing: resolve portless secure setup URLs to `443` while preserving direct cleartext gateway defaults and explicit `:80` manual endpoints in onboarding. (#43540) Thanks @fmercurio.
- CLI/config: make `config set --strict-json` enforce real JSON, prefer `JSON.parse` with JSON5 fallback for machine-written cron/subagent stores, and relabel raw config surfaces as `JSON/JSON5` to match actual compatibility. Related: #48415, #43127, #14529, #21332. Thanks @adhitShet and @vincentkoc.
- CLI/Ollama onboarding: keep the interactive model picker for explicit `openclaw onboard --auth-choice ollama` runs so setup still selects a default model without reintroducing pre-picker auto-pulls. (#49249) Thanks @BruceMacD.
- Plugins/bundler TDZ: fix `RESERVED_COMMANDS` temporal dead zone error that prevented device-pair, phone-control, and talk-voice plugins from registering when the bundler placed the commands module after call sites in the same output chunk. Thanks @BunsDev.
- Plugins/imports: fix stale googlechat runtime-api import paths and signal SDK circular re-exports broken by recent plugin-sdk refactors. Thanks @BunsDev.
- Telegram/setup: seed fresh setups with `channels.telegram.groups["*"].requireMention=true` so new bots stay mention-gated in groups unless you explicitly open them up. Thanks @vincentkoc.
- Google auth/Node 25: patch `gaxios` to use native fetch without injecting `globalThis.window`, while translating proxy and mTLS transport settings so Google Vertex and Google Chat auth keep working on Node 25. (#47914) Thanks @pdd-cli.
- Gateway/startup: load bundled channel plugins from compiled `dist/extensions` entries in built installs, so gateway boot no longer recompiles bundled extension TypeScript on every startup and WhatsApp-class cold starts drop back to seconds instead of tens of seconds or worse. (#47560) Thanks @ngutman.
- Gateway/startup: prewarm the configured primary model before channel startup and retry one transient provider-runtime miss so the first Telegram or Discord message after boot no longer fails with `Unknown model: openai-codex/gpt-5.4`. Thanks @vincentkoc.
- CLI/startup: lazy-load channel add and root help startup paths to trim avoidable RSS and help latency on constrained hosts. (#46784) Thanks @vincentkoc.

View File

@@ -265,6 +265,22 @@
"types": "./dist/plugin-sdk/discord-core.d.ts",
"default": "./dist/plugin-sdk/discord-core.js"
},
"./plugin-sdk/discord-runtime-bindings": {
"types": "./dist/plugin-sdk/discord-runtime-bindings.d.ts",
"default": "./dist/plugin-sdk/discord-runtime-bindings.js"
},
"./plugin-sdk/discord-runtime-directory": {
"types": "./dist/plugin-sdk/discord-runtime-directory.d.ts",
"default": "./dist/plugin-sdk/discord-runtime-directory.js"
},
"./plugin-sdk/discord-runtime-provider": {
"types": "./dist/plugin-sdk/discord-runtime-provider.d.ts",
"default": "./dist/plugin-sdk/discord-runtime-provider.js"
},
"./plugin-sdk/discord-runtime-send": {
"types": "./dist/plugin-sdk/discord-runtime-send.d.ts",
"default": "./dist/plugin-sdk/discord-runtime-send.js"
},
"./plugin-sdk/extension-shared": {
"types": "./dist/plugin-sdk/extension-shared.d.ts",
"default": "./dist/plugin-sdk/extension-shared.js"

View File

@@ -56,6 +56,10 @@
"diffs",
"discord",
"discord-core",
"discord-runtime-bindings",
"discord-runtime-directory",
"discord-runtime-provider",
"discord-runtime-send",
"extension-shared",
"channel-config-helpers",
"channel-config-schema",

View File

@@ -0,0 +1,13 @@
export { discordMessageActions } from "../../extensions/discord/src/channel-actions.js";
export { getThreadBindingManager } from "../../extensions/discord/src/monitor/thread-bindings.manager.js";
export {
resolveThreadBindingIdleTimeoutMs,
resolveThreadBindingInactivityExpiresAt,
resolveThreadBindingMaxAgeExpiresAt,
resolveThreadBindingMaxAgeMs,
} from "../../extensions/discord/src/monitor/thread-bindings.state.js";
export {
setThreadBindingIdleTimeoutBySessionKey,
setThreadBindingMaxAgeBySessionKey,
unbindThreadBindingsBySessionKey,
} from "../../extensions/discord/src/monitor/thread-bindings.lifecycle.js";

View File

@@ -0,0 +1,7 @@
export { auditDiscordChannelPermissions } from "../../extensions/discord/src/audit.js";
export {
listDiscordDirectoryGroupsLive,
listDiscordDirectoryPeersLive,
} from "../../extensions/discord/src/directory-live.js";
export { resolveDiscordChannelAllowlist } from "../../extensions/discord/src/resolve-channels.js";
export { resolveDiscordUserAllowlist } from "../../extensions/discord/src/resolve-users.js";

View File

@@ -0,0 +1,2 @@
export { monitorDiscordProvider } from "../../extensions/discord/src/monitor/provider.runtime.js";
export { probeDiscord } from "../../extensions/discord/src/probe.js";

View File

@@ -0,0 +1,11 @@
export { editChannelDiscord } from "../../extensions/discord/src/send.channels.js";
export { sendDiscordComponentMessage } from "../../extensions/discord/src/send.components.js";
export {
createThreadDiscord,
deleteMessageDiscord,
editMessageDiscord,
pinMessageDiscord,
unpinMessageDiscord,
} from "../../extensions/discord/src/send.messages.js";
export { sendMessageDiscord, sendPollDiscord } from "../../extensions/discord/src/send.outbound.js";
export { sendTypingDiscord } from "../../extensions/discord/src/send.typing.js";

View File

@@ -0,0 +1,23 @@
import { auditDiscordChannelPermissions } from "../../plugin-sdk/discord-runtime-directory.js";
import {
listDiscordDirectoryGroupsLive,
listDiscordDirectoryPeersLive,
} from "../../plugin-sdk/discord-runtime-directory.js";
import { resolveDiscordChannelAllowlist } from "../../plugin-sdk/discord-runtime-directory.js";
import { resolveDiscordUserAllowlist } from "../../plugin-sdk/discord-runtime-directory.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
export const runtimeDiscordDirectory = {
auditChannelPermissions: auditDiscordChannelPermissions,
listDirectoryGroupsLive: listDiscordDirectoryGroupsLive,
listDirectoryPeersLive: listDiscordDirectoryPeersLive,
resolveChannelAllowlist: resolveDiscordChannelAllowlist,
resolveUserAllowlist: resolveDiscordUserAllowlist,
} satisfies Pick<
PluginRuntimeChannel["discord"],
| "auditChannelPermissions"
| "listDirectoryGroupsLive"
| "listDirectoryPeersLive"
| "resolveChannelAllowlist"
| "resolveUserAllowlist"
>;

View File

@@ -1,66 +0,0 @@
import {
auditDiscordChannelPermissions as auditDiscordChannelPermissionsImpl,
listDiscordDirectoryGroupsLive as listDiscordDirectoryGroupsLiveImpl,
listDiscordDirectoryPeersLive as listDiscordDirectoryPeersLiveImpl,
monitorDiscordProvider as monitorDiscordProviderImpl,
probeDiscord as probeDiscordImpl,
resolveDiscordChannelAllowlist as resolveDiscordChannelAllowlistImpl,
resolveDiscordUserAllowlist as resolveDiscordUserAllowlistImpl,
} from "../../plugin-sdk/discord.js";
import {
createThreadDiscord as createThreadDiscordImpl,
deleteMessageDiscord as deleteMessageDiscordImpl,
editChannelDiscord as editChannelDiscordImpl,
editMessageDiscord as editMessageDiscordImpl,
pinMessageDiscord as pinMessageDiscordImpl,
sendDiscordComponentMessage as sendDiscordComponentMessageImpl,
sendMessageDiscord as sendMessageDiscordImpl,
sendPollDiscord as sendPollDiscordImpl,
sendTypingDiscord as sendTypingDiscordImpl,
unpinMessageDiscord as unpinMessageDiscordImpl,
} from "../../plugin-sdk/discord.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
type RuntimeDiscordOps = Pick<
PluginRuntimeChannel["discord"],
| "auditChannelPermissions"
| "listDirectoryGroupsLive"
| "listDirectoryPeersLive"
| "probeDiscord"
| "resolveChannelAllowlist"
| "resolveUserAllowlist"
| "sendComponentMessage"
| "sendMessageDiscord"
| "sendPollDiscord"
| "monitorDiscordProvider"
> & {
typing: Pick<PluginRuntimeChannel["discord"]["typing"], "pulse">;
conversationActions: Pick<
PluginRuntimeChannel["discord"]["conversationActions"],
"editMessage" | "deleteMessage" | "pinMessage" | "unpinMessage" | "createThread" | "editChannel"
>;
};
export const runtimeDiscordOps = {
auditChannelPermissions: auditDiscordChannelPermissionsImpl,
listDirectoryGroupsLive: listDiscordDirectoryGroupsLiveImpl,
listDirectoryPeersLive: listDiscordDirectoryPeersLiveImpl,
probeDiscord: probeDiscordImpl,
resolveChannelAllowlist: resolveDiscordChannelAllowlistImpl,
resolveUserAllowlist: resolveDiscordUserAllowlistImpl,
sendComponentMessage: sendDiscordComponentMessageImpl,
sendMessageDiscord: sendMessageDiscordImpl,
sendPollDiscord: sendPollDiscordImpl,
monitorDiscordProvider: monitorDiscordProviderImpl,
typing: {
pulse: sendTypingDiscordImpl,
},
conversationActions: {
editMessage: editMessageDiscordImpl,
deleteMessage: deleteMessageDiscordImpl,
pinMessage: pinMessageDiscordImpl,
unpinMessage: unpinMessageDiscordImpl,
createThread: createThreadDiscordImpl,
editChannel: editChannelDiscordImpl,
},
} satisfies RuntimeDiscordOps;

View File

@@ -0,0 +1,8 @@
import { monitorDiscordProvider } from "../../plugin-sdk/discord-runtime-provider.js";
import { probeDiscord } from "../../plugin-sdk/discord-runtime-provider.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
export const runtimeDiscordProvider = {
monitorDiscordProvider,
probeDiscord,
} satisfies Pick<PluginRuntimeChannel["discord"], "monitorDiscordProvider" | "probeDiscord">;

View File

@@ -0,0 +1,38 @@
import { editChannelDiscord } from "../../plugin-sdk/discord-runtime-send.js";
import { sendDiscordComponentMessage } from "../../plugin-sdk/discord-runtime-send.js";
import {
createThreadDiscord,
deleteMessageDiscord,
editMessageDiscord,
pinMessageDiscord,
unpinMessageDiscord,
} from "../../plugin-sdk/discord-runtime-send.js";
import { sendMessageDiscord, sendPollDiscord } from "../../plugin-sdk/discord-runtime-send.js";
import { sendTypingDiscord } from "../../plugin-sdk/discord-runtime-send.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
export const runtimeDiscordSend = {
sendComponentMessage: sendDiscordComponentMessage,
sendMessageDiscord,
sendPollDiscord,
typing: {
pulse: sendTypingDiscord,
},
conversationActions: {
editMessage: editMessageDiscord,
deleteMessage: deleteMessageDiscord,
pinMessage: pinMessageDiscord,
unpinMessage: unpinMessageDiscord,
createThread: createThreadDiscord,
editChannel: editChannelDiscord,
},
} satisfies Pick<
PluginRuntimeChannel["discord"],
"sendComponentMessage" | "sendMessageDiscord" | "sendPollDiscord"
> & {
typing: Pick<PluginRuntimeChannel["discord"]["typing"], "pulse">;
conversationActions: Pick<
PluginRuntimeChannel["discord"]["conversationActions"],
"editMessage" | "deleteMessage" | "pinMessage" | "unpinMessage" | "createThread" | "editChannel"
>;
};

View File

@@ -8,7 +8,7 @@ import {
setThreadBindingIdleTimeoutBySessionKey,
setThreadBindingMaxAgeBySessionKey,
unbindThreadBindingsBySessionKey,
} from "../../plugin-sdk/discord.js";
} from "../../plugin-sdk/discord-runtime-bindings.js";
import {
createLazyRuntimeMethodBinder,
createLazyRuntimeSurface,
@@ -16,63 +16,73 @@ import {
import { createDiscordTypingLease } from "./runtime-discord-typing.js";
import type { PluginRuntimeChannel } from "./types-channel.js";
const loadRuntimeDiscordOps = createLazyRuntimeSurface(
() => import("./runtime-discord-ops.runtime.js"),
({ runtimeDiscordOps }) => runtimeDiscordOps,
const loadRuntimeDiscordDirectory = createLazyRuntimeSurface(
() => import("./runtime-discord-directory.runtime.js"),
({ runtimeDiscordDirectory }) => runtimeDiscordDirectory,
);
const loadRuntimeDiscordProvider = createLazyRuntimeSurface(
() => import("./runtime-discord-provider.runtime.js"),
({ runtimeDiscordProvider }) => runtimeDiscordProvider,
);
const loadRuntimeDiscordSend = createLazyRuntimeSurface(
() => import("./runtime-discord-send.runtime.js"),
({ runtimeDiscordSend }) => runtimeDiscordSend,
);
const bindDiscordRuntimeMethod = createLazyRuntimeMethodBinder(loadRuntimeDiscordOps);
const bindDiscordDirectoryMethod = createLazyRuntimeMethodBinder(loadRuntimeDiscordDirectory);
const bindDiscordProviderMethod = createLazyRuntimeMethodBinder(loadRuntimeDiscordProvider);
const bindDiscordSendMethod = createLazyRuntimeMethodBinder(loadRuntimeDiscordSend);
const auditChannelPermissionsLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.auditChannelPermissions,
const auditChannelPermissionsLazy = bindDiscordDirectoryMethod(
(runtimeDiscordDirectory) => runtimeDiscordDirectory.auditChannelPermissions,
);
const listDirectoryGroupsLiveLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.listDirectoryGroupsLive,
const listDirectoryGroupsLiveLazy = bindDiscordDirectoryMethod(
(runtimeDiscordDirectory) => runtimeDiscordDirectory.listDirectoryGroupsLive,
);
const listDirectoryPeersLiveLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.listDirectoryPeersLive,
const listDirectoryPeersLiveLazy = bindDiscordDirectoryMethod(
(runtimeDiscordDirectory) => runtimeDiscordDirectory.listDirectoryPeersLive,
);
const probeDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.probeDiscord,
const probeDiscordLazy = bindDiscordProviderMethod(
(runtimeDiscordProvider) => runtimeDiscordProvider.probeDiscord,
);
const resolveChannelAllowlistLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.resolveChannelAllowlist,
const resolveChannelAllowlistLazy = bindDiscordDirectoryMethod(
(runtimeDiscordDirectory) => runtimeDiscordDirectory.resolveChannelAllowlist,
);
const resolveUserAllowlistLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.resolveUserAllowlist,
const resolveUserAllowlistLazy = bindDiscordDirectoryMethod(
(runtimeDiscordDirectory) => runtimeDiscordDirectory.resolveUserAllowlist,
);
const sendComponentMessageLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.sendComponentMessage,
const sendComponentMessageLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.sendComponentMessage,
);
const sendMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.sendMessageDiscord,
const sendMessageDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.sendMessageDiscord,
);
const sendPollDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.sendPollDiscord,
const sendPollDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.sendPollDiscord,
);
const monitorDiscordProviderLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.monitorDiscordProvider,
const monitorDiscordProviderLazy = bindDiscordProviderMethod(
(runtimeDiscordProvider) => runtimeDiscordProvider.monitorDiscordProvider,
);
const sendTypingDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.typing.pulse,
const sendTypingDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.typing.pulse,
);
const editMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.editMessage,
const editMessageDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.conversationActions.editMessage,
);
const deleteMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.deleteMessage,
const deleteMessageDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.conversationActions.deleteMessage,
);
const pinMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.pinMessage,
const pinMessageDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.conversationActions.pinMessage,
);
const unpinMessageDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.unpinMessage,
const unpinMessageDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.conversationActions.unpinMessage,
);
const createThreadDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.createThread,
const createThreadDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.conversationActions.createThread,
);
const editChannelDiscordLazy = bindDiscordRuntimeMethod(
(runtimeDiscordOps) => runtimeDiscordOps.conversationActions.editChannel,
const editChannelDiscordLazy = bindDiscordSendMethod(
(runtimeDiscordSend) => runtimeDiscordSend.conversationActions.editChannel,
);
export function createRuntimeDiscord(): PluginRuntimeChannel["discord"] {