@openclaw/line 2026.8.1-beta.3 → 2026.8.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 (156) 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-DS6A5V6o.js +252 -0
  5. package/dist/{channel-DmoKZXmG.js → channel-CLCy4jAa.js} +97 -357
  6. package/dist/channel-plugin-api.js +1 -1
  7. package/dist/channel.runtime-Ih7sq1GN.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-rEaras_0.js → markdown-to-line--jytmFan.js} +36 -29
  12. package/dist/{monitor-BYXS_B6r.js → monitor-CSpwQCBI.js} +292 -194
  13. package/dist/monitor.runtime-CV8EjkIT.js +2 -0
  14. package/dist/outbound.runtime-DLqcz_BQ.js +3 -0
  15. package/dist/probe.runtime-DTxBWvKz.js +2 -0
  16. package/dist/rich-messages-CIP7xn5E.js +761 -0
  17. package/dist/runtime-api.js +12 -13
  18. package/dist/{send-receipt-DKZzgHMO.js → send-retry-CUvrarfE.js} +137 -413
  19. package/dist/{send-BJCeiiOB.js → send-wVUV00WS.js} +150 -109
  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/date.d.mts +1 -1
  24. package/node_modules/typebox/build/format/date.mjs +1 -1
  25. package/node_modules/typebox/build/format/date_time.d.mts +2 -2
  26. package/node_modules/typebox/build/format/date_time.mjs +3 -3
  27. package/node_modules/typebox/build/format/email.d.mts +1 -1
  28. package/node_modules/typebox/build/format/email.mjs +2 -2
  29. package/node_modules/typebox/build/format/hostname.mjs +2 -10
  30. package/node_modules/typebox/build/format/idn_email.d.mts +1 -1
  31. package/node_modules/typebox/build/format/idn_email.mjs +3 -3
  32. package/node_modules/typebox/build/format/idn_hostname.d.mts +1 -0
  33. package/node_modules/typebox/build/format/idn_hostname.mjs +3 -11
  34. package/node_modules/typebox/build/format/idna/format/bidi.d.mts +3 -0
  35. package/node_modules/typebox/build/format/idna/format/bidi.mjs +98 -0
  36. package/node_modules/typebox/build/format/idna/format/puny.d.mts +3 -0
  37. package/node_modules/typebox/build/format/idna/format/puny.mjs +144 -0
  38. package/node_modules/typebox/build/format/idna/hostname.d.mts +1 -0
  39. package/node_modules/typebox/build/format/idna/hostname.mjs +16 -0
  40. package/node_modules/typebox/build/format/idna/idn-hostname.d.mts +1 -0
  41. package/node_modules/typebox/build/format/idna/idn-hostname.mjs +41 -0
  42. package/node_modules/typebox/build/format/idna/index.d.mts +2 -0
  43. package/node_modules/typebox/build/format/idna/index.mjs +2 -0
  44. package/node_modules/typebox/build/format/idna/label/ascii.d.mts +1 -0
  45. package/node_modules/typebox/build/format/idna/label/ascii.mjs +9 -0
  46. package/node_modules/typebox/build/format/idna/label/puny.d.mts +1 -0
  47. package/node_modules/typebox/build/format/idna/label/puny.mjs +28 -0
  48. package/node_modules/typebox/build/format/idna/label/unicode.d.mts +1 -0
  49. package/node_modules/typebox/build/format/idna/label/unicode.mjs +106 -0
  50. package/node_modules/typebox/build/format/idna/pattern/pattern.d.mts +30 -0
  51. package/node_modules/typebox/build/format/idna/pattern/pattern.mjs +70 -0
  52. package/node_modules/typebox/build/format/ipv4.d.mts +0 -1
  53. package/node_modules/typebox/build/format/ipv4.mjs +2 -32
  54. package/node_modules/typebox/build/format/ipv6.mjs +2 -61
  55. package/node_modules/typebox/build/format/iri.d.mts +2 -2
  56. package/node_modules/typebox/build/format/iri.mjs +26 -8
  57. package/node_modules/typebox/build/format/iri_reference.d.mts +2 -2
  58. package/node_modules/typebox/build/format/iri_reference.mjs +9 -55
  59. package/node_modules/typebox/build/format/regex.d.mts +1 -2
  60. package/node_modules/typebox/build/format/regex.mjs +2 -6
  61. package/node_modules/typebox/build/format/time.d.mts +2 -2
  62. package/node_modules/typebox/build/format/time.mjs +19 -14
  63. package/node_modules/typebox/build/format/uri.d.mts +1 -1
  64. package/node_modules/typebox/build/format/uri.mjs +3 -130
  65. package/node_modules/typebox/build/format/uri_reference.d.mts +1 -1
  66. package/node_modules/typebox/build/format/uri_reference.mjs +2 -3
  67. package/node_modules/typebox/build/format/uri_template.d.mts +2 -3
  68. package/node_modules/typebox/build/format/uri_template.mjs +4 -5
  69. package/node_modules/typebox/build/format/url.d.mts +3 -3
  70. package/node_modules/typebox/build/format/url.mjs +4 -5
  71. package/node_modules/typebox/build/guard/emit.d.mts +3 -1
  72. package/node_modules/typebox/build/guard/emit.mjs +12 -5
  73. package/node_modules/typebox/build/guard/guard.d.mts +8 -2
  74. package/node_modules/typebox/build/guard/guard.mjs +25 -4
  75. package/node_modules/typebox/build/guard/string.d.mts +2 -2
  76. package/node_modules/typebox/build/guard/string.mjs +7 -7
  77. package/node_modules/typebox/build/schema/engine/_context.mjs +3 -3
  78. package/node_modules/typebox/build/schema/engine/_functions.mjs +1 -2
  79. package/node_modules/typebox/build/schema/engine/_regexp.d.mts +2 -0
  80. package/node_modules/typebox/build/schema/engine/_regexp.mjs +4 -0
  81. package/node_modules/typebox/build/schema/engine/_stack.mjs +12 -13
  82. package/node_modules/typebox/build/schema/engine/additionalItems.mjs +17 -7
  83. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +4 -3
  84. package/node_modules/typebox/build/schema/engine/contains.mjs +18 -6
  85. package/node_modules/typebox/build/schema/engine/enum.mjs +1 -1
  86. package/node_modules/typebox/build/schema/engine/items.mjs +26 -6
  87. package/node_modules/typebox/build/schema/engine/maxContains.mjs +3 -3
  88. package/node_modules/typebox/build/schema/engine/minContains.mjs +14 -4
  89. package/node_modules/typebox/build/schema/engine/not.mjs +4 -2
  90. package/node_modules/typebox/build/schema/engine/oneOf.mjs +7 -6
  91. package/node_modules/typebox/build/schema/engine/pattern.mjs +3 -2
  92. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +4 -3
  93. package/node_modules/typebox/build/schema/engine/ref.mjs +1 -7
  94. package/node_modules/typebox/build/schema/engine/schema.mjs +13 -4
  95. package/node_modules/typebox/build/schema/engine/type.mjs +1 -1
  96. package/node_modules/typebox/build/schema/resolve/resolve.d.mts +4 -1
  97. package/node_modules/typebox/build/schema/resolve/resolve.mjs +202 -1
  98. package/node_modules/typebox/build/schema/static/propertyNames.d.mts +5 -0
  99. package/node_modules/typebox/build/schema/static/propertyNames.mjs +3 -0
  100. package/node_modules/typebox/build/schema/static/schema.d.mts +3 -0
  101. package/node_modules/typebox/build/schema/types/index.d.mts +0 -2
  102. package/node_modules/typebox/build/schema/types/index.mjs +0 -2
  103. package/node_modules/typebox/build/system/memory/clone.mjs +14 -11
  104. package/node_modules/typebox/build/system/memory/discard.mjs +6 -5
  105. package/node_modules/typebox/build/system/settings/settings.d.mts +9 -0
  106. package/node_modules/typebox/build/system/settings/settings.mjs +2 -0
  107. package/node_modules/typebox/build/type/engine/call/distribute_arguments.d.mts +6 -1
  108. package/node_modules/typebox/build/type/engine/call/distribute_arguments.mjs +16 -3
  109. package/node_modules/typebox/build/type/engine/call/instantiate.mjs +34 -4
  110. package/node_modules/typebox/build/type/engine/evaluate/broaden.d.mts +5 -5
  111. package/node_modules/typebox/build/type/engine/evaluate/broaden.mjs +20 -26
  112. package/node_modules/typebox/build/type/engine/evaluate/compare.d.mts +6 -10
  113. package/node_modules/typebox/build/type/engine/evaluate/compare.mjs +9 -13
  114. package/node_modules/typebox/build/type/engine/evaluate/composite.d.mts +7 -4
  115. package/node_modules/typebox/build/type/engine/evaluate/composite.mjs +12 -19
  116. package/node_modules/typebox/build/type/engine/evaluate/distribute.d.mts +4 -20
  117. package/node_modules/typebox/build/type/engine/evaluate/distribute.mjs +8 -20
  118. package/node_modules/typebox/build/type/engine/evaluate/evaluate.d.mts +4 -4
  119. package/node_modules/typebox/build/type/engine/evaluate/evaluate.mjs +11 -8
  120. package/node_modules/typebox/build/type/engine/evaluate/flatten.d.mts +1 -1
  121. package/node_modules/typebox/build/type/engine/evaluate/flatten.mjs +3 -4
  122. package/node_modules/typebox/build/type/engine/evaluate/narrow.d.mts +17 -2
  123. package/node_modules/typebox/build/type/engine/evaluate/narrow.mjs +26 -6
  124. package/node_modules/typebox/build/type/engine/exclude/operation.mjs +3 -4
  125. package/node_modules/typebox/build/type/engine/extract/operation.d.mts +1 -1
  126. package/node_modules/typebox/build/type/engine/extract/operation.mjs +3 -4
  127. package/node_modules/typebox/build/type/engine/priority/priority.d.mts +2 -2
  128. package/node_modules/typebox/build/type/engine/priority/priority.mjs +3 -4
  129. package/node_modules/typebox/build/type/script/mapping.d.mts +32 -29
  130. package/node_modules/typebox/build/type/script/mapping.mjs +18 -25
  131. package/node_modules/typebox/build/type/script/parser.d.mts +19 -25
  132. package/node_modules/typebox/build/type/script/parser.mjs +27 -23
  133. package/node_modules/typebox/build/value/errors/errors.d.mts +2 -2
  134. package/node_modules/typebox/build/value/errors/errors.mjs +1 -1
  135. package/node_modules/typebox/package.json +27 -27
  136. package/node_modules/typebox/readme.md +79 -82
  137. package/openclaw.plugin.json +26 -2
  138. package/package.json +6 -6
  139. package/dist/card-command-CyDSm-OU.js +0 -224
  140. package/dist/channel.runtime-qrUV6wPE.js +0 -4
  141. package/dist/config-schema-Be3azjB_.js +0 -47
  142. package/dist/monitor.runtime-B2z_5ugU.js +0 -2
  143. package/dist/outbound.runtime-DDTeqaBW.js +0 -3
  144. package/dist/probe.runtime-D13ZtXsd.js +0 -2
  145. package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
  146. package/node_modules/typebox/build/format/_idna.d.mts +0 -2
  147. package/node_modules/typebox/build/format/_idna.mjs +0 -246
  148. package/node_modules/typebox/build/format/_puny.d.mts +0 -1
  149. package/node_modules/typebox/build/format/_puny.mjs +0 -77
  150. package/node_modules/typebox/build/schema/resolve/ref.d.mts +0 -3
  151. package/node_modules/typebox/build/schema/resolve/ref.mjs +0 -146
  152. package/node_modules/typebox/build/schema/types/contentEncoding.d.mts +0 -9
  153. package/node_modules/typebox/build/schema/types/contentEncoding.mjs +0 -13
  154. package/node_modules/typebox/build/schema/types/contentMediaType.d.mts +0 -9
  155. package/node_modules/typebox/build/schema/types/contentMediaType.mjs +0 -13
  156. package/dist/{probe-CM03wo6r.js → probe-4gc_YQ2L.js} +1 -1
