@swarmclawai/swarmclaw 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +20 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/models/route.test.ts +60 -0
  49. package/src/app/api/providers/[id]/models/route.ts +33 -1
  50. package/src/app/api/providers/[id]/route.test.ts +49 -0
  51. package/src/app/api/providers/[id]/route.ts +30 -1
  52. package/src/app/api/providers/ollama/route.ts +6 -5
  53. package/src/app/api/schedules/[id]/route.ts +14 -108
  54. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  55. package/src/app/api/schedules/route.ts +9 -51
  56. package/src/app/api/settings/route.ts +4 -3
  57. package/src/app/api/setup/check-provider/route.ts +15 -1
  58. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  59. package/src/app/api/system/status/route.ts +2 -2
  60. package/src/app/api/tasks/[id]/route.ts +16 -202
  61. package/src/app/api/tasks/bulk/route.ts +5 -86
  62. package/src/app/api/tasks/metrics/route.ts +2 -1
  63. package/src/app/api/tasks/route.ts +11 -171
  64. package/src/app/api/upload/route.ts +1 -1
  65. package/src/app/api/uploads/[filename]/route.ts +1 -1
  66. package/src/app/api/uploads/route.ts +1 -1
  67. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  68. package/src/app/layout.tsx +9 -6
  69. package/src/app/protocols/page.tsx +71 -89
  70. package/src/app/tasks/page.tsx +32 -32
  71. package/src/cli/index.js +1 -0
  72. package/src/cli/spec.js +1 -0
  73. package/src/components/agents/agent-sheet.tsx +51 -25
  74. package/src/components/agents/inspector-panel.tsx +15 -4
  75. package/src/components/auth/setup-wizard/index.tsx +27 -18
  76. package/src/components/auth/setup-wizard/shared.tsx +2 -2
  77. package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
  78. package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
  79. package/src/components/auth/setup-wizard/types.ts +6 -4
  80. package/src/components/auth/setup-wizard/utils.test.ts +38 -8
  81. package/src/components/auth/setup-wizard/utils.ts +14 -8
  82. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  83. package/src/components/connectors/connector-list.tsx +26 -40
  84. package/src/components/connectors/connector-sheet.tsx +95 -149
  85. package/src/components/gateways/gateway-sheet.tsx +61 -110
  86. package/src/components/layout/live-query-sync.tsx +121 -0
  87. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  88. package/src/components/providers/app-query-provider.tsx +17 -0
  89. package/src/components/providers/provider-list.tsx +150 -77
  90. package/src/components/providers/provider-sheet.tsx +102 -77
  91. package/src/components/shared/model-combobox.tsx +5 -4
  92. package/src/components/skills/skill-list.tsx +5 -18
  93. package/src/components/skills/skill-sheet.tsx +21 -20
  94. package/src/components/skills/skills-workspace.tsx +48 -87
  95. package/src/components/tasks/task-card.tsx +20 -13
  96. package/src/components/tasks/task-column.tsx +22 -7
  97. package/src/components/tasks/task-list.tsx +8 -11
  98. package/src/components/tasks/task-sheet.tsx +111 -103
  99. package/src/features/agents/queries.ts +20 -0
  100. package/src/features/chatrooms/queries.ts +20 -0
  101. package/src/features/chats/queries.ts +27 -0
  102. package/src/features/connectors/queries.ts +145 -0
  103. package/src/features/credentials/queries.ts +37 -0
  104. package/src/features/extensions/queries.ts +26 -0
  105. package/src/features/external-agents/queries.ts +36 -0
  106. package/src/features/gateways/queries.ts +274 -0
  107. package/src/features/missions/queries.ts +23 -0
  108. package/src/features/projects/queries.ts +20 -0
  109. package/src/features/protocols/queries.ts +149 -0
  110. package/src/features/providers/queries.ts +142 -0
  111. package/src/features/settings/queries.ts +20 -0
  112. package/src/features/skills/queries.ts +182 -0
  113. package/src/features/tasks/queries.ts +189 -0
  114. package/src/hooks/use-ws.ts +3 -2
  115. package/src/lib/agent-provider-options.test.ts +152 -0
  116. package/src/lib/agent-provider-options.ts +84 -0
  117. package/src/lib/app/api-client.ts +2 -2
  118. package/src/lib/providers/index.test.ts +78 -0
  119. package/src/lib/providers/index.ts +13 -10
  120. package/src/lib/query/client.ts +17 -0
  121. package/src/lib/server/agents/agent-runtime-config.ts +6 -6
  122. package/src/lib/server/agents/agent-service.ts +429 -0
  123. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  124. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  125. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  126. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  127. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  128. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  129. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  130. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  131. package/src/lib/server/build-llm.ts +7 -15
  132. package/src/lib/server/capability-router.test.ts +70 -1
  133. package/src/lib/server/capability-router.ts +24 -99
  134. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  135. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  136. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  137. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  138. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  139. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  140. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  141. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  142. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  143. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  144. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  145. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  146. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  147. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  148. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  149. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  150. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  151. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  152. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  153. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  154. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  155. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  156. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  157. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  158. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  159. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  160. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  161. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  162. package/src/lib/server/chats/chat-session-service.ts +410 -0
  163. package/src/lib/server/connectors/access.ts +1 -1
  164. package/src/lib/server/connectors/commands.ts +7 -6
  165. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  166. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  167. package/src/lib/server/connectors/connector-service.ts +453 -0
  168. package/src/lib/server/connectors/delivery.ts +17 -12
  169. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  170. package/src/lib/server/connectors/media.ts +1 -1
  171. package/src/lib/server/connectors/response-media.ts +1 -1
  172. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  173. package/src/lib/server/connectors/session.ts +9 -7
  174. package/src/lib/server/connectors/voice-note.ts +2 -1
  175. package/src/lib/server/context-manager.ts +20 -1
  176. package/src/lib/server/cost.ts +2 -3
  177. package/src/lib/server/credentials/credential-repository.ts +43 -4
  178. package/src/lib/server/credentials/credential-service.ts +112 -0
  179. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  180. package/src/lib/server/daemon/controller.ts +577 -0
  181. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  182. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  183. package/src/lib/server/daemon/types.ts +101 -0
  184. package/src/lib/server/embeddings.ts +3 -9
  185. package/src/lib/server/eval/agent-regression.ts +3 -2
  186. package/src/lib/server/eval/runner.ts +2 -2
  187. package/src/lib/server/execution-brief.test.ts +167 -0
  188. package/src/lib/server/execution-brief.ts +295 -0
  189. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  190. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  191. package/src/lib/server/execution-engine/index.ts +35 -0
  192. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  193. package/src/lib/server/execution-engine/types.ts +33 -0
  194. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  195. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  196. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  197. package/src/lib/server/messages/message-repository.ts +330 -0
  198. package/src/lib/server/missions/mission-service/core.ts +8 -6
  199. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  200. package/src/lib/server/openclaw/doctor.ts +1 -1
  201. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  202. package/src/lib/server/openclaw/gateway.ts +5 -14
  203. package/src/lib/server/openclaw/health.ts +3 -11
  204. package/src/lib/server/openclaw/sync.ts +8 -6
  205. package/src/lib/server/persistence/storage-context.ts +3 -0
  206. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  207. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  208. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  209. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  210. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  211. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  212. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  213. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  214. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  215. package/src/lib/server/protocols/protocol-types.ts +10 -7
  216. package/src/lib/server/provider-endpoint.ts +7 -12
  217. package/src/lib/server/provider-model-discovery.ts +2 -11
  218. package/src/lib/server/query-expansion.ts +5 -6
  219. package/src/lib/server/run-context.test.ts +365 -0
  220. package/src/lib/server/run-context.ts +367 -0
  221. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  222. package/src/lib/server/runtime/queue/core.ts +61 -190
  223. package/src/lib/server/runtime/run-ledger.ts +8 -0
  224. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  225. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  226. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  227. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  228. package/src/lib/server/service-result.ts +16 -0
  229. package/src/lib/server/session-note.ts +2 -3
  230. package/src/lib/server/session-reset-policy.ts +4 -3
  231. package/src/lib/server/session-tools/connector.ts +9 -6
  232. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  233. package/src/lib/server/session-tools/crud.ts +162 -10
  234. package/src/lib/server/session-tools/delegate.ts +1 -1
  235. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  236. package/src/lib/server/session-tools/memory.ts +6 -4
  237. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  238. package/src/lib/server/session-tools/session-info.ts +119 -12
  239. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  240. package/src/lib/server/session-tools/skills.ts +15 -15
  241. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  242. package/src/lib/server/session-tools/subagent.ts +125 -7
  243. package/src/lib/server/session-tools/team-context.ts +4 -3
  244. package/src/lib/server/session-tools/wallet.ts +0 -58
  245. package/src/lib/server/sessions/session-lineage.ts +55 -0
  246. package/src/lib/server/sessions/session-repository.ts +2 -2
  247. package/src/lib/server/skills/learned-skills.ts +24 -23
  248. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  249. package/src/lib/server/skills/skill-repository.ts +136 -13
  250. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  251. package/src/lib/server/storage-normalization.test.ts +42 -215
  252. package/src/lib/server/storage-normalization.ts +98 -0
  253. package/src/lib/server/storage.ts +19 -0
  254. package/src/lib/server/structured-extract.ts +3 -14
  255. package/src/lib/server/tasks/task-followups.ts +16 -11
  256. package/src/lib/server/tasks/task-result.test.ts +25 -29
  257. package/src/lib/server/tasks/task-result.ts +5 -9
  258. package/src/lib/server/tasks/task-route-service.ts +449 -0
  259. package/src/lib/server/text-normalization.ts +41 -0
  260. package/src/lib/server/tool-planning.ts +6 -42
  261. package/src/lib/server/upload-path.ts +5 -0
  262. package/src/lib/server/working-state/extraction.ts +614 -0
  263. package/src/lib/server/working-state/normalization.ts +866 -0
  264. package/src/lib/server/working-state/prompt.ts +60 -0
  265. package/src/lib/server/working-state/repository.ts +38 -0
  266. package/src/lib/server/working-state/service.test.ts +253 -0
  267. package/src/lib/server/working-state/service.ts +293 -0
  268. package/src/lib/validation/schemas.ts +1 -0
  269. package/src/lib/ws-client.ts +3 -3
  270. package/src/stores/slices/task-slice.ts +1 -4
  271. package/src/stores/use-chatroom-store.ts +2 -2
  272. package/src/types/index.ts +288 -22
  273. package/src/views/settings/section-providers.tsx +2 -2
