@openclaw/imessage 2026.10.1 → 2026.10.5-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.setup/{accounts-evTpvJMv.mjs → accounts-DacCvDBt.mjs} +23 -45
- package/dist/.setup/{actions-DJqbUmbN.mjs → actions-Hxh12myO.mjs} +54 -101
- package/dist/.setup/{actions.runtime-DL5i-Y4J.mjs → actions.runtime-DOIX6Fkf.mjs} +29 -37
- package/dist/.setup/{api-oq_MryN7.mjs → api-BV8qHfLl.mjs} +7 -7
- package/dist/.setup/{api-SAVMfMoY.mjs → api-DC1yHVGj.mjs} +8 -8
- package/dist/.setup/{approval-handler.runtime-Bj2NGNXF.mjs → approval-handler.runtime-Dc3KzakG.mjs} +38 -66
- package/dist/.setup/{approval-reactions-L9Ul7PAu.mjs → approval-reactions-JvzJCJ4a.mjs} +26 -33
- package/dist/.setup/{channel-CL73qdMG.mjs → channel-9TWBUDkP.mjs} +14 -16
- package/dist/.setup/{channel.runtime-DY7JJ0_9.mjs → channel.runtime-DrLDEY4R.mjs} +198 -362
- package/dist/.setup/{client-12apu4Ip.mjs → client-HixFQQWY.mjs} +34 -38
- package/dist/.setup/{media-contract-DjQGZlfl.mjs → media-contract-BwWs5PM7.mjs} +1 -1
- package/dist/.setup/{message-tool-api-CqfRxLlT.mjs → message-tool-api-B5NfPnFL.mjs} +2 -2
- package/dist/.setup/{question-reactions-DJV-TnA8.mjs → question-reactions-9jrqVZgE.mjs} +20 -25
- package/dist/.setup/{reaction-context-By7kUMBC.mjs → reaction-context-CRXpeYAr.mjs} +1 -1
- package/dist/.setup/{remote-file-BU7pA-Zi.mjs → remote-file-Dq5Asi_F.mjs} +10 -16
- package/dist/.setup/{sanitize-outbound-C0x4BPky.mjs → sanitize-outbound-DpdQERYJ.mjs} +27 -62
- package/dist/.setup/{send-pkYwL6Rt.mjs → send-BZZzMbdi.mjs} +186 -251
- package/dist/.setup/{setup-core-DqOBe-n0.mjs → setup-core-CaRGbZOL.mjs} +15 -46
- package/dist/.setup/{setup-surface-zKhIinwY.mjs → setup-surface-BMWS7VuY.mjs} +2 -2
- package/dist/.setup/{targets-BicBiYsM.mjs → targets-B4oU7n-v.mjs} +47 -69
- package/dist/api.js +7 -7
- package/dist/channel-plugin-api.js +1 -1
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +21 -45
- package/dist/media-contract-api.js +1 -1
- package/dist/message-tool-api.js +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/setup-entry.js +1 -1
- package/dist/src/chat-db.worker.js +1 -1
- package/package.json +4 -4
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { c as parseIMessageTarget, o as normalizeIMessageHandle, t as formatIMessageChatTarget } from "./targets-
|
|
2
|
-
import { d as resolveIMessageChatDbLookupPath, l as resolveIMessageRemoteHost, n as hasExclusiveIMessageLocalDatabase, o as resolveIMessageAccount } from "./accounts-
|
|
3
|
-
import { C as resolveIMessageDirectChatService, _ as resolveIMessageSentEchoEntryKey, b as chatContextFromIMessageTarget, c as rememberIMessageReplyCache, f as IMESSAGE_SENT_ECHOES_NAMESPACE, g as resolveIMessageEchoMediaKey, n as createIMessageRpcClient, p as IMESSAGE_SENT_ECHOES_TTL_MS, r as runIMessageCliJsonCommand, t as IMessageRpcRequestError } from "./client-
|
|
4
|
-
import { a as normalizeIMessageMessageId, i as normalizeIMessageGuid } from "./actions-
|
|
1
|
+
import { c as parseIMessageTarget, o as normalizeIMessageHandle, t as formatIMessageChatTarget } from "./targets-B4oU7n-v.mjs";
|
|
2
|
+
import { d as resolveIMessageChatDbLookupPath, l as resolveIMessageRemoteHost, n as hasExclusiveIMessageLocalDatabase, o as resolveIMessageAccount } from "./accounts-DacCvDBt.mjs";
|
|
3
|
+
import { C as resolveIMessageDirectChatService, _ as resolveIMessageSentEchoEntryKey, b as chatContextFromIMessageTarget, c as rememberIMessageReplyCache, f as IMESSAGE_SENT_ECHOES_NAMESPACE, g as resolveIMessageEchoMediaKey, n as createIMessageRpcClient, p as IMESSAGE_SENT_ECHOES_TTL_MS, r as runIMessageCliJsonCommand, t as IMessageRpcRequestError } from "./client-HixFQQWY.mjs";
|
|
4
|
+
import { a as normalizeIMessageMessageId, i as normalizeIMessageGuid } from "./actions-Hxh12myO.mjs";
|
|
5
5
|
import { n as getOptionalIMessageRuntime, t as getIMessageRuntime } from "./runtime-Cza4CY5T.mjs";
|
|
6
|
-
import { d as getIMessageApprovalApprovers, f as imessageApprovalAuth } from "./channel-
|
|
7
|
-
import { n as sanitizeIMessageFinalOutboundText, t as protectIMessageFencedRoleMarkers } from "./sanitize-outbound-
|
|
8
|
-
import { a as withIMessageReceiptGuidReader, r as resolveAuthorizedIMessageReplyReference, t as withIMessageRemoteFile } from "./remote-file-
|
|
9
|
-
import { d as normalizeConversationKey, i as registerIMessageApprovalReactionTarget, l as enumerateApprovalTargetKeys, s as buildIMessageApprovalConversationKeyForInbound, u as enumerateConversationKeyForms } from "./approval-reactions-
|
|
6
|
+
import { d as getIMessageApprovalApprovers, f as imessageApprovalAuth } from "./channel-9TWBUDkP.mjs";
|
|
7
|
+
import { n as sanitizeIMessageFinalOutboundText, t as protectIMessageFencedRoleMarkers } from "./sanitize-outbound-DpdQERYJ.mjs";
|
|
8
|
+
import { a as withIMessageReceiptGuidReader, r as resolveAuthorizedIMessageReplyReference, t as withIMessageRemoteFile } from "./remote-file-Dq5Asi_F.mjs";
|
|
9
|
+
import { d as normalizeConversationKey, f as resolveIMessageApprovalControlActor, i as registerIMessageApprovalReactionTarget, l as enumerateApprovalTargetKeys, s as buildIMessageApprovalConversationKeyForInbound, u as enumerateConversationKeyForms } from "./approval-reactions-JvzJCJ4a.mjs";
|
|
10
10
|
import { asOptionalRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
11
11
|
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
12
12
|
import { createDeferred } from "openclaw/plugin-sdk/extension-shared";
|
|
@@ -17,6 +17,7 @@ import { extractOriginalFilename, kindFromMime, resolveOutboundAttachmentFromUrl
|
|
|
17
17
|
import { logVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
18
18
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
19
19
|
import { PlatformMessageNotDispatchedError, isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
|
|
20
|
+
import { captureChannelReadAuthority, captureEffectAuthority } from "openclaw/plugin-sdk/fetch-runtime";
|
|
20
21
|
import { racePromiseWithAbortSignal } from "openclaw/plugin-sdk/time-runtime";
|
|
21
22
|
import { addApprovalReactionHintToText, createApprovalReactionTargetStore, listApprovalReactionBindings } from "openclaw/plugin-sdk/approval-reaction-runtime";
|
|
22
23
|
import { convertMarkdownTables, stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
|
@@ -39,12 +40,9 @@ function bindingKeys(accountId, conversation) {
|
|
|
39
40
|
function beginIMessageApprovalControlBinding(params) {
|
|
40
41
|
const keys = bindingKeys(params.accountId, params.conversation);
|
|
41
42
|
const { promise, resolve } = createDeferred();
|
|
42
|
-
let closed = false;
|
|
43
43
|
const window = {
|
|
44
44
|
done: promise,
|
|
45
45
|
close: () => {
|
|
46
|
-
if (closed) return;
|
|
47
|
-
closed = true;
|
|
48
46
|
for (const key of keys) {
|
|
49
47
|
const windows = pendingByConversation.get(key);
|
|
50
48
|
windows?.delete(window);
|
|
@@ -224,9 +222,7 @@ function readPollVoteEvent(message) {
|
|
|
224
222
|
const poll = message.poll;
|
|
225
223
|
if (!poll || poll.kind !== "vote") return null;
|
|
226
224
|
const pollGuid = normalizeIMessageGuid(typeof poll.original_guid === "string" && poll.original_guid || typeof poll.poll_guid === "string" && poll.poll_guid || "");
|
|
227
|
-
const
|
|
228
|
-
const destinationCallerId = normalizeIMessageHandle((message.destination_caller_id ?? "").trim());
|
|
229
|
-
const actorHandle = (message.is_from_me !== true && Boolean(sender) && Boolean(destinationCallerId) && sender === destinationCallerId ? "" : sender) || (message.is_from_me === true ? destinationCallerId : "");
|
|
225
|
+
const actorHandle = resolveIMessageApprovalControlActor(message);
|
|
230
226
|
if (!pollGuid || !actorHandle) return null;
|
|
231
227
|
const rawVotes = Array.isArray(poll.votes) ? poll.votes : poll.vote ? [poll.vote] : [];
|
|
232
228
|
let malformedVotes = false;
|
|
@@ -246,13 +242,7 @@ function readPollVoteEvent(message) {
|
|
|
246
242
|
selected: vote.event_type === "selected"
|
|
247
243
|
}];
|
|
248
244
|
});
|
|
249
|
-
const conversation = buildIMessageApprovalConversationKeyForInbound(
|
|
250
|
-
chatGuid: message.chat_guid,
|
|
251
|
-
chatIdentifier: message.chat_identifier,
|
|
252
|
-
chatId: message.chat_id,
|
|
253
|
-
isGroup: message.is_group,
|
|
254
|
-
actorHandle
|
|
255
|
-
});
|
|
245
|
+
const conversation = buildIMessageApprovalConversationKeyForInbound(message, actorHandle);
|
|
256
246
|
if (!normalizeConversationKey(conversation)) return null;
|
|
257
247
|
return {
|
|
258
248
|
conversation,
|
|
@@ -279,20 +269,12 @@ async function lookupPollRecord(store, params) {
|
|
|
279
269
|
* decision is security-relevant, and diagnosing "the tap did nothing" must not
|
|
280
270
|
* require re-running the gateway in debug.
|
|
281
271
|
*/
|
|
282
|
-
function
|
|
272
|
+
function logPollVote(level, message, fields) {
|
|
283
273
|
try {
|
|
284
|
-
getOptionalIMessageRuntime()?.logging.getChildLogger({
|
|
274
|
+
(getOptionalIMessageRuntime()?.logging.getChildLogger({
|
|
285
275
|
plugin: "imessage",
|
|
286
276
|
feature: "approval-polls"
|
|
287
|
-
})
|
|
288
|
-
} catch {}
|
|
289
|
-
}
|
|
290
|
-
function warn$1(message, fields) {
|
|
291
|
-
try {
|
|
292
|
-
getOptionalIMessageRuntime()?.logging.getChildLogger({
|
|
293
|
-
plugin: "imessage",
|
|
294
|
-
feature: "approval-polls"
|
|
295
|
-
}).warn(message, fields);
|
|
277
|
+
}))?.[level](message, fields);
|
|
296
278
|
} catch {}
|
|
297
279
|
}
|
|
298
280
|
/**
|
|
@@ -312,7 +294,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
312
294
|
const target = await lookupPollRecord(pollTargets, lookupKey);
|
|
313
295
|
if (!target) return Boolean(await lookupPollRecord(pollTombstones, lookupKey));
|
|
314
296
|
if (event.malformedVotes) {
|
|
315
|
-
|
|
297
|
+
logPollVote("warn", "approval poll vote ignored: malformed complete vote set", {
|
|
316
298
|
approvalId: target.approvalId,
|
|
317
299
|
actorHandle: event.actorHandle
|
|
318
300
|
});
|
|
@@ -322,7 +304,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
322
304
|
const participantKeys = new Set(event.votes.map((vote) => vote.participantKey).filter(Boolean));
|
|
323
305
|
const actorVotes = directlyAttributedVotes.length > 0 ? directlyAttributedVotes : event.votes.length === 1 || participantKeys.size === 1 ? event.votes : [];
|
|
324
306
|
if (actorVotes.length === 0) {
|
|
325
|
-
|
|
307
|
+
logPollVote("warn", "approval poll vote participants did not identify the transport actor", {
|
|
326
308
|
approvalId: target.approvalId,
|
|
327
309
|
actorHandle: event.actorHandle
|
|
328
310
|
});
|
|
@@ -330,7 +312,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
330
312
|
}
|
|
331
313
|
const selectedVotes = actorVotes.filter((vote) => vote.selected);
|
|
332
314
|
if (selectedVotes.length === 0) {
|
|
333
|
-
|
|
315
|
+
logPollVote("info", "approval poll deselect ignored; first selection decides", { approvalId: target.approvalId });
|
|
334
316
|
return true;
|
|
335
317
|
}
|
|
336
318
|
const selectedDecisions = selectedVotes.map((vote) => ({
|
|
@@ -338,7 +320,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
338
320
|
decision: target.optionDecisions.find(([optionId]) => optionId === vote.optionId)?.[1]
|
|
339
321
|
}));
|
|
340
322
|
if (selectedDecisions.some((entry) => !entry.decision)) {
|
|
341
|
-
|
|
323
|
+
logPollVote("warn", "approval poll vote ignored: selected option not bound to a decision", {
|
|
342
324
|
approvalId: target.approvalId,
|
|
343
325
|
optionIds: selectedDecisions.map((entry) => entry.optionId)
|
|
344
326
|
});
|
|
@@ -346,7 +328,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
346
328
|
}
|
|
347
329
|
const decisions = [...new Set(selectedDecisions.map((entry) => entry.decision))];
|
|
348
330
|
if (decisions.length !== 1) {
|
|
349
|
-
|
|
331
|
+
logPollVote("warn", "approval poll vote ignored: ambiguous selected decisions", {
|
|
350
332
|
approvalId: target.approvalId,
|
|
351
333
|
decisions
|
|
352
334
|
});
|
|
@@ -358,7 +340,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
358
340
|
cfg: params.cfg,
|
|
359
341
|
accountId: params.accountId
|
|
360
342
|
}).length === 0) {
|
|
361
|
-
|
|
343
|
+
logPollVote("info", "approval poll vote denied: no explicit approvers configured", { approvalId: target.approvalId });
|
|
362
344
|
return true;
|
|
363
345
|
}
|
|
364
346
|
if (!imessageApprovalAuth.authorizeActorAction({
|
|
@@ -368,7 +350,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
368
350
|
action: "approve",
|
|
369
351
|
approvalKind: target.approvalKind
|
|
370
352
|
}).authorized) {
|
|
371
|
-
|
|
353
|
+
logPollVote("info", "approval poll vote denied: sender not an approver", {
|
|
372
354
|
approvalId: target.approvalId,
|
|
373
355
|
actorHandle: event.actorHandle
|
|
374
356
|
});
|
|
@@ -392,7 +374,7 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
392
374
|
optionDecisions: target.optionDecisions,
|
|
393
375
|
approvalId: target.approvalId
|
|
394
376
|
});
|
|
395
|
-
info
|
|
377
|
+
logPollVote("info", `approval poll vote ${result.applied ? "resolved" : "already resolved"}`, {
|
|
396
378
|
approvalId: target.approvalId,
|
|
397
379
|
actorHandle: event.actorHandle,
|
|
398
380
|
decision
|
|
@@ -405,10 +387,10 @@ async function maybeResolveIMessageApprovalPollVote(params) {
|
|
|
405
387
|
optionDecisions: target.optionDecisions,
|
|
406
388
|
approvalId: target.approvalId
|
|
407
389
|
});
|
|
408
|
-
|
|
390
|
+
logPollVote("info", "approval poll vote ignored: approval already gone", { approvalId: target.approvalId });
|
|
409
391
|
return true;
|
|
410
392
|
}
|
|
411
|
-
|
|
393
|
+
logPollVote("warn", "approval poll vote failed", {
|
|
412
394
|
approvalId: target.approvalId,
|
|
413
395
|
senderId: event.actorHandle,
|
|
414
396
|
error: String(error)
|
|
@@ -534,6 +516,19 @@ async function hasPersistedIMessageEcho(params) {
|
|
|
534
516
|
}
|
|
535
517
|
//#endregion
|
|
536
518
|
//#region extensions/imessage/src/send-transport.ts
|
|
519
|
+
function bindIMessageCliSend(handoff, options) {
|
|
520
|
+
const effect = captureEffectAuthority();
|
|
521
|
+
const assertReadAuthority = captureChannelReadAuthority();
|
|
522
|
+
const runCli = handoff.runCliJson ?? ((args) => runIMessageCliJsonCommand({
|
|
523
|
+
...options,
|
|
524
|
+
args
|
|
525
|
+
}));
|
|
526
|
+
return (args) => effect.initiate(() => {
|
|
527
|
+
assertReadAuthority?.();
|
|
528
|
+
handoff.assertDirectAdapterHandoff?.();
|
|
529
|
+
return runCli(args);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
537
532
|
function normalizeIMessageRpcSendError(error) {
|
|
538
533
|
if (!(error instanceof IMessageRpcRequestError)) return error;
|
|
539
534
|
const data = asOptionalRecord(error.data);
|
|
@@ -544,7 +539,10 @@ async function requestIMessageRpcSend(client, method, params, timeoutMs, handoff
|
|
|
544
539
|
await handoff.onPlatformSendDispatch?.();
|
|
545
540
|
handoff.assertDirectAdapterHandoff?.();
|
|
546
541
|
try {
|
|
547
|
-
return await client.request(method, params, {
|
|
542
|
+
return await client.request(method, params, {
|
|
543
|
+
timeoutMs,
|
|
544
|
+
assertCurrent: handoff.assertDirectAdapterHandoff
|
|
545
|
+
});
|
|
548
546
|
} catch (error) {
|
|
549
547
|
throw normalizeIMessageRpcSendError(error);
|
|
550
548
|
}
|
|
@@ -558,20 +556,6 @@ function resolveMessageId(result) {
|
|
|
558
556
|
const raw = typeof result.messageGuid === "string" && result.messageGuid.trim() || 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);
|
|
559
557
|
return raw ? raw.trim() : null;
|
|
560
558
|
}
|
|
561
|
-
function resolveOutboundMessageGuid(result) {
|
|
562
|
-
if (!result) return null;
|
|
563
|
-
for (const key of [
|
|
564
|
-
"messageGuid",
|
|
565
|
-
"guid",
|
|
566
|
-
"messageId",
|
|
567
|
-
"message_id",
|
|
568
|
-
"id"
|
|
569
|
-
]) {
|
|
570
|
-
const guid = normalizeResolvedMessageGuid(result[key]);
|
|
571
|
-
if (guid) return guid;
|
|
572
|
-
}
|
|
573
|
-
return null;
|
|
574
|
-
}
|
|
575
559
|
function isNumericMessageRowId(value) {
|
|
576
560
|
return typeof value === "string" && /^\d+$/.test(value.trim());
|
|
577
561
|
}
|
|
@@ -580,15 +564,6 @@ function normalizeResolvedMessageGuid(value) {
|
|
|
580
564
|
const trimmed = value.trim();
|
|
581
565
|
return normalizeIMessageMessageId(trimmed) && !isNumericMessageRowId(trimmed) ? trimmed : null;
|
|
582
566
|
}
|
|
583
|
-
async function resolveMessageGuidFromChatDb(params) {
|
|
584
|
-
const dbPath = params.dbPath?.trim();
|
|
585
|
-
const messageId = params.messageId.trim();
|
|
586
|
-
if (!dbPath || !isNumericMessageRowId(messageId)) return null;
|
|
587
|
-
return normalizeResolvedMessageGuid(await withIMessageReceiptGuidReader(dbPath, (read) => read({
|
|
588
|
-
type: "messageGuid",
|
|
589
|
-
input: { messageId }
|
|
590
|
-
})));
|
|
591
|
-
}
|
|
592
567
|
function canResolveLatestSentMessageGuidFromChatDb(dbPath) {
|
|
593
568
|
const normalizedDbPath = dbPath?.trim();
|
|
594
569
|
if (!normalizedDbPath) return false;
|
|
@@ -600,14 +575,27 @@ function canResolveLatestSentMessageGuidFromChatDb(dbPath) {
|
|
|
600
575
|
}
|
|
601
576
|
}
|
|
602
577
|
async function resolveApprovalBindingMessageGuid(params) {
|
|
603
|
-
const
|
|
604
|
-
|
|
578
|
+
for (const key of [
|
|
579
|
+
"messageGuid",
|
|
580
|
+
"guid",
|
|
581
|
+
"messageId",
|
|
582
|
+
"message_id",
|
|
583
|
+
"id"
|
|
584
|
+
]) {
|
|
585
|
+
const guid = normalizeResolvedMessageGuid(params.result?.[key]);
|
|
586
|
+
if (guid) return guid;
|
|
587
|
+
}
|
|
605
588
|
const messageId = params.messageId?.trim();
|
|
606
589
|
if (!messageId || !isNumericMessageRowId(messageId)) return null;
|
|
607
|
-
return normalizeResolvedMessageGuid(await
|
|
590
|
+
if (params.resolveMessageGuidImpl) return normalizeResolvedMessageGuid(await params.resolveMessageGuidImpl({
|
|
608
591
|
dbPath: params.dbPath,
|
|
609
592
|
messageId
|
|
610
593
|
}));
|
|
594
|
+
const dbPath = params.dbPath?.trim();
|
|
595
|
+
return dbPath ? normalizeResolvedMessageGuid(await withIMessageReceiptGuidReader(dbPath, (read) => read({
|
|
596
|
+
type: "messageGuid",
|
|
597
|
+
input: { messageId }
|
|
598
|
+
}))) : null;
|
|
611
599
|
}
|
|
612
600
|
async function resolveFallbackSentMessageGuid(params) {
|
|
613
601
|
const dbPath = params.dbPath?.trim();
|
|
@@ -638,13 +626,6 @@ async function resolveFallbackSentMessageGuid(params) {
|
|
|
638
626
|
}
|
|
639
627
|
})));
|
|
640
628
|
}
|
|
641
|
-
function resolveOutboundEchoMedia(mediaContentType) {
|
|
642
|
-
if (!mediaContentType) return;
|
|
643
|
-
return {
|
|
644
|
-
contentType: mediaContentType,
|
|
645
|
-
kind: kindFromMime(mediaContentType) ?? "unknown"
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
629
|
function createIMessageSendReceipt(params) {
|
|
649
630
|
const messageId = params.messageId.trim();
|
|
650
631
|
const results = normalizeIMessageMessageId(messageId) ? [{
|
|
@@ -671,10 +652,6 @@ async function withOriginalIMessageAttachmentPath(filePath, send) {
|
|
|
671
652
|
prefix: "openclaw-imessage-outbound-"
|
|
672
653
|
}, async (workspace) => await send(await workspace.copyIn(filename, filePath)));
|
|
673
654
|
}
|
|
674
|
-
function canSynthesizeAttachmentChatHandle(raw) {
|
|
675
|
-
const trimmed = raw.trim();
|
|
676
|
-
return trimmed.includes("@") || trimmed.startsWith("+");
|
|
677
|
-
}
|
|
678
655
|
function resolveOutboundEchoScope(params) {
|
|
679
656
|
if (params.target.kind === "chat_id") return `${params.accountId}:${formatIMessageChatTarget(params.target.chatId)}`;
|
|
680
657
|
if (params.target.kind === "chat_guid") return `${params.accountId}:chat_guid:${params.target.chatGuid}`;
|
|
@@ -693,9 +670,6 @@ function resultService(value) {
|
|
|
693
670
|
const normalized = normalizeOptionalString(value)?.toLowerCase();
|
|
694
671
|
return normalized === "imessage" || normalized === "sms" ? normalized : void 0;
|
|
695
672
|
}
|
|
696
|
-
function resolvePendingPersistedEchoTtlMs(timeoutMs) {
|
|
697
|
-
return Math.max(MIN_PENDING_PERSISTED_ECHO_TTL_MS, Math.max(0, timeoutMs) + PENDING_PERSISTED_ECHO_GRACE_MS);
|
|
698
|
-
}
|
|
699
673
|
function isAttachmentCommandFallbackError(error) {
|
|
700
674
|
const message = error instanceof Error ? error.message : String(error);
|
|
701
675
|
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);
|
|
@@ -704,146 +678,6 @@ function isThreadedReplyUnsupportedError(error) {
|
|
|
704
678
|
const message = error instanceof Error ? error.message : String(error);
|
|
705
679
|
return /reply_to requires bridge transport|cannot send threaded repl|threaded repl(?:y|ies)\b.*(?:unsupported|not supported|requires|unavailable)|requires bridge transport/iu.test(message);
|
|
706
680
|
}
|
|
707
|
-
async function resolveAttachmentChatTarget(params) {
|
|
708
|
-
if (params.target.kind === "chat_guid") return params.target.chatGuid;
|
|
709
|
-
if (params.target.kind === "handle") {
|
|
710
|
-
if (!canSynthesizeAttachmentChatHandle(params.target.to)) return null;
|
|
711
|
-
const normalizedHandle = normalizeIMessageHandle(params.target.to);
|
|
712
|
-
if (!normalizedHandle) return null;
|
|
713
|
-
const service = params.target.service !== "auto" ? params.target.service : params.service;
|
|
714
|
-
if (service === "sms") return `SMS;-;${normalizedHandle}`;
|
|
715
|
-
if (service === "imessage") return `iMessage;-;${normalizedHandle}`;
|
|
716
|
-
return `any;-;${normalizedHandle}`;
|
|
717
|
-
}
|
|
718
|
-
if (params.target.kind !== "chat_id") return null;
|
|
719
|
-
const result = await params.runCliJson([
|
|
720
|
-
"group",
|
|
721
|
-
"--chat-id",
|
|
722
|
-
String(params.target.chatId)
|
|
723
|
-
]);
|
|
724
|
-
return normalizeOptionalString(result.guid) ?? normalizeOptionalString(result.chat_guid) ?? null;
|
|
725
|
-
}
|
|
726
|
-
async function trySendAttachmentForTarget(params) {
|
|
727
|
-
if (params.audioAsVoice && params.sendTransport === "applescript") throw new Error("iMessage voice messages require bridge transport; AppleScript cannot send native voice notes. Set sendTransport to bridge or auto.");
|
|
728
|
-
if (params.target.kind === "handle" && !params.audioAsVoice && params.sendTransport !== "bridge" && (params.service === "sms" || params.service === "imessage")) return null;
|
|
729
|
-
if (params.remoteHost && params.sendTransport === "applescript") return null;
|
|
730
|
-
let attachmentChatTarget = null;
|
|
731
|
-
if (params.remoteHost) {
|
|
732
|
-
if (params.target.kind === "chat_guid") attachmentChatTarget = params.target.chatGuid;
|
|
733
|
-
else if (params.target.kind === "chat_identifier") attachmentChatTarget = params.target.chatIdentifier;
|
|
734
|
-
else if (params.target.kind === "handle") {
|
|
735
|
-
const normalizedHandle = normalizeIMessageHandle(params.target.to);
|
|
736
|
-
if (normalizedHandle) {
|
|
737
|
-
const service = params.target.service !== "auto" ? params.target.service : params.service;
|
|
738
|
-
attachmentChatTarget = `${service === "sms" ? "SMS" : service === "imessage" ? "iMessage" : "any"};-;${normalizedHandle}`;
|
|
739
|
-
}
|
|
740
|
-
} else attachmentChatTarget = formatIMessageChatTarget(params.target.chatId);
|
|
741
|
-
} else try {
|
|
742
|
-
attachmentChatTarget = await resolveAttachmentChatTarget({
|
|
743
|
-
target: params.target,
|
|
744
|
-
service: params.service,
|
|
745
|
-
runCliJson: params.runCliJson
|
|
746
|
-
});
|
|
747
|
-
} catch (error) {
|
|
748
|
-
if (!params.audioAsVoice && isAttachmentCommandFallbackError(error)) return null;
|
|
749
|
-
throw error;
|
|
750
|
-
}
|
|
751
|
-
if (!attachmentChatTarget) {
|
|
752
|
-
if (params.audioAsVoice) throw new Error("iMessage voice messages require an existing chat and bridge transport.");
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
params.assertDirectAdapterHandoff?.();
|
|
756
|
-
const echoScope = resolveOutboundEchoScope({
|
|
757
|
-
accountId: params.accountId,
|
|
758
|
-
target: params.target
|
|
759
|
-
});
|
|
760
|
-
let result;
|
|
761
|
-
let pendingEchoKey;
|
|
762
|
-
try {
|
|
763
|
-
pendingEchoKey = await rememberPersistedIMessageEcho({
|
|
764
|
-
scope: echoScope,
|
|
765
|
-
text: params.echoText,
|
|
766
|
-
media: params.echoMedia,
|
|
767
|
-
ttlMs: params.pendingEchoTtlMs,
|
|
768
|
-
pending: true
|
|
769
|
-
});
|
|
770
|
-
result = await withOriginalIMessageAttachmentPath(params.filePath, async (attachmentPath) => {
|
|
771
|
-
if (params.remoteHost) return await params.withRemoteFile({
|
|
772
|
-
remoteHost: params.remoteHost,
|
|
773
|
-
localPath: attachmentPath,
|
|
774
|
-
timeoutMs: params.timeoutMs,
|
|
775
|
-
assertDirectAdapterHandoff: params.assertDirectAdapterHandoff,
|
|
776
|
-
use: async (remotePath) => {
|
|
777
|
-
const rpcParams = {
|
|
778
|
-
file: remotePath,
|
|
779
|
-
...params.audioAsVoice ? { audio: true } : {},
|
|
780
|
-
...params.replyToId ? { reply_to: params.replyToId } : {}
|
|
781
|
-
};
|
|
782
|
-
if (params.target.kind === "chat_id") rpcParams.chat_id = params.target.chatId;
|
|
783
|
-
else if (params.target.kind === "chat_guid") rpcParams.chat_guid = params.target.chatGuid;
|
|
784
|
-
else rpcParams.chat_identifier = attachmentChatTarget;
|
|
785
|
-
return await params.requestRpc("send.attachment", rpcParams);
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
params.assertDirectAdapterHandoff?.();
|
|
789
|
-
await params.onPlatformSendDispatch?.();
|
|
790
|
-
params.assertDirectAdapterHandoff?.();
|
|
791
|
-
return await params.runCliJson([
|
|
792
|
-
"send-attachment",
|
|
793
|
-
"--chat",
|
|
794
|
-
attachmentChatTarget,
|
|
795
|
-
"--file",
|
|
796
|
-
attachmentPath,
|
|
797
|
-
...params.audioAsVoice ? ["--audio"] : [],
|
|
798
|
-
...params.replyToId ? ["--reply-to", params.replyToId] : [],
|
|
799
|
-
"--transport",
|
|
800
|
-
params.sendTransport === "bridge" ? "dylib" : params.sendTransport
|
|
801
|
-
]);
|
|
802
|
-
});
|
|
803
|
-
const failure = resolveIMessageSendFailure(result);
|
|
804
|
-
if (failure) throw new Error(failure);
|
|
805
|
-
} catch (error) {
|
|
806
|
-
await forgetPersistedIMessageEchoKey(pendingEchoKey);
|
|
807
|
-
if (!params.audioAsVoice && isAttachmentCommandFallbackError(error)) return null;
|
|
808
|
-
throw error;
|
|
809
|
-
}
|
|
810
|
-
const resolvedId = resolveMessageId(result);
|
|
811
|
-
const approvalBindingMessageId = await resolveApprovalBindingMessageGuid({
|
|
812
|
-
dbPath: params.dbPath,
|
|
813
|
-
messageId: resolvedId,
|
|
814
|
-
result,
|
|
815
|
-
resolveMessageGuidImpl: params.resolveMessageGuidImpl
|
|
816
|
-
});
|
|
817
|
-
const messageId = resolvedId ?? (result.ok || result.success ? "ok" : "unknown");
|
|
818
|
-
await rememberPersistedIMessageEcho({
|
|
819
|
-
scope: echoScope,
|
|
820
|
-
text: params.echoText,
|
|
821
|
-
media: params.echoMedia,
|
|
822
|
-
messageId: resolvedId ?? void 0
|
|
823
|
-
});
|
|
824
|
-
if (resolvedId && normalizeIMessageMessageId(resolvedId)) await rememberIMessageReplyCache({
|
|
825
|
-
accountId: params.accountId,
|
|
826
|
-
messageId: resolvedId,
|
|
827
|
-
chatGuid: params.target.kind === "chat_guid" ? params.target.chatGuid : params.target.kind === "chat_id" ? attachmentChatTarget : void 0,
|
|
828
|
-
chatIdentifier: params.target.kind === "chat_identifier" || params.target.kind === "handle" ? attachmentChatTarget : void 0,
|
|
829
|
-
chatId: params.target.kind === "chat_id" ? params.target.chatId : void 0,
|
|
830
|
-
timestamp: Date.now(),
|
|
831
|
-
isFromMe: true
|
|
832
|
-
});
|
|
833
|
-
return {
|
|
834
|
-
messageId,
|
|
835
|
-
...approvalBindingMessageId ? { guid: approvalBindingMessageId } : {},
|
|
836
|
-
sentText: "",
|
|
837
|
-
...params.echoText ? { echoText: params.echoText } : {},
|
|
838
|
-
...params.echoMedia ? { echoMedia: params.echoMedia } : {},
|
|
839
|
-
receipt: createIMessageSendReceipt({
|
|
840
|
-
messageId,
|
|
841
|
-
target: params.target,
|
|
842
|
-
kind: params.audioAsVoice ? "voice" : "media",
|
|
843
|
-
...params.replyToId ? { replyToId: params.replyToId } : {}
|
|
844
|
-
})
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
681
|
async function sendMessageIMessage(to, text, opts) {
|
|
848
682
|
const cfg = requireRuntimeConfig(opts.config, "iMessage send");
|
|
849
683
|
opts.assertDirectAdapterHandoff?.();
|
|
@@ -885,7 +719,7 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
885
719
|
});
|
|
886
720
|
opts.assertDirectAdapterHandoff?.();
|
|
887
721
|
const timeoutMs = opts.timeoutMs ?? Math.max(account.config.probeTimeoutMs ?? 0, 18e4);
|
|
888
|
-
const pendingEchoTtlMs =
|
|
722
|
+
const pendingEchoTtlMs = Math.max(MIN_PENDING_PERSISTED_ECHO_TTL_MS, Math.max(0, timeoutMs) + PENDING_PERSISTED_ECHO_GRACE_MS);
|
|
889
723
|
const region = opts.region?.trim() || account.config.region?.trim() || "US";
|
|
890
724
|
const maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : 16777216;
|
|
891
725
|
let message = opts.approvalPrompt ? addApprovalReactionHintToText({
|
|
@@ -928,18 +762,16 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
928
762
|
message = formatted.text;
|
|
929
763
|
if (!message.trim() && !filePath) throw new Error("iMessage send requires text or media");
|
|
930
764
|
const echoText = message.trim() || void 0;
|
|
931
|
-
const echoMedia = filePath
|
|
765
|
+
const echoMedia = filePath && mediaContentType ? {
|
|
766
|
+
contentType: mediaContentType,
|
|
767
|
+
kind: kindFromMime(mediaContentType) ?? "unknown"
|
|
768
|
+
} : void 0;
|
|
932
769
|
let effectiveReplyToId = resolvedReplyToId;
|
|
933
|
-
const
|
|
934
|
-
args,
|
|
770
|
+
const runCliJson = bindIMessageCliSend(opts, {
|
|
935
771
|
cliPath,
|
|
936
772
|
dbPath,
|
|
937
773
|
timeoutMs
|
|
938
|
-
})
|
|
939
|
-
const runCliJson = async (args) => {
|
|
940
|
-
opts.assertDirectAdapterHandoff?.();
|
|
941
|
-
return await runCli(args);
|
|
942
|
-
};
|
|
774
|
+
});
|
|
943
775
|
const requestOwnedRpc = async (method, rpcParams) => {
|
|
944
776
|
opts.assertDirectAdapterHandoff?.();
|
|
945
777
|
const rpcClient = await (opts.createClient ?? createIMessageRpcClient)({
|
|
@@ -954,27 +786,130 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
954
786
|
}
|
|
955
787
|
};
|
|
956
788
|
const withRemoteFile = opts.withRemoteFile ?? withIMessageRemoteFile;
|
|
957
|
-
|
|
958
|
-
const
|
|
959
|
-
|
|
789
|
+
async function trySendAttachment(attachmentFilePath) {
|
|
790
|
+
const { audioAsVoice, resolveMessageGuidImpl, assertDirectAdapterHandoff, onPlatformSendDispatch } = opts;
|
|
791
|
+
const accountId = account.accountId;
|
|
792
|
+
if (audioAsVoice && sendTransport === "applescript") throw new Error("iMessage voice messages require bridge transport; AppleScript cannot send native voice notes. Set sendTransport to bridge or auto.");
|
|
793
|
+
if (target.kind === "handle" && !audioAsVoice && sendTransport !== "bridge" && (service === "sms" || service === "imessage")) return null;
|
|
794
|
+
if (remoteHost && sendTransport === "applescript") return null;
|
|
795
|
+
let attachmentChatTarget = null;
|
|
796
|
+
if (target.kind === "chat_guid") attachmentChatTarget = target.chatGuid;
|
|
797
|
+
else if (target.kind === "handle") {
|
|
798
|
+
const rawHandle = target.to.trim();
|
|
799
|
+
if (remoteHost || rawHandle.includes("@") || rawHandle.startsWith("+")) {
|
|
800
|
+
const normalizedHandle = normalizeIMessageHandle(target.to);
|
|
801
|
+
if (normalizedHandle) {
|
|
802
|
+
const attachmentService = target.service !== "auto" ? target.service : service;
|
|
803
|
+
attachmentChatTarget = `${attachmentService === "sms" ? "SMS" : attachmentService === "imessage" ? "iMessage" : "any"};-;${normalizedHandle}`;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} else if (remoteHost) attachmentChatTarget = target.kind === "chat_identifier" ? target.chatIdentifier : formatIMessageChatTarget(target.chatId);
|
|
807
|
+
else if (target.kind === "chat_id") try {
|
|
808
|
+
const result = await runCliJson([
|
|
809
|
+
"group",
|
|
810
|
+
"--chat-id",
|
|
811
|
+
String(target.chatId)
|
|
812
|
+
]);
|
|
813
|
+
attachmentChatTarget = normalizeOptionalString(result.guid) ?? normalizeOptionalString(result.chat_guid) ?? null;
|
|
814
|
+
} catch (error) {
|
|
815
|
+
if (!audioAsVoice && isAttachmentCommandFallbackError(error)) return null;
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
if (!attachmentChatTarget) {
|
|
819
|
+
if (audioAsVoice) throw new Error("iMessage voice messages require an existing chat and bridge transport.");
|
|
820
|
+
return null;
|
|
821
|
+
}
|
|
822
|
+
assertDirectAdapterHandoff?.();
|
|
823
|
+
const echoScope = resolveOutboundEchoScope({
|
|
824
|
+
accountId,
|
|
825
|
+
target
|
|
826
|
+
});
|
|
827
|
+
let result;
|
|
828
|
+
let pendingEchoKey;
|
|
829
|
+
try {
|
|
830
|
+
pendingEchoKey = await rememberPersistedIMessageEcho({
|
|
831
|
+
scope: echoScope,
|
|
832
|
+
media: echoMedia,
|
|
833
|
+
ttlMs: pendingEchoTtlMs,
|
|
834
|
+
pending: true
|
|
835
|
+
});
|
|
836
|
+
result = await withOriginalIMessageAttachmentPath(attachmentFilePath, async (attachmentPath) => {
|
|
837
|
+
if (remoteHost) return await withRemoteFile({
|
|
838
|
+
remoteHost,
|
|
839
|
+
localPath: attachmentPath,
|
|
840
|
+
timeoutMs,
|
|
841
|
+
assertDirectAdapterHandoff,
|
|
842
|
+
use: async (remotePath) => {
|
|
843
|
+
const rpcParams = {
|
|
844
|
+
file: remotePath,
|
|
845
|
+
...audioAsVoice ? { audio: true } : {},
|
|
846
|
+
...resolvedReplyToId ? { reply_to: resolvedReplyToId } : {}
|
|
847
|
+
};
|
|
848
|
+
if (target.kind === "chat_id") rpcParams.chat_id = target.chatId;
|
|
849
|
+
else if (target.kind === "chat_guid") rpcParams.chat_guid = target.chatGuid;
|
|
850
|
+
else rpcParams.chat_identifier = attachmentChatTarget;
|
|
851
|
+
return await requestOwnedRpc("send.attachment", rpcParams);
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
assertDirectAdapterHandoff?.();
|
|
855
|
+
await onPlatformSendDispatch?.();
|
|
856
|
+
assertDirectAdapterHandoff?.();
|
|
857
|
+
return await runCliJson([
|
|
858
|
+
"send-attachment",
|
|
859
|
+
"--chat",
|
|
860
|
+
attachmentChatTarget,
|
|
861
|
+
"--file",
|
|
862
|
+
attachmentPath,
|
|
863
|
+
...audioAsVoice ? ["--audio"] : [],
|
|
864
|
+
...resolvedReplyToId ? ["--reply-to", resolvedReplyToId] : [],
|
|
865
|
+
"--transport",
|
|
866
|
+
sendTransport === "bridge" ? "dylib" : sendTransport
|
|
867
|
+
]);
|
|
868
|
+
});
|
|
869
|
+
const failure = resolveIMessageSendFailure(result);
|
|
870
|
+
if (failure) throw new Error(failure);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
await forgetPersistedIMessageEchoKey(pendingEchoKey);
|
|
873
|
+
if (!audioAsVoice && isAttachmentCommandFallbackError(error)) return null;
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
const resolvedId = resolveMessageId(result);
|
|
877
|
+
const approvalBindingMessageId = await resolveApprovalBindingMessageGuid({
|
|
960
878
|
dbPath: chatDbLookupPath,
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
filePath,
|
|
965
|
-
audioAsVoice: opts.audioAsVoice,
|
|
966
|
-
...resolvedReplyToId ? { replyToId: resolvedReplyToId } : {},
|
|
967
|
-
echoMedia,
|
|
968
|
-
pendingEchoTtlMs,
|
|
969
|
-
timeoutMs,
|
|
970
|
-
remoteHost,
|
|
971
|
-
runCliJson,
|
|
972
|
-
requestRpc: requestOwnedRpc,
|
|
973
|
-
withRemoteFile,
|
|
974
|
-
resolveMessageGuidImpl: opts.resolveMessageGuidImpl,
|
|
975
|
-
assertDirectAdapterHandoff: opts.assertDirectAdapterHandoff,
|
|
976
|
-
onPlatformSendDispatch: opts.onPlatformSendDispatch
|
|
879
|
+
messageId: resolvedId,
|
|
880
|
+
result,
|
|
881
|
+
resolveMessageGuidImpl
|
|
977
882
|
});
|
|
883
|
+
const messageId = resolvedId ?? (result.ok || result.success ? "ok" : "unknown");
|
|
884
|
+
await rememberPersistedIMessageEcho({
|
|
885
|
+
scope: echoScope,
|
|
886
|
+
media: echoMedia,
|
|
887
|
+
messageId: resolvedId ?? void 0
|
|
888
|
+
});
|
|
889
|
+
if (resolvedId && normalizeIMessageMessageId(resolvedId)) await rememberIMessageReplyCache({
|
|
890
|
+
accountId,
|
|
891
|
+
messageId: resolvedId,
|
|
892
|
+
chatGuid: target.kind === "chat_guid" ? target.chatGuid : target.kind === "chat_id" ? attachmentChatTarget : void 0,
|
|
893
|
+
chatIdentifier: target.kind === "chat_identifier" || target.kind === "handle" ? attachmentChatTarget : void 0,
|
|
894
|
+
chatId: target.kind === "chat_id" ? target.chatId : void 0,
|
|
895
|
+
timestamp: Date.now(),
|
|
896
|
+
isFromMe: true
|
|
897
|
+
});
|
|
898
|
+
return {
|
|
899
|
+
messageId,
|
|
900
|
+
...approvalBindingMessageId ? { guid: approvalBindingMessageId } : {},
|
|
901
|
+
sentText: "",
|
|
902
|
+
...echoMedia ? { echoMedia } : {},
|
|
903
|
+
receipt: createIMessageSendReceipt({
|
|
904
|
+
messageId,
|
|
905
|
+
target,
|
|
906
|
+
kind: audioAsVoice ? "voice" : "media",
|
|
907
|
+
...resolvedReplyToId ? { replyToId: resolvedReplyToId } : {}
|
|
908
|
+
})
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
if (filePath && (!resolvedReplyToId || opts.audioAsVoice)) {
|
|
912
|
+
const attachmentResult = await trySendAttachment(filePath);
|
|
978
913
|
if (attachmentResult) {
|
|
979
914
|
if (!message.trim()) return attachmentResult;
|
|
980
915
|
await opts.onDeliveryResult?.({
|