@swarmclawai/swarmclaw 0.3.0 → 0.4.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 (118) hide show
  1. package/README.md +20 -11
  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 +2 -0
  6. package/package.json +3 -1
  7. package/src/app/api/agents/[id]/route.ts +3 -0
  8. package/src/app/api/agents/[id]/thread/route.ts +2 -1
  9. package/src/app/api/agents/route.ts +5 -1
  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/connectors/[id]/route.ts +4 -0
  13. package/src/app/api/connectors/route.ts +6 -1
  14. package/src/app/api/credentials/route.ts +3 -1
  15. package/src/app/api/daemon/route.ts +6 -1
  16. package/src/app/api/ip/route.ts +3 -1
  17. package/src/app/api/mcp-servers/route.ts +3 -1
  18. package/src/app/api/orchestrator/graph/route.ts +25 -0
  19. package/src/app/api/plugins/marketplace/route.ts +3 -1
  20. package/src/app/api/plugins/route.ts +3 -1
  21. package/src/app/api/providers/[id]/route.ts +3 -0
  22. package/src/app/api/providers/configs/route.ts +3 -1
  23. package/src/app/api/providers/route.ts +5 -1
  24. package/src/app/api/schedules/[id]/route.ts +3 -0
  25. package/src/app/api/schedules/route.ts +6 -1
  26. package/src/app/api/secrets/route.ts +3 -1
  27. package/src/app/api/sessions/[id]/chat/route.ts +5 -2
  28. package/src/app/api/sessions/route.ts +9 -2
  29. package/src/app/api/settings/route.ts +3 -1
  30. package/src/app/api/setup/doctor/route.ts +1 -0
  31. package/src/app/api/setup/openclaw-device/route.ts +3 -1
  32. package/src/app/api/skills/route.ts +3 -1
  33. package/src/app/api/tasks/[id]/approve/route.ts +73 -0
  34. package/src/app/api/tasks/[id]/route.ts +3 -0
  35. package/src/app/api/tasks/route.ts +3 -0
  36. package/src/app/api/usage/route.ts +3 -1
  37. package/src/app/api/version/route.ts +3 -1
  38. package/src/app/api/webhooks/[id]/route.ts +2 -1
  39. package/src/app/api/webhooks/route.ts +3 -1
  40. package/src/app/icon.svg +58 -0
  41. package/src/app/page.tsx +8 -2
  42. package/src/cli/index.js +1 -9
  43. package/src/cli/index.ts +51 -1
  44. package/src/cli/spec.js +0 -8
  45. package/src/components/agents/agent-card.tsx +1 -1
  46. package/src/components/agents/agent-sheet.tsx +63 -80
  47. package/src/components/chat/chat-area.tsx +44 -30
  48. package/src/components/chat/chat-tool-toggles.tsx +12 -53
  49. package/src/components/chat/message-bubble.tsx +110 -42
  50. package/src/components/chat/tool-call-bubble.tsx +41 -3
  51. package/src/components/chat/tool-request-banner.tsx +1 -9
  52. package/src/components/connectors/connector-list.tsx +3 -8
  53. package/src/components/connectors/connector-sheet.tsx +24 -29
  54. package/src/components/input/chat-input.tsx +72 -56
  55. package/src/components/knowledge/knowledge-list.tsx +27 -31
  56. package/src/components/layout/app-layout.tsx +92 -71
  57. package/src/components/layout/daemon-indicator.tsx +3 -5
  58. package/src/components/logs/log-list.tsx +5 -9
  59. package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
  60. package/src/components/memory/memory-detail.tsx +1 -1
  61. package/src/components/plugins/plugin-list.tsx +227 -27
  62. package/src/components/providers/provider-list.tsx +46 -13
  63. package/src/components/providers/provider-sheet.tsx +0 -45
  64. package/src/components/runs/run-list.tsx +6 -15
  65. package/src/components/schedules/schedule-card.tsx +54 -4
  66. package/src/components/schedules/schedule-list.tsx +6 -3
  67. package/src/components/schedules/schedule-sheet.tsx +0 -47
  68. package/src/components/secrets/secrets-list.tsx +20 -2
  69. package/src/components/sessions/new-session-sheet.tsx +8 -9
  70. package/src/components/shared/connector-platform-icon.tsx +22 -20
  71. package/src/components/shared/model-combobox.tsx +148 -0
  72. package/src/components/shared/settings/section-heartbeat.tsx +7 -39
  73. package/src/components/shared/settings/section-orchestrator.tsx +8 -9
  74. package/src/components/skills/skill-list.tsx +260 -34
  75. package/src/components/skills/skill-sheet.tsx +0 -45
  76. package/src/components/tasks/task-board.tsx +3 -6
  77. package/src/components/tasks/task-card.tsx +43 -1
  78. package/src/components/tasks/task-list.tsx +3 -5
  79. package/src/components/tasks/task-sheet.tsx +0 -44
  80. package/src/components/usage/usage-list.tsx +12 -4
  81. package/src/hooks/use-ws.ts +66 -0
  82. package/src/instrumentation.ts +2 -0
  83. package/src/lib/chat.ts +14 -2
  84. package/src/lib/providers/anthropic.ts +1 -1
  85. package/src/lib/providers/index.ts +2 -0
  86. package/src/lib/providers/ollama.ts +1 -1
  87. package/src/lib/providers/openai.ts +33 -12
  88. package/src/lib/server/chat-execution.ts +19 -4
  89. package/src/lib/server/connectors/manager.ts +9 -3
  90. package/src/lib/server/context-manager.ts +1 -1
  91. package/src/lib/server/daemon-state.ts +3 -0
  92. package/src/lib/server/data-dir.ts +1 -0
  93. package/src/lib/server/heartbeat-service.ts +67 -3
  94. package/src/lib/server/langgraph-checkpoint.ts +274 -0
  95. package/src/lib/server/main-agent-loop.ts +61 -2
  96. package/src/lib/server/orchestrator-lg.ts +394 -13
  97. package/src/lib/server/orchestrator.ts +25 -5
  98. package/src/lib/server/queue.ts +17 -3
  99. package/src/lib/server/session-run-manager.ts +6 -1
  100. package/src/lib/server/session-tools/delegate.ts +2 -2
  101. package/src/lib/server/session-tools/index.ts +2 -0
  102. package/src/lib/server/session-tools/sandbox.ts +164 -0
  103. package/src/lib/server/storage-mcp.test.ts +25 -2
  104. package/src/lib/server/storage.ts +24 -7
  105. package/src/lib/server/stream-agent-chat.ts +77 -22
  106. package/src/lib/server/task-validation.test.ts +23 -0
  107. package/src/lib/server/task-validation.ts +5 -3
  108. package/src/lib/server/ws-hub.ts +85 -0
  109. package/src/lib/tool-definitions.ts +42 -0
  110. package/src/lib/upload.ts +7 -1
  111. package/src/lib/ws-client.ts +124 -0
  112. package/src/stores/use-chat-store.ts +33 -13
  113. package/src/types/index.ts +8 -1
  114. package/src/app/api/agents/generate/route.ts +0 -42
  115. package/src/app/api/generate/info/route.ts +0 -12
  116. package/src/app/api/generate/route.ts +0 -106
  117. package/src/app/favicon.ico +0 -0
  118. package/src/components/shared/ai-gen-block.tsx +0 -77
