@openclaw/line 2026.9.6 → 2026.9.8

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 (77) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{accounts-BBEFMYbY.mjs → accounts-DOiM8-y3.mjs} +1 -4
  3. package/dist/.setup/api-4RgpQmHW.mjs +3 -0
  4. package/dist/.setup/{api-D_06cuHl.mjs → api-w4QdlCCB.mjs} +2 -2
  5. package/dist/.setup/{card-command-710u8FBH.mjs → card-command-DYf4gnmI.mjs} +25 -33
  6. package/dist/.setup/{channel-kc3VRglA.mjs → channel-DN6Za8Vm.mjs} +789 -26
  7. package/dist/.setup/channel.runtime-bd5vGeSo.mjs +4 -0
  8. package/dist/.setup/{monitor-morJPcPd.mjs → monitor-C01hAm3f.mjs} +262 -333
  9. package/dist/.setup/monitor.runtime-DwH2zK48.mjs +2 -0
  10. package/dist/.setup/outbound.runtime-D_Gs99Dm.mjs +3 -0
  11. package/dist/.setup/probe.runtime-Cj08-BZ1.mjs +2 -0
  12. package/dist/.setup/runtime-V7yh9OeC.mjs +8 -0
  13. package/dist/.setup/{send-BmsHY4WC.mjs → send-DOY1WTxv.mjs} +7 -26
  14. package/dist/.setup/{send-retry-DbfiHBPd.mjs → send-retry-DV6I0muq.mjs} +104 -208
  15. package/dist/.setup/{setup-surface-CcfKJKtr.mjs → setup-surface-CbfkmkIS.mjs} +15 -4
  16. package/dist/.setup/{markdown-to-line-CRfEHFs0.mjs → template-messages-CxCnYsWN.mjs} +168 -189
  17. package/dist/api.js +2 -2
  18. package/dist/channel-plugin-api.js +1 -1
  19. package/dist/contract-api.js +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/runtime-api.js +2 -260
  22. package/dist/setup-api.js +1 -1
  23. package/dist/setup-entry.js +1 -1
  24. package/node_modules/@line/bot-sdk/node_modules/@types/node/README.md +1 -1
  25. package/node_modules/@line/bot-sdk/node_modules/@types/node/child_process.d.ts +1 -1
  26. package/node_modules/@line/bot-sdk/node_modules/@types/node/package.json +2 -2
  27. package/node_modules/typebox/build/compile/code.mjs +1 -2
  28. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  29. package/node_modules/typebox/build/format/iri.mjs +16 -9
  30. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  31. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  32. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  33. package/node_modules/typebox/build/guard/index.mjs +6 -0
  34. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  35. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  36. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  37. package/node_modules/typebox/build/schema/build.mjs +2 -3
  38. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  39. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  40. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  41. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  42. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  43. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  44. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  45. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  46. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  47. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  48. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  49. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  50. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  51. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  52. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  53. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  54. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  55. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  56. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  57. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  58. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  59. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  60. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  61. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  62. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  63. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  64. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  65. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  66. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  67. package/node_modules/typebox/package.json +1 -1
  68. package/node_modules/typebox/readme.md +3 -1
  69. package/package.json +5 -5
  70. package/dist/.setup/account-helpers-BSUyZwLm.mjs +0 -12
  71. package/dist/.setup/api-RdEuQgW-.mjs +0 -3
  72. package/dist/.setup/channel.runtime-BtkeDELb.mjs +0 -4
  73. package/dist/.setup/monitor.runtime-BGhxYDKG.mjs +0 -2
  74. package/dist/.setup/outbound.runtime-QGvZy8Em.mjs +0 -3
  75. package/dist/.setup/probe.runtime-CMf8b-jB.mjs +0 -2
  76. package/dist/.setup/rich-messages-d8CmBVJj.mjs +0 -872
  77. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -1,8 +1,7 @@
