@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
@@ -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)
@@ -100,13 +117,148 @@ export function SettingsPage() {
100
117
  const [searchQuery, setSearchQuery] = useState('')
101
118
  const credList = Object.values(credentials)
102
119
  const patchSettings = updateSettings
103
- const sectionProps = { appSettings, patchSettings, inputClass }
120
+ const sections = useMemo<SettingsSectionDef[]>(() => {
121
+ const sectionProps = { appSettings, patchSettings, inputClass }
122
+ return [
123
+ {
124
+ id: 'user-preferences',
125
+ tabId: 'general',
126
+ title: 'Profile & Default Chat',
127
+ description: 'User identity, language, and which agent your sidebar shortcut opens.',
128
+ keywords: ['profile', 'default chat', 'default agent', 'shortcut', 'user', 'language', 'main chat'],
129
+ render: () => <UserPreferencesSection {...sectionProps} />,
130
+ },
131
+ {
132
+ id: 'capability-policy',
133
+ tabId: 'general',
134
+ title: 'Capabilities & Permissions',
135
+ description: 'Global controls for tool use, permissions, and execution policy.',
136
+ keywords: ['tools', 'permissions', 'capability', 'policy', 'security', 'approvals'],
137
+ render: () => <CapabilityPolicySection {...sectionProps} />,
138
+ },
139
+ {
140
+ id: 'storage',
141
+ tabId: 'general',
142
+ title: 'Storage & Uploads',
143
+ description: 'Manage upload retention, cleanup, and file storage behavior.',
144
+ keywords: ['storage', 'uploads', 'disk', 'cleanup', 'files'],
145
+ render: () => <StorageSection {...sectionProps} />,
146
+ },
147
+ {
148
+ id: 'theme',
149
+ tabId: 'appearance',
150
+ title: 'Theme',
151
+ description: 'Adjust theme hue and interface styling.',
152
+ keywords: ['theme', 'appearance', 'color', 'hue'],
153
+ render: () => <ThemeSection {...sectionProps} />,
154
+ },
155
+ {
156
+ id: 'coordination-engine',
157
+ tabId: 'agents',
158
+ title: 'Coordination Engine',
159
+ description: 'Choose the model settings used for delegation-heavy agent work.',
160
+ keywords: ['coordination', 'delegation', 'engine', 'orchestrator'],
161
+ render: () => <OrchestratorSection {...sectionProps} />,
162
+ },
163
+ {
164
+ id: 'runtime-loop',
165
+ tabId: 'agents',
166
+ title: 'Automation Limits',
167
+ description: 'Control how far agents can run, recurse, and delegate on their own.',
168
+ keywords: ['automation', 'loop', 'runtime', 'turns', 'autonomy', 'heartbeat'],
169
+ render: () => <RuntimeLoopSection {...sectionProps} />,
170
+ },
171
+ {
172
+ id: 'heartbeat',
173
+ tabId: 'agents',
174
+ title: 'Heartbeat',
175
+ description: 'Configure automatic follow-up checks for active agent chats.',
176
+ keywords: ['heartbeat', 'follow up', 'interval', 'ongoing'],
177
+ render: () => <HeartbeatSection {...sectionProps} />,
178
+ },
179
+ {
180
+ id: 'embedding',
181
+ tabId: 'memory',
182
+ title: 'Embeddings',
183
+ description: 'Configure providers for embeddings and vector-backed features.',
184
+ keywords: ['embedding', 'vector', 'provider', 'semantic'],
185
+ render: () => <EmbeddingSection {...sectionProps} credList={credList} />,
186
+ },
187
+ {
188
+ id: 'memory',
189
+ tabId: 'memory',
190
+ title: 'Memory Governance',
191
+ description: 'Tune how memory is stored, consolidated, and retrieved.',
192
+ keywords: ['memory', 'consolidation', 'retention', 'governance'],
193
+ render: () => <MemorySection {...sectionProps} />,
194
+ },
195
+ {
196
+ id: 'voice',
197
+ tabId: 'memory',
198
+ title: 'Voice',
199
+ description: 'Control speech output and voice provider settings.',
200
+ keywords: ['voice', 'speech', 'tts', 'audio'],
201
+ render: () => <VoiceSection {...sectionProps} />,
202
+ },
203
+ {
204
+ id: 'web-search',
205
+ tabId: 'memory',
206
+ title: 'Web Search',
207
+ description: 'Set defaults for search providers and browsing behavior.',
208
+ keywords: ['web search', 'browse', 'internet', 'search'],
209
+ render: () => <WebSearchSection {...sectionProps} />,
210
+ },
211
+ {
212
+ id: 'providers',
213
+ tabId: 'integrations',
214
+ title: 'Providers',
215
+ description: 'Manage model providers, endpoints, and credentials.',
216
+ keywords: ['providers', 'endpoints', 'openai', 'anthropic', 'ollama', 'models'],
217
+ render: () => <ProvidersSection {...sectionProps} />,
218
+ },
219
+ {
220
+ id: 'secrets',
221
+ tabId: 'integrations',
222
+ title: 'Secrets',
223
+ description: 'Store encrypted credentials for agents and integrations.',
224
+ keywords: ['secrets', 'credentials', 'api keys', 'tokens'],
225
+ render: () => <SecretsSection {...sectionProps} />,
226
+ },
227
+ ]
228
+ }, [appSettings, credList, patchSettings])
229
+ const sectionsByTab = useMemo(() => {
230
+ const map = new Map<string, SettingsSectionDef[]>()
231
+ for (const section of sections) {
232
+ const group = map.get(section.tabId) || []
233
+ group.push(section)
234
+ map.set(section.tabId, group)
235
+ }
236
+ return map
237
+ }, [sections])
238
+ const setSectionRef = useCallback((id: string, node: HTMLDivElement | null) => {
239
+ sectionRefs.current[id] = node
240
+ }, [])
241
+ const focusSection = useCallback((sectionId: string, tabId?: string) => {
242
+ if (tabId && tabId !== activeTab) {
243
+ setPendingSectionId(sectionId)
244
+ setActiveTab(tabId)
245
+ return
246
+ }
247
+ sectionRefs.current[sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
248
+ }, [activeTab, setActiveTab])
249
+
250
+ const matchingSections = useMemo(() => {
251
+ if (!searchQuery.trim()) return sections
252
+ const q = searchQuery.toLowerCase()
253
+ return sections.filter((section) =>
254
+ section.title.toLowerCase().includes(q)
255
+ || section.description.toLowerCase().includes(q)
256
+ || section.keywords.some((keyword) => keyword.toLowerCase().includes(q)),
257
+ )
258
+ }, [searchQuery, sections])
104
259
 
105
260
  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))
