@zhin.js/core 1.1.33 → 1.1.35

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 (380) hide show
  1. package/README.md +140 -61
  2. package/lib/adapter.d.ts +72 -23
  3. package/lib/adapter.js +222 -58
  4. package/lib/built/adapter-process.d.ts +20 -5
  5. package/lib/built/adapter-process.js +54 -7
  6. package/lib/built/agent-preset.d.ts +1 -2
  7. package/lib/built/agent-preset.js +1 -2
  8. package/lib/built/ai-access.d.ts +36 -0
  9. package/lib/built/ai-access.js +46 -0
  10. package/lib/built/ai-outbound/adapter-access.d.ts +3 -0
  11. package/lib/built/ai-outbound/adapter-access.js +9 -0
  12. package/lib/built/ai-outbound/extensions/onebot-keyboard.d.ts +2 -0
  13. package/lib/built/ai-outbound/extensions/onebot-keyboard.js +67 -0
  14. package/lib/built/ai-outbound/extensions/qq-message.d.ts +3 -0
  15. package/lib/built/ai-outbound/extensions/qq-message.js +23 -0
  16. package/lib/built/ai-outbound/index.d.ts +9 -0
  17. package/lib/built/ai-outbound/index.js +9 -0
  18. package/lib/built/ai-outbound/parse.d.ts +13 -0
  19. package/lib/built/ai-outbound/parse.js +104 -0
  20. package/lib/built/ai-outbound/plain-mention-rewrite.d.ts +6 -0
  21. package/lib/built/ai-outbound/plain-mention-rewrite.js +37 -0
  22. package/lib/built/ai-outbound/prompt.d.ts +11 -0
  23. package/lib/built/ai-outbound/prompt.js +44 -0
  24. package/lib/built/ai-outbound/resolve.d.ts +4 -0
  25. package/lib/built/ai-outbound/resolve.js +64 -0
  26. package/lib/built/ai-outbound/structured-detect.d.ts +3 -0
  27. package/lib/built/ai-outbound/structured-detect.js +6 -0
  28. package/lib/built/ai-outbound/types.d.ts +58 -0
  29. package/lib/built/ai-outbound/types.js +5 -0
  30. package/lib/built/ai-trigger.d.ts +17 -17
  31. package/lib/built/ai-trigger.js +65 -70
  32. package/lib/built/authorization.d.ts +10 -0
  33. package/lib/built/authorization.js +76 -0
  34. package/lib/built/command.d.ts +9 -5
  35. package/lib/built/command.js +20 -7
  36. package/lib/built/common-adapter-tools.d.ts +35 -29
  37. package/lib/built/common-adapter-tools.js +83 -67
  38. package/lib/built/component.d.ts +1 -3
  39. package/lib/built/component.js +2 -4
  40. package/lib/built/config.d.ts +4 -5
  41. package/lib/built/config.js +26 -9
  42. package/lib/built/connect-endpoint-instance.d.ts +17 -0
  43. package/lib/built/connect-endpoint-instance.js +42 -0
  44. package/lib/built/database.d.ts +5 -3
  45. package/lib/built/database.js +10 -4
  46. package/lib/built/dispatcher.d.ts +10 -8
  47. package/lib/built/dispatcher.js +31 -9
  48. package/lib/built/endpoint-commands.d.ts +3 -0
  49. package/lib/built/endpoint-commands.js +99 -0
  50. package/lib/built/endpoint-lifecycle-service.d.ts +28 -0
  51. package/lib/built/endpoint-lifecycle-service.js +242 -0
  52. package/lib/built/endpoint-lifecycle.d.ts +27 -0
  53. package/lib/built/endpoint-lifecycle.js +36 -0
  54. package/lib/built/endpoint-manager.d.ts +34 -0
  55. package/lib/built/endpoint-manager.js +1 -0
  56. package/lib/built/generated-qrcode.d.ts +24 -0
  57. package/lib/built/generated-qrcode.js +45 -0
  58. package/lib/built/generic-segment-mapper.d.ts +4 -0
  59. package/lib/built/generic-segment-mapper.js +190 -0
  60. package/lib/built/html-renderer-loader.d.ts +13 -0
  61. package/lib/built/html-renderer-loader.js +33 -0
  62. package/lib/built/html-segment-fallback.d.ts +9 -0
  63. package/lib/built/html-segment-fallback.js +49 -0
  64. package/lib/built/html-to-text.d.ts +4 -1
  65. package/lib/built/html-to-text.js +47 -16
  66. package/lib/built/inbound-pipeline.d.ts +37 -0
  67. package/lib/built/inbound-pipeline.js +63 -0
  68. package/lib/built/inbound-runner.d.ts +0 -1
  69. package/lib/built/inbound-runner.js +0 -3
  70. package/lib/built/interactive-segment-contract.d.ts +6 -0
  71. package/lib/built/interactive-segment-contract.js +9 -0
  72. package/lib/built/interactive-segments/action.d.ts +17 -0
  73. package/lib/built/interactive-segments/action.js +69 -0
  74. package/lib/built/interactive-segments/button-spec.d.ts +10 -0
  75. package/lib/built/interactive-segments/button-spec.js +21 -0
  76. package/lib/built/interactive-segments/fallback-store.d.ts +29 -0
  77. package/lib/built/interactive-segments/fallback-store.js +63 -0
  78. package/lib/built/interactive-segments/handlers.d.ts +16 -0
  79. package/lib/built/interactive-segments/handlers.js +74 -0
  80. package/lib/built/interactive-segments/index.d.ts +8 -0
  81. package/lib/built/interactive-segments/index.js +8 -0
  82. package/lib/built/interactive-segments/keyboard-segment.d.ts +11 -0
  83. package/lib/built/interactive-segments/keyboard-segment.js +12 -0
  84. package/lib/built/interactive-segments/onebot-keyboard.d.ts +7 -0
  85. package/lib/built/interactive-segments/onebot-keyboard.js +51 -0
  86. package/lib/built/interactive-segments/resolve.d.ts +15 -0
  87. package/lib/built/interactive-segments/resolve.js +110 -0
  88. package/lib/built/interactive-segments/types.d.ts +50 -0
  89. package/lib/built/interactive-segments/types.js +9 -0
  90. package/lib/built/introspection-format.d.ts +10 -0
  91. package/lib/built/introspection-format.js +28 -0
  92. package/lib/built/login-assist.d.ts +43 -9
  93. package/lib/built/login-assist.js +113 -11
  94. package/lib/built/management-command-guard.d.ts +15 -0
  95. package/lib/built/management-command-guard.js +25 -0
  96. package/lib/built/message-enrich.d.ts +38 -0
  97. package/lib/built/message-enrich.js +75 -0
  98. package/lib/built/message-filter.d.ts +4 -5
  99. package/lib/built/message-filter.js +5 -7
  100. package/lib/built/optional-peer-import.d.ts +3 -0
  101. package/lib/built/optional-peer-import.js +33 -0
  102. package/lib/built/outbound-media-contract.d.ts +22 -0
  103. package/lib/built/outbound-media-contract.js +10 -0
  104. package/lib/built/outbound-media-utils.d.ts +9 -0
  105. package/lib/built/outbound-media-utils.js +62 -0
  106. package/lib/built/permission.d.ts +3 -7
  107. package/lib/built/permission.js +39 -52
  108. package/lib/built/permit-check.d.ts +7 -0
  109. package/lib/built/permit-check.js +35 -0
  110. package/lib/built/permit-parse.d.ts +16 -0
  111. package/lib/built/permit-parse.js +32 -0
  112. package/lib/built/platform-permit.d.ts +20 -0
  113. package/lib/built/platform-permit.js +91 -0
  114. package/lib/built/queue-im-field-contract.d.ts +7 -2
  115. package/lib/built/queue-im-field-contract.js +13 -4
  116. package/lib/built/rich-segments/adapter-policies.d.ts +9 -0
  117. package/lib/built/rich-segments/adapter-policies.js +27 -0
  118. package/lib/built/rich-segments/base.d.ts +11 -0
  119. package/lib/built/rich-segments/base.js +24 -0
  120. package/lib/built/rich-segments/builtins.d.ts +4 -0
  121. package/lib/built/rich-segments/builtins.js +40 -0
  122. package/lib/built/rich-segments/capabilities.d.ts +6 -0
  123. package/lib/built/rich-segments/capabilities.js +38 -0
  124. package/lib/built/rich-segments/html-segment.d.ts +15 -0
  125. package/lib/built/rich-segments/html-segment.js +62 -0
  126. package/lib/built/rich-segments/index.d.ts +13 -0
  127. package/lib/built/rich-segments/index.js +13 -0
  128. package/lib/built/rich-segments/markdown-segment.d.ts +14 -0
  129. package/lib/built/rich-segments/markdown-segment.js +58 -0
  130. package/lib/built/rich-segments/markdown-to-text.d.ts +3 -0
  131. package/lib/built/rich-segments/markdown-to-text.js +27 -0
  132. package/lib/built/rich-segments/qrcode-segment.d.ts +18 -0
  133. package/lib/built/rich-segments/qrcode-segment.js +73 -0
  134. package/lib/built/rich-segments/registry.d.ts +16 -0
  135. package/lib/built/rich-segments/registry.js +52 -0
  136. package/lib/built/rich-segments/resolve.d.ts +5 -0
  137. package/lib/built/rich-segments/resolve.js +87 -0
  138. package/lib/built/rich-segments/tts-segment.d.ts +13 -0
  139. package/lib/built/rich-segments/tts-segment.js +51 -0
  140. package/lib/built/rich-segments/types.d.ts +79 -0
  141. package/lib/built/rich-segments/types.js +17 -0
  142. package/lib/built/roles.d.ts +4 -5
  143. package/lib/built/roles.js +4 -5
  144. package/lib/built/runtime-io.d.ts +0 -1
  145. package/lib/built/runtime-io.js +0 -1
  146. package/lib/built/schedule.d.ts +54 -0
  147. package/lib/built/schedule.js +112 -0
  148. package/lib/built/schema-endpoint-manager.d.ts +17 -0
  149. package/lib/built/schema-endpoint-manager.js +73 -0
  150. package/lib/built/schema-feature.d.ts +0 -1
  151. package/lib/built/schema-feature.js +1 -3
  152. package/lib/built/segment-contract/assert.d.ts +7 -0
  153. package/lib/built/segment-contract/assert.js +39 -0
  154. package/lib/built/segment-contract/delivery.d.ts +5 -0
  155. package/lib/built/segment-contract/delivery.js +40 -0
  156. package/lib/built/segment-contract/image.d.ts +5 -0
  157. package/lib/built/segment-contract/image.js +10 -0
  158. package/lib/built/segment-contract/index.d.ts +10 -0
  159. package/lib/built/segment-contract/index.js +9 -0
  160. package/lib/built/segment-contract/json-schema.d.ts +28 -0
  161. package/lib/built/segment-contract/json-schema.js +128 -0
  162. package/lib/built/segment-contract/media.d.ts +12 -0
  163. package/lib/built/segment-contract/media.js +22 -0
  164. package/lib/built/segment-contract/mention.d.ts +10 -0
  165. package/lib/built/segment-contract/mention.js +26 -0
  166. package/lib/built/segment-contract/preview.d.ts +3 -0
  167. package/lib/built/segment-contract/preview.js +159 -0
  168. package/lib/built/segment-contract/text.d.ts +7 -0
  169. package/lib/built/segment-contract/text.js +34 -0
  170. package/lib/built/segment-contract/types.d.ts +58 -0
  171. package/lib/built/segment-contract/types.js +1 -0
  172. package/lib/built/segment-contract/validate.d.ts +229 -0
  173. package/lib/built/segment-contract/validate.js +174 -0
  174. package/lib/built/skill.d.ts +0 -1
  175. package/lib/built/skill.js +0 -1
  176. package/lib/built/speech-loader.d.ts +14 -0
  177. package/lib/built/speech-loader.js +32 -0
  178. package/lib/built/tool.d.ts +14 -13
  179. package/lib/built/tool.js +30 -25
  180. package/lib/built/user-interaction.d.ts +30 -0
  181. package/lib/built/user-interaction.js +248 -0
  182. package/lib/command.d.ts +19 -11
  183. package/lib/command.js +42 -11
  184. package/lib/component.d.ts +0 -1
  185. package/lib/component.js +67 -14
  186. package/lib/endpoint-capabilities.d.ts +46 -0
  187. package/lib/endpoint-capabilities.js +55 -0
  188. package/lib/endpoint.d.ts +19 -0
  189. package/lib/endpoint.js +3 -0
  190. package/lib/errors.d.ts +2 -3
  191. package/lib/errors.js +4 -5
  192. package/lib/feature/adapter.d.ts +2 -0
  193. package/lib/feature/adapter.js +2 -0
  194. package/lib/feature/command.d.ts +2 -0
  195. package/lib/feature/command.js +2 -0
  196. package/lib/feature/component.d.ts +2 -0
  197. package/lib/feature/component.js +2 -0
  198. package/lib/feature/handler.d.ts +52 -0
  199. package/lib/feature/handler.js +26 -0
  200. package/lib/feature/middleware.d.ts +2 -0
  201. package/lib/feature/middleware.js +2 -0
  202. package/lib/host-plugin-registry.d.ts +8 -0
  203. package/lib/host-plugin-registry.js +12 -0
  204. package/lib/im-scene.d.ts +29 -0
  205. package/lib/im-scene.js +75 -0
  206. package/lib/im-session-id.d.ts +39 -0
  207. package/lib/im-session-id.js +47 -0
  208. package/lib/index.d.ts +59 -21
  209. package/lib/index.js +53 -28
  210. package/lib/jsx-dev-runtime.d.ts +0 -1
  211. package/lib/jsx-dev-runtime.js +0 -1
  212. package/lib/jsx-runtime.d.ts +1 -1
  213. package/lib/jsx-runtime.js +0 -1
  214. package/lib/jsx.d.ts +19 -22
  215. package/lib/jsx.js +0 -1
  216. package/lib/message.d.ts +16 -10
  217. package/lib/message.js +16 -13
  218. package/lib/models/system-log.d.ts +0 -1
  219. package/lib/models/system-log.js +0 -1
  220. package/lib/models/user.d.ts +0 -1
  221. package/lib/models/user.js +0 -1
  222. package/lib/notice.d.ts +16 -59
  223. package/lib/notice.js +1 -4
  224. package/lib/plugin-context.d.ts +27 -0
  225. package/lib/plugin-context.js +64 -0
  226. package/lib/plugin-runtime/im/contracts.d.ts +191 -0
  227. package/lib/plugin-runtime/im/contracts.js +169 -0
  228. package/lib/plugin-runtime/im/im-runtime.d.ts +177 -0
  229. package/lib/plugin-runtime/im/im-runtime.js +1204 -0
  230. package/lib/plugin-runtime/im/index.d.ts +9 -0
  231. package/lib/plugin-runtime/im/index.js +9 -0
  232. package/lib/plugin-runtime/im/interactive.d.ts +26 -0
  233. package/lib/plugin-runtime/im/interactive.js +46 -0
  234. package/lib/plugin-runtime/im/login-assist-host.d.ts +6 -0
  235. package/lib/plugin-runtime/im/login-assist-host.js +6 -0
  236. package/lib/plugin-runtime/im/message-bus.d.ts +38 -0
  237. package/lib/plugin-runtime/im/message-bus.js +40 -0
  238. package/lib/plugin-runtime/im/message-dispatcher.d.ts +18 -0
  239. package/lib/plugin-runtime/im/message-dispatcher.js +107 -0
  240. package/lib/plugin-runtime/im/outbound-renderer.d.ts +7 -0
  241. package/lib/plugin-runtime/im/outbound-renderer.js +70 -0
  242. package/lib/plugin-runtime/im/outbound-segments.d.ts +72 -0
  243. package/lib/plugin-runtime/im/outbound-segments.js +220 -0
  244. package/lib/plugin-runtime/im/public-api.d.ts +6 -0
  245. package/lib/plugin-runtime/im/public-api.js +6 -0
  246. package/lib/plugin-runtime/im/service-tokens.d.ts +18 -0
  247. package/lib/plugin-runtime/im/service-tokens.js +13 -0
  248. package/lib/plugin.d.ts +34 -24
  249. package/lib/plugin.js +119 -224
  250. package/lib/request.d.ts +17 -61
  251. package/lib/request.js +0 -4
  252. package/lib/{prompt.d.ts → schema-interaction.d.ts} +6 -7
  253. package/lib/{prompt.js → schema-interaction.js} +20 -17
  254. package/lib/side-event/base.d.ts +52 -0
  255. package/lib/side-event/base.js +30 -0
  256. package/lib/side-event/dispatch.d.ts +15 -0
  257. package/lib/side-event/dispatch.js +140 -0
  258. package/lib/side-event/index.d.ts +4 -0
  259. package/lib/side-event/index.js +4 -0
  260. package/lib/side-event/normalize.d.ts +42 -0
  261. package/lib/side-event/normalize.js +121 -0
  262. package/lib/side-event/types.d.ts +23 -0
  263. package/lib/side-event/types.js +55 -0
  264. package/lib/system-event.d.ts +15 -0
  265. package/lib/system-event.js +7 -0
  266. package/lib/tool-zod.d.ts +18 -3
  267. package/lib/tool-zod.js +140 -54
  268. package/lib/types.d.ts +44 -85
  269. package/lib/types.js +0 -1
  270. package/lib/utils.d.ts +68 -1
  271. package/lib/utils.js +73 -11
  272. package/package.json +98 -10
  273. package/lib/adapter.d.ts.map +0 -1
  274. package/lib/adapter.js.map +0 -1
  275. package/lib/agent-prompt.d.ts +0 -37
  276. package/lib/agent-prompt.d.ts.map +0 -1
  277. package/lib/agent-prompt.js +0 -2
  278. package/lib/agent-prompt.js.map +0 -1
  279. package/lib/bot.d.ts +0 -38
  280. package/lib/bot.d.ts.map +0 -1
  281. package/lib/bot.js +0 -2
  282. package/lib/bot.js.map +0 -1
  283. package/lib/built/adapter-process.d.ts.map +0 -1
  284. package/lib/built/adapter-process.js.map +0 -1
  285. package/lib/built/agent-preset.d.ts.map +0 -1
  286. package/lib/built/agent-preset.js.map +0 -1
  287. package/lib/built/ai-trigger.d.ts.map +0 -1
  288. package/lib/built/ai-trigger.js.map +0 -1
  289. package/lib/built/command.d.ts.map +0 -1
  290. package/lib/built/command.js.map +0 -1
  291. package/lib/built/common-adapter-tools.d.ts.map +0 -1
  292. package/lib/built/common-adapter-tools.js.map +0 -1
  293. package/lib/built/component.d.ts.map +0 -1
  294. package/lib/built/component.js.map +0 -1
  295. package/lib/built/config.d.ts.map +0 -1
  296. package/lib/built/config.js.map +0 -1
  297. package/lib/built/cron.d.ts +0 -75
  298. package/lib/built/cron.d.ts.map +0 -1
  299. package/lib/built/cron.js +0 -122
  300. package/lib/built/cron.js.map +0 -1
  301. package/lib/built/database.d.ts.map +0 -1
  302. package/lib/built/database.js.map +0 -1
  303. package/lib/built/dispatcher.d.ts.map +0 -1
  304. package/lib/built/dispatcher.js.map +0 -1
  305. package/lib/built/html-to-text.d.ts.map +0 -1
  306. package/lib/built/html-to-text.js.map +0 -1
  307. package/lib/built/inbound-runner.d.ts.map +0 -1
  308. package/lib/built/inbound-runner.js.map +0 -1
  309. package/lib/built/login-assist.d.ts.map +0 -1
  310. package/lib/built/login-assist.js.map +0 -1
  311. package/lib/built/message-filter.d.ts.map +0 -1
  312. package/lib/built/message-filter.js.map +0 -1
  313. package/lib/built/permission.d.ts.map +0 -1
  314. package/lib/built/permission.js.map +0 -1
  315. package/lib/built/prepend-quote-context.d.ts +0 -33
  316. package/lib/built/prepend-quote-context.d.ts.map +0 -1
  317. package/lib/built/prepend-quote-context.js +0 -129
  318. package/lib/built/prepend-quote-context.js.map +0 -1
  319. package/lib/built/queue-im-field-contract.d.ts.map +0 -1
  320. package/lib/built/queue-im-field-contract.js.map +0 -1
  321. package/lib/built/roles.d.ts.map +0 -1
  322. package/lib/built/roles.js.map +0 -1
  323. package/lib/built/runtime-io.d.ts.map +0 -1
  324. package/lib/built/runtime-io.js.map +0 -1
  325. package/lib/built/schema-feature.d.ts.map +0 -1
  326. package/lib/built/schema-feature.js.map +0 -1
  327. package/lib/built/skill.d.ts.map +0 -1
  328. package/lib/built/skill.js.map +0 -1
  329. package/lib/built/tool.d.ts.map +0 -1
  330. package/lib/built/tool.js.map +0 -1
  331. package/lib/command.d.ts.map +0 -1
  332. package/lib/command.js.map +0 -1
  333. package/lib/component.d.ts.map +0 -1
  334. package/lib/component.js.map +0 -1
  335. package/lib/errors.d.ts.map +0 -1
  336. package/lib/errors.js.map +0 -1
  337. package/lib/index.d.ts.map +0 -1
  338. package/lib/index.js.map +0 -1
  339. package/lib/jsx-dev-runtime.d.ts.map +0 -1
  340. package/lib/jsx-dev-runtime.js.map +0 -1
  341. package/lib/jsx-runtime.d.ts.map +0 -1
  342. package/lib/jsx-runtime.js.map +0 -1
  343. package/lib/jsx.d.ts.map +0 -1
  344. package/lib/jsx.js.map +0 -1
  345. package/lib/message-quote.d.ts +0 -11
  346. package/lib/message-quote.d.ts.map +0 -1
  347. package/lib/message-quote.js +0 -91
  348. package/lib/message-quote.js.map +0 -1
  349. package/lib/message.d.ts.map +0 -1
  350. package/lib/message.js.map +0 -1
  351. package/lib/models/system-log.d.ts.map +0 -1
  352. package/lib/models/system-log.js.map +0 -1
  353. package/lib/models/user.d.ts.map +0 -1
  354. package/lib/models/user.js.map +0 -1
  355. package/lib/notice.d.ts.map +0 -1
  356. package/lib/notice.js.map +0 -1
  357. package/lib/plugin.d.ts.map +0 -1
  358. package/lib/plugin.js.map +0 -1
  359. package/lib/prompt.d.ts.map +0 -1
  360. package/lib/prompt.js.map +0 -1
  361. package/lib/request.d.ts.map +0 -1
  362. package/lib/request.js.map +0 -1
  363. package/lib/scheduler/scheduler.d.ts +0 -49
  364. package/lib/scheduler/scheduler.d.ts.map +0 -1
  365. package/lib/scheduler/scheduler.js +0 -352
  366. package/lib/scheduler/scheduler.js.map +0 -1
  367. package/lib/scheduler/types.d.ts +0 -71
  368. package/lib/scheduler/types.d.ts.map +0 -1
  369. package/lib/scheduler/types.js +0 -8
  370. package/lib/scheduler/types.js.map +0 -1
  371. package/lib/tool-zod.d.ts.map +0 -1
  372. package/lib/tool-zod.js.map +0 -1
  373. package/lib/types-generator.d.ts +0 -6
  374. package/lib/types-generator.d.ts.map +0 -1
  375. package/lib/types-generator.js +0 -72
  376. package/lib/types-generator.js.map +0 -1
  377. package/lib/types.d.ts.map +0 -1
  378. package/lib/types.js.map +0 -1
  379. package/lib/utils.d.ts.map +0 -1
  380. package/lib/utils.js.map +0 -1