@@ -28,6 +28,7 @@ import { resolveStoredOllamaMode } from '@/lib/ollama-mode'
28
28
  import { errorMessage } from '@/lib/shared-utils'
29
29
  import { getDefaultAgentToolIds } from '@/lib/agent-default-tools'
30
30
  import { getEnabledExtensionIds, getEnabledToolIds } from '@/lib/capability-selection'
31
+ import { buildAgentSelectableProviders, resolveAgentSelectableProviderCredentials } from '@/lib/agent-provider-options'
31
32
 
32
33
  const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
33
34
  const FALLBACK_ELEVENLABS_VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb'
@@ -46,6 +47,7 @@ const AUTO_SYNC_MODEL_PROVIDER_IDS = new Set<ProviderType>([
46
47
  'ollama',
47
48
  ])
48
49
  const CONNECTION_TEST_TIMEOUT_MS = 40_000
50
+ type AgentProviderId = string
49
51
 
50
52
  type SafeAgentWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & {
51
53
  balanceAtomic?: string
@@ -171,6 +173,8 @@ export function AgentSheet() {
171
173
  const loadProjects = useAppStore((s) => s.loadProjects)
172
174
  const providers = useAppStore((s) => s.providers)
173
175
  const loadProviders = useAppStore((s) => s.loadProviders)
176
+ const providerConfigs = useAppStore((s) => s.providerConfigs)
177
+ const loadProviderConfigs = useAppStore((s) => s.loadProviderConfigs)
174
178
  const gatewayProfiles = useAppStore((s) => s.gatewayProfiles)
175
179
  const loadGatewayProfiles = useAppStore((s) => s.loadGatewayProfiles)
176
180
  const credentials = useAppStore((s) => s.credentials)
@@ -197,7 +201,7 @@ export function AgentSheet() {
197
201
  const [soulInitial, setSoulInitial] = useState('')
198
202
  const [soulSaveState, setSoulSaveState] = useState<'idle' | 'saved'>('idle')
199
203
  const [systemPrompt, setSystemPrompt] = useState('')
200
- const [provider, setProvider] = useState<ProviderType>('claude-cli')
204
+ const [provider, setProvider] = useState<AgentProviderId>('claude-cli')
201
205
  const [model, setModel] = useState('')
202
206
  const [credentialId, setCredentialId] = useState<string | null>(null)
203
207
  const [apiEndpoint, setApiEndpoint] = useState<string | null>(null)
@@ -308,8 +312,15 @@ export function AgentSheet() {
308
312
  }
309
313
  }, [])
310
314
 
311
- const currentProvider = providers.find((p) => p.id === provider)
312
- const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
315
+ const agentSelectableProviders = useMemo(
316
+ () => buildAgentSelectableProviders(providers, providerConfigs),
317
+ [providers, providerConfigs],
318
+ )
319
+ const currentProvider = agentSelectableProviders.find((p) => p.id === provider)
320
+ const providerCredentials = useMemo(
321
+ () => resolveAgentSelectableProviderCredentials(provider, credentials, providerConfigs),
322
+ [credentials, provider, providerConfigs],
323
+ )
313
324
  const openclawCredentials = Object.values(credentials).filter((c) => c.provider === 'openclaw')
314
325
  const openclawGatewayProfiles = gatewayProfiles.filter((item) => item.provider === 'openclaw')
315
326
  const setAgentPrefill = useAppStore((s) => s.setAgentPrefill)
@@ -327,15 +338,15 @@ export function AgentSheet() {
327
338
  ? 'Global default'
328
339
  : 'Built-in fallback'
329
340
  const syncLiveProviderModels = useCallback(async (
330
- providerId: ProviderType,
341
+ providerId: string,
331
342
  nextCredentialId: string | null,
332
343
  nextEndpoint: string | null,
333
344
  nextOllamaMode: 'local' | 'cloud',
334
345
  force = false,
335
346
  ): Promise<{ synced: boolean; models: string[] } | null> => {
336
347
  if (openclawEnabled) return null
337
- if (!AUTO_SYNC_MODEL_PROVIDER_IDS.has(providerId)) return null
338
- const providerInfo = providers.find((item) => item.id === providerId)
348
+ if (!AUTO_SYNC_MODEL_PROVIDER_IDS.has(providerId as ProviderType)) return null
349
+ const providerInfo = agentSelectableProviders.find((item) => item.id === providerId)
339
350
  if (!providerInfo?.supportsModelDiscovery) return null
340
351
 
341
352
  const result = await fetchProviderModelDiscovery({
@@ -358,7 +369,7 @@ export function AgentSheet() {
358
369
 
359
370
  setModel((currentModel) => currentModel.trim() || result.models[0] || '')
360
371
  return { synced: !sameModels, models: result.models }
361
- }, [loadProviders, openclawEnabled, providers])
372
+ }, [agentSelectableProviders, loadProviders, openclawEnabled])
362
373
 
363
374
  const providerNeedsKey = !editing && (
364
375
  (currentProvider?.requiresApiKey && providerCredentials.length === 0 && !addingKey) ||
@@ -371,7 +382,7 @@ export function AgentSheet() {
371
382
  return
372
383
  }
373
384
  if (openclawEnabled) return
374
- if (!AUTO_SYNC_MODEL_PROVIDER_IDS.has(provider)) return
385
+ if (!AUTO_SYNC_MODEL_PROVIDER_IDS.has(provider as ProviderType)) return
375
386
  if (!currentProvider?.supportsModelDiscovery) return
376
387
 
377
388
  const requiresCredential = currentProvider.requiresApiKey || (provider === 'ollama' && ollamaMode === 'cloud')
@@ -388,6 +399,7 @@ export function AgentSheet() {
388
399
  if (open) {
389
400
  loadSettings()
390
401
  loadProviders()
402
+ loadProviderConfigs()
391
403
  loadGatewayProfiles()
392
404
  loadCredentials()
393
405
  loadSkills()
@@ -435,7 +447,7 @@ export function AgentSheet() {
435
447
  }))
436
448
  setOpenclawEnabled(editing.provider === 'openclaw')
437
449
  setProjectId(editing.projectId)
438
- setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
450
+ setAvatarSeed(editing.avatarSeed || Math.random().toString(36).slice(2, 10))
439
451
  setAvatarUrl(editing.avatarUrl || null)
440
452
  setThinkingLevel(editing.thinkingLevel || '')
441
453
  setMemoryScopeMode(editing.memoryScopeMode || 'auto')
@@ -515,7 +527,7 @@ export function AgentSheet() {
515
527
  }))
516
528
  setOpenclawEnabled(src.provider === 'openclaw')
517
529
  setProjectId(src.projectId)
518
- setAvatarSeed(crypto.randomUUID().slice(0, 8))
530
+ setAvatarSeed(Math.random().toString(36).slice(2, 10))
519
531
  setAvatarUrl(null)
520
532
  setThinkingLevel(src.thinkingLevel || '')
521
533
  setMemoryScopeMode(src.memoryScopeMode || 'auto')
@@ -637,7 +649,7 @@ export function AgentSheet() {
637
649
  setModel(currentProvider.models[0])
638
650
  }
639
651
  // eslint-disable-next-line react-hooks/exhaustive-deps
640
- }, [provider, providers])
652
+ }, [provider, agentSelectableProviders])
641
653
 
