@swarmclawai/swarmclaw 0.7.6 → 0.7.7
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 +18 -10
- package/package.json +1 -1
- package/src/app/api/agents/[id]/route.ts +16 -0
- package/src/app/api/agents/route.ts +2 -0
- package/src/app/api/chats/[id]/route.ts +21 -1
- package/src/app/api/chats/route.ts +12 -1
- package/src/app/api/external-agents/[id]/heartbeat/route.ts +3 -0
- package/src/app/api/external-agents/[id]/route.ts +38 -6
- package/src/app/api/external-agents/route.ts +17 -1
- package/src/app/api/gateways/[id]/health/route.ts +8 -0
- package/src/app/api/gateways/[id]/route.ts +53 -1
- package/src/app/api/gateways/route.ts +53 -0
- package/src/app/api/openclaw/deploy/route.ts +139 -0
- package/src/cli/index.js +40 -0
- package/src/cli/index.test.js +68 -0
- package/src/cli/spec.js +60 -0
- package/src/components/agents/agent-sheet.tsx +97 -19
- package/src/components/auth/setup-wizard.tsx +75 -2
- package/src/components/gateways/gateway-sheet.tsx +140 -8
- package/src/components/openclaw/openclaw-deploy-panel.tsx +591 -9
- package/src/components/providers/provider-list.tsx +221 -17
- package/src/lib/server/agent-runtime-config.ts +142 -7
- package/src/lib/server/agent-thread-session.ts +9 -1
- package/src/lib/server/chat-execution.ts +8 -2
- package/src/lib/server/openclaw-deploy.test.ts +8 -0
- package/src/lib/server/openclaw-deploy.ts +679 -19
- package/src/lib/server/orchestrator.ts +9 -0
- package/src/lib/server/queue.ts +45 -2
- package/src/lib/setup-defaults.ts +2 -2
- package/src/lib/validation/schemas.ts +9 -0
- package/src/types/index.ts +65 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
3
|
+
import { useEffect, useRef, useState } from 'react'
|
|
4
4
|
import { BottomSheet } from '@/components/shared/bottom-sheet'
|
|
5
5
|
import { OpenClawDeployPanel } from '@/components/openclaw/openclaw-deploy-panel'
|
|
6
6
|
import { useAppStore } from '@/stores/use-app-store'
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
OpenClawNode,
|
|
13
13
|
OpenClawNodePairRequest,
|
|
14
14
|
OpenClawPairedDevice,
|
|
15
|
+
GatewayProfile,
|
|
15
16
|
} from '@/types'
|
|
16
17
|
|
|
17
18
|
interface DiscoveryResult {
|
|
@@ -37,6 +38,17 @@ interface PairingListResult<T> {
|
|
|
37
38
|
paired?: OpenClawPairedDevice[]
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
interface GatewayImportShape {
|
|
42
|
+
name?: string
|
|
43
|
+
endpoint?: string
|
|
44
|
+
credentialId?: string | null
|
|
45
|
+
token?: string | null
|
|
46
|
+
notes?: string | null
|
|
47
|
+
tags?: string[]
|
|
48
|
+
isDefault?: boolean
|
|
49
|
+
deployment?: GatewayProfile['deployment']
|
|
50
|
+
}
|
|
51
|
+
|
|
40
52
|
export function GatewaySheet() {
|
|
41
53
|
const open = useAppStore((s) => s.gatewaySheetOpen)
|
|
42
54
|
const setOpen = useAppStore((s) => s.setGatewaySheetOpen)
|
|
@@ -73,6 +85,8 @@ export function GatewaySheet() {
|
|
|
73
85
|
const [invokeParamsText, setInvokeParamsText] = useState('{}')
|
|
74
86
|
const [invokeResult, setInvokeResult] = useState('')
|
|
75
87
|
const [invoking, setInvoking] = useState(false)
|
|
88
|
+
const [deployment, setDeployment] = useState<GatewayProfile['deployment'] | null>(null)
|
|
89
|
+
const importFileRef = useRef<HTMLInputElement>(null)
|
|
76
90
|
|
|
77
91
|
useEffect(() => {
|
|
78
92
|
if (!open) return
|
|
@@ -93,6 +107,7 @@ export function GatewaySheet() {
|
|
|
93
107
|
setNotes(editing.notes || '')
|
|
94
108
|
setTags((editing.tags || []).join(', '))
|
|
95
109
|
setIsDefault(editing.isDefault === true)
|
|
110
|
+
setDeployment(editing.deployment || null)
|
|
96
111
|
return
|
|
97
112
|
}
|
|
98
113
|
setName('')
|
|
@@ -102,6 +117,7 @@ export function GatewaySheet() {
|
|
|
102
117
|
setNotes('')
|
|
103
118
|
setTags('')
|
|
104
119
|
setIsDefault(gatewayProfiles.length === 0)
|
|
120
|
+
setDeployment(null)
|
|
105
121
|
setNodes([])
|
|
106
122
|
setNodePairings([])
|
|
107
123
|
setDevicePairings([])
|
|
@@ -143,10 +159,18 @@ export function GatewaySheet() {
|
|
|
143
159
|
setNodePairings(nextNodePairings)
|
|
144
160
|
setDevicePairings(nextDevicePairings)
|
|
145
161
|
setPairedDevices(nextPairedDevices)
|
|
162
|
+
const nextStats: NonNullable<GatewayProfile['stats']> = {
|
|
163
|
+
nodeCount: nextNodes.length,
|
|
164
|
+
connectedNodeCount: nextNodes.filter((node) => node.connected).length,
|
|
165
|
+
pendingNodePairings: nextNodePairings.length,
|
|
166
|
+
pairedDeviceCount: nextPairedDevices.length,
|
|
167
|
+
pendingDevicePairings: nextDevicePairings.length,
|
|
168
|
+
}
|
|
146
169
|
if (nextNodes[0]) {
|
|
147
170
|
setInvokeNodeId((current) => current || nextNodes[0].nodeId)
|
|
148
171
|
setInvokeCommand((current) => current || nextNodes[0].commands?.[0] || '')
|
|
149
172
|
}
|
|
173
|
+
void api('PUT', `/gateways/${profileId}`, { stats: nextStats }).catch(() => {})
|
|
150
174
|
} catch (err: unknown) {
|
|
151
175
|
setNodesError(err instanceof Error ? err.message : 'Failed to load nodes for this gateway.')
|
|
152
176
|
} finally {
|
|
@@ -182,6 +206,7 @@ export function GatewaySheet() {
|
|
|
182
206
|
credentialId: nextCredentialId || null,
|
|
183
207
|
notes: notes.trim() || null,
|
|
184
208
|
tags: tags.split(',').map((item) => item.trim()).filter(Boolean),
|
|
209
|
+
deployment,
|
|
185
210
|
isDefault,
|
|
186
211
|
}
|
|
187
212
|
if (editing) {
|
|
@@ -298,7 +323,7 @@ export function GatewaySheet() {
|
|
|
298
323
|
|
|
299
324
|
const inputClass = 'w-full px-4 py-3.5 rounded-[14px] border border-white/[0.08] bg-surface text-text text-[15px] outline-none transition-all duration-200 placeholder:text-text-3/50 focus-glow'
|
|
300
325
|
|
|
301
|
-
const applyDeployPatch = (patch: { endpoint?: string; token?: string; name?: string; notes?: string }) => {
|
|
326
|
+
const applyDeployPatch = (patch: { endpoint?: string; token?: string; name?: string; notes?: string; deployment?: GatewayProfile['deployment'] | Record<string, unknown> | null }) => {
|
|
302
327
|
if (patch.endpoint) {
|
|
303
328
|
setEndpoint(patch.endpoint)
|
|
304
329
|
setCheckMessage('')
|
|
@@ -313,17 +338,97 @@ export function GatewaySheet() {
|
|
|
313
338
|
if (patch.notes && !notes.trim()) {
|
|
314
339
|
setNotes(patch.notes)
|
|
315
340
|
}
|
|
341
|
+
if (patch.deployment) {
|
|
342
|
+
setDeployment((current) => ({
|
|
343
|
+
...(current || {}),
|
|
344
|
+
...(patch.deployment as GatewayProfile['deployment']),
|
|
345
|
+
}))
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const handleExportGateway = () => {
|
|
350
|
+
const payload: GatewayImportShape = {
|
|
351
|
+
name: name.trim() || 'OpenClaw Gateway',
|
|
352
|
+
endpoint: endpoint.trim() || 'http://localhost:18789',
|
|
353
|
+
credentialId: credentialId || null,
|
|
354
|
+
token: tokenDraft.trim() || null,
|
|
355
|
+
notes: notes.trim() || null,
|
|
356
|
+
tags: tags.split(',').map((item) => item.trim()).filter(Boolean),
|
|
357
|
+
isDefault,
|
|
358
|
+
deployment,
|
|
359
|
+
}
|
|
360
|
+
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' })
|
|
361
|
+
const url = URL.createObjectURL(blob)
|
|
362
|
+
const link = document.createElement('a')
|
|
363
|
+
link.href = url
|
|
364
|
+
link.download = `${(payload.name || 'openclaw-gateway').replace(/[^a-zA-Z0-9_-]/g, '_')}.gateway.json`
|
|
365
|
+
link.click()
|
|
366
|
+
URL.revokeObjectURL(url)
|
|
367
|
+
toast.success('Gateway config exported')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const handleImportGateway = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
371
|
+
const file = event.target.files?.[0]
|
|
372
|
+
if (!file) return
|
|
373
|
+
const reader = new FileReader()
|
|
374
|
+
reader.onload = (loadEvent) => {
|
|
375
|
+
try {
|
|
376
|
+
const parsed = JSON.parse(String(loadEvent.target?.result || '{}')) as GatewayImportShape
|
|
377
|
+
setName(typeof parsed.name === 'string' ? parsed.name : '')
|
|
378
|
+
setEndpoint(typeof parsed.endpoint === 'string' ? parsed.endpoint : 'http://localhost:18789')
|
|
379
|
+
setCredentialId(typeof parsed.credentialId === 'string' ? parsed.credentialId : null)
|
|
380
|
+
setTokenDraft(typeof parsed.token === 'string' ? parsed.token : '')
|
|
381
|
+
setNotes(typeof parsed.notes === 'string' ? parsed.notes : '')
|
|
382
|
+
setTags(Array.isArray(parsed.tags) ? parsed.tags.join(', ') : '')
|
|
383
|
+
setIsDefault(parsed.isDefault === true)
|
|
384
|
+
setDeployment(parsed.deployment || null)
|
|
385
|
+
setCheckMessage('')
|
|
386
|
+
toast.success('Gateway config imported into this form')
|
|
387
|
+
} catch {
|
|
388
|
+
toast.error('Invalid gateway JSON')
|
|
389
|
+
} finally {
|
|
390
|
+
event.target.value = ''
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
reader.readAsText(file)
|
|
316
394
|
}
|
|
317
395
|
|
|
318
396
|
return (
|
|
319
397
|
<BottomSheet open={open} onClose={onClose} wide>
|
|
398
|
+
<input
|
|
399
|
+
ref={importFileRef}
|
|
400
|
+
type="file"
|
|
401
|
+
accept="application/json,.json"
|
|
402
|
+
onChange={handleImportGateway}
|
|
403
|
+
className="hidden"
|
|
404
|
+
/>
|
|
320
405
|
<div className="mb-10">
|
|
321
|
-
<
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
406
|
+
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
407
|
+
<div>
|
|
408
|
+
<h2 className="font-display text-[28px] font-700 tracking-[-0.03em] mb-2">
|
|
409
|
+
{editing ? 'Edit Gateway' : 'New Gateway'}
|
|
410
|
+
</h2>
|
|
411
|
+
<p className="text-[14px] text-text-3">
|
|
412
|
+
First-class OpenClaw gateway profiles for local or remote control planes.
|
|
413
|
+
</p>
|
|
414
|
+
</div>
|
|
415
|
+
<div className="flex flex-wrap gap-2">
|
|
416
|
+
<button
|
|
417
|
+
type="button"
|
|
418
|
+
onClick={() => importFileRef.current?.click()}
|
|
419
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[12px] font-600 hover:bg-white/[0.04] transition-all cursor-pointer"
|
|
420
|
+
>
|
|
421
|
+
Import JSON
|
|
422
|
+
</button>
|
|
423
|
+
<button
|
|
424
|
+
type="button"
|
|
425
|
+
onClick={handleExportGateway}
|
|
426
|
+
className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-2 text-[12px] font-600 hover:bg-white/[0.04] transition-all cursor-pointer"
|
|
427
|
+
>
|
|
428
|
+
Export JSON
|
|
429
|
+
</button>
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
327
432
|
</div>
|
|
328
433
|
|
|
329
434
|
<div className="mb-6">
|
|
@@ -350,6 +455,7 @@ export function GatewaySheet() {
|
|
|
350
455
|
<OpenClawDeployPanel
|
|
351
456
|
endpoint={endpoint}
|
|
352
457
|
token={tokenDraft}
|
|
458
|
+
deployment={deployment}
|
|
353
459
|
suggestedName={name || null}
|
|
354
460
|
title="Deploy OpenClaw From SwarmClaw"
|
|
355
461
|
description="Use official OpenClaw sources only. Start it on this host, or generate a pre-configured remote bundle for VPS and hosted deployments."
|
|
@@ -357,6 +463,32 @@ export function GatewaySheet() {
|
|
|
357
463
|
/>
|
|
358
464
|
</div>
|
|
359
465
|
|
|
466
|
+
{deployment && (
|
|
467
|
+
<div className="mb-6 rounded-[16px] border border-white/[0.06] bg-white/[0.02] p-4">
|
|
468
|
+
<div className="text-[12px] font-700 uppercase tracking-[0.08em] text-text-3/70 mb-2">Deploy metadata</div>
|
|
469
|
+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 text-[12px] text-text-3/75">
|
|
470
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface px-3 py-3">
|
|
471
|
+
<div className="uppercase tracking-[0.08em] text-text-3/55">Method</div>
|
|
472
|
+
<div className="mt-1 text-text-2">{deployment.method || 'manual'}</div>
|
|
473
|
+
</div>
|
|
474
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface px-3 py-3">
|
|
475
|
+
<div className="uppercase tracking-[0.08em] text-text-3/55">Use case</div>
|
|
476
|
+
<div className="mt-1 text-text-2">{deployment.useCase || 'general'}</div>
|
|
477
|
+
</div>
|
|
478
|
+
<div className="rounded-[12px] border border-white/[0.06] bg-surface px-3 py-3">
|
|
479
|
+
<div className="uppercase tracking-[0.08em] text-text-3/55">Exposure</div>
|
|
480
|
+
<div className="mt-1 text-text-2">{deployment.exposure || 'manual'}</div>
|
|
481
|
+
</div>
|
|
482
|
+
</div>
|
|
483
|
+
{deployment.lastDeploySummary && (
|
|
484
|
+
<p className="mt-3 text-[12px] text-text-3 leading-relaxed">{deployment.lastDeploySummary}</p>
|
|
485
|
+
)}
|
|
486
|
+
{deployment.lastVerifiedMessage && (
|
|
487
|
+
<p className="mt-2 text-[12px] text-text-3 leading-relaxed">{deployment.lastVerifiedMessage}</p>
|
|
488
|
+
)}
|
|
489
|
+
</div>
|
|
490
|
+
)}
|
|
491
|
+
|
|
360
492
|
{discoveries.length > 0 && (
|
|
361
493
|
<div className="mb-6">
|
|
362
494
|
<div className="text-[12px] text-text-3/70 mb-2">Detected healthy gateways</div>
|