@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
@@ -6,6 +6,7 @@ import { createProviderConfig, updateProviderConfig, deleteProviderConfig } from
6
6
  import { api } from '@/lib/api-client'
7
7
  import { fetchProviderModelDiscovery } from '@/lib/provider-model-discovery-client'
8
8
  import { BottomSheet } from '@/components/shared/bottom-sheet'
9
+ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
9
10
  import { toast } from 'sonner'
10
11
 
11
12
  export function ProviderSheet() {
@@ -40,6 +41,8 @@ export function ProviderSheet() {
40
41
  const [liveLoading, setLiveLoading] = useState(false)
41
42
  const [liveMessage, setLiveMessage] = useState('')
42
43
  const [liveCached, setLiveCached] = useState(false)
44
+ const [confirmDelete, setConfirmDelete] = useState(false)
45
+ const [deleting, setDeleting] = useState(false)
43
46
 
44
47
  // Find editing provider in custom configs OR built-in list
45
48
  const editingCustom = editingId ? providerConfigs.find((c) => c.id === editingId) : null
@@ -122,6 +125,8 @@ export function ProviderSheet() {
122
125
  }
123
126
 
124
127
  const onClose = () => {
128
+ setConfirmDelete(false)
129
+ setDeleting(false)
125
130
  setOpen(false)
126
131
  setEditingId(null)
127
132
  }
@@ -162,14 +167,17 @@ export function ProviderSheet() {
162
167
 
163
168
  const handleDelete = async () => {
164
169
  if (editingCustom) {
165
- if (!confirm(`Delete custom provider "${editingCustom.name}"?`)) return
170
+ setDeleting(true)
166
171
  try {
167
172
  await deleteProviderConfig(editingCustom.id)
168
173
  toast.success('Provider deleted')
169
174
  await loadProviderConfigs()
175
+ setConfirmDelete(false)
170
176
  onClose()
171
177
  } catch (err: unknown) {
172
178
  toast.error(err instanceof Error ? err.message : 'Failed to delete provider')
179
+ } finally {
180
+ setDeleting(false)
173
181
  }
174
182
  }
175
183
  }
@@ -493,7 +501,7 @@ export function ProviderSheet() {
493
501
 
494
502
  <div className="flex gap-3 pt-2 border-t border-white/[0.04]">
495
503
  {editingCustom && (
496
- <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' }}>
504
+ <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' }}>
497
505
  Delete
498
506
  </button>
499
507
  )}
@@ -520,6 +528,17 @@ export function ProviderSheet() {
520
528
  </button>
521
529
  )}
522
530
  </div>
531
+ <ConfirmDialog
532
+ open={confirmDelete}
533
+ title="Delete Provider?"
534
+ message={editingCustom ? `Delete custom provider "${editingCustom.name}"?` : 'Delete this provider?'}
535
+ confirmLabel={deleting ? 'Deleting...' : 'Delete'}
536
+ confirmDisabled={deleting}
537
+ cancelDisabled={deleting}
538
+ danger
539
+ onConfirm={() => { void handleDelete() }}
540
+ onCancel={() => { if (!deleting) setConfirmDelete(false) }}
541
+ />
523
542
  </BottomSheet>
524
543
  )
525
544
  }
@@ -4,6 +4,8 @@ import type { Schedule } from '@/types'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { api } from '@/lib/api-client'
6
6
  import { cronToHuman } from '@/lib/cron-human'
7
+ import { AgentAvatar } from '@/components/agents/agent-avatar'
8
+ import { isUserCreatedSchedule } from '@/lib/schedule-origin'
7
9
 
8
10
  const STATUS_COLORS: Record<string, string> = {
9
11
  active: 'text-emerald-400 bg-emerald-400/[0.08]',
@@ -55,6 +57,7 @@ export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
55
57
  }
56
58
 
57
59
  const agent = agents[schedule.agentId]
60
+ const creatorAgent = schedule.createdByAgentId ? agents[schedule.createdByAgentId] : null
58
61
  const statusClass = STATUS_COLORS[schedule.status] || STATUS_COLORS.paused
59
62
  const canToggle = schedule.status === 'active' || schedule.status === 'paused'
60
63
 
@@ -101,7 +104,7 @@ export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
101
104
  </div>
102
105
  </div>
103
106
  <div className="text-[12px] text-text-3/70 mt-1.5 truncate">
104
- {agent?.name || 'Unknown agent'} &middot; {schedule.scheduleType}
107
+ Runs on {agent?.name || 'Unknown agent'} &middot; {schedule.scheduleType}
105
108
  {!inSidebar && schedule.scheduleType === 'cron' && schedule.cron && (
106
109
  <span className="text-text-3/50 ml-1" title={schedule.cron}>({cronToHuman(schedule.cron)})</span>
107
110
  )}
@@ -113,6 +116,27 @@ export function ScheduleCard({ schedule, inSidebar, index = 0 }: Props) {
113
116
  </span>
114
117
  )}
