@openclaw/line 2026.5.7-beta.1 → 2026.5.9-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-CnhV8Dzi.js";
1
+ import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-DkbpfXGE.js";
2
2
  import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-DCH7CE-y.js";
3
3
  //#region extensions/line/src/channel.setup.ts
4
4
  const lineSetupPlugin = {
@@ -2,7 +2,7 @@ import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLin
2
2
  import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-DCH7CE-y.js";
3
3
  import { i as resolveExactLineGroupConfigKey, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-HskUUWsn.js";
4
4
  import { n as parseLineDirectives, o as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-Bmf8GnSU.js";
5
- import { t as resolveLineOutboundMedia } from "./outbound-media-BfhBmWq5.js";
5
+ import { n as resolveLineOutboundMedia, t as createLineSendReceipt } from "./send-receipt-DSg7S42s.js";
6
6
  import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
7
7
  import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
8
8
  import { createRestrictSendersChannelSecurity, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
@@ -12,6 +12,7 @@ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/
12
12
  import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
13
13
  import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
14
14
  import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
15
+ import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
15
16
  import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
16
17
  import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
17
18
  import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "openclaw/plugin-sdk/status-helpers";
@@ -107,7 +108,7 @@ const lineChannelPluginCommon = {
107
108
  //#endregion
108
109
  //#region extensions/line/src/gateway.ts
109
110
  const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-N91OWW1J.js"));
110
- const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-DyxYlrM9.js"));
111
+ const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-C1Toz0OI.js"));
111
112
  const lineGatewayAdapter = {
112
113
  startAccount: async (ctx) => {
113
114
  const account = ctx.account;
@@ -211,7 +212,7 @@ function resolveLineGroupRequireMention(params) {
211
212
  }
212
213
  //#endregion
213
214
  //#region extensions/line/src/outbound.ts
214
- const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-B-luYwEB.js"));
215
+ const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-zBxAf-it.js"));
215
216
  function isLineUserTarget(target) {
216
217
  const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
217
218
  return /^U/i.test(normalized);
@@ -265,17 +266,11 @@ const lineOutboundAdapter = {
265
266
  const quickReply = hasQuickReplies ? (lineRuntime?.createQuickReplyItems ?? outboundRuntime.createQuickReplyItems)(quickReplies) : void 0;
266
267
  const sendMessageBatch = async (messages) => {
267
268
  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
- }
269
+ for (let i = 0; i < messages.length; i += 5) lastResult = await sendBatch(to, messages.slice(i, i + 5), {
270
+ verbose: false,
271
+ cfg,
272
+ accountId: accountId ?? void 0
273
+ });
279
274
  };
280
275
  const processed = payload.text ? outboundRuntime.processLineMessage(payload.text) : {
281
276
  text: "",
@@ -437,7 +432,12 @@ const lineOutboundAdapter = {
437
432
  });
438
433
  else result = {
439
434
  messageId: "processed",
440
- chatId: to
435
+ chatId: to,
436
+ receipt: createLineSendReceipt({
437
+ messageId: "processed",
438
+ chatId: to,
439
+ kind: "card"
440
+ })
441
441
  };
442
442
  for (const flexMsg of processed.flexMessages) {
443
443
  const flexContents = flexMsg.contents;
@@ -457,6 +457,55 @@ const lineOutboundAdapter = {
457
457
  })
458
458
  })
459
459
  };
460
+ function toLineMessageSendResult(result, kind) {
461
+ const source = result;
462
+ const receipt = result.receipt ?? (result.messageId ? createLineSendReceipt({
463
+ messageId: result.messageId,
464
+ chatId: source.chatId ?? "",
465
+ kind
466
+ }) : void 0);
467
+ if (!receipt) throw new Error("LINE message adapter send did not return a receipt");
468
+ return {
469
+ messageId: result.messageId || receipt.primaryPlatformMessageId,
470
+ receipt
471
+ };
472
+ }
473
+ const lineMessageAdapter = defineChannelMessageAdapter({
474
+ id: "line",
475
+ durableFinal: { capabilities: {
476
+ text: true,
477
+ media: true,
478
+ messageSendingHooks: true
479
+ } },
480
+ send: {
481
+ text: async ({ cfg, to, text, accountId }) => {
482
+ return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
483
+ cfg,
484
+ to,
485
+ text,
486
+ accountId,
487
+ payload: { text }
488
+ }), "text");
489
+ },
490
+ media: async ({ cfg, to, text, mediaUrl, accountId }) => {
491
+ return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
492
+ cfg,
493
+ to,
494
+ text,
495
+ mediaUrl,
496
+ accountId,
497
+ payload: {
498
+ text,
499
+ mediaUrl
500
+ }
501
+ }), "media");
502
+ }
503
+ },
504
+ receive: {
505
+ defaultAckPolicy: "after_agent_dispatch",
506
+ supportedAckPolicies: ["after_receive_record", "after_agent_dispatch"]
507
+ }
508
+ });
460
509
  //#endregion
