@openclaw/imessage 2026.9.1-beta.1 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{accounts-DIpGOIiN.js → accounts-CCirBEIm.js} +2 -3
  2. package/dist/{actions.runtime-BhggncbG.js → actions.runtime-DCjNT81u.js} +5 -5
  3. package/dist/api.js +5 -5
  4. package/dist/{approval-handler.runtime-BC-YsgAf.js → approval-handler.runtime-BReeID1z.js} +14 -9
  5. package/dist/{approval-reactions-Cvwmdlev.js → approval-reactions-DcLRPx7O.js} +6 -10
  6. package/dist/channel-config-api.js +1 -1
  7. package/dist/channel-plugin-api.js +1 -1
  8. package/dist/{channel-CnKQ2MVB.js → channel-zDmMTHWD.js} +16 -12
  9. package/dist/{channel.runtime-BQLCh7bE.js → channel.runtime-qQBHCle8.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-BWqG2esy.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-AmSj6QIH.js} +293 -27
  19. package/dist/{remote-file-CyTTuRAt.js → remote-file-COTN7196.js} +9 -8
  20. package/dist/runtime-Cza4CY5T.js +8 -0
  21. package/dist/runtime-api.js +7 -7
  22. package/dist/{sanitize-outbound-g651fIhi.js → sanitize-outbound-CN3Hxbjy.js} +393 -82
  23. package/dist/{send-DVQY6M5M.js → send-v0blZfaI.js} +145 -23
  24. package/dist/state-contract-DvahEY4X.js +56 -0
  25. package/dist/state-migrations-D-fyHVcb.js +277 -0
  26. package/node_modules/typebox/build/format/email.mjs +1 -1
  27. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  28. package/node_modules/typebox/package.json +1 -1
  29. package/openclaw.plugin.json +0 -6
  30. package/package.json +5 -5
  31. package/dist/catchup-Dh-Rv4Ur.js +0 -294
  32. package/dist/monitor-reply-cache-BdeUQaHO.js +0 -353
  33. package/dist/persisted-echo-cache-DGxy-J4t.js +0 -143
@@ -1,13 +1,12 @@
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";
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 { l as isKnownFromMeIMessageMessageId, o as createIMessageRpcClient, r as sanitizeOutboundText, u as rememberIMessageReplyCache, v as resolveIMessageDirectChatService } from "./sanitize-outbound-CN3Hxbjy.js";
4
+ import { f as probeIMessage, r as resolveIMessageInboundConversationId } from "./group-policy-BWqG2esy.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-DcLRPx7O.js";
9
+ import { c as iMessageApprovalControlBindings, n as hasPersistedIMessageEcho, r as stripLeadingEchoTextCorruptionMarkers, s as maybeResolveIMessageApprovalPollVote, t as sendMessageIMessage } from "./send-v0blZfaI.js";
11
10
  import { t as resolveIMessageReactionContext } from "./reaction-context-BAYI7pz0.js";
12
11
  import { n as maybeResolveIMessageQuestionReaction, t as hasIMessageQuestionReactionTarget } from "./question-reactions-viP7ZcyG.js";
13
12
  import { bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor, createChannelMessageReplyPipeline, createMessageReceiptFromOutboundResults, listMessageReceiptPlatformIds, resolveChannelStreamingBlockEnabled } from "openclaw/plugin-sdk/channel-outbound";
