@openclaw/line 2026.7.1 → 2026.7.2-beta.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.
@@ -0,0 +1,2 @@
1
+ import { t as monitorLineProvider } from "./monitor-Db2rZsSX.js";
2
+ export { monitorLineProvider };
@@ -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-DmpZY78n.js";
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-XceqeKRP.js";
2
2
  export { buildTemplateMessageFromPayload, createQuickReplyItems, processLineMessage, pushFlexMessage, pushLocationMessage, pushMessageLine, pushMessagesLine, pushTemplateMessage, pushTextMessageWithQuickReplies, sendMessageLine };
@@ -1,13 +1,14 @@
1
- import { i as createMediaPlayerCard, l as createAgendaCard, n as createAppleTvRemoteCard, r as createDeviceControlCard, u as createEventCard } from "./flex-templates-DEIXn69v.js";
1
+ import { i as createMediaPlayerCard, l as createAgendaCard, n as createAppleTvRemoteCard, r as createDeviceControlCard, u as createEventCard } from "./flex-templates-K1lS9i8L.js";
2
2
  import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
3
3
  import { resolveAccountEntry } from "openclaw/plugin-sdk/account-resolution";
4
4
  import { normalizeLowercaseStringOrEmpty, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
5
- import { buildChannelConfigSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
5
+ import { DmPolicySchema, GroupPolicySchema, buildChannelConfigSchema, buildGroupEntrySchema, buildMultiAccountChannelSchema, requireOpenAllowFrom } from "openclaw/plugin-sdk/channel-config-schema";
6
6
  import { requireChannelOpenAllowFrom } from "openclaw/plugin-sdk/extension-shared";
7
7
  import { z } from "zod";
8
8
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
9
9
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
10
10
  import { resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
11
+ import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
11
12
  import { parseStrictFiniteNumber } from "openclaw/plugin-sdk/number-runtime";
12
13
  //#region extensions/line/src/group-keys.ts
13
14
  function resolveLineGroupLookupIds(groupId) {
@@ -42,23 +43,12 @@ function resolveLineGroupsConfig(cfg, accountId) {
42
43
  return resolveAccountEntry(lineConfig.accounts, normalizedAccountId)?.groups ?? lineConfig.groups;
43
44
  }
44
45
  function resolveExactLineGroupConfigKey(params) {
45
- const groups = resolveLineGroupsConfig(params.cfg, params.accountId);
46
+ const { groups } = params;
46
47
  if (!groups) return;
47
48
  return resolveLineGroupLookupIds(params.groupId).find((candidate) => Object.hasOwn(groups, candidate));
48
49
  }
49
50
  //#endregion
50
51
  //#region extensions/line/src/config-schema.ts
51
- const DmPolicySchema = z.enum([
52
- "open",
53
- "allowlist",
54
- "pairing",
55
- "disabled"
56
- ]);
57
- const GroupPolicySchema = z.enum([
58
- "open",
59
- "allowlist",
60
- "disabled"
61
- ]);
62
52
  const ThreadBindingsSchema = z.object({
63
53
  enabled: z.boolean().optional(),
64
54
  idleHours: z.number().optional(),
@@ -84,39 +74,26 @@ const LineCommonConfigSchemaBase = z.object({
84
74
  webhookPath: z.string().optional(),
85
75
  threadBindings: ThreadBindingsSchema.optional()
86
76
  });
87
- const LineGroupConfigSchema = z.object({
88
- enabled: z.boolean().optional(),
89
- allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
90
- requireMention: z.boolean().optional(),
91
- systemPrompt: z.string().optional(),
92
- skills: z.array(z.string()).optional()
93
- }).strict();
94
- const LineAccountConfigSchema = LineCommonConfigSchemaBase.extend({ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional() }).strict().superRefine((value, ctx) => {
95
- requireChannelOpenAllowFrom({
96
- channel: "line",
97
- policy: value.dmPolicy,
98
- allowFrom: value.allowFrom,
99
- ctx,
100
- requireOpenAllowFrom
101
- });
77
+ const LineGroupConfigSchema = buildGroupEntrySchema().omit({
78
+ tools: true,
79
+ toolsBySender: true
102
80
  });
103
- const LineConfigSchema = LineCommonConfigSchemaBase.extend({
104
- accounts: z.record(z.string(), LineAccountConfigSchema.optional()).optional(),
105
- defaultAccount: z.string().optional(),
106
- groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional()
107
- }).strict().superRefine((value, ctx) => {
108
- requireChannelOpenAllowFrom({
109
- channel: "line",
110
- policy: value.dmPolicy,
111
- allowFrom: value.allowFrom,
112
- ctx,
113
- requireOpenAllowFrom
114
- });
81
+ const LineConfigSchema = buildMultiAccountChannelSchema(LineCommonConfigSchemaBase.extend({ groups: z.record(z.string(), LineGroupConfigSchema.optional()).optional() }).strict(), {
82
+ optionalAccount: true,
83
+ refine: (value, ctx) => {
84
+ requireChannelOpenAllowFrom({
85
+ channel: "line",
86
+ policy: value.dmPolicy,
87
+ allowFrom: value.allowFrom,
88
+ ctx,
89
+ requireOpenAllowFrom
90
+ });
91
+ }
115
92
  });
116
93
  const LineChannelConfigSchema = buildChannelConfigSchema(LineConfigSchema);
117
94
  //#endregion
118
95
  //#region extensions/line/src/runtime.ts
119
- const { setRuntime: setLineRuntime, clearRuntime: clearLineRuntime, getRuntime: getLineRuntime } = createPluginRuntimeStore({
96
+ const { setRuntime: setLineRuntime, getRuntime: getLineRuntime } = createPluginRuntimeStore({
120
97
  pluginId: "line",
121
98
  errorMessage: "LINE runtime not initialized - plugin not registered"
122
99
  });
@@ -128,9 +105,9 @@ async function validateLineMediaUrl(url) {
128
105
  try {
129
106
  parsed = new URL(url);
130
107
  } catch {
131
- throw new Error(`LINE outbound media URL must be a valid URL: ${url}`);
108
+ throw new Error("LINE outbound media URL must be a valid URL");
132
109
  }
133
- if (parsed.protocol !== "https:") throw new Error(`LINE outbound media URL must use HTTPS: ${url}`);
110
+ if (parsed.protocol !== "https:") throw new Error("LINE outbound media URL must use HTTPS");
134
111
  if (url.length > 2e3) throw new Error(`LINE outbound media URL must be 2000 chars or less (got ${url.length})`);
135
112
  await resolvePinnedHostnameWithPolicy(parsed.hostname, { policy: LINE_OUTBOUND_MEDIA_SSRF_POLICY });
136
113
  }
@@ -165,13 +142,47 @@ async function resolveLineOutboundMedia(mediaUrl, opts = {}) {
165
142
  ...opts.trackingId ? { trackingId: opts.trackingId } : {}
166
143
  };
167
144
  }
145
+ let parsed;
168
146
  try {
169
- if (new URL(trimmedUrl).protocol !== "https:") throw new Error(`LINE outbound media URL must use HTTPS: ${trimmedUrl}`);
170
- } catch (e) {
171
- if (e instanceof Error && e.message.startsWith("LINE outbound")) throw e;
172
- }
147
+ parsed = new URL(trimmedUrl);
148
+ } catch {}
149
+ if (parsed) throw new Error("LINE outbound media URL must use HTTPS");
173
150
  throw new Error("LINE outbound media currently requires a public HTTPS URL");
174
151
  }
152
+ function isLineUserTarget(target) {
153
+ const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
154
+ return /^U/i.test(normalized);
155
+ }
156
+ function hasLineSpecificMediaOptions(lineData) {
157
+ return lineData.mediaKind !== void 0 || Boolean(lineData.previewImageUrl?.trim()) || typeof lineData.durationMs === "number" || Boolean(lineData.trackingId?.trim());
158
+ }
159
+ function buildLineMediaMessageObject(resolved, opts) {
160
+ switch (resolved.mediaKind) {
161
+ case "video": {
162
+ const previewImageUrl = resolved.previewImageUrl?.trim();
163
+ if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
164
+ return {
165
+ type: "video",
166
+ originalContentUrl: resolved.mediaUrl,
167
+ previewImageUrl,
168
+ ...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
169
+ };
170
+ }
171
+ case "audio": return {
172
+ type: "audio",
173
+ originalContentUrl: resolved.mediaUrl,
174
+ duration: resolved.durationMs ?? 6e4
175
+ };
176
+ default: return {
177
+ type: "image",
178
+ originalContentUrl: resolved.mediaUrl,
179
+ previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
180
+ };
181
+ }
182
+ }
183
+ async function buildLineMediaMessage(mediaUrl, opts, target) {
184
+ return buildLineMediaMessageObject(await resolveLineOutboundMedia(mediaUrl, opts), { allowTrackingId: isLineUserTarget(target) });
185
+ }
175
186
  //#endregion
176
187
  //#region extensions/line/src/quick-reply-fallback.ts
177
188
  function buildLineQuickReplyFallbackText(labels) {
@@ -223,17 +234,31 @@ function parseLineDirectives(payload) {
223
234
  if (extras) for (const [key, value] of Object.entries(extras)) base.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
224
235
  return base.join("&");
225
236
  };
237
+ const parseConfirmAction = (part) => {
238
+ const colonIndex = part.indexOf(":");
239
+ if (colonIndex === -1) return {
240
+ label: part,
241
+ data: normalizeLowercaseStringOrEmpty(part)
242
+ };
243
+ return {
244
+ label: part.slice(0, colonIndex).trim(),
245
+ data: part.slice(colonIndex + 1).trim()
246
+ };
247
+ };
226
248
  const quickRepliesMatch = text.match(/\[\[quick_replies:\s*([^\]]+)\]\]/i);
227
249
  if (quickRepliesMatch) {
228
- const options = normalizeStringEntries(quickRepliesMatch[1].split(","));
250
+ const options = normalizeStringEntries(expectDefined(quickRepliesMatch[1], "quick replies directive body").split(","));
229
251
  if (options.length > 0) lineData.quickReplies = [...lineData.quickReplies || [], ...options];
230
252
  text = text.replace(quickRepliesMatch[0], "").trim();
231
253
  }
232
254
  const locationMatch = text.match(/\[\[location:\s*([^\]]+)\]\]/i);
233
255
  if (locationMatch && !lineData.location) {
234
- const parts = locationMatch[1].split("|").map((s) => s.trim());
256
+ const parts = expectDefined(locationMatch[1], "location directive body").split("|").map((s) => s.trim());
235
257
  if (parts.length >= 4) {
236
- const [title, address, latStr, lonStr] = parts;
258
+ const title = expectDefined(parts[0], "location title field");
259
+ const address = expectDefined(parts[1], "location address field");
260
+ const latStr = expectDefined(parts[2], "location latitude field");
261
+ const lonStr = expectDefined(parts[3], "location longitude field");
237
262
  const latitude = parseStrictFiniteNumber(latStr);
238
263
  const longitude = parseStrictFiniteNumber(lonStr);
239
264
  if (latitude !== void 0 && longitude !== void 0) lineData.location = {
@@ -247,18 +272,20 @@ function parseLineDirectives(payload) {
247
272
  }
248
273
  const confirmMatch = text.match(/\[\[confirm:\s*([^\]]+)\]\]/i);
249
274
  if (confirmMatch && !lineData.templateMessage) {
250
- const parts = confirmMatch[1].split("|").map((s) => s.trim());
275
+ const parts = expectDefined(confirmMatch[1], "confirm directive body").split("|").map((s) => s.trim());
251
276
  if (parts.length >= 3) {
252
- const [question, yesPart, noPart] = parts;
253
- const [yesLabel, yesData] = yesPart.includes(":") ? yesPart.split(":").map((s) => s.trim()) : [yesPart, normalizeLowercaseStringOrEmpty(yesPart)];
254
- const [noLabel, noData] = noPart.includes(":") ? noPart.split(":").map((s) => s.trim()) : [noPart, normalizeLowercaseStringOrEmpty(noPart)];
255
- lineData.templateMessage = {
277
+ const question = expectDefined(parts[0], "confirm question field");
278
+ const yesPart = expectDefined(parts[1], "confirm yes field");
279
+ const noPart = expectDefined(parts[2], "confirm no field");
280
+ const yesAction = parseConfirmAction(yesPart);
281
+ const noAction = parseConfirmAction(noPart);
282
+ if (question && yesAction.label && noAction.label) lineData.templateMessage = {
256
283
  type: "confirm",
257
284
  text: question,
258
- confirmLabel: yesLabel,
259
- confirmData: yesData,
260
- cancelLabel: noLabel,
261
- cancelData: noData,
285
+ confirmLabel: yesAction.label,
286
+ confirmData: yesAction.data,
287
+ cancelLabel: noAction.label,
288
+ cancelData: noAction.data,
262
289
  altText: question
263
290
  };
264
291
  }
@@ -266,10 +293,11 @@ function parseLineDirectives(payload) {
266
293
  }
267
294
  const buttonsMatch = text.match(/\[\[buttons:\s*([^\]]+)\]\]/i);
268
295
  if (buttonsMatch && !lineData.templateMessage) {
269
- const parts = buttonsMatch[1].split("|").map((s) => s.trim());
296
+ const parts = expectDefined(buttonsMatch[1], "buttons directive body").split("|").map((s) => s.trim());
270
297
  if (parts.length >= 3) {
271
- const [title, bodyText, actionsStr] = parts;
272
- const actions = actionsStr.split(",").map((actionStr) => {
298
+ const title = expectDefined(parts[0], "buttons title field");
299
+ const bodyText = expectDefined(parts[1], "buttons text field");
300
+ const actions = expectDefined(parts[2], "buttons actions field").split(",").map((actionStr) => {
273
301
  const trimmed = actionStr.trim();
274
302
  const colonIndex = (() => {
275
303
  const index = trimmed.indexOf(":");
@@ -302,22 +330,23 @@ function parseLineDirectives(payload) {
302
330
  label,
303
331
  data: data || label
304
332
  };
305
- });
306
- if (actions.length > 0) lineData.templateMessage = {
333
+ }).filter((action) => action.label);
334
+ if (actions.length > 0 && bodyText) lineData.templateMessage = {
307
335
  type: "buttons",
308
- title,
336
+ ...title ? { title } : {},
309
337
  text: bodyText,
310
338
  actions: actions.slice(0, 4),
311
- altText: `${title}: ${bodyText}`
339
+ altText: title ? `${title}: ${bodyText}` : bodyText
312
340
  };
313
341
  }
314
342
  text = text.replace(buttonsMatch[0], "").trim();
315
343
  }
316
344
  const mediaPlayerMatch = text.match(/\[\[media_player:\s*([^\]]+)\]\]/i);
317
345
  if (mediaPlayerMatch && !lineData.flexMessage) {
318
- const parts = mediaPlayerMatch[1].split("|").map((s) => s.trim());
346
+ const parts = expectDefined(mediaPlayerMatch[1], "media player directive body").split("|").map((s) => s.trim());
319
347
  if (parts.length >= 1) {
320
- const [title, artist, source, imageUrl, statusStr] = parts;
348
+ const title = expectDefined(parts[0], "media player title field");
349
+ const [, artist, source, imageUrl, statusStr] = parts;
321
350
  const isPlaying = normalizeLowercaseStringOrEmpty(statusStr) === "playing";
322
351
  const validImageUrl = imageUrl?.startsWith("https://") ? imageUrl : void 0;
323
352
  const deviceKey = toSlug(source || title || "media");
@@ -343,9 +372,13 @@ function parseLineDirectives(payload) {
343
372
  }
344
373
  const eventMatch = text.match(/\[\[event:\s*([^\]]+)\]\]/i);
345
374
  if (eventMatch && !lineData.flexMessage) {
346
- const parts = eventMatch[1].split("|").map((s) => s.trim());
375
+ const parts = expectDefined(eventMatch[1], "event directive body").split("|").map((s) => s.trim());
347
376
  if (parts.length >= 2) {
348
- const [title, date, time, location, description] = parts;
377
+ const title = expectDefined(parts[0], "event title field");
378
+ const date = expectDefined(parts[1], "event date field");
379
+ const time = parts[2];
380
+ const location = parts[3];
381
+ const description = parts[4];
349
382
  const card = createEventCard({
350
383
  title: title || "Event",
351
384
  date: date || "TBD",
@@ -362,9 +395,10 @@ function parseLineDirectives(payload) {
362
395
  }
363
396
  const appleTvMatch = text.match(/\[\[appletv_remote:\s*([^\]]+)\]\]/i);
364
397
  if (appleTvMatch && !lineData.flexMessage) {
365
- const parts = appleTvMatch[1].split("|").map((s) => s.trim());
398
+ const parts = expectDefined(appleTvMatch[1], "Apple TV directive body").split("|").map((s) => s.trim());
366
399
  if (parts.length >= 1) {
367
- const [deviceName, status] = parts;
400
+ const deviceName = expectDefined(parts[0], "Apple TV device name field");
401
+ const [, status] = parts;
368
402
  const deviceKey = toSlug(deviceName || "apple_tv");
369
403
  const card = createAppleTvRemoteCard({
370
404
  deviceName: deviceName || "Apple TV",
@@ -393,10 +427,10 @@ function parseLineDirectives(payload) {
393
427
  }
394
428
  const agendaMatch = text.match(/\[\[agenda:\s*([^\]]+)\]\]/i);
395
429
  if (agendaMatch && !lineData.flexMessage) {
396
- const parts = agendaMatch[1].split("|").map((s) => s.trim());
430
+ const parts = expectDefined(agendaMatch[1], "agenda directive body").split("|").map((s) => s.trim());
397
431
  if (parts.length >= 2) {
398
- const [title, eventsStr] = parts;
399
- const events = eventsStr.split(",").map((eventStr) => {
432
+ const title = expectDefined(parts[0], "agenda title field");
433
+ const events = normalizeStringEntries(expectDefined(parts[1], "agenda events field").split(",")).map((eventStr) => {
400
434
  const trimmed = eventStr.trim();
401
435
  const colonIdx = trimmed.lastIndexOf(":");
402
436
  if (colonIdx > 0) return {
@@ -418,17 +452,20 @@ function parseLineDirectives(payload) {
418
452
  }
419
453
  const deviceMatch = text.match(/\[\[device:\s*([^\]]+)\]\]/i);
420
454
  if (deviceMatch && !lineData.flexMessage) {
421
- const parts = deviceMatch[1].split("|").map((s) => s.trim());
455
+ const parts = expectDefined(deviceMatch[1], "device directive body").split("|").map((s) => s.trim());
422
456
  if (parts.length >= 1) {
423
- const [deviceName, deviceType, status, controlsStr] = parts;
457
+ const deviceName = expectDefined(parts[0], "device name field");
458
+ const [, deviceType, status, controlsStr] = parts;
424
459
  const deviceKey = toSlug(deviceName || "device");
425
- const controls = controlsStr ? controlsStr.split(",").map((ctrlStr) => {
426
- const [label, data] = ctrlStr.split(":").map((s) => s.trim());
427
- const action = data || normalizeLowercaseStringOrEmpty(label).replace(/\s+/g, "_");
428
- return {
460
+ const controls = controlsStr ? normalizeStringEntries(controlsStr.split(",")).flatMap((ctrlStr) => {
461
+ const controlParts = ctrlStr.split(":").map((s) => s.trim());
462
+ const label = expectDefined(controlParts[0], "device control label");
463
+ if (!label) return [];
464
+ const action = controlParts[1] || normalizeLowercaseStringOrEmpty(label).replace(/\s+/g, "_");
465
+ return [{
429
466
  label,
430
467
  data: lineActionData(action, { "line.device": deviceKey })
431
- };
468
+ }];
432
469
  }) : [];
433
470
  const card = createDeviceControlCard({
434
471
  deviceName: deviceName || "Device",
@@ -455,4 +492,4 @@ function hasLineDirectives(text) {
455
492
  return /\[\[(quick_replies|location|confirm|buttons|media_player|event|agenda|device|appletv_remote):/i.test(text);
456
493
  }
457
494
  //#endregion
458
- export { resolveLineOutboundMedia as a, setLineRuntime as c, resolveExactLineGroupConfigKey as d, resolveLineGroupConfigEntry as f, buildLineQuickReplyFallbackText as i, LineChannelConfigSchema as l, resolveLineGroupsConfig as m, parseLineDirectives as n, validateLineMediaUrl as o, resolveLineGroupLookupIds as p, createLineSendReceipt as r, getLineRuntime as s, hasLineDirectives as t, LineConfigSchema as u };
495
+ export { buildLineMediaMessage as a, validateLineMediaUrl as c, LineChannelConfigSchema as d, LineConfigSchema as f, resolveLineGroupsConfig as g, resolveLineGroupLookupIds as h, buildLineQuickReplyFallbackText as i, getLineRuntime as l, resolveLineGroupConfigEntry as m, parseLineDirectives as n, hasLineSpecificMediaOptions as o, resolveExactLineGroupConfigKey as p, createLineSendReceipt as r, resolveLineOutboundMedia as s, hasLineDirectives as t, setLineRuntime as u };
@@ -1,16 +1,17 @@
1
1
  import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-DJVOv1JI.js";
2
- import { c as setLineRuntime, d as resolveExactLineGroupConfigKey, f as resolveLineGroupConfigEntry, l as LineChannelConfigSchema, m as resolveLineGroupsConfig, n as parseLineDirectives, p as resolveLineGroupLookupIds, t as hasLineDirectives, u as LineConfigSchema } from "./reply-payload-transform-C7r5npxP.js";
3
- import { _ as createNotificationBubble, a as datetimePickerAction, c as uriAction, d as createReceiptCard, f as createActionCard, g as createListCard, h as createInfoCard, i as createMediaPlayerCard, l as createAgendaCard, m as createImageCard, n as createAppleTvRemoteCard, o as messageAction, p as createCarousel, r as createDeviceControlCard, s as postbackAction, t as toFlexMessage, u as createEventCard } from "./flex-templates-DEIXn69v.js";
4
- 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-0eXlx_Pe.js";
2
+ import { d as LineChannelConfigSchema, f as LineConfigSchema, g as resolveLineGroupsConfig, h as resolveLineGroupLookupIds, m as resolveLineGroupConfigEntry, n as parseLineDirectives, p as resolveExactLineGroupConfigKey, t as hasLineDirectives, u as setLineRuntime } from "./reply-payload-transform-r9sa-TYT.js";
3
+ import { _ as createNotificationBubble, a as datetimePickerAction, c as uriAction, d as createReceiptCard, f as createActionCard, g as createListCard, h as createInfoCard, i as createMediaPlayerCard, l as createAgendaCard, m as createImageCard, n as createAppleTvRemoteCard, o as messageAction, p as createCarousel, r as createDeviceControlCard, s as postbackAction, t as toFlexMessage, u as createEventCard } from "./flex-templates-K1lS9i8L.js";
4
+ 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-Db2rZsSX.js";
5
5
  import { t as probeLineBot } from "./probe-BslD77tJ.js";
6
- import { A as buildTemplateMessageFromPayload, B as createYesNoConfirm, C as pushMessagesLine, D as sendMessageLine, E as replyMessageLine, F as createImageCarousel, 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, _ 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-DmpZY78n.js";
6
+ import { A as buildTemplateMessageFromPayload, B as createYesNoConfirm, C as pushMessagesLine, D as sendMessageLine, E as replyMessageLine, F as createImageCarousel, 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, _ 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-XceqeKRP.js";
7
7
  import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
8
8
  import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
9
9
  import { createMessageReceiveContext } from "openclaw/plugin-sdk/channel-outbound";
10
10
  import { DEFAULT_ACCOUNT_ID, formatDocsLink, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
11
11
  import { buildComputedAccountStatusSnapshot, buildTokenChannelStatusSummary } from "openclaw/plugin-sdk/status-helpers";
12
- import { messagingApi } from "@line/bot-sdk";
13
12
  import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
13
+ import { messagingApi } from "@line/bot-sdk";
14
+ import { runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
14
15
  import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/agent-media-payload";
15
16
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
16
17
  import { loadWebMediaRaw } from "openclaw/plugin-sdk/web-media";
@@ -69,7 +70,7 @@ function createLineWebhookMiddleware(options) {
69
70
  if (receiveContext.shouldAckAfter("receive_record")) await receiveContext.ack();
70
71
  if (body.events && body.events.length > 0) {
71
72
  logVerbose(`line: received ${body.events.length} webhook events`);
72
- Promise.resolve().then(() => onEvents(body)).catch((err) => logLineWebhookDispatchError(runtime, err));
73
+ runDetachedWebhookWork(() => onEvents(body)).catch((err) => logLineWebhookDispatchError(runtime, err));
73
74
  }
74
75
  } catch (err) {
75
76
  await receiveContext?.nack(err);
@@ -92,7 +93,6 @@ function startLineWebhook(options) {
92
93
  }
93
94
  //#endregion
94
95
  //#region extensions/line/src/rich-menu.ts
95
- const USER_BATCH_SIZE = 500;
96
96
  const graphemeSegmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
97
97
  function getClient(opts) {
98
98
  const account = resolveLineAccount({
@@ -110,11 +110,6 @@ function getBlobClient(opts) {
110
110
  const token = resolveLineChannelAccessToken(opts.channelAccessToken, account);
111
111
  return new messagingApi.MessagingApiBlobClient({ channelAccessToken: token });
112
112
  }
113
- function chunkUserIds(userIds) {
114
- const batches = [];
115
- for (let i = 0; i < userIds.length; i += USER_BATCH_SIZE) batches.push(userIds.slice(i, i + USER_BATCH_SIZE));
116
- return batches;
117
- }
118
113
  function truncateGraphemes(input, maxLength) {
119
114
  let result = "";
120
115
  let count = 0;
@@ -163,27 +158,6 @@ async function getDefaultRichMenuId(opts) {
163
158
  return null;
164
159
  }
165
160
  }
166
- async function linkRichMenuToUser(userId, richMenuId, opts) {
167
- await getClient(opts).linkRichMenuIdToUser(userId, richMenuId);
168
- if (opts.verbose) logVerbose(`line: linked rich menu ${richMenuId} to user ${userId}`);
169
- }
170
- async function linkRichMenuToUsers(userIds, richMenuId, opts) {
171
- const client = getClient(opts);
172
- for (const batch of chunkUserIds(userIds)) await client.linkRichMenuIdToUsers({
173
- richMenuId,
174
- userIds: batch
175
- });
176
- if (opts.verbose) logVerbose(`line: linked rich menu ${richMenuId} to ${userIds.length} users`);
177
- }
178
- async function unlinkRichMenuFromUser(userId, opts) {
179
- await getClient(opts).unlinkRichMenuIdFromUser(userId);
180
- if (opts.verbose) logVerbose(`line: unlinked rich menu from user ${userId}`);
181
- }
182
- async function unlinkRichMenuFromUsers(userIds, opts) {
183
- const client = getClient(opts);
184
- for (const batch of chunkUserIds(userIds)) await client.unlinkRichMenuIdFromUsers({ userIds: batch });
185
- if (opts.verbose) logVerbose(`line: unlinked rich menu from ${userIds.length} users`);
186
- }
187
161
  async function getRichMenuIdOfUser(userId, opts) {
188
162
  const client = getClient(opts);
189
163
  try {
@@ -298,4 +272,4 @@ function createDefaultMenuConfig() {
298
272
  };
299
273
  }
300
274
  //#endregion
301
- 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 };
275
+ 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, 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, uploadRichMenuImage, uriAction, validateLineSignature };
package/dist/setup-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-Bc36YSS3.js";
1
+ import { n as lineSetupAdapter, t as lineSetupWizard } from "./setup-surface-B7W6pwn3.js";
2
2
  export { lineSetupAdapter, lineSetupWizard };
@@ -1,6 +1,6 @@
1
1
  import { i as resolveLineAccount, n as normalizeAccountId, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-DJVOv1JI.js";
2
2
  import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
3
- import { DEFAULT_ACCOUNT_ID, createAllowFromSection, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, formatDocsLink, mergeAllowFromEntries, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
3
+ import { DEFAULT_ACCOUNT_ID, createAllowFromSection, createSetupInputPresenceValidator, createSetupTranslator, createStandardChannelSetupStatus, defineTokenCredential, formatDocsLink, mergeAllowFromEntries, setSetupChannelEnabled, splitSetupEntries } from "openclaw/plugin-sdk/setup";
4
4
  //#region extensions/line/src/account-helpers.ts
5
5
  function hasLineCredentials(account) {
6
6
  return Boolean(account.channelAccessToken?.trim() && account.channelSecret?.trim());
@@ -173,8 +173,10 @@ const lineSetupWizard = {
173
173
  lines: LINE_SETUP_HELP_LINES,
174
174
  shouldShow: ({ cfg, accountId }) => !isLineConfigured(cfg, accountId ?? resolveDefaultLineAccountId(cfg))
175
175
  },
176
- credentials: [{
176
+ credentials: [defineTokenCredential({
177
177
  inputKey: "token",
178
+ configKey: "channelAccessToken",
179
+ configuredFields: ["channelAccessToken", "tokenFile"],
178
180
  providerHint: channel,
179
181
  credentialLabel: t("wizard.line.channelAccessToken"),
180
182
  preferredEnvVar: "LINE_CHANNEL_ACCESS_TOKEN",
@@ -184,34 +186,30 @@ const lineSetupWizard = {
184
186
  keepPrompt: t("wizard.line.tokenKeepPrompt"),
185
187
  inputPrompt: t("wizard.line.tokenInputPrompt"),
186
188
  allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID,
187
- inspect: ({ cfg, accountId }) => {
188
- const resolved = resolveLineAccount({
189
- cfg,
190
- accountId
191
- });
192
- return {
193
- accountConfigured: Boolean(normalizeOptionalString(resolved.channelAccessToken) && normalizeOptionalString(resolved.channelSecret)),
194
- hasConfiguredValue: Boolean(normalizeOptionalString(resolved.config.channelAccessToken) ?? normalizeOptionalString(resolved.config.tokenFile)),
195
- resolvedValue: normalizeOptionalString(resolved.channelAccessToken),
196
- envValue: accountId === DEFAULT_ACCOUNT_ID ? normalizeOptionalString(process.env.LINE_CHANNEL_ACCESS_TOKEN) : void 0
197
- };
198
- },
199
- applyUseEnv: ({ cfg, accountId }) => patchLineAccountConfig({
189
+ resolveAccount: ({ cfg, accountId }) => resolveLineAccount({
200
190
  cfg,
201
- accountId,
202
- enabled: true,
203
- clearFields: ["channelAccessToken", "tokenFile"],
204
- patch: {}
191
+ accountId
205
192
  }),
206
- applySet: ({ cfg, accountId, resolvedValue }) => patchLineAccountConfig({
193
+ accountConfigured: (account) => Boolean(normalizeOptionalString(account.channelAccessToken) && normalizeOptionalString(account.channelSecret)),
194
+ hasConfiguredValue: (account) => Boolean(normalizeOptionalString(account.config.channelAccessToken) ?? normalizeOptionalString(account.config.tokenFile)),
195
+ resolvedValue: (account) => normalizeOptionalString(account.channelAccessToken),
196
+ envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID ? normalizeOptionalString(process.env.LINE_CHANNEL_ACCESS_TOKEN) : void 0,
197
+ patchAccount: ({ cfg, accountId, patch, clearFields }) => patchLineAccountConfig({
207
198
  cfg,
208
199
  accountId,
209
200
  enabled: true,
201
+ clearFields,
202
+ patch
203
+ }),
204
+ useEnv: { clearFields: ["channelAccessToken", "tokenFile"] },
205
+ set: {
210
206
  clearFields: ["tokenFile"],
211
- patch: { channelAccessToken: resolvedValue }
212
- })
213
- }, {
207
+ value: "resolved"
208
+ }
209
+ }), defineTokenCredential({
214
210
  inputKey: "password",
211
+ configKey: "channelSecret",
212
+ configuredFields: ["channelSecret", "secretFile"],
215
213
  providerHint: "line-secret",
216
214
  credentialLabel: t("wizard.line.channelSecret"),
217
215
  preferredEnvVar: "LINE_CHANNEL_SECRET",
@@ -221,33 +219,27 @@ const lineSetupWizard = {
221
219
  keepPrompt: t("wizard.line.secretKeepPrompt"),
222
220
  inputPrompt: t("wizard.line.secretInputPrompt"),
223
221
  allowEnv: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID,
224
- inspect: ({ cfg, accountId }) => {
225
- const resolved = resolveLineAccount({
226
- cfg,
227
- accountId
228
- });
229
- return {
230
- accountConfigured: Boolean(normalizeOptionalString(resolved.channelAccessToken) && normalizeOptionalString(resolved.channelSecret)),
231
- hasConfiguredValue: Boolean(normalizeOptionalString(resolved.config.channelSecret) ?? normalizeOptionalString(resolved.config.secretFile)),
232
- resolvedValue: normalizeOptionalString(resolved.channelSecret),
233
- envValue: accountId === DEFAULT_ACCOUNT_ID ? normalizeOptionalString(process.env.LINE_CHANNEL_SECRET) : void 0
234
- };
235
- },
236
- applyUseEnv: ({ cfg, accountId }) => patchLineAccountConfig({
222
+ resolveAccount: ({ cfg, accountId }) => resolveLineAccount({
237
223
  cfg,
238
- accountId,
239
- enabled: true,
240
- clearFields: ["channelSecret", "secretFile"],
241
- patch: {}
224
+ accountId
242
225
  }),
243
- applySet: ({ cfg, accountId, resolvedValue }) => patchLineAccountConfig({
226
+ accountConfigured: (account) => Boolean(normalizeOptionalString(account.channelAccessToken) && normalizeOptionalString(account.channelSecret)),
227
+ hasConfiguredValue: (account) => Boolean(normalizeOptionalString(account.config.channelSecret) ?? normalizeOptionalString(account.config.secretFile)),
228
+ resolvedValue: (account) => normalizeOptionalString(account.channelSecret),
229
+ envValue: ({ accountId }) => accountId === DEFAULT_ACCOUNT_ID ? normalizeOptionalString(process.env.LINE_CHANNEL_SECRET) : void 0,
230
+ patchAccount: ({ cfg, accountId, patch, clearFields }) => patchLineAccountConfig({
244
231
  cfg,
245
232
  accountId,
246
233
  enabled: true,
234
+ clearFields,
235
+ patch
236
+ }),
237
+ useEnv: { clearFields: ["channelSecret", "secretFile"] },
238
+ set: {
247
239
  clearFields: ["secretFile"],
248
- patch: { channelSecret: resolvedValue }
249
- })
250
- }],
240
+ value: "resolved"
241
+ }
242
+ })],
251
243
  allowFrom: createAllowFromSection({
252
244
  helpTitle: t("wizard.line.allowlistTitle"),
253
245
  helpLines: LINE_ALLOW_FROM_HELP_LINES,
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@openclaw/line",
3
- "version": "2026.7.1",
3
+ "version": "2026.7.2-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/line",
9
- "version": "2026.7.1",
9
+ "version": "2026.7.2-beta.2",
10
10
  "dependencies": {
11
11
  "@line/bot-sdk": "11.1.0",
12
12
  "zod": "4.4.3"
13
13
  },
14
14
  "peerDependencies": {
15
- "openclaw": ">=2026.7.1"
15
+ "openclaw": ">=2026.7.2-beta.2"
16
16
  },
17
17
  "peerDependenciesMeta": {
18
18
  "openclaw": {