@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
@@ -11,8 +11,12 @@ import type { ProviderType, ClaudeSkill } from '@/types'
11
11
  import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
12
12
  import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
13
13
  import { AgentAvatar } from './agent-avatar'
14
+ import { AgentPickerList } from '@/components/shared/agent-picker-list'
15
+ import { randomSoul } from '@/lib/soul-suggestions'
16
+ import { SectionLabel } from '@/components/shared/section-label'
17
+ import { SoulLibraryPicker } from './soul-library-picker'
14
18
 
15
- const HB_PRESETS = [30, 60, 120, 300, 600, 1800, 3600] as const
19
+ const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
16
20
 
17
21
  function formatHbDuration(sec: number): string {
18
22
  if (sec >= 3600) {
@@ -60,6 +64,7 @@ export function AgentSheet() {
60
64
  const loadProviders = useAppStore((s) => s.loadProviders)
61
65
  const credentials = useAppStore((s) => s.credentials)
62
66
  const loadCredentials = useAppStore((s) => s.loadCredentials)
67
+ const appSettings = useAppStore((s) => s.appSettings)
63
68
  const dynamicSkills = useAppStore((s) => s.skills)
64
69
  const mcpServers = useAppStore((s) => s.mcpServers)
65
70
  const loadSkills = useAppStore((s) => s.loadSkills)
@@ -77,6 +82,8 @@ export function AgentSheet() {
77
82
  const [name, setName] = useState('')
78
83
  const [description, setDescription] = useState('')
79
84
  const [soul, setSoul] = useState('')
85
+ const [soulInitial, setSoulInitial] = useState('')
86
+ const [soulSaveState, setSoulSaveState] = useState<'idle' | 'saved'>('idle')
80
87
  const [systemPrompt, setSystemPrompt] = useState('')
81
88
  const [provider, setProvider] = useState<ProviderType>('claude-cli')
82
89
  const [model, setModel] = useState('')
@@ -92,7 +99,6 @@ export function AgentSheet() {
92
99
  const [mcpTools, setMcpTools] = useState<Record<string, { name: string; description: string }[]>>({})
93
100
  const [mcpToolsLoading, setMcpToolsLoading] = useState(false)
94
101
  const [fallbackCredentialIds, setFallbackCredentialIds] = useState<string[]>([])
95
- const [platformAssignScope, setPlatformAssignScope] = useState<'self' | 'all'>('self')
96
102
  const [capabilities, setCapabilities] = useState<string[]>([])
97
103
  const [capInput, setCapInput] = useState('')
98
104
  const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
@@ -100,6 +106,7 @@ export function AgentSheet() {
100
106
  const [projectId, setProjectId] = useState<string | undefined>(undefined)
101
107
  const [avatarSeed, setAvatarSeed] = useState('')
102
108
  const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
109
+ const [voiceId, setVoiceId] = useState('')
103
110
  const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
104
111
  const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
105
112
  const [heartbeatModel, setHeartbeatModel] = useState('')
@@ -118,6 +125,7 @@ export function AgentSheet() {
118
125
  const [configCopied, setConfigCopied] = useState(false)
119
126
 
120
127
  const soulFileRef = useRef<HTMLInputElement>(null)
128
+ const [soulLibraryOpen, setSoulLibraryOpen] = useState(false)
121
129
  const promptFileRef = useRef<HTMLInputElement>(null)
122
130
  const importFileRef = useRef<HTMLInputElement>(null)
123
131
 
@@ -154,6 +162,8 @@ export function AgentSheet() {
154
162
  setName(editing.name)
155
163
  setDescription(editing.description)
156
164
  setSoul(editing.soul || '')
165
+ setSoulInitial(editing.soul || '')
166
+ setSoulSaveState('idle')
157
167
  setSystemPrompt(editing.systemPrompt)
158
168
  setProvider(editing.provider)
159
169
  setModel(editing.model)
@@ -167,14 +177,15 @@ export function AgentSheet() {
167
177
  setMcpServerIds(editing.mcpServerIds || [])
168
178
  setMcpDisabledTools(editing.mcpDisabledTools || [])
169
179
  setFallbackCredentialIds(editing.fallbackCredentialIds || [])
170
- setPlatformAssignScope(editing.platformAssignScope || 'self')
180
+ // platformAssignScope derived from isOrchestrator — no separate state
171
181
  setCapabilities(editing.capabilities || [])
172
182
  setCapInput('')
173
183
  setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
174
184
  setOpenclawEnabled(editing.provider === 'openclaw')
175
185
  setProjectId(editing.projectId)
176
- setAvatarSeed(editing.avatarSeed || '')
186
+ setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
177
187
  setThinkingLevel(editing.thinkingLevel || '')
188
+ setVoiceId(editing.elevenLabsVoiceId || '')
178
189
  setHeartbeatEnabled(editing.heartbeatEnabled || false)
179
190
  setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
180
191
  setHeartbeatModel(editing.heartbeatModel || '')
@@ -182,7 +193,10 @@ export function AgentSheet() {
182
193
  } else {
183
194
  setName('')
184
195
  setDescription('')
185
- setSoul('')
196
+ const newSoul = randomSoul()
197
+ setSoul(newSoul)
198
+ setSoulInitial(newSoul)
199
+ setSoulSaveState('idle')
186
200
  setSystemPrompt('')
187
201
  setProvider('claude-cli')
188
202
  setModel('')
@@ -195,7 +209,6 @@ export function AgentSheet() {
195
209
  setSkillIds([])
196
210
  setMcpDisabledTools([])
197
211
  setFallbackCredentialIds([])
198
- setPlatformAssignScope('self')
199
212
  setCapabilities([])
200
213
  setCapInput('')
201
214
  setOllamaMode('local')
@@ -203,6 +216,7 @@ export function AgentSheet() {
203
216
  setProjectId(undefined)
204
217
  setAvatarSeed('')
205
218
  setThinkingLevel('')
219
+ setVoiceId('')
206
220
  setHeartbeatEnabled(false)
207
221
  setHeartbeatIntervalSec('')
208
222
  setHeartbeatModel('')
@@ -292,11 +306,12 @@ export function AgentSheet() {
292
306
  mcpServerIds,
293
307
  mcpDisabledTools: mcpDisabledTools.length ? mcpDisabledTools : undefined,
294
308
  fallbackCredentialIds,
295
- platformAssignScope,
309
+ platformAssignScope: (isOrchestrator ? 'all' : 'self') as 'all' | 'self',
296
310
  capabilities,
297
311
  projectId: projectId || undefined,
298
312
  avatarSeed: avatarSeed.trim() || undefined,
299
313
  thinkingLevel: thinkingLevel || undefined,
314
+ elevenLabsVoiceId: voiceId.trim() || null,
300
315
  heartbeatEnabled,
301
316
  heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
302
317
  heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
@@ -305,16 +320,22 @@ export function AgentSheet() {
305
320
  }
306
321
  if (editing) {
307
322
  await updateAgent(editing.id, data)
323
+ toast.success('Agent saved')
308
324
  } else {
309
325
  await createAgent(data)
326
+ toast.success('Agent created')
310
327
  }
311
328
  await loadAgents()
329
+ setSoulInitial(soul)
330
+ setSoulSaveState('saved')
331
+ setTimeout(() => setSoulSaveState('idle'), 1500)
312
332
  onClose()
313
333
  }
314
334
 
315
335
  const handleDelete = async () => {
316
336
  if (editing) {
317
337
  await deleteAgent(editing.id)
338
+ toast.success('Agent moved to trash')
318
339
  await loadAgents()
319
340
  onClose()
320
341
  }
@@ -414,6 +435,7 @@ export function AgentSheet() {
414
435
  const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
415
436
 
416
437
  return (
438
+ <>
417
439
  <BottomSheet open={open} onClose={onClose} wide>
418
440
  <div className="mb-10 flex items-start justify-between">
419
441
  <div>
@@ -452,12 +474,12 @@ export function AgentSheet() {
452
474
  </div>
453
475
 
454
476
  <div className="mb-8">
455
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
477
+ <SectionLabel>Name</SectionLabel>
456
478
  <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
457
479
  </div>
458
480
 
459
481
  <div className="mb-8">
460
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Avatar</label>
482
+ <SectionLabel>Avatar</SectionLabel>
461
483
  <div className="flex items-center gap-3">
462
484
  <AgentAvatar seed={avatarSeed || null} name={name || 'A'} size={40} />
463
485
  <input
@@ -470,25 +492,29 @@ export function AgentSheet() {
470
492
  />
471
493
  <button
472
494
  type="button"
473
- onClick={() => setAvatarSeed(Math.random().toString(36).slice(2, 10))}
474
- className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] shrink-0"
495
+ onClick={() => setAvatarSeed(crypto.randomUUID().slice(0, 8))}
496
+ className="inline-flex items-center gap-1.5 px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 cursor-pointer transition-all hover:bg-white/[0.04] hover:text-text-2 active:scale-95 shrink-0"
475
497
  style={{ fontFamily: 'inherit' }}
498
+ title="Shuffle avatar"
476
499
  >
477
- Randomize
500
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
501
+ <rect x="4" y="4" width="16" height="16" rx="2" />
502
+ <circle cx="9" cy="9" r="1" fill="currentColor" />
503
+ <circle cx="15" cy="15" r="1" fill="currentColor" />
504
+ </svg>
505
+ Shuffle
478
506
  </button>
479
507
  </div>
480
508
  </div>
481
509
 
482
510
  <div className="mb-8">
483
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Description</label>
511
+ <SectionLabel>Description</SectionLabel>
484
512
  <input type="text" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="What does this agent do?" className={inputClass} style={{ fontFamily: 'inherit' }} />
485
513
  </div>
486
514
 
487
515
  {/* Capabilities — hidden for OpenClaw (gateway manages its own capabilities) */}
488
516
  {!openclawEnabled && <div className="mb-8">
489
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
490
- Capabilities <span className="normal-case tracking-normal font-normal text-text-3">(for agent delegation)</span>
491
- </label>
517
+ <SectionLabel>Capabilities <span className="normal-case tracking-normal font-normal text-text-3">(for agent delegation)</span></SectionLabel>
492
518
  <div className="flex flex-wrap gap-1.5 mb-2">
493
519
  {capabilities.map((cap) => (
494
520
  <span
@@ -568,6 +594,24 @@ export function AgentSheet() {
568
594
  <p className="text-[11px] text-text-3/70 mt-1.5">Controls reasoning depth. Anthropic models use extended thinking; OpenAI o-series uses reasoning_effort. Others get system prompt guidance.</p>
569
595
  </div>
570
596
 
597
+ {/* ElevenLabs Voice ID */}
598
+ {appSettings.elevenLabsEnabled && (
599
+ <div className="mb-8">
600
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
601
+ ElevenLabs Voice ID <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
602
+ </label>
603
+ <input
604
+ type="text"
605
+ value={voiceId}
606
+ onChange={(e) => setVoiceId(e.target.value)}
607
+ placeholder="Leave blank for global default"
608
+ className={inputClass}
609
+ style={{ fontFamily: 'inherit' }}
610
+ />
611
+ <p className="text-[11px] text-text-3/70 mt-1.5">Override the default voice for this agent. Leave blank to use the global default.</p>
612
+ </div>
613
+ )}
614
+
571
615
  {/* Heartbeat Configuration */}
572
616
  <div className="mb-8">
573
617
  <div className="flex items-center justify-between mb-3">
@@ -624,11 +668,45 @@ export function AgentSheet() {
624
668
 
625
669
  {provider !== 'openclaw' && (
626
670
  <div className="mb-8">
627
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
671
+ <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
628
672
  Soul / Personality <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
673
+ {soul !== soulInitial && soulSaveState === 'idle' && (
674
+ <span className="inline-flex items-center gap-1 normal-case tracking-normal text-[10px] text-amber-400 font-600">
675
+ <span className="w-1.5 h-1.5 rounded-full bg-amber-400" />
676
+ Unsaved
677
+ </span>
678
+ )}
679
+ {soulSaveState === 'saved' && (
680
+ <span className="inline-flex items-center gap-1 normal-case tracking-normal text-[10px] text-emerald-400 font-600">
681
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><polyline points="20 6 9 17 4 12" /></svg>
682
+ Saved
683
+ </span>
684
+ )}
629
685
  </label>
630
686
  <div className="flex items-center gap-2 mb-3">
631
687
  <p className="text-[12px] text-text-3/60">Define the agent&apos;s voice, tone, and personality. Injected before the system prompt.</p>
688
+ <button
689
+ type="button"
690
+ onClick={() => setSoul(randomSoul())}
691
+ className="inline-flex items-center gap-1.5 shrink-0 px-2 py-1 rounded-[8px] border border-white/[0.08] bg-transparent text-[11px] text-text-3 hover:text-text-2 cursor-pointer transition-colors"
692
+ style={{ fontFamily: 'inherit' }}
693
+ title="Randomize personality"
694
+ >
695
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
696
+ <rect x="4" y="4" width="16" height="16" rx="2" />
697
+ <circle cx="9" cy="9" r="1" fill="currentColor" />
698
+ <circle cx="15" cy="15" r="1" fill="currentColor" />
699
+ </svg>
700
+ Shuffle
701
+ </button>
702
+ <button
703
+ type="button"
704
+ onClick={() => setSoulLibraryOpen(true)}
705
+ className="shrink-0 px-2 py-1 rounded-[8px] border border-accent-bright/20 bg-accent-soft text-[11px] text-accent-bright hover:brightness-110 cursor-pointer transition-colors"
706
+ style={{ fontFamily: 'inherit' }}
707
+ >
708
+ Browse Library
709
+ </button>
632
710
  <button onClick={() => soulFileRef.current?.click()} className="shrink-0 px-2 py-1 rounded-[8px] border border-white/[0.08] bg-surface text-[11px] text-text-3 hover:text-text-2 cursor-pointer transition-colors" style={{ fontFamily: 'inherit' }}>Upload .md</button>
633
711
  <input ref={soulFileRef} type="file" accept=".md,.txt,.markdown" onChange={handleFileUpload(setSoul)} className="hidden" />
634
712
  </div>
@@ -854,7 +932,7 @@ export function AgentSheet() {
854
932
  )}
855
933
 
856
934
  {!openclawEnabled && <div className="mb-8">
857
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Provider</label>
935
+ <SectionLabel>Provider</SectionLabel>
858
936
  <div className="grid grid-cols-3 gap-3">
859
937
  {providers.filter((p) => !isOrchestrator || p.id !== 'claude-cli').map((p) => {
860
938
  const isConnected = !p.requiresApiKey || Object.values(credentials).some((c) => c.provider === p.id)
@@ -883,7 +961,7 @@ export function AgentSheet() {
883
961
 
884
962
  {!openclawEnabled && currentProvider && currentProvider.models.length > 0 && (
885
963
  <div className="mb-8">
886
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Model</label>
964
+ <SectionLabel>Model</SectionLabel>
887
965
  <ModelCombobox
888
966
  providerId={currentProvider.id}
889
967
  value={model}
@@ -900,7 +978,7 @@ export function AgentSheet() {
900
978
  {/* Ollama Mode Toggle */}
901
979
  {!openclawEnabled && provider === 'ollama' && (
902
980
  <div className="mb-8">
903
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Mode</label>
981
+ <SectionLabel>Mode</SectionLabel>
904
982
  <div className="flex p-1 rounded-[14px] bg-surface border border-white/[0.06]">
905
983
  {(['local', 'cloud'] as const).map((mode) => (
906
984
  <button
@@ -931,9 +1009,7 @@ export function AgentSheet() {
931
1009
 
932
1010
  {!openclawEnabled && (currentProvider?.requiresApiKey || currentProvider?.optionalApiKey || (provider === 'ollama' && ollamaMode === 'cloud')) && (
933
1011
  <div className="mb-8">
934
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
935
- API Key{currentProvider?.optionalApiKey && !currentProvider?.requiresApiKey && <span className="normal-case tracking-normal font-normal text-text-3"> (optional)</span>}
936
- </label>
1012
+ <SectionLabel>API Key{currentProvider?.optionalApiKey && !currentProvider?.requiresApiKey && <span className="normal-case tracking-normal font-normal text-text-3"> (optional)</span>}</SectionLabel>
937
1013
  {providerCredentials.length > 0 && !addingKey ? (
938
1014
  <div className="flex gap-2">
939
1015
  <select value={credentialId || ''} onChange={(e) => {
@@ -1037,9 +1113,7 @@ export function AgentSheet() {
1037
1113
 
1038
1114
  {currentProvider?.requiresEndpoint && (provider === 'openclaw' || (provider === 'ollama' && ollamaMode === 'local')) && (
1039
1115
  <div className="mb-8">
1040
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">
1041
- {provider === 'openclaw' ? 'OpenClaw Endpoint' : 'Endpoint'}
1042
- </label>
1116
+ <SectionLabel>{provider === 'openclaw' ? 'OpenClaw Endpoint' : 'Endpoint'}</SectionLabel>
1043
1117
  <input type="text" value={apiEndpoint || ''} onChange={(e) => setApiEndpoint(e.target.value || null)} placeholder={currentProvider.defaultEndpoint || 'http://localhost:11434'} className={`${inputClass} font-mono text-[14px]`} />
1044
1118
  {provider === 'openclaw' && (
1045
1119
  <p className="text-[13px] text-text-3/70 mt-2">The URL of your OpenClaw gateway</p>
@@ -1058,7 +1132,7 @@ export function AgentSheet() {
1058
1132
  <div
1059
1133
  onClick={() => setTools((prev) => prev.includes(t.id) ? prev.filter((x) => x !== t.id) : [...prev, t.id])}
1060
1134
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
1061
- ${tools.includes(t.id) ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
1135
+ ${tools.includes(t.id) ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
1062
1136
  >
1063
1137
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
1064
1138
  ${tools.includes(t.id) ? 'left-[22px]' : 'left-0.5'}`} />
@@ -1082,7 +1156,7 @@ export function AgentSheet() {
1082
1156
  <div
1083
1157
  onClick={() => setTools((prev) => prev.includes(t.id) ? prev.filter((x) => x !== t.id) : [...prev, t.id])}
1084
1158
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
1085
- ${tools.includes(t.id) ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
1159
+ ${tools.includes(t.id) ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
1086
1160
  >
1087
1161
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
1088
1162
  ${tools.includes(t.id) ? 'left-[22px]' : 'left-0.5'}`} />
@@ -1092,22 +1166,6 @@ export function AgentSheet() {
1092
1166
  </label>
1093
1167
  ))}
1094
1168
  </div>
1095
- {(tools.includes('manage_tasks') || tools.includes('manage_schedules')) && (
1096
- <div className="mt-4 ml-1 pt-3 border-t border-white/[0.04]">
1097
- <label className="flex items-center gap-3 cursor-pointer">
1098
- <div
1099
- onClick={() => setPlatformAssignScope((prev) => prev === 'all' ? 'self' : 'all')}
1100
- className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
1101
- ${platformAssignScope === 'all' ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
1102
- >
1103
- <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
1104
- ${platformAssignScope === 'all' ? 'left-[22px]' : 'left-0.5'}`} />
1105
- </div>
1106
- <span className="font-display text-[14px] font-600 text-text-2">Assign to Other Agents</span>
1107
- <span className="text-[12px] text-text-3">Allow this agent to assign tasks and schedules to other agents</span>
1108
- </label>
1109
- </div>
1110
- )}
1111
1169
  </div>
1112
1170
  )}
1113
1171
 
@@ -1259,7 +1317,7 @@ export function AgentSheet() {
1259
1317
  enabled ? [...prev, fullName] : prev.filter((x) => x !== fullName)
1260
1318
  )}
1261
1319
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
1262
- ${enabled ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
1320
+ ${enabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
1263
1321
  >
1264
1322
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
1265
1323
  ${enabled ? 'left-[22px]' : 'left-0.5'}`} />
@@ -1287,35 +1345,25 @@ export function AgentSheet() {
1287
1345
  if (next && provider === 'claude-cli') setProvider('anthropic')
1288
1346
  }}
1289
1347
  className={`w-11 h-6 rounded-full transition-all duration-200 relative cursor-pointer
1290
- ${isOrchestrator ? 'bg-[#6366F1]' : 'bg-white/[0.08]'}`}
1348
+ ${isOrchestrator ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
1291
1349
  >
1292
1350
  <div className={`absolute top-0.5 w-5 h-5 rounded-full bg-white transition-all duration-200
1293
1351
  ${isOrchestrator ? 'left-[22px]' : 'left-0.5'}`} />
1294
1352
  </div>
1295
- <span className="font-display text-[14px] font-600 text-text-2">Orchestrator</span>
1296
- <span className="text-[12px] text-text-3">Can delegate tasks to other agents</span>
1353
+ <span className="font-display text-[14px] font-600 text-text-2">Can Delegate to Other Agents</span>
1354
+ <span className="text-[12px] text-text-3">Route work to specialized agents and coordinate multi-agent tasks</span>
1297
1355
  </label>
1298
1356
  </div>
1299
1357
  )}
1300
1358
 
1301
1359
  {provider !== 'openclaw' && isOrchestrator && agentOptions.length > 0 && (
1302
1360
  <div className="mb-8">
1303
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Available Agents</label>
1304
- <div className="flex flex-wrap gap-2">
1305
- {agentOptions.map((a) => (
1306
- <button
1307
- key={a.id}
1308
- onClick={() => toggleAgent(a.id)}
1309
- className={`px-3 py-2 rounded-[10px] text-[13px] font-600 cursor-pointer transition-all border
1310
- ${subAgentIds.includes(a.id)
1311
- ? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
1312
- : 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
1313
- style={{ fontFamily: 'inherit' }}
1314
- >
1315
- {a.name}
1316
- </button>
1317
- ))}
1318
- </div>
1361
+ <SectionLabel>Available Agents</SectionLabel>
1362
+ <AgentPickerList
1363
+ agents={agentOptions}
1364
+ selected={subAgentIds}
1365
+ onSelect={(id) => toggleAgent(id)}
1366
+ />
1319
1367
  </div>
1320
1368
  )}
1321
1369
 
@@ -1366,7 +1414,7 @@ export function AgentSheet() {
1366
1414
  onClick={handleTestAndSave}
1367
1415
  disabled={!name.trim() || providerNeedsKey || testStatus === 'testing' || saving || (!openclawEnabled && testStatus === 'pass')}
1368
1416
  className={`flex-1 py-3.5 rounded-[14px] border-none text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-60 transition-all hover:brightness-110
1369
- ${testStatus === 'pass' ? 'bg-emerald-600 shadow-[0_4px_20px_rgba(16,185,129,0.25)]' : 'bg-[#6366F1] shadow-[0_4px_20px_rgba(99,102,241,0.25)]'}`}
1417
+ ${testStatus === 'pass' ? 'bg-emerald-600 shadow-[0_4px_20px_rgba(16,185,129,0.25)]' : 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.25)]'}`}
1370
1418
  style={{ fontFamily: 'inherit' }}
1371
1419
  >
1372
1420
  {openclawEnabled
@@ -1379,6 +1427,13 @@ export function AgentSheet() {
1379
1427
  </button>
1380
1428
  </div>
1381
1429
  </BottomSheet>
1430
+
1431
+ <SoulLibraryPicker
1432
+ open={soulLibraryOpen}
1433
+ onClose={() => setSoulLibraryOpen(false)}
1434
+ onSelect={(s) => setSoul(s)}
1435
+ />
1436
+ </>
1382
1437
  )
1383
1438
  }
1384
1439
 
@@ -12,6 +12,11 @@ import { CronJobForm } from './cron-job-form'
12
12
 
13
13
  interface Props {
14
14
  agent: Agent
15
+ onEditAgent?: () => void
16
+ onClearHistory?: () => void
17
+ onDeleteAgent?: () => void
18
+ onDeleteChat?: () => void
19
+ isMainChat?: boolean
15
20
  }
16
21
 
17
22
  type InspectorTab = 'overview' | 'files' | 'skills' | 'automations' | 'advanced'
@@ -24,7 +29,7 @@ const TABS: { id: InspectorTab; label: string; openclawOnly?: boolean }[] = [
24
29
  { id: 'advanced', label: 'Advanced' },
25
30
  ]
26
31
 
27
- export function InspectorPanel({ agent }: Props) {
32
+ export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: Props) {
28
33
  const inspectorTab = useAppStore((s) => s.inspectorTab)
29
34
  const setInspectorTab = useAppStore((s) => s.setInspectorTab)
30
35
  const setInspectorOpen = useAppStore((s) => s.setInspectorOpen)
@@ -52,7 +57,7 @@ export function InspectorPanel({ agent }: Props) {
52
57
  const agentSchedules = Object.values(schedules).filter((s) => s.agentId === agent.id)
53
58
 
54
59
  return (
55
- <div className="w-[400px] shrink-0 border-l border-white/[0.06] bg-[#0d0f1a] flex flex-col h-full overflow-hidden fade-up-delay">
60
+ <div className="w-[400px] shrink-0 border-l border-white/[0.06] bg-bg flex flex-col h-full overflow-hidden fade-up-delay">
56
61
  {/* Header */}
57
62
  <div className="flex items-center justify-between px-4 py-3 border-b border-white/[0.06] shrink-0">
58
63
  <h3 className="font-display text-[14px] font-600 text-text truncate">{agent.name}</h3>
@@ -69,12 +74,14 @@ export function InspectorPanel({ agent }: Props) {
69
74
  </div>
70
75
 
71
76
  {/* Tab bar */}
72
- <div className="flex gap-0.5 px-3 pt-2 pb-1 overflow-x-auto shrink-0">
77
+ <div className="flex gap-0.5 px-3 pt-2 pb-1 overflow-x-auto shrink-0" role="tablist">
73
78
  {visibleTabs.map((tab) => (
74
79
  <button
75
80
  key={tab.id}
81
+ role="tab"
76
82
  onClick={() => setInspectorTab(tab.id)}
77
- className={`px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all whitespace-nowrap
83
+ aria-selected={inspectorTab === tab.id}
84
+ className={`px-2.5 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all whitespace-nowrap focus-visible:ring-1 focus-visible:ring-accent-bright/50
78
85
  ${inspectorTab === tab.id
79
86
  ? 'bg-accent-soft text-accent-bright'
80
87
  : 'bg-transparent text-text-3 hover:text-text-2'}`}
@@ -88,7 +95,14 @@ export function InspectorPanel({ agent }: Props) {
88
95
  {/* Tab content */}
89
96
  <div className="flex-1 min-h-0 overflow-y-auto">
90
97
  {inspectorTab === 'overview' && (
91
- <OverviewTab agent={agent} />
98
+ <OverviewTab
99
+ agent={agent}
100
+ onEditAgent={onEditAgent}
101
+ onClearHistory={onClearHistory}
102
+ onDeleteAgent={onDeleteAgent}
103
+ onDeleteChat={onDeleteChat}
104
+ isMainChat={isMainChat}
105
+ />
92
106
  )}
93
107
  {inspectorTab === 'files' && isOpenClaw && (
94
108
  <AgentFilesEditor agentId={agent.id} />
@@ -117,7 +131,16 @@ export function InspectorPanel({ agent }: Props) {
117
131
  )
118
132
  }
119
133
 
120
- function OverviewTab({ agent }: { agent: Agent }) {
134
+ interface OverviewTabProps {
135
+ agent: Agent
136
+ onEditAgent?: () => void
137
+ onClearHistory?: () => void
138
+ onDeleteAgent?: () => void
139
+ onDeleteChat?: () => void
140
+ isMainChat?: boolean
141
+ }
142
+
143
+ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: OverviewTabProps) {
121
144
  return (
122
145
  <div className="p-4 flex flex-col gap-4">
123
146
  <div>
@@ -160,6 +183,58 @@ function OverviewTab({ agent }: { agent: Agent }) {
160
183
  </div>
161
184
  </div>
162
185
  )}
186
+
187
+ {/* Actions */}
188
+ {(onEditAgent || onClearHistory || onDeleteAgent || onDeleteChat) && (
189
+ <>
190
+ <div className="border-t border-white/[0.06] mt-2" />
191
+ <div className="flex flex-col gap-2">
192
+ {onEditAgent && (
193
+ <button
194
+ onClick={onEditAgent}
195
+ className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-accent-bright bg-accent-soft/50 border border-accent-bright/10 cursor-pointer transition-all hover:bg-accent-soft"
196
+ style={{ fontFamily: 'inherit' }}
197
+ >
198
+ Edit Agent
199
+ </button>
200
+ )}
201
+ {(onClearHistory || onDeleteAgent || onDeleteChat) && (
202
+ <>
203
+ <label className="block text-[11px] font-600 uppercase tracking-wider text-red-400/50 mt-2">Danger Zone</label>
204
+ <div className="flex flex-col gap-1.5">
205
+ {onClearHistory && (
206
+ <button
207
+ onClick={onClearHistory}
208
+ className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
209
+ style={{ fontFamily: 'inherit' }}
210
+ >
211
+ Clear History
212
+ </button>
213
+ )}
214
+ {onDeleteAgent && !isMainChat && (
215
+ <button
216
+ onClick={onDeleteAgent}
217
+ className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
218
+ style={{ fontFamily: 'inherit' }}
219
+ >
220
+ Delete Agent
221
+ </button>
222
+ )}
223
+ {onDeleteChat && !isMainChat && (
224
+ <button
225
+ onClick={onDeleteChat}
226
+ className="w-full px-3 py-2 rounded-[8px] text-[12px] font-600 text-red-400/80 bg-red-400/[0.04] border border-red-400/[0.08] cursor-pointer transition-all hover:bg-red-400/[0.08] hover:text-red-400 text-left"
227
+ style={{ fontFamily: 'inherit' }}
228
+ >
229
+ Delete Chat
230
+ </button>
231
+ )}
232
+ </div>
233
+ </>
234
+ )}
235
+ </div>
236
+ </>
237
+ )}
163
238
  </div>
164
239
  )
165
240
  }
@@ -23,6 +23,7 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
23
23
  const [saving, setSaving] = useState(false)
24
24
  const [installTarget, setInstallTarget] = useState<OpenClawSkillEntry | null>(null)
25
25
  const [removeTarget, setRemoveTarget] = useState<OpenClawSkillEntry | null>(null)
26
+ const [readinessFilter, setReadinessFilter] = useState<'all' | 'ready' | 'needs-setup'>('all')
26
27
 
27
28
  const loadSkills = useCallback(async () => {
28
29
  setLoading(true)
@@ -67,15 +68,24 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
67
68
  }
68
69
  }
69
70
 
71
+ const readyCount = skills.filter((s) => s.eligible).length
72
+ const needsSetupCount = skills.filter((s) => !s.eligible).length
73
+
74
+ const filteredSkills = readinessFilter === 'all'
75
+ ? skills
76
+ : readinessFilter === 'ready'
77
+ ? skills.filter((s) => s.eligible)
78
+ : skills.filter((s) => !s.eligible)
79
+
70
80
  const grouped = SOURCE_ORDER
71
81
  .map((source) => ({
72
82
  source,
73
- items: skills.filter((s) => s.source === source),
83
+ items: filteredSkills.filter((s) => s.source === source),
74
84
  }))
75
85
  .filter((g) => g.items.length > 0)
76
86
 
77
87
  if (loading) {
78
- return <div className="flex items-center justify-center h-32 text-[13px] text-text-3/50">Loading skills...</div>
88
+ return <div className="flex items-center justify-center gap-2 h-32 text-[13px] text-text-3/50"><span className="w-3 h-3 rounded-full border-2 border-text-3/20 border-t-accent-bright animate-spin" />Loading skills...</div>
79
89
  }
80
90
 
81
91
  if (error) {
@@ -90,7 +100,7 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
90
100
  <button
91
101
  key={m}
92
102
  onClick={() => handleModeChange(m)}
93
- className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all
103
+ className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 capitalize cursor-pointer transition-all focus-visible:ring-1 focus-visible:ring-accent-bright/50
94
104
  ${mode === m ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
95
105
  style={{ fontFamily: 'inherit' }}
96
106
  >
@@ -99,6 +109,25 @@ export function OpenClawSkillsPanel({ agentId, initialMode = 'all', initialAllow
99
109
  ))}
100
110
  </div>
101
111
 
112
+ {/* Readiness filter */}
113
+ <div className="flex gap-1">
114
+ {([
115
+ { key: 'all' as const, label: `All (${skills.length})` },
116
+ { key: 'ready' as const, label: `Ready (${readyCount})` },
117
+ { key: 'needs-setup' as const, label: `Needs Setup (${needsSetupCount})` },
118
+ ]).map((f) => (
119
+ <button
120
+ key={f.key}
121
+ onClick={() => setReadinessFilter(f.key)}
122
+ className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all focus-visible:ring-1 focus-visible:ring-accent-bright/50
123
+ ${readinessFilter === f.key ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`}
124
+ style={{ fontFamily: 'inherit' }}
125
+ >
126
+ {f.label}
127
+ </button>
128
+ ))}
129
+ </div>
130
+
102
131
  {/* Skill groups */}
103
132
  {grouped.map(({ source, items }) => (
104
133
  <div key={source}>