@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
@@ -1,26 +1,42 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState } from 'react'
3
+ import { useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
- import { createProviderConfig, updateProviderConfig, deleteProviderConfig } from '@/lib/provider-config'
6
- import { api } from '@/lib/app/api-client'
7
- import { fetchProviderModelDiscovery } from '@/lib/provider-model-discovery-client'
8
5
  import { BottomSheet } from '@/components/shared/bottom-sheet'
9
6
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
10
7
  import { toast } from 'sonner'
11
8
  import { errorMessage } from '@/lib/shared-utils'
9
+ import {
10
+ useCheckProviderConnectionMutation,
11
+ useDeleteProviderMutation,
12
+ useProviderConfigsQuery,
13
+ useProviderModelDiscoveryMutation,
14
+ useProvidersQuery,
15
+ useResetProviderModelsMutation,
16
+ useSaveBuiltinProviderMutation,
17
+ useSaveCustomProviderMutation,
18
+ } from '@/features/providers/queries'
19
+ import { useCreateCredentialMutation, useCredentialsQuery } from '@/features/credentials/queries'
12
20
 
13
21
  export function ProviderSheet() {
14
22
  const open = useAppStore((s) => s.providerSheetOpen)
15
23
  const setOpen = useAppStore((s) => s.setProviderSheetOpen)
16
24
  const editingId = useAppStore((s) => s.editingProviderId)
17
25
  const setEditingId = useAppStore((s) => s.setEditingProviderId)
18
- const providerConfigs = useAppStore((s) => s.providerConfigs)
19
- const loadProviderConfigs = useAppStore((s) => s.loadProviderConfigs)
20
- const providers = useAppStore((s) => s.providers)
21
- const loadProviders = useAppStore((s) => s.loadProviders)
22
- const credentials = useAppStore((s) => s.credentials)
23
- const loadCredentials = useAppStore((s) => s.loadCredentials)
26
+ const providerConfigsQuery = useProviderConfigsQuery({ enabled: open })
27
+ const providersQuery = useProvidersQuery({ enabled: open })
28
+ const credentialsQuery = useCredentialsQuery({ enabled: open })
29
+ const saveBuiltinProviderMutation = useSaveBuiltinProviderMutation()
30
+ const saveCustomProviderMutation = useSaveCustomProviderMutation()
31
+ const deleteProviderMutation = useDeleteProviderMutation()
32
+ const resetProviderModelsMutation = useResetProviderModelsMutation()
33
+ const checkProviderConnectionMutation = useCheckProviderConnectionMutation()
34
+ const providerModelDiscoveryMutation = useProviderModelDiscoveryMutation()
35
+ const createCredentialMutation = useCreateCredentialMutation()
36
+
37
+ const providerConfigs = providerConfigsQuery.data ?? []
38
+ const providers = providersQuery.data ?? []
39
+ const credentials = useMemo(() => credentialsQuery.data ?? {}, [credentialsQuery.data])
24
40
 
25
41
  const [name, setName] = useState('')
26
42
  const [baseUrl, setBaseUrl] = useState('')
@@ -46,14 +62,14 @@ export function ProviderSheet() {
46
62
  const [deleting, setDeleting] = useState(false)
47
63
 
48
64
  // Find editing provider in custom configs OR built-in list
49
- const editingCustom = editingId ? providerConfigs.find((c) => c.id === editingId) : null
65
+ const editingCustom = editingId ? providerConfigs.find((c) => c.id === editingId && c.type === 'custom') : null
66
+ const editingBuiltinOverride = editingId ? providerConfigs.find((c) => c.id === editingId && c.type === 'builtin') : null
50
67
  const editingBuiltin = editingId ? providers.find((p) => p.id === editingId) : null
51
68
  const isBuiltin = !!editingBuiltin && !editingCustom
52
69
  const editing = editingCustom || editingBuiltin
53
70
 
54
71
  useEffect(() => {
55
72
  if (open) {
56
- loadCredentials()
57
73
  setNewModel('')
58
74
  setLiveModels([])
59
75
  setLiveMessage('')
@@ -75,7 +91,7 @@ export function ProviderSheet() {
75
91
  // Default to existing credential for this provider
76
92
  const existingCred = Object.values(credentials).find((c) => c.provider === editingBuiltin.id)
77
93
  setCredentialId(existingCred?.id || null)
78
- setIsEnabled(true)
94
+ setIsEnabled(editingBuiltinOverride?.isEnabled !== false)
79
95
  } else {
80
96
  setName('')
81
97
  setBaseUrl('')
@@ -85,7 +101,7 @@ export function ProviderSheet() {
85
101
  setIsEnabled(true)
86
102
  }
87
103
  }
88
- }, [open, editingId, credentials, editingBuiltin, editingCustom, loadCredentials])
104
+ }, [open, editingId, credentials, editingBuiltin, editingBuiltinOverride, editingCustom])
89
105
 
90
106
  // Reset test status when connection params change
91
107
  useEffect(() => {
@@ -100,10 +116,11 @@ export function ProviderSheet() {
100
116
  }, [editingId, credentialId, baseUrl, requiresApiKey])
101
117
 
102
118
  const handleTestConnection = async () => {
119
+ if (!isBuiltin) return
103
120
  setTestStatus('testing')
104
121
  setTestMessage('')
105
122
  try {
106
- const result = await api<{ ok: boolean; message: string }>('POST', '/setup/check-provider', {
123
+ const result = await checkProviderConnectionMutation.mutateAsync({
107
124
  provider: editingId || 'custom',
108
125
  credentialId,
109
126
  endpoint: baseUrl,
@@ -135,11 +152,13 @@ export function ProviderSheet() {
135
152
  const handleSave = async () => {
136
153
  try {
137
154
  if (isBuiltin) {
138
- // Save model overrides for built-in providers
139
155
  const modelList = models.split(',').map((m) => m.trim()).filter(Boolean)
140
- await api('PUT', `/providers/${editingId}/models`, { models: modelList })
141
- toast.success('Built-in provider models updated')
142
- await loadProviders()
156
+ await saveBuiltinProviderMutation.mutateAsync({
157
+ id: editingId || '',
158
+ models: modelList,
159
+ isEnabled,
160
+ })
161
+ toast.success('Built-in provider updated')
143
162
  onClose()
144
163
  return
145
164
  }
@@ -152,14 +171,11 @@ export function ProviderSheet() {
152
171
  credentialId,
153
172
  isEnabled,
154
173
  }
155
- if (editingCustom) {
156
- await updateProviderConfig(editingCustom.id, data)
157
- toast.success('Provider updated')
158
- } else {
159
- await createProviderConfig(data)
160
- toast.success('Provider created')
161
- }
162
- await loadProviderConfigs()
174
+ await saveCustomProviderMutation.mutateAsync({
175
+ id: editingCustom?.id,
176
+ data,
177
+ })
178
+ toast.success(editingCustom ? 'Provider updated' : 'Provider created')
163
179
  onClose()
164
180
  } catch (err: unknown) {
165
181
  toast.error(err instanceof Error ? err.message : 'Failed to save provider')
@@ -170,9 +186,8 @@ export function ProviderSheet() {
170
186
  if (editingCustom) {
171
187
  setDeleting(true)
172
188
  try {
173
- await deleteProviderConfig(editingCustom.id)
189
+ await deleteProviderMutation.mutateAsync(editingCustom.id)
174
190
  toast.success('Provider deleted')
175
- await loadProviderConfigs()
176
191
  setConfirmDelete(false)
177
192
  onClose()
178
193
  } catch (err: unknown) {
@@ -184,35 +199,33 @@ export function ProviderSheet() {
184
199
  }
185
200
 
186
201
  const handleResetModels = async () => {
187
- if (isBuiltin && editingId) {
188
- await api('DELETE', `/providers/${editingId}/models`)
189
- await loadProviders()
190
- // Re-read the reset models
191
- const updated = providers.find((p) => p.id === editingId)
192
- if (updated) setModels(updated.models.join(', '))
193
- }
194
- }
195
-
196
- const handleAddModel = () => {
197
- if (!newModel.trim()) return
198
- const current = models ? models + ', ' + newModel.trim() : newModel.trim()
199
- setModels(current)
200
- setNewModel('')
202
+ if (!isBuiltin || !editingId) return
203
+ await resetProviderModelsMutation.mutateAsync(editingId)
204
+ const refreshedProviders = (await providersQuery.refetch()).data ?? []
205
+ const updated = refreshedProviders.find((provider) => provider.id === editingId)
206
+ if (updated) setModels(updated.models.join(', '))
201
207
  }
202
208
 
203
- const handleRemoveModel = (index: number) => {
204
- const list = models.split(',').map((m) => m.trim()).filter(Boolean)
205
- list.splice(index, 1)
206
- setModels(list.join(', '))
209
+ const handleCreateCredential = async () => {
210
+ const cred = await createCredentialMutation.mutateAsync({
211
+ provider: editingId || name || 'custom',
212
+ name: newKeyName.trim() || `${name || editingId || 'Custom'} key`,
213
+ apiKey: newKeyValue.trim(),
214
+ })
215
+ await credentialsQuery.refetch()
216
+ setCredentialId(cred.id)
217
+ setAddingKey(false)
218
+ setNewKeyName('')
219
+ setNewKeyValue('')
207
220
  }
208
221
 
209
222
  const handleLoadLiveModels = async (force = false) => {
210
- if (!open) return
223
+ if (!open || !isBuiltin) return
211
224
  const providerId = editingId || 'custom'
212
225
  setLiveLoading(true)
213
226
  setLiveMessage('')
214
227
  try {
215
- const result = await fetchProviderModelDiscovery({
228
+ const result = await providerModelDiscoveryMutation.mutateAsync({
216
229
  providerId,
217
230
  credentialId,
218
231
  endpoint: baseUrl,
@@ -237,12 +250,24 @@ export function ProviderSheet() {
237
250
  }
238
251
  }
239
252
 
253
+ const handleAddModel = () => {
254
+ if (!newModel.trim()) return
255
+ const current = models ? models + ', ' + newModel.trim() : newModel.trim()
256
+ setModels(current)
257
+ setNewModel('')
258
+ }
259
+
260
+ const handleRemoveModel = (index: number) => {
261
+ const list = models.split(',').map((m) => m.trim()).filter(Boolean)
262
+ list.splice(index, 1)
263
+ setModels(list.join(', '))
264
+ }
265
+
240
266
  const credList = Object.values(credentials)
241
267
  const modelList = models.split(',').map((m) => m.trim()).filter(Boolean)
242
268
  const showApiKey = isBuiltin ? editingBuiltin?.requiresApiKey || editingBuiltin?.optionalApiKey : requiresApiKey
243
- const canDiscoverModels = isBuiltin
244
- ? Boolean(editingBuiltin?.supportsModelDiscovery)
245
- : !!baseUrl.trim()
269
+ const canDiscoverModels = Boolean(isBuiltin && editingBuiltin?.supportsModelDiscovery)
270
+ const showTestButton = Boolean(isBuiltin && showApiKey && credentialId)
246
271
 
247
272
  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"
248
273
 
@@ -295,7 +320,7 @@ export function ProviderSheet() {
295
320
  </button>
296
321
  )}
297
322
  {isBuiltin && (
298
- <button onClick={handleResetModels}
323
+ <button onClick={() => { void handleResetModels() }}
299
324
  className="text-[11px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none"
300
325
  style={{ fontFamily: 'inherit' }}>
301
326
  Reset to defaults
@@ -367,7 +392,7 @@ export function ProviderSheet() {
367
392
  className={`${inputClass} resize-y min-h-[80px] font-mono text-[14px]`}
368
393
  style={{ fontFamily: 'inherit' }}
369
394
  />
370
- <p className="text-[11px] text-text-3/70 mt-2">Comma-separated model IDs</p>
395
+ <p className="text-[11px] text-text-3/70 mt-2">Comma-separated model IDs. Custom providers are saved as-is, so add the models you want manually.</p>
371
396
  </>
372
397
  )}
373
398
  </div>
@@ -451,14 +476,12 @@ export function ProviderSheet() {
451
476
  onClick={async () => {
452
477
  setSavingKey(true)
453
478
  try {
454
- const cred = await api<{ id: string }>('POST', '/credentials', { provider: editingId || name || 'custom', name: newKeyName.trim() || `${name || editingId || 'Custom'} key`, apiKey: newKeyValue.trim() })
455
- await loadCredentials()
456
- setCredentialId(cred.id)
457
- setAddingKey(false)
458
- setNewKeyName('')
459
- setNewKeyValue('')
460
- } catch (err: unknown) { toast.error(`Failed to save: ${errorMessage(err)}`) }
461
- finally { setSavingKey(false) }
479
+ await handleCreateCredential()
480
+ } catch (err: unknown) {
481
+ toast.error(`Failed to save: ${errorMessage(err)}`)
482
+ } finally {
483
+ setSavingKey(false)
484
+ }
462
485
  }}
463
486
  className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
464
487
  style={{ fontFamily: 'inherit' }}
@@ -471,8 +494,8 @@ export function ProviderSheet() {
471
494
  </div>
472
495
  )}
473
496
 
474
- {/* Enabled toggle — only for custom */}
475
- {!isBuiltin && editingCustom && (
497
+ {/* Enabled toggle */}
498
+ {(isBuiltin || editingCustom) && (
476
499
  <div className="mb-8">
477
500
  <label className="flex items-center gap-3 cursor-pointer">
478
501
  <div
@@ -484,17 +507,20 @@ export function ProviderSheet() {
484
507
  ${isEnabled ? 'left-[22px]' : 'left-0.5'}`} />
485
508
  </div>
486
509
  <span className="font-display text-[14px] font-600 text-text-2">Enabled</span>
510
+ {isBuiltin && (
511
+ <span className="text-[12px] text-text-3">Hidden from the agent sheet when off.</span>
512
+ )}
487
513
  </label>
488
514
  </div>
489
515
  )}
490
516
 
491
517
  {/* Test connection result */}
492
- {testStatus === 'fail' && (
518
+ {isBuiltin && testStatus === 'fail' && (
493
519
  <div className="mb-4 p-3 rounded-[12px] bg-red-500/[0.08] border border-red-500/20">
494
520
  <p className="text-[13px] text-red-400">{testMessage || 'Connection test failed'}</p>
495
521
  </div>
496
522
  )}
497
- {testStatus === 'pass' && (
523
+ {isBuiltin && testStatus === 'pass' && (
498
524
  <div className="mb-4 p-3 rounded-[12px] bg-emerald-500/[0.08] border border-emerald-500/20">
499
525
  <p className="text-[13px] text-emerald-400">{testMessage || 'Connected successfully'}</p>
500
526
  </div>
@@ -509,25 +535,24 @@ export function ProviderSheet() {
509
535
  <button onClick={onClose} className="flex-1 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[15px] font-600 cursor-pointer hover:bg-surface-2 transition-all" style={{ fontFamily: 'inherit' }}>
510
536
  Cancel
511
537
  </button>
512
- {showApiKey && credentialId && testStatus !== 'pass' ? (
538
+ {showTestButton && (
513
539
  <button
514
540
  onClick={handleTestConnection}
515
541
  disabled={testStatus === 'testing'}
516
- className="flex-1 py-3.5 rounded-[14px] border-none bg-emerald-600 text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(16,185,129,0.2)] hover:brightness-110"
542
+ className="py-3.5 px-6 rounded-[14px] border-none bg-emerald-600 text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(16,185,129,0.2)] hover:brightness-110"
517
543
  style={{ fontFamily: 'inherit' }}
518
544
  >
519
545
  {testStatus === 'testing' ? 'Testing...' : testStatus === 'fail' ? 'Retry Connection' : 'Test Connection'}
520
546
  </button>
521
- ) : (
522
- <button
523
- onClick={handleSave}
524
- disabled={isBuiltin ? false : (!name.trim() || !baseUrl.trim())}
525
- className="flex-1 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
526
- style={{ fontFamily: 'inherit' }}
527
- >
528
- {editing ? 'Save' : 'Create'}
529
- </button>
530
547
  )}
548
+ <button
549
+ onClick={handleSave}
550
+ disabled={isBuiltin ? false : (!name.trim() || !baseUrl.trim())}
551
+ className="flex-1 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-600 cursor-pointer active:scale-[0.97] disabled:opacity-30 transition-all shadow-[0_4px_20px_rgba(99,102,241,0.25)] hover:brightness-110"
552
+ style={{ fontFamily: 'inherit' }}
553
+ >
554
+ {editing ? 'Save' : 'Create'}
555
+ </button>
531
556
  </div>
532
557
  <ConfirmDialog
533
558
  open={confirmDelete}
@@ -40,6 +40,7 @@ export function ModelCombobox({
40
40
  const containerRef = useRef<HTMLDivElement>(null)
41
41
  const lastDiscoveryKeyRef = useRef<string | null>(null)
42
42
  const loadProviders = useAppStore((s) => s.loadProviders)
43
+ const loadProviderConfigs = useAppStore((s) => s.loadProviderConfigs)
43
44
 
44
45
  const availableModels = [...models, ...discoveredModels].filter((model, index, source) => source.indexOf(model) === index)
45
46
  const trimmedQuery = query.trim()
@@ -53,8 +54,8 @@ export function ModelCombobox({
53
54
 
54
55
  const persistModels = useCallback(async (next: string[]) => {
55
56
  await api('PUT', `/providers/${providerId}/models`, { models: next })
56
- await loadProviders()
57
- }, [providerId, loadProviders])
57
+ await Promise.all([loadProviders(), loadProviderConfigs()])
58
+ }, [loadProviderConfigs, loadProviders, providerId])
58
59
 
59
60
  const addModel = useCallback(async (name: string) => {
60
61
  const next = [...models, name]
@@ -73,8 +74,8 @@ export function ModelCombobox({
73
74
  } else {
74
75
  await persistModels(next)
75
76
  }
76
- await loadProviders()
77
- }, [models, defaultModels, value, onChange, providerId, persistModels, loadProviders])
77
+ await Promise.all([loadProviders(), loadProviderConfigs()])
78
+ }, [models, defaultModels, value, onChange, providerId, persistModels, loadProviderConfigs, loadProviders])
78
79
 
79
80
  const selectModel = useCallback((m: string) => {
80
81
  onChange(m)
@@ -1,11 +1,11 @@
1
1
  'use client'
2
2
 
3
- import { useCallback, useEffect, useMemo, useState } from 'react'
3
+ import { useCallback, useMemo, useState } from 'react'
4
4
  import { useRouter, useSearchParams } from 'next/navigation'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
- import { useWs } from '@/hooks/use-ws'
7
6
  import type { Skill } from '@/types'
8
7
  import { SkillsWorkspace } from './skills-workspace'
8
+ import { useSkillsQuery } from '@/features/skills/queries'
9
9
 
10
10
  export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
11
11
  if (!inSidebar) return <SkillsWorkspace />
@@ -15,30 +15,17 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
15
15
  function SidebarSkillList() {
16
16
  const router = useRouter()
17
17
  const searchParams = useSearchParams()
18
- const skills = useAppStore((s) => s.skills)
19
- const loadSkills = useAppStore((s) => s.loadSkills)
20
18
  const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
21
19
  const setEditingSkillId = useAppStore((s) => s.setEditingSkillId)
22
20
  const setSkillSheetOpen = useAppStore((s) => s.setSkillSheetOpen)
21
+ const skillsQuery = useSkillsQuery()
22
+ const skills = useMemo(() => skillsQuery.data ?? {}, [skillsQuery.data])
23
23
 
24
24
  const [query, setQuery] = useState('')
25
- const [ready, setReady] = useState(false)
26
25
 
27
26
  const activeTab = searchParams.get('tab') === 'clawhub' ? 'clawhub' : 'skills'
28
27
  const selectedSkillId = activeTab === 'skills' ? searchParams.get('skill') : null
29
28
 
30
- useEffect(() => {
31
- let active = true
32
- void loadSkills().finally(() => {
33
- if (active) setReady(true)
34
- })
35
- return () => {
36
- active = false
37
- }
38
- }, [loadSkills])
39
-
40
- useWs('skills', () => { void loadSkills() })
41
-
42
29
  const setPageState = useCallback((patch: Record<string, string | null | undefined>) => {
43
30
  const next = new URLSearchParams(searchParams.toString())
44
31
  for (const [key, value] of Object.entries(patch)) {
@@ -92,7 +79,7 @@ function SidebarSkillList() {
92
79
  </div>
93
80
 
94
81
  <div className="mt-3 flex-1 overflow-y-auto">
95
- {!ready ? (
82
+ {skillsQuery.isPending ? (
96
83
  <div className="flex items-center justify-center py-10 text-[12px] text-text-3/65">
97
84
  <span className="mr-3 h-4 w-4 animate-spin rounded-full border-2 border-white/[0.12] border-t-accent-bright" />
98
85
  Loading skills...
@@ -5,20 +5,27 @@ import { useAppStore } from '@/stores/use-app-store'
5
5
  import { BottomSheet } from '@/components/shared/bottom-sheet'
6
6
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
7
7
  import { AgentAvatar } from '@/components/agents/agent-avatar'
8
- import { api } from '@/lib/app/api-client'
9
8
  import { buildSkillSavePayload } from '@/lib/skill-save-payload'
10
9
  import { toast } from 'sonner'
11
10
  import type { Skill, SkillSecuritySummary } from '@/types'
11
+ import {
12
+ useDeleteSkillMutation,
13
+ useImportSkillFromUrlMutation,
14
+ useSaveSkillMutation,
15
+ useSkillsQuery,
16
+ } from '@/features/skills/queries'
17
+ import { useAgentsQuery } from '@/features/agents/queries'
12
18
 
13
19
  export function SkillSheet() {
14
20
  const open = useAppStore((s) => s.skillSheetOpen)
15
21
  const setOpen = useAppStore((s) => s.setSkillSheetOpen)
16
22
  const editingId = useAppStore((s) => s.editingSkillId)
17
23
  const setEditingId = useAppStore((s) => s.setEditingSkillId)
18
- const skills = useAppStore((s) => s.skills)
19
- const loadSkills = useAppStore((s) => s.loadSkills)
20
- const agents = useAppStore((s) => s.agents)
21
- const loadAgents = useAppStore((s) => s.loadAgents)
24
+ const skillsQuery = useSkillsQuery({ enabled: open })
25
+ const agentsQuery = useAgentsQuery({ enabled: open })
26
+ const importSkillFromUrlMutation = useImportSkillFromUrlMutation()
27
+ const saveSkillMutation = useSaveSkillMutation()
28
+ const deleteSkillMutation = useDeleteSkillMutation()
22
29
  const fileRef = useRef<HTMLInputElement>(null)
23
30
 
24
31
  const [name, setName] = useState('')
@@ -35,6 +42,8 @@ export function SkillSheet() {
35
42
  const [confirmDelete, setConfirmDelete] = useState(false)
36
43
  const [deleting, setDeleting] = useState(false)
37
44
 
45
+ const skills = skillsQuery.data ?? {}
46
+ const agents = agentsQuery.data ?? {}
38
47
  const editing = editingId ? skills[editingId] : null
39
48
  const agentList = Object.values(agents)
40
49
 
@@ -44,7 +53,7 @@ export function SkillSheet() {
44
53
  setImportError('')
45
54
  setImportNotice('')
46
55
  try {
47
- const result = await api<Partial<Skill> & { name: string; filename: string; description?: string; content: string; sourceFormat?: 'openclaw' | 'plain' }>('POST', '/skills/import', { url: importUrl.trim() })
56
+ const result = await importSkillFromUrlMutation.mutateAsync(importUrl.trim())
48
57
  setName(result.name || '')
49
58
  setFilename(result.filename || '')
50
59
  setDescription(result.description || '')
@@ -62,10 +71,6 @@ export function SkillSheet() {
62
71
  }
63
72
  }
64
73
 
65
- useEffect(() => {
66
- if (open) loadAgents()
67
- }, [open, loadAgents])
68
-
69
74
  useEffect(() => {
70
75
  if (open) {
71
76
  setImportUrl('')
@@ -133,14 +138,11 @@ export function SkillSheet() {
133
138
  agentIds,
134
139
  }, metadataPreview)
135
140
  try {
136
- if (editing) {
137
- await api('PUT', `/skills/${editing.id}`, data)
138
- toast.success('Skill updated successfully')
139
- } else {
140
- await api('POST', '/skills', data)
141
- toast.success('Skill created successfully')
142
- }
143
- await loadSkills()
141
+ await saveSkillMutation.mutateAsync({
142
+ id: editing?.id,
143
+ data: data as Record<string, unknown>,
144
+ })
145
+ toast.success(editing ? 'Skill updated successfully' : 'Skill created successfully')
144
146
  onClose()
145
147
  } catch (err: unknown) {
146
148
  toast.error(err instanceof Error ? err.message : 'Failed to save skill')
@@ -151,9 +153,8 @@ export function SkillSheet() {
151
153
  if (!editing) return
152
154
  setDeleting(true)
153
155
  try {
154
- await api('DELETE', `/skills/${editing.id}`)
156
+ await deleteSkillMutation.mutateAsync(editing.id)
155
157
  toast.success('Skill deleted')
156
- await loadSkills()
157
158
  setConfirmDelete(false)
158
159
  onClose()
159
160
  } catch (err: unknown) {