@swarmclawai/swarmclaw 0.7.8 → 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 -15
- 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 +22 -2
- package/src/app/api/clawhub/install/route.ts +28 -8
- package/src/app/api/connectors/[id]/route.ts +26 -1
- 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/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/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 +73 -24
- package/src/components/agents/inspector-panel.tsx +41 -0
- package/src/components/canvas/canvas-panel.tsx +236 -65
- package/src/components/chat/chat-card.tsx +36 -13
- package/src/components/chat/chat-header.tsx +44 -16
- package/src/components/chat/chat-list.tsx +28 -4
- package/src/components/chat/checkpoint-timeline.tsx +50 -34
- 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/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/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 +7 -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 +191 -95
- package/src/components/tasks/task-board.tsx +273 -2
- package/src/components/tasks/task-card.tsx +38 -9
- 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 +11 -0
- package/src/lib/server/capability-router.ts +26 -1
- 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 +353 -72
- package/src/lib/server/clawhub-client.test.ts +14 -8
- package/src/lib/server/connectors/manager.test.ts +1147 -0
- package/src/lib/server/connectors/manager.ts +362 -63
- 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 +1 -1
- 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 +189 -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 +15 -10
- 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/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 +2 -2
- package/src/lib/server/plugins-advanced.test.ts +351 -0
- package/src/lib/server/plugins.ts +205 -5
- package/src/lib/server/queue-advanced.test.ts +528 -0
- package/src/lib/server/queue-followups.test.ts +262 -0
- package/src/lib/server/queue-reconcile.test.ts +128 -0
- package/src/lib/server/queue.ts +293 -61
- 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 +52 -4
- package/src/lib/server/session-tools/canvas.ts +14 -12
- package/src/lib/server/session-tools/connector.test.ts +138 -0
- package/src/lib/server/session-tools/connector.ts +348 -61
- package/src/lib/server/session-tools/context.ts +12 -3
- package/src/lib/server/session-tools/crud.ts +221 -10
- package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
- package/src/lib/server/session-tools/delegate.ts +64 -8
- 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/memory.test.ts +93 -0
- package/src/lib/server/session-tools/memory.ts +546 -79
- package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
- 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 +162 -1
- package/src/lib/server/session-tools/web.ts +468 -64
- 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 +419 -9
- package/src/lib/server/stream-agent-chat.ts +887 -83
- package/src/lib/server/system-events.ts +1 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
- 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.ts +4 -2
- 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-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 +210 -14
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from './process-manager'
|
|
13
13
|
import { normalizeOpenClawEndpoint, deriveOpenClawWsUrl } from '@/lib/openclaw-endpoint'
|
|
14
14
|
import { probeOpenClawHealth, type OpenClawHealthResult } from './openclaw-health'
|
|
15
|
+
import { DATA_DIR } from './data-dir'
|
|
15
16
|
|
|
16
17
|
export type OpenClawRemoteDeployTemplate = 'docker' | 'render' | 'fly' | 'railway'
|
|
17
18
|
export type OpenClawRemoteDeployProvider =
|
|
@@ -28,6 +29,9 @@ export type OpenClawUseCaseTemplate = 'local-dev' | 'single-vps' | 'private-tail
|
|
|
28
29
|
export type OpenClawExposurePreset = 'private-lan' | 'tailscale' | 'caddy' | 'nginx' | 'ssh-tunnel'
|
|
29
30
|
|
|
30
31
|
export interface OpenClawLocalDeployStatus {
|
|
32
|
+
id: string
|
|
33
|
+
name: string
|
|
34
|
+
isPrimary: boolean
|
|
31
35
|
running: boolean
|
|
32
36
|
processId: string | null
|
|
33
37
|
pid: number | null
|
|
@@ -35,20 +39,35 @@ export interface OpenClawLocalDeployStatus {
|
|
|
35
39
|
endpoint: string
|
|
36
40
|
wsUrl: string
|
|
37
41
|
token: string | null
|
|
42
|
+
stateDir: string
|
|
43
|
+
configPath: string
|
|
44
|
+
workspaceDir: string
|
|
38
45
|
startedAt: number | null
|
|
46
|
+
createdAt: number
|
|
47
|
+
updatedAt: number
|
|
39
48
|
tail: string
|
|
40
49
|
lastError: string | null
|
|
41
50
|
launchCommand: string
|
|
42
51
|
installCommand: string
|
|
43
52
|
}
|
|
44
53
|
|
|
54
|
+
export interface OpenClawLocalDeployCollectionStatus {
|
|
55
|
+
primaryId: string | null
|
|
56
|
+
items: OpenClawLocalDeployStatus[]
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
export interface OpenClawRemoteDeployStatus {
|
|
60
|
+
id: string
|
|
61
|
+
name: string
|
|
62
|
+
isPrimary: boolean
|
|
46
63
|
active: boolean
|
|
47
64
|
processId: string | null
|
|
48
65
|
pid: number | null
|
|
49
66
|
action: string | null
|
|
50
67
|
target: string | null
|
|
51
68
|
startedAt: number | null
|
|
69
|
+
createdAt: number
|
|
70
|
+
updatedAt: number
|
|
52
71
|
status: ProcessStatus | 'idle'
|
|
53
72
|
exitCode: number | null
|
|
54
73
|
tail: string
|
|
@@ -58,6 +77,11 @@ export interface OpenClawRemoteDeployStatus {
|
|
|
58
77
|
lastBackupPath: string | null
|
|
59
78
|
}
|
|
60
79
|
|
|
80
|
+
export interface OpenClawRemoteDeployCollectionStatus {
|
|
81
|
+
primaryId: string | null
|
|
82
|
+
items: OpenClawRemoteDeployStatus[]
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
export interface OpenClawDeployBundleFile {
|
|
62
86
|
name: string
|
|
63
87
|
language: 'bash' | 'yaml' | 'env' | 'toml' | 'text'
|
|
@@ -90,6 +114,7 @@ export interface OpenClawSshConfig {
|
|
|
90
114
|
export interface OpenClawRemoteCommandResult {
|
|
91
115
|
ok: boolean
|
|
92
116
|
started: boolean
|
|
117
|
+
remoteId?: string | null
|
|
93
118
|
processId?: string | null
|
|
94
119
|
summary: string
|
|
95
120
|
commandPreview: string
|
|
@@ -98,20 +123,35 @@ export interface OpenClawRemoteCommandResult {
|
|
|
98
123
|
}
|
|
99
124
|
|
|
100
125
|
interface LocalRuntimeState {
|
|
126
|
+
id: string
|
|
127
|
+
name: string | null
|
|
101
128
|
processId: string | null
|
|
102
129
|
port: number
|
|
103
130
|
endpoint: string
|
|
104
131
|
wsUrl: string
|
|
105
132
|
token: string | null
|
|
106
133
|
startedAt: number | null
|
|
134
|
+
createdAt: number
|
|
135
|
+
updatedAt: number
|
|
107
136
|
lastError: string | null
|
|
108
137
|
}
|
|
109
138
|
|
|
139
|
+
interface LocalRuntimePaths {
|
|
140
|
+
rootDir: string
|
|
141
|
+
stateDir: string
|
|
142
|
+
configPath: string
|
|
143
|
+
workspaceDir: string
|
|
144
|
+
}
|
|
145
|
+
|
|
110
146
|
interface RemoteRuntimeState {
|
|
147
|
+
id: string
|
|
148
|
+
name: string | null
|
|
111
149
|
processId: string | null
|
|
112
150
|
action: string | null
|
|
113
151
|
target: string | null
|
|
114
152
|
startedAt: number | null
|
|
153
|
+
createdAt: number
|
|
154
|
+
updatedAt: number
|
|
115
155
|
lastError: string | null
|
|
116
156
|
lastSummary: string | null
|
|
117
157
|
lastCommandPreview: string | null
|
|
@@ -119,8 +159,10 @@ interface RemoteRuntimeState {
|
|
|
119
159
|
}
|
|
120
160
|
|
|
121
161
|
interface DeployRuntimeState {
|
|
122
|
-
|
|
123
|
-
|
|
162
|
+
locals: Record<string, LocalRuntimeState>
|
|
163
|
+
primaryLocalId: string | null
|
|
164
|
+
remotes: Record<string, RemoteRuntimeState>
|
|
165
|
+
primaryRemoteId: string | null
|
|
124
166
|
}
|
|
125
167
|
|
|
126
168
|
interface RemoteProviderMeta {
|
|
@@ -293,33 +335,220 @@ const EXPOSURE_META: Record<OpenClawExposurePreset, ExposureMeta> = {
|
|
|
293
335
|
},
|
|
294
336
|
}
|
|
295
337
|
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
338
|
+
function createLocalRuntimeState(
|
|
339
|
+
id: string,
|
|
340
|
+
patch?: Partial<LocalRuntimeState>,
|
|
341
|
+
): LocalRuntimeState {
|
|
342
|
+
const port = typeof patch?.port === 'number' && Number.isFinite(patch.port)
|
|
343
|
+
? patch.port
|
|
344
|
+
: DEFAULT_LOCAL_PORT
|
|
345
|
+
const endpoint = normalizeOpenClawEndpoint(
|
|
346
|
+
typeof patch?.endpoint === 'string' && patch.endpoint.trim()
|
|
347
|
+
? patch.endpoint
|
|
348
|
+
: `http://127.0.0.1:${port}`,
|
|
349
|
+
)
|
|
350
|
+
return {
|
|
351
|
+
id,
|
|
352
|
+
name: typeof patch?.name === 'string' && patch.name.trim() ? patch.name.trim() : `Local OpenClaw ${port}`,
|
|
353
|
+
processId: typeof patch?.processId === 'string' && patch.processId.trim() ? patch.processId.trim() : null,
|
|
354
|
+
port,
|
|
355
|
+
endpoint,
|
|
356
|
+
wsUrl: typeof patch?.wsUrl === 'string' && patch.wsUrl.trim() ? patch.wsUrl.trim() : deriveOpenClawWsUrl(endpoint),
|
|
357
|
+
token: typeof patch?.token === 'string' && patch.token.trim() ? patch.token.trim() : null,
|
|
358
|
+
startedAt: typeof patch?.startedAt === 'number' ? patch.startedAt : null,
|
|
359
|
+
createdAt: typeof patch?.createdAt === 'number' ? patch.createdAt : Date.now(),
|
|
360
|
+
updatedAt: typeof patch?.updatedAt === 'number' ? patch.updatedAt : Date.now(),
|
|
361
|
+
lastError: typeof patch?.lastError === 'string' && patch.lastError.trim() ? patch.lastError : null,
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function createRemoteRuntimeState(
|
|
366
|
+
id: string,
|
|
367
|
+
patch?: Partial<RemoteRuntimeState>,
|
|
368
|
+
): RemoteRuntimeState {
|
|
369
|
+
const target = typeof patch?.target === 'string' && patch.target.trim() ? patch.target.trim() : null
|
|
370
|
+
return {
|
|
371
|
+
id,
|
|
372
|
+
name: typeof patch?.name === 'string' && patch.name.trim()
|
|
373
|
+
? patch.name.trim()
|
|
374
|
+
: deriveRemoteDeploymentName(target || ''),
|
|
375
|
+
processId: typeof patch?.processId === 'string' && patch.processId.trim() ? patch.processId.trim() : null,
|
|
376
|
+
action: typeof patch?.action === 'string' && patch.action.trim() ? patch.action.trim() : null,
|
|
377
|
+
target,
|
|
378
|
+
startedAt: typeof patch?.startedAt === 'number' ? patch.startedAt : null,
|
|
379
|
+
createdAt: typeof patch?.createdAt === 'number' ? patch.createdAt : Date.now(),
|
|
380
|
+
updatedAt: typeof patch?.updatedAt === 'number' ? patch.updatedAt : Date.now(),
|
|
381
|
+
lastError: typeof patch?.lastError === 'string' && patch.lastError.trim() ? patch.lastError : null,
|
|
382
|
+
lastSummary: typeof patch?.lastSummary === 'string' && patch.lastSummary.trim() ? patch.lastSummary : null,
|
|
383
|
+
lastCommandPreview: typeof patch?.lastCommandPreview === 'string' && patch.lastCommandPreview.trim()
|
|
384
|
+
? patch.lastCommandPreview
|
|
385
|
+
: null,
|
|
386
|
+
lastBackupPath: typeof patch?.lastBackupPath === 'string' && patch.lastBackupPath.trim()
|
|
387
|
+
? patch.lastBackupPath
|
|
388
|
+
: null,
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function resolveLocalRuntimePaths(id: string): LocalRuntimePaths {
|
|
393
|
+
const safeId = id.trim() || 'local-default'
|
|
394
|
+
const rootDir = path.join(DATA_DIR, 'openclaw-local', safeId)
|
|
395
|
+
return {
|
|
396
|
+
rootDir,
|
|
397
|
+
stateDir: rootDir,
|
|
398
|
+
configPath: path.join(rootDir, 'openclaw.json'),
|
|
399
|
+
workspaceDir: path.join(rootDir, 'workspace'),
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function buildManagedLocalConfig(paths: LocalRuntimePaths): Record<string, unknown> {
|
|
404
|
+
const config: Record<string, unknown> = {
|
|
405
|
+
gateway: {
|
|
406
|
+
mode: 'local',
|
|
407
|
+
bind: 'custom',
|
|
408
|
+
customBindHost: '127.0.0.1',
|
|
409
|
+
http: {
|
|
410
|
+
endpoints: {
|
|
411
|
+
chatCompletions: {
|
|
412
|
+
enabled: true,
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
306
416
|
},
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
startedAt: null,
|
|
312
|
-
lastError: null,
|
|
313
|
-
lastSummary: null,
|
|
314
|
-
lastCommandPreview: null,
|
|
315
|
-
lastBackupPath: null,
|
|
417
|
+
agents: {
|
|
418
|
+
defaults: {
|
|
419
|
+
workspace: paths.workspaceDir,
|
|
420
|
+
},
|
|
316
421
|
},
|
|
317
422
|
}
|
|
423
|
+
|
|
424
|
+
if (process.env.OLLAMA_API_KEY?.trim()) {
|
|
425
|
+
;(config.agents as Record<string, Record<string, unknown>>).defaults.model = {
|
|
426
|
+
primary: 'ollama/glm-5:cloud',
|
|
427
|
+
}
|
|
428
|
+
;(config.agents as Record<string, Record<string, unknown>>).defaults.models = {
|
|
429
|
+
'ollama/glm-5:cloud': {
|
|
430
|
+
alias: 'glm5cloud',
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return config
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
async function ensureManagedLocalRuntimeFiles(id: string): Promise<LocalRuntimePaths> {
|
|
439
|
+
const paths = resolveLocalRuntimePaths(id)
|
|
440
|
+
await fs.mkdir(paths.workspaceDir, { recursive: true })
|
|
441
|
+
const config = buildManagedLocalConfig(paths)
|
|
442
|
+
await fs.writeFile(paths.configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
443
|
+
return paths
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function buildLocalProcessEnv(id: string): Record<string, string> {
|
|
447
|
+
const paths = resolveLocalRuntimePaths(id)
|
|
448
|
+
return {
|
|
449
|
+
OPENCLAW_CONFIG_PATH: paths.configPath,
|
|
450
|
+
OPENCLAW_STATE_DIR: paths.stateDir,
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function normalizeLocalRuntimeState(raw: unknown, id: string): LocalRuntimeState | null {
|
|
455
|
+
if (!raw || typeof raw !== 'object') return null
|
|
456
|
+
return createLocalRuntimeState(id, raw as Partial<LocalRuntimeState>)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function normalizeRemoteRuntimeState(raw: unknown, id: string): RemoteRuntimeState | null {
|
|
460
|
+
if (!raw || typeof raw !== 'object') return null
|
|
461
|
+
return createRemoteRuntimeState(id, raw as Partial<RemoteRuntimeState>)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function sortLocalRuntimeStates(items: LocalRuntimeState[]): LocalRuntimeState[] {
|
|
465
|
+
return [...items].sort((left, right) => {
|
|
466
|
+
if (left.updatedAt !== right.updatedAt) return right.updatedAt - left.updatedAt
|
|
467
|
+
if (left.createdAt !== right.createdAt) return right.createdAt - left.createdAt
|
|
468
|
+
if (left.port !== right.port) return left.port - right.port
|
|
469
|
+
return left.id.localeCompare(right.id)
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function sortRemoteRuntimeStates(items: RemoteRuntimeState[]): RemoteRuntimeState[] {
|
|
474
|
+
return [...items].sort((left, right) => {
|
|
475
|
+
if (left.updatedAt !== right.updatedAt) return right.updatedAt - left.updatedAt
|
|
476
|
+
if (left.createdAt !== right.createdAt) return right.createdAt - left.createdAt
|
|
477
|
+
const leftTarget = left.target || ''
|
|
478
|
+
const rightTarget = right.target || ''
|
|
479
|
+
if (leftTarget !== rightTarget) return leftTarget.localeCompare(rightTarget)
|
|
480
|
+
return left.id.localeCompare(right.id)
|
|
481
|
+
})
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function defaultRuntimeState(): DeployRuntimeState {
|
|
485
|
+
return {
|
|
486
|
+
locals: {},
|
|
487
|
+
primaryLocalId: null,
|
|
488
|
+
remotes: {},
|
|
489
|
+
primaryRemoteId: null,
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function normalizeRuntimeState(raw: unknown): DeployRuntimeState {
|
|
494
|
+
const fallback = defaultRuntimeState()
|
|
495
|
+
if (!raw || typeof raw !== 'object') return fallback
|
|
496
|
+
const state = raw as Partial<DeployRuntimeState> & { local?: unknown; locals?: Record<string, unknown> }
|
|
497
|
+
|
|
498
|
+
const locals: Record<string, LocalRuntimeState> = {}
|
|
499
|
+
if (state.locals && typeof state.locals === 'object') {
|
|
500
|
+
for (const [id, value] of Object.entries(state.locals)) {
|
|
501
|
+
const normalized = normalizeLocalRuntimeState(value, id)
|
|
502
|
+
if (normalized) locals[id] = normalized
|
|
503
|
+
}
|
|
504
|
+
} else if (state.local && typeof state.local === 'object') {
|
|
505
|
+
const normalized = normalizeLocalRuntimeState(state.local, 'local-default')
|
|
506
|
+
if (normalized) locals[normalized.id] = normalized
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const orderedLocals = sortLocalRuntimeStates(Object.values(locals))
|
|
510
|
+
const primaryLocalId = typeof state.primaryLocalId === 'string' && locals[state.primaryLocalId]
|
|
511
|
+
? state.primaryLocalId
|
|
512
|
+
: orderedLocals[0]?.id || null
|
|
513
|
+
|
|
514
|
+
const remotes: Record<string, RemoteRuntimeState> = {}
|
|
515
|
+
const stateWithRemote = state as Partial<DeployRuntimeState> & {
|
|
516
|
+
local?: unknown
|
|
517
|
+
locals?: Record<string, unknown>
|
|
518
|
+
remote?: unknown
|
|
519
|
+
remotes?: Record<string, unknown>
|
|
520
|
+
primaryRemoteId?: unknown
|
|
521
|
+
}
|
|
522
|
+
if (stateWithRemote.remotes && typeof stateWithRemote.remotes === 'object') {
|
|
523
|
+
for (const [id, value] of Object.entries(stateWithRemote.remotes)) {
|
|
524
|
+
const normalized = normalizeRemoteRuntimeState(value, id)
|
|
525
|
+
if (normalized) remotes[id] = normalized
|
|
526
|
+
}
|
|
527
|
+
} else if (stateWithRemote.remote && typeof stateWithRemote.remote === 'object') {
|
|
528
|
+
const normalized = normalizeRemoteRuntimeState(stateWithRemote.remote, 'remote-default')
|
|
529
|
+
if (normalized) remotes[normalized.id] = normalized
|
|
530
|
+
}
|
|
531
|
+
const orderedRemotes = sortRemoteRuntimeStates(Object.values(remotes))
|
|
532
|
+
const primaryRemoteId = typeof stateWithRemote.primaryRemoteId === 'string' && remotes[stateWithRemote.primaryRemoteId]
|
|
533
|
+
? stateWithRemote.primaryRemoteId
|
|
534
|
+
: orderedRemotes[0]?.id || null
|
|
535
|
+
|
|
536
|
+
return {
|
|
537
|
+
locals,
|
|
538
|
+
primaryLocalId,
|
|
539
|
+
remotes,
|
|
540
|
+
primaryRemoteId,
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function getRuntimeState(): DeployRuntimeState {
|
|
318
545
|
const globalState = globalThis as typeof globalThis & { [GLOBAL_KEY]?: DeployRuntimeState }
|
|
319
546
|
if (!globalState[GLOBAL_KEY]) {
|
|
320
|
-
globalState[GLOBAL_KEY] =
|
|
547
|
+
globalState[GLOBAL_KEY] = defaultRuntimeState()
|
|
548
|
+
} else {
|
|
549
|
+
globalState[GLOBAL_KEY] = normalizeRuntimeState(globalState[GLOBAL_KEY])
|
|
321
550
|
}
|
|
322
|
-
return globalState[GLOBAL_KEY] ||
|
|
551
|
+
return globalState[GLOBAL_KEY] || defaultRuntimeState()
|
|
323
552
|
}
|
|
324
553
|
|
|
325
554
|
function shellEscape(value: string): string {
|
|
@@ -338,8 +567,11 @@ function resolveBundledOpenClawBinary(): string {
|
|
|
338
567
|
return 'openclaw'
|
|
339
568
|
}
|
|
340
569
|
|
|
341
|
-
function buildLocalRunCommand(port: number, token?: string | null): string {
|
|
570
|
+
function buildLocalRunCommand(port: number, token?: string | null, localId = 'local-default'): string {
|
|
571
|
+
const env = buildLocalProcessEnv(localId)
|
|
342
572
|
const parts = [
|
|
573
|
+
`OPENCLAW_CONFIG_PATH=${shellEscape(env.OPENCLAW_CONFIG_PATH)}`,
|
|
574
|
+
`OPENCLAW_STATE_DIR=${shellEscape(env.OPENCLAW_STATE_DIR)}`,
|
|
343
575
|
'npx',
|
|
344
576
|
'openclaw',
|
|
345
577
|
'gateway',
|
|
@@ -347,7 +579,7 @@ function buildLocalRunCommand(port: number, token?: string | null): string {
|
|
|
347
579
|
'--allow-unconfigured',
|
|
348
580
|
'--force',
|
|
349
581
|
'--bind',
|
|
350
|
-
'
|
|
582
|
+
'custom',
|
|
351
583
|
'--port',
|
|
352
584
|
String(port),
|
|
353
585
|
]
|
|
@@ -357,8 +589,11 @@ function buildLocalRunCommand(port: number, token?: string | null): string {
|
|
|
357
589
|
return parts.join(' ')
|
|
358
590
|
}
|
|
359
591
|
|
|
360
|
-
function buildLocalInstallCommand(port: number, token?: string | null): string {
|
|
592
|
+
function buildLocalInstallCommand(port: number, token?: string | null, localId = 'local-default'): string {
|
|
593
|
+
const env = buildLocalProcessEnv(localId)
|
|
361
594
|
const parts = [
|
|
595
|
+
`OPENCLAW_CONFIG_PATH=${shellEscape(env.OPENCLAW_CONFIG_PATH)}`,
|
|
596
|
+
`OPENCLAW_STATE_DIR=${shellEscape(env.OPENCLAW_STATE_DIR)}`,
|
|
362
597
|
'npx',
|
|
363
598
|
'openclaw',
|
|
364
599
|
'gateway',
|
|
@@ -468,17 +703,39 @@ async function materializeBundleFiles(bundle: OpenClawDeployBundle): Promise<{ d
|
|
|
468
703
|
return { dir, filePaths }
|
|
469
704
|
}
|
|
470
705
|
|
|
471
|
-
function updateRemoteRuntimeState(patch: Partial<RemoteRuntimeState>) {
|
|
472
|
-
Object.assign(getRuntimeState().remote, patch)
|
|
473
|
-
}
|
|
474
|
-
|
|
475
706
|
async function startRemoteCommand(params: {
|
|
707
|
+
remoteId?: string | null
|
|
708
|
+
name?: string | null
|
|
476
709
|
action: string
|
|
477
710
|
target: string
|
|
478
711
|
command: string
|
|
479
712
|
summary: string
|
|
480
713
|
backupPath?: string | null
|
|
714
|
+
makePrimary?: boolean
|
|
481
715
|
}): Promise<OpenClawRemoteCommandResult> {
|
|
716
|
+
const state = getRuntimeState()
|
|
717
|
+
const requestedRemoteId = typeof params.remoteId === 'string' && params.remoteId.trim()
|
|
718
|
+
? params.remoteId.trim()
|
|
719
|
+
: null
|
|
720
|
+
const existingById = requestedRemoteId ? state.remotes[requestedRemoteId] || null : null
|
|
721
|
+
const existingByTarget = Object.values(state.remotes).find((item) => (
|
|
722
|
+
normalizeRemoteTargetKey(item.target) === normalizeRemoteTargetKey(params.target)
|
|
723
|
+
)) || null
|
|
724
|
+
const remoteId = existingById?.id || existingByTarget?.id || requestedRemoteId || generateRemoteDeployId()
|
|
725
|
+
const existing = existingById || existingByTarget || null
|
|
726
|
+
const current = existing || createRemoteRuntimeState(remoteId, {
|
|
727
|
+
name: params.name || deriveRemoteDeploymentName(params.target),
|
|
728
|
+
target: params.target,
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
if (current.processId) {
|
|
732
|
+
const currentProcess = getManagedProcess(current.processId)
|
|
733
|
+
if (currentProcess?.status === 'running') {
|
|
734
|
+
killManagedProcess(current.processId)
|
|
735
|
+
}
|
|
736
|
+
removeManagedProcess(current.processId)
|
|
737
|
+
}
|
|
738
|
+
|
|
482
739
|
const result = await startManagedProcess({
|
|
483
740
|
command: params.command,
|
|
484
741
|
cwd: process.cwd(),
|
|
@@ -487,19 +744,24 @@ async function startRemoteCommand(params: {
|
|
|
487
744
|
})
|
|
488
745
|
|
|
489
746
|
if (result.status === 'completed' && (result.exitCode ?? 0) === 0) {
|
|
490
|
-
|
|
747
|
+
state.remotes[remoteId] = createRemoteRuntimeState(remoteId, {
|
|
748
|
+
...current,
|
|
749
|
+
name: typeof params.name === 'string' && params.name.trim() ? params.name.trim() : current.name,
|
|
491
750
|
processId: null,
|
|
492
751
|
action: params.action,
|
|
493
752
|
target: params.target,
|
|
494
|
-
startedAt:
|
|
753
|
+
startedAt: null,
|
|
754
|
+
updatedAt: Date.now(),
|
|
495
755
|
lastError: null,
|
|
496
756
|
lastSummary: params.summary,
|
|
497
757
|
lastCommandPreview: params.command,
|
|
498
758
|
lastBackupPath: params.backupPath || null,
|
|
499
759
|
})
|
|
760
|
+
if (params.makePrimary !== false || !state.primaryRemoteId) state.primaryRemoteId = remoteId
|
|
500
761
|
return {
|
|
501
762
|
ok: true,
|
|
502
763
|
started: false,
|
|
764
|
+
remoteId,
|
|
503
765
|
processId: null,
|
|
504
766
|
summary: params.summary,
|
|
505
767
|
commandPreview: params.command,
|
|
@@ -508,38 +770,48 @@ async function startRemoteCommand(params: {
|
|
|
508
770
|
|
|
509
771
|
if (result.status !== 'running') {
|
|
510
772
|
const message = result.output || result.tail || params.summary
|
|
511
|
-
|
|
773
|
+
state.remotes[remoteId] = createRemoteRuntimeState(remoteId, {
|
|
774
|
+
...current,
|
|
775
|
+
name: typeof params.name === 'string' && params.name.trim() ? params.name.trim() : current.name,
|
|
512
776
|
processId: null,
|
|
513
777
|
action: params.action,
|
|
514
778
|
target: params.target,
|
|
515
779
|
startedAt: null,
|
|
780
|
+
updatedAt: Date.now(),
|
|
516
781
|
lastError: message,
|
|
517
782
|
lastSummary: params.summary,
|
|
518
783
|
lastCommandPreview: params.command,
|
|
519
784
|
lastBackupPath: params.backupPath || null,
|
|
520
785
|
})
|
|
786
|
+
if (params.makePrimary !== false || !state.primaryRemoteId) state.primaryRemoteId = remoteId
|
|
521
787
|
return {
|
|
522
788
|
ok: false,
|
|
523
789
|
started: false,
|
|
790
|
+
remoteId,
|
|
524
791
|
processId: null,
|
|
525
792
|
summary: message,
|
|
526
793
|
commandPreview: params.command,
|
|
527
794
|
}
|
|
528
795
|
}
|
|
529
796
|
|
|
530
|
-
|
|
797
|
+
state.remotes[remoteId] = createRemoteRuntimeState(remoteId, {
|
|
798
|
+
...current,
|
|
799
|
+
name: typeof params.name === 'string' && params.name.trim() ? params.name.trim() : current.name,
|
|
531
800
|
processId: result.processId,
|
|
532
801
|
action: params.action,
|
|
533
802
|
target: params.target,
|
|
534
803
|
startedAt: Date.now(),
|
|
804
|
+
updatedAt: Date.now(),
|
|
535
805
|
lastError: null,
|
|
536
806
|
lastSummary: params.summary,
|
|
537
807
|
lastCommandPreview: params.command,
|
|
538
808
|
lastBackupPath: params.backupPath || null,
|
|
539
809
|
})
|
|
810
|
+
if (params.makePrimary !== false || !state.primaryRemoteId) state.primaryRemoteId = remoteId
|
|
540
811
|
return {
|
|
541
812
|
ok: true,
|
|
542
813
|
started: true,
|
|
814
|
+
remoteId,
|
|
543
815
|
processId: result.processId,
|
|
544
816
|
summary: params.summary,
|
|
545
817
|
commandPreview: params.command,
|
|
@@ -564,90 +836,245 @@ function readTail(text: string, size = 1200): string {
|
|
|
564
836
|
return text.length <= size ? text : text.slice(text.length - size)
|
|
565
837
|
}
|
|
566
838
|
|
|
567
|
-
function
|
|
839
|
+
function currentLocalStatusFromState(localState: LocalRuntimeState, isPrimary: boolean): OpenClawLocalDeployStatus {
|
|
568
840
|
const state = getRuntimeState()
|
|
569
|
-
const
|
|
841
|
+
const runtime = state.locals[localState.id] || localState
|
|
842
|
+
const paths = resolveLocalRuntimePaths(runtime.id)
|
|
843
|
+
const processId = runtime.processId
|
|
570
844
|
const process = processId ? getManagedProcess(processId) : null
|
|
571
845
|
const running = !!process && process.status === 'running'
|
|
572
846
|
|
|
573
847
|
if (!running && processId && process && process.status !== 'running') {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
848
|
+
runtime.lastError = readTail(process.log || '') || runtime.lastError
|
|
849
|
+
runtime.processId = null
|
|
850
|
+
runtime.startedAt = null
|
|
851
|
+
runtime.updatedAt = Date.now()
|
|
852
|
+
state.locals[runtime.id] = runtime
|
|
577
853
|
}
|
|
578
854
|
|
|
579
|
-
const endpoint = normalizeOpenClawEndpoint(`http://127.0.0.1:${
|
|
855
|
+
const endpoint = normalizeOpenClawEndpoint(`http://127.0.0.1:${runtime.port}`)
|
|
580
856
|
return {
|
|
857
|
+
id: runtime.id,
|
|
858
|
+
name: runtime.name || `Local OpenClaw ${runtime.port}`,
|
|
859
|
+
isPrimary,
|
|
581
860
|
running,
|
|
582
861
|
processId: running ? processId : null,
|
|
583
862
|
pid: running ? (process?.pid ?? null) : null,
|
|
584
|
-
port:
|
|
863
|
+
port: runtime.port,
|
|
585
864
|
endpoint,
|
|
586
865
|
wsUrl: deriveOpenClawWsUrl(endpoint),
|
|
587
|
-
token:
|
|
588
|
-
|
|
866
|
+
token: runtime.token || null,
|
|
867
|
+
stateDir: paths.stateDir,
|
|
868
|
+
configPath: paths.configPath,
|
|
869
|
+
workspaceDir: paths.workspaceDir,
|
|
870
|
+
startedAt: running ? runtime.startedAt : null,
|
|
871
|
+
createdAt: runtime.createdAt,
|
|
872
|
+
updatedAt: runtime.updatedAt,
|
|
589
873
|
tail: process ? readTail(process.log || '') : '',
|
|
590
|
-
lastError: running ? null : (
|
|
591
|
-
launchCommand: buildLocalRunCommand(
|
|
592
|
-
installCommand: buildLocalInstallCommand(
|
|
874
|
+
lastError: running ? null : (runtime.lastError || null),
|
|
875
|
+
launchCommand: buildLocalRunCommand(runtime.port, runtime.token, runtime.id),
|
|
876
|
+
installCommand: buildLocalInstallCommand(runtime.port, runtime.token, runtime.id),
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function getPrimaryLocalRuntimeState(state: DeployRuntimeState): LocalRuntimeState | null {
|
|
881
|
+
if (state.primaryLocalId && state.locals[state.primaryLocalId]) {
|
|
882
|
+
return state.locals[state.primaryLocalId]
|
|
593
883
|
}
|
|
884
|
+
const ordered = sortLocalRuntimeStates(Object.values(state.locals))
|
|
885
|
+
const next = ordered[0] || null
|
|
886
|
+
state.primaryLocalId = next?.id || null
|
|
887
|
+
return next
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function findLocalRuntimeState(
|
|
891
|
+
state: DeployRuntimeState,
|
|
892
|
+
input?: { localId?: string | null; port?: number | null },
|
|
893
|
+
): LocalRuntimeState | null {
|
|
894
|
+
const localId = typeof input?.localId === 'string' && input.localId.trim() ? input.localId.trim() : ''
|
|
895
|
+
if (localId && state.locals[localId]) return state.locals[localId]
|
|
896
|
+
const port = typeof input?.port === 'number' && Number.isFinite(input.port) ? input.port : null
|
|
897
|
+
if (port !== null) {
|
|
898
|
+
return Object.values(state.locals).find((item) => item.port === port) || null
|
|
899
|
+
}
|
|
900
|
+
return getPrimaryLocalRuntimeState(state)
|
|
594
901
|
}
|
|
595
902
|
|
|
596
|
-
|
|
597
|
-
|
|
903
|
+
function defaultLocalStatus(): OpenClawLocalDeployStatus {
|
|
904
|
+
const fallback = createLocalRuntimeState('local-default')
|
|
905
|
+
return currentLocalStatusFromState(fallback, true)
|
|
598
906
|
}
|
|
599
907
|
|
|
600
|
-
function
|
|
908
|
+
export function getOpenClawLocalDeployStatuses(): OpenClawLocalDeployStatus[] {
|
|
601
909
|
const state = getRuntimeState()
|
|
602
|
-
|
|
910
|
+
return sortLocalRuntimeStates(Object.values(state.locals))
|
|
911
|
+
.map((runtime) => currentLocalStatusFromState(runtime, runtime.id === state.primaryLocalId))
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
export function getOpenClawLocalDeployCollectionStatus(): OpenClawLocalDeployCollectionStatus {
|
|
915
|
+
const state = getRuntimeState()
|
|
916
|
+
const items = getOpenClawLocalDeployStatuses()
|
|
917
|
+
const primaryId = items.find((item) => item.isPrimary)?.id || null
|
|
918
|
+
state.primaryLocalId = primaryId
|
|
919
|
+
return { primaryId, items }
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
export function getOpenClawLocalDeployStatus(localId?: string | null): OpenClawLocalDeployStatus {
|
|
923
|
+
const state = getRuntimeState()
|
|
924
|
+
const runtime = findLocalRuntimeState(state, { localId })
|
|
925
|
+
return runtime
|
|
926
|
+
? currentLocalStatusFromState(runtime, runtime.id === state.primaryLocalId)
|
|
927
|
+
: defaultLocalStatus()
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
function currentRemoteStatusFromState(remoteState: RemoteRuntimeState, isPrimary: boolean): OpenClawRemoteDeployStatus {
|
|
931
|
+
const state = getRuntimeState()
|
|
932
|
+
const runtime = state.remotes[remoteState.id] || remoteState
|
|
933
|
+
const processId = runtime.processId
|
|
603
934
|
const process = processId ? getManagedProcess(processId) : null
|
|
604
935
|
const active = !!process && process.status === 'running'
|
|
605
936
|
|
|
606
937
|
if (!active && processId && process && process.status !== 'running') {
|
|
607
|
-
|
|
608
|
-
|
|
938
|
+
runtime.lastError = readTail(process.log || '') || runtime.lastError
|
|
939
|
+
runtime.processId = null
|
|
940
|
+
runtime.startedAt = null
|
|
941
|
+
runtime.updatedAt = Date.now()
|
|
942
|
+
state.remotes[runtime.id] = runtime
|
|
609
943
|
}
|
|
610
944
|
|
|
611
945
|
return {
|
|
946
|
+
id: runtime.id,
|
|
947
|
+
name: runtime.name || deriveRemoteDeploymentName(runtime.target || ''),
|
|
948
|
+
isPrimary,
|
|
612
949
|
active,
|
|
613
950
|
processId: active ? processId : null,
|
|
614
951
|
pid: active ? (process?.pid ?? null) : null,
|
|
615
|
-
action:
|
|
616
|
-
target:
|
|
617
|
-
startedAt:
|
|
952
|
+
action: runtime.action || null,
|
|
953
|
+
target: runtime.target || null,
|
|
954
|
+
startedAt: runtime.startedAt || null,
|
|
955
|
+
createdAt: runtime.createdAt,
|
|
956
|
+
updatedAt: runtime.updatedAt,
|
|
618
957
|
status: process?.status || 'idle',
|
|
619
958
|
exitCode: process?.exitCode ?? null,
|
|
620
959
|
tail: process ? readTail(process.log || '') : '',
|
|
621
|
-
lastError: active ? null : (
|
|
622
|
-
lastSummary:
|
|
623
|
-
lastCommandPreview:
|
|
624
|
-
lastBackupPath:
|
|
960
|
+
lastError: active ? null : (runtime.lastError || null),
|
|
961
|
+
lastSummary: runtime.lastSummary || null,
|
|
962
|
+
lastCommandPreview: runtime.lastCommandPreview || null,
|
|
963
|
+
lastBackupPath: runtime.lastBackupPath || null,
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function getPrimaryRemoteRuntimeState(state: DeployRuntimeState): RemoteRuntimeState | null {
|
|
968
|
+
if (state.primaryRemoteId && state.remotes[state.primaryRemoteId]) {
|
|
969
|
+
return state.remotes[state.primaryRemoteId]
|
|
970
|
+
}
|
|
971
|
+
const ordered = sortRemoteRuntimeStates(Object.values(state.remotes))
|
|
972
|
+
const next = ordered[0] || null
|
|
973
|
+
state.primaryRemoteId = next?.id || null
|
|
974
|
+
return next
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function normalizeRemoteTargetKey(value: unknown): string {
|
|
978
|
+
return typeof value === 'string' ? value.trim().toLowerCase() : ''
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function findRemoteRuntimeState(
|
|
982
|
+
state: DeployRuntimeState,
|
|
983
|
+
input?: { remoteId?: string | null; target?: string | null },
|
|
984
|
+
): RemoteRuntimeState | null {
|
|
985
|
+
const remoteId = typeof input?.remoteId === 'string' && input.remoteId.trim() ? input.remoteId.trim() : ''
|
|
986
|
+
if (remoteId && state.remotes[remoteId]) return state.remotes[remoteId]
|
|
987
|
+
const targetKey = normalizeRemoteTargetKey(input?.target)
|
|
988
|
+
if (targetKey) {
|
|
989
|
+
return Object.values(state.remotes).find((item) => normalizeRemoteTargetKey(item.target) === targetKey) || null
|
|
625
990
|
}
|
|
991
|
+
return getPrimaryRemoteRuntimeState(state)
|
|
626
992
|
}
|
|
627
993
|
|
|
628
|
-
|
|
629
|
-
|
|
994
|
+
function defaultRemoteStatus(): OpenClawRemoteDeployStatus {
|
|
995
|
+
const fallback = createRemoteRuntimeState('remote-default', { name: 'Remote OpenClaw' })
|
|
996
|
+
return currentRemoteStatusFromState(fallback, true)
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
export function getOpenClawRemoteDeployStatuses(): OpenClawRemoteDeployStatus[] {
|
|
1000
|
+
const state = getRuntimeState()
|
|
1001
|
+
return sortRemoteRuntimeStates(Object.values(state.remotes))
|
|
1002
|
+
.map((runtime) => currentRemoteStatusFromState(runtime, runtime.id === state.primaryRemoteId))
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export function getOpenClawRemoteDeployCollectionStatus(): OpenClawRemoteDeployCollectionStatus {
|
|
1006
|
+
const state = getRuntimeState()
|
|
1007
|
+
const items = getOpenClawRemoteDeployStatuses()
|
|
1008
|
+
const primaryId = items.find((item) => item.isPrimary)?.id || null
|
|
1009
|
+
state.primaryRemoteId = primaryId
|
|
1010
|
+
return { primaryId, items }
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export function getOpenClawRemoteDeployStatus(remoteId?: string | null): OpenClawRemoteDeployStatus {
|
|
1014
|
+
const state = getRuntimeState()
|
|
1015
|
+
const runtime = findRemoteRuntimeState(state, { remoteId })
|
|
1016
|
+
return runtime
|
|
1017
|
+
? currentRemoteStatusFromState(runtime, runtime.id === state.primaryRemoteId)
|
|
1018
|
+
: defaultRemoteStatus()
|
|
630
1019
|
}
|
|
631
1020
|
|
|
632
1021
|
export function generateOpenClawGatewayToken(): string {
|
|
633
1022
|
return randomBytes(24).toString('base64url')
|
|
634
1023
|
}
|
|
635
1024
|
|
|
1025
|
+
function generateLocalDeployId(): string {
|
|
1026
|
+
return `local-${randomBytes(8).toString('hex')}`
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function generateRemoteDeployId(): string {
|
|
1030
|
+
return `remote-${randomBytes(8).toString('hex')}`
|
|
1031
|
+
}
|
|
1032
|
+
|
|
636
1033
|
export async function startOpenClawLocalDeploy(input?: {
|
|
1034
|
+
localId?: string | null
|
|
1035
|
+
name?: string | null
|
|
637
1036
|
port?: number
|
|
638
1037
|
token?: string | null
|
|
639
|
-
|
|
1038
|
+
makePrimary?: boolean
|
|
1039
|
+
}): Promise<{ local: OpenClawLocalDeployStatus; locals: OpenClawLocalDeployStatus[]; token: string }> {
|
|
640
1040
|
const state = getRuntimeState()
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
1041
|
+
const port = sanitizePort(input?.port, DEFAULT_LOCAL_PORT)
|
|
1042
|
+
const requestedLocalId = typeof input?.localId === 'string' && input.localId.trim()
|
|
1043
|
+
? input.localId.trim()
|
|
1044
|
+
: null
|
|
1045
|
+
const existingById = requestedLocalId ? state.locals[requestedLocalId] || null : null
|
|
1046
|
+
const existingByPort = Object.values(state.locals).find((item) => item.port === port) || null
|
|
1047
|
+
const localId = existingById?.id || existingByPort?.id || requestedLocalId || generateLocalDeployId()
|
|
1048
|
+
const existing = existingById || existingByPort || null
|
|
1049
|
+
const current = existing || createLocalRuntimeState(localId, {
|
|
1050
|
+
port,
|
|
1051
|
+
name: typeof input?.name === 'string' && input.name.trim() ? input.name.trim() : `Local OpenClaw ${port}`,
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
if (existingByPort && existingByPort.id !== localId && existingByPort.processId) {
|
|
1055
|
+
const conflictProcess = getManagedProcess(existingByPort.processId)
|
|
1056
|
+
if (conflictProcess?.status === 'running') {
|
|
1057
|
+
killManagedProcess(existingByPort.processId)
|
|
1058
|
+
}
|
|
1059
|
+
removeManagedProcess(existingByPort.processId)
|
|
1060
|
+
existingByPort.processId = null
|
|
1061
|
+
existingByPort.startedAt = null
|
|
1062
|
+
existingByPort.updatedAt = Date.now()
|
|
1063
|
+
state.locals[existingByPort.id] = existingByPort
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (current.processId) {
|
|
1067
|
+
const currentProcess = getManagedProcess(current.processId)
|
|
1068
|
+
if (currentProcess?.status === 'running') {
|
|
1069
|
+
killManagedProcess(current.processId)
|
|
1070
|
+
}
|
|
644
1071
|
removeManagedProcess(current.processId)
|
|
645
1072
|
}
|
|
646
1073
|
|
|
647
|
-
const port = sanitizePort(input?.port, DEFAULT_LOCAL_PORT)
|
|
648
1074
|
const token = normalizeToken(input?.token) || generateOpenClawGatewayToken()
|
|
649
1075
|
const endpoint = normalizeOpenClawEndpoint(`http://127.0.0.1:${port}`)
|
|
650
1076
|
const wsUrl = deriveOpenClawWsUrl(endpoint)
|
|
1077
|
+
await ensureManagedLocalRuntimeFiles(localId)
|
|
651
1078
|
const binary = resolveBundledOpenClawBinary()
|
|
652
1079
|
const args = [
|
|
653
1080
|
binary,
|
|
@@ -656,7 +1083,7 @@ export async function startOpenClawLocalDeploy(input?: {
|
|
|
656
1083
|
'--allow-unconfigured',
|
|
657
1084
|
'--force',
|
|
658
1085
|
'--bind',
|
|
659
|
-
'
|
|
1086
|
+
'custom',
|
|
660
1087
|
'--port',
|
|
661
1088
|
String(port),
|
|
662
1089
|
'--auth',
|
|
@@ -669,45 +1096,62 @@ export async function startOpenClawLocalDeploy(input?: {
|
|
|
669
1096
|
const result = await startManagedProcess({
|
|
670
1097
|
command: args.map(shellEscape).join(' '),
|
|
671
1098
|
cwd: process.cwd(),
|
|
1099
|
+
env: buildLocalProcessEnv(localId),
|
|
672
1100
|
background: true,
|
|
673
1101
|
timeoutMs: 24 * 60 * 60_000,
|
|
674
1102
|
})
|
|
675
1103
|
|
|
676
1104
|
if (result.status !== 'running') {
|
|
677
1105
|
const message = result.output || result.tail || 'OpenClaw failed to start.'
|
|
678
|
-
state.
|
|
1106
|
+
state.locals[localId] = createLocalRuntimeState(localId, {
|
|
1107
|
+
...current,
|
|
1108
|
+
name: typeof input?.name === 'string' && input.name.trim() ? input.name.trim() : current.name,
|
|
679
1109
|
processId: null,
|
|
680
1110
|
port,
|
|
681
1111
|
endpoint,
|
|
682
1112
|
wsUrl,
|
|
683
1113
|
token,
|
|
684
1114
|
startedAt: null,
|
|
1115
|
+
updatedAt: Date.now(),
|
|
685
1116
|
lastError: message,
|
|
686
|
-
}
|
|
1117
|
+
})
|
|
1118
|
+
if (input?.makePrimary !== false || !state.primaryLocalId) state.primaryLocalId = localId
|
|
687
1119
|
throw new Error(message)
|
|
688
1120
|
}
|
|
689
1121
|
|
|
690
|
-
state.
|
|
1122
|
+
state.locals[localId] = createLocalRuntimeState(localId, {
|
|
1123
|
+
...current,
|
|
1124
|
+
name: typeof input?.name === 'string' && input.name.trim() ? input.name.trim() : current.name,
|
|
691
1125
|
processId: result.processId,
|
|
692
1126
|
port,
|
|
693
1127
|
endpoint,
|
|
694
1128
|
wsUrl,
|
|
695
1129
|
token,
|
|
696
1130
|
startedAt: Date.now(),
|
|
1131
|
+
updatedAt: Date.now(),
|
|
697
1132
|
lastError: null,
|
|
698
|
-
}
|
|
1133
|
+
})
|
|
1134
|
+
if (input?.makePrimary !== false || !state.primaryLocalId) state.primaryLocalId = localId
|
|
699
1135
|
|
|
700
1136
|
await waitForLocalRuntime(result.processId)
|
|
701
1137
|
|
|
1138
|
+
const local = getOpenClawLocalDeployStatus(localId)
|
|
702
1139
|
return {
|
|
703
|
-
local
|
|
1140
|
+
local,
|
|
1141
|
+
locals: getOpenClawLocalDeployStatuses(),
|
|
704
1142
|
token,
|
|
705
1143
|
}
|
|
706
1144
|
}
|
|
707
1145
|
|
|
708
|
-
export function stopOpenClawLocalDeploy(): OpenClawLocalDeployStatus {
|
|
1146
|
+
export function stopOpenClawLocalDeploy(localId?: string | null): { local: OpenClawLocalDeployStatus; locals: OpenClawLocalDeployStatus[] } {
|
|
709
1147
|
const state = getRuntimeState()
|
|
710
|
-
const
|
|
1148
|
+
const runtime = findLocalRuntimeState(state, { localId })
|
|
1149
|
+
if (!runtime) {
|
|
1150
|
+
const local = defaultLocalStatus()
|
|
1151
|
+
return { local, locals: getOpenClawLocalDeployStatuses() }
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const processId = runtime.processId
|
|
711
1155
|
if (processId) {
|
|
712
1156
|
const process = getManagedProcess(processId)
|
|
713
1157
|
if (process?.status === 'running') {
|
|
@@ -715,19 +1159,39 @@ export function stopOpenClawLocalDeploy(): OpenClawLocalDeployStatus {
|
|
|
715
1159
|
}
|
|
716
1160
|
removeManagedProcess(processId)
|
|
717
1161
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1162
|
+
runtime.processId = null
|
|
1163
|
+
runtime.startedAt = null
|
|
1164
|
+
runtime.updatedAt = Date.now()
|
|
1165
|
+
state.locals[runtime.id] = runtime
|
|
1166
|
+
if (state.primaryLocalId === runtime.id) {
|
|
1167
|
+
state.primaryLocalId = sortLocalRuntimeStates(Object.values(state.locals).filter((item) => item.id !== runtime.id && item.processId))
|
|
1168
|
+
[0]?.id
|
|
1169
|
+
|| runtime.id
|
|
1170
|
+
}
|
|
1171
|
+
return {
|
|
1172
|
+
local: getOpenClawLocalDeployStatus(runtime.id),
|
|
1173
|
+
locals: getOpenClawLocalDeployStatuses(),
|
|
1174
|
+
}
|
|
721
1175
|
}
|
|
722
1176
|
|
|
723
1177
|
export async function restartOpenClawLocalDeploy(input?: {
|
|
1178
|
+
localId?: string | null
|
|
1179
|
+
name?: string | null
|
|
724
1180
|
port?: number
|
|
725
1181
|
token?: string | null
|
|
726
|
-
|
|
727
|
-
|
|
1182
|
+
makePrimary?: boolean
|
|
1183
|
+
}): Promise<{ local: OpenClawLocalDeployStatus; locals: OpenClawLocalDeployStatus[]; token: string }> {
|
|
1184
|
+
const state = getRuntimeState()
|
|
1185
|
+
const current = findLocalRuntimeState(state, {
|
|
1186
|
+
localId: input?.localId ?? null,
|
|
1187
|
+
port: typeof input?.port === 'number' ? input.port : null,
|
|
1188
|
+
})
|
|
728
1189
|
return startOpenClawLocalDeploy({
|
|
729
|
-
|
|
730
|
-
|
|
1190
|
+
localId: input?.localId ?? current?.id ?? null,
|
|
1191
|
+
name: input?.name ?? current?.name ?? null,
|
|
1192
|
+
port: input?.port ?? current?.port ?? DEFAULT_LOCAL_PORT,
|
|
1193
|
+
token: input?.token ?? current?.token ?? null,
|
|
1194
|
+
makePrimary: input?.makePrimary,
|
|
731
1195
|
})
|
|
732
1196
|
}
|
|
733
1197
|
|
|
@@ -1276,6 +1740,8 @@ export async function verifyOpenClawDeployment(input?: {
|
|
|
1276
1740
|
}
|
|
1277
1741
|
|
|
1278
1742
|
export async function deployOpenClawBundleOverSsh(input?: {
|
|
1743
|
+
remoteId?: string | null
|
|
1744
|
+
name?: string | null
|
|
1279
1745
|
template?: OpenClawRemoteDeployTemplate
|
|
1280
1746
|
target?: string | null
|
|
1281
1747
|
token?: string | null
|
|
@@ -1285,6 +1751,7 @@ export async function deployOpenClawBundleOverSsh(input?: {
|
|
|
1285
1751
|
useCase?: OpenClawUseCaseTemplate
|
|
1286
1752
|
exposure?: OpenClawExposurePreset
|
|
1287
1753
|
ssh?: Partial<OpenClawSshConfig> | null
|
|
1754
|
+
makePrimary?: boolean
|
|
1288
1755
|
}): Promise<OpenClawRemoteCommandResult> {
|
|
1289
1756
|
const sshConfig = sanitizeSshConfig(input?.ssh)
|
|
1290
1757
|
if (!sshConfig) throw new Error('SSH host is required for remote deploy.')
|
|
@@ -1309,10 +1776,13 @@ export async function deployOpenClawBundleOverSsh(input?: {
|
|
|
1309
1776
|
)
|
|
1310
1777
|
const command = `${mkdirCommand} && ${scpCommand} && ${bootstrapCommand}`
|
|
1311
1778
|
const result = await startRemoteCommand({
|
|
1779
|
+
remoteId: input?.remoteId ?? null,
|
|
1780
|
+
name: input?.name ?? bundle.title,
|
|
1312
1781
|
action: 'ssh-deploy',
|
|
1313
1782
|
target: sshConfig.host,
|
|
1314
1783
|
command,
|
|
1315
1784
|
summary: `Deploying OpenClaw to ${sshConfig.host} over SSH.`,
|
|
1785
|
+
makePrimary: input?.makePrimary,
|
|
1316
1786
|
})
|
|
1317
1787
|
return {
|
|
1318
1788
|
...result,
|
|
@@ -1324,11 +1794,14 @@ export async function deployOpenClawBundleOverSsh(input?: {
|
|
|
1324
1794
|
export const deployOpenClawOverSsh = deployOpenClawBundleOverSsh
|
|
1325
1795
|
|
|
1326
1796
|
export async function runOpenClawRemoteLifecycleAction(input?: {
|
|
1797
|
+
remoteId?: string | null
|
|
1798
|
+
name?: string | null
|
|
1327
1799
|
action: 'start' | 'stop' | 'restart' | 'upgrade' | 'backup' | 'restore' | 'rotate-token'
|
|
1328
1800
|
ssh?: Partial<OpenClawSshConfig> | null
|
|
1329
1801
|
image?: string | null
|
|
1330
1802
|
token?: string | null
|
|
1331
1803
|
backupPath?: string | null
|
|
1804
|
+
makePrimary?: boolean
|
|
1332
1805
|
}): Promise<OpenClawRemoteCommandResult> {
|
|
1333
1806
|
const sshConfig = sanitizeSshConfig(input?.ssh)
|
|
1334
1807
|
if (!sshConfig) throw new Error('SSH host is required for remote lifecycle actions.')
|
|
@@ -1369,11 +1842,14 @@ export async function runOpenClawRemoteLifecycleAction(input?: {
|
|
|
1369
1842
|
|
|
1370
1843
|
const command = buildSshInvocation(sshConfig, remoteCommand)
|
|
1371
1844
|
const result = await startRemoteCommand({
|
|
1845
|
+
remoteId: input?.remoteId ?? null,
|
|
1846
|
+
name: input?.name ?? deriveRemoteDeploymentName(sshConfig.host),
|
|
1372
1847
|
action,
|
|
1373
1848
|
target: sshConfig.host,
|
|
1374
1849
|
command,
|
|
1375
1850
|
summary,
|
|
1376
1851
|
backupPath,
|
|
1852
|
+
makePrimary: input?.makePrimary,
|
|
1377
1853
|
})
|
|
1378
1854
|
return {
|
|
1379
1855
|
...result,
|