@swarmclawai/swarmclaw 0.6.4 → 0.6.7

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 (143) hide show
  1. package/README.md +62 -30
  2. package/package.json +10 -1
  3. package/src/app/api/agents/[id]/clone/route.ts +40 -0
  4. package/src/app/api/agents/route.ts +39 -14
  5. package/src/app/api/chatrooms/[id]/chat/route.ts +58 -3
  6. package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
  7. package/src/app/api/chatrooms/[id]/route.ts +34 -2
  8. package/src/app/api/chatrooms/route.ts +26 -3
  9. package/src/app/api/connectors/[id]/health/route.ts +64 -0
  10. package/src/app/api/connectors/route.ts +17 -2
  11. package/src/app/api/knowledge/route.ts +6 -1
  12. package/src/app/api/openclaw/doctor/route.ts +17 -0
  13. package/src/app/api/schedules/[id]/run/route.ts +3 -0
  14. package/src/app/api/sessions/[id]/chat/route.ts +5 -1
  15. package/src/app/api/sessions/route.ts +11 -2
  16. package/src/app/api/tasks/[id]/route.ts +18 -13
  17. package/src/app/api/tasks/route.ts +44 -1
  18. package/src/app/api/usage/route.ts +16 -7
  19. package/src/app/api/wallets/[id]/approve/route.ts +62 -0
  20. package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
  21. package/src/app/api/wallets/[id]/route.ts +118 -0
  22. package/src/app/api/wallets/[id]/send/route.ts +118 -0
  23. package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
  24. package/src/app/api/wallets/route.ts +74 -0
  25. package/src/app/globals.css +8 -0
  26. package/src/cli/index.js +20 -0
  27. package/src/cli/index.ts +223 -39
  28. package/src/cli/spec.js +14 -0
  29. package/src/components/agents/agent-avatar.tsx +15 -1
  30. package/src/components/agents/agent-card.tsx +38 -6
  31. package/src/components/agents/agent-chat-list.tsx +79 -3
  32. package/src/components/agents/agent-sheet.tsx +191 -26
  33. package/src/components/auth/setup-wizard.tsx +268 -353
  34. package/src/components/chat/chat-area.tsx +24 -9
  35. package/src/components/chat/chat-header.tsx +48 -19
  36. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  37. package/src/components/chat/delegation-banner.test.ts +27 -0
  38. package/src/components/chat/delegation-banner.tsx +109 -23
  39. package/src/components/chat/message-bubble.tsx +17 -16
  40. package/src/components/chat/message-list.tsx +6 -5
  41. package/src/components/chat/streaming-bubble.tsx +3 -2
  42. package/src/components/chat/thinking-indicator.tsx +3 -2
  43. package/src/components/chat/transfer-agent-picker.tsx +1 -1
  44. package/src/components/chatrooms/agent-hover-card.tsx +1 -1
  45. package/src/components/chatrooms/chatroom-input.tsx +1 -1
  46. package/src/components/chatrooms/chatroom-message.tsx +165 -23
  47. package/src/components/chatrooms/chatroom-sheet.tsx +289 -4
  48. package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
  49. package/src/components/chatrooms/chatroom-view.tsx +62 -17
  50. package/src/components/connectors/connector-health.tsx +120 -0
  51. package/src/components/connectors/connector-list.tsx +1 -1
  52. package/src/components/connectors/connector-sheet.tsx +9 -0
  53. package/src/components/home/home-view.tsx +25 -3
  54. package/src/components/input/chat-input.tsx +8 -1
  55. package/src/components/knowledge/knowledge-list.tsx +1 -1
  56. package/src/components/knowledge/knowledge-sheet.tsx +1 -1
  57. package/src/components/layout/app-layout.tsx +35 -4
  58. package/src/components/memory/memory-agent-list.tsx +1 -1
  59. package/src/components/memory/memory-browser.tsx +1 -0
  60. package/src/components/memory/memory-card.tsx +3 -2
  61. package/src/components/memory/memory-detail.tsx +3 -3
  62. package/src/components/memory/memory-sheet.tsx +2 -2
  63. package/src/components/projects/project-detail.tsx +4 -4
  64. package/src/components/schedules/schedule-list.tsx +55 -9
  65. package/src/components/schedules/schedule-sheet.tsx +134 -23
  66. package/src/components/secrets/secret-sheet.tsx +1 -1
  67. package/src/components/secrets/secrets-list.tsx +1 -1
  68. package/src/components/sessions/session-card.tsx +1 -1
  69. package/src/components/shared/agent-picker-list.tsx +1 -1
  70. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  71. package/src/components/shared/command-palette.tsx +237 -0
  72. package/src/components/shared/connector-platform-icon.tsx +1 -0
  73. package/src/components/shared/settings/section-user-preferences.tsx +4 -4
  74. package/src/components/skills/skill-list.tsx +1 -1
  75. package/src/components/skills/skill-sheet.tsx +1 -1
  76. package/src/components/tasks/task-board.tsx +3 -3
  77. package/src/components/tasks/task-card.tsx +22 -2
  78. package/src/components/tasks/task-sheet.tsx +112 -17
  79. package/src/components/usage/metrics-dashboard.tsx +13 -25
  80. package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
  81. package/src/components/wallets/wallet-panel.tsx +616 -0
  82. package/src/components/wallets/wallet-section.tsx +100 -0
  83. package/src/hooks/use-swipe.ts +49 -0
  84. package/src/lib/providers/anthropic.ts +16 -2
  85. package/src/lib/providers/claude-cli.ts +7 -1
  86. package/src/lib/providers/index.ts +7 -0
  87. package/src/lib/providers/ollama.ts +16 -2
  88. package/src/lib/providers/openai.ts +7 -2
  89. package/src/lib/providers/openclaw.ts +6 -1
  90. package/src/lib/providers/provider-defaults.ts +7 -0
  91. package/src/lib/schedule-templates.ts +115 -0
  92. package/src/lib/server/agent-registry.ts +2 -2
  93. package/src/lib/server/alert-dispatch.ts +64 -0
  94. package/src/lib/server/chat-execution.ts +76 -4
  95. package/src/lib/server/chatroom-health.ts +60 -0
  96. package/src/lib/server/chatroom-helpers.test.ts +94 -0
  97. package/src/lib/server/chatroom-helpers.ts +86 -12
  98. package/src/lib/server/chatroom-routing.ts +65 -0
  99. package/src/lib/server/connectors/discord.ts +3 -0
  100. package/src/lib/server/connectors/email.ts +267 -0
  101. package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
  102. package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
  103. package/src/lib/server/connectors/manager.ts +239 -5
  104. package/src/lib/server/connectors/openclaw.ts +3 -0
  105. package/src/lib/server/connectors/slack.ts +6 -0
  106. package/src/lib/server/connectors/telegram.ts +18 -0
  107. package/src/lib/server/connectors/types.ts +2 -0
  108. package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
  109. package/src/lib/server/connectors/whatsapp-text.ts +26 -0
  110. package/src/lib/server/connectors/whatsapp.ts +17 -5
  111. package/src/lib/server/cost.ts +70 -0
  112. package/src/lib/server/create-notification.ts +2 -0
  113. package/src/lib/server/daemon-state.ts +124 -0
  114. package/src/lib/server/dag-validation.ts +115 -0
  115. package/src/lib/server/memory-db.ts +12 -7
  116. package/src/lib/server/openclaw-doctor.ts +48 -0
  117. package/src/lib/server/orchestrator-lg.ts +12 -2
  118. package/src/lib/server/orchestrator.ts +6 -1
  119. package/src/lib/server/queue-followups.test.ts +224 -0
  120. package/src/lib/server/queue.ts +238 -24
  121. package/src/lib/server/scheduler.ts +3 -0
  122. package/src/lib/server/session-run-manager.ts +22 -1
  123. package/src/lib/server/session-tools/chatroom.ts +11 -2
  124. package/src/lib/server/session-tools/context-mgmt.ts +2 -2
  125. package/src/lib/server/session-tools/index.ts +8 -2
  126. package/src/lib/server/session-tools/memory.ts +23 -4
  127. package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
  128. package/src/lib/server/session-tools/shell.ts +1 -1
  129. package/src/lib/server/session-tools/wallet.ts +124 -0
  130. package/src/lib/server/session-tools/web.ts +2 -2
  131. package/src/lib/server/solana.ts +122 -0
  132. package/src/lib/server/storage.ts +158 -6
  133. package/src/lib/server/stream-agent-chat.ts +126 -63
  134. package/src/lib/server/task-mention.test.ts +41 -0
  135. package/src/lib/server/task-mention.ts +3 -2
  136. package/src/lib/setup-defaults.ts +277 -0
  137. package/src/lib/tool-definitions.ts +1 -0
  138. package/src/lib/validation/schemas.ts +69 -0
  139. package/src/lib/view-routes.ts +1 -0
  140. package/src/stores/use-app-store.ts +15 -3
  141. package/src/stores/use-chatroom-store.ts +52 -2
  142. package/src/types/index.ts +98 -2
  143. package/tsconfig.json +2 -1
