@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.
- package/README.md +12 -14
- package/next.config.ts +13 -2
- package/package.json +4 -2
- package/src/app/api/agents/[id]/thread/route.ts +9 -0
- package/src/app/api/agents/route.ts +4 -0
- package/src/app/api/agents/thread-route.test.ts +133 -0
- package/src/app/api/approvals/route.test.ts +148 -0
- package/src/app/api/canvas/[sessionId]/route.ts +3 -1
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
- package/src/app/api/chats/[id]/devserver/route.ts +48 -7
- package/src/app/api/chats/[id]/messages/route.ts +42 -18
- package/src/app/api/chats/[id]/route.ts +1 -1
- package/src/app/api/chats/[id]/stop/route.ts +5 -4
- package/src/app/api/chats/route.ts +23 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +46 -3
- package/src/app/api/connectors/route.ts +12 -8
- package/src/app/api/external-agents/route.test.ts +165 -0
- package/src/app/api/gateways/[id]/health/route.ts +27 -12
- package/src/app/api/gateways/[id]/route.ts +2 -0
- package/src/app/api/gateways/health-route.test.ts +135 -0
- package/src/app/api/gateways/route.ts +2 -0
- package/src/app/api/mcp-servers/route.test.ts +130 -0
- package/src/app/api/openclaw/deploy/route.ts +38 -5
- package/src/app/api/plugins/install/route.ts +46 -6
- package/src/app/api/plugins/marketplace/route.ts +48 -15
- package/src/app/api/preview-server/route.ts +26 -11
- package/src/app/api/projects/[id]/route.ts +6 -2
- package/src/app/api/projects/route.ts +4 -3
- package/src/app/api/schedules/[id]/run/route.ts +4 -0
- package/src/app/api/schedules/route.test.ts +86 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/[id]/route.ts +1 -0
- package/src/app/api/secrets/route.ts +2 -1
- package/src/app/api/settings/route.ts +2 -0
- package/src/app/api/setup/check-provider/route.test.ts +19 -0
- package/src/app/api/setup/check-provider/route.ts +40 -10
- package/src/app/api/skills/[id]/route.ts +12 -0
- package/src/app/api/skills/import/route.ts +14 -12
- package/src/app/api/skills/route.ts +13 -1
- package/src/app/api/tasks/[id]/route.ts +10 -1
- package/src/app/api/tasks/import/github/route.test.ts +65 -0
- package/src/app/api/tasks/import/github/route.ts +337 -0
- package/src/app/api/wallets/[id]/approve/route.ts +17 -3
- package/src/app/api/wallets/[id]/route.ts +79 -33
- package/src/app/api/wallets/[id]/send/route.ts +19 -33
- package/src/app/api/wallets/route.ts +78 -61
- package/src/app/api/webhooks/[id]/route.ts +33 -6
- package/src/app/api/webhooks/route.test.ts +272 -0
- package/src/cli/index.js +1 -0
- package/src/cli/spec.js +1 -0
- package/src/components/agents/agent-card.tsx +9 -2
- package/src/components/agents/agent-chat-list.tsx +18 -2
- package/src/components/agents/agent-list.tsx +1 -0
- package/src/components/agents/agent-sheet.tsx +257 -38
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-area.tsx +36 -19
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +48 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- package/src/components/chat/delegation-banner.test.ts +14 -1
- package/src/components/chat/delegation-banner.tsx +1 -1
- package/src/components/chat/message-bubble.tsx +208 -145
- package/src/components/chat/message-list.tsx +48 -19
- package/src/components/chatrooms/chatroom-message.tsx +2 -2
- package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
- package/src/components/connectors/connector-health.tsx +1 -1
- package/src/components/connectors/connector-list.tsx +7 -2
- package/src/components/connectors/connector-sheet.tsx +337 -148
- package/src/components/gateways/gateway-sheet.tsx +2 -2
- package/src/components/layout/app-layout.tsx +40 -23
- package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
- package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
- package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
- package/src/components/plugins/plugin-list.tsx +45 -9
- package/src/components/plugins/plugin-sheet.tsx +55 -7
- package/src/components/projects/project-detail.tsx +217 -0
- package/src/components/projects/project-sheet.tsx +176 -4
- package/src/components/providers/provider-list.tsx +2 -1
- package/src/components/providers/provider-sheet.tsx +21 -2
- package/src/components/schedules/schedule-card.tsx +25 -1
- package/src/components/schedules/schedule-sheet.tsx +44 -2
- package/src/components/secrets/secret-sheet.tsx +21 -2
- package/src/components/shared/agent-switch-dialog.tsx +12 -1
- package/src/components/shared/bottom-sheet.tsx +13 -3
- package/src/components/shared/command-palette.tsx +8 -1
- package/src/components/shared/confirm-dialog.tsx +19 -4
- package/src/components/shared/connector-platform-icon.test.ts +28 -0
- package/src/components/shared/connector-platform-icon.tsx +39 -6
- package/src/components/shared/settings/plugin-manager.tsx +29 -6
- package/src/components/shared/settings/section-capability-policy.tsx +45 -3
- package/src/components/shared/settings/section-voice.tsx +11 -3
- package/src/components/skills/skill-list.tsx +25 -0
- package/src/components/skills/skill-sheet.tsx +84 -12
- package/src/components/tasks/approvals-panel.tsx +289 -34
- package/src/components/tasks/task-board.tsx +410 -25
- package/src/components/tasks/task-card.tsx +66 -8
- package/src/components/tasks/task-sheet.tsx +16 -4
- package/src/components/ui/dialog.tsx +2 -2
- package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
- package/src/components/wallets/wallet-panel.tsx +435 -90
- package/src/components/wallets/wallet-section.tsx +198 -48
- package/src/components/webhooks/webhook-sheet.tsx +22 -2
- package/src/lib/approval-display.ts +20 -0
- package/src/lib/canvas-content.ts +198 -0
- package/src/lib/chat-artifact-summary.ts +165 -0
- package/src/lib/chat-display.test.ts +91 -0
- package/src/lib/chat-display.ts +58 -0
- package/src/lib/chat-streaming-state.test.ts +47 -1
- package/src/lib/chat-streaming-state.ts +42 -0
- package/src/lib/ollama-model.ts +10 -0
- package/src/lib/openclaw-endpoint.test.ts +8 -0
- package/src/lib/openclaw-endpoint.ts +6 -1
- package/src/lib/plugin-install-cors.ts +46 -0
- package/src/lib/plugin-sources.test.ts +43 -0
- package/src/lib/plugin-sources.ts +77 -0
- package/src/lib/providers/ollama.ts +16 -6
- package/src/lib/providers/openclaw.test.ts +54 -0
- package/src/lib/providers/openclaw.ts +127 -11
- package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
- package/src/lib/schedule-dedupe.test.ts +66 -1
- package/src/lib/schedule-dedupe.ts +169 -12
- package/src/lib/schedule-origin.test.ts +20 -0
- package/src/lib/schedule-origin.ts +15 -0
- package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
- package/src/lib/server/agent-availability.ts +16 -0
- package/src/lib/server/agent-runtime-config.ts +12 -4
- package/src/lib/server/agent-thread-session.test.ts +51 -0
- package/src/lib/server/agent-thread-session.ts +7 -0
- package/src/lib/server/approval-match.ts +205 -0
- package/src/lib/server/approvals-auto-approve.test.ts +538 -1
- package/src/lib/server/approvals.ts +214 -1
- package/src/lib/server/assistant-control.test.ts +29 -0
- package/src/lib/server/assistant-control.ts +23 -0
- package/src/lib/server/build-llm.test.ts +79 -0
- package/src/lib/server/build-llm.ts +14 -4
- package/src/lib/server/canvas-content.test.ts +32 -0
- package/src/lib/server/canvas-content.ts +6 -0
- package/src/lib/server/capability-router.test.ts +33 -0
- package/src/lib/server/capability-router.ts +80 -19
- package/src/lib/server/chat-execution-advanced.test.ts +651 -0
- package/src/lib/server/chat-execution-disabled.test.ts +94 -0
- package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
- package/src/lib/server/chat-execution.ts +378 -73
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +461 -137
- package/src/lib/server/connectors/pairing.ts +26 -5
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.test.ts +134 -0
- package/src/lib/server/connectors/whatsapp.ts +271 -47
- package/src/lib/server/context-manager.ts +6 -1
- package/src/lib/server/daemon-state.ts +84 -47
- package/src/lib/server/data-dir.test.ts +37 -0
- package/src/lib/server/data-dir.ts +20 -1
- package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
- package/src/lib/server/devserver-launch.test.ts +60 -0
- package/src/lib/server/devserver-launch.ts +85 -0
- package/src/lib/server/elevenlabs.test.ts +247 -1
- package/src/lib/server/elevenlabs.ts +147 -43
- package/src/lib/server/ethereum.ts +590 -0
- package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
- package/src/lib/server/eval/agent-regression.test.ts +18 -1
- package/src/lib/server/eval/agent-regression.ts +383 -11
- package/src/lib/server/evm-swap.ts +475 -0
- package/src/lib/server/execution-log.ts +1 -0
- package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
- package/src/lib/server/heartbeat-service.ts +20 -11
- package/src/lib/server/heartbeat-wake.test.ts +112 -0
- package/src/lib/server/heartbeat-wake.ts +338 -57
- package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
- package/src/lib/server/main-agent-loop.test.ts +260 -0
- package/src/lib/server/main-agent-loop.ts +559 -14
- package/src/lib/server/mcp-client.test.ts +16 -0
- package/src/lib/server/mcp-client.ts +25 -0
- package/src/lib/server/memory-integration.test.ts +719 -0
- package/src/lib/server/memory-policy.test.ts +43 -0
- package/src/lib/server/memory-policy.ts +132 -0
- package/src/lib/server/memory-tiers.test.ts +60 -0
- package/src/lib/server/memory-tiers.ts +16 -0
- package/src/lib/server/ollama-runtime.ts +58 -0
- package/src/lib/server/openclaw-deploy.test.ts +109 -1
- package/src/lib/server/openclaw-deploy.ts +557 -81
- package/src/lib/server/openclaw-gateway.test.ts +131 -0
- package/src/lib/server/openclaw-gateway.ts +10 -4
- package/src/lib/server/openclaw-health.test.ts +35 -0
- package/src/lib/server/openclaw-health.ts +215 -47
- package/src/lib/server/orchestrator-lg.ts +3 -2
- package/src/lib/server/orchestrator.ts +2 -0
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +211 -6
- package/src/lib/server/project-context.ts +162 -0
- package/src/lib/server/project-utils.ts +150 -0
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +409 -2
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +527 -68
- package/src/lib/server/scheduler.ts +29 -1
- package/src/lib/server/session-note.test.ts +36 -0
- package/src/lib/server/session-note.ts +42 -0
- package/src/lib/server/session-run-manager.ts +83 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +366 -54
- package/src/lib/server/session-tools/context.ts +17 -3
- package/src/lib/server/session-tools/crud.ts +484 -84
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
- package/src/lib/server/session-tools/delegate.ts +102 -10
- package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
- package/src/lib/server/session-tools/discovery.ts +80 -12
- package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
- package/src/lib/server/session-tools/file.ts +43 -4
- package/src/lib/server/session-tools/human-loop.ts +35 -5
- package/src/lib/server/session-tools/index.ts +44 -9
- package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
- package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
- package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
- package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
- package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
- package/src/lib/server/session-tools/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +554 -75
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- package/src/lib/server/session-tools/platform-access.test.ts +58 -0
- package/src/lib/server/session-tools/platform.ts +60 -19
- package/src/lib/server/session-tools/plugin-creator.ts +57 -1
- package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
- package/src/lib/server/session-tools/schedule.ts +6 -1
- package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
- package/src/lib/server/session-tools/shell.ts +22 -3
- package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
- package/src/lib/server/session-tools/wallet.ts +1374 -139
- package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
- package/src/lib/server/session-tools/web.ts +621 -70
- package/src/lib/server/skill-discovery.ts +128 -0
- package/src/lib/server/skill-eligibility.test.ts +84 -0
- package/src/lib/server/skill-eligibility.ts +95 -0
- package/src/lib/server/skill-prompt-budget.test.ts +102 -0
- package/src/lib/server/skill-prompt-budget.ts +125 -0
- package/src/lib/server/skills-normalize.test.ts +54 -0
- package/src/lib/server/skills-normalize.ts +372 -26
- package/src/lib/server/solana.ts +214 -29
- package/src/lib/server/storage.ts +65 -36
- package/src/lib/server/stream-agent-chat.test.ts +437 -2
- package/src/lib/server/stream-agent-chat.ts +957 -79
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-aliases.ts +2 -0
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- package/src/lib/server/tool-capability-policy.test.ts +24 -0
- package/src/lib/server/tool-capability-policy.ts +29 -1
- package/src/lib/server/tool-loop-detection.test.ts +105 -0
- package/src/lib/server/tool-loop-detection.ts +260 -0
- package/src/lib/server/tool-planning.test.ts +44 -0
- package/src/lib/server/tool-planning.ts +271 -0
- package/src/lib/server/wallet-execution.test.ts +198 -0
- package/src/lib/server/wallet-portfolio.test.ts +98 -0
- package/src/lib/server/wallet-portfolio.ts +724 -0
- package/src/lib/server/wallet-service.test.ts +57 -0
- package/src/lib/server/wallet-service.ts +213 -0
- package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
- package/src/lib/server/watch-jobs.ts +17 -2
- package/src/lib/server/workspace-context.ts +111 -0
- package/src/lib/skill-save-payload.test.ts +39 -0
- package/src/lib/skill-save-payload.ts +37 -0
- package/src/lib/tasks.ts +28 -0
- package/src/lib/tool-definitions.ts +2 -1
- package/src/lib/tool-event-summary.test.ts +30 -0
- package/src/lib/tool-event-summary.ts +37 -0
- package/src/lib/validation/schemas.ts +1 -0
- package/src/lib/wallet-transactions.test.ts +75 -0
- package/src/lib/wallet-transactions.ts +43 -0
- package/src/lib/wallet.test.ts +17 -0
- package/src/lib/wallet.ts +183 -0
- package/src/proxy.test.ts +31 -0
- package/src/proxy.ts +34 -2
- package/src/stores/use-chat-store.ts +15 -1
- 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
|
-
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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 ${
|
|
614
|
-
notes: `Official OpenClaw deployed over SSH to ${
|
|
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 ${
|
|
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
|
-
|
|
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 | '
|
|
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 !== '
|
|
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', {
|
|
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 === '
|
|
185
|
-
|
|
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('
|
|
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
|
|
228
|
+
Browse Marketplace
|
|
222
229
|
</button>
|
|
223
230
|
) : undefined}
|
|
224
231
|
/>
|
|
225
232
|
)}
|
|
226
233
|
|
|
227
|
-
{tab === '
|
|
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
|
-
|
|
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
|
+
}
|