@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,122 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState, useRef } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { api } from '@/lib/api-client'
6
+
7
+ const transportColors: Record<string, string> = {
8
+ stdio: 'bg-emerald-500/15 text-emerald-400',
9
+ sse: 'bg-blue-500/15 text-blue-400',
10
+ 'streamable-http': 'bg-purple-500/15 text-purple-400',
11
+ }
12
+
13
+ type McpStatus = { ok: boolean; tools?: string[]; error?: string; loading: boolean }
14
+
15
+ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
16
+ const mcpServers = useAppStore((s) => s.mcpServers)
17
+ const loadMcpServers = useAppStore((s) => s.loadMcpServers)
18
+ const setMcpServerSheetOpen = useAppStore((s) => s.setMcpServerSheetOpen)
19
+ const setEditingMcpServerId = useAppStore((s) => s.setEditingMcpServerId)
20
+ const [statuses, setStatuses] = useState<Record<string, McpStatus>>({})
21
+ const timersRef = useRef<ReturnType<typeof setTimeout>[]>([])
22
+
23
+ useEffect(() => {
24
+ loadMcpServers()
25
+ }, [loadMcpServers])
26
+
27
+ const serverList = Object.values(mcpServers)
28
+
29
+ // Staggered status tests on mount
30
+ useEffect(() => {
31
+ timersRef.current.forEach(clearTimeout)
32
+ timersRef.current = []
33
+
34
+ serverList.forEach((server, i) => {
35
+ setStatuses((prev) => ({ ...prev, [server.id]: { ok: false, loading: true } }))
36
+ const timer = setTimeout(async () => {
37
+ try {
38
+ const res = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${server.id}/test`)
39
+ setStatuses((prev) => ({ ...prev, [server.id]: { ok: res.ok, tools: res.tools, error: res.error, loading: false } }))
40
+ } catch {
41
+ setStatuses((prev) => ({ ...prev, [server.id]: { ok: false, error: 'Test failed', loading: false } }))
42
+ }
43
+ }, i * 200)
44
+ timersRef.current.push(timer)
45
+ })
46
+
47
+ return () => timersRef.current.forEach(clearTimeout)
48
+ // eslint-disable-next-line react-hooks/exhaustive-deps
49
+ }, [mcpServers])
50
+
51
+ const handleEdit = (id: string) => {
52
+ setEditingMcpServerId(id)
53
+ setMcpServerSheetOpen(true)
54
+ }
55
+
56
+ const handleDelete = async (e: React.MouseEvent, id: string) => {
57
+ e.stopPropagation()
58
+ await api('DELETE', `/mcp-servers/${id}`)
59
+ await loadMcpServers()
60
+ }
61
+
62
+ return (
63
+ <div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
64
+ {serverList.length === 0 ? (
65
+ <div className="text-center py-12">
66
+ <p className="text-[13px] text-text-3/60">No MCP servers configured</p>
67
+ <button
68
+ onClick={() => { setEditingMcpServerId(null); setMcpServerSheetOpen(true) }}
69
+ className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[13px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
70
+ style={{ fontFamily: 'inherit' }}
71
+ >
72
+ + Add MCP Server
73
+ </button>
74
+ </div>
75
+ ) : (
76
+ <div className="space-y-2">
77
+ {serverList.map((server) => (
78
+ <button
79
+ key={server.id}
80
+ onClick={() => handleEdit(server.id)}
81
+ className="w-full text-left p-4 rounded-[14px] border border-white/[0.06] bg-surface hover:bg-surface-2 transition-all cursor-pointer"
82
+ >
83
+ <div className="flex items-center justify-between mb-1">
84
+ <div className="flex items-center gap-2 min-w-0">
85
+ {(() => {
86
+ const s = statuses[server.id]
87
+ if (!s || s.loading) return <span className="w-2 h-2 rounded-full bg-yellow-400 animate-pulse shrink-0" title="Testing..." />
88
+ if (s.ok) return (
89
+ <span className="flex items-center gap-1 shrink-0">
90
+ <span className="w-2 h-2 rounded-full bg-emerald-400 shrink-0" />
91
+ {s.tools && <span className="text-[10px] text-emerald-400/80 font-mono">{s.tools.length} tools</span>}
92
+ </span>
93
+ )
94
+ return <span className="w-2 h-2 rounded-full bg-red-400 shrink-0" title={s.error || 'Failed'} />
95
+ })()}
96
+ <span className="font-display text-[14px] font-600 text-text truncate">{server.name}</span>
97
+ </div>
98
+ <div className="flex items-center gap-2 shrink-0 ml-2">
99
+ <span className={`text-[10px] font-mono px-2 py-0.5 rounded-full ${transportColors[server.transport] || 'bg-white/10 text-text-3'}`}>
100
+ {server.transport}
101
+ </span>
102
+ <button
103
+ onClick={(e) => handleDelete(e, server.id)}
104
+ className="text-text-3/40 hover:text-red-400 transition-colors p-0.5"
105
+ title="Delete server"
106
+ >
107
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
108
+ <path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
109
+ </svg>
110
+ </button>
111
+ </div>
112
+ </div>
113
+ <p className="text-[12px] text-text-3/60 font-mono truncate">
114
+ {server.transport === 'stdio' ? server.command : server.url}
115
+ </p>
116
+ </button>
117
+ ))}
118
+ </div>
119
+ )}
120
+ </div>
121
+ )
122
+ }
@@ -0,0 +1,243 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { BottomSheet } from '@/components/shared/bottom-sheet'
6
+ import { api } from '@/lib/api-client'
7
+ import type { McpServerConfig, McpTransport } from '@/types'
8
+
9
+ function McpServerForm({ editing, onClose, loadMcpServers }: {
10
+ editing: McpServerConfig | null
11
+ onClose: () => void
12
+ loadMcpServers: () => Promise<void>
13
+ }) {
14
+ const [name, setName] = useState(editing?.name || '')
15
+ const [transport, setTransport] = useState<McpTransport>(editing?.transport || 'stdio')
16
+ const [command, setCommand] = useState(editing?.command || '')
17
+ const [args, setArgs] = useState(editing?.args?.join(', ') || '')
18
+ const [url, setUrl] = useState(editing?.url || '')
19
+ const [envText, setEnvText] = useState(
20
+ editing?.env ? Object.entries(editing.env).map(([k, v]) => `${k}=${v}`).join('\n') : '',
21
+ )
22
+ const [headersText, setHeadersText] = useState(
23
+ editing?.headers ? Object.entries(editing.headers).map(([k, v]) => `${k}: ${v}`).join('\n') : '',
24
+ )
25
+ const [testing, setTesting] = useState(false)
26
+ const [testResult, setTestResult] = useState<{ ok: boolean; tools?: string[]; error?: string } | null>(null)
27
+
28
+ const parseEnv = (text: string): Record<string, string> | undefined => {
29
+ if (!text.trim()) return undefined
30
+ const env: Record<string, string> = {}
31
+ for (const line of text.split('\n')) {
32
+ const idx = line.indexOf('=')
33
+ if (idx > 0) env[line.slice(0, idx).trim()] = line.slice(idx + 1).trim()
34
+ }
35
+ return Object.keys(env).length > 0 ? env : undefined
36
+ }
37
+
38
+ const parseHeaders = (text: string): Record<string, string> | undefined => {
39
+ if (!text.trim()) return undefined
40
+ const headers: Record<string, string> = {}
41
+ for (const line of text.split('\n')) {
42
+ const idx = line.indexOf(':')
43
+ if (idx > 0) headers[line.slice(0, idx).trim()] = line.slice(idx + 1).trim()
44
+ }
45
+ return Object.keys(headers).length > 0 ? headers : undefined
46
+ }
47
+
48
+ const handleSave = async () => {
49
+ const data: Record<string, unknown> = {
50
+ name: name.trim() || 'Unnamed Server',
51
+ transport,
52
+ env: parseEnv(envText),
53
+ headers: parseHeaders(headersText),
54
+ }
55
+ if (transport === 'stdio') {
56
+ data.command = command.trim()
57
+ data.args = args.trim() ? args.split(',').map((a) => a.trim()).filter(Boolean) : []
58
+ } else {
59
+ data.url = url.trim()
60
+ }
61
+ if (editing) {
62
+ await api('PUT', `/mcp-servers/${editing.id}`, data)
63
+ } else {
64
+ await api('POST', '/mcp-servers', data)
65
+ }
66
+ await loadMcpServers()
67
+ onClose()
68
+ }
69
+
70
+ const handleDelete = async () => {
71
+ if (editing) {
72
+ await api('DELETE', `/mcp-servers/${editing.id}`)
73
+ await loadMcpServers()
74
+ onClose()
75
+ }
76
+ }
77
+
78
+ const handleTest = async () => {
79
+ if (!editing) return
80
+ setTesting(true)
81
+ setTestResult(null)
82
+ try {
83
+ const result = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${editing.id}/test`)
84
+ setTestResult(result)
85
+ } catch (err: unknown) {
86
+ setTestResult({ ok: false, error: err instanceof Error ? err.message : 'Test failed' })
87
+ }
88
+ setTesting(false)
89
+ }
90
+
91
+ const canSave = name.trim() && (transport === 'stdio' ? command.trim() : url.trim())
92
+
93
+ const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
94
+ const labelClass = "block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3"
95
+
96
+ return (
97
+ <>
98
+ <div className="mb-10">
99
+ <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
100
+ {editing ? 'Edit MCP Server' : 'New MCP Server'}
101
+ </h2>
102
+ <p className="text-[14px] text-text-3">Configure an MCP server to provide tools to agents</p>
103
+ </div>
104
+
105
+ <div className="mb-8">
106
+ <label className={labelClass}>Name</label>
107
+ <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Filesystem Server" className={inputClass} style={{ fontFamily: 'inherit' }} />
108
+ </div>
109
+
110
+ <div className="mb-8">
111
+ <label className={labelClass}>Transport</label>
112
+ <select
113
+ value={transport}
114
+ onChange={(e) => setTransport(e.target.value as McpTransport)}
115
+ className={inputClass}
116
+ style={{ fontFamily: 'inherit' }}
117
+ >
118
+ <option value="stdio">stdio</option>
119
+ <option value="sse">sse</option>
120
+ <option value="streamable-http">streamable-http</option>
121
+ </select>
122
+ </div>
123
+
124
+ {transport === 'stdio' ? (
125
+ <>
126
+ <div className="mb-8">
127
+ <label className={labelClass}>Command</label>
128
+ <input type="text" value={command} onChange={(e) => setCommand(e.target.value)} placeholder="e.g. npx -y @modelcontextprotocol/server-filesystem" className={inputClass} style={{ fontFamily: 'inherit' }} />
129
+ </div>
130
+ <div className="mb-8">
131
+ <label className={labelClass}>
132
+ Arguments <span className="normal-case tracking-normal font-normal text-text-3">(comma-separated)</span>
133
+ </label>
134
+ <input type="text" value={args} onChange={(e) => setArgs(e.target.value)} placeholder="e.g. /path/to/dir, --verbose" className={inputClass} style={{ fontFamily: 'inherit' }} />
135
+ </div>
136
+ </>
137
+ ) : (
138
+ <div className="mb-8">
139
+ <label className={labelClass}>URL</label>
140
+ <input type="text" value={url} onChange={(e) => setUrl(e.target.value)} placeholder="e.g. http://localhost:8080/sse" className={inputClass} style={{ fontFamily: 'inherit' }} />
141
+ </div>
142
+ )}
143
+
144
+ <div className="mb-8">
145
+ <label className={labelClass}>
146
+ Environment Variables <span className="normal-case tracking-normal font-normal text-text-3">(optional, KEY=VALUE per line)</span>
147
+ </label>
148
+ <textarea
149
+ value={envText}
150
+ onChange={(e) => setEnvText(e.target.value)}
151
+ placeholder={"API_KEY=sk-...\nDEBUG=true"}
152
+ rows={3}
153
+ className={`${inputClass} resize-y min-h-[80px] font-mono text-[13px]`}
154
+ style={{ fontFamily: 'inherit' }}
155
+ />
156
+ </div>
157
+
158
+ {transport !== 'stdio' && (
159
+ <div className="mb-8">
160
+ <label className={labelClass}>
161
+ Headers <span className="normal-case tracking-normal font-normal text-text-3">(optional, Key: Value per line)</span>
162
+ </label>
163
+ <textarea
164
+ value={headersText}
165
+ onChange={(e) => setHeadersText(e.target.value)}
166
+ placeholder={"Authorization: Bearer sk-...\nX-Custom: value"}
167
+ rows={3}
168
+ className={`${inputClass} resize-y min-h-[80px] font-mono text-[13px]`}
169
+ style={{ fontFamily: 'inherit' }}
170
+ />
171
+ </div>
172
+ )}
173
+
174
+ {editing && (
175
+ <div className="mb-8">
176
+ <button
177
+ onClick={handleTest}
178
+ disabled={testing}
179
+ className="py-3 px-6 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] font-600 cursor-pointer hover:bg-surface-2 transition-all disabled:opacity-30"
180
+ style={{ fontFamily: 'inherit' }}
181
+ >
182
+ {testing ? 'Testing...' : 'Test Connection'}
183
+ </button>
184
+ {testResult && (
185
+ <div className={`mt-3 p-3 rounded-[10px] text-[13px] ${testResult.ok ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'}`}>
186
+ {testResult.ok ? (
187
+ <>
188
+ Connected successfully.{' '}
189
+ {testResult.tools && testResult.tools.length > 0 && (
190
+ <span className="text-text-3">{testResult.tools.length} tool{testResult.tools.length !== 1 ? 's' : ''} available: {testResult.tools.join(', ')}</span>
191
+ )}
192
+ </>
193
+ ) : (
194
+ <span>{testResult.error || 'Connection failed'}</span>
195
+ )}
196
+ </div>
197
+ )}
198
+ </div>
199
+ )}
200
+
201
+ <div className="flex gap-3 pt-2 border-t border-white/[0.04]">
202
+ {editing && (
203
+ <button onClick={handleDelete} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
204
+ Delete
205
+ </button>
206
+ )}
207
+ <button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
208
+ Cancel
209
+ </button>
210
+ <button onClick={handleSave} disabled={!canSave} className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110" style={{ fontFamily: 'inherit' }}>
211
+ {editing ? 'Save' : 'Create'}
212
+ </button>
213
+ </div>
214
+ </>
215
+ )
216
+ }
217
+
218
+ export function McpServerSheet() {
219
+ const open = useAppStore((s) => s.mcpServerSheetOpen)
220
+ const setOpen = useAppStore((s) => s.setMcpServerSheetOpen)
221
+ const editingId = useAppStore((s) => s.editingMcpServerId)
222
+ const setEditingId = useAppStore((s) => s.setEditingMcpServerId)
223
+ const mcpServers = useAppStore((s) => s.mcpServers)
224
+ const loadMcpServers = useAppStore((s) => s.loadMcpServers)
225
+
226
+ const editing = editingId ? mcpServers[editingId] : null
227
+
228
+ const onClose = () => {
229
+ setOpen(false)
230
+ setEditingId(null)
231
+ }
232
+
233
+ return (
234
+ <BottomSheet open={open} onClose={onClose} wide>
235
+ <McpServerForm
236
+ key={editingId || '__new__'}
237
+ editing={editing}
238
+ onClose={onClose}
239
+ loadMcpServers={loadMcpServers}
240
+ />
241
+ </BottomSheet>
242
+ )
243
+ }
@@ -0,0 +1,63 @@
1
+ 'use client'
2
+
3
+ import type { MemoryEntry } from '@/types'
4
+
5
+ function timeAgo(ts: number): string {
6
+ if (!ts) return ''
7
+ const s = Math.floor((Date.now() - ts) / 1000)
8
+ if (s < 60) return 'now'
9
+ if (s < 3600) return Math.floor(s / 60) + 'm'
10
+ if (s < 86400) return Math.floor(s / 3600) + 'h'
11
+ return Math.floor(s / 86400) + 'd'
12
+ }
13
+
14
+ interface Props {
15
+ entry: MemoryEntry
16
+ active?: boolean
17
+ agentName?: string | null
18
+ onClick: () => void
19
+ }
20
+
21
+ export function MemoryCard({ entry, active, agentName, onClick }: Props) {
22
+ return (
23
+ <div
24
+ onClick={onClick}
25
+ className={`relative py-3 px-4 cursor-pointer rounded-[14px]
26
+ transition-all duration-200 active:scale-[0.98]
27
+ ${active
28
+ ? 'bg-accent-soft border border-accent-bright/10'
29
+ : 'bg-transparent border border-transparent hover:bg-white/[0.02] hover:border-white/[0.03]'}`}
30
+ >
31
+ {active && (
32
+ <div className="absolute left-0 top-3 bottom-3 w-[2.5px] rounded-full bg-accent-bright" />
33
+ )}
34
+ <div className="flex items-center gap-2">
35
+ <span className="shrink-0 text-[9px] font-700 uppercase tracking-wider text-accent-bright/70 bg-accent-soft px-1.5 py-0.5 rounded-[5px]">
36
+ {entry.category || 'note'}
37
+ </span>
38
+ <span className="font-display text-[13px] font-600 truncate flex-1 tracking-[-0.01em]">{entry.title}</span>
39
+ <span className="text-[10px] text-text-3/60 shrink-0 tabular-nums font-mono">
40
+ {timeAgo(entry.updatedAt || entry.createdAt)}
41
+ </span>
42
+ </div>
43
+ <div className="text-[12px] text-text-2/40 mt-1 truncate leading-relaxed">
44
+ {entry.content || '(empty)'}
45
+ </div>
46
+ {(entry.references?.length || entry.linkedMemoryIds?.length || entry.image?.path || entry.imagePath) && (
47
+ <div className="flex items-center gap-2 mt-1.5 text-[10px] text-text-3/35">
48
+ {entry.references?.length ? <span>{entry.references.length} ref{entry.references.length === 1 ? '' : 's'}</span> : null}
49
+ {entry.linkedMemoryIds?.length ? <span>{entry.linkedMemoryIds.length} linked</span> : null}
50
+ {(entry.image?.path || entry.imagePath) ? <span>image</span> : null}
51
+ </div>
52
+ )}
53
+ {agentName && (
54
+ <div className="flex items-center gap-1 mt-1.5">
55
+ <svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-text-3/50">
56
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
57
+ </svg>
58
+ <span className="text-[10px] text-text-3/60 truncate">{agentName}</span>
59
+ </div>
60
+ )}
61
+ </div>
62
+ )
63
+ }