im-hub-pro 0.2.29

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 (384) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +497 -0
  3. package/README.zh-CN.md +496 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +921 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/core/acp-server.d.ts +8 -0
  9. package/dist/core/acp-server.d.ts.map +1 -0
  10. package/dist/core/acp-server.js +266 -0
  11. package/dist/core/acp-server.js.map +1 -0
  12. package/dist/core/agent-base.d.ts +94 -0
  13. package/dist/core/agent-base.d.ts.map +1 -0
  14. package/dist/core/agent-base.js +374 -0
  15. package/dist/core/agent-base.js.map +1 -0
  16. package/dist/core/agent-cwd.d.ts +45 -0
  17. package/dist/core/agent-cwd.d.ts.map +1 -0
  18. package/dist/core/agent-cwd.js +178 -0
  19. package/dist/core/agent-cwd.js.map +1 -0
  20. package/dist/core/agent-cwd.test.d.ts +2 -0
  21. package/dist/core/agent-cwd.test.d.ts.map +1 -0
  22. package/dist/core/agent-cwd.test.js +149 -0
  23. package/dist/core/agent-cwd.test.js.map +1 -0
  24. package/dist/core/approval-bus.d.ts +232 -0
  25. package/dist/core/approval-bus.d.ts.map +1 -0
  26. package/dist/core/approval-bus.js +703 -0
  27. package/dist/core/approval-bus.js.map +1 -0
  28. package/dist/core/approval-bus.synthetic.test.d.ts +2 -0
  29. package/dist/core/approval-bus.synthetic.test.d.ts.map +1 -0
  30. package/dist/core/approval-bus.synthetic.test.js +182 -0
  31. package/dist/core/approval-bus.synthetic.test.js.map +1 -0
  32. package/dist/core/approval-bus.test.d.ts +2 -0
  33. package/dist/core/approval-bus.test.d.ts.map +1 -0
  34. package/dist/core/approval-bus.test.js +537 -0
  35. package/dist/core/approval-bus.test.js.map +1 -0
  36. package/dist/core/approval-router.d.ts +95 -0
  37. package/dist/core/approval-router.d.ts.map +1 -0
  38. package/dist/core/approval-router.js +450 -0
  39. package/dist/core/approval-router.js.map +1 -0
  40. package/dist/core/approval-router.test.d.ts +2 -0
  41. package/dist/core/approval-router.test.d.ts.map +1 -0
  42. package/dist/core/approval-router.test.js +413 -0
  43. package/dist/core/approval-router.test.js.map +1 -0
  44. package/dist/core/audit-log.d.ts +55 -0
  45. package/dist/core/audit-log.d.ts.map +1 -0
  46. package/dist/core/audit-log.js +203 -0
  47. package/dist/core/audit-log.js.map +1 -0
  48. package/dist/core/bgjob-reader.d.ts +65 -0
  49. package/dist/core/bgjob-reader.d.ts.map +1 -0
  50. package/dist/core/bgjob-reader.js +212 -0
  51. package/dist/core/bgjob-reader.js.map +1 -0
  52. package/dist/core/bgjob-reader.test.d.ts +2 -0
  53. package/dist/core/bgjob-reader.test.d.ts.map +1 -0
  54. package/dist/core/bgjob-reader.test.js +178 -0
  55. package/dist/core/bgjob-reader.test.js.map +1 -0
  56. package/dist/core/circuit-breaker.d.ts +37 -0
  57. package/dist/core/circuit-breaker.d.ts.map +1 -0
  58. package/dist/core/circuit-breaker.js +115 -0
  59. package/dist/core/circuit-breaker.js.map +1 -0
  60. package/dist/core/commands/agent.d.ts +4 -0
  61. package/dist/core/commands/agent.d.ts.map +1 -0
  62. package/dist/core/commands/agent.js +21 -0
  63. package/dist/core/commands/agent.js.map +1 -0
  64. package/dist/core/commands/approval.d.ts +3 -0
  65. package/dist/core/commands/approval.d.ts.map +1 -0
  66. package/dist/core/commands/approval.js +44 -0
  67. package/dist/core/commands/approval.js.map +1 -0
  68. package/dist/core/commands/approval.test.d.ts +2 -0
  69. package/dist/core/commands/approval.test.d.ts.map +1 -0
  70. package/dist/core/commands/approval.test.js +85 -0
  71. package/dist/core/commands/approval.test.js.map +1 -0
  72. package/dist/core/commands/audit.d.ts +3 -0
  73. package/dist/core/commands/audit.d.ts.map +1 -0
  74. package/dist/core/commands/audit.js +84 -0
  75. package/dist/core/commands/audit.js.map +1 -0
  76. package/dist/core/commands/builtin.d.ts +3 -0
  77. package/dist/core/commands/builtin.d.ts.map +1 -0
  78. package/dist/core/commands/builtin.js +26 -0
  79. package/dist/core/commands/builtin.js.map +1 -0
  80. package/dist/core/commands/job.d.ts +3 -0
  81. package/dist/core/commands/job.d.ts.map +1 -0
  82. package/dist/core/commands/job.js +195 -0
  83. package/dist/core/commands/job.js.map +1 -0
  84. package/dist/core/commands/model.d.ts +9 -0
  85. package/dist/core/commands/model.d.ts.map +1 -0
  86. package/dist/core/commands/model.js +183 -0
  87. package/dist/core/commands/model.js.map +1 -0
  88. package/dist/core/commands/plan.d.ts +3 -0
  89. package/dist/core/commands/plan.d.ts.map +1 -0
  90. package/dist/core/commands/plan.js +75 -0
  91. package/dist/core/commands/plan.js.map +1 -0
  92. package/dist/core/commands/plan.test.d.ts +2 -0
  93. package/dist/core/commands/plan.test.d.ts.map +1 -0
  94. package/dist/core/commands/plan.test.js +122 -0
  95. package/dist/core/commands/plan.test.js.map +1 -0
  96. package/dist/core/commands/router.d.ts +3 -0
  97. package/dist/core/commands/router.d.ts.map +1 -0
  98. package/dist/core/commands/router.js +71 -0
  99. package/dist/core/commands/router.js.map +1 -0
  100. package/dist/core/commands/schedule.d.ts +3 -0
  101. package/dist/core/commands/schedule.d.ts.map +1 -0
  102. package/dist/core/commands/schedule.js +123 -0
  103. package/dist/core/commands/schedule.js.map +1 -0
  104. package/dist/core/commands/sessions.d.ts +3 -0
  105. package/dist/core/commands/sessions.d.ts.map +1 -0
  106. package/dist/core/commands/sessions.js +88 -0
  107. package/dist/core/commands/sessions.js.map +1 -0
  108. package/dist/core/commands/stats.d.ts +3 -0
  109. package/dist/core/commands/stats.d.ts.map +1 -0
  110. package/dist/core/commands/stats.js +73 -0
  111. package/dist/core/commands/stats.js.map +1 -0
  112. package/dist/core/commands/think.d.ts +3 -0
  113. package/dist/core/commands/think.d.ts.map +1 -0
  114. package/dist/core/commands/think.js +28 -0
  115. package/dist/core/commands/think.js.map +1 -0
  116. package/dist/core/commands/workspaces.d.ts +3 -0
  117. package/dist/core/commands/workspaces.d.ts.map +1 -0
  118. package/dist/core/commands/workspaces.js +47 -0
  119. package/dist/core/commands/workspaces.js.map +1 -0
  120. package/dist/core/config-schema.d.ts +58 -0
  121. package/dist/core/config-schema.d.ts.map +1 -0
  122. package/dist/core/config-schema.js +63 -0
  123. package/dist/core/config-schema.js.map +1 -0
  124. package/dist/core/cron.d.ts +29 -0
  125. package/dist/core/cron.d.ts.map +1 -0
  126. package/dist/core/cron.js +184 -0
  127. package/dist/core/cron.js.map +1 -0
  128. package/dist/core/event-bus.d.ts +80 -0
  129. package/dist/core/event-bus.d.ts.map +1 -0
  130. package/dist/core/event-bus.js +62 -0
  131. package/dist/core/event-bus.js.map +1 -0
  132. package/dist/core/intent-llm.d.ts +27 -0
  133. package/dist/core/intent-llm.d.ts.map +1 -0
  134. package/dist/core/intent-llm.js +170 -0
  135. package/dist/core/intent-llm.js.map +1 -0
  136. package/dist/core/intent.d.ts +12 -0
  137. package/dist/core/intent.d.ts.map +1 -0
  138. package/dist/core/intent.js +187 -0
  139. package/dist/core/intent.js.map +1 -0
  140. package/dist/core/job-board.d.ts +84 -0
  141. package/dist/core/job-board.d.ts.map +1 -0
  142. package/dist/core/job-board.js +379 -0
  143. package/dist/core/job-board.js.map +1 -0
  144. package/dist/core/logger.d.ts +6 -0
  145. package/dist/core/logger.d.ts.map +1 -0
  146. package/dist/core/logger.js +54 -0
  147. package/dist/core/logger.js.map +1 -0
  148. package/dist/core/metrics.d.ts +55 -0
  149. package/dist/core/metrics.d.ts.map +1 -0
  150. package/dist/core/metrics.js +291 -0
  151. package/dist/core/metrics.js.map +1 -0
  152. package/dist/core/onboarding.d.ts +94 -0
  153. package/dist/core/onboarding.d.ts.map +1 -0
  154. package/dist/core/onboarding.js +426 -0
  155. package/dist/core/onboarding.js.map +1 -0
  156. package/dist/core/onboarding.test.d.ts +2 -0
  157. package/dist/core/onboarding.test.d.ts.map +1 -0
  158. package/dist/core/onboarding.test.js +112 -0
  159. package/dist/core/onboarding.test.js.map +1 -0
  160. package/dist/core/rate-limiter.d.ts +44 -0
  161. package/dist/core/rate-limiter.d.ts.map +1 -0
  162. package/dist/core/rate-limiter.js +115 -0
  163. package/dist/core/rate-limiter.js.map +1 -0
  164. package/dist/core/registry.d.ts +32 -0
  165. package/dist/core/registry.d.ts.map +1 -0
  166. package/dist/core/registry.js +122 -0
  167. package/dist/core/registry.js.map +1 -0
  168. package/dist/core/router.d.ts +41 -0
  169. package/dist/core/router.d.ts.map +1 -0
  170. package/dist/core/router.js +431 -0
  171. package/dist/core/router.js.map +1 -0
  172. package/dist/core/schedule.d.ts +65 -0
  173. package/dist/core/schedule.d.ts.map +1 -0
  174. package/dist/core/schedule.js +316 -0
  175. package/dist/core/schedule.js.map +1 -0
  176. package/dist/core/session-subtasks.test.d.ts +2 -0
  177. package/dist/core/session-subtasks.test.d.ts.map +1 -0
  178. package/dist/core/session-subtasks.test.js +88 -0
  179. package/dist/core/session-subtasks.test.js.map +1 -0
  180. package/dist/core/session.d.ts +182 -0
  181. package/dist/core/session.d.ts.map +1 -0
  182. package/dist/core/session.js +774 -0
  183. package/dist/core/session.js.map +1 -0
  184. package/dist/core/sqlite-helper.d.ts +37 -0
  185. package/dist/core/sqlite-helper.d.ts.map +1 -0
  186. package/dist/core/sqlite-helper.js +79 -0
  187. package/dist/core/sqlite-helper.js.map +1 -0
  188. package/dist/core/transcribe.d.ts +25 -0
  189. package/dist/core/transcribe.d.ts.map +1 -0
  190. package/dist/core/transcribe.js +217 -0
  191. package/dist/core/transcribe.js.map +1 -0
  192. package/dist/core/transcribe.test.d.ts +2 -0
  193. package/dist/core/transcribe.test.d.ts.map +1 -0
  194. package/dist/core/transcribe.test.js +163 -0
  195. package/dist/core/transcribe.test.js.map +1 -0
  196. package/dist/core/types.d.ts +352 -0
  197. package/dist/core/types.d.ts.map +1 -0
  198. package/dist/core/types.js +3 -0
  199. package/dist/core/types.js.map +1 -0
  200. package/dist/core/workspace.d.ts +67 -0
  201. package/dist/core/workspace.d.ts.map +1 -0
  202. package/dist/core/workspace.js +113 -0
  203. package/dist/core/workspace.js.map +1 -0
  204. package/dist/index.d.ts +5 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +6 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/plugins/agents/acp/acp-adapter.d.ts +16 -0
  209. package/dist/plugins/agents/acp/acp-adapter.d.ts.map +1 -0
  210. package/dist/plugins/agents/acp/acp-adapter.js +49 -0
  211. package/dist/plugins/agents/acp/acp-adapter.js.map +1 -0
  212. package/dist/plugins/agents/acp/acp-client.d.ts +32 -0
  213. package/dist/plugins/agents/acp/acp-client.d.ts.map +1 -0
  214. package/dist/plugins/agents/acp/acp-client.js +175 -0
  215. package/dist/plugins/agents/acp/acp-client.js.map +1 -0
  216. package/dist/plugins/agents/acp/discovery.d.ts +19 -0
  217. package/dist/plugins/agents/acp/discovery.d.ts.map +1 -0
  218. package/dist/plugins/agents/acp/discovery.js +109 -0
  219. package/dist/plugins/agents/acp/discovery.js.map +1 -0
  220. package/dist/plugins/agents/acp/index.d.ts +4 -0
  221. package/dist/plugins/agents/acp/index.d.ts.map +1 -0
  222. package/dist/plugins/agents/acp/index.js +4 -0
  223. package/dist/plugins/agents/acp/index.js.map +1 -0
  224. package/dist/plugins/agents/acp/types.d.ts +62 -0
  225. package/dist/plugins/agents/acp/types.d.ts.map +1 -0
  226. package/dist/plugins/agents/acp/types.js +5 -0
  227. package/dist/plugins/agents/acp/types.js.map +1 -0
  228. package/dist/plugins/agents/claude-code/adapter.test.d.ts +2 -0
  229. package/dist/plugins/agents/claude-code/adapter.test.d.ts.map +1 -0
  230. package/dist/plugins/agents/claude-code/adapter.test.js +195 -0
  231. package/dist/plugins/agents/claude-code/adapter.test.js.map +1 -0
  232. package/dist/plugins/agents/claude-code/index.d.ts +25 -0
  233. package/dist/plugins/agents/claude-code/index.d.ts.map +1 -0
  234. package/dist/plugins/agents/claude-code/index.js +184 -0
  235. package/dist/plugins/agents/claude-code/index.js.map +1 -0
  236. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts +42 -0
  237. package/dist/plugins/agents/claude-code/mcp-approval-server.d.ts.map +1 -0
  238. package/dist/plugins/agents/claude-code/mcp-approval-server.js +235 -0
  239. package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -0
  240. package/dist/plugins/agents/claude-code/mcp-approval-server.test.d.ts +2 -0
  241. package/dist/plugins/agents/claude-code/mcp-approval-server.test.d.ts.map +1 -0
  242. package/dist/plugins/agents/claude-code/mcp-approval-server.test.js +188 -0
  243. package/dist/plugins/agents/claude-code/mcp-approval-server.test.js.map +1 -0
  244. package/dist/plugins/agents/codex/adapter.test.d.ts +2 -0
  245. package/dist/plugins/agents/codex/adapter.test.d.ts.map +1 -0
  246. package/dist/plugins/agents/codex/adapter.test.js +192 -0
  247. package/dist/plugins/agents/codex/adapter.test.js.map +1 -0
  248. package/dist/plugins/agents/codex/index.d.ts +37 -0
  249. package/dist/plugins/agents/codex/index.d.ts.map +1 -0
  250. package/dist/plugins/agents/codex/index.js +254 -0
  251. package/dist/plugins/agents/codex/index.js.map +1 -0
  252. package/dist/plugins/agents/copilot/index.d.ts +35 -0
  253. package/dist/plugins/agents/copilot/index.d.ts.map +1 -0
  254. package/dist/plugins/agents/copilot/index.js +182 -0
  255. package/dist/plugins/agents/copilot/index.js.map +1 -0
  256. package/dist/plugins/agents/opencode/adapter.test.d.ts +2 -0
  257. package/dist/plugins/agents/opencode/adapter.test.d.ts.map +1 -0
  258. package/dist/plugins/agents/opencode/adapter.test.js +139 -0
  259. package/dist/plugins/agents/opencode/adapter.test.js.map +1 -0
  260. package/dist/plugins/agents/opencode/http-adapter.test.d.ts +2 -0
  261. package/dist/plugins/agents/opencode/http-adapter.test.d.ts.map +1 -0
  262. package/dist/plugins/agents/opencode/http-adapter.test.js +492 -0
  263. package/dist/plugins/agents/opencode/http-adapter.test.js.map +1 -0
  264. package/dist/plugins/agents/opencode/index.d.ts +5 -0
  265. package/dist/plugins/agents/opencode/index.d.ts.map +1 -0
  266. package/dist/plugins/agents/opencode/index.js +30 -0
  267. package/dist/plugins/agents/opencode/index.js.map +1 -0
  268. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts +138 -0
  269. package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -0
  270. package/dist/plugins/agents/opencode/opencode-http-adapter.js +549 -0
  271. package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -0
  272. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +24 -0
  273. package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts.map +1 -0
  274. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +103 -0
  275. package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -0
  276. package/dist/plugins/agents/opencode/serve-manager.d.ts +27 -0
  277. package/dist/plugins/agents/opencode/serve-manager.d.ts.map +1 -0
  278. package/dist/plugins/agents/opencode/serve-manager.js +190 -0
  279. package/dist/plugins/agents/opencode/serve-manager.js.map +1 -0
  280. package/dist/plugins/messengers/discord/discord-adapter.d.ts +22 -0
  281. package/dist/plugins/messengers/discord/discord-adapter.d.ts.map +1 -0
  282. package/dist/plugins/messengers/discord/discord-adapter.js +241 -0
  283. package/dist/plugins/messengers/discord/discord-adapter.js.map +1 -0
  284. package/dist/plugins/messengers/discord/discord-adapter.test.d.ts +2 -0
  285. package/dist/plugins/messengers/discord/discord-adapter.test.d.ts.map +1 -0
  286. package/dist/plugins/messengers/discord/discord-adapter.test.js +332 -0
  287. package/dist/plugins/messengers/discord/discord-adapter.test.js.map +1 -0
  288. package/dist/plugins/messengers/discord/index.d.ts +4 -0
  289. package/dist/plugins/messengers/discord/index.d.ts.map +1 -0
  290. package/dist/plugins/messengers/discord/index.js +4 -0
  291. package/dist/plugins/messengers/discord/index.js.map +1 -0
  292. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts +11 -0
  293. package/dist/plugins/messengers/discord/markdown-to-discord.d.ts.map +1 -0
  294. package/dist/plugins/messengers/discord/markdown-to-discord.js +59 -0
  295. package/dist/plugins/messengers/discord/markdown-to-discord.js.map +1 -0
  296. package/dist/plugins/messengers/discord/types.d.ts +9 -0
  297. package/dist/plugins/messengers/discord/types.d.ts.map +1 -0
  298. package/dist/plugins/messengers/discord/types.js +3 -0
  299. package/dist/plugins/messengers/discord/types.js.map +1 -0
  300. package/dist/plugins/messengers/feishu/card-builder.d.ts +23 -0
  301. package/dist/plugins/messengers/feishu/card-builder.d.ts.map +1 -0
  302. package/dist/plugins/messengers/feishu/card-builder.js +89 -0
  303. package/dist/plugins/messengers/feishu/card-builder.js.map +1 -0
  304. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts +33 -0
  305. package/dist/plugins/messengers/feishu/feishu-adapter.d.ts.map +1 -0
  306. package/dist/plugins/messengers/feishu/feishu-adapter.js +195 -0
  307. package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -0
  308. package/dist/plugins/messengers/feishu/feishu-client.d.ts +44 -0
  309. package/dist/plugins/messengers/feishu/feishu-client.d.ts.map +1 -0
  310. package/dist/plugins/messengers/feishu/feishu-client.js +120 -0
  311. package/dist/plugins/messengers/feishu/feishu-client.js.map +1 -0
  312. package/dist/plugins/messengers/feishu/feishu-dedup.test.d.ts +2 -0
  313. package/dist/plugins/messengers/feishu/feishu-dedup.test.d.ts.map +1 -0
  314. package/dist/plugins/messengers/feishu/feishu-dedup.test.js +70 -0
  315. package/dist/plugins/messengers/feishu/feishu-dedup.test.js.map +1 -0
  316. package/dist/plugins/messengers/feishu/index.d.ts +4 -0
  317. package/dist/plugins/messengers/feishu/index.d.ts.map +1 -0
  318. package/dist/plugins/messengers/feishu/index.js +4 -0
  319. package/dist/plugins/messengers/feishu/index.js.map +1 -0
  320. package/dist/plugins/messengers/feishu/types.d.ts +113 -0
  321. package/dist/plugins/messengers/feishu/types.d.ts.map +1 -0
  322. package/dist/plugins/messengers/feishu/types.js +4 -0
  323. package/dist/plugins/messengers/feishu/types.js.map +1 -0
  324. package/dist/plugins/messengers/telegram/index.d.ts +4 -0
  325. package/dist/plugins/messengers/telegram/index.d.ts.map +1 -0
  326. package/dist/plugins/messengers/telegram/index.js +4 -0
  327. package/dist/plugins/messengers/telegram/index.js.map +1 -0
  328. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts +5 -0
  329. package/dist/plugins/messengers/telegram/markdown-to-html.d.ts.map +1 -0
  330. package/dist/plugins/messengers/telegram/markdown-to-html.js +186 -0
  331. package/dist/plugins/messengers/telegram/markdown-to-html.js.map +1 -0
  332. package/dist/plugins/messengers/telegram/media-download.d.ts +51 -0
  333. package/dist/plugins/messengers/telegram/media-download.d.ts.map +1 -0
  334. package/dist/plugins/messengers/telegram/media-download.js +224 -0
  335. package/dist/plugins/messengers/telegram/media-download.js.map +1 -0
  336. package/dist/plugins/messengers/telegram/media-download.test.d.ts +2 -0
  337. package/dist/plugins/messengers/telegram/media-download.test.d.ts.map +1 -0
  338. package/dist/plugins/messengers/telegram/media-download.test.js +125 -0
  339. package/dist/plugins/messengers/telegram/media-download.test.js.map +1 -0
  340. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +62 -0
  341. package/dist/plugins/messengers/telegram/telegram-adapter.d.ts.map +1 -0
  342. package/dist/plugins/messengers/telegram/telegram-adapter.js +653 -0
  343. package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -0
  344. package/dist/plugins/messengers/telegram/types.d.ts +47 -0
  345. package/dist/plugins/messengers/telegram/types.d.ts.map +1 -0
  346. package/dist/plugins/messengers/telegram/types.js +3 -0
  347. package/dist/plugins/messengers/telegram/types.js.map +1 -0
  348. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts +68 -0
  349. package/dist/plugins/messengers/wechat/ilink-adapter.d.ts.map +1 -0
  350. package/dist/plugins/messengers/wechat/ilink-adapter.js +483 -0
  351. package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -0
  352. package/dist/plugins/messengers/wechat/ilink-client.d.ts +66 -0
  353. package/dist/plugins/messengers/wechat/ilink-client.d.ts.map +1 -0
  354. package/dist/plugins/messengers/wechat/ilink-client.js +288 -0
  355. package/dist/plugins/messengers/wechat/ilink-client.js.map +1 -0
  356. package/dist/plugins/messengers/wechat/ilink-types.d.ts +173 -0
  357. package/dist/plugins/messengers/wechat/ilink-types.d.ts.map +1 -0
  358. package/dist/plugins/messengers/wechat/ilink-types.js +12 -0
  359. package/dist/plugins/messengers/wechat/ilink-types.js.map +1 -0
  360. package/dist/utils/backoff.d.ts +35 -0
  361. package/dist/utils/backoff.d.ts.map +1 -0
  362. package/dist/utils/backoff.js +59 -0
  363. package/dist/utils/backoff.js.map +1 -0
  364. package/dist/utils/cross-platform.d.ts +26 -0
  365. package/dist/utils/cross-platform.d.ts.map +1 -0
  366. package/dist/utils/cross-platform.js +58 -0
  367. package/dist/utils/cross-platform.js.map +1 -0
  368. package/dist/utils/message-split.d.ts +14 -0
  369. package/dist/utils/message-split.d.ts.map +1 -0
  370. package/dist/utils/message-split.js +65 -0
  371. package/dist/utils/message-split.js.map +1 -0
  372. package/dist/utils/safe-equal.d.ts +2 -0
  373. package/dist/utils/safe-equal.d.ts.map +1 -0
  374. package/dist/utils/safe-equal.js +11 -0
  375. package/dist/utils/safe-equal.js.map +1 -0
  376. package/dist/web/public/_app.js +196 -0
  377. package/dist/web/public/index.html +935 -0
  378. package/dist/web/public/settings.html +1181 -0
  379. package/dist/web/public/tasks.html +1827 -0
  380. package/dist/web/server.d.ts +11 -0
  381. package/dist/web/server.d.ts.map +1 -0
  382. package/dist/web/server.js +1820 -0
  383. package/dist/web/server.js.map +1 -0
  384. package/package.json +73 -0
