@openclaw/line 2026.8.1-beta.3 → 2026.8.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/api.js +2 -2
- package/dist/{card-command-CyDSm-OU.js → card-command-BywtX77U.js} +66 -29
- package/dist/{channel-DmoKZXmG.js → channel-DpMgRaxl.js} +52 -349
- package/dist/channel-plugin-api.js +1 -1
- package/dist/channel.runtime-Bty5U_1N.js +4 -0
- package/dist/doctor-contract-api.js +141 -0
- package/dist/index.js +1 -1
- package/dist/{markdown-to-line-rEaras_0.js → markdown-to-line-CG9Jy22s.js} +29 -25
- package/dist/{monitor-BYXS_B6r.js → monitor-Ch6TIatG.js} +261 -166
- package/dist/monitor.runtime-Czc3NxNV.js +2 -0
- package/dist/outbound.runtime-CyleVzFR.js +3 -0
- package/dist/probe.runtime-DTxBWvKz.js +2 -0
- package/dist/rich-messages-t_zRL24p.js +760 -0
- package/dist/runtime-api.js +10 -11
- package/dist/{send-BJCeiiOB.js → send-B-K2B6au.js} +138 -104
- package/dist/{send-receipt-DKZzgHMO.js → send-retry-CUvrarfE.js} +137 -413
- package/dist/setup-api.js +1 -1
- package/dist/{setup-surface-W0Dbgu58.js → setup-surface-DSJ92oJ5.js} +24 -17
- package/dist/webhook-spool-contract-CNe2h1I2.js +58 -0
- package/node_modules/typebox/build/format/date.d.mts +1 -1
- package/node_modules/typebox/build/format/date.mjs +1 -1
- package/node_modules/typebox/build/format/date_time.d.mts +2 -2
- package/node_modules/typebox/build/format/date_time.mjs +3 -3
- package/node_modules/typebox/build/format/email.d.mts +1 -1
- package/node_modules/typebox/build/format/email.mjs +2 -2
- package/node_modules/typebox/build/format/hostname.mjs +2 -10
- package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
- package/node_modules/typebox/build/format/idn_email.mjs +3 -3
- package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
- package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
- package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
- package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
- package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/idn-hostname.mjs +23 -0
- package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
- package/node_modules/typebox/build/format/idna/index.mjs +2 -0
- package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
- package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
- package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
- package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
- package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
- package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
- package/node_modules/typebox/build/format/ipv4.mjs +2 -32
- package/node_modules/typebox/build/format/ipv6.mjs +2 -61
- package/node_modules/typebox/build/format/iri.d.mts +2 -2
- package/node_modules/typebox/build/format/iri.mjs +26 -8
- package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
- package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
- package/node_modules/typebox/build/format/regex.d.mts +1 -2
- package/node_modules/typebox/build/format/regex.mjs +2 -6
- package/node_modules/typebox/build/format/time.d.mts +2 -2
- package/node_modules/typebox/build/format/time.mjs +19 -14
- package/node_modules/typebox/build/format/uri.d.mts +1 -1
- package/node_modules/typebox/build/format/uri.mjs +3 -130
- package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
- package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
- package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
- package/node_modules/typebox/build/format/uri_template.mjs +4 -5
- package/node_modules/typebox/build/format/url.d.mts +3 -3
- package/node_modules/typebox/build/format/url.mjs +4 -5
- package/node_modules/typebox/build/guard/emit.d.mts +3 -1
- package/node_modules/typebox/build/guard/emit.mjs +12 -5
- package/node_modules/typebox/build/guard/guard.d.mts +8 -2
- package/node_modules/typebox/build/guard/guard.mjs +25 -4
- package/node_modules/typebox/build/guard/string.d.mts +2 -2
- package/node_modules/typebox/build/guard/string.mjs +7 -7
- package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
- package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
- package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
- package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
- package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
- package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
- package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
- package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
- package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
- package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
- package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
- package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
- package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
- package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
- package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
- package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
- package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
- package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
- package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
- package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
- package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
- package/node_modules/typebox/build/schema/types/index.mjs +0 -2
- package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
- package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
- package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
- package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
- package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
- package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
- package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
- package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
- package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
- package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
- package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
- package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
- package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
- package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
- package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
- package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
- package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
- package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
- package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
- package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
- package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
- package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
- package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
- package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
- package/node_modules/typebox/build/type/script/parser.mjs +27 -23
- package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
- package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
- package/node_modules/typebox/package.json +27 -27
- package/node_modules/typebox/readme.md +79 -82
- package/openclaw.plugin.json +26 -2
- package/package.json +6 -6
- package/dist/channel.runtime-qrUV6wPE.js +0 -4
- package/dist/config-schema-Be3azjB_.js +0 -47
- package/dist/monitor.runtime-B2z_5ugU.js +0 -2
- package/dist/outbound.runtime-DDTeqaBW.js +0 -3
- package/dist/probe.runtime-D13ZtXsd.js +0 -2
- package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
- package/node_modules/typebox/build/format/_idna.d.mts +0 -2
- package/node_modules/typebox/build/format/_idna.mjs +0 -246
- package/node_modules/typebox/build/format/_puny.d.mts +0 -1
- package/node_modules/typebox/build/format/_puny.mjs +0 -77
- package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
- package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
- package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
- package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
- package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
- package/dist/{probe-CM03wo6r.js → probe-4gc_YQ2L.js} +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { i as resolveLineAccount, r as resolveDefaultLineAccountId, t as listLineAccountIds } from "./accounts-BqEVsDoB.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { t as hasLineCredentials } from "./account-helpers-BSUyZwLm.js";
|
|
3
|
+
import { _ as LineChannelConfigSchema, a as renderLineCard, d as resolveExactLineGroupConfigKey, h as getLineRuntime, n as createLineQuickReply, o as renderLinePresentation, r as lineMessageActions, t as LINE_PRESENTATION_CAPABILITIES, u as buildLineQuickReplyFallbackText } from "./rich-messages-t_zRL24p.js";
|
|
4
|
+
import { i as createLineSendReceipt, n as resolveLineNonDispatchRetryable, y as buildLineMediaMessage } from "./send-retry-CUvrarfE.js";
|
|
5
|
+
import { r as lineSetupContract, t as lineSetupWizard } from "./setup-surface-DSJ92oJ5.js";
|
|
6
6
|
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
|
7
7
|
import { buildDmGroupAccountAllowlistAdapter, createFlatAllowlistOverrideResolver } from "openclaw/plugin-sdk/allowlist-config-edit";
|
|
8
8
|
import { buildChannelOutboundSessionRoute, createChatChannelPlugin } from "openclaw/plugin-sdk/channel-core";
|
|
@@ -10,17 +10,15 @@ import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing
|
|
|
10
10
|
import { buildChannelGroupsScopeTree, createRestrictSendersChannelSecurity, resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
|
|
11
11
|
import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
|
|
12
12
|
import { describeWebhookAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
13
|
-
import { DEFAULT_ACCOUNT_ID
|
|
14
|
-
import { createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
15
|
-
import {
|
|
16
|
-
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
|
|
13
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
|
14
|
+
import { clearAccountFieldsFromConfigSection, createScopedChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
15
|
+
import { normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
17
16
|
import { createAccountStatusSink, defineChannelMessageAdapter, listMessageReceiptPlatformIds } from "openclaw/plugin-sdk/channel-outbound";
|
|
18
|
-
import { createChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
17
|
+
import { createChannelPartialDeliveryError, isChannelPartialDeliveryError } from "openclaw/plugin-sdk/channel-inbound";
|
|
19
18
|
import { createAttachedChannelResultAdapter, createEmptyChannelResult } from "openclaw/plugin-sdk/channel-send-result";
|
|
19
|
+
import { PlatformMessageNotDispatchedError } from "openclaw/plugin-sdk/error-runtime";
|
|
20
20
|
import { resolveOutboundMediaUrls } from "openclaw/plugin-sdk/reply-payload";
|
|
21
21
|
import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
|
|
22
|
-
import { resolveMessagePresentationButtonAction, resolveMessagePresentationOptionAction } from "openclaw/plugin-sdk/interactive-runtime";
|
|
23
|
-
import { Type } from "typebox";
|
|
24
22
|
import { buildTokenChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, createDependentCredentialStatusIssueCollector } from "openclaw/plugin-sdk/status-helpers";
|
|
25
23
|
//#region extensions/line/src/bindings.ts
|
|
26
24
|
function normalizeLineConversationId(raw) {
|
|
@@ -121,8 +119,8 @@ const lineChannelPluginCommon = {
|
|
|
121
119
|
};
|
|
122
120
|
//#endregion
|
|
123
121
|
//#region extensions/line/src/gateway.ts
|
|
124
|
-
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-
|
|
125
|
-
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-
|
|
122
|
+
const loadLineProbeRuntime$1 = createLazyRuntimeModule(() => import("./probe.runtime-DTxBWvKz.js"));
|
|
123
|
+
const loadLineMonitorRuntime = createLazyRuntimeModule(() => import("./monitor.runtime-Czc3NxNV.js"));
|
|
126
124
|
const lineGatewayAdapter = {
|
|
127
125
|
startAccount: async (ctx) => {
|
|
128
126
|
const account = ctx.account;
|
|
@@ -158,22 +156,9 @@ const lineGatewayAdapter = {
|
|
|
158
156
|
},
|
|
159
157
|
logoutAccount: async ({ accountId, cfg }) => {
|
|
160
158
|
const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim() ?? "";
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
let changed = false;
|
|
165
|
-
if (accountId === DEFAULT_ACCOUNT_ID$1) {
|
|
166
|
-
if (nextLine.channelAccessToken || nextLine.channelSecret || nextLine.tokenFile || nextLine.secretFile) {
|
|
167
|
-
delete nextLine.channelAccessToken;
|
|
168
|
-
delete nextLine.channelSecret;
|
|
169
|
-
delete nextLine.tokenFile;
|
|
170
|
-
delete nextLine.secretFile;
|
|
171
|
-
cleared = true;
|
|
172
|
-
changed = true;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const accountCleanup = clearAccountEntryFields({
|
|
176
|
-
accounts: nextLine.accounts,
|
|
159
|
+
const { nextConfig, changed, cleared } = clearAccountFieldsFromConfigSection({
|
|
160
|
+
cfg,
|
|
161
|
+
sectionKey: "line",
|
|
177
162
|
accountId,
|
|
178
163
|
fields: [
|
|
179
164
|
"channelAccessToken",
|
|
@@ -183,30 +168,12 @@ const lineGatewayAdapter = {
|
|
|
183
168
|
],
|
|
184
169
|
markClearedOnFieldPresence: true
|
|
185
170
|
});
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
else delete nextLine.accounts;
|
|
191
|
-
}
|
|
192
|
-
if (changed) {
|
|
193
|
-
if (Object.keys(nextLine).length > 0) nextCfg.channels = {
|
|
194
|
-
...nextCfg.channels,
|
|
195
|
-
line: nextLine
|
|
196
|
-
};
|
|
197
|
-
else {
|
|
198
|
-
const nextChannels = { ...nextCfg.channels };
|
|
199
|
-
delete nextChannels.line;
|
|
200
|
-
if (Object.keys(nextChannels).length > 0) nextCfg.channels = nextChannels;
|
|
201
|
-
else delete nextCfg.channels;
|
|
202
|
-
}
|
|
203
|
-
await getLineRuntime().config.replaceConfigFile({
|
|
204
|
-
nextConfig: nextCfg,
|
|
205
|
-
afterWrite: { mode: "auto" }
|
|
206
|
-
});
|
|
207
|
-
}
|
|
171
|
+
if (changed) await getLineRuntime().config.replaceConfigFile({
|
|
172
|
+
nextConfig,
|
|
173
|
+
afterWrite: { mode: "auto" }
|
|
174
|
+
});
|
|
208
175
|
const loggedOut = resolveLineAccount({
|
|
209
|
-
cfg:
|
|
176
|
+
cfg: nextConfig,
|
|
210
177
|
accountId
|
|
211
178
|
}).tokenSource === "none";
|
|
212
179
|
return {
|
|
@@ -243,234 +210,8 @@ function inferLineTargetChatType(target) {
|
|
|
243
210
|
return /^[CR][a-f0-9]{32}$/i.test(normalized) ? "group" : void 0;
|
|
244
211
|
}
|
|
245
212
|
//#endregion
|
|
246
|
-
//#region extensions/line/src/rich-messages.ts
|
|
247
|
-
const nonempty = () => Type.String({ minLength: 1 });
|
|
248
|
-
const closed = (properties) => Type.Object(properties, { additionalProperties: false });
|
|
249
|
-
const lineCardSchema = Type.Union([
|
|
250
|
-
closed({
|
|
251
|
-
type: Type.Literal("media_player"),
|
|
252
|
-
title: nonempty(),
|
|
253
|
-
artist: Type.Optional(nonempty()),
|
|
254
|
-
source: Type.Optional(nonempty()),
|
|
255
|
-
imageUrl: Type.Optional(Type.String({ pattern: "^https://" })),
|
|
256
|
-
status: Type.Optional(Type.Union([Type.Literal("playing"), Type.Literal("paused")]))
|
|
257
|
-
}),
|
|
258
|
-
closed({
|
|
259
|
-
type: Type.Literal("event"),
|
|
260
|
-
title: nonempty(),
|
|
261
|
-
date: nonempty(),
|
|
262
|
-
time: Type.Optional(nonempty()),
|
|
263
|
-
location: Type.Optional(nonempty()),
|
|
264
|
-
description: Type.Optional(nonempty())
|
|
265
|
-
}),
|
|
266
|
-
closed({
|
|
267
|
-
type: Type.Literal("agenda"),
|
|
268
|
-
title: nonempty(),
|
|
269
|
-
events: Type.Array(closed({
|
|
270
|
-
title: nonempty(),
|
|
271
|
-
time: Type.Optional(nonempty()),
|
|
272
|
-
location: Type.Optional(nonempty())
|
|
273
|
-
}), {
|
|
274
|
-
minItems: 1,
|
|
275
|
-
maxItems: 6
|
|
276
|
-
})
|
|
277
|
-
}),
|
|
278
|
-
closed({
|
|
279
|
-
type: Type.Literal("device"),
|
|
280
|
-
name: nonempty(),
|
|
281
|
-
deviceType: Type.Optional(nonempty()),
|
|
282
|
-
status: Type.Optional(nonempty()),
|
|
283
|
-
controls: Type.Optional(Type.Array(closed({
|
|
284
|
-
label: nonempty(),
|
|
285
|
-
action: nonempty()
|
|
286
|
-
}), { maxItems: 6 }))
|
|
287
|
-
}),
|
|
288
|
-
closed({
|
|
289
|
-
type: Type.Literal("appletv_remote"),
|
|
290
|
-
name: Type.Optional(nonempty()),
|
|
291
|
-
status: Type.Optional(nonempty())
|
|
292
|
-
})
|
|
293
|
-
]);
|
|
294
|
-
const lineChannelDataSchema = Type.Optional(closed({ line: closed({
|
|
295
|
-
location: Type.Optional(closed({
|
|
296
|
-
title: nonempty(),
|
|
297
|
-
address: nonempty(),
|
|
298
|
-
latitude: Type.Number({
|
|
299
|
-
minimum: -90,
|
|
300
|
-
maximum: 90
|
|
301
|
-
}),
|
|
302
|
-
longitude: Type.Number({
|
|
303
|
-
minimum: -180,
|
|
304
|
-
maximum: 180
|
|
305
|
-
})
|
|
306
|
-
})),
|
|
307
|
-
card: Type.Optional(lineCardSchema),
|
|
308
|
-
mediaKind: Type.Optional(Type.Union([
|
|
309
|
-
Type.Literal("image"),
|
|
310
|
-
Type.Literal("video"),
|
|
311
|
-
Type.Literal("audio")
|
|
312
|
-
])),
|
|
313
|
-
previewImageUrl: Type.Optional(Type.String({ pattern: "^https://" })),
|
|
314
|
-
durationMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
315
|
-
trackingId: Type.Optional(nonempty())
|
|
316
|
-
}) }));
|
|
317
|
-
const lineMessageActions = {
|
|
318
|
-
describeMessageTool: ({ cfg, accountId }) => {
|
|
319
|
-
const account = resolveLineAccount({
|
|
320
|
-
cfg,
|
|
321
|
-
accountId: accountId ?? void 0
|
|
322
|
-
});
|
|
323
|
-
return account.enabled && hasLineCredentials(account) ? {
|
|
324
|
-
actions: ["send"],
|
|
325
|
-
capabilities: ["presentation"],
|
|
326
|
-
schema: {
|
|
327
|
-
actions: ["send"],
|
|
328
|
-
properties: { channelData: lineChannelDataSchema }
|
|
329
|
-
}
|
|
330
|
-
} : {
|
|
331
|
-
actions: [],
|
|
332
|
-
capabilities: [],
|
|
333
|
-
schema: null
|
|
334
|
-
};
|
|
335
|
-
},
|
|
336
|
-
prepareSendPayload: ({ payload }) => payload
|
|
337
|
-
};
|
|
338
|
-
const LINE_PRESENTATION_CAPABILITIES = {
|
|
339
|
-
supported: true,
|
|
340
|
-
buttons: true,
|
|
341
|
-
selects: true,
|
|
342
|
-
context: true,
|
|
343
|
-
limits: {
|
|
344
|
-
actions: {
|
|
345
|
-
maxActions: 4,
|
|
346
|
-
maxActionsPerRow: 1,
|
|
347
|
-
maxRows: 4,
|
|
348
|
-
maxLabelLength: 40
|
|
349
|
-
},
|
|
350
|
-
selects: {
|
|
351
|
-
maxOptions: 13,
|
|
352
|
-
maxLabelLength: 20,
|
|
353
|
-
maxValueBytes: 300
|
|
354
|
-
},
|
|
355
|
-
text: { markdownDialect: "plain" }
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
function toLineAction(button) {
|
|
359
|
-
const normalized = resolveMessagePresentationButtonAction(button);
|
|
360
|
-
if (normalized?.type === "command") return messageAction(button.label, normalized.command);
|
|
361
|
-
if (normalized?.type === "callback") return postbackAction(button.label, normalized.value, button.label);
|
|
362
|
-
if (normalized?.type === "url") return uriAction(button.label, normalized.url);
|
|
363
|
-
if (normalized?.type === "web-app" && normalized.url) return uriAction(button.label, normalized.url);
|
|
364
|
-
}
|
|
365
|
-
function renderLinePresentation(payload, presentation) {
|
|
366
|
-
const buttons = presentation.blocks.flatMap((block) => block.type === "buttons" ? block.buttons : []);
|
|
367
|
-
const buttonActions = buttons.map(toLineAction);
|
|
368
|
-
const options = presentation.blocks.flatMap((block) => block.type === "select" ? block.options : []);
|
|
369
|
-
const quickReplyItems = options.flatMap((option) => {
|
|
370
|
-
const action = resolveMessagePresentationOptionAction(option);
|
|
371
|
-
return action?.type === "command" || action?.type === "callback" ? [{
|
|
372
|
-
label: option.label,
|
|
373
|
-
action
|
|
374
|
-
}] : [];
|
|
375
|
-
});
|
|
376
|
-
if (buttons.length > 0 && buttonActions.some((action) => !action) || quickReplyItems.length !== options.length || buttons.length === 0 && options.length === 0) return null;
|
|
377
|
-
const lineData = isRecord(payload.channelData?.line) ? payload.channelData.line : {};
|
|
378
|
-
const text = presentation.blocks.flatMap((block) => block.type === "text" || block.type === "context" ? [block.text] : []).join("\n");
|
|
379
|
-
const title = presentation.title || "Choose an option";
|
|
380
|
-
const flexMessage = buttonActions.length > 0 ? {
|
|
381
|
-
altText: title,
|
|
382
|
-
contents: createActionCard(title, text || "Choose an option.", buttons.map((button, index) => ({
|
|
383
|
-
label: button.label,
|
|
384
|
-
action: buttonActions[index]
|
|
385
|
-
})))
|
|
386
|
-
} : void 0;
|
|
387
|
-
return {
|
|
388
|
-
...payload,
|
|
389
|
-
channelData: {
|
|
390
|
-
...payload.channelData,
|
|
391
|
-
line: {
|
|
392
|
-
...lineData,
|
|
393
|
-
...flexMessage ? { flexMessage } : {},
|
|
394
|
-
quickReplyItems
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
const toSlug = (value) => normalizeLowercaseStringOrEmpty(value).replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "device";
|
|
400
|
-
const lineActionData = (action, device) => `line.action=${encodeURIComponent(action)}&line.device=${encodeURIComponent(device)}`;
|
|
401
|
-
function renderLineCard(card) {
|
|
402
|
-
if (card.type === "media_player") {
|
|
403
|
-
const device = toSlug(card.source || card.title);
|
|
404
|
-
return {
|
|
405
|
-
altText: `🎵 ${card.title}${card.artist ? ` - ${card.artist}` : ""}`,
|
|
406
|
-
contents: createMediaPlayerCard({
|
|
407
|
-
title: card.title,
|
|
408
|
-
subtitle: card.artist,
|
|
409
|
-
source: card.source,
|
|
410
|
-
imageUrl: card.imageUrl,
|
|
411
|
-
isPlaying: card.status ? card.status === "playing" : void 0,
|
|
412
|
-
controls: Object.fromEntries([
|
|
413
|
-
"previous",
|
|
414
|
-
"play",
|
|
415
|
-
"pause",
|
|
416
|
-
"next"
|
|
417
|
-
].map((action) => [action, { data: lineActionData(action, device) }]))
|
|
418
|
-
})
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
if (card.type === "event") return {
|
|
422
|
-
altText: `📅 ${card.title} - ${card.date}${card.time ? ` ${card.time}` : ""}`,
|
|
423
|
-
contents: createEventCard(card)
|
|
424
|
-
};
|
|
425
|
-
if (card.type === "agenda") return {
|
|
426
|
-
altText: `📋 ${card.title} (${card.events.length} events)`,
|
|
427
|
-
contents: createAgendaCard(card)
|
|
428
|
-
};
|
|
429
|
-
const device = toSlug(card.type === "device" ? card.name : card.name || "apple_tv");
|
|
430
|
-
if (card.type === "device") return {
|
|
431
|
-
altText: `📱 ${card.name}${card.status ? `: ${card.status}` : ""}`,
|
|
432
|
-
contents: createDeviceControlCard({
|
|
433
|
-
deviceName: card.name,
|
|
434
|
-
deviceType: card.deviceType,
|
|
435
|
-
status: card.status,
|
|
436
|
-
controls: (card.controls ?? []).map((control) => ({
|
|
437
|
-
label: control.label,
|
|
438
|
-
data: lineActionData(control.action, device)
|
|
439
|
-
}))
|
|
440
|
-
})
|
|
441
|
-
};
|
|
442
|
-
const actionData = {
|
|
443
|
-
up: lineActionData("up", device),
|
|
444
|
-
down: lineActionData("down", device),
|
|
445
|
-
left: lineActionData("left", device),
|
|
446
|
-
right: lineActionData("right", device),
|
|
447
|
-
select: lineActionData("select", device),
|
|
448
|
-
menu: lineActionData("menu", device),
|
|
449
|
-
home: lineActionData("home", device),
|
|
450
|
-
play: lineActionData("play", device),
|
|
451
|
-
pause: lineActionData("pause", device),
|
|
452
|
-
volumeUp: lineActionData("volume_up", device),
|
|
453
|
-
volumeDown: lineActionData("volume_down", device),
|
|
454
|
-
mute: lineActionData("mute", device)
|
|
455
|
-
};
|
|
456
|
-
return {
|
|
457
|
-
altText: `📺 ${card.name || "Apple TV"} Remote`,
|
|
458
|
-
contents: createAppleTvRemoteCard({
|
|
459
|
-
deviceName: card.name || "Apple TV",
|
|
460
|
-
status: card.status,
|
|
461
|
-
actionData
|
|
462
|
-
})
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
function createLineQuickReply(items) {
|
|
466
|
-
return { items: items.slice(0, 13).map((item) => ({
|
|
467
|
-
type: "action",
|
|
468
|
-
action: item.action.type === "command" ? messageAction(item.label, item.action.command) : postbackAction(item.label, item.action.value, item.label)
|
|
469
|
-
})) };
|
|
470
|
-
}
|
|
471
|
-
//#endregion
|
|
472
213
|
//#region extensions/line/src/outbound.ts
|
|
473
|
-
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-
|
|
214
|
+
const loadLineOutboundRuntime = createLazyRuntimeModule(() => import("./outbound.runtime-CyleVzFR.js"));
|
|
474
215
|
const lineOutboundAdapter = {
|
|
475
216
|
deliveryMode: "direct",
|
|
476
217
|
chunker: (text, limit) => getLineRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
@@ -496,9 +237,23 @@ const lineOutboundAdapter = {
|
|
|
496
237
|
const sendLocation = lineRuntime?.pushLocationMessage ?? outboundRuntime.pushLocationMessage;
|
|
497
238
|
const sendQuickReplies = lineRuntime?.pushTextMessageWithQuickReplies ?? outboundRuntime.pushTextMessageWithQuickReplies;
|
|
498
239
|
const buildTemplate = lineRuntime?.buildTemplateMessageFromPayload ?? outboundRuntime.buildTemplateMessageFromPayload;
|
|
240
|
+
const sendOptions = {
|
|
241
|
+
verbose: false,
|
|
242
|
+
cfg,
|
|
243
|
+
accountId: accountId ?? void 0
|
|
244
|
+
};
|
|
499
245
|
let lastResult = null;
|
|
500
246
|
const recordResult = async (resultPromise) => {
|
|
501
|
-
|
|
247
|
+
let result;
|
|
248
|
+
try {
|
|
249
|
+
result = await resultPromise;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const retryable = isChannelPartialDeliveryError(error) ? void 0 : resolveLineNonDispatchRetryable(error);
|
|
252
|
+
throw lastResult === null && retryable !== void 0 ? new PlatformMessageNotDispatchedError(error instanceof Error ? error.message : "LINE rejected the message", {
|
|
253
|
+
cause: error,
|
|
254
|
+
retryable
|
|
255
|
+
}) : error;
|
|
256
|
+
}
|
|
502
257
|
lastResult = result;
|
|
503
258
|
try {
|
|
504
259
|
await onDeliveryResult?.(createEmptyChannelResult("line", { ...result }));
|
|
@@ -520,11 +275,7 @@ const lineOutboundAdapter = {
|
|
|
520
275
|
if (messages.length === 0) return;
|
|
521
276
|
for (let i = 0; i < messages.length; i += 5) {
|
|
522
277
|
const batch = messages.slice(i, i + 5);
|
|
523
|
-
await recordResult(sendBatch(to, batch,
|
|
524
|
-
verbose: false,
|
|
525
|
-
cfg,
|
|
526
|
-
accountId: accountId ?? void 0
|
|
527
|
-
}));
|
|
278
|
+
await recordResult(sendBatch(to, batch, sendOptions));
|
|
528
279
|
}
|
|
529
280
|
};
|
|
530
281
|
const sendTextWithQuickReply = async (text) => {
|
|
@@ -536,11 +287,7 @@ const lineOutboundAdapter = {
|
|
|
536
287
|
}]);
|
|
537
288
|
return;
|
|
538
289
|
}
|
|
539
|
-
await recordResult(sendQuickReplies(to, text, quickReplies,
|
|
540
|
-
verbose: false,
|
|
541
|
-
cfg,
|
|
542
|
-
accountId: accountId ?? void 0
|
|
543
|
-
}));
|
|
290
|
+
await recordResult(sendQuickReplies(to, text, quickReplies, sendOptions));
|
|
544
291
|
};
|
|
545
292
|
const processed = payload.text ? outboundRuntime.processLineMessage(payload.text) : {
|
|
546
293
|
text: "",
|
|
@@ -553,9 +300,8 @@ const lineOutboundAdapter = {
|
|
|
553
300
|
})));
|
|
554
301
|
const chunks = orderedMessages ? orderedMessages.flatMap((message) => message.type === "text" ? [message.text] : []) : processed.text ? runtime.channel.text.chunkMarkdownText(processed.text, chunkLimit) : [];
|
|
555
302
|
const mediaUrls = resolveOutboundMediaUrls(payload);
|
|
556
|
-
const useLineSpecificMedia = hasLineSpecificMediaOptions(lineData);
|
|
557
303
|
const mediaOptions = {
|
|
558
|
-
mediaKind:
|
|
304
|
+
mediaKind: lineData.mediaKind,
|
|
559
305
|
previewImageUrl: lineData.previewImageUrl,
|
|
560
306
|
durationMs: lineData.durationMs,
|
|
561
307
|
trackingId: lineData.trackingId
|
|
@@ -565,82 +311,39 @@ const lineOutboundAdapter = {
|
|
|
565
311
|
for (const url of mediaUrls) {
|
|
566
312
|
const trimmed = url?.trim();
|
|
567
313
|
if (!trimmed) continue;
|
|
568
|
-
if (!useLineSpecificMedia) {
|
|
569
|
-
await recordResult((lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
|
|
570
|
-
verbose: false,
|
|
571
|
-
mediaUrl: trimmed,
|
|
572
|
-
cfg,
|
|
573
|
-
accountId: accountId ?? void 0
|
|
574
|
-
}));
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
const resolved = await resolveLineOutboundMedia(trimmed, mediaOptions);
|
|
578
314
|
await recordResult((lineRuntime?.sendMessageLine ?? outboundRuntime.sendMessageLine)(to, "", {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
previewImageUrl: resolved.previewImageUrl,
|
|
583
|
-
durationMs: resolved.durationMs,
|
|
584
|
-
trackingId: resolved.trackingId,
|
|
585
|
-
cfg,
|
|
586
|
-
accountId: accountId ?? void 0
|
|
315
|
+
...sendOptions,
|
|
316
|
+
...mediaOptions,
|
|
317
|
+
mediaUrl: trimmed
|
|
587
318
|
}));
|
|
588
319
|
}
|
|
589
320
|
};
|
|
590
321
|
if (!shouldSendQuickRepliesInline) {
|
|
591
322
|
if (lineData.flexMessage) {
|
|
592
323
|
const flexContents = lineData.flexMessage.contents;
|
|
593
|
-
await recordResult(sendFlex(to, lineData.flexMessage.altText, flexContents,
|
|
594
|
-
verbose: false,
|
|
595
|
-
cfg,
|
|
596
|
-
accountId: accountId ?? void 0
|
|
597
|
-
}));
|
|
324
|
+
await recordResult(sendFlex(to, lineData.flexMessage.altText, flexContents, sendOptions));
|
|
598
325
|
}
|
|
599
326
|
if (lineData.templateMessage) {
|
|
600
327
|
const template = buildTemplate(lineData.templateMessage);
|
|
601
|
-
if (template) await recordResult(sendTemplate(to, template,
|
|
602
|
-
verbose: false,
|
|
603
|
-
cfg,
|
|
604
|
-
accountId: accountId ?? void 0
|
|
605
|
-
}));
|
|
328
|
+
if (template) await recordResult(sendTemplate(to, template, sendOptions));
|
|
606
329
|
}
|
|
607
|
-
if (location) await recordResult(sendLocation(to, location,
|
|
608
|
-
|
|
609
|
-
cfg,
|
|
610
|
-
accountId: accountId ?? void 0
|
|
611
|
-
}));
|
|
612
|
-
if (!orderedMessages) for (const flexMsg of processed.flexMessages) await recordResult(sendFlex(to, flexMsg.altText, flexMsg.contents, {
|
|
613
|
-
verbose: false,
|
|
614
|
-
cfg,
|
|
615
|
-
accountId: accountId ?? void 0
|
|
616
|
-
}));
|
|
330
|
+
if (location) await recordResult(sendLocation(to, location, sendOptions));
|
|
331
|
+
if (!orderedMessages) for (const flexMsg of processed.flexMessages) await recordResult(sendFlex(to, flexMsg.altText, flexMsg.contents, sendOptions));
|
|
617
332
|
}
|
|
618
333
|
const sendMediaAfterText = !(hasQuickReplies && chunks.length > 0);
|
|
619
334
|
if (mediaUrls.length > 0 && !shouldSendQuickRepliesInline && !sendMediaAfterText) await sendMediaMessages();
|
|
620
|
-
if (orderedMessages) for (const [index, message] of orderedMessages.entries()) {
|
|
335
|
+
if (orderedMessages && !shouldSendQuickRepliesInline) for (const [index, message] of orderedMessages.entries()) {
|
|
621
336
|
const isLast = index === orderedMessages.length - 1;
|
|
622
337
|
if (message.type === "flex") if (isLast && quickReply) await sendMessageBatch([{
|
|
623
338
|
...message,
|
|
624
339
|
quickReply
|
|
625
340
|
}]);
|
|
626
|
-
else await recordResult(sendFlex(to, message.altText, message.contents,
|
|
627
|
-
verbose: false,
|
|
628
|
-
cfg,
|
|
629
|
-
accountId: accountId ?? void 0
|
|
630
|
-
}));
|
|
341
|
+
else await recordResult(sendFlex(to, message.altText, message.contents, sendOptions));
|
|
631
342
|
else if (isLast && hasQuickReplies) await sendTextWithQuickReply(message.text);
|
|
632
|
-
else await recordResult(sendText(to, message.text,
|
|
633
|
-
verbose: false,
|
|
634
|
-
cfg,
|
|
635
|
-
accountId: accountId ?? void 0
|
|
636
|
-
}));
|
|
343
|
+
else await recordResult(sendText(to, message.text, sendOptions));
|
|
637
344
|
}
|
|
638
345
|
else if (chunks.length > 0) for (const [i, chunk] of chunks.entries()) if (i === chunks.length - 1 && hasQuickReplies) await sendTextWithQuickReply(chunk);
|
|
639
|
-
else await recordResult(sendText(to, chunk,
|
|
640
|
-
verbose: false,
|
|
641
|
-
cfg,
|
|
642
|
-
accountId: accountId ?? void 0
|
|
643
|
-
}));
|
|
346
|
+
else await recordResult(sendText(to, chunk, sendOptions));
|
|
644
347
|
else if (shouldSendQuickRepliesInline) {
|
|
645
348
|
const quickReplyMessages = [];
|
|
646
349
|
if (lineData.flexMessage) quickReplyMessages.push(outboundRuntime.createFlexMessage(lineData.flexMessage.altText, lineData.flexMessage.contents));
|
|
@@ -740,7 +443,7 @@ const lineMessageAdapter = defineChannelMessageAdapter({
|
|
|
740
443
|
});
|
|
741
444
|
//#endregion
|
|
742
445
|
//#region extensions/line/src/status.ts
|
|
743
|
-
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-
|
|
446
|
+
const loadLineProbeRuntime = createLazyRuntimeModule(() => import("./probe.runtime-DTxBWvKz.js"));
|
|
744
447
|
const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({
|
|
745
448
|
channel: "line",
|
|
746
449
|
dependencySourceKey: "tokenSource",
|
|
@@ -748,7 +451,7 @@ const collectLineStatusIssues = createDependentCredentialStatusIssueCollector({
|
|
|
748
451
|
missingDependentMessage: "LINE channel secret not configured"
|
|
749
452
|
});
|
|
750
453
|
const lineStatusAdapter = createComputedAccountStatusAdapter({
|
|
751
|
-
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID
|
|
454
|
+
defaultRuntime: createDefaultChannelRuntimeState(DEFAULT_ACCOUNT_ID),
|
|
752
455
|
collectStatusIssues: collectLineStatusIssues,
|
|
753
456
|
buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot),
|
|
754
457
|
probeAccount: async ({ account, timeoutMs }) => await (await loadLineProbeRuntime()).probeLineBot(account.channelAccessToken, timeoutMs),
|
|
@@ -768,7 +471,7 @@ const lineStatusAdapter = createComputedAccountStatusAdapter({
|
|
|
768
471
|
});
|
|
769
472
|
//#endregion
|
|
770
473
|
//#region extensions/line/src/channel.ts
|
|
771
|
-
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-
|
|
474
|
+
const loadLineChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime-Bty5U_1N.js"));
|
|
772
475
|
const lineSecurityAdapter = createRestrictSendersChannelSecurity({
|
|
773
476
|
channelKey: "line",
|
|
774
477
|
resolveDmPolicy: (account) => account.config.dmPolicy,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as linePlugin } from "./channel-
|
|
1
|
+
import { t as linePlugin } from "./channel-DpMgRaxl.js";
|
|
2
2
|
export { linePlugin };
|
|
@@ -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-CNe2h1I2.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 };
|