@seqyuan/annovibe 0.8.41 → 0.8.54

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 (239) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +14 -7
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/required-server-files.js +1 -1
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/routes-manifest.json +48 -8
  8. package/.next/server/app/_global-error/page.js +2 -2
  9. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/server/app/_global-error.html +1 -1
  11. package/.next/server/app/_global-error.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  18. package/.next/server/app/_not-found/page.js +2 -2
  19. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/_not-found.html +1 -1
  21. package/.next/server/app/_not-found.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  23. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  24. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  25. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  26. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  27. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  28. package/.next/server/app/api/agent/[id]/events/route.js +1 -1
  29. package/.next/server/app/api/agent/[id]/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js +1 -1
  31. package/.next/server/app/api/agent/runtime/route.js +1 -1
  32. package/.next/server/app/api/apps/[id]/[[...path]]/route.js +1 -1
  33. package/.next/server/app/api/apps/route.js +2 -2
  34. package/.next/server/app/api/auth/all-providers/route.js +1 -1
  35. package/.next/server/app/api/auth/api-key/[provider]/route.js +1 -1
  36. package/.next/server/app/api/auth/login/[provider]/route.js +2 -2
  37. package/.next/server/app/api/auth/login/route.js +1 -1
  38. package/.next/server/app/api/auth/logout/[provider]/route.js +1 -1
  39. package/.next/server/app/api/auth/providers/route.js +1 -1
  40. package/.next/server/app/api/auth/status/route.js +1 -1
  41. package/.next/server/app/api/default-cwd/route.js +1 -1
  42. package/.next/server/app/api/files/[...path]/route.js +3 -3
  43. package/.next/server/app/api/harness/route.js +1 -1
  44. package/.next/server/app/api/home/route.js +1 -1
  45. package/.next/server/app/api/im/cancel/route.js +1 -0
  46. package/.next/server/app/api/im/cancel/route_client-reference-manifest.js +1 -0
  47. package/.next/server/app/api/im/gateway-status/route.js +1 -0
  48. package/.next/server/app/api/im/gateway-status/route_client-reference-manifest.js +1 -0
  49. package/.next/server/app/api/im/gateway-token/route.js +1 -0
  50. package/.next/server/app/api/im/gateway-token/route_client-reference-manifest.js +1 -0
  51. package/.next/server/app/api/im/project/route.js +1 -0
  52. package/.next/server/app/api/im/project/route_client-reference-manifest.js +1 -0
  53. package/.next/server/app/api/im/projects/route.js +1 -0
  54. package/.next/server/app/api/im/projects/route_client-reference-manifest.js +1 -0
  55. package/.next/server/app/api/im/session-ids/route.js +1 -0
  56. package/.next/server/app/api/im/session-ids/route_client-reference-manifest.js +1 -0
  57. package/.next/server/app/api/im/turn/route.js +3 -0
  58. package/.next/server/app/api/im/turn/route_client-reference-manifest.js +1 -0
  59. package/.next/server/app/api/internal/runtime/route.js +1 -1
  60. package/.next/server/app/api/memory/promote/route.js +2 -2
  61. package/.next/server/app/api/models/route.js +1 -1
  62. package/.next/server/app/api/models-config/discover/route.js +1 -1
  63. package/.next/server/app/api/models-config/route.js +1 -1
  64. package/.next/server/app/api/models-config/test/route.js +1 -1
  65. package/.next/server/app/api/plot-kernels/route.js +1 -1
  66. package/.next/server/app/api/plot-kernels/save/route.js +1 -0
  67. package/.next/server/app/api/plot-kernels/save/route_client-reference-manifest.js +1 -0
  68. package/.next/server/app/api/plot-kernels/status/route.js +1 -1
  69. package/.next/server/app/api/plot-kernels/stop/route.js +1 -1
  70. package/.next/server/app/api/projects/browse/route.js +1 -1
  71. package/.next/server/app/api/projects/route.js +2 -2
  72. package/.next/server/app/api/search/route.js +2 -2
  73. package/.next/server/app/api/sessions/[id]/context/route.js +2 -2
  74. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  75. package/.next/server/app/api/sessions/new/route.js +1 -1
  76. package/.next/server/app/api/sessions/route.js +2 -2
  77. package/.next/server/app/api/settings/route.js +1 -1
  78. package/.next/server/app/api/skills/install/route.js +2 -2
  79. package/.next/server/app/api/skills/route.js +2 -2
  80. package/.next/server/app/api/skills/search/route.js +1 -1
  81. package/.next/server/app/api/soul/route.js +1 -1
  82. package/.next/server/app/api/subagents/status/route.js +1 -1
  83. package/.next/server/app/api/version/route.js +1 -1
  84. package/.next/server/app/favicon.ico/route.js +1 -1
  85. package/.next/server/app/icon.svg/route.js +1 -1
  86. package/.next/server/app/index.html +1 -1
  87. package/.next/server/app/index.rsc +2 -2
  88. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  89. package/.next/server/app/index.segments/_full.segment.rsc +2 -2
  90. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/.next/server/app/login/page.js +2 -2
  94. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/login.html +1 -1
  96. package/.next/server/app/login.rsc +1 -1
  97. package/.next/server/app/login.segments/_full.segment.rsc +1 -1
  98. package/.next/server/app/login.segments/_head.segment.rsc +1 -1
  99. package/.next/server/app/login.segments/_index.segment.rsc +1 -1
  100. package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
  101. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
  102. package/.next/server/app/login.segments/login.segment.rsc +1 -1
  103. package/.next/server/app/page.js +10 -10
  104. package/.next/server/app/page_client-reference-manifest.js +1 -1
  105. package/.next/server/app/smoke/page.js +3 -3
  106. package/.next/server/app/smoke/page_client-reference-manifest.js +1 -1
  107. package/.next/server/app/smoke.html +78 -23
  108. package/.next/server/app/smoke.rsc +2 -2
  109. package/.next/server/app/smoke.segments/_full.segment.rsc +2 -2
  110. package/.next/server/app/smoke.segments/_head.segment.rsc +1 -1
  111. package/.next/server/app/smoke.segments/_index.segment.rsc +1 -1
  112. package/.next/server/app/smoke.segments/_tree.segment.rsc +1 -1
  113. package/.next/server/app/smoke.segments/smoke/__PAGE__.segment.rsc +2 -2
  114. package/.next/server/app/smoke.segments/smoke.segment.rsc +1 -1
  115. package/.next/server/app-paths-manifest.json +14 -7
  116. package/.next/server/chunks/1494.js +278 -0
  117. package/.next/server/chunks/7601.js +44 -75
  118. package/.next/server/functions-config-manifest.json +3 -0
  119. package/.next/server/middleware-build-manifest.js +1 -1
  120. package/.next/server/next-font-manifest.js +1 -1
  121. package/.next/server/next-font-manifest.json +1 -1
  122. package/.next/server/pages/404.html +1 -1
  123. package/.next/server/pages/500.html +1 -1
  124. package/.next/server/server-reference-manifest.json +1 -1
  125. package/.next/static/TS6P9hsexC-QhX3lT3LiC/_buildManifest.js +1 -0
  126. package/.next/static/chunks/9901-7e044d778f6622ea.js +278 -0
  127. package/.next/static/chunks/app/_global-error/page-b23e767fd4cc8cb6.js +1 -0
  128. package/.next/static/chunks/app/api/agent/[id]/events/route-b23e767fd4cc8cb6.js +1 -0
  129. package/.next/static/chunks/app/api/agent/[id]/route-b23e767fd4cc8cb6.js +1 -0
  130. package/.next/static/chunks/app/api/agent/new/route-b23e767fd4cc8cb6.js +1 -0
  131. package/.next/static/chunks/app/api/agent/runtime/route-b23e767fd4cc8cb6.js +1 -0
  132. package/.next/static/chunks/app/api/apps/[id]/[[...path]]/route-b23e767fd4cc8cb6.js +1 -0
  133. package/.next/static/chunks/app/api/apps/route-b23e767fd4cc8cb6.js +1 -0
  134. package/.next/static/chunks/app/api/auth/all-providers/route-b23e767fd4cc8cb6.js +1 -0
  135. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-b23e767fd4cc8cb6.js +1 -0
  136. package/.next/static/chunks/app/api/auth/login/[provider]/route-b23e767fd4cc8cb6.js +1 -0
  137. package/.next/static/chunks/app/api/auth/login/route-b23e767fd4cc8cb6.js +1 -0
  138. package/.next/static/chunks/app/api/auth/logout/[provider]/route-b23e767fd4cc8cb6.js +1 -0
  139. package/.next/static/chunks/app/api/auth/providers/route-b23e767fd4cc8cb6.js +1 -0
  140. package/.next/static/chunks/app/api/auth/status/route-b23e767fd4cc8cb6.js +1 -0
  141. package/.next/static/chunks/app/api/default-cwd/route-b23e767fd4cc8cb6.js +1 -0
  142. package/.next/static/chunks/app/api/files/[...path]/route-b23e767fd4cc8cb6.js +1 -0
  143. package/.next/static/chunks/app/api/harness/route-b23e767fd4cc8cb6.js +1 -0
  144. package/.next/static/chunks/app/api/home/route-b23e767fd4cc8cb6.js +1 -0
  145. package/.next/static/chunks/app/api/im/cancel/route-b23e767fd4cc8cb6.js +1 -0
  146. package/.next/static/chunks/app/api/im/gateway-status/route-b23e767fd4cc8cb6.js +1 -0
  147. package/.next/static/chunks/app/api/im/gateway-token/route-b23e767fd4cc8cb6.js +1 -0
  148. package/.next/static/chunks/app/api/im/project/route-b23e767fd4cc8cb6.js +1 -0
  149. package/.next/static/chunks/app/api/im/projects/route-b23e767fd4cc8cb6.js +1 -0
  150. package/.next/static/chunks/app/api/im/session-ids/route-b23e767fd4cc8cb6.js +1 -0
  151. package/.next/static/chunks/app/api/im/turn/route-b23e767fd4cc8cb6.js +1 -0
  152. package/.next/static/chunks/app/api/internal/runtime/route-b23e767fd4cc8cb6.js +1 -0
  153. package/.next/static/chunks/app/api/memory/promote/route-b23e767fd4cc8cb6.js +1 -0
  154. package/.next/static/chunks/app/api/models/route-b23e767fd4cc8cb6.js +1 -0
  155. package/.next/static/chunks/app/api/models-config/discover/route-b23e767fd4cc8cb6.js +1 -0
  156. package/.next/static/chunks/app/api/models-config/route-b23e767fd4cc8cb6.js +1 -0
  157. package/.next/static/chunks/app/api/models-config/test/route-b23e767fd4cc8cb6.js +1 -0
  158. package/.next/static/chunks/app/api/plot-kernels/route-b23e767fd4cc8cb6.js +1 -0
  159. package/.next/static/chunks/app/api/plot-kernels/save/route-b23e767fd4cc8cb6.js +1 -0
  160. package/.next/static/chunks/app/api/plot-kernels/status/route-b23e767fd4cc8cb6.js +1 -0
  161. package/.next/static/chunks/app/api/plot-kernels/stop/route-b23e767fd4cc8cb6.js +1 -0
  162. package/.next/static/chunks/app/api/projects/browse/route-b23e767fd4cc8cb6.js +1 -0
  163. package/.next/static/chunks/app/api/projects/route-b23e767fd4cc8cb6.js +1 -0
  164. package/.next/static/chunks/app/api/search/route-b23e767fd4cc8cb6.js +1 -0
  165. package/.next/static/chunks/app/api/sessions/[id]/context/route-b23e767fd4cc8cb6.js +1 -0
  166. package/.next/static/chunks/app/api/sessions/[id]/route-b23e767fd4cc8cb6.js +1 -0
  167. package/.next/static/chunks/app/api/sessions/new/route-b23e767fd4cc8cb6.js +1 -0
  168. package/.next/static/chunks/app/api/sessions/route-b23e767fd4cc8cb6.js +1 -0
  169. package/.next/static/chunks/app/api/settings/route-b23e767fd4cc8cb6.js +1 -0
  170. package/.next/static/chunks/app/api/skills/install/route-b23e767fd4cc8cb6.js +1 -0
  171. package/.next/static/chunks/app/api/skills/route-b23e767fd4cc8cb6.js +1 -0
  172. package/.next/static/chunks/app/api/skills/search/route-b23e767fd4cc8cb6.js +1 -0
  173. package/.next/static/chunks/app/api/soul/route-b23e767fd4cc8cb6.js +1 -0
  174. package/.next/static/chunks/app/api/subagents/status/route-b23e767fd4cc8cb6.js +1 -0
  175. package/.next/static/chunks/app/api/version/route-b23e767fd4cc8cb6.js +1 -0
  176. package/.next/static/chunks/app/favicon.ico/route-b23e767fd4cc8cb6.js +1 -0
  177. package/.next/static/chunks/app/page-7dae74b4ec345ca7.js +27 -0
  178. package/.next/static/chunks/app/smoke/{page-431310549b820f3b.js → page-d1be0dc9ec4396ba.js} +2 -2
  179. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b23e767fd4cc8cb6.js +1 -0
  180. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b23e767fd4cc8cb6.js +1 -0
  181. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b23e767fd4cc8cb6.js +1 -0
  182. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b23e767fd4cc8cb6.js +1 -0
  183. package/bin/annovibe-im-gateway.js +675 -0
  184. package/bin/pi-web.js +60 -1
  185. package/package.json +3 -2
  186. package/.next/server/app/api/reports/[id]/route.js +0 -12
  187. package/.next/server/app/api/reports/[id]/route_client-reference-manifest.js +0 -1
  188. package/.next/server/chunks/1210.js +0 -246
  189. package/.next/static/3gDAeILrwgLWhlwi2MAOg/_buildManifest.js +0 -1
  190. package/.next/static/chunks/9595-2f001a1f6d133466.js +0 -246
  191. package/.next/static/chunks/app/_global-error/page-412387d4b52026c9.js +0 -1
  192. package/.next/static/chunks/app/api/agent/[id]/events/route-412387d4b52026c9.js +0 -1
  193. package/.next/static/chunks/app/api/agent/[id]/route-412387d4b52026c9.js +0 -1
  194. package/.next/static/chunks/app/api/agent/new/route-412387d4b52026c9.js +0 -1
  195. package/.next/static/chunks/app/api/agent/runtime/route-412387d4b52026c9.js +0 -1
  196. package/.next/static/chunks/app/api/apps/[id]/[[...path]]/route-412387d4b52026c9.js +0 -1
  197. package/.next/static/chunks/app/api/apps/route-412387d4b52026c9.js +0 -1
  198. package/.next/static/chunks/app/api/auth/all-providers/route-412387d4b52026c9.js +0 -1
  199. package/.next/static/chunks/app/api/auth/api-key/[provider]/route-412387d4b52026c9.js +0 -1
  200. package/.next/static/chunks/app/api/auth/login/[provider]/route-412387d4b52026c9.js +0 -1
  201. package/.next/static/chunks/app/api/auth/login/route-412387d4b52026c9.js +0 -1
  202. package/.next/static/chunks/app/api/auth/logout/[provider]/route-412387d4b52026c9.js +0 -1
  203. package/.next/static/chunks/app/api/auth/providers/route-412387d4b52026c9.js +0 -1
  204. package/.next/static/chunks/app/api/auth/status/route-412387d4b52026c9.js +0 -1
  205. package/.next/static/chunks/app/api/default-cwd/route-412387d4b52026c9.js +0 -1
  206. package/.next/static/chunks/app/api/files/[...path]/route-412387d4b52026c9.js +0 -1
  207. package/.next/static/chunks/app/api/harness/route-412387d4b52026c9.js +0 -1
  208. package/.next/static/chunks/app/api/home/route-412387d4b52026c9.js +0 -1
  209. package/.next/static/chunks/app/api/internal/runtime/route-412387d4b52026c9.js +0 -1
  210. package/.next/static/chunks/app/api/memory/promote/route-412387d4b52026c9.js +0 -1
  211. package/.next/static/chunks/app/api/models/route-412387d4b52026c9.js +0 -1
  212. package/.next/static/chunks/app/api/models-config/discover/route-412387d4b52026c9.js +0 -1
  213. package/.next/static/chunks/app/api/models-config/route-412387d4b52026c9.js +0 -1
  214. package/.next/static/chunks/app/api/models-config/test/route-412387d4b52026c9.js +0 -1
  215. package/.next/static/chunks/app/api/plot-kernels/route-412387d4b52026c9.js +0 -1
  216. package/.next/static/chunks/app/api/plot-kernels/status/route-412387d4b52026c9.js +0 -1
  217. package/.next/static/chunks/app/api/plot-kernels/stop/route-412387d4b52026c9.js +0 -1
  218. package/.next/static/chunks/app/api/projects/browse/route-412387d4b52026c9.js +0 -1
  219. package/.next/static/chunks/app/api/projects/route-412387d4b52026c9.js +0 -1
  220. package/.next/static/chunks/app/api/reports/[id]/route-412387d4b52026c9.js +0 -1
  221. package/.next/static/chunks/app/api/search/route-412387d4b52026c9.js +0 -1
  222. package/.next/static/chunks/app/api/sessions/[id]/context/route-412387d4b52026c9.js +0 -1
  223. package/.next/static/chunks/app/api/sessions/[id]/route-412387d4b52026c9.js +0 -1
  224. package/.next/static/chunks/app/api/sessions/new/route-412387d4b52026c9.js +0 -1
  225. package/.next/static/chunks/app/api/sessions/route-412387d4b52026c9.js +0 -1
  226. package/.next/static/chunks/app/api/settings/route-412387d4b52026c9.js +0 -1
  227. package/.next/static/chunks/app/api/skills/install/route-412387d4b52026c9.js +0 -1
  228. package/.next/static/chunks/app/api/skills/route-412387d4b52026c9.js +0 -1
  229. package/.next/static/chunks/app/api/skills/search/route-412387d4b52026c9.js +0 -1
  230. package/.next/static/chunks/app/api/soul/route-412387d4b52026c9.js +0 -1
  231. package/.next/static/chunks/app/api/subagents/status/route-412387d4b52026c9.js +0 -1
  232. package/.next/static/chunks/app/api/version/route-412387d4b52026c9.js +0 -1
  233. package/.next/static/chunks/app/favicon.ico/route-412387d4b52026c9.js +0 -1
  234. package/.next/static/chunks/app/page-d82d53365a2be371.js +0 -27
  235. package/.next/static/chunks/next/dist/client/components/builtin/app-error-412387d4b52026c9.js +0 -1
  236. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-412387d4b52026c9.js +0 -1
  237. package/.next/static/chunks/next/dist/client/components/builtin/not-found-412387d4b52026c9.js +0 -1
  238. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-412387d4b52026c9.js +0 -1
  239. /package/.next/static/{3gDAeILrwgLWhlwi2MAOg → TS6P9hsexC-QhX3lT3LiC}/_ssgManifest.js +0 -0
