@swarmclawai/swarmclaw 0.6.8 → 0.7.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.
- package/README.md +70 -45
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +29 -6
- package/src/components/home/home-view.tsx +20 -14
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +73 -21
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +19 -7
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +170 -66
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +66 -64
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +11 -1
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +66 -31
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState, useCallback } from 'react'
|
|
3
|
+
import { useEffect, useState, useCallback, useMemo } from 'react'
|
|
4
4
|
import { api } from '@/lib/api-client'
|
|
5
5
|
import type { PluginMeta, MarketplacePlugin } from '@/types'
|
|
6
|
+
import { toast } from 'sonner'
|
|
7
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
6
8
|
|
|
7
9
|
export function PluginManager() {
|
|
8
10
|
const [tab, setTab] = useState<'installed' | 'marketplace' | 'url'>('installed')
|
|
@@ -10,9 +12,13 @@ export function PluginManager() {
|
|
|
10
12
|
const [marketplace, setMarketplace] = useState<MarketplacePlugin[]>([])
|
|
11
13
|
const [loading, setLoading] = useState(false)
|
|
12
14
|
const [installing, setInstalling] = useState<string | null>(null)
|
|
15
|
+
const [updating, setUpdating] = useState<string | null>(null)
|
|
16
|
+
const [updatingAll, setUpdatingAll] = useState(false)
|
|
13
17
|
const [urlInput, setUrlInput] = useState('')
|
|
14
18
|
const [urlFilename, setUrlFilename] = useState('')
|
|
15
19
|
const [urlStatus, setUrlStatus] = useState<{ ok: boolean; message: string } | null>(null)
|
|
20
|
+
const [marketplaceQuery, setMarketplaceQuery] = useState('')
|
|
21
|
+
const [confirmDelete, setConfirmDelete] = useState<{ filename: string; name: string } | null>(null)
|
|
16
22
|
|
|
17
23
|
const loadPlugins = useCallback(async () => {
|
|
18
24
|
try {
|
|
@@ -21,33 +27,89 @@ export function PluginManager() {
|
|
|
21
27
|
} catch { /* ignore */ }
|
|
22
28
|
}, [])
|
|
23
29
|
|
|
24
|
-
const loadMarketplace = useCallback(async () => {
|
|
30
|
+
const loadMarketplace = useCallback(async (q = '') => {
|
|
25
31
|
setLoading(true)
|
|
26
32
|
try {
|
|
27
|
-
const data = await api<MarketplacePlugin[]>('GET',
|
|
33
|
+
const data = await api<MarketplacePlugin[]>('GET', `/plugins/marketplace?q=${encodeURIComponent(q)}`)
|
|
28
34
|
if (Array.isArray(data)) setMarketplace(data)
|
|
29
35
|
} catch { /* ignore */ }
|
|
30
36
|
setLoading(false)
|
|
31
37
|
}, [])
|
|
32
38
|
|
|
33
|
-
useEffect(() => { loadPlugins() }, [])
|
|
34
|
-
useEffect(() => { if (tab === 'marketplace') loadMarketplace() }, [tab])
|
|
39
|
+
useEffect(() => { loadPlugins() }, [loadPlugins])
|
|
40
|
+
useEffect(() => { if (tab === 'marketplace') loadMarketplace(marketplaceQuery) }, [tab, loadMarketplace, marketplaceQuery])
|
|
35
41
|
|
|
36
42
|
const togglePlugin = async (filename: string, enabled: boolean) => {
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
try {
|
|
44
|
+
await api('POST', '/plugins', { filename, enabled })
|
|
45
|
+
toast.success(enabled ? 'Plugin enabled' : 'Plugin disabled')
|
|
46
|
+
loadPlugins()
|
|
47
|
+
} catch (err: unknown) {
|
|
48
|
+
toast.error(err instanceof Error ? err.message : 'Failed to toggle plugin')
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const deletePlugin = async (filename: string, name: string) => {
|
|
53
|
+
try {
|
|
54
|
+
await api('DELETE', `/plugins?filename=${encodeURIComponent(filename)}`)
|
|
55
|
+
toast.success(`Deleted ${name}`)
|
|
56
|
+
loadPlugins()
|
|
57
|
+
} catch (err: unknown) {
|
|
58
|
+
toast.error(err instanceof Error ? err.message : 'Delete failed')
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const handleUpdateOne = async (id: string) => {
|
|
63
|
+
setUpdating(id)
|
|
64
|
+
try {
|
|
65
|
+
await api('PATCH', `/plugins?id=${id}`)
|
|
66
|
+
toast.success('Plugin updated')
|
|
67
|
+
await loadPlugins()
|
|
68
|
+
} catch (err: unknown) {
|
|
69
|
+
toast.error(err instanceof Error ? err.message : String(err))
|
|
70
|
+
} finally {
|
|
71
|
+
setUpdating(null)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const handleUpdateAll = async () => {
|
|
76
|
+
setUpdatingAll(true)
|
|
77
|
+
try {
|
|
78
|
+
await api('PATCH', '/plugins?all=true')
|
|
79
|
+
toast.success('All plugins updated')
|
|
80
|
+
await loadPlugins()
|
|
81
|
+
} catch (err: unknown) {
|
|
82
|
+
toast.error(err instanceof Error ? err.message : String(err))
|
|
83
|
+
} finally {
|
|
84
|
+
setUpdatingAll(false)
|
|
85
|
+
}
|
|
39
86
|
}
|
|
40
87
|
|
|
41
88
|
const installFromMarketplace = async (p: MarketplacePlugin) => {
|
|
42
89
|
setInstalling(p.id)
|
|
90
|
+
const toastId = toast.loading(`Installing ${p.name}...`)
|
|
43
91
|
try {
|
|
44
|
-
|
|
92
|
+
if (!p.url) throw new Error('No functional URL found for this plugin')
|
|
93
|
+
|
|
94
|
+
const safeFilename = `${p.id.replace(/[^a-zA-Z0-9.-]/g, '_')}.js`
|
|
95
|
+
|
|
96
|
+
await api('POST', '/plugins/install', {
|
|
97
|
+
url: p.url,
|
|
98
|
+
filename: safeFilename
|
|
99
|
+
})
|
|
100
|
+
|
|
45
101
|
await loadPlugins()
|
|
46
102
|
setTab('installed')
|
|
47
|
-
|
|
48
|
-
|
|
103
|
+
toast.success(`Successfully installed ${p.name}`, { id: toastId })
|
|
104
|
+
} catch (err: unknown) {
|
|
105
|
+
console.error('[plugin-manager] Installation failed:', err)
|
|
106
|
+
toast.error(err instanceof Error ? err.message : 'Install failed', { id: toastId })
|
|
107
|
+
} finally {
|
|
108
|
+
setInstalling(null)
|
|
109
|
+
}
|
|
49
110
|
}
|
|
50
111
|
|
|
112
|
+
|
|
51
113
|
const installFromUrl = async () => {
|
|
52
114
|
if (!urlInput || !urlFilename) return
|
|
53
115
|
setUrlStatus(null)
|
|
@@ -58,150 +120,313 @@ export function PluginManager() {
|
|
|
58
120
|
setUrlStatus({ ok: true, message: 'Installed successfully' })
|
|
59
121
|
setUrlInput('')
|
|
60
122
|
setUrlFilename('')
|
|
61
|
-
|
|
62
|
-
|
|
123
|
+
toast.success('Plugin installed from URL')
|
|
124
|
+
} catch (err: unknown) {
|
|
125
|
+
setUrlStatus({ ok: false, message: err instanceof Error ? err.message : 'Install failed' })
|
|
63
126
|
}
|
|
64
127
|
setInstalling(null)
|
|
65
128
|
}
|
|
66
129
|
|
|
67
|
-
const
|
|
130
|
+
const { corePlugins, installedPlugins } = useMemo(() => {
|
|
131
|
+
return {
|
|
132
|
+
corePlugins: plugins.filter(p => p.source === 'local'),
|
|
133
|
+
installedPlugins: plugins.filter(p => p.source !== 'local')
|
|
134
|
+
}
|
|
135
|
+
}, [plugins])
|
|
68
136
|
|
|
69
137
|
const tabClass = (t: string) =>
|
|
70
|
-
`py-2
|
|
138
|
+
`py-2 px-4 rounded-[10px] text-center cursor-pointer transition-all text-[12px] font-600 border h-9 flex items-center justify-center
|
|
71
139
|
${tab === t
|
|
72
|
-
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
73
|
-
: 'bg-
|
|
140
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright shadow-[0_0_15px_rgba(99,102,241,0.1)]'
|
|
141
|
+
: 'bg-surface border-white/[0.06] text-text-3 hover:bg-surface-2 hover:border-white/[0.12]'}`
|
|
74
142
|
|
|
75
|
-
|
|
76
|
-
<div>
|
|
77
|
-
<div className="flex
|
|
78
|
-
<
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
143
|
+
const renderPluginItem = (p: PluginMeta) => (
|
|
144
|
+
<div key={p.filename} className="group flex items-center gap-4 py-3.5 px-5 rounded-[18px] bg-surface border border-white/[0.06] hover:border-white/[0.12] transition-all">
|
|
145
|
+
<div className="flex-1 min-w-0">
|
|
146
|
+
<div className="flex items-center gap-2 mb-0.5">
|
|
147
|
+
<span className="text-[14px] font-700 text-text truncate tracking-tight">{p.name}</span>
|
|
148
|
+
<span className="text-[9px] font-mono font-700 text-text-3/40 bg-white/[0.04] px-1.5 py-0.5 rounded uppercase tracking-wider">v{p.version}</span>
|
|
149
|
+
{p.openclaw && <span className="text-[9px] font-700 text-emerald-400 bg-emerald-400/10 px-1.5 py-0.5 rounded uppercase tracking-wider border border-emerald-400/10">OpenClaw</span>}
|
|
150
|
+
{p.autoDisabled && (
|
|
151
|
+
<span className="text-[9px] font-700 text-amber-300 bg-amber-500/10 px-1.5 py-0.5 rounded uppercase tracking-wider border border-amber-500/10">
|
|
152
|
+
Auto-disabled
|
|
153
|
+
</span>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
<div className="text-[11px] font-mono text-text-3/40 truncate">{p.filename}</div>
|
|
157
|
+
{p.description && <div className="text-[12px] text-text-3/70 mt-1 line-clamp-1">{p.description}</div>}
|
|
158
|
+
{p.autoDisabled && (
|
|
159
|
+
<div className="text-[11px] text-amber-400/90 mt-1.5 p-2 rounded-[8px] bg-amber-500/[0.03] border border-amber-500/10">
|
|
160
|
+
{p.lastFailureStage ? `Error at ${p.lastFailureStage}:` : 'Last error:'} {p.lastFailureError}
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
87
163
|
</div>
|
|
164
|
+
|
|
165
|
+
<div className="flex items-center gap-2">
|
|
166
|
+
{p.source !== 'local' && (
|
|
167
|
+
<div className="flex items-center opacity-0 group-hover:opacity-100 transition-opacity">
|
|
168
|
+
<button
|
|
169
|
+
onClick={() => handleUpdateOne(p.filename)}
|
|
170
|
+
disabled={!!updating}
|
|
171
|
+
className="p-2 rounded-[8px] text-text-3 hover:text-accent-bright hover:bg-accent-bright/10 transition-all border-none bg-transparent cursor-pointer"
|
|
172
|
+
title="Check for updates"
|
|
173
|
+
>
|
|
174
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className={updating === p.filename ? 'animate-spin' : ''}>
|
|
175
|
+
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" /><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
|
|
176
|
+
</svg>
|
|
177
|
+
</button>
|
|
178
|
+
<button
|
|
179
|
+
onClick={() => setConfirmDelete({ filename: p.filename, name: p.name })}
|
|
180
|
+
className="p-2 rounded-[8px] text-text-3 hover:text-red-400 hover:bg-red-400/10 transition-all border-none bg-transparent cursor-pointer"
|
|
181
|
+
title="Uninstall plugin"
|
|
182
|
+
>
|
|
183
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
184
|
+
<path d="M4 7l16 0" /><path d="M10 11l0 6" /><path d="M14 11l0 6" /><path d="M5 7l1 12a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2l1 -12" /><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
|
|
185
|
+
</svg>
|
|
186
|
+
</button>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
<div
|
|
190
|
+
onClick={() => togglePlugin(p.filename, !p.enabled)}
|
|
191
|
+
className={`w-10 h-6 rounded-full transition-all duration-200 relative cursor-pointer shrink-0
|
|
192
|
+
${p.enabled ? 'bg-accent-bright' : 'bg-white/[0.08]'}`}
|
|
193
|
+
>
|
|
194
|
+
<div className={`absolute top-1 w-4 h-4 rounded-full bg-white shadow-sm transition-all duration-200 ${p.enabled ? 'left-5' : 'left-1'}`} />
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div className="flex-1 overflow-y-auto">
|
|
202
|
+
<div className="max-w-4xl mx-auto px-6 py-10">
|
|
203
|
+
<div className="flex items-center justify-between gap-4 mb-10">
|
|
204
|
+
<div>
|
|
205
|
+
<h1 className="font-display text-[32px] font-800 tracking-[-0.04em] text-text mb-1.5">Plugins</h1>
|
|
206
|
+
<p className="text-[14px] text-text-3 max-w-md leading-relaxed">
|
|
207
|
+
Extend your swarm with new capabilities, UI modules, and platform connectors.
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
<div className="flex bg-surface p-1.5 rounded-[14px] border border-white/[0.04]">
|
|
211
|
+
<button onClick={() => setTab('installed')} className={tabClass('installed')}>Installed</button>
|
|
212
|
+
<button onClick={() => setTab('marketplace')} className={tabClass('marketplace')}>Marketplace</button>
|
|
213
|
+
<button onClick={() => setTab('url')} className={tabClass('url')}>Manual</button>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
88
216
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
217
|
+
{tab === 'installed' && (
|
|
218
|
+
<div className="space-y-10">
|
|
219
|
+
<div className="flex items-center justify-between px-1">
|
|
220
|
+
<div className="flex items-center gap-2.5">
|
|
221
|
+
<div className="w-2 h-2 rounded-full bg-accent-bright animate-pulse" />
|
|
222
|
+
<span className="text-[11px] font-800 uppercase tracking-[0.15em] text-text-3">Active Registry</span>
|
|
223
|
+
</div>
|
|
224
|
+
{installedPlugins.length > 0 && (
|
|
225
|
+
<button
|
|
226
|
+
onClick={handleUpdateAll}
|
|
227
|
+
disabled={updatingAll}
|
|
228
|
+
className="flex items-center gap-2 text-[11px] font-700 uppercase tracking-wider text-accent-bright hover:text-accent-bright/80 disabled:opacity-50 transition-all border-none bg-transparent cursor-pointer"
|
|
229
|
+
>
|
|
230
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" className={updatingAll ? 'animate-spin' : ''}>
|
|
231
|
+
<path d="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" /><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" />
|
|
232
|
+
</svg>
|
|
233
|
+
{updatingAll ? 'Updating...' : 'Update All'}
|
|
234
|
+
</button>
|
|
235
|
+
)}
|
|
236
|
+
</div>
|
|
237
|
+
|
|
238
|
+
{plugins.length === 0 ? (
|
|
239
|
+
<div className="py-20 text-center rounded-[24px] border border-dashed border-white/[0.06]">
|
|
240
|
+
<p className="text-[14px] text-text-3/50">No plugins found in the registry</p>
|
|
241
|
+
</div>
|
|
242
|
+
) : (
|
|
243
|
+
<div className="space-y-10">
|
|
244
|
+
{corePlugins.length > 0 && (
|
|
245
|
+
<section>
|
|
246
|
+
<div className="mb-4 px-1">
|
|
247
|
+
<h3 className="text-[13px] font-700 text-text-2">Core Platform</h3>
|
|
248
|
+
<p className="text-[12px] text-text-3/50 mt-0.5">Built-in SwarmClaw official capabilities</p>
|
|
99
249
|
</div>
|
|
100
|
-
<div className="
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
>
|
|
108
|
-
<div className=
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
250
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
251
|
+
{corePlugins.map(renderPluginItem)}
|
|
252
|
+
</div>
|
|
253
|
+
</section>
|
|
254
|
+
)}
|
|
255
|
+
|
|
256
|
+
{installedPlugins.length > 0 && (
|
|
257
|
+
<section>
|
|
258
|
+
<div className="mb-4 px-1">
|
|
259
|
+
<h3 className="text-[13px] font-700 text-text-2">Extensions</h3>
|
|
260
|
+
<p className="text-[12px] text-text-3/50 mt-0.5">Custom and Marketplace installed plugins</p>
|
|
261
|
+
</div>
|
|
262
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
263
|
+
{installedPlugins.map(renderPluginItem)}
|
|
264
|
+
</div>
|
|
265
|
+
</section>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{tab === 'marketplace' && (
|
|
273
|
+
<div className="space-y-6">
|
|
274
|
+
<div className="relative group">
|
|
275
|
+
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-text-3 group-focus-within:text-accent-bright transition-colors">
|
|
276
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
277
|
+
<circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" />
|
|
278
|
+
</svg>
|
|
279
|
+
</div>
|
|
280
|
+
<input
|
|
281
|
+
type="text"
|
|
282
|
+
value={marketplaceQuery}
|
|
283
|
+
onChange={(e) => setMarketplaceQuery(e.target.value)}
|
|
284
|
+
placeholder="Search ClawHub & SwarmClaw Registry..."
|
|
285
|
+
className="w-full h-14 pl-11 pr-4 bg-surface border border-white/[0.08] rounded-[18px] text-[15px] text-text outline-none focus:border-accent-bright/40 focus:bg-surface-2 transition-all shadow-sm"
|
|
286
|
+
style={{ fontFamily: 'inherit' }}
|
|
287
|
+
/>
|
|
113
288
|
</div>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{marketplace.map((p) =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
289
|
+
|
|
290
|
+
{loading ? (
|
|
291
|
+
<div className="py-20 flex flex-col items-center gap-4">
|
|
292
|
+
<div className="w-8 h-8 border-2 border-accent-bright/20 border-t-accent-bright rounded-full animate-spin" />
|
|
293
|
+
<p className="text-[12px] text-text-3/70 animate-pulse uppercase tracking-[0.1em] font-700">Searching registries...</p>
|
|
294
|
+
</div>
|
|
295
|
+
) : (
|
|
296
|
+
<div className="grid grid-cols-1 gap-4">
|
|
297
|
+
{marketplace.map((p) => (
|
|
298
|
+
<div key={p.id} className="p-6 rounded-[22px] bg-surface border border-white/[0.06] flex items-start gap-6 hover:border-white/[0.12] transition-all">
|
|
299
|
+
<div className="w-12 h-12 rounded-[14px] bg-accent-bright/[0.03] border border-accent-bright/10 flex items-center justify-center shrink-0">
|
|
300
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-accent-bright">
|
|
301
|
+
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
|
302
|
+
</svg>
|
|
303
|
+
</div>
|
|
304
|
+
<div className="min-w-0 flex-1">
|
|
305
|
+
<div className="flex items-center gap-3 mb-1.5">
|
|
306
|
+
<span className="text-[16px] font-700 text-text tracking-tight">{p.name}</span>
|
|
307
|
+
<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'}`}>
|
|
308
|
+
{p.source || 'swarmclaw'}
|
|
309
|
+
</span>
|
|
310
|
+
</div>
|
|
311
|
+
<p className="text-[13px] text-text-3/80 leading-relaxed mb-4 line-clamp-2">{p.description}</p>
|
|
312
|
+
|
|
313
|
+
<div className="flex items-center gap-4">
|
|
314
|
+
<div className="text-[11px] text-text-3/40 font-600 uppercase tracking-wider flex items-center gap-1.5">
|
|
315
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
316
|
+
<circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 3" />
|
|
317
|
+
</svg>
|
|
318
|
+
v{p.version || '1.0.0'}
|
|
141
319
|
</div>
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
320
|
+
{p.author && (
|
|
321
|
+
<div className="text-[11px] text-text-3/40 font-600 uppercase tracking-wider flex items-center gap-1.5">
|
|
322
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
323
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
|
|
324
|
+
</svg>
|
|
325
|
+
{p.author}
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
<a
|
|
329
|
+
href={p.url}
|
|
330
|
+
target="_blank"
|
|
331
|
+
rel="noopener noreferrer"
|
|
332
|
+
className="text-[11px] text-accent-bright/60 hover:text-accent-bright font-700 uppercase tracking-widest no-underline transition-colors ml-auto flex items-center gap-1"
|
|
152
333
|
>
|
|
153
|
-
|
|
154
|
-
|
|
334
|
+
Source
|
|
335
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round">
|
|
336
|
+
<path d="M17 7l-10 10" /><path d="M8 7l9 0l0 9" />
|
|
337
|
+
</svg>
|
|
338
|
+
</a>
|
|
155
339
|
</div>
|
|
156
340
|
</div>
|
|
157
|
-
|
|
158
|
-
|
|
341
|
+
<button
|
|
342
|
+
disabled={!!installing}
|
|
343
|
+
onClick={() => installFromMarketplace(p)}
|
|
344
|
+
className={`px-6 py-2.5 rounded-[14px] font-display text-[13px] font-700 transition-all active:scale-[0.97] shrink-0
|
|
345
|
+
${installing === p.id
|
|
346
|
+
? 'bg-white/[0.04] text-text-3 animate-pulse'
|
|
347
|
+
: 'bg-accent-bright text-white shadow-[0_0_20px_rgba(56,189,248,0.2)] hover:bg-accent-bright/90 hover:shadow-[0_0_25px_rgba(56,189,248,0.3)]'}`}
|
|
348
|
+
style={{ fontFamily: 'inherit' }}
|
|
349
|
+
>
|
|
350
|
+
{installing === p.id ? '...' : 'Install'}
|
|
351
|
+
</button>
|
|
352
|
+
</div>
|
|
353
|
+
))}
|
|
354
|
+
{marketplace.length === 0 && (
|
|
355
|
+
<div className="py-20 text-center opacity-40">
|
|
356
|
+
<p className="text-[15px] font-600 mb-1">No results for "{marketplaceQuery}"</p>
|
|
357
|
+
<p className="text-[12px]">Try searching for generic terms like "wallet", "social", or "files"</p>
|
|
358
|
+
</div>
|
|
359
|
+
)}
|
|
159
360
|
</div>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{tab === 'url' && (
|
|
163
|
-
<div className="p-5 rounded-[14px] bg-surface border border-white/[0.06]">
|
|
164
|
-
<div className="mb-4">
|
|
165
|
-
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Plugin URL</label>
|
|
166
|
-
<input
|
|
167
|
-
type="url"
|
|
168
|
-
value={urlInput}
|
|
169
|
-
onChange={(e) => setUrlInput(e.target.value)}
|
|
170
|
-
placeholder="https://example.com/my-plugin.js"
|
|
171
|
-
className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/60 outline-none focus:border-accent-bright/30"
|
|
172
|
-
style={{ fontFamily: 'inherit' }}
|
|
173
|
-
/>
|
|
174
|
-
</div>
|
|
175
|
-
<div className="mb-4">
|
|
176
|
-
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Save as filename</label>
|
|
177
|
-
<input
|
|
178
|
-
type="text"
|
|
179
|
-
value={urlFilename}
|
|
180
|
-
onChange={(e) => setUrlFilename(e.target.value)}
|
|
181
|
-
placeholder="my-plugin.js"
|
|
182
|
-
className="w-full py-2.5 px-3 rounded-[10px] text-[13px] bg-bg border border-white/[0.06] text-text placeholder:text-text-3/60 outline-none focus:border-accent-bright/30"
|
|
183
|
-
style={{ fontFamily: 'inherit' }}
|
|
184
|
-
/>
|
|
361
|
+
)}
|
|
185
362
|
</div>
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{tab === 'url' && (
|
|
366
|
+
<div className="max-w-2xl mx-auto mt-10 p-8 rounded-[28px] bg-surface border border-white/[0.06] shadow-xl">
|
|
367
|
+
<h2 className="text-[18px] font-800 text-text mb-6 flex items-center gap-2">
|
|
368
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-accent-bright">
|
|
369
|
+
<path d="M10 14l11 -11" /><path d="M21 3l-6.5 18a0.55 .55 0 0 1 -1 0l-3.5 -7l-7 -3.5a0.55 .55 0 0 1 0 -1l18 -6.5" />
|
|
370
|
+
</svg>
|
|
371
|
+
Install from source URL
|
|
372
|
+
</h2>
|
|
373
|
+
<div className="space-y-5">
|
|
374
|
+
<div>
|
|
375
|
+
<label className="block text-[11px] font-700 text-text-3 uppercase tracking-widest mb-2.5 ml-1">JavaScript URL (HTTPS)</label>
|
|
376
|
+
<input
|
|
377
|
+
type="text"
|
|
378
|
+
value={urlInput}
|
|
379
|
+
onChange={(e) => setUrlInput(e.target.value)}
|
|
380
|
+
placeholder="https://example.com/my-plugin.js"
|
|
381
|
+
className="w-full h-12 px-4 bg-bg border border-white/[0.08] rounded-[14px] text-[14px] text-text outline-none focus:border-accent-bright/40 focus:ring-4 focus:ring-accent-bright/5 transition-all"
|
|
382
|
+
style={{ fontFamily: 'inherit' }}
|
|
383
|
+
/>
|
|
384
|
+
</div>
|
|
385
|
+
<div>
|
|
386
|
+
<label className="block text-[11px] font-700 text-text-3 uppercase tracking-widest mb-2.5 ml-1">Target Filename</label>
|
|
387
|
+
<input
|
|
388
|
+
type="text"
|
|
389
|
+
value={urlFilename}
|
|
390
|
+
onChange={(e) => setUrlFilename(e.target.value)}
|
|
391
|
+
placeholder="my-plugin.js"
|
|
392
|
+
className="w-full h-12 px-4 bg-bg border border-white/[0.08] rounded-[14px] text-[14px] text-text outline-none focus:border-accent-bright/40 focus:ring-4 focus:ring-accent-bright/5 transition-all"
|
|
393
|
+
style={{ fontFamily: 'inherit' }}
|
|
394
|
+
/>
|
|
395
|
+
</div>
|
|
396
|
+
<button
|
|
397
|
+
onClick={installFromUrl}
|
|
398
|
+
disabled={!urlInput || !urlFilename || installing === 'url'}
|
|
399
|
+
className="w-full h-12 bg-accent-bright text-white rounded-[14px] text-[14px] font-800 shadow-lg shadow-accent-bright/20 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
400
|
+
style={{ fontFamily: 'inherit' }}
|
|
401
|
+
>
|
|
402
|
+
{installing === 'url' ? 'Installing...' : 'Install Plugin'}
|
|
403
|
+
</button>
|
|
404
|
+
</div>
|
|
405
|
+
{urlStatus && (
|
|
406
|
+
<div className={`mt-5 p-4 rounded-[14px] flex items-center gap-3 text-[13px] font-600 border ${urlStatus.ok ? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20' : 'bg-red-500/10 text-red-400 border-red-500/20'}`}>
|
|
407
|
+
<div className={`w-2 h-2 rounded-full ${urlStatus.ok ? 'bg-emerald-400' : 'bg-red-400'}`} />
|
|
408
|
+
{urlStatus.message}
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
<p className="text-[11px] text-text-3/40 mt-6 leading-relaxed text-center italic">
|
|
412
|
+
SwarmClaw supports standalone CommonJS plugins and OpenClaw activate/deactivate formats.
|
|
198
413
|
</p>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
</div>
|
|
417
|
+
<ConfirmDialog
|
|
418
|
+
open={!!confirmDelete}
|
|
419
|
+
title="Delete Plugin"
|
|
420
|
+
message={confirmDelete ? `Delete "${confirmDelete.name}"? This cannot be undone.` : ''}
|
|
421
|
+
confirmLabel="Delete"
|
|
422
|
+
danger
|
|
423
|
+
onConfirm={() => {
|
|
424
|
+
if (!confirmDelete) return
|
|
425
|
+
void deletePlugin(confirmDelete.filename, confirmDelete.name)
|
|
426
|
+
setConfirmDelete(null)
|
|
427
|
+
}}
|
|
428
|
+
onCancel={() => setConfirmDelete(null)}
|
|
429
|
+
/>
|
|
205
430
|
</div>
|
|
206
431
|
)
|
|
207
432
|
}
|
|
@@ -9,7 +9,7 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
|
|
|
9
9
|
Capability Policy
|
|
10
10
|
</h3>
|
|
11
11
|
<p className="text-[12px] text-text-3 mb-5">
|
|
12
|
-
Centralized guardrails for agent
|
|
12
|
+
Centralized guardrails for agent plugin families. Applies to direct plugin calls and forced auto-routing.
|
|
13
13
|
</p>
|
|
14
14
|
<div className="p-6 rounded-[18px] bg-surface border border-white/[0.06]">
|
|
15
15
|
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-3">Policy Mode</label>
|
|
@@ -53,7 +53,7 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
|
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
55
|
<div>
|
|
56
|
-
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Blocked
|
|
56
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Blocked Plugins</label>
|
|
57
57
|
<input
|
|
58
58
|
type="text"
|
|
59
59
|
value={(appSettings.capabilityBlockedTools || []).join(', ')}
|
|
@@ -70,7 +70,7 @@ export function CapabilityPolicySection({ appSettings, patchSettings, inputClass
|
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
72
|
<div>
|
|
73
|
-
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Allowed
|
|
73
|
+
<label className="block font-display text-[11px] font-600 text-text-3 uppercase tracking-[0.08em] mb-2">Allowed Plugins (Override)</label>
|
|
74
74
|
<input
|
|
75
75
|
type="text"
|
|
76
76
|
value={(appSettings.capabilityAllowedTools || []).join(', ')}
|