fix: handle Slack Bolt import + gateway node ids (#1208) (thanks @24601)

This commit is contained in:
Peter Steinberger
2026-01-20 10:31:44 +00:00
parent a6db1edee3
commit 115b4379bf
4 changed files with 32 additions and 19 deletions

View File

@@ -16,6 +16,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

View File

@@ -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(

View File

@@ -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 () => {

View File

@@ -27,20 +27,21 @@ import { normalizeAllowList } from "./allow-list.js";
import type { MonitorSlackOpts } from "./types.js"; import type { MonitorSlackOpts } from "./types.js";
type SlackBoltNamespace = typeof import("@slack/bolt"); type SlackBoltNamespace = typeof import("@slack/bolt");
type SlackBoltDefault = SlackBoltNamespace | SlackBoltNamespace["App"];
const slackBoltDefault = SlackBoltDefault as unknown; const slackBoltDefaultImport = SlackBoltDefault as SlackBoltDefault | undefined;
const slackBoltModuleDefault = (SlackBoltModule as { default?: SlackBoltDefault }).default;
const slackBoltDefault = slackBoltDefaultImport ?? slackBoltModuleDefault;
const slackBoltNamespace = const slackBoltNamespace =
(typeof slackBoltDefault === "object" && slackBoltDefault typeof slackBoltDefault === "object" && slackBoltDefault
? ("default" in slackBoltDefault ? (slackBoltDefault as SlackBoltNamespace)
? (slackBoltDefault as { default?: unknown }).default : typeof slackBoltModuleDefault === "object" && slackBoltModuleDefault
: slackBoltDefault) ? (slackBoltModuleDefault as SlackBoltNamespace)
: undefined) as SlackBoltNamespace | undefined; : undefined;
// Bun allows named imports from CJS; Node ESM doesn't. Resolve default/module shapes for compatibility. // Bun allows named imports from CJS; Node ESM doesn't. Resolve default/module shapes for compatibility.
const App = const App = ((typeof slackBoltDefault === "function" ? slackBoltDefault : undefined) ??
((typeof slackBoltDefault === "function" slackBoltNamespace?.App ??
? slackBoltDefault SlackBoltModule.App) as SlackBoltNamespace["App"];
: slackBoltNamespace?.App) ??
SlackBoltModule.App) as SlackBoltNamespace["App"];
const HTTPReceiver = (slackBoltNamespace?.HTTPReceiver ?? const HTTPReceiver = (slackBoltNamespace?.HTTPReceiver ??
SlackBoltModule.HTTPReceiver) as SlackBoltNamespace["HTTPReceiver"]; SlackBoltModule.HTTPReceiver) as SlackBoltNamespace["HTTPReceiver"];
function parseApiAppIdFromAppToken(raw?: string) { function parseApiAppIdFromAppToken(raw?: string) {
@@ -132,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({