1
- import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BBEFMYbY.mjs";
2
- import { t as hasLineCredentials } from "./account-helpers-BSUyZwLm.mjs";
3
- import { S as LineChannelConfigSchema, _ as resolveLineGroupLookupIds, a as renderLineCard, b as normalizeAllowFrom, f as buildLineQuickReplyFallbackText, g as resolveLineGroupConfigEntry, h as resolveExactLineGroupConfigKey, n as createLineQuickReply, o as renderLinePresentation, p as getLineRuntime, r as lineMessageActions, t as LINE_PRESENTATION_CAPABILITIES } from "./rich-messages-d8CmBVJj.mjs";
4
- import { F as inferLineTargetChatType, I as normalizeLineMessagingTarget, O as reportLineQuoteCarrierMissing, T as canCarryLineQuoteToken, j as buildLineMediaMessage, k as resolveLineQuoteToken, s as createLineSendReceipt, t as explainLineRefusal } from "./send-retry-DbfiHBPd.mjs";
5
- import { r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-CcfKJKtr.mjs";
1
+ import { n as resolveDefaultLineAccountId, r as resolveLineAccount, t as listLineAccountIds } from "./accounts-DOiM8-y3.mjs";
2
+ import { i as hasLineCredentials, r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-CbfkmkIS.mjs";
3
+ import { t as getLineRuntime } from "./runtime-V7yh9OeC.mjs";
4
+ import { M as inferLineTargetChatType, N as normalizeLineMessagingTarget, O as reportLineQuoteCarrierMissing, S as fitsLineFlexBubble, T as canCarryLineQuoteToken, b as truncateLineActionLabel, c as createAgendaCard, d as createActionCard, g as messageAction, h as createCardBubble, j as buildLineMediaMessage, k as resolveLineQuoteToken, l as createEventCard, s as createLineSendReceipt, t as explainLineRefusal, y as postbackAction } from "./send-retry-DV6I0muq.mjs";
6
5
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
7
6
  import { buildDmGroupAccountAllowlistAdapter, createFlatAllowlistOverrideResolver } from "openclaw/plugin-sdk/allowlist-config-edit";
8
7
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
@@ -12,7 +11,10 @@ import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister } f
12
11
  import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
13
12
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
14
13
  import { clearAccountFieldsFromConfigSection, createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
15
- import { isRecord, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
14
+ import { isRecord, normalizeLowercaseStringOrEmpty, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
15
+ import { ChannelDeliveryStreamingConfigSchema, DmPolicySchema, GroupPolicySchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
16
+ import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
17
+ import { z } from "zod";
16
18
  import { parseAccessGroupAllowFromEntry } from "openclaw/plugin-sdk/access-groups";
17
19
  import { firstDefined } from "openclaw/plugin-sdk/allow-from";
18
20
  import { createAccountStatusSink, defineChannelMessageAdapter, listMessageReceiptPlatformIds } from "openclaw/plugin-sdk/channel-outbound";
@@ -20,8 +22,10 @@ import { buildTokenChannelStatusSummary, collectIssuesForEnabledAccounts, create
20
22
  import { createChannelPartialDeliveryError, isChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
21
23
  import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
22
24
  import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
23
- import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
25
+ import { resolveAskUserQuestionOptionIndex, resolveAskUserQuestionOptionIndices, resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
24
26
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
27
+ import { normalizeMessagePresentation, renderMessagePresentationFallbackText, renderPresentationForDelivery, resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction } from "openclaw/plugin-sdk/interactive-runtime";
28
+ import { Type } from "typebox";
25
29
  //#region extensions/line/src/bindings.ts
26
30
  function normalizeLineConversationId(raw) {
27
31
  const trimmed = raw?.trim() ?? "";
@@ -49,15 +53,8 @@ const lineBindingsAdapter = {
49
53
  matchPriority: 2
50
54
  };
51
55
  },
52
- resolveCommandConversation: ({ originatingTo, commandTo, fallbackTo }) => resolveLineCommandConversation({
53
- originatingTo,
54
- commandTo,
55
- fallbackTo
56
- }),
57
- resolveInboundConversation: ({ to, conversationId }) => resolveLineInboundConversation({
58
- to,
59
- conversationId
60
- })
56
+ resolveCommandConversation: resolveLineCommandConversation,
57
+ resolveInboundConversation: resolveLineInboundConversation
61
58
  };
62
59
  //#endregion
63
60
  //#region extensions/line/src/config-adapter.ts
@@ -82,6 +79,58 @@ const lineConfigAdapter = createScopedChannelConfigAdapter({
82
79
  resolveAllowFrom: (account) => account.config.allowFrom,
83
80
  formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
84
81
  });
82
+ //#endregion
83
+ //#region extensions/line/src/config-schema.ts
84
+ const ThreadBindingsSchema = z.object({
85
+ enabled: z.boolean().optional(),
86
+ idleHours: z.number().optional(),
87
+ maxAgeHours: z.number().optional(),
88
+ spawnSessions: z.boolean().optional(),
89
+ defaultSpawnContext: z.enum(["isolated", "fork"]).optional()
90
+ }).strict();
91
+ const LineReplyToModeSchema = z.enum([
92
+ "off",
93
+ "first",
94
+ "all"
95
+ ]);
96
+ const LineCommonConfigSchemaBase = z.object({
97
+ enabled: z.boolean().optional(),
98
+ configWrites: z.boolean().optional(),
99
+ joinIntro: z.boolean().optional(),
100
+ historyLimit: z.number().int().min(0).optional(),
101
+ channelAccessToken: z.string().optional(),
102
+ channelSecret: z.string().optional(),
103
+ tokenFile: z.string().optional(),
104
+ secretFile: z.string().optional(),
105
+ name: z.string().optional(),
106
+ allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
107
+ groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
108
+ dmPolicy: DmPolicySchema.optional().default("pairing"),
109
+ groupPolicy: GroupPolicySchema.optional().default("allowlist"),
110
+ responsePrefix: z.string().optional(),
111
+ replyToMode: LineReplyToModeSchema.optional(),
112
+ streaming: ChannelDeliveryStreamingConfigSchema.optional(),
113
+ mediaMaxMb: z.number().optional(),
114
+ webhookPath: z.string().optional(),
115
+ threadBindings: ThreadBindingsSchema.optional()
116
+ });
117
+ const LineGroupConfigSchema = buildGroupEntrySchema().omit({
118
+ tools: true,
119
+ toolsBySender: true
120
+ });
121
+ const LineAccountConfigSchema = LineCommonConfigSchemaBase.extend({ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional() }).strict();
122
+ const LineConfigSchema = buildMultiAccountChannelSchema(LineAccountConfigSchema, {
123
+ optionalAccount: true,
124
+ refine: (value, ctx) => {
125
+ requireChannelOpenAllowFrom({
126
+ channel: "line",
127
+ policy: value.dmPolicy,
128
+ allowFrom: value.allowFrom,
129
+ ctx,
130
+ requireOpenAllowFrom
131
+ });
132
+ }
133
+ });
85
134
  const lineChannelPluginCommon = {
86
135
  meta: {
87
136
  id: "line",
@@ -103,7 +152,10 @@ const lineChannelPluginCommon = {
103
152
  blockStreaming: true
104
153
  },
105
154
  reload: { configPrefixes: ["channels.line"] },
106
- configSchema: LineChannelConfigSchema,
155
+ configSchema: buildChannelConfigSchema(LineConfigSchema, { uiHints: { joinIntro: {
156
+ label: "LINE Group Join Introduction",
157
+ help: "Post one brief introduction when the bot joins an allowed LINE group or multi-person room (default: true). Account settings override the channel-wide setting."
158
+ } } }),
107
159
  config: {
108
160
  ...lineConfigAdapter,
109
161
  isConfigured: (account) => hasLineCredentials(account),
@@ -120,10 +172,50 @@ const lineChannelPluginCommon = {
120
172
  }
121
173
  };
122
174
  //#endregion
175
+ //#region extensions/line/src/bot-access.ts
176
+ function normalizeLineAllowEntry(value) {
177
+ const trimmed = String(value).trim();
178
+ if (!trimmed) return "";
179
+ if (trimmed === "*") return "*";
180
+ return trimmed.replace(/^line:(?:user:)?/i, "");
181
+ }
182
+ //#endregion
183
+ //#region extensions/line/src/group-keys.ts
184
+ function resolveLineGroupLookupIds(groupId) {
185
+ const normalized = groupId?.trim();
186
+ if (!normalized) return [];
187
+ if (normalized.startsWith("group:") || normalized.startsWith("room:")) {
188
+ const rawId = normalized.split(":").slice(1).join(":");
189
+ return rawId ? [rawId, normalized] : [normalized];
190
+ }
191
+ return [
192
+ normalized,
193
+ `group:${normalized}`,
194
+ `room:${normalized}`
195
+ ];
196
+ }
197
+ function resolveLineGroupConfigEntry(groups, params) {
198
+ if (!groups) return;
199
+ const defaults = groups["*"];
200
+ for (const candidate of [...resolveLineGroupLookupIds(params.groupId), ...resolveLineGroupLookupIds(params.roomId)]) {
201
+ const hit = groups[candidate];
202
+ if (hit) return defaults && defaults !== hit ? {
203
+ ...defaults,
204
+ ...hit
205
+ } : hit;
206
+ }
207
+ return defaults;
208
+ }
209
+ function resolveExactLineGroupConfigKey(params) {
210
+ const { groups } = params;
211
+ if (!groups) return;
212
+ return resolveLineGroupLookupIds(params.groupId).find((candidate) => Object.hasOwn(groups, candidate));
213
+ }
214
+ //#endregion
123
215
  //#region extensions/line/src/doctor.ts
124
216
  const GROUP_DEFAULTS_KEY = "*";
125
217
  function classifyAllowFrom(values) {
126
- const entries = Array.isArray(values) ? normalizeAllowFrom(values).entries : [];
218
+ const entries = Array.isArray(values) ? values.map(normalizeLineAllowEntry).filter(Boolean) : [];
127
219
  return {
128
220
  covered: entries.some((entry) => parseAccessGroupAllowFromEntry(entry) === null),
129
221
  referenced: entries.some((entry) => parseAccessGroupAllowFromEntry(entry) !== null)
@@ -227,7 +319,7 @@ const lineDoctor = {
227
319
  };
228
320
  //#endregion
229
321
  //#region extensions/line/src/status.ts
230
- const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-CMf8b-jB.mjs"));
322
+ const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-Cj08-BZ1.mjs"));
231
323
  const collectLineCredentialIssues = createDependentCredentialStatusIssueCollector({
232
324
  channel: "line",
233
325
  dependencySourceKey: "tokenSource",
@@ -300,8 +392,8 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
300
392
  });
301
393
  //#endregion
302
394
  //#region extensions/line/src/gateway.ts
303
- const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-CMf8b-jB.mjs"));
304
- const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-BGhxYDKG.mjs"));
395
+ const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-Cj08-BZ1.mjs"));
396
+ const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-DwH2zK48.mjs"));
305
397
  const lineGatewayAdapter = {
306
398
  startAccount: async (ctx) => {
307
399
  const account = ctx.account;
@@ -380,8 +472,678 @@ function resolveLineGroupRequireMention(params) {
380
472
  });
381
473
  }
382
474
  //#endregion
475
+ //#region extensions/line/src/quick-reply-fallback.ts
476
+ function buildLineQuickReplyFallbackText(labels) {
477
+ const normalized = normalizeStringEntries(labels ?? []).slice(0, 13);
478
+ if (normalized.length === 0) return "Choose an option.";
479
+ return `Options:\n${normalized.map((label) => `- ${label}`).join("\n")}`;
480
+ }
481
+ //#endregion
482
+ //#region extensions/line/src/flex-templates/media-control-cards.ts
483
+ function horizontalRow(contents, options = {}) {
484
+ return {
485
+ type: "box",
486
+ layout: "horizontal",
487
+ contents,
488
+ ...options
489
+ };
490
+ }
491
+ function createMediaPlayerCard(params) {
492
+ const { title, subtitle, source, imageUrl, isPlaying, progress, controls, extraActions } = params;
493
+ const trackInfo = [{
494
+ type: "text",
495
+ text: title,
496
+ weight: "bold",
497
+ size: "xl",
498
+ color: "#111111",
499
+ wrap: true
500
+ }];
501
+ if (subtitle) trackInfo.push({
502
+ type: "text",
503
+ text: subtitle,
504
+ size: "md",
505
+ color: "#666666",
506
+ wrap: true,
507
+ margin: "sm"
508
+ });
509
+ const statusItems = [];
510
+ if (isPlaying !== void 0) statusItems.push(horizontalRow([{
511
+ type: "box",
512
+ layout: "vertical",
513
+ contents: [],
514
+ width: "8px",
515
+ height: "8px",
516
+ backgroundColor: isPlaying ? "#06C755" : "#CCCCCC",
517
+ cornerRadius: "4px"
518
+ }, {
519
+ type: "text",
520
+ text: isPlaying ? "Now Playing" : "Paused",
521
+ size: "xs",
522
+ color: isPlaying ? "#06C755" : "#888888",
523
+ weight: "bold",
524
+ margin: "sm"
525
+ }], { alignItems: "center" }));
526
+ if (source) statusItems.push({
527
+ type: "text",
528
+ text: source,
529
+ size: "xs",
530
+ color: "#AAAAAA",
531
+ margin: statusItems.length > 0 ? "lg" : void 0
532
+ });
533
+ if (progress) statusItems.push({
534
+ type: "text",
535
+ text: progress,
536
+ size: "xs",
537
+ color: "#888888",
538
+ align: "end",
539
+ flex: 1
540
+ });
541
+ const bodyContents = [{
542
+ type: "box",
543
+ layout: "vertical",
544
+ contents: trackInfo
545
+ }];
546
+ if (statusItems.length > 0) bodyContents.push(horizontalRow(statusItems, {
547
+ margin: "lg",
548
+ alignItems: "center"
549
+ }));
550
+ const bubble = createCardBubble(bodyContents);
551
+ if (imageUrl) bubble.hero = {
552
+ type: "image",
553
+ url: imageUrl,
554
+ size: "full",
555
+ aspectRatio: "1:1",
556
+ aspectMode: "cover"
557
+ };
558
+ if (controls || extraActions?.length) {
559
+ const footerContents = [];
560
+ if (controls) {
561
+ const controlButtons = [];
562
+ for (const [key, label, style] of [
563
+ [
564
+ "previous",
565
+ "⏮",
566
+ "secondary"
567
+ ],
568
+ [
569
+ "play",
570
+ "▶",
571
+ isPlaying ? "secondary" : "primary"
572
+ ],
573
+ [
574
+ "pause",
575
+ "⏸",
576
+ isPlaying ? "primary" : "secondary"
577
+ ],
578
+ [
579
+ "next",
580
+ "⏭",
581
+ "secondary"
582
+ ]
583
+ ]) {
584
+ const control = controls[key];
585
+ if (!control) continue;
586
+ const button = {
587
+ type: "button",
588
+ action: postbackAction(label, control.data),
589
+ style,
590
+ flex: 1,
591
+ height: "sm"
592
+ };
593
+ if (key !== "previous") button.margin = controlButtons.length > 0 ? "md" : void 0;
594
+ controlButtons.push(button);
595
+ }
596
+ if (controlButtons.length > 0) footerContents.push(horizontalRow(controlButtons));
597
+ }
598
+ if (extraActions?.length) footerContents.push(horizontalRow(extraActions.slice(0, 2).map((action, index) => ({
599
+ type: "button",
600
+ action: postbackAction(truncateLineActionLabel(action.label, 15), action.data),
601
+ style: "secondary",
602
+ flex: 1,
603
+ height: "sm",
604
+ margin: index > 0 ? "md" : void 0
605
+ })), { margin: "md" }));
606
+ if (footerContents.length > 0) bubble.footer = {
607
+ type: "box",
608
+ layout: "vertical",
609
+ contents: footerContents,
610
+ paddingAll: "lg",
611
+ backgroundColor: "#FAFAFA"
612
+ };
613
+ }
614
+ return bubble;
615
+ }
616
+ function createAppleTvRemoteCard(params) {
617
+ const { deviceName, status, actionData } = params;
618
+ const headerContents = [{
619
+ type: "text",
620
+ text: deviceName,
621
+ weight: "bold",
622
+ size: "xl",
623
+ color: "#111111",
624
+ wrap: true
625
+ }];
626
+ if (status) headerContents.push({
627
+ type: "text",
628
+ text: status,
629
+ size: "sm",
630
+ color: "#666666",
631
+ wrap: true,
632
+ margin: "sm"
633
+ });
634
+ const makeButton = (label, data, style = "secondary") => ({
635
+ type: "button",
636
+ action: postbackAction(label, data),
637
+ style,
638
+ height: "sm",
639
+ flex: 1
640
+ });
641
+ const controlRows = [
642
+ horizontalRow([
643
+ { type: "filler" },
644
+ makeButton("↑", actionData.up),
645
+ { type: "filler" }
646
+ ]),
647
+ horizontalRow([
648
+ makeButton("←", actionData.left),
649
+ makeButton("OK", actionData.select, "primary"),
650
+ makeButton("→", actionData.right)
651
+ ], { margin: "md" }),
652
+ horizontalRow([
653
+ { type: "filler" },
654
+ makeButton("↓", actionData.down),
655
+ { type: "filler" }
656
+ ], { margin: "md" }),
657
+ horizontalRow([makeButton("Menu", actionData.menu), makeButton("Home", actionData.home)], { margin: "lg" }),
658
+ horizontalRow([makeButton("Play", actionData.play), makeButton("Pause", actionData.pause)], { margin: "md" }),
659
+ horizontalRow([
660
+ makeButton("Vol +", actionData.volumeUp),
661
+ makeButton("Mute", actionData.mute),
662
+ makeButton("Vol -", actionData.volumeDown)
663
+ ], { margin: "md" })
664
+ ];
665
+ return createCardBubble([
666
+ {
667
+ type: "box",
668
+ layout: "vertical",
669
+ contents: headerContents
670
+ },
671
+ {
672
+ type: "separator",
673
+ margin: "lg",
674
+ color: "#EEEEEE"
675
+ },
676
+ ...controlRows
677
+ ]);
678
+ }
679
+ function createDeviceControlCard(params) {
680
+ const { deviceName, deviceType, status, isOnline, imageUrl, controls } = params;
681
+ const headerContents = [horizontalRow([{
682
+ type: "box",
683
+ layout: "vertical",
684
+ contents: [],
685
+ width: "10px",
686
+ height: "10px",
687
+ backgroundColor: isOnline !== false ? "#06C755" : "#FF5555",
688
+ cornerRadius: "5px"
689
+ }, {
690
+ type: "text",
691
+ text: deviceName,
692
+ weight: "bold",
693
+ size: "xl",
694
+ color: "#111111",
695
+ wrap: true,
696
+ flex: 1,
697
+ margin: "md"
698
+ }], { alignItems: "center" })];
699
+ if (deviceType) headerContents.push({
700
+ type: "text",
701
+ text: deviceType,
702
+ size: "sm",
703
+ color: "#888888",
704
+ margin: "sm"
705
+ });
706
+ if (status) headerContents.push({
707
+ type: "box",
708
+ layout: "vertical",
709
+ contents: [{
710
+ type: "text",
711
+ text: status,
712
+ size: "sm",
713
+ color: "#444444",
714
+ wrap: true
715
+ }],
716
+ margin: "lg",
717
+ paddingAll: "md",
718
+ backgroundColor: "#F8F9FA",
719
+ cornerRadius: "md"
720
+ });
721
+ const bubble = createCardBubble(headerContents);
722
+ if (imageUrl) bubble.hero = {
723
+ type: "image",
724
+ url: imageUrl,
725
+ size: "full",
726
+ aspectRatio: "16:9",
727
+ aspectMode: "cover"
728
+ };
729
+ if (controls.length > 0) {
730
+ const rows = [];
731
+ const limitedControls = controls.slice(0, 6);
732
+ for (let i = 0; i < limitedControls.length; i += 2) {
733
+ const rowButtons = [];
734
+ for (const [offset, ctrl] of limitedControls.slice(i, i + 2).entries()) {
735
+ const buttonLabel = ctrl.icon ? `${ctrl.icon} ${ctrl.label}` : ctrl.label;
736
+ rowButtons.push({
737
+ type: "button",
738
+ action: postbackAction(truncateLineActionLabel(buttonLabel, 18), ctrl.data),
739
+ style: ctrl.style ?? "secondary",
740
+ flex: 1,
741
+ height: "sm",
742
+ margin: offset > 0 ? "md" : void 0
743
+ });
744
+ }
745
+ if (rowButtons.length === 1) rowButtons.push({ type: "filler" });
746
+ rows.push(horizontalRow(rowButtons, { margin: i > 0 ? "md" : void 0 }));
747
+ }
748
+ bubble.footer = {
749
+ type: "box",
750
+ layout: "vertical",
751
+ contents: rows,
752
+ paddingAll: "lg",
753
+ backgroundColor: "#FAFAFA"
754
+ };
755
+ }
756
+ return bubble;
757
+ }
758
+ //#endregion
759
+ //#region extensions/line/src/question-postback.ts
760
+ const QUESTION_ID_PARAM = "line.question";
761
+ const OPTION_INDEX_PARAM = "line.option";
762
+ const POSTBACK_DATA_MAX_BYTES = 300;
763
+ function withinPostbackLimit(data) {
764
+ return Buffer.byteLength(data, "utf8") <= POSTBACK_DATA_MAX_BYTES ? data : void 0;
765
+ }
766
+ /** Encodes one ask_user choice into LINE postback data, or nothing when it cannot fit. */
767
+ function buildLineQuestionPostbackData(callback) {
768
+ const questionId = normalizeOptionalString(callback.questionId);
769
+ if (!questionId) return;
770
+ const params = new URLSearchParams({ [QUESTION_ID_PARAM]: questionId });
771
+ if (!Number.isInteger(callback.optionIndex) || callback.optionIndex < 0) return;
772
+ params.set(OPTION_INDEX_PARAM, String(callback.optionIndex));
773
+ return withinPostbackLimit(params.toString());
774
+ }
775
+ /** Reads a question choice back out of inbound postback data, if it carries one. */
776
+ function parseLineQuestionPostbackData(data) {
777
+ if (!data.includes(QUESTION_ID_PARAM)) return;
778
+ const params = new URLSearchParams(data);
779
+ const questionId = normalizeOptionalString(params.get(QUESTION_ID_PARAM));
780
+ if (!questionId) return;
781
+ const rawIndex = normalizeOptionalString(params.get(OPTION_INDEX_PARAM));
782
+ if (!rawIndex || !/^\d+$/.test(rawIndex)) return;
783
+ return {
784
+ questionId,
785
+ optionIndex: Number(rawIndex)
786
+ };
787
+ }
788
+ /**
789
+ * Submit an ask_user choice a LINE tap carried, and report only what the user needs.
790
+ *
791
+ * A successful answer needs no acknowledgement: the agent's own next reply is the
792
+ * feedback, and LINE already echoes the chosen label through the action's
793
+ * `displayText`. Only an answer that cannot land says so.
794
+ */
795
+ async function resolveLineQuestionPostback(params) {
796
+ try {
797
+ const { questionGatewayRuntime } = await import("openclaw/plugin-sdk/question-gateway-runtime");
798
+ const result = await questionGatewayRuntime.resolveOption({
799
+ cfg: params.cfg,
800
+ questionId: params.callback.questionId,
801
+ senderId: params.senderId,
802
+ clientDisplayName: `LINE question (${params.accountId})`,
803
+ optionIndex: params.callback.optionIndex,
804
+ authorize: params.authorize
805
+ });
806
+ if (result.status === "answered" || result.status === "already-terminal" || result.status === "denied") return { status: result.status };
807
+ return { status: "failed" };
808
+ } catch {
809
+ return { status: "failed" };
810
+ }
811
+ }
812
+ //#endregion
813
+ //#region extensions/line/src/rich-messages.ts
814
+ const nonempty = () => Type.String({ minLength: 1 });
815
+ const closed = (properties) => Type.Object(properties, { additionalProperties: false });
816
+ const lineCardSchema = Type.Union([
817
+ closed({
818
+ type: Type.Literal("media_player"),
819
+ title: nonempty(),
820
+ artist: Type.Optional(nonempty()),
821
+ source: Type.Optional(nonempty()),
822
+ imageUrl: Type.Optional(Type.String({ pattern: "^https://" })),
823
+ status: Type.Optional(Type.Union([Type.Literal("playing"), Type.Literal("paused")]))
824
+ }),
825
+ closed({
826
+ type: Type.Literal("event"),
827
+ title: nonempty(),
828
+ date: nonempty(),
829
+ time: Type.Optional(nonempty()),
830
+ location: Type.Optional(nonempty()),
831
+ description: Type.Optional(nonempty())
832
+ }),
833
+ closed({
834
+ type: Type.Literal("agenda"),
835
+ title: nonempty(),
836
+ events: Type.Array(closed({
837
+ title: nonempty(),
838
+ time: Type.Optional(nonempty()),
839
+ location: Type.Optional(nonempty())
840
+ }), {
841
+ minItems: 1,
842
+ maxItems: 6
843
+ })
844
+ }),
845
+ closed({
846
+ type: Type.Literal("device"),
847
+ name: nonempty(),
848
+ deviceType: Type.Optional(nonempty()),
849
+ status: Type.Optional(nonempty()),
850
+ controls: Type.Optional(Type.Array(closed({
851
+ label: nonempty(),
852
+ action: nonempty()
853
+ }), { maxItems: 6 }))
854
+ }),
855
+ closed({
856
+ type: Type.Literal("appletv_remote"),
857
+ name: Type.Optional(nonempty()),
858
+ status: Type.Optional(nonempty())
859
+ })
860
+ ]);
861
+ const lineChannelDataSchema = Type.Optional(closed({ line: closed({
862
+ location: Type.Optional(closed({
863
+ title: nonempty(),
864
+ address: nonempty(),
865
+ latitude: Type.Number({
866
+ minimum: -90,
867
+ maximum: 90
868
+ }),
869
+ longitude: Type.Number({
870
+ minimum: -180,
871
+ maximum: 180
872
+ })
873
+ })),
874
+ card: Type.Optional(lineCardSchema),
875
+ mediaKind: Type.Optional(Type.Union([
876
+ Type.Literal("image"),
877
+ Type.Literal("video"),
878
+ Type.Literal("audio")
879
+ ])),
880
+ previewImageUrl: Type.Optional(Type.String({ pattern: "^https://" })),
881
+ durationMs: Type.Optional(Type.Integer({ minimum: 1 })),
882
+ trackingId: Type.Optional(nonempty())
883
+ }) }));
884
+ const lineMessageActions = {
885
+ describeMessageTool: ({ cfg, accountId }) => {
886
+ const account = resolveLineAccount({
887
+ cfg,
888
+ accountId: accountId ?? void 0
889
+ });
890
+ return account.enabled && hasLineCredentials(account) ? {
891
+ actions: ["send"],
892
+ capabilities: ["presentation"],
893
+ schema: {
894
+ actions: ["send"],
895
+ properties: { channelData: lineChannelDataSchema }
896
+ }
897
+ } : {
898
+ actions: [],
899
+ capabilities: [],
900
+ schema: null
901
+ };
902
+ },
903
+ prepareSendPayload: ({ payload }) => payload
904
+ };
905
+ const LINE_QUICK_REPLY_LIMIT = 13;
906
+ const LINE_PRESENTATION_CAPABILITIES = {
907
+ supported: true,
908
+ buttons: true,
909
+ selects: true,
910
+ context: true,
911
+ limits: {
912
+ actions: {
913
+ maxActions: 4,
914
+ maxActionsPerRow: 1,
915
+ maxRows: 4,
916
+ maxLabelLength: 40
917
+ },
918
+ selects: {
919
+ maxOptions: LINE_QUICK_REPLY_LIMIT,
920
+ maxValueBytes: 300
921
+ },
922
+ text: { markdownDialect: "plain" }
923
+ }
924
+ };
925
+ function toLineAction(label, normalized, questionOptionIndices) {
926
+ if (normalized?.type === "question") {
927
+ if ("intent" in normalized) return;
928
+ const optionIndex = resolveAskUserQuestionOptionIndex({
929
+ questionOptionIndices,
930
+ questionId: normalized.questionId,
931
+ optionValue: normalized.optionValue
932
+ });
933
+ const data = optionIndex === void 0 ? void 0 : buildLineQuestionPostbackData({
934
+ questionId: normalized.questionId,
935
+ optionIndex
936
+ });
937
+ if (!data) return;
938
+ return {
939
+ type: "postback",
940
+ label,
941
+ data,
942
+ displayText: label
943
+ };
944
+ }
945
+ if (normalized?.type === "command") return {
946
+ type: "message",
947
+ label,
948
+ text: normalized.command
949
+ };
950
+ if (normalized?.type === "callback") return {
951
+ type: "postback",
952
+ label,
953
+ data: normalized.value,
954
+ displayText: label
955
+ };
956
+ if (normalized?.type === "url") return {
957
+ type: "uri",
958
+ label,
959
+ uri: normalized.url
960
+ };
961
+ if (normalized?.type === "web-app" && normalized.url) return {
962
+ type: "uri",
963
+ label,
964
+ uri: normalized.url
965
+ };
966
+ }
967
+ function renderLinePresentation(payload, presentation, to, sourcePresentation = presentation) {
968
+ const hasQuestion = sourcePresentation.blocks.some((block) => block.type === "buttons" && block.buttons.some((button) => resolveMessagePresentationButtonAction(button)?.type === "question"));
969
+ const hasAuthoredPrompt = Boolean(sourcePresentation.title?.trim()) || sourcePresentation.blocks.some((block) => (block.type === "text" || block.type === "context") && block.text.trim());
970
+ if (hasQuestion && !hasAuthoredPrompt) return null;
971
+ if (inferLineTargetChatType(to ?? "") !== "direct" && hasQuestion) return null;
972
+ const hasCard = presentation.blocks.some((block) => block.type === "buttons" && block.buttons.length > 0);
973
+ const buttons = [];
974
+ const quickReplyItems = [];
975
+ const carriedBlocks = [];
976
+ const cardBody = [];
977
+ const omittedControlLabels = [];
978
+ const questionLabels = /* @__PURE__ */ new Set();
979
+ const questionOptionIndices = resolveAskUserQuestionOptionIndices(payload);
980
+ for (const block of presentation.blocks) if (block.type === "buttons") for (const button of block.buttons) {
981
+ const normalized = resolveMessagePresentationButtonAction(button);
982
+ if (normalized?.type === "question" && "intent" in normalized && normalized.intent === "custom-input") {
983
+ omittedControlLabels.push(button.label);
984
+ continue;
985
+ }
986
+ const action = toLineAction(button.label, normalized, questionOptionIndices);
987
+ if (!action) return null;
988
+ if (normalized?.type === "question") {
989
+ if (questionLabels.has(button.label)) return null;
990
+ questionLabels.add(button.label);
991
+ }
992
+ buttons.push({
993
+ label: button.label,
994
+ action
995
+ });
996
+ }
997
+ else if (block.type === "select") {
998
+ const overflow = [];
999
+ for (const option of block.options) {
1000
+ const action = resolveMessagePresentationOptionAction(option);
1001
+ if (!action) return null;
1002
+ if (quickReplyItems.length < LINE_QUICK_REPLY_LIMIT) quickReplyItems.push({
1003
+ label: option.label,
1004
+ action
1005
+ });
1006
+ else overflow.push(option);
1007
+ }
1008
+ if (overflow.length > 0) carriedBlocks.push({
1009
+ ...block,
1010
+ options: overflow
1011
+ });
1012
+ else if (block.placeholder) carriedBlocks.push({
1013
+ type: "context",
1014
+ text: block.placeholder
1015
+ });
1016
+ } else if (!hasCard) carriedBlocks.push(block);
1017
+ else if (block.type === "text" || block.type === "context") cardBody.push(block.text);
1018
+ if (buttons.length === 0 && quickReplyItems.length === 0) return null;
1019
+ if (hasCard && omittedControlLabels.length > 0) cardBody.push(`Actions:\n${omittedControlLabels.map((label) => `- ${label}`).join("\n")}`);
1020
+ const lineData = isRecord(payload.channelData?.line) ? payload.channelData.line : {};
1021
+ const title = presentation.title || "Choose an option";
1022
+ const altText = presentation.title || cardBody[0] || title;
1023
+ const flexMessage = hasCard ? {
1024
+ altText,
1025
+ contents: createActionCard(title, cardBody.join("\n") || "Choose an option.", buttons)
1026
+ } : void 0;
1027
+ if (flexMessage && !fitsLineFlexBubble(flexMessage.contents)) return null;
1028
+ const text = renderMessagePresentationFallbackText({
1029
+ text: payload.text,
1030
+ presentation: {
1031
+ title: hasCard ? void 0 : presentation.title,
1032
+ blocks: carriedBlocks
1033
+ }
1034
+ });
1035
+ return {
1036
+ ...payload,
1037
+ ...text ? { text } : {},
1038
+ channelData: {
1039
+ ...payload.channelData,
1040
+ line: {
1041
+ ...lineData,
1042
+ ...flexMessage ? { flexMessage } : {},
1043
+ quickReplyItems
1044
+ }
1045
+ }
1046
+ };
1047
+ }
1048
+ /**
1049
+ * Resolve a reply's portable presentation into LINE-native controls.
1050
+ *
1051
+ * Core runs the presentation renderer inside the outbound send pipeline only, so
1052
+ * replies the plugin delivers itself reach delivery with the controls still
1053
+ * portable. Preparing them here keeps both LINE delivery paths on one rendering.
1054
+ */
1055
+ async function prepareLineReplyPayload(payload, to) {
1056
+ if (!normalizeMessagePresentation(payload.presentation)) return payload;
1057
+ const usesFallbackText = payload.presentationTextMode === "fallback" && Boolean(payload.text?.trim());
1058
+ return renderPresentationForDelivery({
1059
+ presentationCapabilities: LINE_PRESENTATION_CAPABILITIES,
1060
+ renderPresentation: (adapted, sourcePresentation) => {
1061
+ const rendered = renderLinePresentation(adapted, adapted.presentation, to, sourcePresentation);
1062
+ return rendered && usesFallbackText && rendered.channelData.line.flexMessage === void 0 ? {
1063
+ ...rendered,
1064
+ text: payload.text
1065
+ } : rendered;
1066
+ }
1067
+ }, {
1068
+ ...payload,
1069
+ presentationTextMode: usesFallbackText ? "fallback" : void 0
1070
+ });
1071
+ }
1072
+ const toSlug = (value) => normalizeLowercaseStringOrEmpty(value).replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "device";
1073
+ const lineActionData = (action, device) => `line.action=${encodeURIComponent(action)}&line.device=${encodeURIComponent(device)}`;
1074
+ function renderLineCard(card) {
1075
+ if (card.type === "media_player") {
1076
+ const device = toSlug(card.source || card.title);
1077
+ return {
1078
+ altText: `🎵 ${card.title}${card.artist ? ` - ${card.artist}` : ""}`,
1079
+ contents: createMediaPlayerCard({
1080
+ title: card.title,
1081
+ subtitle: card.artist,
1082
+ source: card.source,
1083
+ imageUrl: card.imageUrl,
1084
+ isPlaying: card.status ? card.status === "playing" : void 0,
1085
+ controls: Object.fromEntries([
1086
+ "previous",
1087
+ "play",
1088
+ "pause",
1089
+ "next"
1090
+ ].map((action) => [action, { data: lineActionData(action, device) }]))
1091
+ })
1092
+ };
1093
+ }
1094
+ if (card.type === "event") return {
1095
+ altText: `📅 ${card.title} - ${card.date}${card.time ? ` ${card.time}` : ""}`,
1096
+ contents: createEventCard(card)
1097
+ };
1098
+ if (card.type === "agenda") return {
1099
+ altText: `📋 ${card.title} (${card.events.length} events)`,
1100
+ contents: createAgendaCard(card)
1101
+ };
1102
+ const device = toSlug(card.type === "device" ? card.name : card.name || "apple_tv");
1103
+ if (card.type === "device") return {
1104
+ altText: `📱 ${card.name}${card.status ? `: ${card.status}` : ""}`,
1105
+ contents: createDeviceControlCard({
1106
+ deviceName: card.name,
1107
+ deviceType: card.deviceType,
1108
+ status: card.status,
1109
+ controls: (card.controls ?? []).map((control) => ({
1110
+ label: control.label,
1111
+ data: lineActionData(control.action, device)
1112
+ }))
1113
+ })
1114
+ };
1115
+ const actionData = {
1116
+ up: lineActionData("up", device),
1117
+ down: lineActionData("down", device),
1118
+ left: lineActionData("left", device),
1119
+ right: lineActionData("right", device),
1120
+ select: lineActionData("select", device),
1121
+ menu: lineActionData("menu", device),
1122
+ home: lineActionData("home", device),
1123
+ play: lineActionData("play", device),
1124
+ pause: lineActionData("pause", device),
1125
+ volumeUp: lineActionData("volume_up", device),
1126
+ volumeDown: lineActionData("volume_down", device),
1127
+ mute: lineActionData("mute", device)
1128
+ };
1129
+ return {
1130
+ altText: `📺 ${card.name || "Apple TV"} Remote`,
1131
+ contents: createAppleTvRemoteCard({
1132
+ deviceName: card.name || "Apple TV",
1133
+ status: card.status,
1134
+ actionData
1135
+ })
1136
+ };
1137
+ }
1138
+ function createLineQuickReply(items) {
1139
+ return { items: items.slice(0, LINE_QUICK_REPLY_LIMIT).map((item) => ({
1140
+ type: "action",
1141
+ action: item.action.type === "command" ? messageAction(item.label, item.action.command) : postbackAction(item.label, item.action.value, item.label)
1142
+ })) };
1143
+ }
1144
+ //#endregion
383
1145
  //#region extensions/line/src/outbound.ts
384
- const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-QGvZy8Em.mjs"));
1146
+ const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-D_Gs99Dm.mjs"));
385
1147
  function quotedOption(quoteToken) {
386
1148
  return quoteToken ? { quoteToken } : {};
387
1149
  }
@@ -574,10 +1336,11 @@ const lineOutboundAdapter = {
574
1336
  if (!trimmed) continue;
575
1337
  quickReplyMessages.push(await buildLineMediaMessage(trimmed, mediaOptions, to));
576
1338
  }
577
- if (quickReplyMessages.length > 0 && quickReply) {
1339
+ const lastMessage = quickReplyMessages.at(-1);
1340
+ if (lastMessage && quickReply) {
578
1341
  const lastIndex = quickReplyMessages.length - 1;
579
1342
  quickReplyMessages[lastIndex] = {
580
- ...quickReplyMessages[lastIndex],
1343
+ ...lastMessage,
581
1344
  quickReply
582
1345
  };
583
1346
  await sendMessageBatch(quickReplyMessages);
@@ -663,7 +1426,7 @@ const lineMessageAdapter = defineChannelMessageAdapter({
663
1426
  });
664
1427
  //#endregion
665
1428
  //#region extensions/line/src/channel.ts
666
- const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-BtkeDELb.mjs"));
1429
+ const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-bd5vGeSo.mjs"));
667
1430
  const lineSecurityAdapter = createRestrictSendersChannelSecurity({
668
1431
  channelKey: "line",
669
1432
  resolveDmPolicy: (account) => account.config.dmPolicy,
@@ -825,4 +1588,4 @@ const linePlugin = createChatChannelPlugin({
825
1588
  outbound: lineOutboundAdapter
826
1589
  });
827
1590
  //#endregion
828
- export { lineChannelPluginCommon as n, linePlugin as t };
1591
+ export { resolveLineQuestionPostback as a, normalizeLineAllowEntry as c, parseLineQuestionPostbackData as i, lineChannelPluginCommon as l, createLineQuickReply as n, buildLineQuickReplyFallbackText as o, prepareLineReplyPayload as r, resolveLineGroupConfigEntry as s, linePlugin as t };