@swarmclawai/swarmclaw 0.7.2 → 0.7.3

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 (197) hide show
  1. package/README.md +81 -22
  2. package/package.json +1 -1
  3. package/src/app/api/agents/[id]/route.ts +26 -0
  4. package/src/app/api/agents/[id]/thread/route.ts +36 -7
  5. package/src/app/api/agents/route.ts +12 -1
  6. package/src/app/api/auth/route.ts +76 -7
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +7 -2
  8. package/src/app/api/chats/[id]/browser/route.ts +5 -1
  9. package/src/app/api/chats/[id]/chat/route.ts +7 -3
  10. package/src/app/api/chats/[id]/main-loop/route.ts +7 -88
  11. package/src/app/api/chats/[id]/messages/route.ts +19 -13
  12. package/src/app/api/chats/[id]/route.ts +18 -0
  13. package/src/app/api/chats/[id]/stop/route.ts +6 -1
  14. package/src/app/api/chats/route.ts +16 -0
  15. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  16. package/src/app/api/connectors/doctor/route.ts +13 -0
  17. package/src/app/api/files/open/route.ts +16 -14
  18. package/src/app/api/memory/maintenance/route.ts +11 -1
  19. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  20. package/src/app/api/openclaw/skills/route.ts +11 -3
  21. package/src/app/api/plugins/dependencies/route.ts +24 -0
  22. package/src/app/api/plugins/install/route.ts +15 -92
  23. package/src/app/api/plugins/route.ts +3 -26
  24. package/src/app/api/plugins/settings/route.ts +17 -12
  25. package/src/app/api/plugins/ui/route.ts +1 -0
  26. package/src/app/api/settings/route.ts +49 -7
  27. package/src/app/api/tasks/[id]/route.ts +15 -6
  28. package/src/app/api/tasks/bulk/route.ts +2 -2
  29. package/src/app/api/tasks/route.ts +9 -4
  30. package/src/app/api/webhooks/[id]/route.ts +8 -1
  31. package/src/app/page.tsx +9 -2
  32. package/src/cli/index.js +4 -0
  33. package/src/cli/index.ts +3 -10
  34. package/src/components/agents/agent-card.tsx +15 -12
  35. package/src/components/agents/agent-chat-list.tsx +101 -1
  36. package/src/components/agents/agent-list.tsx +46 -9
  37. package/src/components/agents/agent-sheet.tsx +207 -16
  38. package/src/components/agents/inspector-panel.tsx +108 -48
  39. package/src/components/auth/access-key-gate.tsx +36 -97
  40. package/src/components/chat/chat-area.tsx +29 -13
  41. package/src/components/chat/chat-card.tsx +4 -20
  42. package/src/components/chat/chat-header.tsx +255 -353
  43. package/src/components/chat/chat-list.tsx +7 -9
  44. package/src/components/chat/checkpoint-timeline.tsx +1 -1
  45. package/src/components/chat/message-list.tsx +3 -1
  46. package/src/components/chatrooms/chatroom-view.tsx +347 -205
  47. package/src/components/connectors/connector-list.tsx +265 -127
  48. package/src/components/connectors/connector-sheet.tsx +217 -0
  49. package/src/components/home/home-view.tsx +128 -4
  50. package/src/components/layout/app-layout.tsx +383 -194
  51. package/src/components/layout/mobile-header.tsx +26 -8
  52. package/src/components/plugins/plugin-list.tsx +15 -3
  53. package/src/components/plugins/plugin-sheet.tsx +118 -9
  54. package/src/components/projects/project-detail.tsx +183 -0
  55. package/src/components/shared/agent-picker-list.tsx +2 -2
  56. package/src/components/shared/command-palette.tsx +111 -24
  57. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  58. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  59. package/src/components/shared/settings/section-heartbeat.tsx +77 -0
  60. package/src/components/shared/settings/section-orchestrator.tsx +3 -3
  61. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  62. package/src/components/shared/settings/section-secrets.tsx +6 -6
  63. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  64. package/src/components/shared/settings/section-voice.tsx +5 -1
  65. package/src/components/shared/settings/section-web-search.tsx +10 -2
  66. package/src/components/shared/settings/settings-page.tsx +245 -46
  67. package/src/components/tasks/approvals-panel.tsx +205 -18
  68. package/src/components/tasks/task-board.tsx +242 -46
  69. package/src/components/usage/metrics-dashboard.tsx +74 -1
  70. package/src/components/wallets/wallet-panel.tsx +17 -5
  71. package/src/components/webhooks/webhook-sheet.tsx +7 -7
  72. package/src/lib/auth.ts +17 -0
  73. package/src/lib/chat-streaming-state.test.ts +108 -0
  74. package/src/lib/chat-streaming-state.ts +108 -0
  75. package/src/lib/openclaw-agent-id.test.ts +14 -0
  76. package/src/lib/openclaw-agent-id.ts +31 -0
  77. package/src/lib/server/agent-assignment.test.ts +112 -0
  78. package/src/lib/server/agent-assignment.ts +169 -0
  79. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  80. package/src/lib/server/approvals-auto-approve.test.ts +205 -0
  81. package/src/lib/server/approvals.ts +483 -75
  82. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  83. package/src/lib/server/browser-state.test.ts +118 -0
  84. package/src/lib/server/browser-state.ts +123 -0
  85. package/src/lib/server/build-llm.test.ts +36 -0
  86. package/src/lib/server/build-llm.ts +11 -4
  87. package/src/lib/server/builtin-plugins.ts +34 -0
  88. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  89. package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
  90. package/src/lib/server/chat-execution.ts +250 -61
  91. package/src/lib/server/chatroom-health.test.ts +26 -0
  92. package/src/lib/server/chatroom-health.ts +2 -3
  93. package/src/lib/server/chatroom-helpers.test.ts +67 -2
  94. package/src/lib/server/chatroom-helpers.ts +45 -5
  95. package/src/lib/server/connectors/discord.ts +175 -11
  96. package/src/lib/server/connectors/doctor.test.ts +80 -0
  97. package/src/lib/server/connectors/doctor.ts +116 -0
  98. package/src/lib/server/connectors/manager.ts +946 -110
  99. package/src/lib/server/connectors/policy.test.ts +222 -0
  100. package/src/lib/server/connectors/policy.ts +452 -0
  101. package/src/lib/server/connectors/slack.ts +188 -9
  102. package/src/lib/server/connectors/telegram.ts +65 -15
  103. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  104. package/src/lib/server/connectors/thread-context.ts +72 -0
  105. package/src/lib/server/connectors/types.ts +41 -11
  106. package/src/lib/server/daemon-state.ts +59 -1
  107. package/src/lib/server/data-dir.ts +13 -0
  108. package/src/lib/server/delegation-jobs.test.ts +140 -0
  109. package/src/lib/server/delegation-jobs.ts +248 -0
  110. package/src/lib/server/document-utils.test.ts +47 -0
  111. package/src/lib/server/document-utils.ts +397 -0
  112. package/src/lib/server/heartbeat-service.ts +13 -39
  113. package/src/lib/server/heartbeat-source.test.ts +22 -0
  114. package/src/lib/server/heartbeat-source.ts +7 -0
  115. package/src/lib/server/identity-continuity.test.ts +77 -0
  116. package/src/lib/server/identity-continuity.ts +127 -0
  117. package/src/lib/server/mailbox-utils.ts +347 -0
  118. package/src/lib/server/main-agent-loop.ts +27 -967
  119. package/src/lib/server/memory-db.ts +4 -6
  120. package/src/lib/server/memory-tiers.ts +40 -0
  121. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  122. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  123. package/src/lib/server/openclaw-exec-config.ts +5 -6
  124. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  125. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  126. package/src/lib/server/openclaw-sync.ts +3 -2
  127. package/src/lib/server/orchestrator-lg.ts +17 -6
  128. package/src/lib/server/orchestrator.ts +2 -2
  129. package/src/lib/server/playwright-proxy.mjs +27 -3
  130. package/src/lib/server/plugins.test.ts +207 -0
  131. package/src/lib/server/plugins.ts +822 -69
  132. package/src/lib/server/provider-health.ts +33 -3
  133. package/src/lib/server/queue.ts +3 -20
  134. package/src/lib/server/scheduler.ts +2 -0
  135. package/src/lib/server/session-archive-memory.test.ts +85 -0
  136. package/src/lib/server/session-archive-memory.ts +230 -0
  137. package/src/lib/server/session-mailbox.ts +8 -18
  138. package/src/lib/server/session-reset-policy.test.ts +99 -0
  139. package/src/lib/server/session-reset-policy.ts +311 -0
  140. package/src/lib/server/session-run-manager.ts +33 -80
  141. package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
  142. package/src/lib/server/session-tools/calendar.ts +2 -12
  143. package/src/lib/server/session-tools/connector.ts +109 -8
  144. package/src/lib/server/session-tools/context.ts +14 -2
  145. package/src/lib/server/session-tools/crawl.ts +447 -0
  146. package/src/lib/server/session-tools/crud.ts +70 -32
  147. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  148. package/src/lib/server/session-tools/delegate.ts +406 -20
  149. package/src/lib/server/session-tools/discovery.ts +22 -4
  150. package/src/lib/server/session-tools/document.ts +283 -0
  151. package/src/lib/server/session-tools/email.ts +1 -3
  152. package/src/lib/server/session-tools/extract.ts +137 -0
  153. package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
  154. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  155. package/src/lib/server/session-tools/file.ts +237 -24
  156. package/src/lib/server/session-tools/human-loop.ts +227 -0
  157. package/src/lib/server/session-tools/image-gen.ts +1 -3
  158. package/src/lib/server/session-tools/index.ts +56 -1
  159. package/src/lib/server/session-tools/mailbox.ts +276 -0
  160. package/src/lib/server/session-tools/memory.ts +35 -3
  161. package/src/lib/server/session-tools/monitor.ts +150 -7
  162. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  163. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  164. package/src/lib/server/session-tools/platform.ts +142 -4
  165. package/src/lib/server/session-tools/plugin-creator.ts +86 -23
  166. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  167. package/src/lib/server/session-tools/replicate.ts +1 -3
  168. package/src/lib/server/session-tools/schedule.ts +20 -10
  169. package/src/lib/server/session-tools/session-info.ts +36 -3
  170. package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
  171. package/src/lib/server/session-tools/subagent.ts +193 -27
  172. package/src/lib/server/session-tools/table.ts +587 -0
  173. package/src/lib/server/session-tools/wallet.ts +13 -10
  174. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  175. package/src/lib/server/session-tools/web.ts +896 -100
  176. package/src/lib/server/storage.ts +226 -7
  177. package/src/lib/server/stream-agent-chat.ts +46 -21
  178. package/src/lib/server/structured-extract.test.ts +72 -0
  179. package/src/lib/server/structured-extract.ts +373 -0
  180. package/src/lib/server/task-mention.test.ts +16 -2
  181. package/src/lib/server/task-mention.ts +61 -10
  182. package/src/lib/server/tool-aliases.ts +44 -7
  183. package/src/lib/server/tool-capability-policy.ts +6 -0
  184. package/src/lib/server/tool-retry.ts +2 -0
  185. package/src/lib/server/watch-jobs.test.ts +173 -0
  186. package/src/lib/server/watch-jobs.ts +532 -0
  187. package/src/lib/server/ws-hub.ts +5 -3
  188. package/src/lib/validation/schemas.test.ts +26 -0
  189. package/src/lib/validation/schemas.ts +7 -0
  190. package/src/lib/ws-client.ts +14 -12
  191. package/src/proxy.ts +5 -5
  192. package/src/stores/use-app-store.ts +0 -6
  193. package/src/stores/use-chat-store.ts +31 -2
  194. package/src/types/index.ts +287 -44
  195. package/src/components/chat/new-chat-sheet.tsx +0 -253
  196. package/src/lib/server/main-session.ts +0 -17
  197. package/src/lib/server/session-run-manager.test.ts +0 -26