@@ -6,8 +6,6 @@ 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
9
  import { SettingsSheet } from '@/components/shared/settings-sheet'
12
10
  import { AgentList } from '@/components/agents/agent-list'
13
11
  import { AgentChatList } from '@/components/agents/agent-chat-list'
@@ -56,7 +54,6 @@ export function AppLayout() {
56
54
  const sidebarOpen = useAppStore((s) => s.sidebarOpen)
57
55
  const setSidebarOpen = useAppStore((s) => s.setSidebarOpen)
58
56
  const setSettingsOpen = useAppStore((s) => s.setSettingsOpen)
59
- const setNewSessionOpen = useAppStore((s) => s.setNewSessionOpen)
60
57
  const setUser = useAppStore((s) => s.setUser)
61
58
  const setCurrentSession = useAppStore((s) => s.setCurrentSession)
62
59
  const activeView = useAppStore((s) => s.activeView)
@@ -72,8 +69,10 @@ export function AppLayout() {
72
69
  const setMcpServerSheetOpen = useAppStore((s) => s.setMcpServerSheetOpen)
73
70
  const setKnowledgeSheetOpen = useAppStore((s) => s.setKnowledgeSheetOpen)
74
71
  const setPluginSheetOpen = useAppStore((s) => s.setPluginSheetOpen)
72
+ const tasks = useAppStore((s) => s.tasks)
75
73
  const isDesktop = useMediaQuery('(min-width: 768px)')
76
74
  const hasSelectedSession = !!(currentSessionId && sessions[currentSessionId])
75
+ const pendingApprovalCount = Object.values(tasks).filter((t) => t.pendingApproval).length
77
76
 
78
77
  const [agentViewMode, setAgentViewMode] = useState<'chat' | 'config'>('chat')
79
78
  const [shortcutsOpen, setShortcutsOpen] = useState(false)
@@ -118,8 +117,7 @@ export function AppLayout() {
118
117
  }
119
118
 
120
119
  const openNewSheet = () => {
121
- if (activeView === 'sessions') setNewSessionOpen(true)
122
- else if (activeView === 'agents') setAgentSheetOpen(true)
120
+ if (activeView === 'agents') setAgentSheetOpen(true)
123
121
  else if (activeView === 'schedules') setScheduleSheetOpen(true)
124
122
  else if (activeView === 'tasks') setTaskSheetOpen(true)
125
123
  else if (activeView === 'secrets') setSecretSheetOpen(true)
@@ -132,6 +130,18 @@ export function AppLayout() {
132
130
  else if (activeView === 'plugins') setPluginSheetOpen(true)
133
131
  }
134
132
 
133
+ const handleNavClick = (view: AppView) => {
134
+ if (FULL_WIDTH_VIEWS.has(view)) {
135
+ setActiveView(view)
136
+ setSidebarOpen(false)
137
+ } else if (activeView === view && sidebarOpen) {
138
+ setSidebarOpen(false)
139
+ } else {
140
+ setActiveView(view)
141
+ setSidebarOpen(true)
142
+ }
143
+ }
144
+
135
145
  const agents = useAppStore((s) => s.agents)
136
146
  const currentAgentId = useAppStore((s) => s.currentAgentId)
137
147
  const setCurrentAgent = useAppStore((s) => s.setCurrentAgent)
@@ -230,82 +240,77 @@ export function AppLayout() {
230
240
 
231
241
  {/* Nav items */}
232
242
  <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) }}>
243
+ <NavItem view="agents" label="Agents" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('agents')}>
234
244
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
235
245
  <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
