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