Merge pull request #1208 from 24601/fix/slack-bolt-import
fix(slack): handle bolt import for CJS/ESM compatibility
This commit is contained in:
@@ -17,6 +17,7 @@ Docs: https://docs.clawd.bot
|
|||||||
- CLI: avoid duplicating --profile/--dev flags when formatting commands.
|
- CLI: avoid duplicating --profile/--dev flags when formatting commands.
|
||||||
- Auth: dedupe codex-cli profiles when tokens match custom openai-codex entries. (#1264) — thanks @odrobnik.
|
- Auth: dedupe codex-cli profiles when tokens match custom openai-codex entries. (#1264) — thanks @odrobnik.
|
||||||
- Agents: avoid misclassifying context-window-too-small errors as context overflow. (#1266) — thanks @humanwritten.
|
- Agents: avoid misclassifying context-window-too-small errors as context overflow. (#1266) — thanks @humanwritten.
|
||||||
|
- Slack: resolve Bolt default-export shapes for monitor startup. (#1208) — thanks @24601.
|
||||||
|
|
||||||
## 2026.1.19-3
|
## 2026.1.19-3
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Command } from "commander";
|
|||||||
|
|
||||||
import { callGateway } from "../gateway/call.js";
|
import { callGateway } from "../gateway/call.js";
|
||||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||||
import { defaultRuntime } from "./runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import { withProgress } from "./progress.js";
|
import { withProgress } from "./progress.js";
|
||||||
|
|
||||||
type DevicesRpcOpts = {
|
type DevicesRpcOpts = {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { DEFAULT_AGENT_MAX_CONCURRENT, DEFAULT_SUBAGENT_MAX_CONCURRENT } from "./agent-limits.js";
|
||||||
import { withTempHome } from "./test-helpers.js";
|
import { withTempHome } from "./test-helpers.js";
|
||||||
|
|
||||||
describe("config identity defaults", () => {
|
describe("config identity defaults", () => {
|
||||||
@@ -284,7 +285,7 @@ describe("config identity defaults", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("does not synthesize agent/session when absent", async () => {
|
it("does not synthesize agent list/session when absent", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const configDir = path.join(home, ".clawdbot");
|
const configDir = path.join(home, ".clawdbot");
|
||||||
await fs.mkdir(configDir, { recursive: true });
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
@@ -306,7 +307,9 @@ describe("config identity defaults", () => {
|
|||||||
|
|
||||||
expect(cfg.messages?.responsePrefix).toBeUndefined();
|
expect(cfg.messages?.responsePrefix).toBeUndefined();
|
||||||
expect(cfg.messages?.groupChat?.mentionPatterns).toBeUndefined();
|
expect(cfg.messages?.groupChat?.mentionPatterns).toBeUndefined();
|
||||||
expect(cfg.agents).toBeUndefined();
|
expect(cfg.agents?.list).toBeUndefined();
|
||||||
|
expect(cfg.agents?.defaults?.maxConcurrent).toBe(DEFAULT_AGENT_MAX_CONCURRENT);
|
||||||
|
expect(cfg.agents?.defaults?.subagents?.maxConcurrent).toBe(DEFAULT_SUBAGENT_MAX_CONCURRENT);
|
||||||
expect(cfg.session).toBeUndefined();
|
expect(cfg.session).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -39,14 +39,16 @@ export const ConnectParamsSchema = Type.Object(
|
|||||||
permissions: Type.Optional(Type.Record(NonEmptyString, Type.Boolean())),
|
permissions: Type.Optional(Type.Record(NonEmptyString, Type.Boolean())),
|
||||||
role: Type.Optional(NonEmptyString),
|
role: Type.Optional(NonEmptyString),
|
||||||
scopes: Type.Optional(Type.Array(NonEmptyString)),
|
scopes: Type.Optional(Type.Array(NonEmptyString)),
|
||||||
device: Type.Object(
|
device: Type.Optional(
|
||||||
{
|
Type.Object(
|
||||||
id: NonEmptyString,
|
{
|
||||||
publicKey: NonEmptyString,
|
id: NonEmptyString,
|
||||||
signature: NonEmptyString,
|
publicKey: NonEmptyString,
|
||||||
signedAt: Type.Integer({ minimum: 0 }),
|
signature: NonEmptyString,
|
||||||
},
|
signedAt: Type.Integer({ minimum: 0 }),
|
||||||
{ additionalProperties: false },
|
},
|
||||||
|
{ additionalProperties: false },
|
||||||
|
),
|
||||||
),
|
),
|
||||||
auth: Type.Optional(
|
auth: Type.Optional(
|
||||||
Type.Object(
|
Type.Object(
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ describe("gateway server auth/connect", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("rejects invalid token", async () => {
|
test("rejects invalid token", async () => {
|
||||||
const { server, ws, port, prevToken } = await startServerWithClient("secret");
|
const { server, ws, prevToken } = await startServerWithClient("secret");
|
||||||
const res = await connectReq(ws, { token: "wrong" });
|
const res = await connectReq(ws, { token: "wrong" });
|
||||||
expect(res.ok).toBe(false);
|
expect(res.ok).toBe(false);
|
||||||
expect(res.error?.message ?? "").toContain("unauthorized");
|
expect(res.error?.message ?? "").toContain("unauthorized");
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ describe("gateway server cron", () => {
|
|||||||
test("accepts jobId for cron.update", async () => {
|
test("accepts jobId for cron.update", async () => {
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-cron-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-gw-cron-"));
|
||||||
testState.cronStorePath = path.join(dir, "cron", "jobs.json");
|
testState.cronStorePath = path.join(dir, "cron", "jobs.json");
|
||||||
|
testState.cronEnabled = false;
|
||||||
await fs.mkdir(path.dirname(testState.cronStorePath), { recursive: true });
|
await fs.mkdir(path.dirname(testState.cronStorePath), { recursive: true });
|
||||||
await fs.writeFile(testState.cronStorePath, JSON.stringify({ version: 1, jobs: [] }));
|
await fs.writeFile(testState.cronStorePath, JSON.stringify({ version: 1, jobs: [] }));
|
||||||
|
|
||||||
@@ -218,6 +219,7 @@ describe("gateway server cron", () => {
|
|||||||
await server.close();
|
await server.close();
|
||||||
await rmTempDir(dir);
|
await rmTempDir(dir);
|
||||||
testState.cronStorePath = undefined;
|
testState.cronStorePath = undefined;
|
||||||
|
testState.cronEnabled = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
test("disables cron jobs via enabled:false patches", async () => {
|
test("disables cron jobs via enabled:false patches", async () => {
|
||||||
|
|||||||
@@ -551,7 +551,7 @@ export function attachGatewayWsMessageHandler(params: {
|
|||||||
deviceFamily: connectParams.client.deviceFamily,
|
deviceFamily: connectParams.client.deviceFamily,
|
||||||
modelIdentifier: connectParams.client.modelIdentifier,
|
modelIdentifier: connectParams.client.modelIdentifier,
|
||||||
mode: connectParams.client.mode,
|
mode: connectParams.client.mode,
|
||||||
instanceId: instanceId ?? connectParams.device?.id,
|
instanceId: connectParams.device?.id ?? instanceId,
|
||||||
reason: "connect",
|
reason: "connect",
|
||||||
});
|
});
|
||||||
incrementPresenceVersion();
|
incrementPresenceVersion();
|
||||||
|
|||||||
@@ -399,7 +399,8 @@ export async function verifyDeviceToken(params: {
|
|||||||
return { ok: false, reason: "scope-mismatch" };
|
return { ok: false, reason: "scope-mismatch" };
|
||||||
}
|
}
|
||||||
entry.lastUsedAtMs = Date.now();
|
entry.lastUsedAtMs = Date.now();
|
||||||
device.tokens = { ...(device.tokens ?? {}), [role]: entry };
|
device.tokens ??= {};
|
||||||
|
device.tokens[role] = entry;
|
||||||
state.pairedByDeviceId[device.deviceId] = device;
|
state.pairedByDeviceId[device.deviceId] = device;
|
||||||
await persistState(state, params.baseDir);
|
await persistState(state, params.baseDir);
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ vi.mock("@slack/bolt", () => {
|
|||||||
start = vi.fn().mockResolvedValue(undefined);
|
start = vi.fn().mockResolvedValue(undefined);
|
||||||
stop = vi.fn().mockResolvedValue(undefined);
|
stop = vi.fn().mockResolvedValue(undefined);
|
||||||
}
|
}
|
||||||
return { App, default: { App } };
|
class HTTPReceiver {
|
||||||
|
requestListener = vi.fn();
|
||||||
|
}
|
||||||
|
return { App, HTTPReceiver, default: { App, HTTPReceiver } };
|
||||||
});
|
});
|
||||||
|
|
||||||
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ vi.mock("@slack/bolt", () => {
|
|||||||
start = vi.fn().mockResolvedValue(undefined);
|
start = vi.fn().mockResolvedValue(undefined);
|
||||||
stop = vi.fn().mockResolvedValue(undefined);
|
stop = vi.fn().mockResolvedValue(undefined);
|
||||||
}
|
}
|
||||||
return { App, default: { App } };
|
class HTTPReceiver {
|
||||||
|
requestListener = vi.fn();
|
||||||
|
}
|
||||||
|
return { App, HTTPReceiver, default: { App, HTTPReceiver } };
|
||||||
});
|
});
|
||||||
|
|
||||||
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ vi.mock("@slack/bolt", () => {
|
|||||||
start = vi.fn().mockResolvedValue(undefined);
|
start = vi.fn().mockResolvedValue(undefined);
|
||||||
stop = vi.fn().mockResolvedValue(undefined);
|
stop = vi.fn().mockResolvedValue(undefined);
|
||||||
}
|
}
|
||||||
return { App, default: { App } };
|
class HTTPReceiver {
|
||||||
|
requestListener = vi.fn();
|
||||||
|
}
|
||||||
|
return { App, HTTPReceiver, default: { App, HTTPReceiver } };
|
||||||
});
|
});
|
||||||
|
|
||||||
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||||
|
|
||||||
import SlackBolt from "@slack/bolt";
|
import SlackBoltDefault, * as SlackBoltModule from "@slack/bolt";
|
||||||
|
|
||||||
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
import { resolveTextChunkLimit } from "../../auto-reply/chunk.js";
|
||||||
import { DEFAULT_GROUP_HISTORY_LIMIT } from "../../auto-reply/reply/history.js";
|
import { DEFAULT_GROUP_HISTORY_LIMIT } from "../../auto-reply/reply/history.js";
|
||||||
@@ -26,12 +26,24 @@ import { normalizeAllowList } from "./allow-list.js";
|
|||||||
|
|
||||||
import type { MonitorSlackOpts } from "./types.js";
|
import type { MonitorSlackOpts } from "./types.js";
|
||||||
|
|
||||||
const slackBoltModule = SlackBolt as typeof import("@slack/bolt") & {
|
type SlackBoltNamespace = typeof import("@slack/bolt");
|
||||||
default?: typeof import("@slack/bolt");
|
type SlackBoltDefault = SlackBoltNamespace | SlackBoltNamespace["App"];
|
||||||
};
|
|
||||||
// Bun allows named imports from CJS; Node ESM doesn't. Use default+fallback for compatibility.
|
const slackBoltDefaultImport = SlackBoltDefault as SlackBoltDefault | undefined;
|
||||||
const slackBolt = slackBoltModule.default ?? slackBoltModule;
|
const slackBoltModuleDefault = (SlackBoltModule as { default?: SlackBoltDefault }).default;
|
||||||
const { App, HTTPReceiver } = slackBolt;
|
const slackBoltDefault = slackBoltDefaultImport ?? slackBoltModuleDefault;
|
||||||
|
const slackBoltNamespace =
|
||||||
|
typeof slackBoltDefault === "object" && slackBoltDefault
|
||||||
|
? (slackBoltDefault as SlackBoltNamespace)
|
||||||
|
: typeof slackBoltModuleDefault === "object" && slackBoltModuleDefault
|
||||||
|
? (slackBoltModuleDefault as SlackBoltNamespace)
|
||||||
|
: undefined;
|
||||||
|
// Bun allows named imports from CJS; Node ESM doesn't. Resolve default/module shapes for compatibility.
|
||||||
|
const App = ((typeof slackBoltDefault === "function" ? slackBoltDefault : undefined) ??
|
||||||
|
slackBoltNamespace?.App ??
|
||||||
|
SlackBoltModule.App) as SlackBoltNamespace["App"];
|
||||||
|
const HTTPReceiver = (slackBoltNamespace?.HTTPReceiver ??
|
||||||
|
SlackBoltModule.HTTPReceiver) as SlackBoltNamespace["HTTPReceiver"];
|
||||||
function parseApiAppIdFromAppToken(raw?: string) {
|
function parseApiAppIdFromAppToken(raw?: string) {
|
||||||
const token = raw?.trim();
|
const token = raw?.trim();
|
||||||
if (!token) return undefined;
|
if (!token) return undefined;
|
||||||
@@ -121,6 +133,13 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
|||||||
const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
|
const mediaMaxBytes = (opts.mediaMaxMb ?? slackCfg.mediaMaxMb ?? 20) * 1024 * 1024;
|
||||||
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
|
|
||||||
|
if (!App) {
|
||||||
|
throw new Error("Slack Bolt App export missing; check @slack/bolt installation.");
|
||||||
|
}
|
||||||
|
if (slackMode === "http" && !HTTPReceiver) {
|
||||||
|
throw new Error("Slack Bolt HTTPReceiver export missing; check @slack/bolt installation.");
|
||||||
|
}
|
||||||
|
|
||||||
const receiver =
|
const receiver =
|
||||||
slackMode === "http"
|
slackMode === "http"
|
||||||
? new HTTPReceiver({
|
? new HTTPReceiver({
|
||||||
|
|||||||
Reference in New Issue
Block a user