@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.
- package/dist/account-helpers-BSUyZwLm.js +12 -0
- package/dist/api.js +2 -2
- package/dist/{card-command-CyDSm-OU.js → card-command-BywtX77U.js} +66 -29
- package/dist/{channel-DmoKZXmG.js → channel-DpMgRaxl.js} +52 -349
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-Bty5U_1N.js +4 -0
- package/dist/doctor-contract-api.js +141 -0
- package/dist/index.js +1 -1
- package/dist/{markdown-to-line-rEaras_0.js → markdown-to-line-CG9Jy22s.js} +29 -25
- package/dist/{monitor-BYXS_B6r.js → monitor-Ch6TIatG.js} +261 -166
- package/dist/monitor.runtime-Czc3NxNV.js +2 -0
- package/dist/outbound.runtime-CyleVzFR.js +3 -0
- package/dist/probe.runtime-DTxBWvKz.js +2 -0
- package/dist/rich-messages-t_zRL24p.js +760 -0
- package/dist/runtime-api.js +10 -11
- package/dist/{send-BJCeiiOB.js → send-B-K2B6au.js} +138 -104
- package/dist/{send-receipt-DKZzgHMO.js → send-retry-CUvrarfE.js} +137 -413
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-W0Dbgu58.js → setup-surface-DSJ92oJ5.js} +24 -17
- package/dist/webhook-spool-contract-CNe2h1I2.js +58 -0
- package/node_modules/typebox/build/format/date.d.mts +1 -1
- package/node_modules/typebox/build/format/date.mjs +1 -1
- package/node_modules/typebox/build/format/date_time.d.mts +2 -2
- package/node_modules/typebox/build/format/date_time.mjs +3 -3
- package/node_modules/typebox/build/format/email.d.mts +1 -1
- package/node_modules/typebox/build/format/email.mjs +2 -2
- package/node_modules/typebox/build/format/hostname.mjs +2 -10
- package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_email.mjs +3 -3
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
- package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
- package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
- package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +23 -0
- package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
- package/node_modules/typebox/build/format/idna/index.mjs +2 -0
- package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
- package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
- package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
- package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
- package/node_modules/typebox/build/format/ipv4.mjs +2 -32
- package/node_modules/typebox/build/format/ipv6.mjs +2 -61
- package/node_modules/typebox/build/format/iri.d.mts +2 -2
- package/node_modules/typebox/build/format/iri.mjs +26 -8
- package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
- package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
- package/node_modules/typebox/build/format/regex.d.mts +1 -2
- package/node_modules/typebox/build/format/regex.mjs +2 -6
- package/node_modules/typebox/build/format/time.d.mts +2 -2
- package/node_modules/typebox/build/format/time.mjs +19 -14
- package/node_modules/typebox/build/format/uri.d.mts +1 -1
- package/node_modules/typebox/build/format/uri.mjs +3 -130
- package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
- package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
- package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
- package/node_modules/typebox/build/format/uri_template.mjs +4 -5
- package/node_modules/typebox/build/format/url.d.mts +3 -3
- package/node_modules/typebox/build/format/url.mjs +4 -5
- package/node_modules/typebox/build/guard/emit.d.mts +3 -1
- package/node_modules/typebox/build/guard/emit.mjs +12 -5
- package/node_modules/typebox/build/guard/guard.d.mts +8 -2
- package/node_modules/typebox/build/guard/guard.mjs +25 -4
- package/node_modules/typebox/build/guard/string.d.mts +2 -2
- package/node_modules/typebox/build/guard/string.mjs +7 -7
- package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
- package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
- package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
- package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
- package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
- package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
- package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
- package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
- package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
- package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
- package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
- package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
- package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
- package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
- package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
- package/node_modules/typebox/build/schema/types/index.mjs +0 -2
- package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
- package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
- package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
- package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
- package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
- package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
- package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
- package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
- package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
- package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
- package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
- package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
- package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
- package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
- package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
- package/node_modules/typebox/build/type/script/parser.mjs +27 -23
- package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
- package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
- package/node_modules/typebox/package.json +27 -27
- package/node_modules/typebox/readme.md +79 -82
- package/openclaw.plugin.json +26 -2
- package/package.json +6 -6
- package/dist/channel.runtime-qrUV6wPE.js +0 -4
- package/dist/config-schema-Be3azjB_.js +0 -47
- package/dist/monitor.runtime-B2z_5ugU.js +0 -2
- package/dist/outbound.runtime-DDTeqaBW.js +0 -3
- package/dist/probe.runtime-D13ZtXsd.js +0 -2
- package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
- package/node_modules/typebox/build/format/_idna.d.mts +0 -2
- package/node_modules/typebox/build/format/_idna.mjs +0 -246
- package/node_modules/typebox/build/format/_puny.d.mts +0 -1
- package/node_modules/typebox/build/format/_puny.mjs +0 -77
- package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
- package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
- package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
- package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
- 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 {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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 {
|
|
8
|
-
import { DEFAULT_INGRESS_ADOPTION_STALL_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions,
|
|
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 =
|
|
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,
|
|
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
|
|
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:
|
|
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
|
|
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(
|
|
301
|
+
text: buildLineQuickReplyFallbackText(quickReplyLabels)
|
|
291
302
|
});
|
|
292
303
|
if (hasQuickReplies) {
|
|
293
|
-
const targetMessages =
|
|
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:
|
|
308
|
+
quickReply: createLineQuickReply(quickReplyItems)
|
|
298
309
|
};
|
|
299
310
|
}
|
|
300
|
-
const messages = hasQuickReplies ? [...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 =
|
|
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(
|
|
313
|
-
quickReply:
|
|
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)
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
|
473
|
-
|
|
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: {
|
|
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: {
|
|
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:
|
|
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
|
|
661
|
-
if (!
|
|
662
|
-
let
|
|
663
|
-
if (
|
|
664
|
-
const searchParams = new URLSearchParams(
|
|
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
|
-
|
|
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 } =
|
|
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
|
|
959
|
-
|
|
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)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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(
|
|
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" ?
|
|
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:
|
|
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
|
|
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) =>
|
|
1348
|
-
cfg
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
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
|
|
1534
|
-
const
|
|
1535
|
-
cfg:
|
|
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 ${
|
|
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:
|
|
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
|
-
...
|
|
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:
|
|
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:
|
|
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,
|