@swarmclawai/swarmclaw 0.3.1 → 0.4.5

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 (203) hide show
  1. package/README.md +33 -13
  2. package/bin/server-cmd.js +14 -7
  3. package/bin/swarmclaw.js +3 -1
  4. package/bin/update-cmd.js +120 -0
  5. package/next.config.ts +10 -0
  6. package/package.json +4 -1
  7. package/src/app/api/agents/[id]/route.ts +20 -18
  8. package/src/app/api/agents/[id]/thread/route.ts +4 -3
  9. package/src/app/api/agents/route.ts +8 -3
  10. package/src/app/api/auth/route.ts +3 -1
  11. package/src/app/api/claude-skills/route.ts +3 -1
  12. package/src/app/api/clawhub/install/route.ts +2 -2
  13. package/src/app/api/connectors/[id]/route.ts +14 -3
  14. package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
  15. package/src/app/api/connectors/route.ts +12 -4
  16. package/src/app/api/credentials/[id]/route.ts +2 -1
  17. package/src/app/api/credentials/route.ts +5 -3
  18. package/src/app/api/daemon/route.ts +6 -1
  19. package/src/app/api/documents/route.ts +2 -2
  20. package/src/app/api/files/serve/route.ts +8 -0
  21. package/src/app/api/ip/route.ts +3 -1
  22. package/src/app/api/knowledge/[id]/route.ts +5 -4
  23. package/src/app/api/knowledge/upload/route.ts +2 -2
  24. package/src/app/api/mcp-servers/[id]/route.ts +11 -14
  25. package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
  26. package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
  27. package/src/app/api/mcp-servers/route.ts +5 -3
  28. package/src/app/api/memory/[id]/route.ts +9 -8
  29. package/src/app/api/memory/route.ts +2 -2
  30. package/src/app/api/memory-images/[filename]/route.ts +2 -1
  31. package/src/app/api/openclaw/directory/route.ts +26 -0
  32. package/src/app/api/openclaw/discover/route.ts +61 -0
  33. package/src/app/api/openclaw/sync/route.ts +30 -0
  34. package/src/app/api/orchestrator/graph/route.ts +25 -0
  35. package/src/app/api/orchestrator/run/route.ts +2 -2
  36. package/src/app/api/plugins/marketplace/route.ts +3 -1
  37. package/src/app/api/plugins/route.ts +3 -1
  38. package/src/app/api/projects/[id]/route.ts +55 -0
  39. package/src/app/api/projects/route.ts +27 -0
  40. package/src/app/api/providers/[id]/models/route.ts +2 -1
  41. package/src/app/api/providers/[id]/route.ts +13 -12
  42. package/src/app/api/providers/configs/route.ts +3 -1
  43. package/src/app/api/providers/route.ts +7 -3
  44. package/src/app/api/schedules/[id]/route.ts +16 -15
  45. package/src/app/api/schedules/[id]/run/route.ts +4 -3
  46. package/src/app/api/schedules/route.ts +8 -3
  47. package/src/app/api/secrets/[id]/route.ts +16 -17
  48. package/src/app/api/secrets/route.ts +5 -3
  49. package/src/app/api/sessions/[id]/chat/route.ts +5 -2
  50. package/src/app/api/sessions/[id]/clear/route.ts +2 -1
  51. package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
  52. package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
  53. package/src/app/api/sessions/[id]/messages/route.ts +2 -1
  54. package/src/app/api/sessions/[id]/retry/route.ts +2 -1
  55. package/src/app/api/sessions/[id]/route.ts +2 -1
  56. package/src/app/api/sessions/route.ts +11 -4
  57. package/src/app/api/settings/route.ts +3 -1
  58. package/src/app/api/setup/doctor/route.ts +1 -0
  59. package/src/app/api/setup/openclaw-device/route.ts +3 -1
  60. package/src/app/api/skills/[id]/route.ts +23 -21
  61. package/src/app/api/skills/import/route.ts +2 -2
  62. package/src/app/api/skills/route.ts +5 -3
  63. package/src/app/api/tasks/[id]/approve/route.ts +74 -0
  64. package/src/app/api/tasks/[id]/route.ts +9 -5
  65. package/src/app/api/tasks/route.ts +5 -2
  66. package/src/app/api/tts/stream/route.ts +48 -0
  67. package/src/app/api/upload/route.ts +2 -2
  68. package/src/app/api/uploads/[filename]/route.ts +4 -1
  69. package/src/app/api/usage/route.ts +3 -1
  70. package/src/app/api/version/route.ts +3 -1
  71. package/src/app/api/webhooks/[id]/route.ts +31 -32
  72. package/src/app/api/webhooks/route.ts +5 -3
  73. package/src/app/icon.svg +58 -0
  74. package/src/app/page.tsx +11 -26
  75. package/src/cli/index.js +28 -9
  76. package/src/cli/index.ts +45 -2
  77. package/src/cli/spec.js +2 -8
  78. package/src/components/agents/agent-card.tsx +1 -1
  79. package/src/components/agents/agent-list.tsx +3 -1
  80. package/src/components/agents/agent-sheet.tsx +166 -81
  81. package/src/components/chat/chat-area.tsx +71 -34
  82. package/src/components/chat/chat-header.tsx +141 -29
  83. package/src/components/chat/chat-tool-toggles.tsx +12 -53
  84. package/src/components/chat/message-bubble.tsx +110 -42
  85. package/src/components/chat/tool-call-bubble.tsx +50 -6
  86. package/src/components/chat/tool-request-banner.tsx +1 -9
  87. package/src/components/chat/voice-overlay.tsx +80 -0
  88. package/src/components/connectors/connector-list.tsx +9 -10
  89. package/src/components/connectors/connector-sheet.tsx +55 -36
  90. package/src/components/input/chat-input.tsx +72 -56
  91. package/src/components/knowledge/knowledge-list.tsx +27 -31
  92. package/src/components/layout/app-layout.tsx +133 -90
  93. package/src/components/layout/daemon-indicator.tsx +3 -5
  94. package/src/components/logs/log-list.tsx +5 -9
  95. package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
  96. package/src/components/memory/memory-detail.tsx +1 -1
  97. package/src/components/plugins/plugin-list.tsx +227 -27
  98. package/src/components/projects/project-list.tsx +122 -0
  99. package/src/components/projects/project-sheet.tsx +135 -0
  100. package/src/components/providers/provider-list.tsx +46 -13
  101. package/src/components/providers/provider-sheet.tsx +0 -45
  102. package/src/components/runs/run-list.tsx +6 -15
  103. package/src/components/schedules/schedule-card.tsx +54 -4
  104. package/src/components/schedules/schedule-list.tsx +9 -4
  105. package/src/components/schedules/schedule-sheet.tsx +0 -47
  106. package/src/components/secrets/secrets-list.tsx +20 -2
  107. package/src/components/sessions/new-session-sheet.tsx +14 -15
  108. package/src/components/sessions/session-card.tsx +1 -1
  109. package/src/components/sessions/session-list.tsx +7 -7
  110. package/src/components/shared/connector-platform-icon.tsx +26 -20
  111. package/src/components/shared/model-combobox.tsx +148 -0
  112. package/src/components/shared/settings/section-heartbeat.tsx +8 -40
  113. package/src/components/shared/settings/section-orchestrator.tsx +9 -11
  114. package/src/components/shared/settings/section-web-search.tsx +56 -0
  115. package/src/components/shared/settings/settings-page.tsx +73 -0
  116. package/src/components/skills/skill-list.tsx +262 -35
  117. package/src/components/skills/skill-sheet.tsx +0 -45
  118. package/src/components/tasks/task-board.tsx +3 -6
  119. package/src/components/tasks/task-card.tsx +43 -1
  120. package/src/components/tasks/task-list.tsx +8 -7
  121. package/src/components/tasks/task-sheet.tsx +0 -44
  122. package/src/components/usage/usage-list.tsx +12 -4
  123. package/src/hooks/use-continuous-speech.ts +144 -0
  124. package/src/hooks/use-view-router.ts +52 -0
  125. package/src/hooks/use-voice-conversation.ts +80 -0
  126. package/src/hooks/use-ws.ts +66 -0
  127. package/src/instrumentation.ts +2 -0
  128. package/src/lib/chat.ts +14 -2
  129. package/src/lib/id.ts +6 -0
  130. package/src/lib/projects.ts +13 -0
  131. package/src/lib/provider-sets.ts +5 -0
  132. package/src/lib/providers/anthropic.ts +15 -2
  133. package/src/lib/providers/index.ts +8 -0
  134. package/src/lib/providers/ollama.ts +10 -2
  135. package/src/lib/providers/openai.ts +42 -13
  136. package/src/lib/providers/openclaw.ts +11 -0
  137. package/src/lib/server/api-routes.test.ts +5 -6
  138. package/src/lib/server/build-llm.ts +17 -4
  139. package/src/lib/server/chat-execution.ts +57 -8
  140. package/src/lib/server/collection-helpers.ts +54 -0
  141. package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
  142. package/src/lib/server/connectors/bluebubbles.ts +357 -0
  143. package/src/lib/server/connectors/connector-routing.test.ts +1 -1
  144. package/src/lib/server/connectors/googlechat.ts +46 -7
  145. package/src/lib/server/connectors/manager.ts +401 -6
  146. package/src/lib/server/connectors/media.ts +2 -2
  147. package/src/lib/server/connectors/openclaw.ts +64 -0
  148. package/src/lib/server/connectors/pairing.test.ts +99 -0
  149. package/src/lib/server/connectors/pairing.ts +256 -0
  150. package/src/lib/server/connectors/signal.ts +1 -0
  151. package/src/lib/server/connectors/teams.ts +5 -5
  152. package/src/lib/server/connectors/types.ts +10 -0
  153. package/src/lib/server/context-manager.ts +1 -1
  154. package/src/lib/server/daemon-state.ts +3 -0
  155. package/src/lib/server/data-dir.ts +1 -0
  156. package/src/lib/server/execution-log.ts +3 -3
  157. package/src/lib/server/heartbeat-service.ts +67 -3
  158. package/src/lib/server/knowledge-db.test.ts +2 -33
  159. package/src/lib/server/langgraph-checkpoint.ts +274 -0
  160. package/src/lib/server/main-agent-loop.ts +67 -8
  161. package/src/lib/server/memory-db.ts +6 -6
  162. package/src/lib/server/openclaw-approvals.ts +105 -0
  163. package/src/lib/server/openclaw-sync.ts +496 -0
  164. package/src/lib/server/orchestrator-lg.ts +422 -20
  165. package/src/lib/server/orchestrator.ts +29 -9
  166. package/src/lib/server/process-manager.ts +2 -2
  167. package/src/lib/server/queue.ts +39 -13
  168. package/src/lib/server/scheduler.ts +2 -2
  169. package/src/lib/server/session-mailbox.ts +2 -2
  170. package/src/lib/server/session-run-manager.ts +8 -3
  171. package/src/lib/server/session-tools/connector.ts +51 -4
  172. package/src/lib/server/session-tools/crud.ts +3 -3
  173. package/src/lib/server/session-tools/delegate.ts +5 -5
  174. package/src/lib/server/session-tools/file.ts +176 -3
  175. package/src/lib/server/session-tools/index.ts +4 -0
  176. package/src/lib/server/session-tools/memory.ts +2 -2
  177. package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
  178. package/src/lib/server/session-tools/sandbox.ts +197 -0
  179. package/src/lib/server/session-tools/search-providers.ts +270 -0
  180. package/src/lib/server/session-tools/session-info.ts +2 -2
  181. package/src/lib/server/session-tools/web.ts +47 -66
  182. package/src/lib/server/storage-mcp.test.ts +25 -2
  183. package/src/lib/server/storage.ts +36 -7
  184. package/src/lib/server/stream-agent-chat.ts +106 -22
  185. package/src/lib/server/task-result.test.ts +44 -0
  186. package/src/lib/server/task-result.ts +14 -0
  187. package/src/lib/server/task-validation.test.ts +23 -0
  188. package/src/lib/server/task-validation.ts +5 -3
  189. package/src/lib/server/ws-hub.ts +85 -0
  190. package/src/lib/tool-definitions.ts +44 -0
  191. package/src/lib/tts-stream.ts +130 -0
  192. package/src/lib/upload.ts +7 -1
  193. package/src/lib/view-routes.ts +28 -0
  194. package/src/lib/ws-client.ts +124 -0
  195. package/src/proxy.ts +3 -0
  196. package/src/stores/use-app-store.ts +28 -1
  197. package/src/stores/use-chat-store.ts +42 -14
  198. package/src/types/index.ts +34 -2
  199. package/src/app/api/agents/generate/route.ts +0 -42
  200. package/src/app/api/generate/info/route.ts +0 -12
  201. package/src/app/api/generate/route.ts +0 -106
  202. package/src/app/favicon.ico +0 -0
  203. package/src/components/shared/ai-gen-block.tsx +0 -77
