@openclaw/line 2026.7.2-beta.1 → 2026.7.2-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,55 +1,113 @@
1
1
  import { DEFAULT_ACCOUNT_ID, normalizeAccountId, normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id";
2
2
  import { resolveAccountEntry } from "openclaw/plugin-sdk/account-resolution";
3
- import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
3
+ import { tryReadSecretFileSync } from "openclaw/plugin-sdk/secret-file-runtime";
4
4
  //#region extensions/line/src/accounts.ts
5
- function readFileIfExists(filePath) {
6
- return tryReadSecretFileSync(filePath, "LINE credential file", { rejectSymlink: true });
5
+ function readCredentialFile(filePath, configPath) {
6
+ return tryReadSecretFileSync(filePath, "LINE credential file", { rejectSymlink: true }, { configPath });
7
7
  }
8
8
  function resolveToken(params) {
9
9
  const { accountId, baseConfig, accountConfig } = params;
10
10
  if (accountConfig?.channelAccessToken?.trim()) return {
11
- token: accountConfig.channelAccessToken.trim(),
12
- tokenSource: "config"
13
- };
14
- const accountFileToken = readFileIfExists(accountConfig?.tokenFile);
15
- if (accountFileToken) return {
16
- token: accountFileToken,
17
- tokenSource: "file"
11
+ value: accountConfig.channelAccessToken.trim(),
12
+ source: "config",
13
+ status: "available"
18
14
  };
15
+ if (accountConfig?.tokenFile?.trim()) {
16
+ const result = readCredentialFile(accountConfig.tokenFile, `channels.line.accounts.${accountId}.tokenFile`);
17
+ return result.status === "available" ? {
18
+ value: result.value,
19
+ source: "file",
20
+ status: "available"
21
+ } : {
22
+ value: "",
23
+ source: "file",
24
+ status: "configured_unavailable",
25
+ diagnostic: result.diagnostic
26
+ };
27
+ }
19
28
  if (accountId === DEFAULT_ACCOUNT_ID) {
20
29
  if (baseConfig?.channelAccessToken?.trim()) return {
21
- token: baseConfig.channelAccessToken.trim(),
22
- tokenSource: "config"
23
- };
24
- const baseFileToken = readFileIfExists(baseConfig?.tokenFile);
25
- if (baseFileToken) return {
26
- token: baseFileToken,
27
- tokenSource: "file"
30
+ value: baseConfig.channelAccessToken.trim(),
31
+ source: "config",
32
+ status: "available"
28
33
  };
34
+ if (baseConfig?.tokenFile?.trim()) {
35
+ const result = readCredentialFile(baseConfig.tokenFile, "channels.line.tokenFile");
36
+ return result.status === "available" ? {
37
+ value: result.value,
38
+ source: "file",
39
+ status: "available"
40
+ } : {
41
+ value: "",
42
+ source: "file",
43
+ status: "configured_unavailable",
44
+ diagnostic: result.diagnostic
45
+ };
46
+ }
29
47
  const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim();
30
48
  if (envToken) return {
31
- token: envToken,
32
- tokenSource: "env"
49
+ value: envToken,
50
+ source: "env",
51
+ status: "available"
33
52
  };
34
53
  }
35
54
  return {
36
- token: "",
37
- tokenSource: "none"
55
+ value: "",
56
+ source: "none",
57
+ status: "missing"
38
58
  };
39
59
  }
40
60
  function resolveSecret(params) {
41
61
  const { accountId, baseConfig, accountConfig } = params;
42
- if (accountConfig?.channelSecret?.trim()) return accountConfig.channelSecret.trim();
43
- const accountFileSecret = readFileIfExists(accountConfig?.secretFile);
44
- if (accountFileSecret) return accountFileSecret;
62
+ if (accountConfig?.channelSecret?.trim()) return {
63
+ value: accountConfig.channelSecret.trim(),
64
+ source: "config",
65
+ status: "available"
66
+ };
67
+ if (accountConfig?.secretFile?.trim()) {
68
+ const result = readCredentialFile(accountConfig.secretFile, `channels.line.accounts.${accountId}.secretFile`);
69
+ return result.status === "available" ? {
70
+ value: result.value,
71
+ source: "file",
72
+ status: "available"
73
+ } : {
74
+ value: "",
75
+ source: "file",
76
+ status: "configured_unavailable",
77
+ diagnostic: result.diagnostic
78
+ };
79
+ }
45
80
  if (accountId === DEFAULT_ACCOUNT_ID) {
46
- if (baseConfig?.channelSecret?.trim()) return baseConfig.channelSecret.trim();
47
- const baseFileSecret = readFileIfExists(baseConfig?.secretFile);
48
- if (baseFileSecret) return baseFileSecret;
81
+ if (baseConfig?.channelSecret?.trim()) return {
82
+ value: baseConfig.channelSecret.trim(),
83
+ source: "config",
84
+ status: "available"
85
+ };
86
+ if (baseConfig?.secretFile?.trim()) {
87
+ const result = readCredentialFile(baseConfig.secretFile, "channels.line.secretFile");
88
+ return result.status === "available" ? {
89
+ value: result.value,
90
+ source: "file",
91
+ status: "available"
92
+ } : {
93
+ value: "",
94
+ source: "file",
95
+ status: "configured_unavailable",
96
+ diagnostic: result.diagnostic
97
+ };
98
+ }
49
99
  const envSecret = process.env.LINE_CHANNEL_SECRET?.trim();
50
- if (envSecret) return envSecret;
100
+ if (envSecret) return {
101
+ value: envSecret,
102
+ source: "env",
103
+ status: "available"
104
+ };
51
105
  }
52
- return "";
106
+ return {
107
+ value: "",
108
+ source: "none",
109
+ status: "missing"
110
+ };
53
111
  }
54
112
  function resolveLineAccount(params) {
55
113
  const cfg = params.cfg;
@@ -57,7 +115,7 @@ function resolveLineAccount(params) {
57
115
  const lineConfig = cfg.channels?.line;
58
116
  const accounts = lineConfig?.accounts;
59
117
  const accountConfig = resolveAccountEntry(accounts, accountId);
60
- const { token, tokenSource } = resolveToken({
118
+ const token = resolveToken({
61
119
  accountId,
62
120
  baseConfig: lineConfig,
63
121
  accountConfig
@@ -79,9 +137,13 @@ function resolveLineAccount(params) {
79
137
  accountId,
80
138
  name: accountConfig?.name ?? (accountId === DEFAULT_ACCOUNT_ID ? lineConfig?.name : void 0),
81
139
  enabled,
82
- channelAccessToken: token,
83
- channelSecret: secret,
84
- tokenSource,
140
+ channelAccessToken: token.value,
141
+ channelSecret: secret.value,
142
+ tokenSource: token.source,
143
+ signingSecretSource: secret.source,
144
+ tokenStatus: token.status,
145
+ signingSecretStatus: secret.status,
146
+ ...[token.diagnostic, secret.diagnostic].some(Boolean) ? { credentialDiagnostics: [token.diagnostic, secret.diagnostic].filter((diagnostic) => Boolean(diagnostic)) } : {},
85
147
  config: mergedConfig
86
148
  };
87
149
  }
package/dist/api.js CHANGED
@@ -1,5 +1,5 @@
1
- import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-CnPxxRbJ.js";
2
- import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-Bc36YSS3.js";
1
+ import { n as lineChannelPluginCommon, t as linePlugin } from "./channel-uT5RTtpi.js";
2
+ import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-BEt4mazl.js";
3
3
  //#region extensions/line/src/channel.setup.ts
4
4
  const lineSetupPlugin = {
5
5
  id: "line",
@@ -1,2 +1,2 @@
1
- import { t as linePlugin } from "./channel-CnPxxRbJ.js";
1
+ import { t as linePlugin } from "./channel-uT5RTtpi.js";
2
2
  export { linePlugin };
@@ -1,19 +1,21 @@
1
- import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-DJVOv1JI.js";
2
- import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-Bc36YSS3.js";
3
- import { a as resolveLineOutboundMedia, d as resolveExactLineGroupConfigKey, i as buildLineQuickReplyFallbackText, l as LineChannelConfigSchema, n as parseLineDirectives, r as createLineSendReceipt, s as getLineRuntime, t as hasLineDirectives } from "./reply-payload-transform-BsbwCJGH.js";
1
+ import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BUBOoW5Q.js";
2
+ import { n as lineSetupAdapter, r as hasLineCredentials, t as lineSetupWizard } from "./setup-surface-BEt4mazl.js";
3
+ import { a as buildLineMediaMessage, d as LineChannelConfigSchema, i as buildLineQuickReplyFallbackText, l as getLineRuntime, n as parseLineDirectives, o as hasLineSpecificMediaOptions, p as resolveExactLineGroupConfigKey, r as createLineSendReceipt, s as resolveLineOutboundMedia, t as hasLineDirectives } from "./reply-payload-transform-r9sa-TYT.js";
4
4
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
5
+ import { buildDmGroupAccountAllowlistAdapter, createFlatAllowlistOverrideResolver } from "openclaw/plugin-sdk/allowlist-config-edit";
5
6
  import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
6
7
  import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
7
8
  import { buildChannelGroupsScopeTree, createRestrictSendersChannelSecurity, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
8
9
  import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
9
10
  import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1 } from "openclaw/plugin-sdk/account-id";
10
- import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
11
11
  import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
12
12
  import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
13
13
  import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
14
+ import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
14
15
  import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
15
16
  import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
16
17
  import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
18
+ import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
17
19
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
18
20
  import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "openclaw/plugin-sdk/status-helpers";
19
21
  //#region extensions/line/src/bindings.ts
@@ -58,6 +60,22 @@ const lineBindingsAdapter = {
58
60
  function normalizeLineAllowFrom(entry) {
59
61
  return entry.replace(/^line:(?:user:)?/i, "");
60
62
  }
63
+ const lineConfigAdapter = createScopedChannelConfigAdapter({
64
+ sectionKey: "line",
65
+ listAccountIds: listLineAccountIds,
66
+ resolveAccount: (cfg, accountId) => resolveLineAccount({
67
+ cfg,
68
+ accountId: accountId ?? void 0
69
+ }),
70
+ defaultAccountId: resolveDefaultLineAccountId,
71
+ clearBaseFields: [
72
+ "channelSecret",
73
+ "tokenFile",
74
+ "secretFile"
75
+ ],
76
+ resolveAllowFrom: (account) => account.config.allowFrom,
77
+ formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
78
+ });
61
79
  const lineChannelPluginCommon = {
62
80
  meta: {
63
81
  id: "line",
@@ -81,34 +99,24 @@ const lineChannelPluginCommon = {
81
99
  reload: { configPrefixes: ["channels.line"] },
82
100
  configSchema: LineChannelConfigSchema,
83
101
  config: {
84
- ...createScopedChannelConfigAdapter({
85
- sectionKey: "line",
86
- listAccountIds: listLineAccountIds,
87
- resolveAccount: (cfg, accountId) => resolveLineAccount({
88
- cfg,
89
- accountId: accountId ?? void 0
90
- }),
91
- defaultAccountId: resolveDefaultLineAccountId,
92
- clearBaseFields: [
93
- "channelSecret",
94
- "tokenFile",
95
- "secretFile"
96
- ],
97
- resolveAllowFrom: (account) => account.config.allowFrom,
98
- formatAllowFrom: (allowFrom) => normalizeStringEntries(allowFrom).map(normalizeLineAllowFrom)
99
- }),
102
+ ...lineConfigAdapter,
100
103
  isConfigured: (account) => hasLineCredentials(account),
101
104
  describeAccount: (account) => describeWebhookAccountSnapshot({
102
105
  account,
103
106
  configured: hasLineCredentials(account),
104
- extra: { tokenSource: account.tokenSource ?? void 0 }
107
+ extra: {
108
+ tokenSource: account.tokenSource ?? void 0,
109
+ signingSecretSource: account.signingSecretSource ?? void 0,
110
+ tokenStatus: account.tokenStatus,
111
+ signingSecretStatus: account.signingSecretStatus
112
+ }
105
113
  })
106
114
  }
107
115
  };
108
116
  //#endregion
109
117
  //#region extensions/line/src/gateway.ts
110
118
  const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-CRhVGqwl.js"));
111
- const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-BiNrVz5G.js"));
119
+ const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-CHlqxGVL.js"));
112
120
  const lineGatewayAdapter = {
113
121
  startAccount: async (ctx) => {
114
122
  const account = ctx.account;
@@ -223,42 +231,12 @@ function inferLineTargetChatType(target) {
223
231
  }
224
232
  //#endregion
225
233
  //#region extensions/line/src/outbound.ts
226
- const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-CI-t7Z8P.js"));
227
- function isLineUserTarget(target) {
228
- const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
229
- return /^U/i.test(normalized);
230
- }
231
- function hasLineSpecificMediaOptions(lineData) {
232
- return Boolean(lineData.mediaKind ?? lineData.previewImageUrl?.trim() ?? (typeof lineData.durationMs === "number" ? lineData.durationMs : void 0) ?? lineData.trackingId?.trim());
233
- }
234
- function buildLineMediaMessageObject(resolved, opts) {
235
- switch (resolved.mediaKind) {
236
- case "video": {
237
- const previewImageUrl = resolved.previewImageUrl?.trim();
238
- if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
239
- return {
240
- type: "video",
241
- originalContentUrl: resolved.mediaUrl,
242
- previewImageUrl,
243
- ...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
244
- };
245
- }
246
- case "audio": return {
247
- type: "audio",
248
- originalContentUrl: resolved.mediaUrl,
249
- duration: resolved.durationMs ?? 6e4
250
- };
251
- default: return {
252
- type: "image",
253
- originalContentUrl: resolved.mediaUrl,
254
- previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
255
- };
256
- }
257
- }
234
+ const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-ByZ0Dsvu.js"));
258
235
  const lineOutboundAdapter = {
259
236
  deliveryMode: "direct",
260
237
  chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit),
261
238
  textChunkLimit: 5e3,
239
+ sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text),
262
240
  sendPayload: async ({ to, payload, accountId, cfg, onDeliveryResult }) => {
263
241
  const runtime = getLineRuntime();
264
242
  const outboundRuntime = await loadLineOutboundRuntime();
@@ -300,6 +278,12 @@ const lineOutboundAdapter = {
300
278
  const chunks = processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : [];
301
279
  const mediaUrls = resolveOutboundMediaUrls(payload);
302
280
  const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData);
281
+ const mediaOptions = {
282
+ mediaKind: useLineSpecificMedia ? lineData.mediaKind : "image",
283
+ previewImageUrl: lineData.previewImageUrl,
284
+ durationMs: lineData.durationMs,
285
+ trackingId: lineData.trackingId
286
+ };
303
287
  const shouldSendQuickRepliesInline = chunks.length === 0 && hasQuickReplies;
304
288
  const sendMediaMessages = async () => {
305
289
  for (const url of mediaUrls) {
@@ -314,12 +298,7 @@ const lineOutboundAdapter = {
314
298
  }));
315
299
  continue;
316
300
  }
317
- const resolved = await resolveLineOutboundMedia(trimmed, {
318
- mediaKind: lineData.mediaKind,
319
- previewImageUrl: lineData.previewImageUrl,
320
- durationMs: lineData.durationMs,
321
- trackingId: lineData.trackingId
322
- });
301
+ const resolved = await resolveLineOutboundMedia(trimmed, mediaOptions);
323
302
  await recordResult((lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
324
303
  verbose: false,
325
304
  mediaUrl: resolved.mediaUrl,
@@ -401,21 +380,7 @@ const lineOutboundAdapter = {
401
380
  for (const url of mediaUrls) {
402
381
  const trimmed = url?.trim();
403
382
  if (!trimmed) continue;
404
- if (!useLineSpecificMedia) {
405
- quickReplyMessages.push({
406
- type: "image",
407
- originalContentUrl: trimmed,
408
- previewImageUrl: trimmed
409
- });
410
- continue;
411
- }
412
- const resolved = await resolveLineOutboundMedia(trimmed, {
413
- mediaKind: lineData.mediaKind,
414
- previewImageUrl: lineData.previewImageUrl,
415
- durationMs: lineData.durationMs,
416
- trackingId: lineData.trackingId
417
- });
418
- quickReplyMessages.push(buildLineMediaMessageObject(resolved, { allowTrackingId: isLineUserTarget(to) }));
383
+ quickReplyMessages.push(await buildLineMediaMessage(trimmed, mediaOptions, to));
419
384
  }
420
385
  if (quickReplyMessages.length > 0 && quickReply) {
421
386
  const lastIndex = quickReplyMessages.length - 1;
@@ -554,13 +519,16 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
554
519
  configured: hasLineCredentials(account),
555
520
  extra: {
556
521
  tokenSource: account.tokenSource,
522
+ signingSecretSource: account.signingSecretSource,
523
+ tokenStatus: account.tokenStatus,
524
+ signingSecretStatus: account.signingSecretStatus,
557
525
  mode: "webhook"
558
526
  }
559
527
  })
560
528
  });
561
529
  //#endregion
562
530
  //#region extensions/line/src/channel.ts
563
- const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-FPcRtjcT.js"));
531
+ const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-BqNglqnr.js"));
564
532
  const lineSecurityAdapter = createRestrictSendersChannelSecurity({
565
533
  channelKey: "line",
566
534
  resolveDmPolicy: (account) => account.config.dmPolicy,
@@ -581,6 +549,27 @@ const linePlugin = createChatChannelPlugin({
581
549
  ...lineChannelPluginCommon,
582
550
  setupWizard: lineSetupWizard,
583
551
  groups: { resolveRequireMention: resolveLineGroupRequireMention },
552
+ allowlist: buildDmGroupAccountAllowlistAdapter({
553
+ channelId: "line",
554
+ resolveAccount: ({ cfg, accountId }) => resolveLineAccount({
555
+ cfg,
556
+ accountId: accountId ?? void 0
557
+ }),
558
+ normalize: ({ cfg, accountId, values }) => lineConfigAdapter.formatAllowFrom({
559
+ cfg,
560
+ accountId,
561
+ allowFrom: values
562
+ }),
563
+ resolveDmAllowFrom: (account) => account.config.allowFrom,
564
+ resolveGroupAllowFrom: (account) => account.config.groupAllowFrom,
565
+ resolveDmPolicy: (account) => account.config.dmPolicy,
566
+ resolveGroupPolicy: (account) => account.config.groupPolicy,
567
+ resolveGroupOverrides: createFlatAllowlistOverrideResolver({
568
+ resolveRecord: (account) => account.config.groups,
569
+ label: (groupId) => groupId,
570
+ resolveEntries: (groupCfg) => groupCfg?.allowFrom
571
+ })
572
+ }),
584
573
  messaging: {
585
574
  targetPrefixes: ["line"],
586
575
  normalizeTarget: normalizeLineMessagingTarget,
@@ -0,0 +1,4 @@
1
+ import { t as monitorLineProvider } from "./monitor-Bk-lpoOZ.js";
2
+ import { t as probeLineBot } from "./probe-BslD77tJ.js";
3
+ import { S as pushMessageLine } from "./markdown-to-line-D1vapcdP.js";
4
+ export { monitorLineProvider, probeLineBot, pushMessageLine };
@@ -1,2 +1,2 @@
1
- import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-DJVOv1JI.js";
1
+ import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BUBOoW5Q.js";
2
2
  export { listLineAccountIds, resolveDefaultLineAccountId, resolveLineAccount };
@@ -1,13 +1,14 @@
1
- import { i as resolveLineAccount } from "./accounts-DJVOv1JI.js";
2
- import { o as validateLineMediaUrl, r as createLineSendReceipt } from "./reply-payload-transform-BsbwCJGH.js";
1
+ import { i as resolveLineAccount } from "./accounts-BUBOoW5Q.js";
2
+ import { c as validateLineMediaUrl, r as createLineSendReceipt } from "./reply-payload-transform-r9sa-TYT.js";
3
3
  import { c as uriAction, d as createReceiptCard, o as messageAction, s as postbackAction, t as toFlexMessage } from "./flex-templates-K1lS9i8L.js";
4
+ import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
4
5
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
5
6
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
6
- import { messagingApi } from "@line/bot-sdk";
7
7
  import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
8
+ import { messagingApi } from "@line/bot-sdk";
8
9
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
10
+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime";
9
11
  import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
10
- import { stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
11
12
  //#region extensions/line/src/template-messages.ts
12
13
  const COMPACT_TEMPLATE_TEXT_LIMIT = 60;
13
14
  const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
@@ -221,6 +222,14 @@ function resolveLineChannelAccessToken(explicit, params) {
221
222
  //#region extensions/line/src/send.ts
222
223
  const userProfileCache = /* @__PURE__ */ new Map();
223
224
  const PROFILE_CACHE_TTL_MS = 300 * 1e3;
225
+ const PROFILE_CACHE_MAX_ENTRIES = 1e3;
226
+ function cacheUserProfile(userId, profile) {
227
+ userProfileCache.delete(userId);
228
+ userProfileCache.set(userId, profile);
229
+ if (userProfileCache.size <= PROFILE_CACHE_MAX_ENTRIES) return;
230
+ for (const [key, cached] of userProfileCache) if (profile.fetchedAt - cached.fetchedAt >= PROFILE_CACHE_TTL_MS) userProfileCache.delete(key);
231
+ pruneMapToMaxSize(userProfileCache, PROFILE_CACHE_MAX_ENTRIES);
232
+ }
224
233
  function normalizeTarget(to) {
225
234
  const trimmed = to.trim();
226
235
  if (!trimmed) throw new Error("Recipient is required for LINE sends");
@@ -469,7 +478,7 @@ async function getUserProfile(userId, opts) {
469
478
  displayName: profile.displayName,
470
479
  pictureUrl: profile.pictureUrl
471
480
  };
472
- userProfileCache.set(userId, {
481
+ cacheUserProfile(userId, {
473
482
  ...result,
474
483
  fetchedAt: Date.now()
475
484
  });