@openclaw/imessage 2026.10.1-beta.2 → 2026.10.5-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.
Files changed (30) hide show
  1. package/dist/.setup/{accounts-evTpvJMv.mjs → accounts-DacCvDBt.mjs} +23 -45
  2. package/dist/.setup/{actions-DJqbUmbN.mjs → actions-Hxh12myO.mjs} +54 -101
  3. package/dist/.setup/{actions.runtime-DL5i-Y4J.mjs → actions.runtime-DOIX6Fkf.mjs} +29 -37
  4. package/dist/.setup/{api-oq_MryN7.mjs → api-BV8qHfLl.mjs} +7 -7
  5. package/dist/.setup/{api-SAVMfMoY.mjs → api-DC1yHVGj.mjs} +8 -8
  6. package/dist/.setup/{approval-handler.runtime-Bj2NGNXF.mjs → approval-handler.runtime-Dc3KzakG.mjs} +38 -66
  7. package/dist/.setup/{approval-reactions-L9Ul7PAu.mjs → approval-reactions-JvzJCJ4a.mjs} +26 -33
  8. package/dist/.setup/{channel-CL73qdMG.mjs → channel-9TWBUDkP.mjs} +14 -16
  9. package/dist/.setup/{channel.runtime-DY7JJ0_9.mjs → channel.runtime-DrLDEY4R.mjs} +198 -362
  10. package/dist/.setup/{client-12apu4Ip.mjs → client-HixFQQWY.mjs} +34 -38
  11. package/dist/.setup/{media-contract-DjQGZlfl.mjs → media-contract-BwWs5PM7.mjs} +1 -1
  12. package/dist/.setup/{message-tool-api-CqfRxLlT.mjs → message-tool-api-B5NfPnFL.mjs} +2 -2
  13. package/dist/.setup/{question-reactions-DJV-TnA8.mjs → question-reactions-9jrqVZgE.mjs} +20 -25
  14. package/dist/.setup/{reaction-context-By7kUMBC.mjs → reaction-context-CRXpeYAr.mjs} +1 -1
  15. package/dist/.setup/{remote-file-BU7pA-Zi.mjs → remote-file-Dq5Asi_F.mjs} +10 -16
  16. package/dist/.setup/{sanitize-outbound-C0x4BPky.mjs → sanitize-outbound-DpdQERYJ.mjs} +27 -62
  17. package/dist/.setup/{send-pkYwL6Rt.mjs → send-BZZzMbdi.mjs} +186 -251
  18. package/dist/.setup/{setup-core-DqOBe-n0.mjs → setup-core-CaRGbZOL.mjs} +15 -46
  19. package/dist/.setup/{setup-surface-zKhIinwY.mjs → setup-surface-BMWS7VuY.mjs} +2 -2
  20. package/dist/.setup/{targets-BicBiYsM.mjs → targets-B4oU7n-v.mjs} +47 -69
  21. package/dist/api.js +7 -7
  22. package/dist/channel-plugin-api.js +1 -1
  23. package/dist/contract-api.js +1 -1
  24. package/dist/doctor-contract-api.js +21 -45
  25. package/dist/media-contract-api.js +1 -1
  26. package/dist/message-tool-api.js +1 -1
  27. package/dist/runtime-api.js +1 -1
  28. package/dist/setup-entry.js +1 -1
  29. package/dist/src/chat-db.worker.js +1 -1
  30. package/package.json +4 -4
@@ -1,5 +1,5 @@
1
- import { u as expandIMessageUserPath } from "./accounts-evTpvJMv.mjs";
2
- import { i as invalidateCachedIMessagePrivateApiStatus } from "./message-tool-api-CqfRxLlT.mjs";
1
+ import { u as expandIMessageUserPath } from "./accounts-DacCvDBt.mjs";
2
+ import { i as invalidateCachedIMessagePrivateApiStatus } from "./message-tool-api-B5NfPnFL.mjs";
3
3
  import { t as getIMessageRuntime } from "./runtime-Cza4CY5T.mjs";
