@openclaw/line 2026.8.1-beta.3 → 2026.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-helpers-BSUyZwLm.js +12 -0
- package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
- package/dist/api.js +2 -2
- package/dist/card-command-DS6A5V6o.js +252 -0
- package/dist/{channel-DmoKZXmG.js → channel-CLCy4jAa.js} +97 -357
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-Ih7sq1GN.js +4 -0
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +141 -0
- package/dist/index.js +5 -17
- package/dist/{markdown-to-line-rEaras_0.js → markdown-to-line--jytmFan.js} +36 -29
- package/dist/{monitor-BYXS_B6r.js → monitor-CSpwQCBI.js} +292 -194
- package/dist/monitor.runtime-CV8EjkIT.js +2 -0
- package/dist/outbound.runtime-DLqcz_BQ.js +3 -0
- package/dist/probe.runtime-DTxBWvKz.js +2 -0
- package/dist/rich-messages-CIP7xn5E.js +761 -0
- package/dist/runtime-api.js +12 -13
- package/dist/{send-receipt-DKZzgHMO.js → send-retry-CUvrarfE.js} +137 -413
- package/dist/{send-BJCeiiOB.js → send-wVUV00WS.js} +150 -109
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
- package/dist/webhook-spool-contract-1rY8OJw8.js +59 -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 +41 -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/card-command-CyDSm-OU.js +0 -224
- 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,36 @@
|
|
|
1
|
-
import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-BBEFMYbY.js";
|
|
2
|
+
import { f as resolveLineGroupConfigEntry, h as getLineRuntime, i as prepareLineReplyPayload, n as createLineQuickReply, u as buildLineQuickReplyFallbackText } from "./rich-messages-CIP7xn5E.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-1rY8OJw8.js";
|
|
5
|
+
import { i as processLineMessage, s as buildTemplateMessageFromPayload } from "./markdown-to-line--jytmFan.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-wVUV00WS.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 {
|
|
24
|
+
import { resolveHumanDelayConfig } from "openclaw/plugin-sdk/agent-runtime";
|
|
24
25
|
import { channelReadyPatch, channelStoppedPatch } from "openclaw/plugin-sdk/gateway-runtime";
|
|
25
26
|
import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
|
|
26
27
|
import { canonicalizeWebhookRouteKey, isRequestBodyLimitError, normalizePluginHttpPath, normalizeWebhookPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
|
|
27
28
|
import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1, runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
28
29
|
import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
|
|
29
30
|
import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
|
|
30
|
-
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
31
|
-
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
31
|
+
import { getRuntimeConfig, getRuntimeConfigSnapshot, getRuntimeConfigSourceSnapshot, selectApplicableRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
32
|
+
import { resolveChannelImplicitMentions, resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
33
|
+
import { reportChannelRoomJoin } from "openclaw/plugin-sdk/channel-join-intro-runtime";
|
|
32
34
|
import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
|
|
33
35
|
import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolvePairingIdLabel, resolvePinnedMainDmOwnerFromAllowlist, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
|
|
34
36
|
import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
|
|
@@ -110,7 +112,7 @@ async function fetchLineContentWhenReady(messageId, channelAccessToken, signal)
|
|
|
110
112
|
}
|
|
111
113
|
await response.body?.cancel().catch(() => void 0);
|
|
112
114
|
if (response.status !== 202) throw new MediaFetchError("http_error", `LINE media download failed for message ${messageId} (HTTP ${response.status})`, { status: response.status });
|
|
113
|
-
if (attempt <
|
|
115
|
+
if (attempt < 5) await setTimeout$1(contentBackoffDelayMs(attempt), void 0, { signal });
|
|
114
116
|
}
|
|
115
117
|
} catch (err) {
|
|
116
118
|
if (signal.aborted) throw signal.reason;
|
|
@@ -125,7 +127,7 @@ function isRetryableLineInboundMediaError(err) {
|
|
|
125
127
|
if (err.code === "http_error") return err.status === 202 || err.status === 408 || err.status === 429 || typeof err.status === "number" && err.status >= 500;
|
|
126
128
|
return false;
|
|
127
129
|
}
|
|
128
|
-
async function downloadLineMedia(messageId, channelAccessToken, maxBytes =
|
|
130
|
+
async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10485760, options) {
|
|
129
131
|
options?.signal?.throwIfAborted();
|
|
130
132
|
const deadlineAbort = new AbortController();
|
|
131
133
|
const signal = options?.signal ? AbortSignal.any([options.signal, deadlineAbort.signal]) : deadlineAbort.signal;
|
|
@@ -159,11 +161,8 @@ async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10 *
|
|
|
159
161
|
function toLineDeliveryError(error) {
|
|
160
162
|
return error instanceof Error ? error : new Error("LINE message send failed", { cause: error });
|
|
161
163
|
}
|
|
162
|
-
function getLineHttpError(error) {
|
|
163
|
-
return collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]).find((candidate) => candidate instanceof HTTPFetchError);
|
|
164
|
-
}
|
|
165
164
|
function canFallbackAfterLineReplyFailure(error) {
|
|
166
|
-
const httpError =
|
|
165
|
+
const httpError = findLineHttpError(error);
|
|
167
166
|
if (httpError) return httpError.status >= 400 && httpError.status < 500 && httpError.status !== 408;
|
|
168
167
|
const candidates = collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]);
|
|
169
168
|
if (candidates.some((candidate) => readErrorName(candidate) === "AbortError" || classifyTransientNetworkErrorCode(extractErrorCode(candidate)) === "ambiguous")) return false;
|
|
@@ -235,7 +234,7 @@ async function deliverLineAutoReply(params) {
|
|
|
235
234
|
} catch (err) {
|
|
236
235
|
if (isChannelPartialDeliveryError(err) || !canFallbackAfterLineReplyFailure(err)) throw err;
|
|
237
236
|
deps.onReplyError?.(err);
|
|
238
|
-
await pushLineMessages(replyBatch,
|
|
237
|
+
await pushLineMessages(replyBatch, findLineHttpError(err)?.status === 400, remaining.slice(replyBatch.length));
|
|
239
238
|
} finally {
|
|
240
239
|
replyTokenUsed = true;
|
|
241
240
|
}
|
|
@@ -244,7 +243,15 @@ async function deliverLineAutoReply(params) {
|
|
|
244
243
|
if (remaining.length > 0) await pushLineMessages(remaining, true);
|
|
245
244
|
};
|
|
246
245
|
const richMessages = [];
|
|
247
|
-
const
|
|
246
|
+
const quickReplyItems = lineData.quickReplyItems?.length ? lineData.quickReplyItems : (lineData.quickReplies ?? []).map((label) => ({
|
|
247
|
+
label,
|
|
248
|
+
action: {
|
|
249
|
+
type: "command",
|
|
250
|
+
command: label
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
const quickReplyLabels = quickReplyItems.map((item) => item.label);
|
|
254
|
+
const hasQuickReplies = quickReplyItems.length > 0;
|
|
248
255
|
if (lineData.flexMessage) richMessages.push(deps.createFlexMessage(lineData.flexMessage.altText, lineData.flexMessage.contents));
|
|
249
256
|
if (lineData.templateMessage) {
|
|
250
257
|
const templateMsg = deps.buildTemplateMessageFromPayload(lineData.templateMessage);
|
|
@@ -264,7 +271,7 @@ async function deliverLineAutoReply(params) {
|
|
|
264
271
|
const chunks = orderedMessages ? orderedMessages.flatMap((message) => message.type === "text" ? [message.text] : []) : processed.text ? deps.chunkMarkdownText(processed.text, textLimit) : [];
|
|
265
272
|
const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
|
|
266
273
|
const mediaOpts = {
|
|
267
|
-
mediaKind:
|
|
274
|
+
mediaKind: lineData.mediaKind,
|
|
268
275
|
previewImageUrl: lineData.previewImageUrl,
|
|
269
276
|
durationMs: lineData.durationMs,
|
|
270
277
|
trackingId: lineData.trackingId
|
|
@@ -284,37 +291,46 @@ async function deliverLineAutoReply(params) {
|
|
|
284
291
|
type: "text",
|
|
285
292
|
text
|
|
286
293
|
}));
|
|
287
|
-
const
|
|
294
|
+
const orderedDeliveryMessages = hasQuickReplies && chunks.length === 0 ? void 0 : orderedMessages;
|
|
295
|
+
const richMediaMessages = [
|
|
296
|
+
...richMessages,
|
|
297
|
+
...orderedDeliveryMessages ? [] : orderedMessages ?? [],
|
|
298
|
+
...mediaMessages
|
|
299
|
+
];
|
|
288
300
|
if (hasQuickReplies && textMessages.length === 0 && richMediaMessages.length === 0) textMessages.push({
|
|
289
301
|
type: "text",
|
|
290
|
-
text: buildLineQuickReplyFallbackText(
|
|
302
|
+
text: buildLineQuickReplyFallbackText(quickReplyLabels)
|
|
291
303
|
});
|
|
292
304
|
if (hasQuickReplies) {
|
|
293
|
-
const targetMessages =
|
|
305
|
+
const targetMessages = orderedDeliveryMessages?.length ? orderedDeliveryMessages : textMessages.length > 0 ? textMessages : richMediaMessages;
|
|
294
306
|
const lastIndex = targetMessages.length - 1;
|
|
295
307
|
targetMessages[lastIndex] = {
|
|
296
308
|
...expectDefined(targetMessages[lastIndex], "last LINE auto-reply message"),
|
|
297
|
-
quickReply:
|
|
309
|
+
quickReply: createLineQuickReply(quickReplyItems)
|
|
298
310
|
};
|
|
299
311
|
}
|
|
300
|
-
const messages = hasQuickReplies ? [...richMediaMessages, ...
|
|
312
|
+
const messages = hasQuickReplies ? [...richMediaMessages, ...orderedDeliveryMessages ?? textMessages] : [...orderedDeliveryMessages ?? textMessages, ...richMediaMessages];
|
|
301
313
|
try {
|
|
302
314
|
await sendLineMessages(messages, true);
|
|
303
315
|
} catch (err) {
|
|
304
316
|
deliveryError ??= err;
|
|
305
317
|
const failedSegment = typeof err === "object" && err !== null ? failedPushSegments.get(err) : void 0;
|
|
306
|
-
const httpError =
|
|
318
|
+
const httpError = findLineHttpError(err);
|
|
307
319
|
const retryCandidates = failedSegment ? [...failedSegment.allowFailedBatchTextRecovery ? failedSegment.failedBatch : [], ...failedSegment.unattemptedTail] : [];
|
|
308
320
|
const retryTextMessages = retryCandidates.filter((message) => message.type === "text");
|
|
309
321
|
const quickRepliesNeedCarrier = hasQuickReplies && retryCandidates.some((message) => "quickReply" in message);
|
|
310
322
|
const retryMessages = retryTextMessages.length > 0 ? retryTextMessages : quickRepliesNeedCarrier ? [{
|
|
311
323
|
type: "text",
|
|
312
|
-
text: buildLineQuickReplyFallbackText(
|
|
313
|
-
quickReply:
|
|
324
|
+
text: buildLineQuickReplyFallbackText(quickReplyLabels),
|
|
325
|
+
quickReply: createLineQuickReply(quickReplyItems)
|
|
314
326
|
}] : [];
|
|
315
|
-
if (retryMessages.length > 0 && failedSegment?.failedBatch.some((message) => message.type !== "text") && httpError?.status === 400)
|
|
316
|
-
|
|
317
|
-
|
|
327
|
+
if (retryMessages.length > 0 && failedSegment?.failedBatch.some((message) => message.type !== "text") && httpError?.status === 400 && resolveLineNonDispatchRetryable(err) !== void 0) {
|
|
328
|
+
const lastRetryMessage = retryMessages.at(-1);
|
|
329
|
+
if (quickRepliesNeedCarrier && lastRetryMessage && !lastRetryMessage.quickReply) lastRetryMessage.quickReply = createLineQuickReply(quickReplyItems);
|
|
330
|
+
try {
|
|
331
|
+
await sendLineMessages(retryMessages, false);
|
|
332
|
+
} catch {}
|
|
333
|
+
}
|
|
318
334
|
}
|
|
319
335
|
if (deliveryError !== void 0) {
|
|
320
336
|
if (!visibleReplySent) throw toLineDeliveryError(deliveryError);
|
|
@@ -332,6 +348,44 @@ async function deliverLineAutoReply(params) {
|
|
|
332
348
|
};
|
|
333
349
|
}
|
|
334
350
|
//#endregion
|
|
351
|
+
//#region extensions/line/src/mentions.ts
|
|
352
|
+
function getLineMentionees(message) {
|
|
353
|
+
return message.type === "text" ? message.mention?.mentionees ?? [] : [];
|
|
354
|
+
}
|
|
355
|
+
function addressesLineBot(mentionee) {
|
|
356
|
+
return mentionee.type === "all" || mentionee.isSelf === true;
|
|
357
|
+
}
|
|
358
|
+
function isLineBotMentioned(message) {
|
|
359
|
+
return getLineMentionees(message).some(addressesLineBot);
|
|
360
|
+
}
|
|
361
|
+
function hasAnyLineMention(message) {
|
|
362
|
+
return getLineMentionees(message).length > 0;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Text to interpret as a command, with the mention that addressed the bot removed.
|
|
366
|
+
*
|
|
367
|
+
* LINE writes a mention as the plain channel display name, so no pattern can
|
|
368
|
+
* tell it apart from a member's name; `mentionees[].index`/`length` (UTF-16
|
|
369
|
+
* code units, the unit LINE counts text in) is the only authoritative marker.
|
|
370
|
+
* Leaving it in place makes a group `@<bot> /status` parse as prose, and group
|
|
371
|
+
* chats require the mention to reach the bot at all.
|
|
372
|
+
*/
|
|
373
|
+
function resolveLineMentionStrippedText(message) {
|
|
374
|
+
const text = message.type === "text" ? message.text : "";
|
|
375
|
+
const spans = getLineMentionees(message).filter(addressesLineBot).map((mentionee) => ({
|
|
376
|
+
start: mentionee.index,
|
|
377
|
+
end: mentionee.index + mentionee.length
|
|
378
|
+
})).toSorted((left, right) => left.start - right.start);
|
|
379
|
+
let stripped = "";
|
|
380
|
+
let cursor = 0;
|
|
381
|
+
for (const span of spans) {
|
|
382
|
+
stripped += text.slice(cursor, span.start);
|
|
383
|
+
if (/\S$/u.test(stripped) && /^\S/u.test(text.slice(span.end))) stripped += " ";
|
|
384
|
+
cursor = Math.max(cursor, span.end);
|
|
385
|
+
}
|
|
386
|
+
return `${stripped}${text.slice(cursor)}`.trim();
|
|
387
|
+
}
|
|
388
|
+
//#endregion
|
|
335
389
|
//#region extensions/line/src/bot-message-context.ts
|
|
336
390
|
function getLineSourceInfo(source) {
|
|
337
391
|
if (!source) return {
|
|
@@ -416,30 +470,23 @@ async function resolveLineInboundRoute(params) {
|
|
|
416
470
|
route
|
|
417
471
|
};
|
|
418
472
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
"
|
|
432
|
-
|
|
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 "";
|
|
473
|
+
/**
|
|
474
|
+
* Describe a sticker from what its webhook actually carries: LINE sends up to
|
|
475
|
+
* 15 keywords for the sticker, and a message sticker also carries the sender's
|
|
476
|
+
* own text. The package name is not among those facts and cannot be derived
|
|
477
|
+
* from the package id, so it is not part of the description.
|
|
478
|
+
*/
|
|
479
|
+
function describeLineSticker(sticker) {
|
|
480
|
+
const description = readNonEmptyStringPreservingWhitespace(sticker.text) ?? normalizeStringEntries(sticker.keywords ?? []).slice(0, 3).join(", ");
|
|
481
|
+
return description ? `[Sent a sticker: ${description}]` : "[Sent a sticker]";
|
|
482
|
+
}
|
|
483
|
+
function readLineTextMessageBody(message) {
|
|
484
|
+
let text = message.text;
|
|
485
|
+
for (const { index, length } of (message.emojis ?? []).toSorted((a, b) => b.index - a.index)) if (index >= 0 && length === 2 && text.slice(index, index + length) === "()") text = `${text.slice(0, index)}[emoji]${text.slice(index + length)}`;
|
|
486
|
+
return text;
|
|
440
487
|
}
|
|
441
488
|
function extractMessageText(message) {
|
|
442
|
-
if (message.type === "text") return message
|
|
489
|
+
if (message.type === "text") return readLineTextMessageBody(message);
|
|
443
490
|
if (message.type === "location") {
|
|
444
491
|
const loc = message;
|
|
445
492
|
return formatLocationText({
|
|
@@ -449,13 +496,7 @@ function extractMessageText(message) {
|
|
|
449
496
|
address: loc.address
|
|
450
497
|
}) ?? "";
|
|
451
498
|
}
|
|
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
|
-
}
|
|
499
|
+
if (message.type === "sticker") return describeLineSticker(message);
|
|
459
500
|
return "";
|
|
460
501
|
}
|
|
461
502
|
function extractNativeMediaKind(message) {
|
|
@@ -469,9 +510,23 @@ function extractNativeMediaKind(message) {
|
|
|
469
510
|
}
|
|
470
511
|
async function finalizeLineInboundContext(params) {
|
|
471
512
|
const senderId = params.source.userId ?? "unknown";
|
|
472
|
-
const
|
|
473
|
-
|
|
513
|
+
const clientOpts = {
|
|
514
|
+
cfg: params.cfg,
|
|
515
|
+
accountId: params.account.accountId,
|
|
516
|
+
channelAccessToken: params.account.channelAccessToken
|
|
517
|
+
};
|
|
518
|
+
const [senderName, groupName] = await Promise.all([params.source.userId ? getUserProfile(params.source.userId, {
|
|
519
|
+
...clientOpts,
|
|
520
|
+
groupId: params.source.groupId,
|
|
521
|
+
roomId: params.source.roomId
|
|
522
|
+
}).then((profile) => profile?.displayName) : void 0, params.source.groupId ? getLineGroupName(params.source.groupId, clientOpts) : void 0]);
|
|
523
|
+
const senderLabel = senderName ?? (params.source.userId ? `user:${params.source.userId}` : "unknown");
|
|
524
|
+
const conversationLabel = params.source.isGroup ? groupName ?? (params.source.groupId ? `group:${params.source.groupId}` : params.source.roomId ? `room:${params.source.roomId}` : "unknown-group") : senderLabel;
|
|
474
525
|
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}`;
|
|
526
|
+
const groupConfig = params.source.isGroup ? resolveLineGroupConfigEntry(params.account.config.groups, {
|
|
527
|
+
groupId: params.source.groupId,
|
|
528
|
+
roomId: params.source.roomId
|
|
529
|
+
}) : void 0;
|
|
475
530
|
const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
|
|
476
531
|
cfg: params.cfg,
|
|
477
532
|
agentId: params.route.agentId,
|
|
@@ -485,7 +540,10 @@ async function finalizeLineInboundContext(params) {
|
|
|
485
540
|
timestamp: params.timestamp,
|
|
486
541
|
body: agentBody,
|
|
487
542
|
chatType: params.source.isGroup ? "group" : "direct",
|
|
488
|
-
sender: {
|
|
543
|
+
sender: {
|
|
544
|
+
id: senderId,
|
|
545
|
+
name: senderName
|
|
546
|
+
},
|
|
489
547
|
previousTimestamp,
|
|
490
548
|
envelope: envelopeOptions
|
|
491
549
|
});
|
|
@@ -496,7 +554,10 @@ async function finalizeLineInboundContext(params) {
|
|
|
496
554
|
messageId: params.messageSid,
|
|
497
555
|
timestamp: params.timestamp,
|
|
498
556
|
from: address,
|
|
499
|
-
sender: {
|
|
557
|
+
sender: {
|
|
558
|
+
id: senderId,
|
|
559
|
+
name: senderName
|
|
560
|
+
},
|
|
500
561
|
conversation: {
|
|
501
562
|
kind: params.source.isGroup ? "group" : "direct",
|
|
502
563
|
id: params.source.peerId,
|
|
@@ -516,18 +577,15 @@ async function finalizeLineInboundContext(params) {
|
|
|
516
577
|
body,
|
|
517
578
|
bodyForAgent: agentBody,
|
|
518
579
|
rawBody: params.rawBody,
|
|
519
|
-
commandBody: params.rawBody,
|
|
580
|
+
commandBody: params.commandBody ?? params.rawBody,
|
|
520
581
|
inboundHistory: params.inboundHistory
|
|
521
582
|
},
|
|
522
583
|
access: { commands: { authorized: params.commandAuthorized } },
|
|
523
584
|
media,
|
|
524
585
|
extra: {
|
|
525
586
|
...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
|
|
587
|
+
GroupSubject: params.source.isGroup ? groupName ?? params.source.groupId ?? params.source.roomId : void 0,
|
|
588
|
+
GroupSystemPrompt: normalizeOptionalString(groupConfig?.systemPrompt)
|
|
531
589
|
}
|
|
532
590
|
});
|
|
533
591
|
const pinnedMainDmOwner = !params.source.isGroup ? resolvePinnedMainDmOwnerFromAllowlist({
|
|
@@ -542,11 +600,13 @@ async function finalizeLineInboundContext(params) {
|
|
|
542
600
|
if (shouldLogVerbose()) {
|
|
543
601
|
const preview = truncateUtf16Safe(body, 200).replace(/\n/g, "\\n");
|
|
544
602
|
const mediaInfo = params.verboseLog.kind === "inbound" && (params.verboseLog.mediaCount ?? 0) > 1 ? ` mediaCount=${params.verboseLog.mediaCount}` : "";
|
|
545
|
-
|
|
603
|
+
const label = params.verboseLog.kind === "inbound" ? "line inbound" : "line postback";
|
|
604
|
+
logVerbose(`${label}: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`);
|
|
546
605
|
}
|
|
547
606
|
return {
|
|
548
607
|
ctxPayload,
|
|
549
608
|
replyToken: params.event.replyToken,
|
|
609
|
+
skillFilter: groupConfig?.skills,
|
|
550
610
|
turn: {
|
|
551
611
|
storePath,
|
|
552
612
|
record: {
|
|
@@ -617,6 +677,7 @@ async function buildLineMessageContext(params) {
|
|
|
617
677
|
},
|
|
618
678
|
rawBody,
|
|
619
679
|
agentBody,
|
|
680
|
+
commandBody: resolveLineMentionStrippedText(message) || rawBody,
|
|
620
681
|
timestamp,
|
|
621
682
|
messageSid: messageId,
|
|
622
683
|
commandAuthorized,
|
|
@@ -638,6 +699,7 @@ async function buildLineMessageContext(params) {
|
|
|
638
699
|
return {
|
|
639
700
|
ctxPayload: finalized.ctxPayload,
|
|
640
701
|
turn: finalized.turn,
|
|
702
|
+
skillFilter: finalized.skillFilter,
|
|
641
703
|
event,
|
|
642
704
|
userId,
|
|
643
705
|
groupId,
|
|
@@ -657,14 +719,18 @@ async function buildLinePostbackContext(params) {
|
|
|
657
719
|
account
|
|
658
720
|
});
|
|
659
721
|
const timestamp = event.timestamp;
|
|
660
|
-
const
|
|
661
|
-
if (!
|
|
662
|
-
let
|
|
663
|
-
if (
|
|
664
|
-
const searchParams = new URLSearchParams(
|
|
722
|
+
const rawBody = event.postback?.data?.trim() ?? "";
|
|
723
|
+
if (!rawBody) return null;
|
|
724
|
+
let agentBody = rawBody;
|
|
725
|
+
if (rawBody.includes("line.action=")) {
|
|
726
|
+
const searchParams = new URLSearchParams(rawBody);
|
|
665
727
|
const action = searchParams.get("line.action") ?? "";
|
|
666
728
|
const device = searchParams.get("line.device");
|
|
667
|
-
|
|
729
|
+
agentBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
|
|
730
|
+
}
|
|
731
|
+
for (const [key, value] of Object.entries(event.postback.params ?? {}).toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
|
|
732
|
+
const picked = normalizeOptionalString(value);
|
|
733
|
+
if (picked) agentBody += ` ${key}=${picked}`;
|
|
668
734
|
}
|
|
669
735
|
const messageSid = event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`;
|
|
670
736
|
const finalized = await finalizeLineInboundContext({
|
|
@@ -680,6 +746,7 @@ async function buildLinePostbackContext(params) {
|
|
|
680
746
|
peerId
|
|
681
747
|
},
|
|
682
748
|
rawBody,
|
|
749
|
+
agentBody,
|
|
683
750
|
timestamp,
|
|
684
751
|
messageSid,
|
|
685
752
|
commandAuthorized,
|
|
@@ -696,6 +763,7 @@ async function buildLinePostbackContext(params) {
|
|
|
696
763
|
return {
|
|
697
764
|
ctxPayload: finalized.ctxPayload,
|
|
698
765
|
turn: finalized.turn,
|
|
766
|
+
skillFilter: finalized.skillFilter,
|
|
699
767
|
event,
|
|
700
768
|
userId,
|
|
701
769
|
groupId,
|
|
@@ -755,12 +823,6 @@ function isDownloadableLineMessageType(messageType) {
|
|
|
755
823
|
function normalizeLineIngressEntry(value) {
|
|
756
824
|
return normalizeLineAllowEntry(value) || null;
|
|
757
825
|
}
|
|
758
|
-
function resolveLineGroupConfig(params) {
|
|
759
|
-
return resolveLineGroupConfigEntry(params.config.groups, {
|
|
760
|
-
groupId: params.groupId,
|
|
761
|
-
roomId: params.roomId
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
826
|
async function sendLinePairingReply(params) {
|
|
765
827
|
const { senderId, replyToken, context } = params;
|
|
766
828
|
const idLabel = (() => {
|
|
@@ -812,12 +874,11 @@ async function sendLinePairingReply(params) {
|
|
|
812
874
|
}
|
|
813
875
|
});
|
|
814
876
|
}
|
|
815
|
-
async function
|
|
877
|
+
async function resolveLineEventAdmission(event, context) {
|
|
816
878
|
const { cfg, account } = context;
|
|
817
879
|
const { userId, groupId, roomId, isGroup } = getLineSourceInfo(event.source);
|
|
818
880
|
const senderId = userId ?? "";
|
|
819
|
-
const groupConfig =
|
|
820
|
-
config: account.config,
|
|
881
|
+
const groupConfig = resolveLineGroupConfigEntry(account.config.groups, {
|
|
821
882
|
groupId,
|
|
822
883
|
roomId
|
|
823
884
|
});
|
|
@@ -835,7 +896,8 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
835
896
|
if (!isGroup || event.type !== "message") return {
|
|
836
897
|
canDetectMention: false,
|
|
837
898
|
wasMentioned: false,
|
|
838
|
-
hasAnyMention: false
|
|
899
|
+
hasAnyMention: false,
|
|
900
|
+
implicitMentionKinds: []
|
|
839
901
|
};
|
|
840
902
|
const peerId = groupId ?? roomId ?? userId ?? "unknown";
|
|
841
903
|
const { agentId } = resolveAgentRoute({
|
|
@@ -853,7 +915,8 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
853
915
|
return {
|
|
854
916
|
canDetectMention: event.message.type === "text",
|
|
855
917
|
wasMentioned: wasMentionedByNative || wasMentionedByPattern,
|
|
856
|
-
hasAnyMention: hasAnyLineMention(event.message)
|
|
918
|
+
hasAnyMention: hasAnyLineMention(event.message),
|
|
919
|
+
implicitMentionKinds: implicitMentionKindWhen("quoted_bot", quotesLineBotMessage(account.accountId, resolveLineQuotedMessageId(event.message)))
|
|
857
920
|
};
|
|
858
921
|
})();
|
|
859
922
|
const resolveAccess = async (contextBinding) => await resolveStableChannelMessageIngress({
|
|
@@ -867,7 +930,7 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
867
930
|
},
|
|
868
931
|
cfg,
|
|
869
932
|
readStoreAllowFrom: async () => await readChannelAllowFromStore("line", void 0, account.accountId),
|
|
870
|
-
subject: { stableId: senderId },
|
|
933
|
+
subject: event.type === "join" ? {} : { stableId: senderId },
|
|
871
934
|
conversation: {
|
|
872
935
|
kind: isGroup ? "group" : "direct",
|
|
873
936
|
id: (groupId ?? roomId ?? senderId) || "unknown"
|
|
@@ -881,16 +944,21 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
881
944
|
canDetectMention: mentionFacts.canDetectMention,
|
|
882
945
|
wasMentioned: mentionFacts.wasMentioned,
|
|
883
946
|
hasAnyMention: mentionFacts.hasAnyMention,
|
|
884
|
-
implicitMentionKinds:
|
|
947
|
+
implicitMentionKinds: mentionFacts.implicitMentionKinds
|
|
885
948
|
} : void 0,
|
|
886
|
-
event: { kind: event.type === "
|
|
949
|
+
event: { kind: event.type === "join" ? "system" : event.type },
|
|
887
950
|
dmPolicy,
|
|
888
951
|
groupPolicy,
|
|
889
952
|
policy: {
|
|
890
953
|
groupAllowFromFallbackToAllowFrom: false,
|
|
891
954
|
activation: {
|
|
892
955
|
requireMention: isGroup && event.type === "message" && requireMention,
|
|
893
|
-
allowTextCommands: true
|
|
956
|
+
allowTextCommands: true,
|
|
957
|
+
implicitMentions: resolveChannelImplicitMentions({
|
|
958
|
+
cfg,
|
|
959
|
+
channel: "line",
|
|
960
|
+
accountId: account.accountId
|
|
961
|
+
})
|
|
894
962
|
}
|
|
895
963
|
},
|
|
896
964
|
allowFrom: normalizeStringEntries(account.config.allowFrom),
|
|
@@ -907,6 +975,10 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
907
975
|
accountId: account.accountId,
|
|
908
976
|
log: (message) => logVerbose(message)
|
|
909
977
|
});
|
|
978
|
+
if (event.type === "join") return groupConfig?.enabled !== false && groupPolicy !== "disabled" && (groupPolicy !== "allowlist" || access.state.allowlists.group.hasMatchableEntries) ? {
|
|
979
|
+
access,
|
|
980
|
+
resolveBoundAccess: resolveAccess
|
|
981
|
+
} : null;
|
|
910
982
|
if (access.senderAccess.decision === "allow" && (access.ingress.admission === "dispatch" || access.ingress.admission === "observe" || access.ingress.admission === "skip")) return {
|
|
911
983
|
access,
|
|
912
984
|
resolveBoundAccess: resolveAccess
|
|
@@ -955,21 +1027,13 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
955
1027
|
logVerbose(`Blocked line sender ${senderId || "unknown"} (dmPolicy: ${account.config.dmPolicy ?? "pairing"})`);
|
|
956
1028
|
return null;
|
|
957
1029
|
}
|
|
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;
|
|
1030
|
+
function resolveLineQuotedMessageId(message) {
|
|
1031
|
+
return message.type === "text" || message.type === "sticker" ? message.quotedMessageId : void 0;
|
|
968
1032
|
}
|
|
969
1033
|
function resolveEventRawText(event) {
|
|
970
1034
|
if (event.type === "message") {
|
|
971
1035
|
const msg = event.message;
|
|
972
|
-
if (msg.type === "text") return msg
|
|
1036
|
+
if (msg.type === "text") return readLineTextMessageBody(msg);
|
|
973
1037
|
return "";
|
|
974
1038
|
}
|
|
975
1039
|
if (event.type === "postback") return event.postback?.data?.trim() ?? "";
|
|
@@ -978,24 +1042,34 @@ function resolveEventRawText(event) {
|
|
|
978
1042
|
async function handleMessageEvent(event, context) {
|
|
979
1043
|
const { cfg, account, runtime, mediaMaxBytes, processMessage } = context;
|
|
980
1044
|
const message = event.message;
|
|
981
|
-
const decision = await
|
|
1045
|
+
const decision = await resolveLineEventAdmission(event, context);
|
|
982
1046
|
if (!decision) return;
|
|
983
1047
|
const { isGroup, groupId, roomId } = getLineSourceInfo(event.source);
|
|
984
1048
|
if (isGroup && decision.access.activationAccess.shouldSkip) {
|
|
985
|
-
const rawText = message.type === "text" ? message
|
|
1049
|
+
const rawText = message.type === "text" ? readLineTextMessageBody(message) : "";
|
|
986
1050
|
const sourceInfo = getLineSourceInfo(event.source);
|
|
987
1051
|
logVerbose(`line: skipping group message (requireMention, not mentioned)`);
|
|
988
1052
|
const historyKey = groupId ?? roomId;
|
|
989
1053
|
const senderId = sourceInfo.userId ?? "unknown";
|
|
990
|
-
if (historyKey && context.groupHistories)
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
|
|
1054
|
+
if (historyKey && context.groupHistories) {
|
|
1055
|
+
const displayName = sourceInfo.userId ? await getUserDisplayName(sourceInfo.userId, {
|
|
1056
|
+
cfg,
|
|
1057
|
+
accountId: account.accountId,
|
|
1058
|
+
channelAccessToken: account.channelAccessToken,
|
|
1059
|
+
groupId,
|
|
1060
|
+
roomId
|
|
1061
|
+
}) : senderId;
|
|
1062
|
+
const sender = displayName === senderId ? senderId : `${displayName} (${senderId})`;
|
|
1063
|
+
createChannelHistoryWindow({ historyMap: context.groupHistories }).record({
|
|
1064
|
+
historyKey,
|
|
1065
|
+
limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
|
|
1066
|
+
entry: {
|
|
1067
|
+
sender,
|
|
1068
|
+
body: rawText || `<${message.type}>`,
|
|
1069
|
+
timestamp: event.timestamp
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
999
1073
|
return;
|
|
1000
1074
|
}
|
|
1001
1075
|
const groupHistoryKey = isGroup ? groupId ?? roomId : void 0;
|
|
@@ -1014,7 +1088,8 @@ async function handleMessageEvent(event, context) {
|
|
|
1014
1088
|
abortSignal?.throwIfAborted();
|
|
1015
1089
|
allMedia.push({
|
|
1016
1090
|
path: media.path,
|
|
1017
|
-
contentType: media.contentType
|
|
1091
|
+
contentType: media.contentType,
|
|
1092
|
+
...originalFilename ? { fileName: originalFilename } : {}
|
|
1018
1093
|
});
|
|
1019
1094
|
} catch (err) {
|
|
1020
1095
|
if (abortSignal?.aborted) throw abortSignal.reason;
|
|
@@ -1040,7 +1115,10 @@ async function handleMessageEvent(event, context) {
|
|
|
1040
1115
|
logVerbose("line: skipping empty message");
|
|
1041
1116
|
return;
|
|
1042
1117
|
}
|
|
1043
|
-
await processMessage(messageContext,
|
|
1118
|
+
await processMessage(messageContext, {
|
|
1119
|
+
cfg,
|
|
1120
|
+
...context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {}
|
|
1121
|
+
});
|
|
1044
1122
|
historyReservation.commit();
|
|
1045
1123
|
} finally {
|
|
1046
1124
|
historyReservation.release();
|
|
@@ -1054,9 +1132,42 @@ async function handleUnfollowEvent(event, _context) {
|
|
|
1054
1132
|
const { userId } = getLineSourceInfo(event.source);
|
|
1055
1133
|
logVerbose(`line: user ${userId ?? "unknown"} unfollowed`);
|
|
1056
1134
|
}
|
|
1057
|
-
async function handleJoinEvent(event,
|
|
1058
|
-
const { groupId, roomId } = getLineSourceInfo(event.source);
|
|
1135
|
+
async function handleJoinEvent(event, context) {
|
|
1136
|
+
const { groupId, roomId, isGroup } = getLineSourceInfo(event.source);
|
|
1137
|
+
const conversationId = groupId ?? roomId;
|
|
1138
|
+
if (!isGroup || !conversationId) return;
|
|
1059
1139
|
logVerbose(`line: bot joined ${groupId ? `group ${groupId}` : `room ${roomId}`}`);
|
|
1140
|
+
const { cfg, account } = context;
|
|
1141
|
+
const roomAllowed = Boolean(await resolveLineEventAdmission(event, context));
|
|
1142
|
+
await reportChannelRoomJoin({
|
|
1143
|
+
cfg,
|
|
1144
|
+
channel: "line",
|
|
1145
|
+
accountId: account.accountId,
|
|
1146
|
+
conversationId,
|
|
1147
|
+
deliverTo: conversationId,
|
|
1148
|
+
route: resolveAgentRoute({
|
|
1149
|
+
cfg,
|
|
1150
|
+
channel: "line",
|
|
1151
|
+
accountId: account.accountId,
|
|
1152
|
+
peer: {
|
|
1153
|
+
kind: "group",
|
|
1154
|
+
id: conversationId
|
|
1155
|
+
}
|
|
1156
|
+
}),
|
|
1157
|
+
roomAllowed,
|
|
1158
|
+
resolveRoomContext: async () => {
|
|
1159
|
+
const roomContext = { historyUnavailable: true };
|
|
1160
|
+
const title = groupId ? await getLineGroupName(groupId, {
|
|
1161
|
+
cfg,
|
|
1162
|
+
accountId: account.accountId,
|
|
1163
|
+
channelAccessToken: account.channelAccessToken
|
|
1164
|
+
}) : void 0;
|
|
1165
|
+
return title ? {
|
|
1166
|
+
...roomContext,
|
|
1167
|
+
title
|
|
1168
|
+
} : roomContext;
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1060
1171
|
}
|
|
1061
1172
|
async function handleLeaveEvent(event, _context) {
|
|
1062
1173
|
const { groupId, roomId } = getLineSourceInfo(event.source);
|
|
@@ -1065,7 +1176,7 @@ async function handleLeaveEvent(event, _context) {
|
|
|
1065
1176
|
async function handlePostbackEvent(event, context) {
|
|
1066
1177
|
const data = event.postback.data;
|
|
1067
1178
|
logVerbose(`line: received postback: ${data}`);
|
|
1068
|
-
const decision = await
|
|
1179
|
+
const decision = await resolveLineEventAdmission(event, context);
|
|
1069
1180
|
if (!decision) return;
|
|
1070
1181
|
const postbackContext = await buildLinePostbackContext({
|
|
1071
1182
|
event,
|
|
@@ -1076,7 +1187,10 @@ async function handlePostbackEvent(event, context) {
|
|
|
1076
1187
|
buildContext: context.buildContext
|
|
1077
1188
|
});
|
|
1078
1189
|
if (!postbackContext) return;
|
|
1079
|
-
await context.processMessage(postbackContext,
|
|
1190
|
+
await context.processMessage(postbackContext, {
|
|
1191
|
+
cfg: context.cfg,
|
|
1192
|
+
...context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {}
|
|
1193
|
+
});
|
|
1080
1194
|
}
|
|
1081
1195
|
async function handleLineWebhookEvents(events, context) {
|
|
1082
1196
|
let firstError;
|
|
@@ -1113,12 +1227,10 @@ async function handleLineWebhookEvent(event, context) {
|
|
|
1113
1227
|
}
|
|
1114
1228
|
//#endregion
|
|
1115
1229
|
//#region extensions/line/src/webhook-spool.ts
|
|
1116
|
-
const LINE_WEBHOOK_SPOOL_VERSION = 1;
|
|
1117
1230
|
const LINE_WEBHOOK_DRAIN_INTERVAL_MS = 500;
|
|
1118
1231
|
const LINE_WEBHOOK_MAX_CONCURRENT_DELIVERIES = 8;
|
|
1119
1232
|
const LINE_WEBHOOK_DRAIN_SCAN_LIMIT = 100;
|
|
1120
1233
|
const LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS = 5e3;
|
|
1121
|
-
const LineWebhookPayloadError = createChannelIngressError("LineWebhookPayloadError");
|
|
1122
1234
|
var LineWebhookTerminalDeliveryError = class extends Error {
|
|
1123
1235
|
constructor(message, options) {
|
|
1124
1236
|
super(message, options);
|
|
@@ -1126,35 +1238,6 @@ var LineWebhookTerminalDeliveryError = class extends Error {
|
|
|
1126
1238
|
this.name = "LineWebhookTerminalDeliveryError";
|
|
1127
1239
|
}
|
|
1128
1240
|
};
|
|
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
1241
|
function parseStoredEvent(rawEvent) {
|
|
1159
1242
|
let event;
|
|
1160
1243
|
try {
|
|
@@ -1194,7 +1277,7 @@ function createLineWebhookSpool(options) {
|
|
|
1194
1277
|
};
|
|
1195
1278
|
},
|
|
1196
1279
|
payload: {
|
|
1197
|
-
version:
|
|
1280
|
+
version: 1,
|
|
1198
1281
|
serialize: ({ event, destination }) => ({
|
|
1199
1282
|
rawEvent: JSON.stringify(event),
|
|
1200
1283
|
destination
|
|
@@ -1209,7 +1292,7 @@ function createLineWebhookSpool(options) {
|
|
|
1209
1292
|
destination: body.destination
|
|
1210
1293
|
}),
|
|
1211
1294
|
decode: (payload) => {
|
|
1212
|
-
if (typeof payload.rawEvent !== "string" || typeof payload.destination !== "string") throw new LineWebhookPayloadError(
|
|
1295
|
+
if (typeof payload.rawEvent !== "string" || typeof payload.destination !== "string") throw new LineWebhookPayloadError(LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE);
|
|
1213
1296
|
return {
|
|
1214
1297
|
version: payload.version,
|
|
1215
1298
|
body: {
|
|
@@ -1218,7 +1301,7 @@ function createLineWebhookSpool(options) {
|
|
|
1218
1301
|
}
|
|
1219
1302
|
};
|
|
1220
1303
|
},
|
|
1221
|
-
createClaimError: (kind) => new LineWebhookPayloadError(kind === "invalid-version" ?
|
|
1304
|
+
createClaimError: (kind) => new LineWebhookPayloadError(kind === "invalid-version" ? LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE : "LINE webhook event identity changed after durable admission.")
|
|
1222
1305
|
},
|
|
1223
1306
|
deliver: async ({ event, destination }, lifecycle) => {
|
|
1224
1307
|
const boundLifecycle = bindIngressLifecycleToReplyOptions(lifecycle).turnAdoptionLifecycle;
|
|
@@ -1278,7 +1361,7 @@ function createLineWebhookSpool(options) {
|
|
|
1278
1361
|
},
|
|
1279
1362
|
resolveNonRetryableFailure: (error) => {
|
|
1280
1363
|
if (error instanceof LineWebhookPayloadError) return {
|
|
1281
|
-
reason:
|
|
1364
|
+
reason: LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON,
|
|
1282
1365
|
message: error.message
|
|
1283
1366
|
};
|
|
1284
1367
|
if (error instanceof LineWebhookTerminalDeliveryError) return {
|
|
@@ -1299,12 +1382,12 @@ function createLineWebhookSpool(options) {
|
|
|
1299
1382
|
let stopTask;
|
|
1300
1383
|
return {
|
|
1301
1384
|
accept: async (body) => {
|
|
1302
|
-
const events = body.events ?? [];
|
|
1303
|
-
if (events.length === 0) return;
|
|
1304
|
-
await monitor.admitBatch(events.map((event) => ({
|
|
1385
|
+
const events = (body.events ?? []).filter((event) => event.mode !== "standby");
|
|
1386
|
+
if (events.length === 0) return "ignored";
|
|
1387
|
+
return (await monitor.admitBatch(events.map((event) => ({
|
|
1305
1388
|
event,
|
|
1306
1389
|
destination: body.destination ?? ""
|
|
1307
|
-
})), { receivedAt: Date.now() });
|
|
1390
|
+
})), { receivedAt: Date.now() })).some((admission) => admission.kind === "durable") ? "durable" : "ignored";
|
|
1308
1391
|
},
|
|
1309
1392
|
start: () => {
|
|
1310
1393
|
if (!stopTask) monitor.start();
|
|
@@ -1331,9 +1414,17 @@ function createLineWebhookSpool(options) {
|
|
|
1331
1414
|
const DEFAULT_MEDIA_MAX_MB = 10;
|
|
1332
1415
|
function createLineBot(opts) {
|
|
1333
1416
|
const runtime = opts.runtime ?? createNonExitingRuntime();
|
|
1334
|
-
const
|
|
1417
|
+
const startupConfig = opts.config ?? getRuntimeConfig();
|
|
1418
|
+
const startupRuntimeConfig = getRuntimeConfigSnapshot();
|
|
1419
|
+
const startupRuntimeSourceConfig = getRuntimeConfigSourceSnapshot();
|
|
1420
|
+
const followsRuntimeConfig = opts.config === void 0 || startupRuntimeConfig === startupConfig || startupRuntimeSourceConfig !== null && selectApplicableRuntimeConfig({
|
|
1421
|
+
inputConfig: startupConfig,
|
|
1422
|
+
runtimeConfig: startupRuntimeConfig,
|
|
1423
|
+
runtimeSourceConfig: startupRuntimeSourceConfig
|
|
1424
|
+
}) === startupRuntimeConfig;
|
|
1425
|
+
const resolveTurnConfig = () => (followsRuntimeConfig ? getRuntimeConfigSnapshot() : void 0) ?? startupConfig;
|
|
1335
1426
|
const account = resolveLineAccount({
|
|
1336
|
-
cfg,
|
|
1427
|
+
cfg: startupConfig,
|
|
1337
1428
|
accountId: opts.accountId
|
|
1338
1429
|
});
|
|
1339
1430
|
const mediaMaxBytes = ([opts.mediaMaxMb, account.config.mediaMaxMb].find((value) => typeof value === "number" && value > 0) ?? DEFAULT_MEDIA_MAX_MB) * 1024 * 1024;
|
|
@@ -1344,17 +1435,20 @@ function createLineBot(opts) {
|
|
|
1344
1435
|
const spool = createLineWebhookSpool({
|
|
1345
1436
|
accountId: account.accountId,
|
|
1346
1437
|
runtime,
|
|
1347
|
-
deliver: async (event, _destination, control) =>
|
|
1348
|
-
cfg
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1438
|
+
deliver: async (event, _destination, control) => {
|
|
1439
|
+
const cfg = resolveTurnConfig();
|
|
1440
|
+
await handleLineWebhookEvents([event], {
|
|
1441
|
+
cfg,
|
|
1442
|
+
account,
|
|
1443
|
+
runtime,
|
|
1444
|
+
buildContext: opts.buildContext,
|
|
1445
|
+
mediaMaxBytes,
|
|
1446
|
+
processMessage,
|
|
1447
|
+
...control.turnAdoptionLifecycle ? { turnAdoptionLifecycle: control.turnAdoptionLifecycle } : {},
|
|
1448
|
+
groupHistories,
|
|
1449
|
+
historyLimit: account.config.historyLimit ?? cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1358
1452
|
});
|
|
1359
1453
|
spool.start();
|
|
1360
1454
|
return {
|
|
@@ -1380,7 +1474,8 @@ function resolveLineDurableReplyOptions(params) {
|
|
|
1380
1474
|
//#endregion
|
|
1381
1475
|
//#region extensions/line/src/signature.ts
|
|
1382
1476
|
function validateLineSignature(body, signature, channelSecret) {
|
|
1383
|
-
|
|
1477
|
+
const hash = crypto.createHmac("SHA256", channelSecret).update(body).digest("base64");
|
|
1478
|
+
return safeEqualSecret(signature, hash);
|
|
1384
1479
|
}
|
|
1385
1480
|
//#endregion
|
|
1386
1481
|
//#region extensions/line/src/webhook-utils.ts
|
|
@@ -1393,8 +1488,8 @@ function parseLineWebhookBody(rawBody) {
|
|
|
1393
1488
|
}
|
|
1394
1489
|
//#endregion
|
|
1395
1490
|
//#region extensions/line/src/webhook-node.ts
|
|
1396
|
-
const LINE_WEBHOOK_MAX_BODY_BYTES =
|
|
1397
|
-
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES$1 =
|
|
1491
|
+
const LINE_WEBHOOK_MAX_BODY_BYTES = 1048576;
|
|
1492
|
+
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES$1 = 65536;
|
|
1398
1493
|
const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS$1 = 5e3;
|
|
1399
1494
|
async function readLineWebhookRequestBody(req, maxBytes = LINE_WEBHOOK_MAX_BODY_BYTES, timeoutMs = LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS$1) {
|
|
1400
1495
|
return await readRequestBodyWithLimit(req, {
|
|
@@ -1482,7 +1577,7 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1482
1577
|
//#endregion
|
|
1483
1578
|
//#region extensions/line/src/monitor.ts
|
|
1484
1579
|
const lineWebhookInFlightLimiter = createWebhookInFlightLimiter();
|
|
1485
|
-
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES =
|
|
1580
|
+
const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 65536;
|
|
1486
1581
|
const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS = 5e3;
|
|
1487
1582
|
async function registerLineWebhookTarget(params, bot) {
|
|
1488
1583
|
try {
|
|
@@ -1530,20 +1625,22 @@ async function monitorLineProvider(opts) {
|
|
|
1530
1625
|
onMessage: async (ctx, deliveryControl) => {
|
|
1531
1626
|
if (!ctx) return;
|
|
1532
1627
|
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,
|
|
1628
|
+
const turnConfig = deliveryControl.cfg;
|
|
1629
|
+
const stopLoading = Boolean(ctx.userId && !ctx.isGroup) ? startLineLoadingKeepalive({
|
|
1630
|
+
cfg: turnConfig,
|
|
1540
1631
|
userId: ctx.userId,
|
|
1541
1632
|
accountId: ctx.accountId
|
|
1542
1633
|
}) : null;
|
|
1543
|
-
logVerbose(`line: received message from ${
|
|
1634
|
+
logVerbose(`line: received message from ${ctxPayload.SenderName ?? ctx.userId ?? ctxPayload.From} (${ctxPayload.From})`);
|
|
1544
1635
|
let replyTokenUsed = false;
|
|
1545
1636
|
let turnAdopted = false;
|
|
1546
1637
|
const ingressLifecycle = deliveryControl.turnAdoptionLifecycle;
|
|
1638
|
+
const turnAbortSignal = ingressLifecycle?.abortSignal;
|
|
1639
|
+
const skillFilter = ctx.skillFilter;
|
|
1640
|
+
const replyOptions = turnAbortSignal || skillFilter ? {
|
|
1641
|
+
...turnAbortSignal ? { abortSignal: turnAbortSignal } : {},
|
|
1642
|
+
...skillFilter ? { skillFilter } : {}
|
|
1643
|
+
} : void 0;
|
|
1547
1644
|
try {
|
|
1548
1645
|
const textLimit = 5e3;
|
|
1549
1646
|
const turnResult = await getLineRuntime().channel.inbound.run({
|
|
@@ -1564,7 +1661,7 @@ async function monitorLineProvider(opts) {
|
|
|
1564
1661
|
rawText: ctxPayload.RawBody ?? ctxPayload.BodyForAgent ?? ""
|
|
1565
1662
|
}),
|
|
1566
1663
|
resolveTurn: () => ({
|
|
1567
|
-
cfg:
|
|
1664
|
+
cfg: turnConfig,
|
|
1568
1665
|
channel: "line",
|
|
1569
1666
|
accountId: route.accountId,
|
|
1570
1667
|
route: {
|
|
@@ -1574,8 +1671,10 @@ async function monitorLineProvider(opts) {
|
|
|
1574
1671
|
ctxPayload,
|
|
1575
1672
|
record: ctx.turn.record,
|
|
1576
1673
|
replyPipeline: {},
|
|
1577
|
-
|
|
1674
|
+
dispatcherOptions: { humanDelay: resolveHumanDelayConfig(turnConfig, route.agentId) },
|
|
1675
|
+
...replyOptions ? { replyOptions } : {},
|
|
1578
1676
|
delivery: {
|
|
1677
|
+
preparePayload: prepareLineReplyPayload,
|
|
1579
1678
|
durable: (payload, info) => resolveLineDurableReplyOptions({
|
|
1580
1679
|
payload,
|
|
1581
1680
|
infoKind: info.kind,
|
|
@@ -1586,7 +1685,7 @@ async function monitorLineProvider(opts) {
|
|
|
1586
1685
|
deliver: async (payload) => {
|
|
1587
1686
|
const lineData = payload.channelData?.line ?? {};
|
|
1588
1687
|
if (ctx.userId && !ctx.isGroup) showLoadingAnimation(ctx.userId, {
|
|
1589
|
-
cfg:
|
|
1688
|
+
cfg: turnConfig,
|
|
1590
1689
|
accountId: ctx.accountId
|
|
1591
1690
|
}).catch(() => {});
|
|
1592
1691
|
const deliveryResult = await deliverLineAutoReply({
|
|
@@ -1596,14 +1695,13 @@ async function monitorLineProvider(opts) {
|
|
|
1596
1695
|
replyToken,
|
|
1597
1696
|
replyTokenUsed,
|
|
1598
1697
|
accountId: ctx.accountId,
|
|
1599
|
-
cfg:
|
|
1698
|
+
cfg: turnConfig,
|
|
1600
1699
|
textLimit,
|
|
1601
1700
|
deps: {
|
|
1602
1701
|
buildTemplateMessageFromPayload,
|
|
1603
1702
|
processLineMessage,
|
|
1604
1703
|
chunkMarkdownText,
|
|
1605
1704
|
replyMessageLine,
|
|
1606
|
-
createQuickReplyItems,
|
|
1607
1705
|
pushMessagesLine,
|
|
1608
1706
|
createFlexMessage,
|
|
1609
1707
|
buildMediaMessage: buildLineMediaMessage,
|
|
@@ -1624,7 +1722,8 @@ async function monitorLineProvider(opts) {
|
|
|
1624
1722
|
})
|
|
1625
1723
|
}
|
|
1626
1724
|
});
|
|
1627
|
-
|
|
1725
|
+
const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
|
|
1726
|
+
if (!hasFinalInboundReplyDispatch(dispatchResult)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
|
|
1628
1727
|
} catch (err) {
|
|
1629
1728
|
runtime.error?.(danger(`line: auto-reply failed: ${String(err)}`));
|
|
1630
1729
|
if (turnAdopted || replyTokenUsed) throw new LineWebhookTerminalDeliveryError("LINE delivery failed after consuming the event reply token.", { cause: err });
|
|
@@ -1711,8 +1810,7 @@ async function monitorLineProvider(opts) {
|
|
|
1711
1810
|
}
|
|
1712
1811
|
if (body.events && body.events.length > 0) {
|
|
1713
1812
|
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
1714
|
-
await match.target.bot.handleWebhook(body);
|
|
1715
|
-
res.setHeader("x-openclaw-delivery-accepted", "durable");
|
|
1813
|
+
if (await match.target.bot.handleWebhook(body) === "durable") res.setHeader("x-openclaw-delivery-accepted", "durable");
|
|
1716
1814
|
}
|
|
1717
1815
|
res.statusCode = 200;
|
|
1718
1816
|
res.setHeader("Content-Type", "application/json");
|