@openclaw/line 2026.5.7 → 2026.5.10-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +1 -1
- package/dist/{channel-CnhV8Dzi.js → channel-CQemYfAf.js} +66 -16
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-7a5SEVBm.js +4 -0
- package/dist/{markdown-to-line-CCWK6PJh.js → markdown-to-line-DASNAjBq.js} +23 -3
- package/dist/{monitor-B4_RxHc0.js → monitor-PVeb2tJQ.js} +173 -184
- package/dist/monitor.runtime-D0Xj2uQz.js +2 -0
- package/dist/{outbound.runtime-B-luYwEB.js → outbound.runtime-zBxAf-it.js} +1 -1
- package/dist/runtime-api.js +19 -6
- package/dist/{outbound-media-BfhBmWq5.js → send-receipt-DSg7S42s.js} +19 -1
- package/package.json +4 -4
- package/dist/channel.runtime-bXoj8_Sl.js +0 -4
- package/dist/monitor.runtime-DyxYlrM9.js +0 -2
package/dist/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-
|
|
1
|
+
import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-CQemYfAf.js";
|
|
2
2
|
import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-DCH7CE-y.js";
|
|
3
3
|
//#region extensions/line/src/channel.setup.ts
|
|
4
4
|
const lineSetupPlugin = {
|
|
@@ -2,7 +2,7 @@ import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLin
|
|
|
2
2
|
import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-DCH7CE-y.js";
|
|
3
3
|
import { i as resolveExactLineGroupConfigKey, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-HskUUWsn.js";
|
|
4
4
|
import { n as parseLineDirectives, o as LineChannelConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-Bmf8GnSU.js";
|
|
5
|
-
import {
|
|
5
|
+
import { n as resolveLineOutboundMedia, t as createLineSendReceipt } from "./send-receipt-DSg7S42s.js";
|
|
6
6
|
import { createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
7
7
|
import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
|
|
8
8
|
import { createRestrictSendersChannelSecurity, resolveChannelGroupRequireMention } from "openclaw/plugin-sdk/channel-policy";
|
|
@@ -12,6 +12,7 @@ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/
|
|
|
12
12
|
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
|
|
13
13
|
import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
14
14
|
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
15
|
+
import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
|
|
15
16
|
import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
16
17
|
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
17
18
|
import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "openclaw/plugin-sdk/status-helpers";
|
|
@@ -107,7 +108,7 @@ const lineChannelPluginCommon = {
|
|
|
107
108
|
//#endregion
|
|
108
109
|
//#region extensions/line/src/gateway.ts
|
|
109
110
|
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-N91OWW1J.js"));
|
|
110
|
-
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-
|
|
111
|
+
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-D0Xj2uQz.js"));
|
|
111
112
|
const lineGatewayAdapter = {
|
|
112
113
|
startAccount: async (ctx) => {
|
|
113
114
|
const account = ctx.account;
|
|
@@ -211,7 +212,7 @@ function resolveLineGroupRequireMention(params) {
|
|
|
211
212
|
}
|
|
212
213
|
//#endregion
|
|
213
214
|
//#region extensions/line/src/outbound.ts
|
|
214
|
-
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-
|
|
215
|
+
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-zBxAf-it.js"));
|
|
215
216
|
function isLineUserTarget(target) {
|
|
216
217
|
const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
|
|
217
218
|
return /^U/i.test(normalized);
|
|
@@ -265,17 +266,11 @@ const lineOutboundAdapter = {
|
|
|
265
266
|
const quickReply = hasQuickReplies ? (lineRuntime?.createQuickReplyItems ?? outboundRuntime.createQuickReplyItems)(quickReplies) : void 0;
|
|
266
267
|
const sendMessageBatch = async (messages) => {
|
|
267
268
|
if (messages.length === 0) return;
|
|
268
|
-
for (let i = 0; i < messages.length; i += 5) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
});
|
|
274
|
-
lastResult = {
|
|
275
|
-
messageId: result.messageId,
|
|
276
|
-
chatId: result.chatId
|
|
277
|
-
};
|
|
278
|
-
}
|
|
269
|
+
for (let i = 0; i < messages.length; i += 5) lastResult = await sendBatch(to, messages.slice(i, i + 5), {
|
|
270
|
+
verbose: false,
|
|
271
|
+
cfg,
|
|
272
|
+
accountId: accountId ?? void 0
|
|
273
|
+
});
|
|
279
274
|
};
|
|
280
275
|
const processed = payload.text ? outboundRuntime.processLineMessage(payload.text) : {
|
|
281
276
|
text: "",
|
|
@@ -437,7 +432,12 @@ const lineOutboundAdapter = {
|
|
|
437
432
|
});
|
|
438
433
|
else result = {
|
|
439
434
|
messageId: "processed",
|
|
440
|
-
chatId: to
|
|
435
|
+
chatId: to,
|
|
436
|
+
receipt: createLineSendReceipt({
|
|
437
|
+
messageId: "processed",
|
|
438
|
+
chatId: to,
|
|
439
|
+
kind: "card"
|
|
440
|
+
})
|
|
441
441
|
};
|
|
442
442
|
for (const flexMsg of processed.flexMessages) {
|
|
443
443
|
const flexContents = flexMsg.contents;
|
|
@@ -457,6 +457,55 @@ const lineOutboundAdapter = {
|
|
|
457
457
|
})
|
|
458
458
|
})
|
|
459
459
|
};
|
|
460
|
+
function toLineMessageSendResult(result, kind) {
|
|
461
|
+
const source = result;
|
|
462
|
+
const receipt = result.receipt ?? (result.messageId ? createLineSendReceipt({
|
|
463
|
+
messageId: result.messageId,
|
|
464
|
+
chatId: source.chatId ?? "",
|
|
465
|
+
kind
|
|
466
|
+
}) : void 0);
|
|
467
|
+
if (!receipt) throw new Error("LINE message adapter send did not return a receipt");
|
|
468
|
+
return {
|
|
469
|
+
messageId: result.messageId || receipt.primaryPlatformMessageId,
|
|
470
|
+
receipt
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
const lineMessageAdapter = defineChannelMessageAdapter({
|
|
474
|
+
id: "line",
|
|
475
|
+
durableFinal: { capabilities: {
|
|
476
|
+
text: true,
|
|
477
|
+
media: true,
|
|
478
|
+
messageSendingHooks: true
|
|
479
|
+
} },
|
|
480
|
+
send: {
|
|
481
|
+
text: async ({ cfg, to, text, accountId }) => {
|
|
482
|
+
return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
|
|
483
|
+
cfg,
|
|
484
|
+
to,
|
|
485
|
+
text,
|
|
486
|
+
accountId,
|
|
487
|
+
payload: { text }
|
|
488
|
+
}), "text");
|
|
489
|
+
},
|
|
490
|
+
media: async ({ cfg, to, text, mediaUrl, accountId }) => {
|
|
491
|
+
return toLineMessageSendResult(await lineOutboundAdapter.sendPayload({
|
|
492
|
+
cfg,
|
|
493
|
+
to,
|
|
494
|
+
text,
|
|
495
|
+
mediaUrl,
|
|
496
|
+
accountId,
|
|
497
|
+
payload: {
|
|
498
|
+
text,
|
|
499
|
+
mediaUrl
|
|
500
|
+
}
|
|
501
|
+
}), "media");
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
receive: {
|
|
505
|
+
defaultAckPolicy: "after_agent_dispatch",
|
|
506
|
+
supportedAckPolicies: ["after_receive_record", "after_agent_dispatch"]
|
|
507
|
+
}
|
|
508
|
+
});
|
|
460
509
|
//#endregion
|
|
461
510
|
//#region extensions/line/src/status.ts
|
|
462
511
|
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-N91OWW1J.js"));
|
|
@@ -484,7 +533,7 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
|
|
|
484
533
|
});
|
|
485
534
|
//#endregion
|
|
486
535
|
//#region extensions/line/src/channel.ts
|
|
487
|
-
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-
|
|
536
|
+
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-7a5SEVBm.js"));
|
|
488
537
|
const lineSecurityAdapter = createRestrictSendersChannelSecurity({
|
|
489
538
|
channelKey: "line",
|
|
490
539
|
resolveDmPolicy: (account) => account.config.dmPolicy,
|
|
@@ -530,6 +579,7 @@ const linePlugin = createChatChannelPlugin({
|
|
|
530
579
|
setup: lineSetupAdapter,
|
|
531
580
|
status: lineStatusAdapter,
|
|
532
581
|
gateway: lineGatewayAdapter,
|
|
582
|
+
message: lineMessageAdapter,
|
|
533
583
|
bindings: lineBindingsAdapter,
|
|
534
584
|
conversationBindings: { defaultTopLevelPlacement: "current" },
|
|
535
585
|
agentPrompt: { messageToolHints: () => [
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as linePlugin } from "./channel-
|
|
1
|
+
import { t as linePlugin } from "./channel-CQemYfAf.js";
|
|
2
2
|
export { linePlugin };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as resolveLineAccount } from "./accounts-UXAvdBo_.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as validateLineMediaUrl, t as createLineSendReceipt } from "./send-receipt-DSg7S42s.js";
|
|
3
3
|
import { r as createReceiptCard } from "./schedule-cards-BwQChTdM.js";
|
|
4
4
|
import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-runtime";
|
|
5
5
|
import { messagingApi } from "@line/bot-sdk";
|
|
@@ -319,6 +319,14 @@ function recordLineOutboundActivity(accountId) {
|
|
|
319
319
|
direction: "outbound"
|
|
320
320
|
});
|
|
321
321
|
}
|
|
322
|
+
function resolveLineReceiptKind(messages) {
|
|
323
|
+
const types = new Set(messages.map((message) => message.type));
|
|
324
|
+
if (types.has("audio")) return "voice";
|
|
325
|
+
if (types.has("image") || types.has("video")) return "media";
|
|
326
|
+
if (types.has("flex") || types.has("template") || types.has("location")) return "card";
|
|
327
|
+
if (types.has("text")) return "text";
|
|
328
|
+
return "unknown";
|
|
329
|
+
}
|
|
322
330
|
async function pushLineMessages(to, messages, opts, behavior = {}) {
|
|
323
331
|
if (messages.length === 0) throw new Error("Message must be non-empty for LINE sends");
|
|
324
332
|
const { account, client, chatId } = createLinePushContext(to, opts);
|
|
@@ -335,7 +343,13 @@ async function pushLineMessages(to, messages, opts, behavior = {}) {
|
|
|
335
343
|
if (opts.verbose) logVerbose(behavior.verboseMessage?.(chatId, messages.length) ?? `line: pushed ${messages.length} messages to ${chatId}`);
|
|
336
344
|
return {
|
|
337
345
|
messageId: "push",
|
|
338
|
-
chatId
|
|
346
|
+
chatId,
|
|
347
|
+
receipt: createLineSendReceipt({
|
|
348
|
+
messageId: "push",
|
|
349
|
+
chatId,
|
|
350
|
+
kind: resolveLineReceiptKind(messages),
|
|
351
|
+
messageCount: messages.length
|
|
352
|
+
})
|
|
339
353
|
};
|
|
340
354
|
}
|
|
341
355
|
async function replyLineMessages(replyToken, messages, opts, behavior = {}) {
|
|
@@ -380,7 +394,13 @@ async function sendMessageLine(to, text, opts) {
|
|
|
380
394
|
await replyLineMessages(opts.replyToken, messages, opts, { verboseMessage: () => `line: replied to ${chatId}` });
|
|
381
395
|
return {
|
|
382
396
|
messageId: "reply",
|
|
383
|
-
chatId
|
|
397
|
+
chatId,
|
|
398
|
+
receipt: createLineSendReceipt({
|
|
399
|
+
messageId: "reply",
|
|
400
|
+
chatId,
|
|
401
|
+
kind: resolveLineReceiptKind(messages),
|
|
402
|
+
messageCount: messages.length
|
|
403
|
+
})
|
|
384
404
|
};
|
|
385
405
|
}
|
|
386
406
|
return pushLineMessages(chatId, messages, opts, { verboseMessage: (resolvedChatId) => `line: pushed message to ${resolvedChatId}` });
|
|
@@ -1,50 +1,44 @@
|
|
|
1
1
|
import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-UXAvdBo_.js";
|
|
2
2
|
import { a as resolveLineGroupConfigEntry, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-HskUUWsn.js";
|
|
3
|
-
import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, E as replyMessageLine, O as showLoadingAnimation, S as pushMessageLine, T as pushTextMessageWithQuickReplies, _ as getUserDisplayName, c as processLineMessage, d as createFlexMessage, f as createImageMessage, h as createTextMessageWithQuickReplies, m as createQuickReplyItems, p as createLocationMessage } from "./markdown-to-line-
|
|
3
|
+
import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, E as replyMessageLine, O as showLoadingAnimation, S as pushMessageLine, T as pushTextMessageWithQuickReplies, _ as getUserDisplayName, c as processLineMessage, d as createFlexMessage, f as createImageMessage, h as createTextMessageWithQuickReplies, m as createQuickReplyItems, p as createLocationMessage } from "./markdown-to-line-DASNAjBq.js";
|
|
4
4
|
import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
|
|
5
|
+
import { createMessageReceiveContext, hasFinalChannelTurnDispatch } from "openclaw/plugin-sdk/channel-message";
|
|
5
6
|
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
|
|
6
|
-
import { lowercasePreservingWhitespace, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
|
7
|
-
import { firstDefined
|
|
7
|
+
import { lowercasePreservingWhitespace, normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/text-runtime";
|
|
8
|
+
import { firstDefined } from "openclaw/plugin-sdk/allow-from";
|
|
8
9
|
import { messagingApi } from "@line/bot-sdk";
|
|
9
10
|
import { saveMediaBuffer } from "openclaw/plugin-sdk/media-store";
|
|
10
11
|
import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
|
|
11
12
|
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
12
|
-
import { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline";
|
|
13
|
-
import { hasFinalInboundReplyDispatch } from "openclaw/plugin-sdk/inbound-reply-dispatch";
|
|
14
13
|
import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
|
|
15
14
|
import { isRequestBodyLimitError, normalizePluginHttpPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
|
|
16
15
|
import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1 } from "openclaw/plugin-sdk/webhook-request-guards";
|
|
17
16
|
import { DEFAULT_GROUP_HISTORY_LIMIT, clearHistoryEntriesIfEnabled, recordPendingHistoryEntryIfEnabled } from "openclaw/plugin-sdk/reply-history";
|
|
18
17
|
import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
19
|
-
import { buildMentionRegexes, formatInboundEnvelope, formatLocationText, matchesMentionPatterns,
|
|
20
|
-
import {
|
|
18
|
+
import { buildMentionRegexes, formatInboundEnvelope, formatLocationText, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
19
|
+
import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
20
|
+
import { shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-auth-native";
|
|
21
21
|
import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolvePairingIdLabel, resolvePinnedMainDmOwnerFromAllowlist, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
|
|
22
|
-
import { evaluateMatchedGroupAccessForPolicy } from "openclaw/plugin-sdk/group-access";
|
|
23
22
|
import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
|
|
24
23
|
import { resolveAgentRoute } from "openclaw/plugin-sdk/routing";
|
|
25
24
|
import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
|
|
26
25
|
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
|
|
27
26
|
import crypto from "node:crypto";
|
|
28
27
|
//#region extensions/line/src/bot-access.ts
|
|
29
|
-
function
|
|
28
|
+
function normalizeLineAllowEntry(value) {
|
|
30
29
|
const trimmed = String(value).trim();
|
|
31
30
|
if (!trimmed) return "";
|
|
32
31
|
if (trimmed === "*") return "*";
|
|
33
32
|
return trimmed.replace(/^line:(?:user:)?/i, "");
|
|
34
33
|
}
|
|
35
34
|
const normalizeAllowFrom = (list) => {
|
|
36
|
-
const entries = (list ?? []).map((value) =>
|
|
35
|
+
const entries = (list ?? []).map((value) => normalizeLineAllowEntry(value)).filter(Boolean);
|
|
37
36
|
return {
|
|
38
37
|
entries,
|
|
39
38
|
hasWildcard: entries.includes("*"),
|
|
40
39
|
hasEntries: entries.length > 0
|
|
41
40
|
};
|
|
42
41
|
};
|
|
43
|
-
const normalizeDmAllowFromWithStore = (params) => normalizeAllowFrom(mergeDmAllowFromSources(params));
|
|
44
|
-
const isSenderAllowed = (params) => {
|
|
45
|
-
const { allow, senderId } = params;
|
|
46
|
-
return isSenderIdAllowed(allow, senderId, false);
|
|
47
|
-
};
|
|
48
42
|
//#endregion
|
|
49
43
|
//#region extensions/line/src/download.ts
|
|
50
44
|
const AUDIO_BRANDS = new Set([
|
|
@@ -581,6 +575,9 @@ function isDownloadableLineMessageType(messageType) {
|
|
|
581
575
|
}
|
|
582
576
|
const LINE_WEBHOOK_REPLAY_WINDOW_MS = 600 * 1e3;
|
|
583
577
|
const LINE_WEBHOOK_REPLAY_MAX_ENTRIES = 4096;
|
|
578
|
+
function normalizeLineIngressEntry(value) {
|
|
579
|
+
return normalizeLineAllowEntry(value) || null;
|
|
580
|
+
}
|
|
584
581
|
var LineRetryableWebhookError = class extends Error {
|
|
585
582
|
constructor(message, options) {
|
|
586
583
|
super(message, options);
|
|
@@ -689,33 +686,91 @@ async function sendLinePairingReply(params) {
|
|
|
689
686
|
});
|
|
690
687
|
}
|
|
691
688
|
async function shouldProcessLineEvent(event, context) {
|
|
692
|
-
const denied = {
|
|
693
|
-
allowed: false,
|
|
694
|
-
commandAuthorized: false
|
|
695
|
-
};
|
|
696
689
|
const { cfg, account } = context;
|
|
697
690
|
const { userId, groupId, roomId, isGroup } = getLineSourceInfo(event.source);
|
|
698
691
|
const senderId = userId ?? "";
|
|
699
|
-
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
700
|
-
const storeAllowFrom = await readChannelAllowFromStore("line", void 0, account.accountId).catch(() => []);
|
|
701
|
-
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
|
702
|
-
allowFrom: account.config.allowFrom,
|
|
703
|
-
storeAllowFrom,
|
|
704
|
-
dmPolicy
|
|
705
|
-
});
|
|
706
692
|
const groupConfig = resolveLineGroupConfig({
|
|
707
693
|
config: account.config,
|
|
708
694
|
groupId,
|
|
709
695
|
roomId
|
|
710
696
|
});
|
|
711
|
-
const
|
|
712
|
-
const
|
|
713
|
-
const
|
|
714
|
-
const
|
|
715
|
-
const { groupPolicy, providerMissingFallbackApplied } = resolveAllowlistProviderRuntimeGroupPolicy({
|
|
697
|
+
const rawText = resolveEventRawText(event);
|
|
698
|
+
const requireMention = isGroup ? groupConfig?.requireMention !== false : false;
|
|
699
|
+
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
700
|
+
const { groupPolicy: runtimeGroupPolicy, providerMissingFallbackApplied } = resolveAllowlistProviderRuntimeGroupPolicy({
|
|
716
701
|
providerConfigPresent: cfg.channels?.line !== void 0,
|
|
717
702
|
groupPolicy: account.config.groupPolicy,
|
|
718
|
-
defaultGroupPolicy
|
|
703
|
+
defaultGroupPolicy: resolveDefaultGroupPolicy(cfg)
|
|
704
|
+
});
|
|
705
|
+
const groupPolicy = runtimeGroupPolicy === "disabled" ? "disabled" : groupConfig?.allowFrom !== void 0 ? "allowlist" : runtimeGroupPolicy;
|
|
706
|
+
const groupAllowFrom = normalizeStringEntries(firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom, account.config.allowFrom?.length ? account.config.allowFrom : void 0));
|
|
707
|
+
const mentionFacts = (() => {
|
|
708
|
+
if (!isGroup || event.type !== "message") return {
|
|
709
|
+
canDetectMention: false,
|
|
710
|
+
wasMentioned: false,
|
|
711
|
+
hasAnyMention: false
|
|
712
|
+
};
|
|
713
|
+
const peerId = groupId ?? roomId ?? userId ?? "unknown";
|
|
714
|
+
const { agentId } = resolveAgentRoute({
|
|
715
|
+
cfg,
|
|
716
|
+
channel: "line",
|
|
717
|
+
accountId: account.accountId,
|
|
718
|
+
peer: {
|
|
719
|
+
kind: "group",
|
|
720
|
+
id: peerId
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
const mentionRegexes = buildMentionRegexes(cfg, agentId);
|
|
724
|
+
const wasMentionedByNative = isLineBotMentioned(event.message);
|
|
725
|
+
const wasMentionedByPattern = event.message.type === "text" ? matchesMentionPatterns(rawText, mentionRegexes) : false;
|
|
726
|
+
return {
|
|
727
|
+
canDetectMention: event.message.type === "text",
|
|
728
|
+
wasMentioned: wasMentionedByNative || wasMentionedByPattern,
|
|
729
|
+
hasAnyMention: hasAnyLineMention(event.message)
|
|
730
|
+
};
|
|
731
|
+
})();
|
|
732
|
+
const access = await resolveStableChannelMessageIngress({
|
|
733
|
+
channelId: "line",
|
|
734
|
+
accountId: account.accountId,
|
|
735
|
+
identity: {
|
|
736
|
+
key: "line-user-id",
|
|
737
|
+
normalize: normalizeLineIngressEntry,
|
|
738
|
+
sensitivity: "pii",
|
|
739
|
+
entryIdPrefix: "line-entry"
|
|
740
|
+
},
|
|
741
|
+
cfg,
|
|
742
|
+
readStoreAllowFrom: async () => await readChannelAllowFromStore("line", void 0, account.accountId),
|
|
743
|
+
subject: { stableId: senderId },
|
|
744
|
+
conversation: {
|
|
745
|
+
kind: isGroup ? "group" : "direct",
|
|
746
|
+
id: (groupId ?? roomId ?? senderId) || "unknown"
|
|
747
|
+
},
|
|
748
|
+
...isGroup && groupConfig?.enabled === false ? { route: {
|
|
749
|
+
id: "line:group-config",
|
|
750
|
+
enabled: false
|
|
751
|
+
} } : {},
|
|
752
|
+
mentionFacts: isGroup && event.type === "message" ? {
|
|
753
|
+
canDetectMention: mentionFacts.canDetectMention,
|
|
754
|
+
wasMentioned: mentionFacts.wasMentioned,
|
|
755
|
+
hasAnyMention: mentionFacts.hasAnyMention,
|
|
756
|
+
implicitMentionKinds: []
|
|
757
|
+
} : void 0,
|
|
758
|
+
event: { kind: event.type === "postback" ? "postback" : "message" },
|
|
759
|
+
dmPolicy,
|
|
760
|
+
groupPolicy,
|
|
761
|
+
policy: {
|
|
762
|
+
groupAllowFromFallbackToAllowFrom: false,
|
|
763
|
+
activation: {
|
|
764
|
+
requireMention: isGroup && event.type === "message" && requireMention,
|
|
765
|
+
allowTextCommands: true
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
allowFrom: normalizeStringEntries(account.config.allowFrom),
|
|
769
|
+
groupAllowFrom,
|
|
770
|
+
command: {
|
|
771
|
+
hasControlCommand: shouldComputeCommandAuthorized(rawText, cfg),
|
|
772
|
+
groupOwnerAllowFrom: "none"
|
|
773
|
+
}
|
|
719
774
|
});
|
|
720
775
|
warnMissingProviderGroupPolicyFallbackOnce({
|
|
721
776
|
providerMissingFallbackApplied,
|
|
@@ -723,90 +778,50 @@ async function shouldProcessLineEvent(event, context) {
|
|
|
723
778
|
accountId: account.accountId,
|
|
724
779
|
log: (message) => logVerbose(message)
|
|
725
780
|
});
|
|
781
|
+
if (access.senderAccess.decision === "allow" && (access.ingress.admission === "dispatch" || access.ingress.admission === "observe" || access.ingress.admission === "skip")) return access;
|
|
782
|
+
if (access.senderAccess.decision === "allow") {
|
|
783
|
+
logVerbose(`Blocked line event (${access.ingress.reasonCode})`);
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
726
786
|
if (isGroup) {
|
|
727
787
|
if (groupConfig?.enabled === false) {
|
|
728
788
|
logVerbose(`Blocked line group ${groupId ?? roomId ?? "unknown"} (group disabled)`);
|
|
729
|
-
return
|
|
789
|
+
return null;
|
|
730
790
|
}
|
|
731
|
-
if (
|
|
791
|
+
if (groupConfig?.allowFrom !== void 0) {
|
|
732
792
|
if (!senderId) {
|
|
733
793
|
logVerbose("Blocked line group message (group allowFrom override, no sender ID)");
|
|
734
|
-
return
|
|
794
|
+
return null;
|
|
735
795
|
}
|
|
736
|
-
if (
|
|
737
|
-
allow: effectiveGroupAllow,
|
|
738
|
-
senderId
|
|
739
|
-
})) {
|
|
796
|
+
if (access.senderAccess.reasonCode !== "group_policy_allowed") {
|
|
740
797
|
logVerbose(`Blocked line group sender ${senderId} (group allowFrom override)`);
|
|
741
|
-
return
|
|
798
|
+
return null;
|
|
742
799
|
}
|
|
743
800
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
allowlistMatched: Boolean(senderId) && isSenderAllowed({
|
|
750
|
-
allow: effectiveGroupAllow,
|
|
751
|
-
senderId
|
|
752
|
-
})
|
|
753
|
-
});
|
|
754
|
-
if (!senderGroupAccess.allowed && senderGroupAccess.reason === "disabled") {
|
|
755
|
-
logVerbose("Blocked line group message (groupPolicy: disabled)");
|
|
756
|
-
return denied;
|
|
757
|
-
}
|
|
758
|
-
if (!senderGroupAccess.allowed && senderGroupAccess.reason === "missing_match_input") {
|
|
759
|
-
logVerbose("Blocked line group message (no sender ID, groupPolicy: allowlist)");
|
|
760
|
-
return denied;
|
|
761
|
-
}
|
|
762
|
-
if (!senderGroupAccess.allowed && senderGroupAccess.reason === "empty_allowlist") {
|
|
763
|
-
logVerbose("Blocked line group message (groupPolicy: allowlist, no groupAllowFrom)");
|
|
764
|
-
return denied;
|
|
765
|
-
}
|
|
766
|
-
if (!senderGroupAccess.allowed && senderGroupAccess.reason === "not_allowlisted") {
|
|
767
|
-
logVerbose(`Blocked line group message from ${senderId} (groupPolicy: allowlist)`);
|
|
768
|
-
return denied;
|
|
769
|
-
}
|
|
770
|
-
return {
|
|
771
|
-
allowed: true,
|
|
772
|
-
commandAuthorized: resolveLineCommandAuthorized({
|
|
773
|
-
cfg,
|
|
774
|
-
event,
|
|
775
|
-
senderId,
|
|
776
|
-
allow: effectiveGroupAllow
|
|
777
|
-
})
|
|
778
|
-
};
|
|
801
|
+
if (access.senderAccess.reasonCode === "group_policy_disabled") logVerbose("Blocked line group message (groupPolicy: disabled)");
|
|
802
|
+
else if (!senderId && groupPolicy === "allowlist") logVerbose("Blocked line group message (no sender ID, groupPolicy: allowlist)");
|
|
803
|
+
else if (access.senderAccess.reasonCode === "group_policy_empty_allowlist") logVerbose("Blocked line group message (groupPolicy: allowlist, no groupAllowFrom)");
|
|
804
|
+
else logVerbose(`Blocked line group message from ${senderId} (groupPolicy: allowlist)`);
|
|
805
|
+
return null;
|
|
779
806
|
}
|
|
780
|
-
if (
|
|
807
|
+
if (access.senderAccess.reasonCode === "dm_policy_disabled") {
|
|
781
808
|
logVerbose("Blocked line sender (dmPolicy: disabled)");
|
|
782
|
-
return
|
|
783
|
-
}
|
|
784
|
-
if (!isSenderAllowed({
|
|
785
|
-
allow: effectiveDmAllow,
|
|
786
|
-
senderId
|
|
787
|
-
})) {
|
|
788
|
-
if (dmPolicy === "pairing") {
|
|
789
|
-
if (!senderId) {
|
|
790
|
-
logVerbose("Blocked line sender (dmPolicy: pairing, no sender ID)");
|
|
791
|
-
return denied;
|
|
792
|
-
}
|
|
793
|
-
await sendLinePairingReply({
|
|
794
|
-
senderId,
|
|
795
|
-
replyToken: "replyToken" in event ? event.replyToken : void 0,
|
|
796
|
-
context
|
|
797
|
-
});
|
|
798
|
-
} else logVerbose(`Blocked line sender ${senderId || "unknown"} (dmPolicy: ${dmPolicy})`);
|
|
799
|
-
return denied;
|
|
809
|
+
return null;
|
|
800
810
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
811
|
+
if (access.senderAccess.decision === "pairing") {
|
|
812
|
+
if (!senderId) {
|
|
813
|
+
logVerbose("Blocked line sender (dmPolicy: pairing, no sender ID)");
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
await sendLinePairingReply({
|
|
806
817
|
senderId,
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
818
|
+
replyToken: "replyToken" in event ? event.replyToken : void 0,
|
|
819
|
+
context
|
|
820
|
+
});
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
logVerbose(`Blocked line sender ${senderId || "unknown"} (dmPolicy: ${account.config.dmPolicy ?? "pairing"})`);
|
|
824
|
+
return null;
|
|
810
825
|
}
|
|
811
826
|
function getLineMentionees(message) {
|
|
812
827
|
if (message.type !== "text") return [];
|
|
@@ -828,81 +843,29 @@ function resolveEventRawText(event) {
|
|
|
828
843
|
if (event.type === "postback") return event.postback?.data?.trim() ?? "";
|
|
829
844
|
return "";
|
|
830
845
|
}
|
|
831
|
-
function resolveLineCommandAuthorized(params) {
|
|
832
|
-
const senderAllowedForCommands = isSenderAllowed({
|
|
833
|
-
allow: params.allow,
|
|
834
|
-
senderId: params.senderId
|
|
835
|
-
});
|
|
836
|
-
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
|
837
|
-
const rawText = resolveEventRawText(params.event);
|
|
838
|
-
return resolveControlCommandGate({
|
|
839
|
-
useAccessGroups,
|
|
840
|
-
authorizers: [{
|
|
841
|
-
configured: params.allow.hasEntries,
|
|
842
|
-
allowed: senderAllowedForCommands
|
|
843
|
-
}],
|
|
844
|
-
allowTextCommands: true,
|
|
845
|
-
hasControlCommand: hasControlCommand(rawText, params.cfg)
|
|
846
|
-
}).commandAuthorized;
|
|
847
|
-
}
|
|
848
846
|
async function handleMessageEvent(event, context) {
|
|
849
847
|
const { cfg, account, runtime, mediaMaxBytes, processMessage } = context;
|
|
850
848
|
const message = event.message;
|
|
851
849
|
const decision = await shouldProcessLineEvent(event, context);
|
|
852
|
-
if (!decision
|
|
850
|
+
if (!decision) return;
|
|
853
851
|
const { isGroup, groupId, roomId } = getLineSourceInfo(event.source);
|
|
854
|
-
if (isGroup) {
|
|
855
|
-
const requireMention = resolveLineGroupConfig({
|
|
856
|
-
config: account.config,
|
|
857
|
-
groupId,
|
|
858
|
-
roomId
|
|
859
|
-
})?.requireMention !== false;
|
|
852
|
+
if (isGroup && decision.activationAccess.shouldSkip) {
|
|
860
853
|
const rawText = message.type === "text" ? message.text : "";
|
|
861
854
|
const sourceInfo = getLineSourceInfo(event.source);
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
855
|
+
logVerbose(`line: skipping group message (requireMention, not mentioned)`);
|
|
856
|
+
const historyKey = groupId ?? roomId;
|
|
857
|
+
const senderId = sourceInfo.userId ?? "unknown";
|
|
858
|
+
if (historyKey && context.groupHistories) recordPendingHistoryEntryIfEnabled({
|
|
859
|
+
historyMap: context.groupHistories,
|
|
860
|
+
historyKey,
|
|
861
|
+
limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
|
|
862
|
+
entry: {
|
|
863
|
+
sender: `user:${senderId}`,
|
|
864
|
+
body: rawText || `<${message.type}>`,
|
|
865
|
+
timestamp: event.timestamp
|
|
870
866
|
}
|
|
871
867
|
});
|
|
872
|
-
|
|
873
|
-
const wasMentionedByNative = isLineBotMentioned(message);
|
|
874
|
-
const wasMentionedByPattern = message.type === "text" ? matchesMentionPatterns(rawText, mentionRegexes) : false;
|
|
875
|
-
const wasMentioned = wasMentionedByNative || wasMentionedByPattern;
|
|
876
|
-
if (resolveInboundMentionDecision({
|
|
877
|
-
facts: {
|
|
878
|
-
canDetectMention: message.type === "text",
|
|
879
|
-
wasMentioned,
|
|
880
|
-
hasAnyMention: hasAnyLineMention(message),
|
|
881
|
-
implicitMentionKinds: []
|
|
882
|
-
},
|
|
883
|
-
policy: {
|
|
884
|
-
isGroup: true,
|
|
885
|
-
requireMention,
|
|
886
|
-
allowTextCommands: true,
|
|
887
|
-
hasControlCommand: hasControlCommand(rawText, cfg),
|
|
888
|
-
commandAuthorized: decision.commandAuthorized
|
|
889
|
-
}
|
|
890
|
-
}).shouldSkip) {
|
|
891
|
-
logVerbose(`line: skipping group message (requireMention, not mentioned)`);
|
|
892
|
-
const historyKey = groupId ?? roomId;
|
|
893
|
-
const senderId = sourceInfo.userId ?? "unknown";
|
|
894
|
-
if (historyKey && context.groupHistories) recordPendingHistoryEntryIfEnabled({
|
|
895
|
-
historyMap: context.groupHistories,
|
|
896
|
-
historyKey,
|
|
897
|
-
limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
|
|
898
|
-
entry: {
|
|
899
|
-
sender: `user:${senderId}`,
|
|
900
|
-
body: rawText || `<${message.type}>`,
|
|
901
|
-
timestamp: event.timestamp
|
|
902
|
-
}
|
|
903
|
-
});
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
868
|
+
return;
|
|
906
869
|
}
|
|
907
870
|
const allMedia = [];
|
|
908
871
|
if (isDownloadableLineMessageType(message.type)) try {
|
|
@@ -921,7 +884,7 @@ async function handleMessageEvent(event, context) {
|
|
|
921
884
|
allMedia,
|
|
922
885
|
cfg,
|
|
923
886
|
account,
|
|
924
|
-
commandAuthorized: decision.
|
|
887
|
+
commandAuthorized: decision.commandAccess.authorized,
|
|
925
888
|
groupHistories: context.groupHistories,
|
|
926
889
|
historyLimit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
|
|
927
890
|
});
|
|
@@ -959,12 +922,12 @@ async function handlePostbackEvent(event, context) {
|
|
|
959
922
|
const data = event.postback.data;
|
|
960
923
|
logVerbose(`line: received postback: ${data}`);
|
|
961
924
|
const decision = await shouldProcessLineEvent(event, context);
|
|
962
|
-
if (!decision
|
|
925
|
+
if (!decision) return;
|
|
963
926
|
const postbackContext = await buildLinePostbackContext({
|
|
964
927
|
event,
|
|
965
928
|
cfg: context.cfg,
|
|
966
929
|
account: context.account,
|
|
967
|
-
commandAuthorized: decision.
|
|
930
|
+
commandAuthorized: decision.commandAccess.authorized
|
|
968
931
|
});
|
|
969
932
|
if (!postbackContext) return;
|
|
970
933
|
await context.processMessage(postbackContext);
|
|
@@ -1049,6 +1012,20 @@ function createLineBot(opts) {
|
|
|
1049
1012
|
};
|
|
1050
1013
|
}
|
|
1051
1014
|
//#endregion
|
|
1015
|
+
//#region extensions/line/src/monitor-durable.ts
|
|
1016
|
+
function hasLineChannelData(payload) {
|
|
1017
|
+
const lineData = payload.channelData?.line;
|
|
1018
|
+
return Boolean(lineData && Object.keys(lineData).length > 0);
|
|
1019
|
+
}
|
|
1020
|
+
function resolveLineDurableReplyOptions(params) {
|
|
1021
|
+
if (params.infoKind !== "final") return false;
|
|
1022
|
+
if (params.replyToken && !params.replyTokenUsed) return false;
|
|
1023
|
+
if (hasLineChannelData(params.payload)) return false;
|
|
1024
|
+
const reply = resolveSendableOutboundReplyParts(params.payload);
|
|
1025
|
+
if (reply.hasMedia || !reply.hasText) return false;
|
|
1026
|
+
return { to: params.to };
|
|
1027
|
+
}
|
|
1028
|
+
//#endregion
|
|
1052
1029
|
//#region extensions/line/src/reply-chunks.ts
|
|
1053
1030
|
async function sendLineReplyChunks(params) {
|
|
1054
1031
|
const hasQuickReplies = Boolean(params.quickReplies?.length);
|
|
@@ -1149,6 +1126,7 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1149
1126
|
res.end(JSON.stringify({ error: "Method Not Allowed" }));
|
|
1150
1127
|
return;
|
|
1151
1128
|
}
|
|
1129
|
+
let receiveContext;
|
|
1152
1130
|
try {
|
|
1153
1131
|
const signatureHeader = req.headers["x-line-signature"];
|
|
1154
1132
|
const signature = typeof signatureHeader === "string" ? signatureHeader.trim() : Array.isArray(signatureHeader) ? (signatureHeader[0] ?? "").trim() : "";
|
|
@@ -1175,14 +1153,25 @@ function createLineNodeWebhookHandler(params) {
|
|
|
1175
1153
|
return;
|
|
1176
1154
|
}
|
|
1177
1155
|
params.onRequestAuthenticated?.();
|
|
1156
|
+
receiveContext = createMessageReceiveContext({
|
|
1157
|
+
id: `${Date.now()}:line:webhook`,
|
|
1158
|
+
channel: "line",
|
|
1159
|
+
message: body,
|
|
1160
|
+
ackPolicy: body.events?.length ? "after_agent_dispatch" : "after_receive_record",
|
|
1161
|
+
onAck: () => {
|
|
1162
|
+
res.statusCode = 200;
|
|
1163
|
+
res.setHeader("Content-Type", "application/json");
|
|
1164
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1178
1167
|
if (body.events && body.events.length > 0) {
|
|
1179
1168
|
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
1180
1169
|
await params.bot.handleWebhook(body);
|
|
1181
1170
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
1171
|
+
const ackStage = body.events?.length ? "agent_dispatch" : "receive_record";
|
|
1172
|
+
if (receiveContext.shouldAckAfter(ackStage)) await receiveContext.ack();
|
|
1185
1173
|
} catch (err) {
|
|
1174
|
+
await receiveContext?.nack(err);
|
|
1186
1175
|
if (isRequestBodyLimitError$1(err, "PAYLOAD_TOO_LARGE")) {
|
|
1187
1176
|
res.statusCode = 413;
|
|
1188
1177
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -1287,12 +1276,6 @@ async function monitorLineProvider(opts) {
|
|
|
1287
1276
|
try {
|
|
1288
1277
|
const textLimit = 5e3;
|
|
1289
1278
|
let replyTokenUsed = false;
|
|
1290
|
-
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
|
|
1291
|
-
cfg: config,
|
|
1292
|
-
agentId: route.agentId,
|
|
1293
|
-
channel: "line",
|
|
1294
|
-
accountId: route.accountId
|
|
1295
|
-
});
|
|
1296
1279
|
const core = getLineRuntime();
|
|
1297
1280
|
const turnResult = await core.channel.turn.run({
|
|
1298
1281
|
channel: "line",
|
|
@@ -1314,9 +1297,15 @@ async function monitorLineProvider(opts) {
|
|
|
1314
1297
|
recordInboundSession: core.channel.session.recordInboundSession,
|
|
1315
1298
|
dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
|
1316
1299
|
record: ctx.turn.record,
|
|
1317
|
-
|
|
1318
|
-
replyOptions: { onModelSelected },
|
|
1300
|
+
replyPipeline: {},
|
|
1319
1301
|
delivery: {
|
|
1302
|
+
durable: (payload, info) => resolveLineDurableReplyOptions({
|
|
1303
|
+
payload,
|
|
1304
|
+
infoKind: info.kind,
|
|
1305
|
+
to: ctxPayload.From,
|
|
1306
|
+
replyToken,
|
|
1307
|
+
replyTokenUsed
|
|
1308
|
+
}),
|
|
1320
1309
|
deliver: async (payload) => {
|
|
1321
1310
|
const lineData = payload.channelData?.line ?? {};
|
|
1322
1311
|
if (ctx.userId && !ctx.isGroup) showLoadingAnimation(ctx.userId, {
|
|
@@ -1365,7 +1354,7 @@ async function monitorLineProvider(opts) {
|
|
|
1365
1354
|
})
|
|
1366
1355
|
}
|
|
1367
1356
|
});
|
|
1368
|
-
if (!
|
|
1357
|
+
if (!hasFinalChannelTurnDispatch(turnResult.dispatched ? turnResult.dispatchResult : void 0)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
|
|
1369
1358
|
} catch (err) {
|
|
1370
1359
|
runtime.error?.(danger(`line: auto-reply failed: ${String(err)}`));
|
|
1371
1360
|
if (replyToken) try {
|
|
@@ -1520,4 +1509,4 @@ async function monitorLineProvider(opts) {
|
|
|
1520
1509
|
};
|
|
1521
1510
|
}
|
|
1522
1511
|
//#endregion
|
|
1523
|
-
export { validateLineSignature as a,
|
|
1512
|
+
export { validateLineSignature as a, normalizeAllowFrom as c, parseLineWebhookBody as i, createLineNodeWebhookHandler as n, downloadLineMedia as o, readLineWebhookRequestBody as r, firstDefined as s, monitorLineProvider as t };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, D as sendMessageLine, S as pushMessageLine, T as pushTextMessageWithQuickReplies, c as processLineMessage, m as createQuickReplyItems, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage } from "./markdown-to-line-
|
|
1
|
+
import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, D as sendMessageLine, S as pushMessageLine, T as pushTextMessageWithQuickReplies, c as processLineMessage, m as createQuickReplyItems, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage } from "./markdown-to-line-DASNAjBq.js";
|
|
2
2
|
export { buildTemplateMessageFromPayload, createQuickReplyItems, processLineMessage, pushFlexMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, sendMessageLine };
|
package/dist/runtime-api.js
CHANGED
|
@@ -3,17 +3,18 @@ import { a as resolveLineGroupConfigEntry, i as resolveExactLineGroupConfigKey,
|
|
|
3
3
|
import { a as createMediaPlayerCard, i as createDeviceControlCard, n as parseLineDirectives, o as LineChannelConfigSchema, r as createAppleTvRemoteCard, s as LineConfigSchema, t as hasLineDirectives } from "./reply-payload-transform-Bmf8GnSU.js";
|
|
4
4
|
import { n as createEventCard, r as createReceiptCard, t as createAgendaCard } from "./schedule-cards-BwQChTdM.js";
|
|
5
5
|
import { a as createListCard, i as createInfoCard, n as createCarousel, o as createNotificationBubble, r as createImageCard, t as createActionCard } from "./basic-cards-7DTG6E-A.js";
|
|
6
|
-
import { A as buildTemplateMessageFromPayload, B as createYesNoConfirm, C as pushMessagesLine, D as sendMessageLine, E as replyMessageLine, F as createImageCarousel, G as toFlexMessage, H as messageAction, I as createImageCarouselColumn, L as createLinkMenu, M as createButtonTemplate, N as createCarouselColumn, O as showLoadingAnimation, P as createConfirmTemplate, R as createProductCarousel, S as pushMessageLine, T as pushTextMessageWithQuickReplies, U as postbackAction, V as datetimePickerAction, W as uriAction, _ as getUserDisplayName, a as extractLinks, b as pushImageMessage, c as processLineMessage, d as createFlexMessage, f as createImageMessage, g as createVideoMessage, h as createTextMessageWithQuickReplies, i as extractCodeBlocks, j as createButtonMenu, k as resolveLineChannelAccessToken, l as stripMarkdown, m as createQuickReplyItems, n as convertLinksToFlexBubble, o as extractMarkdownTables, p as createLocationMessage, r as convertTableToFlexBubble, s as hasMarkdownToConvert, t as convertCodeBlockToFlexBubble, u as createAudioMessage, v as getUserProfile, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage, z as createTemplateCarousel } from "./markdown-to-line-
|
|
7
|
-
import { a as validateLineSignature, c as
|
|
6
|
+
import { A as buildTemplateMessageFromPayload, B as createYesNoConfirm, C as pushMessagesLine, D as sendMessageLine, E as replyMessageLine, F as createImageCarousel, G as toFlexMessage, H as messageAction, I as createImageCarouselColumn, L as createLinkMenu, M as createButtonTemplate, N as createCarouselColumn, O as showLoadingAnimation, P as createConfirmTemplate, R as createProductCarousel, S as pushMessageLine, T as pushTextMessageWithQuickReplies, U as postbackAction, V as datetimePickerAction, W as uriAction, _ as getUserDisplayName, a as extractLinks, b as pushImageMessage, c as processLineMessage, d as createFlexMessage, f as createImageMessage, g as createVideoMessage, h as createTextMessageWithQuickReplies, i as extractCodeBlocks, j as createButtonMenu, k as resolveLineChannelAccessToken, l as stripMarkdown, m as createQuickReplyItems, n as convertLinksToFlexBubble, o as extractMarkdownTables, p as createLocationMessage, r as convertTableToFlexBubble, s as hasMarkdownToConvert, t as convertCodeBlockToFlexBubble, u as createAudioMessage, v as getUserProfile, w as pushTemplateMessage, x as pushLocationMessage, y as pushFlexMessage, z as createTemplateCarousel } from "./markdown-to-line-DASNAjBq.js";
|
|
7
|
+
import { a as validateLineSignature, c as normalizeAllowFrom, i as parseLineWebhookBody, n as createLineNodeWebhookHandler, o as downloadLineMedia, r as readLineWebhookRequestBody, s as firstDefined, t as monitorLineProvider } from "./monitor-PVeb2tJQ.js";
|
|
8
8
|
import { t as probeLineBot } from "./probe-D9BPzb2n.js";
|
|
9
9
|
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
|
|
10
10
|
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
|
11
|
-
import {
|
|
11
|
+
import { createMessageReceiveContext } from "openclaw/plugin-sdk/channel-message";
|
|
12
12
|
import { DEFAULT_ACCOUNT_ID, formatDocsLink, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
|
|
13
13
|
import { buildComputedAccountStatusSnapshot, buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/status-helpers";
|
|
14
14
|
import { messagingApi } from "@line/bot-sdk";
|
|
15
15
|
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
16
16
|
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/agent-media-payload";
|
|
17
|
+
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
17
18
|
import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
|
|
18
19
|
//#region extensions/line/src/webhook.ts
|
|
19
20
|
const LINE_WEBHOOK_MAX_RAW_BODY_BYTES = 64 * 1024;
|
|
@@ -29,6 +30,7 @@ function parseWebhookBody(rawBody) {
|
|
|
29
30
|
function createLineWebhookMiddleware(options) {
|
|
30
31
|
const { channelSecret, onEvents, runtime } = options;
|
|
31
32
|
return async (req, res, _next) => {
|
|
33
|
+
let receiveContext;
|
|
32
34
|
try {
|
|
33
35
|
const signature = req.headers["x-line-signature"];
|
|
34
36
|
if (!signature || typeof signature !== "string") {
|
|
@@ -54,12 +56,23 @@ function createLineWebhookMiddleware(options) {
|
|
|
54
56
|
res.status(400).json({ error: "Invalid webhook payload" });
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
59
|
+
receiveContext = createMessageReceiveContext({
|
|
60
|
+
id: `${Date.now()}:line:webhook`,
|
|
61
|
+
channel: "line",
|
|
62
|
+
message: body,
|
|
63
|
+
ackPolicy: body.events?.length ? "after_agent_dispatch" : "after_receive_record",
|
|
64
|
+
onAck: () => {
|
|
65
|
+
res.status(200).json({ status: "ok" });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
57
68
|
if (body.events && body.events.length > 0) {
|
|
58
69
|
logVerbose(`line: received ${body.events.length} webhook events`);
|
|
59
70
|
await onEvents(body);
|
|
60
71
|
}
|
|
61
|
-
|
|
72
|
+
const ackStage = body.events?.length ? "agent_dispatch" : "receive_record";
|
|
73
|
+
if (receiveContext.shouldAckAfter(ackStage)) await receiveContext.ack();
|
|
62
74
|
} catch (err) {
|
|
75
|
+
await receiveContext?.nack(err);
|
|
63
76
|
runtime?.error?.(danger(`line webhook error: ${String(err)}`));
|
|
64
77
|
if (!res.headersSent) res.status(500).json({ error: "Internal server error" });
|
|
65
78
|
}
|
|
@@ -117,7 +130,7 @@ async function createRichMenu(menu, opts) {
|
|
|
117
130
|
async function uploadRichMenuImage(richMenuId, imagePath, opts) {
|
|
118
131
|
const blobClient = getBlobClient(opts);
|
|
119
132
|
const media = await loadWebMediaRaw(imagePath, { localRoots: opts.mediaLocalRoots ?? getAgentScopedMediaLocalRoots(opts.cfg) });
|
|
120
|
-
const contentType = media.contentType === "image/png" || media.contentType === "image/jpeg" ? media.contentType :
|
|
133
|
+
const contentType = media.contentType === "image/png" || media.contentType === "image/jpeg" ? media.contentType : mimeTypeFromFilePath(imagePath) === "image/png" ? "image/png" : "image/jpeg";
|
|
121
134
|
const imageBytes = new ArrayBuffer(media.buffer.byteLength);
|
|
122
135
|
new Uint8Array(imageBytes).set(media.buffer);
|
|
123
136
|
await blobClient.setRichMenuImage(richMenuId, new Blob([imageBytes], { type: contentType }));
|
|
@@ -274,4 +287,4 @@ function createDefaultMenuConfig() {
|
|
|
274
287
|
};
|
|
275
288
|
}
|
|
276
289
|
//#endregion
|
|
277
|
-
export { DEFAULT_ACCOUNT_ID, LineChannelConfigSchema, LineConfigSchema, buildChannelConfigSchema, buildComputedAccountStatusSnapshot, buildTemplateMessageFromPayload, buildTokenChannelStatusSummary, cancelDefaultRichMenu, clearAccountEntryFields, convertCodeBlockToFlexBubble, convertLinksToFlexBubble, convertTableToFlexBubble, createActionCard, createAgendaCard, createAppleTvRemoteCard, createAudioMessage, createButtonMenu, createButtonTemplate, createCarousel, createCarouselColumn, createConfirmTemplate, createDefaultMenuConfig, createDeviceControlCard, createEventCard, createFlexMessage, createGridLayout, createImageCard, createImageCarousel, createImageCarouselColumn, createImageMessage, createInfoCard, createLineNodeWebhookHandler, createLineWebhookMiddleware, createLinkMenu, createListCard, createLocationMessage, createMediaPlayerCard, createNotificationBubble, createProductCarousel, createQuickReplyItems, createReceiptCard, createRichMenu, createRichMenuAlias, createTemplateCarousel, createTextMessageWithQuickReplies, createVideoMessage, createYesNoConfirm, datetimePickerAction, deleteRichMenu, deleteRichMenuAlias, downloadLineMedia, extractCodeBlocks, extractLinks, extractMarkdownTables, firstDefined, formatDocsLink, getDefaultRichMenuId, getRichMenu, getRichMenuIdOfUser, getRichMenuList, getUserDisplayName, getUserProfile, hasLineDirectives, hasMarkdownToConvert,
|
|
290
|
+
export { DEFAULT_ACCOUNT_ID, LineChannelConfigSchema, LineConfigSchema, buildChannelConfigSchema, buildComputedAccountStatusSnapshot, buildTemplateMessageFromPayload, buildTokenChannelStatusSummary, cancelDefaultRichMenu, clearAccountEntryFields, convertCodeBlockToFlexBubble, convertLinksToFlexBubble, convertTableToFlexBubble, createActionCard, createAgendaCard, createAppleTvRemoteCard, createAudioMessage, createButtonMenu, createButtonTemplate, createCarousel, createCarouselColumn, createConfirmTemplate, createDefaultMenuConfig, createDeviceControlCard, createEventCard, createFlexMessage, createGridLayout, createImageCard, createImageCarousel, createImageCarouselColumn, createImageMessage, createInfoCard, createLineNodeWebhookHandler, createLineWebhookMiddleware, createLinkMenu, createListCard, createLocationMessage, createMediaPlayerCard, createNotificationBubble, createProductCarousel, createQuickReplyItems, createReceiptCard, createRichMenu, createRichMenuAlias, createTemplateCarousel, createTextMessageWithQuickReplies, createVideoMessage, createYesNoConfirm, datetimePickerAction, deleteRichMenu, deleteRichMenuAlias, downloadLineMedia, extractCodeBlocks, extractLinks, extractMarkdownTables, firstDefined, formatDocsLink, getDefaultRichMenuId, getRichMenu, getRichMenuIdOfUser, getRichMenuList, getUserDisplayName, getUserProfile, hasLineDirectives, hasMarkdownToConvert, linkRichMenuToUser, linkRichMenuToUsers, listLineAccountIds, messageAction, monitorLineProvider, normalizeAccountId, normalizeAllowFrom, parseLineDirectives, parseLineWebhookBody, postbackAction, probeLineBot, processLineMessage, pushFlexMessage, pushImageMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, readLineWebhookRequestBody, replyMessageLine, resolveDefaultLineAccountId, resolveExactLineGroupConfigKey, resolveLineAccount, resolveLineChannelAccessToken, resolveLineGroupConfigEntry, resolveLineGroupLookupIds, resolveLineGroupsConfig, sendMessageLine, setDefaultRichMenu, setLineRuntime, setSetupChannelEnabled, showLoadingAnimation, splitSetupEntries, startLineWebhook, stripMarkdown, toFlexMessage, unlinkRichMenuFromUser, unlinkRichMenuFromUsers, uploadRichMenuImage, uriAction, validateLineSignature };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-message";
|
|
1
2
|
import { resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
2
3
|
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
3
4
|
//#region extensions/line/src/outbound-media.ts
|
|
@@ -52,4 +53,21 @@ async function resolveLineOutboundMedia(mediaUrl, opts = {}) {
|
|
|
52
53
|
throw new Error("LINE outbound media currently requires a public HTTPS URL");
|
|
53
54
|
}
|
|
54
55
|
//#endregion
|
|
55
|
-
|
|
56
|
+
//#region extensions/line/src/send-receipt.ts
|
|
57
|
+
function createLineSendReceipt(params) {
|
|
58
|
+
const messageId = params.messageId.trim();
|
|
59
|
+
const chatId = params.chatId.trim();
|
|
60
|
+
return createMessageReceiptFromOutboundResults({
|
|
61
|
+
results: messageId ? [{
|
|
62
|
+
channel: "line",
|
|
63
|
+
messageId,
|
|
64
|
+
chatId,
|
|
65
|
+
conversationId: chatId,
|
|
66
|
+
meta: { messageCount: params.messageCount ?? 1 }
|
|
67
|
+
}] : [],
|
|
68
|
+
...chatId ? { threadId: chatId } : {},
|
|
69
|
+
kind: params.kind ?? "unknown"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { resolveLineOutboundMedia as n, validateLineMediaUrl as r, createLineSendReceipt as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/line",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.10-beta.1",
|
|
4
4
|
"description": "OpenClaw LINE channel plugin",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"openclaw": "workspace:*"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"openclaw": ">=2026.5.
|
|
18
|
+
"openclaw": ">=2026.5.10-beta.1"
|
|
19
19
|
},
|
|
20
20
|
"peerDependenciesMeta": {
|
|
21
21
|
"openclaw": {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"minHostVersion": ">=2026.4.10"
|
|
46
46
|
},
|
|
47
47
|
"compat": {
|
|
48
|
-
"pluginApi": ">=2026.5.
|
|
48
|
+
"pluginApi": ">=2026.5.10-beta.1"
|
|
49
49
|
},
|
|
50
50
|
"build": {
|
|
51
|
-
"openclawVersion": "2026.5.
|
|
51
|
+
"openclawVersion": "2026.5.10-beta.1"
|
|
52
52
|
},
|
|
53
53
|
"release": {
|
|
54
54
|
"publishToClawHub": true,
|