@swarmclawai/swarmclaw 0.7.2 → 0.7.4

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 (274) hide show
  1. package/README.md +116 -50
  2. package/bin/package-manager.js +157 -0
  3. package/bin/package-manager.test.js +90 -0
  4. package/bin/server-cmd.js +38 -7
  5. package/bin/swarmclaw.js +54 -4
  6. package/bin/update-cmd.js +48 -10
  7. package/bin/update-cmd.test.js +55 -0
  8. package/package.json +8 -3
  9. package/scripts/postinstall.mjs +26 -0
  10. package/src/app/api/agents/[id]/route.ts +43 -0
  11. package/src/app/api/agents/[id]/thread/route.ts +39 -8
  12. package/src/app/api/agents/route.ts +35 -2
  13. package/src/app/api/auth/route.ts +77 -8
  14. package/src/app/api/chatrooms/[id]/chat/route.ts +22 -6
  15. package/src/app/api/chatrooms/[id]/pins/route.ts +2 -1
  16. package/src/app/api/chatrooms/[id]/reactions/route.ts +2 -1
  17. package/src/app/api/chatrooms/[id]/route.ts +6 -0
  18. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  19. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  20. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  21. package/src/app/api/chats/[id]/route.ts +30 -0
  22. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  23. package/src/app/api/chats/heartbeat/route.ts +2 -1
  24. package/src/app/api/chats/route.ts +23 -1
  25. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  26. package/src/app/api/connectors/doctor/route.ts +13 -0
  27. package/src/app/api/external-agents/[id]/heartbeat/route.ts +33 -0
  28. package/src/app/api/external-agents/[id]/route.ts +31 -0
  29. package/src/app/api/external-agents/register/route.ts +3 -0
  30. package/src/app/api/external-agents/route.ts +66 -0
  31. package/src/app/api/files/open/route.ts +16 -14
  32. package/src/app/api/gateways/[id]/health/route.ts +28 -0
  33. package/src/app/api/gateways/[id]/route.ts +79 -0
  34. package/src/app/api/gateways/route.ts +57 -0
  35. package/src/app/api/memory/maintenance/route.ts +11 -1
  36. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  37. package/src/app/api/openclaw/gateway/route.ts +10 -7
  38. package/src/app/api/openclaw/skills/route.ts +12 -4
  39. package/src/app/api/plugins/dependencies/route.ts +24 -0
  40. package/src/app/api/plugins/install/route.ts +15 -92
  41. package/src/app/api/plugins/route.ts +3 -26
  42. package/src/app/api/plugins/settings/route.ts +17 -12
  43. package/src/app/api/plugins/ui/route.ts +1 -0
  44. package/src/app/api/providers/[id]/discover-models/route.ts +27 -0
  45. package/src/app/api/schedules/[id]/route.ts +38 -9
  46. package/src/app/api/schedules/route.ts +51 -28
  47. package/src/app/api/settings/route.ts +55 -17
  48. package/src/app/api/setup/doctor/route.ts +6 -4
  49. package/src/app/api/tasks/[id]/route.ts +16 -6
  50. package/src/app/api/tasks/bulk/route.ts +3 -3
  51. package/src/app/api/tasks/route.ts +9 -4
  52. package/src/app/api/webhooks/[id]/route.ts +8 -1
  53. package/src/app/page.tsx +135 -17
  54. package/src/cli/binary.test.js +142 -0
  55. package/src/cli/index.js +38 -11
  56. package/src/cli/index.test.js +195 -0
  57. package/src/cli/index.ts +21 -12
  58. package/src/cli/server-cmd.test.js +59 -0
  59. package/src/cli/spec.js +20 -2
  60. package/src/components/agents/agent-card.tsx +15 -12
  61. package/src/components/agents/agent-chat-list.tsx +101 -1
  62. package/src/components/agents/agent-list.tsx +46 -9
  63. package/src/components/agents/agent-sheet.tsx +456 -23
  64. package/src/components/agents/inspector-panel.tsx +110 -49
  65. package/src/components/agents/sandbox-env-panel.tsx +4 -1
  66. package/src/components/auth/access-key-gate.tsx +36 -97
  67. package/src/components/auth/setup-wizard.tsx +970 -275
  68. package/src/components/chat/chat-area.tsx +70 -27
  69. package/src/components/chat/chat-card.tsx +6 -21
  70. package/src/components/chat/chat-header.tsx +263 -366
  71. package/src/components/chat/chat-list.tsx +62 -26
  72. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  73. package/src/components/chat/message-list.tsx +145 -19
  74. package/src/components/chatrooms/chatroom-input.tsx +96 -33
  75. package/src/components/chatrooms/chatroom-list.tsx +141 -72
  76. package/src/components/chatrooms/chatroom-message.tsx +7 -6
  77. package/src/components/chatrooms/chatroom-sheet.tsx +13 -1
  78. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +5 -2
  79. package/src/components/chatrooms/chatroom-view.tsx +422 -209
  80. package/src/components/chatrooms/reaction-picker.tsx +38 -33
  81. package/src/components/connectors/connector-list.tsx +265 -127
  82. package/src/components/connectors/connector-sheet.tsx +217 -0
  83. package/src/components/gateways/gateway-sheet.tsx +567 -0
  84. package/src/components/home/home-view.tsx +128 -4
  85. package/src/components/input/chat-input.tsx +135 -86
  86. package/src/components/layout/app-layout.tsx +385 -194
  87. package/src/components/layout/mobile-header.tsx +26 -8
  88. package/src/components/memory/memory-browser.tsx +71 -6
  89. package/src/components/memory/memory-card.tsx +18 -0
  90. package/src/components/memory/memory-detail.tsx +58 -31
  91. package/src/components/memory/memory-sheet.tsx +32 -4
  92. package/src/components/plugins/plugin-list.tsx +15 -3
  93. package/src/components/plugins/plugin-sheet.tsx +118 -9
  94. package/src/components/projects/project-detail.tsx +189 -1
  95. package/src/components/providers/provider-list.tsx +158 -2
  96. package/src/components/providers/provider-sheet.tsx +81 -70
  97. package/src/components/shared/agent-picker-list.tsx +2 -2
  98. package/src/components/shared/bottom-sheet.tsx +31 -15
  99. package/src/components/shared/command-palette.tsx +111 -24
  100. package/src/components/shared/confirm-dialog.tsx +45 -30
  101. package/src/components/shared/model-combobox.tsx +90 -8
  102. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  103. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  104. package/src/components/shared/settings/section-heartbeat.tsx +88 -6
  105. package/src/components/shared/settings/section-orchestrator.tsx +6 -3
  106. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  107. package/src/components/shared/settings/section-secrets.tsx +6 -6
  108. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  109. package/src/components/shared/settings/section-voice.tsx +5 -1
  110. package/src/components/shared/settings/section-web-search.tsx +10 -2
  111. package/src/components/shared/settings/settings-page.tsx +248 -47
  112. package/src/components/tasks/approvals-panel.tsx +211 -18
  113. package/src/components/tasks/task-board.tsx +242 -46
  114. package/src/components/ui/dialog.tsx +2 -2
  115. package/src/components/usage/metrics-dashboard.tsx +74 -1
  116. package/src/components/wallets/wallet-approval-dialog.tsx +59 -54
  117. package/src/components/wallets/wallet-panel.tsx +17 -5
  118. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  119. package/src/lib/auth.ts +17 -0
  120. package/src/lib/chat-streaming-state.test.ts +108 -0
  121. package/src/lib/chat-streaming-state.ts +108 -0
  122. package/src/lib/heartbeat-defaults.ts +48 -0
  123. package/src/lib/memory-presentation.ts +59 -0
  124. package/src/lib/openclaw-agent-id.test.ts +14 -0
  125. package/src/lib/openclaw-agent-id.ts +31 -0
  126. package/src/lib/provider-model-discovery-client.ts +29 -0
  127. package/src/lib/providers/index.ts +12 -5
  128. package/src/lib/runtime-loop.ts +105 -3
  129. package/src/lib/safe-storage.ts +6 -1
  130. package/src/lib/server/agent-assignment.test.ts +112 -0
  131. package/src/lib/server/agent-assignment.ts +169 -0
  132. package/src/lib/server/agent-runtime-config.test.ts +141 -0
  133. package/src/lib/server/agent-runtime-config.ts +277 -0
  134. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  135. package/src/lib/server/approvals-auto-approve.test.ts +264 -0
  136. package/src/lib/server/approvals.ts +483 -75
  137. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  138. package/src/lib/server/browser-state.test.ts +118 -0
  139. package/src/lib/server/browser-state.ts +123 -0
  140. package/src/lib/server/build-llm.test.ts +44 -0
  141. package/src/lib/server/build-llm.ts +11 -4
  142. package/src/lib/server/builtin-plugins.ts +34 -0
  143. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  144. package/src/lib/server/chat-execution-tool-events.test.ts +219 -0
  145. package/src/lib/server/chat-execution.ts +402 -125
  146. package/src/lib/server/chatroom-health.test.ts +26 -0
  147. package/src/lib/server/chatroom-health.ts +2 -3
  148. package/src/lib/server/chatroom-helpers.test.ts +74 -2
  149. package/src/lib/server/chatroom-helpers.ts +144 -11
  150. package/src/lib/server/chatroom-session-persistence.test.ts +87 -0
  151. package/src/lib/server/connectors/discord.ts +175 -11
  152. package/src/lib/server/connectors/doctor.test.ts +80 -0
  153. package/src/lib/server/connectors/doctor.ts +116 -0
  154. package/src/lib/server/connectors/manager.ts +994 -130
  155. package/src/lib/server/connectors/policy.test.ts +222 -0
  156. package/src/lib/server/connectors/policy.ts +452 -0
  157. package/src/lib/server/connectors/slack.ts +189 -10
  158. package/src/lib/server/connectors/telegram.ts +65 -15
  159. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  160. package/src/lib/server/connectors/thread-context.ts +72 -0
  161. package/src/lib/server/connectors/types.ts +41 -11
  162. package/src/lib/server/daemon-state.ts +62 -3
  163. package/src/lib/server/data-dir.ts +13 -0
  164. package/src/lib/server/delegation-jobs.test.ts +140 -0
  165. package/src/lib/server/delegation-jobs.ts +248 -0
  166. package/src/lib/server/document-utils.test.ts +47 -0
  167. package/src/lib/server/document-utils.ts +397 -0
  168. package/src/lib/server/eval/agent-regression.test.ts +47 -0
  169. package/src/lib/server/eval/agent-regression.ts +1742 -0
  170. package/src/lib/server/eval/runner.ts +11 -1
  171. package/src/lib/server/eval/store.ts +2 -1
  172. package/src/lib/server/heartbeat-service.ts +23 -43
  173. package/src/lib/server/heartbeat-source.test.ts +22 -0
  174. package/src/lib/server/heartbeat-source.ts +7 -0
  175. package/src/lib/server/identity-continuity.test.ts +77 -0
  176. package/src/lib/server/identity-continuity.ts +127 -0
  177. package/src/lib/server/mailbox-utils.ts +347 -0
  178. package/src/lib/server/main-agent-loop.ts +31 -964
  179. package/src/lib/server/memory-db.ts +4 -6
  180. package/src/lib/server/memory-tiers.ts +40 -0
  181. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  182. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  183. package/src/lib/server/openclaw-exec-config.ts +6 -5
  184. package/src/lib/server/openclaw-gateway.ts +123 -36
  185. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  186. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  187. package/src/lib/server/openclaw-sync.ts +3 -2
  188. package/src/lib/server/orchestrator-lg.ts +18 -8
  189. package/src/lib/server/orchestrator.ts +5 -4
  190. package/src/lib/server/playwright-proxy.mjs +27 -3
  191. package/src/lib/server/plugins.test.ts +215 -0
  192. package/src/lib/server/plugins.ts +832 -69
  193. package/src/lib/server/provider-health.ts +33 -3
  194. package/src/lib/server/provider-model-discovery.ts +481 -0
  195. package/src/lib/server/queue.ts +4 -21
  196. package/src/lib/server/runtime-settings.test.ts +119 -0
  197. package/src/lib/server/runtime-settings.ts +12 -92
  198. package/src/lib/server/schedule-normalization.ts +187 -0
  199. package/src/lib/server/scheduler.ts +2 -0
  200. package/src/lib/server/session-archive-memory.test.ts +85 -0
  201. package/src/lib/server/session-archive-memory.ts +230 -0
  202. package/src/lib/server/session-mailbox.ts +8 -18
  203. package/src/lib/server/session-reset-policy.test.ts +99 -0
  204. package/src/lib/server/session-reset-policy.ts +311 -0
  205. package/src/lib/server/session-run-manager.ts +33 -80
  206. package/src/lib/server/session-tools/autonomy-tools.test.ts +128 -0
  207. package/src/lib/server/session-tools/calendar.ts +2 -12
  208. package/src/lib/server/session-tools/connector.ts +109 -8
  209. package/src/lib/server/session-tools/context.ts +14 -2
  210. package/src/lib/server/session-tools/crawl.ts +447 -0
  211. package/src/lib/server/session-tools/crud.ts +96 -34
  212. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  213. package/src/lib/server/session-tools/delegate.ts +406 -20
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +170 -0
  215. package/src/lib/server/session-tools/discovery.ts +40 -12
  216. package/src/lib/server/session-tools/document.ts +283 -0
  217. package/src/lib/server/session-tools/email.ts +1 -3
  218. package/src/lib/server/session-tools/extract.ts +137 -0
  219. package/src/lib/server/session-tools/file-normalize.test.ts +98 -0
  220. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  221. package/src/lib/server/session-tools/file.ts +243 -24
  222. package/src/lib/server/session-tools/http.ts +9 -3
  223. package/src/lib/server/session-tools/human-loop.ts +227 -0
  224. package/src/lib/server/session-tools/image-gen.ts +1 -3
  225. package/src/lib/server/session-tools/index.ts +87 -2
  226. package/src/lib/server/session-tools/mailbox.ts +276 -0
  227. package/src/lib/server/session-tools/manage-schedules.test.ts +137 -0
  228. package/src/lib/server/session-tools/memory.ts +35 -3
  229. package/src/lib/server/session-tools/monitor.ts +162 -12
  230. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  231. package/src/lib/server/session-tools/openclaw-nodes.test.ts +111 -0
  232. package/src/lib/server/session-tools/openclaw-nodes.ts +86 -20
  233. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  234. package/src/lib/server/session-tools/platform.ts +142 -4
  235. package/src/lib/server/session-tools/plugin-creator.ts +95 -25
  236. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  237. package/src/lib/server/session-tools/replicate.ts +1 -3
  238. package/src/lib/server/session-tools/sandbox.ts +51 -92
  239. package/src/lib/server/session-tools/schedule.ts +20 -10
  240. package/src/lib/server/session-tools/session-info.ts +58 -4
  241. package/src/lib/server/session-tools/session-tools-wiring.test.ts +54 -17
  242. package/src/lib/server/session-tools/shell.ts +2 -2
  243. package/src/lib/server/session-tools/subagent.ts +195 -27
  244. package/src/lib/server/session-tools/table.ts +587 -0
  245. package/src/lib/server/session-tools/wallet.ts +13 -10
  246. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  247. package/src/lib/server/session-tools/web.ts +947 -108
  248. package/src/lib/server/storage.ts +255 -10
  249. package/src/lib/server/stream-agent-chat.test.ts +61 -0
  250. package/src/lib/server/stream-agent-chat.ts +185 -25
  251. package/src/lib/server/structured-extract.test.ts +72 -0
  252. package/src/lib/server/structured-extract.ts +373 -0
  253. package/src/lib/server/task-mention.test.ts +16 -2
  254. package/src/lib/server/task-mention.ts +61 -11
  255. package/src/lib/server/tool-aliases.ts +80 -12
  256. package/src/lib/server/tool-capability-policy.ts +7 -1
  257. package/src/lib/server/tool-retry.ts +2 -0
  258. package/src/lib/server/watch-jobs.test.ts +173 -0
  259. package/src/lib/server/watch-jobs.ts +532 -0
  260. package/src/lib/server/ws-hub.ts +5 -3
  261. package/src/lib/setup-defaults.ts +352 -11
  262. package/src/lib/tool-definitions.ts +3 -4
  263. package/src/lib/validation/schemas.test.ts +26 -0
  264. package/src/lib/validation/schemas.ts +62 -1
  265. package/src/lib/ws-client.ts +14 -12
  266. package/src/proxy.ts +5 -5
  267. package/src/stores/use-app-store.ts +43 -7
  268. package/src/stores/use-chat-store.ts +31 -2
  269. package/src/stores/use-chatroom-store.ts +153 -26
  270. package/src/types/index.ts +470 -44
  271. package/src/app/api/chats/[id]/main-loop/route.ts +0 -94
  272. package/src/components/chat/new-chat-sheet.tsx +0 -253
  273. package/src/lib/server/main-session.ts +0 -17
  274. package/src/lib/server/session-run-manager.test.ts +0 -26
