UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

732 lines (731 loc) 27.1 kB
import { c as kindFromMime } from "./mime-C8mVE2Bw.js"; import { r as stripInlineDirectiveTagsForDelivery } from "./directive-tags-Dg-nkHHo.js"; import { t as createMessageReceiptFromOutboundResults } from "./receipt-B3SXxhHV.js"; import { n as resolveOutboundAttachmentFromUrl } from "./outbound-attachment-DeL7GCBY.js"; import "./media-runtime-fgpmX6eL.js"; import { t as convertMarkdownTables } from "./tables-BgtXxld3.js"; import "./text-chunking-D45TeeEs.js"; import { t as resolveMarkdownTableMode } from "./markdown-tables-LfSv7QSx.js"; import { t as requireRuntimeConfig } from "./plugin-config-runtime-CpE6DYgJ.js"; import "./channel-outbound-B3_Zy-kG.js"; import "./markdown-table-runtime-j_C4iTF_.js"; import { a as resolveIMessageAccount } from "./accounts-DzoymhDS.js"; import { t as createIMessageRpcClient } from "./client-RDkCLFWE.js"; import { c as parseIMessageTarget, o as normalizeIMessageHandle, t as formatIMessageChatTarget } from "./targets-BhNdlUSi.js"; import { s as rememberIMessageReplyCache } from "./monitor-reply-cache-DUH5oJzE.js"; import { a as extractIMessageApprovalPromptBinding, i as appendIMessageApprovalReactionHintForOutboundMessage, u as registerIMessageApprovalReactionTargetForOutboundMessage } from "./normalize-BfDcnrra.js"; import { a as rememberPersistedIMessageEcho, r as forgetPersistedIMessageEchoKey } from "./persisted-echo-cache-C0OpKutp.js"; import { n as appendIMessageCliStderrTail, r as appendIMessageCliStdout, t as extractMarkdownFormatRuns } from "./markdown-format-41-kk349.js"; import { createRequire } from "node:module"; import { accessSync, constants, readFileSync } from "node:fs"; import path from "node:path"; import os from "node:os"; import { spawn } from "node:child_process"; //#region extensions/imessage/src/send.ts const require = createRequire(import.meta.url); const MIN_PENDING_PERSISTED_ECHO_TTL_MS = 6e4; const PENDING_PERSISTED_ECHO_GRACE_MS = 5e3; const MAX_REPLY_TO_ID_LENGTH = 256; const sshWrapperCliPathCache = /* @__PURE__ */ new Map(); function safeHomeDir() { const home = process.env.HOME?.trim(); if (home) return home; try { return os.homedir().trim() || void 0; } catch { return; } } function expandCliPathForInspection(cliPath) { if (!cliPath.startsWith("~")) return cliPath; const home = safeHomeDir(); return home ? cliPath.replace(/^~(?=$|[\\/])/, home) : cliPath; } function isSshIMessageCliWrapper(cliPath) { if (cliPath === "imsg") return false; const cached = sshWrapperCliPathCache.get(cliPath); if (cached !== void 0) return cached; let detected; try { const content = readFileSync(expandCliPathForInspection(cliPath), "utf8"); detected = /\bssh\b[\s\S]*\bimsg\b/u.test(content); } catch { detected = false; } sshWrapperCliPathCache.set(cliPath, detected); return detected; } function isLocalIMessageCliPath(params) { const cliPath = params.cliPath.trim(); if (params.remoteHost?.trim() || isSshIMessageCliWrapper(cliPath)) return false; return cliPath === "imsg" || path.basename(cliPath) === "imsg"; } function resolveChatDbLookupPath(params) { const configured = params.dbPath?.trim(); if (configured) return configured; if (!isLocalIMessageCliPath({ cliPath: params.cliPath, remoteHost: params.remoteHost })) return; const home = safeHomeDir(); return home ? path.join(home, "Library", "Messages", "chat.db") : void 0; } function stripUnsafeReplyTagChars(value) { let next = ""; for (const ch of value) { const code = ch.charCodeAt(0); if (code >= 0 && code <= 31 || code === 127 || ch === "[" || ch === "]") continue; next += ch; } return next; } function sanitizeReplyToId(rawReplyToId) { const trimmed = rawReplyToId?.trim(); if (!trimmed) return; const sanitized = stripUnsafeReplyTagChars(trimmed).trim(); if (!sanitized) return; if (sanitized.length > MAX_REPLY_TO_ID_LENGTH) return sanitized.slice(0, MAX_REPLY_TO_ID_LENGTH); return sanitized; } function resolveMessageId(result) { if (!result) return null; const raw = typeof result.messageId === "string" && result.messageId.trim() || typeof result.message_id === "string" && result.message_id.trim() || typeof result.id === "string" && result.id.trim() || typeof result.guid === "string" && result.guid.trim() || (typeof result.message_id === "number" ? String(result.message_id) : null) || (typeof result.id === "number" ? String(result.id) : null); return raw ? raw.trim() : null; } function resolveOutboundMessageGuid(result) { if (!result) return null; const candidates = [ result.guid, result.messageId, result.message_id, result.id ]; for (const value of candidates) { if (typeof value !== "string") continue; const trimmed = value.trim(); if (trimmed && !/^\d+$/.test(trimmed)) return trimmed; } return null; } function isNumericMessageRowId(value) { return typeof value === "string" && /^\d+$/.test(value.trim()); } function resolveTargetService(target) { if (target.kind !== "handle") return; if (target.serviceExplicit || target.service !== "auto") return target.service; } function normalizeResolvedMessageGuid(value) { if (typeof value !== "string") return null; const trimmed = value.trim(); return trimmed && !isNumericMessageRowId(trimmed) ? trimmed : null; } function loadNodeSqlite() { try { return require("node:sqlite"); } catch { return null; } } function resolveMessageGuidFromChatDb(params) { const dbPath = params.dbPath?.trim(); const messageId = params.messageId.trim(); if (!dbPath || !isNumericMessageRowId(messageId)) return null; const sqlite = loadNodeSqlite(); if (!sqlite) return null; let db = null; try { db = new sqlite.DatabaseSync(dbPath, { readOnly: true }); return normalizeResolvedMessageGuid(db.prepare("SELECT guid FROM message WHERE ROWID = ?").get(messageId)?.guid); } catch { return null; } finally { try { db?.close(); } catch {} } } function getStringRowValue(row, key) { return normalizeResolvedMessageGuid(row?.[key]); } function appleMessageDateLowerBoundMs(sentAfterMs) { if (!Number.isFinite(sentAfterMs)) return null; return Math.max(0, Math.floor((sentAfterMs - 9783072e5 - 5e3) * 1e6)); } function resolveLatestSentMessageGuidFromChatDb(params) { const dbPath = params.dbPath?.trim(); if (!dbPath) return null; const sqlite = loadNodeSqlite(); if (!sqlite) return null; let db = null; try { db = new sqlite.DatabaseSync(dbPath, { readOnly: true }); const targetClauses = []; const targetParams = []; const lowerBound = appleMessageDateLowerBoundMs(params.sentAfterMs); if (params.text) { targetClauses.push("m.text = ?"); targetParams.push(params.text); } if (lowerBound !== null) { targetClauses.push("m.date >= ?"); targetParams.push(lowerBound); } if (params.target.kind === "chat_id") { targetClauses.push("cmj.chat_id = ?"); targetParams.push(params.target.chatId); } else if (params.target.kind === "chat_guid") { targetClauses.push("c.guid = ?"); targetParams.push(params.target.chatGuid); } else if (params.target.kind === "chat_identifier") { targetClauses.push("c.chat_identifier = ?"); targetParams.push(params.target.chatIdentifier); } else { const normalizedHandle = normalizeIMessageHandle(params.target.to); targetClauses.push("(h.id = ? OR h.uncanonicalized_id = ?)"); targetParams.push(normalizedHandle, params.target.to); } const selectSql = ` SELECT m.guid FROM message m LEFT JOIN chat_message_join cmj ON cmj.message_id = m.ROWID LEFT JOIN chat c ON c.ROWID = cmj.chat_id LEFT JOIN handle h ON h.ROWID = m.handle_id WHERE m.is_from_me = 1 ${targetClauses.length ? `AND ${targetClauses.join(" AND ")}` : ""} ORDER BY m.date DESC, m.ROWID DESC LIMIT 10 `; return getStringRowValue(db.prepare(selectSql).all(...targetParams)[0], "guid"); } catch { return null; } finally { try { db?.close(); } catch {} } } function canResolveLatestSentMessageGuidFromChatDb(dbPath) { const normalizedDbPath = dbPath?.trim(); if (!normalizedDbPath || !loadNodeSqlite()) return false; try { accessSync(normalizedDbPath, constants.R_OK); return true; } catch { return false; } } async function resolveApprovalBindingMessageGuid(params) { const immediateGuid = resolveOutboundMessageGuid(params.result); if (immediateGuid) return immediateGuid; const messageId = params.messageId?.trim(); if (!messageId || !isNumericMessageRowId(messageId)) return null; return normalizeResolvedMessageGuid(await (params.resolveMessageGuidImpl ?? resolveMessageGuidFromChatDb)({ dbPath: params.dbPath, messageId })); } function delay(ms) { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function resolveFallbackSentMessageGuid(params) { const resolver = params.resolveSentMessageGuidImpl ?? resolveLatestSentMessageGuidFromChatDb; if (!params.resolveSentMessageGuidImpl && !canResolveLatestSentMessageGuidFromChatDb(params.dbPath)) return null; const deadlineMs = Date.now() + 5e3; while (Date.now() <= deadlineMs) { const resolved = normalizeResolvedMessageGuid(await resolver({ dbPath: params.dbPath, target: params.target, text: params.text, sentAfterMs: params.sentAfterMs })); if (resolved) return resolved; if (Date.now() >= deadlineMs) return null; await delay(250); } return null; } function shouldRecoverApprovalPromptGuid(params) { return !params.filePath && !params.replyToId && Boolean(params.message.trim()) && Boolean(extractIMessageApprovalPromptBinding(params.message)); } function canCheckSentMessageAfterRpcTimeout(params) { return Boolean(params.resolveSentMessageGuidImpl) || canResolveLatestSentMessageGuidFromChatDb(params.dbPath); } function resolveOutboundEchoText(text, mediaContentType) { if (text.trim()) return text; const kind = kindFromMime(mediaContentType ?? void 0); if (!kind) return; return kind === "image" ? "<media:image>" : `<media:${kind}>`; } function createIMessageSendReceipt(params) { const messageId = params.messageId.trim(); const results = messageId && messageId !== "unknown" && messageId !== "ok" ? [{ channel: "imessage", messageId, meta: { targetKind: params.target.kind } }] : []; if (results[0]) { if (params.target.kind === "chat_id") results[0].chatId = String(params.target.chatId); else if (params.target.kind === "chat_guid") results[0].conversationId = params.target.chatGuid; else if (params.target.kind === "chat_identifier") results[0].conversationId = params.target.chatIdentifier; } const receiptParams = { results, kind: params.kind }; if (params.replyToId) receiptParams.replyToId = params.replyToId; return createMessageReceiptFromOutboundResults(receiptParams); } function isConcreteIMessageMessageId(messageId) { const trimmed = messageId?.trim(); return Boolean(trimmed && trimmed !== "unknown" && trimmed !== "ok"); } function canSynthesizeAttachmentChatHandle(raw) { const trimmed = raw.trim(); return trimmed.includes("@") || trimmed.startsWith("+"); } function resolveOutboundEchoScope(params) { if (params.target.kind === "chat_id") return `${params.accountId}:${formatIMessageChatTarget(params.target.chatId)}`; if (params.target.kind === "chat_guid") return `${params.accountId}:chat_guid:${params.target.chatGuid}`; if (params.target.kind === "chat_identifier") return `${params.accountId}:chat_identifier:${params.target.chatIdentifier}`; return `${params.accountId}:imessage:${params.target.to}`; } function buildIMessageCliJsonArgs(args, dbPath) { const trimmedDbPath = dbPath?.trim(); return [ ...args, ...trimmedDbPath ? ["--db", trimmedDbPath] : [], "--json" ]; } function resolveIMessageCliFailure(result) { if (result.success !== false) return null; return typeof result.error === "string" && result.error.trim() ? result.error.trim() : "iMessage action failed"; } function isIMessageRpcSendTimeout(error) { const message = error instanceof Error ? error.message : String(error); return /imsg rpc timeout \(send\)/i.test(message); } async function runIMessageCliJson(cliPath, dbPath, args, timeoutMs) { return await new Promise((resolve, reject) => { const child = spawn(cliPath, buildIMessageCliJsonArgs(args, dbPath), { stdio: [ "ignore", "pipe", "pipe" ] }); let stdout = ""; let stderr = ""; let killEscalation = null; let settled = false; const clearTimers = (options = {}) => { if (timer) clearTimeout(timer); if (killEscalation && !options.keepKillEscalation) clearTimeout(killEscalation); }; const fail = (error, options = {}) => { if (settled) return; settled = true; clearTimers(options); reject(error); }; const succeed = (value) => { if (settled) return; settled = true; clearTimers(); resolve(value); }; const timer = timeoutMs && timeoutMs > 0 ? setTimeout(() => { child.kill("SIGTERM"); killEscalation = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} }, 2e3); fail(/* @__PURE__ */ new Error(`iMessage action timed out after ${timeoutMs}ms`), { keepKillEscalation: true }); }, timeoutMs) : null; child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { if (settled) return; const appended = appendIMessageCliStdout(stdout, chunk); if (!appended.ok) { try { child.kill("SIGKILL"); } catch {} fail(new Error(appended.message)); return; } stdout = appended.value; }); child.stderr.on("data", (chunk) => { stderr = appendIMessageCliStderrTail(stderr, chunk); }); child.on("error", (error) => { if (settled) { clearTimers(); return; } fail(error); }); child.on("close", (code) => { if (settled) { clearTimers(); return; } const last = stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).at(-1); let parsed = null; if (last) try { const json = JSON.parse(last); if (json && typeof json === "object" && !Array.isArray(json)) parsed = json; } catch {} if (code === 0 && parsed) { const failure = resolveIMessageCliFailure(parsed); if (failure) { fail(new Error(failure)); return; } succeed(parsed); return; } if (parsed && typeof parsed.error === "string" && parsed.error.trim()) { fail(new Error(parsed.error.trim())); return; } const detail = stderr.trim() || stdout.trim() || `imsg exited with code ${code}`; fail(new Error(detail)); }); }); } function stringValue(value) { return typeof value === "string" && value.trim() ? value.trim() : void 0; } function resolvePendingPersistedEchoTtlMs(timeoutMs) { return Math.max(MIN_PENDING_PERSISTED_ECHO_TTL_MS, Math.max(0, timeoutMs) + PENDING_PERSISTED_ECHO_GRACE_MS); } function isAttachmentCommandFallbackError(error) { const message = error instanceof Error ? error.message : String(error); return /(?:unknown|unrecognized|invalid|unsupported)\s+(?:command|subcommand)|not a recognized command|send-attachment.*(?:not found|unsupported|unavailable)|private api bridge.*unavailable|requires the imsg private api bridge|run imsg launch/iu.test(message); } async function resolveAttachmentChatTarget(params) { if (params.target.kind === "chat_guid") return params.target.chatGuid; if (params.target.kind === "handle") { if (!canSynthesizeAttachmentChatHandle(params.target.to)) return null; const normalizedHandle = normalizeIMessageHandle(params.target.to); if (!normalizedHandle) return null; const service = params.target.service !== "auto" ? params.target.service : params.service; if (service === "sms") return `SMS;-;${normalizedHandle}`; if (service === "imessage") return `iMessage;-;${normalizedHandle}`; return `any;-;${normalizedHandle}`; } if (params.target.kind !== "chat_id") return null; const result = await params.runCliJson([ "group", "--chat-id", String(params.target.chatId) ]); return stringValue(result.guid) ?? stringValue(result.chat_guid) ?? null; } async function trySendAttachmentForTarget(params) { let attachmentChatTarget; try { attachmentChatTarget = await resolveAttachmentChatTarget({ target: params.target, service: params.service, runCliJson: params.runCliJson }); } catch (error) { if (isAttachmentCommandFallbackError(error)) return null; throw error; } if (!attachmentChatTarget) return null; const echoScope = resolveOutboundEchoScope({ accountId: params.accountId, target: params.target }); let result; let pendingEchoKey; try { if (echoScope) pendingEchoKey = rememberPersistedIMessageEcho({ scope: echoScope, text: params.echoText, ttlMs: params.pendingEchoTtlMs, pending: true }); result = await params.runCliJson([ "send-attachment", "--chat", attachmentChatTarget, "--file", params.filePath, ...params.audioAsVoice ? ["--audio"] : [], ...params.replyToId ? ["--reply-to", params.replyToId] : [], "--transport", "auto" ]); } catch (error) { forgetPersistedIMessageEchoKey(pendingEchoKey); if (isAttachmentCommandFallbackError(error)) return null; throw error; } const failure = resolveIMessageCliFailure(result); if (failure) { const error = new Error(failure); forgetPersistedIMessageEchoKey(pendingEchoKey); if (isAttachmentCommandFallbackError(error)) return null; throw error; } const resolvedId = resolveMessageId(result); const approvalBindingMessageId = await resolveApprovalBindingMessageGuid({ dbPath: params.dbPath, messageId: resolvedId, result, resolveMessageGuidImpl: params.resolveMessageGuidImpl }); const messageId = resolvedId ?? (result.ok || result.success ? "ok" : "unknown"); if (echoScope) rememberPersistedIMessageEcho({ scope: echoScope, text: params.echoText, messageId: resolvedId ?? void 0 }); if (resolvedId) rememberIMessageReplyCache({ accountId: params.accountId, messageId: resolvedId, chatGuid: params.target.kind === "chat_guid" ? params.target.chatGuid : params.target.kind === "chat_id" ? attachmentChatTarget : void 0, chatIdentifier: params.target.kind === "chat_identifier" || params.target.kind === "handle" ? attachmentChatTarget : void 0, chatId: params.target.kind === "chat_id" ? params.target.chatId : void 0, timestamp: Date.now(), isFromMe: true }); return { messageId, ...approvalBindingMessageId ? { guid: approvalBindingMessageId } : {}, sentText: "", ...params.echoText ? { echoText: params.echoText } : {}, receipt: createIMessageSendReceipt({ messageId, target: params.target, kind: params.audioAsVoice ? "voice" : "media", ...params.replyToId ? { replyToId: params.replyToId } : {} }) }; } async function sendMessageIMessage(to, text, opts) { const cfg = requireRuntimeConfig(opts.config, "iMessage send"); const account = opts.account ?? resolveIMessageAccount({ cfg, accountId: opts.accountId }); const cliPath = opts.cliPath?.trim() || account.config.cliPath?.trim() || "imsg"; const dbPath = opts.dbPath?.trim() || account.config.dbPath?.trim(); const chatDbLookupPath = resolveChatDbLookupPath({ cliPath, dbPath, remoteHost: account.config.remoteHost }); const target = parseIMessageTarget(opts.chatId ? formatIMessageChatTarget(opts.chatId) : to); const service = opts.service ?? resolveTargetService(target) ?? account.config.service; const sendTransport = account.config.sendTransport ?? "auto"; const timeoutMs = opts.timeoutMs ?? account.config.probeTimeoutMs ?? 15e4; const pendingEchoTtlMs = resolvePendingPersistedEchoTtlMs(timeoutMs); const region = opts.region?.trim() || account.config.region?.trim() || "US"; const maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : 16 * 1024 * 1024; let message = text ? appendIMessageApprovalReactionHintForOutboundMessage(text) : ""; let filePath; let mediaContentType; if (opts.mediaUrl?.trim()) { const resolved = await (opts.resolveAttachmentImpl ?? resolveOutboundAttachmentFromUrl)(opts.mediaUrl.trim(), maxBytes, { localRoots: opts.mediaLocalRoots, readFile: opts.mediaReadFile }); filePath = resolved.path; mediaContentType = resolved.contentType ?? void 0; } if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media"); if (message.trim()) { const tableMode = resolveMarkdownTableMode({ cfg, channel: "imessage", accountId: account.accountId }); message = convertMarkdownTables(message, tableMode); } message = stripInlineDirectiveTagsForDelivery(message).text; if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media"); const formatted = message.trim() ? extractMarkdownFormatRuns(message) : { text: message, ranges: [] }; message = formatted.text; if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media"); const echoText = resolveOutboundEchoText(message, filePath ? mediaContentType : void 0); const resolvedReplyToId = sanitizeReplyToId(opts.replyToId); const runCliJson = opts.runCliJson ?? ((args) => runIMessageCliJson(cliPath, dbPath, args, timeoutMs)); if (filePath && (!resolvedReplyToId || opts.audioAsVoice)) { const attachmentEchoText = message.trim() ? resolveOutboundEchoText("", mediaContentType) : echoText; const attachmentResult = await trySendAttachmentForTarget({ accountId: account.accountId, dbPath: chatDbLookupPath, target, service, filePath, audioAsVoice: opts.audioAsVoice, ...resolvedReplyToId ? { replyToId: resolvedReplyToId } : {}, echoText: attachmentEchoText, pendingEchoTtlMs, runCliJson, resolveMessageGuidImpl: opts.resolveMessageGuidImpl }); if (attachmentResult) { if (!message.trim()) return attachmentResult; const captionResult = await sendMessageIMessage(to, text, { ...opts, ...opts.client ? { client: opts.client } : {}, mediaUrl: void 0 }); return { messageId: isConcreteIMessageMessageId(attachmentResult.messageId) ? attachmentResult.messageId : captionResult.messageId, ...captionResult.guid ?? attachmentResult.guid ? { guid: captionResult.guid ?? attachmentResult.guid } : {}, sentText: captionResult.sentText, ...captionResult.echoText ?? attachmentResult.echoText ? { echoText: captionResult.echoText ?? attachmentResult.echoText } : {}, receipt: createMessageReceiptFromOutboundResults({ results: [{ receipt: attachmentResult.receipt }, { receipt: captionResult.receipt }], sentAt: Math.max(attachmentResult.receipt.sentAt, captionResult.receipt.sentAt) }) }; } } const params = { text: message, service: service || "auto", region, transport: sendTransport }; if (resolvedReplyToId) params.reply_to = resolvedReplyToId; if (formatted.ranges.length > 0) params.formatting = formatted.ranges; if (filePath) params.file = filePath; if (target.kind === "chat_id") params.chat_id = target.chatId; else if (target.kind === "chat_guid") params.chat_guid = target.chatGuid; else if (target.kind === "chat_identifier") params.chat_identifier = target.chatIdentifier; else params.to = target.to; const echoScope = resolveOutboundEchoScope({ accountId: account.accountId, target }); const client = opts.client ?? (opts.createClient ? await opts.createClient({ cliPath, dbPath }) : await createIMessageRpcClient({ cliPath, dbPath })); const shouldClose = !opts.client; let closedClient = false; const stopOwnedClient = async () => { if (!shouldClose || closedClient) return; closedClient = true; await client.stop(); }; let result; const sendStartedAtMs = Date.now(); let pendingEchoKey; try { try { if (echoScope) pendingEchoKey = rememberPersistedIMessageEcho({ scope: echoScope, text: echoText, ttlMs: pendingEchoTtlMs, pending: true }); result = await client.request("send", params, { timeoutMs }); } catch (error) { if (filePath || !isIMessageRpcSendTimeout(error)) throw error; if (!shouldRecoverApprovalPromptGuid({ message, filePath, replyToId: resolvedReplyToId }) || !canCheckSentMessageAfterRpcTimeout({ dbPath: chatDbLookupPath, resolveSentMessageGuidImpl: opts.resolveSentMessageGuidImpl })) throw error; const recoveredGuid = await resolveFallbackSentMessageGuid({ dbPath: chatDbLookupPath, target, text: message, sentAfterMs: sendStartedAtMs, resolveSentMessageGuidImpl: opts.resolveSentMessageGuidImpl }); if (recoveredGuid) result = { guid: recoveredGuid, status: "sent" }; else throw error; } const resolvedId = resolveMessageId(result); const messageId = resolvedId ?? (result?.ok || result?.success || result?.status === "sent" ? "ok" : "unknown"); let approvalBindingMessageId = await resolveApprovalBindingMessageGuid({ dbPath: chatDbLookupPath, messageId: resolvedId, result, resolveMessageGuidImpl: opts.resolveMessageGuidImpl }); if (!approvalBindingMessageId && shouldRecoverApprovalPromptGuid({ message, filePath, replyToId: resolvedReplyToId })) approvalBindingMessageId = await resolveFallbackSentMessageGuid({ dbPath: chatDbLookupPath, target, text: message, sentAfterMs: sendStartedAtMs, resolveSentMessageGuidImpl: opts.resolveSentMessageGuidImpl }); if (echoScope) rememberPersistedIMessageEcho({ scope: echoScope, text: echoText, messageId: resolvedId ?? void 0 }); if (resolvedId) rememberIMessageReplyCache({ accountId: account.accountId, messageId: resolvedId, chatGuid: target.kind === "chat_guid" ? target.chatGuid : void 0, chatIdentifier: target.kind === "chat_identifier" ? target.chatIdentifier : target.kind === "handle" ? `${target.service === "sms" ? "SMS" : "iMessage"};-;${target.to}` : void 0, chatId: target.kind === "chat_id" ? target.chatId : void 0, timestamp: Date.now(), isFromMe: true }); if (message && approvalBindingMessageId) { const handleForKey = target.kind === "handle" ? normalizeIMessageHandle(target.to) : void 0; const conversation = { ...target.kind === "chat_guid" ? { chatGuid: target.chatGuid } : {}, ...target.kind === "chat_identifier" ? { chatIdentifier: target.chatIdentifier } : {}, ...target.kind === "chat_id" ? { chatId: target.chatId } : {}, ...handleForKey ? { handle: handleForKey } : {} }; registerIMessageApprovalReactionTargetForOutboundMessage({ accountId: account.accountId, conversation, messageId: approvalBindingMessageId, text: message }); } return { messageId, ...approvalBindingMessageId ? { guid: approvalBindingMessageId } : {}, sentText: message, ...echoText ? { echoText } : {}, receipt: createIMessageSendReceipt({ messageId, target, kind: filePath ? "media" : "text", ...resolvedReplyToId ? { replyToId: resolvedReplyToId } : {} }) }; } catch (error) { forgetPersistedIMessageEchoKey(pendingEchoKey); throw error; } finally { await stopOwnedClient(); } } //#endregion export { sendMessageIMessage as t };