4
4
  import { asNullableRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
5
5
  import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
@@ -10,6 +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 { captureChannelReadAuthority, captureEffectAuthority } from "openclaw/plugin-sdk/fetch-runtime";
13
14
  import { raceWithTimeout } from "openclaw/plugin-sdk/time-runtime";
14
15
  //#region extensions/imessage/src/chat-context.ts
15
16
  const EMAIL_HANDLE_PATTERN = /^[^\s@]+@[^\s@]+$/u;
@@ -87,7 +88,6 @@ function resolveIMessageChatMatch(cached, current) {
87
88
  }
88
89
  //#endregion
89
90
  //#region extensions/imessage/src/constants.ts
90
- /** Default timeout for iMessage probe/RPC operations (10 seconds). */
91
91
  const DEFAULT_IMESSAGE_PROBE_TIMEOUT_MS = 1e4;
92
92
  const DEFAULT_IMESSAGE_SEND_TIMEOUT_MS = 18e4;
93
93
  //#endregion
@@ -273,20 +273,16 @@ async function rememberIMessageReplyCache(entry) {
273
273
  imessageReplyCacheByMessageId.set(messageId, fullEntry);
274
274
  const cutoff = Date.now() - IMESSAGE_REPLY_CACHE_TTL_MS;
275
275
  const deletedMessageIds = [];
276
- for (const [key, value] of imessageReplyCacheByMessageId) {
277
- if (value.timestamp >= cutoff) break;
278
- imessageReplyCacheByMessageId.delete(key);
279
- deletedMessageIds.push(key);
280
- if (value.shortId) imessageShortIdToUuid.delete(value.shortId);
281
- }
282
- while (imessageReplyCacheByMessageId.size > IMESSAGE_REPLY_CACHE_MAX_ENTRIES) {
283
- const oldest = imessageReplyCacheByMessageId.keys().next().value;
284
- if (!oldest) break;
285
- const oldEntry = imessageReplyCacheByMessageId.get(oldest);
286
- imessageReplyCacheByMessageId.delete(oldest);
287
- deletedMessageIds.push(oldest);
288
- if (oldEntry?.shortId) imessageShortIdToUuid.delete(oldEntry.shortId);
289
- }
276
+ const pruneUntil = (retain) => {
277
+ for (const [key, cached] of imessageReplyCacheByMessageId) {
278
+ if (retain(key, cached)) break;
279
+ imessageReplyCacheByMessageId.delete(key);
280
+ deletedMessageIds.push(key);
281
+ if (cached.shortId) imessageShortIdToUuid.delete(cached.shortId);
282
+ }
283
+ };
284
+ pruneUntil((_key, cached) => cached.timestamp >= cutoff);
285
+ pruneUntil((key) => !key || imessageReplyCacheByMessageId.size <= 2e3);
290
286
  const counter = imessageShortIdCounter;
291
287
  persistence = persistence.then(async () => {
292
288
  if (isNewMessage) await persistReplyCacheCounter(counter);
@@ -532,7 +528,6 @@ var IMessageRpcClient = class {
532
528
  this.pending = /* @__PURE__ */ new Map();
533
529
  this.terminalResolve = null;
534
530
  this.reapedResolve = null;
535
- this.isReaped = false;
536
531
  this.child = null;
537
532
  this.stopPromise = null;
538
533
  this.stdoutFramer = createLfLineFramer((line) => this.handleStdoutLine(line));
@@ -563,13 +558,8 @@ var IMessageRpcClient = class {
563
558
  "pipe"
564
559
  ] });
565
560
  this.child = child;
566
- child.stdout.on("data", (chunk) => {
567
- if (this.child !== child) return;
568
- this.stdoutFramer.write(chunk);
569
- });
570
- child.stderr?.on("data", (chunk) => {
571
- if (this.child !== child) return;
572
- this.stderrFramer.write(chunk);
561
+ for (const [stream, framer] of [[child.stdout, this.stdoutFramer], [child.stderr, this.stderrFramer]]) stream.on("data", (chunk) => {
562
+ if (this.child === child) framer.write(chunk);
573
563
  });
574
564
  const failFromProcessError = (err) => this.failTransport(err, child);
575
565
  child.on("error", failFromProcessError);
@@ -605,6 +595,17 @@ var IMessageRpcClient = class {
605
595
  throw await this.terminal;
606
596
  }
607
597
  async request(method, params, opts) {
598
+ const child = this.child;
599
+ const stdin = child?.stdin;
600
+ const assertReadAuthority = captureChannelReadAuthority();
601
+ return captureEffectAuthority().initiate(() => {
602
+ if (child !== this.child || stdin !== this.child?.stdin) throw new Error("imsg rpc process changed before request initiation");
603
+ assertReadAuthority?.();
604
+ opts?.assertCurrent?.();
605
+ return this.initiateRequest(method, params, opts);
606
+ });
607
+ }
608
+ async initiateRequest(method, params, opts) {
608
609
  if (!this.child || !this.child.stdin) throw new Error("imsg rpc not running");
609
610
  const id = this.nextId++;
610
611
  const line = `${JSON.stringify({
@@ -614,18 +615,17 @@ var IMessageRpcClient = class {
614
615
  params: params ?? {}
615
616
  })}\n`;
616
617
  const timeoutMs = opts?.timeoutMs ?? 1e4;
617
- const response = new Promise((resolve, reject) => {
618
- const key = String(id);
619
- const timer = timeoutMs > 0 ? setTimeout(() => {
620
- this.pending.delete(key);
621
- reject(/* @__PURE__ */ new Error(`imsg rpc timeout (${method})`));
622
- }, timeoutMs) : void 0;
618
+ const key = String(id);
619
+ const pendingResponse = new Promise((resolve, reject) => {
623
620
  this.pending.set(key, {
624
621
  resolve: (value) => resolve(value),
625
- reject,
626
- timer
622
+ reject
627
623
  });
628
624
  });
625
+ const response = timeoutMs > 0 ? raceWithTimeout(pendingResponse, timeoutMs, () => {
626
+ this.pending.delete(key);
627
+ throw new Error(`imsg rpc timeout (${method})`);
628
+ }) : pendingResponse;
629
629
  try {
630
630
  this.child.stdin.write(line, (err) => {
631
631
  if (err) this.failTransport(err, this.child);
@@ -661,7 +661,7 @@ var IMessageRpcClient = class {
661
661
  if (!await this.waitForReap(IMESSAGE_RPC_CLOSE_GRACE_MS)) throw new Error("imsg rpc did not exit after SIGKILL");
662
662
  }
663
663
  async waitForReap(timeoutMs) {
664
- if (this.isReaped) return true;
664
+ if (!this.reapedResolve) return true;
665
665
  return await raceWithTimeout(this.reaped.then(() => true), timeoutMs, () => false);
666
666
  }
667
667
  signalChild(child, signal) {
@@ -702,7 +702,6 @@ var IMessageRpcClient = class {
702
702
  const key = String(parsed.id);
703
703
  const pending = this.pending.get(key);
704
704
  if (!pending) return;
705
- if (pending.timer) clearTimeout(pending.timer);
706
705
  this.pending.delete(key);
707
706
  if (parsed.error) {
708
707
  const baseMessage = parsed.error.message ?? "imsg rpc error";
@@ -739,7 +738,6 @@ var IMessageRpcClient = class {
739
738
  }
740
739
  failAll(err) {
741
740
  for (const [key, pending] of this.pending.entries()) {
742
- if (pending.timer) clearTimeout(pending.timer);
743
741
  pending.reject(err);
744
742
  this.pending.delete(key);
745
743
  }
@@ -753,8 +751,6 @@ var IMessageRpcClient = class {
753
751
  return true;
754
752
  }
755
753
  markReaped() {
756
- if (this.isReaped) return;
757
- this.isReaped = true;
758
754
  const resolve = this.reapedResolve;
759
755
  this.reapedResolve = null;
760
756
  resolve?.();
@@ -1,4 +1,4 @@
1
- import { o as resolveIMessageAccount } from "./accounts-evTpvJMv.mjs";
1
+ import { o as resolveIMessageAccount } from "./accounts-DacCvDBt.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
- import { n as inferIMessageTargetChatType } from "./targets-BicBiYsM.mjs";
2
- import { c as getCachedIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-evTpvJMv.mjs";
1
+ import { n as inferIMessageTargetChatType } from "./targets-B4oU7n-v.mjs";
2
+ import { c as getCachedIMessageRemoteHost, o as resolveIMessageAccount } from "./accounts-DacCvDBt.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";
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-BBjsoOtd.mjs";
2
- import { i as normalizeIMessageGuid } from "./actions-DJqbUmbN.mjs";
3
- import { t as resolveIMessageReactionContext } from "./reaction-context-By7kUMBC.mjs";
2
+ import { i as normalizeIMessageGuid } from "./actions-Hxh12myO.mjs";
3
+ import { t as resolveIMessageReactionContext } from "./reaction-context-CRXpeYAr.mjs";
4
4
  import { createQuestionReactionTargetStore, questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
5
5
  //#region extensions/imessage/src/question-reactions.ts
6
6
  var question_reactions_exports = /* @__PURE__ */ __exportAll({
@@ -20,15 +20,19 @@ const questionReactionTargets = createQuestionReactionTargetStore({
20
20
  registerChannelDelivery: questionGatewayRuntime.registerChannelDelivery,
21
21
  resolveReaction: questionGatewayRuntime.resolveReaction
22
22
  });
23
- function reactionCandidates(message, bodyText) {
24
- const reaction = resolveIMessageReactionContext(message, bodyText);
25
- if (!reaction) return null;
26
- const guids = Array.from(new Set([...reaction.targetGuids ?? [], reaction.targetGuid ?? ""].map(normalizeIMessageGuid).filter(Boolean)));
27
- return guids.length > 0 ? {
28
- action: reaction.action,
29
- emoji: reaction.emoji,
30
- guids
31
- } : null;
23
+ function resolveQuestionReaction(params) {
24
+ const reaction = resolveIMessageReactionContext(params.message, params.bodyText);
25
+ if (!reaction || reaction.action !== "added") return null;
26
+ const guids = [...new Set((reaction.targetGuids ?? []).map(normalizeIMessageGuid).filter(Boolean))];
27
+ if (guids.length === 0) return null;
28
+ const optionIndex = questionGatewayRuntime.resolveReactionIndex(reaction.emoji);
29
+ return optionIndex === void 0 ? null : {
30
+ optionIndex,
31
+ identities: guids.map((messageGuid) => ({
32
+ accountId: params.accountId,
33
+ messageGuid
34
+ }))
35
+ };
32
36
  }
33
37
  function registerIMessageQuestionReactionTargetForDeliveredPayload(params) {
34
38
  const binding = questionGatewayRuntime.readReactionBinding(params.payload);
@@ -46,23 +50,14 @@ function registerIMessageQuestionReactionTargetForDeliveredPayload(params) {
46
50
  return registered;
47
51
  }
48
52
  function hasIMessageQuestionReactionTarget(params) {
49
- const reaction = reactionCandidates(params.message, params.bodyText);
50
- if (!reaction || reaction.action !== "added" || questionGatewayRuntime.resolveReactionIndex(reaction.emoji) === void 0) return false;
51
- return questionReactionTargets.has(reaction.guids.map((messageGuid) => ({
52
- accountId: params.accountId,
53
- messageGuid
54
- })));
53
+ const reaction = resolveQuestionReaction(params);
54
+ return reaction ? questionReactionTargets.has(reaction.identities) : false;
55
55
  }
56
56
  async function maybeResolveIMessageQuestionReaction(params) {
57
- const reaction = reactionCandidates(params.message, params.bodyText);
58
- const optionIndex = reaction ? questionGatewayRuntime.resolveReactionIndex(reaction.emoji) : void 0;
59
- if (!reaction || reaction.action === "removed" || optionIndex === void 0) return false;
57
+ const reaction = resolveQuestionReaction(params);
58
+ if (!reaction) return false;
60
59
  return await questionReactionTargets.resolve({
61
- identities: reaction.guids.map((messageGuid) => ({
62
- accountId: params.accountId,
63
- messageGuid
64
- })),
65
- optionIndex,
60
+ ...reaction,
66
61
  cfg: params.cfg,
67
62
  senderId: params.senderId,
68
63
  gatewayUrl: params.gatewayUrl,
@@ -1,4 +1,4 @@
1
- import { i as normalizeIMessageGuid } from "./actions-DJqbUmbN.mjs";
1
+ import { i as normalizeIMessageGuid } from "./actions-Hxh12myO.mjs";
2
2
  //#region extensions/imessage/src/monitor/reaction-context.ts
3
3
  const TAPBACK_TEXT_PATTERNS = [
4
4
  {
@@ -1,5 +1,5 @@
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";
1
+ import { p as resolveLocalIMessageChatDbPath } from "./accounts-DacCvDBt.mjs";
2
+ import { b as chatContextFromIMessageTarget, l as resolveIMessageCachedResourceBinding, u as resolveIMessageMessageId, x as isIMessageEmailChatIdentifier } from "./client-HixFQQWY.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";
@@ -188,7 +188,6 @@ async function authorizeIMessageResourceReference(params) {
188
188
  }
189
189
  //#endregion
190
190
  //#region extensions/imessage/src/remote-file.ts
191
- const TOKEN_PATTERN = /^[a-f0-9]{32}$/u;
192
191
  const SSH_OPTIONS = [
193
192
  "-o",
194
193
  "BatchMode=yes",
@@ -205,8 +204,8 @@ const SSH_OPTIONS = [
205
204
  ];
206
205
  const CLEANUP_TIMEOUT_MS = 1e4;
207
206
  const log = createSubsystemLogger("channels/imessage");
208
- async function runChecked(run, label, argv, options) {
209
- const result = await run(argv, {
207
+ async function runChecked(label, argv, options) {
208
+ const result = await runCommandWithTimeout(argv, {
210
209
  killProcessTree: true,
211
210
  maxOutputBytes: {
212
211
  stdout: 4096,
@@ -223,16 +222,10 @@ async function runChecked(run, label, argv, options) {
223
222
  throw new Error(`${label} failed (${result.termination}${result.code === null ? "" : `, code ${result.code}`})${detail ? `: ${detail}` : ""}`);
224
223
  }
225
224
  }
226
- function requireToken(createToken) {
227
- const token = createToken().replaceAll("-", "").toLowerCase();
228
- if (!TOKEN_PATTERN.test(token)) throw new Error("iMessage remote staging generated an invalid temporary token");
229
- return token;
230
- }
231
225
  async function withIMessageRemoteFile(params) {
232
226
  const remoteHost = normalizeScpRemoteHost(params.remoteHost);
233
227
  if (!remoteHost) throw new Error("invalid iMessage remoteHost for SSH/SCP staging");
234
- const run = params.deps?.runCommand ?? runCommandWithTimeout;
235
- const remoteDir = `/tmp/openclaw-imessage-${requireToken(params.deps?.createToken ?? randomUUID)}`;
228
+ const remoteDir = `/tmp/openclaw-imessage-${randomUUID().replaceAll("-", "")}`;
236
229
  const remotePath = `${remoteDir}/${sanitizeTempFileName(path.basename(params.localPath))}`;
237
230
  const createScript = `set -eu
238
231
  umask 077
@@ -246,7 +239,7 @@ rm -rf -- ${remoteDir}
246
239
  `;
247
240
  params.assertDirectAdapterHandoff?.();
248
241
  try {
249
- await runChecked(run, "iMessage remote temporary directory allocation", [
242
+ await runChecked("iMessage remote temporary directory allocation", [
250
243
  "ssh",
251
244
  ...SSH_OPTIONS,
252
245
  "-T",
@@ -259,7 +252,7 @@ rm -rf -- ${remoteDir}
259
252
  signal: params.signal
260
253
  });
261
254
  params.assertDirectAdapterHandoff?.();
262
- await runChecked(run, "iMessage remote file upload", [
255
+ await runChecked("iMessage remote file upload", [
263
256
  "scp",
264
257
  ...SSH_OPTIONS,
265
258
  "--",
@@ -273,7 +266,7 @@ rm -rf -- ${remoteDir}
273
266
  return await params.use(remotePath);
274
267
  } finally {
275
268
  try {
276
- await runChecked(run, "iMessage remote file cleanup", [
269
+ await runChecked("iMessage remote file cleanup", [
277
270
  "ssh",
278
271
  ...SSH_OPTIONS,
279
272
  "-T",
@@ -285,7 +278,8 @@ rm -rf -- ${remoteDir}
285
278
  timeoutMs: CLEANUP_TIMEOUT_MS
286
279
  });
287
280
  } catch (cleanupError) {
288
- (params.deps?.onCleanupError ?? ((error) => log.warn(`remote attachment cleanup failed: ${error.message}`)))(cleanupError instanceof Error ? cleanupError : new Error(String(cleanupError)));
281
+ const detail = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
282
+ log.warn(`remote attachment cleanup failed: ${detail}`);
289
283
  }
290
284
  }
291
285
  }
@@ -1,4 +1,4 @@
1
- import { FormatCapabilityProfile, findCodeRegions, isInsideCode, markdownToIR, renderMarkdownWithAttributedRanges, sanitizeAssistantVisibleText, stripMarkdown, tokenizeHtmlTags } from "openclaw/plugin-sdk/text-chunking";
1
+ import { FormatCapabilityProfile, applyMarkdownTextEdits, findCodeRegions, isInsideCode, markdownToIR, renderMarkdownWithAttributedRanges, sanitizeAssistantVisibleText, stripMarkdown, tokenizeHtmlTags } from "openclaw/plugin-sdk/text-chunking";
2
2
  //#region extensions/imessage/src/markdown-format.ts
3
3
  const IMESSAGE_FORMAT_PROFILE = FormatCapabilityProfile.define({
4
4
  mechanism: "ranges",
@@ -44,32 +44,18 @@ function restoreCodeMarkers(text, ranges, codeRanges) {
44
44
  text,
45
45
  ranges
46
46
  };
47
- let rendered = "";
48
- let cursor = 0;
49
47
  const edits = codeRanges.map((range) => {
50
48
  const end = range.start + range.length;
51
49
  const content = text.slice(range.start, end);
52
50
  const marker = codeDelimiter(content);
53
51
  const padding = content.startsWith("`") || content.endsWith("`") ? " " : "";
54
- rendered += text.slice(cursor, range.start) + `${marker}${padding}${content}${padding}${marker}`;
55
- cursor = end;
56
52
  return {
53
+ start: range.start,
57
54
  end,
58
- shift: rendered.length - end
55
+ text: `${marker}${padding}${content}${padding}${marker}`
59
56
  };
60
57
  });
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
- };
58
+ const { text: rendered, mapOffset } = applyMarkdownTextEdits(text, edits);
73
59
  return {
74
60
  text: rendered,
75
61
  ranges: ranges.map((range) => {
@@ -175,6 +161,23 @@ function assertSafeIMessageOutboundMarkup(text) {
175
161
  }
176
162
  }
177
163
  }
164
+ function findIMessageMarkupEnd(text, start, kind, contentOffset = 2) {
165
+ const terminator = kind === "instruction" ? "?>" : ">";
166
+ let quote;
167
+ let bracketDepth = 0;
168
+ for (let index = start + contentOffset; index <= text.length - terminator.length; index += 1) {
169
+ if (kind === "private" && index - start > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 2) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");
170
+ const character = text[index];
171
+ if (quote) {
172
+ if (character === quote) quote = void 0;
173
+ } else if (character === "'" || character === "\"") quote = character;
174
+ else if (kind === "declaration" && character === "[") bracketDepth += 1;
175
+ else if (kind === "declaration" && character === "]" && bracketDepth > 0) bracketDepth -= 1;
176
+ else if (kind === "private" && character === "<") throw new Error("iMessage outbound runtime scaffolding is malformed");
177
+ else if (bracketDepth === 0 && text.startsWith(terminator, index)) return index + terminator.length;
178
+ }
179
+ return -1;
180
+ }
178
181
  function stripIMessageBalancedPrivateRuntimeBlocks(text, verifyProtectedRoles) {
179
182
  const openBlocks = [];
180
183
  const completedBlocks = [];
@@ -219,7 +222,7 @@ function stripIMessageBalancedPrivateRuntimeBlocks(text, verifyProtectedRoles) {
219
222
  cursor = enclosingTag.end;
220
223
  continue;
221
224
  }
222
- let end = -1;
225
+ let end;
223
226
  let delimiterLength = 0;
224
227
  if (text.startsWith("<!--", start)) {
225
228
  end = text.indexOf("-->", start + 4);
@@ -227,36 +230,9 @@ function stripIMessageBalancedPrivateRuntimeBlocks(text, verifyProtectedRoles) {
227
230
  } else if (/^<!\[CDATA\[/i.test(text.slice(start, start + 9))) {
228
231
  end = text.indexOf("]]>", start + 9);
229
232
  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] ?? "")) {
233
+ } else if (text.startsWith("<?", start)) end = findIMessageMarkupEnd(text, start, "instruction");
234
+ else if (/^<![a-z][a-z0-9_.:-]*\b/i.test(text.slice(start))) end = findIMessageMarkupEnd(text, start, "declaration");
235
+ else if (text.startsWith("<!", start) || text.startsWith("</", start) && start + 2 < text.length && !/[A-Za-z>]/.test(text[start + 2] ?? "")) {
260
236
  end = text.indexOf(">", start + 2);
261
237
  delimiterLength = 1;
262
238
  } else {
@@ -299,19 +275,8 @@ function stripIMessageBalancedPrivateRuntimeBlocks(text, verifyProtectedRoles) {
299
275
  }
300
276
  if (enclosingBlock?.codeRegion && enclosingBlock.codeRegion !== codeRegion) throw new Error("iMessage outbound runtime scaffolding is malformed");
301
277
  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;
278
+ const end = findIMessageMarkupEnd(text, start, "private", token[0].length);
279
+ if (end < 0) throw new Error("iMessage outbound runtime scaffolding is malformed");
315
280
  if (enclosingBlock?.codeRegion && enclosingBlock.codeRegion === codeRegion) {
316
281
  if (!enclosingBlock.nestedCodeRegions) {
317
282
  if (codeRegion.end - enclosingBlock.end > MAX_PRIVATE_MARKDOWN_UNWRAP_PASSES ** 3) throw new Error("iMessage outbound runtime scaffolding exceeded its limit");