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