@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
@@ -10,6 +10,9 @@ type UseCaseTemplate = 'local-dev' | 'single-vps' | 'private-tailnet' | 'browser
10
10
  type ExposurePreset = 'private-lan' | 'tailscale' | 'caddy' | 'nginx' | 'ssh-tunnel'
11
11
 
12
12
  interface LocalDeployStatus {
13
+ id: string
14
+ name: string
15
+ isPrimary: boolean
13
16
  running: boolean
14
17
  processId: string | null
15
18
  pid: number | null
@@ -18,6 +21,8 @@ interface LocalDeployStatus {
18
21
  wsUrl: string
19
22
  token: string | null
20
23
  startedAt: number | null
24
+ createdAt: number
25
+ updatedAt: number
21
26
  tail: string
22
27
  lastError: string | null
23
28
  launchCommand: string
@@ -25,12 +30,17 @@ interface LocalDeployStatus {
25
30
  }
26
31
 
27
32
  interface RemoteDeployStatus {
33
+ id: string
34
+ name: string
35
+ isPrimary: boolean
28
36
  active: boolean
29
37
  processId: string | null
30
38
  pid: number | null
31
39
  action: string | null
32
40
  target: string | null
33
41
  startedAt: number | null
42
+ createdAt: number
43
+ updatedAt: number
34
44
  status: 'idle' | 'running' | 'exited' | 'killed' | 'failed' | 'timeout'
35
45
  exitCode: number | null
36
46
  tail: string
@@ -63,16 +73,24 @@ interface DeployBundle {
63
73
 
64
74
  interface DeployStatusResponse {
65
75
  local: LocalDeployStatus
66
- remote?: RemoteDeployStatus
76
+ locals?: LocalDeployStatus[]
77
+ localPrimaryId?: string | null
78
+ remote?: RemoteDeployStatus | null
79
+ remotes?: RemoteDeployStatus[]
80
+ remotePrimaryId?: string | null
67
81
  }
68
82
 
69
83
  interface DeployActionResponse {
70
84
  ok: boolean
71
85
  local?: LocalDeployStatus
86
+ locals?: LocalDeployStatus[]
87
+ localPrimaryId?: string | null
72
88
  token?: string
73
89
  bundle?: DeployBundle
74
90
  processId?: string | null
75
91
  remote?: RemoteDeployStatus
92
+ remotes?: RemoteDeployStatus[]
93
+ remotePrimaryId?: string | null
76
94
  summary?: string
77
95
  commandPreview?: string
78
96
  verify?: {
@@ -81,6 +99,7 @@ interface DeployActionResponse {
81
99
  wsUrl: string
82
100
  authProvided: boolean
83
101
  models: string[]
102
+ message?: string
84
103
  error?: string
85
104
  hint?: string
86
105
  }
@@ -239,6 +258,12 @@ function inferRemoteTarget(value: string | null | undefined): string {
239
258
  return base.replace(/\/+$/, '')
240
259
  }
241
260
 
261
+ function inferRemoteHost(value: string | null | undefined): string {
262
+ const parsed = parseMaybeUrl(value)
263
+ if (!parsed || isLocalEndpoint(value)) return ''
264
+ return parsed.hostname
265
+ }
266
+
242
267
  function badgeTone(active: boolean): string {
243
268
  return active
244
269
  ? 'border-accent-bright/30 bg-accent-bright/10 text-accent-bright'
@@ -259,7 +284,9 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
259
284
 
260
285
  const [activeTab, setActiveTab] = useState<'local' | 'remote'>('local')
261
286
  const [localStatus, setLocalStatus] = useState<LocalDeployStatus | null>(null)
287
+ const [localStatuses, setLocalStatuses] = useState<LocalDeployStatus[]>([])
262
288
  const [remoteStatus, setRemoteStatus] = useState<RemoteDeployStatus | null>(null)
289
+ const [remoteStatuses, setRemoteStatuses] = useState<RemoteDeployStatus[]>([])
263
290
  const [localPort, setLocalPort] = useState(() => inferPort(endpoint))
264
291
  const [deployToken, setDeployToken] = useState(token || '')
265
292
  const [remoteTarget, setRemoteTarget] = useState(() => inferRemoteTarget(endpoint))
@@ -270,7 +297,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
270
297
  const [remoteProvider, setRemoteProvider] = useState<RemoteProvider>('hetzner')
271
298
  const [useCase, setUseCase] = useState<UseCaseTemplate>(() => deployment?.useCase || 'single-vps')
272
299
  const [exposure, setExposure] = useState<ExposurePreset>(() => deployment?.exposure || 'caddy')
273
- const [sshHost, setSshHost] = useState(() => deployment?.sshHost || inferRemoteTarget(endpoint))
300
+ const [sshHost, setSshHost] = useState(() => deployment?.sshHost || inferRemoteHost(endpoint))
274
301
  const [sshUser, setSshUser] = useState(() => deployment?.sshUser || 'root')
275
302
  const [sshPort, setSshPort] = useState(() => deployment?.sshPort || 22)
276
303
  const [sshKeyPath, setSshKeyPath] = useState(() => deployment?.sshKeyPath || '')
@@ -295,7 +322,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
295
322
  setActiveTab('local')
296
323
  } else if (endpoint && inferRemoteTarget(endpoint)) {
297
324
  setRemoteTarget(inferRemoteTarget(endpoint))
298
- setSshHost((current) => current || inferRemoteTarget(endpoint))
325
+ setSshHost((current) => current || inferRemoteHost(endpoint))
299
326
  setActiveTab('remote')
300
327
  }
301
328
  }, [endpoint])
@@ -318,7 +345,12 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
318
345
  .then((result) => {
319
346
  if (!cancelled) {
320
347
  setLocalStatus(result.local)
321
- setRemoteStatus(result.remote || null)
348
+ setLocalStatuses(Array.isArray(result.locals) ? result.locals : [])
349
+ const nextRemotes = Array.isArray(result.remotes)
350
+ ? result.remotes
351
+ : (result.remote ? [result.remote] : [])
352
+ setRemoteStatuses(nextRemotes)
353
+ setRemoteStatus(nextRemotes.find((item) => item.isPrimary) || nextRemotes[0] || null)
322
354
  if (result.local.token) {
323
355
  setDeployToken((current) => current || result.local.token || '')
324
356
  }
@@ -330,23 +362,47 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
330
362
  }
331
363
  }, [])
332
364
 
365
+ const hasActiveRemote = useMemo(
366
+ () => remoteStatuses.some((status) => status.active) || !!remoteStatus?.active,
367
+ [remoteStatus?.active, remoteStatuses],
368
+ )
369
+
333
370
  useEffect(() => {
334
- if (!remoteStatus?.active) return
371
+ if (!hasActiveRemote) return
335
372
  const timer = window.setInterval(() => {
336
373
  api<DeployStatusResponse>('GET', '/openclaw/deploy')
337
374
  .then((result) => {
338
375
  setLocalStatus(result.local)
339
- setRemoteStatus(result.remote || null)
376
+ setLocalStatuses(Array.isArray(result.locals) ? result.locals : [])
377
+ const nextRemotes = Array.isArray(result.remotes)
378
+ ? result.remotes
379
+ : (result.remote ? [result.remote] : [])
380
+ setRemoteStatuses(nextRemotes)
381
+ setRemoteStatus((current) => {
382
+ const currentId = current?.id || ''
383
+ return nextRemotes.find((item) => item.id === currentId)
384
+ || nextRemotes.find((item) => item.isPrimary)
385
+ || nextRemotes[0]
386
+ || null
387
+ })
340
388
  })
341
389
  .catch(() => {})
342
390
  }, 2500)
343
391
  return () => window.clearInterval(timer)
344
- }, [remoteStatus?.active])
392
+ }, [hasActiveRemote])
345
393
 
346
394
  const selectedFile = useMemo(() => {
347
395
  if (!bundle) return null
348
396
  return bundle.files.find((file) => file.name === bundleFile) || bundle.files[0] || null
349
397
  }, [bundle, bundleFile])
398
+ const visibleLocalStatuses = useMemo(
399
+ () => localStatuses,
400
+ [localStatuses],
401
+ )
402
+ const visibleRemoteStatuses = useMemo(
403
+ () => remoteStatuses,
404
+ [remoteStatuses],
405
+ )
350
406
  const localLaunchCommand = useMemo(() => {
351
407
  const typedToken = deployToken.trim()
352
408
  if (typedToken) return buildLocalRunCommand(localPort, typedToken)
@@ -368,6 +424,35 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
368
424
  }, 2200)
369
425
  }
370
426
 
427
+ const syncLocalResponse = (result: Pick<DeployActionResponse, 'local' | 'locals'>) => {
428
+ if (result.local) setLocalStatus(result.local)
429
+ if (Array.isArray(result.locals)) {
430
+ setLocalStatuses(result.locals)
431
+ } else if (result.local) {
432
+ setLocalStatuses([result.local])
433
+ }
434
+ }
435
+
436
+ const syncRemoteResponse = (result: Pick<DeployActionResponse, 'remote' | 'remotes'>) => {
437
+ const remotes = Array.isArray(result.remotes) ? result.remotes : null
438
+ if (remotes) {
439
+ setRemoteStatuses(remotes)
440
+ setRemoteStatus((current) => {
441
+ const currentId = current?.id || ''
442
+ return (result.remote && remotes.find((item) => item.id === result.remote?.id))
443
+ || remotes.find((item) => item.id === currentId)
444
+ || result.remote
445
+ || remotes[0]
446
+ || null
447
+ })
448
+ return
449
+ }
450
+ if (result.remote) {
451
+ setRemoteStatus(result.remote)
452
+ setRemoteStatuses([result.remote])
453
+ }
454
+ }
455
+
371
456
  const onCopied = async (key: string, value: string) => {
372
457
  const ok = await copyTextToClipboard(value)
373
458
  if (!ok) return
@@ -381,6 +466,13 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
381
466
  await Promise.resolve(onApply?.(patch))
382
467
  }
383
468
 
469
+ const handleSelectRemote = (status: RemoteDeployStatus) => {
470
+ setRemoteStatus(status)
471
+ if (status.target) {
472
+ setSshHost(status.target)
473
+ }
474
+ }
475
+
384
476
  const buildRemoteDeploymentPatch = (overrides?: Partial<NonNullable<ApplyPatch['deployment']>>): NonNullable<ApplyPatch['deployment']> => ({
385
477
  method: overrides?.method || (remoteTemplate === 'docker' ? 'bundle' : 'bundle'),
386
478
  provider: overrides?.provider || (remoteTemplate === 'docker' ? remoteProvider : remoteTemplate),
@@ -413,7 +505,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
413
505
  token: deployToken.trim() || undefined,
414
506
  })
415
507
  if (!result.ok || !result.local) throw new Error(result.error || 'Local OpenClaw deploy failed.')
416
- setLocalStatus(result.local)
508
+ syncLocalResponse(result)
417
509
  if (result.token) setDeployToken(result.token)
418
510
  const verify = await api<DeployActionResponse>('POST', '/openclaw/deploy', {
419
511
  action: 'verify',
@@ -422,7 +514,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
422
514
  }).catch(() => ({ ok: false } as DeployActionResponse))
423
515
  if (verify.verify) {
424
516
  setVerifySummary(verify.verify.ok
425
- ? `Verified ${verify.verify.endpoint} with ${verify.verify.models.length} model${verify.verify.models.length === 1 ? '' : 's'}.`
517
+ ? (verify.verify.message || `Verified ${verify.verify.endpoint} with ${verify.verify.models.length} model${verify.verify.models.length === 1 ? '' : 's'}.`)
426
518
  : (verify.verify.error || verify.verify.hint || 'Verification failed.'))
427
519
  }
428
520
  await applyDeploymentPatch({
@@ -441,7 +533,9 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
441
533
  lastVerifiedAt: verify.verify ? Date.now() : null,
442
534
  lastVerifiedOk: verify.verify?.ok ?? null,
443
535
  lastVerifiedMessage: verify.verify
444
- ? (verify.verify.error || verify.verify.hint || 'Verified successfully.')
536
+ ? (verify.verify.ok
537
+ ? (verify.verify.message || 'Verified successfully.')
538
+ : (verify.verify.error || verify.verify.hint || 'Verification failed.'))
445
539
  : null,
446
540
  },
447
541
  })
