@openclaw/imessage 2026.8.1-beta.2 → 2026.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{accounts-D00Njxg2.js → accounts-CCirBEIm.js} +4 -5
- package/dist/{actions.runtime-DTFKDN5p.js → actions.runtime-CAl3WcqD.js} +2 -3
- package/dist/api.js +6 -5
- package/dist/{approval-handler.runtime-BtLs4gho.js → approval-handler.runtime-UBd1j2Sg.js} +15 -9
- package/dist/approval-reactions-LL5yn1Rc.js +562 -0
- package/dist/{channel-lppmI51h.js → channel-DNI9DKGN.js} +26 -21
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-5wpCKQb7.js → channel.runtime-HSkrmWfW.js} +58 -11
- 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-DF9xSdik.js → group-policy-Dvc-BcWo.js} +19 -651
- package/dist/{media-contract-BEgdvYhW.js → media-contract-D-FhGE98.js} +1 -1
- package/dist/media-contract-api.js +1 -1
- package/dist/{message-tool-api-CPDfJJoE.js → message-tool-api-CvIgJpOo.js} +7 -4
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-DnxJ8pW9.js → monitor-BywJn1QW.js} +343 -111
- package/dist/{question-reactions-DVe50UhB.js → question-reactions-viP7ZcyG.js} +2 -1
- package/dist/reaction-context-BAYI7pz0.js +105 -0
- package/dist/{remote-file-DLwO9ved.js → remote-file-6mCj2ZBy.js} +7 -3
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/runtime-Cza4CY5T.js +8 -0
- package/dist/runtime-api.js +10 -9
- package/dist/{sanitize-outbound-CoTZLXMw.js → sanitize-outbound-Du-r1lNQ.js} +345 -58
- package/dist/{send-BUXW76tw.js → send-mgWPKqSV.js} +147 -26
- package/dist/state-contract-4TK_cDDb.js +56 -0
- package/dist/state-migrations-SFdKp3GQ.js +277 -0
- package/node_modules/typebox/build/format/date.d.mts +1 -1
- package/node_modules/typebox/build/format/date.mjs +1 -1
- package/node_modules/typebox/build/format/date_time.d.mts +2 -2
- package/node_modules/typebox/build/format/date_time.mjs +3 -3
- package/node_modules/typebox/build/format/email.d.mts +1 -1
- package/node_modules/typebox/build/format/email.mjs +2 -2
- package/node_modules/typebox/build/format/hostname.mjs +2 -10
- package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_email.mjs +3 -3
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
- package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
- package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
- package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +23 -0
- package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
- package/node_modules/typebox/build/format/idna/index.mjs +2 -0
- package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
- package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
- package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
- package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
- package/node_modules/typebox/build/format/ipv4.mjs +2 -32
- package/node_modules/typebox/build/format/ipv6.mjs +2 -61
- package/node_modules/typebox/build/format/iri.d.mts +2 -2
- package/node_modules/typebox/build/format/iri.mjs +26 -8
- package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
- package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
- package/node_modules/typebox/build/format/regex.d.mts +1 -2
- package/node_modules/typebox/build/format/regex.mjs +2 -6
- package/node_modules/typebox/build/format/time.d.mts +2 -2
- package/node_modules/typebox/build/format/time.mjs +19 -14
- package/node_modules/typebox/build/format/uri.d.mts +1 -1
- package/node_modules/typebox/build/format/uri.mjs +3 -130
- package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
- package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
- package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
- package/node_modules/typebox/build/format/uri_template.mjs +4 -5
- package/node_modules/typebox/build/format/url.d.mts +3 -3
- package/node_modules/typebox/build/format/url.mjs +4 -5
- package/node_modules/typebox/build/guard/emit.d.mts +3 -1
- package/node_modules/typebox/build/guard/emit.mjs +12 -5
- package/node_modules/typebox/build/guard/guard.d.mts +8 -2
- package/node_modules/typebox/build/guard/guard.mjs +25 -4
- package/node_modules/typebox/build/guard/string.d.mts +2 -2
- package/node_modules/typebox/build/guard/string.mjs +7 -7
- package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
- package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
- package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
- package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
- package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
- package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
- package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
- package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
- package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
- package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
- package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
- package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
- package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
- package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
- package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
- package/node_modules/typebox/build/schema/types/index.mjs +0 -2
- package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
- package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
- package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
- package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
- package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
- package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
- package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
- package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
- package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
- package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
- package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
- package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
- package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
- package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
- package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
- package/node_modules/typebox/build/type/script/parser.mjs +27 -23
- package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
- package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
- package/node_modules/typebox/package.json +27 -27
- package/node_modules/typebox/readme.md +79 -82
- package/openclaw.plugin.json +0 -6
- package/package.json +5 -5
- package/dist/catchup-D32oYNxR.js +0 -294
- package/dist/monitor-reply-cache-DKlYGd5x.js +0 -348
- package/dist/persisted-echo-cache-BPWGUK-L.js +0 -143
- package/node_modules/typebox/build/format/_idna.d.mts +0 -2
- package/node_modules/typebox/build/format/_idna.mjs +0 -246
- package/node_modules/typebox/build/format/_puny.d.mts +0 -1
- package/node_modules/typebox/build/format/_puny.mjs +0 -77
- package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
- package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
- package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
- package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { i as
|
|
1
|
+
import { d as resolveIMessageChatDbLookupPath, l as resolveIMessageRemoteHost, n as hasExclusiveIMessageLocalDatabase, o as resolveIMessageAccount } from "./accounts-CCirBEIm.js";
|
|
2
|
+
import { l as parseIMessageTarget, n as formatIMessageChatTarget, s as normalizeIMessageHandle } from "./message-tool-api-CvIgJpOo.js";
|
|
3
|
+
import { a as IMessageRpcRequestError, h as chatContextFromIMessageTarget, n as sanitizeIMessageFinalOutboundText, o as createIMessageRpcClient, t as protectIMessageFencedRoleMarkers, u as rememberIMessageReplyCache, v as resolveIMessageDirectChatService } from "./sanitize-outbound-Du-r1lNQ.js";
|
|
4
|
+
import { l as getIMessageApprovalApprovers, u as imessageApprovalAuth } from "./group-policy-Dvc-BcWo.js";
|
|
5
|
+
import { n as getOptionalIMessageRuntime, t as getIMessageRuntime } from "./runtime-Cza4CY5T.js";
|
|
6
|
+
import { c as IMESSAGE_SENT_ECHOES_TTL_MS, d as resolveIMessageEchoMediaKey, p as resolveIMessageSentEchoEntryKey, s as IMESSAGE_SENT_ECHOES_NAMESPACE } from "./state-contract-4TK_cDDb.js";
|
|
7
|
+
import { d as normalizeConversationKey, f as normalizeIMessageGuid, i as registerIMessageApprovalReactionTarget, l as enumerateApprovalTargetKeys, s as buildIMessageApprovalConversationKeyForInbound, u as enumerateConversationKeyForms } from "./approval-reactions-LL5yn1Rc.js";
|
|
8
|
+
import { i as runIMessageCliJsonCommand, r as resolveAuthorizedIMessageReplyReference, t as withIMessageRemoteFile } from "./remote-file-6mCj2ZBy.js";
|
|
8
9
|
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
9
10
|
import { createLazyRuntimeSurface } from "openclaw/plugin-sdk/lazy-runtime";
|
|
10
11
|
import { accessSync, constants } from "node:fs";
|
|
11
12
|
import { basename } from "node:path";
|
|
12
13
|
import { asOptionalRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
|
-
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
14
|
+
import { logVerbose, sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
15
|
+
import { extractOriginalFilename, kindFromMime, resolveOutboundAttachmentFromUrl } from "openclaw/plugin-sdk/media-runtime";
|
|
14
16
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
15
17
|
import { PlatformMessageNotDispatchedError, isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
|
|
16
|
-
import { createApprovalReactionTargetStore, listApprovalReactionBindings } from "openclaw/plugin-sdk/approval-reaction-runtime";
|
|
17
|
-
import { createPluginStateErrorReporter } from "openclaw/plugin-sdk/plugin-state-runtime";
|
|
18
|
-
import { extractOriginalFilename, kindFromMime, resolveOutboundAttachmentFromUrl } from "openclaw/plugin-sdk/media-runtime";
|
|
18
|
+
import { addApprovalReactionHintToText, createApprovalReactionTargetStore, listApprovalReactionBindings } from "openclaw/plugin-sdk/approval-reaction-runtime";
|
|
19
19
|
import { convertMarkdownTables, stripInlineDirectiveTagsForDelivery } from "openclaw/plugin-sdk/text-chunking";
|
|
20
20
|
import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
21
21
|
import { openNodeSqliteDatabase } from "openclaw/plugin-sdk/sqlite-runtime";
|
|
22
|
+
import { createPluginStateErrorReporter } from "openclaw/plugin-sdk/plugin-state-runtime";
|
|
22
23
|
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
|
|
23
24
|
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
|
|
24
25
|
import { resolvePreferredOpenClawTmpDir, withTempWorkspace } from "openclaw/plugin-sdk/temp-path";
|
|
@@ -452,6 +453,127 @@ const iMessageApprovalPollTargets = {
|
|
|
452
453
|
clearForTest: clearIMessageApprovalPollTargetsForTest
|
|
453
454
|
};
|
|
454
455
|
//#endregion
|
|
456
|
+
//#region extensions/imessage/src/monitor/echo-text-corruption.ts
|
|
457
|
+
function isLeadingEchoTextCorruptionMarker(code) {
|
|
458
|
+
return code === 0 || code === 65279 || code === 65533 || code === 65534 || code === 65535;
|
|
459
|
+
}
|
|
460
|
+
function stripLeadingEchoTextCorruptionMarkers(text) {
|
|
461
|
+
let offset = 0;
|
|
462
|
+
while (offset < text.length && isLeadingEchoTextCorruptionMarker(text.charCodeAt(offset))) offset += 1;
|
|
463
|
+
return offset === 0 ? text : text.slice(offset);
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region extensions/imessage/src/monitor/persisted-echo-cache.ts
|
|
467
|
+
function normalizeText(text) {
|
|
468
|
+
if (!text) return;
|
|
469
|
+
return stripLeadingEchoTextCorruptionMarkers(text.replace(/\r\n?/g, "\n").trim()).trim() || void 0;
|
|
470
|
+
}
|
|
471
|
+
function normalizeMessageId(messageId) {
|
|
472
|
+
const normalized = messageId?.trim();
|
|
473
|
+
if (!normalized || normalized === "ok" || normalized === "unknown") return;
|
|
474
|
+
return normalized;
|
|
475
|
+
}
|
|
476
|
+
let mirror = null;
|
|
477
|
+
let persistenceFailureLogged = false;
|
|
478
|
+
function reportFailure(scope, err) {
|
|
479
|
+
if (persistenceFailureLogged) return;
|
|
480
|
+
persistenceFailureLogged = true;
|
|
481
|
+
logVerbose(`imessage echo-cache: ${scope} disabled after first failure: ${String(err)}`);
|
|
482
|
+
}
|
|
483
|
+
function normalizeMedia(media) {
|
|
484
|
+
if (!resolveIMessageEchoMediaKey(media)) return;
|
|
485
|
+
const contentType = media?.contentType?.trim().toLowerCase() || void 0;
|
|
486
|
+
const kind = media?.kind ?? void 0;
|
|
487
|
+
return {
|
|
488
|
+
...kind ? { kind } : {},
|
|
489
|
+
...contentType ? { contentType } : {}
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function openPersistedEchoStore() {
|
|
493
|
+
return getIMessageRuntime().state.openSyncKeyedStore({
|
|
494
|
+
namespace: IMESSAGE_SENT_ECHOES_NAMESPACE,
|
|
495
|
+
maxEntries: 256
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
function remainingTtlMs(timestamp) {
|
|
499
|
+
const remaining = IMESSAGE_SENT_ECHOES_TTL_MS - Math.max(0, Date.now() - timestamp);
|
|
500
|
+
return remaining > 0 ? remaining : void 0;
|
|
501
|
+
}
|
|
502
|
+
function resolveEntryTtlMs(entry, ttlMs) {
|
|
503
|
+
if (typeof ttlMs === "number" && Number.isFinite(ttlMs) && ttlMs > 0) return ttlMs;
|
|
504
|
+
return remainingTtlMs(entry.timestamp);
|
|
505
|
+
}
|
|
506
|
+
function isLiveEntry(entry, now = Date.now()) {
|
|
507
|
+
const cutoff = now - IMESSAGE_SENT_ECHOES_TTL_MS;
|
|
508
|
+
return entry.timestamp >= cutoff && (entry.expiresAt == null || entry.expiresAt > now);
|
|
509
|
+
}
|
|
510
|
+
function loadMirrorFromStore() {
|
|
511
|
+
try {
|
|
512
|
+
mirror = openPersistedEchoStore().entries().map(({ value }) => value).filter((entry) => isLiveEntry(entry)).toSorted((a, b) => a.timestamp - b.timestamp).slice(-256);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
reportFailure("read", err);
|
|
515
|
+
mirror = [];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function readRecentEntries() {
|
|
519
|
+
loadMirrorFromStore();
|
|
520
|
+
return mirror ?? [];
|
|
521
|
+
}
|
|
522
|
+
function persistEntry(entry, ttlMs) {
|
|
523
|
+
const effectiveTtlMs = resolveEntryTtlMs(entry, ttlMs);
|
|
524
|
+
if (!effectiveTtlMs) return;
|
|
525
|
+
const key = resolveIMessageSentEchoEntryKey(entry);
|
|
526
|
+
try {
|
|
527
|
+
openPersistedEchoStore().register(key, entry, { ttlMs: effectiveTtlMs });
|
|
528
|
+
} catch (err) {
|
|
529
|
+
reportFailure("write", err);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
return key;
|
|
533
|
+
}
|
|
534
|
+
function rememberPersistedIMessageEcho(params) {
|
|
535
|
+
const text = normalizeText(params.text);
|
|
536
|
+
const media = normalizeMedia(params.media);
|
|
537
|
+
const messageId = normalizeMessageId(params.messageId);
|
|
538
|
+
const entry = {
|
|
539
|
+
scope: params.scope,
|
|
540
|
+
timestamp: Date.now(),
|
|
541
|
+
...text ? { text } : {},
|
|
542
|
+
...media ? { media } : {},
|
|
543
|
+
...messageId ? { messageId } : {},
|
|
544
|
+
...params.pending ? { pending: true } : {}
|
|
545
|
+
};
|
|
546
|
+
if (typeof params.ttlMs === "number" && Number.isFinite(params.ttlMs) && params.ttlMs > 0) entry.expiresAt = entry.timestamp + params.ttlMs;
|
|
547
|
+
if (!entry.text && !entry.media && !entry.messageId) return;
|
|
548
|
+
loadMirrorFromStore();
|
|
549
|
+
const key = persistEntry(entry, params.ttlMs);
|
|
550
|
+
mirror = [...mirror ?? [], entry].filter((candidate) => isLiveEntry(candidate)).slice(-256);
|
|
551
|
+
return key;
|
|
552
|
+
}
|
|
553
|
+
function forgetPersistedIMessageEchoKey(key) {
|
|
554
|
+
if (!key) return;
|
|
555
|
+
try {
|
|
556
|
+
openPersistedEchoStore().delete(key);
|
|
557
|
+
} catch (err) {
|
|
558
|
+
reportFailure("delete", err);
|
|
559
|
+
}
|
|
560
|
+
mirror = (mirror ?? []).filter((entry) => resolveIMessageSentEchoEntryKey(entry) !== key);
|
|
561
|
+
}
|
|
562
|
+
function hasPersistedIMessageEcho(params) {
|
|
563
|
+
const text = normalizeText(params.text);
|
|
564
|
+
const mediaKey = resolveIMessageEchoMediaKey(params.media);
|
|
565
|
+
const messageId = normalizeMessageId(params.messageId);
|
|
566
|
+
if (!text && !mediaKey && !messageId) return false;
|
|
567
|
+
for (const entry of readRecentEntries()) {
|
|
568
|
+
if (entry.scope !== params.scope) continue;
|
|
569
|
+
if (messageId && entry.messageId === messageId) return true;
|
|
570
|
+
const hasConflictingMessageIds = Boolean(messageId && entry.messageId && messageId !== entry.messageId);
|
|
571
|
+
if (text && (!hasConflictingMessageIds || params.skipIdShortCircuit) && entry.text === text && (!entry.pending || params.includePendingText)) return true;
|
|
572
|
+
if (mediaKey && !hasConflictingMessageIds && resolveIMessageEchoMediaKey(entry.media) === mediaKey && (!entry.pending || params.includePendingText)) return true;
|
|
573
|
+
}
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
//#endregion
|
|
455
577
|
//#region extensions/imessage/src/send.ts
|
|
456
578
|
const MIN_PENDING_PERSISTED_ECHO_TTL_MS = 6e4;
|
|
457
579
|
const PENDING_PERSISTED_ECHO_GRACE_MS = 5e3;
|
|
@@ -598,7 +720,7 @@ async function resolveFallbackSentMessageGuid(params) {
|
|
|
598
720
|
return null;
|
|
599
721
|
}
|
|
600
722
|
function shouldRecoverApprovalPromptGuid(params) {
|
|
601
|
-
return
|
|
723
|
+
return Boolean(params.approvalPrompt && !params.filePath && !params.replyToId);
|
|
602
724
|
}
|
|
603
725
|
function canCheckSentMessageAfterRpcTimeout(params) {
|
|
604
726
|
return Boolean(params.resolveSentMessageGuidImpl) || canResolveLatestSentMessageGuidFromChatDb(params.dbPath);
|
|
@@ -686,11 +808,6 @@ function resultService(value) {
|
|
|
686
808
|
const normalized = normalizeOptionalString(value)?.toLowerCase();
|
|
687
809
|
return normalized === "imessage" || normalized === "sms" ? normalized : void 0;
|
|
688
810
|
}
|
|
689
|
-
function resultChatGuidService(value) {
|
|
690
|
-
const chatGuid = normalizeOptionalString(value);
|
|
691
|
-
if (/^imessage;/iu.test(chatGuid ?? "")) return "imessage";
|
|
692
|
-
return /^sms;/iu.test(chatGuid ?? "") ? "sms" : void 0;
|
|
693
|
-
}
|
|
694
811
|
function resolvePendingPersistedEchoTtlMs(timeoutMs) {
|
|
695
812
|
return Math.max(MIN_PENDING_PERSISTED_ECHO_TTL_MS, Math.max(0, timeoutMs) + PENDING_PERSISTED_ECHO_GRACE_MS);
|
|
696
813
|
}
|
|
@@ -886,7 +1003,10 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
886
1003
|
const pendingEchoTtlMs = resolvePendingPersistedEchoTtlMs(timeoutMs);
|
|
887
1004
|
const region = opts.region?.trim() || account.config.region?.trim() || "US";
|
|
888
1005
|
const maxBytes = typeof opts.maxBytes === "number" ? opts.maxBytes : typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : 16 * 1024 * 1024;
|
|
889
|
-
let message =
|
|
1006
|
+
let message = opts.approvalPrompt ? addApprovalReactionHintToText({
|
|
1007
|
+
text,
|
|
1008
|
+
allowedDecisions: opts.approvalPrompt.allowedDecisions
|
|
1009
|
+
}) : text;
|
|
890
1010
|
const protectedRoles = protectIMessageFencedRoleMarkers(message);
|
|
891
1011
|
message = protectedRoles.text;
|
|
892
1012
|
let filePath;
|
|
@@ -1074,7 +1194,7 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
1074
1194
|
effectiveReplyToId = void 0;
|
|
1075
1195
|
} else if (filePath || !isIMessageRpcSendTimeout(error)) throw error;
|
|
1076
1196
|
else if (!shouldRecoverApprovalPromptGuid({
|
|
1077
|
-
|
|
1197
|
+
approvalPrompt: opts.approvalPrompt,
|
|
1078
1198
|
filePath,
|
|
1079
1199
|
replyToId: resolvedReplyToId
|
|
1080
1200
|
}) || !canCheckSentMessageAfterRpcTimeout({
|
|
@@ -1105,7 +1225,7 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
1105
1225
|
resolveMessageGuidImpl: opts.resolveMessageGuidImpl
|
|
1106
1226
|
});
|
|
1107
1227
|
if (!approvalBindingMessageId && shouldRecoverApprovalPromptGuid({
|
|
1108
|
-
|
|
1228
|
+
approvalPrompt: opts.approvalPrompt,
|
|
1109
1229
|
filePath,
|
|
1110
1230
|
replyToId: effectiveReplyToId
|
|
1111
1231
|
})) approvalBindingMessageId = await resolveFallbackSentMessageGuid({
|
|
@@ -1122,7 +1242,7 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
1122
1242
|
messageId: resolvedId ?? void 0
|
|
1123
1243
|
});
|
|
1124
1244
|
const providerChatGuid = normalizeOptionalString(result.chat_guid) ?? normalizeOptionalString(result.chatGuid);
|
|
1125
|
-
const confirmedService = resultService(result.service) ??
|
|
1245
|
+
const confirmedService = resolveIMessageDirectChatService(resultService(result.service) ?? service, providerChatGuid);
|
|
1126
1246
|
if (resolvedId && isConcreteIMessageMessageId(resolvedId)) {
|
|
1127
1247
|
const chatContext = chatContextFromIMessageTarget(target, confirmedService ?? service);
|
|
1128
1248
|
rememberIMessageReplyCache({
|
|
@@ -1134,7 +1254,7 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
1134
1254
|
isFromMe: true
|
|
1135
1255
|
});
|
|
1136
1256
|
}
|
|
1137
|
-
if (message && approvalBindingMessageId && opts.
|
|
1257
|
+
if (message && approvalBindingMessageId && opts.approvalPrompt) {
|
|
1138
1258
|
const handleForKey = target.kind === "handle" ? normalizeIMessageHandle(target.to) : void 0;
|
|
1139
1259
|
const conversation = {
|
|
1140
1260
|
...target.kind === "chat_guid" ? { chatGuid: target.chatGuid } : {},
|
|
@@ -1142,12 +1262,13 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
1142
1262
|
...target.kind === "chat_id" ? { chatId: target.chatId } : {},
|
|
1143
1263
|
...handleForKey ? { handle: handleForKey } : {}
|
|
1144
1264
|
};
|
|
1145
|
-
|
|
1265
|
+
registerIMessageApprovalReactionTarget({
|
|
1146
1266
|
accountId: account.accountId,
|
|
1147
1267
|
conversation,
|
|
1148
1268
|
messageId: approvalBindingMessageId,
|
|
1149
|
-
|
|
1150
|
-
approvalKind: opts.approvalKind
|
|
1269
|
+
approvalId: opts.approvalPrompt.approvalId,
|
|
1270
|
+
approvalKind: opts.approvalPrompt.approvalKind,
|
|
1271
|
+
allowedDecisions: opts.approvalPrompt.allowedDecisions
|
|
1151
1272
|
});
|
|
1152
1273
|
}
|
|
1153
1274
|
return {
|
|
@@ -1173,4 +1294,4 @@ async function sendMessageIMessage(to, text, opts) {
|
|
|
1173
1294
|
}
|
|
1174
1295
|
}
|
|
1175
1296
|
//#endregion
|
|
1176
|
-
export {
|
|
1297
|
+
export { iMessageApprovalPollTargets as a, iMessageApprovalControlBindings as c, buildApprovalPollOptions as i, hasPersistedIMessageEcho as n, mapSentPollOptionsToDecisions as o, stripLeadingEchoTextCorruptionMarkers as r, maybeResolveIMessageApprovalPollVote as s, sendMessageIMessage as t };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
//#region extensions/imessage/src/state-contract.ts
|
|
3
|
+
const IMESSAGE_REPLY_CACHE_NAMESPACE = "imessage.reply-cache";
|
|
4
|
+
const IMESSAGE_REPLY_CACHE_MAX_ENTRIES = 2e3;
|
|
5
|
+
const IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE = "imessage.reply-cache-counter";
|
|
6
|
+
const IMESSAGE_REPLY_CACHE_COUNTER_KEY = "short-id-counter";
|
|
7
|
+
const IMESSAGE_REPLY_CACHE_TTL_MS = 360 * 60 * 1e3;
|
|
8
|
+
function resolveIMessageReplyCacheEntryKey(messageId) {
|
|
9
|
+
return createHash("sha256").update(messageId, "utf8").digest("hex").slice(0, 32);
|
|
10
|
+
}
|
|
11
|
+
const MAX_FAILURE_RETRY_MAP_SIZE = 512;
|
|
12
|
+
const MAX_FAILURE_RETRY_MAP_JSON_BYTES = 48e3;
|
|
13
|
+
const textEncoder = new TextEncoder();
|
|
14
|
+
const IMESSAGE_CATCHUP_CURSOR_NAMESPACE = "imessage.catchup-cursors";
|
|
15
|
+
function resolveIMessageCatchupCursorKey(accountId) {
|
|
16
|
+
return createHash("sha256").update(accountId, "utf8").digest("hex").slice(0, 32);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Bound the retry map so a pathological storm of unique failing GUIDs
|
|
20
|
+
* cannot grow the cursor value without limit. Keeps the `maxSize` entries
|
|
21
|
+
* with the highest counts (closest to give-up) when over the bound.
|
|
22
|
+
*/
|
|
23
|
+
function capFailureRetriesMap(map, maxSize = MAX_FAILURE_RETRY_MAP_SIZE, maxBytes = MAX_FAILURE_RETRY_MAP_JSON_BYTES) {
|
|
24
|
+
const entries = Object.entries(map);
|
|
25
|
+
if (entries.length <= maxSize && textEncoder.encode(JSON.stringify(map)).byteLength <= maxBytes) return map;
|
|
26
|
+
entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
27
|
+
const capped = {};
|
|
28
|
+
for (const [guid, count] of entries.slice(0, maxSize)) {
|
|
29
|
+
capped[guid] = count;
|
|
30
|
+
if (textEncoder.encode(JSON.stringify(capped)).byteLength > maxBytes) {
|
|
31
|
+
delete capped[guid];
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return capped;
|
|
36
|
+
}
|
|
37
|
+
const IMESSAGE_SENT_ECHOES_TTL_MS = 720 * 60 * 1e3;
|
|
38
|
+
const IMESSAGE_SENT_ECHOES_NAMESPACE = "imessage.sent-echoes";
|
|
39
|
+
function resolveIMessageSentEchoEntryKey(entry) {
|
|
40
|
+
return createHash("sha256").update(JSON.stringify([
|
|
41
|
+
entry.scope,
|
|
42
|
+
entry.text ?? "",
|
|
43
|
+
resolveIMessageEchoMediaKey(entry.media) ?? "",
|
|
44
|
+
entry.messageId ?? "",
|
|
45
|
+
entry.timestamp
|
|
46
|
+
])).digest("hex").slice(0, 32);
|
|
47
|
+
}
|
|
48
|
+
function resolveIMessageEchoMediaKey(media) {
|
|
49
|
+
const contentType = media?.contentType?.trim().toLowerCase() || void 0;
|
|
50
|
+
const kind = media?.kind && media.kind !== "unknown" ? media.kind : void 0;
|
|
51
|
+
if (kind) return `kind:${kind}`;
|
|
52
|
+
const normalizedContentType = contentType?.split(";", 1)[0]?.trim();
|
|
53
|
+
return normalizedContentType ? `mime:${normalizedContentType}` : void 0;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { IMESSAGE_REPLY_CACHE_NAMESPACE as a, IMESSAGE_SENT_ECHOES_TTL_MS as c, resolveIMessageEchoMediaKey as d, resolveIMessageReplyCacheEntryKey as f, IMESSAGE_REPLY_CACHE_MAX_ENTRIES as i, capFailureRetriesMap as l, IMESSAGE_REPLY_CACHE_COUNTER_KEY as n, IMESSAGE_REPLY_CACHE_TTL_MS as o, resolveIMessageSentEchoEntryKey as p, IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE as r, IMESSAGE_SENT_ECHOES_NAMESPACE as s, IMESSAGE_CATCHUP_CURSOR_NAMESPACE as t, resolveIMessageCatchupCursorKey as u };
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { a as resolveDefaultIMessageAccountId, i as listIMessageAccountIds, o as resolveIMessageAccount } from "./accounts-CCirBEIm.js";
|
|
2
|
+
import { a as IMESSAGE_REPLY_CACHE_NAMESPACE, c as IMESSAGE_SENT_ECHOES_TTL_MS, f as resolveIMessageReplyCacheEntryKey, i as IMESSAGE_REPLY_CACHE_MAX_ENTRIES, l as capFailureRetriesMap, n as IMESSAGE_REPLY_CACHE_COUNTER_KEY, o as IMESSAGE_REPLY_CACHE_TTL_MS, p as resolveIMessageSentEchoEntryKey, r as IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE, s as IMESSAGE_SENT_ECHOES_NAMESPACE, t as IMESSAGE_CATCHUP_CURSOR_NAMESPACE, u as resolveIMessageCatchupCursorKey } from "./state-contract-4TK_cDDb.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { asFiniteNumber, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { fileExists } from "openclaw/plugin-sdk/security-runtime";
|
|
8
|
+
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
9
|
+
//#region extensions/imessage/src/state-migrations.ts
|
|
10
|
+
function resolveMigrationStateDir(params) {
|
|
11
|
+
return params.stateDir ?? resolveStateDir(params.env);
|
|
12
|
+
}
|
|
13
|
+
function remainingTtlMs(timestamp, ttlMs) {
|
|
14
|
+
const remaining = ttlMs - Math.max(0, Date.now() - timestamp);
|
|
15
|
+
return remaining > 0 ? remaining : void 0;
|
|
16
|
+
}
|
|
17
|
+
function readJsonl(pathValue) {
|
|
18
|
+
try {
|
|
19
|
+
return fs.readFileSync(pathValue, "utf8").split(/\n+/).flatMap((line) => {
|
|
20
|
+
if (!line) return [];
|
|
21
|
+
try {
|
|
22
|
+
return [JSON.parse(line)];
|
|
23
|
+
} catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
throw new Error(`Failed reading ${pathValue}: ${String(err)}`, { cause: err });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function parseReplyCacheEntry(raw) {
|
|
32
|
+
if (!raw || typeof raw !== "object") return null;
|
|
33
|
+
const parsed = raw;
|
|
34
|
+
if (typeof parsed.accountId !== "string" || typeof parsed.messageId !== "string" || typeof parsed.shortId !== "string" || typeof parsed.timestamp !== "number") return null;
|
|
35
|
+
return {
|
|
36
|
+
accountId: parsed.accountId,
|
|
37
|
+
messageId: parsed.messageId,
|
|
38
|
+
shortId: parsed.shortId,
|
|
39
|
+
timestamp: parsed.timestamp,
|
|
40
|
+
...typeof parsed.chatGuid === "string" ? { chatGuid: parsed.chatGuid } : {},
|
|
41
|
+
...typeof parsed.chatIdentifier === "string" ? { chatIdentifier: parsed.chatIdentifier } : {},
|
|
42
|
+
...typeof parsed.chatId === "number" ? { chatId: parsed.chatId } : {},
|
|
43
|
+
...typeof parsed.isFromMe === "boolean" ? { isFromMe: parsed.isFromMe } : {}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function readReplyCacheMaxShortId(sourcePath) {
|
|
47
|
+
let max = 0;
|
|
48
|
+
for (const raw of readJsonl(sourcePath)) {
|
|
49
|
+
if (!raw || typeof raw !== "object") continue;
|
|
50
|
+
const shortId = raw.shortId;
|
|
51
|
+
if (typeof shortId !== "string") continue;
|
|
52
|
+
const numeric = Number.parseInt(shortId, 10);
|
|
53
|
+
if (Number.isFinite(numeric) && numeric > max) max = numeric;
|
|
54
|
+
}
|
|
55
|
+
return max;
|
|
56
|
+
}
|
|
57
|
+
function readReplyCounterValue(value) {
|
|
58
|
+
if (!value || typeof value !== "object") return null;
|
|
59
|
+
const counter = value.counter;
|
|
60
|
+
return asFiniteNumber(counter) ?? null;
|
|
61
|
+
}
|
|
62
|
+
function shouldReplaceReplyCounter(existingValue, incomingValue) {
|
|
63
|
+
const incomingCounter = readReplyCounterValue(incomingValue);
|
|
64
|
+
if (incomingCounter === null) return false;
|
|
65
|
+
const existingCounter = readReplyCounterValue(existingValue);
|
|
66
|
+
return existingCounter === null || incomingCounter > existingCounter;
|
|
67
|
+
}
|
|
68
|
+
function parseSentEchoEntry(raw) {
|
|
69
|
+
if (!raw || typeof raw !== "object") return null;
|
|
70
|
+
const parsed = raw;
|
|
71
|
+
if (typeof parsed.scope !== "string" || typeof parsed.timestamp !== "number") return null;
|
|
72
|
+
return {
|
|
73
|
+
scope: parsed.scope,
|
|
74
|
+
timestamp: parsed.timestamp,
|
|
75
|
+
...typeof parsed.text === "string" ? { text: parsed.text } : {},
|
|
76
|
+
...typeof parsed.messageId === "string" ? { messageId: parsed.messageId } : {}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function listReplyCacheEntries(sourcePath) {
|
|
80
|
+
const entriesByKey = /* @__PURE__ */ new Map();
|
|
81
|
+
for (const entry of readJsonl(sourcePath).map(parseReplyCacheEntry)) {
|
|
82
|
+
if (!entry) continue;
|
|
83
|
+
const ttlMs = remainingTtlMs(entry.timestamp, IMESSAGE_REPLY_CACHE_TTL_MS);
|
|
84
|
+
if (!ttlMs) continue;
|
|
85
|
+
const key = resolveIMessageReplyCacheEntryKey(entry.messageId);
|
|
86
|
+
entriesByKey.delete(key);
|
|
87
|
+
entriesByKey.set(key, {
|
|
88
|
+
value: entry,
|
|
89
|
+
ttlMs
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return [...entriesByKey.entries()].slice(-IMESSAGE_REPLY_CACHE_MAX_ENTRIES).map(([key, entry]) => ({
|
|
93
|
+
key,
|
|
94
|
+
value: entry.value,
|
|
95
|
+
ttlMs: entry.ttlMs
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
function listSentEchoEntries(sourcePath) {
|
|
99
|
+
return readJsonl(sourcePath).map(parseSentEchoEntry).filter((entry) => Boolean(entry)).slice(-256).flatMap((entry) => {
|
|
100
|
+
const ttlMs = remainingTtlMs(entry.timestamp, IMESSAGE_SENT_ECHOES_TTL_MS);
|
|
101
|
+
if (!ttlMs) return [];
|
|
102
|
+
return [{
|
|
103
|
+
key: resolveIMessageSentEchoEntryKey(entry),
|
|
104
|
+
value: entry,
|
|
105
|
+
ttlMs
|
|
106
|
+
}];
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function resolveLegacyCatchupCursorPath(stateDir, accountId) {
|
|
110
|
+
const safePrefix = accountId.replace(/[^a-zA-Z0-9_-]/g, "_") || "account";
|
|
111
|
+
const hash = createHash("sha256").update(accountId, "utf8").digest("hex").slice(0, 12);
|
|
112
|
+
return path.join(stateDir, "imessage", "catchup", `${safePrefix}__${hash}.json`);
|
|
113
|
+
}
|
|
114
|
+
function listLegacyCatchupCursorPaths(stateDir) {
|
|
115
|
+
const dir = path.join(stateDir, "imessage", "catchup");
|
|
116
|
+
try {
|
|
117
|
+
return fs.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path.join(dir, entry.name));
|
|
118
|
+
} catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function normalizeCatchupCursor(raw) {
|
|
123
|
+
if (!raw || typeof raw !== "object") return null;
|
|
124
|
+
const value = raw;
|
|
125
|
+
if (typeof value.lastSeenMs !== "number" || !Number.isFinite(value.lastSeenMs) || typeof value.lastSeenRowid !== "number" || !Number.isFinite(value.lastSeenRowid)) return null;
|
|
126
|
+
const failureRetries = sanitizeCatchupFailureRetries(value.failureRetries);
|
|
127
|
+
const hasRetries = Object.keys(failureRetries).length > 0;
|
|
128
|
+
return {
|
|
129
|
+
lastSeenMs: value.lastSeenMs,
|
|
130
|
+
lastSeenRowid: value.lastSeenRowid,
|
|
131
|
+
updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : 0,
|
|
132
|
+
...hasRetries ? { failureRetries } : {}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function readCatchupCursor(sourcePath) {
|
|
136
|
+
let parsed;
|
|
137
|
+
try {
|
|
138
|
+
parsed = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
|
|
139
|
+
} catch (err) {
|
|
140
|
+
throw new Error(`Failed reading ${sourcePath}: ${String(err)}`, { cause: err });
|
|
141
|
+
}
|
|
142
|
+
const cursor = normalizeCatchupCursor(parsed);
|
|
143
|
+
if (!cursor) throw new Error(`Invalid iMessage catchup cursor: ${sourcePath}`);
|
|
144
|
+
return cursor;
|
|
145
|
+
}
|
|
146
|
+
function sanitizeCatchupFailureRetries(raw) {
|
|
147
|
+
if (!raw || typeof raw !== "object") return {};
|
|
148
|
+
const out = {};
|
|
149
|
+
for (const [guid, count] of Object.entries(raw)) if (typeof count === "number" && Number.isFinite(count) && count > 0) out[guid] = Math.floor(count);
|
|
150
|
+
return capFailureRetriesMap(out);
|
|
151
|
+
}
|
|
152
|
+
function shouldReplaceCatchupCursor(existingValue, incomingValue) {
|
|
153
|
+
const incoming = normalizeCatchupCursor(incomingValue);
|
|
154
|
+
if (!incoming) return false;
|
|
155
|
+
const existing = normalizeCatchupCursor(existingValue);
|
|
156
|
+
return !existing || incoming.lastSeenRowid > existing.lastSeenRowid || incoming.lastSeenRowid === existing.lastSeenRowid && incoming.lastSeenMs > existing.lastSeenMs;
|
|
157
|
+
}
|
|
158
|
+
function detectReplyCacheMigration(params) {
|
|
159
|
+
const stateDir = resolveMigrationStateDir(params);
|
|
160
|
+
const sourcePath = path.join(stateDir, "imessage", "reply-cache.jsonl");
|
|
161
|
+
if (!fileExists(sourcePath)) return [];
|
|
162
|
+
const plans = [];
|
|
163
|
+
plans.push({
|
|
164
|
+
kind: "plugin-state-import",
|
|
165
|
+
label: "iMessage reply short-id counter",
|
|
166
|
+
sourcePath,
|
|
167
|
+
targetPath: `plugin state:${IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE}`,
|
|
168
|
+
pluginId: "imessage",
|
|
169
|
+
namespace: IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE,
|
|
170
|
+
maxEntries: 1,
|
|
171
|
+
scopeKey: "",
|
|
172
|
+
stateDir,
|
|
173
|
+
preview: `- iMessage reply short-id counter: ${sourcePath} → plugin state (${IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE})`,
|
|
174
|
+
readEntries: () => {
|
|
175
|
+
const maxShortId = readReplyCacheMaxShortId(sourcePath);
|
|
176
|
+
return maxShortId > 0 ? [{
|
|
177
|
+
key: IMESSAGE_REPLY_CACHE_COUNTER_KEY,
|
|
178
|
+
value: { counter: maxShortId }
|
|
179
|
+
}] : [];
|
|
180
|
+
},
|
|
181
|
+
shouldReplaceExistingEntry: ({ existingValue, incomingValue }) => shouldReplaceReplyCounter(existingValue, incomingValue)
|
|
182
|
+
});
|
|
183
|
+
plans.push({
|
|
184
|
+
kind: "plugin-state-import",
|
|
185
|
+
label: "iMessage reply short-id cache",
|
|
186
|
+
sourcePath,
|
|
187
|
+
targetPath: `plugin state:${IMESSAGE_REPLY_CACHE_NAMESPACE}`,
|
|
188
|
+
pluginId: "imessage",
|
|
189
|
+
namespace: IMESSAGE_REPLY_CACHE_NAMESPACE,
|
|
190
|
+
maxEntries: IMESSAGE_REPLY_CACHE_MAX_ENTRIES,
|
|
191
|
+
scopeKey: "",
|
|
192
|
+
stateDir,
|
|
193
|
+
cleanupSource: "rename",
|
|
194
|
+
cleanupWhenEmpty: true,
|
|
195
|
+
preview: `- iMessage reply short-id cache: ${sourcePath} → plugin state (${IMESSAGE_REPLY_CACHE_NAMESPACE})`,
|
|
196
|
+
readEntries: () => listReplyCacheEntries(sourcePath)
|
|
197
|
+
});
|
|
198
|
+
return plans;
|
|
199
|
+
}
|
|
200
|
+
function detectSentEchoMigration(params) {
|
|
201
|
+
const stateDir = resolveMigrationStateDir(params);
|
|
202
|
+
const sourcePath = path.join(stateDir, "imessage", "sent-echoes.jsonl");
|
|
203
|
+
if (!fileExists(sourcePath)) return [];
|
|
204
|
+
return [{
|
|
205
|
+
kind: "plugin-state-import",
|
|
206
|
+
label: "iMessage sent-echo dedupe cache",
|
|
207
|
+
sourcePath,
|
|
208
|
+
targetPath: `plugin state:${IMESSAGE_SENT_ECHOES_NAMESPACE}`,
|
|
209
|
+
pluginId: "imessage",
|
|
210
|
+
namespace: IMESSAGE_SENT_ECHOES_NAMESPACE,
|
|
211
|
+
maxEntries: 256,
|
|
212
|
+
scopeKey: "",
|
|
213
|
+
stateDir,
|
|
214
|
+
cleanupSource: "rename",
|
|
215
|
+
cleanupWhenEmpty: true,
|
|
216
|
+
preview: `- iMessage sent-echo dedupe cache: ${sourcePath} → plugin state (${IMESSAGE_SENT_ECHOES_NAMESPACE})`,
|
|
217
|
+
readEntries: () => listSentEchoEntries(sourcePath)
|
|
218
|
+
}];
|
|
219
|
+
}
|
|
220
|
+
function detectCatchupCursorMigrations(params) {
|
|
221
|
+
const stateDir = resolveMigrationStateDir(params);
|
|
222
|
+
const accountIds = uniqueStrings([resolveDefaultIMessageAccountId(params.cfg), ...listIMessageAccountIds(params.cfg)].map((accountId) => resolveIMessageAccount({
|
|
223
|
+
cfg: params.cfg,
|
|
224
|
+
accountId
|
|
225
|
+
}).accountId));
|
|
226
|
+
const configuredPaths = new Set(accountIds.map((accountId) => resolveLegacyCatchupCursorPath(stateDir, accountId)));
|
|
227
|
+
const configuredPlans = accountIds.flatMap((accountId) => {
|
|
228
|
+
const sourcePath = resolveLegacyCatchupCursorPath(stateDir, accountId);
|
|
229
|
+
if (!fileExists(sourcePath)) return [];
|
|
230
|
+
return {
|
|
231
|
+
kind: "plugin-state-import",
|
|
232
|
+
label: "iMessage catchup cursor",
|
|
233
|
+
sourcePath,
|
|
234
|
+
targetPath: `plugin state:${IMESSAGE_CATCHUP_CURSOR_NAMESPACE}`,
|
|
235
|
+
pluginId: "imessage",
|
|
236
|
+
namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
|
|
237
|
+
maxEntries: 256,
|
|
238
|
+
scopeKey: "",
|
|
239
|
+
stateDir,
|
|
240
|
+
cleanupSource: "rename",
|
|
241
|
+
preview: `- iMessage catchup cursor: ${sourcePath} → plugin state (${IMESSAGE_CATCHUP_CURSOR_NAMESPACE})`,
|
|
242
|
+
readEntries: () => {
|
|
243
|
+
const cursor = readCatchupCursor(sourcePath);
|
|
244
|
+
return [{
|
|
245
|
+
key: resolveIMessageCatchupCursorKey(accountId),
|
|
246
|
+
value: cursor
|
|
247
|
+
}];
|
|
248
|
+
},
|
|
249
|
+
shouldReplaceExistingEntry: (replaceParams) => shouldReplaceCatchupCursor(replaceParams.existingValue, replaceParams.incomingValue)
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
const orphanPlans = listLegacyCatchupCursorPaths(stateDir).filter((sourcePath) => !configuredPaths.has(sourcePath)).map((sourcePath) => ({
|
|
253
|
+
kind: "plugin-state-import",
|
|
254
|
+
label: "iMessage orphan catchup cursor",
|
|
255
|
+
sourcePath,
|
|
256
|
+
targetPath: `plugin state:${IMESSAGE_CATCHUP_CURSOR_NAMESPACE}`,
|
|
257
|
+
pluginId: "imessage",
|
|
258
|
+
namespace: IMESSAGE_CATCHUP_CURSOR_NAMESPACE,
|
|
259
|
+
maxEntries: 256,
|
|
260
|
+
scopeKey: "",
|
|
261
|
+
stateDir,
|
|
262
|
+
cleanupSource: "rename",
|
|
263
|
+
cleanupWhenEmpty: true,
|
|
264
|
+
preview: `- iMessage orphan catchup cursor: ${sourcePath} → archived legacy state`,
|
|
265
|
+
readEntries: () => []
|
|
266
|
+
}));
|
|
267
|
+
return [...configuredPlans, ...orphanPlans];
|
|
268
|
+
}
|
|
269
|
+
async function detectIMessageLegacyStateMigrations(params) {
|
|
270
|
+
return [
|
|
271
|
+
...detectCatchupCursorMigrations(params),
|
|
272
|
+
...detectReplyCacheMigration(params),
|
|
273
|
+
...detectSentEchoMigration(params)
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
export { detectIMessageLegacyStateMigrations };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns true if the value is a ISO8601 DateTime string
|
|
3
|
-
* @
|
|
3
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc3339
|
|
4
4
|
* @example `2020-12-12T20:20:40+00:00`
|
|
5
5
|
*/
|
|
6
|
-
export declare function IsDateTime(value: string
|
|
6
|
+
export declare function IsDateTime(value: string): boolean;
|
|
@@ -2,10 +2,10 @@ import { IsDate } from './date.mjs';
|
|
|
2
2
|
import { IsTime } from './time.mjs';
|
|
3
3
|
/**
|
|
4
4
|
* Returns true if the value is a ISO8601 DateTime string
|
|
5
|
-
* @
|
|
5
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc3339
|
|
6
6
|
* @example `2020-12-12T20:20:40+00:00`
|
|
7
7
|
*/
|
|
8
|
-
export function IsDateTime(value
|
|
8
|
+
export function IsDateTime(value) {
|
|
9
9
|
const dateTime = value.split(/T/i);
|
|
10
|
-
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1]
|
|
10
|
+
return dateTime.length === 2 && IsDate(dateTime[0]) && IsTime(dateTime[1]);
|
|
11
11
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const Email = /^(
|
|
1
|
+
const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\.)*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i;
|
|
2
2
|
/**
|
|
3
3
|
* Returns true if the value is an Email
|
|
4
|
-
* @specification
|
|
4
|
+
* @specification https://datatracker.ietf.org/doc/html/rfc5322
|
|
5
5
|
*/
|
|
6
6
|
export function IsEmail(value) {
|
|
7
7
|
return Email.test(value);
|