@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.
- package/dist/account-helpers-BSUyZwLm.js +12 -0
- package/dist/{accounts-BqEVsDoB.js → accounts-BBEFMYbY.js} +2 -1
- package/dist/api.js +2 -2
- package/dist/card-command-pwvE48Wb.js +252 -0
- package/dist/{channel-CsQLcrsA.js → channel-CF2AeULJ.js} +111 -380
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-BF4Wk2dM.js +4 -0
- package/dist/contract-api.js +1 -1
- package/dist/doctor-contract-api.js +141 -0
- package/dist/index.js +5 -17
- package/dist/{markdown-to-line-BmZ4hhfC.js → markdown-to-line-B-sj-7II.js} +22 -23
- package/dist/{monitor-Bd9XDV1i.js → monitor-D0fHFXCi.js} +323 -234
- package/dist/monitor.runtime-Mgecav_D.js +2 -0
- package/dist/outbound.runtime-CGqyJ-Ug.js +3 -0
- package/dist/probe.runtime-CbtMgU4s.js +2 -0
- package/dist/rich-messages-DY1AGKAJ.js +742 -0
- package/dist/runtime-api.js +14 -15
- package/dist/{send-Dce0m539.js → send-CQQxpxPp.js} +142 -112
- package/dist/{send-receipt-CCPTalf9.js → send-retry-ChpfJ-LM.js} +326 -86
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-W0Dbgu58.js → setup-surface-CDlgkTKi.js} +47 -39
- package/dist/webhook-spool-contract-1rY8OJw8.js +59 -0
- package/node_modules/typebox/build/format/email.mjs +1 -1
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +19 -1
- package/node_modules/typebox/package.json +1 -1
- package/openclaw.plugin.json +16 -2
- package/package.json +5 -5
- package/dist/card-command-CmU47gD9.js +0 -230
- package/dist/channel.runtime-B83x7k_i.js +0 -4
- package/dist/media-control-cards-CksNB8Wk.js +0 -474
- package/dist/monitor.runtime-BCCa6qTX.js +0 -2
- package/dist/outbound.runtime-D4w0-Gkc.js +0 -3
- package/dist/probe-CM03wo6r.js +0 -28
- 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
|
-
|
|
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
|
|
21
|
+
description: "Send a rich card message.",
|
|
22
|
+
channels: ["line"],
|
|
36
23
|
acceptsArgs: true,
|
|
37
24
|
requireAuth: false,
|
|
38
25
|
async handler(ctx) {
|
|
39
|
-
|
|
26
|
+
const { handleLineCardCommand } = await loadLineCardCommand();
|
|
27
|
+
return await handleLineCardCommand(ctx.args);
|
|
40
28
|
}
|
|
41
29
|
});
|
|
42
30
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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)
|
|
216
|
-
|
|
217
|
-
|
|
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 >
|
|
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 ||
|
|
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
|
|
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,
|
|
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 };
|