@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,284 @@
1
+ const COMMAND_GROUPS = {
2
+ agents: {
3
+ description: 'Manage agents',
4
+ commands: {
5
+ list: { description: 'List agents', method: 'GET', path: '/agents' },
6
+ get: { description: 'Get an agent by id (from list)', virtualGet: true, collectionPath: '/agents', params: ['id'] },
7
+ create: { description: 'Create an agent', method: 'POST', path: '/agents' },
8
+ update: { description: 'Update an agent', method: 'PUT', path: '/agents/:id', params: ['id'] },
9
+ delete: { description: 'Delete an agent', method: 'DELETE', path: '/agents/:id', params: ['id'] },
10
+ generate: { description: 'Generate an agent definition', method: 'POST', path: '/agents/generate' },
11
+ },
12
+ },
13
+ auth: {
14
+ description: 'Access-key auth checks',
15
+ commands: {
16
+ status: { description: 'Get auth setup status', method: 'GET', path: '/auth' },
17
+ login: { description: 'Validate an access key', method: 'POST', path: '/auth' },
18
+ },
19
+ },
20
+ connectors: {
21
+ description: 'Manage chat connectors',
22
+ commands: {
23
+ list: { description: 'List connectors', method: 'GET', path: '/connectors' },
24
+ get: { description: 'Get connector details', method: 'GET', path: '/connectors/:id', params: ['id'] },
25
+ create: { description: 'Create a connector', method: 'POST', path: '/connectors' },
26
+ update: { description: 'Update connector config', method: 'PUT', path: '/connectors/:id', params: ['id'] },
27
+ delete: { description: 'Delete connector', method: 'DELETE', path: '/connectors/:id', params: ['id'] },
28
+ start: {
29
+ description: 'Start connector runtime',
30
+ method: 'PUT',
31
+ path: '/connectors/:id',
32
+ params: ['id'],
33
+ staticBody: { action: 'start' },
34
+ },
35
+ stop: {
36
+ description: 'Stop connector runtime',
37
+ method: 'PUT',
38
+ path: '/connectors/:id',
39
+ params: ['id'],
40
+ staticBody: { action: 'stop' },
41
+ },
42
+ repair: {
43
+ description: 'Repair connector runtime',
44
+ method: 'PUT',
45
+ path: '/connectors/:id',
46
+ params: ['id'],
47
+ staticBody: { action: 'repair' },
48
+ },
49
+ },
50
+ },
51
+ credentials: {
52
+ description: 'Manage encrypted provider credentials',
53
+ commands: {
54
+ list: { description: 'List credentials', method: 'GET', path: '/credentials' },
55
+ get: { description: 'Get credential metadata by id (from list)', virtualGet: true, collectionPath: '/credentials', params: ['id'] },
56
+ create: { description: 'Create credential', method: 'POST', path: '/credentials' },
57
+ delete: { description: 'Delete credential', method: 'DELETE', path: '/credentials/:id', params: ['id'] },
58
+ },
59
+ },
60
+ daemon: {
61
+ description: 'Daemon lifecycle controls',
62
+ commands: {
63
+ status: { description: 'Get daemon status', method: 'GET', path: '/daemon' },
64
+ start: { description: 'Start daemon', method: 'POST', path: '/daemon', staticBody: { action: 'start' } },
65
+ stop: { description: 'Stop daemon', method: 'POST', path: '/daemon', staticBody: { action: 'stop' } },
66
+ 'health-check': { description: 'Run daemon health checks immediately', method: 'POST', path: '/daemon/health-check' },
67
+ },
68
+ },
69
+ dirs: {
70
+ description: 'Directory browsing helpers',
71
+ commands: {
72
+ list: { description: 'List directories (supports --query path=/some/dir)', method: 'GET', path: '/dirs' },
73
+ pick: { description: 'Open native picker (body: {"mode":"file|folder"})', method: 'POST', path: '/dirs/pick' },
74
+ },
75
+ },
76
+ documents: {
77
+ description: 'File uploads/downloads and TTS audio',
78
+ commands: {
79
+ upload: {
80
+ description: 'Upload a file (requires --file)',
81
+ method: 'POST',
82
+ path: '/upload',
83
+ upload: true,
84
+ },
85
+ fetch: {
86
+ description: 'Download an uploaded file by filename',
87
+ method: 'GET',
88
+ path: '/uploads/:filename',
89
+ params: ['filename'],
90
+ binary: true,
91
+ },
92
+ tts: {
93
+ description: 'Generate TTS audio (body: {"text":"..."})',
94
+ method: 'POST',
95
+ path: '/tts',
96
+ binary: true,
97
+ },
98
+ },
99
+ },
100
+ generate: {
101
+ description: 'Structured AI generation helpers',
102
+ commands: {
103
+ create: { description: 'Generate object from prompt/type', method: 'POST', path: '/generate' },
104
+ info: { description: 'Get active generator provider/model', method: 'GET', path: '/generate/info' },
105
+ },
106
+ },
107
+ logs: {
108
+ description: 'Application logs',
109
+ commands: {
110
+ list: { description: 'Fetch logs (supports --query lines=200,level=INFO)', method: 'GET', path: '/logs' },
111
+ clear: { description: 'Clear log file', method: 'DELETE', path: '/logs' },
112
+ },
113
+ },
114
+ memory: {
115
+ description: 'Agent memory entries',
116
+ commands: {
117
+ list: { description: 'List memory entries (supports --query q=term,agentId=id)', method: 'GET', path: '/memory' },
118
+ get: { description: 'Get memory entry by id', method: 'GET', path: '/memory/:id', params: ['id'] },
119
+ create: { description: 'Create memory entry', method: 'POST', path: '/memory' },
120
+ update: { description: 'Update memory entry', method: 'PUT', path: '/memory/:id', params: ['id'] },
121
+ delete: { description: 'Delete memory entry', method: 'DELETE', path: '/memory/:id', params: ['id'] },
122
+ maintenance: { description: 'Analyze memory dedupe/prune candidates', method: 'GET', path: '/memory/maintenance' },
123
+ 'maintenance-run': { description: 'Run memory dedupe/prune maintenance', method: 'POST', path: '/memory/maintenance' },
124
+ },
125
+ },
126
+ 'memory-images': {
127
+ description: 'Stored memory image assets',
128
+ commands: {
129
+ get: { description: 'Download memory image by filename', method: 'GET', path: '/memory-images/:filename', params: ['filename'], binary: true },
130
+ },
131
+ },
132
+ orchestrator: {
133
+ description: 'Orchestrator runs and run-state APIs',
134
+ commands: {
135
+ run: { description: 'Run orchestrator task now', method: 'POST', path: '/orchestrator/run', waitable: true },
136
+ runs: { description: 'List queued/running/completed runs', method: 'GET', path: '/runs' },
137
+ 'run-get': { description: 'Get run by id', method: 'GET', path: '/runs/:id', params: ['id'] },
138
+ },
139
+ },
140
+ plugins: {
141
+ description: 'Plugin listing/config/install',
142
+ commands: {
143
+ list: { description: 'List installed plugins', method: 'GET', path: '/plugins' },
144
+ update: { description: 'Enable/disable plugin (body: {"filename":"x.js","enabled":true})', method: 'POST', path: '/plugins' },
145
+ marketplace: { description: 'Get plugin marketplace registry', method: 'GET', path: '/plugins/marketplace' },
146
+ install: { description: 'Install plugin by URL', method: 'POST', path: '/plugins/install' },
147
+ },
148
+ },
149
+ providers: {
150
+ description: 'Provider configs and model overrides',
151
+ commands: {
152
+ list: { description: 'List providers', method: 'GET', path: '/providers' },
153
+ create: { description: 'Create custom provider', method: 'POST', path: '/providers' },
154
+ get: { description: 'Get provider by id', method: 'GET', path: '/providers/:id', params: ['id'] },
155
+ update: { description: 'Update provider config', method: 'PUT', path: '/providers/:id', params: ['id'] },
156
+ delete: { description: 'Delete custom provider', method: 'DELETE', path: '/providers/:id', params: ['id'] },
157
+ configs: { description: 'List provider configs only', method: 'GET', path: '/providers/configs' },
158
+ ollama: { description: 'List local Ollama models', method: 'GET', path: '/providers/ollama' },
159
+ 'openclaw-health': { description: 'Probe OpenClaw endpoint and auth status', method: 'GET', path: '/providers/openclaw/health' },
160
+ 'models-get': { description: 'Get provider model overrides', method: 'GET', path: '/providers/:id/models', params: ['id'] },
161
+ 'models-set': { description: 'Set provider model overrides', method: 'PUT', path: '/providers/:id/models', params: ['id'] },
162
+ 'models-reset': { description: 'Delete provider model overrides', method: 'DELETE', path: '/providers/:id/models', params: ['id'] },
163
+ },
164
+ },
165
+ schedules: {
166
+ description: 'Scheduled task automation',
167
+ commands: {
168
+ list: { description: 'List schedules', method: 'GET', path: '/schedules' },
169
+ create: { description: 'Create schedule', method: 'POST', path: '/schedules' },
170
+ get: { description: 'Get schedule by id (from list)', virtualGet: true, collectionPath: '/schedules', params: ['id'] },
171
+ update: { description: 'Update schedule', method: 'PUT', path: '/schedules/:id', params: ['id'] },
172
+ delete: { description: 'Delete schedule', method: 'DELETE', path: '/schedules/:id', params: ['id'] },
173
+ run: { description: 'Trigger schedule immediately', method: 'POST', path: '/schedules/:id/run', params: ['id'] },
174
+ },
175
+ },
176
+ secrets: {
177
+ description: 'Encrypted secret vault',
178
+ commands: {
179
+ list: { description: 'List secret metadata', method: 'GET', path: '/secrets' },
180
+ get: { description: 'Get secret metadata by id (from list)', virtualGet: true, collectionPath: '/secrets', params: ['id'] },
181
+ create: { description: 'Create secret', method: 'POST', path: '/secrets' },
182
+ update: { description: 'Update secret metadata', method: 'PUT', path: '/secrets/:id', params: ['id'] },
183
+ delete: { description: 'Delete secret', method: 'DELETE', path: '/secrets/:id', params: ['id'] },
184
+ },
185
+ },
186
+ sessions: {
187
+ description: 'Interactive chat sessions',
188
+ commands: {
189
+ list: { description: 'List sessions', method: 'GET', path: '/sessions' },
190
+ create: { description: 'Create session', method: 'POST', path: '/sessions' },
191
+ get: { description: 'Get session by id (from list)', virtualGet: true, collectionPath: '/sessions', params: ['id'] },
192
+ update: { description: 'Update session fields', method: 'PUT', path: '/sessions/:id', params: ['id'] },
193
+ delete: { description: 'Delete one session', method: 'DELETE', path: '/sessions/:id', params: ['id'] },
194
+ 'delete-many': { description: 'Delete multiple sessions (body: {"ids":[...]})', method: 'DELETE', path: '/sessions' },
195
+ 'heartbeat-disable-all': { description: 'Disable all session heartbeats and cancel queued heartbeat runs', method: 'POST', path: '/sessions/heartbeat' },
196
+ messages: { description: 'Get session message history', method: 'GET', path: '/sessions/:id/messages', params: ['id'] },
197
+ 'main-loop': { description: 'Get main mission loop state for a session', method: 'GET', path: '/sessions/:id/main-loop', params: ['id'] },
198
+ 'main-loop-action': { description: 'Control main mission loop (pause/resume/set_goal/set_mode/clear_events/nudge)', method: 'POST', path: '/sessions/:id/main-loop', params: ['id'] },
199
+ chat: { description: 'Send chat message (SSE stream)', method: 'POST', path: '/sessions/:id/chat', params: ['id'], stream: true, waitable: true },
200
+ stop: { description: 'Cancel active/running session work', method: 'POST', path: '/sessions/:id/stop', params: ['id'] },
201
+ clear: { description: 'Clear session history', method: 'POST', path: '/sessions/:id/clear', params: ['id'] },
202
+ mailbox: { description: 'List mailbox envelopes for a session', method: 'GET', path: '/sessions/:id/mailbox', params: ['id'] },
203
+ 'mailbox-action': { description: 'Send/ack/clear mailbox envelopes', method: 'POST', path: '/sessions/:id/mailbox', params: ['id'] },
204
+ deploy: { description: 'Deploy session workspace git changes', method: 'POST', path: '/sessions/:id/deploy', params: ['id'] },
205
+ devserver: { description: 'Start/stop/status dev server (body: {"action":"start|stop|status"})', method: 'POST', path: '/sessions/:id/devserver', params: ['id'] },
206
+ browser: { description: 'Check browser runtime for session', method: 'GET', path: '/sessions/:id/browser', params: ['id'] },
207
+ 'browser-clear': { description: 'Close browser runtime for session', method: 'DELETE', path: '/sessions/:id/browser', params: ['id'] },
208
+ },
209
+ },
210
+ settings: {
211
+ description: 'Global app settings',
212
+ commands: {
213
+ get: { description: 'Get settings', method: 'GET', path: '/settings' },
214
+ update: { description: 'Update settings', method: 'PUT', path: '/settings' },
215
+ },
216
+ },
217
+ setup: {
218
+ description: 'Setup and provider validation helpers',
219
+ commands: {
220
+ 'check-provider': { description: 'Validate provider credentials/endpoint', method: 'POST', path: '/setup/check-provider' },
221
+ doctor: { description: 'Run local setup diagnostics', method: 'GET', path: '/setup/doctor' },
222
+ },
223
+ },
224
+ skills: {
225
+ description: 'SwarmClaw and Claude skills',
226
+ commands: {
227
+ list: { description: 'List SwarmClaw skills', method: 'GET', path: '/skills' },
228
+ get: { description: 'Get SwarmClaw skill by id', method: 'GET', path: '/skills/:id', params: ['id'] },
229
+ create: { description: 'Create SwarmClaw skill', method: 'POST', path: '/skills' },
230
+ update: { description: 'Update SwarmClaw skill', method: 'PUT', path: '/skills/:id', params: ['id'] },
231
+ delete: { description: 'Delete SwarmClaw skill', method: 'DELETE', path: '/skills/:id', params: ['id'] },
232
+ import: { description: 'Import skill from URL', method: 'POST', path: '/skills/import' },
233
+ claude: { description: 'List local ~/.claude/skills', method: 'GET', path: '/claude-skills' },
234
+ },
235
+ },
236
+ system: {
237
+ description: 'System and version endpoints',
238
+ commands: {
239
+ ip: { description: 'Get local bind IP/port', method: 'GET', path: '/ip' },
240
+ usage: { description: 'Get usage summary', method: 'GET', path: '/usage' },
241
+ version: { description: 'Get local/remote git version info', method: 'GET', path: '/version' },
242
+ update: { description: 'Update to latest stable release tag (fallback: main)', method: 'POST', path: '/version/update' },
243
+ },
244
+ },
245
+ tasks: {
246
+ description: 'Task board operations',
247
+ commands: {
248
+ list: { description: 'List tasks', method: 'GET', path: '/tasks' },
249
+ get: { description: 'Get task by id', method: 'GET', path: '/tasks/:id', params: ['id'] },
250
+ create: { description: 'Create task', method: 'POST', path: '/tasks' },
251
+ update: { description: 'Update task', method: 'PUT', path: '/tasks/:id', params: ['id'] },
252
+ delete: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
253
+ archive: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
254
+ },
255
+ },
256
+ webhooks: {
257
+ description: 'Inbound webhook triggers',
258
+ commands: {
259
+ trigger: { description: 'Trigger webhook by id', method: 'POST', path: '/webhooks/:id', params: ['id'], waitable: true },
260
+ },
261
+ },
262
+ }
263
+
264
+ const GROUP_NAMES = Object.keys(COMMAND_GROUPS)
265
+
266
+ function listCoveredRoutes() {
267
+ const routes = []
268
+ for (const group of GROUP_NAMES) {
269
+ const commands = COMMAND_GROUPS[group].commands
270
+ for (const action of Object.keys(commands)) {
271
+ const cmd = commands[action]
272
+ if (cmd.method && cmd.path) {
273
+ routes.push(`${cmd.method.toUpperCase()} ${cmd.path}`)
274
+ }
275
+ }
276
+ }
277
+ return routes
278
+ }
279
+
280
+ module.exports = {
281
+ COMMAND_GROUPS,
282
+ GROUP_NAMES,
283
+ listCoveredRoutes,
284
+ }
@@ -0,0 +1,219 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import type { Agent } from '@/types'
5
+ import { useAppStore } from '@/stores/use-app-store'
6
+ import { useChatStore } from '@/stores/use-chat-store'
7
+ import { api } from '@/lib/api-client'
8
+ import { createAgent, deleteAgent } from '@/lib/agents'
9
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
10
+ import {
11
+ DropdownMenu,
12
+ DropdownMenuContent,
13
+ DropdownMenuItem,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuTrigger,
16
+ } from '@/components/ui/dropdown-menu'
17
+ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
18
+
19
+ interface Props {
20
+ agent: Agent
21
+ isDefault?: boolean
22
+ onSetDefault?: (id: string) => void
23
+ }
24
+
25
+ export function AgentCard({ agent, isDefault, onSetDefault }: Props) {
26
+ const setEditingAgentId = useAppStore((s) => s.setEditingAgentId)
27
+ const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
28
+ const loadSessions = useAppStore((s) => s.loadSessions)
29
+ const loadAgents = useAppStore((s) => s.loadAgents)
30
+ const setCurrentSession = useAppStore((s) => s.setCurrentSession)
31
+ const setActiveView = useAppStore((s) => s.setActiveView)
32
+ const setMessages = useChatStore((s) => s.setMessages)
33
+ const [running, setRunning] = useState(false)
34
+ const [dialogOpen, setDialogOpen] = useState(false)
35
+ const [taskInput, setTaskInput] = useState('')
36
+ const [confirmDelete, setConfirmDelete] = useState(false)
37
+
38
+ const handleClick = () => {
39
+ setEditingAgentId(agent.id)
40
+ setAgentSheetOpen(true)
41
+ }
42
+
43
+ const handleRunClick = (e: React.MouseEvent) => {
44
+ e.stopPropagation()
45
+ setTaskInput('')
46
+ setDialogOpen(true)
47
+ }
48
+
49
+ const handleConfirmRun = async () => {
50
+ if (!taskInput.trim()) return
51
+ setDialogOpen(false)
52
+ setRunning(true)
53
+ try {
54
+ const result = await api<{ ok: boolean; sessionId: string }>('POST', '/orchestrator/run', { agentId: agent.id, task: taskInput })
55
+ if (result.sessionId) {
56
+ await loadSessions()
57
+ setMessages([])
58
+ setCurrentSession(result.sessionId)
59
+ setActiveView('sessions')
60
+ }
61
+ } catch (err) {
62
+ console.error('Orchestrator run failed:', err)
63
+ }
64
+ setRunning(false)
65
+ }
66
+
67
+ const handleDuplicate = async () => {
68
+ const { id: _id, createdAt: _ca, updatedAt: _ua, ...rest } = agent
69
+ await createAgent({ ...rest, name: agent.name + ' (Copy)' })
70
+ await loadAgents()
71
+ }
72
+
73
+ const handleDelete = async () => {
74
+ await deleteAgent(agent.id)
75
+ await loadAgents()
76
+ setConfirmDelete(false)
77
+ }
78
+
79
+ return (
80
+ <>
81
+ <div
82
+ onClick={handleClick}
83
+ className="group relative py-3.5 px-4 cursor-pointer rounded-[14px]
84
+ transition-all duration-200 active:scale-[0.98]
85
+ bg-transparent border border-transparent hover:bg-white/[0.05] hover:border-white/[0.08]"
86
+ >
87
+ {/* Three-dot dropdown */}
88
+ <DropdownMenu>
89
+ <DropdownMenuTrigger asChild>
90
+ <button
91
+ onClick={(e) => e.stopPropagation()}
92
+ aria-label="Agent options"
93
+ className="absolute top-3 right-3 p-0.5 rounded-[6px] opacity-0 group-hover:opacity-60 hover:!opacity-100
94
+ transition-opacity bg-transparent border-none cursor-pointer text-text-3 hover:bg-white/[0.06]"
95
+ >
96
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
97
+ <circle cx="12" cy="5" r="2" />
98
+ <circle cx="12" cy="12" r="2" />
99
+ <circle cx="12" cy="19" r="2" />
100
+ </svg>
101
+ </button>
102
+ </DropdownMenuTrigger>
103
+ <DropdownMenuContent align="end" className="min-w-[140px]">
104
+ <DropdownMenuItem onClick={handleClick}>Edit</DropdownMenuItem>
105
+ <DropdownMenuItem onClick={handleDuplicate}>Duplicate</DropdownMenuItem>
106
+ {!isDefault && onSetDefault && (
107
+ <DropdownMenuItem onClick={() => onSetDefault(agent.id)}>Set Default</DropdownMenuItem>
108
+ )}
109
+ <DropdownMenuSeparator />
110
+ <DropdownMenuItem
111
+ onClick={() => setConfirmDelete(true)}
112
+ className="text-red-400 focus:text-red-400"
113
+ >
114
+ Delete
115
+ </DropdownMenuItem>
116
+ </DropdownMenuContent>
117
+ </DropdownMenu>
118
+
119
+ <div className="flex items-center gap-2.5">
120
+ <span className="font-display text-[14px] font-600 truncate flex-1 tracking-[-0.01em]">{agent.name}</span>
121
+ {isDefault && (
122
+ <span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-accent-bright bg-accent-soft px-2 py-0.5 rounded-[6px]">
123
+ default
124
+ </span>
125
+ )}
126
+ {agent.isOrchestrator && (
127
+ <button
128
+ onClick={handleRunClick}
129
+ disabled={running}
130
+ className="shrink-0 text-[10px] font-600 uppercase tracking-wider px-2.5 py-1 rounded-[6px] cursor-pointer
131
+ transition-all border-none bg-[#6366F1]/20 text-[#818CF8] hover:bg-[#6366F1]/30 disabled:opacity-40"
132
+ style={{ fontFamily: 'inherit' }}
133
+ >
134
+ {running ? '...' : 'Run'}
135
+ </button>
136
+ )}
137
+ {agent.isOrchestrator && (
138
+ <span className="shrink-0 text-[10px] font-600 uppercase tracking-wider text-amber-400/80 bg-amber-400/[0.08] px-2 py-0.5 rounded-[6px]">
139
+ orch
140
+ </span>
141
+ )}
142
+ </div>
143
+ <div className="text-[12px] text-text-3/70 mt-1.5 truncate">{agent.description}</div>
144
+ <div className="flex items-center gap-2 mt-1.5">
145
+ <span className="text-[11px] text-text-3/60 font-mono">{agent.model || agent.provider}</span>
146
+ {agent.tools?.includes('browser') && (
147
+ <span className="text-[10px] font-600 uppercase tracking-wider text-sky-400/70 bg-sky-400/[0.08] px-1.5 py-0.5 rounded-[5px]">
148
+ browser
149
+ </span>
150
+ )}
151
+ </div>
152
+ <div className="flex items-center gap-3 mt-1.5 text-[11px] text-text-3/50">
153
+ {(agent as any).lastUsedAt ? (
154
+ <span>Last used: {(() => {
155
+ const days = Math.floor((Date.now() - (agent as any).lastUsedAt) / 86400000)
156
+ return days === 0 ? 'today' : `${days}d ago`
157
+ })()}</span>
158
+ ) : (agent as any).updatedAt ? (
159
+ <span>Updated: {(() => {
160
+ const days = Math.floor((Date.now() - agent.updatedAt) / 86400000)
161
+ return days === 0 ? 'today' : `${days}d ago`
162
+ })()}</span>
163
+ ) : null}
164
+ {(agent as any).totalCost != null && (agent as any).totalCost > 0 && (
165
+ <span>Cost: ${((agent as any).totalCost as number).toFixed(2)}</span>
166
+ )}
167
+ </div>
168
+ </div>
169
+
170
+ <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
171
+ <DialogContent className="sm:max-w-[420px]">
172
+ <DialogHeader>
173
+ <DialogTitle>Run Orchestrator</DialogTitle>
174
+ </DialogHeader>
175
+ <div className="py-3">
176
+ <label className="block text-[12px] font-600 text-text-3 mb-2">Task for {agent.name}</label>
177
+ <input
178
+ type="text"
179
+ value={taskInput}
180
+ onChange={(e) => setTaskInput(e.target.value)}
181
+ onKeyDown={(e) => { if (e.key === 'Enter') handleConfirmRun() }}
182
+ placeholder="Describe the task..."
183
+ autoFocus
184
+ className="w-full px-4 py-3 rounded-[12px] border border-white/[0.08] bg-surface text-text text-[14px] outline-none transition-all placeholder:text-text-3/50 focus:border-white/[0.15]"
185
+ style={{ fontFamily: 'inherit' }}
186
+ />
187
+ </div>
188
+ <DialogFooter>
189
+ <button
190
+ onClick={() => setDialogOpen(false)}
191
+ className="px-4 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[13px] font-600 cursor-pointer hover:bg-surface-2 transition-all"
192
+ style={{ fontFamily: 'inherit' }}
193
+ >
194
+ Cancel
195
+ </button>
196
+ <button
197
+ onClick={handleConfirmRun}
198
+ disabled={!taskInput.trim()}
199
+ className="px-4 py-2 rounded-[10px] border-none bg-[#6366F1] text-white text-[13px] font-600 cursor-pointer disabled:opacity-30 transition-all hover:brightness-110"
200
+ style={{ fontFamily: 'inherit' }}
201
+ >
202
+ Run
203
+ </button>
204
+ </DialogFooter>
205
+ </DialogContent>
206
+ </Dialog>
207
+
208
+ <ConfirmDialog
209
+ open={confirmDelete}
210
+ title="Delete Agent"
211
+ message={`Are you sure you want to delete "${agent.name}"? This cannot be undone.`}
212
+ confirmLabel="Delete"
213
+ danger
214
+ onConfirm={handleDelete}
215
+ onCancel={() => setConfirmDelete(false)}
216
+ />
217
+ </>
218
+ )
219
+ }
@@ -0,0 +1,165 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useMemo, useState } from 'react'
4
+ import { useAppStore } from '@/stores/use-app-store'
5
+ import { useChatStore } from '@/stores/use-chat-store'
6
+ import { fetchMessages } from '@/lib/sessions'
7
+ import type { Agent, Session } from '@/types'
8
+
9
+ interface Props {
10
+ inSidebar?: boolean
11
+ onSelect?: () => void
12
+ }
13
+
14
+ export function AgentChatList({ inSidebar, onSelect }: Props) {
15
+ const agents = useAppStore((s) => s.agents)
16
+ const sessions = useAppStore((s) => s.sessions)
17
+ const loadAgents = useAppStore((s) => s.loadAgents)
18
+ const currentAgentId = useAppStore((s) => s.currentAgentId)
19
+ const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
20
+ const setMessages = useChatStore((s) => s.setMessages)
21
+ const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
22
+ const tasks = useAppStore((s) => s.tasks)
23
+ const streamingSessionId = useChatStore((s) => s.streamingSessionId)
24
+ const [search, setSearch] = useState('')
25
+
26
+ useEffect(() => { loadAgents() }, [loadAgents])
27
+
28
+ // Build agent list sorted by last activity in their thread session
29
+ const sortedAgents = useMemo(() => {
30
+ return Object.values(agents)
31
+ .filter((a) => {
32
+ if (search && !a.name.toLowerCase().includes(search.toLowerCase())) return false
33
+ return true
34
+ })
35
+ .sort((a, b) => {
36
+ const aSession = a.threadSessionId ? sessions[a.threadSessionId] : null
37
+ const bSession = b.threadSessionId ? sessions[b.threadSessionId] : null
38
+ const aTime = (aSession as Session | null)?.lastActiveAt || a.updatedAt
39
+ const bTime = (bSession as Session | null)?.lastActiveAt || b.updatedAt
40
+ return bTime - aTime
41
+ })
42
+ }, [agents, sessions, search])
43
+
44
+ // Compute running tasks per agent
45
+ const runningAgentIds = useMemo(() => {
46
+ const set = new Set<string>()
47
+ for (const task of Object.values(tasks)) {
48
+ if (task.status === 'running' && task.agentId) set.add(task.agentId)
49
+ }
50
+ return set
51
+ }, [tasks])
52
+
53
+ const handleSelect = async (agent: Agent) => {
54
+ await setCurrentAgent(agent.id)
55
+ // Load messages for the thread
56
+ const state = useAppStore.getState()
57
+ if (state.currentSessionId) {
58
+ try {
59
+ const msgs = await fetchMessages(state.currentSessionId)
60
+ setMessages(msgs)
61
+ } catch { /* ignore */ }
62
+ }
63
+ onSelect?.()
64
+ // Delay scroll so React renders the new messages first
65
+ if (typeof window !== 'undefined') {
66
+ setTimeout(() => {
67
+ window.dispatchEvent(new CustomEvent('swarmclaw:scroll-bottom'))
68
+ }, 100)
69
+ }
70
+ }
71
+
72
+ if (!sortedAgents.length && !search) {
73
+ return (
74
+ <div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
75
+ <div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
76
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
77
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
78
+ <circle cx="12" cy="7" r="4" />
79
+ </svg>
80
+ </div>
81
+ <p className="font-display text-[15px] font-600 text-text-2">No agents yet</p>
82
+ <p className="text-[13px] text-text-3/50">Create agents to start chatting</p>
83
+ {!inSidebar && (
84
+ <button
85
+ onClick={() => setAgentSheetOpen(true)}
86
+ className="mt-3 px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white
87
+ text-[14px] font-600 cursor-pointer active:scale-95 transition-all duration-200
88
+ shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
89
+ style={{ fontFamily: 'inherit' }}
90
+ >
91
+ + New Agent
92
+ </button>
93
+ )}
94
+ </div>
95
+ )
96
+ }
97
+
98
+ return (
99
+ <div className="flex-1 overflow-y-auto">
100
+ {(sortedAgents.length > 5 || search) && (
101
+ <div className="px-4 py-2.5">
102
+ <input
103
+ type="text"
104
+ value={search}
105
+ onChange={(e) => setSearch(e.target.value)}
106
+ placeholder="Search agents..."
107
+ className="w-full px-4 py-2.5 rounded-[12px] border border-white/[0.04] bg-surface text-text
108
+ text-[13px] outline-none transition-all duration-200 placeholder:text-text-3/70 focus-glow"
109
+ style={{ fontFamily: 'inherit' }}
110
+ />
111
+ </div>
112
+ )}
113
+ <div className="flex flex-col gap-0.5 px-2 pb-4">
114
+ {sortedAgents.map((agent) => {
115
+ const threadSession = agent.threadSessionId ? sessions[agent.threadSessionId] as Session | undefined : undefined
116
+ const lastMsg = threadSession?.messages?.at(-1)
117
+ const isActive = currentAgentId === agent.id
118
+ const isWorking = runningAgentIds.has(agent.id) || (threadSession?.active ?? false) || (threadSession?.heartbeatEnabled ?? false)
119
+ const isTyping = streamingSessionId === agent.threadSessionId
120
+ const preview = lastMsg?.text?.slice(0, 80)?.replace(/\n/g, ' ') || ''
121
+
122
+ return (
123
+ <button
124
+ key={agent.id}
125
+ onClick={() => handleSelect(agent)}
126
+ className={`w-full text-left py-3 px-3.5 rounded-[12px] cursor-pointer transition-all duration-150 border-none
127
+ ${isActive
128
+ ? 'bg-accent-soft/80 border border-accent-bright/20'
129
+ : 'bg-transparent hover:bg-white/[0.02]'}`}
130
+ style={{ fontFamily: 'inherit' }}
131
+ >
132
+ <div className="flex items-center gap-2.5">
133
+ {/* Status dot */}
134
+ <div className={`w-2 h-2 rounded-full shrink-0 ${
135
+ isWorking ? 'bg-emerald-400 shadow-[0_0_6px_rgba(52,211,153,0.4)]' : 'bg-text-3/20'
136
+ }`} />
137
+ <span className="font-display text-[13.5px] font-600 truncate flex-1 tracking-[-0.01em]">
138
+ {agent.name}
139
+ </span>
140
+ {/* Provider badge */}
141
+ <span className="text-[10px] text-text-3/60 font-mono shrink-0">
142
+ {agent.model ? agent.model.split('/').pop()?.split(':')[0] : agent.provider}
143
+ </span>
144
+ </div>
145
+ {isTyping ? (
146
+ <div className="text-[12px] text-accent-bright/70 mt-1 pl-[18px] flex items-center gap-1.5">
147
+ <span className="flex gap-0.5">
148
+ <span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:0ms]" />
149
+ <span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:150ms]" />
150
+ <span className="w-1 h-1 rounded-full bg-accent-bright/70 animate-bounce [animation-delay:300ms]" />
151
+ </span>
152
+ Typing...
153
+ </div>
154
+ ) : preview ? (
155
+ <div className="text-[12px] text-text-3/70 mt-1 truncate pl-[18px]">
156
+ {preview}
157
+ </div>
158
+ ) : null}
159
+ </button>
160
+ )
161
+ })}
162
+ </div>
163
+ </div>
164
+ )
165
+ }