246
  </svg>
237
247
  </NavItem>
238
- <NavItem view="sessions" label="History" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('sessions'); setSidebarOpen(true) }}>
239
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
240
- <rect x="3" y="3" width="7" height="7" rx="1" /><rect x="14" y="3" width="7" height="7" rx="1" /><rect x="3" y="14" width="7" height="7" rx="1" /><rect x="14" y="14" width="7" height="7" rx="1" />
241
- </svg>
242
- </NavItem>
243
- <NavItem view="schedules" label="Schedules" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('schedules'); setSidebarOpen(true) }}>
248
+ <NavItem view="schedules" label="Schedules" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('schedules')}>
244
249
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
245
250
  <circle cx="12" cy="12" r="10" /><polyline points="12 6 12 12 16 14" />
246
251
  </svg>
247
252
  </NavItem>
248
- <NavItem view="memory" label="Memory" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('memory'); setSidebarOpen(true) }}>
253
+ <NavItem view="memory" label="Memory" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('memory')}>
249
254
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
250
255
  <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
256
  </svg>
252
257
  </NavItem>
253
- <NavItem view="tasks" label="Tasks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('tasks'); setSidebarOpen(true) }}>
258
+ <NavItem view="tasks" label="Tasks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('tasks')} badge={pendingApprovalCount}>
254
259
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
255
260
  <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
261
  </svg>
257
262
  </NavItem>
258
- <NavItem view="secrets" label="Secrets" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('secrets'); setSidebarOpen(true) }}>
263
+ <NavItem view="secrets" label="Secrets" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('secrets')}>
259
264
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
260
265
  <rect x="3" y="11" width="18" height="11" rx="2" ry="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
261
266
  </svg>
262
267
  </NavItem>
263
- <NavItem view="providers" label="Providers" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('providers'); setSidebarOpen(true) }}>
268
+ <NavItem view="providers" label="Providers" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('providers')}>
264
269
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
265
270
  <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
271
  </svg>
267
272
  </NavItem>
268
- <NavItem view="skills" label="Skills" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('skills'); setSidebarOpen(true) }}>
273
+ <NavItem view="skills" label="Skills" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('skills')}>
269
274
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
270
275
  <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
276
  </svg>
272
277
  </NavItem>
273
- <NavItem view="connectors" label="Connectors" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('connectors'); setSidebarOpen(true) }}>
278
+ <NavItem view="connectors" label="Connectors" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('connectors')}>
274
279
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
275
280
  <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