@@ -1,8 +1,11 @@
1
- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
2
1
  import { createMessageReceiptFromOutboundResults } from "openclaw/plugin-sdk/channel-outbound";
2
+ import { collectErrorGraphCandidates, extractErrorCode } from "openclaw/plugin-sdk/error-runtime";
3
+ import { getFileExtension, mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
3
4
  import { resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
4
- import { isRecord as isRecord$1 } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
5
+ import { isRecord } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
5
6
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
7
+ import { HTTPFetchError } from "@line/bot-sdk";
8
+ import { classifyTransientNetworkErrorCode, createChannelApiRetryRunner } from "openclaw/plugin-sdk/retry-runtime";
6
9
  //#region extensions/line/src/outbound-media.ts
7
10
  const LINE_OUTBOUND_MEDIA_SSRF_POLICY = { allowPrivateNetwork: false };
8
11
  async function validateLineMediaUrl(url) {
@@ -23,15 +26,39 @@ function isHttpsUrl(url) {
23
26
  return false;
24
27
  }
25
28
  }
29
+ const LINE_MEDIA_KIND_BY_MIME = {
30
+ "image/jpeg": "image",
31
+ "image/png": "image",
32
+ "video/mp4": "video",
33
+ "audio/mpeg": "audio",
34
+ "audio/x-m4a": "audio"
35
+ };
26
36
  function detectLineMediaKindFromUrl(url) {
27
- try {
28
- const pathname = normalizeLowercaseStringOrEmpty(new URL(url).pathname);
29
- if (/\.(png|jpe?g|gif|webp|bmp|heic|heif|avif)$/i.test(pathname)) return "image";
30
- if (/\.(mp4|mov|m4v|webm)$/i.test(pathname)) return "video";
31
- if (/\.(mp3|m4a|aac|wav|ogg|oga)$/i.test(pathname)) return "audio";
32
- } catch {
33
- return;
34
- }
37
+ const mimeType = mimeTypeFromFilePath(url);
38
+ if (mimeType === void 0) return getFileExtension(url) === void 0 ? void 0 : "unsupported";
39
+ return LINE_MEDIA_KIND_BY_MIME[mimeType] ?? "unsupported";
40
+ }
41
+ function resolveLineMediaKind(url, opts) {
42
+ if (opts.mediaKind !== void 0) return {
43
+ mediaKind: opts.mediaKind,
44
+ kindSource: "declared"
45
+ };
46
+ if (typeof opts.durationMs === "number") return {
47
+ mediaKind: "audio",
48
+ kindSource: "metadata"
49
+ };
50
+ if (opts.trackingId?.trim()) return {
51
+ mediaKind: "video",
52
+ kindSource: "metadata"
53
+ };
54
+ const detected = detectLineMediaKindFromUrl(url);
55
+ return detected === void 0 ? {
56
+ mediaKind: "image",
57
+ kindSource: "fallback"
58
+ } : {
59
+ mediaKind: detected,
60
+ kindSource: "url"
61
+ };
35
62
  }
36
63
  async function resolveLineOutboundMedia(mediaUrl, opts = {}) {
37
64
  const trimmedUrl = mediaUrl.trim();
@@ -41,7 +68,7 @@ async function resolveLineOutboundMedia(mediaUrl, opts = {}) {
41
68
  if (previewImageUrl) await validateLineMediaUrl(previewImageUrl);
42
69
  return {
43
70
  mediaUrl: trimmedUrl,
44
- mediaKind: opts.mediaKind ?? (typeof opts.durationMs === "number" ? "audio" : void 0) ?? (opts.trackingId?.trim() ? "video" : void 0) ?? detectLineMediaKindFromUrl(trimmedUrl) ?? "image",
71
+ ...resolveLineMediaKind(trimmedUrl, opts),
45
72
  ...previewImageUrl ? { previewImageUrl } : {},
46
73
  ...typeof opts.durationMs === "number" ? { durationMs: opts.durationMs } : {},
47
74
  ...opts.trackingId ? { trackingId: opts.trackingId } : {}
@@ -58,31 +85,45 @@ function isLineUserTarget(target) {
58
85
  const normalized = target.trim().replace(/^line:(group|room|user):/i, "").replace(/^line:/i, "");
59
86
  return /^U/i.test(normalized);
60
87
  }
61
- function hasLineSpecificMediaOptions(lineData) {
62
- return lineData.mediaKind !== void 0 || Boolean(lineData.previewImageUrl?.trim()) || typeof lineData.durationMs === "number" || Boolean(lineData.trackingId?.trim());
88
+ function createImageMessage(originalContentUrl, previewImageUrl) {
89
+ return {
90
+ type: "image",
91
+ originalContentUrl,
92
+ previewImageUrl: previewImageUrl ?? originalContentUrl
93
+ };
94
+ }
95
+ function createVideoMessage(originalContentUrl, previewImageUrl, trackingId) {
96
+ return {
97
+ type: "video",
98
+ originalContentUrl,
99
+ previewImageUrl,
100
+ ...trackingId ? { trackingId } : {}
101
+ };
102
+ }
103
+ function createAudioMessage(originalContentUrl, durationMs) {
104
+ return {
105
+ type: "audio",
106
+ originalContentUrl,
107
+ duration: durationMs
108
+ };
109
+ }
110
+ function lineMediaUrlFallback(mediaUrl) {
111
+ return {
112
+ type: "text",
113
+ text: mediaUrl
114
+ };
63
115
  }
64
116
  function buildLineMediaMessageObject(resolved, opts) {
65
117
  switch (resolved.mediaKind) {
118
+ case "unsupported": return lineMediaUrlFallback(resolved.mediaUrl);
66
119
  case "video": {
67
120
  const previewImageUrl = resolved.previewImageUrl?.trim();
68
- if (!previewImageUrl) throw new Error("LINE video messages require previewImageUrl to reference an image URL");
69
- return {
70
- type: "video",
71
- originalContentUrl: resolved.mediaUrl,
72
- previewImageUrl,
73
- ...opts?.allowTrackingId && resolved.trackingId ? { trackingId: resolved.trackingId } : {}
74
- };
121
+ if (previewImageUrl) return createVideoMessage(resolved.mediaUrl, previewImageUrl, opts?.allowTrackingId ? resolved.trackingId : void 0);
122
+ if (resolved.kindSource !== "url") throw new Error("LINE video messages require previewImageUrl to reference an image URL");
123
+ return lineMediaUrlFallback(resolved.mediaUrl);
75
124
  }
76
- case "audio": return {
77
- type: "audio",
78
- originalContentUrl: resolved.mediaUrl,
79
- duration: resolved.durationMs ?? 6e4
80
- };
81
- default: return {
82
- type: "image",
83
- originalContentUrl: resolved.mediaUrl,
84
- previewImageUrl: resolved.previewImageUrl ?? resolved.mediaUrl
85
- };
125
+ case "audio": return createAudioMessage(resolved.mediaUrl, resolved.durationMs ?? 6e4);
126
+ default: return createImageMessage(resolved.mediaUrl, resolved.previewImageUrl);
86
127
  }
87
128
  }
88
129
  async function buildLineMediaMessage(mediaUrl, opts, target) {
@@ -139,7 +180,7 @@ const actionTypes = /* @__PURE__ */ new Set([
139
180
  "uri"
140
181
  ]);
141
182
  function isLineAction(value) {
142
- return isRecord$1(value) && typeof value.type === "string" && actionTypes.has(value.type);
183
+ return isRecord(value) && typeof value.type === "string" && actionTypes.has(value.type);
143
184
  }
144
185
  function isUnavailableAction(action) {
145
186
  return action[unavailableActionMarker] === true;
@@ -150,7 +191,7 @@ function normalizeNestedActions(value, labelLimit, warnings) {
150
191
  for (const item of value) normalized.push(normalizeNestedActions(item, labelLimit, warnings));
151
192
  return normalized;
152
193
  }
153
- if (!isRecord$1(value)) return value;
194
+ if (!isRecord(value)) return value;
154
195
  const normalized = { ...value };
155
196
  for (const [key, nested] of Object.entries(value)) if ((key === "action" || key === "defaultAction") && isLineAction(nested)) {
156
197
  const action = normalizeLineAction(nested, labelLimit);
@@ -163,10 +204,10 @@ function normalizeNestedActions(value, labelLimit, warnings) {
163
204
  return normalized;
164
205
  }
165
206
  function normalizeFlexBubbleActions(value) {
166
- if (!isRecord$1(value) || value.type !== "bubble") return normalizeNestedActions(value, 40);
207
+ if (!isRecord(value) || value.type !== "bubble") return normalizeNestedActions(value, 40);
167
208
  const warnings = [];
168
209
  const normalized = normalizeNestedActions(value, 40, warnings);
169
- if (!isRecord$1(normalized) || warnings.length === 0) return normalized;
210
+ if (!isRecord(normalized) || warnings.length === 0) return normalized;
170
211
  const warning = {
171
212
  type: "text",
172
213
  text: [...new Set(warnings)].join("\n"),
@@ -176,7 +217,7 @@ function normalizeFlexBubbleActions(value) {
176
217
  margin: "md"
177
218
  };
178
219
  const body = normalized.body;
179
- if (isRecord$1(body) && Array.isArray(body.contents)) normalized.body = {
220
+ if (isRecord(body) && Array.isArray(body.contents)) normalized.body = {
180
221
  ...body,
181
222
  contents: [...body.contents, warning]
182
223
  };
@@ -188,7 +229,7 @@ function normalizeFlexBubbleActions(value) {
188
229
  return normalized;
189
230
  }
190
231
  function normalizeFlexContainerActions(value) {
191
- if (!isRecord$1(value)) return value;
232
+ if (!isRecord(value)) return value;
192
233
  if (value.type === "bubble") return normalizeFlexBubbleActions(value);
193
234
  if (value.type === "carousel" && Array.isArray(value.contents)) return {
194
235
  ...value,
@@ -444,7 +485,7 @@ function createInfoCard(title, body, footer) {
444
485
  flex: 1,
445
486
  margin: "lg"
446
487
  }]
447
- }, {
488
+ }, ...body ? [{
448
489
  type: "box",
449
490
  layout: "vertical",
450
491
  contents: [{
@@ -459,7 +500,7 @@ function createInfoCard(title, body, footer) {
459
500
  paddingAll: "lg",
460
501
  backgroundColor: "#F8F9FA",
461
502
  cornerRadius: "lg"
462
- }],
503
+ }] : []],
463
504
  paddingAll: "xl",
464
505
  backgroundColor: "#FFFFFF"
465
506
  }
@@ -604,14 +645,14 @@ function createActionCard(title, body, actions, options) {
604
645
  weight: "bold",
605
646
  size: "xl",
606
647
  wrap: true
607
- }, {
648
+ }, ...body ? [{
608
649
  type: "text",
609
650
  text: body,
610
651
  size: "md",
611
652
  wrap: true,
612
653
  margin: "md",
613
654
  color: "#666666"
614
- }],
655
+ }] : []],
615
656
  paddingAll: "lg"
616
657
  },
617
658
  footer: {
@@ -702,7 +743,7 @@ function createReceiptCard(params) {
702
743
  weight: item.highlight ? "bold" : "regular",
703
744
  flex: 3,
704
745
  wrap: true
705
- }, {
746
+ }, ...item.value ? [{
706
747
  type: "text",
707
748
  text: item.value,
708
749
  size: "sm",
@@ -711,7 +752,7 @@ function createReceiptCard(params) {
711
752
  flex: 2,
712
753
  align: "end",
713
754
  wrap: true
714
- }],
755
+ }] : []],
715
756
  paddingAll: "md",
716
757
  backgroundColor: index % 2 === 0 ? "#FFFFFF" : "#FAFAFA"
717
758
  }));
@@ -976,376 +1017,6 @@ function createAgendaCard(params) {
976
1017
  });
977
1018
  }
978
1019
  //#endregion
979
- //#region extensions/line/src/flex-templates/media-control-cards.ts
980
- /**
981
- * Create a media player card for Sonos, Spotify, Apple Music, etc.
982
- *
983
- * Editorial design: Album art hero with gradient overlay for text,
984
- * prominent now-playing indicator, refined playback controls.
985
- */
986
- function createMediaPlayerCard(params) {
987
- const { title, subtitle, source, imageUrl, isPlaying, progress, controls, extraActions } = params;
988
- const trackInfo = [{
989
- type: "text",
990
- text: title,
991
- weight: "bold",
992
- size: "xl",
993
- color: "#111111",
994
- wrap: true
995
- }];
996
- if (subtitle) trackInfo.push({
997
- type: "text",
998
- text: subtitle,
999
- size: "md",
1000
- color: "#666666",
1001
- wrap: true,
1002
- margin: "sm"
1003
- });
1004
- const statusItems = [];
1005
- if (isPlaying !== void 0) statusItems.push({
1006
- type: "box",
1007
- layout: "horizontal",
1008
- contents: [{
1009
- type: "box",
1010
- layout: "vertical",
1011
- contents: [],
1012
- width: "8px",
1013
- height: "8px",
1014
- backgroundColor: isPlaying ? "#06C755" : "#CCCCCC",
1015
- cornerRadius: "4px"
1016
- }, {
1017
- type: "text",
1018
- text: isPlaying ? "Now Playing" : "Paused",
1019
- size: "xs",
1020
- color: isPlaying ? "#06C755" : "#888888",
1021
- weight: "bold",
1022
- margin: "sm"
1023
- }],
1024
- alignItems: "center"
1025
- });
1026
- if (source) statusItems.push({
1027
- type: "text",
1028
- text: source,
1029
- size: "xs",
1030
- color: "#AAAAAA",
1031
- margin: statusItems.length > 0 ? "lg" : void 0
1032
- });
1033
- if (progress) statusItems.push({
1034
- type: "text",
1035
- text: progress,
1036
- size: "xs",
1037
- color: "#888888",
1038
- align: "end",
1039
- flex: 1
1040
- });
1041
- const bodyContents = [{
1042
- type: "box",
1043
- layout: "vertical",
1044
- contents: trackInfo
1045
- }];
1046
- if (statusItems.length > 0) bodyContents.push({
1047
- type: "box",
1048
- layout: "horizontal",
1049
- contents: statusItems,
1050
- margin: "lg",
1051
- alignItems: "center"
1052
- });
1053
- const bubble = {
1054
- type: "bubble",
1055
- size: "mega",
1056
- body: {
1057
- type: "box",
1058
- layout: "vertical",
1059
- contents: bodyContents,
1060
- paddingAll: "xl",
1061
- backgroundColor: "#FFFFFF"
1062
- }
1063
- };
1064
- if (imageUrl) bubble.hero = {
1065
- type: "image",
1066
- url: imageUrl,
1067
- size: "full",
1068
- aspectRatio: "1:1",
1069
- aspectMode: "cover"
1070
- };
1071
- if (controls || extraActions?.length) {
1072
- const footerContents = [];
1073
- if (controls) {
1074
- const controlButtons = [];
1075
- if (controls.previous) controlButtons.push({
1076
- type: "button",
1077
- action: postbackAction("⏮", controls.previous.data),
1078
- style: "secondary",
1079
- flex: 1,
1080
- height: "sm"
1081
- });
1082
- if (controls.play) controlButtons.push({
1083
- type: "button",
1084
- action: postbackAction("▶", controls.play.data),
1085
- style: isPlaying ? "secondary" : "primary",
1086
- flex: 1,
1087
- height: "sm",
1088
- margin: controls.previous ? "md" : void 0
1089
- });
1090
- if (controls.pause) controlButtons.push({
1091
- type: "button",
1092
- action: postbackAction("⏸", controls.pause.data),
1093
- style: isPlaying ? "primary" : "secondary",
1094
- flex: 1,
1095
- height: "sm",
1096
- margin: controlButtons.length > 0 ? "md" : void 0
1097
- });
1098
- if (controls.next) controlButtons.push({
1099
- type: "button",
1100
- action: postbackAction("⏭", controls.next.data),
1101
- style: "secondary",
1102
- flex: 1,
1103
- height: "sm",
1104
- margin: controlButtons.length > 0 ? "md" : void 0
1105
- });
1106
- if (controlButtons.length > 0) footerContents.push({
1107
- type: "box",
1108
- layout: "horizontal",
1109
- contents: controlButtons
1110
- });
1111
- }
1112
- if (extraActions?.length) footerContents.push({
1113
- type: "box",
1114
- layout: "horizontal",
1115
- contents: extraActions.slice(0, 2).map((action, index) => ({
1116
- type: "button",
1117
- action: postbackAction(truncateLineActionLabel(action.label, 15), action.data),
1118
- style: "secondary",
1119
- flex: 1,
1120
- height: "sm",
1121
- margin: index > 0 ? "md" : void 0
1122
- })),
1123
- margin: "md"
1124
- });
1125
- if (footerContents.length > 0) bubble.footer = {
1126
- type: "box",
1127
- layout: "vertical",
1128
- contents: footerContents,
1129
- paddingAll: "lg",
1130
- backgroundColor: "#FAFAFA"
1131
- };
1132
- }
1133
- return bubble;
1134
- }
1135
- /**
1136
- * Create an Apple TV remote card with a D-pad and control rows.
1137
- */
1138
- function createAppleTvRemoteCard(params) {
1139
- const { deviceName, status, actionData } = params;
1140
- const headerContents = [{
1141
- type: "text",
1142
- text: deviceName,
1143
- weight: "bold",
1144
- size: "xl",
1145
- color: "#111111",
1146
- wrap: true
1147
- }];
1148
- if (status) headerContents.push({
1149
- type: "text",
1150
- text: status,
1151
- size: "sm",
1152
- color: "#666666",
1153
- wrap: true,
1154
- margin: "sm"
1155
- });
1156
- const makeButton = (label, data, style = "secondary") => ({
1157
- type: "button",
1158
- action: postbackAction(label, data),
1159
- style,
1160
- height: "sm",
1161
- flex: 1
1162
- });
1163
- const dpadRows = [
1164
- {
1165
- type: "box",
1166
- layout: "horizontal",
1167
- contents: [
1168
- { type: "filler" },
1169
- makeButton("↑", actionData.up),
1170
- { type: "filler" }
1171
- ]
1172
- },
1173
- {
1174
- type: "box",
1175
- layout: "horizontal",
1176
- contents: [
1177
- makeButton("←", actionData.left),
1178
- makeButton("OK", actionData.select, "primary"),
1179
- makeButton("→", actionData.right)
1180
- ],
1181
- margin: "md"
1182
- },
1183
- {
1184
- type: "box",
1185
- layout: "horizontal",
1186
- contents: [
1187
- { type: "filler" },
1188
- makeButton("↓", actionData.down),
1189
- { type: "filler" }
1190
- ],
1191
- margin: "md"
1192
- }
1193
- ];
1194
- const menuRow = {
1195
- type: "box",
1196
- layout: "horizontal",
1197
- contents: [makeButton("Menu", actionData.menu), makeButton("Home", actionData.home)],
1198
- margin: "lg"
1199
- };
1200
- const playbackRow = {
1201
- type: "box",
1202
- layout: "horizontal",
1203
- contents: [makeButton("Play", actionData.play), makeButton("Pause", actionData.pause)],
1204
- margin: "md"
1205
- };
1206
- const volumeRow = {
1207
- type: "box",
1208
- layout: "horizontal",
1209
- contents: [
1210
- makeButton("Vol +", actionData.volumeUp),
1211
- makeButton("Mute", actionData.mute),
1212
- makeButton("Vol -", actionData.volumeDown)
1213
- ],
1214
- margin: "md"
1215
- };
1216
- return {
1217
- type: "bubble",
1218
- size: "mega",
1219
- body: {
1220
- type: "box",
1221
- layout: "vertical",
1222
- contents: [
1223
- {
1224
- type: "box",
1225
- layout: "vertical",
1226
- contents: headerContents
1227
- },
1228
- {
1229
- type: "separator",
1230
- margin: "lg",
1231
- color: "#EEEEEE"
1232
- },
1233
- ...dpadRows,
1234
- menuRow,
1235
- playbackRow,
1236
- volumeRow
1237
- ],
1238
- paddingAll: "xl",
1239
- backgroundColor: "#FFFFFF"
1240
- }
1241
- };
1242
- }
1243
- /**
1244
- * Create a device control card for Apple TV, smart home devices, etc.
1245
- *
1246
- * Editorial design: Device-focused header with status indicator,
1247
- * clean control grid with clear visual hierarchy.
1248
- */
1249
- function createDeviceControlCard(params) {
1250
- const { deviceName, deviceType, status, isOnline, imageUrl, controls } = params;
1251
- const headerContents = [{
1252
- type: "box",
1253
- layout: "horizontal",
1254
- contents: [{
1255
- type: "box",
1256
- layout: "vertical",
1257
- contents: [],
1258
- width: "10px",
1259
- height: "10px",
1260
- backgroundColor: isOnline !== false ? "#06C755" : "#FF5555",
1261
- cornerRadius: "5px"
1262
- }, {
1263
- type: "text",
1264
- text: deviceName,
1265
- weight: "bold",
1266
- size: "xl",
1267
- color: "#111111",
1268
- wrap: true,
1269
- flex: 1,
1270
- margin: "md"
1271
- }],
1272
- alignItems: "center"
1273
- }];
1274
- if (deviceType) headerContents.push({
1275
- type: "text",
1276
- text: deviceType,
1277
- size: "sm",
1278
- color: "#888888",
1279
- margin: "sm"
1280
- });
1281
- if (status) headerContents.push({
1282
- type: "box",
1283
- layout: "vertical",
1284
- contents: [{
1285
- type: "text",
1286
- text: status,
1287
- size: "sm",
1288
- color: "#444444",
1289
- wrap: true
1290
- }],
1291
- margin: "lg",
1292
- paddingAll: "md",
1293
- backgroundColor: "#F8F9FA",
1294
- cornerRadius: "md"
1295
- });
1296
- const bubble = {
1297
- type: "bubble",
1298
- size: "mega",
1299
- body: {
1300
- type: "box",
1301
- layout: "vertical",
1302
- contents: headerContents,
1303
- paddingAll: "xl",
1304
- backgroundColor: "#FFFFFF"
1305
- }
1306
- };
1307
- if (imageUrl) bubble.hero = {
1308
- type: "image",
1309
- url: imageUrl,
1310
- size: "full",
1311
- aspectRatio: "16:9",
1312
- aspectMode: "cover"
1313
- };
1314
- if (controls.length > 0) {
1315
- const rows = [];
1316
- const limitedControls = controls.slice(0, 6);
1317
- for (let i = 0; i < limitedControls.length; i += 2) {
1318
- const rowButtons = [];
1319
- for (const [offset, ctrl] of limitedControls.slice(i, i + 2).entries()) {
1320
- const buttonLabel = ctrl.icon ? `${ctrl.icon} ${ctrl.label}` : ctrl.label;
1321
- rowButtons.push({
1322
- type: "button",
1323
- action: postbackAction(truncateLineActionLabel(buttonLabel, 18), ctrl.data),
1324
- style: ctrl.style ?? "secondary",
1325
- flex: 1,
1326
- height: "sm",
1327
- margin: offset > 0 ? "md" : void 0
1328
- });
1329
- }
1330
- if (rowButtons.length === 1) rowButtons.push({ type: "filler" });
1331
- rows.push({
1332
- type: "box",
1333
- layout: "horizontal",
1334
- contents: rowButtons,
1335
- margin: i > 0 ? "md" : void 0
1336
- });
1337
- }
1338
- bubble.footer = {
1339
- type: "box",
1340
- layout: "vertical",
1341
- contents: rows,
1342
- paddingAll: "lg",
1343
- backgroundColor: "#FAFAFA"
1344
- };
1345
- }
1346
- return bubble;
1347
- }
1348
- //#endregion
1349
1020
  //#region extensions/line/src/send-receipt.ts
1350
1021
  function createLineSendReceipt(params) {
1351
1022
  const messageIds = (params.messageIds ?? [params.messageId]).map((messageId) => messageId.trim()).filter(Boolean);
@@ -1363,4 +1034,57 @@ function createLineSendReceipt(params) {
1363
1034
  });
1364
1035
  }
