@openclaw/line 2026.9.1-beta.1 → 2026.9.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.
Files changed (46) 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-BJvELOIg.js} +111 -380
  6. package/dist/channel-plugin-api.js +1 -1
  7. package/dist/channel.runtime-CZOszXXj.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-m9h5osFz.js} +339 -237
  13. package/dist/monitor.runtime-C90LL-P3.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/build/format/json_pointer.d.mts +2 -2
  26. package/node_modules/typebox/build/format/json_pointer.mjs +2 -2
  27. package/node_modules/typebox/build/format/json_pointer_uri_fragment.d.mts +2 -2
  28. package/node_modules/typebox/build/format/json_pointer_uri_fragment.mjs +2 -2
  29. package/node_modules/typebox/build/format/relative_json_pointer.d.mts +2 -2
  30. package/node_modules/typebox/build/format/relative_json_pointer.mjs +2 -2
  31. package/node_modules/typebox/build/system/memory/assign.mjs +2 -1
  32. package/node_modules/typebox/build/system/memory/create.mjs +3 -4
  33. package/node_modules/typebox/build/system/memory/discard.mjs +2 -2
  34. package/node_modules/typebox/build/system/memory/freeze.d.mts +4 -0
  35. package/node_modules/typebox/build/system/memory/freeze.mjs +6 -0
  36. package/node_modules/typebox/build/system/memory/update.mjs +3 -2
  37. package/node_modules/typebox/package.json +1 -1
  38. package/openclaw.plugin.json +16 -3
  39. package/package.json +5 -5
  40. package/dist/card-command-CmU47gD9.js +0 -230
  41. package/dist/channel.runtime-B83x7k_i.js +0 -4
  42. package/dist/media-control-cards-CksNB8Wk.js +0 -474
  43. package/dist/monitor.runtime-BCCa6qTX.js +0 -2
  44. package/dist/outbound.runtime-D4w0-Gkc.js +0 -3
  45. package/dist/probe-CM03wo6r.js +0 -28
  46. package/dist/probe.runtime-D13ZtXsd.js +0 -2
