@openclaw/imessage 2026.9.8 → 2026.10.1-beta.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/.setup/{accounts-nzcU0cq2.mjs → accounts-evTpvJMv.mjs} +6 -12
- package/dist/.setup/{group-policy-90pw4tyG.mjs → actions-DJqbUmbN.mjs} +103 -269
- package/dist/.setup/{actions.runtime-DdBN29zj.mjs → actions.runtime-DL5i-Y4J.mjs} +49 -84
- package/dist/.setup/{api-B9Qx21G-.mjs → api-SAVMfMoY.mjs} +5 -5
- package/dist/.setup/{api-D2S6FhGw.mjs → api-oq_MryN7.mjs} +6 -6
- package/dist/.setup/{approval-handler.runtime-DKSq0DWq.mjs → approval-handler.runtime-Bj2NGNXF.mjs} +26 -62
- package/dist/.setup/{approval-reactions-Bp33xc83.mjs → approval-reactions-L9Ul7PAu.mjs} +3 -2
- package/dist/.setup/{channel-9fyvuN_p.mjs → channel-CL73qdMG.mjs} +134 -60
- package/dist/.setup/{monitor-fv9nRuTH.mjs → channel.runtime-DY7JJ0_9.mjs} +202 -206
- package/dist/.setup/{sanitize-outbound-3MLljgtY.mjs → client-12apu4Ip.mjs} +26 -635
- package/dist/.setup/{config-schema-4Q3_EqYt.mjs → config-schema-DXwa_3V5.mjs} +1 -1
- package/dist/.setup/{media-contract-CagwSMvo.mjs → media-contract-DjQGZlfl.mjs} +1 -1
- package/dist/.setup/{message-tool-api-D032l82H.mjs → message-tool-api-CqfRxLlT.mjs} +2 -2
- package/dist/.setup/{question-reactions-Cy-dmU_j.mjs → question-reactions-DJV-TnA8.mjs} +2 -1
- package/dist/.setup/{reaction-context-Br-vAJFp.mjs → reaction-context-By7kUMBC.mjs} +2 -7
- package/dist/.setup/{remote-file-DRPCaVmg.mjs → remote-file-BU7pA-Zi.mjs} +7 -11
- package/dist/.setup/sanitize-outbound-C0x4BPky.mjs +564 -0
- package/dist/.setup/{send-Cu9nl757.mjs → send-pkYwL6Rt.mjs} +56 -103
- package/dist/.setup/{setup-core-8MDdYHj5.mjs → setup-core-DqOBe-n0.mjs} +1 -1
- package/dist/.setup/{setup-surface-tGXFDdWh.mjs → setup-surface-zKhIinwY.mjs} +2 -2
- package/dist/api.js +6 -6
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +29 -21
- package/dist/media-contract-api.js +1 -1
- package/dist/message-tool-api.js +1 -1
- package/dist/runtime-api.js +2 -24
- package/dist/setup-entry.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -4
- package/dist/.setup/channel.runtime-n-MPKL8k.mjs +0 -92
- package/dist/.setup/state-migrations-B-Q7bO0Y.mjs +0 -47
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { u as expandIMessageUserPath } from "./accounts-
|
|
2
|
-
import { i as invalidateCachedIMessagePrivateApiStatus } from "./message-tool-api-
|
|
1
|
+
import { u as expandIMessageUserPath } from "./accounts-evTpvJMv.mjs";
|
|
2
|
+
import { i as invalidateCachedIMessagePrivateApiStatus } from "./message-tool-api-CqfRxLlT.mjs";
|
|
3
3
|
import { t as getIMessageRuntime } from "./runtime-Cza4CY5T.mjs";
|
|
4
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
|
+
import { asNullableRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
5
|
import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
|
6
6
|
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
@@ -10,7 +10,7 @@ import { resolveUserPath } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
|
10
10
|
import { spawn } from "node:child_process";
|
|
11
11
|
import { StringDecoder } from "node:string_decoder";
|
|
12
12
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
13
|
-
import {
|
|
13
|
+
import { raceWithTimeout } from "openclaw/plugin-sdk/time-runtime";
|
|
14
14
|
//#region extensions/imessage/src/chat-context.ts
|
|
15
15
|
const EMAIL_HANDLE_PATTERN = /^[^\s@]+@[^\s@]+$/u;
|
|
16
16
|
function parseDirectChatIdentity(raw) {
|
|
@@ -52,7 +52,7 @@ function chatContextFromIMessageTarget(target, effectiveService) {
|
|
|
52
52
|
if (target.kind === "chat_guid") return { chatGuid: target.chatGuid };
|
|
53
53
|
if (target.kind === "chat_identifier") return { chatIdentifier: target.chatIdentifier };
|
|
54
54
|
const trimmedHandle = target.to.trim();
|
|
55
|
-
const canonicalHandle = trimmedHandle.startsWith("+") ? normalizeE164(trimmedHandle) :
|
|
55
|
+
const canonicalHandle = trimmedHandle.startsWith("+") ? normalizeE164(trimmedHandle) : EMAIL_HANDLE_PATTERN.test(trimmedHandle) ? trimmedHandle.toLowerCase() : void 0;
|
|
56
56
|
if (!canonicalHandle) return {};
|
|
57
57
|
const service = target.service === "auto" ? effectiveService : target.service;
|
|
58
58
|
if (service !== "imessage" && service !== "sms") return {};
|
|
@@ -75,23 +75,16 @@ function compareChatSelector(cachedRaw, currentRaw, crossKind = false) {
|
|
|
75
75
|
return cachedDirect.service === currentDirect.service;
|
|
76
76
|
}
|
|
77
77
|
function resolveIMessageChatMatch(cached, current) {
|
|
78
|
-
const cachedChatGuid = normalizeOptionalString(cached.chatGuid);
|
|
79
|
-
const currentChatGuid = normalizeOptionalString(current.chatGuid);
|
|
80
|
-
const cachedChatIdentifier = normalizeOptionalString(cached.chatIdentifier);
|
|
81
|
-
const currentChatIdentifier = normalizeOptionalString(current.chatIdentifier);
|
|
82
78
|
const comparisons = [
|
|
83
|
-
compareChatSelector(
|
|
84
|
-
compareChatSelector(
|
|
79
|
+
compareChatSelector(cached.chatGuid, current.chatGuid),
|
|
80
|
+
compareChatSelector(cached.chatIdentifier, current.chatIdentifier),
|
|
85
81
|
compareOptional(cached.chatId, current.chatId),
|
|
86
|
-
compareChatSelector(
|
|
87
|
-
compareChatSelector(
|
|
82
|
+
compareChatSelector(cached.chatGuid, current.chatIdentifier, true),
|
|
83
|
+
compareChatSelector(cached.chatIdentifier, current.chatGuid, true)
|
|
88
84
|
].filter((comparison) => comparison !== void 0);
|
|
89
85
|
if (comparisons.length === 0) return "unknown";
|
|
90
86
|
return comparisons.every(Boolean) ? "match" : "mismatch";
|
|
91
87
|
}
|
|
92
|
-
function isPositiveIMessageChatMatch(cached, current) {
|
|
93
|
-
return resolveIMessageChatMatch(cached, current) === "match";
|
|
94
|
-
}
|
|
95
88
|
//#endregion
|
|
96
89
|
//#region extensions/imessage/src/constants.ts
|
|
97
90
|
/** Default timeout for iMessage probe/RPC operations (10 seconds). */
|
|
@@ -262,10 +255,6 @@ function buildReplyCacheEntry(entry, messageId, shortId) {
|
|
|
262
255
|
...typeof entry.isFromMe === "boolean" ? { isFromMe: entry.isFromMe } : {}
|
|
263
256
|
};
|
|
264
257
|
}
|
|
265
|
-
function generateShortId() {
|
|
266
|
-
imessageShortIdCounter += 1;
|
|
267
|
-
return String(imessageShortIdCounter);
|
|
268
|
-
}
|
|
269
258
|
async function rememberIMessageReplyCache(entry) {
|
|
270
259
|
await hydrateFromStoreOnce();
|
|
271
260
|
const messageId = entry.messageId.trim();
|
|
@@ -276,7 +265,7 @@ async function rememberIMessageReplyCache(entry) {
|
|
|
276
265
|
let shortId = imessageReplyCacheByMessageId.get(messageId)?.shortId;
|
|
277
266
|
const isNewMessage = !shortId;
|
|
278
267
|
if (!shortId) {
|
|
279
|
-
shortId =
|
|
268
|
+
shortId = String(++imessageShortIdCounter);
|
|
280
269
|
imessageShortIdToUuid.set(shortId, messageId);
|
|
281
270
|
}
|
|
282
271
|
const fullEntry = buildReplyCacheEntry(entry, messageId, shortId);
|
|
@@ -311,9 +300,6 @@ function hasChatScope(ctx) {
|
|
|
311
300
|
if (!ctx) return false;
|
|
312
301
|
return Boolean(normalizeOptionalString(ctx.chatGuid) || normalizeOptionalString(ctx.chatIdentifier) || typeof ctx.chatId === "number");
|
|
313
302
|
}
|
|
314
|
-
function isCrossChatMismatch(cached, ctx) {
|
|
315
|
-
return resolveIMessageChatMatch(cached, ctx) === "mismatch";
|
|
316
|
-
}
|
|
317
303
|
function describeChatForError(values) {
|
|
318
304
|
const parts = [];
|
|
319
305
|
if (normalizeOptionalString(values.chatGuid)) parts.push("chatGuid=<redacted>");
|
|
@@ -333,26 +319,19 @@ async function resolveIMessageMessageId(shortOrUuid, opts) {
|
|
|
333
319
|
const trimmed = shortOrUuid.trim();
|
|
334
320
|
if (!trimmed) return trimmed;
|
|
335
321
|
await hydrateFromStoreOnce();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
if (opts?.requireFromMe && cached?.isFromMe !== true) throw buildFromMeError(trimmed, "short");
|
|
344
|
-
return uuid;
|
|
322
|
+
const inputKind = /^\d+$/.test(trimmed) ? "short" : "uuid";
|
|
323
|
+
const messageId = inputKind === "short" ? imessageShortIdToUuid.get(trimmed) : trimmed;
|
|
324
|
+
if (!messageId) {
|
|
325
|
+
if (opts?.requireKnownShortId) {
|
|
326
|
+
if (!hasChatScope(opts.chatContext)) throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} requires a chat scope (chatGuid / chatIdentifier / chatId or a target).`);
|
|
327
|
+
throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} is no longer available. Use MessageSidFull.`);
|
|
345
328
|
}
|
|
346
|
-
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).`);
|
|
347
|
-
if (opts?.requireKnownShortId) throw new Error(`iMessage short message id ${describeMessageIdForError(trimmed, "short")} is no longer available. Use MessageSidFull.`);
|
|
348
329
|
return trimmed;
|
|
349
330
|
}
|
|
350
|
-
const cached = imessageReplyCacheByMessageId.get(
|
|
351
|
-
if (opts?.chatContext)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (opts?.requireFromMe && cached?.isFromMe !== true) throw buildFromMeError(trimmed, "uuid");
|
|
355
|
-
return trimmed;
|
|
331
|
+
const cached = imessageReplyCacheByMessageId.get(messageId);
|
|
332
|
+
if (cached && opts?.chatContext && (inputKind === "uuid" || hasChatScope(opts.chatContext)) && resolveIMessageChatMatch(cached, opts.chatContext) === "mismatch") throw buildCrossChatError(trimmed, inputKind, cached, opts.chatContext);
|
|
333
|
+
if (opts?.requireFromMe && cached?.isFromMe !== true) throw buildFromMeError(trimmed, inputKind);
|
|
334
|
+
return messageId;
|
|
356
335
|
}
|
|
357
336
|
async function isKnownFromMeIMessageMessageId(messageId, ctx) {
|
|
358
337
|
const trimmed = normalizeOptionalString(messageId);
|
|
@@ -380,33 +359,15 @@ async function isKnownFromMeIMessageTarget(params) {
|
|
|
380
359
|
function buildFromMeError(inputId, inputKind) {
|
|
381
360
|
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.`);
|
|
382
361
|
}
|
|
383
|
-
/**
|
|
384
|
-
* Return the most recent cached entry whose chat scope matches the supplied
|
|
385
|
-
* context. Used as a fallback when an agent calls a per-message action (e.g.
|
|
386
|
-
* `react`) without specifying a `messageId` — the natural intent is "react
|
|
387
|
-
* to the message I just received in this chat."
|
|
388
|
-
*
|
|
389
|
-
* Strict semantics for safety:
|
|
390
|
-
* - Caller must supply a chat scope. We refuse to "guess" the active chat.
|
|
391
|
-
* - Cached entry must positively match on at least one identifier kind
|
|
392
|
-
* (chatGuid, chatIdentifier, chatId, or normalized direct-DM fingerprint).
|
|
393
|
-
* We do NOT fall through on "no overlapping identifier" — that's how a
|
|
394
|
-
* cached entry from a foreign chat could be returned when the caller's
|
|
395
|
-
* context didn't share any identifier kind with the cache.
|
|
396
|
-
* - Caller must supply an accountId; we never cross account boundaries.
|
|
397
|
-
* - We only consider entries newer than `LATEST_FALLBACK_MS`. The intent
|
|
398
|
-
* of "react to the latest" is "the message I just received," not
|
|
399
|
-
* "anything in this chat from any time."
|
|
400
|
-
*/
|
|
362
|
+
/** Latest recent entry with a positive same-account conversation match; never guess a chat. */
|
|
401
363
|
function findLatestIMessageEntryForChat(ctx) {
|
|
402
|
-
if (!hasChatScope(ctx)) return;
|
|
403
|
-
if (!ctx.accountId) return;
|
|
364
|
+
if (!hasChatScope(ctx) || !ctx.accountId) return;
|
|
404
365
|
const cutoff = Date.now() - LATEST_FALLBACK_MS;
|
|
405
366
|
let best;
|
|
406
367
|
for (const entry of imessageReplyCacheByMessageId.values()) {
|
|
407
368
|
if (entry.accountId !== ctx.accountId) continue;
|
|
408
369
|
if (entry.timestamp < cutoff) continue;
|
|
409
|
-
if (
|
|
370
|
+
if (resolveIMessageChatMatch(entry, ctx) !== "match") continue;
|
|
410
371
|
if (!best || entry.timestamp > best.timestamp) best = entry;
|
|
411
372
|
}
|
|
412
373
|
return best;
|
|
@@ -450,8 +411,7 @@ function parseLastJsonObject(stdout) {
|
|
|
450
411
|
const last = stdout.split(/\r?\n/u).findLast((line) => line.trim().length > 0)?.trim();
|
|
451
412
|
if (!last) return null;
|
|
452
413
|
try {
|
|
453
|
-
|
|
454
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
414
|
+
return asNullableRecord(JSON.parse(last));
|
|
455
415
|
} catch {
|
|
456
416
|
return null;
|
|
457
417
|
}
|
|
@@ -702,14 +662,7 @@ var IMessageRpcClient = class {
|
|
|
702
662
|
}
|
|
703
663
|
async waitForReap(timeoutMs) {
|
|
704
664
|
if (this.isReaped) return true;
|
|
705
|
-
|
|
706
|
-
try {
|
|
707
|
-
return await Promise.race([this.reaped.then(() => true), new Promise((resolve) => {
|
|
708
|
-
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
709
|
-
})]);
|
|
710
|
-
} finally {
|
|
711
|
-
if (timer) clearTimeout(timer);
|
|
712
|
-
}
|
|
665
|
+
return await raceWithTimeout(this.reaped.then(() => true), timeoutMs, () => false);
|
|
713
666
|
}
|
|
714
667
|
signalChild(child, signal) {
|
|
715
668
|
try {
|
|
@@ -813,566 +766,4 @@ async function createIMessageRpcClient(opts = {}) {
|
|
|
813
766
|
return client;
|
|
814
767
|
}
|
|
815
768
|
//#endregion
|
|
816
|
-
|
|
817
|
-
const IMESSAGE_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
818
|
-
mechanism: "ranges",
|
|
819
|
-
constructs: {
|
|
820
|
-
spoiler: "strip",
|
|
821
|
-
codeInline: "fallback",
|
|
822
|
-
codeBlock: "fallback",
|
|
823
|
-
codeLanguage: "strip",
|
|
824
|
-
linkLabel: "fallback",
|
|
825
|
-
heading: "fallback",
|
|
826
|
-
bulletList: "fallback",
|
|
827
|
-
orderedList: "fallback",
|
|
828
|
-
taskList: "fallback",
|
|
829
|
-
table: "fallback",
|
|
830
|
-
blockquote: "fallback",
|
|
831
|
-
image: "fallback",
|
|
832
|
-
mention: "strip"
|
|
833
|
-
},
|
|
834
|
-
chunk: {
|
|
835
|
-
limit: 4e3,
|
|
836
|
-
unit: "utf16"
|
|
837
|
-
}
|
|
838
|
-
});
|
|
839
|
-
const IMESSAGE_CODE_PROFILE = FormatCapabilityProfile.define({
|
|
840
|
-
...IMESSAGE_FORMAT_PROFILE,
|
|
841
|
-
constructs: {
|
|
842
|
-
...IMESSAGE_FORMAT_PROFILE.constructs,
|
|
843
|
-
codeInline: "native"
|
|
844
|
-
}
|
|
845
|
-
});
|
|
846
|
-
const IMESSAGE_STYLE_MAP = {
|
|
847
|
-
bold: "bold",
|
|
848
|
-
italic: "italic",
|
|
849
|
-
underline: "underline",
|
|
850
|
-
strikethrough: "strikethrough"
|
|
851
|
-
};
|
|
852
|
-
function codeDelimiter(content) {
|
|
853
|
-
const longestRun = Math.max(0, ...[...content.matchAll(/`+/gu)].map((match) => match[0].length));
|
|
854
|
-
return "`".repeat(longestRun + 1);
|
|
855
|
-
}
|
|
856
|
-
function restoreCodeMarkers(text, ranges, codeRanges) {
|
|
857
|
-
if (codeRanges.length === 0) return {
|
|
858
|
-
text,
|
|
859
|
-
ranges
|
|
860
|
-
};
|
|
861
|
-
let rendered = "";
|
|
862
|
-
let cursor = 0;
|
|
863
|
-
const edits = codeRanges.map((range) => {
|
|
864
|
-
const end = range.start + range.length;
|
|
865
|
-
const content = text.slice(range.start, end);
|
|
866
|
-
const marker = codeDelimiter(content);
|
|
867
|
-
const padding = content.startsWith("`") || content.endsWith("`") ? " " : "";
|
|
868
|
-
rendered += text.slice(cursor, range.start) + `${marker}${padding}${content}${padding}${marker}`;
|
|
869
|
-
cursor = end;
|
|
870
|
-
return {
|
|
871
|
-
end,
|
|
872
|
-
shift: rendered.length - end
|
|
873
|
-
};
|
|
874
|
-
});
|
|
875
|
-
rendered += text.slice(cursor);
|
|
876
|
-
const mapOffset = (offset) => {
|
|
877
|
-
let low = 0;
|
|
878
|
-
let high = edits.length;
|
|
879
|
-
while (low < high) {
|
|
880
|
-
const middle = low + Math.floor((high - low) / 2);
|
|
881
|
-
const edit = edits[middle];
|
|
882
|
-
if (edit && edit.end <= offset) low = middle + 1;
|
|
883
|
-
else high = middle;
|
|
884
|
-
}
|
|
885
|
-
return offset + (edits[low - 1]?.shift ?? 0);
|
|
886
|
-
};
|
|
887
|
-
return {
|
|
888
|
-
text: rendered,
|
|
889
|
-
ranges: ranges.map((range) => {
|
|
890
|
-
const start = mapOffset(range.start);
|
|
891
|
-
return {
|
|
892
|
-
...range,
|
|
893
|
-
start,
|
|
894
|
-
length: mapOffset(range.start + range.length) - start
|
|
895
|
-
};
|
|
896
|
-
})
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
function extractMarkdownFormatRuns(input) {
|
|
900
|
-
const ir = markdownToIR(input, {
|
|
901
|
-
autolink: false,
|
|
902
|
-
enableHtmlUnderline: true,
|
|
903
|
-
headingStyle: "rich",
|
|
904
|
-
linkify: false,
|
|
905
|
-
preserveDunderIdentifiers: true,
|
|
906
|
-
preserveSourceBlockSpacing: true
|
|
907
|
-
});
|
|
908
|
-
const rendered = renderMarkdownWithAttributedRanges(ir, { styleMap: IMESSAGE_STYLE_MAP }, IMESSAGE_FORMAT_PROFILE);
|
|
909
|
-
const codeRanges = ir.styles.some((span) => span.style === "code") ? renderMarkdownWithAttributedRanges(ir, { styleMap: { code: "code" } }, IMESSAGE_CODE_PROFILE).ranges : [];
|
|
910
|
-
return restoreCodeMarkers(rendered.text, rendered.ranges.map(({ start, length, style }) => ({
|
|
911
|
-
start,
|
|
912
|
-
length,
|
|
913
|
-
styles: [style]
|
|
914
|
-
})), codeRanges);
|
|
915
|
-
}
|
|
916
|
-
//#endregion
|
|
917
|
-
//#region extensions/imessage/src/monitor/sanitize-outbound.ts
|
|
918
|
-
/**
|
|
919
|
-
* Patterns that indicate assistant-internal metadata leaked into text.
|
|
920
|
-
* These must never reach a user-facing channel.
|
|
921
|
-
*/
|
|
922
|
-
const INTERNAL_SEPARATOR_RE = /(?:#\+){2,}#?/g;
|
|
923
|
-
const ASSISTANT_ROLE_MARKER_RE = /\bassistant\s+to\s*=\s*\w+/gi;
|
|
924
|
-
const ROLE_TURN_MARKER_RE = /^[ \t]*(?:>[ \t]*)*(?:user|system|assistant)[ \t]*:[ \t]*(?=\r?$)/gim;
|
|
925
|
-
const FENCED_ROLE_MARKER_RE = /^[ \t]*(user|system|assistant)[ \t]*:[ \t]*(?=\r?$)/gim;
|
|
926
|
-
const FINAL_PRIVATE_MARKER_RE = /(?:#\+){2,}#?|\bassistant\s+to\s*=\s*\w+|^[ \t]*(?:user|system|assistant)[ \t]*:[ \t]*(?=\r?$)/gim;
|
|
927
|
-
const PRIVATE_USE_START = 57344;
|
|
928
|
-
const PRIVATE_USE_END = 63743;
|
|
929
|
-
const MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES = 32;
|
|
930
|
-
const PLAIN_TEXT_HTML_TAG_RE = /<\/?[a-z][a-z0-9_.:-]*(?=[\s/>])[^>]*>/gi;
|
|
931
|
-
const PRIVATE_PROVIDER_TAG_NAMES = [
|
|
932
|
-
"think",
|
|
933
|
-
"thinking",
|
|
934
|
-
"thought",
|
|
935
|
-
"reasoning",
|
|
936
|
-
"antthinking",
|
|
937
|
-
"antml:think",
|
|
938
|
-
"antml:thinking",
|
|
939
|
-
"antml:thought",
|
|
940
|
-
"antml:reasoning",
|
|
941
|
-
"mm:think",
|
|
942
|
-
"mm:thinking",
|
|
943
|
-
"mm:thought",
|
|
944
|
-
"mm:reasoning",
|
|
945
|
-
"relevant_memories",
|
|
946
|
-
"relevant-memories",
|
|
947
|
-
"tool_call",
|
|
948
|
-
"tool_result",
|
|
949
|
-
"function_call",
|
|
950
|
-
"function_calls",
|
|
951
|
-
"function_response",
|
|
952
|
-
"function",
|
|
953
|
-
"tool_calls",
|
|
954
|
-
"antml:invoke",
|
|
955
|
-
"antml:parameter",
|
|
956
|
-
"invoke",
|
|
957
|
-
"parameter"
|
|
958
|
-
];
|
|
959
|
-
const PRIVATE_PROVIDER_TAG_NAME_SET = new Set(PRIVATE_PROVIDER_TAG_NAMES);
|
|
960
|
-
const OPAQUE_PRIVATE_RUNTIME_CONTEXT_BLOCK_RE = /^<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>(?:(?!<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>)[\s\S])*<<<END_OPENCLAW_INTERNAL_CONTEXT>>>/;
|
|
961
|
-
const PRIVATE_RUNTIME_CONTEXT_BLOCK_RE = /<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>(?:(?!<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>)[\s\S])*<<<END_OPENCLAW_INTERNAL_CONTEXT>>>/g;
|
|
962
|
-
const NESTED_PRIVATE_MARKUP_START_RE = /<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>|<\s*\/?\s*[A-Za-z][A-Za-z0-9_.:-]*(?=\s|\/?>)/g;
|
|
963
|
-
const OUTER_PRIVATE_TAG_FRAGMENT_RE = /^\s*\/?\s*([a-z][a-z0-9_.:-]*)?$/i;
|
|
964
|
-
const OPAQUE_PRIVATE_MARKUP_BLOCK_RE = /^<\s*(system-reminder|previous_response|details|summary)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/i;
|
|
965
|
-
const REMOVABLE_PRIVATE_MARKUP_TAG_RE = /^<\s*\/?\s*[a-z][a-z0-9_.:-]*(?=\s|\/?>)[^>]*>/i;
|
|
966
|
-
const PRIVATE_PROVIDER_TAG_FRAGMENT_RE = /^[a-z0-9_.:-]*/i;
|
|
967
|
-
const PRIVATE_RUNTIME_SCAFFOLDING_TAG_TOKEN_RE = /<\s*(\/?)\s*(system-reminder|previous_response)\b/gi;
|
|
968
|
-
function assertSafeIMessageOutboundMarkup(text) {
|
|
969
|
-
for (const nested of text.matchAll(NESTED_PRIVATE_MARKUP_START_RE)) {
|
|
970
|
-
const nestedStart = nested.index ?? 0;
|
|
971
|
-
const outerStart = text.lastIndexOf("<", nestedStart - 1);
|
|
972
|
-
if (outerStart < 0) continue;
|
|
973
|
-
const outer = OUTER_PRIVATE_TAG_FRAGMENT_RE.exec(text.slice(outerStart + 1, nestedStart));
|
|
974
|
-
if (!outer) continue;
|
|
975
|
-
let candidate = (outer[1] ?? "").toLowerCase();
|
|
976
|
-
if (!PRIVATE_PROVIDER_TAG_NAMES.some((name) => name.startsWith(candidate))) continue;
|
|
977
|
-
let cursor = nestedStart;
|
|
978
|
-
for (let depth = 0; depth < MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES; depth += 1) {
|
|
979
|
-
const remaining = text.slice(cursor);
|
|
980
|
-
const removed = OPAQUE_PRIVATE_RUNTIME_CONTEXT_BLOCK_RE.exec(remaining)?.[0] ?? OPAQUE_PRIVATE_MARKUP_BLOCK_RE.exec(remaining)?.[0] ?? REMOVABLE_PRIVATE_MARKUP_TAG_RE.exec(remaining)?.[0];
|
|
981
|
-
if (!removed) break;
|
|
982
|
-
cursor += removed.length;
|
|
983
|
-
const suffix = PRIVATE_PROVIDER_TAG_FRAGMENT_RE.exec(text.slice(cursor))?.[0] ?? "";
|
|
984
|
-
candidate += suffix.toLowerCase();
|
|
985
|
-
cursor += suffix.length;
|
|
986
|
-
if (PRIVATE_PROVIDER_TAG_NAME_SET.has(candidate) && /[\s/>]/.test(text.charAt(cursor))) throw new Error("iMessage outbound ambiguous nested HTML is not allowed");
|
|
987
|
-
if (!PRIVATE_PROVIDER_TAG_NAMES.some((name) => name.startsWith(candidate))) break;
|
|
988
|
-
if (depth + 1 >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES && (OPAQUE_PRIVATE_RUNTIME_CONTEXT_BLOCK_RE.test(text.slice(cursor)) || REMOVABLE_PRIVATE_MARKUP_TAG_RE.test(text.slice(cursor)))) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
function stripIMessageBalancedPrivateRuntimeBlocks(text, verifyProtectedRoles) {
|
|
993
|
-
const openBlocks = [];
|
|
994
|
-
const completedBlocks = [];
|
|
995
|
-
const codeRegions = findCodeRegions(text);
|
|
996
|
-
const parsedHtmlTags = [...tokenizeHtmlTags(text)];
|
|
997
|
-
const htmlTags = parsedHtmlTags.map(({ start, end }) => ({
|
|
998
|
-
start,
|
|
999
|
-
end,
|
|
1000
|
-
terminated: true
|
|
1001
|
-
}));
|
|
1002
|
-
const rawTextHtml = [];
|
|
1003
|
-
const rawTextTagNames = /* @__PURE__ */ new Set([
|
|
1004
|
-
"script",
|
|
1005
|
-
"style",
|
|
1006
|
-
"textarea",
|
|
1007
|
-
"title",
|
|
1008
|
-
"xmp",
|
|
1009
|
-
"iframe",
|
|
1010
|
-
"noembed",
|
|
1011
|
-
"noframes",
|
|
1012
|
-
"noscript",
|
|
1013
|
-
"plaintext"
|
|
1014
|
-
]);
|
|
1015
|
-
for (const [index, opener] of parsedHtmlTags.entries()) {
|
|
1016
|
-
if (opener.closing || !rawTextTagNames.has(opener.name)) continue;
|
|
1017
|
-
const closing = opener.name === "plaintext" ? void 0 : parsedHtmlTags.slice(index + 1).find((candidate) => {
|
|
1018
|
-
return candidate.closing && candidate.name === opener.name;
|
|
1019
|
-
});
|
|
1020
|
-
rawTextHtml.push({
|
|
1021
|
-
start: opener.start,
|
|
1022
|
-
end: closing?.end ?? text.length,
|
|
1023
|
-
terminated: Boolean(closing)
|
|
1024
|
-
});
|
|
1025
|
-
if (rawTextHtml.length > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1026
|
-
}
|
|
1027
|
-
const opaqueHtml = [];
|
|
1028
|
-
for (let cursor = 0; cursor < text.length;) {
|
|
1029
|
-
const start = text.indexOf("<", cursor);
|
|
1030
|
-
if (start < 0) break;
|
|
1031
|
-
const enclosingTag = htmlTags.find((tag) => tag.start < start && start < tag.end);
|
|
1032
|
-
if (enclosingTag) {
|
|
1033
|
-
cursor = enclosingTag.end;
|
|
1034
|
-
continue;
|
|
1035
|
-
}
|
|
1036
|
-
let end = -1;
|
|
1037
|
-
let delimiterLength = 0;
|
|
1038
|
-
if (text.startsWith("<!--", start)) {
|
|
1039
|
-
end = text.indexOf("-->", start + 4);
|
|
1040
|
-
delimiterLength = 3;
|
|
1041
|
-
} else if (/^<!\[CDATA\[/i.test(text.slice(start, start + 9))) {
|
|
1042
|
-
end = text.indexOf("]]>", start + 9);
|
|
1043
|
-
delimiterLength = 3;
|
|
1044
|
-
} else if (text.startsWith("<?", start)) {
|
|
1045
|
-
let quote;
|
|
1046
|
-
for (let index = start + 2; index < text.length - 1; index += 1) {
|
|
1047
|
-
const character = text[index];
|
|
1048
|
-
if (quote) {
|
|
1049
|
-
if (character === quote) quote = void 0;
|
|
1050
|
-
} else if (character === "'" || character === "\"") quote = character;
|
|
1051
|
-
else if (character === "?" && text[index + 1] === ">") {
|
|
1052
|
-
end = index;
|
|
1053
|
-
delimiterLength = 2;
|
|
1054
|
-
break;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
} else if (/^<![a-z][a-z0-9_.:-]*\b/i.test(text.slice(start))) {
|
|
1058
|
-
let quote;
|
|
1059
|
-
let bracketDepth = 0;
|
|
1060
|
-
for (let index = start + 2; index < text.length; index += 1) {
|
|
1061
|
-
const character = text[index];
|
|
1062
|
-
if (quote) {
|
|
1063
|
-
if (character === quote) quote = void 0;
|
|
1064
|
-
} else if (character === "'" || character === "\"") quote = character;
|
|
1065
|
-
else if (character === "[") bracketDepth += 1;
|
|
1066
|
-
else if (character === "]" && bracketDepth > 0) bracketDepth -= 1;
|
|
1067
|
-
else if (character === ">" && bracketDepth === 0) {
|
|
1068
|
-
end = index;
|
|
1069
|
-
delimiterLength = 1;
|
|
1070
|
-
break;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
} else if (text.startsWith("<!", start) || text.startsWith("</", start) && start + 2 < text.length && !/[A-Za-z>]/.test(text[start + 2] ?? "")) {
|
|
1074
|
-
end = text.indexOf(">", start + 2);
|
|
1075
|
-
delimiterLength = 1;
|
|
1076
|
-
} else {
|
|
1077
|
-
cursor = start + 1;
|
|
1078
|
-
continue;
|
|
1079
|
-
}
|
|
1080
|
-
if (opaqueHtml.length >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1081
|
-
const terminated = end >= 0;
|
|
1082
|
-
const regionEnd = terminated ? end + delimiterLength : text.length;
|
|
1083
|
-
opaqueHtml.push({
|
|
1084
|
-
start,
|
|
1085
|
-
end: regionEnd,
|
|
1086
|
-
terminated
|
|
1087
|
-
});
|
|
1088
|
-
cursor = regionEnd;
|
|
1089
|
-
}
|
|
1090
|
-
htmlTags.push(...opaqueHtml);
|
|
1091
|
-
htmlTags.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
1092
|
-
let htmlTagIndex = 0;
|
|
1093
|
-
let scannedTags = 0;
|
|
1094
|
-
let scannedThrough = 0;
|
|
1095
|
-
for (const token of text.matchAll(PRIVATE_RUNTIME_SCAFFOLDING_TAG_TOKEN_RE)) {
|
|
1096
|
-
const start = token.index ?? 0;
|
|
1097
|
-
if (start < scannedThrough) continue;
|
|
1098
|
-
while ((htmlTags[htmlTagIndex]?.end ?? 0) <= start && htmlTagIndex < htmlTags.length) htmlTagIndex += 1;
|
|
1099
|
-
const enclosingHtmlTag = htmlTags[htmlTagIndex];
|
|
1100
|
-
if (enclosingHtmlTag && enclosingHtmlTag.start < start && start < enclosingHtmlTag.end) continue;
|
|
1101
|
-
const codeRegion = codeRegions.find((region) => isInsideCode(start, [region]));
|
|
1102
|
-
const enclosingBlock = openBlocks.at(-1);
|
|
1103
|
-
const enclosingRawText = rawTextHtml.find((region) => {
|
|
1104
|
-
return region.start < start && start < region.end;
|
|
1105
|
-
});
|
|
1106
|
-
if (enclosingBlock && enclosingRawText && enclosingBlock.start < enclosingRawText.start) {
|
|
1107
|
-
enclosingBlock.sawNested = true;
|
|
1108
|
-
continue;
|
|
1109
|
-
}
|
|
1110
|
-
if (enclosingBlock && codeRegion && enclosingBlock.codeRegion !== codeRegion) {
|
|
1111
|
-
enclosingBlock.sawNested = true;
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
if (enclosingBlock?.codeRegion && enclosingBlock.codeRegion !== codeRegion) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
1115
|
-
if (++scannedTags > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1116
|
-
let quote;
|
|
1117
|
-
let end = start + token[0].length;
|
|
1118
|
-
for (; end < text.length; end += 1) {
|
|
1119
|
-
if (end - start > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1120
|
-
const character = text[end];
|
|
1121
|
-
if (quote) {
|
|
1122
|
-
if (character === quote) quote = void 0;
|
|
1123
|
-
} else if (character === "'" || character === "\"") quote = character;
|
|
1124
|
-
else if (character === "<") throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
1125
|
-
else if (character === ">") break;
|
|
1126
|
-
}
|
|
1127
|
-
if (end >= text.length || quote) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
1128
|
-
end += 1;
|
|
1129
|
-
if (enclosingBlock?.codeRegion && enclosingBlock.codeRegion === codeRegion) {
|
|
1130
|
-
if (!enclosingBlock.nestedCodeRegions) {
|
|
1131
|
-
if (codeRegion.end - enclosingBlock.end > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 3) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1132
|
-
enclosingBlock.nestedCodeRegions = findCodeRegions(text.slice(enclosingBlock.end, codeRegion.end));
|
|
1133
|
-
if (enclosingBlock.nestedCodeRegions.length > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1134
|
-
}
|
|
1135
|
-
if (isInsideCode(start - enclosingBlock.end, enclosingBlock.nestedCodeRegions)) {
|
|
1136
|
-
enclosingBlock.sawNested = true;
|
|
1137
|
-
continue;
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
scannedThrough = end;
|
|
1141
|
-
const markup = text.slice(start, end);
|
|
1142
|
-
const closing = token[1] === "/";
|
|
1143
|
-
if (!closing && /\/\s*>$/.test(markup)) {
|
|
1144
|
-
const parent = openBlocks.at(-1);
|
|
1145
|
-
if (parent) parent.sawNested = true;
|
|
1146
|
-
else completedBlocks.push({
|
|
1147
|
-
start,
|
|
1148
|
-
end
|
|
1149
|
-
});
|
|
1150
|
-
continue;
|
|
1151
|
-
}
|
|
1152
|
-
const name = (token[2] ?? "").toLowerCase();
|
|
1153
|
-
if (!closing) {
|
|
1154
|
-
if (openBlocks.length >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1155
|
-
const parent = openBlocks.at(-1);
|
|
1156
|
-
if (parent) parent.sawNested = true;
|
|
1157
|
-
openBlocks.push({
|
|
1158
|
-
name,
|
|
1159
|
-
start,
|
|
1160
|
-
end,
|
|
1161
|
-
sawNested: false,
|
|
1162
|
-
codeRegion
|
|
1163
|
-
});
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
const opening = openBlocks.at(-1);
|
|
1167
|
-
if (!opening) {
|
|
1168
|
-
completedBlocks.push({
|
|
1169
|
-
start,
|
|
1170
|
-
end
|
|
1171
|
-
});
|
|
1172
|
-
continue;
|
|
1173
|
-
}
|
|
1174
|
-
if (opening.name !== name) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
1175
|
-
openBlocks.pop();
|
|
1176
|
-
completedBlocks.push({
|
|
1177
|
-
start: opening.start,
|
|
1178
|
-
end
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
if (openBlocks.length > 1 || openBlocks[0]?.sawNested || openBlocks[0] && [...opaqueHtml, ...rawTextHtml].some((region) => !region.terminated && openBlocks[0].start < region.start)) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
1182
|
-
const orphan = openBlocks[0];
|
|
1183
|
-
if (orphan) completedBlocks.push({
|
|
1184
|
-
start: orphan.start,
|
|
1185
|
-
end: orphan.end
|
|
1186
|
-
});
|
|
1187
|
-
completedBlocks.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
1188
|
-
const outermost = [];
|
|
1189
|
-
for (const block of completedBlocks) {
|
|
1190
|
-
const previous = outermost.at(-1);
|
|
1191
|
-
if (!previous || block.start >= previous.end) outermost.push(block);
|
|
1192
|
-
}
|
|
1193
|
-
let current = text;
|
|
1194
|
-
for (const block of outermost.toReversed()) {
|
|
1195
|
-
verifyProtectedRoles?.(current);
|
|
1196
|
-
const stripped = current.slice(0, block.start) + current.slice(block.end);
|
|
1197
|
-
assertSafeIMessageOutboundMarkup(stripped);
|
|
1198
|
-
verifyProtectedRoles?.(stripped);
|
|
1199
|
-
current = stripped;
|
|
1200
|
-
}
|
|
1201
|
-
return current;
|
|
1202
|
-
}
|
|
1203
|
-
function stripIMessagePrivateRuntimeScaffolding(text, verifyProtectedRoles) {
|
|
1204
|
-
assertSafeIMessageOutboundMarkup(text);
|
|
1205
|
-
let current = text;
|
|
1206
|
-
for (let depth = 0; depth < MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES; depth += 1) {
|
|
1207
|
-
let changed = false;
|
|
1208
|
-
for (const strip of [(value) => value.replace(PRIVATE_RUNTIME_CONTEXT_BLOCK_RE, ""), (value) => stripIMessageBalancedPrivateRuntimeBlocks(value, verifyProtectedRoles)]) {
|
|
1209
|
-
verifyProtectedRoles?.(current);
|
|
1210
|
-
const stripped = strip(current);
|
|
1211
|
-
assertSafeIMessageOutboundMarkup(stripped);
|
|
1212
|
-
verifyProtectedRoles?.(stripped);
|
|
1213
|
-
if (stripped !== current) {
|
|
1214
|
-
current = stripped;
|
|
1215
|
-
changed = true;
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
if (!changed) return current;
|
|
1219
|
-
}
|
|
1220
|
-
throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
1221
|
-
}
|
|
1222
|
-
function sanitizeIMessageAssistantVisibleText(text, verifyProtectedRoles) {
|
|
1223
|
-
const cleaned = stripIMessagePrivateRuntimeScaffolding(text, verifyProtectedRoles);
|
|
1224
|
-
if (!text.trim()) return text;
|
|
1225
|
-
const leadingWhitespace = /^\s*/u.exec(text)?.[0] ?? "";
|
|
1226
|
-
const trailingWhitespace = /\s*$/u.exec(text)?.[0] ?? "";
|
|
1227
|
-
verifyProtectedRoles?.(cleaned);
|
|
1228
|
-
const canonical = cleaned.trim();
|
|
1229
|
-
verifyProtectedRoles?.(canonical);
|
|
1230
|
-
const visible = sanitizeAssistantVisibleText(canonical);
|
|
1231
|
-
verifyProtectedRoles?.(visible);
|
|
1232
|
-
return visible ? `${leadingWhitespace}${visible}${trailingWhitespace}` : "";
|
|
1233
|
-
}
|
|
1234
|
-
function isClosedFencedCodeRegion(text, region) {
|
|
1235
|
-
const lineStart = text.lastIndexOf("\n", region.start - 1) + 1;
|
|
1236
|
-
if (!/^ {0,3}$/.test(text.slice(lineStart, region.start))) return false;
|
|
1237
|
-
const source = text.slice(region.start, region.end);
|
|
1238
|
-
const openingFence = /^ {0,3}(`{3,}|~{3,})[^\r\n]*\r?\n/.exec(source)?.[1];
|
|
1239
|
-
if (!openingFence) return false;
|
|
1240
|
-
const closingFence = new RegExp(`\r?\n {0,3}${openingFence[0]}{${openingFence.length},}[\t ]*(?:\r?\n)?$`);
|
|
1241
|
-
const lineEnd = text.indexOf("\n", region.end);
|
|
1242
|
-
return closingFence.test(source) && /^[\t ]*\r?$/.test(text.slice(region.end, lineEnd < 0 ? text.length : lineEnd));
|
|
1243
|
-
}
|
|
1244
|
-
/** Preserve authenticated fenced role mappings through the final native-render scrub. */
|
|
1245
|
-
function protectIMessageFencedRoleMarkers(text) {
|
|
1246
|
-
const source = stripIMessagePrivateRuntimeScaffolding(text);
|
|
1247
|
-
const closedFencedRegions = findCodeRegions(source).filter((region) => isClosedFencedCodeRegion(source, region));
|
|
1248
|
-
const protectedRoles = /* @__PURE__ */ new Map();
|
|
1249
|
-
let privateUseCodePoint = PRIVATE_USE_START;
|
|
1250
|
-
const protectedText = source.replace(FENCED_ROLE_MARKER_RE, (marker, role, offset) => {
|
|
1251
|
-
if (!isInsideCode(offset, closedFencedRegions)) return marker;
|
|
1252
|
-
while (privateUseCodePoint <= PRIVATE_USE_END) {
|
|
1253
|
-
const protectedGlyph = String.fromCharCode(privateUseCodePoint++);
|
|
1254
|
-
const token = protectedGlyph.repeat(role.length);
|
|
1255
|
-
if (text.includes(protectedGlyph) || protectedRoles.has(token)) continue;
|
|
1256
|
-
protectedRoles.set(token, role);
|
|
1257
|
-
return marker.replace(role, token);
|
|
1258
|
-
}
|
|
1259
|
-
throw new Error("iMessage outbound role protection is unavailable");
|
|
1260
|
-
});
|
|
1261
|
-
const verifyProtectedRoles = (visible) => {
|
|
1262
|
-
assertSafeIMessageOutboundMarkup(visible);
|
|
1263
|
-
let previous = -1;
|
|
1264
|
-
for (const token of protectedRoles.keys()) {
|
|
1265
|
-
const first = visible.indexOf(token);
|
|
1266
|
-
if (first < 0 || first < previous || visible.includes(token, first + token.length)) throw new Error("iMessage outbound role protection failed");
|
|
1267
|
-
for (let offset = visible.indexOf(token[0] ?? ""); offset >= 0;) {
|
|
1268
|
-
if (offset < first || offset >= first + token.length) throw new Error("iMessage outbound role protection failed");
|
|
1269
|
-
offset = visible.indexOf(token[0] ?? "", offset + 1);
|
|
1270
|
-
}
|
|
1271
|
-
previous = first + token.length;
|
|
1272
|
-
}
|
|
1273
|
-
};
|
|
1274
|
-
return {
|
|
1275
|
-
text: protectedText,
|
|
1276
|
-
verifyProtectedRoles,
|
|
1277
|
-
sanitizeFormatted: (formatted) => {
|
|
1278
|
-
verifyProtectedRoles(formatted.text);
|
|
1279
|
-
let visible = formatted.text;
|
|
1280
|
-
let ranges = formatted.ranges;
|
|
1281
|
-
for (let remainingPasses = visible.length;; remainingPasses--) {
|
|
1282
|
-
verifyProtectedRoles(visible);
|
|
1283
|
-
const assistantVisible = sanitizeIMessageAssistantVisibleText(visible, verifyProtectedRoles);
|
|
1284
|
-
const assistantTextChanged = assistantVisible !== visible;
|
|
1285
|
-
if (assistantTextChanged) {
|
|
1286
|
-
visible = assistantVisible;
|
|
1287
|
-
ranges = [];
|
|
1288
|
-
verifyProtectedRoles(visible);
|
|
1289
|
-
}
|
|
1290
|
-
const removed = [];
|
|
1291
|
-
const cleaned = visible.replace(FINAL_PRIVATE_MARKER_RE, (marker, offset) => {
|
|
1292
|
-
removed.push({
|
|
1293
|
-
start: offset,
|
|
1294
|
-
end: offset + marker.length
|
|
1295
|
-
});
|
|
1296
|
-
return "";
|
|
1297
|
-
});
|
|
1298
|
-
if (removed.length > 0) {
|
|
1299
|
-
const mapOffset = (offset) => offset - removed.reduce((total, edit) => total + Math.max(0, Math.min(offset, edit.end) - edit.start), 0);
|
|
1300
|
-
ranges = ranges.flatMap((range) => {
|
|
1301
|
-
const start = mapOffset(range.start);
|
|
1302
|
-
const length = mapOffset(range.start + range.length) - start;
|
|
1303
|
-
return length > 0 ? [{
|
|
1304
|
-
...range,
|
|
1305
|
-
start,
|
|
1306
|
-
length
|
|
1307
|
-
}] : [];
|
|
1308
|
-
});
|
|
1309
|
-
visible = cleaned;
|
|
1310
|
-
verifyProtectedRoles(visible);
|
|
1311
|
-
}
|
|
1312
|
-
if (removed.length === 0) {
|
|
1313
|
-
let projection = visible;
|
|
1314
|
-
for (let depth = 0;; depth += 1) {
|
|
1315
|
-
verifyProtectedRoles(projection);
|
|
1316
|
-
const renderedProjection = extractMarkdownFormatRuns(projection).text;
|
|
1317
|
-
verifyProtectedRoles(renderedProjection);
|
|
1318
|
-
const unwrappedProjection = stripMarkdown(renderedProjection);
|
|
1319
|
-
verifyProtectedRoles(unwrappedProjection);
|
|
1320
|
-
let htmlProjection = unwrappedProjection;
|
|
1321
|
-
for (let htmlDepth = 0;; htmlDepth += 1) {
|
|
1322
|
-
verifyProtectedRoles(htmlProjection);
|
|
1323
|
-
const assistantProjection = sanitizeIMessageAssistantVisibleText(htmlProjection, verifyProtectedRoles);
|
|
1324
|
-
verifyProtectedRoles(assistantProjection);
|
|
1325
|
-
if (assistantProjection !== htmlProjection) throw new Error("iMessage outbound hidden assistant content is not allowed");
|
|
1326
|
-
const htmlRemoved = htmlProjection.replace(PLAIN_TEXT_HTML_TAG_RE, "");
|
|
1327
|
-
verifyProtectedRoles(htmlRemoved);
|
|
1328
|
-
if (htmlRemoved === htmlProjection) break;
|
|
1329
|
-
if (htmlDepth + 1 >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES) throw new Error("iMessage outbound HTML security projection exceeded its limit");
|
|
1330
|
-
htmlProjection = htmlRemoved;
|
|
1331
|
-
}
|
|
1332
|
-
if (unwrappedProjection === projection) break;
|
|
1333
|
-
if (depth + 1 >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES) throw new Error("iMessage outbound Markdown security projection exceeded its limit");
|
|
1334
|
-
projection = unwrappedProjection;
|
|
1335
|
-
}
|
|
1336
|
-
if (!assistantTextChanged) break;
|
|
1337
|
-
}
|
|
1338
|
-
if (remainingPasses <= 0) throw new Error("iMessage outbound role sanitization failed");
|
|
1339
|
-
}
|
|
1340
|
-
verifyProtectedRoles(visible);
|
|
1341
|
-
for (const [token, role] of protectedRoles) visible = visible.replace(token, role);
|
|
1342
|
-
return {
|
|
1343
|
-
text: visible,
|
|
1344
|
-
ranges
|
|
1345
|
-
};
|
|
1346
|
-
}
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
/** Keep each transport's existing rendered or raw wire contract behind one final security gate. */
|
|
1350
|
-
function sanitizeIMessageFinalOutboundText(text, options = {}) {
|
|
1351
|
-
const protection = options.protection ?? protectIMessageFencedRoleMarkers(text);
|
|
1352
|
-
const source = options.protection ? text : protection.text;
|
|
1353
|
-
protection.verifyProtectedRoles(source);
|
|
1354
|
-
const protectedText = sanitizeIMessageAssistantVisibleText(source, protection.verifyProtectedRoles);
|
|
1355
|
-
protection.verifyProtectedRoles(protectedText);
|
|
1356
|
-
const formatted = options.formatMarkdown && protectedText.trim() ? extractMarkdownFormatRuns(protectedText) : {
|
|
1357
|
-
text: protectedText,
|
|
1358
|
-
ranges: []
|
|
1359
|
-
};
|
|
1360
|
-
return protection.sanitizeFormatted(formatted);
|
|
1361
|
-
}
|
|
1362
|
-
/**
|
|
1363
|
-
* Strip all assistant-internal scaffolding from outbound text before delivery.
|
|
1364
|
-
* Applies reasoning/thinking tag removal, memory tag removal, and
|
|
1365
|
-
* model-specific internal separator stripping.
|
|
1366
|
-
*/
|
|
1367
|
-
function sanitizeOutboundText(text) {
|
|
1368
|
-
if (!text) return text;
|
|
1369
|
-
let cleaned = sanitizeIMessageAssistantVisibleText(text);
|
|
1370
|
-
cleaned = cleaned.replace(INTERNAL_SEPARATOR_RE, "");
|
|
1371
|
-
cleaned = cleaned.replace(ASSISTANT_ROLE_MARKER_RE, "");
|
|
1372
|
-
const closedFencedRegions = findCodeRegions(cleaned).filter((region) => isClosedFencedCodeRegion(cleaned, region));
|
|
1373
|
-
cleaned = cleaned.replace(ROLE_TURN_MARKER_RE, (marker, offset) => isInsideCode(offset, closedFencedRegions) ? marker : "");
|
|
1374
|
-
cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
|
|
1375
|
-
return cleaned;
|
|
1376
|
-
}
|
|
1377
|
-
//#endregion
|
|
1378
|
-
export { DEFAULT_IMESSAGE_SEND_TIMEOUT_MS as C, resolveIMessageDirectChatService as D, normalizeDirectChatIdentifier as E, DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS as S, isIMessageEmailChatIdentifier as T, IMESSAGE_SENT_ECHOES_TTL_MS as _, IMessageRpcRequestError as a, resolveIMessageEchoMediaKey as b, findLatestIMessageEntryForChat as c, isKnownFromMeIMessageTarget as d, rememberIMessageReplyCache as f, IMESSAGE_SENT_ECHOES_NAMESPACE as g, IMESSAGE_CATCHUP_CURSOR_NAMESPACE as h, extractMarkdownFormatRuns as i, isIMessageCurrentMessageInChat as l, resolveIMessageMessageId as m, sanitizeIMessageFinalOutboundText as n, createIMessageRpcClient as o, resolveIMessageCachedResourceBinding as p, sanitizeOutboundText as r, runIMessageCliJsonCommand as s, protectIMessageFencedRoleMarkers as t, isIMessageCurrentMessageInChatAsync as u, capFailureRetriesMap as v, chatContextFromIMessageTarget as w, resolveIMessageSentEchoEntryKey as x, resolveIMessageCatchupCursorKey as y };
|
|
769
|
+
export { resolveIMessageDirectChatService as C, normalizeDirectChatIdentifier as S, resolveIMessageSentEchoEntryKey as _, isIMessageCurrentMessageInChat as a, chatContextFromIMessageTarget as b, rememberIMessageReplyCache as c, IMESSAGE_CATCHUP_CURSOR_NAMESPACE as d, IMESSAGE_SENT_ECHOES_NAMESPACE as f, resolveIMessageEchoMediaKey as g, resolveIMessageCatchupCursorKey as h, findLatestIMessageEntryForChat as i, resolveIMessageCachedResourceBinding as l, capFailureRetriesMap as m, createIMessageRpcClient as n, isIMessageCurrentMessageInChatAsync as o, IMESSAGE_SENT_ECHOES_TTL_MS as p, runIMessageCliJsonCommand as r, isKnownFromMeIMessageTarget as s, IMessageRpcRequestError as t, resolveIMessageMessageId as u, DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS as v, isIMessageEmailChatIdentifier as x, DEFAULT_IMESSAGE_SEND_TIMEOUT_MS as y };
|