@@ -6,9 +6,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
6
6
  import { useAppStore } from '@/stores/use-app-store'
7
7
  import { useMediaQuery } from '@/hooks/use-media-query'
8
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'
9
+ import { SettingsPage } from '@/components/shared/settings/settings-page'
12
10
  import { AgentList } from '@/components/agents/agent-list'
13
11
  import { AgentChatList } from '@/components/agents/agent-chat-list'
14
12
  import { AgentSheet } from '@/components/agents/agent-sheet'
@@ -39,6 +37,8 @@ import { PluginList } from '@/components/plugins/plugin-list'
39
37
  import { PluginSheet } from '@/components/plugins/plugin-sheet'
40
38
  import { UsageList } from '@/components/usage/usage-list'
41
39
  import { RunList } from '@/components/runs/run-list'
40
+ import { ProjectList } from '@/components/projects/project-list'
41
+ import { ProjectSheet } from '@/components/projects/project-sheet'
42
42
  import { NetworkBanner } from './network-banner'
43
43
  import { UpdateBanner } from './update-banner'
44
44
  import { MobileHeader } from './mobile-header'
@@ -55,8 +55,6 @@ export function AppLayout() {
55
55
  const currentSessionId = useAppStore((s) => s.currentSessionId)
56
56
  const sidebarOpen = useAppStore((s) => s.sidebarOpen)
57
57
  const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
58
- const setSettingsOpen = useAppStore((s) => s.setSettingsOpen)
59
- const setNewSessionOpen = useAppStore((s) => s.setNewSessionOpen)
60
58
  const setUser = useAppStore((s) => s.setUser)
61
59
  const setCurrentSession = useAppStore((s) => s.setCurrentSession)
62
60
  const activeView = useAppStore((s) => s.activeView)
@@ -72,8 +70,11 @@ export function AppLayout() {
72
70
  const setMcpServerSheetOpen = useAppStore((s) => s.setMcpServerSheetOpen)
73
71
  const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
74
72
  const setPluginSheetOpen = useAppStore((s) => s.setPluginSheetOpen)
73
+ const setProjectSheetOpen = useAppStore((s) => s.setProjectSheetOpen)
74
+ const tasks = useAppStore((s) => s.tasks)
75
75
  const isDesktop = useMediaQuery('(min-width: 768px)')
76
76
  const hasSelectedSession = !!(currentSessionId && sessions[currentSessionId])
77
+ const pendingApprovalCount = Object.values(tasks).filter((t) => t.pendingApproval).length
77
78
 
78
79
  const [agentViewMode, setAgentViewMode] = useState<'chat' | 'config'>('chat')
79
80
  const [shortcutsOpen, setShortcutsOpen] = useState(false)
@@ -118,8 +119,7 @@ export function AppLayout() {
118
119
  }
119
120
 
120
121
  const openNewSheet = () => {
121
- if (activeView === 'sessions') setNewSessionOpen(true)
122
- else if (activeView === 'agents') setAgentSheetOpen(true)
122
+ if (activeView === 'agents') setAgentSheetOpen(true)
123
123
  else if (activeView === 'schedules') setScheduleSheetOpen(true)
124
124
  else if (activeView === 'tasks') setTaskSheetOpen(true)
125
125
  else if (activeView === 'secrets') setSecretSheetOpen(true)
@@ -130,20 +130,29 @@ export function AppLayout() {
130
130
  else if (activeView === 'mcp_servers') setMcpServerSheetOpen(true)
131
131
  else if (activeView === 'knowledge') setKnowledgeSheetOpen(true)
132
132
  else if (activeView === 'plugins') setPluginSheetOpen(true)
133
+ else if (activeView === 'projects') setProjectSheetOpen(true)
134
+ }
135
+
136
+ const handleNavClick = (view: AppView) => {
137
+ if (FULL_WIDTH_VIEWS.has(view)) {
138
+ setActiveView(view)
139
+ setSidebarOpen(false)
140
+ } else if (activeView === view && sidebarOpen) {
141
+ setSidebarOpen(false)
142
+ } else {
143
+ setActiveView(view)
144
+ setSidebarOpen(true)
145
+ }
133
146
  }
134
147
 
135
148
  const agents = useAppStore((s) => s.agents)
136
149
  const currentAgentId = useAppStore((s) => s.currentAgentId)
137
150
  const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
138
- const mainSession = Object.values(sessions).find((s) => s.name === '__main__' && s.user === currentUser)
139
-
140
151
  const goToMainChat = async () => {
141
152
  // Navigate to default agent's chat thread
142
153
  const defaultAgent = agents['default'] || Object.values(agents)[0]
143
154
  if (defaultAgent) {
144
155
  await setCurrentAgent(defaultAgent.id)
145
- } else if (mainSession) {
146
- setCurrentSession(mainSession.id)
147
156
  }
148
157
  setActiveView('agents')
149
158
  setSidebarOpen(false)
@@ -230,82 +239,82 @@ export function AppLayout() {
230
239
 
231
240
  {/* Nav items */}
232
241
  <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) }}>
242
+ <NavItem view="agents" label="Agents" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('agents')}>
234
243
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
235
244
  <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
245
  </svg>
237
246
  </NavItem>
238
- <NavItem view="sessions" label="History" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('sessions'); setSidebarOpen(true) }}>
247
+ <NavItem view="projects" label="Projects" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('projects')}>
239
248
  <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" />