461
510
  //#region extensions/line/src/status.ts
462
511
  const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-N91OWW1J.js"));
@@ -484,7 +533,7 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
484
533
  });
485
534
  //#endregion
486
535
  //#region extensions/line/src/channel.ts
487
- const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-bXoj8_Sl.js"));
536
+ const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-De0BoY61.js"));
488
537
  const lineSecurityAdapter = createRestrictSendersChannelSecurity({
489
538
  channelKey: "line",
490
539
  resolveDmPolicy: (account) => account.config.dmPolicy,
@@ -530,6 +579,7 @@ const linePlugin = createChatChannelPlugin({
530
579
  setup: lineSetupAdapter,
531
580
  status: lineStatusAdapter,
532
581
  gateway: lineGatewayAdapter,
582
+ message: lineMessageAdapter,
533
583
  bindings: lineBindingsAdapter,
534
584
  conversationBindings: { defaultTopLevelPlacement: "current" },
535
585
  agentPrompt: { messageToolHints: () => [
@@ -1,2 +1,2 @@
1
- import { t as linePlugin } from "./channel-CnhV8Dzi.js";
1
+ import { t as linePlugin } from "./channel-DkbpfXGE.js";
2
2
  export { linePlugin };
@@ -0,0 +1,4 @@
1
+ import { S as pushMessageLine } from "./markdown-to-line-DASNAjBq.js";
2
+ import { t as monitorLineProvider } from "./monitor-vLNFYADv.js";
3
+ import { t as probeLineBot } from "./probe-D9BPzb2n.js";
4
+ export { monitorLineProvider, probeLineBot, pushMessageLine };
@@ -1,5 +1,5 @@
1
1
  import { i as resolveLineAccount } from "./accounts-UXAvdBo_.js";
2
- import { n as validateLineMediaUrl } from "./outbound-media-BfhBmWq5.js";
2
+ import { r as validateLineMediaUrl, t as createLineSendReceipt } from "./send-receipt-DSg7S42s.js";
3
3
  import { r as createReceiptCard } from "./schedule-cards-BwQChTdM.js";
4
4
  import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-runtime";
5
5
  import { messagingApi } from "@line/bot-sdk";
@@ -319,6 +319,14 @@ function recordLineOutboundActivity(accountId) {
319
319
  direction: "outbound"
320
320
  });
321
321
  }
322
+ function resolveLineReceiptKind(messages) {
323
+ const types = new Set(messages.map((message) => message.type));
324
+ if (types.has("audio")) return "voice";
325
+ if (types.has("image") || types.has("video")) return "media";
326
+ if (types.has("flex") || types.has("template") || types.has("location")) return "card";
327
+ if (types.has("text")) return "text";
328
+ return "unknown";
329
+ }
322
330
  async function pushLineMessages(to, messages, opts, behavior = {}) {
323
331
  if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
324
332
  const { account, client, chatId } = createLinePushContext(to, opts);
@@ -335,7 +343,13 @@ async function pushLineMessages(to, messages, opts, behavior = {}) {
335
343
  if (opts.verbose) logVerbose(behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`);
336
344
  return {
337
345
  messageId: "push",
338
- chatId
346
+ chatId,
347
+ receipt: createLineSendReceipt({
348
+ messageId: "push",
349
+ chatId,
350
+ kind: resolveLineReceiptKind(messages),
351
+ messageCount: messages.length
352
+ })
339
353
  };
340
354
  }
341
355
  async function replyLineMessages(replyToken, messages, opts, behavior = {}) {
@@ -380,7 +394,13 @@ async function sendMessageLine(to, text, opts) {
380
394
  await replyLineMessages(opts.replyToken, messages, opts, { verboseMessage: () => `line: replied to ${chatId}` });
381
395
  return {
382
396
  messageId: "reply",
383
- chatId
397
+ chatId,
398
+ receipt: createLineSendReceipt({
399
+ messageId: "reply",
400
+ chatId,
401
+ kind: resolveLineReceiptKind(messages),
402
+ messageCount: messages.length
403
+ })
384
404
  };
385
405
  }
386
406
  return pushLineMessages(chatId, messages, opts, { verboseMessage: (resolvedChatId) => `line: pushed message to ${resolvedChatId}` });
@@ -1,7 +1,8 @@
1
1
  import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-UXAvdBo_.js";
2
2
  import { a as resolveLineGroupConfigEntry, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-HskUUWsn.js";
3
- import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, E as replyMessageLine, O as showLoadingAnimation, S as pushMessageLine, T as pushTextMessageWithQuickReplies, _ as getUserDisplayName, c as processLineMessage, d as createFlexMessage, f as createImageMessage, h as createTextMessageWithQuickReplies, m as createQuickReplyItems, p as createLocationMessage } from "./markdown-to-line-CCWK6PJh.js";
3
+ import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, E as replyMessageLine, O as showLoadingAnimation, S as pushMessageLine, T as pushTextMessageWithQuickReplies, _ as getUserDisplayName, c as processLineMessage, d as createFlexMessage, f as createImageMessage, h as createTextMessageWithQuickReplies, m as createQuickReplyItems, p as createLocationMessage } from "./markdown-to-line-DASNAjBq.js";
4
4
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
5
+ import { createMessageReceiveContext, hasFinalChannelTurnDispatch } from "openclaw/plugin-sdk/channel-message";
5
6
  import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
6
7
  import { lowercasePreservingWhitespace, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
7
8
  import { firstDefined, isSenderIdAllowed, mergeDmAllowFromSources } from "openclaw/plugin-sdk/allow-from";
@@ -9,8 +10,6 @@ import { messagingApi } from "@line/bot-sdk";
9
10
  import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
10
11
  import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
11
12
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
12
- import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
13
- import { hasFinalInboundReplyDispatch } from "openclaw/plugin-sdk/inbound-reply-dispatch";
14
13
  import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
15
14
  import { isRequestBodyLimitError, normalizePluginHttpPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
16
15
  import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1 } from "openclaw/plugin-sdk/webhook-request-guards";
@@ -1049,6 +1048,20 @@ function createLineBot(opts) {
1049
1048
  };
1050
1049
  }
1051
1050
  //#endregion
1051
+ //#region extensions/line/src/monitor-durable.ts
1052
+ function hasLineChannelData(payload) {
1053
+ const lineData = payload.channelData?.line;
1054
+ return Boolean(lineData && Object.keys(lineData).length > 0);
1055
+ }
1056
+ function resolveLineDurableReplyOptions(params) {
1057
+ if (params.infoKind !== "final") return false;
1058
+ if (params.replyToken && !params.replyTokenUsed) return false;
1059
+ if (hasLineChannelData(params.payload)) return false;
1060
+ const reply = resolveSendableOutboundReplyParts(params.payload);
1061
+ if (reply.hasMedia || !reply.hasText) return false;
1062
+ return { to: params.to };
1063
+ }
1064
+ //#endregion
1052
1065
  //#region extensions/line/src/reply-chunks.ts
1053
1066
  async function sendLineReplyChunks(params) {
1054
1067
  const hasQuickReplies = Boolean(params.quickReplies?.length);
@@ -1149,6 +1162,7 @@ function createLineNodeWebhookHandler(params) {
1149
1162
  res.end(JSON.stringify({ error: "Method Not Allowed" }));
1150
1163
  return;
1151
1164
  }
1165
+ let receiveContext;
1152
1166
  try {
1153
1167
  const signatureHeader = req.headers["x-line-signature"];
1154
1168
  const signature = typeof signatureHeader === "string" ? signatureHeader.trim() : Array.isArray(signatureHeader) ? (signatureHeader[0] ?? "").trim() : "";
@@ -1175,14 +1189,25 @@ function createLineNodeWebhookHandler(params) {
1175
1189
  return;
1176
1190
  }
1177
1191
  params.onRequestAuthenticated?.();
1192
+ receiveContext = createMessageReceiveContext({
1193
+ id: `${Date.now()}:line:webhook`,
1194
+ channel: "line",
1195
+ message: body,
1196
+ ackPolicy: body.events?.length ? "after_agent_dispatch" : "after_receive_record",
1197
+ onAck: () => {
1198
+ res.statusCode = 200;
1199
+ res.setHeader("Content-Type", "application/json");
1200
+ res.end(JSON.stringify({ status: "ok" }));
1201
+ }
1202
+ });
1178
1203
  if (body.events && body.events.length > 0) {
1179
1204
  logVerbose(`line: received ${body.events.length} webhook events`);
1180
1205
  await params.bot.handleWebhook(body);
1181
1206
  }
1182
- res.statusCode = 200;
1183
- res.setHeader("Content-Type", "application/json");
1184
- res.end(JSON.stringify({ status: "ok" }));
1207
+ const ackStage = body.events?.length ? "agent_dispatch" : "receive_record";
1208
+ if (receiveContext.shouldAckAfter(ackStage)) await receiveContext.ack();
1185
1209
  } catch (err) {
1210
+ await receiveContext?.nack(err);
1186
1211
  if (isRequestBodyLimitError$1(err, "PAYLOAD_TOO_LARGE")) {
1187
1212
  res.statusCode = 413;
1188
1213
  res.setHeader("Content-Type", "application/json");
@@ -1287,12 +1312,6 @@ async function monitorLineProvider(opts) {
1287
1312
  try {
1288
1313
  const textLimit = 5e3;
1289
1314
  let replyTokenUsed = false;
1290
- const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
1291
- cfg: config,
1292
- agentId: route.agentId,
1293
- channel: "line",
1294
- accountId: route.accountId
1295
- });
1296
1315
  const core = getLineRuntime();
1297
1316
  const turnResult = await core.channel.turn.run({
1298
1317
  channel: "line",
@@ -1314,9 +1333,15 @@ async function monitorLineProvider(opts) {
1314
1333
  recordInboundSession: core.channel.session.recordInboundSession,
1315
1334
  dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
1316
1335
  record: ctx.turn.record,
1317
- dispatcherOptions: { ...replyPipeline },
1318
- replyOptions: { onModelSelected },
1336
+ replyPipeline: {},
1319
1337
  delivery: {
1338
+ durable: (payload, info) => resolveLineDurableReplyOptions({
1339
+ payload,
1340
+ infoKind: info.kind,
1341
+ to: ctxPayload.From,
1342
+ replyToken,
1343
+ replyTokenUsed
1344
+ }),
1320
1345
  deliver: async (payload) => {
1321
1346
  const lineData = payload.channelData?.line ?? {};
1322
1347
  if (ctx.userId && !ctx.isGroup) showLoadingAnimation(ctx.userId, {
@@ -1365,7 +1390,7 @@ async function monitorLineProvider(opts) {
1365
1390
  })
1366
1391
  }
1367
1392
  });
1368
- if (!hasFinalInboundReplyDispatch(turnResult.dispatched ? turnResult.dispatchResult : void 0)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
1393
+ if (!hasFinalChannelTurnDispatch(turnResult.dispatched ? turnResult.dispatchResult : void 0)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
1369
1394
  } catch (err) {
1370
1395
  runtime.error?.(danger(`line: auto-reply failed: ${String(err)}`));
1371
1396
  if (replyToken) try {
@@ -0,0 +1,2 @@
1
+ import { t as monitorLineProvider } from "./monitor-vLNFYADv.js";
2
+ export { monitorLineProvider };
@@ -1,2 +1,2 @@
1
- import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, D as sendMessageLine, S as pushMessageLine, T as pushTextMessageWithQuickReplies, c as processLineMessage, m as createQuickReplyItems, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage } from "./markdown-to-line-CCWK6PJh.js";
1
+ import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, D as sendMessageLine, S as pushMessageLine, T as pushTextMessageWithQuickReplies, c as processLineMessage, m as createQuickReplyItems, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage } from "./markdown-to-line-DASNAjBq.js";
2
2
  export { buildTemplateMessageFromPayload, createQuickReplyItems, processLineMessage, pushFlexMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, sendMessageLine };
@@ -3,17 +3,18 @@ import { a as resolveLineGroupConfigEntry, i as resolveExactLineGroupConfigKey,
3
3
  import { a as createMediaPlayerCard, i as createDeviceControlCard, n as parseLineDirectives, o as LineChannelConfigSchema, r as createAppleTvRemoteCard, s as LineConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-Bmf8GnSU.js";
4
4
  import { n as createEventCard, r as createReceiptCard, t as createAgendaCard } from "./schedule-cards-BwQChTdM.js";
5
5
  import { a as createListCard, i as createInfoCard, n as createCarousel, o as createNotificationBubble, r as createImageCard, t as createActionCard } from "./basic-cards-7DTG6E-A.js";
6
- import { A as buildTemplateMessageFromPayload, B as createYesNoConfirm, C as pushMessagesLine, D as sendMessageLine, E as replyMessageLine, F as createImageCarousel, G as toFlexMessage, H as messageAction, I as createImageCarouselColumn, L as createLinkMenu, M as createButtonTemplate, N as createCarouselColumn, O as showLoadingAnimation, P as createConfirmTemplate, R as createProductCarousel, S as pushMessageLine, T as pushTextMessageWithQuickReplies, U as postbackAction, V as datetimePickerAction, W as uriAction, _ as getUserDisplayName, a as extractLinks, b as pushImageMessage, c as processLineMessage, d as createFlexMessage, f as createImageMessage, g as createVideoMessage, h as createTextMessageWithQuickReplies, i as extractCodeBlocks, j as createButtonMenu, k as resolveLineChannelAccessToken, l as stripMarkdown, m as createQuickReplyItems, n as convertLinksToFlexBubble, o as extractMarkdownTables, p as createLocationMessage, r as convertTableToFlexBubble, s as hasMarkdownToConvert, t as convertCodeBlockToFlexBubble, u as createAudioMessage, v as getUserProfile, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage, z as createTemplateCarousel } from "./markdown-to-line-CCWK6PJh.js";
7
- import { a as validateLineSignature, c as isSenderAllowed, i as parseLineWebhookBody, l as normalizeAllowFrom, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider, u as normalizeDmAllowFromWithStore } from "./monitor-B4_RxHc0.js";
6
+ import { A as buildTemplateMessageFromPayload, B as createYesNoConfirm, C as pushMessagesLine, D as sendMessageLine, E as replyMessageLine, F as createImageCarousel, G as toFlexMessage, H as messageAction, I as createImageCarouselColumn, L as createLinkMenu, M as createButtonTemplate, N as createCarouselColumn, O as showLoadingAnimation, P as createConfirmTemplate, R as createProductCarousel, S as pushMessageLine, T as pushTextMessageWithQuickReplies, U as postbackAction, V as datetimePickerAction, W as uriAction, _ as getUserDisplayName, a as extractLinks, b as pushImageMessage, c as processLineMessage, d as createFlexMessage, f as createImageMessage, g as createVideoMessage, h as createTextMessageWithQuickReplies, i as extractCodeBlocks, j as createButtonMenu, k as resolveLineChannelAccessToken, l as stripMarkdown, m as createQuickReplyItems, n as convertLinksToFlexBubble, o as extractMarkdownTables, p as createLocationMessage, r as convertTableToFlexBubble, s as hasMarkdownToConvert, t as convertCodeBlockToFlexBubble, u as createAudioMessage, v as getUserProfile, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage, z as createTemplateCarousel } from "./markdown-to-line-DASNAjBq.js";
7
+ import { a as validateLineSignature, c as isSenderAllowed, i as parseLineWebhookBody, l as normalizeAllowFrom, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider, u as normalizeDmAllowFromWithStore } from "./monitor-vLNFYADv.js";
8
8
  import { t as probeLineBot } from "./probe-D9BPzb2n.js";
9
9
  import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
10
10
  import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
11
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
11
+ import { createMessageReceiveContext } from "openclaw/plugin-sdk/channel-message";
12
12
  import { DEFAULT_ACCOUNT_ID, formatDocsLink, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
13
13
  import { buildComputedAccountStatusSnapshot, buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/status-helpers";
14
14
  import { messagingApi } from "@line/bot-sdk";
15
15
  import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
16
16
  import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/agent-media-payload";
17
+ import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
17
18
  import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
18
19
  //#region extensions/line/src/webhook.ts
19
20
  const LINE_WEBHOOK_MAX_RAW_BODY_BYTES = 64 * 1024;
@@ -29,6 +30,7 @@ function parseWebhookBody(rawBody) {
29
30
  function createLineWebhookMiddleware(options) {
30
31
  const { channelSecret, onEvents, runtime } = options;
31
32
  return async (req, res, _next) => {
33
+ let receiveContext;
32
34
  try {
33
35
  const signature = req.headers["x-line-signature"];
34
36
  if (!signature || typeof signature !== "string") {
@@ -54,12 +56,23 @@ function createLineWebhookMiddleware(options) {
54
56
  res.status(400).json({ error: "Invalid webhook payload" });
55
57
  return;
56
58
  }
59
+ receiveContext = createMessageReceiveContext({
60
+ id: `${Date.now()}:line:webhook`,
61
+ channel: "line",
62
+ message: body,
63
+ ackPolicy: body.events?.length ? "after_agent_dispatch" : "after_receive_record",
64
+ onAck: () => {
65
+ res.status(200).json({ status: "ok" });
66
+ }
67
+ });
57
68
  if (body.events && body.events.length > 0) {
58
69
  logVerbose(`line: received ${body.events.length} webhook events`);
59
70
  await onEvents(body);
60
71
  }
61
- res.status(200).json({ status: "ok" });
72
+ const ackStage = body.events?.length ? "agent_dispatch" : "receive_record";
73
+ if (receiveContext.shouldAckAfter(ackStage)) await receiveContext.ack();
62
74
  } catch (err) {
75
+ await receiveContext?.nack(err);
63
76
  runtime?.error?.(danger(`line webhook error: ${String(err)}`));
64
77
  if (!res.headersSent) res.status(500).json({ error: "Internal server error" });
65
78
  }
@@ -117,7 +130,7 @@ async function createRichMenu(menu, opts) {
117
130
  async function uploadRichMenuImage(richMenuId, imagePath, opts) {
118
131
  const blobClient = getBlobClient(opts);
119
132
  const media = await loadWebMediaRaw(imagePath, { localRoots: opts.mediaLocalRoots ?? getAgentScopedMediaLocalRoots(opts.cfg) });
120
- const contentType = media.contentType === "image/png" || media.contentType === "image/jpeg" ? media.contentType : normalizeLowercaseStringOrEmpty(imagePath).endsWith(".png") ? "image/png" : "image/jpeg";
133
+ const contentType = media.contentType === "image/png" || media.contentType === "image/jpeg" ? media.contentType : mimeTypeFromFilePath(imagePath) === "image/png" ? "image/png" : "image/jpeg";
121
134
  const imageBytes = new ArrayBuffer(media.buffer.byteLength);
122
135
  new Uint8Array(imageBytes).set(media.buffer);
123
136
  await blobClient.setRichMenuImage(richMenuId, new Blob([imageBytes], { type: contentType }));
@@ -1,3 +1,4 @@
1
+ import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-message";
1
2
  import { resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
2
3
  import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
3
4
  //#region extensions/line/src/outbound-media.ts
@@ -52,4 +53,21 @@ async function resolveLineOutboundMedia(mediaUrl, opts = {}) {
52
53
  throw new Error("LINE outbound media currently requires a public HTTPS URL");
53
54
  }
54
55
  //#endregion
55
- export { validateLineMediaUrl as n, resolveLineOutboundMedia as t };
56
+ //#region extensions/line/src/send-receipt.ts
57
+ function createLineSendReceipt(params) {
58
+ const messageId = params.messageId.trim();
59
+ const chatId = params.chatId.trim();
60
+ return createMessageReceiptFromOutboundResults({
61
+ results: messageId ? [{
62
+ channel: "line",
63
+ messageId,
64
+ chatId,
65
+ conversationId: chatId,
66
+ meta: { messageCount: params.messageCount ?? 1 }
67
+ }] : [],
68
+ ...chatId ? { threadId: chatId } : {},
69
+ kind: params.kind ?? "unknown"
70
+ });
71
+ }
72
+ //#endregion
73
+ export { resolveLineOutboundMedia as n, validateLineMediaUrl as r, createLineSendReceipt as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/line",
3
- "version": "2026.5.7-beta.1",
3
+ "version": "2026.5.9-beta.1",
4
4
  "description": "OpenClaw LINE channel plugin",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,7 +15,7 @@
15
15
  "openclaw": "workspace:*"
16
16
  },
17
17
  "peerDependencies": {
18
- "openclaw": ">=2026.5.7-beta.1"
18
+ "openclaw": ">=2026.5.9-beta.1"
19
19
  },
20
20
  "peerDependenciesMeta": {
21
21
  "openclaw": {
@@ -45,10 +45,10 @@
45
45
  "minHostVersion": ">=2026.4.10"
46
46
  },
47
47
  "compat": {
48
- "pluginApi": ">=2026.5.7-beta.1"
48
+ "pluginApi": ">=2026.5.9-beta.1"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.5.7-beta.1"
51
+ "openclawVersion": "2026.5.9-beta.1"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,
@@ -1,4 +0,0 @@
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 };
@@ -1,2 +0,0 @@
1
- import { t as monitorLineProvider } from "./monitor-B4_RxHc0.js";
2
- export { monitorLineProvider };