@swarmclawai/swarmclaw 0.2.0

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 (319) hide show
  1. package/README.md +577 -0
  2. package/bin/server-cmd.js +359 -0
  3. package/bin/swarmclaw.js +29 -0
  4. package/bin/swarmclaw.mjs +1504 -0
  5. package/next.config.ts +33 -0
  6. package/package.json +112 -0
  7. package/postcss.config.mjs +7 -0
  8. package/public/branding/swarmclaw-org-avatar.png +0 -0
  9. package/public/branding/swarmclaw-org-avatar.svg +58 -0
  10. package/public/file.svg +1 -0
  11. package/public/globe.svg +1 -0
  12. package/public/next.svg +1 -0
  13. package/public/screenshots/agents.png +0 -0
  14. package/public/screenshots/connectors.png +0 -0
  15. package/public/screenshots/dashboard.png +0 -0
  16. package/public/screenshots/new-session-openclaw.png +0 -0
  17. package/public/screenshots/providers.png +0 -0
  18. package/public/screenshots/schedules.png +0 -0
  19. package/public/screenshots/tasks.png +0 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/api/agents/[id]/route.ts +30 -0
  23. package/src/app/api/agents/[id]/thread/route.ts +66 -0
  24. package/src/app/api/agents/generate/route.ts +42 -0
  25. package/src/app/api/agents/route.ts +33 -0
  26. package/src/app/api/auth/route.ts +25 -0
  27. package/src/app/api/claude-skills/route.ts +42 -0
  28. package/src/app/api/clawhub/install/route.ts +39 -0
  29. package/src/app/api/clawhub/search/route.ts +11 -0
  30. package/src/app/api/connectors/[id]/route.ts +79 -0
  31. package/src/app/api/connectors/route.ts +60 -0
  32. package/src/app/api/credentials/[id]/route.ts +14 -0
  33. package/src/app/api/credentials/route.ts +31 -0
  34. package/src/app/api/daemon/health-check/route.ts +11 -0
  35. package/src/app/api/daemon/route.ts +22 -0
  36. package/src/app/api/dirs/pick/route.ts +60 -0
  37. package/src/app/api/dirs/route.ts +29 -0
  38. package/src/app/api/documents/[id]/route.ts +47 -0
  39. package/src/app/api/documents/route.ts +93 -0
  40. package/src/app/api/files/serve/route.ts +69 -0
  41. package/src/app/api/generate/info/route.ts +12 -0
  42. package/src/app/api/generate/route.ts +106 -0
  43. package/src/app/api/ip/route.ts +6 -0
  44. package/src/app/api/knowledge/[id]/route.ts +61 -0
  45. package/src/app/api/knowledge/route.ts +48 -0
  46. package/src/app/api/knowledge/upload/route.ts +86 -0
  47. package/src/app/api/logs/route.ts +65 -0
  48. package/src/app/api/mcp-servers/[id]/route.ts +32 -0
  49. package/src/app/api/mcp-servers/[id]/test/route.ts +23 -0
  50. package/src/app/api/mcp-servers/[id]/tools/route.ts +32 -0
  51. package/src/app/api/mcp-servers/route.ts +27 -0
  52. package/src/app/api/memory/[id]/route.ts +126 -0
  53. package/src/app/api/memory/maintenance/route.ts +63 -0
  54. package/src/app/api/memory/route.ts +111 -0
  55. package/src/app/api/memory-images/[filename]/route.ts +36 -0
  56. package/src/app/api/orchestrator/run/route.ts +43 -0
  57. package/src/app/api/plugins/install/route.ts +58 -0
  58. package/src/app/api/plugins/marketplace/route.ts +33 -0
  59. package/src/app/api/plugins/route.ts +21 -0
  60. package/src/app/api/preview-server/route.ts +339 -0
  61. package/src/app/api/providers/[id]/models/route.ts +29 -0
  62. package/src/app/api/providers/[id]/route.ts +34 -0
  63. package/src/app/api/providers/configs/route.ts +7 -0
  64. package/src/app/api/providers/ollama/route.ts +30 -0
  65. package/src/app/api/providers/openclaw/health/route.ts +23 -0
  66. package/src/app/api/providers/route.ts +28 -0
  67. package/src/app/api/runs/[id]/route.ts +9 -0
  68. package/src/app/api/runs/route.ts +13 -0
  69. package/src/app/api/schedules/[id]/route.ts +28 -0
  70. package/src/app/api/schedules/[id]/run/route.ts +104 -0
  71. package/src/app/api/schedules/route.ts +78 -0
  72. package/src/app/api/secrets/[id]/route.ts +29 -0
  73. package/src/app/api/secrets/route.ts +42 -0
  74. package/src/app/api/sessions/[id]/browser/route.ts +13 -0
  75. package/src/app/api/sessions/[id]/chat/route.ts +96 -0
  76. package/src/app/api/sessions/[id]/clear/route.ts +19 -0
  77. package/src/app/api/sessions/[id]/deploy/route.ts +34 -0
  78. package/src/app/api/sessions/[id]/devserver/route.ts +69 -0
  79. package/src/app/api/sessions/[id]/mailbox/route.ts +70 -0
  80. package/src/app/api/sessions/[id]/main-loop/route.ts +94 -0
  81. package/src/app/api/sessions/[id]/messages/route.ts +9 -0
  82. package/src/app/api/sessions/[id]/retry/route.ts +28 -0
  83. package/src/app/api/sessions/[id]/route.ts +103 -0
  84. package/src/app/api/sessions/[id]/stop/route.ts +13 -0
  85. package/src/app/api/sessions/heartbeat/route.ts +26 -0
  86. package/src/app/api/sessions/route.ts +85 -0
  87. package/src/app/api/settings/route.ts +58 -0
  88. package/src/app/api/setup/check-provider/route.ts +326 -0
  89. package/src/app/api/setup/doctor/route.ts +250 -0
  90. package/src/app/api/skills/[id]/route.ts +40 -0
  91. package/src/app/api/skills/import/route.ts +69 -0
  92. package/src/app/api/skills/route.ts +28 -0
  93. package/src/app/api/tasks/[id]/route.ts +102 -0
  94. package/src/app/api/tasks/route.ts +115 -0
  95. package/src/app/api/tts/route.ts +40 -0
  96. package/src/app/api/upload/route.ts +18 -0
  97. package/src/app/api/uploads/[filename]/route.ts +59 -0
  98. package/src/app/api/usage/route.ts +35 -0
  99. package/src/app/api/version/route.ts +81 -0
  100. package/src/app/api/version/update/route.ts +95 -0
  101. package/src/app/api/webhooks/[id]/history/route.ts +13 -0
  102. package/src/app/api/webhooks/[id]/route.ts +204 -0
  103. package/src/app/api/webhooks/route.ts +37 -0
  104. package/src/app/favicon.ico +0 -0
  105. package/src/app/globals.css +370 -0
  106. package/src/app/layout.tsx +52 -0
  107. package/src/app/page.tsx +172 -0
  108. package/src/cli/index.js +1232 -0
  109. package/src/cli/index.test.js +281 -0
  110. package/src/cli/index.ts +1158 -0
  111. package/src/cli/spec.js +284 -0
  112. package/src/components/agents/agent-card.tsx +219 -0
  113. package/src/components/agents/agent-chat-list.tsx +165 -0
  114. package/src/components/agents/agent-list.tsx +110 -0
  115. package/src/components/agents/agent-sheet.tsx +1220 -0
  116. package/src/components/auth/access-key-gate.tsx +248 -0
  117. package/src/components/auth/setup-wizard.tsx +940 -0
  118. package/src/components/auth/user-picker.tsx +88 -0
  119. package/src/components/chat/chat-area.tsx +406 -0
  120. package/src/components/chat/chat-header.tsx +491 -0
  121. package/src/components/chat/chat-tool-toggles.tsx +161 -0
  122. package/src/components/chat/code-block.tsx +146 -0
  123. package/src/components/chat/dev-server-bar.tsx +39 -0
  124. package/src/components/chat/message-bubble.tsx +486 -0
  125. package/src/components/chat/message-list.tsx +299 -0
  126. package/src/components/chat/session-debug-panel.tsx +196 -0
  127. package/src/components/chat/streaming-bubble.tsx +85 -0
  128. package/src/components/chat/thinking-indicator.tsx +26 -0
  129. package/src/components/chat/tool-call-bubble.tsx +438 -0
  130. package/src/components/chat/tool-request-banner.tsx +103 -0
  131. package/src/components/connectors/connector-list.tsx +196 -0
  132. package/src/components/connectors/connector-sheet.tsx +804 -0
  133. package/src/components/input/chat-input.tsx +235 -0
  134. package/src/components/knowledge/knowledge-list.tsx +206 -0
  135. package/src/components/knowledge/knowledge-sheet.tsx +316 -0
  136. package/src/components/layout/app-layout.tsx +1016 -0
  137. package/src/components/layout/daemon-indicator.tsx +56 -0
  138. package/src/components/layout/mobile-header.tsx +31 -0
  139. package/src/components/layout/network-banner.tsx +17 -0
  140. package/src/components/layout/update-banner.tsx +130 -0
  141. package/src/components/logs/log-list.tsx +358 -0
  142. package/src/components/mcp-servers/mcp-server-list.tsx +122 -0
  143. package/src/components/mcp-servers/mcp-server-sheet.tsx +243 -0
  144. package/src/components/memory/memory-card.tsx +63 -0
  145. package/src/components/memory/memory-detail.tsx +339 -0
  146. package/src/components/memory/memory-list.tsx +198 -0
  147. package/src/components/memory/memory-sheet.tsx +70 -0
  148. package/src/components/plugins/plugin-list.tsx +60 -0
  149. package/src/components/plugins/plugin-sheet.tsx +311 -0
  150. package/src/components/providers/provider-list.tsx +96 -0
  151. package/src/components/providers/provider-sheet.tsx +542 -0
  152. package/src/components/runs/run-list.tsx +231 -0
  153. package/src/components/schedules/schedule-card.tsx +63 -0
  154. package/src/components/schedules/schedule-list.tsx +76 -0
  155. package/src/components/schedules/schedule-sheet.tsx +336 -0
  156. package/src/components/secrets/secret-sheet.tsx +180 -0
  157. package/src/components/secrets/secrets-list.tsx +91 -0
  158. package/src/components/sessions/new-session-sheet.tsx +478 -0
  159. package/src/components/sessions/session-card.tsx +144 -0
  160. package/src/components/sessions/session-list.tsx +202 -0
  161. package/src/components/shared/ai-gen-block.tsx +77 -0
  162. package/src/components/shared/avatar.tsx +48 -0
  163. package/src/components/shared/bottom-sheet.tsx +30 -0
  164. package/src/components/shared/confirm-dialog.tsx +47 -0
  165. package/src/components/shared/connector-platform-icon.tsx +113 -0
  166. package/src/components/shared/dir-browser.tsx +285 -0
  167. package/src/components/shared/dropdown.tsx +55 -0
  168. package/src/components/shared/icon-button.tsx +25 -0
  169. package/src/components/shared/settings/plugin-manager.tsx +207 -0
  170. package/src/components/shared/settings/section-capability-policy.tsx +93 -0
  171. package/src/components/shared/settings/section-embedding.tsx +99 -0
  172. package/src/components/shared/settings/section-heartbeat.tsx +168 -0
  173. package/src/components/shared/settings/section-memory.tsx +77 -0
  174. package/src/components/shared/settings/section-orchestrator.tsx +108 -0
  175. package/src/components/shared/settings/section-providers.tsx +181 -0
  176. package/src/components/shared/settings/section-runtime-loop.tsx +183 -0
  177. package/src/components/shared/settings/section-secrets.tsx +132 -0
  178. package/src/components/shared/settings/section-user-preferences.tsx +24 -0
  179. package/src/components/shared/settings/section-voice.tsx +53 -0
  180. package/src/components/shared/settings/settings-sheet.tsx +88 -0
  181. package/src/components/shared/settings/types.ts +7 -0
  182. package/src/components/shared/settings/utils.ts +13 -0
  183. package/src/components/shared/settings-sheet.tsx +1 -0
  184. package/src/components/shared/skeleton.tsx +19 -0
  185. package/src/components/shared/usage-badge.tsx +28 -0
  186. package/src/components/skills/clawhub-browser.tsx +225 -0
  187. package/src/components/skills/skill-list.tsx +70 -0
  188. package/src/components/skills/skill-sheet.tsx +254 -0
  189. package/src/components/tasks/task-board.tsx +96 -0
  190. package/src/components/tasks/task-card.tsx +179 -0
  191. package/src/components/tasks/task-column.tsx +73 -0
  192. package/src/components/tasks/task-list.tsx +118 -0
  193. package/src/components/tasks/task-sheet.tsx +415 -0
  194. package/src/components/ui/avatar.tsx +109 -0
  195. package/src/components/ui/badge.tsx +48 -0
  196. package/src/components/ui/button.tsx +64 -0
  197. package/src/components/ui/card.tsx +92 -0
  198. package/src/components/ui/dialog.tsx +158 -0
  199. package/src/components/ui/dropdown-menu.tsx +257 -0
  200. package/src/components/ui/input.tsx +21 -0
  201. package/src/components/ui/scroll-area.tsx +58 -0
  202. package/src/components/ui/select.tsx +190 -0
  203. package/src/components/ui/separator.tsx +28 -0
  204. package/src/components/ui/sheet.tsx +143 -0
  205. package/src/components/ui/sonner.tsx +22 -0
  206. package/src/components/ui/textarea.tsx +18 -0
  207. package/src/components/ui/tooltip.tsx +56 -0
  208. package/src/components/usage/usage-list.tsx +105 -0
  209. package/src/components/webhooks/webhook-list.tsx +166 -0
  210. package/src/components/webhooks/webhook-sheet.tsx +402 -0
  211. package/src/hooks/use-auto-resize.ts +20 -0
  212. package/src/hooks/use-media-query.ts +21 -0
  213. package/src/hooks/use-speech-recognition.ts +83 -0
  214. package/src/instrumentation.ts +8 -0
  215. package/src/lib/agents.ts +13 -0
  216. package/src/lib/api-client.ts +100 -0
  217. package/src/lib/chat.ts +60 -0
  218. package/src/lib/memory.ts +42 -0
  219. package/src/lib/openclaw-endpoint.test.ts +48 -0
  220. package/src/lib/openclaw-endpoint.ts +67 -0
  221. package/src/lib/provider-config.ts +13 -0
  222. package/src/lib/providers/anthropic.ts +135 -0
  223. package/src/lib/providers/claude-cli.ts +202 -0
  224. package/src/lib/providers/codex-cli.ts +260 -0
  225. package/src/lib/providers/index.ts +351 -0
  226. package/src/lib/providers/ollama.ts +131 -0
  227. package/src/lib/providers/openai.ts +164 -0
  228. package/src/lib/providers/openclaw.ts +330 -0
  229. package/src/lib/providers/opencode-cli.ts +164 -0
  230. package/src/lib/runtime-loop.ts +15 -0
  231. package/src/lib/schedule-dedupe.test.ts +84 -0
  232. package/src/lib/schedule-dedupe.ts +174 -0
  233. package/src/lib/schedule-name.ts +62 -0
  234. package/src/lib/schedules.ts +16 -0
  235. package/src/lib/server/agent-registry.ts +70 -0
  236. package/src/lib/server/api-routes.test.ts +362 -0
  237. package/src/lib/server/autonomy-contract.ts +200 -0
  238. package/src/lib/server/build-llm.ts +155 -0
  239. package/src/lib/server/capability-router.test.ts +21 -0
  240. package/src/lib/server/capability-router.ts +172 -0
  241. package/src/lib/server/chat-execution.ts +894 -0
  242. package/src/lib/server/clawhub-client.test.ts +161 -0
  243. package/src/lib/server/clawhub-client.ts +26 -0
  244. package/src/lib/server/connectors/connector-routing.test.ts +243 -0
  245. package/src/lib/server/connectors/discord.ts +116 -0
  246. package/src/lib/server/connectors/googlechat.ts +66 -0
  247. package/src/lib/server/connectors/manager.ts +559 -0
  248. package/src/lib/server/connectors/matrix.ts +78 -0
  249. package/src/lib/server/connectors/media.ts +149 -0
  250. package/src/lib/server/connectors/openclaw.test.ts +375 -0
  251. package/src/lib/server/connectors/openclaw.ts +1132 -0
  252. package/src/lib/server/connectors/signal.ts +183 -0
  253. package/src/lib/server/connectors/slack.ts +258 -0
  254. package/src/lib/server/connectors/teams.ts +94 -0
  255. package/src/lib/server/connectors/telegram.ts +221 -0
  256. package/src/lib/server/connectors/types.ts +62 -0
  257. package/src/lib/server/connectors/whatsapp.ts +349 -0
  258. package/src/lib/server/context-manager.ts +232 -0
  259. package/src/lib/server/cost.ts +31 -0
  260. package/src/lib/server/daemon-state.ts +354 -0
  261. package/src/lib/server/data-dir.ts +3 -0
  262. package/src/lib/server/embeddings.ts +111 -0
  263. package/src/lib/server/execution-log.ts +257 -0
  264. package/src/lib/server/gateway/protocol.test.ts +54 -0
  265. package/src/lib/server/gateway/protocol.ts +114 -0
  266. package/src/lib/server/heartbeat-service.ts +366 -0
  267. package/src/lib/server/knowledge-db.test.ts +441 -0
  268. package/src/lib/server/logger.ts +47 -0
  269. package/src/lib/server/main-agent-loop.ts +1017 -0
  270. package/src/lib/server/mcp-client.test.ts +342 -0
  271. package/src/lib/server/mcp-client.ts +130 -0
  272. package/src/lib/server/memory-db.ts +1078 -0
  273. package/src/lib/server/memory-graph.test.ts +153 -0
  274. package/src/lib/server/memory-graph.ts +138 -0
  275. package/src/lib/server/openclaw-health.ts +245 -0
  276. package/src/lib/server/orchestrator-lg.ts +431 -0
  277. package/src/lib/server/orchestrator.ts +364 -0
  278. package/src/lib/server/playwright-proxy.mjs +70 -0
  279. package/src/lib/server/plugins.ts +229 -0
  280. package/src/lib/server/process-manager.ts +327 -0
  281. package/src/lib/server/provider-health.ts +113 -0
  282. package/src/lib/server/queue.ts +859 -0
  283. package/src/lib/server/runtime-settings.ts +119 -0
  284. package/src/lib/server/scheduler.ts +196 -0
  285. package/src/lib/server/session-mailbox.ts +129 -0
  286. package/src/lib/server/session-run-manager.ts +512 -0
  287. package/src/lib/server/session-tools/connector.ts +124 -0
  288. package/src/lib/server/session-tools/context-mgmt.ts +103 -0
  289. package/src/lib/server/session-tools/context.ts +114 -0
  290. package/src/lib/server/session-tools/crud.ts +673 -0
  291. package/src/lib/server/session-tools/delegate.ts +708 -0
  292. package/src/lib/server/session-tools/file.ts +264 -0
  293. package/src/lib/server/session-tools/index.ts +164 -0
  294. package/src/lib/server/session-tools/memory.ts +230 -0
  295. package/src/lib/server/session-tools/session-info.ts +422 -0
  296. package/src/lib/server/session-tools/session-tools-wiring.test.ts +166 -0
  297. package/src/lib/server/session-tools/shell.ts +171 -0
  298. package/src/lib/server/session-tools/web.ts +408 -0
  299. package/src/lib/server/session-tools.ts +9 -0
  300. package/src/lib/server/skills-normalize.ts +130 -0
  301. package/src/lib/server/storage-mcp.test.ts +161 -0
  302. package/src/lib/server/storage.ts +670 -0
  303. package/src/lib/server/stream-agent-chat.ts +571 -0
  304. package/src/lib/server/task-reports.ts +122 -0
  305. package/src/lib/server/task-result.ts +161 -0
  306. package/src/lib/server/task-validation.test.ts +27 -0
  307. package/src/lib/server/task-validation.ts +90 -0
  308. package/src/lib/server/tool-capability-policy.test.ts +58 -0
  309. package/src/lib/server/tool-capability-policy.ts +262 -0
  310. package/src/lib/sessions.ts +68 -0
  311. package/src/lib/tasks.ts +20 -0
  312. package/src/lib/tts.ts +42 -0
  313. package/src/lib/upload.ts +10 -0
  314. package/src/lib/utils.ts +6 -0
  315. package/src/proxy.ts +43 -0
  316. package/src/stores/use-app-store.ts +468 -0
  317. package/src/stores/use-chat-store.ts +323 -0
  318. package/src/types/index.ts +621 -0
  319. package/tsconfig.json +34 -0
