fix(imessage): report non-mac default imsg hosts

This commit is contained in:
Vincent Koc
2026-05-07 04:52:01 -07:00
parent 4ad4be9aff
commit 84638bfbb0
4 changed files with 44 additions and 3 deletions

View File

@@ -2,7 +2,7 @@
"name": "@openclaw/imessage",
"version": "2026.5.6",
"private": true,
"description": "OpenClaw iMessage channel plugin",
"description": "OpenClaw iMessage channel plugin using imsg on a signed-in Mac",
"type": "module",
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"
@@ -19,7 +19,7 @@
"detailLabel": "iMessage",
"docsPath": "/channels/imessage",
"docsLabel": "imessage",
"blurb": "this is still a work in progress.",
"blurb": "iMessage via the imsg CLI on a signed-in Mac or SSH wrapper.",
"aliases": [
"imsg"
],

View File

@@ -1,3 +1,4 @@
import path from "node:path";
import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
@@ -17,6 +18,7 @@ export type IMessageProbe = BaseProbeResult & {
export type IMessageProbeOptions = {
cliPath?: string;
dbPath?: string;
platform?: NodeJS.Platform;
runtime?: RuntimeEnv;
};
@@ -28,6 +30,21 @@ type RpcSupportResult = {
const rpcSupportCache = new Map<string, RpcSupportResult>();
function isDefaultLocalIMessageCliPath(cliPath: string): boolean {
const trimmed = cliPath.trim();
return trimmed === "imsg" || (!trimmed.includes("/") && path.basename(trimmed) === "imsg");
}
export function resolveIMessageNonMacHostError(
cliPath: string,
platform: NodeJS.Platform = process.platform,
): string | undefined {
if (platform === "darwin" || !isDefaultLocalIMessageCliPath(cliPath)) {
return undefined;
}
return "iMessage via the default imsg CLI must run on macOS. Run OpenClaw on the signed-in Messages Mac, or set channels.imessage.cliPath to an SSH wrapper that runs imsg on that Mac.";
}
async function probeRpcSupport(cliPath: string, timeoutMs: number): Promise<RpcSupportResult> {
const cached = rpcSupportCache.get(cliPath);
if (cached) {
@@ -76,6 +93,11 @@ export async function probeIMessage(
const effectiveTimeout =
timeoutMs ?? cfg?.channels?.imessage?.probeTimeoutMs ?? DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS;
const nonMacHostError = resolveIMessageNonMacHostError(cliPath, opts.platform);
if (nonMacHostError) {
return { ok: false, fatal: true, error: nonMacHostError };
}
const detected = await detectBinary(cliPath);
if (!detected) {
return { ok: false, error: `imsg not found (${cliPath})` };

View File

@@ -169,7 +169,8 @@ export function createIMessageCliPathTextInput(
export const imessageCompletionNote = {
title: "iMessage next steps",
lines: [
"This is still a work in progress.",
"Run OpenClaw on the Mac signed into Messages, or set cliPath to an SSH wrapper that runs imsg on that Mac.",
"Linux/Windows hosts cannot run the default local imsg path directly.",
"Ensure OpenClaw has Full Disk Access to Messages DB.",
"Grant Automation permission for Messages when prompted.",
"List chats with: imsg chats --limit 20",

View File

@@ -169,6 +169,24 @@ describe("probeIMessage", () => {
expect(createIMessageRpcClientMock).not.toHaveBeenCalled();
});
it("fails fast for default local imsg probes on non-mac hosts", async () => {
const createIMessageRpcClientMock = vi
.spyOn(clientModule, "createIMessageRpcClient")
.mockResolvedValue({
request: vi.fn(),
stop: vi.fn(),
} as unknown as Awaited<ReturnType<typeof clientModule.createIMessageRpcClient>>);
const result = await probeIMessage(1000, { cliPath: "imsg", platform: "linux" });
expect(result.ok).toBe(false);
expect(result.fatal).toBe(true);
expect(result.error).toMatch(/macOS/i);
expect(result.error).toMatch(/SSH wrapper/i);
expect(setupRuntime.detectBinary).not.toHaveBeenCalled();
expect(createIMessageRpcClientMock).not.toHaveBeenCalled();
});
it("status probe uses account-scoped cliPath and dbPath", async () => {
const probeSpy = vi.spyOn(channelRuntimeModule, "probeIMessageAccount").mockResolvedValue({
ok: true,