@swarmclawai/swarmclaw 0.5.3 → 0.6.2

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 (224) hide show
  1. package/README.md +53 -9
  2. package/bin/server-cmd.js +1 -0
  3. package/bin/swarmclaw.js +76 -16
  4. package/next.config.ts +11 -1
  5. package/package.json +5 -2
  6. package/scripts/postinstall.mjs +18 -0
  7. package/src/app/api/canvas/[sessionId]/route.ts +31 -0
  8. package/src/app/api/chatrooms/[id]/chat/route.ts +284 -0
  9. package/src/app/api/chatrooms/[id]/members/route.ts +82 -0
  10. package/src/app/api/chatrooms/[id]/pins/route.ts +39 -0
  11. package/src/app/api/chatrooms/[id]/reactions/route.ts +42 -0
  12. package/src/app/api/chatrooms/[id]/route.ts +84 -0
  13. package/src/app/api/chatrooms/route.ts +50 -0
  14. package/src/app/api/connectors/[id]/route.ts +1 -0
  15. package/src/app/api/connectors/route.ts +2 -1
  16. package/src/app/api/credentials/route.ts +2 -3
  17. package/src/app/api/files/open/route.ts +43 -0
  18. package/src/app/api/knowledge/[id]/route.ts +13 -2
  19. package/src/app/api/knowledge/route.ts +8 -1
  20. package/src/app/api/memory/route.ts +8 -0
  21. package/src/app/api/notifications/route.ts +4 -0
  22. package/src/app/api/orchestrator/run/route.ts +1 -1
  23. package/src/app/api/plugins/install/route.ts +2 -2
  24. package/src/app/api/search/route.ts +53 -1
  25. package/src/app/api/sessions/[id]/chat/route.ts +2 -0
  26. package/src/app/api/sessions/[id]/edit-resend/route.ts +1 -1
  27. package/src/app/api/sessions/[id]/fork/route.ts +1 -1
  28. package/src/app/api/sessions/[id]/messages/route.ts +70 -2
  29. package/src/app/api/sessions/[id]/route.ts +4 -0
  30. package/src/app/api/sessions/route.ts +3 -3
  31. package/src/app/api/settings/route.ts +9 -0
  32. package/src/app/api/setup/check-provider/route.ts +3 -16
  33. package/src/app/api/skills/[id]/route.ts +6 -0
  34. package/src/app/api/skills/route.ts +6 -0
  35. package/src/app/api/tasks/[id]/route.ts +12 -0
  36. package/src/app/api/tasks/bulk/route.ts +100 -0
  37. package/src/app/api/tasks/metrics/route.ts +101 -0
  38. package/src/app/api/tasks/route.ts +18 -2
  39. package/src/app/api/tts/route.ts +3 -2
  40. package/src/app/api/tts/stream/route.ts +3 -2
  41. package/src/app/api/uploads/[filename]/route.ts +19 -34
  42. package/src/app/api/uploads/route.ts +94 -0
  43. package/src/app/api/webhooks/[id]/route.ts +15 -1
  44. package/src/app/globals.css +63 -15
  45. package/src/app/page.tsx +142 -13
  46. package/src/cli/index.js +40 -1
  47. package/src/cli/index.test.js +30 -0
  48. package/src/cli/spec.js +42 -0
  49. package/src/components/agents/agent-avatar.tsx +57 -10
  50. package/src/components/agents/agent-card.tsx +50 -17
  51. package/src/components/agents/agent-chat-list.tsx +148 -12
  52. package/src/components/agents/agent-list.tsx +50 -19
  53. package/src/components/agents/agent-sheet.tsx +120 -65
  54. package/src/components/agents/inspector-panel.tsx +81 -6
  55. package/src/components/agents/openclaw-skills-panel.tsx +32 -3
  56. package/src/components/agents/personality-builder.tsx +42 -14
  57. package/src/components/agents/soul-library-picker.tsx +89 -0
  58. package/src/components/auth/access-key-gate.tsx +10 -3
  59. package/src/components/auth/setup-wizard.tsx +2 -2
  60. package/src/components/auth/user-picker.tsx +31 -3
  61. package/src/components/canvas/canvas-panel.tsx +96 -0
  62. package/src/components/chat/activity-moment.tsx +173 -0
  63. package/src/components/chat/chat-area.tsx +46 -22
  64. package/src/components/chat/chat-header.tsx +457 -286
  65. package/src/components/chat/chat-preview-panel.tsx +1 -2
  66. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  67. package/src/components/chat/delegation-banner.tsx +371 -0
  68. package/src/components/chat/file-path-chip.tsx +146 -0
  69. package/src/components/chat/heartbeat-history-panel.tsx +269 -0
  70. package/src/components/chat/markdown-utils.ts +9 -0
  71. package/src/components/chat/message-bubble.tsx +356 -315
  72. package/src/components/chat/message-list.tsx +230 -8
  73. package/src/components/chat/streaming-bubble.tsx +104 -47
  74. package/src/components/chat/suggestions-bar.tsx +1 -1
  75. package/src/components/chat/thinking-indicator.tsx +72 -10
  76. package/src/components/chat/tool-call-bubble.tsx +111 -73
  77. package/src/components/chat/tool-request-banner.tsx +31 -7
  78. package/src/components/chat/transfer-agent-picker.tsx +63 -0
  79. package/src/components/chatrooms/agent-hover-card.tsx +124 -0
  80. package/src/components/chatrooms/chatroom-input.tsx +320 -0
  81. package/src/components/chatrooms/chatroom-list.tsx +130 -0
  82. package/src/components/chatrooms/chatroom-message.tsx +432 -0
  83. package/src/components/chatrooms/chatroom-sheet.tsx +215 -0
  84. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +134 -0
  85. package/src/components/chatrooms/chatroom-typing-bar.tsx +88 -0
  86. package/src/components/chatrooms/chatroom-view.tsx +344 -0
  87. package/src/components/chatrooms/reaction-picker.tsx +273 -0
  88. package/src/components/connectors/connector-list.tsx +168 -90
  89. package/src/components/connectors/connector-sheet.tsx +95 -56
  90. package/src/components/home/home-view.tsx +501 -0
  91. package/src/components/input/chat-input.tsx +107 -43
  92. package/src/components/knowledge/knowledge-list.tsx +31 -1
  93. package/src/components/knowledge/knowledge-sheet.tsx +83 -2
  94. package/src/components/layout/app-layout.tsx +194 -97
  95. package/src/components/layout/update-banner.tsx +2 -2
  96. package/src/components/logs/log-list.tsx +2 -2
  97. package/src/components/mcp-servers/mcp-server-sheet.tsx +1 -1
  98. package/src/components/memory/memory-agent-list.tsx +143 -0
  99. package/src/components/memory/memory-browser.tsx +205 -0
  100. package/src/components/memory/memory-card.tsx +34 -7
  101. package/src/components/memory/memory-detail.tsx +359 -120
  102. package/src/components/memory/memory-sheet.tsx +157 -23
  103. package/src/components/plugins/plugin-list.tsx +1 -1
  104. package/src/components/plugins/plugin-sheet.tsx +1 -1
  105. package/src/components/projects/project-detail.tsx +509 -0
  106. package/src/components/projects/project-list.tsx +195 -59
  107. package/src/components/providers/provider-list.tsx +2 -2
  108. package/src/components/providers/provider-sheet.tsx +3 -3
  109. package/src/components/schedules/schedule-card.tsx +1 -1
  110. package/src/components/schedules/schedule-list.tsx +1 -1
  111. package/src/components/schedules/schedule-sheet.tsx +259 -126
  112. package/src/components/secrets/secret-sheet.tsx +47 -24
  113. package/src/components/secrets/secrets-list.tsx +18 -8
  114. package/src/components/sessions/new-session-sheet.tsx +33 -65
  115. package/src/components/sessions/session-card.tsx +45 -14
  116. package/src/components/sessions/session-list.tsx +35 -18
  117. package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
  118. package/src/components/shared/agent-picker-list.tsx +90 -0
  119. package/src/components/shared/agent-switch-dialog.tsx +156 -0
  120. package/src/components/shared/attachment-chip.tsx +165 -0
  121. package/src/components/shared/avatar.tsx +10 -1
  122. package/src/components/shared/chatroom-picker-list.tsx +61 -0
  123. package/src/components/shared/check-icon.tsx +12 -0
  124. package/src/components/shared/confirm-dialog.tsx +1 -1
  125. package/src/components/shared/connector-platform-icon.tsx +51 -4
  126. package/src/components/shared/empty-state.tsx +32 -0
  127. package/src/components/shared/file-preview.tsx +34 -0
  128. package/src/components/shared/form-styles.ts +2 -0
  129. package/src/components/shared/icon-button.tsx +16 -2
  130. package/src/components/shared/keyboard-shortcuts-dialog.tsx +116 -0
  131. package/src/components/shared/notification-center.tsx +44 -6
  132. package/src/components/shared/profile-sheet.tsx +115 -0
  133. package/src/components/shared/reply-quote.tsx +26 -0
  134. package/src/components/shared/search-dialog.tsx +31 -15
  135. package/src/components/shared/section-label.tsx +12 -0
  136. package/src/components/shared/settings/plugin-manager.tsx +1 -1
  137. package/src/components/shared/settings/section-embedding.tsx +48 -13
  138. package/src/components/shared/settings/section-orchestrator.tsx +46 -15
  139. package/src/components/shared/settings/section-providers.tsx +1 -1
  140. package/src/components/shared/settings/section-secrets.tsx +1 -1
  141. package/src/components/shared/settings/section-storage.tsx +206 -0
  142. package/src/components/shared/settings/section-theme.tsx +95 -0
  143. package/src/components/shared/settings/section-user-preferences.tsx +57 -0
  144. package/src/components/shared/settings/section-voice.tsx +42 -21
  145. package/src/components/shared/settings/section-web-search.tsx +30 -6
  146. package/src/components/shared/settings/settings-page.tsx +182 -27
  147. package/src/components/shared/settings/settings-sheet.tsx +9 -73
  148. package/src/components/shared/settings/storage-browser.tsx +259 -0
  149. package/src/components/shared/sheet-footer.tsx +33 -0
  150. package/src/components/skills/skill-list.tsx +61 -30
  151. package/src/components/skills/skill-sheet.tsx +81 -2
  152. package/src/components/tasks/task-board.tsx +448 -26
  153. package/src/components/tasks/task-card.tsx +59 -9
  154. package/src/components/tasks/task-column.tsx +62 -3
  155. package/src/components/tasks/task-list.tsx +12 -4
  156. package/src/components/tasks/task-sheet.tsx +416 -74
  157. package/src/components/ui/hover-card.tsx +52 -0
  158. package/src/components/usage/metrics-dashboard.tsx +90 -6
  159. package/src/components/usage/usage-list.tsx +1 -1
  160. package/src/components/webhooks/webhook-sheet.tsx +1 -1
  161. package/src/hooks/use-continuous-speech.ts +10 -4
  162. package/src/hooks/use-view-router.ts +69 -19
  163. package/src/hooks/use-voice-conversation.ts +53 -10
  164. package/src/hooks/use-ws.ts +4 -2
  165. package/src/instrumentation.ts +15 -1
  166. package/src/lib/chat.ts +2 -0
  167. package/src/lib/memory.ts +3 -0
  168. package/src/lib/providers/anthropic.ts +13 -7
  169. package/src/lib/providers/index.ts +1 -0
  170. package/src/lib/providers/openai.ts +13 -7
  171. package/src/lib/server/chat-execution.ts +75 -15
  172. package/src/lib/server/chatroom-helpers.ts +146 -0
  173. package/src/lib/server/connectors/manager.ts +229 -7
  174. package/src/lib/server/context-manager.ts +225 -13
  175. package/src/lib/server/create-notification.ts +14 -2
  176. package/src/lib/server/daemon-state.ts +157 -10
  177. package/src/lib/server/execution-log.ts +1 -0
  178. package/src/lib/server/heartbeat-service.ts +48 -6
  179. package/src/lib/server/heartbeat-wake.ts +110 -0
  180. package/src/lib/server/langgraph-checkpoint.ts +1 -0
  181. package/src/lib/server/main-agent-loop.ts +1 -1
  182. package/src/lib/server/memory-consolidation.ts +105 -0
  183. package/src/lib/server/memory-db.ts +183 -10
  184. package/src/lib/server/mime.ts +51 -0
  185. package/src/lib/server/openclaw-gateway.ts +9 -1
  186. package/src/lib/server/orchestrator-lg.ts +2 -0
  187. package/src/lib/server/orchestrator.ts +5 -2
  188. package/src/lib/server/playwright-proxy.mjs +2 -3
  189. package/src/lib/server/prompt-runtime-context.ts +53 -0
  190. package/src/lib/server/provider-health.ts +125 -0
  191. package/src/lib/server/queue.ts +56 -10
  192. package/src/lib/server/scheduler.ts +8 -0
  193. package/src/lib/server/session-run-manager.ts +4 -0
  194. package/src/lib/server/session-tools/canvas.ts +67 -0
  195. package/src/lib/server/session-tools/chatroom.ts +136 -0
  196. package/src/lib/server/session-tools/connector.ts +83 -9
  197. package/src/lib/server/session-tools/context-mgmt.ts +36 -18
  198. package/src/lib/server/session-tools/crud.ts +21 -0
  199. package/src/lib/server/session-tools/delegate.ts +68 -4
  200. package/src/lib/server/session-tools/git.ts +71 -0
  201. package/src/lib/server/session-tools/http.ts +57 -0
  202. package/src/lib/server/session-tools/index.ts +10 -0
  203. package/src/lib/server/session-tools/memory.ts +7 -1
  204. package/src/lib/server/session-tools/search-providers.ts +16 -8
  205. package/src/lib/server/session-tools/subagent.ts +106 -0
  206. package/src/lib/server/session-tools/web.ts +115 -4
  207. package/src/lib/server/storage.ts +53 -29
  208. package/src/lib/server/stream-agent-chat.ts +185 -57
  209. package/src/lib/server/system-events.ts +49 -0
  210. package/src/lib/server/task-mention.ts +41 -0
  211. package/src/lib/server/ws-hub.ts +11 -0
  212. package/src/lib/sessions.ts +10 -0
  213. package/src/lib/soul-library.ts +103 -0
  214. package/src/lib/soul-suggestions.ts +109 -0
  215. package/src/lib/task-dedupe.ts +26 -0
  216. package/src/lib/tasks.ts +4 -1
  217. package/src/lib/tool-definitions.ts +2 -0
  218. package/src/lib/tts.ts +2 -2
  219. package/src/lib/view-routes.ts +36 -1
  220. package/src/lib/ws-client.ts +14 -4
  221. package/src/stores/use-app-store.ts +41 -3
  222. package/src/stores/use-chat-store.ts +113 -5
  223. package/src/stores/use-chatroom-store.ts +276 -0
  224. package/src/types/index.ts +88 -4
