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
@@ -0,0 +1,533 @@
1
+ import { Hono } from 'hono';
2
+ import crypto from 'crypto';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { getWebDeps } from '../web-context.js';
6
+ import { authMiddleware } from '../middleware/auth.js';
7
+ import { canAccessGroup } from '../web-context.js';
8
+ import { getRegisteredGroup, getAllRegisteredGroups, listAgentsByJid, getAgent, deleteAgent, updateAgentStatus, createAgent, ensureChatExists, deleteMessagesForChatJid, deleteSession, getGroupsByTargetAgent, setRegisteredGroup, getJidsByFolder, updateAgentLastImJid, updateAgentInfo, updateChatName, } from '../db.js';
9
+ import { DATA_DIR } from '../config.js';
10
+ import { logger } from '../logger.js';
11
+ import { getChannelType, extractChatId } from '../im-channel.js';
12
+ import { ensureAgentDirectories } from '../utils.js';
13
+ const router = new Hono();
14
+ // GET /api/groups/:jid/agents — list all agents for a group
15
+ router.get('/:jid/agents', authMiddleware, async (c) => {
16
+ const jid = decodeURIComponent(c.req.param('jid'));
17
+ const user = c.get('user');
18
+ const group = getRegisteredGroup(jid);
19
+ if (!group) {
20
+ return c.json({ error: 'Group not found' }, 404);
21
+ }
22
+ if (!canAccessGroup(user, { ...group, jid })) {
23
+ return c.json({ error: 'Forbidden' }, 403);
24
+ }
25
+ const agents = listAgentsByJid(jid);
26
+ return c.json({
27
+ agents: agents.map((a) => {
28
+ const base = {
29
+ id: a.id,
30
+ name: a.name,
31
+ prompt: a.prompt,
32
+ status: a.status,
33
+ kind: a.kind,
34
+ created_at: a.created_at,
35
+ completed_at: a.completed_at,
36
+ result_summary: a.result_summary,
37
+ };
38
+ if (a.kind === 'conversation') {
39
+ const linked = getGroupsByTargetAgent(a.id);
40
+ return {
41
+ ...base,
42
+ linked_im_groups: linked.map((l) => ({
43
+ jid: l.jid,
44
+ name: l.group.name,
45
+ })),
46
+ };
47
+ }
48
+ return base;
49
+ }),
50
+ });
51
+ });
52
+ // POST /api/groups/:jid/agents — create a user conversation
53
+ router.post('/:jid/agents', authMiddleware, async (c) => {
54
+ const jid = decodeURIComponent(c.req.param('jid'));
55
+ const user = c.get('user');
56
+ const group = getRegisteredGroup(jid);
57
+ if (!group) {
58
+ return c.json({ error: 'Group not found' }, 404);
59
+ }
60
+ if (!canAccessGroup(user, { ...group, jid })) {
61
+ return c.json({ error: 'Forbidden' }, 403);
62
+ }
63
+ const body = await c.req.json().catch(() => ({}));
64
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
65
+ if (!name || name.length > 40) {
66
+ return c.json({ error: 'Name is required (max 40 chars)' }, 400);
67
+ }
68
+ const description = typeof body.description === 'string' ? body.description.trim() : '';
69
+ const agentId = crypto.randomUUID();
70
+ const now = new Date().toISOString();
71
+ const agent = {
72
+ id: agentId,
73
+ group_folder: group.folder,
74
+ chat_jid: jid,
75
+ name,
76
+ prompt: description,
77
+ status: 'idle',
78
+ kind: 'conversation',
79
+ created_by: user.id,
80
+ created_at: now,
81
+ completed_at: null,
82
+ result_summary: null,
83
+ last_im_jid: null,
84
+ spawned_from_jid: null,
85
+ };
86
+ createAgent(agent);
87
+ // Create IPC + session directories
88
+ ensureAgentDirectories(group.folder, agentId);
89
+ // Create virtual chat record for this agent's messages
90
+ const virtualChatJid = `${jid}#agent:${agentId}`;
91
+ ensureChatExists(virtualChatJid);
92
+ // Broadcast agent_status (idle) via WebSocket
93
+ // Import dynamically to avoid circular deps
94
+ const { broadcastAgentStatus } = await import('../web.js');
95
+ broadcastAgentStatus(jid, agentId, 'idle', name, description);
96
+ logger.info({ agentId, jid, name, userId: user.id }, 'User conversation created');
97
+ return c.json({
98
+ agent: {
99
+ id: agent.id,
100
+ name: agent.name,
101
+ prompt: agent.prompt,
102
+ status: agent.status,
103
+ kind: agent.kind,
104
+ created_at: agent.created_at,
105
+ },
106
+ });
107
+ });
108
+ // PATCH /api/groups/:jid/agents/:agentId — rename a conversation agent
109
+ router.patch('/:jid/agents/:agentId', authMiddleware, async (c) => {
110
+ const jid = decodeURIComponent(c.req.param('jid'));
111
+ const agentId = c.req.param('agentId');
112
+ const user = c.get('user');
113
+ const group = getRegisteredGroup(jid);
114
+ if (!group) {
115
+ return c.json({ error: 'Group not found' }, 404);
116
+ }
117
+ if (!canAccessGroup(user, { ...group, jid })) {
118
+ return c.json({ error: 'Forbidden' }, 403);
119
+ }
120
+ const agent = getAgent(agentId);
121
+ if (!agent || agent.chat_jid !== jid) {
122
+ return c.json({ error: 'Agent not found' }, 404);
123
+ }
124
+ const body = await c.req.json().catch(() => ({}));
125
+ const name = typeof body.name === 'string' ? body.name.trim() : '';
126
+ if (!name || name.length > 40) {
127
+ return c.json({ error: 'Name is required (max 40 chars)' }, 400);
128
+ }
129
+ // Update agent name in DB
130
+ updateAgentInfo(agentId, name, agent.prompt);
131
+ // Update virtual chat name
132
+ const virtualChatJid = `${jid}#agent:${agentId}`;
133
+ updateChatName(virtualChatJid, name);
134
+ // Broadcast update via WebSocket
135
+ const { broadcastAgentStatus } = await import('../web.js');
136
+ broadcastAgentStatus(jid, agentId, agent.status, name, agent.prompt);
137
+ logger.info({ agentId, jid, name, userId: user.id }, 'Agent renamed');
138
+ return c.json({ success: true });
139
+ });
140
+ // DELETE /api/groups/:jid/agents/:agentId — stop and delete an agent
141
+ router.delete('/:jid/agents/:agentId', authMiddleware, async (c) => {
142
+ const jid = decodeURIComponent(c.req.param('jid'));
143
+ const agentId = c.req.param('agentId');
144
+ const user = c.get('user');
145
+ const group = getRegisteredGroup(jid);
146
+ if (!group) {
147
+ return c.json({ error: 'Group not found' }, 404);
148
+ }
149
+ if (!canAccessGroup(user, { ...group, jid })) {
150
+ return c.json({ error: 'Forbidden' }, 403);
151
+ }
152
+ const agent = getAgent(agentId);
153
+ if (!agent || agent.chat_jid !== jid) {
154
+ return c.json({ error: 'Agent not found' }, 404);
155
+ }
156
+ // Block deletion if conversation agent has active IM bindings
157
+ if (agent.kind === 'conversation') {
158
+ const linkedImGroups = getGroupsByTargetAgent(agentId);
159
+ if (linkedImGroups.length > 0) {
160
+ return c.json({
161
+ error: 'Agent has active IM bindings. Unbind all IM groups before deleting.',
162
+ linked_im_groups: linkedImGroups.map(({ jid: imJid, group: imGroup }) => ({
163
+ jid: imJid,
164
+ name: imGroup.name,
165
+ })),
166
+ }, 409);
167
+ }
168
+ }
169
+ // If the agent is still running or idle, stop the process
170
+ if (agent.status === 'running' || agent.status === 'idle') {
171
+ updateAgentStatus(agentId, 'error', '用户手动停止');
172
+ // Stop running process via queue
173
+ const deps = getWebDeps();
174
+ if (deps) {
175
+ const virtualJid = `${jid}#agent:${agentId}`;
176
+ deps.queue.stopGroup(virtualJid);
177
+ }
178
+ }
179
+ // Clean up IPC/session directories
180
+ const agentIpcDir = path.join(DATA_DIR, 'ipc', group.folder, 'agents', agentId);
181
+ try {
182
+ fs.rmSync(agentIpcDir, { recursive: true, force: true });
183
+ }
184
+ catch {
185
+ /* ignore */
186
+ }
187
+ const agentSessionDir = path.join(DATA_DIR, 'sessions', group.folder, 'agents', agentId);
188
+ try {
189
+ fs.rmSync(agentSessionDir, { recursive: true, force: true });
190
+ }
191
+ catch {
192
+ /* ignore */
193
+ }
194
+ // Delete virtual chat messages for conversation agents
195
+ if (agent.kind === 'conversation') {
196
+ const virtualChatJid = `${jid}#agent:${agentId}`;
197
+ deleteMessagesForChatJid(virtualChatJid);
198
+ // Note: IM bindings are checked above and block deletion if present.
199
+ // No auto-clear here — user must unbind explicitly before deleting.
200
+ }
201
+ // Delete session records
202
+ deleteSession(group.folder, agentId);
203
+ deleteAgent(agentId);
204
+ // Broadcast removal
205
+ const { broadcastAgentStatus } = await import('../web.js');
206
+ broadcastAgentStatus(jid, agentId, 'error', agent.name, agent.prompt, '__removed__');
207
+ logger.info({ agentId, jid, userId: user.id }, 'Agent deleted by user');
208
+ return c.json({ success: true });
209
+ });
210
+ // Helper: check if a Telegram JID is a private/P2P chat
211
+ function isTelegramPrivateChat(jid) {
212
+ if (!jid.startsWith('telegram:'))
213
+ return false;
214
+ const id = jid.slice('telegram:'.length);
215
+ return !id.startsWith('-');
216
+ }
217
+ // GET /api/groups/:jid/im-groups — list available IM group chats for this folder
218
+ router.get('/:jid/im-groups', authMiddleware, async (c) => {
219
+ const jid = decodeURIComponent(c.req.param('jid'));
220
+ const user = c.get('user');
221
+ const group = getRegisteredGroup(jid);
222
+ if (!group) {
223
+ return c.json({ error: 'Group not found' }, 404);
224
+ }
225
+ if (!canAccessGroup(user, { ...group, jid })) {
226
+ return c.json({ error: 'Forbidden' }, 403);
227
+ }
228
+ // Find all IM groups this user can access (across all folders).
229
+ const allGroups = getAllRegisteredGroups();
230
+ const imJids = Object.keys(allGroups)
231
+ .filter((j) => {
232
+ if (j.startsWith('web:'))
233
+ return false;
234
+ return canAccessGroup(user, { ...allGroups[j], jid: j });
235
+ })
236
+ .filter((j) => !isTelegramPrivateChat(j));
237
+ const candidates = [];
238
+ for (const j of imJids) {
239
+ const g = allGroups[j];
240
+ // Resolve bound target name for display
241
+ let boundTargetName = null;
242
+ let boundWorkspaceName = null;
243
+ if (g.target_agent_id) {
244
+ const boundAgent = getAgent(g.target_agent_id);
245
+ if (boundAgent) {
246
+ boundTargetName = boundAgent.name;
247
+ const ownerGroup = getRegisteredGroup(boundAgent.chat_jid);
248
+ if (ownerGroup)
249
+ boundWorkspaceName = ownerGroup.name;
250
+ }
251
+ }
252
+ else if (g.target_main_jid) {
253
+ let boundGroup = getRegisteredGroup(g.target_main_jid);
254
+ // Legacy fallback: old bindings stored web:${folder} instead of actual JID
255
+ if (!boundGroup && g.target_main_jid.startsWith('web:')) {
256
+ const folder = g.target_main_jid.slice(4);
257
+ const jids = getJidsByFolder(folder);
258
+ for (const fj of jids) {
259
+ if (fj.startsWith('web:')) {
260
+ boundGroup = getRegisteredGroup(fj);
261
+ if (boundGroup)
262
+ break;
263
+ }
264
+ }
265
+ }
266
+ if (boundGroup)
267
+ boundTargetName = boundGroup.name;
268
+ }
269
+ candidates.push({
270
+ jid: j,
271
+ name: g.name,
272
+ bound_agent_id: g.target_agent_id ?? null,
273
+ bound_main_jid: g.target_main_jid ?? null,
274
+ reply_policy: g.reply_policy === 'mirror' ? 'mirror' : 'source_only',
275
+ bound_target_name: boundTargetName,
276
+ bound_workspace_name: boundWorkspaceName,
277
+ channel_type: getChannelType(j) ?? 'unknown',
278
+ activation_mode: g.activation_mode,
279
+ });
280
+ }
281
+ // Enrich Feishu groups with avatar, member count, and chat_mode
282
+ const deps = getWebDeps();
283
+ if (deps?.getFeishuChatInfo) {
284
+ const feishuCandidates = candidates.filter((g) => g.channel_type === 'feishu');
285
+ const chatInfoPromises = feishuCandidates.map(async (g) => {
286
+ const chatId = extractChatId(g.jid);
287
+ const info = await deps.getFeishuChatInfo(user.id, chatId);
288
+ if (info) {
289
+ g.avatar = info.avatar;
290
+ g.chat_mode = info.chat_mode;
291
+ if (info.user_count != null) {
292
+ const count = parseInt(info.user_count, 10);
293
+ if (!isNaN(count))
294
+ g.member_count = count;
295
+ }
296
+ if (info.name && info.name !== g.name)
297
+ g.name = info.name;
298
+ }
299
+ });
300
+ await Promise.allSettled(chatInfoPromises);
301
+ }
302
+ // Feishu chat_mode: 'group' = group chat, 'p2p' = private chat
303
+ // If chat_mode is available, use it directly. When API data is completely
304
+ // missing (permissions not enabled), default to keeping the group rather
305
+ // than filtering it out. Only filter when chat_mode is explicitly 'p2p'.
306
+ const imGroups = candidates
307
+ .filter((g) => {
308
+ if (g.channel_type === 'feishu') {
309
+ if (g.chat_mode === 'p2p')
310
+ return false;
311
+ // Exclude groups with only the bot (user_count=0 means no real users, just bot)
312
+ if (g.member_count !== undefined && g.member_count < 1)
313
+ return false;
314
+ // chat_mode is 'group' or API data completely missing — keep the group
315
+ return true;
316
+ }
317
+ return true;
318
+ })
319
+ .map(({ chat_mode: _, ...rest }) => rest);
320
+ return c.json({ imGroups });
321
+ });
322
+ // PUT /api/groups/:jid/agents/:agentId/im-binding — bind an IM group to this agent
323
+ router.put('/:jid/agents/:agentId/im-binding', authMiddleware, async (c) => {
324
+ const jid = decodeURIComponent(c.req.param('jid'));
325
+ const agentId = c.req.param('agentId');
326
+ const user = c.get('user');
327
+ const group = getRegisteredGroup(jid);
328
+ if (!group) {
329
+ return c.json({ error: 'Group not found' }, 404);
330
+ }
331
+ if (!canAccessGroup(user, { ...group, jid })) {
332
+ return c.json({ error: 'Forbidden' }, 403);
333
+ }
334
+ const agent = getAgent(agentId);
335
+ if (!agent || agent.chat_jid !== jid) {
336
+ return c.json({ error: 'Agent not found' }, 404);
337
+ }
338
+ if (agent.kind !== 'conversation') {
339
+ return c.json({ error: 'Only conversation agents can bind IM groups' }, 400);
340
+ }
341
+ const body = await c.req.json().catch(() => ({}));
342
+ const imJid = typeof body.im_jid === 'string' ? body.im_jid.trim() : '';
343
+ if (!imJid) {
344
+ return c.json({ error: 'im_jid is required' }, 400);
345
+ }
346
+ const imGroup = getRegisteredGroup(imJid);
347
+ if (!imGroup) {
348
+ return c.json({ error: 'IM group not found' }, 404);
349
+ }
350
+ if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
351
+ return c.json({ error: 'Forbidden' }, 403);
352
+ }
353
+ const force = body.force === true;
354
+ const replyPolicy = body.reply_policy === 'mirror' ? 'mirror' : 'source_only';
355
+ const hasConflict = (imGroup.target_agent_id && imGroup.target_agent_id !== agentId) ||
356
+ !!imGroup.target_main_jid;
357
+ if (hasConflict && !force) {
358
+ return c.json({ error: 'IM group is already bound elsewhere' }, 409);
359
+ }
360
+ // Update DB + in-memory cache — clear target_main_jid to avoid conflicts
361
+ const updated = {
362
+ ...imGroup,
363
+ target_agent_id: agentId,
364
+ target_main_jid: undefined,
365
+ reply_policy: replyPolicy,
366
+ };
367
+ setRegisteredGroup(imJid, updated);
368
+ const deps = getWebDeps();
369
+ if (deps) {
370
+ const groups = deps.getRegisteredGroups();
371
+ if (groups[imJid])
372
+ groups[imJid] = updated;
373
+ }
374
+ logger.info({ imJid, agentId, userId: user.id }, 'IM group bound to agent');
375
+ return c.json({ success: true });
376
+ });
377
+ // DELETE /api/groups/:jid/agents/:agentId/im-binding/:imJid — unbind an IM group
378
+ router.delete('/:jid/agents/:agentId/im-binding/:imJid', authMiddleware, async (c) => {
379
+ const jid = decodeURIComponent(c.req.param('jid'));
380
+ const agentId = c.req.param('agentId');
381
+ const imJid = decodeURIComponent(c.req.param('imJid'));
382
+ const user = c.get('user');
383
+ const group = getRegisteredGroup(jid);
384
+ if (!group) {
385
+ return c.json({ error: 'Group not found' }, 404);
386
+ }
387
+ if (!canAccessGroup(user, { ...group, jid })) {
388
+ return c.json({ error: 'Forbidden' }, 403);
389
+ }
390
+ const agent = getAgent(agentId);
391
+ if (!agent || agent.chat_jid !== jid) {
392
+ return c.json({ error: 'Agent not found' }, 404);
393
+ }
394
+ const imGroup = getRegisteredGroup(imJid);
395
+ if (!imGroup) {
396
+ return c.json({ error: 'IM group not found' }, 404);
397
+ }
398
+ if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
399
+ return c.json({ error: 'Forbidden' }, 403);
400
+ }
401
+ if (imGroup.target_agent_id !== agentId) {
402
+ return c.json({ error: 'IM group is not bound to this agent' }, 400);
403
+ }
404
+ // Update DB + in-memory cache
405
+ const updated = { ...imGroup, target_agent_id: undefined };
406
+ setRegisteredGroup(imJid, updated);
407
+ const deps = getWebDeps();
408
+ if (deps) {
409
+ const groups = deps.getRegisteredGroups();
410
+ if (groups[imJid])
411
+ groups[imJid] = updated;
412
+ }
413
+ // Clear persisted IM routing so restart won't route to unbound channel (#225)
414
+ updateAgentLastImJid(agentId, null);
415
+ logger.info({ imJid, agentId, userId: user.id }, 'IM group unbound from agent');
416
+ return c.json({ success: true });
417
+ });
418
+ // PUT /api/groups/:jid/im-binding — bind an IM group to this workspace's main conversation
419
+ router.put('/:jid/im-binding', authMiddleware, async (c) => {
420
+ const jid = decodeURIComponent(c.req.param('jid'));
421
+ const user = c.get('user');
422
+ const group = getRegisteredGroup(jid);
423
+ if (!group) {
424
+ return c.json({ error: 'Group not found' }, 404);
425
+ }
426
+ if (!canAccessGroup(user, { ...group, jid })) {
427
+ return c.json({ error: 'Forbidden' }, 403);
428
+ }
429
+ if (group.is_home) {
430
+ return c.json({ error: 'Home workspace main conversation uses default IM routing' }, 400);
431
+ }
432
+ const body = await c.req.json().catch(() => ({}));
433
+ const imJid = typeof body.im_jid === 'string' ? body.im_jid.trim() : '';
434
+ if (!imJid) {
435
+ return c.json({ error: 'im_jid is required' }, 400);
436
+ }
437
+ const imGroup = getRegisteredGroup(imJid);
438
+ if (!imGroup) {
439
+ return c.json({ error: 'IM group not found' }, 404);
440
+ }
441
+ if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
442
+ return c.json({ error: 'Forbidden' }, 403);
443
+ }
444
+ const targetMainJid = jid; // Use actual registered JID (not folder-based)
445
+ const legacyMainJid = `web:${group.folder}`;
446
+ const force = body.force === true;
447
+ // Only update reply_policy if explicitly provided; otherwise preserve existing value
448
+ const replyPolicy = body.reply_policy === 'mirror'
449
+ ? 'mirror'
450
+ : body.reply_policy === 'source_only'
451
+ ? 'source_only'
452
+ : undefined;
453
+ const hasConflict = !!imGroup.target_agent_id ||
454
+ (imGroup.target_main_jid &&
455
+ imGroup.target_main_jid !== targetMainJid &&
456
+ imGroup.target_main_jid !== legacyMainJid);
457
+ if (hasConflict && !force) {
458
+ return c.json({ error: 'IM group is already bound elsewhere' }, 409);
459
+ }
460
+ // Parse activation_mode from request body
461
+ const validActivationModes = [
462
+ 'always',
463
+ 'when_mentioned',
464
+ 'auto',
465
+ 'disabled',
466
+ ];
467
+ const rawActivationMode = body.activation_mode;
468
+ const activationMode = typeof rawActivationMode === 'string' &&
469
+ validActivationModes.includes(rawActivationMode)
470
+ ? rawActivationMode
471
+ : undefined;
472
+ // Update DB + in-memory cache — clear target_agent_id to avoid conflicts
473
+ const updated = {
474
+ ...imGroup,
475
+ target_main_jid: targetMainJid,
476
+ target_agent_id: undefined,
477
+ ...(replyPolicy !== undefined ? { reply_policy: replyPolicy } : {}),
478
+ ...(activationMode !== undefined
479
+ ? { activation_mode: activationMode }
480
+ : {}),
481
+ };
482
+ setRegisteredGroup(imJid, updated);
483
+ const deps = getWebDeps();
484
+ if (deps) {
485
+ const groups = deps.getRegisteredGroups();
486
+ if (groups[imJid])
487
+ groups[imJid] = updated;
488
+ }
489
+ logger.info({ imJid, targetMainJid, activationMode, userId: user.id }, 'IM group bound to workspace main conversation');
490
+ return c.json({ success: true });
491
+ });
492
+ // DELETE /api/groups/:jid/im-binding/:imJid — unbind an IM group from this workspace's main conversation
493
+ router.delete('/:jid/im-binding/:imJid', authMiddleware, async (c) => {
494
+ const jid = decodeURIComponent(c.req.param('jid'));
495
+ const imJid = decodeURIComponent(c.req.param('imJid'));
496
+ const user = c.get('user');
497
+ const group = getRegisteredGroup(jid);
498
+ if (!group) {
499
+ return c.json({ error: 'Group not found' }, 404);
500
+ }
501
+ if (!canAccessGroup(user, { ...group, jid })) {
502
+ return c.json({ error: 'Forbidden' }, 403);
503
+ }
504
+ const imGroup = getRegisteredGroup(imJid);
505
+ if (!imGroup) {
506
+ return c.json({ error: 'IM group not found' }, 404);
507
+ }
508
+ if (!canAccessGroup(user, { ...imGroup, jid: imJid })) {
509
+ return c.json({ error: 'Forbidden' }, 403);
510
+ }
511
+ const targetMainJid = jid; // Use actual registered JID (not folder-based)
512
+ const legacyMainJid = `web:${group.folder}`;
513
+ if (imGroup.target_main_jid !== targetMainJid &&
514
+ imGroup.target_main_jid !== legacyMainJid) {
515
+ return c.json({ error: 'IM group is not bound to this workspace' }, 400);
516
+ }
517
+ // Update DB + in-memory cache — reset activation_mode to 'auto' on unbind
518
+ const updated = {
519
+ ...imGroup,
520
+ target_main_jid: undefined,
521
+ activation_mode: 'auto',
522
+ };
523
+ setRegisteredGroup(imJid, updated);
524
+ const deps = getWebDeps();
525
+ if (deps) {
526
+ const groups = deps.getRegisteredGroups();
527
+ if (groups[imJid])
528
+ groups[imJid] = updated;
529
+ }
530
+ logger.info({ imJid, targetMainJid, userId: user.id }, 'IM group unbound from workspace main conversation');
531
+ return c.json({ success: true });
532
+ });
533
+ export default router;