cli-claw-kit 0.0.1

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 (295) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +245 -0
  3. package/config/default-groups.json +1 -0
  4. package/config/global-agents-md.template.md +37 -0
  5. package/config/mount-allowlist.json +11 -0
  6. package/container/Dockerfile +160 -0
  7. package/container/agent-runner/dist/.tsbuildinfo +1 -0
  8. package/container/agent-runner/dist/agent-definitions.js +22 -0
  9. package/container/agent-runner/dist/channel-prefixes.js +16 -0
  10. package/container/agent-runner/dist/codex-config.js +29 -0
  11. package/container/agent-runner/dist/image-detector.js +96 -0
  12. package/container/agent-runner/dist/index.js +2587 -0
  13. package/container/agent-runner/dist/mcp-tools.js +1076 -0
  14. package/container/agent-runner/dist/stream-event.types.js +5 -0
  15. package/container/agent-runner/dist/stream-processor.js +867 -0
  16. package/container/agent-runner/dist/types.js +6 -0
  17. package/container/agent-runner/dist/utils.js +115 -0
  18. package/container/agent-runner/package.json +36 -0
  19. package/container/agent-runner/prompts/security-rules.md +31 -0
  20. package/container/agent-runner/src/agent-definitions.ts +27 -0
  21. package/container/agent-runner/src/channel-prefixes.ts +16 -0
  22. package/container/agent-runner/src/codex-config.ts +40 -0
  23. package/container/agent-runner/src/image-detector.ts +116 -0
  24. package/container/agent-runner/src/index.ts +3107 -0
  25. package/container/agent-runner/src/mcp-tools.ts +1295 -0
  26. package/container/agent-runner/src/stream-event.types.ts +10 -0
  27. package/container/agent-runner/src/stream-processor.ts +932 -0
  28. package/container/agent-runner/src/types.ts +75 -0
  29. package/container/agent-runner/src/utils.ts +114 -0
  30. package/container/agent-runner/tsconfig.json +17 -0
  31. package/container/build.sh +28 -0
  32. package/container/entrypoint.sh +64 -0
  33. package/container/skills/agent-browser/SKILL.md +159 -0
  34. package/container/skills/install-skill/SKILL.md +64 -0
  35. package/container/skills/post-test-cleanup/SKILL.md +121 -0
  36. package/dist/.tsbuildinfo +1 -0
  37. package/dist/agent-output-parser.js +459 -0
  38. package/dist/app-root.js +52 -0
  39. package/dist/assistant-meta-footer.js +1 -0
  40. package/dist/auth.js +91 -0
  41. package/dist/billing.js +694 -0
  42. package/dist/channel-prefixes.js +16 -0
  43. package/dist/cli.js +86 -0
  44. package/dist/commands.js +79 -0
  45. package/dist/config.js +120 -0
  46. package/dist/container-runner.js +981 -0
  47. package/dist/daily-summary.js +210 -0
  48. package/dist/db.js +3683 -0
  49. package/dist/dingtalk.js +1347 -0
  50. package/dist/feishu-markdown-style.js +97 -0
  51. package/dist/feishu-streaming-card.js +1875 -0
  52. package/dist/feishu.js +1628 -0
  53. package/dist/file-manager.js +270 -0
  54. package/dist/group-queue.js +1070 -0
  55. package/dist/group-runtime.js +35 -0
  56. package/dist/host-workspace-cwd.js +85 -0
  57. package/dist/im-channel.js +384 -0
  58. package/dist/im-command-utils.js +142 -0
  59. package/dist/im-downloader.js +45 -0
  60. package/dist/im-manager.js +527 -0
  61. package/dist/im-utils.js +53 -0
  62. package/dist/image-detector.js +96 -0
  63. package/dist/index.js +5828 -0
  64. package/dist/logger.js +22 -0
  65. package/dist/mcp-utils.js +66 -0
  66. package/dist/message-attachments.js +69 -0
  67. package/dist/message-notifier.js +36 -0
  68. package/dist/middleware/auth.js +85 -0
  69. package/dist/mount-security.js +315 -0
  70. package/dist/permissions.js +67 -0
  71. package/dist/project-memory.js +6 -0
  72. package/dist/provider-pool.js +189 -0
  73. package/dist/qq.js +826 -0
  74. package/dist/reset-admin.js +42 -0
  75. package/dist/routes/admin.js +543 -0
  76. package/dist/routes/agent-definitions.js +241 -0
  77. package/dist/routes/agents.js +533 -0
  78. package/dist/routes/auth.js +675 -0
  79. package/dist/routes/billing.js +490 -0
  80. package/dist/routes/browse.js +210 -0
  81. package/dist/routes/bug-report.js +387 -0
  82. package/dist/routes/config.js +1868 -0
  83. package/dist/routes/files.js +671 -0
  84. package/dist/routes/groups.js +1367 -0
  85. package/dist/routes/mcp-servers.js +320 -0
  86. package/dist/routes/memory.js +523 -0
  87. package/dist/routes/monitor.js +307 -0
  88. package/dist/routes/skills.js +777 -0
  89. package/dist/routes/tasks.js +509 -0
  90. package/dist/routes/usage.js +64 -0
  91. package/dist/routes/workspace-config.js +458 -0
  92. package/dist/runtime-build.js +112 -0
  93. package/dist/runtime-command-handler.js +189 -0
  94. package/dist/runtime-command-registry.js +1 -0
  95. package/dist/runtime-config.js +1777 -0
  96. package/dist/runtime-identity.js +52 -0
  97. package/dist/schemas.js +590 -0
  98. package/dist/script-runner.js +64 -0
  99. package/dist/sdk-query.js +82 -0
  100. package/dist/skill-utils.js +145 -0
  101. package/dist/sqlite-compat.js +19 -0
  102. package/dist/stream-event.types.js +5 -0
  103. package/dist/streaming-runtime-meta.js +29 -0
  104. package/dist/task-scheduler.js +695 -0
  105. package/dist/task-utils.js +13 -0
  106. package/dist/telegram-pairing.js +59 -0
  107. package/dist/telegram.js +897 -0
  108. package/dist/terminal-manager.js +307 -0
  109. package/dist/tool-step-display.js +1 -0
  110. package/dist/types.js +1 -0
  111. package/dist/utils.js +85 -0
  112. package/dist/web-context.js +161 -0
  113. package/dist/web.js +1377 -0
  114. package/dist/wechat-crypto.js +182 -0
  115. package/dist/wechat.js +589 -0
  116. package/dist/workspace-runtime-reset.js +35 -0
  117. package/package.json +107 -0
  118. package/shared/assistant-meta-footer.ts +127 -0
  119. package/shared/channel-prefixes.ts +16 -0
  120. package/shared/dist/assistant-meta-footer.d.ts +29 -0
  121. package/shared/dist/assistant-meta-footer.js +85 -0
  122. package/shared/dist/channel-prefixes.d.ts +4 -0
  123. package/shared/dist/channel-prefixes.js +16 -0
  124. package/shared/dist/image-detector.d.ts +20 -0
  125. package/shared/dist/image-detector.js +96 -0
  126. package/shared/dist/runtime-command-registry.d.ts +38 -0
  127. package/shared/dist/runtime-command-registry.js +185 -0
  128. package/shared/dist/stream-event.d.ts +65 -0
  129. package/shared/dist/stream-event.js +8 -0
  130. package/shared/dist/tool-step-display.d.ts +4 -0
  131. package/shared/dist/tool-step-display.js +11 -0
  132. package/shared/image-detector.ts +116 -0
  133. package/shared/runtime-command-registry.ts +252 -0
  134. package/shared/stream-event.ts +67 -0
  135. package/shared/tool-step-display.ts +21 -0
  136. package/shared/tsconfig.json +24 -0
  137. package/web/dist/assets/BillingPage-B1wBR_o-.js +52 -0
  138. package/web/dist/assets/ChatPage-6GBZ9nXN.css +32 -0
  139. package/web/dist/assets/ChatPage-BOJcXtaj.js +161 -0
  140. package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  141. package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  142. package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  143. package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  144. package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  145. package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  146. package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  147. package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  148. package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  149. package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  150. package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  151. package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  152. package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  153. package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  154. package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  155. package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  156. package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  157. package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  158. package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  159. package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  160. package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  161. package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  162. package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  163. package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  164. package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  165. package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  166. package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  167. package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  168. package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  169. package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  170. package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  171. package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  172. package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  173. package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  174. package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  175. package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  176. package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  177. package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  178. package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  179. package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  180. package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  181. package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  182. package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  183. package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  184. package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  185. package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  186. package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  187. package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  188. package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  189. package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  190. package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  191. package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  192. package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  193. package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  194. package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  195. package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  196. package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  197. package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  198. package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  199. package/web/dist/assets/SettingsPage-DoY7FoZ_.js +153 -0
  200. package/web/dist/assets/ShareImageDialog-C1ga8b7l.js +22 -0
  201. package/web/dist/assets/TasksPage-CRivnNsx.js +14 -0
  202. package/web/dist/assets/_basePickBy-Bf-bSoS9.js +1 -0
  203. package/web/dist/assets/_baseUniq-zAOaCuKw.js +1 -0
  204. package/web/dist/assets/arc-Dm9mVQ9U.js +1 -0
  205. package/web/dist/assets/architectureDiagram-2XIMDMQ5-BLmzX1wr.js +36 -0
  206. package/web/dist/assets/band-CquvqAHh.js +1 -0
  207. package/web/dist/assets/blockDiagram-WCTKOSBZ-B9pcqm3j.js +132 -0
  208. package/web/dist/assets/c4Diagram-IC4MRINW-Cytx1q3b.js +10 -0
  209. package/web/dist/assets/channel-BOVj73LR.js +1 -0
  210. package/web/dist/assets/channel-meta-CQD0Pei-.js +41 -0
  211. package/web/dist/assets/chunk-4BX2VUAB-0ToDr6RE.js +1 -0
  212. package/web/dist/assets/chunk-55IACEB6-DQDjnXfS.js +1 -0
  213. package/web/dist/assets/chunk-FMBD7UC4-Di8ABm6c.js +15 -0
  214. package/web/dist/assets/chunk-JSJVCQXG-BZQN6rnX.js +1 -0
  215. package/web/dist/assets/chunk-KX2RTZJC-zBbcpaN_.js +1 -0
  216. package/web/dist/assets/chunk-NQ4KR5QH-BCrLoU88.js +220 -0
  217. package/web/dist/assets/chunk-QZHKN3VN-Bqk8juan.js +1 -0
  218. package/web/dist/assets/chunk-WL4C6EOR-D2YX-MHY.js +189 -0
  219. package/web/dist/assets/classDiagram-VBA2DB6C-DUUoMyaK.js +1 -0
  220. package/web/dist/assets/classDiagram-v2-RAHNMMFH-DUUoMyaK.js +1 -0
  221. package/web/dist/assets/clone-BmaCesfa.js +1 -0
  222. package/web/dist/assets/cose-bilkent-S5V4N54A-CTsv6qQA.js +1 -0
  223. package/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  224. package/web/dist/assets/dagre-KLK3FWXG-Ci4Jh9nu.js +4 -0
  225. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  226. package/web/dist/assets/diagram-E7M64L7V-BFRnfTI2.js +24 -0
  227. package/web/dist/assets/diagram-IFDJBPK2-B7Zhnp0b.js +43 -0
  228. package/web/dist/assets/diagram-P4PSJMXO-BVyP7nwq.js +24 -0
  229. package/web/dist/assets/erDiagram-INFDFZHY-NorKdTOF.js +70 -0
  230. package/web/dist/assets/error-CGD5mp5f.js +1 -0
  231. package/web/dist/assets/flowDiagram-PKNHOUZH-Ch97nABF.js +162 -0
  232. package/web/dist/assets/ganttDiagram-A5KZAMGK-BQ2pLWsy.js +292 -0
  233. package/web/dist/assets/gitGraphDiagram-K3NZZRJ6-bcvnBsD2.js +65 -0
  234. package/web/dist/assets/graph-CeAEckur.js +1 -0
  235. package/web/dist/assets/index-CPnL1_qC.js +768 -0
  236. package/web/dist/assets/index-DVevCbcO.css +10 -0
  237. package/web/dist/assets/infoDiagram-LFFYTUFH-CcsrFdj-.js +2 -0
  238. package/web/dist/assets/init-Dmth1JHB.js +1 -0
  239. package/web/dist/assets/ishikawaDiagram-PHBUUO56-1upyMfHN.js +70 -0
  240. package/web/dist/assets/journeyDiagram-4ABVD52K-CKUi-V0c.js +139 -0
  241. package/web/dist/assets/kanban-definition-K7BYSVSG-DOnQwXfL.js +89 -0
  242. package/web/dist/assets/layout-BmMMqTnJ.js +1 -0
  243. package/web/dist/assets/linear-DiaJloY5.js +1 -0
  244. package/web/dist/assets/mermaid.core-BWLV1B2v.js +254 -0
  245. package/web/dist/assets/mindmap-definition-YRQLILUH-BeAKHVWP.js +68 -0
  246. package/web/dist/assets/ordinal-DILIJJjt.js +1 -0
  247. package/web/dist/assets/pieDiagram-SKSYHLDU-DfiMSfWo.js +30 -0
  248. package/web/dist/assets/quadrantDiagram-337W2JSQ-wZxZOJxd.js +7 -0
  249. package/web/dist/assets/requirementDiagram-Z7DCOOCP-BK4HHm17.js +73 -0
  250. package/web/dist/assets/sankeyDiagram-WA2Y5GQK-BX6t2avX.js +10 -0
  251. package/web/dist/assets/sequenceDiagram-2WXFIKYE-BPQlkbAa.js +145 -0
  252. package/web/dist/assets/sheet-rI0FfB1g.js +6 -0
  253. package/web/dist/assets/sliders-horizontal-CuijWFNK.js +6 -0
  254. package/web/dist/assets/sparkles-BsMYXJoT.js +11 -0
  255. package/web/dist/assets/square-0CqMX1Q3.js +11 -0
  256. package/web/dist/assets/stateDiagram-RAJIS63D-DxkV0Vwd.js +1 -0
  257. package/web/dist/assets/stateDiagram-v2-FVOUBMTO-qLYoiOPe.js +1 -0
  258. package/web/dist/assets/step-D51IIHGA.js +1 -0
  259. package/web/dist/assets/tasks-D8JjBTwx.js +1 -0
  260. package/web/dist/assets/time-O8zIGux3.js +1 -0
  261. package/web/dist/assets/timeline-definition-YZTLITO2-kNp1DyFc.js +61 -0
  262. package/web/dist/assets/treemap-KZPCXAKY-CkrClVhk.js +162 -0
  263. package/web/dist/assets/utils-KGAn0XTg.js +11 -0
  264. package/web/dist/assets/vennDiagram-LZ73GAT5-CgdzEZz4.js +34 -0
  265. package/web/dist/assets/xychartDiagram-JWTSCODW-DfYGPfNB.js +7 -0
  266. package/web/dist/assets/zap-_hKJYy7J.js +6 -0
  267. package/web/dist/favicon.svg +332 -0
  268. package/web/dist/fonts/AlibabaPuHuiTi-3-55-Regular.woff2 +0 -0
  269. package/web/dist/fonts/AlibabaPuHuiTi-3-65-Medium.woff2 +0 -0
  270. package/web/dist/fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2 +0 -0
  271. package/web/dist/fonts/DMSans-latin-ext.woff2 +0 -0
  272. package/web/dist/fonts/DMSans-latin.woff2 +0 -0
  273. package/web/dist/icons/README.md +20 -0
  274. package/web/dist/icons/apple-touch-icon-180.png +0 -0
  275. package/web/dist/icons/icon-128.png +0 -0
  276. package/web/dist/icons/icon-144.png +0 -0
  277. package/web/dist/icons/icon-152.png +0 -0
  278. package/web/dist/icons/icon-192.png +0 -0
  279. package/web/dist/icons/icon-192.svg +332 -0
  280. package/web/dist/icons/icon-384.png +0 -0
  281. package/web/dist/icons/icon-48.png +0 -0
  282. package/web/dist/icons/icon-512-maskable.png +0 -0
  283. package/web/dist/icons/icon-512.png +0 -0
  284. package/web/dist/icons/icon-512.svg +332 -0
  285. package/web/dist/icons/icon-72.png +0 -0
  286. package/web/dist/icons/icon-96.png +0 -0
  287. package/web/dist/icons/loading-logo.svg +332 -0
  288. package/web/dist/icons/logo-1024.png +0 -0
  289. package/web/dist/icons/logo-icon.svg +332 -0
  290. package/web/dist/icons/logo-text.svg +332 -0
  291. package/web/dist/index.html +30 -0
  292. package/web/dist/manifest.webmanifest +1 -0
  293. package/web/dist/registerSW.js +1 -0
  294. package/web/dist/sw.js +1 -0
  295. package/web/dist/workbox-08d6266a.js +1 -0
