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