115
118
  </div>
119
+ <div className="flex flex-wrap items-center gap-1.5 mt-2">
120
+ {creatorAgent ? (
121
+ <span className="inline-flex max-w-full items-center gap-1.5 rounded-[7px] bg-white/[0.05] px-2 py-1 text-[10px] font-600 text-text-2">
122
+ <AgentAvatar
123
+ seed={creatorAgent.avatarSeed}
124
+ avatarUrl={creatorAgent.avatarUrl}
125
+ name={creatorAgent.name}
126
+ size={14}
127
+ />
128
+ <span className="truncate">Created by {creatorAgent.name}</span>
129
+ </span>
130
+ ) : (
131
+ <span className="inline-flex max-w-full items-center gap-1.5 rounded-[7px] bg-white/[0.04] px-2 py-1 text-[10px] font-600 text-text-3">
132
+ <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
133
+ <circle cx="12" cy="8" r="4" />
134
+ <path d="M4 20c1.5-3.5 4.6-5 8-5s6.5 1.5 8 5" />
135
+ </svg>
136
+ <span className="truncate">{isUserCreatedSchedule(schedule) ? 'Created manually' : 'Creator unknown'}</span>
137
+ </span>
138
+ )}
139
+ </div>
116
140
  <div className="text-[11px] text-text-3/60 mt-1">
117
141
  Next: {formatNext(schedule.nextRunAt)}
118
142
  </div>
@@ -5,12 +5,15 @@ import { useAppStore } from '@/stores/use-app-store'
5
5
  import { createSchedule, updateSchedule, deleteSchedule } from '@/lib/schedules'
6
6
  import { BottomSheet } from '@/components/shared/bottom-sheet'
7
7
  import { AgentPickerList } from '@/components/shared/agent-picker-list'
8
+ import { ConfirmDialog } from '@/components/shared/confirm-dialog'
8
9
  import { inputClass } from '@/components/shared/form-styles'
10
+ import { AgentAvatar } from '@/components/agents/agent-avatar'
9
11
  import type { ScheduleType, ScheduleStatus } from '@/types'
10
12
  import cronstrue from 'cronstrue'
11
13
  import { SectionLabel } from '@/components/shared/section-label'
12
14
  import { SCHEDULE_TEMPLATES, type ScheduleTemplate } from '@/lib/schedule-templates'
13
15
  import { HintTip } from '@/components/shared/hint-tip'
16
+ import { isUserCreatedSchedule } from '@/lib/schedule-origin'
14
17
  import { toast } from 'sonner'
