@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
@@ -8,26 +8,41 @@ import { toast } from 'sonner'
8
8
  import { useWs } from '@/hooks/use-ws'
9
9
  import { ExecApprovalCard } from '@/components/chat/exec-approval-card'
10
10
  import { getApprovalPayload, getApprovalTitle } from '@/lib/approval-display'
11
- import type { ApprovalRequest } from '@/types'
11
+ import type { AppSettings, ApprovalCategory, ApprovalRequest } from '@/types'
12
12
 
13
13
  const CATEGORY_LABELS: Record<string, string> = {
14
14
  tool_access: 'Plugin Access',
15
15
  wallet_transfer: 'Wallet Transfer',
16
+ wallet_action: 'Wallet Action',
16
17
  plugin_scaffold: 'Plugin Creation',
17
18
  plugin_install: 'Plugin Install',
19
+ connector_sender: 'Connector Sender',
18
20
  task_tool: 'Task Plugin Call',
19
21
  }
20
22
 
21
23
  const CATEGORY_ICONS: Record<string, string> = {
22
24
  tool_access: '🔑',
23
25
  wallet_transfer: '💰',
26
+ wallet_action: '✍️',
24
27
  plugin_scaffold: '🔌',
25
28
  plugin_install: '📦',
29
+ connector_sender: '📲',
26
30
  task_tool: '🤖',
27
31
  }
28
32
 
29
33
  type ApprovalScope = 'all' | 'execution' | 'workflow' | 'task'
30
34
 