package/dist/wechat.js ADDED
@@ -0,0 +1,589 @@
1
+ /**
2
+ * WeChat iLink Bot API Connection Factory
3
+ *
4
+ * Implements WeChat Bot connection using iLink Bot API protocol:
5
+ * - Long-polling message reception (getupdates)
6
+ * - Message sending with context_token (sendmessage)
7
+ * - Typing indicator (getconfig + sendtyping)
8
+ * - CDN image download + AES decryption
9
+ * - Message deduplication (LRU 1000 / 30min TTL)
10
+ *
11
+ * Base URL: https://ilinkai.weixin.qq.com
12
+ * CDN URL: https://novac2c.cdn.weixin.qq.com/c2c
13
+ */
14
+ import crypto from 'crypto';
15
+ import { storeChatMetadata, storeMessageDirect, updateChatName } from './db.js';
16
+ import { notifyNewImMessage } from './message-notifier.js';
17
+ import { broadcastNewMessage } from './web.js';
18
+ import { logger } from './logger.js';
19
+ import { saveDownloadedFile, MAX_FILE_SIZE } from './im-downloader.js';
20
+ import { detectImageMimeType } from './image-detector.js';
21
+ import { downloadAndDecryptMedia } from './wechat-crypto.js';
22
+ import { markdownToPlainText, splitTextChunks } from './im-utils.js';
23
+ // ─── Constants ──────────────────────────────────────────────────
24
+ const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
25
+ const DEFAULT_CDN_BASE_URL = 'https://novac2c.cdn.weixin.qq.com/c2c';
26
+ const MSG_DEDUP_MAX = 1000;
27
+ const MSG_DEDUP_TTL = 30 * 60 * 1000; // 30min
28
+ const MSG_SPLIT_LIMIT = 2000; // WeChat has stricter text limits than other channels
29
+ const LONGPOLL_EXTRA_TIMEOUT_MS = 5000;
30
+ const DEFAULT_LONGPOLL_TIMEOUT_MS = 35000;
31
+ const RECONNECT_MIN_DELAY_MS = 3000;
32
+ const RECONNECT_MAX_DELAY_MS = 60000;
33
+ const IMAGE_MAX_BASE64_SIZE = 5 * 1024 * 1024; // 5 MB for inline base64
34
+ const CHANNEL_VERSION = '0.1.0';
35
+ // iLink message types
36
+ // const MESSAGE_TYPE_USER = 1;
37
+ const MESSAGE_TYPE_BOT = 2;
38
+ // iLink message item types
39
+ const MESSAGE_ITEM_TYPE_TEXT = 1;
40
+ const MESSAGE_ITEM_TYPE_IMAGE = 2;
41
+ // const MESSAGE_ITEM_TYPE_VOICE = 3;
42
+ const MESSAGE_ITEM_TYPE_FILE = 4;
43
+ // const MESSAGE_ITEM_TYPE_VIDEO = 5;
44
+ // iLink message state
45
+ // const MESSAGE_STATE_NEW = 0;
46
+ // const MESSAGE_STATE_GENERATING = 1;
47
+ const MESSAGE_STATE_FINISH = 2;
48
+ // errcode for session expired
49
+ const ERRCODE_SESSION_EXPIRED = -14;
50
+ // ─── Helpers ────────────────────────────────────────────────────
51
+ /**
52
+ * Generate random X-WECHAT-UIN header value.
53
+ * A random uint32 converted to string, then base64-encoded.
54
+ */
55
+ function randomWechatUin() {
56
+ const uint32 = crypto.randomBytes(4).readUInt32BE(0);
57
+ return Buffer.from(String(uint32), 'utf-8').toString('base64');
58
+ }
59
+ /**
60
+ * Extract text content from message item_list.
61
+ * Includes voice-to-text transcription and fallback labels for non-text items.
62
+ */
63
+ function extractTextContent(items) {
64
+ const parts = [];
65
+ for (const item of items) {
66
+ if (item.type === MESSAGE_ITEM_TYPE_TEXT && item.text_item?.text) {
67
+ parts.push(item.text_item.text);
68
+ }
69
+ else if (item.type === MESSAGE_ITEM_TYPE_IMAGE) {
70
+ // Image placeholder — actual image is handled separately via CDN download
71
+ // Only add placeholder if no CDN media to download
72
+ if (!item.image_item?.media?.encrypt_query_param) {
73
+ parts.push('(image)');
74
+ }
75
+ }
76
+ else if (item.type === 3 /* VOICE */) {
77
+ // Voice: prefer speech-to-text transcription
78
+ if (item.voice_item?.text) {
79
+ parts.push(item.voice_item.text);
80
+ }
81
+ else {
82
+ parts.push('(voice)');
83
+ }
84
+ }
85
+ else if (item.type === MESSAGE_ITEM_TYPE_FILE) {
86
+ parts.push(`(file: ${item.file_item?.file_name ?? 'unknown'})`);
87
+ }
88
+ else if (item.type === 5 /* VIDEO */) {
89
+ parts.push('(video)');
90
+ }
91
+ }
92
+ return parts.join('\n').trim();
93
+ }
94
+ /**
95
+ * Generate a unique dedup key from a WeixinMessage.
96
+ */
97
+ function dedupKey(msg) {
98
+ if (msg.message_id !== undefined)
99
+ return `mid:${msg.message_id}`;
100
+ if (msg.seq !== undefined)
101
+ return `seq:${msg.seq}`;
102
+ // Fallback: combination of sender + timestamp + client_id
103
+ return `fallback:${msg.from_user_id}:${msg.create_time_ms}:${msg.client_id}`;
104
+ }
105
+ // ─── Factory Function ───────────────────────────────────────────
106
+ export function createWeChatConnection(config) {
107
+ const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
108
+ const cdnBaseUrl = config.cdnBaseUrl || DEFAULT_CDN_BASE_URL;
109
+ // Generate UIN once per connection instance (no need to regenerate per request)
110
+ const wechatUin = randomWechatUin();
111
+ // Polling state
112
+ let currentGetUpdatesBuf = config.getUpdatesBuf || '';
113
+ let longpollTimeoutMs = DEFAULT_LONGPOLL_TIMEOUT_MS;
114
+ let stopping = false;
115
+ let connected = false;
116
+ let cancelSleep = null;
117
+ // context_token cache: from_user_id -> latest context_token
118
+ const contextTokenCache = new Map();
119
+ // Known JIDs — skip redundant storeChatMetadata/onNewChat for repeat messages
120
+ const knownJids = new Set();
121
+ // Message deduplication: key -> timestamp
122
+ const msgCache = new Map();
123
+ // ─── Deduplication ────────────────────────────────────────
124
+ function isDuplicate(key) {
125
+ const now = Date.now();
126
+ // Evict expired entries — Map preserves insertion order, so oldest entries
127
+ // come first. Stop at the first non-expired entry for O(expired) instead of O(n).
128
+ for (const [id, ts] of msgCache.entries()) {
129
+ if (now - ts > MSG_DEDUP_TTL) {
130
+ msgCache.delete(id);
131
+ }
132
+ else {
133
+ break;
134
+ }
135
+ }
136
+ // Evict oldest if at capacity
137
+ if (msgCache.size >= MSG_DEDUP_MAX) {
138
+ const firstKey = msgCache.keys().next().value;
139
+ if (firstKey)
140
+ msgCache.delete(firstKey);
141
+ }
142
+ return msgCache.has(key);
143
+ }
144
+ function markSeen(key) {
145
+ // delete + set to refresh insertion order (move to end)
146
+ msgCache.delete(key);
147
+ msgCache.set(key, Date.now());
148
+ }
149
+ // ─── HTTP Helpers ─────────────────────────────────────────
150
+ function buildHeaders() {
151
+ return {
152
+ 'Content-Type': 'application/json',
153
+ AuthorizationType: 'ilink_bot_token',
154
+ Authorization: `Bearer ${config.botToken}`,
155
+ 'X-WECHAT-UIN': wechatUin,
156
+ };
157
+ }
158
+ function baseInfo() {
159
+ return { channel_version: CHANNEL_VERSION };
160
+ }
161
+ /**
162
+ * Make an HTTPS POST request to the iLink API using fetch.
163
+ */
164
+ async function apiPost(endpoint, body, timeoutMs) {
165
+ const bodyStr = JSON.stringify(body);
166
+ const url = new URL(endpoint, baseUrl);
167
+ const headers = buildHeaders();
168
+ const controller = new AbortController();
169
+ const timer = timeoutMs
170
+ ? setTimeout(() => controller.abort(), timeoutMs)
171
+ : undefined;
172
+ try {
173
+ const res = await fetch(url.toString(), {
174
+ method: 'POST',
175
+ headers: {
176
+ ...headers,
177
+ 'Content-Length': String(Buffer.byteLength(bodyStr, 'utf-8')),
178
+ },
179
+ body: bodyStr,
180
+ signal: controller.signal,
181
+ });
182
+ const text = await res.text();
183
+ try {
184
+ return JSON.parse(text);
185
+ }
186
+ catch {
187
+ throw new Error(`WeChat API ${endpoint} invalid JSON: ${text.slice(0, 200)}`);
188
+ }
189
+ }
190
+ catch (err) {
191
+ if (err instanceof Error && err.name === 'AbortError') {
192
+ throw new Error(`WeChat API ${endpoint} timed out`);
193
+ }
194
+ throw err;
195
+ }
196
+ finally {
197
+ if (timer)
198
+ clearTimeout(timer);
199
+ }
200
+ }
201
+ // ─── API Methods ──────────────────────────────────────────
202
+ async function getUpdates() {
203
+ const httpTimeout = longpollTimeoutMs + LONGPOLL_EXTRA_TIMEOUT_MS;
204
+ return apiPost('ilink/bot/getupdates', {
205
+ get_updates_buf: currentGetUpdatesBuf,
206
+ base_info: baseInfo(),
207
+ }, httpTimeout);
208
+ }
209
+ async function sendMessageApi(toUserId, contextToken, text) {
210
+ const clientId = String(crypto.randomBytes(4).readUInt32BE(0));
211
+ const resp = await apiPost('ilink/bot/sendmessage', {
212
+ msg: {
213
+ to_user_id: toUserId,
214
+ context_token: contextToken,
215
+ item_list: [
216
+ {
217
+ type: MESSAGE_ITEM_TYPE_TEXT,
218
+ text_item: { text },
219
+ },
220
+ ],
221
+ message_type: MESSAGE_TYPE_BOT,
222
+ message_state: MESSAGE_STATE_FINISH,
223
+ client_id: clientId,
224
+ },
225
+ base_info: baseInfo(),
226
+ });
227
+ if (resp.ret !== undefined && resp.ret !== 0) {
228
+ throw new Error(`sendMessage failed: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ''}`);
229
+ }
230
+ }
231
+ async function getTypingTicket(ilinkUserId, contextToken) {
232
+ try {
233
+ const res = await apiPost('ilink/bot/getconfig', {
234
+ ilink_user_id: ilinkUserId,
235
+ context_token: contextToken,
236
+ base_info: baseInfo(),
237
+ });
238
+ return res.typing_ticket || null;
239
+ }
240
+ catch (err) {
241
+ logger.debug({ err }, 'WeChat getconfig failed');
242
+ return null;
243
+ }
244
+ }
245
+ async function sendTypingApi(ilinkUserId, typingTicket, status) {
246
+ try {
247
+ await apiPost('ilink/bot/sendtyping', {
248
+ ilink_user_id: ilinkUserId,
249
+ typing_ticket: typingTicket,
250
+ status,
251
+ base_info: baseInfo(),
252
+ });
253
+ }
254
+ catch (err) {
255
+ logger.debug({ err, status }, 'WeChat sendtyping failed');
256
+ }
257
+ }
258
+ // ─── Image Handling ───────────────────────────────────────
259
+ async function processImageItem(item, msgIdentifier, groupFolder) {
260
+ const imageItem = item.image_item;
261
+ if (!imageItem)
262
+ return {};
263
+ const media = imageItem.media;
264
+ if (!media?.encrypt_query_param || !media?.aes_key) {
265
+ logger.debug('WeChat image missing media or aes_key, skipping');
266
+ return {};
267
+ }
268
+ try {
269
+ const buffer = await downloadAndDecryptMedia(media.encrypt_query_param, media.aes_key, cdnBaseUrl);
270
+ if (!buffer || buffer.length === 0) {
271
+ logger.warn('WeChat image download returned empty buffer');
272
+ return {};
273
+ }
274
+ if (buffer.length > MAX_FILE_SIZE) {
275
+ logger.warn({ size: buffer.length }, 'WeChat image exceeds max file size, skipping');
276
+ return {};
277
+ }
278
+ const mimeType = detectImageMimeType(buffer);
279
+ const extMap = {
280
+ 'image/jpeg': '.jpg',
281
+ 'image/png': '.png',
282
+ 'image/gif': '.gif',
283
+ 'image/webp': '.webp',
284
+ };
285
+ const ext = extMap[mimeType] ?? '.jpg';
286
+ const fileName = `wechat_img_${msgIdentifier}${ext}`;
287
+ // Save to workspace
288
+ let textPrefix;
289
+ if (groupFolder) {
290
+ try {
291
+ const relPath = await saveDownloadedFile(groupFolder, 'wechat', fileName, buffer);
292
+ if (relPath)
293
+ textPrefix = `[图片: ${relPath}]`;
294
+ }
295
+ catch (err) {
296
+ logger.warn({ err }, 'Failed to save WeChat image to disk');
297
+ }
298
+ }
299
+ // Inline base64 for small images
300
+ let attachmentEntry;
301
+ if (buffer.length <= IMAGE_MAX_BASE64_SIZE) {
302
+ attachmentEntry = {
303
+ type: 'image',
304
+ data: buffer.toString('base64'),
305
+ mimeType,
306
+ };
307
+ }
308
+ return { attachmentEntry, textPrefix };
309
+ }
310
+ catch (err) {
311
+ logger.warn({ err }, 'WeChat image download/decrypt failed, skipping');
312
+ return {};
313
+ }
314
+ }
315
+ // ─── Message Processing ───────────────────────────────────
316
+ async function processMessage(msg, opts) {
317
+ try {
318
+ // Skip bot's own messages
319
+ if (msg.message_type === MESSAGE_TYPE_BOT)
320
+ return;
321
+ const fromUserId = msg.from_user_id;
322
+ if (!fromUserId)
323
+ return;
324
+ // Dedup
325
+ const key = dedupKey(msg);
326
+ if (isDuplicate(key))
327
+ return;
328
+ markSeen(key);
329
+ // Skip stale messages — if no timestamp available, skip as well (can't verify freshness)
330
+ if (opts.ignoreMessagesBefore) {
331
+ if (!msg.create_time_ms ||
332
+ msg.create_time_ms < opts.ignoreMessagesBefore)
333
+ return;
334
+ }
335
+ // Cache context_token for replies
336
+ if (msg.context_token) {
337
+ contextTokenCache.set(fromUserId, msg.context_token);
338
+ }
339
+ const jid = `wechat:${fromUserId}`;
340
+ const senderName = fromUserId.split('@')[0] || 'WeChat用户';
341
+ const chatName = senderName;
342
+ // Extract text content
343
+ let content = msg.item_list ? extractTextContent(msg.item_list) : '';
344
+ // ── Auto-register chat (WeChat is 1:1 bound, no pairing needed) ──
345
+ const nowIso = new Date().toISOString();
346
+ if (!knownJids.has(jid)) {
347
+ knownJids.add(jid);
348
+ storeChatMetadata(jid, nowIso);
349
+ updateChatName(jid, chatName);
350
+ opts.onNewChat(jid, chatName);
351
+ }
352
+ // Handle slash commands
353
+ const slashMatch = content.match(/^\/(\S+)(?:\s+(.*))?$/i);
354
+ if (slashMatch && opts.onCommand) {
355
+ const cmdBody = (slashMatch[1] + (slashMatch[2] ? ' ' + slashMatch[2] : '')).trim();
356
+ try {
357
+ const reply = await opts.onCommand(jid, cmdBody);
358
+ if (reply) {
359
+ const ct = contextTokenCache.get(fromUserId);
360
+ if (ct) {
361
+ await sendMessageApi(fromUserId, ct, markdownToPlainText(reply));
362
+ }
363
+ return;
364
+ }
365
+ }
366
+ catch (err) {
367
+ logger.error({ jid, err }, 'WeChat slash command failed');
368
+ const ct = contextTokenCache.get(fromUserId);
369
+ if (ct) {
370
+ await sendMessageApi(fromUserId, ct, '命令执行失败,请稍后重试');
371
+ }
372
+ return;
373
+ }
374
+ }
375
+ // Handle image attachments
376
+ let attachmentsJson;
377
+ const groupFolder = opts.resolveGroupFolder?.(jid);
378
+ if (msg.item_list) {
379
+ const imageAttachments = [];
380
+ const textPrefixes = [];
381
+ // Download images in parallel (independent CDN requests)
382
+ const msgId = msg.message_id !== undefined
383
+ ? String(msg.message_id)
384
+ : String(msg.seq ?? Date.now());
385
+ const imageItems = msg.item_list.filter((item) => item.type === MESSAGE_ITEM_TYPE_IMAGE);
386
+ if (imageItems.length > 0) {
387
+ const results = await Promise.allSettled(imageItems.map((item) => processImageItem(item, msgId.slice(-8), groupFolder)));
388
+ for (const r of results) {
389
+ if (r.status === 'fulfilled') {
390
+ if (r.value.attachmentEntry) {
391
+ imageAttachments.push(r.value.attachmentEntry);
392
+ }
393
+ if (r.value.textPrefix) {
394
+ textPrefixes.push(r.value.textPrefix);
395
+ }
396
+ }
397
+ }
398
+ }
399
+ // Handle file items — note the path in content
400
+ for (const item of msg.item_list) {
401
+ if (item.type === MESSAGE_ITEM_TYPE_FILE && item.file_item) {
402
+ const fileName = item.file_item.file_name || 'unknown_file';
403
+ content = `[文件: ${fileName}]\n${content}`.trim();
404
+ }
405
+ }
406
+ if (imageAttachments.length > 0) {
407
+ attachmentsJson = JSON.stringify(imageAttachments);
408
+ if (textPrefixes.length > 0) {
409
+ content = `${textPrefixes.join('\n')}\n${content}`.trim();
410
+ }
411
+ }
412
+ if (!content && imageAttachments.length > 0) {
413
+ content = '[图片]';
414
+ }
415
+ }
416
+ if (!content)
417
+ return; // No usable content
418
+ // Route and store message
419
+ const agentRouting = opts.resolveEffectiveChatJid?.(jid);
420
+ const targetJid = agentRouting?.effectiveJid ?? jid;
421
+ const id = crypto.randomUUID();
422
+ const timestamp = msg.create_time_ms
423
+ ? new Date(msg.create_time_ms).toISOString()
424
+ : nowIso;
425
+ const senderId = `wechat:${fromUserId}`;
426
+ if (targetJid !== jid)
427
+ storeChatMetadata(targetJid, timestamp);
428
+ storeMessageDirect(id, targetJid, senderId, senderName, content, timestamp, false, {
429
+ attachments: attachmentsJson,
430
+ sourceJid: jid,
431
+ });
432
+ broadcastNewMessage(targetJid, {
433
+ id,
434
+ chat_jid: targetJid,
435
+ source_jid: jid,
436
+ sender: senderId,
437
+ sender_name: senderName,
438
+ content,
439
+ timestamp,
440
+ attachments: attachmentsJson,
441
+ is_from_me: false,
442
+ }, agentRouting?.agentId ?? undefined);
443
+ notifyNewImMessage();
444
+ if (agentRouting?.agentId) {
445
+ opts.onAgentMessage?.(jid, agentRouting.agentId);
446
+ logger.info({ jid, effectiveJid: targetJid, agentId: agentRouting.agentId }, 'WeChat message routed to agent');
447
+ }
448
+ else {
449
+ logger.info({ jid, sender: senderName, msgId: msg.message_id ?? msg.seq }, 'WeChat message stored');
450
+ }
451
+ }
452
+ catch (err) {
453
+ logger.error({ err, msgId: msg.message_id }, 'Error handling WeChat message');
454
+ }
455
+ }
456
+ // ─── Long-Polling Loop ────────────────────────────────────
457
+ async function pollLoop(opts) {
458
+ let reconnectDelay = RECONNECT_MIN_DELAY_MS;
459
+ while (!stopping) {
460
+ try {
461
+ const response = await getUpdates();
462
+ // Update longpoll timeout from server
463
+ if (response.longpolling_timeout_ms) {
464
+ longpollTimeoutMs = response.longpolling_timeout_ms;
465
+ }
466
+ // Check for session expiry
467
+ if (response.ret === ERRCODE_SESSION_EXPIRED) {
468
+ logger.warn('WeChat session expired (errcode -14), stopping poll loop');
469
+ connected = false;
470
+ break;
471
+ }
472
+ // ret is absent (undefined) when the request succeeds — treat as 0
473
+ if (response.ret !== undefined && response.ret !== 0) {
474
+ logger.warn(`WeChat getUpdates error: ret=${response.ret}, response=${JSON.stringify(response).slice(0, 500)}`);
475
+ // Back off on errors
476
+ await sleep(reconnectDelay);
477
+ reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_DELAY_MS);
478
+ continue;
479
+ }
480
+ // Reset backoff on success
481
+ reconnectDelay = RECONNECT_MIN_DELAY_MS;
482
+ // Update cursor
483
+ if (response.get_updates_buf) {
484
+ currentGetUpdatesBuf = response.get_updates_buf;
485
+ }
486
+ // Process messages
487
+ if (response.msgs && response.msgs.length > 0) {
488
+ for (const msg of response.msgs) {
489
+ await processMessage(msg, opts);
490
+ }
491
+ }
492
+ }
493
+ catch (err) {
494
+ if (stopping)
495
+ break;
496
+ // Long-poll timeout is expected when no new messages arrive — just retry immediately.
497
+ const isTimeout = err instanceof Error && err.message.includes('timed out');
498
+ if (isTimeout) {
499
+ logger.debug({ err }, 'WeChat poll timeout (normal, retrying)');
500
+ continue;
501
+ }
502
+ logger.error({ err }, 'WeChat poll loop error');
503
+ await sleep(reconnectDelay);
504
+ reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX_DELAY_MS);
505
+ }
506
+ }
507
+ }
508
+ function sleep(ms) {
509
+ return new Promise((resolve) => {
510
+ const timer = setTimeout(resolve, ms);
511
+ cancelSleep = () => {
512
+ clearTimeout(timer);
513
+ resolve();
514
+ };
515
+ });
516
+ }
517
+ // ─── Connection Interface ─────────────────────────────────
518
+ const connection = {
519
+ async connect(opts) {
520
+ if (!config.botToken || !config.ilinkBotId) {
521
+ logger.info('WeChat botToken/ilinkBotId not configured, skipping');
522
+ return;
523
+ }
524
+ stopping = false;
525
+ connected = true;
526
+ msgCache.clear();
527
+ contextTokenCache.clear();
528
+ knownJids.clear();
529
+ logger.info({ baseUrl, ilinkBotId: config.ilinkBotId }, 'WeChat iLink bot connecting');
530
+ // Fire onReady immediately since there's no handshake
531
+ opts.onReady?.();
532
+ // Start poll loop in background (non-blocking)
533
+ pollLoop(opts).catch((err) => {
534
+ logger.error({ err }, 'WeChat poll loop exited with error');
535
+ connected = false;
536
+ });
537
+ },
538
+ async disconnect() {
539
+ stopping = true;
540
+ connected = false;
541
+ // Abort any pending sleep
542
+ cancelSleep?.();
543
+ cancelSleep = null;
544
+ msgCache.clear();
545
+ contextTokenCache.clear();
546
+ knownJids.clear();
547
+ logger.info('WeChat iLink bot disconnected');
548
+ },
549
+ async sendMessage(chatId, text, _localImagePaths) {
550
+ // chatId is the raw WeChat user ID (prefix already stripped by IM manager)
551
+ const userId = chatId;
552
+ const contextToken = contextTokenCache.get(userId);
553
+ if (!contextToken) {
554
+ logger.warn({ chatId }, 'No context_token available for WeChat user, cannot send message');
555
+ return;
556
+ }
557
+ try {
558
+ const plainText = markdownToPlainText(text);
559
+ const chunks = splitTextChunks(plainText, MSG_SPLIT_LIMIT);
560
+ for (const chunk of chunks) {
561
+ await sendMessageApi(userId, contextToken, chunk);
562
+ }
563
+ logger.info({ chatId }, 'WeChat message sent');
564
+ }
565
+ catch (err) {
566
+ logger.error({ err, chatId }, 'Failed to send WeChat message');
567
+ throw err;
568
+ }
569
+ },
570
+ async sendTyping(chatId, isTyping) {
571
+ // chatId is the raw WeChat user ID (prefix already stripped by IM manager)
572
+ const userId = chatId;
573
+ const contextToken = contextTokenCache.get(userId);
574
+ if (!contextToken)
575
+ return;
576
+ const ticket = await getTypingTicket(userId, contextToken);
577
+ if (!ticket)
578
+ return;
579
+ await sendTypingApi(userId, ticket, isTyping ? 1 : 2);
580
+ },
581
+ isConnected() {
582
+ return connected && !stopping;
583
+ },
584
+ getUpdatesBuf() {
585
+ return currentGetUpdatesBuf;
586
+ },
587
+ };
588
+ return connection;
589
+ }
@@ -0,0 +1,35 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DATA_DIR } from './config.js';
4
+ import { deleteSession, getJidsByFolder, listAgentsByJid } from './db.js';
5
+ export function clearSessionJsonlFiles(folder, agentId) {
6
+ const claudeDir = agentId
7
+ ? path.join(DATA_DIR, 'sessions', folder, 'agents', agentId, '.claude')
8
+ : path.join(DATA_DIR, 'sessions', folder, '.claude');
9
+ if (!fs.existsSync(claudeDir))
10
+ return;
11
+ const keep = new Set(['settings.json']);
12
+ const entries = fs.readdirSync(claudeDir);
13
+ for (const entry of entries) {
14
+ if (keep.has(entry))
15
+ continue;
16
+ const fullPath = path.join(claudeDir, entry);
17
+ fs.rmSync(fullPath, { recursive: true, force: true });
18
+ }
19
+ }
20
+ export async function resetWorkspaceRuntimeState(deps, jid, group) {
21
+ const siblingJids = getJidsByFolder(group.folder);
22
+ const agents = jid.startsWith('web:') ? listAgentsByJid(jid) : [];
23
+ const stopTargets = new Set(siblingJids);
24
+ for (const agent of agents) {
25
+ stopTargets.add(`${jid}#agent:${agent.id}`);
26
+ }
27
+ await Promise.all([...stopTargets].map((targetJid) => deps.queue.stopGroup(targetJid, { force: true })));
28
+ clearSessionJsonlFiles(group.folder);
29
+ deleteSession(group.folder);
30
+ delete deps.getSessions()[group.folder];
31
+ for (const agent of agents) {
32
+ clearSessionJsonlFiles(group.folder, agent.id);
33
+ deleteSession(group.folder, agent.id);
34
+ }
35
+ }