cclawd 1.0.2 → 1.0.4

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 (68) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/plugin-sdk/active-listener-CN-tMEvN.js +35 -0
  3. package/dist/plugin-sdk/api-key-rotation-CimGYMBc.js +176 -0
  4. package/dist/plugin-sdk/audio-preflight-C-xXBoE2.js +51 -0
  5. package/dist/plugin-sdk/audio-transcription-runner-CTIHpebA.js +2173 -0
  6. package/dist/plugin-sdk/audit-membership-runtime-BFatB2LJ.js +58 -0
  7. package/dist/plugin-sdk/channel-activity-DO0FEzyj.js +95 -0
  8. package/dist/plugin-sdk/channel-web-Da-__nUF.js +2238 -0
  9. package/dist/plugin-sdk/commands-registry-6no2NNrY.js +1118 -0
  10. package/dist/plugin-sdk/compact.runtime-CCoclu5e.js +35 -0
  11. package/dist/plugin-sdk/config-B9ODwgpz.js +37426 -0
  12. package/dist/plugin-sdk/deliver-B1fFpKjV.js +1757 -0
  13. package/dist/plugin-sdk/deliver-runtime-DB-VRMe1.js +15 -0
  14. package/dist/plugin-sdk/deps-send-discord.runtime-DklqycYG.js +15 -0
  15. package/dist/plugin-sdk/deps-send-imessage.runtime-Chs8zeon.js +14 -0
  16. package/dist/plugin-sdk/deps-send-signal.runtime-clW9aSJP.js +13 -0
  17. package/dist/plugin-sdk/deps-send-slack.runtime-BUx0LYY1.js +13 -0
  18. package/dist/plugin-sdk/deps-send-telegram.runtime-LECSHgMG.js +16 -0
  19. package/dist/plugin-sdk/deps-send-whatsapp.runtime-D2d65fw0.js +40 -0
  20. package/dist/plugin-sdk/diagnostic-CxIvS-C2.js +315 -0
  21. package/dist/plugin-sdk/dispatch-BqlR4dPx.js +105863 -0
  22. package/dist/plugin-sdk/env-b9k1PHMI.js +34 -0
  23. package/dist/plugin-sdk/fetch-PoxzAANT.js +326 -0
  24. package/dist/plugin-sdk/fetch-guard-4UVSZ0uS.js +164 -0
  25. package/dist/plugin-sdk/image-Ch6M4tnJ.js +2420 -0
  26. package/dist/plugin-sdk/image-runtime-CSh2o5wY.js +8 -0
  27. package/dist/plugin-sdk/index.js +35 -35
  28. package/dist/plugin-sdk/ir-CugsqGH8.js +1312 -0
  29. package/dist/plugin-sdk/local-roots-adnEg9zb.js +217 -0
  30. package/dist/plugin-sdk/logger-D6zRubj0.js +1164 -0
  31. package/dist/plugin-sdk/login-CYvkQ0At.js +54 -0
  32. package/dist/plugin-sdk/login-qr-ll4NtaT5.js +316 -0
  33. package/dist/plugin-sdk/manager-CHy8IclH.js +3959 -0
  34. package/dist/plugin-sdk/manager-runtime-C70EkEr7.js +11 -0
  35. package/dist/plugin-sdk/outbound-Wzs2iN7X.js +216 -0
  36. package/dist/plugin-sdk/outbound-attachment-khXJwucX.js +17 -0
  37. package/dist/plugin-sdk/paths-BtVqCdw4.js +3063 -0
  38. package/dist/plugin-sdk/pi-model-discovery-Dh4ziodY.js +131 -0
  39. package/dist/plugin-sdk/pi-model-discovery-runtime-b83Xe-HT.js +8 -0
  40. package/dist/plugin-sdk/pi-tools.before-tool-call.runtime-C1z5CDBF.js +349 -0
  41. package/dist/plugin-sdk/proxy-fetch-CJEmoBxi.js +54 -0
  42. package/dist/plugin-sdk/pw-ai-Dj3Cvlzl.js +1990 -0
  43. package/dist/plugin-sdk/qmd-manager-egHUAseQ.js +1581 -0
  44. package/dist/plugin-sdk/resolve-outbound-target-BiICvIKs.js +38 -0
  45. package/dist/plugin-sdk/runtime-whatsapp-login.runtime-DNApufzW.js +9 -0
  46. package/dist/plugin-sdk/runtime-whatsapp-outbound.runtime-CBmtfIQ8.js +13 -0
  47. package/dist/plugin-sdk/send-CScblaI4.js +532 -0
  48. package/dist/plugin-sdk/send-CeHhnld6.js +407 -0
  49. package/dist/plugin-sdk/send-DP_c8JfR.js +3277 -0
  50. package/dist/plugin-sdk/send-Dc5fI6e8.js +495 -0
  51. package/dist/plugin-sdk/send-l-77_s1_.js +2507 -0
  52. package/dist/plugin-sdk/session-CkOKZaqa.js +166 -0
  53. package/dist/plugin-sdk/signal.js +2 -2
  54. package/dist/plugin-sdk/skill-commands-BohYCgkq.js +336 -0
  55. package/dist/plugin-sdk/slash-commands.runtime-DpLfVTM6.js +8 -0
  56. package/dist/plugin-sdk/slash-dispatch.runtime-CASMHwpm.js +35 -0
  57. package/dist/plugin-sdk/slash-skill-commands.runtime-D7rrJEci.js +9 -0
  58. package/dist/plugin-sdk/sqlite-CJE3X7Mv.js +1005 -0
  59. package/dist/plugin-sdk/subagent-registry-runtime-B1oo5bih.js +35 -0
  60. package/dist/plugin-sdk/tables-D5VgpTmm.js +53 -0
  61. package/dist/plugin-sdk/target-errors-C6zZ_OpA.js +191 -0
  62. package/dist/plugin-sdk/tokens-DUnJnpMS.js +50 -0
  63. package/dist/plugin-sdk/web-TfUM1nSi.js +39 -0
  64. package/dist/plugin-sdk/whatsapp-actions-DuWJ0j1r.js +71 -0
  65. package/extensions/mfa-auth/index.ts +36 -17
  66. package/extensions/mfa-auth/src/auth-manager.ts +4 -0
  67. package/extensions/mfa-auth/src/notification-service.ts +5 -1
  68. package/package.json +1 -1