@@ -0,0 +1,141 @@
1
+ import { a as eventIdFor, i as errorText, o as laneKeyFor, r as LineWebhookPayloadError, s as legacyEventIdFor, t as LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON } from "./webhook-spool-contract-1rY8OJw8.js";
2
+ import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime";
3
+ //#region extensions/line/src/webhook-spool-migration.ts
4
+ /** Pre-drain (#109655) rows stored the event object under `event` instead of the
5
+ * canonical serialized `rawEvent`; anything else is not a migratable row. */
6
+ function parseLegacySpoolPayload(payload) {
7
+ if (!isRecord(payload) || "rawEvent" in payload) return null;
8
+ if (typeof payload.destination !== "string") return null;
9
+ if (!isRecord(payload.event)) return null;
10
+ return {
11
+ destination: payload.destination,
12
+ event: payload.event
13
+ };
14
+ }
15
+ /** A dead-letter carries the pre-fix decoder's signature when the canonical spool
16
+ * rejected a pre-drain row before this migration existed. The identity fence and
17
+ * ordinary delivery failures write different reasons or messages, so neither is
18
+ * ever treated as recoverable. */
19
+ function isLegacyDecodeDeadLetter(row) {
20
+ return row.reason === "invalid-event" && row.message === "LINE webhook spool payload is invalid." && parseLegacySpoolPayload(row.payload) !== null;
21
+ }
22
+ /** Counts pre-drain rows (pending, still claimed, or decoder-dead-lettered)
23
+ * without mutating anything. */
24
+ async function countLegacySpoolRows(queue) {
25
+ const pending = await queue.listPending({ limit: "all" });
26
+ const claims = await queue.listClaims();
27
+ const failed = await queue.listFailed?.({ limit: "all" }) ?? [];
28
+ return pending.filter((record) => parseLegacySpoolPayload(record.payload) !== null).length + claims.filter((claim) => parseLegacySpoolPayload(claim.payload) !== null).length + failed.filter((row) => isLegacyDecodeDeadLetter(row)).length;
29
+ }
30
+ /** One-time upgrade migration: rewrite pre-drain (#109655) rows into the canonical
31
+ * payload and message:/event: keyspace before the spool drains, so the runtime
32
+ * reader keeps a single row contract. Idempotent — enqueue deduplicates by id, so
33
+ * a rerun after a crash only re-completes the leftover legacy rows. */
34
+ async function migrateLineLegacySpoolRows(queue) {
35
+ const result = {
36
+ migrated: 0,
37
+ reconciled: 0,
38
+ deadLettered: 0,
39
+ recovered: 0,
40
+ failures: []
41
+ };
42
+ await queue.recoverStaleClaims({
43
+ staleMs: 0,
44
+ shouldRecover: (claim) => parseLegacySpoolPayload(claim.payload) !== null
45
+ });
46
+ const failed = await queue.listFailed?.({ limit: "all" }) ?? [];
47
+ for (const row of failed) {
48
+ if (!isLegacyDecodeDeadLetter(row)) continue;
49
+ try {
50
+ if ((await queue.resubmit?.(row.id, { resubmittedAt: row.receivedAt }))?.kind === "resubmitted") result.recovered += 1;
51
+ } catch (error) {
52
+ result.failures.push(`row ${row.id}: ${errorText(error)}`);
53
+ }
54
+ }
55
+ const pending = await queue.listPending({
56
+ limit: "all",
57
+ orderBy: "received"
58
+ });
59
+ for (const record of pending) {
60
+ const legacy = parseLegacySpoolPayload(record.payload);
61
+ if (!legacy) continue;
62
+ let eventId;
63
+ try {
64
+ if (record.id !== legacyEventIdFor(legacy.event)) throw new LineWebhookPayloadError("LINE webhook event identity changed after durable admission.");
65
+ eventId = eventIdFor(legacy.event);
66
+ } catch (error) {
67
+ await queue.fail(record.id, {
68
+ reason: LINE_WEBHOOK_SPOOL_INVALID_EVENT_REASON,
69
+ message: errorText(error)
70
+ });
71
+ result.deadLettered += 1;
72
+ continue;
73
+ }
74
+ try {
75
+ const admitted = await queue.enqueue(eventId, {
76
+ version: 1,
77
+ rawEvent: JSON.stringify(legacy.event),
78
+ destination: legacy.destination
79
+ }, {
80
+ receivedAt: record.receivedAt,
81
+ laneKey: laneKeyFor(legacy.event, eventId)
82
+ });
83
+ await queue.complete(record.id);
84
+ if (admitted.kind === "completed" || admitted.kind === "failed") result.reconciled += 1;
85
+ else result.migrated += 1;
86
+ } catch (error) {
87
+ result.failures.push(`row ${record.id}: ${errorText(error)}`);
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ //#endregion
93
+ //#region extensions/line/doctor-contract-api.ts
94
+ const LINE_CHANNEL_ID = "line";
95
+ /** Pre-drain rows can outlive the account config that admitted them, so the sweep
96
+ * enumerates accounts from the host's queue lane instead of the config; a host
97
+ * without the lane fails visibly rather than silently skipping the migration. */
98
+ function lineSpoolQueueAccess(context) {
99
+ const access = context.channelIngressQueues?.find((entry) => entry.channelId === LINE_CHANNEL_ID);
100
+ if (!access) throw new Error("LINE pre-drain spool migration requires the doctor host's channel ingress queue access.");
101
+ return access;
102
+ }
103
+ /** Doctor-owned upgrade migration for pre-drain (#109655) webhook spool rows. */
104
+ const stateMigrations = [{
105
+ id: "line-pre-drain-spool-rows",
106
+ label: "LINE pre-drain webhook spool rows",
107
+ async detectLegacyState(params) {
108
+ const spool = lineSpoolQueueAccess(params.context);
109
+ const preview = [];
110
+ for (const accountId of await spool.listChannelIngressQueueAccountIds()) {
111
+ const count = await countLegacySpoolRows(spool.openChannelIngressQueueForInspection({ accountId }));
112
+ if (count > 0) preview.push(`- LINE pre-drain spool rows (account "${accountId}"): ${count} row(s) -> canonical ingress contract`);
113
+ }
114
+ return preview.length > 0 ? { preview } : null;
115
+ },
116
+ async migrateLegacyState(params) {
117
+ const spool = lineSpoolQueueAccess(params.context);
118
+ const lineConfig = params.config.channels?.line;
119
+ const configuredAccountIds = /* @__PURE__ */ new Set([...Object.keys(lineConfig?.accounts ?? {}), ...lineConfig ? ["default"] : []]);
120
+ const openForMigration = spool.openChannelIngressQueue;
121
+ if (!openForMigration) throw new Error("LINE pre-drain spool migration requires mutable ingress access from the doctor host's exclusive repair section.");
122
+ const changes = [];
123
+ const warnings = [];
124
+ for (const accountId of await spool.listChannelIngressQueueAccountIds()) {
125
+ const result = await migrateLineLegacySpoolRows(openForMigration({ accountId }));
126
+ if (result.migrated > 0 || result.reconciled > 0 || result.deadLettered > 0 || result.recovered > 0) {
127
+ const recovered = result.recovered > 0 ? ` (${result.recovered} recovered from the dead-letter table)` : "";
128
+ const reconciled = result.reconciled > 0 ? `, ${result.reconciled} already settled under the canonical id` : "";
129
+ const dispatchNote = configuredAccountIds.has(accountId) ? "" : ` (account not currently configured, so these stay queued until it is restored)`;
130
+ changes.push(`Migrated LINE pre-drain spool rows (account "${accountId}"): ${result.migrated} queued under the canonical contract, ${result.deadLettered} dead-lettered at the identity fence${reconciled}${recovered}${dispatchNote}`);
131
+ }
132
+ for (const failure of result.failures) warnings.push(`Failed migrating a LINE pre-drain spool row (account "${accountId}", ${failure}); the row stays pending and the migration retries on the next run`);
133
+ }
134
+ return {
135
+ changes,
136
+ warnings
137
+ };
138
+ }
139
+ }];
140
+ //#endregion
141
+ export { stateMigrations };
package/dist/index.js CHANGED
@@ -1,20 +1,7 @@
1
1
  import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
2
2
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
3
3
  //#region extensions/line/index.ts
4
- function createLineCardCommandLoader(api) {
5
- return createLazyRuntimeModule(async () => {
6
- let registered = null;
7
- const { registerLineCardCommand } = await import("./card-command-CmU47gD9.js");
8
- registerLineCardCommand({
9
- ...api,
10
- registerCommand(command) {
11
- registered = command;
12
- }
13
- });
14
- if (!registered) throw new Error("LINE card command registration unavailable");
15
- return registered;
16
- });
17
- }
4
+ const loadLineCardCommand = createLazyRuntimeModule(() => import("./card-command-pwvE48Wb.js"));
18
5
  var line_default = defineBundledChannelEntry({
19
6
  id: "line",
20
7
  name: "LINE",
@@ -29,14 +16,15 @@ var line_default = defineBundledChannelEntry({
29
16
  exportName: "setLineRuntime"
30
17
  },
31
18
  registerFull(api) {
32
- const loadLineCardCommand = createLineCardCommandLoader(api);
33
19
  api.registerCommand({
34
20
  name: "card",
35
- description: "Send a rich card message (LINE).",
21
+ description: "Send a rich card message.",
22
+ channels: ["line"],
36
23
  acceptsArgs: true,
37
24
  requireAuth: false,
38
25
  async handler(ctx) {
39
- return await (await loadLineCardCommand()).handler(ctx);
26
+ const { handleLineCardCommand } = await loadLineCardCommand();
27
+ return await handleLineCardCommand(ctx.args);
40
28
  }
41
29
  });
42
30
  }
@@ -1,4 +1,4 @@
1
- import { d as normalizeLineAction, h as uriAction, i as createReceiptCard, p as postbackAction, u as messageAction } from "./send-receipt-CCPTalf9.js";
1
+ import { C as toFlexMessage, S as fitsLineFlexBubble, _ as normalizeLineAction, g as messageAction, u as createReceiptCard, x as uriAction, y as postbackAction } from "./send-retry-ChpfJ-LM.js";
2
2
  import { markdownToIRWithMeta, stripMarkdown, stripMarkdown as stripMarkdown$1 } from "openclaw/plugin-sdk/text-chunking";
3
3
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
4
  //#region extensions/line/src/template-messages.ts
@@ -145,18 +145,6 @@ function buildTemplateMessageFromPayload(payload) {
145
145
  }
146
146
  }
147
147
  //#endregion
148
- //#region extensions/line/src/flex-templates/message.ts
149
- /**
150
- * Wrap a FlexContainer in a FlexMessage
151
- */
152
- function toFlexMessage(altText, contents) {
153
- return {
154
- type: "flex",
155
- altText,
156
- contents
157
- };
158
- }
159
- //#endregion
160
148
  //#region extensions/line/src/markdown-to-line.ts
161
149
  const LINE_MARKDOWN_OPTIONS = {
162
150
  assistantTranscriptRoleHeaders: true,
@@ -168,7 +156,7 @@ const LINE_MARKDOWN_OPTIONS = {
168
156
  preserveSourceBlockSpacing: true
169
157
  };
170
158
  const TRANSCRIPT_ROLE_PREFIX = "[assistant-authored transcript] ";
171
- const LINE_FLEX_BUBBLE_MAX_BYTES = 3e4;
159
+ const LINE_FLEX_CODE_CARD_MAX_CHARS = 2e3;
172
160
  function parseLineMarkdown(text, tableMode = "block") {
173
161
  return markdownToIRWithMeta(text, {
174
162
  ...LINE_MARKDOWN_OPTIONS,
@@ -212,10 +200,13 @@ function projectPlainText(ir, omitted = [], additionalInsertions = [], onSegment
212
200
  text: TRANSCRIPT_ROLE_PREFIX
213
201
  });
214
202
  const inlineCodeSpans = ir.styles.filter((span) => span.style === "code" && !omitted.some((range) => rangesOverlap(range, span)));
215
- for (const span of inlineCodeSpans) if (stripMarkdown(ir.text.slice(span.start, span.end), { assistantTranscriptRoleHeaders: true }).startsWith(TRANSCRIPT_ROLE_PREFIX)) insertions.push({
216
- position: span.start,
217
- text: TRANSCRIPT_ROLE_PREFIX
218
- });
203
+ for (const span of inlineCodeSpans) {
204
+ const code = ir.text.slice(span.start, span.end);
205
+ if (stripMarkdown(code, { assistantTranscriptRoleHeaders: true }).startsWith(TRANSCRIPT_ROLE_PREFIX)) insertions.push({
206
+ position: span.start,
207
+ text: TRANSCRIPT_ROLE_PREFIX
208
+ });
209
+ }
219
210
  insertions.sort((left, right) => left.position - right.position);
220
211
  const underlineTags = [...ir.text.matchAll(/<\/?u>/gi)].map((match) => ({
221
212
  start: match.index,
@@ -420,7 +411,7 @@ function convertTableToFlexBubble(table) {
420
411
  /** Convert a code block to a LINE Flex Message bubble. */
421
412
  function convertCodeBlockToFlexBubble(block) {
422
413
  const titleText = block.language ? `Code (${block.language})` : "Code";
423
- const displayCode = block.code.length > 2e3 ? truncateUtf16Safe(block.code, 2e3) + "\n..." : block.code;
414
+ const displayCode = block.code.length > LINE_FLEX_CODE_CARD_MAX_CHARS ? `${truncateUtf16Safe(block.code, LINE_FLEX_CODE_CARD_MAX_CHARS)}\n...` : block.code;
424
415
  return {
425
416
  type: "bubble",
426
417
  body: {
@@ -458,7 +449,7 @@ function processLineMessage(text) {
458
449
  const plainTextInsertions = [];
459
450
  for (const table of tables) {
460
451
  const bubble = table.rowCells.length > 10 && (table.headers.length !== 2 || table.rowCells.length > 12 || [table.headerCells, ...table.rowCells].some((cells) => cells.some((cell) => renderTableCell(cell, "-").hasMarkup))) ? void 0 : convertTableToFlexBubble(toMarkdownTable(table));
461
- if (!bubble || Buffer.byteLength(JSON.stringify(bubble), "utf8") > LINE_FLEX_BUBBLE_MAX_BYTES) {
452
+ if (!bubble || !fitsLineFlexBubble(bubble)) {
462
453
  plainTextInsertions.push({
463
454
  position: table.placeholderOffset,
464
455
  text: `\n\n${formatOversizedTableAsBullets(table)}\n\n`
@@ -472,10 +463,18 @@ function processLineMessage(text) {
472
463
  });
473
464
  }
474
465
  for (const span of codeSpans) {
475
- const message = toFlexMessage("Code", convertCodeBlockToFlexBubble(toCodeBlock(ir, span)));
466
+ const block = toCodeBlock(ir, span);
467
+ if (!block.code.trim()) continue;
468
+ if (block.code.length > LINE_FLEX_CODE_CARD_MAX_CHARS) {
469
+ plainTextInsertions.push({
470
+ position: span.start,
471
+ text: `\n\n${block.code}\n\n`
472
+ });
473
+ continue;
474
+ }
476
475
  plainTextInsertions.push({
477
476
  position: span.start,
478
- message
477
+ message: toFlexMessage("Code", convertCodeBlockToFlexBubble(block))
479
478
  });
480
479
  }
481
480
  const segments = [];
@@ -491,4 +490,4 @@ function hasMarkdownToConvert(text) {
491
490
  return tables.length > 0 || ir.styles.length > 0 || ir.links.length > 0 || /<\/?u>/i.test(ir.text) || ir.text !== text.trimEnd();
492
491
  }
493
492
  //#endregion
494
- export { stripMarkdown$1 as a, createButtonTemplate as c, createTemplateCarousel as d, processLineMessage as i, createCarouselColumn as l, convertTableToFlexBubble as n, toFlexMessage as o, hasMarkdownToConvert as r, buildTemplateMessageFromPayload as s, convertCodeBlockToFlexBubble as t, createConfirmTemplate as u };
493
+ export { stripMarkdown$1 as a, createCarouselColumn as c, processLineMessage as i, createConfirmTemplate as l, convertTableToFlexBubble as n, buildTemplateMessageFromPayload as o, hasMarkdownToConvert as r, createButtonTemplate as s, convertCodeBlockToFlexBubble as t, createTemplateCarousel as u };