diff --git a/src/gateway/server.ts b/src/gateway/server.ts index 19741db8463..6bade5e6cb3 100644 --- a/src/gateway/server.ts +++ b/src/gateway/server.ts @@ -75,6 +75,7 @@ import { } from "../discord/index.js"; import { type DiscordProbe, probeDiscord } from "../discord/probe.js"; import { isVerbose } from "../globals.js"; +import { startGmailWatcher, stopGmailWatcher } from "../hooks/gmail-watcher.js"; import { monitorIMessageProvider, sendMessageIMessage, @@ -172,10 +173,6 @@ import { type HookMappingResolved, resolveHookMappings, } from "./hooks-mapping.js"; -import { - startGmailWatcher, - stopGmailWatcher, -} from "../hooks/gmail-watcher.js"; ensureClawdisCliOnPath(); @@ -6701,7 +6698,11 @@ export async function startGatewayServer( const gmailResult = await startGmailWatcher(cfgAtStart); if (gmailResult.started) { logHooks.info("gmail watcher started"); - } else if (gmailResult.reason && gmailResult.reason !== "hooks not enabled" && gmailResult.reason !== "no gmail account configured") { + } else if ( + gmailResult.reason && + gmailResult.reason !== "hooks not enabled" && + gmailResult.reason !== "no gmail account configured" + ) { logHooks.warn(`gmail watcher not started: ${gmailResult.reason}`); } } catch (err) { diff --git a/src/hooks/gmail-watcher.test.ts b/src/hooks/gmail-watcher.test.ts index 873c47bff95..cc66f8b762e 100644 --- a/src/hooks/gmail-watcher.test.ts +++ b/src/hooks/gmail-watcher.test.ts @@ -1,5 +1,5 @@ -import { EventEmitter } from "node:events"; import type { ChildProcess } from "node:child_process"; +import { EventEmitter } from "node:events"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const runtimeConfig = { @@ -27,9 +27,10 @@ class MockChild extends EventEmitter { const spawnMock = vi.fn(() => new MockChild() as unknown as ChildProcess); vi.mock("node:child_process", async () => { - const actual = await vi.importActual( - "node:child_process", - ); + const actual = + await vi.importActual( + "node:child_process", + ); return { ...actual, spawn: spawnMock, diff --git a/src/hooks/gmail-watcher.ts b/src/hooks/gmail-watcher.ts index ef51bcab5cb..3ca5434d01a 100644 --- a/src/hooks/gmail-watcher.ts +++ b/src/hooks/gmail-watcher.ts @@ -5,7 +5,7 @@ * if hooks.gmail is configured with an account. */ -import { spawn, type ChildProcess } from "node:child_process"; +import { type ChildProcess, spawn } from "node:child_process"; import { hasBinary } from "../agents/skills.js"; import type { ClawdisConfig } from "../config/config.js"; import { createSubsystemLogger } from "../logging.js"; @@ -13,8 +13,8 @@ import { runCommandWithTimeout } from "../process/exec.js"; import { buildGogWatchServeArgs, buildGogWatchStartArgs, - resolveGmailHookRuntimeConfig, type GmailHookRuntimeConfig, + resolveGmailHookRuntimeConfig, } from "./gmail.js"; import { ensureTailscaleEndpoint } from "./gmail-setup-utils.js"; @@ -66,7 +66,8 @@ async function startGmailWatch( try { const result = await runCommandWithTimeout(args, { timeoutMs: 120_000 }); if (result.code !== 0) { - const message = result.stderr || result.stdout || "gog watch start failed"; + const message = + result.stderr || result.stdout || "gog watch start failed"; log.error(`watch start failed: ${message}`); return false; } @@ -189,7 +190,10 @@ export async function startGmailWatcher( ); } catch (err) { log.error(`tailscale setup failed: ${String(err)}`); - return { started: false, reason: `tailscale setup failed: ${String(err)}` }; + return { + started: false, + reason: `tailscale setup failed: ${String(err)}`, + }; } } @@ -236,7 +240,7 @@ export async function stopGmailWatcher(): Promise { if (watcherProcess) { log.info("stopping gmail watcher"); watcherProcess.kill("SIGTERM"); - + // Wait a bit for graceful shutdown await new Promise((resolve) => { const timeout = setTimeout(() => { @@ -264,8 +268,6 @@ export async function stopGmailWatcher(): Promise { */ export function isGmailWatcherRunning(): boolean { return ( - watcherProcess !== null && - !shuttingDown && - watcherProcess.exitCode === null + watcherProcess !== null && !shuttingDown && watcherProcess.exitCode === null ); } diff --git a/src/web/media.test.ts b/src/web/media.test.ts index a1618d5b552..e5a6f394c28 100644 --- a/src/web/media.test.ts +++ b/src/web/media.test.ts @@ -80,12 +80,34 @@ describe("web media loading", () => { // Create a minimal valid GIF (1x1 pixel) // GIF89a header + minimal image data const gifBuffer = Buffer.from([ - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, // GIF89a - 0x01, 0x00, 0x01, 0x00, // 1x1 dimensions - 0x00, 0x00, 0x00, // no global color table - 0x2c, 0x00, 0x00, 0x00, 0x00, // image descriptor - 0x01, 0x00, 0x01, 0x00, 0x00, // 1x1 image - 0x02, 0x01, 0x44, 0x00, 0x3b, // minimal LZW data + trailer + 0x47, + 0x49, + 0x46, + 0x38, + 0x39, + 0x61, // GIF89a + 0x01, + 0x00, + 0x01, + 0x00, // 1x1 dimensions + 0x00, + 0x00, + 0x00, // no global color table + 0x2c, + 0x00, + 0x00, + 0x00, + 0x00, // image descriptor + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, // 1x1 image + 0x02, + 0x01, + 0x44, + 0x00, + 0x3b, // minimal LZW data + trailer ]); const file = path.join(os.tmpdir(), `clawdis-media-${Date.now()}.gif`); @@ -102,18 +124,19 @@ describe("web media loading", () => { it("preserves GIF from URL without JPEG conversion", async () => { const gifBytes = new Uint8Array([ - 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, - 0x01, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x00, 0x00, - 0x02, 0x01, 0x44, 0x00, 0x3b, + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, + 0x01, 0x44, 0x00, 0x3b, ]); const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValueOnce({ ok: true, body: true, - arrayBuffer: async () => gifBytes.buffer.slice(gifBytes.byteOffset, gifBytes.byteOffset + gifBytes.byteLength), + arrayBuffer: async () => + gifBytes.buffer.slice( + gifBytes.byteOffset, + gifBytes.byteOffset + gifBytes.byteLength, + ), headers: { get: () => "image/gif" }, status: 200, } as Response);