@openclaw/line 2026.8.1-beta.3 → 2026.8.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.
Files changed (153) hide show
  1. package/dist/account-helpers-BSUyZwLm.js +12 -0
  2. package/dist/api.js +2 -2
  3. package/dist/{card-command-CyDSm-OU.js → card-command-BywtX77U.js} +66 -29
  4. package/dist/{channel-DmoKZXmG.js → channel-DpMgRaxl.js} +52 -349
  5. package/dist/channel-plugin-api.js +1 -1
  6. package/dist/channel.runtime-Bty5U_1N.js +4 -0
  7. package/dist/doctor-contract-api.js +141 -0
  8. package/dist/index.js +1 -1
  9. package/dist/{markdown-to-line-rEaras_0.js → markdown-to-line-CG9Jy22s.js} +29 -25
  10. package/dist/{monitor-BYXS_B6r.js → monitor-Ch6TIatG.js} +261 -166
  11. package/dist/monitor.runtime-Czc3NxNV.js +2 -0
  12. package/dist/outbound.runtime-CyleVzFR.js +3 -0
  13. package/dist/probe.runtime-DTxBWvKz.js +2 -0
  14. package/dist/rich-messages-t_zRL24p.js +760 -0
  15. package/dist/runtime-api.js +10 -11
  16. package/dist/{send-BJCeiiOB.js → send-B-K2B6au.js} +138 -104
  17. package/dist/{send-receipt-DKZzgHMO.js → send-retry-CUvrarfE.js} +137 -413
  18. package/dist/setup-api.js +1 -1
  19. package/dist/{setup-surface-W0Dbgu58.js → setup-surface-DSJ92oJ5.js} +24 -17
  20. package/dist/webhook-spool-contract-CNe2h1I2.js +58 -0
  21. package/node_modules/typebox/build/format/date.d.mts +1 -1
  22. package/node_modules/typebox/build/format/date.mjs +1 -1
  23. package/node_modules/typebox/build/format/date_time.d.mts +2 -2
  24. package/node_modules/typebox/build/format/date_time.mjs +3 -3
  25. package/node_modules/typebox/build/format/email.d.mts +1 -1
  26. package/node_modules/typebox/build/format/email.mjs +2 -2
  27. package/node_modules/typebox/build/format/hostname.mjs +2 -10
  28. package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
  29. package/node_modules/typebox/build/format/idn_email.mjs +3 -3
  30. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
  31. package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
  32. package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
  33. package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
  34. package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
  35. package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
  36. package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
  37. package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
  38. package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
  39. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +23 -0
  40. package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
  41. package/node_modules/typebox/build/format/idna/index.mjs +2 -0
  42. package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
  43. package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
  44. package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
  45. package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
  46. package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
  47. package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
  48. package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
  49. package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
  50. package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
  51. package/node_modules/typebox/build/format/ipv4.mjs +2 -32
  52. package/node_modules/typebox/build/format/ipv6.mjs +2 -61
  53. package/node_modules/typebox/build/format/iri.d.mts +2 -2
  54. package/node_modules/typebox/build/format/iri.mjs +26 -8
  55. package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
  56. package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
  57. package/node_modules/typebox/build/format/regex.d.mts +1 -2
  58. package/node_modules/typebox/build/format/regex.mjs +2 -6
  59. package/node_modules/typebox/build/format/time.d.mts +2 -2
  60. package/node_modules/typebox/build/format/time.mjs +19 -14
  61. package/node_modules/typebox/build/format/uri.d.mts +1 -1
  62. package/node_modules/typebox/build/format/uri.mjs +3 -130
  63. package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
  64. package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
  65. package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
  66. package/node_modules/typebox/build/format/uri_template.mjs +4 -5
  67. package/node_modules/typebox/build/format/url.d.mts +3 -3
  68. package/node_modules/typebox/build/format/url.mjs +4 -5
  69. package/node_modules/typebox/build/guard/emit.d.mts +3 -1
  70. package/node_modules/typebox/build/guard/emit.mjs +12 -5
  71. package/node_modules/typebox/build/guard/guard.d.mts +8 -2
  72. package/node_modules/typebox/build/guard/guard.mjs +25 -4
  73. package/node_modules/typebox/build/guard/string.d.mts +2 -2
  74. package/node_modules/typebox/build/guard/string.mjs +7 -7
  75. package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
  76. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
  77. package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
  78. package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
  79. package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
  80. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
  81. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
  82. package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
  83. package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
  84. package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
  85. package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
  86. package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
  87. package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
  88. package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
  89. package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
  90. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
  91. package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
  92. package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
  93. package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
  94. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
  95. package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
  96. package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
  97. package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
  98. package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
  99. package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
  100. package/node_modules/typebox/build/schema/types/index.mjs +0 -2
  101. package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
  102. package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
  103. package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
  104. package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
  105. package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
  106. package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
  107. package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
  108. package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
  109. package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
  110. package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
  111. package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
  112. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
  113. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
  114. package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
  115. package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
  116. package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
  117. package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
  118. package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
  119. package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
  120. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
  121. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
  122. package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
  123. package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
  124. package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
  125. package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
  126. package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
  127. package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
  128. package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
  129. package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
  130. package/node_modules/typebox/build/type/script/parser.mjs +27 -23
  131. package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
  132. package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
  133. package/node_modules/typebox/package.json +27 -27
  134. package/node_modules/typebox/readme.md +79 -82
  135. package/openclaw.plugin.json +26 -2
  136. package/package.json +6 -6
  137. package/dist/channel.runtime-qrUV6wPE.js +0 -4
  138. package/dist/config-schema-Be3azjB_.js +0 -47
  139. package/dist/monitor.runtime-B2z_5ugU.js +0 -2
  140. package/dist/outbound.runtime-DDTeqaBW.js +0 -3
  141. package/dist/probe.runtime-D13ZtXsd.js +0 -2
  142. package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
  143. package/node_modules/typebox/build/format/_idna.d.mts +0 -2
  144. package/node_modules/typebox/build/format/_idna.mjs +0 -246
  145. package/node_modules/typebox/build/format/_puny.d.mts +0 -1
  146. package/node_modules/typebox/build/format/_puny.mjs +0 -77
  147. package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
  148. package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
  149. package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
  150. package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
  151. package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
  152. package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
  153. package/dist/{probe-CM03wo6r.js → probe-4gc_YQ2L.js} +1 -1
