@openclaw/line 2026.7.2-beta.5 → 2026.7.33

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 (235) hide show
  1. package/README.md +1 -1
  2. package/dist/.setup/accounts-DJVOv1JI.mjs +107 -0
  3. package/dist/.setup/api-D3h9w6oz.mjs +3 -0
  4. package/dist/.setup/api-DdlGHKoX.mjs +11 -0
  5. package/dist/.setup/basic-cards-C8Qwh2X1.mjs +307 -0
  6. package/dist/{card-command-BEMIm8D8.js → .setup/card-command-CWrkUiGY.mjs} +51 -23
  7. package/dist/{channel-CTaWrzfl.js → .setup/channel-Du0PrvOH.mjs} +76 -66
  8. package/dist/.setup/channel.runtime-b3suPB-W.mjs +4 -0
  9. package/dist/.setup/flex-templates-BP6VHjzK.mjs +814 -0
  10. package/dist/{markdown-to-line-Dn00k1bq.js → .setup/markdown-to-line-o89GT1Qn.mjs} +252 -254
  11. package/dist/{monitor-DzFU5UPV.js → .setup/monitor-DpxjN6Ks.mjs} +488 -686
  12. package/dist/.setup/monitor.runtime-BS-cGD5Q.mjs +2 -0
  13. package/dist/.setup/outbound.runtime-BeNDuEIF.mjs +2 -0
  14. package/dist/{probe-CM03wo6r.js → .setup/probe-BslD77tJ.mjs} +9 -7
  15. package/dist/.setup/probe.runtime-YwWmWIlk.mjs +2 -0
  16. package/dist/{reply-payload-transform-BRM2fRvK.js → .setup/reply-payload-transform-CYQIy_x8.mjs} +204 -80
  17. package/dist/{setup-surface-BHQOSD8R.js → .setup/setup-surface-M1ivmZ-G.mjs} +110 -143
  18. package/dist/api.js +2 -11
  19. package/dist/channel-plugin-api.js +1 -1
  20. package/dist/contract-api.js +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/runtime-api.js +55 -13
  23. package/dist/setup-api.js +1 -1
  24. package/dist/setup-entry.js +2 -1
  25. package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupCreateRoute.d.ts.map +1 -1
  26. package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupFailedType.d.ts.map +1 -1
  27. package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupJobFailedType.d.ts.map +1 -1
  28. package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupJobStatus.d.ts.map +1 -1
  29. package/node_modules/@line/bot-sdk/dist/cjs/manage-audience/model/audienceGroupStatus.d.ts.map +1 -1
  30. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexBoxBorderWidth.d.ts.map +1 -1
  31. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexBoxCornerRadius.d.ts.map +1 -1
  32. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexBubble.d.ts.map +1 -1
  33. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexIconSize.d.ts.map +1 -1
  34. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexSpanSize.d.ts.map +1 -1
  35. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/flexTextFontSize.d.ts.map +1 -1
  36. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/numberOfMessagesResponse.d.ts.map +1 -1
  37. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/postbackAction.d.ts.map +1 -1
  38. package/node_modules/@line/bot-sdk/dist/cjs/messaging-api/model/subscriptionPeriodDemographic.d.ts.map +1 -1
  39. package/node_modules/@line/bot-sdk/dist/cjs/version.d.ts +1 -1
  40. package/node_modules/@line/bot-sdk/dist/cjs/version.js +1 -1
  41. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/event.d.ts +1 -2
  42. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/event.d.ts.map +1 -1
  43. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.d.ts +0 -1
  44. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.d.ts.map +1 -1
  45. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.js +0 -1
  46. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/models.js.map +1 -1
  47. package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupCreateRoute.d.ts.map +1 -1
  48. package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupFailedType.d.ts.map +1 -1
  49. package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupJobFailedType.d.ts.map +1 -1
  50. package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupJobStatus.d.ts.map +1 -1
  51. package/node_modules/@line/bot-sdk/dist/manage-audience/model/audienceGroupStatus.d.ts.map +1 -1
  52. package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexBoxBorderWidth.d.ts.map +1 -1
  53. package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexBoxCornerRadius.d.ts.map +1 -1
  54. package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexBubble.d.ts.map +1 -1
  55. package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexIconSize.d.ts.map +1 -1
  56. package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexSpanSize.d.ts.map +1 -1
  57. package/node_modules/@line/bot-sdk/dist/messaging-api/model/flexTextFontSize.d.ts.map +1 -1
  58. package/node_modules/@line/bot-sdk/dist/messaging-api/model/numberOfMessagesResponse.d.ts.map +1 -1
  59. package/node_modules/@line/bot-sdk/dist/messaging-api/model/postbackAction.d.ts.map +1 -1
  60. package/node_modules/@line/bot-sdk/dist/messaging-api/model/subscriptionPeriodDemographic.d.ts.map +1 -1
  61. package/node_modules/@line/bot-sdk/dist/version.d.ts +1 -1
  62. package/node_modules/@line/bot-sdk/dist/version.js +1 -1
  63. package/node_modules/@line/bot-sdk/dist/webhook/model/event.d.ts +1 -2
  64. package/node_modules/@line/bot-sdk/dist/webhook/model/event.d.ts.map +1 -1
  65. package/node_modules/@line/bot-sdk/dist/webhook/model/models.d.ts +0 -1
  66. package/node_modules/@line/bot-sdk/dist/webhook/model/models.d.ts.map +1 -1
  67. package/node_modules/@line/bot-sdk/dist/webhook/model/models.js +0 -1
  68. package/node_modules/@line/bot-sdk/dist/webhook/model/models.js.map +1 -1
  69. package/node_modules/@line/bot-sdk/lib/insight/model/getNumberOfFollowersResponse.ts +1 -1
  70. package/node_modules/@line/bot-sdk/lib/liff/model/liffScope.ts +1 -1
  71. package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupCreateRoute.ts +4 -1
  72. package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupFailedType.ts +2 -1
  73. package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupJobFailedType.ts +2 -1
  74. package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupJobStatus.ts +4 -1
  75. package/node_modules/@line/bot-sdk/lib/manage-audience/model/audienceGroupStatus.ts +6 -1
  76. package/node_modules/@line/bot-sdk/lib/messaging-api/model/botInfoResponse.ts +1 -1
  77. package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexBoxBorderWidth.ts +6 -1
  78. package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexBoxCornerRadius.ts +7 -1
  79. package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexBubble.ts +7 -1
  80. package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexIconSize.ts +10 -1
  81. package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexSpanSize.ts +10 -1
  82. package/node_modules/@line/bot-sdk/lib/messaging-api/model/flexTextFontSize.ts +10 -1
  83. package/node_modules/@line/bot-sdk/lib/messaging-api/model/getAggregationUnitNameListResponse.ts +1 -1
  84. package/node_modules/@line/bot-sdk/lib/messaging-api/model/getFollowersResponse.ts +1 -1
  85. package/node_modules/@line/bot-sdk/lib/messaging-api/model/getJoinedMembershipUsersResponse.ts +1 -1
  86. package/node_modules/@line/bot-sdk/lib/messaging-api/model/issueLinkTokenResponse.ts +1 -1
  87. package/node_modules/@line/bot-sdk/lib/messaging-api/model/numberOfMessagesResponse.ts +5 -2
  88. package/node_modules/@line/bot-sdk/lib/messaging-api/model/postbackAction.ts +4 -1
  89. package/node_modules/@line/bot-sdk/lib/messaging-api/model/subscriptionPeriodDemographic.ts +5 -1
  90. package/node_modules/@line/bot-sdk/lib/version.ts +1 -1
  91. package/node_modules/@line/bot-sdk/lib/webhook/.openapi-generator/FILES +0 -1
  92. package/node_modules/@line/bot-sdk/lib/webhook/model/event.ts +0 -2
  93. package/node_modules/@line/bot-sdk/lib/webhook/model/models.ts +0 -1
  94. package/node_modules/@line/bot-sdk/lib/webhook/model/stickerMessageContent.ts +1 -1
  95. package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/README.md +1 -1
  96. package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/fs/promises.d.ts +5 -16
  97. package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/package.json +3 -3
  98. package/node_modules/@line/bot-sdk/package.json +3 -3
  99. package/openclaw.plugin.json +75 -57
  100. package/package.json +6 -71
  101. package/dist/accounts-CSH5sk8U.js +0 -167
  102. package/dist/channel.runtime-D4Paci2y.js +0 -4
  103. package/dist/flex-templates-DVy_wog4.js +0 -1341
  104. package/dist/monitor.runtime-B8xjVqev.js +0 -2
  105. package/dist/outbound.runtime-eHl7NNHu.js +0 -2
  106. package/dist/probe.runtime-D13ZtXsd.js +0 -2
  107. package/dist/quick-reply-fallback-D86FwAfr.js +0 -56
  108. package/dist/send-receipt-CY2ey8XH.js +0 -107
  109. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.d.ts +0 -24
  110. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.d.ts.map +0 -1
  111. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.js +0 -14
  112. package/node_modules/@line/bot-sdk/dist/cjs/webhook/model/messageEditedEvent.js.map +0 -1
  113. package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.d.ts +0 -24
  114. package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.d.ts.map +0 -1
  115. package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.js +0 -13
  116. package/node_modules/@line/bot-sdk/dist/webhook/model/messageEditedEvent.js.map +0 -1
  117. package/node_modules/@line/bot-sdk/lib/webhook/model/messageEditedEvent.ts +0 -32
  118. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/LICENSE +0 -0
  119. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/assert/strict.d.ts +0 -0
  120. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/assert.d.ts +0 -0
  121. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/async_hooks.d.ts +0 -0
  122. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/buffer.buffer.d.ts +0 -0
  123. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/buffer.d.ts +0 -0
  124. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/child_process.d.ts +0 -0
  125. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/cluster.d.ts +0 -0
  126. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/compatibility/iterators.d.ts +0 -0
  127. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/console.d.ts +0 -0
  128. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/constants.d.ts +0 -0
  129. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/crypto.d.ts +0 -0
  130. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dgram.d.ts +0 -0
  131. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/diagnostics_channel.d.ts +0 -0
  132. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dns/promises.d.ts +0 -0
  133. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/dns.d.ts +0 -0
  134. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/domain.d.ts +0 -0
  135. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/events.d.ts +0 -0
  136. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/fs.d.ts +0 -0
  137. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/globals.d.ts +0 -0
  138. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/globals.typedarray.d.ts +0 -0
  139. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/http.d.ts +0 -0
  140. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/http2.d.ts +0 -0
  141. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/https.d.ts +0 -0
  142. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/index.d.ts +0 -0
  143. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/inspector.d.ts +0 -0
  144. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/inspector.generated.d.ts +0 -0
  145. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/module.d.ts +0 -0
  146. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/net.d.ts +0 -0
  147. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/os.d.ts +0 -0
  148. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/path.d.ts +0 -0
  149. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/perf_hooks.d.ts +0 -0
  150. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/process.d.ts +0 -0
  151. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/punycode.d.ts +0 -0
  152. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/querystring.d.ts +0 -0
  153. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/readline/promises.d.ts +0 -0
  154. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/readline.d.ts +0 -0
  155. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/repl.d.ts +0 -0
  156. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/sea.d.ts +0 -0
  157. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/sqlite.d.ts +0 -0
  158. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/consumers.d.ts +0 -0
  159. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/promises.d.ts +0 -0
  160. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream/web.d.ts +0 -0
  161. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/stream.d.ts +0 -0
  162. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/string_decoder.d.ts +0 -0
  163. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/test.d.ts +0 -0
  164. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/timers/promises.d.ts +0 -0
  165. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/timers.d.ts +0 -0
  166. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/tls.d.ts +0 -0
  167. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/trace_events.d.ts +0 -0
  168. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/buffer.buffer.d.ts +0 -0
  169. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/compatibility/float16array.d.ts +0 -0
  170. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/globals.typedarray.d.ts +0 -0
  171. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.6/index.d.ts +0 -0
  172. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.7/compatibility/float16array.d.ts +0 -0
  173. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/ts5.7/index.d.ts +0 -0
  174. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/tty.d.ts +0 -0
  175. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/url.d.ts +0 -0
  176. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/util.d.ts +0 -0
  177. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/v8.d.ts +0 -0
  178. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/vm.d.ts +0 -0
  179. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/wasi.d.ts +0 -0
  180. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/abortcontroller.d.ts +0 -0
  181. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/crypto.d.ts +0 -0
  182. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/domexception.d.ts +0 -0
  183. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/events.d.ts +0 -0
  184. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/fetch.d.ts +0 -0
  185. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/navigator.d.ts +0 -0
  186. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/storage.d.ts +0 -0
  187. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/web-globals/streams.d.ts +0 -0
  188. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/worker_threads.d.ts +0 -0
  189. /package/node_modules/{@types → @line/bot-sdk/node_modules/@types}/node/zlib.d.ts +0 -0
  190. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/LICENSE +0 -0
  191. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/README.md +0 -0
  192. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/agent.d.ts +0 -0
  193. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/api.d.ts +0 -0
  194. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/balanced-pool.d.ts +0 -0
  195. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cache-interceptor.d.ts +0 -0
  196. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cache.d.ts +0 -0
  197. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/client-stats.d.ts +0 -0
  198. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/client.d.ts +0 -0
  199. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/connector.d.ts +0 -0
  200. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/content-type.d.ts +0 -0
  201. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/cookies.d.ts +0 -0
  202. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/diagnostics-channel.d.ts +0 -0
  203. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/dispatcher.d.ts +0 -0
  204. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/env-http-proxy-agent.d.ts +0 -0
  205. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/errors.d.ts +0 -0
  206. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/eventsource.d.ts +0 -0
  207. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/fetch.d.ts +0 -0
  208. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/formdata.d.ts +0 -0
  209. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/global-dispatcher.d.ts +0 -0
  210. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/global-origin.d.ts +0 -0
  211. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/h2c-client.d.ts +0 -0
  212. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/handlers.d.ts +0 -0
  213. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/header.d.ts +0 -0
  214. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/index.d.ts +0 -0
  215. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/interceptors.d.ts +0 -0
  216. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-agent.d.ts +0 -0
  217. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-call-history.d.ts +0 -0
  218. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-client.d.ts +0 -0
  219. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-errors.d.ts +0 -0
  220. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-interceptor.d.ts +0 -0
  221. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/mock-pool.d.ts +0 -0
  222. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/package.json +0 -0
  223. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/patch.d.ts +0 -0
  224. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/pool-stats.d.ts +0 -0
  225. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/pool.d.ts +0 -0
  226. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/proxy-agent.d.ts +0 -0
  227. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/readable.d.ts +0 -0
  228. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/retry-agent.d.ts +0 -0
  229. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/retry-handler.d.ts +0 -0
  230. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/round-robin-pool.d.ts +0 -0
  231. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/snapshot-agent.d.ts +0 -0
  232. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/util.d.ts +0 -0
  233. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/utility.d.ts +0 -0
  234. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/webidl.d.ts +0 -0
  235. /package/node_modules/{undici-types → @line/bot-sdk/node_modules/undici-types}/websocket.d.ts +0 -0
