@swarmclawai/swarmclaw 0.7.1 → 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 (237) hide show
  1. package/README.md +155 -150
  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 +37 -9
  5. package/src/app/api/agents/route.ts +13 -2
  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/{sessions → chats}/[id]/browser/route.ts +5 -1
  9. package/src/app/api/{sessions → chats}/[id]/chat/route.ts +7 -3
  10. package/src/app/api/{sessions → chats}/[id]/checkpoints/route.ts +1 -1
  11. package/src/app/api/chats/[id]/main-loop/route.ts +13 -0
  12. package/src/app/api/{sessions → chats}/[id]/messages/route.ts +19 -13
  13. package/src/app/api/{sessions → chats}/[id]/restore/route.ts +1 -1
  14. package/src/app/api/{sessions → chats}/[id]/route.ts +22 -52
  15. package/src/app/api/{sessions → chats}/[id]/stop/route.ts +6 -1
  16. package/src/app/api/{sessions → chats}/route.ts +21 -7
  17. package/src/app/api/connectors/[id]/doctor/route.ts +26 -0
  18. package/src/app/api/connectors/doctor/route.ts +13 -0
  19. package/src/app/api/files/open/route.ts +16 -14
  20. package/src/app/api/memory/maintenance/route.ts +11 -1
  21. package/src/app/api/openclaw/agent-files/route.ts +27 -4
  22. package/src/app/api/openclaw/skills/route.ts +11 -3
  23. package/src/app/api/plugins/dependencies/route.ts +24 -0
  24. package/src/app/api/plugins/install/route.ts +15 -92
  25. package/src/app/api/plugins/route.ts +6 -26
  26. package/src/app/api/plugins/settings/route.ts +40 -0
  27. package/src/app/api/plugins/ui/route.ts +1 -0
  28. package/src/app/api/settings/route.ts +49 -7
  29. package/src/app/api/tasks/[id]/route.ts +15 -6
  30. package/src/app/api/tasks/bulk/route.ts +2 -2
  31. package/src/app/api/tasks/route.ts +9 -4
  32. package/src/app/api/usage/route.ts +30 -0
  33. package/src/app/api/webhooks/[id]/route.ts +8 -1
  34. package/src/app/page.tsx +9 -2
  35. package/src/cli/index.js +39 -33
  36. package/src/cli/index.ts +43 -49
  37. package/src/cli/spec.js +29 -27
  38. package/src/components/agents/agent-card.tsx +16 -13
  39. package/src/components/agents/agent-chat-list.tsx +104 -4
  40. package/src/components/agents/agent-list.tsx +54 -22
  41. package/src/components/agents/agent-sheet.tsx +209 -18
  42. package/src/components/agents/cron-job-form.tsx +3 -3
  43. package/src/components/agents/inspector-panel.tsx +110 -50
  44. package/src/components/auth/access-key-gate.tsx +36 -97
  45. package/src/components/auth/setup-wizard.tsx +5 -38
  46. package/src/components/chat/chat-area.tsx +39 -27
  47. package/src/components/{sessions/session-card.tsx → chat/chat-card.tsx} +7 -23
  48. package/src/components/chat/chat-header.tsx +299 -314
  49. package/src/components/{sessions/session-list.tsx → chat/chat-list.tsx} +11 -14
  50. package/src/components/chat/chat-tool-toggles.tsx +26 -17
  51. package/src/components/chat/checkpoint-timeline.tsx +4 -4
  52. package/src/components/chat/message-bubble.tsx +4 -1
  53. package/src/components/chat/message-list.tsx +5 -3
  54. package/src/components/chat/session-debug-panel.tsx +1 -1
  55. package/src/components/chat/tool-request-banner.tsx +3 -3
  56. package/src/components/chatrooms/agent-hover-card.tsx +3 -3
  57. package/src/components/chatrooms/chatroom-tool-request-banner.tsx +2 -2
  58. package/src/components/chatrooms/chatroom-view.tsx +347 -205
  59. package/src/components/connectors/connector-list.tsx +265 -127
  60. package/src/components/connectors/connector-sheet.tsx +218 -1
  61. package/src/components/home/home-view.tsx +129 -5
  62. package/src/components/layout/app-layout.tsx +392 -182
  63. package/src/components/layout/mobile-header.tsx +26 -8
  64. package/src/components/plugins/plugin-list.tsx +487 -254
  65. package/src/components/plugins/plugin-sheet.tsx +236 -13
  66. package/src/components/projects/project-detail.tsx +183 -0
  67. package/src/components/settings/gateway-connection-panel.tsx +1 -1
  68. package/src/components/shared/agent-picker-list.tsx +2 -2
  69. package/src/components/shared/command-palette.tsx +111 -25
  70. package/src/components/shared/settings/plugin-manager.tsx +20 -4
  71. package/src/components/shared/settings/section-capability-policy.tsx +105 -0
  72. package/src/components/shared/settings/section-heartbeat.tsx +78 -1
  73. package/src/components/shared/settings/section-orchestrator.tsx +3 -3
  74. package/src/components/shared/settings/section-providers.tsx +1 -1
  75. package/src/components/shared/settings/section-runtime-loop.tsx +5 -5
  76. package/src/components/shared/settings/section-secrets.tsx +6 -6
  77. package/src/components/shared/settings/section-user-preferences.tsx +1 -1
  78. package/src/components/shared/settings/section-voice.tsx +5 -1
  79. package/src/components/shared/settings/section-web-search.tsx +10 -2
  80. package/src/components/shared/settings/settings-page.tsx +244 -56
  81. package/src/components/tasks/approvals-panel.tsx +205 -18
  82. package/src/components/tasks/task-board.tsx +242 -46
  83. package/src/components/usage/metrics-dashboard.tsx +147 -1
  84. package/src/components/wallets/wallet-panel.tsx +17 -5
  85. package/src/components/webhooks/webhook-sheet.tsx +8 -8
  86. package/src/lib/auth.ts +17 -0
  87. package/src/lib/chat-streaming-state.test.ts +108 -0
  88. package/src/lib/chat-streaming-state.ts +108 -0
  89. package/src/lib/chat.ts +1 -1
  90. package/src/lib/{sessions.ts → chats.ts} +28 -18
  91. package/src/lib/openclaw-agent-id.test.ts +14 -0
  92. package/src/lib/openclaw-agent-id.ts +31 -0
  93. package/src/lib/providers/claude-cli.ts +1 -1
  94. package/src/lib/server/agent-assignment.test.ts +112 -0
  95. package/src/lib/server/agent-assignment.ts +169 -0
  96. package/src/lib/server/approval-connector-notify.test.ts +253 -0
  97. package/src/lib/server/approvals-auto-approve.test.ts +205 -0
  98. package/src/lib/server/approvals.ts +483 -75
  99. package/src/lib/server/autonomy-runtime.test.ts +341 -0
  100. package/src/lib/server/browser-state.test.ts +118 -0
  101. package/src/lib/server/browser-state.ts +123 -0
  102. package/src/lib/server/build-llm.test.ts +36 -0
  103. package/src/lib/server/build-llm.ts +11 -4
  104. package/src/lib/server/builtin-plugins.ts +34 -0
  105. package/src/lib/server/capability-router.ts +10 -8
  106. package/src/lib/server/chat-execution-heartbeat.test.ts +40 -0
  107. package/src/lib/server/chat-execution-tool-events.test.ts +134 -0
  108. package/src/lib/server/chat-execution.ts +285 -165
  109. package/src/lib/server/chatroom-health.test.ts +26 -0
  110. package/src/lib/server/chatroom-health.ts +2 -3
  111. package/src/lib/server/chatroom-helpers.test.ts +67 -2
  112. package/src/lib/server/chatroom-helpers.ts +48 -8
  113. package/src/lib/server/connectors/discord.ts +175 -11
  114. package/src/lib/server/connectors/doctor.test.ts +80 -0
  115. package/src/lib/server/connectors/doctor.ts +116 -0
  116. package/src/lib/server/connectors/manager.ts +948 -112
  117. package/src/lib/server/connectors/policy.test.ts +222 -0
  118. package/src/lib/server/connectors/policy.ts +452 -0
  119. package/src/lib/server/connectors/slack.ts +188 -9
  120. package/src/lib/server/connectors/telegram.ts +65 -15
  121. package/src/lib/server/connectors/thread-context.test.ts +44 -0
  122. package/src/lib/server/connectors/thread-context.ts +72 -0
  123. package/src/lib/server/connectors/types.ts +41 -11
  124. package/src/lib/server/cost.ts +34 -1
  125. package/src/lib/server/daemon-state.ts +61 -3
  126. package/src/lib/server/data-dir.ts +13 -0
  127. package/src/lib/server/delegation-jobs.test.ts +140 -0
  128. package/src/lib/server/delegation-jobs.ts +248 -0
  129. package/src/lib/server/document-utils.test.ts +47 -0
  130. package/src/lib/server/document-utils.ts +397 -0
  131. package/src/lib/server/heartbeat-service.ts +14 -40
  132. package/src/lib/server/heartbeat-source.test.ts +22 -0
  133. package/src/lib/server/heartbeat-source.ts +7 -0
  134. package/src/lib/server/identity-continuity.test.ts +77 -0
  135. package/src/lib/server/identity-continuity.ts +127 -0
  136. package/src/lib/server/mailbox-utils.ts +347 -0
  137. package/src/lib/server/main-agent-loop.ts +28 -1103
  138. package/src/lib/server/memory-db.ts +4 -6
  139. package/src/lib/server/memory-tiers.ts +40 -0
  140. package/src/lib/server/openclaw-agent-resolver.test.ts +70 -0
  141. package/src/lib/server/openclaw-agent-resolver.ts +128 -0
  142. package/src/lib/server/openclaw-exec-config.ts +5 -6
  143. package/src/lib/server/openclaw-skills-normalize.test.ts +56 -0
  144. package/src/lib/server/openclaw-skills-normalize.ts +136 -0
  145. package/src/lib/server/openclaw-sync.ts +3 -2
  146. package/src/lib/server/orchestrator-lg.ts +20 -9
  147. package/src/lib/server/orchestrator.ts +7 -7
  148. package/src/lib/server/playwright-proxy.mjs +27 -3
  149. package/src/lib/server/plugins.test.ts +207 -0
  150. package/src/lib/server/plugins.ts +927 -66
  151. package/src/lib/server/provider-health.ts +38 -6
  152. package/src/lib/server/queue.ts +13 -28
  153. package/src/lib/server/scheduler.ts +2 -0
  154. package/src/lib/server/session-archive-memory.test.ts +85 -0
  155. package/src/lib/server/session-archive-memory.ts +230 -0
  156. package/src/lib/server/session-mailbox.ts +8 -18
  157. package/src/lib/server/session-reset-policy.test.ts +99 -0
  158. package/src/lib/server/session-reset-policy.ts +311 -0
  159. package/src/lib/server/session-run-manager.ts +33 -82
  160. package/src/lib/server/session-tools/autonomy-tools.test.ts +105 -0
  161. package/src/lib/server/session-tools/calendar.ts +366 -0
  162. package/src/lib/server/session-tools/canvas.ts +1 -1
  163. package/src/lib/server/session-tools/chatroom.ts +4 -2
  164. package/src/lib/server/session-tools/connector.ts +114 -10
  165. package/src/lib/server/session-tools/context.ts +21 -5
  166. package/src/lib/server/session-tools/crawl.ts +447 -0
  167. package/src/lib/server/session-tools/crud.ts +74 -28
  168. package/src/lib/server/session-tools/delegate-fallback.test.ts +219 -0
  169. package/src/lib/server/session-tools/delegate.ts +497 -24
  170. package/src/lib/server/session-tools/discovery.ts +24 -6
  171. package/src/lib/server/session-tools/document.ts +283 -0
  172. package/src/lib/server/session-tools/edit_file.ts +4 -2
  173. package/src/lib/server/session-tools/email.ts +320 -0
  174. package/src/lib/server/session-tools/extract.ts +137 -0
  175. package/src/lib/server/session-tools/file-normalize.test.ts +93 -0
  176. package/src/lib/server/session-tools/file-send.test.ts +84 -1
  177. package/src/lib/server/session-tools/file.ts +241 -25
  178. package/src/lib/server/session-tools/git.ts +1 -1
  179. package/src/lib/server/session-tools/http.ts +1 -1
  180. package/src/lib/server/session-tools/human-loop.ts +227 -0
  181. package/src/lib/server/session-tools/image-gen.ts +380 -0
  182. package/src/lib/server/session-tools/index.ts +130 -50
  183. package/src/lib/server/session-tools/mailbox.ts +276 -0
  184. package/src/lib/server/session-tools/memory.ts +172 -3
  185. package/src/lib/server/session-tools/monitor.ts +151 -8
  186. package/src/lib/server/session-tools/normalize-tool-args.ts +17 -14
  187. package/src/lib/server/session-tools/openclaw-nodes.ts +1 -1
  188. package/src/lib/server/session-tools/openclaw-workspace.ts +1 -1
  189. package/src/lib/server/session-tools/platform-normalize.test.ts +142 -0
  190. package/src/lib/server/session-tools/platform.ts +148 -7
  191. package/src/lib/server/session-tools/plugin-creator.ts +89 -26
  192. package/src/lib/server/session-tools/primitive-tools.test.ts +257 -0
  193. package/src/lib/server/session-tools/replicate.ts +301 -0
  194. package/src/lib/server/session-tools/sample-ui.ts +1 -1
  195. package/src/lib/server/session-tools/sandbox.ts +4 -2
  196. package/src/lib/server/session-tools/schedule.ts +24 -12
  197. package/src/lib/server/session-tools/session-info.ts +43 -7
  198. package/src/lib/server/session-tools/session-tools-wiring.test.ts +31 -17
  199. package/src/lib/server/session-tools/shell.ts +5 -2
  200. package/src/lib/server/session-tools/subagent.ts +194 -28
  201. package/src/lib/server/session-tools/table.ts +587 -0
  202. package/src/lib/server/session-tools/wallet.ts +42 -12
  203. package/src/lib/server/session-tools/web-browser-config.test.ts +39 -0
  204. package/src/lib/server/session-tools/web.ts +926 -91
  205. package/src/lib/server/storage.ts +255 -16
  206. package/src/lib/server/stream-agent-chat.ts +116 -268
  207. package/src/lib/server/structured-extract.test.ts +72 -0
  208. package/src/lib/server/structured-extract.ts +373 -0
  209. package/src/lib/server/task-mention.test.ts +16 -2
  210. package/src/lib/server/task-mention.ts +61 -10
  211. package/src/lib/server/tool-aliases.ts +66 -18
  212. package/src/lib/server/tool-capability-policy.test.ts +9 -9
  213. package/src/lib/server/tool-capability-policy.ts +38 -27
  214. package/src/lib/server/tool-retry.ts +2 -0
  215. package/src/lib/server/watch-jobs.test.ts +173 -0
  216. package/src/lib/server/watch-jobs.ts +532 -0
  217. package/src/lib/server/ws-hub.ts +5 -3
  218. package/src/lib/tool-definitions.ts +4 -0
  219. package/src/lib/validation/schemas.test.ts +26 -0
  220. package/src/lib/validation/schemas.ts +10 -1
  221. package/src/lib/ws-client.ts +14 -12
  222. package/src/proxy.ts +5 -5
  223. package/src/stores/use-app-store.ts +5 -11
  224. package/src/stores/use-chat-store.ts +38 -9
  225. package/src/types/index.ts +352 -47
  226. package/src/app/api/sessions/[id]/main-loop/route.ts +0 -94
  227. package/src/components/sessions/new-session-sheet.tsx +0 -253
  228. package/src/lib/server/main-session.ts +0 -24
  229. package/src/lib/server/session-run-manager.test.ts +0 -23
  230. /package/src/app/api/{sessions → chats}/[id]/clear/route.ts +0 -0
  231. /package/src/app/api/{sessions → chats}/[id]/deploy/route.ts +0 -0
  232. /package/src/app/api/{sessions → chats}/[id]/devserver/route.ts +0 -0
  233. /package/src/app/api/{sessions → chats}/[id]/edit-resend/route.ts +0 -0
  234. /package/src/app/api/{sessions → chats}/[id]/fork/route.ts +0 -0
  235. /package/src/app/api/{sessions → chats}/[id]/mailbox/route.ts +0 -0
  236. /package/src/app/api/{sessions → chats}/[id]/retry/route.ts +0 -0
  237. /package/src/app/api/{sessions → chats}/heartbeat/route.ts +0 -0