@@ -0,0 +1,675 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Annovibe IM Gateway — Standalone process that bridges WeCom bots to the
4
+ * Annovibe Next.js server.
5
+ *
6
+ * Usage: node bin/annovibe-im-gateway.js
7
+ *
8
+ * Reads project IM configs from localhost:PORT/api/im/projects,
9
+ * connects each enabled bot to WeCom WebSocket, and routes
10
+ * incoming messages through /api/im/turn (SSE streaming with delta push).
11
+ *
12
+ * Auto-started by annovibe supervisor; can also run standalone for debugging.
13
+ */
14
+ "use strict";
15
+
16
+ const fs = require("fs");
17
+ const http = require("http");
18
+ const https = require("https");
19
+ const os = require("os");
20
+ const path = require("path");
21
+ const crypto = require("crypto");
22
+ const WebSocket = require("ws");
23
+
24
+ const REVISION_DIGITS = ["\u200b", "\u200c", "\u200d", "\u2060", "\ufeff"];
25
+ const REVISION_SUFFIX_PATTERN = /[\u200b\u200c\u200d\u2060\ufeff]+$/u;
26
+
27
+ // ── Cancel command vocabulary ───────────────────────────────────────
28
+
29
+ const IM_CANCEL_COMMANDS = new Set([
30
+ "取消", "停止", "终止", "cancel", "stop", "abort",
31
+ ]);
32
+
33
+ function normalizeImCancelText(text) {
34
+ return text.trim()
35
+ .replace(REVISION_SUFFIX_PATTERN, "")
36
+ .replace(/[\u200b\u200c\u200d\u2060\ufeff\u00ad\ufe00-\ufe0f]/g, "")
37
+ .trim().toLowerCase().replace(/\s+/g, "");
38
+ }
39
+
40
+ function isImCancelCommand(text) {
41
+ return IM_CANCEL_COMMANDS.has(normalizeImCancelText(text));
42
+ }
43
+
44
+ // ── Reply text cleanup ──────────────────────────────────────────────
45
+
46
+ const SHOW_WIDGET_FENCE_RE = /```\s*show-widget[\s\S]*?```/gi;
47
+ const SHOW_WIDGET_PARTIAL_RE = /```\s*show-widget[\s\S]*$/;
48
+
49
+ function cleanImReplyText(text) {
50
+ let cleaned = String(text || "").replace(SHOW_WIDGET_FENCE_RE, "");
51
+ cleaned = cleaned.replace(SHOW_WIDGET_PARTIAL_RE, "");
52
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n").trim();
53
+ return cleaned;
54
+ }
55
+
56
+ // ── Config ──────────────────────────────────────────────────────────
57
+
58
+ function envFirst(...names) {
59
+ for (const name of names) {
60
+ const value = process.env[name];
61
+ if (value !== undefined && value !== "") return value;
62
+ }
63
+ return undefined;
64
+ }
65
+
66
+ function getAgentDir() {
67
+ return envFirst("PI_AGENT_DIR") ?? path.join(os.homedir(), ".pi", "agent");
68
+ }
69
+
70
+ function readAnnovibeState() {
71
+ const statePath = path.join(getAgentDir(), "annovibe.json");
72
+ try {
73
+ return JSON.parse(fs.readFileSync(statePath, "utf8"));
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ function getAnnovibePort() {
80
+ const state = readAnnovibeState();
81
+ if (state?.port) return String(state.port);
82
+ return envFirst("ANNOVIBE_PORT", "PORT") ?? "30141";
83
+ }
84
+
85
+ function getBaseUrl() {
86
+ return `http://127.0.0.1:${getAnnovibePort()}`;
87
+ }
88
+
89
+ function readGatewayToken() {
90
+ const statePath = path.join(getAgentDir(), "im", "gateway.json");
91
+ try {
92
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
93
+ return typeof state.token === "string" ? state.token.trim() : "";
94
+ } catch {
95
+ return "";
96
+ }
97
+ }
98
+
99
+ // ── HTTP helpers ────────────────────────────────────────────────────
100
+
101
+ function getToken() {
102
+ return process.env.ANNOVIBE_GATEWAY_TOKEN?.trim() || readGatewayToken();
103
+ }
104
+
105
+ function requestJson(method, pathname, body) {
106
+ const url = new URL(pathname, getBaseUrl());
107
+ const payload = body === undefined ? undefined : JSON.stringify(body);
108
+ const lib = url.protocol === "https:" ? https : http;
109
+ const token = getToken();
110
+
111
+ return new Promise((resolve, reject) => {
112
+ const req = lib.request(
113
+ url,
114
+ {
115
+ method,
116
+ headers: {
117
+ ...(payload ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) } : {}),
118
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
119
+ },
120
+ },
121
+ (res) => {
122
+ let data = "";
123
+ res.setEncoding("utf8");
124
+ res.on("data", (chunk) => { data += chunk; });
125
+ res.on("end", () => {
126
+ let parsed;
127
+ try { parsed = data ? JSON.parse(data) : {}; } catch (e) {
128
+ reject(new Error(`Invalid JSON: ${String(e)}`));
129
+ return;
130
+ }
131
+ if (res.statusCode >= 400) {
132
+ const msg = parsed.error || parsed.reply || `HTTP ${res.statusCode}`;
133
+ reject(new Error(formatImApiError(res.statusCode, msg)));
134
+ return;
135
+ }
136
+ resolve(parsed);
137
+ });
138
+ },
139
+ );
140
+ req.on("error", reject);
141
+ if (payload) req.write(payload);
142
+ req.end();
143
+ });
144
+ }
145
+
146
+ function formatImApiError(statusCode, message) {
147
+ if (statusCode === 401) {
148
+ return `${message}. Ensure annovibe is running in this Linux account and gateway token is synced (restart annovibe-im-gateway after saving IM settings).`;
149
+ }
150
+ if (statusCode === 403) {
151
+ return `${message}. annovibe-im-gateway must reach annovibe on localhost.`;
152
+ }
153
+ return message;
154
+ }
155
+
156
+ /**
157
+ * Consume SSE turn stream, yielding deltas and status changes to callbacks.
158
+ */
159
+ function requestImTurnStream(body, callbacks) {
160
+ const url = new URL("/api/im/turn", getBaseUrl());
161
+ const payload = JSON.stringify(body);
162
+ const lib = url.protocol === "https:" ? https : http;
163
+ const token = getToken();
164
+
165
+ return new Promise((resolve, reject) => {
166
+ const req = lib.request(
167
+ url,
168
+ {
169
+ method: "POST",
170
+ headers: {
171
+ "Content-Type": "application/json",
172
+ "Content-Length": Buffer.byteLength(payload),
173
+ Accept: "text/event-stream",
174
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
175
+ },
176
+ },
177
+ (res) => {
178
+ if (res.statusCode >= 400) {
179
+ let data = "";
180
+ res.setEncoding("utf8");
181
+ res.on("data", (chunk) => { data += chunk; });
182
+ res.on("end", () => {
183
+ let parsed;
184
+ try { parsed = data ? JSON.parse(data) : {}; } catch { parsed = { error: data }; }
185
+ reject(new Error(parsed.error || parsed.reply || `HTTP ${res.statusCode}`));
186
+ });
187
+ return;
188
+ }
189
+
190
+ let buffer = "";
191
+ let doneEvent = null;
192
+
193
+ res.on("data", (chunk) => {
194
+ buffer += chunk;
195
+ const parts = buffer.split("\n\n");
196
+ buffer = parts.pop() || "";
197
+ for (const part of parts) {
198
+ for (const line of part.split("\n")) {
199
+ if (!line.startsWith("data: ")) continue;
200
+ try {
201
+ const event = JSON.parse(line.slice(6));
202
+ if (!event) continue;
203
+ if (event.type === "done") {
204
+ doneEvent = event;
205
+ } else if (event.type === "delta" && callbacks.onDelta) {
206
+ callbacks.onDelta(event.text);
207
+ } else if (event.type === "status" && callbacks.onStatus) {
208
+ callbacks.onStatus(event.phase);
209
+ }
210
+ } catch { /* ignore malformed frames */ }
211
+ }
212
+ }
213
+ });
214
+
215
+ res.on("end", () => {
216
+ if (doneEvent) {
217
+ resolve(doneEvent);
218
+ } else {
219
+ resolve({ type: "done", ok: false, reply: "No response from agent" });
220
+ }
221
+ });
222
+
223
+ res.on("error", reject);
224
+ },
225
+ );
226
+ req.on("error", reject);
227
+ req.write(payload);
228
+ req.end();
229
+ });
230
+ }
231
+
232
+ // ── WeCom helpers ───────────────────────────────────────────────────
233
+
234
+ function encodeRevision(revision) {
235
+ const base = REVISION_DIGITS.length;
236
+ let remaining = Math.max(1, revision);
237
+ let encoded = "";
238
+ while (remaining > 0) {
239
+ const digit = remaining % base;
240
+ encoded += REVISION_DIGITS[digit];
241
+ remaining = Math.floor(remaining / base);
242
+ }
243
+ return encoded;
244
+ }
245
+
246
+ function applyRevisionSuffix(text, revision) {
247
+ return text + encodeRevision(revision);
248
+ }
249
+
250
+ function stripRevisionSuffix(text) {
251
+ return text.replace(REVISION_SUFFIX_PATTERN, "");
252
+ }
253
+
254
+ function shouldAcceptGroupMessage(message, requireMention) {
255
+ if (!message.chatId) return true; // DM, always accept
256
+ if (!requireMention) return true; // group chat, mention not required
257
+ return message.mentionedBot; // group chat, mention required
258
+ }
259
+
260
+ // ── WeCom WebSocket client ──────────────────────────────────────────
261
+
262
+ class WeComWsClient {
263
+ constructor(options) {
264
+ this.options = options;
265
+ this.ws = null;
266
+ this.heartbeatTimer = null;
267
+ this.pendingAcks = new Map();
268
+ this.authenticated = createDeferred();
269
+ }
270
+
271
+ get isConnected() {
272
+ return this.ws?.readyState === WebSocket.OPEN;
273
+ }
274
+
275
+ async connect() {
276
+ if (this.isConnected) return;
277
+
278
+ await new Promise((resolve, reject) => {
279
+ const ws = new WebSocket(this.options.wsUrl || "wss://openws.work.weixin.qq.com");
280
+ this.ws = ws;
281
+ ws.once("open", () => resolve());
282
+ ws.once("error", (err) => reject(err));
283
+ ws.on("error", () => undefined);
284
+ ws.on("message", (raw) => { void this.handleFrame(String(raw)); });
285
+ ws.on("close", () => {
286
+ this.stopHeartbeat();
287
+ this.rejectPendingAcks(new Error("wecom websocket closed"));
288
+ });
289
+ });
290
+
291
+ await this.sendFrame({
292
+ cmd: "aibot_subscribe",
293
+ headers: { req_id: `aibot_subscribe_${Date.now()}` },
294
+ body: { bot_id: this.options.botId, secret: this.options.botSecret },
295
+ });
296
+ }
297
+
298
+ async waitAuthenticated(timeoutMs = 10000) {
299
+ const timer = setTimeout(() => {
300
+ this.authenticated.reject(new Error("wecom authentication timeout"));
301
+ }, timeoutMs);
302
+ try {
303
+ await this.authenticated.promise;
304
+ return true;
305
+ } catch { return false; }
306
+ finally { clearTimeout(timer); }
307
+ }
308
+
309
+ async replyStream(reqId, streamId, content, finish) {
310
+ await this.sendFrame({
311
+ cmd: "aibot_respond_msg",
312
+ headers: { req_id: reqId },
313
+ body: { msgtype: "stream", stream: { id: streamId, content, finish } },
314
+ });
315
+ if (!finish) return;
316
+ await new Promise((resolve, reject) => {
317
+ const timeout = setTimeout(() => {
318
+ this.pendingAcks.delete(reqId);
319
+ reject(new Error(`wecom reply ack timeout for ${reqId}`));
320
+ }, 5000);
321
+ this.pendingAcks.set(reqId, { resolve, reject, timeout });
322
+ }).catch(() => undefined);
323
+ }
324
+
325
+ async disconnect() {
326
+ this.stopHeartbeat();
327
+ this.rejectPendingAcks(new Error("wecom websocket disconnected"));
328
+ if (!this.ws) return;
329
+ const ws = this.ws;
330
+ this.ws = null;
331
+ await new Promise((resolve) => {
332
+ ws.once("close", () => resolve());
333
+ ws.close();
334
+ });
335
+ }
336
+
337
+ async handleFrame(frameText) {
338
+ const frame = JSON.parse(frameText);
339
+ const reqId = frame.headers?.req_id?.trim();
340
+
341
+ if (reqId && this.pendingAcks.has(reqId)) {
342
+ const pending = this.pendingAcks.get(reqId);
343
+ if (pending) {
344
+ this.pendingAcks.delete(reqId);
345
+ clearTimeout(pending.timeout);
346
+ if ((frame.errcode ?? 0) !== 0) {
347
+ pending.reject(new Error(frame.errmsg || "wecom reply ack failed"));
348
+ } else {
349
+ pending.resolve();
350
+ }
351
+ }
352
+ return;
353
+ }
354
+
355
+ if (reqId?.startsWith("aibot_subscribe_")) {
356
+ if ((frame.errcode ?? 0) !== 0) {
357
+ this.authenticated.reject(new Error(frame.errmsg || "wecom authentication failed"));
358
+ return;
359
+ }
360
+ this.authenticated.resolve();
361
+ this.startHeartbeat();
362
+ return;
363
+ }
364
+
365
+ if (reqId?.startsWith("ping_")) return;
366
+
367
+ if (frame.cmd === "aibot_msg_callback" || frame.cmd === "aibot_event_callback") {
368
+ await this.options.onMessage(frame);
369
+ }
370
+ }
371
+
372
+ startHeartbeat() {
373
+ this.stopHeartbeat();
374
+ const intervalMs = this.options.heartbeatIntervalMs ?? 30000;
375
+ this.heartbeatTimer = setInterval(() => {
376
+ void this.sendFrame({
377
+ cmd: "ping",
378
+ headers: { req_id: `ping_${Date.now()}` },
379
+ }).catch(() => undefined);
380
+ }, intervalMs);
381
+ }
382
+
383
+ stopHeartbeat() {
384
+ if (!this.heartbeatTimer) return;
385
+ clearInterval(this.heartbeatTimer);
386
+ this.heartbeatTimer = null;
387
+ }
388
+
389
+ async sendFrame(frame) {
390
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
391
+ throw new Error("wecom websocket not connected");
392
+ }
393
+ this.ws.send(JSON.stringify(frame));
394
+ }
395
+
396
+ rejectPendingAcks(error) {
397
+ for (const [reqId, pending] of this.pendingAcks) {
398
+ this.pendingAcks.delete(reqId);
399
+ clearTimeout(pending.timeout);
400
+ pending.reject(error);
401
+ }
402
+ }
403
+ }
404
+
405
+ function createDeferred() {
406
+ let resolve, reject;
407
+ const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
408
+ return { promise, resolve, reject };
409
+ }
410
+
411
+ // ── Inbound message extraction ──────────────────────────────────────
412
+
413
+ function extractInboundMessage(frame) {
414
+ const reqId = frame.headers?.req_id?.trim();
415
+ const body = frame.body;
416
+ if (!reqId || !body || body.msgtype !== "text") return null;
417
+
418
+ const from = body.from;
419
+ const rawUserId = typeof from?.userid === "string" ? from.userid.trim() : "";
420
+ const textBody = body.text;
421
+ let content = typeof textBody?.content === "string" ? textBody.content.trim() : "";
422
+ if (!rawUserId || !content) return null;
423
+
424
+ const chatId = (typeof body.chat_id === "string" ? body.chat_id : null) ||
425
+ (typeof body.room_id === "string" ? body.room_id : null) || null;
426
+
427
+ let mentionedBot = false;
428
+ if (chatId) {
429
+ const mentions = content.match(/<@[^>]+>\s*/g);
430
+ if (mentions && mentions.length > 0) {
431
+ mentionedBot = true;
432
+ content = content.replace(/<@[^>]+>\s*/g, "").trim();
433
+ }
434
+ if (!content) return null;
435
+ }
436
+
437
+ return { reqId, userId: rawUserId, chatId, content, mentionedBot };
438
+ }
439
+
440
+ // ── Project bridge ──────────────────────────────────────────────────
441
+
442
+ class ProjectWeComBridge {
443
+ constructor(project) {
444
+ this.project = project;
445
+ this.client = null;
446
+ this.stopped = false;
447
+ this.reconnectTimer = null;
448
+ }
449
+
450
+ async start() {
451
+ if (this.stopped) return;
452
+ const secret = this.project.botSecret;
453
+ if (!secret) {
454
+ console.error(`[im] missing bot secret for ${this.project.cwd}`);
455
+ this.scheduleReconnect(30000);
456
+ return;
457
+ }
458
+
459
+ this.client = new WeComWsClient({
460
+ botId: this.project.botId,
461
+ botSecret: secret,
462
+ onMessage: (frame) => this.handleFrame(frame),
463
+ });
464
+
465
+ try {
466
+ await this.client.connect();
467
+ const ok = await this.client.waitAuthenticated();
468
+ if (!ok) throw new Error("wecom authentication failed");
469
+ console.error(`[im] connected project ${this.project.cwd} bot ${this.project.botId}`);
470
+ } catch (error) {
471
+ console.error(`[im] connect failed for ${this.project.cwd}: ${String(error)}`);
472
+ await this.client?.disconnect().catch(() => undefined);
473
+ this.client = null;
474
+ this.scheduleReconnect(10000);
475
+ }
476
+ }
477
+
478
+ scheduleReconnect(delayMs) {
479
+ if (this.stopped || this.reconnectTimer) return;
480
+ this.reconnectTimer = setTimeout(() => {
481
+ this.reconnectTimer = null;
482
+ void this.start();
483
+ }, delayMs);
484
+ }
485
+
486
+ async stop() {
487
+ this.stopped = true;
488
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
489
+ await this.client?.disconnect().catch(() => undefined);
490
+ this.client = null;
491
+ }
492
+
493
+ async handleFrame(frame) {
494
+ const message = extractInboundMessage(frame);
495
+ if (!message) return;
496
+
497
+ // Group message filtering — respect requireMentionInGroup config
498
+ if (!shouldAcceptGroupMessage(message, this.project.requireMentionInGroup)) return;
499
+
500
+ // ── Cancel command intercept ──
501
+ if (isImCancelCommand(message.content)) {
502
+ const reqId = message.reqId;
503
+ try {
504
+ const result = await requestJson("POST", "/api/im/cancel", {
505
+ cwd: this.project.cwd,
506
+ userId: message.userId,
507
+ });
508
+ const text = typeof result.reply === "string" && result.reply.trim()
509
+ ? result.reply.trim()
510
+ : (result.cancelled ? "已取消当前任务。" : "当前没有可取消的任务。");
511
+ if (this.client?.isConnected) {
512
+ const streamId = `${reqId}-cancel-${Date.now()}`;
513
+ await this.client.replyStream(reqId, streamId, text, true);
514
+ }
515
+ } catch (error) {
516
+ console.error(`[im] cancel failed for ${this.project.cwd} user ${message.userId}: ${String(error)}`);
517
+ if (this.client?.isConnected) {
518
+ const streamId = `${reqId}-cancel-${Date.now()}`;
519
+ await this.client.replyStream(reqId, streamId, `Error: ${String(error)}`, true);
520
+ }
521
+ }
522
+ return;
523
+ }
524
+
525
+ // ── Normal turn with streaming ──
526
+ const reqId = message.reqId;
527
+ const streamId = `${reqId}-stream`;
528
+ let revision = 0;
529
+ const startedAt = Date.now();
530
+ let refreshTimer = null;
531
+ let toolsRunning = false;
532
+ let lastText = "";
533
+
534
+ const pushText = async (text) => {
535
+ revision += 1;
536
+ if (this.client?.isConnected) {
537
+ await this.client.replyStream(reqId, streamId, applyRevisionSuffix(text, revision), false).catch(() => undefined);
538
+ }
539
+ };
540
+
541
+ const finishText = async (text) => {
542
+ revision += 1;
543
+ if (this.client?.isConnected) {
544
+ await this.client.replyStream(reqId, streamId, applyRevisionSuffix(text, revision), true).catch(() => undefined);
545
+ }
546
+ };
547
+
548
+ try {
549
+ // Start processing
550
+ await pushText("(working 1s)");
551
+
552
+ refreshTimer = setInterval(() => {
553
+ if (toolsRunning) {
554
+ const elapsed = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
555
+ void pushText(`(working ${elapsed}s)`).catch(() => undefined);
556
+ }
557
+ }, 5000);
558
+
559
+ const result = await requestImTurnStream(
560
+ { cwd: this.project.cwd, userId: message.userId, message: message.content, chatId: message.chatId },
561
+ {
562
+ onStatus(phase) {
563
+ toolsRunning = phase === "running_tools" || phase === "waiting_model";
564
+ },
565
+ onDelta(text) {
566
+ toolsRunning = false;
567
+ lastText = text;
568
+ // Schedule push — debounced by WeCom rate limits via replyStream
569
+ void pushText(text).catch(() => undefined);
570
+ },
571
+ },
572
+ );
573
+
574
+ if (refreshTimer) clearInterval(refreshTimer);
575
+
576
+ const rawReply = typeof result.reply === "string" && result.reply.trim()
577
+ ? result.reply.trim()
578
+ : "No reply from annovibe.";
579
+ const cleaned = cleanImReplyText(lastText || rawReply);
580
+ const finishTextStr = cleaned || "No reply from annovibe.";
581
+ await finishText(finishTextStr);
582
+ } catch (error) {
583
+ if (refreshTimer) clearInterval(refreshTimer);
584
+ await finishText(`Error: ${error.message}`);
585
+ }
586
+ }
587
+ }
588
+
589
+ class ImGateway {
590
+ constructor() {
591
+ this.bridges = new Map();
592
+ this.pollTimer = null;
593
+ this.stopped = false;
594
+ }
595
+
596
+ async start() {
597
+ console.error("[im] Annovibe IM Gateway starting...");
598
+
599
+ // Write gateway state
600
+ const imDir = path.join(getAgentDir(), "im");
601
+ try { fs.mkdirSync(imDir, { recursive: true }); } catch {}
602
+ const token = crypto.randomBytes(32).toString("hex");
603
+ fs.writeFileSync(
604
+ path.join(imDir, "gateway.json"),
605
+ JSON.stringify({
606
+ token,
607
+ startedAt: new Date().toISOString(),
608
+ pid: process.pid,
609
+ }, null, 2),
610
+ );
611
+
612
+ console.error(`[im] Gateway token: ${token.slice(0, 8)}...`);
613
+ console.error(`[im] Annovibe port: ${getAnnovibePort()}`);
614
+
615
+ // Start config poll
616
+ await this.refreshProjects();
617
+ this.pollTimer = setInterval(() => this.refreshProjects(), 30000);
618
+ }
619
+
620
+ async refreshProjects() {
621
+ if (this.stopped) return;
622
+ try {
623
+ const data = await requestJson("GET", "/api/im/projects");
624
+ const projects = data.projects || [];
625
+ const activeCwds = new Set();
626
+
627
+ for (const project of projects) {
628
+ if (!project.cwd || !project.botId) continue;
629
+ activeCwds.add(project.cwd);
630
+
631
+ const bridge = this.bridges.get(project.cwd);
632
+ if (bridge) {
633
+ bridge.project = project;
634
+ } else {
635
+ const newBridge = new ProjectWeComBridge(project);
636
+ this.bridges.set(project.cwd, newBridge);
637
+ void newBridge.start();
638
+ }
639
+ }
640
+
641
+ for (const [cwd, bridge] of this.bridges) {
642
+ if (activeCwds.has(cwd)) continue;
643
+ await bridge.stop();
644
+ this.bridges.delete(cwd);
645
+ }
646
+ } catch (error) {
647
+ console.error(`[im] Failed to poll projects: ${error.message}`);
648
+ }
649
+ }
650
+
651
+ async stop() {
652
+ this.stopped = true;
653
+ if (this.pollTimer) clearInterval(this.pollTimer);
654
+ for (const bridge of this.bridges.values()) {
655
+ await bridge.stop();
656
+ }
657
+ this.bridges.clear();
658
+
659
+ try {
660
+ const statePath = path.join(getAgentDir(), "im", "gateway.json");
661
+ fs.writeFileSync(statePath, JSON.stringify({ token: "" }, null, 2));
662
+ } catch {}
663
+ }
664
+ }
665
+
666
+ // ── Main ────────────────────────────────────────────────────────────
667
+
668
+ const gateway = new ImGateway();
669
+
670
+ process.on("SIGINT", () => { void gateway.stop().then(() => process.exit(0)); });
671
+ process.on("SIGTERM", () => { void gateway.stop().then(() => process.exit(0)); });
672
+
673
+ void gateway.start();
674
+
675
+ setInterval(() => {}, 60000);