@@ -1,35 +1,30 @@
1
- import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-CSH5sk8U.js";
2
- import { a as resolveLineGroupConfigEntry, n as getLineRuntime, t as buildLineQuickReplyFallbackText } from "./quick-reply-fallback-D86FwAfr.js";
3
- import { n as buildLineMediaMessage, r as hasLineSpecificMediaOptions } from "./send-receipt-CY2ey8XH.js";
4
- import { E as buildTemplateMessageFromPayload, S as replyMessageLine, i as processLineMessage, l as createLocationMessage, p as getUserDisplayName, s as createFlexMessage, u as createQuickReplyItems, v as pushMessageLine, w as showLoadingAnimation, y as pushMessagesLine } from "./markdown-to-line-Dn00k1bq.js";
1
+ import { i as resolveLineAccount, r as resolveDefaultLineAccountId } from "./accounts-DJVOv1JI.mjs";
2
+ import { f as resolveLineGroupConfigEntry, i as buildLineQuickReplyFallbackText, s as getLineRuntime } from "./reply-payload-transform-CYQIy_x8.mjs";
3
+ import { A as buildTemplateMessageFromPayload, C as pushMessagesLine, E as replyMessageLine, O as showLoadingAnimation, S as pushMessageLine, T as pushTextMessageWithQuickReplies, _ as getUserDisplayName, c as processLineMessage, d as createFlexMessage, f as createImageMessage, h as createTextMessageWithQuickReplies, m as createQuickReplyItems, p as createLocationMessage } from "./markdown-to-line-o89GT1Qn.mjs";
5
4
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
6
5
  import { normalizeOptionalString, normalizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
7
- import { DEFAULT_INGRESS_ADOPTION_STALL_MS, DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS, bindIngressLifecycleToReplyOptions, createChannelIngressMonitor } from "openclaw/plugin-sdk/channel-outbound";
6
+ import { createMessageReceiveContext } from "openclaw/plugin-sdk/channel-outbound";
8
7
  import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
9
- import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
10
8
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
11
- import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
12
9
  import { firstDefined } from "openclaw/plugin-sdk/allow-from";
13
- import { setTimeout as setTimeout$1 } from "node:timers/promises";
14
- import { MediaFetchError } from "openclaw/plugin-sdk/media-runtime";
10
+ import { messagingApi } from "@line/bot-sdk";
15
11
  import { saveMediaStream } from "openclaw/plugin-sdk/media-store";
16
- import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal, warn } from "openclaw/plugin-sdk/runtime-env";
17
- import { fetchWithRuntimeDispatcherOrMockedGlobal } from "openclaw/plugin-sdk/runtime-fetch";
18
- import { HTTPFetchError } from "@line/bot-sdk";
12
+ import { createNonExitingRuntime, danger, logVerbose, shouldLogVerbose, waitForAbortSignal } from "openclaw/plugin-sdk/runtime-env";
19
13
  import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
20
- import { buildChannelInboundEventContext, buildMentionRegexes, formatInboundEnvelope, formatInboundMediaUnavailableText, formatLocationText, hasFinalInboundReplyDispatch, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toInboundMediaFacts, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
14
+ import { buildMentionRegexes, formatInboundEnvelope, formatInboundMediaUnavailableText, formatLocationText, hasFinalInboundReplyDispatch, matchesMentionPatterns, resolveInboundSessionEnvelopeContext, toLocationContext } from "openclaw/plugin-sdk/channel-inbound";
21
15
  import { chunkMarkdownText } from "openclaw/plugin-sdk/reply-runtime";
22
16
  import { isRequestBodyLimitError, normalizePluginHttpPath, normalizeWebhookPath, registerWebhookTargetWithPluginRoute, requestBodyErrorToText, resolveSingleWebhookTarget } from "openclaw/plugin-sdk/webhook-ingress";
23
- import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1, runDetachedWebhookWork } from "openclaw/plugin-sdk/webhook-request-guards";
17
+ import { beginWebhookRequestPipelineOrReject, createWebhookInFlightLimiter, isRequestBodyLimitError as isRequestBodyLimitError$1, readRequestBodyWithLimit, requestBodyErrorToText as requestBodyErrorToText$1 } from "openclaw/plugin-sdk/webhook-request-guards";
24
18
  import { DEFAULT_GROUP_HISTORY_LIMIT, createChannelHistoryWindow } from "openclaw/plugin-sdk/reply-history";
25
19
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
26
20
  import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel-ingress-runtime";
27
- import { hasControlCommand } from "openclaw/plugin-sdk/command-auth-native";
21
+ import { shouldComputeCommandAuthorized } from "openclaw/plugin-sdk/command-auth-native";
28
22
  import { ensureConfiguredBindingRouteReady, readChannelAllowFromStore, resolveConfiguredBindingRoute, resolvePairingIdLabel, resolvePinnedMainDmOwnerFromAllowlist, resolveRuntimeConversationBindingRoute, upsertChannelPairingRequest } from "openclaw/plugin-sdk/conversation-runtime";