@@ -0,0 +1,48 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { addKnowledge, searchKnowledge, listKnowledge } from '@/lib/server/memory-db'
3
+
4
+ export async function GET(req: Request) {
5
+ const { searchParams } = new URL(req.url)
6
+ const q = searchParams.get('q')
7
+ const tagsParam = searchParams.get('tags')
8
+ const limitParam = searchParams.get('limit')
9
+
10
+ const tags = tagsParam ? tagsParam.split(',').map((t) => t.trim()).filter(Boolean) : undefined
11
+ const limit = limitParam ? Math.max(1, Math.min(500, Number.parseInt(limitParam, 10) || 50)) : undefined
12
+
13
+ if (q) {
14
+ const results = searchKnowledge(q, tags, limit)
15
+ return NextResponse.json(results)
16
+ }
17
+
18
+ const entries = listKnowledge(tags, limit)
19
+ return NextResponse.json(entries)
20
+ }
21
+
22
+ export async function POST(req: Request) {
23
+ const body = await req.json().catch(() => null)
24
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
25
+ return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
26
+ }
27
+
28
+ const { title, content, tags } = body as Record<string, unknown>
29
+
30
+ if (typeof title !== 'string' || !title.trim()) {
31
+ return NextResponse.json({ error: 'title is required.' }, { status: 400 })
32
+ }
33
+ if (typeof content !== 'string') {
34
+ return NextResponse.json({ error: 'content is required.' }, { status: 400 })
35
+ }
36
+
37
+ const normalizedTags = Array.isArray(tags)
38
+ ? (tags as unknown[]).filter((t): t is string => typeof t === 'string' && t.trim().length > 0)
39
+ : undefined
40
+
41
+ const entry = addKnowledge({
42
+ title: title.trim(),
43
+ content,
44
+ tags: normalizedTags,
45
+ })
46
+
47
+ return NextResponse.json(entry)
48
+ }
@@ -0,0 +1,86 @@
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+ import crypto from 'crypto'
5
+ import { UPLOAD_DIR } from '@/lib/server/storage'
6
+
7
+ const TEXT_EXTS = new Set([
8
+ '.txt', '.md', '.markdown', '.csv', '.tsv', '.json', '.jsonl',
9
+ '.html', '.htm', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg',
10
+ '.js', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h',
11
+ '.rb', '.php', '.sh', '.bash', '.zsh', '.sql', '.r', '.swift', '.kt',
12
+ '.env', '.log', '.conf', '.properties', '.gitignore', '.dockerignore',
13
+ ])
14
+
15
+ function isTextFile(filename: string): boolean {
16
+ const ext = path.extname(filename).toLowerCase()
17
+ return TEXT_EXTS.has(ext) || ext === ''
18
+ }
19
+
20
+ function deriveTitle(filename: string): string {
21
+ const name = path.basename(filename, path.extname(filename))
22
+ return name
23
+ .replace(/[-_]+/g, ' ')
24
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
25
+ .replace(/\b\w/g, (c) => c.toUpperCase())
26
+ .trim() || 'Uploaded Document'
27
+ }
28
+
29
+ export async function POST(req: Request) {
30
+ const filename = req.headers.get('x-filename') || 'document.txt'
31
+ const buf = Buffer.from(await req.arrayBuffer())
32
+
33
+ if (buf.length === 0) {
34
+ return NextResponse.json({ error: 'Empty file.' }, { status: 400 })
35
+ }
36
+
37
+ if (buf.length > 10 * 1024 * 1024) {
38
+ return NextResponse.json({ error: 'File too large. Maximum 10MB.' }, { status: 400 })
39
+ }
40
+
41
+ // Save file to uploads
42
+ if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true })
43
+ const safeName = crypto.randomBytes(4).toString('hex') + '-' + filename.replace(/[^a-zA-Z0-9._-]/g, '_')
44
+ const filePath = path.join(UPLOAD_DIR, safeName)
45
+ fs.writeFileSync(filePath, buf)
46
+
47
+ // Extract text content
48
+ let content = ''
49
+ const ext = path.extname(filename).toLowerCase()
50
+
51
+ if (ext === '.pdf') {
52
+ // Try dynamic import of pdf-parse if available
53
+ try {
54
+ // @ts-ignore — pdf-parse is an optional dependency
55
+ const pdfParse = (await import(/* webpackIgnore: true */ 'pdf-parse')).default
56
+ const result = await pdfParse(buf)
57
+ content = result.text || ''
58
+ } catch {
59
+ // pdf-parse not installed — read as raw text fallback
60
+ content = '[PDF document — install pdf-parse for text extraction]\n\nFile saved at: ' + filePath
61
+ }
62
+ } else if (isTextFile(filename)) {
63
+ content = buf.toString('utf-8')
64
+ } else {
65
+ // Binary file — can't extract text
66
+ content = `[Binary file: ${filename}]\n\nFile saved at: ${filePath}`
67
+ }
68
+
69
+ // Truncate very long content to prevent memory issues
70
+ const MAX_CONTENT = 500_000
71
+ if (content.length > MAX_CONTENT) {
72
+ content = content.slice(0, MAX_CONTENT) + '\n\n[... truncated at 500k characters]'
73
+ }
74
+
75
+ const title = deriveTitle(filename)
76
+ const url = `/api/uploads/${safeName}`
77
+
78
+ return NextResponse.json({
79
+ title,
80
+ content,
81
+ filePath,
82
+ url,
83
+ filename,
84
+ size: buf.length,
85
+ })
86
+ }
@@ -0,0 +1,65 @@
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const LOG_FILE = path.join(process.cwd(), 'data', 'app.log')
6
+
7
+ export async function GET(req: Request) {
8
+ const { searchParams } = new URL(req.url)
9
+ const lines = parseInt(searchParams.get('lines') || '200', 10)
10
+ const level = searchParams.get('level') || '' // INFO, WARN, ERROR, DEBUG
11
+ const search = searchParams.get('search') || ''
12
+
13
+ try {
14
+ if (!fs.existsSync(LOG_FILE)) {
15
+ return NextResponse.json({ entries: [], total: 0 })
16
+ }
17
+
18
+ const content = fs.readFileSync(LOG_FILE, 'utf8')
19
+ let allLines = content.split('\n').filter(Boolean)
20
+
21
+ // Filter by level
22
+ if (level) {
23
+ const levels = level.split(',')
24
+ allLines = allLines.filter((l) => levels.some((lv) => l.includes(`[${lv}]`)))
25
+ }
26
+
27
+ // Filter by search term
28
+ if (search) {
29
+ const lower = search.toLowerCase()
30
+ allLines = allLines.filter((l) => l.toLowerCase().includes(lower))
31
+ }
32
+
33
+ const total = allLines.length
34
+ // Return most recent lines
35
+ const entries = allLines.slice(-lines).reverse().map(parseLine)
36
+
37
+ return NextResponse.json({ entries, total })
38
+ } catch (err: any) {
39
+ return NextResponse.json({ error: err.message }, { status: 500 })
40
+ }
41
+ }
42
+
43
+ export async function DELETE() {
44
+ try {
45
+ if (fs.existsSync(LOG_FILE)) {
46
+ fs.writeFileSync(LOG_FILE, '')
47
+ }
48
+ return NextResponse.json({ ok: true })
49
+ } catch (err: any) {
50
+ return NextResponse.json({ error: err.message }, { status: 500 })
51
+ }
52
+ }
53
+
54
+ function parseLine(line: string) {
55
+ // Format: [2026-02-19T17:06:00.000Z] [INFO] [tag] message | data
56
+ const match = line.match(/^\[([^\]]+)\]\s+\[(\w+)\]\s+\[([^\]]+)\]\s+(.*)$/)
57
+ if (!match) return { time: '', level: 'INFO', tag: '', message: line }
58
+
59
+ const [, time, level, tag, rest] = match
60
+ const pipeIdx = rest.indexOf(' | ')
61
+ const message = pipeIdx >= 0 ? rest.slice(0, pipeIdx) : rest
62
+ const data = pipeIdx >= 0 ? rest.slice(pipeIdx + 3) : undefined
63
+
64
+ return { time, level, tag, message, data }
65
+ }
@@ -0,0 +1,32 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadMcpServers, saveMcpServers, deleteMcpServer } from '@/lib/server/storage'
3
+
4
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ const { id } = await params
6
+ const servers = loadMcpServers()
7
+ if (!servers[id]) return new NextResponse(null, { status: 404 })
8
+ return NextResponse.json(servers[id])
9
+ }
10
+
11
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
12
+ const { id } = await params
13
+ const body = await req.json()
14
+ const servers = loadMcpServers()
15
+ if (!servers[id]) return new NextResponse(null, { status: 404 })
16
+ servers[id] = {
17
+ ...servers[id],
18
+ ...body,
19
+ id,
20
+ updatedAt: Date.now(),
21
+ }
22
+ saveMcpServers(servers)
23
+ return NextResponse.json(servers[id])
24
+ }
25
+
26
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
27
+ const { id } = await params
28
+ const servers = loadMcpServers()
29
+ if (!servers[id]) return new NextResponse(null, { status: 404 })
30
+ deleteMcpServer(id)
31
+ return NextResponse.json({ deleted: id })
32
+ }
@@ -0,0 +1,23 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadMcpServers } from '@/lib/server/storage'
3
+ import { connectMcpServer, mcpToolsToLangChain, disconnectMcpServer } from '@/lib/server/mcp-client'
4
+
5
+ export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ const { id } = await params
7
+ const servers = loadMcpServers()
8
+ const server = servers[id]
9
+ if (!server) return new NextResponse(null, { status: 404 })
10
+
11
+ try {
12
+ const { client, transport } = await connectMcpServer(server)
13
+ const tools = await mcpToolsToLangChain(client, server.name)
14
+ const toolNames = tools.map((t: any) => t.name)
15
+ await disconnectMcpServer(client, transport)
16
+ return NextResponse.json({ ok: true, tools: toolNames })
17
+ } catch (err: any) {
18
+ return NextResponse.json(
19
+ { ok: false, error: err.message || 'Connection failed' },
20
+ { status: 500 }
21
+ )
22
+ }
23
+ }
@@ -0,0 +1,32 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadMcpServers } from '@/lib/server/storage'
3
+ import { connectMcpServer, disconnectMcpServer } from '@/lib/server/mcp-client'
4
+
5
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ const { id } = await params
7
+ const servers = loadMcpServers()
8
+ const config = servers[id]
9
+ if (!config) return new NextResponse(null, { status: 404 })
10
+
11
+ let client: any
12
+ let transport: any
13
+ try {
14
+ const conn = await connectMcpServer(config)
15
+ client = conn.client
16
+ transport = conn.transport
17
+ const { tools } = await client.listTools()
18
+ return NextResponse.json(
19
+ tools.map((t: any) => ({
20
+ name: t.name,
21
+ description: t.description ?? '',
22
+ inputSchema: t.inputSchema ?? {},
23
+ }))
24
+ )
25
+ } catch (err: any) {
26
+ return NextResponse.json({ error: err.message }, { status: 502 })
27
+ } finally {
28
+ if (client && transport) {
29
+ await disconnectMcpServer(client, transport)
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,27 @@
1
+ import { NextResponse } from 'next/server'
2
+ import crypto from 'crypto'
3
+ import { loadMcpServers, saveMcpServers } from '@/lib/server/storage'
4
+
5
+ export async function GET() {
6
+ return NextResponse.json(loadMcpServers())
7
+ }
8
+
9
+ export async function POST(req: Request) {
10
+ const body = await req.json()
11
+ const servers = loadMcpServers()
12
+ const id = crypto.randomBytes(4).toString('hex')
13
+ servers[id] = {
14
+ id,
15
+ name: body.name,
16
+ transport: body.transport,
17
+ command: body.command,
18
+ args: body.args,
19
+ url: body.url,
20
+ env: body.env,
21
+ headers: body.headers,
22
+ createdAt: Date.now(),
23
+ updatedAt: Date.now(),
24
+ }
25
+ saveMcpServers(servers)
26
+ return NextResponse.json(servers[id])
27
+ }
@@ -0,0 +1,126 @@
1
+ import crypto from 'crypto'
2
+ import fs from 'fs'
3
+ import { NextResponse } from 'next/server'
4
+ import { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset, storeMemoryImageFromDataUrl } from '@/lib/server/memory-db'
5
+ import { resolveLookupRequest } from '@/lib/server/memory-graph'
6
+ import type { MemoryImage } from '@/types'
7
+
8
+ function parseOptionalInt(raw: string | null): number | undefined {
9
+ if (!raw) return undefined
10
+ const parsed = Number.parseInt(raw, 10)
11
+ return Number.isFinite(parsed) ? parsed : undefined
12
+ }
13
+
14
+ async function parseJsonBody(req: Request): Promise<Record<string, unknown> | null> {
15
+ const body = await req.json().catch(() => null)
16
+ if (!body || typeof body !== 'object' || Array.isArray(body)) return null
17
+ return body as Record<string, unknown>
18
+ }
19
+
20
+ function parseTargetIds(value: unknown): string[] {
21
+ if (!Array.isArray(value)) return []
22
+ return value
23
+ .filter((item): item is string => typeof item === 'string')
24
+ .map((item) => item.trim())
25
+ .filter(Boolean)
26
+ }
27
+
28
+ export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
29
+ const { id } = await params
30
+ const { searchParams } = new URL(req.url)
31
+ const envelope = searchParams.get('envelope') === 'true'
32
+ const requestedDepth = parseOptionalInt(searchParams.get('depth'))
33
+ const requestedLimit = parseOptionalInt(searchParams.get('limit'))
34
+ const requestedLinkedLimit = parseOptionalInt(searchParams.get('linkedLimit'))
35
+ const db = getMemoryDb()
36
+ const defaults = getMemoryLookupLimits()
37
+ const limits = resolveLookupRequest(defaults, {
38
+ depth: requestedDepth,
39
+ limit: requestedLimit,
40
+ linkedLimit: requestedLinkedLimit,
41
+ })
42
+
43
+ if (limits.maxDepth <= 0) {
44
+ const entry = db.get(id)
45
+ if (!entry) return new NextResponse(null, { status: 404 })
46
+ if (envelope) {
47
+ return NextResponse.json({
48
+ entries: [entry],
49
+ truncated: false,
50
+ expandedLinkedCount: 0,
51
+ limits,
52
+ })
53
+ }
54
+ return NextResponse.json(entry)
55
+ }
56
+
57
+ const result = db.getWithLinked(id, limits.maxDepth, limits.maxPerLookup, limits.maxLinkedExpansion)
58
+ if (!result) return new NextResponse(null, { status: 404 })
59
+ if (envelope) return NextResponse.json(result)
60
+ return NextResponse.json(result.entries)
61
+ }
62
+
63
+ export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
64
+ const { id } = await params
65
+ const body = await parseJsonBody(req)
66
+ if (!body) {
67
+ return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
68
+ }
69
+
70
+ const db = getMemoryDb()
71
+ const linkAction = typeof body.linkAction === 'string' ? body.linkAction.trim().toLowerCase() : ''
72
+
73
+ if (linkAction === 'link' || linkAction === 'unlink') {
74
+ const targetIds = parseTargetIds(body.targetIds)
75
+ if (!targetIds.length) {
76
+ return NextResponse.json({ error: 'targetIds is required for linkAction.' }, { status: 400 })
77
+ }
78
+ const updated = linkAction === 'link'
79
+ ? db.link(id, targetIds, true)
80
+ : db.unlink(id, targetIds, true)
81
+ if (!updated) return new NextResponse(null, { status: 404 })
82
+ return NextResponse.json(updated)
83
+ }
84
+
85
+ let image = body.image
86
+ const inputImagePath = typeof body.imagePath === 'string' ? body.imagePath.trim() : ''
87
+ const inputImageDataUrl = typeof body.imageDataUrl === 'string' ? body.imageDataUrl.trim() : ''
88
+ const clearImage = body.clearImage === true || body.image === null
89
+ if (clearImage) {
90
+ image = null
91
+ } else if (inputImageDataUrl) {
92
+ try {
93
+ image = await storeMemoryImageFromDataUrl(inputImageDataUrl, `${id}-${crypto.randomBytes(2).toString('hex')}`)
94
+ } catch (err) {
95
+ return NextResponse.json({ error: err instanceof Error ? err.message : 'Invalid image data URL' }, { status: 400 })
96
+ }
97
+ } else if (inputImagePath) {
98
+ if (!fs.existsSync(inputImagePath)) {
99
+ return NextResponse.json({ error: `Image file not found: ${inputImagePath}` }, { status: 400 })
100
+ }
101
+ try {
102
+ image = await storeMemoryImageAsset(inputImagePath, `${id}-${crypto.randomBytes(2).toString('hex')}`)
103
+ } catch (err) {
104
+ return NextResponse.json({ error: err instanceof Error ? err.message : 'Failed to store memory image' }, { status: 400 })
105
+ }
106
+ }
107
+
108
+ const entry = db.update(id, {
109
+ ...body,
110
+ image: image as MemoryImage | null | undefined,
111
+ imagePath: clearImage
112
+ ? null
113
+ : image && typeof image === 'object' && 'path' in image
114
+ ? String((image as { path: string }).path)
115
+ : (typeof body.imagePath === 'string' ? body.imagePath : undefined),
116
+ })
117
+ if (!entry) return new NextResponse(null, { status: 404 })
118
+ return NextResponse.json(entry)
119
+ }
120
+
121
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
122
+ const { id } = await params
123
+ const db = getMemoryDb()
124
+ db.delete(id)
125
+ return NextResponse.json('ok')
126
+ }
@@ -0,0 +1,63 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getMemoryDb } from '@/lib/server/memory-db'
3
+ import { loadSettings } from '@/lib/server/storage'
4
+
5
+ function parseBool(value: unknown, fallback: boolean): boolean {
6
+ if (typeof value === 'boolean') return value
7
+ if (typeof value === 'string') {
8
+ const v = value.trim().toLowerCase()
9
+ if (v === 'true' || v === '1' || v === 'yes') return true
10
+ if (v === 'false' || v === '0' || v === 'no') return false
11
+ }
12
+ return fallback
13
+ }
14
+
15
+ function parseIntBounded(value: unknown, fallback: number, min: number, max: number): number {
16
+ const parsed = typeof value === 'number'
17
+ ? value
18
+ : typeof value === 'string'
19
+ ? Number.parseInt(value, 10)
20
+ : Number.NaN
21
+ if (!Number.isFinite(parsed)) return fallback
22
+ return Math.max(min, Math.min(max, Math.trunc(parsed)))
23
+ }
24
+
25
+ export async function GET(req: Request) {
26
+ const db = getMemoryDb()
27
+ const settings = loadSettings()
28
+ const { searchParams } = new URL(req.url)
29
+ const ttlHours = parseIntBounded(
30
+ searchParams.get('ttlHours') ?? settings.memoryWorkingTtlHours,
31
+ 24,
32
+ 1,
33
+ 24 * 365,
34
+ )
35
+ const analyzed = db.analyzeMaintenance(ttlHours)
36
+ return NextResponse.json({
37
+ ok: true,
38
+ ttlHours,
39
+ analyzed,
40
+ })
41
+ }
42
+
43
+ export async function POST(req: Request) {
44
+ const body = await req.json().catch(() => ({}))
45
+ const settings = loadSettings()
46
+ const db = getMemoryDb()
47
+ const ttlHours = parseIntBounded(body?.ttlHours ?? settings.memoryWorkingTtlHours, 24, 1, 24 * 365)
48
+ const maxDeletes = parseIntBounded(body?.maxDeletes, 500, 1, 20_000)
49
+ const result = db.maintain({
50
+ ttlHours,
51
+ maxDeletes,
52
+ dedupe: parseBool(body?.dedupe, true),
53
+ canonicalDedupe: parseBool(body?.canonicalDedupe, false),
54
+ pruneWorking: parseBool(body?.pruneWorking, true),
55
+ })
56
+ return NextResponse.json({
57
+ ok: true,
58
+ ttlHours,
59
+ maxDeletes,
60
+ ...result,
61
+ })
62
+ }
63
+
@@ -0,0 +1,111 @@
1
+ import crypto from 'crypto'
2
+ import fs from 'fs'
3
+ import { NextResponse } from 'next/server'
4
+ import { getMemoryDb, getMemoryLookupLimits, storeMemoryImageAsset, storeMemoryImageFromDataUrl } from '@/lib/server/memory-db'
5
+ import { resolveLookupRequest } from '@/lib/server/memory-graph'
6
+ import type { MemoryReference, FileReference, MemoryImage } from '@/types'
7
+
8
+ function parseOptionalInt(raw: string | null): number | undefined {
9
+ if (!raw) return undefined
10
+ const parsed = Number.parseInt(raw, 10)
11
+ return Number.isFinite(parsed) ? parsed : undefined
12
+ }
13
+
14
+ async function parseJsonBody(req: Request): Promise<Record<string, unknown> | null> {
15
+ const body = await req.json().catch(() => null)
16
+ if (!body || typeof body !== 'object' || Array.isArray(body)) return null
17
+ return body as Record<string, unknown>
18
+ }
19
+
20
+ export async function GET(req: Request) {
21
+ const { searchParams } = new URL(req.url)
22
+ const q = searchParams.get('q')
23
+ const agentId = searchParams.get('agentId')
24
+ const envelope = searchParams.get('envelope') === 'true'
25
+ const requestedDepth = parseOptionalInt(searchParams.get('depth'))
26
+ const requestedLimit = parseOptionalInt(searchParams.get('limit'))
27
+ const requestedLinkedLimit = parseOptionalInt(searchParams.get('linkedLimit'))
28
+
29
+ const db = getMemoryDb()
30
+ const defaults = getMemoryLookupLimits()
31
+ const limits = resolveLookupRequest(defaults, {
32
+ depth: requestedDepth,
33
+ limit: requestedLimit,
34
+ linkedLimit: requestedLinkedLimit,
35
+ })
36
+
37
+ if (q) {
38
+ if (limits.maxDepth > 0) {
39
+ const result = db.searchWithLinked(q, agentId || undefined, limits.maxDepth, limits.maxPerLookup, limits.maxLinkedExpansion)
40
+ if (envelope) return NextResponse.json(result)
41
+ return NextResponse.json(result.entries)
42
+ }
43
+ const base = db.search(q, agentId || undefined)
44
+ const entries = base.slice(0, limits.maxPerLookup)
45
+ if (envelope) {
46
+ return NextResponse.json({
47
+ entries,
48
+ truncated: base.length > entries.length,
49
+ expandedLinkedCount: 0,
50
+ limits,
51
+ })
52
+ }
53
+ return NextResponse.json(entries)
54
+ }
55
+
56
+ const entries = db.list(agentId || undefined, limits.maxPerLookup)
57
+ if (envelope) {
58
+ return NextResponse.json({
59
+ entries,
60
+ truncated: false,
61
+ expandedLinkedCount: 0,
62
+ limits,
63
+ })
64
+ }
65
+ return NextResponse.json(entries)
66
+ }
67
+
68
+ export async function POST(req: Request) {
69
+ const body = await parseJsonBody(req)
70
+ if (!body) {
71
+ return NextResponse.json({ error: 'Invalid JSON body.' }, { status: 400 })
72
+ }
73
+
74
+ const db = getMemoryDb()
75
+ const draftId = crypto.randomBytes(6).toString('hex')
76
+
77
+ let image = body.image
78
+ const inputImagePath = typeof body.imagePath === 'string' ? body.imagePath.trim() : ''
79
+ const inputImageDataUrl = typeof body.imageDataUrl === 'string' ? body.imageDataUrl.trim() : ''
80
+ if (inputImageDataUrl) {
81
+ try {
82
+ image = await storeMemoryImageFromDataUrl(inputImageDataUrl, draftId)
83
+ } catch (err) {
84
+ return NextResponse.json({ error: err instanceof Error ? err.message : 'Invalid image data URL' }, { status: 400 })
85
+ }
86
+ } else if (inputImagePath) {
87
+ if (!fs.existsSync(inputImagePath)) {
88
+ return NextResponse.json({ error: `Image file not found: ${inputImagePath}` }, { status: 400 })
89
+ }
90
+ try {
91
+ image = await storeMemoryImageAsset(inputImagePath, draftId)
92
+ } catch (err) {
93
+ return NextResponse.json({ error: err instanceof Error ? err.message : 'Failed to store memory image' }, { status: 400 })
94
+ }
95
+ }
96
+
97
+ const entry = db.add({
98
+ agentId: typeof body.agentId === 'string' ? body.agentId : null,
99
+ sessionId: typeof body.sessionId === 'string' ? body.sessionId : null,
100
+ category: typeof body.category === 'string' && body.category.trim() ? body.category : 'note',
101
+ title: typeof body.title === 'string' && body.title.trim() ? body.title : 'Untitled',
102
+ content: typeof body.content === 'string' ? body.content : '',
103
+ metadata: body.metadata as Record<string, unknown> | undefined,
104
+ references: body.references as MemoryReference[] | undefined,
105
+ filePaths: body.filePaths as FileReference[] | undefined,
106
+ image: image as MemoryImage | null | undefined,
107
+ imagePath: image && typeof image === 'object' && 'path' in image ? String((image as { path: string }).path) : null,
108
+ linkedMemoryIds: body.linkedMemoryIds as string[] | undefined,
109
+ })
110
+ return NextResponse.json(entry)
111
+ }
@@ -0,0 +1,36 @@
1
+ import { NextResponse } from 'next/server'
2
+ import fs from 'fs'
3
+ import path from 'path'
4
+
5
+ const IMAGES_DIR = path.join(process.cwd(), 'data', 'memory-images')
6
+
7
+ const MIME_TYPES: Record<string, string> = {
8
+ '.png': 'image/png',
9
+ '.jpg': 'image/jpeg',
10
+ '.jpeg': 'image/jpeg',
11
+ '.gif': 'image/gif',
12
+ '.webp': 'image/webp',
13
+ '.bmp': 'image/bmp',
14
+ '.tiff': 'image/tiff',
15
+ }
16
+
17
+ export async function GET(_req: Request, { params }: { params: Promise<{ filename: string }> }) {
18
+ const { filename } = await params
19
+ const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, '')
20
+ const filePath = path.join(IMAGES_DIR, safeName)
21
+
22
+ if (!fs.existsSync(filePath)) {
23
+ return new NextResponse(null, { status: 404 })
24
+ }
25
+
26
+ const ext = path.extname(safeName).toLowerCase()
27
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream'
28
+ const data = fs.readFileSync(filePath)
29
+
30
+ return new NextResponse(data, {
31
+ headers: {
32
+ 'Content-Type': contentType,
33
+ 'Cache-Control': 'public, max-age=86400',
34
+ },
35
+ })
36
+ }