249
+ <path d="M2 20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8l-7-7H4a2 2 0 0 0-2 2v17Z" /><path d="M14 2v7h7" />
241
250
  </svg>
242
251
  </NavItem>
243
- <NavItem view="schedules" label="Schedules" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('schedules'); setSidebarOpen(true) }}>
252
+ <NavItem view="schedules" label="Schedules" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('schedules')}>
244
253
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
245
254
  <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
246
255
  </svg>
247
256
  </NavItem>
248
- <NavItem view="memory" label="Memory" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('memory'); setSidebarOpen(true) }}>
257
+ <NavItem view="memory" label="Memory" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('memory')}>
249
258
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
250
259
  <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
260
  </svg>
252
261
  </NavItem>
253
- <NavItem view="tasks" label="Tasks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('tasks'); setSidebarOpen(true) }}>
262
+ <NavItem view="tasks" label="Tasks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('tasks')} badge={pendingApprovalCount}>
254
263
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
255
264
  <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
265
  </svg>
257
266
  </NavItem>
258
- <NavItem view="secrets" label="Secrets" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('secrets'); setSidebarOpen(true) }}>
267
+ <NavItem view="secrets" label="Secrets" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('secrets')}>
259
268
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
260
269
  <rect x="3" y="11" width="18" height="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
