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,935 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>im-hub-pro — Agent Chat</title>
7
+ <!-- Shared utilities: theme manager (applies before first paint), error
8
+ boundary (surfaces silent script failures), i18n + api helpers. -->
9
+ <script src="/_app.js"></script>
10
+ <script>
11
+ // i18n — detect browser language, store preference in localStorage
12
+ const LANGS = { en: 'English', zh: '中文' };
13
+ const savedLang = localStorage.getItem('im-hub-lang');
14
+ const browserLang = navigator.language.startsWith('zh') ? 'zh' : 'en';
15
+ window.__lang = savedLang && LANGS[savedLang] ? savedLang : browserLang;
16
+ document.documentElement.lang = window.__lang === 'zh' ? 'zh-CN' : 'en';
17
+
18
+ const T = {
19
+ en: {
20
+ title: 'im-hub-pro — Agent Chat',
21
+ connecting: 'Connecting...',
22
+ connected: 'Connected',
23
+ disconnected: 'Disconnected',
24
+ connError: 'Connection error',
25
+ loadingAgents: 'Loading agents...',
26
+ newChat: 'New Chat',
27
+ welcomeTitle: 'im-hub-pro Agent Chat',
28
+ welcomeDesc: 'Select an agent and start chatting. Your locally installed AI coding agents are ready.',
29
+ inputPlaceholder: 'Type a message... (Enter to send, Shift+Enter for newline)',
30
+ send: 'Send',
31
+ you: 'You',
32
+ assistant: 'Assistant',
33
+ approvalTitle: 'Tool approval request',
34
+ approvalToolLabel: 'Tool',
35
+ approvalInputLabel: 'Input',
36
+ approvalAllow: 'Allow',
37
+ approvalDeny: 'Deny',
38
+ approvalAllowAll: 'Allow + auto for similar',
39
+ approvalAutoAllowing: 'Auto-allowing in {s}s — click Deny to reject',
40
+ approvalAllowed: '✅ Allowed',
41
+ approvalAllowedPinned: '✅ Allowed (auto-allow rule pinned)',
42
+ approvalDenied: '❌ Denied',
43
+ approvalDeniedRevoked: '❌ Denied (auto-allow rule revoked)',
44
+ approvalExpired: '⏱ Timed out (auto-denied)',
45
+ },
46
+ zh: {
47
+ title: 'im-hub-pro — Agent 对话',
48
+ connecting: '连接中...',
49
+ connected: '已连接',
50
+ disconnected: '已断开',
51
+ connError: '连接错误',
52
+ loadingAgents: '加载 Agent...',
53
+ newChat: '新对话',
54
+ welcomeTitle: 'im-hub-pro Agent 对话',
55
+ welcomeDesc: '选择一个 Agent 开始对话,本地安装的 AI 编程助手已就绪。',
56
+ inputPlaceholder: '输入消息...(Enter 发送,Shift+Enter 换行)',
57
+ send: '发送',
58
+ you: '你',
59
+ assistant: '助手',
60
+ approvalTitle: '工具调用审批',
61
+ approvalToolLabel: '工具',
62
+ approvalInputLabel: '入参',
63
+ approvalAllow: '批准',
64
+ approvalDeny: '拒绝',
65
+ approvalAllowAll: '批准并自动放行后续同类',
66
+ approvalAutoAllowing: '将在 {s}s 内自动放行 — 点击拒绝可阻止',
67
+ approvalAllowed: '✅ 已批准',
68
+ approvalAllowedPinned: '✅ 已批准(已启用自动放行)',
69
+ approvalDenied: '❌ 已拒绝',
70
+ approvalDeniedRevoked: '❌ 已拒绝(已撤销自动放行)',
71
+ approvalExpired: '⏱ 已超时(自动拒绝)',
72
+ },
73
+ };
74
+ function t(key) { return T[window.__lang][key] || T.en[key] || key; }
75
+ document.addEventListener('DOMContentLoaded', () => { document.title = t('title'); });
76
+ </script>
77
+ <style>
78
+ /* Three-state theming. `:root` defaults to light; explicit
79
+ data-theme="dark" forces dark; `prefers-color-scheme: dark` only
80
+ applies when the attribute is absent (mode === 'system'). */
81
+ :root {
82
+ color-scheme: light dark;
83
+ /* light defaults */
84
+ --bg: #f8f9fb;
85
+ --surface: #ffffff;
86
+ --surface2: #f1f3f6;
87
+ --border: #e1e4e8;
88
+ --text: #1a1f2e;
89
+ --text-dim: #6b7280;
90
+ --accent: #6366f1;
91
+ --accent-dim: #818cf8;
92
+ --user-bg: #eef2ff;
93
+ --assistant-bg: #ffffff;
94
+ --code-bg: #f1f5f9;
95
+ --green: #16a34a;
96
+ --red: #dc2626;
97
+ --yellow: #ca8a04;
98
+ }
99
+ :root[data-theme="dark"] {
100
+ --bg: #0a0a0a;
101
+ --surface: #141414;
102
+ --surface2: #1e1e1e;
103
+ --border: #2a2a2a;
104
+ --text: #e5e5e5;
105
+ --text-dim: #888;
106
+ --accent: #6366f1;
107
+ --accent-dim: #4f46e5;
108
+ --user-bg: #1a1a2e;
109
+ --assistant-bg: #1e1e1e;
110
+ --code-bg: #0d0d0d;
111
+ --green: #22c55e;
112
+ --red: #ef4444;
113
+ --yellow: #eab308;
114
+ }
115
+ @media (prefers-color-scheme: dark) {
116
+ :root:not([data-theme]) {
117
+ --bg: #0a0a0a;
118
+ --surface: #141414;
119
+ --surface2: #1e1e1e;
120
+ --border: #2a2a2a;
121
+ --text: #e5e5e5;
122
+ --text-dim: #888;
123
+ --accent-dim: #4f46e5;
124
+ --user-bg: #1a1a2e;
125
+ --assistant-bg: #1e1e1e;
126
+ --code-bg: #0d0d0d;
127
+ --green: #22c55e;
128
+ --red: #ef4444;
129
+ --yellow: #eab308;
130
+ }
131
+ }
132
+
133
+ * { margin: 0; padding: 0; box-sizing: border-box; }
134
+
135
+ body {
136
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
137
+ background: var(--bg);
138
+ color: var(--text);
139
+ height: 100vh;
140
+ display: flex;
141
+ flex-direction: column;
142
+ }
143
+
144
+ /* Header */
145
+ .header {
146
+ display: flex;
147
+ align-items: center;
148
+ justify-content: space-between;
149
+ padding: 12px 20px;
150
+ border-bottom: 1px solid var(--border);
151
+ background: var(--surface);
152
+ }
153
+ .header-left {
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 12px;
157
+ }
158
+ .logo {
159
+ font-weight: 700;
160
+ font-size: 16px;
161
+ color: var(--accent);
162
+ }
163
+ .status-dot {
164
+ width: 8px;
165
+ height: 8px;
166
+ border-radius: 50%;
167
+ background: var(--yellow);
168
+ display: inline-block;
169
+ }
170
+ .status-dot.connected { background: var(--green); }
171
+ .status-dot.disconnected { background: var(--red); }
172
+ .status-text { font-size: 12px; color: var(--text-dim); }
173
+ .header-right { display: flex; align-items: center; gap: 10px; }
174
+
175
+ /* Agent selector */
176
+ .agent-select {
177
+ background: var(--surface2);
178
+ color: var(--text);
179
+ border: 1px solid var(--border);
180
+ border-radius: 6px;
181
+ padding: 6px 10px;
182
+ font-size: 13px;
183
+ cursor: pointer;
184
+ outline: none;
185
+ }
186
+ .agent-select:focus { border-color: var(--accent); }
187
+
188
+ .btn {
189
+ background: var(--surface2);
190
+ color: var(--text-dim);
191
+ border: 1px solid var(--border);
192
+ border-radius: 6px;
193
+ padding: 6px 10px;
194
+ font-size: 12px;
195
+ cursor: pointer;
196
+ }
197
+ .btn:hover { color: var(--text); border-color: var(--text-dim); }
198
+
199
+ /* Language selector */
200
+ .lang-select {
201
+ background: var(--surface2);
202
+ color: var(--text-dim);
203
+ border: 1px solid var(--border);
204
+ border-radius: 6px;
205
+ padding: 4px 6px;
206
+ font-size: 12px;
207
+ cursor: pointer;
208
+ outline: none;
209
+ }
210
+ .lang-select:hover { color: var(--text); }
211
+
212
+ /* Messages */
213
+ .messages {
214
+ flex: 1;
215
+ overflow-y: auto;
216
+ padding: 20px;
217
+ display: flex;
218
+ flex-direction: column;
219
+ gap: 16px;
220
+ }
221
+
222
+ .message {
223
+ max-width: 85%;
224
+ padding: 12px 16px;
225
+ border-radius: 12px;
226
+ font-size: 14px;
227
+ line-height: 1.6;
228
+ white-space: pre-wrap;
229
+ word-break: break-word;
230
+ }
231
+ .message.user {
232
+ align-self: flex-end;
233
+ background: var(--user-bg);
234
+ border-bottom-right-radius: 4px;
235
+ }
236
+ .message.assistant {
237
+ align-self: flex-start;
238
+ background: var(--assistant-bg);
239
+ border-bottom-left-radius: 4px;
240
+ }
241
+ .message.assistant code {
242
+ background: var(--code-bg);
243
+ padding: 2px 6px;
244
+ border-radius: 4px;
245
+ font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
246
+ font-size: 13px;
247
+ }
248
+ .message.assistant pre {
249
+ background: var(--code-bg);
250
+ padding: 12px 16px;
251
+ border-radius: 8px;
252
+ overflow-x: auto;
253
+ margin: 8px 0;
254
+ }
255
+ .message.assistant pre code {
256
+ background: none;
257
+ padding: 0;
258
+ }
259
+
260
+ .message-label {
261
+ font-size: 11px;
262
+ color: var(--text-dim);
263
+ margin-bottom: 4px;
264
+ font-weight: 600;
265
+ }
266
+
267
+ /* Typing indicator */
268
+ .typing {
269
+ align-self: flex-start;
270
+ padding: 12px 16px;
271
+ color: var(--text-dim);
272
+ font-size: 13px;
273
+ }
274
+ .typing span {
275
+ animation: blink 1.4s infinite;
276
+ }
277
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
278
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
279
+ @keyframes blink {
280
+ 0%, 60%, 100% { opacity: 0.2; }
281
+ 30% { opacity: 1; }
282
+ }
283
+
284
+ /* Welcome screen */
285
+ .welcome {
286
+ flex: 1;
287
+ display: flex;
288
+ flex-direction: column;
289
+ align-items: center;
290
+ justify-content: center;
291
+ gap: 16px;
292
+ color: var(--text-dim);
293
+ }
294
+ .welcome h2 { color: var(--text); font-size: 20px; }
295
+ .welcome p { font-size: 14px; max-width: 400px; text-align: center; }
296
+
297
+ /* Input area */
298
+ .input-area {
299
+ padding: 16px 20px;
300
+ border-top: 1px solid var(--border);
301
+ background: var(--surface);
302
+ }
303
+ .input-wrapper {
304
+ display: flex;
305
+ gap: 8px;
306
+ align-items: flex-end;
307
+ }
308
+ .input-wrapper textarea {
309
+ flex: 1;
310
+ background: var(--surface2);
311
+ color: var(--text);
312
+ border: 1px solid var(--border);
313
+ border-radius: 10px;
314
+ padding: 10px 14px;
315
+ font-size: 14px;
316
+ font-family: inherit;
317
+ resize: none;
318
+ outline: none;
319
+ max-height: 150px;
320
+ min-height: 42px;
321
+ }
322
+ .input-wrapper textarea:focus { border-color: var(--accent); }
323
+ .input-wrapper textarea::placeholder { color: var(--text-dim); }
324
+
325
+ .send-btn {
326
+ background: var(--accent);
327
+ color: white;
328
+ border: none;
329
+ border-radius: 10px;
330
+ padding: 10px 16px;
331
+ font-size: 14px;
332
+ cursor: pointer;
333
+ white-space: nowrap;
334
+ }
335
+ .send-btn:hover { background: var(--accent-dim); }
336
+ .send-btn:disabled {
337
+ background: var(--surface2);
338
+ color: var(--text-dim);
339
+ cursor: not-allowed;
340
+ }
341
+
342
+ /* Scrollbar */
343
+ .messages::-webkit-scrollbar { width: 6px; }
344
+ .messages::-webkit-scrollbar-track { background: transparent; }
345
+ .messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
346
+
347
+ /* In-chat HITL approval card. Mirrors the IM card's structure (Telegram
348
+ inline-keyboard variant) so the cross-platform UX is consistent. */
349
+ .approval-card {
350
+ border: 1px solid var(--accent);
351
+ background: var(--surface);
352
+ border-radius: 10px;
353
+ padding: 14px 16px;
354
+ margin: 8px 0;
355
+ max-width: 720px;
356
+ box-shadow: 0 1px 3px rgba(0,0,0,.06);
357
+ }
358
+ .approval-card.auto-allow { border-color: var(--yellow); }
359
+ .approval-card.resolved { opacity: .85; }
360
+ .approval-card .ac-title {
361
+ font-weight: 600;
362
+ font-size: 14px;
363
+ margin-bottom: 8px;
364
+ display: flex;
365
+ align-items: center;
366
+ gap: 6px;
367
+ }
368
+ .approval-card .ac-row { font-size: 13px; margin: 4px 0; color: var(--text-dim); }
369
+ .approval-card .ac-row b { color: var(--text); margin-right: 6px; }
370
+ .approval-card .ac-input {
371
+ background: var(--code-bg);
372
+ border-radius: 6px;
373
+ padding: 8px 10px;
374
+ font: 12px/1.5 'SF Mono', Menlo, Consolas, monospace;
375
+ white-space: pre-wrap;
376
+ word-break: break-word;
377
+ max-height: 180px;
378
+ overflow: auto;
379
+ margin: 6px 0 10px;
380
+ color: var(--text);
381
+ }
382
+ .approval-card .ac-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
383
+ .approval-card .ac-btn {
384
+ border: 1px solid var(--border);
385
+ background: var(--surface2);
386
+ color: var(--text);
387
+ padding: 6px 14px;
388
+ border-radius: 6px;
389
+ cursor: pointer;
390
+ font-size: 13px;
391
+ }
392
+ .approval-card .ac-btn:hover { border-color: var(--accent); }
393
+ .approval-card .ac-btn.allow { background: var(--green); border-color: var(--green); color: #fff; }
394
+ .approval-card .ac-btn.deny { background: var(--red); border-color: var(--red); color: #fff; }
395
+ .approval-card .ac-btn:disabled { opacity: .5; cursor: not-allowed; }
396
+ .approval-card .ac-grace {
397
+ color: var(--yellow);
398
+ font-size: 13px;
399
+ margin-bottom: 8px;
400
+ font-weight: 500;
401
+ }
402
+ .approval-card .ac-outcome {
403
+ font-size: 13px;
404
+ margin-top: 8px;
405
+ padding-top: 8px;
406
+ border-top: 1px solid var(--border);
407
+ color: var(--text-dim);
408
+ }
409
+ </style>
410
+ </head>
411
+ <body>
412
+ <div class="header">
413
+ <div class="header-left">
414
+ <span class="logo">im-hub-pro</span>
415
+ <span class="status-dot" id="statusDot"></span>
416
+ <span class="status-text" id="statusText"></span>
417
+ </div>
418
+ <div class="header-right">
419
+ <select class="lang-select" id="langSelect">
420
+ <option value="en">EN</option>
421
+ <option value="zh">中文</option>
422
+ </select>
423
+ <select class="agent-select" id="agentSelect" disabled>
424
+ <option value=""></option>
425
+ </select>
426
+ <button class="btn" id="newChatBtn"></button>
427
+ <button class="btn" id="theme-toggle" type="button" aria-label="Toggle color theme"></button>
428
+ <a class="btn" href="/tasks" title="Tasks" style="text-decoration:none;font-size:16px;line-height:1">&#9776;</a>
429
+ <a class="btn" href="/settings" title="Settings" style="text-decoration:none;font-size:16px;line-height:1">&#9881;</a>
430
+ </div>
431
+ </div>
432
+
433
+ <div class="messages" id="messages">
434
+ <div class="welcome" id="welcome">
435
+ <h2 id="welcomeTitle"></h2>
436
+ <p id="welcomeDesc"></p>
437
+ </div>
438
+ </div>
439
+
440
+ <div class="input-area">
441
+ <div class="input-wrapper">
442
+ <textarea
443
+ id="input"
444
+ rows="1"
445
+ disabled
446
+ ></textarea>
447
+ <button class="send-btn" id="sendBtn" disabled></button>
448
+ </div>
449
+ </div>
450
+
451
+ <script>
452
+ // Apply i18n to static elements
453
+ function applyLang() {
454
+ document.title = t('title');
455
+ document.documentElement.lang = window.__lang === 'zh' ? 'zh-CN' : 'en';
456
+ statusText.textContent = statusText.dataset.state ? t(statusText.dataset.state) : t('connecting');
457
+ const opt = agentSelect.querySelector('option[value=""]');
458
+ if (opt) opt.textContent = t('loadingAgents');
459
+ newChatBtn.textContent = t('newChat');
460
+ inputEl.placeholder = t('inputPlaceholder');
461
+ sendBtn.textContent = t('send');
462
+ const wt = document.getElementById('welcomeTitle');
463
+ const wd = document.getElementById('welcomeDesc');
464
+ if (wt) wt.textContent = t('welcomeTitle');
465
+ if (wd) wd.textContent = t('welcomeDesc');
466
+ }
467
+
468
+ // State
469
+ let ws = null;
470
+ let currentAgent = '';
471
+ let isStreaming = false;
472
+ let streamingEl = null;
473
+ let streamingText = '';
474
+ let reconnectTimer = null;
475
+
476
+ // DOM
477
+ const messagesEl = document.getElementById('messages');
478
+ const welcomeEl = document.getElementById('welcome');
479
+ const inputEl = document.getElementById('input');
480
+ const sendBtn = document.getElementById('sendBtn');
481
+ const agentSelect = document.getElementById('agentSelect');
482
+ const statusDot = document.getElementById('statusDot');
483
+ const statusText = document.getElementById('statusText');
484
+ const newChatBtn = document.getElementById('newChatBtn');
485
+ const langSelect = document.getElementById('langSelect');
486
+
487
+ // Theme toggle (light / dark / system). _app.js applied the theme
488
+ // synchronously in <head>; here we wire the button so clicks cycle
489
+ // through the three modes and the icon/label re-renders.
490
+ if (window.imhub) imhub.theme.bindToggle(document.getElementById('theme-toggle'));
491
+
492
+ // Language selector
493
+ langSelect.value = window.__lang;
494
+ langSelect.addEventListener('change', () => {
495
+ window.__lang = langSelect.value;
496
+ localStorage.setItem('im-hub-lang', window.__lang);
497
+ applyLang();
498
+ });
499
+
500
+ // Connect
501
+ function connect() {
502
+ const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
503
+ const token = window.IMHUB_TOKEN || '';
504
+ ws = new WebSocket(`${protocol}//${location.host}/?token=${encodeURIComponent(token)}`);
505
+
506
+ ws.onopen = () => {
507
+ setStatus('connected', t('connected'));
508
+ inputEl.disabled = false;
509
+ sendBtn.disabled = false;
510
+ };
511
+
512
+ ws.onmessage = (e) => {
513
+ const msg = JSON.parse(e.data);
514
+ handleMessage(msg);
515
+ };
516
+
517
+ ws.onclose = () => {
518
+ setStatus('disconnected', t('disconnected'));
519
+ inputEl.disabled = true;
520
+ sendBtn.disabled = true;
521
+ reconnectTimer = setTimeout(connect, 3000);
522
+ };
523
+
524
+ ws.onerror = () => {
525
+ setStatus('disconnected', t('connError'));
526
+ };
527
+ }
528
+
529
+ // Handle server messages
530
+ function handleMessage(msg) {
531
+ switch (msg.type) {
532
+ case 'init':
533
+ agentSelect.innerHTML = '';
534
+ msg.agents.forEach(name => {
535
+ const opt = document.createElement('option');
536
+ opt.value = name;
537
+ opt.textContent = name;
538
+ if (name === msg.defaultAgent) opt.selected = true;
539
+ agentSelect.appendChild(opt);
540
+ });
541
+ agentSelect.disabled = false;
542
+ currentAgent = msg.defaultAgent;
543
+ break;
544
+
545
+ case 'history':
546
+ hideWelcome();
547
+ if (msg.agent) {
548
+ currentAgent = msg.agent;
549
+ agentSelect.value = msg.agent;
550
+ }
551
+ msg.messages.forEach(m => {
552
+ addMessage(m.role, m.content, false);
553
+ });
554
+ scrollToBottom();
555
+ break;
556
+
557
+ case 'agents':
558
+ agentSelect.innerHTML = '';
559
+ msg.agents.forEach(name => {
560
+ const opt = document.createElement('option');
561
+ opt.value = name;
562
+ opt.textContent = name;
563
+ if (name === currentAgent) opt.selected = true;
564
+ agentSelect.appendChild(opt);
565
+ });
566
+ agentSelect.disabled = false;
567
+ break;
568
+
569
+ case 'chunk':
570
+ if (!isStreaming) {
571
+ hideWelcome();
572
+ startStreaming();
573
+ }
574
+ streamingText += msg.text;
575
+ updateStreamingEl();
576
+ scrollToBottom();
577
+ break;
578
+
579
+ case 'done':
580
+ if (isStreaming) {
581
+ finishStreaming();
582
+ } else if (msg.text) {
583
+ hideWelcome();
584
+ addMessage('assistant', msg.text);
585
+ scrollToBottom();
586
+ }
587
+ break;
588
+
589
+ case 'error':
590
+ if (isStreaming) finishStreaming();
591
+ addMessage('assistant', '\u274c ' + msg.message);
592
+ scrollToBottom();
593
+ break;
594
+
595
+ case 'agent-switched':
596
+ currentAgent = msg.agent;
597
+ agentSelect.value = msg.agent;
598
+ break;
599
+
600
+ // HITL approval messages relayed by the synthetic 'web' messenger
601
+ // (registered on the server in startWebServer). approval-router
602
+ // calls our messenger.sendApprovalCard / sendMessage / editApprovalCard
603
+ // — those become the three WS message types below.
604
+ case 'approval-text':
605
+ // Fallback / receipt path. Render as a system message.
606
+ hideWelcome();
607
+ addSystemMessage(msg.text);
608
+ scrollToBottom();
609
+ break;
610
+ case 'approval-card':
611
+ hideWelcome();
612
+ renderApprovalCard(msg.messageId, msg.prompt);
613
+ scrollToBottom();
614
+ break;
615
+ case 'approval-card-edit':
616
+ editApprovalCard(msg.messageId, msg.outcome);
617
+ break;
618
+ }
619
+ }
620
+
621
+ // Send message
622
+ function sendMessage() {
623
+ const text = inputEl.value.trim();
624
+ if (!text || !ws || ws.readyState !== ws.OPEN) return;
625
+
626
+ hideWelcome();
627
+ addMessage('user', text);
628
+
629
+ ws.send(JSON.stringify({
630
+ type: 'message',
631
+ text,
632
+ agent: currentAgent,
633
+ }));
634
+
635
+ inputEl.value = '';
636
+ autoResize();
637
+ }
638
+
639
+ // Streaming helpers
640
+ function startStreaming() {
641
+ isStreaming = true;
642
+ streamingText = '';
643
+
644
+ const wrapper = document.createElement('div');
645
+ wrapper.innerHTML = `<div class="message-label">${t('assistant')}</div>`;
646
+ streamingEl = document.createElement('div');
647
+ streamingEl.className = 'message assistant';
648
+ wrapper.appendChild(streamingEl);
649
+ messagesEl.appendChild(wrapper);
650
+ }
651
+
652
+ function updateStreamingEl() {
653
+ if (!streamingEl) return;
654
+ streamingEl.textContent = streamingText;
655
+ }
656
+
657
+ function finishStreaming() {
658
+ isStreaming = false;
659
+ if (streamingEl) {
660
+ streamingEl.innerHTML = renderMarkdown(streamingText);
661
+ }
662
+ streamingEl = null;
663
+ saveHistory();
664
+ }
665
+
666
+ // Message helpers
667
+ /**
668
+ * System / receipt-style message bubble. Used for `approval-text` so the
669
+ * "✅ Allowed" / "⏱ Timed out" notices the bus emits show up in the
670
+ * conversation flow without a "user / assistant" label.
671
+ */
672
+ function addSystemMessage(text) {
673
+ hideWelcome();
674
+ const wrapper = document.createElement('div');
675
+ wrapper.style.cssText = 'margin:6px 0; color:var(--text-dim); font-size:13px;';
676
+ wrapper.textContent = text;
677
+ messagesEl.appendChild(wrapper);
678
+ }
679
+
680
+ /** Active approval cards keyed by server-issued messageId, so the
681
+ * `approval-card-edit` handler can find the same DOM node and
682
+ * collapse it to an outcome stamp. */
683
+ const approvalCards = new Map();
684
+
685
+ function renderApprovalCard(messageId, prompt) {
686
+ const isAuto = prompt.mode === 'auto-allow';
687
+ const card = document.createElement('div');
688
+ card.className = 'approval-card' + (isAuto ? ' auto-allow' : '');
689
+ card.dataset.cardId = messageId;
690
+ card.innerHTML = `
691
+ <div class="ac-title">🔐 <span data-slot="title"></span></div>
692
+ ${isAuto ? `<div class="ac-grace" data-slot="grace"></div>` : ''}
693
+ <div class="ac-row"><b data-slot="tool-label"></b><code data-slot="tool"></code></div>
694
+ <div class="ac-row"><b data-slot="input-label"></b></div>
695
+ <pre class="ac-input" data-slot="input"></pre>
696
+ <div class="ac-actions" data-slot="actions"></div>
697
+ `;
698
+ card.querySelector('[data-slot="title"]').textContent = t('approvalTitle');
699
+ card.querySelector('[data-slot="tool-label"]').textContent = t('approvalToolLabel') + ':';
700
+ card.querySelector('[data-slot="tool"]').textContent = prompt.toolName;
701
+ card.querySelector('[data-slot="input-label"]').textContent = t('approvalInputLabel') + ':';
702
+ card.querySelector('[data-slot="input"]').textContent = prompt.inputJson;
703
+
704
+ const send = (choice) => {
705
+ // Soft disable: dim and ignore further clicks for 800 ms while the
706
+ // server round-trips. We deliberately do NOT hard-disable — if the
707
+ // server returns an error (e.g. 'approval handler not bound'), the
708
+ // card stays usable so the user can retry once they fix it. The
709
+ // success path replaces the buttons via `approval-card-edit` anyway.
710
+ card.querySelectorAll('button').forEach((b) => {
711
+ b.style.opacity = '0.6';
712
+ b.style.pointerEvents = 'none';
713
+ });
714
+ setTimeout(() => {
715
+ card.querySelectorAll('button').forEach((b) => {
716
+ b.style.opacity = '';
717
+ b.style.pointerEvents = '';
718
+ });
719
+ }, 800);
720
+ const payload = { type: 'approval-action', messageId, data: `apv:${prompt.reqId}:${choice}` };
721
+ try { console.debug && console.debug('[approval] click', payload); } catch {}
722
+ if (ws.readyState !== WebSocket.OPEN) {
723
+ imhub?.showError?.('WebSocket not connected — click ignored');
724
+ return;
725
+ }
726
+ ws.send(JSON.stringify(payload));
727
+ };
728
+
729
+ const actions = card.querySelector('[data-slot="actions"]');
730
+ if (isAuto) {
731
+ // Auto-allow grace mode: only Deny is meaningful (Allow happens
732
+ // automatically on grace expiry).
733
+ const denyBtn = document.createElement('button');
734
+ denyBtn.className = 'ac-btn deny';
735
+ denyBtn.textContent = t('approvalDeny');
736
+ denyBtn.onclick = () => send('n');
737
+ actions.appendChild(denyBtn);
738
+
739
+ // Live countdown.
740
+ let s = Number(prompt.graceSeconds) || 5;
741
+ const grace = card.querySelector('[data-slot="grace"]');
742
+ const refreshGrace = () => { grace.textContent = t('approvalAutoAllowing').replace('{s}', String(s)); };
743
+ refreshGrace();
744
+ const tick = setInterval(() => {
745
+ s = Math.max(0, s - 1);
746
+ refreshGrace();
747
+ if (s <= 0) clearInterval(tick);
748
+ }, 1000);
749
+ card._graceTimer = tick;
750
+ } else {
751
+ const allowBtn = document.createElement('button');
752
+ allowBtn.className = 'ac-btn allow';
753
+ allowBtn.textContent = t('approvalAllow');
754
+ allowBtn.onclick = () => send('y');
755
+
756
+ const denyBtn = document.createElement('button');
757
+ denyBtn.className = 'ac-btn deny';
758
+ denyBtn.textContent = t('approvalDeny');
759
+ denyBtn.onclick = () => send('n');
760
+
761
+ const allowAllBtn = document.createElement('button');
762
+ allowAllBtn.className = 'ac-btn';
763
+ allowAllBtn.textContent = t('approvalAllowAll');
764
+ allowAllBtn.onclick = () => send('a');
765
+
766
+ actions.appendChild(allowBtn);
767
+ actions.appendChild(denyBtn);
768
+ actions.appendChild(allowAllBtn);
769
+ }
770
+
771
+ messagesEl.appendChild(card);
772
+ approvalCards.set(messageId, card);
773
+ }
774
+
775
+ function editApprovalCard(messageId, outcome) {
776
+ const card = approvalCards.get(messageId);
777
+ if (!card) return;
778
+ card.classList.add('resolved');
779
+ // Stop any countdown if this was an auto-allow card.
780
+ if (card._graceTimer) { clearInterval(card._graceTimer); card._graceTimer = null; }
781
+ const grace = card.querySelector('[data-slot="grace"]');
782
+ if (grace) grace.remove();
783
+ // Disable any remaining live buttons.
784
+ card.querySelectorAll('.ac-actions button').forEach(b => b.disabled = true);
785
+ const actions = card.querySelector('[data-slot="actions"]');
786
+ if (actions) actions.remove();
787
+ const stamp = document.createElement('div');
788
+ stamp.className = 'ac-outcome';
789
+ const KEY = {
790
+ allowed: 'approvalAllowed',
791
+ 'allowed-pinned': 'approvalAllowedPinned',
792
+ denied: 'approvalDenied',
793
+ 'denied-revoked': 'approvalDeniedRevoked',
794
+ expired: 'approvalExpired',
795
+ };
796
+ const text = t(KEY[outcome.decision] || 'approvalAllowed');
797
+ const by = outcome.byUserDisplay ? ` · ${outcome.byUserDisplay}` : '';
798
+ stamp.textContent = `${text}${by}`;
799
+ card.appendChild(stamp);
800
+ // Keep the card around for visual continuity, but free the map slot
801
+ // so a same-id replay doesn't double-edit it.
802
+ approvalCards.delete(messageId);
803
+ }
804
+
805
+ function addMessage(role, content, save = true) {
806
+ hideWelcome();
807
+
808
+ const wrapper = document.createElement('div');
809
+ const label = document.createElement('div');
810
+ label.className = 'message-label';
811
+ label.textContent = role === 'user' ? t('you') : t('assistant');
812
+
813
+ const el = document.createElement('div');
814
+ el.className = `message ${role}`;
815
+
816
+ if (role === 'assistant') {
817
+ el.innerHTML = renderMarkdown(content);
818
+ } else {
819
+ el.textContent = content;
820
+ }
821
+
822
+ wrapper.appendChild(label);
823
+ wrapper.appendChild(el);
824
+ messagesEl.appendChild(wrapper);
825
+
826
+ if (save) saveHistory();
827
+ }
828
+
829
+ // Basic markdown rendering
830
+ function renderMarkdown(text) {
831
+ if (!text) return '';
832
+ let html = escapeHtml(text);
833
+
834
+ html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
835
+ return `<pre><code>${code.trim()}</code></pre>`;
836
+ });
837
+
838
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
839
+ html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
840
+ html = html.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '<em>$1</em>');
841
+
842
+ return html;
843
+ }
844
+
845
+ function escapeHtml(text) {
846
+ return text
847
+ .replace(/&/g, '&amp;')
848
+ .replace(/</g, '&lt;')
849
+ .replace(/>/g, '&gt;');
850
+ }
851
+
852
+ function hideWelcome() {
853
+ if (welcomeEl) welcomeEl.remove();
854
+ }
855
+
856
+ function scrollToBottom() {
857
+ requestAnimationFrame(() => {
858
+ messagesEl.scrollTop = messagesEl.scrollHeight;
859
+ });
860
+ }
861
+
862
+ // Status
863
+ function setStatus(state, text) {
864
+ statusDot.className = `status-dot ${state}`;
865
+ statusText.textContent = text;
866
+ statusText.dataset.state = state === 'connected' ? 'connected' : state === 'disconnected' ? 'disconnected' : 'connecting';
867
+ }
868
+
869
+ // LocalStorage persistence
870
+ function saveHistory() {
871
+ const msgs = [];
872
+ messagesEl.querySelectorAll('.message').forEach(el => {
873
+ const role = el.classList.contains('user') ? 'user' : 'assistant';
874
+ msgs.push({ role, content: el.textContent });
875
+ });
876
+ try {
877
+ localStorage.setItem('im-hub-history', JSON.stringify(msgs));
878
+ } catch {}
879
+ }
880
+
881
+ function loadHistory() {
882
+ try {
883
+ const saved = localStorage.getItem('im-hub-history');
884
+ if (saved) {
885
+ const msgs = JSON.parse(saved);
886
+ if (msgs.length > 0) {
887
+ hideWelcome();
888
+ msgs.forEach(m => addMessage(m.role, m.content, false));
889
+ scrollToBottom();
890
+ }
891
+ }
892
+ } catch {}
893
+ }
894
+
895
+ // Auto-resize textarea
896
+ function autoResize() {
897
+ inputEl.style.height = 'auto';
898
+ inputEl.style.height = Math.min(inputEl.scrollHeight, 150) + 'px';
899
+ }
900
+
901
+ // Event listeners
902
+ inputEl.addEventListener('input', autoResize);
903
+
904
+ inputEl.addEventListener('keydown', (e) => {
905
+ if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
906
+ e.preventDefault();
907
+ sendMessage();
908
+ }
909
+ });
910
+
911
+ sendBtn.addEventListener('click', sendMessage);
912
+
913
+ agentSelect.addEventListener('change', () => {
914
+ const agent = agentSelect.value;
915
+ if (agent && agent !== currentAgent && ws?.readyState === WebSocket.OPEN) {
916
+ currentAgent = agent;
917
+ ws.send(JSON.stringify({ type: 'switch-agent', agent }));
918
+ }
919
+ });
920
+
921
+ newChatBtn.addEventListener('click', () => {
922
+ messagesEl.innerHTML = '';
923
+ localStorage.removeItem('im-hub-history');
924
+ if (ws?.readyState === WebSocket.OPEN) {
925
+ ws.send(JSON.stringify({ type: 'message', text: '/new' }));
926
+ }
927
+ });
928
+
929
+ // Init
930
+ applyLang();
931
+ loadHistory();
932
+ connect();
933
+ </script>
934
+ </body>
935
+ </html>