@@ -19,7 +18,7 @@ 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
23
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
25
24
  import { sliceUtf16Safe, truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
@@ -27,8 +26,6 @@ import { collectErrorGraphCandidates, formatErrorMessage } from "openclaw/plugin
27
26
  import { buildChannelGroupsScopeTree, resolveChannelGroupPolicy, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
28
27
  import { convertMarkdownTables as convertMarkdownTables$1, findCodeRegions, isInsideCode, sanitizeTerminalText } from "openclaw/plugin-sdk/text-chunking";
29
28
  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";
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";
@@ -264,6 +263,269 @@ function attachIMessageMonitorAbortHandler(params) {
264
263
  return () => abort.removeEventListener("abort", onAbort);
265
264
  }
266
265
  //#endregion
266
+ //#region extensions/imessage/src/monitor/catchup.ts
267
+ const DEFAULT_MAX_AGE_MINUTES = 120;
268
+ const MAX_MAX_AGE_MINUTES = 720;
269
+ const DEFAULT_PER_RUN_LIMIT = 50;
270
+ const MAX_PER_RUN_LIMIT = 500;
271
+ const DEFAULT_FIRST_RUN_LOOKBACK_MINUTES = 30;
272
+ const DEFAULT_MAX_FAILURE_RETRIES = 10;
273
+ const MAX_MAX_FAILURE_RETRIES = 1e3;
274
+ const cursorWriteQueue = new KeyedAsyncQueue();
275
+ function openCatchupCursorStore() {
276
+ return getIMessageRuntime().state.openSyncKeyedStore({
277
+ namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
278
+ maxEntries: 256
279
+ });
280
+ }
281
+ function updateCatchupCursorStore(key, updateValue) {
282
+ const store = openCatchupCursorStore();
283
+ if (!store.update) throw new Error("iMessage catchup cursor persistence requires atomic plugin-state update.");
284
+ return store.update(key, updateValue);
285
+ }
286
+ function enqueueCursorWrite(accountId, fn) {
287
+ const key = resolveIMessageCatchupCursorKey(accountId);
288
+ return cursorWriteQueue.enqueue(key, fn);
289
+ }
290
+ function sanitizeFailureRetriesInput(raw) {
291
+ if (!raw || typeof raw !== "object") return {};
292
+ const out = {};
293
+ for (const [guid, count] of Object.entries(raw)) {
294
+ if (!guid || typeof guid !== "string") continue;
295
+ if (typeof count !== "number" || !Number.isFinite(count) || count <= 0) continue;
296
+ out[guid] = Math.floor(count);
297
+ }
298
+ return out;
299
+ }
300
+ function normalizeIMessageCatchupCursor(value) {
301
+ if (!value || typeof value !== "object") return null;
302
+ const raw = value;
303
+ if (typeof raw.lastSeenMs !== "number" || !Number.isFinite(raw.lastSeenMs)) return null;
304
+ if (typeof raw.lastSeenRowid !== "number" || !Number.isFinite(raw.lastSeenRowid)) return null;
305
+ const failureRetries = sanitizeFailureRetriesInput(raw.failureRetries);
306
+ const hasRetries = Object.keys(failureRetries).length > 0;
307
+ return {
308
+ lastSeenMs: raw.lastSeenMs,
309
+ lastSeenRowid: raw.lastSeenRowid,
310
+ updatedAt: typeof raw.updatedAt === "number" ? raw.updatedAt : 0,
311
+ ...hasRetries ? { failureRetries } : {}
312
+ };
313
+ }
314
+ function readIMessageCatchupCursor(accountId) {
315
+ return normalizeIMessageCatchupCursor(openCatchupCursorStore().lookup(resolveIMessageCatchupCursorKey(accountId)));
316
+ }
317
+ async function loadIMessageCatchupCursor(accountId) {
318
+ return readIMessageCatchupCursor(accountId);
319
+ }
320
+ function buildIMessageCatchupCursor(next) {
321
+ const sanitized = sanitizeFailureRetriesInput(next.failureRetries);
322
+ const hasRetries = Object.keys(sanitized).length > 0;
323
+ return {
324
+ lastSeenMs: next.lastSeenMs,
325
+ lastSeenRowid: next.lastSeenRowid,
326
+ updatedAt: Date.now(),
327
+ ...hasRetries ? { failureRetries: sanitized } : {}
328
+ };
329
+ }
330
+ async function saveIMessageCatchupCursor(accountId, next, options = {}) {
331
+ const cursor = buildIMessageCatchupCursor(next);
332
+ updateCatchupCursorStore(resolveIMessageCatchupCursorKey(accountId), (existingValue) => {
333
+ const existing = normalizeIMessageCatchupCursor(existingValue);
334
+ if (existing && cursor.lastSeenRowid < existing.lastSeenRowid) {
335
+ if (!options.allowCursorRewindForRetries) return;
336
+ return buildIMessageCatchupCursor({
337
+ lastSeenMs: cursor.lastSeenMs,
338
+ lastSeenRowid: cursor.lastSeenRowid,
339
+ failureRetries: {
340
+ ...existing.failureRetries,
341
+ ...cursor.failureRetries
342
+ }
343
+ });
344
+ }
345
+ return cursor;
346
+ });
347
+ }
348
+ function clampInt(value, min, max, fallback) {
349
+ return resolveIntegerOption(value, fallback, {
350
+ min,
351
+ max
352
+ });
353
+ }
354
+ function resolveCatchupConfig(raw) {
355
+ return {
356
+ enabled: Boolean(raw?.enabled),
357
+ maxAgeMinutes: clampInt(raw?.maxAgeMinutes, 1, MAX_MAX_AGE_MINUTES, DEFAULT_MAX_AGE_MINUTES),
358
+ perRunLimit: clampInt(raw?.perRunLimit, 1, MAX_PER_RUN_LIMIT, DEFAULT_PER_RUN_LIMIT),
359
+ firstRunLookbackMinutes: clampInt(raw?.firstRunLookbackMinutes, 1, MAX_MAX_AGE_MINUTES, DEFAULT_FIRST_RUN_LOOKBACK_MINUTES),
360
+ maxFailureRetries: clampInt(raw?.maxFailureRetries, 1, MAX_MAX_FAILURE_RETRIES, DEFAULT_MAX_FAILURE_RETRIES)
361
+ };
362
+ }
363
+ async function advanceIMessageCatchupCursor(accountId, next, config) {
364
+ if (!Number.isFinite(next.lastSeenMs) || !Number.isFinite(next.lastSeenRowid)) return false;
365
+ return await enqueueCursorWrite(accountId, async () => {
366
+ let advanced = false;
367
+ updateCatchupCursorStore(resolveIMessageCatchupCursorKey(accountId), (existingValue) => {
368
+ const cursor = normalizeIMessageCatchupCursor(existingValue);
369
+ if (cursor && next.lastSeenRowid <= cursor.lastSeenRowid) return;
370
+ if (Object.values(cursor?.failureRetries ?? {}).some((count) => count < config.maxFailureRetries)) return;
371
+ advanced = true;
372
+ return buildIMessageCatchupCursor({
373
+ lastSeenMs: Math.max(cursor?.lastSeenMs ?? next.lastSeenMs, next.lastSeenMs),
374
+ lastSeenRowid: next.lastSeenRowid,
375
+ failureRetries: cursor?.failureRetries
376
+ });
377
+ });
378
+ return advanced;
379
+ });
380
+ }
381
+ /**
382
+ * One catchup pass. Loads the cursor, fetches `messages.history`, replays
383
+ * each row through `dispatch`, advances the cursor on success / give-up,
384
+ * persists the cursor, returns a summary.
385
+ *
386
+ * The fetch and dispatch functions are injected so this loop is unit-testable
387
+ * without standing up an `imsg` daemon. The wiring in `monitor-provider.ts`
388
+ * passes the live `client.request("messages.history", ...)` adapter as
389
+ * `fetch` and the `evaluateIMessageInbound` + `runChannelInboundEvent`
390
+ * pipeline as `dispatch`.
391
+ */
392
+ async function performIMessageCatchup(params) {
393
+ const now = params.now ?? Date.now();
394
+ const cfg = params.config;
395
+ const cursor = await loadIMessageCatchupCursor(params.accountId);
396
+ const lookbackMs = cursor === null ? cfg.firstRunLookbackMinutes * 6e4 : cfg.maxAgeMinutes * 6e4;
397
+ const ageBoundMs = now - cfg.maxAgeMinutes * 6e4;
398
+ const windowStartMs = Math.max(cursor?.lastSeenMs ?? now - lookbackMs, ageBoundMs);
399
+ const windowEndMs = now;
400
+ const sinceRowid = cursor?.lastSeenRowid ?? 0;
401
+ const summary = {
402
+ querySucceeded: false,
403
+ fullyCaughtUp: false,
404
+ fetchedCount: 0,
405
+ replayed: 0,
406
+ skippedFromMe: 0,
407
+ skippedPreCursor: 0,
408
+ skippedGivenUp: 0,
409
+ failed: 0,
410
+ givenUp: 0,
411
+ cursorBefore: cursor ? {
412
+ lastSeenMs: cursor.lastSeenMs,
413
+ lastSeenRowid: cursor.lastSeenRowid
414
+ } : null,
415
+ cursorAfter: {
416
+ lastSeenMs: cursor?.lastSeenMs ?? windowStartMs,
417
+ lastSeenRowid: cursor?.lastSeenRowid ?? 0
418
+ },
419
+ windowStartMs,
420
+ windowEndMs
421
+ };
422
+ let fetchResult;
423
+ try {
424
+ fetchResult = await params.fetch({
425
+ sinceMs: windowStartMs,
426
+ sinceRowid,
427
+ limit: cfg.perRunLimit
428
+ });
429
+ } catch (err) {
430
+ params.warn?.(`imessage catchup: fetch failed: ${String(err)}`);
431
+ return summary;
432
+ }
433
+ if (!fetchResult.resolved) {
434
+ params.warn?.(`imessage catchup: fetch returned unresolved result`);
435
+ return summary;
436
+ }
437
+ summary.querySucceeded = true;
438
+ summary.fullyCaughtUp = fetchResult.fullyCaughtUp !== false;
439
+ summary.fetchedCount = fetchResult.rows.length;
440
+ const rows = fetchResult.rows.toSorted((a, b) => a.rowid - b.rowid);
441
+ const failureRetries = { ...cursor?.failureRetries };
442
+ const cursorBeforeMs = cursor?.lastSeenMs ?? windowStartMs;
443
+ const cursorBeforeRowid = cursor?.lastSeenRowid ?? 0;
444
+ let highWatermarkMs = cursorBeforeMs;
445
+ let highWatermarkRowid = cursorBeforeRowid;
446
+ let earliestHeldFailureRow = null;
447
+ for (const row of rows) {
448
+ if (row.rowid <= sinceRowid) {
449
+ summary.skippedPreCursor += 1;
450
+ continue;
451
+ }
452
+ if (row.date < ageBoundMs) {
453
+ summary.skippedPreCursor += 1;
454
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
455
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
456
+ continue;
457
+ }
458
+ if (row.isFromMe) {
459
+ try {
460
+ await params.observeSkippedFromMe?.(row);
461
+ } catch (err) {
462
+ params.warn?.(`imessage catchup: from-me observer failed for guid=${row.guid}: ${String(err)}`);
463
+ }
464
+ summary.skippedFromMe += 1;
465
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
466
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
467
+ continue;
468
+ }
469
+ const priorCount = failureRetries[row.guid] ?? 0;
470
+ if (priorCount >= cfg.maxFailureRetries) {
471
+ summary.skippedGivenUp += 1;
472
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
473
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
474
+ continue;
475
+ }
476
+ let dispatched;
477
+ try {
478
+ dispatched = await params.dispatch(row);
479
+ } catch (err) {
480
+ params.warn?.(`imessage catchup: dispatch threw for guid=${row.guid}: ${String(err)}`);
481
+ dispatched = { ok: false };
482
+ }
483
+ if (dispatched.ok) {
484
+ summary.replayed += 1;
485
+ delete failureRetries[row.guid];
486
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
487
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
488
+ continue;
489
+ }
490
+ const nextCount = priorCount + 1;
491
+ failureRetries[row.guid] = nextCount;
492
+ summary.failed += 1;
493
+ if (nextCount >= cfg.maxFailureRetries) {
494
+ summary.givenUp += 1;
495
+ params.warn?.(`imessage catchup: giving up on guid=${row.guid} after ${nextCount} failures; advancing cursor past it`);
496
+ highWatermarkMs = Math.max(highWatermarkMs, row.date);
497
+ highWatermarkRowid = Math.max(highWatermarkRowid, row.rowid);
498
+ continue;
499
+ }
500
+ if (earliestHeldFailureRow === null || row.rowid < earliestHeldFailureRow.rowid) earliestHeldFailureRow = row;
501
+ }
502
+ if (earliestHeldFailureRow === null) {
503
+ if (typeof fetchResult.highWatermarkMs === "number") highWatermarkMs = Math.max(highWatermarkMs, fetchResult.highWatermarkMs);
504
+ if (typeof fetchResult.highWatermarkRowid === "number") highWatermarkRowid = Math.max(highWatermarkRowid, fetchResult.highWatermarkRowid);
505
+ }
506
+ let lastSeenMs;
507
+ let lastSeenRowid;
508
+ if (earliestHeldFailureRow !== null) {
509
+ lastSeenMs = Math.max(cursorBeforeMs, earliestHeldFailureRow.date - 1);
510
+ lastSeenRowid = Math.max(cursorBeforeRowid, earliestHeldFailureRow.rowid - 1);
511
+ } else {
512
+ lastSeenMs = highWatermarkMs;
513
+ lastSeenRowid = highWatermarkRowid;
514
+ }
515
+ const capped = capFailureRetriesMap(failureRetries);
516
+ summary.cursorAfter = {
517
+ lastSeenMs,
518
+ lastSeenRowid
519
+ };
520
+ await saveIMessageCatchupCursor(params.accountId, {
521
+ lastSeenMs,
522
+ lastSeenRowid,
523
+ failureRetries: capped
524
+ }, { allowCursorRewindForRetries: earliestHeldFailureRow !== null });
525
+ 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}`);
526
+ return summary;
527
+ }
528
+ //#endregion
267
529
  //#region extensions/imessage/src/monitor/strip-imsg-length-prefixed-text.ts
268
530
  const utf8Decoder = new TextDecoder();
269
531
  function readVarint(buf, start) {
@@ -515,7 +777,7 @@ function combineIMessagePayloads(payloads) {
515
777
  const first = expectDefined(payloads[0], "first iMessage payload to coalesce");
516
778
  if (payloads.length === 1) return first;
517
779
  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;
780
+ const boundedPayloads = payloads.length > MAX_COALESCED_ENTRIES ? [...payloads.slice(0, 9), last] : payloads;
519
781
  const seenTexts = /* @__PURE__ */ new Set();
520
782
  const textParts = [];
521
783
  for (const payload of boundedPayloads) {
@@ -698,7 +960,8 @@ async function deliverIMessageReply(params) {
698
960
  accountId
699
961
  });
700
962
  const chunkMode = resolveChunkMode(cfg, "imessage", accountId);
701
- const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables$1(sanitizeOutboundText(payload.text ?? ""), tableMode) });
963
+ const rawText = sanitizeOutboundText(payload.text ?? "");
964
+ const reply = resolveSendableOutboundReplyParts(payload, { text: convertMarkdownTables$1(rawText, tableMode) });
702
965
  const accepted = [];
703
966
  const sendAccepted = async (text, mediaUrl) => {
704
967
  const sent = await sendMessageIMessage(target, text, {
@@ -760,7 +1023,8 @@ async function deliverIMessageReply(params) {
760
1023
  }
761
1024
  function createIMessageEchoCachingSend(params) {
762
1025
  return async (target, text, opts) => {
763
- const sent = await sendMessageIMessage(target, sanitizeOutboundText(text), opts);
1026
+ const sanitizedText = sanitizeOutboundText(text);
1027
+ const sent = await sendMessageIMessage(target, sanitizedText, opts);
764
1028
  const scope = `${params.accountId ?? opts.accountId ?? ""}:${target}`;
765
1029
  const echoText = sent.echoText ?? (sent.sentText || void 0);
766
1030
  params.sentMessageCache?.remember(scope, {
@@ -975,8 +1239,8 @@ function warnGroupAllowlistDropPerChatOnce(params) {
975
1239
  }
976
1240
  //#endregion
977
1241
  //#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;
1242
+ const IMESSAGE_STALE_INBOUND_THRESHOLD_MS = 9e5;
1243
+ const IMESSAGE_RECOVERY_MAX_AGE_MS = 72e5;
980
1244
  /**
981
1245
  * Age fence: true when the message's own send date is materially older than
982
1246
  * now, i.e. stale backlog rather than a live message. Fails open (returns
@@ -1720,6 +1984,7 @@ async function buildIMessageInboundContext(params) {
1720
1984
  const directService = resolveIMessageDirectChatService(params.accountService, decision.chatGuid) ?? "auto";
1721
1985
  const imessageTo = decision.isGroup ? chatTarget || `imessage:${decision.sender}` : `${directService}:${decision.sender}`;
1722
1986
  const imessageFrom = decision.isGroup ? `imessage:group:${chatId ?? "unknown"}` : imessageTo;
1987
+ const replyTarget = decision.isGroup ? imessageTo : chatId != null ? `chat_id:${chatId}` : decision.chatGuid ? `chat_guid:${decision.chatGuid}` : imessageTo;
1723
1988
  const inboundHistory = !decision.isGroup && params.dmHistory?.inboundHistory ? params.dmHistory.inboundHistory : decision.isGroup && decision.historyKey && params.historyLimit > 0 ? createChannelHistoryWindow({ historyMap: params.groupHistories }).buildInboundHistory({
1724
1989
  historyKey: decision.historyKey,
1725
1990
  limit: params.historyLimit
@@ -1747,7 +2012,8 @@ async function buildIMessageInboundContext(params) {
1747
2012
  from: imessageFrom,
1748
2013
  sender: {
1749
2014
  id: decision.sender,
1750
- name: decision.senderNormalized
2015
+ name: decision.senderNormalized,
2016
+ isSelf: params.message.is_from_me === true
1751
2017
  },
1752
2018
  conversation: {
1753
2019
  kind: decision.isGroup ? "group" : "direct",
@@ -1764,7 +2030,7 @@ async function buildIMessageInboundContext(params) {
1764
2030
  accountId: decision.route.accountId,
1765
2031
  routeSessionKey: decision.route.sessionKey
1766
2032
  },
1767
- reply: { to: imessageTo },
2033
+ reply: { to: replyTarget },
1768
2034
  message: {
1769
2035
  body: combinedBody,
1770
2036
  bodyForAgent: decision.agentBodyText ?? decision.bodyText,
@@ -1922,7 +2188,7 @@ function createIMessageDurableIngress(options) {
1922
2188
  },
1923
2189
  pollIntervalMs: IMESSAGE_INGRESS_DRAIN_INTERVAL_MS,
1924
2190
  retention: {
1925
- completedTtlMs: 14400 * 1e3,
2191
+ completedTtlMs: 144e5,
1926
2192
  completedMaxEntries: 1e4,
1927
2193
  failedMaxEntries: 1e3
1928
2194
  },
@@ -2049,18 +2315,18 @@ async function readPinnedAttachmentBytes(handle, maxBytes) {
2049
2315
  let totalBytes = 0;
2050
2316
  while (totalBytes <= maxBytes) {
2051
2317
  const remaining = maxBytes + 1 - totalBytes;
2052
- const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
2318
+ const chunk = Buffer.allocUnsafe(Math.min(65536, remaining));
2053
2319
  const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);
2054
2320
  if (bytesRead === 0) return Buffer.concat(chunks, totalBytes);
2055
2321
  chunks.push(chunk.subarray(0, bytesRead));
2056
2322
  totalBytes += bytesRead;
2057
2323
  }
2058
- throw new Error(`attachment exceeds ${Math.round(maxBytes / (1024 * 1024))}MB limit`);
2324
+ throw new Error(`attachment exceeds ${Math.round(maxBytes / 1048576)}MB limit`);
2059
2325
  }
2060
2326
  async function readAttachmentBuffer(params) {
2061
2327
  const opened = await (params.deps.openLocalFileSafely ?? openLocalFileSafely)({ filePath: params.attachmentPath });
2062
2328
  try {
2063
- if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / (1024 * 1024))}MB limit`);
2329
+ if (opened.stat.size > params.maxBytes) throw new Error(`attachment exceeds ${Math.round(params.maxBytes / 1048576)}MB limit`);
2064
2330
  await assertAllowedCanonicalAttachmentPath({
2065
2331
  canonicalPath: opened.realPath,
2066
2332
  allowedRoots: params.allowedRoots
@@ -2440,7 +2706,7 @@ function sanitizeIMessageWatchErrorPayload(payload) {
2440
2706
  if (typeof payload.code === "number" && Number.isFinite(payload.code)) safe.code = payload.code;
2441
2707
  if (typeof payload.message === "string") {
2442
2708
  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;
2709
+ if (sanitizedMessage) safe.message = sanitizedMessage.length > MAX_WATCH_ERROR_MESSAGE_CHARS ? `${truncateUtf16Safe(sanitizedMessage, 199)}…` : sanitizedMessage;
2444
2710
  }
2445
2711
  return safe;
2446
2712
  }
@@ -2452,7 +2718,7 @@ const CHANNEL_APPROVAL_GATEWAY_RUNTIME_CONTEXT_CAPABILITY = "approval.gateway";
2452
2718
  const APPROVAL_REACTION_POLL_INTERVAL_MS = 2e3;
2453
2719
  const APPROVAL_REACTION_DISCOVERY_INTERVAL_MS = 6e4;
2454
2720
  const IMESSAGE_TYPING_KEEPALIVE_INTERVAL_MS = 8e3;
2455
- const IMESSAGE_TYPING_KEEPALIVE_MAX_DURATION_MS = 10 * 6e4;
2721
+ const IMESSAGE_TYPING_KEEPALIVE_MAX_DURATION_MS = 6e5;
2456
2722
  function resolveConfiguredIMessageTypingMode(cfg, agentId) {
2457
2723
  return resolveAgentConfig(cfg, agentId)?.typingMode ?? cfg.agents?.defaults?.typingMode;
2458
2724
  }
@@ -2836,7 +3102,7 @@ async function monitorIMessageProvider(opts = {}) {
2836
3102
  const conversationKey = chatId != null ? `group:${chatId}` : `dm:${senderForKey}`;
2837
3103
  const rateLimitKey = `${accountInfo.accountId}:${conversationKey}`;
2838
3104
  if (decision.kind === "drop") {
2839
- if (decision.reason === "echo" || decision.reason === "self-chat echo" || decision.reason === "reflected assistant content") loopRateLimiter.record(rateLimitKey);
3105
+ if (decision.reason === "echo" || decision.reason === "reflected assistant content") loopRateLimiter.record(rateLimitKey);
2840
3106
  const diagnostic = describeIMessageInboundDropDiagnostic({
2841
3107
  accountId: accountInfo.accountId,
2842
3108
  reason: decision.reason,
@@ -1,5 +1,5 @@
1
- import { p as resolveLocalIMessageChatDbPath, u as expandIMessageUserPath } from "./accounts-DIpGOIiN.js";
2
- import { g as isIMessageEmailChatIdentifier, h as chatContextFromIMessageTarget, l as resolveIMessageCachedResourceBinding, u as resolveIMessageMessageId } from "./monitor-reply-cache-BdeUQaHO.js";
1
+ import { p as resolveLocalIMessageChatDbPath, u as expandIMessageUserPath } from "./accounts-CCirBEIm.js";
2
+ import { d as resolveIMessageCachedResourceBinding, f as resolveIMessageMessageId, g as isIMessageEmailChatIdentifier, h as chatContextFromIMessageTarget } from "./sanitize-outbound-CN3Hxbjy.js";
3
3
  import path from "node:path";
4
4
  import { normalizeScpRemoteHost } from "openclaw/plugin-sdk/host-runtime";
5
5
  import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
@@ -9,8 +9,8 @@ import { runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
9
9
  import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
10
10
  import { sanitizeTempFileName } from "openclaw/plugin-sdk/temp-path";
11
11
  //#region extensions/imessage/src/cli-output.ts
12
- const IMESSAGE_CLI_STDOUT_MAX_BYTES = 8 * 1024 * 1024;
13
- const IMESSAGE_CLI_STDERR_TAIL_BYTES = 64 * 1024;
12
+ const IMESSAGE_CLI_STDOUT_MAX_BYTES = 8388608;
13
+ const IMESSAGE_CLI_STDERR_TAIL_BYTES = 65536;
14
14
  function parseLastJsonObject(stdout) {
15
15
  const last = stdout.split(/\r?\n/u).findLast((line) => line.trim().length > 0)?.trim();
16
16
  if (!last) return null;
@@ -23,12 +23,13 @@ function parseLastJsonObject(stdout) {
23
23
  }
24
24
  async function runIMessageCliJsonCommand(params) {
25
25
  const dbPath = params.dbPath?.trim();
26
- const result = await runCommandWithTimeout([
26
+ const argv = [
27
27
  expandIMessageUserPath(params.cliPath),
28
28
  ...params.args,
29
29
  ...dbPath ? ["--db", dbPath] : [],
30
30
  "--json"
31
- ], {
31
+ ];
32
+ const result = await runCommandWithTimeout(argv, {
32
33
  killProcessTree: true,
33
34
  maxOutputBytes: {
34
35
  stdout: IMESSAGE_CLI_STDOUT_MAX_BYTES,
@@ -213,8 +214,8 @@ async function runChecked(run, label, argv, options) {
213
214
  const result = await run(argv, {
214
215
  killProcessTree: true,
215
216
  maxOutputBytes: {
216
- stdout: 4 * 1024,
217
- stderr: 64 * 1024
217
+ stdout: 4096,
218
+ stderr: 65536
218
219
  },
219
220
  outputCapture: {
220
221
  stdout: "head",
@@ -0,0 +1,8 @@
1
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
2
+ //#region extensions/imessage/src/runtime.ts
3
+ const { getRuntime: getIMessageRuntime, setRuntime: setIMessageRuntime, tryGetRuntime: getOptionalIMessageRuntime } = createPluginRuntimeStore({
4
+ pluginId: "imessage",
5
+ errorMessage: "iMessage runtime not initialized"
6
+ });
7
+ //#endregion
8
+ export { getOptionalIMessageRuntime as n, setIMessageRuntime as r, getIMessageRuntime as t };
@@ -1,12 +1,12 @@
1
- import { o as resolveIMessageAccount } from "./accounts-DIpGOIiN.js";
2
- import { m as setIMessageRuntime } from "./monitor-reply-cache-BdeUQaHO.js";
3
- import { c as normalizeIMessageMessagingTarget, d as imessageMessageActions, f as probeIMessage, n as resolveIMessageGroupToolPolicy, s as looksLikeIMessageTargetId, t as resolveIMessageGroupRequireMention } from "./group-policy-BipELT2x.js";
4
- import { n as IMessageConfigSchema } from "./config-schema-DV0KP5nC.js";
5
- import { t as sendMessageIMessage } from "./send-DVQY6M5M.js";
6
- import { t as monitorIMessageProvider } from "./monitor-DRD_Dnp9.js";
1
+ import { o as resolveIMessageAccount } from "./accounts-CCirBEIm.js";
2
+ import { c as normalizeIMessageMessagingTarget, d as imessageMessageActions, f as probeIMessage, n as resolveIMessageGroupToolPolicy, s as looksLikeIMessageTargetId, t as resolveIMessageGroupRequireMention } from "./group-policy-BWqG2esy.js";
3
+ import { r as setIMessageRuntime } from "./runtime-Cza4CY5T.js";
4
+ import { n as IMessageConfigSchema } from "./config-schema-DuGbA8Kp.js";
5
+ import { t as sendMessageIMessage } from "./send-v0blZfaI.js";
6
+ import { t as monitorIMessageProvider } from "./monitor-AmSj6QIH.js";
7
7
  import { formatTrimmedAllowFromEntries } from "openclaw/plugin-sdk/channel-config-helpers";
8
8
  import { buildComputedAccountStatusSnapshot, collectStatusIssuesFromLastError } from "openclaw/plugin-sdk/status-helpers";
9
- import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime";
9
+ import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/account-helpers";
10
10
  import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
11
11
  import { DEFAULT_ACCOUNT_ID, getChatChannelMeta } from "openclaw/plugin-sdk/core";
12
12
  import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";