@@ -1,34 +1,35 @@
1
1
  import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-BqEVsDoB.js";
2
- import { a as resolveLineGroupConfigEntry, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-D86FwAfr.js";
3
- import { v as buildLineMediaMessage, y as hasLineSpecificMediaOptions } from "./send-receipt-DKZzgHMO.js";
4
- import { i as processLineMessage, o as buildTemplateMessageFromPayload } from "./markdown-to-line-rEaras_0.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-BJCeiiOB.js";
2
+ import { f as resolveLineGroupConfigEntry, h as getLineRuntime, i as prepareLineReplyPayload, n as createLineQuickReply, u as buildLineQuickReplyFallbackText } from "./rich-messages-t_zRL24p.js";
3
+ import { n as resolveLineNonDispatchRetryable, t as findLineHttpError, y as buildLineMediaMessage } from "./send-retry-CUvrarfE.js";
4
+ import { a as eventIdFor, n as LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE, o as laneKeyFor, r as LineWebhookPayloadError, t as LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON } from "./webhook-spool-contract-CNe2h1I2.js";
5
+ import { i as processLineMessage, s as buildTemplateMessageFromPayload } from "./markdown-to-line-CG9Jy22s.js";
6
+ import { _ as showLoadingAnimation, a as getLineGroupName, d as pushMessageLine, f as pushMessagesLine, h as replyMessageLine, n as createLocationMessage, o as getUserDisplayName, s as getUserProfile, t as createFlexMessage, v as quotesLineBotMessage } from "./send-B-K2B6au.js";
6
7
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
7
- import { normalizeNullableString, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
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
+ import { normalizeOptionalString, normalizeStringEntries, readNonEmptyStringPreservingWhitespace } from "openclaw/plugin-sdk/string-coerce-runtime";
9
+ import { DEFAULT_INGRESS_ADOPTION_STALL_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressMonitor } from "openclaw/plugin-sdk/channel-outbound";
10
+ import { buildChannelInboundEventContext, buildMentionRegexes, formatInboundEnvelope, formatInboundMediaUnavailableText, formatLocationText, hasFinalInboundReplyDispatch, implicitMentionKindWhen, isChannelPartialDeliveryError, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toInboundMediaFactsWithMetadata, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
11
+ import { collectErrorGraphCandidates, extractErrorCode, formatErrorMessage, readErrorName, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
10
12
  import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
11
13
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
12
14
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
15
+ import { classifyTransientNetworkErrorCode } from "openclaw/plugin-sdk/retry-runtime";
13
16
  import { firstDefined } from "openclaw/plugin-sdk/allow-from";
14
17
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
15
18
  import { MediaFetchError } from "openclaw/plugin-sdk/media-runtime";
16
19
  import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
17
20
  import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal, warn } from "openclaw/plugin-sdk/runtime-env";
18
21
  import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
19
- import { HTTPFetchError } from "@line/bot-sdk";
20
- import { collectErrorGraphCandidates, extractErrorCode, formatErrorMessage, readErrorName, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
21
22
  import crypto from "node:crypto";
22
23
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
23
- import { classifyTransientNetworkErrorCode } from "openclaw/plugin-sdk/retry-runtime";
24
24
  import { channelReadyPatch, channelStoppedPatch } from "openclaw/plugin-sdk/gateway-runtime";
25
25
  import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
26
26
  import { canonicalizeWebhookRouteKey, isRequestBodyLimitError, normalizePluginHttpPath, normalizeWebhookPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
27
27
  import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1, runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
28
28
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
29
29
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
30
- import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
30
+ import { getRuntimeConfig, getRuntimeConfigSnapshot, getRuntimeConfigSourceSnapshot, selectApplicableRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
31
31
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
32
+ import { reportChannelRoomJoin } from "openclaw/plugin-sdk/channel-join-intro-runtime";
32
33
  import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
33
34
  import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolvePairingIdLabel, resolvePinnedMainDmOwnerFromAllowlist, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
34
35
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
@@ -159,11 +160,8 @@ async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10 *
159
160
  function toLineDeliveryError(error) {
160
161
  return error instanceof Error ? error : new Error("LINE message send failed", { cause: error });
161
162
  }
162
- function getLineHttpError(error) {
163
- return collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]).find((candidate) => candidate instanceof HTTPFetchError);
164
- }
165
163
  function canFallbackAfterLineReplyFailure(error) {
166
- const httpError = getLineHttpError(error);
164
+ const httpError = findLineHttpError(error);
167
165
  if (httpError) return httpError.status >= 400 && httpError.status < 500 && httpError.status !== 408;
168
166
  const candidates = collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]);
169
167
  if (candidates.some((candidate) => readErrorName(candidate) === "AbortError" || classifyTransientNetworkErrorCode(extractErrorCode(candidate)) === "ambiguous")) return false;
@@ -235,7 +233,7 @@ async function deliverLineAutoReply(params) {
235
233
  } catch (err) {
236
234
  if (isChannelPartialDeliveryError(err) || !canFallbackAfterLineReplyFailure(err)) throw err;
237
235
  deps.onReplyError?.(err);
238
- await pushLineMessages(replyBatch, getLineHttpError(err)?.status === 400, remaining.slice(replyBatch.length));
236
+ await pushLineMessages(replyBatch, findLineHttpError(err)?.status === 400, remaining.slice(replyBatch.length));
239
237
  } finally {
240
238
  replyTokenUsed = true;
241
239
  }
@@ -244,7 +242,15 @@ async function deliverLineAutoReply(params) {
244
242
  if (remaining.length > 0) await pushLineMessages(remaining, true);
245
243
  };
246
244
  const richMessages = [];
247
- const hasQuickReplies = Boolean(lineData.quickReplies?.length);
245
+ const quickReplyItems = lineData.quickReplyItems?.length ? lineData.quickReplyItems : (lineData.quickReplies ?? []).map((label) => ({
246
+ label,
247
+ action: {
248
+ type: "command",
249
+ command: label
250
+ }
251
+ }));
252
+ const quickReplyLabels = quickReplyItems.map((item) => item.label);
253
+ const hasQuickReplies = quickReplyItems.length > 0;
248
254
  if (lineData.flexMessage) richMessages.push(deps.createFlexMessage(lineData.flexMessage.altText, lineData.flexMessage.contents));
249
255
  if (lineData.templateMessage) {
250
256
  const templateMsg = deps.buildTemplateMessageFromPayload(lineData.templateMessage);
@@ -264,7 +270,7 @@ async function deliverLineAutoReply(params) {
264
270
  const chunks = orderedMessages ? orderedMessages.flatMap((message) => message.type === "text" ? [message.text] : []) : processed.text ? deps.chunkMarkdownText(processed.text, textLimit) : [];
265
271
  const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
266
272
  const mediaOpts = {
267
- mediaKind: hasLineSpecificMediaOptions(lineData) ? lineData.mediaKind : "image",
273
+ mediaKind: lineData.mediaKind,
268
274
  previewImageUrl: lineData.previewImageUrl,
269
275
  durationMs: lineData.durationMs,
270
276
  trackingId: lineData.trackingId
@@ -284,37 +290,46 @@ async function deliverLineAutoReply(params) {
284
290
  type: "text",
285
291
  text
286
292
  }));
287
- const richMediaMessages = [...richMessages, ...mediaMessages];
293
+ const orderedDeliveryMessages = hasQuickReplies && chunks.length === 0 ? void 0 : orderedMessages;
294
+ const richMediaMessages = [
295
+ ...richMessages,
296
+ ...orderedDeliveryMessages ? [] : orderedMessages ?? [],
297
+ ...mediaMessages
298
+ ];
288
299
  if (hasQuickReplies && textMessages.length === 0 && richMediaMessages.length === 0) textMessages.push({
289
300
  type: "text",
290
- text: buildLineQuickReplyFallbackText(lineData.quickReplies)
301
+ text: buildLineQuickReplyFallbackText(quickReplyLabels)
291
302
  });
292
303
  if (hasQuickReplies) {
293
- const targetMessages = orderedMessages?.length ? orderedMessages : textMessages.length > 0 ? textMessages : richMediaMessages;
304
+ const targetMessages = orderedDeliveryMessages?.length ? orderedDeliveryMessages : textMessages.length > 0 ? textMessages : richMediaMessages;
294
305
  const lastIndex = targetMessages.length - 1;
295
306
  targetMessages[lastIndex] = {
296
307
  ...expectDefined(targetMessages[lastIndex], "last LINE auto-reply message"),
297
- quickReply: deps.createQuickReplyItems(lineData.quickReplies)
308
+ quickReply: createLineQuickReply(quickReplyItems)
298
309
  };
299
310
  }
300
- const messages = hasQuickReplies ? [...richMediaMessages, ...orderedMessages ?? textMessages] : [...orderedMessages ?? textMessages, ...richMediaMessages];
311
+ const messages = hasQuickReplies ? [...richMediaMessages, ...orderedDeliveryMessages ?? textMessages] : [...orderedDeliveryMessages ?? textMessages, ...richMediaMessages];
301
312
  try {
302
313
  await sendLineMessages(messages, true);
303
314
  } catch (err) {
304
315
  deliveryError ??= err;
305
316
  const failedSegment = typeof err === "object" && err !== null ? failedPushSegments.get(err) : void 0;
306
- const httpError = getLineHttpError(err);
317
+ const httpError = findLineHttpError(err);
307
318
  const retryCandidates = failedSegment ? [...failedSegment.allowFailedBatchTextRecovery ? failedSegment.failedBatch : [], ...failedSegment.unattemptedTail] : [];
308
319
  const retryTextMessages = retryCandidates.filter((message) => message.type === "text");
309
320
  const quickRepliesNeedCarrier = hasQuickReplies && retryCandidates.some((message) => "quickReply" in message);
310
321
  const retryMessages = retryTextMessages.length > 0 ? retryTextMessages : quickRepliesNeedCarrier ? [{
311
322
  type: "text",
312
- text: buildLineQuickReplyFallbackText(lineData.quickReplies),
313
- quickReply: deps.createQuickReplyItems(lineData.quickReplies)
323
+ text: buildLineQuickReplyFallbackText(quickReplyLabels),
324
+ quickReply: createLineQuickReply(quickReplyItems)
314
325
  }] : [];
315
- if (retryMessages.length > 0 && failedSegment?.failedBatch.some((message) => message.type !== "text") && httpError?.status === 400) try {
316
- await sendLineMessages(retryMessages, false);
317
- } catch {}
326
+ if (retryMessages.length > 0 && failedSegment?.failedBatch.some((message) => message.type !== "text") && httpError?.status === 400 && resolveLineNonDispatchRetryable(err) !== void 0) {
327
+ const lastRetryMessage = retryMessages.at(-1);
328
+ if (quickRepliesNeedCarrier && lastRetryMessage && !lastRetryMessage.quickReply) lastRetryMessage.quickReply = createLineQuickReply(quickReplyItems);
329
+ try {
330
+ await sendLineMessages(retryMessages, false);
331
+ } catch {}
332
+ }
318
333
  }
319
334
  if (deliveryError !== void 0) {
320
335
  if (!visibleReplySent) throw toLineDeliveryError(deliveryError);
@@ -332,6 +347,44 @@ async function deliverLineAutoReply(params) {
332
347
  };
333
348
  }
334
349
  //#endregion
350
+ //#region extensions/line/src/mentions.ts
351
+ function getLineMentionees(message) {
352
+ return message.type === "text" ? message.mention?.mentionees ?? [] : [];
353
+ }
354
+ function addressesLineBot(mentionee) {
355
+ return mentionee.type === "all" || mentionee.isSelf === true;
356
+ }
357
+ function isLineBotMentioned(message) {
358
+ return getLineMentionees(message).some(addressesLineBot);
359
+ }
360
+ function hasAnyLineMention(message) {
361
+ return getLineMentionees(message).length > 0;
362
+ }
363
+ /**
364
+ * Text to interpret as a command, with the mention that addressed the bot removed.
365
+ *
366
+ * LINE writes a mention as the plain channel display name, so no pattern can
367
+ * tell it apart from a member's name; `mentionees[].index`/`length` (UTF-16
368
+ * code units, the unit LINE counts text in) is the only authoritative marker.
369
+ * Leaving it in place makes a group `@<bot> /status` parse as prose, and group
370
+ * chats require the mention to reach the bot at all.
371
+ */
372
+ function resolveLineMentionStrippedText(message) {
373
+ const text = message.type === "text" ? message.text : "";
374
+ const spans = getLineMentionees(message).filter(addressesLineBot).map((mentionee) => ({
375
+ start: mentionee.index,
376
+ end: mentionee.index + mentionee.length
377
+ })).toSorted((left, right) => left.start - right.start);
378
+ let stripped = "";
379
+ let cursor = 0;
380
+ for (const span of spans) {
381
+ stripped += text.slice(cursor, span.start);
382
+ if (/\S$/u.test(stripped) && /^\S/u.test(text.slice(span.end))) stripped += " ";
383
+ cursor = Math.max(cursor, span.end);
384
+ }
385
+ return `${stripped}${text.slice(cursor)}`.trim();
386
+ }
387
+ //#endregion
335
388
  //#region extensions/line/src/bot-message-context.ts
336
389
  function getLineSourceInfo(source) {
337
390
  if (!source) return {
@@ -416,27 +469,15 @@ async function resolveLineInboundRoute(params) {
416
469
  route
417
470
  };
418
471
  }
419
- const STICKER_PACKAGES = {
420
- "1": "Moon & James",
421
- "2": "Cony & Brown",
422
- "3": "Brown & Friends",
423
- "4": "Moon Special",
424
- "789": "LINE Characters",
425
- "6136": "Cony's Happy Life",
426
- "6325": "Brown's Life",
427
- "6359": "Choco",
428
- "6362": "Sally",
429
- "6370": "Edward",
430
- "11537": "Cony",
431
- "11538": "Brown",
432
- "11539": "Moon"
433
- };
434
- function describeStickerKeywords(sticker) {
435
- const keywords = sticker.keywords;
436
- if (keywords && keywords.length > 0) return keywords.slice(0, 3).join(", ");
437
- const stickerText = sticker.text;
438
- if (stickerText) return stickerText;
439
- return "";
472
+ /**
473
+ * Describe a sticker from what its webhook actually carries: LINE sends up to
474
+ * 15 keywords for the sticker, and a message sticker also carries the sender's
475
+ * own text. The package name is not among those facts and cannot be derived
476
+ * from the package id, so it is not part of the description.
477
+ */
478
+ function describeLineSticker(sticker) {
479
+ const description = readNonEmptyStringPreservingWhitespace(sticker.text) ?? normalizeStringEntries(sticker.keywords ?? []).slice(0, 3).join(", ");
480
+ return description ? `[Sent a sticker: ${description}]` : "[Sent a sticker]";
440
481
  }
441
482
  function extractMessageText(message) {
442
483
  if (message.type === "text") return message.text;
@@ -449,13 +490,7 @@ function extractMessageText(message) {
449
490
  address: loc.address
450
491
  }) ?? "";
451
492
  }
452
- if (message.type === "sticker") {
453
- const sticker = message;
454
- const packageName = STICKER_PACKAGES[sticker.packageId] ?? "sticker";
455
- const keywords = describeStickerKeywords(sticker);
456
- if (keywords) return `[Sent a ${packageName} sticker: ${keywords}]`;
457
- return `[Sent a ${packageName} sticker]`;
458
- }
493
+ if (message.type === "sticker") return describeLineSticker(message);
459
494
  return "";
460
495
  }
461
496
  function extractNativeMediaKind(message) {
@@ -469,9 +504,23 @@ function extractNativeMediaKind(message) {
469
504
  }
470
505
  async function finalizeLineInboundContext(params) {
471
506
  const senderId = params.source.userId ?? "unknown";
472
- const senderLabel = params.source.userId ? `user:${params.source.userId}` : "unknown";
473
- const conversationLabel = params.source.isGroup ? params.source.groupId ? `group:${params.source.groupId}` : params.source.roomId ? `room:${params.source.roomId}` : "unknown-group" : senderLabel;
507
+ const clientOpts = {
508
+ cfg: params.cfg,
509
+ accountId: params.account.accountId,
510
+ channelAccessToken: params.account.channelAccessToken
511
+ };
512
+ const [senderName, groupName] = await Promise.all([params.source.userId ? getUserProfile(params.source.userId, {
513
+ ...clientOpts,
514
+ groupId: params.source.groupId,
515
+ roomId: params.source.roomId
516
+ }).then((profile) => profile?.displayName) : void 0, params.source.groupId ? getLineGroupName(params.source.groupId, clientOpts) : void 0]);
517
+ const senderLabel = senderName ?? (params.source.userId ? `user:${params.source.userId}` : "unknown");
518
+ const conversationLabel = params.source.isGroup ? groupName ?? (params.source.groupId ? `group:${params.source.groupId}` : params.source.roomId ? `room:${params.source.roomId}` : "unknown-group") : senderLabel;
474
519
  const address = params.source.groupId ? `line:group:${params.source.groupId}` : params.source.roomId ? `line:room:${params.source.roomId}` : `line:${params.source.userId ?? params.source.peerId}`;
520
+ const groupConfig = params.source.isGroup ? resolveLineGroupConfigEntry(params.account.config.groups, {
521
+ groupId: params.source.groupId,
522
+ roomId: params.source.roomId
523
+ }) : void 0;
475
524
  const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
476
525
  cfg: params.cfg,
477
526
  agentId: params.route.agentId,
@@ -485,7 +534,10 @@ async function finalizeLineInboundContext(params) {
485
534
  timestamp: params.timestamp,
486
535
  body: agentBody,
487
536
  chatType: params.source.isGroup ? "group" : "direct",
488
- sender: { id: senderId },
537
+ sender: {
538
+ id: senderId,
539
+ name: senderName
540
+ },
489
541
  previousTimestamp,
490
542
  envelope: envelopeOptions
491
543
  });
@@ -496,7 +548,10 @@ async function finalizeLineInboundContext(params) {
496
548
  messageId: params.messageSid,
497
549
  timestamp: params.timestamp,
498
550
  from: address,
499
- sender: { id: senderId },
551
+ sender: {
552
+ id: senderId,
553
+ name: senderName
554
+ },
500
555
  conversation: {
501
556
  kind: params.source.isGroup ? "group" : "direct",
502
557
  id: params.source.peerId,
@@ -516,18 +571,15 @@ async function finalizeLineInboundContext(params) {
516
571
  body,
517
572
  bodyForAgent: agentBody,
518
573
  rawBody: params.rawBody,
519
- commandBody: params.rawBody,
574
+ commandBody: params.commandBody ?? params.rawBody,
520
575
  inboundHistory: params.inboundHistory
521
576
  },
522
577
  access: { commands: { authorized: params.commandAuthorized } },
523
578
  media,
524
579
  extra: {
525
580
  ...params.locationContext,
526
- GroupSubject: params.source.isGroup ? params.source.groupId ?? params.source.roomId : void 0,
527
- GroupSystemPrompt: params.source.isGroup ? normalizeOptionalString(resolveLineGroupConfigEntry(params.account.config.groups, {
528
- groupId: params.source.groupId,
529
- roomId: params.source.roomId
530
- })?.systemPrompt) : void 0
581
+ GroupSubject: params.source.isGroup ? groupName ?? params.source.groupId ?? params.source.roomId : void 0,
582
+ GroupSystemPrompt: normalizeOptionalString(groupConfig?.systemPrompt)
531
583
  }
532
584
  });
533
585
  const pinnedMainDmOwner = !params.source.isGroup ? resolvePinnedMainDmOwnerFromAllowlist({
@@ -547,6 +599,7 @@ async function finalizeLineInboundContext(params) {
547
599
  return {
548
600
  ctxPayload,
549
601
  replyToken: params.event.replyToken,
602
+ skillFilter: groupConfig?.skills,
550
603
  turn: {
551
604
  storePath,
552
605
  record: {
@@ -617,6 +670,7 @@ async function buildLineMessageContext(params) {
617
670
  },
618
671
  rawBody,
619
672
  agentBody,
673
+ commandBody: resolveLineMentionStrippedText(message) || rawBody,
620
674
  timestamp,
621
675
  messageSid: messageId,
622
676
  commandAuthorized,
@@ -638,6 +692,7 @@ async function buildLineMessageContext(params) {
638
692
  return {
639
693
  ctxPayload: finalized.ctxPayload,
640
694
  turn: finalized.turn,
695
+ skillFilter: finalized.skillFilter,
641
696
  event,
642
697
  userId,
643
698
  groupId,
@@ -657,14 +712,18 @@ async function buildLinePostbackContext(params) {
657
712
  account
658
713
  });
659
714
  const timestamp = event.timestamp;
660
- const rawData = event.postback?.data?.trim() ?? "";
661
- if (!rawData) return null;
662
- let rawBody = rawData;
663
- if (rawData.includes("line.action=")) {
664
- const searchParams = new URLSearchParams(rawData);
715
+ const rawBody = event.postback?.data?.trim() ?? "";
716
+ if (!rawBody) return null;
717
+ let agentBody = rawBody;
718
+ if (rawBody.includes("line.action=")) {
719
+ const searchParams = new URLSearchParams(rawBody);
665
720
  const action = searchParams.get("line.action") ?? "";
666
721
  const device = searchParams.get("line.device");
667
- rawBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
722
+ agentBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
723
+ }
724
+ for (const [key, value] of Object.entries(event.postback.params ?? {}).toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
725
+ const picked = normalizeOptionalString(value);
726
+ if (picked) agentBody += ` ${key}=${picked}`;
668
727
  }
669
728
  const messageSid = event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`;
670
729
  const finalized = await finalizeLineInboundContext({
@@ -680,6 +739,7 @@ async function buildLinePostbackContext(params) {
680
739
  peerId
681
740
  },
682
741
  rawBody,
742
+ agentBody,
683
743
  timestamp,
684
744
  messageSid,
685
745
  commandAuthorized,
@@ -696,6 +756,7 @@ async function buildLinePostbackContext(params) {
696
756
  return {
697
757
  ctxPayload: finalized.ctxPayload,
698
758
  turn: finalized.turn,
759
+ skillFilter: finalized.skillFilter,
699
760
  event,
700
761
  userId,
701
762
  groupId,
@@ -761,6 +822,13 @@ function resolveLineGroupConfig(params) {
761
822
  roomId: params.roomId
762
823
  });
763
824
  }
825
+ function resolveLineRuntimeGroupPolicy({ cfg, account }) {
826
+ return resolveAllowlistProviderRuntimeGroupPolicy({
827
+ providerConfigPresent: cfg.channels?.line !== void 0,
828
+ groupPolicy: account.config.groupPolicy,
829
+ defaultGroupPolicy: resolveDefaultGroupPolicy(cfg)
830
+ });
831
+ }
764
832
  async function sendLinePairingReply(params) {
765
833
  const { senderId, replyToken, context } = params;
766
834
  const idLabel = (() => {
@@ -824,18 +892,15 @@ async function shouldProcessLineEvent(event, context) {
824
892
  const rawText = resolveEventRawText(event);
825
893
  const requireMention = isGroup ? groupConfig?.requireMention !== false : false;
826
894
  const dmPolicy = account.config.dmPolicy ?? "pairing";
827
- const { groupPolicy: runtimeGroupPolicy, providerMissingFallbackApplied } = resolveAllowlistProviderRuntimeGroupPolicy({
828
- providerConfigPresent: cfg.channels?.line !== void 0,
829
- groupPolicy: account.config.groupPolicy,
830
- defaultGroupPolicy: resolveDefaultGroupPolicy(cfg)
831
- });
895
+ const { groupPolicy: runtimeGroupPolicy, providerMissingFallbackApplied } = resolveLineRuntimeGroupPolicy(context);
832
896
  const groupPolicy = runtimeGroupPolicy === "disabled" ? "disabled" : groupConfig?.allowFrom !== void 0 ? "allowlist" : runtimeGroupPolicy;
833
897
  const groupAllowFrom = normalizeStringEntries(firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom));
834
898
  const mentionFacts = (() => {
835
899
  if (!isGroup || event.type !== "message") return {
836
900
  canDetectMention: false,
837
901
  wasMentioned: false,
838
- hasAnyMention: false
902
+ hasAnyMention: false,
903
+ implicitMentionKinds: []
839
904
  };
840
905
  const peerId = groupId ?? roomId ?? userId ?? "unknown";
841
906
  const { agentId } = resolveAgentRoute({
@@ -853,7 +918,8 @@ async function shouldProcessLineEvent(event, context) {
853
918
  return {
854
919
  canDetectMention: event.message.type === "text",
855
920
  wasMentioned: wasMentionedByNative || wasMentionedByPattern,
856
- hasAnyMention: hasAnyLineMention(event.message)
921
+ hasAnyMention: hasAnyLineMention(event.message),
922
+ implicitMentionKinds: implicitMentionKindWhen("quoted_bot", quotesLineBotMessage(account.accountId, resolveLineQuotedMessageId(event.message)))
857
923
  };
858
924
  })();
859
925
  const resolveAccess = async (contextBinding) => await resolveStableChannelMessageIngress({
@@ -881,7 +947,7 @@ async function shouldProcessLineEvent(event, context) {
881
947
  canDetectMention: mentionFacts.canDetectMention,
882
948
  wasMentioned: mentionFacts.wasMentioned,
883
949
  hasAnyMention: mentionFacts.hasAnyMention,
884
- implicitMentionKinds: []
950
+ implicitMentionKinds: mentionFacts.implicitMentionKinds
885
951
  } : void 0,
886
952
  event: { kind: event.type === "postback" ? "postback" : "message" },
887
953
  dmPolicy,
@@ -955,16 +1021,8 @@ async function shouldProcessLineEvent(event, context) {
955
1021
  logVerbose(`Blocked line sender ${senderId || "unknown"} (dmPolicy: ${account.config.dmPolicy ?? "pairing"})`);
956
1022
  return null;
957
1023
  }
958
- function getLineMentionees(message) {
959
- if (message.type !== "text") return [];
960
- const mentionees = message.mention?.mentionees;
961
- return Array.isArray(mentionees) ? mentionees : [];
962
- }
963
- function isLineBotMentioned(message) {
964
- return getLineMentionees(message).some((m) => m.isSelf === true || m.type === "all");
965
- }
966
- function hasAnyLineMention(message) {
967
- return getLineMentionees(message).length > 0;
1024
+ function resolveLineQuotedMessageId(message) {
1025
+ return message.type === "text" || message.type === "sticker" ? message.quotedMessageId : void 0;
968
1026
  }
969
1027
  function resolveEventRawText(event) {
970
1028
  if (event.type === "message") {
@@ -987,15 +1045,25 @@ async function handleMessageEvent(event, context) {
987
1045
  logVerbose(`line: skipping group message (requireMention, not mentioned)`);
988
1046
  const historyKey = groupId ?? roomId;
989
1047
  const senderId = sourceInfo.userId ?? "unknown";
990
- if (historyKey && context.groupHistories) createChannelHistoryWindow({ historyMap: context.groupHistories }).record({
991
- historyKey,
992
- limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
993
- entry: {
994
- sender: `user:${senderId}`,
995
- body: rawText || `<${message.type}>`,
996
- timestamp: event.timestamp
997
- }
998
- });
1048
+ if (historyKey && context.groupHistories) {
1049
+ const displayName = sourceInfo.userId ? await getUserDisplayName(sourceInfo.userId, {
1050
+ cfg,
1051
+ accountId: account.accountId,
1052
+ channelAccessToken: account.channelAccessToken,
1053
+ groupId,
1054
+ roomId
1055
+ }) : senderId;
1056
+ const sender = displayName === senderId ? senderId : `${displayName} (${senderId})`;
1057
+ createChannelHistoryWindow({ historyMap: context.groupHistories }).record({
1058
+ historyKey,
1059
+ limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
1060
+ entry: {
1061
+ sender,
1062
+ body: rawText || `<${message.type}>`,
1063
+ timestamp: event.timestamp
1064
+ }
1065
+ });
1066
+ }
999
1067
  return;
1000
1068
  }
1001
1069
  const groupHistoryKey = isGroup ? groupId ?? roomId : void 0;
@@ -1014,7 +1082,8 @@ async function handleMessageEvent(event, context) {
1014
1082
  abortSignal?.throwIfAborted();
1015
1083
  allMedia.push({
1016
1084
  path: media.path,
1017
- contentType: media.contentType
1085
+ contentType: media.contentType,
1086
+ ...originalFilename ? { fileName: originalFilename } : {}
1018
1087
  });
1019
1088
  } catch (err) {
1020
1089
  if (abortSignal?.aborted) throw abortSignal.reason;
@@ -1040,7 +1109,10 @@ async function handleMessageEvent(event, context) {
1040
1109
  logVerbose("line: skipping empty message");
1041
1110
  return;
1042
1111
  }
1043
- await processMessage(messageContext, context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {});
1112
+ await processMessage(messageContext, {
1113
+ cfg,
1114
+ ...context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {}
1115
+ });
1044
1116
  historyReservation.commit();
1045
1117
  } finally {
1046
1118
  historyReservation.release();
@@ -1054,9 +1126,47 @@ async function handleUnfollowEvent(event, _context) {
1054
1126
  const { userId } = getLineSourceInfo(event.source);
1055
1127
  logVerbose(`line: user ${userId ?? "unknown"} unfollowed`);
1056
1128
  }
1057
- async function handleJoinEvent(event, _context) {
1058
- const { groupId, roomId } = getLineSourceInfo(event.source);
1129
+ async function handleJoinEvent(event, context) {
1130
+ const { groupId, roomId, isGroup } = getLineSourceInfo(event.source);
1131
+ const conversationId = groupId ?? roomId;
1132
+ if (!isGroup || !conversationId) return;
1059
1133
  logVerbose(`line: bot joined ${groupId ? `group ${groupId}` : `room ${roomId}`}`);
1134
+ const { cfg, account } = context;
1135
+ const groupConfig = resolveLineGroupConfig({
1136
+ config: account.config,
1137
+ groupId,
1138
+ roomId
1139
+ });
1140
+ const roomAllowed = resolveLineRuntimeGroupPolicy(context).groupPolicy !== "disabled" && groupConfig?.enabled !== false;
1141
+ await reportChannelRoomJoin({
1142
+ cfg,
1143
+ channel: "line",
1144
+ accountId: account.accountId,
1145
+ conversationId,
1146
+ deliverTo: conversationId,
1147
+ route: resolveAgentRoute({
1148
+ cfg,
1149
+ channel: "line",
1150
+ accountId: account.accountId,
1151
+ peer: {
1152
+ kind: "group",
1153
+ id: conversationId
1154
+ }
1155
+ }),
1156
+ roomAllowed,
1157
+ resolveRoomContext: async () => {
1158
+ const roomContext = { historyUnavailable: true };
1159
+ const title = groupId ? await getLineGroupName(groupId, {
1160
+ cfg,
1161
+ accountId: account.accountId,
1162
+ channelAccessToken: account.channelAccessToken
1163
+ }) : void 0;
1164
+ return title ? {
1165
+ ...roomContext,
1166
+ title
1167
+ } : roomContext;
1168
+ }
1169
+ });
1060
1170
  }
1061
1171
  async function handleLeaveEvent(event, _context) {
1062
1172
  const { groupId, roomId } = getLineSourceInfo(event.source);
@@ -1076,7 +1186,10 @@ async function handlePostbackEvent(event, context) {
1076
1186
  buildContext: context.buildContext
1077
1187
  });
1078
1188
  if (!postbackContext) return;
1079
- await context.processMessage(postbackContext, context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {});
1189
+ await context.processMessage(postbackContext, {
1190
+ cfg: context.cfg,
1191
+ ...context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {}
1192
+ });
1080
1193
  }
1081
1194
  async function handleLineWebhookEvents(events, context) {
1082
1195
  let firstError;
@@ -1113,12 +1226,10 @@ async function handleLineWebhookEvent(event, context) {
1113
1226
  }
1114
1227
  //#endregion
1115
1228
  //#region extensions/line/src/webhook-spool.ts
1116
- const LINE_WEBHOOK_SPOOL_VERSION = 1;
1117
1229
  const LINE_WEBHOOK_DRAIN_INTERVAL_MS = 500;
1118
1230
  const LINE_WEBHOOK_MAX_CONCURRENT_DELIVERIES = 8;
1119
1231
  const LINE_WEBHOOK_DRAIN_SCAN_LIMIT = 100;
1120
1232
  const LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS = 5e3;
1121
- const LineWebhookPayloadError = createChannelIngressError("LineWebhookPayloadError");
1122
1233
  var LineWebhookTerminalDeliveryError = class extends Error {
1123
1234
  constructor(message, options) {
1124
1235
  super(message, options);
@@ -1126,35 +1237,6 @@ var LineWebhookTerminalDeliveryError = class extends Error {
1126
1237
  this.name = "LineWebhookTerminalDeliveryError";
1127
1238
  }
1128
1239
  };
1129
- /** Message ids preserve the shipped replay-guard keyspace; other events use LINE's delivery id. */
1130
- function eventIdFor(event) {
1131
- if (!event || typeof event !== "object") throw new LineWebhookPayloadError("LINE webhook event must be an object.");
1132
- const candidate = event;
1133
- if (candidate.type === "message") {
1134
- const messageId = normalizeNullableString(candidate.message?.id);
1135
- if (messageId) return `message:${messageId}`;
1136
- }
1137
- const webhookEventId = normalizeNullableString(candidate.webhookEventId);
1138
- if (webhookEventId) return `event:${webhookEventId}`;
1139
- throw new LineWebhookPayloadError("LINE webhook event is missing a stable delivery id.");
1140
- }
1141
- function laneKeyFor(event, eventId) {
1142
- if (!event || typeof event !== "object") return eventId;
1143
- const source = event.source;
1144
- if (source?.type === "group") {
1145
- const groupId = normalizeNullableString(source.groupId);
1146
- if (groupId) return `group:${groupId}`;
1147
- }
1148
- if (source?.type === "room") {
1149
- const roomId = normalizeNullableString(source.roomId);
1150
- if (roomId) return `room:${roomId}`;
1151
- }
1152
- if (source?.type === "user") {
1153
- const userId = normalizeNullableString(source.userId);
1154
- if (userId) return `user:${userId}`;
1155
- }
1156
- return eventId;
1157
- }
1158
1240
  function parseStoredEvent(rawEvent) {
1159
1241
  let event;
1160
1242
  try {
@@ -1194,7 +1276,7 @@ function createLineWebhookSpool(options) {
1194
1276
  };
1195
1277
  },
1196
1278
  payload: {
1197
- version: LINE_WEBHOOK_SPOOL_VERSION,
1279
+ version: 1,
1198
1280
  serialize: ({ event, destination }) => ({
1199
1281
  rawEvent: JSON.stringify(event),
1200
1282
  destination
@@ -1209,7 +1291,7 @@ function createLineWebhookSpool(options) {
1209
1291
  destination: body.destination
1210
1292
  }),
1211
1293
  decode: (payload) => {
1212
- if (typeof payload.rawEvent !== "string" || typeof payload.destination !== "string") throw new LineWebhookPayloadError("LINE webhook spool payload is invalid.");
1294
+ if (typeof payload.rawEvent !== "string" || typeof payload.destination !== "string") throw new LineWebhookPayloadError(LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE);
1213
1295
  return {
1214
1296
  version: payload.version,
1215
1297
  body: {
@@ -1218,7 +1300,7 @@ function createLineWebhookSpool(options) {
1218
1300
  }
1219
1301
  };
1220
1302
  },
1221
- createClaimError: (kind) => new LineWebhookPayloadError(kind === "invalid-version" ? "LINE webhook spool payload is invalid." : "LINE webhook event identity changed after durable admission.")
1303
+ createClaimError: (kind) => new LineWebhookPayloadError(kind === "invalid-version" ? LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE : "LINE webhook event identity changed after durable admission.")
1222
1304
  },
1223
1305
  deliver: async ({ event, destination }, lifecycle) => {
1224
1306
  const boundLifecycle = bindIngressLifecycleToReplyOptions(lifecycle).turnAdoptionLifecycle;
@@ -1278,7 +1360,7 @@ function createLineWebhookSpool(options) {
1278
1360
  },
1279
1361
  resolveNonRetryableFailure: (error) => {
1280
1362
  if (error instanceof LineWebhookPayloadError) return {
1281
- reason: "invalid-event",
1363
+ reason: LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON,
1282
1364
  message: error.message
1283
1365
  };
1284
1366
  if (error instanceof LineWebhookTerminalDeliveryError) return {
@@ -1331,9 +1413,17 @@ function createLineWebhookSpool(options) {
1331
1413
  const DEFAULT_MEDIA_MAX_MB = 10;
1332
1414
  function createLineBot(opts) {
1333
1415
  const runtime = opts.runtime ?? createNonExitingRuntime();
1334
- const cfg = opts.config ?? getRuntimeConfig();
1416
+ const startupConfig = opts.config ?? getRuntimeConfig();
1417
+ const startupRuntimeConfig = getRuntimeConfigSnapshot();
1418
+ const startupRuntimeSourceConfig = getRuntimeConfigSourceSnapshot();
1419
+ const followsRuntimeConfig = opts.config === void 0 || startupRuntimeConfig === startupConfig || startupRuntimeSourceConfig !== null && selectApplicableRuntimeConfig({
1420
+ inputConfig: startupConfig,
1421
+ runtimeConfig: startupRuntimeConfig,
1422
+ runtimeSourceConfig: startupRuntimeSourceConfig
1423
+ }) === startupRuntimeConfig;
1424
+ const resolveTurnConfig = () => (followsRuntimeConfig ? getRuntimeConfigSnapshot() : void 0) ?? startupConfig;
1335
1425
  const account = resolveLineAccount({
1336
- cfg,
1426
+ cfg: startupConfig,
1337
1427
  accountId: opts.accountId
1338
1428
  });
1339
1429
  const mediaMaxBytes = ([opts.mediaMaxMb, account.config.mediaMaxMb].find((value) => typeof value === "number" && value > 0) ?? DEFAULT_MEDIA_MAX_MB) * 1024 * 1024;
@@ -1344,17 +1434,20 @@ function createLineBot(opts) {
1344
1434
  const spool = createLineWebhookSpool({
1345
1435
  accountId: account.accountId,
1346
1436
  runtime,
1347
- deliver: async (event, _destination, control) => await handleLineWebhookEvents([event], {
1348
- cfg,
1349
- account,
1350
- runtime,
1351
- buildContext: opts.buildContext,
1352
- mediaMaxBytes,
1353
- processMessage,
1354
- ...control.turnAdoptionLifecycle ? { turnAdoptionLifecycle: control.turnAdoptionLifecycle } : {},
1355
- groupHistories,
1356
- historyLimit: cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
1357
- })
1437
+ deliver: async (event, _destination, control) => {
1438
+ const cfg = resolveTurnConfig();
1439
+ await handleLineWebhookEvents([event], {
1440
+ cfg,
1441
+ account,
1442
+ runtime,
1443
+ buildContext: opts.buildContext,
1444
+ mediaMaxBytes,
1445
+ processMessage,
1446
+ ...control.turnAdoptionLifecycle ? { turnAdoptionLifecycle: control.turnAdoptionLifecycle } : {},
1447
+ groupHistories,
1448
+ historyLimit: account.config.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
1449
+ });
1450
+ }
1358
1451
  });
1359
1452
  spool.start();
1360
1453
  return {
@@ -1530,20 +1623,22 @@ async function monitorLineProvider(opts) {
1530
1623
  onMessage: async (ctx, deliveryControl) => {
1531
1624
  if (!ctx) return;
1532
1625
  const { ctxPayload, replyToken, route } = ctx;
1533
- const shouldShowLoading = Boolean(ctx.userId && !ctx.isGroup);
1534
- const displayNamePromise = ctx.userId ? getUserDisplayName(ctx.userId, {
1535
- cfg: config,
1536
- accountId: ctx.accountId
1537
- }) : Promise.resolve(ctxPayload.From);
1538
- const stopLoading = shouldShowLoading ? startLineLoadingKeepalive({
1539
- cfg: config,
1626
+ const turnConfig = deliveryControl.cfg;
1627
+ const stopLoading = Boolean(ctx.userId && !ctx.isGroup) ? startLineLoadingKeepalive({
1628
+ cfg: turnConfig,
1540
1629
  userId: ctx.userId,
1541
1630
  accountId: ctx.accountId
1542
1631
  }) : null;
1543
- logVerbose(`line: received message from ${await displayNamePromise} (${ctxPayload.From})`);
1632
+ logVerbose(`line: received message from ${ctxPayload.SenderName ?? ctx.userId ?? ctxPayload.From} (${ctxPayload.From})`);
1544
1633
  let replyTokenUsed = false;
1545
1634
  let turnAdopted = false;
1546
1635
  const ingressLifecycle = deliveryControl.turnAdoptionLifecycle;
1636
+ const turnAbortSignal = ingressLifecycle?.abortSignal;
1637
+ const skillFilter = ctx.skillFilter;
1638
+ const replyOptions = turnAbortSignal || skillFilter ? {
1639
+ ...turnAbortSignal ? { abortSignal: turnAbortSignal } : {},
1640
+ ...skillFilter ? { skillFilter } : {}
1641
+ } : void 0;
1547
1642
  try {
1548
1643
  const textLimit = 5e3;
1549
1644
  const turnResult = await getLineRuntime().channel.inbound.run({
@@ -1564,7 +1659,7 @@ async function monitorLineProvider(opts) {
1564
1659
  rawText: ctxPayload.RawBody ?? ctxPayload.BodyForAgent ?? ""
1565
1660
  }),
1566
1661
  resolveTurn: () => ({
1567
- cfg: config,
1662
+ cfg: turnConfig,
1568
1663
  channel: "line",
1569
1664
  accountId: route.accountId,
1570
1665
  route: {
@@ -1574,8 +1669,9 @@ async function monitorLineProvider(opts) {
1574
1669
  ctxPayload,
1575
1670
  record: ctx.turn.record,
1576
1671
  replyPipeline: {},
1577
- ...ingressLifecycle?.abortSignal ? { replyOptions: { abortSignal: ingressLifecycle.abortSignal } } : {},
1672
+ ...replyOptions ? { replyOptions } : {},
1578
1673
  delivery: {
1674
+ preparePayload: prepareLineReplyPayload,
1579
1675
  durable: (payload, info) => resolveLineDurableReplyOptions({
1580
1676
  payload,
1581
1677
  infoKind: info.kind,
@@ -1586,7 +1682,7 @@ async function monitorLineProvider(opts) {
1586
1682
  deliver: async (payload) => {
1587
1683
  const lineData = payload.channelData?.line ?? {};
1588
1684
  if (ctx.userId && !ctx.isGroup) showLoadingAnimation(ctx.userId, {
1589
- cfg: config,
1685
+ cfg: turnConfig,
1590
1686
  accountId: ctx.accountId
1591
1687
  }).catch(() => {});
1592
1688
  const deliveryResult = await deliverLineAutoReply({
@@ -1596,14 +1692,13 @@ async function monitorLineProvider(opts) {
1596
1692
  replyToken,
1597
1693
  replyTokenUsed,
1598
1694
  accountId: ctx.accountId,
1599
- cfg: config,
1695
+ cfg: turnConfig,
1600
1696
  textLimit,
1601
1697
  deps: {
1602
1698
  buildTemplateMessageFromPayload,
1603
1699
  processLineMessage,
1604
1700
  chunkMarkdownText,
1605
1701
  replyMessageLine,
1606
- createQuickReplyItems,
1607
1702
  pushMessagesLine,
1608
1703
  createFlexMessage,
1609
1704
  buildMediaMessage: buildLineMediaMessage,