@swarmclawai/swarmclaw 0.7.7 → 0.8.0

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 (281) hide show
  1. package/README.md +12 -14
  2. package/next.config.ts +13 -2
  3. package/package.json +4 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +9 -0
  5. package/src/app/api/agents/route.ts +4 -0
  6. package/src/app/api/agents/thread-route.test.ts +133 -0
  7. package/src/app/api/approvals/route.test.ts +148 -0
  8. package/src/app/api/canvas/[sessionId]/route.ts +3 -1
  9. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
  10. package/src/app/api/chats/[id]/devserver/route.ts +48 -7
  11. package/src/app/api/chats/[id]/messages/route.ts +42 -18
  12. package/src/app/api/chats/[id]/route.ts +1 -1
  13. package/src/app/api/chats/[id]/stop/route.ts +5 -4
  14. package/src/app/api/chats/route.ts +23 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +46 -3
  17. package/src/app/api/connectors/route.ts +12 -8
  18. package/src/app/api/external-agents/route.test.ts +165 -0
  19. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  20. package/src/app/api/gateways/[id]/route.ts +2 -0
  21. package/src/app/api/gateways/health-route.test.ts +135 -0
  22. package/src/app/api/gateways/route.ts +2 -0
  23. package/src/app/api/mcp-servers/route.test.ts +130 -0
  24. package/src/app/api/openclaw/deploy/route.ts +38 -5
  25. package/src/app/api/plugins/install/route.ts +46 -6
  26. package/src/app/api/plugins/marketplace/route.ts +48 -15
  27. package/src/app/api/preview-server/route.ts +26 -11
  28. package/src/app/api/projects/[id]/route.ts +6 -2
  29. package/src/app/api/projects/route.ts +4 -3
  30. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  31. package/src/app/api/schedules/route.test.ts +86 -0
  32. package/src/app/api/schedules/route.ts +6 -1
  33. package/src/app/api/secrets/[id]/route.ts +1 -0
  34. package/src/app/api/secrets/route.ts +2 -1
  35. package/src/app/api/settings/route.ts +2 -0
  36. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  37. package/src/app/api/setup/check-provider/route.ts +40 -10
  38. package/src/app/api/skills/[id]/route.ts +12 -0
  39. package/src/app/api/skills/import/route.ts +14 -12
  40. package/src/app/api/skills/route.ts +13 -1
  41. package/src/app/api/tasks/[id]/route.ts +10 -1
  42. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  43. package/src/app/api/tasks/import/github/route.ts +337 -0
  44. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  45. package/src/app/api/wallets/[id]/route.ts +79 -33
  46. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  47. package/src/app/api/wallets/route.ts +78 -61
  48. package/src/app/api/webhooks/[id]/route.ts +33 -6
  49. package/src/app/api/webhooks/route.test.ts +272 -0
  50. package/src/cli/index.js +1 -0
  51. package/src/cli/spec.js +1 -0
  52. package/src/components/agents/agent-card.tsx +9 -2
  53. package/src/components/agents/agent-chat-list.tsx +18 -2
  54. package/src/components/agents/agent-list.tsx +1 -0
  55. package/src/components/agents/agent-sheet.tsx +257 -38
  56. package/src/components/agents/inspector-panel.tsx +41 -0
  57. package/src/components/canvas/canvas-panel.tsx +236 -65
  58. package/src/components/chat/chat-area.tsx +36 -19
  59. package/src/components/chat/chat-card.tsx +36 -13
  60. package/src/components/chat/chat-header.tsx +48 -16
  61. package/src/components/chat/chat-list.tsx +28 -4
  62. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  63. package/src/components/chat/delegation-banner.test.ts +14 -1
  64. package/src/components/chat/delegation-banner.tsx +1 -1
  65. package/src/components/chat/message-bubble.tsx +208 -145
  66. package/src/components/chat/message-list.tsx +48 -19
  67. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  68. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  69. package/src/components/connectors/connector-health.tsx +1 -1
  70. package/src/components/connectors/connector-list.tsx +7 -2
  71. package/src/components/connectors/connector-sheet.tsx +337 -148
  72. package/src/components/gateways/gateway-sheet.tsx +2 -2
  73. package/src/components/layout/app-layout.tsx +40 -23
  74. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  75. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  76. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  77. package/src/components/plugins/plugin-list.tsx +45 -9
  78. package/src/components/plugins/plugin-sheet.tsx +55 -7
  79. package/src/components/projects/project-detail.tsx +217 -0
  80. package/src/components/projects/project-sheet.tsx +176 -4
  81. package/src/components/providers/provider-list.tsx +2 -1
  82. package/src/components/providers/provider-sheet.tsx +21 -2
  83. package/src/components/schedules/schedule-card.tsx +25 -1
  84. package/src/components/schedules/schedule-sheet.tsx +44 -2
  85. package/src/components/secrets/secret-sheet.tsx +21 -2
  86. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  87. package/src/components/shared/bottom-sheet.tsx +13 -3
  88. package/src/components/shared/command-palette.tsx +8 -1
  89. package/src/components/shared/confirm-dialog.tsx +19 -4
  90. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  91. package/src/components/shared/connector-platform-icon.tsx +39 -6
  92. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  93. package/src/components/shared/settings/section-capability-policy.tsx +45 -3
  94. package/src/components/shared/settings/section-voice.tsx +11 -3
  95. package/src/components/skills/skill-list.tsx +25 -0
  96. package/src/components/skills/skill-sheet.tsx +84 -12
  97. package/src/components/tasks/approvals-panel.tsx +289 -34
  98. package/src/components/tasks/task-board.tsx +410 -25
  99. package/src/components/tasks/task-card.tsx +66 -8
  100. package/src/components/tasks/task-sheet.tsx +16 -4
  101. package/src/components/ui/dialog.tsx +2 -2
  102. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  103. package/src/components/wallets/wallet-panel.tsx +435 -90
  104. package/src/components/wallets/wallet-section.tsx +198 -48
  105. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  106. package/src/lib/approval-display.ts +20 -0
  107. package/src/lib/canvas-content.ts +198 -0
  108. package/src/lib/chat-artifact-summary.ts +165 -0
  109. package/src/lib/chat-display.test.ts +91 -0
  110. package/src/lib/chat-display.ts +58 -0
  111. package/src/lib/chat-streaming-state.test.ts +47 -1
  112. package/src/lib/chat-streaming-state.ts +42 -0
  113. package/src/lib/ollama-model.ts +10 -0
  114. package/src/lib/openclaw-endpoint.test.ts +8 -0
  115. package/src/lib/openclaw-endpoint.ts +6 -1
  116. package/src/lib/plugin-install-cors.ts +46 -0
  117. package/src/lib/plugin-sources.test.ts +43 -0
  118. package/src/lib/plugin-sources.ts +77 -0
  119. package/src/lib/providers/ollama.ts +16 -6
  120. package/src/lib/providers/openclaw.test.ts +54 -0
  121. package/src/lib/providers/openclaw.ts +127 -11
  122. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  123. package/src/lib/schedule-dedupe.test.ts +66 -1
  124. package/src/lib/schedule-dedupe.ts +169 -12
  125. package/src/lib/schedule-origin.test.ts +20 -0
  126. package/src/lib/schedule-origin.ts +15 -0
  127. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  128. package/src/lib/server/agent-availability.ts +16 -0
  129. package/src/lib/server/agent-runtime-config.ts +12 -4
  130. package/src/lib/server/agent-thread-session.test.ts +51 -0
  131. package/src/lib/server/agent-thread-session.ts +7 -0
  132. package/src/lib/server/approval-match.ts +205 -0
  133. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  134. package/src/lib/server/approvals.ts +214 -1
  135. package/src/lib/server/assistant-control.test.ts +29 -0
  136. package/src/lib/server/assistant-control.ts +23 -0
  137. package/src/lib/server/build-llm.test.ts +79 -0
  138. package/src/lib/server/build-llm.ts +14 -4
  139. package/src/lib/server/canvas-content.test.ts +32 -0
  140. package/src/lib/server/canvas-content.ts +6 -0
  141. package/src/lib/server/capability-router.test.ts +33 -0
  142. package/src/lib/server/capability-router.ts +80 -19
  143. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  144. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  145. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  146. package/src/lib/server/chat-execution.ts +378 -73
  147. package/src/lib/server/clawhub-client.test.ts +14 -8
  148. package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
  149. package/src/lib/server/connectors/manager.test.ts +1147 -0
  150. package/src/lib/server/connectors/manager.ts +461 -137
  151. package/src/lib/server/connectors/pairing.ts +26 -5
  152. package/src/lib/server/connectors/types.ts +2 -0
  153. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  154. package/src/lib/server/connectors/whatsapp.ts +271 -47
  155. package/src/lib/server/context-manager.ts +6 -1
  156. package/src/lib/server/daemon-state.ts +84 -47
  157. package/src/lib/server/data-dir.test.ts +37 -0
  158. package/src/lib/server/data-dir.ts +20 -1
  159. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  160. package/src/lib/server/devserver-launch.test.ts +60 -0
  161. package/src/lib/server/devserver-launch.ts +85 -0
  162. package/src/lib/server/elevenlabs.test.ts +247 -1
  163. package/src/lib/server/elevenlabs.ts +147 -43
  164. package/src/lib/server/ethereum.ts +590 -0
  165. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  166. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  167. package/src/lib/server/eval/agent-regression.ts +383 -11
  168. package/src/lib/server/evm-swap.ts +475 -0
  169. package/src/lib/server/execution-log.ts +1 -0
  170. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  171. package/src/lib/server/heartbeat-service.ts +20 -11
  172. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  173. package/src/lib/server/heartbeat-wake.ts +338 -57
  174. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  175. package/src/lib/server/main-agent-loop.test.ts +260 -0
  176. package/src/lib/server/main-agent-loop.ts +559 -14
  177. package/src/lib/server/mcp-client.test.ts +16 -0
  178. package/src/lib/server/mcp-client.ts +25 -0
  179. package/src/lib/server/memory-integration.test.ts +719 -0
  180. package/src/lib/server/memory-policy.test.ts +43 -0
  181. package/src/lib/server/memory-policy.ts +132 -0
  182. package/src/lib/server/memory-tiers.test.ts +60 -0
  183. package/src/lib/server/memory-tiers.ts +16 -0
  184. package/src/lib/server/ollama-runtime.ts +58 -0
  185. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  186. package/src/lib/server/openclaw-deploy.ts +557 -81
  187. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  188. package/src/lib/server/openclaw-gateway.ts +10 -4
  189. package/src/lib/server/openclaw-health.test.ts +35 -0
  190. package/src/lib/server/openclaw-health.ts +215 -47
  191. package/src/lib/server/orchestrator-lg.ts +3 -2
  192. package/src/lib/server/orchestrator.ts +2 -0
  193. package/src/lib/server/plugins-advanced.test.ts +351 -0
  194. package/src/lib/server/plugins.ts +211 -6
  195. package/src/lib/server/project-context.ts +162 -0
  196. package/src/lib/server/project-utils.ts +150 -0
  197. package/src/lib/server/queue-advanced.test.ts +528 -0
  198. package/src/lib/server/queue-followups.test.ts +409 -2
  199. package/src/lib/server/queue-reconcile.test.ts +128 -0
  200. package/src/lib/server/queue.ts +527 -68
  201. package/src/lib/server/scheduler.ts +29 -1
  202. package/src/lib/server/session-note.test.ts +36 -0
  203. package/src/lib/server/session-note.ts +42 -0
  204. package/src/lib/server/session-run-manager.ts +83 -4
  205. package/src/lib/server/session-tools/canvas.ts +14 -12
  206. package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
  207. package/src/lib/server/session-tools/connector.test.ts +138 -0
  208. package/src/lib/server/session-tools/connector.ts +366 -54
  209. package/src/lib/server/session-tools/context.ts +17 -3
  210. package/src/lib/server/session-tools/crud.ts +484 -84
  211. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  212. package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
  213. package/src/lib/server/session-tools/delegate.ts +102 -10
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  215. package/src/lib/server/session-tools/discovery.ts +80 -12
  216. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  217. package/src/lib/server/session-tools/file.ts +43 -4
  218. package/src/lib/server/session-tools/human-loop.ts +35 -5
  219. package/src/lib/server/session-tools/index.ts +44 -9
  220. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  221. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  222. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  223. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  224. package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
  225. package/src/lib/server/session-tools/memory.test.ts +93 -0
  226. package/src/lib/server/session-tools/memory.ts +554 -75
  227. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  228. package/src/lib/server/session-tools/platform-access.test.ts +58 -0
  229. package/src/lib/server/session-tools/platform.ts +60 -19
  230. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  231. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  232. package/src/lib/server/session-tools/schedule.ts +6 -1
  233. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  234. package/src/lib/server/session-tools/shell.ts +22 -3
  235. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  236. package/src/lib/server/session-tools/wallet.ts +1374 -139
  237. package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
  238. package/src/lib/server/session-tools/web.ts +621 -70
  239. package/src/lib/server/skill-discovery.ts +128 -0
  240. package/src/lib/server/skill-eligibility.test.ts +84 -0
  241. package/src/lib/server/skill-eligibility.ts +95 -0
  242. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  243. package/src/lib/server/skill-prompt-budget.ts +125 -0
  244. package/src/lib/server/skills-normalize.test.ts +54 -0
  245. package/src/lib/server/skills-normalize.ts +372 -26
  246. package/src/lib/server/solana.ts +214 -29
  247. package/src/lib/server/storage.ts +65 -36
  248. package/src/lib/server/stream-agent-chat.test.ts +437 -2
  249. package/src/lib/server/stream-agent-chat.ts +957 -79
  250. package/src/lib/server/system-events.ts +1 -1
  251. package/src/lib/server/tool-aliases.ts +2 -0
  252. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  253. package/src/lib/server/tool-capability-policy.test.ts +24 -0
  254. package/src/lib/server/tool-capability-policy.ts +29 -1
  255. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  256. package/src/lib/server/tool-loop-detection.ts +260 -0
  257. package/src/lib/server/tool-planning.test.ts +44 -0
  258. package/src/lib/server/tool-planning.ts +271 -0
  259. package/src/lib/server/wallet-execution.test.ts +198 -0
  260. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  261. package/src/lib/server/wallet-portfolio.ts +724 -0
  262. package/src/lib/server/wallet-service.test.ts +57 -0
  263. package/src/lib/server/wallet-service.ts +213 -0
  264. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  265. package/src/lib/server/watch-jobs.ts +17 -2
  266. package/src/lib/server/workspace-context.ts +111 -0
  267. package/src/lib/skill-save-payload.test.ts +39 -0
  268. package/src/lib/skill-save-payload.ts +37 -0
  269. package/src/lib/tasks.ts +28 -0
  270. package/src/lib/tool-definitions.ts +2 -1
  271. package/src/lib/tool-event-summary.test.ts +30 -0
  272. package/src/lib/tool-event-summary.ts +37 -0
  273. package/src/lib/validation/schemas.ts +1 -0
  274. package/src/lib/wallet-transactions.test.ts +75 -0
  275. package/src/lib/wallet-transactions.ts +43 -0
  276. package/src/lib/wallet.test.ts +17 -0
  277. package/src/lib/wallet.ts +183 -0
  278. package/src/proxy.test.ts +31 -0
  279. package/src/proxy.ts +34 -2
  280. package/src/stores/use-chat-store.ts +15 -1
  281. package/src/types/index.ts +249 -14
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useRef, useState } from 'react'
3
+ import { useCallback, useEffect, useRef, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { createAgent, updateAgent, deleteAgent } from '@/lib/agents'
6
6
  import { api } from '@/lib/api-client'
@@ -18,8 +18,48 @@ import { copyTextToClipboard } from '@/lib/clipboard'
18
18
  import { SectionLabel } from '@/components/shared/section-label'
19
19
  import { SoulLibraryPicker } from './soul-library-picker'
20
20
  import { HintTip } from '@/components/shared/hint-tip'
21
+ import { isOllamaCloudModel } from '@/lib/ollama-model'
21
22
 
22
23
  const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
24
+ const FALLBACK_ELEVENLABS_VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb'
25
+
26
+ type AgentSheetSectionId = 'overview' | 'instructions' | 'model' | 'tools'
27
+ type SafeAgentWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & {
28
+ balanceAtomic?: string
29
+ balanceLamports?: number
30
+ balanceFormatted?: string
31
+ balanceSymbol?: string
32
+ isActive?: boolean
33
+ }
34
+
35
+ function SectionCard({
36
+ title,
37
+ description,
38
+ action,
39
+ children,
40
+ className = '',
41
+ }: {
42
+ title: string
43
+ description?: string
44
+ action?: React.ReactNode
45
+ children: React.ReactNode
46
+ className?: string
47
+ }) {
48
+ return (
49
+ <section className={`mb-8 rounded-[20px] border border-white/[0.06] bg-surface/70 p-5 sm:p-6 ${className}`}>
50
+ <div className="mb-5 flex items-start justify-between gap-4">
51
+ <div>
52
+ <h3 className="font-display text-[17px] font-700 tracking-[-0.02em] text-text">{title}</h3>
53
+ {description && (
54
+ <p className="mt-1 text-[13px] leading-[1.6] text-text-3/75">{description}</p>
55
+ )}
56
+ </div>
57
+ {action}
58
+ </div>
59
+ {children}
60
+ </section>
61
+ )
62
+ }
23
63
 
24
64
  function formatHbDuration(sec: number): string {
25
65
  if (sec >= 3600) {
@@ -106,6 +146,7 @@ export function AgentSheet() {
106
146
  const credentials = useAppStore((s) => s.credentials)
107
147
  const loadCredentials = useAppStore((s) => s.loadCredentials)
108
148
  const appSettings = useAppStore((s) => s.appSettings)
149
+ const loadSettings = useAppStore((s) => s.loadSettings)
109
150
  const dynamicSkills = useAppStore((s) => s.skills)
110
151
  const mcpServers = useAppStore((s) => s.mcpServers)
111
152
  const loadSkills = useAppStore((s) => s.loadSkills)
@@ -157,6 +198,7 @@ export function AgentSheet() {
157
198
  const [memoryScopeMode, setMemoryScopeMode] = useState<'auto' | 'all' | 'global' | 'agent' | 'session' | 'project'>('auto')
158
199
  const [memoryTierPreference, setMemoryTierPreference] = useState<'working' | 'durable' | 'archive' | 'blended'>('blended')
159
200
  const [autoRecovery, setAutoRecovery] = useState(false)
201
+ const [disabled, setDisabled] = useState(false)
160
202
  const [voiceId, setVoiceId] = useState('')
161
203
  const [heartbeatEnabled, setHeartbeatEnabled] = useState(false)
162
204
  const [heartbeatIntervalSec, setHeartbeatIntervalSec] = useState('') // '' = default (30m)
@@ -178,7 +220,7 @@ export function AgentSheet() {
178
220
  const [dailyBudget, setDailyBudget] = useState('')
179
221
  const [monthlyBudget, setMonthlyBudget] = useState('')
180
222
  const [budgetAction, setBudgetAction] = useState<'warn' | 'block'>('warn')
181
- const [agentWallet, setAgentWallet] = useState<(Omit<AgentWallet, 'encryptedPrivateKey'> & { balanceLamports?: number; balanceSol?: number }) | null>(null)
223
+ const [agentWallets, setAgentWallets] = useState<SafeAgentWallet[]>([])
182
224
  const [addingKey, setAddingKey] = useState(false)
183
225
  const [newKeyName, setNewKeyName] = useState('')
184
226
  const [newKeyValue, setNewKeyValue] = useState('')
@@ -196,6 +238,12 @@ export function AgentSheet() {
196
238
  const [soulLibraryOpen, setSoulLibraryOpen] = useState(false)
197
239
  const promptFileRef = useRef<HTMLInputElement>(null)
198
240
  const importFileRef = useRef<HTMLInputElement>(null)
241
+ const sectionRefs = useRef<Record<AgentSheetSectionId, HTMLDivElement | null>>({
242
+ overview: null,
243
+ instructions: null,
244
+ model: null,
245
+ tools: null,
246
+ })
199
247
 
200
248
  const handleFileUpload = (setter: (v: string) => void) => (e: React.ChangeEvent<HTMLInputElement>) => {
201
249
  const file = e.target.files?.[0]
@@ -206,12 +254,40 @@ export function AgentSheet() {
206
254
  e.target.value = ''
207
255
  }
208
256
 
257
+ const loadAgentWallets = useCallback(async (agentId: string) => {
258
+ try {
259
+ const wallets = await api<Record<string, SafeAgentWallet>>('GET', `/wallets?agentId=${encodeURIComponent(agentId)}`)
260
+ const matches = Object.values(wallets)
261
+ .filter((wallet) => wallet.agentId === agentId)
262
+ .sort((a, b) => {
263
+ if ((a.isActive ? 1 : 0) !== (b.isActive ? 1 : 0)) return a.isActive ? -1 : 1
264
+ return a.chain.localeCompare(b.chain)
265
+ })
266
+ setAgentWallets(matches)
267
+ } catch {
268
+ setAgentWallets([])
269
+ }
270
+ }, [])
271
+
209
272
  const currentProvider = providers.find((p) => p.id === provider)
210
273
  const providerCredentials = Object.values(credentials).filter((c) => c.provider === provider)
211
274
  const openclawCredentials = Object.values(credentials).filter((c) => c.provider === 'openclaw')
212
275
  const openclawGatewayProfiles = gatewayProfiles.filter((item) => item.provider === 'openclaw')
213
276
  const editing = editingId ? agents[editingId] : null
214
277
  const hasNativeCapabilities = NATIVE_CAPABILITY_PROVIDER_IDS.has(provider)
278
+ const globalVoiceId = typeof appSettings.elevenLabsVoiceId === 'string' ? appSettings.elevenLabsVoiceId.trim() : ''
279
+ const agentVoiceId = voiceId.trim()
280
+ const elevenLabsConfigured = appSettings.elevenLabsApiKeyConfigured === true
281
+ const voiceControlsAvailable = elevenLabsConfigured || appSettings.elevenLabsEnabled === true || !!globalVoiceId || !!agentVoiceId
282
+ const voicePlaybackEnabled = appSettings.elevenLabsEnabled === true
283
+ const effectiveVoiceId = agentVoiceId || globalVoiceId || FALLBACK_ELEVENLABS_VOICE_ID
284
+ const effectiveVoiceSource = agentVoiceId
285
+ ? 'Agent override'
286
+ : globalVoiceId
287
+ ? 'Global default'
288
+ : 'Built-in fallback'
289
+ const providerSummary = openclawEnabled ? 'OpenClaw gateway' : (currentProvider?.name || provider)
290
+ const modelSummary = openclawEnabled ? (gatewayProfileId ? 'Gateway-managed' : 'default') : (model || 'Select a model')
215
291
 
216
292
  const providerNeedsKey = !editing && (
217
293
  (currentProvider?.requiresApiKey && providerCredentials.length === 0 && !addingKey) ||
@@ -220,6 +296,7 @@ export function AgentSheet() {
220
296
 
221
297
  useEffect(() => {
222
298
  if (open) {
299
+ loadSettings()
223
300
  loadProviders()
224
301
  loadGatewayProfiles()
225
302
  loadCredentials()
@@ -254,7 +331,11 @@ export function AgentSheet() {
254
331
  setFallbackCredentialIds(editing.fallbackCredentialIds || [])
255
332
  setCapabilities(Array.isArray(editing.capabilities) ? editing.capabilities : [])
256
333
  setCapInput('')
257
- setOllamaMode(editing.credentialId && editing.provider === 'ollama' ? 'cloud' : 'local')
334
+ setOllamaMode(
335
+ editing.provider === 'ollama' && (Boolean(editing.credentialId) || isOllamaCloudModel(editing.model))
336
+ ? 'cloud'
337
+ : 'local'
338
+ )
258
339
  setOpenclawEnabled(editing.provider === 'openclaw')
259
340
  setProjectId(editing.projectId)
260
341
  setAvatarSeed(editing.avatarSeed || crypto.randomUUID().slice(0, 8))
@@ -263,6 +344,7 @@ export function AgentSheet() {
263
344
  setMemoryScopeMode(editing.memoryScopeMode || 'auto')
264
345
  setMemoryTierPreference(editing.memoryTierPreference || 'blended')
265
346
  setAutoRecovery(editing.autoRecovery || false)
347
+ setDisabled(editing.disabled === true)
266
348
  setVoiceId(editing.elevenLabsVoiceId || '')
267
349
  setHeartbeatEnabled(editing.heartbeatEnabled || false)
268
350
  setHeartbeatIntervalSec(parseDurationToSec(editing.heartbeatInterval, editing.heartbeatIntervalSec))
@@ -288,14 +370,7 @@ export function AgentSheet() {
288
370
  setDailyBudget(typeof editing.dailyBudget === 'number' && editing.dailyBudget > 0 ? String(editing.dailyBudget) : '')
289
371
  setMonthlyBudget(typeof editing.monthlyBudget === 'number' && editing.monthlyBudget > 0 ? String(editing.monthlyBudget) : '')
290
372
  setBudgetAction(editing.budgetAction || 'warn')
291
- // Load wallet if agent has one
292
- if (editing.walletId) {
293
- api<Omit<AgentWallet, 'encryptedPrivateKey'> & { balanceLamports?: number; balanceSol?: number }>('GET', `/wallets/${editing.walletId}`)
294
- .then(setAgentWallet)
295
- .catch(() => setAgentWallet(null))
296
- } else {
297
- setAgentWallet(null)
298
- }
373
+ void loadAgentWallets(editing.id)
299
374
  } else {
300
375
  setName('')
301
376
  setDescription('')
@@ -330,6 +405,7 @@ export function AgentSheet() {
330
405
  setMemoryScopeMode('auto')
331
406
  setMemoryTierPreference('blended')
332
407
  setAutoRecovery(false)
408
+ setDisabled(false)
333
409
  setVoiceId('')
334
410
  setHeartbeatEnabled(false)
335
411
  setHeartbeatIntervalSec('')
@@ -351,6 +427,7 @@ export function AgentSheet() {
351
427
  setDailyBudget('')
352
428
  setMonthlyBudget('')
353
429
  setBudgetAction('warn')
430
+ setAgentWallets([])
354
431
  }
355
432
  }
356
433
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -412,6 +489,10 @@ export function AgentSheet() {
412
489
  setEditingId(null)
413
490
  }
414
491
 
492
+ const jumpToSection = (sectionId: AgentSheetSectionId) => {
493
+ sectionRefs.current[sectionId]?.scrollIntoView({ behavior: 'smooth', block: 'start' })
494
+ }
495
+
415
496
  const applyGatewayProfileSelection = (nextGatewayProfileId: string | null) => {
416
497
  setGatewayProfileId(nextGatewayProfileId)
417
498
  const gateway = openclawGatewayProfiles.find((item) => item.id === nextGatewayProfileId)
@@ -514,6 +595,7 @@ export function AgentSheet() {
514
595
  memoryScopeMode,
515
596
  memoryTierPreference,
516
597
  autoRecovery,
598
+ disabled,
517
599
  elevenLabsVoiceId: voiceId.trim() || null,
518
600
  heartbeatEnabled,
519
601
  heartbeatInterval: heartbeatIntervalSec ? formatHbDuration(Number(heartbeatIntervalSec)) : null,
@@ -578,6 +660,7 @@ export function AgentSheet() {
578
660
  tools: editing.tools,
579
661
  plugins: editing.plugins,
580
662
  capabilities: editing.capabilities,
663
+ elevenLabsVoiceId: editing.elevenLabsVoiceId || null,
581
664
  soul: editing.soul,
582
665
  systemPrompt: editing.systemPrompt,
583
666
  }],
@@ -605,11 +688,12 @@ export function AgentSheet() {
605
688
  if (!importedAgent || typeof importedAgent !== 'object') throw new Error('Invalid agent pack')
606
689
  // Strip IDs and timestamps
607
690
  const { id: _id, createdAt: _ca, updatedAt: _ua, threadSessionId: _ts, ...agentData } = importedAgent
691
+ void [_id, _ca, _ua, _ts]
608
692
  await createAgent({ ...agentData, name: agentData.name || 'Imported Agent' })
609
693
  await loadAgents()
610
694
  toast.success(data?.kind === 'swarmclaw-agent-pack' ? 'Agent pack imported' : 'Agent imported')
611
695
  onClose()
612
- } catch (err) {
696
+ } catch {
613
697
  toast.error('Invalid agent JSON file')
614
698
  }
615
699
  }
@@ -685,9 +769,18 @@ export function AgentSheet() {
685
769
  <BottomSheet open={open} onClose={onClose} wide>
686
770
  <div className="mb-10 flex items-start justify-between">
687
771
  <div>
688
- <h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
689
- {editing ? 'Edit Agent' : 'New Agent'}
690
- </h2>
772
+ <div className="mb-2 flex flex-wrap items-center gap-2">
773
+ <h2 className="font-display text-[28px] font-700 tracking-[-0.03em]">
774
+ {editing ? 'Edit Agent' : 'New Agent'}
775
+ </h2>
776
+ <span className={`rounded-[999px] px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.1em] ${
777
+ disabled
778
+ ? 'border border-amber-400/20 bg-amber-400/[0.08] text-amber-300'
779
+ : 'border border-emerald-400/20 bg-emerald-400/[0.08] text-emerald-300'
780
+ }`}>
781
+ {disabled ? 'Disabled' : 'Enabled'}
782
+ </span>
783
+ </div>
691
784
  <p className="text-[14px] text-text-3">Define an AI agent and optional multi-agent delegation behavior</p>
692
785
  </div>
693
786
  <div className="flex items-center gap-3 mt-1.5">
@@ -720,6 +813,56 @@ export function AgentSheet() {
720
813
  </div>
721
814
  </div>
722
815
 
816
+ <div className="mb-8 rounded-[20px] border border-white/[0.06] bg-white/[0.03] p-4 sm:p-5">
817
+ <div className="grid grid-cols-1 gap-3 md:grid-cols-4">
818
+ <div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
819
+ <p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Provider</p>
820
+ <p className="mt-1 text-[14px] font-600 text-text">{providerSummary}</p>
821
+ </div>
822
+ <div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
823
+ <p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Model</p>
824
+ <p className="mt-1 text-[14px] font-600 text-text">{modelSummary}</p>
825
+ </div>
826
+ <div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
827
+ <p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Voice</p>
828
+ <p className="mt-1 text-[14px] font-600 text-text">{voiceControlsAvailable ? effectiveVoiceSource : 'Not configured'}</p>
829
+ {voiceControlsAvailable && (
830
+ <p className="mt-1 truncate text-[12px] text-text-3/75">{effectiveVoiceId}</p>
831
+ )}
832
+ </div>
833
+ <div className="rounded-[14px] border border-white/[0.05] bg-black/10 p-3">
834
+ <p className="text-[11px] font-600 uppercase tracking-[0.08em] text-text-3">Mode</p>
835
+ <p className="mt-1 text-[14px] font-600 text-text">{canDelegateToAgents ? 'Delegating agent' : 'Solo agent'}</p>
836
+ <p className="mt-1 text-[12px] text-text-3/75">
837
+ {routingTargets.length > 0 ? `${routingTargets.length} route${routingTargets.length === 1 ? '' : 's'} configured` : 'Single primary route'}
838
+ </p>
839
+ </div>
840
+ </div>
841
+ <div className="mt-4 flex flex-wrap gap-2">
842
+ {([
843
+ ['overview', 'Overview'],
844
+ ['instructions', 'Instructions'],
845
+ ['model', 'Model Setup'],
846
+ ['tools', 'Tools'],
847
+ ] as const).map(([sectionId, label]) => (
848
+ <button
849
+ key={sectionId}
850
+ type="button"
851
+ onClick={() => jumpToSection(sectionId)}
852
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-2 text-[12px] font-600 text-text-3 transition-all hover:bg-white/[0.04] hover:text-text-2"
853
+ style={{ fontFamily: 'inherit' }}
854
+ >
855
+ {label}
856
+ </button>
857
+ ))}
858
+ </div>
859
+ </div>
860
+
861
+ <div ref={(node) => { sectionRefs.current.overview = node }}>
862
+ <SectionCard
863
+ title="Overview"
864
+ description="Basic identity, defaults, voice, heartbeat, and budget controls for this agent."
865
+ >
723
866
  <div className="mb-8">
724
867
  <SectionLabel>Name</SectionLabel>
725
868
  <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. SEO Researcher" className={inputClass} style={{ fontFamily: 'inherit' }} />
@@ -756,7 +899,7 @@ export function AgentSheet() {
756
899
  setAvatarSeed('')
757
900
  toast.success('Avatar image uploaded')
758
901
  }
759
- } catch (err: unknown) {
902
+ } catch {
760
903
  toast.error('Failed to upload image')
761
904
  } finally {
762
905
  setUploading(false)
@@ -920,6 +1063,28 @@ export function AgentSheet() {
920
1063
  <p className="text-[11px] text-text-3/70 mt-1.5">Use working for fast recent context, durable for facts/preferences, and archive for long-lived history.</p>
921
1064
  </div>
922
1065
 
1066
+ {/* Auto-Recovery */}
1067
+ <div className="mb-8">
1068
+ <div className="flex items-center justify-between mb-1.5">
1069
+ <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">
1070
+ Agent Availability
1071
+ <HintTip text="Disabled agents stay visible, but cannot take chats, heartbeats, schedules, or queued work until re-enabled." />
1072
+ </label>
1073
+ <div
1074
+ onClick={() => setDisabled(!disabled)}
1075
+ className={`w-9 h-5 rounded-full transition-all relative cursor-pointer ${disabled ? 'bg-amber-400' : 'bg-accent-bright'}`}
1076
+ >
1077
+ <div className={`absolute top-0.5 w-4 h-4 rounded-full bg-white transition-all ${disabled ? 'left-0.5' : 'left-[18px]'}`} />
1078
+ </div>
1079
+ </div>
1080
+ <p className="text-[11px] text-text-3/70">
1081
+ {disabled
1082
+ ? 'This agent is paused. Existing chats remain viewable, but new work is blocked until you switch it back on.'
1083
+ : 'This agent is active and can accept chats, heartbeats, schedules, and queued work.'}
1084
+ {' '}Gateway and remote runtime shutdown stay managed separately in Providers and OpenClaw Deploy.
1085
+ </p>
1086
+ </div>
1087
+
923
1088
  {/* Auto-Recovery */}
924
1089
  <div className="mb-8">
925
1090
  <div className="flex items-center justify-between mb-1.5">
@@ -935,20 +1100,58 @@ export function AgentSheet() {
935
1100
  </div>
936
1101
 
937
1102
  {/* ElevenLabs Voice ID */}
938
- {appSettings.elevenLabsEnabled && (
1103
+ {voiceControlsAvailable && (
939
1104
  <div className="mb-8">
940
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
941
- ElevenLabs Voice ID <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
942
- </label>
1105
+ <div className="mb-3 flex flex-wrap items-start justify-between gap-3">
1106
+ <div>
1107
+ <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">
1108
+ Voice & Audio <span className="normal-case tracking-normal font-normal text-text-3">(optional)</span>
1109
+ </label>
1110
+ <p className="mt-1 text-[12px] leading-[1.6] text-text-3/70">
1111
+ Set an agent-specific ElevenLabs voice or inherit the global default configured in Settings.
1112
+ </p>
1113
+ </div>
1114
+ <div className={`rounded-[12px] border px-3 py-2 text-right ${
1115
+ agentVoiceId
1116
+ ? 'border-accent-bright/25 bg-accent-soft/20'
1117
+ : 'border-white/[0.06] bg-white/[0.03]'
1118
+ }`}>
1119
+ <p className="text-[10px] font-700 uppercase tracking-[0.08em] text-text-3">
1120
+ {effectiveVoiceSource}
1121
+ </p>
1122
+ <p className="mt-1 max-w-[240px] truncate font-mono text-[12px] text-text-2">{effectiveVoiceId}</p>
1123
+ </div>
1124
+ </div>
943
1125
  <input
944
1126
  type="text"
945
1127
  value={voiceId}
946
1128
  onChange={(e) => setVoiceId(e.target.value)}
947
- placeholder="Leave blank for global default"
1129
+ placeholder={globalVoiceId ? `Leave blank to use ${globalVoiceId}` : 'Leave blank for the global default'}
948
1130
  className={inputClass}
949
1131
  style={{ fontFamily: 'inherit' }}
950
1132
  />
951
- <p className="text-[11px] text-text-3/70 mt-1.5">Override the default voice for this agent. Leave blank to use the global default.</p>
1133
+ <div className="mt-2 flex flex-wrap items-center gap-2">
1134
+ {agentVoiceId && (
1135
+ <button
1136
+ type="button"
1137
+ onClick={() => setVoiceId('')}
1138
+ className="rounded-[9px] border border-white/[0.08] bg-transparent px-2.5 py-1.5 text-[11px] font-600 text-text-3 transition-all hover:bg-white/[0.04] hover:text-text-2"
1139
+ style={{ fontFamily: 'inherit' }}
1140
+ >
1141
+ Use global default
1142
+ </button>
1143
+ )}
1144
+ {!voicePlaybackEnabled && (
1145
+ <span className="rounded-[9px] border border-amber-400/20 bg-amber-400/[0.08] px-2.5 py-1.5 text-[11px] font-600 text-amber-300">
1146
+ Voice playback is disabled globally
1147
+ </span>
1148
+ )}
1149
+ </div>
1150
+ <p className="text-[11px] text-text-3/70 mt-2">
1151
+ {globalVoiceId
1152
+ ? `Global default: ${globalVoiceId}. This agent can override it with a different voice ID.`
1153
+ : 'No global default voice ID is set yet. If left blank, the built-in ElevenLabs fallback will be used.'}
1154
+ </p>
952
1155
  </div>
953
1156
  )}
954
1157
 
@@ -1107,27 +1310,27 @@ export function AgentSheet() {
1107
1310
  : 'When a configured cap is exceeded, a warning is shown but runs continue.'}
1108
1311
  </p>
1109
1312
  </div>
1313
+ </SectionCard>
1314
+ </div>
1110
1315
 
1111
1316
  {/* Wallet Section */}
1112
1317
  {editingId && (
1113
1318
  <WalletSection
1114
1319
  agentId={editingId}
1115
- wallet={agentWallet}
1320
+ wallets={agentWallets}
1321
+ activeWalletId={editing?.activeWalletId || editing?.walletId || agentWallets.find((wallet) => wallet.isActive)?.id || null}
1116
1322
  onWalletCreated={async () => {
1117
1323
  await loadAgents()
1118
- // Fetch the wallet for this agent
1119
- try {
1120
- const wallets = await api<Record<string, Omit<AgentWallet, 'encryptedPrivateKey'>>>('GET', '/wallets')
1121
- const match = Object.values(wallets).find((w) => w.agentId === editingId)
1122
- if (match) {
1123
- const detail = await api<Omit<AgentWallet, 'encryptedPrivateKey'> & { balanceLamports?: number; balanceSol?: number }>('GET', `/wallets/${match.id}`)
1124
- setAgentWallet(detail)
1125
- }
1126
- } catch { /* ignore */ }
1324
+ await loadAgentWallets(editingId)
1127
1325
  }}
1128
1326
  />
1129
1327
  )}
1130
1328
 
1329
+ <div ref={(node) => { sectionRefs.current.instructions = node }}>
1330
+ <SectionCard
1331
+ title="Instructions & Continuity"
1332
+ description="Define personality, system behavior, and long-running context this agent should preserve."
1333
+ >
1131
1334
  {provider !== 'openclaw' && (
1132
1335
  <div className="mb-8">
1133
1336
  <label className="flex items-center gap-2 font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-2">
@@ -1322,7 +1525,14 @@ export function AgentSheet() {
1322
1525
  />
1323
1526
  </div>
1324
1527
  </div>
1528
+ </SectionCard>
1529
+ </div>
1325
1530
 
1531
+ <div ref={(node) => { sectionRefs.current.model = node }}>
1532
+ <SectionCard
1533
+ title="Model Setup"
1534
+ description="Choose the provider, credentials, routing, and gateway preferences this agent should use."
1535
+ >
1326
1536
  {/* OpenClaw Gateway Fields */}
1327
1537
  {openclawEnabled && (
1328
1538
  <div className="mb-8 space-y-5">
@@ -1411,13 +1621,13 @@ export function AgentSheet() {
1411
1621
  onClick={async () => {
1412
1622
  setSavingKey(true)
1413
1623
  try {
1414
- const cred = await api<any>('POST', '/credentials', { provider: 'openclaw', name: newKeyName.trim() || 'OpenClaw token', apiKey: newKeyValue.trim() })
1624
+ const cred = await api<{ id: string }>('POST', '/credentials', { provider: 'openclaw', name: newKeyName.trim() || 'OpenClaw token', apiKey: newKeyValue.trim() })
1415
1625
  await loadCredentials()
1416
1626
  setCredentialId(cred.id)
1417
1627
  setAddingKey(false)
1418
1628
  setNewKeyName('')
1419
1629
  setNewKeyValue('')
1420
- } catch (err: any) { toast.error(`Failed to save: ${err.message}`) }
1630
+ } catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
1421
1631
  finally { setSavingKey(false) }
1422
1632
  }}
1423
1633
  className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
@@ -1670,13 +1880,13 @@ export function AgentSheet() {
1670
1880
  onClick={async () => {
1671
1881
  setSavingKey(true)
1672
1882
  try {
1673
- const cred = await api<any>('POST', '/credentials', { provider, name: newKeyName.trim() || `${provider} key`, apiKey: newKeyValue.trim() })
1883
+ const cred = await api<{ id: string }>('POST', '/credentials', { provider, name: newKeyName.trim() || `${provider} key`, apiKey: newKeyValue.trim() })
1674
1884
  await loadCredentials()
1675
1885
  setCredentialId(cred.id)
1676
1886
  setAddingKey(false)
1677
1887
  setNewKeyName('')
1678
1888
  setNewKeyValue('')
1679
- } catch (err: any) { toast.error(`Failed to save: ${err.message}`) }
1889
+ } catch (err: unknown) { toast.error(`Failed to save: ${err instanceof Error ? err.message : String(err)}`) }
1680
1890
  finally { setSavingKey(false) }
1681
1891
  }}
1682
1892
  className="px-4 py-1.5 rounded-[8px] bg-accent-bright text-white text-[12px] font-600 cursor-pointer border-none hover:brightness-110 transition-all disabled:opacity-40"
@@ -1877,7 +2087,14 @@ export function AgentSheet() {
1877
2087
  <p className="text-[11px] text-text-3/70 mt-2">No route pool yet. Add one if this agent should switch between cheaper, stronger, or gateway-specific models.</p>
1878
2088
  )}
1879
2089
  </div>
2090
+ </SectionCard>
2091
+ </div>
1880
2092
 
2093
+ <div ref={(node) => { sectionRefs.current.tools = node }}>
2094
+ <SectionCard
2095
+ title="Tools & Delegation"
2096
+ description="Enable plugins, skills, MCP tools, and delegation behavior for this agent."
2097
+ >
1881
2098
  {/* Plugins — hidden for providers that manage capabilities outside LangGraph */}
1882
2099
  {!hasNativeCapabilities && (
1883
2100
  <div className="mb-8">
@@ -2024,7 +2241,7 @@ export function AgentSheet() {
2024
2241
  </label>
2025
2242
  <p className="text-[12px] text-text-3/60 mb-3">Connect external tool servers to this agent via MCP.</p>
2026
2243
  <div className="flex flex-wrap gap-2">
2027
- {Object.values(mcpServers).map((s: any) => {
2244
+ {Object.values(mcpServers).map((s) => {
2028
2245
  const active = mcpServerIds.includes(s.id)
2029
2246
  return (
2030
2247
  <button
@@ -2056,7 +2273,7 @@ export function AgentSheet() {
2056
2273
  </p>
2057
2274
  <div className="space-y-4">
2058
2275
  {mcpServerIds.map((serverId) => {
2059
- const server = (mcpServers as Record<string, any>)[serverId]
2276
+ const server = mcpServers[serverId]
2060
2277
  const serverTools = mcpTools[serverId]
2061
2278
  if (!server || !serverTools?.length) return null
2062
2279
  const safeName = server.name.replace(/[^a-zA-Z0-9_]/g, '_')
@@ -2121,6 +2338,8 @@ export function AgentSheet() {
2121
2338
  />
2122
2339
  </div>
2123
2340
  )}
2341
+ </SectionCard>
2342
+ </div>
2124
2343
 
2125
2344
  {/* Provider key warning */}
2126
2345
  {providerNeedsKey && (
@@ -4,6 +4,7 @@ import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/heartbeat-defaults'
4
4
  import { useCallback, useEffect, useState, type ReactNode } from 'react'
5
5
  import type { Agent } from '@/types'
6
6
  import { useAppStore } from '@/stores/use-app-store'
7
+ import { api } from '@/lib/api-client'
7
8
  import { AgentAvatar } from './agent-avatar'
8
9
  import { AgentFilesEditor } from './agent-files-editor'
9
10
  import { OpenClawSkillsPanel } from './openclaw-skills-panel'
@@ -11,6 +12,7 @@ import { PermissionPresetSelector } from './permission-preset-selector'
11
12
  import { ExecConfigPanel } from './exec-config-panel'
12
13
  import { SandboxEnvPanel } from './sandbox-env-panel'
13
14
  import { CronJobForm } from './cron-job-form'
15
+ import { toast } from 'sonner'
14
16
 
15
17
  interface Props {
16
18
  agent: Agent
@@ -87,6 +89,12 @@ export function InspectorPanel({ agent, onEditAgent, onClearHistory, onDeleteAge
87
89
  <div className="min-w-0 flex-1">
88
90
  <div className="flex items-center gap-2 min-w-0">
89
91
  <h3 className="font-display text-[16px] font-700 text-text truncate tracking-[-0.02em]">{agent.name}</h3>
92
+ {agent.disabled === true && (
93
+ <span className="inline-flex items-center gap-1 rounded-[7px] border border-amber-400/15 bg-amber-400/[0.1] px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-amber-300">
94
+ <span className="w-1.5 h-1.5 rounded-full bg-amber-300" />
95
+ Disabled
96
+ </span>
97
+ )}
90
98
  {agent.heartbeatEnabled && (
91
99
  <span className="inline-flex items-center gap-1 rounded-[7px] border border-emerald-400/15 bg-emerald-400/10 px-2 py-0.5 text-[10px] font-700 uppercase tracking-[0.12em] text-emerald-300">
92
100
  <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
@@ -189,13 +197,32 @@ interface OverviewTabProps {
189
197
  }
190
198
 
191
199
  function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDeleteChat, isMainChat }: OverviewTabProps) {
200
+ const loadAgents = useAppStore((s) => s.loadAgents)
201
+ const loadSessions = useAppStore((s) => s.loadSessions)
202
+ const [availabilitySaving, setAvailabilitySaving] = useState(false)
192
203
  const summaryStats = [
193
204
  { label: 'Provider', value: PROVIDER_LABELS[agent.provider] || agent.provider.replace(/-/g, ' ') },
194
205
  { label: 'Model', value: agent.model || 'Default' },
195
206
  { label: 'Plugins', value: String(agent.plugins?.length ?? 0) },
196
207
  { label: 'Heartbeat', value: agent.heartbeatEnabled ? `Every ${agent.heartbeatIntervalSec ?? DEFAULT_HEARTBEAT_INTERVAL_SEC}s` : 'Off' },
208
+ { label: 'Status', value: agent.disabled === true ? 'Disabled' : 'Enabled' },
197
209
  ]
198
210
 
211
+ const handleToggleAvailability = useCallback(async () => {
212
+ if (availabilitySaving) return
213
+ setAvailabilitySaving(true)
214
+ try {
215
+ const nextDisabled = agent.disabled !== true
216
+ await api('PUT', `/agents/${agent.id}`, { disabled: nextDisabled })
217
+ await Promise.all([loadAgents(), loadSessions()])
218
+ toast.success(nextDisabled ? `${agent.name} disabled` : `${agent.name} enabled`)
219
+ } catch (err: unknown) {
220
+ toast.error(err instanceof Error ? err.message : 'Failed to update agent availability')
221
+ } finally {
222
+ setAvailabilitySaving(false)
223
+ }
224
+ }, [agent.disabled, agent.id, agent.name, availabilitySaving, loadAgents, loadSessions])
225
+
199
226
  return (
200
227
  <div className="p-4 flex flex-col gap-4">
201
228
  <div className={panelCardClass('p-4 bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.02))]')}>
@@ -259,6 +286,20 @@ function OverviewTab({ agent, onEditAgent, onClearHistory, onDeleteAgent, onDele
259
286
  Edit Agent
260
287
  </button>
261
288
  )}
289
+ <button
290
+ onClick={() => void handleToggleAvailability()}
291
+ disabled={availabilitySaving}
292
+ className={`w-full px-3 py-2.5 rounded-[10px] text-[12px] font-700 border cursor-pointer transition-all text-left disabled:opacity-50 ${
293
+ agent.disabled === true
294
+ ? 'text-emerald-300 bg-emerald-400/[0.06] border-emerald-400/[0.12] hover:bg-emerald-400/[0.1]'
295
+ : 'text-amber-300 bg-amber-400/[0.06] border-amber-400/[0.12] hover:bg-amber-400/[0.1]'
296
+ }`}
297
+ style={{ fontFamily: 'inherit' }}
298
+ >
299
+ {availabilitySaving
300
+ ? (agent.disabled === true ? 'Enabling Agent...' : 'Disabling Agent...')
301
+ : (agent.disabled === true ? 'Enable Agent' : 'Disable Agent')}
302
+ </button>
262
303
  {(onClearHistory || onDeleteAgent || onDeleteChat) && (
263
304
  <>
264
305
  <SectionLabel>Danger Zone</SectionLabel>