@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
@@ -7,8 +7,10 @@ const APPROVAL_CATEGORY_OPTIONS: Array<{ id: ApprovalCategory; label: string; de
7
7
  { id: 'tool_access', label: 'Plugin Access', description: 'Auto-enable requested plugins for a chat.' },
8
8
  { id: 'plugin_scaffold', label: 'Plugin Scaffold', description: 'Auto-create plugin files requested by agents.' },
9
9
  { id: 'plugin_install', label: 'Plugin Install', description: 'Auto-install plugins from approved URLs.' },
10
+ { id: 'connector_sender', label: 'Connector Senders', description: 'Auto-approve new connector senders and add them to the allowlist.' },
10
11
  { id: 'human_loop', label: 'Human Approval Requests', description: 'Auto-approve ask-human approval prompts.' },
11
12
  { id: 'wallet_transfer', label: 'Wallet Transfers', description: 'Auto-approve wallet send requests. High risk.' },
13
+ { id: 'wallet_action', label: 'Wallet Actions', description: 'Auto-approve wallet signatures and arbitrary transaction requests. Very high risk.' },
12
14
  { id: 'task_tool', label: 'Task Tool Calls', description: 'Reserved for task-level approval flows.' },
13
15
  ]
14
16
 
@@ -46,6 +48,44 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
46
48
  </div>
47
49
 
48
50
  <div className="grid grid-cols-1 gap-4">
51
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
52
+ <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
53
+ <div className="flex items-center justify-between gap-4">
54
+ <div>
55
+ <div className="text-[12px] font-600 text-text-2">Task Management</div>
56
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
57
+ Controls the task board and agent access to durable backlog tracking. Internal queue execution still works underneath.
58
+ </p>
59
+ </div>
60
+ <button
61
+ onClick={() => patchSettings({ taskManagementEnabled: !(appSettings.taskManagementEnabled ?? true) })}
62
+ className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.taskManagementEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
63
+ aria-label="Toggle task management"
64
+ >
65
+ <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.taskManagementEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
66
+ </button>
67
+ </div>
68
+ </div>
69
+
70
+ <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
71
+ <div className="flex items-center justify-between gap-4">
72
+ <div>
73
+ <div className="text-[12px] font-600 text-text-2">Project Management</div>
74
+ <p className="text-[11px] text-text-3/60 mt-1 leading-relaxed">
75
+ Controls the project operating-system UI and agent access to durable project context for objectives, credentials, and heartbeat plans.
76
+ </p>
77
+ </div>
78
+ <button
79
+ onClick={() => patchSettings({ projectManagementEnabled: !(appSettings.projectManagementEnabled ?? true) })}
80
+ className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.projectManagementEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
81
+ aria-label="Toggle project management"
82
+ >
83
+ <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.projectManagementEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
84
+ </button>
85
+ </div>
86
+ </div>
87
+ </div>
88
+
49
89
  <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
50
90
  <div className="flex items-center justify-between gap-4">
51
91
  <div>
@@ -55,11 +95,13 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
55
95
  </p>
56
96
  </div>
57
97
  <button
