@swarmclawai/swarmclaw 1.9.2 → 1.9.4
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 +23 -3
- package/electron-dist/main.js +218 -0
- package/package.json +2 -2
- package/src/app/api/extensions/managed-resources/route.test.ts +117 -0
- package/src/app/api/extensions/managed-resources/route.ts +116 -0
- package/src/app/api/gateways/[id]/environments/[environmentId]/route.ts +16 -0
- package/src/app/api/gateways/[id]/environments/route.ts +13 -0
- package/src/app/api/gateways/topology-route.test.ts +30 -0
- package/src/app/api/tasks/task-workspace-route.test.ts +4 -0
- package/src/cli/index.js +4 -0
- package/src/cli/spec.js +4 -0
- package/src/components/providers/provider-list.tsx +34 -1
- package/src/components/tasks/task-sheet.tsx +50 -0
- package/src/features/gateways/queries.ts +3 -0
- package/src/lib/server/extension-managed-resources.test.ts +159 -0
- package/src/lib/server/extension-managed-resources.ts +905 -0
- package/src/lib/server/extensions.ts +113 -2
- package/src/lib/server/gateways/gateway-profile-service.ts +2 -0
- package/src/lib/server/gateways/gateway-topology.test.ts +59 -3
- package/src/lib/server/gateways/gateway-topology.ts +129 -3
- package/src/lib/server/operations/operation-pulse.test.ts +29 -0
- package/src/lib/server/operations/operation-pulse.ts +9 -0
- package/src/lib/server/session-tools/extension-creator.ts +50 -0
- package/src/lib/server/tasks/task-execution-workspace.test.ts +14 -0
- package/src/lib/server/tasks/task-execution-workspace.ts +133 -6
- package/src/types/agent.ts +2 -0
- package/src/types/app-settings.ts +8 -0
- package/src/types/extension.ts +132 -0
- package/src/types/misc.ts +31 -0
- package/src/types/schedule.ts +3 -0
- package/src/types/task.ts +30 -0
- package/src/views/settings/extension-manager.tsx +157 -1
|
@@ -8,14 +8,43 @@ import { toast } from 'sonner'
|
|
|
8
8
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
9
9
|
import { errorMessage } from '@/lib/shared-utils'
|
|
10
10
|
|
|
11
|
+
type ManagedResourceStatus = 'declared' | 'resolved' | 'missing' | 'missing_ref' | 'unsupported_trigger'
|
|
12
|
+
type ManagedResourceSummary = {
|
|
13
|
+
extensions: Array<{
|
|
14
|
+
extensionId: string
|
|
15
|
+
extensionName: string
|
|
16
|
+
enabled: boolean
|
|
17
|
+
isBuiltin: boolean
|
|
18
|
+
agents: Array<{ resourceKey: string; displayName: string; status: ManagedResourceStatus; resourceId: string | null }>
|
|
19
|
+
schedules: Array<{ resourceKey: string; displayName: string; status: ManagedResourceStatus; resourceId: string | null }>
|
|
20
|
+
localFolders: Array<{ resourceKey: string; displayName: string; status: ManagedResourceStatus; configured?: boolean; healthy?: boolean }>
|
|
21
|
+
gatewayPlatforms: Array<{ platformKey: string; displayName: string; transport?: string; endpoint?: string | null }>
|
|
22
|
+
setupChecks: Array<{ checkKey: string; displayName: string; kind: string; required: boolean }>
|
|
23
|
+
}>
|
|
24
|
+
totals: {
|
|
25
|
+
extensions: number
|
|
26
|
+
agents: number
|
|
27
|
+
schedules: number
|
|
28
|
+
localFolders: number
|
|
29
|
+
gatewayPlatforms: number
|
|
30
|
+
setupChecks: number
|
|
31
|
+
resolvedAgents: number
|
|
32
|
+
resolvedSchedules: number
|
|
33
|
+
healthyLocalFolders: number
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
export function ExtensionManager() {
|
|
12
|
-
const [tab, setTab] = useState<'installed' | 'marketplace' | 'url'>('installed')
|
|
38
|
+
const [tab, setTab] = useState<'installed' | 'managed' | 'marketplace' | 'url'>('installed')
|
|
13
39
|
const [extensions, setExtensions] = useState<ExtensionMeta[]>([])
|
|
40
|
+
const [managedResources, setManagedResources] = useState<ManagedResourceSummary | null>(null)
|
|
14
41
|
const [marketplace, setMarketplace] = useState<MarketplaceExtension[]>([])
|
|
15
42
|
const [loading, setLoading] = useState(false)
|
|
43
|
+
const [managedLoading, setManagedLoading] = useState(false)
|
|
16
44
|
const [installing, setInstalling] = useState<string | null>(null)
|
|
17
45
|
const [updating, setUpdating] = useState<string | null>(null)
|
|
18
46
|
const [updatingAll, setUpdatingAll] = useState(false)
|
|
47
|
+
const [reconciling, setReconciling] = useState<string | null>(null)
|
|
19
48
|
const [urlInput, setUrlInput] = useState('')
|
|
20
49
|
const [urlFilename, setUrlFilename] = useState('')
|
|
21
50
|
const [urlStatus, setUrlStatus] = useState<{ ok: boolean; message: string } | null>(null)
|
|
@@ -29,6 +58,15 @@ export function ExtensionManager() {
|
|
|
29
58
|
} catch { /* ignore */ }
|
|
30
59
|
}, [])
|
|
31
60
|
|
|
61
|
+
const loadManagedResources = useCallback(async () => {
|
|
62
|
+
setManagedLoading(true)
|
|
63
|
+
try {
|
|
64
|
+
const data = await api<ManagedResourceSummary>('GET', '/extensions/managed-resources')
|
|
65
|
+
setManagedResources(data)
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
setManagedLoading(false)
|
|
68
|
+
}, [])
|
|
69
|
+
|
|
32
70
|
const loadMarketplace = useCallback(async (q = '') => {
|
|
33
71
|
setLoading(true)
|
|
34
72
|
try {
|
|
@@ -39,6 +77,7 @@ export function ExtensionManager() {
|
|
|
39
77
|
}, [])
|
|
40
78
|
|
|
41
79
|
useEffect(() => { loadExtensions() }, [loadExtensions])
|
|
80
|
+
useEffect(() => { if (tab === 'managed') loadManagedResources() }, [tab, loadManagedResources])
|
|
42
81
|
useEffect(() => { if (tab === 'marketplace') loadMarketplace(marketplaceQuery) }, [tab, loadMarketplace, marketplaceQuery])
|
|
43
82
|
|
|
44
83
|
const toggleExtension = async (filename: string, enabled: boolean) => {
|
|
@@ -87,6 +126,25 @@ export function ExtensionManager() {
|
|
|
87
126
|
}
|
|
88
127
|
}
|
|
89
128
|
|
|
129
|
+
const handleReconcileManaged = async (extensionId?: string) => {
|
|
130
|
+
const id = extensionId || 'all'
|
|
131
|
+
setReconciling(id)
|
|
132
|
+
try {
|
|
133
|
+
const result = await api<{ createdAgents: string[]; updatedAgents: string[]; createdSchedules: string[]; updatedSchedules: string[] }>(
|
|
134
|
+
'POST',
|
|
135
|
+
'/extensions/managed-resources',
|
|
136
|
+
{ action: 'reconcile', ...(extensionId ? { extensionId } : {}) },
|
|
137
|
+
{ timeoutMs: 30_000 },
|
|
138
|
+
)
|
|
139
|
+
await Promise.all([loadExtensions(), loadManagedResources()])
|
|
140
|
+
toast.success(`Reconciled ${result.createdAgents.length + result.updatedAgents.length} agents and ${result.createdSchedules.length + result.updatedSchedules.length} schedules`)
|
|
141
|
+
} catch (err: unknown) {
|
|
142
|
+
toast.error(errorMessage(err))
|
|
143
|
+
} finally {
|
|
144
|
+
setReconciling(null)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
90
148
|
const installFromMarketplace = async (p: MarketplaceExtension) => {
|
|
91
149
|
setInstalling(p.id)
|
|
92
150
|
const toastId = toast.loading(`Installing ${p.name}...`)
|
|
@@ -188,6 +246,14 @@ export function ExtensionManager() {
|
|
|
188
246
|
</span>
|
|
189
247
|
</div>
|
|
190
248
|
)}
|
|
249
|
+
{((p.managedAgentCount || 0) + (p.managedScheduleCount || 0) + (p.localFolderCount || 0) + (p.gatewayPlatformCount || 0) + (p.setupCheckCount || 0)) > 0 && (
|
|
250
|
+
<div className="mt-1.5 flex items-center gap-1.5 text-[10px] font-700 uppercase tracking-[0.08em] flex-wrap">
|
|
251
|
+
{(p.managedAgentCount || 0) > 0 && <span className="px-1.5 py-0.5 rounded bg-emerald-500/10 text-emerald-300">{p.managedAgentCount} agents</span>}
|
|
252
|
+
{(p.managedScheduleCount || 0) > 0 && <span className="px-1.5 py-0.5 rounded bg-violet-500/10 text-violet-300">{p.managedScheduleCount} routines</span>}
|
|
253
|
+
{(p.localFolderCount || 0) > 0 && <span className="px-1.5 py-0.5 rounded bg-sky-500/10 text-sky-300">{p.localFolderCount} folders</span>}
|
|
254
|
+
{(p.gatewayPlatformCount || 0) > 0 && <span className="px-1.5 py-0.5 rounded bg-amber-500/10 text-amber-300">{p.gatewayPlatformCount} gateways</span>}
|
|
255
|
+
</div>
|
|
256
|
+
)}
|
|
191
257
|
{p.autoDisabled && (
|
|
192
258
|
<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">
|
|
193
259
|
{p.lastFailureStage ? `Error at ${p.lastFailureStage}:` : 'Last error:'} {p.lastFailureError}
|
|
@@ -242,6 +308,7 @@ export function ExtensionManager() {
|
|
|
242
308
|
</div>
|
|
243
309
|
<div className="flex bg-surface p-1.5 rounded-[14px] border border-white/[0.04]">
|
|
244
310
|
<button onClick={() => setTab('installed')} className={tabClass('installed')}>Installed</button>
|
|
311
|
+
<button onClick={() => setTab('managed')} className={tabClass('managed')}>Managed</button>
|
|
245
312
|
<button onClick={() => setTab('marketplace')} className={tabClass('marketplace')}>Marketplace</button>
|
|
246
313
|
<button onClick={() => setTab('url')} className={tabClass('url')}>Manual</button>
|
|
247
314
|
</div>
|
|
@@ -302,6 +369,95 @@ export function ExtensionManager() {
|
|
|
302
369
|
</div>
|
|
303
370
|
)}
|
|
304
371
|
|
|
372
|
+
{tab === 'managed' && (
|
|
373
|
+
<div className="space-y-6">
|
|
374
|
+
<div className="flex items-center justify-between gap-4 px-1">
|
|
375
|
+
<div>
|
|
376
|
+
<h3 className="text-[13px] font-700 text-text-2">Managed Resources</h3>
|
|
377
|
+
<p className="text-[12px] text-text-3/50 mt-0.5">Extension-declared agents, routines, folders, gateways, and setup checks.</p>
|
|
378
|
+
</div>
|
|
379
|
+
<button
|
|
380
|
+
onClick={() => handleReconcileManaged()}
|
|
381
|
+
disabled={!!reconciling || managedLoading}
|
|
382
|
+
className="h-9 px-4 rounded-[10px] bg-accent-bright text-white text-[11px] font-800 uppercase tracking-[0.08em] border-none cursor-pointer disabled:opacity-50"
|
|
383
|
+
>
|
|
384
|
+
{reconciling === 'all' ? 'Reconciling...' : 'Reconcile All'}
|
|
385
|
+
</button>
|
|
386
|
+
</div>
|
|
387
|
+
|
|
388
|
+
{managedLoading ? (
|
|
389
|
+
<div className="py-20 flex justify-center">
|
|
390
|
+
<div className="w-8 h-8 border-2 border-accent-bright/20 border-t-accent-bright rounded-full animate-spin" />
|
|
391
|
+
</div>
|
|
392
|
+
) : !managedResources || managedResources.extensions.length === 0 ? (
|
|
393
|
+
<div className="py-20 text-center rounded-[24px] border border-dashed border-white/[0.06]">
|
|
394
|
+
<p className="text-[14px] text-text-3/50">No managed resource declarations found</p>
|
|
395
|
+
</div>
|
|
396
|
+
) : (
|
|
397
|
+
<div className="space-y-4">
|
|
398
|
+
<div className="grid grid-cols-2 md:grid-cols-5 gap-3">
|
|
399
|
+
{[
|
|
400
|
+
['Agents', `${managedResources.totals.resolvedAgents}/${managedResources.totals.agents}`],
|
|
401
|
+
['Routines', `${managedResources.totals.resolvedSchedules}/${managedResources.totals.schedules}`],
|
|
402
|
+
['Folders', `${managedResources.totals.healthyLocalFolders}/${managedResources.totals.localFolders}`],
|
|
403
|
+
['Gateways', String(managedResources.totals.gatewayPlatforms)],
|
|
404
|
+
['Setup', String(managedResources.totals.setupChecks)],
|
|
405
|
+
].map(([label, value]) => (
|
|
406
|
+
<div key={label} className="rounded-[12px] bg-surface border border-white/[0.06] p-4">
|
|
407
|
+
<div className="text-[10px] font-800 uppercase tracking-[0.12em] text-text-3/50">{label}</div>
|
|
408
|
+
<div className="text-[22px] font-800 text-text mt-1">{value}</div>
|
|
409
|
+
</div>
|
|
410
|
+
))}
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
{managedResources.extensions.map((entry) => (
|
|
414
|
+
<div key={entry.extensionId} className="rounded-[18px] bg-surface border border-white/[0.06] p-5">
|
|
415
|
+
<div className="flex items-center justify-between gap-4 mb-4">
|
|
416
|
+
<div className="min-w-0">
|
|
417
|
+
<div className="flex items-center gap-2">
|
|
418
|
+
<h4 className="text-[14px] font-800 text-text truncate">{entry.extensionName}</h4>
|
|
419
|
+
<span className="text-[10px] font-mono text-text-3/50 truncate">{entry.extensionId}</span>
|
|
420
|
+
</div>
|
|
421
|
+
<div className="text-[11px] text-text-3/50 mt-0.5">
|
|
422
|
+
{entry.agents.length} agents, {entry.schedules.length} routines, {entry.localFolders.length} folders, {entry.gatewayPlatforms.length} gateways
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
<button
|
|
426
|
+
onClick={() => handleReconcileManaged(entry.extensionId)}
|
|
427
|
+
disabled={!!reconciling}
|
|
428
|
+
className="h-8 px-3 rounded-[9px] bg-white/[0.05] hover:bg-white/[0.08] text-text-2 text-[10px] font-800 uppercase tracking-[0.08em] border border-white/[0.06] cursor-pointer disabled:opacity-50"
|
|
429
|
+
>
|
|
430
|
+
{reconciling === entry.extensionId ? 'Reconciling...' : 'Reconcile'}
|
|
431
|
+
</button>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
435
|
+
{[
|
|
436
|
+
['Agents', entry.agents.map((item) => `${item.displayName} - ${item.status}`)],
|
|
437
|
+
['Routines', entry.schedules.map((item) => `${item.displayName} - ${item.status}`)],
|
|
438
|
+
['Folders', entry.localFolders.map((item) => `${item.displayName} - ${item.configured ? 'configured' : 'missing'}`)],
|
|
439
|
+
].map(([label, rows]) => (
|
|
440
|
+
<div key={label as string} className="rounded-[12px] bg-bg/60 border border-white/[0.04] p-3 min-h-[96px]">
|
|
441
|
+
<div className="text-[10px] font-800 uppercase tracking-[0.12em] text-text-3/50 mb-2">{label as string}</div>
|
|
442
|
+
{(rows as string[]).length > 0 ? (
|
|
443
|
+
<div className="space-y-1">
|
|
444
|
+
{(rows as string[]).slice(0, 4).map((row) => (
|
|
445
|
+
<div key={row} className="text-[11px] text-text-3/80 truncate">{row}</div>
|
|
446
|
+
))}
|
|
447
|
+
</div>
|
|
448
|
+
) : (
|
|
449
|
+
<div className="text-[11px] text-text-3/35">None</div>
|
|
450
|
+
)}
|
|
451
|
+
</div>
|
|
452
|
+
))}
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
))}
|
|
456
|
+
</div>
|
|
457
|
+
)}
|
|
458
|
+
</div>
|
|
459
|
+
)}
|
|
460
|
+
|
|
305
461
|
{tab === 'marketplace' && (
|
|
306
462
|
<div className="space-y-6">
|
|
307
463
|
<div className="relative group">
|