@openclaw/imessage 2026.9.6 → 2026.9.7

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.
Files changed (81) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{accounts-CUxZrTcY.mjs → accounts-nzcU0cq2.mjs} +3 -6
  3. package/dist/.setup/{actions.runtime-D0zG_96v.mjs → actions.runtime-DdBN29zj.mjs} +3 -5
  4. package/dist/.setup/{api-RZv6yXul.mjs → api-B9Qx21G-.mjs} +6 -5
  5. package/dist/.setup/{api-VLVcFh7r.mjs → api-D2S6FhGw.mjs} +7 -7
  6. package/dist/.setup/{approval-handler.runtime-Bv5_XgNr.mjs → approval-handler.runtime-DKSq0DWq.mjs} +8 -8
  7. package/dist/.setup/{approval-reactions-D6RFg6lU.mjs → approval-reactions-Bp33xc83.mjs} +5 -5
  8. package/dist/.setup/{channel-BLTaKdSK.mjs → channel-9fyvuN_p.mjs} +97 -152
  9. package/dist/.setup/channel.runtime-n-MPKL8k.mjs +92 -0
  10. package/dist/.setup/{config-schema-CCbTcJEV.mjs → config-schema-4Q3_EqYt.mjs} +9 -1
  11. package/dist/.setup/{group-policy-Cjztkquv.mjs → group-policy-90pw4tyG.mjs} +48 -334
  12. package/dist/.setup/{media-contract-DKJK7rh3.mjs → media-contract-CagwSMvo.mjs} +1 -1
  13. package/dist/.setup/{message-tool-api-pvlcOJPG.mjs → message-tool-api-D032l82H.mjs} +3 -18
  14. package/dist/.setup/{monitor-BJ3Ozi6I.mjs → monitor-fv9nRuTH.mjs} +211 -280
  15. package/dist/.setup/{question-reactions-lm8OxWbb.mjs → question-reactions-Cy-dmU_j.mjs} +1 -1
  16. package/dist/.setup/{remote-file-Bye99O-C.mjs → remote-file-DRPCaVmg.mjs} +70 -21
  17. package/dist/.setup/rolldown-runtime-BBjsoOtd.mjs +27 -0
  18. package/dist/.setup/{sanitize-outbound-sJPF2DVM.mjs → sanitize-outbound-3MLljgtY.mjs} +86 -24
  19. package/dist/.setup/{send-odAfTQ2J.mjs → send-Cu9nl757.mjs} +28 -112
  20. package/dist/.setup/setup-core-8MDdYHj5.mjs +226 -0
  21. package/dist/.setup/{channel.runtime---s2k4YN.mjs → setup-surface-tGXFDdWh.mjs} +6 -103
  22. package/dist/.setup/state-migrations-B-Q7bO0Y.mjs +47 -0
  23. package/dist/.setup/{targets-Cc9vthzI.mjs → targets-BicBiYsM.mjs} +10 -16
  24. package/dist/api.js +7 -7
  25. package/dist/channel-config-api.js +1 -1
  26. package/dist/channel-plugin-api.js +1 -1
  27. package/dist/contract-api.js +1 -1
  28. package/dist/doctor-contract-api.js +168 -7
  29. package/dist/media-contract-api.js +1 -1
  30. package/dist/message-tool-api.js +1 -1
  31. package/dist/runtime-api.js +6 -6
  32. package/dist/setup-entry.js +1 -1
  33. package/dist/src/chat-db.worker.js +6 -1
  34. package/node_modules/typebox/build/compile/code.mjs +1 -2
  35. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  36. package/node_modules/typebox/build/format/iri.mjs +16 -9
  37. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  38. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  39. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  40. package/node_modules/typebox/build/guard/index.mjs +6 -0
  41. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  42. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  43. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  44. package/node_modules/typebox/build/schema/build.mjs +2 -3
  45. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  46. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  47. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  48. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  49. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  50. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  51. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  52. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  53. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  54. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  55. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  56. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  57. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  58. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  59. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  60. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  61. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  62. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  63. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  64. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  65. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  66. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  67. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  68. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  69. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  70. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  71. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  72. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  73. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  74. package/node_modules/typebox/package.json +1 -1
  75. package/node_modules/typebox/readme.md +3 -1
  76. package/openclaw.plugin.json +14 -0
  77. package/package.json +5 -5
  78. package/dist/.setup/rolldown-runtime-D7D4PA-g.mjs +0 -13
  79. package/dist/.setup/state-contract-DvahEY4X.mjs +0 -56
  80. package/dist/.setup/state-migrations-DIDzI99d.mjs +0 -277
  81. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -1,15 +1,15 @@