@@ -457,9 +551,9 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
457
551
  setLoading('stopping-local')
458
552
  setError('')
459
553
  try {
460
- const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', { action: 'stop-local' })
554
+ const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', { action: 'stop-local', localId: localStatus?.id || undefined })
461
555
  if (!result.ok || !result.local) throw new Error(result.error || 'Failed to stop local OpenClaw.')
462
- setLocalStatus(result.local)
556
+ syncLocalResponse(result)
463
557
  showMessage('Stopped managed local OpenClaw runtime.')
464
558
  } catch (err: unknown) {
465
559
  setError(err instanceof Error ? err.message : 'Failed to stop local OpenClaw.')
@@ -474,11 +568,12 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
474
568
  try {
475
569
  const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', {
476
570
  action: 'restart-local',
571
+ localId: localStatus?.id || undefined,
477
572
  port: localPort,
478
573
  token: deployToken.trim() || undefined,
479
574
  })
480
575
  if (!result.ok || !result.local) throw new Error(result.error || 'Failed to restart local OpenClaw.')
481
- setLocalStatus(result.local)
576
+ syncLocalResponse(result)
482
577
  if (result.token) setDeployToken(result.token)
483
578
  await applyDeploymentPatch({
484
579
  endpoint: result.local.endpoint,
@@ -503,6 +598,45 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
503
598
  }
504
599
  }