261
270
  </svg>
262
271
  </NavItem>
263
- <NavItem view="providers" label="Providers" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('providers'); setSidebarOpen(true) }}>
272
+ <NavItem view="providers" label="Providers" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('providers')}>
264
273
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
265
274
  <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
275
  </svg>
267
276
  </NavItem>
268
- <NavItem view="skills" label="Skills" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('skills'); setSidebarOpen(true) }}>
277
+ <NavItem view="skills" label="Skills" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('skills')}>
269
278
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
270
279
  <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
280
  </svg>
272
281
  </NavItem>
273
- <NavItem view="connectors" label="Connectors" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('connectors'); setSidebarOpen(true) }}>
282
+ <NavItem view="connectors" label="Connectors" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('connectors')}>
274
283
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
275
284
  <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
285
  </svg>
277
286
  </NavItem>
278
- <NavItem view="webhooks" label="Webhooks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('webhooks'); setSidebarOpen(true) }}>
287
+ <NavItem view="webhooks" label="Webhooks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('webhooks')}>
279
288
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
280
289
  <path d="M22 12h-4l-3 7L9 5l-3 7H2" />
281
290
  </svg>
282
291
  </NavItem>
283
- <NavItem view="mcp_servers" label="MCP" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('mcp_servers'); setSidebarOpen(true) }}>
292
+ <NavItem view="mcp_servers" label="MCP" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('mcp_servers')}>
284
293
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
285
294
  <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
