@swarmclawai/swarmclaw 0.3.1 → 0.4.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 (203) hide show
  1. package/README.md +33 -13
  2. package/bin/server-cmd.js +14 -7
  3. package/bin/swarmclaw.js +3 -1
  4. package/bin/update-cmd.js +120 -0
  5. package/next.config.ts +10 -0
  6. package/package.json +4 -1
  7. package/src/app/api/agents/[id]/route.ts +20 -18
  8. package/src/app/api/agents/[id]/thread/route.ts +4 -3
  9. package/src/app/api/agents/route.ts +8 -3
  10. package/src/app/api/auth/route.ts +3 -1
  11. package/src/app/api/claude-skills/route.ts +3 -1
  12. package/src/app/api/clawhub/install/route.ts +2 -2
  13. package/src/app/api/connectors/[id]/route.ts +14 -3
  14. package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
  15. package/src/app/api/connectors/route.ts +12 -4
  16. package/src/app/api/credentials/[id]/route.ts +2 -1
  17. package/src/app/api/credentials/route.ts +5 -3
  18. package/src/app/api/daemon/route.ts +6 -1
  19. package/src/app/api/documents/route.ts +2 -2
  20. package/src/app/api/files/serve/route.ts +8 -0
  21. package/src/app/api/ip/route.ts +3 -1
  22. package/src/app/api/knowledge/[id]/route.ts +5 -4
  23. package/src/app/api/knowledge/upload/route.ts +2 -2
  24. package/src/app/api/mcp-servers/[id]/route.ts +11 -14
  25. package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
  26. package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
  27. package/src/app/api/mcp-servers/route.ts +5 -3
  28. package/src/app/api/memory/[id]/route.ts +9 -8
  29. package/src/app/api/memory/route.ts +2 -2
  30. package/src/app/api/memory-images/[filename]/route.ts +2 -1
  31. package/src/app/api/openclaw/directory/route.ts +26 -0
  32. package/src/app/api/openclaw/discover/route.ts +61 -0
  33. package/src/app/api/openclaw/sync/route.ts +30 -0
  34. package/src/app/api/orchestrator/graph/route.ts +25 -0
  35. package/src/app/api/orchestrator/run/route.ts +2 -2
  36. package/src/app/api/plugins/marketplace/route.ts +3 -1
  37. package/src/app/api/plugins/route.ts +3 -1
  38. package/src/app/api/projects/[id]/route.ts +55 -0
  39. package/src/app/api/projects/route.ts +27 -0
  40. package/src/app/api/providers/[id]/models/route.ts +2 -1
  41. package/src/app/api/providers/[id]/route.ts +13 -12
  42. package/src/app/api/providers/configs/route.ts +3 -1
  43. package/src/app/api/providers/route.ts +7 -3
  44. package/src/app/api/schedules/[id]/route.ts +16 -15
  45. package/src/app/api/schedules/[id]/run/route.ts +4 -3
  46. package/src/app/api/schedules/route.ts +8 -3
  47. package/src/app/api/secrets/[id]/route.ts +16 -17
  48. package/src/app/api/secrets/route.ts +5 -3
  49. package/src/app/api/sessions/[id]/chat/route.ts +5 -2
  50. package/src/app/api/sessions/[id]/clear/route.ts +2 -1
  51. package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
  52. package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
  53. package/src/app/api/sessions/[id]/messages/route.ts +2 -1
  54. package/src/app/api/sessions/[id]/retry/route.ts +2 -1
  55. package/src/app/api/sessions/[id]/route.ts +2 -1
  56. package/src/app/api/sessions/route.ts +11 -4
  57. package/src/app/api/settings/route.ts +3 -1
  58. package/src/app/api/setup/doctor/route.ts +1 -0
  59. package/src/app/api/setup/openclaw-device/route.ts +3 -1
  60. package/src/app/api/skills/[id]/route.ts +23 -21
  61. package/src/app/api/skills/import/route.ts +2 -2
  62. package/src/app/api/skills/route.ts +5 -3
  63. package/src/app/api/tasks/[id]/approve/route.ts +74 -0
  64. package/src/app/api/tasks/[id]/route.ts +9 -5
  65. package/src/app/api/tasks/route.ts +5 -2
  66. package/src/app/api/tts/stream/route.ts +48 -0
  67. package/src/app/api/upload/route.ts +2 -2
  68. package/src/app/api/uploads/[filename]/route.ts +4 -1
  69. package/src/app/api/usage/route.ts +3 -1
  70. package/src/app/api/version/route.ts +3 -1
  71. package/src/app/api/webhooks/[id]/route.ts +31 -32
  72. package/src/app/api/webhooks/route.ts +5 -3
  73. package/src/app/icon.svg +58 -0
  74. package/src/app/page.tsx +11 -26
  75. package/src/cli/index.js +28 -9
  76. package/src/cli/index.ts +45 -2
  77. package/src/cli/spec.js +2 -8
  78. package/src/components/agents/agent-card.tsx +1 -1
  79. package/src/components/agents/agent-list.tsx +3 -1
  80. package/src/components/agents/agent-sheet.tsx +166 -81
  81. package/src/components/chat/chat-area.tsx +71 -34
  82. package/src/components/chat/chat-header.tsx +141 -29
  83. package/src/components/chat/chat-tool-toggles.tsx +12 -53
  84. package/src/components/chat/message-bubble.tsx +110 -42
  85. package/src/components/chat/tool-call-bubble.tsx +50 -6
  86. package/src/components/chat/tool-request-banner.tsx +1 -9
  87. package/src/components/chat/voice-overlay.tsx +80 -0
  88. package/src/components/connectors/connector-list.tsx +9 -10
  89. package/src/components/connectors/connector-sheet.tsx +55 -36
  90. package/src/components/input/chat-input.tsx +72 -56
  91. package/src/components/knowledge/knowledge-list.tsx +27 -31
  92. package/src/components/layout/app-layout.tsx +133 -90
  93. package/src/components/layout/daemon-indicator.tsx +3 -5
  94. package/src/components/logs/log-list.tsx +5 -9
  95. package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
  96. package/src/components/memory/memory-detail.tsx +1 -1
  97. package/src/components/plugins/plugin-list.tsx +227 -27
  98. package/src/components/projects/project-list.tsx +122 -0
  99. package/src/components/projects/project-sheet.tsx +135 -0
  100. package/src/components/providers/provider-list.tsx +46 -13
  101. package/src/components/providers/provider-sheet.tsx +0 -45
  102. package/src/components/runs/run-list.tsx +6 -15
  103. package/src/components/schedules/schedule-card.tsx +54 -4
  104. package/src/components/schedules/schedule-list.tsx +9 -4
  105. package/src/components/schedules/schedule-sheet.tsx +0 -47
  106. package/src/components/secrets/secrets-list.tsx +20 -2
  107. package/src/components/sessions/new-session-sheet.tsx +14 -15
  108. package/src/components/sessions/session-card.tsx +1 -1
  109. package/src/components/sessions/session-list.tsx +7 -7
  110. package/src/components/shared/connector-platform-icon.tsx +26 -20
  111. package/src/components/shared/model-combobox.tsx +148 -0
  112. package/src/components/shared/settings/section-heartbeat.tsx +8 -40
  113. package/src/components/shared/settings/section-orchestrator.tsx +9 -11
  114. package/src/components/shared/settings/section-web-search.tsx +56 -0
  115. package/src/components/shared/settings/settings-page.tsx +73 -0
  116. package/src/components/skills/skill-list.tsx +262 -35
  117. package/src/components/skills/skill-sheet.tsx +0 -45
  118. package/src/components/tasks/task-board.tsx +3 -6
  119. package/src/components/tasks/task-card.tsx +43 -1
  120. package/src/components/tasks/task-list.tsx +8 -7
  121. package/src/components/tasks/task-sheet.tsx +0 -44
  122. package/src/components/usage/usage-list.tsx +12 -4
  123. package/src/hooks/use-continuous-speech.ts +144 -0
  124. package/src/hooks/use-view-router.ts +52 -0
  125. package/src/hooks/use-voice-conversation.ts +80 -0
  126. package/src/hooks/use-ws.ts +66 -0
  127. package/src/instrumentation.ts +2 -0
  128. package/src/lib/chat.ts +14 -2
  129. package/src/lib/id.ts +6 -0
  130. package/src/lib/projects.ts +13 -0
  131. package/src/lib/provider-sets.ts +5 -0
  132. package/src/lib/providers/anthropic.ts +15 -2
  133. package/src/lib/providers/index.ts +8 -0
  134. package/src/lib/providers/ollama.ts +10 -2
  135. package/src/lib/providers/openai.ts +42 -13
  136. package/src/lib/providers/openclaw.ts +11 -0
  137. package/src/lib/server/api-routes.test.ts +5 -6
  138. package/src/lib/server/build-llm.ts +17 -4
  139. package/src/lib/server/chat-execution.ts +57 -8
  140. package/src/lib/server/collection-helpers.ts +54 -0
  141. package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
  142. package/src/lib/server/connectors/bluebubbles.ts +357 -0
  143. package/src/lib/server/connectors/connector-routing.test.ts +1 -1
  144. package/src/lib/server/connectors/googlechat.ts +46 -7
  145. package/src/lib/server/connectors/manager.ts +401 -6
  146. package/src/lib/server/connectors/media.ts +2 -2
  147. package/src/lib/server/connectors/openclaw.ts +64 -0
  148. package/src/lib/server/connectors/pairing.test.ts +99 -0
  149. package/src/lib/server/connectors/pairing.ts +256 -0
  150. package/src/lib/server/connectors/signal.ts +1 -0
  151. package/src/lib/server/connectors/teams.ts +5 -5
  152. package/src/lib/server/connectors/types.ts +10 -0
  153. package/src/lib/server/context-manager.ts +1 -1
  154. package/src/lib/server/daemon-state.ts +3 -0
  155. package/src/lib/server/data-dir.ts +1 -0
  156. package/src/lib/server/execution-log.ts +3 -3
  157. package/src/lib/server/heartbeat-service.ts +67 -3
  158. package/src/lib/server/knowledge-db.test.ts +2 -33
  159. package/src/lib/server/langgraph-checkpoint.ts +274 -0
  160. package/src/lib/server/main-agent-loop.ts +67 -8
  161. package/src/lib/server/memory-db.ts +6 -6
  162. package/src/lib/server/openclaw-approvals.ts +105 -0
  163. package/src/lib/server/openclaw-sync.ts +496 -0
  164. package/src/lib/server/orchestrator-lg.ts +422 -20
  165. package/src/lib/server/orchestrator.ts +29 -9
  166. package/src/lib/server/process-manager.ts +2 -2
  167. package/src/lib/server/queue.ts +39 -13
  168. package/src/lib/server/scheduler.ts +2 -2
  169. package/src/lib/server/session-mailbox.ts +2 -2
  170. package/src/lib/server/session-run-manager.ts +8 -3
  171. package/src/lib/server/session-tools/connector.ts +51 -4
  172. package/src/lib/server/session-tools/crud.ts +3 -3
  173. package/src/lib/server/session-tools/delegate.ts +5 -5
  174. package/src/lib/server/session-tools/file.ts +176 -3
  175. package/src/lib/server/session-tools/index.ts +4 -0
  176. package/src/lib/server/session-tools/memory.ts +2 -2
  177. package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
  178. package/src/lib/server/session-tools/sandbox.ts +197 -0
  179. package/src/lib/server/session-tools/search-providers.ts +270 -0
  180. package/src/lib/server/session-tools/session-info.ts +2 -2
  181. package/src/lib/server/session-tools/web.ts +47 -66
  182. package/src/lib/server/storage-mcp.test.ts +25 -2
  183. package/src/lib/server/storage.ts +36 -7
  184. package/src/lib/server/stream-agent-chat.ts +106 -22
  185. package/src/lib/server/task-result.test.ts +44 -0
  186. package/src/lib/server/task-result.ts +14 -0
  187. package/src/lib/server/task-validation.test.ts +23 -0
  188. package/src/lib/server/task-validation.ts +5 -3
  189. package/src/lib/server/ws-hub.ts +85 -0
  190. package/src/lib/tool-definitions.ts +44 -0
  191. package/src/lib/tts-stream.ts +130 -0
  192. package/src/lib/upload.ts +7 -1
  193. package/src/lib/view-routes.ts +28 -0
  194. package/src/lib/ws-client.ts +124 -0
  195. package/src/proxy.ts +3 -0
  196. package/src/stores/use-app-store.ts +28 -1
  197. package/src/stores/use-chat-store.ts +42 -14
  198. package/src/types/index.ts +34 -2
  199. package/src/app/api/agents/generate/route.ts +0 -42
  200. package/src/app/api/generate/info/route.ts +0 -12
  201. package/src/app/api/generate/route.ts +0 -106
  202. package/src/app/favicon.ico +0 -0
  203. package/src/components/shared/ai-gen-block.tsx +0 -77