505
600
 
601
+ const handleRestartSpecificLocal = async (status: LocalDeployStatus) => {
602
+ setLoading('restarting-local')
603
+ setError('')
604
+ try {
605
+ const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', {
606
+ action: 'restart-local',
607
+ localId: status.id,
608
+ port: status.port,
609
+ token: status.token || undefined,
610
+ })
611
+ if (!result.ok || !result.local) throw new Error(result.error || 'Failed to restart local OpenClaw.')
612
+ syncLocalResponse(result)
613
+ if (result.token) setDeployToken(result.token)
614
+ showMessage(`Restarted ${status.name}.`)
615
+ } catch (err: unknown) {
616
+ setError(err instanceof Error ? err.message : 'Failed to restart local OpenClaw.')
617
+ } finally {
618
+ setLoading('idle')
619
+ }
620
+ }
621
+
622
+ const handleStopSpecificLocal = async (status: LocalDeployStatus) => {
623
+ setLoading('stopping-local')
624
+ setError('')
625
+ try {
626
+ const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', {
627
+ action: 'stop-local',
628
+ localId: status.id,
629
+ })
630
+ if (!result.ok || !result.local) throw new Error(result.error || 'Failed to stop local OpenClaw.')
631
+ syncLocalResponse(result)
632
+ showMessage(`Stopped ${status.name}.`)
633
+ } catch (err: unknown) {
634
+ setError(err instanceof Error ? err.message : 'Failed to stop local OpenClaw.')
635
+ } finally {
636
+ setLoading('idle')
637
+ }
638
+ }
639
+
506
640
  const handleGenerateBundle = async () => {
507
641
  setLoading('generating-bundle')
508
642
  setError('')
@@ -556,7 +690,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
556
690
  })
