@openclaw/line 2026.7.1 → 2026.7.2-beta.2

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/api.js CHANGED
@@ -1,5 +1,5 @@
1
- import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-C_8hLniz.js";
2
- import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-Bc36YSS3.js";
1
+ import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-BHR-GoJ0.js";
2
+ import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-B7W6pwn3.js";
3
3
  //#region extensions/line/src/channel.setup.ts
4
4
  const lineSetupPlugin = {
5
5
  id: "line",
@@ -1,6 +1,7 @@
1
- import { c as uriAction, d as createReceiptCard, f as createActionCard, g as createListCard, h as createInfoCard, m as createImageCard, o as messageAction, s as postbackAction } from "./flex-templates-DEIXn69v.js";
1
+ import { c as uriAction, d as createReceiptCard, f as createActionCard, g as createListCard, h as createInfoCard, m as createImageCard, o as messageAction, s as postbackAction } from "./flex-templates-K1lS9i8L.js";
2
2
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
4
5
  //#region extensions/line/src/card-command.ts
5
6
  const CARD_USAGE = `Usage: /card <type> "title" "body" [options]
6
7
 
@@ -92,9 +93,12 @@ function parseCardArgs(argsStrInput) {
92
93
  }
93
94
  const quotedRegex = /"([^"]*?)"/g;
94
95
  let match;
95
- while ((match = quotedRegex.exec(argsStr)) !== null) result.args.push(match[1]);
96
+ while ((match = quotedRegex.exec(argsStr)) !== null) result.args.push(expectDefined(match[1], "quoted card argument capture"));
96
97
  const flagRegex = /--(\w+)\s+(?:"([^"]*?)"|(\S+))/g;
97
- while ((match = flagRegex.exec(argsStr)) !== null) result.flags[match[1]] = match[2] ?? match[3];
98
+ while ((match = flagRegex.exec(argsStr)) !== null) {
99
+ const key = expectDefined(match[1], "card flag name capture");
100
+ result.flags[key] = expectDefined(match[2] ?? match[3], "card flag value capture");
101
+ }
98
102
  return result;
99
103
  }
100
104
  function registerLineCardCommand(api) {
@@ -1,10 +1,11 @@
1
1
  import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-DJVOv1JI.js";
2
- import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-Bc36YSS3.js";
3
- import { a as resolveLineOutboundMedia, d as resolveExactLineGroupConfigKey, i as buildLineQuickReplyFallbackText, l as LineChannelConfigSchema, n as parseLineDirectives, r as createLineSendReceipt, s as getLineRuntime, t as hasLineDirectives } from "./reply-payload-transform-C7r5npxP.js";
2
+ import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-B7W6pwn3.js";
3
+ import { a as buildLineMediaMessage, d as LineChannelConfigSchema, i as buildLineQuickReplyFallbackText, l as getLineRuntime, n as parseLineDirectives, o as hasLineSpecificMediaOptions, p as resolveExactLineGroupConfigKey, r as createLineSendReceipt, s as resolveLineOutboundMedia, t as hasLineDirectives } from "./reply-payload-transform-r9sa-TYT.js";
4
4
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
5
+ import { buildDmGroupAccountAllowlistAdapter, createFlatAllowlistOverrideResolver } from "openclaw/plugin-sdk/allowlist-config-edit";
5
6
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
6
7
  import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
7
- import { createRestrictSendersChannelSecurity, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
8
+ import { buildChannelGroupsScopeTree, createRestrictSendersChannelSecurity, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
8
9
  import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
9
10
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/account-id";
10
11
  import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
@@ -14,6 +15,7 @@ import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtim
14
15
  import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
15
16
  import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
16
17
  import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
18
+ import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
17
19
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
20
  import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "openclaw/plugin-sdk/status-helpers";
19
21
  //#region extensions/line/src/bindings.ts
@@ -58,6 +60,22 @@ const lineBindingsAdapter = {
58
60
  function normalizeLineAllowFrom(entry) {
59
61
  return entry.replace(/^line:(?:user:)?/i, "");
60
62
  }
63
+ const lineConfigAdapter = createScopedChannelConfigAdapter({
64
+ sectionKey: "line",
65
+ listAccountIds: listLineAccountIds,
66
+ resolveAccount: (cfg, accountId) => resolveLineAccount({
67
+ cfg,
68
+ accountId: accountId ?? void 0
69
+ }),
70
+ defaultAccountId: resolveDefaultLineAccountId,
71
+ clearBaseFields: [
72
+ "channelSecret",
73
+ "tokenFile",
74
+ "secretFile"
75
+ ],
76
+ resolveAllowFrom: (account) => account.config.allowFrom,
77
+ formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
78
+ });
61
79
  const lineChannelPluginCommon = {
62
80
  meta: {
63
81
  id: "line",
@@ -81,22 +99,7 @@ const lineChannelPluginCommon = {
81
99
  reload: { configPrefixes: ["channels.line"] },
82
100
  configSchema: LineChannelConfigSchema,
83
101
  config: {
84
- ...createScopedChannelConfigAdapter({
85
- sectionKey: "line",
86
- listAccountIds: listLineAccountIds,
87
- resolveAccount: (cfg, accountId) => resolveLineAccount({
88
- cfg,
89
- accountId: accountId ?? void 0
90
- }),
91
- defaultAccountId: resolveDefaultLineAccountId,
92
- clearBaseFields: [
93
- "channelSecret",
94
- "tokenFile",
95
- "secretFile"
96
- ],
97
- resolveAllowFrom: (account) => account.config.allowFrom,
98
- formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
99
- }),
102
+ ...lineConfigAdapter,
100
103
  isConfigured: (account) => hasLineCredentials(account),
101
104
  describeAccount: (account) => describeWebhookAccountSnapshot({
102
105
  account,
@@ -108,7 +111,7 @@ const lineChannelPluginCommon = {
108
111
  //#endregion
109
112
  //#region extensions/line/src/gateway.ts
110
113
  const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-CRhVGqwl.js"));
111
- const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-BGIpxzfB.js"));
114
+ const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-CZuQgsEE.js"));
112
115
  const lineGatewayAdapter = {
113
116
  startAccount: async (ctx) => {
114
117
  const account = ctx.account;
@@ -198,16 +201,14 @@ const lineGatewayAdapter = {
198
201
  //#endregion
199
202
  //#region extensions/line/src/group-policy.ts
200
203
  function resolveLineGroupRequireMention(params) {
201
- const exactGroupId = resolveExactLineGroupConfigKey({
202
- cfg: params.cfg,
203
- accountId: params.accountId,
204
+ const tree = buildChannelGroupsScopeTree(params.cfg, "line", params.accountId);
205
+ const matchedKey = resolveExactLineGroupConfigKey({
206
+ groups: tree.scopes,
204
207
  groupId: params.groupId
205
208
  });
206
- return resolveChannelGroupRequireMention({
207
- cfg: params.cfg,
208
- channel: "line",
209
- groupId: exactGroupId ?? params.groupId,
210
- accountId: params.accountId
209
+ return resolveScopeRequireMention({
210
+ tree,
211
+ path: matchedKey ? [matchedKey] : []
211
212
  });
212
213
  }
213
214
  //#endregion
@@ -225,42 +226,12 @@ function inferLineTargetChatType(target) {
225
226
  }
226
227
  //#endregion
227
228
  //#region extensions/line/src/outbound.ts
228
- const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-BwC7Y2U9.js"));
229
- function isLineUserTarget(target) {
230
- const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
231
- return /^U/i.test(normalized);
232
- }
233
- function hasLineSpecificMediaOptions(lineData) {
234
- return Boolean(lineData.mediaKind ?? lineData.previewImageUrl?.trim() ?? (typeof lineData.durationMs === "number" ? lineData.durationMs : void 0) ?? lineData.trackingId?.trim());
235
- }
236
- function buildLineMediaMessageObject(resolved, opts) {
237
- switch (resolved.mediaKind) {
238
- case "video": {
239
- const previewImageUrl = resolved.previewImageUrl?.trim();
240
- if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
241
- return {
242
- type: "video",
243
- originalContentUrl: resolved.mediaUrl,
244
- previewImageUrl,
245
- ...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
246
- };
247
- }
248
- case "audio": return {
249
- type: "audio",
250
- originalContentUrl: resolved.mediaUrl,
251
- duration: resolved.durationMs ?? 6e4
252
- };
253
- default: return {
254
- type: "image",
255
- originalContentUrl: resolved.mediaUrl,
256
- previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
257
- };
258
- }
259
- }
229
+ const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-DnpU0Cms.js"));
260
230
  const lineOutboundAdapter = {
261
231
  deliveryMode: "direct",
262
232
  chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit),
263
233
  textChunkLimit: 5e3,
234
+ sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text),
264
235
  sendPayload: async ({ to, payload, accountId, cfg, onDeliveryResult }) => {
265
236
  const runtime = getLineRuntime();
266
237
  const outboundRuntime = await loadLineOutboundRuntime();
@@ -302,6 +273,12 @@ const lineOutboundAdapter = {
302
273
  const chunks = processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : [];
303
274
  const mediaUrls = resolveOutboundMediaUrls(payload);
304
275
  const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData);
276
+ const mediaOptions = {
277
+ mediaKind: useLineSpecificMedia ? lineData.mediaKind : "image",
278
+ previewImageUrl: lineData.previewImageUrl,
279
+ durationMs: lineData.durationMs,
280
+ trackingId: lineData.trackingId
281
+ };
305
282
  const shouldSendQuickRepliesInline = chunks.length === 0 && hasQuickReplies;
306
283
  const sendMediaMessages = async () => {
307
284
  for (const url of mediaUrls) {
@@ -316,12 +293,7 @@ const lineOutboundAdapter = {
316
293
  }));
317
294
  continue;
318
295
  }
319
- const resolved = await resolveLineOutboundMedia(trimmed, {
320
- mediaKind: lineData.mediaKind,
321
- previewImageUrl: lineData.previewImageUrl,
322
- durationMs: lineData.durationMs,
323
- trackingId: lineData.trackingId
324
- });
296
+ const resolved = await resolveLineOutboundMedia(trimmed, mediaOptions);
325
297
  await recordResult((lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
326
298
  verbose: false,
327
299
  mediaUrl: resolved.mediaUrl,
@@ -367,12 +339,12 @@ const lineOutboundAdapter = {
367
339
  }
368
340
  const sendMediaAfterText = !(hasQuickReplies && chunks.length > 0);
369
341
  if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && !sendMediaAfterText) await sendMediaMessages();
370
- if (chunks.length > 0) for (let i = 0; i < chunks.length; i += 1) if (i === chunks.length - 1 && hasQuickReplies) await recordResult(sendQuickReplies(to, chunks[i], quickReplies, {
342
+ if (chunks.length > 0) for (const [i, chunk] of chunks.entries()) if (i === chunks.length - 1 && hasQuickReplies) await recordResult(sendQuickReplies(to, chunk, quickReplies, {
371
343
  verbose: false,
372
344
  cfg,
373
345
  accountId: accountId ?? void 0
374
346
  }));
375
- else await recordResult(sendText(to, chunks[i], {
347
+ else await recordResult(sendText(to, chunk, {
376
348
  verbose: false,
377
349
  cfg,
378
350
  accountId: accountId ?? void 0
@@ -403,21 +375,7 @@ const lineOutboundAdapter = {
403
375
  for (const url of mediaUrls) {
404
376
  const trimmed = url?.trim();
405
377
  if (!trimmed) continue;
406
- if (!useLineSpecificMedia) {
407
- quickReplyMessages.push({
408
- type: "image",
409
- originalContentUrl: trimmed,
410
- previewImageUrl: trimmed
411
- });
412
- continue;
413
- }
414
- const resolved = await resolveLineOutboundMedia(trimmed, {
415
- mediaKind: lineData.mediaKind,
416
- previewImageUrl: lineData.previewImageUrl,
417
- durationMs: lineData.durationMs,
418
- trackingId: lineData.trackingId
419
- });
420
- quickReplyMessages.push(buildLineMediaMessageObject(resolved, { allowTrackingId: isLineUserTarget(to) }));
378
+ quickReplyMessages.push(await buildLineMediaMessage(trimmed, mediaOptions, to));
421
379
  }
422
380
  if (quickReplyMessages.length > 0 && quickReply) {
423
381
  const lastIndex = quickReplyMessages.length - 1;
@@ -562,7 +520,7 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
562
520
  });
563
521
  //#endregion
564
522
  //#region extensions/line/src/channel.ts
565
- const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-Bh_AQM0k.js"));
523
+ const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-DFgZ_Xwn.js"));
566
524
  const lineSecurityAdapter = createRestrictSendersChannelSecurity({
567
525
  channelKey: "line",
568
526
  resolveDmPolicy: (account) => account.config.dmPolicy,
@@ -583,6 +541,27 @@ const linePlugin = createChatChannelPlugin({
583
541
  ...lineChannelPluginCommon,
584
542
  setupWizard: lineSetupWizard,
585
543
  groups: { resolveRequireMention: resolveLineGroupRequireMention },
544
+ allowlist: buildDmGroupAccountAllowlistAdapter({
545
+ channelId: "line",
546
+ resolveAccount: ({ cfg, accountId }) => resolveLineAccount({
547
+ cfg,
548
+ accountId: accountId ?? void 0
549
+ }),
550
+ normalize: ({ cfg, accountId, values }) => lineConfigAdapter.formatAllowFrom({
551
+ cfg,
552
+ accountId,
553
+ allowFrom: values
554
+ }),
555
+ resolveDmAllowFrom: (account) => account.config.allowFrom,
556
+ resolveGroupAllowFrom: (account) => account.config.groupAllowFrom,
557
+ resolveDmPolicy: (account) => account.config.dmPolicy,
558
+ resolveGroupPolicy: (account) => account.config.groupPolicy,
559
+ resolveGroupOverrides: createFlatAllowlistOverrideResolver({
560
+ resolveRecord: (account) => account.config.groups,
561
+ label: (groupId) => groupId,
562
+ resolveEntries: (groupCfg) => groupCfg?.allowFrom
563
+ })
564
+ }),
586
565
  messaging: {
587
566
  targetPrefixes: ["line"],
588
567
  normalizeTarget: normalizeLineMessagingTarget,
@@ -1,2 +1,2 @@
1
- import { t as linePlugin } from "./channel-C_8hLniz.js";
1
+ import { t as linePlugin } from "./channel-BHR-GoJ0.js";
2
2
  export { linePlugin };
@@ -0,0 +1,4 @@
1
+ import { t as monitorLineProvider } from "./monitor-Db2rZsSX.js";
2
+ import { t as probeLineBot } from "./probe-BslD77tJ.js";
3
+ import { S as pushMessageLine } from "./markdown-to-line-XceqeKRP.js";
4
+ export { monitorLineProvider, probeLineBot, pushMessageLine };
@@ -1081,8 +1081,7 @@ function createDeviceControlCard(params) {
1081
1081
  const limitedControls = controls.slice(0, 6);
1082
1082
  for (let i = 0; i < limitedControls.length; i += 2) {
1083
1083
  const rowButtons = [];
1084
- for (let j = i; j < Math.min(i + 2, limitedControls.length); j++) {
1085
- const ctrl = limitedControls[j];
1084
+ for (const [offset, ctrl] of limitedControls.slice(i, i + 2).entries()) {
1086
1085
  const buttonLabel = ctrl.icon ? `${ctrl.icon} ${ctrl.label}` : ctrl.label;
1087
1086
  rowButtons.push({
1088
1087
  type: "button",
@@ -1094,7 +1093,7 @@ function createDeviceControlCard(params) {
1094
1093
  style: ctrl.style ?? "secondary",
1095
1094
  flex: 1,
1096
1095
  height: "sm",
1097
- margin: j > i ? "md" : void 0
1096
+ margin: offset > 0 ? "md" : void 0
1098
1097
  });
1099
1098
  }
1100
1099
  if (rowButtons.length === 1) rowButtons.push({ type: "filler" });
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
4
4
  function createLineCardCommandLoader(api) {
5
5
  return createLazyRuntimeModule(async () => {
6
6
  let registered = null;
7
- const { registerLineCardCommand } = await import("./card-command--jtkAqp8.js");
7
+ const { registerLineCardCommand } = await import("./card-command-DQ-epsgt.js");
8
8
  registerLineCardCommand({
9
9
  ...api,
10
10
  registerCommand(command) {
@@ -1,12 +1,13 @@
1
1
  import { i as resolveLineAccount } from "./accounts-DJVOv1JI.js";
2
- import { o as validateLineMediaUrl, r as createLineSendReceipt } from "./reply-payload-transform-C7r5npxP.js";
3
- import { c as uriAction, d as createReceiptCard, o as messageAction, s as postbackAction, t as toFlexMessage } from "./flex-templates-DEIXn69v.js";
2
+ import { c as validateLineMediaUrl, r as createLineSendReceipt } from "./reply-payload-transform-r9sa-TYT.js";
3
+ import { c as uriAction, d as createReceiptCard, o as messageAction, s as postbackAction, t as toFlexMessage } from "./flex-templates-K1lS9i8L.js";
4
+ import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
4
5
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
5
- import { messagingApi } from "@line/bot-sdk";
6
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
6
7
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
8
+ import { messagingApi } from "@line/bot-sdk";
7
9
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
8
10
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
9
- import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
10
11
  //#region extensions/line/src/template-messages.ts
11
12
  const COMPACT_TEMPLATE_TEXT_LIMIT = 60;
12
13
  const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
@@ -60,14 +61,15 @@ function createConfirmTemplate(text, confirmAction, cancelAction, altText) {
60
61
  * Create a button template with title, text, and action buttons
61
62
  */
62
63
  function createButtonTemplate(title, text, actions, options) {
64
+ const normalizedTitle = title || void 0;
63
65
  const textLimit = resolveTemplateTextLimit({
64
- title,
66
+ title: normalizedTitle,
65
67
  thumbnailImageUrl: options?.thumbnailImageUrl,
66
68
  textOnlyLimit: 160
67
69
  });
68
70
  const template = {
69
71
  type: "buttons",
70
- title: truncateTemplateText(title, 40),
72
+ ...normalizedTitle ? { title: truncateTemplateText(normalizedTitle, 40) } : {},
71
73
  text: truncateTemplateText(text, textLimit),
72
74
  actions: actions.slice(0, 4),
73
75
  thumbnailImageUrl: options?.thumbnailImageUrl,
@@ -78,7 +80,7 @@ function createButtonTemplate(title, text, actions, options) {
78
80
  };
79
81
  return {
80
82
  type: "template",
81
- altText: truncateOptionalTemplateText(options?.altText, 400) ?? truncateTemplateText(`${title}: ${text}`, 400),
83
+ altText: truncateOptionalTemplateText(options?.altText, 400) ?? truncateTemplateText(normalizedTitle ? `${normalizedTitle}: ${text}` : text, 400),
82
84
  template
83
85
  };
84
86
  }
@@ -224,7 +226,7 @@ function normalizeTarget(to) {
224
226
  if (!trimmed) throw new Error("Recipient is required for LINE sends");
225
227
  const normalized = trimmed.replace(/^line:group:/i, "").replace(/^line:room:/i, "").replace(/^line:user:/i, "").replace(/^line:/i, "");
226
228
  if (!normalized) throw new Error("Recipient is required for LINE sends");
227
- if (normalized.length >= 33 && !/^[CUR]/.test(normalized)) throw new Error(`Recipient is not a valid LINE id (case-sensitive; expected leading capital C/U/R): ${normalized.slice(0, 4)}…`);
229
+ if (normalized.length >= 33 && !/^[CUR]/.test(normalized)) throw new Error(`Recipient is not a valid LINE id (case-sensitive; expected leading capital C/U/R): ${truncateUtf16Safe(normalized, 4)}…`);
228
230
  return normalized;
229
231
  }
230
232
  function isLineUserChatId(chatId) {
@@ -498,9 +500,9 @@ function extractMarkdownTables(text) {
498
500
  let match;
499
501
  const matches = [];
500
502
  while ((match = MARKDOWN_TABLE_REGEX.exec(text)) !== null) {
501
- const fullMatch = match[0];
502
- const headerLine = match[1];
503
- const bodyLines = match[2];
503
+ const fullMatch = expectDefined(match[0], "Markdown table match");
504
+ const headerLine = expectDefined(match[1], "Markdown table header capture");
505
+ const bodyLines = expectDefined(match[2], "Markdown table body capture");
504
506
  const headers = parseTableRow(headerLine);
505
507
  const rows = bodyLines.trim().split(/[\r\n]+/).filter((line) => line.trim()).map(parseTableRow);
506
508
  if (headers.length > 0 && rows.length > 0) matches.push({
@@ -511,8 +513,7 @@ function extractMarkdownTables(text) {
511
513
  }
512
514
  });
513
515
  }
514
- for (let i = matches.length - 1; i >= 0; i--) {
515
- const { fullMatch, table } = matches[i];
516
+ for (const { fullMatch, table } of matches.toReversed()) {
516
517
  tables.unshift(table);
517
518
  textWithoutTables = textWithoutTables.replace(fullMatch, "");
518
519
  }
@@ -627,9 +628,9 @@ function extractCodeBlocks(text) {
627
628
  let match;
628
629
  const matches = [];
629
630
  while ((match = MARKDOWN_CODE_BLOCK_REGEX.exec(text)) !== null) {
630
- const fullMatch = match[0];
631
+ const fullMatch = expectDefined(match[0], "Markdown code block match");
631
632
  const language = match[1] || void 0;
632
- const code = match[2];
633
+ const code = expectDefined(match[2], "Markdown code body capture");
633
634
  matches.push({
634
635
  fullMatch,
635
636
  block: {
@@ -638,8 +639,7 @@ function extractCodeBlocks(text) {
638
639
  }
639
640
  });
640
641
  }
641
- for (let i = matches.length - 1; i >= 0; i--) {
642
- const { fullMatch, block } = matches[i];
642
+ for (const { fullMatch, block } of matches.toReversed()) {
643
643
  codeBlocks.unshift(block);
644
644
  textWithoutCode = textWithoutCode.replace(fullMatch, "");
645
645
  }
@@ -692,8 +692,8 @@ function extractLinks(text) {
692
692
  MARKDOWN_LINK_REGEX.lastIndex = 0;
693
693
  let match;
694
694
  while ((match = MARKDOWN_LINK_REGEX.exec(text)) !== null) links.push({
695
- text: match[1],
696
- url: match[2]
695
+ text: expectDefined(match[1], "Markdown link text capture"),
696
+ url: expectDefined(match[2], "Markdown link URL capture")
697
697
  });
698
698
  return {
699
699
  links,
@@ -756,7 +756,7 @@ function processLineMessage(text) {
756
756
  }
757
757
  const { textWithLinks } = extractLinks(processedText);
758
758
  processedText = textWithLinks;
759
- processedText = stripMarkdown(processedText);
759
+ processedText = stripMarkdown(processedText, { assistantTranscriptRoleHeaders: true });
760
760
  return {
761
761
  text: processedText,
762
762
  flexMessages