@kodelyth/line 2026.5.39 → 2026.5.42

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 (120) hide show
  1. package/api.ts +11 -0
  2. package/channel-plugin-api.ts +1 -0
  3. package/contract-api.ts +5 -0
  4. package/dist/accounts-CD4A1FE7.js +105 -0
  5. package/dist/api.js +11 -0
  6. package/dist/basic-cards-BISytiSa.js +307 -0
  7. package/dist/card-command-dQBX3fVN.js +240 -0
  8. package/dist/channel-DV5h44-j.js +649 -0
  9. package/dist/channel-plugin-api.js +2 -0
  10. package/dist/channel.runtime-Cc-v3szZ.js +4 -0
  11. package/dist/contract-api.js +2 -0
  12. package/dist/index.js +45 -0
  13. package/dist/markdown-to-line-CC3BU6CC.js +810 -0
  14. package/dist/monitor-Ci8Hg8ay.js +1485 -0
  15. package/dist/monitor.runtime-t6-QvlDB.js +2 -0
  16. package/dist/outbound.runtime-D1CxEvcL.js +2 -0
  17. package/dist/probe-BPSs_A_8.js +30 -0
  18. package/dist/probe.runtime-7u2o9QN5.js +2 -0
  19. package/dist/reply-payload-transform-CDuBzoT4.js +855 -0
  20. package/dist/runtime-api.js +291 -0
  21. package/dist/schedule-cards-D-yZMHDE.js +359 -0
  22. package/dist/secret-contract-api.js +5 -0
  23. package/dist/setup-api.js +2 -0
  24. package/dist/setup-entry.js +11 -0
  25. package/dist/setup-surface-CHfQ6Z4i.js +282 -0
  26. package/index.ts +53 -0
  27. package/klaw.plugin.json +2 -329
  28. package/package.json +4 -4
  29. package/runtime-api.ts +179 -0
  30. package/secret-contract-api.ts +4 -0
  31. package/setup-api.ts +2 -0
  32. package/setup-entry.ts +9 -0
  33. package/src/account-helpers.ts +16 -0
  34. package/src/accounts.test.ts +288 -0
  35. package/src/accounts.ts +187 -0
  36. package/src/actions.ts +61 -0
  37. package/src/auto-reply-delivery.test.ts +253 -0
  38. package/src/auto-reply-delivery.ts +200 -0
  39. package/src/bindings.ts +65 -0
  40. package/src/bot-access.ts +30 -0
  41. package/src/bot-handlers.test.ts +1094 -0
  42. package/src/bot-handlers.ts +620 -0
  43. package/src/bot-message-context.test.ts +420 -0
  44. package/src/bot-message-context.ts +586 -0
  45. package/src/bot.ts +66 -0
  46. package/src/card-command.ts +347 -0
  47. package/src/channel-access-token.ts +14 -0
  48. package/src/channel-api.ts +17 -0
  49. package/src/channel-setup-status.contract.test.ts +70 -0
  50. package/src/channel-shared.ts +48 -0
  51. package/src/channel.logout.test.ts +145 -0
  52. package/src/channel.runtime.ts +3 -0
  53. package/src/channel.sendPayload.test.ts +659 -0
  54. package/src/channel.setup.ts +11 -0
  55. package/src/channel.status.test.ts +63 -0
  56. package/src/channel.ts +155 -0
  57. package/src/config-adapter.ts +29 -0
  58. package/src/config-schema.test.ts +53 -0
  59. package/src/config-schema.ts +81 -0
  60. package/src/download.test.ts +164 -0
  61. package/src/download.ts +34 -0
  62. package/src/flex-templates/basic-cards.ts +395 -0
  63. package/src/flex-templates/common.ts +20 -0
  64. package/src/flex-templates/media-control-cards.ts +555 -0
  65. package/src/flex-templates/message.ts +13 -0
  66. package/src/flex-templates/schedule-cards.ts +467 -0
  67. package/src/flex-templates/types.ts +22 -0
  68. package/src/flex-templates.ts +32 -0
  69. package/src/gateway.ts +129 -0
  70. package/src/group-keys.test.ts +123 -0
  71. package/src/group-keys.ts +65 -0
  72. package/src/group-policy.ts +22 -0
  73. package/src/markdown-to-line.test.ts +348 -0
  74. package/src/markdown-to-line.ts +416 -0
  75. package/src/message-cards.test.ts +204 -0
  76. package/src/monitor-durable.test.ts +57 -0
  77. package/src/monitor-durable.ts +37 -0
  78. package/src/monitor.lifecycle.test.ts +499 -0
  79. package/src/monitor.runtime.ts +1 -0
  80. package/src/monitor.ts +507 -0
  81. package/src/outbound-media.test.ts +194 -0
  82. package/src/outbound-media.ts +120 -0
  83. package/src/outbound.runtime.ts +12 -0
  84. package/src/outbound.ts +427 -0
  85. package/src/probe.contract.test.ts +9 -0
  86. package/src/probe.runtime.ts +1 -0
  87. package/src/probe.ts +34 -0
  88. package/src/quick-reply-fallback.ts +10 -0
  89. package/src/reply-chunks.test.ts +180 -0
  90. package/src/reply-chunks.ts +110 -0
  91. package/src/reply-payload-transform.test.ts +392 -0
  92. package/src/reply-payload-transform.ts +317 -0
  93. package/src/rich-menu.test.ts +315 -0
  94. package/src/rich-menu.ts +326 -0
  95. package/src/runtime.ts +32 -0
  96. package/src/send-receipt.ts +32 -0
  97. package/src/send.test.ts +453 -0
  98. package/src/send.ts +531 -0
  99. package/src/setup-core.ts +149 -0
  100. package/src/setup-runtime-api.ts +9 -0
  101. package/src/setup-surface.test.ts +481 -0
  102. package/src/setup-surface.ts +229 -0
  103. package/src/signature.test.ts +34 -0
  104. package/src/signature.ts +24 -0
  105. package/src/status.ts +37 -0
  106. package/src/template-messages.ts +333 -0
  107. package/src/types.ts +130 -0
  108. package/src/webhook-node.test.ts +598 -0
  109. package/src/webhook-node.ts +155 -0
  110. package/src/webhook-utils.ts +10 -0
  111. package/src/webhook.ts +135 -0
  112. package/tsconfig.json +16 -0
  113. package/api.js +0 -7
  114. package/channel-plugin-api.js +0 -7
  115. package/contract-api.js +0 -7
  116. package/index.js +0 -7
  117. package/runtime-api.js +0 -7
  118. package/secret-contract-api.js +0 -7
  119. package/setup-api.js +0 -7
  120. package/setup-entry.js +0 -7
