From 601a052216d90a2ca8acbc13f1a839c449db2e3c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Jan 2026 19:33:58 +0000 Subject: [PATCH] fix: unblock bundled plugin load --- src/agents/cli-credentials.test.ts | 1 + src/agents/subagent-announce.format.test.ts | 1 + src/cli/pairing-cli.test.ts | 13 +++++ src/commands/health.snapshot.test.ts | 1 + src/commands/status.test.ts | 1 + ...essages-without-mention-by-default.test.ts | 1 + ...last-route-chat-id-direct-messages.test.ts | 1 + src/logging.ts | 52 ++++++++++++++++--- src/logging/console.ts | 20 +++++-- src/logging/logger.ts | 20 +++++-- src/logging/redact.ts | 16 +++++- src/plugins/loader.test.ts | 20 +++++++ ...-only-senders-uuid-allowlist-entry.test.ts | 1 + ...ends-tool-summaries-responseprefix.test.ts | 1 + ...es-thread-replies-replytoid-is-set.test.ts | 1 + ...ends-tool-summaries-responseprefix.test.ts | 1 + ...p-level-replies-replytomode-is-all.test.ts | 1 + 17 files changed, 136 insertions(+), 16 deletions(-) diff --git a/src/agents/cli-credentials.test.ts b/src/agents/cli-credentials.test.ts index 712058af4..e63c88b2a 100644 --- a/src/agents/cli-credentials.test.ts +++ b/src/agents/cli-credentials.test.ts @@ -8,6 +8,7 @@ const execSyncMock = vi.fn(); describe("cli credentials", () => { beforeEach(() => { + vi.resetModules(); vi.useFakeTimers(); }); diff --git a/src/agents/subagent-announce.format.test.ts b/src/agents/subagent-announce.format.test.ts index c2b11984b..374a833fa 100644 --- a/src/agents/subagent-announce.format.test.ts +++ b/src/agents/subagent-announce.format.test.ts @@ -39,6 +39,7 @@ vi.mock("../config/sessions.js", () => ({ resolveAgentIdFromSessionKey: () => "main", resolveStorePath: () => "/tmp/sessions.json", resolveMainSessionKey: () => "agent:main:main", + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/cli/pairing-cli.test.ts b/src/cli/pairing-cli.test.ts index 00fae9d30..9bbe0e3f2 100644 --- a/src/cli/pairing-cli.test.ts +++ b/src/cli/pairing-cli.test.ts @@ -39,6 +39,19 @@ vi.mock("../config/config.js", () => ({ })); describe("pairing cli", () => { + it("evaluates pairing channels when registering the CLI (not at import)", async () => { + listPairingChannels.mockClear(); + + const { registerPairingCli } = await import("./pairing-cli.js"); + expect(listPairingChannels).not.toHaveBeenCalled(); + + const program = new Command(); + program.name("test"); + registerPairingCli(program); + + expect(listPairingChannels).toHaveBeenCalledTimes(1); + }); + it("labels Telegram ids as telegramUserId", async () => { const { registerPairingCli } = await import("./pairing-cli.js"); listChannelPairingRequests.mockResolvedValueOnce([ diff --git a/src/commands/health.snapshot.test.ts b/src/commands/health.snapshot.test.ts index e9fae83a2..c2156c9a9 100644 --- a/src/commands/health.snapshot.test.ts +++ b/src/commands/health.snapshot.test.ts @@ -24,6 +24,7 @@ vi.mock("../config/config.js", async (importOriginal) => { vi.mock("../config/sessions.js", () => ({ resolveStorePath: () => "/tmp/sessions.json", loadSessionStore: () => testStore, + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), updateLastRoute: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index e80c0d84f..01babf1cb 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -100,6 +100,7 @@ vi.mock("../config/sessions.js", () => ({ loadSessionStore: mocks.loadSessionStore, resolveMainSessionKey: mocks.resolveMainSessionKey, resolveStorePath: mocks.resolveStorePath, + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); vi.mock("../channels/plugins/index.js", () => ({ diff --git a/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts b/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts index e8a72f836..31af2eb8d 100644 --- a/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts +++ b/src/imessage/monitor.skips-group-messages-without-mention-by-default.test.ts @@ -38,6 +38,7 @@ vi.mock("../pairing/pairing-store.js", () => ({ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts b/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts index 194437544..4fb065b66 100644 --- a/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts +++ b/src/imessage/monitor.updates-last-route-chat-id-direct-messages.test.ts @@ -38,6 +38,7 @@ vi.mock("../pairing/pairing-store.js", () => ({ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/logging.ts b/src/logging.ts index 7fe87c1d2..ed7a11167 100644 --- a/src/logging.ts +++ b/src/logging.ts @@ -1,4 +1,4 @@ -export { +import { enableConsoleCapture, getConsoleSettings, getResolvedConsoleSettings, @@ -7,10 +7,10 @@ export { setConsoleTimestampPrefix, shouldLogSubsystemToConsole, } from "./logging/console.js"; -export type { ConsoleLoggerSettings, ConsoleStyle } from "./logging/console.js"; -export type { LogLevel } from "./logging/levels.js"; -export { ALLOWED_LOG_LEVELS, levelToMinLevel, normalizeLogLevel } from "./logging/levels.js"; -export { +import type { ConsoleLoggerSettings, ConsoleStyle } from "./logging/console.js"; +import { ALLOWED_LOG_LEVELS, levelToMinLevel, normalizeLogLevel } from "./logging/levels.js"; +import type { LogLevel } from "./logging/levels.js"; +import { DEFAULT_LOG_DIR, DEFAULT_LOG_FILE, getChildLogger, @@ -21,11 +21,47 @@ export { setLoggerOverride, toPinoLikeLogger, } from "./logging/logger.js"; -export type { LoggerResolvedSettings, LoggerSettings, PinoLikeLogger } from "./logging/logger.js"; -export { +import type { LoggerResolvedSettings, LoggerSettings, PinoLikeLogger } from "./logging/logger.js"; +import { createSubsystemLogger, createSubsystemRuntime, runtimeForLogger, stripRedundantSubsystemPrefixForConsole, } from "./logging/subsystem.js"; -export type { SubsystemLogger } from "./logging/subsystem.js"; +import type { SubsystemLogger } from "./logging/subsystem.js"; + +export { + enableConsoleCapture, + getConsoleSettings, + getResolvedConsoleSettings, + routeLogsToStderr, + setConsoleSubsystemFilter, + setConsoleTimestampPrefix, + shouldLogSubsystemToConsole, + ALLOWED_LOG_LEVELS, + levelToMinLevel, + normalizeLogLevel, + DEFAULT_LOG_DIR, + DEFAULT_LOG_FILE, + getChildLogger, + getLogger, + getResolvedLoggerSettings, + isFileLogLevelEnabled, + resetLogger, + setLoggerOverride, + toPinoLikeLogger, + createSubsystemLogger, + createSubsystemRuntime, + runtimeForLogger, + stripRedundantSubsystemPrefixForConsole, +}; + +export type { + ConsoleLoggerSettings, + ConsoleStyle, + LogLevel, + LoggerResolvedSettings, + LoggerSettings, + PinoLikeLogger, + SubsystemLogger, +}; diff --git a/src/logging/console.ts b/src/logging/console.ts index 92f4a4235..320e7ce63 100644 --- a/src/logging/console.ts +++ b/src/logging/console.ts @@ -1,6 +1,7 @@ +import { createRequire } from "node:module"; import util from "node:util"; -import { type ClawdbotConfig, loadConfig } from "../config/config.js"; +import type { ClawdbotConfig } from "../config/config.js"; import { isVerbose } from "../globals.js"; import { stripAnsi } from "../terminal/ansi.js"; import { type LogLevel, normalizeLogLevel } from "./levels.js"; @@ -14,6 +15,8 @@ type ConsoleSettings = { }; export type ConsoleLoggerSettings = ConsoleSettings; +const requireConfig = createRequire(import.meta.url); + function normalizeConsoleLevel(level?: string): LogLevel { if (isVerbose()) return "debug"; return normalizeLogLevel(level, "info"); @@ -28,8 +31,19 @@ function normalizeConsoleStyle(style?: string): ConsoleStyle { } function resolveConsoleSettings(): ConsoleSettings { - const cfg: ClawdbotConfig["logging"] | undefined = - (loggingState.overrideSettings as LoggerSettings | null) ?? loadConfig().logging; + let cfg: ClawdbotConfig["logging"] | undefined; + if (loggingState.overrideSettings) { + cfg = loggingState.overrideSettings as LoggerSettings; + } else { + try { + const loaded = requireConfig("../config/config.js") as { + loadConfig?: () => ClawdbotConfig; + }; + cfg = loaded.loadConfig?.().logging; + } catch { + cfg = undefined; + } + } const level = normalizeConsoleLevel(cfg?.consoleLevel); const style = normalizeConsoleStyle(cfg?.consoleStyle); return { level, style }; diff --git a/src/logging/logger.ts b/src/logging/logger.ts index 84ad865ad..31c61d19a 100644 --- a/src/logging/logger.ts +++ b/src/logging/logger.ts @@ -1,9 +1,10 @@ +import { createRequire } from "node:module"; import fs from "node:fs"; import path from "node:path"; import { Logger as TsLogger } from "tslog"; -import { type ClawdbotConfig, loadConfig } from "../config/config.js"; +import type { ClawdbotConfig } from "../config/config.js"; import type { ConsoleStyle } from "./console.js"; import { type LogLevel, levelToMinLevel, normalizeLogLevel } from "./levels.js"; import { loggingState } from "./state.js"; @@ -17,6 +18,8 @@ const LOG_PREFIX = "clawdbot"; const LOG_SUFFIX = ".log"; const MAX_LOG_AGE_MS = 24 * 60 * 60 * 1000; // 24h +const requireConfig = createRequire(import.meta.url); + export type LoggerSettings = { level?: LogLevel; file?: string; @@ -33,8 +36,19 @@ type ResolvedSettings = { export type LoggerResolvedSettings = ResolvedSettings; function resolveSettings(): ResolvedSettings { - const cfg: ClawdbotConfig["logging"] | undefined = - (loggingState.overrideSettings as LoggerSettings | null) ?? loadConfig().logging; + let cfg: ClawdbotConfig["logging"] | undefined; + if (loggingState.overrideSettings) { + cfg = loggingState.overrideSettings as LoggerSettings; + } else { + try { + const loaded = requireConfig("../config/config.js") as { + loadConfig?: () => ClawdbotConfig; + }; + cfg = loaded.loadConfig?.().logging; + } catch { + cfg = undefined; + } + } const level = normalizeLogLevel(cfg?.level, "info"); const file = cfg?.file ?? defaultRollingPathForToday(); return { level, file }; diff --git a/src/logging/redact.ts b/src/logging/redact.ts index 3cff7cffb..9486751e2 100644 --- a/src/logging/redact.ts +++ b/src/logging/redact.ts @@ -1,4 +1,8 @@ -import { loadConfig } from "../config/config.js"; +import { createRequire } from "node:module"; + +import type { ClawdbotConfig } from "../config/config.js"; + +const requireConfig = createRequire(import.meta.url); export type RedactSensitiveMode = "off" | "tools"; @@ -93,7 +97,15 @@ function redactText(text: string, patterns: RegExp[]): string { } function resolveConfigRedaction(): RedactOptions { - const cfg = loadConfig().logging; + let cfg: ClawdbotConfig["logging"] | undefined; + try { + const loaded = requireConfig("../config/config.js") as { + loadConfig?: () => ClawdbotConfig; + }; + cfg = loaded.loadConfig?.().logging; + } catch { + cfg = undefined; + } return { mode: normalizeMode(cfg?.redactSensitive), patterns: cfg?.redactPatterns, diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index 03eec4d1a..94f03b208 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -75,6 +75,26 @@ describe("loadClawdbotPlugins", () => { expect(enabled?.status).toBe("loaded"); }); + it("loads bundled telegram plugin when enabled", () => { + process.env.CLAWDBOT_BUNDLED_PLUGINS_DIR = path.join(process.cwd(), "extensions"); + + const registry = loadClawdbotPlugins({ + cache: false, + config: { + plugins: { + allow: ["telegram"], + entries: { + telegram: { enabled: true }, + }, + }, + }, + }); + + const telegram = registry.plugins.find((entry) => entry.id === "telegram"); + expect(telegram?.status).toBe("loaded"); + expect(registry.channels.some((entry) => entry.plugin.id === "telegram")).toBe(true); + }); + it("enables bundled memory plugin when selected by slot", () => { const bundledDir = makeTempDir(); const bundledPath = path.join(bundledDir, "memory-core.ts"); diff --git a/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.test.ts b/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.test.ts index 65fc3eabc..60852d9e9 100644 --- a/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.test.ts +++ b/src/signal/monitor.tool-result.pairs-uuid-only-senders-uuid-allowlist-entry.test.ts @@ -35,6 +35,7 @@ vi.mock("../pairing/pairing-store.js", () => ({ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index 859cc4ccd..24c4ffcef 100644 --- a/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/src/signal/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -39,6 +39,7 @@ vi.mock("../pairing/pairing-store.js", () => ({ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts b/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts index c2c224a76..8912a4821 100644 --- a/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts +++ b/src/slack/monitor.tool-result.forces-thread-replies-replytoid-is-set.test.ts @@ -54,6 +54,7 @@ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), resolveSessionKey: vi.fn(), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts b/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts index 90bd3df96..70527bfd5 100644 --- a/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts +++ b/src/slack/monitor.tool-result.sends-tool-summaries-responseprefix.test.ts @@ -56,6 +56,7 @@ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), resolveSessionKey: vi.fn(), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), })); diff --git a/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts b/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts index 80c5c273c..35bcff5fe 100644 --- a/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts +++ b/src/slack/monitor.tool-result.threads-top-level-replies-replytomode-is-all.test.ts @@ -54,6 +54,7 @@ vi.mock("../config/sessions.js", () => ({ resolveStorePath: vi.fn(() => "/tmp/clawdbot-sessions.json"), updateLastRoute: (...args: unknown[]) => updateLastRouteMock(...args), resolveSessionKey: vi.fn(), + readSessionUpdatedAt: vi.fn(() => undefined), recordSessionMetaFromInbound: vi.fn().mockResolvedValue(undefined), }));