diff --git a/src/config/types.imessage.ts b/src/config/types.imessage.ts index 3372719cd..0be92fcb7 100644 --- a/src/config/types.imessage.ts +++ b/src/config/types.imessage.ts @@ -52,6 +52,8 @@ export type IMessageAccountConfig = { includeAttachments?: boolean; /** Max outbound media size in MB. */ mediaMaxMb?: number; + /** Timeout for probe/RPC operations in milliseconds (default: 10000). */ + probeTimeoutMs?: number; /** Outbound text chunk size (chars). Default: 4000. */ textChunkLimit?: number; /** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */ diff --git a/src/imessage/client.ts b/src/imessage/client.ts index 9811de083..070765e3b 100644 --- a/src/imessage/client.ts +++ b/src/imessage/client.ts @@ -149,6 +149,7 @@ export class IMessageRpcClient { params: params ?? {}, }; const line = `${JSON.stringify(payload)}\n`; + // Default timeout matches DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS from probe.ts const timeoutMs = opts?.timeoutMs ?? 10_000; const response = new Promise((resolve, reject) => { diff --git a/src/imessage/monitor/monitor-provider.ts b/src/imessage/monitor/monitor-provider.ts index 08fb36aea..1f6c457f3 100644 --- a/src/imessage/monitor/monitor-provider.ts +++ b/src/imessage/monitor/monitor-provider.ts @@ -45,7 +45,7 @@ import { resolveAgentRoute } from "../../routing/resolve-route.js"; import { truncateUtf16Safe } from "../../utils.js"; import { resolveIMessageAccount } from "../accounts.js"; import { createIMessageRpcClient } from "../client.js"; -import { probeIMessage } from "../probe.js"; +import { DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS, probeIMessage } from "../probe.js"; import { sendMessageIMessage } from "../send.js"; import { formatIMessageChatTarget, @@ -139,6 +139,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024; const cliPath = opts.cliPath ?? imessageCfg.cliPath ?? "imsg"; const dbPath = opts.dbPath ?? imessageCfg.dbPath; + const probeTimeoutMs = imessageCfg.probeTimeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS; // Resolve remoteHost: explicit config, or auto-detect from SSH wrapper script let remoteHost = imessageCfg.remoteHost; @@ -618,7 +619,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P abortSignal: opts.abortSignal, runtime, check: async () => { - const probe = await probeIMessage(2000, { cliPath, dbPath, runtime }); + const probe = await probeIMessage(probeTimeoutMs, { cliPath, dbPath, runtime }); if (probe.ok) { return { ok: true }; } diff --git a/src/imessage/probe.ts b/src/imessage/probe.ts index 92d131565..5df7c667d 100644 --- a/src/imessage/probe.ts +++ b/src/imessage/probe.ts @@ -4,6 +4,9 @@ import { loadConfig } from "../config/config.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { createIMessageRpcClient } from "./client.js"; +/** Default timeout for iMessage probe operations (10 seconds). */ +export const DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS = 10_000; + export type IMessageProbe = { ok: boolean; error?: string | null; @@ -24,13 +27,13 @@ type RpcSupportResult = { const rpcSupportCache = new Map(); -async function probeRpcSupport(cliPath: string): Promise { +async function probeRpcSupport(cliPath: string, timeoutMs: number): Promise { const cached = rpcSupportCache.get(cliPath); if (cached) { return cached; } try { - const result = await runCommandWithTimeout([cliPath, "rpc", "--help"], { timeoutMs: 2000 }); + const result = await runCommandWithTimeout([cliPath, "rpc", "--help"], { timeoutMs }); const combined = `${result.stdout}\n${result.stderr}`.trim(); const normalized = combined.toLowerCase(); if (normalized.includes("unknown command") && normalized.includes("rpc")) { @@ -57,18 +60,24 @@ async function probeRpcSupport(cliPath: string): Promise { } export async function probeIMessage( - timeoutMs = 2000, + timeoutMs = DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS, opts: IMessageProbeOptions = {}, ): Promise { const cfg = opts.cliPath || opts.dbPath ? undefined : loadConfig(); const cliPath = opts.cliPath?.trim() || cfg?.channels?.imessage?.cliPath?.trim() || "imsg"; const dbPath = opts.dbPath?.trim() || cfg?.channels?.imessage?.dbPath?.trim(); + // Read probeTimeoutMs from config if not explicitly provided + const effectiveTimeout = + timeoutMs !== DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS + ? timeoutMs + : cfg?.channels?.imessage?.probeTimeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS; + const detected = await detectBinary(cliPath); if (!detected) { return { ok: false, error: `imsg not found (${cliPath})` }; } - const rpcSupport = await probeRpcSupport(cliPath); + const rpcSupport = await probeRpcSupport(cliPath, effectiveTimeout); if (!rpcSupport.supported) { return { ok: false, @@ -83,7 +92,7 @@ export async function probeIMessage( runtime: opts.runtime, }); try { - await client.request("chats.list", { limit: 1 }, { timeoutMs }); + await client.request("chats.list", { limit: 1 }, { timeoutMs: effectiveTimeout }); return { ok: true }; } catch (err) { return { ok: false, error: String(err) };