557
691
  if (!result.verify) throw new Error(result.error || 'Verification failed.')
558
692
  const summary = result.verify.ok
559
- ? `Verified ${result.verify.endpoint} with ${result.verify.models.length} model${result.verify.models.length === 1 ? '' : 's'}.`
693
+ ? (result.verify.message || `Verified ${result.verify.endpoint} with ${result.verify.models.length} model${result.verify.models.length === 1 ? '' : 's'}.`)
560
694
  : (result.verify.error || result.verify.hint || 'Verification failed.')
561
695
  setVerifySummary(summary)
562
696
  await applyDeploymentPatch({
@@ -582,8 +716,12 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
582
716
  setError('')
583
717
  setVerifySummary('')
584
718
  try {
719
+ const trimmedHost = sshHost.trim()
720
+ const selectedRemoteMatchesHost = !!remoteStatus?.id && !!trimmedHost && remoteStatus.target === trimmedHost
585
721
  const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', {
586
722
  action: 'ssh-deploy',
723
+ remoteId: selectedRemoteMatchesHost ? remoteStatus?.id : undefined,
724
+ name: selectedRemoteMatchesHost ? remoteStatus?.name : undefined,
587
725
  template: remoteTemplate,
588
726
  target: remoteTarget.trim(),
589
727
  scheme: remoteScheme,
@@ -592,7 +730,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
592
730
  useCase,
593
731
  exposure,
594
732
  ssh: {
595
- host: sshHost.trim(),
733
+ host: trimmedHost,
596
734
  user: sshUser.trim() || undefined,
597
735
  port: sshPort,
598
736
  keyPath: sshKeyPath.trim() || undefined,
@@ -605,19 +743,19 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
605
743
  setBundleFile(result.bundle.files[0]?.name || '')
606
744
  }
607
745
  if (result.token) setDeployToken(result.token)
608
- setRemoteStatus(result.remote || null)
746
+ syncRemoteResponse(result)
609
747
  setCommandPreview(result.commandPreview || '')
610
748
  await applyDeploymentPatch({
611
749
  endpoint: result.bundle?.endpoint || endpoint || undefined,
612
750
  token: result.token || deployToken,
613
- name: suggestedName || result.bundle?.title || `SSH OpenClaw ${sshHost.trim()}`,
614
- notes: `Official OpenClaw deployed over SSH to ${sshHost.trim()}.`,
751
+ name: suggestedName || result.bundle?.title || `SSH OpenClaw ${trimmedHost}`,
752
+ notes: `Official OpenClaw deployed over SSH to ${trimmedHost}.`,
615
753
  deployment: buildRemoteDeploymentPatch({
616
754
  method: 'ssh',
617
755
  provider: remoteProvider,
618
756
  lastDeployAt: Date.now(),
619
757
  lastDeployAction: 'ssh-deploy',
620
- lastDeploySummary: result.summary || `Started SSH deploy to ${sshHost.trim()}.`,
758
+ lastDeploySummary: result.summary || `Started SSH deploy to ${trimmedHost}.`,
621
759
  lastDeployProcessId: result.processId || null,
622
760
  }),
623
761
  })
@@ -635,8 +773,13 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
635
773
  setLoading('remote-action')
636
774
  setError('')
637
775
  try {
776
+ if (!remoteStatus?.id && !sshHost.trim()) {
777
+ throw new Error('Pick or configure a remote deployment first.')
778
+ }
638
779
  const result = await api<DeployActionResponse>('POST', '/openclaw/deploy', {
639
780
  action,
781
+ remoteId: remoteStatus?.id || undefined,
782
+ name: remoteStatus?.name || undefined,
640
783
  token: action === 'remote-rotate-token' ? (deployToken.trim() || undefined) : undefined,
641
784
  backupPath: action === 'remote-restore' ? (restoreBackupPath.trim() || undefined) : undefined,
642
785
  ssh: {
@@ -649,7 +792,7 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
649
792
  })
650
793
  if (!result.ok) throw new Error(result.error || 'Remote lifecycle action failed.')
651
794
  if (result.token) setDeployToken(result.token)
652
- setRemoteStatus(result.remote || null)
795
+ syncRemoteResponse(result)
653
796
  setCommandPreview(result.commandPreview || '')
654
797
  if (result.remote?.lastBackupPath) {
655
798
  setRestoreBackupPath(result.remote.lastBackupPath)
@@ -833,6 +976,75 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
833
976
  {localStatus.tail}
834
977
  </pre>
835
978
  )}
979
+
980
+ {visibleLocalStatuses.length > 0 && (
981
+ <div className="mt-3 space-y-2">
982
+ <div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60">Managed instances</div>
983
+ {visibleLocalStatuses.map((status) => (
984
+ <div key={status.id} className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-3">
985
+ <div className="flex flex-wrap items-center justify-between gap-2">
986
+ <div>
987
+ <div className="text-[13px] font-600 text-text">
988
+ {status.name}
989
+ {status.isPrimary ? ' · primary' : ''}
990
+ </div>
991
+ <div className="mt-1 text-[11px] text-text-3 font-mono">
992
+ {status.endpoint}
993
+ </div>
994
+ </div>
995
+ <div className={`rounded-full px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${
996
+ status.running
997
+ ? 'bg-emerald-500/10 text-emerald-300'
998
+ : 'bg-white/[0.05] text-text-3'
999
+ }`}>
1000
+ {status.running ? 'running' : 'idle'}
1001
+ </div>
1002
+ </div>
1003
+ <div className="mt-2 flex flex-wrap gap-2">
1004
+ <button
1005
+ type="button"
1006
+ onClick={() => void handleRestartSpecificLocal(status)}
1007
+ disabled={loading !== 'idle'}
1008
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-1.5 text-[11px] font-700 text-text-2 cursor-pointer hover:bg-white/[0.04] transition-all disabled:opacity-40"
1009
+ >
1010
+ Restart
1011
+ </button>
1012
+ <button
1013
+ type="button"
1014
+ onClick={() => void handleVerify(status.endpoint, status.token || deployToken || undefined)}
1015
+ disabled={loading !== 'idle'}
1016
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-1.5 text-[11px] font-700 text-text-2 cursor-pointer hover:bg-white/[0.04] transition-all disabled:opacity-40"
1017
+ >
1018
+ Verify
1019
+ </button>
1020
+ <button
1021
+ type="button"
1022
+ onClick={() => void handleStopSpecificLocal(status)}
1023
+ disabled={loading !== 'idle' || !status.running}
1024
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-1.5 text-[11px] font-700 text-text-2 cursor-pointer hover:bg-white/[0.04] transition-all disabled:opacity-40"
1025
+ >
1026
+ Stop
1027
+ </button>
1028
+ <button
1029
+ type="button"
1030
+ onClick={() => onCopied(`local-endpoint-${status.id}`, status.endpoint)}
1031
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-1.5 text-[11px] font-700 text-text-2 cursor-pointer hover:bg-white/[0.04] transition-all"
1032
+ >
1033
+ {copiedKey === `local-endpoint-${status.id}` ? 'Copied endpoint' : 'Copy endpoint'}
1034
+ </button>
1035
+ <button
1036
+ type="button"
1037
+ onClick={() => onCopied(`local-token-${status.id}`, status.token || '')}
1038
+ disabled={!status.token}
1039
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-1.5 text-[11px] font-700 text-text-2 cursor-pointer hover:bg-white/[0.04] transition-all disabled:opacity-40"
1040
+ >
1041
+ {copiedKey === `local-token-${status.id}` ? 'Copied token' : 'Copy token'}
1042
+ </button>
1043
+ </div>
1044
+ </div>
1045
+ ))}
1046
+ </div>
1047
+ )}
836
1048
  </div>
837
1049
  </div>
838
1050
  )}
@@ -1093,11 +1305,47 @@ export function OpenClawDeployPanel(props: OpenClawDeployPanelProps) {
1093
1305
  </div>
1094
1306
  )}
1095
1307
 
1096
- {(verifySummary || commandPreview || remoteStatus) && (
1308
+ {(verifySummary || commandPreview || remoteStatus || visibleRemoteStatuses.length > 0) && (
1097
1309
  <div className="rounded-[12px] border border-white/[0.06] bg-bg px-4 py-4">
1098
1310
  {verifySummary && (
1099
1311
  <div className="text-[12px] text-text-2 leading-relaxed">{verifySummary}</div>
1100
1312
  )}
1313
+ {visibleRemoteStatuses.length > 0 && (
1314
+ <div className="mt-3 space-y-2">
1315
+ <div className="text-[10px] uppercase tracking-[0.08em] text-text-3/60">Managed remote deployments</div>
1316
+ {visibleRemoteStatuses.map((status) => (
1317
+ <div key={status.id} className="rounded-[10px] border border-white/[0.05] bg-white/[0.02] px-3 py-3">
1318
+ <div className="flex flex-wrap items-center justify-between gap-2">
1319
+ <div>
1320
+ <div className="text-[13px] font-600 text-text">
1321
+ {status.name}
1322
+ {status.isPrimary ? ' · primary' : ''}
1323
+ </div>
1324
+ <div className="mt-1 text-[11px] text-text-3 font-mono break-all">
1325
+ {status.target || 'n/a'}
1326
+ </div>
1327
+ </div>
1328
+ <div className="flex items-center gap-2">
1329
+ <div className={`rounded-full px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${
1330
+ status.active
1331
+ ? 'bg-emerald-500/10 text-emerald-300'
1332
+ : 'bg-white/[0.05] text-text-3'
1333
+ }`}>
1334
+ {status.status}
1335
+ </div>
1336
+ <button
1337
+ type="button"
1338
+ onClick={() => handleSelectRemote(status)}
1339
+ className="rounded-[10px] border border-white/[0.08] bg-transparent px-3 py-1.5 text-[11px] font-700 text-text-2 cursor-pointer hover:bg-white/[0.04] transition-all"
1340
+ >
1341
+ {remoteStatus?.id === status.id ? 'Selected' : 'Select'}
1342
+ </button>
1343
+ </div>
1344
+ </div>
1345
+ </div>
1346
+ ))}
1347
+ </div>
1348
+ )}
1101
1349
  {remoteStatus && (
1102
1350
  <div className="mt-3 grid gap-3 md:grid-cols-3">
1103
1351
  <div className="rounded-[10px] border border-white/[0.06] bg-white/[0.02] px-3 py-2">
@@ -3,13 +3,14 @@
3
3
  import { useEffect, useState, useCallback, useMemo } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { api } from '@/lib/api-client'
6
+ import { getPluginSourceLabel } from '@/lib/plugin-sources'
6
7
  import { toast } from 'sonner'
7
8
  import type { Agent, MarketplacePlugin, PluginMeta } from '@/types'
8
9
  import { AgentAvatar } from '@/components/agents/agent-avatar'
9
10
  import { ConfirmDialog } from '@/components/shared/confirm-dialog'
10
11
 
11
12
  type InstalledTab = 'core' | 'extensions'
12
- type TopTab = InstalledTab | 'swarmforge'
13
+ type TopTab = InstalledTab | 'marketplace'
13
14
 
14
15
  export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
15
16
  const plugins = useAppStore((s) => s.plugins)
@@ -56,7 +57,7 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
56
57
  }, [])
57
58
 
58
59
  useEffect(() => {
59
- if (inSidebar || tab !== 'swarmforge') return
60
+ if (inSidebar || tab !== 'marketplace') return
60
61
  const timer = setTimeout(() => { void loadMarketplace() }, 0)
61
62
  return () => clearTimeout(timer)
62
63
  }, [tab, inSidebar, loadMarketplace])
@@ -120,7 +121,13 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
120
121
  const toastId = toast.loading(`Installing ${p.name}...`)
121
122
  try {
122
123
  const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
123
- await api('POST', '/plugins/install', { url: p.url, filename: safeFilename })
124
+ await api('POST', '/plugins/install', {
125
+ url: p.url,
126
+ filename: safeFilename,
127
+ installMethod: 'marketplace',
128
+ sourceLabel: p.source,
129
+ installSource: p.catalogSource || p.source,
130
+ })
124
131
  await loadPlugins()
125
132
  toast.success(`Installed ${p.name}`, { id: toastId })
126
133
  } catch (err: unknown) {
@@ -181,8 +188,8 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
181
188
  <TabButton active={tab === 'extensions'} onClick={() => setTab('extensions')} count={extensionPlugins.length}>
182
189
  Extensions
183
190
  </TabButton>
184
- <TabButton active={tab === 'swarmforge'} onClick={() => setTab('swarmforge')}>
185
- SwarmForge
191
+ <TabButton active={tab === 'marketplace'} onClick={() => setTab('marketplace')}>
192
+ Marketplace
186
193
  </TabButton>
187
194
  </div>
188
195
 
@@ -214,17 +221,17 @@ export function PluginList({ inSidebar }: { inSidebar?: boolean }) {
214
221
  emptyMessage={search ? 'No extensions match your search' : 'No extensions installed'}
215
222
  emptyAction={!search ? (
216
223
  <button
217
- onClick={() => setTab('swarmforge')}
224
+ onClick={() => setTab('marketplace')}
218
225
  className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[12px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
219
226
  style={{ fontFamily: 'inherit' }}
220
227
  >
221
- Browse SwarmForge
228
+ Browse Marketplace
222
229
  </button>
223
230
  ) : undefined}
224
231
  />
225
232
  )}
226
233
 
227
- {tab === 'swarmforge' && (
234
+ {tab === 'marketplace' && (
228
235
  <MarketplaceTab
229
236
  marketplace={marketplace}
230
237
  loading={mpLoading}
@@ -447,6 +454,12 @@ function PluginCard({ plugin, allowDelete, agents, onEdit, onToggle, onDelete, o
447
454
  {badge}
448
455
  </span>
449
456
  ))}
457
+ {plugin.sourceLabel && (
458
+ <SourceChip label={getPluginSourceLabel(plugin.sourceLabel)} tone="publisher" />
459
+ )}
460
+ {plugin.installSource && plugin.installSource !== plugin.sourceLabel && (
461
+ <SourceChip label={`via ${getPluginSourceLabel(plugin.installSource)}`} tone="catalog" />
462
+ )}
450
463
  {plugin.hasDependencyManifest && (
451
464
  <span className={`text-[10px] font-700 px-1.5 py-0.5 rounded-full ${
452
465
  plugin.dependencyInstallStatus === 'installed'
@@ -550,7 +563,14 @@ function MarketplaceTab({ marketplace, loading, installing, installedFilenames,
550
563
  const q = search.toLowerCase()
551
564
  const filtered = marketplace
552
565
  .filter((p) => {
553
- if (q && !p.name.toLowerCase().includes(q) && !p.description.toLowerCase().includes(q) && !(p.tags ?? []).some((t) => t.toLowerCase().includes(q))) return false
566
+ const sourceTerms = [getPluginSourceLabel(p.source).toLowerCase(), getPluginSourceLabel(p.catalogSource).toLowerCase()]
567
+ if (
568
+ q
569
+ && !p.name.toLowerCase().includes(q)
570
+ && !p.description.toLowerCase().includes(q)
571
+ && !(p.tags ?? []).some((t) => t.toLowerCase().includes(q))
572
+ && !sourceTerms.some((term) => term.includes(q))
573
+ ) return false
554
574
  if (activeTag && !(p.tags ?? []).includes(activeTag)) return false
555
575
  return true
556
576
  })
@@ -606,6 +626,12 @@ function MarketplaceTab({ marketplace, loading, installing, installedFilenames,
606
626
  <span className="text-[10px] font-mono text-text-3/70">v{p.version}</span>
607
627
  {p.openclaw && <span className="text-[9px] font-600 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded-full">OpenClaw</span>}
608
628
  </div>
629
+ <div className="flex items-center gap-1.5 mt-2 flex-wrap">
630
+ {p.source && <SourceChip label={getPluginSourceLabel(p.source)} tone="publisher" />}
631
+ {p.catalogSource && p.catalogSource !== p.source && (
632
+ <SourceChip label={`via ${getPluginSourceLabel(p.catalogSource)}`} tone="catalog" />
633
+ )}
634
+ </div>
609
635
  <div className="text-[11px] text-text-3/60 mt-1 line-clamp-2">{p.description}</div>
610
636
  <div className="flex items-center gap-2 mt-2">
611
637
  <span className="text-[10px] text-text-3/70">by {p.author}</span>
@@ -645,3 +671,13 @@ function MarketplaceTab({ marketplace, loading, installing, installedFilenames,
645
671
  </div>
646
672
  )
647
673
  }
674
+
675
+ function SourceChip({ label, tone }: { label: string; tone: 'publisher' | 'catalog' }) {
676
+ return (
677
+ <span className={tone === 'publisher'
678
+ ? 'text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-sky-500/10 text-sky-300'
679
+ : 'text-[10px] font-700 px-1.5 py-0.5 rounded-full bg-white/[0.05] text-text-3/75'}>
680
+ {label}
681
+ </span>
682
+ )
683
+ }