23
+ import { createClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
29
24
  import { resolveAgentRoute, resolveInboundLastRouteSessionKey } from "openclaw/plugin-sdk/routing";
30
25
  import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
26
+ import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
31
27
  import crypto from "node:crypto";
32
- import { safeEqualSecret } from "openclaw/plugin-sdk/security-runtime";
33
28
  //#region extensions/line/src/bot-access.ts
34
29
  function normalizeLineAllowEntry(value) {
35
30
  const trimmed = String(value).trim();
@@ -47,92 +42,9 @@ const normalizeAllowFrom = (list) => {
47
42
  };
48
43
  //#endregion
49
44
  //#region extensions/line/src/download.ts
50
- const CONTENT_READY_MAX_ATTEMPTS = 6;
51
- const CONTENT_READY_BASE_DELAY_MS = 500;
52
- const CONTENT_READY_MAX_DELAY_MS = 4e3;
53
- const CONTENT_READY_TIMEOUT_MS = 15e3;
54
- const LINE_CONTENT_BASE_URL = "https://api-data.line.me/v2/bot/message";
55
- var RetryableLineMediaFetchError = class extends MediaFetchError {
56
- constructor(message, options) {
57
- super("fetch_failed", message, options);
58
- this.name = "RetryableLineMediaFetchError";
59
- }
60
- };
61
- function contentBackoffDelayMs(attempt) {
62
- return Math.min(CONTENT_READY_BASE_DELAY_MS * 2 ** attempt, CONTENT_READY_MAX_DELAY_MS);
63
- }
64
- function lineContentUrl(messageId) {
65
- return `${LINE_CONTENT_BASE_URL}/${encodeURIComponent(messageId)}/content`;
66
- }
67
- async function* lineResponseBodyChunks(response, messageId) {
68
- const body = response.body;
69
- if (!body) throw new RetryableLineMediaFetchError(`LINE media response for message ${messageId} had no body`);
70
- const reader = body.getReader();
71
- let completed = false;
72
- try {
73
- while (true) {
74
- let chunk;
75
- try {
76
- chunk = await reader.read();
77
- } catch (err) {
78
- throw new RetryableLineMediaFetchError(`LINE media response stream failed for message ${messageId}`, { cause: err });
79
- }
80
- if (chunk.done) {
81
- completed = true;
82
- return;
83
- }
84
- if (chunk.value.byteLength > 0) yield chunk.value;
85
- }
86
- } finally {
87
- if (!completed) await reader.cancel().catch(() => void 0);
88
- try {
89
- reader.releaseLock();
90
- } catch {}
91
- }
92
- }
93
- async function fetchLineContentWhenReady(messageId, channelAccessToken) {
94
- const controller = new AbortController();
95
- const deadline = setTimeout(() => controller.abort(), CONTENT_READY_TIMEOUT_MS);
96
- deadline.unref();
97
- try {
98
- for (let attempt = 0; attempt < CONTENT_READY_MAX_ATTEMPTS; attempt++) {
99
- const response = await fetchWithRuntimeDispatcherOrMockedGlobal(lineContentUrl(messageId), {
100
- headers: { Authorization: `Bearer ${channelAccessToken}` },
101
- redirect: "error",
102
- signal: controller.signal
103
- });
104
- if (response.status === 200) {
105
- if (!response.body) throw new RetryableLineMediaFetchError(`LINE media response for message ${messageId} had no body`);
106
- return response;
107
- }
108
- await response.body?.cancel().catch(() => void 0);
109
- if (response.status !== 202) throw new MediaFetchError("http_error", `LINE media download failed for message ${messageId} (HTTP ${response.status})`, { status: response.status });
110
- if (attempt < CONTENT_READY_MAX_ATTEMPTS - 1) await setTimeout$1(contentBackoffDelayMs(attempt), void 0, { signal: controller.signal });
111
- }
112
- } catch (err) {
113
- if (controller.signal.aborted) throw new RetryableLineMediaFetchError(`LINE media for message ${messageId} did not become ready within ${CONTENT_READY_TIMEOUT_MS / 1e3} seconds`, { cause: err });
114
- if (err instanceof MediaFetchError) throw err;
115
- throw new RetryableLineMediaFetchError(`LINE media download failed for message ${messageId}`, { cause: err });
116
- } finally {
117
- clearTimeout(deadline);
118
- }
119
- throw new MediaFetchError("http_error", `LINE media for message ${messageId} was still preparing (HTTP 202) after ${CONTENT_READY_MAX_ATTEMPTS} attempts`, { status: 202 });
120
- }
121
- function isRetryableLineInboundMediaError(err) {
122
- if (err instanceof RetryableLineMediaFetchError) return true;
123
- if (!(err instanceof MediaFetchError)) return false;
124
- if (err.code === "http_error") return err.status === 202 || err.status === 408 || err.status === 429 || typeof err.status === "number" && err.status >= 500;
125
- return false;
126
- }
127
- async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10 * 1024 * 1024, options) {
128
- const response = await fetchLineContentWhenReady(messageId, channelAccessToken);
129
- let saved;
130
- try {
131
- saved = await saveMediaStream(lineResponseBodyChunks(response, messageId), response.headers.get("content-type") ?? void 0, "inbound", maxBytes, options?.originalFilename);
132
- } catch (err) {
133
- await response.body?.cancel().catch(() => void 0);
134
- throw err;
135
- }
45
+ async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10485760, options) {
46
+ const response = await new messagingApi.MessagingApiBlobClient({ channelAccessToken }).getMessageContent(messageId);
47
+ const saved = await saveMediaStream(response, void 0, "inbound", maxBytes, options?.originalFilename);
136
48
  logVerbose(`line: persisted media ${messageId} to ${saved.path} (${saved.size} bytes)`);
137
49
  return {
138
50
  path: saved.path,
@@ -142,23 +54,15 @@ async function downloadLineMedia(messageId, channelAccessToken, maxBytes = 10 *
142
54
  }
143
55
  //#endregion
144
56
  //#region extensions/line/src/auto-reply-delivery.ts
145
- function toLineDeliveryError(error) {
146
- return error instanceof Error ? error : new Error("LINE message send failed", { cause: error });
147
- }
148
- function getLineHttpError(error) {
149
- if (error instanceof HTTPFetchError) return error;
150
- return error instanceof Error && error.cause instanceof HTTPFetchError ? error.cause : void 0;
151
- }
152
57
  function markLineVisibleDeliveryError(error) {
153
- const deliveryError = toLineDeliveryError(error);
154
- if (Object.isExtensible(deliveryError)) {
155
- Object.assign(deliveryError, {
58
+ if (error instanceof Error && Object.isExtensible(error)) {
59
+ Object.assign(error, {
156
60
  sentBeforeError: true,
157
61
  visibleReplySent: true
158
62
  });
159
- return deliveryError;
63
+ return error;
160
64
  }
161
- const visibleError = new Error("LINE message send failed", { cause: deliveryError });
65
+ const visibleError = new Error("LINE rich or media message send failed", { cause: error });
162
66
  Object.assign(visibleError, {
163
67
  sentBeforeError: true,
164
68
  visibleReplySent: true
@@ -168,37 +72,12 @@ function markLineVisibleDeliveryError(error) {
168
72
  async function deliverLineAutoReply(params) {
169
73
  const { payload, lineData, replyToken, accountId, to, textLimit, deps } = params;
170
74
  let replyTokenUsed = params.replyTokenUsed;
171
- let visibleReplySent = false;
172
- const sendVisible = async (send) => {
173
- try {
174
- const result = await send();
175
- visibleReplySent = true;
176
- return result;
177
- } catch (error) {
178
- if (visibleReplySent) throw markLineVisibleDeliveryError(error);
179
- throw error;
180
- }
181
- };
182
- const replyVisible = (...args) => sendVisible(() => deps.replyMessageLine(...args));
183
- const failedPushSegments = /* @__PURE__ */ new WeakMap();
184
- const pushLineMessages = async (messages, allowFailedBatchTextRecovery, externalTail = []) => {
75
+ const pushLineMessages = async (messages) => {
185
76
  if (messages.length === 0) return;
186
- for (let i = 0; i < messages.length; i += 5) {
187
- const batch = messages.slice(i, i + 5);
188
- try {
189
- await sendVisible(() => deps.pushMessagesLine(to, batch, {
190
- cfg: params.cfg,
191
- accountId
192
- }));
193
- } catch (error) {
194
- if (typeof error === "object" && error !== null) failedPushSegments.set(error, {
195
- allowFailedBatchTextRecovery,
196
- failedBatch: batch,
197
- unattemptedTail: [...messages.slice(i + batch.length), ...externalTail]
198
- });
199
- throw error;
200
- }
201
- }
77
+ for (let i = 0; i < messages.length; i += 5) await deps.pushMessagesLine(to, messages.slice(i, i + 5), {
78
+ cfg: params.cfg,
79
+ accountId
80
+ });
202
81
  };
203
82
  const sendLineMessages = async (messages, allowReplyToken) => {
204
83
  if (messages.length === 0) return;
@@ -206,19 +85,18 @@ async function deliverLineAutoReply(params) {
206
85
  if (allowReplyToken && replyToken && !replyTokenUsed) {
207
86
  const replyBatch = remaining.slice(0, 5);
208
87
  try {
209
- await replyVisible(replyToken, replyBatch, {
88
+ await deps.replyMessageLine(replyToken, replyBatch, {
210
89
  cfg: params.cfg,
211
90
  accountId
212
91
  });
213
92
  } catch (err) {
214
93
  deps.onReplyError?.(err);
215
- await pushLineMessages(replyBatch, getLineHttpError(err)?.status === 400, remaining.slice(replyBatch.length));
216
- } finally {
217
- replyTokenUsed = true;
94
+ await pushLineMessages(replyBatch);
218
95
  }
96
+ replyTokenUsed = true;
219
97
  remaining = remaining.slice(replyBatch.length);
220
98
  }
221
- if (remaining.length > 0) await pushLineMessages(remaining, true);
99
+ if (remaining.length > 0) await pushLineMessages(remaining);
222
100
  };
223
101
  const richMessages = [];
224
102
  const hasQuickReplies = Boolean(lineData.quickReplies?.length);
@@ -228,80 +106,80 @@ async function deliverLineAutoReply(params) {
228
106
  if (templateMsg) richMessages.push(templateMsg);
229
107
  }
230
108
  if (lineData.location) richMessages.push(deps.createLocationMessage(lineData.location));
231
- const visibleText = payload.text ? sanitizeAssistantVisibleText(payload.text) : "";
232
- const processed = visibleText ? deps.processLineMessage(visibleText) : {
109
+ const processed = payload.text ? deps.processLineMessage(payload.text) : {
233
110
  text: "",
234
111
  flexMessages: []
235
112
  };
236
113
  for (const flexMsg of processed.flexMessages) richMessages.push(deps.createFlexMessage(truncateUtf16Safe(flexMsg.altText, 400), flexMsg.contents));
237
114
  const chunks = processed.text ? deps.chunkMarkdownText(processed.text, textLimit) : [];
238
- const mediaUrls = resolveSendableOutboundReplyParts(payload).mediaUrls;
239
- const mediaOpts = {
240
- mediaKind: hasLineSpecificMediaOptions(lineData) ? lineData.mediaKind : "image",
241
- previewImageUrl: lineData.previewImageUrl,
242
- durationMs: lineData.durationMs,
243
- trackingId: lineData.trackingId
244
- };
245
- const mediaMessages = [];
246
- let deliveryError;
247
- for (const rawUrl of mediaUrls) {
248
- const url = rawUrl?.trim();
249
- if (!url) continue;
250
- try {
251
- mediaMessages.push(await deps.buildMediaMessage(url, mediaOpts, to));
115
+ const mediaMessages = resolveSendableOutboundReplyParts(payload).mediaUrls.map((url) => url?.trim()).filter((url) => Boolean(url)).map((url) => deps.createImageMessage(url));
116
+ if (chunks.length > 0) {
117
+ const hasRichOrMedia = richMessages.length > 0 || mediaMessages.length > 0;
118
+ const sendRichBeforeText = hasQuickReplies && hasRichOrMedia;
119
+ let richMediaError;
120
+ if (sendRichBeforeText) try {
121
+ await sendLineMessages([...richMessages, ...mediaMessages], false);
252
122
  } catch (err) {
253
- deliveryError ??= err;
123
+ richMediaError = err;
254
124
  }
255
- }
256
- const textMessages = chunks.map((text) => ({
257
- type: "text",
258
- text
259
- }));
260
- const richMediaMessages = [...richMessages, ...mediaMessages];
261
- if (hasQuickReplies && textMessages.length === 0 && richMediaMessages.length === 0) textMessages.push({
262
- type: "text",
263
- text: buildLineQuickReplyFallbackText(lineData.quickReplies)
264
- });
265
- if (hasQuickReplies) {
266
- const targetMessages = textMessages.length > 0 ? textMessages : richMediaMessages;
267
- const lastIndex = targetMessages.length - 1;
268
- targetMessages[lastIndex] = {
269
- ...expectDefined(targetMessages[lastIndex], "last LINE auto-reply message"),
270
- quickReply: deps.createQuickReplyItems(lineData.quickReplies)
271
- };
272
- }
273
- const messages = hasQuickReplies ? [...richMediaMessages, ...textMessages] : [...textMessages, ...richMediaMessages];
274
- try {
275
- await sendLineMessages(messages, true);
276
- } catch (err) {
277
- deliveryError ??= err;
278
- const failedSegment = typeof err === "object" && err !== null ? failedPushSegments.get(err) : void 0;
279
- const httpError = getLineHttpError(err);
280
- const retryCandidates = failedSegment ? [...failedSegment.allowFailedBatchTextRecovery ? failedSegment.failedBatch : [], ...failedSegment.unattemptedTail] : [];
281
- const retryTextMessages = retryCandidates.filter((message) => message.type === "text");
282
- const quickRepliesNeedCarrier = hasQuickReplies && retryCandidates.some((message) => "quickReply" in message);
283
- const retryMessages = retryTextMessages.length > 0 ? retryTextMessages : quickRepliesNeedCarrier ? [{
284
- type: "text",
285
- text: buildLineQuickReplyFallbackText(lineData.quickReplies),
286
- quickReply: deps.createQuickReplyItems(lineData.quickReplies)
287
- }] : [];
288
- if (retryMessages.length > 0 && failedSegment?.failedBatch.some((message) => message.type !== "text") && httpError?.status === 400) try {
289
- await sendLineMessages(retryMessages, false);
290
- } catch {}
291
- }
292
- if (deliveryError !== void 0) {
293
- if (!visibleReplySent) throw toLineDeliveryError(deliveryError);
294
- return {
125
+ const { replyTokenUsed: nextReplyTokenUsed } = await deps.sendLineReplyChunks({
126
+ to,
127
+ chunks,
128
+ quickReplies: lineData.quickReplies,
129
+ replyToken,
130
+ replyTokenUsed,
131
+ cfg: params.cfg,
132
+ accountId,
133
+ replyMessageLine: deps.replyMessageLine,
134
+ pushMessageLine: deps.pushMessageLine,
135
+ pushTextMessageWithQuickReplies: deps.pushTextMessageWithQuickReplies,
136
+ createTextMessageWithQuickReplies: deps.createTextMessageWithQuickReplies
137
+ });
138
+ replyTokenUsed = nextReplyTokenUsed;
139
+ if (!sendRichBeforeText) try {
140
+ await sendLineMessages(richMessages, false);
141
+ if (mediaMessages.length > 0) await sendLineMessages(mediaMessages, false);
142
+ } catch (err) {
143
+ richMediaError = err;
144
+ }
145
+ if (richMediaError !== void 0) return {
295
146
  status: "partial",
296
147
  replyTokenUsed,
297
- visibleReplySent: true,
298
- error: markLineVisibleDeliveryError(deliveryError)
148
+ error: markLineVisibleDeliveryError(richMediaError)
299
149
  };
150
+ } else {
151
+ const combined = [...richMessages, ...mediaMessages];
152
+ if (hasQuickReplies && combined.length === 0) {
153
+ const { replyTokenUsed: nextReplyTokenUsed } = await deps.sendLineReplyChunks({
154
+ to,
155
+ chunks: [buildLineQuickReplyFallbackText(lineData.quickReplies)],
156
+ quickReplies: lineData.quickReplies,
157
+ replyToken,
158
+ replyTokenUsed,
159
+ cfg: params.cfg,
160
+ accountId,
161
+ replyMessageLine: deps.replyMessageLine,
162
+ pushMessageLine: deps.pushMessageLine,
163
+ pushTextMessageWithQuickReplies: deps.pushTextMessageWithQuickReplies,
164
+ createTextMessageWithQuickReplies: deps.createTextMessageWithQuickReplies,
165
+ onReplyError: deps.onReplyError
166
+ });
167
+ replyTokenUsed = nextReplyTokenUsed;
168
+ } else {
169
+ if (hasQuickReplies && combined.length > 0) {
170
+ const quickReply = deps.createQuickReplyItems(lineData.quickReplies);
171
+ const targetIndex = replyToken && !replyTokenUsed ? Math.min(4, combined.length - 1) : combined.length - 1;
172
+ combined[targetIndex] = {
173
+ ...combined[targetIndex],
174
+ quickReply
175
+ };
176
+ }
177
+ await sendLineMessages(combined, true);
178
+ }
300
179
  }
301
180
  return {
302
181
  status: "delivered",
303
- replyTokenUsed,
304
- visibleReplySent
182
+ replyTokenUsed
305
183
  };
306
184
  }
307
185
  //#endregion
@@ -431,27 +309,48 @@ function extractMessageText(message) {
431
309
  }
432
310
  return "";
433
311
  }
434
- function extractNativeMediaKind(message) {
312
+ function extractMediaPlaceholder(message) {
435
313
  switch (message.type) {
436
- case "image": return "image";
437
- case "video": return "video";
438
- case "audio": return "audio";
439
- case "file": return "document";
440
- default: return;
314
+ case "image": return "<media:image>";
315
+ case "video": return "<media:video>";
316
+ case "audio": return "<media:audio>";
317
+ case "file": return "<media:document>";
318
+ default: return "";
441
319
  }
442
320
  }
321
+ function resolveLineConversationLabel(params) {
322
+ return params.isGroup ? params.groupId ? `group:${params.groupId}` : params.roomId ? `room:${params.roomId}` : "unknown-group" : params.senderLabel;
323
+ }
324
+ function resolveLineAddresses(params) {
325
+ const fromAddress = params.isGroup ? params.groupId ? `line:group:${params.groupId}` : params.roomId ? `line:room:${params.roomId}` : `line:${params.peerId}` : `line:${params.userId ?? params.peerId}`;
326
+ return {
327
+ fromAddress,
328
+ toAddress: params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`,
329
+ originatingTo: params.isGroup ? fromAddress : `line:${params.userId ?? params.peerId}`
330
+ };
331
+ }
443
332
  async function finalizeLineInboundContext(params) {
333
+ const { fromAddress, toAddress, originatingTo } = resolveLineAddresses({
334
+ isGroup: params.source.isGroup,
335
+ groupId: params.source.groupId,
336
+ roomId: params.source.roomId,
337
+ userId: params.source.userId,
338
+ peerId: params.source.peerId
339
+ });
444
340
  const senderId = params.source.userId ?? "unknown";
445
341
  const senderLabel = params.source.userId ? `user:${params.source.userId}` : "unknown";
446
- const conversationLabel = params.source.isGroup ? params.source.groupId ? `group:${params.source.groupId}` : params.source.roomId ? `room:${params.source.roomId}` : "unknown-group" : senderLabel;
447
- const address = params.source.groupId ? `line:group:${params.source.groupId}` : params.source.roomId ? `line:room:${params.source.roomId}` : `line:${params.source.userId ?? params.source.peerId}`;
342
+ const conversationLabel = resolveLineConversationLabel({
343
+ isGroup: params.source.isGroup,
344
+ groupId: params.source.groupId,
345
+ roomId: params.source.roomId,
346
+ senderLabel
347
+ });
448
348
  const { storePath, envelopeOptions, previousTimestamp } = resolveInboundSessionEnvelopeContext({
449
349
  cfg: params.cfg,
450
350
  agentId: params.route.agentId,
451
351
  sessionKey: params.route.sessionKey
452
352
  });
453
353
  const agentBody = params.agentBody ?? params.rawBody;
454
- const media = toInboundMediaFacts(params.media);
455
354
  const body = formatInboundEnvelope({
456
355
  channel: "LINE",
457
356
  from: conversationLabel,
@@ -462,45 +361,38 @@ async function finalizeLineInboundContext(params) {
462
361
  previousTimestamp,
463
362
  envelope: envelopeOptions
464
363
  });
465
- const ctxPayload = buildChannelInboundEventContext({
466
- channel: "line",
467
- accountId: params.route.accountId,
468
- messageId: params.messageSid,
469
- timestamp: params.timestamp,
470
- from: address,
471
- sender: { id: senderId },
472
- conversation: {
473
- kind: params.source.isGroup ? "group" : "direct",
474
- id: params.source.peerId,
475
- label: conversationLabel
476
- },
477
- route: {
478
- agentId: params.route.agentId,
479
- dmScope: params.route.dmScope,
480
- accountId: params.route.accountId,
481
- routeSessionKey: params.route.sessionKey
482
- },
483
- reply: {
484
- to: address,
485
- originatingTo: address
486
- },
487
- message: {
488
- body,
489
- bodyForAgent: agentBody,
490
- rawBody: params.rawBody,
491
- commandBody: params.rawBody,
492
- inboundHistory: params.inboundHistory
493
- },
494
- access: { commands: { authorized: params.commandAuthorized } },
495
- media,
496
- extra: {
497
- ...params.locationContext,
498
- GroupSubject: params.source.isGroup ? params.source.groupId ?? params.source.roomId : void 0,
499
- GroupSystemPrompt: params.source.isGroup ? normalizeOptionalString(resolveLineGroupConfigEntry(params.account.config.groups, {
500
- groupId: params.source.groupId,
501
- roomId: params.source.roomId
502
- })?.systemPrompt) : void 0
503
- }
364
+ const ctxPayload = finalizeInboundContext({
365
+ Body: body,
366
+ BodyForAgent: agentBody,
367
+ RawBody: params.rawBody,
368
+ CommandBody: params.rawBody,
369
+ From: fromAddress,
370
+ To: toAddress,
371
+ SessionKey: params.route.sessionKey,
372
+ AccountId: params.route.accountId,
373
+ ChatType: params.source.isGroup ? "group" : "direct",
374
+ ConversationLabel: conversationLabel,
375
+ GroupSubject: params.source.isGroup ? params.source.groupId ?? params.source.roomId : void 0,
376
+ SenderId: senderId,
377
+ Provider: "line",
378
+ Surface: "line",
379
+ MessageSid: params.messageSid,
380
+ Timestamp: params.timestamp,
381
+ MediaPath: params.media.firstPath,
382
+ MediaType: params.media.firstContentType,
383
+ MediaUrl: params.media.firstPath,
384
+ MediaPaths: params.media.paths,
385
+ MediaUrls: params.media.paths,
386
+ MediaTypes: params.media.types,
387
+ ...params.locationContext,
388
+ CommandAuthorized: params.commandAuthorized,
389
+ OriginatingChannel: "line",
390
+ OriginatingTo: originatingTo,
391
+ GroupSystemPrompt: params.source.isGroup ? normalizeOptionalString(resolveLineGroupConfigEntry(params.account.config.groups, {
392
+ groupId: params.source.groupId,
393
+ roomId: params.source.roomId
394
+ })?.systemPrompt) : void 0,
395
+ InboundHistory: params.inboundHistory
504
396
  });
505
397
  const pinnedMainDmOwner = !params.source.isGroup ? resolvePinnedMainDmOwnerFromAllowlist({
506
398
  dmScope: params.cfg.session?.dmScope,
@@ -514,7 +406,8 @@ async function finalizeLineInboundContext(params) {
514
406
  if (shouldLogVerbose()) {
515
407
  const preview = truncateUtf16Safe(body, 200).replace(/\n/g, "\\n");
516
408
  const mediaInfo = params.verboseLog.kind === "inbound" && (params.verboseLog.mediaCount ?? 0) > 1 ? ` mediaCount=${params.verboseLog.mediaCount}` : "";
517
- logVerbose(`${params.verboseLog.kind === "inbound" ? "line inbound" : "line postback"}: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`);
409
+ const label = params.verboseLog.kind === "inbound" ? "line inbound" : "line postback";
410
+ logVerbose(`${label}: from=${ctxPayload.From} len=${body.length}${mediaInfo} preview="${preview}"`);
518
411
  }
519
412
  return {
520
413
  ctxPayload,
@@ -543,7 +436,7 @@ async function finalizeLineInboundContext(params) {
543
436
  };
544
437
  }
545
438
  async function buildLineMessageContext(params) {
546
- const { event, allMedia, mediaUnavailable, cfg, account, commandAuthorized, inboundHistory } = params;
439
+ const { event, allMedia, mediaUnavailable, cfg, account, commandAuthorized, groupHistories, historyLimit } = params;
547
440
  const source = event.source;
548
441
  const { userId, groupId, roomId, isGroup, peerId, route } = await resolveLineInboundRoute({
549
442
  source,
@@ -554,17 +447,15 @@ async function buildLineMessageContext(params) {
554
447
  const messageId = message.id;
555
448
  const timestamp = event.timestamp;
556
449
  const textContent = extractMessageText(message);
557
- const nativeMediaKind = extractNativeMediaKind(message);
558
- const mediaFacts = allMedia.length > 0 ? allMedia.map((media) => ({
559
- ...media,
560
- kind: nativeMediaKind
561
- })) : nativeMediaKind ? [{ kind: nativeMediaKind }] : [];
562
- const rawBody = textContent;
450
+ const placeholder = extractMediaPlaceholder(message);
451
+ let rawBody = textContent || placeholder;
452
+ if (!rawBody && allMedia.length > 0) rawBody = `<media:image>${allMedia.length > 1 ? ` (${allMedia.length} images)` : ""}`;
563
453
  const agentBody = mediaUnavailable ? formatInboundMediaUnavailableText({
564
454
  body: rawBody,
455
+ mediaPlaceholder: placeholder,
565
456
  notice: "[line attachment unavailable]"
566
457
  }) : rawBody;
567
- if (!agentBody && mediaFacts.length === 0) return null;
458
+ if (!agentBody && allMedia.length === 0) return null;
568
459
  let locationContext;
569
460
  if (message.type === "location") {
570
461
  const loc = message;
@@ -575,6 +466,11 @@ async function buildLineMessageContext(params) {
575
466
  address: loc.address
576
467
  });
577
468
  }
469
+ const historyKey = isGroup ? peerId : void 0;
470
+ const inboundHistory = historyKey && groupHistories && (historyLimit ?? 0) > 0 ? createChannelHistoryWindow({ historyMap: groupHistories }).buildInboundHistory({
471
+ historyKey,
472
+ limit: historyLimit ?? 0
473
+ }) : void 0;
578
474
  const finalized = await finalizeLineInboundContext({
579
475
  cfg,
580
476
  account,
@@ -592,7 +488,12 @@ async function buildLineMessageContext(params) {
592
488
  timestamp,
593
489
  messageSid: messageId,
594
490
  commandAuthorized,
595
- media: mediaFacts,
491
+ media: {
492
+ firstPath: allMedia[0]?.path,
493
+ firstContentType: allMedia[0]?.contentType,
494
+ paths: allMedia.length > 0 ? allMedia.map((m) => m.path) : void 0,
495
+ types: allMedia.length > 0 ? allMedia.map((m) => m.contentType).filter(Boolean) : void 0
496
+ },
596
497
  locationContext,
597
498
  verboseLog: {
598
499
  kind: "inbound",
@@ -622,14 +523,18 @@ async function buildLinePostbackContext(params) {
622
523
  account
623
524
  });
624
525
  const timestamp = event.timestamp;
625
- const rawData = event.postback?.data?.trim() ?? "";
626
- if (!rawData) return null;
627
- let rawBody = rawData;
628
- if (rawData.includes("line.action=")) {
629
- const searchParams = new URLSearchParams(rawData);
526
+ const rawBody = event.postback?.data?.trim() ?? "";
527
+ if (!rawBody) return null;
528
+ let agentBody = rawBody;
529
+ if (rawBody.includes("line.action=")) {
530
+ const searchParams = new URLSearchParams(rawBody);
630
531
  const action = searchParams.get("line.action") ?? "";
631
532
  const device = searchParams.get("line.device");
632
- rawBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
533
+ agentBody = device ? `line action ${action} device ${device}` : `line action ${action}`;
534
+ }
535
+ for (const [key, value] of Object.entries(event.postback.params ?? {}).toSorted(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
536
+ const picked = normalizeOptionalString(value);
537
+ if (picked) agentBody += ` ${key}=${picked}`;
633
538
  }
634
539
  const messageSid = event.replyToken ? `postback:${event.replyToken}` : `postback:${timestamp}`;
635
540
  const finalized = await finalizeLineInboundContext({
@@ -645,10 +550,16 @@ async function buildLinePostbackContext(params) {
645
550
  peerId
646
551
  },
647
552
  rawBody,
553
+ agentBody,
648
554
  timestamp,
649
555
  messageSid,
650
556
  commandAuthorized,
651
- media: [],
557
+ media: {
558
+ firstPath: "",
559
+ firstContentType: void 0,
560
+ paths: void 0,
561
+ types: void 0
562
+ },
652
563
  verboseLog: { kind: "postback" }
653
564
  });
654
565
  return {
@@ -665,41 +576,6 @@ async function buildLinePostbackContext(params) {
665
576
  };
666
577
  }
667
578
  //#endregion
668
- //#region extensions/line/src/group-history.ts
669
- const reservedEntries = /* @__PURE__ */ new WeakSet();
670
- function reserveLineGroupHistory(historyMap, historyKey, limit) {
671
- if (!historyMap || !historyKey || limit <= 0) return {
672
- commit: () => {},
673
- release: () => {}
674
- };
675
- const consumedEntries = (historyMap.get(historyKey) ?? []).filter((entry) => !reservedEntries.has(entry));
676
- for (const entry of consumedEntries) reservedEntries.add(entry);
677
- const inboundHistory = createChannelHistoryWindow({ historyMap: /* @__PURE__ */ new Map([[historyKey, consumedEntries]]) }).buildInboundHistory({
678
- historyKey,
679
- limit
680
- });
681
- let settled = false;
682
- const settle = () => {
683
- if (settled) return false;
684
- settled = true;
685
- for (const entry of consumedEntries) reservedEntries.delete(entry);
686
- return true;
687
- };
688
- return {
689
- inboundHistory,
690
- commit: () => {
691
- if (!settle() || consumedEntries.length === 0) return;
692
- const consumed = new Set(consumedEntries);
693
- const kept = (historyMap.get(historyKey) ?? []).filter((entry) => !consumed.has(entry));
694
- if (kept.length > 0) historyMap.set(historyKey, kept);
695
- else historyMap.delete(historyKey);
696
- },
697
- release: () => {
698
- settle();
699
- }
700
- };
701
- }
702
- //#endregion
703
579
  //#region extensions/line/src/bot-handlers.ts
704
580
  const LINE_DOWNLOADABLE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
705
581
  "image",
@@ -710,9 +586,63 @@ const LINE_DOWNLOADABLE_MESSAGE_TYPES = /* @__PURE__ */ new Set([
710
586
  function isDownloadableLineMessageType(messageType) {
711
587
  return LINE_DOWNLOADABLE_MESSAGE_TYPES.has(messageType);
712
588
  }
589
+ const LINE_WEBHOOK_REPLAY_WINDOW_MS = 6e5;
590
+ const LINE_WEBHOOK_REPLAY_MAX_ENTRIES = 4096;
713
591
  function normalizeLineIngressEntry(value) {
714
592
  return normalizeLineAllowEntry(value) || null;
715
593
  }
594
+ var LineRetryableWebhookError = class extends Error {
595
+ constructor(message, options) {
596
+ super(message, options);
597
+ this.name = "LineRetryableWebhookError";
598
+ }
599
+ };
600
+ function createLineWebhookReplayCache() {
601
+ return createClaimableDedupe({
602
+ ttlMs: LINE_WEBHOOK_REPLAY_WINDOW_MS,
603
+ memoryMaxSize: LINE_WEBHOOK_REPLAY_MAX_ENTRIES
604
+ });
605
+ }
606
+ function buildLineWebhookReplayKey(event, accountId) {
607
+ if (event.type === "message") {
608
+ const messageId = event.message?.id?.trim();
609
+ if (messageId) return {
610
+ key: `${accountId}|message:${messageId}`,
611
+ eventId: `message:${messageId}`
612
+ };
613
+ }
614
+ const eventId = event.webhookEventId?.trim();
615
+ if (!eventId) return null;
616
+ const source = event.source;
617
+ const sourceId = source?.type === "group" ? `group:${source.groupId ?? ""}` : source?.type === "room" ? `room:${source.roomId ?? ""}` : `user:${source?.userId ?? ""}`;
618
+ return {
619
+ key: `${accountId}|${event.type}|${sourceId}|${eventId}`,
620
+ eventId: `event:${eventId}`
621
+ };
622
+ }
623
+ function getLineReplayCandidate(event, context) {
624
+ const replay = buildLineWebhookReplayKey(event, context.account.accountId);
625
+ const cache = context.replayCache;
626
+ if (!replay || !cache) return null;
627
+ return {
628
+ key: replay.key,
629
+ eventId: replay.eventId,
630
+ cache
631
+ };
632
+ }
633
+ async function claimLineReplayEvent(candidate) {
634
+ const claim = await candidate.cache.claim(candidate.key);
635
+ if (claim.kind === "claimed") return { skip: false };
636
+ if (claim.kind === "inflight") {
637
+ logVerbose(`line: skipped in-flight replayed webhook event ${candidate.eventId}`);
638
+ return {
639
+ skip: true,
640
+ inFlightResult: claim.pending.then(() => void 0)
641
+ };
642
+ }
643
+ logVerbose(`line: skipped replayed webhook event ${candidate.eventId}`);
644
+ return { skip: true };
645
+ }
716
646
  function resolveLineGroupConfig(params) {
717
647
  return resolveLineGroupConfigEntry(params.config.groups, {
718
648
  groupId: params.groupId,
@@ -787,7 +717,7 @@ async function shouldProcessLineEvent(event, context) {
787
717
  defaultGroupPolicy: resolveDefaultGroupPolicy(cfg)
788
718
  });
789
719
  const groupPolicy = runtimeGroupPolicy === "disabled" ? "disabled" : groupConfig?.allowFrom !== void 0 ? "allowlist" : runtimeGroupPolicy;
790
- const groupAllowFrom = normalizeStringEntries(firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom));
720
+ const groupAllowFrom = normalizeStringEntries(firstDefined(groupConfig?.allowFrom, account.config.groupAllowFrom, account.config.allowFrom?.length ? account.config.allowFrom : void 0));
791
721
  const mentionFacts = (() => {
792
722
  if (!isGroup || event.type !== "message") return {
793
723
  canDetectMention: false,
@@ -852,7 +782,7 @@ async function shouldProcessLineEvent(event, context) {
852
782
  allowFrom: normalizeStringEntries(account.config.allowFrom),
853
783
  groupAllowFrom,
854
784
  command: {
855
- hasControlCommand: hasControlCommand(rawText, cfg),
785
+ hasControlCommand: shouldComputeCommandAuthorized(rawText, cfg),
856
786
  groupOwnerAllowFrom: "none"
857
787
  }
858
788
  });
@@ -950,42 +880,42 @@ async function handleMessageEvent(event, context) {
950
880
  });
951
881
  return;
952
882
  }
953
- const groupHistoryKey = isGroup ? groupId ?? roomId : void 0;
954
- const historyReservation = reserveLineGroupHistory(context.groupHistories, groupHistoryKey, context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT);
955
- try {
956
- const allMedia = [];
957
- let mediaUnavailable = false;
958
- if (isDownloadableLineMessageType(message.type)) try {
959
- const originalFilename = message.type === "file" ? normalizeOptionalString(message.fileName) : void 0;
960
- const media = await downloadLineMedia(message.id, account.channelAccessToken, mediaMaxBytes, { originalFilename });
961
- allMedia.push({
962
- path: media.path,
963
- contentType: media.contentType
964
- });
965
- } catch (err) {
966
- if (isRetryableLineInboundMediaError(err)) throw err;
967
- mediaUnavailable = true;
968
- const errMsg = String(err);
969
- if (errMsg.includes("exceeds") && errMsg.includes("limit")) logVerbose(`line: media exceeds size limit for message ${message.id}`);
970
- else runtime.error?.(danger(`line: failed to download media: ${errMsg}`));
971
- }
972
- const messageContext = await buildLineMessageContext({
973
- event,
974
- allMedia,
975
- mediaUnavailable,
976
- cfg,
977
- account,
978
- commandAuthorized: decision.commandAccess.authorized,
979
- inboundHistory: historyReservation.inboundHistory
883
+ const allMedia = [];
884
+ let mediaUnavailable = false;
885
+ if (isDownloadableLineMessageType(message.type)) try {
886
+ const originalFilename = message.type === "file" ? normalizeOptionalString(message.fileName) : void 0;
887
+ const media = await downloadLineMedia(message.id, account.channelAccessToken, mediaMaxBytes, { originalFilename });
888
+ allMedia.push({
889
+ path: media.path,
890
+ contentType: media.contentType
891
+ });
892
+ } catch (err) {
893
+ mediaUnavailable = true;
894
+ const errMsg = String(err);
895
+ if (errMsg.includes("exceeds") && errMsg.includes("limit")) logVerbose(`line: media exceeds size limit for message ${message.id}`);
896
+ else runtime.error?.(danger(`line: failed to download media: ${errMsg}`));
897
+ }
898
+ const messageContext = await buildLineMessageContext({
899
+ event,
900
+ allMedia,
901
+ mediaUnavailable,
902
+ cfg,
903
+ account,
904
+ commandAuthorized: decision.commandAccess.authorized,
905
+ groupHistories: context.groupHistories,
906
+ historyLimit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
907
+ });
908
+ if (!messageContext) {
909
+ logVerbose("line: skipping empty message");
910
+ return;
911
+ }
912
+ await processMessage(messageContext);
913
+ if (isGroup && context.groupHistories) {
914
+ const historyKey = groupId ?? roomId;
915
+ if (historyKey && context.groupHistories.has(historyKey)) createChannelHistoryWindow({ historyMap: context.groupHistories }).clear({
916
+ historyKey,
917
+ limit: context.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
980
918
  });
981
- if (!messageContext) {
982
- logVerbose("line: skipping empty message");
983
- return;
984
- }
985
- await processMessage(messageContext, context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {});
986
- historyReservation.commit();
987
- } finally {
988
- historyReservation.release();
989
919
  }
990
920
  }
991
921
  async function handleFollowEvent(event, _context) {
@@ -1016,41 +946,56 @@ async function handlePostbackEvent(event, context) {
1016
946
  commandAuthorized: decision.commandAccess.authorized
1017
947
  });
1018
948
  if (!postbackContext) return;
1019
- await context.processMessage(postbackContext, context.turnAdoptionLifecycle ? { turnAdoptionLifecycle: context.turnAdoptionLifecycle } : {});
949
+ await context.processMessage(postbackContext);
1020
950
  }
1021
951
  async function handleLineWebhookEvents(events, context) {
1022
952
  let firstError;
1023
- for (const event of events) try {
1024
- await handleLineWebhookEvent(event, context);
1025
- } catch (err) {
1026
- context.runtime.error?.(danger(`line: event handler failed: ${String(err)}`));
1027
- firstError ??= err;
953
+ for (const event of events) {
954
+ const replayCandidate = getLineReplayCandidate(event, context);
955
+ const replaySkip = replayCandidate ? await claimLineReplayEvent(replayCandidate) : null;
956
+ if (replaySkip?.skip) {
957
+ if (replaySkip.inFlightResult) try {
958
+ await replaySkip.inFlightResult;
959
+ } catch (err) {
960
+ context.runtime.error?.(danger(`line: replayed in-flight event failed: ${String(err)}`));
961
+ firstError ??= err;
962
+ }
963
+ continue;
964
+ }
965
+ try {
966
+ switch (event.type) {
967
+ case "message":
968
+ await handleMessageEvent(event, context);
969
+ break;
970
+ case "follow":
971
+ await handleFollowEvent(event, context);
972
+ break;
973
+ case "unfollow":
974
+ await handleUnfollowEvent(event, context);
975
+ break;
976
+ case "join":
977
+ await handleJoinEvent(event, context);
978
+ break;
979
+ case "leave":
980
+ await handleLeaveEvent(event, context);
981
+ break;
982
+ case "postback":
983
+ await handlePostbackEvent(event, context);
984
+ break;
985
+ default: logVerbose(`line: unhandled event type: ${event.type}`);
986
+ }
987
+ if (replayCandidate) await replayCandidate.cache.commit(replayCandidate.key);
988
+ } catch (err) {
989
+ if (replayCandidate) {
990
+ if (err instanceof LineRetryableWebhookError) replayCandidate.cache.release(replayCandidate.key, { error: err });
991
+ else await replayCandidate.cache.commit(replayCandidate.key);
992
+ }
993
+ context.runtime.error?.(danger(`line: event handler failed: ${String(err)}`));
994
+ firstError ??= err;
995
+ }
1028
996
  }
1029
997
  if (firstError) throw toLintErrorObject(firstError, "Non-Error thrown");
1030
998
  }
1031
- async function handleLineWebhookEvent(event, context) {
1032
- switch (event.type) {
1033
- case "message":
1034
- await handleMessageEvent(event, context);
1035
- break;
1036
- case "follow":
1037
- await handleFollowEvent(event, context);
1038
- break;
1039
- case "unfollow":
1040
- await handleUnfollowEvent(event, context);
1041
- break;
1042
- case "join":
1043
- await handleJoinEvent(event, context);
1044
- break;
1045
- case "leave":
1046
- await handleLeaveEvent(event, context);
1047
- break;
1048
- case "postback":
1049
- await handlePostbackEvent(event, context);
1050
- break;
1051
- default: logVerbose(`line: unhandled event type: ${event.type}`);
1052
- }
1053
- }
1054
999
  function toLintErrorObject(value, fallbackMessage) {
1055
1000
  if (value instanceof Error) return value;
1056
1001
  if (typeof value === "string") return new Error(value);
@@ -1059,257 +1004,6 @@ function toLintErrorObject(value, fallbackMessage) {
1059
1004
  return error;
1060
1005
  }
1061
1006
  //#endregion
1062
- //#region extensions/line/src/webhook-spool.ts
1063
- const LINE_WEBHOOK_SPOOL_VERSION = 1;
1064
- const LINE_WEBHOOK_DRAIN_INTERVAL_MS = 500;
1065
- const LINE_WEBHOOK_MAX_CONCURRENT_DELIVERIES = 8;
1066
- const LINE_WEBHOOK_DRAIN_SCAN_LIMIT = 100;
1067
- const LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS = 5e3;
1068
- const LINE_WEBHOOK_TOMBSTONE_TTL_MS = 720 * 60 * 6e4;
1069
- const LINE_WEBHOOK_TOMBSTONE_MAX_ENTRIES = 4096;
1070
- var LineWebhookPayloadError = class extends Error {
1071
- constructor(message, options) {
1072
- super(message, options);
1073
- this.name = "LineWebhookPayloadError";
1074
- }
1075
- };
1076
- var LineWebhookTerminalDeliveryError = class extends Error {
1077
- constructor(message, options) {
1078
- super(message, options);
1079
- this.reason = "delivery-side-effects-committed";
1080
- this.name = "LineWebhookTerminalDeliveryError";
1081
- }
1082
- };
1083
- function nonEmptyString(value) {
1084
- return typeof value === "string" && value.trim() ? value.trim() : null;
1085
- }
1086
- /** Message ids preserve the shipped replay-guard keyspace; other events use LINE's delivery id. */
1087
- function eventIdFor(event) {
1088
- if (!event || typeof event !== "object") throw new LineWebhookPayloadError("LINE webhook event must be an object.");
1089
- const candidate = event;
1090
- if (candidate.type === "message") {
1091
- const messageId = nonEmptyString(candidate.message?.id);
1092
- if (messageId) return `message:${messageId}`;
1093
- }
1094
- const webhookEventId = nonEmptyString(candidate.webhookEventId);
1095
- if (webhookEventId) return `event:${webhookEventId}`;
1096
- throw new LineWebhookPayloadError("LINE webhook event is missing a stable delivery id.");
1097
- }
1098
- function laneKeyFor(event, eventId) {
1099
- if (!event || typeof event !== "object") return eventId;
1100
- const source = event.source;
1101
- if (source?.type === "group") {
1102
- const groupId = nonEmptyString(source.groupId);
1103
- if (groupId) return `group:${groupId}`;
1104
- }
1105
- if (source?.type === "room") {
1106
- const roomId = nonEmptyString(source.roomId);
1107
- if (roomId) return `room:${roomId}`;
1108
- }
1109
- if (source?.type === "user") {
1110
- const userId = nonEmptyString(source.userId);
1111
- if (userId) return `user:${userId}`;
1112
- }
1113
- return eventId;
1114
- }
1115
- function parseStoredEvent(rawEvent) {
1116
- let event;
1117
- try {
1118
- event = JSON.parse(rawEvent);
1119
- } catch (error) {
1120
- throw new LineWebhookPayloadError("LINE webhook event JSON is invalid.", { cause: error });
1121
- }
1122
- return event;
1123
- }
1124
- function errorText(error) {
1125
- return error instanceof Error ? error.message : String(error);
1126
- }
1127
- function isLineAuthenticationFailure(error) {
1128
- if (!error || typeof error !== "object") return false;
1129
- const status = error.status;
1130
- return status === 401 || status === 403;
1131
- }
1132
- async function waitForActiveDeliveriesBeforeDispose(activeDeliveries) {
1133
- let timeout;
1134
- try {
1135
- return await Promise.race([Promise.allSettled(activeDeliveries).then(() => true), new Promise((resolve) => {
1136
- timeout = setTimeout(() => resolve(false), LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS);
1137
- timeout.unref?.();
1138
- })]);
1139
- } finally {
1140
- if (timeout) clearTimeout(timeout);
1141
- }
1142
- }
1143
- function createLineWebhookSpool(options) {
1144
- const queue = options.queue ?? getLineRuntime().state.openChannelIngressQueue({ accountId: options.accountId });
1145
- const activeDeliveries = /* @__PURE__ */ new Set();
1146
- const deferredClaims = /* @__PURE__ */ new Map();
1147
- let acceptsDeferredClaims = true;
1148
- const monitor = createChannelIngressMonitor({
1149
- queue,
1150
- inspect: ({ event }) => {
1151
- const eventId = eventIdFor(event);
1152
- return {
1153
- eventId,
1154
- laneKey: laneKeyFor(event, eventId)
1155
- };
1156
- },
1157
- payload: {
1158
- version: LINE_WEBHOOK_SPOOL_VERSION,
1159
- serialize: ({ event, destination }) => ({
1160
- rawEvent: JSON.stringify(event),
1161
- destination
1162
- }),
1163
- deserialize: ({ rawEvent, destination }) => ({
1164
- event: parseStoredEvent(rawEvent),
1165
- destination
1166
- }),
1167
- encode: ({ version, body }) => ({
1168
- version,
1169
- rawEvent: body.rawEvent,
1170
- destination: body.destination
1171
- }),
1172
- decode: (payload) => {
1173
- if (typeof payload.rawEvent !== "string" || typeof payload.destination !== "string") throw new LineWebhookPayloadError("LINE webhook spool payload is invalid.");
1174
- return {
1175
- version: payload.version,
1176
- body: {
1177
- rawEvent: payload.rawEvent,
1178
- destination: payload.destination
1179
- }
1180
- };
1181
- },
1182
- createClaimError: (kind) => new LineWebhookPayloadError(kind === "invalid-version" ? "LINE webhook spool payload is invalid." : "LINE webhook event identity changed after durable admission.")
1183
- },
1184
- deliver: async ({ event, destination }, lifecycle, claim) => {
1185
- const boundLifecycle = bindIngressLifecycleToReplyOptions(lifecycle).turnAdoptionLifecycle;
1186
- let handedOff = false;
1187
- let resolveDeferredClaim;
1188
- const deferredClaim = new Promise((resolve) => {
1189
- resolveDeferredClaim = resolve;
1190
- });
1191
- let deferredClaimSettled = false;
1192
- const settleDeferredClaim = () => {
1193
- if (deferredClaimSettled) return;
1194
- deferredClaimSettled = true;
1195
- if (deferredClaims.get(claim.id) === deferredClaim) deferredClaims.delete(claim.id);
1196
- resolveDeferredClaim();
1197
- };
1198
- const delivery = options.deliver(event, destination, { turnAdoptionLifecycle: {
1199
- ...boundLifecycle,
1200
- onAdopted: async () => {
1201
- handedOff = true;
1202
- try {
1203
- await boundLifecycle.onAdopted();
1204
- } finally {
1205
- settleDeferredClaim();
1206
- }
1207
- },
1208
- onDeferred: () => {
1209
- handedOff = true;
1210
- if (!acceptsDeferredClaims) {
1211
- settleDeferredClaim();
1212
- Promise.resolve().then(() => boundLifecycle.onAbandoned()).catch((error) => {
1213
- options.runtime.error?.(danger(`line: failed to abandon a late webhook delivery: ${errorText(error)}`));
1214
- });
1215
- return;
1216
- }
1217
- if (!deferredClaimSettled) deferredClaims.set(claim.id, deferredClaim);
1218
- boundLifecycle.onDeferred();
1219
- },
1220
- onAbandoned: async () => {
1221
- handedOff = true;
1222
- try {
1223
- await boundLifecycle.onAbandoned();
1224
- } finally {
1225
- settleDeferredClaim();
1226
- }
1227
- }
1228
- } });
1229
- activeDeliveries.add(delivery);
1230
- try {
1231
- await delivery;
1232
- } finally {
1233
- activeDeliveries.delete(delivery);
1234
- }
1235
- if (stopTask && !handedOff) return {
1236
- kind: "failed-retryable",
1237
- error: /* @__PURE__ */ new Error("LINE webhook spool stopped before delivery handoff.")
1238
- };
1239
- },
1240
- pollIntervalMs: LINE_WEBHOOK_DRAIN_INTERVAL_MS,
1241
- retention: {
1242
- pruneIntervalMs: 0,
1243
- completedTtlMs: LINE_WEBHOOK_TOMBSTONE_TTL_MS,
1244
- completedMaxEntries: LINE_WEBHOOK_TOMBSTONE_MAX_ENTRIES,
1245
- failedTtlMs: LINE_WEBHOOK_TOMBSTONE_TTL_MS,
1246
- failedMaxEntries: LINE_WEBHOOK_TOMBSTONE_MAX_ENTRIES
1247
- },
1248
- appendRetryDelaysMs: [0],
1249
- waitForDeliveryIdleBeforeRepump: false,
1250
- waitForDeliveryIdleOnStop: false,
1251
- runPumpTask: runDetachedWebhookWork,
1252
- admissionMode: "durable-after-stop",
1253
- drain: {
1254
- adoptionStallTimeoutMs: DEFAULT_INGRESS_ADOPTION_STALL_MS,
1255
- orderBy: "received",
1256
- scanLimit: LINE_WEBHOOK_DRAIN_SCAN_LIMIT,
1257
- startLimit: LINE_WEBHOOK_MAX_CONCURRENT_DELIVERIES,
1258
- retryPolicy: {
1259
- maxAttempts: DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
1260
- deadLetterMinAgeMs: 0
1261
- },
1262
- resolveNonRetryableFailure: (error) => {
1263
- if (error instanceof LineWebhookPayloadError) return {
1264
- reason: "invalid-event",
1265
- message: error.message
1266
- };
1267
- if (error instanceof LineWebhookTerminalDeliveryError) return {
1268
- reason: error.reason,
1269
- message: error.message
1270
- };
1271
- if (isLineAuthenticationFailure(error)) return {
1272
- reason: "authentication-failed",
1273
- message: errorText(error)
1274
- };
1275
- return null;
1276
- },
1277
- onLog: (message) => options.runtime.error?.(danger(`line: ${message}`))
1278
- },
1279
- createStoppedError: () => /* @__PURE__ */ new Error("LINE webhook spool is stopped."),
1280
- onError: (error) => options.runtime.error?.(danger(`line: webhook spool drain failed: ${errorText(error)}`))
1281
- });
1282
- let stopTask;
1283
- return {
1284
- accept: async (body) => {
1285
- const events = body.events ?? [];
1286
- if (events.length === 0) return;
1287
- await monitor.admitBatch(events.map((event) => ({
1288
- event,
1289
- destination: body.destination ?? ""
1290
- })), { receivedAt: Date.now() });
1291
- },
1292
- start: () => {
1293
- if (!stopTask) monitor.start();
1294
- },
1295
- stop: () => {
1296
- stopTask ??= (async () => {
1297
- await monitor.pause();
1298
- try {
1299
- const deliveriesSettled = await waitForActiveDeliveriesBeforeDispose(activeDeliveries);
1300
- if (!deliveriesSettled) options.runtime.log(warn(`line: timed out after ${LINE_WEBHOOK_ACTIVE_DELIVERY_STOP_GRACE_MS}ms waiting for active webhook deliveries; releasing drain ownership`));
1301
- while (deferredClaims.size > 0) await Promise.allSettled(deferredClaims.values());
1302
- acceptsDeferredClaims = false;
1303
- if (deliveriesSettled) await monitor.waitForIdle();
1304
- } finally {
1305
- await monitor.stop();
1306
- }
1307
- })();
1308
- return stopTask;
1309
- }
1310
- };
1311
- }
1312
- //#endregion
1313
1007
  //#region extensions/line/src/bot.ts
1314
1008
  function createLineBot(opts) {
1315
1009
  const runtime = opts.runtime ?? createNonExitingRuntime();
@@ -1322,26 +1016,24 @@ function createLineBot(opts) {
1322
1016
  const processMessage = opts.onMessage ?? (async () => {
1323
1017
  logVerbose("line: no message handler configured");
1324
1018
  });
1019
+ const replayCache = createLineWebhookReplayCache();
1325
1020
  const groupHistories = /* @__PURE__ */ new Map();
1326
- const spool = createLineWebhookSpool({
1327
- accountId: account.accountId,
1328
- runtime,
1329
- deliver: async (event, _destination, control) => await handleLineWebhookEvents([event], {
1021
+ const handleWebhook = async (body) => {
1022
+ if (!body.events || body.events.length === 0) return;
1023
+ await handleLineWebhookEvents(body.events, {
1330
1024
  cfg,
1331
1025
  account,
1332
1026
  runtime,
1333
1027
  mediaMaxBytes,
1334
1028
  processMessage,
1335
- ...control.turnAdoptionLifecycle ? { turnAdoptionLifecycle: control.turnAdoptionLifecycle } : {},
1029
+ replayCache,
1336
1030
  groupHistories,
1337
1031
  historyLimit: cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT
1338
- })
1339
- });
1340
- spool.start();
1032
+ });
1033
+ };
1341
1034
  return {
1342
- handleWebhook: spool.accept,
1343
- account,
1344
- stop: spool.stop
1035
+ handleWebhook,
1036
+ account
1345
1037
  };
1346
1038
  }
1347
1039
  //#endregion
@@ -1359,9 +1051,63 @@ function resolveLineDurableReplyOptions(params) {
1359
1051
  return { to: params.to };
1360
1052
  }
1361
1053
  //#endregion
1054
+ //#region extensions/line/src/reply-chunks.ts
1055
+ async function sendLineReplyChunks(params) {
1056
+ const hasQuickReplies = Boolean(params.quickReplies?.length);
1057
+ let replyTokenUsed = Boolean(params.replyTokenUsed);
1058
+ if (params.chunks.length === 0) return { replyTokenUsed };
1059
+ if (params.replyToken && !replyTokenUsed) try {
1060
+ const replyBatch = params.chunks.slice(0, 5);
1061
+ const remaining = params.chunks.slice(replyBatch.length);
1062
+ const replyMessages = replyBatch.map((chunk) => ({
1063
+ type: "text",
1064
+ text: chunk
1065
+ }));
1066
+ if (hasQuickReplies && remaining.length === 0 && replyMessages.length > 0) {
1067
+ const lastIndex = replyMessages.length - 1;
1068
+ replyMessages[lastIndex] = params.createTextMessageWithQuickReplies(replyBatch[lastIndex], params.quickReplies);
1069
+ }
1070
+ await params.replyMessageLine(params.replyToken, replyMessages, {
1071
+ cfg: params.cfg,
1072
+ accountId: params.accountId
1073
+ });
1074
+ replyTokenUsed = true;
1075
+ for (let i = 0; i < remaining.length; i += 1) if (i === remaining.length - 1 && hasQuickReplies) await params.pushTextMessageWithQuickReplies(params.to, remaining[i], params.quickReplies, {
1076
+ cfg: params.cfg,
1077
+ accountId: params.accountId
1078
+ });
1079
+ else await params.pushMessageLine(params.to, remaining[i], {
1080
+ cfg: params.cfg,
1081
+ accountId: params.accountId
1082
+ });
1083
+ return { replyTokenUsed };
1084
+ } catch (err) {
1085
+ params.onReplyError?.(err);
1086
+ replyTokenUsed = true;
1087
+ }
1088
+ for (let i = 0; i < params.chunks.length; i += 1) if (i === params.chunks.length - 1 && hasQuickReplies) await params.pushTextMessageWithQuickReplies(params.to, params.chunks[i], params.quickReplies, {
1089
+ cfg: params.cfg,
1090
+ accountId: params.accountId
1091
+ });
1092
+ else await params.pushMessageLine(params.to, params.chunks[i], {
1093
+ cfg: params.cfg,
1094
+ accountId: params.accountId
1095
+ });
1096
+ return { replyTokenUsed };
1097
+ }
1098
+ //#endregion
1362
1099
  //#region extensions/line/src/signature.ts
1363
1100
  function validateLineSignature(body, signature, channelSecret) {
1364
- return safeEqualSecret(signature, crypto.createHmac("SHA256", channelSecret).update(body).digest("base64"));
1101
+ const hash = crypto.createHmac("SHA256", channelSecret).update(body).digest("base64");
1102
+ const hashBuffer = Buffer.from(hash);
1103
+ const signatureBuffer = Buffer.from(signature);
1104
+ const maxLen = Math.max(hashBuffer.length, signatureBuffer.length);
1105
+ const paddedHash = Buffer.alloc(maxLen);
1106
+ const paddedSig = Buffer.alloc(maxLen);
1107
+ hashBuffer.copy(paddedHash);
1108
+ signatureBuffer.copy(paddedSig);
1109
+ const timingResult = crypto.timingSafeEqual(paddedHash, paddedSig);
1110
+ return hashBuffer.length === signatureBuffer.length && timingResult;
1365
1111
  }
1366
1112
  //#endregion
1367
1113
  //#region extensions/line/src/webhook-utils.ts
@@ -1374,8 +1120,8 @@ function parseLineWebhookBody(rawBody) {
1374
1120
  }
1375
1121
  //#endregion
1376
1122
  //#region extensions/line/src/webhook-node.ts
1377
- const LINE_WEBHOOK_MAX_BODY_BYTES = 1024 * 1024;
1378
- const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES$1 = 64 * 1024;
1123
+ const LINE_WEBHOOK_MAX_BODY_BYTES = 1048576;
1124
+ const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES$1 = 65536;
1379
1125
  const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS$1 = 5e3;
1380
1126
  async function readLineWebhookRequestBody(req, maxBytes = LINE_WEBHOOK_MAX_BODY_BYTES, timeoutMs = LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS$1) {
1381
1127
  return await readRequestBodyWithLimit(req, {
@@ -1383,6 +1129,9 @@ async function readLineWebhookRequestBody(req, maxBytes = LINE_WEBHOOK_MAX_BODY_
1383
1129
  timeoutMs
1384
1130
  });
1385
1131
  }
1132
+ function logLineWebhookDispatchError(runtime, err) {
1133
+ runtime?.error?.(danger(`line webhook dispatch failed: ${String(err)}`));
1134
+ }
1386
1135
  function createLineNodeWebhookHandler(params) {
1387
1136
  const maxBodyBytes = params.maxBodyBytes ?? LINE_WEBHOOK_MAX_BODY_BYTES;
1388
1137
  const readBody = params.readBody ?? readLineWebhookRequestBody;
@@ -1405,6 +1154,7 @@ function createLineNodeWebhookHandler(params) {
1405
1154
  res.end(JSON.stringify({ error: "Method Not Allowed" }));
1406
1155
  return;
1407
1156
  }
1157
+ let receiveContext;
1408
1158
  try {
1409
1159
  const signatureHeader = req.headers["x-line-signature"];
1410
1160
  const signature = typeof signatureHeader === "string" ? signatureHeader.trim() : Array.isArray(signatureHeader) ? (signatureHeader[0] ?? "").trim() : "";
@@ -1431,14 +1181,24 @@ function createLineNodeWebhookHandler(params) {
1431
1181
  return;
1432
1182
  }
1433
1183
  params.onRequestAuthenticated?.();
1184
+ receiveContext = createMessageReceiveContext({
1185
+ id: `${Date.now()}:line:webhook`,
1186
+ channel: "line",
1187
+ message: body,
1188
+ ackPolicy: "after_receive_record",
1189
+ onAck: () => {
1190
+ res.statusCode = 200;
1191
+ res.setHeader("Content-Type", "application/json");
1192
+ res.end(JSON.stringify({ status: "ok" }));
1193
+ }
1194
+ });
1195
+ if (receiveContext.shouldAckAfter("receive_record")) await receiveContext.ack();
1434
1196
  if (body.events && body.events.length > 0) {
1435
1197
  logVerbose(`line: received ${body.events.length} webhook events`);
1436
- await params.bot.handleWebhook(body);
1198
+ Promise.resolve().then(() => params.bot.handleWebhook(body)).catch((err) => logLineWebhookDispatchError(params.runtime, err));
1437
1199
  }
1438
- res.statusCode = 200;
1439
- res.setHeader("Content-Type", "application/json");
1440
- res.end(JSON.stringify({ status: "ok" }));
1441
1200
  } catch (err) {
1201
+ await receiveContext?.nack(err);
1442
1202
  if (isRequestBodyLimitError$1(err, "PAYLOAD_TOO_LARGE")) {
1443
1203
  res.statusCode = 413;
1444
1204
  res.setHeader("Content-Type", "application/json");
@@ -1462,10 +1222,24 @@ function createLineNodeWebhookHandler(params) {
1462
1222
  }
1463
1223
  //#endregion
1464
1224
  //#region extensions/line/src/monitor.ts
1225
+ const runtimeState = /* @__PURE__ */ new Map();
1465
1226
  const lineWebhookInFlightLimiter = createWebhookInFlightLimiter();
1466
- const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 64 * 1024;
1227
+ const LINE_WEBHOOK_PREAUTH_MAX_BODY_BYTES = 65536;
1467
1228
  const LINE_WEBHOOK_PREAUTH_BODY_TIMEOUT_MS = 5e3;
1468
1229
  const lineWebhookTargets = /* @__PURE__ */ new Map();
1230
+ function recordChannelRuntimeState(params) {
1231
+ const key = `${params.channel}:${params.accountId}`;
1232
+ const existing = runtimeState.get(key) ?? {
1233
+ running: false,
1234
+ lastStartAt: null,
1235
+ lastStopAt: null,
1236
+ lastError: null
1237
+ };
1238
+ runtimeState.set(key, {
1239
+ ...existing,
1240
+ ...params.state
1241
+ });
1242
+ }
1469
1243
  function startLineLoadingKeepalive(params) {
1470
1244
  const intervalMs = params.intervalMs ?? 18e3;
1471
1245
  const loadingSeconds = params.loadingSeconds ?? 20;
@@ -1499,9 +1273,14 @@ async function monitorLineProvider(opts) {
1499
1273
  accountId,
1500
1274
  runtime,
1501
1275
  config,
1502
- onMessage: async (ctx, deliveryControl) => {
1276
+ onMessage: async (ctx) => {
1503
1277
  if (!ctx) return;
1504
1278
  const { ctxPayload, replyToken, route } = ctx;
1279
+ recordChannelRuntimeState({
1280
+ channel: "line",
1281
+ accountId: resolvedAccountId,
1282
+ state: { lastInboundAt: Date.now() }
1283
+ });
1505
1284
  const shouldShowLoading = Boolean(ctx.userId && !ctx.isGroup);
1506
1285
  const displayNamePromise = ctx.userId ? getUserDisplayName(ctx.userId, {
1507
1286
  cfg: config,
@@ -1512,24 +1291,16 @@ async function monitorLineProvider(opts) {
1512
1291
  userId: ctx.userId,
1513
1292
  accountId: ctx.accountId
1514
1293
  }) : null;
1515
- logVerbose(`line: received message from ${await displayNamePromise} (${ctxPayload.From})`);
1516
- let replyTokenUsed = false;
1517
- let turnAdopted = false;
1518
- const ingressLifecycle = deliveryControl.turnAdoptionLifecycle;
1294
+ const displayName = await displayNamePromise;
1295
+ logVerbose(`line: received message from ${displayName} (${ctxPayload.From})`);
1519
1296
  try {
1520
1297
  const textLimit = 5e3;
1521
- const turnResult = await getLineRuntime().channel.inbound.run({
1298
+ let replyTokenUsed = false;
1299
+ const core = getLineRuntime();
1300
+ const turnResult = await core.channel.inbound.run({
1522
1301
  channel: "line",
1523
1302
  accountId: route.accountId,
1524
1303
  raw: ctx,
1525
- turnAdoptionLifecycle: {
1526
- ...ingressLifecycle,
1527
- admission: "exclusive",
1528
- onAdopted: async () => {
1529
- await ingressLifecycle?.onAdopted();
1530
- turnAdopted = true;
1531
- }
1532
- },
1533
1304
  adapter: {
1534
1305
  ingest: () => ({
1535
1306
  id: ctxPayload.MessageSid ?? `${ctxPayload.From}:${Date.now()}`,
@@ -1539,14 +1310,14 @@ async function monitorLineProvider(opts) {
1539
1310
  cfg: config,
1540
1311
  channel: "line",
1541
1312
  accountId: route.accountId,
1542
- route: {
1543
- agentId: route.agentId,
1544
- sessionKey: route.sessionKey
1545
- },
1313
+ agentId: route.agentId,
1314
+ routeSessionKey: route.sessionKey,
1315
+ storePath: ctx.turn.storePath,
1546
1316
  ctxPayload,
1317
+ recordInboundSession: core.channel.session.recordInboundSession,
1318
+ dispatchReplyWithBufferedBlockDispatcher: core.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
1547
1319
  record: ctx.turn.record,
1548
1320
  replyPipeline: {},
1549
- ...ingressLifecycle?.abortSignal ? { replyOptions: { abortSignal: ingressLifecycle.abortSignal } } : {},
1550
1321
  delivery: {
1551
1322
  durable: (payload, info) => resolveLineDurableReplyOptions({
1552
1323
  payload,
@@ -1574,11 +1345,15 @@ async function monitorLineProvider(opts) {
1574
1345
  buildTemplateMessageFromPayload,
1575
1346
  processLineMessage,
1576
1347
  chunkMarkdownText,
1348
+ sendLineReplyChunks,
1577
1349
  replyMessageLine,
1350
+ pushMessageLine,
1351
+ pushTextMessageWithQuickReplies,
1578
1352
  createQuickReplyItems,
1353
+ createTextMessageWithQuickReplies,
1579
1354
  pushMessagesLine,
1580
1355
  createFlexMessage,
1581
- buildMediaMessage: buildLineMediaMessage,
1356
+ createImageMessage,
1582
1357
  createLocationMessage,
1583
1358
  onReplyError: (replyErr) => {
1584
1359
  logVerbose(`line: reply token failed, falling back to push: ${String(replyErr)}`);
@@ -1587,7 +1362,11 @@ async function monitorLineProvider(opts) {
1587
1362
  });
1588
1363
  replyTokenUsed = deliveryResult.replyTokenUsed;
1589
1364
  if (deliveryResult.status === "partial") throw deliveryResult.error;
1590
- return { visibleReplySent: deliveryResult.visibleReplySent };
1365
+ recordChannelRuntimeState({
1366
+ channel: "line",
1367
+ accountId: resolvedAccountId,
1368
+ state: { lastOutboundAt: Date.now() }
1369
+ });
1591
1370
  },
1592
1371
  onError: (err, info) => {
1593
1372
  runtime.error?.(danger(`line ${info.kind} reply failed: ${String(err)}`));
@@ -1596,11 +1375,21 @@ async function monitorLineProvider(opts) {
1596
1375
  })
1597
1376
  }
1598
1377
  });
1599
- if (!hasFinalInboundReplyDispatch(turnResult.dispatched ? turnResult.dispatchResult : void 0)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
1378
+ const dispatchResult = turnResult.dispatched ? turnResult.dispatchResult : void 0;
1379
+ if (!hasFinalInboundReplyDispatch(dispatchResult)) logVerbose(`line: no response generated for message from ${ctxPayload.From}`);
1600
1380
  } catch (err) {
1601
1381
  runtime.error?.(danger(`line: auto-reply failed: ${String(err)}`));
1602
- if (turnAdopted || replyTokenUsed) throw new LineWebhookTerminalDeliveryError("LINE delivery failed after consuming the event reply token.", { cause: err });
1603
- throw err;
1382
+ if (replyToken) try {
1383
+ await replyMessageLine(replyToken, [{
1384
+ type: "text",
1385
+ text: "Sorry, I encountered an error processing your message."
1386
+ }], {
1387
+ cfg: config,
1388
+ accountId: ctx.accountId
1389
+ });
1390
+ } catch (replyErr) {
1391
+ runtime.error?.(danger(`line: error reply failed: ${String(replyErr)}`));
1392
+ }
1604
1393
  } finally {
1605
1394
  stopLoading?.();
1606
1395
  }
@@ -1678,13 +1467,16 @@ async function monitorLineProvider(opts) {
1678
1467
  res.end(JSON.stringify({ error: "Invalid webhook payload" }));
1679
1468
  return;
1680
1469
  }
1681
- if (body.events && body.events.length > 0) {
1682
- logVerbose(`line: received ${body.events.length} webhook events`);
1683
- await match.target.bot.handleWebhook(body);
1684
- }
1470
+ requestLifecycle.release();
1685
1471
  res.statusCode = 200;
1686
1472
  res.setHeader("Content-Type", "application/json");
1687
1473
  res.end(JSON.stringify({ status: "ok" }));
1474
+ if (body.events && body.events.length > 0) {
1475
+ logVerbose(`line: received ${body.events.length} webhook events`);
1476
+ Promise.resolve().then(() => match.target.bot.handleWebhook(body)).catch((err) => {
1477
+ match.target.runtime.error?.(danger(`line webhook dispatch failed: ${String(err)}`));
1478
+ });
1479
+ }
1688
1480
  } catch (err) {
1689
1481
  if (isRequestBodyLimitError(err, "PAYLOAD_TOO_LARGE")) {
1690
1482
  res.statusCode = 413;
@@ -1710,31 +1502,41 @@ async function monitorLineProvider(opts) {
1710
1502
  }
1711
1503
  }
1712
1504
  });
1505
+ recordChannelRuntimeState({
1506
+ channel: "line",
1507
+ accountId: resolvedAccountId,
1508
+ state: {
1509
+ running: true,
1510
+ lastStartAt: Date.now()
1511
+ }
1512
+ });
1713
1513
  logVerbose(`line: registered webhook handler at ${normalizedPath}`);
1714
1514
  let stopped = false;
1715
- let stopPromise;
1716
1515
  const stopHandler = () => {
1717
- if (stopPromise) return stopPromise;
1718
- if (stopped) return Promise.resolve();
1516
+ if (stopped) return;
1719
1517
  stopped = true;
1720
1518
  logVerbose(`line: stopping provider for account ${resolvedAccountId}`);
1721
1519
  unregisterHttp();
1722
- stopPromise = bot.stop();
1723
- return stopPromise;
1520
+ recordChannelRuntimeState({
1521
+ channel: "line",
1522
+ accountId: resolvedAccountId,
1523
+ state: {
1524
+ running: false,
1525
+ lastStopAt: Date.now()
1526
+ }
1527
+ });
1724
1528
  };
1725
- const stopOnAbort = () => void stopHandler();
1726
- if (abortSignal?.aborted) await stopHandler();
1529
+ if (abortSignal?.aborted) stopHandler();
1727
1530
  else if (abortSignal) {
1728
- abortSignal.addEventListener("abort", stopOnAbort, { once: true });
1531
+ abortSignal.addEventListener("abort", stopHandler, { once: true });
1729
1532
  await waitForAbortSignal(abortSignal);
1730
- await stopHandler();
1731
1533
  }
1732
1534
  return {
1733
1535
  account: bot.account,
1734
1536
  handleWebhook: bot.handleWebhook,
1735
- stop: async () => {
1736
- await stopHandler();
1737
- abortSignal?.removeEventListener("abort", stopOnAbort);
1537
+ stop: () => {
1538
+ stopHandler();
1539
+ abortSignal?.removeEventListener("abort", stopHandler);
1738
1540
  }
1739
1541
  };
1740
1542
  }