295
  </svg>
287
296
  </NavItem>
288
- <NavItem view="knowledge" label="Knowledge" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('knowledge'); setSidebarOpen(true) }}>
297
+ <NavItem view="knowledge" label="Knowledge" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('knowledge')}>
289
298
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
290
299
  <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
300
  </svg>
292
301
  </NavItem>
293
- <NavItem view="plugins" label="Plugins" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('plugins'); setSidebarOpen(true) }}>
302
+ <NavItem view="plugins" label="Plugins" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('plugins')}>
294
303
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
295
304
  <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
305
  </svg>
297
306
  </NavItem>
298
- <NavItem view="usage" label="Usage" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('usage'); setSidebarOpen(true) }}>
307
+ <NavItem view="usage" label="Usage" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('usage')}>
299
308
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
300
309
  <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
310
  </svg>
302
311
  </NavItem>
303
- <NavItem view="runs" label="Runs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('runs'); setSidebarOpen(true) }}>
312
+ <NavItem view="runs" label="Runs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('runs')}>
304
313
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
305
314
  <polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
306
315
  </svg>
307
316
  </NavItem>
308
- <NavItem view="logs" label="Logs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('logs'); setSidebarOpen(true) }}>
317
+ <NavItem view="logs" label="Logs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('logs')}>
309
318
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
310
319
  <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
320
  </svg>