@@ -0,0 +1,1204 @@
1
+ import { createToken, generationAdmissionBinder, htmlRendererToken, } from '@zhin.js/plugin-runtime';
2
+ import { AsyncLocalStorage } from 'node:async_hooks';
3
+ import { createPermissionHost, permissionHostToken } from '@zhin.js/permission';
4
+ import { MessageBus, messageBusToken } from './message-bus.js';
5
+ import { adapterFeatureId, isAdapterIndex, resolveEndpointManagement, endpointEventGatewayToken, } from '@zhin.js/adapter';
6
+ import { MemoryConversationEventStore, conversationRefKey, messageRefKey, } from '@zhin.js/im-contract';
7
+ import { segmentsToPlainText } from '../../built/segment-contract/text.js';
8
+ import { isMiddlewareIndex, middlewareFeatureId } from '@zhin.js/middleware';
9
+ import { isHandlerIndex, handlerFeatureId } from '../../feature/handler.js';
10
+ import { formatCompact, getLogger, truncatePreview } from '@zhin.js/logger';
11
+ import { Message, createOutboundEnvelope, } from './contracts.js';
12
+ import { loginAssistToken } from './login-assist-host.js';
13
+ import { LoginAssist } from '../../built/login-assist.js';
14
+ import { sideEventSendChannel } from '../../side-event/base.js';
15
+ import { defaultCommandPrefixResolver, MessageDispatcher } from './message-dispatcher.js';
16
+ import { OutboundRenderer } from './outbound-renderer.js';
17
+ import { applyOutboundInteractivePolicy, applyOutboundMarkdownPolicy, normalizeOutboundPayload, resolveOutboundInteractivePolicy, resolveOutboundMarkdownPolicy, resolveOutboundMediaPolicy, } from './outbound-segments.js';
18
+ import { assertCanonicalSegments } from '../../built/segment-contract/assert.js';
19
+ import { keyboardFallbackStore } from '../../built/interactive-segments/fallback-store.js';
20
+ import { assertUserInteractionRequest, parseUserInteractionAnswer, projectUserInteraction, renderUserInteraction, } from '../../built/user-interaction.js';
21
+ import { findRuntimeInteractiveHandler, resolveRuntimeInteractivePayload, runtimeInteractiveConversationKey, } from './interactive.js';
22
+ const logger = getLogger('im');
23
+ /** @public Stable inbound and outbound IM gateway token for Adapter integrations. */
24
+ export const outboundMessageToken = createToken('zhin.im.outbound-message');
25
+ export const ingressRouteToken = createToken('zhin.im.ingress-route');
26
+ export const messagePreviewLimit = 200;
27
+ class UserInteractionTimeoutError extends Error {
28
+ constructor(message) {
29
+ super(message);
30
+ this.name = 'UserInteractionTimeoutError';
31
+ }
32
+ }
33
+ class UserInteractionSupersededError extends Error {
34
+ constructor() {
35
+ super('User interaction superseded');
36
+ this.name = 'UserInteractionSupersededError';
37
+ }
38
+ }
39
+ class UserInteractionDeliveryError extends Error {
40
+ constructor(receipt) {
41
+ super(`User interaction delivery failed: ${receipt.failure?.code ?? receipt.status}`);
42
+ this.name = 'UserInteractionDeliveryError';
43
+ }
44
+ }
45
+ export class ImRuntime {
46
+ #dispatcher;
47
+ #renderer;
48
+ #messageListeners = new Set();
49
+ #interactiveHandlers = [];
50
+ #interactionClaims = new Map();
51
+ #operationSnapshot = new AsyncLocalStorage();
52
+ #snapshots;
53
+ #inboundClaim;
54
+ #enrichSender;
55
+ conversationEvents;
56
+ constructor(options = {}) {
57
+ this.#dispatcher = new MessageDispatcher(options.commandPrefix === undefined
58
+ ? defaultCommandPrefixResolver
59
+ : () => options.commandPrefix ?? '');
60
+ this.#renderer = options.renderer ?? new OutboundRenderer();
61
+ this.conversationEvents = options.conversationEvents ?? new MemoryConversationEventStore();
62
+ this.#inboundClaim = options.inboundClaim;
63
+ this.#enrichSender = options.enrichSender;
64
+ }
65
+ /** Process composition replaces the bootstrap memory store after required DB activation. */
66
+ replaceConversationEventStore(store) {
67
+ this.conversationEvents = store;
68
+ }
69
+ /** Pins all nested IM operations to the snapshot current at operation ingress. */
70
+ async runWithSnapshotView(operation) {
71
+ const inherited = this.#operationSnapshot.getStore();
72
+ if (inherited?.active)
73
+ return operation();
74
+ if (!this.#snapshots)
75
+ throw new Error('ImRuntime is not attached to a Root');
76
+ const lease = this.#snapshots.acquire();
77
+ try {
78
+ return await this.#operationSnapshot.run(lease, operation);
79
+ }
80
+ finally {
81
+ lease.release();
82
+ }
83
+ }
84
+ async resolveConversationReference(lease, reference, context) {
85
+ if (!this.#snapshots?.owns(lease) || !lease.active) {
86
+ return Object.freeze({ status: 'expired', code: 'generation_lease_expired' });
87
+ }
88
+ if (reference.kind === 'message') {
89
+ const local = await this.conversationEvents.getMessage(reference.message);
90
+ if (local)
91
+ return Object.freeze({ status: 'resolved', reference, value: local });
92
+ }
93
+ context.signal.throwIfAborted();
94
+ const conversation = reference.kind === 'message' ? reference.message.conversation : reference.conversation;
95
+ return requireAdapters(lease.value).resolveContent(conversation.endpoint.id, reference, context);
96
+ }
97
+ async readConversationContext(conversation, consumer, throughSequence, limit = 50, excludeMessageId) {
98
+ if (!Number.isSafeInteger(throughSequence) || throughSequence < 0) {
99
+ throw new TypeError('Conversation context throughSequence must be a non-negative integer');
100
+ }
101
+ const cursor = await this.conversationEvents.getCursor(consumer, conversation);
102
+ const events = await this.conversationEvents.listBetween(conversation, cursor, throughSequence, limit);
103
+ const blocks = aggregateConversationContext(events, excludeMessageId);
104
+ return Object.freeze({
105
+ blocks: Object.freeze(blocks),
106
+ cursor: Math.max(cursor, throughSequence),
107
+ });
108
+ }
109
+ async commitConversationContext(conversation, consumer, cursor) {
110
+ await this.conversationEvents.commitCursor(consumer, conversation, cursor);
111
+ }
112
+ attach(snapshots) {
113
+ if (this.#snapshots && this.#snapshots !== snapshots) {
114
+ throw new Error('ImRuntime is already attached to another Root');
115
+ }
116
+ this.#snapshots = snapshots;
117
+ }
118
+ permissionHost = createPermissionHost();
119
+ messageBus = new MessageBus();
120
+ loginAssist = new LoginAssist();
121
+ install(resources) {
122
+ resources.provide(outboundMessageToken, this);
123
+ resources.provide(endpointEventGatewayToken, this.endpointEvents);
124
+ resources.provide(loginAssistToken, this.loginAssist);
125
+ resources.provide(permissionHostToken, this.permissionHost);
126
+ resources.provide(messageBusToken, this.messageBus);
127
+ }
128
+ /** The sole Adapter ingress, also installed as endpointEventGatewayToken. */
129
+ endpointEvents = (() => {
130
+ const self = this;
131
+ const gateway = {
132
+ receive: (event) => self.receiveEndpointEvent(event),
133
+ [generationAdmissionBinder](gate) {
134
+ return Object.freeze({
135
+ receive: async (event) => gate.enter(() => self.receiveEndpointEvent(event, gate)),
136
+ });
137
+ },
138
+ };
139
+ return gateway;
140
+ })();
141
+ [generationAdmissionBinder](gate) {
142
+ const gateway = {
143
+ send: async (request) => gate.enter(() => this.send(request))
144
+ ?? failedReceipt('generation_not_admitted'),
145
+ registerInteractiveHandler: (prefix, handler) => this.#registerInteractiveHandler(prefix, handler, gate),
146
+ };
147
+ return Object.freeze(gateway);
148
+ }
149
+ /**
150
+ * 注册 interactive action 回跳 handler(prefix 最长匹配;返回注销函数)。
151
+ * 在 Command dispatch 之前路由:action 段 / 数字回跳 / 指令预填 payload。
152
+ */
153
+ registerInteractiveHandler(prefix, handler) {
154
+ return this.#registerInteractiveHandler(prefix, handler);
155
+ }
156
+ #registerInteractiveHandler(prefix, handler, admission) {
157
+ const entry = Object.freeze({ prefix, handler, admission });
158
+ this.#interactiveHandlers.push(entry);
159
+ return () => {
160
+ const index = this.#interactiveHandlers.indexOf(entry);
161
+ if (index >= 0)
162
+ this.#interactiveHandlers.splice(index, 1);
163
+ };
164
+ }
165
+ // ==========================================================================
166
+ // User interaction claim — 命令/Agent 对话式交互
167
+ // ==========================================================================
168
+ #interactionConversationKey(message, subjectId = message.sender?.id ?? '') {
169
+ return `${conversationRefKey(message.conversation)}:${subjectId}`;
170
+ }
171
+ #resolveInteractionClaim(message) {
172
+ const key = this.#interactionConversationKey(message);
173
+ const claim = this.#interactionClaims.get(key);
174
+ if (!claim)
175
+ return false;
176
+ claim.resolve(resolveRuntimeInteractivePayload(message) ?? message.content);
177
+ return true;
178
+ }
179
+ #claimNextMessage(message, timeout, timeoutText, signal, subjectId) {
180
+ return new Promise((resolve, reject) => {
181
+ if (signal?.aborted) {
182
+ reject(abortError(signal, timeoutText));
183
+ return;
184
+ }
185
+ const key = this.#interactionConversationKey(message, subjectId);
186
+ const existing = this.#interactionClaims.get(key);
187
+ if (existing) {
188
+ clearTimeout(existing.timer);
189
+ existing.reject(new UserInteractionSupersededError());
190
+ }
191
+ const timer = setTimeout(() => {
192
+ settle(undefined, new UserInteractionTimeoutError(timeoutText));
193
+ }, timeout);
194
+ const onAbort = () => settle(undefined, abortError(signal, timeoutText));
195
+ signal?.addEventListener('abort', onAbort, { once: true });
196
+ const settle = (value, error) => {
197
+ if (this.#interactionClaims.get(key) !== claim)
198
+ return;
199
+ this.#interactionClaims.delete(key);
200
+ clearTimeout(timer);
201
+ signal?.removeEventListener('abort', onAbort);
202
+ if (value !== undefined)
203
+ resolve(value);
204
+ else
205
+ reject(error ?? new UserInteractionTimeoutError(timeoutText));
206
+ };
207
+ const claim = {
208
+ resolve: (raw) => settle(raw),
209
+ reject: (error) => settle(undefined, error),
210
+ timer,
211
+ };
212
+ this.#interactionClaims.set(key, claim);
213
+ });
214
+ }
215
+ #buildUserInteraction(message, subjectId) {
216
+ const DEFAULT_TIMEOUT = 3 * 60 * 1000;
217
+ const DEFAULT_TIMEOUT_TEXT = '输入超时';
218
+ const claim = (timeout, timeoutText, signal) => this.#claimNextMessage(message, timeout, timeoutText, signal, subjectId);
219
+ const reply = async (view) => {
220
+ const receipt = await message.$reply(renderUserInteraction(view));
221
+ if (receipt.status !== 'sent')
222
+ throw new UserInteractionDeliveryError(receipt);
223
+ };
224
+ const ask = async (request, progress) => {
225
+ assertUserInteractionRequest(request);
226
+ const timeout = request.timeout ?? DEFAULT_TIMEOUT;
227
+ const timeoutText = request.timeoutText ?? DEFAULT_TIMEOUT_TEXT;
228
+ const deadline = Date.now() + timeout;
229
+ const view = projectUserInteraction(request, progress);
230
+ await reply(view);
231
+ while (true) {
232
+ try {
233
+ const raw = await claim(Math.max(1, deadline - Date.now()), timeoutText, request.signal);
234
+ const result = parseUserInteractionAnswer(request, raw);
235
+ if (result.ok)
236
+ return result.value;
237
+ await reply({
238
+ ...view,
239
+ tip: [request.invalidText ?? result.message, view.tip].filter(Boolean).join('\n'),
240
+ });
241
+ }
242
+ catch (error) {
243
+ if (error instanceof UserInteractionTimeoutError && 'default' in request && request.default !== undefined) {
244
+ return request.default;
245
+ }
246
+ if (error instanceof UserInteractionTimeoutError) {
247
+ await reply({ title: '交互已结束', description: error.message });
248
+ }
249
+ throw error;
250
+ }
251
+ }
252
+ };
253
+ const sequence = async (definition) => {
254
+ if (!definition.title.trim())
255
+ throw new TypeError('User interaction sequence title must not be empty');
256
+ const ids = new Set();
257
+ for (const step of definition.steps) {
258
+ if (!step.id.trim())
259
+ throw new TypeError('User interaction sequence step id must not be empty');
260
+ if (ids.has(step.id))
261
+ throw new TypeError(`Duplicate user interaction sequence step id: ${step.id}`);
262
+ ids.add(step.id);
263
+ }
264
+ const result = {};
265
+ for (let index = 0; index < definition.steps.length; index += 1) {
266
+ const step = definition.steps[index];
267
+ const request = {
268
+ ...step,
269
+ timeout: step.timeout ?? definition.timeout,
270
+ timeoutText: step.timeoutText ?? definition.timeoutText,
271
+ invalidText: step.invalidText ?? definition.invalidText,
272
+ signal: step.signal ?? definition.signal,
273
+ };
274
+ result[step.id] = await ask(request, {
275
+ title: definition.title,
276
+ description: definition.description,
277
+ tip: definition.tip,
278
+ index: index + 1,
279
+ total: definition.steps.length,
280
+ });
281
+ }
282
+ return Object.freeze(result);
283
+ };
284
+ return Object.freeze({ ask, sequence });
285
+ }
286
+ /**
287
+ * User interaction bound to this conversation.
288
+ * `bind.subjectId` waits for that user (e.g. master) instead of the message sender.
289
+ */
290
+ createInteraction(message, bind) {
291
+ const subjectId = bind?.subjectId?.trim();
292
+ if (bind && !subjectId)
293
+ return undefined;
294
+ if (typeof message.$reply !== 'function' || !message.conversation)
295
+ return undefined;
296
+ return this.#buildUserInteraction(message, subjectId);
297
+ }
298
+ #createInteractionForSource(source) {
299
+ if (!source || typeof source !== 'object')
300
+ return undefined;
301
+ const msg = source;
302
+ if (typeof msg.$reply !== 'function' || !msg.conversation)
303
+ return undefined;
304
+ return this.#buildUserInteraction(msg);
305
+ }
306
+ /**
307
+ * 订阅消息事件(入站 dispatch 完成后 / 出站发送成功后回调)。
308
+ * 返回注销函数。listener 抛错不会阻断消息链路。
309
+ */
310
+ onMessage(listener) {
311
+ this.#messageListeners.add(listener);
312
+ return () => { this.#messageListeners.delete(listener); };
313
+ }
314
+ #emitMessage(event) {
315
+ for (const listener of this.#messageListeners) {
316
+ try {
317
+ listener(event);
318
+ }
319
+ catch {
320
+ // listener 异常不得影响消息收发
321
+ }
322
+ }
323
+ }
324
+ async #receive(source, admission) {
325
+ const input = source.payload;
326
+ const lease = this.#acquire();
327
+ let active = true;
328
+ try {
329
+ const conversation = input.conversation;
330
+ const adapter = conversation.endpoint.id;
331
+ const requester = requireAdapters(lease.value).owner(adapter);
332
+ logger.debug(formatCompact({
333
+ op: 'receive',
334
+ conv: formatConversationLog(conversation),
335
+ sender: `${input.sender?.name || 'undefined'}(${input.sender?.id || 'undefined'})`,
336
+ preview: truncatePreview(input.content),
337
+ }));
338
+ const enrichedSender = this.#enrichSender
339
+ ? this.#enrichSender(input.sender, conversation, lease.value)
340
+ : input.sender;
341
+ const message = new Message(conversation, input.content, lease.value.generation, (content, replyRequester = requester, targetConversation) => {
342
+ if (!active)
343
+ throw new Error('Message reply scope has ended');
344
+ const effectiveConversation = targetConversation
345
+ ? { endpoint: conversation.endpoint, ...targetConversation }
346
+ : conversation;
347
+ return this.#sendWithSnapshot({
348
+ conversation: effectiveConversation,
349
+ requester: replyRequester,
350
+ content,
351
+ incoming: {
352
+ sender: enrichedSender,
353
+ content: input.content,
354
+ segments: input.segments,
355
+ messageId: input.message?.id,
356
+ timestamp: Date.now(),
357
+ endpointId: input.endpointId,
358
+ mentioned: input.mentioned,
359
+ },
360
+ }, lease.value);
361
+ }, enrichedSender, Object.freeze({ ...input.metadata }), input.segments ? Object.freeze([...input.segments]) : undefined, input.message, input.endpointId, input.mentioned, input.replyTo, () => {
362
+ if (!active)
363
+ throw new Error('Message Client scope has ended');
364
+ return source.client;
365
+ }, source.endpoint.adapter);
366
+ let conversationSequence;
367
+ if (input.message?.id) {
368
+ const appended = await this.conversationEvents.append(Object.freeze({
369
+ eventId: `message:${messageRefKey(input.message)}`,
370
+ conversation,
371
+ timestamp: Date.now(),
372
+ type: 'message.created',
373
+ message: Object.freeze({
374
+ ref: input.message,
375
+ ...(enrichedSender ? { actor: Object.freeze({
376
+ id: enrichedSender.id,
377
+ ...(enrichedSender.name ? { displayName: enrichedSender.name } : {}),
378
+ }) } : {}),
379
+ segments: Object.freeze(input.segments?.length
380
+ ? [...input.segments]
381
+ : [{ type: 'text', data: { text: input.content } }]),
382
+ timestamp: Date.now(),
383
+ ...(input.replyTo ? { replyTo: Object.freeze({ conversation, id: input.replyTo.id }) } : {}),
384
+ }),
385
+ }));
386
+ conversationSequence = appended.sequence;
387
+ }
388
+ let result = Object.freeze({ matched: false });
389
+ const claimed = await this.#inboundClaim?.(message) === true;
390
+ if (claimed) {
391
+ result = Object.freeze({ matched: true, command: 'interaction', owner: requester });
392
+ }
393
+ else if (this.#resolveInteractionClaim(message)) {
394
+ result = Object.freeze({ matched: true, command: 'interaction', owner: requester });
395
+ }
396
+ else {
397
+ const interactionFactory = (source) => this.#createInteractionForSource(source);
398
+ await runMiddleware(lease.value, message, async () => {
399
+ const ingressRoute = resolveIngressRoute(lease.value);
400
+ const preRouted = await ingressRoute?.preRoute?.(message, lease, requester, conversationSequence) === true;
401
+ if (preRouted) {
402
+ result = Object.freeze({ matched: true, command: 'pre-route', owner: requester });
403
+ return;
404
+ }
405
+ if (ingressRoute?.shouldRouteBeforeDispatch?.(message) === true) {
406
+ const handled = await ingressRoute.route(message, lease, requester, conversationSequence);
407
+ if (handled) {
408
+ result = Object.freeze({ matched: true, command: 'ai', owner: requester });
409
+ return;
410
+ }
411
+ }
412
+ await this.#runHandlers(lease.value, 'message.receive', [
413
+ withEndpointEventPayload(source, message),
414
+ ]);
415
+ result = await this.#dispatchInteractive(message, requester, admission)
416
+ ?? await this.#dispatcher.dispatch(message, lease.value, interactionFactory);
417
+ if (!result.matched && ingressRoute) {
418
+ logger.debug(formatCompact({ op: 'unmatched', conv: formatConversationLog(conversation) }));
419
+ const handled = await ingressRoute.route(message, lease, requester, conversationSequence);
420
+ if (handled) {
421
+ result = Object.freeze({ matched: true, command: 'ai', owner: requester });
422
+ }
423
+ }
424
+ }, 'inbound');
425
+ }
426
+ if (result.matched) {
427
+ logger.debug(formatCompact({
428
+ op: 'dispatched',
429
+ conv: formatConversationLog(conversation),
430
+ command: result.command,
431
+ }));
432
+ }
433
+ this.#emitMessage({
434
+ direction: 'inbound',
435
+ conversation,
436
+ ...(input.sender !== undefined ? { sender: input.sender } : {}),
437
+ contentPreview: previewText(input.content),
438
+ ...(input.message?.id ? { messageId: input.message.id } : {}),
439
+ timestamp: Date.now(),
440
+ });
441
+ return result;
442
+ }
443
+ finally {
444
+ active = false;
445
+ this.#release(lease);
446
+ }
447
+ }
448
+ async send(request) {
449
+ const lease = this.#acquire();
450
+ try {
451
+ return await this.#sendWithSnapshot(request, lease.value);
452
+ }
453
+ finally {
454
+ this.#release(lease);
455
+ }
456
+ }
457
+ /** Sends through the exact generation lease that admitted the current operation. */
458
+ async sendWithSnapshotLease(lease, request) {
459
+ if (!this.#snapshots?.owns(lease) || !lease.active) {
460
+ throw new Error('Outbound message generation lease expired');
461
+ }
462
+ return this.#sendWithSnapshot(request, lease.value);
463
+ }
464
+ async #receiveNotice(source) {
465
+ const notice = source.payload;
466
+ const event = conversationEventFromNotice(notice);
467
+ if (event)
468
+ await this.conversationEvents.append(event);
469
+ await this.#receiveSideEvent(withEndpointEventPayload(source, notice));
470
+ }
471
+ async #receiveRequest(source) {
472
+ await this.#withRequestActionScope(source.payload, async (scoped) => {
473
+ await this.#receiveSideEvent(withEndpointEventPayload(source, scoped));
474
+ });
475
+ }
476
+ async #withRequestActionScope(request, dispatch) {
477
+ let active = true;
478
+ const actions = new Set();
479
+ const run = (action) => {
480
+ if (!active)
481
+ throw new Error('Request action port expired with its generation operation');
482
+ const operation = Promise.resolve().then(action);
483
+ actions.add(operation);
484
+ void operation.then(() => actions.delete(operation), () => actions.delete(operation));
485
+ return operation;
486
+ };
487
+ const scoped = Object.assign(Object.create(Object.getPrototypeOf(request)), request, {
488
+ $approve: async (remark) => run(() => request.$approve(remark)),
489
+ $reject: async (reason) => run(() => request.$reject(reason)),
490
+ });
491
+ try {
492
+ await dispatch(scoped);
493
+ }
494
+ finally {
495
+ active = false;
496
+ await Promise.allSettled([...actions]);
497
+ }
498
+ }
499
+ async #receiveSystem(source) {
500
+ await this.#receiveSideEvent(source);
501
+ }
502
+ async #receiveSideEvent(event) {
503
+ const lease = this.#acquire();
504
+ try {
505
+ await this.#runHandlers(lease.value, event.name, [event]);
506
+ }
507
+ finally {
508
+ this.#release(lease);
509
+ }
510
+ }
511
+ async receiveEndpointEvent(event, admission) {
512
+ switch (event.name) {
513
+ case 'message.receive':
514
+ return this.#receive(event, admission);
515
+ case 'notice.receive':
516
+ return this.#receiveNotice(event);
517
+ case 'request.receive':
518
+ return this.#receiveRequest(event);
519
+ case 'system.receive':
520
+ return this.#receiveSystem(event);
521
+ default: {
522
+ const lease = this.#acquire();
523
+ try {
524
+ await this.#runHandlers(lease.value, event.name, [event]);
525
+ return undefined;
526
+ }
527
+ finally {
528
+ this.#release(lease);
529
+ }
530
+ }
531
+ }
532
+ }
533
+ /**
534
+ * User interaction bound to a side-event scene (private/group channel derived from
535
+ * `$scene_type` / `$scene_id`). Returns undefined when outbound is unavailable.
536
+ */
537
+ #createInteractionForSideEvent(payload, snapshot) {
538
+ const adapter = String(payload.$adapter);
539
+ const endpointKey = String(payload.$endpoint);
540
+ if (!adapter || !endpointKey)
541
+ return undefined;
542
+ const channel = sideEventSendChannel(payload);
543
+ const conversation = Object.freeze({
544
+ endpoint: Object.freeze({ adapter, id: endpointKey }),
545
+ kind: channel.type,
546
+ id: channel.id || endpointKey,
547
+ });
548
+ const requester = snapshot.root;
549
+ const source = Object.freeze({
550
+ conversation,
551
+ ...(payload.$actor?.id
552
+ ? { sender: Object.freeze({ id: payload.$actor.id }) }
553
+ : {}),
554
+ $reply: (content) => this.#sendWithSnapshot({
555
+ conversation,
556
+ requester,
557
+ content,
558
+ }, snapshot),
559
+ });
560
+ return this.#buildUserInteraction(source);
561
+ }
562
+ async #runHandlers(snapshot, event, args) {
563
+ const index = handlers(snapshot);
564
+ if (!index)
565
+ return;
566
+ const options = {
567
+ resolveInteraction: (name, interactionArgs) => {
568
+ const context = interactionArgs[0];
569
+ const payload = context?.payload;
570
+ if (name === 'message.receive' && payload instanceof Message) {
571
+ return this.createInteraction(payload);
572
+ }
573
+ if (name === 'notice.receive'
574
+ || name === 'request.receive'
575
+ || name === 'system.receive') {
576
+ return this.#createInteractionForSideEvent(payload, snapshot);
577
+ }
578
+ return undefined;
579
+ },
580
+ };
581
+ await index.dispatch(event, args, options);
582
+ }
583
+ /** Console `endpoint.list` — empty until Adapter Feature projection is ready. */
584
+ listEndpoints() {
585
+ try {
586
+ const lease = this.#acquire();
587
+ try {
588
+ return requireAdapters(lease.value).describe().map((row) => Object.freeze({
589
+ id: String(row.id),
590
+ name: row.name,
591
+ // adapter 列显示平台类型(owner 包名去 scope/adapter- 前缀),不是 slot localName
592
+ adapter: adapterTypeName(lease.value.tree.get(row.owner)?.packageName) ?? row.name,
593
+ owner: row.owner,
594
+ connected: row.connected,
595
+ status: row.status,
596
+ phase: row.phase,
597
+ operations: row.operations,
598
+ managementCapabilities: row.managementCapabilities,
599
+ }));
600
+ }
601
+ finally {
602
+ this.#release(lease);
603
+ }
604
+ }
605
+ catch {
606
+ return Object.freeze([]);
607
+ }
608
+ }
609
+ /** Exact operation capabilities for one concrete live Endpoint. */
610
+ endpointCapabilities(input) {
611
+ try {
612
+ const lease = this.#acquire();
613
+ try {
614
+ const index = requireAdapters(lease.value);
615
+ const id = index.resolve(input.adapter, input.endpointKey);
616
+ return id ? index.capabilities(id) : undefined;
617
+ }
618
+ finally {
619
+ this.#release(lease);
620
+ }
621
+ }
622
+ catch {
623
+ return undefined;
624
+ }
625
+ }
626
+ /**
627
+ * Console `GET /api/stats` 同源计数:非 root 插件节点 + AdapterIndex endpoints。
628
+ * 供命令 / 状态卡等在 Plugin Runtime 下读取(legacy `root.adapters` / `root.children` 已不存在)。
629
+ */
630
+ inventory() {
631
+ try {
632
+ const lease = this.#acquire();
633
+ try {
634
+ const plugins = [...lease.value.tree.values()]
635
+ .filter((node) => node.parent !== undefined).length;
636
+ const endpoints = requireAdapters(lease.value).describe();
637
+ return Object.freeze({
638
+ plugins,
639
+ endpoints: Object.freeze({
640
+ total: endpoints.length,
641
+ online: endpoints.filter((endpoint) => endpoint.status === 'online').length,
642
+ }),
643
+ });
644
+ }
645
+ finally {
646
+ this.#release(lease);
647
+ }
648
+ }
649
+ catch {
650
+ return Object.freeze({
651
+ plugins: 0,
652
+ endpoints: Object.freeze({ total: 0, online: 0 }),
653
+ });
654
+ }
655
+ }
656
+ getEndpoint(adapter, endpointKey) {
657
+ try {
658
+ const lease = this.#acquire();
659
+ try {
660
+ const index = requireAdapters(lease.value);
661
+ const id = index.resolve(adapter, endpointKey);
662
+ if (!id)
663
+ return null;
664
+ const row = index.describe().find((item) => item.id === id);
665
+ if (!row)
666
+ return null;
667
+ // adapter 与 listEndpoints 对齐:平台类型(owner 包名去 scope/adapter- 前缀),
668
+ // 不是 live name(如 ICQQ uin)。此前误写 row.name 导致 endpoint.info 与 list 不一致。
669
+ return Object.freeze({
670
+ name: row.name,
671
+ adapter: adapterTypeName(lease.value.tree.get(row.owner)?.packageName) ?? row.name,
672
+ connected: row.connected,
673
+ status: row.status,
674
+ phase: row.phase,
675
+ operations: row.operations,
676
+ managementCapabilities: row.managementCapabilities,
677
+ });
678
+ }
679
+ finally {
680
+ this.#release(lease);
681
+ }
682
+ }
683
+ catch {
684
+ return null;
685
+ }
686
+ }
687
+ async sendEndpointMessage(input) {
688
+ const lease = this.#acquire();
689
+ try {
690
+ const index = requireAdapters(lease.value);
691
+ const resolved = index.resolve(input.adapter, input.endpointKey);
692
+ if (!resolved)
693
+ throw new Error('endpoint not found');
694
+ const requester = index.owner(resolved);
695
+ const conversation = {
696
+ endpoint: { id: String(resolved), adapter: String(requester) },
697
+ ...input.conversation,
698
+ };
699
+ const content = normalizeConsoleContent(input.content);
700
+ const result = await this.#sendWithSnapshot({
701
+ conversation,
702
+ requester,
703
+ content,
704
+ }, lease.value);
705
+ return { messageId: result.message?.id ?? '' };
706
+ }
707
+ finally {
708
+ this.#release(lease);
709
+ }
710
+ }
711
+ /** Activity-feedback: add a message reaction when the live Endpoint supports it. */
712
+ async addEndpointReaction(input) {
713
+ return this.#withEndpointControl(input.adapter, input.endpointKey, 'reaction', (control) => control.addReaction?.(input.message, input.emoji, {
714
+ sceneType: input.sceneType,
715
+ channelId: input.channelId,
716
+ }) ?? null, null);
717
+ }
718
+ async removeEndpointReaction(input) {
719
+ await this.#withEndpointControl(input.adapter, input.endpointKey, 'reaction', (control) => control.removeReaction?.(input.message, input.reactionId), undefined);
720
+ }
721
+ /** Activity-feedback autoRemove: recall a previously sent status message. */
722
+ async recallEndpointMessage(input) {
723
+ await this.#withEndpointControl(input.adapter, input.endpointKey, 'recall', (control) => control.recall?.(input.message), undefined);
724
+ }
725
+ async editEndpointMessage(input) {
726
+ return this.#withEndpointControl(input.adapter, input.endpointKey, 'edit', (control) => control.edit?.(input.message, input.content) ?? null, null);
727
+ }
728
+ async setEndpointTyping(input) {
729
+ await this.#withEndpointControl(input.adapter, input.endpointKey, 'typing', (control) => control.typing?.(input.conversation, input.active), undefined);
730
+ }
731
+ async #withEndpointControl(adapter, endpointKey, operation, run, fallback) {
732
+ let lease;
733
+ try {
734
+ lease = this.#acquire();
735
+ }
736
+ catch {
737
+ return fallback;
738
+ }
739
+ try {
740
+ const index = requireAdapters(lease.value);
741
+ const id = index.resolve(adapter, endpointKey);
742
+ const control = id ? index.control(id, operation) : undefined;
743
+ return control ? await run(control) : fallback;
744
+ }
745
+ finally {
746
+ this.#release(lease);
747
+ }
748
+ }
749
+ /** Run one management operation while the Endpoint generation stays leased. */
750
+ async withEndpointManagement(adapter, endpointKey, run) {
751
+ let lease;
752
+ try {
753
+ lease = this.#acquire();
754
+ }
755
+ catch {
756
+ return null;
757
+ }
758
+ try {
759
+ const endpoint = requireAdapters(lease.value).connection(adapter, endpointKey);
760
+ if (!endpoint)
761
+ return null;
762
+ return await run(resolveEndpointManagement(endpoint) ?? Object.freeze({}));
763
+ }
764
+ finally {
765
+ this.#release(lease);
766
+ }
767
+ }
768
+ async #sendWithSnapshot(request, snapshot) {
769
+ const adapter = request.conversation.endpoint.id;
770
+ let initialPayload;
771
+ try {
772
+ const rendered = await this.#renderer.render(request.content, request.requester, snapshot, request.conversation, request.incoming);
773
+ initialPayload = await prepareOutboundPayload(rendered, request.conversation, snapshot);
774
+ }
775
+ catch {
776
+ return rejectedReceipt('outbound_payload_rejected');
777
+ }
778
+ let adapters;
779
+ try {
780
+ adapters = requireAdapters(snapshot);
781
+ }
782
+ catch (error) {
783
+ return receiptFromEndpointError(error);
784
+ }
785
+ const envelope = createOutboundEnvelope({
786
+ conversation: request.conversation,
787
+ requester: request.requester,
788
+ generation: snapshot.generation,
789
+ clientAdapter: adapters.clientAdapter(adapter),
790
+ }, initialPayload, () => adapters.clientById(adapter));
791
+ let terminalEntered = false;
792
+ let receipt;
793
+ try {
794
+ await runMiddleware(snapshot, envelope, async () => {
795
+ terminalEntered = true;
796
+ let payload;
797
+ try {
798
+ payload = await prepareOutboundPayload(envelope.payload, request.conversation, snapshot, true);
799
+ }
800
+ catch {
801
+ receipt = rejectedReceipt('outbound_payload_rejected');
802
+ return;
803
+ }
804
+ try {
805
+ const result = await requireAdapters(snapshot).send(adapter, {
806
+ conversation: request.conversation,
807
+ payload,
808
+ });
809
+ receipt = receiptFromEndpointResult(result, request.conversation);
810
+ }
811
+ catch (error) {
812
+ receipt = receiptFromEndpointError(error);
813
+ }
814
+ if (receipt?.status === 'sent') {
815
+ if (receipt.message?.id) {
816
+ const segments = conversationSegmentsFromContent(request.content);
817
+ await this.conversationEvents.append(Object.freeze({
818
+ eventId: `message:${messageRefKey(receipt.message)}`,
819
+ conversation: request.conversation,
820
+ timestamp: Date.now(),
821
+ type: 'message.created',
822
+ message: Object.freeze({
823
+ ref: receipt.message,
824
+ segments,
825
+ timestamp: Date.now(),
826
+ }),
827
+ }));
828
+ }
829
+ this.#emitMessage({
830
+ direction: 'outbound',
831
+ conversation: request.conversation,
832
+ requester: request.requester,
833
+ contentPreview: previewText(payload),
834
+ ...(receipt.message?.id
835
+ ? { messageId: receipt.message.id }
836
+ : {}),
837
+ timestamp: Date.now(),
838
+ });
839
+ }
840
+ }, 'outbound');
841
+ }
842
+ catch {
843
+ return receipt ?? failedReceipt('outbound_middleware_failed');
844
+ }
845
+ if (!terminalEntered) {
846
+ logger.debug(formatCompact({
847
+ op: 'replychain_runtime_send',
848
+ status: 'suppressed',
849
+ reason: 'middleware_stopped_before_terminal',
850
+ conv: formatConversationLog(request.conversation),
851
+ }));
852
+ return suppressedReceipt();
853
+ }
854
+ logger.debug(formatCompact({
855
+ op: 'replychain_runtime_send',
856
+ status: receipt?.status ?? 'missing',
857
+ code: receipt?.failure?.code,
858
+ conv: formatConversationLog(request.conversation),
859
+ }));
860
+ return receipt ?? failedReceipt('outbound_delivery_incomplete');
861
+ }
862
+ #acquire() {
863
+ const inherited = this.#operationSnapshot.getStore();
864
+ if (inherited?.active)
865
+ return inherited;
866
+ if (!this.#snapshots)
867
+ throw new Error('ImRuntime is not attached to a Root');
868
+ return this.#snapshots.acquire();
869
+ }
870
+ #release(lease) {
871
+ if (this.#operationSnapshot.getStore() === lease)
872
+ return;
873
+ lease.release();
874
+ }
875
+ /**
876
+ * interactive 回跳分发(Command dispatch 之前):action 段 / 中央 fallback
877
+ * 数字回跳 / 指令预填 payload → prefix 最长匹配 handler。
878
+ */
879
+ async #dispatchInteractive(message, requester, admission) {
880
+ if (this.#interactiveHandlers.length === 0)
881
+ return undefined;
882
+ const payload = resolveRuntimeInteractivePayload(message);
883
+ if (!payload)
884
+ return undefined;
885
+ const handler = findRuntimeInteractiveHandler(this.#interactiveHandlers.filter((entry) => !entry.admission || entry.admission === admission), payload);
886
+ if (!handler)
887
+ return undefined;
888
+ const handled = await handler(message);
889
+ return handled
890
+ ? Object.freeze({ matched: true, command: 'interactive', owner: requester })
891
+ : undefined;
892
+ }
893
+ }
894
+ function conversationEventFromNotice(notice) {
895
+ const sceneType = notice.$scene_type;
896
+ const kind = sceneType === 'group' ? 'group'
897
+ : sceneType === 'channel' ? 'channel'
898
+ : sceneType === 'friend' || sceneType === 'private' ? 'private'
899
+ : undefined;
900
+ if (!kind || !notice.$scene_id || !notice.$id)
901
+ return undefined;
902
+ const conversation = Object.freeze({
903
+ endpoint: Object.freeze({ adapter: String(notice.$adapter), id: String(notice.$endpoint) }),
904
+ kind,
905
+ id: String(notice.$scene_id),
906
+ });
907
+ const actor = notice.$actor?.id
908
+ ? Object.freeze({ id: String(notice.$actor.id), ...(notice.$actor.name ? { displayName: notice.$actor.name } : {}) })
909
+ : undefined;
910
+ const target = notice.$target?.id
911
+ ? Object.freeze({ id: String(notice.$target.id), ...(notice.$target.name ? { displayName: notice.$target.name } : {}) })
912
+ : undefined;
913
+ const base = Object.freeze({
914
+ eventId: `notice:${conversationRefKey(conversation)}:${String(notice.$id)}`,
915
+ conversation,
916
+ timestamp: notice.$timestamp,
917
+ });
918
+ switch (notice.$sub_type) {
919
+ case 'member_increase':
920
+ case 'increase':
921
+ return target ? Object.freeze({ ...base, type: 'member.joined', member: target, ...(actor ? { actor } : {}) }) : undefined;
922
+ case 'member_decrease':
923
+ return target ? Object.freeze({ ...base, type: 'member.left', member: target, ...(actor ? { actor } : {}), reason: actor ? 'removed' : 'left' }) : undefined;
924
+ case 'ban':
925
+ if (!target)
926
+ return undefined;
927
+ return notice.$duration_seconds === 0
928
+ ? Object.freeze({ ...base, type: 'member.unmuted', member: target, ...(actor ? { actor } : {}) })
929
+ : Object.freeze({ ...base, type: 'member.muted', member: target, ...(actor ? { actor } : {}), durationSeconds: notice.$duration_seconds ?? 0 });
930
+ case 'admin_change':
931
+ return target && notice.$role && typeof notice.$enabled === 'boolean'
932
+ ? Object.freeze({ ...base, type: 'member.role_changed', member: target, ...(actor ? { actor } : {}), role: notice.$role, enabled: notice.$enabled })
933
+ : undefined;
934
+ case 'recall':
935
+ return notice.$message_id
936
+ ? Object.freeze({ ...base, type: 'message.recalled', message: Object.freeze({ conversation, id: notice.$message_id }), ...(actor ? { actor } : {}) })
937
+ : undefined;
938
+ case 'emoji_reaction':
939
+ return notice.$message_id && notice.$reaction && notice.$operation
940
+ ? Object.freeze({ ...base, type: 'message.reaction_changed', message: Object.freeze({ conversation, id: notice.$message_id }), ...(actor ? { actor } : {}), reaction: notice.$reaction, operation: notice.$operation })
941
+ : undefined;
942
+ case 'poke':
943
+ return Object.freeze({ ...base, type: 'conversation.poked', ...(actor ? { actor } : {}), ...(target ? { target } : {}) });
944
+ default:
945
+ return undefined;
946
+ }
947
+ }
948
+ function describeConversationEvent(event) {
949
+ const actor = 'actor' in event && event.actor ? `${event.actor.displayName ?? event.actor.id} (${event.actor.id})` : undefined;
950
+ switch (event.type) {
951
+ case 'message.recalled': return `${actor ?? 'Someone'} recalled message ${event.message.id}.`;
952
+ case 'message.reaction_changed': return `${actor ?? 'Someone'} ${event.operation} reaction ${event.reaction} on message ${event.message.id}.`;
953
+ case 'conversation.poked': return `${actor ?? 'Someone'} poked ${event.target ? `${event.target.displayName ?? event.target.id} (${event.target.id})` : 'someone'}.`;
954
+ case 'member.joined': return `${event.member.displayName ?? event.member.id} (${event.member.id}) joined the conversation.`;
955
+ case 'member.left': return `${event.member.displayName ?? event.member.id} (${event.member.id}) left the conversation (${event.reason ?? 'left'}).`;
956
+ case 'member.muted': return `${event.member.displayName ?? event.member.id} (${event.member.id}) was muted for ${event.durationSeconds} seconds${actor ? ` by ${actor}` : ''}.`;
957
+ case 'member.unmuted': return `${event.member.displayName ?? event.member.id} (${event.member.id}) was unmuted${actor ? ` by ${actor}` : ''}.`;
958
+ case 'member.role_changed': return `${event.member.displayName ?? event.member.id} (${event.member.id}) role ${event.role} was ${event.enabled ? 'enabled' : 'disabled'}${actor ? ` by ${actor}` : ''}.`;
959
+ case 'message.created': return '';
960
+ }
961
+ }
962
+ function describeConversationMessage(message) {
963
+ const actor = message.actor
964
+ ? `${message.actor.displayName ?? message.actor.id} (${message.actor.id})`
965
+ : undefined;
966
+ if (!actor)
967
+ return '';
968
+ const text = segmentsToPlainText(message.segments).trim();
969
+ const attachmentTypes = [...new Set(message.segments
970
+ .filter((segment) => segment.type !== 'text' && segment.type !== 'mention' && segment.type !== 'at')
971
+ .map((segment) => segment.type))];
972
+ const attachmentNote = attachmentTypes.length > 0
973
+ ? ` [message also contains ${attachmentTypes.join(', ')} data; inspect its reference before relying on that content]`
974
+ : '';
975
+ return `${actor}: ${text || '(no plain-text content)'}${attachmentNote}`;
976
+ }
977
+ function aggregateConversationContext(events, excludeMessageId) {
978
+ const ordinary = [];
979
+ const noisy = new Map();
980
+ for (const { sequence, event } of events) {
981
+ if (event.type === 'message.created') {
982
+ if (event.message.ref.id === excludeMessageId)
983
+ continue;
984
+ const text = describeConversationMessage(event.message);
985
+ if (!text)
986
+ continue;
987
+ ordinary.push(Object.freeze({
988
+ kind: 'conversation_event',
989
+ sequence,
990
+ eventType: event.type,
991
+ text,
992
+ }));
993
+ continue;
994
+ }
995
+ if (event.type === 'message.reaction_changed' || event.type === 'conversation.poked') {
996
+ const key = event.type === 'message.reaction_changed'
997
+ ? `${event.type}:${event.message.id}:${event.actor?.id ?? ''}:${event.reaction}:${event.operation}`
998
+ : `${event.type}:${event.actor?.id ?? ''}:${event.target?.id ?? ''}`;
999
+ const previous = noisy.get(key);
1000
+ noisy.set(key, { sequence, event, count: (previous?.count ?? 0) + 1 });
1001
+ continue;
1002
+ }
1003
+ ordinary.push(Object.freeze({
1004
+ kind: 'conversation_event',
1005
+ sequence,
1006
+ eventType: event.type,
1007
+ text: describeConversationEvent(event),
1008
+ }));
1009
+ }
1010
+ for (const { sequence, event, count } of noisy.values()) {
1011
+ ordinary.push(Object.freeze({
1012
+ kind: 'conversation_event',
1013
+ sequence,
1014
+ eventType: event.type,
1015
+ text: `${describeConversationEvent(event)}${count > 1 ? ` (${count} similar events.)` : ''}`,
1016
+ }));
1017
+ }
1018
+ ordinary.sort((left, right) => left.sequence - right.sequence);
1019
+ return Object.freeze(ordinary);
1020
+ }
1021
+ function resolveIngressRoute(snapshot) {
1022
+ const candidate = snapshot.resources.get(snapshot.root)?.get(ingressRouteToken.id);
1023
+ return candidate
1024
+ && typeof candidate === 'object'
1025
+ && typeof candidate.route === 'function'
1026
+ ? candidate
1027
+ : undefined;
1028
+ }
1029
+ function requireAdapters(snapshot) {
1030
+ const projection = snapshot.projections.get(adapterFeatureId);
1031
+ if (!isAdapterIndex(projection)) {
1032
+ throw new Error('Adapter Feature projection is not installed');
1033
+ }
1034
+ return projection;
1035
+ }
1036
+ /** Root resources 上的可选 html-renderer Host(未安装时降级为文本)。 */
1037
+ function resolveHtmlRenderer(snapshot) {
1038
+ const host = snapshot.resources.get(snapshot.root)?.get(htmlRendererToken.id);
1039
+ return host && typeof host.render === 'function'
1040
+ ? host
1041
+ : undefined;
1042
+ }
1043
+ /**
1044
+ * Sandbox(控制台 UI)按设计直接消费 html 段,不做 html→image/text 规范化。
1045
+ * 通过 adapter 能力 slot 的 owner 包名判断平台类型。
1046
+ */
1047
+ function isDirectHtmlConsumer(snapshot, adapter) {
1048
+ const owner = snapshot.capabilities.get(adapter)?.owner;
1049
+ return adapterTypeName(snapshot.tree.get(owner)?.packageName) === 'sandbox';
1050
+ }
1051
+ async function prepareOutboundPayload(rendered, conversation, snapshot, finalizeInteractive = false) {
1052
+ const adapter = conversation.endpoint.id;
1053
+ const directHtml = isDirectHtmlConsumer(snapshot, adapter);
1054
+ const markdownResolved = applyOutboundMarkdownPolicy(rendered, resolveOutboundMarkdownPolicy(adapter, snapshot));
1055
+ let payload = directHtml
1056
+ ? markdownResolved
1057
+ : await normalizeOutboundPayload(markdownResolved, resolveHtmlRenderer(snapshot), {
1058
+ mediaPolicy: resolveOutboundMediaPolicy(adapter, snapshot),
1059
+ });
1060
+ if (finalizeInteractive) {
1061
+ payload = applyOutboundInteractivePolicy(payload, resolveOutboundInteractivePolicy(adapter, snapshot), (map) => keyboardFallbackStore.remember(runtimeInteractiveConversationKey(conversation), map));
1062
+ }
1063
+ if (!directHtml && Array.isArray(payload))
1064
+ assertCanonicalSegments(payload);
1065
+ return payload;
1066
+ }
1067
+ function receiptFromEndpointResult(messageId, conversation) {
1068
+ return Object.freeze({
1069
+ status: 'sent',
1070
+ message: Object.freeze({ conversation, id: messageId }),
1071
+ });
1072
+ }
1073
+ function receiptFromEndpointError(error) {
1074
+ const message = error instanceof Error ? error.message : String(error);
1075
+ if (/Unknown Adapter Endpoint|does not support outbound/u.test(message)) {
1076
+ return unsupportedReceipt('outbound_unsupported');
1077
+ }
1078
+ if (/not active/u.test(message))
1079
+ return failedReceipt('endpoint_inactive', true);
1080
+ return failedReceipt('endpoint_send_failed', true);
1081
+ }
1082
+ function suppressedReceipt() {
1083
+ return Object.freeze({ status: 'suppressed' });
1084
+ }
1085
+ function unsupportedReceipt(code) {
1086
+ return Object.freeze({
1087
+ status: 'unsupported',
1088
+ failure: Object.freeze({ code, message: 'Outbound delivery is not supported.' }),
1089
+ });
1090
+ }
1091
+ function rejectedReceipt(code) {
1092
+ return Object.freeze({
1093
+ status: 'rejected',
1094
+ failure: Object.freeze({ code, message: 'Outbound payload was rejected.' }),
1095
+ });
1096
+ }
1097
+ function failedReceipt(code, retryable = false) {
1098
+ return Object.freeze({
1099
+ status: 'failed',
1100
+ failure: Object.freeze({
1101
+ code,
1102
+ message: 'Outbound delivery failed.',
1103
+ ...(retryable ? { retryable: true } : {}),
1104
+ }),
1105
+ });
1106
+ }
1107
+ /** `@zhin.js/adapter-icqq` → `icqq`;非 adapter 包名原样返回。 */
1108
+ function adapterTypeName(packageName) {
1109
+ if (!packageName)
1110
+ return undefined;
1111
+ return packageName.replace(/^@[^/]+\/adapter-/, '');
1112
+ }
1113
+ function middleware(snapshot) {
1114
+ const projection = snapshot.projections.get(middlewareFeatureId);
1115
+ return isMiddlewareIndex(projection) ? projection : undefined;
1116
+ }
1117
+ async function runMiddleware(snapshot, input, terminal, target) {
1118
+ const index = middleware(snapshot);
1119
+ if (index)
1120
+ await index.run(input, terminal, target);
1121
+ else
1122
+ await terminal();
1123
+ }
1124
+ function handlers(snapshot) {
1125
+ const projection = snapshot.projections.get(handlerFeatureId);
1126
+ return isHandlerIndex(projection) ? projection : undefined;
1127
+ }
1128
+ function normalizeConsoleContent(content) {
1129
+ if (typeof content === 'string')
1130
+ return content;
1131
+ // Array content passes through untouched, matching the legacy console RPC
1132
+ // contract (element arrays must not be stringified to '[object Object]').
1133
+ if (Array.isArray(content))
1134
+ return content;
1135
+ return String(content);
1136
+ }
1137
+ /** 日志用会话摘要(`kind:id@parentKind:parentId#threadId`),不拼 legacy target。 */
1138
+ function formatConversationLog(conversation) {
1139
+ const base = `${conversation.kind}:${conversation.id}`;
1140
+ const parent = conversation.parent
1141
+ ? `@${conversation.parent.kind}:${conversation.parent.id}`
1142
+ : '';
1143
+ const thread = conversation.threadId ? `#${conversation.threadId}` : '';
1144
+ return `${base}${parent}${thread}`;
1145
+ }
1146
+ /** 消息内容 → 预览文本(截断 200 字);wire 段取 `data.text`,其余段记 `[type]`。 */
1147
+ function previewText(content) {
1148
+ const text = flattenContent(content);
1149
+ return text.length > messagePreviewLimit
1150
+ ? `${text.slice(0, messagePreviewLimit)}…`
1151
+ : text;
1152
+ }
1153
+ function flattenContent(content) {
1154
+ if (typeof content === 'string')
1155
+ return content;
1156
+ if (content == null)
1157
+ return '';
1158
+ if (Array.isArray(content)) {
1159
+ return content.map((item) => flattenContent(item)).join('');
1160
+ }
1161
+ if (typeof content === 'object') {
1162
+ const record = content;
1163
+ const data = record.data;
1164
+ if (typeof record.type === 'string') {
1165
+ if (data && typeof data.text === 'string')
1166
+ return data.text;
1167
+ return `[${record.type}]`;
1168
+ }
1169
+ if (typeof record.text === 'string')
1170
+ return record.text;
1171
+ try {
1172
+ return JSON.stringify(content) ?? '';
1173
+ }
1174
+ catch {
1175
+ return String(content);
1176
+ }
1177
+ }
1178
+ return String(content);
1179
+ }
1180
+ function conversationSegmentsFromContent(content) {
1181
+ if (Array.isArray(content)) {
1182
+ try {
1183
+ assertCanonicalSegments(content);
1184
+ return Object.freeze([...content]);
1185
+ }
1186
+ catch {
1187
+ // Fall through to a truthful text projection of non-canonical output.
1188
+ }
1189
+ }
1190
+ return Object.freeze([{ type: 'text', data: Object.freeze({ text: flattenContent(content) }) }]);
1191
+ }
1192
+ function abortError(signal, fallback) {
1193
+ if (signal?.reason instanceof Error)
1194
+ return signal.reason;
1195
+ return new Error(fallback);
1196
+ }
1197
+ function withEndpointEventPayload(source, payload) {
1198
+ return Object.freeze({
1199
+ name: source.name,
1200
+ payload,
1201
+ endpoint: source.endpoint,
1202
+ client: source.client,
1203
+ });
1204
+ }