35
+ const AUTO_APPROVE_OPTIONS: Array<{ id: ApprovalCategory; label: string; description: string; risk?: 'high' | 'very-high' }> = [
36
+ { id: 'tool_access', label: 'Plugin Access', description: 'Auto-enable requested plugins for a chat.' },
37
+ { id: 'plugin_scaffold', label: 'Plugin Scaffold', description: 'Auto-create plugin files requested by agents.' },
38
+ { id: 'plugin_install', label: 'Plugin Install', description: 'Auto-install plugins from approved URLs.' },
39
+ { id: 'connector_sender', label: 'Connector Senders', description: 'Auto-approve new connector senders and add them to the allowlist.' },
40
+ { id: 'human_loop', label: 'Human Approval Requests', description: 'Auto-approve ask-human approval prompts.' },
41
+ { id: 'wallet_transfer', label: 'Wallet Transfers', description: 'Auto-approve wallet send requests.', risk: 'high' },
42
+ { id: 'wallet_action', label: 'Wallet Actions', description: 'Auto-approve wallet signatures and arbitrary transaction requests.', risk: 'very-high' },
43
+ { id: 'task_tool', label: 'Task Tool Calls', description: 'Auto-approve task-level tool approvals.' },
44
+ ]
45
+
31
46
  function relativeTime(ts: number): string {
32
47
  const diff = Date.now() - ts
33
48
  if (diff < 60_000) return 'just now'
@@ -39,9 +54,11 @@ function relativeTime(ts: number): string {
39
54
  export function ApprovalsPanel() {
40
55
  const tasks = useAppStore((s) => s.tasks)
41
56
  const agents = useAppStore((s) => s.agents)
57
+ const appSettings = useAppStore((s) => s.appSettings)
42
58
  const serverApprovals = useAppStore((s) => s.approvals)
43
59
  const loadTasks = useAppStore((s) => s.loadTasks)
44
60
  const loadServerApprovals = useAppStore((s) => s.loadApprovals)
61
+ const loadAppSettings = useAppStore((s) => s.loadSettings)
45
62
 
46
63
  const execApprovals = useApprovalStore((s) => s.approvals)
47
64
  const loadExecApprovals = useApprovalStore((s) => s.loadApprovals)
@@ -73,12 +90,17 @@ export function ApprovalsPanel() {
73
90
  const [scope, setScope] = useState<ApprovalScope>('all')
74
91
  const [categoryFilter, setCategoryFilter] = useState('all')
75
92
  const [now, setNow] = useState(() => Date.now())
93
+ const [savingSetting, setSavingSetting] = useState<string | null>(null)
76
94
 
77
95
  useEffect(() => {
78
96
  const intervalId = window.setInterval(() => setNow(Date.now()), 60_000)
79
97
  return () => window.clearInterval(intervalId)
80
98
  }, [])
81
99
 
100
+ useEffect(() => {
101
+ void loadAppSettings()
102
+ }, [loadAppSettings])
103
+
82
104
  const taskApprovals = useMemo(() => {
83
105
  return Object.values(tasks)
84
106
  .filter((t) => t.pendingApproval)
@@ -158,28 +180,51 @@ export function ApprovalsPanel() {
158
180
  label: 'Execution',
159
181
  value: sortedExecApprovals.length,
160
182
  tone: 'text-amber-400',
183
+ dotClass: 'bg-amber-400',
161
184
  hint: 'Command approvals from OpenClaw',
162
185
  },
163
186
  {
164
187
  label: 'Workflow',
165
188
  value: sessionApprovals.length,
166
189
  tone: 'text-sky-400',
190
+ dotClass: 'bg-sky-400',
167
191
  hint: 'Agent and plugin governance requests',
168
192
  },
169
193
  {
170
194
  label: 'Task Calls',
171
195
  value: taskApprovals.length,
172
196
  tone: 'text-violet-400',
197
+ dotClass: 'bg-violet-400',
173
198
  hint: 'Tasks waiting on tool approval',
174
199
  },
175
200
  {
176
201
  label: 'Recently Active',
177
202
  value: workflowApprovals.filter((req) => now - req.updatedAt < 60 * 60 * 1000).length,
178
203
  tone: 'text-emerald-400',
204
+ dotClass: 'bg-emerald-400',
179
205
  hint: 'Updated in the last hour',
180
206
  },
181
207
  ]
182
208
 
209
+ const autoApproved = useMemo(() => new Set(appSettings.approvalAutoApproveCategories || []), [appSettings.approvalAutoApproveCategories])
210
+ const approvalsEnabled = appSettings.approvalsEnabled ?? false
211
+ const outboundApprovalEnabled = appSettings.safetyRequireApprovalForOutbound ?? false
212
+ const autoApproveEnabledCount = autoApproved.size
213
+ const autoApproveManualCount = AUTO_APPROVE_OPTIONS.length - autoApproveEnabledCount
214
+
215
+ const saveApprovalSettings = async (patch: Partial<AppSettings>, successMessage: string, key: string) => {
216
+ try {
217
+ setSavingSetting(key)
218
+ const settings = await api<AppSettings>('PUT', '/settings', patch)
219
+ useAppStore.setState({ appSettings: settings })
220
+ toast.success(successMessage)
221
+ } catch (err: unknown) {
222
+ toast.error(err instanceof Error ? err.message : 'Failed to update approval settings')
223
+ } finally {
224
+ setSavingSetting((current) => (current === key ? null : current))
225
+ }
226
+ }
227
+
183
228
  const handleDecision = async (req: ApprovalRequest, approved: boolean) => {
184
229
  try {
185
230
  if (req.category === 'task_tool') {
@@ -195,51 +240,235 @@ export function ApprovalsPanel() {
195
240
  }
196
241
  }
197
242
 
198
- if (pendingCount === 0) {
199
- return (
200
- <div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
201
- <div className="w-16 h-16 rounded-[24px] bg-white/[0.02] border border-white/[0.04] flex items-center justify-center mb-6">
202
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40">
203
- <path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
204
- <path d="m9 12 2 2 4-4"/>
205
- </svg>
206
- </div>
207
- <h2 className="font-display text-[18px] font-600 text-text-2 mb-2">No pending approvals</h2>
208
- <p className="text-[13px] text-text-3/60 max-w-[320px]">
209
- Your swarm is operating autonomously. Actions requiring oversight will appear here.
210
- </p>
211
- </div>
212
- )
213
- }
214
-
215
243
  return (
216
244
  <div className="flex-1 overflow-y-auto px-6 py-8">
217
- <div className="max-w-3xl mx-auto">
218
- <div className="flex items-center justify-between mb-8">
245
+ <div className="max-w-5xl mx-auto space-y-6">
246
+ <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
219
247
  <div>
220
248
  <h1 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-1">Approvals</h1>
221
249
  <p className="text-[13px] text-text-3">Execution, task, and governance requests queued for review</p>
222
250
  </div>
223
- <div className="px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-400 text-[11px] font-600">
224
- {pendingCount} Pending
251
+ <div className="flex flex-wrap items-center gap-2">
252
+ <div className={`px-3 py-1.5 rounded-full border text-[11px] font-700 ${
253
+ approvalsEnabled
254
+ ? 'bg-amber-500/10 border-amber-500/20 text-amber-300'
255
+ : 'bg-emerald-500/10 border-emerald-500/20 text-emerald-300'
256
+ }`}>
257
+ {approvalsEnabled ? 'Manual approvals enabled' : 'Approvals auto-run'}
258
+ </div>
259
+ <div className="px-3 py-1.5 rounded-full bg-white/[0.04] border border-white/[0.06] text-text-2 text-[11px] font-600">
260
+ {pendingCount} pending
261
+ </div>
225
262
  </div>
226
263
  </div>
227
264
 
228
- <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-6">
265
+ <div className="grid grid-cols-2 xl:grid-cols-4 gap-3">
229
266
  {summaryCards.map((card) => (
230
- <div key={card.label} className="rounded-[14px] border border-white/[0.06] bg-white/[0.02] px-4 py-3.5">
267
+ <div key={card.label} className="relative overflow-hidden rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-4 py-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
268
+ <div className="flex items-center justify-between gap-3">
269
+ <div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/65">{card.label}</div>
270
+ <div className={`h-2.5 w-2.5 rounded-full ${card.dotClass}`} />
271
+ </div>
231
272
  <div className={`text-[22px] font-display font-700 tracking-[-0.03em] ${card.tone}`}>
232
273
  {card.value}
233
274
  </div>
234
- <div className="text-[11px] font-600 text-text-2 mt-0.5">{card.label}</div>
235
- <p className="text-[10px] text-text-3/50 mt-1 leading-relaxed">{card.hint}</p>
275
+ <p className="text-[10px] text-text-3/55 mt-1.5 leading-relaxed">{card.hint}</p>
236
276
  </div>
237
277
  ))}
238
278
  </div>
239
279
 
240
- <div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4 mb-6">
241
- <div className="flex flex-col lg:flex-row gap-3 lg:items-center lg:justify-between">
242
- <div className="flex flex-wrap gap-2">
280
+ <div className="grid gap-4 xl:grid-cols-[320px_minmax(0,1fr)]">
281
+ <div className="rounded-[18px] border border-white/[0.06] bg-white/[0.02] p-5 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
282
+ <div className="flex items-center justify-between gap-3 mb-4">
283
+ <div>
284
+ <div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/65">Approval Mode</div>
285
+ <div className="text-[16px] font-display font-700 tracking-[-0.02em] text-text mt-1">
286
+ {approvalsEnabled ? 'Manual review queue' : 'Auto-run workflow mode'}
287
+ </div>
288
+ </div>
289
+ <div className={`px-2.5 py-1 rounded-full text-[10px] font-700 border ${
290
+ approvalsEnabled
291
+ ? 'bg-amber-500/10 border-amber-500/20 text-amber-300'
292
+ : 'bg-emerald-500/10 border-emerald-500/20 text-emerald-300'
293
+ }`}>
294
+ {approvalsEnabled ? 'On' : 'Off'}
295
+ </div>
296
+ </div>
297
+ <p className="text-[12px] text-text-3/70 leading-relaxed">
298
+ {approvalsEnabled
299
+ ? 'Requests pause here until someone approves or rejects them. Auto-approve lets you carve out safe request classes without disabling oversight entirely.'
300
+ : 'Workflow approvals will auto-run by default. Use outbound and category-specific controls below to keep higher-risk actions gated.'}
301
+ </p>
302
+ <div className="grid grid-cols-1 gap-2 mt-5">
303
+ <div className="rounded-[12px] border border-white/[0.06] bg-black/20 px-3.5 py-3">
304
+ <div className="text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/55">Queue</div>
305
+ <div className="mt-1 flex items-center justify-between gap-3">
306
+ <span className="text-[13px] font-600 text-text-2">Pending right now</span>
307
+ <span className="text-[13px] font-700 text-text">{pendingCount}</span>
308
+ </div>
309
+ </div>
310
+ <div className="rounded-[12px] border border-white/[0.06] bg-black/20 px-3.5 py-3">
311
+ <div className="text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/55">Auto-Approve</div>
312
+ <div className="mt-1 flex items-center justify-between gap-3">
313
+ <span className="text-[13px] font-600 text-text-2">Categories enabled</span>
314
+ <span className="text-[13px] font-700 text-text">{autoApproveEnabledCount}</span>
315
+ </div>
316
+ <div className="text-[11px] text-text-3/55 mt-1">{autoApproveManualCount} still require manual review</div>
317
+ </div>
318
+ <div className="rounded-[12px] border border-white/[0.06] bg-black/20 px-3.5 py-3">
319
+ <div className="text-[10px] font-700 uppercase tracking-[0.08em] text-text-3/55">Outbound Sends</div>
320
+ <div className="mt-1 flex items-center justify-between gap-3">
321
+ <span className="text-[13px] font-600 text-text-2">Connector message sends</span>
322
+ <span className={`text-[11px] font-700 ${outboundApprovalEnabled ? 'text-amber-300' : 'text-emerald-300'}`}>
323
+ {outboundApprovalEnabled ? 'Needs approval' : 'Direct send'}
324
+ </span>
325
+ </div>
326
+ </div>
327
+ </div>
328
+ </div>
329
+
330
+ <div className="rounded-[18px] border border-white/[0.06] bg-white/[0.02] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
331
+ <div className="flex flex-col gap-4">
332
+ <div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-3">
333
+ <div>
334
+ <h2 className="text-[13px] font-700 text-text">Approval Controls</h2>
335
+ <p className="text-[12px] text-text-3/70 mt-1 max-w-[640px]">
336
+ Control whether actions queue for review, which approval types auto-run, and whether outbound connector sends need explicit confirmation.
337
+ </p>
338
+ </div>
339
+ </div>
340
+
341
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
342
+ <div className="rounded-[14px] border border-white/[0.06] bg-black/20 px-4 py-4">
343
+ <div className="flex items-center justify-between gap-4">
344
+ <div>
345
+ <div className="text-[12px] font-600 text-text-2">Platform Approvals</div>
346
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
347
+ Turn this off to auto-approve workflow approvals across the app. Audit records are still kept.
348
+ </p>
349
+ </div>
350
+ <button
351
+ type="button"
352
+ disabled={savingSetting === 'approvalsEnabled'}
353
+ onClick={() => {
354
+ const next = !approvalsEnabled
355
+ void saveApprovalSettings(
356
+ { approvalsEnabled: next },
357
+ next ? 'Platform approvals enabled' : 'Platform approvals disabled',
358
+ 'approvalsEnabled',
359
+ )
360
+ }}
361
+ className={`inline-flex h-[22px] w-10 shrink-0 items-center rounded-full border border-white/[0.08] p-[3px] transition-colors duration-200 cursor-pointer disabled:opacity-50 ${
362
+ approvalsEnabled ? 'justify-end bg-accent' : 'justify-start bg-white/[0.16]'
363
+ }`}
364
+ aria-label="Toggle platform approvals"
365
+ >
366
+ <span className="h-4 w-4 rounded-full bg-white shadow-[0_1px_4px_rgba(0,0,0,0.35)]" />
367
+ </button>
368
+ </div>
369
+ </div>
370
+
371
+ <div className="rounded-[14px] border border-white/[0.06] bg-black/20 px-4 py-4">
372
+ <div className="flex items-center justify-between gap-4">
373
+ <div>
374
+ <div className="text-[12px] font-600 text-text-2">Outbound Send Approvals</div>
375
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
376
+ Require explicit approval before agents send messages or media over connectors.
377
+ </p>
378
+ </div>
379
+ <button
380
+ type="button"
381
+ disabled={savingSetting === 'safetyRequireApprovalForOutbound'}
382
+ onClick={() => {
383
+ const next = !outboundApprovalEnabled
384
+ void saveApprovalSettings(
385
+ { safetyRequireApprovalForOutbound: next },
386
+ next ? 'Outbound send approvals enabled' : 'Outbound send approvals disabled',
387
+ 'safetyRequireApprovalForOutbound',
388
+ )
389
+ }}
390
+ className={`inline-flex h-[22px] w-10 shrink-0 items-center rounded-full border border-white/[0.08] p-[3px] transition-colors duration-200 cursor-pointer disabled:opacity-50 ${
391
+ outboundApprovalEnabled ? 'justify-end bg-accent' : 'justify-start bg-white/[0.16]'
392
+ }`}
393
+ aria-label="Toggle outbound send approvals"
394
+ >
395
+ <span className="h-4 w-4 rounded-full bg-white shadow-[0_1px_4px_rgba(0,0,0,0.35)]" />
396
+ </button>
397
+ </div>
398
+ </div>
399
+ </div>
400
+
401
+ <div>
402
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 mb-3">
403
+ <div>
404
+ <div className="text-[12px] font-600 text-text-2">Auto-Approve Categories</div>
405
+ <p className="text-[11px] text-text-3/60 mt-1">
406
+ Keep the approval system on, but let low-friction request types flow through automatically.
407
+ </p>
408
+ </div>
409
+ <div className="text-[11px] text-text-3/60">
410
+ {autoApproveEnabledCount} enabled
411
+ </div>
412
+ </div>
413
+ <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-2">
414
+ {AUTO_APPROVE_OPTIONS.map((option) => {
415
+ const checked = autoApproved.has(option.id)
416
+ return (
417
+ <label
418
+ key={option.id}
419
+ className={`rounded-[14px] border px-3 py-3 cursor-pointer transition-all ${
420
+ checked
421
+ ? 'border-accent-bright/30 bg-accent-soft/60'
422
+ : 'border-white/[0.06] bg-black/20 hover:bg-white/[0.04]'
423
+ }`}
424
+ >
425
+ <div className="flex items-start gap-3">
426
+ <input
427
+ type="checkbox"
428
+ checked={checked}
429
+ disabled={savingSetting === `auto:${option.id}`}
430
+ onChange={(e) => {
431
+ const next = new Set(appSettings.approvalAutoApproveCategories || [])
432
+ if (e.target.checked) next.add(option.id)
433
+ else next.delete(option.id)
434
+ void saveApprovalSettings(
435
+ { approvalAutoApproveCategories: [...next] },
436
+ checked ? `${option.label} now requires approval` : `${option.label} will auto-approve`,
437
+ `auto:${option.id}`,
438
+ )
439
+ }}
440
+ className="mt-0.5"
441
+ />
442
+ <div>
443
+ <div className="flex items-center gap-2 flex-wrap">
444
+ <div className="text-[12px] font-600 text-text-2">{option.label}</div>
445
+ {option.risk && (
446
+ <span className={`px-1.5 py-0.5 rounded-full text-[9px] font-700 uppercase tracking-[0.08em] ${
447
+ option.risk === 'very-high'
448
+ ? 'bg-red-500/10 text-red-300 border border-red-500/20'
449
+ : 'bg-amber-500/10 text-amber-300 border border-amber-500/20'
450
+ }`}>
451
+ {option.risk === 'very-high' ? 'Very high risk' : 'High risk'}
452
+ </span>
453
+ )}
454
+ </div>
455
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">{option.description}</p>
456
+ </div>
457
+ </div>
458
+ </label>
459
+ )
460
+ })}
461
+ </div>
462
+ </div>
463
+ </div>
464
+ </div>
465
+ </div>
466
+
467
+ <div className="rounded-[18px] border border-white/[0.06] bg-white/[0.02] p-4 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
468
+ <div className="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
469
+ <div className="flex-1">
470
+ <div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/60 mb-2">Queue Filters</div>
471
+ <div className="flex flex-wrap gap-2">
243
472
  {([
244
473
  ['all', `All (${pendingCount})`],
245
474
  ['execution', `Execution (${sortedExecApprovals.length})`],
@@ -259,10 +488,11 @@ export function ApprovalsPanel() {
259
488
  {label}
260
489
  </button>
261
490
  ))}
491
+ </div>
262
492
  </div>
263
493
 
264
- <div className="flex items-center gap-2">
265
- <div className="text-[11px] text-text-3/60 font-600">
494
+ <div className="flex items-center gap-2 justify-between lg:justify-end">
495
+ <div className="text-[11px] text-text-3/60 font-600 whitespace-nowrap">
266
496
  Showing {visibleCount} of {pendingCount}
267
497
  </div>
268
498
  {workflowCategories.length > 1 && scope !== 'execution' && (
@@ -283,7 +513,7 @@ export function ApprovalsPanel() {
283
513
  </div>
284
514
  </div>
285
515
 
286
- <div className="mt-3">
516
+ <div className="mt-1">
287
517
  <input
288
518
  value={search}
289
519
  onChange={(e) => setSearch(e.target.value)}
@@ -295,7 +525,7 @@ export function ApprovalsPanel() {
295
525
  </div>
296
526
 
297
527
  {filteredExecApprovals.length > 0 && (
298
- <div className="mb-6">
528
+ <div>
299
529
  <h2 className="text-[12px] font-700 uppercase tracking-[0.1em] text-amber-400/90 mb-2">Execution Approvals</h2>
300
530
  <div className="grid grid-cols-1 gap-3">
301
531
  {filteredExecApprovals.map((approval) => (
@@ -406,11 +636,36 @@ export function ApprovalsPanel() {
406
636
  )}
407
637
 
408
638
  {visibleCount === 0 && pendingCount > 0 && (
409
- <div className="rounded-[16px] border border-dashed border-white/[0.08] px-6 py-10 text-center">
639
+ <div className="rounded-[18px] border border-dashed border-white/[0.08] bg-white/[0.015] px-6 py-10 text-center">
410
640
  <p className="text-[13px] font-600 text-text-2 mb-1">No approvals match the current filters</p>
411
641
  <p className="text-[12px] text-text-3/60">Try clearing the search or switching the queue scope.</p>
412
642
  </div>
413
643
  )}
644
+
645
+ {pendingCount === 0 && (
646
+ <div className="rounded-[20px] border border-white/[0.06] bg-white/[0.02] px-8 py-12 text-center shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
647
+ <div className="w-16 h-16 rounded-[24px] bg-white/[0.02] border border-white/[0.04] flex items-center justify-center mx-auto mb-6">
648
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3/40">
649
+ <path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"/>
650
+ <path d="m9 12 2 2 4-4"/>
651
+ </svg>
652
+ </div>
653
+ <h2 className="font-display text-[20px] font-600 text-text-2 mb-2">No pending approvals</h2>
654
+ <p className="text-[13px] text-text-3/60 max-w-[420px] mx-auto">
655
+ {approvalsEnabled
656
+ ? 'Your swarm is operating autonomously. Actions requiring oversight will appear here.'
657
+ : 'Approvals are currently disabled, so eligible requests will auto-run instead of queuing here.'}
658
+ </p>
659
+ <div className={`inline-flex items-center gap-2 mt-5 px-3 py-1.5 rounded-full border text-[11px] font-700 ${
660
+ approvalsEnabled
661
+ ? 'bg-amber-500/10 border-amber-500/20 text-amber-300'
662
+ : 'bg-emerald-500/10 border-emerald-500/20 text-emerald-300'
663
+ }`}>
664
+ <span className={`w-2 h-2 rounded-full ${approvalsEnabled ? 'bg-amber-300' : 'bg-emerald-300'}`} />
665
+ {approvalsEnabled ? 'Manual approvals active' : 'Workflow approvals auto-run'}
666
+ </div>
667
+ </div>
668
+ )}
414
669
  </div>
415
670
  </div>
416
671
  )