mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
fix(slack): harden bolt import interop (#45953)
* fix(slack): harden bolt import interop * fix(slack): simplify bolt interop resolver * fix(slack): harden startup bolt interop * fix(slack): place changelog entry at section end --------- Co-authored-by: Ubuntu <ubuntu@vps-1c82b947.vps.ovh.net> Co-authored-by: Altay <altay@uinaf.dev>
This commit is contained in:
committed by
GitHub
parent
7d4ccee717
commit
80bef826f8
@@ -93,6 +93,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Nodes/pending actions: re-check queued foreground actions against the current node command policy before returning them to the node. (#46815) Thanks @zpbrent and @vincentkoc.
|
||||
- Node/startup: remove leftover debug `console.log("node host PATH: ...")` that printed the resolved PATH on every `openclaw node run` invocation. (#46411)
|
||||
- CLI/completion: reduce recursive completion-script string churn and fix nested PowerShell command-path matching so generated nested completions resolve on PowerShell too. (#45537) Thanks @yiShanXin and @vincentkoc.
|
||||
- Slack/startup: harden `@slack/bolt` import interop across current bundled runtime shapes so Slack monitors no longer crash with `App is not a constructor` after plugin-sdk bundling changes. (#45953) thanks @merc1305.
|
||||
|
||||
## 2026.3.13
|
||||
|
||||
|
||||
94
extensions/slack/src/monitor/provider.interop.test.ts
Normal file
94
extensions/slack/src/monitor/provider.interop.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __testing } from "./provider.js";
|
||||
|
||||
describe("resolveSlackBoltInterop", () => {
|
||||
class FakeApp {}
|
||||
class FakeHTTPReceiver {}
|
||||
|
||||
it("uses the default import when it already exposes named exports", () => {
|
||||
const resolved = __testing.resolveSlackBoltInterop({
|
||||
defaultImport: {
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
},
|
||||
namespaceImport: {},
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses nested default export when the default import is a wrapper object", () => {
|
||||
const resolved = __testing.resolveSlackBoltInterop({
|
||||
defaultImport: {
|
||||
default: {
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
},
|
||||
},
|
||||
namespaceImport: {},
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the namespace receiver when the default import is the App constructor itself", () => {
|
||||
const resolved = __testing.resolveSlackBoltInterop({
|
||||
defaultImport: FakeApp,
|
||||
namespaceImport: {
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
});
|
||||
});
|
||||
|
||||
it("uses namespace.default when it exposes named exports", () => {
|
||||
const resolved = __testing.resolveSlackBoltInterop({
|
||||
defaultImport: undefined,
|
||||
namespaceImport: {
|
||||
default: {
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the namespace import when it exposes named exports", () => {
|
||||
const resolved = __testing.resolveSlackBoltInterop({
|
||||
defaultImport: undefined,
|
||||
namespaceImport: {
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved).toEqual({
|
||||
App: FakeApp,
|
||||
HTTPReceiver: FakeHTTPReceiver,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws when the module cannot be resolved", () => {
|
||||
expect(() =>
|
||||
__testing.resolveSlackBoltInterop({
|
||||
defaultImport: null,
|
||||
namespaceImport: {},
|
||||
}),
|
||||
).toThrow("Unable to resolve @slack/bolt App/HTTPReceiver exports");
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import SlackBolt from "@slack/bolt";
|
||||
import SlackBolt, * as SlackBoltNamespace from "@slack/bolt";
|
||||
import { resolveTextChunkLimit } from "../../../../src/auto-reply/chunk.js";
|
||||
import { DEFAULT_GROUP_HISTORY_LIMIT } from "../../../../src/auto-reply/reply/history.js";
|
||||
import {
|
||||
@@ -46,14 +46,77 @@ import {
|
||||
import { registerSlackMonitorSlashCommands } from "./slash.js";
|
||||
import type { MonitorSlackOpts } from "./types.js";
|
||||
|
||||
const slackBoltModule = SlackBolt as typeof import("@slack/bolt") & {
|
||||
default?: typeof import("@slack/bolt");
|
||||
type SlackAppConstructor = typeof import("@slack/bolt").App;
|
||||
type SlackHttpReceiverConstructor = typeof import("@slack/bolt").HTTPReceiver;
|
||||
type SlackBoltResolvedExports = {
|
||||
App: SlackAppConstructor;
|
||||
HTTPReceiver: SlackHttpReceiverConstructor;
|
||||
};
|
||||
// Bun allows named imports from CJS; Node ESM doesn't. Use default+fallback for compatibility.
|
||||
// Fix: Check if module has App property directly (Node 25.x ESM/CJS compat issue)
|
||||
const slackBolt =
|
||||
(slackBoltModule.App ? slackBoltModule : slackBoltModule.default) ?? slackBoltModule;
|
||||
const { App, HTTPReceiver } = slackBolt;
|
||||
type Constructor = abstract new (...args: never[]) => unknown;
|
||||
|
||||
function isConstructorFunction<T extends Constructor>(value: unknown): value is T {
|
||||
return typeof value === "function";
|
||||
}
|
||||
|
||||
function resolveSlackBoltModule(value: unknown): SlackBoltResolvedExports | null {
|
||||
if (!value || typeof value !== "object") {
|
||||
return null;
|
||||
}
|
||||
const app = Reflect.get(value, "App");
|
||||
const httpReceiver = Reflect.get(value, "HTTPReceiver");
|
||||
if (
|
||||
!isConstructorFunction<SlackAppConstructor>(app) ||
|
||||
!isConstructorFunction<SlackHttpReceiverConstructor>(httpReceiver)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
App: app,
|
||||
HTTPReceiver: httpReceiver,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveSlackBoltInterop(params: {
|
||||
defaultImport: unknown;
|
||||
namespaceImport: unknown;
|
||||
}): SlackBoltResolvedExports {
|
||||
const { defaultImport, namespaceImport } = params;
|
||||
const nestedDefault =
|
||||
defaultImport && typeof defaultImport === "object"
|
||||
? Reflect.get(defaultImport, "default")
|
||||
: undefined;
|
||||
const namespaceDefault =
|
||||
namespaceImport && typeof namespaceImport === "object"
|
||||
? Reflect.get(namespaceImport, "default")
|
||||
: undefined;
|
||||
const namespaceReceiver =
|
||||
namespaceImport && typeof namespaceImport === "object"
|
||||
? Reflect.get(namespaceImport, "HTTPReceiver")
|
||||
: undefined;
|
||||
const directModule =
|
||||
resolveSlackBoltModule(defaultImport) ??
|
||||
resolveSlackBoltModule(nestedDefault) ??
|
||||
resolveSlackBoltModule(namespaceDefault) ??
|
||||
resolveSlackBoltModule(namespaceImport);
|
||||
if (directModule) {
|
||||
return directModule;
|
||||
}
|
||||
if (
|
||||
isConstructorFunction<SlackAppConstructor>(defaultImport) &&
|
||||
isConstructorFunction<SlackHttpReceiverConstructor>(namespaceReceiver)
|
||||
) {
|
||||
return {
|
||||
App: defaultImport,
|
||||
HTTPReceiver: namespaceReceiver,
|
||||
};
|
||||
}
|
||||
throw new TypeError("Unable to resolve @slack/bolt App/HTTPReceiver exports");
|
||||
}
|
||||
|
||||
const { App, HTTPReceiver } = resolveSlackBoltInterop({
|
||||
defaultImport: SlackBolt,
|
||||
namespaceImport: SlackBoltNamespace,
|
||||
});
|
||||
|
||||
const SLACK_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
|
||||
const SLACK_WEBHOOK_BODY_TIMEOUT_MS = 30_000;
|
||||
@@ -515,6 +578,7 @@ export const __testing = {
|
||||
publishSlackDisconnectedStatus,
|
||||
resolveSlackRuntimeGroupPolicy: resolveOpenProviderRuntimeGroupPolicy,
|
||||
resolveDefaultGroupPolicy,
|
||||
resolveSlackBoltInterop,
|
||||
getSocketEmitter,
|
||||
waitForSlackSocketDisconnect,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user