642
654
  // Reset test status when connection params change
643
655
  useEffect(() => {
@@ -713,7 +725,7 @@ export function AgentSheet() {
713
725
 
714
726
  const addRoutingTargetFromCurrent = () => {
715
727
  const nextTarget: AgentRoutingTarget = {
716
- id: crypto.randomUUID(),
728
+ id: Math.random().toString(16).slice(2, 10),
717
729
  label: routingTargets.length === 0 ? 'Primary route' : `Route ${routingTargets.length + 1}`,
718
730
  role: routingTargets.length === 0 ? 'primary' : 'backup',
719
731
  provider,
@@ -873,13 +885,18 @@ export function AgentSheet() {
873
885
 
874
886
  const handleExport = () => {
875
887
  if (!editing) return
888
+ const recommendedProviders = agentSelectableProviders.some((providerOption) => (
889
+ providerOption.id === editing.provider && providerOption.type === 'builtin'
890
+ ))
891
+ ? [editing.provider as ProviderType]
892
+ : undefined
876
893
  const pack: AgentPackManifest = {
877
894
  schemaVersion: 1,
878
895
  kind: 'swarmclaw-agent-pack',
879
896
  name: `${editing.name} Pack`,
880
897
  description: editing.description || undefined,
881
898
  exportedAt: Date.now(),
882
- recommendedProviders: [editing.provider],
899
+ recommendedProviders,
883
900
  agents: [{
884
901
  id: editing.name.replace(/\s+/g, '-').toLowerCase(),
885
902
  name: editing.name,
@@ -1148,7 +1165,7 @@ export function AgentSheet() {
1148
1165
  type="button"
1149
1166
  onClick={() => {
1150
1167
  setAvatarUrl(null)
1151
- if (!avatarSeed) setAvatarSeed(crypto.randomUUID().slice(0, 8))
1168
+ if (!avatarSeed) setAvatarSeed(Math.random().toString(36).slice(2, 10))
1152
1169
  }}
1153
1170
  className="text-[11px] text-text-3 hover:text-red-400 transition-colors self-start cursor-pointer"
1154
1171
  >
@@ -1169,7 +1186,7 @@ export function AgentSheet() {
1169
1186
  />
1170
1187
  <button
1171
1188
  type="button"
1172
- onClick={() => { setAvatarSeed(crypto.randomUUID().slice(0, 8)); setAvatarUrl(null) }}
1189
+ onClick={() => { setAvatarSeed(Math.random().toString(36).slice(2, 10)); setAvatarUrl(null) }}
1173
1190
  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"
1174
1191
  style={{ fontFamily: 'inherit' }}
1175
1192
  title="Shuffle avatar"
@@ -1213,7 +1230,7 @@ export function AgentSheet() {
1213
1230
  if (!apiEndpoint) setApiEndpoint('http://localhost:18789')
1214
1231
  } else {
1215
1232
  setOpenclawEnabled(false)
1216
- const first = providers[0]?.id || 'claude-cli'
1233
+ const first = agentSelectableProviders[0]?.id || 'claude-cli'
1217
1234
  setProvider(first)
1218
1235
  setModel('')
1219
1236
  setApiEndpoint(null)
@@ -1445,13 +1462,17 @@ export function AgentSheet() {
1445
1462
  {!openclawEnabled && <div className="mb-8">
1446
1463
  <SectionLabel>Provider</SectionLabel>
1447
1464
  <div className="grid grid-cols-3 gap-3">
1448
- {providers.map((p) => {
1449
- const isConnected = !p.requiresApiKey || Object.values(credentials).some((c) => c.provider === p.id)
1465
+ {agentSelectableProviders.map((p) => {
1466
+ const nextCredentials = resolveAgentSelectableProviderCredentials(p.id, credentials, providerConfigs)
1467
+ const isConnected = !p.requiresApiKey || nextCredentials.length > 0
1450
1468
  return (
1451
1469
  <button
1452
1470
  key={p.id}
1453
1471
  onClick={() => {
1454
1472
  setProvider(p.id)
1473
+ if (!nextCredentials.some((item) => item.id === credentialId)) {
1474
+ setCredentialId(nextCredentials[0]?.id || null)
1475
+ }
1455
1476
  setGatewayProfileId(null)
1456
1477
  }}
1457
1478
  className={`relative py-3.5 px-4 rounded-[14px] text-center cursor-pointer transition-all duration-200
@@ -2161,7 +2182,7 @@ export function AgentSheet() {
2161
2182
  </div>
2162
2183
  <div className="space-y-3">
2163
2184
  {routingTargets.map((target, index) => {
2164
- const targetCredentials = Object.values(credentials).filter((item) => item.provider === target.provider)
2185
+ const targetCredentials = resolveAgentSelectableProviderCredentials(target.provider, credentials, providerConfigs)
2165
2186
  return (
2166
2187
  <div key={target.id} className="p-4 rounded-[12px] border border-white/[0.08] bg-white/[0.02] space-y-3">
2167
2188
  <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
@@ -2182,19 +2203,24 @@ export function AgentSheet() {
2182
2203
  <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
2183
2204
  <select
2184
2205
  value={target.provider}
2185
- onChange={(e) => updateRoutingTarget(target.id, {
2186
- provider: e.target.value as ProviderType,
2187
- gatewayProfileId: e.target.value === 'openclaw' ? target.gatewayProfileId : null,
2188
- ollamaMode: e.target.value === 'ollama'
2206
+ onChange={(e) => {
2207
+ const nextProviderId = e.target.value
2208
+ const nextCredentials = resolveAgentSelectableProviderCredentials(nextProviderId, credentials, providerConfigs)
2209
+ updateRoutingTarget(target.id, {
2210
+ provider: nextProviderId,
2211
+ credentialId: nextCredentials[0]?.id || null,
2212
+ gatewayProfileId: nextProviderId === 'openclaw' ? target.gatewayProfileId : null,
2213
+ ollamaMode: nextProviderId === 'ollama'
2189
2214
  ? resolveStoredOllamaMode({
2190
2215
  ollamaMode: target.ollamaMode ?? null,
2191
2216
  apiEndpoint: target.apiEndpoint ?? null,
2192
2217
  })
2193
2218
  : null,
2194
- })}
2219
+ })
2220
+ }}
2195
2221
  className={inputClass}
2196
2222
  >
2197
- {providers.map((item) => (
2223
+ {agentSelectableProviders.map((item) => (
2198
2224
  <option key={item.id} value={item.id}>{item.name}</option>
2199
2225
  ))}
2200
2226
  </select>
@@ -26,6 +26,7 @@ import { ModelCombobox } from '@/components/shared/model-combobox'
26
26
  import { buildOpenClawMainSessionKey } from '@/lib/openclaw/openclaw-agent-id'
27
27
  import { StructuredSessionLauncher } from '@/components/protocols/structured-session-launcher'
28
28
  import { useWs } from '@/hooks/use-ws'
29
+ import { buildAgentSelectableProviders } from '@/lib/agent-provider-options'
29
30
 
30
31
  interface Props {
31
32
  agent: Agent
@@ -61,17 +62,27 @@ const PROVIDER_LABELS: Record<string, string> = {
61
62
  function ModelSwitcherInline({ session, agent }: { session: Session; agent: Agent }) {
62
63
  const providers = useAppStore((s) => s.providers)
63
64
  const loadProviders = useAppStore((s) => s.loadProviders)
65
+ const providerConfigs = useAppStore((s) => s.providerConfigs)
66
+ const loadProviderConfigs = useAppStore((s) => s.loadProviderConfigs)
64
67
  const refreshSession = useAppStore((s) => s.refreshSession)
65
68
  const streaming = useChatStore((s) => s.streaming)
66
69
  const [expanded, setExpanded] = useState(false)
67
70
  const [selectedProvider, setSelectedProvider] = useState(agent.provider)
68
71
  const [saving, setSaving] = useState(false)
69
72
 
70
- useEffect(() => { if (!providers.length) void loadProviders() }, [providers.length, loadProviders])
73
+ useEffect(() => {
74
+ void loadProviders()
75
+ void loadProviderConfigs()
76
+ }, [loadProviderConfigs, loadProviders])
71
77
  useEffect(() => { setSelectedProvider(agent.provider) }, [agent.provider])
72
78
 
73
- const currentProviderInfo = providers.find((p) => p.id === selectedProvider)
74
- const providerLabel = PROVIDER_LABELS[agent.provider] || agent.provider.replace(/-/g, ' ')
79
+ const agentSelectableProviders = useMemo(
80
+ () => buildAgentSelectableProviders(providers, providerConfigs),
81
+ [providerConfigs, providers],
82
+ )
83
+ const currentProviderInfo = agentSelectableProviders.find((p) => p.id === selectedProvider)
84
+ const activeAgentProvider = agentSelectableProviders.find((p) => p.id === agent.provider)
85
+ const providerLabel = PROVIDER_LABELS[agent.provider] || activeAgentProvider?.name || agent.provider.replace(/-/g, ' ')
75
86
 
76
87
  const handleModelChange = async (model: string) => {
77
88
  if (saving) return
@@ -122,7 +133,7 @@ function ModelSwitcherInline({ session, agent }: { session: Session; agent: Agen
122
133
  </button>
123
134
  </div>
124
135
  <div className="flex flex-wrap gap-1 mb-2">
125
- {providers.filter((p) => p.models.length > 0).map((p) => (
136
+ {agentSelectableProviders.filter((p) => p.models.length > 0).map((p) => (
126
137
  <button
127
138
  key={p.id}
128
139
  type="button"
@@ -4,7 +4,7 @@ import { useMemo, useState } from 'react'
4
4
  import { api } from '@/lib/app/api-client'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
6
  import { dedup, errorMessage } from '@/lib/shared-utils'
7
- import type { ProviderType, GatewayProfile } from '@/types'
7
+ import type { ProviderId, GatewayProfile } from '@/types'
8
8
  import {
9
9
  SETUP_PROVIDERS,
10
10
  SWARMCLAW_ASSISTANT_PROMPT,
@@ -21,7 +21,7 @@ import type {
21
21
  ProviderCheckResponse,
22
22
  } from './types'
23
23
  import { STEP_ORDER } from './types'
24
- import { stepIndex } from './utils'
24
+ import { requiresSetupProviderVerification, stepIndex } from './utils'
25
25
  import { SparkleIcon } from './shared'
26
26
  import { StepProgress } from './step-progress'
27
27
  import { StepProviders } from './step-providers'
@@ -53,7 +53,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
53
53
  [editingProviderId, configuredProviders],
54
54
  )
55
55
  const totalSteps = STEP_ORDER.length
56
- const configuredProviderIds = new Set(configuredProviders.map((cp) => cp.provider))
56
+ const configuredProviderIds = new Set(configuredProviders.map((cp) => cp.setupProvider))
57
57
  const canContinueFromProviders = configuredProviders.length > 0
58
58
 
59
59
  const skip = async () => {
@@ -76,7 +76,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
76
76
 
77
77
  const selectProvider = (nextProvider: SetupProvider) => {
78
78
  const meta = SETUP_PROVIDERS.find((candidate) => candidate.id === nextProvider)
79
- const existing = configuredProviders.find((cp) => cp.provider === nextProvider)
79
+ const existing = configuredProviders.find((cp) => cp.setupProvider === nextProvider)
80
80
  setActiveProvider(nextProvider)
81
81
  setEditingProviderId(existing?.id || null)
82
82
  setActiveProviderLabel(existing?.name || meta?.name || '')
@@ -94,6 +94,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
94
94
  return {
95
95
  ...draft,
96
96
  providerConfigId: fallback?.id || null,
97
+ setupProvider: fallback?.setupProvider || null,
97
98
  provider: fallback?.provider || null,
98
99
  credentialId: fallback?.credentialId || null,
99
100
  apiEndpoint: fallback?.endpoint || null,
@@ -118,13 +119,14 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
118
119
  if (!editingProviderId && draftAgents.length === 0) {
119
120
  const cp = configured
120
121
  setDraftAgents([{
121
- id: `auto:${crypto.randomUUID().slice(0, 8)}`,
122
+ id: `auto:${Math.random().toString(36).slice(2, 10)}`,
122
123
  templateId: 'auto',
123
124
  name: 'Assistant',
124
125
  description: 'A helpful assistant.',
125
126
  systemPrompt: SWARMCLAW_ASSISTANT_PROMPT,
126
127
  soul: '',
127
128
  providerConfigId: cp.id,
129
+ setupProvider: cp.setupProvider,
128
130
  provider: cp.provider,
129
131
  model: cp.defaultModel,
130
132
  credentialId: cp.credentialId,
@@ -138,7 +140,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
138
140
  autoDraftSkillSuggestions: true,
139
141
  orchestratorEnabled: false,
140
142
  orchestratorMission: '',
141
- avatarSeed: crypto.randomUUID().slice(0, 8),
143
+ avatarSeed: Math.random().toString(36).slice(2, 10),
142
144
  avatarUrl: null,
143
145
  enabled: true,
144
146
  }])
@@ -149,6 +151,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
149
151
  if (draft.providerConfigId !== editingProviderId) return draft
150
152
  return {
151
153
  ...draft,
154
+ setupProvider: configured.setupProvider,
152
155
  provider: configured.provider,
153
156
  credentialId: configured.credentialId,
154
157
  apiEndpoint: configured.endpoint,
@@ -179,13 +182,14 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
179
182
  const addBlankAgent = () => {
180
183
  const defaultProvider = configuredProviders[0] || null
181
184
  const newAgent: StarterDraftAgent = {
182
- id: `custom:${crypto.randomUUID().slice(0, 8)}`,
185
+ id: `custom:${Math.random().toString(36).slice(2, 10)}`,
183
186
  templateId: 'custom',
184
187
  name: `Agent ${draftAgents.length + 1}`,
185
188
  description: '',
186
189
  systemPrompt: '',
187
190
  soul: '',
188
191
  providerConfigId: defaultProvider?.id || null,
192
+ setupProvider: defaultProvider?.setupProvider || null,
189
193
  provider: defaultProvider?.provider || null,
190
194
  model: defaultProvider?.defaultModel || '',
191
195
  credentialId: defaultProvider?.credentialId || null,
@@ -199,7 +203,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
199
203
  autoDraftSkillSuggestions: true,
200
204
  orchestratorEnabled: false,
201
205
  orchestratorMission: '',
202
- avatarSeed: crypto.randomUUID().slice(0, 8),
206
+ avatarSeed: Math.random().toString(36).slice(2, 10),
203
207
  avatarUrl: null,
204
208
  enabled: true,
205
209
  }
@@ -224,13 +228,14 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
224
228
 
225
229
  setDraftAgents((current) => current.map((draft) => {
226
230
  if (draft.id !== id) return draft
227
- const previousDefault = draft.provider ? getDefaultModelForProvider(draft.provider) : ''
231
+ const previousDefault = draft.setupProvider ? getDefaultModelForProvider(draft.setupProvider) : ''
228
232
  const nextModel = !draft.model || draft.model === previousDefault
229
233
  ? configuredProvider.defaultModel
230
234
  : draft.model
231
235
  return {
232
236
  ...draft,
233
237
  providerConfigId: configuredProvider.id,
238
+ setupProvider: configuredProvider.setupProvider,
234
239
  provider: configuredProvider.provider,
235
240
  credentialId: configuredProvider.credentialId,
236
241
  apiEndpoint: configuredProvider.endpoint,
@@ -246,6 +251,10 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
246
251
  setError('Every enabled agent needs a provider assignment before you continue.')
247
252
  return
248
253
  }
254
+ if (enabledDrafts.some((draft) => draft.setupProvider === 'custom' && !draft.model.trim())) {
255
+ setError('Every custom-provider agent needs a model before you continue.')
256
+ return
257
+ }
249
258
 
250
259
  setSaving(true)
251
260
  setError('')
@@ -254,12 +263,12 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
254
263
  const checkedCombos = new Map<string, ProviderCheckResponse>()
255
264
  for (const draft of enabledDrafts) {
256
265
  const cp = configuredProviders.find((c) => c.id === draft.providerConfigId)
257
- if (!cp || cp.provider === 'openclaw') continue
258
- const comboKey = `${cp.provider}|${draft.apiEndpoint || cp.endpoint || ''}|${draft.model}`
266
+ if (!cp || !requiresSetupProviderVerification(cp.setupProvider)) continue
267
+ const comboKey = `${cp.setupProvider}|${draft.apiEndpoint || cp.endpoint || ''}|${draft.model}`
259
268
  if (checkedCombos.has(comboKey)) continue
260
269
  try {
261
270
  const result = await api<ProviderCheckResponse>('POST', '/setup/check-provider', {
262
- provider: cp.provider,
271
+ provider: cp.setupProvider,
263
272
  credentialId: cp.credentialId || undefined,
264
273
  endpoint: draft.apiEndpoint || cp.endpoint || undefined,
265
274
  model: draft.model || undefined,
@@ -276,7 +285,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
276
285
  }
277
286
 
278
287
  const gatewayProfileIdsByProviderConfig = new Map<string, string>()
279
- const openClawProviders = configuredProviders.filter((candidate) => candidate.provider === 'openclaw')
288
+ const openClawProviders = configuredProviders.filter((candidate) => candidate.setupProvider === 'openclaw')
280
289
  if (openClawProviders.length > 0) {
281
290
  const existingGateways = await api<GatewayProfile[]>('GET', '/gateways')
282
291
  let shouldCreateDefault = existingGateways.length === 0
@@ -322,8 +331,8 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
322
331
  description: draft.description.trim(),
323
332
  systemPrompt: draft.systemPrompt.trim(),
324
333
  soul: draft.soul.trim() || undefined,
325
- provider: draft.provider as ProviderType,
326
- model: draft.model.trim() || getDefaultModelForProvider(draft.provider as SetupProvider),
334
+ provider: draft.provider,
335
+ model: draft.model.trim() || (draft.setupProvider ? getDefaultModelForProvider(draft.setupProvider) : ''),
327
336
  credentialId: draft.credentialId || null,
328
337
  tools: draft.tools,
329
338
  capabilities: draft.capabilities,
@@ -355,7 +364,7 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
355
364
  }
356
365
 
357
366
  // Push soul and identity files to the OpenClaw gateway (non-fatal)
358
- if (draft.provider === 'openclaw') {
367
+ if (draft.setupProvider === 'openclaw') {
359
368
  try {
360
369
  if (draft.soul.trim()) {
361
370
  await api('PUT', '/openclaw/agent-files', { agentId, filename: 'SOUL.md', content: draft.soul.trim() })
@@ -373,8 +382,8 @@ export function SetupWizard({ onComplete }: SetupWizardProps) {
373
382
  created.push({
374
383
  id: agentId,
375
384
  name: draft.name.trim(),
376
- provider: draft.provider as SetupProvider,
377
- providerName: configuredProviders.find((candidate) => candidate.id === draft.providerConfigId)?.name || draft.provider as SetupProvider,
385
+ provider: draft.provider as ProviderId,
386
+ providerName: configuredProviders.find((candidate) => candidate.id === draft.providerConfigId)?.name || String(draft.provider || ''),
378
387
  })
379
388
  }
380
389
 
@@ -69,10 +69,10 @@ export function ConfiguredProviderChips({
69
69
  {formatEndpointHost(cp.endpoint)
70
70
  ? `· ${formatEndpointHost(cp.endpoint)}`
71
71
  : ''}
72
- {cp.provider === 'openclaw' && cp.deployment?.useCase
72
+ {cp.setupProvider === 'openclaw' && cp.deployment?.useCase
73
73
  ? ` · ${OPENCLAW_USE_CASE_LABELS[cp.deployment.useCase]}`
74
74
  : ''}
75
- {cp.provider === 'openclaw' && cp.deployment?.exposure
75
+ {cp.setupProvider === 'openclaw' && cp.deployment?.exposure
76
76
  ? ` · ${OPENCLAW_EXPOSURE_LABELS[cp.deployment.exposure]}`
77
77
  : ''}
78
78
  {cp.defaultModel ? ` · ${cp.defaultModel}` : ''}
@@ -36,9 +36,10 @@ function ModelCombobox({
36
36
  const fetched = useRef<string | null>(null)
37
37
 
38
38
  const effectiveEndpoint = endpointOverride || provider?.endpoint || null
39
+ const supportsModelDiscovery = Boolean(provider && provider.setupProvider !== 'custom')
39
40
 
40
41
  const fetchModels = useCallback(async (force?: boolean) => {
41
- if (!provider) return
42
+ if (!provider || !supportsModelDiscovery) return
42
43
  const cacheKey = `${provider.provider}|${effectiveEndpoint || ''}|${provider.credentialId || ''}`
43
44
  if (!force && fetched.current === cacheKey) return
44
45
  fetched.current = cacheKey
@@ -63,11 +64,21 @@ function ModelCombobox({
63
64
  } finally {
64
65
  setLoading(false)
65
66
  }
66
- }, [provider, effectiveEndpoint])
67
+ }, [provider, effectiveEndpoint, supportsModelDiscovery])
67
68
 
68
69
  useEffect(() => {
70
+ if (!provider) {
71
+ setModels([])
72
+ setFetchError('')
73
+ return
74
+ }
75
+ if (!supportsModelDiscovery) {
76
+ setModels(provider.defaultModel ? [provider.defaultModel] : [])
77
+ setFetchError('')
78
+ return
79
+ }
69
80
  fetchModels()
70
- }, [fetchModels])
81
+ }, [fetchModels, provider, supportsModelDiscovery])
71
82
 
72
83
  // Close dropdown on outside click
73
84
  useEffect(() => {
@@ -116,38 +127,40 @@ function ModelCombobox({
116
127
  </svg>
117
128
  </a>
118
129
  )}
119
- <button
120
- type="button"
121
- onClick={() => {
122
- if (models.length > 0 && !loading) {
123
- setOpen(!open)
124
- if (!open) setSearch(value)
125
- } else {
126
- fetchModels(true)
127
- }
128
- }}
129
- disabled={loading}
130
- className="text-text-3 hover:text-accent-bright transition-colors bg-transparent border-none cursor-pointer disabled:opacity-40"
131
- title={models.length > 0 ? 'Show models' : 'Fetch available models'}
132
- >
133
- {loading ? (
134
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="animate-spin">
135
- <circle cx="7" cy="7" r="5.5" stroke="currentColor" strokeWidth="1.5" opacity="0.25" />
136
- <path d="M12.5 7A5.5 5.5 0 0 0 7 1.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
137
- </svg>
138
- ) : models.length > 0 ? (
139
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
140
- <path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
141
- </svg>
142
- ) : (
143
- <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
144
- <path d="M1.5 7A5.5 5.5 0 1 1 7 12.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
145
- <path d="M1.5 12.5V9.5H4.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
146
- </svg>
147
- )}
148
- </button>
130
+ {supportsModelDiscovery && (
131
+ <button
132
+ type="button"
133
+ onClick={() => {
134
+ if (models.length > 0 && !loading) {
135
+ setOpen(!open)
136
+ if (!open) setSearch(value)
137
+ } else {
138
+ fetchModels(true)
139
+ }
140
+ }}
141
+ disabled={loading}
142
+ className="text-text-3 hover:text-accent-bright transition-colors bg-transparent border-none cursor-pointer disabled:opacity-40"
143
+ title={models.length > 0 ? 'Show models' : 'Fetch available models'}
144
+ >
145
+ {loading ? (
146
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="animate-spin">
147
+ <circle cx="7" cy="7" r="5.5" stroke="currentColor" strokeWidth="1.5" opacity="0.25" />
148
+ <path d="M12.5 7A5.5 5.5 0 0 0 7 1.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
149
+ </svg>
150
+ ) : models.length > 0 ? (
151
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
152
+ <path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
153
+ </svg>
154
+ ) : (
155
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
156
+ <path d="M1.5 7A5.5 5.5 0 1 1 7 12.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" />
157
+ <path d="M1.5 12.5V9.5H4.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
158
+ </svg>
159
+ )}
160
+ </button>
161
+ )}
149
162
  </div>
150
- {fetchError && models.length === 0 && (
163
+ {fetchError && models.length === 0 && supportsModelDiscovery && (
151
164
  <div className="mt-1 text-[11px] text-amber-300/80">{fetchError}</div>
152
165
  )}
153
166
  {open && filtered.length > 0 && (
@@ -348,7 +361,7 @@ export function StepAgents({
348
361
  />
349
362
  <button
350
363
  type="button"
351
- onClick={() => onUpdateDraft(draft.id, { avatarSeed: crypto.randomUUID().slice(0, 8), avatarUrl: null })}
364
+ onClick={() => onUpdateDraft(draft.id, { avatarSeed: Math.random().toString(36).slice(2, 10), avatarUrl: null })}
352
365
  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"
353
366
  title="Shuffle avatar"
354
367
  >
@@ -423,7 +436,7 @@ export function StepAgents({
423
436
  focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]"
424
437
  />
425
438
  </div>
426
- {matchedProvider?.provider === 'openclaw' ? (
439
+ {matchedProvider?.setupProvider === 'openclaw' ? (
427
440
  <div className="md:col-span-2">
428
441
  <label className="block text-[12px] text-text-3 font-500 mb-1.5 ml-1">Model</label>
429
442
  <div className="flex items-center gap-3 px-4 py-3 rounded-[12px] border border-white/[0.08] bg-bg">
@@ -448,7 +461,7 @@ export function StepAgents({
448
461
  provider={matchedProvider}
449
462
  endpointOverride={draft.apiEndpoint}
450
463
  onChange={(model) => onUpdateDraft(draft.id, { model })}
451
- modelLibraryUrl={matchedProvider ? SETUP_PROVIDERS.find((sp) => sp.id === matchedProvider.provider)?.modelLibraryUrl : null}
464
+ modelLibraryUrl={matchedProvider ? SETUP_PROVIDERS.find((sp) => sp.id === matchedProvider.setupProvider)?.modelLibraryUrl : null}
452
465
  />
453
466
  </div>
454
467
  )}
@@ -457,7 +470,7 @@ export function StepAgents({
457
470
  value={draft.soul}
458
471
  onChange={(soul) => onUpdateDraft(draft.id, { soul })}
459
472
  />
460
- {matchedProvider?.provider === 'openclaw' && (
473
+ {matchedProvider?.setupProvider === 'openclaw' && (
461
474
  <p className="mt-1.5 ml-1 text-[11px] text-text-3/70">
462
475
  Synced to the gateway as SOUL.md on save.
463
476
  </p>