mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-28 14:13:40 +00:00
Append imsg's own status message (SIP / library validation / macOS 26 AMFI gate) to iMessage private-API blocked-action errors so operators see the real blocker instead of a generic "run imsg launch". Add a dedicated 150s default timeout for iMessage send RPCs (explicit opts and probeTimeoutMs still win) so macOS 26 bridge stalls are not aborted mid-send. Staged mitigation: the longer wait fully activates once the companion bridge timeout (openclaw/imsg#139) ships; on current imsg the bridge still returns at its own 10s, so there is no regression. Diagnostics half is live-proven; the delayed-send timeout is covered by source + unit proof + maintainer waiver.
95 lines
2.8 KiB
TypeScript
95 lines
2.8 KiB
TypeScript
// Imessage plugin module implements private api status behavior.
|
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
|
|
export type IMessagePrivateApiStatus = {
|
|
available: boolean;
|
|
v2Ready: boolean;
|
|
selectors: Record<string, boolean>;
|
|
rpcMethods: string[];
|
|
// CLI-flag-level capabilities probed from `imsg <cmd> --help`. Only fields
|
|
// we actively branch on are listed; missing entries mean "not yet probed"
|
|
// and callers should treat them as unsupported.
|
|
cliCapabilities?: {
|
|
sendRichSupportsAttachment?: boolean;
|
|
};
|
|
// imsg's own `status --json` `message` field. When advanced features are off
|
|
// it explains why (SIP enabled, library validation, macOS 26 AMFI gate), so
|
|
// callers can surface a real reason instead of a generic "run imsg launch".
|
|
statusMessage?: string;
|
|
error?: string;
|
|
};
|
|
|
|
type PrivateApiCacheEntry = {
|
|
status: IMessagePrivateApiStatus;
|
|
expiresAt: number;
|
|
};
|
|
|
|
// Methods that have always existed on imsg's rpc surface, before the
|
|
// `rpc_methods` capability list was added. An older imsg build that
|
|
// reports `available: true` but ships no rpc_methods array is assumed to
|
|
// support these; newer/private bridge methods remain explicit.
|
|
const FOUNDATIONAL_RPC_METHODS = new Set<string>([
|
|
"chats.list",
|
|
"messages.history",
|
|
"watch.subscribe",
|
|
"watch.unsubscribe",
|
|
"send",
|
|
]);
|
|
|
|
const bridgeStatusCache = new Map<string, PrivateApiCacheEntry>();
|
|
|
|
function normalizeCliPath(cliPath?: string | null): string {
|
|
return cliPath?.trim() || "imsg";
|
|
}
|
|
|
|
export function imessageRpcSupportsMethod(
|
|
status: IMessagePrivateApiStatus | undefined,
|
|
method: string,
|
|
): boolean {
|
|
if (!status?.available) {
|
|
return false;
|
|
}
|
|
if (status.rpcMethods.length === 0) {
|
|
return FOUNDATIONAL_RPC_METHODS.has(method);
|
|
}
|
|
return status.rpcMethods.includes(method);
|
|
}
|
|
|
|
export function getCachedIMessagePrivateApiStatus(
|
|
cliPath?: string | null,
|
|
): IMessagePrivateApiStatus | undefined {
|
|
const key = normalizeCliPath(cliPath);
|
|
const entry = bridgeStatusCache.get(key);
|
|
if (!entry) {
|
|
return undefined;
|
|
}
|
|
if (entry.expiresAt === 0) {
|
|
return entry.status;
|
|
}
|
|
const now = asDateTimestampMs(Date.now());
|
|
if (now === undefined || entry.expiresAt <= now) {
|
|
bridgeStatusCache.delete(key);
|
|
return undefined;
|
|
}
|
|
return entry.status;
|
|
}
|
|
|
|
export function setCachedIMessagePrivateApiStatus(
|
|
cliPath: string,
|
|
status: IMessagePrivateApiStatus,
|
|
expiresAt = 0,
|
|
): void {
|
|
if (expiresAt !== 0 && asDateTimestampMs(expiresAt) === undefined) {
|
|
return;
|
|
}
|
|
bridgeStatusCache.set(normalizeCliPath(cliPath), { status, expiresAt });
|
|
}
|
|
|
|
export function clearCachedIMessagePrivateApiStatus(cliPath?: string): void {
|
|
if (cliPath) {
|
|
bridgeStatusCache.delete(normalizeCliPath(cliPath));
|
|
} else {
|
|
bridgeStatusCache.clear();
|
|
}
|
|
}
|