261
+ ? new Set(matchingSections.map((section) => section.tabId))
110
262
  : null
111
263
 
112
264
  // Auto-switch to first matching tab when searching
@@ -118,6 +270,31 @@ export function SettingsPage() {
118
270
  // eslint-disable-next-line react-hooks/exhaustive-deps
119
271
  }, [searchQuery])
120
272
 
273
+ useEffect(() => {
274
+ if (!pendingSectionId) return
275
+ const frame = window.requestAnimationFrame(() => {
276
+ sectionRefs.current[pendingSectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
277
+ setPendingSectionId(null)
278
+ })
279
+ return () => window.cancelAnimationFrame(frame)
280
+ }, [activeTab, pendingSectionId])
281
+
282
+ useEffect(() => {
283
+ const handleFocus = (event: Event) => {
284
+ const detail = (event as CustomEvent<SettingsFocusDetail>).detail
285
+ if (!detail) return
286
+ if (detail.sectionId) {
287
+ focusSection(detail.sectionId, detail.tabId)
288
+ return
289
+ }
290
+ if (detail.tabId) setActiveTab(detail.tabId)
291
+ }
292
+ window.addEventListener('swarmclaw:settings-focus', handleFocus as EventListener)
293
+ return () => window.removeEventListener('swarmclaw:settings-focus', handleFocus as EventListener)
294
+ }, [focusSection, setActiveTab])
295
+
296
+ const visibleSections = sectionsByTab.get(activeTab) || []
297
+
121
298
  return (
122
299
  <div className="flex-1 flex h-full min-w-0">
123
300
  {/* Tab sidebar */}
@@ -133,7 +310,7 @@ export function SettingsPage() {
133
310
  type="text"
134
311
  value={searchQuery}
135
312
  onChange={(e) => setSearchQuery(e.target.value)}
136
- placeholder="Search settings..."
313
+ placeholder="Search settings or jump to a section..."
137
314
  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
315
  style={{ fontFamily: 'inherit' }}
139
316
  />
@@ -168,49 +345,73 @@ export function SettingsPage() {
168
345
  {TABS.find((t) => t.id === activeTab)?.label}
169
346
  </h3>
170
347
  <p className="text-[13px] text-text-3 mt-1">
171
- {activeTab === 'general' && 'User preferences and global behavior settings.'}
348
+ {activeTab === 'general' && 'User preferences, default-chat behavior, and global controls.'}
172
349
  {activeTab === 'appearance' && 'Customize the look and feel of the interface.'}
173
- {activeTab === 'agents' && 'Orchestrator, runtime loops, capabilities and heartbeat.'}
350
+ {activeTab === 'agents' && 'Agent coordination, autonomy, delegation, and heartbeat.'}
174
351
  {activeTab === 'memory' && 'Embedding, memory governance, voice and web search.'}
175
- {activeTab === 'integrations' && 'Providers, secrets and plugins.'}
352
+ {activeTab === 'integrations' && 'Providers, endpoints, and encrypted credentials.'}
176
353
  </p>
177
354
  </div>
178
355
 
179
- {activeTab === 'general' && (
180
- <>
181
- <UserPreferencesSection {...sectionProps} />
182
- <CapabilityPolicySection {...sectionProps} />
183
- <StorageSection {...sectionProps} />
184
- </>
185
- )}
186
-
187
- {activeTab === 'appearance' && (
188
- <ThemeSection {...sectionProps} />
356
+ {searchQuery && (
357
+ <div className="mb-8 rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4">
358
+ <div className="flex items-center justify-between gap-3 mb-3">
359
+ <div>
360
+ <p className="text-[12px] font-600 text-text-2">
361
+ {matchingSections.length > 0 ? `${matchingSections.length} matching section${matchingSections.length === 1 ? '' : 's'}` : 'No direct section matches'}
362
+ </p>
363
+ <p className="text-[11px] text-text-3/60">
364
+ Search now lands on individual settings sections instead of only tab names.
365
+ </p>
366
+ </div>
367
+ {searchQuery && (
368
+ <button
369
+ onClick={() => setSearchQuery('')}
370
+ 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"
371
+ style={{ fontFamily: 'inherit' }}
372
+ >
373
+ Clear
374
+ </button>
375
+ )}
376
+ </div>
377
+ {matchingSections.length > 0 && (
378
+ <div className="flex flex-wrap gap-2">
379
+ {matchingSections.slice(0, 8).map((section) => (
380
+ <button
381
+ key={section.id}
382
+ onClick={() => focusSection(section.id, section.tabId)}
383
+ 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"
384
+ style={{ fontFamily: 'inherit' }}
385
+ >
386
+ <div className="text-[12px] font-600 text-text">{section.title}</div>
387
+ <div className="text-[10px] text-text-3/60">{TABS.find((tab) => tab.id === section.tabId)?.label}</div>
388
+ </button>
389
+ ))}
390
+ </div>
391
+ )}
392
+ </div>
189
393
  )}
190
394
 
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
- )}
395
+ {visibleSections.map((section) => (
396
+ <div
397
+ key={section.id}
398
+ ref={(node) => setSectionRef(section.id, node)}
399
+ className="mb-10 scroll-mt-6 last:mb-0"
400
+ >
401
+ <div className="mb-4">
402
+ <div className="text-[11px] uppercase tracking-[0.08em] text-text-3/45 mb-1">
403
+ {TABS.find((tab) => tab.id === section.tabId)?.label}
404
+ </div>
405
+ <h4 className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">
406
+ {section.title}
407
+ </h4>
408
+ <p className="text-[12px] text-text-3 mt-1">
409
+ {section.description}
410
+ </p>
411
+ </div>
412
+ {section.render()}
413
+ </div>
414
+ ))}
214
415
  </div>
215
416
  </div>
216
417
  </div>