fix(imessage): unify timeout configuration with configurable probeTimeoutMs
- Add probeTimeoutMs config option to channels.imessage - Export DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS constant (10s) from probe.ts - Propagate timeout config through all iMessage probe/RPC operations - Fix hardcoded 2000ms timeouts that were too short for SSH connections Closes: timeout issues when using SSH wrapper scripts (imsg-ssh)main
parent
78fd194722
commit
78f8a29071
|
|
@ -52,6 +52,8 @@ export type IMessageAccountConfig = {
|
||||||
includeAttachments?: boolean;
|
includeAttachments?: boolean;
|
||||||
/** Max outbound media size in MB. */
|
/** Max outbound media size in MB. */
|
||||||
mediaMaxMb?: number;
|
mediaMaxMb?: number;
|
||||||
|
/** Timeout for probe/RPC operations in milliseconds (default: 10000). */
|
||||||
|
probeTimeoutMs?: number;
|
||||||
/** Outbound text chunk size (chars). Default: 4000. */
|
/** Outbound text chunk size (chars). Default: 4000. */
|
||||||
textChunkLimit?: number;
|
textChunkLimit?: number;
|
||||||
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
|
/** Chunking mode: "length" (default) splits by size; "newline" splits on every newline. */
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,7 @@ export class IMessageRpcClient {
|
||||||
params: params ?? {},
|
params: params ?? {},
|
||||||
};
|
};
|
||||||
const line = `${JSON.stringify(payload)}\n`;
|
const line = `${JSON.stringify(payload)}\n`;
|
||||||
|
// Default timeout matches DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS from probe.ts
|
||||||
const timeoutMs = opts?.timeoutMs ?? 10_000;
|
const timeoutMs = opts?.timeoutMs ?? 10_000;
|
||||||
|
|
||||||
const response = new Promise<T>((resolve, reject) => {
|
const response = new Promise<T>((resolve, reject) => {
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||||
import { truncateUtf16Safe } from "../../utils.js";
|
import { truncateUtf16Safe } from "../../utils.js";
|
||||||
import { resolveIMessageAccount } from "../accounts.js";
|
import { resolveIMessageAccount } from "../accounts.js";
|
||||||
import { createIMessageRpcClient } from "../client.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 { sendMessageIMessage } from "../send.js";
|
||||||
import {
|
import {
|
||||||
formatIMessageChatTarget,
|
formatIMessageChatTarget,
|
||||||
|
|
@ -139,6 +139,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||||
const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
|
const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
|
||||||
const cliPath = opts.cliPath ?? imessageCfg.cliPath ?? "imsg";
|
const cliPath = opts.cliPath ?? imessageCfg.cliPath ?? "imsg";
|
||||||
const dbPath = opts.dbPath ?? imessageCfg.dbPath;
|
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
|
// Resolve remoteHost: explicit config, or auto-detect from SSH wrapper script
|
||||||
let remoteHost = imessageCfg.remoteHost;
|
let remoteHost = imessageCfg.remoteHost;
|
||||||
|
|
@ -618,7 +619,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||||
abortSignal: opts.abortSignal,
|
abortSignal: opts.abortSignal,
|
||||||
runtime,
|
runtime,
|
||||||
check: async () => {
|
check: async () => {
|
||||||
const probe = await probeIMessage(2000, { cliPath, dbPath, runtime });
|
const probe = await probeIMessage(probeTimeoutMs, { cliPath, dbPath, runtime });
|
||||||
if (probe.ok) {
|
if (probe.ok) {
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { loadConfig } from "../config/config.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
import { createIMessageRpcClient } from "./client.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 = {
|
export type IMessageProbe = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
error?: string | null;
|
error?: string | null;
|
||||||
|
|
@ -24,13 +27,13 @@ type RpcSupportResult = {
|
||||||
|
|
||||||
const rpcSupportCache = new Map<string, RpcSupportResult>();
|
const rpcSupportCache = new Map<string, RpcSupportResult>();
|
||||||
|
|
||||||
async function probeRpcSupport(cliPath: string): Promise<RpcSupportResult> {
|
async function probeRpcSupport(cliPath: string, timeoutMs: number): Promise<RpcSupportResult> {
|
||||||
const cached = rpcSupportCache.get(cliPath);
|
const cached = rpcSupportCache.get(cliPath);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
try {
|
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 combined = `${result.stdout}\n${result.stderr}`.trim();
|
||||||
const normalized = combined.toLowerCase();
|
const normalized = combined.toLowerCase();
|
||||||
if (normalized.includes("unknown command") && normalized.includes("rpc")) {
|
if (normalized.includes("unknown command") && normalized.includes("rpc")) {
|
||||||
|
|
@ -57,18 +60,24 @@ async function probeRpcSupport(cliPath: string): Promise<RpcSupportResult> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function probeIMessage(
|
export async function probeIMessage(
|
||||||
timeoutMs = 2000,
|
timeoutMs = DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS,
|
||||||
opts: IMessageProbeOptions = {},
|
opts: IMessageProbeOptions = {},
|
||||||
): Promise<IMessageProbe> {
|
): Promise<IMessageProbe> {
|
||||||
const cfg = opts.cliPath || opts.dbPath ? undefined : loadConfig();
|
const cfg = opts.cliPath || opts.dbPath ? undefined : loadConfig();
|
||||||
const cliPath = opts.cliPath?.trim() || cfg?.channels?.imessage?.cliPath?.trim() || "imsg";
|
const cliPath = opts.cliPath?.trim() || cfg?.channels?.imessage?.cliPath?.trim() || "imsg";
|
||||||
const dbPath = opts.dbPath?.trim() || cfg?.channels?.imessage?.dbPath?.trim();
|
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);
|
const detected = await detectBinary(cliPath);
|
||||||
if (!detected) {
|
if (!detected) {
|
||||||
return { ok: false, error: `imsg not found (${cliPath})` };
|
return { ok: false, error: `imsg not found (${cliPath})` };
|
||||||
}
|
}
|
||||||
|
|
||||||
const rpcSupport = await probeRpcSupport(cliPath);
|
const rpcSupport = await probeRpcSupport(cliPath, effectiveTimeout);
|
||||||
if (!rpcSupport.supported) {
|
if (!rpcSupport.supported) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
|
|
@ -83,7 +92,7 @@ export async function probeIMessage(
|
||||||
runtime: opts.runtime,
|
runtime: opts.runtime,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await client.request("chats.list", { limit: 1 }, { timeoutMs });
|
await client.request("chats.list", { limit: 1 }, { timeoutMs: effectiveTimeout });
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return { ok: false, error: String(err) };
|
return { ok: false, error: String(err) };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue