@openclaw/line 2026.9.1-beta.1 → 2026.9.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.
Files changed (34) hide show
  1. package/dist/account-helpers-BSUyZwLm.js +12 -0
  2. package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
  3. package/dist/api.js +2 -2
  4. package/dist/card-command-pwvE48Wb.js +252 -0
  5. package/dist/{channel-CsQLcrsA.js → channel-CF2AeULJ.js} +111 -380
  6. package/dist/channel-plugin-api.js +1 -1
  7. package/dist/channel.runtime-BF4Wk2dM.js +4 -0
  8. package/dist/contract-api.js +1 -1
  9. package/dist/doctor-contract-api.js +141 -0
  10. package/dist/index.js +5 -17
  11. package/dist/{markdown-to-line-BmZ4hhfC.js → markdown-to-line-B-sj-7II.js} +22 -23
  12. package/dist/{monitor-Bd9XDV1i.js → monitor-D0fHFXCi.js} +323 -234
  13. package/dist/monitor.runtime-Mgecav_D.js +2 -0
  14. package/dist/outbound.runtime-CGqyJ-Ug.js +3 -0
  15. package/dist/probe.runtime-CbtMgU4s.js +2 -0
  16. package/dist/rich-messages-DY1AGKAJ.js +742 -0
  17. package/dist/runtime-api.js +14 -15
  18. package/dist/{send-Dce0m539.js → send-CQQxpxPp.js} +142 -112
  19. package/dist/{send-receipt-CCPTalf9.js → send-retry-ChpfJ-LM.js} +326 -86
  20. package/dist/setup-api.js +1 -1
  21. package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
  22. package/dist/webhook-spool-contract-1rY8OJw8.js +59 -0
  23. package/node_modules/typebox/build/format/email.mjs +1 -1
  24. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
  25. package/node_modules/typebox/package.json +1 -1
  26. package/openclaw.plugin.json +16 -2
  27. package/package.json +5 -5
  28. package/dist/card-command-CmU47gD9.js +0 -230
  29. package/dist/channel.runtime-B83x7k_i.js +0 -4
  30. package/dist/media-control-cards-CksNB8Wk.js +0 -474
  31. package/dist/monitor.runtime-BCCa6qTX.js +0 -2
  32. package/dist/outbound.runtime-D4w0-Gkc.js +0 -3
  33. package/dist/probe-CM03wo6r.js +0 -28
  34. package/dist/probe.runtime-D13ZtXsd.js +0 -2
