@openclaw/imessage 2026.9.1-beta.1 → 2026.9.2

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 (47) hide show
  1. package/dist/{accounts-DIpGOIiN.js → accounts-CCirBEIm.js} +2 -3
  2. package/dist/{actions.runtime-BhggncbG.js → actions.runtime-D5MAgeSs.js} +5 -5
  3. package/dist/api.js +5 -5
  4. package/dist/{approval-handler.runtime-BC-YsgAf.js → approval-handler.runtime-47rs6THN.js} +14 -9
  5. package/dist/{approval-reactions-Cvwmdlev.js → approval-reactions-sVpDno7d.js} +8 -16
  6. package/dist/{channel-CnKQ2MVB.js → channel-BxraMrmK.js} +21 -14
  7. package/dist/channel-config-api.js +1 -1
  8. package/dist/channel-plugin-api.js +1 -1
  9. package/dist/{channel.runtime-BQLCh7bE.js → channel.runtime-2jwkMPGR.js} +59 -14
  10. package/dist/{config-schema-DV0KP5nC.js → config-schema-DuGbA8Kp.js} +8 -7
  11. package/dist/contract-api.js +1 -1
  12. package/dist/doctor-contract-api.js +8 -281
  13. package/dist/{group-policy-BipELT2x.js → group-policy-Cp8SDG4G.js} +18 -12
  14. package/dist/media-contract-api.js +1 -1
  15. package/dist/{media-contract-BVXuQHG2.js → media-contract-pns3TSS5.js} +3 -2
  16. package/dist/{message-tool-api-BwIxJDoz.js → message-tool-api-CUoFESHj.js} +11 -6
  17. package/dist/message-tool-api.js +1 -1
  18. package/dist/{monitor-DRD_Dnp9.js → monitor-CESwxMQ4.js} +319 -48
  19. package/dist/{question-reactions-viP7ZcyG.js → question-reactions-9tBYB1_F.js} +4 -7
  20. package/dist/{reaction-context-BAYI7pz0.js → reaction-context-Br-vAJFp.js} +9 -3
  21. package/dist/{remote-file-CyTTuRAt.js → remote-file-Biz5PjXi.js} +5 -53
  22. package/dist/runtime-Cza4CY5T.js +8 -0
  23. package/dist/runtime-api.js +7 -7
  24. package/dist/{sanitize-outbound-g651fIhi.js → sanitize-outbound-Bvo4g1Mp.js} +470 -82
  25. package/dist/{send-DVQY6M5M.js → send-DC981dqK.js} +146 -23
  26. package/dist/state-contract-DvahEY4X.js +56 -0
  27. package/dist/state-migrations-D-fyHVcb.js +277 -0
  28. package/node_modules/typebox/build/format/email.mjs +1 -1
  29. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  30. package/node_modules/typebox/build/format/json_pointer.d.mts +2 -2
  31. package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
  32. package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
  33. package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
  34. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  35. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  36. package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
  37. package/node_modules/typebox/build/system/memory/create.mjs +3 -4
  38. package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
  39. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  40. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  41. package/node_modules/typebox/build/system/memory/update.mjs +3 -2
  42. package/node_modules/typebox/package.json +1 -1
  43. package/openclaw.plugin.json +2 -7
  44. package/package.json +5 -5
  45. package/dist/catchup-Dh-Rv4Ur.js +0 -294
  46. package/dist/monitor-reply-cache-BdeUQaHO.js +0 -353
  47. package/dist/persisted-echo-cache-DGxy-J4t.js +0 -143