58
- onClick={() => patchSettings({ approvalsEnabled: !(appSettings.approvalsEnabled ?? true) })}
59
- className={`relative w-10 h-[22px] rounded-full transition-colors duration-200 cursor-pointer ${(appSettings.approvalsEnabled ?? true) ? 'bg-accent' : 'bg-white/[0.12]'}`}
98
+ onClick={() => patchSettings({ approvalsEnabled: !(appSettings.approvalsEnabled ?? false) })}
99
+ 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 ${
100
+ (appSettings.approvalsEnabled ?? false) ? 'justify-end bg-accent' : 'justify-start bg-white/[0.16]'
101
+ }`}
60
102
  aria-label="Toggle platform approvals"
61
103
  >
62
- <span className={`absolute top-[3px] left-[3px] w-4 h-4 rounded-full bg-white transition-transform duration-200 ${(appSettings.approvalsEnabled ?? true) ? 'translate-x-[18px]' : ''}`} />
104
+ <span className="h-4 w-4 rounded-full bg-white shadow-[0_1px_4px_rgba(0,0,0,0.35)]" />
63
105
  </button>
64
106
  </div>
65
107
  </div>
@@ -5,6 +5,8 @@ import type { SettingsSectionProps } from './types'
5
5
  export function VoiceSection({ appSettings, patchSettings, inputClass }: SettingsSectionProps) {
6
6
  const enabled = appSettings.elevenLabsEnabled ?? false
7
7
  const hasApiKey = appSettings.elevenLabsApiKeyConfigured === true
8
+ const defaultVoiceId = typeof appSettings.elevenLabsVoiceId === 'string' ? appSettings.elevenLabsVoiceId.trim() : ''
9
+ const showVoiceConfig = enabled || hasApiKey || Boolean(defaultVoiceId)
8
10
 
9
11
  return (
10
12
  <div className="mb-10">
@@ -12,7 +14,7 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
12
14
  Voice
13
15
  </h3>
14
16
  <p className="text-[12px] text-text-3 mb-5">
15
- Configure voice playback (TTS) and speech-to-text input.
17
+ Configure voice playback (TTS), the default ElevenLabs voice, and speech-to-text input.
16
18
  </p>
17
19
  <div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
18
20
  {/* ElevenLabs toggle */}
@@ -30,7 +32,7 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
30
32
  </button>
31
33
  </div>
32
34
 
33
- {enabled && (
35
+ {showVoiceConfig && (
34
36
  <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-5">
35
37
  <div>
36
38
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">API Key</label>
@@ -56,11 +58,17 @@ export function VoiceSection({ appSettings, patchSettings, inputClass }: Setting
56
58
  className={inputClass}
57
59
  style={{ fontFamily: 'inherit' }}
58
60
  />
59
- <p className="text-[11px] text-text-3/60 mt-1.5">Fallback voice when an agent has no override set.</p>
61
+ <p className="text-[11px] text-text-3/60 mt-1.5">Fallback voice when an agent has no override set. Agents can override this in their own create/edit sheet.</p>
60
62
  </div>
61
63
  </div>
62
64
  )}
63
65
 
66
+ {showVoiceConfig && !enabled && (
67
+ <p className="mb-5 rounded-[12px] border border-white/[0.06] bg-white/[0.03] px-3 py-2.5 text-[11px] text-text-3/70">
68
+ ElevenLabs credentials and default voice can be prepared here even while playback is turned off.
69
+ </p>
70
+ )}
71
+
64
72
  <div>
65
73
  <label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Speech Recognition Language</label>
66
74
  <input
@@ -275,6 +275,14 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
275
275
  const scopedAgents = skillScope === 'agent'
276
276
  ? skillAgentIds.map((id) => agents[id]).filter(Boolean)
277
277
  : []
278
+ const securityTone = skill.security?.level === 'high'
279
+ ? 'bg-red-500/10 text-red-300 border-red-500/20'
280
+ : skill.security?.level === 'medium'
281
+ ? 'bg-amber-500/10 text-amber-300 border-amber-500/20'
282
+ : 'bg-emerald-500/10 text-emerald-300 border-emerald-500/20'
283
+ const requirementCount = (skill.skillRequirements?.env?.length || 0)
284
+ + (skill.skillRequirements?.bins?.length || 0)
285
+ + (skill.skillRequirements?.config?.length || 0)
278
286
  return (
279
287
  <div
280
288
  key={skill.id}
@@ -315,6 +323,23 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
315
323
  {skill.description && (
316
324
  <p className="text-[12px] text-text-3/60 line-clamp-2">{skill.description}</p>
317
325
  )}
326
+ <div className="mt-2 flex flex-wrap gap-1.5">
327
+ {skill.version && (
328
+ <span className="rounded-full border border-white/[0.08] px-2 py-1 text-[10px] font-700 text-text-3/70">
329
+ v{skill.version}
330
+ </span>
331
+ )}
332
+ {typeof requirementCount === 'number' && requirementCount > 0 && (
333
+ <span className="rounded-full border border-white/[0.08] px-2 py-1 text-[10px] font-700 text-text-3/70">
334
+ {requirementCount} reqs
335
+ </span>
336
+ )}
337
+ {skill.security && (
338
+ <span className={`rounded-full border px-2 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${securityTone}`}>
339
+ {skill.security.level}
340
+ </span>
341
+ )}
342
+ </div>
318
343
  <div className="flex items-center gap-2 mt-1.5">
319
344
  <span className="text-[11px] text-text-3/70">{skill.content.length} chars</span>
320
345
  <span className="text-[11px] text-text-3/60">·</span>
@@ -1,11 +1,14 @@
1
1
  'use client'
2
2
 
3
- import { useEffect, useState, useRef } from 'react'
3
+ import { useEffect, useState, useRef, type ChangeEvent } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { BottomSheet } from '@/components/shared/bottom-sheet'
6
+ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
6
7
  import { AgentAvatar } from '@/components/agents/agent-avatar'
7
8
  import { api } from '@/lib/api-client'
9
+ import { buildSkillSavePayload } from '@/lib/skill-save-payload'
8
10
  import { toast } from 'sonner'
11
+ import type { Skill, SkillSecuritySummary } from '@/types'
9
12
 
10
13
  export function SkillSheet() {
11
14
  const open = useAppStore((s) => s.skillSheetOpen)
@@ -28,6 +31,9 @@ export function SkillSheet() {
28
31
  const [importingUrl, setImportingUrl] = useState(false)
29
32
  const [importError, setImportError] = useState('')
30
33
  const [importNotice, setImportNotice] = useState('')
34
+ const [metadataPreview, setMetadataPreview] = useState<Partial<Skill> | null>(null)
35
+ const [confirmDelete, setConfirmDelete] = useState(false)
36
+ const [deleting, setDeleting] = useState(false)
31
37
 
32
38
  const editing = editingId ? skills[editingId] : null
33
39
  const agentList = Object.values(agents)
@@ -38,13 +44,14 @@ export function SkillSheet() {
38
44
  setImportError('')
39
45
  setImportNotice('')
40
46
  try {
41
- const result = await api<{ name: string; filename: string; description?: string; content: string; sourceFormat?: 'openclaw' | 'plain' }>('POST', '/skills/import', { url: importUrl.trim() })
47
+ const result = await api<Partial<Skill> & { name: string; filename: string; description?: string; content: string; sourceFormat?: 'openclaw' | 'plain' }>('POST', '/skills/import', { url: importUrl.trim() })
42
48
  setName(result.name || '')
43
49
  setFilename(result.filename || '')
44
50
  setDescription(result.description || '')
45
51
  setContent(result.content || '')
52
+ setMetadataPreview(result)
46
53
  if (result.sourceFormat === 'openclaw') {
47
- setImportNotice('Imported OpenClaw SKILL.md format and stripped frontmatter automatically.')
54
+ setImportNotice(`Imported OpenClaw SKILL.md format.${result.security ? ` Security review: ${result.security.level}.` : ''}`)
48
55
  } else {
49
56
  setImportNotice('Skill imported from URL.')
50
57
  }
@@ -73,6 +80,7 @@ export function SkillSheet() {
73
80
  setContent(editing.content)
74
81
  setScope(editing.scope || 'global')
75
82
  setAgentIds(editing.agentIds || [])
83
+ setMetadataPreview(editing)
76
84
  } else {
77
85
  setName('')
78
86
  setFilename('')
@@ -80,16 +88,19 @@ export function SkillSheet() {
80
88
  setContent('')
81
89
  setScope('global')
82
90
  setAgentIds([])
91
+ setMetadataPreview(null)
83
92
  }
84
93
  }
85
94
  }, [open, editingId])
86
95
 
87
96
  const onClose = () => {
97
+ setConfirmDelete(false)
98
+ setDeleting(false)
88
99
  setOpen(false)
89
100
  setEditingId(null)
90
101
  }
91
102
 
92
- const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
103
+ const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
93
104
  const file = e.target.files?.[0]
94
105
  if (!file) return
95
106
  const reader = new FileReader()
@@ -114,14 +125,14 @@ export function SkillSheet() {
114
125
  : `${agentIds.length} agent(s) selected`
115
126
 
116
127
  const handleSave = async () => {
117
- const data = {
118
- name: name.trim() || 'Unnamed Skill',
119
- filename: filename.trim() || `${name.trim().toLowerCase().replace(/\s+/g, '-')}.md`,
128
+ const data = buildSkillSavePayload({
129
+ name,
130
+ filename,
120
131
  description,
121
132
  content,
122
133
  scope,
123
- agentIds: scope === 'agent' ? agentIds : [],
124
- }
134
+ agentIds,
135
+ }, metadataPreview)
125
136
  try {
126
137
  if (editing) {
127
138
  await api('PUT', `/skills/${editing.id}`, data)
@@ -139,19 +150,25 @@ export function SkillSheet() {
139
150
 
140
151
  const handleDelete = async () => {
141
152
  if (!editing) return
142
- if (!confirm(`Delete skill "${editing.name}"? This will remove it from all assigned agents.`)) return
143
-
153
+ setDeleting(true)
144
154
  try {
145
155
  await api('DELETE', `/skills/${editing.id}`)
146
156
  toast.success('Skill deleted')
147
157
  await loadSkills()
158
+ setConfirmDelete(false)
148
159
  onClose()
149
160
  } catch (err: unknown) {
150
161
  toast.error(err instanceof Error ? err.message : 'Failed to delete skill')
162
+ } finally {
163
+ setDeleting(false)
151
164
  }
152
165
  }
153
166
 
154
167
  const inputClass = "w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow"
168
+ const previewSecurity = metadataPreview?.security as SkillSecuritySummary | undefined
169
+ const requirementCount = (metadataPreview?.skillRequirements?.env?.length || 0)
170
+ + (metadataPreview?.skillRequirements?.bins?.length || 0)
171
+ + (metadataPreview?.skillRequirements?.config?.length || 0)
155
172
 
156
173
  return (
157
174
  <BottomSheet open={open} onClose={onClose} wide>
@@ -206,6 +223,50 @@ export function SkillSheet() {
206
223
  </div>
207
224
  )}
208
225
 
226
+ {metadataPreview && (
227
+ <div className="mb-8 rounded-[14px] border border-white/[0.08] bg-white/[0.03] p-4">
228
+ <div className="flex items-center justify-between gap-3">
229
+ <div>
230
+ <div className="text-[11px] font-700 uppercase tracking-[0.08em] text-text-3/60">Skill Metadata</div>
231
+ <p className="mt-1 text-[13px] text-text-2">
232
+ {metadataPreview.version ? `v${metadataPreview.version}` : 'Unversioned'}
233
+ {metadataPreview.sourceFormat ? ` · ${metadataPreview.sourceFormat}` : ''}
234
+ {requirementCount > 0 ? ` · ${requirementCount} declared requirement${requirementCount === 1 ? '' : 's'}` : ''}
235
+ </p>
236
+ </div>
237
+ {previewSecurity && (
238
+ <span className={`rounded-full px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${
239
+ previewSecurity.level === 'high'
240
+ ? 'bg-red-500/10 text-red-300 border border-red-500/20'
241
+ : previewSecurity.level === 'medium'
242
+ ? 'bg-amber-500/10 text-amber-300 border border-amber-500/20'
243
+ : 'bg-emerald-500/10 text-emerald-300 border border-emerald-500/20'
244
+ }`}>
245
+ {previewSecurity.level} risk
246
+ </span>
247
+ )}
248
+ </div>
249
+
250
+ {(metadataPreview.primaryEnv || metadataPreview.homepage || metadataPreview.skillKey) && (
251
+ <div className="mt-3 flex flex-wrap gap-2 text-[11px] text-text-3/70">
252
+ {metadataPreview.primaryEnv && <span className="rounded-full border border-white/[0.08] px-2 py-1">Primary env: {metadataPreview.primaryEnv}</span>}
253
+ {metadataPreview.skillKey && <span className="rounded-full border border-white/[0.08] px-2 py-1">Skill key: {metadataPreview.skillKey}</span>}
254
+ {metadataPreview.homepage && <span className="rounded-full border border-white/[0.08] px-2 py-1">Homepage linked</span>}
255
+ </div>
256
+ )}
257
+
258
+ {previewSecurity?.notes?.length ? (
259
+ <div className="mt-3 space-y-1">
260
+ {previewSecurity.notes.slice(0, 4).map((note) => (
261
+ <p key={note} className="text-[12px] text-text-3/75">- {note}</p>
262
+ ))}
263
+ </div>
264
+ ) : (
265
+ <p className="mt-3 text-[12px] text-text-3/65">No obvious requirement or security signals were detected.</p>
266
+ )}
267
+ </div>
268
+ )}
269
+
209
270
  <div className="mb-8">
210
271
  <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em] mb-3">Name</label>
211
272
  <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="e.g. Frontend Design" className={inputClass} style={{ fontFamily: 'inherit' }} />
@@ -285,7 +346,7 @@ export function SkillSheet() {
285
346
 
286
347
  <div className="flex gap-3 pt-2 border-t border-white/[0.04]">
287
348
  {editing && (
288
- <button onClick={handleDelete} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
349
+ <button onClick={() => setConfirmDelete(true)} className="py-3.5 px-6 rounded-[14px] border border-red-500/20 bg-transparent text-red-400 text-[15px] font-600 cursor-pointer hover:bg-red-500/10 transition-all" style={{ fontFamily: 'inherit' }}>
289
350
  Delete
290
351
  </button>
291
352
  )}
@@ -296,6 +357,17 @@ export function SkillSheet() {
296
357
  {editing ? 'Save' : 'Create'}
297
358
  </button>
298
359
  </div>
360
+ <ConfirmDialog
361
+ open={confirmDelete}
362
+ title="Delete Skill?"
363
+ message={editing ? `Delete "${editing.name}"? This will remove it from all assigned agents.` : 'Delete this skill?'}
364
+ confirmLabel={deleting ? 'Deleting...' : 'Delete'}
365
+ confirmDisabled={deleting}
366
+ cancelDisabled={deleting}
367
+ danger
368
+ onConfirm={() => { void handleDelete() }}
369
+ onCancel={() => { if (!deleting) setConfirmDelete(false) }}
370
+ />
299
371
  </BottomSheet>
300
372
  )
301
373
  }