281
  </svg>
277
282
  </NavItem>
278
- <NavItem view="webhooks" label="Webhooks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('webhooks'); setSidebarOpen(true) }}>
283
+ <NavItem view="webhooks" label="Webhooks" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('webhooks')}>
279
284
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
280
285
  <path d="M22 12h-4l-3 7L9 5l-3 7H2" />
281
286
  </svg>
282
287
  </NavItem>
283
- <NavItem view="mcp_servers" label="MCP" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('mcp_servers'); setSidebarOpen(true) }}>
288
+ <NavItem view="mcp_servers" label="MCP" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('mcp_servers')}>
284
289
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
285
290
  <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
291
  </svg>
287
292
  </NavItem>
288
- <NavItem view="knowledge" label="Knowledge" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('knowledge'); setSidebarOpen(true) }}>
293
+ <NavItem view="knowledge" label="Knowledge" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('knowledge')}>
289
294
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
290
295
  <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
296
  </svg>
292
297
  </NavItem>
293
- <NavItem view="plugins" label="Plugins" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('plugins'); setSidebarOpen(true) }}>
298
+ <NavItem view="plugins" label="Plugins" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('plugins')}>
294
299
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
295
300
  <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
301
  </svg>
297
302
  </NavItem>
298
- <NavItem view="usage" label="Usage" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('usage'); setSidebarOpen(true) }}>
303
+ <NavItem view="usage" label="Usage" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('usage')}>
299
304
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
300
305
  <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
306
  </svg>
302
307
  </NavItem>
303
- <NavItem view="runs" label="Runs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('runs'); setSidebarOpen(true) }}>
308
+ <NavItem view="runs" label="Runs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('runs')}>
304
309
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
305
310
  <polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
306
311
  </svg>
307
312
  </NavItem>
308
- <NavItem view="logs" label="Logs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => { setActiveView('logs'); setSidebarOpen(true) }}>
313
+ <NavItem view="logs" label="Logs" expanded={railExpanded} active={activeView} sidebarOpen={sidebarOpen} onClick={() => handleNavClick('logs')}>
309
314
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
310
315
  <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
316
  </svg>
