@openclaw/imessage 2026.8.1-beta.2 → 2026.8.1-beta.3

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.
@@ -77,6 +77,9 @@ function setCachedIMessagePrivateApiStatus(cliPath, status, expiresAt = 0) {
77
77
  //#endregion
78
78
  //#region extensions/imessage/src/target-identifiers.ts
79
79
  const BARE_CHAT_IDENTIFIER_RE = /^[0-9a-f]{32}$/i;
80
+ function isIMessagePhoneLikeHandle(raw) {
81
+ return /^(?:tel:)?[+\d\s().-]+$/i.test(raw.trim());
82
+ }
80
83
  function normalizeBareIMessageChatIdentifier(raw) {
81
84
  const trimmed = raw.trim();
82
85
  if (!BARE_CHAT_IDENTIFIER_RE.test(trimmed)) return;
@@ -136,7 +139,7 @@ function normalizeIMessageHandle(raw) {
136
139
  if (trimmed.includes("@")) return normalizeLowercaseStringOrEmpty(trimmed);
137
140
  const bareChatIdentifier = normalizeBareIMessageChatIdentifier(trimmed);
138
141
  if (bareChatIdentifier) return `chat_identifier:${bareChatIdentifier}`;
139
- const normalized = normalizeE164(trimmed);
142
+ const normalized = isIMessagePhoneLikeHandle(trimmed) ? normalizeE164(trimmed) : "";
140
143
  if (normalized) return normalized;
141
144
  return trimmed.replace(/\s+/g, "");
142
145
  }
@@ -312,4 +315,4 @@ function describeIMessageMessageTool({ cfg, accountId, currentChannelId }) {
312
315
  };
313
316
  }
314
317
  //#endregion
315
- export { IMESSAGE_ACTION_NAMES as S, normalizeBareIMessageChatIdentifier as _, isAllowedIMessageSender as a, setCachedIMessagePrivateApiStatus as b, parseIMessageAllowTarget as c, parseChatAllowTargetPrefixes as d, parseChatTargetPrefixesOrThrow as f, resolveServicePrefixedTarget as g, resolveServicePrefixedOrChatAllowTarget as h, isAllowedIMessageReplyContextSender as i, parseIMessageTarget as l, resolveServicePrefixedChatTarget as m, formatIMessageChatTarget as n, looksLikeIMessageExplicitTargetId as o, resolveServicePrefixedAllowTarget as p, inferIMessageTargetChatType as r, normalizeIMessageHandle as s, describeIMessageMessageTool as t, createAllowedChatSenderMatcher as u, getCachedIMessagePrivateApiStatus as v, IMESSAGE_ACTIONS as x, imessageRpcSupportsMethod as y };
318
+ export { IMESSAGE_ACTION_NAMES as C, IMESSAGE_ACTIONS as S, isIMessagePhoneLikeHandle as _, isAllowedIMessageSender as a, imessageRpcSupportsMethod as b, parseIMessageAllowTarget as c, parseChatAllowTargetPrefixes as d, parseChatTargetPrefixesOrThrow as f, resolveServicePrefixedTarget as g, resolveServicePrefixedOrChatAllowTarget as h, isAllowedIMessageReplyContextSender as i, parseIMessageTarget as l, resolveServicePrefixedChatTarget as m, formatIMessageChatTarget as n, looksLikeIMessageExplicitTargetId as o, resolveServicePrefixedAllowTarget as p, inferIMessageTargetChatType as r, normalizeIMessageHandle as s, describeIMessageMessageTool as t, createAllowedChatSenderMatcher as u, normalizeBareIMessageChatIdentifier as v, setCachedIMessagePrivateApiStatus as x, getCachedIMessagePrivateApiStatus as y };
@@ -1,2 +1,2 @@
1
- import { t as describeIMessageMessageTool } from "./message-tool-api-CPDfJJoE.js";
1
+ import { t as describeIMessageMessageTool } from "./message-tool-api-TZiNYA17.js";
2
2
  export { describeIMessageMessageTool as describeMessageTool };
@@ -1,13 +1,15 @@
1
- import { C as registerIMessageApprovalReactionTarget, D as buildIMessageApprovalConversationKeyForInbound, E as resolveIMessageReactionContext, I as probeIMessage, S as maybeResolveIMessageApprovalReaction, b as handleIMessageApprovalReaction, r as resolveIMessageInboundConversationId, x as listPendingIMessageApprovalReactionPollTargets, y as extractIMessageApprovalPromptBinding } from "./group-policy-DF9xSdik.js";
2
1
  import { d as resolveIMessageChatDbLookupPath, f as resolveIMessageHomeDir, l as resolveIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-D00Njxg2.js";
3
- import { c as parseIMessageAllowTarget, i as isAllowedIMessageReplyContextSender, l as parseIMessageTarget, n as formatIMessageChatTarget, s as normalizeIMessageHandle, v as getCachedIMessagePrivateApiStatus, y as imessageRpcSupportsMethod } from "./message-tool-api-CPDfJJoE.js";
4
- import { c as rememberIMessageReplyCache, f as getIMessageRuntime, s as isKnownFromMeIMessageMessageId } from "./monitor-reply-cache-DKlYGd5x.js";
5
- import { o as createIMessageRpcClient, r as sanitizeOutboundText } from "./sanitize-outbound-CoTZLXMw.js";
2
+ import { b as imessageRpcSupportsMethod, c as parseIMessageAllowTarget, i as isAllowedIMessageReplyContextSender, l as parseIMessageTarget, n as formatIMessageChatTarget, s as normalizeIMessageHandle, y as getCachedIMessagePrivateApiStatus } from "./message-tool-api-TZiNYA17.js";
3
+ import { c as rememberIMessageReplyCache, f as getIMessageRuntime, s as isKnownFromMeIMessageMessageId, v as resolveIMessageDirectChatService } from "./monitor-reply-cache-BdeUQaHO.js";
4
+ import { o as createIMessageRpcClient, r as sanitizeOutboundText } from "./sanitize-outbound-DaLmdkbf.js";
5
+ import { r as resolveIMessageInboundConversationId, y as probeIMessage } from "./group-policy-Ca3c5KES.js";
6
6
  import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-BEgdvYhW.js";
7
- import { a as resolveCatchupConfig, i as performIMessageCatchup, n as advanceIMessageCatchupCursor } from "./catchup-D32oYNxR.js";
8
- import { c as stripLeadingEchoTextCorruptionMarkers, i as hasPersistedIMessageEcho, o as resolveIMessageEchoMediaKey } from "./persisted-echo-cache-BPWGUK-L.js";
9
- import { a as maybeResolveIMessageApprovalPollVote, o as iMessageApprovalControlBindings, t as sendMessageIMessage } from "./send-BUXW76tw.js";
10
- import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-DVe50UhB.js";
7
+ import { a as resolveCatchupConfig, i as performIMessageCatchup, n as advanceIMessageCatchupCursor } from "./catchup-Dh-Rv4Ur.js";
8
+ import { c as stripLeadingEchoTextCorruptionMarkers, i as hasPersistedIMessageEcho, o as resolveIMessageEchoMediaKey } from "./persisted-echo-cache-DGxy-J4t.js";
9
+ import { i as registerIMessageApprovalReactionTarget, n as handleIMessageApprovalReaction, o as listPendingIMessageApprovalReactionPollTargets, r as maybeResolveIMessageApprovalReaction, s as buildIMessageApprovalConversationKeyForInbound } from "./approval-reactions-BME5eA6T.js";
10
+ import { a as maybeResolveIMessageApprovalPollVote, o as iMessageApprovalControlBindings, t as sendMessageIMessage } from "./send-Dake4Awx.js";
11
+ import { t as resolveIMessageReactionContext } from "./reaction-context-DaNrZqYo.js";
12
+ import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-to0i8vxE.js";
11
13
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, listMessageReceiptPlatformIds, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
12
14
  import { chunkMarkdownTextWithMode as chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
13
15
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
@@ -16,7 +18,7 @@ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
16
18
  import { asSafeIntegerInRange, hasNonEmptyString, isRecord, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
17
19
  import fs from "node:fs/promises";
18
20
  import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
19
- import { asDateTimestampMs, asPositiveFiniteNumber, parseDateStringTimestampMs, resolveExpiresAtMsFromDurationMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
21
+ import { asDateTimestampMs, asPositiveFiniteNumber, parseDateStringTimestampMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
20
22
  import { createHash } from "node:crypto";
21
23
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
22
24
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -33,7 +35,7 @@ import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
33
35
  import { createChannelIngressResolver, defineStableChannelIngressIdentity, fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
34
36
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
35
37
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
36
- import { readChannelAllowFromStore, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
38
+ import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
37
39
  import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
38
40
  import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
39
41
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
@@ -55,8 +57,6 @@ import { enqueueSystemEvent } from "openclaw/plugin-sdk/system-event-runtime";
55
57
  //#region extensions/imessage/src/approval-reaction-poller.ts
56
58
  const RECENT_CHAT_LIMIT = 50;
57
59
  const PER_CHAT_HISTORY_LIMIT = 30;
58
- const OBSERVED_APPROVAL_PROMPT_TARGET_TTL_MS = 300 * 1e3;
59
- const accountIdsWithCompletedNoTargetDiscovery = /* @__PURE__ */ new Set();
60
60
  function normalizeChatId(value) {
61
61
  return asPositiveFiniteNumber(value) ?? null;
62
62
  }
@@ -150,42 +150,13 @@ function bindObservedConversation(params) {
150
150
  ttlMs
151
151
  });
152
152
  }
153
- function bindObservedApprovalPrompt(params) {
154
- if (params.message.is_from_me !== true) return null;
155
- const messageId = params.message.guid?.trim();
156
- if (!messageId) return null;
157
- const binding = extractIMessageApprovalPromptBinding(params.message.text ?? "");
158
- if (!binding) return null;
159
- const conversation = buildConversationKeyFromMessage(params.message);
160
- const expiresAtMs = resolveExpiresAtMsFromDurationMs(OBSERVED_APPROVAL_PROMPT_TARGET_TTL_MS);
161
- if (expiresAtMs === void 0) return null;
162
- const target = {
163
- accountId: params.accountId,
164
- conversation,
165
- messageId,
166
- approvalId: binding.approvalId,
167
- approvalKind: binding.approvalKind,
168
- allowedDecisions: binding.allowedDecisions,
169
- expiresAtMs
170
- };
171
- bindObservedConversation({
172
- target,
173
- message: params.message
174
- });
175
- return target;
176
- }
177
153
  async function pollPendingIMessageApprovalReactions(params) {
178
- const targets = listPendingIMessageApprovalReactionPollTargets({ accountId: params.accountId });
179
- const shouldAttemptNoTargetDiscovery = targets.length === 0 && params.allowRecentChatDiscovery === true && !accountIdsWithCompletedNoTargetDiscovery.has(params.accountId);
180
- if (targets.length === 0 && !shouldAttemptNoTargetDiscovery) return;
154
+ const targets = await listPendingIMessageApprovalReactionPollTargets({ accountId: params.accountId });
155
+ if (targets.length === 0) return;
181
156
  const pendingByMessageId = buildPendingTargetsByMessageId(targets);
182
157
  const explicitChatIds = listTargetChatIds(targets);
183
- const chatIds = params.allowRecentChatDiscovery === true && (targets.length === 0 || hasUnscopedTarget(targets)) ? uniqueChatIds([...explicitChatIds, ...await listRecentChatIds(params.client)]) : explicitChatIds;
184
- if (chatIds.length === 0) {
185
- if (shouldAttemptNoTargetDiscovery) accountIdsWithCompletedNoTargetDiscovery.add(params.accountId);
186
- return;
187
- }
188
- let hadHistoryFetchError = false;
158
+ const chatIds = params.allowRecentChatDiscovery === true && targets.length > 0 && hasUnscopedTarget(targets) ? uniqueChatIds([...explicitChatIds, ...await listRecentChatIds(params.client)]) : explicitChatIds;
159
+ if (chatIds.length === 0) return;
189
160
  for (const chatId of chatIds) {
190
161
  let messages;
191
162
  try {
@@ -194,17 +165,13 @@ async function pollPendingIMessageApprovalReactions(params) {
194
165
  chatId
195
166
  });
196
167
  } catch (err) {
197
- hadHistoryFetchError = true;
198
168
  params.logVerboseMessage?.(`imessage: approval reaction poll skipped chat_id=${chatId}: ${String(err)}`);
199
169
  continue;
200
170
  }
201
171
  for (const message of messages) {
202
172
  const targetGuid = message.guid?.trim();
203
173
  if (!targetGuid) continue;
204
- const target = pendingByMessageId.get(targetGuid) ?? pendingByMessageId.get(normalizeMessageGuid(targetGuid)) ?? bindObservedApprovalPrompt({
205
- accountId: params.accountId,
206
- message
207
- });
174
+ const target = pendingByMessageId.get(targetGuid) ?? pendingByMessageId.get(normalizeMessageGuid(targetGuid));
208
175
  if (!target) continue;
209
176
  bindObservedConversation({
210
177
  target,
@@ -216,22 +183,17 @@ async function pollPendingIMessageApprovalReactions(params) {
216
183
  reaction
217
184
  });
218
185
  if (!reactionPayload) continue;
219
- const handled = await handleIMessageApprovalReaction({
186
+ if ((await handleIMessageApprovalReaction({
220
187
  cfg: params.cfg,
221
188
  accountId: params.accountId,
222
189
  message: reactionPayload,
223
190
  bodyText: reactionPayload.text ?? "",
224
191
  gatewayRuntime: params.gatewayRuntime,
225
192
  logVerboseMessage: params.logVerboseMessage
226
- });
227
- if (handled.stopPolling) {
228
- if (shouldAttemptNoTargetDiscovery && handled.stopPollingReason !== "resolver-error") break;
229
- return;
230
- }
193
+ })).stopPolling) return;
231
194
  }
232
195
  }
233
196
  }
234
- if (shouldAttemptNoTargetDiscovery && !hadHistoryFetchError) accountIdsWithCompletedNoTargetDiscovery.add(params.accountId);
235
197
  }
236
198
  //#endregion
237
199
  //#region extensions/imessage/src/chat.ts
@@ -1033,7 +995,7 @@ function isStaleIMessageBacklog(message, nowMs, thresholdMs = IMESSAGE_STALE_INB
1033
995
  //#endregion
1034
996
  //#region extensions/imessage/src/conversation-route.ts
1035
997
  function resolveIMessageConversationRoute(params) {
1036
- let route = resolveAgentRoute({
998
+ const route = resolveAgentRoute({
1037
999
  cfg: params.cfg,
1038
1000
  channel: "imessage",
1039
1001
  accountId: params.accountId,
@@ -1047,28 +1009,30 @@ function resolveIMessageConversationRoute(params) {
1047
1009
  sender: params.sender,
1048
1010
  chatId: params.chatId
1049
1011
  });
1050
- if (!conversationId) return route;
1051
- route = resolveConfiguredBindingRoute({
1012
+ if (!conversationId) return {
1013
+ route,
1014
+ bindingResolution: null
1015
+ };
1016
+ const conversation = {
1017
+ channel: "imessage",
1018
+ accountId: params.accountId,
1019
+ conversationId
1020
+ };
1021
+ const configuredRoute = resolveConfiguredBindingRoute({
1052
1022
  cfg: params.cfg,
1053
1023
  route,
1054
- conversation: {
1055
- channel: "imessage",
1056
- accountId: params.accountId,
1057
- conversationId
1058
- }
1059
- }).route;
1024
+ conversation
1025
+ });
1060
1026
  const runtimeRoute = resolveRuntimeConversationBindingRoute({
1061
- route,
1062
- conversation: {
1063
- channel: "imessage",
1064
- accountId: params.accountId,
1065
- conversationId
1066
- }
1027
+ route: configuredRoute.route,
1028
+ conversation
1067
1029
  });
1068
- route = runtimeRoute.route;
1069
1030
  if (runtimeRoute.bindingRecord && !runtimeRoute.boundSessionKey) logVerbose(`imessage: plugin-bound conversation ${conversationId}`);
1070
1031
  else if (runtimeRoute.boundSessionKey) logVerbose(`imessage: routed via bound conversation ${conversationId} -> ${runtimeRoute.boundSessionKey}`);
1071
- return route;
1032
+ return {
1033
+ route: runtimeRoute.route,
1034
+ bindingResolution: runtimeRoute.bindingRecord ? null : configuredRoute.bindingResolution
1035
+ };
1072
1036
  }
1073
1037
  //#endregion
1074
1038
  //#region extensions/imessage/src/monitor/reflection-guard.ts
@@ -1391,7 +1355,7 @@ async function resolveIMessageInboundDecision(params) {
1391
1355
  const groupId = isGroup ? groupIdCandidate : void 0;
1392
1356
  const hasControlCommandInMessage = hasControlCommand(messageText, params.cfg);
1393
1357
  const groupAllowFromForAccess = isGroup ? groupAllowFromWithLegacyChatTargets : params.groupAllowFrom;
1394
- const route = resolveIMessageConversationRoute({
1358
+ const { route, bindingResolution } = resolveIMessageConversationRoute({
1395
1359
  cfg: params.cfg,
1396
1360
  accountId: params.accountId,
1397
1361
  isGroup,
@@ -1689,6 +1653,7 @@ async function resolveIMessageInboundDecision(params) {
1689
1653
  sender,
1690
1654
  senderNormalized,
1691
1655
  route,
1656
+ bindingResolution,
1692
1657
  bodyText,
1693
1658
  createdAt,
1694
1659
  replyContext: filteredReplyContext,
@@ -1754,11 +1719,8 @@ async function buildIMessageInboundContext(params) {
1754
1719
  envelope: envelopeOptions
1755
1720
  })
1756
1721
  });
1757
- const imessageTo = decision.isGroup ? chatTarget || `imessage:${decision.sender}` : buildDirectIMessageReplyTarget({
1758
- cfg: params.cfg,
1759
- accountId: decision.route.accountId,
1760
- sender: decision.sender
1761
- });
1722
+ const directService = resolveIMessageDirectChatService(params.accountService, decision.chatGuid) ?? "auto";
1723
+ const imessageTo = decision.isGroup ? chatTarget || `imessage:${decision.sender}` : `${directService}:${decision.sender}`;
1762
1724
  const imessageFrom = decision.isGroup ? `imessage:group:${chatId ?? "unknown"}` : imessageTo;
1763
1725
  const inboundHistory = !decision.isGroup && params.dmHistory?.inboundHistory ? params.dmHistory.inboundHistory : decision.isGroup && decision.historyKey && params.historyLimit > 0 ? createChannelHistoryWindow({ historyMap: params.groupHistories }).buildInboundHistory({
1764
1726
  historyKey: decision.historyKey,
@@ -1792,6 +1754,10 @@ async function buildIMessageInboundContext(params) {
1792
1754
  conversation: {
1793
1755
  kind: decision.isGroup ? "group" : "direct",
1794
1756
  id: chatId != null ? String(chatId) : decision.sender,
1757
+ ...decision.isGroup && chatId == null ? {} : { routePeer: {
1758
+ kind: decision.isGroup ? "group" : "direct",
1759
+ id: decision.isGroup ? String(chatId) : decision.senderNormalized
1760
+ } },
1795
1761
  label: fromLabel
1796
1762
  },
1797
1763
  route: {
@@ -1840,13 +1806,6 @@ function buildIMessageEchoScope(params) {
1840
1806
  if (params.chatIdentifier) scopes.push(`${params.accountId}:chat_identifier:${params.chatIdentifier}`);
1841
1807
  return scopes;
1842
1808
  }
1843
- function buildDirectIMessageReplyTarget(params) {
1844
- if (resolveIMessageAccount({
1845
- cfg: params.cfg,
1846
- accountId: params.accountId
1847
- }).config.service === "sms") return `sms:${params.sender}`;
1848
- return `imessage:${params.sender}`;
1849
- }
1850
1809
  function describeIMessageEchoDropLog(params) {
1851
1810
  const preview = truncateUtf16Safe(params.messageText, 50);
1852
1811
  return `imessage: skipping echo message${params.messageId ? ` id=${params.messageId}` : ""}: "${preview}"`;
@@ -2956,6 +2915,16 @@ async function monitorIMessageProvider(opts = {}) {
2956
2915
  });
2957
2916
  return;
2958
2917
  }
2918
+ if (decision.bindingResolution) {
2919
+ const readiness = await ensureConfiguredBindingRouteReady({
2920
+ cfg,
2921
+ bindingResolution: decision.bindingResolution
2922
+ });
2923
+ if (!readiness.ok) {
2924
+ runtime.error?.(`imessage: dropped inbound message; configured ACP binding unavailable for ${decision.bindingResolution.record.conversation.conversationId}: ${readiness.error}`);
2925
+ return;
2926
+ }
2927
+ }
2959
2928
  const storePath = resolveStorePath(cfg.session?.store, { agentId: decision.route.agentId });
2960
2929
  const privateApiStatus = getCachedIMessagePrivateApiStatus(cliPath);
2961
2930
  const supportsTyping = imessageRpcSupportsMethod(privateApiStatus, "typing");
@@ -2975,11 +2944,9 @@ async function monitorIMessageProvider(opts = {}) {
2975
2944
  chatType: decision.isGroup ? "group" : "direct"
2976
2945
  });
2977
2946
  const shouldUseDirectToolTypingOptions = !decision.isGroup && sendPolicy !== "deny" && (configuredTypingMode === void 0 || configuredTypingMode === "instant");
2978
- const earlyDirectTypingTarget = supportsTyping && shouldUseDirectToolTypingOptions ? buildDirectIMessageReplyTarget({
2979
- cfg,
2980
- accountId: decision.route.accountId,
2981
- sender: decision.sender
2982
- }) : void 0;
2947
+ const shouldStartDirectTyping = supportsTyping && shouldUseDirectToolTypingOptions;
2948
+ const earlyDirectTypingService = resolveIMessageDirectChatService(imessageCfg.service, decision.chatGuid) ?? "auto";
2949
+ const earlyDirectTypingTarget = shouldStartDirectTyping ? `${earlyDirectTypingService}:${decision.sender}` : void 0;
2983
2950
  let stopEarlyDirectTyping;
2984
2951
  if (earlyDirectTypingTarget) {
2985
2952
  const earlyDirectTypingStarted = sendIMessageTyping(earlyDirectTypingTarget, true, {
@@ -3056,8 +3023,10 @@ async function monitorIMessageProvider(opts = {}) {
3056
3023
  envelopeOptions: resolveEnvelopeFormatOptions(cfg),
3057
3024
  logVerbose
3058
3025
  }) : void 0;
3026
+ const pluginChannelRuntime = opts.channelRuntime;
3059
3027
  const { ctxPayload, chatTarget, imessageTo } = await buildIMessageInboundContext({
3060
3028
  cfg,
3029
+ accountService: imessageCfg.service,
3061
3030
  decision: contextDecision,
3062
3031
  message,
3063
3032
  previousTimestamp,
@@ -3065,7 +3034,7 @@ async function monitorIMessageProvider(opts = {}) {
3065
3034
  historyLimit,
3066
3035
  groupHistories,
3067
3036
  dmHistory,
3068
- buildContext: opts.channelRuntime?.inbound.buildContext,
3037
+ buildContext: pluginChannelRuntime?.inbound.buildContext,
3069
3038
  media: { facts: mediaAttachments }
3070
3039
  });
3071
3040
  const updateTarget = chatTarget || imessageTo;
@@ -3080,7 +3049,8 @@ async function monitorIMessageProvider(opts = {}) {
3080
3049
  }
3081
3050
  const sendReadReceipts = imessageCfg.sendReadReceipts !== false;
3082
3051
  const typingTarget = ctxPayload.To;
3083
- if (supportsRead && sendReadReceipts && typingTarget) markIMessageChatRead(typingTarget, {
3052
+ const readTarget = !decision.isGroup && decision.chatGuid ? `chat_guid:${decision.chatGuid}` : typingTarget;
3053
+ if (supportsRead && sendReadReceipts && readTarget) markIMessageChatRead(readTarget, {
3084
3054
  cfg,
3085
3055
  accountId: accountInfo.accountId,
3086
3056
  cliPath,
@@ -3217,6 +3187,7 @@ async function monitorIMessageProvider(opts = {}) {
3217
3187
  sessionKey: decision.route.sessionKey
3218
3188
  },
3219
3189
  ctxPayload,
3190
+ dispatchReplyFromConfig: pluginChannelRuntime?.reply?.dispatchReplyFromConfig,
3220
3191
  record: {
3221
3192
  updateLastRoute: !decision.isGroup && updateTarget ? {
3222
3193
  sessionKey: inboundLastRouteSessionKey,
@@ -21,6 +21,11 @@ function parseDirectChatIdentity(raw) {
21
21
  if (parts.length !== 1) return;
22
22
  if (trimmed.startsWith("+") || EMAIL_HANDLE_PATTERN.test(trimmed)) return { identifier: trimmed.toLowerCase() };
23
23
  }
24
+ function resolveIMessageDirectChatService(configuredService, chatGuid) {
25
+ if (configuredService === "imessage" || configuredService === "sms") return configuredService;
26
+ const observedService = chatGuid ? parseDirectChatIdentity(chatGuid)?.service : void 0;
27
+ return observedService === "imessage" || observedService === "sms" ? observedService : void 0;
28
+ }
24
29
  function isIMessageEmailChatIdentifier(raw) {
25
30
  const identity = parseDirectChatIdentity(raw);
26
31
  return Boolean(identity && EMAIL_HANDLE_PATTERN.test(identity.identifier));
@@ -345,4 +350,4 @@ function isIMessageCurrentMessageInChat(params) {
345
350
  }) === "match";
346
351
  }
347
352
  //#endregion
348
- export { normalizeDirectChatIdentifier as _, findLatestIMessageEntryForChat as a, rememberIMessageReplyCache as c, resolveIMessageReplyCacheEntryKey as d, getIMessageRuntime as f, isIMessageEmailChatIdentifier as g, chatContextFromIMessageTarget as h, IMESSAGE_REPLY_CACHE_NAMESPACE as i, resolveIMessageCachedResourceBinding as l, setIMessageRuntime as m, IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE as n, isIMessageCurrentMessageInChat as o, getOptionalIMessageRuntime as p, IMESSAGE_REPLY_CACHE_MAX_ENTRIES as r, isKnownFromMeIMessageMessageId as s, IMESSAGE_REPLY_CACHE_COUNTER_KEY as t, resolveIMessageMessageId as u };
353
+ export { normalizeDirectChatIdentifier as _, findLatestIMessageEntryForChat as a, rememberIMessageReplyCache as c, resolveIMessageReplyCacheEntryKey as d, getIMessageRuntime as f, isIMessageEmailChatIdentifier as g, chatContextFromIMessageTarget as h, IMESSAGE_REPLY_CACHE_NAMESPACE as i, resolveIMessageCachedResourceBinding as l, setIMessageRuntime as m, IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE as n, isIMessageCurrentMessageInChat as o, getOptionalIMessageRuntime as p, IMESSAGE_REPLY_CACHE_MAX_ENTRIES as r, isKnownFromMeIMessageMessageId as s, IMESSAGE_REPLY_CACHE_COUNTER_KEY as t, resolveIMessageMessageId as u, resolveIMessageDirectChatService as v };
@@ -1,4 +1,4 @@
1
- import { f as getIMessageRuntime } from "./monitor-reply-cache-DKlYGd5x.js";
1
+ import { f as getIMessageRuntime } from "./monitor-reply-cache-BdeUQaHO.js";
2
2
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
3
3
  import { createHash } from "node:crypto";
4
4
  //#region extensions/imessage/src/monitor/echo-text-corruption.ts
@@ -1,4 +1,5 @@
1
- import { E as resolveIMessageReactionContext, K as __exportAll } from "./group-policy-DF9xSdik.js";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { t as resolveIMessageReactionContext } from "./reaction-context-DaNrZqYo.js";
2
3
  import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
3
4
  //#region extensions/imessage/src/question-reactions.ts
4
5
  var question_reactions_exports = /* @__PURE__ */ __exportAll({
@@ -0,0 +1,105 @@
1
+ //#region extensions/imessage/src/monitor/reaction-context.ts
2
+ const TAPBACK_TEXT_PATTERNS = [
3
+ {
4
+ prefix: "loved",
5
+ action: "added",
6
+ emoji: "❤️"
7
+ },
8
+ {
9
+ prefix: "liked",
10
+ action: "added",
11
+ emoji: "👍"
12
+ },
13
+ {
14
+ prefix: "disliked",
15
+ action: "added",
16
+ emoji: "👎"
17
+ },
18
+ {
19
+ prefix: "laughed at",
20
+ action: "added",
21
+ emoji: "😂"
22
+ },
23
+ {
24
+ prefix: "emphasized",
25
+ action: "added",
26
+ emoji: "‼️"
27
+ },
28
+ {
29
+ prefix: "questioned",
30
+ action: "added",
31
+ emoji: "❓"
32
+ },
33
+ {
34
+ prefix: "removed a heart from",
35
+ action: "removed",
36
+ emoji: "❤️"
37
+ },
38
+ {
39
+ prefix: "removed a like from",
40
+ action: "removed",
41
+ emoji: "👍"
42
+ },
43
+ {
44
+ prefix: "removed a dislike from",
45
+ action: "removed",
46
+ emoji: "👎"
47
+ },
48
+ {
49
+ prefix: "removed a laugh from",
50
+ action: "removed",
51
+ emoji: "😂"
52
+ },
53
+ {
54
+ prefix: "removed an emphasis from",
55
+ action: "removed",
56
+ emoji: "‼️"
57
+ },
58
+ {
59
+ prefix: "removed a question from",
60
+ action: "removed",
61
+ emoji: "❓"
62
+ }
63
+ ];
64
+ function normalizeReactionValue(value) {
65
+ return typeof value === "string" ? value.trim().replace(/^p:\d+\//iu, "") || void 0 : void 0;
66
+ }
67
+ function resolveReactionTargetGuidCandidates(...values) {
68
+ const candidates = [];
69
+ for (const value of values) {
70
+ if (typeof value !== "string") continue;
71
+ const raw = value.trim();
72
+ if (!raw) continue;
73
+ const normalized = raw.replace(/^p:\d+\//iu, "");
74
+ for (const candidate of [normalized, raw]) if (candidate && !candidates.includes(candidate)) candidates.push(candidate);
75
+ }
76
+ return candidates;
77
+ }
78
+ function resolveTapbackTextContext(bodyText) {
79
+ const lower = bodyText.toLowerCase();
80
+ for (const pattern of TAPBACK_TEXT_PATTERNS) {
81
+ if (!lower.startsWith(pattern.prefix)) continue;
82
+ const afterPrefix = bodyText.slice(pattern.prefix.length).trim();
83
+ if (!/^["“]/u.test(afterPrefix)) continue;
84
+ return {
85
+ action: pattern.action,
86
+ emoji: pattern.emoji,
87
+ targetText: afterPrefix.replace(/^["“]/u, "").replace(/["”]$/u, "").trim()
88
+ };
89
+ }
90
+ return null;
91
+ }
92
+ function resolveIMessageReactionContext(message, bodyText) {
93
+ if (message.is_reaction === true || message.is_tapback === true || typeof message.associated_message_type === "number" && Number.isFinite(message.associated_message_type) && message.associated_message_type >= 2e3 && message.associated_message_type < 4e3) {
94
+ const targetGuids = resolveReactionTargetGuidCandidates(message.reacted_to_guid, message.associated_message_guid);
95
+ return {
96
+ action: message.is_reaction_add === false ? "removed" : "added",
97
+ emoji: normalizeReactionValue(message.reaction_emoji) ?? normalizeReactionValue(message.reaction_type) ?? "reaction",
98
+ targetGuid: targetGuids[0],
99
+ targetGuids
100
+ };
101
+ }
102
+ return resolveTapbackTextContext(bodyText);
103
+ }
104
+ //#endregion
105
+ export { resolveIMessageReactionContext as t };
@@ -1,5 +1,5 @@
1
1
  import { p as resolveLocalIMessageChatDbPath, u as expandIMessageUserPath } from "./accounts-D00Njxg2.js";
2
- import { g as isIMessageEmailChatIdentifier, h as chatContextFromIMessageTarget, l as resolveIMessageCachedResourceBinding, u as resolveIMessageMessageId } from "./monitor-reply-cache-DKlYGd5x.js";
2
+ import { g as isIMessageEmailChatIdentifier, h as chatContextFromIMessageTarget, l as resolveIMessageCachedResourceBinding, u as resolveIMessageMessageId } from "./monitor-reply-cache-BdeUQaHO.js";
3
3
  import path from "node:path";
4
4
  import { normalizeScpRemoteHost } from "openclaw/plugin-sdk/host-runtime";
5
5
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
@@ -121,7 +121,11 @@ function checkIMessageResourceBinding(params) {
121
121
  FROM message m
122
122
  JOIN chat_message_join cmj ON cmj.message_id = m.ROWID
123
123
  JOIN chat c ON c.ROWID = cmj.chat_id
124
- WHERE m.guid = ?`).all(messageGuid).some((row) => (!hasChatId || row.chatId === chatId) && (expectedChatGuids.length === 0 || matchesAnyChatCandidate(row.chatGuid, expectedChatGuids)) && (expectedChatIdentifiers.length === 0 || matchesAnyChatCandidate(row.chatIdentifier, expectedChatIdentifiers)) && (identifierChatGuids.length === 0 || matchesAnyChatCandidate(row.chatGuid, identifierChatGuids))) ? "match" : "mismatch";
124
+ WHERE m.guid = ?`).all(messageGuid).map((row) => ({
125
+ chatId: row.chatId,
126
+ chatGuid: row.chatGuid,
127
+ chatIdentifier: row.chatIdentifier
128
+ })).some((row) => (!hasChatId || row.chatId === chatId) && (expectedChatGuids.length === 0 || matchesAnyChatCandidate(row.chatGuid, expectedChatGuids)) && (expectedChatIdentifiers.length === 0 || matchesAnyChatCandidate(row.chatIdentifier, expectedChatIdentifiers)) && (identifierChatGuids.length === 0 || matchesAnyChatCandidate(row.chatGuid, identifierChatGuids))) ? "match" : "mismatch";
125
129
  } catch {
126
130
  return "unavailable";
127
131
  } finally {
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
@@ -1,9 +1,9 @@
1
- import { F as imessageMessageActions, I as probeIMessage, h as normalizeIMessageMessagingTarget, l as chunkTextForOutbound, m as looksLikeIMessageTargetId, n as resolveIMessageGroupToolPolicy, t as resolveIMessageGroupRequireMention } from "./group-policy-DF9xSdik.js";
2
1
  import { o as resolveIMessageAccount } from "./accounts-D00Njxg2.js";
3
- import { m as setIMessageRuntime } from "./monitor-reply-cache-DKlYGd5x.js";
2
+ import { m as setIMessageRuntime } from "./monitor-reply-cache-BdeUQaHO.js";
3
+ import { h as normalizeIMessageMessagingTarget, l as chunkTextForOutbound, m as looksLikeIMessageTargetId, n as resolveIMessageGroupToolPolicy, t as resolveIMessageGroupRequireMention, v as imessageMessageActions, y as probeIMessage } from "./group-policy-Ca3c5KES.js";
4
4
  import { n as IMessageConfigSchema } from "./config-schema-DV0KP5nC.js";
5
- import { t as sendMessageIMessage } from "./send-BUXW76tw.js";
6
- import { t as monitorIMessageProvider } from "./monitor-DnxJ8pW9.js";
5
+ import { t as sendMessageIMessage } from "./send-Dake4Awx.js";
6
+ import { t as monitorIMessageProvider } from "./monitor-Uv5RaRTk.js";
7
7
  import { buildComputedAccountStatusSnapshot, collectStatusIssuesFromLastError } from "openclaw/plugin-sdk/status-helpers";
8
8
  import { formatTrimmedAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
9
9
  import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
@@ -445,7 +445,7 @@ const FINAL_PRIVATE_MARKER_RE = /(?:#\+){2,}#?|\bassistant\s+to\s*=\s*\w+|^[ \t]
445
445
  const PRIVATE_USE_START = 57344;
446
446
  const PRIVATE_USE_END = 63743;
447
447
  const MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES = 32;
448
- const PLAIN_TEXT_HTML_TAG_RE = /<\/?[a-z][a-z0-9_-]*\b[^>]*>/gi;
448
+ const PLAIN_TEXT_HTML_TAG_RE = /<\/?[a-z][a-z0-9_.:-]*(?=[\s/>])[^>]*>/gi;
449
449
  const PRIVATE_PROVIDER_TAG_NAMES = [
450
450
  "think",
451
451
  "thinking",