@openclaw/line 2026.7.2-beta.7 → 2026.8.1-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.
@@ -1,14 +1,16 @@
1
1
  import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-BqEVsDoB.js";
2
2
  import { a as resolveLineGroupConfigEntry, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-D86FwAfr.js";
3
- import { n as buildLineMediaMessage, r as hasLineSpecificMediaOptions } from "./send-receipt-CY2ey8XH.js";
4
- import { E as buildTemplateMessageFromPayload, S as replyMessageLine, i as processLineMessage, l as createLocationMessage, p as getUserDisplayName, s as createFlexMessage, u as createQuickReplyItems, v as pushMessageLine, w as showLoadingAnimation, y as pushMessagesLine } from "./markdown-to-line-DMDQTMhx.js";
3
+ import { v as buildLineMediaMessage, y as hasLineSpecificMediaOptions } from "./flex-templates-BBBlNqpV.js";
4
+ import { i as processLineMessage, o as buildTemplateMessageFromPayload } from "./markdown-to-line-D4I-6lm6.js";
5
+ import { _ as replyMessageLine, a as createQuickReplyItems, c as getUserDisplayName, i as createLocationMessage, m as pushMessagesLine, n as createFlexMessage, p as pushMessageLine, y as showLoadingAnimation } from "./send-CFFr0-rZ.js";
5
6
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
6
7
  import { normalizeNullableString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
7
8
  import { DEFAULT_INGRESS_ADOPTION_STALL_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressError, createChannelIngressMonitor } from "openclaw/plugin-sdk/channel-outbound";
9
+ import { buildChannelInboundEventContext, buildMentionRegexes, formatInboundEnvelope, formatInboundMediaUnavailableText, formatLocationText, hasFinalInboundReplyDispatch, isChannelPartialDeliveryError, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toInboundMediaFactsWithMetadata, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
8
10
  import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
9
11
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
10
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
11
12
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
13
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
12
14
  import { firstDefined } from "openclaw/plugin-sdk/allow-from";
13
15
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
14
16
  import { MediaFetchError } from "openclaw/plugin-sdk/media-runtime";
@@ -16,12 +18,13 @@ import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
16
18
  import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal, warn } from "openclaw/plugin-sdk/runtime-env";
17
19
  import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
18
20
  import { HTTPFetchError } from "@line/bot-sdk";
19
- import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
21
+ import { collectErrorGraphCandidates, extractErrorCode, formatErrorMessage, readErrorName, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
20
22
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
21
- import { buildChannelInboundEventContext, buildMentionRegexes, formatInboundEnvelope, formatInboundMediaUnavailableText, formatLocationText, hasFinalInboundReplyDispatch, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toInboundMediaFactsWithMetadata, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
23
+ import { channelReadyPatch, channelStoppedPatch } from "openclaw/plugin-sdk/gateway-runtime";
22
24
  import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
23
- import { isRequestBodyLimitError, normalizePluginHttpPath, normalizeWebhookPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
25
+ import { canonicalizeWebhookRouteKey, isRequestBodyLimitError, normalizePluginHttpPath, normalizeWebhookPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
24
26
  import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1, runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
27
+ import { classifyTransientNetworkErrorCode } from "openclaw/plugin-sdk/retry-runtime";
25
28
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
26
29
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
27
30
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
@@ -147,8 +150,15 @@ function toLineDeliveryError(error) {
147
150
  return error instanceof Error ? error : new Error("LINE message send failed", { cause: error });
148
151
  }
149
152
  function getLineHttpError(error) {
150
- if (error instanceof HTTPFetchError) return error;
151
- return error instanceof Error && error.cause instanceof HTTPFetchError ? error.cause : void 0;
153
+ return collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]).find((candidate) => candidate instanceof HTTPFetchError);
154
+ }
155
+ function canFallbackAfterLineReplyFailure(error) {
156
+ const httpError = getLineHttpError(error);
157
+ if (httpError) return httpError.status >= 400 && httpError.status < 500 && httpError.status !== 408;
158
+ const candidates = collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]);
159
+ if (candidates.some((candidate) => readErrorName(candidate) === "AbortError" || classifyTransientNetworkErrorCode(extractErrorCode(candidate)) === "ambiguous")) return false;
160
+ if (candidates.some((candidate) => classifyTransientNetworkErrorCode(extractErrorCode(candidate)) === "pre-connect")) return true;
161
+ return !candidates.some((candidate) => candidate instanceof TypeError && candidate.message === "fetch failed");
152
162
  }
153
163
  function markLineVisibleDeliveryError(error) {
154
164
  const deliveryError = toLineDeliveryError(error);
@@ -176,6 +186,7 @@ async function deliverLineAutoReply(params) {
176
186
  visibleReplySent = true;
177
187
  return result;
178
188
  } catch (error) {
189
+ if (isChannelPartialDeliveryError(error)) visibleReplySent = true;
179
190
  if (visibleReplySent) throw markLineVisibleDeliveryError(error);
180
191
  throw error;
181
192
  }
@@ -192,7 +203,7 @@ async function deliverLineAutoReply(params) {
192
203
  accountId
193
204
  }));
194
205
  } catch (error) {
195
- if (typeof error === "object" && error !== null) failedPushSegments.set(error, {
206
+ if (!isChannelPartialDeliveryError(error) && typeof error === "object" && error !== null) failedPushSegments.set(error, {
196
207
  allowFailedBatchTextRecovery,
197
208
  failedBatch: batch,
198
209
  unattemptedTail: [...messages.slice(i + batch.length), ...externalTail]
@@ -212,6 +223,7 @@ async function deliverLineAutoReply(params) {
212
223
  accountId
213
224
  });
214
225
  } catch (err) {
226
+ if (isChannelPartialDeliveryError(err) || !canFallbackAfterLineReplyFailure(err)) throw err;
215
227
  deps.onReplyError?.(err);
216
228
  await pushLineMessages(replyBatch, getLineHttpError(err)?.status === 400, remaining.slice(replyBatch.length));
217
229
  } finally {
@@ -223,19 +235,26 @@ async function deliverLineAutoReply(params) {
223
235
  };
224
236
  const richMessages = [];
225
237
  const hasQuickReplies = Boolean(lineData.quickReplies?.length);
226
- if (lineData.flexMessage) richMessages.push(deps.createFlexMessage(truncateUtf16Safe(lineData.flexMessage.altText, 400), lineData.flexMessage.contents));
238
+ if (lineData.flexMessage) richMessages.push(deps.createFlexMessage(lineData.flexMessage.altText, lineData.flexMessage.contents));
227
239
  if (lineData.templateMessage) {
228
240
  const templateMsg = deps.buildTemplateMessageFromPayload(lineData.templateMessage);
229
241
  if (templateMsg) richMessages.push(templateMsg);
230
242
  }
231
- if (lineData.location) richMessages.push(deps.createLocationMessage(lineData.location));
243
+ if (lineData.location) {
244
+ const locationMessage = deps.createLocationMessage(lineData.location);
245
+ if (locationMessage) richMessages.push(locationMessage);
246
+ }
232
247
  const visibleText = payload.text ? sanitizeAssistantVisibleText(payload.text) : "";
233
248
  const processed = visibleText ? deps.processLineMessage(visibleText) : {
234
249
  text: "",
235
250
  flexMessages: []
236
251
  };
237
- for (const flexMsg of processed.flexMessages) richMessages.push(deps.createFlexMessage(truncateUtf16Safe(flexMsg.altText, 400), flexMsg.contents));
238
- const chunks = processed.text ? deps.chunkMarkdownText(processed.text, textLimit) : [];
252
+ if (!processed.segments) for (const flexMsg of processed.flexMessages) richMessages.push(deps.createFlexMessage(flexMsg.altText, flexMsg.contents));
253
+ const orderedMessages = processed.segments?.flatMap((segment) => segment.type === "flex" ? [deps.createFlexMessage(segment.message.altText, segment.message.contents)] : deps.chunkMarkdownText(segment.text, textLimit).map((text) => ({
254
+ type: "text",
255
+ text
256
+ })));
257
+ const chunks = orderedMessages ? orderedMessages.flatMap((message) => message.type === "text" ? [message.text] : []) : processed.text ? deps.chunkMarkdownText(processed.text, textLimit) : [];
239
258
  const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
240
259
  const mediaOpts = {
241
260
  mediaKind: hasLineSpecificMediaOptions(lineData) ? lineData.mediaKind : "image",
@@ -264,14 +283,14 @@ async function deliverLineAutoReply(params) {
264
283
  text: buildLineQuickReplyFallbackText(lineData.quickReplies)
265
284
  });
266
285
  if (hasQuickReplies) {
267
- const targetMessages = textMessages.length > 0 ? textMessages : richMediaMessages;
286
+ const targetMessages = orderedMessages?.length ? orderedMessages : textMessages.length > 0 ? textMessages : richMediaMessages;
268
287
  const lastIndex = targetMessages.length - 1;
269
288
  targetMessages[lastIndex] = {
270
289
  ...expectDefined(targetMessages[lastIndex], "last LINE auto-reply message"),
271
290
  quickReply: deps.createQuickReplyItems(lineData.quickReplies)
272
291
  };
273
292
  }
274
- const messages = hasQuickReplies ? [...richMediaMessages, ...textMessages] : [...textMessages, ...richMediaMessages];
293
+ const messages = hasQuickReplies ? [...richMediaMessages, ...orderedMessages ?? textMessages] : [...orderedMessages ?? textMessages, ...richMediaMessages];
275
294
  try {
276
295
  await sendLineMessages(messages, true);
277
296
  } catch (err) {
@@ -463,7 +482,8 @@ async function finalizeLineInboundContext(params) {
463
482
  previousTimestamp,
464
483
  envelope: envelopeOptions
465
484
  });
466
- const ctxPayload = buildChannelInboundEventContext({
485
+ const ctxPayload = (params.buildContext ?? buildChannelInboundEventContext)({
486
+ channelIngress: params.channelIngress,
467
487
  channel: "line",
468
488
  accountId: params.route.accountId,
469
489
  messageId: params.messageSid,
@@ -593,6 +613,13 @@ async function buildLineMessageContext(params) {
593
613
  timestamp,
594
614
  messageSid: messageId,
595
615
  commandAuthorized,
616
+ channelIngress: params.resolveChannelIngress ? await params.resolveChannelIngress({
617
+ agentId: route.agentId,
618
+ sessionKey: route.sessionKey,
619
+ messageId,
620
+ inboundEventKind: "user_request"
621
+ }) : params.channelIngress,
622
+ buildContext: params.buildContext,
596
623
  media: mediaFacts,
597
624
  locationContext,
598
625
  verboseLog: {
@@ -649,6 +676,13 @@ async function buildLinePostbackContext(params) {
649
676
  timestamp,
650
677
  messageSid,
651
678
  commandAuthorized,
679
+ channelIngress: params.resolveChannelIngress ? await params.resolveChannelIngress({
680
+ agentId: route.agentId,
681
+ sessionKey: route.sessionKey,
682
+ messageId: messageSid,
683
+ inboundEventKind: "user_request"
684
+ }) : params.channelIngress,
685
+ buildContext: params.buildContext,
652
686
  media: [],
653
687
  verboseLog: { kind: "postback" }
654
688
  });
@@ -757,6 +791,7 @@ async function sendLinePairingReply(params) {
757
791
  return;
758
792
  } catch (err) {
759
793
  logVerbose(`line pairing reply failed for ${senderId}: ${String(err)}`);
794
+ if (isChannelPartialDeliveryError(err)) return;
760
795
  }
761
796
  try {
762
797
  await pushMessageLine(`line:${senderId}`, text, {
@@ -814,7 +849,7 @@ async function shouldProcessLineEvent(event, context) {
814
849
  hasAnyMention: hasAnyLineMention(event.message)
815
850
  };
816
851
  })();
817
- const access = await resolveStableChannelMessageIngress({
852
+ const resolveAccess = async (contextBinding) => await resolveStableChannelMessageIngress({
818
853
  channelId: "line",
819
854
  accountId: account.accountId,
820
855
  identity: {
@@ -830,6 +865,7 @@ async function shouldProcessLineEvent(event, context) {
830
865
  kind: isGroup ? "group" : "direct",
831
866
  id: (groupId ?? roomId ?? senderId) || "unknown"
832
867
  },
868
+ ...contextBinding ? { contextBinding } : {},
833
869
  ...isGroup && groupConfig?.enabled === false ? { route: {
834
870
  id: "line:group-config",
835
871
  enabled: false
@@ -857,13 +893,17 @@ async function shouldProcessLineEvent(event, context) {
857
893
  groupOwnerAllowFrom: "none"
858
894
  }
859
895
  });
896
+ const access = await resolveAccess();
860
897
  warnMissingProviderGroupPolicyFallbackOnce({
861
898
  providerMissingFallbackApplied,
862
899
  providerKey: "line",
863
900
  accountId: account.accountId,
864
901
  log: (message) => logVerbose(message)
865
902
  });
866
- if (access.senderAccess.decision === "allow" && (access.ingress.admission === "dispatch" || access.ingress.admission === "observe" || access.ingress.admission === "skip")) return access;
903
+ if (access.senderAccess.decision === "allow" && (access.ingress.admission === "dispatch" || access.ingress.admission === "observe" || access.ingress.admission === "skip")) return {
904
+ access,
905
+ resolveBoundAccess: resolveAccess
906
+ };
867
907
  if (access.senderAccess.decision === "allow") {
868
908
  logVerbose(`Blocked line event (${access.ingress.reasonCode})`);
869
909
  return null;
@@ -934,7 +974,7 @@ async function handleMessageEvent(event, context) {
934
974
  const decision = await shouldProcessLineEvent(event, context);
935
975
  if (!decision) return;
936
976
  const { isGroup, groupId, roomId } = getLineSourceInfo(event.source);
937
- if (isGroup && decision.activationAccess.shouldSkip) {
977
+ if (isGroup && decision.access.activationAccess.shouldSkip) {
938
978
  const rawText = message.type === "text" ? message.text : "";
939
979
  const sourceInfo = getLineSourceInfo(event.source);
940
980
  logVerbose(`line: skipping group message (requireMention, not mentioned)`);
@@ -976,8 +1016,10 @@ async function handleMessageEvent(event, context) {
976
1016
  mediaUnavailable,
977
1017
  cfg,
978
1018
  account,
979
- commandAuthorized: decision.commandAccess.authorized,
980
- inboundHistory: historyReservation.inboundHistory
1019
+ commandAuthorized: decision.access.commandAccess.authorized,
1020
+ resolveChannelIngress: decision.resolveBoundAccess,
1021
+ inboundHistory: historyReservation.inboundHistory,
1022
+ buildContext: context.buildContext
981
1023
  });
982
1024
  if (!messageContext) {
983
1025
  logVerbose("line: skipping empty message");
@@ -1014,7 +1056,9 @@ async function handlePostbackEvent(event, context) {
1014
1056
  event,
1015
1057
  cfg: context.cfg,
1016
1058
  account: context.account,
1017
- commandAuthorized: decision.commandAccess.authorized
1059
+ commandAuthorized: decision.access.commandAccess.authorized,
1060
+ resolveChannelIngress: decision.resolveBoundAccess,
1061
+ buildContext: context.buildContext
1018
1062
  });
1019
1063
  if (!postbackContext) return;
1020
1064
  await context.processMessage(postbackContext, context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {});
@@ -1027,7 +1071,7 @@ async function handleLineWebhookEvents(events, context) {
1027
1071
  context.runtime.error?.(danger(`line: event handler failed: ${String(err)}`));
1028
1072
  firstError ??= err;
1029
1073
  }
1030
- if (firstError) throw toLintErrorObject(firstError, "Non-Error thrown");
1074
+ if (firstError) throw toErrorObject(firstError, "Non-Error thrown");
1031
1075
  }
1032
1076
  async function handleLineWebhookEvent(event, context) {
1033
1077
  switch (event.type) {
@@ -1052,13 +1096,6 @@ async function handleLineWebhookEvent(event, context) {
1052
1096
  default: logVerbose(`line: unhandled event type: ${event.type}`);
1053
1097
  }
1054
1098
  }
1055
- function toLintErrorObject(value, fallbackMessage) {
1056
- if (value instanceof Error) return value;
1057
- if (typeof value === "string") return new Error(value);
1058
- const error = new Error(fallbackMessage, { cause: value });
1059
- if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value);
1060
- return error;
1061
- }
1062
1099
  //#endregion
1063
1100
  //#region extensions/line/src/webhook-spool.ts
1064
1101
  const LINE_WEBHOOK_SPOOL_VERSION = 1;
@@ -1112,9 +1149,6 @@ function parseStoredEvent(rawEvent) {
1112
1149
  }
1113
1150
  return event;
1114
1151
  }
1115
- function errorText(error) {
1116
- return error instanceof Error ? error.message : String(error);
1117
- }
1118
1152
  function isLineAuthenticationFailure(error) {
1119
1153
  if (!error || typeof error !== "object") return false;
1120
1154
  const status = error.status;
@@ -1184,7 +1218,7 @@ function createLineWebhookSpool(options) {
1184
1218
  handedOff = true;
1185
1219
  if (!acceptsDeferredClaims) {
1186
1220
  Promise.resolve().then(() => boundLifecycle.onAbandoned()).catch((error) => {
1187
- options.runtime.error?.(danger(`line: failed to abandon a late webhook delivery: ${errorText(error)}`));
1221
+ options.runtime.error?.(danger(`line: failed to abandon a late webhook delivery: ${formatErrorMessage(error)}`));
1188
1222
  });
1189
1223
  return;
1190
1224
  }
@@ -1238,14 +1272,14 @@ function createLineWebhookSpool(options) {
1238
1272
  };
1239
1273
  if (isLineAuthenticationFailure(error)) return {
1240
1274
  reason: "authentication-failed",
1241
- message: errorText(error)
1275
+ message: formatErrorMessage(error)
1242
1276
  };
1243
1277
  return null;
1244
1278
  },
1245
1279
  onLog: (message) => options.runtime.error?.(danger(`line: ${message}`))
1246
1280
  },
1247
1281
  createStoppedError: () => /* @__PURE__ */ new Error("LINE webhook spool is stopped."),
1248
- onError: (error) => options.runtime.error?.(danger(`line: webhook spool drain failed: ${errorText(error)}`))
1282
+ onError: (error) => options.runtime.error?.(danger(`line: webhook spool drain failed: ${formatErrorMessage(error)}`))
1249
1283
  });
1250
1284
  let stopTask;
1251
1285
  return {
@@ -1279,6 +1313,7 @@ function createLineWebhookSpool(options) {
1279
1313
  }
1280
1314
  //#endregion
1281
1315
  //#region extensions/line/src/bot.ts
1316
+ const DEFAULT_MEDIA_MAX_MB = 10;
1282
1317
  function createLineBot(opts) {
1283
1318
  const runtime = opts.runtime ?? createNonExitingRuntime();
1284
1319
  const cfg = opts.config ?? getRuntimeConfig();
@@ -1286,7 +1321,7 @@ function createLineBot(opts) {
1286
1321
  cfg,
1287
1322
  accountId: opts.accountId
1288
1323
  });
1289
- const mediaMaxBytes = (opts.mediaMaxMb ?? account.config.mediaMaxMb ?? 10) * 1024 * 1024;
1324
+ const mediaMaxBytes = ([opts.mediaMaxMb, account.config.mediaMaxMb].find((value) => typeof value === "number" && value > 0) ?? DEFAULT_MEDIA_MAX_MB) * 1024 * 1024;
1290
1325
  const processMessage = opts.onMessage ?? (async () => {
1291
1326
  logVerbose("line: no message handler configured");
1292
1327
  });
@@ -1298,6 +1333,7 @@ function createLineBot(opts) {
1298
1333
  cfg,
1299
1334
  account,
1300
1335
  runtime,
1336
+ buildContext: opts.buildContext,
1301
1337
  mediaMaxBytes,
1302
1338
  processMessage,
1303
1339
  ...control.turnAdoptionLifecycle ? { turnAdoptionLifecycle: control.turnAdoptionLifecycle } : {},
@@ -1433,6 +1469,14 @@ function createLineNodeWebhookHandler(params) {
1433
1469
  const lineWebhookInFlightLimiter = createWebhookInFlightLimiter();
1434
1470
  const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 64 * 1024;
1435
1471
  const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS = 5e3;
1472
+ async function registerLineWebhookTarget(params, bot) {
1473
+ try {
1474
+ return registerWebhookTargetWithPluginRoute(params);
1475
+ } catch (error) {
1476
+ await Promise.allSettled([bot.stop()]);
1477
+ throw error;
1478
+ }
1479
+ }
1436
1480
  const lineWebhookTargets = /* @__PURE__ */ new Map();
1437
1481
  function startLineLoadingKeepalive(params) {
1438
1482
  const intervalMs = params.intervalMs ?? 18e3;
@@ -1455,7 +1499,7 @@ function startLineLoadingKeepalive(params) {
1455
1499
  };
1456
1500
  }
1457
1501
  async function monitorLineProvider(opts) {
1458
- const { channelAccessToken, channelSecret, accountId, config, runtime, abortSignal, webhookPath } = opts;
1502
+ const { channelAccessToken, channelSecret, accountId, config, runtime, buildContext, abortSignal, webhookPath, statusSink } = opts;
1459
1503
  const resolvedAccountId = accountId ?? resolveDefaultLineAccountId(config);
1460
1504
  const token = channelAccessToken.trim();
1461
1505
  const secret = channelSecret.trim();
@@ -1466,6 +1510,7 @@ async function monitorLineProvider(opts) {
1466
1510
  channelSecret: secret,
1467
1511
  accountId,
1468
1512
  runtime,
1513
+ buildContext,
1469
1514
  config,
1470
1515
  onMessage: async (ctx, deliveryControl) => {
1471
1516
  if (!ctx) return;
@@ -1575,12 +1620,13 @@ async function monitorLineProvider(opts) {
1575
1620
  }
1576
1621
  });
1577
1622
  const normalizedPath = normalizeWebhookPath(normalizePluginHttpPath(webhookPath, "/line/webhook") ?? "/line/webhook");
1623
+ const webhookRouteKey = canonicalizeWebhookRouteKey(normalizedPath);
1578
1624
  const createScopedLineWebhookHandler = (target) => createLineNodeWebhookHandler({
1579
1625
  channelSecret: target.channelSecret,
1580
1626
  bot: target.bot,
1581
1627
  runtime: target.runtime
1582
1628
  });
1583
- const { unregister: unregisterHttp } = registerWebhookTargetWithPluginRoute({
1629
+ const { unregister: unregisterHttp } = await registerLineWebhookTarget({
1584
1630
  targetsByPath: lineWebhookTargets,
1585
1631
  target: {
1586
1632
  accountId: resolvedAccountId,
@@ -1592,10 +1638,12 @@ async function monitorLineProvider(opts) {
1592
1638
  route: {
1593
1639
  auth: "plugin",
1594
1640
  pluginId: "line",
1641
+ source: "line-webhook",
1595
1642
  accountId: resolvedAccountId,
1596
1643
  log: (msg) => logVerbose(msg),
1644
+ throwOnFailure: true,
1597
1645
  handler: async (req, res) => {
1598
- const targets = lineWebhookTargets.get(normalizedPath) ?? [];
1646
+ const targets = lineWebhookTargets.get(webhookRouteKey) ?? [];
1599
1647
  const firstTarget = targets[0];
1600
1648
  if (req.method !== "POST") {
1601
1649
  if (!firstTarget) {
@@ -1610,7 +1658,7 @@ async function monitorLineProvider(opts) {
1610
1658
  req,
1611
1659
  res,
1612
1660
  inFlightLimiter: lineWebhookInFlightLimiter,
1613
- inFlightKey: `line:${normalizedPath}`
1661
+ inFlightKey: `line:${webhookRouteKey}`
1614
1662
  });
1615
1663
  if (!requestLifecycle.ok) return;
1616
1664
  try {
@@ -1678,8 +1726,9 @@ async function monitorLineProvider(opts) {
1678
1726
  }
1679
1727
  }
1680
1728
  }
1681
- });
1729
+ }, bot);
1682
1730
  logVerbose(`line: registered webhook handler at ${normalizedPath}`);
1731
+ statusSink?.(channelReadyPatch());
1683
1732
  let stopped = false;
1684
1733
  let stopPromise;
1685
1734
  const stopHandler = () => {
@@ -1688,7 +1737,9 @@ async function monitorLineProvider(opts) {
1688
1737
  stopped = true;
1689
1738
  logVerbose(`line: stopping provider for account ${resolvedAccountId}`);
1690
1739
  unregisterHttp();
1691
- stopPromise = bot.stop();
1740
+ stopPromise = bot.stop().finally(() => {
1741
+ statusSink?.(channelStoppedPatch());
1742
+ });
1692
1743
  return stopPromise;
1693
1744
  };
1694
1745
  const stopOnAbort = () => void stopHandler();
@@ -0,0 +1,2 @@
1
+ import { t as monitorLineProvider } from "./monitor-QEh_ZqyR.js";
2
+ export { monitorLineProvider };
@@ -0,0 +1,3 @@
1
+ import { i as processLineMessage, o as buildTemplateMessageFromPayload } from "./markdown-to-line-D4I-6lm6.js";
2
+ import { a as createQuickReplyItems, f as pushLocationMessage, g as pushTextMessageWithQuickReplies, h as pushTemplateMessage, i as createLocationMessage, m as pushMessagesLine, n as createFlexMessage, p as pushMessageLine, u as pushFlexMessage, v as sendMessageLine } from "./send-CFFr0-rZ.js";
3
+ export { buildTemplateMessageFromPayload, createFlexMessage, createLocationMessage, createQuickReplyItems, processLineMessage, pushFlexMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, sendMessageLine };
@@ -1,4 +1,4 @@
1
- import { a as createEventCard, i as createAgendaCard, n as createDeviceControlCard, r as createMediaPlayerCard, t as createAppleTvRemoteCard } from "./flex-templates-iKJsyCq-.js";
1
+ import { a as createEventCard, i as createAgendaCard, n as createDeviceControlCard, r as createMediaPlayerCard, t as createAppleTvRemoteCard } from "./flex-templates-BBBlNqpV.js";
2
2
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
3
3
  import { DmPolicySchema, GroupPolicySchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
4
4
  import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
@@ -15,6 +15,7 @@ const ThreadBindingsSchema = z.object({
15
15
  }).strict();
16
16
  const LineCommonConfigSchemaBase = z.object({
17
17
  enabled: z.boolean().optional(),
18
+ configWrites: z.boolean().optional(),
18
19
  channelAccessToken: z.string().optional(),
19
20
  channelSecret: z.string().optional(),
20
21
  tokenFile: z.string().optional(),
@@ -100,9 +101,9 @@ function parseLineDirectives(payload) {
100
101
  const lonStr = expectDefined(parts[3], "location longitude field");
101
102
  const latitude = parseStrictFiniteNumber(latStr);
102
103
  const longitude = parseStrictFiniteNumber(lonStr);
103
- if (latitude !== void 0 && longitude !== void 0) lineData.location = {
104
+ if (address && latitude !== void 0 && longitude !== void 0) lineData.location = {
104
105
  title: title || "Location",
105
- address: address || "",
106
+ address,
106
107
  latitude,
107
108
  longitude
108
109
  };
@@ -1,10 +1,11 @@
1
1
  import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BqEVsDoB.js";
2
2
  import { a as resolveLineGroupConfigEntry, i as resolveExactLineGroupConfigKey, o as resolveLineGroupLookupIds, r as setLineRuntime, s as resolveLineGroupsConfig } from "./quick-reply-fallback-D86FwAfr.js";
3
- import { i as LineConfigSchema, n as parseLineDirectives, r as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-D5-dHB8b.js";
4
- import { _ as postbackAction, a as createEventCard, c as createCarousel, d as createListCard, f as createNotificationBubble, i as createAgendaCard, l as createImageCard, m as messageAction, n as createDeviceControlCard, o as createReceiptCard, p as datetimePickerAction, r as createMediaPlayerCard, s as createActionCard, t as createAppleTvRemoteCard, u as createInfoCard, v as uriAction } from "./flex-templates-iKJsyCq-.js";
5
- import { A as createConfirmTemplate, C as sendMessageLine, D as createButtonMenu, E as buildTemplateMessageFromPayload, F as createTemplateCarousel, I as createYesNoConfirm, L as toFlexMessage, M as createImageCarouselColumn, N as createLinkMenu, O as createButtonTemplate, P as createProductCarousel, S as replyMessageLine, T as resolveLineChannelAccessToken, _ as pushLocationMessage, a as stripMarkdown, b as pushTemplateMessage, c as createImageMessage, d as createTextMessageWithQuickReplies, f as createVideoMessage, g as pushImageMessage, h as pushFlexMessage, i as processLineMessage, j as createImageCarousel, k as createCarouselColumn, l as createLocationMessage, m as getUserProfile, n as convertTableToFlexBubble, o as createAudioMessage, p as getUserDisplayName, r as hasMarkdownToConvert, s as createFlexMessage, t as convertCodeBlockToFlexBubble, u as createQuickReplyItems, v as pushMessageLine, w as showLoadingAnimation, x as pushTextMessageWithQuickReplies, y as pushMessagesLine } from "./markdown-to-line-DMDQTMhx.js";
6
- import { a as validateLineSignature, c as normalizeAllowFrom, i as parseLineWebhookBody, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider } from "./monitor-DXEodYFJ.js";
3
+ import { i as LineConfigSchema, n as parseLineDirectives, r as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-BOPGHVPo.js";
4
+ import { a as createEventCard, c as createImageCard, d as datetimePickerAction, f as messageAction, g as uriAction, h as postbackAction, i as createAgendaCard, l as createInfoCard, n as createDeviceControlCard, o as createReceiptCard, r as createMediaPlayerCard, s as createActionCard, t as createAppleTvRemoteCard, u as createListCard } from "./flex-templates-BBBlNqpV.js";
5
+ import { a as stripMarkdown, c as createCarouselColumn, d as toFlexMessage, i as processLineMessage, l as createConfirmTemplate, n as convertTableToFlexBubble, o as buildTemplateMessageFromPayload, r as hasMarkdownToConvert, s as createButtonTemplate, t as convertCodeBlockToFlexBubble, u as createTemplateCarousel } from "./markdown-to-line-D4I-6lm6.js";
6
+ import { a as validateLineSignature, c as normalizeAllowFrom, i as parseLineWebhookBody, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider } from "./monitor-QEh_ZqyR.js";
7
7
  import { t as probeLineBot } from "./probe-CM03wo6r.js";
8
+ import { _ as replyMessageLine, a as createQuickReplyItems, b as resolveLineChannelAccessToken, c as getUserDisplayName, d as pushImageMessage, f as pushLocationMessage, g as pushTextMessageWithQuickReplies, h as pushTemplateMessage, i as createLocationMessage, l as getUserProfile, m as pushMessagesLine, n as createFlexMessage, o as createTextMessageWithQuickReplies, p as pushMessageLine, r as createImageMessage, s as createVideoMessage, t as createAudioMessage, u as pushFlexMessage, v as sendMessageLine, y as showLoadingAnimation } from "./send-CFFr0-rZ.js";
8
9
  import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
9
10
  import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
10
11
  import { DEFAULT_ACCOUNT_ID, formatDocsLink, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
@@ -258,4 +259,4 @@ function createDefaultMenuConfig() {
258
259
  };
259
260
  }
260
261
  //#endregion
261
- export { DEFAULT_ACCOUNT_ID, LineChannelConfigSchema, LineConfigSchema, buildChannelConfigSchema, buildComputedAccountStatusSnapshot, buildTemplateMessageFromPayload, buildTokenChannelStatusSummary, cancelDefaultRichMenu, clearAccountEntryFields, convertCodeBlockToFlexBubble, convertTableToFlexBubble, createActionCard, createAgendaCard, createAppleTvRemoteCard, createAudioMessage, createButtonMenu, createButtonTemplate, createCarousel, createCarouselColumn, createConfirmTemplate, createDefaultMenuConfig, createDeviceControlCard, createEventCard, createFlexMessage, createGridLayout, createImageCard, createImageCarousel, createImageCarouselColumn, createImageMessage, createInfoCard, createLineNodeWebhookHandler, createLineWebhookMiddleware, createLinkMenu, createListCard, createLocationMessage, createMediaPlayerCard, createNotificationBubble, createProductCarousel, createQuickReplyItems, createReceiptCard, createRichMenu, createRichMenuAlias, createTemplateCarousel, createTextMessageWithQuickReplies, createVideoMessage, createYesNoConfirm, datetimePickerAction, deleteRichMenu, deleteRichMenuAlias, downloadLineMedia, firstDefined, formatDocsLink, getDefaultRichMenuId, getRichMenu, getRichMenuIdOfUser, getRichMenuList, getUserDisplayName, getUserProfile, hasLineDirectives, hasMarkdownToConvert, listLineAccountIds, messageAction, monitorLineProvider, normalizeAccountId, normalizeAllowFrom, parseLineDirectives, parseLineWebhookBody, postbackAction, probeLineBot, processLineMessage, pushFlexMessage, pushImageMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, readLineWebhookRequestBody, replyMessageLine, resolveDefaultLineAccountId, resolveExactLineGroupConfigKey, resolveLineAccount, resolveLineChannelAccessToken, resolveLineGroupConfigEntry, resolveLineGroupLookupIds, resolveLineGroupsConfig, sendMessageLine, setDefaultRichMenu, setLineRuntime, setSetupChannelEnabled, showLoadingAnimation, splitSetupEntries, startLineWebhook, stripMarkdown, toFlexMessage, uploadRichMenuImage, uriAction, validateLineSignature };
262
+ export { DEFAULT_ACCOUNT_ID, LineChannelConfigSchema, LineConfigSchema, buildChannelConfigSchema, buildComputedAccountStatusSnapshot, buildTemplateMessageFromPayload, buildTokenChannelStatusSummary, cancelDefaultRichMenu, clearAccountEntryFields, convertCodeBlockToFlexBubble, convertTableToFlexBubble, createActionCard, createAgendaCard, createAppleTvRemoteCard, createAudioMessage, createButtonTemplate, createCarouselColumn, createConfirmTemplate, createDefaultMenuConfig, createDeviceControlCard, createEventCard, createFlexMessage, createGridLayout, createImageCard, createImageMessage, createInfoCard, createLineNodeWebhookHandler, createLineWebhookMiddleware, createListCard, createLocationMessage, createMediaPlayerCard, createQuickReplyItems, createReceiptCard, createRichMenu, createRichMenuAlias, createTemplateCarousel, createTextMessageWithQuickReplies, createVideoMessage, datetimePickerAction, deleteRichMenu, deleteRichMenuAlias, downloadLineMedia, firstDefined, formatDocsLink, getDefaultRichMenuId, getRichMenu, getRichMenuIdOfUser, getRichMenuList, getUserDisplayName, getUserProfile, hasLineDirectives, hasMarkdownToConvert, listLineAccountIds, messageAction, monitorLineProvider, normalizeAccountId, normalizeAllowFrom, parseLineDirectives, parseLineWebhookBody, postbackAction, probeLineBot, processLineMessage, pushFlexMessage, pushImageMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, readLineWebhookRequestBody, replyMessageLine, resolveDefaultLineAccountId, resolveExactLineGroupConfigKey, resolveLineAccount, resolveLineChannelAccessToken, resolveLineGroupConfigEntry, resolveLineGroupLookupIds, resolveLineGroupsConfig, sendMessageLine, setDefaultRichMenu, setLineRuntime, setSetupChannelEnabled, showLoadingAnimation, splitSetupEntries, startLineWebhook, stripMarkdown, toFlexMessage, uploadRichMenuImage, uriAction, validateLineSignature };