@@ -1,8 +1,9 @@
1
1
  'use client'
2
2
 
3
- import { useState, useRef, useEffect, useCallback } from 'react'
3
+ import { useState, useRef, useEffect, useCallback, useEffectEvent } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
+ import { fetchProviderModelDiscovery } from '@/lib/provider-model-discovery-client'
6
7
 
7
8
  interface ModelComboboxProps {
8
9
  providerId: string
@@ -10,6 +11,9 @@ interface ModelComboboxProps {
10
11
  onChange: (model: string) => void
11
12
  models: string[]
12
13
  defaultModels?: string[]
14
+ credentialId?: string | null
15
+ apiEndpoint?: string | null
16
+ supportsDiscovery?: boolean
13
17
  className?: string
14
18
  }
15
19
 
@@ -19,20 +23,31 @@ export function ModelCombobox({
19
23
  onChange,
20
24
  models,
21
25
  defaultModels = [],
26
+ credentialId,
27
+ apiEndpoint,
28
+ supportsDiscovery = true,
22
29
  className,
23
30
  }: ModelComboboxProps) {
24
31
  const [open, setOpen] = useState(false)
25
32
  const [query, setQuery] = useState('')
33
+ const [discoveredModels, setDiscoveredModels] = useState<string[]>([])
34
+ const [discoveryState, setDiscoveryState] = useState<'idle' | 'loading' | 'ready' | 'notice'>('idle')
35
+ const [discoveryMessage, setDiscoveryMessage] = useState('')
36
+ const [discoveryCached, setDiscoveryCached] = useState(false)
26
37
  const inputRef = useRef<HTMLInputElement>(null)
27
38
  const containerRef = useRef<HTMLDivElement>(null)
39
+ const lastDiscoveryKeyRef = useRef<string | null>(null)
28
40
  const loadProviders = useAppStore((s) => s.loadProviders)
29
41
 
30
- const filtered = query
31
- ? models.filter((m) => m.toLowerCase().includes(query.toLowerCase()))
32
- : models
42
+ const availableModels = [...models, ...discoveredModels].filter((model, index, source) => source.indexOf(model) === index)
43
+ const trimmedQuery = query.trim()
44
+ const filtered = trimmedQuery
45
+ ? availableModels.filter((m) => m.toLowerCase().includes(trimmedQuery.toLowerCase()))
46
+ : availableModels
33
47
 
34
- const isCustom = (m: string) => defaultModels.length > 0 && !defaultModels.includes(m)
35
- const showAdd = query && !models.some((m) => m.toLowerCase() === query.toLowerCase())
48
+ const isCustom = (m: string) => models.includes(m) && defaultModels.length > 0 && !defaultModels.includes(m)
49
+ const showAdd = trimmedQuery && !availableModels.some((m) => m.toLowerCase() === trimmedQuery.toLowerCase())
50
+ const discoveryKey = `${providerId}::${credentialId || ''}::${apiEndpoint?.trim() || ''}`
36
51
 
37
52
  const persistModels = useCallback(async (next: string[]) => {
38
53
  await api('PUT', `/providers/${providerId}/models`, { models: next })
@@ -65,6 +80,43 @@ export function ModelCombobox({
65
80
  setOpen(false)
66
81
  }, [onChange])
67
82
 
83
+ const loadDiscoveredModels = useCallback(async (force = false) => {
84
+ if (!supportsDiscovery) return
85
+ if (!force && lastDiscoveryKeyRef.current === discoveryKey) return
86
+ setDiscoveryState('loading')
87
+ setDiscoveryMessage('')
88
+ try {
89
+ const result = await fetchProviderModelDiscovery({
90
+ providerId,
91
+ credentialId,
92
+ endpoint: apiEndpoint,
93
+ force,
94
+ })
95
+ lastDiscoveryKeyRef.current = discoveryKey
96
+ setDiscoveryCached(result.cached)
97
+ setDiscoveredModels(result.models)
98
+ setDiscoveryState(result.ok ? 'ready' : 'notice')
99
+ setDiscoveryMessage(result.message || '')
100
+ } catch (error) {
101
+ const message = error instanceof Error ? error.message : 'Failed to load live models.'
102
+ setDiscoveryState('notice')
103
+ setDiscoveryMessage(message)
104
+ }
105
+ }, [apiEndpoint, credentialId, discoveryKey, providerId, supportsDiscovery])
106
+
107
+ const resetDiscoveryState = useEffectEvent(() => {
108
+ lastDiscoveryKeyRef.current = null
109
+ setDiscoveredModels([])
110
+ setDiscoveryState('idle')
111
+ setDiscoveryMessage('')
112
+ setDiscoveryCached(false)
113
+ })
114
+
115
+ const syncDiscoveryOnOpen = useEffectEvent(() => {
116
+ if (!open || !supportsDiscovery) return
117
+ void loadDiscoveredModels()
118
+ })
119
+
68
120
  useEffect(() => {
69
121
  const handler = (e: MouseEvent) => {
70
122
  if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
@@ -76,6 +128,14 @@ export function ModelCombobox({
76
128
  return () => document.removeEventListener('mousedown', handler)
77
129
  }, [])
78
130
 
131
+ useEffect(() => {
132
+ resetDiscoveryState()
133
+ }, [providerId, credentialId, apiEndpoint])
134
+
135
+ useEffect(() => {
136
+ syncDiscoveryOnOpen()
137
+ }, [open, supportsDiscovery, loadDiscoveredModels])
138
+
79
139
  return (
80
140
  <div ref={containerRef} className="relative">
81
141
  <div
@@ -105,6 +165,28 @@ export function ModelCombobox({
105
165
 
106
166
  {open && (
107
167
  <div className="absolute z-50 top-full left-0 right-0 mt-1 max-h-[240px] overflow-y-auto rounded-[12px] border border-white/[0.08] bg-surface-2 shadow-xl">
168
+ {supportsDiscovery && (
169
+ <div className="sticky top-0 z-[1] flex items-center justify-between gap-3 border-b border-white/[0.06] bg-surface-2/95 px-3 py-2 backdrop-blur">
170
+ <div className={`min-w-0 text-[11px] ${discoveryState === 'notice' ? 'text-text-3/80' : 'text-text-3/60'}`}>
171
+ {discoveryState === 'loading'
172
+ ? 'Checking live model catalog...'
173
+ : discoveryMessage || 'Type any model name or load the live catalog.'}
174
+ {discoveryCached && discoveryState === 'ready' ? ' Cached.' : ''}
175
+ </div>
176
+ <button
177
+ type="button"
178
+ onClick={(e) => {
179
+ e.stopPropagation()
180
+ void loadDiscoveredModels(true)
181
+ }}
182
+ disabled={discoveryState === 'loading'}
183
+ className="shrink-0 rounded-[7px] border border-white/[0.08] bg-white/[0.03] px-2 py-1 text-[10px] font-600 text-text-3/80 transition-colors hover:bg-white/[0.06] hover:text-text disabled:cursor-default disabled:opacity-60"
184
+ >
185
+ {discoveryState === 'loading' ? 'Loading...' : discoveredModels.length > 0 ? 'Refresh' : 'Fetch live'}
186
+ </button>
187
+ </div>
188
+ )}
189
+
108
190
  {filtered.map((m) => (
109
191
  <div
110
192
  key={m}
@@ -128,13 +210,13 @@ export function ModelCombobox({
128
210
 
129
211
  {showAdd && (
130
212
  <div
131
- onClick={() => addModel(query.trim())}
213
+ onClick={() => addModel(trimmedQuery)}
132
214
  className="flex items-center gap-2 px-3 py-2 text-[14px] cursor-pointer transition-colors hover:bg-white/[0.04] text-accent-bright border-t border-white/[0.06]"
133
215
  >
134
216
  <svg className="w-3.5 h-3.5 shrink-0" viewBox="0 0 16 16" fill="none">
135
217
  <path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
136
218
  </svg>
137
- <span className="truncate">Add &ldquo;{query.trim()}&rdquo;</span>
219
+ <span className="truncate">Add &ldquo;{trimmedQuery}&rdquo;</span>
138
220
  </div>
139
221
  )}
140
222
 
@@ -129,8 +129,8 @@ export function PluginManager() {
129
129
 
130
130
  const { corePlugins, installedPlugins } = useMemo(() => {
131
131
  return {
132
- corePlugins: plugins.filter(p => p.source === 'local'),
133
- installedPlugins: plugins.filter(p => p.source !== 'local')
132
+ corePlugins: plugins.filter(p => p.isBuiltin),
133
+ installedPlugins: plugins.filter(p => !p.isBuiltin)
134
134
  }
135
135
  }, [plugins])
136
136
 
@@ -155,6 +155,22 @@ export function PluginManager() {
155
155
  </div>
156
156
  <div className="text-[11px] font-mono text-text-3/40 truncate">{p.filename}</div>
157
157
  {p.description && <div className="text-[12px] text-text-3/70 mt-1 line-clamp-1">{p.description}</div>}
158
+ {p.hasDependencyManifest && (
159
+ <div className="mt-1.5 flex items-center gap-2 text-[10px] font-700 uppercase tracking-[0.08em]">
160
+ <span className="px-1.5 py-0.5 rounded bg-white/[0.04] text-text-3/70">
161
+ {p.dependencyCount ?? 0} deps
162
+ </span>
163
+ <span className={`px-1.5 py-0.5 rounded ${
164
+ p.dependencyInstallStatus === 'installed'
165
+ ? 'bg-emerald-500/10 text-emerald-400'
166
+ : p.dependencyInstallStatus === 'error'
167
+ ? 'bg-red-500/10 text-red-400'
168
+ : 'bg-amber-500/10 text-amber-400'
169
+ }`}>
170
+ {p.dependencyInstallStatus || 'ready'}
171
+ </span>
172
+ </div>
173
+ )}
158
174
  {p.autoDisabled && (
159
175
  <div className="text-[11px] text-amber-400/90 mt-1.5 p-2 rounded-[8px] bg-amber-500/[0.03] border border-amber-500/10">
160
176
  {p.lastFailureStage ? `Error at ${p.lastFailureStage}:` : 'Last error:'} {p.lastFailureError}
@@ -163,7 +179,7 @@ export function PluginManager() {
163
179
  </div>
164
180
 
165
181
  <div className="flex items-center gap-2">
166
- {p.source !== 'local' && (
182
+ {!p.isBuiltin && (
167
183
  <div className="flex items-center opacity-0 group-hover:opacity-100 transition-opacity">
168
184
  <button
169
185
  onClick={() => handleUpdateOne(p.filename)}
@@ -409,7 +425,7 @@ export function PluginManager() {
409
425
  </div>
410
426
  )}
411
427
  <p className="text-[11px] text-text-3/40 mt-6 leading-relaxed text-center italic">
412
- SwarmClaw supports standalone CommonJS plugins and OpenClaw activate/deactivate formats.
428
+ SwarmClaw supports `.js` / `.mjs` plugins, native SwarmClaw hooks/tools, and OpenClaw activate/register formats.
413
429
  </p>
414
430
  </div>
415
431
  )}
@@ -1,8 +1,20 @@
1
1
  'use client'
2
2
 
3
+ import type { ApprovalCategory } from '@/types'
3
4
  import type { SettingsSectionProps } from './types'
4
5
 
6
+ const APPROVAL_CATEGORY_OPTIONS: Array<{ id: ApprovalCategory; label: string; description: string }> = [
7
+ { id: 'tool_access', label: 'Plugin Access', description: 'Auto-enable requested plugins for a chat.' },
8
+ { id: 'plugin_scaffold', label: 'Plugin Scaffold', description: 'Auto-create plugin files requested by agents.' },
9
+ { id: 'plugin_install', label: 'Plugin Install', description: 'Auto-install plugins from approved URLs.' },
10
+ { id: 'human_loop', label: 'Human Approval Requests', description: 'Auto-approve ask-human approval prompts.' },
11
+ { id: 'wallet_transfer', label: 'Wallet Transfers', description: 'Auto-approve wallet send requests. High risk.' },
12
+ { id: 'task_tool', label: 'Task Tool Calls', description: 'Reserved for task-level approval flows.' },
13
+ ]
14
+
5
15
  export function CapabilityPolicySection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
16
+ const autoApproved = new Set(appSettings.approvalAutoApproveCategories || [])
17
+
6
18
  return (
7
19
  <div className="mb-10">
8
20
  <h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
@@ -34,6 +46,24 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
34
46
  </div>
35
47
 
36
48
  <div className="grid grid-cols-1 gap-4">
49
+ <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
50
+ <div className="flex items-center justify-between gap-4">
51
+ <div>
52
+ <div className="text-[12px] font-600 text-text-2">Platform Approvals</div>
53
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
54
+ Turn this off to auto-approve every approval request across the platform for maximum autonomy. Audit records are still kept.
55
+ </p>
56
+ </div>
57
+ <button
58
+ onClick={() => patchSettings({ approvalsEnabled: !(appSettings.approvalsEnabled ?? true) })}
59
+ className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.approvalsEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
60
+ aria-label="Toggle platform approvals"
61
+ >
62
+ <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.approvalsEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
63
+ </button>
64
+ </div>
65
+ </div>
66
+
37
67
  <div>
38
68
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Blocked Categories</label>
39
69
  <input
@@ -86,6 +116,81 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
86
116
  />
87
117
  <p className="text-[11px] text-text-3/60 mt-2">Use this to re-allow specific tool families when running in strict mode.</p>
88
118
  </div>
119
+
120
+ <div>
121
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Auto-Approve Workflow Requests</label>
122
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
123
+ {APPROVAL_CATEGORY_OPTIONS.map((option) => {
124
+ const checked = autoApproved.has(option.id)
125
+ return (
126
+ <label
127
+ key={option.id}
128
+ className={`rounded-[12px] border px-3 py-3 cursor-pointer transition-all ${
129
+ checked
130
+ ? 'border-accent-bright/30 bg-accent-soft/60'
131
+ : 'border-white/[0.06] bg-bg hover:bg-surface-2'
132
+ }`}
133
+ >
134
+ <div className="flex items-start gap-3">
135
+ <input
136
+ type="checkbox"
137
+ checked={checked}
138
+ onChange={(e) => {
139
+ const next = new Set(appSettings.approvalAutoApproveCategories || [])
140
+ if (e.target.checked) next.add(option.id)
141
+ else next.delete(option.id)
142
+ patchSettings({ approvalAutoApproveCategories: [...next] })
143
+ }}
144
+ className="mt-0.5"
145
+ />
146
+ <div>
147
+ <div className="text-[12px] font-600 text-text-2">{option.label}</div>
148
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">{option.description}</p>
149
+ </div>
150
+ </div>
151
+ </label>
152
+ )
153
+ })}
154
+ </div>
155
+ <p className="text-[11px] text-text-3/60 mt-2">
156
+ Auto-approved categories execute immediately instead of waiting in the Approvals queue. Leave high-risk categories off unless the user explicitly wants fully autonomous execution.
157
+ </p>
158
+ </div>
159
+
160
+ <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
161
+ <div className="flex items-center justify-between gap-4">
162
+ <div>
163
+ <div className="text-[12px] font-600 text-text-2">Connector Approval Reminders</div>
164
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
165
+ If an approval sits too long, let the agent send a one-time reminder over an active connector conversation it already has access to.
166
+ </p>
167
+ </div>
168
+ <button
169
+ onClick={() => patchSettings({ approvalConnectorNotifyEnabled: !(appSettings.approvalConnectorNotifyEnabled ?? true) })}
170
+ className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.approvalConnectorNotifyEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
171
+ aria-label="Toggle connector approval reminders"
172
+ >
173
+ <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.approvalConnectorNotifyEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
174
+ </button>
175
+ </div>
176
+ <div className="mt-4 max-w-[220px]">
177
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Reminder Delay (Sec)</label>
178
+ <input
179
+ type="number"
180
+ min={30}
181
+ max={86400}
182
+ value={appSettings.approvalConnectorNotifyDelaySec ?? 300}
183
+ onChange={(e) => {
184
+ const next = Number.parseInt(e.target.value, 10)
185
+ patchSettings({
186
+ approvalConnectorNotifyDelaySec: Number.isFinite(next) ? Math.max(30, Math.min(86400, next)) : 300,
187
+ })
188
+ }}
189
+ className={inputClass}
190
+ style={{ fontFamily: 'inherit' }}
191
+ />
192
+ </div>
193
+ </div>
89
194
  </div>
90
195
  </div>
91
196
  </div>
@@ -1,14 +1,24 @@
1
1
  'use client'
2
2
 
3
+ import {
4
+ DEFAULT_HEARTBEAT_ACK_MAX_CHARS,
5
+ DEFAULT_HEARTBEAT_SHOW_ALERTS,
6
+ DEFAULT_HEARTBEAT_SHOW_OK,
7
+ } from '@/lib/heartbeat-defaults'
3
8
  import { useState } from 'react'
4
9
  import { useAppStore } from '@/stores/use-app-store'
5
10
  import { api } from '@/lib/api-client'
11
+ import type { SessionResetMode } from '@/types'
6
12
  import type { SettingsSectionProps } from './types'
7
13
 
8
14
  export function HeartbeatSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
9
15
  const loadSessions = useAppStore((s) => s.loadSessions)
10
16
  const [disablingHeartbeats, setDisablingHeartbeats] = useState(false)
11
17
  const [heartbeatBulkNotice, setHeartbeatBulkNotice] = useState('')
18
+ const parseResetMode = (value: string): SessionResetMode | null => {
19
+ if (value === 'idle' || value === 'daily') return value
20
+ return null
21
+ }
12
22
 
13
23
  const handleDisableAllHeartbeats = async () => {
14
24
  if (disablingHeartbeats) return
@@ -58,10 +68,10 @@ export function HeartbeatSection({ appSettings, patchSettings, inputClass }: Set
58
68
  <input
59
69
  type="number"
60
70
  min={0}
61
- value={appSettings.heartbeatAckMaxChars ?? 300}
71
+ value={appSettings.heartbeatAckMaxChars ?? DEFAULT_HEARTBEAT_ACK_MAX_CHARS}
62
72
  onChange={(e) => {
63
73
  const n = Number.parseInt(e.target.value, 10)
64
- patchSettings({ heartbeatAckMaxChars: Number.isFinite(n) ? Math.max(0, n) : 300 })
74
+ patchSettings({ heartbeatAckMaxChars: Number.isFinite(n) ? Math.max(0, n) : DEFAULT_HEARTBEAT_ACK_MAX_CHARS })
65
75
  }}
66
76
  className={inputClass}
67
77
  style={{ fontFamily: 'inherit' }}
@@ -74,7 +84,7 @@ export function HeartbeatSection({ appSettings, patchSettings, inputClass }: Set
74
84
  <div>
75
85
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Show OK Messages</label>
76
86
  <button
77
- onClick={() => patchSettings({ heartbeatShowOk: !(appSettings.heartbeatShowOk ?? false) })}
87
+ onClick={() => patchSettings({ heartbeatShowOk: !(appSettings.heartbeatShowOk ?? DEFAULT_HEARTBEAT_SHOW_OK) })}
78
88
  className={`px-3 py-2 rounded-[10px] border text-[12px] font-600 transition-colors cursor-pointer ${
79
89
  appSettings.heartbeatShowOk
80
90
  ? 'border-emerald-400/25 bg-emerald-500/10 text-emerald-300'
@@ -88,15 +98,15 @@ export function HeartbeatSection({ appSettings, patchSettings, inputClass }: Set
88
98
  <div>
89
99
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Show Alert Messages</label>
90
100
  <button
91
- onClick={() => patchSettings({ heartbeatShowAlerts: !(appSettings.heartbeatShowAlerts ?? true) })}
101
+ onClick={() => patchSettings({ heartbeatShowAlerts: !(appSettings.heartbeatShowAlerts ?? DEFAULT_HEARTBEAT_SHOW_ALERTS) })}
92
102
  className={`px-3 py-2 rounded-[10px] border text-[12px] font-600 transition-colors cursor-pointer ${
93
- (appSettings.heartbeatShowAlerts ?? true)
103
+ (appSettings.heartbeatShowAlerts ?? DEFAULT_HEARTBEAT_SHOW_ALERTS)
94
104
  ? 'border-emerald-400/25 bg-emerald-500/10 text-emerald-300'
95
105
  : 'border-white/[0.08] bg-white/[0.03] text-text-3'
96
106
  }`}
97
107
  style={{ fontFamily: 'inherit' }}
98
108
  >
99
- {(appSettings.heartbeatShowAlerts ?? true) ? 'On' : 'Off'}
109
+ {(appSettings.heartbeatShowAlerts ?? DEFAULT_HEARTBEAT_SHOW_ALERTS) ? 'On' : 'Off'}
100
110
  </button>
101
111
  </div>
102
112
  <div>
@@ -112,6 +122,78 @@ export function HeartbeatSection({ appSettings, patchSettings, inputClass }: Set
112
122
  </div>
113
123
  </div>
114
124
 
125
+ <div className="border-t border-white/[0.06] pt-5 mt-5">
126
+ <h4 className="font-display text-[11px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
127
+ Session Reset Defaults
128
+ </h4>
129
+ <p className="text-[11px] text-text-3/60 mb-4">
130
+ Freshness policy inherited by new sessions unless overridden on the agent or session itself.
131
+ </p>
132
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3">
133
+ <div>
134
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Reset Mode</label>
135
+ <select
136
+ value={appSettings.sessionResetMode || ''}
137
+ onChange={(e) => patchSettings({ sessionResetMode: parseResetMode(e.target.value) })}
138
+ className={inputClass}
139
+ style={{ fontFamily: 'inherit' }}
140
+ >
141
+ <option value="">Use built-in defaults</option>
142
+ <option value="idle">Idle</option>
143
+ <option value="daily">Daily</option>
144
+ </select>
145
+ </div>
146
+ <div>
147
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Idle Timeout (sec)</label>
148
+ <input
149
+ type="number"
150
+ min={0}
151
+ value={appSettings.sessionIdleTimeoutSec ?? ''}
152
+ onChange={(e) => patchSettings({ sessionIdleTimeoutSec: e.target.value ? Number.parseInt(e.target.value, 10) : null })}
153
+ placeholder="43200"
154
+ className={inputClass}
155
+ style={{ fontFamily: 'inherit' }}
156
+ />
157
+ </div>
158
+ </div>
159
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
160
+ <div>
161
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Max Age (sec)</label>
162
+ <input
163
+ type="number"
164
+ min={0}
165
+ value={appSettings.sessionMaxAgeSec ?? ''}
166
+ onChange={(e) => patchSettings({ sessionMaxAgeSec: e.target.value ? Number.parseInt(e.target.value, 10) : null })}
167
+ placeholder="604800"
168
+ className={inputClass}
169
+ style={{ fontFamily: 'inherit' }}
170
+ />
171
+ </div>
172
+ <div>
173
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Daily Reset Time</label>
174
+ <input
175
+ type="text"
176
+ value={appSettings.sessionDailyResetAt || ''}
177
+ onChange={(e) => patchSettings({ sessionDailyResetAt: e.target.value || null })}
178
+ placeholder="04:00"
179
+ className={inputClass}
180
+ style={{ fontFamily: 'inherit' }}
181
+ />
182
+ </div>
183
+ <div>
184
+ <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Reset Timezone</label>
185
+ <input
186
+ type="text"
187
+ value={appSettings.sessionResetTimezone || ''}
188
+ onChange={(e) => patchSettings({ sessionResetTimezone: e.target.value || null })}
189
+ placeholder="UTC"
190
+ className={inputClass}
191
+ style={{ fontFamily: 'inherit' }}
192
+ />
193
+ </div>
194
+ </div>
195
+ </div>
196
+
115
197
  <div>
116
198
  <div className="flex items-center gap-2.5">
117
199
  <button
@@ -27,10 +27,10 @@ export function OrchestratorSection({ appSettings, patchSettings, inputClass }:
27
27
  return (
28
28
  <div className="mb-10">
29
29
  <h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
30
- Orchestrator Engine
30
+ Coordination Engine
31
31
  </h3>
32
32
  <p className="text-[12px] text-text-3 mb-5">
33
- The LLM provider used by orchestrators for tool calling, agent generation, and task delegation.
33
+ The LLM provider used when agents coordinate delegation, tool calling, and automation-heavy runs.
34
34
  </p>
35
35
 
36
36
  <div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
@@ -53,7 +53,7 @@ export function OrchestratorSection({ appSettings, patchSettings, inputClass }:
53
53
  </div>
54
54
  {lgProviders.length === 0 && (
55
55
  <p className="text-[12px] text-text-3/60 mb-5">
56
- No orchestration-compatible providers available. Add an API provider in Providers.
56
+ No coordination-compatible providers available. Add an API provider in Providers.
57
57
  </p>
58
58
  )}
59
59
 
@@ -67,6 +67,9 @@ export function OrchestratorSection({ appSettings, patchSettings, inputClass }:
67
67
  onChange={(m) => patchSettings({ langGraphModel: m })}
68
68
  models={lgProviderInfo.models}
69
69
  defaultModels={lgProviderInfo.defaultModels}
70
+ credentialId={appSettings.langGraphCredentialId}
71
+ apiEndpoint={appSettings.langGraphEndpoint}
72
+ supportsDiscovery={lgProviderInfo.supportsModelDiscovery}
70
73
  className={`${inputClass} cursor-pointer`}
71
74
  />
72
75
  </div>
@@ -24,7 +24,7 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
24
24
  Runtime &amp; Loop Controls
25
25
  </h3>
26
26
  <p className="text-[12px] text-text-3 mb-5">
27
- Choose bounded or ongoing agent loops and set safety guards for task execution.
27
+ Control how far agents can run on their own and set safety guards for delegation and tool execution.
28
28
  </p>
29
29
  <div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
30
30
  <label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Loop Mode <HintTip text="Bounded = fixed max steps. Ongoing = runs until the task completes (with a safety cap)" /></label>
@@ -64,8 +64,8 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
64
64
  style={{ fontFamily: 'inherit' }}
65
65
  />
66
66
  </div>
67
- <div>
68
- <label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Orchestrator Steps <HintTip text="Maximum tool calls the orchestrator can make when coordinating multiple agents" /></label>
67
+ <div>
68
+ <label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Coordination Steps <HintTip text="Maximum tool calls an agent can make while coordinating multiple delegated agents" /></label>
69
69
  <input
70
70
  type="number"
71
71
  min={1}
@@ -79,8 +79,8 @@ export function RuntimeLoopSection({ appSettings, patchSettings, inputClass }: S
79
79
  style={{ fontFamily: 'inherit' }}
80
80
  />
81
81
  </div>
82
- <div>
83
- <label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Legacy Turns <HintTip text="Max conversation turns for older orchestration mode increase if agents get cut off mid-task" /></label>
82
+ <div>
83
+ <label className="flex items-center gap-1.5 font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Legacy Turns <HintTip text="Compatibility limit for older coordination flows that still rely on turn-based execution" /></label>
84
84
  <input
85
85
  type="number"
86
86
  min={1}
@@ -53,7 +53,7 @@ export function SecretsSection({ appSettings, inputClass }: SettingsSectionProps
53
53
  }
54
54
  }
55
55
 
56
- const orchestrators = Object.values(agents).filter((p) => p.isOrchestrator)
56
+ const selectableAgents = Object.values(agents)
57
57
  const secretList = Object.entries(secrets).map(([rowId, secret]) => ({ ...secret, rowId }))
58
58
 
59
59
  return (
@@ -62,7 +62,7 @@ export function SecretsSection({ appSettings, inputClass }: SettingsSectionProps
62
62
  Service Credentials
63
63
  </h3>
64
64
  <p className="text-[12px] text-text-3 mb-5">
65
- Credentials for external services (Gmail, APIs, etc.) that orchestrators can use during task execution.
65
+ Credentials for external services (Gmail, APIs, etc.) that agents can use during task execution.
66
66
  </p>
67
67
 
68
68
  {secretList.length > 0 && (
@@ -78,7 +78,7 @@ export function SecretsSection({ appSettings, inputClass }: SettingsSectionProps
78
78
  ? 'bg-emerald-500/10 text-emerald-400'
79
79
  : 'bg-amber-500/10 text-amber-400'
80
80
  }`}>
81
- {secret.scope === 'global' ? 'All orchestrators' : `${secret.agentIds.length} orchestrator(s)`}
81
+ {secret.scope === 'global' ? 'All eligible agents' : `${secret.agentIds.length} agent(s)`}
82
82
  </span>
83
83
  </div>
84
84
  </div>
@@ -106,14 +106,14 @@ export function SecretsSection({ appSettings, inputClass }: SettingsSectionProps
106
106
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Scope</label>
107
107
  <div className="flex p-1 rounded-[12px] bg-bg border border-white/[0.06]">
108
108
  {(['global', 'agent'] as const).map((s) => (
109
- <button key={s} onClick={() => setSecretScope(s)} className={`flex-1 py-2.5 rounded-[10px] text-center cursor-pointer transition-all text-[13px] font-600 capitalize ${secretScope === s ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`} style={{ fontFamily: 'inherit' }}>{s === 'global' ? 'All Orchestrators' : 'Specific'}</button>
109
+ <button key={s} onClick={() => setSecretScope(s)} className={`flex-1 py-2.5 rounded-[10px] text-center cursor-pointer transition-all text-[13px] font-600 capitalize ${secretScope === s ? 'bg-accent-soft text-accent-bright' : 'bg-transparent text-text-3 hover:text-text-2'}`} style={{ fontFamily: 'inherit' }}>{s === 'global' ? 'All Eligible Agents' : 'Specific Agents'}</button>
110
110
  ))}
111
111
  </div>
112
112
  </div>
113
113
 
114
- {secretScope === 'agent' && orchestrators.length > 0 && (
114
+ {secretScope === 'agent' && selectableAgents.length > 0 && (
115
115
  <div className="flex flex-wrap gap-2">
116
- {orchestrators.map((p) => (
116
+ {selectableAgents.map((p) => (
117
117
  <button key={p.id} onClick={() => setSecretAgentIds((prev) => prev.includes(p.id) ? prev.filter((x) => x !== p.id) : [...prev, p.id])} className={`px-3 py-2 rounded-[10px] text-[12px] font-600 cursor-pointer transition-all border ${secretAgentIds.includes(p.id) ? 'bg-accent-soft border-accent-bright/25 text-accent-bright' : 'bg-bg border-white/[0.06] text-text-3 hover:text-text-2'}`} style={{ fontFamily: 'inherit' }}>{p.name}</button>
118
118
  ))}
119
119
  </div>
@@ -47,7 +47,7 @@ export function UserPreferencesSection({ appSettings, patchSettings, inputClass
47
47
  <div className="mt-6">
48
48
  <label className="text-[12px] font-600 text-text-2 block mb-1.5">Default Agent</label>
49
49
  <p className="text-[11px] text-text-3/60 mb-2">
50
- The agent that opens automatically when you start the app or click Main Chat.
50
+ The agent that opens automatically when you start the app or use the default-agent shortcut.
51
51
  </p>
52
52
  <div className="flex flex-wrap gap-2">
53
53
  <button
@@ -4,6 +4,7 @@ import type { SettingsSectionProps } from './types'
4
4
 
5
5
  export function VoiceSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
6
6
  const enabled = appSettings.elevenLabsEnabled ?? false
7
+ const hasApiKey = appSettings.elevenLabsApiKeyConfigured === true
7
8
 
8
9
  return (
9
10
  <div className="mb-10">
@@ -37,10 +38,13 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
37
38
  type="password"
38
39
  value={appSettings.elevenLabsApiKey || ''}
39
40
  onChange={(e) => patchSettings({ elevenLabsApiKey: e.target.value || null })}
40
- placeholder="sk_..."
41
+ placeholder={hasApiKey ? 'Stored securely. Enter a new key to replace it.' : 'sk_...'}
41
42
  className={inputClass}
42
43
  style={{ fontFamily: 'inherit' }}
43
44
  />
45
+ {hasApiKey && (
46
+ <p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
47
+ )}
44
48
  </div>
45
49
  <div>
46
50
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Default Voice ID</label>