@@ -0,0 +1,59 @@
1
+ import { isRecord, normalizeNullableString } from "openclaw/plugin-sdk/string-coerce-runtime";
2
+ //#region extensions/line/src/webhook-spool-contract.ts
3
+ /** Message the canonical decoder attaches when it rejects a spool payload. The
4
+ * upgrade migration matches this signature to recover rows the pre-fix decoder
5
+ * dead-lettered; the identity fence writes a different message on purpose. */
6
+ const LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE = "LINE webhook spool payload is invalid.";
7
+ /** Dead-letter reason for undecodable events; shared so the migration's
8
+ * recovery signature can never drift from what the spool writes. */
9
+ const LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON = "invalid-event";
10
+ /** Defined locally (not via createChannelIngressError) because this module sits
11
+ * in the doctor contract closure, which must stay off the channel-outbound
12
+ * runtime barrel; the spool's permanent-failure classifier matches by class. */
13
+ var LineWebhookPayloadError = class extends Error {
14
+ constructor(message, options) {
15
+ super(message, options);
16
+ this.name = "LineWebhookPayloadError";
17
+ }
18
+ };
19
+ /** Message ids preserve the shipped replay-guard keyspace; other events use LINE's delivery id. */
20
+ function eventIdFor(event) {
21
+ if (!isRecord(event)) throw new LineWebhookPayloadError("LINE webhook event must be an object.");
22
+ if (event.type === "message") {
23
+ const message = isRecord(event.message) ? event.message : void 0;
24
+ const messageId = normalizeNullableString(message?.id);
25
+ if (messageId) return `message:${messageId}`;
26
+ }
27
+ const webhookEventId = normalizeNullableString(event.webhookEventId);
28
+ if (webhookEventId) return `event:${webhookEventId}`;
29
+ throw new LineWebhookPayloadError("LINE webhook event is missing a stable delivery id.");
30
+ }
31
+ /** Pre-drain (#109655) rows were keyed by the raw webhookEventId, before the
32
+ * message:/event: keyspace. The upgrade migration uses this prior derivation as its
33
+ * identity fence so a genuinely changed event still dead-letters. */
34
+ function legacyEventIdFor(event) {
35
+ if (!isRecord(event)) return null;
36
+ return normalizeNullableString(event.webhookEventId);
37
+ }
38
+ function laneKeyFor(event, eventId) {
39
+ if (!isRecord(event)) return eventId;
40
+ const source = isRecord(event.source) ? event.source : void 0;
41
+ if (source?.type === "group") {
42
+ const groupId = normalizeNullableString(source.groupId);
43
+ if (groupId) return `group:${groupId}`;
44
+ }
45
+ if (source?.type === "room") {
46
+ const roomId = normalizeNullableString(source.roomId);
47
+ if (roomId) return `room:${roomId}`;
48
+ }
49
+ if (source?.type === "user") {
50
+ const userId = normalizeNullableString(source.userId);
51
+ if (userId) return `user:${userId}`;
52
+ }
53
+ return eventId;
54
+ }
55
+ function errorText(error) {
56
+ return error instanceof Error ? error.message : String(error);
57
+ }
58
+ //#endregion
59
+ export { eventIdFor as a, errorText as i, LINE_WEBHOOK_SPOOL_INVALID_PAYLOAD_MESSAGE as n, laneKeyFor as o, LineWebhookPayloadError as r, legacyEventIdFor as s, LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON as t };
@@ -1,4 +1,4 @@
1
- const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\.)*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i;
1
+ const Email = /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[^"\\]|\\[\x20-\x7e])*")@(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*|\[(?:IPv6:[a-f0-9:]+|(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?:\.(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3})\])$/i;
2
2
  /**
3
3
  * Returns true if the value is an Email
4
4
  * @specification https://datatracker.ietf.org/doc/html/rfc5322
@@ -1,6 +1,9 @@
1
1
  import * as FormatBidi from './format/bidi.mjs';
2
2
  import * as LabelUnicode from './label/unicode.mjs';
3
3
  import * as LabelPuny from './label/puny.mjs';
4
+ // ------------------------------------------------------------------
5
+ // IsLabel
6
+ // ------------------------------------------------------------------
4
7
  function IsValidLabelLength(value) {
5
8
  return value.length > 0 && value.length <= 63;
6
9
  }
@@ -8,9 +11,24 @@ function IsLabel(value) {
8
11
  return IsValidLabelLength(value) && (LabelPuny.IsPunyLabel(value) ||
9
12
  LabelUnicode.IsUnicodeLabel(value));
10
13
  }
14
+ // ------------------------------------------------------------------
15
+ // NormalizeHostname
16
+ //
17
+ // Normalizes a hostname for label validation. Normalizes for NFC
18
+ // such that equivalent codepoint sequences can be uniformly
19
+ // compared. It also removes codepoints that IDNA mapping ignores,
20
+ // and converts full stop variants to ASCII '.' so the hostname can
21
+ // be split into labels.
22
+ // ------------------------------------------------------------------
11
23
  function NormalizeHostname(value) {
12
- return value.normalize('NFC').replace(/[\u002E\u3002\uFF0E\uFF61]/g, '.');
24
+ return value
25
+ .normalize('NFC')
26
+ .replace(/[\u00ad\u034f\u180b-\u180d\u200b\ufe00-\ufe0f\u{e0100}-\u{e01ef}]/gu, '') // RE_IGNORED_MAPPING
27
+ .replace(/[\u002E\u3002\uFF0E\uFF61]/g, '.'); // RE_FULL_STOP_MAPPING
13
28
  }
29
+ // ------------------------------------------------------------------
30
+ // IsIdnHostname
31
+ // ------------------------------------------------------------------
14
32
  export function IsIdnHostname(value) {
15
33
  if (value.length === 0 || value.includes(' '))
16
34
  return false;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typebox",
3
3
  "description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
4
- "version": "1.3.16",
4
+ "version": "1.3.17",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "jsonschema"
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "id": "line",
3
- "doctorContract": {},
3
+ "doctorContract": {
4
+ "stateMigrations": true
5
+ },
4
6
  "name": "LINE",
5
7
  "description": "OpenClaw LINE channel plugin for LINE Bot API chats.",
6
8
  "icon": "https://cdn.simpleicons.org/line",
@@ -27,6 +29,9 @@
27
29
  "configWrites": {
28
30
  "type": "boolean"
29
31
  },
32
+ "joinIntro": {
33
+ "type": "boolean"
34
+ },
30
35
  "historyLimit": {
31
36
  "type": "integer",
32
37
  "minimum": 0,
@@ -180,6 +185,9 @@
180
185
  "configWrites": {
181
186
  "type": "boolean"
182
187
  },
188
+ "joinIntro": {
189
+ "type": "boolean"
190
+ },
183
191
  "historyLimit": {
184
192
  "type": "integer",
185
193
  "minimum": 0,
@@ -338,7 +346,13 @@
338
346
  "additionalProperties": false
339
347
  },
340
348
  "label": "LINE",
341
- "description": "LINE Messaging API webhook bot."
349
+ "description": "LINE Messaging API webhook bot.",
350
+ "uiHints": {
351
+ "joinIntro": {
352
+ "label": "LINE Group Join Introduction",
353
+ "help": "Post one brief introduction when the bot joins an allowed LINE group or multi-person room (default: true). Account settings override the channel-wide setting."
354
+ }
355
+ }
342
356
  }
343
357
  }
344
358
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/line",
3
- "version": "2026.9.1-beta.1",
3
+ "version": "2026.9.1",
4
4
  "description": "OpenClaw LINE channel plugin for LINE Bot API chats.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,11 +9,11 @@
9
9
  "type": "module",
10
10
  "dependencies": {
11
11
  "@line/bot-sdk": "11.2.0",
12
- "typebox": "1.3.16",
12
+ "typebox": "1.3.17",
13
13
  "zod": "4.4.3"
14
14
  },
15
15
  "peerDependencies": {
16
- "openclaw": ">=2026.9.1-beta.1"
16
+ "openclaw": ">=2026.9.1"
17
17
  },
18
18
  "peerDependenciesMeta": {
19
19
  "openclaw": {
@@ -112,10 +112,10 @@
112
112
  "minHostVersion": ">=2026.4.10"
113
113
  },
114
114
  "compat": {
115
- "pluginApi": ">=2026.9.1-beta.1"
115
+ "pluginApi": ">=2026.9.1"
116
116
  },
117
117
  "build": {
118
- "openclawVersion": "2026.9.1-beta.1"
118
+ "openclawVersion": "2026.9.1"
119
119
  },
120
120
  "release": {
121
121
  "publishToClawHub": true,
@@ -1,230 +0,0 @@
1
- import { a as createActionCard, c as createListCard, i as createReceiptCard, o as createImageCard, s as createInfoCard } from "./send-receipt-CCPTalf9.js";
2
- import { n as createFlexMessage } from "./send-Dce0m539.js";
3
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
4
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
5
- //#region extensions/line/src/card-command.ts
6
- const CARD_USAGE = `Usage: /card <type> "title" "body" [options]
7
-
8
- Types:
9
- info "Title" "Body" ["Footer"]
10
- image "Title" "Caption" --url <image-url>
11
- action "Title" "Body" --actions "Btn1|url1,Btn2|text2"
12
- list "Title" "Item1|Desc1,Item2|Desc2"
13
- receipt "Title" "Item1:$10,Item2:$20" --total "$30"
14
- confirm "Question?" --yes "Yes|data" --no "No|data"
15
- buttons "Title" "Text" --actions "Btn1|url1,Btn2|data2"
16
-
17
- Examples:
18
- /card info "Welcome" "Thanks for joining!"
19
- /card image "Product" "Check it out" --url https://example.com/img.jpg
20
- /card action "Menu" "Choose an option" --actions "Order|/order,Help|/help"`;
21
- function buildLineReply(lineData) {
22
- return { channelData: { line: lineData } };
23
- }
24
- function buildLineFlexReply(altText, contents) {
25
- const message = createFlexMessage(altText, contents);
26
- return buildLineReply({ flexMessage: {
27
- altText: message.altText,
28
- contents: message.contents
29
- } });
30
- }
31
- /**
32
- * Parse action string format: "Label|data,Label2|data2"
33
- * Data can be a URL (uri action) or plain text (message action) or key=value (postback)
34
- */
35
- function parseActions(actionsStr) {
36
- if (!actionsStr) return [];
37
- const results = [];
38
- for (const part of actionsStr.split(",")) {
39
- const [label, data] = part.trim().split("|").map((s) => s.trim());
40
- if (!label) continue;
41
- const actionData = data || label;
42
- const action = actionData.startsWith("http://") || actionData.startsWith("https://") ? {
43
- type: "uri",
44
- label,
45
- uri: actionData
46
- } : actionData.includes("=") ? {
47
- type: "postback",
48
- label,
49
- data: actionData,
50
- displayText: label
51
- } : {
52
- type: "message",
53
- label,
54
- text: actionData
55
- };
56
- results.push({
57
- label,
58
- action
59
- });
60
- }
61
- return results;
62
- }
63
- /**
64
- * Parse list items format: "Item1|Subtitle1,Item2|Subtitle2"
65
- */
66
- function parseListItems(itemsStr) {
67
- return itemsStr.split(",").map((part) => {
68
- const [title, subtitle] = part.trim().split("|").map((s) => s.trim());
69
- return {
70
- title: title || "",
71
- subtitle
72
- };
73
- }).filter((item) => item.title);
74
- }
75
- /**
76
- * Parse receipt items format: "Item1:$10,Item2:$20"
77
- */
78
- function parseReceiptItems(itemsStr) {
79
- return itemsStr.split(",").map((part) => {
80
- const colonIndex = part.lastIndexOf(":");
81
- if (colonIndex === -1) return {
82
- name: part.trim(),
83
- value: ""
84
- };
85
- return {
86
- name: part.slice(0, colonIndex).trim(),
87
- value: part.slice(colonIndex + 1).trim()
88
- };
89
- }).filter((item) => item.name);
90
- }
91
- /**
92
- * Parse quoted arguments from command string
93
- * Supports: /card type "arg1" "arg2" "arg3" --flag value
94
- */
95
- function parseCardArgs(argsStrInput) {
96
- let argsStr = argsStrInput;
97
- const result = {
98
- type: "",
99
- args: [],
100
- flags: {}
101
- };
102
- const typeMatch = argsStr.match(/^(\w+)/);
103
- if (typeMatch) {
104
- result.type = normalizeLowercaseStringOrEmpty(typeMatch[1]);
105
- argsStr = argsStr.slice(typeMatch[0].length).trim();
106
- }
107
- const quotedRegex = /"([^"]*?)"/g;
108
- let match;
109
- while ((match = quotedRegex.exec(argsStr)) !== null) result.args.push(expectDefined(match[1], "quoted card argument capture"));
110
- const flagRegex = /--(\w+)\s+(?:"([^"]*?)"|(\S+))/g;
111
- while ((match = flagRegex.exec(argsStr)) !== null) {
112
- const key = expectDefined(match[1], "card flag name capture");
113
- result.flags[key] = expectDefined(match[2] ?? match[3], "card flag value capture");
114
- }
115
- return result;
116
- }
117
- function registerLineCardCommand(api) {
118
- api.registerCommand({
119
- name: "card",
120
- description: "Send a rich card message (LINE).",
121
- acceptsArgs: true,
122
- requireAuth: false,
123
- handler: async (ctx) => {
124
- const argsStr = ctx.args?.trim() ?? "";
125
- if (!argsStr) return { text: CARD_USAGE };
126
- const { type, args, flags } = parseCardArgs(argsStr);
127
- if (!type) return { text: CARD_USAGE };
128
- if (ctx.channel !== "line") return { text: `[${type} card] ${args.join(" - ")}`.trim() };
129
- try {
130
- switch (type) {
131
- case "info": {
132
- const [title = "Info", body = "", footer] = args;
133
- const bubble = createInfoCard(title, body, footer);
134
- return buildLineFlexReply(`${title}: ${body}`, bubble);
135
- }
136
- case "image": {
137
- const [title = "Image", caption = ""] = args;
138
- const imageUrl = flags.url || flags.image;
139
- if (!imageUrl) return { text: "Error: Image card requires --url <image-url>" };
140
- const bubble = createImageCard(imageUrl, title, caption);
141
- return buildLineFlexReply(`${title}: ${caption}`, bubble);
142
- }
143
- case "action": {
144
- const [title = "Actions", body = ""] = args;
145
- const actions = parseActions(flags.actions);
146
- if (actions.length === 0) return { text: "Error: Action card requires --actions \"Label1|data1,Label2|data2\"" };
147
- const bubble = createActionCard(title, body, actions, { imageUrl: flags.url || flags.image });
148
- return buildLineFlexReply(`${title}: ${body}`, bubble);
149
- }
150
- case "list": {
151
- const [title = "List", itemsStr = ""] = args;
152
- const items = parseListItems(itemsStr || flags.items || "");
153
- if (items.length === 0) return { text: "Error: List card requires items. Usage: /card list \"Title\" \"Item1|Desc1,Item2|Desc2\"" };
154
- const bubble = createListCard(title, items);
155
- return buildLineFlexReply(`${title}: ${items.map((item) => item.title).join(", ")}`, bubble);
156
- }
157
- case "receipt": {
158
- const [title = "Receipt", itemsStr = ""] = args;
159
- const items = parseReceiptItems(itemsStr || flags.items || "");
160
- const total = flags.total ? {
161
- label: "Total",
162
- value: flags.total
163
- } : void 0;
164
- const footer = flags.footer;
165
- if (items.length === 0) return { text: "Error: Receipt card requires items. Usage: /card receipt \"Title\" \"Item1:$10,Item2:$20\" --total \"$30\"" };
166
- const bubble = createReceiptCard({
167
- title,
168
- items,
169
- total,
170
- footer
171
- });
172
- return buildLineFlexReply(`${title}: ${items.map((item) => `${item.name} ${item.value}`).join(", ")}`, bubble);
173
- }
174
- case "confirm": {
175
- const [question = "Confirm?"] = args;
176
- const yesStr = flags.yes || "Yes|yes";
177
- const noStr = flags.no || "No|no";
178
- const [yesLabel, yesData] = yesStr.split("|").map((s) => s.trim());
179
- const [noLabel, noData] = noStr.split("|").map((s) => s.trim());
180
- return buildLineReply({ templateMessage: {
181
- type: "confirm",
182
- text: question,
183
- confirmLabel: yesLabel || "Yes",
184
- confirmData: yesData || "yes",
185
- cancelLabel: noLabel || "No",
186
- cancelData: noData || "no",
187
- altText: question
188
- } });
189
- }
190
- case "buttons": {
191
- const [title = "Menu", text = "Choose an option"] = args;
192
- const actionParts = parseActions(flags.actions || "");
193
- if (actionParts.length === 0) return { text: "Error: Buttons card requires --actions \"Label1|data1,Label2|data2\"" };
194
- const templateActions = actionParts.map((a) => {
195
- const action = a.action;
196
- const label = action.label ?? a.label;
197
- if (action.type === "uri") return {
198
- type: "uri",
199
- label,
200
- uri: action.uri
201
- };
202
- if (action.type === "postback") return {
203
- type: "postback",
204
- label,
205
- data: action.data
206
- };
207
- return {
208
- type: "message",
209
- label,
210
- data: action.text
211
- };
212
- });
213
- return buildLineReply({ templateMessage: {
214
- type: "buttons",
215
- title,
216
- text,
217
- thumbnailImageUrl: flags.url || flags.image,
218
- actions: templateActions
219
- } });
220
- }
221
- default: return { text: `Unknown card type: "${type}". Available types: info, image, action, list, receipt, confirm, buttons` };
222
- }
223
- } catch (err) {
224
- return { text: `Error creating card: ${String(err)}` };
225
- }
226
- }
227
- });
228
- }
229
- //#endregion
230
- export { registerLineCardCommand };
@@ -1,4 +0,0 @@
1
- import { t as monitorLineProvider } from "./monitor-Bd9XDV1i.js";
2
- import { t as probeLineBot } from "./probe-CM03wo6r.js";
3
- import { p as pushMessageLine } from "./send-Dce0m539.js";
4
- export { monitorLineProvider, probeLineBot, pushMessageLine };