@@ -0,0 +1,649 @@
1
+ import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-CD4A1FE7.js";
2
+ import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-CHfQ6Z4i.js";
3
+ import { c as resolveLineOutboundMedia, f as LineChannelConfigSchema, m as resolveExactLineGroupConfigKey, n as parseLineDirectives, o as createLineSendReceipt, s as buildLineQuickReplyFallbackText, t as hasLineDirectives, u as getLineRuntime } from "./reply-payload-transform-CDuBzoT4.js";
4
+ import { createChatChannelPlugin } from "klaw/plugin-sdk/channel-core";
5
+ import { createPairingPrefixStripper } from "klaw/plugin-sdk/channel-pairing";
6
+ import { createRestrictSendersChannelSecurity, resolveChannelGroupRequireMention } from "klaw/plugin-sdk/channel-policy";
7
+ import { createEmptyChannelDirectoryAdapter } from "klaw/plugin-sdk/directory-runtime";
8
+ import { createLazyRuntimeModule } from "klaw/plugin-sdk/lazy-runtime";
9
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "klaw/plugin-sdk/account-id";
10
+ import { clearAccountEntryFields } from "klaw/plugin-sdk/core";
11
+ import { describeWebhookAccountSnapshot } from "klaw/plugin-sdk/account-helpers";
12
+ import { createScopedChannelConfigAdapter } from "klaw/plugin-sdk/channel-config-helpers";
13
+ import { defineChannelMessageAdapter } from "klaw/plugin-sdk/channel-message";
14
+ import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "klaw/plugin-sdk/channel-send-result";
15
+ import { resolveOutboundMediaUrls } from "klaw/plugin-sdk/reply-payload";
16
+ import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "klaw/plugin-sdk/status-helpers";
17
+ //#region extensions/line/src/bindings.ts
18
+ function normalizeLineConversationId(raw) {
19
+ const trimmed = raw?.trim() ?? "";
20
+ if (!trimmed) return null;
21
+ return (trimmed.match(/^line:(?:(?:user|group|room):)?(.+)$/i)?.[1] ?? trimmed).trim() || null;
22
+ }
23
+ function resolveLineCommandConversation(params) {
24
+ const conversationId = normalizeLineConversationId(params.originatingTo) ?? normalizeLineConversationId(params.commandTo) ?? normalizeLineConversationId(params.fallbackTo);
25
+ return conversationId ? { conversationId } : null;
26
+ }
27
+ function resolveLineInboundConversation(params) {
28
+ const conversationId = normalizeLineConversationId(params.conversationId) ?? normalizeLineConversationId(params.to);
29
+ return conversationId ? { conversationId } : null;
30
+ }
31
+ const lineBindingsAdapter = {
32
+ compileConfiguredBinding: ({ conversationId }) => {
33
+ const normalized = normalizeLineConversationId(conversationId);
34
+ return normalized ? { conversationId: normalized } : null;
35
+ },
36
+ matchInboundConversation: ({ compiledBinding, conversationId }) => {
37
+ const normalizedIncoming = normalizeLineConversationId(conversationId);
38
+ if (!normalizedIncoming || compiledBinding.conversationId !== normalizedIncoming) return null;
39
+ return {
40
+ conversationId: normalizedIncoming,
41
+ matchPriority: 2
42
+ };
43
+ },
44
+ resolveCommandConversation: ({ originatingTo, commandTo, fallbackTo }) => resolveLineCommandConversation({
45
+ originatingTo,
46
+ commandTo,
47
+ fallbackTo
48
+ }),
49
+ resolveInboundConversation: ({ to, conversationId }) => resolveLineInboundConversation({
50
+ to,
51
+ conversationId
52
+ })
53
+ };
54
+ //#endregion
55
+ //#region extensions/line/src/config-adapter.ts
56
+ function normalizeLineAllowFrom(entry) {
57
+ return entry.replace(/^line:(?:user:)?/i, "");
58
+ }
59
+ const lineChannelPluginCommon = {
60
+ meta: {
61
+ id: "line",
62
+ label: "LINE",
63
+ selectionLabel: "LINE (Messaging API)",
64
+ detailLabel: "LINE Bot",
65
+ docsPath: "/channels/line",
66
+ docsLabel: "line",
67
+ blurb: "LINE Messaging API bot for Japan/Taiwan/Thailand markets.",
68
+ systemImage: "message.fill",
69
+ quickstartAllowFrom: true
70
+ },
71
+ capabilities: {
72
+ chatTypes: ["direct", "group"],
73
+ reactions: false,
74
+ threads: false,
75
+ media: true,
76
+ nativeCommands: false,
77
+ blockStreaming: true
78
+ },
79
+ reload: { configPrefixes: ["channels.line"] },
80
+ configSchema: LineChannelConfigSchema,
81
+ config: {
82
+ ...createScopedChannelConfigAdapter({
83
+ sectionKey: "line",
84
+ listAccountIds: listLineAccountIds,
85
+ resolveAccount: (cfg, accountId) => resolveLineAccount({
86
+ cfg,
87
+ accountId: accountId ?? void 0
88
+ }),
89
+ defaultAccountId: resolveDefaultLineAccountId,
90
+ clearBaseFields: [
91
+ "channelSecret",
92
+ "tokenFile",
93
+ "secretFile"
94
+ ],
95
+ resolveAllowFrom: (account) => account.config.allowFrom,
96
+ formatAllowFrom: (allowFrom) => allowFrom.map((entry) => String(entry).trim()).filter(Boolean).map(normalizeLineAllowFrom)
97
+ }),
98
+ isConfigured: (account) => hasLineCredentials(account),
99
+ describeAccount: (account) => describeWebhookAccountSnapshot({
100
+ account,
101
+ configured: hasLineCredentials(account),
102
+ extra: { tokenSource: account.tokenSource ?? void 0 }
103
+ })
104
+ }
105
+ };
106
+ //#endregion
107
+ //#region extensions/line/src/gateway.ts
108
+ const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-7u2o9QN5.js"));
109
+ const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-t6-QvlDB.js"));
110
+ const lineGatewayAdapter = {
111
+ startAccount: async (ctx) => {
112
+ const account = ctx.account;
113
+ const token = account.channelAccessToken.trim();
114
+ const secret = account.channelSecret.trim();
115
+ if (!token) throw new Error(`LINE webhook mode requires a non-empty channel access token for account "${account.accountId}".`);
116
+ if (!secret) throw new Error(`LINE webhook mode requires a non-empty channel secret for account "${account.accountId}".`);
117
+ let lineBotLabel = "";
118
+ try {
119
+ const probe = await (await loadLineProbeRuntime$1()).probeLineBot(token, 2500);
120
+ const displayName = probe.ok ? probe.bot?.displayName?.trim() : null;
121
+ if (displayName) lineBotLabel = ` (${displayName})`;
122
+ } catch (err) {
123
+ if (getLineRuntime().logging.shouldLogVerbose()) ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`);
124
+ }
125
+ ctx.log?.info(`[${account.accountId}] starting LINE provider${lineBotLabel}`);
126
+ return await (getLineRuntime().channel.line?.monitorLineProvider ?? (await loadLineMonitorRuntime()).monitorLineProvider)({
127
+ channelAccessToken: token,
128
+ channelSecret: secret,
129
+ accountId: account.accountId,
130
+ config: ctx.cfg,
131
+ runtime: ctx.runtime,
132
+ abortSignal: ctx.abortSignal,
133
+ webhookPath: account.config.webhookPath
134
+ });
135
+ },
136
+ logoutAccount: async ({ accountId, cfg }) => {
137
+ const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim() ?? "";
138
+ const nextCfg = { ...cfg };
139
+ const nextLine = { ...cfg.channels?.line ?? {} };
140
+ let cleared = false;
141
+ let changed = false;
142
+ if (accountId === DEFAULT_ACCOUNT_ID$1) {
143
+ if (nextLine.channelAccessToken || nextLine.channelSecret || nextLine.tokenFile || nextLine.secretFile) {
144
+ delete nextLine.channelAccessToken;
145
+ delete nextLine.channelSecret;
146
+ delete nextLine.tokenFile;
147
+ delete nextLine.secretFile;
148
+ cleared = true;
149
+ changed = true;
150
+ }
151
+ }
152
+ const accountCleanup = clearAccountEntryFields({
153
+ accounts: nextLine.accounts,
154
+ accountId,
155
+ fields: [
156
+ "channelAccessToken",
157
+ "channelSecret",
158
+ "tokenFile",
159
+ "secretFile"
160
+ ],
161
+ markClearedOnFieldPresence: true
162
+ });
163
+ if (accountCleanup.changed) {
164
+ changed = true;
165
+ if (accountCleanup.cleared) cleared = true;
166
+ if (accountCleanup.nextAccounts) nextLine.accounts = accountCleanup.nextAccounts;
167
+ else delete nextLine.accounts;
168
+ }
169
+ if (changed) {
170
+ if (Object.keys(nextLine).length > 0) nextCfg.channels = {
171
+ ...nextCfg.channels,
172
+ line: nextLine
173
+ };
174
+ else {
175
+ const nextChannels = { ...nextCfg.channels };
176
+ delete nextChannels.line;
177
+ if (Object.keys(nextChannels).length > 0) nextCfg.channels = nextChannels;
178
+ else delete nextCfg.channels;
179
+ }
180
+ await getLineRuntime().config.replaceConfigFile({
181
+ nextConfig: nextCfg,
182
+ afterWrite: { mode: "auto" }
183
+ });
184
+ }
185
+ const loggedOut = resolveLineAccount({
186
+ cfg: changed ? nextCfg : cfg,
187
+ accountId
188
+ }).tokenSource === "none";
189
+ return {
190
+ cleared,
191
+ envToken: Boolean(envToken),
192
+ loggedOut
193
+ };
194
+ }
195
+ };
196
+ //#endregion
197
+ //#region extensions/line/src/group-policy.ts
198
+ function resolveLineGroupRequireMention(params) {
199
+ const exactGroupId = resolveExactLineGroupConfigKey({
200
+ cfg: params.cfg,
201
+ accountId: params.accountId,
202
+ groupId: params.groupId
203
+ });
204
+ return resolveChannelGroupRequireMention({
205
+ cfg: params.cfg,
206
+ channel: "line",
207
+ groupId: exactGroupId ?? params.groupId,
208
+ accountId: params.accountId
209
+ });
210
+ }
211
+ //#endregion
212
+ //#region extensions/line/src/outbound.ts
213
+ const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-D1CxEvcL.js"));
214
+ function isLineUserTarget(target) {
215
+ const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
216
+ return /^U/i.test(normalized);
217
+ }
218
+ function hasLineSpecificMediaOptions(lineData) {
219
+ return Boolean(lineData.mediaKind ?? lineData.previewImageUrl?.trim() ?? (typeof lineData.durationMs === "number" ? lineData.durationMs : void 0) ?? lineData.trackingId?.trim());
220
+ }
221
+ function buildLineMediaMessageObject(resolved, opts) {
222
+ switch (resolved.mediaKind) {
223
+ case "video": {
224
+ const previewImageUrl = resolved.previewImageUrl?.trim();
225
+ if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
226
+ return {
227
+ type: "video",
228
+ originalContentUrl: resolved.mediaUrl,
229
+ previewImageUrl,
230
+ ...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
231
+ };
232
+ }
233
+ case "audio": return {
234
+ type: "audio",
235
+ originalContentUrl: resolved.mediaUrl,
236
+ duration: resolved.durationMs ?? 6e4
237
+ };
238
+ default: return {
239
+ type: "image",
240
+ originalContentUrl: resolved.mediaUrl,
241
+ previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
242
+ };
243
+ }
244
+ }
245
+ const lineOutboundAdapter = {
246
+ deliveryMode: "direct",
247
+ chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit),
248
+ textChunkLimit: 5e3,
249
+ sendPayload: async ({ to, payload, accountId, cfg }) => {
250
+ const runtime = getLineRuntime();
251
+ const outboundRuntime = await loadLineOutboundRuntime();
252
+ const lineData = payload.channelData?.line ?? {};
253
+ const lineRuntime = runtime.channel.line;
254
+ const sendText = lineRuntime?.pushMessageLine ?? outboundRuntime.pushMessageLine;
255
+ const sendBatch = lineRuntime?.pushMessagesLine ?? outboundRuntime.pushMessagesLine;
256
+ const sendFlex = lineRuntime?.pushFlexMessage ?? outboundRuntime.pushFlexMessage;
257
+ const sendTemplate = lineRuntime?.pushTemplateMessage ?? outboundRuntime.pushTemplateMessage;
258
+ const sendLocation = lineRuntime?.pushLocationMessage ?? outboundRuntime.pushLocationMessage;
259
+ const sendQuickReplies = lineRuntime?.pushTextMessageWithQuickReplies ?? outboundRuntime.pushTextMessageWithQuickReplies;
260
+ const buildTemplate = lineRuntime?.buildTemplateMessageFromPayload ?? outboundRuntime.buildTemplateMessageFromPayload;
261
+ let lastResult = null;
262
+ const quickReplies = lineData.quickReplies ?? [];
263
+ const hasQuickReplies = quickReplies.length > 0;
264
+ const quickReply = hasQuickReplies ? (lineRuntime?.createQuickReplyItems ?? outboundRuntime.createQuickReplyItems)(quickReplies) : void 0;
265
+ const sendMessageBatch = async (messages) => {
266
+ if (messages.length === 0) return;
267
+ for (let i = 0; i < messages.length; i += 5) lastResult = await sendBatch(to, messages.slice(i, i + 5), {
268
+ verbose: false,
269
+ cfg,
270
+ accountId: accountId ?? void 0
271
+ });
272
+ };
273
+ const processed = payload.text ? outboundRuntime.processLineMessage(payload.text) : {
274
+ text: "",
275
+ flexMessages: []
276
+ };
277
+ const chunkLimit = runtime.channel.text.resolveTextChunkLimit?.(cfg, "line", accountId ?? void 0, { fallbackLimit: 5e3 }) ?? 5e3;
278
+ const chunks = processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : [];
279
+ const mediaUrls = resolveOutboundMediaUrls(payload);
280
+ const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData);
281
+ const shouldSendQuickRepliesInline = chunks.length === 0 && hasQuickReplies;
282
+ const sendMediaMessages = async () => {
283
+ for (const url of mediaUrls) {
284
+ const trimmed = url?.trim();
285
+ if (!trimmed) continue;
286
+ if (!useLineSpecificMedia) {
287
+ lastResult = await (lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
288
+ verbose: false,
289
+ mediaUrl: trimmed,
290
+ cfg,
291
+ accountId: accountId ?? void 0
292
+ });
293
+ continue;
294
+ }
295
+ const resolved = await resolveLineOutboundMedia(trimmed, {
296
+ mediaKind: lineData.mediaKind,
297
+ previewImageUrl: lineData.previewImageUrl,
298
+ durationMs: lineData.durationMs,
299
+ trackingId: lineData.trackingId
300
+ });
301
+ lastResult = await (lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
302
+ verbose: false,
303
+ mediaUrl: resolved.mediaUrl,
304
+ mediaKind: resolved.mediaKind,
305
+ previewImageUrl: resolved.previewImageUrl,
306
+ durationMs: resolved.durationMs,
307
+ trackingId: resolved.trackingId,
308
+ cfg,
309
+ accountId: accountId ?? void 0
310
+ });
311
+ }
312
+ };
313
+ if (!shouldSendQuickRepliesInline) {
314
+ if (lineData.flexMessage) {
315
+ const flexContents = lineData.flexMessage.contents;
316
+ lastResult = await sendFlex(to, lineData.flexMessage.altText, flexContents, {
317
+ verbose: false,
318
+ cfg,
319
+ accountId: accountId ?? void 0
320
+ });
321
+ }
322
+ if (lineData.templateMessage) {
323
+ const template = buildTemplate(lineData.templateMessage);
324
+ if (template) lastResult = await sendTemplate(to, template, {
325
+ verbose: false,
326
+ cfg,
327
+ accountId: accountId ?? void 0
328
+ });
329
+ }
330
+ if (lineData.location) lastResult = await sendLocation(to, lineData.location, {
331
+ verbose: false,
332
+ cfg,
333
+ accountId: accountId ?? void 0
334
+ });
335
+ for (const flexMsg of processed.flexMessages) {
336
+ const flexContents = flexMsg.contents;
337
+ lastResult = await sendFlex(to, flexMsg.altText, flexContents, {
338
+ verbose: false,
339
+ cfg,
340
+ accountId: accountId ?? void 0
341
+ });
342
+ }
343
+ }
344
+ const sendMediaAfterText = !(hasQuickReplies && chunks.length > 0);
345
+ if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && !sendMediaAfterText) await sendMediaMessages();
346
+ if (chunks.length > 0) for (let i = 0; i < chunks.length; i += 1) if (i === chunks.length - 1 && hasQuickReplies) lastResult = await sendQuickReplies(to, chunks[i], quickReplies, {
347
+ verbose: false,
348
+ cfg,
349
+ accountId: accountId ?? void 0
350
+ });
351
+ else lastResult = await sendText(to, chunks[i], {
352
+ verbose: false,
353
+ cfg,
354
+ accountId: accountId ?? void 0
355
+ });
356
+ else if (shouldSendQuickRepliesInline) {
357
+ const quickReplyMessages = [];
358
+ if (lineData.flexMessage) quickReplyMessages.push({
359
+ type: "flex",
360
+ altText: lineData.flexMessage.altText.slice(0, 400),
361
+ contents: lineData.flexMessage.contents
362
+ });
363
+ if (lineData.templateMessage) {
364
+ const template = buildTemplate(lineData.templateMessage);
365
+ if (template) quickReplyMessages.push(template);
366
+ }
367
+ if (lineData.location) quickReplyMessages.push({
368
+ type: "location",
369
+ title: lineData.location.title.slice(0, 100),
370
+ address: lineData.location.address.slice(0, 100),
371
+ latitude: lineData.location.latitude,
372
+ longitude: lineData.location.longitude
373
+ });
374
+ for (const flexMsg of processed.flexMessages) quickReplyMessages.push({
375
+ type: "flex",
376
+ altText: flexMsg.altText.slice(0, 400),
377
+ contents: flexMsg.contents
378
+ });
379
+ for (const url of mediaUrls) {
380
+ const trimmed = url?.trim();
381
+ if (!trimmed) continue;
382
+ if (!useLineSpecificMedia) {
383
+ quickReplyMessages.push({
384
+ type: "image",
385
+ originalContentUrl: trimmed,
386
+ previewImageUrl: trimmed
387
+ });
388
+ continue;
389
+ }
390
+ const resolved = await resolveLineOutboundMedia(trimmed, {
391
+ mediaKind: lineData.mediaKind,
392
+ previewImageUrl: lineData.previewImageUrl,
393
+ durationMs: lineData.durationMs,
394
+ trackingId: lineData.trackingId
395
+ });
396
+ quickReplyMessages.push(buildLineMediaMessageObject(resolved, { allowTrackingId: isLineUserTarget(to) }));
397
+ }
398
+ if (quickReplyMessages.length > 0 && quickReply) {
399
+ const lastIndex = quickReplyMessages.length - 1;
400
+ quickReplyMessages[lastIndex] = {
401
+ ...quickReplyMessages[lastIndex],
402
+ quickReply
403
+ };
404
+ await sendMessageBatch(quickReplyMessages);
405
+ } else if (quickReply) lastResult = await sendQuickReplies(to, buildLineQuickReplyFallbackText(quickReplies), quickReplies, {
406
+ verbose: false,
407
+ cfg,
408
+ accountId: accountId ?? void 0
409
+ });
410
+ }
411
+ if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && sendMediaAfterText) await sendMediaMessages();
412
+ if (lastResult) return createEmptyChannelResult("line", { ...lastResult });
413
+ return createEmptyChannelResult("line", {
414
+ messageId: "empty",
415
+ chatId: to
416
+ });
417
+ },
418
+ ...createAttachedChannelResultAdapter({
419
+ channel: "line",
420
+ sendText: async ({ cfg, to, text, accountId }) => {
421
+ const outboundRuntime = await loadLineOutboundRuntime();
422
+ const sendText = outboundRuntime.pushMessageLine;
423
+ const sendFlex = outboundRuntime.pushFlexMessage;
424
+ const processed = outboundRuntime.processLineMessage(text);
425
+ let result;
426
+ if (processed.text.trim()) result = await sendText(to, processed.text, {
427
+ verbose: false,
428
+ cfg,
429
+ accountId: accountId ?? void 0
430
+ });
431
+ else result = {
432
+ messageId: "processed",
433
+ chatId: to,
434
+ receipt: createLineSendReceipt({
435
+ messageId: "processed",
436
+ chatId: to,
437
+ kind: "card"
438
+ })
439
+ };
440
+ for (const flexMsg of processed.flexMessages) {
441
+ const flexContents = flexMsg.contents;
442
+ await sendFlex(to, flexMsg.altText, flexContents, {
443
+ verbose: false,
444
+ cfg,
445
+ accountId: accountId ?? void 0
446
+ });
447
+ }
448
+ return result;
449
+ },
450
+ sendMedia: async ({ cfg, to, text, mediaUrl, accountId }) => await (await loadLineOutboundRuntime()).sendMessageLine(to, text, {
451
+ verbose: false,
452
+ mediaUrl,
453
+ cfg,
454
+ accountId: accountId ?? void 0
455
+ })
456
+ })
457
+ };
458
+ function toLineMessageSendResult(result, kind) {
459
+ const source = result;
460
+ const receipt = result.receipt ?? (result.messageId ? createLineSendReceipt({
461
+ messageId: result.messageId,
462
+ chatId: source.chatId ?? "",
463
+ kind
464
+ }) : void 0);
465
+ if (!receipt) throw new Error("LINE message adapter send did not return a receipt");
466
+ return {
467
+ messageId: result.messageId || receipt.primaryPlatformMessageId,
468
+ receipt
469
+ };
470
+ }
471
+ const lineMessageAdapter = defineChannelMessageAdapter({
472
+ id: "line",
473
+ durableFinal: { capabilities: {
474
+ text: true,
475
+ media: true,
476
+ messageSendingHooks: true
477
+ } },
478
+ send: {
479
+ text: async ({ cfg, to, text, accountId }) => {
480
+ return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
481
+ cfg,
482
+ to,
483
+ text,
484
+ accountId,
485
+ payload: { text }
486
+ }), "text");
487
+ },
488
+ media: async ({ cfg, to, text, mediaUrl, accountId }) => {
489
+ return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
490
+ cfg,
491
+ to,
492
+ text,
493
+ mediaUrl,
494
+ accountId,
495
+ payload: {
496
+ text,
497
+ mediaUrl
498
+ }
499
+ }), "media");
500
+ }
501
+ },
502
+ receive: {
503
+ defaultAckPolicy: "after_receive_record",
504
+ supportedAckPolicies: ["after_receive_record"]
505
+ }
506
+ });
507
+ //#endregion
508
+ //#region extensions/line/src/status.ts
509
+ const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-7u2o9QN5.js"));
510
+ const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({
511
+ channel: "line",
512
+ dependencySourceKey: "tokenSource",
513
+ missingPrimaryMessage: "LINE channel access token not configured",
514
+ missingDependentMessage: "LINE channel secret not configured"
515
+ });
516
+ const lineStatusAdapter = createComputedAccountStatusAdapter({
517
+ defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID$1),
518
+ collectStatusIssues: collectLineStatusIssues,
519
+ buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
520
+ probeAccount: async ({ account, timeoutMs }) => await (await loadLineProbeRuntime()).probeLineBot(account.channelAccessToken, timeoutMs),
521
+ resolveAccountSnapshot: ({ account }) => ({
522
+ accountId: account.accountId,
523
+ name: account.name,
524
+ enabled: account.enabled,
525
+ configured: hasLineCredentials(account),
526
+ extra: {
527
+ tokenSource: account.tokenSource,
528
+ mode: "webhook"
529
+ }
530
+ })
531
+ });
532
+ //#endregion
533
+ //#region extensions/line/src/channel.ts
534
+ const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-Cc-v3szZ.js"));
535
+ const lineSecurityAdapter = createRestrictSendersChannelSecurity({
536
+ channelKey: "line",
537
+ resolveDmPolicy: (account) => account.config.dmPolicy,
538
+ resolveDmAllowFrom: (account) => account.config.allowFrom,
539
+ resolveGroupPolicy: (account) => account.config.groupPolicy,
540
+ surface: "LINE groups",
541
+ openScope: "any member in groups",
542
+ groupPolicyPath: "channels.line.groupPolicy",
543
+ groupAllowFromPath: "channels.line.groupAllowFrom",
544
+ mentionGated: false,
545
+ policyPathSuffix: "dmPolicy",
546
+ approveHint: "klaw pairing approve line <code>",
547
+ normalizeDmEntry: (raw) => raw.replace(/^line:(?:user:)?/i, "")
548
+ });
549
+ const linePlugin = createChatChannelPlugin({
550
+ base: {
551
+ id: "line",
552
+ ...lineChannelPluginCommon,
553
+ setupWizard: lineSetupWizard,
554
+ groups: { resolveRequireMention: resolveLineGroupRequireMention },
555
+ messaging: {
556
+ targetPrefixes: ["line"],
557
+ normalizeTarget: (target) => {
558
+ const trimmed = target.trim();
559
+ if (!trimmed) return;
560
+ return trimmed.replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
561
+ },
562
+ resolveInboundConversation: lineBindingsAdapter.resolveInboundConversation,
563
+ transformReplyPayload: ({ payload }) => {
564
+ if (!payload.text || !hasLineDirectives(payload.text)) return payload;
565
+ return parseLineDirectives(payload);
566
+ },
567
+ targetResolver: {
568
+ looksLikeId: (id) => {
569
+ const trimmed = id?.trim();
570
+ if (!trimmed) return false;
571
+ return /^[UCR][a-f0-9]{32}$/i.test(trimmed) || /^line:/i.test(trimmed);
572
+ },
573
+ hint: "<userId|groupId|roomId>"
574
+ }
575
+ },
576
+ directory: createEmptyChannelDirectoryAdapter(),
577
+ setup: lineSetupAdapter,
578
+ status: lineStatusAdapter,
579
+ gateway: lineGatewayAdapter,
580
+ message: lineMessageAdapter,
581
+ bindings: lineBindingsAdapter,
582
+ conversationBindings: { defaultTopLevelPlacement: "current" },
583
+ agentPrompt: { messageToolHints: () => [
584
+ "",
585
+ "### LINE Rich Messages",
586
+ "LINE supports rich visual messages. Use these directives in your reply when appropriate:",
587
+ "",
588
+ "**Quick Replies** (bottom button suggestions):",
589
+ " [[quick_replies: Option 1, Option 2, Option 3]]",
590
+ "",
591
+ "**Location** (map pin):",
592
+ " [[location: Place Name | Address | latitude | longitude]]",
593
+ "",
594
+ "**Confirm Dialog** (yes/no prompt):",
595
+ " [[confirm: Question text? | Yes Label | No Label]]",
596
+ "",
597
+ "**Button Menu** (title + text + buttons):",
598
+ " [[buttons: Title | Description | Btn1:action1, Btn2:https://url.com]]",
599
+ "",
600
+ "**Media Player Card** (music status):",
601
+ " [[media_player: Song Title | Artist Name | Source | https://albumart.url | playing]]",
602
+ " - Status: 'playing' or 'paused' (optional)",
603
+ "",
604
+ "**Event Card** (calendar events, meetings):",
605
+ " [[event: Event Title | Date | Time | Location | Description]]",
606
+ " - Time, Location, Description are optional",
607
+ "",
608
+ "**Agenda Card** (multiple events/schedule):",
609
+ " [[agenda: Schedule Title | Event1:9:00 AM, Event2:12:00 PM, Event3:3:00 PM]]",
610
+ "",
611
+ "**Device Control Card** (smart devices, TVs, etc.):",
612
+ " [[device: Device Name | Device Type | Status | Control1:data1, Control2:data2]]",
613
+ "",
614
+ "**Apple TV Remote** (full D-pad + transport):",
615
+ " [[appletv_remote: Apple TV | Playing]]",
616
+ "",
617
+ "**Auto-converted**: Markdown tables become Flex cards, code blocks become styled cards.",
618
+ "",
619
+ "When to use rich messages:",
620
+ "- Use [[quick_replies:...]] when offering 2-4 clear options",
621
+ "- Use [[confirm:...]] for yes/no decisions",
622
+ "- Use [[buttons:...]] for menus with actions/links",
623
+ "- Use [[location:...]] when sharing a place",
624
+ "- Use [[media_player:...]] when showing what's playing",
625
+ "- Use [[event:...]] for calendar event details",
626
+ "- Use [[agenda:...]] for a day's schedule or event list",
627
+ "- Use [[device:...]] for smart device status/controls",
628
+ "- Tables/code in your response auto-convert to visual cards"
629
+ ] }
630
+ },
631
+ pairing: { text: {
632
+ idLabel: "lineUserId",
633
+ message: "Klaw: your access has been approved.",
634
+ normalizeAllowEntry: createPairingPrefixStripper(/^line:(?:user:)?/i),
635
+ notify: async ({ cfg, id, message }) => {
636
+ const account = (getLineRuntime().channel.line?.resolveLineAccount ?? resolveLineAccount)({ cfg });
637
+ if (!account.channelAccessToken) throw new Error("LINE channel access token not configured");
638
+ await (getLineRuntime().channel.line?.pushMessageLine ?? (await loadLineChannelRuntime()).pushMessageLine)(id, message, {
639
+ cfg,
640
+ accountId: account.accountId,
641
+ channelAccessToken: account.channelAccessToken
642
+ });
643
+ }
644
+ } },
645
+ security: lineSecurityAdapter,
646
+ outbound: lineOutboundAdapter
647
+ });
648
+ //#endregion
649
+ export { lineChannelPluginCommon as n, linePlugin as t };
@@ -0,0 +1,2 @@
1
+ import { t as linePlugin } from "./channel-DV5h44-j.js";
2
+ export { linePlugin };
@@ -0,0 +1,4 @@
1
+ import { S as pushMessageLine } from "./markdown-to-line-CC3BU6CC.js";
2
+ import { t as monitorLineProvider } from "./monitor-Ci8Hg8ay.js";
3
+ import { t as probeLineBot } from "./probe-BPSs_A_8.js";
4
+ export { monitorLineProvider, probeLineBot, pushMessageLine };
@@ -0,0 +1,2 @@
1
+ import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-CD4A1FE7.js";
2
+ export { listLineAccountIds, resolveDefaultLineAccountId, resolveLineAccount };