@@ -0,0 +1,1312 @@
1
+ import { It as normalizeAccountId, b as readLocalFileSafely, p as SafeOpenError } from "./paths-BtVqCdw4.js";
2
+ import { Es as extensionForMime, Ps as maxBytesForKind, Ts as detectMime, cs as hasAlphaChannel, jo as resolveAccountEntry, js as kindFromMime, ls as optimizeImageToPng, os as convertHeicToJpeg, us as resizeToJpeg, vi as normalizeChannelId } from "./config-B9ODwgpz.js";
3
+ import { B as shouldLogVerbose, D as resolveUserPath, R as logVerbose } from "./logger-D6zRubj0.js";
4
+ import { i as fetchRemoteMedia, n as getDefaultMediaLocalRoots } from "./local-roots-adnEg9zb.js";
5
+ import path from "node:path";
6
+ import fs from "node:fs/promises";
7
+ import { fileURLToPath } from "node:url";
8
+ import MarkdownIt from "markdown-it";
9
+ //#region src/web/media.ts
10
+ function resolveWebMediaOptions(params) {
11
+ if (typeof params.maxBytesOrOptions === "number" || params.maxBytesOrOptions === void 0) return {
12
+ maxBytes: params.maxBytesOrOptions,
13
+ optimizeImages: params.optimizeImages,
14
+ ssrfPolicy: params.options?.ssrfPolicy,
15
+ localRoots: params.options?.localRoots
16
+ };
17
+ return {
18
+ ...params.maxBytesOrOptions,
19
+ optimizeImages: params.optimizeImages ? params.maxBytesOrOptions.optimizeImages ?? true : false
20
+ };
21
+ }
22
+ var LocalMediaAccessError = class extends Error {
23
+ constructor(code, message, options) {
24
+ super(message, options);
25
+ this.code = code;
26
+ this.name = "LocalMediaAccessError";
27
+ }
28
+ };
29
+ function getDefaultLocalRoots() {
30
+ return getDefaultMediaLocalRoots();
31
+ }
32
+ async function assertLocalMediaAllowed(mediaPath, localRoots) {
33
+ if (localRoots === "any") return;
34
+ const roots = localRoots ?? getDefaultLocalRoots();
35
+ let resolved;
36
+ try {
37
+ resolved = await fs.realpath(mediaPath);
38
+ } catch {
39
+ resolved = path.resolve(mediaPath);
40
+ }
41
+ if (localRoots === void 0) {
42
+ const workspaceRoot = roots.find((root) => path.basename(root) === "workspace");
43
+ if (workspaceRoot) {
44
+ const stateDir = path.dirname(workspaceRoot);
45
+ const rel = path.relative(stateDir, resolved);
46
+ if (rel && !rel.startsWith("..") && !path.isAbsolute(rel)) {
47
+ if ((rel.split(path.sep)[0] ?? "").startsWith("workspace-")) throw new LocalMediaAccessError("path-not-allowed", `Local media path is not under an allowed directory: ${mediaPath}`);
48
+ }
49
+ }
50
+ }
51
+ for (const root of roots) {
52
+ let resolvedRoot;
53
+ try {
54
+ resolvedRoot = await fs.realpath(root);
55
+ } catch {
56
+ resolvedRoot = path.resolve(root);
57
+ }
58
+ if (resolvedRoot === path.parse(resolvedRoot).root) throw new LocalMediaAccessError("invalid-root", `Invalid localRoots entry (refuses filesystem root): ${root}. Pass a narrower directory.`);
59
+ if (resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path.sep)) return;
60
+ }
61
+ throw new LocalMediaAccessError("path-not-allowed", `Local media path is not under an allowed directory: ${mediaPath}`);
62
+ }
63
+ const HEIC_MIME_RE = /^image\/hei[cf]$/i;
64
+ const HEIC_EXT_RE = /\.(heic|heif)$/i;
65
+ const MB = 1024 * 1024;
66
+ function formatMb(bytes, digits = 2) {
67
+ return (bytes / MB).toFixed(digits);
68
+ }
69
+ function formatCapLimit(label, cap, size) {
70
+ return `${label} exceeds ${formatMb(cap, 0)}MB limit (got ${formatMb(size)}MB)`;
71
+ }
72
+ function formatCapReduce(label, cap, size) {
73
+ return `${label} could not be reduced below ${formatMb(cap, 0)}MB (got ${formatMb(size)}MB)`;
74
+ }
75
+ function isHeicSource(opts) {
76
+ if (opts.contentType && HEIC_MIME_RE.test(opts.contentType.trim())) return true;
77
+ if (opts.fileName && HEIC_EXT_RE.test(opts.fileName.trim())) return true;
78
+ return false;
79
+ }
80
+ function toJpegFileName(fileName) {
81
+ if (!fileName) return;
82
+ const trimmed = fileName.trim();
83
+ if (!trimmed) return fileName;
84
+ const parsed = path.parse(trimmed);
85
+ if (!parsed.ext || HEIC_EXT_RE.test(parsed.ext)) return path.format({
86
+ dir: parsed.dir,
87
+ name: parsed.name || trimmed,
88
+ ext: ".jpg"
89
+ });
90
+ return path.format({
91
+ dir: parsed.dir,
92
+ name: parsed.name,
93
+ ext: ".jpg"
94
+ });
95
+ }
96
+ function logOptimizedImage(params) {
97
+ if (!shouldLogVerbose()) return;
98
+ if (params.optimized.optimizedSize >= params.originalSize) return;
99
+ if (params.optimized.format === "png") {
100
+ logVerbose(`Optimized PNG (preserving alpha) from ${formatMb(params.originalSize)}MB to ${formatMb(params.optimized.optimizedSize)}MB (side≤${params.optimized.resizeSide}px)`);
101
+ return;
102
+ }
103
+ logVerbose(`Optimized media from ${formatMb(params.originalSize)}MB to ${formatMb(params.optimized.optimizedSize)}MB (side≤${params.optimized.resizeSide}px, q=${params.optimized.quality})`);
104
+ }
105
+ async function optimizeImageWithFallback(params) {
106
+ const { buffer, cap, meta } = params;
107
+ if ((meta?.contentType === "image/png" || meta?.fileName?.toLowerCase().endsWith(".png")) && await hasAlphaChannel(buffer)) {
108
+ const optimized = await optimizeImageToPng(buffer, cap);
109
+ if (optimized.buffer.length <= cap) return {
110
+ ...optimized,
111
+ format: "png"
112
+ };
113
+ if (shouldLogVerbose()) logVerbose(`PNG with alpha still exceeds ${formatMb(cap, 0)}MB after optimization; falling back to JPEG`);
114
+ }
115
+ return {
116
+ ...await optimizeImageToJpeg(buffer, cap, meta),
117
+ format: "jpeg"
118
+ };
119
+ }
120
+ async function loadWebMediaInternal(mediaUrl, options = {}) {
121
+ const { maxBytes, optimizeImages = true, ssrfPolicy, localRoots, sandboxValidated = false, readFile: readFileOverride } = options;
122
+ mediaUrl = mediaUrl.replace(/^\s*MEDIA\s*:\s*/i, "");
123
+ if (mediaUrl.startsWith("file://")) try {
124
+ mediaUrl = fileURLToPath(mediaUrl);
125
+ } catch {
126
+ throw new LocalMediaAccessError("invalid-file-url", `Invalid file:// URL: ${mediaUrl}`);
127
+ }
128
+ const optimizeAndClampImage = async (buffer, cap, meta) => {
129
+ const originalSize = buffer.length;
130
+ const optimized = await optimizeImageWithFallback({
131
+ buffer,
132
+ cap,
133
+ meta
134
+ });
135
+ logOptimizedImage({
136
+ originalSize,
137
+ optimized
138
+ });
139
+ if (optimized.buffer.length > cap) throw new Error(formatCapReduce("Media", cap, optimized.buffer.length));
140
+ const contentType = optimized.format === "png" ? "image/png" : "image/jpeg";
141
+ const fileName = optimized.format === "jpeg" && meta && isHeicSource(meta) ? toJpegFileName(meta.fileName) : meta?.fileName;
142
+ return {
143
+ buffer: optimized.buffer,
144
+ contentType,
145
+ kind: "image",
146
+ fileName
147
+ };
148
+ };
149
+ const clampAndFinalize = async (params) => {
150
+ const cap = maxBytes !== void 0 ? maxBytes : maxBytesForKind(params.kind ?? "document");
151
+ if (params.kind === "image") {
152
+ const isGif = params.contentType === "image/gif";
153
+ if (isGif || !optimizeImages) {
154
+ if (params.buffer.length > cap) throw new Error(formatCapLimit(isGif ? "GIF" : "Media", cap, params.buffer.length));
155
+ return {
156
+ buffer: params.buffer,
157
+ contentType: params.contentType,
158
+ kind: params.kind,
159
+ fileName: params.fileName
160
+ };
161
+ }
162
+ return { ...await optimizeAndClampImage(params.buffer, cap, {
163
+ contentType: params.contentType,
164
+ fileName: params.fileName
165
+ }) };
166
+ }
167
+ if (params.buffer.length > cap) throw new Error(formatCapLimit("Media", cap, params.buffer.length));
168
+ return {
169
+ buffer: params.buffer,
170
+ contentType: params.contentType ?? void 0,
171
+ kind: params.kind,
172
+ fileName: params.fileName
173
+ };
174
+ };
175
+ if (/^https?:\/\//i.test(mediaUrl)) {
176
+ const defaultFetchCap = maxBytesForKind("document");
177
+ const { buffer, contentType, fileName } = await fetchRemoteMedia({
178
+ url: mediaUrl,
179
+ maxBytes: maxBytes === void 0 ? defaultFetchCap : optimizeImages ? Math.max(maxBytes, defaultFetchCap) : maxBytes,
180
+ ssrfPolicy
181
+ });
182
+ return await clampAndFinalize({
183
+ buffer,
184
+ contentType,
185
+ kind: kindFromMime(contentType),
186
+ fileName
187
+ });
188
+ }
189
+ if (mediaUrl.startsWith("~")) mediaUrl = resolveUserPath(mediaUrl);
190
+ if ((sandboxValidated || localRoots === "any") && !readFileOverride) throw new LocalMediaAccessError("unsafe-bypass", "Refusing localRoots bypass without readFile override. Use sandboxValidated with readFile, or pass explicit localRoots.");
191
+ if (!(sandboxValidated || localRoots === "any")) await assertLocalMediaAllowed(mediaUrl, localRoots);
192
+ let data;
193
+ if (readFileOverride) data = await readFileOverride(mediaUrl);
194
+ else try {
195
+ data = (await readLocalFileSafely({ filePath: mediaUrl })).buffer;
196
+ } catch (err) {
197
+ if (err instanceof SafeOpenError) {
198
+ if (err.code === "not-found") throw new LocalMediaAccessError("not-found", `Local media file not found: ${mediaUrl}`, { cause: err });
199
+ if (err.code === "not-file") throw new LocalMediaAccessError("not-file", `Local media path is not a file: ${mediaUrl}`, { cause: err });
200
+ throw new LocalMediaAccessError("invalid-path", `Local media path is not safe to read: ${mediaUrl}`, { cause: err });
201
+ }
202
+ throw err;
203
+ }
204
+ const mime = await detectMime({
205
+ buffer: data,
206
+ filePath: mediaUrl
207
+ });
208
+ const kind = kindFromMime(mime);
209
+ let fileName = path.basename(mediaUrl) || void 0;
210
+ if (fileName && !path.extname(fileName) && mime) {
211
+ const ext = extensionForMime(mime);
212
+ if (ext) fileName = `${fileName}${ext}`;
213
+ }
214
+ return await clampAndFinalize({
215
+ buffer: data,
216
+ contentType: mime,
217
+ kind,
218
+ fileName
219
+ });
220
+ }
221
+ async function loadWebMedia(mediaUrl, maxBytesOrOptions, options) {
222
+ return await loadWebMediaInternal(mediaUrl, resolveWebMediaOptions({
223
+ maxBytesOrOptions,
224
+ options,
225
+ optimizeImages: true
226
+ }));
227
+ }
228
+ async function loadWebMediaRaw(mediaUrl, maxBytesOrOptions, options) {
229
+ return await loadWebMediaInternal(mediaUrl, resolveWebMediaOptions({
230
+ maxBytesOrOptions,
231
+ options,
232
+ optimizeImages: false
233
+ }));
234
+ }
235
+ async function optimizeImageToJpeg(buffer, maxBytes, opts = {}) {
236
+ let source = buffer;
237
+ if (isHeicSource(opts)) try {
238
+ source = await convertHeicToJpeg(buffer);
239
+ } catch (err) {
240
+ throw new Error(`HEIC image conversion failed: ${String(err)}`, { cause: err });
241
+ }
242
+ const sides = [
243
+ 2048,
244
+ 1536,
245
+ 1280,
246
+ 1024,
247
+ 800
248
+ ];
249
+ const qualities = [
250
+ 80,
251
+ 70,
252
+ 60,
253
+ 50,
254
+ 40
255
+ ];
256
+ let smallest = null;
257
+ for (const side of sides) for (const quality of qualities) try {
258
+ const out = await resizeToJpeg({
259
+ buffer: source,
260
+ maxSide: side,
261
+ quality,
262
+ withoutEnlargement: true
263
+ });
264
+ const size = out.length;
265
+ if (!smallest || size < smallest.size) smallest = {
266
+ buffer: out,
267
+ size,
268
+ resizeSide: side,
269
+ quality
270
+ };
271
+ if (size <= maxBytes) return {
272
+ buffer: out,
273
+ optimizedSize: size,
274
+ resizeSide: side,
275
+ quality
276
+ };
277
+ } catch {}
278
+ if (smallest) return {
279
+ buffer: smallest.buffer,
280
+ optimizedSize: smallest.size,
281
+ resizeSide: smallest.resizeSide,
282
+ quality: smallest.quality
283
+ };
284
+ throw new Error("Failed to optimize image");
285
+ }
286
+ //#endregion
287
+ //#region src/markdown/fences.ts
288
+ function parseFenceSpans(buffer) {
289
+ const spans = [];
290
+ let open;
291
+ let offset = 0;
292
+ while (offset <= buffer.length) {
293
+ const nextNewline = buffer.indexOf("\n", offset);
294
+ const lineEnd = nextNewline === -1 ? buffer.length : nextNewline;
295
+ const line = buffer.slice(offset, lineEnd);
296
+ const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);
297
+ if (match) {
298
+ const indent = match[1];
299
+ const marker = match[2];
300
+ const markerChar = marker[0];
301
+ const markerLen = marker.length;
302
+ if (!open) open = {
303
+ start: offset,
304
+ markerChar,
305
+ markerLen,
306
+ openLine: line,
307
+ marker,
308
+ indent
309
+ };
310
+ else if (open.markerChar === markerChar && markerLen >= open.markerLen) {
311
+ const end = lineEnd;
312
+ spans.push({
313
+ start: open.start,
314
+ end,
315
+ openLine: open.openLine,
316
+ marker: open.marker,
317
+ indent: open.indent
318
+ });
319
+ open = void 0;
320
+ }
321
+ }
322
+ if (nextNewline === -1) break;
323
+ offset = nextNewline + 1;
324
+ }
325
+ if (open) spans.push({
326
+ start: open.start,
327
+ end: buffer.length,
328
+ openLine: open.openLine,
329
+ marker: open.marker,
330
+ indent: open.indent
331
+ });
332
+ return spans;
333
+ }
334
+ function findFenceSpanAt(spans, index) {
335
+ let low = 0;
336
+ let high = spans.length - 1;
337
+ while (low <= high) {
338
+ const mid = Math.floor((low + high) / 2);
339
+ const span = spans[mid];
340
+ if (!span) break;
341
+ if (index <= span.start) {
342
+ high = mid - 1;
343
+ continue;
344
+ }
345
+ if (index >= span.end) {
346
+ low = mid + 1;
347
+ continue;
348
+ }
349
+ return span;
350
+ }
351
+ }
352
+ function isSafeFenceBreak(spans, index) {
353
+ return !findFenceSpanAt(spans, index);
354
+ }
355
+ //#endregion
356
+ //#region src/shared/text-chunking.ts
357
+ function chunkTextByBreakResolver(text, limit, resolveBreakIndex) {
358
+ if (!text) return [];
359
+ if (limit <= 0 || text.length <= limit) return [text];
360
+ const chunks = [];
361
+ let remaining = text;
362
+ while (remaining.length > limit) {
363
+ const candidateBreak = resolveBreakIndex(remaining.slice(0, limit));
364
+ const breakIdx = Number.isFinite(candidateBreak) && candidateBreak > 0 && candidateBreak <= limit ? candidateBreak : limit;
365
+ const chunk = remaining.slice(0, breakIdx).trimEnd();
366
+ if (chunk.length > 0) chunks.push(chunk);
367
+ const brokeOnSeparator = breakIdx < remaining.length && /\s/.test(remaining[breakIdx]);
368
+ const nextStart = Math.min(remaining.length, breakIdx + (brokeOnSeparator ? 1 : 0));
369
+ remaining = remaining.slice(nextStart).trimStart();
370
+ }
371
+ if (remaining.length) chunks.push(remaining);
372
+ return chunks;
373
+ }
374
+ //#endregion
375
+ //#region src/auto-reply/chunk.ts
376
+ const DEFAULT_CHUNK_LIMIT = 4e3;
377
+ const DEFAULT_CHUNK_MODE = "length";
378
+ function resolveChunkLimitForProvider(cfgSection, accountId) {
379
+ if (!cfgSection) return;
380
+ const normalizedAccountId = normalizeAccountId(accountId);
381
+ const accounts = cfgSection.accounts;
382
+ if (accounts && typeof accounts === "object") {
383
+ const direct = resolveAccountEntry(accounts, normalizedAccountId);
384
+ if (typeof direct?.textChunkLimit === "number") return direct.textChunkLimit;
385
+ }
386
+ return cfgSection.textChunkLimit;
387
+ }
388
+ function resolveTextChunkLimit(cfg, provider, accountId, opts) {
389
+ const fallback = typeof opts?.fallbackLimit === "number" && opts.fallbackLimit > 0 ? opts.fallbackLimit : DEFAULT_CHUNK_LIMIT;
390
+ const providerOverride = (() => {
391
+ if (!provider || provider === "webchat") return;
392
+ return resolveChunkLimitForProvider((cfg?.channels)?.[provider] ?? cfg?.[provider], accountId);
393
+ })();
394
+ if (typeof providerOverride === "number" && providerOverride > 0) return providerOverride;
395
+ return fallback;
396
+ }
397
+ function resolveChunkModeForProvider(cfgSection, accountId) {
398
+ if (!cfgSection) return;
399
+ const normalizedAccountId = normalizeAccountId(accountId);
400
+ const accounts = cfgSection.accounts;
401
+ if (accounts && typeof accounts === "object") {
402
+ const direct = resolveAccountEntry(accounts, normalizedAccountId);
403
+ if (direct?.chunkMode) return direct.chunkMode;
404
+ }
405
+ return cfgSection.chunkMode;
406
+ }
407
+ function resolveChunkMode(cfg, provider, accountId) {
408
+ if (!provider || provider === "webchat") return DEFAULT_CHUNK_MODE;
409
+ return resolveChunkModeForProvider((cfg?.channels)?.[provider] ?? cfg?.[provider], accountId) ?? DEFAULT_CHUNK_MODE;
410
+ }
411
+ /**
412
+ * Split text on newlines, trimming line whitespace.
413
+ * Blank lines are folded into the next non-empty line as leading "\n" prefixes.
414
+ * Long lines can be split by length (default) or kept intact via splitLongLines:false.
415
+ */
416
+ function chunkByNewline(text, maxLineLength, opts) {
417
+ if (!text) return [];
418
+ if (maxLineLength <= 0) return text.trim() ? [text] : [];
419
+ const splitLongLines = opts?.splitLongLines !== false;
420
+ const trimLines = opts?.trimLines !== false;
421
+ const lines = splitByNewline(text, opts?.isSafeBreak);
422
+ const chunks = [];
423
+ let pendingBlankLines = 0;
424
+ for (const line of lines) {
425
+ const trimmed = line.trim();
426
+ if (!trimmed) {
427
+ pendingBlankLines += 1;
428
+ continue;
429
+ }
430
+ const maxPrefix = Math.max(0, maxLineLength - 1);
431
+ const cappedBlankLines = pendingBlankLines > 0 ? Math.min(pendingBlankLines, maxPrefix) : 0;
432
+ const prefix = cappedBlankLines > 0 ? "\n".repeat(cappedBlankLines) : "";
433
+ pendingBlankLines = 0;
434
+ const lineValue = trimLines ? trimmed : line;
435
+ if (!splitLongLines || lineValue.length + prefix.length <= maxLineLength) {
436
+ chunks.push(prefix + lineValue);
437
+ continue;
438
+ }
439
+ const firstLimit = Math.max(1, maxLineLength - prefix.length);
440
+ const first = lineValue.slice(0, firstLimit);
441
+ chunks.push(prefix + first);
442
+ const remaining = lineValue.slice(firstLimit);
443
+ if (remaining) chunks.push(...chunkText(remaining, maxLineLength));
444
+ }
445
+ if (pendingBlankLines > 0 && chunks.length > 0) chunks[chunks.length - 1] += "\n".repeat(pendingBlankLines);
446
+ return chunks;
447
+ }
448
+ /**
449
+ * Split text into chunks on paragraph boundaries (blank lines), preserving lists and
450
+ * single-newline line wraps inside paragraphs.
451
+ *
452
+ * - Only breaks at paragraph separators ("\n\n" or more, allowing whitespace on blank lines)
453
+ * - Packs multiple paragraphs into a single chunk up to `limit`
454
+ * - Falls back to length-based splitting when a single paragraph exceeds `limit`
455
+ * (unless `splitLongParagraphs` is disabled)
456
+ */
457
+ function chunkByParagraph(text, limit, opts) {
458
+ if (!text) return [];
459
+ if (limit <= 0) return [text];
460
+ const splitLongParagraphs = opts?.splitLongParagraphs !== false;
461
+ const normalized = text.replace(/\r\n?/g, "\n");
462
+ if (!/\n[\t ]*\n+/.test(normalized)) {
463
+ if (normalized.length <= limit) return [normalized];
464
+ if (!splitLongParagraphs) return [normalized];
465
+ return chunkText(normalized, limit);
466
+ }
467
+ const spans = parseFenceSpans(normalized);
468
+ const parts = [];
469
+ const re = /\n[\t ]*\n+/g;
470
+ let lastIndex = 0;
471
+ for (const match of normalized.matchAll(re)) {
472
+ const idx = match.index ?? 0;
473
+ if (!isSafeFenceBreak(spans, idx)) continue;
474
+ parts.push(normalized.slice(lastIndex, idx));
475
+ lastIndex = idx + match[0].length;
476
+ }
477
+ parts.push(normalized.slice(lastIndex));
478
+ const chunks = [];
479
+ for (const part of parts) {
480
+ const paragraph = part.replace(/\s+$/g, "");
481
+ if (!paragraph.trim()) continue;
482
+ if (paragraph.length <= limit) chunks.push(paragraph);
483
+ else if (!splitLongParagraphs) chunks.push(paragraph);
484
+ else chunks.push(...chunkText(paragraph, limit));
485
+ }
486
+ return chunks;
487
+ }
488
+ /**
489
+ * Unified chunking function that dispatches based on mode.
490
+ */
491
+ function chunkTextWithMode(text, limit, mode) {
492
+ if (mode === "newline") return chunkByParagraph(text, limit);
493
+ return chunkText(text, limit);
494
+ }
495
+ function chunkMarkdownTextWithMode(text, limit, mode) {
496
+ if (mode === "newline") {
497
+ const paragraphChunks = chunkByParagraph(text, limit, { splitLongParagraphs: false });
498
+ const out = [];
499
+ for (const chunk of paragraphChunks) {
500
+ const nested = chunkMarkdownText(chunk, limit);
501
+ if (!nested.length && chunk) out.push(chunk);
502
+ else out.push(...nested);
503
+ }
504
+ return out;
505
+ }
506
+ return chunkMarkdownText(text, limit);
507
+ }
508
+ function splitByNewline(text, isSafeBreak = () => true) {
509
+ const lines = [];
510
+ let start = 0;
511
+ for (let i = 0; i < text.length; i++) if (text[i] === "\n" && isSafeBreak(i)) {
512
+ lines.push(text.slice(start, i));
513
+ start = i + 1;
514
+ }
515
+ lines.push(text.slice(start));
516
+ return lines;
517
+ }
518
+ function resolveChunkEarlyReturn(text, limit) {
519
+ if (!text) return [];
520
+ if (limit <= 0) return [text];
521
+ if (text.length <= limit) return [text];
522
+ }
523
+ function chunkText(text, limit) {
524
+ const early = resolveChunkEarlyReturn(text, limit);
525
+ if (early) return early;
526
+ return chunkTextByBreakResolver(text, limit, (window) => {
527
+ const { lastNewline, lastWhitespace } = scanParenAwareBreakpoints(window, 0, window.length);
528
+ return lastNewline > 0 ? lastNewline : lastWhitespace;
529
+ });
530
+ }
531
+ function chunkMarkdownText(text, limit) {
532
+ const early = resolveChunkEarlyReturn(text, limit);
533
+ if (early) return early;
534
+ const chunks = [];
535
+ const spans = parseFenceSpans(text);
536
+ let start = 0;
537
+ let reopenFence;
538
+ while (start < text.length) {
539
+ const reopenPrefix = reopenFence ? `${reopenFence.openLine}\n` : "";
540
+ const contentLimit = Math.max(1, limit - reopenPrefix.length);
541
+ if (text.length - start <= contentLimit) {
542
+ const finalChunk = `${reopenPrefix}${text.slice(start)}`;
543
+ if (finalChunk.length > 0) chunks.push(finalChunk);
544
+ break;
545
+ }
546
+ const windowEnd = Math.min(text.length, start + contentLimit);
547
+ const softBreak = pickSafeBreakIndex(text, start, windowEnd, spans);
548
+ let breakIdx = softBreak > start ? softBreak : windowEnd;
549
+ const initialFence = isSafeFenceBreak(spans, breakIdx) ? void 0 : findFenceSpanAt(spans, breakIdx);
550
+ let fenceToSplit = initialFence;
551
+ if (initialFence) {
552
+ const closeLine = `${initialFence.indent}${initialFence.marker}`;
553
+ const maxIdxIfNeedNewline = start + (contentLimit - (closeLine.length + 1));
554
+ if (maxIdxIfNeedNewline <= start) {
555
+ fenceToSplit = void 0;
556
+ breakIdx = windowEnd;
557
+ } else {
558
+ const minProgressIdx = Math.min(text.length, Math.max(start + 1, initialFence.start + initialFence.openLine.length + 2));
559
+ const maxIdxIfAlreadyNewline = start + (contentLimit - closeLine.length);
560
+ let pickedNewline = false;
561
+ let lastNewline = text.lastIndexOf("\n", Math.max(start, maxIdxIfAlreadyNewline - 1));
562
+ while (lastNewline >= start) {
563
+ const candidateBreak = lastNewline + 1;
564
+ if (candidateBreak < minProgressIdx) break;
565
+ const candidateFence = findFenceSpanAt(spans, candidateBreak);
566
+ if (candidateFence && candidateFence.start === initialFence.start) {
567
+ breakIdx = candidateBreak;
568
+ pickedNewline = true;
569
+ break;
570
+ }
571
+ lastNewline = text.lastIndexOf("\n", lastNewline - 1);
572
+ }
573
+ if (!pickedNewline) if (minProgressIdx > maxIdxIfAlreadyNewline) {
574
+ fenceToSplit = void 0;
575
+ breakIdx = windowEnd;
576
+ } else breakIdx = Math.max(minProgressIdx, maxIdxIfNeedNewline);
577
+ }
578
+ const fenceAtBreak = findFenceSpanAt(spans, breakIdx);
579
+ fenceToSplit = fenceAtBreak && fenceAtBreak.start === initialFence.start ? fenceAtBreak : void 0;
580
+ }
581
+ const rawContent = text.slice(start, breakIdx);
582
+ if (!rawContent) break;
583
+ let rawChunk = `${reopenPrefix}${rawContent}`;
584
+ const brokeOnSeparator = breakIdx < text.length && /\s/.test(text[breakIdx]);
585
+ let nextStart = Math.min(text.length, breakIdx + (brokeOnSeparator ? 1 : 0));
586
+ if (fenceToSplit) {
587
+ const closeLine = `${fenceToSplit.indent}${fenceToSplit.marker}`;
588
+ rawChunk = rawChunk.endsWith("\n") ? `${rawChunk}${closeLine}` : `${rawChunk}\n${closeLine}`;
589
+ reopenFence = fenceToSplit;
590
+ } else {
591
+ nextStart = skipLeadingNewlines(text, nextStart);
592
+ reopenFence = void 0;
593
+ }
594
+ chunks.push(rawChunk);
595
+ start = nextStart;
596
+ }
597
+ return chunks;
598
+ }
599
+ function skipLeadingNewlines(value, start = 0) {
600
+ let i = start;
601
+ while (i < value.length && value[i] === "\n") i++;
602
+ return i;
603
+ }
604
+ function pickSafeBreakIndex(text, start, end, spans) {
605
+ const { lastNewline, lastWhitespace } = scanParenAwareBreakpoints(text, start, end, (index) => isSafeFenceBreak(spans, index));
606
+ if (lastNewline > start) return lastNewline;
607
+ if (lastWhitespace > start) return lastWhitespace;
608
+ return -1;
609
+ }
610
+ function scanParenAwareBreakpoints(text, start, end, isAllowed = () => true) {
611
+ let lastNewline = -1;
612
+ let lastWhitespace = -1;
613
+ let depth = 0;
614
+ for (let i = start; i < end; i++) {
615
+ if (!isAllowed(i)) continue;
616
+ const char = text[i];
617
+ if (char === "(") {
618
+ depth += 1;
619
+ continue;
620
+ }
621
+ if (char === ")" && depth > 0) {
622
+ depth -= 1;
623
+ continue;
624
+ }
625
+ if (depth !== 0) continue;
626
+ if (char === "\n") lastNewline = i;
627
+ else if (/\s/.test(char)) lastWhitespace = i;
628
+ }
629
+ return {
630
+ lastNewline,
631
+ lastWhitespace
632
+ };
633
+ }
634
+ //#endregion
635
+ //#region src/config/markdown-tables.ts
636
+ const DEFAULT_TABLE_MODES = new Map([["signal", "bullets"], ["whatsapp", "bullets"]]);
637
+ const isMarkdownTableMode = (value) => value === "off" || value === "bullets" || value === "code";
638
+ function resolveMarkdownModeFromSection(section, accountId) {
639
+ if (!section) return;
640
+ const normalizedAccountId = normalizeAccountId(accountId);
641
+ const accounts = section.accounts;
642
+ if (accounts && typeof accounts === "object") {
643
+ const matchMode = resolveAccountEntry(accounts, normalizedAccountId)?.markdown?.tables;
644
+ if (isMarkdownTableMode(matchMode)) return matchMode;
645
+ }
646
+ const sectionMode = section.markdown?.tables;
647
+ return isMarkdownTableMode(sectionMode) ? sectionMode : void 0;
648
+ }
649
+ function resolveMarkdownTableMode(params) {
650
+ const channel = normalizeChannelId(params.channel);
651
+ const defaultMode = channel ? DEFAULT_TABLE_MODES.get(channel) ?? "code" : "code";
652
+ if (!channel || !params.cfg) return defaultMode;
653
+ return resolveMarkdownModeFromSection(params.cfg.channels?.[channel] ?? params.cfg?.[channel], params.accountId) ?? defaultMode;
654
+ }
655
+ //#endregion
656
+ //#region src/markdown/ir.ts
657
+ function createMarkdownIt(options) {
658
+ const md = new MarkdownIt({
659
+ html: false,
660
+ linkify: options.linkify ?? true,
661
+ breaks: false,
662
+ typographer: false
663
+ });
664
+ md.enable("strikethrough");
665
+ if (options.tableMode && options.tableMode !== "off") md.enable("table");
666
+ else md.disable("table");
667
+ if (options.autolink === false) md.disable("autolink");
668
+ return md;
669
+ }
670
+ function getAttr(token, name) {
671
+ if (token.attrGet) return token.attrGet(name);
672
+ if (token.attrs) {
673
+ for (const [key, value] of token.attrs) if (key === name) return value;
674
+ }
675
+ return null;
676
+ }
677
+ function createTextToken(base, content) {
678
+ return {
679
+ ...base,
680
+ type: "text",
681
+ content,
682
+ children: void 0
683
+ };
684
+ }
685
+ function applySpoilerTokens(tokens) {
686
+ for (const token of tokens) if (token.children && token.children.length > 0) token.children = injectSpoilersIntoInline(token.children);
687
+ }
688
+ function injectSpoilersIntoInline(tokens) {
689
+ let totalDelims = 0;
690
+ for (const token of tokens) {
691
+ if (token.type !== "text") continue;
692
+ const content = token.content ?? "";
693
+ let i = 0;
694
+ while (i < content.length) {
695
+ const next = content.indexOf("||", i);
696
+ if (next === -1) break;
697
+ totalDelims += 1;
698
+ i = next + 2;
699
+ }
700
+ }
701
+ if (totalDelims < 2) return tokens;
702
+ const usableDelims = totalDelims - totalDelims % 2;
703
+ const result = [];
704
+ const state = { spoilerOpen: false };
705
+ let consumedDelims = 0;
706
+ for (const token of tokens) {
707
+ if (token.type !== "text") {
708
+ result.push(token);
709
+ continue;
710
+ }
711
+ const content = token.content ?? "";
712
+ if (!content.includes("||")) {
713
+ result.push(token);
714
+ continue;
715
+ }
716
+ let index = 0;
717
+ while (index < content.length) {
718
+ const next = content.indexOf("||", index);
719
+ if (next === -1) {
720
+ if (index < content.length) result.push(createTextToken(token, content.slice(index)));
721
+ break;
722
+ }
723
+ if (consumedDelims >= usableDelims) {
724
+ result.push(createTextToken(token, content.slice(index)));
725
+ break;
726
+ }
727
+ if (next > index) result.push(createTextToken(token, content.slice(index, next)));
728
+ consumedDelims += 1;
729
+ state.spoilerOpen = !state.spoilerOpen;
730
+ result.push({ type: state.spoilerOpen ? "spoiler_open" : "spoiler_close" });
731
+ index = next + 2;
732
+ }
733
+ }
734
+ return result;
735
+ }
736
+ function initRenderTarget() {
737
+ return {
738
+ text: "",
739
+ styles: [],
740
+ openStyles: [],
741
+ links: [],
742
+ linkStack: []
743
+ };
744
+ }
745
+ function resolveRenderTarget(state) {
746
+ return state.table?.currentCell ?? state;
747
+ }
748
+ function appendText(state, value) {
749
+ if (!value) return;
750
+ const target = resolveRenderTarget(state);
751
+ target.text += value;
752
+ }
753
+ function openStyle(state, style) {
754
+ const target = resolveRenderTarget(state);
755
+ target.openStyles.push({
756
+ style,
757
+ start: target.text.length
758
+ });
759
+ }
760
+ function closeStyle(state, style) {
761
+ const target = resolveRenderTarget(state);
762
+ for (let i = target.openStyles.length - 1; i >= 0; i -= 1) if (target.openStyles[i]?.style === style) {
763
+ const start = target.openStyles[i].start;
764
+ target.openStyles.splice(i, 1);
765
+ const end = target.text.length;
766
+ if (end > start) target.styles.push({
767
+ start,
768
+ end,
769
+ style
770
+ });
771
+ return;
772
+ }
773
+ }
774
+ function appendParagraphSeparator(state) {
775
+ if (state.env.listStack.length > 0) return;
776
+ if (state.table) return;
777
+ state.text += "\n\n";
778
+ }
779
+ function appendListPrefix(state) {
780
+ const stack = state.env.listStack;
781
+ const top = stack[stack.length - 1];
782
+ if (!top) return;
783
+ top.index += 1;
784
+ const indent = " ".repeat(Math.max(0, stack.length - 1));
785
+ const prefix = top.type === "ordered" ? `${top.index}. ` : "• ";
786
+ state.text += `${indent}${prefix}`;
787
+ }
788
+ function renderInlineCode(state, content) {
789
+ if (!content) return;
790
+ const target = resolveRenderTarget(state);
791
+ const start = target.text.length;
792
+ target.text += content;
793
+ target.styles.push({
794
+ start,
795
+ end: start + content.length,
796
+ style: "code"
797
+ });
798
+ }
799
+ function renderCodeBlock(state, content) {
800
+ let code = content ?? "";
801
+ if (!code.endsWith("\n")) code = `${code}\n`;
802
+ const target = resolveRenderTarget(state);
803
+ const start = target.text.length;
804
+ target.text += code;
805
+ target.styles.push({
806
+ start,
807
+ end: start + code.length,
808
+ style: "code_block"
809
+ });
810
+ if (state.env.listStack.length === 0) target.text += "\n";
811
+ }
812
+ function handleLinkClose(state) {
813
+ const target = resolveRenderTarget(state);
814
+ const link = target.linkStack.pop();
815
+ if (!link?.href) return;
816
+ const href = link.href.trim();
817
+ if (!href) return;
818
+ const start = link.labelStart;
819
+ const end = target.text.length;
820
+ if (end <= start) {
821
+ target.links.push({
822
+ start,
823
+ end,
824
+ href
825
+ });
826
+ return;
827
+ }
828
+ target.links.push({
829
+ start,
830
+ end,
831
+ href
832
+ });
833
+ }
834
+ function initTableState() {
835
+ return {
836
+ headers: [],
837
+ rows: [],
838
+ currentRow: [],
839
+ currentCell: null,
840
+ inHeader: false
841
+ };
842
+ }
843
+ function finishTableCell(cell) {
844
+ closeRemainingStyles(cell);
845
+ return {
846
+ text: cell.text,
847
+ styles: cell.styles,
848
+ links: cell.links
849
+ };
850
+ }
851
+ function trimCell(cell) {
852
+ const text = cell.text;
853
+ let start = 0;
854
+ let end = text.length;
855
+ while (start < end && /\s/.test(text[start] ?? "")) start += 1;
856
+ while (end > start && /\s/.test(text[end - 1] ?? "")) end -= 1;
857
+ if (start === 0 && end === text.length) return cell;
858
+ const trimmedText = text.slice(start, end);
859
+ const trimmedLength = trimmedText.length;
860
+ const trimmedStyles = [];
861
+ for (const span of cell.styles) {
862
+ const sliceStart = Math.max(0, span.start - start);
863
+ const sliceEnd = Math.min(trimmedLength, span.end - start);
864
+ if (sliceEnd > sliceStart) trimmedStyles.push({
865
+ start: sliceStart,
866
+ end: sliceEnd,
867
+ style: span.style
868
+ });
869
+ }
870
+ const trimmedLinks = [];
871
+ for (const span of cell.links) {
872
+ const sliceStart = Math.max(0, span.start - start);
873
+ const sliceEnd = Math.min(trimmedLength, span.end - start);
874
+ if (sliceEnd > sliceStart) trimmedLinks.push({
875
+ start: sliceStart,
876
+ end: sliceEnd,
877
+ href: span.href
878
+ });
879
+ }
880
+ return {
881
+ text: trimmedText,
882
+ styles: trimmedStyles,
883
+ links: trimmedLinks
884
+ };
885
+ }
886
+ function appendCell(state, cell) {
887
+ if (!cell.text) return;
888
+ const start = state.text.length;
889
+ state.text += cell.text;
890
+ for (const span of cell.styles) state.styles.push({
891
+ start: start + span.start,
892
+ end: start + span.end,
893
+ style: span.style
894
+ });
895
+ for (const link of cell.links) state.links.push({
896
+ start: start + link.start,
897
+ end: start + link.end,
898
+ href: link.href
899
+ });
900
+ }
901
+ function appendCellTextOnly(state, cell) {
902
+ if (!cell.text) return;
903
+ state.text += cell.text;
904
+ }
905
+ function appendTableBulletValue(state, params) {
906
+ const { header, value, columnIndex, includeColumnFallback } = params;
907
+ if (!value?.text) return;
908
+ state.text += "• ";
909
+ if (header?.text) {
910
+ appendCell(state, header);
911
+ state.text += ": ";
912
+ } else if (includeColumnFallback) state.text += `Column ${columnIndex}: `;
913
+ appendCell(state, value);
914
+ state.text += "\n";
915
+ }
916
+ function renderTableAsBullets(state) {
917
+ if (!state.table) return;
918
+ const headers = state.table.headers.map(trimCell);
919
+ const rows = state.table.rows.map((row) => row.map(trimCell));
920
+ if (headers.length === 0 && rows.length === 0) return;
921
+ if (headers.length > 1 && rows.length > 0) for (const row of rows) {
922
+ if (row.length === 0) continue;
923
+ const rowLabel = row[0];
924
+ if (rowLabel?.text) {
925
+ const labelStart = state.text.length;
926
+ appendCell(state, rowLabel);
927
+ const labelEnd = state.text.length;
928
+ if (labelEnd > labelStart) state.styles.push({
929
+ start: labelStart,
930
+ end: labelEnd,
931
+ style: "bold"
932
+ });
933
+ state.text += "\n";
934
+ }
935
+ for (let i = 1; i < row.length; i++) appendTableBulletValue(state, {
936
+ header: headers[i],
937
+ value: row[i],
938
+ columnIndex: i,
939
+ includeColumnFallback: true
940
+ });
941
+ state.text += "\n";
942
+ }
943
+ else for (const row of rows) {
944
+ for (let i = 0; i < row.length; i++) appendTableBulletValue(state, {
945
+ header: headers[i],
946
+ value: row[i],
947
+ columnIndex: i,
948
+ includeColumnFallback: false
949
+ });
950
+ state.text += "\n";
951
+ }
952
+ }
953
+ function renderTableAsCode(state) {
954
+ if (!state.table) return;
955
+ const headers = state.table.headers.map(trimCell);
956
+ const rows = state.table.rows.map((row) => row.map(trimCell));
957
+ const columnCount = Math.max(headers.length, ...rows.map((row) => row.length));
958
+ if (columnCount === 0) return;
959
+ const widths = Array.from({ length: columnCount }, () => 0);
960
+ const updateWidths = (cells) => {
961
+ for (let i = 0; i < columnCount; i += 1) {
962
+ const width = cells[i]?.text.length ?? 0;
963
+ if (widths[i] < width) widths[i] = width;
964
+ }
965
+ };
966
+ updateWidths(headers);
967
+ for (const row of rows) updateWidths(row);
968
+ const codeStart = state.text.length;
969
+ const appendRow = (cells) => {
970
+ state.text += "|";
971
+ for (let i = 0; i < columnCount; i += 1) {
972
+ state.text += " ";
973
+ const cell = cells[i];
974
+ if (cell) appendCellTextOnly(state, cell);
975
+ const pad = widths[i] - (cell?.text.length ?? 0);
976
+ if (pad > 0) state.text += " ".repeat(pad);
977
+ state.text += " |";
978
+ }
979
+ state.text += "\n";
980
+ };
981
+ const appendDivider = () => {
982
+ state.text += "|";
983
+ for (let i = 0; i < columnCount; i += 1) {
984
+ const dashCount = Math.max(3, widths[i]);
985
+ state.text += ` ${"-".repeat(dashCount)} |`;
986
+ }
987
+ state.text += "\n";
988
+ };
989
+ appendRow(headers);
990
+ appendDivider();
991
+ for (const row of rows) appendRow(row);
992
+ const codeEnd = state.text.length;
993
+ if (codeEnd > codeStart) state.styles.push({
994
+ start: codeStart,
995
+ end: codeEnd,
996
+ style: "code_block"
997
+ });
998
+ if (state.env.listStack.length === 0) state.text += "\n";
999
+ }
1000
+ function renderTokens(tokens, state) {
1001
+ for (const token of tokens) switch (token.type) {
1002
+ case "inline":
1003
+ if (token.children) renderTokens(token.children, state);
1004
+ break;
1005
+ case "text":
1006
+ appendText(state, token.content ?? "");
1007
+ break;
1008
+ case "em_open":
1009
+ openStyle(state, "italic");
1010
+ break;
1011
+ case "em_close":
1012
+ closeStyle(state, "italic");
1013
+ break;
1014
+ case "strong_open":
1015
+ openStyle(state, "bold");
1016
+ break;
1017
+ case "strong_close":
1018
+ closeStyle(state, "bold");
1019
+ break;
1020
+ case "s_open":
1021
+ openStyle(state, "strikethrough");
1022
+ break;
1023
+ case "s_close":
1024
+ closeStyle(state, "strikethrough");
1025
+ break;
1026
+ case "code_inline":
1027
+ renderInlineCode(state, token.content ?? "");
1028
+ break;
1029
+ case "spoiler_open":
1030
+ if (state.enableSpoilers) openStyle(state, "spoiler");
1031
+ break;
1032
+ case "spoiler_close":
1033
+ if (state.enableSpoilers) closeStyle(state, "spoiler");
1034
+ break;
1035
+ case "link_open": {
1036
+ const href = getAttr(token, "href") ?? "";
1037
+ const target = resolveRenderTarget(state);
1038
+ target.linkStack.push({
1039
+ href,
1040
+ labelStart: target.text.length
1041
+ });
1042
+ break;
1043
+ }
1044
+ case "link_close":
1045
+ handleLinkClose(state);
1046
+ break;
1047
+ case "image":
1048
+ appendText(state, token.content ?? "");
1049
+ break;
1050
+ case "softbreak":
1051
+ case "hardbreak":
1052
+ appendText(state, "\n");
1053
+ break;
1054
+ case "paragraph_close":
1055
+ appendParagraphSeparator(state);
1056
+ break;
1057
+ case "heading_open":
1058
+ if (state.headingStyle === "bold") openStyle(state, "bold");
1059
+ break;
1060
+ case "heading_close":
1061
+ if (state.headingStyle === "bold") closeStyle(state, "bold");
1062
+ appendParagraphSeparator(state);
1063
+ break;
1064
+ case "blockquote_open":
1065
+ if (state.blockquotePrefix) state.text += state.blockquotePrefix;
1066
+ openStyle(state, "blockquote");
1067
+ break;
1068
+ case "blockquote_close":
1069
+ closeStyle(state, "blockquote");
1070
+ break;
1071
+ case "bullet_list_open":
1072
+ if (state.env.listStack.length > 0) state.text += "\n";
1073
+ state.env.listStack.push({
1074
+ type: "bullet",
1075
+ index: 0
1076
+ });
1077
+ break;
1078
+ case "bullet_list_close":
1079
+ state.env.listStack.pop();
1080
+ if (state.env.listStack.length === 0) state.text += "\n";
1081
+ break;
1082
+ case "ordered_list_open": {
1083
+ if (state.env.listStack.length > 0) state.text += "\n";
1084
+ const start = Number(getAttr(token, "start") ?? "1");
1085
+ state.env.listStack.push({
1086
+ type: "ordered",
1087
+ index: start - 1
1088
+ });
1089
+ break;
1090
+ }
1091
+ case "ordered_list_close":
1092
+ state.env.listStack.pop();
1093
+ if (state.env.listStack.length === 0) state.text += "\n";
1094
+ break;
1095
+ case "list_item_open":
1096
+ appendListPrefix(state);
1097
+ break;
1098
+ case "list_item_close":
1099
+ if (!state.text.endsWith("\n")) state.text += "\n";
1100
+ break;
1101
+ case "code_block":
1102
+ case "fence":
1103
+ renderCodeBlock(state, token.content ?? "");
1104
+ break;
1105
+ case "html_block":
1106
+ case "html_inline":
1107
+ appendText(state, token.content ?? "");
1108
+ break;
1109
+ case "table_open":
1110
+ if (state.tableMode !== "off") {
1111
+ state.table = initTableState();
1112
+ state.hasTables = true;
1113
+ }
1114
+ break;
1115
+ case "table_close":
1116
+ if (state.table) {
1117
+ if (state.tableMode === "bullets") renderTableAsBullets(state);
1118
+ else if (state.tableMode === "code") renderTableAsCode(state);
1119
+ }
1120
+ state.table = null;
1121
+ break;
1122
+ case "thead_open":
1123
+ if (state.table) state.table.inHeader = true;
1124
+ break;
1125
+ case "thead_close":
1126
+ if (state.table) state.table.inHeader = false;
1127
+ break;
1128
+ case "tbody_open":
1129
+ case "tbody_close": break;
1130
+ case "tr_open":
1131
+ if (state.table) state.table.currentRow = [];
1132
+ break;
1133
+ case "tr_close":
1134
+ if (state.table) {
1135
+ if (state.table.inHeader) state.table.headers = state.table.currentRow;
1136
+ else state.table.rows.push(state.table.currentRow);
1137
+ state.table.currentRow = [];
1138
+ }
1139
+ break;
1140
+ case "th_open":
1141
+ case "td_open":
1142
+ if (state.table) state.table.currentCell = initRenderTarget();
1143
+ break;
1144
+ case "th_close":
1145
+ case "td_close":
1146
+ if (state.table?.currentCell) {
1147
+ state.table.currentRow.push(finishTableCell(state.table.currentCell));
1148
+ state.table.currentCell = null;
1149
+ }
1150
+ break;
1151
+ case "hr":
1152
+ state.text += "───\n\n";
1153
+ break;
1154
+ default:
1155
+ if (token.children) renderTokens(token.children, state);
1156
+ break;
1157
+ }
1158
+ }
1159
+ function closeRemainingStyles(target) {
1160
+ for (let i = target.openStyles.length - 1; i >= 0; i -= 1) {
1161
+ const open = target.openStyles[i];
1162
+ const end = target.text.length;
1163
+ if (end > open.start) target.styles.push({
1164
+ start: open.start,
1165
+ end,
1166
+ style: open.style
1167
+ });
1168
+ }
1169
+ target.openStyles = [];
1170
+ }
1171
+ function clampStyleSpans(spans, maxLength) {
1172
+ const clamped = [];
1173
+ for (const span of spans) {
1174
+ const start = Math.max(0, Math.min(span.start, maxLength));
1175
+ const end = Math.max(start, Math.min(span.end, maxLength));
1176
+ if (end > start) clamped.push({
1177
+ start,
1178
+ end,
1179
+ style: span.style
1180
+ });
1181
+ }
1182
+ return clamped;
1183
+ }
1184
+ function clampLinkSpans(spans, maxLength) {
1185
+ const clamped = [];
1186
+ for (const span of spans) {
1187
+ const start = Math.max(0, Math.min(span.start, maxLength));
1188
+ const end = Math.max(start, Math.min(span.end, maxLength));
1189
+ if (end > start) clamped.push({
1190
+ start,
1191
+ end,
1192
+ href: span.href
1193
+ });
1194
+ }
1195
+ return clamped;
1196
+ }
1197
+ function mergeStyleSpans(spans) {
1198
+ const sorted = [...spans].toSorted((a, b) => {
1199
+ if (a.start !== b.start) return a.start - b.start;
1200
+ if (a.end !== b.end) return a.end - b.end;
1201
+ return a.style.localeCompare(b.style);
1202
+ });
1203
+ const merged = [];
1204
+ for (const span of sorted) {
1205
+ const prev = merged[merged.length - 1];
1206
+ if (prev && prev.style === span.style && (span.start < prev.end || span.start === prev.end && span.style !== "blockquote")) {
1207
+ prev.end = Math.max(prev.end, span.end);
1208
+ continue;
1209
+ }
1210
+ merged.push({ ...span });
1211
+ }
1212
+ return merged;
1213
+ }
1214
+ function resolveSliceBounds(span, start, end) {
1215
+ const sliceStart = Math.max(span.start, start);
1216
+ const sliceEnd = Math.min(span.end, end);
1217
+ if (sliceEnd <= sliceStart) return null;
1218
+ return {
1219
+ start: sliceStart,
1220
+ end: sliceEnd
1221
+ };
1222
+ }
1223
+ function sliceStyleSpans(spans, start, end) {
1224
+ if (spans.length === 0) return [];
1225
+ const sliced = [];
1226
+ for (const span of spans) {
1227
+ const bounds = resolveSliceBounds(span, start, end);
1228
+ if (!bounds) continue;
1229
+ sliced.push({
1230
+ start: bounds.start - start,
1231
+ end: bounds.end - start,
1232
+ style: span.style
1233
+ });
1234
+ }
1235
+ return mergeStyleSpans(sliced);
1236
+ }
1237
+ function sliceLinkSpans(spans, start, end) {
1238
+ if (spans.length === 0) return [];
1239
+ const sliced = [];
1240
+ for (const span of spans) {
1241
+ const bounds = resolveSliceBounds(span, start, end);
1242
+ if (!bounds) continue;
1243
+ sliced.push({
1244
+ start: bounds.start - start,
1245
+ end: bounds.end - start,
1246
+ href: span.href
1247
+ });
1248
+ }
1249
+ return sliced;
1250
+ }
1251
+ function markdownToIR(markdown, options = {}) {
1252
+ return markdownToIRWithMeta(markdown, options).ir;
1253
+ }
1254
+ function markdownToIRWithMeta(markdown, options = {}) {
1255
+ const env = { listStack: [] };
1256
+ const tokens = createMarkdownIt(options).parse(markdown ?? "", env);
1257
+ if (options.enableSpoilers) applySpoilerTokens(tokens);
1258
+ const tableMode = options.tableMode ?? "off";
1259
+ const state = {
1260
+ text: "",
1261
+ styles: [],
1262
+ openStyles: [],
1263
+ links: [],
1264
+ linkStack: [],
1265
+ env,
1266
+ headingStyle: options.headingStyle ?? "none",
1267
+ blockquotePrefix: options.blockquotePrefix ?? "",
1268
+ enableSpoilers: options.enableSpoilers ?? false,
1269
+ tableMode,
1270
+ table: null,
1271
+ hasTables: false
1272
+ };
1273
+ renderTokens(tokens, state);
1274
+ closeRemainingStyles(state);
1275
+ const trimmedLength = state.text.trimEnd().length;
1276
+ let codeBlockEnd = 0;
1277
+ for (const span of state.styles) {
1278
+ if (span.style !== "code_block") continue;
1279
+ if (span.end > codeBlockEnd) codeBlockEnd = span.end;
1280
+ }
1281
+ const finalLength = Math.max(trimmedLength, codeBlockEnd);
1282
+ return {
1283
+ ir: {
1284
+ text: finalLength === state.text.length ? state.text : state.text.slice(0, finalLength),
1285
+ styles: mergeStyleSpans(clampStyleSpans(state.styles, finalLength)),
1286
+ links: clampLinkSpans(state.links, finalLength)
1287
+ },
1288
+ hasTables: state.hasTables
1289
+ };
1290
+ }
1291
+ function chunkMarkdownIR(ir, limit) {
1292
+ if (!ir.text) return [];
1293
+ if (limit <= 0 || ir.text.length <= limit) return [ir];
1294
+ const chunks = chunkText(ir.text, limit);
1295
+ const results = [];
1296
+ let cursor = 0;
1297
+ chunks.forEach((chunk, index) => {
1298
+ if (!chunk) return;
1299
+ if (index > 0) while (cursor < ir.text.length && /\s/.test(ir.text[cursor] ?? "")) cursor += 1;
1300
+ const start = cursor;
1301
+ const end = Math.min(ir.text.length, start + chunk.length);
1302
+ results.push({
1303
+ text: chunk,
1304
+ styles: sliceStyleSpans(ir.styles, start, end),
1305
+ links: sliceLinkSpans(ir.links, start, end)
1306
+ });
1307
+ cursor = end;
1308
+ });
1309
+ return results;
1310
+ }
1311
+ //#endregion
1312
+ export { getDefaultLocalRoots as _, chunkByNewline as a, chunkMarkdownTextWithMode as c, resolveChunkMode as d, resolveTextChunkLimit as f, parseFenceSpans as g, isSafeFenceBreak as h, resolveMarkdownTableMode as i, chunkText as l, findFenceSpanAt as m, markdownToIR as n, chunkByParagraph as o, chunkTextByBreakResolver as p, markdownToIRWithMeta as r, chunkMarkdownText as s, chunkMarkdownIR as t, chunkTextWithMode as u, loadWebMedia as v, loadWebMediaRaw as y };