@@ -1,15 +1,14 @@
1
- import { d as resolveIMessageChatDbLookupPath, f as resolveIMessageHomeDir, l as resolveIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-DIpGOIiN.js";
2
- import { c as parseIMessageAllowTarget, f as getCachedIMessagePrivateApiStatus, i as isAllowedIMessageReplyContextSender, l as parseIMessageTarget, n as formatIMessageChatTarget, p as imessageRpcSupportsMethod, s as normalizeIMessageHandle } from "./message-tool-api-BwIxJDoz.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-g651fIhi.js";
5
- import { f as probeIMessage, r as resolveIMessageInboundConversationId } from "./group-policy-BipELT2x.js";
6
- import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-BVXuQHG2.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-Cvwmdlev.js";
10
- import { a as maybeResolveIMessageApprovalPollVote, o as iMessageApprovalControlBindings, t as sendMessageIMessage } from "./send-DVQY6M5M.js";
11
- import { t as resolveIMessageReactionContext } from "./reaction-context-BAYI7pz0.js";
12
- import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-viP7ZcyG.js";
1
+ import { d as resolveIMessageChatDbLookupPath, f as resolveIMessageHomeDir, l as resolveIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-CCirBEIm.js";
2
+ import { c as parseIMessageAllowTarget, f as getCachedIMessagePrivateApiStatus, i as isAllowedIMessageReplyContextSender, l as parseIMessageTarget, n as formatIMessageChatTarget, p as imessageRpcSupportsMethod, s as normalizeIMessageHandle } from "./message-tool-api-CUoFESHj.js";
3
+ import { d as rememberIMessageReplyCache, o as createIMessageRpcClient, r as sanitizeOutboundText, u as isKnownFromMeIMessageMessageId, y as resolveIMessageDirectChatService } from "./sanitize-outbound-Bvo4g1Mp.js";
4
+ import { f as probeIMessage, r as resolveIMessageInboundConversationId } from "./group-policy-Cp8SDG4G.js";
5
+ import { t as getIMessageRuntime } from "./runtime-Cza4CY5T.js";
6
+ import { d as resolveIMessageEchoMediaKey, l as capFailureRetriesMap, t as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, u as resolveIMessageCatchupCursorKey } from "./state-contract-DvahEY4X.js";
7
+ import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-pns3TSS5.js";
8
+ import { i as registerIMessageApprovalReactionTarget, n as handleIMessageApprovalReaction, o as listPendingIMessageApprovalReactionPollTargets, r as maybeResolveIMessageApprovalReaction, s as buildIMessageApprovalConversationKeyForInbound } from "./approval-reactions-sVpDno7d.js";
9
+ import { c as iMessageApprovalControlBindings, n as hasPersistedIMessageEcho, r as stripLeadingEchoTextCorruptionMarkers, s as maybeResolveIMessageApprovalPollVote, t as sendMessageIMessage } from "./send-DC981dqK.js";
10
+ import { n as normalizeIMessageGuid, t as resolveIMessageReactionContext } from "./reaction-context-Br-vAJFp.js";
11
+ import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-9tBYB1_F.js";
13
12
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, listMessageReceiptPlatformIds, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
14
13
  import { chunkMarkdownTextWithMode as chunkTextWithMode, resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
15
14
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
@@ -19,16 +18,14 @@ import { asSafeIntegerInRange, hasNonEmptyString, isRecord, normalizeOptionalStr
19
18
  import fs from "node:fs/promises";
20
19
  import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, warn } from "openclaw/plugin-sdk/runtime-env";
21
20
  import { isInboundPathAllowed, kindFromMime } from "openclaw/plugin-sdk/media-runtime";
22
- import { asDateTimestampMs, asPositiveFiniteNumber, parseDateStringTimestampMs, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
21
+ import { asDateTimestampMs, asPositiveFiniteNumber, parseDateStringTimestampMs, resolveIntegerOption, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
23
22
  import { createHash } from "node:crypto";
24
- import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
23
+ import { createRuntimeConfigReader, getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
25
24
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
26
25
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
27
- import { buildChannelGroupsScopeTree, resolveChannelGroupPolicy, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
26
+ import { buildChannelGroupsScopeTree, resolveChannelGroupPolicy, resolveChannelGroups, resolveChannelGroupsConfigPath, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
28
27
  import { convertMarkdownTables as convertMarkdownTables$1, findCodeRegions, isInsideCode, sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
29
- import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, createChannelPartialDeliveryError, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, formatMediaPlaceholderText, isChannelPartialDeliveryError, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
30
- import { openLocalFileSafely, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
31
- import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
28
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, createChannelPartialDeliveryError, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, formatMediaPlaceholderText, isChannelPartialDeliveryError, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundDebounceMs, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
32
29
  import { resolveAgentConfig, resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
33
30
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
34
31
  import { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback";
@@ -40,12 +37,14 @@ import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
40
37
  import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
41
38
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
42
39
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
40
+ import { openLocalFileSafely, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
43
41
  import { getSessionEntry, readSessionUpdatedAt, resolveSendPolicy, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
44
42
  import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
45
43
  import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
46
44
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
47
45
  import { resolveMarkdownTableMode as resolveMarkdownTableMode$1 } from "openclaw/plugin-sdk/markdown-table-runtime";
48
46
  import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
47
+ import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
49
48
  import { deliverTextOrMediaReply, resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
50
49
  import { createDedupeCache } from "openclaw/plugin-sdk/dedupe-runtime";
51
50
  import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
@@ -74,13 +73,10 @@ function hasUnscopedTarget(targets) {
74
73
  function uniqueChatIds(chatIds) {
75
74
  return [...new Set(chatIds)];
76
75
  }
77
- function normalizeMessageGuid(value) {
78
- return value.trim().replace(/^p:\d+\//iu, "");
79
- }
80
76
  function enumerateMessageGuidCandidates(value) {
81
77
  const trimmed = value.trim();
82
78
  if (!trimmed) return [];
83
- return [trimmed, normalizeMessageGuid(trimmed)].filter((candidate, index, candidates) => candidate.length > 0 && candidates.indexOf(candidate) === index);
79
+ return [trimmed, normalizeIMessageGuid(trimmed)].filter((candidate, index, candidates) => candidate.length > 0 && candidates.indexOf(candidate) === index);
84
80
  }
85
81
  function buildPendingTargetsByMessageId(targets) {
86
82
  const pendingByMessageId = /* @__PURE__ */ new Map();
@@ -171,7 +167,7 @@ async function pollPendingIMessageApprovalReactions(params) {
171
167
  for (const message of messages) {
172
168
  const targetGuid = message.guid?.trim();
173
169
  if (!targetGuid) continue;
174
- const target = pendingByMessageId.get(targetGuid) ?? pendingByMessageId.get(normalizeMessageGuid(targetGuid));
170
+ const target = pendingByMessageId.get(targetGuid) ?? pendingByMessageId.get(normalizeIMessageGuid(targetGuid));
175
171
  if (!target) continue;
176
172
  bindObservedConversation({
177
173
  target,
@@ -264,6 +260,269 @@ function attachIMessageMonitorAbortHandler(params) {
264
260
  return () => abort.removeEventListener("abort", onAbort);
265
261
  }
266
262
  //#endregion
263
+ //#region extensions/imessage/src/monitor/catchup.ts
264
+ const DEFAULT_MAX_AGE_MINUTES = 120;
265
+ const MAX_MAX_AGE_MINUTES = 720;
266
+ const DEFAULT_PER_RUN_LIMIT = 50;
267
+ const MAX_PER_RUN_LIMIT = 500;
268
+ const DEFAULT_FIRST_RUN_LOOKBACK_MINUTES = 30;
269
+ const DEFAULT_MAX_FAILURE_RETRIES = 10;
270
+ const MAX_MAX_FAILURE_RETRIES = 1e3;
271
+ const cursorWriteQueue = new KeyedAsyncQueue();
272
+ function openCatchupCursorStore() {
273
+ return getIMessageRuntime().state.openSyncKeyedStore({
274
+ namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
275
+ maxEntries: 256
276
+ });
277
+ }
278
+ function updateCatchupCursorStore(key, updateValue) {
279
+ const store = openCatchupCursorStore();
280
+ if (!store.update) throw new Error("iMessage catchup cursor persistence requires atomic plugin-state update.");
281
+ return store.update(key, updateValue);
282
+ }
283
+ function enqueueCursorWrite(accountId, fn) {
284
+ const key = resolveIMessageCatchupCursorKey(accountId);
285
+ return cursorWriteQueue.enqueue(key, fn);
286
+ }
287
+ function sanitizeFailureRetriesInput(raw) {
288
+ if (!raw || typeof raw !== "object") return {};
289
+ const out = {};
290
+ for (const [guid, count] of Object.entries(raw)) {
291
+ if (!guid || typeof guid !== "string") continue;
292
+ if (typeof count !== "number" || !Number.isFinite(count) || count <= 0) continue;
293
+ out[guid] = Math.floor(count);
294
+ }
295
+ return out;
296
+ }
297
+ function normalizeIMessageCatchupCursor(value) {
298
+ if (!value || typeof value !== "object") return null;
299
+ const raw = value;
300
+ if (typeof raw.lastSeenMs !== "number" || !Number.isFinite(raw.lastSeenMs)) return null;
301
+ if (typeof raw.lastSeenRowid !== "number" || !Number.isFinite(raw.lastSeenRowid)) return null;
302
+ const failureRetries = sanitizeFailureRetriesInput(raw.failureRetries);
303
+ const hasRetries = Object.keys(failureRetries).length > 0;
304
+ return {
305
+ lastSeenMs: raw.lastSeenMs,
306
+ lastSeenRowid: raw.lastSeenRowid,
307
+ updatedAt: typeof raw.updatedAt === "number" ? raw.updatedAt : 0,
308
+ ...hasRetries ? { failureRetries } : {}
309
+ };
310
+ }
311
+ function readIMessageCatchupCursor(accountId) {
312
+ return normalizeIMessageCatchupCursor(openCatchupCursorStore().lookup(resolveIMessageCatchupCursorKey(accountId)));
313
+ }
314
+ async function loadIMessageCatchupCursor(accountId) {
315
+ return readIMessageCatchupCursor(accountId);
316
+ }
317
+ function buildIMessageCatchupCursor(next) {
318
+ const sanitized = sanitizeFailureRetriesInput(next.failureRetries);
319
+ const hasRetries = Object.keys(sanitized).length > 0;
320
+ return {
321
+ lastSeenMs: next.lastSeenMs,
322
+ lastSeenRowid: next.lastSeenRowid,
323
+ updatedAt: Date.now(),
324
+ ...hasRetries ? { failureRetries: sanitized } : {}
325
+ };
326
+ }
327
+ async function saveIMessageCatchupCursor(accountId, next, options = {}) {
328
+ const cursor = buildIMessageCatchupCursor(next);
329
+ updateCatchupCursorStore(resolveIMessageCatchupCursorKey(accountId), (existingValue) => {
330
+ const existing = normalizeIMessageCatchupCursor(existingValue);
331
+ if (existing && cursor.lastSeenRowid < existing.lastSeenRowid) {
332
+ if (!options.allowCursorRewindForRetries) return;
333
+ return buildIMessageCatchupCursor({
334
+ lastSeenMs: cursor.lastSeenMs,
335
+ lastSeenRowid: cursor.lastSeenRowid,
336
+ failureRetries: {
337
+ ...existing.failureRetries,
338
+ ...cursor.failureRetries
339
+ }
340
+ });
341
+ }
342
+ return cursor;
343
+ });
344
+ }
345
+ function clampInt(value, min, max, fallback) {
346
+ return resolveIntegerOption(value, fallback, {
347
+ min,
348
+ max
349
+ });
350
+ }
351
+ function resolveCatchupConfig(raw) {
352
+ return {
353
+ enabled: Boolean(raw?.enabled),
354
+ maxAgeMinutes: clampInt(raw?.maxAgeMinutes, 1, MAX_MAX_AGE_MINUTES, DEFAULT_MAX_AGE_MINUTES),
355
+ perRunLimit: clampInt(raw?.perRunLimit, 1, MAX_PER_RUN_LIMIT, DEFAULT_PER_RUN_LIMIT),
356
+ firstRunLookbackMinutes: clampInt(raw?.firstRunLookbackMinutes, 1, MAX_MAX_AGE_MINUTES, DEFAULT_FIRST_RUN_LOOKBACK_MINUTES),
357
+ maxFailureRetries: clampInt(raw?.maxFailureRetries, 1, MAX_MAX_FAILURE_RETRIES, DEFAULT_MAX_FAILURE_RETRIES)
358
+ };
359
+ }
360
+ async function advanceIMessageCatchupCursor(accountId, next, config) {
361
+ if (!Number.isFinite(next.lastSeenMs) || !Number.isFinite(next.lastSeenRowid)) return false;
362
+ return await enqueueCursorWrite(accountId, async () => {
363
+ let advanced = false;
364
+ updateCatchupCursorStore(resolveIMessageCatchupCursorKey(accountId), (existingValue) => {
365
+ const cursor = normalizeIMessageCatchupCursor(existingValue);
366
+ if (cursor && next.lastSeenRowid <= cursor.lastSeenRowid) return;
367
+ if (Object.values(cursor?.failureRetries ?? {}).some((count) => count < config.maxFailureRetries)) return;
368
+ advanced = true;
369
+ return buildIMessageCatchupCursor({
370
+ lastSeenMs: Math.max(cursor?.lastSeenMs ?? next.lastSeenMs, next.lastSeenMs),
371
+ lastSeenRowid: next.lastSeenRowid,
372
+ failureRetries: cursor?.failureRetries
373
+ });
374
+ });
375
+ return advanced;
376
+ });
377
+ }
378
+ /**
379
+ * One catchup pass. Loads the cursor, fetches `messages.history`, replays
380
+ * each row through `dispatch`, advances the cursor on success / give-up,
381
+ * persists the cursor, returns a summary.
382
+ *
383
+ * The fetch and dispatch functions are injected so this loop is unit-testable
384
+ * without standing up an `imsg` daemon. The wiring in `monitor-provider.ts`
385
+ * passes the live `client.request("messages.history", ...)` adapter as
386
+ * `fetch` and the `evaluateIMessageInbound` + `runChannelInboundEvent`
387
+ * pipeline as `dispatch`.
388
+ */
389
+ async function performIMessageCatchup(params) {
390
+ const now = params.now ?? Date.now();
391
+ const cfg = params.config;
392
+ const cursor = await loadIMessageCatchupCursor(params.accountId);
393
+ const lookbackMs = cursor === null ? cfg.firstRunLookbackMinutes * 6e4 : cfg.maxAgeMinutes * 6e4;
394
+ const ageBoundMs = now - cfg.maxAgeMinutes * 6e4;
395
+ const windowStartMs = Math.max(cursor?.lastSeenMs ?? now - lookbackMs, ageBoundMs);
396
+ const windowEndMs = now;
397
+ const sinceRowid = cursor?.lastSeenRowid ?? 0;
398
+ const summary = {
399
+ querySucceeded: false,
400
+ fullyCaughtUp: false,
401
+ fetchedCount: 0,
402
+ replayed: 0,
403
+ skippedFromMe: 0,
404
+ skippedPreCursor: 0,
405
+ skippedGivenUp: 0,
406
+ failed: 0,
407
+ givenUp: 0,
408
+ cursorBefore: cursor ? {
409
+ lastSeenMs: cursor.lastSeenMs,
410
+ lastSeenRowid: cursor.lastSeenRowid
411
+ } : null,
412
+ cursorAfter: {
413
+ lastSeenMs: cursor?.lastSeenMs ?? windowStartMs,
414
+ lastSeenRowid: cursor?.lastSeenRowid ?? 0
415
+ },
416
+ windowStartMs,
417
+ windowEndMs
418
+ };
419
+ let fetchResult;
420
+ try {
421
+ fetchResult = await params.fetch({
422
+ sinceMs: windowStartMs,
423
+ sinceRowid,
424
+ limit: cfg.perRunLimit
425
+ });
426
+ } catch (err) {
427
+ params.warn?.(`imessage catchup: fetch failed: ${String(err)}`);
428
+ return summary;
429
+ }
430
+ if (!fetchResult.resolved) {
431
+ params.warn?.(`imessage catchup: fetch returned unresolved result`);
432
+ return summary;
433
+ }
434
+ summary.querySucceeded = true;
435
+ summary.fullyCaughtUp = fetchResult.fullyCaughtUp !== false;
436
+ summary.fetchedCount = fetchResult.rows.length;
437
+ const rows = fetchResult.rows.toSorted((a, b) => a.rowid - b.rowid);
438
+ const failureRetries = { ...cursor?.failureRetries };
439
+ const cursorBeforeMs = cursor?.lastSeenMs ?? windowStartMs;
440
+ const cursorBeforeRowid = cursor?.lastSeenRowid ?? 0;
441
+ let highWatermarkMs = cursorBeforeMs;
442
+ let highWatermarkRowid = cursorBeforeRowid;
443
+ let earliestHeldFailureRow = null;
444
+ for (const row of rows) {
445
+ if (row.rowid <= sinceRowid) {
446
+ summary.skippedPreCursor += 1;
447
+ continue;
448
+ }
449
+ if (row.date < ageBoundMs) {
450
+ summary.skippedPreCursor += 1;
451
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
452
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
453
+ continue;
454
+ }
455
+ if (row.isFromMe) {
456
+ try {
457
+ await params.observeSkippedFromMe?.(row);
458
+ } catch (err) {
459
+ params.warn?.(`imessage catchup: from-me observer failed for guid=${row.guid}: ${String(err)}`);
460
+ }
461
+ summary.skippedFromMe += 1;
462
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
463
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
464
+ continue;
465
+ }
466
+ const priorCount = failureRetries[row.guid] ?? 0;
467
+ if (priorCount >= cfg.maxFailureRetries) {
468
+ summary.skippedGivenUp += 1;
469
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
470
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
471
+ continue;
472
+ }
473
+ let dispatched;
474
+ try {
475
+ dispatched = await params.dispatch(row);
476
+ } catch (err) {
477
+ params.warn?.(`imessage catchup: dispatch threw for guid=${row.guid}: ${String(err)}`);
478
+ dispatched = { ok: false };
479
+ }
480
+ if (dispatched.ok) {
481
+ summary.replayed += 1;
482
+ delete failureRetries[row.guid];
483
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
484
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
485
+ continue;
486
+ }
487
+ const nextCount = priorCount + 1;
488
+ failureRetries[row.guid] = nextCount;
489
+ summary.failed += 1;
490
+ if (nextCount >= cfg.maxFailureRetries) {
491
+ summary.givenUp += 1;
492
+ params.warn?.(`imessage catchup: giving up on guid=${row.guid} after ${nextCount} failures; advancing cursor past it`);
493
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
494
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
495
+ continue;
496
+ }
497
+ if (earliestHeldFailureRow === null || row.rowid < earliestHeldFailureRow.rowid) earliestHeldFailureRow = row;
498
+ }
499
+ if (earliestHeldFailureRow === null) {
500
+ if (typeof fetchResult.highWatermarkMs === "number") highWatermarkMs = Math.max(highWatermarkMs, fetchResult.highWatermarkMs);
501
+ if (typeof fetchResult.highWatermarkRowid === "number") highWatermarkRowid = Math.max(highWatermarkRowid, fetchResult.highWatermarkRowid);
502
+ }
503
+ let lastSeenMs;
504
+ let lastSeenRowid;
505
+ if (earliestHeldFailureRow !== null) {
506
+ lastSeenMs = Math.max(cursorBeforeMs, earliestHeldFailureRow.date - 1);
507
+ lastSeenRowid = Math.max(cursorBeforeRowid, earliestHeldFailureRow.rowid - 1);
508
+ } else {
509
+ lastSeenMs = highWatermarkMs;
510
+ lastSeenRowid = highWatermarkRowid;
511
+ }
512
+ const capped = capFailureRetriesMap(failureRetries);
513
+ summary.cursorAfter = {
514
+ lastSeenMs,
515
+ lastSeenRowid
516
+ };
517
+ await saveIMessageCatchupCursor(params.accountId, {
518
+ lastSeenMs,
519
+ lastSeenRowid,
520
+ failureRetries: capped
521
+ }, { allowCursorRewindForRetries: earliestHeldFailureRow !== null });
522
+ if (summary.replayed > 0 || summary.failed > 0 || summary.givenUp > 0) params.log?.(`imessage catchup: replayed=${summary.replayed} skippedFromMe=${summary.skippedFromMe} skippedGivenUp=${summary.skippedGivenUp} failed=${summary.failed} givenUp=${summary.givenUp} fetchedCount=${summary.fetchedCount}`);
523
+ return summary;
524
+ }
525
+ //#endregion
267
526
  //#region extensions/imessage/src/monitor/strip-imsg-length-prefixed-text.ts
268
527
  const utf8Decoder = new TextDecoder();
269
528
  function readVarint(buf, start) {
@@ -515,7 +774,7 @@ function combineIMessagePayloads(payloads) {
515
774
  const first = expectDefined(payloads[0], "first iMessage payload to coalesce");
516
775
  if (payloads.length === 1) return first;
517
776
  const last = expectDefined(payloads.at(-1), "last iMessage payload to coalesce");
518
- const boundedPayloads = payloads.length > MAX_COALESCED_ENTRIES ? [...payloads.slice(0, MAX_COALESCED_ENTRIES - 1), last] : payloads;
777
+ const boundedPayloads = payloads.length > MAX_COALESCED_ENTRIES ? [...payloads.slice(0, 9), last] : payloads;
519
778
  const seenTexts = /* @__PURE__ */ new Set();
520
779
  const textParts = [];
521
780
  for (const payload of boundedPayloads) {
@@ -698,7 +957,8 @@ async function deliverIMessageReply(params) {
698
957
  accountId
699
958
  });
700
959
  const chunkMode = resolveChunkMode(cfg, "imessage", accountId);
701
- const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables$1(sanitizeOutboundText(payload.text ?? ""), tableMode) });
960
+ const rawText = sanitizeOutboundText(payload.text ?? "");
961
+ const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables$1(rawText, tableMode) });
702
962
  const accepted = [];
703
963
  const sendAccepted = async (text, mediaUrl) => {
704
964
  const sent = await sendMessageIMessage(target, text, {
@@ -760,7 +1020,8 @@ async function deliverIMessageReply(params) {
760
1020
  }
761
1021
  function createIMessageEchoCachingSend(params) {
762
1022
  return async (target, text, opts) => {
763
- const sent = await sendMessageIMessage(target, sanitizeOutboundText(text), opts);
1023
+ const sanitizedText = sanitizeOutboundText(text);
1024
+ const sent = await sendMessageIMessage(target, sanitizedText, opts);
764
1025
  const scope = `${params.accountId ?? opts.accountId ?? ""}:${target}`;
765
1026
  const echoText = sent.echoText ?? (sent.sentText || void 0);
766
1027
  params.sentMessageCache?.remember(scope, {
@@ -975,8 +1236,8 @@ function warnGroupAllowlistDropPerChatOnce(params) {
975
1236
  }
976
1237
  //#endregion
977
1238
  //#region extensions/imessage/src/monitor/inbound-dedupe.ts
978
- const IMESSAGE_STALE_INBOUND_THRESHOLD_MS = 900 * 1e3;
979
- const IMESSAGE_RECOVERY_MAX_AGE_MS = 7200 * 1e3;
1239
+ const IMESSAGE_STALE_INBOUND_THRESHOLD_MS = 9e5;
1240
+ const IMESSAGE_RECOVERY_MAX_AGE_MS = 72e5;
980
1241
  /**
981
1242
  * Age fence: true when the message's own send date is materially older than
982
1243
  * now, i.e. stale backlog rather than a live message. Fails open (returns
@@ -1720,6 +1981,7 @@ async function buildIMessageInboundContext(params) {
1720
1981
  const directService = resolveIMessageDirectChatService(params.accountService, decision.chatGuid) ?? "auto";
1721
1982
  const imessageTo = decision.isGroup ? chatTarget || `imessage:${decision.sender}` : `${directService}:${decision.sender}`;
1722
1983
  const imessageFrom = decision.isGroup ? `imessage:group:${chatId ?? "unknown"}` : imessageTo;
1984
+ const replyTarget = decision.isGroup ? imessageTo : chatId != null ? `chat_id:${chatId}` : decision.chatGuid ? `chat_guid:${decision.chatGuid}` : imessageTo;
1723
1985
  const inboundHistory = !decision.isGroup && params.dmHistory?.inboundHistory ? params.dmHistory.inboundHistory : decision.isGroup && decision.historyKey && params.historyLimit > 0 ? createChannelHistoryWindow({ historyMap: params.groupHistories }).buildInboundHistory({
1724
1986
  historyKey: decision.historyKey,
1725
1987
  limit: params.historyLimit
@@ -1747,7 +2009,8 @@ async function buildIMessageInboundContext(params) {
1747
2009
  from: imessageFrom,
1748
2010
  sender: {
1749
2011
  id: decision.sender,
1750
- name: decision.senderNormalized
2012
+ name: decision.senderNormalized,
2013
+ isSelf: params.message.is_from_me === true
1751
2014
  },
1752
2015
  conversation: {
1753
2016
  kind: decision.isGroup ? "group" : "direct",
@@ -1764,7 +2027,7 @@ async function buildIMessageInboundContext(params) {
1764
2027
  accountId: decision.route.accountId,
1765
2028
  routeSessionKey: decision.route.sessionKey
1766
2029
  },
1767
- reply: { to: imessageTo },
2030
+ reply: { to: replyTarget },
1768
2031
  message: {
1769
2032
  body: combinedBody,
1770
2033
  bodyForAgent: decision.agentBodyText ?? decision.bodyText,
@@ -1922,7 +2185,7 @@ function createIMessageDurableIngress(options) {
1922
2185
  },
1923
2186
  pollIntervalMs: IMESSAGE_INGRESS_DRAIN_INTERVAL_MS,
1924
2187
  retention: {
1925
- completedTtlMs: 14400 * 1e3,
2188
+ completedTtlMs: 144e5,
1926
2189
  completedMaxEntries: 1e4,
1927
2190
  failedMaxEntries: 1e3
1928
2191
  },
@@ -2049,18 +2312,18 @@ async function readPinnedAttachmentBytes(handle, maxBytes) {
2049
2312
  let totalBytes = 0;
2050
2313
  while (totalBytes <= maxBytes) {
2051
2314
  const remaining = maxBytes + 1 - totalBytes;
2052
- const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
2315
+ const chunk = Buffer.allocUnsafe(Math.min(65536, remaining));
2053
2316
  const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
2054
2317
  if (bytesRead === 0) return Buffer.concat(chunks, totalBytes);
2055
2318
  chunks.push(chunk.subarray(0, bytesRead));
2056
2319
  totalBytes += bytesRead;
2057
2320
  }
2058
- throw new Error(`attachment exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
2321
+ throw new Error(`attachment exceeds ${Math.round(maxBytes / 1048576)}MB limit`);
2059
2322
  }
2060
2323
  async function readAttachmentBuffer(params) {
2061
2324
  const opened = await (params.deps.openLocalFileSafely ?? openLocalFileSafely)({ filePath: params.attachmentPath });
2062
2325
  try {
2063
- if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / (1024 * 1024))}MB limit`);
2326
+ if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2064
2327
  await assertAllowedCanonicalAttachmentPath({
2065
2328
  canonicalPath: opened.realPath,
2066
2329
  allowedRoots: params.allowedRoots
@@ -2440,7 +2703,7 @@ function sanitizeIMessageWatchErrorPayload(payload) {
2440
2703
  if (typeof payload.code === "number" && Number.isFinite(payload.code)) safe.code = payload.code;
2441
2704
  if (typeof payload.message === "string") {
2442
2705
  const sanitizedMessage = sanitizeTerminalText(payload.message);
2443
- if (sanitizedMessage) safe.message = sanitizedMessage.length > MAX_WATCH_ERROR_MESSAGE_CHARS ? `${truncateUtf16Safe(sanitizedMessage, MAX_WATCH_ERROR_MESSAGE_CHARS - 1)}…` : sanitizedMessage;
2706
+ if (sanitizedMessage) safe.message = sanitizedMessage.length > MAX_WATCH_ERROR_MESSAGE_CHARS ? `${truncateUtf16Safe(sanitizedMessage, 199)}…` : sanitizedMessage;
2444
2707
  }
2445
2708
  return safe;
2446
2709
  }
@@ -2452,7 +2715,7 @@ const CHANNEL_APPROVAL_GATEWAY_RUNTIME_CONTEXT_CAPABILITY = "approval.gateway";
2452
2715
  const APPROVAL_REACTION_POLL_INTERVAL_MS = 2e3;
2453
2716
  const APPROVAL_REACTION_DISCOVERY_INTERVAL_MS = 6e4;
2454
2717
  const IMESSAGE_TYPING_KEEPALIVE_INTERVAL_MS = 8e3;
2455
- const IMESSAGE_TYPING_KEEPALIVE_MAX_DURATION_MS = 10 * 6e4;
2718
+ const IMESSAGE_TYPING_KEEPALIVE_MAX_DURATION_MS = 6e5;
2456
2719
  function resolveConfiguredIMessageTypingMode(cfg, agentId) {
2457
2720
  return resolveAgentConfig(cfg, agentId)?.typingMode ?? cfg.agents?.defaults?.typingMode;
2458
2721
  }
@@ -2533,17 +2796,16 @@ const IMESSAGE_DIAGNOSTIC_DROP_REASONS = /* @__PURE__ */ new Set([
2533
2796
  "agent echo in self-chat",
2534
2797
  "echo",
2535
2798
  "from me",
2799
+ "no mention",
2536
2800
  "reflected assistant content",
2537
2801
  "self-chat echo"
2538
2802
  ]);
2539
- const IMESSAGE_THROTTLED_DIAGNOSTIC_DROP_REASONS = /* @__PURE__ */ new Set(["from me"]);
2540
- function shouldThrottleIMessageInboundDropDiagnostic(reason) {
2541
- return IMESSAGE_THROTTLED_DIAGNOSTIC_DROP_REASONS.has(reason);
2542
- }
2803
+ const IMESSAGE_THROTTLED_DIAGNOSTIC_DROP_REASONS = /* @__PURE__ */ new Set(["from me", "no mention"]);
2543
2804
  function describeIMessageInboundDropDiagnostic(params) {
2544
2805
  if (!IMESSAGE_DIAGNOSTIC_DROP_REASONS.has(params.reason)) return null;
2545
2806
  const messageId = typeof params.message.id === "number" || typeof params.message.id === "string" ? String(params.message.id) : "unknown";
2546
- return `imessage: dropped inbound message account=${params.accountId} reason=${JSON.stringify(params.reason)} chat_id=${params.message.chat_id ?? "unknown"} group=${params.message.is_group === true} message_id=${messageId} guid=${params.message.guid ? "present" : "missing"} created_at=${params.message.created_at ?? "unknown"}`;
2807
+ const mentionHint = params.reason === "no mention" ? ` Mention the agent (default patterns come from its identity name/emoji), or set ${params.groupsConfigPath}["${params.message.chat_id}"].requireMention=false. Preserve existing groups entries; when adding the first groups map, include "*": {} to keep other chats admitted.` : "";
2808
+ return `imessage: dropped inbound message account=${params.accountId} reason=${JSON.stringify(params.reason)} chat_id=${params.message.chat_id ?? "unknown"} group=${params.message.is_group === true} message_id=${messageId} guid=${params.message.guid ? "present" : "missing"} created_at=${params.message.created_at ?? "unknown"}${mentionHint}`;
2547
2809
  }
2548
2810
  function describeIMessageWatchSubscribeStartupFailure(params) {
2549
2811
  const retry = params.retryDelayMs !== void 0 ? ` retry_in_ms=${params.retryDelayMs}` : "";
@@ -2567,10 +2829,17 @@ async function waitForWatchSubscribeRetryDelay(params) {
2567
2829
  async function monitorIMessageProvider(opts = {}) {
2568
2830
  const runtime = resolveRuntime(opts);
2569
2831
  const cfg = opts.config ?? getRuntimeConfig();
2832
+ const readConfig = createRuntimeConfigReader(cfg);
2570
2833
  const accountInfo = resolveIMessageAccount({
2571
2834
  cfg,
2572
2835
  accountId: opts.accountId
2573
2836
  });
2837
+ const groupsConfigPath = resolveChannelGroupsConfigPath({
2838
+ cfg,
2839
+ channel: "imessage",
2840
+ accountId: accountInfo.accountId,
2841
+ groups: resolveChannelGroups(cfg, "imessage", accountInfo.accountId)
2842
+ });
2574
2843
  const approvalGatewayRuntime = opts.channelRuntime?.runtimeContexts.get({
2575
2844
  channelId: "imessage",
2576
2845
  accountId: accountInfo.accountId,
@@ -2675,6 +2944,10 @@ async function monitorIMessageProvider(opts = {}) {
2675
2944
  const { debouncer: inboundDebouncer } = createChannelInboundDebouncer({
2676
2945
  cfg,
2677
2946
  channel: "imessage",
2947
+ resolveDebounceMs: () => resolveInboundDebounceMs({
2948
+ cfg: readConfig(),
2949
+ channel: "imessage"
2950
+ }),
2678
2951
  buildKey: (entry) => {
2679
2952
  const msg = entry.message;
2680
2953
  const sender = msg.sender?.trim();
@@ -2775,9 +3048,6 @@ async function monitorIMessageProvider(opts = {}) {
2775
3048
  runtime.error?.(`imessage catchup: failed to advance pending live cursor: ${String(err)}`);
2776
3049
  }
2777
3050
  }
2778
- async function handleMessageNow(message, ingressLifecycle) {
2779
- await handleMessageNowInner(message, ingressLifecycle);
2780
- }
2781
3051
  const pollCommentFolder = createPollCommentFolder();
2782
3052
  function resolveIMessageInboundBodyText(message) {
2783
3053
  const messageText = ((message.poll ? renderIMessagePollBody(message.poll, message.sender, { preferOptionId: Boolean(remoteHost) }) : null) ?? message.text ?? "").trim();
@@ -2794,7 +3064,7 @@ async function monitorIMessageProvider(opts = {}) {
2794
3064
  effectiveAttachmentRoots
2795
3065
  };
2796
3066
  }
2797
- async function handleMessageNowInner(rawMessage, ingressLifecycle) {
3067
+ async function handleMessageNow(rawMessage, ingressLifecycle) {
2798
3068
  const message = await repairMessageConversationAnchor(rawMessage);
2799
3069
  if (!message) return;
2800
3070
  const pollFoldAtMs = message.created_at ? Date.parse(message.created_at) : NaN;
@@ -2836,15 +3106,16 @@ async function monitorIMessageProvider(opts = {}) {
2836
3106
  const conversationKey = chatId != null ? `group:${chatId}` : `dm:${senderForKey}`;
2837
3107
  const rateLimitKey = `${accountInfo.accountId}:${conversationKey}`;
2838
3108
  if (decision.kind === "drop") {
2839
- if (decision.reason === "echo" || decision.reason === "self-chat echo" || decision.reason === "reflected assistant content") loopRateLimiter.record(rateLimitKey);
3109
+ if (decision.reason === "echo" || decision.reason === "reflected assistant content") loopRateLimiter.record(rateLimitKey);
2840
3110
  const diagnostic = describeIMessageInboundDropDiagnostic({
2841
3111
  accountId: accountInfo.accountId,
3112
+ groupsConfigPath,
2842
3113
  reason: decision.reason,
2843
3114
  message
2844
3115
  });
2845
3116
  if (diagnostic) {
2846
3117
  const throttleKey = `${rateLimitKey}:${decision.reason}`;
2847
- if (!shouldThrottleIMessageInboundDropDiagnostic(decision.reason) || !loggedThrottledDropDiagnostics.check(throttleKey)) runtime.log?.(warn(diagnostic));
3118
+ if (!IMESSAGE_THROTTLED_DIAGNOSTIC_DROP_REASONS.has(decision.reason) || !loggedThrottledDropDiagnostics.check(throttleKey)) runtime.log?.(warn(diagnostic));
2848
3119
  }
2849
3120
  if (decision.reason === "group id not in allowlist") warnGroupAllowlistDropPerChatOnce({
2850
3121
  accountId: accountInfo.accountId,
@@ -3273,7 +3544,7 @@ async function monitorIMessageProvider(opts = {}) {
3273
3544
  const repairedMessage = await repairMessageConversationAnchor(message);
3274
3545
  if (!repairedMessage) return { kind: "completed" };
3275
3546
  if (isApprovalCommand) {
3276
- await handleMessageNowInner(repairedMessage);
3547
+ await handleMessageNow(repairedMessage);
3277
3548
  return { kind: "completed" };
3278
3549
  }
3279
3550
  const conversation = resolveApprovalControlConversation(repairedMessage);
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { t as resolveIMessageReactionContext } from "./reaction-context-BAYI7pz0.js";
2
+ import { n as normalizeIMessageGuid, t as resolveIMessageReactionContext } from "./reaction-context-Br-vAJFp.js";
3
3
  import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
4
4
  //#region extensions/imessage/src/question-reactions.ts
5
5
  var question_reactions_exports = /* @__PURE__ */ __exportAll({
@@ -7,12 +7,9 @@ var question_reactions_exports = /* @__PURE__ */ __exportAll({
7
7
  maybeResolveIMessageQuestionReaction: () => maybeResolveIMessageQuestionReaction,
8
8
  registerIMessageQuestionReactionTargetForDeliveredPayload: () => registerIMessageQuestionReactionTargetForDeliveredPayload
9
9
  });
10
- function normalizeGuid(value) {
11
- return value.trim().replace(/^p:\d+\//iu, "");
12
- }
13
10
  function buildKey(identity) {
14
11
  const account = identity.accountId.trim();
15
- const guid = normalizeGuid(identity.messageGuid);
12
+ const guid = normalizeIMessageGuid(identity.messageGuid);
16
13
  return account && guid ? `${account}:${guid}` : null;
17
14
  }
18
15
  const questionReactionTargets = createQuestionReactionTargetStore({
@@ -25,7 +22,7 @@ const questionReactionTargets = createQuestionReactionTargetStore({
25
22
  function reactionCandidates(message, bodyText) {
26
23
  const reaction = resolveIMessageReactionContext(message, bodyText);
27
24
  if (!reaction) return null;
28
- const guids = Array.from(new Set([...reaction.targetGuids ?? [], reaction.targetGuid ?? ""].map(normalizeGuid).filter(Boolean)));
25
+ const guids = Array.from(new Set([...reaction.targetGuids ?? [], reaction.targetGuid ?? ""].map(normalizeIMessageGuid).filter(Boolean)));
29
26
  return guids.length > 0 ? {
30
27
  action: reaction.action,
31
28
  emoji: reaction.emoji,
@@ -39,7 +36,7 @@ function registerIMessageQuestionReactionTargetForDeliveredPayload(params) {
39
36
  for (const result of params.results) {
40
37
  if (result.channel !== "imessage") continue;
41
38
  const guid = typeof result.meta?.imessageMessageGuid === "string" ? result.meta.imessageMessageGuid : result.messageId;
42
- if (/^\d+$/u.test(normalizeGuid(guid))) continue;
39
+ if (/^\d+$/u.test(normalizeIMessageGuid(guid))) continue;
43
40
  registered = questionReactionTargets.register(binding, {
44
41
  accountId: params.accountId,
45
42
  messageGuid: guid
@@ -1,3 +1,9 @@
1
+ //#region extensions/imessage/src/message-guid.ts
2
+ /** Strip the `p:<n>/` part prefix Messages puts on some GUIDs so keys match. */
3
+ function normalizeIMessageGuid(value) {
4
+ return value.trim().replace(/^p:\d+\//iu, "");
5
+ }
6
+ //#endregion
1
7
  //#region extensions/imessage/src/monitor/reaction-context.ts
2
8
  const TAPBACK_TEXT_PATTERNS = [
3
9
  {
@@ -62,7 +68,7 @@ const TAPBACK_TEXT_PATTERNS = [
62
68
  }
63
69
  ];
64
70
  function normalizeReactionValue(value) {
65
- return typeof value === "string" ? value.trim().replace(/^p:\d+\//iu, "") || void 0 : void 0;
71
+ return typeof value === "string" ? normalizeIMessageGuid(value) || void 0 : void 0;
66
72
  }
67
73
  function resolveReactionTargetGuidCandidates(...values) {
68
74
  const candidates = [];
@@ -70,7 +76,7 @@ function resolveReactionTargetGuidCandidates(...values) {
70
76
  if (typeof value !== "string") continue;
71
77
  const raw = value.trim();
72
78
  if (!raw) continue;
73
- const normalized = raw.replace(/^p:\d+\//iu, "");
79
+ const normalized = normalizeIMessageGuid(raw);
74
80
  for (const candidate of [normalized, raw]) if (candidate && !candidates.includes(candidate)) candidates.push(candidate);
75
81
  }
76
82
  return candidates;
@@ -102,4 +108,4 @@ function resolveIMessageReactionContext(message, bodyText) {
102
108
  return resolveTapbackTextContext(bodyText);
103
109
  }
104
110
  //#endregion
105
- export { resolveIMessageReactionContext as t };
111
+ export { normalizeIMessageGuid as n, resolveIMessageReactionContext as t };