@@ -350,7 +359,7 @@ export function AppLayout() {
350
359
  {railExpanded && <DaemonIndicator />}
351
360
  {railExpanded ? (
352
361
  <button
353
- onClick={() => setSettingsOpen(true)}
362
+ onClick={() => handleNavClick('settings')}
354
363
  className="w-full flex items-center gap-2.5 px-3 py-2 rounded-[10px] text-[13px] font-500 cursor-pointer transition-all
355
364
  bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04] border-none"
356
365
  style={{ fontFamily: 'inherit' }}
@@ -363,7 +372,7 @@ export function AppLayout() {
363
372
  </button>
364
373
  ) : (
365
374
  <RailTooltip label="Settings" description="API keys, providers & app config">
366
- <button onClick={() => setSettingsOpen(true)} className="rail-btn">
375
+ <button onClick={() => handleNavClick('settings')} className="rail-btn">
367
376
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
368
377
  <circle cx="12" cy="12" r="3" />
369
378
  <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" />
@@ -400,7 +409,7 @@ export function AppLayout() {
400
409
  style={{ animation: 'panel-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)' }}
401
410
  >
402
411
  <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>
412
+ <h2 className="font-display text-[14px] font-600 text-text-2 tracking-[-0.01em] capitalize flex-1">{activeView}</h2>
404
413
  {activeView === 'logs' || activeView === 'usage' || activeView === 'runs' ? null : activeView === 'memory' ? (
405
414
  <button
406
415
  onClick={() => useAppStore.getState().setMemorySheetOpen(true)}
@@ -422,17 +431,10 @@ export function AppLayout() {
422
431
  <line x1="12" y1="5" x2="12" y2="19" />
423
432
  <line x1="5" y1="12" x2="19" y2="12" />
424
433
  </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'}
434
+ {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
435
  </button>
427
436
  )}
428
437
  </div>
429
- {activeView === 'sessions' && (
430
- <>
431
- <UpdateBanner />
432
- <NetworkBanner />
433
- <SessionList inSidebar onSelect={() => {}} />
434
- </>
435
- )}
436
438
  {activeView === 'agents' && (
437
439
  <>
438
440
  <div className="flex gap-1 px-4 pb-2">
@@ -488,7 +490,7 @@ export function AppLayout() {
488
490
  <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
491
  </svg>
490
492
  </a>
491
- <button onClick={() => setSettingsOpen(true)} className="rail-btn">
493
+ <button onClick={() => handleNavClick('settings')} className="rail-btn">
492
494
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
493
495
  <circle cx="12" cy="12" r="3" />
494
496
  <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" />
@@ -500,7 +502,7 @@ export function AppLayout() {
500
502
  </div>
501
503
  {/* View selector tabs */}
502
504
  <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) => (
505
+ {(['agents', 'schedules', 'memory', 'tasks', 'secrets', 'providers', 'skills', 'connectors', 'webhooks', 'mcp_servers', 'knowledge', 'plugins', 'usage', 'runs', 'logs'] as AppView[]).map((v) => (
504
506
  <button
505
507
  key={v}
506
508
  onClick={() => setActiveView(v)}
@@ -514,7 +516,7 @@ export function AppLayout() {
514
516
  </button>
515
517
  ))}
516
518
  </div>
517
- {activeView !== 'logs' && activeView !== 'usage' && activeView !== 'runs' && (
519
+ {activeView !== 'logs' && activeView !== 'usage' && activeView !== 'runs' && activeView !== 'settings' && (
518
520
  <div className="px-4 py-2.5 shrink-0">
519
521
  <button
520
522
  onClick={() => {
@@ -526,17 +528,10 @@ export function AppLayout() {
526
528
  shadow-[0_2px_12px_rgba(99,102,241,0.15)]"
527
529
  style={{ fontFamily: 'inherit' }}
528
530
  >
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'}
531
+ + New {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' : activeView === 'projects' ? 'Project' : 'Entry'}
530
532
  </button>
531
533
  </div>
532
534
  )}
533
- {activeView === 'sessions' && (
534
- <>
535
- <UpdateBanner />
536
- <NetworkBanner />
537
- <SessionList inSidebar onSelect={() => setSidebarOpen(false)} />
538
- </>
539
- )}
540
535
  {activeView === 'agents' && (
541
536
  <>
542
537
  <div className="flex gap-1 px-4 pb-2">
@@ -566,6 +561,7 @@ export function AppLayout() {
566
561
  {activeView === 'mcp_servers' && <McpServerList />}
567
562
  {activeView === 'knowledge' && <KnowledgeList />}
568
563
  {activeView === 'plugins' && <PluginList inSidebar />}
564
+ {activeView === 'projects' && <ProjectList />}
569
565
  {activeView === 'usage' && <UsageList />}
570
566
  {activeView === 'runs' && <RunList />}
571
567
  {activeView === 'logs' && <LogList />}
@@ -596,37 +592,51 @@ export function AppLayout() {
596
592
  </div>
597
593
  )}
598
594
  </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
595
  ) : activeView === 'tasks' && isDesktop ? (
619
596
  <TaskBoard />
620
597
  ) : activeView === 'memory' ? (
621
598
  <MemoryDetail />
599
+ ) : activeView === 'settings' ? (
600
+ <SettingsPage />
601
+ ) : !sidebarOpen && FULL_WIDTH_VIEWS.has(activeView) ? (
602
+ <div className="flex-1 flex flex-col h-full">
603
+ <div className="flex items-center px-6 pt-5 pb-3 shrink-0">
604
+ <h2 className="font-display text-[14px] font-600 text-text-2 tracking-[-0.01em] capitalize flex-1">
605
+ {activeView === 'mcp_servers' ? 'MCP Servers' : activeView.replace('_', ' ')}
606
+ </h2>
607
+ {activeView !== 'usage' && activeView !== 'runs' && activeView !== 'logs' && (
608
+ <button
609
+ onClick={openNewSheet}
610
+ 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"
611
+ style={{ fontFamily: 'inherit' }}
612
+ >
613
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
614
+ <line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
615
+ </svg>
616
+ {activeView === 'schedules' ? 'Schedule' : 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' : activeView === 'projects' ? 'Project' : 'New'}
617
+ </button>
618
+ )}
619
+ </div>
620
+ {activeView === 'schedules' && <ScheduleList />}
621
+ {activeView === 'secrets' && <SecretsList />}
622
+ {activeView === 'providers' && <ProviderList />}
623
+ {activeView === 'skills' && <SkillList />}
624
+ {activeView === 'connectors' && <ConnectorList />}
625
+ {activeView === 'webhooks' && <WebhookList />}
626
+ {activeView === 'mcp_servers' && <McpServerList />}
627
+ {activeView === 'knowledge' && <KnowledgeList />}
628
+ {activeView === 'plugins' && <PluginList />}
629
+ {activeView === 'projects' && <ProjectList />}
630
+ {activeView === 'usage' && <UsageList />}
631
+ {activeView === 'runs' && <RunList />}
632
+ {activeView === 'logs' && <LogList />}
633
+ </div>
622
634
  ) : (
623
635
  <ViewEmptyState view={activeView} />
624
636
  )}
625
637
  </div>
626
638
  </ErrorBoundary>
627
639
 
628
- <NewSessionSheet />
629
- <SettingsSheet />
630
640
  <AgentSheet />
631
641
  <ScheduleSheet />
632
642
  <MemorySheet />
@@ -639,6 +649,7 @@ export function AppLayout() {
639
649
  <McpServerSheet />
640
650
  <KnowledgeSheet />
641
651
  <PluginSheet />
652
+ <ProjectSheet />
642
653
 
643
654
  <Dialog open={shortcutsOpen} onOpenChange={setShortcutsOpen}>
644
655
  <DialogContent className="sm:max-w-[380px] bg-raised border-white/[0.08]">
@@ -718,7 +729,6 @@ class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boole
718
729
  }
719
730
 
720
731
  const VIEW_DESCRIPTIONS: Record<AppView, string> = {
721
- sessions: 'Session history & debug view',
722
732
  agents: 'Chat with & configure your AI agents',
723
733
  schedules: 'Automated task schedules',
724
734
  memory: 'Long-term agent memory store',
@@ -733,10 +743,18 @@ const VIEW_DESCRIPTIONS: Record<AppView, string> = {
733
743
  logs: 'Application logs & error tracking',
734
744
  plugins: 'Extend agent capabilities with custom plugins',
735
745
  usage: 'Token usage analytics & cost tracking',
736
- runs: 'Live session run monitoring & history',
746
+ runs: 'Live run monitoring & history',
747
+ settings: 'Manage providers, API keys & orchestrator engine',
748
+ projects: 'Group agents, tasks & schedules into projects',
737
749
  }
738
750
 
739
- const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon: string; title: string; description: string; features: string[] }> = {
751
+ const FULL_WIDTH_VIEWS = new Set<AppView>([
752
+ 'schedules', 'secrets', 'providers', 'skills',
753
+ 'connectors', 'webhooks', 'mcp_servers', 'knowledge', 'plugins',
754
+ 'usage', 'runs', 'logs', 'settings', 'projects',
755
+ ])
756
+
757
+ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'agents'>, { icon: string; title: string; description: string; features: string[] }> = {
740
758
  schedules: {
741
759
  icon: 'clock',
742
760
  title: 'Schedules',
@@ -746,14 +764,14 @@ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon:
746
764
  memory: {
747
765
  icon: 'database',
748
766
  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'],
767
+ description: 'Long-term memory store for AI agents. Orchestrators can store and retrieve knowledge across conversations.',
768
+ features: ['Agents store findings and learnings automatically', 'Full-text search across all stored memories', 'Organized by categories and agents', 'Persists across conversations for continuity'],
751
769
  },
752
770
  tasks: {
753
771
  icon: 'clipboard',
754
772
  title: 'Task Board',
755
773
  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'],
774
+ features: ['Kanban columns: Backlog, Queued, Running, Completed, Failed', 'Assign tasks to specific orchestrator agents', 'Sequential queue ensures orchestrators don\'t conflict', 'View results and logs for completed tasks'],
757
775
  },
758
776
  secrets: {
759
777
  icon: 'lock',
@@ -777,7 +795,7 @@ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon:
777
795
  icon: 'link',
778
796
  title: 'Connectors',
779
797
  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'],
798
+ features: ['Connect Discord, Telegram, Slack, or WhatsApp bots', 'Route incoming messages to any agent', 'Each platform channel gets its own chat thread', 'Start and stop connectors from the UI'],
781
799
  },
782
800
  webhooks: {
783
801
  icon: 'webhook',
@@ -788,7 +806,7 @@ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon:
788
806
  mcp_servers: {
789
807
  icon: 'server',
790
808
  title: 'MCP Servers',
791
- description: 'Connect agents to external MCP (Model Context Protocol) servers, injecting their tools into chat sessions.',
809
+ description: 'Connect agents to external MCP (Model Context Protocol) servers, injecting their tools into agent chats.',
792
810
  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
811
  },
794
812
  knowledge: {
@@ -812,20 +830,32 @@ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon:
812
830
  usage: {
813
831
  icon: 'bar-chart',
814
832
  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'],
833
+ description: 'Track token usage and costs across all providers and agents.',
834
+ features: ['Per-provider cost breakdown', 'Token usage over time', 'Per-agent cost tracking', 'Export usage data'],
817
835
  },
818
836
  runs: {
819
837
  icon: 'activity',
820
838
  title: 'Runs',
821
- description: 'View the session run queue and execution history.',
839
+ description: 'View the run queue and execution history.',
822
840
  features: ['Monitor queued and running tasks', 'View run results and errors', 'Cancel pending runs', 'Automatic retry tracking'],
823
841
  },
842
+ settings: {
843
+ icon: 'settings',
844
+ title: 'Settings',
845
+ description: 'Manage providers, API keys & orchestrator engine.',
846
+ features: ['Configure LLM providers', 'Manage API credentials', 'Tune orchestrator settings', 'Set up voice & embedding'],
847
+ },
848
+ projects: {
849
+ icon: 'folder',
850
+ title: 'Projects',
851
+ description: 'Organize your work into projects. Group agents, tasks, and schedules under a common scope.',
852
+ features: ['Create named projects with color badges', 'Assign agents and tasks to projects', 'Filter sidebar views by project', 'Global view when no filter is active'],
853
+ },
824
854
  }
825
855
 
826
856
  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'>]
857
+ if (view === 'agents') return null
858
+ const config = VIEW_EMPTY_STATES[view as Exclude<AppView, 'agents'>]
829
859
  if (!config) return null
830
860
 
831
861
  return (
@@ -907,16 +937,17 @@ function ViewEmptyIcon({ type }: { type: string }) {
907
937
  }
908
938
  }
909
939
 
910
- function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children }: {
940
+ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, badge, children }: {
911
941
  view: AppView
912
942
  label: string
913
943
  expanded: boolean
914
944
  active: AppView
915
945
  sidebarOpen: boolean
916
946
  onClick: () => void
947
+ badge?: number
917
948
  children: React.ReactNode
918
949
  }) {
919
- const isActive = active === view && sidebarOpen
950
+ const isActive = active === view && (sidebarOpen || FULL_WIDTH_VIEWS.has(view))
920
951
 
921
952
  if (expanded) {
922
953
  return (
@@ -928,7 +959,14 @@ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children
928
959
  : 'bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04]'}`}
929
960
  style={{ fontFamily: 'inherit' }}
930
961
  >
931
- <span className="shrink-0">{children}</span>
962
+ <span className="shrink-0 relative">
963
+ {children}
964
+ {!!badge && (
965
+ <span className="absolute -top-1.5 -right-1.5 min-w-[14px] h-[14px] rounded-full bg-amber-500 text-black text-[9px] font-700 flex items-center justify-center px-0.5">
966
+ {badge}
967
+ </span>
968
+ )}
969
+ </span>
932
970
  <span className="truncate">{label}</span>
933
971
  </button>
934
972
  )
@@ -937,8 +975,13 @@ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children
937
975
  return (
938
976
  <Tooltip>
939
977
  <TooltipTrigger asChild>
940
- <button onClick={onClick} className={`rail-btn ${isActive ? 'active' : ''}`}>
978
+ <button onClick={onClick} className={`rail-btn ${isActive ? 'active' : ''} relative`}>
941
979
  {children}
980
+ {!!badge && (
981
+ <span className="absolute -top-0.5 -right-0.5 min-w-[14px] h-[14px] rounded-full bg-amber-500 text-black text-[9px] font-700 flex items-center justify-center px-0.5">
982
+ {badge}
983
+ </span>
984
+ )}
942
985
  </button>
943
986
  </TooltipTrigger>
944
987
  <TooltipContent side="right" sideOffset={8}
@@ -995,7 +1038,7 @@ function DesktopEmptyState({ userName }: { userName: string | null }) {
995
1038
  <span className="text-text-2">What would you like to do?</span>
996
1039
  </h1>
997
1040
  <p className="text-[15px] text-text-3 mb-12">
998
- Create a new session to start chatting
1041
+ Create a new chat to start chatting
999
1042
  </p>
1000
1043
  <button
1001
1044
  onClick={() => setNewSessionOpen(true)}
@@ -1008,7 +1051,7 @@ function DesktopEmptyState({ userName }: { userName: string | null }) {
1008
1051
  <line x1="12" y1="5" x2="12" y2="19" />
1009
1052
  <line x1="5" y1="12" x2="19" y2="12" />
1010
1053
  </svg>
1011
- New Session
1054
+ New Chat
1012
1055
  </button>
1013
1056
  </div>
1014
1057
  </div>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useState } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { useWs } from '@/hooks/use-ws'
5
6
 
6
7
  interface DaemonStatus {
7
8
  running: boolean
@@ -21,11 +22,8 @@ export function DaemonIndicator() {
21
22
  } catch { /* ignore */ }
22
23
  }
23
24
 
24
- useEffect(() => {
25
- fetchStatus()
26
- const interval = setInterval(fetchStatus, 30_000)
27
- return () => clearInterval(interval)
28
- }, [])
25
+ useEffect(() => { fetchStatus() }, [])
26
+ useWs('daemon', fetchStatus, 30_000)
29
27
 
30
28
  const toggle = async () => {
31
29
  try {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useState, useRef, useCallback } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { useWs } from '@/hooks/use-ws'
5
6
  import { useAppStore } from '@/stores/use-app-store'
6
7
  import { BottomSheet } from '@/components/shared/bottom-sheet'
7
8
 
@@ -67,12 +68,7 @@ export function LogList() {
67
68
  loadAgents()
68
69
  }, [fetchLogs])
69
70
 
70
- // Auto-refresh every 3s
71
- useEffect(() => {
72
- if (!autoRefresh) return
73
- const id = setInterval(fetchLogs, 3000)
74
- return () => clearInterval(id)
75
- }, [autoRefresh, fetchLogs])
71
+ useWs('logs', fetchLogs, autoRefresh ? 3000 : undefined)
76
72
 
77
73
  const clearLogs = async () => {
78
74
  try {
@@ -151,7 +147,7 @@ export function LogList() {
151
147
  return (
152
148
  <div className="flex-1 flex flex-col overflow-hidden">
153
149
  {/* Controls */}
154
- <div className="px-4 py-2 space-y-2 shrink-0">
150
+ <div className="px-5 py-2 space-y-2 shrink-0">
155
151
  {/* Search */}
156
152
  <input
157
153
  value={search}
@@ -250,12 +246,12 @@ export function LogList() {
250
246
  </div>
251
247
 
252
248
  {/* Total count */}
253
- <div className="px-4 py-1 text-[10px] text-text-3/60">
249
+ <div className="px-5 py-1 text-[10px] text-text-3/60">
254
250
  {entries.length} of {total} entries
255
251
  </div>
256
252
 
257
253
  {/* Log entries */}
258
- <div ref={scrollRef} className="flex-1 overflow-y-auto px-2 pb-20">
254
+ <div ref={scrollRef} className="flex-1 overflow-y-auto px-4 pb-8">
259
255
  {entries.length === 0 ? (
260
256
  <div className="flex items-center justify-center h-32 text-text-3 text-[12px]">
261
257
  No log entries