@@ -0,0 +1,653 @@
1
+ // Telegram Bot API Adapter using grammy
2
+ // Implements MessengerAdapter interface with native typing indicator support
3
+ import { Bot } from 'grammy';
4
+ import { markdownToTelegramHtml } from './markdown-to-html.js';
5
+ import { cleanupOldMedia, downloadToMediaRoot, pickExtension, } from './media-download.js';
6
+ import { splitMessage } from '../../../utils/message-split.js';
7
+ import { Backoff } from '../../../utils/backoff.js';
8
+ import { logger as rootLogger } from '../../../core/logger.js';
9
+ import { transcribe, detectProvider, TranscribeError } from '../../../core/transcribe.js';
10
+ const log = rootLogger.child({ component: 'telegram' });
11
+ export class TelegramAdapter {
12
+ name = 'telegram';
13
+ bot = null;
14
+ config = null;
15
+ messageHandler;
16
+ buttonHandler;
17
+ isRunning = false;
18
+ typingIntervals = new Map();
19
+ // grammy 的 bot.start() 长轮询偶尔会被一次网络抖动 wedge —— 既不报错也不
20
+ // resolve,看起来还在跑但不再 fetch updates,TG 那边 pending_update_count
21
+ // 一路涨。watchdog 周期性 ping getMe;连续多次失败就强制 stop+start,让
22
+ // 卡死的 polling loop 重置。
23
+ watchdogTimer;
24
+ consecutivePingFailures = 0;
25
+ static WATCHDOG_INTERVAL_MS = 60_000;
26
+ static WATCHDOG_FAILURE_THRESHOLD = 3; // ~3min 无响应就重启 polling
27
+ mediaCleanupTimer;
28
+ static MEDIA_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // hourly
29
+ async start() {
30
+ // Load config
31
+ const { readFile } = await import('fs/promises');
32
+ const { homedir } = await import('os');
33
+ const { join } = await import('path');
34
+ const configPath = join(homedir(), '.im-hub', 'config.json');
35
+ try {
36
+ const data = await readFile(configPath, 'utf-8');
37
+ const config = JSON.parse(data);
38
+ this.config = config.telegram;
39
+ }
40
+ catch {
41
+ throw new Error('Telegram config not found. Run "im-hub config telegram" first.');
42
+ }
43
+ if (!this.config?.botToken) {
44
+ throw new Error('Telegram bot token not configured. Run "im-hub config telegram" first.');
45
+ }
46
+ // Initialize bot
47
+ this.bot = new Bot(this.config.botToken);
48
+ // Set up message handler.
49
+ //
50
+ // CRITICAL: do NOT await messageHandler here. grammy processes updates
51
+ // sequentially per chat — if the handler awaits an agent run that's
52
+ // waiting on a separate IM-side approval reply, polling stalls and the
53
+ // user's approval reply piles up in TG's pending_update_count, never
54
+ // reaching us. Fire-and-forget mirrors how the WeChat ilink adapter
55
+ // dispatches (ilink-adapter.ts:258).
56
+ this.bot.on('message:text', (ctx) => {
57
+ // Ignore messages from bots
58
+ if (ctx.message.from.is_bot)
59
+ return;
60
+ if (!this.messageHandler)
61
+ return;
62
+ const message = {
63
+ id: ctx.message.message_id.toString(),
64
+ threadId: ctx.chat.id.toString(),
65
+ userId: ctx.message.from?.id?.toString() || 'unknown',
66
+ text: ctx.message.text || '',
67
+ timestamp: new Date(ctx.message.date * 1000),
68
+ channelId: this.config?.channelId || 'default',
69
+ };
70
+ const msgCtx = {
71
+ message,
72
+ platform: 'telegram',
73
+ channelId: this.config?.channelId || 'default',
74
+ };
75
+ this.messageHandler(msgCtx).catch((err) => {
76
+ const errMsg = err instanceof Error ? err.message : String(err);
77
+ const stack = err instanceof Error ? err.stack : undefined;
78
+ log.error({ err: errMsg, stack, threadId: message.threadId }, 'Error in message handler');
79
+ });
80
+ });
81
+ // Media handlers — TG photos and image documents. We await the download
82
+ // inside the handler (it's bounded; typical TG photo is < 1 s, hard cap
83
+ // 20 MB) so the resulting Message reflects the image being on disk before
84
+ // we kick off the agent. messageHandler itself is fire-and-forget for the
85
+ // same reasons as message:text above. Order across photo / text within a
86
+ // chat is preserved because grammy serializes updates per chat.
87
+ this.bot.on('message:photo', (ctx) => {
88
+ if (ctx.message.from?.is_bot)
89
+ return;
90
+ if (!this.messageHandler)
91
+ return;
92
+ // Largest size is the last entry — TG ships scaled-down siblings for
93
+ // bandwidth-conscious clients which we ignore.
94
+ const photo = ctx.message.photo[ctx.message.photo.length - 1];
95
+ void this.handleMediaUpload(ctx, photo.file_id, undefined, ctx.message.caption ?? '');
96
+ });
97
+ this.bot.on('message:document', (ctx) => {
98
+ if (ctx.message.from?.is_bot)
99
+ return;
100
+ if (!this.messageHandler)
101
+ return;
102
+ const doc = ctx.message.document;
103
+ // Image documents → media upload path. Audio documents → voice path.
104
+ // Anything else gets dropped (videos / archives / misc).
105
+ if (doc.mime_type?.startsWith('image/')) {
106
+ void this.handleMediaUpload(ctx, doc.file_id, doc.mime_type, ctx.message.caption ?? '');
107
+ return;
108
+ }
109
+ if (doc.mime_type?.startsWith('audio/')) {
110
+ // Document type has no `duration` field even when MIME is audio/*;
111
+ // only message:voice / message:audio carry it. Pass undefined.
112
+ void this.handleVoiceUpload(ctx, doc.file_id, doc.mime_type, ctx.message.caption ?? '', undefined);
113
+ return;
114
+ }
115
+ log.debug({ mime: doc.mime_type, chatId: ctx.chat.id }, 'ignoring non-image/audio document');
116
+ });
117
+ // Voice messages (the mic-button "press and hold" recording, OGG OPUS).
118
+ // Caption is rare on voice but TG allows it.
119
+ this.bot.on('message:voice', (ctx) => {
120
+ if (ctx.message.from?.is_bot)
121
+ return;
122
+ if (!this.messageHandler)
123
+ return;
124
+ const v = ctx.message.voice;
125
+ void this.handleVoiceUpload(ctx, v.file_id, v.mime_type, ctx.message.caption ?? '', v.duration);
126
+ });
127
+ // Audio messages (a music file or the "Audio" attachment button).
128
+ this.bot.on('message:audio', (ctx) => {
129
+ if (ctx.message.from?.is_bot)
130
+ return;
131
+ if (!this.messageHandler)
132
+ return;
133
+ const a = ctx.message.audio;
134
+ void this.handleVoiceUpload(ctx, a.file_id, a.mime_type, ctx.message.caption ?? '', a.duration);
135
+ });
136
+ // Inline-button taps (approval cards). Same fire-and-forget discipline
137
+ // as message:text — buttonHandler may resolve a pending approval which
138
+ // calls back into editApprovalCard; we don't want that to block grammy's
139
+ // sequential update queue.
140
+ //
141
+ // Telegram requires answerCallbackQuery within ~1s or the client shows
142
+ // a spinner. We wrap ack() so the handler can call it explicitly when
143
+ // it has a meaningful toast; if it doesn't, we send an empty ack at the
144
+ // end as a safety net (idempotent — TG ignores the second call).
145
+ this.bot.on('callback_query:data', (ctx) => {
146
+ if (!this.buttonHandler) {
147
+ void ctx.answerCallbackQuery({ text: '系统未就绪' }).catch(() => { });
148
+ return;
149
+ }
150
+ const data = ctx.callbackQuery.data;
151
+ const from = ctx.callbackQuery.from;
152
+ const msg = ctx.callbackQuery.message;
153
+ if (!msg) {
154
+ void ctx.answerCallbackQuery({ text: '消息已不可用' }).catch(() => { });
155
+ return;
156
+ }
157
+ // Optional allowlist gate. Empty / missing list = allow anyone (matches
158
+ // the text-reply path). When configured, refuse with a toast — the
159
+ // refusal does NOT resolve the pending, so an authorized user can
160
+ // still click later.
161
+ const allowlist = this.config?.approvalAllowlist;
162
+ if (allowlist && allowlist.length > 0) {
163
+ const fromIdStr = from.id.toString();
164
+ if (!allowlist.includes(fromIdStr)) {
165
+ log.warn({
166
+ event: 'telegram.approval.unauthorized_click',
167
+ userId: fromIdStr,
168
+ username: from.username,
169
+ chatType: msg.chat.type,
170
+ chatId: msg.chat.id,
171
+ }, 'Unauthorized button click rejected by allowlist');
172
+ void ctx.answerCallbackQuery({ text: '无权审批此请求' }).catch(() => { });
173
+ return;
174
+ }
175
+ }
176
+ let acked = false;
177
+ const cb = {
178
+ data,
179
+ threadId: msg.chat.id.toString(),
180
+ userId: from.id.toString(),
181
+ userDisplay: from.username ? `@${from.username}` : (from.first_name || from.id.toString()),
182
+ messageId: msg.message_id.toString(),
183
+ ack: async (text) => {
184
+ if (acked)
185
+ return;
186
+ acked = true;
187
+ try {
188
+ await ctx.answerCallbackQuery(text ? { text } : undefined);
189
+ }
190
+ catch (err) {
191
+ log.warn({ err: String(err) }, 'answerCallbackQuery failed');
192
+ }
193
+ },
194
+ };
195
+ this.buttonHandler(cb)
196
+ .catch((err) => {
197
+ log.error({ err: String(err), data }, 'Error in button handler');
198
+ })
199
+ .finally(() => {
200
+ if (!acked) {
201
+ // Safety net so the user's TG client doesn't keep spinning when
202
+ // the handler forgot to ack.
203
+ void ctx.answerCallbackQuery().catch(() => { });
204
+ }
205
+ });
206
+ });
207
+ // Start bot in background. grammy's start() uses long polling and resolves
208
+ // only when polling stops. We wrap it in a self-healing loop so that:
209
+ // 1. an unexpected resolve while still isRunning → restart polling
210
+ // 2. a thrown error → log it and retry after a short backoff
211
+ // Combined with the watchdog (below), this defends against the silent
212
+ // wedge mode where bot.start() neither resolves nor rejects but stops
213
+ // fetching updates.
214
+ log.info('Starting bot with long polling');
215
+ this.isRunning = true;
216
+ void this.runPollingLoop();
217
+ this.startWatchdog();
218
+ this.startMediaCleanup();
219
+ log.info('Telegram adapter started');
220
+ }
221
+ /** Run media cleanup once now (so a long-running im-hub doesn't accumulate
222
+ * files indefinitely when restarts are infrequent) and then hourly. The
223
+ * hourly cadence matches the typical 7-day TTL with plenty of slack. */
224
+ startMediaCleanup() {
225
+ if (this.mediaCleanupTimer)
226
+ clearInterval(this.mediaCleanupTimer);
227
+ void cleanupOldMedia().catch((err) => {
228
+ log.warn({ err: String(err), event: 'telegram.media.cleanup_failed' }, 'startup media cleanup failed');
229
+ });
230
+ this.mediaCleanupTimer = setInterval(() => {
231
+ void cleanupOldMedia().catch((err) => {
232
+ log.warn({ err: String(err), event: 'telegram.media.cleanup_failed' }, 'periodic media cleanup failed');
233
+ });
234
+ }, TelegramAdapter.MEDIA_CLEANUP_INTERVAL_MS);
235
+ }
236
+ async runPollingLoop() {
237
+ // M9: exponential backoff with jitter replaces the previous fixed 2s
238
+ // (unexpected resolve) / 5s (error) delays. Auth-revoked states no
239
+ // longer hammer the API at a fixed cadence, and fleet-wide network
240
+ // recovery doesn't lock-step its reconnects.
241
+ //
242
+ // baseMs = 2_000 preserves the previous "first error → ~2s" feel so
243
+ // operators eyeballing logs during normal restarts see the same
244
+ // ballpark; capMs = 60_000 prevents indefinite drift past 1 minute.
245
+ const backoff = new Backoff({ baseMs: 2_000, capMs: 60_000, jitter: 0.5 });
246
+ /** Reset the backoff if bot.start() ran healthy for at least this long
247
+ * before erroring. Without this, a single transient error at hour 12
248
+ * of operation would still queue the same exponential tail as a
249
+ * startup-time crash loop. 30 s is comfortably longer than any
250
+ * realistic handshake / first-fetch round-trip. */
251
+ const HEALTHY_RUN_THRESHOLD_MS = 30_000;
252
+ while (this.isRunning && this.bot) {
253
+ const startedAt = Date.now();
254
+ try {
255
+ await this.bot.start();
256
+ if (!this.isRunning) {
257
+ log.info('Bot stopped gracefully');
258
+ return;
259
+ }
260
+ // bot.start() resolved while we still want to be running — that's
261
+ // grammy's silent-wedge mode (or an internal early return). Treat
262
+ // it as a fault and back off before restarting.
263
+ if (Date.now() - startedAt >= HEALTHY_RUN_THRESHOLD_MS)
264
+ backoff.reset();
265
+ const delayMs = backoff.nextDelayMs();
266
+ log.warn({
267
+ event: 'telegram.polling.unexpected_stop',
268
+ attempt: backoff.currentAttempt(),
269
+ delayMs,
270
+ }, 'bot.start() resolved while still running; restarting after backoff');
271
+ await new Promise((r) => setTimeout(r, delayMs));
272
+ }
273
+ catch (err) {
274
+ if (!this.isRunning)
275
+ return;
276
+ if (Date.now() - startedAt >= HEALTHY_RUN_THRESHOLD_MS)
277
+ backoff.reset();
278
+ const delayMs = backoff.nextDelayMs();
279
+ log.error({
280
+ err: err instanceof Error ? err.message : String(err),
281
+ event: 'telegram.polling.error',
282
+ attempt: backoff.currentAttempt(),
283
+ delayMs,
284
+ }, 'Bot polling error; restarting after backoff');
285
+ await new Promise((r) => setTimeout(r, delayMs));
286
+ }
287
+ }
288
+ }
289
+ startWatchdog() {
290
+ if (this.watchdogTimer)
291
+ clearInterval(this.watchdogTimer);
292
+ this.consecutivePingFailures = 0;
293
+ this.watchdogTimer = setInterval(async () => {
294
+ if (!this.isRunning || !this.bot)
295
+ return;
296
+ try {
297
+ await this.bot.api.getMe();
298
+ if (this.consecutivePingFailures > 0) {
299
+ log.info({ event: 'telegram.watchdog.recovered' }, 'getMe ping recovered');
300
+ }
301
+ this.consecutivePingFailures = 0;
302
+ }
303
+ catch (err) {
304
+ this.consecutivePingFailures += 1;
305
+ log.warn({
306
+ event: 'telegram.watchdog.ping_failed',
307
+ consecutive: this.consecutivePingFailures,
308
+ err: err instanceof Error ? err.message : String(err),
309
+ }, 'Watchdog getMe ping failed');
310
+ if (this.consecutivePingFailures >= TelegramAdapter.WATCHDOG_FAILURE_THRESHOLD) {
311
+ log.error({ event: 'telegram.watchdog.restarting_polling' }, 'Polling appears wedged; forcing bot.stop() to trigger restart');
312
+ this.consecutivePingFailures = 0;
313
+ try {
314
+ await this.bot.stop();
315
+ }
316
+ catch { /* ignore */ }
317
+ // runPollingLoop will see bot.start() resolve and restart it.
318
+ }
319
+ }
320
+ }, TelegramAdapter.WATCHDOG_INTERVAL_MS);
321
+ }
322
+ async stop() {
323
+ this.isRunning = false;
324
+ if (this.watchdogTimer) {
325
+ clearInterval(this.watchdogTimer);
326
+ this.watchdogTimer = undefined;
327
+ }
328
+ if (this.mediaCleanupTimer) {
329
+ clearInterval(this.mediaCleanupTimer);
330
+ this.mediaCleanupTimer = undefined;
331
+ }
332
+ // Clean up all typing intervals
333
+ for (const interval of this.typingIntervals.values()) {
334
+ clearInterval(interval);
335
+ }
336
+ this.typingIntervals.clear();
337
+ if (this.bot) {
338
+ await this.bot.stop();
339
+ this.bot = null;
340
+ }
341
+ log.info('Telegram adapter stopped');
342
+ }
343
+ onMessage(handler) {
344
+ this.messageHandler = handler;
345
+ }
346
+ /**
347
+ * Download a TG photo / image-document, save it under MEDIA_ROOT, and surface
348
+ * the result to messageHandler as a Message whose `text` includes the
349
+ * caption (if any) plus a "[图片附件:/path/x.jpg]" marker — claude-code
350
+ * picks that up and uses Read to view it.
351
+ *
352
+ * On download failure we still call messageHandler with a "[图片下载失败]"
353
+ * marker so the user's interaction isn't silently dropped — they can resend
354
+ * or be told what went wrong.
355
+ */
356
+ async handleMediaUpload(ctx, fileId, mime, caption) {
357
+ if (!this.bot || !ctx.chat || !ctx.message)
358
+ return;
359
+ const chatId = ctx.chat.id;
360
+ const msgId = ctx.message.message_id;
361
+ let attachmentLine;
362
+ const botToken = this.config.botToken;
363
+ // Scrub the bot token from any string before it reaches a log line or a
364
+ // user-facing chat reply. The token rides inside the file URL we hand to
365
+ // curl, and curl's stderr can echo the URL on TLS / DNS failures.
366
+ const scrub = (s) => botToken ? s.split(botToken).join('[REDACTED]') : s;
367
+ try {
368
+ const file = await this.bot.api.getFile(fileId);
369
+ if (!file.file_path)
370
+ throw new Error('TG returned no file_path');
371
+ const ext = pickExtension(file.file_path, mime);
372
+ const url = `https://api.telegram.org/file/bot${botToken}/${file.file_path}`;
373
+ const { path } = await downloadToMediaRoot({
374
+ url,
375
+ subdir: `telegram/${chatId}`,
376
+ filename: `${msgId}.${ext}`,
377
+ });
378
+ attachmentLine = `[图片附件:${path}]`;
379
+ }
380
+ catch (err) {
381
+ const msg = scrub(err instanceof Error ? err.message : String(err));
382
+ log.warn({ event: 'telegram.media.download_failed', err: msg, chatId, msgId }, 'media download failed');
383
+ attachmentLine = `[图片附件下载失败:${msg}]`;
384
+ }
385
+ const text = caption ? `${caption}\n\n${attachmentLine}` : attachmentLine;
386
+ const message = {
387
+ id: msgId.toString(),
388
+ threadId: chatId.toString(),
389
+ userId: ctx.message.from?.id?.toString() || 'unknown',
390
+ text,
391
+ timestamp: new Date(ctx.message.date * 1000),
392
+ channelId: this.config?.channelId || 'default',
393
+ };
394
+ const msgCtx = {
395
+ message,
396
+ platform: 'telegram',
397
+ channelId: this.config?.channelId || 'default',
398
+ };
399
+ if (!this.messageHandler)
400
+ return;
401
+ this.messageHandler(msgCtx).catch((err) => {
402
+ log.error({
403
+ err: err instanceof Error ? err.message : String(err),
404
+ threadId: message.threadId,
405
+ }, 'Error in media message handler');
406
+ });
407
+ }
408
+ /**
409
+ * Download a TG voice / audio message, transcribe it via whichever provider
410
+ * is configured (OpenAI Whisper or whisper.cpp), and surface the transcript
411
+ * to messageHandler as Message.text. The downloaded audio file path is
412
+ * also included so the agent can reference it (e.g. send it back, replay).
413
+ *
414
+ * Failures are surfaced as text markers, not silent drops:
415
+ * - download failure → "[语音附件下载失败:…]"
416
+ * - no provider configured → "[语音附件未转写:未配置 OPENAI_API_KEY 或 IMHUB_WHISPERCPP_BIN]"
417
+ * - transcribe error → "[语音转写失败(${provider}):…]"
418
+ *
419
+ * Since transcription can take 5-30s on a slow CPU + whisper.cpp medium,
420
+ * we fire-and-forget the entire operation so grammy's update queue keeps
421
+ * draining for other chats. Within this chat, ordering is still serialized
422
+ * by grammy.
423
+ */
424
+ async handleVoiceUpload(ctx, fileId, mime, caption, durationSec) {
425
+ if (!this.bot || !ctx.chat || !ctx.message)
426
+ return;
427
+ const chatId = ctx.chat.id;
428
+ const msgId = ctx.message.message_id;
429
+ let savedPath = null;
430
+ let downloadErr = null;
431
+ const botToken = this.config.botToken;
432
+ const scrub = (s) => botToken ? s.split(botToken).join('[REDACTED]') : s;
433
+ try {
434
+ const file = await this.bot.api.getFile(fileId);
435
+ if (!file.file_path)
436
+ throw new Error('TG returned no file_path');
437
+ const ext = pickExtension(file.file_path, mime);
438
+ const url = `https://api.telegram.org/file/bot${botToken}/${file.file_path}`;
439
+ const { path } = await downloadToMediaRoot({
440
+ url,
441
+ subdir: `telegram/${chatId}`,
442
+ filename: `${msgId}.${ext}`,
443
+ });
444
+ savedPath = path;
445
+ }
446
+ catch (err) {
447
+ downloadErr = scrub(err instanceof Error ? err.message : String(err));
448
+ log.warn({ event: 'telegram.voice.download_failed', err: downloadErr, chatId, msgId }, 'voice download failed');
449
+ }
450
+ let voiceLine;
451
+ if (!savedPath) {
452
+ voiceLine = `[语音附件下载失败:${downloadErr}]`;
453
+ }
454
+ else if (detectProvider() === 'none') {
455
+ voiceLine = `[语音附件未转写(未配置 OPENAI_API_KEY 或 IMHUB_WHISPERCPP_BIN):${savedPath}]`;
456
+ }
457
+ else {
458
+ try {
459
+ const result = await transcribe(savedPath, { language: 'zh' });
460
+ const dur = durationSec != null ? `${durationSec}s, ` : '';
461
+ voiceLine = [
462
+ `[语音转写(${dur}provider=${result.provider}, ${result.elapsedMs}ms):`,
463
+ result.text || '(空)',
464
+ `源文件:${savedPath}]`,
465
+ ].join('\n');
466
+ }
467
+ catch (err) {
468
+ const reason = err instanceof TranscribeError
469
+ ? `${err.provider}: ${err.reason}`
470
+ : err instanceof Error ? err.message : String(err);
471
+ voiceLine = `[语音转写失败(${reason})\n源文件:${savedPath}]`;
472
+ }
473
+ }
474
+ const text = caption ? `${caption}\n\n${voiceLine}` : voiceLine;
475
+ const message = {
476
+ id: msgId.toString(),
477
+ threadId: chatId.toString(),
478
+ userId: ctx.message.from?.id?.toString() || 'unknown',
479
+ text,
480
+ timestamp: new Date(ctx.message.date * 1000),
481
+ channelId: this.config?.channelId || 'default',
482
+ };
483
+ const msgCtx = {
484
+ message,
485
+ platform: 'telegram',
486
+ channelId: this.config?.channelId || 'default',
487
+ };
488
+ if (!this.messageHandler)
489
+ return;
490
+ this.messageHandler(msgCtx).catch((err) => {
491
+ log.error({
492
+ err: err instanceof Error ? err.message : String(err),
493
+ threadId: message.threadId,
494
+ }, 'Error in voice message handler');
495
+ });
496
+ }
497
+ async sendMessage(threadId, text) {
498
+ if (!this.bot) {
499
+ throw new Error('Telegram adapter not started');
500
+ }
501
+ const htmlText = markdownToTelegramHtml(text);
502
+ const chunks = splitMessage(htmlText, { maxLength: 4000, addContinuationMarker: false });
503
+ for (const chunk of chunks) {
504
+ await this.bot.api.sendMessage(threadId, chunk, { parse_mode: 'HTML' });
505
+ }
506
+ }
507
+ onButtonCallback(handler) {
508
+ this.buttonHandler = handler;
509
+ }
510
+ async sendApprovalCard(threadId, prompt) {
511
+ if (!this.bot)
512
+ throw new Error('Telegram adapter not started');
513
+ const text = renderApprovalCardHtml(prompt);
514
+ const reply_markup = renderApprovalKeyboard(prompt);
515
+ const sent = await this.bot.api.sendMessage(threadId, text, {
516
+ parse_mode: 'HTML',
517
+ reply_markup,
518
+ });
519
+ return { messageId: sent.message_id.toString() };
520
+ }
521
+ async editApprovalCard(threadId, messageId, outcome) {
522
+ if (!this.bot)
523
+ return;
524
+ const numericId = Number.parseInt(messageId, 10);
525
+ if (!Number.isFinite(numericId)) {
526
+ log.warn({ messageId }, 'editApprovalCard: non-numeric messageId');
527
+ return;
528
+ }
529
+ const text = renderApprovalOutcomeHtml(outcome);
530
+ try {
531
+ await this.bot.api.editMessageText(threadId, numericId, text, {
532
+ parse_mode: 'HTML',
533
+ // Omit reply_markup → buttons stay. We want to drop them, so pass
534
+ // an empty inline_keyboard to clear.
535
+ reply_markup: { inline_keyboard: [] },
536
+ });
537
+ }
538
+ catch (err) {
539
+ // Common: "message is not modified", "message can't be edited" (>48h),
540
+ // "message to edit not found". All non-fatal — bus already resolved.
541
+ log.warn({ err: String(err), messageId }, 'editApprovalCard failed (non-fatal)');
542
+ }
543
+ }
544
+ async sendTyping(threadId, isTyping) {
545
+ if (!this.bot) {
546
+ return;
547
+ }
548
+ if (isTyping) {
549
+ // Send initial typing action
550
+ try {
551
+ await this.bot.api.sendChatAction(threadId, 'typing');
552
+ }
553
+ catch {
554
+ // Ignore errors during typing
555
+ }
556
+ // Clear any existing interval
557
+ const existing = this.typingIntervals.get(threadId);
558
+ if (existing) {
559
+ clearInterval(existing);
560
+ }
561
+ // Set up periodic refresh every 4 seconds (Telegram expires after ~5s)
562
+ const interval = setInterval(async () => {
563
+ try {
564
+ await this.bot.api.sendChatAction(threadId, 'typing');
565
+ }
566
+ catch {
567
+ // Ignore errors during typing refresh
568
+ }
569
+ }, 4000);
570
+ this.typingIntervals.set(threadId, interval);
571
+ }
572
+ else {
573
+ // Clear the refresh interval
574
+ const interval = this.typingIntervals.get(threadId);
575
+ if (interval) {
576
+ clearInterval(interval);
577
+ this.typingIntervals.delete(threadId);
578
+ }
579
+ // Note: Telegram has no "cancel" action - typing just expires
580
+ }
581
+ }
582
+ }
583
+ function escapeHtml(s) {
584
+ // M12: cover quote characters too. Most usages place the value in a text
585
+ // node where `'` and `"` are harmless, but the approval-card template
586
+ // builds attributes like <a href="..."> with user-derived values, where
587
+ // an unescaped quote could close the attribute and inject markup.
588
+ return s.replace(/&/g, '&amp;')
589
+ .replace(/</g, '&lt;')
590
+ .replace(/>/g, '&gt;')
591
+ .replace(/"/g, '&quot;')
592
+ .replace(/'/g, '&#39;');
593
+ }
594
+ function formatHm(d) {
595
+ const hh = d.getHours().toString().padStart(2, '0');
596
+ const mm = d.getMinutes().toString().padStart(2, '0');
597
+ return `${hh}:${mm}`;
598
+ }
599
+ function renderApprovalCardHtml(p) {
600
+ const tool = escapeHtml(p.toolName);
601
+ const input = escapeHtml(p.inputJson);
602
+ const reqShort = escapeHtml(p.reqId.slice(0, 8));
603
+ if (p.mode === 'auto-allow') {
604
+ const sec = p.graceSeconds ?? 5;
605
+ return [
606
+ `⏱ <b>自动放行中</b>(${sec}s 后执行)`,
607
+ `工具:<b>${tool}</b>`,
608
+ `入参:<pre>${input}</pre>`,
609
+ `点 ❌ 拒绝可同时撤销该工具的自动放行规则`,
610
+ `<i>req: ${reqShort}</i>`,
611
+ ].join('\n');
612
+ }
613
+ return [
614
+ `🔐 <b>工具调用审批</b>`,
615
+ `工具:<b>${tool}</b>`,
616
+ `入参:<pre>${input}</pre>`,
617
+ `<i>req: ${reqShort}</i>`,
618
+ ].join('\n');
619
+ }
620
+ function renderApprovalKeyboard(p) {
621
+ const r = p.reqId;
622
+ if (p.mode === 'auto-allow') {
623
+ return {
624
+ inline_keyboard: [[
625
+ { text: '❌ 拒绝(撤销规则)', callback_data: `apv:${r}:n` },
626
+ ]],
627
+ };
628
+ }
629
+ return {
630
+ inline_keyboard: [
631
+ [
632
+ { text: '✅ 同意', callback_data: `apv:${r}:y` },
633
+ { text: '❌ 拒绝', callback_data: `apv:${r}:n` },
634
+ ],
635
+ [
636
+ { text: '🛡 本会话自动放行同类', callback_data: `apv:${r}:a` },
637
+ ],
638
+ ],
639
+ };
640
+ }
641
+ function renderApprovalOutcomeHtml(o) {
642
+ const t = formatHm(o.atDate);
643
+ const by = o.byUserDisplay ? ` · by ${escapeHtml(o.byUserDisplay)}` : '';
644
+ switch (o.decision) {
645
+ case 'allowed': return `✅ <b>已批准</b> · ${t}${by}`;
646
+ case 'allowed-pinned': return `🛡 <b>已批准并加入自动放行</b> · ${t}${by}`;
647
+ case 'denied': return `❌ <b>已拒绝</b> · ${t}${by}`;
648
+ case 'denied-revoked': return `❌ <b>已拒绝并撤销自动放行</b> · ${t}${by}`;
649
+ case 'expired': return `⏱ <b>已过期</b> · ${t}`;
650
+ }
651
+ }
652
+ export const telegramAdapter = new TelegramAdapter();
653
+ //# sourceMappingURL=telegram-adapter.js.map