@@ -4,36 +4,14 @@ import { useMemo, useState } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
6
  import type { ProviderType, Credential } from '@/types'
7
+ import {
8
+ SETUP_PROVIDERS,
9
+ DEFAULT_AGENTS,
10
+ type SetupProvider,
11
+ } from '@/lib/setup-defaults'
7
12
 
8
- type WizardProvider =
9
- | 'anthropic'
10
- | 'openai'
11
- | 'google'
12
- | 'deepseek'
13
- | 'groq'
14
- | 'together'
15
- | 'mistral'
16
- | 'xai'
17
- | 'fireworks'
18
- | 'ollama'
19
- | 'openclaw'
20
13
  type CheckState = 'idle' | 'checking' | 'ok' | 'error'
21
14
 
22
- interface WizardProviderOption {
23
- id: WizardProvider
24
- name: string
25
- description: string
26
- requiresKey: boolean
27
- supportsEndpoint: boolean
28
- defaultEndpoint?: string
29
- keyUrl?: string
30
- keyLabel?: string
31
- keyPlaceholder?: string
32
- optionalKey?: boolean
33
- badge?: string
34
- icon: string
35
- }
36
-
37
15
  interface ProviderCheckResponse {
38
16
  ok: boolean
39
17
  message: string
@@ -60,240 +38,11 @@ interface SetupWizardProps {
60
38
  onComplete: () => void
61
39
  }
62
40
 
63
- const STARTER_AGENT_TOOLS = [
64
- 'memory',
65
- 'files',
66
- 'web_search',
67
- 'web_fetch',
68
- 'browser',
69
- 'manage_agents',
70
- 'manage_tasks',
71
- 'manage_schedules',
72
- 'manage_skills',
73
- 'manage_connectors',
74
- 'manage_sessions',
75
- 'manage_secrets',
76
- 'manage_documents',
77
- 'manage_webhooks',
78
- 'claude_code',
79
- 'codex_cli',
80
- 'opencode_cli',
81
- ]
82
-
83
- const SWARMCLAW_ASSISTANT_PROMPT = `You are the default SwarmClaw assistant inside the SwarmClaw dashboard.
84
-
85
- Primary objective:
86
- - Help the user operate SwarmClaw itself before anything else.
87
-
88
- When the user asks about SwarmClaw, prioritize concrete guidance with exact UI paths and commands:
89
- - Sessions: create, configure provider/model, and run chats.
90
- - Agents: create specialist agents/orchestrators, set provider/model, tools, and prompts.
91
- - Providers: connect API keys/endpoints, troubleshoot auth/model issues.
92
- - Tasks + Schedules: queue work and automate recurring runs.
93
- - Skills + Connectors + Webhooks + Secrets + Memory: explain when to use each and how to configure safely.
94
-
95
- Behavior:
96
- - Be concise, direct, and action-oriented.
97
- - If the request is ambiguous, ask one focused clarifying question.
98
- - Prefer step-by-step instructions that can be executed immediately.
99
- - When the user asks for direct execution (for example browsing, screenshots, research, or file edits), use available tools and return real results instead of only describing what to do.
100
- - If a capability depends on provider/tool configuration, call that out explicitly.`
101
-
102
- const PROVIDERS: WizardProviderOption[] = [
103
- {
104
- id: 'openai',
105
- name: 'OpenAI',
106
- description: 'Great default for most users. Fast, reliable GPT models.',
107
- requiresKey: true,
108
- supportsEndpoint: true,
109
- defaultEndpoint: 'https://api.openai.com/v1',
110
- keyUrl: 'https://platform.openai.com/api-keys',
111
- keyLabel: 'platform.openai.com',
112
- badge: 'Recommended',
113
- icon: 'O',
114
- },
115
- {
116
- id: 'anthropic',
117
- name: 'Anthropic',
118
- description: 'Claude models — strong for coding, analysis, and long-form reasoning.',
119
- requiresKey: true,
120
- supportsEndpoint: false,
121
- keyUrl: 'https://console.anthropic.com/settings/keys',
122
- keyLabel: 'console.anthropic.com',
123
- icon: 'A',
124
- },
125
- {
126
- id: 'google',
127
- name: 'Google Gemini',
128
- description: 'Gemini models with strong multimodal and coding support.',
129
- requiresKey: true,
130
- supportsEndpoint: false,
131
- keyUrl: 'https://aistudio.google.com/app/apikey',
132
- keyLabel: 'aistudio.google.com',
133
- keyPlaceholder: 'AIza...',
134
- icon: 'G',
135
- },
136
- {
137
- id: 'deepseek',
138
- name: 'DeepSeek',
139
- description: 'High-value reasoning and coding models from DeepSeek.',
140
- requiresKey: true,
141
- supportsEndpoint: false,
142
- keyUrl: 'https://platform.deepseek.com/api_keys',
143
- keyLabel: 'platform.deepseek.com',
144
- icon: 'D',
145
- },
146
- {
147
- id: 'groq',
148
- name: 'Groq',
149
- description: 'Very fast inference with open and reasoning model options.',
150
- requiresKey: true,
151
- supportsEndpoint: false,
152
- keyUrl: 'https://console.groq.com/keys',
153
- keyLabel: 'console.groq.com',
154
- icon: 'G',
155
- },
156
- {
157
- id: 'together',
158
- name: 'Together AI',
159
- description: 'Broad catalog of open models with OpenAI-compatible APIs.',
160
- requiresKey: true,
161
- supportsEndpoint: false,
162
- keyUrl: 'https://api.together.xyz/settings/api-keys',
163
- keyLabel: 'api.together.xyz',
164
- icon: 'T',
165
- },
166
- {
167
- id: 'mistral',
168
- name: 'Mistral AI',
169
- description: 'Efficient frontier models with strong latency and quality.',
170
- requiresKey: true,
171
- supportsEndpoint: false,
172
- keyUrl: 'https://console.mistral.ai/api-keys/',
173
- keyLabel: 'console.mistral.ai',
174
- icon: 'M',
175
- },
176
- {
177
- id: 'xai',
178
- name: 'xAI (Grok)',
179
- description: 'Grok models for fast answers, coding, and analysis.',
180
- requiresKey: true,
181
- supportsEndpoint: false,
182
- keyUrl: 'https://console.x.ai',
183
- keyLabel: 'console.x.ai',
184
- icon: 'X',
185
- },
186
- {
187
- id: 'fireworks',
188
- name: 'Fireworks AI',
189
- description: 'Serverless and optimized open-model inference endpoints.',
190
- requiresKey: true,
191
- supportsEndpoint: false,
192
- keyUrl: 'https://fireworks.ai/account/api-keys',
193
- keyLabel: 'fireworks.ai',
194
- icon: 'F',
195
- },
196
- {
197
- id: 'openclaw',
198
- name: 'OpenClaw',
199
- description: 'Connect to your local or remote OpenClaw gateway (multi-OpenClaw ready).',
200
- requiresKey: false,
201
- supportsEndpoint: true,
202
- defaultEndpoint: 'http://localhost:18789/v1',
203
- optionalKey: true,
204
- badge: 'OpenClaw',
205
- icon: 'C',
206
- },
207
- {
208
- id: 'ollama',
209
- name: 'Ollama',
210
- description: 'Run local open-source models. No API key required.',
211
- requiresKey: false,
212
- supportsEndpoint: true,
213
- defaultEndpoint: 'http://localhost:11434',
214
- badge: 'Local',
215
- icon: 'L',
216
- },
217
- ]
218
-
219
- const DEFAULT_AGENTS: Record<WizardProvider, { name: string; description: string; systemPrompt: string; model: string; tools: string[] }> = {
220
- anthropic: {
221
- name: 'Assistant',
222
- description: 'A helpful Claude-powered assistant.',
223
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
224
- model: 'claude-sonnet-4-6',
225
- tools: STARTER_AGENT_TOOLS,
226
- },
227
- openai: {
228
- name: 'Assistant',
229
- description: 'A helpful GPT-powered assistant.',
230
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
231
- model: 'gpt-4o',
232
- tools: STARTER_AGENT_TOOLS,
233
- },
234
- google: {
235
- name: 'Assistant',
236
- description: 'A helpful Gemini-powered assistant.',
237
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
238
- model: 'gemini-2.5-pro',
239
- tools: STARTER_AGENT_TOOLS,
240
- },
241
- deepseek: {
242
- name: 'Assistant',
243
- description: 'A helpful DeepSeek-powered assistant.',
244
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
245
- model: 'deepseek-chat',
246
- tools: STARTER_AGENT_TOOLS,
247
- },
248
- groq: {
249
- name: 'Assistant',
250
- description: 'A low-latency assistant powered by Groq.',
251
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
252
- model: 'llama-3.3-70b-versatile',
253
- tools: STARTER_AGENT_TOOLS,
254
- },
255
- together: {
256
- name: 'Assistant',
257
- description: 'A helpful assistant powered by Together AI.',
258
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
259
- model: 'meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8',
260
- tools: STARTER_AGENT_TOOLS,
261
- },
262
- mistral: {
263
- name: 'Assistant',
264
- description: 'A helpful assistant powered by Mistral.',
265
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
266
- model: 'mistral-large-latest',
267
- tools: STARTER_AGENT_TOOLS,
268
- },
269
- xai: {
270
- name: 'Assistant',
271
- description: 'A helpful assistant powered by xAI Grok.',
272
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
273
- model: 'grok-3',
274
- tools: STARTER_AGENT_TOOLS,
275
- },
276
- fireworks: {
277
- name: 'Assistant',
278
- description: 'A helpful assistant powered by Fireworks AI.',
279
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
280
- model: 'accounts/fireworks/models/deepseek-r1-0528',
281
- tools: STARTER_AGENT_TOOLS,
282
- },
283
- ollama: {
284
- name: 'Assistant',
285
- description: 'A local assistant running through Ollama.',
286
- systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
287
- model: 'llama3',
288
- tools: STARTER_AGENT_TOOLS,
289
- },
290
- openclaw: {
291
- name: 'OpenClaw Operator',
292
- description: 'A manager agent for talking to and coordinating OpenClaw instances.',
293
- systemPrompt: 'You are an operator focused on reliable execution, clear status updates, and task completion.',
294
- model: 'default',
295
- tools: STARTER_AGENT_TOOLS,
296
- },
41
+ interface ConfiguredProvider {
42
+ provider: SetupProvider
43
+ credentialId: string | null
44
+ agentId: string
45
+ agentName: string
297
46
  }
298
47
 
299
48
  function SparkleIcon() {
@@ -339,13 +88,13 @@ function StepDots({ current, total }: { current: number; total: number }) {
339
88
  )
340
89
  }
341
90
 
342
- function SkipLink({ onClick }: { onClick: () => void }) {
91
+ function SkipLink({ onClick, label }: { onClick: () => void; label?: string }) {
343
92
  return (
344
93
  <button
345
94
  onClick={onClick}
346
95
  className="mt-8 text-[13px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none"
347
96
  >
348
- Skip setup for now
97
+ {label || 'Skip setup for now'}
349
98
  </button>
350
99
  )
351
100
  }
@@ -359,6 +108,30 @@ function ProviderBadge({ label }: { label?: string }) {
359
108
  )
360
109
  }
361
110
 
111
+ function ConfiguredChips({ providers }: { providers: ConfiguredProvider[] }) {
112
+ if (providers.length === 0) return null
113
+ return (
114
+ <div className="flex flex-wrap gap-2 justify-center mb-6">
115
+ {providers.map((cp) => (
116
+ <span
117
+ key={cp.provider}
118
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/25 text-emerald-300 text-[12px] font-500"
119
+ >
120
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
121
+ {cp.agentName}
122
+ </span>
123
+ ))}
124
+ </div>
125
+ )
126
+ }
127
+
128
+ const CONNECTOR_ICONS = [
129
+ { name: 'Discord', icon: 'D' },
130
+ { name: 'Slack', icon: 'S' },
131
+ { name: 'Telegram', icon: 'T' },
132
+ { name: 'WhatsApp', icon: 'W' },
133
+ ]
134
+
362
135
  function getOpenClawErrorHint(message: string): string | null {
363
136
  const lower = message.toLowerCase()
364
137
  if (lower.includes('timeout') || lower.includes('timed out'))
@@ -374,7 +147,7 @@ function getOpenClawErrorHint(message: string): string | null {
374
147
 
375
148
  export function SetupWizard({ onComplete }: SetupWizardProps) {
376
149
  const [step, setStep] = useState(0)
377
- const [provider, setProvider] = useState<WizardProvider | null>(null)
150
+ const [provider, setProvider] = useState<SetupProvider | null>(null)
378
151
  const [endpoint, setEndpoint] = useState('')
379
152
  const [apiKey, setApiKey] = useState('')
380
153
  const [credentialId, setCredentialId] = useState<string | null>(null)
@@ -391,15 +164,18 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
391
164
  const [agentPrompt, setAgentPrompt] = useState('')
392
165
  const [agentModel, setAgentModel] = useState('')
393
166
 
167
+ const [configuredProviders, setConfiguredProviders] = useState<ConfiguredProvider[]>([])
168
+
394
169
  const selectedProvider = useMemo(
395
- () => PROVIDERS.find((p) => p.id === provider) || null,
170
+ () => SETUP_PROVIDERS.find((p) => p.id === provider) || null,
396
171
  [provider],
397
172
  )
398
- const totalSteps = 3
173
+ const totalSteps = 4
399
174
  const requiresKey = selectedProvider?.requiresKey || false
400
175
  const supportsEndpoint = selectedProvider?.supportsEndpoint || false
401
176
  const keyIsOptional = selectedProvider?.optionalKey || false
402
177
  const requiresVerifiedConnection = provider === 'openclaw'
178
+ const configuredIds = new Set(configuredProviders.map((cp) => cp.provider))
403
179
 
404
180
  const skip = async () => {
405
181
  try {
@@ -410,9 +186,9 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
410
186
  onComplete()
411
187
  }
412
188
 
413
- const selectProvider = (next: WizardProvider) => {
189
+ const selectProvider = (next: SetupProvider) => {
414
190
  const defaults = DEFAULT_AGENTS[next]
415
- const meta = PROVIDERS.find((p) => p.id === next)
191
+ const meta = SETUP_PROVIDERS.find((p) => p.id === next)
416
192
 
417
193
  setProvider(next)
418
194
  setEndpoint(meta?.defaultEndpoint || '')
@@ -462,9 +238,9 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
462
238
  setCheckState(result.ok ? 'ok' : 'error')
463
239
  setCheckMessage(result.message || (result.ok ? 'Connected successfully.' : 'Connection failed.'))
464
240
  return !!result.ok
465
- } catch (err: any) {
241
+ } catch (err: unknown) {
466
242
  setCheckState('error')
467
- setCheckMessage(err?.message || 'Connection check failed.')
243
+ setCheckMessage(err instanceof Error ? err.message : String(err))
468
244
  return false
469
245
  }
470
246
  }
@@ -476,10 +252,10 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
476
252
  const report = await api<SetupDoctorResponse>('GET', '/setup/doctor')
477
253
  setDoctorReport(report)
478
254
  setDoctorState('done')
479
- } catch (err: any) {
255
+ } catch (err: unknown) {
480
256
  setDoctorState('error')
481
257
  setDoctorReport(null)
482
- setDoctorError(err?.message || 'Failed to run setup diagnostics.')
258
+ setDoctorError(err instanceof Error ? err.message : String(err))
483
259
  }
484
260
  }
485
261
 
@@ -515,14 +291,14 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
515
291
 
516
292
  setCredentialId(nextCredentialId || null)
517
293
  setStep(2)
518
- } catch (err: any) {
519
- setError(err?.message || 'Failed to save provider setup.')
294
+ } catch (err: unknown) {
295
+ setError(err instanceof Error ? err.message : String(err))
520
296
  } finally {
521
297
  setSaving(false)
522
298
  }
523
299
  }
524
300
 
525
- const createStarterAgent = async () => {
301
+ const createAgentAndFinish = async (addAnother: boolean) => {
526
302
  if (!provider || !agentName.trim()) return
527
303
  if (requiresVerifiedConnection && checkState !== 'ok') {
528
304
  setError('OpenClaw connection is not verified. Go back and run the connection check.')
@@ -546,35 +322,51 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
546
322
  payload.apiEndpoint = endpoint.trim()
547
323
  }
548
324
 
549
- const agents = await api<Record<string, { id: string }>>('GET', '/agents')
550
- const canReuseDefault =
551
- !!agents.default
552
- && Object.keys(agents).length === 1
553
- && agentName.trim().toLowerCase() === 'assistant'
325
+ const isFirstProvider = configuredProviders.length === 0
326
+ let agentId: string
554
327
 
555
- const agentId = canReuseDefault
556
- ? 'default'
557
- : (await api<{ id: string }>('POST', '/agents', payload)).id
328
+ if (isFirstProvider) {
329
+ const agents = await api<Record<string, { id: string }>>('GET', '/agents')
330
+ const canReuseDefault =
331
+ !!agents.default
332
+ && Object.keys(agents).length === 1
333
+ && agentName.trim().toLowerCase() === DEFAULT_AGENTS[provider].name.toLowerCase()
558
334
 
559
- if (canReuseDefault) {
560
- await api('PUT', '/agents/default', payload)
561
- }
335
+ agentId = canReuseDefault
336
+ ? 'default'
337
+ : (await api<{ id: string }>('POST', '/agents', payload)).id
338
+
339
+ if (canReuseDefault) {
340
+ await api('PUT', '/agents/default', payload)
341
+ }
562
342
 
563
- const appState = useAppStore.getState()
564
- const currentUser = appState.currentUser
565
- if (currentUser && agentId) {
566
- const sessionMap = await api<Record<string, { id: string; name: string; user: string }>>('GET', '/sessions')
567
- const existingMain = Object.values(sessionMap).find((s) => s.name === '__main__' && s.user === currentUser)
568
- const mainId = existingMain?.id || `main-${currentUser}`
569
- const selectedModel = (payload.model as string) || DEFAULT_AGENTS[provider].model
570
- const selectedEndpoint = supportsEndpoint ? (payload.apiEndpoint as string | undefined) || null : null
571
- const selectedTools = Array.isArray(payload.tools) ? payload.tools as string[] : DEFAULT_AGENTS[provider].tools
572
-
573
- if (!existingMain) {
574
- await api('POST', '/sessions', {
575
- id: mainId,
576
- name: '__main__',
577
- user: currentUser,
343
+ // Create/update __main__ chat for the first provider
344
+ const appState = useAppStore.getState()
345
+ const currentUser = appState.currentUser
346
+ if (currentUser && agentId) {
347
+ const sessionMap = await api<Record<string, { id: string; name: string; user: string }>>('GET', '/sessions')
348
+ const existingMain = Object.values(sessionMap).find((s) => s.name === '__main__' && s.user === currentUser)
349
+ const mainId = existingMain?.id || `main-${currentUser}`
350
+ const selectedModel = (payload.model as string) || DEFAULT_AGENTS[provider].model
351
+ const selectedEndpoint = supportsEndpoint ? (payload.apiEndpoint as string | undefined) || null : null
352
+ const selectedTools = Array.isArray(payload.tools) ? payload.tools as string[] : DEFAULT_AGENTS[provider].tools
353
+
354
+ if (!existingMain) {
355
+ await api('POST', '/sessions', {
356
+ id: mainId,
357
+ name: '__main__',
358
+ user: currentUser,
359
+ agentId,
360
+ provider,
361
+ model: selectedModel,
362
+ credentialId: credentialId || null,
363
+ apiEndpoint: selectedEndpoint,
364
+ tools: selectedTools,
365
+ heartbeatEnabled: true,
366
+ })
367
+ }
368
+
369
+ await api('PUT', `/sessions/${mainId}`, {
578
370
  agentId,
579
371
  provider,
580
372
  model: selectedModel,
@@ -583,25 +375,45 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
583
375
  tools: selectedTools,
584
376
  heartbeatEnabled: true,
585
377
  })
586
- }
587
378
 
588
- await api('PUT', `/sessions/${mainId}`, {
589
- agentId,
590
- provider,
591
- model: selectedModel,
592
- credentialId: credentialId || null,
593
- apiEndpoint: selectedEndpoint,
594
- tools: selectedTools,
595
- heartbeatEnabled: true,
596
- })
379
+ await appState.loadSessions()
380
+ appState.setCurrentSession(mainId)
381
+ }
382
+ } else {
383
+ // Additional providers just create the agent
384
+ agentId = (await api<{ id: string }>('POST', '/agents', payload)).id
385
+ }
597
386
 
598
- await appState.loadSessions()
599
- appState.setCurrentSession(mainId)
387
+ const newConfigured: ConfiguredProvider = {
388
+ provider,
389
+ credentialId: credentialId || null,
390
+ agentId,
391
+ agentName: agentName.trim(),
600
392
  }
601
- await api('PUT', '/settings', { setupCompleted: true })
602
- onComplete()
603
- } catch (err: any) {
604
- setError(err?.message || 'Failed to create starter assistant.')
393
+ const nextConfigured = [...configuredProviders, newConfigured]
394
+ setConfiguredProviders(nextConfigured)
395
+
396
+ if (addAnother) {
397
+ // Reset for next provider selection
398
+ setProvider(null)
399
+ setEndpoint('')
400
+ setApiKey('')
401
+ setCredentialId(null)
402
+ setCheckState('idle')
403
+ setCheckMessage('')
404
+ setAgentName('')
405
+ setAgentDescription('')
406
+ setAgentPrompt('')
407
+ setAgentModel('')
408
+ setError('')
409
+ setStep(0)
410
+ } else {
411
+ // Finish setup
412
+ await api('PUT', '/settings', { setupCompleted: true })
413
+ setStep(3)
414
+ }
415
+ } catch (err: unknown) {
416
+ setError(err instanceof Error ? err.message : String(err))
605
417
  } finally {
606
418
  setSaving(false)
607
419
  }
@@ -629,44 +441,58 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
629
441
  {step === 0 && (
630
442
  <>
631
443
  <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
632
- 2-Minute Setup
444
+ {configuredProviders.length > 0 ? 'Add Another Provider' : '2-Minute Setup'}
633
445
  </h1>
634
446
  <p className="text-[15px] text-text-2 mb-2">
635
- No coding required. Pick a provider, paste a key if needed, and start chatting.
447
+ {configuredProviders.length > 0
448
+ ? 'Pick another provider to set up, or finish below.'
449
+ : 'No coding required. Pick a provider, paste a key if needed, and start chatting.'}
636
450
  </p>
637
451
  <p className="text-[13px] text-text-3 mb-8">
638
452
  You can change providers, models, and agent settings anytime later.
639
453
  </p>
640
454
 
455
+ <ConfiguredChips providers={configuredProviders} />
456
+
641
457
  <div className="flex flex-col gap-3 max-h-[44vh] overflow-y-auto pr-1">
642
- {PROVIDERS.map((p) => (
643
- <button
644
- key={p.id}
645
- onClick={() => selectProvider(p.id)}
646
- className="w-full px-5 py-4 rounded-[14px] border border-white/[0.08] bg-surface text-left
647
- cursor-pointer hover:border-accent-bright/30 hover:bg-surface-hover transition-all duration-200
648
- flex items-start gap-4"
649
- >
650
- <div className="w-10 h-10 rounded-[10px] bg-white/[0.04] border border-white/[0.06] flex items-center justify-center shrink-0 mt-0.5">
651
- <span className="text-[16px] font-display font-700 text-accent-bright">
652
- {p.icon}
653
- </span>
654
- </div>
655
- <div>
656
- <div className="text-[15px] font-display font-600 text-text mb-1">
657
- {p.name}
658
- <ProviderBadge label={p.badge} />
458
+ {SETUP_PROVIDERS.map((p) => {
459
+ const isConfigured = configuredIds.has(p.id)
460
+ return (
461
+ <button
462
+ key={p.id}
463
+ onClick={() => !isConfigured && selectProvider(p.id)}
464
+ disabled={isConfigured}
465
+ className={`w-full px-5 py-4 rounded-[14px] border border-white/[0.08] bg-surface text-left
466
+ transition-all duration-200 flex items-start gap-4
467
+ ${isConfigured
468
+ ? 'opacity-40 cursor-not-allowed'
469
+ : 'cursor-pointer hover:border-accent-bright/30 hover:bg-surface-hover'
470
+ }`}
471
+ >
472
+ <div className="w-10 h-10 rounded-[10px] bg-white/[0.04] border border-white/[0.06] flex items-center justify-center shrink-0 mt-0.5">
473
+ <span className="text-[16px] font-display font-700 text-accent-bright">
474
+ {p.icon}
475
+ </span>
659
476
  </div>
660
- <div className="text-[13px] text-text-3 leading-relaxed">{p.description}</div>
661
- {!p.requiresKey && (
662
- <div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
663
- <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
664
- No API key required
477
+ <div>
478
+ <div className="text-[15px] font-display font-600 text-text mb-1">
479
+ {p.name}
480
+ {isConfigured
481
+ ? <span className="ml-2 text-[10px] text-emerald-400 uppercase tracking-[0.08em]">Configured</span>
482
+ : <ProviderBadge label={p.badge} />
483
+ }
665
484
  </div>
666
- )}
667
- </div>
668
- </button>
669
- ))}
485
+ <div className="text-[13px] text-text-3 leading-relaxed">{p.description}</div>
486
+ {!p.requiresKey && !isConfigured && (
487
+ <div className="mt-1.5 inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-emerald-500/10 text-emerald-400 text-[11px] font-500">
488
+ <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
489
+ No API key required
490
+ </div>
491
+ )}
492
+ </div>
493
+ </button>
494
+ )
495
+ })}
670
496
  </div>
671
497
 
672
498
  <div className="mt-4 text-left">
@@ -702,7 +528,13 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
702
528
  )}
703
529
  </div>
704
530
 
705
- <SkipLink onClick={skip} />
531
+ <SkipLink
532
+ onClick={configuredProviders.length > 0 ? async () => {
533
+ await api('PUT', '/settings', { setupCompleted: true })
534
+ setStep(3)
535
+ } : skip}
536
+ label={configuredProviders.length > 0 ? 'Finish Setup' : 'Skip setup for now'}
537
+ />
706
538
  </>
707
539
  )}
708
540
 
@@ -831,17 +663,23 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
831
663
  </button>
832
664
  </div>
833
665
 
834
- <SkipLink onClick={skip} />
666
+ <SkipLink
667
+ onClick={configuredProviders.length > 0 ? async () => {
668
+ await api('PUT', '/settings', { setupCompleted: true })
669
+ setStep(3)
670
+ } : skip}
671
+ label={configuredProviders.length > 0 ? 'Finish Setup' : 'Skip setup for now'}
672
+ />
835
673
  </>
836
674
  )}
837
675
 
838
676
  {step === 2 && provider && selectedProvider && (
839
677
  <>
840
678
  <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
841
- You&apos;re Ready
679
+ Create Your Agent
842
680
  </h1>
843
681
  <p className="text-[15px] text-text-2 mb-7">
844
- We&apos;ll create a starter assistant so you can begin immediately.
682
+ We&apos;ll create a starter agent so you can begin immediately.
845
683
  </p>
846
684
 
847
685
  <div className="mb-5 p-4 rounded-[14px] border border-white/[0.08] bg-surface text-left">
@@ -921,17 +759,94 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
921
759
  Back
922
760
  </button>
923
761
  <button
924
- onClick={createStarterAgent}
762
+ onClick={() => createAgentAndFinish(true)}
925
763
  disabled={!agentName.trim() || saving}
926
- className="px-10 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-display font-600
764
+ className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-white/[0.03] text-text text-[14px]
765
+ font-display font-600 cursor-pointer hover:bg-white/[0.06] transition-all duration-200 disabled:opacity-40"
766
+ >
767
+ {saving ? 'Creating...' : 'Create & Add Another'}
768
+ </button>
769
+ <button
770
+ onClick={() => createAgentAndFinish(false)}
771
+ disabled={!agentName.trim() || saving}
772
+ className="px-8 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-display font-600
927
773
  cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
928
774
  shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30"
929
775
  >
930
- {saving ? 'Creating...' : 'Create Starter Assistant'}
776
+ {saving ? 'Creating...' : 'Create & Finish'}
931
777
  </button>
932
778
  </div>
933
779
 
934
- <SkipLink onClick={skip} />
780
+ <SkipLink
781
+ onClick={configuredProviders.length > 0 ? async () => {
782
+ await api('PUT', '/settings', { setupCompleted: true })
783
+ setStep(3)
784
+ } : skip}
785
+ label={configuredProviders.length > 0 ? 'Finish Setup' : 'Skip setup for now'}
786
+ />
787
+ </>
788
+ )}
789
+
790
+ {step === 3 && (
791
+ <>
792
+ <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-3">
793
+ You&apos;re All Set
794
+ </h1>
795
+ <p className="text-[15px] text-text-2 mb-7">
796
+ {configuredProviders.length === 1
797
+ ? 'Your agent is ready to chat.'
798
+ : `${configuredProviders.length} agents created and ready to chat.`}
799
+ </p>
800
+
801
+ {configuredProviders.length > 0 && (
802
+ <div className="mb-6 p-4 rounded-[14px] border border-white/[0.08] bg-surface text-left">
803
+ <div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-3">Agents Created</div>
804
+ <div className="space-y-2">
805
+ {configuredProviders.map((cp) => {
806
+ const meta = SETUP_PROVIDERS.find((p) => p.id === cp.provider)
807
+ return (
808
+ <div key={cp.provider} className="flex items-center gap-3">
809
+ <div className="w-8 h-8 rounded-[8px] bg-white/[0.04] border border-white/[0.06] flex items-center justify-center shrink-0">
810
+ <span className="text-[13px] font-display font-700 text-accent-bright">
811
+ {meta?.icon || '?'}
812
+ </span>
813
+ </div>
814
+ <div>
815
+ <div className="text-[14px] text-text font-500">{cp.agentName}</div>
816
+ <div className="text-[12px] text-text-3">{meta?.name || cp.provider}</div>
817
+ </div>
818
+ </div>
819
+ )
820
+ })}
821
+ </div>
822
+ </div>
823
+ )}
824
+
825
+ <div className="mb-8 p-4 rounded-[14px] border border-white/[0.08] bg-surface text-left">
826
+ <div className="text-[12px] uppercase tracking-[0.08em] text-text-3 mb-3">Coming Soon — Connectors</div>
827
+ <p className="text-[13px] text-text-2 mb-3">
828
+ Bridge your agents to chat platforms. Set this up anytime from Connectors.
829
+ </p>
830
+ <div className="flex gap-3">
831
+ {CONNECTOR_ICONS.map((c) => (
832
+ <div key={c.name} className="flex flex-col items-center gap-1.5">
833
+ <div className="w-10 h-10 rounded-[10px] bg-white/[0.04] border border-white/[0.06] flex items-center justify-center">
834
+ <span className="text-[14px] font-display font-600 text-text-3">{c.icon}</span>
835
+ </div>
836
+ <span className="text-[10px] text-text-3">{c.name}</span>
837
+ </div>
838
+ ))}
839
+ </div>
840
+ </div>
841
+
842
+ <button
843
+ onClick={onComplete}
844
+ className="px-10 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-display font-600
845
+ cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200
846
+ shadow-[0_6px_28px_rgba(99,102,241,0.3)]"
847
+ >
848
+ Get Started
849
+ </button>
935
850
  </>
936
851
  )}
937
852
  </div>