@openclaw/imessage 2026.8.1-beta.3 → 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-B_xJiBXF.js → actions.runtime-CAl3WcqD.js} +2 -3
- package/dist/api.js +6 -5
- package/dist/{approval-handler.runtime-BKku8aUf.js → approval-handler.runtime-UBd1j2Sg.js} +7 -7
- package/dist/{approval-reactions-BME5eA6T.js → approval-reactions-LL5yn1Rc.js} +5 -9
- package/dist/{channel-BFQTpvrG.js → channel-DNI9DKGN.js} +17 -16
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-BjkXwX9_.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-Ca3c5KES.js → group-policy-Dvc-BcWo.js} +12 -17
- 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-TZiNYA17.js → message-tool-api-CvIgJpOo.js} +3 -3
- package/dist/message-tool-api.js +1 -1
- package/dist/{monitor-Uv5RaRTk.js → monitor-BywJn1QW.js} +289 -28
- package/dist/{question-reactions-to0i8vxE.js → question-reactions-viP7ZcyG.js} +1 -1
- package/dist/{reaction-context-DaNrZqYo.js → reaction-context-BAYI7pz0.js} +2 -2
- package/dist/{remote-file-CDuk0joc.js → remote-file-6mCj2ZBy.js} +2 -2
- package/dist/runtime-Cza4CY5T.js +8 -0
- package/dist/runtime-api.js +10 -9
- package/dist/{sanitize-outbound-DaLmdkbf.js → sanitize-outbound-Du-r1lNQ.js} +344 -57
- package/dist/{send-Dake4Awx.js → send-mgWPKqSV.js} +132 -11
- 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-Dh-Rv4Ur.js +0 -294
- package/dist/monitor-reply-cache-BdeUQaHO.js +0 -353
- package/dist/persisted-echo-cache-DGxy-J4t.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,15 +1,352 @@
|
|
|
1
|
-
import { u as expandIMessageUserPath } from "./accounts-
|
|
2
|
-
import {
|
|
1
|
+
import { u as expandIMessageUserPath } from "./accounts-CCirBEIm.js";
|
|
2
|
+
import { t as getIMessageRuntime } from "./runtime-Cza4CY5T.js";
|
|
3
|
+
import { a as IMESSAGE_REPLY_CACHE_NAMESPACE, f as resolveIMessageReplyCacheEntryKey, i as IMESSAGE_REPLY_CACHE_MAX_ENTRIES, n as IMESSAGE_REPLY_CACHE_COUNTER_KEY, o as IMESSAGE_REPLY_CACHE_TTL_MS, r as IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE } from "./state-contract-4TK_cDDb.js";
|
|
4
|
+
import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
|
5
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
3
7
|
import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
4
8
|
import { spawn } from "node:child_process";
|
|
5
9
|
import { StringDecoder } from "node:string_decoder";
|
|
6
10
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
11
|
import { FormatCapabilityProfile, findCodeRegions, isInsideCode, markdownToIR, renderMarkdownWithAttributedRanges, sanitizeAssistantVisibleText, stripMarkdown, tokenizeHtmlTags } from "openclaw/plugin-sdk/text-chunking";
|
|
12
|
+
//#region extensions/imessage/src/chat-context.ts
|
|
13
|
+
const EMAIL_HANDLE_PATTERN = /^[^\s@]+@[^\s@]+$/u;
|
|
14
|
+
function parseDirectChatIdentity(raw) {
|
|
15
|
+
const trimmed = raw.trim();
|
|
16
|
+
const parts = trimmed.split(";");
|
|
17
|
+
if (parts.length === 3 && parts[1] === "-" && parts[2]) {
|
|
18
|
+
const service = parts[0]?.toLowerCase();
|
|
19
|
+
if (service === "imessage" || service === "sms" || service === "any") {
|
|
20
|
+
const identifier = parts[2];
|
|
21
|
+
return {
|
|
22
|
+
service,
|
|
23
|
+
identifier: EMAIL_HANDLE_PATTERN.test(identifier) ? identifier.toLowerCase() : identifier
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (parts.length !== 1) return;
|
|
28
|
+
if (trimmed.startsWith("+") || EMAIL_HANDLE_PATTERN.test(trimmed)) return { identifier: trimmed.toLowerCase() };
|
|
29
|
+
}
|
|
30
|
+
function resolveIMessageDirectChatService(configuredService, chatGuid) {
|
|
31
|
+
if (configuredService === "imessage" || configuredService === "sms") return configuredService;
|
|
32
|
+
const observedService = chatGuid ? parseDirectChatIdentity(chatGuid)?.service : void 0;
|
|
33
|
+
return observedService === "imessage" || observedService === "sms" ? observedService : void 0;
|
|
34
|
+
}
|
|
35
|
+
function isIMessageEmailChatIdentifier(raw) {
|
|
36
|
+
const identity = parseDirectChatIdentity(raw);
|
|
37
|
+
return Boolean(identity && EMAIL_HANDLE_PATTERN.test(identity.identifier));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Strip the `iMessage;-;` / `SMS;-;` / `any;-;` service prefix that Messages
|
|
41
|
+
* uses for direct chats. Different layers report direct DMs in different
|
|
42
|
+
* forms, so raw comparison would falsely treat one DM as different chats.
|
|
43
|
+
*/
|
|
44
|
+
function normalizeDirectChatIdentifier(raw) {
|
|
45
|
+
const trimmed = raw.trim();
|
|
46
|
+
return parseDirectChatIdentity(trimmed)?.identifier ?? trimmed;
|
|
47
|
+
}
|
|
48
|
+
function chatContextFromIMessageTarget(target, effectiveService) {
|
|
49
|
+
if (target.kind === "chat_id") return { chatId: target.chatId };
|
|
50
|
+
if (target.kind === "chat_guid") return { chatGuid: target.chatGuid };
|
|
51
|
+
if (target.kind === "chat_identifier") return { chatIdentifier: target.chatIdentifier };
|
|
52
|
+
const trimmedHandle = target.to.trim();
|
|
53
|
+
const canonicalHandle = trimmedHandle.startsWith("+") ? normalizeE164(trimmedHandle) : /^[^\s@]+@[^\s@]+$/u.test(trimmedHandle) ? trimmedHandle.toLowerCase() : void 0;
|
|
54
|
+
if (!canonicalHandle) return {};
|
|
55
|
+
const service = target.service === "auto" ? effectiveService : target.service;
|
|
56
|
+
if (service !== "imessage" && service !== "sms") return {};
|
|
57
|
+
return { chatIdentifier: `${service === "sms" ? "SMS" : "iMessage"};-;${canonicalHandle}` };
|
|
58
|
+
}
|
|
59
|
+
function compareOptional(left, right) {
|
|
60
|
+
return left === void 0 || right === void 0 ? void 0 : left === right;
|
|
61
|
+
}
|
|
62
|
+
function compareChatSelector(cachedRaw, currentRaw, crossKind = false) {
|
|
63
|
+
const cached = normalizeOptionalString(cachedRaw);
|
|
64
|
+
const current = normalizeOptionalString(currentRaw);
|
|
65
|
+
if (!cached || !current) return;
|
|
66
|
+
if (cached === current) return true;
|
|
67
|
+
const cachedDirect = parseDirectChatIdentity(cached);
|
|
68
|
+
const currentDirect = parseDirectChatIdentity(current);
|
|
69
|
+
if (!cachedDirect || !currentDirect) return crossKind ? void 0 : false;
|
|
70
|
+
if (cachedDirect.identifier !== currentDirect.identifier) return false;
|
|
71
|
+
if (cachedDirect.service === "any") return true;
|
|
72
|
+
if (!cachedDirect.service || !currentDirect.service) return;
|
|
73
|
+
return cachedDirect.service === currentDirect.service;
|
|
74
|
+
}
|
|
75
|
+
function resolveIMessageChatMatch(cached, current) {
|
|
76
|
+
const cachedChatGuid = normalizeOptionalString(cached.chatGuid);
|
|
77
|
+
const currentChatGuid = normalizeOptionalString(current.chatGuid);
|
|
78
|
+
const cachedChatIdentifier = normalizeOptionalString(cached.chatIdentifier);
|
|
79
|
+
const currentChatIdentifier = normalizeOptionalString(current.chatIdentifier);
|
|
80
|
+
const comparisons = [
|
|
81
|
+
compareChatSelector(cachedChatGuid, currentChatGuid),
|
|
82
|
+
compareChatSelector(cachedChatIdentifier, currentChatIdentifier),
|
|
83
|
+
compareOptional(cached.chatId, current.chatId),
|
|
84
|
+
compareChatSelector(cachedChatGuid, currentChatIdentifier, true),
|
|
85
|
+
compareChatSelector(cachedChatIdentifier, currentChatGuid, true)
|
|
86
|
+
].filter((comparison) => comparison !== void 0);
|
|
87
|
+
if (comparisons.length === 0) return "unknown";
|
|
88
|
+
return comparisons.every(Boolean) ? "match" : "mismatch";
|
|
89
|
+
}
|
|
90
|
+
function isPositiveIMessageChatMatch(cached, current) {
|
|
91
|
+
return resolveIMessageChatMatch(cached, current) === "match";
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
8
94
|
//#region extensions/imessage/src/constants.ts
|
|
9
95
|
/** Default timeout for iMessage probe/RPC operations (10 seconds). */
|
|
10
96
|
const DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS = 1e4;
|
|
11
97
|
const DEFAULT_IMESSAGE_SEND_TIMEOUT_MS = 18e4;
|
|
12
98
|
//#endregion
|
|
99
|
+
//#region extensions/imessage/src/monitor-reply-cache.ts
|
|
100
|
+
/** Recency window for the "react to the latest message" fallback. */
|
|
101
|
+
const LATEST_FALLBACK_MS = 600 * 1e3;
|
|
102
|
+
let persistenceFailureLogged = false;
|
|
103
|
+
function reportPersistenceFailure(scope, err) {
|
|
104
|
+
if (persistenceFailureLogged) return;
|
|
105
|
+
persistenceFailureLogged = true;
|
|
106
|
+
logVerbose(`imessage reply-cache: ${scope} disabled after first failure: ${String(err)}`);
|
|
107
|
+
}
|
|
108
|
+
const imessageReplyCacheByMessageId = /* @__PURE__ */ new Map();
|
|
109
|
+
const imessageShortIdToUuid = /* @__PURE__ */ new Map();
|
|
110
|
+
const imessageUuidToShortId = /* @__PURE__ */ new Map();
|
|
111
|
+
let imessageShortIdCounter = 0;
|
|
112
|
+
function openReplyCacheStore() {
|
|
113
|
+
return getIMessageRuntime().state.openSyncKeyedStore({
|
|
114
|
+
namespace: IMESSAGE_REPLY_CACHE_NAMESPACE,
|
|
115
|
+
maxEntries: IMESSAGE_REPLY_CACHE_MAX_ENTRIES
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function openReplyCacheCounterStore() {
|
|
119
|
+
return getIMessageRuntime().state.openSyncKeyedStore({
|
|
120
|
+
namespace: IMESSAGE_REPLY_CACHE_COUNTER_NAMESPACE,
|
|
121
|
+
maxEntries: 1
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function remainingTtlMs(timestamp) {
|
|
125
|
+
const remaining = IMESSAGE_REPLY_CACHE_TTL_MS - Math.max(0, Date.now() - timestamp);
|
|
126
|
+
return remaining > 0 ? remaining : void 0;
|
|
127
|
+
}
|
|
128
|
+
let hydrated = false;
|
|
129
|
+
function hydrateFromStoreOnce() {
|
|
130
|
+
if (hydrated) return;
|
|
131
|
+
hydrated = true;
|
|
132
|
+
const cutoff = Date.now() - IMESSAGE_REPLY_CACHE_TTL_MS;
|
|
133
|
+
let entries;
|
|
134
|
+
try {
|
|
135
|
+
const counter = openReplyCacheCounterStore().lookup(IMESSAGE_REPLY_CACHE_COUNTER_KEY);
|
|
136
|
+
if (counter && Number.isSafeInteger(counter.counter) && counter.counter > 0) imessageShortIdCounter = Math.max(imessageShortIdCounter, counter.counter);
|
|
137
|
+
entries = openReplyCacheStore().entries().map(({ value }) => value).filter((entry) => entry.timestamp >= cutoff).toSorted((a, b) => a.timestamp - b.timestamp).slice(-IMESSAGE_REPLY_CACHE_MAX_ENTRIES);
|
|
138
|
+
for (const entry of entries) {
|
|
139
|
+
const numeric = Number.parseInt(entry.shortId, 10);
|
|
140
|
+
if (Number.isFinite(numeric) && numeric > imessageShortIdCounter) imessageShortIdCounter = numeric;
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
reportPersistenceFailure("read", err);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (entries.length === 0) return;
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
imessageReplyCacheByMessageId.set(entry.messageId, entry);
|
|
149
|
+
imessageShortIdToUuid.set(entry.shortId, entry.messageId);
|
|
150
|
+
imessageUuidToShortId.set(entry.messageId, entry.shortId);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function persistReplyCacheEntry(entry) {
|
|
154
|
+
const ttlMs = remainingTtlMs(entry.timestamp);
|
|
155
|
+
if (!ttlMs) return;
|
|
156
|
+
try {
|
|
157
|
+
openReplyCacheStore().register(resolveIMessageReplyCacheEntryKey(entry.messageId), entry, { ttlMs });
|
|
158
|
+
} catch (err) {
|
|
159
|
+
reportPersistenceFailure("write", err);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function deleteReplyCacheEntry(messageId) {
|
|
163
|
+
try {
|
|
164
|
+
openReplyCacheStore().delete(resolveIMessageReplyCacheEntryKey(messageId));
|
|
165
|
+
} catch (err) {
|
|
166
|
+
reportPersistenceFailure("delete", err);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function persistReplyCacheCounter() {
|
|
170
|
+
try {
|
|
171
|
+
openReplyCacheCounterStore().register(IMESSAGE_REPLY_CACHE_COUNTER_KEY, { counter: imessageShortIdCounter });
|
|
172
|
+
} catch (err) {
|
|
173
|
+
reportPersistenceFailure("counter", err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function buildReplyCacheEntry(entry, messageId, shortId) {
|
|
177
|
+
return {
|
|
178
|
+
accountId: entry.accountId,
|
|
179
|
+
messageId,
|
|
180
|
+
shortId,
|
|
181
|
+
timestamp: entry.timestamp,
|
|
182
|
+
...typeof entry.chatGuid === "string" ? { chatGuid: entry.chatGuid } : {},
|
|
183
|
+
...typeof entry.chatIdentifier === "string" ? { chatIdentifier: entry.chatIdentifier } : {},
|
|
184
|
+
...typeof entry.chatId === "number" ? { chatId: entry.chatId } : {},
|
|
185
|
+
...typeof entry.isFromMe === "boolean" ? { isFromMe: entry.isFromMe } : {}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function generateShortId() {
|
|
189
|
+
imessageShortIdCounter += 1;
|
|
190
|
+
persistReplyCacheCounter();
|
|
191
|
+
return String(imessageShortIdCounter);
|
|
192
|
+
}
|
|
193
|
+
function rememberIMessageReplyCache(entry) {
|
|
194
|
+
hydrateFromStoreOnce();
|
|
195
|
+
const messageId = entry.messageId.trim();
|
|
196
|
+
if (!messageId) return {
|
|
197
|
+
...entry,
|
|
198
|
+
shortId: ""
|
|
199
|
+
};
|
|
200
|
+
let shortId = imessageUuidToShortId.get(messageId);
|
|
201
|
+
if (!shortId) {
|
|
202
|
+
shortId = generateShortId();
|
|
203
|
+
imessageShortIdToUuid.set(shortId, messageId);
|
|
204
|
+
imessageUuidToShortId.set(messageId, shortId);
|
|
205
|
+
}
|
|
206
|
+
const fullEntry = buildReplyCacheEntry(entry, messageId, shortId);
|
|
207
|
+
imessageReplyCacheByMessageId.delete(messageId);
|
|
208
|
+
imessageReplyCacheByMessageId.set(messageId, fullEntry);
|
|
209
|
+
const cutoff = Date.now() - IMESSAGE_REPLY_CACHE_TTL_MS;
|
|
210
|
+
let evicted = false;
|
|
211
|
+
const deletedMessageIds = [];
|
|
212
|
+
for (const [key, value] of imessageReplyCacheByMessageId) {
|
|
213
|
+
if (value.timestamp >= cutoff) break;
|
|
214
|
+
imessageReplyCacheByMessageId.delete(key);
|
|
215
|
+
deletedMessageIds.push(key);
|
|
216
|
+
if (value.shortId) {
|
|
217
|
+
imessageShortIdToUuid.delete(value.shortId);
|
|
218
|
+
imessageUuidToShortId.delete(key);
|
|
219
|
+
}
|
|
220
|
+
evicted = true;
|
|
221
|
+
}
|
|
222
|
+
while (imessageReplyCacheByMessageId.size > IMESSAGE_REPLY_CACHE_MAX_ENTRIES) {
|
|
223
|
+
const oldest = imessageReplyCacheByMessageId.keys().next().value;
|
|
224
|
+
if (!oldest) break;
|
|
225
|
+
const oldEntry = imessageReplyCacheByMessageId.get(oldest);
|
|
226
|
+
imessageReplyCacheByMessageId.delete(oldest);
|
|
227
|
+
deletedMessageIds.push(oldest);
|
|
228
|
+
if (oldEntry?.shortId) {
|
|
229
|
+
imessageShortIdToUuid.delete(oldEntry.shortId);
|
|
230
|
+
imessageUuidToShortId.delete(oldest);
|
|
231
|
+
}
|
|
232
|
+
evicted = true;
|
|
233
|
+
}
|
|
234
|
+
if (evicted) for (const messageIdToDelete of deletedMessageIds) deleteReplyCacheEntry(messageIdToDelete);
|
|
235
|
+
persistReplyCacheEntry(fullEntry);
|
|
236
|
+
return fullEntry;
|
|
237
|
+
}
|
|
238
|
+
function hasChatScope(ctx) {
|
|
239
|
+
if (!ctx) return false;
|
|
240
|
+
return Boolean(normalizeOptionalString(ctx.chatGuid) || normalizeOptionalString(ctx.chatIdentifier) || typeof ctx.chatId === "number");
|
|
241
|
+
}
|
|
242
|
+
function isCrossChatMismatch(cached, ctx) {
|
|
243
|
+
return resolveIMessageChatMatch(cached, ctx) === "mismatch";
|
|
244
|
+
}
|
|
245
|
+
function describeChatForError(values) {
|
|
246
|
+
const parts = [];
|
|
247
|
+
if (normalizeOptionalString(values.chatGuid)) parts.push("chatGuid=<redacted>");
|
|
248
|
+
if (normalizeOptionalString(values.chatIdentifier)) parts.push("chatIdentifier=<redacted>");
|
|
249
|
+
if (typeof values.chatId === "number") parts.push("chatId=<redacted>");
|
|
250
|
+
return parts.length === 0 ? "<unknown chat>" : parts.join(", ");
|
|
251
|
+
}
|
|
252
|
+
function describeMessageIdForError(inputId, inputKind) {
|
|
253
|
+
if (inputKind === "short") return `<short:${inputId.length}-digit>`;
|
|
254
|
+
return `<uuid:${inputId.slice(0, 8)}...>`;
|
|
255
|
+
}
|
|
256
|
+
function buildCrossChatError(inputId, inputKind, cached, ctx) {
|
|
257
|
+
const remediation = inputKind === "short" ? "Use a message ID from the current chat target; MessageSidFull from another chat is rejected." : "Retry with the correct chat target.";
|
|
258
|
+
return /* @__PURE__ */ new Error(`iMessage message id ${describeMessageIdForError(inputId, inputKind)} belongs to a different chat (${describeChatForError(cached)}) than the current call target (${describeChatForError(ctx)}). ${remediation}`);
|
|
259
|
+
}
|
|
260
|
+
function resolveIMessageMessageId(shortOrUuid, opts) {
|
|
261
|
+
const trimmed = shortOrUuid.trim();
|
|
262
|
+
if (!trimmed) return trimmed;
|
|
263
|
+
hydrateFromStoreOnce();
|
|
264
|
+
if (/^\d+$/.test(trimmed)) {
|
|
265
|
+
const uuid = imessageShortIdToUuid.get(trimmed);
|
|
266
|
+
if (uuid) {
|
|
267
|
+
const cached = imessageReplyCacheByMessageId.get(uuid);
|
|
268
|
+
if (opts?.chatContext && hasChatScope(opts.chatContext)) {
|
|
269
|
+
if (cached && isCrossChatMismatch(cached, opts.chatContext)) throw buildCrossChatError(trimmed, "short", cached, opts.chatContext);
|
|
270
|
+
}
|
|
271
|
+
if (opts?.requireFromMe && cached?.isFromMe !== true) throw buildFromMeError(trimmed, "short");
|
|
272
|
+
return uuid;
|
|
273
|
+
}
|
|
274
|
+
if (opts?.requireKnownShortId && !hasChatScope(opts.chatContext)) throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} requires a chat scope (chatGuid / chatIdentifier / chatId or a target).`);
|
|
275
|
+
if (opts?.requireKnownShortId) throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} is no longer available. Use MessageSidFull.`);
|
|
276
|
+
return trimmed;
|
|
277
|
+
}
|
|
278
|
+
const cached = imessageReplyCacheByMessageId.get(trimmed);
|
|
279
|
+
if (opts?.chatContext) {
|
|
280
|
+
if (cached && isCrossChatMismatch(cached, opts.chatContext)) throw buildCrossChatError(trimmed, "uuid", cached, opts.chatContext);
|
|
281
|
+
}
|
|
282
|
+
if (opts?.requireFromMe && cached?.isFromMe !== true) throw buildFromMeError(trimmed, "uuid");
|
|
283
|
+
return trimmed;
|
|
284
|
+
}
|
|
285
|
+
function isKnownFromMeIMessageMessageId(messageId, ctx) {
|
|
286
|
+
const trimmed = normalizeOptionalString(messageId);
|
|
287
|
+
if (!trimmed || !ctx.accountId || !hasChatScope(ctx)) return false;
|
|
288
|
+
hydrateFromStoreOnce();
|
|
289
|
+
const cached = imessageReplyCacheByMessageId.get(trimmed);
|
|
290
|
+
if (!cached || cached.isFromMe !== true || cached.accountId !== ctx.accountId) return false;
|
|
291
|
+
return isPositiveIMessageChatMatch(cached, ctx);
|
|
292
|
+
}
|
|
293
|
+
function buildFromMeError(inputId, inputKind) {
|
|
294
|
+
return /* @__PURE__ */ new Error(`iMessage message id ${describeMessageIdForError(inputId, inputKind)} is not one this agent sent. edit and unsend can only target messages the gateway delivered itself; messages received from other participants cannot be modified.`);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Return the most recent cached entry whose chat scope matches the supplied
|
|
298
|
+
* context. Used as a fallback when an agent calls a per-message action (e.g.
|
|
299
|
+
* `react`) without specifying a `messageId` — the natural intent is "react
|
|
300
|
+
* to the message I just received in this chat."
|
|
301
|
+
*
|
|
302
|
+
* Strict semantics for safety:
|
|
303
|
+
* - Caller must supply a chat scope. We refuse to "guess" the active chat.
|
|
304
|
+
* - Cached entry must positively match on at least one identifier kind
|
|
305
|
+
* (chatGuid, chatIdentifier, chatId, or normalized direct-DM fingerprint).
|
|
306
|
+
* We do NOT fall through on "no overlapping identifier" — that's how a
|
|
307
|
+
* cached entry from a foreign chat could be returned when the caller's
|
|
308
|
+
* context didn't share any identifier kind with the cache.
|
|
309
|
+
* - Caller must supply an accountId; we never cross account boundaries.
|
|
310
|
+
* - We only consider entries newer than `LATEST_FALLBACK_MS`. The intent
|
|
311
|
+
* of "react to the latest" is "the message I just received," not
|
|
312
|
+
* "anything in this chat from any time."
|
|
313
|
+
*/
|
|
314
|
+
function findLatestIMessageEntryForChat(ctx) {
|
|
315
|
+
if (!hasChatScope(ctx)) return;
|
|
316
|
+
if (!ctx.accountId) return;
|
|
317
|
+
const cutoff = Date.now() - LATEST_FALLBACK_MS;
|
|
318
|
+
let best;
|
|
319
|
+
for (const entry of imessageReplyCacheByMessageId.values()) {
|
|
320
|
+
if (entry.accountId !== ctx.accountId) continue;
|
|
321
|
+
if (entry.timestamp < cutoff) continue;
|
|
322
|
+
if (!isPositiveIMessageChatMatch(entry, ctx)) continue;
|
|
323
|
+
if (!best || entry.timestamp > best.timestamp) best = entry;
|
|
324
|
+
}
|
|
325
|
+
return best;
|
|
326
|
+
}
|
|
327
|
+
function resolveIMessageCachedResourceBinding(messageId, ctx) {
|
|
328
|
+
hydrateFromStoreOnce();
|
|
329
|
+
const entry = imessageReplyCacheByMessageId.get(messageId.trim());
|
|
330
|
+
if (!entry) return "unknown";
|
|
331
|
+
if (Date.now() - entry.timestamp > 216e5) return "unknown";
|
|
332
|
+
if (entry.accountId !== ctx.accountId) return "mismatch";
|
|
333
|
+
const chatMatch = resolveIMessageChatMatch(entry, ctx);
|
|
334
|
+
if (chatMatch !== "match") return chatMatch;
|
|
335
|
+
return "match";
|
|
336
|
+
}
|
|
337
|
+
function isIMessageCurrentMessageInChat(params) {
|
|
338
|
+
if (!params.accountId || !hasChatScope(params.chatContext)) return false;
|
|
339
|
+
const currentMessageId = normalizeOptionalString(String(params.currentMessageId));
|
|
340
|
+
if (!currentMessageId) return false;
|
|
341
|
+
hydrateFromStoreOnce();
|
|
342
|
+
const fullMessageId = /^\d+$/.test(currentMessageId) ? imessageShortIdToUuid.get(currentMessageId) : currentMessageId;
|
|
343
|
+
if (!fullMessageId) return false;
|
|
344
|
+
return resolveIMessageCachedResourceBinding(fullMessageId, {
|
|
345
|
+
...params.chatContext,
|
|
346
|
+
accountId: params.accountId
|
|
347
|
+
}) === "match";
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
13
350
|
//#region extensions/imessage/src/client.ts
|
|
14
351
|
var IMessageRpcRequestError = class extends Error {
|
|
15
352
|
constructor(message, code, data) {
|
|
@@ -329,35 +666,6 @@ function codeDelimiter(content) {
|
|
|
329
666
|
const longestRun = Math.max(0, ...[...content.matchAll(/`+/gu)].map((match) => match[0].length));
|
|
330
667
|
return "`".repeat(longestRun + 1);
|
|
331
668
|
}
|
|
332
|
-
function protectDunderIdentifiers(input) {
|
|
333
|
-
const protections = [];
|
|
334
|
-
let markdown = "";
|
|
335
|
-
let cursor = 0;
|
|
336
|
-
for (const match of input.matchAll(/__[\p{L}_][\p{L}\p{N}_]*__/gu)) {
|
|
337
|
-
const identifier = match[0];
|
|
338
|
-
const start = match.index ?? 0;
|
|
339
|
-
const end = start + identifier.length;
|
|
340
|
-
const before = input[start - 1];
|
|
341
|
-
const beforeBefore = input[start - 2];
|
|
342
|
-
const after = input[end];
|
|
343
|
-
const member = before === ".";
|
|
344
|
-
const call = after === "(";
|
|
345
|
-
const functionArgument = before === "(" && /[\p{L}\p{N}_]/u.test(beforeBefore ?? "");
|
|
346
|
-
if (!member && !call && !functionArgument && !(after === "[")) continue;
|
|
347
|
-
let token = `OCdunder${protections.length}token`;
|
|
348
|
-
while (input.includes(token)) token += "x";
|
|
349
|
-
markdown += input.slice(cursor, start) + token;
|
|
350
|
-
protections.push({
|
|
351
|
-
token,
|
|
352
|
-
identifier
|
|
353
|
-
});
|
|
354
|
-
cursor = end;
|
|
355
|
-
}
|
|
356
|
-
return {
|
|
357
|
-
markdown: markdown + input.slice(cursor),
|
|
358
|
-
protections
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
669
|
function applyTextEdits(text, edits) {
|
|
362
670
|
const ordered = edits.toSorted((left, right) => left.start - right.start);
|
|
363
671
|
let rendered = "";
|
|
@@ -372,26 +680,6 @@ function applyTextEdits(text, edits) {
|
|
|
372
680
|
mapOffset: (offset) => offset + ordered.reduce((delta, edit) => delta + (edit.end <= offset ? edit.text.length - edit.end + edit.start : 0), 0)
|
|
373
681
|
};
|
|
374
682
|
}
|
|
375
|
-
function restoreDunderIdentifiers(text, ranges, codeRanges, protections) {
|
|
376
|
-
const edited = applyTextEdits(text, protections.flatMap((protection) => {
|
|
377
|
-
const start = text.indexOf(protection.token);
|
|
378
|
-
return start === -1 ? [] : [{
|
|
379
|
-
start,
|
|
380
|
-
end: start + protection.token.length,
|
|
381
|
-
text: protection.identifier
|
|
382
|
-
}];
|
|
383
|
-
}));
|
|
384
|
-
const mapRange = (range) => ({
|
|
385
|
-
...range,
|
|
386
|
-
start: edited.mapOffset(range.start),
|
|
387
|
-
length: edited.mapOffset(range.start + range.length) - edited.mapOffset(range.start)
|
|
388
|
-
});
|
|
389
|
-
return {
|
|
390
|
-
text: edited.text,
|
|
391
|
-
ranges: ranges.map(mapRange),
|
|
392
|
-
codeRanges: codeRanges.map(mapRange)
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
683
|
function restoreCodeMarkers(text, ranges, codeRanges) {
|
|
396
684
|
const edited = applyTextEdits(text, codeRanges.map((range) => {
|
|
397
685
|
const end = range.start + range.length;
|
|
@@ -414,22 +702,21 @@ function restoreCodeMarkers(text, ranges, codeRanges) {
|
|
|
414
702
|
};
|
|
415
703
|
}
|
|
416
704
|
function extractMarkdownFormatRuns(input) {
|
|
417
|
-
const
|
|
418
|
-
const ir = markdownToIR(protectedDunders.markdown, {
|
|
705
|
+
const ir = markdownToIR(input, {
|
|
419
706
|
autolink: false,
|
|
420
707
|
enableHtmlUnderline: true,
|
|
421
708
|
headingStyle: "rich",
|
|
422
709
|
linkify: false,
|
|
710
|
+
preserveDunderIdentifiers: true,
|
|
423
711
|
preserveSourceBlockSpacing: true
|
|
424
712
|
});
|
|
425
713
|
const rendered = renderMarkdownWithAttributedRanges(ir, { styleMap: IMESSAGE_STYLE_MAP }, IMESSAGE_FORMAT_PROFILE);
|
|
426
714
|
const code = renderMarkdownWithAttributedRanges(ir, { styleMap: { code: "code" } }, IMESSAGE_CODE_PROFILE);
|
|
427
|
-
|
|
715
|
+
return restoreCodeMarkers(rendered.text, rendered.ranges.map(({ start, length, style }) => ({
|
|
428
716
|
start,
|
|
429
717
|
length,
|
|
430
718
|
styles: [style]
|
|
431
|
-
})), code.ranges
|
|
432
|
-
return restoreCodeMarkers(dunders.text, dunders.ranges, dunders.codeRanges);
|
|
719
|
+
})), code.ranges);
|
|
433
720
|
}
|
|
434
721
|
//#endregion
|
|
435
722
|
//#region extensions/imessage/src/monitor/sanitize-outbound.ts
|
|
@@ -893,4 +1180,4 @@ function sanitizeOutboundText(text) {
|
|
|
893
1180
|
return cleaned;
|
|
894
1181
|
}
|
|
895
1182
|
//#endregion
|
|
896
|
-
export { IMessageRpcRequestError as a,
|
|
1183
|
+
export { normalizeDirectChatIdentifier as _, IMessageRpcRequestError as a, isIMessageCurrentMessageInChat as c, resolveIMessageCachedResourceBinding as d, resolveIMessageMessageId as f, isIMessageEmailChatIdentifier as g, chatContextFromIMessageTarget as h, extractMarkdownFormatRuns as i, isKnownFromMeIMessageMessageId as l, DEFAULT_IMESSAGE_SEND_TIMEOUT_MS as m, sanitizeIMessageFinalOutboundText as n, createIMessageRpcClient as o, DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS as p, sanitizeOutboundText as r, findLatestIMessageEntryForChat as s, protectIMessageFencedRoleMarkers as t, rememberIMessageReplyCache as u, resolveIMessageDirectChatService as v };
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { d as resolveIMessageChatDbLookupPath, l as resolveIMessageRemoteHost, n as hasExclusiveIMessageLocalDatabase, o as resolveIMessageAccount } from "./accounts-
|
|
2
|
-
import { l as parseIMessageTarget, n as formatIMessageChatTarget, s as normalizeIMessageHandle } from "./message-tool-api-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { d as normalizeConversationKey, f as normalizeIMessageGuid, i as registerIMessageApprovalReactionTarget, l as enumerateApprovalTargetKeys, s as buildIMessageApprovalConversationKeyForInbound, u as enumerateConversationKeyForms } from "./approval-reactions-
|
|
8
|
-
import { i as runIMessageCliJsonCommand, r as resolveAuthorizedIMessageReplyReference, t as withIMessageRemoteFile } from "./remote-file-
|
|
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";
|
|
9
9
|
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
|
|
10
10
|
import { createLazyRuntimeSurface } from "openclaw/plugin-sdk/lazy-runtime";
|
|
11
11
|
import { accessSync, constants } from "node:fs";
|
|
12
12
|
import { basename } from "node:path";
|
|
13
13
|
import { asOptionalRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
14
|
-
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";
|
|
15
16
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
16
17
|
import { PlatformMessageNotDispatchedError, isApprovalNotFoundError } from "openclaw/plugin-sdk/error-runtime";
|
|
17
18
|
import { addApprovalReactionHintToText, createApprovalReactionTargetStore, listApprovalReactionBindings } from "openclaw/plugin-sdk/approval-reaction-runtime";
|
|
18
|
-
import { extractOriginalFilename, kindFromMime, resolveOutboundAttachmentFromUrl } from "openclaw/plugin-sdk/media-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";
|
|
@@ -453,6 +453,127 @@ const iMessageApprovalPollTargets = {
|
|
|
453
453
|
clearForTest: clearIMessageApprovalPollTargetsForTest
|
|
454
454
|
};
|
|
455
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
|
|
456
577
|
//#region extensions/imessage/src/send.ts
|
|
457
578
|
const MIN_PENDING_PERSISTED_ECHO_TTL_MS = 6e4;
|
|
458
579
|
const PENDING_PERSISTED_ECHO_GRACE_MS = 5e3;
|
|
@@ -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 };
|