@@ -4,6 +4,8 @@ import type { SettingsSectionProps } from './types'
4
4
 
5
5
  export function WebSearchSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
6
6
  const provider = appSettings.webSearchProvider || 'duckduckgo'
7
+ const hasTavilyKey = appSettings.tavilyApiKeyConfigured === true
8
+ const hasBraveKey = appSettings.braveApiKeyConfigured === true
7
9
 
8
10
  return (
9
11
  <div className="mb-10">
@@ -52,10 +54,13 @@ export function WebSearchSection({ appSettings, patchSettings, inputClass }: Set
52
54
  type="password"
53
55
  value={appSettings.tavilyApiKey || ''}
54
56
  onChange={(e) => patchSettings({ tavilyApiKey: e.target.value || null })}
55
- placeholder="tvly-..."
57
+ placeholder={hasTavilyKey ? 'Stored securely. Enter a new key to replace it.' : 'tvly-...'}
56
58
  className={inputClass}
57
59
  style={{ fontFamily: 'inherit' }}
58
60
  />
61
+ {hasTavilyKey && (
62
+ <p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
63
+ )}
59
64
  <p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://tavily.com" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">tavily.com</a></p>
60
65
  </div>
61
66
  )}
@@ -67,10 +72,13 @@ export function WebSearchSection({ appSettings, patchSettings, inputClass }: Set
67
72
  type="password"
68
73
  value={appSettings.braveApiKey || ''}
69
74
  onChange={(e) => patchSettings({ braveApiKey: e.target.value || null })}
70
- placeholder="BSA..."
75
+ placeholder={hasBraveKey ? 'Stored securely. Enter a new key to replace it.' : 'BSA...'}
71
76
  className={inputClass}
72
77
  style={{ fontFamily: 'inherit' }}
73
78
  />
79
+ {hasBraveKey && (
80
+ <p className="text-[11px] text-emerald-400/90 mt-1.5">Stored securely. Clear the field and save to remove it.</p>
81
+ )}
74
82
  <p className="text-[11px] text-text-3/60 mt-2">Get your API key from <a href="https://brave.com/search/api/" target="_blank" rel="noopener noreferrer" className="text-accent-bright hover:underline">brave.com/search/api</a></p>
75
83
  </div>
76
84
  )}
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState, useRef, useCallback } from 'react'
3
+ import { useEffect, useState, useRef, useCallback, useMemo } from 'react'
4
+ import type { ReactNode } from 'react'
4
5
  import { useAppStore } from '@/stores/use-app-store'