@@ -400,7 +405,7 @@ export function AppLayout() {
400
405
  style={{ animation: 'panel-in 0.2s cubic-bezier(0.16, 1, 0.3, 1)' }}
401
406
  >
402
407
  <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>
408
+ <h2 className="font-display text-[14px] font-600 text-text-2 tracking-[-0.01em] capitalize flex-1">{activeView}</h2>
404
409
  {activeView === 'logs' || activeView === 'usage' || activeView === 'runs' ? null : activeView === 'memory' ? (
405
410
  <button
406
411
  onClick={() => useAppStore.getState().setMemorySheetOpen(true)}
@@ -422,17 +427,10 @@ export function AppLayout() {
422
427
  <line x1="12" y1="5" x2="12" y2="19" />
423
428
  <line x1="5" y1="12" x2="19" y2="12" />
424
429
  </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'}
430
+ {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
431
  </button>
427
432
  )}
428
433
  </div>
429
- {activeView === 'sessions' && (
430
- <>
431
- <UpdateBanner />
432
- <NetworkBanner />
433
- <SessionList inSidebar onSelect={() => {}} />
434
- </>
435
- )}
436
434
  {activeView === 'agents' && (
437
435
  <>
438
436
  <div className="flex gap-1 px-4 pb-2">
@@ -500,7 +498,7 @@ export function AppLayout() {
500
498
  </div>
501
499
  {/* View selector tabs */}
502
500
  <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) => (
501
+ {(['agents', 'schedules', 'memory', 'tasks', 'secrets', 'providers', 'skills', 'connectors', 'webhooks', 'mcp_servers', 'knowledge', 'plugins', 'usage', 'runs', 'logs'] as AppView[]).map((v) => (
504
502
  <button
505
503
  key={v}
506
504
  onClick={() => setActiveView(v)}
@@ -526,17 +524,10 @@ export function AppLayout() {
526
524
  shadow-[0_2px_12px_rgba(99,102,241,0.15)]"
527
525
  style={{ fontFamily: 'inherit' }}
528
526
  >
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'}
527
+ + 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' : 'Entry'}
530
528
  </button>
531
529
  </div>
532
530
  )}
533
- {activeView === 'sessions' && (
534
- <>
535
- <UpdateBanner />
536
- <NetworkBanner />
537
- <SessionList inSidebar onSelect={() => setSidebarOpen(false)} />
538
- </>
539
- )}
540
531
  {activeView === 'agents' && (
541
532
  <>
542
533
  <div className="flex gap-1 px-4 pb-2">
@@ -596,36 +587,48 @@ export function AppLayout() {
596
587
  </div>
597
588
  )}
598
589
  </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
590
  ) : activeView === 'tasks' && isDesktop ? (
619
591
  <TaskBoard />
620
592
  ) : activeView === 'memory' ? (
621
593
  <MemoryDetail />
594
+ ) : !sidebarOpen && FULL_WIDTH_VIEWS.has(activeView) ? (
595
+ <div className="flex-1 flex flex-col h-full">
596
+ <div className="flex items-center px-6 pt-5 pb-3 shrink-0">
597
+ <h2 className="font-display text-[14px] font-600 text-text-2 tracking-[-0.01em] capitalize flex-1">
598
+ {activeView === 'mcp_servers' ? 'MCP Servers' : activeView.replace('_', ' ')}
599
+ </h2>
600
+ {activeView !== 'usage' && activeView !== 'runs' && activeView !== 'logs' && (
601
+ <button
602
+ onClick={openNewSheet}
603
+ 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"
604
+ style={{ fontFamily: 'inherit' }}
605
+ >
606
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
607
+ <line x1="12" y1="5" x2="12" y2="19" /><line x1="5" y1="12" x2="19" y2="12" />
608
+ </svg>
609
+ {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' : 'New'}
610
+ </button>
611
+ )}
612
+ </div>
613
+ {activeView === 'schedules' && <ScheduleList />}
614
+ {activeView === 'secrets' && <SecretsList />}
615
+ {activeView === 'providers' && <ProviderList />}
616
+ {activeView === 'skills' && <SkillList />}
617
+ {activeView === 'connectors' && <ConnectorList />}
618
+ {activeView === 'webhooks' && <WebhookList />}
619
+ {activeView === 'mcp_servers' && <McpServerList />}
620
+ {activeView === 'knowledge' && <KnowledgeList />}
621
+ {activeView === 'plugins' && <PluginList />}
622
+ {activeView === 'usage' && <UsageList />}
623
+ {activeView === 'runs' && <RunList />}
624
+ {activeView === 'logs' && <LogList />}
625
+ </div>
622
626
  ) : (
623
627
  <ViewEmptyState view={activeView} />
624
628
  )}
625
629
  </div>
626
630
  </ErrorBoundary>
627
631
 
628
- <NewSessionSheet />
629
632
  <SettingsSheet />
630
633
  <AgentSheet />
631
634
  <ScheduleSheet />
@@ -718,7 +721,6 @@ class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boole
718
721
  }
719
722
 
720
723
  const VIEW_DESCRIPTIONS: Record<AppView, string> = {
721
- sessions: 'Session history & debug view',
722
724
  agents: 'Chat with & configure your AI agents',
723
725
  schedules: 'Automated task schedules',
724
726
  memory: 'Long-term agent memory store',
@@ -736,7 +738,13 @@ const VIEW_DESCRIPTIONS: Record<AppView, string> = {
736
738
  runs: 'Live session run monitoring & history',
737
739
  }
738
740
 
739
- const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon: string; title: string; description: string; features: string[] }> = {
741
+ const FULL_WIDTH_VIEWS = new Set<AppView>([
742
+ 'schedules', 'secrets', 'providers', 'skills',
743
+ 'connectors', 'webhooks', 'mcp_servers', 'knowledge', 'plugins',
744
+ 'usage', 'runs', 'logs',
745
+ ])
746
+
747
+ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'agents'>, { icon: string; title: string; description: string; features: string[] }> = {
740
748
  schedules: {
741
749
  icon: 'clock',
742
750
  title: 'Schedules',
@@ -824,8 +832,8 @@ const VIEW_EMPTY_STATES: Record<Exclude<AppView, 'sessions' | 'agents'>, { icon:
824
832
  }
825
833
 
826
834
  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'>]
835
+ if (view === 'agents') return null
836
+ const config = VIEW_EMPTY_STATES[view as Exclude<AppView, 'agents'>]
829
837
  if (!config) return null
830
838
 
831
839
  return (
@@ -907,16 +915,17 @@ function ViewEmptyIcon({ type }: { type: string }) {
907
915
  }
908
916
  }
909
917
 
910
- function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children }: {
918
+ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, badge, children }: {
911
919
  view: AppView
912
920
  label: string
913
921
  expanded: boolean
914
922
  active: AppView
915
923
  sidebarOpen: boolean
916
924
  onClick: () => void
925
+ badge?: number
917
926
  children: React.ReactNode
918
927
  }) {
919
- const isActive = active === view && sidebarOpen
928
+ const isActive = active === view && (sidebarOpen || FULL_WIDTH_VIEWS.has(view))
920
929
 
921
930
  if (expanded) {
922
931
  return (
@@ -928,7 +937,14 @@ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children
928
937
  : 'bg-transparent text-text-3 hover:text-text hover:bg-white/[0.04]'}`}
929
938
  style={{ fontFamily: 'inherit' }}
930
939
  >
931
- <span className="shrink-0">{children}</span>
940
+ <span className="shrink-0 relative">
941
+ {children}
942
+ {!!badge && (
943
+ <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">
944
+ {badge}
945
+ </span>
946
+ )}
947
+ </span>
932
948
  <span className="truncate">{label}</span>
933
949
  </button>
934
950
  )
@@ -937,8 +953,13 @@ function NavItem({ view, label, expanded, active, sidebarOpen, onClick, children
937
953
  return (
938
954
  <Tooltip>
939
955
  <TooltipTrigger asChild>
940
- <button onClick={onClick} className={`rail-btn ${isActive ? 'active' : ''}`}>
956
+ <button onClick={onClick} className={`rail-btn ${isActive ? 'active' : ''} relative`}>
941
957
  {children}
958
+ {!!badge && (
959
+ <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">
960
+ {badge}
961
+ </span>
962
+ )}
942
963
  </button>
943
964
  </TooltipTrigger>
944
965
  <TooltipContent side="right" sideOffset={8}
@@ -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
@@ -59,8 +59,19 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
59
59
  await loadMcpServers()
60
60
  }
61
61
 
62
+ const handleRetest = async (e: React.MouseEvent, id: string) => {
63
+ e.stopPropagation()
64
+ setStatuses((prev) => ({ ...prev, [id]: { ok: false, loading: true } }))
65
+ try {
66
+ const res = await api<{ ok: boolean; tools?: string[]; error?: string }>('POST', `/mcp-servers/${id}/test`)
67
+ setStatuses((prev) => ({ ...prev, [id]: { ok: res.ok, tools: res.tools, error: res.error, loading: false } }))
68
+ } catch {
69
+ setStatuses((prev) => ({ ...prev, [id]: { ok: false, error: 'Test failed', loading: false } }))
70
+ }
71
+ }
72
+
62
73
  return (
63
- <div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-4'}`}>
74
+ <div className={`flex-1 overflow-y-auto ${inSidebar ? 'px-3 pb-4' : 'px-5 pb-6'}`}>
64
75
  {serverList.length === 0 ? (
65
76
  <div className="text-center py-12">
66
77
  <p className="text-[13px] text-text-3/60">No MCP servers configured</p>
@@ -73,7 +84,7 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
73
84
  </button>
74
85
  </div>
75
86
  ) : (
76
- <div className="space-y-2">
87
+ <div className={inSidebar ? 'space-y-2' : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3'}>
77
88
  {serverList.map((server) => (
78
89
  <button
79
90
  key={server.id}
@@ -96,6 +107,17 @@ export function McpServerList({ inSidebar }: { inSidebar?: boolean }) {
96
107
  <span className="font-display text-[14px] font-600 text-text truncate">{server.name}</span>
97
108
  </div>
98
109
  <div className="flex items-center gap-2 shrink-0 ml-2">
110
+ {!inSidebar && (
111
+ <button
112
+ onClick={(e) => handleRetest(e, server.id)}
113
+ className="text-text-3/40 hover:text-text-2 transition-colors p-0.5"
114
+ title="Re-test connection"
115
+ >
116
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
117
+ <path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16" />
118
+ </svg>
119
+ </button>
120
+ )}
99
121
  <span className={`text-[10px] font-mono px-2 py-0.5 rounded-full ${transportColors[server.transport] || 'bg-white/10 text-text-3'}`}>
100
122
  {server.transport}
101
123
  </span>
@@ -76,7 +76,7 @@ export function MemoryDetail() {
76
76
 
77
77
  const handleNavigateToSession = useCallback(() => {
78
78
  if (!entry?.sessionId) return
79
- setActiveView('sessions')
79
+ setActiveView('agents')
80
80
  setCurrentSession(entry.sessionId)
81
81
  }, [entry])
82
82