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,523 @@
1
+ // Memory management routes and utilities
2
+ import { Hono } from 'hono';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { authMiddleware } from '../middleware/auth.js';
6
+ import { MemoryFileSchema, MemoryGlobalSchema, } from '../schemas.js';
7
+ import { getAllRegisteredGroups, getUserById } from '../db.js';
8
+ import { logger } from '../logger.js';
9
+ import { GROUPS_DIR, DATA_DIR } from '../config.js';
10
+ import { AGENT_MEMORY_FILENAME } from '../project-memory.js';
11
+ const memoryRoutes = new Hono();
12
+ // --- Constants ---
13
+ const USER_GLOBAL_DIR = path.join(GROUPS_DIR, 'user-global');
14
+ const MEMORY_DATA_DIR = path.join(DATA_DIR, 'memory');
15
+ const MAX_GLOBAL_MEMORY_LENGTH = 200_000;
16
+ const MAX_MEMORY_FILE_LENGTH = 500_000;
17
+ const MEMORY_LIST_LIMIT = 500;
18
+ const MEMORY_SEARCH_LIMIT = 120;
19
+ const MEMORY_SOURCE_EXTENSIONS = new Set([
20
+ '.md',
21
+ '.txt',
22
+ '.json',
23
+ '.jsonl',
24
+ '.yaml',
25
+ '.yml',
26
+ '.toml',
27
+ '.ini',
28
+ '.cfg',
29
+ '.conf',
30
+ ]);
31
+ // --- Utility Functions ---
32
+ function isWithinRoot(targetPath, rootPath) {
33
+ const relative = path.relative(rootPath, targetPath);
34
+ return (relative === '' ||
35
+ (!relative.startsWith('..') && !path.isAbsolute(relative)));
36
+ }
37
+ function normalizeRelativePath(input) {
38
+ if (typeof input !== 'string') {
39
+ throw new Error('path must be a string');
40
+ }
41
+ const normalized = input.replace(/\\/g, '/').trim().replace(/^\/+/, '');
42
+ if (!normalized || normalized.includes('\0')) {
43
+ throw new Error('Invalid memory path');
44
+ }
45
+ const parts = normalized.split('/');
46
+ if (parts.some((p) => !p || p === '.' || p === '..')) {
47
+ throw new Error('Invalid memory path');
48
+ }
49
+ return normalized;
50
+ }
51
+ export function fromMemoryApiPath(relativePath, rootDir = DATA_DIR) {
52
+ return path.resolve(rootDir, relativePath);
53
+ }
54
+ export function toMemoryApiPath(absolutePath, rootDir = DATA_DIR) {
55
+ return path.relative(rootDir, absolutePath).replace(/\\/g, '/');
56
+ }
57
+ function resolveMemoryPath(relativePath, user) {
58
+ const absolute = fromMemoryApiPath(relativePath);
59
+ const inGroups = isWithinRoot(absolute, GROUPS_DIR);
60
+ const inMemoryData = isWithinRoot(absolute, MEMORY_DATA_DIR);
61
+ const writable = inGroups || inMemoryData;
62
+ if (!writable) {
63
+ throw new Error('Memory path out of allowed scope');
64
+ }
65
+ // User ownership check for non-admin
66
+ if (user.role !== 'admin') {
67
+ // user-global/{userId}/... — member can only access their own
68
+ if (isWithinRoot(absolute, USER_GLOBAL_DIR)) {
69
+ const relToUserGlobal = path.relative(USER_GLOBAL_DIR, absolute);
70
+ const ownerUserId = relToUserGlobal.split(path.sep)[0];
71
+ if (ownerUserId !== user.id) {
72
+ throw new Error('Memory path out of allowed scope');
73
+ }
74
+ }
75
+ // groups/{folder}/... — check group ownership
76
+ else if (inGroups) {
77
+ const relToGroups = path.relative(GROUPS_DIR, absolute);
78
+ const folder = relToGroups.split(path.sep)[0];
79
+ if (!isUserOwnedFolder(user, folder)) {
80
+ throw new Error('Memory path out of allowed scope');
81
+ }
82
+ }
83
+ // memory/{folder}/... — check group ownership
84
+ else if (inMemoryData) {
85
+ const relToMemory = path.relative(MEMORY_DATA_DIR, absolute);
86
+ const folder = relToMemory.split(path.sep)[0];
87
+ if (!isUserOwnedFolder(user, folder)) {
88
+ throw new Error('Memory path out of allowed scope');
89
+ }
90
+ }
91
+ }
92
+ return { absolutePath: absolute, writable };
93
+ }
94
+ /** Check if a folder belongs to the user (via registered_groups). */
95
+ function isUserOwnedFolder(user, folder) {
96
+ if (user.role === 'admin')
97
+ return true;
98
+ if (!folder)
99
+ return false;
100
+ const groups = getAllRegisteredGroups();
101
+ for (const group of Object.values(groups)) {
102
+ if (group.folder === folder && group.created_by === user.id) {
103
+ return true;
104
+ }
105
+ }
106
+ return false;
107
+ }
108
+ function classifyMemorySource(relativePath) {
109
+ const parts = relativePath.split('/');
110
+ // groups/user-global/{userId}/...
111
+ if (parts[0] === 'groups' && parts[1] === 'user-global') {
112
+ const userId = parts[2] || 'unknown';
113
+ const name = parts.slice(3).join('/') || AGENT_MEMORY_FILENAME;
114
+ const owner = getUserById(userId);
115
+ const ownerLabel = owner ? owner.display_name || owner.username : userId;
116
+ if (name === 'HEARTBEAT.md') {
117
+ return {
118
+ type: 'heartbeat',
119
+ label: `${ownerLabel} / 每日心跳`,
120
+ ownerName: ownerLabel,
121
+ };
122
+ }
123
+ return {
124
+ type: 'global',
125
+ label: `${ownerLabel} / 全局记忆 / ${name}`,
126
+ ownerName: ownerLabel,
127
+ };
128
+ }
129
+ // memory/{folder}/...
130
+ if (parts[0] === 'memory') {
131
+ const folder = parts[1] || 'unknown';
132
+ const name = parts.slice(2).join('/') || 'memory';
133
+ return {
134
+ type: 'date',
135
+ label: `${folder} / 日期记忆 / ${name}`,
136
+ folder,
137
+ };
138
+ }
139
+ // groups/{folder}/conversations/...
140
+ if (parts[0] === 'groups' &&
141
+ parts.length >= 3 &&
142
+ parts[2] === 'conversations') {
143
+ const folder = parts[1] || 'unknown';
144
+ const name = parts.slice(3).join('/');
145
+ return {
146
+ type: 'conversation',
147
+ label: `${folder} / 对话归档 / ${name}`,
148
+ folder,
149
+ };
150
+ }
151
+ // groups/{folder}/... (session memory)
152
+ if (parts[0] === 'groups') {
153
+ const folder = parts[1] || 'unknown';
154
+ const name = parts.slice(2).join('/');
155
+ return {
156
+ type: 'session',
157
+ label: `${folder} / ${name}`,
158
+ folder,
159
+ };
160
+ }
161
+ // Fallback
162
+ return {
163
+ type: 'session',
164
+ label: parts.join('/'),
165
+ folder: parts[1] || undefined,
166
+ };
167
+ }
168
+ function readMemoryFile(relativePath, user) {
169
+ const normalized = normalizeRelativePath(relativePath);
170
+ const { absolutePath, writable } = resolveMemoryPath(normalized, user);
171
+ if (!fs.existsSync(absolutePath)) {
172
+ if (!writable) {
173
+ throw new Error('Memory file not found');
174
+ }
175
+ return {
176
+ path: normalized,
177
+ content: '',
178
+ updatedAt: null,
179
+ size: 0,
180
+ writable,
181
+ };
182
+ }
183
+ const content = fs.readFileSync(absolutePath, 'utf-8');
184
+ const stat = fs.statSync(absolutePath);
185
+ return {
186
+ path: normalized,
187
+ content,
188
+ updatedAt: stat.mtime.toISOString(),
189
+ size: Buffer.byteLength(content, 'utf-8'),
190
+ writable,
191
+ };
192
+ }
193
+ // 记忆路径中禁止写入的系统子目录(AGENTS.md 除外,它是记忆文件)
194
+ const MEMORY_BLOCKED_DIRS = ['logs', '.claude', 'conversations'];
195
+ function isBlockedMemoryPath(normalizedPath) {
196
+ const parts = normalizedPath.split('/');
197
+ // 路径格式: groups/{folder}/{subpath...} 或 memory/{folder}/{subpath...}
198
+ // 检查 groups/{folder}/ 下的系统子目录
199
+ if (parts[0] === 'groups' && parts.length >= 3) {
200
+ const subPath = parts[2];
201
+ if (MEMORY_BLOCKED_DIRS.includes(subPath))
202
+ return true;
203
+ }
204
+ return false;
205
+ }
206
+ function writeMemoryFile(relativePath, content, user) {
207
+ const normalized = normalizeRelativePath(relativePath);
208
+ const { absolutePath, writable } = resolveMemoryPath(normalized, user);
209
+ if (!writable) {
210
+ throw new Error('Memory file is read-only');
211
+ }
212
+ if (isBlockedMemoryPath(normalized)) {
213
+ throw new Error('Cannot write to system path');
214
+ }
215
+ if (normalized.includes('user-global/') &&
216
+ normalized.endsWith('/HEARTBEAT.md')) {
217
+ throw new Error('HEARTBEAT.md is read-only (auto-generated)');
218
+ }
219
+ if (Buffer.byteLength(content, 'utf-8') > MAX_MEMORY_FILE_LENGTH) {
220
+ throw new Error('Memory file is too large');
221
+ }
222
+ fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
223
+ const tempPath = `${absolutePath}.tmp`;
224
+ fs.writeFileSync(tempPath, content, 'utf-8');
225
+ fs.renameSync(tempPath, absolutePath);
226
+ const stat = fs.statSync(absolutePath);
227
+ return {
228
+ path: normalized,
229
+ content,
230
+ updatedAt: stat.mtime.toISOString(),
231
+ size: Buffer.byteLength(content, 'utf-8'),
232
+ writable,
233
+ };
234
+ }
235
+ // Directories to skip when scanning group workspaces for memory files
236
+ const WALK_SKIP_DIRS = new Set([
237
+ 'logs',
238
+ '.claude',
239
+ 'conversations',
240
+ 'downloads',
241
+ 'node_modules',
242
+ ]);
243
+ function walkFiles(baseDir, maxDepth, limit, out, currentDepth = 0) {
244
+ if (out.length >= limit || currentDepth > maxDepth || !fs.existsSync(baseDir))
245
+ return;
246
+ let entries;
247
+ try {
248
+ entries = fs.readdirSync(baseDir, { withFileTypes: true });
249
+ }
250
+ catch {
251
+ return;
252
+ }
253
+ for (const entry of entries) {
254
+ if (out.length >= limit)
255
+ break;
256
+ const fullPath = path.join(baseDir, entry.name);
257
+ if (entry.isDirectory()) {
258
+ if (WALK_SKIP_DIRS.has(entry.name))
259
+ continue;
260
+ walkFiles(fullPath, maxDepth, limit, out, currentDepth + 1);
261
+ continue;
262
+ }
263
+ out.push(fullPath);
264
+ }
265
+ }
266
+ function isMemoryCandidateFile(filePath) {
267
+ const ext = path.extname(filePath).toLowerCase();
268
+ return MEMORY_SOURCE_EXTENSIONS.has(ext);
269
+ }
270
+ function listMemorySources(user) {
271
+ const files = new Set();
272
+ const isAdmin = user.role === 'admin';
273
+ const groups = getAllRegisteredGroups();
274
+ const accessibleFolders = new Set();
275
+ if (isAdmin) {
276
+ for (const group of Object.values(groups)) {
277
+ accessibleFolders.add(group.folder);
278
+ }
279
+ }
280
+ else {
281
+ for (const group of Object.values(groups)) {
282
+ if (group.created_by === user.id) {
283
+ accessibleFolders.add(group.folder);
284
+ }
285
+ }
286
+ }
287
+ // 1. User-global memory + heartbeat
288
+ files.add(path.join(USER_GLOBAL_DIR, user.id, AGENT_MEMORY_FILENAME));
289
+ const heartbeatPath = path.join(USER_GLOBAL_DIR, user.id, 'HEARTBEAT.md');
290
+ if (fs.existsSync(heartbeatPath)) {
291
+ files.add(heartbeatPath);
292
+ }
293
+ // 2. Group AGENTS.md files
294
+ for (const folder of accessibleFolders) {
295
+ files.add(path.join(GROUPS_DIR, folder, AGENT_MEMORY_FILENAME));
296
+ }
297
+ // 3. Scan group workspace directories (skips system dirs via WALK_SKIP_DIRS)
298
+ for (const folder of accessibleFolders) {
299
+ const folderDir = path.join(GROUPS_DIR, folder);
300
+ const scanned = [];
301
+ walkFiles(folderDir, 4, MEMORY_LIST_LIMIT, scanned);
302
+ for (const f of scanned) {
303
+ if (isMemoryCandidateFile(f))
304
+ files.add(f);
305
+ }
306
+ }
307
+ // 4. Scan memory/ (date memory files)
308
+ if (fs.existsSync(MEMORY_DATA_DIR)) {
309
+ const memFolders = fs.readdirSync(MEMORY_DATA_DIR, { withFileTypes: true });
310
+ for (const d of memFolders) {
311
+ if (d.isDirectory() && (isAdmin || accessibleFolders.has(d.name))) {
312
+ const scanned = [];
313
+ walkFiles(path.join(MEMORY_DATA_DIR, d.name), 4, MEMORY_LIST_LIMIT, scanned);
314
+ for (const f of scanned) {
315
+ if (isMemoryCandidateFile(f))
316
+ files.add(f);
317
+ }
318
+ }
319
+ }
320
+ }
321
+ // 5. Scan conversations/ directories (read-only archives)
322
+ for (const folder of accessibleFolders) {
323
+ const convDir = path.join(GROUPS_DIR, folder, 'conversations');
324
+ if (!fs.existsSync(convDir))
325
+ continue;
326
+ try {
327
+ const entries = fs.readdirSync(convDir, { withFileTypes: true });
328
+ for (const entry of entries) {
329
+ if (files.size >= MEMORY_LIST_LIMIT)
330
+ break;
331
+ if (!entry.isFile())
332
+ continue;
333
+ const fullPath = path.join(convDir, entry.name);
334
+ if (isMemoryCandidateFile(fullPath))
335
+ files.add(fullPath);
336
+ }
337
+ }
338
+ catch {
339
+ /* skip unreadable */
340
+ }
341
+ }
342
+ const sources = [];
343
+ for (const absolutePath of files) {
344
+ const inGroups = isWithinRoot(absolutePath, GROUPS_DIR);
345
+ const inMemoryData = isWithinRoot(absolutePath, MEMORY_DATA_DIR);
346
+ if (!inGroups && !inMemoryData)
347
+ continue;
348
+ const relativePath = toMemoryApiPath(absolutePath);
349
+ const exists = fs.existsSync(absolutePath);
350
+ let updatedAt = null;
351
+ let size = 0;
352
+ if (exists) {
353
+ const stat = fs.statSync(absolutePath);
354
+ updatedAt = stat.mtime.toISOString();
355
+ size = stat.size;
356
+ }
357
+ const classified = classifyMemorySource(relativePath);
358
+ const writable = classified.type !== 'heartbeat' && classified.type !== 'conversation';
359
+ sources.push({
360
+ path: relativePath,
361
+ writable,
362
+ exists,
363
+ updatedAt,
364
+ size,
365
+ ...classified,
366
+ });
367
+ }
368
+ const typeRank = {
369
+ global: 0,
370
+ heartbeat: 1,
371
+ session: 2,
372
+ date: 3,
373
+ conversation: 4,
374
+ };
375
+ sources.sort((a, b) => {
376
+ if (typeRank[a.type] !== typeRank[b.type])
377
+ return typeRank[a.type] - typeRank[b.type];
378
+ if (a.folder !== b.folder)
379
+ return (a.folder || '').localeCompare(b.folder || '', 'zh-CN');
380
+ return a.path.localeCompare(b.path, 'zh-CN');
381
+ });
382
+ return sources.slice(0, MEMORY_LIST_LIMIT);
383
+ }
384
+ function buildSearchSnippet(content, index, keywordLength) {
385
+ const start = Math.max(0, index - 36);
386
+ const end = Math.min(content.length, index + keywordLength + 36);
387
+ return content.slice(start, end).replace(/\s+/g, ' ').trim();
388
+ }
389
+ function searchMemorySources(keyword, user, limit = MEMORY_SEARCH_LIMIT) {
390
+ const normalizedKeyword = keyword.trim().toLowerCase();
391
+ if (!normalizedKeyword)
392
+ return [];
393
+ const maxResults = Number.isFinite(limit)
394
+ ? Math.max(1, Math.min(MEMORY_SEARCH_LIMIT, Math.trunc(limit)))
395
+ : MEMORY_SEARCH_LIMIT;
396
+ const hits = [];
397
+ const sources = listMemorySources(user);
398
+ for (const source of sources) {
399
+ if (hits.length >= maxResults)
400
+ break;
401
+ if (!source.exists || source.size === 0)
402
+ continue;
403
+ if (source.size > MAX_MEMORY_FILE_LENGTH)
404
+ continue;
405
+ try {
406
+ const payload = readMemoryFile(source.path, user);
407
+ const lower = payload.content.toLowerCase();
408
+ const firstIndex = lower.indexOf(normalizedKeyword);
409
+ if (firstIndex === -1)
410
+ continue;
411
+ let count = 0;
412
+ let from = 0;
413
+ while (from < lower.length) {
414
+ const idx = lower.indexOf(normalizedKeyword, from);
415
+ if (idx === -1)
416
+ break;
417
+ count += 1;
418
+ from = idx + normalizedKeyword.length;
419
+ }
420
+ hits.push({
421
+ ...source,
422
+ hits: count,
423
+ snippet: buildSearchSnippet(payload.content, firstIndex, normalizedKeyword.length),
424
+ });
425
+ }
426
+ catch {
427
+ continue;
428
+ }
429
+ }
430
+ return hits;
431
+ }
432
+ // --- Routes ---
433
+ // All memory routes require authentication (member + admin).
434
+ // User-level filtering is handled inside each function.
435
+ memoryRoutes.get('/sources', authMiddleware, (c) => {
436
+ try {
437
+ const user = c.get('user');
438
+ return c.json({ sources: listMemorySources(user) });
439
+ }
440
+ catch (err) {
441
+ logger.error({ err }, 'Failed to list memory sources');
442
+ return c.json({ error: 'Failed to list memory sources' }, 500);
443
+ }
444
+ });
445
+ memoryRoutes.get('/search', authMiddleware, (c) => {
446
+ const query = c.req.query('q');
447
+ if (!query || !query.trim()) {
448
+ return c.json({ error: 'Missing q' }, 400);
449
+ }
450
+ const limitRaw = Number(c.req.query('limit'));
451
+ const limit = Number.isFinite(limitRaw) ? limitRaw : MEMORY_SEARCH_LIMIT;
452
+ try {
453
+ const user = c.get('user');
454
+ return c.json({ hits: searchMemorySources(query, user, limit) });
455
+ }
456
+ catch (err) {
457
+ logger.error({ err }, 'Failed to search memory sources');
458
+ return c.json({ error: 'Failed to search memory sources' }, 500);
459
+ }
460
+ });
461
+ memoryRoutes.get('/file', authMiddleware, (c) => {
462
+ const filePath = c.req.query('path');
463
+ if (!filePath)
464
+ return c.json({ error: 'Missing path' }, 400);
465
+ try {
466
+ const user = c.get('user');
467
+ return c.json(readMemoryFile(filePath, user));
468
+ }
469
+ catch (err) {
470
+ const message = err instanceof Error ? err.message : 'Failed to read memory file';
471
+ const status = message.includes('not found') ? 404 : 400;
472
+ return c.json({ error: message }, status);
473
+ }
474
+ });
475
+ memoryRoutes.put('/file', authMiddleware, async (c) => {
476
+ const body = await c.req.json().catch(() => ({}));
477
+ const validation = MemoryFileSchema.safeParse(body);
478
+ if (!validation.success) {
479
+ return c.json({ error: 'Invalid request body', details: validation.error.format() }, 400);
480
+ }
481
+ try {
482
+ const user = c.get('user');
483
+ return c.json(writeMemoryFile(validation.data.path, validation.data.content, user));
484
+ }
485
+ catch (err) {
486
+ const message = err instanceof Error ? err.message : 'Failed to write memory file';
487
+ return c.json({ error: message }, 400);
488
+ }
489
+ });
490
+ // Legacy /global API — now operates on the current user's user-global memory.
491
+ memoryRoutes.get('/global', authMiddleware, (c) => {
492
+ try {
493
+ const user = c.get('user');
494
+ const userGlobalPath = `groups/user-global/${user.id}/${AGENT_MEMORY_FILENAME}`;
495
+ return c.json(readMemoryFile(userGlobalPath, user));
496
+ }
497
+ catch (err) {
498
+ logger.error({ err }, 'Failed to read user global memory');
499
+ return c.json({ error: 'Failed to read global memory' }, 500);
500
+ }
501
+ });
502
+ memoryRoutes.put('/global', authMiddleware, async (c) => {
503
+ const body = await c.req.json().catch(() => ({}));
504
+ const validation = MemoryGlobalSchema.safeParse(body);
505
+ if (!validation.success) {
506
+ return c.json({ error: 'Invalid request body', details: validation.error.format() }, 400);
507
+ }
508
+ if (Buffer.byteLength(validation.data.content, 'utf-8') >
509
+ MAX_GLOBAL_MEMORY_LENGTH) {
510
+ return c.json({ error: 'Global memory is too large' }, 400);
511
+ }
512
+ try {
513
+ const user = c.get('user');
514
+ const userGlobalPath = `groups/user-global/${user.id}/${AGENT_MEMORY_FILENAME}`;
515
+ return c.json(writeMemoryFile(userGlobalPath, validation.data.content, user));
516
+ }
517
+ catch (err) {
518
+ const message = err instanceof Error ? err.message : 'Failed to write global memory';
519
+ logger.error({ err }, 'Failed to write user global memory');
520
+ return c.json({ error: message }, 400);
521
+ }
522
+ });
523
+ export default memoryRoutes;