15
18
  import {
16
19
  Newspaper, BarChart3, HeartPulse, PenLine, Trash2,
@@ -102,6 +105,8 @@ export function ScheduleSheet() {
102
105
  const [intervalMs, setIntervalMs] = useState(3600000)
103
106
  const [status, setStatus] = useState<ScheduleStatus>('active')
104
107
  const [customCron, setCustomCron] = useState(false)
108
+ const [confirmDelete, setConfirmDelete] = useState(false)
109
+ const [deleting, setDeleting] = useState(false)
105
110
 
106
111
  const editing = editingId ? schedules[editingId] : null
107
112
  const isCreating = !editing
@@ -163,6 +168,8 @@ export function ScheduleSheet() {
163
168
  }, [cron])
164
169
 
165
170
  const onClose = () => {
171
+ setConfirmDelete(false)
172
+ setDeleting(false)
166
173
  setOpen(false)
167
174
  setEditingId(null)
168
175
  }
@@ -195,14 +202,17 @@ export function ScheduleSheet() {
195
202
 
196
203
  const handleDelete = async () => {
197
204
  if (!editing) return
198
- if (!confirm(`Delete schedule "${editing.name}"?`)) return
205
+ setDeleting(true)
199
206
  try {
200
207
  await deleteSchedule(editing.id)
201
208
  toast.success('Schedule deleted')
202
209
  await loadSchedules()
210
+ setConfirmDelete(false)
203
211
  onClose()
204
212
  } catch (err: unknown) {
205
213
  toast.error(err instanceof Error ? err.message : 'Failed to delete schedule')
214
+ } finally {
215
+ setDeleting(false)
206
216
  }
207
217
  }
208
218
 
@@ -211,6 +221,7 @@ export function ScheduleSheet() {
211
221
  const step1Valid = scheduleType === 'cron' ? cron.trim().length > 0 : intervalMs > 0
212
222
 
213
223
  const selectedAgent = agentId ? agents[agentId] : null
224
+ const creatorAgent = editing?.createdByAgentId ? agents[editing.createdByAgentId] : null
214
225
 
215
226
  return (
216
227
  <BottomSheet open={open} onClose={onClose} wide>
@@ -466,6 +477,26 @@ export function ScheduleSheet() {
466
477
  <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Agent</span>
467
478
  <div className="text-[14px] text-text font-600 mt-0.5">{selectedAgent?.name || agentId}</div>
468
479
  </div>
480
+ {editing && (
481
+ <div>
482
+ <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Created By</span>
483
+ {creatorAgent ? (
484
+ <div className="mt-1 inline-flex items-center gap-2 rounded-[10px] bg-white/[0.04] px-3 py-2 text-[13px] text-text-2">
485
+ <AgentAvatar
486
+ seed={creatorAgent.avatarSeed}
487
+ avatarUrl={creatorAgent.avatarUrl}
488
+ name={creatorAgent.name}
489
+ size={18}
490
+ />
491
+ <span>{creatorAgent.name}</span>
492
+ </div>
493
+ ) : (
494
+ <div className="text-[13px] text-text-2 mt-0.5">
495
+ {isUserCreatedSchedule(editing) ? 'Manual / user-created' : 'Unknown'}
496
+ </div>
497
+ )}
498
+ </div>
499
+ )}
469
500
  <div>
470
501
  <span className="text-[11px] text-text-3/50 uppercase tracking-wider font-600">Task</span>
471
502
  <div className="text-[13px] text-text-2 mt-0.5 whitespace-pre-wrap">{taskPrompt}</div>
@@ -497,7 +528,7 @@ export function ScheduleSheet() {
497
528
  {/* Footer */}
498
529
  <div className="flex gap-3 pt-2 border-t border-white/[0.04]">
499
530
  {editing && step === 0 && (
500
- <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' }}>
531
+ <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' }}>
501
532
  Delete
502
533
  </button>
503
534
  )}
@@ -547,6 +578,17 @@ export function ScheduleSheet() {
547
578
  </button>
548
579
  )}
549
580
  </div>
581
+ <ConfirmDialog
582
+ open={confirmDelete}
583
+ title="Delete Schedule?"
584
+ message={editing ? `Delete "${editing.name}"? This will remove the schedule from the app.` : 'Delete this schedule?'}
585
+ confirmLabel={deleting ? 'Deleting...' : 'Delete'}
586
+ confirmDisabled={deleting}
587
+ cancelDisabled={deleting}
588
+ danger
589
+ onConfirm={() => { void handleDelete() }}
590
+ onCancel={() => { if (!deleting) setConfirmDelete(false) }}
591
+ />
550
592
  </BottomSheet>
551
593
  )
552
594
  }
@@ -3,6 +3,7 @@
3
3
  import { useEffect, useState } 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'
8
9
  import { toast } from 'sonner'
@@ -25,6 +26,8 @@ export function SecretSheet() {
25
26
  const [scope, setScope] = useState<'global' | 'agent'>('global')
26
27
  const [agentIds, setAgentIds] = useState<string[]>([])
27
28
  const [saving, setSaving] = useState(false)
29
+ const [confirmDelete, setConfirmDelete] = useState(false)
30
+ const [deleting, setDeleting] = useState(false)
28
31
 
29
32
  const editing = editingId ? secrets[editingId] : null
30
33
  const agentList = Object.values(agents)
@@ -50,6 +53,8 @@ export function SecretSheet() {
50
53
  }, [editing, open])
51
54
 
52
55
  const handleClose = () => {
56
+ setConfirmDelete(false)
57
+ setDeleting(false)
53
58
  setOpen(false)
54
59
  setEditingId(null)
55
60
  }
@@ -87,14 +92,17 @@ export function SecretSheet() {
87
92
 
88
93
  const handleDelete = async () => {
89
94
  if (!editing) return
90
- if (!confirm(`Delete secret "${editing.name}"?`)) return
95
+ setDeleting(true)
91
96
  try {
92
97
  await api('DELETE', `/secrets/${editing.id}`)
93
98
  toast.success('Secret deleted')
94
99
  await loadSecrets()
100
+ setConfirmDelete(false)
95
101
  handleClose()
96
102
  } catch (err: unknown) {
97
103
  toast.error(err instanceof Error ? err.message : 'Failed to delete secret')
104
+ } finally {
105
+ setDeleting(false)
98
106
  }
99
107
  }
100
108
 
@@ -184,7 +192,7 @@ export function SecretSheet() {
184
192
  <div className="flex gap-3 pt-3">
185
193
  {editing && (
186
194
  <button
187
- onClick={handleDelete}
195
+ onClick={() => setConfirmDelete(true)}
188
196
  className="px-5 py-3 rounded-[14px] border border-danger/30 bg-transparent text-danger text-[14px] font-600 cursor-pointer hover:bg-danger/10 transition-colors"
189
197
  style={{ fontFamily: 'inherit' }}
190
198
  >
@@ -202,6 +210,17 @@ export function SecretSheet() {
202
210
  {saving ? 'Saving...' : editing ? 'Update' : 'Save'}
203
211
  </button>
204
212
  </div>
213
+ <ConfirmDialog
214
+ open={confirmDelete}
215
+ title="Delete Secret?"
216
+ message={editing ? `Delete "${editing.name}"? This will remove the stored secret from the app.` : 'Delete this secret?'}
217
+ confirmLabel={deleting ? 'Deleting...' : 'Delete'}
218
+ confirmDisabled={deleting}
219
+ cancelDisabled={deleting}
220
+ danger
221
+ onConfirm={() => { void handleDelete() }}
222
+ onCancel={() => { if (!deleting) setConfirmDelete(false) }}
223
+ />
205
224
  </div>
206
225
  </BottomSheet>
207
226
  )
@@ -4,6 +4,7 @@ import { useState, useEffect, useRef, useCallback, useMemo } from 'react'
4
4
  import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'
5
5
  import { useAppStore } from '@/stores/use-app-store'
6
6
  import { AgentAvatar } from '@/components/agents/agent-avatar'
7
+ import { toast } from 'sonner'
7
8
 
8
9
  export function AgentSwitchDialog() {
9
10
  const [open, setOpen] = useState(false)
@@ -47,10 +48,15 @@ export function AgentSwitchDialog() {
47
48
  }, [agents, query])
48
49
 
49
50
  const handleSelect = useCallback((agentId: string) => {
51
+ const agent = agents[agentId]
52
+ if (agent?.disabled === true && !agent.threadSessionId) {
53
+ toast.error(`${agent.name} is disabled. Re-enable it to start a new chat.`)
54
+ return
55
+ }
50
56
  setOpen(false)
51
57
  void setCurrentAgent(agentId)
52
58
  // eslint-disable-next-line react-hooks/exhaustive-deps
53
- }, [])
59
+ }, [agents, setCurrentAgent])
54
60
 
55
61
  const handleKeyDown = (e: React.KeyboardEvent) => {
56
62
  if (e.key === 'ArrowDown') {
@@ -119,6 +125,11 @@ export function AgentSwitchDialog() {
119
125
  <div className="flex-1 min-w-0">
120
126
  <div className="flex items-center gap-2">
121
127
  <span className="text-[13px] font-500 text-text truncate">{agent.name}</span>
128
+ {agent.disabled === true && (
129
+ <span className="px-1.5 py-0.5 rounded-[4px] bg-amber-400/[0.08] text-[10px] font-500 text-amber-300 shrink-0">
130
+ disabled
131
+ </span>
132
+ )}
122
133
  {agent.id === currentAgentId && (
123
134
  <span className="px-1.5 py-0.5 rounded-[4px] bg-accent-bright/15 text-[10px] font-500 text-accent-bright shrink-0">
124
135
  current
@@ -9,9 +9,11 @@ interface Props {
9
9
  onClose: () => void
10
10
  children: ReactNode
11
11
  wide?: boolean
12
+ title?: string
13
+ description?: string
12
14
  }
13
15
 
14
- export function BottomSheet({ open, onClose, children, wide }: Props) {
16
+ export function BottomSheet({ open, onClose, children, wide, title, description }: Props) {
15
17
  return (
16
18
  <DialogPrimitive.Root open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>
17
19
  <DialogPrimitive.Portal>
@@ -27,10 +29,18 @@ export function BottomSheet({ open, onClose, children, wide }: Props) {
27
29
  ${wide ? 'sm:max-w-[760px]' : 'sm:max-w-[560px]'}`}
28
30
  style={{ animationDuration: '220ms' }}
29
31
  >
30
- <div className="relative shrink-0 px-4 pt-3 sm:px-5 sm:pt-5">
32
+ <div className="relative shrink-0 px-4 pt-4 pr-14 sm:px-5 sm:pt-6 sm:pr-16">
31
33
  <div className="mx-auto h-1 w-10 rounded-full bg-white/[0.08] sm:hidden" />
34
+ <DialogPrimitive.Title className="sr-only">
35
+ {title || 'Dialog'}
36
+ </DialogPrimitive.Title>
37
+ {description ? (
38
+ <DialogPrimitive.Description className="sr-only">
39
+ {description}
40
+ </DialogPrimitive.Description>
41
+ ) : null}
32
42
  <DialogPrimitive.Close
33
- className="absolute right-3 top-2.5 inline-flex h-9 w-9 items-center justify-center rounded-[12px] border border-white/[0.06] bg-white/[0.03] text-text-3 transition-all hover:bg-white/[0.06] hover:text-text-2 focus:outline-none focus:ring-2 focus:ring-accent-bright/30 sm:right-4 sm:top-4"
43
+ className="absolute right-4 top-3.5 inline-flex h-9 w-9 items-center justify-center rounded-[12px] border border-white/[0.06] bg-white/[0.03] text-text-3 transition-all hover:bg-white/[0.06] hover:text-text-2 focus:outline-none focus:ring-2 focus:ring-accent-bright/30 sm:right-5 sm:top-5"
34
44
  >
35
45
  <XIcon className="size-4" />
36
46
  <span className="sr-only">Close</span>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
+ import { toast } from 'sonner'
5
6
 
6
7
  interface CommandItem {
7
8
  id: string
@@ -129,10 +130,16 @@ export function CommandPalette() {
129
130
  result.push({
130
131
  id: `agent:${agent.id}`,
131
132
  label: agent.name,
132
- description: `Open ${agent.name}'s chat`,
133
+ description: agent.disabled === true
134
+ ? `${agent.name} is disabled`
135
+ : `Open ${agent.name}'s chat`,
133
136
  keywords: [agent.provider, agent.model, agent.description || ''].filter(Boolean),
134
137
  category: 'agent',
135
138
  onSelect: async () => {
139
+ if (agent.disabled === true && !agent.threadSessionId) {
140
+ toast.error(`${agent.name} is disabled. Re-enable it to start a new chat.`)
141
+ return
142
+ }
136
143
  await setCurrentAgent(agent.id)
137
144
  setActiveView('agents')
138
145
  setOpen(false)
@@ -14,14 +14,26 @@ interface Props {
14
14
  title: string
15
15
  message: string
16
16
  confirmLabel?: string
17
+ confirmDisabled?: boolean
18
+ cancelDisabled?: boolean
17
19
  danger?: boolean
18
20
  onConfirm: () => void
19
21
  onCancel: () => void
20
22
  }
21
23
 
22
- export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm', danger, onConfirm, onCancel }: Props) {
24
+ export function ConfirmDialog({
25
+ open,
26
+ title,
27
+ message,
28
+ confirmLabel = 'Confirm',
29
+ confirmDisabled = false,
30
+ cancelDisabled = false,
31
+ danger,
32
+ onConfirm,
33
+ onCancel,
34
+ }: Props) {
23
35
  return (
24
- <Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onCancel() }}>
36
+ <Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen && !cancelDisabled) onCancel() }}>
25
37
  <DialogContent
26
38
  className="sm:max-w-[400px] rounded-[20px] border-white/[0.06] bg-raised p-0 shadow-[0_24px_80px_rgba(0,0,0,0.55)]"
27
39
  >
@@ -38,7 +50,8 @@ export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm',
38
50
  <button
39
51
  type="button"
40
52
  onClick={onCancel}
41
- className="flex-1 rounded-[12px] border border-white/[0.06] bg-transparent px-4 py-2.5 text-[13px] font-600 text-text-2 transition-all duration-200 hover:bg-surface"
53
+ disabled={cancelDisabled}
54
+ className="flex-1 rounded-[12px] border border-white/[0.06] bg-transparent px-4 py-2.5 text-[13px] font-600 text-text-2 transition-all duration-200 hover:bg-surface disabled:cursor-not-allowed disabled:opacity-50"
42
55
  style={{ fontFamily: 'inherit' }}
43
56
  >
44
57
  Cancel
@@ -46,10 +59,12 @@ export function ConfirmDialog({ open, title, message, confirmLabel = 'Confirm',
46
59
  <button
47
60
  type="button"
48
61
  onClick={onConfirm}
62
+ disabled={confirmDisabled}
49
63
  className={`flex-1 rounded-[12px] border-none px-4 py-2.5 text-[13px] font-600 text-white transition-all duration-200 active:scale-[0.98]
50
64
  ${danger
51
65
  ? 'bg-danger shadow-[0_4px_20px_rgba(244,63,94,0.2)]'
52
- : 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}`}
66
+ : 'bg-accent-bright shadow-[0_4px_20px_rgba(99,102,241,0.2)]'}
67
+ disabled:cursor-not-allowed disabled:opacity-50 disabled:active:scale-100`}
53
68
  style={{ fontFamily: 'inherit' }}
54
69
  >
55
70
  {confirmLabel}
@@ -0,0 +1,28 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+
4
+ import {
5
+ getConnectorPlatformLabel,
6
+ resolveConnectorPlatformMeta,
7
+ } from './connector-platform-icon'
8
+
9
+ describe('connector platform metadata', () => {
10
+ it('resolves legacy connector platforms used by stored runtime data', () => {
11
+ assert.deepEqual(resolveConnectorPlatformMeta('webchat'), {
12
+ label: 'Web Chat',
13
+ color: '#0EA5E9',
14
+ })
15
+ assert.deepEqual(resolveConnectorPlatformMeta('mockmail'), {
16
+ label: 'MockMail',
17
+ color: '#7C3AED',
18
+ })
19
+ })
20
+
21
+ it('falls back safely for unknown connector platform strings', () => {
22
+ assert.deepEqual(resolveConnectorPlatformMeta('custom-bridge'), {
23
+ label: 'Custom Bridge',
24
+ color: '#64748B',
25
+ })
26
+ assert.equal(getConnectorPlatformLabel('custom-bridge'), 'Custom Bridge')
27
+ })
28
+ })
@@ -24,10 +24,35 @@ export const CONNECTOR_PLATFORM_META: Record<ConnectorPlatform, { label: string;
24
24
  googlechat: { label: 'Google Chat', color: '#00AC47' },
25
25
  matrix: { label: 'Matrix', color: '#0DBD8B' },
26
26
  email: { label: 'Email', color: '#EA4335' },
27
+ webchat: { label: 'Web Chat', color: '#0EA5E9' },
28
+ mockmail: { label: 'MockMail', color: '#7C3AED' },
27
29
  }
28
30
 
29
- export function getConnectorPlatformLabel(platform: ConnectorPlatform): string {
30
- return CONNECTOR_PLATFORM_META[platform]?.label || platform
31
+ const FALLBACK_CONNECTOR_PLATFORM_META = { label: 'Connector', color: '#64748B' } as const
32
+
33
+ function formatUnknownConnectorPlatformLabel(platform: string): string {
34
+ const trimmed = platform.trim()
35
+ if (!trimmed) return FALLBACK_CONNECTOR_PLATFORM_META.label
36
+ return trimmed
37
+ .replace(/[_-]+/g, ' ')
38
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
39
+ .split(/\s+/)
40
+ .filter(Boolean)
41
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
42
+ .join(' ')
43
+ }
44
+
45
+ export function resolveConnectorPlatformMeta(platform: string): { label: string; color: string } {
46
+ const known = CONNECTOR_PLATFORM_META[platform as ConnectorPlatform]
47
+ if (known) return known
48
+ return {
49
+ label: formatUnknownConnectorPlatformLabel(platform),
50
+ color: FALLBACK_CONNECTOR_PLATFORM_META.color,
51
+ }
52
+ }
53
+
54
+ export function getConnectorPlatformLabel(platform: string): string {
55
+ return resolveConnectorPlatformMeta(platform).label
31
56
  }
32
57
 
33
58
  export function getConnectorIdFromSessionName(sessionName?: string | null): string | null {
@@ -46,7 +71,7 @@ export function getSessionConnector(
46
71
  }
47
72
 
48
73
  interface ConnectorPlatformIconProps {
49
- platform: ConnectorPlatform
74
+ platform: string
50
75
  size?: number
51
76
  className?: string
52
77
  }
@@ -131,12 +156,20 @@ export function ConnectorPlatformIcon({
131
156
  </svg>
132
157
  )
133
158
  default:
134
- return null
159
+ return (
160
+ <span
161
+ aria-hidden
162
+ className={cn('inline-flex items-center justify-center rounded-full font-700 uppercase', className)}
163
+ style={{ width: size, height: size, fontSize: Math.max(8, Math.floor(size * 0.5)), lineHeight: 1 }}
164
+ >
165
+ {getConnectorPlatformLabel(platform).charAt(0)}
166
+ </span>
167
+ )
135
168
  }
136
169
  }
137
170
 
138
171
  interface ConnectorPlatformBadgeProps {
139
- platform: ConnectorPlatform
172
+ platform: string
140
173
  size?: number
141
174
  iconSize?: number
142
175
  className?: string
@@ -152,7 +185,7 @@ export function ConnectorPlatformBadge({
152
185
  roundedClassName = 'rounded-[10px]',
153
186
  title,
154
187
  }: ConnectorPlatformBadgeProps) {
155
- const meta = CONNECTOR_PLATFORM_META[platform]
188
+ const meta = resolveConnectorPlatformMeta(platform)
156
189
  const glyphSize = iconSize ?? Math.max(12, Math.floor(size * 0.52))
157
190
 
158
191
  return (
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useState, useCallback, useMemo } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { getPluginSourceLabel } from '@/lib/plugin-sources'
5
6
  import type { PluginMeta, MarketplacePlugin } from '@/types'
6
7
  import { toast } from 'sonner'
7
8
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
@@ -93,9 +94,12 @@ export function PluginManager() {
93
94
 
94
95
  const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
95
96
 
96
- await api('POST', '/plugins/install', {
97
- url: p.url,
98
- filename: safeFilename
97
+ await api('POST', '/plugins/install', {
98
+ url: p.url,
99
+ filename: safeFilename,
100
+ installMethod: 'marketplace',
101
+ sourceLabel: p.source,
102
+ installSource: p.catalogSource || p.source,
99
103
  })
100
104
 
101
105
  await loadPlugins()
@@ -154,6 +158,18 @@ export function PluginManager() {
154
158
  )}
155
159
  </div>
156
160
  <div className="text-[11px] font-mono text-text-3/40 truncate">{p.filename}</div>
161
+ <div className="flex items-center gap-1.5 mt-1.5 flex-wrap">
162
+ {p.sourceLabel && (
163
+ <span className="text-[9px] font-700 px-1.5 py-0.5 rounded uppercase tracking-wider bg-sky-500/10 text-sky-300">
164
+ {getPluginSourceLabel(p.sourceLabel)}
165
+ </span>
166
+ )}
167
+ {p.installSource && p.installSource !== p.sourceLabel && (
168
+ <span className="text-[9px] font-700 px-1.5 py-0.5 rounded uppercase tracking-wider bg-white/[0.04] text-text-3/65">
169
+ via {getPluginSourceLabel(p.installSource)}
170
+ </span>
171
+ )}
172
+ </div>
157
173
  {p.description && <div className="text-[12px] text-text-3/70 mt-1 line-clamp-1">{p.description}</div>}
158
174
  {p.hasDependencyManifest && (
159
175
  <div className="mt-1.5 flex items-center gap-2 text-[10px] font-700 uppercase tracking-[0.08em]">
@@ -320,9 +336,16 @@ export function PluginManager() {
320
336
  <div className="min-w-0 flex-1">
321
337
  <div className="flex items-center gap-3 mb-1.5">
322
338
  <span className="text-[16px] font-700 text-text tracking-tight">{p.name}</span>
323
- <span className={`text-[9px] font-800 uppercase px-2 py-0.5 rounded-[6px] border ${p.source === 'clawhub' ? 'bg-indigo-500/10 text-indigo-400 border-indigo-500/20' : 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20'}`}>
324
- {p.source || 'swarmclaw'}
325
- </span>
339
+ {p.source && (
340
+ <span className="text-[9px] font-800 uppercase px-2 py-0.5 rounded-[6px] border bg-sky-500/10 text-sky-300 border-sky-500/20">
341
+ {getPluginSourceLabel(p.source)}
342
+ </span>
343
+ )}
344
+ {p.catalogSource && p.catalogSource !== p.source && (
345
+ <span className="text-[9px] font-800 uppercase px-2 py-0.5 rounded-[6px] border bg-white/[0.04] text-text-3/70 border-white/[0.08]">
346
+ via {getPluginSourceLabel(p.catalogSource)}
347
+ </span>
348
+ )}
326
349
  </div>
327
350
  <p className="text-[13px] text-text-3/80 leading-relaxed mb-4 line-clamp-2">{p.description}</p>
328
351