@openclaw/imessage 2026.10.2 → 2026.10.5-beta.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 (30) hide show
  1. package/dist/.setup/{accounts-evTpvJMv.mjs → accounts-DacCvDBt.mjs} +23 -45
  2. package/dist/.setup/{actions-DJqbUmbN.mjs → actions-Hxh12myO.mjs} +54 -101
  3. package/dist/.setup/{actions.runtime-DL5i-Y4J.mjs → actions.runtime-DOIX6Fkf.mjs} +29 -37
  4. package/dist/.setup/{api-oq_MryN7.mjs → api-BV8qHfLl.mjs} +7 -7
  5. package/dist/.setup/{api-SAVMfMoY.mjs → api-DC1yHVGj.mjs} +8 -8
  6. package/dist/.setup/{approval-handler.runtime-Bj2NGNXF.mjs → approval-handler.runtime-Dc3KzakG.mjs} +38 -66
  7. package/dist/.setup/{approval-reactions-L9Ul7PAu.mjs → approval-reactions-JvzJCJ4a.mjs} +26 -33
  8. package/dist/.setup/{channel-CL73qdMG.mjs → channel-9TWBUDkP.mjs} +14 -16
  9. package/dist/.setup/{channel.runtime-DY7JJ0_9.mjs → channel.runtime-DrLDEY4R.mjs} +198 -362
  10. package/dist/.setup/{client-12apu4Ip.mjs → client-HixFQQWY.mjs} +34 -38
  11. package/dist/.setup/{media-contract-DjQGZlfl.mjs → media-contract-BwWs5PM7.mjs} +1 -1
  12. package/dist/.setup/{message-tool-api-CqfRxLlT.mjs → message-tool-api-B5NfPnFL.mjs} +2 -2
  13. package/dist/.setup/{question-reactions-DJV-TnA8.mjs → question-reactions-9jrqVZgE.mjs} +20 -25
  14. package/dist/.setup/{reaction-context-By7kUMBC.mjs → reaction-context-CRXpeYAr.mjs} +1 -1
  15. package/dist/.setup/{remote-file-BU7pA-Zi.mjs → remote-file-Dq5Asi_F.mjs} +10 -16
  16. package/dist/.setup/{sanitize-outbound-C0x4BPky.mjs → sanitize-outbound-DpdQERYJ.mjs} +27 -62
  17. package/dist/.setup/{send-pkYwL6Rt.mjs → send-BZZzMbdi.mjs} +186 -251
  18. package/dist/.setup/{setup-core-DqOBe-n0.mjs → setup-core-CaRGbZOL.mjs} +15 -46
  19. package/dist/.setup/{setup-surface-zKhIinwY.mjs → setup-surface-BMWS7VuY.mjs} +2 -2
  20. package/dist/.setup/{targets-BicBiYsM.mjs → targets-B4oU7n-v.mjs} +47 -69
  21. package/dist/api.js +7 -7
  22. package/dist/channel-plugin-api.js +1 -1
  23. package/dist/contract-api.js +1 -1
  24. package/dist/doctor-contract-api.js +21 -45
  25. package/dist/media-contract-api.js +1 -1
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/runtime-api.js +1 -1
  28. package/dist/setup-entry.js +1 -1
  29. package/dist/src/chat-db.worker.js +1 -1
  30. package/package.json +4 -4
@@ -1,22 +1,22 @@
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, r as listEnabledIMessageAccounts, s as resolveIMessageDuplicateSourceOwner } from "./accounts-evTpvJMv.mjs";
3
- import { r as imessageRpcSupportsMethod } from "./message-tool-api-CqfRxLlT.mjs";
4
- import { C as resolveIMessageDirectChatService, c as rememberIMessageReplyCache, d as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, g as resolveIMessageEchoMediaKey, h as resolveIMessageCatchupCursorKey, m as capFailureRetriesMap, n as createIMessageRpcClient, s as isKnownFromMeIMessageTarget } from "./client-12apu4Ip.mjs";
5
- import { a as normalizeIMessageMessageId, i as normalizeIMessageGuid, n as probeIMessage, r as probeIMessagePrivateApi } from "./actions-DJqbUmbN.mjs";
1
+ import { c as parseIMessageTarget, o as normalizeIMessageHandle, r as isAllowedIMessageReplyContextSender, s as parseIMessageAllowTarget, t as formatIMessageChatTarget } from "./targets-B4oU7n-v.mjs";
2
+ import { d as resolveIMessageChatDbLookupPath, f as resolveIMessageHomeDir, l as resolveIMessageRemoteHost, o as resolveIMessageAccount, r as listEnabledIMessageAccounts, s as resolveIMessageDuplicateSourceOwner } from "./accounts-DacCvDBt.mjs";
3
+ import { r as imessageRpcSupportsMethod } from "./message-tool-api-B5NfPnFL.mjs";
4
+ import { C as resolveIMessageDirectChatService, c as rememberIMessageReplyCache, d as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, g as resolveIMessageEchoMediaKey, h as resolveIMessageCatchupCursorKey, m as capFailureRetriesMap, n as createIMessageRpcClient, s as isKnownFromMeIMessageTarget } from "./client-HixFQQWY.mjs";
5
+ import { a as normalizeIMessageMessageId, i as normalizeIMessageGuid, n as probeIMessage, r as probeIMessagePrivateApi } from "./actions-Hxh12myO.mjs";
6
6
  import { t as getIMessageRuntime } from "./runtime-Cza4CY5T.mjs";