5
6
  import { inputClass } from './utils'
6
7
  import { UserPreferencesSection } from './section-user-preferences'
@@ -20,15 +21,29 @@ import { ProvidersSection } from './section-providers'
20
21
  interface Tab {
21
22
  id: string
22
23
  label: string
23
- icon: React.ReactNode
24
+ icon: ReactNode
24
25
  keywords: string[]
25
26
  }
26
27
 
28
+ interface SettingsSectionDef {
29
+ id: string
30
+ tabId: string
31
+ title: string
32
+ description: string
33
+ keywords: string[]
34
+ render: () => ReactNode
35
+ }
36
+
37
+ interface SettingsFocusDetail {
38
+ tabId?: string
39
+ sectionId?: string
40
+ }
41
+
27
42
  const TABS: Tab[] = [
28
43
  {
29
44
  id: 'general',
30
45
  label: 'General',
31
- keywords: ['preferences', 'user', 'language', 'default', 'capability', 'policy', 'permissions', 'tools', 'storage', 'uploads', 'disk', 'files', 'cleanup'],
46
+ keywords: ['preferences', 'user', 'language', 'default', 'default agent', 'shortcut', 'capability', 'policy', 'permissions', 'tools', 'storage', 'uploads', 'disk', 'files', 'cleanup'],
32
47
  icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /></svg>,
33
48
  },
34
49
  {
@@ -39,8 +54,8 @@ const TABS: Tab[] = [
39
54
  },
40
55
  {
41
56
  id: 'agents',
42
- label: 'Agents & Loops',
43
- keywords: ['orchestrator', 'runtime', 'loop', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
57
+ label: 'Agents & Automation',
58
+ keywords: ['orchestrator', 'runtime', 'loop', 'automation', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
44
59
  icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 0 0-3-3.87" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg>,
45
60
  },
46
61
  {
@@ -74,6 +89,8 @@ export function SettingsPage() {
74
89
  return tab && validTabIds.includes(tab) ? tab : 'general'
75
90
  })
76
91
  const contentRef = useRef<HTMLDivElement>(null)
92
+ const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
93
+ const [pendingSectionId, setPendingSectionId] = useState<string | null>(null)
77
94
 
78
95
  const setActiveTab = useCallback((tab: string) => {
79
96
  setActiveTabRaw(tab)
@@ -101,12 +118,145 @@ export function SettingsPage() {
101
118
  const credList = Object.values(credentials)
102
119
  const patchSettings = updateSettings
103
120
  const sectionProps = { appSettings, patchSettings, inputClass }
121
+ const sections = useMemo<SettingsSectionDef[]>(() => [
122
+ {
123
+ id: 'user-preferences',
124
+ tabId: 'general',
125
+ title: 'Profile & Default Chat',
126
+ description: 'User identity, language, and which agent your sidebar shortcut opens.',
127
+ keywords: ['profile', 'default chat', 'default agent', 'shortcut', 'user', 'language', 'main chat'],
128
+ render: () => <UserPreferencesSection {...sectionProps} />,
129
+ },
130
+ {
131
+ id: 'capability-policy',
132
+ tabId: 'general',
133
+ title: 'Capabilities & Permissions',
134
+ description: 'Global controls for tool use, permissions, and execution policy.',
135
+ keywords: ['tools', 'permissions', 'capability', 'policy', 'security', 'approvals'],
136
+ render: () => <CapabilityPolicySection {...sectionProps} />,
137
+ },
138
+ {
139
+ id: 'storage',
140
+ tabId: 'general',
141
+ title: 'Storage & Uploads',
142
+ description: 'Manage upload retention, cleanup, and file storage behavior.',
143
+ keywords: ['storage', 'uploads', 'disk', 'cleanup', 'files'],
144
+ render: () => <StorageSection {...sectionProps} />,
145
+ },
146
+ {
147
+ id: 'theme',
148
+ tabId: 'appearance',
149
+ title: 'Theme',
150
+ description: 'Adjust theme hue and interface styling.',
151
+ keywords: ['theme', 'appearance', 'color', 'hue'],
152
+ render: () => <ThemeSection {...sectionProps} />,
153
+ },
154
+ {
155
+ id: 'coordination-engine',
156
+ tabId: 'agents',
157
+ title: 'Coordination Engine',
158
+ description: 'Choose the model settings used for delegation-heavy agent work.',
159
+ keywords: ['coordination', 'delegation', 'engine', 'orchestrator'],
160
+ render: () => <OrchestratorSection {...sectionProps} />,
161
+ },
162
+ {
163
+ id: 'runtime-loop',
164
+ tabId: 'agents',
165
+ title: 'Automation Limits',
166
+ description: 'Control how far agents can run, recurse, and delegate on their own.',
167
+ keywords: ['automation', 'loop', 'runtime', 'turns', 'autonomy', 'heartbeat'],
168
+ render: () => <RuntimeLoopSection {...sectionProps} />,
169
+ },
170
+ {
171
+ id: 'heartbeat',
172
+ tabId: 'agents',
173
+ title: 'Heartbeat',
174
+ description: 'Configure automatic follow-up checks for active agent chats.',
175
+ keywords: ['heartbeat', 'follow up', 'interval', 'ongoing'],
176
+ render: () => <HeartbeatSection {...sectionProps} />,
177
+ },
178
+ {
179
+ id: 'embedding',
180
+ tabId: 'memory',
181
+ title: 'Embeddings',
182
+ description: 'Configure providers for embeddings and vector-backed features.',
183
+ keywords: ['embedding', 'vector', 'provider', 'semantic'],
184
+ render: () => <EmbeddingSection {...sectionProps} credList={credList} />,
185
+ },
186
+ {
187
+ id: 'memory',
188
+ tabId: 'memory',
189
+ title: 'Memory Governance',
190
+ description: 'Tune how memory is stored, consolidated, and retrieved.',
191
+ keywords: ['memory', 'consolidation', 'retention', 'governance'],
192
+ render: () => <MemorySection {...sectionProps} />,
193
+ },
194
+ {
195
+ id: 'voice',
196
+ tabId: 'memory',
197
+ title: 'Voice',
198
+ description: 'Control speech output and voice provider settings.',
199
+ keywords: ['voice', 'speech', 'tts', 'audio'],
200
+ render: () => <VoiceSection {...sectionProps} />,
201
+ },
202
+ {
203
+ id: 'web-search',
204
+ tabId: 'memory',
205
+ title: 'Web Search',
206
+ description: 'Set defaults for search providers and browsing behavior.',
207
+ keywords: ['web search', 'browse', 'internet', 'search'],
208
+ render: () => <WebSearchSection {...sectionProps} />,
209
+ },
210
+ {
211
+ id: 'providers',
212
+ tabId: 'integrations',
213
+ title: 'Providers',
214
+ description: 'Manage model providers, endpoints, and credentials.',
215
+ keywords: ['providers', 'endpoints', 'openai', 'anthropic', 'ollama', 'models'],
216
+ render: () => <ProvidersSection {...sectionProps} />,
217
+ },
218
+ {
219
+ id: 'secrets',
220
+ tabId: 'integrations',
221
+ title: 'Secrets',
222
+ description: 'Store encrypted credentials for agents and integrations.',
223
+ keywords: ['secrets', 'credentials', 'api keys', 'tokens'],
224
+ render: () => <SecretsSection {...sectionProps} />,
225
+ },
226
+ ], [credList, sectionProps])
227
+ const sectionsByTab = useMemo(() => {
228
+ const map = new Map<string, SettingsSectionDef[]>()
229
+ for (const section of sections) {
230
+ const group = map.get(section.tabId) || []
231
+ group.push(section)
232
+ map.set(section.tabId, group)
233
+ }
234
+ return map
235
+ }, [sections])
236
+ const setSectionRef = useCallback((id: string, node: HTMLDivElement | null) => {
237
+ sectionRefs.current[id] = node
238
+ }, [])
239
+ const focusSection = useCallback((sectionId: string, tabId?: string) => {
240
+ if (tabId && tabId !== activeTab) {
241
+ setPendingSectionId(sectionId)
242
+ setActiveTab(tabId)
243
+ return
244
+ }
245
+ sectionRefs.current[sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
246
+ }, [activeTab, setActiveTab])
247
+
248
+ const matchingSections = useMemo(() => {
249
+ if (!searchQuery.trim()) return sections
250
+ const q = searchQuery.toLowerCase()
251
+ return sections.filter((section) =>
252
+ section.title.toLowerCase().includes(q)
253
+ || section.description.toLowerCase().includes(q)
254
+ || section.keywords.some((keyword) => keyword.toLowerCase().includes(q)),
255
+ )
256
+ }, [searchQuery, sections])
104
257
 
105
258
  const matchingTabIds = searchQuery
106
- ? new Set(TABS.filter((t) => {
107
- const q = searchQuery.toLowerCase()
108
- return t.label.toLowerCase().includes(q) || t.keywords.some((k) => k.includes(q))
109
- }).map((t) => t.id))
259
+ ? new Set(matchingSections.map((section) => section.tabId))
110
260
  : null
111
261
 
112
262
  // Auto-switch to first matching tab when searching
@@ -118,6 +268,31 @@ export function SettingsPage() {
118
268
  // eslint-disable-next-line react-hooks/exhaustive-deps
119
269
  }, [searchQuery])
120
270
 
271
+ useEffect(() => {
272
+ if (!pendingSectionId) return
273
+ const frame = window.requestAnimationFrame(() => {
274
+ sectionRefs.current[pendingSectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
275
+ setPendingSectionId(null)
276
+ })
277
+ return () => window.cancelAnimationFrame(frame)
278
+ }, [activeTab, pendingSectionId])
279
+
280
+ useEffect(() => {
281
+ const handleFocus = (event: Event) => {
282
+ const detail = (event as CustomEvent<SettingsFocusDetail>).detail
283
+ if (!detail) return
284
+ if (detail.sectionId) {
285
+ focusSection(detail.sectionId, detail.tabId)
286
+ return
287
+ }
288
+ if (detail.tabId) setActiveTab(detail.tabId)
289
+ }
290
+ window.addEventListener('swarmclaw:settings-focus', handleFocus as EventListener)
291
+ return () => window.removeEventListener('swarmclaw:settings-focus', handleFocus as EventListener)
292
+ }, [focusSection, setActiveTab])
293
+
294
+ const visibleSections = sectionsByTab.get(activeTab) || []
295
+
121
296
  return (
122
297
  <div className="flex-1 flex h-full min-w-0">
123
298
  {/* Tab sidebar */}
@@ -133,7 +308,7 @@ export function SettingsPage() {
133
308
  type="text"
134
309
  value={searchQuery}
135
310
  onChange={(e) => setSearchQuery(e.target.value)}
136
- placeholder="Search settings..."
311
+ placeholder="Search settings or jump to a section..."
137
312
  className="w-full pl-8 pr-2 py-1.5 text-[12px] bg-white/[0.04] rounded-[8px] border border-white/[0.06] text-text placeholder:text-text-3/40 outline-none focus:border-white/[0.12] transition-colors"
138
313
  style={{ fontFamily: 'inherit' }}
139
314
  />
@@ -168,49 +343,73 @@ export function SettingsPage() {
168
343
  {TABS.find((t) => t.id === activeTab)?.label}
169
344
  </h3>
170
345
  <p className="text-[13px] text-text-3 mt-1">
171
- {activeTab === 'general' && 'User preferences and global behavior settings.'}
346
+ {activeTab === 'general' && 'User preferences, default-chat behavior, and global controls.'}
172
347
  {activeTab === 'appearance' && 'Customize the look and feel of the interface.'}
173
- {activeTab === 'agents' && 'Orchestrator, runtime loops, capabilities and heartbeat.'}
348
+ {activeTab === 'agents' && 'Agent coordination, autonomy, delegation, and heartbeat.'}
174
349
  {activeTab === 'memory' && 'Embedding, memory governance, voice and web search.'}
175
- {activeTab === 'integrations' && 'Providers, secrets and plugins.'}
350
+ {activeTab === 'integrations' && 'Providers, endpoints, and encrypted credentials.'}
176
351
  </p>
177
352
  </div>
178
353
 
179
- {activeTab === 'general' && (
180
- <>
181
- <UserPreferencesSection {...sectionProps} />
182
- <CapabilityPolicySection {...sectionProps} />
183
- <StorageSection {...sectionProps} />
184
- </>
185
- )}
186
-
187
- {activeTab === 'appearance' && (
188
- <ThemeSection {...sectionProps} />
354
+ {searchQuery && (
355
+ <div className="mb-8 rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4">
356
+ <div className="flex items-center justify-between gap-3 mb-3">
357
+ <div>
358
+ <p className="text-[12px] font-600 text-text-2">
359
+ {matchingSections.length > 0 ? `${matchingSections.length} matching section${matchingSections.length === 1 ? '' : 's'}` : 'No direct section matches'}
360
+ </p>
361
+ <p className="text-[11px] text-text-3/60">
362
+ Search now lands on individual settings sections instead of only tab names.
363
+ </p>
364
+ </div>
365
+ {searchQuery && (
366
+ <button
367
+ onClick={() => setSearchQuery('')}
368
+ className="px-2.5 py-1.5 rounded-[8px] bg-white/[0.04] text-[11px] text-text-3 hover:text-text hover:bg-white/[0.08] transition-colors border-none cursor-pointer"
369
+ style={{ fontFamily: 'inherit' }}
370
+ >
371
+ Clear
372
+ </button>
373
+ )}
374
+ </div>
375
+ {matchingSections.length > 0 && (
376
+ <div className="flex flex-wrap gap-2">
377
+ {matchingSections.slice(0, 8).map((section) => (
378
+ <button
379
+ key={section.id}
380
+ onClick={() => focusSection(section.id, section.tabId)}
381
+ className="px-3 py-2 rounded-[10px] border border-white/[0.06] bg-transparent text-left hover:bg-white/[0.04] transition-colors cursor-pointer"
382
+ style={{ fontFamily: 'inherit' }}
383
+ >
384
+ <div className="text-[12px] font-600 text-text">{section.title}</div>
385
+ <div className="text-[10px] text-text-3/60">{TABS.find((tab) => tab.id === section.tabId)?.label}</div>
386
+ </button>
387
+ ))}
388
+ </div>
389
+ )}
390
+ </div>
189
391
  )}
190
392
 
191
- {activeTab === 'agents' && (
192
- <>
193
- <OrchestratorSection {...sectionProps} />
194
- <RuntimeLoopSection {...sectionProps} />
195
- <HeartbeatSection {...sectionProps} />
196
- </>
197
- )}
198
-
199
- {activeTab === 'memory' && (
200
- <>
201
- <EmbeddingSection {...sectionProps} credList={credList} />
202
- <MemorySection {...sectionProps} />
203
- <VoiceSection {...sectionProps} />
204
- <WebSearchSection {...sectionProps} />
205
- </>
206
- )}
207
-
208
- {activeTab === 'integrations' && (
209
- <>
210
- <ProvidersSection {...sectionProps} />
211
- <SecretsSection {...sectionProps} />
212
- </>
213
- )}
393
+ {visibleSections.map((section) => (
394
+ <div
395
+ key={section.id}
396
+ ref={(node) => setSectionRef(section.id, node)}
397
+ className="mb-10 scroll-mt-6 last:mb-0"
398
+ >
399
+ <div className="mb-4">
400
+ <div className="text-[11px] uppercase tracking-[0.08em] text-text-3/45 mb-1">
401
+ {TABS.find((tab) => tab.id === section.tabId)?.label}
402
+ </div>
403
+ <h4 className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">
404
+ {section.title}
405
+ </h4>
406
+ <p className="text-[12px] text-text-3 mt-1">
407
+ {section.description}
408
+ </p>
409
+ </div>
410
+ {section.render()}
411
+ </div>
412
+ ))}
214
413
  </div>
215
414
  </div>
216
415
  </div>