package/src/app/page.tsx CHANGED
@@ -4,10 +4,13 @@ import { useEffect, useState, useCallback } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { initAudioContext } from '@/lib/tts'
6
6
  import { getStoredAccessKey, clearStoredAccessKey, api } from '@/lib/api-client'
7
+ import { connectWs, disconnectWs } from '@/lib/ws-client'
8
+ import { useWs } from '@/hooks/use-ws'
7
9
  import { AccessKeyGate } from '@/components/auth/access-key-gate'
8
10
  import { UserPicker } from '@/components/auth/user-picker'
9
11
  import { SetupWizard } from '@/components/auth/setup-wizard'
10
12
  import { AppLayout } from '@/components/layout/app-layout'
13
+ import { useViewRouter } from '@/hooks/use-view-router'
11
14
 
12
15
  export default function Home() {
13
16
  const currentUser = useAppStore((s) => s.currentUser)
@@ -15,7 +18,6 @@ export default function Home() {
15
18
  const hydrated = useAppStore((s) => s._hydrated)
16
19
  const hydrate = useAppStore((s) => s.hydrate)
17
20
  const loadNetworkInfo = useAppStore((s) => s.loadNetworkInfo)
18
- const sessions = useAppStore((s) => s.sessions)
19
21
  const loadSessions = useAppStore((s) => s.loadSessions)
20
22
  const loadSettings = useAppStore((s) => s.loadSettings)
21
23
 
@@ -70,14 +72,17 @@ export default function Home() {
70
72
 
71
73
  useEffect(() => {
72
74
  if (!authenticated) return
75
+ const key = getStoredAccessKey()
76
+ if (key) connectWs(key)
73
77
  syncUserFromServer()
74
78
  loadNetworkInfo()
75
79
  loadSettings()
76
80
  loadSessions()
77
- const interval = setInterval(loadSessions, 5000)
78
- return () => clearInterval(interval)
81
+ return () => { disconnectWs() }
79
82
  }, [authenticated])
80
83
 
84
+ useWs('sessions', loadSessions, 5000)
85
+
81
86
  // Auto-select default agent's thread on load
82
87
  useEffect(() => {
83
88
  if (!authenticated || !currentUser) return
@@ -102,29 +107,6 @@ export default function Home() {
102
107
  return () => { cancelled = true }
103
108
  }, [authenticated, currentUser])
104
109
 
105
- // Keep __main__ session for backward compat — create if missing
106
- useEffect(() => {
107
- if (!authenticated || !currentUser) return
108
- const sessionList = Object.values(sessions)
109
- const mainSession = sessionList.find((s: any) => s.name === '__main__' && s.user === currentUser)
110
- if (mainSession) return
111
- let cancelled = false
112
- ;(async () => {
113
- try {
114
- const mainId = `main-${currentUser}`
115
- await api<any>('POST', '/sessions', {
116
- id: mainId,
117
- name: '__main__',
118
- user: currentUser,
119
- agentId: 'default',
120
- heartbeatEnabled: true,
121
- })
122
- if (!cancelled) await loadSessions()
123
- } catch { /* ignore */ }
124
- })()
125
- return () => { cancelled = true }
126
- }, [authenticated, currentUser, sessions, loadSessions])
127
-
128
110
  // Check if first-run setup is needed
129
111
  useEffect(() => {
130
112
  if (!authenticated || !currentUser) return
@@ -156,6 +138,7 @@ export default function Home() {
156
138
 
157
139
  useEffect(() => {
158
140
  const handler = () => {
141
+ disconnectWs()
159
142
  setAuthenticated(false)
160
143
  setAuthChecked(true)
161
144
  }
@@ -163,6 +146,8 @@ export default function Home() {
163
146
  return () => window.removeEventListener('sc_auth_required', handler)
164
147
  }, [])
165
148
 
149
+ useViewRouter()
150
+
166
151
  if (!hydrated || !authChecked) return null
167
152
  if (!authenticated) return <AccessKeyGate onAuthenticated={() => setAuthenticated(true)} />
168
153
  if (!currentUser) return <UserPicker />
package/src/cli/index.js CHANGED
@@ -17,7 +17,6 @@ const COMMAND_GROUPS = [
17
17
  cmd('create', 'POST', '/agents', 'Create an agent', { expectsJsonBody: true }),
18
18
  cmd('update', 'PUT', '/agents/:id', 'Update an agent', { expectsJsonBody: true }),
19
19
  cmd('delete', 'DELETE', '/agents/:id', 'Delete an agent'),
20
- cmd('generate', 'POST', '/agents/generate', 'Generate agent definition from prompt', { expectsJsonBody: true }),
21
20
  cmd('thread', 'POST', '/agents/:id/thread', 'Get or create agent thread session'),
22
21
  ],
23
22
  },
@@ -56,6 +55,7 @@ const COMMAND_GROUPS = [
56
55
  cmd('create', 'POST', '/connectors', 'Create connector', { expectsJsonBody: true }),
57
56
  cmd('update', 'PUT', '/connectors/:id', 'Update connector', { expectsJsonBody: true }),
58
57
  cmd('delete', 'DELETE', '/connectors/:id', 'Delete connector'),
58
+ cmd('webhook', 'POST', '/connectors/:id/webhook', 'Trigger connector webhook ingress', { expectsJsonBody: true }),
59
59
  cmd('start', 'PUT', '/connectors/:id', 'Start connector', {
60
60
  expectsJsonBody: true,
61
61
  defaultBody: { action: 'start' },
@@ -123,14 +123,6 @@ const COMMAND_GROUPS = [
123
123
  cmd('serve', 'GET', '/files/serve', 'Serve a local file (use --query path=/abs/path)'),
124
124
  ],
125
125
  },
126
- {
127
- name: 'generate',
128
- description: 'AI generation endpoints',
129
- commands: [
130
- cmd('run', 'POST', '/generate', 'Generate schedule/task/skill/provider payload', { expectsJsonBody: true }),
131
- cmd('info', 'GET', '/generate/info', 'Get generation provider/model info'),
132
- ],
133
- },
134
126
  {
135
127
  name: 'ip',
136
128
  description: 'Get local IP/port metadata',
@@ -208,6 +200,16 @@ const COMMAND_GROUPS = [
208
200
  expectsJsonBody: true,
209
201
  waitEntityFrom: 'taskId',
210
202
  }),
203
+ cmd('graph', 'GET', '/orchestrator/graph', 'Get orchestrator graph structure'),
204
+ ],
205
+ },
206
+ {
207
+ name: 'openclaw',
208
+ description: 'OpenClaw discovery and sync',
209
+ commands: [
210
+ cmd('discover', 'GET', '/openclaw/discover', 'Discover OpenClaw gateways'),
211
+ cmd('directory', 'GET', '/openclaw/directory', 'List directory entries from running OpenClaw connectors'),
212
+ cmd('sync', 'POST', '/openclaw/sync', 'Run OpenClaw sync action', { expectsJsonBody: true }),
211
213
  ],
212
214
  },
213
215
  {
@@ -217,6 +219,17 @@ const COMMAND_GROUPS = [
217
219
  cmd('manage', 'POST', '/preview-server', 'Start/stop/status/detect preview server', { expectsJsonBody: true }),
218
220
  ],
219
221
  },
222
+ {
223
+ name: 'projects',
224
+ description: 'Manage projects',
225
+ commands: [
226
+ cmd('list', 'GET', '/projects', 'List projects'),
227
+ cmd('get', 'GET', '/projects/:id', 'Get project by id'),
228
+ cmd('create', 'POST', '/projects', 'Create project', { expectsJsonBody: true }),
229
+ cmd('update', 'PUT', '/projects/:id', 'Update project', { expectsJsonBody: true }),
230
+ cmd('delete', 'DELETE', '/projects/:id', 'Delete project'),
231
+ ],
232
+ },
220
233
  {
221
234
  name: 'plugins',
222
235
  description: 'Manage plugins and marketplace',
@@ -360,6 +373,7 @@ const COMMAND_GROUPS = [
360
373
  cmd('update', 'PUT', '/tasks/:id', 'Update task', { expectsJsonBody: true }),
361
374
  cmd('delete', 'DELETE', '/tasks/:id', 'Delete task'),
362
375
  cmd('purge', 'DELETE', '/tasks', 'Bulk delete tasks', { expectsJsonBody: true }),
376
+ cmd('approve', 'POST', '/tasks/:id/approve', 'Approve or reject a pending tool execution', { expectsJsonBody: true }),
363
377
  ],
364
378
  },
365
379
  {
@@ -371,6 +385,11 @@ const COMMAND_GROUPS = [
371
385
  responseType: 'binary',
372
386
  bodyFlagMap: { text: 'text' },
373
387
  }),
388
+ cmd('stream', 'POST', '/tts/stream', 'Generate streaming TTS audio', {
389
+ expectsJsonBody: true,
390
+ responseType: 'binary',
391
+ bodyFlagMap: { text: 'text' },
392
+ }),
374
393
  ],
375
394
  },
376
395
  {
package/src/cli/index.ts CHANGED
@@ -928,7 +928,7 @@ export function buildProgram(): Command {
928
928
  connectors
929
929
  .command('create')
930
930
  .description('Create connector')
931
- .requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw)')
931
+ .requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw|bluebubbles|signal|teams|googlechat|matrix)')
932
932
  .requiredOption('--agent-id <agentId>', 'Agent id')
933
933
  .option('--name <name>', 'Connector name')
934
934
  .option('--credential-id <credentialId>', 'Credential id')
@@ -1140,14 +1140,57 @@ export function buildProgram(): Command {
1140
1140
  console.log('Run: swarmclaw server --help')
1141
1141
  })
1142
1142
 
1143
+ program
1144
+ .command('update')
1145
+ .description('Pull the latest SwarmClaw release via git')
1146
+ .action(() => {
1147
+ console.log('The update command is handled directly by the swarmclaw binary.')
1148
+ console.log('Run: swarmclaw update --help')
1149
+ })
1150
+
1143
1151
  return program
1144
1152
  }
1145
1153
 
1154
+ async function checkForUpdate(baseUrl: string, accessKey: string): Promise<void> {
1155
+ try {
1156
+ const url = `${baseUrl}/api/version`
1157
+ const headers: Record<string, string> = {}
1158
+ if (accessKey) headers['X-Access-Key'] = accessKey
1159
+ const controller = new AbortController()
1160
+ const timeout = setTimeout(() => controller.abort(), 2000)
1161
+ const res = await fetch(url, { headers, signal: controller.signal })
1162
+ clearTimeout(timeout)
1163
+ if (!res.ok) return
1164
+ const data = (await res.json()) as { updateAvailable?: boolean; behindBy?: number }
1165
+ if (data.updateAvailable && data.behindBy) {
1166
+ process.stderr.write(`\n Update available (${data.behindBy} behind). Run: swarmclaw update\n`)
1167
+ }
1168
+ } catch {
1169
+ // Server unreachable or timed out — silently skip
1170
+ }
1171
+ }
1172
+
1146
1173
  export async function runCli(argv: string[] = process.argv.slice(2)): Promise<number> {
1147
1174
  const program = buildProgram()
1148
1175
  try {
1176
+ // Skip update hint for commands that don't talk to the server
1177
+ const skipHint = !argv.length || ['update', 'server', '--help', '-h'].includes(argv[0])
1178
+ const hintPromise = skipHint
1179
+ ? null
1180
+ : checkForUpdate(
1181
+ normalizeBaseUrl(process.env.SWARMCLAW_URL || process.env.SWARMCLAW_BASE_URL || 'http://localhost:3456'),
1182
+ (process.env.SWARMCLAW_ACCESS_KEY || '').trim(),
1183
+ )
1184
+
1149
1185
  await program.parseAsync(['node', 'swarmclaw', ...argv])
1150
- return (process.exitCode as number | undefined) ?? 0
1186
+ const code = (process.exitCode as number | undefined) ?? 0
1187
+
1188
+ // Wait briefly for the hint if the command succeeded
1189
+ if (hintPromise && code === 0) {
1190
+ await Promise.race([hintPromise, new Promise((r) => setTimeout(r, 2000))])
1191
+ }
1192
+
1193
+ return code
1151
1194
  } catch (err) {
1152
1195
  const msg = err instanceof Error ? err.message : String(err)
1153
1196
  console.error(msg)
package/src/cli/spec.js CHANGED
@@ -7,7 +7,6 @@ const COMMAND_GROUPS = {
7
7
  create: { description: 'Create an agent', method: 'POST', path: '/agents' },
8
8
  update: { description: 'Update an agent', method: 'PUT', path: '/agents/:id', params: ['id'] },
9
9
  delete: { description: 'Delete an agent', method: 'DELETE', path: '/agents/:id', params: ['id'] },
10
- generate: { description: 'Generate an agent definition', method: 'POST', path: '/agents/generate' },
11
10
  },
12
11
  },
13
12
  auth: {
@@ -97,13 +96,6 @@ const COMMAND_GROUPS = {
97
96
  },
98
97
  },
99
98
  },
100
- generate: {
101
- description: 'Structured AI generation helpers',
102
- commands: {
103
- create: { description: 'Generate object from prompt/type', method: 'POST', path: '/generate' },
104
- info: { description: 'Get active generator provider/model', method: 'GET', path: '/generate/info' },
105
- },
106
- },
107
99
  logs: {
108
100
  description: 'Application logs',
109
101
  commands: {
@@ -135,6 +127,7 @@ const COMMAND_GROUPS = {
135
127
  run: { description: 'Run orchestrator task now', method: 'POST', path: '/orchestrator/run', waitable: true },
136
128
  runs: { description: 'List queued/running/completed runs', method: 'GET', path: '/runs' },
137
129
  'run-get': { description: 'Get run by id', method: 'GET', path: '/runs/:id', params: ['id'] },
130
+ graph: { description: 'Get orchestrator graph structure', method: 'GET', path: '/orchestrator/graph' },
138
131
  },
139
132
  },
140
133
  plugins: {
@@ -251,6 +244,7 @@ const COMMAND_GROUPS = {
251
244
  update: { description: 'Update task', method: 'PUT', path: '/tasks/:id', params: ['id'] },
252
245
  delete: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
253
246
  archive: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
247
+ approve: { description: 'Approve or reject a pending tool execution', method: 'POST', path: '/tasks/:id/approve', params: ['id'] },
254
248
  },
255
249
  },
256
250
  webhooks: {
@@ -56,7 +56,7 @@ export function AgentCard({ agent, isDefault, onSetDefault }: Props) {
56
56
  await loadSessions()
57
57
  setMessages([])
58
58
  setCurrentSession(result.sessionId)
59
- setActiveView('sessions')
59
+ setActiveView('agents')
60
60
  }
61
61
  } catch (err) {
62
62
  console.error('Orchestrator run failed:', err)
@@ -16,6 +16,7 @@ export function AgentList({ inSidebar }: Props) {
16
16
  const currentUser = useAppStore((s) => s.currentUser)
17
17
  const loadSessions = useAppStore((s) => s.loadSessions)
18
18
  const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
19
+ const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
19
20
  const [search, setSearch] = useState('')
20
21
  const [filter, setFilter] = useState<'all' | 'orchestrator' | 'agent'>('all')
21
22
 
@@ -41,10 +42,11 @@ export function AgentList({ inSidebar }: Props) {
41
42
  if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false
42
43
  if (filter === 'orchestrator' && !p.isOrchestrator) return false
43
44
  if (filter === 'agent' && p.isOrchestrator) return false
45
+ if (activeProjectFilter && p.projectId !== activeProjectFilter) return false
44
46
  return true
45
47
  })
46
48
  .sort((a, b) => b.updatedAt - a.updatedAt)
47
- }, [agents, search, filter])
49
+ }, [agents, search, filter, activeProjectFilter])
48
50
 
49
51
  if (!filtered.length && !search) {
50
52
  return (
@@ -5,40 +5,46 @@ import { useAppStore } from '@/stores/use-app-store'
5
5
  import { createAgent, updateAgent, deleteAgent } from '@/lib/agents'
6
6
  import { api } from '@/lib/api-client'
7
7
  import { BottomSheet } from '@/components/shared/bottom-sheet'
8
- import { AiGenBlock } from '@/components/shared/ai-gen-block'
9
8
  import { toast } from 'sonner'
9
+ import { ModelCombobox } from '@/components/shared/model-combobox'
10
10
  import type { ProviderType, ClaudeSkill } from '@/types'
11
+ import { AVAILABLE_TOOLS, PLATFORM_TOOLS } from '@/lib/tool-definitions'
12
+ import { NATIVE_CAPABILITY_PROVIDER_IDS, NON_LANGGRAPH_PROVIDER_IDS } from '@/lib/provider-sets'
11
13
 
12
- const AVAILABLE_TOOLS: { id: string; label: string; description: string }[] = [
13
- { id: 'shell', label: 'Shell', description: 'Execute commands in the working directory' },
14
- { id: 'files', label: 'Files', description: 'Read, write, list, move, copy, and send files' },
15
- { id: 'copy_file', label: 'Copy File', description: 'Copy files within the working directory' },
16
- { id: 'move_file', label: 'Move File', description: 'Move/rename files within the working directory' },
17
- { id: 'delete_file', label: 'Delete File', description: 'Delete files/directories (disabled by default)' },
18
- { id: 'edit_file', label: 'Edit File', description: 'Search-and-replace editing within files' },
19
- { id: 'process', label: 'Process', description: 'Monitor and control long-running shell commands' },
20
- { id: 'web_search', label: 'Web Search', description: 'Search the web via DuckDuckGo' },
21
- { id: 'web_fetch', label: 'Web Fetch', description: 'Fetch and extract text from URLs' },
22
- { id: 'claude_code', label: 'Claude Code', description: 'Delegate complex tasks to Claude Code CLI' },
23
- { id: 'codex_cli', label: 'Codex CLI', description: 'Delegate complex tasks to OpenAI Codex CLI' },
24
- { id: 'opencode_cli', label: 'OpenCode CLI', description: 'Delegate complex tasks to OpenCode CLI' },
25
- { id: 'browser', label: 'Browser', description: 'Playwright — browse, scrape, interact with web pages' },
26
- { id: 'memory', label: 'Memory', description: 'Store and retrieve long-term memories across sessions' },
27
- ]
14
+ const HB_PRESETS = [30, 60, 120, 300, 600, 1800, 3600] as const
28
15
 
29
- const PLATFORM_TOOLS: { id: string; label: string; description: string }[] = [
30
- { id: 'manage_agents', label: 'Agents', description: 'Create, edit, and delete agents' },
31
- { id: 'manage_tasks', label: 'Tasks', description: 'Create, edit, and delete tasks' },
32
- { id: 'manage_schedules', label: 'Schedules', description: 'Create, edit, and delete schedules' },
33
- { id: 'manage_skills', label: 'Skills', description: 'Create, edit, and delete skills' },
34
- { id: 'manage_documents', label: 'Documents', description: 'Upload, search, and delete indexed documents' },
35
- { id: 'manage_webhooks', label: 'Webhooks', description: 'Register webhooks that trigger agent sessions' },
36
- { id: 'manage_connectors', label: 'Connectors', description: 'Create, edit, and delete connectors' },
37
- { id: 'manage_sessions', label: 'Sessions', description: 'List sessions, send messages, and spawn session work' },
38
- { id: 'manage_secrets', label: 'Secrets', description: 'Store and retrieve encrypted service secrets' },
39
- ]
16
+ function formatHbDuration(sec: number): string {
17
+ if (sec >= 3600) {
18
+ const h = Math.floor(sec / 3600)
19
+ const m = Math.floor((sec % 3600) / 60)
20
+ return m > 0 ? `${h}h${m}m` : `${h}h`
21
+ }
22
+ if (sec >= 60) return `${Math.floor(sec / 60)}m`
23
+ return `${sec}s`
24
+ }
40
25
 
41
- const NATIVE_CAPABILITY_PROVIDER_IDS = new Set<ProviderType>(['claude-cli', 'codex-cli', 'opencode-cli', 'openclaw'])
26
+ /** Parse a stored heartbeatInterval string or heartbeatIntervalSec number to a select-friendly string of seconds */
27
+ function parseDurationToSec(interval: string | number | null | undefined, intervalSec: number | null | undefined): string {
28
+ if (intervalSec != null && Number.isFinite(intervalSec) && intervalSec > 0) {
29
+ // Snap to nearest preset if close, otherwise use raw value
30
+ const closest = HB_PRESETS.find((p) => p === Math.round(intervalSec))
31
+ if (closest) return String(closest)
32
+ }
33
+ if (typeof interval === 'number' && Number.isFinite(interval) && interval > 0) {
34
+ return String(Math.round(interval))
35
+ }
36
+ if (interval != null && typeof interval === 'string' && interval.trim()) {
37
+ const t = interval.trim().toLowerCase()
38
+ const n = Number(t)
39
+ if (Number.isFinite(n) && n > 0) return String(Math.round(n))
40
+ const m = t.match(/^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s?)?$/)
41
+ if (m && (m[1] || m[2] || m[3])) {
42
+ const total = (m[1] ? parseInt(m[1]) * 3600 : 0) + (m[2] ? parseInt(m[2]) * 60 : 0) + (m[3] ? parseInt(m[3]) : 0)
43
+ if (total > 0) return String(total)
44
+ }
45
+ }
46
+ return '' // default
47
+ }
42
48
 
43
49
  export function AgentSheet() {
44
50
  const open = useAppStore((s) => s.agentSheetOpen)
@@ -47,6 +53,8 @@ export function AgentSheet() {
47
53
  const setEditingId = useAppStore((s) => s.setEditingAgentId)
48
54
  const agents = useAppStore((s) => s.agents)
49
55
  const loadAgents = useAppStore((s) => s.loadAgents)
56
+ const projects = useAppStore((s) => s.projects)
57
+ const loadProjects = useAppStore((s) => s.loadProjects)
50
58
  const providers = useAppStore((s) => s.providers)
51
59
  const loadProviders = useAppStore((s) => s.loadProviders)
52
60
  const credentials = useAppStore((s) => s.credentials)
@@ -54,9 +62,6 @@ export function AgentSheet() {
54
62
  const dynamicSkills = useAppStore((s) => s.skills)
55
63
  const mcpServers = useAppStore((s) => s.mcpServers)
56
64
  const loadSkills = useAppStore((s) => s.loadSkills)
57
- const appSettings = useAppStore((s) => s.appSettings)
58
- const loadSettings = useAppStore((s) => s.loadSettings)
59
-
60
65
  const [claudeSkills, setClaudeSkills] = useState<ClaudeSkill[]>([])
61
66
  const [claudeSkillsLoading, setClaudeSkillsLoading] = useState(false)
62
67
  const loadClaudeSkills = async () => {
@@ -91,6 +96,12 @@ export function AgentSheet() {
91
96
  const [capInput, setCapInput] = useState('')
92
97
  const [ollamaMode, setOllamaMode] = useState<'local' | 'cloud'>('local')
93
98
  const [openclawEnabled, setOpenclawEnabled] = useState(false)
99
+ const [projectId, setProjectId] = useState<string | undefined>(undefined)
100
+ const [thinkingLevel, setThinkingLevel] = useState<'' | 'minimal' | 'low' | 'medium' | 'high'>('')
101
+ const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
102
+ const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
103
+ const [heartbeatModel, setHeartbeatModel] = useState('')
104
+ const [heartbeatPrompt, setHeartbeatPrompt] = useState('')
94
105
  const [addingKey, setAddingKey] = useState(false)
95
106
  const [newKeyName, setNewKeyName] = useState('')
96
107
  const [newKeyValue, setNewKeyValue] = useState('')
@@ -117,12 +128,6 @@ export function AgentSheet() {
117
128
  e.target.value = ''
118
129
  }
119
130
 
120
- // AI generation state
121
- const [aiPrompt, setAiPrompt] = useState('')
122
- const [generating, setGenerating] = useState(false)
123
- const [generated, setGenerated] = useState(false)
124
- const [genError, setGenError] = useState('')
125
-
126
131
  const currentProvider = providers.find((p) => p.id === provider)
127
132
  const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
128
133
  const openclawCredentials = Object.values(credentials).filter((c) => c.provider === 'openclaw')
@@ -139,12 +144,8 @@ export function AgentSheet() {
139
144
  loadProviders()
140
145
  loadCredentials()
141
146
  loadSkills()
147
+ loadProjects()
142
148
  loadClaudeSkills()
143
- loadSettings()
144
- setAiPrompt('')
145
- setGenerating(false)
146
- setGenerated(false)
147
- setGenError('')
148
149
  setTestStatus('idle')
149
150
  setTestMessage('')
150
151
  if (editing) {
@@ -169,6 +170,12 @@ export function AgentSheet() {
169
170
  setCapInput('')
170
171
  setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
171
172
  setOpenclawEnabled(editing.provider === 'openclaw')
173
+ setProjectId(editing.projectId)
174
+ setThinkingLevel(editing.thinkingLevel || '')
175
+ setHeartbeatEnabled(editing.heartbeatEnabled || false)
176
+ setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
177
+ setHeartbeatModel(editing.heartbeatModel || '')
178
+ setHeartbeatPrompt(editing.heartbeatPrompt || '')
172
179
  } else {
173
180
  setName('')
174
181
  setDescription('')
@@ -190,6 +197,12 @@ export function AgentSheet() {
190
197
  setCapInput('')
191
198
  setOllamaMode('local')
192
199
  setOpenclawEnabled(false)
200
+ setProjectId(undefined)
201
+ setThinkingLevel('')
202
+ setHeartbeatEnabled(false)
203
+ setHeartbeatIntervalSec('')
204
+ setHeartbeatModel('')
205
+ setHeartbeatPrompt('')
193
206
  }
194
207
  }
195
208
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -246,29 +259,6 @@ export function AgentSheet() {
246
259
  return () => { cancelled = true }
247
260
  }, [openclawEnabled])
248
261
 
249
- const handleGenerate = async () => {
250
- if (!aiPrompt.trim()) return
251
- setGenerating(true)
252
- setGenError('')
253
- try {
254
- const result = await api<{ name?: string; description?: string; systemPrompt?: string; isOrchestrator?: boolean; error?: string }>('POST', '/agents/generate', { prompt: aiPrompt })
255
- if (result.error) {
256
- setGenError(result.error)
257
- } else if (result.name || result.systemPrompt) {
258
- if (result.name) setName(result.name)
259
- if (result.description) setDescription(result.description)
260
- if (result.systemPrompt) setSystemPrompt(result.systemPrompt)
261
- if (result.isOrchestrator !== undefined) setIsOrchestrator(result.isOrchestrator)
262
- setGenerated(true)
263
- } else {
264
- setGenError('AI returned empty response — try again')
265
- }
266
- } catch (err: unknown) {
267
- setGenError(err instanceof Error ? err.message : 'Generation failed')
268
- }
269
- setGenerating(false)
270
- }
271
-
272
262
  const onClose = () => {
273
263
  setOpen(false)
274
264
  setEditingId(null)
@@ -300,6 +290,13 @@ export function AgentSheet() {
300
290
  fallbackCredentialIds,
301
291
  platformAssignScope,
302
292
  capabilities,
293
+ projectId: projectId || undefined,
294
+ thinkingLevel: thinkingLevel || undefined,
295
+ heartbeatEnabled,
296
+ heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
297
+ heartbeatIntervalSec: heartbeatIntervalSec ? Number(heartbeatIntervalSec) : null,
298
+ heartbeatModel: heartbeatModel.trim() || null,
299
+ heartbeatPrompt: heartbeatPrompt.trim() || null,
303
300
  }
304
301
  if (editing) {
305
302
  await updateAgent(editing.id, data)
@@ -383,8 +380,7 @@ export function AgentSheet() {
383
380
 
384
381
  // Whether this provider needs a connection test before saving.
385
382
  // Only CLI providers (no remote connection) skip the test.
386
- const CLI_ONLY_PROVIDERS: Set<ProviderType> = new Set(['claude-cli', 'codex-cli', 'opencode-cli'])
387
- const needsTest = !providerNeedsKey && !CLI_ONLY_PROVIDERS.has(provider)
383
+ const needsTest = !providerNeedsKey && !NON_LANGGRAPH_PROVIDER_IDS.has(provider)
388
384
 
389
385
  const [saving, setSaving] = useState(false)
390
386
 
@@ -450,14 +446,6 @@ export function AgentSheet() {
450
446
  </div>
451
447
  </div>
452
448
 
453
- {/* AI Generation */}
454
- {!editing && !openclawEnabled && <AiGenBlock
455
- aiPrompt={aiPrompt} setAiPrompt={setAiPrompt}
456
- generating={generating} generated={generated} genError={genError}
457
- onGenerate={handleGenerate} appSettings={appSettings}
458
- placeholder='Describe the agent you want, e.g. "An SEO keyword researcher that finds low-competition long-tail keywords"'
459
- />}
460
-
461
449
  <div className="mb-8">
462
450
  <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
463
451
  <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
@@ -512,6 +500,100 @@ export function AgentSheet() {
512
500
  <p className="text-[11px] text-text-3/70 mt-1.5">Press Enter or comma to add. Other agents see these when deciding delegation.</p>
513
501
  </div>}
514
502
 
503
+ {/* Project */}
504
+ {Object.keys(projects).length > 0 && (
505
+ <div className="mb-8">
506
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
507
+ Project <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
508
+ </label>
509
+ <select
510
+ value={projectId || ''}
511
+ onChange={(e) => setProjectId(e.target.value || undefined)}
512
+ className={inputClass}
513
+ style={{ fontFamily: 'inherit' }}
514
+ >
515
+ <option value="">None</option>
516
+ {Object.values(projects).map((p) => (
517
+ <option key={p.id} value={p.id}>{p.name}</option>
518
+ ))}
519
+ </select>
520
+ </div>
521
+ )}
522
+
523
+ {/* Thinking Level */}
524
+ <div className="mb-8">
525
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
526
+ Thinking Level <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
527
+ </label>
528
+ <select
529
+ value={thinkingLevel}
530
+ onChange={(e) => setThinkingLevel(e.target.value as typeof thinkingLevel)}
531
+ className={inputClass}
532
+ style={{ fontFamily: 'inherit' }}
533
+ >
534
+ <option value="">None (default)</option>
535
+ <option value="minimal">Minimal — Direct and concise</option>
536
+ <option value="low">Low — Brief reasoning</option>
537
+ <option value="medium">Medium — Moderate analysis</option>
538
+ <option value="high">High — Deep, thorough reasoning</option>
539
+ </select>
540
+ <p className="text-[11px] text-text-3/70 mt-1.5">Controls reasoning depth. Anthropic models use extended thinking; OpenAI o-series uses reasoning_effort. Others get system prompt guidance.</p>
541
+ </div>
542
+
543
+ {/* Heartbeat Configuration */}
544
+ <div className="mb-8">
545
+ <div className="flex items-center justify-between mb-3">
546
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">Heartbeat</label>
547
+ <button
548
+ type="button"
549
+ onClick={() => setHeartbeatEnabled(!heartbeatEnabled)}
550
+ className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${heartbeatEnabled ? 'bg-accent' : 'bg-white/[0.12]'}`}
551
+ >
552
+ <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${heartbeatEnabled ? 'translate-x-[18px]' : ''}`} />
553
+ </button>
554
+ </div>
555
+ {heartbeatEnabled && (
556
+ <div className="space-y-4 mt-3">
557
+ <div>
558
+ <label className="block text-[12px] text-text-3/70 mb-1.5">Interval</label>
559
+ <select
560
+ value={heartbeatIntervalSec}
561
+ onChange={(e) => setHeartbeatIntervalSec(e.target.value)}
562
+ className={inputClass}
563
+ >
564
+ <option value="">Default (30m)</option>
565
+ {HB_PRESETS.map((sec) => (
566
+ <option key={sec} value={String(sec)}>{formatHbDuration(sec)}</option>
567
+ ))}
568
+ </select>
569
+ </div>
570
+ <div>
571
+ <label className="block text-[12px] text-text-3/70 mb-1.5">Model override <span className="text-text-3/50">(optional, cheaper model)</span></label>
572
+ <input
573
+ type="text"
574
+ value={heartbeatModel}
575
+ onChange={(e) => setHeartbeatModel(e.target.value)}
576
+ placeholder="e.g. gpt-4o-mini"
577
+ className={inputClass}
578
+ style={{ fontFamily: 'inherit' }}
579
+ />
580
+ </div>
581
+ <div>
582
+ <label className="block text-[12px] text-text-3/70 mb-1.5">Instructions <span className="text-text-3/50">(what to do each tick)</span></label>
583
+ <textarea
584
+ value={heartbeatPrompt}
585
+ onChange={(e) => setHeartbeatPrompt(e.target.value)}
586
+ placeholder="Describe what this agent should do during heartbeat ticks..."
587
+ rows={4}
588
+ className={`${inputClass} resize-y min-h-[100px]`}
589
+ style={{ fontFamily: 'inherit' }}
590
+ />
591
+ </div>
592
+ </div>
593
+ )}
594
+ <p className="text-[11px] text-text-3/70 mt-1.5">Periodic check-in runs on idle sessions using this agent. Processes pending events and monitors status.</p>
595
+ </div>
596
+
515
597
  {provider !== 'openclaw' && (
516
598
  <div className="mb-8">
517
599
  <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
@@ -774,11 +856,14 @@ export function AgentSheet() {
774
856
  {!openclawEnabled && currentProvider && currentProvider.models.length > 0 && (
775
857
  <div className="mb-8">
776
858
  <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Model</label>
777
- <select value={model} onChange={(e) => setModel(e.target.value)} className={`${inputClass} appearance-none cursor-pointer`} style={{ fontFamily: 'inherit' }}>
778
- {currentProvider.models.map((m) => (
779
- <option key={m} value={m}>{m}</option>
780
- ))}
781
- </select>
859
+ <ModelCombobox
860
+ providerId={currentProvider.id}
861
+ value={model}
862
+ onChange={setModel}
863
+ models={currentProvider.models}
864
+ defaultModels={currentProvider.defaultModels}
865
+ className={`${inputClass} cursor-pointer`}
866
+ />
782
867
  </div>
783
868
  )}
784
869