7
- import { a as resolveIMessageGroupSystemPrompt, s as resolveIMessageInboundConversationId } from "./channel-CL73qdMG.mjs";
8
- import { r as sanitizeOutboundText } from "./sanitize-outbound-C0x4BPky.mjs";
9
- import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-DjQGZlfl.mjs";
7
+ import { a as resolveIMessageGroupSystemPrompt, s as resolveIMessageInboundConversationId } from "./channel-9TWBUDkP.mjs";
8
+ import { r as sanitizeOutboundText } from "./sanitize-outbound-DpdQERYJ.mjs";
9
+ import { n as resolveIMessageAttachmentRoots, r as resolveIMessageRemoteAttachmentRoots } from "./media-contract-BwWs5PM7.mjs";
10
10
  import { t as IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps-B-QEsLSM.mjs";
11
- import { i as resolveIMessageStartupRowidWatermark } from "./remote-file-BU7pA-Zi.mjs";
12
- import { i as registerIMessageApprovalReactionTarget, n as handleIMessageApprovalReaction, o as listPendingIMessageApprovalReactionPollTargets, r as maybeResolveIMessageApprovalReaction, s as buildIMessageApprovalConversationKeyForInbound } from "./approval-reactions-L9Ul7PAu.mjs";
13
- import { c as iMessageApprovalControlBindings, n as hasPersistedIMessageEcho, r as normalizeIMessageEchoText, s as maybeResolveIMessageApprovalPollVote, t as sendMessageIMessage } from "./send-pkYwL6Rt.mjs";
14
- import { t as resolveIMessageReactionContext } from "./reaction-context-By7kUMBC.mjs";
15
- import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-DJV-TnA8.mjs";
16
- import { imessageSetupWizard } from "./setup-surface-zKhIinwY.mjs";
17
- import { asSafeIntegerInRange, hasNonEmptyString, isRecord, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
11
+ import { i as resolveIMessageStartupRowidWatermark } from "./remote-file-Dq5Asi_F.mjs";
12
+ import { f as resolveIMessageApprovalControlActor, i as registerIMessageApprovalReactionTarget, n as handleIMessageApprovalReaction, o as listPendingIMessageApprovalReactionPollTargets, r as maybeResolveIMessageApprovalReaction, s as buildIMessageApprovalConversationKeyForInbound } from "./approval-reactions-JvzJCJ4a.mjs";
13
+ import { c as iMessageApprovalControlBindings, n as hasPersistedIMessageEcho, r as normalizeIMessageEchoText, s as maybeResolveIMessageApprovalPollVote, t as sendMessageIMessage } from "./send-BZZzMbdi.mjs";
14
+ import { t as resolveIMessageReactionContext } from "./reaction-context-CRXpeYAr.mjs";
15
+ import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-9jrqVZgE.mjs";
16
+ import { imessageSetupWizard } from "./setup-surface-BMWS7VuY.mjs";
17
+ import { asSafeIntegerInRange, hasNonEmptyString, isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
18
18
  import { normalizeChannelDmPolicy } from "openclaw/plugin-sdk/channel-config-helpers";
19
- import { bindIngressLifecycleToReplyOptions, createAccountStatusSink, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled, resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
19
+ import { bindIngressLifecycleToReplyOptions, createAccountStatusSink, createChannelDeliveryAccumulator, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, resolveChannelStreamingBlockEnabled, resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
20
20
  import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
21
21
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
22
22
  import path from "node:path";
@@ -32,7 +32,7 @@ import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-util
32
32
  import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
33
33
  import { resolveChannelGroupPolicy, resolveChannelGroups, resolveChannelGroupsConfigPath, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
34
34
  import { convertMarkdownTables, findCodeRegions, isInsideCode, sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
35
- 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";
35
+ import { buildChannelInboundEventContext, buildMentionRegexes, createChannelInboundDebouncer, filterChannelInboundQuoteContext, formatInboundEnvelope, formatInboundFromLabel, formatInboundMediaUnavailableText, formatMediaPlaceholderText, logInboundDrop, matchesMentionPatterns, resolveEnvelopeFormatOptions, resolveInboundDebounceMs, resolveInboundMentionDecision, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent, shouldDebounceTextInbound, toInboundMediaFactsWithMetadata } from "openclaw/plugin-sdk/channel-inbound";
36
36
  import { FsSafeError, openLocalFileSafely, resolvePinnedMainDmOwnerFromAllowlist } from "openclaw/plugin-sdk/security-runtime";
37
37
  import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
38
38
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-runtime";
@@ -47,7 +47,7 @@ import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, upsertCha
47
47
  import { channelReadyPatch } from "openclaw/plugin-sdk/gateway-runtime";
48
48
  import { redactIdentifier } from "openclaw/plugin-sdk/logging-core";
49
49
  import { resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
50
- import { getSessionEntry, readSessionUpdatedAt, resolveSendPolicy, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
50
+ import { getSessionEntry, readSessionUpdatedAtAsync, resolveSendPolicy, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
51
51
  import { waitForTransportReady } from "openclaw/plugin-sdk/transport-ready-runtime";
52
52
  import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
53
53
  import { chunkMarkdownTextWithMode, resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
@@ -201,7 +201,7 @@ async function pollPendingIMessageApprovalReactions(params) {
201
201
  }
202
202
  //#endregion
203
203
  //#region extensions/imessage/src/chat.ts
204
- function buildChatTargetParams(to, opts) {
204
+ async function runChatAction(method, to, opts, isTyping) {
205
205
  const cfg = requireRuntimeConfig(opts.cfg, "iMessage chat action");
206
206
  const account = opts.account ?? resolveIMessageAccount({
207
207
  cfg,
@@ -213,17 +213,11 @@ function buildChatTargetParams(to, opts) {
213
213
  else if (target.kind === "chat_guid") params.chat_guid = target.chatGuid;
214
214
  else if (target.kind === "chat_identifier") params.chat_identifier = target.chatIdentifier;
215
215
  else params.to = target.to;
216
- return {
217
- params,
218
- service: opts.service ?? (target.kind === "handle" ? target.service : void 0) ?? account.config.service
219
- };
220
- }
221
- async function runChatAction(method, params, opts) {
222
- const cfg = requireRuntimeConfig(opts.cfg, "iMessage chat action");
223
- const account = opts.account ?? resolveIMessageAccount({
224
- cfg,
225
- accountId: opts.accountId
226
- });
216
+ if (method === "typing") {
217
+ params.typing = isTyping;
218
+ const service = opts.service ?? (target.kind === "handle" ? target.service : void 0) ?? account.config.service;
219
+ if (service) params.service = service;
220
+ }
227
221
  const cliPath = opts.cliPath?.trim() || account.config.cliPath?.trim() || "imsg";
228
222
  const dbPath = opts.dbPath?.trim() || account.config.dbPath?.trim();
229
223
  const remoteHost = await resolveIMessageRemoteHost({
@@ -237,20 +231,16 @@ async function runChatAction(method, params, opts) {
237
231
  });
238
232
  const shouldClose = !opts.client;
239
233
  try {
240
- return await client.request(method, params, { timeoutMs: opts.timeoutMs });
234
+ await client.request(method, params, { timeoutMs: opts.timeoutMs });
241
235
  } finally {
242
236
  if (shouldClose) await client.stop();
243
237
  }
244
238
  }
245
239
  async function sendIMessageTyping(to, isTyping, opts) {
246
- const { params, service } = buildChatTargetParams(to, opts);
247
- params.typing = isTyping;
248
- if (service) params.service = service;
249
- await runChatAction("typing", params, opts);
240
+ await runChatAction("typing", to, opts, isTyping);
250
241
  }
251
242
  async function markIMessageChatRead(to, opts) {
252
- const { params } = buildChatTargetParams(to, opts);
253
- await runChatAction("read", params, opts);
243
+ await runChatAction("read", to, opts);
254
244
  }
255
245
  //#endregion
256
246
  //#region extensions/imessage/src/monitor/abort-handler.ts
@@ -296,14 +286,11 @@ function normalizeIMessageCatchupCursor(value) {
296
286
  const raw = value;
297
287
  if (typeof raw.lastSeenMs !== "number" || !Number.isFinite(raw.lastSeenMs)) return null;
298
288
  if (typeof raw.lastSeenRowid !== "number" || !Number.isFinite(raw.lastSeenRowid)) return null;
299
- const failureRetries = sanitizeFailureRetriesInput(raw.failureRetries);
300
- const hasRetries = Object.keys(failureRetries).length > 0;
301
- return {
289
+ return buildIMessageCatchupCursor({
302
290
  lastSeenMs: raw.lastSeenMs,
303
291
  lastSeenRowid: raw.lastSeenRowid,
304
- updatedAt: typeof raw.updatedAt === "number" ? raw.updatedAt : 0,
305
- ...hasRetries ? { failureRetries } : {}
306
- };
292
+ failureRetries: raw.failureRetries
293
+ }, typeof raw.updatedAt === "number" ? raw.updatedAt : 0);
307
294
  }
308
295
  async function loadIMessageCatchupCursor(accountId) {
309
296
  return normalizeIMessageCatchupCursor(await openCatchupCursorStore().lookup(resolveIMessageCatchupCursorKey(accountId)));
@@ -344,11 +331,6 @@ function decideCatchupCursorSave(existingValue, cursor, allowCursorRewindForRetr
344
331
  value: cursor
345
332
  };
346
333
  }
347
- async function saveIMessageCatchupCursor(accountId, next, options = {}) {
348
- const cursor = buildIMessageCatchupCursor(next, Date.now());
349
- const allowCursorRewindForRetries = options.allowCursorRewindForRetries === true;
350
- await updateIMessageCatchupCursor(accountId, (existing) => decideCatchupCursorSave(existing, cursor, allowCursorRewindForRetries));
351
- }
352
334
  async function updateIMessageCatchupCursor(accountId, decide) {
353
335
  const store = openCatchupCursorStore();
354
336
  if (!store.observe || !store.compareAndApply) throw new Error("iMessage catchup cursor persistence requires plugin-state comparison support.");
@@ -464,10 +446,10 @@ async function performIMessageCatchup(params) {
464
446
  summary.skippedPreCursor += 1;
465
447
  continue;
466
448
  }
449
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
450
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
467
451
  if (row.date < ageBoundMs) {
468
452
  summary.skippedPreCursor += 1;
469
- highWatermarkMs = Math.max(highWatermarkMs, row.date);
470
- highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
471
453
  continue;
472
454
  }
473
455
  if (row.isFromMe) {
@@ -477,15 +459,11 @@ async function performIMessageCatchup(params) {
477
459
  params.warn?.(`imessage catchup: from-me observer failed for guid=${row.guid}: ${String(err)}`);
478
460
  }
479
461
  summary.skippedFromMe += 1;
480
- highWatermarkMs = Math.max(highWatermarkMs, row.date);
481
- highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
482
462
  continue;
483
463
  }
484
464
  const priorCount = failureRetries[row.guid] ?? 0;
485
465
  if (priorCount >= cfg.maxFailureRetries) {
486
466
  summary.skippedGivenUp += 1;
487
- highWatermarkMs = Math.max(highWatermarkMs, row.date);
488
- highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
489
467
  continue;
490
468
  }
491
469
  let dispatched;
@@ -498,8 +476,6 @@ async function performIMessageCatchup(params) {
498
476
  if (dispatched.ok) {
499
477
  summary.replayed += 1;
500
478
  delete failureRetries[row.guid];
501
- highWatermarkMs = Math.max(highWatermarkMs, row.date);
502
- highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
503
479
  continue;
504
480
  }
505
481
  const nextCount = priorCount + 1;
@@ -508,8 +484,6 @@ async function performIMessageCatchup(params) {
508
484
  if (nextCount >= cfg.maxFailureRetries) {
509
485
  summary.givenUp += 1;
510
486
  params.warn?.(`imessage catchup: giving up on guid=${row.guid} after ${nextCount} failures; advancing cursor past it`);
511
- highWatermarkMs = Math.max(highWatermarkMs, row.date);
512
- highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
513
487
  continue;
514
488
  }
515
489
  if (earliestHeldFailureRow === null || row.rowid < earliestHeldFailureRow.rowid) earliestHeldFailureRow = row;
@@ -532,11 +506,12 @@ async function performIMessageCatchup(params) {
532
506
  lastSeenMs,
533
507
  lastSeenRowid
534
508
  };
535
- await saveIMessageCatchupCursor(params.accountId, {
509
+ const next = buildIMessageCatchupCursor({
536
510
  lastSeenMs,
537
511
  lastSeenRowid,
538
512
  failureRetries: capped
539
- }, { allowCursorRewindForRetries: earliestHeldFailureRow !== null });
513
+ }, Date.now());
514
+ await updateIMessageCatchupCursor(params.accountId, (existing) => decideCatchupCursorSave(existing, next, earliestHeldFailureRow !== null));
540
515
  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}`);
541
516
  return summary;
542
517
  }
@@ -853,7 +828,7 @@ async function repairIMessageConversationAnchor(params) {
853
828
  }
854
829
  let chatsResult;
855
830
  try {
856
- chatsResult = await client.request("chats.list", { limit: params.chatsLimit ?? DEFAULT_CHATS_LIMIT }, { timeoutMs: params.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS });
831
+ chatsResult = await client.request("chats.list", { limit: DEFAULT_CHATS_LIMIT }, { timeoutMs: DEFAULT_RPC_TIMEOUT_MS });
857
832
  } catch (err) {
858
833
  runtime?.error?.(`imessage: anchorless message recovery failed listing chats: ${String(err)}`);
859
834
  return null;
@@ -868,8 +843,8 @@ async function repairIMessageConversationAnchor(params) {
868
843
  historyResult = await client.request("messages.history", {
869
844
  attachments: false,
870
845
  chat_id: chatId,
871
- limit: params.perChatHistoryLimit ?? DEFAULT_PER_CHAT_HISTORY_LIMIT
872
- }, { timeoutMs: params.rpcTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS });
846
+ limit: DEFAULT_PER_CHAT_HISTORY_LIMIT
847
+ }, { timeoutMs: DEFAULT_RPC_TIMEOUT_MS });
873
848
  } catch {
874
849
  continue;
875
850
  }
@@ -921,7 +896,7 @@ async function deliverIMessageReply(params) {
921
896
  const chunkMode = resolveChunkMode(cfg, "imessage", accountId);
922
897
  const rawText = sanitizeOutboundText(payload.text ?? "");
923
898
  const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables(rawText, tableMode) });
924
- const accepted = [];
899
+ const accepted = createChannelDeliveryAccumulator({ kind: reply.mediaUrls.length > 0 ? "media" : "text" });
925
900
  const sendAccepted = async (text, mediaUrl) => {
926
901
  const sent = await sendMessageIMessage(target, text, {
927
902
  config: cfg,
@@ -933,7 +908,7 @@ async function deliverIMessageReply(params) {
933
908
  accountId,
934
909
  replyToId: payload.replyToId
935
910
  });
936
- accepted.push(sent);
911
+ accepted.add({ receipt: sent.receipt }, sent.sentText);
937
912
  const echoText = sent.echoText ?? (sent.sentText || void 0);
938
913
  sentMessageCache?.remember(scope, {
939
914
  ...echoText ? { text: echoText } : {},
@@ -951,25 +926,10 @@ async function deliverIMessageReply(params) {
951
926
  sendMedia: ({ mediaUrl, caption }) => sendAccepted(caption ?? "", mediaUrl)
952
927
  });
953
928
  } catch (error) {
954
- const partial = isChannelPartialDeliveryError(error) ? error.deliveryResult : void 0;
955
- if (accepted.length === 0 && partial?.visibleReplySent !== true) throw error;
956
- throw createChannelPartialDeliveryError(error, createAcceptedChannelDeliveryResult({
957
- results: accepted.map((result) => ({ receipt: result.receipt })),
958
- deliveryResults: partial ? [partial] : [],
959
- kind: reply.mediaUrls.length > 0 ? "media" : "text",
960
- content: [...accepted.map((result) => result.sentText), partial?.content].filter(Boolean).join("\n")
961
- }));
962
- }
963
- if (delivered === "empty") return {
964
- visibleReplySent: false,
965
- suppression: { reason: "no_visible_result" }
966
- };
967
- const deliveryResult = createAcceptedChannelDeliveryResult({
968
- results: accepted.map((result) => ({ receipt: result.receipt })),
969
- kind: delivered,
970
- content: accepted.map((result) => result.sentText).filter(Boolean).join("\n")
971
- });
972
- runtime.log?.(`imessage: delivered reply to ${target}`);
929
+ throw accepted.partialError(error);
930
+ }
931
+ const deliveryResult = accepted.result();
932
+ if (delivered !== "empty") runtime.log?.(`imessage: delivered reply to ${target}`);
973
933
  return deliveryResult;
974
934
  }
975
935
  function createIMessageEchoCachingSend(params) {
@@ -1070,29 +1030,27 @@ function createIMessageThrottledDropDiagnosticCache() {
1070
1030
  //#region extensions/imessage/src/monitor/echo-cache.ts
1071
1031
  const SENT_MESSAGE_TEXT_TTL_MS = 4e3;
1072
1032
  const SENT_MESSAGE_ID_TTL_MS = 6e4;
1073
- var DefaultSentMessageCache = class {
1074
- constructor() {
1075
- this.textCache = /* @__PURE__ */ new Map();
1076
- this.textBackedByIdCache = /* @__PURE__ */ new Map();
1077
- this.mediaCache = /* @__PURE__ */ new Map();
1078
- this.mediaBackedByIdCache = /* @__PURE__ */ new Map();
1079
- this.messageIdCache = /* @__PURE__ */ new Map();
1080
- }
1081
- remember(scope, lookup) {
1033
+ function createSentMessageCache() {
1034
+ const textCache = /* @__PURE__ */ new Map();
1035
+ const textBackedByIdCache = /* @__PURE__ */ new Map();
1036
+ const mediaCache = /* @__PURE__ */ new Map();
1037
+ const mediaBackedByIdCache = /* @__PURE__ */ new Map();
1038
+ const messageIdCache = /* @__PURE__ */ new Map();
1039
+ function remember(scope, lookup) {
1082
1040
  const textKey = normalizeIMessageEchoText(lookup.text);
1083
- if (textKey) this.textCache.set(`${scope}:${textKey}`, Date.now());
1041
+ if (textKey) textCache.set(`${scope}:${textKey}`, Date.now());
1084
1042
  const mediaKey = resolveIMessageEchoMediaKey(lookup.media);
1085
- if (mediaKey) this.mediaCache.set(`${scope}:${mediaKey}`, Date.now());
1043
+ if (mediaKey) mediaCache.set(`${scope}:${mediaKey}`, Date.now());
1086
1044
  const messageIdKey = normalizeIMessageMessageId(lookup.messageId);
1087
1045
  if (messageIdKey) {
1088
- this.messageIdCache.set(`${scope}:${messageIdKey}`, Date.now());
1089
- if (textKey) this.textBackedByIdCache.set(`${scope}:${textKey}`, Date.now());
1090
- if (mediaKey) this.mediaBackedByIdCache.set(`${scope}:${mediaKey}`, Date.now());
1046
+ messageIdCache.set(`${scope}:${messageIdKey}`, Date.now());
1047
+ if (textKey) textBackedByIdCache.set(`${scope}:${textKey}`, Date.now());
1048
+ if (mediaKey) mediaBackedByIdCache.set(`${scope}:${mediaKey}`, Date.now());
1091
1049
  }
1092
- this.cleanup();
1050
+ cleanup();
1093
1051
  }
1094
- async has(scope, lookup, options = false) {
1095
- this.cleanup();
1052
+ async function has(scope, lookup, options = false) {
1053
+ cleanup();
1096
1054
  const resolvedOptions = typeof options === "boolean" ? { skipIdShortCircuit: options } : options;
1097
1055
  if (await hasPersistedIMessageEcho({
1098
1056
  scope,
@@ -1102,48 +1060,45 @@ var DefaultSentMessageCache = class {
1102
1060
  skipIdShortCircuit: resolvedOptions.skipIdShortCircuit,
1103
1061
  includePendingText: resolvedOptions.includePendingText
1104
1062
  })) return true;
1063
+ const matchesWithin = (cache, key, ttlMs) => {
1064
+ const timestamp = key ? cache.get(`${scope}:${key}`) : void 0;
1065
+ return Boolean(timestamp && Date.now() - timestamp <= ttlMs);
1066
+ };
1067
+ const hasContentOnlyMatch = (cache, backedById, key) => {
1068
+ const timestamp = key ? cache.get(`${scope}:${key}`) : void 0;
1069
+ const idTimestamp = key ? backedById.get(`${scope}:${key}`) : void 0;
1070
+ return typeof timestamp === "number" && (!idTimestamp || timestamp > idTimestamp);
1071
+ };
1105
1072
  const textKey = normalizeIMessageEchoText(lookup.text);
1106
1073
  const mediaKey = resolveIMessageEchoMediaKey(lookup.media);
1107
1074
  const messageIdKey = normalizeIMessageMessageId(lookup.messageId);
1108
1075
  let canUseMediaFallback = !messageIdKey;
1109
1076
  if (messageIdKey) {
1110
- const idTimestamp = this.messageIdCache.get(`${scope}:${messageIdKey}`);
1111
- if (idTimestamp && Date.now() - idTimestamp <= SENT_MESSAGE_ID_TTL_MS) return true;
1112
- const textTimestamp = textKey ? this.textCache.get(`${scope}:${textKey}`) : void 0;
1113
- const textBackedByIdTimestamp = textKey ? this.textBackedByIdCache.get(`${scope}:${textKey}`) : void 0;
1114
- const hasTextOnlyMatch = typeof textTimestamp === "number" && (!textBackedByIdTimestamp || textTimestamp > textBackedByIdTimestamp);
1115
- const mediaTimestamp = mediaKey ? this.mediaCache.get(`${scope}:${mediaKey}`) : void 0;
1116
- const mediaBackedByIdTimestamp = mediaKey ? this.mediaBackedByIdCache.get(`${scope}:${mediaKey}`) : void 0;
1117
- const hasMediaOnlyMatch = typeof mediaTimestamp === "number" && (!mediaBackedByIdTimestamp || mediaTimestamp > mediaBackedByIdTimestamp);
1077
+ if (matchesWithin(messageIdCache, messageIdKey, SENT_MESSAGE_ID_TTL_MS)) return true;
1078
+ const hasTextOnlyMatch = hasContentOnlyMatch(textCache, textBackedByIdCache, textKey);
1079
+ const hasMediaOnlyMatch = hasContentOnlyMatch(mediaCache, mediaBackedByIdCache, mediaKey);
1118
1080
  canUseMediaFallback = hasMediaOnlyMatch;
1119
1081
  if (!resolvedOptions.skipIdShortCircuit && !hasTextOnlyMatch && !hasMediaOnlyMatch) return false;
1120
1082
  }
1121
- if (textKey) {
1122
- const textTimestamp = this.textCache.get(`${scope}:${textKey}`);
1123
- if (textTimestamp && Date.now() - textTimestamp <= SENT_MESSAGE_TEXT_TTL_MS) return true;
1124
- }
1125
- if (mediaKey && canUseMediaFallback) {
1126
- const mediaTimestamp = this.mediaCache.get(`${scope}:${mediaKey}`);
1127
- if (mediaTimestamp && Date.now() - mediaTimestamp <= SENT_MESSAGE_TEXT_TTL_MS) return true;
1128
- }
1129
- return false;
1083
+ return matchesWithin(textCache, textKey, SENT_MESSAGE_TEXT_TTL_MS) || canUseMediaFallback && matchesWithin(mediaCache, mediaKey, SENT_MESSAGE_TEXT_TTL_MS);
1130
1084
  }
1131
- cleanup() {
1085
+ function cleanup() {
1132
1086
  const now = Date.now();
1133
1087
  for (const cache of [
1134
- this.textCache,
1135
- this.textBackedByIdCache,
1136
- this.mediaCache,
1137
- this.mediaBackedByIdCache,
1138
- this.messageIdCache
1088
+ textCache,
1089
+ textBackedByIdCache,
1090
+ mediaCache,
1091
+ mediaBackedByIdCache,
1092
+ messageIdCache
1139
1093
  ]) {
1140
- const ttlMs = cache === this.messageIdCache ? SENT_MESSAGE_ID_TTL_MS : SENT_MESSAGE_TEXT_TTL_MS;
1094
+ const ttlMs = cache === messageIdCache ? SENT_MESSAGE_ID_TTL_MS : SENT_MESSAGE_TEXT_TTL_MS;
1141
1095
  for (const [key, timestamp] of cache) if (now - timestamp > ttlMs) cache.delete(key);
1142
1096
  }
1143
1097
  }
1144
- };
1145
- function createSentMessageCache() {
1146
- return new DefaultSentMessageCache();
1098
+ return {
1099
+ remember,
1100
+ has
1101
+ };
1147
1102
  }
1148
1103
  //#endregion
1149
1104
  //#region extensions/imessage/src/monitor/group-allowlist-warnings.ts
@@ -1338,10 +1293,7 @@ const REFLECTION_PATTERNS = [
1338
1293
  ];
1339
1294
  function hasMatchOutsideCode(text, re, codeRegions) {
1340
1295
  const globalRe = new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
1341
- for (const match of text.matchAll(globalRe)) {
1342
- const start = match.index ?? -1;
1343
- if (start >= 0 && !isInsideCode(start, codeRegions)) return true;
1344
- }
1296
+ for (const match of text.matchAll(globalRe)) if (!isInsideCode(match.index, codeRegions)) return true;
1345
1297
  return false;
1346
1298
  }
1347
1299
  /**
@@ -1372,13 +1324,12 @@ const imessageConversationIdentityKinds = /* @__PURE__ */ new Set([
1372
1324
  const matchIMessageIngressEntry = ({ entry, context }) => {
1373
1325
  if (imessageConversationIdentityKinds.has(entry.kind) && context !== "group") return false;
1374
1326
  };
1375
- function isIMessageConversationAllowTarget(entry) {
1376
- const parsed = parseIMessageAllowTarget(entry);
1377
- return parsed.kind === "chat_id" || parsed.kind === "chat_guid" || parsed.kind === "chat_identifier";
1378
- }
1379
1327
  function mergeIMessageGroupAllowFromWithLegacyChatTargets(params) {
1380
1328
  if (params.groupAllowFrom.length > 0 || !params.allowLegacyConversationTargets) return params.groupAllowFrom;
1381
- const legacyChatTargets = params.allowFrom.filter((entry) => isIMessageConversationAllowTarget(entry));
1329
+ const legacyChatTargets = params.allowFrom.filter((entry) => {
1330
+ const parsed = parseIMessageAllowTarget(entry);
1331
+ return parsed.kind === "chat_id" || parsed.kind === "chat_guid" || parsed.kind === "chat_identifier";
1332
+ });
1382
1333
  if (legacyChatTargets.length === 0) return params.groupAllowFrom;
1383
1334
  return uniqueStrings([...params.groupAllowFrom, ...legacyChatTargets]);
1384
1335
  }
@@ -1429,25 +1380,9 @@ function normalizeIMessageChatIdentifierEntry(entry) {
1429
1380
  const parsed = parseIMessageAllowTarget(entry.trim());
1430
1381
  return parsed.kind === "chat_identifier" ? parsed.chatIdentifier.trim() || null : null;
1431
1382
  }
1432
- function normalizeGroupPolicy(policy) {
1433
- return policy === "open" || policy === "disabled" ? policy : "allowlist";
1434
- }
1435
1383
  function normalizeReplyField(value) {
1436
1384
  return typeof value === "number" ? String(value) : normalizeOptionalString(value);
1437
1385
  }
1438
- function describeReplyContext(message) {
1439
- const body = normalizeReplyField(message.reply_to_text);
1440
- if (!body) return null;
1441
- return {
1442
- body,
1443
- id: normalizeReplyField(message.thread_originator_guid) ?? normalizeReplyField(message.reply_to_guid),
1444
- sender: normalizeReplyField(message.reply_to_sender)
1445
- };
1446
- }
1447
- function resolveInboundEchoMessageIds(message) {
1448
- const values = [message.id != null ? String(message.id) : void 0, normalizeReplyField(message.guid)];
1449
- return uniqueStrings(values.filter((value) => Boolean(value)));
1450
- }
1451
1386
  function classifyIMessageSelfChat(message, isGroup, senderNormalized) {
1452
1387
  const chatIdentifier = normalizeIMessageHandle(message.chat_identifier ?? "") || void 0;
1453
1388
  const destination = normalizeIMessageHandle(message.destination_caller_id ?? "") || void 0;
@@ -1480,9 +1415,7 @@ function rememberIMessageSkippedFromMeForSelfChatDedupe(params) {
1480
1415
  else if (isAmbiguousSelfThread) params.selfChatCache?.remember(lookup);
1481
1416
  }
1482
1417
  async function hasIMessageEchoMatch(params) {
1483
- const scopes = typeof params.scope === "string" ? [params.scope] : params.scope;
1484
- for (const scope of scopes) {
1485
- if (!scope) continue;
1418
+ for (const scope of params.scope) {
1486
1419
  for (const messageId of params.messageIds) if (await params.echoCache.has(scope, { messageId })) return true;
1487
1420
  const fallbackMessageId = params.messageIds[0];
1488
1421
  if (!params.text && !params.media && !fallbackMessageId) continue;
@@ -1542,17 +1475,16 @@ async function resolveIMessageInboundDecision(params) {
1542
1475
  };
1543
1476
  const { isSelfChat, isAmbiguousSelfThread } = classifyIMessageSelfChat(params.message, isGroup, senderNormalized);
1544
1477
  let skipSelfChatHasCheck = false;
1545
- const inboundMessageIds = resolveInboundEchoMessageIds(params.message);
1478
+ const inboundMessageIds = uniqueStrings([params.message.id != null ? String(params.message.id) : void 0, normalizeReplyField(params.message.guid)].filter((value) => Boolean(value)));
1546
1479
  const inboundMessageId = inboundMessageIds[0];
1547
1480
  const hasInboundGuid = Boolean(normalizeReplyField(params.message.guid));
1548
- const echoScope = buildIMessageEchoScope({
1549
- accountId: params.accountId,
1550
- isGroup,
1551
- chatId,
1552
- chatGuid,
1553
- chatIdentifier,
1554
- sender
1555
- });
1481
+ const echoScope = [];
1482
+ if (isGroup) {
1483
+ const chatIdScope = formatIMessageChatTarget(chatId);
1484
+ if (chatIdScope) echoScope.push(`${params.accountId}:${chatIdScope}`);
1485
+ } else echoScope.push(`${params.accountId}:imessage:${sender}`);
1486
+ if (chatGuid) echoScope.push(`${params.accountId}:chat_guid:${chatGuid}`);
1487
+ if (chatIdentifier) echoScope.push(`${params.accountId}:chat_identifier:${chatIdentifier}`);
1556
1488
  if (params.message.is_from_me) {
1557
1489
  if (isAmbiguousSelfThread) params.selfChatCache?.remember(selfChatLookup);
1558
1490
  if (isSelfChat) {
@@ -1615,7 +1547,7 @@ async function resolveIMessageInboundDecision(params) {
1615
1547
  },
1616
1548
  contextBinding,
1617
1549
  dmPolicy: normalizeChannelDmPolicy(params.dmPolicy) ?? "pairing",
1618
- groupPolicy: normalizeGroupPolicy(params.groupPolicy),
1550
+ groupPolicy: params.groupPolicy === "open" || params.groupPolicy === "disabled" ? params.groupPolicy : "allowlist",
1619
1551
  policy: { groupAllowFromFallbackToAllowFrom: false },
1620
1552
  allowFrom: params.allowFrom,
1621
1553
  groupAllowFrom: groupAllowFromForAccess,
@@ -1749,10 +1681,7 @@ async function resolveIMessageInboundDecision(params) {
1749
1681
  messageIds: inboundMessageIds,
1750
1682
  includePendingText: isSelfChat
1751
1683
  })) {
1752
- params.logVerbose?.(describeIMessageEchoDropLog({
1753
- messageText: bodyText,
1754
- messageId: inboundMessageId
1755
- }));
1684
+ params.logVerbose?.(`imessage: skipping echo message${inboundMessageId ? ` id=${inboundMessageId}` : ""}: "${truncateUtf16Safe(bodyText, 50)}"`);
1756
1685
  return {
1757
1686
  kind: "drop",
1758
1687
  reason: "echo"
@@ -1767,7 +1696,12 @@ async function resolveIMessageInboundDecision(params) {
1767
1696
  reason: "reflected assistant content"
1768
1697
  };
1769
1698
  }
1770
- const replyContext = describeReplyContext(params.message);
1699
+ const replyBody = normalizeReplyField(params.message.reply_to_text);
1700
+ const replyContext = replyBody ? {
1701
+ body: replyBody,
1702
+ id: normalizeReplyField(params.message.thread_originator_guid) ?? normalizeReplyField(params.message.reply_to_guid),
1703
+ sender: normalizeReplyField(params.message.reply_to_sender)
1704
+ } : null;
1771
1705
  const contextVisibilityMode = resolveChannelContextVisibilityMode({
1772
1706
  cfg: params.cfg,
1773
1707
  channel: "imessage",
@@ -2020,20 +1954,6 @@ async function buildIMessageInboundContext(params) {
2020
1954
  inboundHistory
2021
1955
  };
2022
1956
  }
2023
- function buildIMessageEchoScope(params) {
2024
- const scopes = [];
2025
- if (params.isGroup) {
2026
- const chatIdScope = formatIMessageChatTarget(params.chatId);
2027
- if (chatIdScope) scopes.push(`${params.accountId}:${chatIdScope}`);
2028
- } else scopes.push(`${params.accountId}:imessage:${params.sender}`);
2029
- if (params.chatGuid) scopes.push(`${params.accountId}:chat_guid:${params.chatGuid}`);
2030
- if (params.chatIdentifier) scopes.push(`${params.accountId}:chat_identifier:${params.chatIdentifier}`);
2031
- return scopes;
2032
- }
2033
- function describeIMessageEchoDropLog(params) {
2034
- const preview = truncateUtf16Safe(params.messageText, 50);
2035
- return `imessage: skipping echo message${params.messageId ? ` id=${params.messageId}` : ""}: "${preview}"`;
2036
- }
2037
1957
  //#endregion
2038
1958
  //#region extensions/imessage/src/monitor/ingress.ts
2039
1959
  const IMESSAGE_INGRESS_PAYLOAD_VERSION = 1;
@@ -2195,9 +2115,7 @@ function createIMessageDurableIngress(options) {
2195
2115
  const DEFAULT_WINDOW_MS = 6e4;
2196
2116
  const DEFAULT_MAX_HITS = 5;
2197
2117
  const CLEANUP_INTERVAL_MS = 12e4;
2198
- function createLoopRateLimiter(opts) {
2199
- const windowMs = opts?.windowMs ?? DEFAULT_WINDOW_MS;
2200
- const maxHits = opts?.maxHits ?? DEFAULT_MAX_HITS;
2118
+ function createLoopRateLimiter() {
2201
2119
  const conversations = /* @__PURE__ */ new Map();
2202
2120
  let lastCleanup = Date.now();
2203
2121
  function cleanup() {
@@ -2205,7 +2123,7 @@ function createLoopRateLimiter(opts) {
2205
2123
  if (now - lastCleanup < CLEANUP_INTERVAL_MS) return;
2206
2124
  lastCleanup = now;
2207
2125
  for (const [key, timestamps] of conversations.entries()) {
2208
- const recent = timestamps.filter((ts) => now - ts <= windowMs);
2126
+ const recent = timestamps.filter((ts) => now - ts <= DEFAULT_WINDOW_MS);
2209
2127
  if (recent.length === 0) conversations.delete(key);
2210
2128
  else conversations.set(key, recent);
2211
2129
  }
@@ -2225,9 +2143,9 @@ function createLoopRateLimiter(opts) {
2225
2143
  const timestamps = conversations.get(conversationKey);
2226
2144
  if (!timestamps) return false;
2227
2145
  const now = Date.now();
2228
- const recent = timestamps.filter((ts) => now - ts <= windowMs);
2146
+ const recent = timestamps.filter((ts) => now - ts <= DEFAULT_WINDOW_MS);
2229
2147
  conversations.set(conversationKey, recent);
2230
- return recent.length >= maxHits;
2148
+ return recent.length >= DEFAULT_MAX_HITS;
2231
2149
  }
2232
2150
  };
2233
2151
  }
@@ -2246,37 +2164,22 @@ function isHeicAttachment(attachmentPath, mimeType) {
2246
2164
  const ext = path.extname(attachmentPath).toLowerCase();
2247
2165
  return ext === ".heic" || ext === ".heif";
2248
2166
  }
2249
- function jpegFilenameForAttachment(attachmentPath) {
2250
- return `${path.parse(attachmentPath).name || "imessage-attachment"}.jpg`;
2251
- }
2252
- function hasWildcardSegment(root) {
2253
- return root.replaceAll("\\", "/").split("/").includes("*");
2254
- }
2255
- async function canonicalizeAllowedRoots(roots) {
2256
- const canonicalRoots = [];
2257
- for (const root of roots) {
2258
- canonicalRoots.push(root);
2259
- if (hasWildcardSegment(root)) continue;
2260
- const canonicalRoot = await fs.realpath(root).catch(() => void 0);
2261
- if (canonicalRoot && canonicalRoot !== root) canonicalRoots.push(canonicalRoot);
2262
- }
2263
- return canonicalRoots;
2264
- }
2265
- async function assertAllowedCanonicalAttachmentPath(params) {
2266
- if (!params.allowedRoots) return;
2267
- const canonicalRoots = await canonicalizeAllowedRoots(params.allowedRoots);
2268
- if (!isInboundPathAllowed({
2269
- filePath: params.canonicalPath,
2270
- roots: canonicalRoots
2271
- })) throw new Error("attachment path resolves outside allowed roots");
2272
- }
2273
2167
  async function readAttachmentBuffer(params) {
2274
2168
  await using opened = await (params.deps.openLocalFileSafely ?? openLocalFileSafely)({ filePath: params.attachmentPath });
2275
2169
  if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2276
- await assertAllowedCanonicalAttachmentPath({
2277
- canonicalPath: opened.realPath,
2278
- allowedRoots: params.allowedRoots
2279
- });
2170
+ if (params.allowedRoots) {
2171
+ const canonicalRoots = [];
2172
+ for (const root of params.allowedRoots) {
2173
+ canonicalRoots.push(root);
2174
+ if (root.replaceAll("\\", "/").split("/").includes("*")) continue;
2175
+ const canonicalRoot = await fs.realpath(root).catch(() => void 0);
2176
+ if (canonicalRoot && canonicalRoot !== root) canonicalRoots.push(canonicalRoot);
2177
+ }
2178
+ if (!isInboundPathAllowed({
2179
+ filePath: opened.realPath,
2180
+ roots: canonicalRoots
2181
+ })) throw new Error("attachment path resolves outside allowed roots");
2182
+ }
2280
2183
  const buffer = await readFileHandleBounded(opened.handle, params.maxBytes).catch((error) => {
2281
2184
  if (error instanceof FsSafeError && error.code === "too-large") throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2282
2185
  throw error;
@@ -2295,7 +2198,7 @@ async function readAttachmentBuffer(params) {
2295
2198
  });
2296
2199
  })).buffer,
2297
2200
  contentType: "image/jpeg",
2298
- originalFilename: jpegFilenameForAttachment(params.attachmentPath)
2201
+ originalFilename: `${path.parse(params.attachmentPath).name || "imessage-attachment"}.jpg`
2299
2202
  };
2300
2203
  } catch (err) {
2301
2204
  params.deps.logVerbose?.(`imessage: HEIC attachment conversion failed; staging original instead: ${String(err)}`);
@@ -2347,12 +2250,6 @@ async function stageIMessageAttachments(attachments, params) {
2347
2250
  //#endregion
2348
2251
  //#region extensions/imessage/src/monitor/poll-comment.ts
2349
2252
  const DEFAULT_COMMENT_WINDOW_MS = 15e3;
2350
- function normalizeGuid(guid) {
2351
- return guid?.trim() ?? "";
2352
- }
2353
- function normalizeSender(sender) {
2354
- return sender?.trim().toLowerCase() ?? "";
2355
- }
2356
2253
  function createPollCommentFolder(options) {
2357
2254
  const windowMs = options?.windowMs ?? DEFAULT_COMMENT_WINDOW_MS;
2358
2255
  const seenPolls = /* @__PURE__ */ new Map();
@@ -2361,20 +2258,20 @@ function createPollCommentFolder(options) {
2361
2258
  }
2362
2259
  return {
2363
2260
  rememberPoll(guid, atMs, sender) {
2364
- const key = normalizeGuid(guid);
2261
+ const key = guid?.trim();
2365
2262
  if (!key || !Number.isFinite(atMs)) return;
2366
2263
  prune(atMs);
2367
2264
  seenPolls.set(key, {
2368
2265
  atMs,
2369
- sender: normalizeSender(sender)
2266
+ sender: normalizeLowercaseStringOrEmpty(sender)
2370
2267
  });
2371
2268
  },
2372
2269
  isPollComment(replyToGuid, atMs, sender) {
2373
- const key = normalizeGuid(replyToGuid);
2270
+ const key = replyToGuid?.trim();
2374
2271
  if (!key || !Number.isFinite(atMs)) return false;
2375
2272
  const seen = seenPolls.get(key);
2376
2273
  if (!seen || atMs < seen.atMs || atMs - seen.atMs > windowMs) return false;
2377
- const replySender = normalizeSender(sender);
2274
+ const replySender = normalizeLowercaseStringOrEmpty(sender);
2378
2275
  return seen.sender.length > 0 && replySender.length > 0 && seen.sender === replySender;
2379
2276
  }
2380
2277
  };
@@ -2599,7 +2496,6 @@ var DefaultSelfChatCache = class {
2599
2496
  this.cache = /* @__PURE__ */ new Map();
2600
2497
  this.insertionOrder = /* @__PURE__ */ new Map();
2601
2498
  this.lastCleanupAt = 0;
2602
- this.nextEntryId = 1;
2603
2499
  }
2604
2500
  buildBucketKey(lookup) {
2605
2501
  const text = normalizeText(lookup.text);
@@ -2609,16 +2505,15 @@ var DefaultSelfChatCache = class {
2609
2505
  remember(lookup) {
2610
2506
  const key = this.buildBucketKey(lookup);
2611
2507
  if (!key || !isUsableTimestamp(lookup.createdAt)) return;
2612
- const entries = this.cache.get(key) ?? /* @__PURE__ */ new Map();
2508
+ const entries = this.cache.get(key) ?? /* @__PURE__ */ new Set();
2613
2509
  const entry = {
2614
2510
  createdAt: lookup.createdAt,
2615
2511
  createdAtSkewToleranceMs: lookup.allowCreatedAtSkew ? SELF_CHAT_CREATED_AT_TOLERANCE_MS : 0,
2616
2512
  rememberedAt: Date.now()
2617
2513
  };
2618
- const id = this.nextEntryId++;
2619
- entries.set(id, entry);
2514
+ entries.add(entry);
2620
2515
  this.cache.set(key, entries);
2621
- this.insertionOrder.set(id, key);
2516
+ this.insertionOrder.set(entry, key);
2622
2517
  this.maybeCleanup();
2623
2518
  }
2624
2519
  has(lookup) {
@@ -2634,24 +2529,20 @@ var DefaultSelfChatCache = class {
2634
2529
  return now - entry.rememberedAt <= SELF_CHAT_TTL_MS && (createdAtDelta === 0 || createdAtDelta < entry.createdAtSkewToleranceMs);
2635
2530
  });
2636
2531
  }
2532
+ removeEntry(entry, key) {
2533
+ this.insertionOrder.delete(entry);
2534
+ const entries = this.cache.get(key);
2535
+ entries?.delete(entry);
2536
+ if (entries?.size === 0) this.cache.delete(key);
2537
+ }
2637
2538
  maybeCleanup() {
2638
2539
  const now = Date.now();
2639
2540
  if (now - this.lastCleanupAt < CLEANUP_MIN_INTERVAL_MS) return;
2640
2541
  this.lastCleanupAt = now;
2641
- for (const [key, entries] of this.cache.entries()) {
2642
- for (const [id, entry] of entries.entries()) if (now - entry.rememberedAt > SELF_CHAT_TTL_MS) {
2643
- entries.delete(id);
2644
- this.insertionOrder.delete(id);
2645
- }
2646
- if (entries.size === 0) this.cache.delete(key);
2647
- }
2542
+ for (const [entry, key] of this.insertionOrder) if (now - entry.rememberedAt > SELF_CHAT_TTL_MS) this.removeEntry(entry, key);
2648
2543
  while (this.insertionOrder.size > MAX_SELF_CHAT_CACHE_ENTRIES) {
2649
- const [id, key] = expectDefined(this.insertionOrder.entries().next().value, "oldest iMessage self-chat cache entry");
2650
- this.insertionOrder.delete(id);
2651
- const entries = this.cache.get(key);
2652
- if (!entries) continue;
2653
- entries.delete(id);
2654
- if (entries.size === 0) this.cache.delete(key);
2544
+ const [entry, key] = expectDefined(this.insertionOrder.entries().next().value, "oldest iMessage self-chat cache entry");
2545
+ this.removeEntry(entry, key);
2655
2546
  }
2656
2547
  }
2657
2548
  };
@@ -2680,53 +2571,12 @@ const APPROVAL_REACTION_POLL_INTERVAL_MS = 2e3;
2680
2571
  const APPROVAL_REACTION_DISCOVERY_INTERVAL_MS = 6e4;
2681
2572
  const IMESSAGE_TYPING_KEEPALIVE_INTERVAL_MS = 8e3;
2682
2573
  const IMESSAGE_TYPING_KEEPALIVE_MAX_DURATION_MS = 6e5;
2683
- function resolveConfiguredIMessageTypingMode(cfg, agentId) {
2684
- return resolveAgentConfig(cfg, agentId)?.typingMode ?? cfg.agents?.defaults?.typingMode;
2685
- }
2686
2574
  function isIMessagePluginPayloadAttachment(attachment) {
2687
2575
  const attachmentPath = attachment.original_path?.trim().toLowerCase() ?? "";
2688
2576
  const transferName = attachment.transfer_name?.trim().toLowerCase() ?? "";
2689
2577
  const uti = attachment.uti?.trim().toLowerCase() ?? "";
2690
2578
  return attachmentPath.endsWith(".pluginpayloadattachment") || transferName.endsWith(".pluginpayloadattachment") || uti === "com.apple.messages.pluginpayloadattachment";
2691
2579
  }
2692
- function resolveIMessageInboundMediaInput(params) {
2693
- const mediaCandidates = params.attachments.filter((entry) => !isIMessagePluginPayloadAttachment(entry));
2694
- const mediaFacts = mediaCandidates.map((attachment) => {
2695
- const contentType = attachment.mime_type?.trim() || void 0;
2696
- return {
2697
- contentType,
2698
- kind: kindFromMime(contentType) ?? "unknown"
2699
- };
2700
- });
2701
- const rawMediaAttachments = mediaCandidates.map((attachment, index) => {
2702
- const fact = mediaFacts[index] ?? { kind: "unknown" };
2703
- const attachmentPath = attachment.original_path?.trim();
2704
- if (!attachmentPath || attachment.missing) return fact;
2705
- if (!isInboundPathAllowed({
2706
- filePath: attachmentPath,
2707
- roots: params.effectiveAttachmentRoots
2708
- })) {
2709
- params.logVerbose?.(`imessage: dropping inbound attachment outside allowed roots: ${attachmentPath}`);
2710
- return fact;
2711
- }
2712
- return {
2713
- ...fact,
2714
- path: attachmentPath
2715
- };
2716
- });
2717
- return {
2718
- bodyText: params.messageText,
2719
- mediaFacts,
2720
- mediaCandidates,
2721
- rawMediaAttachments
2722
- };
2723
- }
2724
- function formatIMessageInboundMediaBody(params) {
2725
- return formatInboundMediaUnavailableText({
2726
- body: params.messageText,
2727
- notice: `[imessage ${params.unavailableCount > 1 ? `${params.unavailableCount} attachments` : "attachment"} unavailable]`
2728
- });
2729
- }
2730
2580
  const warnIfImsgUpgradeNeeded = (() => {
2731
2581
  let fired = false;
2732
2582
  return { fireOnce: (rpcMethods, runtime) => {
@@ -2748,16 +2598,6 @@ const IMESSAGE_DIAGNOSTIC_DROP_REASONS = /* @__PURE__ */ new Set([
2748
2598
  "self-chat echo"
2749
2599
  ]);
2750
2600
  const IMESSAGE_THROTTLED_DIAGNOSTIC_DROP_REASONS = /* @__PURE__ */ new Set(["from me", "no mention"]);
2751
- function describeIMessageInboundDropDiagnostic(params) {
2752
- if (!IMESSAGE_DIAGNOSTIC_DROP_REASONS.has(params.reason)) return null;
2753
- const messageId = typeof params.message.id === "number" || typeof params.message.id === "string" ? String(params.message.id) : "unknown";
2754
- 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.` : "";
2755
- 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}`;
2756
- }
2757
- function describeIMessageWatchSubscribeStartupFailure(params) {
2758
- const retry = params.retryDelayMs !== void 0 ? ` retry_in_ms=${params.retryDelayMs}` : "";
2759
- return `imessage: watch.subscribe startup failed attempt=${params.attempt}/${params.maxAttempts} account=${params.accountId} cliPath=${params.cliPath} dbPath=${params.dbPath ? "configured" : "default"} remoteHost=${params.remoteHost ? "configured" : "none"} timeoutMs=${params.probeTimeoutMs} since_rowid=${params.watchSinceRowid ?? "none"} attachments=${params.includeAttachments} include_reactions=true${retry}: ${String(params.error)}`;
2760
- }
2761
2601
  async function monitorIMessageProvider(opts) {
2762
2602
  const { scheduler } = opts;
2763
2603
  const runtime = opts.runtime ?? createNonExitingRuntime();
@@ -2785,8 +2625,8 @@ async function monitorIMessageProvider(opts) {
2785
2625
  const selfChatCache = createSelfChatCache();
2786
2626
  const loopRateLimiter = createLoopRateLimiter();
2787
2627
  const textLimit = resolveTextChunkLimit(cfg, "imessage", accountInfo.accountId);
2788
- const allowFrom = normalizeStringEntries(opts.allowFrom ?? imessageCfg.allowFrom);
2789
- const configuredGroupAllowFrom = opts.groupAllowFrom ?? imessageCfg.groupAllowFrom;
2628
+ const allowFrom = normalizeStringEntries(imessageCfg.allowFrom);
2629
+ const configuredGroupAllowFrom = imessageCfg.groupAllowFrom;
2790
2630
  const groupAllowFrom = normalizeStringEntries(configuredGroupAllowFrom ?? (imessageCfg.allowFrom && imessageCfg.allowFrom.length > 0 ? imessageCfg.allowFrom : []));
2791
2631
  const allowLegacyConversationAllowFromForGroup = configuredGroupAllowFrom == null;
2792
2632
  const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
@@ -2813,10 +2653,10 @@ async function monitorIMessageProvider(opts) {
2813
2653
  });
2814
2654
  const dmPolicy = imessageCfg.dmPolicy ?? "pairing";
2815
2655
  const catchupCfg = resolveCatchupConfig(imessageCfg.catchup);
2816
- const includeAttachments = opts.includeAttachments ?? imessageCfg.includeAttachments ?? false;
2817
- const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
2818
- const cliPath = opts.cliPath ?? imessageCfg.cliPath ?? "imsg";
2819
- const dbPath = opts.dbPath ?? imessageCfg.dbPath;
2656
+ const includeAttachments = imessageCfg.includeAttachments ?? false;
2657
+ const mediaMaxBytes = (imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
2658
+ const cliPath = imessageCfg.cliPath ?? "imsg";
2659
+ const dbPath = imessageCfg.dbPath;
2820
2660
  const probeTimeoutMs = imessageCfg.probeTimeoutMs ?? 1e4;
2821
2661
  const attachmentRoots = resolveIMessageAttachmentRoots({
2822
2662
  cfg,
@@ -2995,13 +2835,34 @@ async function monitorIMessageProvider(opts) {
2995
2835
  const messageText = ((message.poll ? renderIMessagePollBody(message.poll, message.sender, { preferOptionId: Boolean(remoteHost) }) : null) ?? message.text ?? "").trim();
2996
2836
  const attachments = includeAttachments ? message.attachments ?? [] : [];
2997
2837
  const effectiveAttachmentRoots = remoteHost ? remoteAttachmentRoots : attachmentRoots;
2838
+ const mediaCandidates = attachments.filter((entry) => !isIMessagePluginPayloadAttachment(entry));
2839
+ const mediaFacts = mediaCandidates.map((attachment) => {
2840
+ const contentType = attachment.mime_type?.trim() || void 0;
2841
+ return {
2842
+ contentType,
2843
+ kind: kindFromMime(contentType) ?? "unknown"
2844
+ };
2845
+ });
2998
2846
  return {
2999
2847
  messageText,
3000
- ...resolveIMessageInboundMediaInput({
3001
- messageText,
3002
- attachments,
3003
- effectiveAttachmentRoots,
3004
- logVerbose
2848
+ bodyText: messageText,
2849
+ mediaFacts,
2850
+ mediaCandidates,
2851
+ rawMediaAttachments: mediaCandidates.map((attachment, index) => {
2852
+ const fact = mediaFacts[index] ?? { kind: "unknown" };
2853
+ const attachmentPath = attachment.original_path?.trim();
2854
+ if (!attachmentPath || attachment.missing) return fact;
2855
+ if (!isInboundPathAllowed({
2856
+ filePath: attachmentPath,
2857
+ roots: effectiveAttachmentRoots
2858
+ })) {
2859
+ logVerbose(`imessage: dropping inbound attachment outside allowed roots: ${attachmentPath}`);
2860
+ return fact;
2861
+ }
2862
+ return {
2863
+ ...fact,
2864
+ path: attachmentPath
2865
+ };
3005
2866
  }),
3006
2867
  effectiveAttachmentRoots
3007
2868
  };
@@ -3049,13 +2910,10 @@ async function monitorIMessageProvider(opts) {
3049
2910
  const rateLimitKey = `${accountInfo.accountId}:${conversationKey}`;
3050
2911
  if (decision.kind === "drop") {
3051
2912
  if (decision.reason === "echo" || decision.reason === "reflected assistant content") loopRateLimiter.record(rateLimitKey);
3052
- const diagnostic = describeIMessageInboundDropDiagnostic({
3053
- accountId: accountInfo.accountId,
3054
- groupsConfigPath,
3055
- reason: decision.reason,
3056
- message
3057
- });
3058
- if (diagnostic) {
2913
+ if (IMESSAGE_DIAGNOSTIC_DROP_REASONS.has(decision.reason)) {
2914
+ const messageId = typeof message.id === "number" || typeof message.id === "string" ? String(message.id) : "unknown";
2915
+ const mentionHint = decision.reason === "no mention" ? ` Mention the agent (default patterns come from its identity name/emoji), or set ${groupsConfigPath}["${message.chat_id}"].requireMention=false. Preserve existing groups entries; when adding the first groups map, include "*": {} to keep other chats admitted.` : "";
2916
+ const diagnostic = `imessage: dropped inbound message account=${accountInfo.accountId} reason=${JSON.stringify(decision.reason)} chat_id=${message.chat_id ?? "unknown"} group=${message.is_group === true} message_id=${messageId} guid=${message.guid ? "present" : "missing"} created_at=${message.created_at ?? "unknown"}${mentionHint}`;
3059
2917
  const throttleKey = `${rateLimitKey}:${decision.reason}`;
3060
2918
  if (!IMESSAGE_THROTTLED_DIAGNOSTIC_DROP_REASONS.has(decision.reason) || !loggedThrottledDropDiagnostics.check(throttleKey)) runtime.log?.(warn(diagnostic));
3061
2919
  }
@@ -3143,7 +3001,7 @@ async function monitorIMessageProvider(opts) {
3143
3001
  if (privateApiStatus.available) {
3144
3002
  if (!supportsTyping || !supportsRead) warnIfImsgUpgradeNeeded.fireOnce(privateApiStatus.rpcMethods, runtime);
3145
3003
  }
3146
- const configuredTypingMode = resolveConfiguredIMessageTypingMode(cfg, decision.route.agentId);
3004
+ const configuredTypingMode = resolveAgentConfig(cfg, decision.route.agentId)?.typingMode ?? cfg.agents?.defaults?.typingMode;
3147
3005
  const sendPolicy = resolveSendPolicy({
3148
3006
  cfg,
3149
3007
  entry: getSessionEntry({
@@ -3188,12 +3046,12 @@ async function monitorIMessageProvider(opts) {
3188
3046
  const unavailableCount = staged.unavailableCount;
3189
3047
  const contextDecision = unavailableCount > 0 ? {
3190
3048
  ...decision,
3191
- agentBodyText: formatIMessageInboundMediaBody({
3192
- messageText,
3193
- unavailableCount
3049
+ agentBodyText: formatInboundMediaUnavailableText({
3050
+ body: messageText,
3051
+ notice: `[imessage ${unavailableCount > 1 ? `${unavailableCount} attachments` : "attachment"} unavailable]`
3194
3052
  })
3195
3053
  } : decision;
3196
- const previousTimestamp = readSessionUpdatedAt({
3054
+ const previousTimestamp = await readSessionUpdatedAtAsync({
3197
3055
  storePath,
3198
3056
  sessionKey: decision.route.sessionKey
3199
3057
  });
@@ -3409,16 +3267,8 @@ async function monitorIMessageProvider(opts) {
3409
3267
  });
3410
3268
  };
3411
3269
  const resolveApprovalControlConversation = (message) => {
3412
- const sender = normalizeIMessageHandle((message.sender ?? "").trim());
3413
- const destination = normalizeIMessageHandle((message.destination_caller_id ?? "").trim());
3414
- const actorHandle = (message.is_from_me !== true && Boolean(sender) && sender === destination ? "" : sender) || (message.is_from_me === true ? destination : "");
3415
- return actorHandle ? buildIMessageApprovalConversationKeyForInbound({
3416
- chatGuid: message.chat_guid,
3417
- chatIdentifier: message.chat_identifier,
3418
- chatId: message.chat_id,
3419
- isGroup: message.is_group,
3420
- actorHandle
3421
- }) : null;
3270
+ const actorHandle = resolveIMessageApprovalControlActor(message);
3271
+ return actorHandle ? buildIMessageApprovalConversationKeyForInbound(message, actorHandle) : null;
3422
3272
  };
3423
3273
  const ingress = createIMessageDurableIngress({
3424
3274
  accountId: accountInfo.accountId,
@@ -3492,25 +3342,24 @@ async function monitorIMessageProvider(opts) {
3492
3342
  });
3493
3343
  if (opts.abortSignal?.aborted) return;
3494
3344
  const abort = opts.abortSignal;
3495
- const createWatchClient = async () => await createIMessageRpcClient({
3496
- cliPath,
3497
- dbPath,
3498
- remoteHost,
3499
- runtime,
3500
- onNotification: (msg) => {
3501
- if (msg.method === "message") ingress.receive(msg.params).catch((err) => {
3502
- runtime.error?.(`imessage: durable admission failed: ${String(err)}`);
3503
- });
3504
- else if (msg.method === "error") runtime.error?.(`imessage: watch error ${JSON.stringify(sanitizeIMessageWatchErrorPayload(msg.params))}`);
3505
- }
3506
- });
3507
3345
  for (let attempt = 1; attempt <= WATCH_SUBSCRIBE_MAX_ATTEMPTS; attempt++) {
3508
3346
  if (abort?.aborted) return;
3509
3347
  let attemptClient;
3510
3348
  let attemptDetachAbortHandler = () => {};
3511
3349
  let keepAttemptClient = false;
3512
3350
  try {
3513
- attemptClient = await createWatchClient();
3351
+ attemptClient = await createIMessageRpcClient({
3352
+ cliPath,
3353
+ dbPath,
3354
+ remoteHost,
3355
+ runtime,
3356
+ onNotification: (msg) => {
3357
+ if (msg.method === "message") ingress.receive(msg.params).catch((err) => {
3358
+ runtime.error?.(`imessage: durable admission failed: ${String(err)}`);
3359
+ });
3360
+ else if (msg.method === "error") runtime.error?.(`imessage: watch error ${JSON.stringify(sanitizeIMessageWatchErrorPayload(msg.params))}`);
3361
+ }
3362
+ });
3514
3363
  let attemptSubscriptionId = null;
3515
3364
  attemptDetachAbortHandler = attachIMessageMonitorAbortHandler({
3516
3365
  abortSignal: abort,
@@ -3531,18 +3380,8 @@ async function monitorIMessageProvider(opts) {
3531
3380
  if (abort?.aborted) return;
3532
3381
  const retriable = isRetriableWatchSubscribeStartupError(err);
3533
3382
  const shouldRetry = attempt < WATCH_SUBSCRIBE_MAX_ATTEMPTS && retriable;
3534
- const failureParams = {
3535
- accountId: accountInfo.accountId,
3536
- attempt,
3537
- maxAttempts: WATCH_SUBSCRIBE_MAX_ATTEMPTS,
3538
- cliPath,
3539
- dbPath,
3540
- remoteHost,
3541
- includeAttachments,
3542
- probeTimeoutMs,
3543
- watchSinceRowid,
3544
- error: err
3545
- };
3383
+ const retry = shouldRetry ? ` retry_in_ms=${WATCH_SUBSCRIBE_RETRY_DELAY_MS}` : "";
3384
+ const failure = `imessage: watch.subscribe startup failed attempt=${attempt}/${WATCH_SUBSCRIBE_MAX_ATTEMPTS} account=${accountInfo.accountId} cliPath=${cliPath} dbPath=${dbPath ? "configured" : "default"} remoteHost=${remoteHost ? "configured" : "none"} timeoutMs=${probeTimeoutMs} since_rowid=${watchSinceRowid ?? "none"} attachments=${includeAttachments} include_reactions=true${retry}: ${String(err)}`;
3546
3385
  if (!shouldRetry) {
3547
3386
  opts.statusSink?.({
3548
3387
  connected: false,
@@ -3550,7 +3389,7 @@ async function monitorIMessageProvider(opts) {
3550
3389
  terminalDisconnect: retriable ? void 0 : true,
3551
3390
  lastError: String(err)
3552
3391
  });
3553
- runtime.error?.(danger(`imessage: monitor failed: ${describeIMessageWatchSubscribeStartupFailure(failureParams)}`));
3392
+ runtime.error?.(danger(`imessage: monitor failed: ${failure}`));
3554
3393
  throw err;
3555
3394
  }
3556
3395
  opts.statusSink?.({
@@ -3558,10 +3397,7 @@ async function monitorIMessageProvider(opts) {
3558
3397
  lifecycle: "recovering",
3559
3398
  lastError: String(err)
3560
3399
  });
3561
- runtime.log?.(warn(describeIMessageWatchSubscribeStartupFailure({
3562
- ...failureParams,
3563
- retryDelayMs: WATCH_SUBSCRIBE_RETRY_DELAY_MS
3564
- })));
3400
+ runtime.log?.(warn(failure));
3565
3401
  attemptDetachAbortHandler();
3566
3402
  attemptDetachAbortHandler = () => {};
3567
3403
  await attemptClient?.stop();