@@ -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>
@@ -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'
@@ -16,20 +17,33 @@ import { EmbeddingSection } from './section-embedding'
16
17
  import { MemorySection } from './section-memory'
17
18
  import { SecretsSection } from './section-secrets'
18
19
  import { ProvidersSection } from './section-providers'
19
- import { PluginManager } from './plugin-manager'
20
20
 
21
21
  interface Tab {
22
22
  id: string
23
23
  label: string
24
- icon: React.ReactNode
24
+ icon: ReactNode
25
25
  keywords: string[]
26
26
  }
27
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
+
28
42
  const TABS: Tab[] = [
29
43
  {
30
44
  id: 'general',
31
45
  label: 'General',
32
- 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'],
33
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>,
34
48
  },
35
49
  {
@@ -40,8 +54,8 @@ const TABS: Tab[] = [
40
54
  },
41
55
  {
42
56
  id: 'agents',
43
- label: 'Agents & Loops',
44
- keywords: ['orchestrator', 'runtime', 'loop', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
57
+ label: 'Agents & Automation',
58
+ keywords: ['orchestrator', 'runtime', 'loop', 'automation', 'heartbeat', 'delegation', 'agent', 'swarm', 'turns'],
45
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>,
46
60
  },
47
61
  {
@@ -53,7 +67,7 @@ const TABS: Tab[] = [
53
67
  {
54
68
  id: 'integrations',
55
69
  label: 'Integrations',
56
- keywords: ['provider', 'secret', 'plugin', 'api', 'key', 'openai', 'anthropic', 'ollama', 'credential'],
70
+ keywords: ['provider', 'secret', 'api', 'key', 'openai', 'anthropic', 'ollama', 'credential'],
57
71
  icon: <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 2v4m0 12v4M2 12h4m12 0h4" /><circle cx="12" cy="12" r="4" /><path d="M8 8L5.5 5.5M16 8l2.5-2.5M8 16l-2.5 2.5M16 16l2.5 2.5" /></svg>,
58
72
  },
59
73
  ]
@@ -75,6 +89,8 @@ export function SettingsPage() {
75
89
  return tab && validTabIds.includes(tab) ? tab : 'general'
76
90
  })
77
91
  const contentRef = useRef<HTMLDivElement>(null)
92
+ const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
93
+ const [pendingSectionId, setPendingSectionId] = useState<string | null>(null)
78
94
 
79
95
  const setActiveTab = useCallback((tab: string) => {
80
96
  setActiveTabRaw(tab)
@@ -102,12 +118,145 @@ export function SettingsPage() {
102
118
  const credList = Object.values(credentials)
103
119
  const patchSettings = updateSettings
104
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])
105
257
 
106
258
  const matchingTabIds = searchQuery
107
- ? new Set(TABS.filter((t) => {
108
- const q = searchQuery.toLowerCase()
109
- return t.label.toLowerCase().includes(q) || t.keywords.some((k) => k.includes(q))
110
- }).map((t) => t.id))
259
+ ? new Set(matchingSections.map((section) => section.tabId))
111
260
  : null
112
261
 
113
262
  // Auto-switch to first matching tab when searching
@@ -119,6 +268,31 @@ export function SettingsPage() {
119
268
  // eslint-disable-next-line react-hooks/exhaustive-deps
120
269
  }, [searchQuery])
121
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
+
122
296
  return (
123
297
  <div className="flex-1 flex h-full min-w-0">
124
298
  {/* Tab sidebar */}
@@ -134,7 +308,7 @@ export function SettingsPage() {
134
308
  type="text"
135
309
  value={searchQuery}
136
310
  onChange={(e) => setSearchQuery(e.target.value)}
137
- placeholder="Search settings..."
311
+ placeholder="Search settings or jump to a section..."
138
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"
139
313
  style={{ fontFamily: 'inherit' }}
140
314
  />
@@ -169,59 +343,73 @@ export function SettingsPage() {
169
343
  {TABS.find((t) => t.id === activeTab)?.label}
170
344
  </h3>
171
345
  <p className="text-[13px] text-text-3 mt-1">
172
- {activeTab === 'general' && 'User preferences and global behavior settings.'}
346
+ {activeTab === 'general' && 'User preferences, default-chat behavior, and global controls.'}
173
347
  {activeTab === 'appearance' && 'Customize the look and feel of the interface.'}
174
- {activeTab === 'agents' && 'Orchestrator, runtime loops, capabilities and heartbeat.'}
348
+ {activeTab === 'agents' && 'Agent coordination, autonomy, delegation, and heartbeat.'}
175
349
  {activeTab === 'memory' && 'Embedding, memory governance, voice and web search.'}
176
- {activeTab === 'integrations' && 'Providers, secrets and plugins.'}
350
+ {activeTab === 'integrations' && 'Providers, endpoints, and encrypted credentials.'}
177
351
  </p>
178
352
  </div>
179
353
 
180
- {activeTab === 'general' && (
181
- <>
182
- <UserPreferencesSection {...sectionProps} />
183
- <CapabilityPolicySection {...sectionProps} />
184
- <StorageSection {...sectionProps} />
185
- </>
186
- )}
187
-
188
- {activeTab === 'appearance' && (
189
- <ThemeSection {...sectionProps} />
190
- )}
191
-
192
- {activeTab === 'agents' && (
193
- <>
194
- <OrchestratorSection {...sectionProps} />
195
- <RuntimeLoopSection {...sectionProps} />
196
- <HeartbeatSection {...sectionProps} />
197
- </>
198
- )}
199
-
200
- {activeTab === 'memory' && (
201
- <>
202
- <EmbeddingSection {...sectionProps} credList={credList} />
203
- <MemorySection {...sectionProps} />
204
- <VoiceSection {...sectionProps} />
205
- <WebSearchSection {...sectionProps} />
206
- </>
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>
207
391
  )}
208
392
 
209
- {activeTab === 'integrations' && (
210
- <>
211
- <ProvidersSection {...sectionProps} />
212
- <SecretsSection {...sectionProps} />
213
- <div className="mb-10">
214
- <h3 className="font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
215
- Plugins
216
- </h3>
217
- <p className="text-[12px] text-text-3 mb-5">
218
- Extend agent behavior with hooks. Install from the marketplace, a URL, or drop .js files into <code className="text-[11px] font-mono text-text-2">data/plugins/</code>.
219
- <span className="text-text-3/70 ml-1">OpenClaw plugins are also supported.</span>
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}
220
408
  </p>
221
- <PluginManager />
222
409
  </div>
223
- </>
224
- )}
410
+ {section.render()}
411
+ </div>
412
+ ))}
225
413
  </div>
226
414
  </div>
227
415
  </div>