1
- import { c as parseIMessageTarget, o as normalizeIMessageHandle, r as isAllowedIMessageReplyContextSender, s as parseIMessageAllowTarget, t as formatIMessageChatTarget } from "./targets-Cc9vthzI.mjs";
2
- import { d as resolveIMessageChatDbLookupPath, f as resolveIMessageHomeDir, l as resolveIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-CUxZrTcY.mjs";
3
- import { r as imessageRpcSupportsMethod } from "./message-tool-api-pvlcOJPG.mjs";
4
- import { b as resolveIMessageDirectChatService, d as isKnownFromMeIMessageMessageId, f as rememberIMessageReplyCache, o as createIMessageRpcClient, r as sanitizeOutboundText } from "./sanitize-outbound-sJPF2DVM.mjs";
5
- import { d as probeIMessage, f as probeIMessagePrivateApi, i as resolveIMessageInboundConversationId, n as resolveIMessageGroupSystemPrompt } from "./group-policy-Cjztkquv.mjs";
1
+ import { c as parseIMessageTarget, o as normalizeIMessageHandle, r as isAllowedIMessageReplyContextSender, s as parseIMessageAllowTarget, t as formatIMessageChatTarget } from "./targets-BicBiYsM.mjs";
2
+ import { d as resolveIMessageChatDbLookupPath, f as resolveIMessageHomeDir, l as resolveIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-nzcU0cq2.mjs";
3
+ import { r as imessageRpcSupportsMethod } from "./message-tool-api-D032l82H.mjs";
4
+ import { D as resolveIMessageDirectChatService, b as resolveIMessageEchoMediaKey, d as isKnownFromMeIMessageTarget, f as rememberIMessageReplyCache, h as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, o as createIMessageRpcClient, r as sanitizeOutboundText, v as capFailureRetriesMap, y as resolveIMessageCatchupCursorKey } from "./sanitize-outbound-3MLljgtY.mjs";
5
+ import { d as probeIMessage, f as probeIMessagePrivateApi, i as resolveIMessageInboundConversationId, n as resolveIMessageGroupSystemPrompt } from "./group-policy-90pw4tyG.mjs";
6
6
  import { t as getIMessageRuntime } from "./runtime-Cza4CY5T.mjs";
7
- import { d as resolveIMessageEchoMediaKey, l as capFailureRetriesMap, t as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, u as resolveIMessageCatchupCursorKey } from "./state-contract-DvahEY4X.mjs";
8
- import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-DKJK7rh3.mjs";
9
- import { i as registerIMessageApprovalReactionTarget, n as handleIMessageApprovalReaction, o as listPendingIMessageApprovalReactionPollTargets, r as maybeResolveIMessageApprovalReaction, s as buildIMessageApprovalConversationKeyForInbound } from "./approval-reactions-D6RFg6lU.mjs";
10
- import { c as maybeResolveIMessageApprovalPollVote, i as resolveIMessageStartupRowidWatermark, l as iMessageApprovalControlBindings, n as hasPersistedIMessageEcho, r as stripLeadingEchoTextCorruptionMarkers, t as sendMessageIMessage } from "./send-odAfTQ2J.mjs";
7
+ import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-CagwSMvo.mjs";
8
+ import { i as registerIMessageApprovalReactionTarget, n as handleIMessageApprovalReaction, o as listPendingIMessageApprovalReactionPollTargets, r as maybeResolveIMessageApprovalReaction, s as buildIMessageApprovalConversationKeyForInbound } from "./approval-reactions-Bp33xc83.mjs";
9
+ import { c as iMessageApprovalControlBindings, n as hasPersistedIMessageEcho, r as normalizeIMessageEchoText, s as maybeResolveIMessageApprovalPollVote, t as sendMessageIMessage } from "./send-Cu9nl757.mjs";
11
10
  import { n as normalizeIMessageGuid, t as resolveIMessageReactionContext } from "./reaction-context-Br-vAJFp.mjs";
12
- import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-lm8OxWbb.mjs";
11
+ import { i as resolveIMessageStartupRowidWatermark } from "./remote-file-DRPCaVmg.mjs";
12
+ import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-Cy-dmU_j.mjs";
13
13
  import { asSafeIntegerInRange, hasNonEmptyString, isRecord, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
14
14
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
15
15
  import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
@@ -24,8 +24,8 @@ import { createHash } from "node:crypto";
24
24
  import { createRuntimeConfigReader, getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
25
25
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
26
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
27
- import { buildChannelGroupsScopeTree, resolveChannelGroupPolicy, resolveChannelGroups, resolveChannelGroupsConfigPath, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
28
- import { convertMarkdownTables as convertMarkdownTables$1, findCodeRegions, isInsideCode, sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
27
+ import { resolveChannelGroupPolicy, resolveChannelGroups, resolveChannelGroupsConfigPath, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
28
+ import { convertMarkdownTables, findCodeRegions, isInsideCode, sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
29
29
  import { buildChannelInboundEventContext, buildMentionRegexes, createAcceptedChannelDeliveryResult, createChannelInboundDebouncer, createChannelPartialDeliveryError, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, formatMediaPlaceholderText, isChannelPartialDeliveryError, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundDebounceMs, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
30
30
  import { resolveAgentConfig, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
31
31
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
@@ -33,7 +33,7 @@ import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
33
33
  import { defineStableChannelIngressIdentity, fanInChannelIngressLifecycles } from "openclaw/plugin-sdk/channel-ingress-runtime";
34
34
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
35
35
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
36
- import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
36
+ import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
37
37
  import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
38
38
  import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
39
39
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
@@ -41,15 +41,16 @@ import { FsSafeError, openLocalFileSafely, resolvePinnedMainDmOwnerFromAllowlist
41
41
  import { getSessionEntry, readSessionUpdatedAt, resolveSendPolicy, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
42
42
  import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
43
43
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
44
- import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
44
+ import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
45
45
  import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
46
46
  import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
47
+ import { chunkMarkdownTextWithMode, resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
47
48
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
48
- import { chunkMarkdownTextWithMode as chunkTextWithMode, resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
49
49
  import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
50
50
  import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
51
51
  import { resolveChannelContextVisibilityMode } from "openclaw/plugin-sdk/context-visibility-runtime";
52
52
  import { createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
53
+ import { getSessionBindingService, inspectRuntimeConversationBindingRoute, resolveConfiguredBindingRoute } from "openclaw/plugin-sdk/conversation-binding-runtime";
53
54
  import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
54
55
  import { readFileHandleBounded } from "openclaw/plugin-sdk/file-access-runtime";
55
56
  import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
@@ -72,9 +73,6 @@ function listTargetChatIds(targets) {
72
73
  function hasUnscopedTarget(targets) {
73
74
  return targets.some((target) => normalizeChatId(target.conversation.chatId) === null);
74
75
  }
75
- function uniqueChatIds(chatIds) {
76
- return [...new Set(chatIds)];
77
- }
78
76
  function enumerateMessageGuidCandidates(value) {
79
77
  const trimmed = value.trim();
80
78
  if (!trimmed) return [];
@@ -153,7 +151,7 @@ async function pollPendingIMessageApprovalReactions(params) {
153
151
  if (targets.length === 0) return;
154
152
  const pendingByMessageId = buildPendingTargetsByMessageId(targets);
155
153
  const explicitChatIds = listTargetChatIds(targets);
156
- const chatIds = params.allowRecentChatDiscovery === true && targets.length > 0 && hasUnscopedTarget(targets) ? uniqueChatIds([...explicitChatIds, ...await listRecentChatIds(params.client)]) : explicitChatIds;
154
+ const chatIds = params.allowRecentChatDiscovery === true && hasUnscopedTarget(targets) ? [.../* @__PURE__ */ new Set([...explicitChatIds, ...await listRecentChatIds(params.client)])] : explicitChatIds;
157
155
  if (chatIds.length === 0) return;
158
156
  for (const chatId of chatIds) {
159
157
  let messages;
@@ -209,9 +207,7 @@ function buildChatTargetParams(to, opts) {
209
207
  else params.to = target.to;
210
208
  return {
211
209
  params,
212
- service: opts.service ?? (target.kind === "handle" ? target.service : void 0) ?? account.config.service,
213
- region: opts.region?.trim() || account.config.region?.trim() || "US",
214
- account
210
+ service: opts.service ?? (target.kind === "handle" ? target.service : void 0) ?? account.config.service
215
211
  };
216
212
  }
217
213
  async function runChatAction(method, params, opts) {
@@ -277,15 +273,11 @@ function openCatchupCursorStore() {
277
273
  maxEntries: 256
278
274
  });
279
275
  }
280
- function enqueueCursorWrite(accountId, fn) {
281
- const key = resolveIMessageCatchupCursorKey(accountId);
282
- return cursorWriteQueue.enqueue(key, fn);
283
- }
284
276
  function sanitizeFailureRetriesInput(raw) {
285
277
  if (!raw || typeof raw !== "object") return {};
286
278
  const out = {};
287
279
  for (const [guid, count] of Object.entries(raw)) {
288
- if (!guid || typeof guid !== "string") continue;
280
+ if (!guid) continue;
289
281
  if (typeof count !== "number" || !Number.isFinite(count) || count <= 0) continue;
290
282
  out[guid] = Math.floor(count);
291
283
  }
@@ -347,14 +339,17 @@ function decideCatchupCursorSave(existingValue, cursor, allowCursorRewindForRetr
347
339
  async function saveIMessageCatchupCursor(accountId, next, options = {}) {
348
340
  const cursor = buildIMessageCatchupCursor(next, Date.now());
349
341
  const allowCursorRewindForRetries = options.allowCursorRewindForRetries === true;
342
+ await updateIMessageCatchupCursor(accountId, (existing) => decideCatchupCursorSave(existing, cursor, allowCursorRewindForRetries));
343
+ }
344
+ async function updateIMessageCatchupCursor(accountId, decide) {
350
345
  const store = openCatchupCursorStore();
351
346
  if (!store.observe || !store.compareAndApply) throw new Error("iMessage catchup cursor persistence requires plugin-state comparison support.");
352
347
  const key = resolveIMessageCatchupCursorKey(accountId);
353
348
  let observation = await store.observe(key);
354
349
  for (;;) {
355
- const intent = decideCatchupCursorSave(observation.value, cursor, allowCursorRewindForRetries);
350
+ const intent = decide(observation.value);
356
351
  const result = await store.compareAndApply(key, observation.comparison, intent);
357
- if (result.status !== "conflict") return;
352
+ if (result.status !== "conflict") return result.status === "applied";
358
353
  observation = result.current;
359
354
  }
360
355
  }
@@ -395,32 +390,12 @@ function decideLiveCatchupCursorAdvance(existingValue, next, maxFailureRetries)
395
390
  }
396
391
  async function advanceIMessageCatchupCursor(accountId, next, config) {
397
392
  if (!Number.isFinite(next.lastSeenMs) || !Number.isFinite(next.lastSeenRowid)) return false;
398
- return await enqueueCursorWrite(accountId, async () => {
393
+ return await cursorWriteQueue.enqueue(resolveIMessageCatchupCursorKey(accountId), async () => {
399
394
  const cursor = buildIMessageCatchupCursor(next, Date.now());
400
395
  const maxFailureRetries = config.maxFailureRetries;
401
- const store = openCatchupCursorStore();
402
- if (!store.observe || !store.compareAndApply) throw new Error("iMessage catchup cursor persistence requires plugin-state comparison support.");
403
- const key = resolveIMessageCatchupCursorKey(accountId);
404
- let observation = await store.observe(key);
405
- for (;;) {
406
- const intent = decideLiveCatchupCursorAdvance(observation.value, cursor, maxFailureRetries);
407
- const result = await store.compareAndApply(key, observation.comparison, intent);
408
- if (result.status !== "conflict") return result.status === "applied";
409
- observation = result.current;
410
- }
396
+ return updateIMessageCatchupCursor(accountId, (existing) => decideLiveCatchupCursorAdvance(existing, cursor, maxFailureRetries));
411
397
  });
412
398
  }
413
- /**
414
- * One catchup pass. Loads the cursor, fetches `messages.history`, replays
415
- * each row through `dispatch`, advances the cursor on success / give-up,
416
- * persists the cursor, returns a summary.
417
- *
418
- * The fetch and dispatch functions are injected so this loop is unit-testable
419
- * without standing up an `imsg` daemon. The wiring in `monitor-provider.ts`
420
- * passes the live `client.request("messages.history", ...)` adapter as
421
- * `fetch` and the `evaluateIMessageInbound` + `runChannelInboundEvent`
422
- * pipeline as `dispatch`.
423
- */
424
399
  async function performIMessageCatchup(params) {
425
400
  const now = params.now ?? Date.now();
426
401
  const cfg = params.config;
@@ -755,13 +730,8 @@ async function runIMessageCatchup(params) {
755
730
  warnLog(`imessage catchup: missing payload for guid=${row.guid}, skipping`);
756
731
  return { ok: false };
757
732
  }
758
- try {
759
- await dispatchPayload(entry.message, entry.rawEnvelope);
760
- return { ok: true };
761
- } catch (err) {
762
- warnLog(`imessage catchup: dispatch threw for guid=${row.guid}: ${String(err)}`);
763
- return { ok: false };
764
- }
733
+ await dispatchPayload(entry.message, entry.rawEnvelope);
734
+ return { ok: true };
765
735
  };
766
736
  return await performIMessageCatchup({
767
737
  accountId,
@@ -783,13 +753,6 @@ async function runIMessageCatchup(params) {
783
753
  }
784
754
  //#endregion
785
755
  //#region extensions/imessage/src/monitor/coalesce.ts
786
- /**
787
- * Bounds on the merged output when multiple inbound iMessage payloads are
788
- * folded into one agent turn. Caps each merge so a sender who
789
- * rapid-fires DMs inside the debounce window cannot amplify the downstream
790
- * prompt past a safe ceiling. Every source GUID still surfaces via
791
- * `coalescedMessageGuids` so a future replay path can recognize duplicates.
792
- */
793
756
  const MAX_COALESCED_TEXT_CHARS = 4e3;
794
757
  const MAX_COALESCED_ATTACHMENTS = 20;
795
758
  /**
@@ -809,12 +772,8 @@ function combineIMessagePayloads(payloads) {
809
772
  const seenTexts = /* @__PURE__ */ new Set();
810
773
  const textParts = [];
811
774
  const allAttachments = [];
812
- const seenGuids = /* @__PURE__ */ new Set();
813
- const coalescedMessageGuids = [];
814
775
  let textLength = 0;
815
776
  let latestCreatedAt;
816
- let maxRowid = -Infinity;
817
- let maxDateMs = -Infinity;
818
777
  let reply;
819
778
  let payloadIndex = 0;
820
779
  for (const payload of payloads) {
@@ -822,14 +781,6 @@ function combineIMessagePayloads(payloads) {
822
781
  payloadIndex += 1;
823
782
  const createdAt = payload.created_at;
824
783
  if (typeof createdAt === "string" && createdAt.length > 0 && (latestCreatedAt === void 0 || createdAt > latestCreatedAt)) latestCreatedAt = createdAt;
825
- if (typeof payload.id === "number" && Number.isFinite(payload.id)) maxRowid = Math.max(maxRowid, payload.id);
826
- const dateMs = typeof createdAt === "string" ? Date.parse(createdAt) : NaN;
827
- if (Number.isFinite(dateMs)) maxDateMs = Math.max(maxDateMs, dateMs);
828
- const guid = payload.guid?.trim();
829
- if (guid && !seenGuids.has(guid)) {
830
- seenGuids.add(guid);
831
- coalescedMessageGuids.push(guid);
832
- }
833
784
  if (!reply && (payload.thread_originator_guid != null || payload.reply_to_guid != null)) reply = payload;
834
785
  if (!keepContent) continue;
835
786
  const text = textLength <= MAX_COALESCED_TEXT_CHARS ? (payload.text ?? "").trim() : "";
@@ -859,12 +810,7 @@ function combineIMessagePayloads(payloads) {
859
810
  thread_originator_guid: reply.thread_originator_guid ?? null,
860
811
  reply_to_guid: reply.reply_to_guid ?? null,
861
812
  reply_to_text: reply.reply_to_text ?? null,
862
- reply_to_sender: reply.reply_to_sender ?? null,
863
- coalescedMessageGuids: coalescedMessageGuids.length > 0 ? coalescedMessageGuids : void 0,
864
- coalescedCatchupCursor: Number.isFinite(maxRowid) && Number.isFinite(maxDateMs) ? {
865
- lastSeenMs: maxDateMs,
866
- lastSeenRowid: maxRowid
867
- } : void 0
813
+ reply_to_sender: reply.reply_to_sender ?? null
868
814
  };
869
815
  }
870
816
  //#endregion
@@ -912,20 +858,6 @@ function projectionConflictKey(projection) {
912
858
  is_from_me: projection.is_from_me
913
859
  });
914
860
  }
915
- function applyAuthoritativeRecoveryProjection(message, projection) {
916
- return {
917
- ...message,
918
- ...projection.chat_id !== void 0 ? { chat_id: projection.chat_id } : {},
919
- ...projection.chat_guid !== void 0 ? { chat_guid: projection.chat_guid } : {},
920
- ...projection.chat_identifier !== void 0 ? { chat_identifier: projection.chat_identifier } : {},
921
- ...projection.chat_name !== void 0 ? { chat_name: projection.chat_name } : {},
922
- ...projection.participants !== void 0 ? { participants: projection.participants } : {},
923
- is_group: projection.is_group,
924
- sender: projection.sender,
925
- destination_caller_id: projection.destination_caller_id ?? null,
926
- is_from_me: projection.is_from_me
927
- };
928
- }
929
861
  async function repairIMessageConversationAnchor(params) {
930
862
  const { client, message, runtime } = params;
931
863
  if (!isIMessageAnchorless(message)) return message;
@@ -982,12 +914,12 @@ async function repairIMessageConversationAnchor(params) {
982
914
  runtime?.error?.(`imessage: dropping anchorless message GUID=${guid}; recovered authoritative row is from-me`);
983
915
  return null;
984
916
  }
985
- const repaired = applyAuthoritativeRecoveryProjection(message, projection);
986
- if (isIMessageAnchorless(repaired)) {
987
- runtime?.error?.(`imessage: dropping anchorless message GUID=${guid} after recovery found no usable conversation anchor`);
988
- return null;
989
- }
990
- runtime?.log?.(`imessage: recovered anchorless message GUID=${guid} chat_id=${repaired.chat_id ?? "unknown"} is_group=${repaired.is_group === true}`);
917
+ const repaired = {
918
+ ...message,
919
+ ...projection,
920
+ destination_caller_id: projection.destination_caller_id ?? null
921
+ };
922
+ runtime?.log?.(`imessage: recovered anchorless message GUID=${guid} chat_id=${repaired.chat_id ?? "unknown"} is_group=${repaired.is_group}`);
991
923
  return repaired;
992
924
  }
993
925
  //#endregion
@@ -996,14 +928,14 @@ async function deliverIMessageReply(params) {
996
928
  const { payload, target, runtime, maxBytes, textLimit, accountId, sentMessageCache } = params;
997
929
  const scope = `${accountId ?? ""}:${target}`;
998
930
  const { cfg } = params;
999
- const tableMode = resolveMarkdownTableMode$1({
931
+ const tableMode = resolveMarkdownTableMode({
1000
932
  cfg,
1001
933
  channel: "imessage",
1002
934
  accountId
1003
935
  });
1004
936
  const chunkMode = resolveChunkMode(cfg, "imessage", accountId);
1005
937
  const rawText = sanitizeOutboundText(payload.text ?? "");
1006
- const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables$1(rawText, tableMode) });
938
+ const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables(rawText, tableMode) });
1007
939
  const accepted = [];
1008
940
  const sendAccepted = async (text, mediaUrl) => {
1009
941
  const sent = await sendMessageIMessage(target, text, {
@@ -1029,7 +961,7 @@ async function deliverIMessageReply(params) {
1029
961
  delivered = await deliverTextOrMediaReply({
1030
962
  payload,
1031
963
  text: reply.text,
1032
- chunkText: (value) => chunkTextWithMode(value, textLimit, chunkMode),
964
+ chunkText: (value) => chunkMarkdownTextWithMode(value, textLimit, chunkMode),
1033
965
  sendText: sendAccepted,
1034
966
  sendMedia: ({ mediaUrl, caption }) => sendAccepted(caption ?? "", mediaUrl)
1035
967
  });
@@ -1153,11 +1085,6 @@ function createIMessageThrottledDropDiagnosticCache() {
1153
1085
  //#region extensions/imessage/src/monitor/echo-cache.ts
1154
1086
  const SENT_MESSAGE_TEXT_TTL_MS = 4e3;
1155
1087
  const SENT_MESSAGE_ID_TTL_MS = 6e4;
1156
- function normalizeEchoTextKey(text) {
1157
- if (!text) return null;
1158
- const normalized = stripLeadingEchoTextCorruptionMarkers(text.replace(/\r\n?/g, "\n").trim()).trim();
1159
- return normalized ? normalized : null;
1160
- }
1161
1088
  function normalizeEchoMessageIdKey(messageId) {
1162
1089
  if (!messageId) return null;
1163
1090
  const normalized = messageId.trim();
@@ -1173,7 +1100,7 @@ var DefaultSentMessageCache = class {
1173
1100
  this.messageIdCache = /* @__PURE__ */ new Map();
1174
1101
  }
1175
1102
  remember(scope, lookup) {
1176
- const textKey = normalizeEchoTextKey(lookup.text);
1103
+ const textKey = normalizeIMessageEchoText(lookup.text);
1177
1104
  if (textKey) this.textCache.set(`${scope}:${textKey}`, Date.now());
1178
1105
  const mediaKey = resolveIMessageEchoMediaKey(lookup.media);
1179
1106
  if (mediaKey) this.mediaCache.set(`${scope}:${mediaKey}`, Date.now());
@@ -1196,7 +1123,7 @@ var DefaultSentMessageCache = class {
1196
1123
  skipIdShortCircuit: resolvedOptions.skipIdShortCircuit,
1197
1124
  includePendingText: resolvedOptions.includePendingText
1198
1125
  })) return true;
1199
- const textKey = normalizeEchoTextKey(lookup.text);
1126
+ const textKey = normalizeIMessageEchoText(lookup.text);
1200
1127
  const mediaKey = resolveIMessageEchoMediaKey(lookup.media);
1201
1128
  const messageIdKey = normalizeEchoMessageIdKey(lookup.messageId);
1202
1129
  let canUseMediaFallback = !messageIdKey;
@@ -1224,11 +1151,16 @@ var DefaultSentMessageCache = class {
1224
1151
  }
1225
1152
  cleanup() {
1226
1153
  const now = Date.now();
1227
- for (const [key, timestamp] of this.textCache.entries()) if (now - timestamp > SENT_MESSAGE_TEXT_TTL_MS) this.textCache.delete(key);
1228
- for (const [key, timestamp] of this.textBackedByIdCache.entries()) if (now - timestamp > SENT_MESSAGE_TEXT_TTL_MS) this.textBackedByIdCache.delete(key);
1229
- for (const [key, timestamp] of this.mediaCache.entries()) if (now - timestamp > SENT_MESSAGE_TEXT_TTL_MS) this.mediaCache.delete(key);
1230
- for (const [key, timestamp] of this.mediaBackedByIdCache.entries()) if (now - timestamp > SENT_MESSAGE_TEXT_TTL_MS) this.mediaBackedByIdCache.delete(key);
1231
- for (const [key, timestamp] of this.messageIdCache.entries()) if (now - timestamp > SENT_MESSAGE_ID_TTL_MS) this.messageIdCache.delete(key);
1154
+ for (const cache of [
1155
+ this.textCache,
1156
+ this.textBackedByIdCache,
1157
+ this.mediaCache,
1158
+ this.mediaBackedByIdCache,
1159
+ this.messageIdCache
1160
+ ]) {
1161
+ const ttlMs = cache === this.messageIdCache ? SENT_MESSAGE_ID_TTL_MS : SENT_MESSAGE_TEXT_TTL_MS;
1162
+ for (const [key, timestamp] of cache) if (now - timestamp > ttlMs) cache.delete(key);
1163
+ }
1232
1164
  }
1233
1165
  };
1234
1166
  function createSentMessageCache() {
@@ -1290,23 +1222,25 @@ function isStaleIMessageBacklog(message, nowMs, thresholdMs = IMESSAGE_STALE_INB
1290
1222
  }
1291
1223
  //#endregion
1292
1224
  //#region extensions/imessage/src/conversation-route.ts
1293
- function resolveIMessageConversationRoute(params) {
1294
- const route = resolveAgentRoute({
1295
- cfg: params.cfg,
1225
+ async function resolveIMessageConversationRoute(params) {
1226
+ const routeInput = {
1296
1227
  channel: "imessage",
1297
1228
  accountId: params.accountId,
1298
1229
  peer: {
1299
1230
  kind: params.isGroup ? "group" : "direct",
1300
1231
  id: params.peerId
1301
1232
  }
1302
- });
1233
+ };
1303
1234
  const conversationId = resolveIMessageInboundConversationId({
1304
1235
  isGroup: params.isGroup,
1305
1236
  sender: params.sender,
1306
1237
  chatId: params.chatId
1307
1238
  });
1308
1239
  if (!conversationId) return {
1309
- route,
1240
+ route: resolveAgentRoute({
1241
+ ...routeInput,
1242
+ cfg: params.cfg
1243
+ }),
1310
1244
  bindingResolution: null
1311
1245
  };
1312
1246
  const conversation = {
@@ -1314,20 +1248,81 @@ function resolveIMessageConversationRoute(params) {
1314
1248
  accountId: params.accountId,
1315
1249
  conversationId
1316
1250
  };
1317
- const configuredRoute = resolveConfiguredBindingRoute({
1318
- cfg: params.cfg,
1319
- route,
1320
- conversation
1321
- });
1322
- const runtimeRoute = resolveRuntimeConversationBindingRoute({
1323
- route: configuredRoute.route,
1324
- conversation
1251
+ const service = getSessionBindingService();
1252
+ const inspection = await service.inspectByConversationAsync(conversation);
1253
+ if (inspection.status === "unavailable") throw new Error("iMessage conversation binding owner is temporarily unavailable; retry the message.");
1254
+ let bindingResolution = null;
1255
+ const runtimeRoute = inspectRuntimeConversationBindingRoute({
1256
+ inspection,
1257
+ resolveRoute: ({ boundAgentId }) => {
1258
+ if (boundAgentId) return resolveAgentRoute({
1259
+ ...routeInput,
1260
+ cfg: { session: params.cfg.session },
1261
+ defaultAgentId: boundAgentId
1262
+ });
1263
+ const configuredRoute = resolveConfiguredBindingRoute({
1264
+ cfg: params.cfg,
1265
+ route: resolveAgentRoute({
1266
+ ...routeInput,
1267
+ cfg: params.cfg
1268
+ }),
1269
+ conversation
1270
+ });
1271
+ bindingResolution = configuredRoute.bindingResolution;
1272
+ return configuredRoute.route;
1273
+ }
1325
1274
  });
1275
+ if (runtimeRoute.bindingRecord) await service.touchAsync(runtimeRoute.bindingRecord.bindingId, void 0, runtimeRoute.bindingRecord.conversation);
1326
1276
  if (runtimeRoute.bindingRecord && !runtimeRoute.boundSessionKey) logVerbose(`imessage: plugin-bound conversation ${conversationId}`);
1327
1277
  else if (runtimeRoute.boundSessionKey) logVerbose(`imessage: routed via bound conversation ${conversationId} -> ${runtimeRoute.boundSessionKey}`);
1328
1278
  return {
1329
1279
  route: runtimeRoute.route,
1330
- bindingResolution: runtimeRoute.bindingRecord ? null : configuredRoute.bindingResolution
1280
+ bindingResolution: runtimeRoute.bindingRecord ? null : bindingResolution
1281
+ };
1282
+ }
1283
+ //#endregion
1284
+ //#region src/channels/mention-gating.ts
1285
+ function resolveBotThreadMentionPolicy(params) {
1286
+ if (!params.isBotOwnedThread || params.requireMentionInBotThreads === void 0) return {
1287
+ requireMention: params.requireMention,
1288
+ implicitMentionKinds: params.implicitMentionKinds
1289
+ };
1290
+ return {
1291
+ requireMention: params.requireMentionInBotThreads,
1292
+ implicitMentionKinds: params.requireMentionInBotThreads ? params.implicitMentionKinds?.filter((kind) => kind === "native") : params.implicitMentionKinds
1293
+ };
1294
+ }
1295
+ //#endregion
1296
+ //#region extensions/imessage/src/monitor/mention-policy.ts
1297
+ async function resolveIMessageInboundMentionPolicy(params) {
1298
+ const groups = resolveChannelGroups(params.cfg, "imessage", params.accountId);
1299
+ const { "*": defaults, ...scopes } = groups ?? {};
1300
+ const requireMention = resolveScopeRequireMention({
1301
+ tree: {
1302
+ defaults,
1303
+ scopes
1304
+ },
1305
+ path: params.groupId ? [params.groupId] : [],
1306
+ requireMentionOverride: params.requireMentionOverride,
1307
+ overrideOrder: "before-config"
1308
+ });
1309
+ const requireMentionInBotThreads = (params.groupId ? groups?.[params.groupId]?.requireMentionInBotThreads : void 0) ?? defaults?.requireMentionInBotThreads;
1310
+ const threadOriginatorGuid = params.message.thread_originator_guid?.trim();
1311
+ const isBotOwnedThread = Boolean(params.isGroup && requireMentionInBotThreads !== void 0 && threadOriginatorGuid && await isKnownFromMeIMessageTarget({
1312
+ messageIds: uniqueStrings([normalizeIMessageGuid(threadOriginatorGuid), threadOriginatorGuid]),
1313
+ accountId: params.accountId,
1314
+ chatId: params.message.chat_id ?? void 0,
1315
+ chatGuid: params.message.chat_guid ?? void 0,
1316
+ chatIdentifier: params.message.chat_identifier ?? void 0,
1317
+ isKnownFromMeMessageId: params.isKnownFromMeMessageId
1318
+ }));
1319
+ return {
1320
+ ...resolveBotThreadMentionPolicy({
1321
+ isBotOwnedThread,
1322
+ requireMentionInBotThreads,
1323
+ requireMention
1324
+ }),
1325
+ enforceMentionRequirement: isBotOwnedThread && requireMentionInBotThreads === true
1331
1326
  };
1332
1327
  }
1333
1328
  //#endregion
@@ -1362,8 +1357,7 @@ const REFLECTION_PATTERNS = [
1362
1357
  label: "gateway-missing-api-key"
1363
1358
  }
1364
1359
  ];
1365
- function hasMatchOutsideCode(text, re) {
1366
- const codeRegions = findCodeRegions(text);
1360
+ function hasMatchOutsideCode(text, re, codeRegions) {
1367
1361
  const globalRe = new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
1368
1362
  for (const match of text.matchAll(globalRe)) {
1369
1363
  const start = match.index ?? -1;
@@ -1380,8 +1374,9 @@ function detectReflectedContent(text) {
1380
1374
  isReflection: false,
1381
1375
  matchedLabels: []
1382
1376
  };
1377
+ const codeRegions = findCodeRegions(text);
1383
1378
  const matchedLabels = [];
1384
- for (const { re, label } of REFLECTION_PATTERNS) if (hasMatchOutsideCode(text, re)) matchedLabels.push(label);
1379
+ for (const { re, label } of REFLECTION_PATTERNS) if (hasMatchOutsideCode(text, re, codeRegions)) matchedLabels.push(label);
1385
1380
  return {
1386
1381
  isReflection: matchedLabels.length > 0,
1387
1382
  matchedLabels
@@ -1481,15 +1476,21 @@ function resolveInboundEchoMessageIds(message) {
1481
1476
  const values = [message.id != null ? String(message.id) : void 0, normalizeReplyField(message.guid)];
1482
1477
  return uniqueStrings(values.filter((value) => Boolean(value)));
1483
1478
  }
1479
+ function classifyIMessageSelfChat(message, isGroup, senderNormalized) {
1480
+ const chatIdentifier = normalizeIMessageHandle(message.chat_identifier ?? "") || void 0;
1481
+ const destination = normalizeIMessageHandle(message.destination_caller_id ?? "") || void 0;
1482
+ const matchesThread = !isGroup && chatIdentifier != null && senderNormalized === chatIdentifier;
1483
+ return {
1484
+ isSelfChat: matchesThread && destination != null && destination === senderNormalized,
1485
+ isAmbiguousSelfThread: matchesThread && destination == null
1486
+ };
1487
+ }
1484
1488
  function rememberIMessageSkippedFromMeForSelfChatDedupe(params) {
1485
1489
  if (params.message.is_from_me !== true) return;
1486
1490
  const sender = params.message.sender?.trim();
1487
1491
  if (!sender) return;
1488
1492
  const chatId = params.message.chat_id ?? void 0;
1489
1493
  const isGroup = Boolean(params.message.is_group);
1490
- const chatIdentifierNormalized = normalizeIMessageHandle(params.message.chat_identifier ?? "") || void 0;
1491
- const destinationCallerIdNormalized = normalizeIMessageHandle(params.message.destination_caller_id ?? "") || void 0;
1492
- const senderNormalized = normalizeIMessageHandle(sender);
1493
1494
  const createdAt = params.message.created_at ? Date.parse(params.message.created_at) : void 0;
1494
1495
  const lookup = {
1495
1496
  accountId: params.accountId,
@@ -1499,8 +1500,7 @@ function rememberIMessageSkippedFromMeForSelfChatDedupe(params) {
1499
1500
  text: params.bodyText.trim(),
1500
1501
  createdAt
1501
1502
  };
1502
- const isSelfChat = !isGroup && chatIdentifierNormalized != null && senderNormalized === chatIdentifierNormalized && destinationCallerIdNormalized != null && destinationCallerIdNormalized === senderNormalized;
1503
- const isAmbiguousSelfThread = !isGroup && chatIdentifierNormalized != null && senderNormalized === chatIdentifierNormalized && destinationCallerIdNormalized == null;
1503
+ const { isSelfChat, isAmbiguousSelfThread } = classifyIMessageSelfChat(params.message, isGroup, normalizeIMessageHandle(sender));
1504
1504
  if (isSelfChat) params.selfChatCache?.remember({
1505
1505
  ...lookup,
1506
1506
  allowCreatedAtSkew: true
@@ -1525,18 +1525,6 @@ async function hasIMessageEchoMatch(params) {
1525
1525
  }
1526
1526
  return false;
1527
1527
  }
1528
- async function isKnownFromMeIMessageReactionTarget(params) {
1529
- const { accountId, chatId, chatGuid, chatIdentifier } = params;
1530
- const ctx = {
1531
- accountId,
1532
- chatId,
1533
- chatGuid,
1534
- chatIdentifier
1535
- };
1536
- const isKnownFromMe = params.isKnownFromMeMessageId ?? isKnownFromMeIMessageMessageId;
1537
- for (const messageId of params.messageIds) if (await isKnownFromMe(messageId, ctx)) return true;
1538
- return false;
1539
- }
1540
1528
  async function resolveIMessageInboundDecision(params) {
1541
1529
  const sender = (params.message.sender ?? "").trim();
1542
1530
  if (!sender) return {
@@ -1547,7 +1535,6 @@ async function resolveIMessageInboundDecision(params) {
1547
1535
  const chatId = params.message.chat_id ?? void 0;
1548
1536
  const chatGuid = params.message.chat_guid ?? void 0;
1549
1537
  const chatIdentifier = params.message.chat_identifier ?? void 0;
1550
- const destinationCallerId = params.message.destination_caller_id ?? void 0;
1551
1538
  const createdAt = params.message.created_at ? Date.parse(params.message.created_at) : void 0;
1552
1539
  const messageText = params.messageText.trim();
1553
1540
  const bodyText = params.bodyText.trim();
@@ -1581,10 +1568,7 @@ async function resolveIMessageInboundDecision(params) {
1581
1568
  text: bodyText,
1582
1569
  createdAt
1583
1570
  };
1584
- const chatIdentifierNormalized = normalizeIMessageHandle(chatIdentifier ?? "") || void 0;
1585
- const destinationCallerIdNormalized = normalizeIMessageHandle(destinationCallerId ?? "") || void 0;
1586
- const isSelfChat = !isGroup && chatIdentifierNormalized != null && senderNormalized === chatIdentifierNormalized && destinationCallerIdNormalized != null && destinationCallerIdNormalized === senderNormalized;
1587
- const isAmbiguousSelfThread = !isGroup && chatIdentifierNormalized != null && senderNormalized === chatIdentifierNormalized && destinationCallerIdNormalized == null;
1571
+ const { isSelfChat, isAmbiguousSelfThread } = classifyIMessageSelfChat(params.message, isGroup, senderNormalized);
1588
1572
  let skipSelfChatHasCheck = false;
1589
1573
  const inboundMessageIds = resolveInboundEchoMessageIds(params.message);
1590
1574
  const inboundMessageId = inboundMessageIds[0];
@@ -1629,7 +1613,7 @@ async function resolveIMessageInboundDecision(params) {
1629
1613
  const groupId = isGroup ? groupIdCandidate : void 0;
1630
1614
  const hasControlCommandInMessage = hasControlCommand(messageText, params.cfg);
1631
1615
  const groupAllowFromForAccess = isGroup ? groupAllowFromWithLegacyChatTargets : params.groupAllowFrom;
1632
- const { route, bindingResolution } = resolveIMessageConversationRoute({
1616
+ const { route, bindingResolution } = await resolveIMessageConversationRoute({
1633
1617
  cfg: params.cfg,
1634
1618
  accountId: params.accountId,
1635
1619
  isGroup,
@@ -1740,7 +1724,7 @@ async function resolveIMessageInboundDecision(params) {
1740
1724
  sender
1741
1725
  }),
1742
1726
  messageIds: targetGuids
1743
- }) || await isKnownFromMeIMessageReactionTarget({
1727
+ }) || await isKnownFromMeIMessageTarget({
1744
1728
  messageIds: targetGuids,
1745
1729
  accountId: params.accountId,
1746
1730
  chatId,
@@ -1859,13 +1843,16 @@ async function resolveIMessageInboundDecision(params) {
1859
1843
  if (replyContext && !filteredReplyContext && isGroup) params.logVerbose?.(`imessage: drop reply context (mode=${contextVisibilityMode}, sender_allowed=${replySenderAllowed ? "yes" : "no"})`);
1860
1844
  const historyKey = isGroup ? String(chatId ?? chatGuid ?? chatIdentifier ?? "unknown") : void 0;
1861
1845
  const mentioned = isGroup ? matchesMentionPatterns(messageText, mentionRegexes) : true;
1862
- const requireMention = resolveScopeRequireMention({
1863
- tree: buildChannelGroupsScopeTree(params.cfg, "imessage", params.accountId),
1864
- path: groupId ? [groupId] : [],
1846
+ const { requireMention, implicitMentionKinds, enforceMentionRequirement } = await resolveIMessageInboundMentionPolicy({
1847
+ cfg: params.cfg,
1848
+ accountId: params.accountId,
1849
+ groupId,
1850
+ isGroup,
1851
+ message: params.message,
1865
1852
  requireMentionOverride: params.opts?.requireMention,
1866
- overrideOrder: "before-config"
1853
+ isKnownFromMeMessageId: params.isKnownFromMeMessageId
1867
1854
  });
1868
- const canDetectMention = mentionRegexes.length > 0;
1855
+ const canDetectMention = mentionRegexes.length > 0 || enforceMentionRequirement;
1869
1856
  const commandAuthorized = commandAccess.authorized;
1870
1857
  if (commandAccess.shouldBlockControlCommand) {
1871
1858
  if (params.logVerbose) logInboundDrop({
@@ -1884,7 +1871,7 @@ async function resolveIMessageInboundDecision(params) {
1884
1871
  canDetectMention,
1885
1872
  wasMentioned: mentioned,
1886
1873
  hasAnyMention: false,
1887
- implicitMentionKinds: []
1874
+ implicitMentionKinds
1888
1875
  },
1889
1876
  policy: {
1890
1877
  isGroup,
@@ -2327,44 +2314,40 @@ async function assertAllowedCanonicalAttachmentPath(params) {
2327
2314
  })) throw new Error("attachment path resolves outside allowed roots");
2328
2315
  }
2329
2316
  async function readAttachmentBuffer(params) {
2330
- const opened = await (params.deps.openLocalFileSafely ?? openLocalFileSafely)({ filePath: params.attachmentPath });
2331
- try {
2332
- if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2333
- await assertAllowedCanonicalAttachmentPath({
2334
- canonicalPath: opened.realPath,
2335
- allowedRoots: params.allowedRoots
2336
- });
2337
- const buffer = await readFileHandleBounded(opened.handle, params.maxBytes).catch((error) => {
2338
- if (error instanceof FsSafeError && error.code === "too-large") throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2339
- throw error;
2340
- });
2341
- if (isHeicAttachment(params.attachmentPath, params.mimeType)) try {
2342
- const convert = params.deps.convertHeicToJpeg;
2343
- return {
2344
- buffer: (await withTempWorkspace({
2345
- rootDir: resolvePreferredOpenClawTmpDir(),
2346
- prefix: "openclaw-imessage-heic-"
2347
- }, async (workspace) => {
2348
- const pinnedPath = await workspace.write("attachment.heic", buffer);
2349
- return convert ? { buffer: await convert(pinnedPath, params.maxBytes) } : await loadWebMedia(pinnedPath, {
2350
- maxBytes: params.maxBytes,
2351
- localRoots: [workspace.dir]
2352
- });
2353
- })).buffer,
2354
- contentType: "image/jpeg",
2355
- originalFilename: jpegFilenameForAttachment(params.attachmentPath)
2356
- };
2357
- } catch (err) {
2358
- params.deps.logVerbose?.(`imessage: HEIC attachment conversion failed; staging original instead: ${String(err)}`);
2359
- }
2317
+ await using opened = await (params.deps.openLocalFileSafely ?? openLocalFileSafely)({ filePath: params.attachmentPath });
2318
+ if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2319
+ await assertAllowedCanonicalAttachmentPath({
2320
+ canonicalPath: opened.realPath,
2321
+ allowedRoots: params.allowedRoots
2322
+ });
2323
+ const buffer = await readFileHandleBounded(opened.handle, params.maxBytes).catch((error) => {
2324
+ if (error instanceof FsSafeError && error.code === "too-large") throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2325
+ throw error;
2326
+ });
2327
+ if (isHeicAttachment(params.attachmentPath, params.mimeType)) try {
2328
+ const convert = params.deps.convertHeicToJpeg;
2360
2329
  return {
2361
- buffer,
2362
- contentType: params.mimeType ?? void 0,
2363
- originalFilename: path.basename(params.attachmentPath)
2330
+ buffer: (await withTempWorkspace({
2331
+ rootDir: resolvePreferredOpenClawTmpDir(),
2332
+ prefix: "openclaw-imessage-heic-"
2333
+ }, async (workspace) => {
2334
+ const pinnedPath = await workspace.write("attachment.heic", buffer);
2335
+ return convert ? { buffer: await convert(pinnedPath, params.maxBytes) } : await loadWebMedia(pinnedPath, {
2336
+ maxBytes: params.maxBytes,
2337
+ localRoots: [workspace.dir]
2338
+ });
2339
+ })).buffer,
2340
+ contentType: "image/jpeg",
2341
+ originalFilename: jpegFilenameForAttachment(params.attachmentPath)
2364
2342
  };
2365
- } finally {
2366
- await opened.handle.close().catch(() => void 0);
2343
+ } catch (err) {
2344
+ params.deps.logVerbose?.(`imessage: HEIC attachment conversion failed; staging original instead: ${String(err)}`);
2367
2345
  }
2346
+ return {
2347
+ buffer,
2348
+ contentType: params.mimeType ?? void 0,
2349
+ originalFilename: path.basename(params.attachmentPath)
2350
+ };
2368
2351
  }
2369
2352
  async function stageIMessageAttachments(attachments, params) {
2370
2353
  const deps = params.deps ?? {};
@@ -2633,11 +2616,6 @@ async function advanceIMessageRecoveryCursor(accountId, dbIdentity, rowid) {
2633
2616
  } catch {}
2634
2617
  }
2635
2618
  //#endregion
2636
- //#region extensions/imessage/src/monitor/runtime.ts
2637
- function resolveRuntime(opts) {
2638
- return opts.runtime ?? createNonExitingRuntime();
2639
- }
2640
- //#endregion
2641
2619
  //#region extensions/imessage/src/monitor/self-chat-cache.ts
2642
2620
  const SELF_CHAT_TTL_MS = 1e4;
2643
2621
  const SELF_CHAT_CREATED_AT_TOLERANCE_MS = 1e3;
@@ -2806,9 +2784,6 @@ function formatIMessageInboundMediaBody(params) {
2806
2784
  notice: `[imessage ${params.unavailableCount > 1 ? `${params.unavailableCount} attachments` : "attachment"} unavailable]`
2807
2785
  });
2808
2786
  }
2809
- function resolveIMessageWatchSourceDbPath(params) {
2810
- return resolveIMessageChatDbLookupPath(params);
2811
- }
2812
2787
  const warnIfImsgUpgradeNeeded = (() => {
2813
2788
  let fired = false;
2814
2789
  return { fireOnce: (rpcMethods, runtime) => {
@@ -2856,7 +2831,7 @@ async function waitForWatchSubscribeRetryDelay(params) {
2856
2831
  });
2857
2832
  }
2858
2833
  async function monitorIMessageProvider(opts = {}) {
2859
- const runtime = resolveRuntime(opts);
2834
+ const runtime = opts.runtime ?? createNonExitingRuntime();
2860
2835
  const cfg = opts.config ?? getRuntimeConfig();
2861
2836
  const readConfig = createRuntimeConfigReader(cfg);
2862
2837
  const accountInfo = resolveIMessageAccount({
@@ -2926,9 +2901,24 @@ async function monitorIMessageProvider(opts = {}) {
2926
2901
  cliPath,
2927
2902
  remoteHost: imessageCfg.remoteHost
2928
2903
  });
2904
+ const sendTyping = (target, isTyping, client) => sendIMessageTyping(target, isTyping, {
2905
+ cfg,
2906
+ accountId: accountInfo.accountId,
2907
+ cliPath,
2908
+ dbPath,
2909
+ remoteHost,
2910
+ ...client ? { client } : {}
2911
+ });
2912
+ const logTypingError = (action, target, error) => logTypingFailure({
2913
+ log: logVerbose,
2914
+ channel: "imessage",
2915
+ action,
2916
+ target,
2917
+ error
2918
+ });
2929
2919
  let staleBacklogSuppressed = 0;
2930
2920
  const loggedThrottledDropDiagnostics = createIMessageThrottledDropDiagnosticCache();
2931
- const watchSourceDbPath = resolveIMessageWatchSourceDbPath({
2921
+ const watchSourceDbPath = resolveIMessageChatDbLookupPath({
2932
2922
  cliPath,
2933
2923
  dbPath,
2934
2924
  remoteHost
@@ -3047,9 +3037,8 @@ async function monitorIMessageProvider(opts = {}) {
3047
3037
  });
3048
3038
  }
3049
3039
  function resolveLiveCatchupCursor(message) {
3050
- const coalescedCursor = message.coalescedCatchupCursor;
3051
- const rowid = typeof coalescedCursor?.lastSeenRowid === "number" && Number.isFinite(coalescedCursor.lastSeenRowid) ? coalescedCursor.lastSeenRowid : typeof message.id === "number" && Number.isFinite(message.id) ? message.id : null;
3052
- const dateMs = typeof coalescedCursor?.lastSeenMs === "number" && Number.isFinite(coalescedCursor.lastSeenMs) ? coalescedCursor.lastSeenMs : typeof message.created_at === "string" ? Date.parse(message.created_at) : NaN;
3040
+ const rowid = typeof message.id === "number" && Number.isFinite(message.id) ? message.id : null;
3041
+ const dateMs = typeof message.created_at === "string" ? Date.parse(message.created_at) : NaN;
3053
3042
  if (rowid === null || !Number.isFinite(dateMs)) return null;
3054
3043
  return {
3055
3044
  lastSeenMs: dateMs,
@@ -3247,20 +3236,8 @@ async function monitorIMessageProvider(opts = {}) {
3247
3236
  const earlyDirectTypingTarget = shouldStartDirectTyping ? `${earlyDirectTypingService}:${decision.sender}` : void 0;
3248
3237
  let stopEarlyDirectTyping;
3249
3238
  if (earlyDirectTypingTarget) {
3250
- const earlyDirectTypingStarted = sendIMessageTyping(earlyDirectTypingTarget, true, {
3251
- cfg,
3252
- accountId: accountInfo.accountId,
3253
- cliPath,
3254
- dbPath,
3255
- remoteHost
3256
- }).then(() => true, (err) => {
3257
- logTypingFailure({
3258
- log: (msg) => logVerbose(msg),
3259
- channel: "imessage",
3260
- action: "start",
3261
- target: earlyDirectTypingTarget,
3262
- error: err
3263
- });
3239
+ const earlyDirectTypingStarted = sendTyping(earlyDirectTypingTarget, true).then(() => true, (err) => {
3240
+ logTypingError("start", earlyDirectTypingTarget, err);
3264
3241
  return false;
3265
3242
  });
3266
3243
  let earlyTypingStopQueued = false;
@@ -3269,21 +3246,9 @@ async function monitorIMessageProvider(opts = {}) {
3269
3246
  earlyTypingStopQueued = true;
3270
3247
  earlyDirectTypingStarted.then(async (started) => {
3271
3248
  if (!started) return;
3272
- await sendIMessageTyping(earlyDirectTypingTarget, false, {
3273
- cfg,
3274
- accountId: accountInfo.accountId,
3275
- cliPath,
3276
- dbPath,
3277
- remoteHost
3278
- });
3249
+ await sendTyping(earlyDirectTypingTarget, false);
3279
3250
  }).catch((err) => {
3280
- logTypingFailure({
3281
- log: (msg) => logVerbose(msg),
3282
- channel: "imessage",
3283
- action: "stop",
3284
- target: earlyDirectTypingTarget,
3285
- error: err
3286
- });
3251
+ logTypingError("stop", earlyDirectTypingTarget, err);
3287
3252
  });
3288
3253
  };
3289
3254
  }
@@ -3364,45 +3329,15 @@ async function monitorIMessageProvider(opts = {}) {
3364
3329
  accountId: decision.route.accountId,
3365
3330
  typing: supportsTyping && typingTarget ? {
3366
3331
  start: async () => {
3367
- await sendIMessageTyping(typingTarget, true, {
3368
- cfg,
3369
- accountId: accountInfo.accountId,
3370
- client: getActiveClient(),
3371
- cliPath,
3372
- dbPath,
3373
- remoteHost
3374
- });
3332
+ await sendTyping(typingTarget, true, getActiveClient());
3375
3333
  },
3376
3334
  stop: async () => {
3377
- await sendIMessageTyping(typingTarget, false, {
3378
- cfg,
3379
- accountId: accountInfo.accountId,
3380
- client: getActiveClient(),
3381
- cliPath,
3382
- dbPath,
3383
- remoteHost
3384
- });
3335
+ await sendTyping(typingTarget, false, getActiveClient());
3385
3336
  },
3386
3337
  keepaliveIntervalMs: IMESSAGE_TYPING_KEEPALIVE_INTERVAL_MS,
3387
3338
  maxDurationMs: IMESSAGE_TYPING_KEEPALIVE_MAX_DURATION_MS,
3388
- onStartError: (err) => {
3389
- logTypingFailure({
3390
- log: (msg) => logVerbose(msg),
3391
- channel: "imessage",
3392
- action: "start",
3393
- target: typingTarget,
3394
- error: err
3395
- });
3396
- },
3397
- onStopError: (err) => {
3398
- logTypingFailure({
3399
- log: (msg) => logVerbose(msg),
3400
- channel: "imessage",
3401
- action: "stop",
3402
- target: typingTarget,
3403
- error: err
3404
- });
3405
- }
3339
+ onStartError: (err) => logTypingError("start", typingTarget, err),
3340
+ onStopError: (err) => logTypingError("stop", typingTarget, err)
3406
3341
  } : void 0
3407
3342
  });
3408
3343
  const dispatcherOptions = {
@@ -3647,17 +3582,13 @@ async function monitorIMessageProvider(opts = {}) {
3647
3582
  else if (msg.method === "error") runtime.error?.(`imessage: watch error ${JSON.stringify(sanitizeIMessageWatchErrorPayload(msg.params))}`);
3648
3583
  }
3649
3584
  });
3650
- const requireWatchClient = (watchClient) => {
3651
- if (!watchClient) throw new Error("imessage monitor client not initialized");
3652
- return watchClient;
3653
- };
3654
3585
  for (let attempt = 1; attempt <= WATCH_SUBSCRIBE_MAX_ATTEMPTS; attempt++) {
3655
3586
  if (abort?.aborted) return;
3656
3587
  let attemptClient;
3657
3588
  let attemptDetachAbortHandler = () => {};
3658
3589
  let keepAttemptClient = false;
3659
3590
  try {
3660
- attemptClient = requireWatchClient(await createWatchClient());
3591
+ attemptClient = await createWatchClient();
3661
3592
  let attemptSubscriptionId = null;
3662
3593
  attemptDetachAbortHandler = attachIMessageMonitorAbortHandler({
3663
3594
  abortSignal: abort,