@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,1016 @@
1
+ 'use client'
2
+
3
+ import { Component, useState, useEffect, useCallback } from 'react'
4
+ import type { ReactNode, ErrorInfo } from 'react'
5
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
6
+ import { useAppStore } from '@/stores/use-app-store'
7
+ import { useMediaQuery } from '@/hooks/use-media-query'
8
+ import { Avatar } from '@/components/shared/avatar'
9
+ import { SessionList } from '@/components/sessions/session-list'
10
+ import { NewSessionSheet } from '@/components/sessions/new-session-sheet'
11
+ import { SettingsSheet } from '@/components/shared/settings-sheet'
12
+ import { AgentList } from '@/components/agents/agent-list'
13
+ import { AgentChatList } from '@/components/agents/agent-chat-list'
14
+ import { AgentSheet } from '@/components/agents/agent-sheet'
15
+ import { ScheduleList } from '@/components/schedules/schedule-list'
16
+ import { ScheduleSheet } from '@/components/schedules/schedule-sheet'
17
+ import { MemoryList } from '@/components/memory/memory-list'
18
+ import { MemorySheet } from '@/components/memory/memory-sheet'
19
+ import { MemoryDetail } from '@/components/memory/memory-detail'
20
+ import { TaskList } from '@/components/tasks/task-list'
21
+ import { TaskSheet } from '@/components/tasks/task-sheet'
22
+ import { TaskBoard } from '@/components/tasks/task-board'
23
+ import { SecretsList } from '@/components/secrets/secrets-list'
24
+ import { SecretSheet } from '@/components/secrets/secret-sheet'
25
+ import { ProviderList } from '@/components/providers/provider-list'
26
+ import { ProviderSheet } from '@/components/providers/provider-sheet'
27
+ import { SkillList } from '@/components/skills/skill-list'
28
+ import { SkillSheet } from '@/components/skills/skill-sheet'
29
+ import { ConnectorList } from '@/components/connectors/connector-list'
30
+ import { ConnectorSheet } from '@/components/connectors/connector-sheet'
31
+ import { WebhookList } from '@/components/webhooks/webhook-list'
32
+ import { WebhookSheet } from '@/components/webhooks/webhook-sheet'
33
+ import { LogList } from '@/components/logs/log-list'
34
+ import { McpServerList } from '@/components/mcp-servers/mcp-server-list'
35
+ import { McpServerSheet } from '@/components/mcp-servers/mcp-server-sheet'
36
+ import { KnowledgeList } from '@/components/knowledge/knowledge-list'
37
+ import { KnowledgeSheet } from '@/components/knowledge/knowledge-sheet'
38
+ import { PluginList } from '@/components/plugins/plugin-list'
39
+ import { PluginSheet } from '@/components/plugins/plugin-sheet'
40
+ import { UsageList } from '@/components/usage/usage-list'
41
+ import { RunList } from '@/components/runs/run-list'
42
+ import { NetworkBanner } from './network-banner'
43
+ import { UpdateBanner } from './update-banner'
44
+ import { MobileHeader } from './mobile-header'
45
+ import { DaemonIndicator } from './daemon-indicator'
46
+ import { ChatArea } from '@/components/chat/chat-area'
47
+ import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
48
+ import type { AppView } from '@/types'
49
+
50
+ const RAIL_EXPANDED_KEY = 'sc_rail_expanded'
51
+
52
+ export function AppLayout() {
53
+ const currentUser = useAppStore((s) => s.currentUser)
54
+ const sessions = useAppStore((s) => s.sessions)
55
+ const currentSessionId = useAppStore((s) => s.currentSessionId)
56
+ const sidebarOpen = useAppStore((s) => s.sidebarOpen)
57
+ const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
58
+ const setSettingsOpen = useAppStore((s) => s.setSettingsOpen)
59
+ const setNewSessionOpen = useAppStore((s) => s.setNewSessionOpen)
60
+ const setUser = useAppStore((s) => s.setUser)
61
+ const setCurrentSession = useAppStore((s) => s.setCurrentSession)
62
+ const activeView = useAppStore((s) => s.activeView)
63
+ const setActiveView = useAppStore((s) => s.setActiveView)
64
+ const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
65
+ const setScheduleSheetOpen = useAppStore((s) => s.setScheduleSheetOpen)
66
+ const setTaskSheetOpen = useAppStore((s) => s.setTaskSheetOpen)
67
+ const setSecretSheetOpen = useAppStore((s) => s.setSecretSheetOpen)
68
+ const setProviderSheetOpen = useAppStore((s) => s.setProviderSheetOpen)
69
+ const setSkillSheetOpen = useAppStore((s) => s.setSkillSheetOpen)
70
+ const setConnectorSheetOpen = useAppStore((s) => s.setConnectorSheetOpen)
71
+ const setWebhookSheetOpen = useAppStore((s) => s.setWebhookSheetOpen)
72
+ const setMcpServerSheetOpen = useAppStore((s) => s.setMcpServerSheetOpen)
73
+ const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
74
+ const setPluginSheetOpen = useAppStore((s) => s.setPluginSheetOpen)
75
+ const isDesktop = useMediaQuery('(min-width: 768px)')
76
+ const hasSelectedSession = !!(currentSessionId && sessions[currentSessionId])
77
+
78
+ const [agentViewMode, setAgentViewMode] = useState<'chat' | 'config'>('chat')
79
+ const [shortcutsOpen, setShortcutsOpen] = useState(false)
80
+
81
+ const handleShortcutKey = useCallback((e: KeyboardEvent) => {
82
+ // Ctrl+/ or Cmd+/
83
+ if ((e.ctrlKey || e.metaKey) && e.key === '/') {
84
+ e.preventDefault()
85
+ setShortcutsOpen((v) => !v)
86
+ return
87
+ }
88
+ // ? key when not in an input/textarea/contenteditable
89
+ if (e.key === '?' && !e.ctrlKey && !e.metaKey && !e.altKey) {
90
+ const tag = (e.target as HTMLElement)?.tagName?.toLowerCase()
91
+ const editable = (e.target as HTMLElement)?.isContentEditable
92
+ if (tag === 'input' || tag === 'textarea' || editable) return
93
+ e.preventDefault()
94
+ setShortcutsOpen((v) => !v)
95
+ }
96
+ }, [])
97
+
98
+ useEffect(() => {
99
+ window.addEventListener('keydown', handleShortcutKey)
100
+ return () => window.removeEventListener('keydown', handleShortcutKey)
101
+ }, [handleShortcutKey])
102
+
103
+ const [railExpanded, setRailExpanded] = useState(() => {
104
+ if (typeof window === 'undefined') return true
105
+ const stored = localStorage.getItem(RAIL_EXPANDED_KEY)
106
+ return stored === null ? true : stored === 'true'
107
+ })
108
+
109
+ const toggleRail = () => {
110
+ const next = !railExpanded
111
+ setRailExpanded(next)
112
+ localStorage.setItem(RAIL_EXPANDED_KEY, String(next))
113
+ }
114
+
115
+ const handleSwitchUser = () => {
116
+ setUser(null)
117
+ setCurrentSession(null)
118
+ }
119
+
120
+ const openNewSheet = () => {
121
+ if (activeView === 'sessions') setNewSessionOpen(true)
122
+ else if (activeView === 'agents') setAgentSheetOpen(true)
123
+ else if (activeView === 'schedules') setScheduleSheetOpen(true)
124
+ else if (activeView === 'tasks') setTaskSheetOpen(true)
125
+ else if (activeView === 'secrets') setSecretSheetOpen(true)
126
+ else if (activeView === 'providers') setProviderSheetOpen(true)
127
+ else if (activeView === 'skills') setSkillSheetOpen(true)
128
+ else if (activeView === 'connectors') setConnectorSheetOpen(true)
129
+ else if (activeView === 'webhooks') setWebhookSheetOpen(true)
130
+ else if (activeView === 'mcp_servers') setMcpServerSheetOpen(true)
131
+ else if (activeView === 'knowledge') setKnowledgeSheetOpen(true)
132
+ else if (activeView === 'plugins') setPluginSheetOpen(true)
133
+ }
134
+
135
+ const agents = useAppStore((s) => s.agents)
136
+ const currentAgentId = useAppStore((s) => s.currentAgentId)
137
+ const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
138
+ const mainSession = Object.values(sessions).find((s) => s.name === '__main__' && s.user === currentUser)
139
+
140
+ const goToMainChat = async () => {
141
+ // Navigate to default agent's chat thread
142
+ const defaultAgent = agents['default'] || Object.values(agents)[0]
143
+ if (defaultAgent) {
144
+ await setCurrentAgent(defaultAgent.id)
145
+ } else if (mainSession) {
146
+ setCurrentSession(mainSession.id)
147
+ }
148
+ setActiveView('agents')
149
+ setSidebarOpen(false)
150
+ if (typeof window !== 'undefined') {
151
+ window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
152
+ }
153
+ }
154
+
155
+ return (
156
+ <div className="h-full flex overflow-hidden">
157
+ {/* Desktop: Navigation rail (expandable) */}
158
+ {isDesktop && (
159
+ <div
160
+ className="shrink-0 bg-raised border-r border-white/[0.04] flex flex-col py-4 transition-all duration-200 overflow-hidden"
161
+ style={{ width: railExpanded ? 180 : 60 }}
162
+ >
163
+ {/* Logo + collapse toggle */}
164
+ <div className={`flex items-center mb-4 shrink-0 ${railExpanded ? 'px-4 gap-3' : 'justify-center'}`}>
165
+ <div className="w-10 h-10 rounded-[11px] bg-gradient-to-br from-[#4338CA] to-[#6366F1] flex items-center justify-center shrink-0
166
+ shadow-[0_2px_12px_rgba(99,102,241,0.2)]">
167
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" className="text-white">
168
+ <path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
169
+ </svg>
170
+ </div>
171
+ {railExpanded && (
172
+ <button
173
+ onClick={toggleRail}
174
+ className="ml-auto w-7 h-7 rounded-[8px] flex items-center justify-center text-text-3 hover:text-text hover:bg-white/[0.04] transition-all cursor-pointer bg-transparent border-none"
175
+ title="Collapse sidebar"
176
+ >
177
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
178
+ <polyline points="11 17 6 12 11 7" />
179
+ <polyline points="18 17 13 12 18 7" />
180
+ </svg>
181
+ </button>
182
+ )}
183
+ </div>
184
+
185
+ {/* Expand button when collapsed */}
186
+ {!railExpanded && (
187
+ <div className="flex justify-center mb-2">
188
+ <button
189
+ onClick={toggleRail}
190
+ className="rail-btn"
191
+ title="Expand sidebar"
192
+ >
193
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
194
+ <polyline points="13 17 18 12 13 7" />
195
+ <polyline points="6 17 11 12 6 7" />
196
+ </svg>
197
+ </button>
198
+ </div>
199
+ )}
200
+
201
+ {/* Main Chat shortcut */}
202
+ {railExpanded ? (
203
+ <div className="px-3 mb-2">
204
+ <button
205
+ onClick={goToMainChat}
206
+ className={`w-full flex items-center gap-2.5 px-3 py-2.5 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all
207
+ ${activeView === 'agents' && currentAgentId && (currentAgentId === 'default' || currentAgentId === Object.keys(agents)[0])
208
+ ? 'bg-[#6366F1]/15 border border-[#6366F1]/25 text-accent-bright'
209
+ : 'bg-[#6366F1]/10 border border-[#6366F1]/20 text-accent-bright hover:bg-[#6366F1]/15'}`}
210
+ style={{ fontFamily: 'inherit' }}
211
+ >
212
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
213
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
214
+ </svg>
215
+ Main Chat
216
+ </button>
217
+ </div>
218
+ ) : (
219
+ <RailTooltip label="Main Chat" description="Your persistent assistant chat">
220
+ <button
221
+ onClick={goToMainChat}
222
+ className={`rail-btn self-center mb-2 ${activeView === 'agents' && currentAgentId && (currentAgentId === 'default' || currentAgentId === Object.keys(agents)[0]) ? 'active' : ''}`}
223
+ >
224
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
225
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
226
+ </svg>
227
+ </button>
228
+ </RailTooltip>
229
+ )}
230
+
231
+ {/* Nav items */}
232
+ <div className={`flex flex-col gap-0.5 ${railExpanded ? 'px-3' : 'items-center'}`}>
233
+ <NavItem view="agents" label="Agents" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('agents'); setSidebarOpen(true) }}>
234
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
235
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
236
+ </svg>
237
+ </NavItem>
238
+ <NavItem view="sessions" label="History" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('sessions'); setSidebarOpen(true) }}>
239
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
240
+ <rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" />
241
+ </svg>
242
+ </NavItem>
243
+ <NavItem view="schedules" label="Schedules" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('schedules'); setSidebarOpen(true) }}>
244
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
245
+ <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
246
+ </svg>
247
+ </NavItem>
248
+ <NavItem view="memory" label="Memory" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('memory'); setSidebarOpen(true) }}>
249
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
250
+ <ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" /><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
251
+ </svg>
252
+ </NavItem>
253
+ <NavItem view="tasks" label="Tasks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('tasks'); setSidebarOpen(true) }}>
254
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
255
+ <path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" /><rect x="9" y="3" width="6" height="4" rx="1" /><path d="M9 14l2 2 4-4" />
256
+ </svg>
257
+ </NavItem>
258
+ <NavItem view="secrets" label="Secrets" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('secrets'); setSidebarOpen(true) }}>
259
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
260
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
261
+ </svg>
262
+ </NavItem>
263
+ <NavItem view="providers" label="Providers" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('providers'); setSidebarOpen(true) }}>
264
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
265
+ <path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" />
266
+ </svg>
267
+ </NavItem>
268
+ <NavItem view="skills" label="Skills" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('skills'); setSidebarOpen(true) }}>
269
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
270
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
271
+ </svg>
272
+ </NavItem>
273
+ <NavItem view="connectors" label="Connectors" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('connectors'); setSidebarOpen(true) }}>
274
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
275
+ <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" /><line x1="8" y1="12" x2="16" y2="12" />
276
+ </svg>
277
+ </NavItem>
278
+ <NavItem view="webhooks" label="Webhooks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('webhooks'); setSidebarOpen(true) }}>
279
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
280
+ <path d="M22 12h-4l-3 7L9 5l-3 7H2" />
281
+ </svg>
282
+ </NavItem>
283
+ <NavItem view="mcp_servers" label="MCP" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('mcp_servers'); setSidebarOpen(true) }}>
284
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
285
+ <rect x="2" y="2" width="20" height="8" rx="2" /><rect x="2" y="14" width="20" height="8" rx="2" /><line x1="6" y1="6" x2="6.01" y2="6" /><line x1="6" y1="18" x2="6.01" y2="18" />
286
+ </svg>
287
+ </NavItem>
288
+ <NavItem view="knowledge" label="Knowledge" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('knowledge'); setSidebarOpen(true) }}>
289
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
290
+ <circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
291
+ </svg>
292
+ </NavItem>
293
+ <NavItem view="plugins" label="Plugins" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('plugins'); setSidebarOpen(true) }}>
294
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
295
+ <path d="M12 2v4m0 12v4M2 12h4m12 0h4" /><circle cx="12" cy="12" r="4" /><path d="M8 8L5.5 5.5M16 8l2.5-2.5M8 16l-2.5 2.5M16 16l2.5 2.5" />
296
+ </svg>
297
+ </NavItem>
298
+ <NavItem view="usage" label="Usage" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('usage'); setSidebarOpen(true) }}>
299
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
300
+ <line x1="18" y1="20" x2="18" y2="10" /><line x1="12" y1="20" x2="12" y2="4" /><line x1="6" y1="20" x2="6" y2="14" />
301
+ </svg>
302
+ </NavItem>
303
+ <NavItem view="runs" label="Runs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('runs'); setSidebarOpen(true) }}>
304
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
305
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
306
+ </svg>
307
+ </NavItem>
308
+ <NavItem view="logs" label="Logs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('logs'); setSidebarOpen(true) }}>
309
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
310
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /><polyline points="10 9 9 9 8 9" />
311
+ </svg>
312
+ </NavItem>
313
+ </div>
314
+
315
+ <div className="flex-1" />
316
+
317
+ {/* Bottom: Docs + Daemon + Settings + User */}
318
+ <div className={`flex flex-col gap-1 ${railExpanded ? 'px-3' : 'items-center'}`}>
319
+ {railExpanded ? (
320
+ <a
321
+ href="https://swarmclaw.ai/docs"
322
+ target="_blank"
323
+ rel="noopener noreferrer"
324
+ className="w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] text-[13px] font-500 cursor-pointer transition-all
325
+ bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04] no-underline"
326
+ style={{ fontFamily: 'inherit' }}
327
+ >
328
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
329
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
330
+ </svg>
331
+ Docs
332
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="ml-auto opacity-40">
333
+ <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /><polyline points="15 3 21 3 21 9" /><line x1="10" y1="14" x2="21" y2="3" />
334
+ </svg>
335
+ </a>
336
+ ) : (
337
+ <RailTooltip label="Docs" description="Open documentation site">
338
+ <a
339
+ href="https://swarmclaw.ai/docs"
340
+ target="_blank"
341
+ rel="noopener noreferrer"
342
+ className="rail-btn"
343
+ >
344
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
345
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
346
+ </svg>
347
+ </a>
348
+ </RailTooltip>
349
+ )}
350
+ {railExpanded && <DaemonIndicator />}
351
+ {railExpanded ? (
352
+ <button
353
+ onClick={() => setSettingsOpen(true)}
354
+ className="w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] text-[13px] font-500 cursor-pointer transition-all
355
+ bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04] border-none"
356
+ style={{ fontFamily: 'inherit' }}
357
+ >
358
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="shrink-0">
359
+ <circle cx="12" cy="12" r="3" />
360
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
361
+ </svg>
362
+ Settings
363
+ </button>
364
+ ) : (
365
+ <RailTooltip label="Settings" description="API keys, providers & app config">
366
+ <button onClick={() => setSettingsOpen(true)} className="rail-btn">
367
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
368
+ <circle cx="12" cy="12" r="3" />
369
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
370
+ </svg>
371
+ </button>
372
+ </RailTooltip>
373
+ )}
374
+
375
+ {railExpanded ? (
376
+ <button
377
+ onClick={handleSwitchUser}
378
+ className="w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] cursor-pointer transition-all
379
+ bg-transparent hover:bg-white/[0.04] border-none"
380
+ style={{ fontFamily: 'inherit' }}
381
+ >
382
+ <Avatar user={currentUser!} size="sm" />
383
+ <span className="text-[13px] font-500 text-text-2 capitalize truncate">{currentUser}</span>
384
+ </button>
385
+ ) : (
386
+ <RailTooltip label="Switch User" description="Sign in as a different user">
387
+ <button onClick={handleSwitchUser} className="mt-2 bg-transparent border-none cursor-pointer shrink-0">
388
+ <Avatar user={currentUser!} size="sm" />
389
+ </button>
390
+ </RailTooltip>
391
+ )}
392
+ </div>
393
+ </div>
394
+ )}
395
+
396
+ {/* Desktop: Side panel */}
397
+ {isDesktop && sidebarOpen && (
398
+ <div
399
+ className="w-[280px] shrink-0 bg-raised border-r border-white/[0.04] flex flex-col h-full"
400
+ style={{ animation: 'panel-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)' }}
401
+ >
402
+ <div className="flex items-center px-5 pt-5 pb-3 shrink-0">
403
+ <h2 className="font-display text-[14px] font-600 text-text-2 tracking-[-0.01em] capitalize flex-1">{activeView === 'sessions' ? 'History' : activeView}</h2>
404
+ {activeView === 'logs' || activeView === 'usage' || activeView === 'runs' ? null : activeView === 'memory' ? (
405
+ <button
406
+ onClick={() => useAppStore.getState().setMemorySheetOpen(true)}
407
+ className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 text-accent-bright bg-accent-soft hover:bg-[#6366F1]/15 transition-all cursor-pointer"
408
+ style={{ fontFamily: 'inherit' }}
409
+ >
410
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
411
+ <line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
412
+ </svg>
413
+ Memory
414
+ </button>
415
+ ) : (
416
+ <button
417
+ onClick={openNewSheet}
418
+ className="flex items-center gap-1.5 px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 text-accent-bright bg-accent-soft hover:bg-[#6366F1]/15 transition-all cursor-pointer"
419
+ style={{ fontFamily: 'inherit' }}
420
+ >
421
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
422
+ <line x1="12" y1="5" x2="12" y2="19" />
423
+ <line x1="5" y1="12" x2="19" y2="12" />
424
+ </svg>
425
+ {activeView === 'sessions' ? 'Session' : activeView === 'agents' ? 'Agent' : activeView === 'schedules' ? 'Schedule' : activeView === 'tasks' ? 'Task' : activeView === 'secrets' ? 'Secret' : activeView === 'providers' ? 'Provider' : activeView === 'skills' ? 'Skill' : activeView === 'connectors' ? 'Connector' : activeView === 'webhooks' ? 'Webhook' : activeView === 'mcp_servers' ? 'MCP Server' : activeView === 'knowledge' ? 'Knowledge' : 'New'}
426
+ </button>
427
+ )}
428
+ </div>
429
+ {activeView === 'sessions' && (
430
+ <>
431
+ <UpdateBanner />
432
+ <NetworkBanner />
433
+ <SessionList inSidebar onSelect={() => {}} />
434
+ </>
435
+ )}
436
+ {activeView === 'agents' && (
437
+ <>
438
+ <div className="flex gap-1 px-4 pb-2">
439
+ {(['chat', 'config'] as const).map((mode) => (
440
+ <button
441
+ key={mode}
442
+ onClick={() => setAgentViewMode(mode)}
443
+ className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
444
+ ${agentViewMode === mode ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
445
+ style={{ fontFamily: 'inherit' }}
446
+ >
447
+ {mode}
448
+ </button>
449
+ ))}
450
+ </div>
451
+ {agentViewMode === 'chat' ? <AgentChatList inSidebar /> : <AgentList inSidebar />}
452
+ </>
453
+ )}
454
+ {activeView === 'schedules' && <ScheduleList inSidebar />}
455
+ {activeView === 'memory' && <MemoryList inSidebar />}
456
+ {activeView === 'tasks' && <TaskList inSidebar />}
457
+ {activeView === 'secrets' && <SecretsList inSidebar />}
458
+ {activeView === 'providers' && <ProviderList inSidebar />}
459
+ {activeView === 'skills' && <SkillList inSidebar />}
460
+ {activeView === 'connectors' && <ConnectorList inSidebar />}
461
+ {activeView === 'webhooks' && <WebhookList inSidebar />}
462
+ {activeView === 'mcp_servers' && <McpServerList />}
463
+ {activeView === 'knowledge' && <KnowledgeList />}
464
+ {activeView === 'usage' && <UsageList />}
465
+ {activeView === 'runs' && <RunList />}
466
+ {activeView === 'logs' && <LogList />}
467
+ </div>
468
+ )}
469
+
470
+ {/* Mobile: Drawer */}
471
+ {!isDesktop && sidebarOpen && (
472
+ <div className="fixed inset-0 z-50">
473
+ <div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={() => setSidebarOpen(false)} />
474
+ <div
475
+ className="absolute inset-y-0 left-0 w-[300px] bg-raised shadow-[4px_0_60px_rgba(0,0,0,0.7)] flex flex-col"
476
+ style={{ animation: 'slide-in-left 0.25s cubic-bezier(0.16, 1, 0.3, 1)' }}
477
+ >
478
+ <div className="flex items-center gap-3 px-5 py-4 shrink-0">
479
+ <div className="w-9 h-9 rounded-[10px] bg-gradient-to-br from-[#4338CA] to-[#6366F1] flex items-center justify-center
480
+ shadow-[0_2px_8px_rgba(99,102,241,0.15)]">
481
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" className="text-white">
482
+ <path d="M12 2L14.5 9.5L22 12L14.5 14.5L12 22L9.5 14.5L2 12L9.5 9.5L12 2Z" fill="currentColor" />
483
+ </svg>
484
+ </div>
485
+ <span className="font-display text-[15px] font-600 flex-1 tracking-[-0.02em]">SwarmClaw</span>
486
+ <a href="https://swarmclaw.ai/docs" target="_blank" rel="noopener noreferrer" className="rail-btn" title="Documentation">
487
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
488
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" />
489
+ </svg>
490
+ </a>
491
+ <button onClick={() => setSettingsOpen(true)} className="rail-btn">
492
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
493
+ <circle cx="12" cy="12" r="3" />
494
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
495
+ </svg>
496
+ </button>
497
+ <button onClick={handleSwitchUser} className="bg-transparent border-none cursor-pointer shrink-0">
498
+ <Avatar user={currentUser!} size="sm" />
499
+ </button>
500
+ </div>
501
+ {/* View selector tabs */}
502
+ <div className="flex px-4 py-2 gap-1 shrink-0 flex-wrap">
503
+ {(['agents', 'sessions', 'schedules', 'memory', 'tasks', 'secrets', 'providers', 'skills', 'connectors', 'webhooks', 'mcp_servers', 'knowledge', 'plugins', 'usage', 'runs', 'logs'] as AppView[]).map((v) => (
504
+ <button
505
+ key={v}
506
+ onClick={() => setActiveView(v)}
507
+ className={`py-2 px-2.5 rounded-[10px] text-[11px] font-600 capitalize cursor-pointer transition-all
508
+ ${activeView === v
509
+ ? 'bg-accent-soft text-accent-bright'
510
+ : 'bg-transparent text-text-3 hover:text-text-2'}`}
511
+ style={{ fontFamily: 'inherit' }}
512
+ >
513
+ {v}
514
+ </button>
515
+ ))}
516
+ </div>
517
+ {activeView !== 'logs' && activeView !== 'usage' && activeView !== 'runs' && (
518
+ <div className="px-4 py-2.5 shrink-0">
519
+ <button
520
+ onClick={() => {
521
+ setSidebarOpen(false)
522
+ openNewSheet()
523
+ }}
524
+ className="w-full py-3 rounded-[12px] border-none bg-[#6366F1] text-white text-[14px] font-600 cursor-pointer
525
+ hover:brightness-110 active:scale-[0.98] transition-all
526
+ shadow-[0_2px_12px_rgba(99,102,241,0.15)]"
527
+ style={{ fontFamily: 'inherit' }}
528
+ >
529
+ + New {activeView === 'sessions' ? 'Session' : activeView === 'agents' ? 'Agent' : activeView === 'schedules' ? 'Schedule' : activeView === 'tasks' ? 'Task' : activeView === 'secrets' ? 'Secret' : activeView === 'providers' ? 'Provider' : activeView === 'skills' ? 'Skill' : activeView === 'connectors' ? 'Connector' : activeView === 'webhooks' ? 'Webhook' : activeView === 'mcp_servers' ? 'MCP Server' : activeView === 'knowledge' ? 'Knowledge' : activeView === 'plugins' ? 'Plugin' : 'Entry'}
530
+ </button>
531
+ </div>
532
+ )}
533
+ {activeView === 'sessions' && (
534
+ <>
535
+ <UpdateBanner />
536
+ <NetworkBanner />
537
+ <SessionList inSidebar onSelect={() => setSidebarOpen(false)} />
538
+ </>
539
+ )}
540
+ {activeView === 'agents' && (
541
+ <>
542
+ <div className="flex gap-1 px-4 pb-2">
543
+ {(['chat', 'config'] as const).map((mode) => (
544
+ <button
545
+ key={mode}
546
+ onClick={() => setAgentViewMode(mode)}
547
+ className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
548
+ ${agentViewMode === mode ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
549
+ style={{ fontFamily: 'inherit' }}
550
+ >
551
+ {mode}
552
+ </button>
553
+ ))}
554
+ </div>
555
+ {agentViewMode === 'chat' ? <AgentChatList inSidebar onSelect={() => setSidebarOpen(false)} /> : <AgentList inSidebar />}
556
+ </>
557
+ )}
558
+ {activeView === 'schedules' && <ScheduleList inSidebar />}
559
+ {activeView === 'memory' && <MemoryList inSidebar onSelect={() => setSidebarOpen(false)} />}
560
+ {activeView === 'tasks' && <TaskList inSidebar />}
561
+ {activeView === 'secrets' && <SecretsList inSidebar />}
562
+ {activeView === 'providers' && <ProviderList inSidebar />}
563
+ {activeView === 'skills' && <SkillList inSidebar />}
564
+ {activeView === 'connectors' && <ConnectorList inSidebar />}
565
+ {activeView === 'webhooks' && <WebhookList inSidebar />}
566
+ {activeView === 'mcp_servers' && <McpServerList />}
567
+ {activeView === 'knowledge' && <KnowledgeList />}
568
+ {activeView === 'plugins' && <PluginList inSidebar />}
569
+ {activeView === 'usage' && <UsageList />}
570
+ {activeView === 'runs' && <RunList />}
571
+ {activeView === 'logs' && <LogList />}
572
+ </div>
573
+ </div>
574
+ )}
575
+
576
+ {/* Main content */}
577
+ <ErrorBoundary>
578
+ <div className="flex-1 flex flex-col h-full min-w-0 bg-bg">
579
+ {!isDesktop && <MobileHeader />}
580
+ {activeView === 'agents' && hasSelectedSession ? (
581
+ <ChatArea />
582
+ ) : activeView === 'agents' ? (
583
+ <div className="flex-1 flex flex-col">
584
+ {!isDesktop ? (
585
+ <AgentChatList />
586
+ ) : (
587
+ <div className="flex-1 flex items-center justify-center px-8">
588
+ <div className="text-center max-w-[420px]">
589
+ <h2 className="font-display text-[24px] font-700 text-text mb-2 tracking-[-0.02em]">
590
+ Select an Agent
591
+ </h2>
592
+ <p className="text-[14px] text-text-3">
593
+ Choose an agent from the sidebar to start chatting.
594
+ </p>
595
+ </div>
596
+ </div>
597
+ )}
598
+ </div>
599
+ ) : activeView === 'sessions' && hasSelectedSession ? (
600
+ <ChatArea />
601
+ ) : activeView === 'sessions' ? (
602
+ <div className="flex-1 flex flex-col">
603
+ {!isDesktop ? (
604
+ <SessionList />
605
+ ) : (
606
+ <div className="flex-1 flex items-center justify-center px-8">
607
+ <div className="text-center max-w-[420px]">
608
+ <h2 className="font-display text-[24px] font-700 text-text mb-2 tracking-[-0.02em]">
609
+ No Chat Selected
610
+ </h2>
611
+ <p className="text-[14px] text-text-3">
612
+ Choose a session from the sidebar or switch to Agents view.
613
+ </p>
614
+ </div>
615
+ </div>
616
+ )}
617
+ </div>
618
+ ) : activeView === 'tasks' && isDesktop ? (
619
+ <TaskBoard />
620
+ ) : activeView === 'memory' ? (
621
+ <MemoryDetail />
622
+ ) : (
623
+ <ViewEmptyState view={activeView} />
624
+ )}
625
+ </div>
626
+ </ErrorBoundary>
627
+
628
+ <NewSessionSheet />
629
+ <SettingsSheet />
630
+ <AgentSheet />
631
+ <ScheduleSheet />
632
+ <MemorySheet />
633
+ <TaskSheet />
634
+ <SecretSheet />
635
+ <ProviderSheet />
636
+ <SkillSheet />
637
+ <ConnectorSheet />
638
+ <WebhookSheet />
639
+ <McpServerSheet />
640
+ <KnowledgeSheet />
641
+ <PluginSheet />
642
+
643
+ <Dialog open={shortcutsOpen} onOpenChange={setShortcutsOpen}>
644
+ <DialogContent className="sm:max-w-[380px] bg-raised border-white/[0.08]">
645
+ <DialogHeader>
646
+ <DialogTitle className="text-text">Keyboard Shortcuts</DialogTitle>
647
+ </DialogHeader>
648
+ <div className="space-y-3 py-2">
649
+ {([
650
+ ['Enter', 'Send message'],
651
+ ['Shift + Enter', 'New line'],
652
+ ['Ctrl + F', 'Search in chat'],
653
+ ['Ctrl + /', 'Show shortcuts'],
654
+ ] as const).map(([keys, desc]) => (
655
+ <div key={keys} className="flex items-center justify-between">
656
+ <span className="text-[13px] text-text-2">{desc}</span>
657
+ <kbd className="px-2 py-1 rounded-[6px] bg-white/[0.06] border border-white/[0.08] text-[11px] font-mono text-text-3">{keys}</kbd>
658
+ </div>
659
+ ))}
660
+ </div>
661
+ </DialogContent>
662
+ </Dialog>
663
+ </div>
664
+ )
665
+ }
666
+
667
+ class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
668
+ constructor(props: { children: ReactNode }) {
669
+ super(props)
670
+ this.state = { hasError: false }
671
+ }
672
+
673
+ static getDerivedStateFromError(_error: Error) {
674
+ return { hasError: true }
675
+ }
676
+
677
+ componentDidCatch(error: Error, info: ErrorInfo) {
678
+ console.error('ErrorBoundary caught:', error, info)
679
+ }
680
+
681
+ render() {
682
+ if (this.state.hasError) {
683
+ return (
684
+ <div className="flex-1 flex flex-col items-center justify-center px-8 bg-bg">
685
+ <div className="text-center max-w-[400px]">
686
+ <div className="w-14 h-14 rounded-[16px] bg-red-500/10 border border-red-500/20 flex items-center justify-center mx-auto mb-5">
687
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-red-400">
688
+ <circle cx="12" cy="12" r="10" />
689
+ <line x1="12" y1="8" x2="12" y2="12" />
690
+ <line x1="12" y1="16" x2="12.01" y2="16" />
691
+ </svg>
692
+ </div>
693
+ <h2 className="font-display text-[22px] font-700 text-text mb-2 tracking-[-0.02em]">
694
+ Something went wrong
695
+ </h2>
696
+ <p className="text-[14px] text-text-3 mb-6">
697
+ An unexpected error occurred. Try reloading the page.
698
+ </p>
699
+ <button
700
+ onClick={() => window.location.reload()}
701
+ className="inline-flex items-center gap-2 px-6 py-3 rounded-[12px] border-none bg-[#6366F1] text-white text-[14px] font-600 cursor-pointer
702
+ hover:brightness-110 active:scale-[0.97] transition-all shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
703
+ style={{ fontFamily: 'inherit' }}
704
+ >
705
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
706
+ <polyline points="23 4 23 10 17 10" />
707
+ <path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
708
+ </svg>
709
+ Reload
710
+ </button>
711
+ </div>
712
+ </div>
713
+ )
714
+ }
715
+
716
+ return this.props.children
717
+ }
718
+ }
719
+
720
+ const VIEW_DESCRIPTIONS: Record<AppView, string> = {
721
+ sessions: 'Session history & debug view',
722
+ agents: 'Chat with & configure your AI agents',
723
+ schedules: 'Automated task schedules',
724
+ memory: 'Long-term agent memory store',
725
+ tasks: 'Task board for orchestrator jobs',
726
+ secrets: 'API keys & credentials for orchestrators',
727
+ providers: 'LLM providers & custom endpoints',
728
+ skills: 'Reusable instruction sets for agents',
729
+ connectors: 'Chat platform bridges (Discord, Slack, etc.)',
730
+ webhooks: 'Inbound HTTP triggers for event-driven workflows',
731
+ mcp_servers: 'Connect agents to external MCP tool servers',
732
+ knowledge: 'Shared knowledge base accessible by all agents',
733
+ logs: 'Application logs & error tracking',
734
+ plugins: 'Extend agent capabilities with custom plugins',
735
+ usage: 'Token usage analytics & cost tracking',
736
+ runs: 'Live session run monitoring & history',
737
+ }
738
+
739
+ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon: string; title: string; description: string; features: string[] }> = {
740
+ schedules: {
741
+ icon: 'clock',
742
+ title: 'Schedules',
743
+ description: 'Automate recurring tasks by scheduling orchestrators to run on a cron, interval, or one-time basis.',
744
+ features: ['Set up cron expressions for precise timing', 'Run orchestrators automatically on intervals', 'Schedule one-time future tasks', 'View execution history and results'],
745
+ },
746
+ memory: {
747
+ icon: 'database',
748
+ title: 'Memory',
749
+ description: 'Long-term memory store for AI agents. Orchestrators can store and retrieve knowledge across sessions.',
750
+ features: ['Agents store findings and learnings automatically', 'Full-text search across all stored memories', 'Organized by categories and agents', 'Persists across sessions for continuity'],
751
+ },
752
+ tasks: {
753
+ icon: 'clipboard',
754
+ title: 'Task Board',
755
+ description: 'A Trello-style board for managing orchestrator jobs. Create tasks, assign them to orchestrators, and track progress.',
756
+ features: ['Kanban columns: Backlog, Queued, Running, Completed, Failed', 'Assign tasks to specific orchestrator agents', 'Sequential queue ensures orchestrators don\'t conflict', 'View results and session logs for completed tasks'],
757
+ },
758
+ secrets: {
759
+ icon: 'lock',
760
+ title: 'Secrets',
761
+ description: 'Manage API keys and credentials that orchestrators can access during task execution.',
762
+ features: ['Store keys for external services (Gmail, APIs, etc.)', 'Scope secrets globally or to specific orchestrators', 'Encrypted at rest with AES-256-GCM', 'Orchestrators retrieve secrets via the get_secret tool'],
763
+ },
764
+ providers: {
765
+ icon: 'zap',
766
+ title: 'Providers',
767
+ description: 'Manage LLM providers including built-in and custom OpenAI-compatible endpoints.',
768
+ features: ['Built-in support for Claude, OpenAI, Anthropic, and Ollama', 'Add custom OpenAI-compatible providers (OpenRouter, Together, Groq)', 'Configure base URLs, models, and API keys per provider', 'Custom providers work seamlessly with all features'],
769
+ },
770
+ skills: {
771
+ icon: 'book',
772
+ title: 'Skills',
773
+ description: 'Upload and manage reusable instruction sets that agents can use during task execution.',
774
+ features: ['Upload markdown files with specialized instructions', 'Assign skills to specific agents', 'Skills are injected into agent system prompts', 'Create libraries of reusable expertise'],
775
+ },
776
+ connectors: {
777
+ icon: 'link',
778
+ title: 'Connectors',
779
+ description: 'Bridge chat platforms to your AI agents. Receive messages from Discord, Telegram, Slack, or WhatsApp and route them to agents.',
780
+ features: ['Connect Discord, Telegram, Slack, or WhatsApp bots', 'Route incoming messages to any agent', 'Each platform channel gets its own session', 'Start and stop connectors from the UI'],
781
+ },
782
+ webhooks: {
783
+ icon: 'webhook',
784
+ title: 'Webhooks',
785
+ description: 'Receive external events over HTTP and trigger orchestrator runs automatically.',
786
+ features: ['Create secure inbound webhook endpoints', 'Filter events by type or source', 'Route each webhook to a specific orchestrator', 'Use x-webhook-secret for request authentication'],
787
+ },
788
+ mcp_servers: {
789
+ icon: 'server',
790
+ title: 'MCP Servers',
791
+ description: 'Connect agents to external MCP (Model Context Protocol) servers, injecting their tools into chat sessions.',
792
+ features: ['Configure stdio, SSE, or streamable HTTP transports', 'Test connections and discover available tools', 'Assign MCP servers to specific agents', 'Tools appear alongside built-in tools in chat'],
793
+ },
794
+ knowledge: {
795
+ icon: 'globe',
796
+ title: 'Knowledge Base',
797
+ description: 'A shared knowledge graph accessible by all agents, enabling cross-agent information sharing and orchestration.',
798
+ features: ['Create tagged knowledge entries', 'Agents can store and search knowledge via tools', 'Full-text and vector search', 'Provenance tracking per entry'],
799
+ },
800
+ logs: {
801
+ icon: 'file-text',
802
+ title: 'Logs',
803
+ description: 'View application logs, errors, and debug information. Logs auto-refresh in real-time.',
804
+ features: ['Filter by level: ERROR, WARN, INFO, DEBUG', 'Search through log entries', 'Auto-refresh with live mode', 'Click entries to expand details'],
805
+ },
806
+ plugins: {
807
+ icon: 'puzzle',
808
+ title: 'Plugins',
809
+ description: 'Extend agent behavior with hooks. Install from the marketplace, a URL, or drop .js files into data/plugins/.',
810
+ features: ['Install plugins from the marketplace or a URL', 'Toggle plugins on/off', 'Lifecycle hooks: beforeChat, afterChat, onError', 'Compatible with OpenClaw plugin format'],
811
+ },
812
+ usage: {
813
+ icon: 'bar-chart',
814
+ title: 'Usage',
815
+ description: 'Track token usage and costs across all providers and sessions.',
816
+ features: ['Per-provider cost breakdown', 'Token usage over time', 'Session-level cost tracking', 'Export usage data'],
817
+ },
818
+ runs: {
819
+ icon: 'activity',
820
+ title: 'Runs',
821
+ description: 'View the session run queue and execution history.',
822
+ features: ['Monitor queued and running tasks', 'View run results and errors', 'Cancel pending runs', 'Automatic retry tracking'],
823
+ },
824
+ }
825
+
826
+ function ViewEmptyState({ view }: { view: AppView }) {
827
+ if (view === 'sessions' || view === 'agents') return null
828
+ const config = VIEW_EMPTY_STATES[view as Exclude<AppView, 'sessions' | 'agents'>]
829
+ if (!config) return null
830
+
831
+ return (
832
+ <div className="flex-1 flex flex-col items-center justify-center px-8 pb-20 relative overflow-hidden">
833
+ <div className="absolute inset-0 pointer-events-none">
834
+ <div className="absolute top-[30%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[600px] h-[350px]"
835
+ style={{
836
+ background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.03) 0%, transparent 70%)',
837
+ animation: 'glow-pulse 8s ease-in-out infinite',
838
+ }} />
839
+ </div>
840
+
841
+ <div className="relative max-w-[520px] w-full text-center"
842
+ style={{ animation: 'fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1)' }}>
843
+ <div className="flex justify-center mb-6">
844
+ <div className="w-14 h-14 rounded-[16px] bg-white/[0.03] border border-white/[0.06] flex items-center justify-center">
845
+ <ViewEmptyIcon type={config.icon} />
846
+ </div>
847
+ </div>
848
+
849
+ <h2 className="font-display text-[28px] font-800 leading-[1.15] tracking-[-0.03em] mb-3 text-text">
850
+ {config.title}
851
+ </h2>
852
+ <p className="text-[14px] text-text-3 leading-[1.6] mb-8 max-w-[400px] mx-auto">
853
+ {config.description}
854
+ </p>
855
+
856
+ <div className="text-left max-w-[380px] mx-auto space-y-3">
857
+ {config.features.map((feature) => (
858
+ <div key={feature} className="flex items-start gap-3">
859
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" className="text-accent-bright shrink-0 mt-0.5">
860
+ <path d="M9 12l2 2 4-4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
861
+ <circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="1.5" opacity="0.3" />
862
+ </svg>
863
+ <span className="text-[13px] text-text-2/70 leading-[1.5]">{feature}</span>
864
+ </div>
865
+ ))}
866
+ </div>
867
+ </div>
868
+ </div>
869
+ )
870
+ }
871
+
872
+ function ViewEmptyIcon({ type }: { type: string }) {
873
+ const cls = "text-text-3"
874
+ switch (type) {
875
+ case 'user':
876
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" /></svg>
877
+ case 'clock':
878
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" /></svg>
879
+ case 'database':
880
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" /><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" /></svg>
881
+ case 'clipboard':
882
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2" /><rect x="9" y="3" width="6" height="4" rx="1" /><path d="M9 14l2 2 4-4" /></svg>
883
+ case 'lock':
884
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><rect x="3" y="11" width="18" height="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
885
+ case 'zap':
886
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" /></svg>
887
+ case 'book':
888
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" /><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z" /></svg>
889
+ case 'link':
890
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3" /><line x1="8" y1="12" x2="16" y2="12" /></svg>
891
+ case 'webhook':
892
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M22 12h-4l-3 7L9 5l-3 7H2" /></svg>
893
+ case 'server':
894
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><rect x="2" y="2" width="20" height="8" rx="2" /><rect x="2" y="14" width="20" height="8" rx="2" /><line x1="6" y1="6" x2="6.01" y2="6" /><line x1="6" y1="18" x2="6.01" y2="18" /></svg>
895
+ case 'globe':
896
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><circle cx="12" cy="12" r="10" /><line x1="2" y1="12" x2="22" y2="12" /><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></svg>
897
+ case 'file-text':
898
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /><polyline points="14 2 14 8 20 8" /><line x1="16" y1="13" x2="8" y2="13" /><line x1="16" y1="17" x2="8" y2="17" /></svg>
899
+ case 'puzzle':
900
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className={cls}><path d="M12 2v4m0 12v4M2 12h4m12 0h4" /><circle cx="12" cy="12" r="4" /><path d="M8 8L5.5 5.5M16 8l2.5-2.5M8 16l-2.5 2.5M16 16l2.5 2.5" /></svg>
901
+ case 'bar-chart':
902
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><line x1="18" y1="20" x2="18" y2="10" /><line x1="12" y1="20" x2="12" y2="4" /><line x1="6" y1="20" x2="6" y2="14" /></svg>
903
+ case 'activity':
904
+ return <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className={cls}><polyline points="22 12 18 12 15 21 9 3 6 12 2 12" /></svg>
905
+ default:
906
+ return null
907
+ }
908
+ }
909
+
910
+ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children }: {
911
+ view: AppView
912
+ label: string
913
+ expanded: boolean
914
+ active: AppView
915
+ sidebarOpen: boolean
916
+ onClick: () => void
917
+ children: React.ReactNode
918
+ }) {
919
+ const isActive = active === view && sidebarOpen
920
+
921
+ if (expanded) {
922
+ return (
923
+ <button
924
+ onClick={onClick}
925
+ className={`w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] text-[13px] font-500 cursor-pointer transition-all border-none
926
+ ${isActive
927
+ ? 'bg-accent-soft text-accent-bright'
928
+ : 'bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04]'}`}
929
+ style={{ fontFamily: 'inherit' }}
930
+ >
931
+ <span className="shrink-0">{children}</span>
932
+ <span className="truncate">{label}</span>
933
+ </button>
934
+ )
935
+ }
936
+
937
+ return (
938
+ <Tooltip>
939
+ <TooltipTrigger asChild>
940
+ <button onClick={onClick} className={`rail-btn ${isActive ? 'active' : ''}`}>
941
+ {children}
942
+ </button>
943
+ </TooltipTrigger>
944
+ <TooltipContent side="right" sideOffset={8}
945
+ className="bg-raised border border-white/[0.08] text-text shadow-[0_8px_32px_rgba(0,0,0,0.5)] rounded-[10px] px-3.5 py-2.5 max-w-[200px]">
946
+ <div className="font-display text-[13px] font-600 mb-0.5">{label}</div>
947
+ <div className="text-[11px] text-text-3 leading-[1.4]">{VIEW_DESCRIPTIONS[view]}</div>
948
+ </TooltipContent>
949
+ </Tooltip>
950
+ )
951
+ }
952
+
953
+ function RailTooltip({ label, description, children }: { label: string; description: string; children: React.ReactNode }) {
954
+ return (
955
+ <Tooltip>
956
+ <TooltipTrigger asChild>{children}</TooltipTrigger>
957
+ <TooltipContent side="right" sideOffset={8}
958
+ className="bg-raised border border-white/[0.08] text-text shadow-[0_8px_32px_rgba(0,0,0,0.5)] rounded-[10px] px-3.5 py-2.5 max-w-[200px]">
959
+ <div className="font-display text-[13px] font-600 mb-0.5">{label}</div>
960
+ <div className="text-[11px] text-text-3 leading-[1.4]">{description}</div>
961
+ </TooltipContent>
962
+ </Tooltip>
963
+ )
964
+ }
965
+
966
+ function DesktopEmptyState({ userName }: { userName: string | null }) {
967
+ const setNewSessionOpen = useAppStore((s) => s.setNewSessionOpen)
968
+
969
+ return (
970
+ <div className="flex-1 flex flex-col items-center justify-center px-8 pb-20 relative overflow-hidden">
971
+ <div className="absolute inset-0 pointer-events-none">
972
+ <div className="absolute top-[30%] left-[50%] -translate-x-1/2 -translate-y-1/2 w-[700px] h-[400px]"
973
+ style={{
974
+ background: 'radial-gradient(ellipse at center, rgba(99,102,241,0.04) 0%, transparent 70%)',
975
+ animation: 'glow-pulse 8s ease-in-out infinite',
976
+ }} />
977
+ </div>
978
+
979
+ <div className="relative max-w-[560px] w-full text-center"
980
+ style={{ animation: 'fade-in 0.6s cubic-bezier(0.16, 1, 0.3, 1)' }}>
981
+ <div className="flex justify-center mb-8">
982
+ <div className="relative">
983
+ <svg width="40" height="40" viewBox="0 0 48 48" fill="none" className="text-accent-bright"
984
+ style={{ animation: 'sparkle-spin 10s linear infinite' }}>
985
+ <path d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z"
986
+ fill="currentColor" opacity="0.8" />
987
+ </svg>
988
+ <div className="absolute inset-0 blur-xl bg-accent-bright/15" />
989
+ </div>
990
+ </div>
991
+
992
+ <h1 className="font-display text-[44px] font-800 leading-[1.1] tracking-[-0.04em] mb-5">
993
+ Hi, <span className="text-accent-bright">{userName ? userName.charAt(0).toUpperCase() + userName.slice(1) : 'there'}</span>
994
+ <br />
995
+ <span className="text-text-2">What would you like to do?</span>
996
+ </h1>
997
+ <p className="text-[15px] text-text-3 mb-12">
998
+ Create a new session to start chatting
999
+ </p>
1000
+ <button
1001
+ onClick={() => setNewSessionOpen(true)}
1002
+ className="inline-flex items-center gap-2.5 px-12 py-4 rounded-[16px] border-none bg-[#6366F1] text-white text-[16px] font-display font-600
1003
+ cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
1004
+ shadow-[0_6px_28px_rgba(99,102,241,0.3)]"
1005
+ style={{ fontFamily: 'inherit' }}
1006
+ >
1007
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
1008
+ <line x1="12" y1="5" x2="12" y2="19" />
1009
+ <line x1="5" y1="12" x2="19" y2="12" />
1010
+ </svg>
1011
+ New Session
1012
+ </button>
1013
+ </div>
1014
+ </div>
1015
+ )
1016
+ }