@@ -2,20 +2,45 @@
2
2
 
3
3
  import { useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
+ import type { Agent, BoardTask, Schedule } from '@/types'
6
+
7
+ function relativeDate(ts: number): string {
8
+ const diff = Date.now() - ts
9
+ if (diff < 60_000) return 'just now'
10
+ if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`
11
+ if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`
12
+ if (diff < 604_800_000) return `${Math.floor(diff / 86_400_000)}d ago`
13
+ return new Date(ts).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
14
+ }
15
+
16
+ interface ProjectStats {
17
+ agents: number
18
+ tasks: number
19
+ completedTasks: number
20
+ schedules: number
21
+ lastActivity: number
22
+ }
5
23
 
6
24
  export function ProjectList() {
7
25
  const projects = useAppStore((s) => s.projects)
8
26
  const loadProjects = useAppStore((s) => s.loadProjects)
9
- const agents = useAppStore((s) => s.agents)
10
- const tasks = useAppStore((s) => s.tasks)
27
+ const agents = useAppStore((s) => s.agents) as Record<string, Agent>
28
+ const tasks = useAppStore((s) => s.tasks) as Record<string, BoardTask>
29
+ const schedules = useAppStore((s) => s.schedules) as Record<string, Schedule>
11
30
  const setProjectSheetOpen = useAppStore((s) => s.setProjectSheetOpen)
12
31
  const setEditingProjectId = useAppStore((s) => s.setEditingProjectId)
13
32
  const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
14
33
  const setActiveProjectFilter = useAppStore((s) => s.setActiveProjectFilter)
34
+ const loadTasks = useAppStore((s) => s.loadTasks)
35
+ const loadSchedules = useAppStore((s) => s.loadSchedules)
15
36
  const [search, setSearch] = useState('')
16
37
 
17
- // eslint-disable-next-line react-hooks/exhaustive-deps
18
- useEffect(() => { loadProjects() }, [])
38
+ useEffect(() => {
39
+ loadProjects()
40
+ loadTasks()
41
+ loadSchedules()
42
+ // eslint-disable-next-line react-hooks/exhaustive-deps
43
+ }, [])
19
44
 
20
45
  const filtered = useMemo(() => {
21
46
  return Object.values(projects)
@@ -26,97 +51,208 @@ export function ProjectList() {
26
51
  .sort((a, b) => b.updatedAt - a.updatedAt)
27
52
  }, [projects, search])
28
53
 
29
- const entityCounts = useMemo(() => {
30
- const counts: Record<string, { agents: number; tasks: number }> = {}
54
+ const statsMap = useMemo(() => {
55
+ const map: Record<string, ProjectStats> = {}
31
56
  for (const p of Object.values(projects)) {
32
- counts[p.id] = { agents: 0, tasks: 0 }
57
+ map[p.id] = { agents: 0, tasks: 0, completedTasks: 0, schedules: 0, lastActivity: p.updatedAt }
33
58
  }
34
59
  for (const a of Object.values(agents)) {
35
- if (a.projectId && counts[a.projectId]) counts[a.projectId].agents++
60
+ if (a.projectId && map[a.projectId]) {
61
+ map[a.projectId].agents++
62
+ if (a.updatedAt && a.updatedAt > map[a.projectId].lastActivity) {
63
+ map[a.projectId].lastActivity = a.updatedAt
64
+ }
65
+ }
36
66
  }
37
67
  for (const t of Object.values(tasks)) {
38
- if (t.projectId && counts[t.projectId]) counts[t.projectId].tasks++
68
+ if (t.projectId && map[t.projectId]) {
69
+ map[t.projectId].tasks++
70
+ if (t.status === 'completed') map[t.projectId].completedTasks++
71
+ if (t.updatedAt && t.updatedAt > map[t.projectId].lastActivity) {
72
+ map[t.projectId].lastActivity = t.updatedAt
73
+ }
74
+ }
75
+ }
76
+ for (const s of Object.values(schedules)) {
77
+ if (s.projectId && map[s.projectId]) {
78
+ map[s.projectId].schedules++
79
+ }
39
80
  }
40
- return counts
41
- }, [projects, agents, tasks])
81
+ return map
82
+ }, [projects, agents, tasks, schedules])
83
+
84
+ // Summary stats
85
+ const totalProjects = Object.keys(projects).length
86
+ const totalTasks = Object.values(tasks).filter((t) => t.projectId).length
87
+ const totalCompleted = Object.values(tasks).filter((t) => t.projectId && t.status === 'completed').length
42
88
 
43
89
  if (!filtered.length && !search) {
44
90
  return (
45
91
  <div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center">
46
- <div className="w-12 h-12 rounded-[14px] bg-accent-soft flex items-center justify-center mb-1">
47
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-accent-bright">
92
+ <div className="w-14 h-14 rounded-[16px] bg-accent-soft flex items-center justify-center mb-1">
93
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-accent-bright">
48
94
  <path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7-7H4a2 2 0 0 0-2 2v17Z" />
49
95
  <path d="M14 2v7h7" />
50
96
  </svg>
51
97
  </div>
52
- <p className="font-display text-[15px] font-600 text-text-2">No projects yet</p>
53
- <p className="text-[13px] text-text-3/50">Group agents, tasks, and schedules into projects</p>
98
+ <p className="font-display text-[16px] font-600 text-text-2">No projects yet</p>
99
+ <p className="text-[13px] text-text-3/60 max-w-[280px]">
100
+ Projects group your agents, tasks, and schedules together. Create one to get organized.
101
+ </p>
54
102
  <button
55
103
  onClick={() => { setEditingProjectId(null); setProjectSheetOpen(true) }}
56
- className="inline-flex items-center gap-1.5 px-4 py-2 text-[13px] font-500 text-white bg-accent rounded-lg hover:bg-accent-bright transition-colors"
104
+ className="inline-flex items-center gap-1.5 px-5 py-2.5 text-[13px] font-600 text-white bg-accent-bright rounded-[10px] hover:brightness-110 transition-all cursor-pointer border-none"
105
+ style={{ fontFamily: 'inherit' }}
57
106
  >
58
- <span className="text-lg leading-none">+</span> New Project
107
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
108
+ <line x1="12" y1="5" x2="12" y2="19" />
109
+ <line x1="5" y1="12" x2="19" y2="12" />
110
+ </svg>
111
+ New Project
59
112
  </button>
60
113
  </div>
61
114
  )
62
115
  }
63
116
 
64
117
  return (
65
- <div className="flex-1 flex flex-col h-full overflow-y-auto">
66
- <div className="p-4 pb-0">
67
- <div className="flex items-center gap-2 mb-4">
118
+ <div className="flex-1 flex flex-col h-full overflow-hidden">
119
+ {/* Header with search and new button */}
120
+ <div className="px-5 pt-5 pb-3 shrink-0">
121
+ <div className="flex items-center justify-between mb-4">
122
+ <div>
123
+ <h2 className="font-display text-[20px] font-700 text-text tracking-[-0.02em]">Projects</h2>
124
+ <p className="text-[12px] text-text-3/60 mt-0.5">
125
+ {totalProjects} project{totalProjects !== 1 ? 's' : ''}
126
+ {totalTasks > 0 && <> &middot; {totalCompleted}/{totalTasks} tasks done</>}
127
+ </p>
128
+ </div>
129
+ <button
130
+ onClick={() => { setEditingProjectId(null); setProjectSheetOpen(true) }}
131
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 text-[12px] font-600 text-white bg-accent-bright rounded-[10px] hover:brightness-110 transition-all cursor-pointer border-none"
132
+ style={{ fontFamily: 'inherit' }}
133
+ >
134
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
135
+ <line x1="12" y1="5" x2="12" y2="19" />
136
+ <line x1="5" y1="12" x2="19" y2="12" />
137
+ </svg>
138
+ New
139
+ </button>
140
+ </div>
141
+
142
+ {/* Search */}
143
+ <div className="relative">
144
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="absolute left-3 top-1/2 -translate-y-1/2 text-text-3/50">
145
+ <circle cx="11" cy="11" r="8" />
146
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
147
+ </svg>
68
148
  <input
69
149
  type="text"
70
150
  value={search}
71
151
  onChange={(e) => setSearch(e.target.value)}
72
152
  placeholder="Search projects..."
73
- className="flex-1 px-3 py-2 rounded-lg bg-white/[0.06] border border-white/[0.06] text-[13px] text-text-1 placeholder:text-text-3/40 focus:outline-none focus:border-accent/40"
153
+ className="w-full pl-9 pr-3 py-2.5 rounded-[10px] bg-white/[0.04] border border-white/[0.06] text-[13px] text-text placeholder:text-text-3/40 focus:outline-none focus:border-accent-bright/30 transition-colors"
74
154
  style={{ fontFamily: 'inherit' }}
75
155
  />
76
156
  </div>
77
157
  </div>
78
- <div className="flex-1 overflow-y-auto px-4 pb-4 space-y-2">
79
- {filtered.map((project) => {
80
- const counts = entityCounts[project.id] || { agents: 0, tasks: 0 }
81
- const isActive = activeProjectFilter === project.id
82
- return (
83
- <div
84
- key={project.id}
85
- className={`group relative p-4 rounded-xl border transition-colors cursor-pointer ${
86
- isActive
87
- ? 'bg-accent/10 border-accent/30'
88
- : 'bg-white/[0.03] border-white/[0.06] hover:bg-white/[0.06]'
89
- }`}
90
- onClick={() => setActiveProjectFilter(isActive ? null : project.id)}
91
- >
92
- <div className="flex items-start justify-between gap-3">
93
- <div className="flex items-center gap-2.5 min-w-0">
94
- {project.color && (
95
- <div className="w-3 h-3 rounded-full shrink-0" style={{ backgroundColor: project.color }} />
96
- )}
97
- <div className="min-w-0">
98
- <div className="font-display text-[14px] font-600 text-text-1 truncate">{project.name}</div>
99
- {project.description && (
100
- <p className="text-[12px] text-text-3/60 mt-0.5 line-clamp-2">{project.description}</p>
158
+
159
+ {/* Project cards */}
160
+ <div className="flex-1 overflow-y-auto px-5 pb-5">
161
+ <div className="grid gap-3">
162
+ {filtered.map((project) => {
163
+ const stats = statsMap[project.id] || { agents: 0, tasks: 0, completedTasks: 0, schedules: 0, lastActivity: project.updatedAt }
164
+ const isActive = activeProjectFilter === project.id
165
+ const progressPct = stats.tasks > 0 ? Math.round((stats.completedTasks / stats.tasks) * 100) : 0
166
+
167
+ return (
168
+ <div
169
+ key={project.id}
170
+ className={`group relative rounded-[14px] border transition-all duration-200 cursor-pointer overflow-hidden
171
+ ${isActive
172
+ ? 'bg-white/[0.06] border-accent-bright/30 shadow-[0_0_20px_rgba(99,102,241,0.08)]'
173
+ : 'bg-white/[0.02] border-white/[0.06] hover:bg-white/[0.05] hover:border-white/[0.1]'}`}
174
+ onClick={() => setActiveProjectFilter(isActive ? null : project.id)}
175
+ >
176
+ {/* Color accent stripe */}
177
+ <div className="absolute left-0 top-0 bottom-0 w-1 rounded-l-[14px]" style={{ backgroundColor: project.color || '#6B7280' }} />
178
+
179
+ <div className="pl-5 pr-4 py-4">
180
+ <div className="flex items-start justify-between gap-3">
181
+ <div className="min-w-0 flex-1">
182
+ <div className="flex items-center gap-2">
183
+ <h3 className="font-display text-[14px] font-600 text-text truncate">{project.name}</h3>
184
+ {isActive && (
185
+ <span className="shrink-0 text-[9px] font-700 uppercase tracking-wider text-accent-bright bg-accent-soft px-1.5 py-0.5 rounded-[5px]">
186
+ active filter
187
+ </span>
188
+ )}
189
+ </div>
190
+ {project.description && (
191
+ <p className="text-[12px] text-text-3/60 mt-1 line-clamp-2 leading-relaxed">{project.description}</p>
192
+ )}
193
+ </div>
194
+ <button
195
+ onClick={(e) => { e.stopPropagation(); setEditingProjectId(project.id); setProjectSheetOpen(true) }}
196
+ className="opacity-0 group-hover:opacity-100 p-1.5 rounded-[8px] hover:bg-white/[0.08] transition-all text-text-3/50 hover:text-text-2 cursor-pointer bg-transparent border-none shrink-0"
197
+ >
198
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
199
+ <path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
200
+ </svg>
201
+ </button>
202
+ </div>
203
+
204
+ {/* Stats row */}
205
+ <div className="flex items-center gap-4 mt-3 text-[11px] text-text-3/50">
206
+ <span className="flex items-center gap-1.5">
207
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
208
+ <path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
209
+ <circle cx="9" cy="7" r="4" />
210
+ </svg>
211
+ {stats.agents} agent{stats.agents !== 1 ? 's' : ''}
212
+ </span>
213
+ <span className="flex items-center gap-1.5">
214
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
215
+ <path d="M9 11l3 3L22 4" />
216
+ <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" />
217
+ </svg>
218
+ {stats.completedTasks}/{stats.tasks} task{stats.tasks !== 1 ? 's' : ''}
219
+ </span>
220
+ {stats.schedules > 0 && (
221
+ <span className="flex items-center gap-1.5">
222
+ <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
223
+ <circle cx="12" cy="12" r="10" />
224
+ <polyline points="12 6 12 12 16 14" />
225
+ </svg>
226
+ {stats.schedules} schedule{stats.schedules !== 1 ? 's' : ''}
227
+ </span>
101
228
  )}
229
+ <span className="ml-auto text-text-3/40">
230
+ {relativeDate(stats.lastActivity)}
231
+ </span>
102
232
  </div>
233
+
234
+ {/* Progress bar */}
235
+ {stats.tasks > 0 && (
236
+ <div className="mt-3 flex items-center gap-2.5">
237
+ <div className="flex-1 h-1.5 rounded-full bg-white/[0.06] overflow-hidden">
238
+ <div
239
+ className="h-full rounded-full transition-all duration-500"
240
+ style={{
241
+ width: `${progressPct}%`,
242
+ backgroundColor: progressPct === 100 ? '#22C55E' : (project.color || '#6366F1'),
243
+ }}
244
+ />
245
+ </div>
246
+ <span className={`text-[10px] font-mono font-600 ${progressPct === 100 ? 'text-emerald-400' : 'text-text-3/50'}`}>
247
+ {progressPct}%
248
+ </span>
249
+ </div>
250
+ )}
103
251
  </div>
104
- <button
105
- onClick={(e) => { e.stopPropagation(); setEditingProjectId(project.id); setProjectSheetOpen(true) }}
106
- className="opacity-0 group-hover:opacity-100 p-1.5 rounded-md hover:bg-white/[0.08] transition-all text-text-3/50 hover:text-text-2"
107
- >
108
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
109
- <path d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
110
- </svg>
111
- </button>
112
252
  </div>
113
- <div className="flex items-center gap-3 mt-2.5 text-[11px] text-text-3/50">
114
- <span>{counts.agents} agent{counts.agents !== 1 ? 's' : ''}</span>
115
- <span>{counts.tasks} task{counts.tasks !== 1 ? 's' : ''}</span>
116
- </div>
117
- </div>
118
- )
119
- })}
253
+ )
254
+ })}
255
+ </div>
120
256
  </div>
121
257
  </div>
122
258
  )
@@ -86,7 +86,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
86
86
  <span className="font-display text-[14px] font-600 text-text truncate">{item.name}</span>
87
87
  <div className="flex items-center gap-2 shrink-0">
88
88
  <span className={`text-[10px] font-600 px-2 py-0.5 rounded-[5px] uppercase tracking-wider
89
- ${item.type === 'builtin' ? 'bg-white/[0.04] text-text-3' : 'bg-[#6366F1]/10 text-[#6366F1]'}`}>
89
+ ${item.type === 'builtin' ? 'bg-white/[0.04] text-text-3' : 'bg-accent-bright/10 text-[#6366F1]'}`}>
90
90
  {item.type === 'builtin' ? 'Built-in' : 'Custom'}
91
91
  </span>
92
92
  {!inSidebar && item.type === 'custom' && (
@@ -94,7 +94,7 @@ export function ProviderList({ inSidebar }: { inSidebar?: boolean }) {
94
94
  <div
95
95
  onClick={(e) => handleToggle(e, item.id, item.isEnabled)}
96
96
  className={`w-9 h-5 rounded-full transition-all relative cursor-pointer shrink-0
97
- ${item.isEnabled ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
97
+ ${item.isEnabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
98
98
  >
99
99
  <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all
100
100
  ${item.isEnabled ? 'left-[18px]' : 'left-0.5'}`} />
@@ -342,7 +342,7 @@ export function ProviderSheet() {
342
342
  <div
343
343
  onClick={() => setRequiresApiKey(!requiresApiKey)}
344
344
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer
345
- ${requiresApiKey ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
345
+ ${requiresApiKey ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
346
346
  >
347
347
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
348
348
  ${requiresApiKey ? 'left-[22px]' : 'left-0.5'}`} />
@@ -441,7 +441,7 @@ export function ProviderSheet() {
441
441
  <div
442
442
  onClick={() => setIsEnabled(!isEnabled)}
443
443
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer
444
- ${isEnabled ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
444
+ ${isEnabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
445
445
  >
446
446
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
447
447
  ${isEnabled ? 'left-[22px]' : 'left-0.5'}`} />
@@ -485,7 +485,7 @@ export function ProviderSheet() {
485
485
  <button
486
486
  onClick={handleSave}
487
487
  disabled={isBuiltin ? false : (!name.trim() || !baseUrl.trim())}
488
- className="flex-1 py-3.5 rounded-[14px] border-none bg-[#6366F1] text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
488
+ className="flex-1 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
489
489
  style={{ fontFamily: 'inherit' }}
490
490
  >
491
491
  {editing ? 'Save' : 'Create'}
@@ -71,7 +71,7 @@ export function ScheduleCard({ schedule, inSidebar }: Props) {
71
71
  <div
72
72
  onClick={handleToggle}
73
73
  className={`w-9 h-5 rounded-full transition-all relative cursor-pointer shrink-0
74
- ${schedule.status === 'active' ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
74
+ ${schedule.status === 'active' ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
75
75
  >
76
76
  <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all
77
77
  ${schedule.status === 'active' ? 'left-[18px]' : 'left-0.5'}`} />
@@ -41,7 +41,7 @@ export function ScheduleList({ inSidebar }: Props) {
41
41
  {!inSidebar && (
42
42
  <button
43
43
  onClick={() => setScheduleSheetOpen(true)}
44
- className="mt-3 px-8 py-3 rounded-[14px] border-none bg-[#6366F1] text-white
44
+ className="mt-3 px-8 py-3 rounded-[14px] border-none bg-accent-bright text-white
45
45
  text-[14px] font-600 cursor-pointer active:scale-95 transition-all duration-200
46
46
  shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
47
47
  style={{ fontFamily: 'inherit' }}