@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
|
@@ -119,4 +119,4 @@ const IMessageConfigSchema = IMessageAccountSchemaBase.extend({
|
|
|
119
119
|
});
|
|
120
120
|
const IMessageChannelConfigSchema = buildChannelConfigSchema(IMessageConfigSchema, { uiHints: iMessageChannelConfigUiHints });
|
|
121
121
|
//#endregion
|
|
122
|
-
export {
|
|
122
|
+
export { IMessageChannelConfigSchema as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as resolveIMessageAccount } from "./accounts-
|
|
1
|
+
import { o as resolveIMessageAccount } from "./accounts-evTpvJMv.mjs";
|
|
2
2
|
import { mergeInboundPathRoots } from "openclaw/plugin-sdk/channel-inbound";
|
|
3
3
|
//#region extensions/imessage/src/media-contract.ts
|
|
4
4
|
const DEFAULT_IMESSAGE_ATTACHMENT_ROOTS = ["/Users/*/Library/Messages/Attachments"];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as inferIMessageTargetChatType } from "./targets-BicBiYsM.mjs";
|
|
2
|
-
import { c as getCachedIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-
|
|
2
|
+
import { c as getCachedIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-evTpvJMv.mjs";
|
|
3
3
|
import { createActionGate } from "openclaw/plugin-sdk/channel-actions";
|
|
4
4
|
import { Type } from "typebox";
|
|
5
5
|
import { asDateTimestampMs } from "openclaw/plugin-sdk/number-runtime";
|
|
@@ -98,7 +98,7 @@ function describeIMessageMessageTool({ cfg, accountId, chatType, currentChannelI
|
|
|
98
98
|
const actions = /* @__PURE__ */ new Set();
|
|
99
99
|
for (const action of IMESSAGE_ACTION_NAMES) {
|
|
100
100
|
const spec = IMESSAGE_ACTIONS[action];
|
|
101
|
-
if (!
|
|
101
|
+
if (!gate(spec.gate)) continue;
|
|
102
102
|
if (privateApiStatus?.available === false) continue;
|
|
103
103
|
if (action === "edit" && privateApiStatus?.selectors && !privateApiStatus.selectors.editMessage && !privateApiStatus.selectors.editMessageItem) continue;
|
|
104
104
|
if (action === "unsend" && privateApiStatus?.selectors?.retractMessagePart !== true) continue;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BBjsoOtd.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { i as normalizeIMessageGuid } from "./actions-DJqbUmbN.mjs";
|
|
3
|
+
import { t as resolveIMessageReactionContext } from "./reaction-context-By7kUMBC.mjs";
|
|
3
4
|
import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
|
|
4
5
|
//#region extensions/imessage/src/question-reactions.ts
|
|
5
6
|
var question_reactions_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
/** Strip the `p:<n>/` part prefix Messages puts on some GUIDs so keys match. */
|
|
3
|
-
function normalizeIMessageGuid(value) {
|
|
4
|
-
return value.trim().replace(/^p:\d+\//iu, "");
|
|
5
|
-
}
|
|
6
|
-
//#endregion
|
|
1
|
+
import { i as normalizeIMessageGuid } from "./actions-DJqbUmbN.mjs";
|
|
7
2
|
//#region extensions/imessage/src/monitor/reaction-context.ts
|
|
8
3
|
const TAPBACK_TEXT_PATTERNS = [
|
|
9
4
|
{
|
|
@@ -108,4 +103,4 @@ function resolveIMessageReactionContext(message, bodyText) {
|
|
|
108
103
|
return resolveTapbackTextContext(bodyText);
|
|
109
104
|
}
|
|
110
105
|
//#endregion
|
|
111
|
-
export {
|
|
106
|
+
export { resolveIMessageReactionContext as t };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { p as resolveLocalIMessageChatDbPath } from "./accounts-
|
|
2
|
-
import {
|
|
1
|
+
import { p as resolveLocalIMessageChatDbPath } from "./accounts-evTpvJMv.mjs";
|
|
2
|
+
import { b as chatContextFromIMessageTarget, l as resolveIMessageCachedResourceBinding, u as resolveIMessageMessageId, x as isIMessageEmailChatIdentifier } from "./client-12apu4Ip.mjs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { normalizeScpRemoteHost } from "openclaw/plugin-sdk/host-runtime";
|
|
5
5
|
import { createActionGate } from "openclaw/plugin-sdk/channel-actions";
|
|
6
6
|
import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { resolveRuntimeWorkerUrl, runCommandWithTimeout } from "openclaw/plugin-sdk/process-runtime";
|
|
9
|
-
import { openSqliteWorkerStore } from "openclaw/plugin-sdk/sqlite-runtime";
|
|
10
9
|
import { sanitizeTempFileName } from "openclaw/plugin-sdk/temp-path";
|
|
10
|
+
import { openSqliteWorkerStore } from "openclaw/plugin-sdk/sqlite-runtime";
|
|
11
11
|
//#region extensions/imessage/src/chat-db.ts
|
|
12
12
|
function openIMessageChatDbReader(databasePath) {
|
|
13
13
|
return openSqliteWorkerStore({
|
|
@@ -205,11 +205,6 @@ const SSH_OPTIONS = [
|
|
|
205
205
|
];
|
|
206
206
|
const CLEANUP_TIMEOUT_MS = 1e4;
|
|
207
207
|
const log = createSubsystemLogger("channels/imessage");
|
|
208
|
-
function commandError(label, result) {
|
|
209
|
-
if (result.code === 0 && result.termination === "exit") return;
|
|
210
|
-
const detail = result.stderr.trim() || result.stdout.trim();
|
|
211
|
-
return /* @__PURE__ */ new Error(`${label} failed (${result.termination}${result.code === null ? "" : `, code ${result.code}`})${detail ? `: ${detail}` : ""}`);
|
|
212
|
-
}
|
|
213
208
|
async function runChecked(run, label, argv, options) {
|
|
214
209
|
const result = await run(argv, {
|
|
215
210
|
killProcessTree: true,
|
|
@@ -223,9 +218,10 @@ async function runChecked(run, label, argv, options) {
|
|
|
223
218
|
},
|
|
224
219
|
...options
|
|
225
220
|
});
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
221
|
+
if (result.code !== 0 || result.termination !== "exit") {
|
|
222
|
+
const detail = result.stderr.trim() || result.stdout.trim();
|
|
223
|
+
throw new Error(`${label} failed (${result.termination}${result.code === null ? "" : `, code ${result.code}`})${detail ? `: ${detail}` : ""}`);
|
|
224
|
+
}
|
|
229
225
|
}
|
|
230
226
|
function requireToken(createToken) {
|
|
231
227
|
const token = createToken().replaceAll("-", "").toLowerCase();
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
import { FormatCapabilityProfile, findCodeRegions, isInsideCode, markdownToIR, renderMarkdownWithAttributedRanges, sanitizeAssistantVisibleText, stripMarkdown, tokenizeHtmlTags } from "openclaw/plugin-sdk/text-chunking";
|
|
2
|
+
//#region extensions/imessage/src/markdown-format.ts
|
|
3
|
+
const IMESSAGE_FORMAT_PROFILE = FormatCapabilityProfile.define({
|
|
4
|
+
mechanism: "ranges",
|
|
5
|
+
constructs: {
|
|
6
|
+
spoiler: "strip",
|
|
7
|
+
codeInline: "fallback",
|
|
8
|
+
codeBlock: "fallback",
|
|
9
|
+
codeLanguage: "strip",
|
|
10
|
+
linkLabel: "fallback",
|
|
11
|
+
heading: "fallback",
|
|
12
|
+
bulletList: "fallback",
|
|
13
|
+
orderedList: "fallback",
|
|
14
|
+
taskList: "fallback",
|
|
15
|
+
table: "fallback",
|
|
16
|
+
blockquote: "fallback",
|
|
17
|
+
image: "fallback",
|
|
18
|
+
mention: "strip"
|
|
19
|
+
},
|
|
20
|
+
chunk: {
|
|
21
|
+
limit: 4e3,
|
|
22
|
+
unit: "utf16"
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const IMESSAGE_CODE_PROFILE = FormatCapabilityProfile.define({
|
|
26
|
+
...IMESSAGE_FORMAT_PROFILE,
|
|
27
|
+
constructs: {
|
|
28
|
+
...IMESSAGE_FORMAT_PROFILE.constructs,
|
|
29
|
+
codeInline: "native"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const IMESSAGE_STYLE_MAP = {
|
|
33
|
+
bold: "bold",
|
|
34
|
+
italic: "italic",
|
|
35
|
+
underline: "underline",
|
|
36
|
+
strikethrough: "strikethrough"
|
|
37
|
+
};
|
|
38
|
+
function codeDelimiter(content) {
|
|
39
|
+
const longestRun = Math.max(0, ...[...content.matchAll(/`+/gu)].map((match) => match[0].length));
|
|
40
|
+
return "`".repeat(longestRun + 1);
|
|
41
|
+
}
|
|
42
|
+
function restoreCodeMarkers(text, ranges, codeRanges) {
|
|
43
|
+
if (codeRanges.length === 0) return {
|
|
44
|
+
text,
|
|
45
|
+
ranges
|
|
46
|
+
};
|
|
47
|
+
let rendered = "";
|
|
48
|
+
let cursor = 0;
|
|
49
|
+
const edits = codeRanges.map((range) => {
|
|
50
|
+
const end = range.start + range.length;
|
|
51
|
+
const content = text.slice(range.start, end);
|
|
52
|
+
const marker = codeDelimiter(content);
|
|
53
|
+
const padding = content.startsWith("`") || content.endsWith("`") ? " " : "";
|
|
54
|
+
rendered += text.slice(cursor, range.start) + `${marker}${padding}${content}${padding}${marker}`;
|
|
55
|
+
cursor = end;
|
|
56
|
+
return {
|
|
57
|
+
end,
|
|
58
|
+
shift: rendered.length - end
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
rendered += text.slice(cursor);
|
|
62
|
+
const mapOffset = (offset) => {
|
|
63
|
+
let low = 0;
|
|
64
|
+
let high = edits.length;
|
|
65
|
+
while (low < high) {
|
|
66
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
67
|
+
const edit = edits[middle];
|
|
68
|
+
if (edit && edit.end <= offset) low = middle + 1;
|
|
69
|
+
else high = middle;
|
|
70
|
+
}
|
|
71
|
+
return offset + (edits[low - 1]?.shift ?? 0);
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
text: rendered,
|
|
75
|
+
ranges: ranges.map((range) => {
|
|
76
|
+
const start = mapOffset(range.start);
|
|
77
|
+
return {
|
|
78
|
+
...range,
|
|
79
|
+
start,
|
|
80
|
+
length: mapOffset(range.start + range.length) - start
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function extractMarkdownFormatRuns(input) {
|
|
86
|
+
const ir = markdownToIR(input, {
|
|
87
|
+
autolink: false,
|
|
88
|
+
enableHtmlUnderline: true,
|
|
89
|
+
headingStyle: "rich",
|
|
90
|
+
linkify: false,
|
|
91
|
+
preserveDunderIdentifiers: true,
|
|
92
|
+
preserveSourceBlockSpacing: true
|
|
93
|
+
});
|
|
94
|
+
const rendered = renderMarkdownWithAttributedRanges(ir, { styleMap: IMESSAGE_STYLE_MAP }, IMESSAGE_FORMAT_PROFILE);
|
|
95
|
+
const codeRanges = ir.styles.some((span) => span.style === "code") ? renderMarkdownWithAttributedRanges(ir, { styleMap: { code: "code" } }, IMESSAGE_CODE_PROFILE).ranges : [];
|
|
96
|
+
return restoreCodeMarkers(rendered.text, rendered.ranges.map(({ start, length, style }) => ({
|
|
97
|
+
start,
|
|
98
|
+
length,
|
|
99
|
+
styles: [style]
|
|
100
|
+
})), codeRanges);
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region extensions/imessage/src/monitor/sanitize-outbound.ts
|
|
104
|
+
/**
|
|
105
|
+
* Patterns that indicate assistant-internal metadata leaked into text.
|
|
106
|
+
* These must never reach a user-facing channel.
|
|
107
|
+
*/
|
|
108
|
+
const INTERNAL_SEPARATOR_RE = /(?:#\+){2,}#?/g;
|
|
109
|
+
const ASSISTANT_ROLE_MARKER_RE = /\bassistant\s+to\s*=\s*\w+/gi;
|
|
110
|
+
const ROLE_TURN_MARKER_RE = /^[ \t]*(?:>[ \t]*)*(?:user|system|assistant)[ \t]*:[ \t]*(?=\r?$)/gim;
|
|
111
|
+
const FENCED_ROLE_MARKER_RE = /^[ \t]*(user|system|assistant)[ \t]*:[ \t]*(?=\r?$)/gim;
|
|
112
|
+
const FINAL_PRIVATE_MARKER_RE = /(?:#\+){2,}#?|\bassistant\s+to\s*=\s*\w+|^[ \t]*(?:user|system|assistant)[ \t]*:[ \t]*(?=\r?$)/gim;
|
|
113
|
+
const PRIVATE_USE_START = 57344;
|
|
114
|
+
const PRIVATE_USE_END = 63743;
|
|
115
|
+
const MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES = 32;
|
|
116
|
+
const PLAIN_TEXT_HTML_TAG_RE = /<\/?[a-z][a-z0-9_.:-]*(?=[\s/>])[^>]*>/gi;
|
|
117
|
+
const PRIVATE_PROVIDER_TAG_NAMES = [
|
|
118
|
+
"think",
|
|
119
|
+
"thinking",
|
|
120
|
+
"thought",
|
|
121
|
+
"reasoning",
|
|
122
|
+
"antthinking",
|
|
123
|
+
"antml:think",
|
|
124
|
+
"antml:thinking",
|
|
125
|
+
"antml:thought",
|
|
126
|
+
"antml:reasoning",
|
|
127
|
+
"mm:think",
|
|
128
|
+
"mm:thinking",
|
|
129
|
+
"mm:thought",
|
|
130
|
+
"mm:reasoning",
|
|
131
|
+
"relevant_memories",
|
|
132
|
+
"relevant-memories",
|
|
133
|
+
"tool_call",
|
|
134
|
+
"tool_result",
|
|
135
|
+
"function_call",
|
|
136
|
+
"function_calls",
|
|
137
|
+
"function_response",
|
|
138
|
+
"function",
|
|
139
|
+
"tool_calls",
|
|
140
|
+
"antml:invoke",
|
|
141
|
+
"antml:parameter",
|
|
142
|
+
"invoke",
|
|
143
|
+
"parameter"
|
|
144
|
+
];
|
|
145
|
+
const PRIVATE_PROVIDER_TAG_NAME_SET = new Set(PRIVATE_PROVIDER_TAG_NAMES);
|
|
146
|
+
const OPAQUE_PRIVATE_RUNTIME_CONTEXT_BLOCK_RE = /^<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>(?:(?!<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>)[\s\S])*<<<END_OPENCLAW_INTERNAL_CONTEXT>>>/;
|
|
147
|
+
const PRIVATE_RUNTIME_CONTEXT_BLOCK_RE = /<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>(?:(?!<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>)[\s\S])*<<<END_OPENCLAW_INTERNAL_CONTEXT>>>/g;
|
|
148
|
+
const NESTED_PRIVATE_MARKUP_START_RE = /<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>|<\s*\/?\s*[A-Za-z][A-Za-z0-9_.:-]*(?=\s|\/?>)/g;
|
|
149
|
+
const OUTER_PRIVATE_TAG_FRAGMENT_RE = /^\s*\/?\s*([a-z][a-z0-9_.:-]*)?$/i;
|
|
150
|
+
const OPAQUE_PRIVATE_MARKUP_BLOCK_RE = /^<\s*(system-reminder|previous_response|details|summary)\b[^>]*>[\s\S]*?<\s*\/\s*\1\s*>/i;
|
|
151
|
+
const REMOVABLE_PRIVATE_MARKUP_TAG_RE = /^<\s*\/?\s*[a-z][a-z0-9_.:-]*(?=\s|\/?>)[^>]*>/i;
|
|
152
|
+
const PRIVATE_PROVIDER_TAG_FRAGMENT_RE = /^[a-z0-9_.:-]*/i;
|
|
153
|
+
const PRIVATE_RUNTIME_SCAFFOLDING_TAG_TOKEN_RE = /<\s*(\/?)\s*(system-reminder|previous_response)\b/gi;
|
|
154
|
+
function assertSafeIMessageOutboundMarkup(text) {
|
|
155
|
+
for (const nested of text.matchAll(NESTED_PRIVATE_MARKUP_START_RE)) {
|
|
156
|
+
const nestedStart = nested.index ?? 0;
|
|
157
|
+
const outerStart = text.lastIndexOf("<", nestedStart - 1);
|
|
158
|
+
if (outerStart < 0) continue;
|
|
159
|
+
const outer = OUTER_PRIVATE_TAG_FRAGMENT_RE.exec(text.slice(outerStart + 1, nestedStart));
|
|
160
|
+
if (!outer) continue;
|
|
161
|
+
let candidate = (outer[1] ?? "").toLowerCase();
|
|
162
|
+
if (!PRIVATE_PROVIDER_TAG_NAMES.some((name) => name.startsWith(candidate))) continue;
|
|
163
|
+
let cursor = nestedStart;
|
|
164
|
+
for (let depth = 0; depth < MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES; depth += 1) {
|
|
165
|
+
const remaining = text.slice(cursor);
|
|
166
|
+
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];
|
|
167
|
+
if (!removed) break;
|
|
168
|
+
cursor += removed.length;
|
|
169
|
+
const suffix = PRIVATE_PROVIDER_TAG_FRAGMENT_RE.exec(text.slice(cursor))?.[0] ?? "";
|
|
170
|
+
candidate += suffix.toLowerCase();
|
|
171
|
+
cursor += suffix.length;
|
|
172
|
+
if (PRIVATE_PROVIDER_TAG_NAME_SET.has(candidate) && /[\s/>]/.test(text.charAt(cursor))) throw new Error("iMessage outbound ambiguous nested HTML is not allowed");
|
|
173
|
+
if (!PRIVATE_PROVIDER_TAG_NAMES.some((name) => name.startsWith(candidate))) break;
|
|
174
|
+
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");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function stripIMessageBalancedPrivateRuntimeBlocks(text, verifyProtectedRoles) {
|
|
179
|
+
const openBlocks = [];
|
|
180
|
+
const completedBlocks = [];
|
|
181
|
+
const codeRegions = findCodeRegions(text);
|
|
182
|
+
const parsedHtmlTags = [...tokenizeHtmlTags(text)];
|
|
183
|
+
const htmlTags = parsedHtmlTags.map(({ start, end }) => ({
|
|
184
|
+
start,
|
|
185
|
+
end,
|
|
186
|
+
terminated: true
|
|
187
|
+
}));
|
|
188
|
+
const rawTextHtml = [];
|
|
189
|
+
const rawTextTagNames = /* @__PURE__ */ new Set([
|
|
190
|
+
"script",
|
|
191
|
+
"style",
|
|
192
|
+
"textarea",
|
|
193
|
+
"title",
|
|
194
|
+
"xmp",
|
|
195
|
+
"iframe",
|
|
196
|
+
"noembed",
|
|
197
|
+
"noframes",
|
|
198
|
+
"noscript",
|
|
199
|
+
"plaintext"
|
|
200
|
+
]);
|
|
201
|
+
for (const [index, opener] of parsedHtmlTags.entries()) {
|
|
202
|
+
if (opener.closing || !rawTextTagNames.has(opener.name)) continue;
|
|
203
|
+
const closing = opener.name === "plaintext" ? void 0 : parsedHtmlTags.slice(index + 1).find((candidate) => {
|
|
204
|
+
return candidate.closing && candidate.name === opener.name;
|
|
205
|
+
});
|
|
206
|
+
rawTextHtml.push({
|
|
207
|
+
start: opener.start,
|
|
208
|
+
end: closing?.end ?? text.length,
|
|
209
|
+
terminated: Boolean(closing)
|
|
210
|
+
});
|
|
211
|
+
if (rawTextHtml.length > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
212
|
+
}
|
|
213
|
+
const opaqueHtml = [];
|
|
214
|
+
for (let cursor = 0; cursor < text.length;) {
|
|
215
|
+
const start = text.indexOf("<", cursor);
|
|
216
|
+
if (start < 0) break;
|
|
217
|
+
const enclosingTag = htmlTags.find((tag) => tag.start < start && start < tag.end);
|
|
218
|
+
if (enclosingTag) {
|
|
219
|
+
cursor = enclosingTag.end;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
let end = -1;
|
|
223
|
+
let delimiterLength = 0;
|
|
224
|
+
if (text.startsWith("<!--", start)) {
|
|
225
|
+
end = text.indexOf("-->", start + 4);
|
|
226
|
+
delimiterLength = 3;
|
|
227
|
+
} else if (/^<!\[CDATA\[/i.test(text.slice(start, start + 9))) {
|
|
228
|
+
end = text.indexOf("]]>", start + 9);
|
|
229
|
+
delimiterLength = 3;
|
|
230
|
+
} else if (text.startsWith("<?", start)) {
|
|
231
|
+
let quote;
|
|
232
|
+
for (let index = start + 2; index < text.length - 1; index += 1) {
|
|
233
|
+
const character = text[index];
|
|
234
|
+
if (quote) {
|
|
235
|
+
if (character === quote) quote = void 0;
|
|
236
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
237
|
+
else if (character === "?" && text[index + 1] === ">") {
|
|
238
|
+
end = index;
|
|
239
|
+
delimiterLength = 2;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} else if (/^<![a-z][a-z0-9_.:-]*\b/i.test(text.slice(start))) {
|
|
244
|
+
let quote;
|
|
245
|
+
let bracketDepth = 0;
|
|
246
|
+
for (let index = start + 2; index < text.length; index += 1) {
|
|
247
|
+
const character = text[index];
|
|
248
|
+
if (quote) {
|
|
249
|
+
if (character === quote) quote = void 0;
|
|
250
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
251
|
+
else if (character === "[") bracketDepth += 1;
|
|
252
|
+
else if (character === "]" && bracketDepth > 0) bracketDepth -= 1;
|
|
253
|
+
else if (character === ">" && bracketDepth === 0) {
|
|
254
|
+
end = index;
|
|
255
|
+
delimiterLength = 1;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else if (text.startsWith("<!", start) || text.startsWith("</", start) && start + 2 < text.length && !/[A-Za-z>]/.test(text[start + 2] ?? "")) {
|
|
260
|
+
end = text.indexOf(">", start + 2);
|
|
261
|
+
delimiterLength = 1;
|
|
262
|
+
} else {
|
|
263
|
+
cursor = start + 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (opaqueHtml.length >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
267
|
+
const terminated = end >= 0;
|
|
268
|
+
const regionEnd = terminated ? end + delimiterLength : text.length;
|
|
269
|
+
opaqueHtml.push({
|
|
270
|
+
start,
|
|
271
|
+
end: regionEnd,
|
|
272
|
+
terminated
|
|
273
|
+
});
|
|
274
|
+
cursor = regionEnd;
|
|
275
|
+
}
|
|
276
|
+
htmlTags.push(...opaqueHtml);
|
|
277
|
+
htmlTags.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
278
|
+
let htmlTagIndex = 0;
|
|
279
|
+
let scannedTags = 0;
|
|
280
|
+
let scannedThrough = 0;
|
|
281
|
+
for (const token of text.matchAll(PRIVATE_RUNTIME_SCAFFOLDING_TAG_TOKEN_RE)) {
|
|
282
|
+
const start = token.index ?? 0;
|
|
283
|
+
if (start < scannedThrough) continue;
|
|
284
|
+
while ((htmlTags[htmlTagIndex]?.end ?? 0) <= start && htmlTagIndex < htmlTags.length) htmlTagIndex += 1;
|
|
285
|
+
const enclosingHtmlTag = htmlTags[htmlTagIndex];
|
|
286
|
+
if (enclosingHtmlTag && enclosingHtmlTag.start < start && start < enclosingHtmlTag.end) continue;
|
|
287
|
+
const codeRegion = codeRegions.find((region) => isInsideCode(start, [region]));
|
|
288
|
+
const enclosingBlock = openBlocks.at(-1);
|
|
289
|
+
const enclosingRawText = rawTextHtml.find((region) => {
|
|
290
|
+
return region.start < start && start < region.end;
|
|
291
|
+
});
|
|
292
|
+
if (enclosingBlock && enclosingRawText && enclosingBlock.start < enclosingRawText.start) {
|
|
293
|
+
enclosingBlock.sawNested = true;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (enclosingBlock && codeRegion && enclosingBlock.codeRegion !== codeRegion) {
|
|
297
|
+
enclosingBlock.sawNested = true;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (enclosingBlock?.codeRegion && enclosingBlock.codeRegion !== codeRegion) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
301
|
+
if (++scannedTags > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
302
|
+
let quote;
|
|
303
|
+
let end = start + token[0].length;
|
|
304
|
+
for (; end < text.length; end += 1) {
|
|
305
|
+
if (end - start > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
306
|
+
const character = text[end];
|
|
307
|
+
if (quote) {
|
|
308
|
+
if (character === quote) quote = void 0;
|
|
309
|
+
} else if (character === "'" || character === "\"") quote = character;
|
|
310
|
+
else if (character === "<") throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
311
|
+
else if (character === ">") break;
|
|
312
|
+
}
|
|
313
|
+
if (end >= text.length || quote) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
314
|
+
end += 1;
|
|
315
|
+
if (enclosingBlock?.codeRegion && enclosingBlock.codeRegion === codeRegion) {
|
|
316
|
+
if (!enclosingBlock.nestedCodeRegions) {
|
|
317
|
+
if (codeRegion.end - enclosingBlock.end > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 3) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
318
|
+
enclosingBlock.nestedCodeRegions = findCodeRegions(text.slice(enclosingBlock.end, codeRegion.end));
|
|
319
|
+
if (enclosingBlock.nestedCodeRegions.length > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
320
|
+
}
|
|
321
|
+
if (isInsideCode(start - enclosingBlock.end, enclosingBlock.nestedCodeRegions)) {
|
|
322
|
+
enclosingBlock.sawNested = true;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
scannedThrough = end;
|
|
327
|
+
const markup = text.slice(start, end);
|
|
328
|
+
const closing = token[1] === "/";
|
|
329
|
+
if (!closing && /\/\s*>$/.test(markup)) {
|
|
330
|
+
const parent = openBlocks.at(-1);
|
|
331
|
+
if (parent) parent.sawNested = true;
|
|
332
|
+
else completedBlocks.push({
|
|
333
|
+
start,
|
|
334
|
+
end
|
|
335
|
+
});
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const name = (token[2] ?? "").toLowerCase();
|
|
339
|
+
if (!closing) {
|
|
340
|
+
if (openBlocks.length >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
341
|
+
const parent = openBlocks.at(-1);
|
|
342
|
+
if (parent) parent.sawNested = true;
|
|
343
|
+
openBlocks.push({
|
|
344
|
+
name,
|
|
345
|
+
start,
|
|
346
|
+
end,
|
|
347
|
+
sawNested: false,
|
|
348
|
+
codeRegion
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const opening = openBlocks.at(-1);
|
|
353
|
+
if (!opening) {
|
|
354
|
+
completedBlocks.push({
|
|
355
|
+
start,
|
|
356
|
+
end
|
|
357
|
+
});
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (opening.name !== name) throw new Error("iMessage outbound runtime scaffolding is malformed");
|
|
361
|
+
openBlocks.pop();
|
|
362
|
+
completedBlocks.push({
|
|
363
|
+
start: opening.start,
|
|
364
|
+
end
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
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");
|
|
368
|
+
const orphan = openBlocks[0];
|
|
369
|
+
if (orphan) completedBlocks.push({
|
|
370
|
+
start: orphan.start,
|
|
371
|
+
end: orphan.end
|
|
372
|
+
});
|
|
373
|
+
completedBlocks.sort((left, right) => left.start - right.start || right.end - left.end);
|
|
374
|
+
const outermost = [];
|
|
375
|
+
for (const block of completedBlocks) {
|
|
376
|
+
const previous = outermost.at(-1);
|
|
377
|
+
if (!previous || block.start >= previous.end) outermost.push(block);
|
|
378
|
+
}
|
|
379
|
+
let current = text;
|
|
380
|
+
for (const block of outermost.toReversed()) {
|
|
381
|
+
verifyProtectedRoles?.(current);
|
|
382
|
+
const stripped = current.slice(0, block.start) + current.slice(block.end);
|
|
383
|
+
assertSafeIMessageOutboundMarkup(stripped);
|
|
384
|
+
verifyProtectedRoles?.(stripped);
|
|
385
|
+
current = stripped;
|
|
386
|
+
}
|
|
387
|
+
return current;
|
|
388
|
+
}
|
|
389
|
+
function stripIMessagePrivateRuntimeScaffolding(text, verifyProtectedRoles) {
|
|
390
|
+
assertSafeIMessageOutboundMarkup(text);
|
|
391
|
+
let current = text;
|
|
392
|
+
for (let depth = 0; depth < MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES; depth += 1) {
|
|
393
|
+
let changed = false;
|
|
394
|
+
for (const strip of [(value) => value.replace(PRIVATE_RUNTIME_CONTEXT_BLOCK_RE, ""), (value) => stripIMessageBalancedPrivateRuntimeBlocks(value, verifyProtectedRoles)]) {
|
|
395
|
+
verifyProtectedRoles?.(current);
|
|
396
|
+
const stripped = strip(current);
|
|
397
|
+
assertSafeIMessageOutboundMarkup(stripped);
|
|
398
|
+
verifyProtectedRoles?.(stripped);
|
|
399
|
+
if (stripped !== current) {
|
|
400
|
+
current = stripped;
|
|
401
|
+
changed = true;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (!changed) return current;
|
|
405
|
+
}
|
|
406
|
+
throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
|
|
407
|
+
}
|
|
408
|
+
function sanitizeIMessageAssistantVisibleText(text, verifyProtectedRoles) {
|
|
409
|
+
const cleaned = stripIMessagePrivateRuntimeScaffolding(text, verifyProtectedRoles);
|
|
410
|
+
if (!text.trim()) return text;
|
|
411
|
+
const leadingWhitespace = /^\s*/u.exec(text)?.[0] ?? "";
|
|
412
|
+
const trailingWhitespace = /\s*$/u.exec(text)?.[0] ?? "";
|
|
413
|
+
verifyProtectedRoles?.(cleaned);
|
|
414
|
+
const canonical = cleaned.trim();
|
|
415
|
+
verifyProtectedRoles?.(canonical);
|
|
416
|
+
const visible = sanitizeAssistantVisibleText(canonical);
|
|
417
|
+
verifyProtectedRoles?.(visible);
|
|
418
|
+
return visible ? `${leadingWhitespace}${visible}${trailingWhitespace}` : "";
|
|
419
|
+
}
|
|
420
|
+
function isClosedFencedCodeRegion(text, region) {
|
|
421
|
+
const lineStart = text.lastIndexOf("\n", region.start - 1) + 1;
|
|
422
|
+
if (!/^ {0,3}$/.test(text.slice(lineStart, region.start))) return false;
|
|
423
|
+
const source = text.slice(region.start, region.end);
|
|
424
|
+
const openingFence = /^ {0,3}(`{3,}|~{3,})[^\r\n]*\r?\n/.exec(source)?.[1];
|
|
425
|
+
if (!openingFence) return false;
|
|
426
|
+
const closingFence = new RegExp(`\r?\n {0,3}${openingFence[0]}{${openingFence.length},}[\t ]*(?:\r?\n)?$`);
|
|
427
|
+
const lineEnd = text.indexOf("\n", region.end);
|
|
428
|
+
return closingFence.test(source) && /^[\t ]*\r?$/.test(text.slice(region.end, lineEnd < 0 ? text.length : lineEnd));
|
|
429
|
+
}
|
|
430
|
+
/** Preserve authenticated fenced role mappings through the final native-render scrub. */
|
|
431
|
+
function protectIMessageFencedRoleMarkers(text) {
|
|
432
|
+
const source = stripIMessagePrivateRuntimeScaffolding(text);
|
|
433
|
+
const closedFencedRegions = findCodeRegions(source).filter((region) => isClosedFencedCodeRegion(source, region));
|
|
434
|
+
const protectedRoles = /* @__PURE__ */ new Map();
|
|
435
|
+
let privateUseCodePoint = PRIVATE_USE_START;
|
|
436
|
+
const protectedText = source.replace(FENCED_ROLE_MARKER_RE, (marker, role, offset) => {
|
|
437
|
+
if (!isInsideCode(offset, closedFencedRegions)) return marker;
|
|
438
|
+
while (privateUseCodePoint <= PRIVATE_USE_END) {
|
|
439
|
+
const protectedGlyph = String.fromCharCode(privateUseCodePoint++);
|
|
440
|
+
const token = protectedGlyph.repeat(role.length);
|
|
441
|
+
if (text.includes(protectedGlyph) || protectedRoles.has(token)) continue;
|
|
442
|
+
protectedRoles.set(token, role);
|
|
443
|
+
return marker.replace(role, token);
|
|
444
|
+
}
|
|
445
|
+
throw new Error("iMessage outbound role protection is unavailable");
|
|
446
|
+
});
|
|
447
|
+
const verifyProtectedRoles = (visible) => {
|
|
448
|
+
assertSafeIMessageOutboundMarkup(visible);
|
|
449
|
+
let previous = -1;
|
|
450
|
+
for (const token of protectedRoles.keys()) {
|
|
451
|
+
const first = visible.indexOf(token);
|
|
452
|
+
if (first < 0 || first < previous || visible.includes(token, first + token.length)) throw new Error("iMessage outbound role protection failed");
|
|
453
|
+
for (let offset = visible.indexOf(token[0] ?? ""); offset >= 0;) {
|
|
454
|
+
if (offset < first || offset >= first + token.length) throw new Error("iMessage outbound role protection failed");
|
|
455
|
+
offset = visible.indexOf(token[0] ?? "", offset + 1);
|
|
456
|
+
}
|
|
457
|
+
previous = first + token.length;
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
return {
|
|
461
|
+
text: protectedText,
|
|
462
|
+
verifyProtectedRoles,
|
|
463
|
+
sanitizeFormatted: (formatted) => {
|
|
464
|
+
verifyProtectedRoles(formatted.text);
|
|
465
|
+
let visible = formatted.text;
|
|
466
|
+
let ranges = formatted.ranges;
|
|
467
|
+
for (let remainingPasses = visible.length;; remainingPasses--) {
|
|
468
|
+
verifyProtectedRoles(visible);
|
|
469
|
+
const assistantVisible = sanitizeIMessageAssistantVisibleText(visible, verifyProtectedRoles);
|
|
470
|
+
const assistantTextChanged = assistantVisible !== visible;
|
|
471
|
+
if (assistantTextChanged) {
|
|
472
|
+
visible = assistantVisible;
|
|
473
|
+
ranges = [];
|
|
474
|
+
verifyProtectedRoles(visible);
|
|
475
|
+
}
|
|
476
|
+
const removed = [];
|
|
477
|
+
const cleaned = visible.replace(FINAL_PRIVATE_MARKER_RE, (marker, offset) => {
|
|
478
|
+
removed.push({
|
|
479
|
+
start: offset,
|
|
480
|
+
end: offset + marker.length
|
|
481
|
+
});
|
|
482
|
+
return "";
|
|
483
|
+
});
|
|
484
|
+
if (removed.length > 0) {
|
|
485
|
+
const mapOffset = (offset) => offset - removed.reduce((total, edit) => total + Math.max(0, Math.min(offset, edit.end) - edit.start), 0);
|
|
486
|
+
ranges = ranges.flatMap((range) => {
|
|
487
|
+
const start = mapOffset(range.start);
|
|
488
|
+
const length = mapOffset(range.start + range.length) - start;
|
|
489
|
+
return length > 0 ? [{
|
|
490
|
+
...range,
|
|
491
|
+
start,
|
|
492
|
+
length
|
|
493
|
+
}] : [];
|
|
494
|
+
});
|
|
495
|
+
visible = cleaned;
|
|
496
|
+
verifyProtectedRoles(visible);
|
|
497
|
+
}
|
|
498
|
+
if (removed.length === 0) {
|
|
499
|
+
let projection = visible;
|
|
500
|
+
for (let depth = 0;; depth += 1) {
|
|
501
|
+
verifyProtectedRoles(projection);
|
|
502
|
+
const renderedProjection = extractMarkdownFormatRuns(projection).text;
|
|
503
|
+
verifyProtectedRoles(renderedProjection);
|
|
504
|
+
const unwrappedProjection = stripMarkdown(renderedProjection);
|
|
505
|
+
verifyProtectedRoles(unwrappedProjection);
|
|
506
|
+
let htmlProjection = unwrappedProjection;
|
|
507
|
+
for (let htmlDepth = 0;; htmlDepth += 1) {
|
|
508
|
+
verifyProtectedRoles(htmlProjection);
|
|
509
|
+
const assistantProjection = sanitizeIMessageAssistantVisibleText(htmlProjection, verifyProtectedRoles);
|
|
510
|
+
verifyProtectedRoles(assistantProjection);
|
|
511
|
+
if (assistantProjection !== htmlProjection) throw new Error("iMessage outbound hidden assistant content is not allowed");
|
|
512
|
+
const htmlRemoved = htmlProjection.replace(PLAIN_TEXT_HTML_TAG_RE, "");
|
|
513
|
+
verifyProtectedRoles(htmlRemoved);
|
|
514
|
+
if (htmlRemoved === htmlProjection) break;
|
|
515
|
+
if (htmlDepth + 1 >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES) throw new Error("iMessage outbound HTML security projection exceeded its limit");
|
|
516
|
+
htmlProjection = htmlRemoved;
|
|
517
|
+
}
|
|
518
|
+
if (unwrappedProjection === projection) break;
|
|
519
|
+
if (depth + 1 >= MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES) throw new Error("iMessage outbound Markdown security projection exceeded its limit");
|
|
520
|
+
projection = unwrappedProjection;
|
|
521
|
+
}
|
|
522
|
+
if (!assistantTextChanged) break;
|
|
523
|
+
}
|
|
524
|
+
if (remainingPasses <= 0) throw new Error("iMessage outbound role sanitization failed");
|
|
525
|
+
}
|
|
526
|
+
verifyProtectedRoles(visible);
|
|
527
|
+
for (const [token, role] of protectedRoles) visible = visible.replace(token, role);
|
|
528
|
+
return {
|
|
529
|
+
text: visible,
|
|
530
|
+
ranges
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
/** Keep each transport's existing rendered or raw wire contract behind one final security gate. */
|
|
536
|
+
function sanitizeIMessageFinalOutboundText(text, options = {}) {
|
|
537
|
+
const protection = options.protection ?? protectIMessageFencedRoleMarkers(text);
|
|
538
|
+
const source = options.protection ? text : protection.text;
|
|
539
|
+
protection.verifyProtectedRoles(source);
|
|
540
|
+
const protectedText = sanitizeIMessageAssistantVisibleText(source, protection.verifyProtectedRoles);
|
|
541
|
+
protection.verifyProtectedRoles(protectedText);
|
|
542
|
+
const formatted = options.formatMarkdown && protectedText.trim() ? extractMarkdownFormatRuns(protectedText) : {
|
|
543
|
+
text: protectedText,
|
|
544
|
+
ranges: []
|
|
545
|
+
};
|
|
546
|
+
return protection.sanitizeFormatted(formatted);
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Strip all assistant-internal scaffolding from outbound text before delivery.
|
|
550
|
+
* Applies reasoning/thinking tag removal, memory tag removal, and
|
|
551
|
+
* model-specific internal separator stripping.
|
|
552
|
+
*/
|
|
553
|
+
function sanitizeOutboundText(text) {
|
|
554
|
+
if (!text) return text;
|
|
555
|
+
let cleaned = sanitizeIMessageAssistantVisibleText(text);
|
|
556
|
+
cleaned = cleaned.replace(INTERNAL_SEPARATOR_RE, "");
|
|
557
|
+
cleaned = cleaned.replace(ASSISTANT_ROLE_MARKER_RE, "");
|
|
558
|
+
const closedFencedRegions = findCodeRegions(cleaned).filter((region) => isClosedFencedCodeRegion(cleaned, region));
|
|
559
|
+
cleaned = cleaned.replace(ROLE_TURN_MARKER_RE, (marker, offset) => isInsideCode(offset, closedFencedRegions) ? marker : "");
|
|
560
|
+
cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
|
|
561
|
+
return cleaned;
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
export { extractMarkdownFormatRuns as i, sanitizeIMessageFinalOutboundText as n, sanitizeOutboundText as r, protectIMessageFencedRoleMarkers as t };
|