1365
1036
  //#endregion
1366
- export { uriAction as _, createAgendaCard as a, resolveLineOutboundMedia as b, createActionCard as c, createListCard as d, datetimePickerAction as f, postbackAction as g, normalizeLineMessageActions as h, createMediaPlayerCard as i, createImageCard as l, normalizeLineAction as m, createAppleTvRemoteCard as n, createEventCard as o, messageAction as p, createDeviceControlCard as r, createReceiptCard as s, createLineSendReceipt as t, createInfoCard as u, buildLineMediaMessage as v, validateLineMediaUrl as x, hasLineSpecificMediaOptions as y };
1037
+ //#region extensions/line/src/send-retry.ts
1038
+ /** The LINE HTTP response carried by an error graph, when the request reached LINE. */
1039
+ function findLineHttpError(error) {
1040
+ return collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]).find((candidate) => candidate instanceof HTTPFetchError);
1041
+ }
1042
+ /**
1043
+ * LINE answered this attempt with a client error, so it rejected the request and
1044
+ * sent nothing.
1045
+ *
1046
+ * A 429 proves this attempt was refused but remains retryable by durable
1047
+ * delivery. A 408 stays ambiguous because the request may have reached LINE.
1048
+ */
1049
+ function resolveAttemptNonDispatchRetryable(error) {
1050
+ const status = findLineHttpError(error)?.status;
1051
+ if (status === 429) return true;
1052
+ if (status === 409) return;
1053
+ return status !== void 0 && status >= 400 && status < 500 && status !== 408 ? false : void 0;
1054
+ }
1055
+ const pushErrorsWithAmbiguousAttempt = /* @__PURE__ */ new WeakSet();
1056
+ /** Retryability when LINE refused every attempt, or undefined when delivery is ambiguous. */
1057
+ function resolveLineNonDispatchRetryable(error) {
1058
+ if (collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]).some((candidate) => typeof candidate === "object" && candidate !== null && pushErrorsWithAmbiguousAttempt.has(candidate))) return;
1059
+ return resolveAttemptNonDispatchRetryable(error);
1060
+ }
1061
+ function isRetryableLinePushError(error) {
1062
+ const httpError = findLineHttpError(error);
1063
+ if (httpError) return httpError.status >= 500;
1064
+ return collectErrorGraphCandidates(error, (candidate) => [candidate.cause, candidate.error]).some((candidate) => classifyTransientNetworkErrorCode(extractErrorCode(candidate)) !== void 0);
1065
+ }
1066
+ /**
1067
+ * Pushes are non-idempotent without a retry key, so the generic message-matching
1068
+ * fallback stays off and only the classification above may replay a request.
1069
+ */
1070
+ const runLinePushAttempts = createChannelApiRetryRunner({
1071
+ shouldRetry: isRetryableLinePushError,
1072
+ strictShouldRetry: true,
1073
+ verbose: true
1074
+ });
1075
+ const runLinePushWithRetries = (fn, label) => {
1076
+ let sawAmbiguousAttempt = false;
1077
+ return runLinePushAttempts(async () => {
1078
+ try {
1079
+ return await fn();
1080
+ } catch (error) {
1081
+ sawAmbiguousAttempt ||= resolveAttemptNonDispatchRetryable(error) === void 0;
1082
+ throw error;
1083
+ }
1084
+ }, label).catch((error) => {
1085
+ if (sawAmbiguousAttempt && typeof error === "object" && error !== null) pushErrorsWithAmbiguousAttempt.add(error);
1086
+ throw error;
1087
+ });
1088
+ };
1089
+ //#endregion
1090
+ export { createVideoMessage as S, truncateLineActionLabel as _, createAgendaCard as a, createAudioMessage as b, createActionCard as c, createListCard as d, datetimePickerAction as f, postbackAction as g, normalizeLineMessageActions as h, createLineSendReceipt as i, createImageCard as l, normalizeLineAction as m, resolveLineNonDispatchRetryable as n, createEventCard as o, messageAction as p, runLinePushWithRetries as r, createReceiptCard as s, findLineHttpError as t, createInfoCard as u, uriAction as v, createImageMessage as x, buildLineMediaMessage as y };